python-roborock 3.14.3__tar.gz → 3.16.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.14.3 → python_roborock-3.16.0}/PKG-INFO +1 -1
- {python_roborock-3.14.3 → python_roborock-3.16.0}/pyproject.toml +1 -1
- python_roborock-3.16.0/roborock/data/b01_q7/b01_q7_containers.py +130 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/containers.py +9 -0
- python_roborock-3.16.0/roborock/devices/b01_channel.py +77 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/device.py +20 -8
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/b01/q7/__init__.py +5 -3
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/mqtt/roborock_session.py +29 -15
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/protocol.py +9 -6
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/protocols/b01_protocol.py +5 -5
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/web_api.py +23 -0
- python_roborock-3.14.3/roborock/data/b01_q7/b01_q7_containers.py +0 -130
- python_roborock-3.14.3/roborock/devices/b01_channel.py +0 -27
- {python_roborock-3.14.3 → python_roborock-3.16.0}/.gitignore +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/LICENSE +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/README.md +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/api.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/callbacks.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/cli.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/cloud_api.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/command_cache.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/const.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/b01_q10/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/b01_q7/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/code_mappings.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/dyad/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/dyad/dyad_code_mappings.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/dyad/dyad_containers.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/v1/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/v1/v1_clean_modes.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/v1/v1_code_mappings.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/v1/v1_containers.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/zeo/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/zeo/zeo_code_mappings.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/zeo/zeo_containers.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/device_features.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/README.md +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/cache.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/channel.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/device_manager.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/file_cache.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/b01/q10/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/traits_mixin.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/child_lock.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/command.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/common.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/device_features.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/flow_led_status.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/home.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/led_status.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/map_content.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/maps.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/network_info.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/rooms.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/routines.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/exceptions.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/map/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/mqtt/health_manager.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/protocols/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/protocols/v1_protocol.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/py.typed +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/roborock_future.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/roborock_message.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/util.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.16.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.16.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"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from ..containers import RoborockBase
|
|
4
|
+
from .b01_q7_code_mappings import B01Fault, SCWindMapping, WorkModeMapping, WorkStatusMapping
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class NetStatus(RoborockBase):
|
|
9
|
+
"""Represents the network status of the device."""
|
|
10
|
+
|
|
11
|
+
rssi: str
|
|
12
|
+
loss: int
|
|
13
|
+
ping: int
|
|
14
|
+
ip: str
|
|
15
|
+
mac: str
|
|
16
|
+
ssid: str
|
|
17
|
+
frequency: int
|
|
18
|
+
bssid: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class OrderTotal(RoborockBase):
|
|
23
|
+
"""Represents the order total information."""
|
|
24
|
+
|
|
25
|
+
total: int
|
|
26
|
+
enable: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Privacy(RoborockBase):
|
|
31
|
+
"""Represents the privacy settings of the device."""
|
|
32
|
+
|
|
33
|
+
ai_recognize: int
|
|
34
|
+
dirt_recognize: int
|
|
35
|
+
pet_recognize: int
|
|
36
|
+
carpet_turbo: int
|
|
37
|
+
carpet_avoid: int
|
|
38
|
+
carpet_show: int
|
|
39
|
+
map_uploads: int
|
|
40
|
+
ai_agent: int
|
|
41
|
+
ai_avoidance: int
|
|
42
|
+
record_uploads: int
|
|
43
|
+
along_floor: int
|
|
44
|
+
auto_upgrade: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class PvCharging(RoborockBase):
|
|
49
|
+
"""Represents the photovoltaic charging status."""
|
|
50
|
+
|
|
51
|
+
status: int
|
|
52
|
+
begin_time: int
|
|
53
|
+
end_time: int
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class Recommend(RoborockBase):
|
|
58
|
+
"""Represents cleaning recommendations."""
|
|
59
|
+
|
|
60
|
+
sill: int
|
|
61
|
+
wall: int
|
|
62
|
+
room_id: list[int] = field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class B01Props(RoborockBase):
|
|
67
|
+
"""
|
|
68
|
+
Represents the complete properties and status for a Roborock B01 model.
|
|
69
|
+
This dataclass is generated based on the device's status JSON object.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
status: WorkStatusMapping | None = None
|
|
73
|
+
fault: B01Fault | None = None
|
|
74
|
+
wind: SCWindMapping | None = None
|
|
75
|
+
water: int | None = None
|
|
76
|
+
mode: int | None = None
|
|
77
|
+
quantity: int | None = None
|
|
78
|
+
alarm: int | None = None
|
|
79
|
+
volume: int | None = None
|
|
80
|
+
hypa: int | None = None
|
|
81
|
+
main_brush: int | None = None
|
|
82
|
+
side_brush: int | None = None
|
|
83
|
+
mop_life: int | None = None
|
|
84
|
+
main_sensor: int | None = None
|
|
85
|
+
net_status: NetStatus | None = None
|
|
86
|
+
repeat_state: int | None = None
|
|
87
|
+
tank_state: int | None = None
|
|
88
|
+
sweep_type: int | None = None
|
|
89
|
+
clean_path_preference: int | None = None
|
|
90
|
+
cloth_state: int | None = None
|
|
91
|
+
time_zone: int | None = None
|
|
92
|
+
time_zone_info: str | None = None
|
|
93
|
+
language: int | None = None
|
|
94
|
+
cleaning_time: int | None = None
|
|
95
|
+
real_clean_time: int | None = None
|
|
96
|
+
cleaning_area: int | None = None
|
|
97
|
+
custom_type: int | None = None
|
|
98
|
+
sound: int | None = None
|
|
99
|
+
work_mode: WorkModeMapping | None = None
|
|
100
|
+
station_act: int | None = None
|
|
101
|
+
charge_state: int | None = None
|
|
102
|
+
current_map_id: int | None = None
|
|
103
|
+
map_num: int | None = None
|
|
104
|
+
dust_action: int | None = None
|
|
105
|
+
quiet_is_open: int | None = None
|
|
106
|
+
quiet_begin_time: int | None = None
|
|
107
|
+
quiet_end_time: int | None = None
|
|
108
|
+
clean_finish: int | None = None
|
|
109
|
+
voice_type: int | None = None
|
|
110
|
+
voice_type_version: int | None = None
|
|
111
|
+
order_total: OrderTotal | None = None
|
|
112
|
+
build_map: int | None = None
|
|
113
|
+
privacy: Privacy | None = None
|
|
114
|
+
dust_auto_state: int | None = None
|
|
115
|
+
dust_frequency: int | None = None
|
|
116
|
+
child_lock: int | None = None
|
|
117
|
+
multi_floor: int | None = None
|
|
118
|
+
map_save: int | None = None
|
|
119
|
+
light_mode: int | None = None
|
|
120
|
+
green_laser: int | None = None
|
|
121
|
+
dust_bag_used: int | None = None
|
|
122
|
+
order_save_mode: int | None = None
|
|
123
|
+
manufacturer: str | None = None
|
|
124
|
+
back_to_wash: int | None = None
|
|
125
|
+
charge_station_type: int | None = None
|
|
126
|
+
pv_cut_charge: int | None = None
|
|
127
|
+
pv_charging: PvCharging | None = None
|
|
128
|
+
serial_number: str | None = None
|
|
129
|
+
recommend: Recommend | None = None
|
|
130
|
+
add_sweep_status: int | None = None
|
|
@@ -284,6 +284,15 @@ class HomeDataScene(RoborockBase):
|
|
|
284
284
|
name: str
|
|
285
285
|
|
|
286
286
|
|
|
287
|
+
@dataclass
|
|
288
|
+
class HomeDataSchedule(RoborockBase):
|
|
289
|
+
id: int
|
|
290
|
+
cron: str
|
|
291
|
+
repeated: bool
|
|
292
|
+
enabled: bool
|
|
293
|
+
param: dict | None = None
|
|
294
|
+
|
|
295
|
+
|
|
287
296
|
@dataclass
|
|
288
297
|
class HomeData(RoborockBase):
|
|
289
298
|
id: int
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Thin wrapper around the MQTT channel for Roborock B01 devices."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from roborock.exceptions import RoborockException
|
|
11
|
+
from roborock.protocols.b01_protocol import (
|
|
12
|
+
CommandType,
|
|
13
|
+
ParamsType,
|
|
14
|
+
decode_rpc_response,
|
|
15
|
+
encode_mqtt_payload,
|
|
16
|
+
)
|
|
17
|
+
from roborock.roborock_message import RoborockMessage
|
|
18
|
+
from roborock.util import get_next_int
|
|
19
|
+
|
|
20
|
+
from .mqtt_channel import MqttChannel
|
|
21
|
+
|
|
22
|
+
_LOGGER = logging.getLogger(__name__)
|
|
23
|
+
_TIMEOUT = 10.0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def send_decoded_command(
|
|
27
|
+
mqtt_channel: MqttChannel,
|
|
28
|
+
dps: int,
|
|
29
|
+
command: CommandType,
|
|
30
|
+
params: ParamsType,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""Send a command on the MQTT channel and get a decoded response."""
|
|
33
|
+
_LOGGER.debug("Sending MQTT command: %s", params)
|
|
34
|
+
msg_id = str(get_next_int(100000000000, 999999999999))
|
|
35
|
+
roborock_message = encode_mqtt_payload(dps, command, params, msg_id)
|
|
36
|
+
future: asyncio.Future[dict[str, Any]] = asyncio.get_running_loop().create_future()
|
|
37
|
+
|
|
38
|
+
def find_response(response_message: RoborockMessage) -> None:
|
|
39
|
+
"""Handle incoming messages and resolve the future."""
|
|
40
|
+
try:
|
|
41
|
+
decoded_dps = decode_rpc_response(response_message)
|
|
42
|
+
except RoborockException as ex:
|
|
43
|
+
_LOGGER.info("Failed to decode b01 message: %s: %s", response_message, ex)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
for dps_value in decoded_dps.values():
|
|
47
|
+
# valid responses are JSON strings wrapped in the dps value
|
|
48
|
+
if not isinstance(dps_value, str):
|
|
49
|
+
_LOGGER.debug("Received unexpected response: %s", dps_value)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
inner = json.loads(dps_value)
|
|
54
|
+
except (json.JSONDecodeError, TypeError):
|
|
55
|
+
_LOGGER.debug("Received unexpected response: %s", dps_value)
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
if isinstance(inner, dict) and inner.get("msgId") == msg_id:
|
|
59
|
+
_LOGGER.debug("Received query response: %s", inner)
|
|
60
|
+
data = inner.get("data")
|
|
61
|
+
if not future.done():
|
|
62
|
+
if isinstance(data, dict):
|
|
63
|
+
future.set_result(data)
|
|
64
|
+
else:
|
|
65
|
+
future.set_exception(RoborockException(f"Unexpected data type for response: {data}"))
|
|
66
|
+
|
|
67
|
+
unsub = await mqtt_channel.subscribe(find_response)
|
|
68
|
+
|
|
69
|
+
_LOGGER.debug("Sending MQTT message: %s", roborock_message)
|
|
70
|
+
try:
|
|
71
|
+
await mqtt_channel.publish(roborock_message)
|
|
72
|
+
try:
|
|
73
|
+
return await asyncio.wait_for(future, timeout=_TIMEOUT)
|
|
74
|
+
except TimeoutError as ex:
|
|
75
|
+
raise RoborockException(f"Command timed out after {_TIMEOUT}s") from ex
|
|
76
|
+
finally:
|
|
77
|
+
unsub()
|
|
@@ -147,34 +147,45 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
147
147
|
called. The device will automatically attempt to reconnect if the connection
|
|
148
148
|
is lost.
|
|
149
149
|
"""
|
|
150
|
-
|
|
150
|
+
# The future will be set to True if the first attempt succeeds, False if
|
|
151
|
+
# it fails, or an exception if an unexpected error occurs.
|
|
152
|
+
# We use this to wait a short time for the first attempt to complete. We
|
|
153
|
+
# don't actually care about the result, just that we waited long enough.
|
|
154
|
+
start_attempt: asyncio.Future[bool] = asyncio.Future()
|
|
151
155
|
|
|
152
156
|
async def connect_loop() -> None:
|
|
153
|
-
backoff = MIN_BACKOFF_INTERVAL
|
|
154
157
|
try:
|
|
158
|
+
backoff = MIN_BACKOFF_INTERVAL
|
|
155
159
|
while True:
|
|
156
160
|
try:
|
|
157
161
|
await self.connect()
|
|
158
|
-
start_attempt.
|
|
162
|
+
if not start_attempt.done():
|
|
163
|
+
start_attempt.set_result(True)
|
|
159
164
|
self._has_connected = True
|
|
160
165
|
self._ready_callbacks(self)
|
|
161
166
|
return
|
|
162
167
|
except RoborockException as e:
|
|
163
|
-
start_attempt.
|
|
168
|
+
if not start_attempt.done():
|
|
169
|
+
start_attempt.set_result(False)
|
|
164
170
|
self._logger.info("Failed to connect (retry %s): %s", backoff.total_seconds(), e)
|
|
165
171
|
await asyncio.sleep(backoff.total_seconds())
|
|
166
172
|
backoff = min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_INTERVAL)
|
|
173
|
+
except Exception as e: # pylint: disable=broad-except
|
|
174
|
+
if not start_attempt.done():
|
|
175
|
+
start_attempt.set_exception(e)
|
|
176
|
+
self._logger.exception("Uncaught error during connect: %s", e)
|
|
177
|
+
return
|
|
167
178
|
except asyncio.CancelledError:
|
|
168
179
|
self._logger.debug("connect_loop was cancelled for device %s", self.duid)
|
|
169
|
-
# Clean exit on cancellation
|
|
170
|
-
return
|
|
171
180
|
finally:
|
|
172
|
-
start_attempt.
|
|
181
|
+
if not start_attempt.done():
|
|
182
|
+
start_attempt.set_result(False)
|
|
173
183
|
|
|
174
184
|
self._connect_task = asyncio.create_task(connect_loop())
|
|
175
185
|
|
|
176
186
|
try:
|
|
177
|
-
|
|
187
|
+
async with asyncio.timeout(START_ATTEMPT_TIMEOUT.total_seconds()):
|
|
188
|
+
await start_attempt
|
|
178
189
|
except TimeoutError:
|
|
179
190
|
self._logger.debug("Initial connection attempt took longer than expected, will keep trying in background")
|
|
180
191
|
|
|
@@ -189,6 +200,7 @@ class RoborockDevice(ABC, TraitsMixin):
|
|
|
189
200
|
except RoborockException:
|
|
190
201
|
unsub()
|
|
191
202
|
raise
|
|
203
|
+
self._logger.info("Connected to device")
|
|
192
204
|
self._unsub = unsub
|
|
193
205
|
|
|
194
206
|
async def close(self) -> None:
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/b01/q7/__init__.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Traits for Q7 B01 devices.
|
|
2
2
|
Potentially other devices may fall into this category in the future."""
|
|
3
3
|
|
|
4
|
+
from roborock import B01Props
|
|
4
5
|
from roborock.devices.b01_channel import send_decoded_command
|
|
5
6
|
from roborock.devices.mqtt_channel import MqttChannel
|
|
6
7
|
from roborock.devices.traits import Trait
|
|
@@ -13,17 +14,18 @@ __all__ = [
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class Q7PropertiesApi(Trait):
|
|
16
|
-
"""API for interacting with
|
|
17
|
+
"""API for interacting with B01 devices."""
|
|
17
18
|
|
|
18
19
|
def __init__(self, channel: MqttChannel) -> None:
|
|
19
20
|
"""Initialize the B01Props API."""
|
|
20
21
|
self._channel = channel
|
|
21
22
|
|
|
22
|
-
async def query_values(self, props: list[RoborockB01Props]) -> None:
|
|
23
|
+
async def query_values(self, props: list[RoborockB01Props]) -> B01Props | None:
|
|
23
24
|
"""Query the device for the values of the given Q7 properties."""
|
|
24
|
-
await send_decoded_command(
|
|
25
|
+
result = await send_decoded_command(
|
|
25
26
|
self._channel, dps=10000, command=RoborockB01Q7Methods.GET_PROP, params={"property": props}
|
|
26
27
|
)
|
|
28
|
+
return B01Props.from_dict(result)
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
def create(channel: MqttChannel) -> Q7PropertiesApi:
|
|
@@ -69,6 +69,7 @@ class RoborockMqttSession(MqttSession):
|
|
|
69
69
|
self._stop = False
|
|
70
70
|
self._backoff = MIN_BACKOFF_INTERVAL
|
|
71
71
|
self._client: aiomqtt.Client | None = None
|
|
72
|
+
self._client_subscribed_topics: set[str] = set()
|
|
72
73
|
self._client_lock = asyncio.Lock()
|
|
73
74
|
self._listeners: CallbackMap[str, bytes] = CallbackMap(_LOGGER)
|
|
74
75
|
self._connection_task: asyncio.Task[None] | None = None
|
|
@@ -218,7 +219,7 @@ class RoborockMqttSession(MqttSession):
|
|
|
218
219
|
# Re-establish any existing subscriptions
|
|
219
220
|
async with self._client_lock:
|
|
220
221
|
self._client = client
|
|
221
|
-
for topic in self.
|
|
222
|
+
for topic in self._client_subscribed_topics:
|
|
222
223
|
_LOGGER.debug("Re-establishing subscription to topic %s", topic)
|
|
223
224
|
# TODO: If this fails it will break the whole connection. Make
|
|
224
225
|
# this retry again in the background with backoff.
|
|
@@ -249,32 +250,42 @@ class RoborockMqttSession(MqttSession):
|
|
|
249
250
|
unsub = self._listeners.add_callback(topic, callback)
|
|
250
251
|
|
|
251
252
|
async with self._client_lock:
|
|
252
|
-
if self.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
253
|
+
if topic not in self._client_subscribed_topics:
|
|
254
|
+
self._client_subscribed_topics.add(topic)
|
|
255
|
+
if self._client:
|
|
256
|
+
_LOGGER.debug("Establishing subscription to topic %s", topic)
|
|
257
|
+
try:
|
|
258
|
+
await self._client.subscribe(topic)
|
|
259
|
+
except MqttError as err:
|
|
260
|
+
# Clean up the callback if subscription fails
|
|
261
|
+
unsub()
|
|
262
|
+
self._client_subscribed_topics.discard(topic)
|
|
263
|
+
raise MqttSessionException(f"Error subscribing to topic: {err}") from err
|
|
264
|
+
else:
|
|
265
|
+
_LOGGER.debug("Client not connected, will establish subscription later")
|
|
266
|
+
|
|
267
|
+
def schedule_unsubscribe() -> None:
|
|
264
268
|
async def idle_unsubscribe():
|
|
265
269
|
try:
|
|
266
270
|
await asyncio.sleep(self._topic_idle_timeout.total_seconds())
|
|
267
271
|
# Only unsubscribe if there are no callbacks left for this topic
|
|
268
272
|
if not self._listeners.get_callbacks(topic):
|
|
269
273
|
async with self._client_lock:
|
|
274
|
+
# Check again if we have listeners, in case a subscribe happened
|
|
275
|
+
# while we were waiting for the lock or after we popped the timer.
|
|
276
|
+
if self._listeners.get_callbacks(topic):
|
|
277
|
+
_LOGGER.debug("Skipping unsubscribe for %s, new listeners added", topic)
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
self._idle_timers.pop(topic, None)
|
|
281
|
+
self._client_subscribed_topics.discard(topic)
|
|
282
|
+
|
|
270
283
|
if self._client:
|
|
271
284
|
_LOGGER.debug("Idle timeout expired, unsubscribing from topic %s", topic)
|
|
272
285
|
try:
|
|
273
286
|
await self._client.unsubscribe(topic)
|
|
274
287
|
except MqttError as err:
|
|
275
288
|
_LOGGER.warning("Error unsubscribing from topic %s: %s", topic, err)
|
|
276
|
-
# Clean up timer from dict
|
|
277
|
-
self._idle_timers.pop(topic, None)
|
|
278
289
|
except asyncio.CancelledError:
|
|
279
290
|
_LOGGER.debug("Idle unsubscribe for topic %s cancelled", topic)
|
|
280
291
|
|
|
@@ -286,7 +297,10 @@ class RoborockMqttSession(MqttSession):
|
|
|
286
297
|
unsub() # Remove the callback from CallbackMap
|
|
287
298
|
# If no more callbacks for this topic, start idle timer
|
|
288
299
|
if not self._listeners.get_callbacks(topic):
|
|
300
|
+
_LOGGER.debug("Unsubscribing topic %s, starting idle timer", topic)
|
|
289
301
|
schedule_unsubscribe()
|
|
302
|
+
else:
|
|
303
|
+
_LOGGER.debug("Unsubscribing topic %s, still have active callbacks", topic)
|
|
290
304
|
|
|
291
305
|
return delayed_unsub
|
|
292
306
|
|
|
@@ -276,12 +276,11 @@ class EncryptionAdapter(Construct):
|
|
|
276
276
|
if context.version == b"A01":
|
|
277
277
|
iv = md5hex(format(context.random, "08x") + A01_HASH)[8:24]
|
|
278
278
|
decipher = AES.new(bytes(context.search("local_key"), "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
|
|
279
|
-
|
|
280
|
-
return f
|
|
279
|
+
return decipher.encrypt(pad(obj, AES.block_size))
|
|
281
280
|
elif context.version == b"B01":
|
|
282
281
|
iv = md5hex(f"{context.random:08x}" + B01_HASH)[9:25]
|
|
283
282
|
decipher = AES.new(bytes(context.search("local_key"), "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
|
|
284
|
-
return decipher.encrypt(obj)
|
|
283
|
+
return decipher.encrypt(pad(obj, AES.block_size))
|
|
285
284
|
elif context.version == b"L01":
|
|
286
285
|
return Utils.encrypt_gcm_l01(
|
|
287
286
|
plaintext=obj,
|
|
@@ -301,12 +300,11 @@ class EncryptionAdapter(Construct):
|
|
|
301
300
|
if context.version == b"A01":
|
|
302
301
|
iv = md5hex(format(context.random, "08x") + A01_HASH)[8:24]
|
|
303
302
|
decipher = AES.new(bytes(context.search("local_key"), "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
|
|
304
|
-
|
|
305
|
-
return f
|
|
303
|
+
return unpad(decipher.decrypt(obj), AES.block_size)
|
|
306
304
|
elif context.version == b"B01":
|
|
307
305
|
iv = md5hex(f"{context.random:08x}" + B01_HASH)[9:25]
|
|
308
306
|
decipher = AES.new(bytes(context.search("local_key"), "utf-8"), AES.MODE_CBC, bytes(iv, "utf-8"))
|
|
309
|
-
return decipher.decrypt(obj)
|
|
307
|
+
return unpad(decipher.decrypt(obj), AES.block_size)
|
|
310
308
|
elif context.version == b"L01":
|
|
311
309
|
return Utils.decrypt_gcm_l01(
|
|
312
310
|
payload=obj,
|
|
@@ -350,6 +348,11 @@ class PrefixedStruct(Struct):
|
|
|
350
348
|
# Read remaining data to find a valid header
|
|
351
349
|
data = stream.read()
|
|
352
350
|
|
|
351
|
+
if not data:
|
|
352
|
+
# EOF reached, let the parser fail naturally without logging
|
|
353
|
+
stream_seek(stream, current_pos, 0, path)
|
|
354
|
+
return super()._parse(stream, context, path)
|
|
355
|
+
|
|
353
356
|
start_index = -1
|
|
354
357
|
# Find the earliest occurrence of any valid version in a single pass
|
|
355
358
|
for i in range(len(data) - 2):
|
|
@@ -13,7 +13,6 @@ from roborock.roborock_message import (
|
|
|
13
13
|
RoborockMessage,
|
|
14
14
|
RoborockMessageProtocol,
|
|
15
15
|
)
|
|
16
|
-
from roborock.util import get_next_int
|
|
17
16
|
|
|
18
17
|
_LOGGER = logging.getLogger(__name__)
|
|
19
18
|
|
|
@@ -22,13 +21,13 @@ CommandType = RoborockB01Q7Methods | str
|
|
|
22
21
|
ParamsType = list | dict | int | None
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
def encode_mqtt_payload(dps: int, command: CommandType, params: ParamsType) -> RoborockMessage:
|
|
24
|
+
def encode_mqtt_payload(dps: int, command: CommandType, params: ParamsType, msg_id: str) -> RoborockMessage:
|
|
26
25
|
"""Encode payload for B01 commands over MQTT."""
|
|
27
26
|
dps_data = {
|
|
28
27
|
"dps": {
|
|
29
28
|
dps: {
|
|
30
29
|
"method": str(command),
|
|
31
|
-
"msgId":
|
|
30
|
+
"msgId": msg_id,
|
|
32
31
|
"params": params or [],
|
|
33
32
|
}
|
|
34
33
|
}
|
|
@@ -47,8 +46,9 @@ def decode_rpc_response(message: RoborockMessage) -> dict[int, Any]:
|
|
|
47
46
|
raise RoborockException("Invalid B01 message format: missing payload")
|
|
48
47
|
try:
|
|
49
48
|
unpadded = unpad(message.payload, AES.block_size)
|
|
50
|
-
except ValueError
|
|
51
|
-
|
|
49
|
+
except ValueError:
|
|
50
|
+
# It would be better to fail down the line.
|
|
51
|
+
unpadded = message.payload
|
|
52
52
|
|
|
53
53
|
try:
|
|
54
54
|
payload = json.loads(unpadded.decode())
|
|
@@ -14,6 +14,7 @@ import aiohttp
|
|
|
14
14
|
from aiohttp import ContentTypeError, FormData
|
|
15
15
|
from pyrate_limiter import BucketFullException, Duration, Limiter, Rate
|
|
16
16
|
|
|
17
|
+
from roborock import HomeDataSchedule
|
|
17
18
|
from roborock.data import HomeData, HomeDataRoom, HomeDataScene, ProductResponse, RRiot, UserData
|
|
18
19
|
from roborock.exceptions import (
|
|
19
20
|
RoborockAccountDoesNotExist,
|
|
@@ -607,6 +608,28 @@ class RoborockApiClient:
|
|
|
607
608
|
if not execute_scene_response.get("success"):
|
|
608
609
|
raise RoborockException(execute_scene_response)
|
|
609
610
|
|
|
611
|
+
async def get_schedules(self, user_data: UserData, device_id: str) -> list[HomeDataSchedule]:
|
|
612
|
+
rriot = user_data.rriot
|
|
613
|
+
if rriot is None:
|
|
614
|
+
raise RoborockException("rriot is none")
|
|
615
|
+
if rriot.r.a is None:
|
|
616
|
+
raise RoborockException("Missing field 'a' in rriot reference")
|
|
617
|
+
schedules_request = PreparedRequest(
|
|
618
|
+
rriot.r.a,
|
|
619
|
+
self.session,
|
|
620
|
+
{
|
|
621
|
+
"Authorization": _get_hawk_authentication(rriot, f"/user/devices/{device_id}/jobs"),
|
|
622
|
+
},
|
|
623
|
+
)
|
|
624
|
+
schedules_response = await schedules_request.request("get", f"/user/devices/{str(device_id)}/jobs")
|
|
625
|
+
if not schedules_response.get("success"):
|
|
626
|
+
raise RoborockException(schedules_response)
|
|
627
|
+
schedules = schedules_response.get("result")
|
|
628
|
+
if isinstance(schedules, list):
|
|
629
|
+
return [HomeDataSchedule.from_dict(schedule) for schedule in schedules]
|
|
630
|
+
else:
|
|
631
|
+
raise RoborockException(f"schedule_response result was an unexpected type: {schedules}")
|
|
632
|
+
|
|
610
633
|
async def get_products(self, user_data: UserData) -> ProductResponse:
|
|
611
634
|
"""Gets all products and their schemas, good for determining status codes and model numbers."""
|
|
612
635
|
base_url = await self.base_url
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass, field
|
|
2
|
-
|
|
3
|
-
from ..containers import RoborockBase
|
|
4
|
-
from .b01_q7_code_mappings import B01Fault, SCWindMapping, WorkModeMapping, WorkStatusMapping
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass
|
|
8
|
-
class NetStatus(RoborockBase):
|
|
9
|
-
"""Represents the network status of the device."""
|
|
10
|
-
|
|
11
|
-
rssi: str
|
|
12
|
-
loss: int
|
|
13
|
-
ping: int
|
|
14
|
-
ip: str
|
|
15
|
-
mac: str
|
|
16
|
-
ssid: str
|
|
17
|
-
frequency: int
|
|
18
|
-
bssid: str
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass
|
|
22
|
-
class OrderTotal(RoborockBase):
|
|
23
|
-
"""Represents the order total information."""
|
|
24
|
-
|
|
25
|
-
total: int
|
|
26
|
-
enable: int
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@dataclass
|
|
30
|
-
class Privacy(RoborockBase):
|
|
31
|
-
"""Represents the privacy settings of the device."""
|
|
32
|
-
|
|
33
|
-
ai_recognize: int
|
|
34
|
-
dirt_recognize: int
|
|
35
|
-
pet_recognize: int
|
|
36
|
-
carpet_turbo: int
|
|
37
|
-
carpet_avoid: int
|
|
38
|
-
carpet_show: int
|
|
39
|
-
map_uploads: int
|
|
40
|
-
ai_agent: int
|
|
41
|
-
ai_avoidance: int
|
|
42
|
-
record_uploads: int
|
|
43
|
-
along_floor: int
|
|
44
|
-
auto_upgrade: int
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@dataclass
|
|
48
|
-
class PvCharging(RoborockBase):
|
|
49
|
-
"""Represents the photovoltaic charging status."""
|
|
50
|
-
|
|
51
|
-
status: int
|
|
52
|
-
begin_time: int
|
|
53
|
-
end_time: int
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dataclass
|
|
57
|
-
class Recommend(RoborockBase):
|
|
58
|
-
"""Represents cleaning recommendations."""
|
|
59
|
-
|
|
60
|
-
sill: int
|
|
61
|
-
wall: int
|
|
62
|
-
room_id: list[int] = field(default_factory=list)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@dataclass
|
|
66
|
-
class B01Props(RoborockBase):
|
|
67
|
-
"""
|
|
68
|
-
Represents the complete properties and status for a Roborock B01 model.
|
|
69
|
-
This dataclass is generated based on the device's status JSON object.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
status: WorkStatusMapping
|
|
73
|
-
fault: B01Fault
|
|
74
|
-
wind: SCWindMapping
|
|
75
|
-
water: int
|
|
76
|
-
mode: int
|
|
77
|
-
quantity: int
|
|
78
|
-
alarm: int
|
|
79
|
-
volume: int
|
|
80
|
-
hypa: int
|
|
81
|
-
main_brush: int
|
|
82
|
-
side_brush: int
|
|
83
|
-
mop_life: int
|
|
84
|
-
main_sensor: int
|
|
85
|
-
net_status: NetStatus
|
|
86
|
-
repeat_state: int
|
|
87
|
-
tank_state: int
|
|
88
|
-
sweep_type: int
|
|
89
|
-
clean_path_preference: int
|
|
90
|
-
cloth_state: int
|
|
91
|
-
time_zone: int
|
|
92
|
-
time_zone_info: str
|
|
93
|
-
language: int
|
|
94
|
-
cleaning_time: int
|
|
95
|
-
real_clean_time: int
|
|
96
|
-
cleaning_area: int
|
|
97
|
-
custom_type: int
|
|
98
|
-
sound: int
|
|
99
|
-
work_mode: WorkModeMapping
|
|
100
|
-
station_act: int
|
|
101
|
-
charge_state: int
|
|
102
|
-
current_map_id: int
|
|
103
|
-
map_num: int
|
|
104
|
-
dust_action: int
|
|
105
|
-
quiet_is_open: int
|
|
106
|
-
quiet_begin_time: int
|
|
107
|
-
quiet_end_time: int
|
|
108
|
-
clean_finish: int
|
|
109
|
-
voice_type: int
|
|
110
|
-
voice_type_version: int
|
|
111
|
-
order_total: OrderTotal
|
|
112
|
-
build_map: int
|
|
113
|
-
privacy: Privacy
|
|
114
|
-
dust_auto_state: int
|
|
115
|
-
dust_frequency: int
|
|
116
|
-
child_lock: int
|
|
117
|
-
multi_floor: int
|
|
118
|
-
map_save: int
|
|
119
|
-
light_mode: int
|
|
120
|
-
green_laser: int
|
|
121
|
-
dust_bag_used: int
|
|
122
|
-
order_save_mode: int
|
|
123
|
-
manufacturer: str
|
|
124
|
-
back_to_wash: int
|
|
125
|
-
charge_station_type: int
|
|
126
|
-
pv_cut_charge: int
|
|
127
|
-
pv_charging: PvCharging
|
|
128
|
-
serial_number: str
|
|
129
|
-
recommend: Recommend
|
|
130
|
-
add_sweep_status: int
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Thin wrapper around the MQTT channel for Roborock B01 devices."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
from roborock.protocols.b01_protocol import (
|
|
8
|
-
CommandType,
|
|
9
|
-
ParamsType,
|
|
10
|
-
encode_mqtt_payload,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
from .mqtt_channel import MqttChannel
|
|
14
|
-
|
|
15
|
-
_LOGGER = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def send_decoded_command(
|
|
19
|
-
mqtt_channel: MqttChannel,
|
|
20
|
-
dps: int,
|
|
21
|
-
command: CommandType,
|
|
22
|
-
params: ParamsType,
|
|
23
|
-
) -> None:
|
|
24
|
-
"""Send a command on the MQTT channel and get a decoded response."""
|
|
25
|
-
_LOGGER.debug("Sending MQTT command: %s", params)
|
|
26
|
-
roborock_message = encode_mqtt_payload(dps, command, params)
|
|
27
|
-
await mqtt_channel.publish(roborock_message)
|
|
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.14.3 → python_roborock-3.16.0}/roborock/data/b01_q10/b01_q10_code_mappings.py
RENAMED
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/data/b01_q10/b01_q10_containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.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
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/b01/q10/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/clean_summary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/device_features.py
RENAMED
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/do_not_disturb.py
RENAMED
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/dust_collection_mode.py
RENAMED
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.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
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/devices/traits/v1/network_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.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.14.3 → python_roborock-3.16.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
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-3.14.3 → python_roborock-3.16.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|