Skip to content

Commit

Permalink
Merge branch 'main' into calibration_command_and_codes
Browse files Browse the repository at this point in the history
  • Loading branch information
Lash-L authored Nov 11, 2024
2 parents f88fb3f + df331ea commit d80dd03
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 21 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

<!--next-version-placeholder-->

## v2.7.2 (2024-11-08)

### Fix

* Add some new roborock codes ([#233](https://github.com/humbertogontijo/python-roborock/issues/233)) ([`59546dd`](https://github.com/humbertogontijo/python-roborock/commit/59546dd68f7b40ad368d58fd502680ff9c03c81b))

## v2.7.1 (2024-10-28)

### Fix

* Check that clean area is not a str ([#230](https://github.com/humbertogontijo/python-roborock/issues/230)) ([`e66a91e`](https://github.com/humbertogontijo/python-roborock/commit/e66a91edaf6fedf5d4b2ab9117b7759295add492))

## v2.7.0 (2024-10-28)

### Feature

* Remove dacite ([#227](https://github.com/humbertogontijo/python-roborock/issues/227)) ([`86878a7`](https://github.com/humbertogontijo/python-roborock/commit/86878a71d82c2cc707daa16dec109fc07360e3f6))

## v2.6.1 (2024-10-22)

### Fix
Expand Down
2 changes: 1 addition & 1 deletion docs/source/_templates/footer.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "!footer.html" %}
{%- block contentinfo %}
{{ super }}
{{ super() }}
<p>We are looking for contributors to help with our documentation, if you are interested please <a href="https://github.com/humbertogontijo/python-roborock">contribute here.</a>
{% endblock %}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "python-roborock"
version = "2.6.1"
version = "2.7.2"
description = "A package to control Roborock vacuums."
authors = ["humbertogontijo <[email protected]>"]
license = "GPL-3.0-only"
Expand Down
9 changes: 8 additions & 1 deletion roborock/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
RoborockMessage,
)
from .roborock_typing import RoborockCommand
from .util import RoborockLoggerAdapter, get_running_loop_or_create_one
from .util import RoborockLoggerAdapter, get_next_int, get_running_loop_or_create_one

_LOGGER = logging.getLogger(__name__)
KEEPALIVE = 60
Expand Down Expand Up @@ -113,6 +113,13 @@ def _async_response(
self, request_id: int, protocol_id: int = 0
) -> Coroutine[Any, Any, tuple[Any, VacuumError | None]]:
queue = RoborockFuture(protocol_id)
if request_id in self._waiting_queue:
new_id = get_next_int(10000, 32767)
_LOGGER.warning(
f"Attempting to create a future with an existing request_id... New id is {new_id}. "
f"Code may not function properly."
)
request_id = new_id
self._waiting_queue[request_id] = queue
return self._wait_response(request_id, queue)

Expand Down
11 changes: 11 additions & 0 deletions roborock/code_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ class RoborockMopModeS8ProUltra(RoborockMopModeCode):
class RoborockMopModeS8MaxVUltra(RoborockMopModeCode):
standard = 300
deep = 301
custom = 302
deep_plus = 303
fast = 304
deep_plus_pearl = 305
Expand Down Expand Up @@ -382,6 +383,16 @@ class RoborockMopIntensityS6MaxV(RoborockMopIntensityCode):
custom_water_flow = 207


class RoborockMopIntensityQ7Max(RoborockMopIntensityCode):
"""Describes the mop intensity of the vacuum cleaner."""

off = 200
low = 201
medium = 202
high = 203
custom_water_flow = 207


class RoborockDockErrorCode(RoborockEnum):
"""Describes the error code of the dock."""

Expand Down
77 changes: 66 additions & 11 deletions roborock/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
from dataclasses import asdict, dataclass, field
from datetime import timezone
from enum import Enum
from typing import Any, NamedTuple

from dacite import Config, from_dict
from typing import Any, NamedTuple, get_args, get_origin

from .code_mappings import (
RoborockCategory,
Expand All @@ -32,11 +30,11 @@
RoborockMopIntensityCode,
RoborockMopIntensityP10,
RoborockMopIntensityQRevoMaster,
RoborockMopIntensityQ7Max,
RoborockMopIntensityS5Max,
RoborockMopIntensityS6MaxV,
RoborockMopIntensityS7,
RoborockMopIntensityS8MaxVUltra,
RoborockMopIntensityV2,
RoborockMopModeCode,
RoborockMopModeS7,
RoborockMopModeS8MaxVUltra,
Expand Down Expand Up @@ -105,14 +103,70 @@ class RoborockBase:
_ignore_keys = [] # type: ignore
is_cached = False

@staticmethod
def convert_to_class_obj(type, value):
try:
class_type = eval(type)
if get_origin(class_type) is list:
return_list = []
cls_type = get_args(class_type)[0]
for obj in value:
if issubclass(cls_type, RoborockBase):
return_list.append(cls_type.from_dict(obj))
elif cls_type in {str, int, float}:
return_list.append(cls_type(obj))
else:
return_list.append(cls_type(**obj))
return return_list
if issubclass(class_type, RoborockBase):
converted_value = class_type.from_dict(value)
else:
converted_value = class_type(value)
return converted_value
except NameError as err:
_LOGGER.exception(err)
except ValueError as err:
_LOGGER.exception(err)
except Exception as err:
_LOGGER.exception(err)
raise Exception("Fail")

@classmethod
def from_dict(cls, data: dict[str, Any]):
if isinstance(data, dict):
ignore_keys = cls._ignore_keys
try:
return from_dict(cls, decamelize_obj(data, ignore_keys), config=Config(cast=[Enum]))
except AttributeError as err:
raise RoborockException("It seems like you have an outdated version of dacite.") from err
data = decamelize_obj(data, ignore_keys)
cls_annotations: dict[str, str] = {}
for base in reversed(cls.__mro__):
cls_annotations.update(getattr(base, "__annotations__", {}))
remove_keys = []
for key, value in data.items():
if value == "None" or value is None:
data[key] = None
continue
if key not in cls_annotations:
remove_keys.append(key)
continue
field_type: str = cls_annotations[key]
if "|" in field_type:
# It's a union
types = field_type.split("|")
for type in types:
if "None" in type or "Any" in type:
continue
try:
data[key] = RoborockBase.convert_to_class_obj(type, value)
break
except Exception:
...
else:
try:
data[key] = RoborockBase.convert_to_class_obj(field_type, value)
except Exception:
...
for key in remove_keys:
del data[key]
return cls(**data)

def as_dict(self) -> dict:
return asdict(
Expand Down Expand Up @@ -188,6 +242,7 @@ class HomeDataProductSchema(RoborockBase):
mode: Any | None = None
type: Any | None = None
product_property: Any | None = None
property: Any | None = None
desc: Any | None = None


Expand All @@ -198,7 +253,7 @@ class HomeDataProduct(RoborockBase):
model: str
category: RoborockCategory
code: str | None = None
iconurl: str | None = None
icon_url: str | None = None
attribute: Any | None = None
capability: int | None = None
schema: list[HomeDataProductSchema] | None = None
Expand Down Expand Up @@ -515,7 +570,7 @@ class S5MaxStatus(Status):
@dataclass
class Q7MaxStatus(Status):
fan_power: RoborockFanSpeedQ7Max | None = None
water_box_mode: RoborockMopIntensityV2 | None = None
water_box_mode: RoborockMopIntensityQ7Max | None = None


@dataclass
Expand Down Expand Up @@ -622,7 +677,7 @@ class CleanSummary(RoborockBase):
last_clean_t: int | None = None

def __post_init__(self) -> None:
if isinstance(self.clean_area, list):
if isinstance(self.clean_area, list | str):
_LOGGER.warning(f"Clean area is a unexpected type! Please give the following in a issue: {self.clean_area}")
else:
self.square_meter_clean_area = round(self.clean_area / 1000000, 1) if self.clean_area is not None else None
Expand Down
6 changes: 3 additions & 3 deletions roborock/roborock_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import math
import time
from dataclasses import dataclass
from random import randint

from roborock import RoborockEnum
from roborock.util import get_next_int


class RoborockMessageProtocol(RoborockEnum):
Expand Down Expand Up @@ -155,9 +155,9 @@ class MessageRetry:
class RoborockMessage:
protocol: RoborockMessageProtocol
payload: bytes | None = None
seq: int = randint(100000, 999999)
seq: int = get_next_int(100000, 999999)
version: bytes = b"1.0"
random: int = randint(10000, 99999)
random: int = get_next_int(10000, 99999)
timestamp: int = math.floor(time.time())
message_retry: MessageRetry | None = None

Expand Down
12 changes: 12 additions & 0 deletions roborock/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,15 @@ def __init__(self, prefix: str, logger: logging.Logger) -> None:

def process(self, msg: str, kwargs: MutableMapping[str, Any]) -> tuple[str, MutableMapping[str, Any]]:
return f"[{self.prefix}] {msg}", kwargs


counter_map: dict[tuple[int, int], int] = {}


def get_next_int(min_val: int, max_val: int):
"""Gets a random int in the range, precached to help keep it fast."""
if (min_val, max_val) not in counter_map:
# If we have never seen this range, or if the cache is getting low, make a bunch of preshuffled values.
counter_map[(min_val, max_val)] = min_val
counter_map[(min_val, max_val)] += 1
return counter_map[(min_val, max_val)] % max_val + min_val
5 changes: 2 additions & 3 deletions roborock/version_1_apis/roborock_client_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import struct
import time
from collections.abc import Callable, Coroutine
from random import randint
from typing import Any, TypeVar, final

from roborock import (
Expand Down Expand Up @@ -54,7 +53,7 @@
RoborockMessage,
RoborockMessageProtocol,
)
from roborock.util import RepeatableTask, unpack_list
from roborock.util import RepeatableTask, get_next_int, unpack_list

COMMANDS_SECURED = {
RoborockCommand.GET_MAP_V1,
Expand Down Expand Up @@ -338,7 +337,7 @@ def _get_payload(
secured=False,
):
timestamp = math.floor(time.time())
request_id = randint(10000, 32767)
request_id = get_next_int(10000, 32767)
inner = {
"id": request_id,
"method": method,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_home_data():
assert product.name == "Roborock S7 MaxV"
assert product.code == "a27"
assert product.model == "roborock.vacuum.a27"
assert product.iconurl is None
assert product.icon_url is None
assert product.attribute is None
assert product.capability == 0
assert product.category == RoborockCategory.VACUUM
Expand Down

0 comments on commit d80dd03

Please sign in to comment.