From 62dd32a1328197c34fdbea2dbdbb520c59e928ca Mon Sep 17 00:00:00 2001 From: Li Shuzhen Date: Fri, 23 May 2025 09:10:11 +0800 Subject: [PATCH] feat: add an alongside switch entity for the water heater (#1115) --- .../xiaomi_home/miot/miot_spec.py | 2 +- .../xiaomi_home/miot/specs/spec_add.json | 40 +++++++++ .../xiaomi_home/miot/specs/specv2entity.py | 2 +- custom_components/xiaomi_home/water_heater.py | 90 ++++++++----------- 4 files changed, 81 insertions(+), 53 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index 609520b..9cabdcb 100644 --- a/custom_components/xiaomi_home/miot/miot_spec.py +++ b/custom_components/xiaomi_home/miot/miot_spec.py @@ -601,7 +601,7 @@ class MIoTSpecProperty(_MIoTSpecBase): if value is None: return None if self.format_ == int: - return int(value) + return int(round(value)) if self.format_ == float: return round(value, self.precision) if self.format_ == bool: diff --git a/custom_components/xiaomi_home/miot/specs/spec_add.json b/custom_components/xiaomi_home/miot/specs/spec_add.json index 9d21ad8..3ead8d0 100644 --- a/custom_components/xiaomi_home/miot/specs/spec_add.json +++ b/custom_components/xiaomi_home/miot/specs/spec_add.json @@ -18,5 +18,45 @@ } ] } + ], + "urn:miot-spec-v2:device:water-heater:0000A02A:xiaomi-yms2:1": [ + { + "iid": 2, + "type": "urn:miot-spec-v2:service:switch:0000780C:xiaomi-yms2:1", + "description": "Switch", + "properties": [ + { + "iid": 6, + "type": "urn:miot-spec-v2:property:on:00000006:xiaomi-yms2:1", + "description": "Switch Status", + "format": "bool", + "access": [ + "read", + "write", + "notify" + ] + } + ] + } + ], + "urn:miot-spec-v2:device:water-heater:0000A02A:zimi-h03:1": [ + { + "iid": 2, + "type": "urn:miot-spec-v2:service:switch:0000780C:zimi-h03:1", + "description": "Switch", + "properties": [ + { + "iid": 6, + "type": "urn:miot-spec-v2:property:on:00000006:zimi-h03:1", + "description": "Switch Status", + "format": "bool", + "access": [ + "read", + "write", + "notify" + ] + } + ] + } ] } \ No newline at end of file diff --git a/custom_components/xiaomi_home/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index 72a2a52..68632dd 100644 --- a/custom_components/xiaomi_home/miot/specs/specv2entity.py +++ b/custom_components/xiaomi_home/miot/specs/specv2entity.py @@ -397,7 +397,7 @@ SPEC_SERVICE_TRANS_MAP: dict = { } }, 'optional': { - 'properties': {'on', 'temperature', 'target-temperature', 'mode'} + 'properties': {'temperature', 'target-temperature', 'mode'} }, 'entity': 'water_heater' }, diff --git a/custom_components/xiaomi_home/water_heater.py b/custom_components/xiaomi_home/water_heater.py index 3c255ec..8830197 100644 --- a/custom_components/xiaomi_home/water_heater.py +++ b/custom_components/xiaomi_home/water_heater.py @@ -52,25 +52,22 @@ from typing import Any, Optional from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.components.water_heater import ( - STATE_ON, - STATE_OFF, - ATTR_TEMPERATURE, - WaterHeaterEntity, - WaterHeaterEntityFeature -) +from homeassistant.components.water_heater import (STATE_ON, STATE_OFF, + ATTR_TEMPERATURE, + WaterHeaterEntity, + WaterHeaterEntityFeature) from .miot.const import DOMAIN -from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity +from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity from .miot.miot_spec import 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: """Set up a config entry.""" device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][ @@ -79,8 +76,8 @@ async def async_setup_entry( new_entities = [] for miot_device in device_list: for data in miot_device.entity_list.get('water_heater', []): - new_entities.append(WaterHeater( - miot_device=miot_device, entity_data=data)) + new_entities.append( + WaterHeater(miot_device=miot_device, entity_data=data)) if new_entities: async_add_entities(new_entities) @@ -95,12 +92,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): _mode_map: Optional[dict[Any, Any]] - def __init__( - self, miot_device: MIoTDevice, entity_data: MIoTEntityData - ) -> None: + def __init__(self, miot_device: MIoTDevice, + entity_data: MIoTEntityData) -> None: """Initialize the Water heater.""" super().__init__(miot_device=miot_device, entity_data=entity_data) - self._attr_temperature_unit = None # type: ignore + self._attr_temperature_unit = None self._attr_supported_features = WaterHeaterEntityFeature(0) self._prop_on = None self._prop_temp = None @@ -117,14 +113,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): # temperature if prop.name == 'temperature': if not prop.value_range: - _LOGGER.error( - 'invalid temperature value_range format, %s', - self.entity_id) + _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': @@ -133,9 +126,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): 'invalid target-temperature value_range format, %s', self.entity_id) continue - 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 + self._attr_min_temp = prop.value_range.min_ + self._attr_max_temp = prop.value_range.max_ + self._attr_target_temperature_step = prop.value_range.step if self._attr_temperature_unit is None and prop.external_unit: self._attr_temperature_unit = prop.external_unit self._attr_supported_features |= ( @@ -144,8 +137,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): # mode if prop.name == 'mode': if not prop.value_list: - _LOGGER.error( - 'mode value_list is None, %s', self.entity_id) + _LOGGER.error('mode value_list is None, %s', self.entity_id) continue self._mode_map = prop.value_list.to_map() self._attr_operation_list = list(self._mode_map.values()) @@ -165,16 +157,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): await self.set_property_async(prop=self._prop_on, value=False) 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]) + """Set the target temperature.""" + await self.set_property_async(prop=self._prop_target_temp, + value=kwargs[ATTR_TEMPERATURE]) async def async_set_operation_mode(self, operation_mode: str) -> None: - """Set the operation mode of the water heater. - Must be in the operation_list. - """ + """Set the operation mode of the water heater.""" if operation_mode == STATE_OFF: await self.set_property_async(prop=self._prop_on, value=False) return @@ -182,32 +170,32 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity): await self.set_property_async(prop=self._prop_on, value=True) return if self.get_prop_value(prop=self._prop_on) is False: - await self.set_property_async( - 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)) + await self.set_property_async(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)) @property def current_temperature(self) -> Optional[float]: - """Return the current temperature.""" - return self.get_prop_value(prop=self._prop_temp) + """The current temperature.""" + return (None if self._prop_temp is None else self.get_prop_value( + prop=self._prop_temp)) @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) + """The target temperature.""" + return (None if self._prop_target_temp is None else self.get_prop_value( + prop=self._prop_target_temp)) @property def current_operation(self) -> Optional[str]: - """Return the current mode.""" + """The current mode.""" if self.get_prop_value(prop=self._prop_on) is False: return STATE_OFF if not self._prop_mode and self.get_prop_value(prop=self._prop_on): return STATE_ON - return self.get_map_value( - map_=self._mode_map, - key=self.get_prop_value(prop=self._prop_mode)) + return self.get_map_value(map_=self._mode_map, + key=self.get_prop_value(prop=self._prop_mode))