From a8dce103e1268b343bc748b4ad9bd0031cf491f7 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Wed, 17 Mar 2021 11:27:12 +0100 Subject: [PATCH 01/14] Initial integration walkingpad --- docs/api/miio.gateway.rst | 28 ++++- docs/api/miio.rst | 22 +++- miio/__init__.py | 1 + miio/tests/test_walkingpad.py | 117 +++++++++++++++++++++ miio/walkingpad.py | 189 ++++++++++++++++++++++++++++++++++ 5 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 miio/tests/test_walkingpad.py create mode 100644 miio/walkingpad.py diff --git a/docs/api/miio.gateway.rst b/docs/api/miio.gateway.rst index cfa6209e6..a010f3119 100644 --- a/docs/api/miio.gateway.rst +++ b/docs/api/miio.gateway.rst @@ -1,5 +1,29 @@ -miio.gateway module -=================== +miio.gateway package +==================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + miio.gateway.devices + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + miio.gateway.alarm + miio.gateway.gateway + miio.gateway.gatewaydevice + miio.gateway.light + miio.gateway.radio + miio.gateway.zigbee + +Module contents +--------------- .. automodule:: miio.gateway :members: diff --git a/docs/api/miio.rst b/docs/api/miio.rst index 08897c3c6..b628f99fd 100644 --- a/docs/api/miio.rst +++ b/docs/api/miio.rst @@ -1,12 +1,21 @@ miio package ============ +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + miio.gateway + Submodules ---------- .. toctree:: :maxdepth: 4 + miio.airconditioner_miot miio.airconditioningcompanion miio.airconditioningcompanionMCN miio.airdehumidifier @@ -18,8 +27,10 @@ Submodules miio.airhumidifier_miot miio.airhumidifier_mjjsq miio.airpurifier + miio.airpurifier_airdog miio.airpurifier_miot miio.airqualitymonitor + miio.airqualitymonitor_miot miio.alarmclock miio.aqaracamera miio.ceil @@ -30,15 +41,19 @@ Submodules miio.cli miio.click_common miio.cooker + miio.curtain_youpin miio.device miio.discovery + miio.dreamevacuum_miot miio.exceptions miio.extract_tokens miio.fan miio.fan_common + miio.fan_leshow miio.fan_miot - miio.gateway miio.heater + miio.heater_miot + miio.huizuo miio.miioprotocol miio.miot_device miio.philips_bulb @@ -50,17 +65,22 @@ Submodules miio.powerstrip miio.protocol miio.pwzn_relay + miio.scishare_coffeemaker miio.toiletlid miio.updater miio.utils miio.vacuum miio.vacuum_cli + miio.vacuum_tui miio.vacuumcontainers miio.viomivacuum + miio.walkingpad miio.waterpurifier + miio.waterpurifier_yunmi miio.wifirepeater miio.wifispeaker miio.yeelight + miio.yeelight_dual_switch Module contents --------------- diff --git a/miio/__init__.py b/miio/__init__.py index 0160941f7..6d459aa0d 100644 --- a/miio/__init__.py +++ b/miio/__init__.py @@ -62,6 +62,7 @@ VacuumStatus, ) from miio.viomivacuum import ViomiVacuum +from miio.walkingpad import Walkingpad from miio.waterpurifier import WaterPurifier from miio.waterpurifier_yunmi import WaterPurifierYunmi from miio.wifirepeater import WifiRepeater diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py new file mode 100644 index 000000000..1b19835f1 --- /dev/null +++ b/miio/tests/test_walkingpad.py @@ -0,0 +1,117 @@ +from unittest import TestCase + +import pytest + +from miio import Walkingpad +from miio.walkingpad import WalkingpadException, WalkingpadStatus + +from .dummies import DummyDevice + + +class DummyWalkingpad(DummyDevice, Walkingpad): + def __init__(self, *args, **kwargs): + self.state = { + "power": "on", + "mode": 1, + "time": 1387, + "step": 2117, + "dist": 1150, + "sp": 3.05, + "cal": 71710, + "all": [ + "mode:1", + "time:1387", + "sp:3.05", + "dist:1150", + "cal:71710", + "step:2117", + ], + } + self.return_values = { + "get_prop": self._get_state, + "set_power": lambda x: self._set_state("power", x), + "set_mode": lambda x: self._set_state("mode", x), + "set_speed": lambda x: ( + self._set_state( + "all", + [ + "mode:1", + "time:1387", + "sp:" + str(x), + "dist:1150", + "cal:71710", + "step:2117", + ], + ), + self._set_state("sp", x), + ), + "set_step": lambda x: self._set_state("step", x), + "set_time": lambda x: self._set_state("time", x), + "set_distance": lambda x: self._set_state("dist", x), + } + super().__init__(args, kwargs) + + +@pytest.fixture(scope="class") +def walkingpad(request): + request.cls.device = DummyWalkingpad() + # TODO add ability to test on a real device + + +@pytest.mark.usefixtures("walkingpad") +class TestWalkingpad(TestCase): + def is_on(self): + return self.device.status().is_on + + def state(self): + return self.device.status() + + def test_on(self): + self.device.off() # ensure off + assert self.is_on() is False + + self.device.on() + assert self.is_on() is True + + def test_off(self): + self.device.on() # ensure on + assert self.is_on() is True + + self.device.off() + assert self.is_on() is False + + def test_status(self): + self.device._reset_state() + print(self.state()) + + # Because we get a set of status values from the walkingpad via 'all' the dummy device doesnt work for testing. + # TODO Need to figure out how to test this properly. + + assert repr(self.state()) == repr(WalkingpadStatus(self.device.start_state)) + + assert self.is_on() is True + assert self.state().power == self.device.start_state["power"] + assert self.state().mode == self.device.start_state["mode"] + # assert self.state().speed == self.device.start_state["speed"] + # assert self.state().step == self.device.start_state["step"] + # assert self.state().distance == self.device.start_state["dist"] + # assert self.state().time == self.device.start_state["time"] + + def test_set_mode(self): + def mode(): + return self.device.status().mode + + self.device.set_mode(1) + assert mode() == 1 + + self.device.set_mode(0) + assert mode() == 0 + + with pytest.raises(WalkingpadException): + self.device.set_mode(-1) + + with pytest.raises(WalkingpadException): + self.device.set_mode(3) + + with pytest.raises(WalkingpadException): + self.device.set_mode("blah") diff --git a/miio/walkingpad.py b/miio/walkingpad.py new file mode 100644 index 000000000..31ba8e3eb --- /dev/null +++ b/miio/walkingpad.py @@ -0,0 +1,189 @@ +import logging +from collections import defaultdict +from typing import Any, Dict + +import click + +from .click_common import command, format_output +from .device import Device, DeviceStatus +from .exceptions import DeviceException + +_LOGGER = logging.getLogger(__name__) + + +class WalkingpadException(DeviceException): + pass + + +class WalkingpadStatus(DeviceStatus): + """Container for status reports from Xiaomi Walkingpad.""" + + # raw_command set_start_speed '["1.0"]' finally made my WalkingPad autostart on speed 1! Hurray! + # + # raw_command get_prop '["mode"]' returns + # + # [0] while mode is auto + # [1] while mode is manual + # [2] when in standby + # raw_command get_prop '["step"]' returns [2303] + # raw_command get_prop '["time"]' returns [1970] (while time is 32:50) + # raw_command get_prop '["dist"]' returns [1869] + # raw_command get_prop '["cal"]' returns [67340] + # raw_command get_prop '["goal"]' returns [0, 60] + # raw_command get_prop '["max"]' returns [6.0] + # raw_command get_prop '["initial"]' returns [3] + # raw_command get_prop '["offline"]' returns [0] + # raw_command get_prop '["sensitivity"]' returns [2] + # raw_command get_prop '["sp"]' returns [1.0] + # raw_command get_prop '["start_speed"]' returns [1.0] + # raw_command get_prop '["auto"]' returns [1] + # raw_command get_prop '["disp"]' returns [19] + # raw_command get_prop '["lock"]' returns [0] + + def __init__(self, data: Dict[str, Any]) -> None: + + # NOTE: Only 1 property can be requested at the same time + self.data = data + + @property + def power(self) -> str: + """Power state.""" + return self.data["power"] + + @property + def is_on(self) -> bool: + """True if the device is turned on.""" + return self.power == "on" + + @property + def time(self) -> int: + """Current lock status.""" + return self.data["time"] + + @property + def speed(self) -> float: + """Current speed.""" + return self.data["sp"] + + @property + def mode(self) -> int: + """Current mode.""" + return self.data["mode"] + + @property + def step(self) -> int: + """Current steps.""" + return self.data["step"] + + @property + def distance(self) -> int: + """Current distance.""" + return self.data["dist"] + + @property + def calories(self) -> int: + """Current distance.""" + return self.data["cal"] + + +class Walkingpad(Device): + """Main class representing Xiaomi Walkingpad.""" + + # TODO: - Auto On/Off Not Supported + # - Adjust Scenes with Wall Switch Not Supported + + @command( + default_output=format_output( + "", + "Mode: {result.mode}\n" + "Time: {result.time}\n" + "Steps: {result.step}\n" + "Speed: {result.speed}\n" + "Distance: {result.distance}\n" + "Calories: {result.calories} ", + ) + ) + def status(self) -> WalkingpadStatus: + """Retrieve properties.""" + + properties_received = [] + values_received = [] + + # Walkingpad A1 allows you to retrieve a subset of values with "all" + # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] + + properties = ["all"] + + # Walkingpad A1 only allows 1 property to be read at a time + + values = self.get_properties(properties, max_properties=1) + + # When running the tests, for some reason the list provided is passed within another list, so I take + # care of this here. + + if any(isinstance(el, list) for el in values): + values = values[0] + + for x in values: + prop_split, value = x.split(":") + properties_received.append(prop_split) + values_received.append(value) + + properties_additional = ["power", "mode"] + + values_additional = self.get_properties(properties_additional, max_properties=1) + + properties_received = properties_received + properties_additional + values_received = values_received + values_additional + return WalkingpadStatus( + defaultdict(lambda: None, zip(properties_received, values_received)) + ) + + @command(default_output=format_output("Powering on")) + def on(self): + """Power on.""" + return self.send("set_power", ["on"]) + + @command(default_output=format_output("Powering off")) + def off(self): + """Power off.""" + return self.send("set_power", ["off"]) + + @command(default_output=format_output("Starting the treadmill")) + def start(self): + """Starting Up.""" + return self.send("set_state", ["run"]) + + @command(default_output=format_output("Stopping the treadmill")) + def stop(self): + """Starting Up.""" + return self.send("set_state", ["stop"]) + + @command( + click.argument("mode", type=int), + default_output=format_output("Setting mode to {mode}"), + ) + def set_mode(self, mode: int): + """Set mode (auto/manual).""" + + if not isinstance(mode, int): + raise WalkingpadException("Invalid mode: %s" % mode) + + elif mode < 0 or mode >= 3: + raise WalkingpadException("Invalid mode: %s" % mode) + return self.send("set_mode", [mode]) + + @command( + click.argument("speed", type=float), + default_output=format_output("Setting speed to {speed}"), + ) + def set_speed(self, speed: float): + """Set speed.""" + + if not isinstance(speed, float): + raise WalkingpadException("Invalid speed: %s" % speed) + + elif speed < 0 or speed > 6: + raise WalkingpadException("Invalid speed: %s" % speed) + + return self.send("set_speed", [speed]) From 6ff43b45f240dd1568361432cfebe494e5abd14d Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Wed, 17 Mar 2021 12:02:34 +0100 Subject: [PATCH 02/14] Removed print statement --- miio/tests/test_walkingpad.py | 1 - 1 file changed, 1 deletion(-) diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index 1b19835f1..323a8fc89 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -82,7 +82,6 @@ def test_off(self): def test_status(self): self.device._reset_state() - print(self.state()) # Because we get a set of status values from the walkingpad via 'all' the dummy device doesnt work for testing. # TODO Need to figure out how to test this properly. From 1ca15a37ee21bcfc19a4401e91ab36b56103e930 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Thu, 18 Mar 2021 10:37:56 +0100 Subject: [PATCH 03/14] Implement PR review suggestions --- miio/tests/test_walkingpad.py | 10 ++++---- miio/walkingpad.py | 46 +++++++++++++++++------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index 323a8fc89..a5401144a 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -3,7 +3,7 @@ import pytest from miio import Walkingpad -from miio.walkingpad import WalkingpadException, WalkingpadStatus +from miio.walkingpad import OperationMode, WalkingpadException, WalkingpadStatus from .dummies import DummyDevice @@ -100,11 +100,11 @@ def test_set_mode(self): def mode(): return self.device.status().mode - self.device.set_mode(1) - assert mode() == 1 + self.device.set_mode(OperationMode.Auto) + assert mode() == OperationMode.Auto.value - self.device.set_mode(0) - assert mode() == 0 + self.device.set_mode(OperationMode.Manual) + assert mode() == OperationMode.Manual.value with pytest.raises(WalkingpadException): self.device.set_mode(-1) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 31ba8e3eb..3814f86fc 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -1,10 +1,11 @@ +import enum import logging from collections import defaultdict from typing import Any, Dict import click -from .click_common import command, format_output +from .click_common import EnumType, command, format_output from .device import Device, DeviceStatus from .exceptions import DeviceException @@ -15,6 +16,12 @@ class WalkingpadException(DeviceException): pass +class OperationMode(enum.Enum): + Auto = 0 + Manual = 1 + Off = 2 + + class WalkingpadStatus(DeviceStatus): """Container for status reports from Xiaomi Walkingpad.""" @@ -57,7 +64,7 @@ def is_on(self) -> bool: @property def time(self) -> int: - """Current lock status.""" + """Current walking time.""" return self.data["time"] @property @@ -71,7 +78,7 @@ def mode(self) -> int: return self.data["mode"] @property - def step(self) -> int: + def step_count(self) -> int: """Current steps.""" return self.data["step"] @@ -106,9 +113,6 @@ class Walkingpad(Device): def status(self) -> WalkingpadStatus: """Retrieve properties.""" - properties_received = [] - values_received = [] - # Walkingpad A1 allows you to retrieve a subset of values with "all" # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] @@ -124,20 +128,18 @@ def status(self) -> WalkingpadStatus: if any(isinstance(el, list) for el in values): values = values[0] + data = {} for x in values: - prop_split, value = x.split(":") - properties_received.append(prop_split) - values_received.append(value) + prop, value = x.split(":") + data[prop] = value properties_additional = ["power", "mode"] - values_additional = self.get_properties(properties_additional, max_properties=1) - properties_received = properties_received + properties_additional - values_received = values_received + values_additional - return WalkingpadStatus( - defaultdict(lambda: None, zip(properties_received, values_received)) - ) + for i in range(len(properties_additional)): + data[properties_additional[i]] = values_additional[i] + + return WalkingpadStatus(defaultdict(lambda: None, data)) @command(default_output=format_output("Powering on")) def on(self): @@ -160,18 +162,16 @@ def stop(self): return self.send("set_state", ["stop"]) @command( - click.argument("mode", type=int), - default_output=format_output("Setting mode to {mode}"), + click.argument("mode", type=EnumType(OperationMode)), + default_output=format_output("Setting mode to '{mode.name}'"), ) - def set_mode(self, mode: int): + def set_mode(self, mode: OperationMode): """Set mode (auto/manual).""" - if not isinstance(mode, int): + if not isinstance(mode, OperationMode): raise WalkingpadException("Invalid mode: %s" % mode) - elif mode < 0 or mode >= 3: - raise WalkingpadException("Invalid mode: %s" % mode) - return self.send("set_mode", [mode]) + return self.send("set_mode", [mode.value]) @command( click.argument("speed", type=float), @@ -183,7 +183,7 @@ def set_speed(self, speed: float): if not isinstance(speed, float): raise WalkingpadException("Invalid speed: %s" % speed) - elif speed < 0 or speed > 6: + if speed < 0 or speed > 6: raise WalkingpadException("Invalid speed: %s" % speed) return self.send("set_speed", [speed]) From ed872f67b9f2e78be72450fdcbaeec8895c4ba83 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Fri, 19 Mar 2021 08:29:27 +0100 Subject: [PATCH 04/14] Fix step_count bug --- miio/walkingpad.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 3814f86fc..ba7c3e832 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -96,15 +96,12 @@ def calories(self) -> int: class Walkingpad(Device): """Main class representing Xiaomi Walkingpad.""" - # TODO: - Auto On/Off Not Supported - # - Adjust Scenes with Wall Switch Not Supported - @command( default_output=format_output( "", "Mode: {result.mode}\n" "Time: {result.time}\n" - "Steps: {result.step}\n" + "Steps: {result.step_count}\n" "Speed: {result.speed}\n" "Distance: {result.distance}\n" "Calories: {result.calories} ", @@ -125,8 +122,8 @@ def status(self) -> WalkingpadStatus: # When running the tests, for some reason the list provided is passed within another list, so I take # care of this here. - if any(isinstance(el, list) for el in values): - values = values[0] + if len(values) <= 1: + values = values.pop() data = {} for x in values: From 470bb070198b36d3cc18d3e1c21f1615e6c86675 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Fri, 19 Mar 2021 10:07:15 +0100 Subject: [PATCH 05/14] Update miio/walkingpad.py Co-authored-by: Teemu R. --- miio/walkingpad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 3814f86fc..5eed4a507 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -139,7 +139,7 @@ def status(self) -> WalkingpadStatus: for i in range(len(properties_additional)): data[properties_additional[i]] = values_additional[i] - return WalkingpadStatus(defaultdict(lambda: None, data)) + return WalkingpadStatus(data) @command(default_output=format_output("Powering on")) def on(self): From 14d72f936742a3d5eda0e5b8dce8243197c03533 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Fri, 19 Mar 2021 10:10:24 +0100 Subject: [PATCH 06/14] Fix step_count bug --- miio/walkingpad.py | 1 - 1 file changed, 1 deletion(-) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index f53601142..87477329d 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -1,6 +1,5 @@ import enum import logging -from collections import defaultdict from typing import Any, Dict import click From 7098534cf16752118fad21241f36f6845214f910 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Tue, 30 Mar 2021 14:42:58 +0200 Subject: [PATCH 07/14] Update based on PR feedback & add startup speed / sensitivity functions --- miio/tests/test_walkingpad.py | 102 +++++++++++++++++++++++---- miio/walkingpad.py | 128 ++++++++++++++++++++++++---------- 2 files changed, 180 insertions(+), 50 deletions(-) diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index a5401144a..98698a911 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -3,25 +3,53 @@ import pytest from miio import Walkingpad -from miio.walkingpad import OperationMode, WalkingpadException, WalkingpadStatus +from miio.walkingpad import ( + OperationMode, + OperationSensitivity, + WalkingpadException, + WalkingpadStatus, +) from .dummies import DummyDevice class DummyWalkingpad(DummyDevice, Walkingpad): + def _get_state(self, props): + """Return wanted properties.""" + + # Overriding here to deal with case of 'all' being requested + + if props[0] == "all": + return self.state[props[0]] + + return [self.state[x] for x in props if x in self.state] + + def _set_state(self, var, value): + """Set a state of a variable, the value is expected to be an array with length + of 1.""" + + # Overriding here to deal with case of 'all' being set + + if var == "all": + self.state[var] = value + else: + self.state[var] = value.pop(0) + def __init__(self, *args, **kwargs): self.state = { "power": "on", "mode": 1, "time": 1387, "step": 2117, + "sensitivity": OperationSensitivity.Low, "dist": 1150, - "sp": 3.05, + "sp": 3.15, "cal": 71710, + "start_speed": 3.1, "all": [ "mode:1", "time:1387", - "sp:3.05", + "sp:3.15", "dist:1150", "cal:71710", "step:2117", @@ -37,7 +65,7 @@ def __init__(self, *args, **kwargs): [ "mode:1", "time:1387", - "sp:" + str(x), + "sp:" + str(x[0]), "dist:1150", "cal:71710", "step:2117", @@ -46,6 +74,8 @@ def __init__(self, *args, **kwargs): self._set_state("sp", x), ), "set_step": lambda x: self._set_state("step", x), + "set_sensitivity": lambda x: self._set_state("sensitivity", x), + "set_start_speed": lambda x: self._set_state("start_speed", x), "set_time": lambda x: self._set_state("time", x), "set_distance": lambda x: self._set_state("dist", x), } @@ -55,7 +85,6 @@ def __init__(self, *args, **kwargs): @pytest.fixture(scope="class") def walkingpad(request): request.cls.device = DummyWalkingpad() - # TODO add ability to test on a real device @pytest.mark.usefixtures("walkingpad") @@ -83,18 +112,16 @@ def test_off(self): def test_status(self): self.device._reset_state() - # Because we get a set of status values from the walkingpad via 'all' the dummy device doesnt work for testing. - # TODO Need to figure out how to test this properly. - assert repr(self.state()) == repr(WalkingpadStatus(self.device.start_state)) assert self.is_on() is True assert self.state().power == self.device.start_state["power"] assert self.state().mode == self.device.start_state["mode"] - # assert self.state().speed == self.device.start_state["speed"] - # assert self.state().step == self.device.start_state["step"] - # assert self.state().distance == self.device.start_state["dist"] - # assert self.state().time == self.device.start_state["time"] + assert self.state().speed == str(self.device.start_state["sp"]) + assert self.state().step_count == str(self.device.start_state["step"]) + assert self.state().distance == str(self.device.start_state["dist"]) + assert self.state().sensitivity == self.device.start_state["sensitivity"] + assert self.state().time == str(self.device.start_state["time"]) def test_set_mode(self): def mode(): @@ -114,3 +141,54 @@ def mode(): with pytest.raises(WalkingpadException): self.device.set_mode("blah") + + def test_set_speed(self): + def speed(): + return self.device.status().speed + + self.device.set_speed(3.055) + assert speed() == str(3.055) + + with pytest.raises(WalkingpadException): + self.device.set_speed(7.6) + + with pytest.raises(WalkingpadException): + self.device.set_speed(-1) + + with pytest.raises(WalkingpadException): + self.device.set_speed("blah") + + def test_set_start_speed(self): + def speed(): + return self.device.status().start_speed + + self.device.set_start_speed(3.055) + assert speed() == 3.055 + + with pytest.raises(WalkingpadException): + self.device.set_start_speed(7.6) + + with pytest.raises(WalkingpadException): + self.device.set_start_speed(-1) + + with pytest.raises(WalkingpadException): + self.device.set_start_speed("blah") + + def test_set_sensitivity(self): + def sensitivity(): + return self.device.status().sensitivity + + self.device.set_sensitivity(OperationSensitivity.High) + assert sensitivity() == OperationSensitivity.High.value + + self.device.set_sensitivity(OperationSensitivity.Medium) + assert sensitivity() == OperationSensitivity.Medium.value + + with pytest.raises(WalkingpadException): + self.device.set_sensitivity(-1) + + with pytest.raises(WalkingpadException): + self.device.set_sensitivity(99) + + with pytest.raises(WalkingpadException): + self.device.set_sensitivity("blah") diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 87477329d..abd2ed43b 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -21,31 +21,15 @@ class OperationMode(enum.Enum): Off = 2 +class OperationSensitivity(enum.Enum): + High = 1 + Medium = 2 + Low = 3 + + class WalkingpadStatus(DeviceStatus): """Container for status reports from Xiaomi Walkingpad.""" - # raw_command set_start_speed '["1.0"]' finally made my WalkingPad autostart on speed 1! Hurray! - # - # raw_command get_prop '["mode"]' returns - # - # [0] while mode is auto - # [1] while mode is manual - # [2] when in standby - # raw_command get_prop '["step"]' returns [2303] - # raw_command get_prop '["time"]' returns [1970] (while time is 32:50) - # raw_command get_prop '["dist"]' returns [1869] - # raw_command get_prop '["cal"]' returns [67340] - # raw_command get_prop '["goal"]' returns [0, 60] - # raw_command get_prop '["max"]' returns [6.0] - # raw_command get_prop '["initial"]' returns [3] - # raw_command get_prop '["offline"]' returns [0] - # raw_command get_prop '["sensitivity"]' returns [2] - # raw_command get_prop '["sp"]' returns [1.0] - # raw_command get_prop '["start_speed"]' returns [1.0] - # raw_command get_prop '["auto"]' returns [1] - # raw_command get_prop '["disp"]' returns [19] - # raw_command get_prop '["lock"]' returns [0] - def __init__(self, data: Dict[str, Any]) -> None: # NOTE: Only 1 property can be requested at the same time @@ -72,10 +56,20 @@ def speed(self) -> float: return self.data["sp"] @property - def mode(self) -> int: + def start_speed(self) -> float: + """Current speed.""" + return self.data["start_speed"] + + @property + def mode(self) -> OperationMode: """Current mode.""" return self.data["mode"] + @property + def sensitivity(self) -> OperationSensitivity: + """Current sensitivity.""" + return self.data["sensitivity"] + @property def step_count(self) -> int: """Current steps.""" @@ -88,7 +82,7 @@ def distance(self) -> int: @property def calories(self) -> int: - """Current distance.""" + """Current calories burnt.""" return self.data["cal"] @@ -102,38 +96,59 @@ class Walkingpad(Device): "Time: {result.time}\n" "Steps: {result.step_count}\n" "Speed: {result.speed}\n" + "Start Speed: {result.start_speed}\n" + "Sensitivity: {result.sensitivity}\n" "Distance: {result.distance}\n" - "Calories: {result.calories} ", + "Calories: {result.calories}", ) ) def status(self) -> WalkingpadStatus: """Retrieve properties.""" - # Walkingpad A1 allows you to retrieve a subset of values with "all" - # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] - properties = ["all"] # Walkingpad A1 only allows 1 property to be read at a time - values = self.get_properties(properties, max_properties=1) - # When running the tests, for some reason the list provided is passed within another list, so I take - # care of this here. - - if len(values) <= 1: - values = values.pop() - data = {} for x in values: prop, value = x.split(":") data[prop] = value - properties_additional = ["power", "mode"] + properties_additional = ["power", "mode", "start_speed", "sensitivity"] values_additional = self.get_properties(properties_additional, max_properties=1) - for i in range(len(properties_additional)): - data[properties_additional[i]] = values_additional[i] + additional_props = dict(zip(properties_additional, values_additional)) + data.update(additional_props) + + return WalkingpadStatus(data) + + @command( + default_output=format_output( + "", + "Mode: {result.mode}\n" + "Time: {result.time}\n" + "Steps: {result.step_count}\n" + "Speed: {result.speed}\n" + "Distance: {result.distance}\n" + "Calories: {result.calories}", + ) + ) + def quick_status(self) -> WalkingpadStatus: + """Retrieve quick properties.""" + + # Walkingpad A1 allows you to retrieve a subset of values with "all" + # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] + + properties = ["all"] + + # Walkingpad A1 only allows 1 property to be read at a time + values = self.get_properties(properties, max_properties=1) + # print(values) + data = {} + for x in values: + prop, value = x.split(":") + data[prop] = value return WalkingpadStatus(data) @@ -147,6 +162,16 @@ def off(self): """Power off.""" return self.send("set_power", ["off"]) + @command(default_output=format_output("Locking")) + def lock(self): + """Lock device.""" + return self.send("set_lock", [1]) + + @command(default_output=format_output("Unlocking")) + def unlock(self): + """Unlock device.""" + return self.send("set_lock", [0]) + @command(default_output=format_output("Starting the treadmill")) def start(self): """Starting Up.""" @@ -183,3 +208,30 @@ def set_speed(self, speed: float): raise WalkingpadException("Invalid speed: %s" % speed) return self.send("set_speed", [speed]) + + @command( + click.argument("speed", type=float), + default_output=format_output("Setting start speed to {speed}"), + ) + def set_start_speed(self, speed: float): + """Set start speed.""" + + if not isinstance(speed, float): + raise WalkingpadException("Invalid start speed: %s" % speed) + + if speed < 0 or speed > 6: + raise WalkingpadException("Invalid start speed: %s" % speed) + + return self.send("set_start_speed", [speed]) + + @command( + click.argument("sensitivity", type=EnumType(OperationSensitivity)), + default_output=format_output("Setting sensitivity to {sensitivity}"), + ) + def set_sensitivity(self, sensitivity: OperationSensitivity): + """Set sensitivity.""" + + if not isinstance(sensitivity, OperationSensitivity): + raise WalkingpadException("Invalid mode: %s" % sensitivity) + + return self.send("set_sensitivity", [sensitivity.value]) From 1e0767422ded46c6cdd6c47e8edb9c031811a65e Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Tue, 30 Mar 2021 15:04:25 +0200 Subject: [PATCH 08/14] Update docstring with class initialisation --- miio/walkingpad.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index abd2ed43b..d16b72fba 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -28,10 +28,22 @@ class OperationSensitivity(enum.Enum): class WalkingpadStatus(DeviceStatus): - """Container for status reports from Xiaomi Walkingpad.""" + """Container for status reports from Xiaomi Walkingpad. - def __init__(self, data: Dict[str, Any]) -> None: + Input data dictionary to initialise this class: + + {'cal': '0', + 'dist': '0', + 'mode': 2, + 'power': 'off', + 'sensitivity': 1, + 'sp': '0.0', + 'start_speed': 3.0, + 'step': '0', + 'time': '0'} + """ + def __init__(self, data: Dict[str, Any]) -> None: # NOTE: Only 1 property can be requested at the same time self.data = data From 465d6fabd9431e5ff27339020296b9b57776e915 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Tue, 30 Mar 2021 18:03:37 +0200 Subject: [PATCH 09/14] Implement PR feedback --- README.rst | 1 + miio/tests/test_walkingpad.py | 10 ++-- miio/walkingpad.py | 102 ++++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 41 deletions(-) diff --git a/README.rst b/README.rst index b4ec3f62d..315c919b8 100644 --- a/README.rst +++ b/README.rst @@ -133,6 +133,7 @@ Supported devices - Yeelight Dual Control Module (yeelink.switch.sw1) - Scishare coffee maker (scishare.coffee.s1102) - Qingping Air Monitor Lite (cgllc.airm.cgdn1) +- Xiaomi Walkingpad A1 (ksmb.walkingpad.v3) *Feel free to create a pull request to add support for new devices as diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index 98698a911..1da515fab 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -117,11 +117,11 @@ def test_status(self): assert self.is_on() is True assert self.state().power == self.device.start_state["power"] assert self.state().mode == self.device.start_state["mode"] - assert self.state().speed == str(self.device.start_state["sp"]) - assert self.state().step_count == str(self.device.start_state["step"]) - assert self.state().distance == str(self.device.start_state["dist"]) + assert self.state().speed == self.device.start_state["sp"] + assert self.state().step_count == self.device.start_state["step"] + assert self.state().distance == self.device.start_state["dist"] assert self.state().sensitivity == self.device.start_state["sensitivity"] - assert self.state().time == str(self.device.start_state["time"]) + assert self.state().time == self.device.start_state["time"] def test_set_mode(self): def mode(): @@ -147,7 +147,7 @@ def speed(): return self.device.status().speed self.device.set_speed(3.055) - assert speed() == str(3.055) + assert speed() == 3.055 with pytest.raises(WalkingpadException): self.device.set_speed(7.6) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index d16b72fba..27ae94e55 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -32,19 +32,18 @@ class WalkingpadStatus(DeviceStatus): Input data dictionary to initialise this class: - {'cal': '0', - 'dist': '0', - 'mode': 2, - 'power': 'off', + {'cal': 6130, + 'dist': 90, + 'mode': 1, + 'power': 'on', 'sensitivity': 1, - 'sp': '0.0', + 'sp': 3.0, 'start_speed': 3.0, - 'step': '0', - 'time': '0'} + 'step': 180, + 'time': 121} """ def __init__(self, data: Dict[str, Any]) -> None: - # NOTE: Only 1 property can be requested at the same time self.data = data @property @@ -59,13 +58,13 @@ def is_on(self) -> bool: @property def time(self) -> int: - """Current walking time.""" - return self.data["time"] + """Current walking time in seconds.""" + return int(self.data["time"]) @property def speed(self) -> float: """Current speed.""" - return self.data["sp"] + return float(self.data["sp"]) @property def start_speed(self) -> float: @@ -85,17 +84,17 @@ def sensitivity(self) -> OperationSensitivity: @property def step_count(self) -> int: """Current steps.""" - return self.data["step"] + return int(self.data["step"]) @property def distance(self) -> int: - """Current distance.""" - return self.data["dist"] + """Current distance in meters.""" + return int(self.data["dist"]) @property def calories(self) -> int: """Current calories burnt.""" - return self.data["cal"] + return int(self.data["cal"]) class Walkingpad(Device): @@ -104,6 +103,7 @@ class Walkingpad(Device): @command( default_output=format_output( "", + "Power: {result.power}\n" "Mode: {result.mode}\n" "Time: {result.time}\n" "Steps: {result.step_count}\n" @@ -117,16 +117,9 @@ class Walkingpad(Device): def status(self) -> WalkingpadStatus: """Retrieve properties.""" - properties = ["all"] - - # Walkingpad A1 only allows 1 property to be read at a time - values = self.get_properties(properties, max_properties=1) - - data = {} - for x in values: - prop, value = x.split(":") - data[prop] = value + data = self._get_quick_status() + # The quick status only retrieves a subset of the properties. The rest of them are retrieved here. properties_additional = ["power", "mode", "start_speed", "sensitivity"] values_additional = self.get_properties(properties_additional, max_properties=1) @@ -147,20 +140,19 @@ def status(self) -> WalkingpadStatus: ) ) def quick_status(self) -> WalkingpadStatus: - """Retrieve quick properties.""" + """Retrieve quick properties. The walkingpad provides the option to retrieve a + subset of. - # Walkingpad A1 allows you to retrieve a subset of values with "all" - # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] + properties in one call - steps, mode, speed, distance, calories and time. + If you can, use this instead of the full status request, as it is much + faster. + """ - properties = ["all"] + # Walkingpad A1 allows you to quickly retrieve a subset of values with "all" + # all other properties need to be retrieved one by one and are therefore slower + # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] - # Walkingpad A1 only allows 1 property to be read at a time - values = self.get_properties(properties, max_properties=1) - # print(values) - data = {} - for x in values: - prop, value = x.split(":") - data[prop] = value + data = self._get_quick_status() return WalkingpadStatus(data) @@ -187,7 +179,12 @@ def unlock(self): @command(default_output=format_output("Starting the treadmill")) def start(self): """Starting Up.""" - return self.send("set_state", ["run"]) + if not self.status().is_on: + raise WalkingpadException( + "Can't start the treadmill, it's not turned on - try issuing an 'on' command first" + ) + else: + return self.send("set_state", ["run"]) @command(default_output=format_output("Stopping the treadmill")) def stop(self): @@ -247,3 +244,36 @@ def set_sensitivity(self, sensitivity: OperationSensitivity): raise WalkingpadException("Invalid mode: %s" % sensitivity) return self.send("set_sensitivity", [sensitivity.value]) + + def _get_quick_status(self): + + # internal helper to get the quick status via the "all" property + + properties = ["all"] + + values = self.get_properties(properties, max_properties=1) + + data = {} + for x in values: + prop, value = x.split(":") + + if prop not in ["sp", "step", "cal", "time", "dist", "mode"]: + raise WalkingpadException("Invalid data received: %s" % value) + + # Ensure that he different properties are correctly typed + if prop == "sp": + value = float(value) + elif prop == "step": + value = int(value) + elif prop == "cal": + value = int(value) + elif prop == "time": + value = int(value) + elif prop == "dist": + value = int(value) + elif prop == "mode": + value = int(value) + + data[prop] = value + + return data From 4a7aaf6cff63868bc5be876b9f46ee0c5f73f33a Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Tue, 30 Mar 2021 18:13:40 +0200 Subject: [PATCH 10/14] Rename time to walking_time and change to return timedelta --- miio/tests/test_walkingpad.py | 2 +- miio/walkingpad.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index 1da515fab..d4a74a5b0 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -121,7 +121,7 @@ def test_status(self): assert self.state().step_count == self.device.start_state["step"] assert self.state().distance == self.device.start_state["dist"] assert self.state().sensitivity == self.device.start_state["sensitivity"] - assert self.state().time == self.device.start_state["time"] + assert self.state().walking_time == self.device.start_state["time"] def test_set_mode(self): def mode(): diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 27ae94e55..f5175c3db 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -1,5 +1,6 @@ import enum import logging +from datetime import timedelta from typing import Any, Dict import click @@ -57,8 +58,8 @@ def is_on(self) -> bool: return self.power == "on" @property - def time(self) -> int: - """Current walking time in seconds.""" + def walking_time(self) -> timedelta: + """Current walking duration in seconds.""" return int(self.data["time"]) @property From 85475db0688ccd3eb3e567826913b34569f8c577 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Tue, 30 Mar 2021 18:20:39 +0200 Subject: [PATCH 11/14] Rename time to walking_time and change to return timedelta --- miio/tests/test_walkingpad.py | 1 - miio/walkingpad.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index d4a74a5b0..a2e77ca6f 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -113,7 +113,6 @@ def test_status(self): self.device._reset_state() assert repr(self.state()) == repr(WalkingpadStatus(self.device.start_state)) - assert self.is_on() is True assert self.state().power == self.device.start_state["power"] assert self.state().mode == self.device.start_state["mode"] diff --git a/miio/walkingpad.py b/miio/walkingpad.py index f5175c3db..3d6529b4c 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -69,7 +69,7 @@ def speed(self) -> float: @property def start_speed(self) -> float: - """Current speed.""" + """Current start speed.""" return self.data["start_speed"] @property From 7e18d238e500b0b5338729b8be835ab7a9189d15 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Wed, 31 Mar 2021 13:56:18 +0200 Subject: [PATCH 12/14] Correct the description for starting & stopping --- miio/walkingpad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 3d6529b4c..cf519c568 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -179,7 +179,7 @@ def unlock(self): @command(default_output=format_output("Starting the treadmill")) def start(self): - """Starting Up.""" + """Start the treadmill.""" if not self.status().is_on: raise WalkingpadException( "Can't start the treadmill, it's not turned on - try issuing an 'on' command first" @@ -189,7 +189,7 @@ def start(self): @command(default_output=format_output("Stopping the treadmill")) def stop(self): - """Starting Up.""" + """Stop the treadmill.""" return self.send("set_state", ["stop"]) @command( From 48bba4a35838e1ca7cc26e36c1028d5379c82c26 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Thu, 1 Apr 2021 11:51:05 +0200 Subject: [PATCH 13/14] Fix mode and sensitivity return types. Resolve more PR feedback --- miio/tests/test_walkingpad.py | 12 +++---- miio/walkingpad.py | 63 ++++++++++++++++------------------- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/miio/tests/test_walkingpad.py b/miio/tests/test_walkingpad.py index a2e77ca6f..d1e094bcb 100644 --- a/miio/tests/test_walkingpad.py +++ b/miio/tests/test_walkingpad.py @@ -38,7 +38,7 @@ def _set_state(self, var, value): def __init__(self, *args, **kwargs): self.state = { "power": "on", - "mode": 1, + "mode": OperationMode.Manual, "time": 1387, "step": 2117, "sensitivity": OperationSensitivity.Low, @@ -47,7 +47,7 @@ def __init__(self, *args, **kwargs): "cal": 71710, "start_speed": 3.1, "all": [ - "mode:1", + "mode:" + str(OperationMode.Manual.value), "time:1387", "sp:3.15", "dist:1150", @@ -127,10 +127,10 @@ def mode(): return self.device.status().mode self.device.set_mode(OperationMode.Auto) - assert mode() == OperationMode.Auto.value + assert mode() == OperationMode.Auto self.device.set_mode(OperationMode.Manual) - assert mode() == OperationMode.Manual.value + assert mode() == OperationMode.Manual with pytest.raises(WalkingpadException): self.device.set_mode(-1) @@ -178,10 +178,10 @@ def sensitivity(): return self.device.status().sensitivity self.device.set_sensitivity(OperationSensitivity.High) - assert sensitivity() == OperationSensitivity.High.value + assert sensitivity() == OperationSensitivity.High self.device.set_sensitivity(OperationSensitivity.Medium) - assert sensitivity() == OperationSensitivity.Medium.value + assert sensitivity() == OperationSensitivity.Medium with pytest.raises(WalkingpadException): self.device.set_sensitivity(-1) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index cf519c568..590a8204f 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -29,7 +29,7 @@ class OperationSensitivity(enum.Enum): class WalkingpadStatus(DeviceStatus): - """Container for status reports from Xiaomi Walkingpad. + """Container for status reports from Xiaomi Walkingpad A1 (ksmb.walkingpad.v3). Input data dictionary to initialise this class: @@ -75,12 +75,12 @@ def start_speed(self) -> float: @property def mode(self) -> OperationMode: """Current mode.""" - return self.data["mode"] + return OperationMode(self.data["mode"]) @property def sensitivity(self) -> OperationSensitivity: """Current sensitivity.""" - return self.data["sensitivity"] + return OperationSensitivity(self.data["sensitivity"]) @property def step_count(self) -> int: @@ -105,12 +105,12 @@ class Walkingpad(Device): default_output=format_output( "", "Power: {result.power}\n" - "Mode: {result.mode}\n" - "Time: {result.time}\n" + "Mode: {result.mode.name}\n" + "Time: {result.walking_time}\n" "Steps: {result.step_count}\n" "Speed: {result.speed}\n" "Start Speed: {result.start_speed}\n" - "Sensitivity: {result.sensitivity}\n" + "Sensitivity: {result.sensitivity.name}\n" "Distance: {result.distance}\n" "Calories: {result.calories}", ) @@ -132,8 +132,8 @@ def status(self) -> WalkingpadStatus: @command( default_output=format_output( "", - "Mode: {result.mode}\n" - "Time: {result.time}\n" + "Mode: {result.mode.name}\n" + "Time: {result.walking_time}\n" "Steps: {result.step_count}\n" "Speed: {result.speed}\n" "Distance: {result.distance}\n" @@ -141,18 +141,13 @@ def status(self) -> WalkingpadStatus: ) ) def quick_status(self) -> WalkingpadStatus: - """Retrieve quick properties. The walkingpad provides the option to retrieve a - subset of. + """Retrieve quick properties. The walkingpad provides the option. - properties in one call - steps, mode, speed, distance, calories and time. - If you can, use this instead of the full status request, as it is much - faster. + to retrieve a subset of properties in one call - steps, mode, + speed, distance, calories and time. If you can, use this instead + of the full status request, as it is way faster. """ - # Walkingpad A1 allows you to quickly retrieve a subset of values with "all" - # all other properties need to be retrieved one by one and are therefore slower - # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] - data = self._get_quick_status() return WalkingpadStatus(data) @@ -247,34 +242,34 @@ def set_sensitivity(self, sensitivity: OperationSensitivity): return self.send("set_sensitivity", [sensitivity.value]) def _get_quick_status(self): + """Internal helper to get the quick status via the "all" property.""" - # internal helper to get the quick status via the "all" property + # Walkingpad A1 allows you to quickly retrieve a subset of values with "all" + # all other properties need to be retrieved one by one and are therefore slower + # eg ['mode:1', 'time:1387', 'sp:3.0', 'dist:1150', 'cal:71710', 'step:2117'] properties = ["all"] values = self.get_properties(properties, max_properties=1) + value_map = { + "sp": float, + "step": int, + "cal": int, + "time": int, + "dist": int, + "mode": int, + } + data = {} for x in values: prop, value = x.split(":") - if prop not in ["sp", "step", "cal", "time", "dist", "mode"]: + if prop not in value_map: raise WalkingpadException("Invalid data received: %s" % value) - # Ensure that he different properties are correctly typed - if prop == "sp": - value = float(value) - elif prop == "step": - value = int(value) - elif prop == "cal": - value = int(value) - elif prop == "time": - value = int(value) - elif prop == "dist": - value = int(value) - elif prop == "mode": - value = int(value) - data[prop] = value - return data + converted_data = {key: value_map[key](value) for key, value in data.items()} + + return converted_data From e9342931c5554048ac3b6da1a011e1f424fb6e87 Mon Sep 17 00:00:00 2001 From: dewgenenny Date: Fri, 9 Apr 2021 13:14:42 +0200 Subject: [PATCH 14/14] Change start function to power-on if treadmill is off when called. Also other minor PR feedback changes. --- miio/walkingpad.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/miio/walkingpad.py b/miio/walkingpad.py index 590a8204f..e5470c61d 100644 --- a/miio/walkingpad.py +++ b/miio/walkingpad.py @@ -141,11 +141,13 @@ def status(self) -> WalkingpadStatus: ) ) def quick_status(self) -> WalkingpadStatus: - """Retrieve quick properties. The walkingpad provides the option. + """Retrieve quick status. - to retrieve a subset of properties in one call - steps, mode, - speed, distance, calories and time. If you can, use this instead - of the full status request, as it is way faster. + The walkingpad provides the option to retrieve a subset of properties in one call: + steps, mode, speed, distance, calories and time. + + `status()` will do four more separate I/O requests for power, mode, start_speed, and sensitivity. + If you don't need any of that, prefer this method for status updates. """ data = self._get_quick_status() @@ -175,12 +177,12 @@ def unlock(self): @command(default_output=format_output("Starting the treadmill")) def start(self): """Start the treadmill.""" + + # In case the treadmill is not already turned on, turn it on. if not self.status().is_on: - raise WalkingpadException( - "Can't start the treadmill, it's not turned on - try issuing an 'on' command first" - ) - else: - return self.send("set_state", ["run"]) + self.on(self) + + return self.send("set_state", ["run"]) @command(default_output=format_output("Stopping the treadmill")) def stop(self): @@ -266,7 +268,7 @@ def _get_quick_status(self): prop, value = x.split(":") if prop not in value_map: - raise WalkingpadException("Invalid data received: %s" % value) + _LOGGER.warning("Received unknown data from device: %s=%s", prop, value) data[prop] = value