88A01 devices expose a single API object that handles all device interactions. This API is
99available on the device instance (typically via `device.a01_properties`).
1010
11- The API provides two main methods:
11+ The API provides these methods:
12121. **query_values(protocols)**: Fetches current state for specific data points.
1313 You must pass a list of protocol enums (e.g. `RoborockDyadDataProtocol` or
1414 `RoborockZeoProtocol`) to request specific data.
15152. **set_value(protocol, value)**: Sends a command to the device to change a setting
1616 or perform an action.
17+ 3. **add_listener(callback)**: Subscribes to state the device pushes on its own (for
18+ example when its state changes), invoking the callback with decoded values.
1719
1820Note that these APIs fetch data directly from the device upon request and do not
1921cache state internally.
5153from roborock .devices .rpc .a01_channel import send_decoded_command
5254from roborock .devices .traits import Trait
5355from roborock .devices .transport .mqtt_channel import MqttChannel
54- from roborock .roborock_message import RoborockDyadDataProtocol , RoborockZeoProtocol
56+ from roborock .exceptions import RoborockException
57+ from roborock .protocols .a01_protocol import decode_rpc_response
58+ from roborock .roborock_message import RoborockDyadDataProtocol , RoborockMessage , RoborockZeoProtocol
5559
5660__init__ = [
5761 "DyadApi" ,
@@ -134,6 +138,12 @@ def convert_zeo_value(protocol_value: RoborockZeoProtocol, value: Any) -> Any:
134138 return None
135139
136140
141+ # RoborockDyadDataProtocol._missing_ maps any unknown code to its first member
142+ # instead of raising, so incoming data points must be checked against this set
143+ # before being converted to a protocol.
144+ _DYAD_PROTOCOL_VALUES = frozenset (protocol .value for protocol in RoborockDyadDataProtocol )
145+
146+
137147class DyadApi (Trait ):
138148 """API for interacting with Dyad devices."""
139149
@@ -155,6 +165,32 @@ async def set_value(self, protocol: RoborockDyadDataProtocol, value: Any) -> dic
155165 params = {protocol : value }
156166 return await send_decoded_command (self ._channel , params )
157167
168+ async def add_listener (
169+ self , callback : Callable [[dict [RoborockDyadDataProtocol , Any ]], None ]
170+ ) -> Callable [[], None ]:
171+ """Listen for state the device pushes on its own.
172+
173+ The callback is invoked with decoded values whenever the device sends a
174+ message, including unsolicited pushes when its state changes. Only known
175+ protocols are delivered. Returns a callable to remove the listener.
176+ """
177+
178+ def on_message (message : RoborockMessage ) -> None :
179+ try :
180+ datapoints = decode_rpc_response (message )
181+ except RoborockException :
182+ return
183+ values : dict [RoborockDyadDataProtocol , Any ] = {}
184+ for code , value in datapoints .items ():
185+ if code not in _DYAD_PROTOCOL_VALUES :
186+ continue
187+ protocol = RoborockDyadDataProtocol (code )
188+ values [protocol ] = convert_dyad_value (protocol , value )
189+ if values :
190+ callback (values )
191+
192+ return await self ._channel .subscribe (on_message )
193+
158194
159195class ZeoApi (Trait ):
160196 """API for interacting with Zeo devices."""
0 commit comments