Moving waiting logic to CLI.

This commit is contained in:
William Pietri
2017-12-19 16:01:16 -08:00
parent 7748bcd705
commit d300df99eb
5 changed files with 78 additions and 119 deletions
+16 -49
View File
@@ -5,7 +5,6 @@ from base64 import b64decode, b64encode
from collections import OrderedDict from collections import OrderedDict
from threading import Event from threading import Event
import click
import requests import requests
import stringcase import stringcase
from sleekxmpp import ClientXMPP, Callback, MatchXPath from sleekxmpp import ClientXMPP, Callback, MatchXPath
@@ -149,7 +148,6 @@ class VacBot():
if hasattr(self, method): if hasattr(self, method):
getattr(self, method)(ctl) getattr(self, method)(ctl)
def _handle_clean_report(self, event): def _handle_clean_report(self, event):
self.clean_status = event['type'] self.clean_status = event['type']
logging.debug("*** clean_status = " + self.clean_status) logging.debug("*** clean_status = " + self.clean_status)
@@ -186,7 +184,6 @@ class VacBot():
def run(self, action): def run(self, action):
self.send_command(action.to_xml()) self.send_command(action.to_xml())
action.wait_for_completion(self)
def disconnect(self, wait=False): def disconnect(self, wait=False):
self.xmpp.disconnect(wait=wait) self.xmpp.disconnect(wait=wait)
@@ -221,7 +218,6 @@ class EcoVacsXMPP(ClientXMPP):
def subscribe_to_ctls(self, function): def subscribe_to_ctls(self, function):
self.ctl_subscribers.append(function) self.ctl_subscribers.append(function)
def _handle_ctl(self, message): def _handle_ctl(self, message):
the_good_part = message.get_payload()[0][0] the_good_part = message.get_payload()[0][0]
as_dict = self._ctl_to_dict(the_good_part) as_dict = self._ctl_to_dict(the_good_part)
@@ -272,8 +268,7 @@ class EcoVacsXMPP(ClientXMPP):
class VacBotCommand: class VacBotCommand:
CLEAN_MODE = {
CLEAN_MODE ={
'auto': 'auto', 'auto': 'auto',
'edge': 'border', 'edge': 'border',
'spot': 'spot', 'spot': 'spot',
@@ -303,16 +298,11 @@ class VacBotCommand:
'stop': 'stop' '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.name = name
self.args = args 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): def to_xml(self):
ctl = ET.Element('ctl', {'td': self.name}) ctl = ET.Element('ctl', {'td': self.name})
@@ -332,51 +322,28 @@ class VacBotCommand:
class Clean(VacBotCommand): class Clean(VacBotCommand):
def __init__(self, mode='auto', speed='normal', wait=None, terminal=False): def __init__(self, mode='auto', speed='normal', terminal=False):
super().__init__('Clean', {'clean': {'type': self.CLEAN_MODE[mode], 'speed': self.FAN_SPEED[speed]}}, wait=wait, terminal=terminal ) super().__init__('Clean', {'clean': {'type': self.CLEAN_MODE[mode], 'speed': self.FAN_SPEED[speed]}})
class Edge(Clean): class Edge(Clean):
def __init__(self, wait=None, terminal=False): def __init__(self):
super().__init__('edge', 'high', wait=wait, terminal=terminal) super().__init__('edge', 'high')
class Spot(Clean): class Spot(Clean):
def __init__(self, wait=None, terminal=False): def __init__(self):
super().__init__('spot', 'high', wait=wait, terminal=terminal) super().__init__('spot', 'high')
class Stop(Clean): class Stop(Clean):
def __init__(self, wait=None, terminal=False): def __init__(self):
super().__init__('stop', 'normal', wait=wait, terminal=terminal) super().__init__('stop', 'normal')
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)
class Charge(VacBotCommand): class Charge(VacBotCommand):
def __init__(self, terminal=False): def __init__(self):
super().__init__('Charge', {'charge': {'type': self.CHARGE_MODE['return']}}, terminal=terminal) super().__init__('Charge', {'charge': {'type': self.CHARGE_MODE['return']}})
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")
class Move(VacBotCommand): class Move(VacBotCommand):
@@ -401,9 +368,9 @@ class GetBatteryState(VacBotCommand):
class GetLifeSpan(VacBotCommand): class GetLifeSpan(VacBotCommand):
def __init__(self, component): def __init__(self, component):
super().__init__('GetLifeSpan', {'type':self.COMPONENT[component]}) super().__init__('GetLifeSpan', {'type': self.COMPONENT[component]})
class SetTime(VacBotCommand): class SetTime(VacBotCommand):
def __init__(self, timestamp, timezone): def __init__(self, timestamp, timezone):
super().__init__('SetTime', {'time':{'t':timestamp, 'tz':timezone}}) super().__init__('SetTime', {'time': {'t': timestamp, 'tz': timezone}})
+53 -7
View File
@@ -5,6 +5,7 @@ import platform
import random import random
import re import re
import click
from pycountry_convert import country_alpha2_to_continent_code from pycountry_convert import country_alpha2_to_continent_code
from sucks import * from sucks import *
@@ -39,6 +40,46 @@ class FrequencyParamType(click.ParamType):
FREQUENCY = FrequencyParamType() 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(): def config_file():
if platform.system() == 'Windows': if platform.system() == 'Windows':
return os.path.join(os.getenv('APPDATA'), 'sucks.conf') 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) @click.argument('minutes', type=click.FLOAT)
def clean(frequency, minutes): def clean(frequency, minutes):
if should_run(frequency): 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') @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) @click.argument('minutes', type=click.FLOAT)
def edge(frequency, minutes): def edge(frequency, minutes):
if should_run(frequency): if should_run(frequency):
return Edge(wait=minutes * 60) return CliAction(Edge(), wait=TimeWait(minutes * 60))
@cli.command(help='returns to charger') @cli.command(help='returns to charger')
def charge(): 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') @cli.command(help='stops the robot in its current position')
def stop(): def stop():
return StopAndWaitForCompletion(terminal=True) return CliAction(Stop(), terminal=True, wait=StatusWait('clean_status', 'stop'))
@cli.resultcallback() @cli.resultcallback()
def run(actions, debug): def run(actions, debug):
actions = list(filter(None.__ne__, actions)) actions = list(filter(None.__ne__, actions))
if actions and charge and not actions[-1].terminal: if actions and charge and not actions[-1].terminal:
actions.append(Charge()) actions.append(charge_action())
if not config_file_exists(): if not config_file_exists():
click.echo("Not logged in. Do 'click login' first.") click.echo("Not logged in. Do 'click login' first.")
@@ -168,8 +213,9 @@ def run(actions, debug):
vacbot.connect_and_wait_until_ready() vacbot.connect_and_wait_until_ready()
for action in actions: for action in actions:
click.echo("performing " + str(action)) click.echo("performing " + str(action.vac_command))
vacbot.run(action) vacbot.run(action.vac_command)
action.wait.wait(vacbot)
vacbot.disconnect(wait=True) vacbot.disconnect(wait=True)
+3 -43
View File
@@ -1,7 +1,6 @@
import tempfile import tempfile
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import requests_mock
from nose.tools import * from nose.tools import *
from sucks.cli import * from sucks.cli import *
@@ -16,12 +15,14 @@ def test_config_file_name():
def test_write_and_read_config(): 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}) write_config({'a': "ayyy", 'b': 2})
config2 = read_config() config2 = read_config()
assert_equals(config2['a'], 'ayyy') assert_equals(config2['a'], 'ayyy')
assert_equals(config2['b'], '2') assert_equals(config2['b'], '2')
def test_frequency_param_type(): def test_frequency_param_type():
t = FREQUENCY t = FREQUENCY
assert_equals(t.convert('0', None, None), 0) assert_equals(t.convert('0', None, None), 0)
@@ -59,44 +60,3 @@ def test_should_run():
def test_continent_for_country(): def test_continent_for_country():
assert_equal(continent_for_country('us'), 'na') assert_equal(continent_for_country('us'), 'na')
assert_equal(continent_for_country('fr'), 'eu') 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')
+5 -19
View File
@@ -28,63 +28,51 @@ def test_custom_command_noargs():
def test_clean_command(): def test_clean_command():
c = Clean() c = Clean()
assert_equals(c.terminal, False)
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
c = Clean('edge', 'high', 10, terminal=True) c = Clean('edge', 'high')
assert_equals(c.wait, 10)
assert_equals(c.terminal, True)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Clean"><clean speed="strong" type="border" /></ctl>') # protocol has attribs in other order b'<ctl td="Clean"><clean speed="strong" type="border" /></ctl>') # protocol has attribs in other order
def test_edge_command(): def test_edge_command():
c = Edge(wait=10) c = Edge()
assert_equals(c.terminal, False)
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="strong" type="border" /></ctl>') # protocol has attribs in other order b'<ctl td="Clean"><clean speed="strong" type="border" /></ctl>') # protocol has attribs in other order
def test_spot_command(): def test_spot_command():
c = Spot(wait=5) c = Spot()
assert_equals(c.terminal, False)
assert_equals(c.wait, 5)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Clean"><clean speed="strong" type="spot" /></ctl>') # protocol has attribs in other order b'<ctl td="Clean"><clean speed="strong" type="spot" /></ctl>') # protocol has attribs in other order
def test_charge_command(): def test_charge_command():
c = Charge(terminal=True) 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(): def test_stop_command():
c = Stop(terminal=True) c = Stop()
assert_equals(c.terminal, True)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Clean"><clean speed="standard" type="stop" /></ctl>') b'<ctl td="Clean"><clean speed="standard" type="stop" /></ctl>')
def test_get_clean_state_command(): def test_get_clean_state_command():
c = GetCleanState() c = GetCleanState()
assert_equals(c.terminal, False)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="GetCleanState" />') b'<ctl td="GetCleanState" />')
def test_get_charge_state_command(): def test_get_charge_state_command():
c = GetChargeState() c = GetChargeState()
assert_equals(c.terminal, False)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="GetChargeState" />') b'<ctl td="GetChargeState" />')
def test_get_battery_state_command(): def test_get_battery_state_command():
c = GetBatteryState() c = GetBatteryState()
assert_equals(c.terminal, False)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="GetBatteryInfo" />') b'<ctl td="GetBatteryInfo" />')
@@ -92,7 +80,6 @@ def test_get_battery_state_command():
def test_move_command(): def test_move_command():
c = Move(action='left') c = Move(action='left')
assert_equals(c.terminal, False)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="Move"><move action="SpinLeft" /></ctl>') b'<ctl td="Move"><move action="SpinLeft" /></ctl>')
c = Move(action='right') c = Move(action='right')
@@ -111,7 +98,6 @@ def test_move_command():
def test_get_lifepsan_command(): def test_get_lifepsan_command():
c = GetLifeSpan('main_brush') c = GetLifeSpan('main_brush')
assert_equals(c.terminal, False)
assert_equals(ElementTree.tostring(c.to_xml()), assert_equals(ElementTree.tostring(c.to_xml()),
b'<ctl td="GetLifeSpan" type="Brush" />') b'<ctl td="GetLifeSpan" type="Brush" />')
c = GetLifeSpan('side_brush') c = GetLifeSpan('side_brush')
+1 -1
View File
@@ -10,7 +10,7 @@ from sucks import *
def test_wrap_command(): def test_wrap_command():
x = make_ecovacs_xmpp() 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'from="20170101abcdefabcdefa@ecouser.net/abcdef12"', c))
assert_true(search(r'to="E0000000001234567890@126.ecorobot.net/atom"', c)) assert_true(search(r'to="E0000000001234567890@126.ecorobot.net/atom"', c))