diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6b818fe9..2c16942c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,82 +1,82 @@
name: CI
on:
- push:
- branches:
- - main
- pull_request:
+ push:
+ branches:
+ - main
+ pull_request:
concurrency:
- group: ${{ github.head_ref || github.run_id }}
- cancel-in-progress: true
+ group: ${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
jobs:
- # Make sure commit messages follow the conventional commits convention:
- # https://www.conventionalcommits.org
- commitlint:
- name: Lint Commit Messages
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - uses: wagoid/commitlint-github-action@v5.3.0
- lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
- with:
- python-version: "3.10"
- - uses: pre-commit/action@v3.0.0
+ # Make sure commit messages follow the conventional commits convention:
+ # https://www.conventionalcommits.org
+ commitlint:
+ name: Lint Commit Messages
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: wagoid/commitlint-github-action@v5.3.0
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - uses: pre-commit/action@v3.0.0
- test:
- strategy:
- fail-fast: false
- matrix:
- python-version:
- - "3.10"
- - "3.11"
- os:
- - ubuntu-latest
- - windows-latest
- - macOS-latest
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
- - uses: snok/install-poetry@v1.3.3
- - name: Install Dependencies
- run: poetry install
- shell: bash
- - name: Test with Pytest
- run: poetry run pytest
- shell: bash
- release:
- runs-on: ubuntu-latest
- environment: release
- if: github.ref == 'refs/heads/main'
- needs:
- - test
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version:
+ - "3.10"
+ - "3.11"
+ os:
+ - ubuntu-latest
+ - windows-latest
+ - macOS-latest
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - uses: snok/install-poetry@v1.3.3
+ - name: Install Dependencies
+ run: poetry install
+ shell: bash
+ - name: Test with Pytest
+ run: poetry run pytest
+ shell: bash
+ release:
+ runs-on: ubuntu-latest
+ environment: release
+ if: github.ref == 'refs/heads/main'
+ needs:
+ - test
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- persist-credentials: false
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ persist-credentials: false
- # Run semantic release:
- # - Update CHANGELOG.md
- # - Update version in code
- # - Create git tag
- # - Create GitHub release
- # - Publish to PyPI
- - name: Python Semantic Release
- uses: relekang/python-semantic-release@v7.33.2
- with:
- github_token: ${{ secrets.GH_TOKEN }}
- repository_username: __token__
- repository_password: ${{ secrets.PYPI_TOKEN }}
+ # Run semantic release:
+ # - Update CHANGELOG.md
+ # - Update version in code
+ # - Create git tag
+ # - Create GitHub release
+ # - Publish to PyPI
+ - name: Python Semantic Release
+ uses: relekang/python-semantic-release@v7.33.2
+ with:
+ github_token: ${{ secrets.GH_TOKEN }}
+ repository_username: __token__
+ repository_password: ${{ secrets.PYPI_TOKEN }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f2ac1ee2..e17110f9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,12 +2,42 @@
# See https://pre-commit.com/hooks.html for more hooks
default_stages: [ commit ]
-
repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: debug-statements
+ - id: check-builtin-literals
+ - id: check-case-conflict
+ - id: check-docstring-first
+ - id: check-json
+ - id: check-toml
+ - id: check-yaml
+ - id: detect-private-key
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
- repo: https://github.com/python-poetry/poetry
rev: 1.3.2
hooks:
- id: poetry-check
+ - repo: https://github.com/PyCQA/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+ - repo: https://github.com/psf/black
+ rev: 22.12.0
+ hooks:
+ - id: black
+ - repo: https://github.com/codespell-project/codespell
+ rev: v2.2.2
+ hooks:
+ - id: codespell
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
+ rev: v0.0.260
+ hooks:
+ - id: ruff
+ args:
+ - --fix
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.931
hooks:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a490b85f..92e9accd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,22 +3,30 @@
## v0.6.4 (2023-04-11)
+
### Fix
-* Disconnect on timeout so next command can work ([`5ad397b`](https://github.com/humbertogontijo/python-roborock/commit/5ad397b3bbb4bc600888baba6c0cc15be9d17ef7))
+
+- Disconnect on timeout so next command can work ([`5ad397b`](https://github.com/humbertogontijo/python-roborock/commit/5ad397b3bbb4bc600888baba6c0cc15be9d17ef7))
## v0.6.3 (2023-04-11)
+
### Fix
-* Semantic_release ([`63b249d`](https://github.com/humbertogontijo/python-roborock/commit/63b249d65d3fc40b048320e6596aedc40f588bf9))
+
+- Semantic_release ([`63b249d`](https://github.com/humbertogontijo/python-roborock/commit/63b249d65d3fc40b048320e6596aedc40f588bf9))
## v0.6.2 (2023-04-11)
+
### Fix
-* Error code nogo_zone_detected ([`722e4b5`](https://github.com/humbertogontijo/python-roborock/commit/722e4b5cfd0c4891adc506e9fe99740860027670))
+
+- Error code nogo_zone_detected ([`722e4b5`](https://github.com/humbertogontijo/python-roborock/commit/722e4b5cfd0c4891adc506e9fe99740860027670))
## v0.6.1 (2023-04-10)
+
### Fix
-* Trigger release ([`f1ce0ed`](https://github.com/humbertogontijo/python-roborock/commit/f1ce0ed55a254bccd8567b48974ff74dd9ec8b25))
-* Trigger release ([`9a4462c`](https://github.com/humbertogontijo/python-roborock/commit/9a4462c800762393cc047085156acbe119cd0fe4))
-* Trigger release ([`b7a664b`](https://github.com/humbertogontijo/python-roborock/commit/b7a664b15b7c5180d816de325537693f47c24860))
-* Trigger release ([`9256849`](https://github.com/humbertogontijo/python-roborock/commit/9256849252f019f4fea2f59384bc0ea7c57adb5c))
-* Lowercase true ([`774c3cc`](https://github.com/humbertogontijo/python-roborock/commit/774c3cc9765ee76a3a553ca6911751124ae7164c))
-* Semantic release not updating changelong ([`eaf6e90`](https://github.com/humbertogontijo/python-roborock/commit/eaf6e90264b6ab69549da0e5bc3d17c4c0a2c07c))
+
+- Trigger release ([`f1ce0ed`](https://github.com/humbertogontijo/python-roborock/commit/f1ce0ed55a254bccd8567b48974ff74dd9ec8b25))
+- Trigger release ([`9a4462c`](https://github.com/humbertogontijo/python-roborock/commit/9a4462c800762393cc047085156acbe119cd0fe4))
+- Trigger release ([`b7a664b`](https://github.com/humbertogontijo/python-roborock/commit/b7a664b15b7c5180d816de325537693f47c24860))
+- Trigger release ([`9256849`](https://github.com/humbertogontijo/python-roborock/commit/9256849252f019f4fea2f59384bc0ea7c57adb5c))
+- Lowercase true ([`774c3cc`](https://github.com/humbertogontijo/python-roborock/commit/774c3cc9765ee76a3a553ca6911751124ae7164c))
+- Semantic release not updating changelong ([`eaf6e90`](https://github.com/humbertogontijo/python-roborock/commit/eaf6e90264b6ab69549da0e5bc3d17c4c0a2c07c))
diff --git a/README.md b/README.md
index 898a80a0..bcbb223b 100644
--- a/README.md
+++ b/README.md
@@ -1,90 +1,86 @@
-# Roborock
-
-
-
-
-
-
-
-
-
-Roborock library for online and offline control of your vacuums.
-
-## Installation
-
-Install this via pip (or your favourite package manager):
-
-`pip install python-roborock`
-
-## Functionality
-
-This package can encrypt and decrypt the following commands:
-
-- GET_CLEAN_RECORD
-- GET_CONSUMABLE
-- GET_MULTI_MAPS_LIST
-- APP_START
-- APP_PAUSE
-- APP_STOP
-- APP_CHARGE
-- APP_SPOT
-- FIND_ME
-- RESUME_ZONED_CLEAN
-- RESUME_SEGMENT_CLEAN
-- SET_CUSTOM_MODE
-- SET_MOP_MODE
-- SET_WATER_BOX_CUSTOM_MODE
-- RESET_CONSUMABLE
-- LOAD_MULTI_MAP
-- APP_RC_START
-- APP_RC_END
-- APP_RC_MOVE
-- APP_GOTO_TARGET
-- APP_SEGMENT_CLEAN
-- APP_ZONED_CLEAN
-- APP_GET_DRYER_SETTING
-- APP_SET_DRYER_SETTING
-- APP_START_WASH
-- APP_STOP_WASH
-- GET_DUST_COLLECTION_MODE
-- SET_DUST_COLLECTION_MODE
-- GET_SMART_WASH_PARAMS
-- SET_SMART_WASH_PARAMS
-- GET_WASH_TOWEL_MODE
-- SET_WASH_TOWEL_MODE
-- SET_CHILD_LOCK_STATUS
-- GET_CHILD_LOCK_STATUS
-- START_WASH_THEN_CHARGE
-- GET_CURRENT_SOUND
-- GET_SERIAL_NUMBER
-- GET_TIMEZONE
-- GET_SERVER_TIMER
-- GET_CUSTOMIZE_CLEAN_MODE
-- GET_CLEAN_SEQUENCE
-- SET_FDS_ENDPOINT
-- ENABLE_LOG_UPLOAD
-- APP_WAKEUP_ROBOT
-- GET_LED_STATUS
-- GET_FLOW_LED_STATUS
-- SET_FLOW_LED_STATUS
-- GET_SOUND_PROGRESS
-- GET_SOUND_VOLUME
-- TEST_SOUND_VOLUME
-- CHANGE_SOUND_VOLUME
-- GET_CARPET_MODE
-- SET_CARPET_MODE
-- GET_CARPET_CLEAN_MODE
-- SET_CARPET_CLEAN_MODE
-- UPD_SERVER_TIMER
-- SET_SERVER_TIMER
-- APP_GET_INIT_STATUS
-- SET_APP_TIMEZONE
-- GET_NETWORK_INFO
-
-
-
-## Credits
-
-Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor
-
-
+# Roborock
+
+
+
+
+
+
+
+
+
+Roborock library for online and offline control of your vacuums.
+
+## Installation
+
+Install this via pip (or your favourite package manager):
+
+`pip install python-roborock`
+
+## Functionality
+
+This package can encrypt and decrypt the following commands:
+
+- GET_CLEAN_RECORD
+- GET_CONSUMABLE
+- GET_MULTI_MAPS_LIST
+- APP_START
+- APP_PAUSE
+- APP_STOP
+- APP_CHARGE
+- APP_SPOT
+- FIND_ME
+- RESUME_ZONED_CLEAN
+- RESUME_SEGMENT_CLEAN
+- SET_CUSTOM_MODE
+- SET_MOP_MODE
+- SET_WATER_BOX_CUSTOM_MODE
+- RESET_CONSUMABLE
+- LOAD_MULTI_MAP
+- APP_RC_START
+- APP_RC_END
+- APP_RC_MOVE
+- APP_GOTO_TARGET
+- APP_SEGMENT_CLEAN
+- APP_ZONED_CLEAN
+- APP_GET_DRYER_SETTING
+- APP_SET_DRYER_SETTING
+- APP_START_WASH
+- APP_STOP_WASH
+- GET_DUST_COLLECTION_MODE
+- SET_DUST_COLLECTION_MODE
+- GET_SMART_WASH_PARAMS
+- SET_SMART_WASH_PARAMS
+- GET_WASH_TOWEL_MODE
+- SET_WASH_TOWEL_MODE
+- SET_CHILD_LOCK_STATUS
+- GET_CHILD_LOCK_STATUS
+- START_WASH_THEN_CHARGE
+- GET_CURRENT_SOUND
+- GET_SERIAL_NUMBER
+- GET_TIMEZONE
+- GET_SERVER_TIMER
+- GET_CUSTOMIZE_CLEAN_MODE
+- GET_CLEAN_SEQUENCE
+- SET_FDS_ENDPOINT
+- ENABLE_LOG_UPLOAD
+- APP_WAKEUP_ROBOT
+- GET_LED_STATUS
+- GET_FLOW_LED_STATUS
+- SET_FLOW_LED_STATUS
+- GET_SOUND_PROGRESS
+- GET_SOUND_VOLUME
+- TEST_SOUND_VOLUME
+- CHANGE_SOUND_VOLUME
+- GET_CARPET_MODE
+- SET_CARPET_MODE
+- GET_CARPET_CLEAN_MODE
+- SET_CARPET_CLEAN_MODE
+- UPD_SERVER_TIMER
+- SET_SERVER_TIMER
+- APP_GET_INIT_STATUS
+- SET_APP_TIMEZONE
+- GET_NETWORK_INFO
+
+## Credits
+
+Thanks @rovo89 for https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7 And thanks @PiotrMachowski for https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor
diff --git a/pyproject.toml b/pyproject.toml
index 3ef77950..9bb9cdc9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,8 +38,23 @@ pytest-asyncio = "*"
pytest = "*"
pre-commit = "*"
mypy = "*"
+ruff = "*"
+isort = "*"
+black = "*"
+codespell = "*"
[tool.semantic_release]
branch = "main"
version_toml = "pyproject.toml:tool.poetry.version"
build_command = "pip install poetry && poetry build"
+
+[tool.ruff]
+ignore = ["F403", "E741"]
+line-length = 120
+
+[tool.black]
+line-length = 120
+
+[tool.isort]
+profile = "black"
+line_length = 120
diff --git a/roborock/__init__.py b/roborock/__init__.py
index babf188c..dbbfbb69 100644
--- a/roborock/__init__.py
+++ b/roborock/__init__.py
@@ -1,9 +1,6 @@
"""Roborock API."""
-from roborock.api import RoborockApiClient
-from roborock.cloud_api import RoborockMqttClient
-from roborock.local_api import RoborockLocalClient
+from roborock.code_mappings import *
from roborock.containers import *
from roborock.exceptions import *
from roborock.typing import *
-from roborock.code_mappings import *
diff --git a/roborock/api.py b/roborock/api.py
index 9e4c7b00..d31fc964 100644
--- a/roborock/api.py
+++ b/roborock/api.py
@@ -14,39 +14,33 @@
import struct
import time
from random import randint
-from typing import Optional, Any, Callable, Coroutine, Mapping
+from typing import Any, Callable, Coroutine, Mapping, Optional
import aiohttp
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
-from roborock.exceptions import (
- RoborockException, RoborockTimeout, VacuumError,
-)
+from roborock.exceptions import RoborockException, RoborockTimeout, VacuumError
+
from .code_mappings import RoborockDockTypeCode, RoborockEnum
from .containers import (
- UserData,
- Status,
+ CleanRecord,
CleanSummary,
Consumable,
DNDTimer,
- CleanRecord,
+ DustCollectionMode,
HomeData,
MultiMapsList,
- SmartWashParams,
+ NetworkInfo,
RoborockDeviceInfo,
+ SmartWashParams,
+ Status,
+ UserData,
WashTowelMode,
- DustCollectionMode,
- NetworkInfo,
-
)
from .roborock_message import RoborockMessage
from .roborock_queue import RoborockQueue
-from .typing import (
- RoborockDeviceProp,
- RoborockCommand,
- RoborockDockSummary
-)
+from .typing import RoborockCommand, RoborockDeviceProp, RoborockDockSummary
_LOGGER = logging.getLogger(__name__)
QUEUE_TIMEOUT = 4
@@ -67,9 +61,7 @@ def __init__(self, base_url: str, base_headers: Optional[dict] = None) -> None:
self.base_url = base_url
self.base_headers = base_headers or {}
- async def request(
- self, method: str, url: str, params=None, data=None, headers=None
- ) -> dict:
+ async def request(self, method: str, url: str, params=None, data=None, headers=None) -> dict:
_url = "/".join(s.strip("/") for s in [self.base_url, url])
_headers = {**self.base_headers, **(headers or {})}
async with aiohttp.ClientSession() as session:
@@ -84,7 +76,6 @@ async def request(
class RoborockClient:
-
def __init__(self, endpoint: str, devices_info: Mapping[str, RoborockDeviceInfo]) -> None:
self.devices_info = devices_info
self._endpoint = endpoint
@@ -117,7 +108,8 @@ async def on_message(self, messages: list[RoborockMessage]) -> None:
(
None,
VacuumError(
- error.get("code"), error.get("message")
+ error.get("code"),
+ error.get("message"),
),
),
timeout=QUEUE_TIMEOUT,
@@ -126,18 +118,14 @@ async def on_message(self, messages: list[RoborockMessage]) -> None:
result = data_point_response.get("result")
if isinstance(result, list) and len(result) == 1:
result = result[0]
- await queue.async_put(
- (result, None), timeout=QUEUE_TIMEOUT
- )
+ await queue.async_put((result, None), timeout=QUEUE_TIMEOUT)
elif protocol == 301:
payload = data.payload[0:24]
[endpoint, _, request_id, _] = struct.unpack("<15sBH6s", payload)
if endpoint.decode().startswith(self._endpoint):
iv = bytes(AES.block_size)
decipher = AES.new(self._nonce, AES.MODE_CBC, iv)
- decrypted = unpad(
- decipher.decrypt(data.payload[24:]), AES.block_size
- )
+ decrypted = unpad(decipher.decrypt(data.payload[24:]), AES.block_size)
decrypted = gzip.decompress(decrypted)
queue = self._waiting_queue.get(request_id)
if queue:
@@ -154,15 +142,11 @@ async def _async_response(self, request_id: int, protocol_id: int = 0) -> tuple[
(response, err) = await queue.async_get(QUEUE_TIMEOUT)
return response, err
except (asyncio.TimeoutError, asyncio.CancelledError):
- raise RoborockTimeout(
- f"Timeout after {QUEUE_TIMEOUT} seconds waiting for response"
- ) from None
+ raise RoborockTimeout(f"Timeout after {QUEUE_TIMEOUT} seconds waiting for response") from None
finally:
del self._waiting_queue[request_id]
- def _get_payload(
- self, method: RoborockCommand, params: Optional[list] = None, secured=False
- ):
+ def _get_payload(self, method: RoborockCommand, params: Optional[list] = None, secured=False):
timestamp = math.floor(time.time())
request_id = randint(10000, 99999)
inner = {
@@ -186,9 +170,7 @@ def _get_payload(
)
return request_id, timestamp, payload
- async def send_command(
- self, device_id: str, method: RoborockCommand, params: Optional[list] = None
- ):
+ async def send_command(self, device_id: str, method: RoborockCommand, params: Optional[list] = None):
raise NotImplementedError
async def get_status(self, device_id: str) -> Status | None:
@@ -208,22 +190,18 @@ async def get_dnd_timer(self, device_id: str) -> DNDTimer | None:
async def get_clean_summary(self, device_id: str) -> CleanSummary | None:
try:
- clean_summary = await self.send_command(
- device_id, RoborockCommand.GET_CLEAN_SUMMARY
- )
+ clean_summary = await self.send_command(device_id, RoborockCommand.GET_CLEAN_SUMMARY)
if isinstance(clean_summary, dict):
return CleanSummary.from_dict(clean_summary)
elif isinstance(clean_summary, bytes):
- return CleanSummary(clean_time=int.from_bytes(clean_summary, 'big'))
+ return CleanSummary(clean_time=int.from_bytes(clean_summary, "big"))
except RoborockTimeout as e:
_LOGGER.error(e)
return None
async def get_clean_record(self, device_id: str, record_id: int) -> CleanRecord | None:
try:
- clean_record = await self.send_command(
- device_id, RoborockCommand.GET_CLEAN_RECORD, [record_id]
- )
+ clean_record = await self.send_command(device_id, RoborockCommand.GET_CLEAN_RECORD, [record_id])
if isinstance(clean_record, dict):
return CleanRecord.from_dict(clean_record)
except RoborockTimeout as e:
@@ -273,18 +251,21 @@ async def get_dock_summary(self, device_id: str, dock_type: RoborockEnum) -> Rob
if RoborockDockTypeCode.name != "RoborockDockTypeCode":
raise RoborockException("Invalid enum given for dock type")
try:
- commands: list[Coroutine[Any, Any, DustCollectionMode | WashTowelMode | SmartWashParams | None]] = [
- self.get_dust_collection_mode(device_id)]
- if dock_type == RoborockDockTypeCode['3']:
- commands += [self.get_wash_towel_mode(device_id), self.get_smart_wash_params(device_id)]
- [
- dust_collection_mode,
- wash_towel_mode,
- smart_wash_params
- ] = (
- list(await asyncio.gather(*commands))
- + [None, None]
- )[:3]
+ commands: list[
+ Coroutine[
+ Any,
+ Any,
+ DustCollectionMode | WashTowelMode | SmartWashParams | None,
+ ]
+ ] = [self.get_dust_collection_mode(device_id)]
+ if dock_type == RoborockDockTypeCode["3"]:
+ commands += [
+ self.get_wash_towel_mode(device_id),
+ self.get_smart_wash_params(device_id),
+ ]
+ [dust_collection_mode, wash_towel_mode, smart_wash_params] = (
+ list(await asyncio.gather(*commands)) + [None, None]
+ )[:3]
return RoborockDockSummary(dust_collection_mode, wash_towel_mode, smart_wash_params)
except RoborockTimeout as e:
@@ -302,23 +283,24 @@ async def get_prop(self, device_id: str) -> RoborockDeviceProp | None:
)
last_clean_record = None
if clean_summary and clean_summary.records and len(clean_summary.records) > 0:
- last_clean_record = await self.get_clean_record(
- device_id, clean_summary.records[0]
- )
+ last_clean_record = await self.get_clean_record(device_id, clean_summary.records[0])
dock_summary = None
- if status and status.dock_type != RoborockDockTypeCode['0']:
+ if status and status.dock_type != RoborockDockTypeCode["0"]:
dock_summary = await self.get_dock_summary(device_id, status.dock_type)
if any([status, dnd_timer, clean_summary, consumable]):
return RoborockDeviceProp(
- status, dnd_timer, clean_summary, consumable, last_clean_record, dock_summary
+ status,
+ dnd_timer,
+ clean_summary,
+ consumable,
+ last_clean_record,
+ dock_summary,
)
return None
async def get_multi_maps_list(self, device_id) -> MultiMapsList | None:
try:
- multi_maps_list = await self.send_command(
- device_id, RoborockCommand.GET_MULTI_MAPS_LIST
- )
+ multi_maps_list = await self.send_command(device_id, RoborockCommand.GET_MULTI_MAPS_LIST)
if isinstance(multi_maps_list, dict):
return MultiMapsList.from_dict(multi_maps_list)
except RoborockTimeout as e:
@@ -437,9 +419,7 @@ async def get_home_data(self, user_data: UserData) -> HomeData:
rriot = user_data.rriot
if rriot is None:
raise RoborockException("rriot is none")
- home_id_request = PreparedRequest(
- base_url, {"header_clientid": header_clientid}
- )
+ home_id_request = PreparedRequest(base_url, {"header_clientid": header_clientid})
home_id_response = await home_id_request.request(
"get",
"/api/v1/getHomeDetail",
@@ -450,7 +430,7 @@ async def get_home_data(self, user_data: UserData) -> HomeData:
if home_id_response.get("code") != 200:
raise RoborockException(home_id_response.get("msg"))
- home_id = home_id_response['data'].get("rrHomeId")
+ home_id = home_id_response["data"].get("rrHomeId")
timestamp = math.floor(time.time())
nonce = secrets.token_urlsafe(6)
prestr = ":".join(
@@ -464,16 +444,14 @@ async def get_home_data(self, user_data: UserData) -> HomeData:
"",
]
)
- mac = base64.b64encode(
- hmac.new(rriot.h.encode(), prestr.encode(), hashlib.sha256).digest()
- ).decode()
+ mac = base64.b64encode(hmac.new(rriot.h.encode(), prestr.encode(), hashlib.sha256).digest()).decode()
if rriot.r.a is None:
raise RoborockException("Missing field 'a' in rriot reference")
home_request = PreparedRequest(
rriot.r.a,
{
"Authorization": f'Hawk id="{rriot.u}", s="{rriot.s}", ts="{timestamp}", nonce="{nonce}", '
- f'mac="{mac}"',
+ f'mac="{mac}"',
},
)
home_response = await home_request.request("get", "/user/homes/" + str(home_id))
diff --git a/roborock/cli.py b/roborock/cli.py
index bd1b9ccc..2727a67d 100644
--- a/roborock/cli.py
+++ b/roborock/cli.py
@@ -25,14 +25,14 @@ def __init__(self):
def reload(self):
if self.roborock_file.is_file():
- with open(self.roborock_file, 'r') as f:
+ with open(self.roborock_file, "r") as f:
data = json.load(f)
if data:
self._login_data = LoginData.from_dict(data)
def update(self, login_data: LoginData):
data = json.dumps(login_data.as_dict(), default=vars)
- with open(self.roborock_file, 'w') as f:
+ with open(self.roborock_file, "w") as f:
f.write(data)
self.reload()
@@ -50,9 +50,7 @@ def login_data(self):
@click.group()
@click.pass_context
def cli(ctx, debug: int):
- logging_config: Dict[str, Any] = {
- "level": logging.DEBUG if debug > 0 else logging.INFO
- }
+ logging_config: Dict[str, Any] = {"level": logging.DEBUG if debug > 0 else logging.INFO}
logging.basicConfig(**logging_config) # type: ignore
ctx.obj = RoborockContext()
@@ -83,7 +81,8 @@ async def _discover(ctx):
home_data = await client.get_home_data(login_data.user_data)
context.update(LoginData({**login_data, "home_data": home_data}))
click.echo(
- f"Discovered devices {', '.join([device.name for device in home_data.devices + home_data.received_devices])}")
+ f"Discovered devices {', '.join([device.name for device in home_data.devices + home_data.received_devices])}"
+ )
@click.command()
diff --git a/roborock/cloud_api.py b/roborock/cloud_api.py
index 69a44beb..6ee074cb 100644
--- a/roborock/cloud_api.py
+++ b/roborock/cloud_api.py
@@ -5,22 +5,16 @@
import threading
import uuid
from asyncio import Lock
-from typing import Optional, Any, Mapping
+from typing import Any, Mapping, Optional
from urllib.parse import urlparse
import paho.mqtt.client as mqtt
-from roborock.api import md5hex, RoborockClient, SPECIAL_COMMANDS
-from roborock.exceptions import (
- RoborockException,
- CommandVacuumError,
- VacuumError,
-)
-from .containers import (
- UserData,
- RoborockDeviceInfo,
-)
-from .roborock_message import RoborockParser, md5bin, RoborockMessage
+from roborock.api import SPECIAL_COMMANDS, RoborockClient, md5hex
+from roborock.exceptions import CommandVacuumError, RoborockException, VacuumError
+
+from .containers import RoborockDeviceInfo, UserData
+from .roborock_message import RoborockMessage, RoborockParser, md5bin
from .roborock_queue import RoborockQueue
from .typing import RoborockCommand
from .util import run_in_executor
@@ -70,9 +64,7 @@ async def on_connect(self, _client, _, __, rc, ___=None) -> None:
message = f"Failed to connect (rc: {rc})"
_LOGGER.error(message)
if connection_queue:
- await connection_queue.async_put(
- (None, VacuumError(rc, message)), timeout=QUEUE_TIMEOUT
- )
+ await connection_queue.async_put((None, VacuumError(rc, message)), timeout=QUEUE_TIMEOUT)
return
_LOGGER.info(f"Connected to mqtt {self._mqtt_host}:{self._mqtt_port}")
topic = f"rr/m/o/{self._mqtt_user}/{self._hashed_user}/#"
@@ -81,9 +73,7 @@ async def on_connect(self, _client, _, __, rc, ___=None) -> None:
message = f"Failed to subscribe (rc: {result})"
_LOGGER.error(message)
if connection_queue:
- await connection_queue.async_put(
- (None, VacuumError(rc, message)), timeout=QUEUE_TIMEOUT
- )
+ await connection_queue.async_put((None, VacuumError(rc, message)), timeout=QUEUE_TIMEOUT)
return
_LOGGER.info(f"Subscribed to topic {topic}")
if connection_queue:
@@ -107,9 +97,7 @@ async def on_disconnect(self, _client: mqtt.Client, _, rc, __=None) -> None:
_LOGGER.warning(message)
connection_queue = self._waiting_queue.get(1)
if connection_queue:
- await connection_queue.async_put(
- (True, None), timeout=QUEUE_TIMEOUT
- )
+ await connection_queue.async_put((True, None), timeout=QUEUE_TIMEOUT)
except Exception as ex:
_LOGGER.exception(ex)
@@ -121,8 +109,10 @@ async def _async_check_keepalive(self) -> None:
async with self._mutex:
now = mqtt.time_func()
# noinspection PyUnresolvedReferences
- if now - self._last_disconnection > self._keepalive ** 2 and now - self._last_device_msg_in > self._keepalive: # type: ignore[attr-defined]
-
+ if (
+ now - self._last_disconnection > self._keepalive**2 # type: ignore[attr-defined]
+ and now - self._last_device_msg_in > self._keepalive # type: ignore[attr-defined]
+ ):
self._ping_t = self._last_device_msg_in
def _check_keepalive(self) -> None:
@@ -146,7 +136,7 @@ def sync_disconnect(self) -> bool:
if self.is_connected():
_LOGGER.info("Disconnecting from mqtt")
rc = super().disconnect()
- if not rc in [mqtt.MQTT_ERR_SUCCESS, mqtt.MQTT_ERR_NO_CONN]:
+ if rc not in [mqtt.MQTT_ERR_SUCCESS, mqtt.MQTT_ERR_NO_CONN]:
raise RoborockException(f"Failed to disconnect (rc:{rc})")
return rc == mqtt.MQTT_ERR_SUCCESS
@@ -157,11 +147,7 @@ def sync_connect(self) -> bool:
if self._mqtt_port is None or self._mqtt_host is None:
raise RoborockException("Mqtt information was not entered. Cannot connect.")
_LOGGER.info("Connecting to mqtt")
- rc = super().connect(
- host=self._mqtt_host,
- port=self._mqtt_port,
- keepalive=MQTT_KEEPALIVE
- )
+ rc = super().connect(host=self._mqtt_host, port=self._mqtt_port, keepalive=MQTT_KEEPALIVE)
if rc != mqtt.MQTT_ERR_SUCCESS:
raise RoborockException(f"Failed to connect (rc:{rc})")
return rc == mqtt.MQTT_ERR_SUCCESS
@@ -188,25 +174,17 @@ async def validate_connection(self) -> None:
await self.async_connect()
def _send_msg_raw(self, device_id, msg) -> None:
- info = self.publish(
- f"rr/m/i/{self._mqtt_user}/{self._hashed_user}/{device_id}", msg
- )
+ info = self.publish(f"rr/m/i/{self._mqtt_user}/{self._hashed_user}/{device_id}", msg)
if info.rc != mqtt.MQTT_ERR_SUCCESS:
raise RoborockException(f"Failed to publish (rc: {info.rc})")
- async def send_command(
- self, device_id: str, method: RoborockCommand, params: Optional[list] = None
- ):
+ async def send_command(self, device_id: str, method: RoborockCommand, params: Optional[list] = None):
await self.validate_connection()
request_id, timestamp, payload = super()._get_payload(method, params, True)
_LOGGER.debug(f"id={request_id} Requesting method {method} with {params}")
request_protocol = 101
response_protocol = 301 if method in SPECIAL_COMMANDS else 102
- roborock_message = RoborockMessage(
- timestamp=timestamp,
- protocol=request_protocol,
- payload=payload
- )
+ roborock_message = RoborockMessage(timestamp=timestamp, protocol=request_protocol, payload=payload)
local_key = self.devices_info[device_id].device.local_key
msg = RoborockParser.encode(roborock_message, local_key)
self._send_msg_raw(device_id, msg)
@@ -214,9 +192,7 @@ async def send_command(
if err:
raise CommandVacuumError(method, err) from err
if response_protocol == 301:
- _LOGGER.debug(
- f"id={request_id} Response from {method}: {len(response)} bytes"
- )
+ _LOGGER.debug(f"id={request_id} Response from {method}: {len(response)} bytes")
else:
_LOGGER.debug(f"id={request_id} Response from {method}: {response}")
return response
diff --git a/roborock/code_mappings.py b/roborock/code_mappings.py
index 61c5646f..36fa706b 100644
--- a/roborock/code_mappings.py
+++ b/roborock/code_mappings.py
@@ -1,16 +1,13 @@
from __future__ import annotations
from enum import Enum
-from typing import TypeVar, Type, Any
+from typing import Any, Type, TypeVar
_StrEnumT = TypeVar("_StrEnumT", bound="RoborockEnum")
class RoborockEnum(str, Enum):
-
- def __new__(
- cls: Type[_StrEnumT], value: str, *args: Any, **kwargs: Any
- ) -> _StrEnumT:
+ def __new__(cls: Type[_StrEnumT], value: str, *args: Any, **kwargs: Any) -> _StrEnumT:
"""Create a new StrEnum instance."""
if not isinstance(value, str):
raise TypeError(f"{value!r} is not a string")
@@ -74,7 +71,8 @@ def create_code_enum(name: str, data: dict) -> RoborockEnum:
26: "going_to_wash_the_mop", # on a46, #1435
100: "charging_complete",
101: "device_offline",
- })
+ },
+)
RoborockErrorCode = create_code_enum(
"RoborockErrorCode",
diff --git a/roborock/containers.py b/roborock/containers.py
index dc0ae18d..fa1fe453 100644
--- a/roborock/containers.py
+++ b/roborock/containers.py
@@ -1,25 +1,35 @@
from __future__ import annotations
import re
-from dataclasses import dataclass, asdict
+from dataclasses import asdict, dataclass
from enum import Enum
from typing import Any, Optional
-from dacite import from_dict, Config
+from dacite import Config, from_dict
-from roborock.code_mappings import RoborockDockWashTowelModeCode, RoborockDockTypeCode, RoborockMopIntensityCode, \
- RoborockStateCode
-from .code_mappings import RoborockMopModeCode, RoborockDockErrorCode, \
- RoborockErrorCode, RoborockDockDustCollectionModeCode, RoborockFanPowerCode
+from roborock.code_mappings import (
+ RoborockDockTypeCode,
+ RoborockDockWashTowelModeCode,
+ RoborockMopIntensityCode,
+ RoborockStateCode,
+)
+
+from .code_mappings import (
+ RoborockDockDustCollectionModeCode,
+ RoborockDockErrorCode,
+ RoborockErrorCode,
+ RoborockFanPowerCode,
+ RoborockMopModeCode,
+)
def camelize(s: str):
- first, *others = s.split('_')
- return ''.join([first.lower(), *map(str.title, others)])
+ first, *others = s.split("_")
+ return "".join([first.lower(), *map(str.title, others)])
def decamelize(s: str):
- return re.sub('([A-Z]+)', '_\\1', s).lower()
+ return re.sub("([A-Z]+)", "_\\1", s).lower()
def decamelize_obj(d: dict | list):
@@ -30,15 +40,15 @@ def decamelize_obj(d: dict | list):
@dataclass
class RoborockBase:
-
@classmethod
def from_dict(cls, data: dict[str, Any]):
return from_dict(cls, decamelize_obj(data), config=Config(cast=[Enum]))
def as_dict(self):
- return asdict(self, dict_factory=lambda _fields: {
- camelize(key): value for (key, value) in _fields if value is not None
- })
+ return asdict(
+ self,
+ dict_factory=lambda _fields: {camelize(key): value for (key, value) in _fields if value is not None},
+ )
@dataclass
diff --git a/roborock/exceptions.py b/roborock/exceptions.py
index 65aa10c3..bcfeaef3 100644
--- a/roborock/exceptions.py
+++ b/roborock/exceptions.py
@@ -1,31 +1,38 @@
"""Roborock exceptions."""
+
class RoborockException(BaseException):
"""Class for Roborock exceptions."""
+
class RoborockTimeout(RoborockException):
"""Class for Roborock timeout exceptions."""
+
class RoborockConnectionException(RoborockException):
"""Class for Roborock connection exceptions."""
+
class RoborockBackoffException(RoborockException):
"""Class for Roborock exceptions when many retries were made."""
+
class VacuumError(RoborockException):
"""Class for vacuum errors."""
+
def __init__(self, code, message):
self.code = code
self.message = message
super().__init__()
- def __str__(self, *args, **kwargs): # real signature unknown
- """ Return str(self). """
+ def __str__(self, *args, **kwargs): # real signature unknown
+ """Return str(self)."""
return f"{self.code}: {self.message}"
class CommandVacuumError(RoborockException):
"""Class for command vacuum errors."""
+
def __init__(self, command: str, vacuum_error: VacuumError):
self.message = f"{command}: {str(vacuum_error)}"
super().__init__(self.message)
diff --git a/roborock/local_api.py b/roborock/local_api.py
index 50ccf249..3d5dea5d 100644
--- a/roborock/local_api.py
+++ b/roborock/local_api.py
@@ -4,22 +4,21 @@
import logging
import socket
from asyncio import Lock
-from typing import Optional, Callable, Awaitable, Any, Mapping
+from typing import Any, Awaitable, Callable, Mapping, Optional
import async_timeout
-from roborock.api import RoborockClient, SPECIAL_COMMANDS
+from roborock.api import SPECIAL_COMMANDS, RoborockClient
from roborock.containers import RoborockLocalDeviceInfo
-from roborock.exceptions import RoborockTimeout, CommandVacuumError, RoborockConnectionException, RoborockException
-from roborock.roborock_message import RoborockParser, RoborockMessage
-from roborock.typing import RoborockCommand, CommandInfoMap
+from roborock.exceptions import CommandVacuumError, RoborockConnectionException, RoborockException, RoborockTimeout
+from roborock.roborock_message import RoborockMessage, RoborockParser
+from roborock.typing import CommandInfoMap, RoborockCommand
from roborock.util import get_running_loop_or_create_one
_LOGGER = logging.getLogger(__name__)
class RoborockLocalClient(RoborockClient):
-
def __init__(self, devices_info: Mapping[str, RoborockLocalDeviceInfo]):
super().__init__("abc", devices_info)
self.loop = get_running_loop_or_create_one()
@@ -27,7 +26,7 @@ def __init__(self, devices_info: Mapping[str, RoborockLocalDeviceInfo]):
device_id: RoborockSocketListener(
device_info.network_info.ip,
device_info.device.local_key,
- self.on_message
+ self.on_message,
)
for device_id, device_info in devices_info.items()
}
@@ -36,17 +35,12 @@ def __init__(self, devices_info: Mapping[str, RoborockLocalDeviceInfo]):
self._executing = False
async def async_connect(self):
- await asyncio.gather(*[
- listener.connect()
- for listener in self.device_listener.values()
- ])
+ await asyncio.gather(*[listener.connect() for listener in self.device_listener.values()])
async def async_disconnect(self) -> None:
await asyncio.gather(*[listener.disconnect() for listener in self.device_listener.values()])
- def build_roborock_message(
- self, method: RoborockCommand, params: Optional[list] = None
- ) -> RoborockMessage:
+ def build_roborock_message(self, method: RoborockCommand, params: Optional[list] = None) -> RoborockMessage:
secured = True if method in SPECIAL_COMMANDS else False
request_id, timestamp, payload = self._get_payload(method, params, secured)
_LOGGER.debug(f"id={request_id} Requesting method {method} with {params}")
@@ -62,12 +56,10 @@ def build_roborock_message(
prefix=prefix,
timestamp=timestamp,
protocol=request_protocol,
- payload=payload
+ payload=payload,
)
- async def send_command(
- self, device_id: str, method: RoborockCommand, params: Optional[list] = None
- ):
+ async def send_command(self, device_id: str, method: RoborockCommand, params: Optional[list] = None):
roborock_message = self.build_roborock_message(method, params)
response = (await self.send_message(device_id, roborock_message))[0]
if isinstance(response, BaseException):
@@ -85,9 +77,7 @@ async def async_local_response(self, roborock_message: RoborockMessage):
_LOGGER.debug(f"id={request_id} Response from {roborock_message.get_method()}: {response}")
return response
- async def send_message(
- self, device_id: str, roborock_messages: list[RoborockMessage] | RoborockMessage
- ):
+ async def send_message(self, device_id: str, roborock_messages: list[RoborockMessage] | RoborockMessage):
if isinstance(roborock_messages, RoborockMessage):
roborock_messages = [roborock_messages]
local_key = self.devices_info[device_id].device.local_key
@@ -101,7 +91,8 @@ async def send_message(
return await asyncio.gather(
*[self.async_local_response(roborock_message) for roborock_message in roborock_messages],
- return_exceptions=True)
+ return_exceptions=True,
+ )
class RoborockSocket(socket.socket):
@@ -115,8 +106,13 @@ def is_closed(self):
class RoborockSocketListener:
roborock_port = 58867
- def __init__(self, ip: str, local_key: str, on_message: Callable[[list[RoborockMessage]], Awaitable[Any]],
- timeout: float | int = 4):
+ def __init__(
+ self,
+ ip: str,
+ local_key: str,
+ on_message: Callable[[list[RoborockMessage]], Awaitable[Any]],
+ timeout: float | int = 4,
+ ):
self.ip = ip
self.local_key = local_key
self.socket = RoborockSocket(socket.AF_INET, socket.SOCK_STREAM)
@@ -126,7 +122,7 @@ def __init__(self, ip: str, local_key: str, on_message: Callable[[list[RoborockM
self.timeout = timeout
self.is_connected = False
self._mutex = Lock()
- self.remaining = b''
+ self.remaining = b""
async def _main_coro(self):
while not self.socket.is_closed:
@@ -135,7 +131,7 @@ async def _main_coro(self):
try:
if self.remaining:
message = self.remaining + message
- self.remaining = b''
+ self.remaining = b""
(parser_msg, remaining) = RoborockParser.decode(message, self.local_key)
self.remaining = remaining
await self.on_message(parser_msg)
@@ -172,9 +168,7 @@ async def send_message(self, data: bytes) -> None:
await self.loop.sock_sendall(self.socket, data)
except (asyncio.TimeoutError, asyncio.CancelledError):
await self.disconnect()
- raise RoborockTimeout(
- f"Timeout after {self.timeout} seconds waiting for response"
- ) from None
+ raise RoborockTimeout(f"Timeout after {self.timeout} seconds waiting for response") from None
except BrokenPipeError as e:
_LOGGER.exception(e)
await self.disconnect()
diff --git a/roborock/roborock_message.py b/roborock/roborock_message.py
index 544d39ee..6a84dc60 100644
--- a/roborock/roborock_message.py
+++ b/roborock/roborock_message.py
@@ -35,8 +35,8 @@ class RoborockMessage:
protocol: int
payload: bytes
seq: int = randint(100000, 999999)
- prefix: bytes = b''
- version: bytes = b'1.0'
+ prefix: bytes = b""
+ version: bytes = b"1.0"
random: int = randint(10000, 99999)
timestamp: int = math.floor(time.time())
@@ -72,13 +72,12 @@ def get_params(self) -> list | None:
class RoborockParser:
-
@staticmethod
def encode(roborock_messages: list[RoborockMessage] | RoborockMessage, local_key: str):
if isinstance(roborock_messages, RoborockMessage):
roborock_messages = [roborock_messages]
- msg = b''
+ msg = b""
for roborock_message in roborock_messages:
aes_key = md5bin(encode_timestamp(roborock_message.timestamp) + local_key + salt)
cipher = AES.new(aes_key, AES.MODE_ECB)
@@ -95,62 +94,67 @@ def encode(roborock_messages: list[RoborockMessage] | RoborockMessage, local_key
roborock_message.timestamp,
roborock_message.protocol,
encrypted_len,
- encrypted
+ encrypted,
)
if payload:
crc32 = binascii.crc32(_msg)
_msg += struct.pack("!I", crc32)
else:
- _msg += b'\x00\x00'
+ _msg += b"\x00\x00"
msg += roborock_message.prefix + _msg
return msg
@staticmethod
def decode(msg: bytes, local_key: str, index=0) -> tuple[list[RoborockMessage], bytes]:
- prefix = b''
+ prefix = b""
original_index = index
if len(msg) - index < 17:
## broken message
return [], msg[original_index:]
- if msg[index + 4:index + 7] == "1.0".encode():
- prefix = msg[index:index + 4]
+ if msg[index + 4 : index + 7] == "1.0".encode():
+ prefix = msg[index : index + 4]
index += 4
- elif msg[index:index + 3] != "1.0".encode():
+ elif msg[index : index + 3] != "1.0".encode():
raise RoborockException(f"Unknown protocol version {msg[0:3]!r}")
if len(msg) - index in [17]:
- [version, request_id, random, timestamp, protocol] = struct.unpack_from(
- "!3sIIIH", msg, index
- )
- return [RoborockMessage(
- prefix=prefix,
- version=version,
- seq=request_id,
- random=random,
- timestamp=timestamp,
- protocol=protocol,
- payload=b''
- )], b''
+ [version, request_id, random, timestamp, protocol] = struct.unpack_from("!3sIIIH", msg, index)
+ return [
+ RoborockMessage(
+ prefix=prefix,
+ version=version,
+ seq=request_id,
+ random=random,
+ timestamp=timestamp,
+ protocol=protocol,
+ payload=b"",
+ )
+ ], b""
if len(msg) - index < 19:
## broken message
return [], msg[original_index:]
- [version, request_id, random, timestamp, protocol, payload_len] = struct.unpack_from(
- "!3sIIIHH", msg, index
- )
+ [
+ version,
+ request_id,
+ random,
+ timestamp,
+ protocol,
+ payload_len,
+ ] = struct.unpack_from("!3sIIIHH", msg, index)
index += 19
if payload_len + index + 4 > len(msg):
## broken message
return [], msg[original_index:]
- payload = b''
+ payload = b""
if payload_len == 0:
index += 2
else:
[payload, expected_crc32] = struct.unpack_from(f"!{payload_len}sI", msg, index)
- crc32 = binascii.crc32(msg[index - 19: index + payload_len])
+ crc32 = binascii.crc32(msg[index - 19 : index + payload_len])
index += 4 + payload_len
if crc32 != expected_crc32:
raise RoborockException(f"Wrong CRC32 {crc32}, expected {expected_crc32}")
@@ -160,14 +164,16 @@ def decode(msg: bytes, local_key: str, index=0) -> tuple[list[RoborockMessage],
decipher = AES.new(aes_key, AES.MODE_ECB)
payload = unpad(decipher.decrypt(payload), AES.block_size)
- [structs, remaining] = RoborockParser.decode(msg, local_key, index) if index < len(msg) else ([], b'')
-
- return [RoborockMessage(
- prefix=prefix,
- version=version,
- seq=request_id,
- random=random,
- timestamp=timestamp,
- protocol=protocol,
- payload=payload
- )] + structs, remaining
+ [structs, remaining] = RoborockParser.decode(msg, local_key, index) if index < len(msg) else ([], b"")
+
+ return [
+ RoborockMessage(
+ prefix=prefix,
+ version=version,
+ seq=request_id,
+ random=random,
+ timestamp=timestamp,
+ protocol=protocol,
+ payload=payload,
+ )
+ ] + structs, remaining
diff --git a/roborock/typing.py b/roborock/typing.py
index eaaa16f3..5cc20b80 100644
--- a/roborock/typing.py
+++ b/roborock/typing.py
@@ -4,8 +4,16 @@
from dataclasses import dataclass
from enum import Enum
-from .containers import Status, CleanSummary, Consumable, \
- DNDTimer, CleanRecord, SmartWashParams, DustCollectionMode, WashTowelMode
+from .containers import (
+ CleanRecord,
+ CleanSummary,
+ Consumable,
+ DNDTimer,
+ DustCollectionMode,
+ SmartWashParams,
+ Status,
+ WashTowelMode,
+)
class RoborockDevicePropField(str, Enum):
@@ -117,82 +125,82 @@ class CommandInfo:
CommandInfoMap: dict[RoborockCommand, CommandInfo] = {
- RoborockCommand.GET_PROP: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x77'),
- RoborockCommand.SET_CUSTOM_MODE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_CHILD_LOCK_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_MULTI_MAPS_LIST: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_IDENTIFY_FURNITURE_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.SET_WATER_BOX_CUSTOM_MODE: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_CLEAN_SEQUENCE: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_CUSTOMIZE_CLEAN_MODE: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_CARPET_CLEAN_MODE: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.SET_CAMERA_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.SET_DND_TIMER: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_FLOW_LED_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_COLLISION_AVOID_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.CLOSE_VALLEY_ELECTRICITY_TIMER: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.SET_FLOW_LED_STATUS: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_VALLEY_ELECTRICITY_TIMER: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_CLEAN_RECORD: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_MAP_V1: CommandInfo(prefix=b'\x00\x00\x00\xc7'),
- RoborockCommand.SET_CLEAN_MOTOR_MODE: CommandInfo(prefix=b'\x00\x00\x00\xb7'),
- RoborockCommand.GET_CONSUMABLE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_SERVER_TIMER: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_SERIAL_NUMBER: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_CURRENT_SOUND: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.SET_LED_STATUS: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_CAMERA_STATUS: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_PAUSE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_CLEAN_SUMMARY: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_NETWORK_INFO: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_LED_STATUS: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.CLOSE_DND_TIMER: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_WAKEUP_ROBOT: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.SET_MOP_MODE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_DND_TIMER: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_CARPET_MODE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.GET_TIMEZONE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.SET_CARPET_MODE: CommandInfo(prefix=b'\x00\x00\x00\xd7'),
- RoborockCommand.GET_MULTI_MAP: CommandInfo(prefix=b'\x00\x00\x00\xd7'),
- RoborockCommand.SET_COLLISION_AVOID_STATUS: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.SET_CARPET_CLEAN_MODE: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.SET_IDENTIFY_GROUND_MATERIAL_STATUS: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.GET_IDENTIFY_GROUND_MATERIAL_STATUS: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.SWITCH_WATER_MARK: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.SET_IDENTIFY_FURNITURE_STATUS: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.SET_CHILD_LOCK_STATUS: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.GET_CLEAN_RECORD_MAP: CommandInfo(prefix=b'\x00\x00\xe7'),
- RoborockCommand.APP_START: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_STOP: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_CHARGE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_SPOT: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.FIND_ME: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.RESUME_ZONED_CLEAN: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.RESUME_SEGMENT_CLEAN: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.RESET_CONSUMABLE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.LOAD_MULTI_MAP: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_RC_START: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_RC_END: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_RC_MOVE: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_GOTO_TARGET: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.APP_SEGMENT_CLEAN: CommandInfo(prefix=b'\x00\x00\x00\xc7'),
- RoborockCommand.APP_ZONED_CLEAN: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_START_WASH: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.APP_STOP_WASH: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.SET_FDS_ENDPOINT: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.ENABLE_LOG_UPLOAD: CommandInfo(prefix=b'\x00\x00\x87'),
- RoborockCommand.GET_SOUND_VOLUME: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.TEST_SOUND_VOLUME: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.UPD_SERVER_TIMER: CommandInfo(prefix=b'\x00\x00\x00\x97'),
- RoborockCommand.SET_APP_TIMEZONE: CommandInfo(prefix=b'\x00\x00\x97'),
- RoborockCommand.CHANGE_SOUND_VOLUME: CommandInfo(prefix=b'\x00\x00\x00\x87'),
- RoborockCommand.GET_SOUND_PROGRESS: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.SET_SERVER_TIMER: CommandInfo(prefix=b'\x00\x00\x00\xc7'),
- RoborockCommand.GET_ROOM_MAPPING: CommandInfo(prefix=b'\x00\x00\x00w'),
- RoborockCommand.NAME_SEGMENT: CommandInfo(prefix=b'\x00\x00\x027'),
- RoborockCommand.SET_TIMEZONE: CommandInfo(prefix=b'\x00\x00\x00\x97')
+ RoborockCommand.GET_PROP: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x77"),
+ RoborockCommand.SET_CUSTOM_MODE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_CHILD_LOCK_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_MULTI_MAPS_LIST: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_IDENTIFY_FURNITURE_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.SET_WATER_BOX_CUSTOM_MODE: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_CLEAN_SEQUENCE: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_CUSTOMIZE_CLEAN_MODE: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_CARPET_CLEAN_MODE: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.SET_CAMERA_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.SET_DND_TIMER: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_FLOW_LED_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_COLLISION_AVOID_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.CLOSE_VALLEY_ELECTRICITY_TIMER: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.SET_FLOW_LED_STATUS: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_VALLEY_ELECTRICITY_TIMER: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_CLEAN_RECORD: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_MAP_V1: CommandInfo(prefix=b"\x00\x00\x00\xc7"),
+ RoborockCommand.SET_CLEAN_MOTOR_MODE: CommandInfo(prefix=b"\x00\x00\x00\xb7"),
+ RoborockCommand.GET_CONSUMABLE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_SERVER_TIMER: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_SERIAL_NUMBER: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_CURRENT_SOUND: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.SET_LED_STATUS: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_CAMERA_STATUS: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_PAUSE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_CLEAN_SUMMARY: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_NETWORK_INFO: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_LED_STATUS: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.CLOSE_DND_TIMER: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_WAKEUP_ROBOT: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.SET_MOP_MODE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_DND_TIMER: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_CARPET_MODE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.GET_TIMEZONE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.SET_CARPET_MODE: CommandInfo(prefix=b"\x00\x00\x00\xd7"),
+ RoborockCommand.GET_MULTI_MAP: CommandInfo(prefix=b"\x00\x00\x00\xd7"),
+ RoborockCommand.SET_COLLISION_AVOID_STATUS: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.SET_CARPET_CLEAN_MODE: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.SET_IDENTIFY_GROUND_MATERIAL_STATUS: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.GET_IDENTIFY_GROUND_MATERIAL_STATUS: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.SET_VALLEY_ELECTRICITY_TIMER: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.SWITCH_WATER_MARK: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.SET_IDENTIFY_FURNITURE_STATUS: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.SET_CHILD_LOCK_STATUS: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.GET_CLEAN_RECORD_MAP: CommandInfo(prefix=b"\x00\x00\xe7"),
+ RoborockCommand.APP_START: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_STOP: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_CHARGE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_SPOT: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.FIND_ME: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.RESUME_ZONED_CLEAN: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.RESUME_SEGMENT_CLEAN: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.RESET_CONSUMABLE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.LOAD_MULTI_MAP: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_RC_START: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_RC_END: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_RC_MOVE: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_GOTO_TARGET: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.APP_SEGMENT_CLEAN: CommandInfo(prefix=b"\x00\x00\x00\xc7"),
+ RoborockCommand.APP_ZONED_CLEAN: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_START_WASH: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.APP_STOP_WASH: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.SET_FDS_ENDPOINT: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.ENABLE_LOG_UPLOAD: CommandInfo(prefix=b"\x00\x00\x87"),
+ RoborockCommand.GET_SOUND_VOLUME: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.TEST_SOUND_VOLUME: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.UPD_SERVER_TIMER: CommandInfo(prefix=b"\x00\x00\x00\x97"),
+ RoborockCommand.SET_APP_TIMEZONE: CommandInfo(prefix=b"\x00\x00\x97"),
+ RoborockCommand.CHANGE_SOUND_VOLUME: CommandInfo(prefix=b"\x00\x00\x00\x87"),
+ RoborockCommand.GET_SOUND_PROGRESS: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.SET_SERVER_TIMER: CommandInfo(prefix=b"\x00\x00\x00\xc7"),
+ RoborockCommand.GET_ROOM_MAPPING: CommandInfo(prefix=b"\x00\x00\x00w"),
+ RoborockCommand.NAME_SEGMENT: CommandInfo(prefix=b"\x00\x00\x027"),
+ RoborockCommand.SET_TIMEZONE: CommandInfo(prefix=b"\x00\x00\x00\x97")
# TODO discover prefix for following commands
# RoborockCommand.APP_GET_DRYER_SETTING: CommandInfo(prefix=b'\x00\x00\x00w'),
# RoborockCommand.APP_SET_DRYER_SETTING: CommandInfo(prefix=b'\x00\x00\x00w'),
@@ -207,8 +215,12 @@ class CommandInfo:
class RoborockDockSummary:
- def __init__(self, dust_collection_mode: DustCollectionMode,
- wash_towel_mode: WashTowelMode, smart_wash_params: SmartWashParams) -> None:
+ def __init__(
+ self,
+ dust_collection_mode: DustCollectionMode,
+ wash_towel_mode: WashTowelMode,
+ smart_wash_params: SmartWashParams,
+ ) -> None:
self.dust_collection_mode = dust_collection_mode
self.wash_towel_mode = wash_towel_mode
self.smart_wash_params = smart_wash_params
@@ -223,7 +235,7 @@ class RoborockDeviceProp:
last_clean_record: typing.Optional[CleanRecord] = None
dock_summary: typing.Optional[RoborockDockSummary] = None
- def update(self, device_prop: 'RoborockDeviceProp'):
+ def update(self, device_prop: "RoborockDeviceProp"):
if device_prop.status:
self.status = device_prop.status
if device_prop.dnd_timer:
diff --git a/tests/conftest.py b/tests/conftest.py
index 557d2006..ab90395e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,7 +1,8 @@
import pytest
-from roborock import RoborockMqttClient, UserData, HomeData
-from tests.mock_data import USER_DATA, HOME_DATA_RAW
+from roborock import HomeData, UserData
+from roborock.cloud_api import RoborockMqttClient
+from tests.mock_data import HOME_DATA_RAW, USER_DATA
@pytest.fixture(name="mqtt_client")
diff --git a/tests/mock_data.py b/tests/mock_data.py
index 600fd6ee..a1701f89 100644
--- a/tests/mock_data.py
+++ b/tests/mock_data.py
@@ -333,11 +333,9 @@
}
BASE_URL_REQUEST = {
- 'code': 200,
- 'msg': 'success',
- 'data': {
- 'url': 'https://sample.com'
- }
+ "code": 200,
+ "msg": "success",
+ "data": {"url": "https://sample.com"},
}
-GET_CODE_RESPONSE = {'code': 200, 'msg': 'success', 'data': None}
+GET_CODE_RESPONSE = {"code": 200, "msg": "success", "data": None}
diff --git a/tests/test_api.py b/tests/test_api.py
index 086a5f58..5fa0c85a 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -3,11 +3,10 @@
import paho.mqtt.client as mqtt
import pytest
-from roborock import RoborockApiClient, UserData, HomeData, RoborockDockDustCollectionModeCode, \
- RoborockDockWashTowelModeCode
-from roborock.api import PreparedRequest
+from roborock import HomeData, RoborockDockDustCollectionModeCode, RoborockDockWashTowelModeCode, UserData
+from roborock.api import PreparedRequest, RoborockApiClient
from roborock.cloud_api import RoborockMqttClient
-from tests.mock_data import BASE_URL_REQUEST, GET_CODE_RESPONSE, USER_DATA, HOME_DATA_RAW
+from tests.mock_data import BASE_URL_REQUEST, GET_CODE_RESPONSE, HOME_DATA_RAW, USER_DATA
def test_can_create_roborock_client():
@@ -42,9 +41,9 @@ async def test_get_base_url_no_url():
@pytest.mark.asyncio
async def test_request_code():
rc = RoborockApiClient("sample@gmail.com")
- with patch("roborock.api.RoborockApiClient._get_base_url") as mock_url, patch(
- "roborock.api.RoborockApiClient._get_header_client_id") as mock_header_client, patch(
- "roborock.api.PreparedRequest.request") as mock_request:
+ with patch("roborock.api.RoborockApiClient._get_base_url"), patch(
+ "roborock.api.RoborockApiClient._get_header_client_id"
+ ), patch("roborock.api.PreparedRequest.request") as mock_request:
mock_request.return_value = GET_CODE_RESPONSE
await rc.request_code()
@@ -52,11 +51,13 @@ async def test_request_code():
@pytest.mark.asyncio
async def test_get_home_data():
rc = RoborockApiClient("sample@gmail.com")
- with patch("roborock.api.RoborockApiClient._get_base_url") as mock_url, patch(
- "roborock.api.RoborockApiClient._get_header_client_id") as mock_header_client, patch(
- "roborock.api.PreparedRequest.request") as mock_prepared_request:
- mock_prepared_request.side_effect = [{'code': 200, 'msg': 'success', 'data': {"rrHomeId": 1}},
- {'code': 200, 'success': True, 'result': HOME_DATA_RAW}]
+ with patch("roborock.api.RoborockApiClient._get_base_url"), patch(
+ "roborock.api.RoborockApiClient._get_header_client_id"
+ ), patch("roborock.api.PreparedRequest.request") as mock_prepared_request:
+ mock_prepared_request.side_effect = [
+ {"code": 200, "msg": "success", "data": {"rrHomeId": 1}},
+ {"code": 200, "success": True, "result": HOME_DATA_RAW},
+ ]
user_data = UserData.from_dict(USER_DATA)
result = await rc.get_home_data(user_data)
@@ -73,7 +74,7 @@ async def test_get_dust_collection_mode():
command.return_value = {"mode": 1}
dust = await rmc.get_dust_collection_mode(home_data.devices[0].duid)
assert dust is not None
- assert dust.mode == RoborockDockDustCollectionModeCode['1']
+ assert dust.mode == RoborockDockDustCollectionModeCode["1"]
@pytest.mark.asyncio
@@ -82,7 +83,7 @@ async def test_get_mop_wash_mode():
device_map = {home_data.devices[0].duid: home_data.devices[0]}
rmc = RoborockMqttClient(UserData.from_dict(USER_DATA), device_map)
with patch("roborock.cloud_api.RoborockMqttClient.send_command") as command:
- command.return_value = {'smart_wash': 0, 'wash_interval': 1500}
+ command.return_value = {"smart_wash": 0, "wash_interval": 1500}
mop_wash = await rmc.get_smart_wash_params(home_data.devices[0].duid)
assert mop_wash is not None
assert mop_wash.smart_wash == 0
@@ -95,7 +96,7 @@ async def test_get_washing_mode():
device_map = {home_data.devices[0].duid: home_data.devices[0]}
rmc = RoborockMqttClient(UserData.from_dict(USER_DATA), device_map)
with patch("roborock.cloud_api.RoborockMqttClient.send_command") as command:
- command.return_value = {'wash_mode': 2}
+ command.return_value = {"wash_mode": 2}
washing_mode = await rmc.get_wash_towel_mode(home_data.devices[0].duid)
assert washing_mode is not None
- assert washing_mode.wash_mode == RoborockDockWashTowelModeCode['2']
+ assert washing_mode.wash_mode == RoborockDockWashTowelModeCode["2"]
diff --git a/tests/test_containers.py b/tests/test_containers.py
index 0af5cf6d..f14fc699 100644
--- a/tests/test_containers.py
+++ b/tests/test_containers.py
@@ -1,7 +1,15 @@
-from roborock import UserData, HomeData, Consumable, Status, DNDTimer, CleanSummary, CleanRecord, HomeDataProduct
-from roborock.code_mappings import RoborockStateCode, RoborockErrorCode, RoborockFanPowerCode, RoborockMopIntensityCode, \
- RoborockDockTypeCode, RoborockMopModeCode, RoborockDockErrorCode
-from .mock_data import USER_DATA, HOME_DATA_RAW, CONSUMABLE, STATUS, DND_TIMER, CLEAN_SUMMARY, CLEAN_RECORD
+from roborock import CleanRecord, CleanSummary, Consumable, DNDTimer, HomeData, Status, UserData
+from roborock.code_mappings import (
+ RoborockDockErrorCode,
+ RoborockDockTypeCode,
+ RoborockErrorCode,
+ RoborockFanPowerCode,
+ RoborockMopIntensityCode,
+ RoborockMopModeCode,
+ RoborockStateCode,
+)
+
+from .mock_data import CLEAN_RECORD, CLEAN_SUMMARY, CONSUMABLE, DND_TIMER, HOME_DATA_RAW, STATUS, USER_DATA
def test_user_data():
@@ -12,7 +20,7 @@ def test_user_data():
assert ud.rruid == "abc123"
assert ud.region == "us"
assert ud.country == "US"
- assert ud.countrycode == '1'
+ assert ud.countrycode == "1"
assert ud.nickname == "user_nickname"
assert ud.rriot.u == "user123"
assert ud.rriot.s == "pass123"
@@ -103,11 +111,11 @@ def test_status():
s = Status.from_dict(STATUS)
assert s.msg_ver == 2
assert s.msg_seq == 458
- assert s.state == RoborockStateCode['8']
+ assert s.state == RoborockStateCode["8"]
assert s.battery == 100
assert s.clean_time == 1176
assert s.clean_area == 20965000
- assert s.error_code == RoborockErrorCode['0']
+ assert s.error_code == RoborockErrorCode["0"]
assert s.map_present == 1
assert s.in_cleaning == 0
assert s.in_returning == 0
@@ -117,12 +125,12 @@ def test_status():
assert s.back_type == -1
assert s.wash_phase == 0
assert s.wash_ready == 0
- assert s.fan_power == RoborockFanPowerCode['102']
+ assert s.fan_power == RoborockFanPowerCode["102"]
assert s.dnd_enabled == 0
assert s.map_status == 3
assert s.is_locating == 0
assert s.lock_status == 0
- assert s.water_box_mode == RoborockMopIntensityCode['203']
+ assert s.water_box_mode == RoborockMopIntensityCode["203"]
assert s.water_box_carriage_status == 1
assert s.mop_forbidden_enable == 1
assert s.camera_status == 3457
@@ -131,15 +139,15 @@ def test_status():
assert s.home_sec_enable_password == 0
assert s.adbumper_status == [0, 0, 0]
assert s.water_shortage_status == 0
- assert s.dock_type == RoborockDockTypeCode['3']
+ assert s.dock_type == RoborockDockTypeCode["3"]
assert s.dust_collection_status == 0
assert s.auto_dust_collection == 1
assert s.avoid_count == 19
- assert s.mop_mode == RoborockMopModeCode['300']
+ assert s.mop_mode == RoborockMopModeCode["300"]
assert s.debug_mode == 0
assert s.collision_avoid_status == 1
assert s.switch_map_mode == 0
- assert s.dock_error_status == RoborockDockErrorCode['0']
+ assert s.dock_error_status == RoborockDockErrorCode["0"]
assert s.charge_status == 1
assert s.unsave_map_reason == 0
assert s.unsave_map_flag == 0
@@ -178,4 +186,4 @@ def test_clean_record():
assert cr.dust_collection_status == 1
assert cr.avoid_count == 19
assert cr.wash_count == 2
- assert cr.map_flag == 0
\ No newline at end of file
+ assert cr.map_flag == 0
diff --git a/tests/test_queue.py b/tests/test_queue.py
index e42d2366..8fdfa099 100644
--- a/tests/test_queue.py
+++ b/tests/test_queue.py
@@ -20,7 +20,7 @@ async def test_put():
async def test_get_timeout():
rq = RoborockQueue(1)
with pytest.raises(asyncio.TimeoutError):
- await rq.async_get(.01)
+ await rq.async_get(0.01)
@pytest.mark.asyncio