Skip to content

Zeo H1 (roborock.wm.a63): force-loading DP 225 (DEFAULT_SETTING) causes repeated 10s query timeouts #945

Description

@yangqian

Summary

The Zeo force-load DP list includes DP 225 (RoborockZeoProtocol.DEFAULT_SETTING) for every model that has a softener compartment. The H1 (roborock.wm.a63) has a softener compartment but never returns DP 225, so every force-load query times out after 10 s, repeatedly.

Environment

  • Home Assistant 2026.9.0, python-roborock 7.1.1 (the code path is unchanged on main / 7.2.3)
  • Device: Zeo H1, roborock.wm.a63

Root cause

build_force_load_dp_list() adds the softener block unconditionally for any model with a softener compartment — roborock/devices/traits/a01/device_feature.py:

    # ── Softener block (212, 214, 225, 227) ──
    if has_softener_compartment(model):
        base.extend(
            [
                RoborockZeoProtocol.SOFTENER_SET,  # 212
                RoborockZeoProtocol.SOFTENER_TYPE,  # 214
                RoborockZeoProtocol.DEFAULT_SETTING,  # 225
                RoborockZeoProtocol.SOFTENER_EMPTY,  # 227
            ]
        )

The a63 does have a softener compartment, so it is asked for DP 225 — but it never answers that DP.

The query completion check in send_decoded_command() is all-or-nothing (roborock/devices/rpc/a01_channel.py:87-89):

        if len(result) != len(query_values):
            _LOGGER.debug("Incomplete query response: %s != %s", result, query_values)
            return

Since DP 225 never arrives, result never reaches the expected length, finished is never set, and the wait hits _TIMEOUT = 10.0 — raising Command timed out after 10.0s and recording health_manager.on_timeout(), which restarts the MQTT session once enough consecutive timeouts accumulate. Every DP the device did answer is discarded along with it, so no Zeo state loads at all.

There is already a precedent for this kind of per-model gating: DP 237 (FEATURE_BITS) is stripped for a63/a90 via _UNSUPPORTED_FEATURE_BITS.

Fix

Gate DP 225 the same way FEATURE_BITS is gated. Tested on a real a63: the force-load completes and state loads correctly.

# Devices known to lack DEFAULT_SETTING (DP 225).
_UNSUPPORTED_DEFAULT_SETTING: frozenset[str] = frozenset(
    {
        "roborock.wm.a63",  # H1
    }
)


def supports_default_setting(model: str | None) -> bool:
    """H1 (a63) does not support DP 225 even though it has a softener compartment."""
    if model is None:
        return True  # conservative: assume yes
    return model not in _UNSUPPORTED_DEFAULT_SETTING

with the corresponding strip in build_force_load_dp_list():

    # ── Strip unsupported DEFAULT_SETTING ──
    if not supports_default_setting(model):
        base = [dp for dp in base if dp != RoborockZeoProtocol.DEFAULT_SETTING]

Open questions

  1. roborock.wm.a102 (H1 Overseas) is the same physical machine as the a63, so it likely has the same problem. I only have an a63 to test with — should a102 be added to the set pre-emptively?
  2. Should one unsupported DP be able to fail an entire query? Making the merge in send_decoded_command() tolerant of missing DPs (e.g. return whatever arrived after a settle window instead of requiring an exact length match) would make this whole class of per-model DP gaps degrade gracefully rather than taking the device offline.

Happy to open a PR with the fix and tests.

Activity

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

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