8 Commits
v0.4.1 ... main

Author SHA1 Message Date
ec833b6539 feat: subscribe the proxy gateway child device up messages even though the device is offline (#1393)
Some checks failed
Tests / check-rule-format (push) Failing after 2s
Validate / validate-hassfest (push) Failing after 3s
Validate / validate-hacs (push) Failing after 10s
Validate / validate-lint (push) Failing after 2s
Validate / validate-setup (push) Failing after 5s
* feat: subscribe the proxy gateway child device up messages even though the device is offline (#1313)

* feat: do not subscribe proxy gateway child device online/offline state message
2025-09-02 17:22:40 +08:00
f2200ba003 fix: contact-state value format (#1387)
Some checks failed
Tests / check-rule-format (push) Failing after 4s
Validate / validate-hassfest (push) Failing after 3s
Validate / validate-hacs (push) Failing after 9s
Validate / validate-lint (push) Failing after 2s
Validate / validate-setup (push) Failing after 3s
2025-08-29 17:36:25 +08:00
073cdf2dcb fix: integer value step (#1388) 2025-08-29 17:35:46 +08:00
0f65635342 docs: update changelog and version to v0.4.2 (#1368)
Some checks failed
Tests / check-rule-format (push) Failing after 4s
Validate / validate-hassfest (push) Failing after 3s
Validate / validate-hacs (push) Failing after 10s
Validate / validate-lint (push) Failing after 2s
Validate / validate-setup (push) Failing after 4s
2025-08-20 09:15:31 +08:00
947169f18d Fix specs (#1367)
Some checks failed
Tests / check-rule-format (push) Failing after 5s
Validate / validate-hassfest (push) Failing after 3s
Validate / validate-hacs (push) Failing after 9s
Validate / validate-lint (push) Failing after 3s
Validate / validate-setup (push) Failing after 5s
* fix: xiaomi.fan.p70 fan level

* fix: xiaomi.fan.p76 fan level

* fix: ignore hmpace.motion.v6nfc

* fix: xiaomi.airc.rr0r00 humidity-range

* fix: xiaomi.airc.h43h00 humidity-range

* fix: zhimi.humidifier.ca4 water level
2025-08-19 17:49:27 +08:00
65a7a6d22a fix: correct the property value format after expression calculation (#1366) 2025-08-19 14:36:30 +08:00
c29f7eecbd fix: delete all unsupported MIoT-Spec-V2 instances of Narwa vacuum (#1355) 2025-08-19 14:12:35 +08:00
58c671483e fix: add RETURN_HOME functionality for new vacuum cleaner model (#1344)
Some checks failed
Tests / check-rule-format (push) Failing after 4s
Validate / validate-hassfest (push) Failing after 3s
Validate / validate-hacs (push) Failing after 9s
Validate / validate-lint (push) Failing after 2s
Validate / validate-setup (push) Failing after 4s
2025-08-19 10:15:01 +08:00
13 changed files with 1496 additions and 12 deletions

View File

@ -1,4 +1,13 @@
# CHANGELOG # CHANGELOG
## v0.4.2
### Changed
- Set the battery service's start-charge action as the fallback action to support RETURN_HOME feature of the vacuum entity. [#1344](https://github.com/XiaoMi/ha_xiaomi_home/pull/1344)
### Fixed
- Correct the property value format after expression calculation. [#1366](https://github.com/XiaoMi/ha_xiaomi_home/pull/1366)
- Fix the MIoT-Spec-V2 of fix: xiaomi.fan.p70 and fix: xiaomi.fan.p76 fan level, xiaomi.airc.rr0r00 and xiaomi.airc.h43h00 humidity-range, and zhimi.humidifier.ca4 water level. [#1367](https://github.com/XiaoMi/ha_xiaomi_home/pull/1367)
- Ignore the unsupported model hmpace.motion.v6nfc.
- Delete all unsupported MIoT-Spec-V2 instances of narwa.vacuum.001 and narwa.vacuum.ax11. [#1355](https://github.com/XiaoMi/ha_xiaomi_home/pull/1355)
## v0.4.1 ## v0.4.1
### Changed ### Changed
- The setting option "Cover closed position" in CONFIGURE is changed to "Cover dead zone width". [#1301](https://github.com/XiaoMi/ha_xiaomi_home/pull/1301) - The setting option "Cover closed position" in CONFIGURE is changed to "Cover dead zone width". [#1301](https://github.com/XiaoMi/ha_xiaomi_home/pull/1301)

View File

@ -70,8 +70,8 @@ async def async_setup_entry(
for miot_device in device_list: for miot_device in device_list:
if miot_device.miot_client.display_binary_bool: if miot_device.miot_client.display_binary_bool:
for prop in miot_device.prop_list.get('binary_sensor', []): for prop in miot_device.prop_list.get('binary_sensor', []):
new_entities.append(BinarySensor( new_entities.append(
miot_device=miot_device, spec=prop)) BinarySensor(miot_device=miot_device, spec=prop))
if new_entities: if new_entities:
async_add_entities(new_entities) async_add_entities(new_entities)
@ -90,7 +90,7 @@ class BinarySensor(MIoTPropertyEntity, BinarySensorEntity):
def is_on(self) -> bool: def is_on(self) -> bool:
"""On/Off state. True if the binary sensor is on, False otherwise.""" """On/Off state. True if the binary sensor is on, False otherwise."""
if self.spec.name == 'contact-state': if self.spec.name == 'contact-state':
return self._value is False return bool(self._value) is False
elif self.spec.name == 'occupancy-status': elif self.spec.name == 'occupancy-status':
return bool(self._value) return bool(self._value)
return self._value is True return self._value is True

View File

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

View File

@ -89,6 +89,7 @@ SUPPORTED_PLATFORMS: list = [
UNSUPPORTED_MODELS: list = [ UNSUPPORTED_MODELS: list = [
'chuangmi.ir.v2', 'chuangmi.ir.v2',
'hmpace.motion.v6nfc',
'xiaomi.router.rd03' 'xiaomi.router.rd03'
] ]

View File

@ -1374,10 +1374,13 @@ 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 # MIoT cloud may not publish the online state updating message
# for the BLE device. Assume that all BLE devices are online. # for the BLE device. Assume that all BLE devices are online.
# MIoT cloud does not publish the online state updating message for the
# child device under the proxy gateway (eg, VRF air conditioner
# controller). Assume that all proxy gateway child devices are online.
for did, info in cloud_list.items(): for did, info in cloud_list.items():
if did.startswith('blt.'): if did.startswith('blt.') or did.startswith('proxy.'):
info['online'] = True 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:

View File

@ -1108,6 +1108,8 @@ class MIoTServiceEntity(Entity):
): ):
continue continue
value: Any = prop.value_format(params['value']) value: Any = prop.value_format(params['value'])
value = prop.eval_expr(value)
value = prop.value_format(value)
self._prop_value_map[prop] = value self._prop_value_map[prop] = value
if prop in self._prop_changed_subs: if prop in self._prop_changed_subs:
self._prop_changed_subs[prop](prop, value) self._prop_changed_subs[prop](prop, value)
@ -1279,8 +1281,9 @@ class MIoTPropertyEntity(Entity):
def __on_value_changed(self, params: dict, ctx: Any) -> None: def __on_value_changed(self, params: dict, ctx: Any) -> None:
_LOGGER.debug('property changed, %s', params) _LOGGER.debug('property changed, %s', params)
self._value = self.spec.value_format(params['value']) value: Any = self.spec.value_format(params['value'])
self._value = self.spec.eval_expr(self._value) value = self.spec.eval_expr(value)
self._value = self.spec.value_format(value)
if not self._pending_write_ha_state_timer: if not self._pending_write_ha_state_timer:
self.async_write_ha_state() self.async_write_ha_state()

View File

@ -513,6 +513,7 @@ class _MipsClient(ABC):
""" """
self.__thread_check() self.__thread_check()
if not self._mqtt or not self._mqtt.is_connected(): if not self._mqtt or not self._mqtt.is_connected():
self.log_error(f'mips sub when not connected, {topic}')
return return
try: try:
if topic not in self._mips_sub_pending_map: if topic not in self._mips_sub_pending_map:
@ -531,6 +532,7 @@ class _MipsClient(ABC):
""" """
self.__thread_check() self.__thread_check()
if not self._mqtt or not self._mqtt.is_connected(): if not self._mqtt or not self._mqtt.is_connected():
self.log_debug(f'mips unsub when not connected, {topic}')
return return
try: try:
result, mid = self._mqtt.unsubscribe(topic=topic) result, mid = self._mqtt.unsubscribe(topic=topic)
@ -639,6 +641,7 @@ class _MipsClient(ABC):
_LOGGER.error('__on_connect, but mqtt is None') _LOGGER.error('__on_connect, but mqtt is None')
return return
if not self._mqtt.is_connected(): if not self._mqtt.is_connected():
_LOGGER.error('__on_connect, but mqtt is disconnected')
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.__reset_reconnect_time()
@ -995,9 +998,11 @@ class MipsCloudClient(_MipsClient):
did, MIoTDeviceState.ONLINE if msg['event'] == 'online' did, MIoTDeviceState.ONLINE if msg['event'] == 'online'
else MIoTDeviceState.OFFLINE, ctx) else MIoTDeviceState.OFFLINE, ctx)
if did.startswith('blt.'): if did.startswith('blt.') or did.startswith('proxy.'):
# MIoT cloud may not publish BLE device online/offline state message. # MIoT cloud may not publish BLE device or proxy gateway child device
# Do not subscribe BLE device online/offline state. # online/offline state message.
# Do not subscribe BLE device or proxy gateway child device
# online/offline state.
return True return True
return self.__reg_broadcast_external( return self.__reg_broadcast_external(
topic=topic, handler=on_state_msg, handler_ctx=handler_ctx) topic=topic, handler=on_state_msg, handler_ctx=handler_ctx)

View File

@ -601,7 +601,10 @@ class MIoTSpecProperty(_MIoTSpecBase):
if value is None: if value is None:
return None return None
if self.format_ == int: if self.format_ == int:
if self.value_range is None:
return int(round(value)) return int(round(value))
return int(
round(value / self.value_range.step) * self.value_range.step)
if self.format_ == float: if self.format_ == float:
return round(value, self.precision) return round(value, self.precision)
if self.format_ == bool: if self.format_ == bool:

File diff suppressed because it is too large Load Diff

View File

@ -48,3 +48,9 @@ urn:miot-spec-v2:device:thermostat:0000A031:tofan-wk01:
services: services:
- '2' - '2'
- '4' - '4'
urn:miot-spec-v2:device:vacuum:0000A006:narwa-001:
services:
- '*'
urn:miot-spec-v2:device:vacuum:0000A006:narwa-ax11:
services:
- '*'

View File

@ -22,6 +22,10 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-c35:2: urn:miot-spec-v2:
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h40h00:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h40h00:1:
prop.10.6: prop.10.6:
unit: none unit: none
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h43h00:1:
prop.10.6:
unit: none
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h43h00:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-h43h00:1
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:1: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-m16:1:
prop.10.6: prop.10.6:
unit: none unit: none
@ -41,6 +45,12 @@ urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1:
prop.10.6: prop.10.6:
unit: none 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-conditioner:0000A004:xiaomi-mt0:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-mt0:1
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1:
prop.10.6:
unit: none
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:2: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:3: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:4: urn:miot-spec-v2:device:air-conditioner:0000A004:xiaomi-rr0r00:1
urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-cgd1st:1: urn:miot-spec-v2:device:air-monitor:0000A008:cgllc-cgd1st:1:
prop.3.7: prop.3.7:
value-range: value-range:
@ -130,6 +140,12 @@ urn:miot-spec-v2:device:fan:0000A005:xiaomi-p51:1:
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p69:1:0000D062: urn:miot-spec-v2:device:fan:0000A005:xiaomi-p69:1:0000D062:
prop.2.4: prop.2.4:
name: fan-level-a name: fan-level-a
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p70:1:0000D062:
prop.2.4:
name: fan-level-a
urn:miot-spec-v2:device:fan:0000A005:xiaomi-p76:1:0000D062:
prop.2.4:
name: fan-level-a
urn:miot-spec-v2:device:fan:0000A005:zhimi-sa1:3: urn:miot-spec-v2:device:fan:0000A005:zhimi-sa1:3:
prop.2.2: prop.2.2:
name: fan-level-a name: fan-level-a
@ -175,6 +191,10 @@ urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:2: urn:miot-spec-v2:device:hood
urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3: urn:miot-spec-v2:device:hood:0000A01B:cykj-jyj22:3:
prop.3.1: prop.3.1:
name: on-ventilation name: on-ventilation
urn:miot-spec-v2:device:humidifier:0000A00E:zhimi-ca4:2:
prop.2.7:
unit: percentage
expr: round(src_value*0.83)
urn:miot-spec-v2:device:kettle:0000A009:yunmi-r3:1: urn:miot-spec-v2:device:kettle:0000A009:yunmi-r3:1:
prop.3.1: prop.3.1:
unit: ppm unit: ppm

View File

@ -170,6 +170,11 @@ SPEC_DEVICE_TRANS_MAP: dict = {
'properties': { 'properties': {
'battery-level': {'read'} 'battery-level': {'read'}
} }
},
'optional': {
'actions': {
'start-charge'
}
} }
} }
}, },

View File

@ -203,6 +203,16 @@ class Vacuum(MIoTServiceEntity, StateVacuumEntity):
self._attr_supported_features |= VacuumEntityFeature.LOCATE self._attr_supported_features |= VacuumEntityFeature.LOCATE
self._action_identify = action self._action_identify = action
# Use start-charge from battery service as fallback
# if stop-and-gocharge is not available
if self._action_stop_and_gocharge is None:
for action in entity_data.actions:
if action.name == 'start-charge':
self._attr_supported_features |= (
VacuumEntityFeature.RETURN_HOME)
self._action_stop_and_gocharge = action
break
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._prop_status is not None: if self._prop_status is not None: