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
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?
- 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.
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
main/ 7.2.3)roborock.wm.a63Root 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: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):Since DP 225 never arrives,
resultnever reaches the expected length,finishedis never set, and the wait hits_TIMEOUT = 10.0— raisingCommand timed out after 10.0sand recordinghealth_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.
with the corresponding strip in
build_force_load_dp_list():Open questions
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?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.