From 45acbf9d5025350b364fae9e729a81b5832d4470 Mon Sep 17 00:00:00 2001 From: fredpoint Date: Sat, 24 Jan 2026 14:11:50 +0000 Subject: [PATCH] Add initial support for Air Purifier 4 Compact * Need to limit the properties to be retrieved by request * Add -9998 as a recovable error --- .../zhimi/airpurifier/airpurifier_miot.py | 54 ++++++++++++++++++- miio/miioprotocol.py | 2 +- 2 files changed, 54 insertions(+), 2 deletions(-) mode change 100644 => 100755 miio/miioprotocol.py diff --git a/miio/integrations/zhimi/airpurifier/airpurifier_miot.py b/miio/integrations/zhimi/airpurifier/airpurifier_miot.py index 46e48408e..69f284727 100644 --- a/miio/integrations/zhimi/airpurifier/airpurifier_miot.py +++ b/miio/integrations/zhimi/airpurifier/airpurifier_miot.py @@ -105,6 +105,33 @@ "led_brightness": {"siid": 13, "piid": 2}, } + +# https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-cpa4:1 +_MAPPING_CPA4 = { + # Air Purifier + "power": {"siid": 2, "piid": 1}, + "mode": {"siid": 2, "piid": 4}, + "fan_level": {"siid": 2, "piid": 5}, + # Environment + "aqi": {"siid": 3, "piid": 4}, + # Filter + "filter_life_remaining": {"siid": 4, "piid": 1}, + "filter_hours_used": {"siid": 4, "piid": 3}, + "filter_left_time": {"siid": 4, "piid": 4}, + # Alarm + "buzzer": {"siid": 6, "piid": 1}, + # Physical Control Locked + "child_lock": {"siid": 8, "piid": 1}, + # custom-service + "motor_speed": {"siid": 9, "piid": 1}, + "favorite_rpm": {"siid": 9, "piid": 3}, + "favorite_level": {"siid": 9, "piid": 11}, + # aqi + "aqi_realtime_update_duration": {"siid": 11, "piid": 4}, + # Screen + "led_brightness": {"siid": 13, "piid": 2}, +} + # https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-vb4:1 _MAPPING_VB4 = { # Air Purifier @@ -277,6 +304,7 @@ "zhimi.airp.mb5a": _MAPPING_VA2, # airpurifier 4 "zhimi.airp.va2": _MAPPING_VA2, # airpurifier 4 pro "zhimi.airp.vb4": _MAPPING_VB4, # airpurifier 4 pro + "zhimi.airp.cpa4": _MAPPING_CPA4, # airpurifier 4 compact "zhimi.airpurifier.rma1": _MAPPING_RMA1, # airpurifier 4 lite "zhimi.airpurifier.rma2": _MAPPING_RMA2, # airpurifier 4 lite "zhimi.airp.rmb1": _MAPPING_RMB1, # airpurifier 4 lite @@ -289,6 +317,7 @@ "zhimi.airp.mb5", "zhimi.airp.mb5a", "zhimi.airp.vb4", + "zhimi.airp.cpa4", "zhimi.airp.rmb1", ] @@ -343,6 +372,7 @@ def __init__(self, data: dict[str, Any], model: str) -> None: self.data = data self.model = model + @property def is_on(self) -> bool: """Return True if device is on.""" @@ -558,6 +588,18 @@ class AirPurifierMiot(MiotDevice): "Filter type: {result.filter_type}\n", ) ) + + def _initialize_descriptors(self) -> None: + """Initialize device descriptors. + + Overridden to collect descriptors also from the update helper. + """ + if self._initialized: + return + + res = self.status() + self._descriptors.descriptors_from_object(res) + def status(self) -> AirPurifierMiotStatus: """Retrieve properties.""" # Some devices update the aqi information only every 30min. @@ -566,10 +608,20 @@ def status(self) -> AirPurifierMiotStatus: if self.model == "zhimi.airpurifier.mb3": self.set_property("aqi_realtime_update_duration", 5) + # Some devices can't handle more than x parameters per + # request. Default is 15. cpa4 works at 4 but when doing + # multiple requests, the second one encountered an issue. + # All is OK with 3. + _props_per_request = 15 + _LOGGER.debug("Model: %s", self.model) + if self.model == "zhimi.airp.cpa4": + _props_per_request = 3 + + _LOGGER.debug("Properties per request: %i", _props_per_request) return AirPurifierMiotStatus( { 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=_props_per_request) }, self.model, ) diff --git a/miio/miioprotocol.py b/miio/miioprotocol.py old mode 100644 new mode 100755 index c05d92882..c909cc41b --- a/miio/miioprotocol.py +++ b/miio/miioprotocol.py @@ -276,7 +276,7 @@ def raw_id(self): def _handle_error(self, error): """Raise exception based on the given error code.""" - RECOVERABLE_ERRORS = [-30001, -9999] + RECOVERABLE_ERRORS = [-30001, -9999, -9998] if "code" in error and error["code"] in RECOVERABLE_ERRORS: raise RecoverableError(error) raise DeviceError(error)