From d300df99eb5e24415edb51547be6fd0c79a0b994 Mon Sep 17 00:00:00 2001 From: William Pietri Date: Tue, 19 Dec 2017 16:01:16 -0800 Subject: [PATCH] Moving waiting logic to CLI. --- sucks/__init__.py | 65 ++++++++++---------------------------- sucks/cli.py | 60 +++++++++++++++++++++++++++++++---- tests/test_cli.py | 46 ++------------------------- tests/test_commands.py | 24 +++----------- tests/test_ecovacs_xmpp.py | 2 +- 5 files changed, 78 insertions(+), 119 deletions(-) diff --git a/sucks/__init__.py b/sucks/__init__.py index 938b155..b12c115 100644 --- a/sucks/__init__.py +++ b/sucks/__init__.py @@ -5,7 +5,6 @@ from collections import OrderedDict from threading import Event -import click import requests import stringcase from sleekxmpp import ClientXMPP, Callback, MatchXPath @@ -149,7 +148,6 @@ def _handle_ctl(self, ctl): if hasattr(self, method): getattr(self, method)(ctl) - def _handle_clean_report(self, event): self.clean_status = event['type'] logging.debug("*** clean_status = " + self.clean_status) @@ -186,7 +184,6 @@ def send_command(self, xml): def run(self, action): self.send_command(action.to_xml()) - action.wait_for_completion(self) def disconnect(self, wait=False): self.xmpp.disconnect(wait=wait) @@ -221,7 +218,6 @@ def session_start(self, event): def subscribe_to_ctls(self, function): self.ctl_subscribers.append(function) - def _handle_ctl(self, message): the_good_part = message.get_payload()[0][0] as_dict = self._ctl_to_dict(the_good_part) @@ -272,8 +268,7 @@ def connect_and_wait_until_ready(self): class VacBotCommand: - - CLEAN_MODE ={ + CLEAN_MODE = { 'auto': 'auto', 'edge': 'border', 'spot': 'spot', @@ -303,16 +298,11 @@ class VacBotCommand: 'stop': 'stop' } - def __init__(self, name, args={}, wait=None, terminal=False): + def __init__(self, name, args=None): + if args is None: + args = {} self.name = name self.args = args - self.wait = wait - self.terminal = terminal - - def wait_for_completion(self, bot): - if self.wait: - click.echo("waiting in " + self.command_name() + " for " + str(self.wait) + "s") - time.sleep(self.wait) def to_xml(self): ctl = ET.Element('ctl', {'td': self.name}) @@ -332,51 +322,28 @@ def command_name(self): class Clean(VacBotCommand): - def __init__(self, mode='auto', speed='normal', wait=None, terminal=False): - super().__init__('Clean', {'clean': {'type': self.CLEAN_MODE[mode], 'speed': self.FAN_SPEED[speed]}}, wait=wait, terminal=terminal ) + def __init__(self, mode='auto', speed='normal', terminal=False): + super().__init__('Clean', {'clean': {'type': self.CLEAN_MODE[mode], 'speed': self.FAN_SPEED[speed]}}) class Edge(Clean): - def __init__(self, wait=None, terminal=False): - super().__init__('edge', 'high', wait=wait, terminal=terminal) + def __init__(self): + super().__init__('edge', 'high') class Spot(Clean): - def __init__(self, wait=None, terminal=False): - super().__init__('spot', 'high', wait=wait, terminal=terminal) + def __init__(self): + super().__init__('spot', 'high') class Stop(Clean): - def __init__(self, wait=None, terminal=False): - super().__init__('stop', 'normal', wait=wait, terminal=terminal) - - -class StopAndWaitForCompletion(Stop): - def __init__(self, terminal=False): - super().__init__(terminal=False) - - 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 __init__(self): + super().__init__('stop', 'normal') class Charge(VacBotCommand): - def __init__(self, terminal=False): - super().__init__('Charge', {'charge': {'type': self.CHARGE_MODE['return']}}, terminal=terminal) - - -class ChargeAndWaitForCompletion(Charge): - def __init__(self, terminal=False): - super().__init__(terminal=terminal) - - 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") + def __init__(self): + super().__init__('Charge', {'charge': {'type': self.CHARGE_MODE['return']}}) class Move(VacBotCommand): @@ -401,9 +368,9 @@ def __init__(self): class GetLifeSpan(VacBotCommand): def __init__(self, component): - super().__init__('GetLifeSpan', {'type':self.COMPONENT[component]}) + super().__init__('GetLifeSpan', {'type': self.COMPONENT[component]}) class SetTime(VacBotCommand): def __init__(self, timestamp, timezone): - super().__init__('SetTime', {'time':{'t':timestamp, 'tz':timezone}}) + super().__init__('SetTime', {'time': {'t': timestamp, 'tz': timezone}}) diff --git a/sucks/cli.py b/sucks/cli.py index c943b7f..57acef6 100644 --- a/sucks/cli.py +++ b/sucks/cli.py @@ -5,6 +5,7 @@ import random import re +import click from pycountry_convert import country_alpha2_to_continent_code from sucks import * @@ -39,6 +40,46 @@ def convert(self, value, param, ctx): FREQUENCY = FrequencyParamType() +class BotWait(): + pass + + def wait(self, bot): + raise NotImplementedError() + + +class TimeWait(BotWait): + def __init__(self, seconds): + super().__init__() + self.seconds = seconds + + def wait(self, bot): + click.echo("waiting for " + str(self.seconds) + "s") + time.sleep(self.seconds) + + +class StatusWait(BotWait): + def __init__(self, wait_on, wait_for): + super().__init__() + self.wait_on = wait_on + self.wait_for = wait_for + + def wait(self, bot): + if not hasattr(bot, self.wait_on): + raise ValueError("object " + bot + " does not have method " + self.wait_on) + logging.debug("waiting on " + self.wait_on + " for value " + self.wait_for) + + while getattr(bot, self.wait_on) != self.wait_for: + time.sleep(0.5) + logging.debug("wait complete; " + self.wait_on + " is now " + self.wait_for) + + +class CliAction: + def __init__(self, vac_command, terminal=False, wait=None): + self.vac_command = vac_command + self.terminal = terminal + self.wait = wait + + def config_file(): if platform.system() == 'Windows': return os.path.join(os.getenv('APPDATA'), 'sucks.conf') @@ -125,7 +166,7 @@ def login(email, password, country_code, continent_code): @click.argument('minutes', type=click.FLOAT) def clean(frequency, minutes): if should_run(frequency): - return Clean(wait=minutes * 60) + return CliAction(Clean(), wait=TimeWait(minutes * 60)) @cli.command(help='cleans room edges for the specified number of minutes') @@ -133,24 +174,28 @@ def clean(frequency, minutes): @click.argument('minutes', type=click.FLOAT) def edge(frequency, minutes): if should_run(frequency): - return Edge(wait=minutes * 60) + return CliAction(Edge(), wait=TimeWait(minutes * 60)) @cli.command(help='returns to charger') def charge(): - return ChargeAndWaitForCompletion(terminal=True) + return charge_action() + + +def charge_action(): + return CliAction(Charge(), terminal=True, wait=StatusWait('charge_status', 'charging')) @cli.command(help='stops the robot in its current position') def stop(): - return StopAndWaitForCompletion(terminal=True) + return CliAction(Stop(), terminal=True, wait=StatusWait('clean_status', 'stop')) @cli.resultcallback() def run(actions, debug): actions = list(filter(None.__ne__, actions)) if actions and charge and not actions[-1].terminal: - actions.append(Charge()) + actions.append(charge_action()) if not config_file_exists(): click.echo("Not logged in. Do 'click login' first.") @@ -168,8 +213,9 @@ def run(actions, debug): vacbot.connect_and_wait_until_ready() for action in actions: - click.echo("performing " + str(action)) - vacbot.run(action) + click.echo("performing " + str(action.vac_command)) + vacbot.run(action.vac_command) + action.wait.wait(vacbot) vacbot.disconnect(wait=True) diff --git a/tests/test_cli.py b/tests/test_cli.py index 035e1e3..0ccd861 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,6 @@ import tempfile from unittest.mock import Mock, patch -import requests_mock from nose.tools import * from sucks.cli import * @@ -16,12 +15,14 @@ def test_config_file_name(): def test_write_and_read_config(): - with patch('sucks.cli.config_file', Mock(return_value=os.path.join(tempfile.mkdtemp(), 'some_other_dir', 'sucks.conf'))): + with patch('sucks.cli.config_file', + Mock(return_value=os.path.join(tempfile.mkdtemp(), 'some_other_dir', 'sucks.conf'))): write_config({'a': "ayyy", 'b': 2}) config2 = read_config() assert_equals(config2['a'], 'ayyy') assert_equals(config2['b'], '2') + def test_frequency_param_type(): t = FREQUENCY assert_equals(t.convert('0', None, None), 0) @@ -59,44 +60,3 @@ def test_should_run(): def test_continent_for_country(): assert_equal(continent_for_country('us'), 'na') assert_equal(continent_for_country('fr'), 'eu') - - - -def test_main_api_setup(): - with requests_mock.mock() as m: - r1 = m.get(re.compile('user/login'), - text='{"time": 1511200804243, "data": {"accessToken": "7a375650b0b1efd780029284479c4e41", "uid": "2017102559f0ee63c588d", "username": null, "email": "william-ecovacs@pota.to", "country": "us"}, "code": "0000", "msg": "X"}') - r2 = m.get(re.compile('user/getAuthCode'), - text='{"time": 1511200804607, "data": {"authCode": "5c28dac1ff580210e11292df57e87bef"}, "code": "0000", "msg": "X"}') - r3 = m.post(re.compile('user.do'), - text='{"todo": "result", "token": "jt5O7oDR3gPHdVKCeb8Czx8xw8mDXM6s", "result": "ok", "userId": "2017102559f0ee63c588d", "resource": "f8d99c4d"}') - EcoVacsAPI("long_device_id", "account_id", "password_hash", 'us', 'na') - assert_equals(r1.call_count, 1) - assert_equals(r2.call_count, 1) - assert_equals(r3.call_count, 1) - - -def test_device_lookup(): - api = make_api() - with requests_mock.mock() as m: - device_id = 'E0000001234567890123' - - r = m.post(re.compile('user.do'), - text='{"todo": "result", "devices": [{"did": "%s", "class": "126", "nick": "bob"}], "result": "ok"}' % device_id) - d = api.devices() - assert_equals(r.call_count, 1) - assert_equals(len(d), 1) - vacuum = d[0] - assert_equals(vacuum['did'], device_id) - assert_equals(vacuum['class'], '126') - - -def make_api(): - with requests_mock.mock() as m: - m.get(re.compile('user/login'), - text='{"time": 1511200804243, "data": {"accessToken": "0123456789abcdef0123456789abcdef", "uid": "20170101abcdefabcdefa", "username": null, "email": "username@example.com", "country": "us"}, "code": "0000", "msg": "X"}') - m.get(re.compile('user/getAuthCode'), - text='{"time": 1511200804607, "data": {"authCode": "abcdef01234567890abcdef012345678"}, "code": "0000", "msg": "X"}') - m.post(re.compile('user.do'), - text='{"todo": "result", "token": "base64base64base64base64base64ba", "result": "ok", "userId": "20170101abcdefabcdefa", "resource": "abcdef12"}') - return EcoVacsAPI("long_device_id", "account_id", "password_hash", 'us', 'na') diff --git a/tests/test_commands.py b/tests/test_commands.py index 0d21014..d3fe743 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -28,63 +28,51 @@ def test_custom_command_noargs(): def test_clean_command(): c = Clean() - assert_equals(c.terminal, False) assert_equals(ElementTree.tostring(c.to_xml()), b'') # protocol has attribs in other order - c = Clean('edge', 'high', 10, terminal=True) - assert_equals(c.wait, 10) - assert_equals(c.terminal, True) + c = Clean('edge', 'high') assert_equals(ElementTree.tostring(c.to_xml()), b'') # protocol has attribs in other order def test_edge_command(): - c = Edge(wait=10) - assert_equals(c.terminal, False) - assert_equals(c.wait, 10) + c = Edge() assert_equals(ElementTree.tostring(c.to_xml()), b'') # protocol has attribs in other order def test_spot_command(): - c = Spot(wait=5) - assert_equals(c.terminal, False) - assert_equals(c.wait, 5) + c = Spot() assert_equals(ElementTree.tostring(c.to_xml()), b'') # protocol has attribs in other order def test_charge_command(): - c = Charge(terminal=True) - assert_equals(c.terminal, True) + c = Charge() assert_equals(ElementTree.tostring(c.to_xml()), b'') def test_stop_command(): - c = Stop(terminal=True) - assert_equals(c.terminal, True) + c = Stop() assert_equals(ElementTree.tostring(c.to_xml()), b'') def test_get_clean_state_command(): c = GetCleanState() - assert_equals(c.terminal, False) assert_equals(ElementTree.tostring(c.to_xml()), b'') def test_get_charge_state_command(): c = GetChargeState() - assert_equals(c.terminal, False) assert_equals(ElementTree.tostring(c.to_xml()), b'') def test_get_battery_state_command(): c = GetBatteryState() - assert_equals(c.terminal, False) assert_equals(ElementTree.tostring(c.to_xml()), b'') @@ -92,7 +80,6 @@ def test_get_battery_state_command(): def test_move_command(): c = Move(action='left') - assert_equals(c.terminal, False) assert_equals(ElementTree.tostring(c.to_xml()), b'') c = Move(action='right') @@ -111,7 +98,6 @@ def test_move_command(): def test_get_lifepsan_command(): c = GetLifeSpan('main_brush') - assert_equals(c.terminal, False) assert_equals(ElementTree.tostring(c.to_xml()), b'') c = GetLifeSpan('side_brush') diff --git a/tests/test_ecovacs_xmpp.py b/tests/test_ecovacs_xmpp.py index 63b2d46..ba79d4c 100644 --- a/tests/test_ecovacs_xmpp.py +++ b/tests/test_ecovacs_xmpp.py @@ -10,7 +10,7 @@ def test_wrap_command(): x = make_ecovacs_xmpp() - c = str(x._wrap_command(Clean(wait=1).to_xml(), 'E0000000001234567890@126.ecorobot.net/atom')) + c = str(x._wrap_command(Clean().to_xml(), 'E0000000001234567890@126.ecorobot.net/atom')) assert_true(search(r'from="20170101abcdefabcdefa@ecouser.net/abcdef12"', c)) assert_true(search(r'to="E0000000001234567890@126.ecorobot.net/atom"', c))