From 36d5a3e4def910c121fa325a2933df84ae0ac5c8 Mon Sep 17 00:00:00 2001 From: tedwang Date: Fri, 3 Jan 2025 10:54:41 +0800 Subject: [PATCH 1/7] docs: fix table header misplacement fix table header misplacement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 872808a..642c499 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ In MIoT-Spec-V2 protocol, a product is defined as a device. A device contains se - Property -| format | access | value-list | value-range | Entity in Home Assistant | +| access | format | value-list | value-range | Entity in Home Assistant | | ------------ | --------------------- | ------------ | ----------- | ------------------------ | | writable | string | - | - | Text | | writable | bool | - | - | Switch | From 13e6863678f9fcbd908f86f86b504e71909870bc Mon Sep 17 00:00:00 2001 From: tedwang Date: Thu, 23 Jan 2025 10:09:15 +0800 Subject: [PATCH 2/7] fix: Fix the HA warning in the logs related to vacuum state setting Adapt to new vacuum state property, set the activity property instead of directly setting the state property. --- .../xiaomi_home/miot/miot_spec.py | 6 +++ custom_components/xiaomi_home/vacuum.py | 44 +++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index 67fde7b..8bb70f9 100644 --- a/custom_components/xiaomi_home/miot/miot_spec.py +++ b/custom_components/xiaomi_home/miot/miot_spec.py @@ -213,6 +213,12 @@ class MIoTSpecValueList: return item.description return None + def get_name_by_value(self, value: Any) -> Optional[str]: + for item in self.items: + if item.value == value: + return item.name + return None + def dump(self) -> list: return [item.dump() for item in self.items] diff --git a/custom_components/xiaomi_home/vacuum.py b/custom_components/xiaomi_home/vacuum.py index 232e676..b2f6015 100644 --- a/custom_components/xiaomi_home/vacuum.py +++ b/custom_components/xiaomi_home/vacuum.py @@ -54,7 +54,8 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.components.vacuum import ( StateVacuumEntity, - VacuumEntityFeature + VacuumEntityFeature, + VacuumActivity ) from .miot.const import DOMAIN @@ -191,10 +192,47 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): @property def state(self) -> Optional[str]: - """Return the current state of the vacuum cleaner.""" + """Return the current state of the vacuum cleaner. + + To fix the HA warning below: + Detected that custom integration 'xiaomi_home' is setting state + directly.Entity XXX()should implement the 'activity' property and return + its state using the VacuumActivity enum.This will stop working in + Home Assistant 2026.1. + + Refer to + https://developers.home-assistant.io/blog/2024/12/08/new-vacuum-state-property + + There are only 6 states in VacuumActivity enum. To be compatible with + more constants, try get matching VacuumActivity enum first, return state + string as before if there is no match. In Home Assistant 2026.1, every + state should map to a VacuumActivity enum. + """ + if (activity := self.activity) is not None: + return activity return self.get_map_value( map_=self._status_map, - key=self.get_prop_value(prop=self._prop_status)) + key=self.get_prop_value(prop=self._prop_status) + ) + + @property + def activity(self) -> VacuumActivity | None: + """Return the current vacuum activity.""" + state_trans_map = { + 'Sleep': VacuumActivity.IDLE, + 'Idle': VacuumActivity.IDLE, + 'Paused': VacuumActivity.PAUSED, + 'Go Charging': VacuumActivity.RETURNING, + 'Charging': VacuumActivity.DOCKED, + 'Sweeping': VacuumActivity.CLEANING, + 'Sweeping and Mopping': VacuumActivity.CLEANING, + 'Mopping': VacuumActivity.CLEANING, + 'Error': VacuumActivity.ERROR, + } + prop_value = self.get_prop_value(prop=self._prop_status) + state_name = self._prop_status.value_list.get_name_by_value(prop_value) + return state_trans_map.get(state_name) @property def battery_level(self) -> Optional[int]: From e7a96e3464a6243aa3a49aea1847bb7d826be056 Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Mon, 28 Apr 2025 12:36:03 +0800 Subject: [PATCH 3/7] refactor: vacuum.py VacuumActivity compatibility VacuumActivity is introduced in Home Assistant core 2025.1.0. It will cause the integration reloading failure in the HA with an old version core. --- custom_components/xiaomi_home/vacuum.py | 126 ++++++++++++++---------- 1 file changed, 76 insertions(+), 50 deletions(-) diff --git a/custom_components/xiaomi_home/vacuum.py b/custom_components/xiaomi_home/vacuum.py index b2f6015..a08b821 100644 --- a/custom_components/xiaomi_home/vacuum.py +++ b/custom_components/xiaomi_home/vacuum.py @@ -47,30 +47,26 @@ Vacuum entities for Xiaomi Home. """ from __future__ import annotations from typing import Any, Optional +import re import logging from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.components.vacuum import ( - StateVacuumEntity, - VacuumEntityFeature, - VacuumActivity -) +from homeassistant.components.vacuum import (StateVacuumEntity, + VacuumEntityFeature) from .miot.const import DOMAIN from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData -from .miot.miot_spec import ( - MIoTSpecAction, - MIoTSpecProperty) +from .miot.miot_spec import (MIoTSpecAction, MIoTSpecProperty) _LOGGER = logging.getLogger(__name__) async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][ config_entry.entry_id] @@ -100,10 +96,12 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): _status_map: Optional[dict[int, str]] _fan_level_map: Optional[dict[int, str]] - def __init__( - self, miot_device: MIoTDevice, entity_data: MIoTEntityData - ) -> None: + _device_name: str + + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: super().__init__(miot_device=miot_device, entity_data=entity_data) + self._device_name = miot_device.name self._attr_supported_features = VacuumEntityFeature(0) self._prop_status = None @@ -122,21 +120,21 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): for prop in entity_data.props: if prop.name == 'status': if not prop.value_list: - _LOGGER.error( - 'invalid status value_list, %s', self.entity_id) + _LOGGER.error('invalid status value_list, %s', + self.entity_id) continue self._status_map = prop.value_list.to_map() + self._attr_supported_features |= VacuumEntityFeature.STATE self._prop_status = prop elif prop.name == 'fan-level': if not prop.value_list: - _LOGGER.error( - 'invalid fan-level value_list, %s', self.entity_id) + _LOGGER.error('invalid fan-level value_list, %s', + self.entity_id) continue self._fan_level_map = prop.value_list.to_map() self._attr_fan_speed_list = list(self._fan_level_map.values()) self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED self._prop_fan_level = prop - elif prop.name == 'battery-level': self._attr_supported_features |= VacuumEntityFeature.BATTERY self._prop_battery_level = prop @@ -156,16 +154,23 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): elif action.name == 'stop-and-gocharge': self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME self._action_stop_and_gocharge = action - elif action.name == 'identify': self._attr_supported_features |= VacuumEntityFeature.LOCATE self._action_identify = action async def async_start(self) -> None: """Start or resume the cleaning task.""" - if self.state.lower() in ['paused', '暂停中']: - await self.action_async(action=self._action_continue_sweep) - return + try: # VacuumActivity is introduced in HA core 2025.1.0 + from homeassistant.components.vacuum import VacuumActivity + if (self.activity + == VacuumActivity.PAUSED) and self._action_continue_sweep: + await self.action_async(action=self._action_continue_sweep) + return + except ImportError: + if self.state and (self.state in {'paused', 'pause' + }) and self._action_continue_sweep: + await self.action_async(action=self._action_continue_sweep) + return await self.action_async(action=self._action_start_sweep) async def async_stop(self, **kwargs: Any) -> None: @@ -180,15 +185,21 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): """Set the vacuum cleaner to return to the dock.""" await self.action_async(action=self._action_stop_and_gocharge) - async def async_clean_spot(self, **kwargs: Any) -> None: - """Perform a spot clean-up.""" - async def async_locate(self, **kwargs: Any) -> None: """Locate the vacuum cleaner.""" await self.action_async(action=self._action_identify) async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: """Set fan speed.""" + fan_level_value = self.get_map_key(map_=self._fan_level_map, + value=fan_speed) + await self.set_property_async(prop=self._prop_fan_level, + value=fan_level_value) + + @property + def name(self) -> Optional[str]: + """Name of the vacuum entity.""" + return self._device_name @property def state(self) -> Optional[str]: @@ -209,39 +220,54 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): string as before if there is no match. In Home Assistant 2026.1, every state should map to a VacuumActivity enum. """ - if (activity := self.activity) is not None: - return activity - return self.get_map_value( - map_=self._status_map, - key=self.get_prop_value(prop=self._prop_status) - ) + return self.activity @property - def activity(self) -> VacuumActivity | None: - """Return the current vacuum activity.""" - state_trans_map = { - 'Sleep': VacuumActivity.IDLE, - 'Idle': VacuumActivity.IDLE, - 'Paused': VacuumActivity.PAUSED, - 'Go Charging': VacuumActivity.RETURNING, - 'Charging': VacuumActivity.DOCKED, - 'Sweeping': VacuumActivity.CLEANING, - 'Sweeping and Mopping': VacuumActivity.CLEANING, - 'Mopping': VacuumActivity.CLEANING, - 'Error': VacuumActivity.ERROR, - } - prop_value = self.get_prop_value(prop=self._prop_status) - state_name = self._prop_status.value_list.get_name_by_value(prop_value) - return state_trans_map.get(state_name) + def activity(self) -> Optional[str]: + """The current vacuum activity.""" + status = self.get_prop_value(prop=self._prop_status) + if status is None: + return None + status_value = self.get_map_value(map_=self._status_map, key=status) + try: + from homeassistant.components.vacuum import VacuumActivity + status_value = status_value.lower() + status_str = re.sub(r'[^a-z]', '', status_value) + if status_str in { + 'charging', 'charged', 'chargingcompleted', 'fullcharge', + 'fullpower', 'findchargerpause', 'drying', 'washing', + 'wash', 'inthewash', 'inthedry', 'stationworking', + 'dustcollecting', 'upgrade', 'upgrading', 'updating' + }: + return VacuumActivity.DOCKED + if status_str in {'paused', 'pause'}: + return VacuumActivity.PAUSED + if status_str in { + 'gocharging', 'cleancompletegocharging', 'findchargewash', + 'backtowashmop', 'gowash', 'gowashing', 'summon' + }: + return VacuumActivity.RETURNING + if (status_str.find('sweeping') + != -1) or (status_str.find('mopping') + != -1) or (status_str in { + 'cleaning', 'continuesweep', 'busy', + 'building', 'buildingmap', 'mapping' + }): + return VacuumActivity.CLEANING + if status_str in {'error', 'breakcharging', 'gochargebreak'}: + return VacuumActivity.ERROR + return VacuumActivity.IDLE + except ImportError: + return status_value @property def battery_level(self) -> Optional[int]: - """Return the current battery level of the vacuum cleaner.""" + """The current battery level of the vacuum cleaner.""" return self.get_prop_value(prop=self._prop_battery_level) @property def fan_speed(self) -> Optional[str]: - """Return the current fan speed of the vacuum cleaner.""" + """The current fan speed of the vacuum cleaner.""" return self.get_map_value( map_=self._fan_level_map, key=self.get_prop_value(prop=self._prop_fan_level)) From 83ce6c251e5aa3adfaa7ce77edd3cb3ebf1d3175 Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Mon, 28 Apr 2025 12:44:38 +0800 Subject: [PATCH 4/7] fix: pylint --- custom_components/xiaomi_home/vacuum.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/xiaomi_home/vacuum.py b/custom_components/xiaomi_home/vacuum.py index a08b821..a33bd56 100644 --- a/custom_components/xiaomi_home/vacuum.py +++ b/custom_components/xiaomi_home/vacuum.py @@ -160,7 +160,8 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): async def async_start(self) -> None: """Start or resume the cleaning task.""" - try: # VacuumActivity is introduced in HA core 2025.1.0 + try:# VacuumActivity is introduced in HA core 2025.1.0 + # pylint: disable=import-outside-toplevel from homeassistant.components.vacuum import VacuumActivity if (self.activity == VacuumActivity.PAUSED) and self._action_continue_sweep: @@ -230,6 +231,7 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): return None status_value = self.get_map_value(map_=self._status_map, key=status) try: + # pylint: disable=import-outside-toplevel from homeassistant.components.vacuum import VacuumActivity status_value = status_value.lower() status_str = re.sub(r'[^a-z]', '', status_value) From e42a3a5997c5590ab12c6e0d80ad16948a250919 Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Tue, 3 Jun 2025 14:37:43 +0800 Subject: [PATCH 5/7] fix: remove unused function get_name_by_value --- custom_components/xiaomi_home/miot/miot_spec.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index 99ba9b6..9cabdcb 100644 --- a/custom_components/xiaomi_home/miot/miot_spec.py +++ b/custom_components/xiaomi_home/miot/miot_spec.py @@ -202,12 +202,6 @@ class MIoTSpecValueList: return item.description return None - def get_name_by_value(self, value: Any) -> Optional[str]: - for item in self.items: - if item.value == value: - return item.name - return None - def dump(self) -> list: return [item.dump() for item in self.items] From c9f588d7abde02c62e421f64a8e4facb5f6e0ae5 Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Tue, 3 Jun 2025 14:38:54 +0800 Subject: [PATCH 6/7] fix: add remoteclean for cleaning status --- custom_components/xiaomi_home/vacuum.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/xiaomi_home/vacuum.py b/custom_components/xiaomi_home/vacuum.py index a33bd56..a55fee7 100644 --- a/custom_components/xiaomi_home/vacuum.py +++ b/custom_components/xiaomi_home/vacuum.py @@ -160,7 +160,7 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): async def async_start(self) -> None: """Start or resume the cleaning task.""" - try:# VacuumActivity is introduced in HA core 2025.1.0 + try: # VacuumActivity is introduced in HA core 2025.1.0 # pylint: disable=import-outside-toplevel from homeassistant.components.vacuum import VacuumActivity if (self.activity @@ -252,8 +252,8 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): if (status_str.find('sweeping') != -1) or (status_str.find('mopping') != -1) or (status_str in { - 'cleaning', 'continuesweep', 'busy', - 'building', 'buildingmap', 'mapping' + 'cleaning', 'remoteclean', 'continuesweep', + 'busy', 'building', 'buildingmap', 'mapping' }): return VacuumActivity.CLEANING if status_str in {'error', 'breakcharging', 'gochargebreak'}: From 473a33ea7656c91a539bfef763afc6d5b09fd2ff Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Wed, 4 Jun 2025 11:25:06 +0800 Subject: [PATCH 7/7] fix: unknown status --- custom_components/xiaomi_home/vacuum.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/xiaomi_home/vacuum.py b/custom_components/xiaomi_home/vacuum.py index a55fee7..9582b6a 100644 --- a/custom_components/xiaomi_home/vacuum.py +++ b/custom_components/xiaomi_home/vacuum.py @@ -230,6 +230,8 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity): if status is None: return None status_value = self.get_map_value(map_=self._status_map, key=status) + if status_value is None: + return None try: # pylint: disable=import-outside-toplevel from homeassistant.components.vacuum import VacuumActivity