From 52fd6371ab439f588fc149e0ee5a83aeed9dd1c7 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:55:41 +0800 Subject: [PATCH] fix: fix water heater error & some type error (#684) * fix: fix water heater target-temp error * feat: update miot device logic * fix: fix some type error --- custom_components/xiaomi_home/climate.py | 72 +++++++++---------- custom_components/xiaomi_home/cover.py | 2 +- custom_components/xiaomi_home/fan.py | 2 +- custom_components/xiaomi_home/light.py | 36 ++++++---- .../xiaomi_home/miot/miot_device.py | 18 +++-- custom_components/xiaomi_home/water_heater.py | 35 +++++---- 6 files changed, 87 insertions(+), 78 deletions(-) diff --git a/custom_components/xiaomi_home/climate.py b/custom_components/xiaomi_home/climate.py index 5a6b7b9..d90c3dd 100644 --- a/custom_components/xiaomi_home/climate.py +++ b/custom_components/xiaomi_home/climate.py @@ -296,39 +296,37 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity): async def async_set_swing_mode(self, swing_mode): """Set new target swing operation.""" if swing_mode == SWING_BOTH: - if await self.set_property_async( - prop=self._prop_horizontal_swing, value=True, update=False): - self.set_prop_value(self._prop_horizontal_swing, value=True) - if await self.set_property_async( - prop=self._prop_vertical_swing, value=True, update=False): - self.set_prop_value(self._prop_vertical_swing, value=True) + await self.set_property_async( + prop=self._prop_horizontal_swing, value=True, + write_ha_state=False) + await self.set_property_async( + prop=self._prop_vertical_swing, value=True) elif swing_mode == SWING_HORIZONTAL: - if await self.set_property_async( - prop=self._prop_horizontal_swing, value=True, update=False): - self.set_prop_value(self._prop_horizontal_swing, value=True) + await self.set_property_async( + prop=self._prop_horizontal_swing, value=True) elif swing_mode == SWING_VERTICAL: - if await self.set_property_async( - prop=self._prop_vertical_swing, value=True, update=False): - self.set_prop_value(self._prop_vertical_swing, value=True) + await self.set_property_async( + prop=self._prop_vertical_swing, value=True) elif swing_mode == SWING_ON: - if await self.set_property_async( - prop=self._prop_fan_on, value=True, update=False): - self.set_prop_value(self._prop_fan_on, value=True) + await self.set_property_async( + prop=self._prop_fan_on, value=True) elif swing_mode == SWING_OFF: - if self._prop_fan_on and await self.set_property_async( - prop=self._prop_fan_on, value=False, update=False): - self.set_prop_value(self._prop_fan_on, value=False) - if self._prop_horizontal_swing and await self.set_property_async( + if self._prop_fan_on: + await self.set_property_async( + prop=self._prop_fan_on, value=False, + write_ha_state=False) + if self._prop_horizontal_swing: + await self.set_property_async( prop=self._prop_horizontal_swing, value=False, - update=False): - self.set_prop_value(self._prop_horizontal_swing, value=False) - if self._prop_vertical_swing and await self.set_property_async( - prop=self._prop_vertical_swing, value=False, update=False): - self.set_prop_value(self._prop_vertical_swing, value=False) + write_ha_state=False) + if self._prop_vertical_swing: + await self.set_property_async( + prop=self._prop_vertical_swing, value=False, + write_ha_state=False) + self.async_write_ha_state() else: raise RuntimeError( f'unknown swing_mode, {swing_mode}, {self.entity_id}') - self.async_write_ha_state() async def async_set_fan_mode(self, fan_mode): """Set new target fan mode.""" @@ -389,12 +387,10 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity): Requires ClimateEntityFeature.SWING_MODE. """ - horizontal: bool = ( - self.get_prop_value(prop=self._prop_horizontal_swing) - if self._prop_horizontal_swing else None) - vertical: bool = ( - self.get_prop_value(prop=self._prop_vertical_swing) - if self._prop_vertical_swing else None) + horizontal = ( + self.get_prop_value(prop=self._prop_horizontal_swing)) + vertical = ( + self.get_prop_value(prop=self._prop_vertical_swing)) if horizontal and vertical: return SWING_BOTH if horizontal: @@ -450,7 +446,11 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity): self.set_prop_value(prop=self._prop_fan_level, value=v_ac_state['S']) # D: swing mode. 0: on, 1: off - if 'D' in v_ac_state and len(self._attr_swing_modes) == 2: + if ( + 'D' in v_ac_state + and self._attr_swing_modes + and len(self._attr_swing_modes) == 2 + ): if ( SWING_HORIZONTAL in self._attr_swing_modes and self._prop_horizontal_swing @@ -465,10 +465,10 @@ class AirConditioner(MIoTServiceEntity, ClimateEntity): self.set_prop_value( prop=self._prop_vertical_swing, value=v_ac_state['D'] == 0) - - self._value_ac_state.update(v_ac_state) - _LOGGER.debug( - 'ac_state update, %s', self._value_ac_state) + if self._value_ac_state: + self._value_ac_state.update(v_ac_state) + _LOGGER.debug( + 'ac_state update, %s', self._value_ac_state) class Heater(MIoTServiceEntity, ClimateEntity): diff --git a/custom_components/xiaomi_home/cover.py b/custom_components/xiaomi_home/cover.py index 78a6a02..f2ebaeb 100644 --- a/custom_components/xiaomi_home/cover.py +++ b/custom_components/xiaomi_home/cover.py @@ -200,7 +200,7 @@ class Cover(MIoTServiceEntity, CoverEntity): if pos is None: return None pos = round(pos*self._prop_position_value_range/100) - return await self.set_property_async( + await self.set_property_async( prop=self._prop_target_position, value=pos) @property diff --git a/custom_components/xiaomi_home/fan.py b/custom_components/xiaomi_home/fan.py index a28b989..92a41f4 100644 --- a/custom_components/xiaomi_home/fan.py +++ b/custom_components/xiaomi_home/fan.py @@ -303,7 +303,7 @@ class Fan(MIoTServiceEntity, FanEntity): fan_level = self.get_prop_value(prop=self._prop_fan_level) if fan_level is None: return None - if self._speed_names: + if self._speed_names and self._speed_name_map: return ordered_list_item_to_percentage( self._speed_names, self._speed_name_map[fan_level]) else: diff --git a/custom_components/xiaomi_home/light.py b/custom_components/xiaomi_home/light.py index 1667662..ef9fed2 100644 --- a/custom_components/xiaomi_home/light.py +++ b/custom_components/xiaomi_home/light.py @@ -96,7 +96,7 @@ class Light(MIoTServiceEntity, LightEntity): """Light entities for Xiaomi Home.""" # pylint: disable=unused-argument _VALUE_RANGE_MODE_COUNT_MAX = 30 - _prop_on: MIoTSpecProperty + _prop_on: Optional[MIoTSpecProperty] _prop_brightness: Optional[MIoTSpecProperty] _prop_color_temp: Optional[MIoTSpecProperty] _prop_color: Optional[MIoTSpecProperty] @@ -250,23 +250,25 @@ class Light(MIoTServiceEntity, LightEntity): Shall set attributes in kwargs if applicable. """ - result: bool = False # on # Dirty logic for lumi.gateway.mgl03 indicator light - value_on = True if self._prop_on.format_ == bool else 1 - result = await self.set_property_async( - prop=self._prop_on, value=value_on) + if self._prop_on: + value_on = True if self._prop_on.format_ == bool else 1 + await self.set_property_async( + prop=self._prop_on, value=value_on) # brightness if ATTR_BRIGHTNESS in kwargs: brightness = brightness_to_value( self._brightness_scale, kwargs[ATTR_BRIGHTNESS]) - result = await self.set_property_async( - prop=self._prop_brightness, value=brightness) + await self.set_property_async( + prop=self._prop_brightness, value=brightness, + write_ha_state=False) # color-temperature if ATTR_COLOR_TEMP_KELVIN in kwargs: - result = await self.set_property_async( + await self.set_property_async( prop=self._prop_color_temp, - value=kwargs[ATTR_COLOR_TEMP_KELVIN]) + value=kwargs[ATTR_COLOR_TEMP_KELVIN], + write_ha_state=False) self._attr_color_mode = ColorMode.COLOR_TEMP # rgb color if ATTR_RGB_COLOR in kwargs: @@ -274,19 +276,23 @@ class Light(MIoTServiceEntity, LightEntity): g = kwargs[ATTR_RGB_COLOR][1] b = kwargs[ATTR_RGB_COLOR][2] rgb = (r << 16) | (g << 8) | b - result = await self.set_property_async( - prop=self._prop_color, value=rgb) + await self.set_property_async( + prop=self._prop_color, value=rgb, + write_ha_state=False) self._attr_color_mode = ColorMode.RGB # mode if ATTR_EFFECT in kwargs: - result = await self.set_property_async( + await self.set_property_async( prop=self._prop_mode, value=self.get_map_key( - map_=self._mode_map, value=kwargs[ATTR_EFFECT])) - return result + map_=self._mode_map, value=kwargs[ATTR_EFFECT]), + write_ha_state=False) + self.async_write_ha_state() async def async_turn_off(self, **kwargs) -> None: """Turn the light off.""" + if not self._prop_on: + return # Dirty logic for lumi.gateway.mgl03 indicator light value_on = False if self._prop_on.format_ == bool else 0 - return await self.set_property_async(prop=self._prop_on, value=value_on) + await self.set_property_async(prop=self._prop_on, value=value_on) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index dbc59e5..1f3f186 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -992,14 +992,14 @@ class MIoTServiceEntity(Entity): siid=event.service.iid, eiid=event.iid, sub_id=sub_id) def get_map_value( - self, map_: dict[int, Any], key: int + self, map_: Optional[dict[int, Any]], key: int ) -> Any: if map_ is None: return None return map_.get(key, None) def get_map_key( - self, map_: dict[int, Any], value: Any + self, map_: Optional[dict[int, Any]], value: Any ) -> Optional[int]: if map_ is None: return None @@ -1008,7 +1008,7 @@ class MIoTServiceEntity(Entity): return key return None - def get_prop_value(self, prop: MIoTSpecProperty) -> Any: + def get_prop_value(self, prop: Optional[MIoTSpecProperty]) -> Any: if not prop: _LOGGER.error( 'get_prop_value error, property is None, %s, %s', @@ -1016,7 +1016,9 @@ class MIoTServiceEntity(Entity): return None return self._prop_value_map.get(prop, None) - def set_prop_value(self, prop: MIoTSpecProperty, value: Any) -> None: + def set_prop_value( + self, prop: Optional[MIoTSpecProperty], value: Any + ) -> None: if not prop: _LOGGER.error( 'set_prop_value error, property is None, %s, %s', @@ -1025,13 +1027,14 @@ class MIoTServiceEntity(Entity): self._prop_value_map[prop] = value async def set_property_async( - self, prop: MIoTSpecProperty, value: Any, update: bool = True + self, prop: Optional[MIoTSpecProperty], value: Any, + update_value: bool = True, write_ha_state: bool = True ) -> bool: - value = prop.value_format(value) if not prop: raise RuntimeError( f'set property failed, property is None, ' f'{self.entity_id}, {self.name}') + value = prop.value_format(value) if prop not in self.entity_data.props: raise RuntimeError( f'set property failed, unknown property, ' @@ -1047,8 +1050,9 @@ class MIoTServiceEntity(Entity): except MIoTClientError as e: raise RuntimeError( f'{e}, {self.entity_id}, {self.name}, {prop.name}') from e - if update: + if update_value: self._prop_value_map[prop] = value + if write_ha_state: self.async_write_ha_state() return True diff --git a/custom_components/xiaomi_home/water_heater.py b/custom_components/xiaomi_home/water_heater.py index aba6093..3c255ec 100644 --- a/custom_components/xiaomi_home/water_heater.py +++ b/custom_components/xiaomi_home/water_heater.py @@ -100,7 +100,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): ) -> None: """Initialize the Water heater.""" super().__init__(miot_device=miot_device, entity_data=entity_data) - self._attr_temperature_unit = None + self._attr_temperature_unit = None # type: ignore self._attr_supported_features = WaterHeaterEntityFeature(0) self._prop_on = None self._prop_temp = None @@ -112,20 +112,20 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): for prop in entity_data.props: # on if prop.name == 'on': + self._attr_supported_features |= WaterHeaterEntityFeature.ON_OFF self._prop_on = prop # temperature if prop.name == 'temperature': - if prop.value_range: - if ( - self._attr_temperature_unit is None - and prop.external_unit - ): - self._attr_temperature_unit = prop.external_unit - self._prop_temp = prop - else: + if not prop.value_range: _LOGGER.error( 'invalid temperature value_range format, %s', self.entity_id) + continue + if prop.external_unit: + self._attr_temperature_unit = prop.external_unit + self._attr_min_temp = prop.value_range.min_ + self._attr_max_temp = prop.value_range.max_ + self._prop_temp = prop # target-temperature if prop.name == 'target-temperature': if not prop.value_range: @@ -133,8 +133,8 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): 'invalid target-temperature value_range format, %s', self.entity_id) continue - self._attr_min_temp = prop.value_range.min_ - self._attr_max_temp = prop.value_range.max_ + self._attr_target_temperature_low = prop.value_range.min_ + self._attr_target_temperature_high = prop.value_range.max_ self._attr_precision = prop.value_range.step if self._attr_temperature_unit is None and prop.external_unit: self._attr_temperature_unit = prop.external_unit @@ -166,6 +166,8 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): async def async_set_temperature(self, **kwargs: Any) -> None: """Set the temperature the water heater should heat water to.""" + if not self._prop_target_temp: + return await self.set_property_async( prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE]) @@ -181,16 +183,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): return if self.get_prop_value(prop=self._prop_on) is False: await self.set_property_async( - prop=self._prop_on, value=True, update=False) + prop=self._prop_on, value=True, write_ha_state=False) await self.set_property_async( prop=self._prop_mode, value=self.get_map_key( - map_=self._mode_map, - value=operation_mode)) - - async def async_turn_away_mode_on(self) -> None: - """Set the water heater to away mode.""" - await self.hass.async_add_executor_job(self.turn_away_mode_on) + map_=self._mode_map, value=operation_mode)) @property def current_temperature(self) -> Optional[float]: @@ -200,6 +197,8 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): @property def target_temperature(self) -> Optional[float]: """Return the target temperature.""" + if not self._prop_target_temp: + return None return self.get_prop_value(prop=self._prop_target_temp) @property