-
-
Notifications
You must be signed in to change notification settings - Fork 37.4k
Add support for Mi AirPurifier 3 #31729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9cd4464
65c2f15
7a6fd59
7d6857e
d220841
d700d22
1ec5cac
9ee5178
e230c72
93d5aa4
3639b1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||
| AirFresh, | ||||||||||
| AirHumidifier, | ||||||||||
| AirPurifier, | ||||||||||
| AirPurifierMiot, | ||||||||||
| Device, | ||||||||||
| DeviceException, | ||||||||||
| ) | ||||||||||
|
|
@@ -23,6 +24,10 @@ | |||||||||
| LedBrightness as AirpurifierLedBrightness, | ||||||||||
| OperationMode as AirpurifierOperationMode, | ||||||||||
| ) | ||||||||||
| from miio.airpurifier_miot import ( # pylint: disable=import-error, import-error | ||||||||||
| LedBrightness as AirpurifierMiotLedBrightness, | ||||||||||
| OperationMode as AirpurifierMiotOperationMode, | ||||||||||
| ) | ||||||||||
| import voluptuous as vol | ||||||||||
|
|
||||||||||
| from homeassistant.components.fan import PLATFORM_SCHEMA, SUPPORT_SET_SPEED, FanEntity | ||||||||||
|
|
@@ -48,6 +53,7 @@ | |||||||||
| SERVICE_SET_DRY_OFF, | ||||||||||
| SERVICE_SET_DRY_ON, | ||||||||||
| SERVICE_SET_EXTRA_FEATURES, | ||||||||||
| SERVICE_SET_FAN_LEVEL, | ||||||||||
| SERVICE_SET_FAVORITE_LEVEL, | ||||||||||
| SERVICE_SET_LEARN_MODE_OFF, | ||||||||||
| SERVICE_SET_LEARN_MODE_ON, | ||||||||||
|
|
@@ -77,6 +83,8 @@ | |||||||||
| MODEL_AIRPURIFIER_SA1 = "zhimi.airpurifier.sa1" | ||||||||||
| MODEL_AIRPURIFIER_SA2 = "zhimi.airpurifier.sa2" | ||||||||||
| MODEL_AIRPURIFIER_2S = "zhimi.airpurifier.mc1" | ||||||||||
| MODEL_AIRPURIFIER_3 = "zhimi.airpurifier.ma4" | ||||||||||
| MODEL_AIRPURIFIER_3H = "zhimi.airpurifier.mb3" | ||||||||||
|
|
||||||||||
| MODEL_AIRHUMIDIFIER_V1 = "zhimi.humidifier.v1" | ||||||||||
| MODEL_AIRHUMIDIFIER_CA1 = "zhimi.humidifier.ca1" | ||||||||||
|
|
@@ -104,6 +112,8 @@ | |||||||||
| MODEL_AIRPURIFIER_SA1, | ||||||||||
| MODEL_AIRPURIFIER_SA2, | ||||||||||
| MODEL_AIRPURIFIER_2S, | ||||||||||
| MODEL_AIRPURIFIER_3, | ||||||||||
| MODEL_AIRPURIFIER_3H, | ||||||||||
| MODEL_AIRHUMIDIFIER_V1, | ||||||||||
|
foxel marked this conversation as resolved.
|
||||||||||
| MODEL_AIRHUMIDIFIER_CA1, | ||||||||||
| MODEL_AIRHUMIDIFIER_CB1, | ||||||||||
|
|
@@ -131,6 +141,7 @@ | |||||||||
| ATTR_PURIFY_VOLUME = "purify_volume" | ||||||||||
| ATTR_BRIGHTNESS = "brightness" | ||||||||||
| ATTR_LEVEL = "level" | ||||||||||
| ATTR_FAN_LEVEL = "fan_level" | ||||||||||
| ATTR_MOTOR2_SPEED = "motor2_speed" | ||||||||||
| ATTR_ILLUMINANCE = "illuminance" | ||||||||||
| ATTR_FILTER_RFID_PRODUCT_ID = "filter_rfid_product_id" | ||||||||||
|
|
@@ -154,13 +165,15 @@ | |||||||||
| ATTR_HARDWARE_VERSION = "hardware_version" | ||||||||||
|
|
||||||||||
| # Air Humidifier CA | ||||||||||
| ATTR_MOTOR_SPEED = "motor_speed" | ||||||||||
| # ATTR_MOTOR_SPEED = "motor_speed" | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why change this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicates ATTR_MOTOR_SPEED on line 139. Didn't drop this entirely just to keep the notion of this attribute in this "Air Humidifier CA" section |
||||||||||
| ATTR_DEPTH = "depth" | ||||||||||
| ATTR_DRY = "dry" | ||||||||||
|
|
||||||||||
| # Air Fresh | ||||||||||
| ATTR_CO2 = "co2" | ||||||||||
|
|
||||||||||
| PURIFIER_MIOT = [MODEL_AIRPURIFIER_3, MODEL_AIRPURIFIER_3H] | ||||||||||
|
|
||||||||||
| # Map attributes to properties of the state object | ||||||||||
| AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON = { | ||||||||||
| ATTR_TEMPERATURE: "temperature", | ||||||||||
|
|
@@ -227,6 +240,28 @@ | |||||||||
| ATTR_ILLUMINANCE: "illuminance", | ||||||||||
| } | ||||||||||
|
|
||||||||||
| AVAILABLE_ATTRIBUTES_AIRPURIFIER_3 = { | ||||||||||
| ATTR_TEMPERATURE: "temperature", | ||||||||||
| ATTR_HUMIDITY: "humidity", | ||||||||||
| ATTR_AIR_QUALITY_INDEX: "aqi", | ||||||||||
| ATTR_MODE: "mode", | ||||||||||
| ATTR_FILTER_HOURS_USED: "filter_hours_used", | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Relative times are not allowed in the state machine. We only allow absolute utc timestamps. Times should preferably be represented as sensors with device class timestamp. https://developers.home-assistant.io/docs/entity_sensor#available-device-classes
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, should this be completely removed then? The problem with this value is, I presume, that there is no specific timestamp as it depends on the usage. ping @foxel - do you have an idea on this? P.S. I think we need to have a checklist for reviewers, much like what we have for PR creators, to list things to check. It is sometimes hard to spot all mistakes, and I'm very sorry for not catching this one..
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should update this dev docs page with the allowed fan speeds: I'll put it on my todo. In general we need to make sure that we follow our architecture docs and ADRs. But those are not complete yet, so that's a problem.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's interesting that relative timestamps are not allowed in state attributes. Didn't know that. Where can I find more docs about it? @rytilahti solution for it is to move attributes to independent sensors. |
||||||||||
| ATTR_FILTER_LIFE: "filter_life_remaining", | ||||||||||
| ATTR_FAVORITE_LEVEL: "favorite_level", | ||||||||||
| ATTR_CHILD_LOCK: "child_lock", | ||||||||||
| ATTR_LED: "led", | ||||||||||
| ATTR_MOTOR_SPEED: "motor_speed", | ||||||||||
| ATTR_AVERAGE_AIR_QUALITY_INDEX: "average_aqi", | ||||||||||
| ATTR_PURIFY_VOLUME: "purify_volume", | ||||||||||
| ATTR_USE_TIME: "use_time", | ||||||||||
| ATTR_BUZZER: "buzzer", | ||||||||||
| ATTR_LED_BRIGHTNESS: "led_brightness", | ||||||||||
| ATTR_FILTER_RFID_PRODUCT_ID: "filter_rfid_product_id", | ||||||||||
| ATTR_FILTER_RFID_TAG: "filter_rfid_tag", | ||||||||||
| ATTR_FILTER_TYPE: "filter_type", | ||||||||||
| ATTR_FAN_LEVEL: "fan_level", | ||||||||||
| } | ||||||||||
|
|
||||||||||
| AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 = { | ||||||||||
| # Common set isn't used here. It's a very basic version of the device. | ||||||||||
| ATTR_AIR_QUALITY_INDEX: "aqi", | ||||||||||
|
|
@@ -302,6 +337,7 @@ | |||||||||
| OPERATION_MODES_AIRPURIFIER_PRO = ["Auto", "Silent", "Favorite"] | ||||||||||
| OPERATION_MODES_AIRPURIFIER_PRO_V7 = OPERATION_MODES_AIRPURIFIER_PRO | ||||||||||
| OPERATION_MODES_AIRPURIFIER_2S = ["Auto", "Silent", "Favorite"] | ||||||||||
| OPERATION_MODES_AIRPURIFIER_3 = ["Auto", "Silent", "Favorite", "Fan"] | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only allowed speed modes for fans are off, low, medium and high. core/homeassistant/components/fan/__init__.py Lines 35 to 38 in dc7127a
We should not continue to extend this platform with architecture design breaking changes.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I didn't notice that... How can we make the situation better? Should the tests be improved to capture such misuses?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how we can test this generally. Some platforms are already breaking the rules which might also complicate any general tests.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MartinHjelmare what if a device does not fall into such a specification?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The platform can try to translate between home assistant mode and device mode. The platform can also register custom services. The current design does not cater well to all fans but until we have a better design we have to follow it. We have an open architecture issue on fan speeds. You're welcome to contribute to that and try to bring it forward. We need to figure out a design that can cater for a majority of fans but is still compatible with our frontend and voice assistants. |
||||||||||
| OPERATION_MODES_AIRPURIFIER_V3 = [ | ||||||||||
| "Auto", | ||||||||||
| "Silent", | ||||||||||
|
|
@@ -327,6 +363,7 @@ | |||||||||
| FEATURE_SET_EXTRA_FEATURES = 512 | ||||||||||
| FEATURE_SET_TARGET_HUMIDITY = 1024 | ||||||||||
| FEATURE_SET_DRY = 2048 | ||||||||||
| FEATURE_SET_FAN_LEVEL = 4096 | ||||||||||
|
|
||||||||||
| FEATURE_FLAGS_AIRPURIFIER = ( | ||||||||||
| FEATURE_SET_BUZZER | ||||||||||
|
|
@@ -361,6 +398,15 @@ | |||||||||
| | FEATURE_SET_FAVORITE_LEVEL | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| FEATURE_FLAGS_AIRPURIFIER_3 = ( | ||||||||||
| FEATURE_SET_BUZZER | ||||||||||
| | FEATURE_SET_CHILD_LOCK | ||||||||||
| | FEATURE_SET_LED | ||||||||||
| | FEATURE_SET_FAVORITE_LEVEL | ||||||||||
|
foxel marked this conversation as resolved.
|
||||||||||
| | FEATURE_SET_FAN_LEVEL | ||||||||||
| | FEATURE_SET_LED_BRIGHTNESS | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| FEATURE_FLAGS_AIRPURIFIER_V3 = ( | ||||||||||
| FEATURE_SET_BUZZER | FEATURE_SET_CHILD_LOCK | FEATURE_SET_LED | ||||||||||
| ) | ||||||||||
|
|
@@ -394,6 +440,10 @@ | |||||||||
| {vol.Required(ATTR_LEVEL): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=17))} | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| SERVICE_SCHEMA_FAN_LEVEL = AIRPURIFIER_SERVICE_SCHEMA.extend( | ||||||||||
| {vol.Required(ATTR_LEVEL): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3))} | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| SERVICE_SCHEMA_VOLUME = AIRPURIFIER_SERVICE_SCHEMA.extend( | ||||||||||
| {vol.Required(ATTR_VOLUME): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=100))} | ||||||||||
| ) | ||||||||||
|
|
@@ -430,6 +480,10 @@ | |||||||||
| "method": "async_set_favorite_level", | ||||||||||
| "schema": SERVICE_SCHEMA_FAVORITE_LEVEL, | ||||||||||
| }, | ||||||||||
| SERVICE_SET_FAN_LEVEL: { | ||||||||||
| "method": "async_set_fan_level", | ||||||||||
| "schema": SERVICE_SCHEMA_FAN_LEVEL, | ||||||||||
| }, | ||||||||||
| SERVICE_SET_VOLUME: {"method": "async_set_volume", "schema": SERVICE_SCHEMA_VOLUME}, | ||||||||||
| SERVICE_SET_EXTRA_FEATURES: { | ||||||||||
| "method": "async_set_extra_features", | ||||||||||
|
|
@@ -472,7 +526,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= | |||||||||
| except DeviceException: | ||||||||||
| raise PlatformNotReady | ||||||||||
|
|
||||||||||
| if model.startswith("zhimi.airpurifier."): | ||||||||||
| if model in PURIFIER_MIOT: | ||||||||||
| air_purifier = AirPurifierMiot(host, token) | ||||||||||
| device = XiaomiAirPurifierMiot(name, air_purifier, model, unique_id) | ||||||||||
| elif model.startswith("zhimi.airpurifier."): | ||||||||||
| air_purifier = AirPurifier(host, token) | ||||||||||
| device = XiaomiAirPurifier(name, air_purifier, model, unique_id) | ||||||||||
| elif model.startswith("zhimi.humidifier."): | ||||||||||
|
|
@@ -690,6 +747,10 @@ def __init__(self, name, device, model, unique_id): | |||||||||
| self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S | ||||||||||
| self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_2S | ||||||||||
| self._speed_list = OPERATION_MODES_AIRPURIFIER_2S | ||||||||||
| elif self._model == MODEL_AIRPURIFIER_3 or self._model == MODEL_AIRPURIFIER_3H: | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was rechecking this PR and saw these and wondered why the one below is not merged here, before I realized that one is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't ever see V3 version but it's something older and different from 3/3h from 2019...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or simply use some part of the real model number? It's mildly confusing to differentiate between those constants. Anyway, this is not a blocking issue.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One last thing to do before merging, please update the documentation (for the model listing & the new service): https://github.com/home-assistant/home-assistant.io/blob/current/source/_integrations/fan.xiaomi_miio.markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made one
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, let's get this merged, the docs will follow as soon as someone reviews it! Thanks for your hard work and patience on this! 🥇 |
||||||||||
| self._device_features = FEATURE_FLAGS_AIRPURIFIER_3 | ||||||||||
| self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_3 | ||||||||||
| self._speed_list = OPERATION_MODES_AIRPURIFIER_3 | ||||||||||
|
foxel marked this conversation as resolved.
|
||||||||||
| elif self._model == MODEL_AIRPURIFIER_V3: | ||||||||||
| self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3 | ||||||||||
| self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 | ||||||||||
|
|
@@ -795,6 +856,17 @@ async def async_set_favorite_level(self, level: int = 1): | |||||||||
| level, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| async def async_set_fan_level(self, level: int = 1): | ||||||||||
| """Set the favorite level.""" | ||||||||||
| if self._device_features & FEATURE_SET_FAN_LEVEL == 0: | ||||||||||
| return | ||||||||||
|
|
||||||||||
| await self._try_command( | ||||||||||
| "Setting the fan level of the miio device failed.", | ||||||||||
| self._device.set_fan_level, | ||||||||||
| level, | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| async def async_set_auto_detect_on(self): | ||||||||||
| """Turn the auto detect on.""" | ||||||||||
| if self._device_features & FEATURE_SET_AUTO_DETECT == 0: | ||||||||||
|
|
@@ -872,6 +944,42 @@ async def async_reset_filter(self): | |||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class XiaomiAirPurifierMiot(XiaomiAirPurifier): | ||||||||||
| """Representation of a Xiaomi Air Purifier (MiOT protocol).""" | ||||||||||
|
|
||||||||||
| @property | ||||||||||
| def speed(self): | ||||||||||
| """Return the current speed.""" | ||||||||||
| if self._state: | ||||||||||
| return AirpurifierMiotOperationMode(self._state_attrs[ATTR_MODE]).name | ||||||||||
|
|
||||||||||
| return None | ||||||||||
|
|
||||||||||
| async def async_set_speed(self, speed: str) -> None: | ||||||||||
| """Set the speed of the fan.""" | ||||||||||
| if self.supported_features & SUPPORT_SET_SPEED == 0: | ||||||||||
| return | ||||||||||
|
|
||||||||||
| _LOGGER.debug("Setting the operation mode to: %s", speed) | ||||||||||
|
|
||||||||||
| await self._try_command( | ||||||||||
| "Setting operation mode of the miio device failed.", | ||||||||||
| self._device.set_mode, | ||||||||||
| AirpurifierMiotOperationMode[speed.title()], | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| async def async_set_led_brightness(self, brightness: int = 2): | ||||||||||
| """Set the led brightness.""" | ||||||||||
| if self._device_features & FEATURE_SET_LED_BRIGHTNESS == 0: | ||||||||||
| return | ||||||||||
|
|
||||||||||
| await self._try_command( | ||||||||||
| "Setting the led brightness of the miio device failed.", | ||||||||||
| self._device.set_led_brightness, | ||||||||||
| AirpurifierMiotLedBrightness(brightness), | ||||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class XiaomiAirHumidifier(XiaomiGenericDevice): | ||||||||||
| """Representation of a Xiaomi Air Humidifier.""" | ||||||||||
|
|
||||||||||
|
|
||||||||||
Uh oh!
There was an error while loading. Please reload this page.