From d88fbb35b24ecc510d4004b03378825b4644a326 Mon Sep 17 00:00:00 2001 From: William Pietri Date: Fri, 3 Nov 2017 21:02:20 -0700 Subject: [PATCH] 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. --- README.md | 7 ++--- sucks.py | 78 +++++++++++++++++++++++++++++++++++++++++++-------- test_sucks.py | 9 ++++++ 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 14bf462..b9dee92 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,6 @@ it's a vacuum. ## 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 +* implement more commands +* add a status commmand diff --git a/sucks.py b/sucks.py index 6d49153..3247979 100644 --- a/sucks.py +++ b/sucks.py @@ -6,7 +6,7 @@ import time from threading import Event import click -from sleekxmpp import ClientXMPP +from sleekxmpp import ClientXMPP, Callback, MatchXPath from sleekxmpp.xmlstream import ET @@ -22,16 +22,41 @@ class VacBot(ClientXMPP): self.add_event_handler("session_start", self.session_start) self.ready_flag = Event() + self.clean_status = None + self.charge_status = None def wait_until_ready(self): self.ready_flag.wait() def session_start(self, event): - print("----------------- starting session ----------------") + logging.debug("----------------- starting session ----------------") self.ready_flag.set() - def send_command(self, action): - c = self.wrap_command(action.to_xml()) + self.register_handler(Callback('clean report', + 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() def wrap_command(self, ctl): @@ -45,17 +70,13 @@ class VacBot(ClientXMPP): def connect_and_wait_until_ready(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 run(self, action): - click.echo("running " + str(action)) - self.send_command(action) - if action.wait: - click.echo("sleeping for " + str(action.wait) + "s") - time.sleep(action.wait) + click.echo("performing " + str(action)) + self.send_command(action.to_xml()) + action.wait_for_completion(self) class VacBotCommand(): @@ -65,12 +86,20 @@ class VacBotCommand(): self.wait = wait 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): clean = ET.Element(self.name, self.args) ctl = ET.Element('ctl', {'td': self.name.capitalize()}) ctl.append(clean) return ctl + def __str__(self, *args, **kwargs): + return self.name + " command" + class Clean(VacBotCommand): def __init__(self, wait): @@ -81,6 +110,24 @@ class Charge(VacBotCommand): def __init__(self): 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): 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('--debug/--no-debug', default=False) 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') @@ -107,6 +155,11 @@ def charge(): return Charge() +@cli.command(help='stops the robot in its current position') +def stop(): + return Stop() + + @cli.resultcallback() def run(actions, charge, debug): 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: vacbot.run(Charge()) vacbot.disconnect(wait=True) + click.echo("done") if __name__ == '__main__': diff --git a/test_sucks.py b/test_sucks.py index 7cd10ab..6ec5f8a 100644 --- a/test_sucks.py +++ b/test_sucks.py @@ -7,6 +7,7 @@ from sucks import * def test_clean_command(): c = Clean(10) + assert_equals(c.terminal, False) assert_equals(c.wait, 10) assert_equals(ElementTree.tostring(c.to_xml()), b'') # protocol has attribs in other order @@ -14,6 +15,14 @@ def test_clean_command(): def test_charge_command(): c = Charge() + assert_equals(c.terminal, True) assert_equals(ElementTree.tostring(c.to_xml()), b'') + +def test_stop_command(): + c = Stop() + assert_equals(c.terminal, True) + assert_equals(ElementTree.tostring(c.to_xml()), + b'') +