Skip to content

Commit 55189c6

Browse files
author
NOisi-X
committed
fix(zeo): call TraitUpdateListener.__init__ in ZeoApi and fix connect() to use zeo attribute
1 parent faf8f32 commit 55189c6

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

roborock/devices/device.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ async def connect(self) -> None:
202202
await self.v1_properties.start()
203203
elif self.b01_q10_properties is not None:
204204
await self.b01_q10_properties.start()
205+
if self.zeo is not None:
206+
await self.zeo.start()
205207
except RoborockException:
206208
# Expected: start() can fail transiently. Unsubscribe before propagating
207209
# so the retry by connect_loop() gets a clean channel.

roborock/devices/traits/a01/__init__.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020
"""
2121

2222
import json
23+
import logging
2324
from collections.abc import Callable
2425
from datetime import time
2526
from typing import Any
2627

28+
from roborock.exceptions import RoborockException
29+
from roborock.protocols.a01_protocol import decode_rpc_response
30+
2731
from roborock.data import DyadProductInfo, DyadSndState, HomeDataProduct, RoborockCategory
2832
from roborock.data.dyad.dyad_code_mappings import (
2933
DyadBrushSpeed,
@@ -50,8 +54,16 @@
5054
)
5155
from roborock.devices.rpc.a01_channel import send_decoded_command
5256
from roborock.devices.traits import Trait
57+
from roborock.devices.traits.common import TraitUpdateListener
5358
from roborock.devices.transport.mqtt_channel import MqttChannel
54-
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
59+
from roborock.roborock_message import (
60+
RoborockDyadDataProtocol,
61+
RoborockMessage,
62+
RoborockMessageProtocol,
63+
RoborockZeoProtocol,
64+
)
65+
66+
_LOGGER = logging.getLogger(__name__)
5567

5668
__init__ = [
5769
"DyadApi",
@@ -156,14 +168,50 @@ async def set_value(self, protocol: RoborockDyadDataProtocol, value: Any) -> dic
156168
return await send_decoded_command(self._channel, params)
157169

158170

159-
class ZeoApi(Trait):
171+
class ZeoApi(Trait, TraitUpdateListener):
160172
"""API for interacting with Zeo devices."""
161173

162174
name = "zeo"
163175

164176
def __init__(self, channel: MqttChannel) -> None:
165177
"""Initialize the Zeo API."""
178+
TraitUpdateListener.__init__(self, _LOGGER)
166179
self._channel = channel
180+
self._dps_cache: dict[int, Any] = {}
181+
self._dps_unsub: Callable[[], None] | None = None
182+
183+
async def start(self) -> None:
184+
"""Subscribe to MQTT push (called once after device connects)."""
185+
await self._ensure_subscribed()
186+
187+
def close(self) -> None:
188+
"""Unsubscribe from MQTT push and release resources."""
189+
if self._dps_unsub is not None:
190+
self._dps_unsub()
191+
self._dps_unsub = None
192+
193+
async def _ensure_subscribed(self) -> None:
194+
"""Subscribe to MQTT DPS push (idempotent)."""
195+
if self._dps_unsub is not None:
196+
return
197+
self._dps_unsub = await self._channel.subscribe(self._on_dps_message)
198+
199+
def _on_dps_message(self, message: RoborockMessage) -> None:
200+
"""Handle unsolicited MQTT push (protocol 102 — RPC_RESPONSE).
201+
202+
Zeo devices broadcast status changes as ``{"dps": {...}}`` JSON
203+
payloads. This callback decodes them and feeds the cache so
204+
that ``query_values`` can skip the device round-trip when the
205+
requested DPs are already up to date.
206+
"""
207+
if message.protocol != RoborockMessageProtocol.RPC_RESPONSE:
208+
return
209+
try:
210+
decoded = decode_rpc_response(message)
211+
self._dps_cache.update(decoded)
212+
self._notify_update()
213+
except RoborockException:
214+
_LOGGER.debug("Failed to decode push message, skipping: %s", message, exc_info=True)
167215

168216
async def query_values(self, protocols: list[RoborockZeoProtocol]) -> dict[RoborockZeoProtocol, Any]:
169217
"""Query the device for the values of the given protocols."""
@@ -172,6 +220,9 @@ async def query_values(self, protocols: list[RoborockZeoProtocol]) -> dict[Robor
172220
{RoborockZeoProtocol.ID_QUERY: protocols},
173221
value_encoder=json.dumps,
174222
)
223+
for protocol, value in response.items():
224+
if value is not None:
225+
self._dps_cache[int(protocol)] = value
175226
return {protocol: convert_zeo_value(protocol, response.get(protocol)) for protocol in protocols}
176227

177228
async def set_value(self, protocol: RoborockZeoProtocol, value: Any) -> dict[RoborockZeoProtocol, Any]:

0 commit comments

Comments
 (0)