Skip to content

Setup fails with TypeError: argument of type 'NoneType' is not a container or iterable when legacy map_objects option is None #1787

Description

@8cH9azbsFifZ

Setup fails with TypeError: argument of type 'NoneType' is not a container or iterable when legacy map_objects option is None

Environment

Integration version v2.0.0b25 (also affects b20–b24)
Home Assistant Core 2026.9.0
Installation type Home Assistant OS
Device model dreame.vacuum.r2211o, firmware 4.3.9_1406
Connection type local only (no Mi cloud account on the entry)

Description

Two of my four config entries fail to set up with setup_error. Both are the ones configured without a cloud account. The two cloud-configured entries (same integration, one of them the exact same model and firmware) load fine.

The failure happens in the map-objects migration in coordinator.py.

Traceback

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 803, in __async_setup_with_context
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/dreame_vacuum/__init__.py", line 39, in async_setup_entry
    coordinator = DreameVacuumDataUpdateCoordinator(hass, entry=entry)
  File "/config/custom_components/dreame_vacuum/coordinator.py", line 143, in __init__
    if key not in options[CONF_MAP_OBJECTS] and key != "curtain" and key != "ramp":
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: argument of type 'NoneType' is not a container or iterable

Root cause

coordinator.py (around line 140) guards the migration with a key-existence check instead of a value check:

## Migration: Convert map objects to hidden map objects
if CONF_MAP_OBJECTS in entry.options and CONF_HIDDEN_MAP_OBJECTS not in options:
    options[CONF_HIDDEN_MAP_OBJECTS] = []
    for key in list(MAP_OBJECTS.keys()):
        if key not in options[CONF_MAP_OBJECTS] and key != "curtain" and key != "ramp":
            options[CONF_HIDDEN_MAP_OBJECTS].append(key)
    del options[CONF_MAP_OBJECTS]

On affected entries the option is present but its value is None:

"options": { "map_objects": null, ... }

so key not in None raises.

Where the None comes from

This is self-inflicted by the config flow. In async_step_options the options dict is built with unconditional .get() calls:

self.options = {
    CONF_NOTIFY: user_input[CONF_NOTIFY],
    CONF_COLOR_SCHEME: user_input.get(CONF_COLOR_SCHEME),
    CONF_ICON_SET: user_input.get(CONF_ICON_SET),
    CONF_HIDDEN_MAP_OBJECTS: user_input.get(CONF_HIDDEN_MAP_OBJECTS),
    CONF_SQUARE: user_input.get(CONF_SQUARE),
    CONF_LOW_RESOLUTION: user_input.get(CONF_LOW_RESOLUTION),
    CONF_PREFER_CLOUD: self.prefer_cloud,
}

but the corresponding schema fields are only added for non-local accounts:

if self.account_type != ACCOUNT_TYPE_LOCAL:
    data_schema = data_schema.extend({ ... CONF_HIDDEN_MAP_OBJECTS ... })

For a local-only setup those keys are never in user_input, so .get() stores None. Older versions did the same with CONF_MAP_OBJECTS, which is exactly the value that now breaks the migration.

This still happens today with CONF_HIDDEN_MAP_OBJECTS. A freshly created local entry gets "hidden_map_objects": null. It does not crash right now, because the config flow also writes CONF_VERSION, which makes the migration block unreachable. But it is passed straight into the renderer in camera.py:

hidden_map_objects = entry.options.get(CONF_HIDDEN_MAP_OBJECTS, [])   # returns None, not []
...
objects = hidden_map_objects
self._renderer = DreameVacuumMapRenderer(color_scheme, icon_set, objects, ...)

The .get(..., []) default does not help when the key exists with value None.

Steps to reproduce

  1. Add a device using a local connection (host + token, no cloud account) with a version that still wrote map_objects.
  2. Update the integration to b20 or later.
  3. Restart Home Assistant.
  4. The entry fails with the traceback above; all its entities become unavailable.

Suggested fix

Check the value, not just the key, in coordinator.py:

-if CONF_MAP_OBJECTS in entry.options and CONF_HIDDEN_MAP_OBJECTS not in options:
+if entry.options.get(CONF_MAP_OBJECTS) and CONF_HIDDEN_MAP_OBJECTS not in options:

I applied this locally and both entries set up correctly again. Since the block also sets CONF_VERSION, the migration is then marked as done and the patch is only needed once.

To fix the underlying cause as well, the config flow should not persist None for fields it never presented, e.g.:

CONF_HIDDEN_MAP_OBJECTS: user_input.get(CONF_HIDDEN_MAP_OBJECTS) or [],

and camera.py would be more robust as:

hidden_map_objects = entry.options.get(CONF_HIDDEN_MAP_OBJECTS) or []

Workaround for other users

Users hitting this can either apply the one-line patch above, or delete and re-create the affected config entries (a newly created entry sets CONF_VERSION, so the migration is skipped entirely).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingquestionFurther information is requested

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions