Skip to content

Fan entity stuck in 'unknown' (KeyError) when device has both gear (value-list) and stepless (value-range) fan-level props (xiaomi.fan.p76, xiaomi.fan.p70) #1728

Description

@Simonvoss97

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:

  1. 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>
  2. 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

  1. 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.
  2. From Mi Home, set the fan speed to any stepless value outside the gear value-list (e.g. 25 / 35).
  3. In Home Assistant, observe fan.<...>_p76_s_2_fan is stuck in unknown.
  4. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions