python-roborock 2.40.0__tar.gz → 2.41.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-2.40.0 → python_roborock-2.41.0}/PKG-INFO +1 -1
- {python_roborock-2.40.0 → python_roborock-2.41.0}/pyproject.toml +1 -1
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/containers.py +15 -14
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/device_manager.py +2 -0
- python_roborock-2.41.0/roborock/devices/traits/dnd.py +41 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/traits/status.py +3 -3
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/v1_rpc_channel.py +6 -2
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/protocols/v1_protocol.py +7 -6
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_1_apis/roborock_client_v1.py +9 -8
- {python_roborock-2.40.0 → python_roborock-2.41.0}/LICENSE +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/README.md +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/__init__.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/api.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/b01_containers.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/callbacks.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/clean_modes.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/cli.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/code_mappings.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/const.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/device_features.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/a01_channel.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/cache.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/channel.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/device.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/traits/b01/props.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/traits/dyad.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/traits/trait.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/traits/zeo.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/devices/v1_channel.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/exceptions.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/protocol.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/py.typed +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/roborock_future.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/util.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
- {python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/web_api.py +0 -0
|
@@ -109,8 +109,6 @@ def _decamelize(s: str):
|
|
|
109
109
|
|
|
110
110
|
@dataclass
|
|
111
111
|
class RoborockBase:
|
|
112
|
-
_ignore_keys = [] # type: ignore
|
|
113
|
-
|
|
114
112
|
@staticmethod
|
|
115
113
|
def _convert_to_class_obj(class_type: type, value):
|
|
116
114
|
if get_origin(class_type) is list:
|
|
@@ -134,8 +132,8 @@ class RoborockBase:
|
|
|
134
132
|
return None
|
|
135
133
|
field_types = {field.name: field.type for field in dataclasses.fields(cls)}
|
|
136
134
|
result: dict[str, Any] = {}
|
|
137
|
-
for
|
|
138
|
-
key = _decamelize(
|
|
135
|
+
for orig_key, value in data.items():
|
|
136
|
+
key = _decamelize(orig_key)
|
|
139
137
|
if (field_type := field_types.get(key)) is None:
|
|
140
138
|
continue
|
|
141
139
|
if value == "None" or value is None:
|
|
@@ -178,16 +176,18 @@ class RoborockBaseTimer(RoborockBase):
|
|
|
178
176
|
end_hour: int | None = None
|
|
179
177
|
end_minute: int | None = None
|
|
180
178
|
enabled: int | None = None
|
|
181
|
-
start_time: datetime.time | None = None
|
|
182
|
-
end_time: datetime.time | None = None
|
|
183
179
|
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
@property
|
|
181
|
+
def start_time(self) -> datetime.time | None:
|
|
182
|
+
return (
|
|
186
183
|
datetime.time(hour=self.start_hour, minute=self.start_minute)
|
|
187
184
|
if self.start_hour is not None and self.start_minute is not None
|
|
188
185
|
else None
|
|
189
186
|
)
|
|
190
|
-
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def end_time(self) -> datetime.time | None:
|
|
190
|
+
return (
|
|
191
191
|
datetime.time(hour=self.end_hour, minute=self.end_minute)
|
|
192
192
|
if self.end_hour is not None and self.end_minute is not None
|
|
193
193
|
else None
|
|
@@ -684,19 +684,20 @@ class MultiMapsListMapInfoBakMaps(RoborockBase):
|
|
|
684
684
|
|
|
685
685
|
@dataclass
|
|
686
686
|
class MultiMapsListMapInfo(RoborockBase):
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
mapFlag: int
|
|
687
|
+
map_flag: int
|
|
690
688
|
name: str
|
|
691
689
|
add_time: Any | None = None
|
|
692
690
|
length: Any | None = None
|
|
693
691
|
bak_maps: list[MultiMapsListMapInfoBakMaps] | None = None
|
|
694
692
|
|
|
693
|
+
@property
|
|
694
|
+
def mapFlag(self) -> int:
|
|
695
|
+
"""Alias for map_flag, returns the map flag as an integer."""
|
|
696
|
+
return self.map_flag
|
|
697
|
+
|
|
695
698
|
|
|
696
699
|
@dataclass
|
|
697
700
|
class MultiMapsList(RoborockBase):
|
|
698
|
-
_ignore_keys = ["mapFlag"]
|
|
699
|
-
|
|
700
701
|
max_multi_map: int | None = None
|
|
701
702
|
max_bak_map: int | None = None
|
|
702
703
|
multi_map_count: int | None = None
|
|
@@ -22,6 +22,7 @@ from .cache import Cache, NoCache
|
|
|
22
22
|
from .channel import Channel
|
|
23
23
|
from .mqtt_channel import create_mqtt_channel
|
|
24
24
|
from .traits.b01.props import B01PropsApi
|
|
25
|
+
from .traits.dnd import DoNotDisturbTrait
|
|
25
26
|
from .traits.dyad import DyadApi
|
|
26
27
|
from .traits.status import StatusTrait
|
|
27
28
|
from .traits.trait import Trait
|
|
@@ -152,6 +153,7 @@ async def create_device_manager(
|
|
|
152
153
|
case DeviceVersion.V1:
|
|
153
154
|
channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device, cache)
|
|
154
155
|
traits.append(StatusTrait(product, channel.rpc_channel))
|
|
156
|
+
traits.append(DoNotDisturbTrait(channel.rpc_channel))
|
|
155
157
|
case DeviceVersion.A01:
|
|
156
158
|
mqtt_channel = create_mqtt_channel(user_data, mqtt_params, mqtt_session, device)
|
|
157
159
|
match product.category:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Module for Roborock V1 devices.
|
|
2
|
+
|
|
3
|
+
This interface is experimental and subject to breaking changes without notice
|
|
4
|
+
until the API is stable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from roborock.containers import DnDTimer
|
|
10
|
+
from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
11
|
+
from roborock.roborock_typing import RoborockCommand
|
|
12
|
+
|
|
13
|
+
from .trait import Trait
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"DoNotDisturbTrait",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DoNotDisturbTrait(Trait):
|
|
23
|
+
"""Trait for managing Do Not Disturb (DND) settings on Roborock devices."""
|
|
24
|
+
|
|
25
|
+
name = "do_not_disturb"
|
|
26
|
+
|
|
27
|
+
def __init__(self, rpc_channel: V1RpcChannel) -> None:
|
|
28
|
+
"""Initialize the DoNotDisturbTrait."""
|
|
29
|
+
self._rpc_channel = rpc_channel
|
|
30
|
+
|
|
31
|
+
async def get_dnd_timer(self) -> DnDTimer:
|
|
32
|
+
"""Get the current Do Not Disturb (DND) timer settings of the device."""
|
|
33
|
+
return await self._rpc_channel.send_command(RoborockCommand.GET_DND_TIMER, response_type=DnDTimer)
|
|
34
|
+
|
|
35
|
+
async def set_dnd_timer(self, dnd_timer: DnDTimer) -> None:
|
|
36
|
+
"""Set the Do Not Disturb (DND) timer settings of the device."""
|
|
37
|
+
await self._rpc_channel.send_command(RoborockCommand.SET_DND_TIMER, params=dnd_timer.as_dict())
|
|
38
|
+
|
|
39
|
+
async def clear_dnd_timer(self) -> None:
|
|
40
|
+
"""Clear the Do Not Disturb (DND) timer settings of the device."""
|
|
41
|
+
await self._rpc_channel.send_command(RoborockCommand.CLOSE_DND_TIMER)
|
|
@@ -12,20 +12,20 @@ from roborock.containers import (
|
|
|
12
12
|
S7MaxVStatus,
|
|
13
13
|
Status,
|
|
14
14
|
)
|
|
15
|
+
from roborock.devices.v1_rpc_channel import V1RpcChannel
|
|
15
16
|
from roborock.roborock_typing import RoborockCommand
|
|
16
17
|
|
|
17
|
-
from ..v1_rpc_channel import V1RpcChannel
|
|
18
18
|
from .trait import Trait
|
|
19
19
|
|
|
20
20
|
_LOGGER = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
22
|
__all__ = [
|
|
23
|
-
"
|
|
23
|
+
"StatusTrait",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class StatusTrait(Trait):
|
|
28
|
-
"""
|
|
28
|
+
"""Trait for managing the status of Roborock devices."""
|
|
29
29
|
|
|
30
30
|
name = "status"
|
|
31
31
|
|
|
@@ -132,8 +132,10 @@ class PayloadEncodedV1RpcChannel(BaseV1RpcChannel):
|
|
|
132
132
|
params: ParamsType = None,
|
|
133
133
|
) -> Any:
|
|
134
134
|
"""Send a command and return a parsed response RoborockBase type."""
|
|
135
|
-
_LOGGER.debug("Sending command (%s): %s, params=%s", self._name, method, params)
|
|
136
135
|
request_message = RequestMessage(method, params=params)
|
|
136
|
+
_LOGGER.debug(
|
|
137
|
+
"Sending command (%s, request_id=%s): %s, params=%s", self._name, request_message.request_id, method, params
|
|
138
|
+
)
|
|
137
139
|
message = self._payload_encoder(request_message)
|
|
138
140
|
|
|
139
141
|
future: asyncio.Future[dict[str, Any]] = asyncio.Future()
|
|
@@ -141,8 +143,10 @@ class PayloadEncodedV1RpcChannel(BaseV1RpcChannel):
|
|
|
141
143
|
def find_response(response_message: RoborockMessage) -> None:
|
|
142
144
|
try:
|
|
143
145
|
decoded = decode_rpc_response(response_message)
|
|
144
|
-
except RoborockException:
|
|
146
|
+
except RoborockException as ex:
|
|
147
|
+
_LOGGER.debug("Exception while decoding message (%s): %s", response_message, ex)
|
|
145
148
|
return
|
|
149
|
+
_LOGGER.debug("Received response (request_id=%s): %s", self._name, decoded.request_id)
|
|
146
150
|
if decoded.request_id == request_message.request_id:
|
|
147
151
|
future.set_result(decoded.data)
|
|
148
152
|
|
|
@@ -109,7 +109,7 @@ class ResponseMessage:
|
|
|
109
109
|
def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
|
|
110
110
|
"""Decode a V1 RPC_RESPONSE message."""
|
|
111
111
|
if not message.payload:
|
|
112
|
-
|
|
112
|
+
return ResponseMessage(request_id=message.seq, data={})
|
|
113
113
|
try:
|
|
114
114
|
payload = json.loads(message.payload.decode())
|
|
115
115
|
except (json.JSONDecodeError, TypeError) as e:
|
|
@@ -141,6 +141,8 @@ def decode_rpc_response(message: RoborockMessage) -> ResponseMessage:
|
|
|
141
141
|
_LOGGER.debug("Decoded V1 message result: %s", result)
|
|
142
142
|
if isinstance(result, list) and result:
|
|
143
143
|
result = result[0]
|
|
144
|
+
if isinstance(result, str) and result == "ok":
|
|
145
|
+
result = {}
|
|
144
146
|
if not isinstance(result, dict):
|
|
145
147
|
raise RoborockException(f"Invalid V1 message format: 'result' should be a dictionary for {message.payload!r}")
|
|
146
148
|
return ResponseMessage(request_id=request_id, data=result)
|
|
@@ -157,19 +159,18 @@ class MapResponse:
|
|
|
157
159
|
"""The map data, decrypted and decompressed."""
|
|
158
160
|
|
|
159
161
|
|
|
160
|
-
def create_map_response_decoder(security_data: SecurityData) -> Callable[[RoborockMessage], MapResponse]:
|
|
162
|
+
def create_map_response_decoder(security_data: SecurityData) -> Callable[[RoborockMessage], MapResponse | None]:
|
|
161
163
|
"""Create a decoder for V1 map response messages."""
|
|
162
164
|
|
|
163
|
-
def _decode_map_response(message: RoborockMessage) -> MapResponse:
|
|
165
|
+
def _decode_map_response(message: RoborockMessage) -> MapResponse | None:
|
|
164
166
|
"""Decode a V1 map response message."""
|
|
165
167
|
if not message.payload or len(message.payload) < 24:
|
|
166
168
|
raise RoborockException("Invalid V1 map response format: missing payload")
|
|
167
169
|
header, body = message.payload[:24], message.payload[24:]
|
|
168
170
|
[endpoint, _, request_id, _] = struct.unpack("<8s8sH6s", header)
|
|
169
171
|
if not endpoint.decode().startswith(security_data.endpoint):
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
172
|
+
_LOGGER.debug("Received map response requested not made by this device, ignoring.")
|
|
173
|
+
return None
|
|
173
174
|
try:
|
|
174
175
|
decrypted = Utils.decrypt_cbc(body, security_data.nonce)
|
|
175
176
|
except ValueError as err:
|
{python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_1_apis/roborock_client_v1.py
RENAMED
|
@@ -150,7 +150,7 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
150
150
|
"""Roborock client base class for version 1 devices."""
|
|
151
151
|
|
|
152
152
|
_listeners: dict[str, ListenerModel] = {}
|
|
153
|
-
_map_response_decoder: Callable[[RoborockMessage], MapResponse] | None = None
|
|
153
|
+
_map_response_decoder: Callable[[RoborockMessage], MapResponse | None] | None = None
|
|
154
154
|
|
|
155
155
|
def __init__(self, device_info: DeviceData, security_data: SecurityData | None) -> None:
|
|
156
156
|
"""Initializes the Roborock client."""
|
|
@@ -439,13 +439,14 @@ class RoborockClientV1(RoborockClient, ABC):
|
|
|
439
439
|
elif data.payload and protocol == RoborockMessageProtocol.MAP_RESPONSE:
|
|
440
440
|
if self._map_response_decoder is not None:
|
|
441
441
|
map_response = self._map_response_decoder(data)
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
queue
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
442
|
+
if map_response is not None:
|
|
443
|
+
queue = self._waiting_queue.get(map_response.request_id)
|
|
444
|
+
if queue:
|
|
445
|
+
queue.set_result(map_response.data)
|
|
446
|
+
else:
|
|
447
|
+
self._logger.debug(
|
|
448
|
+
"Received unsolicited map response for request_id %s", map_response.request_id
|
|
449
|
+
)
|
|
449
450
|
else:
|
|
450
451
|
queue = self._waiting_queue.get(data.seq)
|
|
451
452
|
if queue:
|
|
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
|
|
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-2.40.0 → python_roborock-2.41.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_roborock-2.40.0 → python_roborock-2.41.0}/roborock/version_a01_apis/roborock_client_a01.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|