python-roborock 3.0.0__tar.gz → 3.2.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-3.0.0 → python_roborock-3.2.0}/PKG-INFO +1 -1
- {python_roborock-3.0.0 → python_roborock-3.2.0}/pyproject.toml +16 -9
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/cli.py +11 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/v1/v1_containers.py +11 -5
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/device.py +40 -1
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/device_manager.py +1 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/__init__.py +30 -7
- python_roborock-3.0.0/roborock/devices/traits/v1/clean_record.py → python_roborock-3.2.0/roborock/devices/traits/v1/clean_summary.py +34 -21
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/device_features.py +5 -0
- python_roborock-3.2.0/roborock/devices/traits/v1/network_info.py +57 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/roborock_client_v1.py +1 -1
- python_roborock-3.0.0/roborock/devices/traits/v1/clean_summary.py +0 -29
- {python_roborock-3.0.0 → python_roborock-3.2.0}/.gitignore +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/LICENSE +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/README.md +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/api.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/callbacks.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/cloud_api.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/command_cache.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/const.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/containers.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/device_features.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/README.md +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/cache.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/v1_rpc_channel.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/exceptions.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/map/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/protocol.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/py.typed +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/roborock_future.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/roborock_message.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/util.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.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 = "3.
|
|
3
|
+
version = "3.2.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"
|
|
@@ -44,7 +44,7 @@ dev = [
|
|
|
44
44
|
"pytest",
|
|
45
45
|
"pre-commit>=3.5,<5.0",
|
|
46
46
|
"mypy",
|
|
47
|
-
"ruff==0.14.
|
|
47
|
+
"ruff==0.14.1",
|
|
48
48
|
"codespell",
|
|
49
49
|
"pyshark>=0.6,<0.7",
|
|
50
50
|
"aioresponses>=0.7.7,<0.8",
|
|
@@ -52,6 +52,8 @@ dev = [
|
|
|
52
52
|
"pytest-timeout>=2.3.1,<3",
|
|
53
53
|
"syrupy>=4.9.1,<5",
|
|
54
54
|
"pdoc>=15.0.4,<16",
|
|
55
|
+
"pyyaml>=6.0.3",
|
|
56
|
+
"pyshark>=0.6",
|
|
55
57
|
]
|
|
56
58
|
|
|
57
59
|
[tool.hatch.build.targets.sdist]
|
|
@@ -67,7 +69,18 @@ build-backend = "hatchling.build"
|
|
|
67
69
|
[tool.semantic_release]
|
|
68
70
|
branch = "main"
|
|
69
71
|
version_toml = ["pyproject.toml:project.version"]
|
|
70
|
-
build_command = "pip install uv && uv build"
|
|
72
|
+
build_command = "pip install uv && uv lock --upgrade-package python-roborock && git add uv.lock && uv build"
|
|
73
|
+
changelog_file = 'CHANGELOG.md'
|
|
74
|
+
commit = true
|
|
75
|
+
|
|
76
|
+
[tool.semantic_release.branches.main]
|
|
77
|
+
match = "main"
|
|
78
|
+
prerelease = false
|
|
79
|
+
|
|
80
|
+
[tool.semantic_release.branches.temp-main-branch]
|
|
81
|
+
match = "temp-main-branch"
|
|
82
|
+
prerelease = false
|
|
83
|
+
|
|
71
84
|
|
|
72
85
|
[tool.semantic_release.commit_parser_options]
|
|
73
86
|
allowed_tags = [
|
|
@@ -92,9 +105,3 @@ asyncio_mode = "auto"
|
|
|
92
105
|
asyncio_default_fixture_loop_scope = "function"
|
|
93
106
|
timeout = 30
|
|
94
107
|
log_format = "%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
|
95
|
-
|
|
96
|
-
[tool.uv]
|
|
97
|
-
dev-dependencies = [
|
|
98
|
-
"pyyaml>=6.0.3",
|
|
99
|
-
"pyshark>=0.6",
|
|
100
|
-
]
|
|
@@ -731,6 +731,16 @@ async def home(ctx, device_id: str, refresh: bool):
|
|
|
731
731
|
click.echo("No maps discovered")
|
|
732
732
|
|
|
733
733
|
|
|
734
|
+
@session.command()
|
|
735
|
+
@click.option("--device_id", required=True)
|
|
736
|
+
@click.pass_context
|
|
737
|
+
@async_command
|
|
738
|
+
async def network_info(ctx, device_id: str):
|
|
739
|
+
"""Get device network information."""
|
|
740
|
+
context: RoborockContext = ctx.obj
|
|
741
|
+
await _display_v1_trait(context, device_id, lambda v1: v1.network_info)
|
|
742
|
+
|
|
743
|
+
|
|
734
744
|
@click.command()
|
|
735
745
|
@click.option("--device_id", required=True)
|
|
736
746
|
@click.option("--cmd", required=True)
|
|
@@ -979,6 +989,7 @@ cli.add_command(child_lock)
|
|
|
979
989
|
cli.add_command(dnd)
|
|
980
990
|
cli.add_command(flow_led_status)
|
|
981
991
|
cli.add_command(led_status)
|
|
992
|
+
cli.add_command(network_info)
|
|
982
993
|
|
|
983
994
|
|
|
984
995
|
def main():
|
|
@@ -156,15 +156,15 @@ class Status(RoborockBase):
|
|
|
156
156
|
|
|
157
157
|
@property
|
|
158
158
|
def error_code_name(self) -> str | None:
|
|
159
|
-
return self.error_code.name if self.error_code else None
|
|
159
|
+
return self.error_code.name if self.error_code is not None else None
|
|
160
160
|
|
|
161
161
|
@property
|
|
162
162
|
def state_name(self) -> str | None:
|
|
163
|
-
return self.state.name if self.state else None
|
|
163
|
+
return self.state.name if self.state is not None else None
|
|
164
164
|
|
|
165
165
|
@property
|
|
166
166
|
def water_box_mode_name(self) -> str | None:
|
|
167
|
-
return self.water_box_mode.name if self.water_box_mode else None
|
|
167
|
+
return self.water_box_mode.name if self.water_box_mode is not None else None
|
|
168
168
|
|
|
169
169
|
@property
|
|
170
170
|
def fan_power_options(self) -> list[str]:
|
|
@@ -174,11 +174,11 @@ class Status(RoborockBase):
|
|
|
174
174
|
|
|
175
175
|
@property
|
|
176
176
|
def fan_power_name(self) -> str | None:
|
|
177
|
-
return self.fan_power.name if self.fan_power else None
|
|
177
|
+
return self.fan_power.name if self.fan_power is not None else None
|
|
178
178
|
|
|
179
179
|
@property
|
|
180
180
|
def mop_mode_name(self) -> str | None:
|
|
181
|
-
return self.mop_mode.name if self.mop_mode else None
|
|
181
|
+
return self.mop_mode.name if self.mop_mode is not None else None
|
|
182
182
|
|
|
183
183
|
def get_fan_speed_code(self, fan_speed: str) -> int:
|
|
184
184
|
if self.fan_power is None:
|
|
@@ -449,6 +449,12 @@ class CleanRecord(RoborockBase):
|
|
|
449
449
|
return _attr_repr(self)
|
|
450
450
|
|
|
451
451
|
|
|
452
|
+
class CleanSummaryWithDetail(CleanSummary):
|
|
453
|
+
"""CleanSummary with the last CleanRecord included."""
|
|
454
|
+
|
|
455
|
+
last_clean_record: CleanRecord | None = None
|
|
456
|
+
|
|
457
|
+
|
|
452
458
|
@dataclass
|
|
453
459
|
class Consumable(RoborockBase):
|
|
454
460
|
main_brush_work_time: int | None = None
|
|
@@ -6,7 +6,8 @@ until the API is stable.
|
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
8
|
from abc import ABC
|
|
9
|
-
from collections.abc import Callable
|
|
9
|
+
from collections.abc import Callable, Mapping
|
|
10
|
+
from typing import Any, TypeVar, cast
|
|
10
11
|
|
|
11
12
|
from roborock.data import HomeDataDevice, HomeDataProduct
|
|
12
13
|
from roborock.roborock_message import RoborockMessage
|
|
@@ -113,3 +114,41 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
113
114
|
def _on_message(self, message: RoborockMessage) -> None:
|
|
114
115
|
"""Handle incoming messages from the device."""
|
|
115
116
|
_LOGGER.debug("Received message from device: %s", message)
|
|
117
|
+
|
|
118
|
+
def diagnostic_data(self) -> dict[str, Any]:
|
|
119
|
+
"""Return diagnostics information about the device."""
|
|
120
|
+
extra: dict[str, Any] = {}
|
|
121
|
+
if self.v1_properties:
|
|
122
|
+
extra["traits"] = _redact_data(self.v1_properties.as_dict())
|
|
123
|
+
return {
|
|
124
|
+
"device": _redact_data(self.device_info.as_dict()),
|
|
125
|
+
"product": _redact_data(self.product.as_dict()),
|
|
126
|
+
**extra,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
T = TypeVar("T")
|
|
131
|
+
|
|
132
|
+
REDACT_KEYS = {"duid", "localKey", "mac", "bssid", "sn", "ip"}
|
|
133
|
+
REDACTED = "**REDACTED**"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _redact_data(data: T) -> T | dict[str, Any]:
|
|
137
|
+
"""Redact sensitive data in a dict."""
|
|
138
|
+
if not isinstance(data, (Mapping, list)):
|
|
139
|
+
return data
|
|
140
|
+
|
|
141
|
+
if isinstance(data, list):
|
|
142
|
+
return cast(T, [_redact_data(item) for item in data])
|
|
143
|
+
|
|
144
|
+
redacted = {**data}
|
|
145
|
+
|
|
146
|
+
for key, value in redacted.items():
|
|
147
|
+
if key in REDACT_KEYS:
|
|
148
|
+
redacted[key] = REDACTED
|
|
149
|
+
elif isinstance(value, dict):
|
|
150
|
+
redacted[key] = _redact_data(value)
|
|
151
|
+
elif isinstance(value, list):
|
|
152
|
+
redacted[key] = [_redact_data(item) for item in value]
|
|
153
|
+
|
|
154
|
+
return redacted
|
|
@@ -34,7 +34,7 @@ import logging
|
|
|
34
34
|
from dataclasses import dataclass, field, fields
|
|
35
35
|
from typing import Any, get_args
|
|
36
36
|
|
|
37
|
-
from roborock.data.containers import HomeData, HomeDataProduct
|
|
37
|
+
from roborock.data.containers import HomeData, HomeDataProduct, RoborockBase
|
|
38
38
|
from roborock.data.v1.v1_code_mappings import RoborockDockTypeCode
|
|
39
39
|
from roborock.devices.cache import Cache
|
|
40
40
|
from roborock.devices.traits import Trait
|
|
@@ -42,7 +42,6 @@ from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
|
42
42
|
from roborock.map.map_parser import MapParserConfig
|
|
43
43
|
|
|
44
44
|
from .child_lock import ChildLockTrait
|
|
45
|
-
from .clean_record import CleanRecordTrait
|
|
46
45
|
from .clean_summary import CleanSummaryTrait
|
|
47
46
|
from .command import CommandTrait
|
|
48
47
|
from .common import V1TraitMixin
|
|
@@ -55,6 +54,7 @@ from .home import HomeTrait
|
|
|
55
54
|
from .led_status import LedStatusTrait
|
|
56
55
|
from .map_content import MapContentTrait
|
|
57
56
|
from .maps import MapsTrait
|
|
57
|
+
from .network_info import NetworkInfoTrait
|
|
58
58
|
from .rooms import RoomsTrait
|
|
59
59
|
from .smart_wash_params import SmartWashParamsTrait
|
|
60
60
|
from .status import StatusTrait
|
|
@@ -70,7 +70,6 @@ __all__ = [
|
|
|
70
70
|
"StatusTrait",
|
|
71
71
|
"DoNotDisturbTrait",
|
|
72
72
|
"CleanSummaryTrait",
|
|
73
|
-
"CleanRecordTrait",
|
|
74
73
|
"SoundVolumeTrait",
|
|
75
74
|
"MapsTrait",
|
|
76
75
|
"MapContentTrait",
|
|
@@ -85,6 +84,7 @@ __all__ = [
|
|
|
85
84
|
"DustCollectionModeTrait",
|
|
86
85
|
"WashTowelModeTrait",
|
|
87
86
|
"SmartWashParamsTrait",
|
|
87
|
+
"NetworkInfoTrait",
|
|
88
88
|
]
|
|
89
89
|
|
|
90
90
|
|
|
@@ -100,7 +100,6 @@ class PropertiesApi(Trait):
|
|
|
100
100
|
command: CommandTrait
|
|
101
101
|
dnd: DoNotDisturbTrait
|
|
102
102
|
clean_summary: CleanSummaryTrait
|
|
103
|
-
clean_record: CleanRecordTrait
|
|
104
103
|
sound_volume: SoundVolumeTrait
|
|
105
104
|
rooms: RoomsTrait
|
|
106
105
|
maps: MapsTrait
|
|
@@ -108,6 +107,7 @@ class PropertiesApi(Trait):
|
|
|
108
107
|
consumables: ConsumableTrait
|
|
109
108
|
home: HomeTrait
|
|
110
109
|
device_features: DeviceFeaturesTrait
|
|
110
|
+
network_info: NetworkInfoTrait
|
|
111
111
|
|
|
112
112
|
# Optional features that may not be supported on all devices
|
|
113
113
|
child_lock: ChildLockTrait | None = None
|
|
@@ -120,6 +120,7 @@ class PropertiesApi(Trait):
|
|
|
120
120
|
|
|
121
121
|
def __init__(
|
|
122
122
|
self,
|
|
123
|
+
device_uid: str,
|
|
123
124
|
product: HomeDataProduct,
|
|
124
125
|
home_data: HomeData,
|
|
125
126
|
rpc_channel: V1RpcChannel,
|
|
@@ -129,20 +130,20 @@ class PropertiesApi(Trait):
|
|
|
129
130
|
map_parser_config: MapParserConfig | None = None,
|
|
130
131
|
) -> None:
|
|
131
132
|
"""Initialize the V1TraitProps."""
|
|
133
|
+
self._device_uid = device_uid
|
|
132
134
|
self._rpc_channel = rpc_channel
|
|
133
135
|
self._mqtt_rpc_channel = mqtt_rpc_channel
|
|
134
136
|
self._map_rpc_channel = map_rpc_channel
|
|
135
137
|
self._cache = cache
|
|
136
138
|
|
|
137
139
|
self.status = StatusTrait(product)
|
|
138
|
-
self.clean_summary = CleanSummaryTrait()
|
|
139
|
-
self.clean_record = CleanRecordTrait(self.clean_summary)
|
|
140
140
|
self.consumables = ConsumableTrait()
|
|
141
141
|
self.rooms = RoomsTrait(home_data)
|
|
142
142
|
self.maps = MapsTrait(self.status)
|
|
143
143
|
self.map_content = MapContentTrait(map_parser_config)
|
|
144
144
|
self.home = HomeTrait(self.status, self.maps, self.rooms, cache)
|
|
145
145
|
self.device_features = DeviceFeaturesTrait(product.product_nickname, cache)
|
|
146
|
+
self.network_info = NetworkInfoTrait(device_uid, cache)
|
|
146
147
|
|
|
147
148
|
# Dynamically create any traits that need to be populated
|
|
148
149
|
for item in fields(self):
|
|
@@ -246,8 +247,21 @@ class PropertiesApi(Trait):
|
|
|
246
247
|
_LOGGER.debug("Updating cached trait data: %s", cache_data.trait_data)
|
|
247
248
|
await self._cache.set(cache_data)
|
|
248
249
|
|
|
250
|
+
def as_dict(self) -> dict[str, Any]:
|
|
251
|
+
"""Return the trait data as a dictionary."""
|
|
252
|
+
result: dict[str, Any] = {}
|
|
253
|
+
for item in fields(self):
|
|
254
|
+
trait = getattr(self, item.name, None)
|
|
255
|
+
if trait is None or not isinstance(trait, RoborockBase):
|
|
256
|
+
continue
|
|
257
|
+
data = trait.as_dict()
|
|
258
|
+
if data: # Don't omit unset traits
|
|
259
|
+
result[item.name] = data
|
|
260
|
+
return result
|
|
261
|
+
|
|
249
262
|
|
|
250
263
|
def create(
|
|
264
|
+
device_uid: str,
|
|
251
265
|
product: HomeDataProduct,
|
|
252
266
|
home_data: HomeData,
|
|
253
267
|
rpc_channel: V1RpcChannel,
|
|
@@ -257,4 +271,13 @@ def create(
|
|
|
257
271
|
map_parser_config: MapParserConfig | None = None,
|
|
258
272
|
) -> PropertiesApi:
|
|
259
273
|
"""Create traits for V1 devices."""
|
|
260
|
-
return PropertiesApi(
|
|
274
|
+
return PropertiesApi(
|
|
275
|
+
device_uid,
|
|
276
|
+
product,
|
|
277
|
+
home_data,
|
|
278
|
+
rpc_channel,
|
|
279
|
+
mqtt_rpc_channel,
|
|
280
|
+
map_rpc_channel,
|
|
281
|
+
cache,
|
|
282
|
+
map_parser_config,
|
|
283
|
+
)
|
|
@@ -1,44 +1,57 @@
|
|
|
1
|
-
"""Trait for getting the last clean record."""
|
|
2
|
-
|
|
3
1
|
import logging
|
|
4
2
|
from typing import Self
|
|
5
3
|
|
|
6
|
-
from roborock.data import CleanRecord
|
|
4
|
+
from roborock.data import CleanRecord, CleanSummaryWithDetail
|
|
7
5
|
from roborock.devices.traits.v1 import common
|
|
8
6
|
from roborock.roborock_typing import RoborockCommand
|
|
9
7
|
from roborock.util import unpack_list
|
|
10
8
|
|
|
11
|
-
from .clean_summary import CleanSummaryTrait
|
|
12
|
-
|
|
13
9
|
_LOGGER = logging.getLogger(__name__)
|
|
14
10
|
|
|
15
11
|
|
|
16
|
-
class
|
|
17
|
-
"""Trait for
|
|
12
|
+
class CleanSummaryTrait(CleanSummaryWithDetail, common.V1TraitMixin):
|
|
13
|
+
"""Trait for managing the clean summary of Roborock devices."""
|
|
18
14
|
|
|
19
|
-
command = RoborockCommand.
|
|
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
|
|
15
|
+
command = RoborockCommand.GET_CLEAN_SUMMARY
|
|
25
16
|
|
|
26
17
|
async def refresh(self) -> Self:
|
|
27
|
-
"""
|
|
18
|
+
"""Refresh the clean summary data and last clean record.
|
|
28
19
|
|
|
29
20
|
Assumes that the clean summary has already been fetched.
|
|
30
21
|
"""
|
|
31
|
-
|
|
22
|
+
await super().refresh()
|
|
23
|
+
if not self.records:
|
|
32
24
|
_LOGGER.debug("No clean records available in clean summary.")
|
|
25
|
+
self.last_clean_record = None
|
|
33
26
|
return self
|
|
34
|
-
last_record_id = self.
|
|
35
|
-
|
|
36
|
-
new_self = self._parse_response(response)
|
|
37
|
-
self._update_trait_values(new_self)
|
|
27
|
+
last_record_id = self.records[-1]
|
|
28
|
+
self.last_clean_record = await self.get_clean_record(last_record_id)
|
|
38
29
|
return self
|
|
39
30
|
|
|
40
31
|
@classmethod
|
|
41
|
-
def _parse_type_response(cls, response: common.V1ResponseData) ->
|
|
32
|
+
def _parse_type_response(cls, response: common.V1ResponseData) -> Self:
|
|
33
|
+
"""Parse the response from the device into a CleanSummary."""
|
|
34
|
+
if isinstance(response, dict):
|
|
35
|
+
return cls.from_dict(response)
|
|
36
|
+
elif isinstance(response, list):
|
|
37
|
+
clean_time, clean_area, clean_count, records = unpack_list(response, 4)
|
|
38
|
+
return cls(
|
|
39
|
+
clean_time=clean_time,
|
|
40
|
+
clean_area=clean_area,
|
|
41
|
+
clean_count=clean_count,
|
|
42
|
+
records=records,
|
|
43
|
+
)
|
|
44
|
+
elif isinstance(response, int):
|
|
45
|
+
return cls(clean_time=response)
|
|
46
|
+
raise ValueError(f"Unexpected clean summary format: {response!r}")
|
|
47
|
+
|
|
48
|
+
async def get_clean_record(self, record_id: int) -> CleanRecord:
|
|
49
|
+
"""Load a specific clean record by ID."""
|
|
50
|
+
response = await self.rpc_channel.send_command(RoborockCommand.GET_CLEAN_RECORD, params=[record_id])
|
|
51
|
+
return self._parse_clean_record_response(response)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _parse_clean_record_response(cls, response: common.V1ResponseData) -> CleanRecord:
|
|
42
55
|
"""Parse the response from the device into a CleanRecord."""
|
|
43
56
|
if isinstance(response, dict):
|
|
44
57
|
return CleanRecord.from_dict(response)
|
|
@@ -47,7 +60,7 @@ class CleanRecordTrait(CleanRecord, common.V1TraitMixin):
|
|
|
47
60
|
records = [CleanRecord.from_dict(rec) for rec in response]
|
|
48
61
|
final_record = records[-1]
|
|
49
62
|
try:
|
|
50
|
-
# This code is semi-
|
|
63
|
+
# This code is semi-presumptuous - so it is put in a try finally to be safe.
|
|
51
64
|
final_record.begin = records[0].begin
|
|
52
65
|
final_record.begin_datetime = records[0].begin_datetime
|
|
53
66
|
final_record.start_type = records[0].start_type
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from dataclasses import fields
|
|
1
2
|
from typing import Self
|
|
2
3
|
|
|
3
4
|
from roborock.data import AppInitStatus, RoborockProductNickname
|
|
@@ -16,6 +17,10 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
16
17
|
"""Initialize MapContentTrait."""
|
|
17
18
|
self._nickname = product_nickname
|
|
18
19
|
self._cache = cache
|
|
20
|
+
# All fields of DeviceFeatures are required. Initialize them to False
|
|
21
|
+
# so we have some known state.
|
|
22
|
+
for field in fields(self):
|
|
23
|
+
setattr(self, field.name, False)
|
|
19
24
|
|
|
20
25
|
async def refresh(self) -> Self:
|
|
21
26
|
"""Refresh the contents of this trait.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Trait for device network information."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Self
|
|
7
|
+
|
|
8
|
+
from roborock.data import NetworkInfo
|
|
9
|
+
from roborock.devices.cache import Cache
|
|
10
|
+
from roborock.devices.traits.v1 import common
|
|
11
|
+
from roborock.roborock_typing import RoborockCommand
|
|
12
|
+
|
|
13
|
+
_LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
|
|
17
|
+
"""Trait for device network information.
|
|
18
|
+
|
|
19
|
+
This trait will always prefer reading from the cache if available. This
|
|
20
|
+
information is usually already fetched when creating the device local
|
|
21
|
+
connection, so reading from the cache avoids an unnecessary RPC call.
|
|
22
|
+
However, we have the fallback to reading from the device if the cache is
|
|
23
|
+
not populated for some reason.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
command = RoborockCommand.GET_NETWORK_INFO
|
|
27
|
+
|
|
28
|
+
def __init__(self, device_uid: str, cache: Cache) -> None:
|
|
29
|
+
"""Initialize the trait."""
|
|
30
|
+
self._device_uid = device_uid
|
|
31
|
+
self._cache = cache
|
|
32
|
+
self.ip = ""
|
|
33
|
+
|
|
34
|
+
async def refresh(self) -> Self:
|
|
35
|
+
"""Refresh the network info from the cache."""
|
|
36
|
+
|
|
37
|
+
cache_data = await self._cache.get()
|
|
38
|
+
if cache_data.network_info and (network_info := cache_data.network_info.get(self._device_uid)):
|
|
39
|
+
_LOGGER.debug("Using cached network info for device %s", self._device_uid)
|
|
40
|
+
self._update_trait_values(network_info)
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
# Load from device if not in cache
|
|
44
|
+
_LOGGER.debug("No cached network info for device %s, fetching from device", self._device_uid)
|
|
45
|
+
await super().refresh()
|
|
46
|
+
|
|
47
|
+
# Update the cache with the new network info
|
|
48
|
+
cache_data.network_info[self._device_uid] = self
|
|
49
|
+
await self._cache.set(cache_data)
|
|
50
|
+
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def _parse_response(self, response: common.V1ResponseData) -> NetworkInfo:
|
|
54
|
+
"""Parse the response from the device into a NetworkInfo."""
|
|
55
|
+
if not isinstance(response, dict):
|
|
56
|
+
raise ValueError(f"Unexpected NetworkInfoTrait response format: {response!r}")
|
|
57
|
+
return NetworkInfo.from_dict(response)
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
@@ -207,7 +207,7 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
207
207
|
records = [CleanRecord.from_dict(rec) for rec in record]
|
|
208
208
|
final_record = records[-1]
|
|
209
209
|
try:
|
|
210
|
-
# This code is semi-
|
|
210
|
+
# This code is semi-presumptuous - so it is put in a try finally to be safe.
|
|
211
211
|
final_record.begin = records[0].begin
|
|
212
212
|
final_record.begin_datetime = records[0].begin_datetime
|
|
213
213
|
final_record.start_type = records[0].start_type
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from typing import Self
|
|
2
|
-
|
|
3
|
-
from roborock.data import CleanSummary
|
|
4
|
-
from roborock.devices.traits.v1 import common
|
|
5
|
-
from roborock.roborock_typing import RoborockCommand
|
|
6
|
-
from roborock.util import unpack_list
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class CleanSummaryTrait(CleanSummary, common.V1TraitMixin):
|
|
10
|
-
"""Trait for managing the clean summary of Roborock devices."""
|
|
11
|
-
|
|
12
|
-
command = RoborockCommand.GET_CLEAN_SUMMARY
|
|
13
|
-
|
|
14
|
-
@classmethod
|
|
15
|
-
def _parse_type_response(cls, response: common.V1ResponseData) -> Self:
|
|
16
|
-
"""Parse the response from the device into a CleanSummary."""
|
|
17
|
-
if isinstance(response, dict):
|
|
18
|
-
return cls.from_dict(response)
|
|
19
|
-
elif isinstance(response, list):
|
|
20
|
-
clean_time, clean_area, clean_count, records = unpack_list(response, 4)
|
|
21
|
-
return cls(
|
|
22
|
-
clean_time=clean_time,
|
|
23
|
-
clean_area=clean_area,
|
|
24
|
-
clean_count=clean_count,
|
|
25
|
-
records=records,
|
|
26
|
-
)
|
|
27
|
-
elif isinstance(response, int):
|
|
28
|
-
return cls(clean_time=response)
|
|
29
|
-
raise ValueError(f"Unexpected clean summary format: {response!r}")
|
|
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-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/data/b01_q7/b01_q7_code_mappings.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/flow_led_status.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/devices/traits/v1/wash_towel_mode.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
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.0.0 → python_roborock-3.2.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|