python-roborock 5.20.1__tar.gz → 5.22.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_roborock-5.20.1 → python_roborock-5.22.0}/PKG-INFO +1 -1
- {python_roborock-5.20.1 → python_roborock-5.22.0}/pyproject.toml +1 -1
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/cli.py +221 -5
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +33 -3
- python_roborock-5.22.0/roborock/data/b01_q10/b01_q10_containers.py +211 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/code_mappings.py +17 -9
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q10/__init__.py +59 -3
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/button_light.py +29 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/child_lock.py +36 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/common.py +70 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/consumable.py +21 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/do_not_disturb.py +36 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/dust_collection.py +43 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/network_info.py +21 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q10/remote.py +1 -1
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/status.py +26 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/vacuum.py +109 -0
- python_roborock-5.22.0/roborock/devices/traits/b01/q10/volume.py +29 -0
- python_roborock-5.20.1/roborock/data/b01_q10/b01_q10_containers.py +0 -110
- python_roborock-5.20.1/roborock/devices/traits/b01/q10/status.py +0 -31
- python_roborock-5.20.1/roborock/devices/traits/b01/q10/vacuum.py +0 -81
- {python_roborock-5.20.1 → python_roborock-5.22.0}/.gitignore +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/LICENSE +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/README.md +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/callbacks.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/const.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/containers.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/device_features.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/README.md +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/cache.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/device.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/rpc/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/rpc/a01_channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/rpc/b01_q10_channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/rpc/b01_q7_channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/rpc/v1_channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q10/command.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q10/map.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q7/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q7/clean_summary.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q7/map.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/b01/q7/map_content.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/common.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/transport/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/transport/channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/transport/local_channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/devices/transport/mqtt_channel.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/diagnostics.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/exceptions.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/b01_grid_layers.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/b01_map_parser.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/b01_q10_map_parser.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/b01_q10_overlays.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/proto/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/proto/b01_scmap.proto +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/map/proto/b01_scmap_pb2.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/protocol.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/protocols/b01_q10_protocol.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/protocols/b01_q7_protocol.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/py.typed +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/roborock_message.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/util.py +0 -0
- {python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.22.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/python-roborock/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-roborock"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.22.0"
|
|
4
4
|
description = "A package to control Roborock vacuums."
|
|
5
5
|
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
|
|
6
6
|
requires-python = ">=3.11, <4"
|
|
@@ -49,7 +49,12 @@ except ImportError as err:
|
|
|
49
49
|
|
|
50
50
|
from roborock import RoborockCommand
|
|
51
51
|
from roborock.data import RoborockBase, UserData
|
|
52
|
-
from roborock.data.b01_q10.b01_q10_code_mappings import
|
|
52
|
+
from roborock.data.b01_q10.b01_q10_code_mappings import (
|
|
53
|
+
B01_Q10_DP,
|
|
54
|
+
YXCleanType,
|
|
55
|
+
YXDeviceDustCollectionFrequency,
|
|
56
|
+
YXFanLevel,
|
|
57
|
+
)
|
|
53
58
|
from roborock.data.code_mappings import SHORT_MODEL_TO_ENUM
|
|
54
59
|
from roborock.device_features import DeviceFeatures
|
|
55
60
|
from roborock.devices.cache import Cache, CacheData
|
|
@@ -446,13 +451,67 @@ async def _display_v1_trait(context: RoborockContext, device_id: str, display_fu
|
|
|
446
451
|
click.echo(dump_json(trait.as_dict()))
|
|
447
452
|
|
|
448
453
|
|
|
449
|
-
async def
|
|
450
|
-
"""Get
|
|
454
|
+
async def _q10_properties(context: RoborockContext, device_id: str) -> Q10PropertiesApi:
|
|
455
|
+
"""Get the B01 Q10 properties API for a device."""
|
|
451
456
|
device_manager = await context.get_device_manager()
|
|
452
457
|
device = await device_manager.get_device(device_id)
|
|
453
458
|
if device.b01_q10_properties is None:
|
|
454
459
|
raise RoborockUnsupportedFeature("Device does not support B01 Q10 protocol. Is it a Q10?")
|
|
455
|
-
return device.b01_q10_properties
|
|
460
|
+
return device.b01_q10_properties
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
async def _q10_vacuum_trait(context: RoborockContext, device_id: str) -> VacuumTrait:
|
|
464
|
+
"""Get VacuumTrait from Q10 device."""
|
|
465
|
+
return (await _q10_properties(context, device_id)).vacuum
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
async def _display_q10_status(context: RoborockContext, device_id: str) -> None:
|
|
469
|
+
"""Refresh and display the full status of a B01 Q10 device.
|
|
470
|
+
|
|
471
|
+
Unlike V1 devices, the Q10 reports its state asynchronously: ``refresh()``
|
|
472
|
+
sends a request and the device streams the values back over the persistent
|
|
473
|
+
subscribe loop. That loop also delivers unsolicited pushes, so the read-model
|
|
474
|
+
traits may already hold (possibly stale) values from before this command ran
|
|
475
|
+
-- checking that a field is merely populated isn't enough. To display data
|
|
476
|
+
the device sent *in response to this refresh*, we register update listeners,
|
|
477
|
+
fire the refresh, and wait for a fresh update before reading the traits.
|
|
478
|
+
|
|
479
|
+
All read-model traits refreshed by :meth:`Q10PropertiesApi.refresh` are shown,
|
|
480
|
+
not just ``status`` (volume, child lock, do-not-disturb, dust collection,
|
|
481
|
+
network info and consumables are part of the device's reported state too).
|
|
482
|
+
"""
|
|
483
|
+
properties = await _q10_properties(context, device_id)
|
|
484
|
+
|
|
485
|
+
# Read-model traits populated from the device's DPS push stream.
|
|
486
|
+
traits = {
|
|
487
|
+
"status": properties.status,
|
|
488
|
+
"volume": properties.volume,
|
|
489
|
+
"child_lock": properties.child_lock,
|
|
490
|
+
"do_not_disturb": properties.do_not_disturb,
|
|
491
|
+
"dust_collection": properties.dust_collection,
|
|
492
|
+
"network_info": properties.network_info,
|
|
493
|
+
"consumable": properties.consumable,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
updated = asyncio.Event()
|
|
497
|
+
unsubscribes = [trait.add_update_listener(updated.set) for trait in traits.values()]
|
|
498
|
+
try:
|
|
499
|
+
await properties.refresh()
|
|
500
|
+
try:
|
|
501
|
+
await asyncio.wait_for(updated.wait(), timeout=5)
|
|
502
|
+
except TimeoutError:
|
|
503
|
+
click.echo("Timed out waiting for status from device")
|
|
504
|
+
return
|
|
505
|
+
# The device streams its DPS across several pushes; give the remaining
|
|
506
|
+
# ones a brief window to arrive after the first fresh update.
|
|
507
|
+
await asyncio.sleep(0.5)
|
|
508
|
+
finally:
|
|
509
|
+
for unsubscribe in unsubscribes:
|
|
510
|
+
unsubscribe()
|
|
511
|
+
|
|
512
|
+
# Each concrete trait also subclasses a RoborockBase read-model, so it has
|
|
513
|
+
# ``as_dict``; the cast satisfies the typed UpdatableTrait view above.
|
|
514
|
+
click.echo(dump_json({name: cast(RoborockBase, trait).as_dict() for name, trait in traits.items()}))
|
|
456
515
|
|
|
457
516
|
|
|
458
517
|
@session.command()
|
|
@@ -462,7 +521,14 @@ async def _q10_vacuum_trait(context: RoborockContext, device_id: str) -> VacuumT
|
|
|
462
521
|
async def status(ctx, device_id: str):
|
|
463
522
|
"""Get device status."""
|
|
464
523
|
context: RoborockContext = ctx.obj
|
|
465
|
-
await
|
|
524
|
+
device_manager = await context.get_device_manager()
|
|
525
|
+
device = await device_manager.get_device(device_id)
|
|
526
|
+
if device.v1_properties is not None:
|
|
527
|
+
await _display_v1_trait(context, device_id, lambda v1: v1.status)
|
|
528
|
+
elif device.b01_q10_properties is not None:
|
|
529
|
+
await _display_q10_status(context, device_id)
|
|
530
|
+
else:
|
|
531
|
+
click.echo("Feature not supported by device")
|
|
466
532
|
|
|
467
533
|
|
|
468
534
|
@session.command()
|
|
@@ -1399,6 +1465,156 @@ async def q10_vacuum_dock(ctx: click.Context, device_id: str) -> None:
|
|
|
1399
1465
|
click.echo(f"Error: {e}")
|
|
1400
1466
|
|
|
1401
1467
|
|
|
1468
|
+
@session.command()
|
|
1469
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1470
|
+
@click.pass_context
|
|
1471
|
+
@async_command
|
|
1472
|
+
async def q10_vacuum_spot(ctx: click.Context, device_id: str) -> None:
|
|
1473
|
+
"""Start a spot / part clean on a Q10 device."""
|
|
1474
|
+
context: RoborockContext = ctx.obj
|
|
1475
|
+
try:
|
|
1476
|
+
trait = await _q10_vacuum_trait(context, device_id)
|
|
1477
|
+
await trait.spot_clean()
|
|
1478
|
+
click.echo("Starting spot clean...")
|
|
1479
|
+
except RoborockUnsupportedFeature:
|
|
1480
|
+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
|
|
1481
|
+
except RoborockException as e:
|
|
1482
|
+
click.echo(f"Error: {e}")
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
@session.command()
|
|
1486
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1487
|
+
@click.option(
|
|
1488
|
+
"--segments",
|
|
1489
|
+
required=True,
|
|
1490
|
+
help="Comma-separated room/segment ids to clean (see the `rooms` command), e.g. 9,2",
|
|
1491
|
+
)
|
|
1492
|
+
@click.pass_context
|
|
1493
|
+
@async_command
|
|
1494
|
+
async def q10_clean_segments(ctx: click.Context, device_id: str, segments: str) -> None:
|
|
1495
|
+
"""Start a room / segment clean on a Q10 device.
|
|
1496
|
+
|
|
1497
|
+
Room ids come from the `rooms` command (the device's map rooms).
|
|
1498
|
+
"""
|
|
1499
|
+
context: RoborockContext = ctx.obj
|
|
1500
|
+
try:
|
|
1501
|
+
segment_ids = [int(s) for s in segments.split(",") if s.strip()]
|
|
1502
|
+
except ValueError:
|
|
1503
|
+
click.echo("--segments must be comma-separated integers, e.g. 9,2")
|
|
1504
|
+
return
|
|
1505
|
+
if not segment_ids:
|
|
1506
|
+
click.echo("No segment ids provided")
|
|
1507
|
+
return
|
|
1508
|
+
try:
|
|
1509
|
+
trait = await _q10_vacuum_trait(context, device_id)
|
|
1510
|
+
await trait.clean_segments(segment_ids)
|
|
1511
|
+
click.echo(f"Starting room clean of segments {segment_ids}...")
|
|
1512
|
+
except RoborockUnsupportedFeature:
|
|
1513
|
+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
|
|
1514
|
+
except RoborockException as e:
|
|
1515
|
+
click.echo(f"Error: {e}")
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
async def _q10_set(ctx: click.Context, device_id: str, apply: Callable[[Any], Any], message: str) -> None:
|
|
1519
|
+
"""Run a Q10 settings write and report the result."""
|
|
1520
|
+
context: RoborockContext = ctx.obj
|
|
1521
|
+
try:
|
|
1522
|
+
properties = await _q10_properties(context, device_id)
|
|
1523
|
+
await apply(properties)
|
|
1524
|
+
click.echo(message)
|
|
1525
|
+
except RoborockUnsupportedFeature:
|
|
1526
|
+
click.echo("Device does not support B01 Q10 protocol. Is it a Q10?")
|
|
1527
|
+
except (RoborockException, ValueError) as e:
|
|
1528
|
+
click.echo(f"Error: {e}")
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
@session.command()
|
|
1532
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1533
|
+
@click.option("--volume", required=True, type=int, help="Volume 0-100")
|
|
1534
|
+
@click.pass_context
|
|
1535
|
+
@async_command
|
|
1536
|
+
async def q10_set_volume(ctx: click.Context, device_id: str, volume: int) -> None:
|
|
1537
|
+
"""Set the speaker volume on a Q10 device."""
|
|
1538
|
+
await _q10_set(ctx, device_id, lambda p: p.volume.set_volume(volume), f"Volume set to {volume}")
|
|
1539
|
+
|
|
1540
|
+
|
|
1541
|
+
@session.command()
|
|
1542
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1543
|
+
@click.option("--enabled", required=True, type=bool, help="Enable (True) or disable (False)")
|
|
1544
|
+
@click.pass_context
|
|
1545
|
+
@async_command
|
|
1546
|
+
async def q10_set_child_lock(ctx: click.Context, device_id: str, enabled: bool) -> None:
|
|
1547
|
+
"""Enable or disable the child lock on a Q10 device."""
|
|
1548
|
+
await _q10_set(
|
|
1549
|
+
ctx,
|
|
1550
|
+
device_id,
|
|
1551
|
+
lambda p: p.child_lock.enable() if enabled else p.child_lock.disable(),
|
|
1552
|
+
f"Child lock set to {enabled}",
|
|
1553
|
+
)
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
@session.command()
|
|
1557
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1558
|
+
@click.option("--enabled", required=True, type=bool, help="Enable (True) or disable (False)")
|
|
1559
|
+
@click.pass_context
|
|
1560
|
+
@async_command
|
|
1561
|
+
async def q10_set_dnd(ctx: click.Context, device_id: str, enabled: bool) -> None:
|
|
1562
|
+
"""Enable or disable Do Not Disturb on a Q10 device."""
|
|
1563
|
+
await _q10_set(
|
|
1564
|
+
ctx,
|
|
1565
|
+
device_id,
|
|
1566
|
+
lambda p: p.do_not_disturb.enable() if enabled else p.do_not_disturb.disable(),
|
|
1567
|
+
f"Do Not Disturb set to {enabled}",
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
@session.command()
|
|
1572
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1573
|
+
@click.option("--enabled", required=True, type=bool, help="Enable (True) or disable (False)")
|
|
1574
|
+
@click.pass_context
|
|
1575
|
+
@async_command
|
|
1576
|
+
async def q10_set_led(ctx: click.Context, device_id: str, enabled: bool) -> None:
|
|
1577
|
+
"""Enable or disable the indicator light (LED) on a Q10 device."""
|
|
1578
|
+
await _q10_set(
|
|
1579
|
+
ctx,
|
|
1580
|
+
device_id,
|
|
1581
|
+
lambda p: p.button_light.enable() if enabled else p.button_light.disable(),
|
|
1582
|
+
f"LED set to {enabled}",
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1585
|
+
|
|
1586
|
+
@session.command()
|
|
1587
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1588
|
+
@click.option("--enabled", required=True, type=bool, help="Enable (True) or disable (False)")
|
|
1589
|
+
@click.pass_context
|
|
1590
|
+
@async_command
|
|
1591
|
+
async def q10_set_dust_collection(ctx: click.Context, device_id: str, enabled: bool) -> None:
|
|
1592
|
+
"""Enable or disable automatic dust collection on a Q10 device."""
|
|
1593
|
+
await _q10_set(
|
|
1594
|
+
ctx,
|
|
1595
|
+
device_id,
|
|
1596
|
+
lambda p: p.dust_collection.enable() if enabled else p.dust_collection.disable(),
|
|
1597
|
+
f"Dust collection set to {enabled}",
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
@session.command()
|
|
1602
|
+
@click.option("--device_id", required=True, help="Device ID")
|
|
1603
|
+
@click.option(
|
|
1604
|
+
"--frequency",
|
|
1605
|
+
required=True,
|
|
1606
|
+
type=click.Choice([str(m.code) for m in YXDeviceDustCollectionFrequency]),
|
|
1607
|
+
help="Empty after every N cleans (0 = daily).",
|
|
1608
|
+
)
|
|
1609
|
+
@click.pass_context
|
|
1610
|
+
@async_command
|
|
1611
|
+
async def q10_set_dust_frequency(ctx: click.Context, device_id: str, frequency: str) -> None:
|
|
1612
|
+
"""Set how often the dock empties the bin (0 = daily, else every N cleans)."""
|
|
1613
|
+
freq = YXDeviceDustCollectionFrequency.from_code(int(frequency))
|
|
1614
|
+
label = "daily" if freq.code == 0 else f"every {freq.code} cleans"
|
|
1615
|
+
await _q10_set(ctx, device_id, lambda p: p.dust_collection.set_frequency(freq), f"Dust frequency set to {label}")
|
|
1616
|
+
|
|
1617
|
+
|
|
1402
1618
|
@session.command()
|
|
1403
1619
|
@click.option("--device_id", required=True, help="Device ID")
|
|
1404
1620
|
@click.pass_context
|
{python_roborock-5.20.1 → python_roborock-5.22.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
@@ -85,9 +85,14 @@ class B01_Q10_DP(RoborockModeEnum):
|
|
|
85
85
|
SUSPECTED_THRESHOLD = ("dpSuspectedThreshold", 99)
|
|
86
86
|
SUSPECTED_THRESHOLD_UP = ("dpSuspectedThresholdUp", 100)
|
|
87
87
|
COMMON = ("dpCommon", 101)
|
|
88
|
-
JUMP_SCAN = ("dpJumpScan", 101)
|
|
89
88
|
REQUEST_DPS = ("dpRequestDps", 102) # NOTE: typo "dpRequetdps" in source code
|
|
90
|
-
|
|
89
|
+
# NOTE: the legacy B01 source also listed dpJumpScan (101) and
|
|
90
|
+
# dpCliffRestrictedArea (102), which collided with the confirmed dpCommon /
|
|
91
|
+
# dpRequestDps codes above (verified against ss07 hardware and the official app
|
|
92
|
+
# plugin) and shadowed them in ``from_code``. Both are unused and their real
|
|
93
|
+
# codes could not be verified, so they were removed rather than left as wrong
|
|
94
|
+
# duplicates. dpCliffRestrictedAreaUp (103) is kept: ss07 hardware does push
|
|
95
|
+
# data point 103 (an empty list when no cliff-restricted areas are set).
|
|
91
96
|
CLIFF_RESTRICTED_AREA_UP = ("dpCliffRestrictedAreaUp", 103)
|
|
92
97
|
BREAKPOINT_CLEAN = ("dpBreakpointClean", 104)
|
|
93
98
|
VALLEY_POINT_CHARGING = ("dpValleyPointCharging", 105)
|
|
@@ -96,6 +101,13 @@ class B01_Q10_DP(RoborockModeEnum):
|
|
|
96
101
|
VOICE_VERSION = ("dpVoiceVersion", 108)
|
|
97
102
|
ROBOT_COUNTRY_CODE = ("dpRobotCountryCode", 109)
|
|
98
103
|
HEARTBEAT = ("dpHeartbeat", 110)
|
|
104
|
+
# NOTE: ss07 hardware also pushes data points 112 and 113 in its full status
|
|
105
|
+
# dump. They are absent from the official app's vacuum plugin and stayed 0
|
|
106
|
+
# across every observed state (docked/charging, segment cleaning, lifted-off-
|
|
107
|
+
# ground fault, returning to dock, dustbin removed), so their meaning is not
|
|
108
|
+
# yet known. They are intentionally left unmapped; ``decode_rpc_response``
|
|
109
|
+
# silently ignores unknown codes via ``from_code_optional``, so they do not
|
|
110
|
+
# produce "not a valid code" warnings. Map them here once identified.
|
|
99
111
|
STATUS = ("dpStatus", 121)
|
|
100
112
|
BATTERY = ("dpBattery", 122)
|
|
101
113
|
FAN_LEVEL = ("dpFanLevel", 123) # NOTE: typo "dpfunLevel" in source code
|
|
@@ -215,13 +227,31 @@ class YXDeviceCleanTask(RoborockModeEnum):
|
|
|
215
227
|
|
|
216
228
|
|
|
217
229
|
class YXDeviceDustCollectionFrequency(RoborockModeEnum):
|
|
218
|
-
|
|
230
|
+
# The app exposes "regular" (code 0) vs "frequent", where "frequent" selects
|
|
231
|
+
# one of the every-N-cleans intervals below.
|
|
232
|
+
REGULAR = "regular", 0
|
|
219
233
|
INTERVAL_15 = "interval_15", 15
|
|
220
234
|
INTERVAL_30 = "interval_30", 30
|
|
221
235
|
INTERVAL_45 = "interval_45", 45
|
|
222
236
|
INTERVAL_60 = "interval_60", 60
|
|
223
237
|
|
|
224
238
|
|
|
239
|
+
class YXAreaUnit(RoborockModeEnum):
|
|
240
|
+
"""Unit used to report cleaned area (dpAreaUnit)."""
|
|
241
|
+
|
|
242
|
+
SQUARE_METER = "square_meter", 0
|
|
243
|
+
SQUARE_FEET = "square_feet", 1
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class YXCarpetCleanType(RoborockModeEnum):
|
|
247
|
+
"""Carpet handling behavior (dpCarpetCleanType)."""
|
|
248
|
+
|
|
249
|
+
RISE = "rise", 0 # lift the mop and boost over carpet
|
|
250
|
+
AVOID = "avoid", 1
|
|
251
|
+
IGNORE = "ignore", 2
|
|
252
|
+
CROSS = "cross", 3
|
|
253
|
+
|
|
254
|
+
|
|
225
255
|
class RemoteCommand(IntEnum):
|
|
226
256
|
FORWARD = 0
|
|
227
257
|
LEFT = 2
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Data container classes for Q10 B01 devices.
|
|
2
|
+
|
|
3
|
+
Many of these classes use the `field(metadata={"dps": ...})` convention to map
|
|
4
|
+
dataclass fields to device Data Points (DPS). This metadata is utilized by the
|
|
5
|
+
`UpdatableTrait` helper in `roborock.devices.traits.b01.q10.common` to
|
|
6
|
+
automatically update objects from raw device responses.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from ..containers import RoborockBase
|
|
12
|
+
from .b01_q10_code_mappings import (
|
|
13
|
+
B01_Q10_DP,
|
|
14
|
+
YXAreaUnit,
|
|
15
|
+
YXBackType,
|
|
16
|
+
YXCarpetCleanType,
|
|
17
|
+
YXCleanLine,
|
|
18
|
+
YXCleanType,
|
|
19
|
+
YXDeviceCleanTask,
|
|
20
|
+
YXDeviceDustCollectionFrequency,
|
|
21
|
+
YXDeviceState,
|
|
22
|
+
YXFanLevel,
|
|
23
|
+
YXWaterLevel,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class dpCleanRecord(RoborockBase):
|
|
29
|
+
op: str
|
|
30
|
+
result: int
|
|
31
|
+
id: str
|
|
32
|
+
data: list
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class dpMultiMap(RoborockBase):
|
|
37
|
+
op: str
|
|
38
|
+
result: int
|
|
39
|
+
data: list
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class dpGetCarpet(RoborockBase):
|
|
44
|
+
op: str
|
|
45
|
+
result: int
|
|
46
|
+
data: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class dpSelfIdentifyingCarpet(RoborockBase):
|
|
51
|
+
op: str
|
|
52
|
+
result: int
|
|
53
|
+
data: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class dpNetInfo(RoborockBase):
|
|
58
|
+
wifi_name: str | None = None
|
|
59
|
+
# "ip_adress" intentionally mirrors the device's "ipAdress" key (sic).
|
|
60
|
+
ip_adress: str | None = None
|
|
61
|
+
mac: str | None = None
|
|
62
|
+
signal: int | None = None
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def ip_address(self) -> str | None:
|
|
66
|
+
"""Correctly-spelled alias for :attr:`ip_adress`."""
|
|
67
|
+
return self.ip_adress
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class dpNotDisturbExpand(RoborockBase):
|
|
72
|
+
disturb_dust_enable: int | None = None
|
|
73
|
+
disturb_light: int | None = None
|
|
74
|
+
disturb_resume_clean: int | None = None
|
|
75
|
+
disturb_voice: int | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class dpCurrentCleanRoomIds(RoborockBase):
|
|
80
|
+
room_id_list: list
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class dpVoiceVersion(RoborockBase):
|
|
85
|
+
version: int
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class dpTimeZone(RoborockBase):
|
|
90
|
+
time_zone_city: str | None = None
|
|
91
|
+
time_zone_sec: int | None = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class Q10Status(RoborockBase):
|
|
96
|
+
"""Core vacuum status for Q10 devices.
|
|
97
|
+
|
|
98
|
+
Fields are mapped to DPS values using metadata. Objects of this class can be
|
|
99
|
+
automatically updated using the `UpdatableTrait` helper. Settings that have
|
|
100
|
+
their own trait (volume, child lock, do-not-disturb, dust collection,
|
|
101
|
+
network info, consumables) live on those traits instead of here.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
clean_time: int | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_TIME})
|
|
105
|
+
clean_area: int | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_AREA})
|
|
106
|
+
battery: int | None = field(default=None, metadata={"dps": B01_Q10_DP.BATTERY})
|
|
107
|
+
status: YXDeviceState | None = field(default=None, metadata={"dps": B01_Q10_DP.STATUS})
|
|
108
|
+
fan_level: YXFanLevel | None = field(default=None, metadata={"dps": B01_Q10_DP.FAN_LEVEL})
|
|
109
|
+
water_level: YXWaterLevel | None = field(default=None, metadata={"dps": B01_Q10_DP.WATER_LEVEL})
|
|
110
|
+
clean_count: int | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_COUNT})
|
|
111
|
+
total_clean_area: int | None = field(default=None, metadata={"dps": B01_Q10_DP.TOTAL_CLEAN_AREA})
|
|
112
|
+
total_clean_count: int | None = field(default=None, metadata={"dps": B01_Q10_DP.TOTAL_CLEAN_COUNT})
|
|
113
|
+
total_clean_time: int | None = field(default=None, metadata={"dps": B01_Q10_DP.TOTAL_CLEAN_TIME})
|
|
114
|
+
clean_mode: YXCleanType | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_MODE})
|
|
115
|
+
clean_task_type: YXDeviceCleanTask | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_TASK_TYPE})
|
|
116
|
+
back_type: YXBackType | None = field(default=None, metadata={"dps": B01_Q10_DP.BACK_TYPE})
|
|
117
|
+
cleaning_progress: int | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_PROGRESS})
|
|
118
|
+
fault: int | None = field(default=None, metadata={"dps": B01_Q10_DP.FAULT})
|
|
119
|
+
|
|
120
|
+
# Additional state reported in the device's full status dump.
|
|
121
|
+
clean_line: YXCleanLine | None = field(default=None, metadata={"dps": B01_Q10_DP.CLEAN_LINE})
|
|
122
|
+
carpet_clean_type: YXCarpetCleanType | None = field(default=None, metadata={"dps": B01_Q10_DP.CARPET_CLEAN_TYPE})
|
|
123
|
+
area_unit: YXAreaUnit | None = field(default=None, metadata={"dps": B01_Q10_DP.AREA_UNIT})
|
|
124
|
+
auto_boost: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.AUTO_BOOST})
|
|
125
|
+
multi_map_switch: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.MULTI_MAP_SWITCH})
|
|
126
|
+
map_save_switch: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.MAP_SAVE_SWITCH})
|
|
127
|
+
recent_clean_record: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.RECENT_CLEAN_RECORD})
|
|
128
|
+
valley_point_charging: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.VALLEY_POINT_CHARGING})
|
|
129
|
+
line_laser_obstacle_avoidance: bool | None = field(
|
|
130
|
+
default=None, metadata={"dps": B01_Q10_DP.LINE_LASER_OBSTACLE_AVOIDANCE}
|
|
131
|
+
)
|
|
132
|
+
# Whether a mop module is attached, and whether "clean along floor direction" is on.
|
|
133
|
+
mop_state: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.MOP_STATE})
|
|
134
|
+
ground_clean: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.GROUND_CLEAN})
|
|
135
|
+
# True while an "add area" / re-clean (the app's draw-a-rectangle "re cleaning")
|
|
136
|
+
# request is in progress; pulses back to False once the robot has the area.
|
|
137
|
+
add_clean_state: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.ADD_CLEAN_STATE})
|
|
138
|
+
robot_country_code: str | None = field(default=None, metadata={"dps": B01_Q10_DP.ROBOT_COUNTRY_CODE})
|
|
139
|
+
time_zone: dpTimeZone | None = field(default=None, metadata={"dps": B01_Q10_DP.TIME_ZONE})
|
|
140
|
+
|
|
141
|
+
# TODO(#846): value mappings for these ints are not yet decoded (no app
|
|
142
|
+
# control found / internal / constant); keep as int until reverse-engineered.
|
|
143
|
+
breakpoint_clean: int | None = field(default=None, metadata={"dps": B01_Q10_DP.BREAKPOINT_CLEAN})
|
|
144
|
+
timer_type: int | None = field(default=None, metadata={"dps": B01_Q10_DP.TIMER_TYPE})
|
|
145
|
+
user_plan: int | None = field(default=None, metadata={"dps": B01_Q10_DP.USER_PLAN})
|
|
146
|
+
robot_type: int | None = field(default=None, metadata={"dps": B01_Q10_DP.ROBOT_TYPE})
|
|
147
|
+
|
|
148
|
+
# DEPRECATED: consumable/accessory remaining-life now lives on the
|
|
149
|
+
# ``Q10Consumable`` trait. These aliases are kept here for backwards
|
|
150
|
+
# compatibility and will be removed in a follow-up release. See PR #846.
|
|
151
|
+
main_brush_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.MAIN_BRUSH_LIFE})
|
|
152
|
+
side_brush_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.SIDE_BRUSH_LIFE})
|
|
153
|
+
filter_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.FILTER_LIFE})
|
|
154
|
+
sensor_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.SENSOR_LIFE})
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class SoundVolume(RoborockBase):
|
|
159
|
+
"""Speaker volume read-model (0-100)."""
|
|
160
|
+
|
|
161
|
+
volume: int | None = field(default=None, metadata={"dps": B01_Q10_DP.VOLUME})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass
|
|
165
|
+
class ChildLock(RoborockBase):
|
|
166
|
+
"""Child-lock read-model."""
|
|
167
|
+
|
|
168
|
+
child_lock: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.CHILD_LOCK})
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass
|
|
172
|
+
class DoNotDisturb(RoborockBase):
|
|
173
|
+
"""Do Not Disturb read-model."""
|
|
174
|
+
|
|
175
|
+
not_disturb: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.NOT_DISTURB})
|
|
176
|
+
not_disturb_expand: dpNotDisturbExpand | None = field(default=None, metadata={"dps": B01_Q10_DP.NOT_DISTURB_EXPAND})
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class DustCollection(RoborockBase):
|
|
181
|
+
"""Dock auto-empty (dust collection) read-model."""
|
|
182
|
+
|
|
183
|
+
dust_switch: bool | None = field(default=None, metadata={"dps": B01_Q10_DP.DUST_SWITCH})
|
|
184
|
+
dust_setting: YXDeviceDustCollectionFrequency | None = field(
|
|
185
|
+
default=None, metadata={"dps": B01_Q10_DP.DUST_SETTING}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class Q10Consumable(RoborockBase):
|
|
191
|
+
"""Consumable / accessory remaining-life read-model.
|
|
192
|
+
|
|
193
|
+
Named with a ``Q10`` prefix to avoid shadowing the v1 ``Consumable`` when both
|
|
194
|
+
are star-imported into the ``roborock.data`` namespace.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
main_brush_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.MAIN_BRUSH_LIFE})
|
|
198
|
+
side_brush_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.SIDE_BRUSH_LIFE})
|
|
199
|
+
filter_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.FILTER_LIFE})
|
|
200
|
+
sensor_life: int | None = field(default=None, metadata={"dps": B01_Q10_DP.SENSOR_LIFE})
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass
|
|
204
|
+
class Q10NetworkInfo(RoborockBase):
|
|
205
|
+
"""Network information read-model.
|
|
206
|
+
|
|
207
|
+
Named with a ``Q10`` prefix to avoid shadowing the v1 ``NetworkInfo`` when both
|
|
208
|
+
are star-imported into the ``roborock.data`` namespace.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
net_info: dpNetInfo | None = field(default=None, metadata={"dps": B01_Q10_DP.NET_INFO})
|
|
@@ -76,11 +76,17 @@ class RoborockModeEnum(StrEnum):
|
|
|
76
76
|
|
|
77
77
|
@classmethod
|
|
78
78
|
def from_code_optional(cls, code: int) -> Self | None:
|
|
79
|
-
"""Gracefully return None if the code does not exist.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
"""Gracefully return None if the code does not exist.
|
|
80
|
+
|
|
81
|
+
This is the silent counterpart to :meth:`from_code`: callers use it when
|
|
82
|
+
an unknown code is expected and tolerable (e.g. decoding a device push
|
|
83
|
+
that may include data points this library does not model yet), so it must
|
|
84
|
+
not emit the "not a valid code" warning that ``from_code`` logs.
|
|
85
|
+
"""
|
|
86
|
+
for member in cls:
|
|
87
|
+
if member.code == code:
|
|
88
|
+
return member
|
|
89
|
+
return None
|
|
84
90
|
|
|
85
91
|
@classmethod
|
|
86
92
|
def from_value(cls, value: str) -> Self:
|
|
@@ -115,12 +121,14 @@ class RoborockModeEnum(StrEnum):
|
|
|
115
121
|
return cls.from_value(str(value))
|
|
116
122
|
except ValueError:
|
|
117
123
|
pass
|
|
118
|
-
# Try integer code lookup (e.g. "11")
|
|
124
|
+
# Try integer code lookup (e.g. "11"). Use the silent optional variant so
|
|
125
|
+
# a value that is neither a name, a DP string, nor a known code resolves
|
|
126
|
+
# to None without logging a spurious "not a valid code" warning.
|
|
119
127
|
try:
|
|
120
|
-
|
|
128
|
+
int_code = int(value)
|
|
121
129
|
except (ValueError, TypeError):
|
|
122
|
-
|
|
123
|
-
return
|
|
130
|
+
return None
|
|
131
|
+
return cls.from_code_optional(int_code)
|
|
124
132
|
|
|
125
133
|
@classmethod
|
|
126
134
|
def keys(cls) -> list[str]:
|