python-roborock 2.53.1__tar.gz → 2.58.0__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.
- python_roborock-2.58.0/.gitignore +21 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/PKG-INFO +19 -25
- python_roborock-2.58.0/pyproject.toml +100 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/api.py +1 -1
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/clean_modes.py +27 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/cli.py +192 -20
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/code_mappings.py +34 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/containers.py +93 -7
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/device_features.py +84 -1
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/a01_channel.py +2 -4
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/cache.py +8 -1
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/device.py +21 -1
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/device_manager.py +8 -2
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/traits_mixin.py +1 -1
- python_roborock-2.58.0/roborock/devices/traits/v1/__init__.py +163 -0
- python_roborock-2.58.0/roborock/devices/traits/v1/child_lock.py +20 -0
- python_roborock-2.58.0/roborock/devices/traits/v1/command.py +21 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/common.py +5 -1
- python_roborock-2.58.0/roborock/devices/traits/v1/device_features.py +47 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/do_not_disturb.py +22 -0
- python_roborock-2.58.0/roborock/devices/traits/v1/flow_led_status.py +20 -0
- python_roborock-2.58.0/roborock/devices/traits/v1/home.py +174 -0
- python_roborock-2.58.0/roborock/devices/traits/v1/led_status.py +34 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/map_content.py +1 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/maps.py +9 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/rooms.py +1 -13
- python_roborock-2.58.0/roborock/devices/traits/v1/valley_electricity_timer.py +40 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/v1_channel.py +3 -3
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/v1_rpc_channel.py +0 -1
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/exceptions.py +8 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/protocols/v1_protocol.py +3 -3
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/roborock_future.py +2 -3
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_1_apis/roborock_local_client_v1.py +1 -3
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/web_api.py +12 -3
- python_roborock-2.53.1/pyproject.toml +0 -82
- python_roborock-2.53.1/roborock/devices/traits/v1/__init__.py +0 -96
- {python_roborock-2.53.1 → python_roborock-2.58.0}/LICENSE +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/README.md +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/b01_containers.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/broadcast_protocol.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/callbacks.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/cloud_api.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/command_cache.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/const.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/README.md +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/b01_channel.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/channel.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/local_channel.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/mqtt_channel.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/a01/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/b01/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/clean_summary.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/consumeable.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/status.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/devices/traits/v1/volume.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/map/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/map/map_parser.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/mqtt/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/mqtt/roborock_session.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/mqtt/session.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/protocol.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/protocols/a01_protocol.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/protocols/b01_protocol.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/py.typed +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/roborock_message.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/roborock_typing.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/util.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_1_apis/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_1_apis/roborock_client_v1.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_a01_apis/__init__.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
- {python_roborock-2.53.1 → python_roborock-2.58.0}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
dist
|
|
2
|
+
venv
|
|
3
|
+
.venv
|
|
4
|
+
.idea
|
|
5
|
+
roborock/__pycache__
|
|
6
|
+
*.pyc
|
|
7
|
+
.coverage
|
|
8
|
+
|
|
9
|
+
# Sphinx documentation
|
|
10
|
+
docs/_build/
|
|
11
|
+
|
|
12
|
+
# mkdocs documentation
|
|
13
|
+
/site
|
|
14
|
+
/docs/build/
|
|
15
|
+
.DS_Store
|
|
16
|
+
|
|
17
|
+
# gemini-cli settings
|
|
18
|
+
.gemini/
|
|
19
|
+
|
|
20
|
+
# GitHub App credentials
|
|
21
|
+
gha-creds-*.json
|
|
@@ -1,35 +1,30 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-roborock
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.58.0
|
|
4
4
|
Summary: A package to control Roborock vacuums.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Author: humbertogontijo
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
|
|
6
|
+
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
7
|
+
Author: Lash-L, allenporter
|
|
8
|
+
Author-email: humbertogontijo <humbertogontijo@users.noreply.github.com>
|
|
9
|
+
License-Expression: GPL-3.0-only
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: homeassistant,roborock,vacuum
|
|
11
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
14
14
|
Classifier: Natural Language :: English
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
16
|
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
-
Requires-
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist: click
|
|
24
|
-
Requires-Dist: click
|
|
25
|
-
Requires-Dist: construct
|
|
26
|
-
Requires-Dist: paho-mqtt
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist: pyrate-limiter
|
|
17
|
+
Requires-Python: <4,>=3.11
|
|
18
|
+
Requires-Dist: aiohttp<4,>=3.8.2
|
|
19
|
+
Requires-Dist: aiomqtt<3,>=2.3.2
|
|
20
|
+
Requires-Dist: click-shell~=2.1
|
|
21
|
+
Requires-Dist: click>=8
|
|
22
|
+
Requires-Dist: construct<3,>=2.10.57
|
|
23
|
+
Requires-Dist: paho-mqtt<3.0.0,>=1.6.1
|
|
24
|
+
Requires-Dist: pycryptodomex~=3.18; sys_platform == 'darwin'
|
|
25
|
+
Requires-Dist: pycryptodome~=3.18
|
|
26
|
+
Requires-Dist: pyrate-limiter<4,>=3.7.0
|
|
30
27
|
Requires-Dist: vacuum-map-parser-roborock
|
|
31
|
-
Project-URL: Documentation, https://python-roborock.readthedocs.io/
|
|
32
|
-
Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
|
|
33
28
|
Description-Content-Type: text/markdown
|
|
34
29
|
|
|
35
30
|
# Roborock
|
|
@@ -108,4 +103,3 @@ Please note this may not immediately contain the latest devices.
|
|
|
108
103
|
## Credits
|
|
109
104
|
|
|
110
105
|
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
|
|
111
|
-
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "python-roborock"
|
|
3
|
+
version = "2.58.0"
|
|
4
|
+
description = "A package to control Roborock vacuums."
|
|
5
|
+
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
|
|
6
|
+
requires-python = ">=3.11, <4"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "GPL-3.0-only"
|
|
9
|
+
keywords = [
|
|
10
|
+
"roborock",
|
|
11
|
+
"vacuum",
|
|
12
|
+
"homeassistant",
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"click>=8",
|
|
23
|
+
"aiohttp>=3.8.2,<4",
|
|
24
|
+
"pycryptodome~=3.18",
|
|
25
|
+
"pycryptodomex~=3.18 ; sys_platform == 'darwin'",
|
|
26
|
+
"paho-mqtt>=1.6.1,<3.0.0",
|
|
27
|
+
"construct>=2.10.57,<3",
|
|
28
|
+
"vacuum-map-parser-roborock",
|
|
29
|
+
"pyrate-limiter>=3.7.0,<4",
|
|
30
|
+
"aiomqtt>=2.3.2,<3",
|
|
31
|
+
"click-shell~=2.1",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Repository = "https://github.com/humbertogontijo/python-roborock"
|
|
36
|
+
Documentation = "https://python-roborock.readthedocs.io/"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
roborock = "roborock.cli:main"
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest-asyncio>=1.1.0",
|
|
44
|
+
"pytest",
|
|
45
|
+
"pre-commit>=3.5,<5.0",
|
|
46
|
+
"mypy",
|
|
47
|
+
"ruff==0.14.0",
|
|
48
|
+
"codespell",
|
|
49
|
+
"pyshark>=0.6,<0.7",
|
|
50
|
+
"aioresponses>=0.7.7,<0.8",
|
|
51
|
+
"freezegun>=1.5.1,<2",
|
|
52
|
+
"pytest-timeout>=2.3.1,<3",
|
|
53
|
+
"syrupy>=4.9.1,<5",
|
|
54
|
+
"pdoc>=15.0.4,<16",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.sdist]
|
|
58
|
+
include = ["roborock"]
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
include = ["roborock"]
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["hatchling"]
|
|
65
|
+
build-backend = "hatchling.build"
|
|
66
|
+
|
|
67
|
+
[tool.semantic_release]
|
|
68
|
+
branch = "main"
|
|
69
|
+
version_toml = ["pyproject.toml:tool.poetry.version"]
|
|
70
|
+
build_command = "pip install poetry && poetry build"
|
|
71
|
+
|
|
72
|
+
[tool.semantic_release.commit_parser_options]
|
|
73
|
+
allowed_tags = [
|
|
74
|
+
"chore",
|
|
75
|
+
"docs",
|
|
76
|
+
"feat",
|
|
77
|
+
"fix",
|
|
78
|
+
"refactor"
|
|
79
|
+
]
|
|
80
|
+
major_tags= ["refactor"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff]
|
|
83
|
+
lint.ignore = ["F403", "E741"]
|
|
84
|
+
lint.select=["E", "F", "UP", "I"]
|
|
85
|
+
line-length = 120
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint.per-file-ignores]
|
|
88
|
+
"*/__init__.py" = ["F401"]
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
asyncio_mode = "auto"
|
|
92
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
93
|
+
timeout = 30
|
|
94
|
+
log_format = "%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
|
95
|
+
|
|
96
|
+
[tool.uv]
|
|
97
|
+
dev-dependencies = [
|
|
98
|
+
"pyyaml>=6.0.3",
|
|
99
|
+
"pyshark>=0.6",
|
|
100
|
+
]
|
|
@@ -83,7 +83,7 @@ class RoborockClient(ABC):
|
|
|
83
83
|
if response == "unknown_method":
|
|
84
84
|
raise UnknownMethodError("Unknown method")
|
|
85
85
|
return response
|
|
86
|
-
except (
|
|
86
|
+
except (TimeoutError, asyncio.CancelledError):
|
|
87
87
|
raise RoborockTimeout(f"id={request_id} Timeout after {self.queue_timeout} seconds") from None
|
|
88
88
|
finally:
|
|
89
89
|
self._waiting_queue.pop(request_id, None)
|
|
@@ -45,6 +45,12 @@ class WaterModes(RoborockModeEnum):
|
|
|
45
45
|
CUSTOM = ("custom_water_flow", 207)
|
|
46
46
|
EXTREME = ("extreme", 208)
|
|
47
47
|
SMART_MODE = ("smart_mode", 209)
|
|
48
|
+
PURE_WATER_FLOW_START = ("slight", 221)
|
|
49
|
+
PURE_WATER_FLOW_SMALL = ("low", 225)
|
|
50
|
+
PURE_WATER_FLOW_MIDDLE = ("medium", 235)
|
|
51
|
+
PURE_WATER_FLOW_LARGE = ("moderate", 245)
|
|
52
|
+
PURE_WATER_SUPER_BEGIN = ("high", 248)
|
|
53
|
+
PURE_WATER_FLOW_END = ("extreme", 250)
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
class WashTowelModes(RoborockModeEnum):
|
|
@@ -112,6 +118,18 @@ def get_clean_routes(features: DeviceFeatures, region: str) -> list[CleanRoutes]
|
|
|
112
118
|
|
|
113
119
|
def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
|
|
114
120
|
"""Get the valid water modes for the device - also known as 'water flow' or 'water level'"""
|
|
121
|
+
# If the device supports water slide mode, it uses a completely different set of modes. Technically, it can even
|
|
122
|
+
# support values in between. But for now we will just support the main values.
|
|
123
|
+
if features.is_water_slide_mode_supported:
|
|
124
|
+
return [
|
|
125
|
+
WaterModes.PURE_WATER_FLOW_START,
|
|
126
|
+
WaterModes.PURE_WATER_FLOW_SMALL,
|
|
127
|
+
WaterModes.PURE_WATER_FLOW_MIDDLE,
|
|
128
|
+
WaterModes.PURE_WATER_FLOW_LARGE,
|
|
129
|
+
WaterModes.PURE_WATER_SUPER_BEGIN,
|
|
130
|
+
WaterModes.PURE_WATER_FLOW_END,
|
|
131
|
+
]
|
|
132
|
+
|
|
115
133
|
supported_modes = [WaterModes.OFF]
|
|
116
134
|
if features.is_mop_shake_module_supported:
|
|
117
135
|
# For mops that have the vibrating mop pad, they do mild standard intense
|
|
@@ -131,6 +149,15 @@ def get_water_modes(features: DeviceFeatures) -> list[WaterModes]:
|
|
|
131
149
|
return supported_modes
|
|
132
150
|
|
|
133
151
|
|
|
152
|
+
def is_mode_customized(clean_mode: VacuumModes, water_mode: WaterModes, mop_mode: CleanRoutes) -> bool:
|
|
153
|
+
"""Check if any of the cleaning modes are set to a custom value."""
|
|
154
|
+
return (
|
|
155
|
+
clean_mode == VacuumModes.CUSTOMIZED
|
|
156
|
+
or water_mode == WaterModes.CUSTOMIZED
|
|
157
|
+
or mop_mode == CleanRoutes.CUSTOMIZED
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
134
161
|
def is_smart_mode_set(water_mode: WaterModes, clean_mode: VacuumModes, mop_mode: CleanRoutes) -> bool:
|
|
135
162
|
"""Check if the smart mode is set for the given water mode and clean mode"""
|
|
136
163
|
return (
|
|
@@ -41,8 +41,9 @@ from pyshark import FileCapture # type: ignore
|
|
|
41
41
|
from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException # type: ignore
|
|
42
42
|
from pyshark.packet.packet import Packet # type: ignore
|
|
43
43
|
|
|
44
|
-
from roborock import SHORT_MODEL_TO_ENUM,
|
|
45
|
-
from roborock.containers import DeviceData, HomeData, NetworkInfo, RoborockBase, UserData
|
|
44
|
+
from roborock import SHORT_MODEL_TO_ENUM, RoborockCommand
|
|
45
|
+
from roborock.containers import CombinedMapInfo, DeviceData, HomeData, NetworkInfo, RoborockBase, UserData
|
|
46
|
+
from roborock.device_features import DeviceFeatures
|
|
46
47
|
from roborock.devices.cache import Cache, CacheData
|
|
47
48
|
from roborock.devices.device import RoborockDevice
|
|
48
49
|
from roborock.devices.device_manager import DeviceManager, create_device_manager, create_home_data_api
|
|
@@ -50,6 +51,7 @@ from roborock.devices.traits import Trait
|
|
|
50
51
|
from roborock.devices.traits.v1 import V1TraitMixin
|
|
51
52
|
from roborock.devices.traits.v1.consumeable import ConsumableAttribute
|
|
52
53
|
from roborock.devices.traits.v1.map_content import MapContentTrait
|
|
54
|
+
from roborock.exceptions import RoborockException, RoborockUnsupportedFeature
|
|
53
55
|
from roborock.protocol import MessageParser
|
|
54
56
|
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
|
55
57
|
from roborock.web_api import RoborockApiClient
|
|
@@ -116,6 +118,7 @@ class ConnectionCache(RoborockBase):
|
|
|
116
118
|
email: str
|
|
117
119
|
home_data: HomeData | None = None
|
|
118
120
|
network_info: dict[str, NetworkInfo] | None = None
|
|
121
|
+
home_cache: dict[int, CombinedMapInfo] | None = None
|
|
119
122
|
|
|
120
123
|
|
|
121
124
|
class DeviceConnectionManager:
|
|
@@ -258,14 +261,21 @@ class RoborockContext(Cache):
|
|
|
258
261
|
|
|
259
262
|
async def get(self) -> CacheData:
|
|
260
263
|
"""Get cached value."""
|
|
264
|
+
_LOGGER.debug("Getting cache data")
|
|
261
265
|
connection_cache = self.cache_data()
|
|
262
|
-
return CacheData(
|
|
266
|
+
return CacheData(
|
|
267
|
+
home_data=connection_cache.home_data,
|
|
268
|
+
network_info=connection_cache.network_info or {},
|
|
269
|
+
home_cache=connection_cache.home_cache,
|
|
270
|
+
)
|
|
263
271
|
|
|
264
272
|
async def set(self, value: CacheData) -> None:
|
|
265
273
|
"""Set value in the cache."""
|
|
274
|
+
_LOGGER.debug("Setting cache data")
|
|
266
275
|
connection_cache = self.cache_data()
|
|
267
276
|
connection_cache.home_data = value.home_data
|
|
268
277
|
connection_cache.network_info = value.network_info
|
|
278
|
+
connection_cache.home_cache = value.home_cache
|
|
269
279
|
self.update(connection_cache)
|
|
270
280
|
|
|
271
281
|
|
|
@@ -392,14 +402,21 @@ async def _v1_trait(context: RoborockContext, device_id: str, display_func: Call
|
|
|
392
402
|
device = await device_manager.get_device(device_id)
|
|
393
403
|
if device.v1_properties is None:
|
|
394
404
|
raise RoborockException(f"Device {device.name} does not support V1 protocol")
|
|
395
|
-
|
|
405
|
+
await device.v1_properties.discover_features()
|
|
396
406
|
trait = display_func(device.v1_properties)
|
|
397
407
|
await trait.refresh()
|
|
398
408
|
return trait
|
|
399
409
|
|
|
400
410
|
|
|
401
411
|
async def _display_v1_trait(context: RoborockContext, device_id: str, display_func: Callable[[], Trait]) -> None:
|
|
402
|
-
|
|
412
|
+
try:
|
|
413
|
+
trait = await _v1_trait(context, device_id, display_func)
|
|
414
|
+
except RoborockUnsupportedFeature:
|
|
415
|
+
click.echo("Feature not supported by device")
|
|
416
|
+
return
|
|
417
|
+
except RoborockException as e:
|
|
418
|
+
click.echo(f"Error: {e}")
|
|
419
|
+
return
|
|
403
420
|
click.echo(dump_json(trait.as_dict()))
|
|
404
421
|
|
|
405
422
|
|
|
@@ -523,6 +540,116 @@ async def reset_consumable(ctx, device_id: str, consumable: str):
|
|
|
523
540
|
click.echo(f"Reset {consumable} for device {device_id}")
|
|
524
541
|
|
|
525
542
|
|
|
543
|
+
@session.command()
|
|
544
|
+
@click.option("--device_id", required=True)
|
|
545
|
+
@click.option("--enabled", type=bool, help="Enable (True) or disable (False) the child lock.")
|
|
546
|
+
@click.pass_context
|
|
547
|
+
@async_command
|
|
548
|
+
async def child_lock(ctx, device_id: str, enabled: bool | None):
|
|
549
|
+
"""Get device child lock status."""
|
|
550
|
+
context: RoborockContext = ctx.obj
|
|
551
|
+
try:
|
|
552
|
+
trait = await _v1_trait(context, device_id, lambda v1: v1.child_lock)
|
|
553
|
+
except RoborockUnsupportedFeature:
|
|
554
|
+
click.echo("Feature not supported by device")
|
|
555
|
+
return
|
|
556
|
+
if enabled is not None:
|
|
557
|
+
if enabled:
|
|
558
|
+
await trait.enable()
|
|
559
|
+
else:
|
|
560
|
+
await trait.disable()
|
|
561
|
+
click.echo(f"Set child lock to {enabled} for device {device_id}")
|
|
562
|
+
await trait.refresh()
|
|
563
|
+
|
|
564
|
+
click.echo(dump_json(trait.as_dict()))
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@session.command()
|
|
568
|
+
@click.option("--device_id", required=True)
|
|
569
|
+
@click.option("--enabled", type=bool, help="Enable (True) or disable (False) the DND status.")
|
|
570
|
+
@click.pass_context
|
|
571
|
+
@async_command
|
|
572
|
+
async def dnd(ctx, device_id: str, enabled: bool | None):
|
|
573
|
+
"""Get Do Not Disturb Timer status."""
|
|
574
|
+
context: RoborockContext = ctx.obj
|
|
575
|
+
try:
|
|
576
|
+
trait = await _v1_trait(context, device_id, lambda v1: v1.dnd)
|
|
577
|
+
except RoborockUnsupportedFeature:
|
|
578
|
+
click.echo("Feature not supported by device")
|
|
579
|
+
return
|
|
580
|
+
if enabled is not None:
|
|
581
|
+
if enabled:
|
|
582
|
+
await trait.enable()
|
|
583
|
+
else:
|
|
584
|
+
await trait.disable()
|
|
585
|
+
click.echo(f"Set DND to {enabled} for device {device_id}")
|
|
586
|
+
await trait.refresh()
|
|
587
|
+
|
|
588
|
+
click.echo(dump_json(trait.as_dict()))
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@session.command()
|
|
592
|
+
@click.option("--device_id", required=True)
|
|
593
|
+
@click.option("--enabled", required=False, type=bool, help="Enable (True) or disable (False) the Flow LED.")
|
|
594
|
+
@click.pass_context
|
|
595
|
+
@async_command
|
|
596
|
+
async def flow_led_status(ctx, device_id: str, enabled: bool | None):
|
|
597
|
+
"""Get device Flow LED status."""
|
|
598
|
+
context: RoborockContext = ctx.obj
|
|
599
|
+
try:
|
|
600
|
+
trait = await _v1_trait(context, device_id, lambda v1: v1.flow_led_status)
|
|
601
|
+
except RoborockUnsupportedFeature:
|
|
602
|
+
click.echo("Feature not supported by device")
|
|
603
|
+
return
|
|
604
|
+
if enabled is not None:
|
|
605
|
+
if enabled:
|
|
606
|
+
await trait.enable()
|
|
607
|
+
else:
|
|
608
|
+
await trait.disable()
|
|
609
|
+
click.echo(f"Set Flow LED to {enabled} for device {device_id}")
|
|
610
|
+
await trait.refresh()
|
|
611
|
+
|
|
612
|
+
click.echo(dump_json(trait.as_dict()))
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
@session.command()
|
|
616
|
+
@click.option("--device_id", required=True)
|
|
617
|
+
@click.option("--enabled", required=False, type=bool, help="Enable (True) or disable (False) the LED.")
|
|
618
|
+
@click.pass_context
|
|
619
|
+
@async_command
|
|
620
|
+
async def led_status(ctx, device_id: str, enabled: bool | None):
|
|
621
|
+
"""Get device LED status."""
|
|
622
|
+
context: RoborockContext = ctx.obj
|
|
623
|
+
try:
|
|
624
|
+
trait = await _v1_trait(context, device_id, lambda v1: v1.led_status)
|
|
625
|
+
except RoborockUnsupportedFeature:
|
|
626
|
+
click.echo("Feature not supported by device")
|
|
627
|
+
return
|
|
628
|
+
if enabled is not None:
|
|
629
|
+
if enabled:
|
|
630
|
+
await trait.enable()
|
|
631
|
+
else:
|
|
632
|
+
await trait.disable()
|
|
633
|
+
click.echo(f"Set LED Status to {enabled} for device {device_id}")
|
|
634
|
+
await trait.refresh()
|
|
635
|
+
|
|
636
|
+
click.echo(dump_json(trait.as_dict()))
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@session.command()
|
|
640
|
+
@click.option("--device_id", required=True)
|
|
641
|
+
@click.option("--enabled", required=True, type=bool, help="Enable (True) or disable (False) the child lock.")
|
|
642
|
+
@click.pass_context
|
|
643
|
+
@async_command
|
|
644
|
+
async def set_child_lock(ctx, device_id: str, enabled: bool):
|
|
645
|
+
"""Set the child lock status."""
|
|
646
|
+
context: RoborockContext = ctx.obj
|
|
647
|
+
trait = await _v1_trait(context, device_id, lambda v1: v1.child_lock)
|
|
648
|
+
await trait.set_child_lock(enabled)
|
|
649
|
+
status = "enabled" if enabled else "disabled"
|
|
650
|
+
click.echo(f"Child lock {status} for device {device_id}")
|
|
651
|
+
|
|
652
|
+
|
|
526
653
|
@session.command()
|
|
527
654
|
@click.option("--device_id", required=True)
|
|
528
655
|
@click.pass_context
|
|
@@ -533,6 +660,52 @@ async def rooms(ctx, device_id: str):
|
|
|
533
660
|
await _display_v1_trait(context, device_id, lambda v1: v1.rooms)
|
|
534
661
|
|
|
535
662
|
|
|
663
|
+
@session.command()
|
|
664
|
+
@click.option("--device_id", required=True)
|
|
665
|
+
@click.pass_context
|
|
666
|
+
@async_command
|
|
667
|
+
async def features(ctx, device_id: str):
|
|
668
|
+
"""Get device room mapping info."""
|
|
669
|
+
context: RoborockContext = ctx.obj
|
|
670
|
+
await _display_v1_trait(context, device_id, lambda v1: v1.device_features)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
@session.command()
|
|
674
|
+
@click.option("--device_id", required=True)
|
|
675
|
+
@click.option("--refresh", is_flag=True, default=False, help="Refresh status before discovery.")
|
|
676
|
+
@click.pass_context
|
|
677
|
+
@async_command
|
|
678
|
+
async def home(ctx, device_id: str, refresh: bool):
|
|
679
|
+
"""Discover and cache home layout (maps and rooms)."""
|
|
680
|
+
context: RoborockContext = ctx.obj
|
|
681
|
+
device_manager = await context.get_device_manager()
|
|
682
|
+
device = await device_manager.get_device(device_id)
|
|
683
|
+
if device.v1_properties is None:
|
|
684
|
+
raise RoborockException(f"Device {device.name} does not support V1 protocol")
|
|
685
|
+
|
|
686
|
+
# Ensure we have the latest status before discovery
|
|
687
|
+
await device.v1_properties.status.refresh()
|
|
688
|
+
|
|
689
|
+
home_trait = device.v1_properties.home
|
|
690
|
+
await home_trait.discover_home()
|
|
691
|
+
if refresh:
|
|
692
|
+
await home_trait.refresh()
|
|
693
|
+
|
|
694
|
+
# Display the discovered home cache
|
|
695
|
+
if home_trait.home_cache:
|
|
696
|
+
cache_summary = {
|
|
697
|
+
map_flag: {
|
|
698
|
+
"name": map_data.name,
|
|
699
|
+
"room_count": len(map_data.rooms),
|
|
700
|
+
"rooms": [{"segment_id": room.segment_id, "name": room.name} for room in map_data.rooms],
|
|
701
|
+
}
|
|
702
|
+
for map_flag, map_data in home_trait.home_cache.items()
|
|
703
|
+
}
|
|
704
|
+
click.echo(dump_json(cache_summary))
|
|
705
|
+
else:
|
|
706
|
+
click.echo("No maps discovered")
|
|
707
|
+
|
|
708
|
+
|
|
536
709
|
@click.command()
|
|
537
710
|
@click.option("--device_id", required=True)
|
|
538
711
|
@click.option("--cmd", required=True)
|
|
@@ -541,21 +714,14 @@ async def rooms(ctx, device_id: str):
|
|
|
541
714
|
@async_command
|
|
542
715
|
async def command(ctx, cmd, device_id, params):
|
|
543
716
|
context: RoborockContext = ctx.obj
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
)
|
|
553
|
-
if model is None:
|
|
554
|
-
raise RoborockException(f"Could not find model for device {device.name}")
|
|
555
|
-
device_info = DeviceData(device=device, model=model)
|
|
556
|
-
mqtt_client = RoborockMqttClientV1(cache_data.user_data, device_info)
|
|
557
|
-
await mqtt_client.send_command(cmd, json.loads(params) if params is not None else None)
|
|
558
|
-
await mqtt_client.async_release()
|
|
717
|
+
device_manager = await context.get_device_manager()
|
|
718
|
+
device = await device_manager.get_device(device_id)
|
|
719
|
+
if device.v1_properties is None:
|
|
720
|
+
raise RoborockException(f"Device {device.name} does not support V1 protocol")
|
|
721
|
+
command_trait: Trait = device.v1_properties.command
|
|
722
|
+
result = await command_trait.send(cmd, json.loads(params) if params is not None else None)
|
|
723
|
+
if result:
|
|
724
|
+
click.echo(dump_json(result))
|
|
559
725
|
|
|
560
726
|
|
|
561
727
|
@click.command()
|
|
@@ -780,6 +946,12 @@ cli.add_command(map_data)
|
|
|
780
946
|
cli.add_command(consumables)
|
|
781
947
|
cli.add_command(reset_consumable)
|
|
782
948
|
cli.add_command(rooms)
|
|
949
|
+
cli.add_command(home)
|
|
950
|
+
cli.add_command(features)
|
|
951
|
+
cli.add_command(child_lock)
|
|
952
|
+
cli.add_command(dnd)
|
|
953
|
+
cli.add_command(flow_led_status)
|
|
954
|
+
cli.add_command(led_status)
|
|
783
955
|
|
|
784
956
|
|
|
785
957
|
def main():
|
|
@@ -768,6 +768,40 @@ class RoborockStartType(RoborockEnum):
|
|
|
768
768
|
smart_watch = 821
|
|
769
769
|
|
|
770
770
|
|
|
771
|
+
class RoborockDssCodes(RoborockEnum):
|
|
772
|
+
@classmethod
|
|
773
|
+
def _missing_(cls: type[RoborockEnum], key) -> RoborockEnum:
|
|
774
|
+
# If the calculated value is not provided, then it should be viewed as okay.
|
|
775
|
+
# As the math will sometimes result in you getting numbers that don't matter.
|
|
776
|
+
return cls.okay # type: ignore
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
class ClearWaterBoxStatus(RoborockDssCodes):
|
|
780
|
+
"""Status of the clear water box."""
|
|
781
|
+
|
|
782
|
+
okay = 0
|
|
783
|
+
out_of_water = 1
|
|
784
|
+
out_of_water_2 = 38
|
|
785
|
+
refill_error = 48
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
class DirtyWaterBoxStatus(RoborockDssCodes):
|
|
789
|
+
"""Status of the dirty water box."""
|
|
790
|
+
|
|
791
|
+
okay = 0
|
|
792
|
+
full_not_installed = 1
|
|
793
|
+
full_not_installed_2 = 39
|
|
794
|
+
drain_error = 49
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
class DustBagStatus(RoborockDssCodes):
|
|
798
|
+
"""Status of the dust bag."""
|
|
799
|
+
|
|
800
|
+
okay = 0
|
|
801
|
+
not_installed = 1
|
|
802
|
+
full = 34
|
|
803
|
+
|
|
804
|
+
|
|
771
805
|
class DyadSelfCleanMode(RoborockEnum):
|
|
772
806
|
self_clean = 1
|
|
773
807
|
self_clean_and_dry = 2
|