python-roborock 3.9.0__tar.gz → 3.9.2__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.9.0 → python_roborock-3.9.2}/PKG-INFO +1 -1
- {python_roborock-3.9.0 → python_roborock-3.9.2}/pyproject.toml +1 -1
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/cli.py +34 -39
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/containers.py +12 -6
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/device_features.py +1 -1
- python_roborock-3.9.2/roborock/devices/cache.py +143 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/device_manager.py +4 -3
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/__init__.py +12 -11
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/device_features.py +5 -5
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/home.py +24 -34
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/network_info.py +9 -8
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/v1_channel.py +21 -12
- python_roborock-3.9.0/roborock/devices/cache.py +0 -77
- {python_roborock-3.9.0 → python_roborock-3.9.2}/.gitignore +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/LICENSE +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/README.md +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/api.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/callbacks.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/cloud_api.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/command_cache.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/const.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/code_mappings.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/README.md +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/channel.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/device.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/file_cache.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/local_channel.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/exceptions.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/map/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/map/map_parser.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/session.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocol.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/py.typed +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/roborock_future.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/roborock_message.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/roborock_typing.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/util.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/web_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 3.9.
|
|
3
|
+
Version: 3.9.2
|
|
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.9.
|
|
3
|
+
version = "3.9.2"
|
|
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"
|
|
@@ -42,7 +42,7 @@ from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException
|
|
|
42
42
|
from pyshark.packet.packet import Packet # type: ignore
|
|
43
43
|
|
|
44
44
|
from roborock import SHORT_MODEL_TO_ENUM, RoborockCommand
|
|
45
|
-
from roborock.data import
|
|
45
|
+
from roborock.data import DeviceData, RoborockBase, UserData
|
|
46
46
|
from roborock.device_features import DeviceFeatures
|
|
47
47
|
from roborock.devices.cache import Cache, CacheData
|
|
48
48
|
from roborock.devices.device import RoborockDevice
|
|
@@ -116,10 +116,8 @@ class ConnectionCache(RoborockBase):
|
|
|
116
116
|
|
|
117
117
|
user_data: UserData
|
|
118
118
|
email: str
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
home_map_info: dict[int, CombinedMapInfo] | None = None
|
|
122
|
-
trait_data: dict[str, Any] | None = None
|
|
119
|
+
# TODO: Used new APIs for cache file storage
|
|
120
|
+
cache_data: CacheData | None = None
|
|
123
121
|
|
|
124
122
|
|
|
125
123
|
class DeviceConnectionManager:
|
|
@@ -134,10 +132,10 @@ class DeviceConnectionManager:
|
|
|
134
132
|
async def ensure_device_manager(self) -> DeviceManager:
|
|
135
133
|
"""Ensure device manager is initialized."""
|
|
136
134
|
if self.device_manager is None:
|
|
137
|
-
|
|
135
|
+
connection_cache = self.context.connection_cache()
|
|
138
136
|
user_params = UserParams(
|
|
139
|
-
username=
|
|
140
|
-
user_data=
|
|
137
|
+
username=connection_cache.email,
|
|
138
|
+
user_data=connection_cache.user_data,
|
|
141
139
|
)
|
|
142
140
|
self.device_manager = await create_device_manager(user_params, cache=self.context)
|
|
143
141
|
# Cache devices for quick lookup
|
|
@@ -164,7 +162,8 @@ class RoborockContext(Cache):
|
|
|
164
162
|
"""Context that handles both CLI and session modes internally."""
|
|
165
163
|
|
|
166
164
|
roborock_file = Path("~/.roborock").expanduser()
|
|
167
|
-
|
|
165
|
+
roborock_cache_file = Path("~/.roborock.cache").expanduser()
|
|
166
|
+
_connection_cache: ConnectionCache | None = None
|
|
168
167
|
|
|
169
168
|
def __init__(self):
|
|
170
169
|
self.reload()
|
|
@@ -177,22 +176,22 @@ class RoborockContext(Cache):
|
|
|
177
176
|
with open(self.roborock_file) as f:
|
|
178
177
|
data = json.load(f)
|
|
179
178
|
if data:
|
|
180
|
-
self.
|
|
179
|
+
self._connection_cache = ConnectionCache.from_dict(data)
|
|
181
180
|
|
|
182
|
-
def update(self,
|
|
183
|
-
data = json.dumps(
|
|
181
|
+
def update(self, connection_cache: ConnectionCache):
|
|
182
|
+
data = json.dumps(connection_cache.as_dict(), default=vars, indent=4)
|
|
184
183
|
with open(self.roborock_file, "w") as f:
|
|
185
184
|
f.write(data)
|
|
186
185
|
self.reload()
|
|
187
186
|
|
|
188
187
|
def validate(self):
|
|
189
|
-
if self.
|
|
188
|
+
if self._connection_cache is None:
|
|
190
189
|
raise RoborockException("You must login first")
|
|
191
190
|
|
|
192
|
-
def
|
|
191
|
+
def connection_cache(self) -> ConnectionCache:
|
|
193
192
|
"""Get the cache data."""
|
|
194
193
|
self.validate()
|
|
195
|
-
return cast(ConnectionCache, self.
|
|
194
|
+
return cast(ConnectionCache, self._connection_cache)
|
|
196
195
|
|
|
197
196
|
def start_session_mode(self):
|
|
198
197
|
"""Start session mode with a background event loop."""
|
|
@@ -229,19 +228,21 @@ class RoborockContext(Cache):
|
|
|
229
228
|
|
|
230
229
|
async def refresh_devices(self) -> ConnectionCache:
|
|
231
230
|
"""Refresh device data from server (always fetches fresh data)."""
|
|
232
|
-
|
|
233
|
-
client = RoborockApiClient(
|
|
234
|
-
home_data = await client.get_home_data_v3(
|
|
235
|
-
cache_data
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
connection_cache = self.connection_cache()
|
|
232
|
+
client = RoborockApiClient(connection_cache.email)
|
|
233
|
+
home_data = await client.get_home_data_v3(connection_cache.user_data)
|
|
234
|
+
if connection_cache.cache_data is None:
|
|
235
|
+
connection_cache.cache_data = CacheData()
|
|
236
|
+
connection_cache.cache_data.home_data = home_data
|
|
237
|
+
self.update(connection_cache)
|
|
238
|
+
return connection_cache
|
|
238
239
|
|
|
239
240
|
async def get_devices(self) -> ConnectionCache:
|
|
240
241
|
"""Get device data (uses cache if available, fetches if needed)."""
|
|
241
|
-
|
|
242
|
-
if
|
|
243
|
-
|
|
244
|
-
return
|
|
242
|
+
connection_cache = self.connection_cache()
|
|
243
|
+
if (connection_cache.cache_data is None) or (connection_cache.cache_data.home_data is None):
|
|
244
|
+
connection_cache = await self.refresh_devices()
|
|
245
|
+
return connection_cache
|
|
245
246
|
|
|
246
247
|
async def cleanup(self):
|
|
247
248
|
"""Clean up resources (mainly for session mode)."""
|
|
@@ -266,22 +267,16 @@ class RoborockContext(Cache):
|
|
|
266
267
|
async def get(self) -> CacheData:
|
|
267
268
|
"""Get cached value."""
|
|
268
269
|
_LOGGER.debug("Getting cache data")
|
|
269
|
-
connection_cache = self.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
home_map_info=connection_cache.home_map_info,
|
|
274
|
-
trait_data=connection_cache.trait_data or {},
|
|
275
|
-
)
|
|
270
|
+
connection_cache = self.connection_cache()
|
|
271
|
+
if connection_cache.cache_data is not None:
|
|
272
|
+
return connection_cache.cache_data
|
|
273
|
+
return CacheData()
|
|
276
274
|
|
|
277
275
|
async def set(self, value: CacheData) -> None:
|
|
278
276
|
"""Set value in the cache."""
|
|
279
277
|
_LOGGER.debug("Setting cache data")
|
|
280
|
-
connection_cache = self.
|
|
281
|
-
connection_cache.
|
|
282
|
-
connection_cache.network_info = value.network_info
|
|
283
|
-
connection_cache.home_map_info = value.home_map_info
|
|
284
|
-
connection_cache.trait_data = value.trait_data
|
|
278
|
+
connection_cache = self.connection_cache()
|
|
279
|
+
connection_cache.cache_data = value
|
|
285
280
|
self.update(connection_cache)
|
|
286
281
|
|
|
287
282
|
|
|
@@ -367,9 +362,9 @@ async def discover(ctx):
|
|
|
367
362
|
@async_command
|
|
368
363
|
async def list_devices(ctx):
|
|
369
364
|
context: RoborockContext = ctx.obj
|
|
370
|
-
|
|
365
|
+
connection_cache = await context.get_devices()
|
|
371
366
|
|
|
372
|
-
home_data = cache_data.home_data
|
|
367
|
+
home_data = connection_cache.cache_data.home_data
|
|
373
368
|
|
|
374
369
|
device_name_id = {device.name: device.duid for device in home_data.get_all_devices()}
|
|
375
370
|
click.echo(json.dumps(device_name_id, indent=4))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import dataclasses
|
|
2
2
|
import datetime
|
|
3
|
+
import inspect
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
5
6
|
import re
|
|
@@ -27,7 +28,11 @@ def _camelize(s: str):
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def _decamelize(s: str):
|
|
30
|
-
|
|
31
|
+
# Split before uppercase letters not at the start, and before numbers
|
|
32
|
+
s = re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", s)
|
|
33
|
+
s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s) # Split acronyms followed by normal camelCase
|
|
34
|
+
s = re.sub(r"([a-zA-Z])([0-9]+)", r"\1_\2", s)
|
|
35
|
+
return s.lower()
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def _attr_repr(obj: Any) -> str:
|
|
@@ -64,11 +69,12 @@ class RoborockBase:
|
|
|
64
69
|
if get_origin(class_type) is dict:
|
|
65
70
|
_, value_type = get_args(class_type) # assume keys are only basic types
|
|
66
71
|
return {k: RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
if inspect.isclass(class_type):
|
|
73
|
+
if issubclass(class_type, RoborockBase):
|
|
74
|
+
return class_type.from_dict(value)
|
|
75
|
+
if issubclass(class_type, RoborockModeEnum):
|
|
76
|
+
return class_type.from_code(value)
|
|
77
|
+
if class_type is Any or type(class_type) is str:
|
|
72
78
|
return value
|
|
73
79
|
return class_type(value) # type: ignore[call-arg]
|
|
74
80
|
|
|
@@ -313,7 +313,7 @@ class DeviceFeatures(RoborockBase):
|
|
|
313
313
|
is_support_incremental_map: bool = field(metadata={"new_feature_str_mask": (4194304, 8)})
|
|
314
314
|
is_offline_map_supported: bool = field(metadata={"new_feature_str_mask": (16384, 8)})
|
|
315
315
|
is_super_deep_wash_supported: bool = field(metadata={"new_feature_str_mask": (32768, 8)})
|
|
316
|
-
|
|
316
|
+
is_ces_2022_supported: bool = field(metadata={"new_feature_str_mask": (65536, 8)})
|
|
317
317
|
is_dss_believable: bool = field(metadata={"new_feature_str_mask": (131072, 8)})
|
|
318
318
|
is_main_brush_up_down_supported_from_str: bool = field(metadata={"new_feature_str_mask": (262144, 8)})
|
|
319
319
|
is_goto_pure_clean_path_supported: bool = field(metadata={"new_feature_str_mask": (524288, 8)})
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""This module provides caching functionality for the Roborock device management system.
|
|
2
|
+
|
|
3
|
+
This module defines a cache interface that you may use to cache device
|
|
4
|
+
information to avoid unnecessary API calls. Callers may implement
|
|
5
|
+
this interface to provide their own caching mechanism.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Protocol
|
|
10
|
+
|
|
11
|
+
from roborock.data import CombinedMapInfo, HomeData, NetworkInfo, RoborockBase
|
|
12
|
+
from roborock.device_features import DeviceFeatures
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class DeviceCacheData(RoborockBase):
|
|
17
|
+
"""Data structure for caching device information."""
|
|
18
|
+
|
|
19
|
+
network_info: NetworkInfo | None = None
|
|
20
|
+
"""Network information for the device"""
|
|
21
|
+
|
|
22
|
+
home_map_info: dict[int, CombinedMapInfo] | None = None
|
|
23
|
+
"""Home map information for the device by map_flag."""
|
|
24
|
+
|
|
25
|
+
home_map_content_base64: dict[int, str] | None = None
|
|
26
|
+
"""Home cache content for the device (encoded base64) by map_flag."""
|
|
27
|
+
|
|
28
|
+
device_features: DeviceFeatures | None = None
|
|
29
|
+
"""Device features information."""
|
|
30
|
+
|
|
31
|
+
trait_data: dict[str, Any] | None = None
|
|
32
|
+
"""Trait-specific cached data used internally for caching device features."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CacheData(RoborockBase):
|
|
37
|
+
"""Data structure for caching device information."""
|
|
38
|
+
|
|
39
|
+
home_data: HomeData | None = None
|
|
40
|
+
"""Home data containing device and product information."""
|
|
41
|
+
|
|
42
|
+
device_info: dict[str, DeviceCacheData] = field(default_factory=dict)
|
|
43
|
+
"""Per-device cached information indexed by device DUID."""
|
|
44
|
+
|
|
45
|
+
network_info: dict[str, NetworkInfo] = field(default_factory=dict)
|
|
46
|
+
"""Network information indexed by device DUID.
|
|
47
|
+
|
|
48
|
+
This is deprecated. Use the per-device `network_info` field instead.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
home_map_info: dict[int, CombinedMapInfo] = field(default_factory=dict)
|
|
52
|
+
"""Home map information indexed by map_flag.
|
|
53
|
+
|
|
54
|
+
This is deprecated. Use the per-device `home_map_info` field instead.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
home_map_content: dict[int, bytes] = field(default_factory=dict)
|
|
58
|
+
"""Home cache content for each map data indexed by map_flag.
|
|
59
|
+
|
|
60
|
+
This is deprecated. Use the per-device `home_map_content_base64` field instead.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
home_map_content_base64: dict[int, str] = field(default_factory=dict)
|
|
64
|
+
"""Home cache content for each map data (encoded base64) indexed by map_flag.
|
|
65
|
+
|
|
66
|
+
This is deprecated. Use the per-device `home_map_content_base64` field instead.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
device_features: DeviceFeatures | None = None
|
|
70
|
+
"""Device features information.
|
|
71
|
+
|
|
72
|
+
This is deprecated. Use the per-device `device_features` field instead.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
trait_data: dict[str, Any] | None = None
|
|
76
|
+
"""Trait-specific cached data used internally for caching device features.
|
|
77
|
+
|
|
78
|
+
This is deprecated. Use the per-device `trait_data` field instead.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Cache(Protocol):
|
|
83
|
+
"""Protocol for a cache that can store and retrieve values."""
|
|
84
|
+
|
|
85
|
+
async def get(self) -> CacheData:
|
|
86
|
+
"""Get cached value."""
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
async def set(self, value: CacheData) -> None:
|
|
90
|
+
"""Set value in the cache."""
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class DeviceCache(RoborockBase):
|
|
96
|
+
"""Provides a cache interface for a specific device.
|
|
97
|
+
|
|
98
|
+
This is a convenience wrapper around a general Cache implementation to
|
|
99
|
+
provide device-specific caching functionality.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def __init__(self, duid: str, cache: Cache) -> None:
|
|
103
|
+
"""Initialize the device cache with the given cache implementation."""
|
|
104
|
+
self._duid = duid
|
|
105
|
+
self._cache = cache
|
|
106
|
+
|
|
107
|
+
async def get(self) -> DeviceCacheData:
|
|
108
|
+
"""Get cached device-specific information."""
|
|
109
|
+
cache_data = await self._cache.get()
|
|
110
|
+
if self._duid not in cache_data.device_info:
|
|
111
|
+
cache_data.device_info[self._duid] = DeviceCacheData()
|
|
112
|
+
await self._cache.set(cache_data)
|
|
113
|
+
return cache_data.device_info[self._duid]
|
|
114
|
+
|
|
115
|
+
async def set(self, device_cache_data: DeviceCacheData) -> None:
|
|
116
|
+
"""Set cached device-specific information."""
|
|
117
|
+
cache_data = await self._cache.get()
|
|
118
|
+
cache_data.device_info[self._duid] = device_cache_data
|
|
119
|
+
await self._cache.set(cache_data)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class InMemoryCache(Cache):
|
|
123
|
+
"""In-memory cache implementation."""
|
|
124
|
+
|
|
125
|
+
def __init__(self) -> None:
|
|
126
|
+
"""Initialize the in-memory cache."""
|
|
127
|
+
self._data = CacheData()
|
|
128
|
+
|
|
129
|
+
async def get(self) -> CacheData:
|
|
130
|
+
return self._data
|
|
131
|
+
|
|
132
|
+
async def set(self, value: CacheData) -> None:
|
|
133
|
+
self._data = value
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class NoCache(Cache):
|
|
137
|
+
"""No-op cache implementation."""
|
|
138
|
+
|
|
139
|
+
async def get(self) -> CacheData:
|
|
140
|
+
return CacheData()
|
|
141
|
+
|
|
142
|
+
async def set(self, value: CacheData) -> None:
|
|
143
|
+
pass
|
|
@@ -21,7 +21,7 @@ from roborock.mqtt.session import MqttSession
|
|
|
21
21
|
from roborock.protocol import create_mqtt_params
|
|
22
22
|
from roborock.web_api import RoborockApiClient, UserWebApiClient
|
|
23
23
|
|
|
24
|
-
from .cache import Cache, NoCache
|
|
24
|
+
from .cache import Cache, DeviceCache, NoCache
|
|
25
25
|
from .channel import Channel
|
|
26
26
|
from .mqtt_channel import create_mqtt_channel
|
|
27
27
|
from .traits import Trait, a01, b01, v1
|
|
@@ -177,9 +177,10 @@ async def create_device_manager(
|
|
|
177
177
|
def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDataProduct) -> RoborockDevice:
|
|
178
178
|
channel: Channel
|
|
179
179
|
trait: Trait
|
|
180
|
+
device_cache: DeviceCache = DeviceCache(device.duid, cache)
|
|
180
181
|
match device.pv:
|
|
181
182
|
case DeviceVersion.V1:
|
|
182
|
-
channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device,
|
|
183
|
+
channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device, device_cache)
|
|
183
184
|
trait = v1.create(
|
|
184
185
|
device.duid,
|
|
185
186
|
product,
|
|
@@ -188,7 +189,7 @@ async def create_device_manager(
|
|
|
188
189
|
channel.mqtt_rpc_channel,
|
|
189
190
|
channel.map_rpc_channel,
|
|
190
191
|
web_api,
|
|
191
|
-
|
|
192
|
+
device_cache=device_cache,
|
|
192
193
|
map_parser_config=map_parser_config,
|
|
193
194
|
)
|
|
194
195
|
case DeviceVersion.A01:
|
|
@@ -32,11 +32,12 @@ optional traits:
|
|
|
32
32
|
|
|
33
33
|
import logging
|
|
34
34
|
from dataclasses import dataclass, field, fields
|
|
35
|
+
from functools import cache
|
|
35
36
|
from typing import Any, get_args
|
|
36
37
|
|
|
37
38
|
from roborock.data.containers import HomeData, HomeDataProduct, RoborockBase
|
|
38
39
|
from roborock.data.v1.v1_code_mappings import RoborockDockTypeCode
|
|
39
|
-
from roborock.devices.cache import
|
|
40
|
+
from roborock.devices.cache import DeviceCache
|
|
40
41
|
from roborock.devices.traits import Trait
|
|
41
42
|
from roborock.map.map_parser import MapParserConfig
|
|
42
43
|
from roborock.protocols.v1_protocol import V1RpcChannel
|
|
@@ -131,7 +132,7 @@ class PropertiesApi(Trait):
|
|
|
131
132
|
mqtt_rpc_channel: V1RpcChannel,
|
|
132
133
|
map_rpc_channel: V1RpcChannel,
|
|
133
134
|
web_api: UserWebApiClient,
|
|
134
|
-
|
|
135
|
+
device_cache: DeviceCache,
|
|
135
136
|
map_parser_config: MapParserConfig | None = None,
|
|
136
137
|
) -> None:
|
|
137
138
|
"""Initialize the V1TraitProps."""
|
|
@@ -140,16 +141,16 @@ class PropertiesApi(Trait):
|
|
|
140
141
|
self._mqtt_rpc_channel = mqtt_rpc_channel
|
|
141
142
|
self._map_rpc_channel = map_rpc_channel
|
|
142
143
|
self._web_api = web_api
|
|
143
|
-
self.
|
|
144
|
+
self._device_cache = device_cache
|
|
144
145
|
|
|
145
146
|
self.status = StatusTrait(product)
|
|
146
147
|
self.consumables = ConsumableTrait()
|
|
147
148
|
self.rooms = RoomsTrait(home_data)
|
|
148
149
|
self.maps = MapsTrait(self.status)
|
|
149
150
|
self.map_content = MapContentTrait(map_parser_config)
|
|
150
|
-
self.home = HomeTrait(self.status, self.maps, self.map_content, self.rooms,
|
|
151
|
-
self.device_features = DeviceFeaturesTrait(product.product_nickname,
|
|
152
|
-
self.network_info = NetworkInfoTrait(device_uid,
|
|
151
|
+
self.home = HomeTrait(self.status, self.maps, self.map_content, self.rooms, self._device_cache)
|
|
152
|
+
self.device_features = DeviceFeaturesTrait(product.product_nickname, self._device_cache)
|
|
153
|
+
self.network_info = NetworkInfoTrait(device_uid, self._device_cache)
|
|
153
154
|
self.routines = RoutinesTrait(device_uid, web_api)
|
|
154
155
|
|
|
155
156
|
# Dynamically create any traits that need to be populated
|
|
@@ -239,7 +240,7 @@ class PropertiesApi(Trait):
|
|
|
239
240
|
|
|
240
241
|
async def _get_cached_trait_data(self, name: str) -> Any:
|
|
241
242
|
"""Get the dock type from the status trait or cache."""
|
|
242
|
-
cache_data = await self.
|
|
243
|
+
cache_data = await self._device_cache.get()
|
|
243
244
|
if cache_data.trait_data is None:
|
|
244
245
|
cache_data.trait_data = {}
|
|
245
246
|
_LOGGER.debug("Cached trait data: %s", cache_data.trait_data)
|
|
@@ -247,12 +248,12 @@ class PropertiesApi(Trait):
|
|
|
247
248
|
|
|
248
249
|
async def _set_cached_trait_data(self, name: str, value: Any) -> None:
|
|
249
250
|
"""Set trait-specific cached data."""
|
|
250
|
-
cache_data = await self.
|
|
251
|
+
cache_data = await self._device_cache.get()
|
|
251
252
|
if cache_data.trait_data is None:
|
|
252
253
|
cache_data.trait_data = {}
|
|
253
254
|
cache_data.trait_data[name] = value
|
|
254
255
|
_LOGGER.debug("Updating cached trait data: %s", cache_data.trait_data)
|
|
255
|
-
await self.
|
|
256
|
+
await self._device_cache.set(cache_data)
|
|
256
257
|
|
|
257
258
|
def as_dict(self) -> dict[str, Any]:
|
|
258
259
|
"""Return the trait data as a dictionary."""
|
|
@@ -275,7 +276,7 @@ def create(
|
|
|
275
276
|
mqtt_rpc_channel: V1RpcChannel,
|
|
276
277
|
map_rpc_channel: V1RpcChannel,
|
|
277
278
|
web_api: UserWebApiClient,
|
|
278
|
-
|
|
279
|
+
device_cache: DeviceCache,
|
|
279
280
|
map_parser_config: MapParserConfig | None = None,
|
|
280
281
|
) -> PropertiesApi:
|
|
281
282
|
"""Create traits for V1 devices."""
|
|
@@ -287,6 +288,6 @@ def create(
|
|
|
287
288
|
mqtt_rpc_channel,
|
|
288
289
|
map_rpc_channel,
|
|
289
290
|
web_api,
|
|
290
|
-
|
|
291
|
+
device_cache,
|
|
291
292
|
map_parser_config,
|
|
292
293
|
)
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/device_features.py
RENAMED
|
@@ -2,7 +2,7 @@ from dataclasses import fields
|
|
|
2
2
|
|
|
3
3
|
from roborock.data import AppInitStatus, RoborockProductNickname
|
|
4
4
|
from roborock.device_features import DeviceFeatures
|
|
5
|
-
from roborock.devices.cache import
|
|
5
|
+
from roborock.devices.cache import DeviceCache
|
|
6
6
|
from roborock.devices.traits.v1 import common
|
|
7
7
|
from roborock.roborock_typing import RoborockCommand
|
|
8
8
|
|
|
@@ -12,10 +12,10 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
12
12
|
|
|
13
13
|
command = RoborockCommand.APP_GET_INIT_STATUS
|
|
14
14
|
|
|
15
|
-
def __init__(self, product_nickname: RoborockProductNickname,
|
|
15
|
+
def __init__(self, product_nickname: RoborockProductNickname, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called
|
|
16
16
|
"""Initialize MapContentTrait."""
|
|
17
17
|
self._nickname = product_nickname
|
|
18
|
-
self.
|
|
18
|
+
self._device_cache = device_cache
|
|
19
19
|
# All fields of DeviceFeatures are required. Initialize them to False
|
|
20
20
|
# so we have some known state.
|
|
21
21
|
for field in fields(self):
|
|
@@ -28,14 +28,14 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
|
|
|
28
28
|
change often and this avoids unnecessary RPC calls. This would only
|
|
29
29
|
ever change with a firmware update, so caching is appropriate.
|
|
30
30
|
"""
|
|
31
|
-
cache_data = await self.
|
|
31
|
+
cache_data = await self._device_cache.get()
|
|
32
32
|
if cache_data.device_features is not None:
|
|
33
33
|
self._update_trait_values(cache_data.device_features)
|
|
34
34
|
return
|
|
35
35
|
# Save cached device features
|
|
36
36
|
await super().refresh()
|
|
37
37
|
cache_data.device_features = self
|
|
38
|
-
await self.
|
|
38
|
+
await self._device_cache.set(cache_data)
|
|
39
39
|
|
|
40
40
|
def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures:
|
|
41
41
|
"""Parse the response from the device into a MapContentTrait instance."""
|
|
@@ -22,7 +22,7 @@ from typing import Self
|
|
|
22
22
|
|
|
23
23
|
from roborock.data import CombinedMapInfo, RoborockBase
|
|
24
24
|
from roborock.data.v1.v1_code_mappings import RoborockStateCode
|
|
25
|
-
from roborock.devices.cache import
|
|
25
|
+
from roborock.devices.cache import DeviceCache
|
|
26
26
|
from roborock.devices.traits.v1 import common
|
|
27
27
|
from roborock.exceptions import RoborockDeviceBusy, RoborockException
|
|
28
28
|
from roborock.roborock_typing import RoborockCommand
|
|
@@ -48,7 +48,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
48
48
|
maps_trait: MapsTrait,
|
|
49
49
|
map_content: MapContentTrait,
|
|
50
50
|
rooms_trait: RoomsTrait,
|
|
51
|
-
|
|
51
|
+
device_cache: DeviceCache,
|
|
52
52
|
) -> None:
|
|
53
53
|
"""Initialize the HomeTrait.
|
|
54
54
|
|
|
@@ -70,7 +70,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
70
70
|
self._maps_trait = maps_trait
|
|
71
71
|
self._map_content = map_content
|
|
72
72
|
self._rooms_trait = rooms_trait
|
|
73
|
-
self.
|
|
73
|
+
self._device_cache = device_cache
|
|
74
74
|
self._discovery_completed = False
|
|
75
75
|
self._home_map_info: dict[int, CombinedMapInfo] | None = None
|
|
76
76
|
self._home_map_content: dict[int, MapContent] | None = None
|
|
@@ -86,21 +86,16 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
86
86
|
|
|
87
87
|
After discovery, the home cache will be populated and can be accessed via the `home_map_info` property.
|
|
88
88
|
"""
|
|
89
|
-
|
|
90
|
-
if
|
|
89
|
+
device_cache_data = await self._device_cache.get()
|
|
90
|
+
if device_cache_data and device_cache_data.home_map_info:
|
|
91
91
|
_LOGGER.debug("Home cache already populated, skipping discovery")
|
|
92
|
-
self._home_map_info =
|
|
92
|
+
self._home_map_info = device_cache_data.home_map_info
|
|
93
93
|
self._discovery_completed = True
|
|
94
94
|
try:
|
|
95
|
-
|
|
96
|
-
self.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
else:
|
|
101
|
-
self._home_map_content = {
|
|
102
|
-
k: self._map_content.parse_map_content(v) for k, v in cache_data.home_map_content.items()
|
|
103
|
-
}
|
|
95
|
+
self._home_map_content = {
|
|
96
|
+
k: self._map_content.parse_map_content(base64.b64decode(v))
|
|
97
|
+
for k, v in (device_cache_data.home_map_content_base64 or {}).items()
|
|
98
|
+
}
|
|
104
99
|
except (ValueError, RoborockException) as ex:
|
|
105
100
|
_LOGGER.warning("Failed to parse cached home map content, will re-discover: %s", ex)
|
|
106
101
|
self._home_map_content = {}
|
|
@@ -223,15 +218,14 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
223
218
|
self, home_map_info: dict[int, CombinedMapInfo], home_map_content: dict[int, MapContent]
|
|
224
219
|
) -> None:
|
|
225
220
|
"""Update the entire home cache with new map info and content."""
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
device_cache_data = await self._device_cache.get()
|
|
222
|
+
device_cache_data.home_map_info = home_map_info
|
|
223
|
+
device_cache_data.home_map_content_base64 = {
|
|
229
224
|
k: base64.b64encode(v.raw_api_response).decode("utf-8")
|
|
230
225
|
for k, v in home_map_content.items()
|
|
231
226
|
if v.raw_api_response
|
|
232
227
|
}
|
|
233
|
-
|
|
234
|
-
await self._cache.set(cache_data)
|
|
228
|
+
await self._device_cache.set(device_cache_data)
|
|
235
229
|
self._home_map_info = home_map_info
|
|
236
230
|
self._home_map_content = home_map_content
|
|
237
231
|
|
|
@@ -247,21 +241,17 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
|
|
|
247
241
|
# completed and we want to keep it fresh. Otherwise just update the
|
|
248
242
|
# in memory map below.
|
|
249
243
|
if update_cache:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
cache_data.home_map_content_base64 = {
|
|
255
|
-
k: base64.b64encode(v).decode("utf-8") for k, v in cache_data.home_map_content.items()
|
|
256
|
-
}
|
|
257
|
-
cache_data.home_map_content = {}
|
|
244
|
+
device_cache_data = await self._device_cache.get()
|
|
245
|
+
if device_cache_data.home_map_info is None:
|
|
246
|
+
device_cache_data.home_map_info = {}
|
|
247
|
+
device_cache_data.home_map_info[map_flag] = map_info
|
|
258
248
|
if map_content.raw_api_response:
|
|
259
|
-
if
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
)
|
|
264
|
-
await self.
|
|
249
|
+
if device_cache_data.home_map_content_base64 is None:
|
|
250
|
+
device_cache_data.home_map_content_base64 = {}
|
|
251
|
+
device_cache_data.home_map_content_base64[map_flag] = base64.b64encode(
|
|
252
|
+
map_content.raw_api_response
|
|
253
|
+
).decode("utf-8")
|
|
254
|
+
await self._device_cache.set(device_cache_data)
|
|
265
255
|
|
|
266
256
|
if self._home_map_info is None:
|
|
267
257
|
self._home_map_info = {}
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
7
|
from roborock.data import NetworkInfo
|
|
8
|
-
from roborock.devices.cache import
|
|
8
|
+
from roborock.devices.cache import DeviceCache
|
|
9
9
|
from roborock.devices.traits.v1 import common
|
|
10
10
|
from roborock.roborock_typing import RoborockCommand
|
|
11
11
|
|
|
@@ -24,19 +24,19 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
|
|
|
24
24
|
|
|
25
25
|
command = RoborockCommand.GET_NETWORK_INFO
|
|
26
26
|
|
|
27
|
-
def __init__(self, device_uid: str,
|
|
27
|
+
def __init__(self, device_uid: str, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called
|
|
28
28
|
"""Initialize the trait."""
|
|
29
29
|
self._device_uid = device_uid
|
|
30
|
-
self.
|
|
30
|
+
self._device_cache = device_cache
|
|
31
31
|
self.ip = ""
|
|
32
32
|
|
|
33
33
|
async def refresh(self) -> None:
|
|
34
34
|
"""Refresh the network info from the cache."""
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
if
|
|
36
|
+
device_cache_data = await self._device_cache.get()
|
|
37
|
+
if device_cache_data.network_info:
|
|
38
38
|
_LOGGER.debug("Using cached network info for device %s", self._device_uid)
|
|
39
|
-
self._update_trait_values(network_info)
|
|
39
|
+
self._update_trait_values(device_cache_data.network_info)
|
|
40
40
|
return
|
|
41
41
|
|
|
42
42
|
# Load from device if not in cache
|
|
@@ -44,8 +44,9 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
|
|
|
44
44
|
await super().refresh()
|
|
45
45
|
|
|
46
46
|
# Update the cache with the new network info
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
device_cache_data = await self._device_cache.get()
|
|
48
|
+
device_cache_data.network_info = self
|
|
49
|
+
await self._device_cache.set(device_cache_data)
|
|
49
50
|
|
|
50
51
|
def _parse_response(self, response: common.V1ResponseData) -> NetworkInfo:
|
|
51
52
|
"""Parse the response from the device into a NetworkInfo."""
|
|
@@ -31,7 +31,7 @@ from roborock.protocols.v1_protocol import (
|
|
|
31
31
|
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
|
|
32
32
|
from roborock.roborock_typing import RoborockCommand
|
|
33
33
|
|
|
34
|
-
from .cache import
|
|
34
|
+
from .cache import DeviceCache
|
|
35
35
|
from .channel import Channel
|
|
36
36
|
from .local_channel import LocalChannel, LocalSession, create_local_session
|
|
37
37
|
from .mqtt_channel import MqttChannel
|
|
@@ -170,7 +170,7 @@ class V1Channel(Channel):
|
|
|
170
170
|
security_data: SecurityData,
|
|
171
171
|
mqtt_channel: MqttChannel,
|
|
172
172
|
local_session: LocalSession,
|
|
173
|
-
|
|
173
|
+
device_cache: DeviceCache,
|
|
174
174
|
) -> None:
|
|
175
175
|
"""Initialize the V1Channel."""
|
|
176
176
|
self._device_uid = device_uid
|
|
@@ -182,7 +182,7 @@ class V1Channel(Channel):
|
|
|
182
182
|
self._mqtt_unsub: Callable[[], None] | None = None
|
|
183
183
|
self._local_unsub: Callable[[], None] | None = None
|
|
184
184
|
self._callback: Callable[[RoborockMessage], None] | None = None
|
|
185
|
-
self.
|
|
185
|
+
self._device_cache = device_cache
|
|
186
186
|
self._reconnect_task: asyncio.Task[None] | None = None
|
|
187
187
|
self._last_network_info_refresh: datetime.datetime | None = None
|
|
188
188
|
|
|
@@ -318,24 +318,27 @@ class V1Channel(Channel):
|
|
|
318
318
|
|
|
319
319
|
This is a cloud only command used to get the local device's IP address.
|
|
320
320
|
"""
|
|
321
|
-
|
|
322
|
-
|
|
321
|
+
device_cache_data = await self._device_cache.get()
|
|
322
|
+
|
|
323
|
+
if prefer_cache and device_cache_data.network_info:
|
|
323
324
|
_LOGGER.debug("Using cached network info for device %s", self._device_uid)
|
|
324
|
-
return network_info
|
|
325
|
+
return device_cache_data.network_info
|
|
325
326
|
try:
|
|
326
327
|
network_info = await self.mqtt_rpc_channel.send_command(
|
|
327
328
|
RoborockCommand.GET_NETWORK_INFO, response_type=NetworkInfo
|
|
328
329
|
)
|
|
329
330
|
except RoborockException as e:
|
|
330
331
|
_LOGGER.debug("Error fetching network info for device %s", self._device_uid)
|
|
331
|
-
if
|
|
332
|
+
if device_cache_data.network_info:
|
|
332
333
|
_LOGGER.debug("Falling back to cached network info for device %s after error", self._device_uid)
|
|
333
|
-
return network_info
|
|
334
|
+
return device_cache_data.network_info
|
|
334
335
|
raise RoborockException(f"Network info failed for device {self._device_uid}") from e
|
|
335
336
|
_LOGGER.debug("Network info for device %s: %s", self._device_uid, network_info)
|
|
336
337
|
self._last_network_info_refresh = datetime.datetime.now(datetime.UTC)
|
|
337
|
-
|
|
338
|
-
await self.
|
|
338
|
+
|
|
339
|
+
device_cache_data = await self._device_cache.get()
|
|
340
|
+
device_cache_data.network_info = network_info
|
|
341
|
+
await self._device_cache.set(device_cache_data)
|
|
339
342
|
return network_info
|
|
340
343
|
|
|
341
344
|
async def _local_connect(self, *, prefer_cache: bool = True) -> None:
|
|
@@ -425,10 +428,16 @@ def create_v1_channel(
|
|
|
425
428
|
mqtt_params: MqttParams,
|
|
426
429
|
mqtt_session: MqttSession,
|
|
427
430
|
device: HomeDataDevice,
|
|
428
|
-
|
|
431
|
+
device_cache: DeviceCache,
|
|
429
432
|
) -> V1Channel:
|
|
430
433
|
"""Create a V1Channel for the given device."""
|
|
431
434
|
security_data = create_security_data(user_data.rriot)
|
|
432
435
|
mqtt_channel = MqttChannel(mqtt_session, device.duid, device.local_key, user_data.rriot, mqtt_params)
|
|
433
436
|
local_session = create_local_session(device.local_key)
|
|
434
|
-
return V1Channel(
|
|
437
|
+
return V1Channel(
|
|
438
|
+
device.duid,
|
|
439
|
+
security_data,
|
|
440
|
+
mqtt_channel,
|
|
441
|
+
local_session=local_session,
|
|
442
|
+
device_cache=device_cache,
|
|
443
|
+
)
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"""This module provides caching functionality for the Roborock device management system.
|
|
2
|
-
|
|
3
|
-
This module defines a cache interface that you may use to cache device
|
|
4
|
-
information to avoid unnecessary API calls. Callers may implement
|
|
5
|
-
this interface to provide their own caching mechanism.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass, field
|
|
9
|
-
from typing import Any, Protocol
|
|
10
|
-
|
|
11
|
-
from roborock.data import CombinedMapInfo, HomeData, NetworkInfo, RoborockBase
|
|
12
|
-
from roborock.device_features import DeviceFeatures
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class CacheData(RoborockBase):
|
|
17
|
-
"""Data structure for caching device information."""
|
|
18
|
-
|
|
19
|
-
home_data: HomeData | None = None
|
|
20
|
-
"""Home data containing device and product information."""
|
|
21
|
-
|
|
22
|
-
network_info: dict[str, NetworkInfo] = field(default_factory=dict)
|
|
23
|
-
"""Network information indexed by device DUID."""
|
|
24
|
-
|
|
25
|
-
home_map_info: dict[int, CombinedMapInfo] = field(default_factory=dict)
|
|
26
|
-
"""Home map information indexed by map_flag."""
|
|
27
|
-
|
|
28
|
-
home_map_content: dict[int, bytes] = field(default_factory=dict)
|
|
29
|
-
"""Home cache content for each map data indexed by map_flag.
|
|
30
|
-
|
|
31
|
-
This is deprecated in favor of `home_map_content_base64`.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
home_map_content_base64: dict[int, str] = field(default_factory=dict)
|
|
35
|
-
"""Home cache content for each map data (encoded base64) indexed by map_flag."""
|
|
36
|
-
|
|
37
|
-
device_features: DeviceFeatures | None = None
|
|
38
|
-
"""Device features information."""
|
|
39
|
-
|
|
40
|
-
trait_data: dict[str, Any] | None = None
|
|
41
|
-
"""Trait-specific cached data used internally for caching device features."""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class Cache(Protocol):
|
|
45
|
-
"""Protocol for a cache that can store and retrieve values."""
|
|
46
|
-
|
|
47
|
-
async def get(self) -> CacheData:
|
|
48
|
-
"""Get cached value."""
|
|
49
|
-
...
|
|
50
|
-
|
|
51
|
-
async def set(self, value: CacheData) -> None:
|
|
52
|
-
"""Set value in the cache."""
|
|
53
|
-
...
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class InMemoryCache(Cache):
|
|
57
|
-
"""In-memory cache implementation."""
|
|
58
|
-
|
|
59
|
-
def __init__(self) -> None:
|
|
60
|
-
"""Initialize the in-memory cache."""
|
|
61
|
-
self._data = CacheData()
|
|
62
|
-
|
|
63
|
-
async def get(self) -> CacheData:
|
|
64
|
-
return self._data
|
|
65
|
-
|
|
66
|
-
async def set(self, value: CacheData) -> None:
|
|
67
|
-
self._data = value
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class NoCache(Cache):
|
|
71
|
-
"""No-op cache implementation."""
|
|
72
|
-
|
|
73
|
-
async def get(self) -> CacheData:
|
|
74
|
-
return CacheData()
|
|
75
|
-
|
|
76
|
-
async def set(self, value: CacheData) -> None:
|
|
77
|
-
pass
|
|
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.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/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
|
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/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.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/smart_wash_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/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.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_local_client_v1.py
RENAMED
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|