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) diff --git a/miio/airhumidifier.py b/miio/airhumidifier.py index fc69c31d8..d084e8a0b 100644 --- a/miio/airhumidifier.py +++ b/miio/airhumidifier.py @@ -198,12 +198,16 @@ 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 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 37d96c13e..72b09b7be 100644 --- a/miio/airhumidifier_miot.py +++ b/miio/airhumidifier_miot.py @@ -129,11 +129,17 @@ 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 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: diff --git a/miio/tests/test_airhumidifier.py b/miio/tests/test_airhumidifier.py index 578c9f93d..71f54235f 100644 --- a/miio/tests/test_airhumidifier.py +++ b/miio/tests/test_airhumidifier.py @@ -1,14 +1,11 @@ -from unittest import TestCase - 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 +14,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 +36,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 +51,47 @@ 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 + 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" - assert self.state().led_brightness is None + # V1 doesn't support try, so return an error + def raise_error(): + raise DeviceException("v1 does not support set_dry") - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity + self.return_values["set_dry"] = lambda x: raise_error() - 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 + 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) - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(-1) + # CB1 reports temperature differently + if self._model == MODEL_HUMIDIFIER_CB1: + self.state["temperature"] = self.state["temp_dec"] / 10.0 + del self.state["temp_dec"] - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(20) - - 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 - - -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": 1, - "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,406 +99,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 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 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_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 +def test_on(dev): + dev.off() # ensure off + assert dev.status().is_on is False - self.device.set_mode(OperationMode.Medium) - assert mode() == OperationMode.Medium + dev.on() + assert dev.status().is_on is True - 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 +def test_off(dev): + dev.on() # ensure on + assert dev.status().is_on is True - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright + dev.off() + assert dev.status().is_on is False - 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_mode(dev): + def mode(): + return dev.status().mode - def test_set_led(self): - def led_brightness(): - return self.device.status().led_brightness + dev.set_mode(OperationMode.Silent) + assert mode() == OperationMode.Silent - self.device.set_led(True) - assert led_brightness() == LedBrightness.Bright + dev.set_mode(OperationMode.Medium) + assert mode() == OperationMode.Medium - self.device.set_led(False) - assert led_brightness() == LedBrightness.Off + dev.set_mode(OperationMode.High) + assert mode() == OperationMode.High - def test_set_buzzer(self): - def buzzer(): - return self.device.status().buzzer - self.device.set_buzzer(True) - assert buzzer() is True +def test_set_led(dev): + def led_brightness(): + return dev.status().led_brightness - self.device.set_buzzer(False) - assert buzzer() is False + dev.set_led(True) + assert led_brightness() == LedBrightness.Bright - 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) - - 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 - - -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 + dev.set_led(False) + assert led_brightness() == LedBrightness.Off - 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": 1, - "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, _): - """Return dummy device info.""" - return self.dummy_device_info +def test_set_buzzer(dev): + def buzzer(): + return dev.status().buzzer + dev.set_buzzer(True) + assert buzzer() is True -@pytest.fixture(scope="class") -def airhumidifiercb1(request): - request.cls.device = DummyAirHumidifierCB1() - # TODO add ability to test on a real device + dev.set_buzzer(False) + assert buzzer() is False -@pytest.mark.usefixtures("airhumidifiercb1") -class TestAirHumidifierCB1(TestCase): - def is_on(self): - return self.device.status().is_on +def test_status_without_temperature(dev): + key = "temperature" if dev.model == MODEL_HUMIDIFIER_CB1 else "temp_dec" + dev.state[key] = None - def state(self): - return self.device.status() + assert dev.status().temperature is None - 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_led_brightness(dev): + dev.state["led_b"] = None - def test_off(self): - self.device.on() # ensure on - assert self.is_on() is True + assert dev.status().led_brightness is None - self.device.off() - assert self.is_on() is False - def test_status(self): - self.device._reset_state() +def test_set_target_humidity(dev): + def target_humidity(): + return dev.status().target_humidity - device_info = DeviceInfo(self.device.dummy_device_info) + 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 - assert repr(self.state()) == repr( - AirHumidifierStatus(self.device.start_state, device_info) - ) + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(-1) - 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().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 + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(20) - def test_set_mode(self): - def mode(): - return self.device.status().mode + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(90) - self.device.set_mode(OperationMode.Silent) - assert mode() == OperationMode.Silent + with pytest.raises(AirHumidifierException): + dev.set_target_humidity(110) - self.device.set_mode(OperationMode.Medium) - assert mode() == OperationMode.Medium - self.device.set_mode(OperationMode.High) - assert mode() == OperationMode.High +def test_set_child_lock(dev): + def child_lock(): + return dev.status().child_lock - def test_set_led_brightness(self): - def led_brightness(): - return self.device.status().led_brightness + dev.set_child_lock(True) + assert child_lock() is True - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright + dev.set_child_lock(False) + assert child_lock() is False - 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_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_led(self): - def led_brightness(): - return self.device.status().led_brightness + 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_led(True) - assert led_brightness() == LedBrightness.Bright + 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_led(False) - assert led_brightness() == LedBrightness.Off + 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_set_buzzer(self): - def buzzer(): - return self.device.status().buzzer + 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") - self.device.set_buzzer(True) - assert buzzer() is True + # Extra props only on v1 should be none now + assert dev.status().trans_level is None + assert dev.status().button_pressed is None - self.device.set_buzzer(False) - assert buzzer() is False + assert dev.status().use_time == dev.start_state["use_time"] + assert dev.status().hardware_version == dev.start_state["hw_version"] - def test_status_without_temperature(self): - self.device._reset_state() - self.device.state["temperature"] = 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] + ) - assert self.state().temperature is None + try: + version_minor = int(device_info.firmware_version.rsplit("_", 1)[1]) + except IndexError: + version_minor = 0 - def test_status_without_led_brightness(self): - self.device._reset_state() - self.device.state["led_b"] = None + assert dev.status().firmware_version_minor == version_minor + assert dev.status().strong_mode_enabled is False - assert self.state().led_brightness is None - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity +def test_set_led_brightness(dev): + def led_brightness(): + return dev.status().led_brightness - 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 + dev.set_led_brightness(LedBrightness.Bright) + assert led_brightness() == LedBrightness.Bright - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(-1) + dev.set_led_brightness(LedBrightness.Dim) + assert led_brightness() == LedBrightness.Dim - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(20) + dev.set_led_brightness(LedBrightness.Off) + assert led_brightness() == LedBrightness.Off - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(90) - with pytest.raises(AirHumidifierException): - self.device.set_target_humidity(110) +def test_set_dry(dev): + def dry(): + return dev.status().dry - def test_set_child_lock(self): - def child_lock(): - return self.device.status().child_lock + # set_dry is not supported on V1 + if dev.model == MODEL_HUMIDIFIER_V1: + assert dry() is None + with pytest.raises(DeviceException): + dev.set_dry(True) - self.device.set_child_lock(True) - assert child_lock() is True + return - self.device.set_child_lock(False) - assert child_lock() is False + dev.set_dry(True) + assert dry() is True - def test_set_dry(self): - def dry(): - return self.device.status().dry + dev.set_dry(False) + assert dry() is False - self.device.set_dry(True) - assert dry() is True - self.device.set_dry(False) - assert dry() 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): + """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 diff --git a/miio/tests/test_airhumidifier_miot.py b/miio/tests/test_airhumidifier_miot.py index 0993c1d0d..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,143 +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 + + +def test_off(dev): + dev.on() # ensure on + assert dev.status().is_on is True + + dev.off() + assert dev.status().is_on is False -@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_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"] - self.device.on() - assert self.device.status().is_on is True - def test_off(self): - self.device.on() # ensure on - assert self.device.status().is_on is True +def test_set_speed(dev): + def speed_level(): + return dev.status().motor_speed - self.device.off() - assert self.device.status().is_on is False + dev.set_speed(200) + assert speed_level() == 200 + dev.set_speed(2000) + assert speed_level() == 2000 - 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.25) - 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"] + with pytest.raises(AirHumidifierMiotException): + dev.set_speed(199) - def test_set_speed(self): - def speed_level(): - return self.device.status().motor_speed + with pytest.raises(AirHumidifierMiotException): + dev.set_speed(2001) - self.device.set_speed(200) - assert speed_level() == 200 - self.device.set_speed(2000) - assert speed_level() == 2000 - with pytest.raises(AirHumidifierMiotException): - self.device.set_speed(199) +def test_set_target_humidity(dev): + def target_humidity(): + return dev.status().target_humidity - with pytest.raises(AirHumidifierMiotException): - self.device.set_speed(2001) + dev.set_target_humidity(30) + assert target_humidity() == 30 + dev.set_target_humidity(80) + assert target_humidity() == 80 - def test_set_target_humidity(self): - def target_humidity(): - return self.device.status().target_humidity + with pytest.raises(AirHumidifierMiotException): + dev.set_target_humidity(29) - self.device.set_target_humidity(30) - assert target_humidity() == 30 - self.device.set_target_humidity(80) - assert target_humidity() == 80 + with pytest.raises(AirHumidifierMiotException): + dev.set_target_humidity(81) - with pytest.raises(AirHumidifierMiotException): - self.device.set_target_humidity(29) - with pytest.raises(AirHumidifierMiotException): - self.device.set_target_humidity(81) +def test_set_mode(dev): + def mode(): + return dev.status().mode - def test_set_mode(self): - def mode(): - return self.device.status().mode + dev.set_mode(OperationMode.Auto) + assert mode() == OperationMode.Auto - self.device.set_mode(OperationMode.Auto) - assert mode() == OperationMode.Auto + dev.set_mode(OperationMode.Low) + assert mode() == OperationMode.Low - self.device.set_mode(OperationMode.Low) - assert mode() == OperationMode.Low + dev.set_mode(OperationMode.Mid) + assert mode() == OperationMode.Mid - self.device.set_mode(OperationMode.Mid) - assert mode() == OperationMode.Mid + dev.set_mode(OperationMode.High) + assert mode() == OperationMode.High - 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 +def test_set_led_brightness(dev): + def led_brightness(): + return dev.status().led_brightness - self.device.set_led_brightness(LedBrightness.Bright) - assert led_brightness() == LedBrightness.Bright + dev.set_led_brightness(LedBrightness.Bright) + assert led_brightness() == LedBrightness.Bright - self.device.set_led_brightness(LedBrightness.Dim) - assert led_brightness() == LedBrightness.Dim + dev.set_led_brightness(LedBrightness.Dim) + assert led_brightness() == LedBrightness.Dim - self.device.set_led_brightness(LedBrightness.Off) - assert led_brightness() == LedBrightness.Off + dev.set_led_brightness(LedBrightness.Off) + 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 +def test_set_buzzer(dev): + def buzzer(): + return dev.status().buzzer - self.device.set_buzzer(False) - assert buzzer() is False + dev.set_buzzer(True) + assert buzzer() is True - def test_set_child_lock(self): - def child_lock(): - return self.device.status().child_lock + dev.set_buzzer(False) + assert buzzer() is False - 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_child_lock(dev): + def child_lock(): + return dev.status().child_lock - def test_set_dry(self): - def dry(): - return self.device.status().dry + dev.set_child_lock(True) + assert child_lock() is True - self.device.set_dry(True) - assert dry() is True + dev.set_child_lock(False) + assert child_lock() is False - self.device.set_dry(False) - assert dry() is False - def test_set_clean_mode(self): - def clean_mode(): - return self.device.status().clean_mode +def test_set_dry(dev): + def dry(): + return dev.status().dry - self.device.set_clean_mode(True) - assert clean_mode() is True + dev.set_dry(True) + assert dry() is True - self.device.set_clean_mode(False) - assert clean_mode() is False + dev.set_dry(False) + assert dry() is False + + +def test_set_clean_mode(dev): + def clean_mode(): + return dev.status().clean_mode + + 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