fix: climate on/off feature initialization (#899)
Some checks are pending
Tests / check-rule-format (push) Waiting to run
Validate / validate-hassfest (push) Waiting to run
Validate / validate-hacs (push) Waiting to run
Validate / validate-lint (push) Waiting to run
Validate / validate-setup (push) Waiting to run

This commit is contained in:
Li Shuzhen 2025-03-20 18:02:25 +08:00 committed by GitHub
parent 60d054cf19
commit 3759aa9a1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 84 additions and 34 deletions

View File

@ -51,6 +51,7 @@ from typing import Any, Optional
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.const import UnitOfTemperature
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.climate import ( from homeassistant.components.climate import (
FAN_ON, FAN_OFF, SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL, FAN_ON, FAN_OFF, SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL,
@ -101,21 +102,19 @@ class FeatureOnOff(MIoTServiceEntity, ClimateEntity):
self._prop_on = None self._prop_on = None
super().__init__(miot_device=miot_device, entity_data=entity_data) super().__init__(miot_device=miot_device, entity_data=entity_data)
# properties
for prop in entity_data.props: def _init_on_off(self, service_name: str, prop_name: str) -> None:
if prop.name == 'on': """Initialize the on_off feature."""
if ( for prop in self.entity_data.props:
# The "on" property of the "fan-control" service is not if prop.name == prop_name and prop.service.name == service_name:
# the on/off feature of the entity. if prop.format_ != bool:
prop.service.name == 'air-conditioner' or _LOGGER.error('wrong format %s %s, %s', service_name,
prop.service.name == 'heater' or prop_name, self.entity_id)
prop.service.name == 'thermostat' or continue
prop.service.name == 'electric-blanket'): self._attr_supported_features |= ClimateEntityFeature.TURN_ON
self._attr_supported_features |= ( self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
ClimateEntityFeature.TURN_ON) self._prop_on = prop
self._attr_supported_features |= ( break
ClimateEntityFeature.TURN_OFF)
self._prop_on = prop
async def async_turn_on(self) -> None: async def async_turn_on(self) -> None:
"""Turn on.""" """Turn on."""
@ -134,6 +133,7 @@ class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
entity_data: MIoTEntityData) -> None: entity_data: MIoTEntityData) -> None:
"""Initialize the feature class.""" """Initialize the feature class."""
self._prop_target_temp = None self._prop_target_temp = None
self._attr_temperature_unit = None
super().__init__(miot_device=miot_device, entity_data=entity_data) super().__init__(miot_device=miot_device, entity_data=entity_data)
# properties # properties
@ -151,6 +151,10 @@ class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
self._attr_supported_features |= ( self._attr_supported_features |= (
ClimateEntityFeature.TARGET_TEMPERATURE) ClimateEntityFeature.TARGET_TEMPERATURE)
self._prop_target_temp = prop self._prop_target_temp = prop
break
# temperature_unit is required by the climate entity
if not self._attr_temperature_unit:
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
async def async_set_temperature(self, **kwargs): async def async_set_temperature(self, **kwargs):
"""Set the target temperature.""" """Set the target temperature."""
@ -197,6 +201,7 @@ class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
self._attr_supported_features |= ( self._attr_supported_features |= (
ClimateEntityFeature.PRESET_MODE) ClimateEntityFeature.PRESET_MODE)
self._prop_mode = prop self._prop_mode = prop
break
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode.""" """Set the preset mode."""
@ -365,6 +370,7 @@ class FeatureTemperature(MIoTServiceEntity, ClimateEntity):
for prop in entity_data.props: for prop in entity_data.props:
if prop.name == 'temperature': if prop.name == 'temperature':
self._prop_env_temperature = prop self._prop_env_temperature = prop
break
@property @property
def current_temperature(self) -> Optional[float]: def current_temperature(self) -> Optional[float]:
@ -387,6 +393,7 @@ class FeatureHumidity(MIoTServiceEntity, ClimateEntity):
for prop in entity_data.props: for prop in entity_data.props:
if prop.name == 'relative-humidity': if prop.name == 'relative-humidity':
self._prop_env_humidity = prop self._prop_env_humidity = prop
break
@property @property
def current_humidity(self) -> Optional[float]: def current_humidity(self) -> Optional[float]:
@ -418,6 +425,7 @@ class FeatureTargetHumidity(MIoTServiceEntity, ClimateEntity):
self._attr_supported_features |= ( self._attr_supported_features |= (
ClimateEntityFeature.TARGET_HUMIDITY) ClimateEntityFeature.TARGET_HUMIDITY)
self._prop_target_humidity = prop self._prop_target_humidity = prop
break
async def async_set_humidity(self, humidity): async def async_set_humidity(self, humidity):
"""Set the target humidity.""" """Set the target humidity."""
@ -447,6 +455,8 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
self._attr_icon = 'mdi:radiator' self._attr_icon = 'mdi:radiator'
# hvac modes # hvac modes
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
# on/off
self._init_on_off('heater', 'on')
# preset modes # preset modes
self._init_preset_modes('heater', 'heat-level') self._init_preset_modes('heater', 'heat-level')
@ -482,10 +492,12 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
super().__init__(miot_device=miot_device, entity_data=entity_data) super().__init__(miot_device=miot_device, entity_data=entity_data)
self._attr_icon = 'mdi:air-conditioner' self._attr_icon = 'mdi:air-conditioner'
# on/off
self._init_on_off('air-conditioner', 'on')
# hvac modes # hvac modes
self._attr_hvac_modes = None self._attr_hvac_modes = None
for prop in entity_data.props: for prop in entity_data.props:
if prop.name == 'mode': if prop.name == 'mode' and prop.service.name == 'air-conditioner':
if not prop.value_list: if not prop.value_list:
_LOGGER.error('invalid mode value_list, %s', self.entity_id) _LOGGER.error('invalid mode value_list, %s', self.entity_id)
continue continue
@ -620,22 +632,17 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
self._attr_icon = 'mdi:hvac' self._attr_icon = 'mdi:hvac'
# hvac modes # hvac modes
for prop in entity_data.props: for prop in entity_data.props:
if prop.name == 'mode': if prop.name == 'mode' and prop.service.name == 'ptc-bath-heater':
if not prop.value_list: if not prop.value_list:
_LOGGER.error('invalid mode value_list, %s', self.entity_id) _LOGGER.error('invalid mode value_list, %s', self.entity_id)
continue continue
self._hvac_mode_map = {} self._hvac_mode_map = {}
for item in prop.value_list.items: for item in prop.value_list.items:
if item.name in {'off', 'idle'}: if item.name in {'off', 'idle'}:
if (HVACMode.OFF self._hvac_mode_map[item.value] = HVACMode.OFF
not in list(self._hvac_mode_map.values())): break
self._hvac_mode_map[item.value] = HVACMode.OFF if self._hvac_mode_map:
elif (HVACMode.AUTO self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
not in list(self._hvac_mode_map.values())):
self._hvac_mode_map[item.value] = HVACMode.AUTO
self._attr_hvac_modes = list(self._hvac_mode_map.values())
if HVACMode.OFF in self._attr_hvac_modes:
self._prop_mode = prop
else: else:
_LOGGER.error('no idle mode, %s', self.entity_id) _LOGGER.error('no idle mode, %s', self.entity_id)
# preset modes # preset modes
@ -643,7 +650,7 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the target hvac mode.""" """Set the target hvac mode."""
if self._prop_mode is None: if self._prop_mode is None or hvac_mode != HVACMode.OFF:
return return
mode_value = self.get_map_key(map_=self._hvac_mode_map, value=hvac_mode) mode_value = self.get_map_key(map_=self._hvac_mode_map, value=hvac_mode)
if mode_value is None or not await self.set_property_async( if mode_value is None or not await self.set_property_async(
@ -656,13 +663,12 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
"""The current hvac mode.""" """The current hvac mode."""
if self._prop_mode is None: if self._prop_mode is None:
return None return None
mode_value = self.get_map_value( current_mode = self.get_prop_value(prop=self._prop_mode)
map_=self._hvac_mode_map, if current_mode is None:
key=self.get_prop_value(prop=self._prop_mode)) return None
if mode_value == HVACMode.OFF or mode_value is None: mode_value = self.get_map_value(map_=self._hvac_mode_map,
return mode_value key=current_mode)
return HVACMode.AUTO if (HVACMode.AUTO return HVACMode.OFF if mode_value == HVACMode.OFF else HVACMode.AUTO
in self._attr_hvac_modes) else None
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature, class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
@ -677,6 +683,8 @@ class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
self._attr_icon = 'mdi:thermostat' self._attr_icon = 'mdi:thermostat'
# hvac modes # hvac modes
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF] self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
# on/off
self._init_on_off('thermostat', 'on')
# preset modes # preset modes
self._init_preset_modes('thermostat', 'mode') self._init_preset_modes('thermostat', 'mode')
@ -705,6 +713,8 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
self._attr_icon = 'mdi:rug' self._attr_icon = 'mdi:rug'
# hvac modes # hvac modes
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
# on/off
self._init_on_off('electric-blanket', 'on')
# preset modes # preset modes
self._init_preset_modes('electric-blanket', 'mode') self._init_preset_modes('electric-blanket', 'mode')

View File

@ -76,3 +76,43 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:d
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
urn:miot-spec-v2:device:airer:0000A00D:mrbond-m33a:1:
prop.2.3:
name: current-position-a
prop.2.11:
name: current-position-b
urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
prop.2.3:
value-list:
- value: 1
description: one
- value: 2
description: two
- value: 3
description: three
- value: 4
description: four
- value: 5
description: five
- value: 6
description: six
- value: 7
description: seven
- value: 8
description: eight
- value: 9
description: nine
- value: 10
description: ten
- value: 11
description: eleven
- value: 12
description: twelve
- value: 13
description: thirteen
- value: 14
description: fourteen
- value: 15
description: fifteen
- value: 16
description: sixteen