Compare commits

...

3 Commits

Author SHA1 Message Date
Li Shuzhen
a4f9c29b6b
docs: update changelog and version to v0.3.2 (#1119)
Some checks failed
Tests / check-rule-format (push) Has been cancelled
Validate / validate-hassfest (push) Has been cancelled
Validate / validate-hacs (push) Has been cancelled
Validate / validate-lint (push) Has been cancelled
Validate / validate-setup (push) Has been cancelled
2025-05-23 09:42:16 +08:00
Li Shuzhen
62dd32a132
feat: add an alongside switch entity for the water heater (#1115) 2025-05-23 09:10:11 +08:00
Li Shuzhen
1bd338639b
feat: modify MIoT-Spec-V2 property format (#1111) 2025-05-23 08:45:35 +08:00
8 changed files with 106 additions and 55 deletions

View File

@ -1,4 +1,16 @@
# CHANGELOG # CHANGELOG
## v0.3.2
> Xiaomi Home has been added to the Home Assistant Community Store (HACS) as a default since May 8, 2025.
### Added
- Modify MIoT-Spec-V2 property format by spec_modify.yaml. [#1111](https://github.com/XiaoMi/ha_xiaomi_home/pull/1111)
### Changed
- Update the instructions of Xiaomi Home integration installation from HACS. [#102](https://github.com/XiaoMi/ha_xiaomi_home/pull/102) [#1088](https://github.com/XiaoMi/ha_xiaomi_home/pull/1088)
- Add an alongside switch entity for zimi.waterheater.h03 and xiaomi.waterheater.yms2. [#1115](https://github.com/XiaoMi/ha_xiaomi_home/pull/1115)
### Fixed
- Fix Chinese encoding in LAN Control. [#1114](https://github.com/XiaoMi/ha_xiaomi_home/pull/1114)
- Fix the MIoT-Spec-V2 of lxzn.switch.jcbcsm power consumption, voltage and current, shhf.light.sfla10 fan direction, zhimi.fan.za4 fan-level, zhimi.fan.sa1 fan-level. [#1110](https://github.com/XiaoMi/ha_xiaomi_home/pull/1110)
- Revise the Chinese descriptions of loock.lock.t2pv1 door state value-list. [#1110](https://github.com/XiaoMi/ha_xiaomi_home/pull/1110)
## v0.3.1 ## v0.3.1
### Changed ### Changed
- Setting the fan speed level when the fan is off will turning the fan on first. [#1031](https://github.com/XiaoMi/ha_xiaomi_home/pull/1031) - Setting the fan speed level when the fan is off will turning the fan on first. [#1031](https://github.com/XiaoMi/ha_xiaomi_home/pull/1031)

View File

@ -25,7 +25,7 @@
"cryptography", "cryptography",
"psutil" "psutil"
], ],
"version": "v0.3.1", "version": "v0.3.2",
"zeroconf": [ "zeroconf": [
"_miot-central._tcp.local." "_miot-central._tcp.local."
] ]

View File

@ -646,7 +646,8 @@ class MIoTClient:
result = await self._miot_lan.set_prop_async( result = await self._miot_lan.set_prop_async(
did=did, siid=siid, piid=piid, value=value) did=did, siid=siid, piid=piid, value=value)
_LOGGER.debug( _LOGGER.debug(
'lan set prop, %s, %s, %s -> %s', did, siid, piid, result) 'lan set prop, %s.%d.%d, %s -> %s',
did, siid, piid, value, result)
rc = (result or {}).get( rc = (result or {}).get(
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value) 'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
if rc in [0, 1]: if rc in [0, 1]:

View File

@ -601,7 +601,7 @@ class MIoTSpecProperty(_MIoTSpecBase):
if value is None: if value is None:
return None return None
if self.format_ == int: if self.format_ == int:
return int(value) return int(round(value))
if self.format_ == float: if self.format_ == float:
return round(value, self.precision) return round(value, self.precision)
if self.format_ == bool: if self.format_ == bool:
@ -1195,6 +1195,9 @@ class _SpecModify:
def get_prop_unit(self, siid: int, piid: int) -> Optional[str]: def get_prop_unit(self, siid: int, piid: int) -> Optional[str]:
return self.__get_prop_item(siid=siid, piid=piid, key='unit') return self.__get_prop_item(siid=siid, piid=piid, key='unit')
def get_prop_format(self, siid: int, piid: int) -> Optional[str]:
return self.__get_prop_item(siid=siid, piid=piid, key='format')
def get_prop_expr(self, siid: int, piid: int) -> Optional[str]: def get_prop_expr(self, siid: int, piid: int) -> Optional[str]:
return self.__get_prop_item(siid=siid, piid=piid, key='expr') return self.__get_prop_item(siid=siid, piid=piid, key='expr')
@ -1518,6 +1521,10 @@ class MIoTSpecParser:
siid=service['iid'], piid=property_['iid']) siid=service['iid'], piid=property_['iid'])
if custom_access: if custom_access:
spec_prop.access = custom_access spec_prop.access = custom_access
custom_format = self._spec_modify.get_prop_format(
siid=service['iid'], piid=property_['iid'])
if custom_format:
spec_prop.format_ = custom_format
custom_range = self._spec_modify.get_prop_value_range( custom_range = self._spec_modify.get_prop_value_range(
siid=service['iid'], piid=property_['iid']) siid=service['iid'], piid=property_['iid'])
if custom_range: if custom_range:

View File

@ -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"
]
}
]
}
] ]
} }

