initial import

This commit is contained in:
William Pietri
2017-10-31 10:55:18 -07:00
commit 20579d92ab
4 changed files with 170 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[dev-packages]
[packages]
sleekxmpp = ">=1.3"
click = ">=6"
+37
View File
@@ -0,0 +1,37 @@
sucks
=====
A simple command-line python script to drive a robot vacuum. Currently
works only with the Ecovacs Deebot N79, as that's what I have.
Right now this code offered more as inspiration than something for other
people to just download and use. But if you'd like to help flesh it out,
send email to my first name at williampietri.com.
If you do try to use it, you'll need to create ~/.config/sucks.conf. It
should look something like this:
```
email=user@example.org
user=2017010101abdef012345
domain=ecouser.net
resource=abcdef01
secret=[long base64 string]
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.
## To Do
* implement common commands
* stop sending back error messages in response to robot updates
* track robot state (e.g., cleaning, stopped)
* use tracked state to be smarter
* log activity to aid in debugging
* add probabilistic cleaning options
+112
View File
@@ -0,0 +1,112 @@
import configparser
import itertools
import logging
import os
import time
from threading import Event
import click
from sleekxmpp import ClientXMPP
from sleekxmpp.xmlstream import ET
class VacBot(ClientXMPP):
def __init__(self, user, domain, resource, secret, vacuum):
ClientXMPP.__init__(self, user + '@' + domain, '0/' + resource + '/' + secret)
self.user = user
self.domain = domain
self.resource = resource
self.vacuum = vacuum
self.credentials['authzid'] = user
self.add_event_handler("session_start", self.session_start)
self.ready_flag = Event()
def wait_until_ready(self):
self.ready_flag.wait()
def session_start(self, event):
print("----------------- starting session ----------------")
self.ready_flag.set()
def clean(self, speed='standard', type='auto'):
self.make_command('clean', {'speed': speed, 'type': type}).send()
def charge(self):
self.make_command('charge', {'type': 'go'}).send()
def make_command(self, name, args):
clean = ET.Element(name, args)
ctl = ET.Element('ctl', {'td': name.capitalize()})
ctl.append(clean)
command = self.wrap_command(ctl)
return command
def wrap_command(self, ctl):
q = self.make_iq_query(xmlns=u'com:ctl', ito=self.vacuum + '/atom',
ifrom=self.user + '@' + self.domain + '/' + self.resource)
q['type'] = 'set'
for child in q.xml:
if child.tag.endswith('query'):
child.append(ctl)
return q
def run(self):
self.connect(('47.88.66.164', '5223')) # TODO: change to domain name
click.echo("starting")
self.process()
click.echo("done with process")
self.wait_until_ready()
def read_config(filename):
parser = configparser.ConfigParser()
with open(filename) as fp:
parser.read_file(itertools.chain(['[global]'], fp), source=filename)
return parser['global']
@click.group(chain=True)
@click.option('--charge/--no-charge', default=True, help='Return to charge after running. Defaults to yes.')
@click.option('--debug/--no-debug', default=False)
def cli(charge, debug):
logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')
@cli.command(help='cleans for the specified number of minutes')
@click.argument('minutes', type=click.FLOAT)
def clean(minutes):
def action(vacbot):
click.echo('cleaning')
vacbot.clean()
time.sleep(minutes * 60)
return action
@cli.command(help='returns to charger')
def charge(vacbot): # TODO convert
click.echo('charging')
vacbot.charge()
time.sleep(20)
@cli.resultcallback()
def run(actions, charge, debug):
config = read_config(os.path.expanduser('~/.config/sucks.conf'))
vacbot = VacBot(config['user'], config['domain'], config['resource'], config['secret'],
config['vacuum'])
vacbot.run()
for action in actions:
click.echo("running " + str(action))
action(vacbot)
if charge:
vacbot.charge()
vacbot.disconnect(wait=True)
if __name__ == '__main__':
click.echo("starting commands")
cli()
click.echo("done with commands")
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd ${DIR}
pipenv run -- python ./sucks.py "$@"