mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-07-02 10:03:09 +08:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b1d003bb2 | |||
6069eaaba8 | |||
fd57e7c565 | |||
096b33f3c9 | |||
664787ca58 | |||
d659d13e49 | |||
3402587b1c | |||
028399c0b1 | |||
5179e97e38 | |||
9fdbf3dff2 | |||
d0508ead25 | |||
d05bdcbba9 | |||
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 / 报告问题
|
||||
description: Create a report to help us improve. / 报告问题以帮助我们改进
|
||||
body:
|
||||
- type: input
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug / 描述问题
|
||||
description: |
|
||||
|
71
CHANGELOG.md
71
CHANGELOG.md
@ -1,4 +1,75 @@
|
||||
# CHANGELOG
|
||||
## v0.3.3
|
||||
### Changed
|
||||
- Change the log level of error "mips unsub internal error, 4, None". [#1135](https://github.com/XiaoMi/ha_xiaomi_home/pull/1135)
|
||||
- Add necessary logs for distinguishing the set_properties command source. [#1160](https://github.com/XiaoMi/ha_xiaomi_home/pull/1160)
|
||||
|
||||
### Fixed
|
||||
- Fix tofan.airrtc.wk01 thermostat and air conditioner service. [#1160](https://github.com/XiaoMi/ha_xiaomi_home/pull/1160)
|
||||
- Fix mrbond.airer.m1t closing status. [#1134](https://github.com/XiaoMi/ha_xiaomi_home/pull/1134)
|
||||
- Fix the MIoT-Spec-V2 of xiaomi.fan.p69 fan service, ainice.sensor_occupy.3b people number, cykj.hood.jyj22 ventilation switch status, xiaomi.fan.p43 fan level, zhimi.airp.ua1a pm10 density, 090615.switch.x1tpm switch status, dmaker.fan.p33 fan-level. [#1132](https://github.com/XiaoMi/ha_xiaomi_home/pull/1132)
|
||||
- Fix cubee.airrtc.th123e and cubee.airrtc.th123w MIoT-Spec-V2 instance descriptions in Russian.
|
||||
- Fix ijai.vacuum.v1 suction-state value-list descriptions in Chinese.
|
||||
- Fix the misuse of Chinese brackets in multi_lang.json.
|
||||
- The unit of the humidity-range property of xiaomi.aircondition.mt0, xiaomi.aircondition.c35, xiaomi.aircondition.c24 and xiaomi.aircondition.c20 is "none". [#1187](https://github.com/XiaoMi/ha_xiaomi_home/pull/1187)
|
||||
|
||||
## 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
|
||||
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 >
|
||||
|
12
README.md
12
README.md
@ -33,9 +33,11 @@ git checkout v1.0.0
|
||||
|
||||
### 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)
|
||||
|
||||
@ -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
|
||||
|
||||
[](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
|
||||
|
||||
@ -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
|
||||
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||
|
||||
### Update Configurations
|
||||
|
||||
@ -374,7 +376,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
> If you edit `specv2entity.py`, `spec_filter.json` or `multi_lang.json` in the `custom_components/xiaomi_home/miot/specs` directory in your Home Assistant, you need to update the entity conversion rule in the integration's CONFIGURE page to take effect. Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update Entity Conversion Rule
|
||||
> If you edit any files in the `custom_components/xiaomi_home/miot/specs` directory (`spec_filter.py`, `spec_modify.json`, `multi_lang.json`, etc.) in your Home Assistant, you need to update the entity conversion rule in the integration's CONFIGURE page to take effect. Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update entity conversion rules
|
||||
|
||||
## Documents
|
||||
|
||||
|
@ -156,7 +156,8 @@ async def async_setup_entry(
|
||||
device.entity_list[platform].remove(entity)
|
||||
entity_id = device.gen_service_entity_id(
|
||||
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):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
if platform in device.prop_list:
|
||||
|
@ -89,4 +89,8 @@ class BinarySensor(MIoTPropertyEntity, BinarySensorEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""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
|
||||
|
@ -51,10 +51,11 @@ from typing import Any, Optional
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.climate import (
|
||||
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.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||
@ -101,21 +102,19 @@ class FeatureOnOff(MIoTServiceEntity, ClimateEntity):
|
||||
self._prop_on = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'on':
|
||||
if (
|
||||
# The "on" property of the "fan-control" service is not
|
||||
# the on/off feature of the entity.
|
||||
prop.service.name == 'air-conditioner' or
|
||||
prop.service.name == 'heater' or
|
||||
prop.service.name == 'thermostat' or
|
||||
prop.service.name == 'electric-blanket'):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_ON)
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF)
|
||||
self._prop_on = prop
|
||||
|
||||
def _init_on_off(self, service_name: str, prop_name: str) -> None:
|
||||
"""Initialize the on_off feature."""
|
||||
for prop in self.entity_data.props:
|
||||
if prop.name == prop_name and prop.service.name == service_name:
|
||||
if prop.format_ != bool:
|
||||
_LOGGER.error('wrong format %s %s, %s', service_name,
|
||||
prop_name, self.entity_id)
|
||||
continue
|
||||
self._attr_supported_features |= ClimateEntityFeature.TURN_ON
|
||||
self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
|
||||
self._prop_on = prop
|
||||
break
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn on."""
|
||||
@ -134,6 +133,7 @@ class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_target_temp = None
|
||||
self._attr_temperature_unit = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
@ -151,6 +151,10 @@ class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE)
|
||||
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):
|
||||
"""Set the target temperature."""
|
||||
@ -197,6 +201,7 @@ class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.PRESET_MODE)
|
||||
self._prop_mode = prop
|
||||
break
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set the preset mode."""
|
||||
@ -225,6 +230,7 @@ class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
|
||||
self._prop_fan_on = None
|
||||
self._prop_fan_level = None
|
||||
self._fan_mode_map = None
|
||||
self._attr_fan_modes = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
@ -365,6 +371,7 @@ class FeatureTemperature(MIoTServiceEntity, ClimateEntity):
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'temperature':
|
||||
self._prop_env_temperature = prop
|
||||
break
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
@ -387,6 +394,7 @@ class FeatureHumidity(MIoTServiceEntity, ClimateEntity):
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'relative-humidity':
|
||||
self._prop_env_humidity = prop
|
||||
break
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> Optional[float]:
|
||||
@ -418,6 +426,7 @@ class FeatureTargetHumidity(MIoTServiceEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_HUMIDITY)
|
||||
self._prop_target_humidity = prop
|
||||
break
|
||||
|
||||
async def async_set_humidity(self, humidity):
|
||||
"""Set the target humidity."""
|
||||
@ -447,6 +456,8 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
self._attr_icon = 'mdi:radiator'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
# on/off
|
||||
self._init_on_off('heater', 'on')
|
||||
# preset modes
|
||||
self._init_preset_modes('heater', 'heat-level')
|
||||
|
||||
@ -462,6 +473,13 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
return (HVACMode.HEAT if self.get_prop_value(
|
||||
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,
|
||||
FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
|
||||
@ -482,10 +500,12 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._attr_icon = 'mdi:air-conditioner'
|
||||
# on/off
|
||||
self._init_on_off('air-conditioner', 'on')
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = None
|
||||
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:
|
||||
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
@ -549,6 +569,23 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||
prop=self._prop_mode))
|
||||
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:
|
||||
del prop
|
||||
if not isinstance(value, str):
|
||||
@ -620,22 +657,17 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
self._attr_icon = 'mdi:hvac'
|
||||
# hvac modes
|
||||
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:
|
||||
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._hvac_mode_map = {}
|
||||
for item in prop.value_list.items:
|
||||
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
|
||||
elif (HVACMode.AUTO
|
||||
not in list(self._hvac_mode_map.values())):
|
||||
self._hvac_mode_map[item.value] = HVACMode.AUTO
|
||||
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
||||
if HVACMode.OFF in self._attr_hvac_modes:
|
||||
self._prop_mode = prop
|
||||
self._hvac_mode_map[item.value] = HVACMode.OFF
|
||||
break
|
||||
if self._hvac_mode_map:
|
||||
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
|
||||
else:
|
||||
_LOGGER.error('no idle mode, %s', self.entity_id)
|
||||
# preset modes
|
||||
@ -643,7 +675,7 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the target hvac mode."""
|
||||
if self._prop_mode is None:
|
||||
if self._prop_mode is None or hvac_mode != HVACMode.OFF:
|
||||
return
|
||||
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(
|
||||
@ -656,13 +688,12 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
"""The current hvac mode."""
|
||||
if self._prop_mode is None:
|
||||
return None
|
||||
mode_value = self.get_map_value(
|
||||
map_=self._hvac_mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
if mode_value == HVACMode.OFF or mode_value is None:
|
||||
return mode_value
|
||||
return HVACMode.AUTO if (HVACMode.AUTO
|
||||
in self._attr_hvac_modes) else None
|
||||
current_mode = self.get_prop_value(prop=self._prop_mode)
|
||||
if current_mode is None:
|
||||
return None
|
||||
mode_value = self.get_map_value(map_=self._hvac_mode_map,
|
||||
key=current_mode)
|
||||
return HVACMode.OFF if mode_value == HVACMode.OFF else HVACMode.AUTO
|
||||
|
||||
|
||||
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
@ -677,6 +708,8 @@ class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
self._attr_icon = 'mdi:thermostat'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
|
||||
# on/off
|
||||
self._init_on_off('thermostat', 'on')
|
||||
# preset modes
|
||||
self._init_preset_modes('thermostat', 'mode')
|
||||
|
||||
@ -705,6 +738,8 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
||||
self._attr_icon = 'mdi:rug'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
# on/off
|
||||
self._init_on_off('electric-blanket', 'on')
|
||||
# preset modes
|
||||
self._init_preset_modes('electric-blanket', 'mode')
|
||||
|
||||
@ -719,3 +754,10 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
||||
"""The current hvac mode."""
|
||||
return (HVACMode.HEAT if self.get_prop_value(
|
||||
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,27 +565,32 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
home_list = {}
|
||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||
# home list
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
# i18n
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
dev_list = {
|
||||
device['did']: device
|
||||
for device in list(self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
mips_list
|
||||
and group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in dev_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[
|
||||
'homes'][device_source].items():
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get('did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
dev_list = {
|
||||
device['did']: device
|
||||
for device in list(self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
mips_list
|
||||
and group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in dev_list
|
||||
):
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get(
|
||||
'did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
||||
|
||||
self._cc_home_list_show = dict(sorted(home_list.items()))
|
||||
|
||||
@ -660,10 +665,14 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if not home_selected:
|
||||
return await self.__show_homes_select_form(
|
||||
'no_family_selected')
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
if home_id in home_selected:
|
||||
self._home_selected[home_id] = home_info
|
||||
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[
|
||||
'homes'][device_source].items():
|
||||
if home_id in home_selected:
|
||||
self._home_selected[home_id] = home_info
|
||||
self._area_name_rule = user_input.get(
|
||||
'area_name_rule', self._area_name_rule)
|
||||
# Storage device list
|
||||
@ -1420,27 +1429,31 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
home_list = {}
|
||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||
# home list
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
# i18n
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
did_list = {
|
||||
device['did']: device for device in list(
|
||||
self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in did_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[
|
||||
'homes'][device_source].items():
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get(
|
||||
'did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(did_list)} {tip_devices} {tip_central} ]')
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
did_list = {
|
||||
device['did']: device for device in list(
|
||||
self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in did_list
|
||||
):
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get(
|
||||
'did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(did_list)} {tip_devices} {tip_central} ]')
|
||||
# Remove deleted item
|
||||
self._home_selected_list = [
|
||||
home_id for home_id in self._home_selected_list
|
||||
@ -1460,10 +1473,14 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
return await self.__show_homes_select_form('no_family_selected')
|
||||
self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
|
||||
self._home_selected = {}
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
if home_id in self._home_selected_list:
|
||||
self._home_selected[home_id] = home_info
|
||||
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[
|
||||
'homes'][device_source].items():
|
||||
if home_id in self._home_selected_list:
|
||||
self._home_selected[home_id] = home_info
|
||||
# Get device list
|
||||
device_list: dict = {
|
||||
did: dev_info
|
||||
|
@ -161,7 +161,7 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'opening', 'open', 'up'}:
|
||||
self._prop_status_opening.append(item.value)
|
||||
elif item.name in {'closing', 'close', 'down'}:
|
||||
elif item.name in {'closing', 'close', 'down', 'dowm'}:
|
||||
self._prop_status_closing.append(item.value)
|
||||
elif item.name in {'stop', 'stopped', 'pause'}:
|
||||
self._prop_status_stop.append(item.value)
|
||||
|
@ -236,6 +236,9 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the percentage of the fan speed."""
|
||||
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:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_fan_level,
|
||||
@ -249,9 +252,6 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
value=int(percentage_to_ranged_value(
|
||||
low_high_range=(self._speed_min, self._speed_max),
|
||||
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:
|
||||
await self.set_property_async(prop=self._prop_on, value=False)
|
||||
|
||||
|
@ -52,23 +52,22 @@ from typing import Any, Optional
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.humidifier import (
|
||||
HumidifierEntity,
|
||||
HumidifierDeviceClass,
|
||||
HumidifierEntityFeature
|
||||
)
|
||||
from homeassistant.components.humidifier import (HumidifierEntity,
|
||||
HumidifierDeviceClass,
|
||||
HumidifierEntityFeature,
|
||||
HumidifierAction)
|
||||
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
@ -82,8 +81,8 @@ async def async_setup_entry(
|
||||
Humidifier(miot_device=miot_device, entity_data=data))
|
||||
for data in miot_device.entity_list.get('dehumidifier', []):
|
||||
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
|
||||
new_entities.append(Humidifier(
|
||||
miot_device=miot_device, entity_data=data))
|
||||
new_entities.append(
|
||||
Humidifier(miot_device=miot_device, entity_data=data))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
@ -99,9 +98,8 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
|
||||
_mode_map: dict[Any, Any]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
) -> None:
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the Humidifier."""
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._attr_device_class = entity_data.device_class
|
||||
@ -130,12 +128,10 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
# mode
|
||||
elif prop.name == 'mode':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_available_modes = list(
|
||||
self._mode_map.values())
|
||||
self._attr_available_modes = list(self._mode_map.values())
|
||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||
self._prop_mode = prop
|
||||
# relative-humidity
|
||||
@ -152,33 +148,45 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set new target humidity."""
|
||||
await self.set_property_async(
|
||||
prop=self._prop_target_humidity, value=humidity)
|
||||
if self._prop_target_humidity is None:
|
||||
return
|
||||
await self.set_property_async(prop=self._prop_target_humidity,
|
||||
value=humidity)
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set new target preset mode."""
|
||||
await self.set_property_async(
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(map_=self._mode_map, value=mode))
|
||||
await self.set_property_async(prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=mode))
|
||||
|
||||
@property
|
||||
def is_on(self) -> Optional[bool]:
|
||||
"""Return if the humidifier is 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
|
||||
def current_humidity(self) -> Optional[int]:
|
||||
"""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
|
||||
def target_humidity(self) -> Optional[int]:
|
||||
"""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
|
||||
def mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode."""
|
||||
return self.get_map_value(
|
||||
map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
return self.get_map_value(map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
|
@ -25,7 +25,7 @@
|
||||
"cryptography",
|
||||
"psutil"
|
||||
],
|
||||
"version": "v0.2.2",
|
||||
"version": "v0.3.3",
|
||||
"zeroconf": [
|
||||
"_miot-central._tcp.local."
|
||||
]
|
||||
|
@ -85,6 +85,11 @@ SUPPORTED_PLATFORMS: list = [
|
||||
'water_heater',
|
||||
]
|
||||
|
||||
UNSUPPORTED_MODELS: list = [
|
||||
'chuangmi.ir.v2',
|
||||
'xiaomi.router.rd03'
|
||||
]
|
||||
|
||||
DEFAULT_CLOUD_SERVER: str = 'cn'
|
||||
CLOUD_SERVERS: dict = {
|
||||
'cn': '中国大陆',
|
||||
|
@ -629,11 +629,14 @@ class MIoTClient:
|
||||
mips = self._mips_local.get(device_gw['group_id'], None)
|
||||
if mips is None:
|
||||
_LOGGER.error(
|
||||
'no gw route, %s, try control throw cloud',
|
||||
'no gateway route, %s, try control through cloud',
|
||||
device_gw)
|
||||
else:
|
||||
result = await mips.set_prop_async(
|
||||
did=did, siid=siid, piid=piid, value=value)
|
||||
_LOGGER.debug(
|
||||
'gateway set prop, %s.%d.%d, %s -> %s',
|
||||
did, siid, piid, value, result)
|
||||
rc = (result or {}).get(
|
||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||
if rc in [0, 1]:
|
||||
@ -646,7 +649,8 @@ class MIoTClient:
|
||||
result = await self._miot_lan.set_prop_async(
|
||||
did=did, siid=siid, piid=piid, value=value)
|
||||
_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(
|
||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||
if rc in [0, 1]:
|
||||
@ -662,7 +666,7 @@ class MIoTClient:
|
||||
{'did': did, 'siid': siid, 'piid': piid, 'value': value}
|
||||
])
|
||||
_LOGGER.debug(
|
||||
'set prop response, %s.%d.%d, %s, result, %s',
|
||||
'cloud set prop, %s.%d.%d, %s -> %s',
|
||||
did, siid, piid, value, result)
|
||||
if result and len(result) == 1:
|
||||
rc = result[0].get(
|
||||
@ -879,16 +883,7 @@ class MIoTClient:
|
||||
sub_from = self._sub_source_list.pop(did, None)
|
||||
# Unsub
|
||||
if sub_from:
|
||||
if sub_from == 'cloud':
|
||||
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)
|
||||
self.__unsub_from(sub_from, did)
|
||||
# Storage
|
||||
await self._storage.save_async(
|
||||
domain='miot_devices',
|
||||
@ -936,6 +931,39 @@ class MIoTClient:
|
||||
delay_sec, lambda: self._main_loop.create_task(
|
||||
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
|
||||
def __update_device_msg_sub(self, did: str) -> None:
|
||||
if did not in self._device_list_cache:
|
||||
@ -967,27 +995,9 @@ class MIoTClient:
|
||||
return
|
||||
# Unsub old
|
||||
if from_old:
|
||||
if from_old == 'cloud':
|
||||
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)
|
||||
self.__unsub_from(from_old, did)
|
||||
# Sub new
|
||||
if from_new == 'cloud':
|
||||
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_from(from_new, did)
|
||||
self._sub_source_list[did] = from_new
|
||||
_LOGGER.info(
|
||||
'device sub changed, %s, from %s to %s', did, from_old, from_new)
|
||||
@ -1344,6 +1354,11 @@ class MIoTClient:
|
||||
"""Update cloud devices.
|
||||
NOTICE: This function will operate the cloud_list
|
||||
"""
|
||||
# MIoT cloud service may not publish the online state updating message
|
||||
# for the BLE device. Assume that all BLE devices are online.
|
||||
for did, info in cloud_list.items():
|
||||
if did.startswith('blt.'):
|
||||
info['online'] = True
|
||||
for did, info in self._device_list_cache.items():
|
||||
if filter_dids and did not in filter_dids:
|
||||
continue
|
||||
|
@ -59,6 +59,7 @@ import aiohttp
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import calc_group_id
|
||||
from .const import (
|
||||
UNSUPPORTED_MODELS,
|
||||
DEFAULT_OAUTH2_API_HOST,
|
||||
MIHOME_HTTP_API_TIMEOUT,
|
||||
OAUTH2_AUTH_URL)
|
||||
@ -444,6 +445,17 @@ class MIoTHttpClient:
|
||||
|
||||
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:
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/homeroom/gethome',
|
||||
@ -499,19 +511,22 @@ class MIoTHttpClient:
|
||||
):
|
||||
more_list = await self.__get_dev_room_page_async(
|
||||
max_id=res_obj['result']['max_id'])
|
||||
for home_id, info in more_list.items():
|
||||
if home_id not in home_infos['homelist']:
|
||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||
continue
|
||||
home_infos['homelist'][home_id]['dids'].extend(info['dids'])
|
||||
for room_id, info in info['room_info'].items():
|
||||
home_infos['homelist'][home_id]['room_info'].setdefault(
|
||||
room_id, {
|
||||
'room_id': room_id,
|
||||
'room_name': '',
|
||||
'dids': []})
|
||||
home_infos['homelist'][home_id]['room_info'][
|
||||
room_id]['dids'].extend(info['dids'])
|
||||
for device_source in ['homelist', 'share_home_list']:
|
||||
for home_id, info in more_list.items():
|
||||
if home_id not in home_infos[device_source]:
|
||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||
continue
|
||||
home_infos[device_source][home_id]['dids'].extend(
|
||||
info['dids'])
|
||||
for room_id, info in info['room_info'].items():
|
||||
home_infos[device_source][home_id][
|
||||
'room_info'].setdefault(
|
||||
room_id, {
|
||||
'room_id': room_id,
|
||||
'room_name': '',
|
||||
'dids': []})
|
||||
home_infos[device_source][home_id]['room_info'][
|
||||
room_id]['dids'].extend(info['dids'])
|
||||
|
||||
return {
|
||||
'uid': uid,
|
||||
@ -559,6 +574,10 @@ class MIoTHttpClient:
|
||||
# were implemented.
|
||||
_LOGGER.info('ignore miwifi.* device, cloud, %s', did)
|
||||
continue
|
||||
if model in UNSUPPORTED_MODELS:
|
||||
_LOGGER.info('ignore unsupported model %s, cloud, %s',
|
||||
model, did)
|
||||
continue
|
||||
device_infos[did] = {
|
||||
'did': did,
|
||||
'uid': device.get('uid', None),
|
||||
@ -651,6 +670,25 @@ class MIoTHttpClient:
|
||||
'room_name': room_name,
|
||||
'group_id': group_id
|
||||
} 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()))
|
||||
results = await self.get_devices_with_dids_async(dids=dids)
|
||||
if results is None:
|
||||
|
@ -345,10 +345,11 @@ class MIoTDevice:
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
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 (
|
||||
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(
|
||||
self, ha_domain: str, spec_name: str, siid: int, piid: int
|
||||
@ -778,8 +779,10 @@ class MIoTDevice:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant.const import UnitOfConductivity # type: ignore
|
||||
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
|
||||
unit_map['mWh'] = UnitOfEnergy.MILLIWATT_HOUR
|
||||
except Exception: # pylint: disable=broad-except
|
||||
unit_map['μS/cm'] = 'μS/cm'
|
||||
unit_map['mWh'] = 'mWh'
|
||||
|
||||
return unit_map.get(spec_unit, None)
|
||||
|
||||
@ -894,7 +897,8 @@ class MIoTServiceEntity(Entity):
|
||||
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
||||
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
||||
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 = (
|
||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||
f'{self.entity_data.spec.description_trans}')
|
||||
@ -1203,10 +1207,9 @@ class MIoTPropertyEntity(Entity):
|
||||
self._attr_available = miot_device.online
|
||||
|
||||
_LOGGER.info(
|
||||
'new miot property entity, %s, %s, %s, %s, %s, %s, %s',
|
||||
'new miot property entity, %s, %s, %s, %s, %s',
|
||||
self.miot_device.name, self._attr_name, spec.platform,
|
||||
spec.device_class, self.entity_id, self._value_range,
|
||||
self._value_list)
|
||||
spec.device_class, self.entity_id)
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[DeviceInfo]:
|
||||
|
@ -226,7 +226,7 @@ class _MIoTLanDevice:
|
||||
def gen_packet(
|
||||
self, out_buffer: bytearray, clear_data: dict, did: str, offset: 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()
|
||||
padded_data = padder.update(clear_bytes) + padder.finalize()
|
||||
if len(padded_data) + self.OT_HEADER_LEN > len(out_buffer):
|
||||
|
@ -60,6 +60,7 @@ from typing import Any, Callable, Optional, final, Coroutine
|
||||
|
||||
from paho.mqtt.client import (
|
||||
MQTT_ERR_SUCCESS,
|
||||
MQTT_ERR_NO_CONN,
|
||||
MQTT_ERR_UNKNOWN,
|
||||
Client,
|
||||
MQTTv5,
|
||||
@ -67,7 +68,7 @@ from paho.mqtt.client import (
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import MIoTMatcher
|
||||
from .const import MIHOME_MQTT_KEEPALIVE
|
||||
from .const import UNSUPPORTED_MODELS, MIHOME_MQTT_KEEPALIVE
|
||||
from .miot_error import MIoTErrorCode, MIoTMipsError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -215,7 +216,7 @@ class _MipsClient(ABC):
|
||||
MQTT_INTERVAL_S = 1
|
||||
MIPS_QOS: int = 2
|
||||
UINT32_MAX: int = 0xFFFFFFFF
|
||||
MIPS_RECONNECT_INTERVAL_MIN: float = 30
|
||||
MIPS_RECONNECT_INTERVAL_MIN: float = 10
|
||||
MIPS_RECONNECT_INTERVAL_MAX: float = 600
|
||||
MIPS_SUB_PATCH: int = 300
|
||||
MIPS_SUB_INTERVAL: float = 1
|
||||
@ -533,7 +534,7 @@ class _MipsClient(ABC):
|
||||
return
|
||||
try:
|
||||
result, mid = self._mqtt.unsubscribe(topic=topic)
|
||||
if result == MQTT_ERR_SUCCESS:
|
||||
if (result == MQTT_ERR_SUCCESS) or (result == MQTT_ERR_NO_CONN):
|
||||
self.log_debug(
|
||||
f'mips unsub internal success, {result}, {mid}, {topic}')
|
||||
return
|
||||
@ -640,6 +641,7 @@ class _MipsClient(ABC):
|
||||
if not self._mqtt.is_connected():
|
||||
return
|
||||
self.log_info(f'mips connect, {flags}, {rc}, {props}')
|
||||
self.__reset_reconnect_time()
|
||||
self._mqtt_state = True
|
||||
self._internal_loop.call_soon(
|
||||
self._on_mips_connect, rc, props)
|
||||
@ -821,7 +823,7 @@ class _MipsClient(ABC):
|
||||
self._internal_loop.stop()
|
||||
|
||||
def __get_next_reconnect_time(self) -> float:
|
||||
if self._mips_reconnect_interval == 0:
|
||||
if self._mips_reconnect_interval < self.MIPS_RECONNECT_INTERVAL_MIN:
|
||||
self._mips_reconnect_interval = self.MIPS_RECONNECT_INTERVAL_MIN
|
||||
else:
|
||||
self._mips_reconnect_interval = min(
|
||||
@ -829,6 +831,9 @@ class _MipsClient(ABC):
|
||||
self.MIPS_RECONNECT_INTERVAL_MAX)
|
||||
return self._mips_reconnect_interval
|
||||
|
||||
def __reset_reconnect_time(self) -> None:
|
||||
self._mips_reconnect_interval = 0
|
||||
|
||||
|
||||
class MipsCloudClient(_MipsClient):
|
||||
"""MIoT Pub/Sub Cloud Client."""
|
||||
@ -1215,7 +1220,7 @@ class MipsLocalClient(_MipsClient):
|
||||
or 'eiid' 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
|
||||
if 'arguments' not in msg:
|
||||
self.log_info('wrong event msg, %s', payload)
|
||||
@ -1360,6 +1365,9 @@ class MipsLocalClient(_MipsClient):
|
||||
if name is None or urn is None or model is None:
|
||||
self.log_error(f'invalid device info, {did}, {info}')
|
||||
continue
|
||||
if model in UNSUPPORTED_MODELS:
|
||||
self.log_info(f'unsupported model, {model}, {did}')
|
||||
continue
|
||||
device_list[did] = {
|
||||
'did': did,
|
||||
'name': name,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,172 +1,290 @@
|
||||
{
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"de": {
|
||||
"service:001": "Geräteinformationen",
|
||||
"service:001:property:003": "Geräte-ID",
|
||||
"service:001:property:005": "Seriennummer (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Netzwerk geändert",
|
||||
"service:002:event:002": "Netzwerk geändert",
|
||||
"service:002:property:001": "Zugriffsmethode",
|
||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
||||
"service:002:property:002": "IP-Adresse",
|
||||
"service:002:property:003": "WiFi-Netzwerkname",
|
||||
"service:002:property:004": "Aktuelle Zeit",
|
||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
||||
"service:003": "Anzeigelampe",
|
||||
"service:003:property:001": "Schalter",
|
||||
"service:004": "Virtueller Dienst",
|
||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
||||
"service:004:property:001": "Ereignisname"
|
||||
},
|
||||
"en": {
|
||||
"service:001": "Device Information",
|
||||
"service:001:property:003": "Device ID",
|
||||
"service:001:property:005": "Serial Number (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Network Changed",
|
||||
"service:002:event:002": "Network Changed",
|
||||
"service:002:property:001": "Access Method",
|
||||
"service:002:property:001:valuelist:000": "Wired",
|
||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
||||
"service:002:property:002": "IP Address",
|
||||
"service:002:property:003": "WiFi Network Name",
|
||||
"service:002:property:004": "Current Time",
|
||||
"service:002:property:005": "DHCP Server MAC Address",
|
||||
"service:003": "Indicator Light",
|
||||
"service:003:property:001": "Switch",
|
||||
"service:004": "Virtual Service",
|
||||
"service:004:action:001": "Generate Virtual Event",
|
||||
"service:004:event:001": "Virtual Event Occurred",
|
||||
"service:004:property:001": "Event Name"
|
||||
},
|
||||
"es": {
|
||||
"service:001": "Información del dispositivo",
|
||||
"service:001:property:003": "ID del dispositivo",
|
||||
"service:001:property:005": "Número de serie (SN)",
|
||||
"service:002": "Puerta de enlace",
|
||||
"service:002:event:001": "Cambio de red",
|
||||
"service:002:event:002": "Cambio de red",
|
||||
"service:002:property:001": "Método de acceso",
|
||||
"service:002:property:001:valuelist:000": "Cableado",
|
||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
||||
"service:002:property:002": "Dirección IP",
|
||||
"service:002:property:003": "Nombre de red WiFi",
|
||||
"service:002:property:004": "Hora actual",
|
||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
||||
"service:003": "Luz indicadora",
|
||||
"service:003:property:001": "Interruptor",
|
||||
"service:004": "Servicio virtual",
|
||||
"service:004:action:001": "Generar evento virtual",
|
||||
"service:004:event:001": "Ocurrió un evento virtual",
|
||||
"service:004:property:001": "Nombre del evento"
|
||||
},
|
||||
"fr": {
|
||||
"service:001": "Informations sur l'appareil",
|
||||
"service:001:property:003": "ID de l'appareil",
|
||||
"service:001:property:005": "Numéro de série (SN)",
|
||||
"service:002": "Passerelle",
|
||||
"service:002:event:001": "Changement de réseau",
|
||||
"service:002:event:002": "Changement de réseau",
|
||||
"service:002:property:001": "Méthode d'accès",
|
||||
"service:002:property:001:valuelist:000": "Câblé",
|
||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
||||
"service:002:property:002": "Adresse IP",
|
||||
"service:002:property:003": "Nom du réseau WiFi",
|
||||
"service:002:property:004": "Heure actuelle",
|
||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
||||
"service:003": "Voyant lumineux",
|
||||
"service:003:property:001": "Interrupteur",
|
||||
"service:004": "Service virtuel",
|
||||
"service:004:action:001": "Générer un événement virtuel",
|
||||
"service:004:event:001": "Événement virtuel survenu",
|
||||
"service:004:property:001": "Nom de l'événement"
|
||||
},
|
||||
"ja": {
|
||||
"service:001": "デバイス情報",
|
||||
"service:001:property:003": "デバイスID",
|
||||
"service:001:property:005": "シリアル番号 (SN)",
|
||||
"service:002": "ゲートウェイ",
|
||||
"service:002:event:001": "ネットワークが変更されました",
|
||||
"service:002:event:002": "ネットワークが変更されました",
|
||||
"service:002:property:001": "アクセス方法",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
||||
"service:002:property:002": "IPアドレス",
|
||||
"service:002:property:003": "WiFiネットワーク名",
|
||||
"service:002:property:004": "現在の時間",
|
||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
||||
"service:003": "インジケータライト",
|
||||
"service:003:property:001": "スイッチ",
|
||||
"service:004": "バーチャルサービス",
|
||||
"service:004:action:001": "バーチャルイベントを生成",
|
||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
||||
"service:004:property:001": "イベント名"
|
||||
},
|
||||
"ru": {
|
||||
"service:001": "Информация об устройстве",
|
||||
"service:001:property:003": "ID устройства",
|
||||
"service:001:property:005": "Серийный номер (SN)",
|
||||
"service:002": "Шлюз",
|
||||
"service:002:event:001": "Сеть изменена",
|
||||
"service:002:event:002": "Сеть изменена",
|
||||
"service:002:property:001": "Метод доступа",
|
||||
"service:002:property:001:valuelist:000": "Проводной",
|
||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
||||
"service:002:property:002": "IP Адрес",
|
||||
"service:002:property:003": "Название WiFi сети",
|
||||
"service:002:property:004": "Текущее время",
|
||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
||||
"service:003": "Световой индикатор",
|
||||
"service:003:property:001": "Переключатель",
|
||||
"service:004": "Виртуальная служба",
|
||||
"service:004:action:001": "Создать виртуальное событие",
|
||||
"service:004:event:001": "Произошло виртуальное событие",
|
||||
"service:004:property:001": "Название события"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"service:001": "設備信息",
|
||||
"service:001:property:003": "設備ID",
|
||||
"service:001:property:005": "序號 (SN)",
|
||||
"service:002": "網關",
|
||||
"service:002:event:001": "網路發生變化",
|
||||
"service:002:event:002": "網路發生變化",
|
||||
"service:002:property:001": "接入方式",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G 無線",
|
||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
||||
"service:002:property:002": "IP地址",
|
||||
"service:002:property:003": "WiFi網路名稱",
|
||||
"service:002:property:004": "當前時間",
|
||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
||||
"service:003": "指示燈",
|
||||
"service:003:property:001": "開關",
|
||||
"service:004": "虛擬服務",
|
||||
"service:004:action:001": "產生虛擬事件",
|
||||
"service:004:event:001": "虛擬事件發生",
|
||||
"service:004:property:001": "事件名稱"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||
"en": {
|
||||
"service:011": "Right Button On and Off",
|
||||
"service:011:property:001": "Right Button On and Off",
|
||||
"service:015:action:001": "Left Button Identify",
|
||||
"service:016:action:001": "Middle Button Identify",
|
||||
"service:017:action:001": "Right Button Identify"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:015:action:001": "左键确认",
|
||||
"service:016:action:001": "中键确认",
|
||||
"service:017:action:001": "右键确认"
|
||||
}
|
||||
"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10": {
|
||||
"en": {
|
||||
"service:003:property:001:valuelist:000": "Idle",
|
||||
"service:003:property:001:valuelist:001": "Dry"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"de": {
|
||||
"service:001": "Geräteinformationen",
|
||||
"service:001:property:003": "Geräte-ID",
|
||||
"service:001:property:005": "Seriennummer (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Netzwerk geändert",
|
||||
"service:002:event:002": "Netzwerk geändert",
|
||||
"service:002:property:001": "Zugriffsmethode",
|
||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
||||
"service:002:property:002": "IP-Adresse",
|
||||
"service:002:property:003": "WiFi-Netzwerkname",
|
||||
"service:002:property:004": "Aktuelle Zeit",
|
||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
||||
"service:003": "Anzeigelampe",
|
||||
"service:003:property:001": "Schalter",
|
||||
"service:004": "Virtueller Dienst",
|
||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
||||
"service:004:property:001": "Ereignisname"
|
||||
},
|
||||
"en": {
|
||||
"service:001": "Device Information",
|
||||
"service:001:property:003": "Device ID",
|
||||
"service:001:property:005": "Serial Number (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Network Changed",
|
||||
"service:002:event:002": "Network Changed",
|
||||
"service:002:property:001": "Access Method",
|
||||
"service:002:property:001:valuelist:000": "Wired",
|
||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
||||
"service:002:property:002": "IP Address",
|
||||
"service:002:property:003": "WiFi Network Name",
|
||||
"service:002:property:004": "Current Time",
|
||||
"service:002:property:005": "DHCP Server MAC Address",
|
||||
"service:003": "Indicator Light",
|
||||
"service:003:property:001": "Switch",
|
||||
"service:004": "Virtual Service",
|
||||
"service:004:action:001": "Generate Virtual Event",
|
||||
"service:004:event:001": "Virtual Event Occurred",
|
||||
"service:004:property:001": "Event Name"
|
||||
},
|
||||
"es": {
|
||||
"service:001": "Información del dispositivo",
|
||||
"service:001:property:003": "ID del dispositivo",
|
||||
"service:001:property:005": "Número de serie (SN)",
|
||||
"service:002": "Puerta de enlace",
|
||||
"service:002:event:001": "Cambio de red",
|
||||
"service:002:event:002": "Cambio de red",
|
||||
"service:002:property:001": "Método de acceso",
|
||||
"service:002:property:001:valuelist:000": "Cableado",
|
||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
||||
"service:002:property:002": "Dirección IP",
|
||||
"service:002:property:003": "Nombre de red WiFi",
|
||||
"service:002:property:004": "Hora actual",
|
||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
||||
"service:003": "Luz indicadora",
|
||||
"service:003:property:001": "Interruptor",
|
||||
"service:004": "Servicio virtual",
|
||||
"service:004:action:001": "Generar evento virtual",
|
||||
"service:004:event:001": "Ocurrió un evento virtual",
|
||||
"service:004:property:001": "Nombre del evento"
|
||||
},
|
||||
"fr": {
|
||||
"service:001": "Informations sur l'appareil",
|
||||
"service:001:property:003": "ID de l'appareil",
|
||||
"service:001:property:005": "Numéro de série (SN)",
|
||||
"service:002": "Passerelle",
|
||||
"service:002:event:001": "Changement de réseau",
|
||||
"service:002:event:002": "Changement de réseau",
|
||||
"service:002:property:001": "Méthode d'accès",
|
||||
"service:002:property:001:valuelist:000": "Câblé",
|
||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
||||
"service:002:property:002": "Adresse IP",
|
||||
"service:002:property:003": "Nom du réseau WiFi",
|
||||
"service:002:property:004": "Heure actuelle",
|
||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
||||
"service:003": "Voyant lumineux",
|
||||
"service:003:property:001": "Interrupteur",
|
||||
"service:004": "Service virtuel",
|
||||
"service:004:action:001": "Générer un événement virtuel",
|
||||
"service:004:event:001": "Événement virtuel survenu",
|
||||
"service:004:property:001": "Nom de l'événement"
|
||||
},
|
||||
"ja": {
|
||||
"service:001": "デバイス情報",
|
||||
"service:001:property:003": "デバイスID",
|
||||
"service:001:property:005": "シリアル番号 (SN)",
|
||||
"service:002": "ゲートウェイ",
|
||||
"service:002:event:001": "ネットワークが変更されました",
|
||||
"service:002:event:002": "ネットワークが変更されました",
|
||||
"service:002:property:001": "アクセス方法",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
||||
"service:002:property:002": "IPアドレス",
|
||||
"service:002:property:003": "WiFiネットワーク名",
|
||||
"service:002:property:004": "現在の時間",
|
||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
||||
"service:003": "インジケータライト",
|
||||
"service:003:property:001": "スイッチ",
|
||||
"service:004": "バーチャルサービス",
|
||||
"service:004:action:001": "バーチャルイベントを生成",
|
||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
||||
"service:004:property:001": "イベント名"
|
||||
},
|
||||
"ru": {
|
||||
"service:001": "Информация об устройстве",
|
||||
"service:001:property:003": "ID устройства",
|
||||
"service:001:property:005": "Серийный номер (SN)",
|
||||
"service:002": "Шлюз",
|
||||
"service:002:event:001": "Сеть изменена",
|
||||
"service:002:event:002": "Сеть изменена",
|
||||
"service:002:property:001": "Метод доступа",
|
||||
"service:002:property:001:valuelist:000": "Проводной",
|
||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
||||
"service:002:property:002": "IP Адрес",
|
||||
"service:002:property:003": "Название WiFi сети",
|
||||
"service:002:property:004": "Текущее время",
|
||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
||||
"service:003": "Световой индикатор",
|
||||
"service:003:property:001": "Переключатель",
|
||||
"service:004": "Виртуальная служба",
|
||||
"service:004:action:001": "Создать виртуальное событие",
|
||||
"service:004:event:001": "Произошло виртуальное событие",
|
||||
"service:004:property:001": "Название события"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"service:001": "設備信息",
|
||||
"service:001:property:003": "設備ID",
|
||||
"service:001:property:005": "序號 (SN)",
|
||||
"service:002": "網關",
|
||||
"service:002:event:001": "網路發生變化",
|
||||
"service:002:event:002": "網路發生變化",
|
||||
"service:002:property:001": "接入方式",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G 無線",
|
||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
||||
"service:002:property:002": "IP地址",
|
||||
"service:002:property:003": "WiFi網路名稱",
|
||||
"service:002:property:004": "當前時間",
|
||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
||||
"service:003": "指示燈",
|
||||
"service:003:property:001": "開關",
|
||||
"service:004": "虛擬服務",
|
||||
"service:004:action:001": "產生虛擬事件",
|
||||
"service:004:event: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:090615-x1tpm": {
|
||||
"en": {
|
||||
"service:027:property:001": "Fan Switch",
|
||||
"service:027:property:003": "Light Switch",
|
||||
"service:027:property:004": "Fan and Light Switch"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||
"en": {
|
||||
"service:011": "Right Button On and Off",
|
||||
"service:011:property:001": "Right Button On and Off",
|
||||
"service:015:action:001": "Left Button Identify",
|
||||
"service:016:action:001": "Middle Button Identify",
|
||||
"service:017:action:001": "Right Button Identify"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:015:action:001": "左键确认",
|
||||
"service:016:action:001": "中键确认",
|
||||
"service:017:action:001": "右键确认"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e": {
|
||||
"ru": {
|
||||
"service:002": "термостат",
|
||||
"service:002:property:001": "выключатель",
|
||||
"service:002:property:002": "режим нагрузки",
|
||||
"service:002:property:002:valuelist:000": "без подогрева",
|
||||
"service:002:property:002:valuelist:001": "нагрев",
|
||||
"service:002:property:003": "неисправность",
|
||||
"service:002:property:003:valuelist:000": "ошибка датчика",
|
||||
"service:002:property:003:valuelist:001": "без ошибок",
|
||||
"service:002:property:003:valuelist:002": "защита от высоких температур",
|
||||
"service:002:property:003:valuelist:003": "криогенная защита",
|
||||
"service:002:property:004": "режим",
|
||||
"service:002:property:004:valuelist:000": "ручной режим",
|
||||
"service:002:property:004:valuelist:001": "домашний режим",
|
||||
"service:002:property:004:valuelist:002": "режим выхода из дома",
|
||||
"service:002:property:004:valuelist:003": "автоматический режим",
|
||||
"service:002:property:004:valuelist:004": "Режим сна",
|
||||
"service:002:property:005": "температура цели",
|
||||
"service:002:property:007": "текущая температура",
|
||||
"service:004": "Пользовательские услуги",
|
||||
"service:004:property:001": "детский замок",
|
||||
"service:004:property:002": "тип датчика",
|
||||
"service:004:property:002:valuelist:000": "внутренний датчик",
|
||||
"service:004:property:002:valuelist:001": "выносной датчик",
|
||||
"service:004:property:002:valuelist:002": "встроенный и внешний датчик",
|
||||
"service:004:property:003": "пусковая разность температур",
|
||||
"service:004:property:004": "компенсационная температура",
|
||||
"service:004:property:005": "температура выносного датчика",
|
||||
"service:004:property:006": "максимальная температура цели",
|
||||
"service:004:property:007": "минимальная температура цели "
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123w": {
|
||||
"ru": {
|
||||
"service:002": "термостат",
|
||||
"service:002:property:001": "выключатель",
|
||||
"service:002:property:002": "режим нагрузки",
|
||||
"service:002:property:002:valuelist:000": "нагрев",
|
||||
"service:002:property:002:valuelist:001": "без подогрева",
|
||||
"service:002:property:003": "неисправность",
|
||||
"service:002:property:003:valuelist:000": "без ошибок",
|
||||
"service:002:property:003:valuelist:001": "ошибка датчика",
|
||||
"service:002:property:003:valuelist:002": "защита от высоких температур",
|
||||
"service:002:property:003:valuelist:003": "криогенная защита",
|
||||
"service:002:property:004": "режим",
|
||||
"service:002:property:004:valuelist:000": "ручной режим",
|
||||
"service:002:property:004:valuelist:001": "домашний режим",
|
||||
"service:002:property:004:valuelist:002": "режим выхода из дома",
|
||||
"service:002:property:004:valuelist:003": "автоматический режим",
|
||||
"service:002:property:004:valuelist:004": "Режим сна",
|
||||
"service:002:property:005": "температура цели",
|
||||
"service:002:property:007": "текущая температура",
|
||||
"service:004": "Пользовательские услуги",
|
||||
"service:004:property:001": "детский замок",
|
||||
"service:004:property:002": "тип датчика",
|
||||
"service:004:property:002:valuelist:000": "внутренний датчик",
|
||||
"service:004:property:002:valuelist:001": "выносной датчик",
|
||||
"service:004:property:002:valuelist:002": "встроенный и внешний датчик",
|
||||
"service:004:property:003": "пусковая разность температур",
|
||||
"service:004:property:004": "компенсационная температура",
|
||||
"service:004:property:005": "температура выносного датчика",
|
||||
"service:004:property:006": "максимальная температура цели",
|
||||
"service:004:property:007": "минимальная температура цели "
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01": {
|
||||
"en": {
|
||||
"service:002": "Thermostat",
|
||||
"service:002:property:002": "Air Conditioner Mode",
|
||||
"service:004": "Air Conditioner"
|
||||
},
|
||||
"zh_cn": {
|
||||
"service:002": "地暖",
|
||||
"service:004": "空调"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:ijai-v1": {
|
||||
"zh_cn": {
|
||||
"service:007:property:005:valuelist:000": "安静",
|
||||
"service:007:property:005:valuelist:001": "标准",
|
||||
"service:007:property:005:valuelist:002": "中档",
|
||||
"service:007:property:005:valuelist:003": "强力"
|
||||
}
|
||||
}
|
||||
}
|
246
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
246
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
@ -0,0 +1,246 @@
|
||||
{
|
||||
"urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:1": [
|
||||
{
|
||||
"iid": 4,
|
||||
"type": "urn:miot-spec-v2:service:environment:0000780A:090615-ktf:1",
|
||||
"description": "Environment",
|
||||
"properties": [
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:property:temperature:00000020:090615-ktf:1",
|
||||
"description": "Temperature",
|
||||
"format": "float",
|
||||
"access": [
|
||||
"read",
|
||||
"notify"
|
||||
],
|
||||
"unit": "celsius",
|
||||
"value-range": [
|
||||
-30,
|
||||
100,
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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:thermostat:0000A031:tofan-wk01:1:0000C822": [
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:service:thermostat:0000784A:tofan-wk01:1",
|
||||
"description": "Thermostat",
|
||||
"properties": [
|
||||
{
|
||||
"iid": 1,
|
||||
"type": "urn:miot-spec-v2:property:on:00000006:tofan-wk01:1",
|
||||
"description": "Switch Status",
|
||||
"format": "bool",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:property:mode-a:00000008:tofan-wk01:1",
|
||||
"description": "Mode",
|
||||
"format": "uint8",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
],
|
||||
"value-list": [
|
||||
{
|
||||
"value": 0,
|
||||
"description": "Auto"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"description": "Cool"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"description": "Heat"
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"description": "Fan"
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"description": "Dry"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"iid": 3,
|
||||
"type": "urn:miot-spec-v2:property:fault:00000009:tofan-wk01:1",
|
||||
"description": "Device Fault",
|
||||
"format": "uint8",
|
||||
"access": [
|
||||
"read",
|
||||
"notify"
|
||||
],
|
||||
"value-list": [
|
||||
{
|
||||
"value": 0,
|
||||
"description": "No Faults"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"iid": 4,
|
||||
"type": "urn:miot-spec-v2:property:target-temperature:00000021:tofan-wk01:1",
|
||||
"description": "Target Temperature",
|
||||
"format": "uint8",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
],
|
||||
"unit": "celsius",
|
||||
"value-range": [
|
||||
16,
|
||||
35,
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"iid": 1,
|
||||
"type": "urn:miot-spec-v2:action:toggle:00002811:tofan-wk01:1",
|
||||
"description": "Toggle",
|
||||
"in": [],
|
||||
"out": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"iid": 4,
|
||||
"type": "urn:miot-spec-v2:service:air-conditioner:0000780F:tofan-wk01:1",
|
||||
"description": "Air Conditioner",
|
||||
"properties": [
|
||||
{
|
||||
"iid": 1,
|
||||
"type": "urn:miot-spec-v2:property:on:00000006:tofan-wk01:1",
|
||||
"description": "Switch Status",
|
||||
"format": "bool",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
]
|
||||
},
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:property:target-temperature:00000021:tofan-wk01:1",
|
||||
"description": "Target Temperature",
|
||||
"format": "uint8",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
],
|
||||
"unit": "celsius",
|
||||
"value-range": [
|
||||
16,
|
||||
32,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"iid": 3,
|
||||
"type": "urn:miot-spec-v2:property:fan-level:00000016:tofan-wk01:1",
|
||||
"description": "Fan Level",
|
||||
"format": "uint8",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
],
|
||||
"value-list": [
|
||||
{
|
||||
"value": 0,
|
||||
"description": "Auto"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"description": "Low"
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"description": "Medium"
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"description": "High"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:
|
||||
services:
|
||||
- '4'
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||
properties:
|
||||
- 9.*
|
||||
@ -5,6 +8,9 @@ urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||
- 15.*
|
||||
services:
|
||||
- '10'
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:
|
||||
properties:
|
||||
- '3.2'
|
||||
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
||||
properties:
|
||||
- '5.1'
|
||||
@ -38,6 +44,7 @@ urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1:
|
||||
services:
|
||||
- '1'
|
||||
- '5'
|
||||
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03:
|
||||
urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:
|
||||
services:
|
||||
- '*'
|
||||
- '2'
|
||||
- '4'
|
||||
|
@ -1,3 +1,125 @@
|
||||
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-c20:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c20:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c24:1
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:1
|
||||
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-conditioner:0000A004:xiaomi-m9:7: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1
|
||||
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-s1:1:
|
||||
prop.2.5:
|
||||
name: voc-density
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:1: urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:3
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:2: urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:3
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ua1a:3:
|
||||
prop.3.5:
|
||||
expr: (src_value*6)
|
||||
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-m1t:1:
|
||||
prop.2.3:
|
||||
name: current-position-a
|
||||
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-p33:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
prop.2.6:
|
||||
name: fan-level
|
||||
access:
|
||||
- read
|
||||
- write
|
||||
- notify
|
||||
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-p43:1:
|
||||
prop.2.2:
|
||||
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:xiaomi-p69:1:0000D062:
|
||||
prop.2.4:
|
||||
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:
|
||||
prop.2.1:
|
||||
name: access-mode
|
||||
@ -14,25 +136,95 @@ urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
||||
- 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: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:hood:0000A01B:cykj-jyj22:1: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:2: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3:
|
||||
prop.3.1:
|
||||
name: on-ventilation
|
||||
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
|
||||
value-list:
|
||||
- value: 0
|
||||
description: open
|
||||
- value: 1
|
||||
description: closed
|
||||
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:ainice-3b:1: urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2:
|
||||
prop.2.8:
|
||||
name: people-number
|
||||
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:
|
||||
name: power-consumption
|
||||
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
|
||||
expr: round(src_value*6/1000000, 3)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1:
|
||||
prop.2.2:
|
||||
name: power-consumption
|
||||
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:
|
||||
prop.11.1:
|
||||
name: power-consumption
|
||||
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: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:
|
||||
prop.3.1:
|
||||
name: electric-power
|
||||
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:
|
||||
prop.2.1:
|
||||
name: download-speed
|
||||
@ -42,37 +234,64 @@ urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
||||
name: upload-speed
|
||||
icon: mdi:upload
|
||||
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:090615-x1tpm:1:0000D042:
|
||||
prop.27.3:
|
||||
name: light-on
|
||||
prop.27.4:
|
||||
name: light-fan-on
|
||||
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:
|
||||
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: 1
|
||||
description: low
|
||||
- value: 128
|
||||
description: medium
|
||||
- value: 255
|
||||
description: high
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:mike-2:1:
|
||||
prop.3.1:
|
||||
name: mode-a
|
||||
prop.3.11:
|
||||
name: mode-b
|
||||
prop.3.12:
|
||||
name: mode-c
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
description: '1'
|
||||
- value: 2
|
||||
description: '2'
|
||||
- value: 3
|
||||
description: '3'
|
||||
- value: 4
|
||||
description: '4'
|
||||
- value: 5
|
||||
description: '5'
|
||||
- value: 6
|
||||
description: '6'
|
||||
- value: 7
|
||||
description: '7'
|
||||
- value: 8
|
||||
description: '8'
|
||||
- value: 9
|
||||
description: '9'
|
||||
- value: 10
|
||||
description: '10'
|
||||
- value: 11
|
||||
description: '11'
|
||||
- value: 12
|
||||
description: '12'
|
||||
- value: 13
|
||||
description: '13'
|
||||
- value: 14
|
||||
description: '14'
|
||||
- value: 15
|
||||
description: '15'
|
||||
- value: 16
|
||||
description: '16'
|
||||
urn:miot-spec-v2:device: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 SensorStateClass
|
||||
from homeassistant.components.event import EventDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
@ -388,6 +389,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
'fan-control': 'fan',
|
||||
'ceiling-fan': 'fan',
|
||||
'air-fresh': 'fan',
|
||||
'air-purifier': 'fan',
|
||||
'water-heater': {
|
||||
'required': {
|
||||
'properties': {
|
||||
@ -395,7 +397,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'on', 'temperature', 'target-temperature', 'mode'}
|
||||
'properties': {'temperature', 'target-temperature', 'mode'}
|
||||
},
|
||||
'entity': 'water_heater'
|
||||
},
|
||||
@ -454,12 +456,28 @@ SPEC_PROP_TRANS_MAP: dict = {
|
||||
'format': {'int', 'float'},
|
||||
'access': {'read'}
|
||||
},
|
||||
'binary_sensor': {
|
||||
'format': {'bool', 'int'},
|
||||
'access': {'read'}
|
||||
},
|
||||
'switch': {
|
||||
'format': {'bool'},
|
||||
'access': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'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': {
|
||||
'device_class': SensorDeviceClass.TEMPERATURE,
|
||||
'entity': 'sensor',
|
||||
@ -506,7 +524,11 @@ SPEC_PROP_TRANS_MAP: dict = {
|
||||
'entity': 'sensor',
|
||||
'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': {
|
||||
'device_class': SensorDeviceClass.BATTERY,
|
||||
'entity': 'sensor',
|
||||
@ -560,12 +582,6 @@ SPEC_PROP_TRANS_MAP: dict = {
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'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(
|
||||
unit_sets)[0] if unit_sets else None
|
||||
# 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
|
||||
# Set state_class
|
||||
if spec.state_class:
|
||||
|
@ -52,25 +52,22 @@ from typing import Any, Optional
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.water_heater import (
|
||||
STATE_ON,
|
||||
STATE_OFF,
|
||||
ATTR_TEMPERATURE,
|
||||
WaterHeaterEntity,
|
||||
WaterHeaterEntityFeature
|
||||
)
|
||||
from homeassistant.components.water_heater import (STATE_ON, STATE_OFF,
|
||||
ATTR_TEMPERATURE,
|
||||
WaterHeaterEntity,
|
||||
WaterHeaterEntityFeature)
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
@ -79,8 +76,8 @@ async def async_setup_entry(
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for data in miot_device.entity_list.get('water_heater', []):
|
||||
new_entities.append(WaterHeater(
|
||||
miot_device=miot_device, entity_data=data))
|
||||
new_entities.append(
|
||||
WaterHeater(miot_device=miot_device, entity_data=data))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
@ -95,12 +92,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
|
||||
_mode_map: Optional[dict[Any, Any]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
) -> None:
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the Water heater."""
|
||||
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._prop_on = None
|
||||
self._prop_temp = None
|
||||
@ -117,14 +113,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
# temperature
|
||||
if prop.name == 'temperature':
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'invalid temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
_LOGGER.error('invalid temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
if 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
|
||||
# target-temperature
|
||||
if prop.name == 'target-temperature':
|
||||
@ -133,9 +126,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
'invalid target-temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._attr_target_temperature_low = prop.value_range.min_
|
||||
self._attr_target_temperature_high = prop.value_range.max_
|
||||
self._attr_precision = prop.value_range.step
|
||||
self._attr_min_temp = prop.value_range.min_
|
||||
self._attr_max_temp = prop.value_range.max_
|
||||
self._attr_target_temperature_step = prop.value_range.step
|
||||
if self._attr_temperature_unit is None and prop.external_unit:
|
||||
self._attr_temperature_unit = prop.external_unit
|
||||
self._attr_supported_features |= (
|
||||
@ -144,17 +137,15 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
# mode
|
||||
if prop.name == 'mode':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_operation_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= (
|
||||
WaterHeaterEntityFeature.OPERATION_MODE)
|
||||
self._prop_mode = prop
|
||||
if not self._attr_operation_list:
|
||||
self._attr_operation_list = [STATE_ON]
|
||||
self._attr_operation_list.append(STATE_OFF)
|
||||
self._attr_supported_features |= WaterHeaterEntityFeature.OPERATION_MODE
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn the water heater on."""
|
||||
@ -165,16 +156,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
await self.set_property_async(prop=self._prop_on, value=False)
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the temperature the water heater should heat water to."""
|
||||
if not self._prop_target_temp:
|
||||
return
|
||||
await self.set_property_async(
|
||||
prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE])
|
||||
"""Set the target 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:
|
||||
"""Set the operation mode of the water heater.
|
||||
Must be in the operation_list.
|
||||
"""
|
||||
"""Set the operation mode of the water heater."""
|
||||
if operation_mode == STATE_OFF:
|
||||
await self.set_property_async(prop=self._prop_on, value=False)
|
||||
return
|
||||
@ -182,32 +169,32 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
await self.set_property_async(prop=self._prop_on, value=True)
|
||||
return
|
||||
if self.get_prop_value(prop=self._prop_on) is False:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_on, value=True, write_ha_state=False)
|
||||
await self.set_property_async(
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=operation_mode))
|
||||
await self.set_property_async(prop=self._prop_on,
|
||||
value=True,
|
||||
write_ha_state=False)
|
||||
await self.set_property_async(prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map,
|
||||
value=operation_mode))
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature."""
|
||||
return self.get_prop_value(prop=self._prop_temp)
|
||||
"""The current temperature."""
|
||||
return (None if self._prop_temp is None else self.get_prop_value(
|
||||
prop=self._prop_temp))
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the target temperature."""
|
||||
if not self._prop_target_temp:
|
||||
return None
|
||||
return self.get_prop_value(prop=self._prop_target_temp)
|
||||
"""The target temperature."""
|
||||
return (None if self._prop_target_temp is None else self.get_prop_value(
|
||||
prop=self._prop_target_temp))
|
||||
|
||||
@property
|
||||
def current_operation(self) -> Optional[str]:
|
||||
"""Return the current mode."""
|
||||
"""The current mode."""
|
||||
if self.get_prop_value(prop=self._prop_on) is False:
|
||||
return STATE_OFF
|
||||
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
||||
return STATE_ON
|
||||
return self.get_map_value(
|
||||
map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
return (None if self._prop_mode is None else self.get_map_value(
|
||||
map_=self._mode_map, key=self.get_prop_value(prop=self._prop_mode)))
|
||||
|
@ -33,9 +33,11 @@ git checkout v1.0.0
|
||||
|
||||
### 方法 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) 手动安装
|
||||
|
||||
@ -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/config_flow_start/?domain=xiaomi_home)
|
||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||
|
||||
### 添加 MIoT 设备
|
||||
|
||||
@ -59,7 +61,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
||||
|
||||
方法:[设置 > 设备与服务 > 已配置 > 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>:property:<piid> # 属性
|
||||
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的值
|
||||
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的索引值
|
||||
service:<siid>:event:<eiid> # 事件
|
||||
service:<siid>:action:<aiid> # 方法
|
||||
```
|
||||
@ -376,7 +378,7 @@ siid、piid、eiid、aiid、value 均为十进制三位整数。
|
||||
}
|
||||
```
|
||||
|
||||
> 在 Home Assistant 中修改了 `custom_components/xiaomi_home/miot/specs` 路径下的 `specv2entity.py`、`spec_filter.json`、`multi_lang.json` 文件的内容,需要在集成配置中更新实体转换规则才能生效。方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新实体转换规则
|
||||
> 在 Home Assistant 中修改了 `custom_components/xiaomi_home/miot/specs` 路径下的任何文件(`spec_filter.py`、`spec_modify.json`、`multi_lang.json`等),需要在集成配置中更新实体转换规则才能生效。方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新实体转换规则
|
||||
|
||||
## 文档
|
||||
|
||||
|
@ -15,14 +15,15 @@ TRANS_RELATIVE_PATH: str = path.join(
|
||||
MIOT_I18N_RELATIVE_PATH: str = path.join(
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
||||
SPEC_BOOL_TRANS_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||
SPEC_FILTER_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
||||
ROOT_PATH, '../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(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
||||
|
||||
|
||||
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:
|
||||
return json.load(file)
|
||||
except FileNotFoundError:
|
||||
_LOGGER.info('%s is not found.', file_path,)
|
||||
_LOGGER.info('%s is not found.', file_path)
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
_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:
|
||||
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]:
|
||||
@ -56,9 +57,12 @@ def load_yaml_file(file_path: str) -> Optional[dict]:
|
||||
|
||||
def save_yaml_file(file_path: str, data: dict) -> None:
|
||||
with open(file_path, 'w', encoding='utf-8') as file:
|
||||
yaml.safe_dump(
|
||||
data, file, default_flow_style=False,
|
||||
allow_unicode=True, indent=2, sort_keys=False)
|
||||
yaml.safe_dump(data,
|
||||
file,
|
||||
default_flow_style=False,
|
||||
allow_unicode=True,
|
||||
indent=2,
|
||||
sort_keys=False)
|
||||
|
||||
|
||||
def dict_str_str(d: dict) -> bool:
|
||||
@ -132,13 +136,120 @@ def bool_trans(d: dict) -> bool:
|
||||
for key, trans in d['translate'].items():
|
||||
trans_keys: set[str] = set(trans.keys())
|
||||
if set(trans.keys()) != default_keys:
|
||||
_LOGGER.info(
|
||||
'bool trans inconsistent, %s, %s, %s',
|
||||
key, default_keys, trans_keys)
|
||||
_LOGGER.info('bool trans inconsistent, %s, %s, %s', 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 True
|
||||
|
||||
|
||||
def spec_modify(data: dict) -> bool:
|
||||
"""dict[str, str | dict[str, dict]]"""
|
||||
if not isinstance(data, dict):
|
||||
@ -159,25 +270,22 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
|
||||
_LOGGER.info('invalid type')
|
||||
return False
|
||||
if dict1.keys() != dict2.keys():
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, %s, %s', dict1.keys(), dict2.keys())
|
||||
_LOGGER.info('inconsistent key values, %s, %s', dict1.keys(),
|
||||
dict2.keys())
|
||||
return False
|
||||
for key in dict1:
|
||||
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
||||
if not compare_dict_structure(dict1[key], dict2[key]):
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, dict, %s', key)
|
||||
_LOGGER.info('inconsistent key values, dict, %s', key)
|
||||
return False
|
||||
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
|
||||
if not all(
|
||||
isinstance(i, type(j))
|
||||
for i, j in zip(dict1[key], dict2[key])):
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, list, %s', key)
|
||||
_LOGGER.info('inconsistent key values, list, %s', key)
|
||||
return False
|
||||
elif not isinstance(dict1[key], type(dict2[key])):
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, type, %s', key)
|
||||
_LOGGER.info('inconsistent key values, type, %s', key)
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -200,6 +308,16 @@ def sort_spec_filter(file_path: str):
|
||||
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):
|
||||
filter_data = load_yaml_file(file_path=file_path)
|
||||
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'
|
||||
|
||||
|
||||
@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
|
||||
def test_spec_modify():
|
||||
data = load_yaml_file(SPEC_MODIFY_FILE)
|
||||
@ -255,7 +389,8 @@ def test_miot_lang_integrity():
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from miot.const import INTEGRATION_LANGUAGES
|
||||
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))
|
||||
assert len(translations_names) == len(integration_lang_list)
|
||||
assert translations_names == set(integration_lang_list)
|
||||
@ -271,21 +406,18 @@ def test_miot_lang_integrity():
|
||||
default_dict = load_json_file(
|
||||
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
||||
for name in list(integration_lang_list)[1:]:
|
||||
compare_dict = load_json_file(
|
||||
path.join(TRANS_RELATIVE_PATH, name))
|
||||
compare_dict = load_json_file(path.join(TRANS_RELATIVE_PATH, name))
|
||||
if not compare_dict_structure(default_dict, compare_dict):
|
||||
_LOGGER.info(
|
||||
'compare_dict_structure failed /translations, %s', name)
|
||||
_LOGGER.info('compare_dict_structure failed /translations, %s',
|
||||
name)
|
||||
assert False
|
||||
# Check i18n files structure
|
||||
default_dict = load_json_file(
|
||||
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
||||
for name in list(integration_lang_list)[1:]:
|
||||
compare_dict = load_json_file(
|
||||
path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||
compare_dict = load_json_file(path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||
if not compare_dict_structure(default_dict, compare_dict):
|
||||
_LOGGER.info(
|
||||
'compare_dict_structure failed /miot/i18n, %s', name)
|
||||
_LOGGER.info('compare_dict_structure failed /miot/i18n, %s', name)
|
||||
assert False
|
||||
|
||||
|
||||
@ -303,12 +435,27 @@ def test_miot_data_sort():
|
||||
f'{SPEC_BOOL_TRANS_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_FILTER_FILE)) == json.dumps(
|
||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||
f'{SPEC_FILTER_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_FILTER_FILE)) == json.dumps(
|
||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
||||
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
|
||||
@ -319,6 +466,11 @@ def test_sort_spec_data():
|
||||
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
||||
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
||||
_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)
|
||||
save_yaml_file(file_path=SPEC_MODIFY_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_MODIFY_FILE)
|
||||
|
Reference in New Issue
Block a user