View File

@ -1,3 +1,6 @@
urn:miot-spec-v2:device:air-condition-outlet:0000A045:lumi-mcn04:1:
prop.3.4:
format: uint8
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: 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:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6

View File

@ -397,7 +397,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
} }
}, },
'optional': { 'optional': {
'properties': {'on', 'temperature', 'target-temperature', 'mode'} 'properties': {'temperature', 'target-temperature', 'mode'}
}, },
'entity': 'water_heater' 'entity': 'water_heater'
}, },

View File

@ -52,13 +52,10 @@ 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.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.water_heater import ( from homeassistant.components.water_heater import (STATE_ON, STATE_OFF,
STATE_ON,
STATE_OFF,
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
WaterHeaterEntity, WaterHeaterEntity,
WaterHeaterEntityFeature WaterHeaterEntityFeature)
)
from .miot.const import DOMAIN from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
@ -79,8 +76,8 @@ async def async_setup_entry(
new_entities = [] new_entities = []
for miot_device in device_list: for miot_device in device_list:
for data in miot_device.entity_list.get('water_heater', []): for data in miot_device.entity_list.get('water_heater', []):
new_entities.append(WaterHeater( new_entities.append(
miot_device=miot_device, entity_data=data)) WaterHeater(miot_device=miot_device, entity_data=data))
if new_entities: if new_entities:
async_add_entities(new_entities) async_add_entities(new_entities)
@ -95,12 +92,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
_mode_map: Optional[dict[Any, Any]] _mode_map: Optional[dict[Any, Any]]
def __init__( def __init__(self, miot_device: MIoTDevice,
self, miot_device: MIoTDevice, entity_data: MIoTEntityData entity_data: MIoTEntityData) -> None:
) -> None:
"""Initialize the Water heater.""" """Initialize the Water heater."""
super().__init__(miot_device=miot_device, entity_data=entity_data) 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._attr_supported_features = WaterHeaterEntityFeature(0)
self._prop_on = None self._prop_on = None
self._prop_temp = None self._prop_temp = None
@ -117,14 +113,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
# temperature # temperature
if prop.name == 'temperature': if prop.name == 'temperature':
if not prop.value_range: if not prop.value_range:
_LOGGER.error( _LOGGER.error('invalid temperature value_range format, %s',
'invalid temperature value_range format, %s',
self.entity_id) self.entity_id)
continue continue
if prop.external_unit: if prop.external_unit:
self._attr_temperature_unit = 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 self._prop_temp = prop
# target-temperature # target-temperature
if prop.name == 'target-temperature': if prop.name == 'target-temperature':
@ -133,9 +126,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
'invalid target-temperature value_range format, %s', 'invalid target-temperature value_range format, %s',
self.entity_id) self.entity_id)
continue continue
self._attr_target_temperature_low = prop.value_range.min_ self._attr_min_temp = prop.value_range.min_
self._attr_target_temperature_high = prop.value_range.max_ self._attr_max_temp = prop.value_range.max_
self._attr_precision = prop.value_range.step self._attr_target_temperature_step = prop.value_range.step
if self._attr_temperature_unit is None and prop.external_unit: if self._attr_temperature_unit is None and prop.external_unit:
self._attr_temperature_unit = prop.external_unit self._attr_temperature_unit = prop.external_unit
self._attr_supported_features |= ( self._attr_supported_features |= (
@ -144,8 +137,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
# mode # mode
if prop.name == 'mode': if prop.name == 'mode':
if not prop.value_list: if not prop.value_list:
_LOGGER.error( _LOGGER.error('mode value_list is None, %s', self.entity_id)
'mode value_list is None, %s', self.entity_id)
continue continue
self._mode_map = prop.value_list.to_map() self._mode_map = prop.value_list.to_map()
self._attr_operation_list = list(self._mode_map.values()) 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) await self.set_property_async(prop=self._prop_on, value=False)
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the temperature the water heater should heat water to.""" """Set the target temperature."""
if not self._prop_target_temp: await self.set_property_async(prop=self._prop_target_temp,
return value=kwargs[ATTR_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: async def async_set_operation_mode(self, operation_mode: str) -> None:
"""Set the operation mode of the water heater. """Set the operation mode of the water heater."""
Must be in the operation_list.
"""
if operation_mode == STATE_OFF: if operation_mode == STATE_OFF:
await self.set_property_async(prop=self._prop_on, value=False) await self.set_property_async(prop=self._prop_on, value=False)
return return
@ -182,32 +170,32 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
await self.set_property_async(prop=self._prop_on, value=True) await self.set_property_async(prop=self._prop_on, value=True)
return return
if self.get_prop_value(prop=self._prop_on) is False: if self.get_prop_value(prop=self._prop_on) is False:
await self.set_property_async( await self.set_property_async(prop=self._prop_on,
prop=self._prop_on, value=True, write_ha_state=False) value=True,
await self.set_property_async( write_ha_state=False)
prop=self._prop_mode, await self.set_property_async(prop=self._prop_mode,
value=self.get_map_key( value=self.get_map_key(
map_=self._mode_map, value=operation_mode)) map_=self._mode_map,
value=operation_mode))
@property @property
def current_temperature(self) -> Optional[float]: def current_temperature(self) -> Optional[float]:
"""Return the current temperature.""" """The current temperature."""
return self.get_prop_value(prop=self._prop_temp) return (None if self._prop_temp is None else self.get_prop_value(
prop=self._prop_temp))
@property @property
def target_temperature(self) -> Optional[float]: def target_temperature(self) -> Optional[float]:
"""Return the target temperature.""" """The target temperature."""
if not self._prop_target_temp: return (None if self._prop_target_temp is None else self.get_prop_value(
return None prop=self._prop_target_temp))
return self.get_prop_value(prop=self._prop_target_temp)
@property @property
def current_operation(self) -> Optional[str]: def current_operation(self) -> Optional[str]:
"""Return the current mode.""" """The current mode."""
if self.get_prop_value(prop=self._prop_on) is False: if self.get_prop_value(prop=self._prop_on) is False:
return STATE_OFF return STATE_OFF
if not self._prop_mode and self.get_prop_value(prop=self._prop_on): if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
return STATE_ON return STATE_ON
return self.get_map_value( return self.get_map_value(map_=self._mode_map,
map_=self._mode_map,
key=self.get_prop_value(prop=self._prop_mode)) key=self.get_prop_value(prop=self._prop_mode))