python-roborock 2.6.1__tar.gz → 2.7.1__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.
Files changed (28) hide show
  1. {python_roborock-2.6.1 → python_roborock-2.7.1}/PKG-INFO +1 -1
  2. {python_roborock-2.6.1 → python_roborock-2.7.1}/pyproject.toml +1 -1
  3. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/api.py +8 -1
  4. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/containers.py +64 -9
  5. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/roborock_message.py +3 -3
  6. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/util.py +12 -0
  7. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_1_apis/roborock_client_v1.py +2 -3
  8. {python_roborock-2.6.1 → python_roborock-2.7.1}/LICENSE +0 -0
  9. {python_roborock-2.6.1 → python_roborock-2.7.1}/README.md +0 -0
  10. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/__init__.py +0 -0
  11. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/cli.py +0 -0
  12. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/cloud_api.py +0 -0
  13. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/code_mappings.py +0 -0
  14. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/command_cache.py +0 -0
  15. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/const.py +0 -0
  16. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/exceptions.py +0 -0
  17. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/local_api.py +0 -0
  18. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/protocol.py +0 -0
  19. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/py.typed +0 -0
  20. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/roborock_future.py +0 -0
  21. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/roborock_typing.py +0 -0
  22. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_1_apis/__init__.py +0 -0
  23. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  24. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  25. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_a01_apis/__init__.py +0 -0
  26. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  27. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
  28. {python_roborock-2.6.1 → python_roborock-2.7.1}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-roborock
3
- Version: 2.6.1
3
+ Version: 2.7.1
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 = "2.6.1"
3
+ version = "2.7.1"
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"
@@ -23,7 +23,7 @@ from .roborock_message import (
23
23
  RoborockMessage,
24
24
  )
25
25
  from .roborock_typing import RoborockCommand
26
- from .util import RoborockLoggerAdapter, get_running_loop_or_create_one
26
+ from .util import RoborockLoggerAdapter, get_next_int, get_running_loop_or_create_one
27
27
 
28
28
  _LOGGER = logging.getLogger(__name__)
29
29
  KEEPALIVE = 60
@@ -113,6 +113,13 @@ class RoborockClient:
113
113
  self, request_id: int, protocol_id: int = 0
114
114
  ) -> Coroutine[Any, Any, tuple[Any, VacuumError | None]]:
115
115
  queue = RoborockFuture(protocol_id)
116
+ if request_id in self._waiting_queue:
117
+ new_id = get_next_int(10000, 32767)
118
+ _LOGGER.warning(
119
+ f"Attempting to create a future with an existing request_id... New id is {new_id}. "
120
+ f"Code may not function properly."
121
+ )
122
+ request_id = new_id
116
123
  self._waiting_queue[request_id] = queue
117
124
  return self._wait_response(request_id, queue)
118
125
 
@@ -7,9 +7,7 @@ import re
7
7
  from dataclasses import asdict, dataclass, field
8
8
  from datetime import timezone
9
9
  from enum import Enum
10
- from typing import Any, NamedTuple
11
-
12
- from dacite import Config, from_dict
10
+ from typing import Any, NamedTuple, get_args, get_origin
13
11
 
