Describe the Bug
When a fan device exposes two fan-level properties on the same service - one with value-list and one with value-range - the Fan entity in fan.py ends up with inconsistent internal state and the entity stays in unknown whenever the device reports a stepless level value that is not contained in the value-list map.
Affected device model: xiaomi.fan.p76 (URN: urn:miot-spec-v2:device:fan:0000A005:xiaomi-p76:1:0000D062).
Service 2 (fan) on this device has these properties:
iid=4 type=...:property:fan-level:00000016:xiaomi-p76:1 value-list = [0=Level1, 1=Level2, 2=Level3, 3=Level4]
iid=5 type=...:property:fan-level:00000016:xiaomi-p76:1 value-range = [1, 100, 1]
Both properties carry the same MiOT type name fan-level, so both match prop.name == 'fan-level' in fan.py.
In Fan.__init__, properties are processed in spec order:
-
iid=4 (value-list) is processed first - the elif prop.value_list branch sets:
_speed_name_map = {0:'Level1', 1:'Level2', 2:'Level3', 3:'Level4'}
_speed_names = ['Level1', 'Level2', 'Level3', 'Level4']
_prop_fan_level = <iid=4>
-
iid=5 (value-range) is processed next - the if prop.value_range branch sets:
_speed_min/_speed_max/_speed_step for [1, 100, 1]
_prop_fan_level = <iid=5> (overrides the previous binding, as the in-code comment says: "Fan level with value-range is prior to fan level with value-list")
- but does not clear
_speed_names / _speed_name_map
Result: _prop_fan_level points to the stepless prop, but _speed_name_map still holds the gear value-list.
In the percentage property, the guard if self._speed_names and self._speed_name_map: is still truthy, so it takes the value-list branch and crashes:
# fan.py - percentage()
if self._speed_names and self._speed_name_map:
return ordered_list_item_to_percentage(
self._speed_names, self._speed_name_map[fan_level]) # KeyError
The device reports a stepless fan-level of 25, which is not a key in {0,1,2,3}, so _speed_name_map[25] raises KeyError: 25. The exception happens inside async_write_ha_state -> state_attributes, so the entity never publishes a state and stays at unknown. Other entities on the same device (selects, switches, sensors, light) work correctly - this is specific to the Fan platform.
The same crash also reproduces for xiaomi.fan.p70 with value 35, since p70 has the same dual fan-level setup.
How to Reproduce
- Add a device whose MiOT spec exposes two
fan-level properties on the fan service - one with value-list, one with value-range. Examples: xiaomi.fan.p76, xiaomi.fan.p70.
- From Mi Home, set the fan speed to any stepless value outside the gear value-list (e.g. 25 / 35).
- In Home Assistant, observe
fan.<...>_p76_s_2_fan is stuck in unknown.
- Try
fan.turn_on or fan.turn_off from HA - the property is written to the cloud successfully, but the state update throws.
Expected Behavior
The fan entity should reflect the device state correctly using the stepless (value-range) fan-level, since the in-code comment already documents that value-range takes precedence. The leftover value-list state from the gear prop should not be used.
Reproduce Time
2025-10-31 16:44:21 and many subsequent occurrences.
Home Assistant Logs
Traceback (most recent call last):
...
File "/config/custom_components/xiaomi_home/fan.py", line 230, in async_turn_off
await self.set_property_async(prop=self._prop_on, value=False)
File "/config/custom_components/xiaomi_home/miot/miot_device.py", line 1060, in set_property_async
self.async_write_ha_state()
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1026, in async_write_ha_state
self._async_write_ha_state()
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1151, in _async_write_ha_state
self.__async_calculate_state()
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 1090, in __async_calculate_state
if state_attributes := self.state_attributes:
File "/usr/src/homeassistant/homeassistant/components/fan/__init__.py", line 402, in state_attributes
data[ATTR_PERCENTAGE] = self.percentage
File "/config/custom_components/xiaomi_home/fan.py", line 314, in percentage
self._speed_names, self._speed_name_map[fan_level])
KeyError: 25
2025-10-31 16:44:22.458 ERROR (MainThread) [custom_components.xiaomi_home.miot.miot_client]
on prop msg error, {'did': '929691876', 'siid': 2, 'piid': 1, 'value': False}, 25
The same trace also occurs for xiaomi.fan.p70 with value 35.
Log Timezone
Europe/Copenhagen
Home Assistant Core Version
2026.6.1
Home Assistant Operation System Version
Home Assistant Container (not HAOS)
Xiaomi Home Integration Version
v0.4.7
Additional Context
Proposed fix in PR (linked below): clear _speed_names / _speed_name_map in the value-range branch of Fan.__init__, so the percentage property does not pick the value-list branch with a stale map. Verified locally on xiaomi.fan.p76 and xiaomi.fan.p70 - after restart, both entities reflect state correctly (on, percentage from the stepless prop, preset mode).
Describe the Bug
When a fan device exposes two
fan-levelproperties on the same service - one withvalue-listand one withvalue-range- theFanentity infan.pyends up with inconsistent internal state and the entity stays inunknownwhenever the device reports a stepless level value that is not contained in the value-list map.Affected device model:
xiaomi.fan.p76(URN:urn:miot-spec-v2:device:fan:0000A005:xiaomi-p76:1:0000D062).Service 2 (
fan) on this device has these properties:Both properties carry the same MiOT type name
fan-level, so both matchprop.name == 'fan-level'infan.py.In
Fan.__init__, properties are processed in spec order:iid=4(value-list) is processed first - theelif prop.value_listbranch sets:_speed_name_map = {0:'Level1', 1:'Level2', 2:'Level3', 3:'Level4'}_speed_names = ['Level1', 'Level2', 'Level3', 'Level4']_prop_fan_level = <iid=4>iid=5(value-range) is processed next - theif prop.value_rangebranch sets:_speed_min/_speed_max/_speed_stepfor[1, 100, 1]_prop_fan_level = <iid=5>(overrides the previous binding, as the in-code comment says: "Fan level with value-range is prior to fan level with value-list")_speed_names/_speed_name_mapResult:
_prop_fan_levelpoints to the stepless prop, but_speed_name_mapstill holds the gear value-list.In the
percentageproperty, the guardif self._speed_names and self._speed_name_map:is still truthy, so it takes the value-list branch and crashes:The device reports a stepless
fan-levelof25, which is not a key in{0,1,2,3}, so_speed_name_map[25]raisesKeyError: 25. The exception happens insideasync_write_ha_state->state_attributes, so the entity never publishes a state and stays atunknown. Other entities on the same device (selects, switches, sensors, light) work correctly - this is specific to theFanplatform.The same crash also reproduces for
xiaomi.fan.p70with value35, since p70 has the same dualfan-levelsetup.How to Reproduce
fan-levelproperties on the fan service - one withvalue-list, one withvalue-range. Examples:xiaomi.fan.p76,xiaomi.fan.p70.fan.<...>_p76_s_2_fanis stuck inunknown.fan.turn_onorfan.turn_offfrom HA - the property is written to the cloud successfully, but the state update throws.Expected Behavior
The fan entity should reflect the device state correctly using the stepless (value-range) fan-level, since the in-code comment already documents that value-range takes precedence. The leftover value-list state from the gear prop should not be used.
Reproduce Time
2025-10-31 16:44:21 and many subsequent occurrences.
Home Assistant Logs
The same trace also occurs for
xiaomi.fan.p70with value35.Log Timezone
Europe/Copenhagen
Home Assistant Core Version
2026.6.1
Home Assistant Operation System Version
Home Assistant Container (not HAOS)
Xiaomi Home Integration Version
v0.4.7
Additional Context
Proposed fix in PR (linked below): clear
_speed_names/_speed_name_mapin the value-range branch ofFan.__init__, so thepercentageproperty does not pick the value-list branch with a stale map. Verified locally onxiaomi.fan.p76andxiaomi.fan.p70- after restart, both entities reflect state correctly (on, percentage from the stepless prop, preset mode).