Skip to content

Commit a7a6a28

Browse files
author
NOisi-X
committed
feat(zeo): add _discover_features() and supports() with FEATURE_BITS; add DP 243
1 parent 55189c6 commit a7a6a28

3 files changed

Lines changed: 34 additions & 5 deletions

File tree

roborock/devices/traits/a01/__init__.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
from datetime import time
2626
from typing import Any
2727

28-
from roborock.exceptions import RoborockException
29-
from roborock.protocols.a01_protocol import decode_rpc_response
30-
3128
from roborock.data import DyadProductInfo, DyadSndState, HomeDataProduct, RoborockCategory
3229
from roborock.data.dyad.dyad_code_mappings import (
3330
DyadBrushSpeed,
@@ -44,6 +41,7 @@
4441
ZeoDetergentType,
4542
ZeoDryingMode,
4643
ZeoError,
44+
ZeoFeatureBits,
4745
ZeoMode,
4846
ZeoProgram,
4947
ZeoRinse,
@@ -56,6 +54,8 @@
5654
from roborock.devices.traits import Trait
5755
from roborock.devices.traits.common import TraitUpdateListener
5856
from roborock.devices.transport.mqtt_channel import MqttChannel
57+
from roborock.exceptions import RoborockException
58+
from roborock.protocols.a01_protocol import decode_rpc_response
5959
from roborock.roborock_message import (
6060
RoborockDyadDataProtocol,
6161
RoborockMessage,
@@ -179,10 +179,16 @@ def __init__(self, channel: MqttChannel) -> None:
179179
self._channel = channel
180180
self._dps_cache: dict[int, Any] = {}
181181
self._dps_unsub: Callable[[], None] | None = None
182+
self._feature_bits: int = 0
182183

183184
async def start(self) -> None:
184-
"""Subscribe to MQTT push (called once after device connects)."""
185+
"""Subscribe to MQTT push and discover device features.
186+
187+
Subscribes to the DPS MQTT topic, then queries FEATURE_BITS
188+
(DP 237) to wake the device and cache supported capabilities.
189+
"""
185190
await self._ensure_subscribed()
191+
await self._discover_features()
186192

187193
def close(self) -> None:
188194
"""Unsubscribe from MQTT push and release resources."""
@@ -196,6 +202,24 @@ async def _ensure_subscribed(self) -> None:
196202
return
197203
self._dps_unsub = await self._channel.subscribe(self._on_dps_message)
198204

205+
async def _discover_features(self) -> None:
206+
"""Query FEATURE_BITS to wake the device and cache capabilities.
207+
208+
Sending an RPC query after subscribing triggers the device to
209+
start pushing its full state — equivalent to how V1's
210+
``discover_features()`` uses ``device_features.refresh()`` to
211+
initiate the push cycle.
212+
"""
213+
try:
214+
result = await self.query_values([RoborockZeoProtocol.FEATURE_BITS])
215+
self._feature_bits = result.get(RoborockZeoProtocol.FEATURE_BITS, 0)
216+
except Exception:
217+
self._feature_bits = 0
218+
219+
def supports(self, feature: ZeoFeatureBits) -> bool:
220+
"""Check whether the device supports a given feature bit."""
221+
return bool(self._feature_bits & (1 << feature.value))
222+
199223
def _on_dps_message(self, message: RoborockMessage) -> None:
200224
"""Handle unsolicited MQTT push (protocol 102 — RPC_RESPONSE).
201225

roborock/roborock_message.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,9 @@ class RoborockZeoProtocol(RoborockEnum):
179179
SILENT_MODE_ON = 240 # rw [independent] use set_silent_mode() for bundled set
180180
SILENT_MODE_START_TIME = 241 # rw [independent] minute-of-day
181181
SILENT_MODE_END_TIME = 242 # rw [independent] minute-of-day
182+
UNKNOWN_243 = (
183+
243 # unknown, not found in plugin bundle; present in MQTT push from some devices, increments with each push
184+
)
182185
DRY_CARE_MODE = 244 # rw [startWith]
183186
SOFTENER_EXPANSION_TYPE = 245 # rw [independent]
184187
SMILE_LIGHT_STATUS = 247 # rw [independent]

tests/e2e/test_device_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,10 @@ async def test_a01_device(
527527
test_topic = TEST_TOPIC_FORMAT.format(duid="zeo_duid")
528528
mqtt_responses: list[bytes] = [
529529
*MQTT_DEFAULT_RESPONSES,
530+
# ACK the FEATURE_BITS query sent by _discover_features(). id is deterministic based on deterministic_message_fixtures
531+
mqtt_packet.gen_publish(test_topic, mid=2, payload=response_builder.build_a01_rpc({"237": 1})),
530532
# ACK the Query state call sent below. id is deterministic based on deterministic_message_fixtures
531-
mqtt_packet.gen_publish(test_topic, mid=2, payload=response_builder.build_a01_rpc({"203": 6})),
533+
mqtt_packet.gen_publish(test_topic, mid=3, payload=response_builder.build_a01_rpc({"203": 6})),
532534
]
533535
for response in mqtt_responses:
534536
push_mqtt_response(response)

0 commit comments

Comments
 (0)