mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2025-07-19 05:59:24 +08:00
Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
94583a23d1 | |||
925cf3b90f | |||
836bd01ead | |||
4ad040d2ea | |||
981429670a | |||
a82fd86c60 | |||
df3faea257 | |||
e09676661c | |||
aebeaf0245 | |||
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 | |||
7d9250914c | |||
a09289ef90 | |||
b0428dc95a | |||
19ed04f2f5 | |||
e174a73f52 | |||
a1aa1c024f | |||
372e635681 | |||
3759aa9a1b |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -1,7 +1,7 @@
|
||||
name: Bug Report / 报告问题
|
||||
description: Create a report to help us improve. / 报告问题以帮助我们改进
|
||||
body:
|
||||
- type: input
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug / 描述问题
|
||||
description: |
|
||||
|
85
CHANGELOG.md
85
CHANGELOG.md
@ -1,4 +1,89 @@
|
||||
# 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
|
||||
### Added
|
||||
- Convert the submersion-state, the contact-state and the occupancy-status property to the binary_sensor entity. [#905](https://github.com/XiaoMi/ha_xiaomi_home/pull/905)
|
||||
### Changed
|
||||
- suittc.airrtc.wk168 mode descriptions are set to strings of numbers from 1 to 16. [#921](https://github.com/XiaoMi/ha_xiaomi_home/pull/921)
|
||||
- Do not set _attr_suggested_display_precision when the spec.expr is set in spec_modify.yaml [#929](https://github.com/XiaoMi/ha_xiaomi_home/pull/929)
|
||||
- Set "unknown event msg" log to info level.
|
||||
### Fixed
|
||||
- hhcc.plantmonitor.v1 soil moisture and soil ec icon and unit. [#927](https://github.com/XiaoMi/ha_xiaomi_home/pull/27)
|
||||
- cuco.plug.cp2 voltage and power value ratio.
|
||||
- cgllc.airmonitor.s1 unit ppb.
|
||||
- roswan.waterpuri.lte01 tds unit.
|
||||
- lumi.relay.c2acn01 power consumption unit
|
||||
- xiaomi.bhf_light.s1 fan level of ventilation.
|
||||
|
||||
## v0.2.3
|
||||
### Changed
|
||||
- Specify the service name and the property name during the climate entity's on/off feature initialization. [#899](https://github.com/XiaoMi/ha_xiaomi_home/pull/899)
|
||||
- Remove the useless total-battery property from `SPEC_PROP_TRANS_MAP`.
|
||||
### Fixed
|
||||
- Fix the hvac mode setting error when changing the preset mode of the ptc-bath-heater.
|
||||
- Fix the ambiguous descriptions of yeelink.bhf_light.v10 ptc-bath-heater mode value-list.
|
||||
- Fix the power consumption value of chuangmi.plug.212a01. [#910](https://github.com/XiaoMi/ha_xiaomi_home/pull/910)
|
||||
|
||||
## v0.2.2
|
||||
This version has modified the conversion rules of the climate entity, which will have effect on the devices with the ptc-bath-heater, the air-conditioner and the air-fresh service. After updating, you need to restart Home Assistant and check `xiaomi_home > CONFIGURE >
|
||||
|
60
README.md
60
README.md
@ -33,9 +33,11 @@ git checkout v1.0.0
|
||||
|
||||
### Method 2: [HACS](https://hacs.xyz/)
|
||||
|
||||
HACS > Overflow Menu > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > Xiaomi Home in New or Available for download section of HACS > DOWNLOAD
|
||||
One-click installation from HACS:
|
||||
|
||||
> Xiaomi Home has not been added to the HACS store as a default yet. It's coming soon.
|
||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)
|
||||
|
||||
Or, HACS > In the search box, type **Xiaomi Home** > Click **Xiaomi Home**, getting into the detail page > DOWNLOAD
|
||||
|
||||
### Method 3: Manually installation via [Samba](https://github.com/home-assistant/addons/tree/master/samba) / [FTPS](https://github.com/hassio-addons/addon-ftp)
|
||||
|
||||
@ -47,7 +49,7 @@ Download and copy `custom_components/xiaomi_home` folder to `config/custom_compo
|
||||
|
||||
[Settings > Devices & services > ADD INTEGRATION](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > Search `Xiaomi Home` > NEXT > Click here to login > Sign in with Xiaomi account
|
||||
|
||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||
|
||||
### Add MIoT Devices
|
||||
|
||||
@ -59,7 +61,7 @@ After a Xiaomi account login and its user configuration are completed, you can c
|
||||
|
||||
Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > ADD HUB > NEXT > Click here to login > Sign in with Xiaomi account
|
||||
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||
|
||||
### Update Configurations
|
||||
|
||||
@ -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'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
|
||||
|
||||
| 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
|
||||
|
||||
`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.yaml` is as follows.
|
||||
|
||||
```
|
||||
{
|
||||
"<MIoT-Spec-V2 device instance>":{
|
||||
"services": list<service_iid: str>,
|
||||
"properties": list<service_iid.property_iid: str>,
|
||||
"events": list<service_iid.event_iid: str>,
|
||||
"actions": list<service_iid.action_iid: str>,
|
||||
}
|
||||
}
|
||||
```yaml
|
||||
<MIoT-Spec-V2 device instance urn without the version field>:
|
||||
services: list<service_iid: str>
|
||||
properties: list<service_iid.property_iid: str>
|
||||
events: list<service_iid.event_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.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
{
|
||||
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{
|
||||
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance.
|
||||
},
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"services": ["3"], # Filter out the service whose iid=3.
|
||||
"properties": ["4.*"] # Filter out all properties in the service whose iid=4.
|
||||
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service.
|
||||
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service.
|
||||
}
|
||||
}
|
||||
```yaml
|
||||
urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1:
|
||||
services:
|
||||
- '*' # 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:
|
||||
services:
|
||||
- '3' # Filter out the siid=3 service.
|
||||
properties:
|
||||
- '4.*' # Filter out all properties in the siid=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.
|
||||
@ -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
|
||||
|
||||
|
@ -156,7 +156,8 @@ async def async_setup_entry(
|
||||
device.entity_list[platform].remove(entity)
|
||||
entity_id = device.gen_service_entity_id(
|
||||
ha_domain=platform,
|
||||
siid=entity.spec.iid) # type: ignore
|
||||
siid=entity.spec.iid,
|
||||
description=entity.spec.description)
|
||||
if er.async_get(entity_id_or_uuid=entity_id):
|
||||
er.async_remove(entity_id=entity_id)
|
||||
if platform in device.prop_list:
|
||||
|
@ -89,4 +89,8 @@ class BinarySensor(MIoTPropertyEntity, BinarySensorEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""On/Off state. True if the binary sensor is on, False otherwise."""
|
||||
if self.spec.name == 'contact-state':
|
||||
return self._value is False
|
||||
elif self.spec.name == 'occupancy-status':
|
||||
return bool(self._value)
|
||||
return self._value is True
|
||||
|
@ -51,10 +51,11 @@ from typing import Any, Optional
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.climate import (
|
||||
FAN_ON, FAN_OFF, SWING_OFF, SWING_BOTH, SWING_VERTICAL, SWING_HORIZONTAL,
|
||||
ATTR_TEMPERATURE, HVACMode, ClimateEntity, ClimateEntityFeature)
|
||||
ATTR_TEMPERATURE, HVACMode, HVACAction, ClimateEntity, ClimateEntityFeature)
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||
@ -101,21 +102,19 @@ class FeatureOnOff(MIoTServiceEntity, ClimateEntity):
|
||||
self._prop_on = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'on':
|
||||
if (
|
||||
# The "on" property of the "fan-control" service is not
|
||||
# the on/off feature of the entity.
|
||||
prop.service.name == 'air-conditioner' or
|
||||
prop.service.name == 'heater' or
|
||||
prop.service.name == 'thermostat' or
|
||||
prop.service.name == 'electric-blanket'):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_ON)
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TURN_OFF)
|
||||
self._prop_on = prop
|
||||
|
||||
def _init_on_off(self, service_name: str, prop_name: str) -> None:
|
||||
"""Initialize the on_off feature."""
|
||||
for prop in self.entity_data.props:
|
||||
if prop.name == prop_name and prop.service.name == service_name:
|
||||
if prop.format_ != bool:
|
||||
_LOGGER.error('wrong format %s %s, %s', service_name,
|
||||
prop_name, self.entity_id)
|
||||
continue
|
||||
self._attr_supported_features |= ClimateEntityFeature.TURN_ON
|
||||
self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
|
||||
self._prop_on = prop
|
||||
break
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn on."""
|
||||
@ -134,6 +133,7 @@ class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_target_temp = None
|
||||
self._attr_temperature_unit = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
@ -151,6 +151,10 @@ class FeatureTargetTemperature(MIoTServiceEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE)
|
||||
self._prop_target_temp = prop
|
||||
break
|
||||
# temperature_unit is required by the climate entity
|
||||
if not self._attr_temperature_unit:
|
||||
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set the target temperature."""
|
||||
@ -197,6 +201,7 @@ class FeaturePresetMode(MIoTServiceEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.PRESET_MODE)
|
||||
self._prop_mode = prop
|
||||
break
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set the preset mode."""
|
||||
@ -225,6 +230,7 @@ class FeatureFanMode(MIoTServiceEntity, ClimateEntity):
|
||||
self._prop_fan_on = None
|
||||
self._prop_fan_level = None
|
||||
self._fan_mode_map = None
|
||||
self._attr_fan_modes = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
@ -365,6 +371,7 @@ class FeatureTemperature(MIoTServiceEntity, ClimateEntity):
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'temperature':
|
||||
self._prop_env_temperature = prop
|
||||
break
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
@ -387,6 +394,7 @@ class FeatureHumidity(MIoTServiceEntity, ClimateEntity):
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'relative-humidity':
|
||||
self._prop_env_humidity = prop
|
||||
break
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> Optional[float]:
|
||||
@ -418,6 +426,7 @@ class FeatureTargetHumidity(MIoTServiceEntity, ClimateEntity):
|
||||
self._attr_supported_features |= (
|
||||
ClimateEntityFeature.TARGET_HUMIDITY)
|
||||
self._prop_target_humidity = prop
|
||||
break
|
||||
|
||||
async def async_set_humidity(self, humidity):
|
||||
"""Set the target humidity."""
|
||||
@ -447,6 +456,8 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
self._attr_icon = 'mdi:radiator'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
# on/off
|
||||
self._init_on_off('heater', 'on')
|
||||
# preset modes
|
||||
self._init_preset_modes('heater', 'heat-level')
|
||||
|
||||
@ -462,6 +473,13 @@ class Heater(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
return (HVACMode.HEAT if self.get_prop_value(
|
||||
prop=self._prop_on) else HVACMode.OFF)
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> Optional[HVACAction]:
|
||||
"""The current hvac action."""
|
||||
if self.hvac_mode == HVACMode.HEAT:
|
||||
return HVACAction.HEATING
|
||||
return HVACAction.OFF
|
||||
|
||||
|
||||
class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||
FeatureTargetHumidity, FeatureTemperature, FeatureHumidity,
|
||||
@ -482,10 +500,12 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._attr_icon = 'mdi:air-conditioner'
|
||||
# on/off
|
||||
self._init_on_off('air-conditioner', 'on')
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = None
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'mode':
|
||||
if prop.name == 'mode' and prop.service.name == 'air-conditioner':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
@ -549,6 +569,23 @@ class AirConditioner(FeatureOnOff, FeatureTargetTemperature,
|
||||
prop=self._prop_mode))
|
||||
if self._prop_mode else None)
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> Optional[HVACAction]:
|
||||
"""The current hvac action."""
|
||||
if self.hvac_mode is None:
|
||||
return None
|
||||
if self.hvac_mode == HVACMode.OFF:
|
||||
return HVACAction.OFF
|
||||
if self.hvac_mode == HVACMode.FAN_ONLY:
|
||||
return HVACAction.FAN
|
||||
if self.hvac_mode == HVACMode.COOL:
|
||||
return HVACAction.COOLING
|
||||
if self.hvac_mode == HVACMode.HEAT:
|
||||
return HVACAction.HEATING
|
||||
if self.hvac_mode == HVACMode.DRY:
|
||||
return HVACAction.DRYING
|
||||
return HVACAction.IDLE
|
||||
|
||||
def __ac_state_changed(self, prop: MIoTSpecProperty, value: Any) -> None:
|
||||
del prop
|
||||
if not isinstance(value, str):
|
||||
@ -620,22 +657,17 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
self._attr_icon = 'mdi:hvac'
|
||||
# hvac modes
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'mode':
|
||||
if prop.name == 'mode' and prop.service.name == 'ptc-bath-heater':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._hvac_mode_map = {}
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'off', 'idle'}:
|
||||
if (HVACMode.OFF
|
||||
not in list(self._hvac_mode_map.values())):
|
||||
self._hvac_mode_map[item.value] = HVACMode.OFF
|
||||
elif (HVACMode.AUTO
|
||||
not in list(self._hvac_mode_map.values())):
|
||||
self._hvac_mode_map[item.value] = HVACMode.AUTO
|
||||
self._attr_hvac_modes = list(self._hvac_mode_map.values())
|
||||
if HVACMode.OFF in self._attr_hvac_modes:
|
||||
self._prop_mode = prop
|
||||
self._hvac_mode_map[item.value] = HVACMode.OFF
|
||||
break
|
||||
if self._hvac_mode_map:
|
||||
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
|
||||
else:
|
||||
_LOGGER.error('no idle mode, %s', self.entity_id)
|
||||
# preset modes
|
||||
@ -643,7 +675,7 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the target hvac mode."""
|
||||
if self._prop_mode is None:
|
||||
if self._prop_mode is None or hvac_mode != HVACMode.OFF:
|
||||
return
|
||||
mode_value = self.get_map_key(map_=self._hvac_mode_map, value=hvac_mode)
|
||||
if mode_value is None or not await self.set_property_async(
|
||||
@ -656,13 +688,12 @@ class PtcBathHeater(FeatureTargetTemperature, FeatureTemperature,
|
||||
"""The current hvac mode."""
|
||||
if self._prop_mode is None:
|
||||
return None
|
||||
mode_value = self.get_map_value(
|
||||
map_=self._hvac_mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
if mode_value == HVACMode.OFF or mode_value is None:
|
||||
return mode_value
|
||||
return HVACMode.AUTO if (HVACMode.AUTO
|
||||
in self._attr_hvac_modes) else None
|
||||
current_mode = self.get_prop_value(prop=self._prop_mode)
|
||||
if current_mode is None:
|
||||
return None
|
||||
mode_value = self.get_map_value(map_=self._hvac_mode_map,
|
||||
key=current_mode)
|
||||
return HVACMode.OFF if mode_value == HVACMode.OFF else HVACMode.AUTO
|
||||
|
||||
|
||||
class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
@ -677,6 +708,8 @@ class Thermostat(FeatureOnOff, FeatureTargetTemperature, FeatureTemperature,
|
||||
self._attr_icon = 'mdi:thermostat'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = [HVACMode.AUTO, HVACMode.OFF]
|
||||
# on/off
|
||||
self._init_on_off('thermostat', 'on')
|
||||
# preset modes
|
||||
self._init_preset_modes('thermostat', 'mode')
|
||||
|
||||
@ -705,6 +738,8 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
||||
self._attr_icon = 'mdi:rug'
|
||||
# hvac modes
|
||||
self._attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
# on/off
|
||||
self._init_on_off('electric-blanket', 'on')
|
||||
# preset modes
|
||||
self._init_preset_modes('electric-blanket', 'mode')
|
||||
|
||||
@ -719,3 +754,10 @@ class ElectricBlanket(FeatureOnOff, FeatureTargetTemperature,
|
||||
"""The current hvac mode."""
|
||||
return (HVACMode.HEAT if self.get_prop_value(
|
||||
prop=self._prop_on) else HVACMode.OFF)
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> Optional[HVACAction]:
|
||||
"""The current hvac action."""
|
||||
if self.hvac_mode == HVACMode.OFF:
|
||||
return HVACAction.OFF
|
||||
return HVACAction.HEATING
|
||||
|
@ -75,6 +75,9 @@ from .miot.const import (
|
||||
DEFAULT_CLOUD_SERVER,
|
||||
DEFAULT_CTRL_MODE,
|
||||
DEFAULT_INTEGRATION_LANGUAGE,
|
||||
DEFAULT_COVER_CLOSED_POSITION,
|
||||
MIN_COVER_CLOSED_POSITION,
|
||||
MAX_COVER_CLOSED_POSITION,
|
||||
DEFAULT_NICK_NAME,
|
||||
DEFAULT_OAUTH2_API_HOST,
|
||||
DOMAIN,
|
||||
@ -129,6 +132,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
_cloud_server: str
|
||||
_integration_language: str
|
||||
_cover_closed_position: int
|
||||
_auth_info: dict
|
||||
_nick_name: str
|
||||
_home_selected: dict
|
||||
@ -151,6 +155,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._main_loop = asyncio.get_running_loop()
|
||||
self._cloud_server = DEFAULT_CLOUD_SERVER
|
||||
self._integration_language = DEFAULT_INTEGRATION_LANGUAGE
|
||||
self._cover_closed_position = DEFAULT_COVER_CLOSED_POSITION
|
||||
self._storage_path = ''
|
||||
self._virtual_did = ''
|
||||
self._uid = ''
|
||||
@ -565,27 +570,32 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
home_list = {}
|
||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||
# home list
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
# i18n
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
dev_list = {
|
||||
device['did']: device
|
||||
for device in list(self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
mips_list
|
||||
and group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in dev_list
|
||||
):
|
||||
for device_source in ['home_list','share_home_list',
|
||||
'separated_shared_list']:
|
||||
if device_source not in self._cc_home_info['homes']:
|
||||
continue
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes'][device_source].items():
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get('did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
dev_list = {
|
||||
device['did']: device
|
||||
for device in list(self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
mips_list
|
||||
and group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in dev_list
|
||||
):
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get(
|
||||
'did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(dev_list)} {tip_devices} {tip_central} ]')
|
||||
|
||||
self._cc_home_list_show = dict(sorted(home_list.items()))
|
||||
|
||||
@ -660,10 +670,14 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if not home_selected:
|
||||
return await self.__show_homes_select_form(
|
||||
'no_family_selected')
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
if home_id in home_selected:
|
||||
self._home_selected[home_id] = home_info
|
||||
for device_source in ['home_list','share_home_list',
|
||||
'separated_shared_list']:
|
||||
if device_source not in self._cc_home_info['homes']:
|
||||
continue
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes'][device_source].items():
|
||||
if home_id in home_selected:
|
||||
self._home_selected[home_id] = home_info
|
||||
self._area_name_rule = user_input.get(
|
||||
'area_name_rule', self._area_name_rule)
|
||||
# Storage device list
|
||||
@ -942,6 +956,7 @@ class XiaomiMihomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
'action_debug': self._action_debug,
|
||||
'hide_non_standard_entities':
|
||||
self._hide_non_standard_entities,
|
||||
'cover_closed_position': self._cover_closed_position,
|
||||
'display_binary_mode': self._display_binary_mode,
|
||||
'display_devices_changed_notify':
|
||||
self._display_devices_changed_notify
|
||||
@ -986,6 +1001,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
_hide_non_standard_entities: bool
|
||||
_display_binary_mode: list[str]
|
||||
_display_devs_notify: list[str]
|
||||
_cover_closed_position: int
|
||||
|
||||
_oauth_redirect_url_full: str
|
||||
_auth_info: dict
|
||||
@ -1006,6 +1022,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
_opt_lan_ctrl_cfg: bool
|
||||
_opt_network_detect_cfg: bool
|
||||
_opt_check_network_deps: bool
|
||||
_cover_pos_new: int
|
||||
|
||||
_trans_rules_count: int
|
||||
_trans_rules_count_success: int
|
||||
@ -1034,6 +1051,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
self._ctrl_mode = self._entry_data.get('ctrl_mode', DEFAULT_CTRL_MODE)
|
||||
self._integration_language = self._entry_data.get(
|
||||
'integration_language', DEFAULT_INTEGRATION_LANGUAGE)
|
||||
self._cover_closed_position = self._entry_data.get(
|
||||
'cover_closed_position', DEFAULT_COVER_CLOSED_POSITION)
|
||||
self._nick_name = self._entry_data.get('nick_name', DEFAULT_NICK_NAME)
|
||||
self._action_debug = self._entry_data.get('action_debug', False)
|
||||
self._hide_non_standard_entities = self._entry_data.get(
|
||||
@ -1059,6 +1078,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
self._action_debug_new = False
|
||||
self._hide_non_standard_entities_new = False
|
||||
self._display_binary_mode_new = []
|
||||
self._cover_pos_new = self._cover_closed_position
|
||||
self._update_user_info = False
|
||||
self._update_devices = False
|
||||
self._update_trans_rules = False
|
||||
@ -1331,6 +1351,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
): cv.multi_select(
|
||||
self._miot_i18n.translate(
|
||||
'config.binary_mode')), # type: ignore
|
||||
vol.Optional(
|
||||
'cover_closed_position',
|
||||
default=self._cover_closed_position # type: ignore
|
||||
): vol.All(vol.Coerce(int), vol.Range(
|
||||
min=MIN_COVER_CLOSED_POSITION,
|
||||
max=MAX_COVER_CLOSED_POSITION)),
|
||||
vol.Required(
|
||||
'update_trans_rules',
|
||||
default=self._update_trans_rules # type: ignore
|
||||
@ -1369,6 +1395,8 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
'update_lan_ctrl_config', self._opt_lan_ctrl_cfg)
|
||||
self._opt_network_detect_cfg = user_input.get(
|
||||
'network_detect_config', self._opt_network_detect_cfg)
|
||||
self._cover_pos_new = user_input.get(
|
||||
'cover_closed_position', self._cover_closed_position)
|
||||
|
||||
return await self.async_step_update_user_info()
|
||||
|
||||
@ -1420,27 +1448,31 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
home_list = {}
|
||||
tip_devices = self._miot_i18n.translate(key='config.other.devices')
|
||||
# home list
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
# i18n
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
did_list = {
|
||||
device['did']: device for device in list(
|
||||
self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in did_list
|
||||
):
|
||||
for device_source in ['home_list','share_home_list',
|
||||
'separated_shared_list']:
|
||||
if device_source not in self._cc_home_info['homes']:
|
||||
continue
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes'][device_source].items():
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get(
|
||||
'did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(did_list)} {tip_devices} {tip_central} ]')
|
||||
tip_central = ''
|
||||
group_id = home_info.get('group_id', None)
|
||||
did_list = {
|
||||
device['did']: device for device in list(
|
||||
self._cc_home_info['devices'].values())
|
||||
if device.get('home_id', None) == home_id}
|
||||
if (
|
||||
group_id in mips_list
|
||||
and mips_list[group_id].get('did', None) in did_list
|
||||
):
|
||||
# i18n
|
||||
tip_central = self._miot_i18n.translate(
|
||||
key='config.other.found_central_gateway')
|
||||
home_info['central_did'] = mips_list[group_id].get(
|
||||
'did', None)
|
||||
home_list[home_id] = (
|
||||
f'{home_info["home_name"]} '
|
||||
f'[ {len(did_list)} {tip_devices} {tip_central} ]')
|
||||
# Remove deleted item
|
||||
self._home_selected_list = [
|
||||
home_id for home_id in self._home_selected_list
|
||||
@ -1460,10 +1492,14 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
return await self.__show_homes_select_form('no_family_selected')
|
||||
self._ctrl_mode = user_input.get('ctrl_mode', self._ctrl_mode)
|
||||
self._home_selected = {}
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes']['home_list'].items():
|
||||
if home_id in self._home_selected_list:
|
||||
self._home_selected[home_id] = home_info
|
||||
for device_source in ['home_list','share_home_list',
|
||||
'separated_shared_list']:
|
||||
if device_source not in self._cc_home_info['homes']:
|
||||
continue
|
||||
for home_id, home_info in self._cc_home_info[
|
||||
'homes'][device_source].items():
|
||||
if home_id in self._home_selected_list:
|
||||
self._home_selected[home_id] = home_info
|
||||
# Get device list
|
||||
device_list: dict = {
|
||||
did: dev_info
|
||||
@ -1909,6 +1945,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
'nick_name': self._nick_name,
|
||||
'lang_new': INTEGRATION_LANGUAGES[self._lang_new],
|
||||
'nick_name_new': self._nick_name_new,
|
||||
'cover_pos_new': self._cover_pos_new,
|
||||
'devices_add': len(self._devices_add),
|
||||
'devices_remove': len(self._devices_remove),
|
||||
'trans_rules_count': self._trans_rules_count,
|
||||
@ -1935,6 +1972,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
if self._lang_new != self._integration_language:
|
||||
self._entry_data['integration_language'] = self._lang_new
|
||||
self._need_reload = True
|
||||
if self._cover_pos_new != self._cover_closed_position:
|
||||
self._entry_data['cover_closed_position'] = self._cover_pos_new
|
||||
self._need_reload = True
|
||||
if self._update_user_info:
|
||||
self._entry_data['nick_name'] = self._nick_name_new
|
||||
if self._update_devices:
|
||||
|
@ -46,8 +46,9 @@ off Xiaomi or its affiliates' products.
|
||||
Cover entities for Xiaomi Home.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
import re
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -90,6 +91,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry,
|
||||
class Cover(MIoTServiceEntity, CoverEntity):
|
||||
"""Cover entities for Xiaomi Home."""
|
||||
# pylint: disable=unused-argument
|
||||
_cover_closed_position: int
|
||||
_prop_motor_control: Optional[MIoTSpecProperty]
|
||||
_prop_motor_value_open: Optional[int]
|
||||
_prop_motor_value_close: Optional[int]
|
||||
@ -97,7 +99,6 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
_prop_status: Optional[MIoTSpecProperty]
|
||||
_prop_status_opening: Optional[list[int]]
|
||||
_prop_status_closing: Optional[list[int]]
|
||||
_prop_status_stop: Optional[list[int]]
|
||||
_prop_status_closed: Optional[list[int]]
|
||||
_prop_current_position: Optional[MIoTSpecProperty]
|
||||
_prop_target_position: Optional[MIoTSpecProperty]
|
||||
@ -115,6 +116,9 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
self._attr_supported_color_modes = set()
|
||||
self._attr_supported_features = CoverEntityFeature(0)
|
||||
|
||||
self._cover_closed_position = (
|
||||
miot_device.miot_client.cover_closed_position)
|
||||
|
||||
self._prop_motor_control = None
|
||||
self._prop_motor_value_open = None
|
||||
self._prop_motor_value_close = None
|
||||
@ -122,7 +126,6 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
self._prop_status = None
|
||||
self._prop_status_opening = []
|
||||
self._prop_status_closing = []
|
||||
self._prop_status_stop = []
|
||||
self._prop_status_closed = []
|
||||
self._prop_current_position = None
|
||||
self._prop_target_position = None
|
||||
@ -159,13 +162,22 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
self.entity_id)
|
||||
continue
|
||||
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)
|
||||
elif item.name in {'closing', 'close', 'down'}:
|
||||
elif item_name in {
|
||||
'closing', 'close', 'down', 'dowm', 'falling',
|
||||
'fallin', 'dropping', 'downing', 'lower'
|
||||
}:
|
||||
self._prop_status_closing.append(item.value)
|
||||
elif item.name in {'stop', 'stopped', 'pause'}:
|
||||
self._prop_status_stop.append(item.value)
|
||||
elif item.name in {'closed'}:
|
||||
elif item_name in {
|
||||
'closed', 'closeover', 'stopatlowest',
|
||||
'stoplowerlimit', 'lowerlimitstop', 'floor',
|
||||
'lowerlimit'
|
||||
}:
|
||||
self._prop_status_closed.append(item.value)
|
||||
self._prop_status = prop
|
||||
elif prop.name == 'current-position':
|
||||
@ -290,7 +302,7 @@ class Cover(MIoTServiceEntity, CoverEntity):
|
||||
def is_closed(self) -> Optional[bool]:
|
||||
"""Return if the cover is closed."""
|
||||
if self.current_cover_position is not None:
|
||||
return self.current_cover_position == 0
|
||||
return self.current_cover_position <= self._cover_closed_position
|
||||
# The current position is prior to the status when determining
|
||||
# whether the cover is closed.
|
||||
if self._prop_status and self._prop_status_closed:
|
||||
|
126
custom_components/xiaomi_home/device_tracker.py
Normal file
126
custom_components/xiaomi_home/device_tracker.py
Normal file
@ -0,0 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
Device tracker entities for Xiaomi Home.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.device_tracker import TrackerEntity
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
config_entry.entry_id]
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for data in miot_device.entity_list.get('device_tracker', []):
|
||||
new_entities.append(
|
||||
DeviceTracker(miot_device=miot_device, entity_data=data))
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
|
||||
class DeviceTracker(MIoTServiceEntity, TrackerEntity):
|
||||
"""Tracker entities for Xiaomi Home."""
|
||||
_prop_battery_level: Optional[MIoTSpecProperty]
|
||||
_prop_latitude: Optional[MIoTSpecProperty]
|
||||
_prop_longitude: Optional[MIoTSpecProperty]
|
||||
_prop_area_id: Optional[MIoTSpecProperty]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._prop_battery_level = None
|
||||
self._prop_latitude = None
|
||||
self._prop_longitude = None
|
||||
self._prop_area_id = None
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'battery-level':
|
||||
self._prop_battery_level = prop
|
||||
elif prop.name == 'latitude':
|
||||
self._prop_latitude = prop
|
||||
elif prop.name == 'longitude':
|
||||
self._prop_longitude = prop
|
||||
elif prop.name == 'area-id':
|
||||
self._prop_area_id = prop
|
||||
|
||||
@property
|
||||
def battery_level(self) -> Optional[int]:
|
||||
"""The battery level of the device."""
|
||||
return None if (self._prop_battery_level
|
||||
is None) else self.get_prop_value(
|
||||
prop=self._prop_battery_level)
|
||||
|
||||
@property
|
||||
def latitude(self) -> Optional[float]:
|
||||
"""The latitude coordinate of the device."""
|
||||
return None if self._prop_latitude is None else self.get_prop_value(
|
||||
prop=self._prop_latitude)
|
||||
|
||||
@property
|
||||
def longitude(self) -> Optional[float]:
|
||||
"""The longitude coordinate of the device."""
|
||||
return None if self._prop_longitude is None else self.get_prop_value(
|
||||
prop=self._prop_longitude)
|
||||
|
||||
@property
|
||||
def location_name(self) -> Optional[str]:
|
||||
"""The location name of the device."""
|
||||
return None if self._prop_area_id is None else self.get_prop_value(
|
||||
prop=self._prop_area_id)
|
@ -221,7 +221,7 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
# preset_mode
|
||||
if preset_mode:
|
||||
await self.set_property_async(
|
||||
self._prop_mode,
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=preset_mode))
|
||||
|
||||
@ -236,6 +236,9 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
async def async_set_percentage(self, percentage: int) -> None:
|
||||
"""Set the percentage of the fan speed."""
|
||||
if percentage > 0:
|
||||
if not self.is_on:
|
||||
# If the fan is off, turn it on.
|
||||
await self.set_property_async(prop=self._prop_on, value=True)
|
||||
if self._speed_names:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_fan_level,
|
||||
@ -249,16 +252,13 @@ class Fan(MIoTServiceEntity, FanEntity):
|
||||
value=int(percentage_to_ranged_value(
|
||||
low_high_range=(self._speed_min, self._speed_max),
|
||||
percentage=percentage)))
|
||||
if not self.is_on:
|
||||
# If the fan is off, turn it on.
|
||||
await self.set_property_async(prop=self._prop_on, value=True)
|
||||
else:
|
||||
await self.set_property_async(prop=self._prop_on, value=False)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set the preset mode."""
|
||||
await self.set_property_async(
|
||||
self._prop_mode,
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=preset_mode))
|
||||
|
||||
|
@ -52,23 +52,22 @@ from typing import Any, Optional
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.humidifier import (
|
||||
HumidifierEntity,
|
||||
HumidifierDeviceClass,
|
||||
HumidifierEntityFeature
|
||||
)
|
||||
from homeassistant.components.humidifier import (HumidifierEntity,
|
||||
HumidifierDeviceClass,
|
||||
HumidifierEntityFeature,
|
||||
HumidifierAction)
|
||||
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
@ -82,8 +81,8 @@ async def async_setup_entry(
|
||||
Humidifier(miot_device=miot_device, entity_data=data))
|
||||
for data in miot_device.entity_list.get('dehumidifier', []):
|
||||
data.device_class = HumidifierDeviceClass.DEHUMIDIFIER
|
||||
new_entities.append(Humidifier(
|
||||
miot_device=miot_device, entity_data=data))
|
||||
new_entities.append(
|
||||
Humidifier(miot_device=miot_device, entity_data=data))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
@ -99,9 +98,8 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
|
||||
_mode_map: dict[Any, Any]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
) -> None:
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the Humidifier."""
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._attr_device_class = entity_data.device_class
|
||||
@ -130,12 +128,10 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
# mode
|
||||
elif prop.name == 'mode':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_available_modes = list(
|
||||
self._mode_map.values())
|
||||
self._attr_available_modes = list(self._mode_map.values())
|
||||
self._attr_supported_features |= HumidifierEntityFeature.MODES
|
||||
self._prop_mode = prop
|
||||
# relative-humidity
|
||||
@ -152,33 +148,45 @@ class Humidifier(MIoTServiceEntity, HumidifierEntity):
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set new target humidity."""
|
||||
await self.set_property_async(
|
||||
prop=self._prop_target_humidity, value=humidity)
|
||||
if self._prop_target_humidity is None:
|
||||
return
|
||||
await self.set_property_async(prop=self._prop_target_humidity,
|
||||
value=humidity)
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set new target preset mode."""
|
||||
await self.set_property_async(
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(map_=self._mode_map, value=mode))
|
||||
await self.set_property_async(prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=mode))
|
||||
|
||||
@property
|
||||
def is_on(self) -> Optional[bool]:
|
||||
"""Return if the humidifier is on."""
|
||||
return self.get_prop_value(prop=self._prop_on)
|
||||
|
||||
@property
|
||||
def action(self) -> Optional[HumidifierAction]:
|
||||
"""Return the current status of the device."""
|
||||
if not self.is_on:
|
||||
return HumidifierAction.OFF
|
||||
if self._attr_device_class == HumidifierDeviceClass.HUMIDIFIER:
|
||||
return HumidifierAction.HUMIDIFYING
|
||||
return HumidifierAction.DRYING
|
||||
|
||||
@property
|
||||
def current_humidity(self) -> Optional[int]:
|
||||
"""Return the current humidity."""
|
||||
return self.get_prop_value(prop=self._prop_humidity)
|
||||
return (self.get_prop_value(
|
||||
prop=self._prop_humidity) if self._prop_humidity else None)
|
||||
|
||||
@property
|
||||
def target_humidity(self) -> Optional[int]:
|
||||
"""Return the target humidity."""
|
||||
return self.get_prop_value(prop=self._prop_target_humidity)
|
||||
return (self.get_prop_value(prop=self._prop_target_humidity)
|
||||
if self._prop_target_humidity else None)
|
||||
|
||||
@property
|
||||
def mode(self) -> Optional[str]:
|
||||
"""Return the current preset mode."""
|
||||
return self.get_map_value(
|
||||
map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
return self.get_map_value(map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
|
@ -25,7 +25,7 @@
|
||||
"cryptography",
|
||||
"psutil"
|
||||
],
|
||||
"version": "v0.2.2",
|
||||
"version": "v0.3.4",
|
||||
"zeroconf": [
|
||||
"_miot-central._tcp.local."
|
||||
]
|
||||
|
470
custom_components/xiaomi_home/media_player.py
Normal file
470
custom_components/xiaomi_home/media_player.py
Normal file
@ -0,0 +1,470 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2024 Xiaomi Corporation.
|
||||
|
||||
The ownership and intellectual property rights of Xiaomi Home Assistant
|
||||
Integration and related Xiaomi cloud service API interface provided under this
|
||||
license, including source code and object code (collectively, "Licensed Work"),
|
||||
are owned by Xiaomi. Subject to the terms and conditions of this License, Xiaomi
|
||||
hereby grants you a personal, limited, non-exclusive, non-transferable,
|
||||
non-sublicensable, and royalty-free license to reproduce, use, modify, and
|
||||
distribute the Licensed Work only for your use of Home Assistant for
|
||||
non-commercial purposes. For the avoidance of doubt, Xiaomi does not authorize
|
||||
you to use the Licensed Work for any other purpose, including but not limited
|
||||
to use Licensed Work to develop applications (APP), Web services, and other
|
||||
forms of software.
|
||||
|
||||
You may reproduce and distribute copies of the Licensed Work, with or without
|
||||
modifications, whether in source or object form, provided that you must give
|
||||
any other recipients of the Licensed Work a copy of this License and retain all
|
||||
copyright and disclaimers.
|
||||
|
||||
Xiaomi provides the Licensed Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied, including, without
|
||||
limitation, any warranties, undertakes, or conditions of TITLE, NO ERROR OR
|
||||
OMISSION, CONTINUITY, RELIABILITY, NON-INFRINGEMENT, MERCHANTABILITY, or
|
||||
FITNESS FOR A PARTICULAR PURPOSE. In any event, you are solely responsible
|
||||
for any direct, indirect, special, incidental, or consequential damages or
|
||||
losses arising from the use or inability to use the Licensed Work.
|
||||
|
||||
Xiaomi reserves all rights not expressly granted to you in this License.
|
||||
Except for the rights expressly granted by Xiaomi under this License, Xiaomi
|
||||
does not authorize you in any form to use the trademarks, copyrights, or other
|
||||
forms of intellectual property rights of Xiaomi and its affiliates, including,
|
||||
without limitation, without obtaining other written permission from Xiaomi, you
|
||||
shall not use "Xiaomi", "Mijia" and other words related to Xiaomi or words that
|
||||
may make the public associate with Xiaomi in any form to publicize or promote
|
||||
the software or hardware devices that use the Licensed Work.
|
||||
|
||||
Xiaomi has the right to immediately terminate all your authorization under this
|
||||
License in the event:
|
||||
1. You assert patent invalidation, litigation, or other claims against patents
|
||||
or other intellectual property rights of Xiaomi or its affiliates; or,
|
||||
2. You make, have made, manufacture, sell, or offer to sell products that knock
|
||||
off Xiaomi or its affiliates' products.
|
||||
|
||||
Media player entities for Xiaomi Home.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.media_player import (MediaPlayerEntity,
|
||||
MediaPlayerEntityFeature,
|
||||
MediaPlayerDeviceClass,
|
||||
MediaPlayerState, MediaType)
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||
from .miot.miot_spec import MIoTSpecProperty, MIoTSpecAction
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
config_entry.entry_id]
|
||||
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for data in miot_device.entity_list.get('wifi-speaker', []):
|
||||
new_entities.append(
|
||||
WifiSpeaker(miot_device=miot_device, entity_data=data))
|
||||
for data in miot_device.entity_list.get('television', []):
|
||||
new_entities.append(
|
||||
Television(miot_device=miot_device, entity_data=data))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
|
||||
class FeatureVolumeMute(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""VOLUME_MUTE feature of the media player entity."""
|
||||
_prop_mute: Optional[MIoTSpecProperty]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_mute = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'mute':
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.VOLUME_MUTE)
|
||||
self._prop_mute = prop
|
||||
|
||||
@property
|
||||
def is_volume_muted(self) -> Optional[bool]:
|
||||
"""True if volume is currently muted."""
|
||||
return self.get_prop_value(
|
||||
prop=self._prop_mute) if self._prop_mute else None
|
||||
|
||||
async def async_mute_volume(self, mute: bool) -> None:
|
||||
"""Mute the volume."""
|
||||
await self.set_property_async(prop=self._prop_mute, value=mute)
|
||||
|
||||
|
||||
class FeatureVolumeSet(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""VOLUME_SET feature of the media player entity."""
|
||||
_prop_volume: Optional[MIoTSpecProperty]
|
||||
_volume_value_min: Optional[float]
|
||||
_volume_value_max: Optional[float]
|
||||
_volume_value_range: Optional[float]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_volume = None
|
||||
self._volume_value_min = None
|
||||
self._volume_value_max = None
|
||||
self._volume_value_range = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'volume':
|
||||
if not prop.value_range:
|
||||
_LOGGER.error('invalid volume value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._volume_value_min = prop.value_range.min_
|
||||
self._volume_value_max = prop.value_range.max_
|
||||
self._volume_value_range = (prop.value_range.max_ -
|
||||
prop.value_range.min_)
|
||||
self._attr_volume_step = (prop.value_range.step /
|
||||
self._volume_value_range)
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.VOLUME_SET |
|
||||
MediaPlayerEntityFeature.VOLUME_STEP)
|
||||
self._prop_volume = prop
|
||||
|
||||
async def async_set_volume_level(self, volume: float) -> None:
|
||||
"""Set volume level."""
|
||||
value = volume * self._volume_value_range + self._volume_value_min
|
||||
if value > self._volume_value_max:
|
||||
value = self._volume_value_max
|
||||
elif value < self._volume_value_min:
|
||||
value = self._volume_value_min
|
||||
await self.set_property_async(prop=self._prop_volume, value=value)
|
||||
|
||||
@property
|
||||
def volume_level(self) -> Optional[float]:
|
||||
"""The current volume level, range [0, 1]."""
|
||||
value = self.get_prop_value(
|
||||
prop=self._prop_volume) if self._prop_volume else None
|
||||
if value is None:
|
||||
return None
|
||||
return (value - self._volume_value_min) / self._volume_value_range
|
||||
|
||||
|
||||
class FeaturePlay(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""PLAY feature of the media player entity."""
|
||||
_action_play: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_play = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'play':
|
||||
self._attr_supported_features |= (MediaPlayerEntityFeature.PLAY)
|
||||
self._action_play = act
|
||||
|
||||
async def async_media_play(self) -> None:
|
||||
"""Send play command."""
|
||||
await self.action_async(action=self._action_play)
|
||||
|
||||
|
||||
class FeaturePause(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""PAUSE feature of the media player entity."""
|
||||
_action_pause: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_pause = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'pause':
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.PAUSE)
|
||||
self._action_pause = act
|
||||
|
||||
async def async_media_pause(self) -> None:
|
||||
"""Send pause command."""
|
||||
await self.action_async(action=self._action_pause)
|
||||
|
||||
|
||||
class FeatureStop(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""STOP feature of the media player entity."""
|
||||
_action_stop: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_stop = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'stop':
|
||||
self._attr_supported_features |= (MediaPlayerEntityFeature.STOP)
|
||||
self._action_stop = act
|
||||
|
||||
async def async_media_stop(self) -> None:
|
||||
"""Send stop command."""
|
||||
await self.action_async(action=self._action_stop)
|
||||
|
||||
|
||||
class FeatureNextTrack(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""NEXT_TRACK feature of the media player entity."""
|
||||
_action_next: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_next = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'next':
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.NEXT_TRACK)
|
||||
self._action_next = act
|
||||
|
||||
async def async_media_next_track(self) -> None:
|
||||
"""Send next track command."""
|
||||
await self.action_async(action=self._action_next)
|
||||
|
||||
|
||||
class FeaturePreviousTrack(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""PREVIOUS_TRACK feature of the media player entity."""
|
||||
_action_previous: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_previous = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'previous':
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.PREVIOUS_TRACK)
|
||||
self._action_previous = act
|
||||
|
||||
async def async_media_previous_track(self) -> None:
|
||||
"""Send previous track command."""
|
||||
await self.action_async(action=self._action_previous)
|
||||
|
||||
|
||||
class FeatureSoundMode(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""SELECT_SOUND_MODE feature of the media player entity."""
|
||||
_prop_play_loop_mode: Optional[MIoTSpecProperty]
|
||||
_sound_mode_map: Optional[dict[int, str]]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_play_loop_mode = None
|
||||
self._sound_mode_map = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'play-loop-mode':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid play-loop-mode value_list, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._sound_mode_map = prop.value_list.to_map()
|
||||
self._attr_sound_mode_list = list(self._sound_mode_map.values())
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.SELECT_SOUND_MODE)
|
||||
self._prop_play_loop_mode = prop
|
||||
|
||||
async def async_select_sound_mode(self, sound_mode: str):
|
||||
"""Switch the sound mode of the entity."""
|
||||
await self.set_property_async(prop=self._prop_play_loop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._sound_mode_map,
|
||||
value=sound_mode))
|
||||
|
||||
@property
|
||||
def sound_mode(self) -> Optional[str]:
|
||||
"""The current sound mode."""
|
||||
return (self.get_map_value(map_=self._sound_mode_map,
|
||||
key=self.get_prop_value(
|
||||
prop=self._prop_play_loop_mode))
|
||||
if self._prop_play_loop_mode else None)
|
||||
|
||||
|
||||
class FeatureTurnOn(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""TURN_ON feature of the media player entity."""
|
||||
_action_turn_on: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_turn_on = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'turn-on':
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.TURN_ON)
|
||||
self._action_turn_on = act
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn the media player on."""
|
||||
await self.action_async(action=self._action_turn_on)
|
||||
|
||||
|
||||
class FeatureTurnOff(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""TURN_OFF feature of the media player entity."""
|
||||
_action_turn_off: Optional[MIoTSpecAction]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._action_turn_off = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# actions
|
||||
for act in entity_data.actions:
|
||||
if act.name == 'turn-off':
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.TURN_OFF)
|
||||
self._action_turn_off = act
|
||||
|
||||
async def async_turn_off(self) -> None:
|
||||
"""Turn the media player off."""
|
||||
await self.action_async(action=self._action_turn_off)
|
||||
|
||||
|
||||
class FeatureSource(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""SELECT_SOURCE feature of the media player entity."""
|
||||
_prop_input_control: Optional[MIoTSpecProperty]
|
||||
_input_source_map: Optional[dict[int, str]]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_input_control = None
|
||||
self._input_source_map = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'input-control':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid input-control value_list, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._input_source_map = prop.value_list.to_map()
|
||||
self._attr_source_list = list(self._input_source_map.values())
|
||||
self._attr_supported_features |= (
|
||||
MediaPlayerEntityFeature.SELECT_SOURCE)
|
||||
self._prop_input_control = prop
|
||||
|
||||
async def async_select_source(self, source: str) -> None:
|
||||
"""Select input source."""
|
||||
await self.set_property_async(prop=self._prop_input_control,
|
||||
value=self.get_map_key(
|
||||
map_=self._input_source_map,
|
||||
value=source))
|
||||
|
||||
@property
|
||||
def source(self) -> Optional[str]:
|
||||
"""The current input source."""
|
||||
return (self.get_map_value(map_=self._input_source_map,
|
||||
key=self.get_prop_value(
|
||||
prop=self._prop_input_control))
|
||||
if self._prop_input_control else None)
|
||||
|
||||
|
||||
class FeatureState(MIoTServiceEntity, MediaPlayerEntity):
|
||||
"""States feature of the media player entity."""
|
||||
_prop_playing_state: Optional[MIoTSpecProperty]
|
||||
_playing_state_map: Optional[dict[int, str]]
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the feature class."""
|
||||
self._prop_playing_state = None
|
||||
self._playing_state_map = None
|
||||
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'playing-state':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error('invalid mode value_list, %s', self.entity_id)
|
||||
continue
|
||||
self._playing_state_map = {}
|
||||
for item in prop.value_list.items:
|
||||
if item.name in {'off'}:
|
||||
self._playing_state_map[
|
||||
item.value] = MediaPlayerState.OFF
|
||||
elif item.name in {'idle', 'stop', 'stopped'}:
|
||||
self._playing_state_map[
|
||||
item.value] = MediaPlayerState.IDLE
|
||||
elif item.name in {'playing'}:
|
||||
self._playing_state_map[
|
||||
item.value] = MediaPlayerState.PLAYING
|
||||
elif item.name in {'pause', 'paused'}:
|
||||
self._playing_state_map[
|
||||
item.value] = MediaPlayerState.PAUSED
|
||||
self._prop_playing_state = prop
|
||||
|
||||
@property
|
||||
def state(self) -> Optional[MediaPlayerState]:
|
||||
"""The current state."""
|
||||
return (self.get_map_value(map_=self._playing_state_map,
|
||||
key=self.get_prop_value(
|
||||
prop=self._prop_playing_state))
|
||||
if self._prop_playing_state else MediaPlayerState.ON)
|
||||
|
||||
|
||||
class WifiSpeaker(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay,
|
||||
FeaturePause, FeatureStop, FeatureNextTrack,
|
||||
FeaturePreviousTrack, FeatureSoundMode, FeatureState):
|
||||
"""WiFi speaker, aka XiaoAI sound speaker."""
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the device."""
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
|
||||
self._attr_device_class = MediaPlayerDeviceClass.SPEAKER
|
||||
self._attr_media_content_type = MediaType.MUSIC
|
||||
|
||||
|
||||
class Television(FeatureVolumeSet, FeatureVolumeMute, FeaturePlay, FeaturePause,
|
||||
FeatureStop, FeatureNextTrack, FeaturePreviousTrack,
|
||||
FeatureSoundMode, FeatureState, FeatureSource, FeatureTurnOn,
|
||||
FeatureTurnOff):
|
||||
"""Television"""
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the device."""
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
|
||||
self._attr_device_class = MediaPlayerDeviceClass.TV
|
||||
self._attr_media_content_type = MediaType.VIDEO
|
@ -71,10 +71,12 @@ SUPPORTED_PLATFORMS: list = [
|
||||
'button',
|
||||
'climate',
|
||||
'cover',
|
||||
'device_tracker',
|
||||
'event',
|
||||
'fan',
|
||||
'humidifier',
|
||||
'light',
|
||||
'media_player',
|
||||
'notify',
|
||||
'number',
|
||||
'select',
|
||||
@ -85,6 +87,11 @@ SUPPORTED_PLATFORMS: list = [
|
||||
'water_heater',
|
||||
]
|
||||
|
||||
UNSUPPORTED_MODELS: list = [
|
||||
'chuangmi.ir.v2',
|
||||
'xiaomi.router.rd03'
|
||||
]
|
||||
|
||||
DEFAULT_CLOUD_SERVER: str = 'cn'
|
||||
CLOUD_SERVERS: dict = {
|
||||
'cn': '中国大陆',
|
||||
@ -113,6 +120,10 @@ INTEGRATION_LANGUAGES = {
|
||||
'zh-Hant': '繁體中文'
|
||||
}
|
||||
|
||||
DEFAULT_COVER_CLOSED_POSITION: int = 0
|
||||
MIN_COVER_CLOSED_POSITION: int = 0
|
||||
MAX_COVER_CLOSED_POSITION: int = 5
|
||||
|
||||
DEFAULT_CTRL_MODE: str = 'auto'
|
||||
|
||||
# Registered in Xiaomi OAuth 2.0 Service
|
||||
|
@ -63,7 +63,8 @@ from .common import MIoTMatcher, slugify_did
|
||||
from .const import (
|
||||
DEFAULT_CTRL_MODE, DEFAULT_INTEGRATION_LANGUAGE, DEFAULT_NICK_NAME, DOMAIN,
|
||||
MIHOME_CERT_EXPIRE_MARGIN, NETWORK_REFRESH_INTERVAL,
|
||||
OAUTH2_CLIENT_ID, SUPPORT_CENTRAL_GATEWAY_CTRL)
|
||||
OAUTH2_CLIENT_ID, SUPPORT_CENTRAL_GATEWAY_CTRL,
|
||||
DEFAULT_COVER_CLOSED_POSITION)
|
||||
from .miot_cloud import MIoTHttpClient, MIoTOauthClient
|
||||
from .miot_error import MIoTClientError, MIoTErrorCode
|
||||
from .miot_mips import (
|
||||
@ -486,6 +487,11 @@ class MIoTClient:
|
||||
def display_binary_bool(self) -> bool:
|
||||
return self._display_binary_bool
|
||||
|
||||
@property
|
||||
def cover_closed_position(self) -> int:
|
||||
return self._entry_data.get('cover_closed_position',
|
||||
DEFAULT_COVER_CLOSED_POSITION)
|
||||
|
||||
@display_devices_changed_notify.setter
|
||||
def display_devices_changed_notify(self, value: list[str]) -> None:
|
||||
if set(value) == set(self._display_devs_notify):
|
||||
@ -629,11 +635,14 @@ class MIoTClient:
|
||||
mips = self._mips_local.get(device_gw['group_id'], None)
|
||||
if mips is None:
|
||||
_LOGGER.error(
|
||||
'no gw route, %s, try control throw cloud',
|
||||
'no gateway route, %s, try control through cloud',
|
||||
device_gw)
|
||||
else:
|
||||
result = await mips.set_prop_async(
|
||||
did=did, siid=siid, piid=piid, value=value)
|
||||
_LOGGER.debug(
|
||||
'gateway set prop, %s.%d.%d, %s -> %s',
|
||||
did, siid, piid, value, result)
|
||||
rc = (result or {}).get(
|
||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||
if rc in [0, 1]:
|
||||
@ -646,7 +655,8 @@ class MIoTClient:
|
||||
result = await self._miot_lan.set_prop_async(
|
||||
did=did, siid=siid, piid=piid, value=value)
|
||||
_LOGGER.debug(
|
||||
'lan set prop, %s, %s, %s -> %s', did, siid, piid, result)
|
||||
'lan set prop, %s.%d.%d, %s -> %s',
|
||||
did, siid, piid, value, result)
|
||||
rc = (result or {}).get(
|
||||
'code', MIoTErrorCode.CODE_MIPS_INVALID_RESULT.value)
|
||||
if rc in [0, 1]:
|
||||
@ -662,7 +672,7 @@ class MIoTClient:
|
||||
{'did': did, 'siid': siid, 'piid': piid, 'value': value}
|
||||
])
|
||||
_LOGGER.debug(
|
||||
'set prop response, %s.%d.%d, %s, result, %s',
|
||||
'cloud set prop, %s.%d.%d, %s -> %s',
|
||||
did, siid, piid, value, result)
|
||||
if result and len(result) == 1:
|
||||
rc = result[0].get(
|
||||
@ -879,16 +889,7 @@ class MIoTClient:
|
||||
sub_from = self._sub_source_list.pop(did, None)
|
||||
# Unsub
|
||||
if sub_from:
|
||||
if sub_from == 'cloud':
|
||||
self._mips_cloud.unsub_prop(did=did)
|
||||
self._mips_cloud.unsub_event(did=did)
|
||||
elif sub_from == 'lan':
|
||||
self._miot_lan.unsub_prop(did=did)
|
||||
self._miot_lan.unsub_event(did=did)
|
||||
elif sub_from in self._mips_local:
|
||||
mips = self._mips_local[sub_from]
|
||||
mips.unsub_prop(did=did)
|
||||
mips.unsub_event(did=did)
|
||||
self.__unsub_from(sub_from, did)
|
||||
# Storage
|
||||
await self._storage.save_async(
|
||||
domain='miot_devices',
|
||||
@ -936,6 +937,39 @@ class MIoTClient:
|
||||
delay_sec, lambda: self._main_loop.create_task(
|
||||
self.refresh_user_cert_async()))
|
||||
|
||||
@final
|
||||
def __unsub_from(self, sub_from: str, did: str) -> None:
|
||||
mips: Any = None
|
||||
if sub_from == 'cloud':
|
||||
mips = self._mips_cloud
|
||||
elif sub_from == 'lan':
|
||||
mips = self._miot_lan
|
||||
elif sub_from in self._mips_local:
|
||||
mips = self._mips_local[sub_from]
|
||||
if mips is not None:
|
||||
try:
|
||||
mips.unsub_prop(did=did)
|
||||
mips.unsub_event(did=did)
|
||||
except RuntimeError as e:
|
||||
if 'Event loop is closed' in str(e):
|
||||
# Ignore unsub exception when loop is closed
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
@final
|
||||
def __sub_from(self, sub_from: str, did: str) -> None:
|
||||
mips = None
|
||||
if sub_from == 'cloud':
|
||||
mips = self._mips_cloud
|
||||
elif sub_from == 'lan':
|
||||
mips = self._miot_lan
|
||||
elif sub_from in self._mips_local:
|
||||
mips = self._mips_local[sub_from]
|
||||
if mips is not None:
|
||||
mips.sub_prop(did=did, handler=self.__on_prop_msg)
|
||||
mips.sub_event(did=did, handler=self.__on_event_msg)
|
||||
|
||||
@final
|
||||
def __update_device_msg_sub(self, did: str) -> None:
|
||||
if did not in self._device_list_cache:
|
||||
@ -962,32 +996,14 @@ class MIoTClient:
|
||||
and self._device_list_cloud[did].get('online', False)
|
||||
):
|
||||
from_new = 'cloud'
|
||||
if from_new == from_old:
|
||||
if (from_new == from_old) and (from_new=='cloud' or from_new=='lan'):
|
||||
# No need to update
|
||||
return
|
||||
# Unsub old
|
||||
if from_old:
|
||||
if from_old == 'cloud':
|
||||
self._mips_cloud.unsub_prop(did=did)
|
||||
self._mips_cloud.unsub_event(did=did)
|
||||
elif from_old == 'lan':
|
||||
self._miot_lan.unsub_prop(did=did)
|
||||
self._miot_lan.unsub_event(did=did)
|
||||
elif from_old in self._mips_local:
|
||||
mips = self._mips_local[from_old]
|
||||
mips.unsub_prop(did=did)
|
||||
mips.unsub_event(did=did)
|
||||
self.__unsub_from(from_old, did)
|
||||
# Sub new
|
||||
if from_new == 'cloud':
|
||||
self._mips_cloud.sub_prop(did=did, handler=self.__on_prop_msg)
|
||||
self._mips_cloud.sub_event(did=did, handler=self.__on_event_msg)
|
||||
elif from_new == 'lan':
|
||||
self._miot_lan.sub_prop(did=did, handler=self.__on_prop_msg)
|
||||
self._miot_lan.sub_event(did=did, handler=self.__on_event_msg)
|
||||
elif from_new in self._mips_local:
|
||||
mips = self._mips_local[from_new]
|
||||
mips.sub_prop(did=did, handler=self.__on_prop_msg)
|
||||
mips.sub_event(did=did, handler=self.__on_event_msg)
|
||||
self.__sub_from(from_new, did)
|
||||
self._sub_source_list[did] = from_new
|
||||
_LOGGER.info(
|
||||
'device sub changed, %s, from %s to %s', did, from_old, from_new)
|
||||
@ -1344,6 +1360,11 @@ class MIoTClient:
|
||||
"""Update cloud devices.
|
||||
NOTICE: This function will operate the cloud_list
|
||||
"""
|
||||
# MIoT cloud service may not publish the online state updating message
|
||||
# for the BLE device. Assume that all BLE devices are online.
|
||||
for did, info in cloud_list.items():
|
||||
if did.startswith('blt.'):
|
||||
info['online'] = True
|
||||
for did, info in self._device_list_cache.items():
|
||||
if filter_dids and did not in filter_dids:
|
||||
continue
|
||||
@ -1471,8 +1492,6 @@ class MIoTClient:
|
||||
if did not in filter_dids:
|
||||
continue
|
||||
device_old = self._device_list_gateway.get(did, None)
|
||||
gw_state_old = device_old.get(
|
||||
'online', False) if device_old else False
|
||||
gw_state_new: bool = False
|
||||
device_new = gw_list.pop(did, None)
|
||||
if device_new:
|
||||
@ -1486,7 +1505,7 @@ class MIoTClient:
|
||||
device_old['online'] = False
|
||||
# Update cache group_id
|
||||
info['group_id'] = group_id
|
||||
if gw_state_old == gw_state_new:
|
||||
if not gw_state_new:
|
||||
continue
|
||||
self.__update_device_msg_sub(did=did)
|
||||
state_old: Optional[bool] = info.get('online', None)
|
||||
|
@ -59,6 +59,7 @@ import aiohttp
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import calc_group_id
|
||||
from .const import (
|
||||
UNSUPPORTED_MODELS,
|
||||
DEFAULT_OAUTH2_API_HOST,
|
||||
MIHOME_HTTP_API_TIMEOUT,
|
||||
OAUTH2_AUTH_URL)
|
||||
@ -444,6 +445,17 @@ class MIoTHttpClient:
|
||||
|
||||
return home_list
|
||||
|
||||
async def get_separated_shared_devices_async(self) -> dict[str, dict]:
|
||||
separated_shared_devices: dict = {}
|
||||
device_list: dict[str, dict] = await self.__get_device_list_page_async(
|
||||
dids=[], start_did=None)
|
||||
for did, value in device_list.items():
|
||||
if value['owner'] is not None and ('userid' in value['owner']) and (
|
||||
'nickname' in value['owner']
|
||||
):
|
||||
separated_shared_devices.setdefault(did, value['owner'])
|
||||
return separated_shared_devices
|
||||
|
||||
async def get_homeinfos_async(self) -> dict:
|
||||
res_obj = await self.__mihome_api_post_async(
|
||||
url_path='/app/v2/homeroom/gethome',
|
||||
@ -499,19 +511,22 @@ class MIoTHttpClient:
|
||||
):
|
||||
more_list = await self.__get_dev_room_page_async(
|
||||
max_id=res_obj['result']['max_id'])
|
||||
for home_id, info in more_list.items():
|
||||
if home_id not in home_infos['homelist']:
|
||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||
continue
|
||||
home_infos['homelist'][home_id]['dids'].extend(info['dids'])
|
||||
for room_id, info in info['room_info'].items():
|
||||
home_infos['homelist'][home_id]['room_info'].setdefault(
|
||||
room_id, {
|
||||
'room_id': room_id,
|
||||
'room_name': '',
|
||||
'dids': []})
|
||||
home_infos['homelist'][home_id]['room_info'][
|
||||
room_id]['dids'].extend(info['dids'])
|
||||
for device_source in ['homelist', 'share_home_list']:
|
||||
for home_id, info in more_list.items():
|
||||
if home_id not in home_infos[device_source]:
|
||||
_LOGGER.info('unknown home, %s, %s', home_id, info)
|
||||
continue
|
||||
home_infos[device_source][home_id]['dids'].extend(
|
||||
info['dids'])
|
||||
for room_id, info in info['room_info'].items():
|
||||
home_infos[device_source][home_id][
|
||||
'room_info'].setdefault(
|
||||
room_id, {
|
||||
'room_id': room_id,
|
||||
'room_name': '',
|
||||
'dids': []})
|
||||
home_infos[device_source][home_id]['room_info'][
|
||||
room_id]['dids'].extend(info['dids'])
|
||||
|
||||
return {
|
||||
'uid': uid,
|
||||
@ -528,6 +543,7 @@ class MIoTHttpClient:
|
||||
req_data: dict = {
|
||||
'limit': 200,
|
||||
'get_split_device': True,
|
||||
'get_third_device': True,
|
||||
'dids': dids
|
||||
}
|
||||
if start_did:
|
||||
@ -559,6 +575,10 @@ class MIoTHttpClient:
|
||||
# were implemented.
|
||||
_LOGGER.info('ignore miwifi.* device, cloud, %s', did)
|
||||
continue
|
||||
if model in UNSUPPORTED_MODELS:
|
||||
_LOGGER.info('ignore unsupported model %s, cloud, %s',
|
||||
model, did)
|
||||
continue
|
||||
device_infos[did] = {
|
||||
'did': did,
|
||||
'uid': device.get('uid', None),
|
||||
@ -651,6 +671,25 @@ class MIoTHttpClient:
|
||||
'room_name': room_name,
|
||||
'group_id': group_id
|
||||
} for did in room_info.get('dids', [])})
|
||||
separated_shared_devices: dict = (
|
||||
await self.get_separated_shared_devices_async())
|
||||
if separated_shared_devices:
|
||||
homes.setdefault('separated_shared_list', {})
|
||||
for did, owner in separated_shared_devices.items():
|
||||
owner_id = str(owner['userid'])
|
||||
homes['separated_shared_list'].setdefault(owner_id,{
|
||||
'home_name': owner['nickname'],
|
||||
'uid': owner_id,
|
||||
'group_id': 'NotSupport',
|
||||
'room_info': {'shared_device': 'shared_device'}
|
||||
})
|
||||
devices.update({did: {
|
||||
'home_id': owner_id,
|
||||
'home_name': owner['nickname'],
|
||||
'room_id': 'shared_device',
|
||||
'room_name': 'shared_device',
|
||||
'group_id': 'NotSupport'
|
||||
}})
|
||||
dids = sorted(list(devices.keys()))
|
||||
results = await self.get_devices_with_dids_async(dids=dids)
|
||||
if results is None:
|
||||
|
@ -345,10 +345,11 @@ class MIoTDevice:
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}')
|
||||
|
||||
def gen_service_entity_id(self, ha_domain: str, siid: int) -> str:
|
||||
def gen_service_entity_id(self, ha_domain: str, siid: int,
|
||||
description: str) -> str:
|
||||
return (
|
||||
f'{ha_domain}.{self._model_strs[0][:9]}_{self.did_tag}_'
|
||||
f'{self._model_strs[-1][:20]}_s_{siid}')
|
||||
f'{self._model_strs[-1][:20]}_s_{siid}_{description}')
|
||||
|
||||
def gen_prop_entity_id(
|
||||
self, ha_domain: str, spec_name: str, siid: int, piid: int
|
||||
@ -778,8 +779,10 @@ class MIoTDevice:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant.const import UnitOfConductivity # type: ignore
|
||||
unit_map['μS/cm'] = UnitOfConductivity.MICROSIEMENS_PER_CM
|
||||
unit_map['mWh'] = UnitOfEnergy.MILLIWATT_HOUR
|
||||
except Exception: # pylint: disable=broad-except
|
||||
unit_map['μS/cm'] = 'μS/cm'
|
||||
unit_map['mWh'] = 'mWh'
|
||||
|
||||
return unit_map.get(spec_unit, None)
|
||||
|
||||
@ -894,7 +897,8 @@ class MIoTServiceEntity(Entity):
|
||||
self._attr_name = f' {self.entity_data.spec.description_trans}'
|
||||
elif isinstance(self.entity_data.spec, MIoTSpecService):
|
||||
self.entity_id = miot_device.gen_service_entity_id(
|
||||
DOMAIN, siid=self.entity_data.spec.iid)
|
||||
DOMAIN, siid=self.entity_data.spec.iid,
|
||||
description=self.entity_data.spec.description)
|
||||
self._attr_name = (
|
||||
f'{"* "if self.entity_data.spec.proprietary else " "}'
|
||||
f'{self.entity_data.spec.description_trans}')
|
||||
@ -1203,10 +1207,9 @@ class MIoTPropertyEntity(Entity):
|
||||
self._attr_available = miot_device.online
|
||||
|
||||
_LOGGER.info(
|
||||
'new miot property entity, %s, %s, %s, %s, %s, %s, %s',
|
||||
'new miot property entity, %s, %s, %s, %s, %s',
|
||||
self.miot_device.name, self._attr_name, spec.platform,
|
||||
spec.device_class, self.entity_id, self._value_range,
|
||||
self._value_list)
|
||||
spec.device_class, self.entity_id)
|
||||
|
||||
@property
|
||||
def device_info(self) -> Optional[DeviceInfo]:
|
||||
|
@ -226,7 +226,7 @@ class _MIoTLanDevice:
|
||||
def gen_packet(
|
||||
self, out_buffer: bytearray, clear_data: dict, did: str, offset: int
|
||||
) -> int:
|
||||
clear_bytes = json.dumps(clear_data).encode('utf-8')
|
||||
clear_bytes = json.dumps(clear_data, ensure_ascii=False).encode('utf-8')
|
||||
padder = padding.PKCS7(algorithms.AES128.block_size).padder()
|
||||
padded_data = padder.update(clear_bytes) + padder.finalize()
|
||||
if len(padded_data) + self.OT_HEADER_LEN > len(out_buffer):
|
||||
|
@ -110,7 +110,6 @@ class MipsServiceData:
|
||||
version=IPVersion.V4Only)
|
||||
if not self.addresses:
|
||||
raise MipsServiceError('invalid addresses')
|
||||
self.addresses.sort()
|
||||
if not service_info.port:
|
||||
raise MipsServiceError('invalid port')
|
||||
self.port = service_info.port
|
||||
@ -226,7 +225,7 @@ class MipsService:
|
||||
state_change: ServiceStateChange
|
||||
) -> None:
|
||||
_LOGGER.debug(
|
||||
'mips service state changed, %s, %s, %s',
|
||||
'mdns discovery changed, %s, %s, %s',
|
||||
state_change, name, service_type)
|
||||
|
||||
if state_change is ServiceStateChange.Removed:
|
||||
|
@ -60,6 +60,7 @@ from typing import Any, Callable, Optional, final, Coroutine
|
||||
|
||||
from paho.mqtt.client import (
|
||||
MQTT_ERR_SUCCESS,
|
||||
MQTT_ERR_NO_CONN,
|
||||
MQTT_ERR_UNKNOWN,
|
||||
Client,
|
||||
MQTTv5,
|
||||
@ -67,7 +68,7 @@ from paho.mqtt.client import (
|
||||
|
||||
# pylint: disable=relative-beyond-top-level
|
||||
from .common import MIoTMatcher
|
||||
from .const import MIHOME_MQTT_KEEPALIVE
|
||||
from .const import UNSUPPORTED_MODELS, MIHOME_MQTT_KEEPALIVE
|
||||
from .miot_error import MIoTErrorCode, MIoTMipsError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -215,7 +216,7 @@ class _MipsClient(ABC):
|
||||
MQTT_INTERVAL_S = 1
|
||||
MIPS_QOS: int = 2
|
||||
UINT32_MAX: int = 0xFFFFFFFF
|
||||
MIPS_RECONNECT_INTERVAL_MIN: float = 30
|
||||
MIPS_RECONNECT_INTERVAL_MIN: float = 10
|
||||
MIPS_RECONNECT_INTERVAL_MAX: float = 600
|
||||
MIPS_SUB_PATCH: int = 300
|
||||
MIPS_SUB_INTERVAL: float = 1
|
||||
@ -533,7 +534,7 @@ class _MipsClient(ABC):
|
||||
return
|
||||
try:
|
||||
result, mid = self._mqtt.unsubscribe(topic=topic)
|
||||
if result == MQTT_ERR_SUCCESS:
|
||||
if (result == MQTT_ERR_SUCCESS) or (result == MQTT_ERR_NO_CONN):
|
||||
self.log_debug(
|
||||
f'mips unsub internal success, {result}, {mid}, {topic}')
|
||||
return
|
||||
@ -640,6 +641,7 @@ class _MipsClient(ABC):
|
||||
if not self._mqtt.is_connected():
|
||||
return
|
||||
self.log_info(f'mips connect, {flags}, {rc}, {props}')
|
||||
self.__reset_reconnect_time()
|
||||
self._mqtt_state = True
|
||||
self._internal_loop.call_soon(
|
||||
self._on_mips_connect, rc, props)
|
||||
@ -821,7 +823,7 @@ class _MipsClient(ABC):
|
||||
self._internal_loop.stop()
|
||||
|
||||
def __get_next_reconnect_time(self) -> float:
|
||||
if self._mips_reconnect_interval == 0:
|
||||
if self._mips_reconnect_interval < self.MIPS_RECONNECT_INTERVAL_MIN:
|
||||
self._mips_reconnect_interval = self.MIPS_RECONNECT_INTERVAL_MIN
|
||||
else:
|
||||
self._mips_reconnect_interval = min(
|
||||
@ -829,6 +831,9 @@ class _MipsClient(ABC):
|
||||
self.MIPS_RECONNECT_INTERVAL_MAX)
|
||||
return self._mips_reconnect_interval
|
||||
|
||||
def __reset_reconnect_time(self) -> None:
|
||||
self._mips_reconnect_interval = 0
|
||||
|
||||
|
||||
class MipsCloudClient(_MipsClient):
|
||||
"""MIoT Pub/Sub Cloud Client."""
|
||||
@ -989,6 +994,11 @@ class MipsCloudClient(_MipsClient):
|
||||
handler(
|
||||
did, MIoTDeviceState.ONLINE if msg['event'] == 'online'
|
||||
else MIoTDeviceState.OFFLINE, ctx)
|
||||
|
||||
if did.startswith('blt.'):
|
||||
# MIoT cloud may not publish BLE device online/offline state message.
|
||||
# Do not subscribe BLE device online/offline state.
|
||||
return True
|
||||
return self.__reg_broadcast_external(
|
||||
topic=topic, handler=on_state_msg, handler_ctx=handler_ctx)
|
||||
|
||||
@ -1173,7 +1183,7 @@ class MipsLocalClient(_MipsClient):
|
||||
or 'piid' 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
|
||||
if handler:
|
||||
self.log_debug('local, on properties_changed, %s', payload)
|
||||
@ -1215,7 +1225,7 @@ class MipsLocalClient(_MipsClient):
|
||||
or 'eiid' not in msg
|
||||
# or 'arguments' not in msg
|
||||
):
|
||||
self.log_error('unknown event msg, %s', payload)
|
||||
self.log_info('unknown event msg, %s', payload)
|
||||
return
|
||||
if 'arguments' not in msg:
|
||||
self.log_info('wrong event msg, %s', payload)
|
||||
@ -1360,6 +1370,9 @@ class MipsLocalClient(_MipsClient):
|
||||
if name is None or urn is None or model is None:
|
||||
self.log_error(f'invalid device info, {did}, {info}')
|
||||
continue
|
||||
if model in UNSUPPORTED_MODELS:
|
||||
self.log_info(f'unsupported model, {model}, {did}')
|
||||
continue
|
||||
device_list[did] = {
|
||||
'did': did,
|
||||
'name': name,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,172 +1,290 @@
|
||||
{
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"de": {
|
||||
"service:001": "Geräteinformationen",
|
||||
"service:001:property:003": "Geräte-ID",
|
||||
"service:001:property:005": "Seriennummer (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Netzwerk geändert",
|
||||
"service:002:event:002": "Netzwerk geändert",
|
||||
"service:002:property:001": "Zugriffsmethode",
|
||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
||||
"service:002:property:002": "IP-Adresse",
|
||||
"service:002:property:003": "WiFi-Netzwerkname",
|
||||
"service:002:property:004": "Aktuelle Zeit",
|
||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
||||
"service:003": "Anzeigelampe",
|
||||
"service:003:property:001": "Schalter",
|
||||
"service:004": "Virtueller Dienst",
|
||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
||||
"service:004:property:001": "Ereignisname"
|
||||
},
|
||||
"en": {
|
||||
"service:001": "Device Information",
|
||||
"service:001:property:003": "Device ID",
|
||||
"service:001:property:005": "Serial Number (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Network Changed",
|
||||
"service:002:event:002": "Network Changed",
|
||||
"service:002:property:001": "Access Method",
|
||||
"service:002:property:001:valuelist:000": "Wired",
|
||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
||||
"service:002:property:002": "IP Address",
|
||||
"service:002:property:003": "WiFi Network Name",
|
||||
"service:002:property:004": "Current Time",
|
||||
"service:002:property:005": "DHCP Server MAC Address",
|
||||
"service:003": "Indicator Light",
|
||||
"service:003:property:001": "Switch",
|
||||
"service:004": "Virtual Service",
|
||||
"service:004:action:001": "Generate Virtual Event",
|
||||
"service:004:event:001": "Virtual Event Occurred",
|
||||
"service:004:property:001": "Event Name"
|
||||
},
|
||||
"es": {
|
||||
"service:001": "Información del dispositivo",
|
||||
"service:001:property:003": "ID del dispositivo",
|
||||
"service:001:property:005": "Número de serie (SN)",
|
||||
"service:002": "Puerta de enlace",
|
||||
"service:002:event:001": "Cambio de red",
|
||||
"service:002:event:002": "Cambio de red",
|
||||
"service:002:property:001": "Método de acceso",
|
||||
"service:002:property:001:valuelist:000": "Cableado",
|
||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
||||
"service:002:property:002": "Dirección IP",
|
||||
"service:002:property:003": "Nombre de red WiFi",
|
||||
"service:002:property:004": "Hora actual",
|
||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
||||
"service:003": "Luz indicadora",
|
||||
"service:003:property:001": "Interruptor",
|
||||
"service:004": "Servicio virtual",
|
||||
"service:004:action:001": "Generar evento virtual",
|
||||
"service:004:event:001": "Ocurrió un evento virtual",
|
||||
"service:004:property:001": "Nombre del evento"
|
||||
},
|
||||
"fr": {
|
||||
"service:001": "Informations sur l'appareil",
|
||||
"service:001:property:003": "ID de l'appareil",
|
||||
"service:001:property:005": "Numéro de série (SN)",
|
||||
"service:002": "Passerelle",
|
||||
"service:002:event:001": "Changement de réseau",
|
||||
"service:002:event:002": "Changement de réseau",
|
||||
"service:002:property:001": "Méthode d'accès",
|
||||
"service:002:property:001:valuelist:000": "Câblé",
|
||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
||||
"service:002:property:002": "Adresse IP",
|
||||
"service:002:property:003": "Nom du réseau WiFi",
|
||||
"service:002:property:004": "Heure actuelle",
|
||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
||||
"service:003": "Voyant lumineux",
|
||||
"service:003:property:001": "Interrupteur",
|
||||
"service:004": "Service virtuel",
|
||||
"service:004:action:001": "Générer un événement virtuel",
|
||||
"service:004:event:001": "Événement virtuel survenu",
|
||||
"service:004:property:001": "Nom de l'événement"
|
||||
},
|
||||
"ja": {
|
||||
"service:001": "デバイス情報",
|
||||
"service:001:property:003": "デバイスID",
|
||||
"service:001:property:005": "シリアル番号 (SN)",
|
||||
"service:002": "ゲートウェイ",
|
||||
"service:002:event:001": "ネットワークが変更されました",
|
||||
"service:002:event:002": "ネットワークが変更されました",
|
||||
"service:002:property:001": "アクセス方法",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
||||
"service:002:property:002": "IPアドレス",
|
||||
"service:002:property:003": "WiFiネットワーク名",
|
||||
"service:002:property:004": "現在の時間",
|
||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
||||
"service:003": "インジケータライト",
|
||||
"service:003:property:001": "スイッチ",
|
||||
"service:004": "バーチャルサービス",
|
||||
"service:004:action:001": "バーチャルイベントを生成",
|
||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
||||
"service:004:property:001": "イベント名"
|
||||
},
|
||||
"ru": {
|
||||
"service:001": "Информация об устройстве",
|
||||
"service:001:property:003": "ID устройства",
|
||||
"service:001:property:005": "Серийный номер (SN)",
|
||||
"service:002": "Шлюз",
|
||||
"service:002:event:001": "Сеть изменена",
|
||||
"service:002:event:002": "Сеть изменена",
|
||||
"service:002:property:001": "Метод доступа",
|
||||
"service:002:property:001:valuelist:000": "Проводной",
|
||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
||||
"service:002:property:002": "IP Адрес",
|
||||
"service:002:property:003": "Название WiFi сети",
|
||||
"service:002:property:004": "Текущее время",
|
||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
||||
"service:003": "Световой индикатор",
|
||||
"service:003:property:001": "Переключатель",
|
||||
"service:004": "Виртуальная служба",
|
||||
"service:004:action:001": "Создать виртуальное событие",
|
||||
"service:004:event:001": "Произошло виртуальное событие",
|
||||
"service:004:property:001": "Название события"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"service:001": "設備信息",
|
||||
"service:001:property:003": "設備ID",
|
||||
"service:001:property:005": "序號 (SN)",
|
||||
"service:002": "網關",
|
||||
"service:002:event:001": "網路發生變化",
|
||||
"service:002:event:002": "網路發生變化",
|
||||
"service:002:property:001": "接入方式",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G 無線",
|
||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
||||
"service:002:property:002": "IP地址",
|
||||
"service:002:property:003": "WiFi網路名稱",
|
||||
"service:002:property:004": "當前時間",
|
||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
||||
"service:003": "指示燈",
|
||||
"service:003:property:001": "開關",
|
||||
"service:004": "虛擬服務",
|
||||
"service:004:action:001": "產生虛擬事件",
|
||||
"service:004:event:001": "虛擬事件發生",
|
||||
"service:004:property:001": "事件名稱"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||
"en": {
|
||||
"service:011": "Right Button On and Off",
|
||||
"service:011:property:001": "Right Button On and Off",
|
||||
"service:015:action:001": "Left Button Identify",
|
||||
"service:016:action:001": "Middle Button Identify",
|
||||
"service:017:action:001": "Right Button Identify"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:015:action:001": "左键确认",
|
||||
"service:016:action:001": "中键确认",
|
||||
"service:017:action:001": "右键确认"
|
||||
}
|
||||
"urn:miot-spec-v2:device:bath-heater:0000A028:yeelink-v10": {
|
||||
"en": {
|
||||
"service:003:property:001:valuelist:000": "Idle",
|
||||
"service:003:property:001:valuelist:001": "Dry"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"de": {
|
||||
"service:001": "Geräteinformationen",
|
||||
"service:001:property:003": "Geräte-ID",
|
||||
"service:001:property:005": "Seriennummer (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Netzwerk geändert",
|
||||
"service:002:event:002": "Netzwerk geändert",
|
||||
"service:002:property:001": "Zugriffsmethode",
|
||||
"service:002:property:001:valuelist:000": "Kabelgebunden",
|
||||
"service:002:property:001:valuelist:001": "5G Drahtlos",
|
||||
"service:002:property:001:valuelist:002": "2.4G Drahtlos",
|
||||
"service:002:property:002": "IP-Adresse",
|
||||
"service:002:property:003": "WiFi-Netzwerkname",
|
||||
"service:002:property:004": "Aktuelle Zeit",
|
||||
"service:002:property:005": "DHCP-Server-MAC-Adresse",
|
||||
"service:003": "Anzeigelampe",
|
||||
"service:003:property:001": "Schalter",
|
||||
"service:004": "Virtueller Dienst",
|
||||
"service:004:action:001": "Virtuelles Ereignis erzeugen",
|
||||
"service:004:event:001": "Virtuelles Ereignis aufgetreten",
|
||||
"service:004:property:001": "Ereignisname"
|
||||
},
|
||||
"en": {
|
||||
"service:001": "Device Information",
|
||||
"service:001:property:003": "Device ID",
|
||||
"service:001:property:005": "Serial Number (SN)",
|
||||
"service:002": "Gateway",
|
||||
"service:002:event:001": "Network Changed",
|
||||
"service:002:event:002": "Network Changed",
|
||||
"service:002:property:001": "Access Method",
|
||||
"service:002:property:001:valuelist:000": "Wired",
|
||||
"service:002:property:001:valuelist:001": "5G Wireless",
|
||||
"service:002:property:001:valuelist:002": "2.4G Wireless",
|
||||
"service:002:property:002": "IP Address",
|
||||
"service:002:property:003": "WiFi Network Name",
|
||||
"service:002:property:004": "Current Time",
|
||||
"service:002:property:005": "DHCP Server MAC Address",
|
||||
"service:003": "Indicator Light",
|
||||
"service:003:property:001": "Switch",
|
||||
"service:004": "Virtual Service",
|
||||
"service:004:action:001": "Generate Virtual Event",
|
||||
"service:004:event:001": "Virtual Event Occurred",
|
||||
"service:004:property:001": "Event Name"
|
||||
},
|
||||
"es": {
|
||||
"service:001": "Información del dispositivo",
|
||||
"service:001:property:003": "ID del dispositivo",
|
||||
"service:001:property:005": "Número de serie (SN)",
|
||||
"service:002": "Puerta de enlace",
|
||||
"service:002:event:001": "Cambio de red",
|
||||
"service:002:event:002": "Cambio de red",
|
||||
"service:002:property:001": "Método de acceso",
|
||||
"service:002:property:001:valuelist:000": "Cableado",
|
||||
"service:002:property:001:valuelist:001": "5G inalámbrico",
|
||||
"service:002:property:001:valuelist:002": "2.4G inalámbrico",
|
||||
"service:002:property:002": "Dirección IP",
|
||||
"service:002:property:003": "Nombre de red WiFi",
|
||||
"service:002:property:004": "Hora actual",
|
||||
"service:002:property:005": "Dirección MAC del servidor DHCP",
|
||||
"service:003": "Luz indicadora",
|
||||
"service:003:property:001": "Interruptor",
|
||||
"service:004": "Servicio virtual",
|
||||
"service:004:action:001": "Generar evento virtual",
|
||||
"service:004:event:001": "Ocurrió un evento virtual",
|
||||
"service:004:property:001": "Nombre del evento"
|
||||
},
|
||||
"fr": {
|
||||
"service:001": "Informations sur l'appareil",
|
||||
"service:001:property:003": "ID de l'appareil",
|
||||
"service:001:property:005": "Numéro de série (SN)",
|
||||
"service:002": "Passerelle",
|
||||
"service:002:event:001": "Changement de réseau",
|
||||
"service:002:event:002": "Changement de réseau",
|
||||
"service:002:property:001": "Méthode d'accès",
|
||||
"service:002:property:001:valuelist:000": "Câblé",
|
||||
"service:002:property:001:valuelist:001": "Sans fil 5G",
|
||||
"service:002:property:001:valuelist:002": "Sans fil 2.4G",
|
||||
"service:002:property:002": "Adresse IP",
|
||||
"service:002:property:003": "Nom du réseau WiFi",
|
||||
"service:002:property:004": "Heure actuelle",
|
||||
"service:002:property:005": "Adresse MAC du serveur DHCP",
|
||||
"service:003": "Voyant lumineux",
|
||||
"service:003:property:001": "Interrupteur",
|
||||
"service:004": "Service virtuel",
|
||||
"service:004:action:001": "Générer un événement virtuel",
|
||||
"service:004:event:001": "Événement virtuel survenu",
|
||||
"service:004:property:001": "Nom de l'événement"
|
||||
},
|
||||
"ja": {
|
||||
"service:001": "デバイス情報",
|
||||
"service:001:property:003": "デバイスID",
|
||||
"service:001:property:005": "シリアル番号 (SN)",
|
||||
"service:002": "ゲートウェイ",
|
||||
"service:002:event:001": "ネットワークが変更されました",
|
||||
"service:002:event:002": "ネットワークが変更されました",
|
||||
"service:002:property:001": "アクセス方法",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G ワイヤレス",
|
||||
"service:002:property:001:valuelist:002": "2.4G ワイヤレス",
|
||||
"service:002:property:002": "IPアドレス",
|
||||
"service:002:property:003": "WiFiネットワーク名",
|
||||
"service:002:property:004": "現在の時間",
|
||||
"service:002:property:005": "DHCPサーバーMACアドレス",
|
||||
"service:003": "インジケータライト",
|
||||
"service:003:property:001": "スイッチ",
|
||||
"service:004": "バーチャルサービス",
|
||||
"service:004:action:001": "バーチャルイベントを生成",
|
||||
"service:004:event:001": "バーチャルイベントが発生しました",
|
||||
"service:004:property:001": "イベント名"
|
||||
},
|
||||
"ru": {
|
||||
"service:001": "Информация об устройстве",
|
||||
"service:001:property:003": "ID устройства",
|
||||
"service:001:property:005": "Серийный номер (SN)",
|
||||
"service:002": "Шлюз",
|
||||
"service:002:event:001": "Сеть изменена",
|
||||
"service:002:event:002": "Сеть изменена",
|
||||
"service:002:property:001": "Метод доступа",
|
||||
"service:002:property:001:valuelist:000": "Проводной",
|
||||
"service:002:property:001:valuelist:001": "5G Беспроводной",
|
||||
"service:002:property:001:valuelist:002": "2.4G Беспроводной",
|
||||
"service:002:property:002": "IP Адрес",
|
||||
"service:002:property:003": "Название WiFi сети",
|
||||
"service:002:property:004": "Текущее время",
|
||||
"service:002:property:005": "MAC адрес DHCP сервера",
|
||||
"service:003": "Световой индикатор",
|
||||
"service:003:property:001": "Переключатель",
|
||||
"service:004": "Виртуальная служба",
|
||||
"service:004:action:001": "Создать виртуальное событие",
|
||||
"service:004:event:001": "Произошло виртуальное событие",
|
||||
"service:004:property:001": "Название события"
|
||||
},
|
||||
"zh-Hant": {
|
||||
"service:001": "設備信息",
|
||||
"service:001:property:003": "設備ID",
|
||||
"service:001:property:005": "序號 (SN)",
|
||||
"service:002": "網關",
|
||||
"service:002:event:001": "網路發生變化",
|
||||
"service:002:event:002": "網路發生變化",
|
||||
"service:002:property:001": "接入方式",
|
||||
"service:002:property:001:valuelist:000": "有線",
|
||||
"service:002:property:001:valuelist:001": "5G 無線",
|
||||
"service:002:property:001:valuelist:002": "2.4G 無線",
|
||||
"service:002:property:002": "IP地址",
|
||||
"service:002:property:003": "WiFi網路名稱",
|
||||
"service:002:property:004": "當前時間",
|
||||
"service:002:property:005": "DHCP伺服器MAC地址",
|
||||
"service:003": "指示燈",
|
||||
"service:003:property:001": "開關",
|
||||
"service:004": "虛擬服務",
|
||||
"service:004:action:001": "產生虛擬事件",
|
||||
"service:004:event:001": "虛擬事件發生",
|
||||
"service:004:property:001": "事件名稱"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:lock:0000A038:loock-t2pv1": {
|
||||
"zh-Hans": {
|
||||
"service:003:property:1021:valuelist:000": "已上锁",
|
||||
"service:003:property:1021:valuelist:001": "已上锁(童锁)",
|
||||
"service:003:property:1021:valuelist:002": "已上锁(反锁)",
|
||||
"service:003:property:1021:valuelist:003": "已上锁(反锁+童锁)",
|
||||
"service:003:property:1021:valuelist:004": "已开锁",
|
||||
"service:003:property:1021:valuelist:008": "门未关(门超时未关)",
|
||||
"service:003:property:1021:valuelist:012": "门虚掩"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1": {
|
||||
"en": {
|
||||
"service:002:property:001": "Soil Moisture"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:002:property:001": "土壤湿度",
|
||||
"service:002:property:003": "光照强度"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:090615-x1tpm": {
|
||||
"en": {
|
||||
"service:027:property:001": "Fan Switch",
|
||||
"service:027:property:003": "Light Switch",
|
||||
"service:027:property:004": "Fan and Light Switch"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:switch:0000A003:lumi-acn040": {
|
||||
"en": {
|
||||
"service:011": "Right Button On and Off",
|
||||
"service:011:property:001": "Right Button On and Off",
|
||||
"service:015:action:001": "Left Button Identify",
|
||||
"service:016:action:001": "Middle Button Identify",
|
||||
"service:017:action:001": "Right Button Identify"
|
||||
},
|
||||
"zh-Hans": {
|
||||
"service:015:action:001": "左键确认",
|
||||
"service:016:action:001": "中键确认",
|
||||
"service:017:action:001": "右键确认"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e": {
|
||||
"ru": {
|
||||
"service:002": "термостат",
|
||||
"service:002:property:001": "выключатель",
|
||||
"service:002:property:002": "режим нагрузки",
|
||||
"service:002:property:002:valuelist:000": "без подогрева",
|
||||
"service:002:property:002:valuelist:001": "нагрев",
|
||||
"service:002:property:003": "неисправность",
|
||||
"service:002:property:003:valuelist:000": "ошибка датчика",
|
||||
"service:002:property:003:valuelist:001": "без ошибок",
|
||||
"service:002:property:003:valuelist:002": "защита от высоких температур",
|
||||
"service:002:property:003:valuelist:003": "криогенная защита",
|
||||
"service:002:property:004": "режим",
|
||||
"service:002:property:004:valuelist:000": "ручной режим",
|
||||
"service:002:property:004:valuelist:001": "домашний режим",
|
||||
"service:002:property:004:valuelist:002": "режим выхода из дома",
|
||||
"service:002:property:004:valuelist:003": "автоматический режим",
|
||||
"service:002:property:004:valuelist:004": "Режим сна",
|
||||
"service:002:property:005": "температура цели",
|
||||
"service:002:property:007": "текущая температура",
|
||||
"service:004": "Пользовательские услуги",
|
||||
"service:004:property:001": "детский замок",
|
||||
"service:004:property:002": "тип датчика",
|
||||
"service:004:property:002:valuelist:000": "внутренний датчик",
|
||||
"service:004:property:002:valuelist:001": "выносной датчик",
|
||||
"service:004:property:002:valuelist:002": "встроенный и внешний датчик",
|
||||
"service:004:property:003": "пусковая разность температур",
|
||||
"service:004:property:004": "компенсационная температура",
|
||||
"service:004:property:005": "температура выносного датчика",
|
||||
"service:004:property:006": "максимальная температура цели",
|
||||
"service:004:property:007": "минимальная температура цели "
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123w": {
|
||||
"ru": {
|
||||
"service:002": "термостат",
|
||||
"service:002:property:001": "выключатель",
|
||||
"service:002:property:002": "режим нагрузки",
|
||||
"service:002:property:002:valuelist:000": "нагрев",
|
||||
"service:002:property:002:valuelist:001": "без подогрева",
|
||||
"service:002:property:003": "неисправность",
|
||||
"service:002:property:003:valuelist:000": "без ошибок",
|
||||
"service:002:property:003:valuelist:001": "ошибка датчика",
|
||||
"service:002:property:003:valuelist:002": "защита от высоких температур",
|
||||
"service:002:property:003:valuelist:003": "криогенная защита",
|
||||
"service:002:property:004": "режим",
|
||||
"service:002:property:004:valuelist:000": "ручной режим",
|
||||
"service:002:property:004:valuelist:001": "домашний режим",
|
||||
"service:002:property:004:valuelist:002": "режим выхода из дома",
|
||||
"service:002:property:004:valuelist:003": "автоматический режим",
|
||||
"service:002:property:004:valuelist:004": "Режим сна",
|
||||
"service:002:property:005": "температура цели",
|
||||
"service:002:property:007": "текущая температура",
|
||||
"service:004": "Пользовательские услуги",
|
||||
"service:004:property:001": "детский замок",
|
||||
"service:004:property:002": "тип датчика",
|
||||
"service:004:property:002:valuelist:000": "внутренний датчик",
|
||||
"service:004:property:002:valuelist:001": "выносной датчик",
|
||||
"service:004:property:002:valuelist:002": "встроенный и внешний датчик",
|
||||
"service:004:property:003": "пусковая разность температур",
|
||||
"service:004:property:004": "компенсационная температура",
|
||||
"service:004:property:005": "температура выносного датчика",
|
||||
"service:004:property:006": "максимальная температура цели",
|
||||
"service:004:property:007": "минимальная температура цели "
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01": {
|
||||
"en": {
|
||||
"service:002": "Thermostat",
|
||||
"service:002:property:002": "Air Conditioner Mode",
|
||||
"service:004": "Air Conditioner"
|
||||
},
|
||||
"zh_cn": {
|
||||
"service:002": "地暖",
|
||||
"service:004": "空调"
|
||||
}
|
||||
},
|
||||
"urn:miot-spec-v2:device:vacuum:0000A006:ijai-v1": {
|
||||
"zh_cn": {
|
||||
"service:007:property:005:valuelist:000": "安静",
|
||||
"service:007:property:005:valuelist:001": "标准",
|
||||
"service:007:property:005:valuelist:002": "中档",
|
||||
"service:007:property:005:valuelist:003": "强力"
|
||||
}
|
||||
}
|
||||
}
|
266
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
266
custom_components/xiaomi_home/miot/specs/spec_add.json
Normal file
@ -0,0 +1,266 @@
|
||||
{
|
||||
"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:viomi-m1:2": [
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:service:switch:0000780C:viomi-m1:1",
|
||||
"description": "Water Heater",
|
||||
"properties": [
|
||||
{
|
||||
"iid": 6,
|
||||
"type": "urn:miot-spec-v2:property:on:00000006:viomi-m1:1",
|
||||
"description": "Switch Status",
|
||||
"format": "bool",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"urn:miot-spec-v2:device:water-heater:0000A02A:xiaomi-yms2:1": [
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:service:switch:0000780C:xiaomi-yms2:1",
|
||||
"description": "Switch",
|
||||
"properties": [
|
||||
{
|
||||
"iid": 6,
|
||||
"type": "urn:miot-spec-v2:property:on:00000006:xiaomi-yms2:1",
|
||||
"description": "Switch Status",
|
||||
"format": "bool",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"urn:miot-spec-v2:device:water-heater:0000A02A:zimi-h03:1": [
|
||||
{
|
||||
"iid": 2,
|
||||
"type": "urn:miot-spec-v2:service:switch:0000780C:zimi-h03:1",
|
||||
"description": "Switch",
|
||||
"properties": [
|
||||
{
|
||||
"iid": 6,
|
||||
"type": "urn:miot-spec-v2:property:on:00000006:zimi-h03:1",
|
||||
"description": "Switch Status",
|
||||
"format": "bool",
|
||||
"access": [
|
||||
"read",
|
||||
"write",
|
||||
"notify"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:
|
||||
services:
|
||||
- '4'
|
||||
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||
properties:
|
||||
- 9.*
|
||||
@ -5,6 +8,9 @@ urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
|
||||
- 15.*
|
||||
services:
|
||||
- '10'
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:
|
||||
properties:
|
||||
- '3.2'
|
||||
urn:miot-spec-v2:device:curtain:0000A00C:lumi-hmcn01:
|
||||
properties:
|
||||
- '5.1'
|
||||
@ -38,6 +44,7 @@ urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1:
|
||||
services:
|
||||
- '1'
|
||||
- '5'
|
||||
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03:
|
||||
urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:
|
||||
services:
|
||||
- '*'
|
||||
- '2'
|
||||
- '4'
|
||||
|
@ -1,3 +1,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:
|
||||
prop.2.1:
|
||||
name: access-mode
|
||||
@ -14,25 +139,98 @@ urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1:
|
||||
- notify
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:2: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:3: urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:1
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1:
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:1: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:2: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3
|
||||
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3:
|
||||
prop.3.1:
|
||||
name: on-ventilation
|
||||
urn:miot-spec-v2:device:kettle:0000A009:yunmi-r3:1:
|
||||
prop.3.1:
|
||||
unit: ppm
|
||||
urn:miot-spec-v2:device:light:0000A001:shhf-sfla10:1:
|
||||
prop.8.9:
|
||||
name: wind-reverse
|
||||
urn:miot-spec-v2:device:light:0000A001:shhf-sfla12:1:
|
||||
prop.8.11:
|
||||
name: on-a
|
||||
urn:miot-spec-v2:device:magnet-sensor:0000A016:linp-m1:1:
|
||||
prop.2.1004:
|
||||
name: contact-state
|
||||
value-list:
|
||||
- value: 0
|
||||
description: open
|
||||
- value: 1
|
||||
description: closed
|
||||
expr: src_value!=1
|
||||
urn:miot-spec-v2:device:motion-sensor:0000A014:lumi-acn001:1:
|
||||
prop.3.2:
|
||||
access:
|
||||
- read
|
||||
- notify
|
||||
unit: mV
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:1: urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:ainice-3b:2:
|
||||
prop.2.8:
|
||||
name: people-number
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:izq-24:2:0000C824:
|
||||
prop.2.6:
|
||||
unit: cm
|
||||
urn:miot-spec-v2:device:occupancy-sensor:0000A0BF:linp-hb01:2:0000C824:
|
||||
prop.3.3:
|
||||
unit: m
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3:
|
||||
prop.5.1:
|
||||
name: power-consumption
|
||||
expr: round(src_value/1000, 3)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:2: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1
|
||||
urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:3: urn:miot-spec-v2:device:outlet:0000A002:chuangmi-212a01:1
|
||||
expr: round(src_value*6/1000000, 3)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp1md:1:
|
||||
prop.2.2:
|
||||
name: power-consumption
|
||||
expr: round(src_value/1000, 3)
|
||||
prop.2.3:
|
||||
expr: round(src_value/10, 1)
|
||||
prop.2.4:
|
||||
unit: mA
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:1: urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-cp2:2:
|
||||
prop.2.3:
|
||||
expr: round(src_value/10, 1)
|
||||
prop.2.4:
|
||||
unit: mA
|
||||
prop.3.2:
|
||||
expr: round(src_value/10, 1)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1:
|
||||
prop.11.1:
|
||||
name: power-consumption
|
||||
expr: round(src_value/100, 2)
|
||||
urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:2: urn:miot-spec-v2:device:outlet:0000A002:cuco-v3:1
|
||||
urn:miot-spec-v2:device:outlet:0000A002:giot-v8icm:1:0000C816:
|
||||
prop.4.1:
|
||||
unit: mWh
|
||||
urn:miot-spec-v2:device:outlet:0000A002:qmi-psv3:1:0000C816:
|
||||
prop.3.3:
|
||||
unit: mV
|
||||
prop.3.4:
|
||||
unit: mA
|
||||
urn:miot-spec-v2:device:outlet:0000A002: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:
|
||||
prop.3.1:
|
||||
name: electric-power
|
||||
expr: round(src_value/100, 2)
|
||||
urn:miot-spec-v2:device:plant-monitor:0000A030:hhcc-v1:1:
|
||||
prop.2.1:
|
||||
name: soil-moisture
|
||||
icon: mdi:watering-can
|
||||
prop.2.2:
|
||||
name: soil-ec
|
||||
icon: mdi:sprout-outline
|
||||
unit: μS/cm
|
||||
urn:miot-spec-v2:device:relay:0000A03D:lumi-c2acn01:1:
|
||||
prop.4.1:
|
||||
unit: kWh
|
||||
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
||||
prop.2.1:
|
||||
name: download-speed
|
||||
@ -42,37 +240,64 @@ urn:miot-spec-v2:device:router:0000A036:xiaomi-rd08:1:
|
||||
name: upload-speed
|
||||
icon: mdi:upload
|
||||
unit: B/s
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1:
|
||||
urn:miot-spec-v2:device:safe-box:0000A042:loock-v1:1:
|
||||
prop.5.1:
|
||||
name: contact-state
|
||||
expr: src_value!=1
|
||||
urn:miot-spec-v2:device:switch:0000A003:090615-x1tpm:1:0000D042:
|
||||
prop.27.3:
|
||||
name: light-on
|
||||
prop.27.4:
|
||||
name: light-fan-on
|
||||
urn:miot-spec-v2:device:switch:0000A003:lxzn-cbcsmj:1:0000D00D:
|
||||
prop.3.1:
|
||||
expr: round(src_value/100, 2)
|
||||
prop.3.2:
|
||||
expr: round(src_value/1000, 2)
|
||||
prop.3.3:
|
||||
expr: round(src_value/10, 1)
|
||||
urn:miot-spec-v2:device:thermostat:0000A031:suittc-wk168:1:
|
||||
prop.2.3:
|
||||
value-range:
|
||||
- 0
|
||||
- 1
|
||||
- 1
|
||||
urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:2: urn:miot-spec-v2:device:airer:0000A00D:hyd-znlyj5:1
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
|
||||
prop.5.2:
|
||||
value-list:
|
||||
- value: 1
|
||||
description: low
|
||||
- value: 128
|
||||
description: medium
|
||||
- value: 255
|
||||
description: high
|
||||
urn:miot-spec-v2:device:bath-heater:0000A028:mike-2:1:
|
||||
prop.3.1:
|
||||
name: mode-a
|
||||
prop.3.11:
|
||||
name: mode-b
|
||||
prop.3.12:
|
||||
name: mode-c
|
||||
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
|
||||
prop.2.2:
|
||||
name: fan-level-a
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6:
|
||||
prop.10.6:
|
||||
unit: none
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:5: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m9:6
|
||||
description: '1'
|
||||
- value: 2
|
||||
description: '2'
|
||||
- value: 3
|
||||
description: '3'
|
||||
- value: 4
|
||||
description: '4'
|
||||
- value: 5
|
||||
description: '5'
|
||||
- value: 6
|
||||
description: '6'
|
||||
- value: 7
|
||||
description: '7'
|
||||
- value: 8
|
||||
description: '8'
|
||||
- value: 9
|
||||
description: '9'
|
||||
- value: 10
|
||||
description: '10'
|
||||
- value: 11
|
||||
description: '11'
|
||||
- value: 12
|
||||
description: '12'
|
||||
- value: 13
|
||||
description: '13'
|
||||
- value: 14
|
||||
description: '14'
|
||||
- value: 15
|
||||
description: '15'
|
||||
- value: 16
|
||||
description: '16'
|
||||
urn:miot-spec-v2:device:water-purifier:0000A013:roswan-lte01:1:0000D05A:
|
||||
prop.4.1:
|
||||
unit: ppm
|
||||
prop.4.2:
|
||||
unit: ppm
|
||||
urn:miot-spec-v2:device:water-purifier:0000A013:yunmi-s20:1:
|
||||
prop.4.1:
|
||||
unit: ppm
|
||||
prop.4.2:
|
||||
unit: ppm
|
||||
|
@ -48,19 +48,13 @@ Conversion rules of MIoT-Spec-V2 instance to Home Assistant entity.
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.components.sensor import SensorStateClass
|
||||
from homeassistant.components.event import EventDeviceClass
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
EntityCategory,
|
||||
LIGHT_LUX,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfTemperature,
|
||||
UnitOfPressure,
|
||||
PERCENTAGE
|
||||
)
|
||||
from homeassistant.const import (CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
EntityCategory, LIGHT_LUX, UnitOfEnergy,
|
||||
UnitOfPower, UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential, UnitOfTemperature,
|
||||
UnitOfPressure, PERCENTAGE)
|
||||
|
||||
# pylint: disable=pointless-string-statement
|
||||
"""SPEC_DEVICE_TRANS_MAP
|
||||
@ -106,7 +100,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'humidifier': {
|
||||
'required': {
|
||||
'humidifier': {
|
||||
'required': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
@ -118,7 +112,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
},
|
||||
'optional': {
|
||||
'environment': {
|
||||
'required': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'relative-humidity': {'read'}
|
||||
}
|
||||
@ -130,7 +124,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'dehumidifier': {
|
||||
'required': {
|
||||
'dehumidifier': {
|
||||
'required': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'on': {'read', 'write'}
|
||||
}
|
||||
@ -142,7 +136,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
},
|
||||
'optional': {
|
||||
'environment': {
|
||||
'required': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'relative-humidity': {'read'}
|
||||
}
|
||||
@ -154,15 +148,13 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'vacuum': {
|
||||
'required': {
|
||||
'vacuum': {
|
||||
'required': {
|
||||
'required': {
|
||||
'actions': {'start-sweep', 'stop-sweeping'},
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'status', 'fan-level'},
|
||||
'actions': {
|
||||
'pause-sweeping',
|
||||
'continue-sweep',
|
||||
'stop-and-gocharge'
|
||||
'pause-sweeping', 'continue-sweep', 'stop-and-gocharge'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,7 +162,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'optional': {
|
||||
'identify': {
|
||||
'required': {
|
||||
'actions': {'identify'}
|
||||
'actions': {'identify'}
|
||||
}
|
||||
},
|
||||
'battery': {
|
||||
@ -203,10 +195,9 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'on',
|
||||
'fan-level',
|
||||
'horizontal-swing',
|
||||
'vertical-swing'}}
|
||||
'on', 'fan-level', 'horizontal-swing', 'vertical-swing'
|
||||
}
|
||||
}
|
||||
},
|
||||
'environment': {
|
||||
'required': {},
|
||||
@ -234,8 +225,8 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'target-temperature', 'mode', 'fan-level',
|
||||
'temperature'}
|
||||
'target-temperature', 'mode', 'fan-level', 'temperature'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -277,7 +268,7 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'ptc-bath-heater': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'mode':{'read', 'write'}
|
||||
'mode': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
@ -320,6 +311,89 @@ SPEC_DEVICE_TRANS_MAP: dict = {
|
||||
'optional': {},
|
||||
'entity': 'electric-blanket'
|
||||
},
|
||||
'speaker': {
|
||||
'required': {
|
||||
'speaker': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'volume': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mute'}
|
||||
}
|
||||
},
|
||||
'play-control': {
|
||||
'required': {
|
||||
'actions': {'play'}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'playing-state'},
|
||||
'actions': {'pause', 'stop', 'next', 'previous'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {},
|
||||
'entity': 'wifi-speaker'
|
||||
},
|
||||
'television': {
|
||||
'required': {
|
||||
'speaker': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'volume': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'mute'}
|
||||
}
|
||||
},
|
||||
'television': {
|
||||
'required': {
|
||||
'actions': {'turn-off'}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'input-control'},
|
||||
'actions': {'turn-on'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'play-control': {
|
||||
'required': {},
|
||||
'optional': {
|
||||
'properties': {'playing-state'},
|
||||
'actions': {'play', 'pause', 'stop', 'next', 'previous'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'television'
|
||||
},
|
||||
'watch': {
|
||||
'required': {
|
||||
'watch': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'longitude': {'read'},
|
||||
'latitude': {'read'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'area-id'}
|
||||
}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'battery': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'battery-level': {'read'}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'entity': 'device_tracker'
|
||||
}
|
||||
}
|
||||
|
||||
"""SPEC_SERVICE_TRANS_MAP
|
||||
@ -350,9 +424,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'mode', 'brightness', 'color', 'color-temperature'
|
||||
}
|
||||
'properties': {'mode', 'brightness', 'color', 'color-temperature'}
|
||||
},
|
||||
'entity': 'light'
|
||||
},
|
||||
@ -367,7 +439,8 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'mode', 'brightness',
|
||||
'mode',
|
||||
'brightness',
|
||||
}
|
||||
},
|
||||
'entity': 'light',
|
||||
@ -388,6 +461,7 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
'fan-control': 'fan',
|
||||
'ceiling-fan': 'fan',
|
||||
'air-fresh': 'fan',
|
||||
'air-purifier': 'fan',
|
||||
'water-heater': {
|
||||
'required': {
|
||||
'properties': {
|
||||
@ -395,20 +469,18 @@ SPEC_SERVICE_TRANS_MAP: dict = {
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {'on', 'temperature', 'target-temperature', 'mode'}
|
||||
'properties': {'temperature', 'target-temperature', 'mode'}
|
||||
},
|
||||
'entity': 'water_heater'
|
||||
},
|
||||
'curtain': {
|
||||
'curtain': {
|
||||
'required': {
|
||||
'properties': {
|
||||
'motor-control': {'write'}
|
||||
}
|
||||
},
|
||||
'optional': {
|
||||
'properties': {
|
||||
'status', 'current-position', 'target-position'
|
||||
}
|
||||
'properties': {'status', 'current-position', 'target-position'}
|
||||
},
|
||||
'entity': 'cover'
|
||||
},
|
||||
@ -454,12 +526,28 @@ SPEC_PROP_TRANS_MAP: dict = {
|
||||
'format': {'int', 'float'},
|
||||
'access': {'read'}
|
||||
},
|
||||
'binary_sensor': {
|
||||
'format': {'bool', 'int'},
|
||||
'access': {'read'}
|
||||
},
|
||||
'switch': {
|
||||
'format': {'bool'},
|
||||
'access': {'read', 'write'}
|
||||
}
|
||||
},
|
||||
'properties': {
|
||||
'submersion-state': {
|
||||
'device_class': BinarySensorDeviceClass.MOISTURE,
|
||||
'entity': 'binary_sensor'
|
||||
},
|
||||
'contact-state': {
|
||||
'device_class': BinarySensorDeviceClass.DOOR,
|
||||
'entity': 'binary_sensor'
|
||||
},
|
||||
'occupancy-status': {
|
||||
'device_class': BinarySensorDeviceClass.OCCUPANCY,
|
||||
'entity': 'binary_sensor',
|
||||
},
|
||||
'temperature': {
|
||||
'device_class': SensorDeviceClass.TEMPERATURE,
|
||||
'entity': 'sensor',
|
||||
@ -506,7 +594,11 @@ SPEC_PROP_TRANS_MAP: dict = {
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT
|
||||
},
|
||||
'voc-density': 'tvoc-density',
|
||||
'voc-density': {
|
||||
'device_class': SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT
|
||||
},
|
||||
'battery-level': {
|
||||
'device_class': SensorDeviceClass.BATTERY,
|
||||
'entity': 'sensor',
|
||||
@ -560,12 +652,6 @@ SPEC_PROP_TRANS_MAP: dict = {
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.MEASUREMENT,
|
||||
'unit_of_measurement': UnitOfPower.WATT
|
||||
},
|
||||
'total-battery': {
|
||||
'device_class': SensorDeviceClass.ENERGY,
|
||||
'entity': 'sensor',
|
||||
'state_class': SensorStateClass.TOTAL_INCREASING,
|
||||
'unit_of_measurement': UnitOfEnergy.KILO_WATT_HOUR
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -584,6 +670,4 @@ SPEC_EVENT_TRANS_MAP: dict[str, str] = {
|
||||
'doorbell-ring': EventDeviceClass.DOORBELL
|
||||
}
|
||||
|
||||
SPEC_ACTION_TRANS_MAP = {
|
||||
|
||||
}
|
||||
SPEC_ACTION_TRANS_MAP = {}
|
||||
|
@ -110,7 +110,7 @@ class Sensor(MIoTPropertyEntity, SensorEntity):
|
||||
self._attr_native_unit_of_measurement = list(
|
||||
unit_sets)[0] if unit_sets else None
|
||||
# Set suggested precision
|
||||
if spec.format_ in {int, float}:
|
||||
if spec.format_ in {int, float} and spec.expr is None:
|
||||
self._attr_suggested_display_precision = spec.precision
|
||||
# Set state_class
|
||||
if spec.state_class:
|
||||
|
@ -4,7 +4,7 @@
|
||||
"step": {
|
||||
"eula": {
|
||||
"title": "Risikohinweis",
|
||||
"description": "1. Ihre **Xiaomi-Benutzerinformationen und Geräteinformationen** werden in Ihrem Home Assistant-System gespeichert. **Xiaomi kann die Sicherheit des Home Assistant-Speichermechanismus nicht garantieren**. Sie sind dafür verantwortlich, Ihre Informationen vor Diebstahl zu schützen.\r\n2. Diese Integration wird von der Open-Source-Community unterstützt und gewartet. Es können jedoch Stabilitätsprobleme oder andere Probleme auftreten. Wenn Sie auf ein Problem stoßen, das mit dieser Integration zusammenhängt, sollten Sie **die Open-Source-Community um Hilfe bitten, anstatt sich an den Xiaomi Home Kundendienst zu wenden**.\r\n3. Sie benötigen bestimmte technische Fähigkeiten, um Ihre lokale Laufzeitumgebung zu warten. Diese Integration ist für Anfänger nicht geeignet. \r\n4. Bevor Sie diese Integration verwenden, lesen Sie bitte die **README-Datei sorgfältig durch**.\r\n5. Um eine stabile Nutzung der Integration zu gewährleisten und Missbrauch der Schnittstelle zu verhindern, **darf diese Integration nur in Home Assistant verwendet werden. Weitere Informationen finden Sie in der LICENSE**.",
|
||||
"description": "1. Ihre **Xiaomi-Benutzerinformationen und Geräteinformationen** werden in Ihrem Home Assistant-System gespeichert. **Xiaomi kann die Sicherheit des Home Assistant-Speichermechanismus nicht garantieren**. Sie sind dafür verantwortlich, Ihre Informationen vor Diebstahl zu schützen.\r\n2. Diese Integration wird von der Open-Source-Community unterstützt und gewartet. Es können jedoch Stabilitätsprobleme oder andere Probleme auftreten. Wenn Sie auf ein Problem stoßen, das mit dieser Integration zusammenhängt, sollten Sie **die Open-Source-Community um Hilfe bitten, anstatt sich an den Xiaomi Home Kundendienst zu wenden**.\r\n3. Sie benötigen bestimmte technische Fähigkeiten, um Ihre lokale Laufzeitumgebung zu warten. Diese Integration ist für Anfänger nicht geeignet.\r\n4. Bevor Sie diese Integration verwenden, lesen Sie bitte die **README-Datei sorgfältig durch**.\r\n5. Um eine stabile Nutzung der Integration zu gewährleisten und Missbrauch der Schnittstelle zu verhindern, **darf diese Integration nur in Home Assistant verwendet werden. Weitere Informationen finden Sie in der LICENSE**.",
|
||||
"data": {
|
||||
"eula": "Ich habe das oben genannte Risiko zur Kenntnis genommen und übernehme freiwillig die damit verbundenen Risiken durch die Verwendung der Integration."
|
||||
}
|
||||
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Gerätestatusänderungen anzeigen",
|
||||
"update_trans_rules": "Entitätskonvertierungsregeln aktualisieren",
|
||||
"update_lan_ctrl_config": "LAN-Steuerungskonfiguration aktualisieren",
|
||||
"network_detect_config": "Integrierte Netzwerkkonfiguration"
|
||||
"network_detect_config": "Integrierte Netzwerkkonfiguration",
|
||||
"cover_closed_position": "Die Position der geschlossenen Vorhänge"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Bestätigen Sie die Konfiguration",
|
||||
"description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nGerätestatusänderungen anzeigen:\t{display_devices_changed_notify}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln",
|
||||
"description": "**{nick_name}**, bitte bestätigen Sie die neuesten Konfigurationsinformationen und klicken Sie dann auf \"Senden\". Die Integration wird mit den aktualisierten Konfigurationen erneut geladen.\r\n\r\nIntegrationsprache:\t{lang_new}\r\nBenutzername:\t{nick_name_new}\r\nAction-Debug-Modus:\t{action_debug}\r\nVerstecke Nicht-Standard-Entitäten:\t{hide_non_standard_entities}\r\nDie Position der geschlossenen Vorhänge:\t{cover_pos_new}\r\nGerätestatusänderungen anzeigen:\t{display_devices_changed_notify}\r\nGeräteänderungen:\t{devices_add} neue Geräte hinzufügen, {devices_remove} Geräte entfernen\r\nKonvertierungsregeländerungen:\tInsgesamt {trans_rules_count} Regeln, aktualisiert {trans_rules_count_success} Regeln",
|
||||
"data": {
|
||||
"confirm": "Änderungen bestätigen"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Display device status change notifications",
|
||||
"update_trans_rules": "Update entity conversion rules",
|
||||
"update_lan_ctrl_config": "Update LAN control configuration",
|
||||
"network_detect_config": "Integrated Network Configuration"
|
||||
"network_detect_config": "Integrated network configuration",
|
||||
"cover_closed_position": "Cover closed position"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Confirm Configuration",
|
||||
"description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language: \t{lang_new}\r\nNickname: \t{nick_name_new}\r\nDebug mode for action: \t{action_debug}\r\nHide non-standard created entities: \t{hide_non_standard_entities}\r\nDisplay device status change notifications:\t{display_devices_changed_notify}\r\nDevice Changes: \tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change: \tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules",
|
||||
"description": "Hello **{nick_name}**, please confirm the latest configuration information and then Click SUBMIT.\r\nThe integration will reload using the updated configuration.\r\n\r\nIntegration Language:\t{lang_new}\r\nNickname:\t{nick_name_new}\r\nDebug mode for action:\t{action_debug}\r\nHide non-standard created entities:\t{hide_non_standard_entities}\r\nCover closed position:\t{cover_pos_new}\r\nDisplay device status change notifications:\t{display_devices_changed_notify}\r\nDevice Changes:\tAdd **{devices_add}** devices, Remove **{devices_remove}** devices\r\nTransformation rules change:\tThere are a total of **{trans_rules_count}** rules, and updated **{trans_rules_count_success}** rules",
|
||||
"data": {
|
||||
"confirm": "Confirm the change"
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"step": {
|
||||
"eula": {
|
||||
"title": "Aviso de riesgo",
|
||||
"description": "1. Su **información de usuario de Xiaomi e información del dispositivo** se almacenará en su sistema Home Assistant. **Xiaomi no puede garantizar la seguridad del mecanismo de almacenamiento de Home Assistant**. Usted es responsable de evitar que su información sea robada.\r\n2. Esta integración es mantenida por la comunidad de código abierto y puede haber problemas de estabilidad u otros problemas. Cuando tenga problemas relacionados con el uso de esta integración, **busque ayuda en la comunidad de código abierto en lugar de contactar al servicio al cliente de Xiaomi**.\r\n3. Es necesario tener ciertas habilidades técnicas para mantener su entorno de ejecución local, esta integración no es amigable para los usuarios novatos.\r\n4. Antes de utilizar esta integración, por favor **lea detenidamente el archivo README**. \r\n5. Para garantizar el uso estable de la integración y prevenir el abuso de la interfaz, **esta integración solo está permitida en Home Assistant. Para más detalles, consulte la LICENSE**.",
|
||||
"description": "1. Su **información de usuario de Xiaomi e información del dispositivo** se almacenará en su sistema Home Assistant. **Xiaomi no puede garantizar la seguridad del mecanismo de almacenamiento de Home Assistant**. Usted es responsable de evitar que su información sea robada.\r\n2. Esta integración es mantenida por la comunidad de código abierto y puede haber problemas de estabilidad u otros problemas. Cuando tenga problemas relacionados con el uso de esta integración, **busque ayuda en la comunidad de código abierto en lugar de contactar al servicio al cliente de Xiaomi**.\r\n3. Es necesario tener ciertas habilidades técnicas para mantener su entorno de ejecución local, esta integración no es amigable para los usuarios novatos.\r\n4. Antes de utilizar esta integración, por favor **lea detenidamente el archivo README**.\r\n5. Para garantizar el uso estable de la integración y prevenir el abuso de la interfaz, **esta integración solo está permitida en Home Assistant. Para más detalles, consulte la LICENSE**.",
|
||||
"data": {
|
||||
"eula": "He leído y entiendo los riesgos anteriores, y estoy dispuesto a asumir cualquier riesgo relacionado con el uso de esta integración."
|
||||
}
|
||||
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Mostrar notificaciones de cambio de estado del dispositivo",
|
||||
"update_trans_rules": "Actualizar reglas de conversión de entidad",
|
||||
"update_lan_ctrl_config": "Actualizar configuración de control LAN",
|
||||
"network_detect_config": "Configuración de Red Integrada"
|
||||
"network_detect_config": "Configuración de Red Integrada",
|
||||
"cover_closed_position": "La posición de las cortinas cerradas"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Confirmar configuración",
|
||||
"description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nMostrar notificaciones de cambio de estado del dispositivo:\t{display_devices_changed_notify}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas",
|
||||
"description": "¡Hola, **{nick_name}**! Por favor, confirme la última información de configuración y haga clic en \"Enviar\" para finalizar la configuración.\r\nLa integración se volverá a cargar con la nueva configuración.\r\n\r\nIdioma de la integración:\t{lang_new}\r\nApodo de usuario:\t{nick_name_new}\r\nModo de depuración de Action:\t{action_debug}\r\nOcultar entidades generadas no estándar:\t{hide_non_standard_entities}\r\nLa posición de las cortinas cerradas:\t{cover_pos_new}\r\nMostrar notificaciones de cambio de estado del dispositivo:\t{display_devices_changed_notify}\r\nCambios de dispositivos:\t{devices_add} dispositivos agregados, {devices_remove} dispositivos eliminados\r\nCambios en las reglas de conversión:\t{trans_rules_count} reglas en total, {trans_rules_count_success} reglas actualizadas",
|
||||
"data": {
|
||||
"confirm": "Confirmar modificación"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Afficher les notifications de changement d'état de l'appareil",
|
||||
"update_trans_rules": "Mettre à jour les règles de conversion d'entités",
|
||||
"update_lan_ctrl_config": "Mettre à jour la configuration de contrôle LAN",
|
||||
"network_detect_config": "Configuration Réseau Intégrée"
|
||||
"network_detect_config": "Configuration Réseau Intégrée",
|
||||
"cover_closed_position": "La position des rideaux fermés"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Confirmer la configuration",
|
||||
"description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nAfficher les notifications de changement d'état de l'appareil:\t{display_devices_changed_notify}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles",
|
||||
"description": "**{nick_name}** Bonjour ! Veuillez confirmer les dernières informations de configuration et cliquer sur \"Soumettre\".\r\nL'intégration rechargera avec la nouvelle configuration.\r\n\r\nLangue d'intégration : {lang_new}\r\nPseudo utilisateur : {nick_name_new}\r\nMode de débogage d'action : {action_debug}\r\nMasquer les entités générées non standard : {hide_non_standard_entities}\r\nLa position des rideaux fermés:\t{cover_pos_new}\r\nAfficher les notifications de changement d'état de l'appareil:\t{display_devices_changed_notify}\r\nModifications des appareils : Ajouter **{devices_add}** appareils, supprimer **{devices_remove}** appareils\r\nModifications des règles de conversion : **{trans_rules_count}** règles au total, mise à jour de **{trans_rules_count_success}** règles",
|
||||
"data": {
|
||||
"confirm": "Confirmer la modification"
|
||||
}
|
||||
|
@ -113,7 +113,7 @@
|
||||
},
|
||||
"config_options": {
|
||||
"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": {
|
||||
"integration_language": "Lingua dell'Integrazione",
|
||||
"update_user_info": "Aggiorna le informazioni dell'utente",
|
||||
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Mostra notifiche di cambio stato del dispositivo",
|
||||
"update_trans_rules": "Aggiorna le regole di conversione delle entità",
|
||||
"update_lan_ctrl_config": "Aggiorna configurazione del controllo LAN",
|
||||
"network_detect_config": "Configurazione di Rete Integrata"
|
||||
"network_detect_config": "Configurazione di Rete Integrata",
|
||||
"cover_closed_position": "La posizione delle tende chiuse"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Conferma Configurazione",
|
||||
"description": "Ciao **{nick_name}**, si prega di confermare le informazioni di configurazione più recenti e poi fare clic su INVIA.\r\nL'integrazione verrà ricaricata utilizzando la configurazione aggiornata.\r\n\r\nLingua dell'Integrazione: \t{lang_new}\r\nSoprannome: \t{nick_name_new}\r\nModalità di debug per azione: \t{action_debug}\r\nNascondi entità create non standard: \t{hide_non_standard_entities}\r\nMostra notifiche di cambio stato del dispositivo:\t{display_devices_changed_notify}\r\nCambiamenti del Dispositivo: \tAggiungi **{devices_add}** dispositivi, Rimuovi **{devices_remove}** dispositivi\r\nCambiamenti delle regole di trasformazione: \tCi sono un totale di **{trans_rules_count}** regole, e aggiornate **{trans_rules_count_success}** regole",
|
||||
"description": "Ciao **{nick_name}**, si prega di confermare le informazioni di configurazione più recenti e poi fare clic su INVIA.\r\nL'integrazione verrà ricaricata utilizzando la configurazione aggiornata.\r\n\r\nLingua dell'Integrazione:\t{lang_new}\r\nSoprannome:\t{nick_name_new}\r\nModalità di debug per azione:\t{action_debug}\r\nNascondi entità create non standard:\t{hide_non_standard_entities}\r\nLa posizione delle tende chiuse:\t{cover_pos_new}\r\nMostra notifiche di cambio stato del dispositivo:\t{display_devices_changed_notify}\r\nCambiamenti del Dispositivo:\tAggiungi **{devices_add}** dispositivi, Rimuovi **{devices_remove}** dispositivi\r\nCambiamenti delle regole di trasformazione:\tCi sono un totale di **{trans_rules_count}** regole, e aggiornate **{trans_rules_count_success}** regole",
|
||||
"data": {
|
||||
"confirm": "Conferma la modifica"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "デバイスの状態変化通知を表示",
|
||||
"update_trans_rules": "エンティティ変換ルールを更新する",
|
||||
"update_lan_ctrl_config": "LAN制御構成を更新する",
|
||||
"network_detect_config": "統合ネットワーク構成"
|
||||
"network_detect_config": "統合ネットワーク構成",
|
||||
"cover_closed_position": "カーテンを閉じた位置"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "構成を確認する",
|
||||
"description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語:\t{lang_new}\r\nユーザー名:\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする:\t{hide_non_standard_entities}\r\nデバイスの状態変化通知を表示:\t{display_devices_changed_notify}\r\nデバイス変更:\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更:\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則",
|
||||
"description": "**{nick_name}** さん、こんにちは! 最新の構成情報を確認してください。[送信] をクリックして、更新された構成を使用して再度読み込みます。\r\n\r\n統合言語:\t{lang_new}\r\nユーザー名:\t{nick_name_new}\r\nAction デバッグモード:\t{action_debug}\r\n非標準生成エンティティを非表示にする:\t{hide_non_standard_entities}\r\nカーテンを閉じた位置:\t{cover_pos_new}\r\nデバイスの状態変化通知を表示:\t{display_devices_changed_notify}\r\nデバイス変更:\t追加 **{devices_add}** 個のデバイス、削除 **{devices_remove}** 個のデバイス\r\n変換ルール変更:\t合計 **{trans_rules_count}** 個の規則、更新 **{trans_rules_count_success}** 個の規則",
|
||||
"data": {
|
||||
"confirm": "変更を確認する"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Apparaatstatuswijzigingen weergeven",
|
||||
"update_trans_rules": "Werk entiteitsconversieregels bij",
|
||||
"update_lan_ctrl_config": "Werk LAN controleconfiguratie bij",
|
||||
"network_detect_config": "Geïntegreerde Netwerkconfiguratie"
|
||||
"network_detect_config": "Geïntegreerde Netwerkconfiguratie",
|
||||
"cover_closed_position": "De positie van de gesloten gordijnen"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Bevestig Configuratie",
|
||||
"description": "Hallo **{nick_name}**, bevestig alstublieft de nieuwste configuratie-informatie en klik vervolgens op INDENKEN.\r\nDe integratie zal opnieuw laden met de bijgewerkte configuratie.\r\n\r\nIntegratietaal: \t{lang_new}\r\nBijnaam: \t{nick_name_new}\r\nDebugmodus voor actie: \t{action_debug}\r\nVerberg niet-standaard gemaakte entiteiten: \t{hide_non_standard_entities}\r\nApparaatstatuswijzigingen weergeven:\t{display_devices_changed_notify}\r\nWijzigingen in apparaten: \tVoeg **{devices_add}** apparaten toe, Verwijder **{devices_remove}** apparaten\r\nWijzigingen in transformateregels: \tEr zijn in totaal **{trans_rules_count}** regels, en **{trans_rules_count_success}** regels zijn bijgewerkt",
|
||||
"description": "Hallo **{nick_name}**, bevestig alstublieft de nieuwste configuratie-informatie en klik vervolgens op INDENKEN.\r\nDe integratie zal opnieuw laden met de bijgewerkte configuratie.\r\n\r\nIntegratietaal:\t{lang_new}\r\nBijnaam:\t{nick_name_new}\r\nDebugmodus voor actie:\t{action_debug}\r\nVerberg niet-standaard gemaakte entiteiten:\t{hide_non_standard_entities}\r\nDe positie van de gesloten gordijnen:\t{cover_pos_new}\r\nApparaatstatuswijzigingen weergeven:\t{display_devices_changed_notify}\r\nWijzigingen in apparaten:\tVoeg **{devices_add}** apparaten toe, Verwijder **{devices_remove}** apparaten\r\nWijzigingen in transformateregels:\tEr zijn in totaal **{trans_rules_count}** regels, en **{trans_rules_count_success}** regels zijn bijgewerkt",
|
||||
"data": {
|
||||
"confirm": "Bevestig de wijziging"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo",
|
||||
"update_trans_rules": "Atualizar regras de conversão de entidades",
|
||||
"update_lan_ctrl_config": "Atualizar configuração de controle LAN",
|
||||
"network_detect_config": "Configuração de Rede Integrada"
|
||||
"network_detect_config": "Configuração de Rede Integrada",
|
||||
"cover_closed_position": "A posição das cortinas fechadas"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Confirmar Configuração",
|
||||
"description": "Olá **{nick_name}**, confirme as informações da configuração mais recente e depois clique em ENVIAR.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nApelido:\t{nick_name_new}\r\nModo de depuração para ação:\t{action_debug}\r\nOcultar entidades não padrão criadas:\t{hide_non_standard_entities}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações de Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração nas Regras de Transformação:\tUm total de **{trans_rules_count}** regras, e **{trans_rules_count_success}** regras atualizadas",
|
||||
"description": "Olá **{nick_name}**, confirme as informações da configuração mais recente e depois clique em ENVIAR.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nApelido:\t{nick_name_new}\r\nModo de depuração para ação:\t{action_debug}\r\nOcultar entidades não padrão criadas:\t{hide_non_standard_entities}\r\nA posição das cortinas fechadas:\t{cover_pos_new}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações de Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração nas Regras de Transformação:\tUm total de **{trans_rules_count}** regras, e **{trans_rules_count_success}** regras atualizadas",
|
||||
"data": {
|
||||
"confirm": "Confirmar a mudança"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Exibir notificações de mudança de status do dispositivo",
|
||||
"update_trans_rules": "Atualizar regras de conversão de entidades",
|
||||
"update_lan_ctrl_config": "Atualizar configuração de controlo LAN",
|
||||
"network_detect_config": "Configuração de Rede Integrada"
|
||||
"network_detect_config": "Configuração de Rede Integrada",
|
||||
"cover_closed_position": "A posição das cortinas fechadas"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Confirmar Configuração",
|
||||
"description": "Olá **{nick_name}**, confirme a informação da configuração mais recente e depois clique em SUBMETER.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nAlcunha:\t{nick_name_new}\r\nModo de depuração de ação:\t{action_debug}\r\nOcultar entidades não padrão:\t{hide_non_standard_entities}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações aos Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração das Regras de Transformação:\tExistem **{trans_rules_count}** regras no total, com **{trans_rules_count_success}** regras atualizadas",
|
||||
"description": "Olá **{nick_name}**, confirme a informação da configuração mais recente e depois clique em SUBMETER.\r\nA integração será recarregada com a configuração atualizada.\r\n\r\nIdioma da Integração:\t{lang_new}\r\nAlcunha:\t{nick_name_new}\r\nModo de depuração de ação:\t{action_debug}\r\nOcultar entidades não padrão:\t{hide_non_standard_entities}\r\nA posição das cortinas fechadas:\t{cover_pos_new}\r\nExibir notificações de mudança de status do dispositivo:\t{display_devices_changed_notify}\r\nAlterações aos Dispositivos:\tAdicionar **{devices_add}** dispositivos, Remover **{devices_remove}** dispositivos\r\nAlteração das Regras de Transformação:\tExistem **{trans_rules_count}** regras no total, com **{trans_rules_count_success}** regras atualizadas",
|
||||
"data": {
|
||||
"confirm": "Confirmar a alteração"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "Отображать уведомления о изменении состояния устройства",
|
||||
"update_trans_rules": "Обновить правила преобразования сущностей",
|
||||
"update_lan_ctrl_config": "Обновить конфигурацию управления LAN",
|
||||
"network_detect_config": "Интегрированная Сетевая Конфигурация"
|
||||
"network_detect_config": "Интегрированная Сетевая Конфигурация",
|
||||
"cover_closed_position": "Положение закрытых штор"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "Подтверждение настройки",
|
||||
"description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nОтображать уведомления о изменении состояния устройства:\t{display_devices_changed_notify}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил",
|
||||
"description": "**{nick_name}** Здравствуйте! Подтвердите последнюю информацию о настройке и нажмите «Отправить». Интеграция будет перезагружена с использованием обновленных настроек.\r\n\r\nЯзык интеграции:\t{lang_new}\r\nИмя пользователя:\t{nick_name_new}\r\nРежим отладки Action:\t{action_debug}\r\nСкрыть непроизводственные сущности:\t{hide_non_standard_entities}\r\nПоложение закрытых штор:\t{cover_pos_new}\r\nОтображать уведомления о изменении состояния устройства:\t{display_devices_changed_notify}\r\nИзменение устройства:\tДобавлено **{devices_add}** устройство, удалено **{devices_remove}** устройства\r\nИзменение правил преобразования:\tВсего **{trans_rules_count}** правил, обновлено **{trans_rules_count_success}** правил",
|
||||
"data": {
|
||||
"confirm": "Подтвердить изменения"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "显示设备状态变化通知",
|
||||
"update_trans_rules": "更新实体转换规则",
|
||||
"update_lan_ctrl_config": "更新局域网控制配置",
|
||||
"network_detect_config": "集成网络配置"
|
||||
"network_detect_config": "集成网络配置",
|
||||
"cover_closed_position": "窗帘关闭位置"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "确认配置",
|
||||
"description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言:\t{lang_new}\r\n用户昵称:\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体:\t{hide_non_standard_entities}\r\n显示设备状态变化通知:\t{display_devices_changed_notify}\r\n设备变化:\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化:\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则",
|
||||
"description": "**{nick_name}** 您好!请确认最新的配置信息,然后点击“提交”。\r\n集成将会使用更新后的配置重新载入。\r\n\r\n集成语言:\t{lang_new}\r\n用户昵称:\t{nick_name_new}\r\nAction 调试模式:\t{action_debug}\r\n隐藏非标准生成实体:\t{hide_non_standard_entities}\r\n窗帘关闭位置:\t{cover_pos_new}\r\n显示设备状态变化通知:\t{display_devices_changed_notify}\r\n设备变化:\t新增 **{devices_add}** 个设备,移除 **{devices_remove}** 个设备\r\n转换规则变化:\t共条 **{trans_rules_count}** 规则,更新 **{trans_rules_count_success}** 条规则",
|
||||
"data": {
|
||||
"confirm": "确认修改"
|
||||
}
|
||||
|
@ -124,7 +124,8 @@
|
||||
"display_devices_changed_notify": "顯示設備狀態變化通知",
|
||||
"update_trans_rules": "更新實體轉換規則",
|
||||
"update_lan_ctrl_config": "更新局域網控制配置",
|
||||
"network_detect_config": "集成網絡配置"
|
||||
"network_detect_config": "集成網絡配置",
|
||||
"cover_closed_position": "窗簾關閉位置"
|
||||
}
|
||||
},
|
||||
"update_user_info": {
|
||||
@ -183,7 +184,7 @@
|
||||
},
|
||||
"config_confirm": {
|
||||
"title": "確認配置",
|
||||
"description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言:\t{lang_new}\r\n用戶暱稱:\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體:\t{hide_non_standard_entities}\r\n顯示設備狀態變化通知:\t{display_devices_changed_notify}\r\n設備變化:\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化:\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則",
|
||||
"description": "**{nick_name}** 您好!請確認最新的配置信息,然後點擊“提交”。\r\n集成將會使用更新後的配置重新載入。\r\n\r\n集成語言:\t{lang_new}\r\n用戶暱稱:\t{nick_name_new}\r\nAction 調試模式:\t{action_debug}\r\n隱藏非標準生成實體:\t{hide_non_standard_entities}\r\n窗簾關閉位置:\t{cover_pos_new}\r\n顯示設備狀態變化通知:\t{display_devices_changed_notify}\r\n設備變化:\t新增 **{devices_add}** 個設備,移除 **{devices_remove}** 個設備\r\n轉換規則變化:\t共條 **{trans_rules_count}** 規則,更新 **{trans_rules_count_success}** 條規則",
|
||||
"data": {
|
||||
"confirm": "確認修改"
|
||||
}
|
||||
|
@ -47,29 +47,26 @@ Vacuum entities for Xiaomi Home.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Any, Optional
|
||||
import re
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.vacuum import (
|
||||
StateVacuumEntity,
|
||||
VacuumEntityFeature
|
||||
)
|
||||
from homeassistant.components.vacuum import (StateVacuumEntity,
|
||||
VacuumEntityFeature)
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
|
||||
from .miot.miot_spec import (
|
||||
MIoTSpecAction,
|
||||
MIoTSpecProperty)
|
||||
from .miot.miot_spec import (MIoTSpecAction, MIoTSpecProperty)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
config_entry.entry_id]
|
||||
@ -99,10 +96,12 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
_status_map: Optional[dict[int, str]]
|
||||
_fan_level_map: Optional[dict[int, str]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
) -> None:
|
||||
_device_name: str
|
||||
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._device_name = miot_device.name
|
||||
self._attr_supported_features = VacuumEntityFeature(0)
|
||||
|
||||
self._prop_status = None
|
||||
@ -121,21 +120,21 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
for prop in entity_data.props:
|
||||
if prop.name == 'status':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid status value_list, %s', self.entity_id)
|
||||
_LOGGER.error('invalid status value_list, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._status_map = prop.value_list.to_map()
|
||||
self._attr_supported_features |= VacuumEntityFeature.STATE
|
||||
self._prop_status = prop
|
||||
elif prop.name == 'fan-level':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'invalid fan-level value_list, %s', self.entity_id)
|
||||
_LOGGER.error('invalid fan-level value_list, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._fan_level_map = prop.value_list.to_map()
|
||||
self._attr_fan_speed_list = list(self._fan_level_map.values())
|
||||
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
||||
self._prop_fan_level = prop
|
||||
|
||||
elif prop.name == 'battery-level':
|
||||
self._attr_supported_features |= VacuumEntityFeature.BATTERY
|
||||
self._prop_battery_level = prop
|
||||
@ -155,16 +154,24 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
elif action.name == 'stop-and-gocharge':
|
||||
self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME
|
||||
self._action_stop_and_gocharge = action
|
||||
|
||||
elif action.name == 'identify':
|
||||
self._attr_supported_features |= VacuumEntityFeature.LOCATE
|
||||
self._action_identify = action
|
||||
|
||||
async def async_start(self) -> None:
|
||||
"""Start or resume the cleaning task."""
|
||||
if self.state.lower() in ['paused', '暂停中']:
|
||||
await self.action_async(action=self._action_continue_sweep)
|
||||
return
|
||||
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)
|
||||
return
|
||||
await self.action_async(action=self._action_start_sweep)
|
||||
|
||||
async def async_stop(self, **kwargs: Any) -> None:
|
||||
@ -179,31 +186,92 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
|
||||
"""Set the vacuum cleaner to return to the dock."""
|
||||
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:
|
||||
"""Locate the vacuum cleaner."""
|
||||
await self.action_async(action=self._action_identify)
|
||||
|
||||
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
|
||||
"""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
|
||||
def state(self) -> Optional[str]:
|
||||
"""Return the current state of the vacuum cleaner."""
|
||||
return self.get_map_value(
|
||||
map_=self._status_map,
|
||||
key=self.get_prop_value(prop=self._prop_status))
|
||||
"""Return the current state of the vacuum cleaner.
|
||||
|
||||
To fix the HA warning below:
|
||||
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
|
||||
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)
|
||||
|
||||
@property
|
||||
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(
|
||||
map_=self._fan_level_map,
|
||||
key=self.get_prop_value(prop=self._prop_fan_level))
|
||||
|
@ -52,25 +52,22 @@ from typing import Any, Optional
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.water_heater import (
|
||||
STATE_ON,
|
||||
STATE_OFF,
|
||||
ATTR_TEMPERATURE,
|
||||
WaterHeaterEntity,
|
||||
WaterHeaterEntityFeature
|
||||
)
|
||||
from homeassistant.components.water_heater import (STATE_ON, STATE_OFF,
|
||||
ATTR_TEMPERATURE,
|
||||
WaterHeaterEntity,
|
||||
WaterHeaterEntityFeature)
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
@ -79,8 +76,8 @@ async def async_setup_entry(
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for data in miot_device.entity_list.get('water_heater', []):
|
||||
new_entities.append(WaterHeater(
|
||||
miot_device=miot_device, entity_data=data))
|
||||
new_entities.append(
|
||||
WaterHeater(miot_device=miot_device, entity_data=data))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
@ -95,12 +92,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
|
||||
_mode_map: Optional[dict[Any, Any]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
) -> None:
|
||||
def __init__(self, miot_device: MIoTDevice,
|
||||
entity_data: MIoTEntityData) -> None:
|
||||
"""Initialize the Water heater."""
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self._attr_temperature_unit = None # type: ignore
|
||||
self._attr_temperature_unit = None
|
||||
self._attr_supported_features = WaterHeaterEntityFeature(0)
|
||||
self._prop_on = None
|
||||
self._prop_temp = None
|
||||
@ -117,14 +113,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
# temperature
|
||||
if prop.name == 'temperature':
|
||||
if not prop.value_range:
|
||||
_LOGGER.error(
|
||||
'invalid temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
_LOGGER.error('invalid temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
if prop.external_unit:
|
||||
self._attr_temperature_unit = prop.external_unit
|
||||
self._attr_min_temp = prop.value_range.min_
|
||||
self._attr_max_temp = prop.value_range.max_
|
||||
self._prop_temp = prop
|
||||
# target-temperature
|
||||
if prop.name == 'target-temperature':
|
||||
@ -133,9 +126,9 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
'invalid target-temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
continue
|
||||
self._attr_target_temperature_low = prop.value_range.min_
|
||||
self._attr_target_temperature_high = prop.value_range.max_
|
||||
self._attr_precision = prop.value_range.step
|
||||
self._attr_min_temp = prop.value_range.min_
|
||||
self._attr_max_temp = prop.value_range.max_
|
||||
self._attr_target_temperature_step = prop.value_range.step
|
||||
if self._attr_temperature_unit is None and prop.external_unit:
|
||||
self._attr_temperature_unit = prop.external_unit
|
||||
self._attr_supported_features |= (
|
||||
@ -144,17 +137,15 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
# mode
|
||||
if prop.name == 'mode':
|
||||
if not prop.value_list:
|
||||
_LOGGER.error(
|
||||
'mode value_list is None, %s', self.entity_id)
|
||||
_LOGGER.error('mode value_list is None, %s', self.entity_id)
|
||||
continue
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_operation_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= (
|
||||
WaterHeaterEntityFeature.OPERATION_MODE)
|
||||
self._prop_mode = prop
|
||||
if not self._attr_operation_list:
|
||||
self._attr_operation_list = [STATE_ON]
|
||||
self._attr_operation_list.append(STATE_OFF)
|
||||
self._attr_supported_features |= WaterHeaterEntityFeature.OPERATION_MODE
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Turn the water heater on."""
|
||||
@ -165,16 +156,12 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
await self.set_property_async(prop=self._prop_on, value=False)
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the temperature the water heater should heat water to."""
|
||||
if not self._prop_target_temp:
|
||||
return
|
||||
await self.set_property_async(
|
||||
prop=self._prop_target_temp, value=kwargs[ATTR_TEMPERATURE])
|
||||
"""Set the target temperature."""
|
||||
await self.set_property_async(prop=self._prop_target_temp,
|
||||
value=kwargs[ATTR_TEMPERATURE])
|
||||
|
||||
async def async_set_operation_mode(self, operation_mode: str) -> None:
|
||||
"""Set the operation mode of the water heater.
|
||||
Must be in the operation_list.
|
||||
"""
|
||||
"""Set the operation mode of the water heater."""
|
||||
if operation_mode == STATE_OFF:
|
||||
await self.set_property_async(prop=self._prop_on, value=False)
|
||||
return
|
||||
@ -182,32 +169,32 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
|
||||
await self.set_property_async(prop=self._prop_on, value=True)
|
||||
return
|
||||
if self.get_prop_value(prop=self._prop_on) is False:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_on, value=True, write_ha_state=False)
|
||||
await self.set_property_async(
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=operation_mode))
|
||||
await self.set_property_async(prop=self._prop_on,
|
||||
value=True,
|
||||
write_ha_state=False)
|
||||
await self.set_property_async(prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map,
|
||||
value=operation_mode))
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> Optional[float]:
|
||||
"""Return the current temperature."""
|
||||
return self.get_prop_value(prop=self._prop_temp)
|
||||
"""The current temperature."""
|
||||
return (None if self._prop_temp is None else self.get_prop_value(
|
||||
prop=self._prop_temp))
|
||||
|
||||
@property
|
||||
def target_temperature(self) -> Optional[float]:
|
||||
"""Return the target temperature."""
|
||||
if not self._prop_target_temp:
|
||||
return None
|
||||
return self.get_prop_value(prop=self._prop_target_temp)
|
||||
"""The target temperature."""
|
||||
return (None if self._prop_target_temp is None else self.get_prop_value(
|
||||
prop=self._prop_target_temp))
|
||||
|
||||
@property
|
||||
def current_operation(self) -> Optional[str]:
|
||||
"""Return the current mode."""
|
||||
"""The current mode."""
|
||||
if self.get_prop_value(prop=self._prop_on) is False:
|
||||
return STATE_OFF
|
||||
if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
|
||||
return STATE_ON
|
||||
return self.get_map_value(
|
||||
map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
return (None if self._prop_mode is None else self.get_map_value(
|
||||
map_=self._mode_map, key=self.get_prop_value(prop=self._prop_mode)))
|
||||
|
@ -33,9 +33,11 @@ git checkout v1.0.0
|
||||
|
||||
### 方法 2: [HACS](https://hacs.xyz/)
|
||||
|
||||
HACS > 右上角三个点 > Custom repositories > Repository: https://github.com/XiaoMi/ha_xiaomi_home.git & Category or Type: Integration > ADD > 点击 HACS 的 New 或 Available for download 分类下的 Xiaomi Home ,进入集成详情页 > DOWNLOAD
|
||||
一键从 HACS 安装米家集成:
|
||||
|
||||
> 米家集成暂未添加到 HACS 商店,敬请期待。
|
||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=XiaoMi&repository=ha_xiaomi_home&category=integration)
|
||||
|
||||
或者,HACS > 在搜索框中输入 **Xiaomi Home** > 点击 **Xiaomi Home** ,进入集成详情页 > DOWNLOAD
|
||||
|
||||
### 方法 3:通过 [Samba](https://github.com/home-assistant/addons/tree/master/samba) 或 [FTPS](https://github.com/hassio-addons/addon-ftp) 手动安装
|
||||
|
||||
@ -47,7 +49,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
||||
|
||||
[设置 > 设备与服务 > 添加集成](https://my.home-assistant.io/redirect/brand/?brand=xiaomi_home) > 搜索“`Xiaomi Home`” > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
||||
|
||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||
[](https://my.home-assistant.io/redirect/config_flow_start/?domain=xiaomi_home)
|
||||
|
||||
### 添加 MIoT 设备
|
||||
|
||||
@ -59,7 +61,7 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
||||
|
||||
方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 添加中枢 > 下一步 > 请点击此处进行登录 > 使用小米账号登录
|
||||
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||
[](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home)
|
||||
|
||||
### 修改配置项
|
||||
|
||||
@ -155,6 +157,8 @@ HACS > 右上角三个点 > Custom repositories > Repository: https://github.com
|
||||
|
||||
转换后的实体为 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)
|
||||
|
||||
| in(输入参数列表) | 转换后的实体 |
|
||||
@ -289,39 +293,37 @@ event instance name 下的值表示转换后实体所用的 `_attr_device_class`
|
||||
|
||||
### 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`的格式如下:
|
||||
|
||||
```
|
||||
{
|
||||
"<MIoT-Spec-V2 device instance>":{
|
||||
"services": list<service_iid: str>,
|
||||
"properties": list<service_iid.property_iid: str>,
|
||||
"events": list<service_iid.event_iid: str>,
|
||||
"actions": list<service_iid.action_iid: str>,
|
||||
}
|
||||
}
|
||||
```yaml
|
||||
<MIoT-Spec-V2 device instance urn without the version field>:
|
||||
services: list<service_iid: str>
|
||||
properties: list<service_iid.property_iid: str>
|
||||
events: list<service_iid.event_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 )。支持通配符匹配。
|
||||
|
||||
示例:
|
||||
|
||||
```
|
||||
{
|
||||
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{
|
||||
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance.
|
||||
},
|
||||
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": {
|
||||
"services": ["3"], # Filter out the service whose iid=3.
|
||||
"properties": ["4.*"] # Filter out all properties in the service whose iid=4.
|
||||
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service.
|
||||
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service.
|
||||
}
|
||||
}
|
||||
```yaml
|
||||
urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1:
|
||||
services:
|
||||
- '*' # 排除所有服务,相当于排除拥有该 MIoT-Spec-V2 的设备。
|
||||
urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
|
||||
services:
|
||||
- '3' # 排除 siid=3 的服务。
|
||||
properties:
|
||||
- '4.*' # 排除 siid=4 服务的所有属性。
|
||||
events:
|
||||
- '4.1' # 排除 siid=4 服务的 eiid=1 的事件。
|
||||
actions:
|
||||
- '4.1' # 排除 siid=4 服务的 aiid=1 的方法。
|
||||
```
|
||||
|
||||
所有设备的设备信息服务( urn:miot-spec-v2:service:device-information:00007801 )均不会生成 Home Assistant 实体。
|
||||
@ -353,7 +355,7 @@ instance code 为 MIoT-Spec-V2 实例代码,格式如下:
|
||||
```
|
||||
service:<siid> # 服务
|
||||
service:<siid>:property:<piid> # 属性
|
||||
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的值
|
||||
service:<siid>:property:<piid>:valuelist:<value> # 属性取值列表的索引值
|
||||
service:<siid>:event:<eiid> # 事件
|
||||
service:<siid>:action:<aiid> # 方法
|
||||
```
|
||||
@ -376,7 +378,7 @@ siid、piid、eiid、aiid、value 均为十进制三位整数。
|
||||
}
|
||||
```
|
||||
|
||||
> 在 Home Assistant 中修改了 `custom_components/xiaomi_home/miot/specs` 路径下的 `specv2entity.py`、`spec_filter.json`、`multi_lang.json` 文件的内容,需要在集成配置中更新实体转换规则才能生效。方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新实体转换规则
|
||||
> 在 Home Assistant 中修改了 `custom_components/xiaomi_home/miot/specs` 路径下的任何文件(`spec_filter.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(
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/i18n')
|
||||
SPEC_BOOL_TRANS_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/bool_trans.yaml')
|
||||
SPEC_FILTER_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_filter.yaml')
|
||||
SPEC_MULTI_LANG_FILE = path.join(
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/multi_lang.json')
|
||||
SPEC_ADD_FILE = path.join(
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_add.json')
|
||||
SPEC_MODIFY_FILE = path.join(
|
||||
ROOT_PATH,
|
||||
'../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
||||
ROOT_PATH, '../custom_components/xiaomi_home/miot/specs/spec_modify.yaml')
|
||||
|
||||
|
||||
def load_json_file(file_path: str) -> Optional[dict]:
|
||||
@ -30,7 +31,7 @@ def load_json_file(file_path: str) -> Optional[dict]:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
return json.load(file)
|
||||
except FileNotFoundError:
|
||||
_LOGGER.info('%s is not found.', file_path,)
|
||||
_LOGGER.info('%s is not found.', file_path)
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
_LOGGER.info('%s is not a valid JSON file.', file_path)
|
||||
@ -39,7 +40,7 @@ def load_json_file(file_path: str) -> Optional[dict]:
|
||||
|
||||
def save_json_file(file_path: str, data: dict) -> None:
|
||||
with open(file_path, 'w', encoding='utf-8') as file:
|
||||
json.dump(data, file, ensure_ascii=False, indent=4)
|
||||
json.dump(data, file, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def load_yaml_file(file_path: str) -> Optional[dict]:
|
||||
@ -56,9 +57,12 @@ def load_yaml_file(file_path: str) -> Optional[dict]:
|
||||
|
||||
def save_yaml_file(file_path: str, data: dict) -> None:
|
||||
with open(file_path, 'w', encoding='utf-8') as file:
|
||||
yaml.safe_dump(
|
||||
data, file, default_flow_style=False,
|
||||
allow_unicode=True, indent=2, sort_keys=False)
|
||||
yaml.safe_dump(data,
|
||||
file,
|
||||
default_flow_style=False,
|
||||
allow_unicode=True,
|
||||
indent=2,
|
||||
sort_keys=False)
|
||||
|
||||
|
||||
def dict_str_str(d: dict) -> bool:
|
||||
@ -132,13 +136,120 @@ def bool_trans(d: dict) -> bool:
|
||||
for key, trans in d['translate'].items():
|
||||
trans_keys: set[str] = set(trans.keys())
|
||||
if set(trans.keys()) != default_keys:
|
||||
_LOGGER.info(
|
||||
'bool trans inconsistent, %s, %s, %s',
|
||||
key, default_keys, trans_keys)
|
||||
_LOGGER.info('bool trans inconsistent, %s, %s, %s', key,
|
||||
default_keys, trans_keys)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def multi_lang(data: dict) -> bool:
|
||||
"""dict[str, dict[str, dict[str, str]]]"""
|
||||
for key in data.keys():
|
||||
if key.count(':') != 5:
|
||||
return False
|
||||
return nested_3_dict_str_str(data)
|
||||
|
||||
|
||||
def spec_add(data: dict) -> bool:
|
||||
"""dict[str, list[dict[str, int| str | list]]]"""
|
||||
if not isinstance(data, dict):
|
||||
return False
|
||||
for urn, content in data.items():
|
||||
if not isinstance(urn, str) or not isinstance(content, (list, str)):
|
||||
return False
|
||||
if isinstance(content, str):
|
||||
continue
|
||||
for service in content:
|
||||
if ('iid' not in service) or ('type' not in service) or (
|
||||
'description'
|
||||
not in service) or (('properties' not in service) and
|
||||
('actions' not in service) and
|
||||
('events' not in service)):
|
||||
return False
|
||||
type_strs: list[str] = service['type'].split(':')
|
||||
if type_strs[1] != 'miot-spec-v2':
|
||||
return False
|
||||
if 'properties' in service:
|
||||
if not isinstance(service['properties'], list):
|
||||
return False
|
||||
for prop in service['properties']:
|
||||
if ('iid' not in prop) or ('type' not in prop) or (
|
||||
'description' not in prop) or (
|
||||
'format' not in prop) or ('access' not in prop):
|
||||
return False
|
||||
if not isinstance(prop['iid'], int) or not isinstance(
|
||||
prop['type'], str) or not isinstance(
|
||||
prop['description'], str) or not isinstance(
|
||||
prop['format'], str) or not isinstance(
|
||||
prop['access'], list):
|
||||
return False
|
||||
type_strs = prop['type'].split(':')
|
||||
if type_strs[1] != 'miot-spec-v2':
|
||||
return False
|
||||
for access in prop['access']:
|
||||
if access not in ['read', 'write', 'notify']:
|
||||
return False
|
||||
if 'value-range' in prop:
|
||||
if not isinstance(prop['value-range'], list):
|
||||
return False
|
||||
for value in prop['value-range']:
|
||||
if not isinstance(value, (int, float)):
|
||||
return False
|
||||
if 'value-list' in prop:
|
||||
if not isinstance(prop['value-list'], list):
|
||||
return False
|
||||
for item in prop['value-list']:
|
||||
if 'value' not in item or 'description' not in item:
|
||||
return False
|
||||
if not isinstance(item['value'],
|
||||
int) or not isinstance(
|
||||
item['description'], str):
|
||||
return False
|
||||
if 'actions' in service:
|
||||
if not isinstance(service['actions'], list):
|
||||
return False
|
||||
for action in service['actions']:
|
||||
if ('iid' not in action) or ('type' not in action) or (
|
||||
'description' not in action) or (
|
||||
'in' not in action) or ('out' not in action):
|
||||
return False
|
||||
if not isinstance(action['iid'], int) or not isinstance(
|
||||
action['type'], str) or not isinstance(
|
||||
action['description'], str) or not isinstance(
|
||||
action['in'], list) or not isinstance(
|
||||
action['out'], list):
|
||||
return False
|
||||
type_strs = action['type'].split(':')
|
||||
if type_strs[1] != 'miot-spec-v2':
|
||||
return False
|
||||
for param in action['in']:
|
||||
if not isinstance(param, int):
|
||||
return False
|
||||
for param in action['out']:
|
||||
if not isinstance(param, int):
|
||||
return False
|
||||
if 'events' in service:
|
||||
if not isinstance(service['events'], list):
|
||||
return False
|
||||
for event in service['events']:
|
||||
if ('iid' not in event) or ('type' not in event) or (
|
||||
'description' not in event) or ('arguments'
|
||||
not in event):
|
||||
return False
|
||||
if not isinstance(event['iid'], int) or not isinstance(
|
||||
event['type'], str) or not isinstance(
|
||||
event['description'], str) or not isinstance(
|
||||
event['arguments'], list):
|
||||
return False
|
||||
type_strs = event['type'].split(':')
|
||||
if type_strs[1] != 'miot-spec-v2':
|
||||
return False
|
||||
for param in event['arguments']:
|
||||
if not isinstance(param, int):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def spec_modify(data: dict) -> bool:
|
||||
"""dict[str, str | dict[str, dict]]"""
|
||||
if not isinstance(data, dict):
|
||||
@ -159,25 +270,22 @@ def compare_dict_structure(dict1: dict, dict2: dict) -> bool:
|
||||
_LOGGER.info('invalid type')
|
||||
return False
|
||||
if dict1.keys() != dict2.keys():
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, %s, %s', dict1.keys(), dict2.keys())
|
||||
_LOGGER.info('inconsistent key values, %s, %s', dict1.keys(),
|
||||
dict2.keys())
|
||||
return False
|
||||
for key in dict1:
|
||||
if isinstance(dict1[key], dict) and isinstance(dict2[key], dict):
|
||||
if not compare_dict_structure(dict1[key], dict2[key]):
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, dict, %s', key)
|
||||
_LOGGER.info('inconsistent key values, dict, %s', key)
|
||||
return False
|
||||
elif isinstance(dict1[key], list) and isinstance(dict2[key], list):
|
||||
if not all(
|
||||
isinstance(i, type(j))
|
||||
for i, j in zip(dict1[key], dict2[key])):
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, list, %s', key)
|
||||
_LOGGER.info('inconsistent key values, list, %s', key)
|
||||
return False
|
||||
elif not isinstance(dict1[key], type(dict2[key])):
|
||||
_LOGGER.info(
|
||||
'inconsistent key values, type, %s', key)
|
||||
_LOGGER.info('inconsistent key values, type, %s', key)
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -200,6 +308,16 @@ def sort_spec_filter(file_path: str):
|
||||
return filter_data
|
||||
|
||||
|
||||
def sort_spec_add(file_path: str):
|
||||
filter_data = load_json_file(file_path=file_path)
|
||||
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||
return dict(sorted(filter_data.items()))
|
||||
|
||||
|
||||
def sort_multi_lang(file_path: str):
|
||||
return sort_spec_add(file_path)
|
||||
|
||||
|
||||
def sort_spec_modify(file_path: str):
|
||||
filter_data = load_yaml_file(file_path=file_path)
|
||||
assert isinstance(filter_data, dict), f'{file_path} format error'
|
||||
@ -222,6 +340,22 @@ def test_spec_filter():
|
||||
assert spec_filter(data), f'{SPEC_FILTER_FILE} format error'
|
||||
|
||||
|
||||
@pytest.mark.github
|
||||
def test_multi_lang():
|
||||
data = load_json_file(SPEC_MULTI_LANG_FILE)
|
||||
assert isinstance(data, dict)
|
||||
assert data, f'load {SPEC_MULTI_LANG_FILE} failed'
|
||||
assert multi_lang(data), f'{SPEC_MULTI_LANG_FILE} format error'
|
||||
|
||||
|
||||
@pytest.mark.github
|
||||
def test_spec_add():
|
||||
data = load_json_file(SPEC_ADD_FILE)
|
||||
assert isinstance(data, dict)
|
||||
assert data, f'load {SPEC_ADD_FILE} failed'
|
||||
assert spec_add(data), f'{SPEC_ADD_FILE} format error'
|
||||
|
||||
|
||||
@pytest.mark.github
|
||||
def test_spec_modify():
|
||||
data = load_yaml_file(SPEC_MODIFY_FILE)
|
||||
@ -255,7 +389,8 @@ def test_miot_lang_integrity():
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from miot.const import INTEGRATION_LANGUAGES
|
||||
integration_lang_list: list[str] = [
|
||||
f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())]
|
||||
f'{key}.json' for key in list(INTEGRATION_LANGUAGES.keys())
|
||||
]
|
||||
translations_names: set[str] = set(listdir(TRANS_RELATIVE_PATH))
|
||||
assert len(translations_names) == len(integration_lang_list)
|
||||
assert translations_names == set(integration_lang_list)
|
||||
@ -271,21 +406,18 @@ def test_miot_lang_integrity():
|
||||
default_dict = load_json_file(
|
||||
path.join(TRANS_RELATIVE_PATH, integration_lang_list[0]))
|
||||
for name in list(integration_lang_list)[1:]:
|
||||
compare_dict = load_json_file(
|
||||
path.join(TRANS_RELATIVE_PATH, name))
|
||||
compare_dict = load_json_file(path.join(TRANS_RELATIVE_PATH, name))
|
||||
if not compare_dict_structure(default_dict, compare_dict):
|
||||
_LOGGER.info(
|
||||
'compare_dict_structure failed /translations, %s', name)
|
||||
_LOGGER.info('compare_dict_structure failed /translations, %s',
|
||||
name)
|
||||
assert False
|
||||
# Check i18n files structure
|
||||
default_dict = load_json_file(
|
||||
path.join(MIOT_I18N_RELATIVE_PATH, integration_lang_list[0]))
|
||||
for name in list(integration_lang_list)[1:]:
|
||||
compare_dict = load_json_file(
|
||||
path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||
compare_dict = load_json_file(path.join(MIOT_I18N_RELATIVE_PATH, name))
|
||||
if not compare_dict_structure(default_dict, compare_dict):
|
||||
_LOGGER.info(
|
||||
'compare_dict_structure failed /miot/i18n, %s', name)
|
||||
_LOGGER.info('compare_dict_structure failed /miot/i18n, %s', name)
|
||||
assert False
|
||||
|
||||
|
||||
@ -303,12 +435,27 @@ def test_miot_data_sort():
|
||||
f'{SPEC_BOOL_TRANS_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
assert json.dumps(load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
assert json.dumps(
|
||||
load_yaml_file(file_path=SPEC_FILTER_FILE)) == json.dumps(
|
||||
sort_spec_filter(file_path=SPEC_FILTER_FILE)), (
|
||||
f'{SPEC_FILTER_FILE} not sorted, goto project root path'
|
||||
load_json_file(file_path=SPEC_MULTI_LANG_FILE)) == json.dumps(
|
||||
sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)), (
|
||||
f'{SPEC_MULTI_LANG_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
assert json.dumps(load_json_file(file_path=SPEC_ADD_FILE)) == json.dumps(
|
||||
sort_spec_add(file_path=SPEC_ADD_FILE)), (
|
||||
f'{SPEC_ADD_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
assert json.dumps(load_yaml_file(file_path=SPEC_MODIFY_FILE)) == json.dumps(
|
||||
sort_spec_modify(file_path=SPEC_MODIFY_FILE)), (
|
||||
f'{SPEC_MODIFY_FILE} not sorted, goto project root path'
|
||||
' and run the following command sorting, ',
|
||||
'pytest -s -v -m update ./test/check_rule_format.py')
|
||||
|
||||
|
||||
@pytest.mark.update
|
||||
@ -319,6 +466,11 @@ def test_sort_spec_data():
|
||||
sort_data = sort_spec_filter(file_path=SPEC_FILTER_FILE)
|
||||
save_yaml_file(file_path=SPEC_FILTER_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_FILTER_FILE)
|
||||
sort_data = sort_multi_lang(file_path=SPEC_MULTI_LANG_FILE)
|
||||
save_json_file(file_path=SPEC_MULTI_LANG_FILE, data=sort_data)
|
||||
sort_data = sort_spec_add(file_path=SPEC_ADD_FILE)
|
||||
save_json_file(file_path=SPEC_ADD_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_ADD_FILE)
|
||||
sort_data = sort_spec_modify(file_path=SPEC_MODIFY_FILE)
|
||||
save_yaml_file(file_path=SPEC_MODIFY_FILE, data=sort_data)
|
||||
_LOGGER.info('%s formatted.', SPEC_MODIFY_FILE)
|
||||
|
Reference in New Issue
Block a user