mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-04-07 02:02:47 +08:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cb34b6ce46 | ||
|
d0a7940c59 | ||
|
899d616da4 | ||
|
c6be6be1ec | ||
|
77b0a4531b | ||
|
7d9250914c | ||
|
a09289ef90 | ||
|
b0428dc95a | ||
|
19ed04f2f5 | ||
|
e174a73f52 | ||
|
a1aa1c024f | ||
|
372e635681 | ||
|
3759aa9a1b | ||
|
60d054cf19 | ||
|
6680d9e8cb | ||
|
0ef8cb6370 | ||
|
8f0a69c611 | ||
|
8be0fa5d61 | ||
|
07cb4ed193 |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Bug Report / 报告问题
|
name: Bug Report / 报告问题
|
||||||
description: Create a report to help us improve. / 报告问题以帮助我们改进
|
description: Create a report to help us improve. / 报告问题以帮助我们改进
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the Bug / 描述问题
|
label: Describe the Bug / 描述问题
|
||||||
description: |
|
description: |
|
||||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -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)
|
||||||
|
@ -156,7 +156,8 @@ async def async_setup_entry(
|
|||||||
device.entity_list[platform].remove(entity)
|
device.entity_list[platform].remove(entity)
|
||||||
entity_id = device.gen_service_entity_id(
|
entity_id = device.gen_service_entity_id(
|
||||||
ha_domain=platform,
|
ha_domain=platform,
|
||||||
siid=entity.spec.iid) # type: ignore
|
siid=entity.spec.iid,
|
||||||
|
description=entity.spec.description)
|
||||||
if er.async_get(entity_id_or_uuid=entity_id):
|
if er.async_get(entity_id_or_uuid=entity_id):
|
||||||
er.async_remove(entity_id=entity_id)
|
er.async_remove(entity_id=entity_id)
|
||||||
if platform in device.prop_list:
|
if platform in device.prop_list:
|
||||||
|
@ -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
|
||||||
|
@ -51,10 +51,11 @@ 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,
|
||||||
ATTR_TEMPERATURE, HVACMode, ClimateEntity, ClimateEntityFeature)
|
ATTR_TEMPERATURE, HVACMode, HVACAction, ClimateEntity, ClimateEntityFeature)
|
||||||
|
|
||||||
from .miot.const import DOMAIN
|
from .miot.const import DOMAIN
|
||||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||||
@ -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._attr_supported_features |= (
|
|
||||||
ClimateEntityFeature.TURN_OFF)
|
|
||||||
self._prop_on = prop
|
self._prop_on = prop
|
||||||
|
break
|
||||||
|
|
||||||
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')
|
||||||
|
|
||||||
@ -462,6 +473,13 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
|||||||
return (HVACMode.HEAT if self.get_prop_value(
|
return (HVACMode.HEAT if self.get_prop_value(
|
||||||
prop=self._prop_on) else HVACMode.OFF)
|
prop=self._prop_on) else HVACMode.OFF)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> Optional[HVACAction]:
|
||||||
|
"""The current hvac action."""
|
||||||
|
if self.hvac_mode == HVACMode.HEAT:
|
||||||
|
return HVACAction.HEATING
|
||||||
|
return HVACAction.OFF
|
||||||
|
|
||||||
|
|
||||||
class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||||
FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
|
FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
|
||||||
@ -482,10 +500,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
|
||||||
@ -549,6 +569,23 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
|||||||
prop=self._prop_mode))
|
prop=self._prop_mode))
|
||||||
if self._prop_mode else None)
|
if self._prop_mode else None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> Optional[HVACAction]:
|
||||||
|
"""The current hvac action."""
|
||||||
|
if self.hvac_mode is None:
|
||||||
|
return None
|
||||||
|
if self.hvac_mode == HVACMode.OFF:
|
||||||
|
return HVACAction.OFF
|
||||||
|
if self.hvac_mode == HVACMode.FAN_ONLY:
|
||||||
|
return HVACAction.FAN
|
||||||
|
if self.hvac_mode == HVACMode.COOL:
|
||||||
|
return HVACAction.COOLING
|
||||||
|
if self.hvac_mode == HVACMode.HEAT:
|
||||||
|
return HVACAction.HEATING
|
||||||
|
if self.hvac_mode == HVACMode.DRY:
|
||||||
|
return HVACAction.DRYING
|
||||||
|
return HVACAction.IDLE
|
||||||
|
|
||||||
def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None:
|
def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None:
|
||||||
del prop
|
del prop
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
@ -605,7 +642,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 +657,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 +708,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 +738,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')
|
||||||
|
|
||||||
@ -720,3 +754,10 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
|||||||
"""The current hvac mode."""
|
"""The current hvac mode."""
|
||||||
return (HVACMode.HEAT if self.get_prop_value(
|
return (HVACMode.HEAT if self.get_prop_value(
|
||||||
prop=self._prop_on) else HVACMode.OFF)
|
prop=self._prop_on) else HVACMode.OFF)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> Optional[HVACAction]:
|
||||||
|
"""The current hvac action."""
|
||||||
|
if self.hvac_mode == HVACMode.OFF:
|
||||||
|
return HVACAction.OFF
|
||||||
|
return HVACAction.HEATING
|
||||||
|
@ -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)
|
||||||
|
@ -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."
|
||||||
]
|
]
|
||||||
|
@ -345,10 +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(self, ha_domain: str, siid: int) -> str:
|
def gen_service_entity_id(self, ha_domain: str, siid: int,
|
||||||
|
description: str) -> str:
|
||||||
return (
|
return (
|
||||||
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]}_s_{siid}')
|
f'{self._model_strs[-1][:20]}_s_{siid}_{description}')
|
||||||
|
|
||||||
def gen_prop_entity_id(
|
def gen_prop_entity_id(
|
||||||
self, ha_domain: str, spec_name: str, siid: int, piid: int
|
self, ha_domain: str, spec_name: str, siid: int, piid: int
|
||||||
@ -591,13 +592,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
|
||||||
|
|
||||||
@ -899,7 +895,8 @@ class MIoTServiceEntity(Entity):
|
|||||||
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
||||||
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
||||||
self.entity_id = miot_device.gen_service_entity_id(
|
self.entity_id = miot_device.gen_service_entity_id(
|
||||||
DOMAIN, siid=self.entity_data.spec.iid)
|
DOMAIN, siid=self.entity_data.spec.iid,
|
||||||
|
description=self.entity_data.spec.description)
|
||||||
self._attr_name = (
|
self._attr_name = (
|
||||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||||
f'{self.entity_data.spec.description_trans}')
|
f'{self.entity_data.spec.description_trans}')
|
||||||
|
@ -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)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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": "光照强度"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
22
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
22
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:1": [
|
||||||
|
{
|
||||||
|
"iid": 3,
|
||||||
|
"type": "urn:miot-spec-v2:service:light:00007802:hyd-lyjpro:1",
|
||||||
|
"description": "Moon Light",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"iid": 2,
|
||||||
|
"type": "urn:miot-spec-v2:property:on:00000006:hyd-lyjpro:1",
|
||||||
|
"description": "Switch Status",
|
||||||
|
"format": "bool",
|
||||||
|
"access": [
|
||||||
|
"read",
|
||||||
|
"write",
|
||||||
|
"notify"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -5,6 +5,9 @@ urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
|||||||
- 15.*
|
- 15.*
|
||||||
services:
|
services:
|
||||||
- '10'
|
- '10'
|
||||||
|
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:
|
||||||
|
properties:
|
||||||
|
- '3.2'
|
||||||
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
||||||
properties:
|
properties:
|
||||||
- '5.1'
|
- '5.1'
|
||||||
|
@ -1,3 +1,70 @@
|
|||||||
|
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:air-conditioner:0000A004:xiaomi-m9:6:
|
||||||
|
prop.10.6:
|
||||||
|
unit: none
|
||||||
|
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-s1:1:
|
||||||
|
prop.2.5:
|
||||||
|
name: voc-density
|
||||||
|
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:1:
|
||||||
|
prop.2.3:
|
||||||
|
name: current-position-a
|
||||||
|
prop.2.8:
|
||||||
|
name: target-position-a
|
||||||
|
prop.2.9:
|
||||||
|
name: target-position-b
|
||||||
|
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
||||||
|
prop.2.3:
|
||||||
|
value-range:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 1
|
||||||
|
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
|
||||||
|
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: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:bath-heater:0000A028:opple-acmoto:1:
|
||||||
|
prop.5.2:
|
||||||
|
value-list:
|
||||||
|
- value: 1
|
||||||
|
description: low
|
||||||
|
- value: 128
|
||||||
|
description: medium
|
||||||
|
- value: 255
|
||||||
|
description: high
|
||||||
|
urn:miot-spec-v2:device:bath-heater:0000A028:xiaomi-s1:1:
|
||||||
|
prop.4.4:
|
||||||
|
name: fan-level-ventilation
|
||||||
|
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
||||||
|
prop.2.2:
|
||||||
|
name: fan-level-a
|
||||||
|
urn:miot-spec-v2:device:gateway:0000A019:lumi-mcn001:1:
|
||||||
|
prop.2.1:
|
||||||
|
access:
|
||||||
|
- read
|
||||||
|
- notify
|
||||||
|
prop.2.2:
|
||||||
|
icon: mdi:ip
|
||||||
|
prop.2.3:
|
||||||
|
access:
|
||||||
|
- read
|
||||||
|
- notify
|
||||||
|
prop.2.5:
|
||||||
|
access:
|
||||||
|
- read
|
||||||
|
- notify
|
||||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
||||||
prop.2.1:
|
prop.2.1:
|
||||||
name: access-mode
|
name: access-mode
|
||||||
@ -14,25 +81,63 @@ 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:light:0000A001:shhf-sfla12:1:
|
||||||
|
prop.8.11:
|
||||||
|
name: on-a
|
||||||
|
urn:miot-spec-v2:device:motion-sensor:0000A014:lumi-acn001:1:
|
||||||
|
prop.3.2:
|
||||||
|
access:
|
||||||
|
- read
|
||||||
|
- notify
|
||||||
|
unit: mV
|
||||||
|
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:izq-24:2:0000C824:
|
||||||
|
prop.2.6:
|
||||||
|
unit: cm
|
||||||
|
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:linp-hb01:2:0000C824:
|
||||||
|
prop.3.3:
|
||||||
|
unit: m
|
||||||
|
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:chuangmi-212a01:3:
|
||||||
prop.5.1:
|
prop.5.1:
|
||||||
name: power-consumption
|
expr: round(src_value*6/1000000, 3)
|
||||||
expr: round(src_value/1000, 3)
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: 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:1
|
|
||||||
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
|
||||||
expr: round(src_value/1000, 3)
|
expr: round(src_value/1000, 3)
|
||||||
|
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: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-v3:1:
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1:
|
||||||
prop.11.1:
|
prop.11.1:
|
||||||
name: power-consumption
|
name: power-consumption
|
||||||
expr: round(src_value/100, 2)
|
expr: round(src_value/100, 2)
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:qmi-psv3:1:0000C816:
|
||||||
|
prop.3.3:
|
||||||
|
unit: mV
|
||||||
|
prop.3.4:
|
||||||
|
unit: mA
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816:
|
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: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:relay:0000A03D:lumi-c2acn01:1:
|
||||||
|
prop.4.1:
|
||||||
|
unit: kWh
|
||||||
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
|
||||||
@ -42,19 +147,48 @@ urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
|||||||
name: upload-speed
|
name: upload-speed
|
||||||
icon: mdi:upload
|
icon: mdi:upload
|
||||||
unit: B/s
|
unit: B/s
|
||||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
|
||||||
prop.2.3:
|
prop.2.3:
|
||||||
value-range:
|
|
||||||
- 0
|
|
||||||
- 1
|
|
||||||
- 1
|
|
||||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
|
|
||||||
urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
|
|
||||||
prop.5.2:
|
|
||||||
value-list:
|
value-list:
|
||||||
- value: 1
|
- value: 1
|
||||||
description: low
|
description: '1'
|
||||||
- value: 128
|
- value: 2
|
||||||
description: medium
|
description: '2'
|
||||||
- value: 255
|
- value: 3
|
||||||
description: high
|
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: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:
|
||||||
|
prop.4.1:
|
||||||
|
unit: ppm
|
||||||
|
prop.4.2:
|
||||||
|
unit: ppm
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -15,14 +15,13 @@ TRANS_RELATIVE_PATH: str = path.join(
|
|||||||
MIOT_I18N_RELATIVE_PATH: str = path.join(
|
MIOT_I18N_RELATIVE_PATH: str = path.join(
|
||||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
||||||
SPEC_BOOL_TRANS_FILE = path.join(
|
SPEC_BOOL_TRANS_FILE = path.join(
|
||||||
ROOT_PATH,
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||||
'../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
|
||||||
SPEC_FILTER_FILE = path.join(
|
SPEC_FILTER_FILE = path.join(
|
||||||
ROOT_PATH,
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
||||||
'../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
SPEC_ADD_FILE = path.join(
|
||||||
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_add.json')
|
||||||
SPEC_MODIFY_FILE = path.join(
|
SPEC_MODIFY_FILE = path.join(
|
||||||
ROOT_PATH,
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
||||||
'../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
|
||||||
|
|
||||||
|
|
||||||
def load_json_file(file_path: str) -> Optional[dict]:
|
def load_json_file(file_path: str) -> Optional[dict]:
|
||||||
@ -30,7 +29,7 @@ def load_json_file(file_path: str) -> Optional[dict]:
|
|||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
return json.load(file)
|
return json.load(file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
_LOGGER.info('%s is not found.', file_path,)
|
_LOGGER.info('%s is not found.', file_path)
|
||||||
return None
|
return None
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
_LOGGER.info('%s is not a valid JSON file.', file_path)
|
_LOGGER.info('%s is not a valid JSON file.', file_path)
|
||||||
@ -56,9 +55,12 @@ def load_yaml_file(file_path: str) -> Optional[dict]:
|
|||||||
|
|
||||||
def save_yaml_file(file_path: str, data: dict) -> None:
|
def save_yaml_file(file_path: str, data: dict) -> None:
|
||||||
with open(file_path, 'w', encoding='utf-8') as file:
|
with open(file_path, 'w', encoding='utf-8') as file:
|
||||||
yaml.safe_dump(
|
yaml.safe_dump(data,
|
||||||
data, file, default_flow_style=False,
|
file,
|
||||||
allow_unicode=True, indent=2, sort_keys=False)
|
default_flow_style=False,
|
||||||
|
allow_unicode=True,
|
||||||
|
indent=2,
|
||||||
|
sort_keys=False)
|
||||||
|
|
||||||
|
|
||||||
def dict_str_str(d: dict) -> bool:
|
def dict_str_str(d: dict) -> bool:
|
||||||
@ -132,9 +134,108 @@ def bool_trans(d: dict) -> bool:
|
|||||||
for key, trans in d['translate'].items():
|
for key, trans in d['translate'].items():
|
||||||
trans_keys: set[str] = set(trans.keys())
|
trans_keys: set[str] = set(trans.keys())
|
||||||
if set(trans.keys()) != default_keys:
|
if set(trans.keys()) != default_keys:
|
||||||
_LOGGER.info(
|
_LOGGER.info('bool trans inconsistent, %s, %s, %s', key,
|
||||||
'bool trans inconsistent, %s, %s, %s',
|
default_keys, trans_keys)
|
||||||
key, default_keys, trans_keys)
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def spec_add(data: dict) -> bool:
|
||||||
|
"""dict[str, list[dict[str, int| str | list]]]"""
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return False
|
||||||
|
for urn, content in data.items():
|
||||||
|
if not isinstance(urn, str) or not isinstance(content, (list, str)):
|
||||||
|
return False
|
||||||
|
if isinstance(content, str):
|
||||||
|
continue
|
||||||
|
for service in content:
|
||||||
|
if ('iid' not in service) or ('type' not in service) or (
|
||||||
|
'description'
|
||||||
|
not in service) or (('properties' not in service) and
|
||||||
|
('actions' not in service) and
|
||||||
|
('events' not in service)):
|
||||||
|
return False
|
||||||
|
type_strs: list[str] = service['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
if 'properties' in service:
|
||||||
|
if not isinstance(service['properties'], list):
|
||||||
|
return False
|
||||||
|
for prop in service['properties']:
|
||||||
|
if ('iid' not in prop) or ('type' not in prop) or (
|
||||||
|
'description' not in prop) or (
|
||||||
|
'format' not in prop) or ('access' not in prop):
|
||||||
|
return False
|
||||||
|
if not isinstance(prop['iid'], int) or not isinstance(
|
||||||
|
prop['type'], str) or not isinstance(
|
||||||
|
prop['description'], str) or not isinstance(
|
||||||
|
prop['format'], str) or not isinstance(
|
||||||
|
prop['access'], list):
|
||||||
|
return False
|
||||||
|
type_strs = prop['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
for access in prop['access']:
|
||||||
|
if access not in ['read', 'write', 'notify']:
|
||||||
|
return False
|
||||||
|
if 'value-range' in prop:
|
||||||
|
if not isinstance(prop['value-range'], list):
|
||||||
|
return False
|
||||||
|
for value in prop['value-range']:
|
||||||
|
if not isinstance(value, (int, float)):
|
||||||
|
return False
|
||||||
|
if 'value-list' in prop:
|
||||||
|
if not isinstance(prop['value-list'], list):
|
||||||
|
return False
|
||||||
|
for item in prop['value-list']:
|
||||||
|
if 'value' not in item or 'description' not in item:
|
||||||
|
return False
|
||||||
|
if not isinstance(item['value'],
|
||||||
|
int) or not isinstance(
|
||||||
|
item['description'], str):
|
||||||
|
return False
|
||||||
|
if 'actions' in service:
|
||||||
|
if not isinstance(service['actions'], list):
|
||||||
|
return False
|
||||||
|
for action in service['actions']:
|
||||||
|
if ('iid' not in action) or ('type' not in action) or (
|
||||||
|
'description' not in action) or (
|
||||||
|
'in' not in action) or ('out' not in action):
|
||||||
|
return False
|
||||||
|
if not isinstance(action['iid'], int) or not isinstance(
|
||||||
|
action['type'], str) or not isinstance(
|
||||||
|
action['description'], str) or not isinstance(
|
||||||
|
action['in'], list) or not isinstance(
|
||||||
|
action['out'], list):
|
||||||
|
return False
|
||||||
|
type_strs = action['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
for param in action['in']:
|
||||||
|
if not isinstance(param, int):
|
||||||
|
return False
|
||||||
|
for param in action['out']:
|
||||||
|
if not isinstance(param, int):
|
||||||
|
return False
|
||||||
|
if 'events' in service:
|
||||||
|
if not isinstance(service['events'], list):
|
||||||
|
return False
|
||||||
|
for event in service['events']:
|
||||||
|
if ('iid' not in event) or ('type' not in event) or (
|
||||||
|
'description' not in event) or ('arguments'
|
||||||
|
not in event):
|
||||||
|
return False
|
||||||
|
if not isinstance(event['iid'], int) or not isinstance(
|
||||||
|
event['type'], str) or not isinstance(
|
||||||
|
event['description'], str) or not isinstance(
|
||||||
|
event['arguments'], list):
|
||||||
|
return False
|
||||||
|
type_strs = event['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
for param in event['arguments']:
|
||||||
|
if not isinstance(param, int):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -159,25 +260,22 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
|
|||||||
_LOGGER.info('invalid type')
|
_LOGGER.info('invalid type')
|
||||||
return False
|
return False
|
||||||
if dict1.keys() != dict2.keys():
|
if dict1.keys() != dict2.keys():
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, %s, %s', dict1.keys(),
|
||||||
'inconsistent key values, %s, %s', dict1.keys(), dict2.keys())
|
dict2.keys())
|
||||||
return False
|
return False
|
||||||
for key in dict1:
|
for key in dict1:
|
||||||
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
||||||
if not compare_dict_structure(dict1[key], dict2[key]):
|
if not compare_dict_structure(dict1[key], dict2[key]):
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, dict, %s', key)
|
||||||
'inconsistent key values, dict, %s', key)
|
|
||||||
return False
|
return False
|
||||||
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
|
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
|
||||||
if not all(
|
if not all(
|
||||||
isinstance(i, type(j))
|
isinstance(i, type(j))
|
||||||
for i, j in zip(dict1[key], dict2[key])):
|
for i, j in zip(dict1[key], dict2[key])):
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, list, %s', key)
|
||||||
'inconsistent key values, list, %s', key)
|
|
||||||
return False
|
return False
|
||||||
elif not isinstance(dict1[key], type(dict2[key])):
|
elif not isinstance(dict1[key], type(dict2[key])):
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, type, %s', key)
|
||||||
'inconsistent key values, type, %s', key)
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -200,6 +298,12 @@ def sort_spec_filter(file_path: str):
|
|||||||
return filter_data
|
return filter_data
|
||||||
|
|
||||||
|
|
||||||
|
def sort_spec_add(file_path: str):
|
||||||
|
filter_data = load_json_file(file_path=file_path)
|
||||||
|
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||||
|
return dict(sorted(filter_data.items()))
|
||||||
|
|
||||||
|
|
||||||
def sort_spec_modify(file_path: str):
|
def sort_spec_modify(file_path: str):
|
||||||
filter_data = load_yaml_file(file_path=file_path)
|
filter_data = load_yaml_file(file_path=file_path)
|
||||||
assert isinstance(filter_data, dict), f'{file_path} format error'
|
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||||
@ -222,6 +326,14 @@ def test_spec_filter():
|
|||||||
assert spec_filter(data), f'{SPEC_FILTER_FILE} format error'
|
assert spec_filter(data), f'{SPEC_FILTER_FILE} format error'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.github
|
||||||
|
def test_spec_add():
|
||||||
|
data = load_json_file(SPEC_ADD_FILE)
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert data, f'load {SPEC_ADD_FILE} failed'
|
||||||
|
assert spec_add(data), f'{SPEC_ADD_FILE} format error'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.github
|
@pytest.mark.github
|
||||||
def test_spec_modify():
|
def test_spec_modify():
|
||||||
data = load_yaml_file(SPEC_MODIFY_FILE)
|
data = load_yaml_file(SPEC_MODIFY_FILE)
|
||||||
@ -255,7 +367,8 @@ def test_miot_lang_integrity():
|
|||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
from miot.const import INTEGRATION_LANGUAGES
|
from miot.const import INTEGRATION_LANGUAGES
|
||||||
integration_lang_list: list[str] = [
|
integration_lang_list: list[str] = [
|
||||||
f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())]
|
f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())
|
||||||
|
]
|
||||||
translations_names: set[str] = set(listdir(TRANS_RELATIVE_PATH))
|
translations_names: set[str] = set(listdir(TRANS_RELATIVE_PATH))
|
||||||
assert len(translations_names) == len(integration_lang_list)
|
assert len(translations_names) == len(integration_lang_list)
|
||||||
assert translations_names == set(integration_lang_list)
|
assert translations_names == set(integration_lang_list)
|
||||||
@ -271,21 +384,18 @@ def test_miot_lang_integrity():
|
|||||||
default_dict = load_json_file(
|
default_dict = load_json_file(
|
||||||
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
||||||
for name in list(integration_lang_list)[1:]:
|
for name in list(integration_lang_list)[1:]:
|
||||||
compare_dict = load_json_file(
|
compare_dict = load_json_file(path.join(TRANS_RELATIVE_PATH, name))
|
||||||
path.join(TRANS_RELATIVE_PATH, name))
|
|
||||||
if not compare_dict_structure(default_dict, compare_dict):
|
if not compare_dict_structure(default_dict, compare_dict):
|
||||||
_LOGGER.info(
|
_LOGGER.info('compare_dict_structure failed /translations, %s',
|
||||||
'compare_dict_structure failed /translations, %s', name)
|
name)
|
||||||
assert False
|
assert False
|
||||||
# Check i18n files structure
|
# Check i18n files structure
|
||||||
default_dict = load_json_file(
|
default_dict = load_json_file(
|
||||||
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
||||||
for name in list(integration_lang_list)[1:]:
|
for name in list(integration_lang_list)[1:]:
|
||||||
compare_dict = load_json_file(
|
compare_dict = load_json_file(path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||||
path.join(MIOT_I18N_RELATIVE_PATH, name))
|
|
||||||
if not compare_dict_structure(default_dict, compare_dict):
|
if not compare_dict_structure(default_dict, compare_dict):
|
||||||
_LOGGER.info(
|
_LOGGER.info('compare_dict_structure failed /miot/i18n, %s', name)
|
||||||
'compare_dict_structure failed /miot/i18n, %s', name)
|
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
@ -303,12 +413,21 @@ def test_miot_data_sort():
|
|||||||
f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path'
|
f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path'
|
||||||
' and run the following command sorting, ',
|
' and run the following command sorting, ',
|
||||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
assert json.dumps(
|
assert json.dumps(load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
||||||
load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
|
||||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||||
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
||||||
' and run the following command sorting, ',
|
' and run the following command sorting, ',
|
||||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
assert json.dumps(load_json_file(file_path=SPEC_ADD_FILE)) == json.dumps(
|
||||||
|
sort_spec_add(file_path=SPEC_ADD_FILE)), (
|
||||||
|
f'{SPEC_ADD_FILE} not sorted, goto project root path'
|
||||||
|
' and run the following command sorting, ',
|
||||||
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
assert json.dumps(load_yaml_file(file_path=SPEC_MODIFY_FILE)) == json.dumps(
|
||||||
|
sort_spec_modify(file_path=SPEC_MODIFY_FILE)), (
|
||||||
|
f'{SPEC_MODIFY_FILE} not sorted, goto project root path'
|
||||||
|
' and run the following command sorting, ',
|
||||||
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.update
|
@pytest.mark.update
|
||||||
@ -319,6 +438,9 @@ def test_sort_spec_data():
|
|||||||
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
||||||
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
||||||
_LOGGER.info('%s formatted.', SPEC_FILTER_FILE)
|
_LOGGER.info('%s formatted.', SPEC_FILTER_FILE)
|
||||||
|
sort_data = sort_spec_add(file_path=SPEC_ADD_FILE)
|
||||||
|
save_json_file(file_path=SPEC_ADD_FILE, data=sort_data)
|
||||||
|
_LOGGER.info('%s formatted.', SPEC_ADD_FILE)
|
||||||
sort_data = sort_spec_modify(file_path=SPEC_MODIFY_FILE)
|
sort_data = sort_spec_modify(file_path=SPEC_MODIFY_FILE)
|
||||||
save_yaml_file(file_path=SPEC_MODIFY_FILE, data=sort_data)
|
save_yaml_file(file_path=SPEC_MODIFY_FILE, data=sort_data)
|
||||||
_LOGGER.info('%s formatted.', SPEC_MODIFY_FILE)
|
_LOGGER.info('%s formatted.', SPEC_MODIFY_FILE)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user