|
8 | 8 | import hashlib |
9 | 9 | import json |
10 | 10 | import logging |
11 | | -import math |
12 | 11 | import secrets |
13 | 12 | import struct |
14 | 13 | import time |
15 | 14 | from collections.abc import Callable, Coroutine |
16 | | -from random import randint |
17 | 15 | from typing import Any, TypeVar, final |
18 | 16 |
|
19 | | -from .code_mappings import RoborockDockTypeCode |
20 | 17 | from .command_cache import CacheableAttribute, CommandType, RoborockAttribute, find_cacheable_attribute, get_cache_map |
21 | 18 | from .containers import ( |
22 | | - ChildLockStatus, |
23 | | - CleanRecord, |
24 | | - CleanSummary, |
25 | 19 | Consumable, |
26 | 20 | DeviceData, |
27 | | - DnDTimer, |
28 | | - DustCollectionMode, |
29 | | - FlowLedStatus, |
30 | 21 | ModelStatus, |
31 | | - MultiMapsList, |
32 | | - NetworkInfo, |
33 | 22 | RoborockBase, |
34 | | - RoomMapping, |
35 | 23 | S7MaxVStatus, |
36 | | - ServerTimer, |
37 | | - SmartWashParams, |
38 | 24 | Status, |
39 | | - ValleyElectricityTimer, |
40 | | - WashTowelMode, |
41 | 25 | ) |
42 | 26 | from .exceptions import ( |
43 | 27 | RoborockException, |
|
54 | 38 | RoborockMessage, |
55 | 39 | RoborockMessageProtocol, |
56 | 40 | ) |
57 | | -from .roborock_typing import DeviceProp, DockSummary, RoborockCommand |
58 | | -from .util import RepeatableTask, RoborockLoggerAdapter, get_running_loop_or_create_one, unpack_list |
| 41 | +from .roborock_typing import RoborockCommand |
| 42 | +from .util import RepeatableTask, RoborockLoggerAdapter, get_running_loop_or_create_one |
59 | 43 |
|
60 | 44 | _LOGGER = logging.getLogger(__name__) |
61 | 45 | KEEPALIVE = 60 |
62 | | -COMMANDS_SECURED = [ |
63 | | - RoborockCommand.GET_MAP_V1, |
64 | | - RoborockCommand.GET_MULTI_MAP, |
65 | | -] |
66 | 46 | RT = TypeVar("RT", bound=RoborockBase) |
67 | | -WASH_N_FILL_DOCK = [ |
68 | | - RoborockDockTypeCode.empty_wash_fill_dock, |
69 | | - RoborockDockTypeCode.s8_dock, |
70 | | - RoborockDockTypeCode.p10_dock, |
71 | | -] |
72 | 47 |
|
73 | 48 |
|
74 | 49 | def md5hex(message: str) -> str: |
@@ -286,7 +261,7 @@ def on_message_received(self, messages: list[RoborockMessage]) -> None: |
286 | 261 | try: |
287 | 262 | decrypted = Utils.decrypt_cbc(data.payload[24:], self._nonce) |
288 | 263 | except ValueError as err: |
289 | | - raise RoborockException("Failed to decode %s for %s", data.payload, data.protocol) from err |
| 264 | + raise RoborockException(f"Failed to decode {data.payload!r} for {data.protocol}") from err |
290 | 265 | decompressed = Utils.decompress(decrypted) |
291 | 266 | queue = self._waiting_queue.get(request_id) |
292 | 267 | if queue: |
@@ -336,35 +311,6 @@ def _async_response( |
336 | 311 | self._waiting_queue[request_id] = queue |
337 | 312 | return self._wait_response(request_id, queue) |
338 | 313 |
|
339 | | - def _get_payload( |
340 | | - self, |
341 | | - method: RoborockCommand | str, |
342 | | - params: list | dict | int | None = None, |
343 | | - secured=False, |
344 | | - ): |
345 | | - timestamp = math.floor(time.time()) |
346 | | - request_id = randint(10000, 32767) |
347 | | - inner = { |
348 | | - "id": request_id, |
349 | | - "method": method, |
350 | | - "params": params or [], |
351 | | - } |
352 | | - if secured: |
353 | | - inner["security"] = { |
354 | | - "endpoint": self._endpoint, |
355 | | - "nonce": self._nonce.hex().lower(), |
356 | | - } |
357 | | - payload = bytes( |
358 | | - json.dumps( |
359 | | - { |
360 | | - "dps": {"101": json.dumps(inner, separators=(",", ":"))}, |
361 | | - "t": timestamp, |
362 | | - }, |
363 | | - separators=(",", ":"), |
364 | | - ).encode() |
365 | | - ) |
366 | | - return request_id, timestamp, payload |
367 | | - |
368 | 314 | async def send_message(self, roborock_message: RoborockMessage): |
369 | 315 | raise NotImplementedError |
370 | 316 |
|
@@ -402,148 +348,6 @@ async def send_command( |
402 | 348 | return return_type.from_dict(response) |
403 | 349 | return response |
404 | 350 |
|
405 | | - async def get_status(self) -> Status: |
406 | | - data = self._status_type.from_dict(await self.cache[CacheableAttribute.status].async_value()) |
407 | | - if data is None: |
408 | | - return self._status_type() |
409 | | - return data |
410 | | - |
411 | | - async def get_dnd_timer(self) -> DnDTimer | None: |
412 | | - return DnDTimer.from_dict(await self.cache[CacheableAttribute.dnd_timer].async_value()) |
413 | | - |
414 | | - async def get_valley_electricity_timer(self) -> ValleyElectricityTimer | None: |
415 | | - return ValleyElectricityTimer.from_dict( |
416 | | - await self.cache[CacheableAttribute.valley_electricity_timer].async_value() |
417 | | - ) |
418 | | - |
419 | | - async def get_clean_summary(self) -> CleanSummary | None: |
420 | | - clean_summary: dict | list | int = await self.send_command(RoborockCommand.GET_CLEAN_SUMMARY) |
421 | | - if isinstance(clean_summary, dict): |
422 | | - return CleanSummary.from_dict(clean_summary) |
423 | | - elif isinstance(clean_summary, list): |
424 | | - clean_time, clean_area, clean_count, records = unpack_list(clean_summary, 4) |
425 | | - return CleanSummary( |
426 | | - clean_time=clean_time, |
427 | | - clean_area=clean_area, |
428 | | - clean_count=clean_count, |
429 | | - records=records, |
430 | | - ) |
431 | | - elif isinstance(clean_summary, int): |
432 | | - return CleanSummary(clean_time=clean_summary) |
433 | | - return None |
434 | | - |
435 | | - async def get_clean_record(self, record_id: int) -> CleanRecord | None: |
436 | | - record: dict | list = await self.send_command(RoborockCommand.GET_CLEAN_RECORD, [record_id]) |
437 | | - if isinstance(record, dict): |
438 | | - return CleanRecord.from_dict(record) |
439 | | - elif isinstance(record, list): |
440 | | - # There are still a few unknown variables in this. |
441 | | - begin, end, duration, area = unpack_list(record, 4) |
442 | | - return CleanRecord(begin=begin, end=end, duration=duration, area=area) |
443 | | - else: |
444 | | - _LOGGER.warning("Clean record was of a new type, please submit an issue request: %s", record) |
445 | | - return None |
446 | | - |
447 | | - async def get_consumable(self) -> Consumable: |
448 | | - data = Consumable.from_dict(await self.cache[CacheableAttribute.consumable].async_value()) |
449 | | - if data is None: |
450 | | - return Consumable() |
451 | | - return data |
452 | | - |
453 | | - async def get_wash_towel_mode(self) -> WashTowelMode | None: |
454 | | - return WashTowelMode.from_dict(await self.cache[CacheableAttribute.wash_towel_mode].async_value()) |
455 | | - |
456 | | - async def get_dust_collection_mode(self) -> DustCollectionMode | None: |
457 | | - return DustCollectionMode.from_dict(await self.cache[CacheableAttribute.dust_collection_mode].async_value()) |
458 | | - |
459 | | - async def get_smart_wash_params(self) -> SmartWashParams | None: |
460 | | - return SmartWashParams.from_dict(await self.cache[CacheableAttribute.smart_wash_params].async_value()) |
461 | | - |
462 | | - async def get_dock_summary(self, dock_type: RoborockDockTypeCode) -> DockSummary: |
463 | | - """Gets the status summary from the dock with the methods available for a given dock. |
464 | | -
|
465 | | - :param dock_type: RoborockDockTypeCode""" |
466 | | - commands: list[ |
467 | | - Coroutine[ |
468 | | - Any, |
469 | | - Any, |
470 | | - DustCollectionMode | WashTowelMode | SmartWashParams | None, |
471 | | - ] |
472 | | - ] = [self.get_dust_collection_mode()] |
473 | | - if dock_type in WASH_N_FILL_DOCK: |
474 | | - commands += [ |
475 | | - self.get_wash_towel_mode(), |
476 | | - self.get_smart_wash_params(), |
477 | | - ] |
478 | | - [dust_collection_mode, wash_towel_mode, smart_wash_params] = unpack_list( |
479 | | - list(await asyncio.gather(*commands)), 3 |
480 | | - ) # type: DustCollectionMode, WashTowelMode | None, SmartWashParams | None # type: ignore |
481 | | - |
482 | | - return DockSummary(dust_collection_mode, wash_towel_mode, smart_wash_params) |
483 | | - |
484 | | - async def get_prop(self) -> DeviceProp | None: |
485 | | - """Gets device general properties.""" |
486 | | - # Mypy thinks that each one of these is typed as a union of all the others. so we do type ignore. |
487 | | - status, clean_summary, consumable = await asyncio.gather( |
488 | | - *[ |
489 | | - self.get_status(), |
490 | | - self.get_clean_summary(), |
491 | | - self.get_consumable(), |
492 | | - ] |
493 | | - ) # type: Status, CleanSummary, Consumable # type: ignore |
494 | | - last_clean_record = None |
495 | | - if clean_summary and clean_summary.records and len(clean_summary.records) > 0: |
496 | | - last_clean_record = await self.get_clean_record(clean_summary.records[0]) |
497 | | - dock_summary = None |
498 | | - if status and status.dock_type is not None and status.dock_type != RoborockDockTypeCode.no_dock: |
499 | | - dock_summary = await self.get_dock_summary(status.dock_type) |
500 | | - if any([status, clean_summary, consumable]): |
501 | | - return DeviceProp( |
502 | | - status, |
503 | | - clean_summary, |
504 | | - consumable, |
505 | | - last_clean_record, |
506 | | - dock_summary, |
507 | | - ) |
508 | | - return None |
509 | | - |
510 | | - async def get_multi_maps_list(self) -> MultiMapsList | None: |
511 | | - return await self.send_command(RoborockCommand.GET_MULTI_MAPS_LIST, return_type=MultiMapsList) |
512 | | - |
513 | | - async def get_networking(self) -> NetworkInfo | None: |
514 | | - return await self.send_command(RoborockCommand.GET_NETWORK_INFO, return_type=NetworkInfo) |
515 | | - |
516 | | - async def get_room_mapping(self) -> list[RoomMapping] | None: |
517 | | - """Gets the mapping from segment id -> iot id. Only works on local api.""" |
518 | | - mapping: list = await self.send_command(RoborockCommand.GET_ROOM_MAPPING) |
519 | | - if isinstance(mapping, list): |
520 | | - return [ |
521 | | - RoomMapping(segment_id=segment_id, iot_id=iot_id) # type: ignore |
522 | | - for segment_id, iot_id in [unpack_list(room, 2) for room in mapping if isinstance(room, list)] |
523 | | - ] |
524 | | - return None |
525 | | - |
526 | | - async def get_child_lock_status(self) -> ChildLockStatus: |
527 | | - """Gets current child lock status.""" |
528 | | - return ChildLockStatus.from_dict(await self.cache[CacheableAttribute.child_lock_status].async_value()) |
529 | | - |
530 | | - async def get_flow_led_status(self) -> FlowLedStatus: |
531 | | - """Gets current flow led status.""" |
532 | | - return FlowLedStatus.from_dict(await self.cache[CacheableAttribute.flow_led_status].async_value()) |
533 | | - |
534 | | - async def get_sound_volume(self) -> int | None: |
535 | | - """Gets current volume level.""" |
536 | | - return await self.cache[CacheableAttribute.sound_volume].async_value() |
537 | | - |
538 | | - async def get_server_timer(self) -> list[ServerTimer]: |
539 | | - """Gets current server timer.""" |
540 | | - server_timers = await self.cache[CacheableAttribute.server_timer].async_value() |
541 | | - if server_timers: |
542 | | - if isinstance(server_timers[0], list): |
543 | | - return [ServerTimer(*server_timer) for server_timer in server_timers] |
544 | | - return [ServerTimer(*server_timers)] |
545 | | - return [] |
546 | | - |
547 | 351 | def add_listener( |
548 | 352 | self, protocol: RoborockDataProtocol, listener: Callable, cache: dict[CacheableAttribute, AttributeCache] |
549 | 353 | ) -> None: |
|
0 commit comments