Added a script to clean up log dumps for consumption and cut out some of the noise. Still not perfect, but useful. Also added more to the protocol documenation.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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('<auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">([-A-Za-z0-9+/=]+)</auth>', 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('<iq id="(\d+)" to="ROBOTID@126.ecorobot.net/atom" from="USERID@ecouser.net/RESOURCEID" type="set"><query xmlns="com:ctl">(<ctl .*>)</query></iq>', 'id=\\1 command=\\2', line)
|
||||
|
||||
# translate server responses
|
||||
|
||||
line = re.sub('<iq to="USERID@ecouser.net/RESOURCEID" type="result" id="(\d+)" from="ROBOTID@126.ecorobot.net/atom"/>', 'id=\\1 result =empty', line)
|
||||
line = re.sub('<iq to="USERID@ecouser.net/RESOURCEID" type="set" id="(\d+)" from="ROBOTID@126.ecorobot.net/atom"><query xmlns="com:ctl"><ctl id="(\d+)" ret="([^"]+)"/></query></iq>', 'id=\\1 id=\\2 result=\\3', line)
|
||||
line = re.sub('<iq to="USERID@ecouser.net/RESOURCEID" type="set" id="(\d+)" from="ROBOTID@126.ecorobot.net/atom"><query xmlns="com:ctl">(<ctl .*)</query></iq>', 'id=\\1 response=\\2', line)
|
||||
|
||||
print(line)
|
||||
|
||||
+61
-1
@@ -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:
|
||||
```
|
||||
<iq id="TXID" to="ROBOTID@126.ecorobot.net/atom" from="USERID@ecouser.net/RESOURCEID" type="set"><query xmlns="com:ctl"><ctl td="Charge"><charge type="go"/></ctl></query></iq>
|
||||
```
|
||||
```
|
||||
|
||||
Focusing on the core ctl elements, this is a sampling of commands seen on the wire after punching all the app buttons:
|
||||
|
||||
```
|
||||
<ctl id="12351409" td="PlaySound" sid="0"/>
|
||||
<ctl id="13259797" td="SetTime"><time t="1509622697" tz="-7"/></ctl>
|
||||
<ctl id="30800321" td="GetSched"/>
|
||||
<ctl td="Charge"><charge type="go"/></ctl>
|
||||
<ctl td="Clean"><clean type="auto" speed="standard"/></ctl>
|
||||
<ctl td="Clean"><clean type="border" speed="strong"/></ctl>
|
||||
<ctl td="Clean"><clean type="singleRoom" speed="standard"/></ctl>
|
||||
<ctl td="Clean"><clean type="spot" speed="strong"/></ctl>
|
||||
<ctl td="Clean"><clean type="stop" speed="standard"/></ctl>
|
||||
<ctl td="GetBatteryInfo"/>
|
||||
<ctl td="GetChargeState"/>
|
||||
<ctl td="GetCleanState"/>
|
||||
<ctl td="GetLifeSpan" type="Brush"/>
|
||||
<ctl td="GetLifeSpan" type="DustCaseHeap"/>
|
||||
<ctl td="GetLifeSpan" type="SideBrush"/>
|
||||
<ctl td="Move"><move action="forward"/></ctl>
|
||||
<ctl td="Move"><move action="SpinLeft"/></ctl>
|
||||
<ctl td="Move"><move action="SpinRight"/></ctl>
|
||||
<ctl td="Move"><move action="stop"/></ctl>
|
||||
<ctl td="Move"><move action="TurnAround"/></ctl>
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
<ctl td="BatteryInfo"><battery power="095"/></ctl>
|
||||
<ctl td="ChargeState"><charge type="going"/></ctl>
|
||||
<ctl td="ChargeState"><charge type="Going"/></ctl>
|
||||
<ctl td="ChargeState"><charge type="Idle"/></ctl>
|
||||
<ctl td="ChargeState"><charge type="SlotCharging"/></ctl>
|
||||
<ctl td="CleanReport"><clean type="auto"/></ctl>
|
||||
<ctl td="CleanReport"> <clean type="auto" speed="strong"/> </ctl>
|
||||
<ctl td="CleanReport"><clean type="border"/></ctl>
|
||||
<ctl td="CleanReport"> <clean type="border" speed="strong"/> </ctl>
|
||||
<ctl td="CleanReport"><clean type="singleRoom"/></ctl>
|
||||
<ctl td="CleanReport"> <clean type="singleRoom" speed="strong"/> </ctl>
|
||||
<ctl td="CleanReport"><clean type="spot"/></ctl>
|
||||
<ctl td="CleanReport"> <clean type="spot" speed="strong"/> </ctl>
|
||||
<ctl td="CleanReport"><clean type="stop"/></ctl>
|
||||
<ctl td="LifeSpan" type="Brush" val="099" total="365"/>
|
||||
<ctl td="LifeSpan" type="DustCaseHeap" val="098" total="365"/>
|
||||
<ctl td="LifeSpan" type="SideBrush" val="098" total="365"/>
|
||||
<ctl td="Sched2"/>
|
||||
<ctl td="Sched2" id="30800321"/>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user