mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-07-06 19:59:05 +08:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
a4f9c29b6b | |||
62dd32a132 | |||
1bd338639b | |||
6a2534934c | |||
d06a564917 | |||
23cc1130fe | |||
a83ad60b38 | |||
00f24bd3e1 | |||
f384034854 | |||
b0204ad9b7 | |||
b4ece958ac | |||
db77af8a13 | |||
a9f1fc630d | |||
51a27a1e30 | |||
2e0ea642a4 | |||
80d962897a | |||
d17784070d | |||
218c96e5e6 | |||
eacc0d02da | |||
23f0a2d360 | |||
3abccc2491 | |||
7a459de766 | |||
2f619ff51d | |||
cb34b6ce46 | |||
d0a7940c59 | |||
899d616da4 | |||
c6be6be1ec | |||
77b0a4531b | |||
7d9250914c | |||
a09289ef90 | |||
b0428dc95a | |||
19ed04f2f5 | |||
e174a73f52 | |||
a1aa1c024f | |||
372e635681 | |||
3759aa9a1b |
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: |
|
||||||
|
57
CHANGELOG.md
57
CHANGELOG.md
@ -1,4 +1,61 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
## v0.3.2
|
||||||
|
> Xiaomi Home has been added to the Home Assistant Community Store (HACS) as a default since May 8, 2025.
|
||||||
|
### Added
|
||||||
|
- Modify MIoT-Spec-V2 property format by spec_modify.yaml. [#1111](https://github.com/XiaoMi/ha_xiaomi_home/pull/1111)
|
||||||
|
### Changed
|
||||||
|
- Update the instructions of Xiaomi Home integration installation from HACS. [#102](https://github.com/XiaoMi/ha_xiaomi_home/pull/102) [#1088](https://github.com/XiaoMi/ha_xiaomi_home/pull/1088)
|
||||||
|
- Add an alongside switch entity for zimi.waterheater.h03 and xiaomi.waterheater.yms2. [#1115](https://github.com/XiaoMi/ha_xiaomi_home/pull/1115)
|
||||||
|
### Fixed
|
||||||
|
- Fix Chinese encoding in LAN Control. [#1114](https://github.com/XiaoMi/ha_xiaomi_home/pull/1114)
|
||||||
|
- Fix the MIoT-Spec-V2 of lxzn.switch.jcbcsm power consumption, voltage and current, shhf.light.sfla10 fan direction, zhimi.fan.za4 fan-level, zhimi.fan.sa1 fan-level. [#1110](https://github.com/XiaoMi/ha_xiaomi_home/pull/1110)
|
||||||
|
- Revise the Chinese descriptions of loock.lock.t2pv1 door state value-list. [#1110](https://github.com/XiaoMi/ha_xiaomi_home/pull/1110)
|
||||||
|
|
||||||
|
## v0.3.1
|
||||||
|
### Changed
|
||||||
|
- Setting the fan speed level when the fan is off will turning the fan on first. [#1031](https://github.com/XiaoMi/ha_xiaomi_home/pull/1031)
|
||||||
|
### Fixed
|
||||||
|
- Fix update device list error when there is no shared devices. [#1024](https://github.com/XiaoMi/ha_xiaomi_home/pull/1024)
|
||||||
|
- Fix the humidifier get_prop_value error when the property is None. [#1035](https://github.com/XiaoMi/ha_xiaomi_home/pull/1035)
|
||||||
|
- Fix the MIoT-Spec-V2 of zhimi.fan.v3 fan-level, cuco.plug.cp1md voltage and current, zimi.plug.zncz01 electric-power, giot.plug.v8icm power-consumption unit, yunmi.kettle.r3 tds unit, and dmaker.fan.p5 fan-level. [#1037](https://github.com/XiaoMi/ha_xiaomi_home/pull/1037)
|
||||||
|
|
||||||
|
## v0.3.0
|
||||||
|
注意:v0.3.0 变更了部分实体 unique_id 的生成规则,如果勾选 xiaomi_home > 配置 > 更新实体转换规则,会导致部分实体已配置的自动化失效。如果想要避免重新配置大量自动化,可使用这个[补丁](https://github.com/XiaoMi/ha_xiaomi_home/pull/972)。
|
||||||
|
|
||||||
|
CAUTION: v0.3.0 changes the unique_id of some entities. If you check the option `xiaomi_home > CONFIGURE > Update entity conversion rules`, it may cause the automation settings for these entities to fail. To avoid having to reconfigure a large number of automation settings, you can use this [patch](https://github.com/XiaoMi/ha_xiaomi_home/pull/972).
|
||||||
|
### Added
|
||||||
|
- Import the devices in the shared homes and the separated shared devices. [#1021](https://github.com/XiaoMi/ha_xiaomi_home/pull/1021)
|
||||||
|
- Support _attr_hvac_action of the climate entity. [#956](https://github.com/XiaoMi/ha_xiaomi_home/pull/956)
|
||||||
|
- Add custom defined MIoT-Spec-V2 instance via spec_add.json. [#953](https://github.com/XiaoMi/ha_xiaomi_home/pull/953)
|
||||||
|
### Fixed
|
||||||
|
- Ignore 'Event loop is closed' when unsub a closed event loop. [#991](https://github.com/XiaoMi/ha_xiaomi_home/pull/991)
|
||||||
|
- Fix contact-state for linp.magnet.m1 and loock.safe.v1. [#977](https://github.com/XiaoMi/ha_xiaomi_home/pull/977)
|
||||||
|
- Fix the mode initialization error of aupu.bhf_light.s368m. [#955](https://github.com/XiaoMi/ha_xiaomi_home/pull/955)
|
||||||
|
- Fix the MIoT-Spec-V2 of lumi.gateway.mcn001, qmi.plug.psv3, lumi.motion.acn001, izq.sensor_occupy.24, linp.sensor_occupy.hb01 and yunmi.waterpuri.s20. [#949](https://github.com/XiaoMi/ha_xiaomi_home/pull/949)
|
||||||
|
|
||||||
|
## 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
|
## 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 >
|
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 >
|
||||||
|
10
README.md
10
README.md
@ -33,9 +33,11 @@ git checkout v1.0.0
|
|||||||
|
|
||||||
### Method 2: [HACS](https://hacs.xyz/)
|
### Method 2: [HACS](https://hacs.xyz/)
|
||||||
|
|
||||||
HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > Xiaomi Home in New or Available for download section of HACS > DOWNLOAD
|
One-click installation from HACS:
|
||||||
|
|
||||||
> Xiaomi Home has not been added to the HACS store as a default yet. It's coming soon.
|
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)
|
||||||
|
|
||||||
|
Or, HACS > In the search box, type **Xiaomi Home** > Click **Xiaomi Home**, getting into the detail page > DOWNLOAD
|
||||||
|
|
||||||
### Method 3: Manually installation via [Samba](https://github.com/home-assistant/addons/tree/master/samba) / [FTPS](https://github.com/hassio-addons/addon-ftp)
|
### Method 3: Manually installation via [Samba](https://github.com/home-assistant/addons/tree/master/samba) / [FTPS](https://github.com/hassio-addons/addon-ftp)
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ Download and copy `custom_components/xiaomi_home` folder to `config/custom_compo
|
|||||||
|
|
||||||
[Settings > Devices & services > ADD INTEGRATION](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > Search `Xiaomi Home` > NEXT > Click here to login > Sign in with Xiaomi account
|
[Settings > Devices & services > ADD INTEGRATION](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > Search `Xiaomi Home` > NEXT > Click here to login > Sign in with Xiaomi account
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||||
|
|
||||||
### Add MIoT Devices
|
### Add MIoT Devices
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ After a Xiaomi account login and its user configuration are completed, you can c
|
|||||||
|
|
||||||
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > ADD HUB > NEXT > Click here to login > Sign in with Xiaomi account
|
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > ADD HUB > NEXT > Click here to login > Sign in with Xiaomi account
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||||
|
|
||||||
### Update Configurations
|
### Update Configurations
|
||||||
|
|
||||||
|
@ -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):
|
||||||
@ -620,22 +657,17 @@ 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'}:
|
||||||
if (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 (HVACMode.AUTO
|
break
|
||||||
not in list(self._hvac_mode_map.values())):
|
if self._hvac_mode_map:
|
||||||
self._hvac_mode_map[item.value] = HVACMode.AUTO
|
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
|
||||||
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
|
||||||
if HVACMode.OFF in self._attr_hvac_modes:
|
|
||||||
self._prop_mode = prop
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.error('no idle mode, %s', self.entity_id)
|
_LOGGER.error('no idle mode, %s', self.entity_id)
|
||||||
# preset modes
|
# preset modes
|
||||||
@ -643,7 +675,7 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
|||||||
|
|
||||||
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(
|
||||||
@ -656,13 +688,12 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
|||||||
"""The current hvac mode."""
|
"""The current hvac mode."""
|
||||||
if self._prop_mode is None:
|
if self._prop_mode is None:
|
||||||
return None
|
return None
|
||||||
mode_value = self.get_map_value(
|
current_mode = self.get_prop_value(prop=self._prop_mode)
|
||||||
map_=self._hvac_mode_map,
|
if current_mode is None:
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
return None
|
||||||
if mode_value == HVACMode.OFF or mode_value is None:
|
mode_value = self.get_map_value(map_=self._hvac_mode_map,
|
||||||
return mode_value
|
key=current_mode)
|
||||||
return HVACMode.AUTO if (HVACMode.AUTO
|
return HVACMode.OFF if mode_value == HVACMode.OFF else HVACMode.AUTO
|
||||||
in self._attr_hvac_modes) else None
|
|
||||||
|
|
||||||
|
|
||||||
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||||
@ -677,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')
|
||||||
|
|
||||||
@ -705,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')
|
||||||
|
|
||||||
@ -719,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
|
||||||
|
@ -565,8 +565,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
home_list = {}
|
home_list = {}
|
||||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||||
# home list
|
# home list
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
# i18n
|
# i18n
|
||||||
tip_central = ''
|
tip_central = ''
|
||||||
group_id = home_info.get('group_id', None)
|
group_id = home_info.get('group_id', None)
|
||||||
@ -582,7 +586,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
# i18n
|
# i18n
|
||||||
tip_central = self._miot_i18n.translate(
|
tip_central = self._miot_i18n.translate(
|
||||||
key='config.other.found_central_gateway')
|
key='config.other.found_central_gateway')
|
||||||
home_info['central_did'] = mips_list[group_id].get('did', None)
|
home_info['central_did'] = mips_list[group_id].get(
|
||||||
|
'did', None)
|
||||||
home_list[home_id] = (
|
home_list[home_id] = (
|
||||||
f'{home_info["home_name"]} '
|
f'{home_info["home_name"]} '
|
||||||
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
||||||
@ -660,8 +665,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if not home_selected:
|
if not home_selected:
|
||||||
return await self.__show_homes_select_form(
|
return await self.__show_homes_select_form(
|
||||||
'no_family_selected')
|
'no_family_selected')
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
if home_id in home_selected:
|
if home_id in home_selected:
|
||||||
self._home_selected[home_id] = home_info
|
self._home_selected[home_id] = home_info
|
||||||
self._area_name_rule = user_input.get(
|
self._area_name_rule = user_input.get(
|
||||||
@ -1420,8 +1429,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
home_list = {}
|
home_list = {}
|
||||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||||
# home list
|
# home list
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
# i18n
|
# i18n
|
||||||
tip_central = ''
|
tip_central = ''
|
||||||
group_id = home_info.get('group_id', None)
|
group_id = home_info.get('group_id', None)
|
||||||
@ -1460,8 +1473,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
return await self.__show_homes_select_form('no_family_selected')
|
return await self.__show_homes_select_form('no_family_selected')
|
||||||
self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
|
self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
|
||||||
self._home_selected = {}
|
self._home_selected = {}
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
if home_id in self._home_selected_list:
|
if home_id in self._home_selected_list:
|
||||||
self._home_selected[home_id] = home_info
|
self._home_selected[home_id] = home_info
|
||||||
# Get device list
|
# Get device list
|
||||||
|
@ -236,6 +236,9 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
async def async_set_percentage(self, percentage: int) -> None:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the percentage of the fan speed."""
|
"""Set the percentage of the fan speed."""
|
||||||
if percentage > 0:
|
if percentage > 0:
|
||||||
|
if not self.is_on:
|
||||||
|
# If the fan is off, turn it on.
|
||||||
|
await self.set_property_async(prop=self._prop_on, value=True)
|
||||||
if self._speed_names:
|
if self._speed_names:
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_fan_level,
|
prop=self._prop_fan_level,
|
||||||
@ -249,9 +252,6 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
value=int(percentage_to_ranged_value(
|
value=int(percentage_to_ranged_value(
|
||||||
low_high_range=(self._speed_min, self._speed_max),
|
low_high_range=(self._speed_min, self._speed_max),
|
||||||
percentage=percentage)))
|
percentage=percentage)))
|
||||||
if not self.is_on:
|
|
||||||
# If the fan is off, turn it on.
|
|
||||||
await self.set_property_async(prop=self._prop_on, value=True)
|
|
||||||
else:
|
else:
|
||||||
await self.set_property_async(prop=self._prop_on, value=False)
|
await self.set_property_async(prop=self._prop_on, value=False)
|
||||||
|
|
||||||
|
@ -52,11 +52,10 @@ from typing import Any, Optional
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.humidifier import (
|
from homeassistant.components.humidifier import (HumidifierEntity,
|
||||||
HumidifierEntity,
|
|
||||||
HumidifierDeviceClass,
|
HumidifierDeviceClass,
|
||||||
HumidifierEntityFeature
|
HumidifierEntityFeature,
|
||||||
)
|
HumidifierAction)
|
||||||
|
|
||||||
from .miot.miot_spec import MIoTSpecProperty
|
from .miot.miot_spec import MIoTSpecProperty
|
||||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||||
@ -82,8 +81,8 @@ async def async_setup_entry(
|
|||||||
Humidifier(miot_device=miot_device, entity_data=data))
|
Humidifier(miot_device=miot_device, entity_data=data))
|
||||||
for data in miot_device.entity_list.get('dehumidifier', []):
|
for data in miot_device.entity_list.get('dehumidifier', []):
|
||||||
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
|
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
|
||||||
new_entities.append(Humidifier(
|
new_entities.append(
|
||||||
miot_device=miot_device, entity_data=data))
|
Humidifier(miot_device=miot_device, entity_data=data))
|
||||||
|
|
||||||
if new_entities:
|
if new_entities:
|
||||||
async_add_entities(new_entities)
|
async_add_entities(new_entities)
|
||||||
@ -99,9 +98,8 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
|
|
||||||
_mode_map: dict[Any, Any]
|
_mode_map: dict[Any, Any]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, miot_device: MIoTDevice,
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
entity_data: MIoTEntityData) -> None:
|
||||||
) -> None:
|
|
||||||
"""Initialize the Humidifier."""
|
"""Initialize the Humidifier."""
|
||||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||||
self._attr_device_class = entity_data.device_class
|
self._attr_device_class = entity_data.device_class
|
||||||
@ -130,12 +128,10 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
# mode
|
# mode
|
||||||
elif prop.name == 'mode':
|
elif prop.name == 'mode':
|
||||||
if not prop.value_list:
|
if not prop.value_list:
|
||||||
_LOGGER.error(
|
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||||
'mode value_list is None, %s', self.entity_id)
|
|
||||||
continue
|
continue
|
||||||
self._mode_map = prop.value_list.to_map()
|
self._mode_map = prop.value_list.to_map()
|
||||||
self._attr_available_modes = list(
|
self._attr_available_modes = list(self._mode_map.values())
|
||||||
self._mode_map.values())
|
|
||||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
# relative-humidity
|
# relative-humidity
|
||||||
@ -152,33 +148,45 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
|
|
||||||
async def async_set_humidity(self, humidity: int) -> None:
|
async def async_set_humidity(self, humidity: int) -> None:
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
await self.set_property_async(
|
if self._prop_target_humidity is None:
|
||||||
prop=self._prop_target_humidity, value=humidity)
|
return
|
||||||
|
await self.set_property_async(prop=self._prop_target_humidity,
|
||||||
|
value=humidity)
|
||||||
|
|
||||||
async def async_set_mode(self, mode: str) -> None:
|
async def async_set_mode(self, mode: str) -> None:
|
||||||
"""Set new target preset mode."""
|
"""Set new target preset mode."""
|
||||||
await self.set_property_async(
|
await self.set_property_async(prop=self._prop_mode,
|
||||||
prop=self._prop_mode,
|
value=self.get_map_key(
|
||||||
value=self.get_map_key(map_=self._mode_map, value=mode))
|
map_=self._mode_map, value=mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> Optional[bool]:
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return if the humidifier is on."""
|
"""Return if the humidifier is on."""
|
||||||
return self.get_prop_value(prop=self._prop_on)
|
return self.get_prop_value(prop=self._prop_on)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action(self) -> Optional[HumidifierAction]:
|
||||||
|
"""Return the current status of the device."""
|
||||||
|
if not self.is_on:
|
||||||
|
return HumidifierAction.OFF
|
||||||
|
if self._attr_device_class == HumidifierDeviceClass.HUMIDIFIER:
|
||||||
|
return HumidifierAction.HUMIDIFYING
|
||||||
|
return HumidifierAction.DRYING
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_humidity(self) -> Optional[int]:
|
def current_humidity(self) -> Optional[int]:
|
||||||
"""Return the current humidity."""
|
"""Return the current humidity."""
|
||||||
return self.get_prop_value(prop=self._prop_humidity)
|
return (self.get_prop_value(
|
||||||
|
prop=self._prop_humidity) if self._prop_humidity else None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_humidity(self) -> Optional[int]:
|
def target_humidity(self) -> Optional[int]:
|
||||||
"""Return the target humidity."""
|
"""Return the target humidity."""
|
||||||
return self.get_prop_value(prop=self._prop_target_humidity)
|
return (self.get_prop_value(prop=self._prop_target_humidity)
|
||||||
|
if self._prop_target_humidity else None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode(self) -> Optional[str]:
|
def mode(self) -> Optional[str]:
|
||||||
"""Return the current preset mode."""
|
"""Return the current preset mode."""
|
||||||
return self.get_map_value(
|
return self.get_map_value(map_=self._mode_map,
|
||||||
map_=self._mode_map,
|
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"cryptography",
|
"cryptography",
|
||||||
"psutil"
|
"psutil"
|
||||||
],
|
],
|
||||||
"version": "v0.2.2",
|
"version": "v0.3.2",
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
"_miot-central._tcp.local."
|
"_miot-central._tcp.local."
|
||||||
]
|
]
|
||||||
|
@ -646,7 +646,8 @@ class MIoTClient:
|
|||||||
result = await self._miot_lan.set_prop_async(
|
result = await self._miot_lan.set_prop_async(
|
||||||
did=did, siid=siid, piid=piid, value=value)
|
did=did, siid=siid, piid=piid, value=value)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
'lan set prop, %s, %s, %s -> %s', did, siid, piid, result)
|
'lan set prop, %s.%d.%d, %s -> %s',
|
||||||
|
did, siid, piid, value, result)
|
||||||
rc = (result or {}).get(
|
rc = (result or {}).get(
|
||||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||||
if rc in [0, 1]:
|
if rc in [0, 1]:
|
||||||
@ -879,16 +880,7 @@ class MIoTClient:
|
|||||||
sub_from = self._sub_source_list.pop(did, None)
|
sub_from = self._sub_source_list.pop(did, None)
|
||||||
# Unsub
|
# Unsub
|
||||||
if sub_from:
|
if sub_from:
|
||||||
if sub_from == 'cloud':
|
self.__unsub_from(sub_from, did)
|
||||||
self._mips_cloud.unsub_prop(did=did)
|
|
||||||
self._mips_cloud.unsub_event(did=did)
|
|
||||||
elif sub_from == 'lan':
|
|
||||||
self._miot_lan.unsub_prop(did=did)
|
|
||||||
self._miot_lan.unsub_event(did=did)
|
|
||||||
elif sub_from in self._mips_local:
|
|
||||||
mips = self._mips_local[sub_from]
|
|
||||||
mips.unsub_prop(did=did)
|
|
||||||
mips.unsub_event(did=did)
|
|
||||||
# Storage
|
# Storage
|
||||||
await self._storage.save_async(
|
await self._storage.save_async(
|
||||||
domain='miot_devices',
|
domain='miot_devices',
|
||||||
@ -936,6 +928,39 @@ class MIoTClient:
|
|||||||
delay_sec, lambda: self._main_loop.create_task(
|
delay_sec, lambda: self._main_loop.create_task(
|
||||||
self.refresh_user_cert_async()))
|
self.refresh_user_cert_async()))
|
||||||
|
|
||||||
|
@final
|
||||||
|
def __unsub_from(self, sub_from: str, did: str) -> None:
|
||||||
|
mips: Any = None
|
||||||
|
if sub_from == 'cloud':
|
||||||
|
mips = self._mips_cloud
|
||||||
|
elif sub_from == 'lan':
|
||||||
|
mips = self._miot_lan
|
||||||
|
elif sub_from in self._mips_local:
|
||||||
|
mips = self._mips_local[sub_from]
|
||||||
|
if mips is not None:
|
||||||
|
try:
|
||||||
|
mips.unsub_prop(did=did)
|
||||||
|
mips.unsub_event(did=did)
|
||||||
|
except RuntimeError as e:
|
||||||
|
if 'Event loop is closed' in str(e):
|
||||||
|
# Ignore unsub exception when loop is closed
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
@final
|
||||||
|
def __sub_from(self, sub_from: str, did: str) -> None:
|
||||||
|
mips = None
|
||||||
|
if sub_from == 'cloud':
|
||||||
|
mips = self._mips_cloud
|
||||||
|
elif sub_from == 'lan':
|
||||||
|
mips = self._miot_lan
|
||||||
|
elif sub_from in self._mips_local:
|
||||||
|
mips = self._mips_local[sub_from]
|
||||||
|
if mips is not None:
|
||||||
|
mips.sub_prop(did=did, handler=self.__on_prop_msg)
|
||||||
|
mips.sub_event(did=did, handler=self.__on_event_msg)
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def __update_device_msg_sub(self, did: str) -> None:
|
def __update_device_msg_sub(self, did: str) -> None:
|
||||||
if did not in self._device_list_cache:
|
if did not in self._device_list_cache:
|
||||||
@ -967,27 +992,9 @@ class MIoTClient:
|
|||||||
return
|
return
|
||||||
# Unsub old
|
# Unsub old
|
||||||
if from_old:
|
if from_old:
|
||||||
if from_old == 'cloud':
|
self.__unsub_from(from_old, did)
|
||||||
self._mips_cloud.unsub_prop(did=did)
|
|
||||||
self._mips_cloud.unsub_event(did=did)
|
|
||||||
elif from_old == 'lan':
|
|
||||||
self._miot_lan.unsub_prop(did=did)
|
|
||||||
self._miot_lan.unsub_event(did=did)
|
|
||||||
elif from_old in self._mips_local:
|
|
||||||
mips = self._mips_local[from_old]
|
|
||||||
mips.unsub_prop(did=did)
|
|
||||||
mips.unsub_event(did=did)
|
|
||||||
# Sub new
|
# Sub new
|
||||||
if from_new == 'cloud':
|
self.__sub_from(from_new, did)
|
||||||
self._mips_cloud.sub_prop(did=did, handler=self.__on_prop_msg)
|
|
||||||
self._mips_cloud.sub_event(did=did, handler=self.__on_event_msg)
|
|
||||||
elif from_new == 'lan':
|
|
||||||
self._miot_lan.sub_prop(did=did, handler=self.__on_prop_msg)
|
|
||||||
self._miot_lan.sub_event(did=did, handler=self.__on_event_msg)
|
|
||||||
elif from_new in self._mips_local:
|
|
||||||
mips = self._mips_local[from_new]
|
|
||||||
mips.sub_prop(did=did, handler=self.__on_prop_msg)
|
|
||||||
mips.sub_event(did=did, handler=self.__on_event_msg)
|
|
||||||
self._sub_source_list[did] = from_new
|
self._sub_source_list[did] = from_new
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
'device sub changed, %s, from %s to %s', did, from_old, from_new)
|
'device sub changed, %s, from %s to %s', did, from_old, from_new)
|
||||||
|
@ -444,6 +444,17 @@ class MIoTHttpClient:
|
|||||||
|
|
||||||
return home_list
|
return home_list
|
||||||
|
|
||||||
|
async def get_separated_shared_devices_async(self) -> dict[str, dict]:
|
||||||
|
separated_shared_devices: dict = {}
|
||||||
|
device_list: dict[str, dict] = await self.__get_device_list_page_async(
|
||||||
|
dids=[], start_did=None)
|
||||||
|
for did, value in device_list.items():
|
||||||
|
if value['owner'] is not None and ('userid' in value['owner']) and (
|
||||||
|
'nickname' in value['owner']
|
||||||
|
):
|
||||||
|
separated_shared_devices.setdefault(did, value['owner'])
|
||||||
|
return separated_shared_devices
|
||||||
|
|
||||||
async def get_homeinfos_async(self) -> dict:
|
async def get_homeinfos_async(self) -> dict:
|
||||||
res_obj = await self.__mihome_api_post_async(
|
res_obj = await self.__mihome_api_post_async(
|
||||||
url_path='/app/v2/homeroom/gethome',
|
url_path='/app/v2/homeroom/gethome',
|
||||||
@ -499,18 +510,21 @@ class MIoTHttpClient:
|
|||||||
):
|
):
|
||||||
more_list = await self.__get_dev_room_page_async(
|
more_list = await self.__get_dev_room_page_async(
|
||||||
max_id=res_obj['result']['max_id'])
|
max_id=res_obj['result']['max_id'])
|
||||||
|
for device_source in ['homelist', 'share_home_list']:
|
||||||
for home_id, info in more_list.items():
|
for home_id, info in more_list.items():
|
||||||
if home_id not in home_infos['homelist']:
|
if home_id not in home_infos[device_source]:
|
||||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||||
continue
|
continue
|
||||||
home_infos['homelist'][home_id]['dids'].extend(info['dids'])
|
home_infos[device_source][home_id]['dids'].extend(
|
||||||
|
info['dids'])
|
||||||
for room_id, info in info['room_info'].items():
|
for room_id, info in info['room_info'].items():
|
||||||
home_infos['homelist'][home_id]['room_info'].setdefault(
|
home_infos[device_source][home_id][
|
||||||
|
'room_info'].setdefault(
|
||||||
room_id, {
|
room_id, {
|
||||||
'room_id': room_id,
|
'room_id': room_id,
|
||||||
'room_name': '',
|
'room_name': '',
|
||||||
'dids': []})
|
'dids': []})
|
||||||
home_infos['homelist'][home_id]['room_info'][
|
home_infos[device_source][home_id]['room_info'][
|
||||||
room_id]['dids'].extend(info['dids'])
|
room_id]['dids'].extend(info['dids'])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -651,6 +665,25 @@ class MIoTHttpClient:
|
|||||||
'room_name': room_name,
|
'room_name': room_name,
|
||||||
'group_id': group_id
|
'group_id': group_id
|
||||||
} for did in room_info.get('dids', [])})
|
} for did in room_info.get('dids', [])})
|
||||||
|
separated_shared_devices: dict = (
|
||||||
|
await self.get_separated_shared_devices_async())
|
||||||
|
if separated_shared_devices:
|
||||||
|
homes.setdefault('separated_shared_list', {})
|
||||||
|
for did, owner in separated_shared_devices.items():
|
||||||
|
owner_id = str(owner['userid'])
|
||||||
|
homes['separated_shared_list'].setdefault(owner_id,{
|
||||||
|
'home_name': owner['nickname'],
|
||||||
|
'uid': owner_id,
|
||||||
|
'group_id': 'NotSupport',
|
||||||
|
'room_info': {'shared_device': 'shared_device'}
|
||||||
|
})
|
||||||
|
devices.update({did: {
|
||||||
|
'home_id': owner_id,
|
||||||
|
'home_name': owner['nickname'],
|
||||||
|
'room_id': 'shared_device',
|
||||||
|
'room_name': 'shared_device',
|
||||||
|
'group_id': 'NotSupport'
|
||||||
|
}})
|
||||||
dids = sorted(list(devices.keys()))
|
dids = sorted(list(devices.keys()))
|
||||||
results = await self.get_devices_with_dids_async(dids=dids)
|
results = await self.get_devices_with_dids_async(dids=dids)
|
||||||
if results is None:
|
if results is None:
|
||||||
|
@ -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
|
||||||
@ -778,8 +779,10 @@ class MIoTDevice:
|
|||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
from homeassistant.const import UnitOfConductivity # type: ignore
|
from homeassistant.const import UnitOfConductivity # type: ignore
|
||||||
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
|
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
|
||||||
|
unit_map['mWh'] = UnitOfEnergy.MILLIWATT_HOUR
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
unit_map['μS/cm'] = 'μS/cm'
|
unit_map['μS/cm'] = 'μS/cm'
|
||||||
|
unit_map['mWh'] = 'mWh'
|
||||||
|
|
||||||
return unit_map.get(spec_unit, None)
|
return unit_map.get(spec_unit, None)
|
||||||
|
|
||||||
@ -894,7 +897,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}')
|
||||||
|
@ -226,7 +226,7 @@ class _MIoTLanDevice:
|
|||||||
def gen_packet(
|
def gen_packet(
|
||||||
self, out_buffer: bytearray, clear_data: dict, did: str, offset: int
|
self, out_buffer: bytearray, clear_data: dict, did: str, offset: int
|
||||||
) -> int:
|
) -> int:
|
||||||
clear_bytes = json.dumps(clear_data).encode('utf-8')
|
clear_bytes = json.dumps(clear_data, ensure_ascii=False).encode('utf-8')
|
||||||
padder = padding.PKCS7(algorithms.AES128.block_size).padder()
|
padder = padding.PKCS7(algorithms.AES128.block_size).padder()
|
||||||
padded_data = padder.update(clear_bytes) + padder.finalize()
|
padded_data = padder.update(clear_bytes) + padder.finalize()
|
||||||
if len(padded_data) + self.OT_HEADER_LEN > len(out_buffer):
|
if len(padded_data) + self.OT_HEADER_LEN > len(out_buffer):
|
||||||
|
@ -1215,7 +1215,7 @@ 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('unknown event msg, %s', 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)
|
self.log_info('wrong event msg, %s', payload)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"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:gateway:0000A019:xiaomi-hub1": {
|
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||||
"de": {
|
"de": {
|
||||||
"service:001": "Geräteinformationen",
|
"service:001": "Geräteinformationen",
|
||||||
@ -155,6 +161,26 @@
|
|||||||
"service:004:property:001": "事件名稱"
|
"service:004:property:001": "事件名稱"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"urn:miot-spec-v2:device:lock:0000A038:loock-t2pv1": {
|
||||||
|
"zh-Hans": {
|
||||||
|
"service:003:property:1021:valuelist:000": "已上锁",
|
||||||
|
"service:003:property:1021:valuelist:001": "已上锁(童锁)",
|
||||||
|
"service:003:property:1021:valuelist:002": "已上锁(反锁)",
|
||||||
|
"service:003:property:1021:valuelist:003": "已上锁(反锁+童锁)",
|
||||||
|
"service:003:property:1021:valuelist:004": "已开锁",
|
||||||
|
"service:003:property:1021:valuelist:008": "门未关(门超时未关)",
|
||||||
|
"service:003:property:1021:valuelist:012": "门虚掩"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": "光照强度"
|
||||||
|
}
|
||||||
|
},
|
||||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||||
"en": {
|
"en": {
|
||||||
"service:011": "Right Button On and Off",
|
"service:011": "Right Button On and Off",
|
||||||
|
62
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
62
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"urn:miot-spec-v2:device:water-heater:0000A02A:xiaomi-yms2:1": [
|
||||||
|
{
|
||||||
|
"iid": 2,
|
||||||
|
"type": "urn:miot-spec-v2:service:switch:0000780C:xiaomi-yms2:1",
|
||||||
|
"description": "Switch",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"iid": 6,
|
||||||
|
"type": "urn:miot-spec-v2:property:on:00000006:xiaomi-yms2:1",
|
||||||
|
"description": "Switch Status",
|
||||||
|
"format": "bool",
|
||||||
|
"access": [
|
||||||
|
"read",
|
||||||
|
"write",
|
||||||
|
"notify"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"urn:miot-spec-v2:device:water-heater:0000A02A:zimi-h03:1": [
|
||||||
|
{
|
||||||
|
"iid": 2,
|
||||||
|
"type": "urn:miot-spec-v2:service:switch:0000780C:zimi-h03:1",
|
||||||
|
"description": "Switch",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"iid": 6,
|
||||||
|
"type": "urn:miot-spec-v2:property:on:00000006:zimi-h03:1",
|
||||||
|
"description": "Switch Status",
|
||||||
|
"format": "bool",
|
||||||
|
"access": [
|
||||||
|
"read",
|
||||||
|
"write",
|
||||||
|
"notify"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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,85 @@
|
|||||||
|
urn:miot-spec-v2:device:air-condition-outlet:0000A045:lumi-mcn04:1:
|
||||||
|
prop.3.4:
|
||||||
|
format: uint8
|
||||||
|
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||||
|
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9: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:dmaker-p5:1:
|
||||||
|
prop.2.4:
|
||||||
|
name: fan-level-a
|
||||||
|
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
||||||
|
prop.2.2:
|
||||||
|
name: fan-level-a
|
||||||
|
urn:miot-spec-v2:device:fan:0000A005:zhimi-sa1:3:
|
||||||
|
prop.2.2:
|
||||||
|
name: fan-level-a
|
||||||
|
urn:miot-spec-v2:device:fan:0000A005:zhimi-v3:3:
|
||||||
|
prop.2.2:
|
||||||
|
name: fan-level-a
|
||||||
|
urn:miot-spec-v2:device:fan:0000A005:zhimi-za4:3:
|
||||||
|
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 +96,81 @@ 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:kettle:0000A009:yunmi-r3:1:
|
||||||
|
prop.3.1:
|
||||||
|
unit: ppm
|
||||||
|
urn:miot-spec-v2:device:light:0000A001:shhf-sfla10:1:
|
||||||
|
prop.8.9:
|
||||||
|
name: wind-reverse
|
||||||
|
urn:miot-spec-v2:device:light:0000A001:shhf-sfla12:1:
|
||||||
|
prop.8.11:
|
||||||
|
name: on-a
|
||||||
|
urn:miot-spec-v2:device:magnet-sensor:0000A016:linp-m1:1:
|
||||||
|
prop.2.1004:
|
||||||
|
name: contact-state
|
||||||
|
expr: src_value!=1
|
||||||
|
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)
|
||||||
|
prop.2.3:
|
||||||
|
expr: round(src_value/10, 1)
|
||||||
|
prop.2.4:
|
||||||
|
unit: mA
|
||||||
|
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:giot-v8icm:1:0000C816:
|
||||||
|
prop.4.1:
|
||||||
|
unit: mWh
|
||||||
|
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:1:0000C816: urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816
|
||||||
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,37 +180,59 @@ 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:safe-box:0000A042:loock-v1:1:
|
||||||
|
prop.5.1:
|
||||||
|
name: contact-state
|
||||||
|
expr: src_value!=1
|
||||||
|
urn:miot-spec-v2:device:switch:0000A003:lxzn-cbcsmj:1:0000D00D:
|
||||||
|
prop.3.1:
|
||||||
|
expr: round(src_value/100, 2)
|
||||||
|
prop.3.2:
|
||||||
|
expr: round(src_value/1000, 2)
|
||||||
|
prop.3.3:
|
||||||
|
expr: round(src_value/10, 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'
|
||||||
urn:miot-spec-v2:device:bath-heater:0000A028:mike-2:1:
|
- value: 4
|
||||||
prop.3.1:
|
description: '4'
|
||||||
name: mode-a
|
- value: 5
|
||||||
prop.3.11:
|
description: '5'
|
||||||
name: mode-b
|
- value: 6
|
||||||
prop.3.12:
|
description: '6'
|
||||||
name: mode-c
|
- value: 7
|
||||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
description: '7'
|
||||||
prop.2.2:
|
- value: 8
|
||||||
name: fan-level-a
|
description: '8'
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6:
|
- value: 9
|
||||||
prop.10.6:
|
description: '9'
|
||||||
unit: none
|
- value: 10
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
description: '10'
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
- value: 11
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
description: '11'
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
- value: 12
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
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,
|
||||||
@ -388,6 +389,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
|||||||
'fan-control': 'fan',
|
'fan-control': 'fan',
|
||||||
'ceiling-fan': 'fan',
|
'ceiling-fan': 'fan',
|
||||||
'air-fresh': 'fan',
|
'air-fresh': 'fan',
|
||||||
|
'air-purifier': 'fan',
|
||||||
'water-heater': {
|
'water-heater': {
|
||||||
'required': {
|
'required': {
|
||||||
'properties': {
|
'properties': {
|
||||||
@ -395,7 +397,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'optional': {
|
'optional': {
|
||||||
'properties': {'on', 'temperature', 'target-temperature', 'mode'}
|
'properties': {'temperature', 'target-temperature', 'mode'}
|
||||||
},
|
},
|
||||||
'entity': 'water_heater'
|
'entity': 'water_heater'
|
||||||
},
|
},
|
||||||
@ -454,12 +456,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',
|
||||||
@ -506,7 +524,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',
|
||||||
@ -560,12 +582,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
|||||||
self._attr_native_unit_of_measurement = list(
|
self._attr_native_unit_of_measurement = list(
|
||||||
unit_sets)[0] if unit_sets else None
|
unit_sets)[0] if unit_sets else None
|
||||||
# Set suggested precision
|
# Set suggested precision
|
||||||
if spec.format_ in {int, float}:
|
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:
|
||||||
|
@ -52,13 +52,10 @@ from typing import Any, Optional
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.water_heater import (
|
from homeassistant.components.water_heater import (STATE_ON, STATE_OFF,
|
||||||
STATE_ON,
|
|
||||||
STATE_OFF,
|
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
WaterHeaterEntity,
|
WaterHeaterEntity,
|
||||||
WaterHeaterEntityFeature
|
WaterHeaterEntityFeature)
|
||||||
)
|
|
||||||
|
|
||||||
from .miot.const import DOMAIN
|
from .miot.const import DOMAIN
|
||||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||||
@ -79,8 +76,8 @@ async def async_setup_entry(
|
|||||||
new_entities = []
|
new_entities = []
|
||||||
for miot_device in device_list:
|
for miot_device in device_list:
|
||||||
for data in miot_device.entity_list.get('water_heater', []):
|
for data in miot_device.entity_list.get('water_heater', []):
|
||||||
new_entities.append(WaterHeater(
|
new_entities.append(
|
||||||
miot_device=miot_device, entity_data=data))
|
WaterHeater(miot_device=miot_device, entity_data=data))
|
||||||
|
|
||||||
if new_entities:
|
if new_entities:
|
||||||
async_add_entities(new_entities)
|
async_add_entities(new_entities)
|
||||||
@ -95,12 +92,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
|
|
||||||
_mode_map: Optional[dict[Any, Any]]
|
_mode_map: Optional[dict[Any, Any]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, miot_device: MIoTDevice,
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
entity_data: MIoTEntityData) -> None:
|
||||||
) -> None:
|
|
||||||
"""Initialize the Water heater."""
|
"""Initialize the Water heater."""
|
||||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||||
self._attr_temperature_unit = None # type: ignore
|
self._attr_temperature_unit = None
|
||||||
self._attr_supported_features = WaterHeaterEntityFeature(0)
|
self._attr_supported_features = WaterHeaterEntityFeature(0)
|
||||||
self._prop_on = None
|
self._prop_on = None
|
||||||
self._prop_temp = None
|
self._prop_temp = None
|
||||||
@ -117,14 +113,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
# temperature
|
# temperature
|
||||||
if prop.name == 'temperature':
|
if prop.name == 'temperature':
|
||||||
if not prop.value_range:
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error('invalid temperature value_range format, %s',
|
||||||
'invalid temperature value_range format, %s',
|
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
if prop.external_unit:
|
if prop.external_unit:
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_min_temp = prop.value_range.min_
|
|
||||||
self._attr_max_temp = prop.value_range.max_
|
|
||||||
self._prop_temp = prop
|
self._prop_temp = prop
|
||||||
# target-temperature
|
# target-temperature
|
||||||
if prop.name == 'target-temperature':
|
if prop.name == 'target-temperature':
|
||||||
@ -133,9 +126,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
'invalid target-temperature value_range format, %s',
|
'invalid target-temperature value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_target_temperature_low = prop.value_range.min_
|
self._attr_min_temp = prop.value_range.min_
|
||||||
self._attr_target_temperature_high = prop.value_range.max_
|
self._attr_max_temp = prop.value_range.max_
|
||||||
self._attr_precision = prop.value_range.step
|
self._attr_target_temperature_step = prop.value_range.step
|
||||||
if self._attr_temperature_unit is None and prop.external_unit:
|
if self._attr_temperature_unit is None and prop.external_unit:
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
@ -144,8 +137,7 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
# mode
|
# mode
|
||||||
if prop.name == 'mode':
|
if prop.name == 'mode':
|
||||||
if not prop.value_list:
|
if not prop.value_list:
|
||||||
_LOGGER.error(
|
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||||
'mode value_list is None, %s', self.entity_id)
|
|
||||||
continue
|
continue
|
||||||
self._mode_map = prop.value_list.to_map()
|
self._mode_map = prop.value_list.to_map()
|
||||||
self._attr_operation_list = list(self._mode_map.values())
|
self._attr_operation_list = list(self._mode_map.values())
|
||||||
@ -165,16 +157,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
await self.set_property_async(prop=self._prop_on, value=False)
|
await self.set_property_async(prop=self._prop_on, value=False)
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set the temperature the water heater should heat water to."""
|
"""Set the target temperature."""
|
||||||
if not self._prop_target_temp:
|
await self.set_property_async(prop=self._prop_target_temp,
|
||||||
return
|
value=kwargs[ATTR_TEMPERATURE])
|
||||||
await self.set_property_async(
|
|
||||||
prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE])
|
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
||||||
"""Set the operation mode of the water heater.
|
"""Set the operation mode of the water heater."""
|
||||||
Must be in the operation_list.
|
|
||||||
"""
|
|
||||||
if operation_mode == STATE_OFF:
|
if operation_mode == STATE_OFF:
|
||||||
await self.set_property_async(prop=self._prop_on, value=False)
|
await self.set_property_async(prop=self._prop_on, value=False)
|
||||||
return
|
return
|
||||||
@ -182,32 +170,32 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
await self.set_property_async(prop=self._prop_on, value=True)
|
await self.set_property_async(prop=self._prop_on, value=True)
|
||||||
return
|
return
|
||||||
if self.get_prop_value(prop=self._prop_on) is False:
|
if self.get_prop_value(prop=self._prop_on) is False:
|
||||||
await self.set_property_async(
|
await self.set_property_async(prop=self._prop_on,
|
||||||
prop=self._prop_on, value=True, write_ha_state=False)
|
value=True,
|
||||||
await self.set_property_async(
|
write_ha_state=False)
|
||||||
prop=self._prop_mode,
|
await self.set_property_async(prop=self._prop_mode,
|
||||||
value=self.get_map_key(
|
value=self.get_map_key(
|
||||||
map_=self._mode_map, value=operation_mode))
|
map_=self._mode_map,
|
||||||
|
value=operation_mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> Optional[float]:
|
def current_temperature(self) -> Optional[float]:
|
||||||
"""Return the current temperature."""
|
"""The current temperature."""
|
||||||
return self.get_prop_value(prop=self._prop_temp)
|
return (None if self._prop_temp is None else self.get_prop_value(
|
||||||
|
prop=self._prop_temp))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> Optional[float]:
|
def target_temperature(self) -> Optional[float]:
|
||||||
"""Return the target temperature."""
|
"""The target temperature."""
|
||||||
if not self._prop_target_temp:
|
return (None if self._prop_target_temp is None else self.get_prop_value(
|
||||||
return None
|
prop=self._prop_target_temp))
|
||||||
return self.get_prop_value(prop=self._prop_target_temp)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self) -> Optional[str]:
|
def current_operation(self) -> Optional[str]:
|
||||||
"""Return the current mode."""
|
"""The current mode."""
|
||||||
if self.get_prop_value(prop=self._prop_on) is False:
|
if self.get_prop_value(prop=self._prop_on) is False:
|
||||||
return STATE_OFF
|
return STATE_OFF
|
||||||
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
return self.get_map_value(
|
return self.get_map_value(map_=self._mode_map,
|
||||||
map_=self._mode_map,
|
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
@ -33,9 +33,11 @@ git checkout v1.0.0
|
|||||||
|
|
||||||
### 方法 2: [HACS](https://hacs.xyz/)
|
### 方法 2: [HACS](https://hacs.xyz/)
|
||||||
|
|
||||||
HACS > 右上角三个点 > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > 点击 HACS 的 New 或 Available for download 分类下的 Xiaomi Home ,进入集成详情页 > DOWNLOAD
|
一键从 HACS 安装米家集成:
|
||||||
|
|
||||||
> 米家集成暂未添加到 HACS 商店,敬请期待。
|
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)
|
||||||
|
|
||||||
|
或者,HACS > 在搜索框中输入 **Xiaomi Home** > 点击 **Xiaomi Home** ,进入集成详情页 > DOWNLOAD
|
||||||
|
|
||||||
### 方法 3:通过 [Samba](https://github.com/home-assistant/addons/tree/master/samba) 或 [FTPS](https://github.com/hassio-addons/addon-ftp) 手动安装
|
### 方法 3:通过 [Samba](https://github.com/home-assistant/addons/tree/master/samba) 或 [FTPS](https://github.com/hassio-addons/addon-ftp) 手动安装
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
|||||||
|
|
||||||
[设置 > 设备与服务 > 添加集成](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > 搜索“`Xiaomi Home`” > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
[设置 > 设备与服务 > 添加集成](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > 搜索“`Xiaomi Home`” > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||||
|
|
||||||
### 添加 MIoT 设备
|
### 添加 MIoT 设备
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
|||||||
|
|
||||||
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 添加中枢 > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 添加中枢 > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||||
|
|
||||||
### 修改配置项
|
### 修改配置项
|
||||||
|
|
||||||
@ -353,7 +355,7 @@ instance code 为 MIoT-Spec-V2 实例代码,格式如下:
|
|||||||
```
|
```
|
||||||
service:<siid> # 服务
|
service:<siid> # 服务
|
||||||
service:<siid>:property:<piid> # 属性
|
service:<siid>:property:<piid> # 属性
|
||||||
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的值
|
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的索引值
|
||||||
service:<siid>:event:<eiid> # 事件
|
service:<siid>:event:<eiid> # 事件
|
||||||
service:<siid>:action:<aiid> # 方法
|
service:<siid>:action:<aiid> # 方法
|
||||||
```
|
```
|
||||||
|
@ -15,14 +15,15 @@ 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_MULTI_LANG_FILE = path.join(
|
||||||
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/multi_lang.json')
|
||||||
|
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 +31,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)
|
||||||
@ -39,7 +40,7 @@ def load_json_file(file_path: str) -> Optional[dict]:
|
|||||||
|
|
||||||
def save_json_file(file_path: str, data: dict) -> None:
|
def save_json_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:
|
||||||
json.dump(data, file, ensure_ascii=False, indent=4)
|
json.dump(data, file, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
def load_yaml_file(file_path: str) -> Optional[dict]:
|
def load_yaml_file(file_path: str) -> Optional[dict]:
|
||||||
@ -56,9 +57,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 +136,116 @@ 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 multi_lang(data: dict) -> bool:
|
||||||
|
"""dict[str, dict[str, dict[str, str]]]"""
|
||||||
|
for key in data.keys():
|
||||||
|
if key.count(':') != 5:
|
||||||
|
return False
|
||||||
|
return nested_3_dict_str_str(data)
|
||||||
|
|
||||||
|
|
||||||
|
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 +270,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 +308,16 @@ 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_multi_lang(file_path: str):
|
||||||
|
return sort_spec_add(file_path)
|
||||||
|
|
||||||
|
|
||||||
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 +340,22 @@ 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_multi_lang():
|
||||||
|
data = load_json_file(SPEC_MULTI_LANG_FILE)
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert data, f'load {SPEC_MULTI_LANG_FILE} failed'
|
||||||
|
assert multi_lang(data), f'{SPEC_MULTI_LANG_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 +389,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 +406,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 +435,27 @@ 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_MULTI_LANG_FILE)) == json.dumps(
|
||||||
|
sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)), (
|
||||||
|
f'{SPEC_MULTI_LANG_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_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 +466,11 @@ 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_multi_lang(file_path=SPEC_MULTI_LANG_FILE)
|
||||||
|
save_json_file(file_path=SPEC_MULTI_LANG_FILE, data=sort_data)
|
||||||
|
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)
|
||||||
|
Reference in New Issue
Block a user