14
12
  from .code_mappings import (
15
13
  RoborockCategory,
@@ -102,14 +100,70 @@ class RoborockBase:
102
100
  _ignore_keys = [] # type: ignore
103
101
  is_cached = False
104
102
 
103
+ @staticmethod
104
+ def convert_to_class_obj(type, value):
105
+ try:
106
+ class_type = eval(type)
107
+ if get_origin(class_type) is list:
108
+ return_list = []
109
+ cls_type = get_args(class_type)[0]
110
+ for obj in value:
111
+ if issubclass(cls_type, RoborockBase):
112
+ return_list.append(cls_type.from_dict(obj))
113
+ elif cls_type in {str, int, float}:
114
+ return_list.append(cls_type(obj))
115
+ else:
116
+ return_list.append(cls_type(**obj))
117
+ return return_list
118
+ if issubclass(class_type, RoborockBase):
119
+ converted_value = class_type.from_dict(value)
120
+ else:
121
+ converted_value = class_type(value)
122
+ return converted_value
123
+ except NameError as err:
124
+ _LOGGER.exception(err)
125
+ except ValueError as err:
126
+ _LOGGER.exception(err)
127
+ except Exception as err:
128
+ _LOGGER.exception(err)
129
+ raise Exception("Fail")
130
+
105
131
  @classmethod
106
132
  def from_dict(cls, data: dict[str, Any]):
107
133
  if isinstance(data, dict):
108
134
  ignore_keys = cls._ignore_keys
109
- try:
110
- return from_dict(cls, decamelize_obj(data, ignore_keys), config=Config(cast=[Enum]))
111
- except AttributeError as err:
112
- raise RoborockException("It seems like you have an outdated version of dacite.") from err
135
+ data = decamelize_obj(data, ignore_keys)
136
+ cls_annotations: dict[str, str] = {}
137
+ for base in reversed(cls.__mro__):
138
+ cls_annotations.update(getattr(base, "__annotations__", {}))
139
+ remove_keys = []
140
+ for key, value in data.items():
141
+ if value == "None" or value is None:
142
+ data[key] = None
143
+ continue
144
+ if key not in cls_annotations:
145
+ remove_keys.append(key)
146
+ continue
147
+ field_type: str = cls_annotations[key]
148
+ if "|" in field_type:
149
+ # It's a union
150
+ types = field_type.split("|")
151
+ for type in types:
152
+ if "None" in type or "Any" in type:
153
+ continue
154
+ try:
155
+ data[key] = RoborockBase.convert_to_class_obj(type, value)
156
+ break
157
+ except Exception:
158
+ ...
159
+ else:
160
+ try:
161
+ data[key] = RoborockBase.convert_to_class_obj(field_type, value)
162
+ except Exception:
163
+ ...
164
+ for key in remove_keys:
165
+ del data[key]
166
+ return cls(**data)
113
167
 
114
168
  def as_dict(self) -> dict:
115
169
  return asdict(
@@ -185,6 +239,7 @@ class HomeDataProductSchema(RoborockBase):
185
239
  mode: Any | None = None
186
240
  type: Any | None = None
187
241
  product_property: Any | None = None
242
+ property: Any | None = None
188
243
  desc: Any | None = None
189
244
 
190
245
 
@@ -195,7 +250,7 @@ class HomeDataProduct(RoborockBase):
195
250
  model: str
196
251
  category: RoborockCategory
197
252
  code: str | None = None
198
- iconurl: str | None = None
253
+ icon_url: str | None = None
199
254
  attribute: Any | None = None
200
255
  capability: int | None = None
201
256
  schema: list[HomeDataProductSchema] | None = None
@@ -612,7 +667,7 @@ class CleanSummary(RoborockBase):
612
667
  last_clean_t: int | None = None
613
668
 
614
669
  def __post_init__(self) -> None:
615
- if isinstance(self.clean_area, list):
670
+ if isinstance(self.clean_area, list | str):
616
671
  _LOGGER.warning(f"Clean area is a unexpected type! Please give the following in a issue: {self.clean_area}")
617
672
  else:
618
673
  self.square_meter_clean_area = round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
@@ -4,9 +4,9 @@ import json
4
4
  import math
5
5
  import time
6
6
  from dataclasses import dataclass
7
- from random import randint
8
7
 
9
8
  from roborock import RoborockEnum
9
+ from roborock.util import get_next_int
10
10
 
11
11
 
12
12
  class RoborockMessageProtocol(RoborockEnum):
@@ -155,9 +155,9 @@ class MessageRetry:
155
155
  class RoborockMessage:
156
156
  protocol: RoborockMessageProtocol
157
157
  payload: bytes | None = None
158
- seq: int = randint(100000, 999999)
158
+ seq: int = get_next_int(100000, 999999)
159
159
  version: bytes = b"1.0"
160
- random: int = randint(10000, 99999)
160
+ random: int = get_next_int(10000, 99999)
161
161
  timestamp: int = math.floor(time.time())
162
162
  message_retry: MessageRetry | None = None
163
163
 
@@ -108,3 +108,15 @@ class RoborockLoggerAdapter(logging.LoggerAdapter):
108
108
 
109
109
  def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, MutableMapping[str, Any]]:
110
110
  return f"[{self.prefix}] {msg}", kwargs
111
+
112
+
113
+ counter_map: dict[tuple[int, int], int] = {}
114
+
115
+
116
+ def get_next_int(min_val: int, max_val: int):
117
+ """Gets a random int in the range, precached to help keep it fast."""
118
+ if (min_val, max_val) not in counter_map:
119
+ # If we have never seen this range, or if the cache is getting low, make a bunch of preshuffled values.
120
+ counter_map[(min_val, max_val)] = min_val
121
+ counter_map[(min_val, max_val)] += 1
122
+ return counter_map[(min_val, max_val)] % max_val + min_val
@@ -5,7 +5,6 @@ import math
5
5
  import struct
6
6
  import time
7
7
  from collections.abc import Callable, Coroutine
8
- from random import randint
9
8
  from typing import Any, TypeVar, final
10
9
 
11
10
  from roborock import (
@@ -54,7 +53,7 @@ from roborock.roborock_message import (
54
53
  RoborockMessage,
55
54
  RoborockMessageProtocol,
56
55
  )
57
- from roborock.util import RepeatableTask, unpack_list
56
+ from roborock.util import RepeatableTask, get_next_int, unpack_list
58
57
 
59
58
  COMMANDS_SECURED = [
60
59
  RoborockCommand.GET_MAP_V1,
@@ -334,7 +333,7 @@ class RoborockClientV1(RoborockClient):
334
333
  secured=False,
335
334
  ):
336
335
  timestamp = math.floor(time.time())
337
- request_id = randint(10000, 32767)
336
+ request_id = get_next_int(10000, 32767)
338
337
  inner = {
339
338
  "id": request_id,
340
339
  "method": method,
File without changes