Version: v2.0.0b25 Device: Dreame X60 Ultra (Dreamehome cloud account, dreame_cloud protocol) HA: HAOS
Summary
On a freshly-added device that has no saved map yet, the first map frame is an empty (2×2) map. Handling that frame calls _init_data(), which nulls _capability and _aes_iv. Nothing ever restores them, so every subsequent map decode raises AttributeError and fails the whole coordinator update. The device never recovers until the integration is reloaded.
Traceback
WARNING (Thread-4208) [custom_components.dreame_vacuum.dreame.map] Map update Failed: Traceback (most recent call last):
File ".../dreame/map.py", line 1512, in update
File ".../dreame/map.py", line 452, in _request_current_map
File ".../dreame/map.py", line 350, in _request_i_map
File ".../dreame/map.py", line 681, in _add_map_data_file
File ".../dreame/map.py", line 684, in _add_raw_map_data
File ".../dreame/map.py", line 896, in _add_map_data
File ".../dreame/map.py", line 6658, in set_floor_material
File ".../dreame/map.py", line 6610, in set_segment_floor_material
ERROR (MainThread) [custom_components.dreame_vacuum] Error requesting dreame_vacuum data:
'NoneType' object has no attribute 'floor_direction_cleaning'
Root cause
_init_data() is the map-state reset, but it also clears three device-level fields:
map.py:174
def _init_data(self) -> None:
...
self._aes_iv: str = None # 201
self._aes_key: str = None # 202
self._capability: DreameVacuumDeviceCapability = None # 203
_capability and _aes_iv are assigned only in set_capability():
map.py:1530
def set_capability(self, capability) -> None:
self._capability = capability
self._aes_iv = capability.key
…which is called only from device.py:498 (construction) and device.py:641 (guarded by if not self.capability.loaded:, so effectively once). Neither runs again during the device's lifetime.
_add_map_data calls _init_data() when it sees the first empty map:
map.py:814
if map_data.empty_map:
if self._map_data is None or not self._map_data.empty_map:
self._init_data() # <-- wipes _capability and _aes_iv
On a new device with no saved map, map_data.empty_map is True (2×2, per map.py:4464) and self._map_data is None on the first frame after startup, so this always fires. Once the robot builds a real map, the next non-empty I-frame reaches:
map.py:896
DreameVacuumMapDecoder.set_floor_material(map_data, self._capability, saved_map_data) # None
and set_segment_floor_material dereferences it unguarded:
map.py:6610
if capability.floor_direction_cleaning and material == 1:
This is the only unguarded capability access in that function — line 6604 uses capability is not None and ... and line 6639 uses elif capability and capability.floor_material:.
Impact
The AttributeError propagates to update(), is caught at map.py:1520, sets _available = False and fires _error_callback(DeviceUpdateFailedException(ex)). That fails the entire coordinator refresh, so all entities for the device go unavailable — not just map entities — even while the robot is actively cleaning. The camera serves the "no map" placeholder, calibration_points stay as all-zero stubs, and no rooms attribute is exposed, so xiaomi-vacuum-map-card room selection cannot work.
Because nothing re-calls set_capability(), this state is permanent until reload.
Note _aes_iv is also lost, which affects map decryption (used at lines 614, 1312, 1363, 1668, 2713). _aes_key is fine to reset — get_aes_key() re-derives it lazily via the if self._aes_key is None check at line 1179.
Suggested fix
Move the two device-level fields out of the map-state reset:
def __init__(self, _protocol: DreameVacuumProtocol) -> None:
...
self._connected: bool = True
+
+ self._aes_iv: str = None
+ self._capability: DreameVacuumDeviceCapability = None
self._init_data()
def _init_data(self) -> None:
...
- self._aes_iv: str = None
self._aes_key: str = None
- self._capability: DreameVacuumDeviceCapability = None
Guarding line 6610 (if capability is not None and capability.floor_direction_cleaning ...) would additionally match the defensive style used at 6604/6639, but it treats the symptom — _aes_iv would still be silently lost.
Reproduce
Add a device that has no saved map (factory reset / first setup).
Let it send an empty map, then start a mapping run.
Every map update fails from the first non-empty I-frame onward; all entities go unavailable.
Version: v2.0.0b25 Device: Dreame X60 Ultra (Dreamehome cloud account, dreame_cloud protocol) HA: HAOS
Summary
On a freshly-added device that has no saved map yet, the first map frame is an empty (2×2) map. Handling that frame calls _init_data(), which nulls _capability and _aes_iv. Nothing ever restores them, so every subsequent map decode raises AttributeError and fails the whole coordinator update. The device never recovers until the integration is reloaded.
Traceback
Root cause
_init_data() is the map-state reset, but it also clears three device-level fields:
map.py:174
_capability and _aes_iv are assigned only in set_capability():
map.py:1530
…which is called only from device.py:498 (construction) and device.py:641 (guarded by if not self.capability.loaded:, so effectively once). Neither runs again during the device's lifetime.
_add_map_data calls _init_data() when it sees the first empty map:
map.py:814
On a new device with no saved map, map_data.empty_map is True (2×2, per map.py:4464) and self._map_data is None on the first frame after startup, so this always fires. Once the robot builds a real map, the next non-empty I-frame reaches:
map.py:896
and set_segment_floor_material dereferences it unguarded:
map.py:6610
This is the only unguarded capability access in that function — line 6604 uses capability is not None and ... and line 6639 uses elif capability and capability.floor_material:.
Impact
The AttributeError propagates to update(), is caught at map.py:1520, sets _available = False and fires _error_callback(DeviceUpdateFailedException(ex)). That fails the entire coordinator refresh, so all entities for the device go unavailable — not just map entities — even while the robot is actively cleaning. The camera serves the "no map" placeholder, calibration_points stay as all-zero stubs, and no rooms attribute is exposed, so xiaomi-vacuum-map-card room selection cannot work.
Because nothing re-calls set_capability(), this state is permanent until reload.
Note _aes_iv is also lost, which affects map decryption (used at lines 614, 1312, 1363, 1668, 2713). _aes_key is fine to reset — get_aes_key() re-derives it lazily via the if self._aes_key is None check at line 1179.
Suggested fix
Move the two device-level fields out of the map-state reset:
Guarding line 6610 (if capability is not None and capability.floor_direction_cleaning ...) would additionally match the defensive style used at 6604/6639, but it treats the symptom — _aes_iv would still be silently lost.
Reproduce
Add a device that has no saved map (factory reset / first setup).
Let it send an empty map, then start a mapping run.
Every map update fails from the first non-empty I-frame onward; all entities go unavailable.