python-roborock 0.6.13__tar.gz → 0.6.15__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-roborock
3
- Version: 0.6.13
3
+ Version: 0.6.15
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Home-page: https://github.com/humbertogontijo/python-roborock
6
6
  License: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-roborock"
3
- version = "0.6.13"
3
+ version = "0.6.15"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = ["humbertogontijo <humbertogontijo@users.noreply.github.com>"]
6
6
  license = "GPL-3.0-only"
@@ -40,6 +40,7 @@ from .exceptions import RoborockException, RoborockTimeout, VacuumError
40
40
  from .roborock_future import RoborockFuture
41
41
  from .roborock_message import RoborockMessage
42
42
  from .typing import RoborockCommand, RoborockDeviceProp, RoborockDockSummary
43
+ from .util import unpack_list
43
44
 
44
45
  _LOGGER = logging.getLogger(__name__)
45
46
  QUEUE_TIMEOUT = 4
@@ -190,6 +191,14 @@ class RoborockClient:
190
191
  clean_summary = await self.send_command(device_id, RoborockCommand.GET_CLEAN_SUMMARY)
191
192
  if isinstance(clean_summary, dict):
192
193
  return CleanSummary.from_dict(clean_summary)
194
+ elif isinstance(clean_summary, list):
195
+ clean_time, clean_area, clean_count, records = unpack_list(clean_summary, 4)
196
+ return CleanSummary(
197
+ clean_time=clean_time,
198
+ clean_area=clean_area,
199
+ clean_count=clean_count,
200
+ records=records
201
+ )
193
202
  elif isinstance(clean_summary, int):
194
203
  return CleanSummary(clean_time=clean_summary)
195
204
  except RoborockTimeout as e:
@@ -244,6 +253,7 @@ class RoborockClient:
244
253
  async def get_dock_summary(self, device_id: str, dock_type: RoborockEnum) -> RoborockDockSummary | None:
245
254
  """Gets the status summary from the dock with the methods available for a given dock.
246
255
 
256
+ :param device_id: Device id
247
257
  :param dock_type: RoborockDockTypeCode"""
248
258
  try:
249
259
  commands: list[
@@ -258,9 +268,7 @@ class RoborockClient:
258
268
  self.get_wash_towel_mode(device_id),
259
269
  self.get_smart_wash_params(device_id),
260
270
  ]
261
- [dust_collection_mode, wash_towel_mode, smart_wash_params] = (
262
- list(await asyncio.gather(*commands)) + [None, None]
263
- )[:3]
271
+ [dust_collection_mode, wash_towel_mode, smart_wash_params] = unpack_list(list(await asyncio.gather(*commands)), 3)
264
272
 
265
273
  return RoborockDockSummary(dust_collection_mode, wash_towel_mode, smart_wash_params)
266
274
  except RoborockTimeout as e:
