Skip to content

Commit cc6b202

Browse files
lboueclaude
andcommitted
feat(q10): add Roborock Q10 S5+ support with CLI commands
Add vacuum trait methods (empty_dustbin, set_clean_mode, set_fan_level), dedicated Q10 CLI session commands, and ROBOROCK_Q10 constant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dfa2fb1 commit cc6b202

File tree

3 files changed

+187
-2
lines changed

3 files changed

+187
-2
lines changed

roborock/cli.py

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@
4343

4444
from roborock import RoborockCommand
4545
from roborock.data import RoborockBase, UserData
46-
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
46+
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP, YXCleanType, YXFanLevel
4747
from roborock.data.code_mappings import SHORT_MODEL_TO_ENUM
4848
from roborock.device_features import DeviceFeatures
4949
from roborock.devices.cache import Cache, CacheData
5050
from roborock.devices.device import RoborockDevice
5151
from roborock.devices.device_manager import DeviceManager, UserParams, create_device_manager
5252
from roborock.devices.traits import Trait
53+
from roborock.devices.traits.b01.q10.vacuum import VacuumTrait
5354
from roborock.devices.traits.v1 import V1TraitMixin
5455
from roborock.devices.traits.v1.consumeable import ConsumableAttribute
5556
from roborock.devices.traits.v1.map_content import MapContentTrait
@@ -438,6 +439,15 @@ async def _display_v1_trait(context: RoborockContext, device_id: str, display_fu
438439
click.echo(dump_json(trait.as_dict()))
439440

440441

442+
async def _q10_vacuum_trait(context: RoborockContext, device_id: str) -> VacuumTrait:
443+
"""Get VacuumTrait from Q10 device."""
444+
device_manager = await context.get_device_manager()
445+
device = await device_manager.get_device(device_id)
446+
if device.b01_q10_properties is None:
447+
raise RoborockUnsupportedFeature("Device does not support B01 Q10 protocol. Is it a Q10?")
448+
return device.b01_q10_properties.vacuum
449+
450+
441451
@session.command()
442452
@click.option("--device_id", required=True)
443453
@click.pass_context
@@ -1172,6 +1182,154 @@ def write_markdown_table(product_features: dict[str, dict[str, any]], all_featur
11721182
cli.add_command(network_info)
11731183

11741184

1185+
# --- Q10 session commands ---
1186+
1187+
1188+
@session.command()
1189+
@click.option("--device_id", required=True, help="Device ID")
1190+
@click.pass_context
1191+
@async_command
1192+
async def q10_vacuum_start(ctx: click.Context, device_id: str) -> None:
1193+
"""Start vacuum cleaning on Q10 device."""
1194+
context: RoborockContext = ctx.obj
1195+
try:
1196+
trait = await _q10_vacuum_trait(context, device_id)
1197+
await trait.start_clean()
1198+
click.echo("Starting vacuum cleaning...")
1199+
except RoborockUnsupportedFeature:
1200+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1201+
except RoborockException as e:
1202+
click.echo(f"Error: {e}")
1203+
1204+
1205+
@session.command()
1206+
@click.option("--device_id", required=True, help="Device ID")
1207+
@click.pass_context
1208+
@async_command
1209+
async def q10_vacuum_pause(ctx: click.Context, device_id: str) -> None:
1210+
"""Pause vacuum cleaning on Q10 device."""
1211+
context: RoborockContext = ctx.obj
1212+
try:
1213+
trait = await _q10_vacuum_trait(context, device_id)
1214+
await trait.pause_clean()
1215+
click.echo("Pausing vacuum cleaning...")
1216+
except RoborockUnsupportedFeature:
1217+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1218+
except RoborockException as e:
1219+
click.echo(f"Error: {e}")
1220+
1221+
1222+
@session.command()
1223+
@click.option("--device_id", required=True, help="Device ID")
1224+
@click.pass_context
1225+
@async_command
1226+
async def q10_vacuum_resume(ctx: click.Context, device_id: str) -> None:
1227+
"""Resume vacuum cleaning on Q10 device."""
1228+
context: RoborockContext = ctx.obj
1229+
try:
1230+
trait = await _q10_vacuum_trait(context, device_id)
1231+
await trait.resume_clean()
1232+
click.echo("Resuming vacuum cleaning...")
1233+
except RoborockUnsupportedFeature:
1234+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1235+
except RoborockException as e:
1236+
click.echo(f"Error: {e}")
1237+
1238+
1239+
@session.command()
1240+
@click.option("--device_id", required=True, help="Device ID")
1241+
@click.pass_context
1242+
@async_command
1243+
async def q10_vacuum_stop(ctx: click.Context, device_id: str) -> None:
1244+
"""Stop vacuum cleaning on Q10 device."""
1245+
context: RoborockContext = ctx.obj
1246+
try:
1247+
trait = await _q10_vacuum_trait(context, device_id)
1248+
await trait.stop_clean()
1249+
click.echo("Stopping vacuum cleaning...")
1250+
except RoborockUnsupportedFeature:
1251+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1252+
except RoborockException as e:
1253+
click.echo(f"Error: {e}")
1254+
1255+
1256+
@session.command()
1257+
@click.option("--device_id", required=True, help="Device ID")
1258+
@click.pass_context
1259+
@async_command
1260+
async def q10_vacuum_dock(ctx: click.Context, device_id: str) -> None:
1261+
"""Return vacuum to dock on Q10 device."""
1262+
context: RoborockContext = ctx.obj
1263+
try:
1264+
trait = await _q10_vacuum_trait(context, device_id)
1265+
await trait.return_to_dock()
1266+
click.echo("Returning vacuum to dock...")
1267+
except RoborockUnsupportedFeature:
1268+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1269+
except RoborockException as e:
1270+
click.echo(f"Error: {e}")
1271+
1272+
1273+
@session.command()
1274+
@click.option("--device_id", required=True, help="Device ID")
1275+
@click.pass_context
1276+
@async_command
1277+
async def q10_empty_dustbin(ctx: click.Context, device_id: str) -> None:
1278+
"""Empty the dustbin at the dock on Q10 device."""
1279+
context: RoborockContext = ctx.obj
1280+
try:
1281+
trait = await _q10_vacuum_trait(context, device_id)
1282+
await trait.empty_dustbin()
1283+
click.echo("Emptying dustbin...")
1284+
except RoborockUnsupportedFeature:
1285+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1286+
except RoborockException as e:
1287+
click.echo(f"Error: {e}")
1288+
1289+
1290+
@session.command()
1291+
@click.option("--device_id", required=True, help="Device ID")
1292+
@click.option("--mode", required=True, type=click.Choice(["bothwork", "onlysweep", "onlymop"]), help="Clean mode")
1293+
@click.pass_context
1294+
@async_command
1295+
async def q10_set_clean_mode(ctx: click.Context, device_id: str, mode: str) -> None:
1296+
"""Set the cleaning mode on Q10 device (vacuum, mop, or both)."""
1297+
context: RoborockContext = ctx.obj
1298+
try:
1299+
trait = await _q10_vacuum_trait(context, device_id)
1300+
clean_mode = YXCleanType.from_value(mode)
1301+
await trait.set_clean_mode(clean_mode)
1302+
click.echo(f"Clean mode set to {mode}")
1303+
except RoborockUnsupportedFeature:
1304+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1305+
except RoborockException as e:
1306+
click.echo(f"Error: {e}")
1307+
1308+
1309+
@session.command()
1310+
@click.option("--device_id", required=True, help="Device ID")
1311+
@click.option(
1312+
"--level",
1313+
required=True,
1314+
type=click.Choice(["close", "quite", "normal", "strong", "max", "super"]),
1315+
help="Fan suction level",
1316+
)
1317+
@click.pass_context
1318+
@async_command
1319+
async def q10_set_fan_level(ctx: click.Context, device_id: str, level: str) -> None:
1320+
"""Set the fan suction level on Q10 device."""
1321+
context: RoborockContext = ctx.obj
1322+
try:
1323+
trait = await _q10_vacuum_trait(context, device_id)
1324+
fan_level = YXFanLevel.from_value(level)
1325+
await trait.set_fan_level(fan_level)
1326+
click.echo(f"Fan level set to {level}")
1327+
except RoborockUnsupportedFeature:
1328+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
1329+
except RoborockException as e:
1330+
click.echo(f"Error: {e}")
1331+
1332+
11751333
def main():
11761334
return cli()
11771335

roborock/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ROBOROCK_QREVO_MASTER = "roborock.vacuum.a117"
3434
ROBOROCK_QREVO_CURV = "roborock.vacuum.a135"
3535
ROBOROCK_Q8_MAX = "roborock.vacuum.a73"
36+
ROBOROCK_Q10 = "roborock.vacuum.ss07"
3637
ROBOROCK_G10S_PRO = "roborock.vacuum.a26"
3738
ROBOROCK_G20S_Ultra = "roborock.vacuum.a143" # cn saros_r10
3839
ROBOROCK_G10S = "roborock.vacuum.a46"
@@ -76,6 +77,7 @@
7677
ROBOROCK_S4_MAX,
7778
ROBOROCK_S7,
7879
ROBOROCK_P10,
80+
ROBOROCK_Q10,
7981
ROCKROBO_G10_SG,
8082
]
8183

roborock/devices/traits/b01/q10/vacuum.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""Traits for Q10 B01 devices."""
22

3-
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
3+
from roborock.data.b01_q10.b01_q10_code_mappings import (
4+
B01_Q10_DP,
5+
YXCleanType,
6+
YXFanLevel,
7+
)
48

59
from .command import CommandTrait
610

@@ -54,3 +58,24 @@ async def return_to_dock(self) -> None:
5458
command=B01_Q10_DP.START_DOCK_TASK,
5559
params={},
5660
)
61+
62+
async def empty_dustbin(self) -> None:
63+
"""Empty the dustbin at the dock."""
64+
await self._command.send(
65+
command=B01_Q10_DP.START_DOCK_TASK,
66+
params=2,
67+
)
68+
69+
async def set_clean_mode(self, mode: YXCleanType) -> None:
70+
"""Set the cleaning mode (vacuum, mop, or both)."""
71+
await self._command.send(
72+
command=B01_Q10_DP.CLEAN_MODE,
73+
params=mode.code,
74+
)
75+
76+
async def set_fan_level(self, level: YXFanLevel) -> None:
77+
"""Set the fan suction level."""
78+
await self._command.send(
79+
command=B01_Q10_DP.FAN_LEVEL,
80+
params=level.code,
81+
)

0 commit comments

Comments
 (0)