mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-07-18 21:49:20 +08:00
Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
4c2e10038c | |||
9afc62f39a | |||
b46805b92c | |||
a43447ef61 | |||
e5165f34da | |||
9fbbb26d33 | |||
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 |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Bug Report / 报告问题
|
name: Bug Report / 报告问题
|
||||||
description: Create a report to help us improve. / 报告问题以帮助我们改进
|
description: Create a report to help us improve. / 报告问题以帮助我们改进
|
||||||
body:
|
body:
|
||||||
- type: input
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the Bug / 描述问题
|
label: Describe the Bug / 描述问题
|
||||||
description: |
|
description: |
|
||||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@ -1,8 +1,69 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
## v0.3.4
|
||||||
|
### Added
|
||||||
|
- Exclude the unsupported device models. [#1205](https://github.com/XiaoMi/ha_xiaomi_home/pull/1205)
|
||||||
|
### Changed
|
||||||
|
- Subscribe the BLE device upstream messages even though the device is offline. [#1207](https://github.com/XiaoMi/ha_xiaomi_home/pull/1207)
|
||||||
|
- Record "opening", "closing" and "closed" status of the airer service that occur frequently and do not record "stop" status for the cover entity. [#1235](https://github.com/XiaoMi/ha_xiaomi_home/pull/1235)
|
||||||
|
- Modify README about spec_filter.yaml and the event attributes. [#1237](https://github.com/XiaoMi/ha_xiaomi_home/pull/1237)
|
||||||
|
### Fixed
|
||||||
|
- Fix the reconnect delay time to be reset when the client is connected to the broker. [#1200](https://github.com/XiaoMi/ha_xiaomi_home/pull/1200)
|
||||||
|
- Fix the HA warning in the logs related to vacuum state setting. [#694](https://github.com/XiaoMi/ha_xiaomi_home/pull/694)
|
||||||
|
- Fix the operation mode when the device does not have a mode property. [#1199](https://github.com/XiaoMi/ha_xiaomi_home/pull/1199)
|
||||||
|
- Fix 090615.aircondition.ktf environment temperature. [#1210](https://github.com/XiaoMi/ha_xiaomi_home/pull/1210)
|
||||||
|
- Fix a missing variable in translation it.json. [#1215](https://github.com/XiaoMi/ha_xiaomi_home/pull/1215)
|
||||||
|
- Fix yutai.plug.fsov8m power consumption and ignore bjkcz.curtain.kczble curtain status. [#1236](https://github.com/XiaoMi/ha_xiaomi_home/pull/1236)
|
||||||
|
|
||||||
|
## 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
|
## v0.2.4
|
||||||
### Added
|
### 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)
|
- 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
|
### 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)
|
- 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)
|
- 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)
|
||||||
|
58
README.md
58
README.md
@ -33,9 +33,11 @@ git checkout v1.0.0
|
|||||||
|
|
||||||
### Method 2: [HACS](https://hacs.xyz/)
|
### Method 2: [HACS](https://hacs.xyz/)
|
||||||
|
|
||||||
HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > Xiaomi Home in New or Available for download section of HACS > DOWNLOAD
|
One-click installation from HACS:
|
||||||
|
|
||||||
> Xiaomi Home has not been added to the HACS store as a default yet. It's coming soon.
|
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)
|
||||||
|
|
||||||
|
Or, HACS > In the search box, type **Xiaomi Home** > Click **Xiaomi Home**, getting into the detail page > DOWNLOAD
|
||||||
|
|
||||||
### Method 3: Manually installation via [Samba](https://github.com/home-assistant/addons/tree/master/samba) / [FTPS](https://github.com/hassio-addons/addon-ftp)
|
### Method 3: Manually installation via [Samba](https://github.com/home-assistant/addons/tree/master/samba) / [FTPS](https://github.com/hassio-addons/addon-ftp)
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ Download and copy `custom_components/xiaomi_home` folder to `config/custom_compo
|
|||||||
|
|
||||||
[Settings > Devices & services > ADD INTEGRATION](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > Search `Xiaomi Home` > NEXT > Click here to login > Sign in with Xiaomi account
|
[Settings > Devices & services > ADD INTEGRATION](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > Search `Xiaomi Home` > NEXT > Click here to login > Sign in with Xiaomi account
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||||
|
|
||||||
### Add MIoT Devices
|
### Add MIoT Devices
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ After a Xiaomi account login and its user configuration are completed, you can c
|
|||||||
|
|
||||||
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > ADD HUB > NEXT > Click here to login > Sign in with Xiaomi account
|
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > ADD HUB > NEXT > Click here to login > Sign in with Xiaomi account
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||||
|
|
||||||
### Update Configurations
|
### Update Configurations
|
||||||
|
|
||||||
@ -153,6 +155,8 @@ In MIoT-Spec-V2 protocol, a product is defined as a device. A device contains se
|
|||||||
|
|
||||||
MIoT-Spec-V2 event is transformed to Event entity in Home Assistant. The event's parameters are also passed to entity's `_trigger_event`.
|
MIoT-Spec-V2 event is transformed to Event entity in Home Assistant. The event's parameters are also passed to entity's `_trigger_event`.
|
||||||
|
|
||||||
|
MIoT-Spec-V2 event's arguments field is the list of parameters of the event. The list elements represent the piid of the property in the same service. For example, the [MIoT-Spec-V2](http://poc.miot-spec.srv/miot-spec-v2/instance?type=urn:miot-spec-v2:device:remote-control:0000A021:xiaomi-mcn002:1:0000D057) of the Xiaomi Wireless Double-key Switch contains the siid=2 Switch Sensor service. The eiid=1014 Long Press event of the service is triggered when a button is long pressed. The debug level log will print `Press and hold, attributes: {'Button Type': 1}`. This is an example log that the button type is 1, which means the right button is long pressed.
|
||||||
|
|
||||||
- Action
|
- Action
|
||||||
|
|
||||||
| in | Entity in Home Assistant |
|
| in | Entity in Home Assistant |
|
||||||
@ -287,39 +291,37 @@ The value of the event instance name indicates `_attr_device_class` of the Home
|
|||||||
|
|
||||||
### MIoT-Spec-V2 Filter
|
### MIoT-Spec-V2 Filter
|
||||||
|
|
||||||
`spec_filter.json` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity.
|
`spec_filter.yaml` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity.
|
||||||
|
|
||||||
The format of `spec_filter.json` is as follows.
|
The format of `spec_filter.json` is as follows.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
{
|
<MIoT-Spec-V2 device instance urn without the version field>:
|
||||||
"<MIoT-Spec-V2 device instance>":{
|
services: list<service_iid: str>
|
||||||
"services": list<service_iid: str>,
|
properties: list<service_iid.property_iid: str>
|
||||||
"properties": list<service_iid.property_iid: str>,
|
events: list<service_iid.event_iid: str>
|
||||||
"events": list<service_iid.event_iid: str>,
|
actions: list<service_iid.action_iid: str>
|
||||||
"actions": list<service_iid.action_iid: str>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The key of `spec_filter.json` dictionary is the urn excluding the "version" field of the MIoT-Spec-V2 device instance. The firmware of different versions of the same product may be associated with the MIoT-Spec-V2 device instances of different versions. It is required that the MIoT-Spec-V2 instance of a higher version must contain all MIoT-Spec-V2 instances of the lower versions when a vendor defines the MIoT-Spec-V2 of its product on MIoT platform. Thus, the key of `spec_filter.json` does not need to specify the version number of MIoT-Spec-V2 device instance.
|
The key of `spec_filter.yaml` dictionary is the urn excluding the "version" field of the MIoT-Spec-V2 device instance. The firmware of different versions of the same product may be associated with the MIoT-Spec-V2 device instances of different versions. It is required that the MIoT-Spec-V2 instance of a higher version must contain all MIoT-Spec-V2 instances of the lower versions when a vendor defines the MIoT-Spec-V2 of its product on MIoT platform. Thus, the key of `spec_filter.yaml` does not need to specify the version number of MIoT-Spec-V2 device instance.
|
||||||
|
|
||||||
The value of "services", "properties", "events" or "actions" fields under "device instance" is the instance id (iid) of the service, property, event or action that will be ignored in the conversion process. Wildcard matching is supported.
|
The value of "services", "properties", "events" or "actions" fields under "device instance" is the instance id (iid) of the service, property, event or action that will be ignored in the conversion process. Wildcard matching is supported.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
{
|
urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1:
|
||||||
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{
|
services:
|
||||||
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance.
|
- '*' # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2.
|
||||||
},
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
|
||||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
services:
|
||||||
"services": ["3"], # Filter out the service whose iid=3.
|
- '3' # Filter out the siid=3 service.
|
||||||
"properties": ["4.*"] # Filter out all properties in the service whose iid=4.
|
properties:
|
||||||
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service.
|
- '4.*' # Filter out all properties in the siid=4 service.
|
||||||
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service.
|
events:
|
||||||
}
|
- '4.1' # Filter out the eiid=1 event in the siid=4 service.
|
||||||
}
|
actions:
|
||||||
|
- '4.1' # Filter out the aiid=1 action in the siid=4 service.
|
||||||
```
|
```
|
||||||
|
|
||||||
Device information service (urn:miot-spec-v2:service:device-information:00007801) of all devices will never be converted to Home Assistant entity.
|
Device information service (urn:miot-spec-v2:service:device-information:00007801) of all devices will never be converted to Home Assistant entity.
|
||||||
@ -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.yaml`, `spec_modify.yaml`, `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
|
## Documents
|
||||||
|
|
||||||
|
@ -156,7 +156,8 @@ async def async_setup_entry(
|
|||||||
device.entity_list[platform].remove(entity)
|
device.entity_list[platform].remove(entity)
|
||||||
entity_id = device.gen_service_entity_id(
|
entity_id = device.gen_service_entity_id(
|
||||||
ha_domain=platform,
|
ha_domain=platform,
|
||||||
siid=entity.spec.iid) # type: ignore
|
siid=entity.spec.iid,
|
||||||
|
description=entity.spec.description)
|
||||||
if er.async_get(entity_id_or_uuid=entity_id):
|
if er.async_get(entity_id_or_uuid=entity_id):
|
||||||
er.async_remove(entity_id=entity_id)
|
er.async_remove(entity_id=entity_id)
|
||||||
if platform in device.prop_list:
|
if platform in device.prop_list:
|
||||||
|
@ -55,7 +55,7 @@ from homeassistant.const import UnitOfTemperature
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
FAN_ON, FAN_OFF, SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL,
|
FAN_ON, FAN_OFF, SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL,
|
||||||
ATTR_TEMPERATURE, HVACMode, ClimateEntity, ClimateEntityFeature)
|
ATTR_TEMPERATURE, HVACMode, HVACAction, ClimateEntity, ClimateEntityFeature)
|
||||||
|
|
||||||
from .miot.const import DOMAIN
|
from .miot.const import DOMAIN
|
||||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||||
@ -230,6 +230,7 @@ class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
|
|||||||
self._prop_fan_on = None
|
self._prop_fan_on = None
|
||||||
self._prop_fan_level = None
|
self._prop_fan_level = None
|
||||||
self._fan_mode_map = None
|
self._fan_mode_map = None
|
||||||
|
self._attr_fan_modes = None
|
||||||
|
|
||||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||||
# properties
|
# properties
|
||||||
@ -472,6 +473,13 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
|||||||
return (HVACMode.HEAT if self.get_prop_value(
|
return (HVACMode.HEAT if self.get_prop_value(
|
||||||
prop=self._prop_on) else HVACMode.OFF)
|
prop=self._prop_on) else HVACMode.OFF)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> Optional[HVACAction]:
|
||||||
|
"""The current hvac action."""
|
||||||
|
if self.hvac_mode == HVACMode.HEAT:
|
||||||
|
return HVACAction.HEATING
|
||||||
|
return HVACAction.OFF
|
||||||
|
|
||||||
|
|
||||||
class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||||
FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
|
FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
|
||||||
@ -561,6 +569,23 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
|||||||
prop=self._prop_mode))
|
prop=self._prop_mode))
|
||||||
if self._prop_mode else None)
|
if self._prop_mode else None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> Optional[HVACAction]:
|
||||||
|
"""The current hvac action."""
|
||||||
|
if self.hvac_mode is None:
|
||||||
|
return None
|
||||||
|
if self.hvac_mode == HVACMode.OFF:
|
||||||
|
return HVACAction.OFF
|
||||||
|
if self.hvac_mode == HVACMode.FAN_ONLY:
|
||||||
|
return HVACAction.FAN
|
||||||
|
if self.hvac_mode == HVACMode.COOL:
|
||||||
|
return HVACAction.COOLING
|
||||||
|
if self.hvac_mode == HVACMode.HEAT:
|
||||||
|
return HVACAction.HEATING
|
||||||
|
if self.hvac_mode == HVACMode.DRY:
|
||||||
|
return HVACAction.DRYING
|
||||||
|
return HVACAction.IDLE
|
||||||
|
|
||||||
def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None:
|
def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None:
|
||||||
del prop
|
del prop
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
@ -729,3 +754,10 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
|||||||
"""The current hvac mode."""
|
"""The current hvac mode."""
|
||||||
return (HVACMode.HEAT if self.get_prop_value(
|
return (HVACMode.HEAT if self.get_prop_value(
|
||||||
prop=self._prop_on) else HVACMode.OFF)
|
prop=self._prop_on) else HVACMode.OFF)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> Optional[HVACAction]:
|
||||||
|
"""The current hvac action."""
|
||||||
|
if self.hvac_mode == HVACMode.OFF:
|
||||||
|
return HVACAction.OFF
|
||||||
|
return HVACAction.HEATING
|
||||||
|
@ -565,8 +565,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
home_list = {}
|
home_list = {}
|
||||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||||
# home list
|
# home list
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
# i18n
|
# i18n
|
||||||
tip_central = ''
|
tip_central = ''
|
||||||
group_id = home_info.get('group_id', None)
|
group_id = home_info.get('group_id', None)
|
||||||
@ -582,7 +586,8 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
# i18n
|
# i18n
|
||||||
tip_central = self._miot_i18n.translate(
|
tip_central = self._miot_i18n.translate(
|
||||||
key='config.other.found_central_gateway')
|
key='config.other.found_central_gateway')
|
||||||
home_info['central_did'] = mips_list[group_id].get('did', None)
|
home_info['central_did'] = mips_list[group_id].get(
|
||||||
|
'did', None)
|
||||||
home_list[home_id] = (
|
home_list[home_id] = (
|
||||||
f'{home_info["home_name"]} '
|
f'{home_info["home_name"]} '
|
||||||
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
||||||
@ -660,8 +665,12 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if not home_selected:
|
if not home_selected:
|
||||||
return await self.__show_homes_select_form(
|
return await self.__show_homes_select_form(
|
||||||
'no_family_selected')
|
'no_family_selected')
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
if home_id in home_selected:
|
if home_id in home_selected:
|
||||||
self._home_selected[home_id] = home_info
|
self._home_selected[home_id] = home_info
|
||||||
self._area_name_rule = user_input.get(
|
self._area_name_rule = user_input.get(
|
||||||
@ -1420,8 +1429,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
home_list = {}
|
home_list = {}
|
||||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||||
# home list
|
# home list
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
# i18n
|
# i18n
|
||||||
tip_central = ''
|
tip_central = ''
|
||||||
group_id = home_info.get('group_id', None)
|
group_id = home_info.get('group_id', None)
|
||||||
@ -1460,8 +1473,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
return await self.__show_homes_select_form('no_family_selected')
|
return await self.__show_homes_select_form('no_family_selected')
|
||||||
self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
|
self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
|
||||||
self._home_selected = {}
|
self._home_selected = {}
|
||||||
|
for device_source in ['home_list','share_home_list',
|
||||||
|
'separated_shared_list']:
|
||||||
|
if device_source not in self._cc_home_info['homes']:
|
||||||
|
continue
|
||||||
for home_id, home_info in self._cc_home_info[
|
for home_id, home_info in self._cc_home_info[
|
||||||
'homes']['home_list'].items():
|
'homes'][device_source].items():
|
||||||
if home_id in self._home_selected_list:
|
if home_id in self._home_selected_list:
|
||||||
self._home_selected[home_id] = home_info
|
self._home_selected[home_id] = home_info
|
||||||
# Get device list
|
# Get device list
|
||||||
|
@ -46,8 +46,9 @@ off Xiaomi or its affiliates' products.
|
|||||||
Cover entities for Xiaomi Home.
|
Cover entities for Xiaomi Home.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import logging
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -97,7 +98,6 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
_prop_status: Optional[MIoTSpecProperty]
|
_prop_status: Optional[MIoTSpecProperty]
|
||||||
_prop_status_opening: Optional[list[int]]
|
_prop_status_opening: Optional[list[int]]
|
||||||
_prop_status_closing: Optional[list[int]]
|
_prop_status_closing: Optional[list[int]]
|
||||||
_prop_status_stop: Optional[list[int]]
|
|
||||||
_prop_status_closed: Optional[list[int]]
|
_prop_status_closed: Optional[list[int]]
|
||||||
_prop_current_position: Optional[MIoTSpecProperty]
|
_prop_current_position: Optional[MIoTSpecProperty]
|
||||||
_prop_target_position: Optional[MIoTSpecProperty]
|
_prop_target_position: Optional[MIoTSpecProperty]
|
||||||
@ -122,7 +122,6 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
self._prop_status = None
|
self._prop_status = None
|
||||||
self._prop_status_opening = []
|
self._prop_status_opening = []
|
||||||
self._prop_status_closing = []
|
self._prop_status_closing = []
|
||||||
self._prop_status_stop = []
|
|
||||||
self._prop_status_closed = []
|
self._prop_status_closed = []
|
||||||
self._prop_current_position = None
|
self._prop_current_position = None
|
||||||
self._prop_target_position = None
|
self._prop_target_position = None
|
||||||
@ -159,13 +158,21 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
|||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
for item in prop.value_list.items:
|
for item in prop.value_list.items:
|
||||||
if item.name in {'opening', 'open', 'up'}:
|
item_str: str = item.name
|
||||||
|
item_name: str = re.sub(r'[^a-z]', '', item_str)
|
||||||
|
if item_name in {
|
||||||
|
'opening', 'open', 'up', 'uping', 'rise', 'rising'
|
||||||
|
}:
|
||||||
self._prop_status_opening.append(item.value)
|
self._prop_status_opening.append(item.value)
|
||||||
elif item.name in {'closing', 'close', 'down'}:
|
elif item_name in {
|
||||||
|
'closing', 'close', 'down', 'dowm', 'falling',
|
||||||
|
'dropping', 'downing', 'lower'
|
||||||
|
}:
|
||||||
self._prop_status_closing.append(item.value)
|
self._prop_status_closing.append(item.value)
|
||||||
elif item.name in {'stop', 'stopped', 'pause'}:
|
elif item_name in {
|
||||||
self._prop_status_stop.append(item.value)
|
'stopatlowest', 'stoplowerlimit', 'lowerlimitstop',
|
||||||
elif item.name in {'closed'}:
|
'floor', 'lowerlimit'
|
||||||
|
}:
|
||||||
self._prop_status_closed.append(item.value)
|
self._prop_status_closed.append(item.value)
|
||||||
self._prop_status = prop
|
self._prop_status = prop
|
||||||
elif prop.name == 'current-position':
|
elif prop.name == 'current-position':
|
||||||
|
@ -236,6 +236,9 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
async def async_set_percentage(self, percentage: int) -> None:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the percentage of the fan speed."""
|
"""Set the percentage of the fan speed."""
|
||||||
if percentage > 0:
|
if percentage > 0:
|
||||||
|
if not self.is_on:
|
||||||
|
# If the fan is off, turn it on.
|
||||||
|
await self.set_property_async(prop=self._prop_on, value=True)
|
||||||
if self._speed_names:
|
if self._speed_names:
|
||||||
await self.set_property_async(
|
await self.set_property_async(
|
||||||
prop=self._prop_fan_level,
|
prop=self._prop_fan_level,
|
||||||
@ -249,9 +252,6 @@ class Fan(MIoTServiceEntity, FanEntity):
|
|||||||
value=int(percentage_to_ranged_value(
|
value=int(percentage_to_ranged_value(
|
||||||
low_high_range=(self._speed_min, self._speed_max),
|
low_high_range=(self._speed_min, self._speed_max),
|
||||||
percentage=percentage)))
|
percentage=percentage)))
|
||||||
if not self.is_on:
|
|
||||||
# If the fan is off, turn it on.
|
|
||||||
await self.set_property_async(prop=self._prop_on, value=True)
|
|
||||||
else:
|
else:
|
||||||
await self.set_property_async(prop=self._prop_on, value=False)
|
await self.set_property_async(prop=self._prop_on, value=False)
|
||||||
|
|
||||||
|
@ -52,11 +52,10 @@ from typing import Any, Optional
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.humidifier import (
|
from homeassistant.components.humidifier import (HumidifierEntity,
|
||||||
HumidifierEntity,
|
|
||||||
HumidifierDeviceClass,
|
HumidifierDeviceClass,
|
||||||
HumidifierEntityFeature
|
HumidifierEntityFeature,
|
||||||
)
|
HumidifierAction)
|
||||||
|
|
||||||
from .miot.miot_spec import MIoTSpecProperty
|
from .miot.miot_spec import MIoTSpecProperty
|
||||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||||
@ -82,8 +81,8 @@ async def async_setup_entry(
|
|||||||
Humidifier(miot_device=miot_device, entity_data=data))
|
Humidifier(miot_device=miot_device, entity_data=data))
|
||||||
for data in miot_device.entity_list.get('dehumidifier', []):
|
for data in miot_device.entity_list.get('dehumidifier', []):
|
||||||
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
|
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
|
||||||
new_entities.append(Humidifier(
|
new_entities.append(
|
||||||
miot_device=miot_device, entity_data=data))
|
Humidifier(miot_device=miot_device, entity_data=data))
|
||||||
|
|
||||||
if new_entities:
|
if new_entities:
|
||||||
async_add_entities(new_entities)
|
async_add_entities(new_entities)
|
||||||
@ -99,9 +98,8 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
|
|
||||||
_mode_map: dict[Any, Any]
|
_mode_map: dict[Any, Any]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, miot_device: MIoTDevice,
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
entity_data: MIoTEntityData) -> None:
|
||||||
) -> None:
|
|
||||||
"""Initialize the Humidifier."""
|
"""Initialize the Humidifier."""
|
||||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||||
self._attr_device_class = entity_data.device_class
|
self._attr_device_class = entity_data.device_class
|
||||||
@ -130,12 +128,10 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
# mode
|
# mode
|
||||||
elif prop.name == 'mode':
|
elif prop.name == 'mode':
|
||||||
if not prop.value_list:
|
if not prop.value_list:
|
||||||
_LOGGER.error(
|
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||||
'mode value_list is None, %s', self.entity_id)
|
|
||||||
continue
|
continue
|
||||||
self._mode_map = prop.value_list.to_map()
|
self._mode_map = prop.value_list.to_map()
|
||||||
self._attr_available_modes = list(
|
self._attr_available_modes = list(self._mode_map.values())
|
||||||
self._mode_map.values())
|
|
||||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
# relative-humidity
|
# relative-humidity
|
||||||
@ -152,33 +148,45 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
|||||||
|
|
||||||
async def async_set_humidity(self, humidity: int) -> None:
|
async def async_set_humidity(self, humidity: int) -> None:
|
||||||
"""Set new target humidity."""
|
"""Set new target humidity."""
|
||||||
await self.set_property_async(
|
if self._prop_target_humidity is None:
|
||||||
prop=self._prop_target_humidity, value=humidity)
|
return
|
||||||
|
await self.set_property_async(prop=self._prop_target_humidity,
|
||||||
|
value=humidity)
|
||||||
|
|
||||||
async def async_set_mode(self, mode: str) -> None:
|
async def async_set_mode(self, mode: str) -> None:
|
||||||
"""Set new target preset mode."""
|
"""Set new target preset mode."""
|
||||||
await self.set_property_async(
|
await self.set_property_async(prop=self._prop_mode,
|
||||||
prop=self._prop_mode,
|
value=self.get_map_key(
|
||||||
value=self.get_map_key(map_=self._mode_map, value=mode))
|
map_=self._mode_map, value=mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> Optional[bool]:
|
def is_on(self) -> Optional[bool]:
|
||||||
"""Return if the humidifier is on."""
|
"""Return if the humidifier is on."""
|
||||||
return self.get_prop_value(prop=self._prop_on)
|
return self.get_prop_value(prop=self._prop_on)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action(self) -> Optional[HumidifierAction]:
|
||||||
|
"""Return the current status of the device."""
|
||||||
|
if not self.is_on:
|
||||||
|
return HumidifierAction.OFF
|
||||||
|
if self._attr_device_class == HumidifierDeviceClass.HUMIDIFIER:
|
||||||
|
return HumidifierAction.HUMIDIFYING
|
||||||
|
return HumidifierAction.DRYING
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_humidity(self) -> Optional[int]:
|
def current_humidity(self) -> Optional[int]:
|
||||||
"""Return the current humidity."""
|
"""Return the current humidity."""
|
||||||
return self.get_prop_value(prop=self._prop_humidity)
|
return (self.get_prop_value(
|
||||||
|
prop=self._prop_humidity) if self._prop_humidity else None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_humidity(self) -> Optional[int]:
|
def target_humidity(self) -> Optional[int]:
|
||||||
"""Return the target humidity."""
|
"""Return the target humidity."""
|
||||||
return self.get_prop_value(prop=self._prop_target_humidity)
|
return (self.get_prop_value(prop=self._prop_target_humidity)
|
||||||
|
if self._prop_target_humidity else None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode(self) -> Optional[str]:
|
def mode(self) -> Optional[str]:
|
||||||
"""Return the current preset mode."""
|
"""Return the current preset mode."""
|
||||||
return self.get_map_value(
|
return self.get_map_value(map_=self._mode_map,
|
||||||
map_=self._mode_map,
|
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
key=self.get_prop_value(prop=self._prop_mode))
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"cryptography",
|
"cryptography",
|
||||||
"psutil"
|
"psutil"
|
||||||
],
|
],
|
||||||
"version": "v0.2.4",
|
"version": "v0.3.4",
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
"_miot-central._tcp.local."
|
"_miot-central._tcp.local."
|
||||||
]
|
]
|
||||||
|
@ -85,6 +85,11 @@ SUPPORTED_PLATFORMS: list = [
|
|||||||
'water_heater',
|
'water_heater',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
UNSUPPORTED_MODELS: list = [
|
||||||
|
'chuangmi.ir.v2',
|
||||||
|
'xiaomi.router.rd03'
|
||||||
|
]
|
||||||
|
|
||||||
DEFAULT_CLOUD_SERVER: str = 'cn'
|
DEFAULT_CLOUD_SERVER: str = 'cn'
|
||||||
CLOUD_SERVERS: dict = {
|
CLOUD_SERVERS: dict = {
|
||||||
'cn': '中国大陆',
|
'cn': '中国大陆',
|
||||||
|
@ -629,11 +629,14 @@ class MIoTClient:
|
|||||||
mips = self._mips_local.get(device_gw['group_id'], None)
|
mips = self._mips_local.get(device_gw['group_id'], None)
|
||||||
if mips is None:
|
if mips is None:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'no gw route, %s, try control throw cloud',
|
'no gateway route, %s, try control through cloud',
|
||||||
device_gw)
|
device_gw)
|
||||||
else:
|
else:
|
||||||
result = await mips.set_prop_async(
|
result = await mips.set_prop_async(
|
||||||
did=did, siid=siid, piid=piid, value=value)
|
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(
|
rc = (result or {}).get(
|
||||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||||
if rc in [0, 1]:
|
if rc in [0, 1]:
|
||||||
@ -646,7 +649,8 @@ class MIoTClient:
|
|||||||
result = await self._miot_lan.set_prop_async(
|
result = await self._miot_lan.set_prop_async(
|
||||||
did=did, siid=siid, piid=piid, value=value)
|
did=did, siid=siid, piid=piid, value=value)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
'lan set prop, %s, %s, %s -> %s', did, siid, piid, result)
|
'lan set prop, %s.%d.%d, %s -> %s',
|
||||||
|
did, siid, piid, value, result)
|
||||||
rc = (result or {}).get(
|
rc = (result or {}).get(
|
||||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||||
if rc in [0, 1]:
|
if rc in [0, 1]:
|
||||||
@ -662,7 +666,7 @@ class MIoTClient:
|
|||||||
{'did': did, 'siid': siid, 'piid': piid, 'value': value}
|
{'did': did, 'siid': siid, 'piid': piid, 'value': value}
|
||||||
])
|
])
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
'set prop response, %s.%d.%d, %s, result, %s',
|
'cloud set prop, %s.%d.%d, %s -> %s',
|
||||||
did, siid, piid, value, result)
|
did, siid, piid, value, result)
|
||||||
if result and len(result) == 1:
|
if result and len(result) == 1:
|
||||||
rc = result[0].get(
|
rc = result[0].get(
|
||||||
@ -879,16 +883,7 @@ class MIoTClient:
|
|||||||
sub_from = self._sub_source_list.pop(did, None)
|
sub_from = self._sub_source_list.pop(did, None)
|
||||||
# Unsub
|
# Unsub
|
||||||
if sub_from:
|
if sub_from:
|
||||||
if sub_from == 'cloud':
|
self.__unsub_from(sub_from, did)
|
||||||
self._mips_cloud.unsub_prop(did=did)
|
|
||||||
self._mips_cloud.unsub_event(did=did)
|
|
||||||
elif sub_from == 'lan':
|
|
||||||
self._miot_lan.unsub_prop(did=did)
|
|
||||||
self._miot_lan.unsub_event(did=did)
|
|
||||||
elif sub_from in self._mips_local:
|
|
||||||
mips = self._mips_local[sub_from]
|
|
||||||
mips.unsub_prop(did=did)
|
|
||||||
mips.unsub_event(did=did)
|
|
||||||
# Storage
|
# Storage
|
||||||
await self._storage.save_async(
|
await self._storage.save_async(
|
||||||
domain='miot_devices',
|
domain='miot_devices',
|
||||||
@ -936,6 +931,39 @@ class MIoTClient:
|
|||||||
delay_sec, lambda: self._main_loop.create_task(
|
delay_sec, lambda: self._main_loop.create_task(
|
||||||
self.refresh_user_cert_async()))
|
self.refresh_user_cert_async()))
|
||||||
|
|
||||||
|
@final
|
||||||
|
def __unsub_from(self, sub_from: str, did: str) -> None:
|
||||||
|
mips: Any = None
|
||||||
|
if sub_from == 'cloud':
|
||||||
|
mips = self._mips_cloud
|
||||||
|
elif sub_from == 'lan':
|
||||||
|
mips = self._miot_lan
|
||||||
|
elif sub_from in self._mips_local:
|
||||||
|
mips = self._mips_local[sub_from]
|
||||||
|
if mips is not None:
|
||||||
|
try:
|
||||||
|
mips.unsub_prop(did=did)
|
||||||
|
mips.unsub_event(did=did)
|
||||||
|
except RuntimeError as e:
|
||||||
|
if 'Event loop is closed' in str(e):
|
||||||
|
# Ignore unsub exception when loop is closed
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
@final
|
||||||
|
def __sub_from(self, sub_from: str, did: str) -> None:
|
||||||
|
mips = None
|
||||||
|
if sub_from == 'cloud':
|
||||||
|
mips = self._mips_cloud
|
||||||
|
elif sub_from == 'lan':
|
||||||
|
mips = self._miot_lan
|
||||||
|
elif sub_from in self._mips_local:
|
||||||
|
mips = self._mips_local[sub_from]
|
||||||
|
if mips is not None:
|
||||||
|
mips.sub_prop(did=did, handler=self.__on_prop_msg)
|
||||||
|
mips.sub_event(did=did, handler=self.__on_event_msg)
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def __update_device_msg_sub(self, did: str) -> None:
|
def __update_device_msg_sub(self, did: str) -> None:
|
||||||
if did not in self._device_list_cache:
|
if did not in self._device_list_cache:
|
||||||
@ -967,27 +995,9 @@ class MIoTClient:
|
|||||||
return
|
return
|
||||||
# Unsub old
|
# Unsub old
|
||||||
if from_old:
|
if from_old:
|
||||||
if from_old == 'cloud':
|
self.__unsub_from(from_old, did)
|
||||||
self._mips_cloud.unsub_prop(did=did)
|
|
||||||
self._mips_cloud.unsub_event(did=did)
|
|
||||||
elif from_old == 'lan':
|
|
||||||
self._miot_lan.unsub_prop(did=did)
|
|
||||||
self._miot_lan.unsub_event(did=did)
|
|
||||||
elif from_old in self._mips_local:
|
|
||||||
mips = self._mips_local[from_old]
|
|
||||||
mips.unsub_prop(did=did)
|
|
||||||
mips.unsub_event(did=did)
|
|
||||||
# Sub new
|
# Sub new
|
||||||
if from_new == 'cloud':
|
self.__sub_from(from_new, did)
|
||||||
self._mips_cloud.sub_prop(did=did, handler=self.__on_prop_msg)
|
|
||||||
self._mips_cloud.sub_event(did=did, handler=self.__on_event_msg)
|
|
||||||
elif from_new == 'lan':
|
|
||||||
self._miot_lan.sub_prop(did=did, handler=self.__on_prop_msg)
|
|
||||||
self._miot_lan.sub_event(did=did, handler=self.__on_event_msg)
|
|
||||||
elif from_new in self._mips_local:
|
|
||||||
mips = self._mips_local[from_new]
|
|
||||||
mips.sub_prop(did=did, handler=self.__on_prop_msg)
|
|
||||||
mips.sub_event(did=did, handler=self.__on_event_msg)
|
|
||||||
self._sub_source_list[did] = from_new
|
self._sub_source_list[did] = from_new
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
'device sub changed, %s, from %s to %s', did, from_old, from_new)
|
'device sub changed, %s, from %s to %s', did, from_old, from_new)
|
||||||
@ -1344,6 +1354,11 @@ class MIoTClient:
|
|||||||
"""Update cloud devices.
|
"""Update cloud devices.
|
||||||
NOTICE: This function will operate the cloud_list
|
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():
|
for did, info in self._device_list_cache.items():
|
||||||
if filter_dids and did not in filter_dids:
|
if filter_dids and did not in filter_dids:
|
||||||
continue
|
continue
|
||||||
|
@ -59,6 +59,7 @@ import aiohttp
|
|||||||
# pylint: disable=relative-beyond-top-level
|
# pylint: disable=relative-beyond-top-level
|
||||||
from .common import calc_group_id
|
from .common import calc_group_id
|
||||||
from .const import (
|
from .const import (
|
||||||
|
UNSUPPORTED_MODELS,
|
||||||
DEFAULT_OAUTH2_API_HOST,
|
DEFAULT_OAUTH2_API_HOST,
|
||||||
MIHOME_HTTP_API_TIMEOUT,
|
MIHOME_HTTP_API_TIMEOUT,
|
||||||
OAUTH2_AUTH_URL)
|
OAUTH2_AUTH_URL)
|
||||||
@ -444,6 +445,17 @@ class MIoTHttpClient:
|
|||||||
|
|
||||||
return home_list
|
return home_list
|
||||||
|
|
||||||
|
async def get_separated_shared_devices_async(self) -> dict[str, dict]:
|
||||||
|
separated_shared_devices: dict = {}
|
||||||
|
device_list: dict[str, dict] = await self.__get_device_list_page_async(
|
||||||
|
dids=[], start_did=None)
|
||||||
|
for did, value in device_list.items():
|
||||||
|
if value['owner'] is not None and ('userid' in value['owner']) and (
|
||||||
|
'nickname' in value['owner']
|
||||||
|
):
|
||||||
|
separated_shared_devices.setdefault(did, value['owner'])
|
||||||
|
return separated_shared_devices
|
||||||
|
|
||||||
async def get_homeinfos_async(self) -> dict:
|
async def get_homeinfos_async(self) -> dict:
|
||||||
res_obj = await self.__mihome_api_post_async(
|
res_obj = await self.__mihome_api_post_async(
|
||||||
url_path='/app/v2/homeroom/gethome',
|
url_path='/app/v2/homeroom/gethome',
|
||||||
@ -499,18 +511,21 @@ class MIoTHttpClient:
|
|||||||
):
|
):
|
||||||
more_list = await self.__get_dev_room_page_async(
|
more_list = await self.__get_dev_room_page_async(
|
||||||
max_id=res_obj['result']['max_id'])
|
max_id=res_obj['result']['max_id'])
|
||||||
|
for device_source in ['homelist', 'share_home_list']:
|
||||||
for home_id, info in more_list.items():
|
for home_id, info in more_list.items():
|
||||||
if home_id not in home_infos['homelist']:
|
if home_id not in home_infos[device_source]:
|
||||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||||
continue
|
continue
|
||||||
home_infos['homelist'][home_id]['dids'].extend(info['dids'])
|
home_infos[device_source][home_id]['dids'].extend(
|
||||||
|
info['dids'])
|
||||||
for room_id, info in info['room_info'].items():
|
for room_id, info in info['room_info'].items():
|
||||||
home_infos['homelist'][home_id]['room_info'].setdefault(
|
home_infos[device_source][home_id][
|
||||||
|
'room_info'].setdefault(
|
||||||
room_id, {
|
room_id, {
|
||||||
'room_id': room_id,
|
'room_id': room_id,
|
||||||
'room_name': '',
|
'room_name': '',
|
||||||
'dids': []})
|
'dids': []})
|
||||||
home_infos['homelist'][home_id]['room_info'][
|
home_infos[device_source][home_id]['room_info'][
|
||||||
room_id]['dids'].extend(info['dids'])
|
room_id]['dids'].extend(info['dids'])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -559,6 +574,10 @@ class MIoTHttpClient:
|
|||||||
# were implemented.
|
# were implemented.
|
||||||
_LOGGER.info('ignore miwifi.* device, cloud, %s', did)
|
_LOGGER.info('ignore miwifi.* device, cloud, %s', did)
|
||||||
continue
|
continue
|
||||||
|
if model in UNSUPPORTED_MODELS:
|
||||||
|
_LOGGER.info('ignore unsupported model %s, cloud, %s',
|
||||||
|
model, did)
|
||||||
|
continue
|
||||||
device_infos[did] = {
|
device_infos[did] = {
|
||||||
'did': did,
|
'did': did,
|
||||||
'uid': device.get('uid', None),
|
'uid': device.get('uid', None),
|
||||||
@ -651,6 +670,25 @@ class MIoTHttpClient:
|
|||||||
'room_name': room_name,
|
'room_name': room_name,
|
||||||
'group_id': group_id
|
'group_id': group_id
|
||||||
} for did in room_info.get('dids', [])})
|
} for did in room_info.get('dids', [])})
|
||||||
|
separated_shared_devices: dict = (
|
||||||
|
await self.get_separated_shared_devices_async())
|
||||||
|
if separated_shared_devices:
|
||||||
|
homes.setdefault('separated_shared_list', {})
|
||||||
|
for did, owner in separated_shared_devices.items():
|
||||||
|
owner_id = str(owner['userid'])
|
||||||
|
homes['separated_shared_list'].setdefault(owner_id,{
|
||||||
|
'home_name': owner['nickname'],
|
||||||
|
'uid': owner_id,
|
||||||
|
'group_id': 'NotSupport',
|
||||||
|
'room_info': {'shared_device': 'shared_device'}
|
||||||
|
})
|
||||||
|
devices.update({did: {
|
||||||
|
'home_id': owner_id,
|
||||||
|
'home_name': owner['nickname'],
|
||||||
|
'room_id': 'shared_device',
|
||||||
|
'room_name': 'shared_device',
|
||||||
|
'group_id': 'NotSupport'
|
||||||
|
}})
|
||||||
dids = sorted(list(devices.keys()))
|
dids = sorted(list(devices.keys()))
|
||||||
results = await self.get_devices_with_dids_async(dids=dids)
|
results = await self.get_devices_with_dids_async(dids=dids)
|
||||||
if results is None:
|
if results is None:
|
||||||
|
@ -345,10 +345,11 @@ class MIoTDevice:
|
|||||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||||
f'{self._model_strs[-1][:20]}')
|
f'{self._model_strs[-1][:20]}')
|
||||||
|
|
||||||
def gen_service_entity_id(self, ha_domain: str, siid: int) -> str:
|
def gen_service_entity_id(self, ha_domain: str, siid: int,
|
||||||
|
description: str) -> str:
|
||||||
return (
|
return (
|
||||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||||
f'{self._model_strs[-1][:20]}_s_{siid}')
|
f'{self._model_strs[-1][:20]}_s_{siid}_{description}')
|
||||||
|
|
||||||
def gen_prop_entity_id(
|
def gen_prop_entity_id(
|
||||||
self, ha_domain: str, spec_name: str, siid: int, piid: int
|
self, ha_domain: str, spec_name: str, siid: int, piid: int
|
||||||
@ -778,8 +779,10 @@ class MIoTDevice:
|
|||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
from homeassistant.const import UnitOfConductivity # type: ignore
|
from homeassistant.const import UnitOfConductivity # type: ignore
|
||||||
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
|
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
|
||||||
|
unit_map['mWh'] = UnitOfEnergy.MILLIWATT_HOUR
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
unit_map['μS/cm'] = 'μS/cm'
|
unit_map['μS/cm'] = 'μS/cm'
|
||||||
|
unit_map['mWh'] = 'mWh'
|
||||||
|
|
||||||
return unit_map.get(spec_unit, None)
|
return unit_map.get(spec_unit, None)
|
||||||
|
|
||||||
@ -894,7 +897,8 @@ class MIoTServiceEntity(Entity):
|
|||||||
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
||||||
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
||||||
self.entity_id = miot_device.gen_service_entity_id(
|
self.entity_id = miot_device.gen_service_entity_id(
|
||||||
DOMAIN, siid=self.entity_data.spec.iid)
|
DOMAIN, siid=self.entity_data.spec.iid,
|
||||||
|
description=self.entity_data.spec.description)
|
||||||
self._attr_name = (
|
self._attr_name = (
|
||||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||||
f'{self.entity_data.spec.description_trans}')
|
f'{self.entity_data.spec.description_trans}')
|
||||||
@ -1203,10 +1207,9 @@ class MIoTPropertyEntity(Entity):
|
|||||||
self._attr_available = miot_device.online
|
self._attr_available = miot_device.online
|
||||||
|
|
||||||
_LOGGER.info(
|
_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,
|
self.miot_device.name, self._attr_name, spec.platform,
|
||||||
spec.device_class, self.entity_id, self._value_range,
|
spec.device_class, self.entity_id)
|
||||||
self._value_list)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> Optional[DeviceInfo]:
|
def device_info(self) -> Optional[DeviceInfo]:
|
||||||
|
@ -226,7 +226,7 @@ class _MIoTLanDevice:
|
|||||||
def gen_packet(
|
def gen_packet(
|
||||||
self, out_buffer: bytearray, clear_data: dict, did: str, offset: int
|
self, out_buffer: bytearray, clear_data: dict, did: str, offset: int
|
||||||
) -> int:
|
) -> int:
|
||||||
clear_bytes = json.dumps(clear_data).encode('utf-8')
|
clear_bytes = json.dumps(clear_data, ensure_ascii=False).encode('utf-8')
|
||||||
padder = padding.PKCS7(algorithms.AES128.block_size).padder()
|
padder = padding.PKCS7(algorithms.AES128.block_size).padder()
|
||||||
padded_data = padder.update(clear_bytes) + padder.finalize()
|
padded_data = padder.update(clear_bytes) + padder.finalize()
|
||||||
if len(padded_data) + self.OT_HEADER_LEN > len(out_buffer):
|
if len(padded_data) + self.OT_HEADER_LEN > len(out_buffer):
|
||||||
|
@ -60,6 +60,7 @@ from typing import Any, Callable, Optional, final, Coroutine
|
|||||||
|
|
||||||
from paho.mqtt.client import (
|
from paho.mqtt.client import (
|
||||||
MQTT_ERR_SUCCESS,
|
MQTT_ERR_SUCCESS,
|
||||||
|
MQTT_ERR_NO_CONN,
|
||||||
MQTT_ERR_UNKNOWN,
|
MQTT_ERR_UNKNOWN,
|
||||||
Client,
|
Client,
|
||||||
MQTTv5,
|
MQTTv5,
|
||||||
@ -67,7 +68,7 @@ from paho.mqtt.client import (
|
|||||||
|
|
||||||
# pylint: disable=relative-beyond-top-level
|
# pylint: disable=relative-beyond-top-level
|
||||||
from .common import MIoTMatcher
|
from .common import MIoTMatcher
|
||||||
from .const import MIHOME_MQTT_KEEPALIVE
|
from .const import UNSUPPORTED_MODELS, MIHOME_MQTT_KEEPALIVE
|
||||||
from .miot_error import MIoTErrorCode, MIoTMipsError
|
from .miot_error import MIoTErrorCode, MIoTMipsError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -215,7 +216,7 @@ class _MipsClient(ABC):
|
|||||||
MQTT_INTERVAL_S = 1
|
MQTT_INTERVAL_S = 1
|
||||||
MIPS_QOS: int = 2
|
MIPS_QOS: int = 2
|
||||||
UINT32_MAX: int = 0xFFFFFFFF
|
UINT32_MAX: int = 0xFFFFFFFF
|
||||||
MIPS_RECONNECT_INTERVAL_MIN: float = 30
|
MIPS_RECONNECT_INTERVAL_MIN: float = 10
|
||||||
MIPS_RECONNECT_INTERVAL_MAX: float = 600
|
MIPS_RECONNECT_INTERVAL_MAX: float = 600
|
||||||
MIPS_SUB_PATCH: int = 300
|
MIPS_SUB_PATCH: int = 300
|
||||||
MIPS_SUB_INTERVAL: float = 1
|
MIPS_SUB_INTERVAL: float = 1
|
||||||
@ -533,7 +534,7 @@ class _MipsClient(ABC):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
result, mid = self._mqtt.unsubscribe(topic=topic)
|
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(
|
self.log_debug(
|
||||||
f'mips unsub internal success, {result}, {mid}, {topic}')
|
f'mips unsub internal success, {result}, {mid}, {topic}')
|
||||||
return
|
return
|
||||||
@ -640,6 +641,7 @@ class _MipsClient(ABC):
|
|||||||
if not self._mqtt.is_connected():
|
if not self._mqtt.is_connected():
|
||||||
return
|
return
|
||||||
self.log_info(f'mips connect, {flags}, {rc}, {props}')
|
self.log_info(f'mips connect, {flags}, {rc}, {props}')
|
||||||
|
self.__reset_reconnect_time()
|
||||||
self._mqtt_state = True
|
self._mqtt_state = True
|
||||||
self._internal_loop.call_soon(
|
self._internal_loop.call_soon(
|
||||||
self._on_mips_connect, rc, props)
|
self._on_mips_connect, rc, props)
|
||||||
@ -821,7 +823,7 @@ class _MipsClient(ABC):
|
|||||||
self._internal_loop.stop()
|
self._internal_loop.stop()
|
||||||
|
|
||||||
def __get_next_reconnect_time(self) -> float:
|
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
|
self._mips_reconnect_interval = self.MIPS_RECONNECT_INTERVAL_MIN
|
||||||
else:
|
else:
|
||||||
self._mips_reconnect_interval = min(
|
self._mips_reconnect_interval = min(
|
||||||
@ -829,6 +831,9 @@ class _MipsClient(ABC):
|
|||||||
self.MIPS_RECONNECT_INTERVAL_MAX)
|
self.MIPS_RECONNECT_INTERVAL_MAX)
|
||||||
return self._mips_reconnect_interval
|
return self._mips_reconnect_interval
|
||||||
|
|
||||||
|
def __reset_reconnect_time(self) -> None:
|
||||||
|
self._mips_reconnect_interval = 0
|
||||||
|
|
||||||
|
|
||||||
class MipsCloudClient(_MipsClient):
|
class MipsCloudClient(_MipsClient):
|
||||||
"""MIoT Pub/Sub Cloud Client."""
|
"""MIoT Pub/Sub Cloud Client."""
|
||||||
@ -1173,7 +1178,7 @@ class MipsLocalClient(_MipsClient):
|
|||||||
or 'piid' not in msg
|
or 'piid' not in msg
|
||||||
or 'value' not in msg
|
or 'value' not in msg
|
||||||
):
|
):
|
||||||
# self.log_error(f'on_prop_msg, recv unknown msg, {payload}')
|
self.log_info('unknown prop msg, %s', payload)
|
||||||
return
|
return
|
||||||
if handler:
|
if handler:
|
||||||
self.log_debug('local, on properties_changed, %s', payload)
|
self.log_debug('local, on properties_changed, %s', payload)
|
||||||
@ -1360,6 +1365,9 @@ class MipsLocalClient(_MipsClient):
|
|||||||
if name is None or urn is None or model is None:
|
if name is None or urn is None or model is None:
|
||||||
self.log_error(f'invalid device info, {did}, {info}')
|
self.log_error(f'invalid device info, {did}, {info}')
|
||||||
continue
|
continue
|
||||||
|
if model in UNSUPPORTED_MODELS:
|
||||||
|
self.log_info(f'unsupported model, {model}, {did}')
|
||||||
|
continue
|
||||||
device_list[did] = {
|
device_list[did] = {
|
||||||
'did': did,
|
'did': did,
|
||||||
'name': name,
|
'name': name,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10": {
|
||||||
|
"en": {
|
||||||
|
"service:003:property:001:valuelist:000": "Idle",
|
||||||
|
"service:003:property:001:valuelist:001": "Dry"
|
||||||
|
}
|
||||||
|
},
|
||||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||||
"de": {
|
"de": {
|
||||||
"service:001": "Geräteinformationen",
|
"service:001": "Geräteinformationen",
|
||||||
@ -155,6 +161,33 @@
|
|||||||
"service:004:property:001": "事件名稱"
|
"service:004:property:001": "事件名稱"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"urn:miot-spec-v2:device:lock:0000A038:loock-t2pv1": {
|
||||||
|
"zh-Hans": {
|
||||||
|
"service:003:property:1021:valuelist:000": "已上锁",
|
||||||
|
"service:003:property:1021:valuelist:001": "已上锁(童锁)",
|
||||||
|
"service:003:property:1021:valuelist:002": "已上锁(反锁)",
|
||||||
|
"service:003:property:1021:valuelist:003": "已上锁(反锁+童锁)",
|
||||||
|
"service:003:property:1021:valuelist:004": "已开锁",
|
||||||
|
"service:003:property:1021:valuelist:008": "门未关(门超时未关)",
|
||||||
|
"service:003:property:1021:valuelist:012": "门虚掩"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1": {
|
||||||
|
"en": {
|
||||||
|
"service:002:property:001": "Soil Moisture"
|
||||||
|
},
|
||||||
|
"zh-Hans": {
|
||||||
|
"service:002:property:001": "土壤湿度",
|
||||||
|
"service:002:property:003": "光照强度"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"urn:miot-spec-v2:device:switch:0000A003: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": {
|
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||||
"en": {
|
"en": {
|
||||||
"service:011": "Right Button On and Off",
|
"service:011": "Right Button On and Off",
|
||||||
@ -169,19 +202,89 @@
|
|||||||
"service:017:action:001": "右键确认"
|
"service:017:action:001": "右键确认"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10": {
|
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e": {
|
||||||
"en": {
|
"ru": {
|
||||||
"service:003:property:001:valuelist:000": "Idle",
|
"service:002": "термостат",
|
||||||
"service:003:property:001:valuelist:001": "Dry"
|
"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:plant-monitor:0000A030:hhcc-v1": {
|
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123w": {
|
||||||
"en": {
|
"ru": {
|
||||||
"service:002:property:001": "Soil Moisture"
|
"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": "минимальная температура цели "
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"zh-Hans": {
|
"urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01": {
|
||||||
"service:002:property:001": "土壤湿度",
|
"en": {
|
||||||
"service:002:property:003": "光照强度"
|
"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:
|
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||||
properties:
|
properties:
|
||||||
- 9.*
|
- 9.*
|
||||||
@ -5,6 +8,9 @@ urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
|||||||
- 15.*
|
- 15.*
|
||||||
services:
|
services:
|
||||||
- '10'
|
- '10'
|
||||||
|
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:
|
||||||
|
properties:
|
||||||
|
- '3.2'
|
||||||
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
||||||
properties:
|
properties:
|
||||||
- '5.1'
|
- '5.1'
|
||||||
@ -38,6 +44,7 @@ urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1:
|
|||||||
services:
|
services:
|
||||||
- '1'
|
- '1'
|
||||||
- '5'
|
- '5'
|
||||||
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03:
|
urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:
|
||||||
services:
|
services:
|
||||||
- '*'
|
- '2'
|
||||||
|
- '4'
|
||||||
|
@ -1,3 +1,128 @@
|
|||||||
|
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:curtain:0000A00C:bjkcz-kczble:1:0000D031:
|
||||||
|
prop.2.2:
|
||||||
|
name: status-a
|
||||||
|
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:
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
||||||
prop.2.1:
|
prop.2.1:
|
||||||
name: access-mode
|
name: access-mode
|
||||||
@ -14,25 +139,98 @@ urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
|||||||
- notify
|
- notify
|
||||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:2: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:2: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
||||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:3: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:3: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1:
|
urn:miot-spec-v2:device: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:
|
prop.5.1:
|
||||||
name: power-consumption
|
expr: round(src_value*6/1000000, 3)
|
||||||
expr: round(src_value/1000, 3)
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1:
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1:
|
||||||
prop.2.2:
|
prop.2.2:
|
||||||
name: power-consumption
|
name: power-consumption
|
||||||
expr: round(src_value/1000, 3)
|
expr: round(src_value/1000, 3)
|
||||||
|
prop.2.3:
|
||||||
|
expr: round(src_value/10, 1)
|
||||||
|
prop.2.4:
|
||||||
|
unit: mA
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2:
|
||||||
|
prop.2.3:
|
||||||
|
expr: round(src_value/10, 1)
|
||||||
|
prop.2.4:
|
||||||
|
unit: mA
|
||||||
|
prop.3.2:
|
||||||
|
expr: round(src_value/10, 1)
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1:
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1:
|
||||||
prop.11.1:
|
prop.11.1:
|
||||||
name: power-consumption
|
name: power-consumption
|
||||||
expr: round(src_value/100, 2)
|
expr: round(src_value/100, 2)
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1
|
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:giot-v8icm:1:0000C816:
|
||||||
|
prop.4.1:
|
||||||
|
unit: mWh
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:qmi-psv3:1:0000C816:
|
||||||
|
prop.3.3:
|
||||||
|
unit: mV
|
||||||
|
prop.3.4:
|
||||||
|
unit: mA
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:yutai-fsov8m:1:0000C816:
|
||||||
|
prop.4.1:
|
||||||
|
expr: round(src_value/10000, 2)
|
||||||
|
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:1:0000C816: urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816:
|
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816:
|
||||||
prop.3.1:
|
prop.3.1:
|
||||||
name: electric-power
|
name: electric-power
|
||||||
expr: round(src_value/100, 2)
|
expr: round(src_value/100, 2)
|
||||||
|
urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1:1:
|
||||||
|
prop.2.1:
|
||||||
|
name: soil-moisture
|
||||||
|
icon: mdi:watering-can
|
||||||
|
prop.2.2:
|
||||||
|
name: soil-ec
|
||||||
|
icon: mdi:sprout-outline
|
||||||
|
unit: μS/cm
|
||||||
|
urn:miot-spec-v2:device:relay:0000A03D:lumi-c2acn01:1:
|
||||||
|
prop.4.1:
|
||||||
|
unit: kWh
|
||||||
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
||||||
prop.2.1:
|
prop.2.1:
|
||||||
name: download-speed
|
name: download-speed
|
||||||
@ -42,45 +240,22 @@ urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
|||||||
name: upload-speed
|
name: upload-speed
|
||||||
icon: mdi:upload
|
icon: mdi:upload
|
||||||
unit: B/s
|
unit: B/s
|
||||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
urn:miot-spec-v2:device:safe-box:0000A042:loock-v1:1:
|
||||||
prop.2.3:
|
prop.5.1:
|
||||||
value-range:
|
name: contact-state
|
||||||
- 0
|
expr: src_value!=1
|
||||||
- 1
|
urn:miot-spec-v2:device:switch:0000A003:090615-x1tpm:1:0000D042:
|
||||||
- 1
|
prop.27.3:
|
||||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
|
name: light-on
|
||||||
urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
|
prop.27.4:
|
||||||
prop.5.2:
|
name: light-fan-on
|
||||||
value-list:
|
urn:miot-spec-v2:device:switch:0000A003:lxzn-cbcsmj:1:0000D00D:
|
||||||
- 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:
|
prop.3.1:
|
||||||
name: mode-a
|
expr: round(src_value/100, 2)
|
||||||
prop.3.11:
|
prop.3.2:
|
||||||
name: mode-b
|
expr: round(src_value/1000, 2)
|
||||||
prop.3.12:
|
prop.3.3:
|
||||||
name: mode-c
|
expr: round(src_value/10, 1)
|
||||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
|
||||||
prop.2.2:
|
|
||||||
name: fan-level-a
|
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6:
|
|
||||||
prop.10.6:
|
|
||||||
unit: none
|
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
|
||||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
|
||||||
urn:miot-spec-v2:device:airer:0000A00D:mrbond-m33a:1:
|
|
||||||
prop.2.3:
|
|
||||||
name: current-position-a
|
|
||||||
prop.2.11:
|
|
||||||
name: current-position-b
|
|
||||||
urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
|
urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
|
||||||
prop.2.3:
|
prop.2.3:
|
||||||
value-list:
|
value-list:
|
||||||
@ -116,38 +291,13 @@ urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
|
|||||||
description: '15'
|
description: '15'
|
||||||
- value: 16
|
- value: 16
|
||||||
description: '16'
|
description: '16'
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3:
|
|
||||||
prop.5.1:
|
|
||||||
expr: round(src_value*6/1000000, 3)
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2:
|
|
||||||
prop.2.3:
|
|
||||||
expr: round(src_value/10, 1)
|
|
||||||
prop.2.4:
|
|
||||||
unit: mA
|
|
||||||
prop.3.2:
|
|
||||||
expr: round(src_value/10, 1)
|
|
||||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2
|
|
||||||
urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1:1:
|
|
||||||
prop.2.1:
|
|
||||||
name: soil-moisture
|
|
||||||
icon: mdi:watering-can
|
|
||||||
prop.2.2:
|
|
||||||
name: soil-ec
|
|
||||||
icon: mdi:sprout-outline
|
|
||||||
unit: μS/cm
|
|
||||||
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-s1:1:
|
|
||||||
prop.2.5:
|
|
||||||
name: voc-density
|
|
||||||
urn:miot-spec-v2:device:water-purifier:0000A013:roswan-lte01:1:0000D05A:
|
urn:miot-spec-v2:device:water-purifier:0000A013:roswan-lte01:1:0000D05A:
|
||||||
prop.4.1:
|
prop.4.1:
|
||||||
unit: ppm
|
unit: ppm
|
||||||
prop.4.2:
|
prop.4.2:
|
||||||
unit: ppm
|
unit: ppm
|
||||||
urn:miot-spec-v2:device:relay:0000A03D:lumi-c2acn01:1:
|
urn:miot-spec-v2:device:water-purifier:0000A013:yunmi-s20:1:
|
||||||
prop.4.1:
|
prop.4.1:
|
||||||
unit: kWh
|
unit: ppm
|
||||||
urn:miot-spec-v2:device:bath-heater:0000A028:xiaomi-s1:1:
|
prop.4.2:
|
||||||
prop.4.4:
|
unit: ppm
|
||||||
name: fan-level-ventilation
|
|
||||||
|
@ -389,6 +389,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
|||||||
'fan-control': 'fan',
|
'fan-control': 'fan',
|
||||||
'ceiling-fan': 'fan',
|
'ceiling-fan': 'fan',
|
||||||
'air-fresh': 'fan',
|
'air-fresh': 'fan',
|
||||||
|
'air-purifier': 'fan',
|
||||||
'water-heater': {
|
'water-heater': {
|
||||||
'required': {
|
'required': {
|
||||||
'properties': {
|
'properties': {
|
||||||
@ -396,7 +397,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'optional': {
|
'optional': {
|
||||||
'properties': {'on', 'temperature', 'target-temperature', 'mode'}
|
'properties': {'temperature', 'target-temperature', 'mode'}
|
||||||
},
|
},
|
||||||
'entity': 'water_heater'
|
'entity': 'water_heater'
|
||||||
},
|
},
|
||||||
|
@ -113,7 +113,7 @@
|
|||||||
},
|
},
|
||||||
"config_options": {
|
"config_options": {
|
||||||
"title": "Opzioni di Configurazione",
|
"title": "Opzioni di Configurazione",
|
||||||
"description": "### Ciao, {nick_name}\r\n\r\nID Xiaomi: {uid}\r\nRegione di Login Corrente: {cloud_server}\r\n\r\nSeleziona le opzioni che desideri configurare, poi clicca AVANTI.",
|
"description": "### Ciao, {nick_name}\r\n\r\nID Xiaomi: {uid}\r\nRegione di Login Corrente: {cloud_server}\r\nID istanza di integrazione: {instance_id}\r\n\r\nSeleziona le opzioni che desideri configurare, poi clicca AVANTI.",
|
||||||
"data": {
|
"data": {
|
||||||
"integration_language": "Lingua dell'Integrazione",
|
"integration_language": "Lingua dell'Integrazione",
|
||||||
"update_user_info": "Aggiorna le informazioni dell'utente",
|
"update_user_info": "Aggiorna le informazioni dell'utente",
|
||||||
|
@ -47,21 +47,18 @@ Vacuum entities for Xiaomi Home.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.vacuum import (
|
from homeassistant.components.vacuum import (StateVacuumEntity,
|
||||||
StateVacuumEntity,
|
VacuumEntityFeature)
|
||||||
VacuumEntityFeature
|
|
||||||
)
|
|
||||||
|
|
||||||
from .miot.const import DOMAIN
|
from .miot.const import DOMAIN
|
||||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||||
from .miot.miot_spec import (
|
from .miot.miot_spec import (MIoTSpecAction, MIoTSpecProperty)
|
||||||
MIoTSpecAction,
|
|
||||||
MIoTSpecProperty)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -99,10 +96,12 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
_status_map: Optional[dict[int, str]]
|
_status_map: Optional[dict[int, str]]
|
||||||
_fan_level_map: Optional[dict[int, str]]
|
_fan_level_map: Optional[dict[int, str]]
|
||||||
|
|
||||||
def __init__(
|
_device_name: str
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
|
||||||
) -> None:
|
def __init__(self, miot_device: MIoTDevice,
|
||||||
|
entity_data: MIoTEntityData) -> None:
|
||||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||||
|
self._device_name = miot_device.name
|
||||||
self._attr_supported_features = VacuumEntityFeature(0)
|
self._attr_supported_features = VacuumEntityFeature(0)
|
||||||
|
|
||||||
self._prop_status = None
|
self._prop_status = None
|
||||||
@ -121,21 +120,21 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
for prop in entity_data.props:
|
for prop in entity_data.props:
|
||||||
if prop.name == 'status':
|
if prop.name == 'status':
|
||||||
if not prop.value_list:
|
if not prop.value_list:
|
||||||
_LOGGER.error(
|
_LOGGER.error('invalid status value_list, %s',
|
||||||
'invalid status value_list, %s', self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._status_map = prop.value_list.to_map()
|
self._status_map = prop.value_list.to_map()
|
||||||
|
self._attr_supported_features |= VacuumEntityFeature.STATE
|
||||||
self._prop_status = prop
|
self._prop_status = prop
|
||||||
elif prop.name == 'fan-level':
|
elif prop.name == 'fan-level':
|
||||||
if not prop.value_list:
|
if not prop.value_list:
|
||||||
_LOGGER.error(
|
_LOGGER.error('invalid fan-level value_list, %s',
|
||||||
'invalid fan-level value_list, %s', self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._fan_level_map = prop.value_list.to_map()
|
self._fan_level_map = prop.value_list.to_map()
|
||||||
self._attr_fan_speed_list = list(self._fan_level_map.values())
|
self._attr_fan_speed_list = list(self._fan_level_map.values())
|
||||||
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
||||||
self._prop_fan_level = prop
|
self._prop_fan_level = prop
|
||||||
|
|
||||||
elif prop.name == 'battery-level':
|
elif prop.name == 'battery-level':
|
||||||
self._attr_supported_features |= VacuumEntityFeature.BATTERY
|
self._attr_supported_features |= VacuumEntityFeature.BATTERY
|
||||||
self._prop_battery_level = prop
|
self._prop_battery_level = prop
|
||||||
@ -155,14 +154,22 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
elif action.name == 'stop-and-gocharge':
|
elif action.name == 'stop-and-gocharge':
|
||||||
self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME
|
self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME
|
||||||
self._action_stop_and_gocharge = action
|
self._action_stop_and_gocharge = action
|
||||||
|
|
||||||
elif action.name == 'identify':
|
elif action.name == 'identify':
|
||||||
self._attr_supported_features |= VacuumEntityFeature.LOCATE
|
self._attr_supported_features |= VacuumEntityFeature.LOCATE
|
||||||
self._action_identify = action
|
self._action_identify = action
|
||||||
|
|
||||||
async def async_start(self) -> None:
|
async def async_start(self) -> None:
|
||||||
"""Start or resume the cleaning task."""
|
"""Start or resume the cleaning task."""
|
||||||
if self.state.lower() in ['paused', '暂停中']:
|
try: # VacuumActivity is introduced in HA core 2025.1.0
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from homeassistant.components.vacuum import VacuumActivity
|
||||||
|
if (self.activity
|
||||||
|
== VacuumActivity.PAUSED) and self._action_continue_sweep:
|
||||||
|
await self.action_async(action=self._action_continue_sweep)
|
||||||
|
return
|
||||||
|
except ImportError:
|
||||||
|
if self.state and (self.state in {'paused', 'pause'
|
||||||
|
}) and self._action_continue_sweep:
|
||||||
await self.action_async(action=self._action_continue_sweep)
|
await self.action_async(action=self._action_continue_sweep)
|
||||||
return
|
return
|
||||||
await self.action_async(action=self._action_start_sweep)
|
await self.action_async(action=self._action_start_sweep)
|
||||||
@ -179,31 +186,92 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
|||||||
"""Set the vacuum cleaner to return to the dock."""
|
"""Set the vacuum cleaner to return to the dock."""
|
||||||
await self.action_async(action=self._action_stop_and_gocharge)
|
await self.action_async(action=self._action_stop_and_gocharge)
|
||||||
|
|
||||||
async def async_clean_spot(self, **kwargs: Any) -> None:
|
|
||||||
"""Perform a spot clean-up."""
|
|
||||||
|
|
||||||
async def async_locate(self, **kwargs: Any) -> None:
|
async def async_locate(self, **kwargs: Any) -> None:
|
||||||
"""Locate the vacuum cleaner."""
|
"""Locate the vacuum cleaner."""
|
||||||
await self.action_async(action=self._action_identify)
|
await self.action_async(action=self._action_identify)
|
||||||
|
|
||||||
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
|
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
|
||||||
"""Set fan speed."""
|
"""Set fan speed."""
|
||||||
|
fan_level_value = self.get_map_key(map_=self._fan_level_map,
|
||||||
|
value=fan_speed)
|
||||||
|
await self.set_property_async(prop=self._prop_fan_level,
|
||||||
|
value=fan_level_value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> Optional[str]:
|
||||||
|
"""Name of the vacuum entity."""
|
||||||
|
return self._device_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> Optional[str]:
|
def state(self) -> Optional[str]:
|
||||||
"""Return the current state of the vacuum cleaner."""
|
"""Return the current state of the vacuum cleaner.
|
||||||
return self.get_map_value(
|
|
||||||
map_=self._status_map,
|
To fix the HA warning below:
|
||||||
key=self.get_prop_value(prop=self._prop_status))
|
Detected that custom integration 'xiaomi_home' is setting state
|
||||||
|
directly.Entity XXX(<class 'custom_components.xiaomi_home.vacuum
|
||||||
|
.Vacuum'>)should implement the 'activity' property and return
|
||||||
|
its state using the VacuumActivity enum.This will stop working in
|
||||||
|
Home Assistant 2026.1.
|
||||||
|
|
||||||
|
Refer to
|
||||||
|
https://developers.home-assistant.io/blog/2024/12/08/new-vacuum-state-property
|
||||||
|
|
||||||
|
There are only 6 states in VacuumActivity enum. To be compatible with
|
||||||
|
more constants, try get matching VacuumActivity enum first, return state
|
||||||
|
string as before if there is no match. In Home Assistant 2026.1, every
|
||||||
|
state should map to a VacuumActivity enum.
|
||||||
|
"""
|
||||||
|
return self.activity
|
||||||
|
|
||||||
|
@property
|
||||||
|
def activity(self) -> Optional[str]:
|
||||||
|
"""The current vacuum activity."""
|
||||||
|
status = self.get_prop_value(prop=self._prop_status)
|
||||||
|
if status is None:
|
||||||
|
return None
|
||||||
|
status_value = self.get_map_value(map_=self._status_map, key=status)
|
||||||
|
if status_value is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from homeassistant.components.vacuum import VacuumActivity
|
||||||
|
status_value = status_value.lower()
|
||||||
|
status_str = re.sub(r'[^a-z]', '', status_value)
|
||||||
|
if status_str in {
|
||||||
|
'charging', 'charged', 'chargingcompleted', 'fullcharge',
|
||||||
|
'fullpower', 'findchargerpause', 'drying', 'washing',
|
||||||
|
'wash', 'inthewash', 'inthedry', 'stationworking',
|
||||||
|
'dustcollecting', 'upgrade', 'upgrading', 'updating'
|
||||||
|
}:
|
||||||
|
return VacuumActivity.DOCKED
|
||||||
|
if status_str in {'paused', 'pause'}:
|
||||||
|
return VacuumActivity.PAUSED
|
||||||
|
if status_str in {
|
||||||
|
'gocharging', 'cleancompletegocharging', 'findchargewash',
|
||||||
|
'backtowashmop', 'gowash', 'gowashing', 'summon'
|
||||||
|
}:
|
||||||
|
return VacuumActivity.RETURNING
|
||||||
|
if (status_str.find('sweeping')
|
||||||
|
!= -1) or (status_str.find('mopping')
|
||||||
|
!= -1) or (status_str in {
|
||||||
|
'cleaning', 'remoteclean', 'continuesweep',
|
||||||
|
'busy', 'building', 'buildingmap', 'mapping'
|
||||||
|
}):
|
||||||
|
return VacuumActivity.CLEANING
|
||||||
|
if status_str in {'error', 'breakcharging', 'gochargebreak'}:
|
||||||
|
return VacuumActivity.ERROR
|
||||||
|
return VacuumActivity.IDLE
|
||||||
|
except ImportError:
|
||||||
|
return status_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def battery_level(self) -> Optional[int]:
|
def battery_level(self) -> Optional[int]:
|
||||||
"""Return the current battery level of the vacuum cleaner."""
|
"""The current battery level of the vacuum cleaner."""
|
||||||
return self.get_prop_value(prop=self._prop_battery_level)
|
return self.get_prop_value(prop=self._prop_battery_level)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_speed(self) -> Optional[str]:
|
def fan_speed(self) -> Optional[str]:
|
||||||
"""Return the current fan speed of the vacuum cleaner."""
|
"""The current fan speed of the vacuum cleaner."""
|
||||||
return self.get_map_value(
|
return self.get_map_value(
|
||||||
map_=self._fan_level_map,
|
map_=self._fan_level_map,
|
||||||
key=self.get_prop_value(prop=self._prop_fan_level))
|
key=self.get_prop_value(prop=self._prop_fan_level))
|
||||||
|
@ -52,13 +52,10 @@ from typing import Any, Optional
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.components.water_heater import (
|
from homeassistant.components.water_heater import (STATE_ON, STATE_OFF,
|
||||||
STATE_ON,
|
|
||||||
STATE_OFF,
|
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
WaterHeaterEntity,
|
WaterHeaterEntity,
|
||||||
WaterHeaterEntityFeature
|
WaterHeaterEntityFeature)
|
||||||
)
|
|
||||||
|
|
||||||
from .miot.const import DOMAIN
|
from .miot.const import DOMAIN
|
||||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||||
@ -79,8 +76,8 @@ async def async_setup_entry(
|
|||||||
new_entities = []
|
new_entities = []
|
||||||
for miot_device in device_list:
|
for miot_device in device_list:
|
||||||
for data in miot_device.entity_list.get('water_heater', []):
|
for data in miot_device.entity_list.get('water_heater', []):
|
||||||
new_entities.append(WaterHeater(
|
new_entities.append(
|
||||||
miot_device=miot_device, entity_data=data))
|
WaterHeater(miot_device=miot_device, entity_data=data))
|
||||||
|
|
||||||
if new_entities:
|
if new_entities:
|
||||||
async_add_entities(new_entities)
|
async_add_entities(new_entities)
|
||||||
@ -95,12 +92,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
|
|
||||||
_mode_map: Optional[dict[Any, Any]]
|
_mode_map: Optional[dict[Any, Any]]
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, miot_device: MIoTDevice,
|
||||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
entity_data: MIoTEntityData) -> None:
|
||||||
) -> None:
|
|
||||||
"""Initialize the Water heater."""
|
"""Initialize the Water heater."""
|
||||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||||
self._attr_temperature_unit = None # type: ignore
|
self._attr_temperature_unit = None
|
||||||
self._attr_supported_features = WaterHeaterEntityFeature(0)
|
self._attr_supported_features = WaterHeaterEntityFeature(0)
|
||||||
self._prop_on = None
|
self._prop_on = None
|
||||||
self._prop_temp = None
|
self._prop_temp = None
|
||||||
@ -117,14 +113,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
# temperature
|
# temperature
|
||||||
if prop.name == 'temperature':
|
if prop.name == 'temperature':
|
||||||
if not prop.value_range:
|
if not prop.value_range:
|
||||||
_LOGGER.error(
|
_LOGGER.error('invalid temperature value_range format, %s',
|
||||||
'invalid temperature value_range format, %s',
|
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
if prop.external_unit:
|
if prop.external_unit:
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_min_temp = prop.value_range.min_
|
|
||||||
self._attr_max_temp = prop.value_range.max_
|
|
||||||
self._prop_temp = prop
|
self._prop_temp = prop
|
||||||
# target-temperature
|
# target-temperature
|
||||||
if prop.name == 'target-temperature':
|
if prop.name == 'target-temperature':
|
||||||
@ -133,9 +126,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
'invalid target-temperature value_range format, %s',
|
'invalid target-temperature value_range format, %s',
|
||||||
self.entity_id)
|
self.entity_id)
|
||||||
continue
|
continue
|
||||||
self._attr_target_temperature_low = prop.value_range.min_
|
self._attr_min_temp = prop.value_range.min_
|
||||||
self._attr_target_temperature_high = prop.value_range.max_
|
self._attr_max_temp = prop.value_range.max_
|
||||||
self._attr_precision = prop.value_range.step
|
self._attr_target_temperature_step = prop.value_range.step
|
||||||
if self._attr_temperature_unit is None and prop.external_unit:
|
if self._attr_temperature_unit is None and prop.external_unit:
|
||||||
self._attr_temperature_unit = prop.external_unit
|
self._attr_temperature_unit = prop.external_unit
|
||||||
self._attr_supported_features |= (
|
self._attr_supported_features |= (
|
||||||
@ -144,17 +137,15 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
# mode
|
# mode
|
||||||
if prop.name == 'mode':
|
if prop.name == 'mode':
|
||||||
if not prop.value_list:
|
if not prop.value_list:
|
||||||
_LOGGER.error(
|
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||||
'mode value_list is None, %s', self.entity_id)
|
|
||||||
continue
|
continue
|
||||||
self._mode_map = prop.value_list.to_map()
|
self._mode_map = prop.value_list.to_map()
|
||||||
self._attr_operation_list = list(self._mode_map.values())
|
self._attr_operation_list = list(self._mode_map.values())
|
||||||
self._attr_supported_features |= (
|
|
||||||
WaterHeaterEntityFeature.OPERATION_MODE)
|
|
||||||
self._prop_mode = prop
|
self._prop_mode = prop
|
||||||
if not self._attr_operation_list:
|
if not self._attr_operation_list:
|
||||||
self._attr_operation_list = [STATE_ON]
|
self._attr_operation_list = [STATE_ON]
|
||||||
self._attr_operation_list.append(STATE_OFF)
|
self._attr_operation_list.append(STATE_OFF)
|
||||||
|
self._attr_supported_features |= WaterHeaterEntityFeature.OPERATION_MODE
|
||||||
|
|
||||||
async def async_turn_on(self) -> None:
|
async def async_turn_on(self) -> None:
|
||||||
"""Turn the water heater on."""
|
"""Turn the water heater on."""
|
||||||
@ -165,16 +156,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
await self.set_property_async(prop=self._prop_on, value=False)
|
await self.set_property_async(prop=self._prop_on, value=False)
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set the temperature the water heater should heat water to."""
|
"""Set the target temperature."""
|
||||||
if not self._prop_target_temp:
|
await self.set_property_async(prop=self._prop_target_temp,
|
||||||
return
|
value=kwargs[ATTR_TEMPERATURE])
|
||||||
await self.set_property_async(
|
|
||||||
prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE])
|
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
||||||
"""Set the operation mode of the water heater.
|
"""Set the operation mode of the water heater."""
|
||||||
Must be in the operation_list.
|
|
||||||
"""
|
|
||||||
if operation_mode == STATE_OFF:
|
if operation_mode == STATE_OFF:
|
||||||
await self.set_property_async(prop=self._prop_on, value=False)
|
await self.set_property_async(prop=self._prop_on, value=False)
|
||||||
return
|
return
|
||||||
@ -182,32 +169,32 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
|||||||
await self.set_property_async(prop=self._prop_on, value=True)
|
await self.set_property_async(prop=self._prop_on, value=True)
|
||||||
return
|
return
|
||||||
if self.get_prop_value(prop=self._prop_on) is False:
|
if self.get_prop_value(prop=self._prop_on) is False:
|
||||||
await self.set_property_async(
|
await self.set_property_async(prop=self._prop_on,
|
||||||
prop=self._prop_on, value=True, write_ha_state=False)
|
value=True,
|
||||||
await self.set_property_async(
|
write_ha_state=False)
|
||||||
prop=self._prop_mode,
|
await self.set_property_async(prop=self._prop_mode,
|
||||||
value=self.get_map_key(
|
value=self.get_map_key(
|
||||||
map_=self._mode_map, value=operation_mode))
|
map_=self._mode_map,
|
||||||
|
value=operation_mode))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> Optional[float]:
|
def current_temperature(self) -> Optional[float]:
|
||||||
"""Return the current temperature."""
|
"""The current temperature."""
|
||||||
return self.get_prop_value(prop=self._prop_temp)
|
return (None if self._prop_temp is None else self.get_prop_value(
|
||||||
|
prop=self._prop_temp))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self) -> Optional[float]:
|
def target_temperature(self) -> Optional[float]:
|
||||||
"""Return the target temperature."""
|
"""The target temperature."""
|
||||||
if not self._prop_target_temp:
|
return (None if self._prop_target_temp is None else self.get_prop_value(
|
||||||
return None
|
prop=self._prop_target_temp))
|
||||||
return self.get_prop_value(prop=self._prop_target_temp)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_operation(self) -> Optional[str]:
|
def current_operation(self) -> Optional[str]:
|
||||||
"""Return the current mode."""
|
"""The current mode."""
|
||||||
if self.get_prop_value(prop=self._prop_on) is False:
|
if self.get_prop_value(prop=self._prop_on) is False:
|
||||||
return STATE_OFF
|
return STATE_OFF
|
||||||
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
||||||
return STATE_ON
|
return STATE_ON
|
||||||
return self.get_map_value(
|
return (None if self._prop_mode is None else self.get_map_value(
|
||||||
map_=self._mode_map,
|
map_=self._mode_map, key=self.get_prop_value(prop=self._prop_mode)))
|
||||||
key=self.get_prop_value(prop=self._prop_mode))
|
|
||||||
|
@ -33,9 +33,11 @@ git checkout v1.0.0
|
|||||||
|
|
||||||
### 方法 2: [HACS](https://hacs.xyz/)
|
### 方法 2: [HACS](https://hacs.xyz/)
|
||||||
|
|
||||||
HACS > 右上角三个点 > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > 点击 HACS 的 New 或 Available for download 分类下的 Xiaomi Home ,进入集成详情页 > DOWNLOAD
|
一键从 HACS 安装米家集成:
|
||||||
|
|
||||||
> 米家集成暂未添加到 HACS 商店,敬请期待。
|
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)
|
||||||
|
|
||||||
|
或者,HACS > 在搜索框中输入 **Xiaomi Home** > 点击 **Xiaomi Home** ,进入集成详情页 > DOWNLOAD
|
||||||
|
|
||||||
### 方法 3:通过 [Samba](https://github.com/home-assistant/addons/tree/master/samba) 或 [FTPS](https://github.com/hassio-addons/addon-ftp) 手动安装
|
### 方法 3:通过 [Samba](https://github.com/home-assistant/addons/tree/master/samba) 或 [FTPS](https://github.com/hassio-addons/addon-ftp) 手动安装
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
|||||||
|
|
||||||
[设置 > 设备与服务 > 添加集成](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > 搜索“`Xiaomi Home`” > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
[设置 > 设备与服务 > 添加集成](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > 搜索“`Xiaomi Home`” > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||||
|
|
||||||
### 添加 MIoT 设备
|
### 添加 MIoT 设备
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
|||||||
|
|
||||||
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 添加中枢 > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 添加中枢 > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
||||||
|
|
||||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||||
|
|
||||||
### 修改配置项
|
### 修改配置项
|
||||||
|
|
||||||
@ -155,6 +157,8 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
|||||||
|
|
||||||
转换后的实体为 Event,事件参数同时传递给实体的 `_trigger_event` 。
|
转换后的实体为 Event,事件参数同时传递给实体的 `_trigger_event` 。
|
||||||
|
|
||||||
|
MIoT-Spec-V2 事件的 arguments 字段是事件的参数列表,列表元素代表同服务下属性的 piid 。例如,小米智能无线开关(双开)的 [MIoT-Spec-V2](http://poc.miot-spec.srv/miot-spec-v2/instance?type=urn:miot-spec-v2:device:remote-control:0000A021:xiaomi-mcn002:1:0000D057)的 siid=2 无线开关服务下包含 eiid=1014 长按事件,该事件触发时会携带一个 piid=2 的按键类型属性作为事件参数, debug 等级日志会打印 `长按, attributes: {'按键类型': 1}` (日志示例,按键类型为 1 表示右键触发了长按事件)。
|
||||||
|
|
||||||
- 方法(Action)
|
- 方法(Action)
|
||||||
|
|
||||||
| in(输入参数列表) | 转换后的实体 |
|
| in(输入参数列表) | 转换后的实体 |
|
||||||
@ -289,39 +293,37 @@ event instance name 下的值表示转换后实体所用的 `_attr_device_class`
|
|||||||
|
|
||||||
### MIoT-Spec-V2 过滤规则
|
### MIoT-Spec-V2 过滤规则
|
||||||
|
|
||||||
`spec_filter.json` 用于过滤掉不需要的 MIoT-Spec-V2 实例,过滤掉的实例不会转换成 Home Assistant 实体。
|
`spec_filter.yaml` 用于过滤掉不需要的 MIoT-Spec-V2 实例,过滤掉的实例不会转换成 Home Assistant 实体。
|
||||||
|
|
||||||
`spec_filter.json`的格式如下:
|
`spec_filter.yaml`的格式如下:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
{
|
<MIoT-Spec-V2 device instance urn without the version field>:
|
||||||
"<MIoT-Spec-V2 device instance>":{
|
services: list<service_iid: str>
|
||||||
"services": list<service_iid: str>,
|
properties: list<service_iid.property_iid: str>
|
||||||
"properties": list<service_iid.property_iid: str>,
|
events: list<service_iid.event_iid: str>
|
||||||
"events": list<service_iid.event_iid: str>,
|
actions: list<service_iid.action_iid: str>
|
||||||
"actions": list<service_iid.action_iid: str>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`spec_filter.json` 的键值为 MIoT-Spec-V2 设备实例的 urn (不含版本号“version”字段)。一个产品的不同版本的固件可能会关联不同版本的 MIoT-Spec-V2 设备实例。 MIoT 平台要求厂商定义产品的 MIoT-Spec-V2 时,高版本的 MIoT-Spec-V2 实例必须包含全部低版本的 MIoT-Spec-V2 实例。因此, `spec_filter.json` 的键值不需要指定设备实例的版本号。
|
`spec_filter.yaml` 的键值为 MIoT-Spec-V2 设备实例的 urn (不含版本号“version”字段)。一个产品的不同版本的固件可能会关联不同版本的 MIoT-Spec-V2 设备实例。 MIoT 平台要求厂商定义产品的 MIoT-Spec-V2 时,高版本的 MIoT-Spec-V2 实例必须包含全部低版本的 MIoT-Spec-V2 实例。因此, `spec_filter.yaml` 的键值不需要指定设备实例的版本号。
|
||||||
|
|
||||||
设备实例下的 services 、 properties 、 events 、 actions 域的值表示需要过滤掉的服务、属性、事件、方法的实例号( iid ,即 instance id )。支持通配符匹配。
|
设备实例下的 services 、 properties 、 events 、 actions 域的值表示需要过滤掉的服务、属性、事件、方法的实例号( iid ,即 instance id )。支持通配符匹配。
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
{
|
urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1:
|
||||||
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{
|
services:
|
||||||
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance.
|
- '*' # 排除所有服务,相当于排除拥有该 MIoT-Spec-V2 的设备。
|
||||||
},
|
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
|
||||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
services:
|
||||||
"services": ["3"], # Filter out the service whose iid=3.
|
- '3' # 排除 siid=3 的服务。
|
||||||
"properties": ["4.*"] # Filter out all properties in the service whose iid=4.
|
properties:
|
||||||
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service.
|
- '4.*' # 排除 siid=4 服务的所有属性。
|
||||||
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service.
|
events:
|
||||||
}
|
- '4.1' # 排除 siid=4 服务的 eiid=1 的事件。
|
||||||
}
|
actions:
|
||||||
|
- '4.1' # 排除 siid=4 服务的 aiid=1 的方法。
|
||||||
```
|
```
|
||||||
|
|
||||||
所有设备的设备信息服务( urn:miot-spec-v2:service:device-information:00007801 )均不会生成 Home Assistant 实体。
|
所有设备的设备信息服务( urn:miot-spec-v2:service:device-information:00007801 )均不会生成 Home Assistant 实体。
|
||||||
@ -353,7 +355,7 @@ instance code 为 MIoT-Spec-V2 实例代码,格式如下:
|
|||||||
```
|
```
|
||||||
service:<siid> # 服务
|
service:<siid> # 服务
|
||||||
service:<siid>:property:<piid> # 属性
|
service:<siid>:property:<piid> # 属性
|
||||||
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的值
|
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的索引值
|
||||||
service:<siid>:event:<eiid> # 事件
|
service:<siid>:event:<eiid> # 事件
|
||||||
service:<siid>:action:<aiid> # 方法
|
service:<siid>:action:<aiid> # 方法
|
||||||
```
|
```
|
||||||
@ -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.yaml`、`spec_modify.yaml`、`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(
|
MIOT_I18N_RELATIVE_PATH: str = path.join(
|
||||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
||||||
SPEC_BOOL_TRANS_FILE = path.join(
|
SPEC_BOOL_TRANS_FILE = path.join(
|
||||||
ROOT_PATH,
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||||
'../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
|
||||||
SPEC_FILTER_FILE = path.join(
|
SPEC_FILTER_FILE = path.join(
|
||||||
ROOT_PATH,
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
||||||
'../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
SPEC_MULTI_LANG_FILE = path.join(
|
||||||
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/multi_lang.json')
|
||||||
|
SPEC_ADD_FILE = path.join(
|
||||||
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_add.json')
|
||||||
SPEC_MODIFY_FILE = path.join(
|
SPEC_MODIFY_FILE = path.join(
|
||||||
ROOT_PATH,
|
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
||||||
'../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
|
||||||
|
|
||||||
|
|
||||||
def load_json_file(file_path: str) -> Optional[dict]:
|
def load_json_file(file_path: str) -> Optional[dict]:
|
||||||
@ -30,7 +31,7 @@ def load_json_file(file_path: str) -> Optional[dict]:
|
|||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
return json.load(file)
|
return json.load(file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
_LOGGER.info('%s is not found.', file_path,)
|
_LOGGER.info('%s is not found.', file_path)
|
||||||
return None
|
return None
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
_LOGGER.info('%s is not a valid JSON file.', file_path)
|
_LOGGER.info('%s is not a valid JSON file.', file_path)
|
||||||
@ -39,7 +40,7 @@ def load_json_file(file_path: str) -> Optional[dict]:
|
|||||||
|
|
||||||
def save_json_file(file_path: str, data: dict) -> None:
|
def save_json_file(file_path: str, data: dict) -> None:
|
||||||
with open(file_path, 'w', encoding='utf-8') as file:
|
with open(file_path, 'w', encoding='utf-8') as file:
|
||||||
json.dump(data, file, ensure_ascii=False, indent=4)
|
json.dump(data, file, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
def load_yaml_file(file_path: str) -> Optional[dict]:
|
def load_yaml_file(file_path: str) -> Optional[dict]:
|
||||||
@ -56,9 +57,12 @@ def load_yaml_file(file_path: str) -> Optional[dict]:
|
|||||||
|
|
||||||
def save_yaml_file(file_path: str, data: dict) -> None:
|
def save_yaml_file(file_path: str, data: dict) -> None:
|
||||||
with open(file_path, 'w', encoding='utf-8') as file:
|
with open(file_path, 'w', encoding='utf-8') as file:
|
||||||
yaml.safe_dump(
|
yaml.safe_dump(data,
|
||||||
data, file, default_flow_style=False,
|
file,
|
||||||
allow_unicode=True, indent=2, sort_keys=False)
|
default_flow_style=False,
|
||||||
|
allow_unicode=True,
|
||||||
|
indent=2,
|
||||||
|
sort_keys=False)
|
||||||
|
|
||||||
|
|
||||||
def dict_str_str(d: dict) -> bool:
|
def dict_str_str(d: dict) -> bool:
|
||||||
@ -132,9 +136,116 @@ def bool_trans(d: dict) -> bool:
|
|||||||
for key, trans in d['translate'].items():
|
for key, trans in d['translate'].items():
|
||||||
trans_keys: set[str] = set(trans.keys())
|
trans_keys: set[str] = set(trans.keys())
|
||||||
if set(trans.keys()) != default_keys:
|
if set(trans.keys()) != default_keys:
|
||||||
_LOGGER.info(
|
_LOGGER.info('bool trans inconsistent, %s, %s, %s', key,
|
||||||
'bool trans inconsistent, %s, %s, %s',
|
default_keys, trans_keys)
|
||||||
key, default_keys, trans_keys)
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def multi_lang(data: dict) -> bool:
|
||||||
|
"""dict[str, dict[str, dict[str, str]]]"""
|
||||||
|
for key in data.keys():
|
||||||
|
if key.count(':') != 5:
|
||||||
|
return False
|
||||||
|
return nested_3_dict_str_str(data)
|
||||||
|
|
||||||
|
|
||||||
|
def spec_add(data: dict) -> bool:
|
||||||
|
"""dict[str, list[dict[str, int| str | list]]]"""
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return False
|
||||||
|
for urn, content in data.items():
|
||||||
|
if not isinstance(urn, str) or not isinstance(content, (list, str)):
|
||||||
|
return False
|
||||||
|
if isinstance(content, str):
|
||||||
|
continue
|
||||||
|
for service in content:
|
||||||
|
if ('iid' not in service) or ('type' not in service) or (
|
||||||
|
'description'
|
||||||
|
not in service) or (('properties' not in service) and
|
||||||
|
('actions' not in service) and
|
||||||
|
('events' not in service)):
|
||||||
|
return False
|
||||||
|
type_strs: list[str] = service['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
if 'properties' in service:
|
||||||
|
if not isinstance(service['properties'], list):
|
||||||
|
return False
|
||||||
|
for prop in service['properties']:
|
||||||
|
if ('iid' not in prop) or ('type' not in prop) or (
|
||||||
|
'description' not in prop) or (
|
||||||
|
'format' not in prop) or ('access' not in prop):
|
||||||
|
return False
|
||||||
|
if not isinstance(prop['iid'], int) or not isinstance(
|
||||||
|
prop['type'], str) or not isinstance(
|
||||||
|
prop['description'], str) or not isinstance(
|
||||||
|
prop['format'], str) or not isinstance(
|
||||||
|
prop['access'], list):
|
||||||
|
return False
|
||||||
|
type_strs = prop['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
for access in prop['access']:
|
||||||
|
if access not in ['read', 'write', 'notify']:
|
||||||
|
return False
|
||||||
|
if 'value-range' in prop:
|
||||||
|
if not isinstance(prop['value-range'], list):
|
||||||
|
return False
|
||||||
|
for value in prop['value-range']:
|
||||||
|
if not isinstance(value, (int, float)):
|
||||||
|
return False
|
||||||
|
if 'value-list' in prop:
|
||||||
|
if not isinstance(prop['value-list'], list):
|
||||||
|
return False
|
||||||
|
for item in prop['value-list']:
|
||||||
|
if 'value' not in item or 'description' not in item:
|
||||||
|
return False
|
||||||
|
if not isinstance(item['value'],
|
||||||
|
int) or not isinstance(
|
||||||
|
item['description'], str):
|
||||||
|
return False
|
||||||
|
if 'actions' in service:
|
||||||
|
if not isinstance(service['actions'], list):
|
||||||
|
return False
|
||||||
|
for action in service['actions']:
|
||||||
|
if ('iid' not in action) or ('type' not in action) or (
|
||||||
|
'description' not in action) or (
|
||||||
|
'in' not in action) or ('out' not in action):
|
||||||
|
return False
|
||||||
|
if not isinstance(action['iid'], int) or not isinstance(
|
||||||
|
action['type'], str) or not isinstance(
|
||||||
|
action['description'], str) or not isinstance(
|
||||||
|
action['in'], list) or not isinstance(
|
||||||
|
action['out'], list):
|
||||||
|
return False
|
||||||
|
type_strs = action['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
for param in action['in']:
|
||||||
|
if not isinstance(param, int):
|
||||||
|
return False
|
||||||
|
for param in action['out']:
|
||||||
|
if not isinstance(param, int):
|
||||||
|
return False
|
||||||
|
if 'events' in service:
|
||||||
|
if not isinstance(service['events'], list):
|
||||||
|
return False
|
||||||
|
for event in service['events']:
|
||||||
|
if ('iid' not in event) or ('type' not in event) or (
|
||||||
|
'description' not in event) or ('arguments'
|
||||||
|
not in event):
|
||||||
|
return False
|
||||||
|
if not isinstance(event['iid'], int) or not isinstance(
|
||||||
|
event['type'], str) or not isinstance(
|
||||||
|
event['description'], str) or not isinstance(
|
||||||
|
event['arguments'], list):
|
||||||
|
return False
|
||||||
|
type_strs = event['type'].split(':')
|
||||||
|
if type_strs[1] != 'miot-spec-v2':
|
||||||
|
return False
|
||||||
|
for param in event['arguments']:
|
||||||
|
if not isinstance(param, int):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -159,25 +270,22 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
|
|||||||
_LOGGER.info('invalid type')
|
_LOGGER.info('invalid type')
|
||||||
return False
|
return False
|
||||||
if dict1.keys() != dict2.keys():
|
if dict1.keys() != dict2.keys():
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, %s, %s', dict1.keys(),
|
||||||
'inconsistent key values, %s, %s', dict1.keys(), dict2.keys())
|
dict2.keys())
|
||||||
return False
|
return False
|
||||||
for key in dict1:
|
for key in dict1:
|
||||||
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
||||||
if not compare_dict_structure(dict1[key], dict2[key]):
|
if not compare_dict_structure(dict1[key], dict2[key]):
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, dict, %s', key)
|
||||||
'inconsistent key values, dict, %s', key)
|
|
||||||
return False
|
return False
|
||||||
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
|
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
|
||||||
if not all(
|
if not all(
|
||||||
isinstance(i, type(j))
|
isinstance(i, type(j))
|
||||||
for i, j in zip(dict1[key], dict2[key])):
|
for i, j in zip(dict1[key], dict2[key])):
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, list, %s', key)
|
||||||
'inconsistent key values, list, %s', key)
|
|
||||||
return False
|
return False
|
||||||
elif not isinstance(dict1[key], type(dict2[key])):
|
elif not isinstance(dict1[key], type(dict2[key])):
|
||||||
_LOGGER.info(
|
_LOGGER.info('inconsistent key values, type, %s', key)
|
||||||
'inconsistent key values, type, %s', key)
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -200,6 +308,16 @@ def sort_spec_filter(file_path: str):
|
|||||||
return filter_data
|
return filter_data
|
||||||
|
|
||||||
|
|
||||||
|
def sort_spec_add(file_path: str):
|
||||||
|
filter_data = load_json_file(file_path=file_path)
|
||||||
|
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||||
|
return dict(sorted(filter_data.items()))
|
||||||
|
|
||||||
|
|
||||||
|
def sort_multi_lang(file_path: str):
|
||||||
|
return sort_spec_add(file_path)
|
||||||
|
|
||||||
|
|
||||||
def sort_spec_modify(file_path: str):
|
def sort_spec_modify(file_path: str):
|
||||||
filter_data = load_yaml_file(file_path=file_path)
|
filter_data = load_yaml_file(file_path=file_path)
|
||||||
assert isinstance(filter_data, dict), f'{file_path} format error'
|
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||||
@ -222,6 +340,22 @@ def test_spec_filter():
|
|||||||
assert spec_filter(data), f'{SPEC_FILTER_FILE} format error'
|
assert spec_filter(data), f'{SPEC_FILTER_FILE} format error'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.github
|
||||||
|
def test_multi_lang():
|
||||||
|
data = load_json_file(SPEC_MULTI_LANG_FILE)
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert data, f'load {SPEC_MULTI_LANG_FILE} failed'
|
||||||
|
assert multi_lang(data), f'{SPEC_MULTI_LANG_FILE} format error'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.github
|
||||||
|
def test_spec_add():
|
||||||
|
data = load_json_file(SPEC_ADD_FILE)
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert data, f'load {SPEC_ADD_FILE} failed'
|
||||||
|
assert spec_add(data), f'{SPEC_ADD_FILE} format error'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.github
|
@pytest.mark.github
|
||||||
def test_spec_modify():
|
def test_spec_modify():
|
||||||
data = load_yaml_file(SPEC_MODIFY_FILE)
|
data = load_yaml_file(SPEC_MODIFY_FILE)
|
||||||
@ -255,7 +389,8 @@ def test_miot_lang_integrity():
|
|||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
from miot.const import INTEGRATION_LANGUAGES
|
from miot.const import INTEGRATION_LANGUAGES
|
||||||
integration_lang_list: list[str] = [
|
integration_lang_list: list[str] = [
|
||||||
f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())]
|
f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())
|
||||||
|
]
|
||||||
translations_names: set[str] = set(listdir(TRANS_RELATIVE_PATH))
|
translations_names: set[str] = set(listdir(TRANS_RELATIVE_PATH))
|
||||||
assert len(translations_names) == len(integration_lang_list)
|
assert len(translations_names) == len(integration_lang_list)
|
||||||
assert translations_names == set(integration_lang_list)
|
assert translations_names == set(integration_lang_list)
|
||||||
@ -271,21 +406,18 @@ def test_miot_lang_integrity():
|
|||||||
default_dict = load_json_file(
|
default_dict = load_json_file(
|
||||||
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
||||||
for name in list(integration_lang_list)[1:]:
|
for name in list(integration_lang_list)[1:]:
|
||||||
compare_dict = load_json_file(
|
compare_dict = load_json_file(path.join(TRANS_RELATIVE_PATH, name))
|
||||||
path.join(TRANS_RELATIVE_PATH, name))
|
|
||||||
if not compare_dict_structure(default_dict, compare_dict):
|
if not compare_dict_structure(default_dict, compare_dict):
|
||||||
_LOGGER.info(
|
_LOGGER.info('compare_dict_structure failed /translations, %s',
|
||||||
'compare_dict_structure failed /translations, %s', name)
|
name)
|
||||||
assert False
|
assert False
|
||||||
# Check i18n files structure
|
# Check i18n files structure
|
||||||
default_dict = load_json_file(
|
default_dict = load_json_file(
|
||||||
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
||||||
for name in list(integration_lang_list)[1:]:
|
for name in list(integration_lang_list)[1:]:
|
||||||
compare_dict = load_json_file(
|
compare_dict = load_json_file(path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||||
path.join(MIOT_I18N_RELATIVE_PATH, name))
|
|
||||||
if not compare_dict_structure(default_dict, compare_dict):
|
if not compare_dict_structure(default_dict, compare_dict):
|
||||||
_LOGGER.info(
|
_LOGGER.info('compare_dict_structure failed /miot/i18n, %s', name)
|
||||||
'compare_dict_structure failed /miot/i18n, %s', name)
|
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
@ -303,12 +435,27 @@ def test_miot_data_sort():
|
|||||||
f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path'
|
f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path'
|
||||||
' and run the following command sorting, ',
|
' and run the following command sorting, ',
|
||||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
assert json.dumps(
|
assert json.dumps(load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
||||||
load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
|
||||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||||
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
||||||
' and run the following command sorting, ',
|
' and run the following command sorting, ',
|
||||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
assert json.dumps(
|
||||||
|
load_json_file(file_path=SPEC_MULTI_LANG_FILE)) == json.dumps(
|
||||||
|
sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)), (
|
||||||
|
f'{SPEC_MULTI_LANG_FILE} not sorted, goto project root path'
|
||||||
|
' and run the following command sorting, ',
|
||||||
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
assert json.dumps(load_json_file(file_path=SPEC_ADD_FILE)) == json.dumps(
|
||||||
|
sort_spec_add(file_path=SPEC_ADD_FILE)), (
|
||||||
|
f'{SPEC_ADD_FILE} not sorted, goto project root path'
|
||||||
|
' and run the following command sorting, ',
|
||||||
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
assert json.dumps(load_yaml_file(file_path=SPEC_MODIFY_FILE)) == json.dumps(
|
||||||
|
sort_spec_modify(file_path=SPEC_MODIFY_FILE)), (
|
||||||
|
f'{SPEC_MODIFY_FILE} not sorted, goto project root path'
|
||||||
|
' and run the following command sorting, ',
|
||||||
|
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.update
|
@pytest.mark.update
|
||||||
@ -319,6 +466,11 @@ def test_sort_spec_data():
|
|||||||
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
||||||
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
||||||
_LOGGER.info('%s formatted.', SPEC_FILTER_FILE)
|
_LOGGER.info('%s formatted.', SPEC_FILTER_FILE)
|
||||||
|
sort_data = sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)
|
||||||
|
save_json_file(file_path=SPEC_MULTI_LANG_FILE, data=sort_data)
|
||||||
|
sort_data = sort_spec_add(file_path=SPEC_ADD_FILE)
|
||||||
|
save_json_file(file_path=SPEC_ADD_FILE, data=sort_data)
|
||||||
|
_LOGGER.info('%s formatted.', SPEC_ADD_FILE)
|
||||||
sort_data = sort_spec_modify(file_path=SPEC_MODIFY_FILE)
|
sort_data = sort_spec_modify(file_path=SPEC_MODIFY_FILE)
|
||||||
save_yaml_file(file_path=SPEC_MODIFY_FILE, data=sort_data)
|
save_yaml_file(file_path=SPEC_MODIFY_FILE, data=sort_data)
|
||||||
_LOGGER.info('%s formatted.', SPEC_MODIFY_FILE)
|
_LOGGER.info('%s formatted.', SPEC_MODIFY_FILE)
|
||||||
|
Reference in New Issue
Block a user