python-roborock 3.10.0__tar.gz → 3.10.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.10.0 → python_roborock-3.10.2}/PKG-INFO +1 -1
- {python_roborock-3.10.0 → python_roborock-3.10.2}/pyproject.toml +1 -1
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/containers.py +3 -1
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/mqtt/roborock_session.py +56 -7
- {python_roborock-3.10.0 → python_roborock-3.10.2}/.gitignore +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/LICENSE +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/README.md +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/api.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/callbacks.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/cli.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/cloud_api.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/command_cache.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/const.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/code_mappings.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/device_features.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/README.md +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/cache.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/channel.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/device.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/device_manager.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/file_cache.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/local_channel.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/exceptions.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/map/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/map/map_parser.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/mqtt/session.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/protocol.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/protocols/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/py.typed +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/roborock_future.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/roborock_message.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/roborock_typing.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/util.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-3.10.0 → python_roborock-3.10.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.10.
|
|
3
|
+
Version: 3.10.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.10.
|
|
3
|
+
version = "3.10.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"
|
|
@@ -67,7 +67,9 @@ class RoborockBase:
|
|
|
67
67
|
sub_type = get_args(class_type)[0]
|
|
68
68
|
return [RoborockBase._convert_to_class_obj(sub_type, obj) for obj in value]
|
|
69
69
|
if get_origin(class_type) is dict:
|
|
70
|
-
|
|
70
|
+
key_type, value_type = get_args(class_type)
|
|
71
|
+
if key_type is not None:
|
|
72
|
+
return {key_type(k): RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
|
|
71
73
|
return {k: RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
|
|
72
74
|
if inspect.isclass(class_type):
|
|
73
75
|
if issubclass(class_type, RoborockBase):
|
|
@@ -24,7 +24,8 @@ from .session import MqttParams, MqttSession, MqttSessionException
|
|
|
24
24
|
_LOGGER = logging.getLogger(__name__)
|
|
25
25
|
_MQTT_LOGGER = logging.getLogger(f"{__name__}.aiomqtt")
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
CLIENT_KEEPALIVE = datetime.timedelta(seconds=120)
|
|
28
|
+
TOPIC_KEEPALIVE = datetime.timedelta(seconds=60)
|
|
28
29
|
|
|
29
30
|
# Exponential backoff parameters
|
|
30
31
|
MIN_BACKOFF_INTERVAL = datetime.timedelta(seconds=10)
|
|
@@ -47,7 +48,11 @@ class RoborockMqttSession(MqttSession):
|
|
|
47
48
|
re-established.
|
|
48
49
|
"""
|
|
49
50
|
|
|
50
|
-
def __init__(
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
params: MqttParams,
|
|
54
|
+
topic_idle_timeout: datetime.timedelta = TOPIC_KEEPALIVE,
|
|
55
|
+
):
|
|
51
56
|
self._params = params
|
|
52
57
|
self._reconnect_task: asyncio.Task[None] | None = None
|
|
53
58
|
self._healthy = False
|
|
@@ -57,6 +62,8 @@ class RoborockMqttSession(MqttSession):
|
|
|
57
62
|
self._client_lock = asyncio.Lock()
|
|
58
63
|
self._listeners: CallbackMap[str, bytes] = CallbackMap(_LOGGER)
|
|
59
64
|
self._connection_task: asyncio.Task[None] | None = None
|
|
65
|
+
self._topic_idle_timeout = topic_idle_timeout
|
|
66
|
+
self._idle_timers: dict[str, asyncio.Task[None]] = {}
|
|
60
67
|
|
|
61
68
|
@property
|
|
62
69
|
def connected(self) -> bool:
|
|
@@ -86,11 +93,15 @@ class RoborockMqttSession(MqttSession):
|
|
|
86
93
|
async def close(self) -> None:
|
|
87
94
|
"""Cancels the MQTT loop and shutdown the client library."""
|
|
88
95
|
self._stop = True
|
|
89
|
-
tasks = [task for task in [self._connection_task, self._reconnect_task] if task]
|
|
96
|
+
tasks = [task for task in [self._connection_task, self._reconnect_task, *self._idle_timers.values()] if task]
|
|
97
|
+
self._connection_task = None
|
|
98
|
+
self._reconnect_task = None
|
|
99
|
+
self._idle_timers.clear()
|
|
100
|
+
|
|
90
101
|
for task in tasks:
|
|
91
102
|
task.cancel()
|
|
92
103
|
try:
|
|
93
|
-
await asyncio.gather(*tasks)
|
|
104
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
94
105
|
except asyncio.CancelledError:
|
|
95
106
|
pass
|
|
96
107
|
|
|
@@ -183,7 +194,7 @@ class RoborockMqttSession(MqttSession):
|
|
|
183
194
|
port=params.port,
|
|
184
195
|
username=params.username,
|
|
185
196
|
password=params.password,
|
|
186
|
-
keepalive=
|
|
197
|
+
keepalive=int(CLIENT_KEEPALIVE.total_seconds()),
|
|
187
198
|
protocol=aiomqtt.ProtocolVersion.V5,
|
|
188
199
|
tls_params=TLSParameters() if params.tls else None,
|
|
189
200
|
timeout=params.timeout,
|
|
@@ -210,9 +221,17 @@ class RoborockMqttSession(MqttSession):
|
|
|
210
221
|
The callback will be called with the message payload as a bytes object. The callback
|
|
211
222
|
should not block since it runs in the async loop. It should not raise any exceptions.
|
|
212
223
|
|
|
213
|
-
The returned callable unsubscribes from the topic when called
|
|
224
|
+
The returned callable unsubscribes from the topic when called, but will delay actual
|
|
225
|
+
unsubscription for the idle timeout period. If a new subscription comes in during the
|
|
226
|
+
timeout, the timer is cancelled and the subscription is reused.
|
|
214
227
|
"""
|
|
215
228
|
_LOGGER.debug("Subscribing to topic %s", topic)
|
|
229
|
+
|
|
230
|
+
# If there is an idle timer for this topic, cancel it (reuse subscription)
|
|
231
|
+
if idle_timer := self._idle_timers.pop(topic, None):
|
|
232
|
+
idle_timer.cancel()
|
|
233
|
+
_LOGGER.debug("Cancelled idle timer for topic %s (reused subscription)", topic)
|
|
234
|
+
|
|
216
235
|
unsub = self._listeners.add_callback(topic, callback)
|
|
217
236
|
|
|
218
237
|
async with self._client_lock:
|
|
@@ -221,11 +240,41 @@ class RoborockMqttSession(MqttSession):
|
|
|
221
240
|
try:
|
|
222
241
|
await self._client.subscribe(topic)
|
|
223
242
|
except MqttError as err:
|
|
243
|
+
# Clean up the callback if subscription fails
|
|
244
|
+
unsub()
|
|
224
245
|
raise MqttSessionException(f"Error subscribing to topic: {err}") from err
|
|
225
246
|
else:
|
|
226
247
|
_LOGGER.debug("Client not connected, will establish subscription later")
|
|
227
248
|
|
|
228
|
-
|
|
249
|
+
def schedule_unsubscribe():
|
|
250
|
+
async def idle_unsubscribe():
|
|
251
|
+
try:
|
|
252
|
+
await asyncio.sleep(self._topic_idle_timeout.total_seconds())
|
|
253
|
+
# Only unsubscribe if there are no callbacks left for this topic
|
|
254
|
+
if not self._listeners.get_callbacks(topic):
|
|
255
|
+
async with self._client_lock:
|
|
256
|
+
if self._client:
|
|
257
|
+
_LOGGER.debug("Idle timeout expired, unsubscribing from topic %s", topic)
|
|
258
|
+
try:
|
|
259
|
+
await self._client.unsubscribe(topic)
|
|
260
|
+
except MqttError as err:
|
|
261
|
+
_LOGGER.warning("Error unsubscribing from topic %s: %s", topic, err)
|
|
262
|
+
# Clean up timer from dict
|
|
263
|
+
self._idle_timers.pop(topic, None)
|
|
264
|
+
except asyncio.CancelledError:
|
|
265
|
+
_LOGGER.debug("Idle unsubscribe for topic %s cancelled", topic)
|
|
266
|
+
|
|
267
|
+
# Start the idle timer task
|
|
268
|
+
task = asyncio.create_task(idle_unsubscribe())
|
|
269
|
+
self._idle_timers[topic] = task
|
|
270
|
+
|
|
271
|
+
def delayed_unsub():
|
|
272
|
+
unsub() # Remove the callback from CallbackMap
|
|
273
|
+
# If no more callbacks for this topic, start idle timer
|
|
274
|
+
if not self._listeners.get_callbacks(topic):
|
|
275
|
+
schedule_unsubscribe()
|
|
276
|
+
|
|
277
|
+
return delayed_unsub
|
|
229
278
|
|
|
230
279
|
async def publish(self, topic: str, message: bytes) -> None:
|
|
231
280
|
"""Publish a message on the topic."""
|
|
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.10.0 → python_roborock-3.10.2}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/data/b01_q10/b01_q10_containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.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.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.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
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/devices/traits/v1/network_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.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.10.0 → python_roborock-3.10.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.10.0 → python_roborock-3.10.2}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.10.0 → python_roborock-3.10.2}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|