From e5165f34da907b58dd8a9d1f7c4324a7a7073f37 Mon Sep 17 00:00:00 2001 From: ted <44641251+zghnwsq@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:48:17 +0800 Subject: [PATCH] fix: Fix the HA warning in the logs related to vacuum state setting (#694) --- custom_components/xiaomi_home/vacuum.py | 130 ++++++++++++++++++------ 1 file changed, 99 insertions(+), 31 deletions(-) diff --git a/custom_components/xiaomi_home/vacuum.py b/custom_components/xiaomi_home/vacuum.py index 232e676..9582b6a 100644 --- a/custom_components/xiaomi_home/vacuum.py +++ b/custom_components/xiaomi_home/vacuum.py @@ -47,29 +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 -) +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] @@ -99,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 @@ -121,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 @@ -155,16 +154,24 @@ 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 + # pylint: disable=import-outside-toplevel + 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: @@ -179,31 +186,92 @@ 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]: - """Return the current state of the vacuum cleaner.""" - return self.get_map_value( - map_=self._status_map, - key=self.get_prop_value(prop=self._prop_status)) + """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. + """ + return self.activity + + @property + 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) + if status_value is None: + return None + 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) + 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', 'remoteclean', '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))