python-roborock 7.14.0__py3-none-any.whl → 7.15.0__py3-none-any.whl
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-7.14.0.dist-info → python_roborock-7.15.0.dist-info}/METADATA +1 -1
- {python_roborock-7.14.0.dist-info → python_roborock-7.15.0.dist-info}/RECORD +13 -10
- roborock/data/__init__.py +1 -0
- roborock/data/camera.py +158 -0
- roborock/data/mower/mower_code_mappings.py +396 -70
- roborock/data/mower/mower_containers.py +73 -29
- roborock/data/v1/__init__.py +1 -0
- roborock/data/v1/v1_dynamic_map.py +221 -0
- roborock/devices/traits/a01/device_feature.py +5 -2
- roborock/protocols/camera_signaling.py +311 -0
- {python_roborock-7.14.0.dist-info → python_roborock-7.15.0.dist-info}/WHEEL +0 -0
- {python_roborock-7.14.0.dist-info → python_roborock-7.15.0.dist-info}/entry_points.txt +0 -0
- {python_roborock-7.14.0.dist-info → python_roborock-7.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.15.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
5
|
Project-URL: Repository, https://github.com/python-roborock/python-roborock
|
|
6
6
|
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
@@ -12,7 +12,8 @@ roborock/roborock_message.py,sha256=VWsytnm7nQqOMBj7oYbiqPPqwE9qClDSI03CFuHXsds,
|
|
|
12
12
|
roborock/roborock_typing.py,sha256=xppavdf57-Cu6elj3MP403uT75Tjw5Zs6bLGPLQmZf4,15483
|
|
13
13
|
roborock/util.py,sha256=4zibjn2MgPrLCiha66LUXZj907LOXUtNzugbbb0NIlo,1634
|
|
14
14
|
roborock/web_api.py,sha256=G42qiPdTGx4LhMA4uTDASAMYw1HIipDnD6_eziwCPqs,43202
|
|
15
|
-
roborock/data/__init__.py,sha256=
|
|
15
|
+
roborock/data/__init__.py,sha256=mmFs7SGa1ciqXME5Al9gfIKiCnbhgu1V84wa4j5mdfs,300
|
|
16
|
+
roborock/data/camera.py,sha256=AaTsDqNlUwV_6zdIX9wQkPXGFzyt2EiiKnitcnfQJA4,5792
|
|
16
17
|
roborock/data/code_mappings.py,sha256=j-ZZRtQQdIMJQAgEwDAxWKreNhD6U36oIhBKr_nLbis,9783
|
|
17
18
|
roborock/data/containers.py,sha256=ePz1tmhNY1QpgiVlyc8j6PH5kjU6fkZYtBByCFj2wzU,17896
|
|
18
19
|
roborock/data/b01_q10/__init__.py,sha256=E4lwJcz4AI1z92FcYOJ-cY3fcfHSoXqvYVgfA-TgaRk,71
|
|
@@ -25,12 +26,13 @@ roborock/data/dyad/__init__.py,sha256=Ksx7Ri1alsAYAyILOpj9iNbCC_6sF7xvETFRnTO4rd
|
|
|
25
26
|
roborock/data/dyad/dyad_code_mappings.py,sha256=PGzEpkdc1jsR5RLRnjVnI2Y87O_HFAFulwOjjjbT4B0,2872
|
|
26
27
|
roborock/data/dyad/dyad_containers.py,sha256=klJD2wV-2McjAoSN8Bthuyeid59L7M8nzSvc3MtmqOI,434
|
|
27
28
|
roborock/data/mower/__init__.py,sha256=RXFcOE0-rchWViq3F-cLXB9Jnx4nKrn98TUR917szWM,114
|
|
28
|
-
roborock/data/mower/mower_code_mappings.py,sha256=
|
|
29
|
-
roborock/data/mower/mower_containers.py,sha256=
|
|
30
|
-
roborock/data/v1/__init__.py,sha256=
|
|
29
|
+
roborock/data/mower/mower_code_mappings.py,sha256=dKHgZxpNVaO-uFZvqjPouYbpzgukAlnFgHa8vGsNxKs,14110
|
|
30
|
+
roborock/data/mower/mower_containers.py,sha256=s8xzDOW7Pgoipw7P2ROjj-4-c0qZRHuZ-kw_Zu-Aqt0,4938
|
|
31
|
+
roborock/data/v1/__init__.py,sha256=3cdjyhCkD4EmEn9JVlSakGusnEgzGajpn8tAvzB3FkI,121
|
|
31
32
|
roborock/data/v1/v1_clean_modes.py,sha256=uePsc82-7C6tjybYmnYtxA6WAOPS9ofnJXMEP218KTQ,15767
|
|
32
33
|
roborock/data/v1/v1_code_mappings.py,sha256=9lbpJ5Ugzb0IhjiHjNNQvgLBxEsjZdfq1sEOHkjJIfI,12970
|
|
33
34
|
roborock/data/v1/v1_containers.py,sha256=s5ivZjkNIX7ls6IXBnppBWATTqdAlRY-Busw3aoKPnc,18602
|
|
35
|
+
roborock/data/v1/v1_dynamic_map.py,sha256=W6eGGR25euxvilnl8KyYBhzUZCbizUC29tCY36mXQhM,7795
|
|
34
36
|
roborock/data/zeo/__init__.py,sha256=VxG5qG4M24DGOq2YAaxownWp6bEfj9DEOgOMHmTpcqY,63
|
|
35
37
|
roborock/data/zeo/zeo_code_mappings.py,sha256=mfm2xoI8RllPZfyZE0HmUhUhLECwRD0llkMNWfaEHvw,8057
|
|
36
38
|
roborock/data/zeo/zeo_containers.py,sha256=huDeisutagKz1rzsLZr_tGAietec_qOd0KB3i09YGgo,4339
|
|
@@ -49,7 +51,7 @@ roborock/devices/traits/__init__.py,sha256=wUiMPJXm4oVULQy2zjPkChrb4cWy2L1kUqzU7
|
|
|
49
51
|
roborock/devices/traits/common.py,sha256=n3fHK4i4bkTU1QL7skMStJiUPIDlpN37z0T9mPQYoOY,4588
|
|
50
52
|
roborock/devices/traits/traits_mixin.py,sha256=7t15l_Ty1cN-3-fKNtRH1XR6-mfadPnTBCQRZP4rAtc,2092
|
|
51
53
|
roborock/devices/traits/a01/__init__.py,sha256=rRrdXgSsolyNC6MOCyYIfW8oaUmzt-p_uppuhzfYYIU,18375
|
|
52
|
-
roborock/devices/traits/a01/device_feature.py,sha256=
|
|
54
|
+
roborock/devices/traits/a01/device_feature.py,sha256=wgYNBJPS9fax4j4Pu831hd6GEXw4t9Z0HAVhPmKA7yw,14315
|
|
53
55
|
roborock/devices/traits/b01/__init__.py,sha256=pGN6wEocE6Fr2BTasG8Z_75crD6OdTwpju59Vw7eTDk,202
|
|
54
56
|
roborock/devices/traits/b01/q10/__init__.py,sha256=fucVVugsvIHl09ypuVRnN5KeUgqFixngxMsm3ktxXts,6804
|
|
55
57
|
roborock/devices/traits/b01/q10/button_light.py,sha256=_CCWSjFe_iZnnSXgBHOju31VUmwG1KKd_jGkmSpLew4,994
|
|
@@ -120,6 +122,7 @@ roborock/protocols/__init__.py,sha256=1ems3I1_KUQoMf2Hf6ZYDKFhu5Gnsc0PbfD-dkQJ7v
|
|
|
120
122
|
roborock/protocols/a01_protocol.py,sha256=nyUAn39CXH0xLqcRcr7JiORt2zFNgxP4F4ClVc3dvB4,2501
|
|
121
123
|
roborock/protocols/b01_q10_protocol.py,sha256=cQnVo5HIVoYhjtVAdnZsPkM-5Glrq9x2jcMG9vhHtRw,7213
|
|
122
124
|
roborock/protocols/b01_q7_protocol.py,sha256=2dk6nbDOhb0O-Q_UPKeZHjzdazbS-MA3PIqpXvqRqoo,4657
|
|
125
|
+
roborock/protocols/camera_signaling.py,sha256=tCaVbHPjlHpsGL4RLjkgBVZFYgYg1PvBA8Ns-eEErEE,13179
|
|
123
126
|
roborock/protocols/v1_protocol.py,sha256=ntFQoGn1IF7v_BwjiGow478SJHBSKr6jq6M2s01OdaI,12918
|
|
124
127
|
roborock/testing/__init__.py,sha256=rji4a-JBIWC_I6SUTG9j4r2bGxfhYUoopdDAzYQ1tmA,5086
|
|
125
128
|
roborock/testing/a01_simulator.py,sha256=M4R0ATgebIva6Jl0BPKfwdGqdZjmjGZHlozAVVteJ5I,10477
|
|
@@ -128,8 +131,8 @@ roborock/testing/channel.py,sha256=vjulT5MHBe8lAq6XITUqcxJVcCILWx7ZEcw5imTx0Tk,7
|
|
|
128
131
|
roborock/testing/cloud.py,sha256=L2L0KlZDYaMuoV19KUQUoq5EAUDeNQ1lJ8Ew5YaOgjo,11946
|
|
129
132
|
roborock/testing/simulator.py,sha256=g8ZUWVSjgAzuRRxWOCeOq-7deodyEItwUpqEw9RzCmQ,4347
|
|
130
133
|
roborock/testing/v1_simulator.py,sha256=dDlaaCqKMrE-IotSqaENGf-mZYLDFVelozDJAXnet0s,18929
|
|
131
|
-
python_roborock-7.
|
|
132
|
-
python_roborock-7.
|
|
133
|
-
python_roborock-7.
|
|
134
|
-
python_roborock-7.
|
|
135
|
-
python_roborock-7.
|
|
134
|
+
python_roborock-7.15.0.dist-info/METADATA,sha256=XxXWSGrnE-azLQjTId5OhnGB5Vq0LZ1MxiB86b3gVXE,5279
|
|
135
|
+
python_roborock-7.15.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
136
|
+
python_roborock-7.15.0.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
|
|
137
|
+
python_roborock-7.15.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
138
|
+
python_roborock-7.15.0.dist-info/RECORD,,
|
roborock/data/__init__.py
CHANGED
roborock/data/camera.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Data models for Roborock camera live view."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import Any, Self
|
|
6
|
+
|
|
7
|
+
from .containers import RoborockBase
|
|
8
|
+
|
|
9
|
+
__all__ = ["CameraIceCandidate", "CameraQuality", "CameraSdpDescription", "CameraTurnServer"]
|
|
10
|
+
|
|
11
|
+
_VALID_TURN_SCHEMES = ("turn:", "turns:", "stun:", "stuns:")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CameraQuality(StrEnum):
|
|
15
|
+
"""Camera video streaming resolution setting."""
|
|
16
|
+
|
|
17
|
+
HD = "HD"
|
|
18
|
+
SD = "SD"
|
|
19
|
+
FHD = "FHD"
|
|
20
|
+
AUTO = "auto"
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_str(cls, value: str) -> Self:
|
|
24
|
+
"""Parse CameraQuality case-insensitively."""
|
|
25
|
+
normalized = value.strip()
|
|
26
|
+
# "auto" is lowercase in the Roborock app enum, while HD/SD/FHD are uppercase
|
|
27
|
+
for member in cls:
|
|
28
|
+
if member.value.lower() == normalized.lower():
|
|
29
|
+
return member
|
|
30
|
+
valid = ", ".join(repr(m.value) for m in cls)
|
|
31
|
+
raise ValueError(f"Invalid camera quality: {value!r}. Expected one of {valid}.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class CameraTurnServer(RoborockBase):
|
|
36
|
+
"""Ephemeral TURN server credentials for WebRTC peer connection relay."""
|
|
37
|
+
|
|
38
|
+
urls: list[str]
|
|
39
|
+
username: str | None = None
|
|
40
|
+
credential: str | None = field(default=None, repr=False)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_dict(cls, data: dict[str, Any] | list[dict[str, Any]]) -> Self:
|
|
44
|
+
"""Create CameraTurnServer from get_turn_server response or dict.
|
|
45
|
+
|
|
46
|
+
Handles both device RPC responses (single-item list or dict with 'url', 'user', 'pwd')
|
|
47
|
+
and standard WebRTC configuration dicts ('urls', 'username', 'credential').
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(data, list):
|
|
50
|
+
if not data:
|
|
51
|
+
raise ValueError("Empty TURN server data list")
|
|
52
|
+
item = data[0]
|
|
53
|
+
if not isinstance(item, dict):
|
|
54
|
+
raise TypeError(f"Expected dict inside TURN server list, got {type(item).__name__}")
|
|
55
|
+
data = item
|
|
56
|
+
if not isinstance(data, dict):
|
|
57
|
+
raise TypeError(f"Expected dict or list of dicts for TURN server, got {type(data).__name__}")
|
|
58
|
+
|
|
59
|
+
if "urls" in data:
|
|
60
|
+
urls_val = data["urls"]
|
|
61
|
+
elif "url" in data:
|
|
62
|
+
urls_val = data["url"]
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("Missing 'url' or 'urls' in TURN server response")
|
|
65
|
+
|
|
66
|
+
urls: list[str] = []
|
|
67
|
+
if isinstance(urls_val, str):
|
|
68
|
+
urls_clean = urls_val.strip()
|
|
69
|
+
if not urls_clean:
|
|
70
|
+
raise ValueError("TURN server URL cannot be empty")
|
|
71
|
+
urls = [urls_clean]
|
|
72
|
+
elif isinstance(urls_val, list):
|
|
73
|
+
if not urls_val:
|
|
74
|
+
raise ValueError("TURN server 'urls' list cannot be empty")
|
|
75
|
+
for u in urls_val:
|
|
76
|
+
if not isinstance(u, str) or not u.strip():
|
|
77
|
+
raise ValueError(f"Each URL in 'urls' must be a non-empty string, got {u!r}")
|
|
78
|
+
urls.append(u.strip())
|
|
79
|
+
else:
|
|
80
|
+
raise TypeError(f"Expected str or list for urls, got {type(urls_val).__name__}")
|
|
81
|
+
|
|
82
|
+
# Validate URL scheme (stun:, stuns:, turn:, turns:)
|
|
83
|
+
for u in urls:
|
|
84
|
+
if not any(u.lower().startswith(scheme) for scheme in _VALID_TURN_SCHEMES):
|
|
85
|
+
raise ValueError(f"Invalid ICE server URL scheme in {u!r}. Expected turn:, turns:, stun:, or stuns:")
|
|
86
|
+
|
|
87
|
+
user_val = data.get("username") if "username" in data else data.get("user")
|
|
88
|
+
pwd_val = data.get("credential") if "credential" in data else data.get("pwd")
|
|
89
|
+
|
|
90
|
+
if user_val is not None and not isinstance(user_val, str):
|
|
91
|
+
raise TypeError(f"Expected str for username, got {type(user_val).__name__}")
|
|
92
|
+
if pwd_val is not None and not isinstance(pwd_val, str):
|
|
93
|
+
raise TypeError(f"Expected str for credential, got {type(pwd_val).__name__}")
|
|
94
|
+
|
|
95
|
+
return cls(
|
|
96
|
+
urls=urls,
|
|
97
|
+
username=user_val,
|
|
98
|
+
credential=pwd_val,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class CameraIceCandidate(RoborockBase):
|
|
104
|
+
"""Parsed WebRTC ICE candidate representation (RFC 5245 / RFC 8839)."""
|
|
105
|
+
|
|
106
|
+
foundation: str
|
|
107
|
+
component: int
|
|
108
|
+
protocol: str
|
|
109
|
+
priority: int
|
|
110
|
+
ip: str
|
|
111
|
+
port: int
|
|
112
|
+
candidate_type: str
|
|
113
|
+
related_address: str | None = None
|
|
114
|
+
related_port: int | None = None
|
|
115
|
+
tcp_type: str | None = None
|
|
116
|
+
sdp_mid: str | None = None
|
|
117
|
+
sdp_mline_index: int | None = None
|
|
118
|
+
extensions: dict[str, str] = field(default_factory=dict)
|
|
119
|
+
|
|
120
|
+
def to_sdp(self) -> str:
|
|
121
|
+
"""Format candidate as an SDP attribute line value (RFC 5245 / RFC 8839)."""
|
|
122
|
+
parts = [
|
|
123
|
+
f"candidate:{self.foundation}",
|
|
124
|
+
str(self.component),
|
|
125
|
+
self.protocol.lower(),
|
|
126
|
+
str(self.priority),
|
|
127
|
+
self.ip,
|
|
128
|
+
str(self.port),
|
|
129
|
+
"typ",
|
|
130
|
+
self.candidate_type.lower(),
|
|
131
|
+
]
|
|
132
|
+
if self.related_address is not None and self.related_port is not None:
|
|
133
|
+
parts.extend(["raddr", self.related_address, "rport", str(self.related_port)])
|
|
134
|
+
if self.tcp_type is not None:
|
|
135
|
+
parts.extend(["tcptype", self.tcp_type.lower()])
|
|
136
|
+
for ext_k, ext_v in self.extensions.items():
|
|
137
|
+
parts.extend([ext_k, ext_v])
|
|
138
|
+
return " ".join(parts)
|
|
139
|
+
|
|
140
|
+
def to_signaling_dict(self) -> dict[str, str | int]:
|
|
141
|
+
"""Convert to Roborock camera signaling dictionary format."""
|
|
142
|
+
return {
|
|
143
|
+
"candidate": self.to_sdp(),
|
|
144
|
+
"sdpMid": self.sdp_mid if self.sdp_mid is not None else "0",
|
|
145
|
+
"sdpMLineIndex": self.sdp_mline_index if self.sdp_mline_index is not None else 0,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def __str__(self) -> str:
|
|
149
|
+
"""Return the SDP candidate string."""
|
|
150
|
+
return self.to_sdp()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class CameraSdpDescription(RoborockBase):
|
|
155
|
+
"""WebRTC session description exchanged during camera signaling."""
|
|
156
|
+
|
|
157
|
+
sdp: str
|
|
158
|
+
type: str
|