@@ -25,6 +25,8 @@ from .code_mappings import (
25
25
 
26
26
  def camelize(s: str):
27
27
  first, *others = s.split("_")
28
+ if len(others) == 0:
29
+ return s
28
30
  return "".join([first.lower(), *map(str.title, others)])
29
31
 
30
32
 
@@ -32,19 +34,27 @@ def decamelize(s: str):
32
34
  return re.sub("([A-Z]+)", "_\\1", s).lower()
33
35
 
34
36
 
35
- def decamelize_obj(d: dict | list):
37
+ def decamelize_obj(d: dict | list, ignore_keys: list[str]):
36
38
  if isinstance(d, list):
37
- return [decamelize_obj(i) if isinstance(i, (dict, list)) else i for i in d]
38
- return {decamelize(a): decamelize_obj(b) if isinstance(b, (dict, list)) else b for a, b in d.items()}
39
+ return [decamelize_obj(i, ignore_keys) if isinstance(i, (dict, list)) else i for i in d]
40
+ return {
41
+ (decamelize(a) if a not in ignore_keys else a): decamelize_obj(b, ignore_keys)
42
+ if isinstance(b, (dict, list))
43
+ else b
44
+ for a, b in d.items()
45
+ }
39
46
 
40
47
 
41
48
  @dataclass
42
49
  class RoborockBase:
50
+ _ignore_keys = [] # type: ignore
51
+
43
52
  @classmethod
44
53
  def from_dict(cls, data: dict[str, Any]):
45
- return from_dict(cls, decamelize_obj(data), config=Config(cast=[Enum]))
54
+ ignore_keys = cls._ignore_keys
55
+ return from_dict(cls, decamelize_obj(data, ignore_keys), config=Config(cast=[Enum]))
46
56
 
47
- def as_dict(self):
57
+ def as_dict(self) -> dict:
48
58
  return asdict(
49
59
  self,
50
60
  dict_factory=lambda _fields: {camelize(key): value for (key, value) in _fields if value is not None},
@@ -277,7 +287,9 @@ class MultiMapsListMapInfoBakMaps(RoborockBase):
277
287
 
278
288
  @dataclass
279
289
  class MultiMapsListMapInfo(RoborockBase):
280
- mapflag: Optional[Any] = None
290
+ _ignore_keys = ["mapFlag"]
291
+
292
+ mapFlag: Optional[Any] = None
281
293
  add_time: Optional[Any] = None
282
294
  length: Optional[Any] = None
283
295
  name: Optional[Any] = None
@@ -286,6 +298,8 @@ class MultiMapsListMapInfo(RoborockBase):
286
298
 
287
299
  @dataclass
288
300
  class MultiMapsList(RoborockBase):
301
+ _ignore_keys = ["mapFlag"]
302
+
289
303
  max_multi_map: Optional[int] = None
290
304
  max_bak_map: Optional[int] = None
291
305
  multi_map_count: Optional[int] = None
@@ -60,7 +60,7 @@ class RoborockMessage:
60
60
  return data_point_response.get("method")
61
61
  return None
62
62
 
63
- def get_params(self) -> list | None:
63
+ def get_params(self) -> list | dict | None:
64
64
  protocol = self.protocol
65
65
  if protocol in [4, 101, 102]:
66
66
  payload = json.loads(self.payload.decode())
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import typing
3
+ from typing import Optional
4
4
  from dataclasses import dataclass
5
5
  from enum import Enum
6
6
 
@@ -117,6 +117,12 @@ class RoborockCommand(str, Enum):
117
117
  GET_ROOM_MAPPING = "get_room_mapping"
118
118
  NAME_SEGMENT = "name_segment"
119
119
  SET_TIMEZONE = "set_timezone"
120
+ GET_HOMESEC_CONNECT_STATUS = "get_homesec_connect_status"
121
+ START_CAMERA_PREVIEW = "start_camera_preview"
122
+ GET_TURN_SERVER = "get_turn_server"
123
+ GET_DEVICE_ICE = "get_device_ice"
124
+ START_VOICE_CHAT = "start_voice_chat"
125
+ SEND_SDP_TO_ROBOT = "send_sdp_to_robot"
120
126
 
121
127
 
122
128
  @dataclass
@@ -200,7 +206,11 @@ CommandInfoMap: dict[RoborockCommand, CommandInfo] = {
200
206
  RoborockCommand.SET_SERVER_TIMER: CommandInfo(prefix=b"\x00\x00\x00\xc7"),
201
207
  RoborockCommand.GET_ROOM_MAPPING: CommandInfo(prefix=b"\x00\x00\x00w"),
202
208
  RoborockCommand.NAME_SEGMENT: CommandInfo(prefix=b"\x00\x00\x027"),
203
- RoborockCommand.SET_TIMEZONE: CommandInfo(prefix=b"\x00\x00\x00\x97")
209
+ RoborockCommand.SET_TIMEZONE: CommandInfo(prefix=b"\x00\x00\x00\x97"),
210
+ RoborockCommand.GET_HOMESEC_CONNECT_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
211
+ RoborockCommand.START_CAMERA_PREVIEW: CommandInfo(prefix=b"\x00\x00\x00\x87"),
212
+ RoborockCommand.GET_TURN_SERVER: CommandInfo(prefix=b"\x00\x00\x00\x77"),
213
+ RoborockCommand.GET_DEVICE_ICE: CommandInfo(prefix=b"\x00\x00\x00\x77"),
204
214
  # TODO discover prefix for following commands
205
215
  # RoborockCommand.APP_GET_DRYER_SETTING: CommandInfo(prefix=b'\x00\x00\x00w'),
206
216
  # RoborockCommand.APP_SET_DRYER_SETTING: CommandInfo(prefix=b'\x00\x00\x00w'),
@@ -214,26 +224,21 @@ CommandInfoMap: dict[RoborockCommand, CommandInfo] = {
214
224
  }
215
225
 
216
226
 
227
+ @dataclass
217
228
  class RoborockDockSummary:
218
- def __init__(
219
- self,
220
- dust_collection_mode: DustCollectionMode,
221
- wash_towel_mode: WashTowelMode,
222
- smart_wash_params: SmartWashParams,
223
- ) -> None:
224
- self.dust_collection_mode = dust_collection_mode
225
- self.wash_towel_mode = wash_towel_mode
226
- self.smart_wash_params = smart_wash_params
229
+ dust_collection_mode: Optional[DustCollectionMode] = None
230
+ wash_towel_mode: Optional[WashTowelMode] = None
231
+ smart_wash_params: Optional[SmartWashParams] = None
227
232
 
228
233
 
229
234
  @dataclass
230
235
  class RoborockDeviceProp:
231
- status: typing.Optional[Status] = None
232
- dnd_timer: typing.Optional[DNDTimer] = None
233
- clean_summary: typing.Optional[CleanSummary] = None
234
- consumable: typing.Optional[Consumable] = None
235
- last_clean_record: typing.Optional[CleanRecord] = None
236
- dock_summary: typing.Optional[RoborockDockSummary] = None
236
+ status: Optional[Status] = None
237
+ dnd_timer: Optional[DNDTimer] = None
238
+ clean_summary: Optional[CleanSummary] = None
239
+ consumable: Optional[Consumable] = None
240
+ last_clean_record: Optional[CleanRecord] = None
241
+ dock_summary: Optional[RoborockDockSummary] = None
237
242
 
238
243
  def update(self, device_prop: "RoborockDeviceProp"):
239
244
  if device_prop.status:
@@ -1,7 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import functools
3
5
  from asyncio import AbstractEventLoop
6
+ from typing import TypeVar
7
+
8
+ T = TypeVar("T")
4
9
 
10
+ def unpack_list(value: list[T], size: int) -> list[T | None]:
11
+ return (value + [None] * size)[:size]
5
12
 
6
13
  def get_running_loop_or_create_one() -> AbstractEventLoop:
7
14
  try: