Added stop command.

Cleaned up output; made debugging option work properly.
Track basic robot state reports.
Stop command waits for robot to stop.
 Charge command  waits for robot to dock.
This commit is contained in:
William Pietri
2017-11-03 21:02:20 -07:00
parent 552c8c5fdf
commit d88fbb35b2
3 changed files with 77 additions and 17 deletions
+2 -5
View File
@@ -37,9 +37,6 @@ it's a vacuum.
## To Do ## 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 * add probabilistic cleaning options
* implement more commands
* add a status commmand
+66 -12
View File
@@ -6,7 +6,7 @@ import time
from threading import Event from threading import Event
import click import click
from sleekxmpp import ClientXMPP from sleekxmpp import ClientXMPP, Callback, MatchXPath
from sleekxmpp.xmlstream import ET from sleekxmpp.xmlstream import ET
@@ -22,16 +22,41 @@ class VacBot(ClientXMPP):
self.add_event_handler("session_start", self.session_start) self.add_event_handler("session_start", self.session_start)
self.ready_flag = Event() self.ready_flag = Event()
self.clean_status = None
self.charge_status = None
def wait_until_ready(self): def wait_until_ready(self):
self.ready_flag.wait() self.ready_flag.wait()
def session_start(self, event): def session_start(self, event):
print("----------------- starting session ----------------") logging.debug("----------------- starting session ----------------")
self.ready_flag.set() self.ready_flag.set()
def send_command(self, action): self.register_handler(Callback('clean report',
c = self.wrap_command(action.to_xml()) MatchXPath('{jabber:client}iq/{com:ctl}query/{com:ctl}ctl[@td="CleanReport"]'),
self.handle_clean_report))
self.register_handler(Callback('clean report',
MatchXPath('{jabber:client}iq/{com:ctl}query/{com:ctl}ctl[@td="ChargeState"]'),
self.handle_charge_report))
def handle_clean_report(self, iq):
self.clean_status = iq.find('{com:ctl}query/{com:ctl}ctl/{com:ctl}clean').get('type')
logging.debug("*** clean_status =" + self.clean_status)
def handle_charge_report(self, iq):
report = iq.find('{com:ctl}query/{com:ctl}ctl/{com:ctl}charge').get('type')
if report.lower() == 'going':
self.charge_status = 'returning'
elif report.lower() == 'slotcharging':
self.charge_status = 'charging'
elif report.lower() == 'idle':
self.charge_status = 'idle'
else:
logging.warning("Unknown charging status '" + report + "'")
logging.debug("*** charge_status =" + self.charge_status)
def send_command(self, xml):
c = self.wrap_command(xml)
c.send() c.send()
def wrap_command(self, ctl): def wrap_command(self, ctl):
@@ -45,17 +70,13 @@ class VacBot(ClientXMPP):
def connect_and_wait_until_ready(self): def connect_and_wait_until_ready(self):
self.connect(('47.88.66.164', '5223')) # TODO: change to domain name self.connect(('47.88.66.164', '5223')) # TODO: change to domain name
click.echo("starting")
self.process() self.process()
click.echo("done with process")
self.wait_until_ready() self.wait_until_ready()
def run(self, action): def run(self, action):
click.echo("running " + str(action)) click.echo("performing " + str(action))
self.send_command(action) self.send_command(action.to_xml())
if action.wait: action.wait_for_completion(self)
click.echo("sleeping for " + str(action.wait) + "s")
time.sleep(action.wait)
class VacBotCommand(): class VacBotCommand():
@@ -65,12 +86,20 @@ class VacBotCommand():
self.wait = wait self.wait = wait
self.terminal = terminal self.terminal = terminal
def wait_for_completion(self, bot):
if self.wait:
click.echo("waiting in " + self.name + " for " + str(self.wait) + "s")
time.sleep(self.wait)
def to_xml(self): def to_xml(self):
clean = ET.Element(self.name, self.args) clean = ET.Element(self.name, self.args)
ctl = ET.Element('ctl', {'td': self.name.capitalize()}) ctl = ET.Element('ctl', {'td': self.name.capitalize()})
ctl.append(clean) ctl.append(clean)
return ctl return ctl
def __str__(self, *args, **kwargs):
return self.name + " command"
class Clean(VacBotCommand): class Clean(VacBotCommand):
def __init__(self, wait): def __init__(self, wait):
@@ -81,6 +110,24 @@ class Charge(VacBotCommand):
def __init__(self): def __init__(self):
super().__init__('charge', {'type': 'go'}, terminal=True) super().__init__('charge', {'type': 'go'}, terminal=True)
def wait_for_completion(self, bot):
logging.debug("waiting in " + self.name)
while bot.charge_status not in ['charging']:
time.sleep(0.5)
logging.debug("done waiting in " + self.name)
click.echo("docked")
class Stop(VacBotCommand):
def __init__(self):
super().__init__('clean', {'type': 'stop', 'speed': 'standard'}, terminal=True)
def wait_for_completion(self, bot):
logging.debug("waiting in " + self.name)
while bot.clean_status not in ['stop']:
time.sleep(0.5)
logging.debug("done waiting in " + self.name)
def read_config(filename): def read_config(filename):
parser = configparser.ConfigParser() parser = configparser.ConfigParser()
@@ -93,7 +140,8 @@ def read_config(filename):
@click.option('--charge/--no-charge', default=True, help='Return to charge after running. Defaults to yes.') @click.option('--charge/--no-charge', default=True, help='Return to charge after running. Defaults to yes.')
@click.option('--debug/--no-debug', default=False) @click.option('--debug/--no-debug', default=False)
def cli(charge, debug): def cli(charge, debug):
logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s') level = logging.DEBUG if debug else logging.ERROR
logging.basicConfig(level=level, format='%(levelname)-8s %(message)s')
@cli.command(help='cleans for the specified number of minutes') @cli.command(help='cleans for the specified number of minutes')
@@ -107,6 +155,11 @@ def charge():
return Charge() return Charge()
@cli.command(help='stops the robot in its current position')
def stop():
return Stop()
@cli.resultcallback() @cli.resultcallback()
def run(actions, charge, debug): def run(actions, charge, debug):
config = read_config(os.path.expanduser('~/.config/sucks.conf')) config = read_config(os.path.expanduser('~/.config/sucks.conf'))
@@ -118,6 +171,7 @@ def run(actions, charge, debug):
if charge and not actions[-1].terminal: if charge and not actions[-1].terminal:
vacbot.run(Charge()) vacbot.run(Charge())
vacbot.disconnect(wait=True) vacbot.disconnect(wait=True)
click.echo("done")
if __name__ == '__main__': if __name__ == '__main__':
+9
View File
@@ -7,6 +7,7 @@ from sucks import *
def test_clean_command(): def test_clean_command():
c = Clean(10) c = Clean(10)
assert_equals(c.terminal, False)
assert_equals(c.wait, 10) assert_equals(c.wait, 10)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Clean"><clean speed="standard" type="auto" /></ctl>') # protocol has attribs in other order b'<ctl td="Clean"><clean speed="standard" type="auto" /></ctl>') # protocol has attribs in other order
@@ -14,6 +15,14 @@ def test_clean_command():
def test_charge_command(): def test_charge_command():
c = Charge() c = Charge()
assert_equals(c.terminal, True)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Charge"><charge type="go" /></ctl>') b'<ctl td="Charge"><charge type="go" /></ctl>')
def test_stop_command():
c = Stop()
assert_equals(c.terminal, True)
assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Clean"><clean speed="standard" type="stop" /></ctl>')