11 Commits

Author SHA1 Message Date
4c2e10038c docs: update changelog and version to v0.3.4 (#1238) 2025-07-09 09:39:43 +08:00
9afc62f39a docs: modify README (#1237)
* docs: modify spec_filter.yaml in README

* docs: modify event descriptions in README
2025-07-09 09:17:57 +08:00
b46805b92c fix: airer status for cover entity (#1235)
* fix: xiaomi.airer.pro3 airer status rising (#1222)

* fix: airer status

* fix: filter out non alphabetic characters from status descriptions
2025-07-09 09:16:55 +08:00
a43447ef61 Fix specs (#1236)
* fix: ignore bjkcz.curtain.kczble curtain status (#1184)

* fix: yutai.plug.fsov8m power consumption (#1229)
2025-07-09 09:14:10 +08:00
ted
e5165f34da fix: Fix the HA warning in the logs related to vacuum state setting (#694)
Some checks failed
Tests / check-rule-format (push) Failing after 12s
Validate / validate-hassfest (push) Failing after 8s
Validate / validate-hacs (push) Failing after 3m6s
Validate / validate-lint (push) Failing after 11s
Validate / validate-setup (push) Failing after 9s
2025-07-08 13:48:17 +08:00
9fbbb26d33 fix: translation it.json (#1215) 2025-07-08 13:46:36 +08:00
5b1d003bb2 feat: subscribe the BLE device up messages even though the device is offline (#1207)
Some checks failed
Tests / check-rule-format (push) Failing after 10s
Validate / validate-hassfest (push) Failing after 5s
Validate / validate-hacs (push) Failing after 11s
Validate / validate-lint (push) Failing after 4s
Validate / validate-setup (push) Failing after 5s
* feat: subscribe the BLE device up messages even though the device is offline (#1170)

* fix: set all BLE devices online
2025-06-30 11:27:12 +08:00
6069eaaba8 feat: exclude unsupported model (#1205)
* feat: ignore unsupported models (#933)

* fix: remove unnecessary logs
2025-06-30 11:12:58 +08:00
fd57e7c565 fix: reconnect delay time (#1200)
* fix: reset the reconnect interval when connected (#1175)

* feat: set the default reconnect delay time as 10 seconds

* fix: get the minimum reconnect interval
2025-06-30 11:12:18 +08:00
096b33f3c9 fix: the operation mode when the device does not have a mode property (#1199) 2025-06-30 11:11:36 +08:00
664787ca58 fix: ptx air-conditioner environment temperature (#1210)
Some checks failed
Tests / check-rule-format (push) Failing after 6s
Validate / validate-hassfest (push) Failing after 5s
Validate / validate-hacs (push) Failing after 13s
Validate / validate-lint (push) Failing after 4s
Validate / validate-setup (push) Failing after 7s
* fix: ptx air-conditioner temperature #1163

* fix: environment temperature siid and piid
2025-06-27 17:52:39 +08:00
16 changed files with 245 additions and 105 deletions

View File

@ -1,9 +1,23 @@
# CHANGELOG # CHANGELOG
## v0.3.4
### Added
- Exclude the unsupported device models. [#1205](https://github.com/XiaoMi/ha_xiaomi_home/pull/1205)
### Changed
- Subscribe the BLE device upstream messages even though the device is offline. [#1207](https://github.com/XiaoMi/ha_xiaomi_home/pull/1207)
- Record "opening", "closing" and "closed" status of the airer service that occur frequently and do not record "stop" status for the cover entity. [#1235](https://github.com/XiaoMi/ha_xiaomi_home/pull/1235)
- Modify README about spec_filter.yaml and the event attributes. [#1237](https://github.com/XiaoMi/ha_xiaomi_home/pull/1237)
### Fixed
- Fix the reconnect delay time to be reset when the client is connected to the broker. [#1200](https://github.com/XiaoMi/ha_xiaomi_home/pull/1200)
- Fix the HA warning in the logs related to vacuum state setting. [#694](https://github.com/XiaoMi/ha_xiaomi_home/pull/694)
- Fix the operation mode when the device does not have a mode property. [#1199](https://github.com/XiaoMi/ha_xiaomi_home/pull/1199)
- Fix 090615.aircondition.ktf environment temperature. [#1210](https://github.com/XiaoMi/ha_xiaomi_home/pull/1210)
- Fix a missing variable in translation it.json. [#1215](https://github.com/XiaoMi/ha_xiaomi_home/pull/1215)
- Fix yutai.plug.fsov8m power consumption and ignore bjkcz.curtain.kczble curtain status. [#1236](https://github.com/XiaoMi/ha_xiaomi_home/pull/1236)
## v0.3.3 ## v0.3.3
### Changed ### Changed
- Change the log level of error "mips unsub internal error, 4, None". [#1135](https://github.com/XiaoMi/ha_xiaomi_home/pull/1135) - 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) - Add necessary logs for distinguishing the set_properties command source. [#1160](https://github.com/XiaoMi/ha_xiaomi_home/pull/1160)
### Fixed ### Fixed
- Fix tofan.airrtc.wk01 thermostat and air conditioner service. [#1160](https://github.com/XiaoMi/ha_xiaomi_home/pull/1160) - 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 mrbond.airer.m1t closing status. [#1134](https://github.com/XiaoMi/ha_xiaomi_home/pull/1134)

View File

@ -155,6 +155,8 @@ In MIoT-Spec-V2 protocol, a product is defined as a device. A device contains se
MIoT-Spec-V2 event is transformed to Event entity in Home Assistant. The event's parameters are also passed to entity's `_trigger_event`. MIoT-Spec-V2 event is transformed to Event entity in Home Assistant. The event's parameters are also passed to entity's `_trigger_event`.
MIoT-Spec-V2 event's arguments field is the list of parameters of the event. The list elements represent the piid of the property in the same service. For example, the [MIoT-Spec-V2](http://poc.miot-spec.srv/miot-spec-v2/instance?type=urn:miot-spec-v2:device:remote-control:0000A021:xiaomi-mcn002:1:0000D057) of the Xiaomi Wireless Double-key Switch contains the siid=2 Switch Sensor service. The eiid=1014 Long Press event of the service is triggered when a button is long pressed. The debug level log will print `Press and hold, attributes: {'Button Type': 1}`. This is an example log that the button type is 1, which means the right button is long pressed.
- Action - Action
| in | Entity in Home Assistant | | in | Entity in Home Assistant |
@ -289,39 +291,37 @@ The value of the event instance name indicates `_attr_device_class` of the Home
### MIoT-Spec-V2 Filter ### MIoT-Spec-V2 Filter
`spec_filter.json` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity. `spec_filter.yaml` is used to filter out the MIoT-Spec-V2 instance that will not be converted to Home Assistant entity.
The format of `spec_filter.json` is as follows. The format of `spec_filter.json` is as follows.
``` ```yaml
{ <MIoT-Spec-V2 device instance urn without the version field>:
"<MIoT-Spec-V2 device instance>":{ services: list<service_iid: str>
"services": list<service_iid: str>, properties: list<service_iid.property_iid: str>
"properties": list<service_iid.property_iid: str>, events: list<service_iid.event_iid: str>
"events": list<service_iid.event_iid: str>, actions: list<service_iid.action_iid: str>
"actions": list<service_iid.action_iid: str>,
}
}
``` ```
The key of `spec_filter.json` dictionary is the urn excluding the "version" field of the MIoT-Spec-V2 device instance. The firmware of different versions of the same product may be associated with the MIoT-Spec-V2 device instances of different versions. It is required that the MIoT-Spec-V2 instance of a higher version must contain all MIoT-Spec-V2 instances of the lower versions when a vendor defines the MIoT-Spec-V2 of its product on MIoT platform. Thus, the key of `spec_filter.json` does not need to specify the version number of MIoT-Spec-V2 device instance. The key of `spec_filter.yaml` dictionary is the urn excluding the "version" field of the MIoT-Spec-V2 device instance. The firmware of different versions of the same product may be associated with the MIoT-Spec-V2 device instances of different versions. It is required that the MIoT-Spec-V2 instance of a higher version must contain all MIoT-Spec-V2 instances of the lower versions when a vendor defines the MIoT-Spec-V2 of its product on MIoT platform. Thus, the key of `spec_filter.yaml` does not need to specify the version number of MIoT-Spec-V2 device instance.
The value of "services", "properties", "events" or "actions" fields under "device instance" is the instance id (iid) of the service, property, event or action that will be ignored in the conversion process. Wildcard matching is supported. The value of "services", "properties", "events" or "actions" fields under "device instance" is the instance id (iid) of the service, property, event or action that will be ignored in the conversion process. Wildcard matching is supported.
Example: Example:
``` ```yaml
{ urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1:
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{ services:
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance. - '*' # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2.
}, urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": { services:
"services": ["3"], # Filter out the service whose iid=3. - '3' # Filter out the siid=3 service.
"properties": ["4.*"] # Filter out all properties in the service whose iid=4. properties:
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service. - '4.*' # Filter out all properties in the siid=4 service.
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service. events:
} - '4.1' # Filter out the eiid=1 event in the siid=4 service.
} actions:
- '4.1' # Filter out the aiid=1 action in the siid=4 service.
``` ```
Device information service (urn:miot-spec-v2:service:device-information:00007801) of all devices will never be converted to Home Assistant entity. Device information service (urn:miot-spec-v2:service:device-information:00007801) of all devices will never be converted to Home Assistant entity.
@ -376,7 +376,7 @@ Example:
} }
``` ```
> If you edit any files in the `custom_components/xiaomi_home/miot/specs` directory (`spec_filter.py`, `spec_modify.json`, `multi_lang.json`, etc.) in your Home Assistant, you need to update the entity conversion rule in the integration's CONFIGURE page to take effect. Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update entity conversion rules > If you edit any files in the `custom_components/xiaomi_home/miot/specs` directory (`spec_filter.yaml`, `spec_modify.yaml`, `multi_lang.json`, etc.) in your Home Assistant, you need to update the entity conversion rule in the integration's CONFIGURE page to take effect. Method: [Settings > Devices & services > Configured > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > CONFIGURE > Update entity conversion rules
## Documents ## Documents

View File

@ -46,8 +46,9 @@ off Xiaomi or its affiliates' products.
Cover entities for Xiaomi Home. Cover entities for Xiaomi Home.
""" """
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any, Optional from typing import Any, Optional
import re
import logging
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -97,7 +98,6 @@ class Cover(MIoTServiceEntity, CoverEntity):
_prop_status: Optional[MIoTSpecProperty] _prop_status: Optional[MIoTSpecProperty]
_prop_status_opening: Optional[list[int]] _prop_status_opening: Optional[list[int]]
_prop_status_closing: Optional[list[int]] _prop_status_closing: Optional[list[int]]
_prop_status_stop: Optional[list[int]]
_prop_status_closed: Optional[list[int]] _prop_status_closed: Optional[list[int]]
_prop_current_position: Optional[MIoTSpecProperty] _prop_current_position: Optional[MIoTSpecProperty]
_prop_target_position: Optional[MIoTSpecProperty] _prop_target_position: Optional[MIoTSpecProperty]
@ -122,7 +122,6 @@ class Cover(MIoTServiceEntity, CoverEntity):
self._prop_status = None self._prop_status = None
self._prop_status_opening = [] self._prop_status_opening = []
self._prop_status_closing = [] self._prop_status_closing = []
self._prop_status_stop = []
self._prop_status_closed = [] self._prop_status_closed = []
self._prop_current_position = None self._prop_current_position = None
self._prop_target_position = None self._prop_target_position = None
@ -159,13 +158,21 @@ class Cover(MIoTServiceEntity, CoverEntity):
self.entity_id) self.entity_id)
continue continue
for item in prop.value_list.items: for item in prop.value_list.items:
if item.name in {'opening', 'open', 'up'}: item_str: str = item.name
item_name: str = re.sub(r'[^a-z]', '', item_str)
if item_name in {
'opening', 'open', 'up', 'uping', 'rise', 'rising'
}:
self._prop_status_opening.append(item.value) self._prop_status_opening.append(item.value)
elif item.name in {'closing', 'close', 'down', 'dowm'}: elif item_name in {
'closing', 'close', 'down', 'dowm', 'falling',
'dropping', 'downing', 'lower'
}:
self._prop_status_closing.append(item.value) self._prop_status_closing.append(item.value)
elif item.name in {'stop', 'stopped', 'pause'}: elif item_name in {
self._prop_status_stop.append(item.value) 'stopatlowest', 'stoplowerlimit', 'lowerlimitstop',
elif item.name in {'closed'}: 'floor', 'lowerlimit'
}:
self._prop_status_closed.append(item.value) self._prop_status_closed.append(item.value)
self._prop_status = prop self._prop_status = prop
elif prop.name == 'current-position': elif prop.name == 'current-position':

View File

@ -25,7 +25,7 @@
"cryptography", "cryptography",
"psutil" "psutil"
], ],
"version": "v0.3.3", "version": "v0.3.4",
"zeroconf": [ "zeroconf": [
"_miot-central._tcp.local." "_miot-central._tcp.local."
] ]

View File

@ -85,6 +85,11 @@ SUPPORTED_PLATFORMS: list = [
'water_heater', 'water_heater',
] ]
UNSUPPORTED_MODELS: list = [
'chuangmi.ir.v2',
'xiaomi.router.rd03'
]
DEFAULT_CLOUD_SERVER: str = 'cn' DEFAULT_CLOUD_SERVER: str = 'cn'
CLOUD_SERVERS: dict = { CLOUD_SERVERS: dict = {
'cn': '中国大陆', 'cn': '中国大陆',

View File

@ -1354,6 +1354,11 @@ class MIoTClient:
"""Update cloud devices. """Update cloud devices.
NOTICE: This function will operate the cloud_list NOTICE: This function will operate the cloud_list
""" """
# MIoT cloud service may not publish the online state updating message
# for the BLE device. Assume that all BLE devices are online.
for did, info in cloud_list.items():
if did.startswith('blt.'):
info['online'] = True
for did, info in self._device_list_cache.items(): for did, info in self._device_list_cache.items():
if filter_dids and did not in filter_dids: if filter_dids and did not in filter_dids:
continue continue

View File

@ -59,6 +59,7 @@ import aiohttp
# pylint: disable=relative-beyond-top-level # pylint: disable=relative-beyond-top-level
from .common import calc_group_id from .common import calc_group_id
from .const import ( from .const import (
UNSUPPORTED_MODELS,
DEFAULT_OAUTH2_API_HOST, DEFAULT_OAUTH2_API_HOST,
MIHOME_HTTP_API_TIMEOUT, MIHOME_HTTP_API_TIMEOUT,
OAUTH2_AUTH_URL) OAUTH2_AUTH_URL)
@ -573,6 +574,10 @@ class MIoTHttpClient:
# were implemented. # were implemented.
_LOGGER.info('ignore miwifi.* device, cloud, %s', did) _LOGGER.info('ignore miwifi.* device, cloud, %s', did)
continue continue
if model in UNSUPPORTED_MODELS:
_LOGGER.info('ignore unsupported model %s, cloud, %s',
model, did)
continue
device_infos[did] = { device_infos[did] = {
'did': did, 'did': did,
'uid': device.get('uid', None), 'uid': device.get('uid', None),

View File

@ -1207,10 +1207,9 @@ class MIoTPropertyEntity(Entity):
self._attr_available = miot_device.online self._attr_available = miot_device.online
_LOGGER.info( _LOGGER.info(
'new miot property entity, %s, %s, %s, %s, %s, %s, %s', 'new miot property entity, %s, %s, %s, %s, %s',
self.miot_device.name, self._attr_name, spec.platform, self.miot_device.name, self._attr_name, spec.platform,
spec.device_class, self.entity_id, self._value_range, spec.device_class, self.entity_id)
self._value_list)
@property @property
def device_info(self) -> Optional[DeviceInfo]: def device_info(self) -> Optional[DeviceInfo]:

View File

@ -68,7 +68,7 @@ from paho.mqtt.client import (
# pylint: disable=relative-beyond-top-level # pylint: disable=relative-beyond-top-level
from .common import MIoTMatcher from .common import MIoTMatcher
from .const import MIHOME_MQTT_KEEPALIVE from .const import UNSUPPORTED_MODELS, MIHOME_MQTT_KEEPALIVE
from .miot_error import MIoTErrorCode, MIoTMipsError from .miot_error import MIoTErrorCode, MIoTMipsError
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -216,7 +216,7 @@ class _MipsClient(ABC):
MQTT_INTERVAL_S = 1 MQTT_INTERVAL_S = 1
MIPS_QOS: int = 2 MIPS_QOS: int = 2
UINT32_MAX: int = 0xFFFFFFFF UINT32_MAX: int = 0xFFFFFFFF
MIPS_RECONNECT_INTERVAL_MIN: float = 30 MIPS_RECONNECT_INTERVAL_MIN: float = 10
MIPS_RECONNECT_INTERVAL_MAX: float = 600 MIPS_RECONNECT_INTERVAL_MAX: float = 600
MIPS_SUB_PATCH: int = 300 MIPS_SUB_PATCH: int = 300
MIPS_SUB_INTERVAL: float = 1 MIPS_SUB_INTERVAL: float = 1
@ -641,6 +641,7 @@ class _MipsClient(ABC):
if not self._mqtt.is_connected(): if not self._mqtt.is_connected():
return return
self.log_info(f'mips connect, {flags}, {rc}, {props}') self.log_info(f'mips connect, {flags}, {rc}, {props}')
self.__reset_reconnect_time()
self._mqtt_state = True self._mqtt_state = True
self._internal_loop.call_soon( self._internal_loop.call_soon(
self._on_mips_connect, rc, props) self._on_mips_connect, rc, props)
@ -822,7 +823,7 @@ class _MipsClient(ABC):
self._internal_loop.stop() self._internal_loop.stop()
def __get_next_reconnect_time(self) -> float: def __get_next_reconnect_time(self) -> float:
if self._mips_reconnect_interval == 0: if self._mips_reconnect_interval < self.MIPS_RECONNECT_INTERVAL_MIN:
self._mips_reconnect_interval = self.MIPS_RECONNECT_INTERVAL_MIN self._mips_reconnect_interval = self.MIPS_RECONNECT_INTERVAL_MIN
else: else:
self._mips_reconnect_interval = min( self._mips_reconnect_interval = min(
@ -830,6 +831,9 @@ class _MipsClient(ABC):
self.MIPS_RECONNECT_INTERVAL_MAX) self.MIPS_RECONNECT_INTERVAL_MAX)
return self._mips_reconnect_interval return self._mips_reconnect_interval
def __reset_reconnect_time(self) -> None:
self._mips_reconnect_interval = 0
class MipsCloudClient(_MipsClient): class MipsCloudClient(_MipsClient):
"""MIoT Pub/Sub Cloud Client.""" """MIoT Pub/Sub Cloud Client."""
@ -1174,7 +1178,7 @@ class MipsLocalClient(_MipsClient):
or 'piid' not in msg or 'piid' not in msg
or 'value' not in msg or 'value' not in msg
): ):
# self.log_error(f'on_prop_msg, recv unknown msg, {payload}') self.log_info('unknown prop msg, %s', payload)
return return
if handler: if handler:
self.log_debug('local, on properties_changed, %s', payload) self.log_debug('local, on properties_changed, %s', payload)
@ -1361,6 +1365,9 @@ class MipsLocalClient(_MipsClient):
if name is None or urn is None or model is None: if name is None or urn is None or model is None:
self.log_error(f'invalid device info, {did}, {info}') self.log_error(f'invalid device info, {did}, {info}')
continue continue
if model in UNSUPPORTED_MODELS:
self.log_info(f'unsupported model, {model}, {did}')
continue
device_list[did] = { device_list[did] = {
'did': did, 'did': did,
'name': name, 'name': name,

View File

@ -1,4 +1,29 @@
{ {
"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": [ "urn:miot-spec-v2:device:airer:0000A00D:hyd-lyjpro:1": [
{ {
"iid": 3, "iid": 3,

View File

@ -1,3 +1,6 @@
urn:miot-spec-v2:device:air-conditioner:0000A004:090615-ktf:
services:
- '4'
urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4: urn:miot-spec-v2:device:air-purifier:0000A007:zhimi-ma4:
properties: properties:
- 9.* - 9.*
@ -41,9 +44,6 @@ urn:miot-spec-v2:device:motion-sensor:0000A014:xiaomi-pir1:
services: services:
- '1' - '1'
- '5' - '5'
urn:miot-spec-v2:device:router:0000A036:xiaomi-rd03:
services:
- '*'
urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01: urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:
services: services:
- '2' - '2'

View File

@ -75,6 +75,9 @@ urn:miot-spec-v2:device:bath-heater:0000A028:opple-acmoto:1:
urn:miot-spec-v2:device:bath-heater:0000A028:xiaomi-s1:1: urn:miot-spec-v2:device:bath-heater:0000A028:xiaomi-s1:1:
prop.4.4: prop.4.4:
name: fan-level-ventilation 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: urn:miot-spec-v2:device:fan:0000A005:dmaker-p33:1:
prop.2.2: prop.2.2:
name: fan-level-a name: fan-level-a
@ -209,6 +212,9 @@ urn:miot-spec-v2:device:outlet:0000A002:qmi-psv3:1:0000C816:
unit: mV unit: mV
prop.3.4: prop.3.4:
unit: mA 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:1:0000C816: urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816
urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816: urn:miot-spec-v2:device:outlet:0000A002:zimi-zncz01:2:0000C816:
prop.3.1: prop.3.1:

View File

@ -113,7 +113,7 @@
}, },
"config_options": { "config_options": {
"title": "Opzioni di Configurazione", "title": "Opzioni di Configurazione",
"description": "### Ciao, {nick_name}\r\n\r\nID Xiaomi: {uid}\r\nRegione di Login Corrente: {cloud_server}\r\n\r\nSeleziona le opzioni che desideri configurare, poi clicca AVANTI.", "description": "### Ciao, {nick_name}\r\n\r\nID Xiaomi: {uid}\r\nRegione di Login Corrente: {cloud_server}\r\nID istanza di integrazione: {instance_id}\r\n\r\nSeleziona le opzioni che desideri configurare, poi clicca AVANTI.",
"data": { "data": {
"integration_language": "Lingua dell'Integrazione", "integration_language": "Lingua dell'Integrazione",
"update_user_info": "Aggiorna le informazioni dell'utente", "update_user_info": "Aggiorna le informazioni dell'utente",

View File

@ -47,21 +47,18 @@ Vacuum entities for Xiaomi Home.
""" """
from __future__ import annotations from __future__ import annotations
from typing import Any, Optional from typing import Any, Optional
import re
import logging import logging
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (StateVacuumEntity,
StateVacuumEntity, VacuumEntityFeature)
VacuumEntityFeature
)
from .miot.const import DOMAIN from .miot.const import DOMAIN
from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData from .miot.miot_device import MIoTDevice, MIoTServiceEntity, MIoTEntityData
from .miot.miot_spec import ( from .miot.miot_spec import (MIoTSpecAction, MIoTSpecProperty)
MIoTSpecAction,
MIoTSpecProperty)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -99,10 +96,12 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
_status_map: Optional[dict[int, str]] _status_map: Optional[dict[int, str]]
_fan_level_map: Optional[dict[int, str]] _fan_level_map: Optional[dict[int, str]]
def __init__( _device_name: str
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
) -> None: def __init__(self, miot_device: MIoTDevice,
entity_data: MIoTEntityData) -> None:
super().__init__(miot_device=miot_device, entity_data=entity_data) super().__init__(miot_device=miot_device, entity_data=entity_data)
self._device_name = miot_device.name
self._attr_supported_features = VacuumEntityFeature(0) self._attr_supported_features = VacuumEntityFeature(0)
self._prop_status = None self._prop_status = None
@ -121,21 +120,21 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
for prop in entity_data.props: for prop in entity_data.props:
if prop.name == 'status': if prop.name == 'status':
if not prop.value_list: if not prop.value_list:
_LOGGER.error( _LOGGER.error('invalid status value_list, %s',
'invalid status value_list, %s', self.entity_id) self.entity_id)
continue continue
self._status_map = prop.value_list.to_map() self._status_map = prop.value_list.to_map()
self._attr_supported_features |= VacuumEntityFeature.STATE
self._prop_status = prop self._prop_status = prop
elif prop.name == 'fan-level': elif prop.name == 'fan-level':
if not prop.value_list: if not prop.value_list:
_LOGGER.error( _LOGGER.error('invalid fan-level value_list, %s',
'invalid fan-level value_list, %s', self.entity_id) self.entity_id)
continue continue
self._fan_level_map = prop.value_list.to_map() self._fan_level_map = prop.value_list.to_map()
self._attr_fan_speed_list = list(self._fan_level_map.values()) self._attr_fan_speed_list = list(self._fan_level_map.values())
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
self._prop_fan_level = prop self._prop_fan_level = prop
elif prop.name == 'battery-level': elif prop.name == 'battery-level':
self._attr_supported_features |= VacuumEntityFeature.BATTERY self._attr_supported_features |= VacuumEntityFeature.BATTERY
self._prop_battery_level = prop self._prop_battery_level = prop
@ -155,14 +154,22 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
elif action.name == 'stop-and-gocharge': elif action.name == 'stop-and-gocharge':
self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME self._attr_supported_features |= VacuumEntityFeature.RETURN_HOME
self._action_stop_and_gocharge = action self._action_stop_and_gocharge = action
elif action.name == 'identify': elif action.name == 'identify':
self._attr_supported_features |= VacuumEntityFeature.LOCATE self._attr_supported_features |= VacuumEntityFeature.LOCATE
self._action_identify = action self._action_identify = action
async def async_start(self) -> None: async def async_start(self) -> None:
"""Start or resume the cleaning task.""" """Start or resume the cleaning task."""
if self.state.lower() in ['paused', '暂停中']: try: # VacuumActivity is introduced in HA core 2025.1.0
# pylint: disable=import-outside-toplevel
from homeassistant.components.vacuum import VacuumActivity
if (self.activity
== VacuumActivity.PAUSED) and self._action_continue_sweep:
await self.action_async(action=self._action_continue_sweep)
return
except ImportError:
if self.state and (self.state in {'paused', 'pause'
}) and self._action_continue_sweep:
await self.action_async(action=self._action_continue_sweep) await self.action_async(action=self._action_continue_sweep)
return return
await self.action_async(action=self._action_start_sweep) await self.action_async(action=self._action_start_sweep)
@ -179,31 +186,92 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
"""Set the vacuum cleaner to return to the dock.""" """Set the vacuum cleaner to return to the dock."""
await self.action_async(action=self._action_stop_and_gocharge) await self.action_async(action=self._action_stop_and_gocharge)
async def async_clean_spot(self, **kwargs: Any) -> None:
"""Perform a spot clean-up."""
async def async_locate(self, **kwargs: Any) -> None: async def async_locate(self, **kwargs: Any) -> None:
"""Locate the vacuum cleaner.""" """Locate the vacuum cleaner."""
await self.action_async(action=self._action_identify) await self.action_async(action=self._action_identify)
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
"""Set fan speed.""" """Set fan speed."""
fan_level_value = self.get_map_key(map_=self._fan_level_map,
value=fan_speed)
await self.set_property_async(prop=self._prop_fan_level,
value=fan_level_value)
@property
def name(self) -> Optional[str]:
"""Name of the vacuum entity."""
return self._device_name
@property @property
def state(self) -> Optional[str]: def state(self) -> Optional[str]:
"""Return the current state of the vacuum cleaner.""" """Return the current state of the vacuum cleaner.
return self.get_map_value(
map_=self._status_map, To fix the HA warning below:
key=self.get_prop_value(prop=self._prop_status)) Detected that custom integration 'xiaomi_home' is setting state
directly.Entity XXX(<class 'custom_components.xiaomi_home.vacuum
.Vacuum'>)should implement the 'activity' property and return
its state using the VacuumActivity enum.This will stop working in
Home Assistant 2026.1.
Refer to
https://developers.home-assistant.io/blog/2024/12/08/new-vacuum-state-property
There are only 6 states in VacuumActivity enum. To be compatible with
more constants, try get matching VacuumActivity enum first, return state
string as before if there is no match. In Home Assistant 2026.1, every
state should map to a VacuumActivity enum.
"""
return self.activity
@property
def activity(self) -> Optional[str]:
"""The current vacuum activity."""
status = self.get_prop_value(prop=self._prop_status)
if status is None:
return None
status_value = self.get_map_value(map_=self._status_map, key=status)
if status_value is None:
return None
try:
# pylint: disable=import-outside-toplevel
from homeassistant.components.vacuum import VacuumActivity
status_value = status_value.lower()
status_str = re.sub(r'[^a-z]', '', status_value)
if status_str in {
'charging', 'charged', 'chargingcompleted', 'fullcharge',
'fullpower', 'findchargerpause', 'drying', 'washing',
'wash', 'inthewash', 'inthedry', 'stationworking',
'dustcollecting', 'upgrade', 'upgrading', 'updating'
}:
return VacuumActivity.DOCKED
if status_str in {'paused', 'pause'}:
return VacuumActivity.PAUSED
if status_str in {
'gocharging', 'cleancompletegocharging', 'findchargewash',
'backtowashmop', 'gowash', 'gowashing', 'summon'
}:
return VacuumActivity.RETURNING
if (status_str.find('sweeping')
!= -1) or (status_str.find('mopping')
!= -1) or (status_str in {
'cleaning', 'remoteclean', 'continuesweep',
'busy', 'building', 'buildingmap', 'mapping'
}):
return VacuumActivity.CLEANING
if status_str in {'error', 'breakcharging', 'gochargebreak'}:
return VacuumActivity.ERROR
return VacuumActivity.IDLE
except ImportError:
return status_value
@property @property
def battery_level(self) -> Optional[int]: def battery_level(self) -> Optional[int]:
"""Return the current battery level of the vacuum cleaner.""" """The current battery level of the vacuum cleaner."""
return self.get_prop_value(prop=self._prop_battery_level) return self.get_prop_value(prop=self._prop_battery_level)
@property @property
def fan_speed(self) -> Optional[str]: def fan_speed(self) -> Optional[str]:
"""Return the current fan speed of the vacuum cleaner.""" """The current fan speed of the vacuum cleaner."""
return self.get_map_value( return self.get_map_value(
map_=self._fan_level_map, map_=self._fan_level_map,
key=self.get_prop_value(prop=self._prop_fan_level)) key=self.get_prop_value(prop=self._prop_fan_level))

View File

@ -141,12 +141,11 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
continue continue
self._mode_map = prop.value_list.to_map() self._mode_map = prop.value_list.to_map()
self._attr_operation_list = list(self._mode_map.values()) self._attr_operation_list = list(self._mode_map.values())
self._attr_supported_features |= (
WaterHeaterEntityFeature.OPERATION_MODE)
self._prop_mode = prop self._prop_mode = prop
if not self._attr_operation_list: if not self._attr_operation_list:
self._attr_operation_list = [STATE_ON] self._attr_operation_list = [STATE_ON]
self._attr_operation_list.append(STATE_OFF) self._attr_operation_list.append(STATE_OFF)
self._attr_supported_features |= WaterHeaterEntityFeature.OPERATION_MODE
async def async_turn_on(self) -> None: async def async_turn_on(self) -> None:
"""Turn the water heater on.""" """Turn the water heater on."""
@ -197,5 +196,5 @@ class WaterHeater(MIoTServiceEntity, WaterHeaterEntity):
return STATE_OFF return STATE_OFF
if not self._prop_mode and self.get_prop_value(prop=self._prop_on): if not self._prop_mode and self.get_prop_value(prop=self._prop_on):
return STATE_ON return STATE_ON
return self.get_map_value(map_=self._mode_map, return (None if self._prop_mode is None else self.get_map_value(
key=self.get_prop_value(prop=self._prop_mode)) map_=self._mode_map, key=self.get_prop_value(prop=self._prop_mode)))

View File

@ -157,6 +157,8 @@ git checkout v1.0.0
转换后的实体为 Event事件参数同时传递给实体的 `_trigger_event` 转换后的实体为 Event事件参数同时传递给实体的 `_trigger_event`
MIoT-Spec-V2 事件的 arguments 字段是事件的参数列表,列表元素代表同服务下属性的 piid 。例如,小米智能无线开关(双开)的 [MIoT-Spec-V2](http://poc.miot-spec.srv/miot-spec-v2/instance?type=urn:miot-spec-v2:device:remote-control:0000A021:xiaomi-mcn002:1:0000D057)的 siid=2 无线开关服务下包含 eiid=1014 长按事件,该事件触发时会携带一个 piid=2 的按键类型属性作为事件参数, debug 等级日志会打印 `长按, attributes: {'按键类型': 1}` (日志示例,按键类型为 1 表示右键触发了长按事件)。
- 方法Action - 方法Action
| in输入参数列表 | 转换后的实体 | | in输入参数列表 | 转换后的实体 |
@ -291,39 +293,37 @@ event instance name 下的值表示转换后实体所用的 `_attr_device_class`
### MIoT-Spec-V2 过滤规则 ### MIoT-Spec-V2 过滤规则
`spec_filter.json` 用于过滤掉不需要的 MIoT-Spec-V2 实例,过滤掉的实例不会转换成 Home Assistant 实体。 `spec_filter.yaml` 用于过滤掉不需要的 MIoT-Spec-V2 实例,过滤掉的实例不会转换成 Home Assistant 实体。
`spec_filter.json`的格式如下: `spec_filter.yaml`的格式如下:
``` ```yaml
{ <MIoT-Spec-V2 device instance urn without the version field>:
"<MIoT-Spec-V2 device instance>":{ services: list<service_iid: str>
"services": list<service_iid: str>, properties: list<service_iid.property_iid: str>
"properties": list<service_iid.property_iid: str>, events: list<service_iid.event_iid: str>
"events": list<service_iid.event_iid: str>, actions: list<service_iid.action_iid: str>
"actions": list<service_iid.action_iid: str>,
}
}
``` ```
`spec_filter.json` 的键值为 MIoT-Spec-V2 设备实例的 urn 不含版本号“version”字段。一个产品的不同版本的固件可能会关联不同版本的 MIoT-Spec-V2 设备实例。 MIoT 平台要求厂商定义产品的 MIoT-Spec-V2 时,高版本的 MIoT-Spec-V2 实例必须包含全部低版本的 MIoT-Spec-V2 实例。因此, `spec_filter.json` 的键值不需要指定设备实例的版本号。 `spec_filter.yaml` 的键值为 MIoT-Spec-V2 设备实例的 urn 不含版本号“version”字段。一个产品的不同版本的固件可能会关联不同版本的 MIoT-Spec-V2 设备实例。 MIoT 平台要求厂商定义产品的 MIoT-Spec-V2 时,高版本的 MIoT-Spec-V2 实例必须包含全部低版本的 MIoT-Spec-V2 实例。因此, `spec_filter.yaml` 的键值不需要指定设备实例的版本号。
设备实例下的 services 、 properties 、 events 、 actions 域的值表示需要过滤掉的服务、属性、事件、方法的实例号( iid ,即 instance id )。支持通配符匹配。 设备实例下的 services 、 properties 、 events 、 actions 域的值表示需要过滤掉的服务、属性、事件、方法的实例号( iid ,即 instance id )。支持通配符匹配。
示例: 示例:
``` ```yaml
{ urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1:
"urn:miot-spec-v2:device:television:0000A010:xiaomi-rmi1":{ services:
"services": ["*"] # Filter out all services. It is equivalent to completely ignoring the device with such MIoT-Spec-V2 device instance. - '*' # 排除所有服务,相当于排除拥有该 MIoT-Spec-V2 的设备。
}, urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1:
"urn:miot-spec-v2:device:gateway:0000A019:xiaomi-hub1": { services:
"services": ["3"], # Filter out the service whose iid=3. - '3' # 排除 siid=3 的服务。
"properties": ["4.*"] # Filter out all properties in the service whose iid=4. properties:
"events": ["4.1"], # Filter out the iid=1 event in the iid=4 service. - '4.*' # 排除 siid=4 服务的所有属性。
"actions": ["4.1"] # Filter out the iid=1 action in the iid=4 service. events:
} - '4.1' # 排除 siid=4 服务的 eiid=1 的事件。
} actions:
- '4.1' # 排除 siid=4 服务的 aiid=1 的方法。
``` ```
所有设备的设备信息服务( urn:miot-spec-v2:service:device-information:00007801 )均不会生成 Home Assistant 实体。 所有设备的设备信息服务( urn:miot-spec-v2:service:device-information:00007801 )均不会生成 Home Assistant 实体。
@ -378,7 +378,7 @@ siid、piid、eiid、aiid、value 均为十进制三位整数。
} }
``` ```
> 在 Home Assistant 中修改了 `custom_components/xiaomi_home/miot/specs` 路径下的任何文件(`spec_filter.py`、`spec_modify.json`、`multi_lang.json`等),需要在集成配置中更新实体转换规则才能生效。方法:[设置 > 设备与服务 > 已配置 > Xiaomi Home](https://my.home-assistant.io/redirect/integration/?domain=xiaomi_home) > 配置 > 更新实体转换规则 > 在 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) > 配置 > 更新实体转换规则
## 文档 ## 文档