diff --git a/custom_components/xiaomi_home/climate.py b/custom_components/xiaomi_home/climate.py index fb3dc457..af7f2a0a 100644 --- a/custom_components/xiaomi_home/climate.py +++ b/custom_components/xiaomi_home/climate.py @@ -295,39 +295,37 @@ async def async_set_humidity(self, humidity): 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.""" @@ -368,9 +366,9 @@ def hvac_mode(self) -> Optional[HVACMode]: """Return the hvac mode. e.g., heat, cool mode.""" if self.get_prop_value(prop=self._prop_on) is False: return HVACMode.OFF - return self.get_map_key( + return self.get_map_value( map_=self._hvac_mode_map, - value=self.get_prop_value(prop=self._prop_mode)) + key=self.get_prop_value(prop=self._prop_mode)) @property def fan_mode(self) -> Optional[str]: @@ -388,12 +386,10 @@ def swing_mode(self) -> Optional[str]: 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: @@ -449,7 +445,11 @@ def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None: 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 @@ -464,10 +464,10 @@ def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None: 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 78a6a028..f2ebaebf 100644 --- a/custom_components/xiaomi_home/cover.py +++ b/custom_components/xiaomi_home/cover.py @@ -200,7 +200,7 @@ async def async_set_cover_position(self, **kwargs) -> None: 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 a28b989c..92a41f42 100644 --- a/custom_components/xiaomi_home/fan.py +++ b/custom_components/xiaomi_home/fan.py @@ -303,7 +303,7 @@ def percentage(self) -> Optional[int]: 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 16676623..ef9fed26 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 @@ async def async_turn_on(self, **kwargs) -> None: 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 @@ async def async_turn_on(self, **kwargs) -> None: 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 991e2b13..1465a450 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -903,14 +903,14 @@ async def async_will_remove_from_hass(self) -> None: 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 @@ -919,7 +919,7 @@ def get_map_key( 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', @@ -927,7 +927,9 @@ def get_prop_value(self, prop: MIoTSpecProperty) -> Any: 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', @@ -936,13 +938,14 @@ def set_prop_value(self, prop: MIoTSpecProperty, value: Any) -> None: 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, ' @@ -958,8 +961,9 @@ async def set_property_async( 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 aba60934..3c255ecb 100644 --- a/custom_components/xiaomi_home/water_heater.py +++ b/custom_components/xiaomi_home/water_heater.py @@ -100,7 +100,7 @@ def __init__( ) -> 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 @@ def __init__( 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 @@ def __init__( '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 @@ async def async_turn_off(self) -> None: 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 @@ async def async_set_operation_mode(self, operation_mode: str) -> None: 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 @@ def current_temperature(self) -> Optional[float]: @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