|
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