python-roborock 7.14.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-roborock
3
- Version: 7.14.1
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=jSZHOrd-LfVN-TdBkjNW334ef99rGBz4-hPT_7KvoLs,278
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=T3NJEjgkYv3svIt7-gqFgrnNmVeBOkqit8_D52vSIao,1741
29
- roborock/data/mower/mower_containers.py,sha256=PYNVhbGCafkchenBlBmG_fUnIC-9WfxF8gNB0C4_9Yo,4106
30
- roborock/data/v1/__init__.py,sha256=Lx_D6baZrCMxp57D0NcdiA8rssJ5NnInZwUwrT79XOI,91
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
@@ -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.14.1.dist-info/METADATA,sha256=vvndtBwZRgub5PRRfgYSqRW8llXWGtcHZ8vpLobbrdc,5279
132
- python_roborock-7.14.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
133
- python_roborock-7.14.1.dist-info/entry_points.txt,sha256=EvC1nMqi9ZXKgZnqlNXA33v_3nzgPNjnM0mzWlrehnY,47
134
- python_roborock-7.14.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
135
- python_roborock-7.14.1.dist-info/RECORD,,
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .b01_q7 import *
4
4
  from .b01_q10 import *
5
+ from .camera import *
5
6
  from .code_mappings import *
6
7
  from .containers import *
7
8
  from .dyad import *
@@ -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