Skip to content

Commit f0dccc2

Browse files
committed
feat: add Q10 integration test and support stream streaming on FakeChannel
1 parent 7bbf593 commit f0dccc2

3 files changed

Lines changed: 101 additions & 2 deletions

File tree

roborock/testing/channel.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
in-memory replacement for `MqttChannel` and `LocalChannel` during testing.
66
"""
77

8-
from collections.abc import Awaitable, Callable
8+
import asyncio
9+
from collections.abc import AsyncGenerator, Awaitable, Callable
910
from typing import Any
1011
from unittest.mock import AsyncMock, MagicMock
1112

@@ -146,3 +147,17 @@ def clear_error(self) -> None:
146147
self.publish.side_effect = self._publish
147148
self.subscribe.side_effect = self._subscribe
148149
self.connect.side_effect = self._connect
150+
151+
async def subscribe_stream(self) -> AsyncGenerator[RoborockMessage, None]:
152+
"""Stream messages received via this channel."""
153+
queue: asyncio.Queue[RoborockMessage] = asyncio.Queue()
154+
155+
def callback(message: RoborockMessage) -> None:
156+
queue.put_nowait(message)
157+
158+
unsub = await self.subscribe(callback)
159+
try:
160+
while True:
161+
yield await queue.get()
162+
finally:
163+
unsub()

roborock/testing/cloud.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from roborock.data import HomeData, Reference, RRiot, UserData
1717
from roborock.devices.rpc.v1_channel import create_v1_channel as original_create_v1_channel
1818
from roborock.devices.transport.mqtt_channel import create_mqtt_channel as original_create_mqtt_channel
19+
from roborock.testing.b01_q10_simulator import Q10VacuumSimulator
1920
from roborock.testing.simulator import RoborockDeviceSimulator
2021
from roborock.testing.v1_simulator import V1VacuumSimulator
2122

@@ -261,6 +262,12 @@ def mock_create_v1_channel(user_data, mqtt_params, mqtt_session, device, device_
261262
def mock_create_mqtt_channel(user_data, mqtt_params, mqtt_session, device):
262263
server = self.simulated_devices.get(device.duid)
263264
if server:
265+
if device.pv == "B01" and not isinstance(server, Q10VacuumSimulator):
266+
raise NotImplementedError(
267+
f"Simulating protocol {device.pv} is not yet supported. "
268+
"Only Q10VacuumSimulator is supported for B01 protocols currently."
269+
)
270+
server.mqtt_channel._is_connected = True
264271
return server.mqtt_channel
265272
if device.pv in ("A01", "B01"):
266273
raise NotImplementedError(

tests/devices/test_device_manager.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from roborock.devices.device import RoborockDevice
1616
from roborock.devices.device_manager import UserParams, create_device_manager, create_web_api_wrapper
1717
from roborock.exceptions import RoborockException, RoborockInvalidCredentials
18-
from roborock.testing import FakeRoborockCloud, V1VacuumSimulator
18+
from roborock.testing import FakeRoborockCloud, Q10VacuumSimulator, V1VacuumSimulator
1919
from tests import mock_data
2020

2121
USER_DATA = UserData.from_dict(mock_data.USER_DATA)
@@ -56,6 +56,83 @@ async def test_no_devices(cloud: FakeRoborockCloud, patch_device_manager: None)
5656
assert devices == []
5757

5858

59+
async def test_with_q10_device(cloud: FakeRoborockCloud, patch_device_manager: None) -> None:
60+
"""Test DeviceManager with a Q10 device simulator registered."""
61+
product = HomeDataProduct(
62+
id="product-id-q10",
63+
name="Roborock Q10",
64+
model="roborock.vacuum.ss07",
65+
category=RoborockCategory.VACUUM,
66+
)
67+
device_info = HomeDataDevice(
68+
duid="q10_duid",
69+
name="My Q10",
70+
local_key="key123key123key1",
71+
product_id=product.id,
72+
sn="q10_serial",
73+
pv="B01",
74+
)
75+
76+
home_data = cloud.web_api.get_default_home_data()
77+
home_data.devices.append(device_info)
78+
home_data.products.append(product)
79+
cloud.web_api.set_homes_response(home_data)
80+
81+
q10_sim = Q10VacuumSimulator(
82+
duid="q10_duid",
83+
device_info=device_info,
84+
product=product,
85+
)
86+
cloud.add_device(q10_sim)
87+
88+
device_manager = await create_device_manager(USER_PARAMS)
89+
devices = await device_manager.get_devices()
90+
91+
# The setup includes fake_device (V1) by default because of the fake_device fixture
92+
# which is not requested here (we only request cloud and patch_device_manager),
93+
# but the mock EAPI returns the full home_data layout containing our Q10 device.
94+
assert len(devices) == 1
95+
96+
q10_device = await device_manager.get_device("q10_duid")
97+
assert q10_device is not None
98+
assert q10_device.name == "My Q10"
99+
assert q10_device.product.model == "roborock.vacuum.ss07"
100+
101+
# Wait for background connect to establish
102+
for _ in range(20):
103+
if q10_device.is_connected:
104+
break
105+
await asyncio.sleep(0.05)
106+
assert q10_device.is_connected
107+
108+
assert q10_device.b01_q10_properties is not None
109+
assert q10_device.b01_q10_properties.status.status is None
110+
111+
await q10_device.b01_q10_properties.refresh()
112+
113+
for _ in range(20):
114+
if q10_device.b01_q10_properties.status.battery == 100:
115+
break
116+
await asyncio.sleep(0.05)
117+
118+
assert q10_device.b01_q10_properties.status.battery == 100
119+
from roborock.data.b01_q10.b01_q10_code_mappings import YXDeviceState
120+
121+
assert q10_device.b01_q10_properties.status.status == YXDeviceState.CHARGING
122+
123+
await q10_device.b01_q10_properties.vacuum.start_clean()
124+
125+
for _ in range(20):
126+
if q10_device.b01_q10_properties.status.status == YXDeviceState.CLEANING:
127+
break
128+
await asyncio.sleep(0.05)
129+
130+
assert q10_device.b01_q10_properties.status.status == YXDeviceState.CLEANING
131+
assert q10_sim.status[121] == 5
132+
133+
await device_manager.close()
134+
135+
59136
async def test_with_device(
60137
cloud: FakeRoborockCloud, fake_device: V1VacuumSimulator, patch_device_manager: None
61138
) -> None:

0 commit comments

Comments
 (0)