python-roborock 2.59.0__tar.gz → 2.61.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-2.59.0 → python_roborock-2.61.0}/PKG-INFO +1 -1
- {python_roborock-2.59.0 → python_roborock-2.61.0}/pyproject.toml +3 -3
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/cli.py +28 -1
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/device_features.py +23 -1
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/cache.py +4 -1
- python_roborock-2.61.0/roborock/devices/traits/v1/__init__.py +260 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/child_lock.py +6 -1
- python_roborock-2.61.0/roborock/devices/traits/v1/clean_record.py +69 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/common.py +19 -1
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/do_not_disturb.py +6 -1
- python_roborock-2.61.0/roborock/devices/traits/v1/dust_collection_mode.py +13 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/flow_led_status.py +6 -1
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/led_status.py +6 -1
- python_roborock-2.61.0/roborock/devices/traits/v1/smart_wash_params.py +13 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/valley_electricity_timer.py +5 -0
- python_roborock-2.61.0/roborock/devices/traits/v1/wash_towel_mode.py +13 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/v1_channel.py +12 -4
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_1_apis/roborock_client_v1.py +2 -11
- python_roborock-2.59.0/roborock/devices/traits/v1/__init__.py +0 -163
- {python_roborock-2.59.0 → python_roborock-2.61.0}/.gitignore +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/LICENSE +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/README.md +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/api.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/b01_containers.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/callbacks.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/clean_modes.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/const.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/containers.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/channel.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/device.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/exceptions.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/map/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/protocol.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/py.typed +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/util.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.61.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/humbertogontijo/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 = "2.
|
|
3
|
+
version = "2.61.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"
|
|
@@ -66,8 +66,8 @@ build-backend = "hatchling.build"
|
|
|
66
66
|
|
|
67
67
|
[tool.semantic_release]
|
|
68
68
|
branch = "main"
|
|
69
|
-
version_toml = ["pyproject.toml:
|
|
70
|
-
build_command = "pip install
|
|
69
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
70
|
+
build_command = "pip install uv && uv build"
|
|
71
71
|
|
|
72
72
|
[tool.semantic_release.commit_parser_options]
|
|
73
73
|
allowed_tags = [
|
|
@@ -119,6 +119,7 @@ class ConnectionCache(RoborockBase):
|
|
|
119
119
|
home_data: HomeData | None = None
|
|
120
120
|
network_info: dict[str, NetworkInfo] | None = None
|
|
121
121
|
home_cache: dict[int, CombinedMapInfo] | None = None
|
|
122
|
+
trait_data: dict[str, Any] | None = None
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
class DeviceConnectionManager:
|
|
@@ -267,6 +268,7 @@ class RoborockContext(Cache):
|
|
|
267
268
|
home_data=connection_cache.home_data,
|
|
268
269
|
network_info=connection_cache.network_info or {},
|
|
269
270
|
home_cache=connection_cache.home_cache,
|
|
271
|
+
trait_data=connection_cache.trait_data or {},
|
|
270
272
|
)
|
|
271
273
|
|
|
272
274
|
async def set(self, value: CacheData) -> None:
|
|
@@ -276,6 +278,7 @@ class RoborockContext(Cache):
|
|
|
276
278
|
connection_cache.home_data = value.home_data
|
|
277
279
|
connection_cache.network_info = value.network_info
|
|
278
280
|
connection_cache.home_cache = value.home_cache
|
|
281
|
+
connection_cache.trait_data = value.trait_data
|
|
279
282
|
self.update(connection_cache)
|
|
280
283
|
|
|
281
284
|
|
|
@@ -401,9 +404,11 @@ async def _v1_trait(context: RoborockContext, device_id: str, display_func: Call
|
|
|
401
404
|
device_manager = await context.get_device_manager()
|
|
402
405
|
device = await device_manager.get_device(device_id)
|
|
403
406
|
if device.v1_properties is None:
|
|
404
|
-
raise
|
|
407
|
+
raise RoborockUnsupportedFeature(f"Device {device.name} does not support V1 protocol")
|
|
405
408
|
await device.v1_properties.discover_features()
|
|
406
409
|
trait = display_func(device.v1_properties)
|
|
410
|
+
if trait is None:
|
|
411
|
+
raise RoborockUnsupportedFeature("Trait not supported by device")
|
|
407
412
|
await trait.refresh()
|
|
408
413
|
return trait
|
|
409
414
|
|
|
@@ -440,6 +445,26 @@ async def clean_summary(ctx, device_id: str):
|
|
|
440
445
|
await _display_v1_trait(context, device_id, lambda v1: v1.clean_summary)
|
|
441
446
|
|
|
442
447
|
|
|
448
|
+
@session.command()
|
|
449
|
+
@click.option("--device_id", required=True)
|
|
450
|
+
@click.pass_context
|
|
451
|
+
@async_command
|
|
452
|
+
async def clean_record(ctx, device_id: str):
|
|
453
|
+
"""Get device last clean record."""
|
|
454
|
+
context: RoborockContext = ctx.obj
|
|
455
|
+
await _display_v1_trait(context, device_id, lambda v1: v1.clean_record)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@session.command()
|
|
459
|
+
@click.option("--device_id", required=True)
|
|
460
|
+
@click.pass_context
|
|
461
|
+
@async_command
|
|
462
|
+
async def dock_summary(ctx, device_id: str):
|
|
463
|
+
"""Get device dock summary."""
|
|
464
|
+
context: RoborockContext = ctx.obj
|
|
465
|
+
await _display_v1_trait(context, device_id, lambda v1: v1.dock_summary)
|
|
466
|
+
|
|
467
|
+
|
|
443
468
|
@session.command()
|
|
444
469
|
@click.option("--device_id", required=True)
|
|
445
470
|
@click.pass_context
|
|
@@ -938,6 +963,8 @@ cli.add_command(session)
|
|
|
938
963
|
cli.add_command(get_device_info)
|
|
939
964
|
cli.add_command(update_docs)
|
|
940
965
|
cli.add_command(clean_summary)
|
|
966
|
+
cli.add_command(clean_record)
|
|
967
|
+
cli.add_command(dock_summary)
|
|
941
968
|
cli.add_command(volume)
|
|
942
969
|
cli.add_command(set_volume)
|
|
943
970
|
cli.add_command(maps)
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field, fields
|
|
|
4
4
|
from enum import IntEnum, StrEnum
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from .code_mappings import RoborockProductNickname
|
|
7
|
+
from .code_mappings import RoborockDockTypeCode, RoborockProductNickname
|
|
8
8
|
from .containers import RoborockBase
|
|
9
9
|
|
|
10
10
|
|
|
@@ -630,3 +630,25 @@ class DeviceFeatures(RoborockBase):
|
|
|
630
630
|
def get_supported_features(self) -> list[str]:
|
|
631
631
|
"""Returns a list of supported features (Primarily used for logging purposes)."""
|
|
632
632
|
return [k for k, v in vars(self).items() if v]
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
WASH_N_FILL_DOCK_TYPES = [
|
|
636
|
+
RoborockDockTypeCode.empty_wash_fill_dock,
|
|
637
|
+
RoborockDockTypeCode.s8_dock,
|
|
638
|
+
RoborockDockTypeCode.p10_dock,
|
|
639
|
+
RoborockDockTypeCode.p10_pro_dock,
|
|
640
|
+
RoborockDockTypeCode.s8_maxv_ultra_dock,
|
|
641
|
+
RoborockDockTypeCode.qrevo_s_dock,
|
|
642
|
+
RoborockDockTypeCode.saros_r10_dock,
|
|
643
|
+
RoborockDockTypeCode.qrevo_curv_dock,
|
|
644
|
+
]
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def is_wash_n_fill_dock(dock_type: RoborockDockTypeCode) -> bool:
|
|
648
|
+
"""Check if the dock type is a wash and fill dock."""
|
|
649
|
+
return dock_type in WASH_N_FILL_DOCK_TYPES
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def is_valid_dock(dock_type: RoborockDockTypeCode) -> bool:
|
|
653
|
+
"""Check if device supports a dock."""
|
|
654
|
+
return dock_type != RoborockDockTypeCode.no_dock
|
|
@@ -6,7 +6,7 @@ this interface to provide their own caching mechanism.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
from typing import Protocol
|
|
9
|
+
from typing import Any, Protocol
|
|
10
10
|
|
|
11
11
|
from roborock.containers import CombinedMapInfo, HomeData, NetworkInfo
|
|
12
12
|
from roborock.device_features import DeviceFeatures
|
|
@@ -28,6 +28,9 @@ class CacheData:
|
|
|
28
28
|
device_features: DeviceFeatures | None = None
|
|
29
29
|
"""Device features information."""
|
|
30
30
|
|
|
31
|
+
trait_data: dict[str, Any] | None = None
|
|
32
|
+
"""Trait-specific cached data used internally for caching device features."""
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
class Cache(Protocol):
|
|
33
36
|
"""Protocol for a cache that can store and retrieve values."""
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Create traits for V1 devices.
|
|
2
|
+
|
|
3
|
+
Traits are modular components that encapsulate specific features of a Roborock
|
|
4
|
+
device. This module provides a factory function to create and initialize the
|
|
5
|
+
appropriate traits for V1 devices based on their capabilities. They can also
|
|
6
|
+
be considered groups of commands and parsing logic for that command.
|
|
7
|
+
|
|
8
|
+
Traits have a `refresh()` method that can be called to update their state
|
|
9
|
+
from the device. Some traits may also provide additional methods for modifying
|
|
10
|
+
the device state.
|
|
11
|
+
|
|
12
|
+
The most common pattern for a trait is to subclass `V1TraitMixin` and a `RoborockBase`
|
|
13
|
+
dataclass, and define a `command` class variable that specifies the `RoborockCommand`
|
|
14
|
+
used to fetch the trait data from the device. See `common.py` for more details
|
|
15
|
+
on common patterns used across traits.
|
|
16
|
+
|
|
17
|
+
There are some additional decorators in `common.py` that can be used to specify which
|
|
18
|
+
RPC channel to use for the trait (standard, MQTT/cloud, or map-specific).
|
|
19
|
+
|
|
20
|
+
- `@common.mqtt_rpc_channel` - Use the MQTT RPC channel for this trait.
|
|
21
|
+
- `@common.map_rpc_channel` - Use the map RPC channel for this trait.
|
|
22
|
+
|
|
23
|
+
There are also some attributes that specify device feature dependencies for
|
|
24
|
+
optional traits:
|
|
25
|
+
|
|
26
|
+
- `requires_feature` - The string name of the device feature that must be supported
|
|
27
|
+
for this trait to be enabled. See `DeviceFeaturesTrait` for a list of
|
|
28
|
+
available features.
|
|
29
|
+
- `requires_dock_type` - If set, this is a function that accepts a `RoborockDockTypeCode`
|
|
30
|
+
and returns a boolean indicating whether the trait is supported for that dock type.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
from dataclasses import dataclass, field, fields
|
|
35
|
+
from typing import Any, get_args
|
|
36
|
+
|
|
37
|
+
from roborock.code_mappings import RoborockDockTypeCode
|
|
38
|
+
from roborock.containers import HomeData, HomeDataProduct
|
|
39
|
+
from roborock.devices.cache import Cache
|
|
40
|
+
from roborock.devices.traits import Trait
|
|
41
|
+
from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
42
|
+
from roborock.map.map_parser import MapParserConfig
|
|
43
|
+
|
|
44
|
+
from .child_lock import ChildLockTrait
|
|
45
|
+
from .clean_record import CleanRecordTrait
|
|
46
|
+
from .clean_summary import CleanSummaryTrait
|
|
47
|
+
from .command import CommandTrait
|
|
48
|
+
from .common import V1TraitMixin
|
|
49
|
+
from .consumeable import ConsumableTrait
|
|
50
|
+
from .device_features import DeviceFeaturesTrait
|
|
51
|
+
from .do_not_disturb import DoNotDisturbTrait
|
|
52
|
+
from .dust_collection_mode import DustCollectionModeTrait
|
|
53
|
+
from .flow_led_status import FlowLedStatusTrait
|
|
54
|
+
from .home import HomeTrait
|
|
55
|
+
from .led_status import LedStatusTrait
|
|
56
|
+
from .map_content import MapContentTrait
|
|
57
|
+
from .maps import MapsTrait
|
|
58
|
+
from .rooms import RoomsTrait
|
|
59
|
+
from .smart_wash_params import SmartWashParamsTrait
|
|
60
|
+
from .status import StatusTrait
|
|
61
|
+
from .valley_electricity_timer import ValleyElectricityTimerTrait
|
|
62
|
+
from .volume import SoundVolumeTrait
|
|
63
|
+
from .wash_towel_mode import WashTowelModeTrait
|
|
64
|
+
|
|
65
|
+
_LOGGER = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"create",
|
|
69
|
+
"PropertiesApi",
|
|
70
|
+
"StatusTrait",
|
|
71
|
+
"DoNotDisturbTrait",
|
|
72
|
+
"CleanSummaryTrait",
|
|
73
|
+
"CleanRecordTrait",
|
|
74
|
+
"SoundVolumeTrait",
|
|
75
|
+
"MapsTrait",
|
|
76
|
+
"MapContentTrait",
|
|
77
|
+
"ConsumableTrait",
|
|
78
|
+
"HomeTrait",
|
|
79
|
+
"DeviceFeaturesTrait",
|
|
80
|
+
"CommandTrait",
|
|
81
|
+
"ChildLockTrait",
|
|
82
|
+
"FlowLedStatusTrait",
|
|
83
|
+
"LedStatusTrait",
|
|
84
|
+
"ValleyElectricityTimerTrait",
|
|
85
|
+
"DustCollectionModeTrait",
|
|
86
|
+
"WashTowelModeTrait",
|
|
87
|
+
"SmartWashParamsTrait",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class PropertiesApi(Trait):
|
|
93
|
+
"""Common properties for V1 devices.
|
|
94
|
+
|
|
95
|
+
This class holds all the traits that are common across all V1 devices.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# All v1 devices have these traits
|
|
99
|
+
status: StatusTrait
|
|
100
|
+
command: CommandTrait
|
|
101
|
+
dnd: DoNotDisturbTrait
|
|
102
|
+
clean_summary: CleanSummaryTrait
|
|
103
|
+
clean_record: CleanRecordTrait
|
|
104
|
+
sound_volume: SoundVolumeTrait
|
|
105
|
+
rooms: RoomsTrait
|
|
106
|
+
maps: MapsTrait
|
|
107
|
+
map_content: MapContentTrait
|
|
108
|
+
consumables: ConsumableTrait
|
|
109
|
+
home: HomeTrait
|
|
110
|
+
device_features: DeviceFeaturesTrait
|
|
111
|
+
|
|
112
|
+
# Optional features that may not be supported on all devices
|
|
113
|
+
child_lock: ChildLockTrait | None = None
|
|
114
|
+
led_status: LedStatusTrait | None = None
|
|
115
|
+
flow_led_status: FlowLedStatusTrait | None = None
|
|
116
|
+
valley_electricity_timer: ValleyElectricityTimerTrait | None = None
|
|
117
|
+
dust_collection_mode: DustCollectionModeTrait | None = None
|
|
118
|
+
wash_towel_mode: WashTowelModeTrait | None = None
|
|
119
|
+
smart_wash_params: SmartWashParamsTrait | None = None
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
product: HomeDataProduct,
|
|
124
|
+
home_data: HomeData,
|
|
125
|
+
rpc_channel: V1RpcChannel,
|
|
126
|
+
mqtt_rpc_channel: V1RpcChannel,
|
|
127
|
+
map_rpc_channel: V1RpcChannel,
|
|
128
|
+
cache: Cache,
|
|
129
|
+
map_parser_config: MapParserConfig | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Initialize the V1TraitProps."""
|
|
132
|
+
self._rpc_channel = rpc_channel
|
|
133
|
+
self._mqtt_rpc_channel = mqtt_rpc_channel
|
|
134
|
+
self._map_rpc_channel = map_rpc_channel
|
|
135
|
+
self._cache = cache
|
|
136
|
+
|
|
137
|
+
self.status = StatusTrait(product)
|
|
138
|
+
self.clean_summary = CleanSummaryTrait()
|
|
139
|
+
self.clean_record = CleanRecordTrait(self.clean_summary)
|
|
140
|
+
self.consumables = ConsumableTrait()
|
|
141
|
+
self.rooms = RoomsTrait(home_data)
|
|
142
|
+
self.maps = MapsTrait(self.status)
|
|
143
|
+
self.map_content = MapContentTrait(map_parser_config)
|
|
144
|
+
self.home = HomeTrait(self.status, self.maps, self.rooms, cache)
|
|
145
|
+
self.device_features = DeviceFeaturesTrait(product.product_nickname, cache)
|
|
146
|
+
|
|
147
|
+
# Dynamically create any traits that need to be populated
|
|
148
|
+
for item in fields(self):
|
|
149
|
+
if (trait := getattr(self, item.name, None)) is None:
|
|
150
|
+
# We exclude optional features and them via discover_features
|
|
151
|
+
if (union_args := get_args(item.type)) is None or len(union_args) > 0:
|
|
152
|
+
continue
|
|
153
|
+
_LOGGER.debug("Trait '%s' is supported, initializing", item.name)
|
|
154
|
+
trait = item.type()
|
|
155
|
+
setattr(self, item.name, trait)
|
|
156
|
+
# This is a hack to allow setting the rpc_channel on all traits. This is
|
|
157
|
+
# used so we can preserve the dataclass behavior when the values in the
|
|
158
|
+
# traits are updated, but still want to allow them to have a reference
|
|
159
|
+
# to the rpc channel for sending commands.
|
|
160
|
+
trait._rpc_channel = self._get_rpc_channel(trait)
|
|
161
|
+
|
|
162
|
+
def _get_rpc_channel(self, trait: V1TraitMixin) -> V1RpcChannel:
|
|
163
|
+
# The decorator `@common.mqtt_rpc_channel` means that the trait needs
|
|
164
|
+
# to use the mqtt_rpc_channel (cloud only) instead of the rpc_channel (adaptive)
|
|
165
|
+
if hasattr(trait, "mqtt_rpc_channel"):
|
|
166
|
+
return self._mqtt_rpc_channel
|
|
167
|
+
elif hasattr(trait, "map_rpc_channel"):
|
|
168
|
+
return self._map_rpc_channel
|
|
169
|
+
else:
|
|
170
|
+
return self._rpc_channel
|
|
171
|
+
|
|
172
|
+
async def discover_features(self) -> None:
|
|
173
|
+
"""Populate any supported traits that were not initialized in __init__."""
|
|
174
|
+
_LOGGER.debug("Starting optional trait discovery")
|
|
175
|
+
await self.device_features.refresh()
|
|
176
|
+
# Dock type also acts like a device feature for some traits.
|
|
177
|
+
dock_type = await self._dock_type()
|
|
178
|
+
|
|
179
|
+
# Dynamically create any traits that need to be populated
|
|
180
|
+
for item in fields(self):
|
|
181
|
+
if (trait := getattr(self, item.name, None)) is not None:
|
|
182
|
+
continue
|
|
183
|
+
if (union_args := get_args(item.type)) is None:
|
|
184
|
+
raise ValueError(f"Unexpected non-union type for trait {item.name}: {item.type}")
|
|
185
|
+
if len(union_args) != 2 or type(None) not in union_args:
|
|
186
|
+
raise ValueError(f"Unexpected non-optional type for trait {item.name}: {item.type}")
|
|
187
|
+
|
|
188
|
+
# Union args may not be in declared order
|
|
189
|
+
item_type = union_args[0] if union_args[1] is type(None) else union_args[1]
|
|
190
|
+
if not self._is_supported(item_type, item.name, dock_type):
|
|
191
|
+
_LOGGER.debug("Trait '%s' not supported, skipping", item.name)
|
|
192
|
+
continue
|
|
193
|
+
_LOGGER.debug("Trait '%s' is supported, initializing", item.name)
|
|
194
|
+
trait = item_type()
|
|
195
|
+
setattr(self, item.name, trait)
|
|
196
|
+
trait._rpc_channel = self._get_rpc_channel(trait)
|
|
197
|
+
|
|
198
|
+
def _is_supported(self, trait_type: type[V1TraitMixin], name: str, dock_type: RoborockDockTypeCode) -> bool:
|
|
199
|
+
"""Check if a trait is supported by the device."""
|
|
200
|
+
|
|
201
|
+
if (requires_dock_type := getattr(trait_type, "requires_dock_type", None)) is not None:
|
|
202
|
+
return requires_dock_type(dock_type)
|
|
203
|
+
|
|
204
|
+
if (feature_name := getattr(trait_type, "requires_feature", None)) is None:
|
|
205
|
+
_LOGGER.debug("Optional trait missing 'requires_feature' attribute %s, skipping", name)
|
|
206
|
+
return False
|
|
207
|
+
if (is_supported := getattr(self.device_features, feature_name)) is None:
|
|
208
|
+
raise ValueError(f"Device feature '{feature_name}' on trait '{name}' is unknown")
|
|
209
|
+
return is_supported
|
|
210
|
+
|
|
211
|
+
async def _dock_type(self) -> RoborockDockTypeCode:
|
|
212
|
+
"""Get the dock type from the status trait or cache."""
|
|
213
|
+
dock_type = await self._get_cached_trait_data("dock_type")
|
|
214
|
+
if dock_type is not None:
|
|
215
|
+
_LOGGER.debug("Using cached dock type: %s", dock_type)
|
|
216
|
+
try:
|
|
217
|
+
return RoborockDockTypeCode(dock_type)
|
|
218
|
+
except ValueError:
|
|
219
|
+
_LOGGER.debug("Cached dock type %s is invalid, refreshing", dock_type)
|
|
220
|
+
|
|
221
|
+
_LOGGER.debug("Starting dock type discovery")
|
|
222
|
+
await self.status.refresh()
|
|
223
|
+
_LOGGER.debug("Fetched dock type: %s", self.status.dock_type)
|
|
224
|
+
if self.status.dock_type is None:
|
|
225
|
+
# Explicitly set so we reuse cached value next type
|
|
226
|
+
dock_type = RoborockDockTypeCode.no_dock
|
|
227
|
+
else:
|
|
228
|
+
dock_type = self.status.dock_type
|
|
229
|
+
await self._set_cached_trait_data("dock_type", dock_type)
|
|
230
|
+
return dock_type
|
|
231
|
+
|
|
232
|
+
async def _get_cached_trait_data(self, name: str) -> Any:
|
|
233
|
+
"""Get the dock type from the status trait or cache."""
|
|
234
|
+
cache_data = await self._cache.get()
|
|
235
|
+
if cache_data.trait_data is None:
|
|
236
|
+
cache_data.trait_data = {}
|
|
237
|
+
_LOGGER.debug("Cached trait data: %s", cache_data.trait_data)
|
|
238
|
+
return cache_data.trait_data.get(name)
|
|
239
|
+
|
|
240
|
+
async def _set_cached_trait_data(self, name: str, value: Any) -> None:
|
|
241
|
+
"""Set trait-specific cached data."""
|
|
242
|
+
cache_data = await self._cache.get()
|
|
243
|
+
if cache_data.trait_data is None:
|
|
244
|
+
cache_data.trait_data = {}
|
|
245
|
+
cache_data.trait_data[name] = value
|
|
246
|
+
_LOGGER.debug("Updating cached trait data: %s", cache_data.trait_data)
|
|
247
|
+
await self._cache.set(cache_data)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def create(
|
|
251
|
+
product: HomeDataProduct,
|
|
252
|
+
home_data: HomeData,
|
|
253
|
+
rpc_channel: V1RpcChannel,
|
|
254
|
+
mqtt_rpc_channel: V1RpcChannel,
|
|
255
|
+
map_rpc_channel: V1RpcChannel,
|
|
256
|
+
cache: Cache,
|
|
257
|
+
map_parser_config: MapParserConfig | None = None,
|
|
258
|
+
) -> PropertiesApi:
|
|
259
|
+
"""Create traits for V1 devices."""
|
|
260
|
+
return PropertiesApi(product, home_data, rpc_channel, mqtt_rpc_channel, map_rpc_channel, cache, map_parser_config)
|
|
@@ -5,12 +5,17 @@ from roborock.roborock_typing import RoborockCommand
|
|
|
5
5
|
_STATUS_PARAM = "lock_status"
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class ChildLockTrait(ChildLockStatus, common.V1TraitMixin):
|
|
8
|
+
class ChildLockTrait(ChildLockStatus, common.V1TraitMixin, common.RoborockSwitchBase):
|
|
9
9
|
"""Trait for controlling the child lock of a Roborock device."""
|
|
10
10
|
|
|
11
11
|
command = RoborockCommand.GET_CHILD_LOCK_STATUS
|
|
12
12
|
requires_feature = "is_set_child_supported"
|
|
13
13
|
|
|
14
|
+
@property
|
|
15
|
+
def is_on(self) -> bool:
|
|
16
|
+
"""Return whether the child lock is enabled."""
|
|
17
|
+
return self.lock_status == 1
|
|
18
|
+
|
|
14
19
|
async def enable(self) -> None:
|
|
15
20
|
"""Enable the child lock."""
|
|
16
21
|
await self.rpc_channel.send_command(RoborockCommand.SET_CHILD_LOCK_STATUS, params={_STATUS_PARAM: 1})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Trait for getting the last clean record."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
from roborock.containers import CleanRecord
|
|
7
|
+
from roborock.devices.traits.v1 import common
|
|
8
|
+
from roborock.roborock_typing import RoborockCommand
|
|
9
|
+
from roborock.util import unpack_list
|
|
10
|
+
|
|
11
|
+
from .clean_summary import CleanSummaryTrait
|
|
12
|
+
|
|
13
|
+
_LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CleanRecordTrait(CleanRecord, common.V1TraitMixin):
|
|
17
|
+
"""Trait for getting the last clean record."""
|
|
18
|
+
|
|
19
|
+
command = RoborockCommand.GET_CLEAN_RECORD
|
|
20
|
+
|
|
21
|
+
def __init__(self, clean_summary_trait: CleanSummaryTrait) -> None:
|
|
22
|
+
"""Initialize the clean record trait."""
|
|
23
|
+
super().__init__()
|
|
24
|
+
self._clean_summary_trait = clean_summary_trait
|
|
25
|
+
|
|
26
|
+
async def refresh(self) -> Self:
|
|
27
|
+
"""Get the last clean record.
|
|
28
|
+
|
|
29
|
+
Assumes that the clean summary has already been fetched.
|
|
30
|
+
"""
|
|
31
|
+
if not self._clean_summary_trait.records:
|
|
32
|
+
_LOGGER.debug("No clean records available in clean summary.")
|
|
33
|
+
return self
|
|
34
|
+
last_record_id = self._clean_summary_trait.records[-1]
|
|
35
|
+
response = await self.rpc_channel.send_command(self.command, params=[last_record_id])
|
|
36
|
+
new_self = self._parse_response(response)
|
|
37
|
+
self._update_trait_values(new_self)
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _parse_type_response(cls, response: common.V1ResponseData) -> CleanRecord:
|
|
42
|
+
"""Parse the response from the device into a CleanRecord."""
|
|
43
|
+
if isinstance(response, dict):
|
|
44
|
+
return CleanRecord.from_dict(response)
|
|
45
|
+
if isinstance(response, list):
|
|
46
|
+
if isinstance(response[-1], dict):
|
|
47
|
+
records = [CleanRecord.from_dict(rec) for rec in response]
|
|
48
|
+
final_record = records[-1]
|
|
49
|
+
try:
|
|
50
|
+
# This code is semi-presumptions - so it is put in a try finally to be safe.
|
|
51
|
+
final_record.begin = records[0].begin
|
|
52
|
+
final_record.begin_datetime = records[0].begin_datetime
|
|
53
|
+
final_record.start_type = records[0].start_type
|
|
54
|
+
for rec in records[0:-1]:
|
|
55
|
+
final_record.duration = (final_record.duration or 0) + (rec.duration or 0)
|
|
56
|
+
final_record.area = (final_record.area or 0) + (rec.area or 0)
|
|
57
|
+
final_record.avoid_count = (final_record.avoid_count or 0) + (rec.avoid_count or 0)
|
|
58
|
+
final_record.wash_count = (final_record.wash_count or 0) + (rec.wash_count or 0)
|
|
59
|
+
final_record.square_meter_area = (final_record.square_meter_area or 0) + (
|
|
60
|
+
rec.square_meter_area or 0
|
|
61
|
+
)
|
|
62
|
+
return final_record
|
|
63
|
+
except Exception:
|
|
64
|
+
# Return final record when an exception occurred
|
|
65
|
+
return final_record
|
|
66
|
+
# There are still a few unknown variables in this.
|
|
67
|
+
begin, end, duration, area = unpack_list(response, 4)
|
|
68
|
+
return CleanRecord(begin=begin, end=end, duration=duration, area=area)
|
|
69
|
+
raise ValueError(f"Unexpected clean record format: {response!r}")
|
|
@@ -4,7 +4,7 @@ This is an internal library and should not be used directly by consumers.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from abc import ABC
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
8
|
from dataclasses import dataclass, fields
|
|
9
9
|
from typing import ClassVar, Self
|
|
10
10
|
|
|
@@ -82,6 +82,7 @@ class V1TraitMixin(ABC):
|
|
|
82
82
|
new_data = self._parse_response(response)
|
|
83
83
|
if not isinstance(new_data, RoborockBase):
|
|
84
84
|
raise ValueError(f"Internal error, unexpected response type: {new_data!r}")
|
|
85
|
+
_LOGGER.debug("Refreshed %s: %s", self.__class__.__name__, new_data)
|
|
85
86
|
self._update_trait_values(new_data)
|
|
86
87
|
return self
|
|
87
88
|
|
|
@@ -124,6 +125,23 @@ class RoborockValueBase(V1TraitMixin, RoborockBase):
|
|
|
124
125
|
return cls(**{value_field: response})
|
|
125
126
|
|
|
126
127
|
|
|
128
|
+
class RoborockSwitchBase(ABC):
|
|
129
|
+
"""Base class for traits that represent a boolean switch."""
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
@abstractmethod
|
|
133
|
+
def is_on(self) -> bool:
|
|
134
|
+
"""Return whether the switch is on."""
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
async def enable(self) -> None:
|
|
138
|
+
"""Enable the switch."""
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
async def disable(self) -> None:
|
|
142
|
+
"""Disable the switch."""
|
|
143
|
+
|
|
144
|
+
|
|
127
145
|
def mqtt_rpc_channel(cls):
|
|
128
146
|
"""Decorator to mark a function as cloud only.
|
|
129
147
|
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
@@ -5,11 +5,16 @@ from roborock.roborock_typing import RoborockCommand
|
|
|
5
5
|
_ENABLED_PARAM = "enabled"
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class DoNotDisturbTrait(DnDTimer, common.V1TraitMixin):
|
|
8
|
+
class DoNotDisturbTrait(DnDTimer, common.V1TraitMixin, common.RoborockSwitchBase):
|
|
9
9
|
"""Trait for managing Do Not Disturb (DND) settings on Roborock devices."""
|
|
10
10
|
|
|
11
11
|
command = RoborockCommand.GET_DND_TIMER
|
|
12
12
|
|
|
13
|
+
@property
|
|
14
|
+
def is_on(self) -> bool:
|
|
15
|
+
"""Return whether the Do Not Disturb (DND) timer is enabled."""
|
|
16
|
+
return self.enabled == 1
|
|
17
|
+
|
|
13
18
|
async def set_dnd_timer(self, dnd_timer: DnDTimer) -> None:
|
|
14
19
|
"""Set the Do Not Disturb (DND) timer settings of the device."""
|
|
15
20
|
await self.rpc_channel.send_command(RoborockCommand.SET_DND_TIMER, params=dnd_timer.as_dict())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Trait for dust collection mode."""
|
|
2
|
+
|
|
3
|
+
from roborock.containers import DustCollectionMode
|
|
4
|
+
from roborock.device_features import is_valid_dock
|
|
5
|
+
from roborock.devices.traits.v1 import common
|
|
6
|
+
from roborock.roborock_typing import RoborockCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DustCollectionModeTrait(DustCollectionMode, common.V1TraitMixin):
|
|
10
|
+
"""Trait for dust collection mode."""
|
|
11
|
+
|
|
12
|
+
command = RoborockCommand.GET_DUST_COLLECTION_MODE
|
|
13
|
+
requires_dock_type = is_valid_dock
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/flow_led_status.py
RENAMED
|
@@ -5,12 +5,17 @@ from roborock.roborock_typing import RoborockCommand
|
|
|
5
5
|
_STATUS_PARAM = "status"
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class FlowLedStatusTrait(FlowLedStatus, common.V1TraitMixin):
|
|
8
|
+
class FlowLedStatusTrait(FlowLedStatus, common.V1TraitMixin, common.RoborockSwitchBase):
|
|
9
9
|
"""Trait for controlling the Flow LED status of a Roborock device."""
|
|
10
10
|
|
|
11
11
|
command = RoborockCommand.GET_FLOW_LED_STATUS
|
|
12
12
|
requires_feature = "is_flow_led_setting_supported"
|
|
13
13
|
|
|
14
|
+
@property
|
|
15
|
+
def is_on(self) -> bool:
|
|
16
|
+
"""Return whether the Flow LED status is enabled."""
|
|
17
|
+
return self.status == 1
|
|
18
|
+
|
|
14
19
|
async def enable(self) -> None:
|
|
15
20
|
"""Enable the Flow LED status."""
|
|
16
21
|
await self.rpc_channel.send_command(RoborockCommand.SET_FLOW_LED_STATUS, params={_STATUS_PARAM: 1})
|
|
@@ -5,12 +5,17 @@ from roborock.roborock_typing import RoborockCommand
|
|
|
5
5
|
from .common import V1ResponseData
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class LedStatusTrait(LedStatus, common.V1TraitMixin):
|
|
8
|
+
class LedStatusTrait(LedStatus, common.V1TraitMixin, common.RoborockSwitchBase):
|
|
9
9
|
"""Trait for controlling the LED status of a Roborock device."""
|
|
10
10
|
|
|
11
11
|
command = RoborockCommand.GET_LED_STATUS
|
|
12
12
|
requires_feature = "is_led_status_switch_supported"
|
|
13
13
|
|
|
14
|
+
@property
|
|
15
|
+
def is_on(self) -> bool:
|
|
16
|
+
"""Return whether the LED status is enabled."""
|
|
17
|
+
return self.status == 1
|
|
18
|
+
|
|
14
19
|
async def enable(self) -> None:
|
|
15
20
|
"""Enable the LED status."""
|
|
16
21
|
await self.rpc_channel.send_command(RoborockCommand.SET_LED_STATUS, params=[1])
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Trait for smart wash parameters."""
|
|
2
|
+
|
|
3
|
+
from roborock.containers import SmartWashParams
|
|
4
|
+
from roborock.device_features import is_wash_n_fill_dock
|
|
5
|
+
from roborock.devices.traits.v1 import common
|
|
6
|
+
from roborock.roborock_typing import RoborockCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SmartWashParamsTrait(SmartWashParams, common.V1TraitMixin):
|
|
10
|
+
"""Trait for smart wash parameters."""
|
|
11
|
+
|
|
12
|
+
command = RoborockCommand.GET_SMART_WASH_PARAMS
|
|
13
|
+
requires_dock_type = is_wash_n_fill_dock
|
|
@@ -11,6 +11,11 @@ class ValleyElectricityTimerTrait(ValleyElectricityTimer, common.V1TraitMixin):
|
|
|
11
11
|
command = RoborockCommand.GET_VALLEY_ELECTRICITY_TIMER
|
|
12
12
|
requires_feature = "is_supported_valley_electricity"
|
|
13
13
|
|
|
14
|
+
@property
|
|
15
|
+
def is_on(self) -> bool:
|
|
16
|
+
"""Return whether the Valley Electricity Timer is enabled."""
|
|
17
|
+
return self.enabled == 1
|
|
18
|
+
|
|
14
19
|
async def set_timer(self, timer: ValleyElectricityTimer) -> None:
|
|
15
20
|
"""Set the Valley Electricity Timer settings of the device."""
|
|
16
21
|
await self.rpc_channel.send_command(RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER, params=timer.as_dict())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Trait for wash towel mode."""
|
|
2
|
+
|
|
3
|
+
from roborock.containers import WashTowelMode
|
|
4
|
+
from roborock.device_features import is_wash_n_fill_dock
|
|
5
|
+
from roborock.devices.traits.v1 import common
|
|
6
|
+
from roborock.roborock_typing import RoborockCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WashTowelModeTrait(WashTowelMode, common.V1TraitMixin):
|
|
10
|
+
"""Trait for wash towel mode."""
|
|
11
|
+
|
|
12
|
+
command = RoborockCommand.GET_WASH_TOWEL_MODE
|
|
13
|
+
requires_dock_type = is_wash_n_fill_dock
|
|
@@ -123,10 +123,18 @@ class V1Channel(Channel):
|
|
|
123
123
|
async def subscribe(self, callback: Callable[[RoborockMessage], None]) -> Callable[[], None]:
|
|
124
124
|
"""Subscribe to all messages from the device.
|
|
125
125
|
|
|
126
|
-
This will
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
This will first attempt to establish a local connection to the device
|
|
127
|
+
using cached network information if available. If that fails, it will
|
|
128
|
+
fall back to using the MQTT connection.
|
|
129
|
+
|
|
130
|
+
A background task will be started to monitor and maintain the local
|
|
131
|
+
connection, attempting to reconnect as needed.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
callback: Callback to invoke for each received message.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Unsubscribe function to stop receiving messages and clean up resources.
|
|
130
138
|
"""
|
|
131
139
|
if self._callback is not None:
|
|
132
140
|
raise ValueError("Only one subscription allowed at a time")
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
@@ -45,6 +45,7 @@ from roborock.containers import (
|
|
|
45
45
|
ValleyElectricityTimer,
|
|
46
46
|
WashTowelMode,
|
|
47
47
|
)
|
|
48
|
+
from roborock.device_features import WASH_N_FILL_DOCK_TYPES
|
|
48
49
|
from roborock.protocols.v1_protocol import MapResponse, SecurityData, create_map_response_decoder
|
|
49
50
|
from roborock.roborock_message import (
|
|
50
51
|
ROBOROCK_DATA_CONSUMABLE_PROTOCOL,
|
|
@@ -64,16 +65,6 @@ COMMANDS_SECURED = {
|
|
|
64
65
|
|
|
65
66
|
CLOUD_REQUIRED = COMMANDS_SECURED.union(CUSTOM_COMMANDS)
|
|
66
67
|
|
|
67
|
-
WASH_N_FILL_DOCK = [
|
|
68
|
-
RoborockDockTypeCode.empty_wash_fill_dock,
|
|
69
|
-
RoborockDockTypeCode.s8_dock,
|
|
70
|
-
RoborockDockTypeCode.p10_dock,
|
|
71
|
-
RoborockDockTypeCode.p10_pro_dock,
|
|
72
|
-
RoborockDockTypeCode.s8_maxv_ultra_dock,
|
|
73
|
-
RoborockDockTypeCode.qrevo_s_dock,
|
|
74
|
-
RoborockDockTypeCode.saros_r10_dock,
|
|
75
|
-
RoborockDockTypeCode.qrevo_curv_dock,
|
|
76
|
-
]
|
|
77
68
|
RT = TypeVar("RT", bound=RoborockBase)
|
|
78
69
|
EVICT_TIME = 60
|
|
79
70
|
|
|
@@ -264,7 +255,7 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
264
255
|
DustCollectionMode | WashTowelMode | SmartWashParams | None,
|
|
265
256
|
]
|
|
266
257
|
] = [self.get_dust_collection_mode()]
|
|
267
|
-
if dock_type in
|
|
258
|
+
if dock_type in WASH_N_FILL_DOCK_TYPES:
|
|
268
259
|
commands += [
|
|
269
260
|
self.get_wash_towel_mode(),
|
|
270
261
|
self.get_smart_wash_params(),
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
"""Create traits for V1 devices."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from dataclasses import dataclass, field, fields
|
|
5
|
-
from typing import get_args
|
|
6
|
-
|
|
7
|
-
from roborock.containers import HomeData, HomeDataProduct
|
|
8
|
-
from roborock.devices.cache import Cache
|
|
9
|
-
from roborock.devices.traits import Trait
|
|
10
|
-
from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
11
|
-
from roborock.map.map_parser import MapParserConfig
|
|
12
|
-
|
|
13
|
-
from .child_lock import ChildLockTrait
|
|
14
|
-
from .clean_summary import CleanSummaryTrait
|
|
15
|
-
from .command import CommandTrait
|
|
16
|
-
from .common import V1TraitMixin
|
|
17
|
-
from .consumeable import ConsumableTrait
|
|
18
|
-
from .device_features import DeviceFeaturesTrait
|
|
19
|
-
from .do_not_disturb import DoNotDisturbTrait
|
|
20
|
-
from .flow_led_status import FlowLedStatusTrait
|
|
21
|
-
from .home import HomeTrait
|
|
22
|
-
from .led_status import LedStatusTrait
|
|
23
|
-
from .map_content import MapContentTrait
|
|
24
|
-
from .maps import MapsTrait
|
|
25
|
-
from .rooms import RoomsTrait
|
|
26
|
-
from .status import StatusTrait
|
|
27
|
-
from .volume import SoundVolumeTrait
|
|
28
|
-
|
|
29
|
-
_LOGGER = logging.getLogger(__name__)
|
|
30
|
-
|
|
31
|
-
__all__ = [
|
|
32
|
-
"create",
|
|
33
|
-
"PropertiesApi",
|
|
34
|
-
"StatusTrait",
|
|
35
|
-
"DoNotDisturbTrait",
|
|
36
|
-
"CleanSummaryTrait",
|
|
37
|
-
"SoundVolumeTrait",
|
|
38
|
-
"MapsTrait",
|
|
39
|
-
"MapContentTrait",
|
|
40
|
-
"ConsumableTrait",
|
|
41
|
-
"HomeTrait",
|
|
42
|
-
"DeviceFeaturesTrait",
|
|
43
|
-
"CommandTrait",
|
|
44
|
-
"ChildLockTrait",
|
|
45
|
-
"FlowLedStatusTrait",
|
|
46
|
-
"LedStatusTrait",
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@dataclass
|
|
51
|
-
class PropertiesApi(Trait):
|
|
52
|
-
"""Common properties for V1 devices.
|
|
53
|
-
|
|
54
|
-
This class holds all the traits that are common across all V1 devices.
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
# All v1 devices have these traits
|
|
58
|
-
status: StatusTrait
|
|
59
|
-
command: CommandTrait
|
|
60
|
-
dnd: DoNotDisturbTrait
|
|
61
|
-
clean_summary: CleanSummaryTrait
|
|
62
|
-
sound_volume: SoundVolumeTrait
|
|
63
|
-
rooms: RoomsTrait
|
|
64
|
-
maps: MapsTrait
|
|
65
|
-
map_content: MapContentTrait
|
|
66
|
-
consumables: ConsumableTrait
|
|
67
|
-
home: HomeTrait
|
|
68
|
-
device_features: DeviceFeaturesTrait
|
|
69
|
-
|
|
70
|
-
# Optional features that may not be supported on all devices
|
|
71
|
-
child_lock: ChildLockTrait | None = None
|
|
72
|
-
led_status: LedStatusTrait | None = None
|
|
73
|
-
flow_led_status: FlowLedStatusTrait | None = None
|
|
74
|
-
|
|
75
|
-
def __init__(
|
|
76
|
-
self,
|
|
77
|
-
product: HomeDataProduct,
|
|
78
|
-
home_data: HomeData,
|
|
79
|
-
rpc_channel: V1RpcChannel,
|
|
80
|
-
mqtt_rpc_channel: V1RpcChannel,
|
|
81
|
-
map_rpc_channel: V1RpcChannel,
|
|
82
|
-
cache: Cache,
|
|
83
|
-
map_parser_config: MapParserConfig | None = None,
|
|
84
|
-
) -> None:
|
|
85
|
-
"""Initialize the V1TraitProps."""
|
|
86
|
-
self._rpc_channel = rpc_channel
|
|
87
|
-
self._mqtt_rpc_channel = mqtt_rpc_channel
|
|
88
|
-
self._map_rpc_channel = map_rpc_channel
|
|
89
|
-
|
|
90
|
-
self.status = StatusTrait(product)
|
|
91
|
-
self.rooms = RoomsTrait(home_data)
|
|
92
|
-
self.maps = MapsTrait(self.status)
|
|
93
|
-
self.map_content = MapContentTrait(map_parser_config)
|
|
94
|
-
self.home = HomeTrait(self.status, self.maps, self.rooms, cache)
|
|
95
|
-
self.device_features = DeviceFeaturesTrait(product.product_nickname, cache)
|
|
96
|
-
|
|
97
|
-
# Dynamically create any traits that need to be populated
|
|
98
|
-
for item in fields(self):
|
|
99
|
-
if (trait := getattr(self, item.name, None)) is None:
|
|
100
|
-
# We exclude optional features and them via discover_features
|
|
101
|
-
if (union_args := get_args(item.type)) is None or len(union_args) > 0:
|
|
102
|
-
continue
|
|
103
|
-
_LOGGER.debug("Initializing trait %s", item.name)
|
|
104
|
-
trait = item.type()
|
|
105
|
-
setattr(self, item.name, trait)
|
|
106
|
-
# This is a hack to allow setting the rpc_channel on all traits. This is
|
|
107
|
-
# used so we can preserve the dataclass behavior when the values in the
|
|
108
|
-
# traits are updated, but still want to allow them to have a reference
|
|
109
|
-
# to the rpc channel for sending commands.
|
|
110
|
-
trait._rpc_channel = self._get_rpc_channel(trait)
|
|
111
|
-
|
|
112
|
-
def _get_rpc_channel(self, trait: V1TraitMixin) -> V1RpcChannel:
|
|
113
|
-
# The decorator `@common.mqtt_rpc_channel` means that the trait needs
|
|
114
|
-
# to use the mqtt_rpc_channel (cloud only) instead of the rpc_channel (adaptive)
|
|
115
|
-
if hasattr(trait, "mqtt_rpc_channel"):
|
|
116
|
-
return self._mqtt_rpc_channel
|
|
117
|
-
elif hasattr(trait, "map_rpc_channel"):
|
|
118
|
-
return self._map_rpc_channel
|
|
119
|
-
else:
|
|
120
|
-
return self._rpc_channel
|
|
121
|
-
|
|
122
|
-
async def discover_features(self) -> None:
|
|
123
|
-
"""Populate any supported traits that were not initialized in __init__."""
|
|
124
|
-
await self.device_features.refresh()
|
|
125
|
-
|
|
126
|
-
for item in fields(self):
|
|
127
|
-
if (trait := getattr(self, item.name, None)) is not None:
|
|
128
|
-
continue
|
|
129
|
-
if (union_args := get_args(item.type)) is None:
|
|
130
|
-
raise ValueError(f"Unexpected non-union type for trait {item.name}: {item.type}")
|
|
131
|
-
if len(union_args) != 2 or type(None) not in union_args:
|
|
132
|
-
raise ValueError(f"Unexpected non-optional type for trait {item.name}: {item.type}")
|
|
133
|
-
|
|
134
|
-
# Union args may not be in declared order
|
|
135
|
-
item_type = union_args[0] if union_args[1] is type(None) else union_args[1]
|
|
136
|
-
trait = item_type()
|
|
137
|
-
if not hasattr(trait, "requires_feature"):
|
|
138
|
-
_LOGGER.debug("Trait missing required feature %s", item.name)
|
|
139
|
-
continue
|
|
140
|
-
_LOGGER.debug("Checking for feature %s", trait.requires_feature)
|
|
141
|
-
is_supported = getattr(self.device_features, trait.requires_feature)
|
|
142
|
-
# _LOGGER.debug("Device features: %s", self.device_features)
|
|
143
|
-
if is_supported is None:
|
|
144
|
-
raise ValueError(f"Device feature '{trait.requires_feature}' on trait '{item.name}' is unknown")
|
|
145
|
-
if not is_supported:
|
|
146
|
-
_LOGGER.debug("Disabling optional feature trait %s", item.name)
|
|
147
|
-
continue
|
|
148
|
-
_LOGGER.debug("Enabling optional feature trait %s", item.name)
|
|
149
|
-
setattr(self, item.name, trait)
|
|
150
|
-
trait._rpc_channel = self._get_rpc_channel(trait)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def create(
|
|
154
|
-
product: HomeDataProduct,
|
|
155
|
-
home_data: HomeData,
|
|
156
|
-
rpc_channel: V1RpcChannel,
|
|
157
|
-
mqtt_rpc_channel: V1RpcChannel,
|
|
158
|
-
map_rpc_channel: V1RpcChannel,
|
|
159
|
-
cache: Cache,
|
|
160
|
-
map_parser_config: MapParserConfig | None = None,
|
|
161
|
-
) -> PropertiesApi:
|
|
162
|
-
"""Create traits for V1 devices."""
|
|
163
|
-
return PropertiesApi(product, home_data, rpc_channel, mqtt_rpc_channel, map_rpc_channel, cache, map_parser_config)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.59.0 → python_roborock-2.61.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|