diff --git a/miio/integrations/zhimi/airpurifier/airpurifier_miot.py b/miio/integrations/zhimi/airpurifier/airpurifier_miot.py index 46e48408e..aa39bc447 100644 --- a/miio/integrations/zhimi/airpurifier/airpurifier_miot.py +++ b/miio/integrations/zhimi/airpurifier/airpurifier_miot.py @@ -105,6 +105,36 @@ "led_brightness": {"siid": 13, "piid": 2}, } +# https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:xiaomi-va2b:1 +_MAPPING_VA2B = { + # Air Purifier + "power": {"siid": 2, "piid": 1}, + "mode": {"siid": 2, "piid": 4}, + "fan_level": {"siid": 2, "piid": 5}, + "anion": {"siid": 2, "piid": 6}, + # Environment + "humidity": {"siid": 3, "piid": 1}, + "temperature": {"siid": 3, "piid": 2}, + "aqi": {"siid": 3, "piid": 4}, + # Filter + "filter_life_remaining": {"siid": 4, "piid": 1}, + "filter_hours_used": {"siid": 4, "piid": 2}, + "filter_left_time": {"siid": 4, "piid": 3}, + # Alarm + "buzzer": {"siid": 6, "piid": 1}, + # Physical Control Locked + "child_lock": {"siid": 8, "piid": 1}, + # custom-service + "motor_speed": {"siid": 9, "piid": 1}, + # RFID + "filter_rfid_tag": {"siid": 12, "piid": 1}, + "filter_rfid_product_id": {"siid": 12, "piid": 3}, + # Screen + "led_brightness": {"siid": 13, "piid": 1}, + # Air Purifier Favorite + "favorite_level": {"siid": 14, "piid": 1}, +} + # https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-vb4:1 _MAPPING_VB4 = { # Air Purifier @@ -276,6 +306,7 @@ "zhimi.airp.mb5": _MAPPING_VA2, # airpurifier 4 "zhimi.airp.mb5a": _MAPPING_VA2, # airpurifier 4 "zhimi.airp.va2": _MAPPING_VA2, # airpurifier 4 pro + "xiaomi.airp.va2b": _MAPPING_VA2B, # airpurifier 4 pro "zhimi.airp.vb4": _MAPPING_VB4, # airpurifier 4 pro "zhimi.airpurifier.rma1": _MAPPING_RMA1, # airpurifier 4 lite "zhimi.airpurifier.rma2": _MAPPING_RMA2, # airpurifier 4 lite @@ -568,8 +599,10 @@ def status(self) -> AirPurifierMiotStatus: return AirPurifierMiotStatus( { + # max_properties limited to 10 to avoid "user ack timeout" + # messages from some of the devices. (e.g. xiaomi.airp.va2b) prop["did"]: prop["value"] if prop["code"] == 0 else None - for prop in self.get_properties_for_mapping() + for prop in self.get_properties_for_mapping(max_properties=10) }, self.model, ) diff --git a/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py b/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py index b4b4411c3..6f94ba99d 100644 --- a/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py +++ b/miio/integrations/zhimi/airpurifier/tests/test_airpurifier_miot.py @@ -69,6 +69,27 @@ "button_pressed": "power", } +_INITIAL_STATE_VA2B = { + "power": True, + "aqi": 10, + "anion": True, + "humidity": 62, + "temperature": 18.599999, + "fan_level": 2, + "mode": 0, + "led_brightness": 1, + "buzzer": False, + "child_lock": False, + "favorite_level": 10, + "filter_life_remaining": 80, + "filter_hours_used": 682, + "filter_left_time": 309, + "motor_speed": 354, + "filter_rfid_product_id": "0:0:41:30", + "filter_rfid_tag": "10:20:30:40:50:60:7", + "button_pressed": "power", +} + class DummyAirPurifierMiot(DummyMiotDevice, AirPurifierMiot): def __init__(self, *args, **kwargs): @@ -306,6 +327,13 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) +class DummyAirPurifierMiotVA2B(DummyAirPurifierMiot): + def __init__(self, *args, **kwargs): + self._model = "xiaomi.airp.va2b" + self.state = _INITIAL_STATE_VA2B + super().__init__(*args, **kwargs) + + class DummyAirPurifierMiotMB5(DummyAirPurifierMiot): def __init__(self, *args, **kwargs): self._model = "zhimi.airp.mb5" @@ -373,3 +401,63 @@ def anion(): self.device.set_anion(False) assert anion() is False + + +@pytest.fixture(scope="function") +def airpurifierVA2B(request): + request.cls.device = DummyAirPurifierMiotVA2B() + + +@pytest.mark.usefixtures("airpurifierVA2B") +class TestAirPurifierVA2B(TestCase): + def test_status(self): + status = self.device.status() + assert status.is_on is _INITIAL_STATE_VA2B["power"] + assert status.anion == _INITIAL_STATE_VA2B["anion"] + assert status.aqi == _INITIAL_STATE_VA2B["aqi"] + assert status.humidity == _INITIAL_STATE_VA2B["humidity"] + assert status.temperature == 18.6 + assert status.fan_level == _INITIAL_STATE_VA2B["fan_level"] + assert status.mode == OperationMode(_INITIAL_STATE_VA2B["mode"]) + assert status.led is None + assert status.led_brightness == LedBrightness( + _INITIAL_STATE_VA2B["led_brightness"] + ) + assert status.buzzer == _INITIAL_STATE_VA2B["buzzer"] + assert status.child_lock == _INITIAL_STATE_VA2B["child_lock"] + assert status.favorite_level == _INITIAL_STATE_VA2B["favorite_level"] + assert ( + status.filter_life_remaining == _INITIAL_STATE_VA2B["filter_life_remaining"] + ) + assert status.filter_hours_used == _INITIAL_STATE_VA2B["filter_hours_used"] + assert status.filter_left_time == _INITIAL_STATE_VA2B["filter_left_time"] + assert status.use_time is None + assert status.motor_speed == _INITIAL_STATE_VA2B["motor_speed"] + assert ( + status.filter_rfid_product_id + == _INITIAL_STATE_VA2B["filter_rfid_product_id"] + ) + assert status.filter_type == FilterType.AntiBacterial + + 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_anion(self): + def anion(): + return self.device.status().anion + + self.device.set_anion(True) + assert anion() is True + + self.device.set_anion(False) + assert anion() is False