Compare commits

...

16 Commits
v0.2.1 ... main

Author SHA1 Message Date
Joel
c6be6be1ec
fix: initialization problem of _attr_fan_modes (#955)
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-04-02 11:30:58 +08:00
Necroneco
77b0a4531b
fix: fix some specs (#949) 2025-04-02 08:53:15 +08:00
Li Shuzhen
7d9250914c
docs: update changelog and version to v0.2.4 (#937)
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-03-28 09:26:10 +08:00
Li Shuzhen
a09289ef90
Fix specs (#929)
* fix: cuco.plug.cp2 voltage and power value ratio

* fix: cgllc.airmonitor.s1 unit ppb

* fix: roswan.waterpuri.lte01 tds unit

* fix: lumi.relay.c2acn01 power consumption unit

* fix: xiaomi.bhf_light.s1 fan level of ventilation

* fix: error log
2025-03-28 09:10:09 +08:00
Necroneco
b0428dc95a
feat: make submersion-state, contact-state, occupancy-status as binary_sensor (#905)
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-03-27 15:45:46 +08:00
whoami
19ed04f2f5
fix: correct unit,icon and translations for hhcc-v1 (#927)
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-03-25 09:54:02 +08:00
dw881114
e174a73f52
Update spec_modify.yaml (#921)
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-03-24 16:34:48 +08:00
Li Shuzhen
a1aa1c024f
docs: update changelog and version to v0.2.3 (#911)
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-03-21 09:53:39 +08:00
Li Shuzhen
372e635681
Fix specs (#910)
* fix: chuangmi.plug.212a01 power consumption value

* fix: yeelink.bhf_light.v10 mode description in English
2025-03-21 09:36:14 +08:00
Li Shuzhen
3759aa9a1b
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
2025-03-20 18:02:25 +08:00
Li Shuzhen
60d054cf19
docs: update changelog and version to v0.2.2 (#882)
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-03-14 08:47:45 +08:00
Li Shuzhen
6680d9e8cb
feat: add conversion rules for the air-conditioner service and the air-fresh service (#879) 2025-03-14 08:23:03 +08:00
Li Shuzhen
0ef8cb6370
fix: xiaomi.aircondition.m9 humidity-range unit (#878)
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-03-13 17:41:02 +08:00
Li Shuzhen
8f0a69c611
feat: convert the mode of the ptc bath heater to the preset mode (#874) 2025-03-13 17:37:44 +08:00
Li Shuzhen
8be0fa5d61
fix: MIoT-Spec-V2 conflicts of xiaomi.fan.p5 and mike.bhf_light.2 (#866)
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-03-12 15:22:03 +08:00
Necroneco
07cb4ed193
feat: avoid setting icon when device_class is defined (#855) 2025-03-12 15:17:02 +08:00
13 changed files with 319 additions and 88 deletions

View File

@ -1,13 +1,49 @@
# CHANGELOG # CHANGELOG
## v0.2.4
### Added
- Convert the submersion-state, the contact-state and the occupancy-status property to the binary_sensor entity. [#905](https://github.com/XiaoMi/ha_xiaomi_home/pull/905)
### Changed
- suittc.airrtc.wk168 mode descriptions are set to strings of numbers from 1 to 16. [#921](https://github.com/XiaoMi/ha_xiaomi_home/pull/921)
- Do not set _attr_suggested_display_precision when the spec.expr is set in spec_modify.yaml [#929](https://github.com/XiaoMi/ha_xiaomi_home/pull/929)
- Set "unknown event msg" log to info level.
### Fixed
- hhcc.plantmonitor.v1 soil moisture and soil ec icon and unit. [#927](https://github.com/XiaoMi/ha_xiaomi_home/pull/27)
- cuco.plug.cp2 voltage and power value ratio.
- cgllc.airmonitor.s1 unit ppb.
- roswan.waterpuri.lte01 tds unit.
- lumi.relay.c2acn01 power consumption unit
- xiaomi.bhf_light.s1 fan level of ventilation.
## v0.2.3
### Changed
- Specify the service name and the property name during the climate entity's on/off feature initialization. [#899](https://github.com/XiaoMi/ha_xiaomi_home/pull/899)
- Remove the useless total-battery property from `SPEC_PROP_TRANS_MAP`.
### Fixed
- Fix the hvac mode setting error when changing the preset mode of the ptc-bath-heater.
- Fix the ambiguous descriptions of yeelink.bhf_light.v10 ptc-bath-heater mode value-list.
- Fix the power consumption value of chuangmi.plug.212a01. [#910](https://github.com/XiaoMi/ha_xiaomi_home/pull/910)
## v0.2.2
This version has modified the conversion rules of the climate entity, which will have effect on the devices with the ptc-bath-heater, the air-conditioner and the air-fresh service. After updating, you need to restart Home Assistant and check `xiaomi_home > CONFIGURE >
Update entity conversion rules > NEXT` to reload the integration.
这个版本修改了浴霸、空调、新风机的实体转换规则,更新之后需要重启 Home Assistant并且勾选 `xiaomi_home > 配置 > 更新实体转换规则 > 下一步` 重新加载集成。
### Added
- Add conversion rules for the air-conditioner service and the air-fresh service. [#879](https://github.com/XiaoMi/ha_xiaomi_home/pull/879)
### Changed
- Convert the mode of the ptc bath heater to the preset mode of the climate entity. [#874](https://github.com/XiaoMi/ha_xiaomi_home/pull/874)
- Use Home Assistant default icon when device_class is set. [#855](https://github.com/XiaoMi/ha_xiaomi_home/pull/855)
### Fixed
- Fix xiaomi.aircondition.m9 humidity-range unit. [#878](https://github.com/XiaoMi/ha_xiaomi_home/pull/878)
- Fix MIoT-Spec-V2 conflicts of xiaomi.fan.p5 and mike.bhf_light.2. [#866](https://github.com/XiaoMi/ha_xiaomi_home/pull/866)
## v0.2.1 ## v0.2.1
### Added ### Added
- Add the preset mode for the thermostat. [#833](https://github.com/XiaoMi/ha_xiaomi_home/pull/833) - Add the preset mode for the thermostat. [#833](https://github.com/XiaoMi/ha_xiaomi_home/pull/833)
### Changed ### Changed
- Change paho-mqtt version to adapt Home Assistant 2025.03. [#839](https://github.com/XiaoMi/ha_xiaomi_home/pull/839) - Change paho-mqtt version to adapt Home Assistant 2025.03. [#839](https://github.com/XiaoMi/ha_xiaomi_home/pull/839)
- Revert to use multi_lang.json. [#834](https://github.com/XiaoMi/ha_xiaomi_home/pull/834) - Revert to use multi_lang.json. [#834](https://github.com/XiaoMi/ha_xiaomi_home/pull/834)
### Fixed ### Fixed
- Fix the opening and the closing status of linp.wopener.wd1lb. [#826](https://github.com/XiaoMi/ha_xiaomi_home/pull/826) - Fix the opening and the closing status of linp.wopener.wd1lb. [#826](https://github.com/XiaoMi/ha_xiaomi_home/pull/826)
- Fix the format type of the wind-reverse property. [#810](https://github.com/XiaoMi/ha_xiaomi_home/pull/810) - Fix the format type of the wind-reverse property. [#810](https://github.com/XiaoMi/ha_xiaomi_home/pull/810)

View File

@ -89,4 +89,8 @@ class BinarySensor(MIoTPropertyEntity, BinarySensorEntity):
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""On/Off state. True if the binary sensor is on, False otherwise.""" """On/Off state. True if the binary sensor is on, False otherwise."""
if self.spec.name == 'contact-state':
return self._value is False
elif self.spec.name == 'occupancy-status':
return bool(self._value)
return self._value is True return self._value is True

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."""
@ -225,6 +230,7 @@ class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
self._prop_fan_on = None self._prop_fan_on = None
self._prop_fan_level = None self._prop_fan_level = None
self._fan_mode_map = None self._fan_mode_map = None
self._attr_fan_modes = None
super().__init__(miot_device=miot_device, entity_data=entity_data) super().__init__(miot_device=miot_device, entity_data=entity_data)
# properties # properties
@ -365,6 +371,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 +394,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 +426,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 +456,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 +493,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
@ -605,7 +618,7 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature, class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
FeatureFanMode, FeatureSwingMode): FeatureFanMode, FeatureSwingMode, FeaturePresetMode):
"""Ptc bath heater""" """Ptc bath heater"""
_prop_mode: Optional[MIoTSpecProperty] _prop_mode: Optional[MIoTSpecProperty]
_hvac_mode_map: Optional[dict[int, HVACMode]] _hvac_mode_map: Optional[dict[int, HVACMode]]
@ -620,50 +633,43 @@ 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'}:
} and (HVACMode.OFF not in list(
self._hvac_mode_map.values())):
self._hvac_mode_map[item.value] = HVACMode.OFF self._hvac_mode_map[item.value] = HVACMode.OFF
elif item.name in {'auto'}: break
self._hvac_mode_map[item.value] = HVACMode.AUTO if self._hvac_mode_map:
elif item.name in {'ventilate'}: self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
self._hvac_mode_map[item.value] = HVACMode.COOL else:
elif item.name in {'heat', 'quick_heat' _LOGGER.error('no idle mode, %s', self.entity_id)
} and (HVACMode.HEAT not in list( # preset modes
self._hvac_mode_map.values())): self._init_preset_modes('ptc-bath-heater', 'mode')
self._hvac_mode_map[item.value] = HVACMode.HEAT
elif item.name in {'defog'}:
self._hvac_mode_map[item.value] = HVACMode.HEAT_COOL
elif item.name in {'dry'}:
self._hvac_mode_map[item.value] = HVACMode.DRY
elif item.name in {'fan'}:
self._hvac_mode_map[item.value] = HVACMode.FAN_ONLY
self._attr_hvac_modes = list(self._hvac_mode_map.values())
self._prop_mode = prop
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(
prop=self._prop_mode, value=mode_value): prop=self._prop_mode, value=mode_value):
raise RuntimeError( raise RuntimeError(
f'set climate prop.mode failed, {hvac_mode}, {self.entity_id}') f'set ptc-bath-heater {hvac_mode} failed, {self.entity_id}')
@property @property
def hvac_mode(self) -> Optional[HVACMode]: def hvac_mode(self) -> Optional[HVACMode]:
"""The current hvac mode.""" """The current hvac mode."""
return (self.get_map_value(map_=self._hvac_mode_map, if self._prop_mode is None:
key=self.get_prop_value( return None
prop=self._prop_mode)) current_mode = self.get_prop_value(prop=self._prop_mode)
if self._prop_mode else None) if current_mode is None:
return None
mode_value = self.get_map_value(map_=self._hvac_mode_map,
key=current_mode)
return HVACMode.OFF if mode_value == HVACMode.OFF else HVACMode.AUTO
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature, class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
@ -678,6 +684,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')
@ -706,6 +714,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

@ -46,6 +46,7 @@ off Xiaomi or its affiliates' products.
Event entities for Xiaomi Home. Event entities for Xiaomi Home.
""" """
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any from typing import Any
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -57,6 +58,8 @@ from .miot.miot_spec import MIoTSpecEvent
from .miot.miot_device import MIoTDevice, MIoTEventEntity from .miot.miot_device import MIoTDevice, MIoTEventEntity
from .miot.const import DOMAIN from .miot.const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
@ -89,4 +92,5 @@ class Event(MIoTEventEntity, EventEntity):
self, name: str, arguments: dict[str, Any] | None = None self, name: str, arguments: dict[str, Any] | None = None
) -> None: ) -> None:
"""An event is occurred.""" """An event is occurred."""
_LOGGER.debug('%s, attributes: %s', name, str(arguments))
self._trigger_event(event_type=name, event_attributes=arguments) self._trigger_event(event_type=name, event_attributes=arguments)

View File

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

View File

@ -591,13 +591,8 @@ class MIoTDevice:
# Priority: spec_modify.unit > unit_convert > specv2entity.unit # Priority: spec_modify.unit > unit_convert > specv2entity.unit
miot_prop.external_unit = SPEC_PROP_TRANS_MAP['properties'][ miot_prop.external_unit = SPEC_PROP_TRANS_MAP['properties'][
prop_name]['unit_of_measurement'] prop_name]['unit_of_measurement']
if ( # Priority: default.icon when device_class is set > spec_modify.icon
not miot_prop.icon # > icon_convert
and 'icon' in SPEC_PROP_TRANS_MAP['properties'][prop_name]
):
# Priority: spec_modify.icon > icon_convert > specv2entity.icon
miot_prop.icon = SPEC_PROP_TRANS_MAP['properties'][prop_name][
'icon']
miot_prop.platform = platform miot_prop.platform = platform
return True return True

View File

@ -1215,9 +1215,10 @@ class MipsLocalClient(_MipsClient):
or 'eiid' not in msg or 'eiid' not in msg
# or 'arguments' not in msg # or 'arguments' not in msg
): ):
# self.log_error(f'on_event_msg, recv unknown msg, {payload}') self.log_info('unknown event msg, %s', payload)
return return
if 'arguments' not in msg: if 'arguments' not in msg:
self.log_info('wrong event msg, %s', payload)
msg['arguments'] = [] msg['arguments'] = []
if handler: if handler:
self.log_debug('local, on event_occurred, %s', payload) self.log_debug('local, on event_occurred, %s', payload)

View File

@ -1205,6 +1205,9 @@ class _SpecModify:
if isinstance(self._selected, str): if isinstance(self._selected, str):
return await self.set_spec_async(urn=self._selected) return await self.set_spec_async(urn=self._selected)
def get_prop_name(self, siid: int, piid: int) -> Optional[str]:
return self.__get_prop_item(siid=siid, piid=piid, key='name')
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')
@ -1518,6 +1521,10 @@ class MIoTSpecParser:
siid=service['iid'], piid=property_['iid']) siid=service['iid'], piid=property_['iid'])
if custom_range: if custom_range:
spec_prop.value_range = custom_range spec_prop.value_range = custom_range
custom_name = self._spec_modify.get_prop_name(
siid=service['iid'], piid=property_['iid'])
if custom_name:
spec_prop.name = custom_name
# Parse service event # Parse service event
for event in service.get('events', []): for event in service.get('events', []):
if ( if (

View File

@ -168,5 +168,20 @@
"service:016:action:001": "中键确认", "service:016:action:001": "中键确认",
"service:017:action:001": "右键确认" "service:017:action:001": "右键确认"
} }
},
"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10": {
"en": {
"service:003:property:001:valuelist:000": "Idle",
"service:003:property:001:valuelist:001": "Dry"
}
},
"urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1": {
"en": {
"service:002:property:001": "Soil Moisture"
},
"zh-Hans": {
"service:002:property:001": "土壤湿度",
"service:002:property:003": "光照强度"
}
} }
} }

View File

@ -14,12 +14,21 @@ urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
- notify - notify
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:2: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1 urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:2: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:3: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1 urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:3: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1: urn:miot-spec-v2:device:gateway:0000A019:lumi-mcn001:1: # lumi.gateway.mcn001
prop.5.1: prop.2.1: # access-mode
name: power-consumption access:
expr: round(src_value/1000, 3) - read
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1 - notify
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1 prop.2.2: # ip-address
icon: mdi:ip
prop.2.3: # wifi-ssid
access:
- read
- notify
prop.2.5: # access-mode
access:
- read
- notify
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1:
prop.2.2: prop.2.2:
name: power-consumption name: power-consumption
@ -33,6 +42,23 @@ urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816:
prop.3.1: prop.3.1:
name: electric-power name: electric-power
expr: round(src_value/100, 2) expr: round(src_value/100, 2)
urn:miot-spec-v2:device:outlet:0000A002:qmi-psv3:1:0000C816: # qmi.plug.psv3
prop.3.3: # voltage
unit: mV
prop.3.4: # electric-current
unit: mA
urn:miot-spec-v2:device:motion-sensor:0000A014:lumi-acn001:1: # lumi.motion.acn001
prop.3.2: # voltage
access:
- read
- notify
unit: mV
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:izq-24:2:0000C824: # izq.sensor_occupy.24
prop.2.6: # distance
unit: cm
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:linp-hb01:2:0000C824: # linp.sensor_occupy.hb01
prop.3.3: # body-distance
unit: m
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1: urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
prop.2.1: prop.2.1:
name: download-speed name: download-speed
@ -58,3 +84,101 @@ urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
description: medium description: medium
- value: 255 - value: 255
description: high description: high
urn:miot-spec-v2:device:bath-heater:0000A028:mike-2:1:
prop.3.1:
name: mode-a
prop.3.11:
name: mode-b
prop.3.12:
name: mode-c
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
prop.2.2:
name: fan-level-a
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6:
prop.10.6:
unit: none
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: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: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: '1'
- value: 2
description: '2'
- value: 3
description: '3'
- value: 4
description: '4'
- value: 5
description: '5'
- value: 6
description: '6'
- value: 7
description: '7'
- value: 8
description: '8'
- value: 9
description: '9'
- value: 10
description: '10'
- value: 11
description: '11'
- value: 12
description: '12'
- value: 13
description: '13'
- value: 14
description: '14'
- value: 15
description: '15'
- value: 16
description: '16'
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3:
prop.5.1:
expr: round(src_value*6/1000000, 3)
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2:
prop.2.3:
expr: round(src_value/10, 1)
prop.2.4:
unit: mA
prop.3.2:
expr: round(src_value/10, 1)
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2
urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1:1:
prop.2.1:
name: soil-moisture
icon: mdi:watering-can
prop.2.2:
name: soil-ec
icon: mdi:sprout-outline
unit: μS/cm
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-s1:1:
prop.2.5:
name: voc-density
urn:miot-spec-v2:device:water-purifier:0000A013:roswan-lte01:1:0000D05A:
prop.4.1:
unit: ppm
prop.4.2:
unit: ppm
urn:miot-spec-v2:device:water-purifier:0000A013:yunmi-s20:1: # yunmi.waterpuri.s20
prop.4.1: # tds-in
unit: ppm
prop.4.2: # tds-out
unit: ppm
urn:miot-spec-v2:device:relay:0000A03D:lumi-c2acn01:1:
prop.4.1:
unit: kWh
urn:miot-spec-v2:device:bath-heater:0000A028:xiaomi-s1:1:
prop.4.4:
name: fan-level-ventilation

View File

@ -48,6 +48,7 @@ Conversion rules of MIoT-Spec-V2 instance to Home Assistant entity.
from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.sensor import SensorStateClass from homeassistant.components.sensor import SensorStateClass
from homeassistant.components.event import EventDeviceClass from homeassistant.components.event import EventDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
@ -138,7 +139,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'optional': { 'optional': {
'properties': {'mode', 'target-humidity'} 'properties': {'mode', 'target-humidity'}
} }
}, }
}, },
'optional': { 'optional': {
'environment': { 'environment': {
@ -164,8 +165,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'continue-sweep', 'continue-sweep',
'stop-and-gocharge' 'stop-and-gocharge'
} }
}, }
} }
}, },
'optional': { 'optional': {
@ -178,9 +178,9 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'required': { 'required': {
'properties': { 'properties': {
'battery-level': {'read'} 'battery-level': {'read'}
}, }
} }
}, }
}, },
'entity': 'vacuum' 'entity': 'vacuum'
}, },
@ -196,7 +196,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
}, },
'optional': { 'optional': {
'properties': {'target-humidity'} 'properties': {'target-humidity'}
}, }
} }
}, },
'optional': { 'optional': {
@ -237,7 +237,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'properties': { 'properties': {
'target-temperature', 'mode', 'fan-level', 'target-temperature', 'mode', 'fan-level',
'temperature'} 'temperature'}
}, }
} }
}, },
'optional': { 'optional': {
@ -246,7 +246,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'optional': { 'optional': {
'properties': {'temperature', 'relative-humidity'} 'properties': {'temperature', 'relative-humidity'}
} }
}, }
}, },
'entity': 'thermostat' 'entity': 'thermostat'
}, },
@ -260,7 +260,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
}, },
'optional': { 'optional': {
'properties': {'target-temperature', 'heat-level'} 'properties': {'target-temperature', 'heat-level'}
}, }
} }
}, },
'optional': { 'optional': {
@ -269,20 +269,21 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'optional': { 'optional': {
'properties': {'temperature', 'relative-humidity'} 'properties': {'temperature', 'relative-humidity'}
} }
}, }
}, },
'entity': 'heater' 'entity': 'heater'
}, },
'bath-heater': { 'bath-heater': {
'required': { 'required': {
'ptc-bath-heater': { 'ptc-bath-heater': {
'required': {}, 'required': {
'optional': {
'properties': { 'properties': {
'target-temperature', 'heat-level', 'mode':{'read', 'write'}
'temperature', 'mode'
} }
}, },
'optional': {
'properties': {'target-temperature', 'temperature'}
}
} }
}, },
'optional': { 'optional': {
@ -292,7 +293,13 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'properties': { 'properties': {
'on', 'fan-level', 'horizontal-swing', 'vertical-swing' 'on', 'fan-level', 'horizontal-swing', 'vertical-swing'
} }
}, }
},
'environment': {
'required': {},
'optional': {
'properties': {'temperature'}
}
} }
}, },
'entity': 'bath-heater', 'entity': 'bath-heater',
@ -308,7 +315,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
}, },
'optional': { 'optional': {
'properties': {'mode', 'temperature'} 'properties': {'mode', 'temperature'}
}, }
} }
}, },
'optional': {}, 'optional': {},
@ -381,6 +388,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
}, },
'fan-control': 'fan', 'fan-control': 'fan',
'ceiling-fan': 'fan', 'ceiling-fan': 'fan',
'air-fresh': 'fan',
'water-heater': { 'water-heater': {
'required': { 'required': {
'properties': { 'properties': {
@ -400,14 +408,27 @@ SPEC_SERVICE_TRANS_MAP: dict = {
}, },
'optional': { 'optional': {
'properties': { 'properties': {
'motor-control', 'status', 'current-position', 'target-position' 'status', 'current-position', 'target-position'
} }
}, },
'entity': 'cover' 'entity': 'cover'
}, },
'window-opener': 'curtain', 'window-opener': 'curtain',
'motor-controller': 'curtain', 'motor-controller': 'curtain',
'airer': 'curtain' 'airer': 'curtain',
'air-conditioner': {
'required': {
'properties': {
'on': {'read', 'write'},
'mode': {'read', 'write'},
'target-temperature': {'read', 'write'}
}
},
'optional': {
'properties': {'target-humidity'}
},
'entity': 'air-conditioner'
}
} }
"""SPEC_PROP_TRANS_MAP """SPEC_PROP_TRANS_MAP
@ -434,12 +455,28 @@ SPEC_PROP_TRANS_MAP: dict = {
'format': {'int', 'float'}, 'format': {'int', 'float'},
'access': {'read'} 'access': {'read'}
}, },
'binary_sensor': {
'format': {'bool', 'int'},
'access': {'read'}
},
'switch': { 'switch': {
'format': {'bool'}, 'format': {'bool'},
'access': {'read', 'write'} 'access': {'read', 'write'}
} }
}, },
'properties': { 'properties': {
'submersion-state': {
'device_class': BinarySensorDeviceClass.MOISTURE,
'entity': 'binary_sensor'
},
'contact-state': {
'device_class': BinarySensorDeviceClass.DOOR,
'entity': 'binary_sensor'
},
'occupancy-status': {
'device_class': BinarySensorDeviceClass.OCCUPANCY,
'entity': 'binary_sensor',
},
'temperature': { 'temperature': {
'device_class': SensorDeviceClass.TEMPERATURE, 'device_class': SensorDeviceClass.TEMPERATURE,
'entity': 'sensor', 'entity': 'sensor',
@ -486,7 +523,11 @@ SPEC_PROP_TRANS_MAP: dict = {
'entity': 'sensor', 'entity': 'sensor',
'state_class': SensorStateClass.MEASUREMENT 'state_class': SensorStateClass.MEASUREMENT
}, },
'voc-density': 'tvoc-density', 'voc-density': {
'device_class': SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
'entity': 'sensor',
'state_class': SensorStateClass.MEASUREMENT
},
'battery-level': { 'battery-level': {
'device_class': SensorDeviceClass.BATTERY, 'device_class': SensorDeviceClass.BATTERY,
'entity': 'sensor', 'entity': 'sensor',
@ -540,12 +581,6 @@ SPEC_PROP_TRANS_MAP: dict = {
'entity': 'sensor', 'entity': 'sensor',
'state_class': SensorStateClass.MEASUREMENT, 'state_class': SensorStateClass.MEASUREMENT,
'unit_of_measurement': UnitOfPower.WATT 'unit_of_measurement': UnitOfPower.WATT
},
'total-battery': {
'device_class': SensorDeviceClass.ENERGY,
'entity': 'sensor',
'state_class': SensorStateClass.TOTAL_INCREASING,
'unit_of_measurement': UnitOfEnergy.KILO_WATT_HOUR
} }
} }
} }

View File

@ -88,7 +88,7 @@ class Number(MIoTPropertyEntity, NumberEntity):
if self.spec.external_unit: if self.spec.external_unit:
self._attr_native_unit_of_measurement = self.spec.external_unit self._attr_native_unit_of_measurement = self.spec.external_unit
# Set icon # Set icon
if self.spec.icon: if self.spec.icon and not self.device_class:
self._attr_icon = self.spec.icon self._attr_icon = self.spec.icon
# Set value range # Set value range
if self._value_range: if self._value_range:

View File

@ -110,13 +110,13 @@ 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}: if spec.format_ in {int, float} and spec.expr is None:
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:
self._attr_state_class = spec.state_class self._attr_state_class = spec.state_class
# Set icon # Set icon
if spec.icon: if spec.icon and not self.device_class:
self._attr_icon = spec.icon self._attr_icon = spec.icon
@property @property