diff --git a/README.md b/README.md index e034336..740ff63 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,15 @@ vacuum=[robot id]@126.ecorobot.net I got these values by using xmppeek to do a man-in-the-middle attack on the android app. I suspect that the Android app re-keys the connection on a regular basis, as the secret was changing regularly up until I -cleared the Android app's data from my phone. It should be possible to -give this tool a command something like "login" that would create the config -file automatically. +cleared the Android app's data from my phone. + +If you're curious about the protocol, I have [a very rough +doc](protocol.md) started. I'll happily accept pull requests for it. + +Why the project name? Well, a) it's ridiculous that I needed to MITM +my own vacuum. This is not the future I signed up for. There should +be a nice, tidy RESTful API. That would be easy enough to make. And b), +it's a vacuum. ## To Do diff --git a/log_clean.py b/log_clean.py new file mode 100644 index 0000000..7230b79 --- /dev/null +++ b/log_clean.py @@ -0,0 +1,63 @@ +import sys +import re + +# a script to take an xmpppeek log of a Ecovacs app session with a Deebot N79 and strip out some of the nonsense, +# including any private identifiers + +source_ip = None +userid = None +resourceid = None +robotid = None +auth_glob = None + +for line in sys.stdin: + # remove the garbage + line = line.rstrip() + line = re.sub("\[\\d{4}-\\d{2}-\\d{2} ", '', line) + line = re.sub("\.\\d{6}-\\d{2}:\\d{2}\] \[", ' ', line) + line = re.sub("]$", ' ', line) + line = re.sub("\(([SC])2[SC]\) [.0-9]+:\\d+ -> [.0-9]+:\d+\]", '\\1', line) + line = re.sub("\}\}\}", '', line) + line = re.sub("\{\{\{", '', line) + + # find the private bits and remove them + if not source_ip: + match = re.search('Client connect from ([.0-9]+)', line) + if match: + source_ip = match.group(1) + if not userid: + match = re.search('(20\d{6}[0-9a-f]{13})@ecouser.net/([0-9a-f]{8})', line) + if match: + userid = match.group(1) + resourceid = match.group(2) + if not robotid: + match = re.search('(E\d{8,})@126.ecorobot.net/atom', line) + if match: + robotid = match.group(1) + if not auth_glob: + match = re.search('([-A-Za-z0-9+/=]+)', line) + if match: + auth_glob = match.group(1) + if source_ip: + line = re.sub(source_ip, 'SOURCEIP', line) + if userid: + line = re.sub(userid, 'USERID', line) + if resourceid: + line = re.sub(resourceid, 'RESOURCEID', line) + if robotid: + line = re.sub(robotid, 'ROBOTID', line) + if auth_glob: + line = re.sub(auth_glob, 'AUTHGLOB', line) + + # translate client commmands + + line = re.sub('()', 'id=\\1 command=\\2', line) + + # translate server responses + + line = re.sub('', 'id=\\1 result =empty', line) + line = re.sub('', 'id=\\1 id=\\2 result=\\3', line) + line = re.sub('(', 'id=\\1 response=\\2', line) + + print(line) + diff --git a/protocol.md b/protocol.md index c724690..64b68e9 100644 --- a/protocol.md +++ b/protocol.md @@ -1,3 +1,8 @@ +The core protocol is XMPP. The Android app establishes a connection to an XMPP server and logs in using +a secret that the android app appears to change from time to time. It then sends XMPP IQ commands. It describes +them as queries, but they all contain "ctl" elements that appear to be commands. Here are a couple of full +examples with the private information removed: + A clean command: ``` @@ -8,4 +13,59 @@ A clean command: A charge command: ``` -``` \ No newline at end of file +``` + +Focusing on the core ctl elements, this is a sampling of commands seen on the wire after punching all the app buttons: + +``` + + + + + + + + + + + + + + + + + + + + +``` + +It appears that it adds an extra id when it cares to receive a specific response. This is a little odd in that +the iq blocks already contain ids, but perhaps one is more a server id and the other is used by the robot itself. + +Here are some assorted responses from that session: + +``` + + + + + + + + + + + + + + + + + + + +``` + +I don't totally get the relationship between the duplicate-ish items here, like the various clean reports, +or the charge type differences, but I'll try to come back after rummaging through the logs further.