Compare commits

...

6 Commits

Author SHA1 Message Date
Necroneco
1597329f16
Merge 506bd9f52e into d5c5e387c2 2025-11-25 12:30:46 +09:00
Li Shuzhen
d5c5e387c2
docs: update changelog and version to v0.4.5 (#1521)
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-11-25 10:04:22 +08:00
Li Shuzhen
e6750bb746
feat: format value, then evaluate by expression, and set precision at last (#1516)
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
2025-11-25 09:31:40 +08:00
Li Shuzhen
1b87381f43
Fix specs (#1517)
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
* fix: xiaomi.waterpuri.s1200g filter property unit (#1489)

* fix: xiaomi.aircondition.c24 power consumption device class (#1243)

* fix: cuco.plug.cp7pd power consumption and power value precision (#1502)

* fix: lxzn.valve.02 electricity property unit (#1514)
2025-11-24 17:11:38 +08:00
caibinqing
506bd9f52e
fix pylint 2025-04-07 11:19:19 +08:00
caibinqing
7c0caa9df7
fix: add migration step for config entry 2025-04-07 10:54:09 +08:00
11 changed files with 198 additions and 11 deletions

View File

@ -1,4 +1,12 @@
# CHANGELOG # CHANGELOG
## v0.4.5
### Changed
- Ignore mdns REMOVED package. [#1296](https://github.com/XiaoMi/ha_xiaomi_home/pull/1296)
- Format value type first, then evaluate by expression, and set precision at last. [#1516](https://github.com/XiaoMi/ha_xiaomi_home/pull/1516)
### Fixed
- Fix xiaomi.derh.lite temperature precision. [#1505](https://github.com/XiaoMi/ha_xiaomi_home/pull/1505)
- Fix xiaomi.waterpuri.s1200g filter property unit, lxzn.valve.02 electricity property unit, xiaomi.aircondition.c24 power consumption device class, and cuco.plug.cp7pd power consumption and power value precision. [#1517](https://github.com/XiaoMi/ha_xiaomi_home/pull/1517)
## v0.4.4 ## v0.4.4
### Added ### Added
- Add Turkish language support. [#1468](https://github.com/XiaoMi/ha_xiaomi_home/pull/1468) - Add Turkish language support. [#1468](https://github.com/XiaoMi/ha_xiaomi_home/pull/1468)

View File

@ -384,6 +384,7 @@ Example:
- Contribution Guidelines: [English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md) - Contribution Guidelines: [English](./CONTRIBUTING.md) | [简体中文](./doc/CONTRIBUTING_zh.md)
- [ChangeLog](./CHANGELOG.md) - [ChangeLog](./CHANGELOG.md)
- Development Documents: https://developers.home-assistant.io/docs/creating_component_index - Development Documents: https://developers.home-assistant.io/docs/creating_component_index
- [FAQ](https://github.com/XiaoMi/ha_xiaomi_home/wiki)
## Directory Structure ## Directory Structure

View File

@ -349,3 +349,101 @@ async def async_remove_config_entry_device(
_LOGGER.info( _LOGGER.info(
'remove device, %s, %s', identifiers[1], device_entry.id) 'remove device, %s, %s', identifiers[1], device_entry.id)
return True return True
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug(
'Migrating configuration from version %s.%s',
config_entry.version,
config_entry.minor_version,
)
if config_entry.version > 1:
# This means the user has downgraded from a future version
return False
if config_entry.version == 1:
await _migrate_v1_to_v2(hass, config_entry)
_LOGGER.debug(
'Migration to configuration version %s.%s successful',
config_entry.version,
config_entry.minor_version,
)
return True
async def _migrate_v1_to_v2(hass: HomeAssistant, config_entry: ConfigEntry):
def ha_persistent_notify(
notify_id: str, title: Optional[str] = None,
message: Optional[str] = None
) -> None:
"""Send messages in Notifications dialog box."""
if title:
persistent_notification.async_create(
hass=hass, message=message or '',
title=title, notification_id=notify_id)
else:
persistent_notification.async_dismiss(
hass=hass, notification_id=notify_id)
entry_id = config_entry.entry_id
entry_data = dict(config_entry.data)
ha_persistent_notify(
notify_id=f'{entry_id}.oauth_error', title=None, message=None)
miot_client: MIoTClient = await get_miot_instance_async(
hass=hass, entry_id=entry_id,
entry_data=entry_data,
persistent_notify=ha_persistent_notify)
# Spec parser
spec_parser = MIoTSpecParser(
lang=entry_data.get(
'integration_language', DEFAULT_INTEGRATION_LANGUAGE),
storage=miot_client.miot_storage,
loop=miot_client.main_loop
)
await spec_parser.init_async()
# Manufacturer
manufacturer: DeviceManufacturer = DeviceManufacturer(
storage=miot_client.miot_storage,
loop=miot_client.main_loop)
await manufacturer.init_async()
er = entity_registry.async_get(hass)
for _, info in miot_client.device_list.items():
spec_instance = await spec_parser.parse(urn=info['urn'])
if not isinstance(spec_instance, MIoTSpecInstance):
continue
device: MIoTDevice = MIoTDevice(
miot_client=miot_client,
device_info={
**info, 'manufacturer': manufacturer.get_name(
info.get('manufacturer', ''))},
spec_instance=spec_instance)
device.spec_transform()
# Update unique_id
for platform, entities in device.entity_list.items():
for entity in entities:
if not isinstance(entity.spec, MIoTSpecService):
continue
old_unique_id = device.gen_service_entity_id_v1(
ha_domain=DOMAIN,
siid=entity.spec.iid,
)
entity_id = er.async_get_entity_id(
platform, DOMAIN, old_unique_id
)
if entity_id is None:
continue
new_unique_id = device.gen_service_entity_id(
ha_domain=DOMAIN,
siid=entity.spec.iid,
description=entity.spec.description,
)
er.async_update_entity(entity_id, new_unique_id=new_unique_id)
hass.config_entries.async_update_entry(config_entry, version=2)

View File

@ -108,7 +108,7 @@ _LOGGER = logging.getLogger(__name__)
class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Xiaomi Home config flow.""" """Xiaomi Home config flow."""
# pylint: disable=unused-argument, inconsistent-quotes # pylint: disable=unused-argument, inconsistent-quotes
VERSION = 1 VERSION = 2
MINOR_VERSION = 1 MINOR_VERSION = 1
DEFAULT_AREA_NAME_RULE = 'room' DEFAULT_AREA_NAME_RULE = 'room'
_main_loop: asyncio.AbstractEventLoop _main_loop: asyncio.AbstractEventLoop

View File

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

View File

@ -345,6 +345,11 @@ class MIoTDevice:
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_' f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
f'{self._model_strs[-1][:20]}') f'{self._model_strs[-1][:20]}')
def gen_service_entity_id_v1(self, ha_domain: str, siid: int) -> str:
return (
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
f'{self._model_strs[-1][:20]}_s_{siid}')
def gen_service_entity_id(self, ha_domain: str, siid: int, def gen_service_entity_id(self, ha_domain: str, siid: int,
description: str) -> str: description: str) -> str:
return ( return (
@ -748,6 +753,7 @@ class MIoTDevice:
'w': UnitOfPower.WATT, 'w': UnitOfPower.WATT,
'W': UnitOfPower.WATT, 'W': UnitOfPower.WATT,
'kW': UnitOfPower.KILO_WATT, 'kW': UnitOfPower.KILO_WATT,
'Wh': UnitOfEnergy.WATT_HOUR,
'kWh': UnitOfEnergy.KILO_WATT_HOUR, 'kWh': UnitOfEnergy.KILO_WATT_HOUR,
'A': UnitOfElectricCurrent.AMPERE, 'A': UnitOfElectricCurrent.AMPERE,
'mA': UnitOfElectricCurrent.MILLIAMPERE, 'mA': UnitOfElectricCurrent.MILLIAMPERE,
@ -1040,6 +1046,7 @@ class MIoTServiceEntity(Entity):
f'set property failed, property is None, ' f'set property failed, property is None, '
f'{self.entity_id}, {self.name}') f'{self.entity_id}, {self.name}')
value = prop.value_format(value) value = prop.value_format(value)
value = prop.value_precision(value)
if prop not in self.entity_data.props: if prop not in self.entity_data.props:
raise RuntimeError( raise RuntimeError(
f'set property failed, unknown property, ' f'set property failed, unknown property, '
@ -1077,9 +1084,11 @@ class MIoTServiceEntity(Entity):
'get property failed, not readable, %s, %s, %s', 'get property failed, not readable, %s, %s, %s',
self.entity_id, self.name, prop.name) self.entity_id, self.name, prop.name)
return None return None
result = prop.value_format( value: Any = prop.value_format(
await self.miot_device.miot_client.get_prop_async( await self.miot_device.miot_client.get_prop_async(
did=self.miot_device.did, siid=prop.service.iid, piid=prop.iid)) did=self.miot_device.did, siid=prop.service.iid, piid=prop.iid))
value = prop.eval_expr(value)
result = prop.value_precision(value)
if result != self._prop_value_map[prop]: if result != self._prop_value_map[prop]:
self._prop_value_map[prop] = result self._prop_value_map[prop] = result
self.async_write_ha_state() self.async_write_ha_state()
@ -1110,7 +1119,7 @@ class MIoTServiceEntity(Entity):
continue continue
value: Any = prop.value_format(params['value']) value: Any = prop.value_format(params['value'])
value = prop.eval_expr(value) value = prop.eval_expr(value)
value = prop.value_format(value) value = prop.value_precision(value)
self._prop_value_map[prop] = value self._prop_value_map[prop] = value
if prop in self._prop_changed_subs: if prop in self._prop_changed_subs:
self._prop_changed_subs[prop](prop, value) self._prop_changed_subs[prop](prop, value)
@ -1258,6 +1267,7 @@ class MIoTPropertyEntity(Entity):
f'set property failed, not writable, ' f'set property failed, not writable, '
f'{self.entity_id}, {self.name}') f'{self.entity_id}, {self.name}')
value = self.spec.value_format(value) value = self.spec.value_format(value)
value = self.spec.value_precision(value)
try: try:
await self.miot_device.miot_client.set_prop_async( await self.miot_device.miot_client.set_prop_async(
did=self.miot_device.did, siid=self.spec.service.iid, did=self.miot_device.did, siid=self.spec.service.iid,
@ -1275,16 +1285,19 @@ class MIoTPropertyEntity(Entity):
'get property failed, not readable, %s, %s', 'get property failed, not readable, %s, %s',
self.entity_id, self.name) self.entity_id, self.name)
return None return None
return self.spec.value_format( value: Any = self.spec.value_format(
await self.miot_device.miot_client.get_prop_async( await self.miot_device.miot_client.get_prop_async(
did=self.miot_device.did, siid=self.spec.service.iid, did=self.miot_device.did, siid=self.spec.service.iid,
piid=self.spec.iid)) piid=self.spec.iid))
value = self.spec.eval_expr(value)
result = self.spec.value_precision(value)
return result
def __on_value_changed(self, params: dict, ctx: Any) -> None: def __on_value_changed(self, params: dict, ctx: Any) -> None:
_LOGGER.debug('property changed, %s', params) _LOGGER.debug('property changed, %s', params)
value: Any = self.spec.value_format(params['value']) value: Any = self.spec.value_format(params['value'])
value = self.spec.eval_expr(value) value = self.spec.eval_expr(value)
self._value = self.spec.value_format(value) self._value = self.spec.value_precision(value)
if not self._pending_write_ha_state_timer: if not self._pending_write_ha_state_timer:
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -599,15 +599,25 @@ class MIoTSpecProperty(_MIoTSpecBase):
def value_format(self, value: Any) -> Any: def value_format(self, value: Any) -> Any:
if value is None: if value is None:
return None return None
if isinstance(value, str):
if self.format_ == int:
value = int(float(value))
elif self.format_ == float:
value = float(value)
if self.format_ == bool:
return bool(value in [True, 1, 'True', 'true', '1'])
return value
def value_precision(self, value: Any) -> Any:
if value is None:
return None
if self.format_ == float:
return round(value, self.precision)
if self.format_ == int: if self.format_ == int:
if self.value_range is None: if self.value_range is None:
return int(round(value)) return int(round(value))
return int( return int(
round(value / self.value_range.step) * self.value_range.step) round(value / self.value_range.step) * self.value_range.step)
if self.format_ == float:
return round(value, self.precision)
if self.format_ == bool:
return bool(value in [True, 1, 'True', 'true', '1'])
return value return value
def dump(self) -> dict: def dump(self) -> dict:

View File

@ -5,6 +5,11 @@
"service:003:property:001:valuelist:001": "Dry" "service:003:property:001:valuelist:001": "Dry"
} }
}, },
"urn:miot-spec-v2:device:electronic-valve:0000A0A7:lxzn-02": {
"zh-Hans": {
"service:004:property:001": "功率过高-阈值设置"
}
},
"urn:miot-spec-v2:device:electronic-valve:0000A0A7:ykcn-cbcs": { "urn:miot-spec-v2:device:electronic-valve:0000A0A7:ykcn-cbcs": {
"zh-Hans": { "zh-Hans": {
"service:004:property:001": "功率过高-阈值设置", "service:004:property:001": "功率过高-阈值设置",

View File

@ -26,6 +26,7 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1:
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1 urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1:
prop.8.6: prop.8.6:
name: power-consumption
unit: kWh unit: kWh
prop.10.6: prop.10.6:
unit: none unit: none
@ -144,6 +145,25 @@ urn:miot-spec-v2:device:dehumidifier:0000A02D:xiaomi-lite:1:
- -30 - -30
- 100 - 100
- 0.1 - 0.1
urn:miot-spec-v2:device:electronic-valve:0000A0A7:lxzn-02:1:0000C833:
prop.3.1:
format: float
value-range:
- 0
- 999999
- 0.01
expr: (src_value/100)
prop.3.2:
unit: mA
prop.3.3:
format: float
value-range:
- 0
- 65535
- 0.1
expr: (src_value/10)
prop.4.1:
unit: kW
urn:miot-spec-v2:device:electronic-valve:0000A0A7:sanmei-s1:1: urn:miot-spec-v2:device:electronic-valve:0000A0A7:sanmei-s1:1:
prop.3.1: prop.3.1:
format: float format: float
@ -370,6 +390,19 @@ urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2:
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2d:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2d:1:
prop.3.2: prop.3.2:
unit: mA unit: mA
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp7pd:1:
prop.11.1:
unit: Wh
value-range:
- 0
- 65535
- 0.001
expr: (src_value*1000)
prop.11.4:
value-range:
- 0
- 10000
- 0.01
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1:
prop.11.1: prop.11.1:
format: float format: float
@ -490,6 +523,24 @@ urn:miot-spec-v2:device:water-purifier:0000A013:roswan-lte01:1:0000D05A:
unit: ppm unit: ppm
prop.4.2: prop.4.2:
unit: ppm unit: ppm
urn:miot-spec-v2:device:water-purifier:0000A013:xiaomi-s1200g:1:0000D05A:
prop.3.2:
unit: days
prop.3.3:
unit: days
prop.3.4:
unit: L
prop.3.5:
unit: L
prop.5.2:
unit: days
prop.5.3:
unit: days
prop.5.4:
unit: L
prop.5.5:
unit: L
urn:miot-spec-v2:device:water-purifier:0000A013:xiaomi-s1200g:2:0000D05A: urn:miot-spec-v2:device:water-purifier:0000A013:xiaomi-s1200g:1:0000D05A
urn:miot-spec-v2:device:water-purifier:0000A013:yunmi-s20:1: urn:miot-spec-v2:device:water-purifier:0000A013:yunmi-s20:1:
prop.4.1: prop.4.1:
unit: ppm unit: ppm

View File

@ -110,7 +110,7 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
self._attr_native_unit_of_measurement = list( self._attr_native_unit_of_measurement = list(
unit_sets)[0] if unit_sets else None unit_sets)[0] if unit_sets else None
# Set suggested precision # Set suggested precision
if spec.format_ in {int, float} and spec.expr is None: if spec.format_ == float:
self._attr_suggested_display_precision = spec.precision self._attr_suggested_display_precision = spec.precision
# Set state_class # Set state_class
if spec.state_class: if spec.state_class:

View File

@ -386,6 +386,7 @@ siid、piid、eiid、aiid、value 均为十进制三位整数。
- 贡献指南: [English](../CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md) - 贡献指南: [English](../CONTRIBUTING.md) | [简体中文](./CONTRIBUTING_zh.md)
- [更新日志](../CHANGELOG.md) - [更新日志](../CHANGELOG.md)
- 开发文档: https://developers.home-assistant.io/docs/creating_component_index - 开发文档: https://developers.home-assistant.io/docs/creating_component_index
- [常见问题](https://github.com/XiaoMi/ha_xiaomi_home/wiki)
## 目录结构 ## 目录结构