Skip to content

feat: add MQTT QoS support and timestamp to A01 protocol payload - #891

Merged
allenporter merged 1 commit into
Python-roborock:mainfrom
NOisi-x:pr/zeo-protocol-fixes
Jul 21, 2026
Merged

feat: add MQTT QoS support and timestamp to A01 protocol payload#891
allenporter merged 1 commit into
Python-roborock:mainfrom
NOisi-x:pr/zeo-protocol-fixes

Conversation

@NOisi-x

@NOisi-x NOisi-x commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Add MQTT QoS support pipeline and a Unix timestamp field to the A01 protocol — the two minimal, backward-compatible prerequisites needed for Zeo washer START commands to work over MQTT.

Motivation

The device firmware requires three conditions for a START command to be accepted:

  1. QoS 1 — DP200 must be published at AT_LEAST_ONCE; the default AT_MOST_ONCE (QoS 0) is silently rejected by the broker.
  2. Timestamp — A01 command payloads must include a "t" field with a Unix timestamp.
  3. Parameter bundle — MODE, PROGRAM, and other wash parameters must be sent together with START in a single MQTT message.

This PR addresses (1) and (2) — the protocol-level infrastructure. (3) will follow in a separate PR built on top of this one.

These findings build upon the excellent reverse engineering work done in ndwzy/roborock_washer_ha — many thanks for the foundational research.

Changes

New MqttQos enum (roborock/mqtt/session.py)

class MqttQos(IntEnum):
    AT_MOST_ONCE = 0   # Fire-and-forget. No acknowledgment required.
    AT_LEAST_ONCE = 1  # Guaranteed delivery with possible duplicates. Broker sends PUBACK.
    EXACTLY_ONCE = 2   # Guaranteed delivery with no duplicates.

Thread qos through the publish chain

File Change
roborock/mqtt/session.py Abstract MqttSession.publish() gains qos parameter
roborock/mqtt/roborock_session.py RoborockMqttSession.publish() & LazyMqttSession.publish() pass qos through
roborock/devices/transport/mqtt_channel.py MqttChannel.publish() passes qos through
roborock/devices/rpc/a01_channel.py send_decoded_command() gains qos parameter, passes it on non-query publishes
roborock/testing/channel.py FakeChannel._publish() accepts qos for compatibility (not simulated)

Add timestamp to A01 payload (roborock/protocols/a01_protocol.py)

encode_mqtt_payload() now includes "t": int(time.time()) in every payload.

Tests & fixtures

  • tests/devices/traits/a01/test_init.py — 4 assertions updated to tolerate "t" field
  • tests/e2e/__snapshots__/test_device_manager.ambr — snapshot regenerated
  • tests/fixtures/logging_fixtures.pydeterministic_message_fixtures now patches time.time for A01

Breaking changes

None. Every new qos parameter defaults to MqttQos.AT_MOST_ONCE (= 0), which is identical to the current hardcoded behavior. All existing callers work unchanged.

Reviewer notes

Addresses the following feedback from #871:

  • [allenporter] Use an enum for QoS instead of raw integers
  • [allenporter] Place docstrings below each enum value
  • [allenporter] Move time.time monkeypatch into deterministic_message_fixtures

Part of: #871 (closed)

Add MqttQos enum (AT_MOST_ONCE=0, AT_LEAST_ONCE=1, EXACTLY_ONCE=2) and thread a qos parameter through the publish chain (MqttSession -> MqttChannel -> send_decoded_command). All existing callers keep default AT_MOST_ONCE (backward compatible). Also add a unix timestamp field to A01 encode_mqtt_payload, required by Zeo/Dyad devices for command acceptance.
@allenporter
allenporter merged commit 3deed81 into Python-roborock:main Jul 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants