From dd8b0718d9f73780b153e1c1eedcd6e8fe882473 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 17 Sep 2021 12:06:39 +0200 Subject: [PATCH 01/12] Fix water level calculation --- miio/airhumidifier.py | 5 +++-- miio/airhumidifier_miot.py | 5 +++-- miio/tests/test_airhumidifier.py | 2 +- miio/tests/test_airhumidifier_miot.py | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/miio/airhumidifier.py b/miio/airhumidifier.py index fc69c31d8..28f93b37b 100644 --- a/miio/airhumidifier.py +++ b/miio/airhumidifier.py @@ -198,11 +198,12 @@ def depth(self) -> Optional[int]: def water_level(self) -> Optional[int]: """Return current water level in percent. - If water tank is full, depth is 125. + If water tank is full, depth is 120. + If water tank is overfilled, depth is 125. """ depth = self.data.get("depth") if depth is not None and depth <= 125: - return int(depth / 1.25) + return int(max(min(depth / 1.2, 100), 0)) return None @property diff --git a/miio/airhumidifier_miot.py b/miio/airhumidifier_miot.py index 37d96c13e..5ad00c2f6 100644 --- a/miio/airhumidifier_miot.py +++ b/miio/airhumidifier_miot.py @@ -129,10 +129,11 @@ def target_humidity(self) -> int: def water_level(self) -> Optional[int]: """Return current water level in percent. - If water tank is full, depth is 125. + If water tank is full, raw water_level value is 120. + If water tank is overfilled, raw water_level value is 125. """ if self.data["water_level"] <= 125: - return int(self.data["water_level"] / 1.25) + return int(max(min(self.data["water_level"] / 1.2, 100), 0)) return None @property diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index 578c9f93d..9a0a72c05 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -343,7 +343,7 @@ def test_status(self): assert self.state().trans_level is None assert self.state().motor_speed == self.device.start_state["speed"] assert self.state().depth == self.device.start_state["depth"] - assert self.state().water_level == int(self.device.start_state["depth"] / 1.25) + assert self.state().water_level == int(self.device.start_state["depth"] / 1.2) assert self.state().water_tank_detached == ( self.device.start_state["depth"] == 127 ) diff --git a/miio/tests/test_airhumidifier_miot.py b/miio/tests/test_airhumidifier_miot.py index 0993c1d0d..7eaf85236 100644 --- a/miio/tests/test_airhumidifier_miot.py +++ b/miio/tests/test_airhumidifier_miot.py @@ -80,7 +80,7 @@ def test_status(self): assert status.error == _INITIAL_STATE["fault"] assert status.mode == OperationMode(_INITIAL_STATE["mode"]) assert status.target_humidity == _INITIAL_STATE["target_humidity"] - assert status.water_level == int(_INITIAL_STATE["water_level"] / 1.25) + assert status.water_level == int(_INITIAL_STATE["water_level"] / 1.2) assert status.water_tank_detached == (_INITIAL_STATE["water_level"] == 127) assert status.dry == _INITIAL_STATE["dry"] assert status.use_time == _INITIAL_STATE["use_time"] From 077739600468d301d38b37cb014918da1d130dba Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 17 Sep 2021 12:32:16 +0200 Subject: [PATCH 02/12] Run docformatter --- miio/airhumidifier.py | 4 ++-- miio/airhumidifier_miot.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/miio/airhumidifier.py b/miio/airhumidifier.py index 28f93b37b..ad11b47ec 100644 --- a/miio/airhumidifier.py +++ b/miio/airhumidifier.py @@ -198,8 +198,8 @@ def depth(self) -> Optional[int]: def water_level(self) -> Optional[int]: """Return current water level in percent. - If water tank is full, depth is 120. - If water tank is overfilled, depth is 125. + If water tank is full, depth is 120. If water tank is + overfilled, depth is 125. """ depth = self.data.get("depth") if depth is not None and depth <= 125: diff --git a/miio/airhumidifier_miot.py b/miio/airhumidifier_miot.py index 5ad00c2f6..30e820ebf 100644 --- a/miio/airhumidifier_miot.py +++ b/miio/airhumidifier_miot.py @@ -129,8 +129,8 @@ def target_humidity(self) -> int: def water_level(self) -> Optional[int]: """Return current water level in percent. - If water tank is full, raw water_level value is 120. - If water tank is overfilled, raw water_level value is 125. + If water tank is full, raw water_level value is 120. If water + tank is overfilled, raw water_level value is 125. """ if self.data["water_level"] <= 125: return int(max(min(self.data["water_level"] / 1.2, 100), 0)) From a94f8d92c4fa4917423f63729070e38ba26f0f95 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 17 Sep 2021 12:36:54 +0200 Subject: [PATCH 03/12] Run docformatter once again --- miio/airhumidifier.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/miio/airhumidifier.py b/miio/airhumidifier.py index ad11b47ec..52bb67997 100644 --- a/miio/airhumidifier.py +++ b/miio/airhumidifier.py @@ -136,7 +136,8 @@ def target_humidity(self) -> int: def trans_level(self) -> Optional[int]: """The meaning of the property is unknown. - The property is used to determine the strong mode is enabled on old firmware. + The property is used to determine the strong mode is enabled on + old firmware. """ if "trans_level" in self.data and self.data["trans_level"] is not None: return self.data["trans_level"] @@ -218,8 +219,8 @@ def water_tank_detached(self) -> Optional[bool]: @property def dry(self) -> Optional[bool]: - """Dry mode: The amount of water is not enough to continue to work for about 8 - hours. + """Dry mode: The amount of water is not enough to continue to work for + about 8 hours. Return True if dry mode is on if available. """ From e97121f5d667ea95d0d0a9f669c2a5e3d4078d52 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 28 Sep 2021 10:09:09 +0200 Subject: [PATCH 04/12] Run pre-commit --- miio/airhumidifier.py | 10 ++++------ miio/airhumidifier_miot.py | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/miio/airhumidifier.py b/miio/airhumidifier.py index 52bb67997..0811adf2e 100644 --- a/miio/airhumidifier.py +++ b/miio/airhumidifier.py @@ -136,8 +136,7 @@ def target_humidity(self) -> int: def trans_level(self) -> Optional[int]: """The meaning of the property is unknown. - The property is used to determine the strong mode is enabled on - old firmware. + The property is used to determine the strong mode is enabled on old firmware. """ if "trans_level" in self.data and self.data["trans_level"] is not None: return self.data["trans_level"] @@ -199,8 +198,7 @@ def depth(self) -> Optional[int]: def water_level(self) -> Optional[int]: """Return current water level in percent. - If water tank is full, depth is 120. If water tank is - overfilled, depth is 125. + If water tank is full, depth is 120. If water tank is overfilled, depth is 125. """ depth = self.data.get("depth") if depth is not None and depth <= 125: @@ -219,8 +217,8 @@ def water_tank_detached(self) -> Optional[bool]: @property def dry(self) -> Optional[bool]: - """Dry mode: The amount of water is not enough to continue to work for - about 8 hours. + """Dry mode: The amount of water is not enough to continue to work for about 8 + hours. Return True if dry mode is on if available. """ diff --git a/miio/airhumidifier_miot.py b/miio/airhumidifier_miot.py index 30e820ebf..cb41ff07d 100644 --- a/miio/airhumidifier_miot.py +++ b/miio/airhumidifier_miot.py @@ -129,8 +129,8 @@ def target_humidity(self) -> int: def water_level(self) -> Optional[int]: """Return current water level in percent. - If water tank is full, raw water_level value is 120. If water - tank is overfilled, raw water_level value is 125. + If water tank is full, raw water_level value is 120. If water tank is + overfilled, raw water_level value is 125. """ if self.data["water_level"] <= 125: return int(max(min(self.data["water_level"] / 1.2, 100), 0)) From dbb19bb3e1c6473b0e55bdb19ed84d4935e97ea0 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 28 Sep 2021 11:31:53 +0200 Subject: [PATCH 05/12] Add water level tests --- miio/tests/test_airhumidifier.py | 43 +++++++++++++++++++++++++-- miio/tests/test_airhumidifier_miot.py | 19 ++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index 9a0a72c05..64f29dfa0 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -270,7 +270,7 @@ def __init__(self, *args, **kwargs): "hw_version": 0, # Additional attributes of the zhimi.humidifier.ca1 "speed": 100, - "depth": 1, + "depth": 60, "dry": "off", } self.return_values = { @@ -467,6 +467,25 @@ def dry(): self.device.set_dry(False) assert dry() is False + def test_water_level(self): + self.device.state["depth"] = -1 + assert self.state().water_level == 0 + + self.device.state["depth"] = 0 + assert self.state().water_level == 0 + + self.device.state["depth"] = 60 + assert self.state().water_level == 50 + + self.device.state["depth"] = 120 + assert self.state().water_level == 100 + + self.device.state["depth"] = 125 + assert self.state().water_level == 100 + + self.device.state["depth"] = 127 + assert self.state().water_level is None + class DummyAirHumidifierCB1(DummyDevice, AirHumidifier): def __init__(self, *args, **kwargs): @@ -506,7 +525,7 @@ def __init__(self, *args, **kwargs): # Additional attributes of the zhimi.humidifier.cb1 "temperature": 29.4, "speed": 100, - "depth": 1, + "depth": 60, "dry": "off", } self.return_values = { @@ -579,6 +598,7 @@ def test_status(self): assert self.state().trans_level is None assert self.state().motor_speed == self.device.start_state["speed"] assert self.state().depth == self.device.start_state["depth"] + assert self.state().water_level == self.device.start_state["depth"] / 1.2 assert self.state().dry == (self.device.start_state["dry"] == "on") assert self.state().use_time == self.device.start_state["use_time"] assert self.state().hardware_version == self.device.start_state["hw_version"] @@ -694,3 +714,22 @@ def dry(): self.device.set_dry(False) assert dry() is False + + def test_water_level(self): + self.device._set_state("depth", [-1]) + assert self.state().water_level == 0 + + self.device._set_state("depth", [0]) + assert self.state().water_level == 0 + + self.device._set_state("depth", [60]) + assert self.state().water_level == 50 + + self.device._set_state("depth", [120]) + assert self.state().water_level == 100 + + self.device._set_state("depth", [125]) + assert self.state().water_level == 100 + + self.device._set_state("depth", [127]) + assert self.state().water_level is None \ No newline at end of file diff --git a/miio/tests/test_airhumidifier_miot.py b/miio/tests/test_airhumidifier_miot.py index 7eaf85236..883f48d96 100644 --- a/miio/tests/test_airhumidifier_miot.py +++ b/miio/tests/test_airhumidifier_miot.py @@ -193,3 +193,22 @@ def clean_mode(): self.device.set_clean_mode(False) assert clean_mode() is False + + def test_water_level(self): + self.device.set_property("water_level", -1) + assert self.device.status().water_level == 0 + + self.device.set_property("water_level", 0) + assert self.device.status().water_level == 0 + + self.device.set_property("water_level", 60) + assert self.device.status().water_level == 50 + + self.device.set_property("water_level", 120) + assert self.device.status().water_level == 100 + + self.device.set_property("water_level", 125) + assert self.device.status().water_level == 100 + + self.device.set_property("water_level", 127) + assert self.device.status().water_level is None \ No newline at end of file From 7d45e42cdb7ce4db69bb3704efb8c56e38661a6d Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 28 Sep 2021 11:34:56 +0200 Subject: [PATCH 06/12] Black --- miio/tests/test_airhumidifier.py | 2 +- miio/tests/test_airhumidifier_miot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index 64f29dfa0..d2a2ef825 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -732,4 +732,4 @@ def test_water_level(self): assert self.state().water_level == 100 self.device._set_state("depth", [127]) - assert self.state().water_level is None \ No newline at end of file + assert self.state().water_level is None diff --git a/miio/tests/test_airhumidifier_miot.py b/miio/tests/test_airhumidifier_miot.py index 883f48d96..b2559bfc3 100644 --- a/miio/tests/test_airhumidifier_miot.py +++ b/miio/tests/test_airhumidifier_miot.py @@ -211,4 +211,4 @@ def test_water_level(self): assert self.device.status().water_level == 100 self.device.set_property("water_level", 127) - assert self.device.status().water_level is None \ No newline at end of file + assert self.device.status().water_level is None From 6f67f750137bd93aa1f04b1b8ef8b76214957d89 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 26 Sep 2021 16:35:47 +0200 Subject: [PATCH 07/12] make linters happy --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f65cde381..9be1503a8 100644 --- a/README.rst +++ b/README.rst @@ -189,7 +189,7 @@ Home Assistant (custom) Other related projects ---------------------- -This is a list of other projects around the xiaomi ecosystem that you can find interesting. +This is a list of other projects around the Xiaomi ecosystem that you can find interesting. Feel free to submit more related projects. - `dustcloud `__ (reverse engineering and rooting xiaomi devices) From f47267df7dbb7c1364d680af4f39c7a0ee3e134c Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 3 Oct 2021 19:13:04 +0200 Subject: [PATCH 08/12] Generalize airhumidifer tests, parametrize water_level tests --- miio/tests/test_airhumidifier.py | 782 +++++++------------------------ 1 file changed, 180 insertions(+), 602 deletions(-) diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index d2a2ef825..a0d627a5a 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -1,14 +1,13 @@ -from unittest import TestCase +from contextlib import nullcontext import pytest -from miio import AirHumidifier +from miio import AirHumidifier, DeviceException from miio.airhumidifier import ( MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1, MODEL_HUMIDIFIER_V1, AirHumidifierException, - AirHumidifierStatus, LedBrightness, OperationMode, ) @@ -17,11 +16,10 @@ from .dummies import DummyDevice -class DummyAirHumidifierV1(DummyDevice, AirHumidifier): - def __init__(self, *args, **kwargs): - self._model = MODEL_HUMIDIFIER_V1 +class DummyAirHumidifier(DummyDevice, AirHumidifier): + def __init__(self, model, *args, **kwargs): + self._model = model self.dummy_device_info = { - "fw_ver": "1.2.9_5033", "token": "68ffffffffffffffffffffffffffffff", "otu_stat": [101, 74, 5343, 0, 5327, 407], "mmfree": 228248, @@ -40,7 +38,11 @@ def __init__(self, *args, **kwargs): "ot": "otu", "mac": "78:11:FF:FF:FF:FF", } - self.device_info = None + + # Special version handling for CA1 + self.dummy_device_info["fw_ver"] = ( + "1.6.6" if self._model == MODEL_HUMIDIFIER_CA1 else "1.2.9_5033" + ) self.state = { "power": "on", @@ -51,239 +53,49 @@ def __init__(self, *args, **kwargs): "led_b": 2, "child_lock": "on", "limit_hum": 40, - "trans_level": 85, "use_time": 941100, - "button_pressed": "led", "hw_version": 0, } + 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_led_b": lambda x: self._set_state("led_b", x), + "set_led_b": lambda x: self._set_state("led_b", [int(x[0])]), "set_buzzer": lambda x: self._set_state("buzzer", x), "set_child_lock": lambda x: self._set_state("child_lock", x), "set_limit_hum": lambda x: self._set_state("limit_hum", x), + "set_dry": lambda x: self._set_state("dry", x), "miIO.info": self._get_device_info, } - super().__init__(args, kwargs) - - def _get_device_info(self, _): - """Return dummy device info.""" - return self.dummy_device_info - - -@pytest.fixture(scope="class") -def airhumidifierv1(request): - request.cls.device = DummyAirHumidifierV1() - # TODO add ability to test on a real device - - -@pytest.mark.usefixtures("airhumidifierv1") -class TestAirHumidifierV1(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() - - device_info = DeviceInfo(self.device.dummy_device_info) - - assert repr(self.state()) == repr( - AirHumidifierStatus(self.device.start_state, device_info) - ) - - assert self.is_on() is True - assert self.state().temperature == self.device.start_state["temp_dec"] / 10.0 - assert self.state().humidity == self.device.start_state["humidity"] - assert self.state().mode == OperationMode(self.device.start_state["mode"]) - assert self.state().led_brightness == LedBrightness( - self.device.start_state["led_b"] - ) - assert self.state().buzzer == (self.device.start_state["buzzer"] == "on") - assert self.state().child_lock == ( - self.device.start_state["child_lock"] == "on" - ) - assert self.state().target_humidity == self.device.start_state["limit_hum"] - assert self.state().trans_level == self.device.start_state["trans_level"] - assert self.state().motor_speed is None - assert self.state().depth is None - assert self.state().dry is None - assert self.state().use_time == self.device.start_state["use_time"] - assert self.state().hardware_version == self.device.start_state["hw_version"] - assert self.state().button_pressed == self.device.start_state["button_pressed"] - - assert self.state().firmware_version == device_info.firmware_version - assert ( - self.state().firmware_version_major - == device_info.firmware_version.rsplit("_", 1)[0] - ) - assert self.state().firmware_version_minor == int( - device_info.firmware_version.rsplit("_", 1)[1] - ) - assert self.state().strong_mode_enabled is False - - def test_set_mode(self): - def mode(): - return self.device.status().mode - - self.device.set_mode(OperationMode.Silent) - assert mode() == OperationMode.Silent - - self.device.set_mode(OperationMode.Medium) - assert mode() == OperationMode.Medium - - self.device.set_mode(OperationMode.High) - assert mode() == OperationMode.High - - def test_set_led_brightness(self): - def led_brightness(): - return self.device.status().led_brightness - - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright - - self.device.set_led_brightness(LedBrightness.Dim) - assert led_brightness() == LedBrightness.Dim - - self.device.set_led_brightness(LedBrightness.Off) - assert led_brightness() == LedBrightness.Off - - def test_set_led(self): - def led_brightness(): - return self.device.status().led_brightness - - self.device.set_led(True) - assert led_brightness() == LedBrightness.Bright - - self.device.set_led(False) - assert led_brightness() == LedBrightness.Off - - def test_set_buzzer(self): - def buzzer(): - return self.device.status().buzzer - - self.device.set_buzzer(True) - assert buzzer() is True - - self.device.set_buzzer(False) - assert buzzer() is False - - def test_status_without_temperature(self): - self.device._reset_state() - self.device.state["temp_dec"] = None - - assert self.state().temperature is None - - def test_status_without_led_brightness(self): - self.device._reset_state() - self.device.state["led_b"] = None - - assert self.state().led_brightness is None - - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity - - self.device.set_target_humidity(30) - assert target_humidity() == 30 - self.device.set_target_humidity(60) - assert target_humidity() == 60 - self.device.set_target_humidity(80) - assert target_humidity() == 80 - - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(-1) - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(20) + if model == MODEL_HUMIDIFIER_V1: + # V1 has some extra properties that are not currently tested + self.state["trans_level"] = 85 + self.state["button_pressed"] = "led" - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(90) + # V1 doesn't support try, so return an error + from miio import DeviceError - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(110) + def raise_error(): + raise DeviceError("blah") - def test_set_child_lock(self): - def child_lock(): - return self.device.status().child_lock + self.return_values["set_dry"] = lambda x: raise_error - self.device.set_child_lock(True) - assert child_lock() is True + elif model in [MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1]: + # Additional attributes of the CA1 & CB1 + extra_states = { + "speed": 100, + "depth": 60, + "dry": "off", + } + self.state.update(extra_states) - self.device.set_child_lock(False) - assert child_lock() is False + # CB1 reports temperature differently + if self._model == MODEL_HUMIDIFIER_CB1: + self.state["temperature"] = self.state["temp_dec"] / 10.0 + del self.state["temp_dec"] - -class DummyAirHumidifierCA1(DummyDevice, AirHumidifier): - def __init__(self, *args, **kwargs): - self._model = MODEL_HUMIDIFIER_CA1 - self.dummy_device_info = { - "fw_ver": "1.6.6", - "token": "68ffffffffffffffffffffffffffffff", - "otu_stat": [101, 74, 5343, 0, 5327, 407], - "mmfree": 228248, - "netif": { - "gw": "192.168.0.1", - "localIp": "192.168.0.25", - "mask": "255.255.255.0", - }, - "ott_stat": [0, 0, 0, 0], - "model": "zhimi.humidifier.v1", - "cfg_time": 0, - "life": 575661, - "ap": {"rssi": -35, "ssid": "ap", "bssid": "FF:FF:FF:FF:FF:FF"}, - "wifi_fw_ver": "SD878x-14.76.36.p84-702.1.0-WM", - "hw_ver": "MW300", - "ot": "otu", - "mac": "78:11:FF:FF:FF:FF", - } - self.device_info = None - - self.state = { - "power": "on", - "mode": "medium", - "temp_dec": 294, - "humidity": 33, - "buzzer": "off", - "led_b": 2, - "child_lock": "on", - "limit_hum": 40, - "use_time": 941100, - "hw_version": 0, - # Additional attributes of the zhimi.humidifier.ca1 - "speed": 100, - "depth": 60, - "dry": "off", - } - 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_led_b": lambda x: self._set_state("led_b", [int(x[0])]), - "set_buzzer": lambda x: self._set_state("buzzer", x), - "set_child_lock": lambda x: self._set_state("child_lock", x), - "set_limit_hum": lambda x: self._set_state("limit_hum", x), - "set_dry": lambda x: self._set_state("dry", x), - "miIO.info": self._get_device_info, - } super().__init__(args, kwargs) def _get_device_info(self, _): @@ -291,445 +103,211 @@ def _get_device_info(self, _): return self.dummy_device_info -@pytest.fixture(scope="class") -def airhumidifierca1(request): - request.cls.device = DummyAirHumidifierCA1() +@pytest.fixture( + params=[MODEL_HUMIDIFIER_V1, MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1] +) +def dev(request): + yield DummyAirHumidifier(model=request.param) # TODO add ability to test on a real device -@pytest.mark.usefixtures("airhumidifierca1") -class TestAirHumidifierCA1(TestCase): - def is_on(self): - return self.device.status().is_on +def test_on(dev): + dev.off() # ensure off + assert dev.status().is_on is False - def state(self): - return self.device.status() + dev.on() + assert dev.status().is_on is True - 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(dev): + dev.on() # ensure on + assert dev.status().is_on is True - def test_off(self): - self.device.on() # ensure on - assert self.is_on() is True + dev.off() + assert dev.status().is_on is False - self.device.off() - assert self.is_on() is False - def test_status(self): - self.device._reset_state() +def test_set_mode(dev): + def mode(): + return dev.status().mode - device_info = DeviceInfo(self.device.dummy_device_info) + dev.set_mode(OperationMode.Silent) + assert mode() == OperationMode.Silent - assert repr(self.state()) == repr( - AirHumidifierStatus(self.device.start_state, device_info) - ) + dev.set_mode(OperationMode.Medium) + assert mode() == OperationMode.Medium - assert self.is_on() is True - assert self.state().temperature == self.device.start_state["temp_dec"] / 10.0 - assert self.state().humidity == self.device.start_state["humidity"] - assert self.state().mode == OperationMode(self.device.start_state["mode"]) - assert self.state().led_brightness == LedBrightness( - self.device.start_state["led_b"] - ) - assert self.state().buzzer == (self.device.start_state["buzzer"] == "on") - assert self.state().child_lock == ( - self.device.start_state["child_lock"] == "on" - ) - assert self.state().target_humidity == self.device.start_state["limit_hum"] - assert self.state().trans_level is None - assert self.state().motor_speed == self.device.start_state["speed"] - assert self.state().depth == self.device.start_state["depth"] - assert self.state().water_level == int(self.device.start_state["depth"] / 1.2) - assert self.state().water_tank_detached == ( - self.device.start_state["depth"] == 127 - ) - assert self.state().dry == (self.device.start_state["dry"] == "on") - assert self.state().use_time == self.device.start_state["use_time"] - assert self.state().hardware_version == self.device.start_state["hw_version"] - assert self.state().button_pressed is None - - assert self.state().firmware_version == device_info.firmware_version - assert ( - self.state().firmware_version_major - == device_info.firmware_version.rsplit("_", 1)[0] - ) - - try: - version_minor = int(device_info.firmware_version.rsplit("_", 1)[1]) - except IndexError: - version_minor = 0 - - assert self.state().firmware_version_minor == version_minor - assert self.state().strong_mode_enabled is False - - def test_set_mode(self): - def mode(): - return self.device.status().mode - - self.device.set_mode(OperationMode.Silent) - assert mode() == OperationMode.Silent - - self.device.set_mode(OperationMode.Medium) - assert mode() == OperationMode.Medium - - self.device.set_mode(OperationMode.High) - assert mode() == OperationMode.High - - def test_set_led_brightness(self): - def led_brightness(): - return self.device.status().led_brightness - - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright - - self.device.set_led_brightness(LedBrightness.Dim) - assert led_brightness() == LedBrightness.Dim - - self.device.set_led_brightness(LedBrightness.Off) - assert led_brightness() == LedBrightness.Off - - def test_set_led(self): - def led_brightness(): - return self.device.status().led_brightness - - self.device.set_led(True) - assert led_brightness() == LedBrightness.Bright - - self.device.set_led(False) - assert led_brightness() == LedBrightness.Off - - def test_set_buzzer(self): - def buzzer(): - return self.device.status().buzzer - - self.device.set_buzzer(True) - assert buzzer() is True - - self.device.set_buzzer(False) - assert buzzer() is False - - def test_status_without_temperature(self): - self.device._reset_state() - self.device.state["temp_dec"] = None - - assert self.state().temperature is None - - def test_status_without_led_brightness(self): - self.device._reset_state() - self.device.state["led_b"] = None - - assert self.state().led_brightness is None - - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity - - self.device.set_target_humidity(30) - assert target_humidity() == 30 - self.device.set_target_humidity(60) - assert target_humidity() == 60 - self.device.set_target_humidity(80) - assert target_humidity() == 80 - - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(-1) - - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(20) + dev.set_mode(OperationMode.High) + assert mode() == OperationMode.High - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(90) - - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(110) - - def test_set_child_lock(self): - def child_lock(): - return self.device.status().child_lock - - self.device.set_child_lock(True) - assert child_lock() is True - - self.device.set_child_lock(False) - assert child_lock() is False - - def test_set_dry(self): - def dry(): - return self.device.status().dry - - self.device.set_dry(True) - assert dry() is True - - self.device.set_dry(False) - assert dry() is False - - def test_water_level(self): - self.device.state["depth"] = -1 - assert self.state().water_level == 0 - - self.device.state["depth"] = 0 - assert self.state().water_level == 0 - - self.device.state["depth"] = 60 - assert self.state().water_level == 50 - - self.device.state["depth"] = 120 - assert self.state().water_level == 100 - - self.device.state["depth"] = 125 - assert self.state().water_level == 100 - - self.device.state["depth"] = 127 - assert self.state().water_level is None - - -class DummyAirHumidifierCB1(DummyDevice, AirHumidifier): - def __init__(self, *args, **kwargs): - self._model = MODEL_HUMIDIFIER_CB1 - self.dummy_device_info = { - "fw_ver": "1.2.9_5033", - "token": "68ffffffffffffffffffffffffffffff", - "otu_stat": [101, 74, 5343, 0, 5327, 407], - "mmfree": 228248, - "netif": { - "gw": "192.168.0.1", - "localIp": "192.168.0.25", - "mask": "255.255.255.0", - }, - "ott_stat": [0, 0, 0, 0], - "model": "zhimi.humidifier.v1", - "cfg_time": 0, - "life": 575661, - "ap": {"rssi": -35, "ssid": "ap", "bssid": "FF:FF:FF:FF:FF:FF"}, - "wifi_fw_ver": "SD878x-14.76.36.p84-702.1.0-WM", - "hw_ver": "MW300", - "ot": "otu", - "mac": "78:11:FF:FF:FF:FF", - } - self.device_info = None - self.state = { - "power": "on", - "mode": "medium", - "humidity": 33, - "buzzer": "off", - "led_b": 2, - "child_lock": "on", - "limit_hum": 40, - "use_time": 941100, - "hw_version": 0, - # Additional attributes of the zhimi.humidifier.cb1 - "temperature": 29.4, - "speed": 100, - "depth": 60, - "dry": "off", - } - 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_led_b": lambda x: self._set_state("led_b", [int(x[0])]), - "set_buzzer": lambda x: self._set_state("buzzer", x), - "set_child_lock": lambda x: self._set_state("child_lock", x), - "set_limit_hum": lambda x: self._set_state("limit_hum", x), - "set_dry": lambda x: self._set_state("dry", x), - "miIO.info": self._get_device_info, - } - super().__init__(args, kwargs) +def test_set_led(dev): + def led_brightness(): + return dev.status().led_brightness - def _get_device_info(self, _): - """Return dummy device info.""" - return self.dummy_device_info + dev.set_led(True) + assert led_brightness() == LedBrightness.Bright + dev.set_led(False) + assert led_brightness() == LedBrightness.Off -@pytest.fixture(scope="class") -def airhumidifiercb1(request): - request.cls.device = DummyAirHumidifierCB1() - # TODO add ability to test on a real device +def test_set_buzzer(dev): + def buzzer(): + return dev.status().buzzer -@pytest.mark.usefixtures("airhumidifiercb1") -class TestAirHumidifierCB1(TestCase): - def is_on(self): - return self.device.status().is_on + dev.set_buzzer(True) + assert buzzer() is True - def state(self): - return self.device.status() + dev.set_buzzer(False) + assert buzzer() is False - 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_status_without_temperature(dev): + key = "temperature" if dev.model == MODEL_HUMIDIFIER_CB1 else "temp_dec" + dev.state[key] = None - def test_off(self): - self.device.on() # ensure on - assert self.is_on() is True + assert dev.status().temperature is None - self.device.off() - assert self.is_on() is False - def test_status(self): - self.device._reset_state() +def test_status_without_led_brightness(dev): + dev.state["led_b"] = None - device_info = DeviceInfo(self.device.dummy_device_info) + assert dev.status().led_brightness is None - assert repr(self.state()) == repr( - AirHumidifierStatus(self.device.start_state, device_info) - ) - assert self.is_on() is True - assert self.state().temperature == self.device.start_state["temperature"] - assert self.state().humidity == self.device.start_state["humidity"] - assert self.state().mode == OperationMode(self.device.start_state["mode"]) - assert self.state().led_brightness == LedBrightness( - self.device.start_state["led_b"] - ) - assert self.state().buzzer == (self.device.start_state["buzzer"] == "on") - assert self.state().child_lock == ( - self.device.start_state["child_lock"] == "on" - ) - assert self.state().target_humidity == self.device.start_state["limit_hum"] - assert self.state().trans_level is None - assert self.state().motor_speed == self.device.start_state["speed"] - assert self.state().depth == self.device.start_state["depth"] - assert self.state().water_level == self.device.start_state["depth"] / 1.2 - assert self.state().dry == (self.device.start_state["dry"] == "on") - assert self.state().use_time == self.device.start_state["use_time"] - assert self.state().hardware_version == self.device.start_state["hw_version"] - assert self.state().button_pressed is None - - assert self.state().firmware_version == device_info.firmware_version - assert ( - self.state().firmware_version_major - == device_info.firmware_version.rsplit("_", 1)[0] - ) - assert self.state().firmware_version_minor == int( - device_info.firmware_version.rsplit("_", 1)[1] - ) - assert self.state().strong_mode_enabled is False +def test_set_target_humidity(dev): + def target_humidity(): + return dev.status().target_humidity - def test_set_mode(self): - def mode(): - return self.device.status().mode + dev.set_target_humidity(30) + assert target_humidity() == 30 + dev.set_target_humidity(60) + assert target_humidity() == 60 + dev.set_target_humidity(80) + assert target_humidity() == 80 - self.device.set_mode(OperationMode.Silent) - assert mode() == OperationMode.Silent + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(-1) - self.device.set_mode(OperationMode.Medium) - assert mode() == OperationMode.Medium + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(20) - self.device.set_mode(OperationMode.High) - assert mode() == OperationMode.High + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(90) - def test_set_led_brightness(self): - def led_brightness(): - return self.device.status().led_brightness + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(110) - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright - self.device.set_led_brightness(LedBrightness.Dim) - assert led_brightness() == LedBrightness.Dim +def test_set_child_lock(dev): + def child_lock(): + return dev.status().child_lock - self.device.set_led_brightness(LedBrightness.Off) - assert led_brightness() == LedBrightness.Off + dev.set_child_lock(True) + assert child_lock() is True - def test_set_led(self): - def led_brightness(): - return self.device.status().led_brightness + dev.set_child_lock(False) + assert child_lock() is False - self.device.set_led(True) - assert led_brightness() == LedBrightness.Bright - self.device.set_led(False) - assert led_brightness() == LedBrightness.Off +def test_status(dev): + assert dev.status().is_on is True + assert dev.status().humidity == dev.start_state["humidity"] + assert dev.status().mode == OperationMode(dev.start_state["mode"]) + assert dev.status().led_brightness == LedBrightness(dev.start_state["led_b"]) + assert dev.status().buzzer == (dev.start_state["buzzer"] == "on") + assert dev.status().child_lock == (dev.start_state["child_lock"] == "on") + assert dev.status().target_humidity == dev.start_state["limit_hum"] - def test_set_buzzer(self): - def buzzer(): - return self.device.status().buzzer + if dev.model == MODEL_HUMIDIFIER_CB1: + assert dev.status().temperature == dev.start_state["temperature"] + else: + assert dev.status().temperature == dev.start_state["temp_dec"] / 10.0 - self.device.set_buzzer(True) - assert buzzer() is True + if dev.model == MODEL_HUMIDIFIER_V1: + # Extra props only on v1 + assert dev.status().trans_level == dev.start_state["trans_level"] + assert dev.status().button_pressed == dev.start_state["button_pressed"] - self.device.set_buzzer(False) - assert buzzer() is False + assert dev.status().motor_speed is None + assert dev.status().depth is None + assert dev.status().dry is None + assert dev.status().water_level is None + assert dev.status().water_tank_detached is None - def test_status_without_temperature(self): - self.device._reset_state() - self.device.state["temperature"] = None + if dev.model in [MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1]: + assert dev.status().motor_speed == dev.start_state["speed"] + assert dev.status().depth == dev.start_state["depth"] + assert dev.status().water_level == int(dev.start_state["depth"] / 1.2) + assert dev.status().water_tank_detached == (dev.start_state["depth"] == 127) + assert dev.status().dry == (dev.start_state["dry"] == "on") - assert self.state().temperature is None + # Extra props only on v1 should be none now + assert dev.status().trans_level is None + assert dev.status().button_pressed is None - def test_status_without_led_brightness(self): - self.device._reset_state() - self.device.state["led_b"] = None + assert dev.status().use_time == dev.start_state["use_time"] + assert dev.status().hardware_version == dev.start_state["hw_version"] - assert self.state().led_brightness is None + device_info = DeviceInfo(dev.dummy_device_info) + assert dev.status().firmware_version == device_info.firmware_version + assert ( + dev.status().firmware_version_major + == device_info.firmware_version.rsplit("_", 1)[0] + ) - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity + try: + version_minor = int(device_info.firmware_version.rsplit("_", 1)[1]) + except IndexError: + version_minor = 0 - self.device.set_target_humidity(30) - assert target_humidity() == 30 - self.device.set_target_humidity(60) - assert target_humidity() == 60 - self.device.set_target_humidity(80) - assert target_humidity() == 80 + assert dev.status().firmware_version_minor == version_minor + assert dev.status().strong_mode_enabled is False - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(-1) - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(20) +def test_set_led_brightness(dev): + def led_brightness(): + return dev.status().led_brightness - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(90) + dev.set_led_brightness(LedBrightness.Bright) + assert led_brightness() == LedBrightness.Bright - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(110) + dev.set_led_brightness(LedBrightness.Dim) + assert led_brightness() == LedBrightness.Dim - def test_set_child_lock(self): - def child_lock(): - return self.device.status().child_lock + dev.set_led_brightness(LedBrightness.Off) + assert led_brightness() == LedBrightness.Off - self.device.set_child_lock(True) - assert child_lock() is True - self.device.set_child_lock(False) - assert child_lock() is False +def test_set_dry(dev): + def dry(): + return dev.status().dry - def test_set_dry(self): - def dry(): - return self.device.status().dry + # set_dry is not supported on V1 + if dev.model == MODEL_HUMIDIFIER_V1: + context = pytest.raises(DeviceException) + else: + context = nullcontext() - self.device.set_dry(True) + with context: + dev.set_dry(True) assert dry() is True - self.device.set_dry(False) + with context: + dev.set_dry(False) assert dry() is False - def test_water_level(self): - self.device._set_state("depth", [-1]) - assert self.state().water_level == 0 - - self.device._set_state("depth", [0]) - assert self.state().water_level == 0 - - self.device._set_state("depth", [60]) - assert self.state().water_level == 50 - self.device._set_state("depth", [120]) - assert self.state().water_level == 100 - - self.device._set_state("depth", [125]) - assert self.state().water_level == 100 - - self.device._set_state("depth", [127]) - assert self.state().water_level is None +@pytest.mark.parametrize( + "depth,expected", [(-1, 0), (0, 0), (60, 50), (120, 100), (125, 100), (127, None)] +) +def test_water_level(dev, depth, expected): + """Test the water level conversions.""" + if dev.model == MODEL_HUMIDIFIER_V1: + # Water level is always none for v1 + assert dev.status().water_level is None + return + + dev.state["depth"] = depth + assert dev.status().water_level == expected From f75ba5b07a5e37cb90b9d7226d6a47b70edcae73 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 3 Oct 2021 19:23:41 +0200 Subject: [PATCH 09/12] Cleanup set_dry test --- miio/tests/test_airhumidifier.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index a0d627a5a..7c241d654 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -1,5 +1,3 @@ -from contextlib import nullcontext - import pytest from miio import AirHumidifier, DeviceException @@ -286,17 +284,17 @@ def dry(): # set_dry is not supported on V1 if dev.model == MODEL_HUMIDIFIER_V1: - context = pytest.raises(DeviceException) - else: - context = nullcontext() + assert dry() is None + with pytest.raises(DeviceException): + dev.set_dry(True) + + return - with context: - dev.set_dry(True) - assert dry() is True + dev.set_dry(True) + assert dry() is True - with context: - dev.set_dry(False) - assert dry() is False + dev.set_dry(False) + assert dry() is False @pytest.mark.parametrize( From 251965cbebe7bf53ce7d655325e1f20f1a912ff4 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 3 Oct 2021 19:34:14 +0200 Subject: [PATCH 10/12] Fix set_dry tests on v1 --- miio/tests/test_airhumidifier.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index 7c241d654..71f54235f 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -73,12 +73,10 @@ def __init__(self, model, *args, **kwargs): self.state["button_pressed"] = "led" # V1 doesn't support try, so return an error - from miio import DeviceError - def raise_error(): - raise DeviceError("blah") + raise DeviceException("v1 does not support set_dry") - self.return_values["set_dry"] = lambda x: raise_error + self.return_values["set_dry"] = lambda x: raise_error() elif model in [MODEL_HUMIDIFIER_CA1, MODEL_HUMIDIFIER_CB1]: # Additional attributes of the CA1 & CB1 From 34116d16efcfaf7cadb491b6238375f75db87781 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 3 Oct 2021 19:48:19 +0200 Subject: [PATCH 11/12] Simplify water_level logic --- miio/airhumidifier.py | 10 +++++++--- miio/airhumidifier_miot.py | 11 ++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/miio/airhumidifier.py b/miio/airhumidifier.py index 0811adf2e..d084e8a0b 100644 --- a/miio/airhumidifier.py +++ b/miio/airhumidifier.py @@ -201,9 +201,13 @@ def water_level(self) -> Optional[int]: If water tank is full, depth is 120. If water tank is overfilled, depth is 125. """ depth = self.data.get("depth") - if depth is not None and depth <= 125: - return int(max(min(depth / 1.2, 100), 0)) - return None + if depth is None or depth > 125: + return None + + if depth < 0: + return 0 + + return int(min(depth / 1.2, 100)) @property def water_tank_detached(self) -> Optional[bool]: diff --git a/miio/airhumidifier_miot.py b/miio/airhumidifier_miot.py index cb41ff07d..72b09b7be 100644 --- a/miio/airhumidifier_miot.py +++ b/miio/airhumidifier_miot.py @@ -132,9 +132,14 @@ def water_level(self) -> Optional[int]: If water tank is full, raw water_level value is 120. If water tank is overfilled, raw water_level value is 125. """ - if self.data["water_level"] <= 125: - return int(max(min(self.data["water_level"] / 1.2, 100), 0)) - return None + water_level = self.data["water_level"] + if water_level > 125: + return None + + if water_level < 0: + return 0 + + return int(min(water_level / 1.2, 100)) @property def water_tank_detached(self) -> bool: From 8497e0d68ebc5d76329bb5d217b3e088e547407a Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sun, 3 Oct 2021 20:10:49 +0200 Subject: [PATCH 12/12] Convert test_airhumidifer_miot away from TestCase --- miio/tests/test_airhumidifier_miot.py | 235 +++++++++++++------------- 1 file changed, 115 insertions(+), 120 deletions(-) diff --git a/miio/tests/test_airhumidifier_miot.py b/miio/tests/test_airhumidifier_miot.py index b2559bfc3..317234eac 100644 --- a/miio/tests/test_airhumidifier_miot.py +++ b/miio/tests/test_airhumidifier_miot.py @@ -1,5 +1,3 @@ -from unittest import TestCase - import pytest from miio import AirHumidifierMiot @@ -53,162 +51,159 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture(scope="function") -def airhumidifier(request): - request.cls.device = DummyAirHumidifierMiot() +@pytest.fixture() +def dev(request): + yield DummyAirHumidifierMiot() + + +def test_on(dev): + dev.off() # ensure off + assert dev.status().is_on is False + + dev.on() + assert dev.status().is_on is True -@pytest.mark.usefixtures("airhumidifier") -class TestAirHumidifier(TestCase): - def test_on(self): - self.device.off() # ensure off - assert self.device.status().is_on is False +def test_off(dev): + dev.on() # ensure on + assert dev.status().is_on is True - self.device.on() - assert self.device.status().is_on is True + dev.off() + assert dev.status().is_on is False - def test_off(self): - self.device.on() # ensure on - assert self.device.status().is_on is True - self.device.off() - assert self.device.status().is_on is False +def test_status(dev): + status = dev.status() + assert status.is_on is _INITIAL_STATE["power"] + assert status.error == _INITIAL_STATE["fault"] + assert status.mode == OperationMode(_INITIAL_STATE["mode"]) + assert status.target_humidity == _INITIAL_STATE["target_humidity"] + assert status.water_level == int(_INITIAL_STATE["water_level"] / 1.2) + assert status.water_tank_detached == (_INITIAL_STATE["water_level"] == 127) + assert status.dry == _INITIAL_STATE["dry"] + assert status.use_time == _INITIAL_STATE["use_time"] + assert status.button_pressed == PressedButton(_INITIAL_STATE["button_pressed"]) + assert status.motor_speed == _INITIAL_STATE["speed_level"] + assert status.temperature == _INITIAL_STATE["temperature"] + assert status.fahrenheit == _INITIAL_STATE["fahrenheit"] + assert status.humidity == _INITIAL_STATE["humidity"] + assert status.buzzer == _INITIAL_STATE["buzzer"] + assert status.led_brightness == LedBrightness(_INITIAL_STATE["led_brightness"]) + assert status.child_lock == _INITIAL_STATE["child_lock"] + assert status.actual_speed == _INITIAL_STATE["actual_speed"] + assert status.power_time == _INITIAL_STATE["power_time"] - def test_status(self): - status = self.device.status() - assert status.is_on is _INITIAL_STATE["power"] - assert status.error == _INITIAL_STATE["fault"] - assert status.mode == OperationMode(_INITIAL_STATE["mode"]) - assert status.target_humidity == _INITIAL_STATE["target_humidity"] - assert status.water_level == int(_INITIAL_STATE["water_level"] / 1.2) - assert status.water_tank_detached == (_INITIAL_STATE["water_level"] == 127) - assert status.dry == _INITIAL_STATE["dry"] - assert status.use_time == _INITIAL_STATE["use_time"] - assert status.button_pressed == PressedButton(_INITIAL_STATE["button_pressed"]) - assert status.motor_speed == _INITIAL_STATE["speed_level"] - assert status.temperature == _INITIAL_STATE["temperature"] - assert status.fahrenheit == _INITIAL_STATE["fahrenheit"] - assert status.humidity == _INITIAL_STATE["humidity"] - assert status.buzzer == _INITIAL_STATE["buzzer"] - assert status.led_brightness == LedBrightness(_INITIAL_STATE["led_brightness"]) - assert status.child_lock == _INITIAL_STATE["child_lock"] - assert status.actual_speed == _INITIAL_STATE["actual_speed"] - assert status.power_time == _INITIAL_STATE["power_time"] - def test_set_speed(self): - def speed_level(): - return self.device.status().motor_speed +def test_set_speed(dev): + def speed_level(): + return dev.status().motor_speed - self.device.set_speed(200) - assert speed_level() == 200 - self.device.set_speed(2000) - assert speed_level() == 2000 + dev.set_speed(200) + assert speed_level() == 200 + dev.set_speed(2000) + assert speed_level() == 2000 - with pytest.raises(AirHumidifierMiotException): - self.device.set_speed(199) + with pytest.raises(AirHumidifierMiotException): + dev.set_speed(199) - with pytest.raises(AirHumidifierMiotException): - self.device.set_speed(2001) + with pytest.raises(AirHumidifierMiotException): + dev.set_speed(2001) - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity - self.device.set_target_humidity(30) - assert target_humidity() == 30 - self.device.set_target_humidity(80) - assert target_humidity() == 80 +def test_set_target_humidity(dev): + def target_humidity(): + return dev.status().target_humidity - with pytest.raises(AirHumidifierMiotException): - self.device.set_target_humidity(29) + dev.set_target_humidity(30) + assert target_humidity() == 30 + dev.set_target_humidity(80) + assert target_humidity() == 80 - with pytest.raises(AirHumidifierMiotException): - self.device.set_target_humidity(81) + with pytest.raises(AirHumidifierMiotException): + dev.set_target_humidity(29) - def test_set_mode(self): - def mode(): - return self.device.status().mode + with pytest.raises(AirHumidifierMiotException): + dev.set_target_humidity(81) - self.device.set_mode(OperationMode.Auto) - assert mode() == OperationMode.Auto - self.device.set_mode(OperationMode.Low) - assert mode() == OperationMode.Low +def test_set_mode(dev): + def mode(): + return dev.status().mode - self.device.set_mode(OperationMode.Mid) - assert mode() == OperationMode.Mid + dev.set_mode(OperationMode.Auto) + assert mode() == OperationMode.Auto - self.device.set_mode(OperationMode.High) - assert mode() == OperationMode.High + dev.set_mode(OperationMode.Low) + assert mode() == OperationMode.Low - def test_set_led_brightness(self): - def led_brightness(): - return self.device.status().led_brightness + dev.set_mode(OperationMode.Mid) + assert mode() == OperationMode.Mid - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright + dev.set_mode(OperationMode.High) + assert mode() == OperationMode.High - self.device.set_led_brightness(LedBrightness.Dim) - assert led_brightness() == LedBrightness.Dim - self.device.set_led_brightness(LedBrightness.Off) - assert led_brightness() == LedBrightness.Off +def test_set_led_brightness(dev): + def led_brightness(): + return dev.status().led_brightness - def test_set_buzzer(self): - def buzzer(): - return self.device.status().buzzer + dev.set_led_brightness(LedBrightness.Bright) + assert led_brightness() == LedBrightness.Bright - self.device.set_buzzer(True) - assert buzzer() is True + dev.set_led_brightness(LedBrightness.Dim) + assert led_brightness() == LedBrightness.Dim - self.device.set_buzzer(False) - assert buzzer() is False + dev.set_led_brightness(LedBrightness.Off) + assert led_brightness() == LedBrightness.Off - def test_set_child_lock(self): - def child_lock(): - return self.device.status().child_lock - self.device.set_child_lock(True) - assert child_lock() is True +def test_set_buzzer(dev): + def buzzer(): + return dev.status().buzzer - self.device.set_child_lock(False) - assert child_lock() is False + dev.set_buzzer(True) + assert buzzer() is True - def test_set_dry(self): - def dry(): - return self.device.status().dry + dev.set_buzzer(False) + assert buzzer() is False - self.device.set_dry(True) - assert dry() is True - self.device.set_dry(False) - assert dry() is False +def test_set_child_lock(dev): + def child_lock(): + return dev.status().child_lock - def test_set_clean_mode(self): - def clean_mode(): - return self.device.status().clean_mode + dev.set_child_lock(True) + assert child_lock() is True - self.device.set_clean_mode(True) - assert clean_mode() is True + dev.set_child_lock(False) + assert child_lock() is False - self.device.set_clean_mode(False) - assert clean_mode() is False - def test_water_level(self): - self.device.set_property("water_level", -1) - assert self.device.status().water_level == 0 +def test_set_dry(dev): + def dry(): + return dev.status().dry - self.device.set_property("water_level", 0) - assert self.device.status().water_level == 0 + dev.set_dry(True) + assert dry() is True - self.device.set_property("water_level", 60) - assert self.device.status().water_level == 50 + dev.set_dry(False) + assert dry() is False - self.device.set_property("water_level", 120) - assert self.device.status().water_level == 100 - self.device.set_property("water_level", 125) - assert self.device.status().water_level == 100 +def test_set_clean_mode(dev): + def clean_mode(): + return dev.status().clean_mode - self.device.set_property("water_level", 127) - assert self.device.status().water_level is None + dev.set_clean_mode(True) + assert clean_mode() is True + + dev.set_clean_mode(False) + assert clean_mode() is False + + +@pytest.mark.parametrize( + "depth,expected", [(-1, 0), (0, 0), (60, 50), (120, 100), (125, 100), (127, None)] +) +def test_water_level(dev, depth, expected): + dev.set_property("water_level", depth) + assert dev.status().water_level == expected