5454from roborock .devices .traits import Trait
5555from roborock .devices .traits .common import TraitUpdateListener
5656from roborock .devices .transport .mqtt_channel import MqttChannel
57- from roborock .exceptions import RoborockException
57+ from roborock .exceptions import RoborockException , RoborockTimeout
5858from roborock .protocols .a01_protocol import decode_rpc_response
5959from roborock .roborock_message import (
6060 RoborockDyadDataProtocol ,
104104 RoborockDyadDataProtocol .PRODUCT_INFO : lambda val : DyadProductInfo .from_dict (val ),
105105}
106106
107+ # Devices known to lack FEATURE_BITS (DP 237).
108+ _UNSUPPORTED_FEATURE_BITS : frozenset [str ] = frozenset ({
109+ "roborock.wm.a63" , # H1
110+ "roborock.wm.a90" , # H1 Lite
111+ })
112+
107113ZEO_PROTOCOL_ENTRIES : dict [RoborockZeoProtocol , Callable ] = {
108114 # read-only
109115 RoborockZeoProtocol .STATE : lambda val : ZeoState (val ).name ,
@@ -173,13 +179,14 @@ class ZeoApi(Trait, TraitUpdateListener):
173179
174180 name = "zeo"
175181
176- def __init__ (self , channel : MqttChannel ) -> None :
182+ def __init__ (self , channel : MqttChannel , product_id : str | None = None ) -> None :
177183 """Initialize the Zeo API."""
178184 TraitUpdateListener .__init__ (self , _LOGGER )
179185 self ._channel = channel
180186 self ._dps_cache : dict [int , Any ] = {}
181187 self ._dps_unsub : Callable [[], None ] | None = None
182188 self ._feature_bits : int = 0
189+ self ._product_id = product_id
183190
184191 async def start (self ) -> None :
185192 """Subscribe to MQTT push and discover device features.
@@ -205,20 +212,17 @@ async def _ensure_subscribed(self) -> None:
205212 async def _discover_features (self ) -> None :
206213 """Query FEATURE_BITS to wake the device and cache capabilities.
207214
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-
213215 Only devices that support the FeatureBits DP will respond;
214- older or unsupported devices return nothing.
215- A failed query defaults to 0 — all feature-gated DPs are
216- disabled and the device operates in basic mode .
216+ For devices known to lack this DP
217+ the query is skipped entirely; for all other devices a
218+ timeout propagates as a connection error .
217219 """
220+ if self ._product_id in _UNSUPPORTED_FEATURE_BITS :
221+ return
218222 try :
219223 result = await self .query_values ([RoborockZeoProtocol .FEATURE_BITS ])
220224 self ._feature_bits = result .get (RoborockZeoProtocol .FEATURE_BITS , 0 )
221- except RoborockException :
225+ except RoborockTimeout :
222226 self ._feature_bits = 0
223227
224228 def supports (self , feature : ZeoFeatureBits ) -> bool :
@@ -250,9 +254,6 @@ async def query_values(self, protocols: list[RoborockZeoProtocol]) -> dict[Robor
250254 {RoborockZeoProtocol .ID_QUERY : protocols },
251255 value_encoder = json .dumps ,
252256 )
253- for protocol , value in response .items ():
254- if value is not None :
255- self ._dps_cache [int (protocol )] = value
256257 return {protocol : convert_zeo_value (protocol , response .get (protocol )) for protocol in protocols }
257258
258259 async def set_value (self , protocol : RoborockZeoProtocol , value : Any ) -> dict [RoborockZeoProtocol , Any ]:
@@ -267,6 +268,6 @@ def create(product: HomeDataProduct, mqtt_channel: MqttChannel) -> DyadApi | Zeo
267268 case RoborockCategory .WET_DRY_VAC :
268269 return DyadApi (mqtt_channel )
269270 case RoborockCategory .WASHING_MACHINE :
270- return ZeoApi (mqtt_channel )
271+ return ZeoApi (mqtt_channel , product_id = product . id )
271272 case _:
272273 raise NotImplementedError (f"Unsupported category { product .category } " )
0 commit comments