Skip to content

Commit 647073f

Browse files
authored
Merge branch 'main' into dynamic_status_trait
2 parents ae7450e + 0372d41 commit 647073f

File tree

92 files changed

+5769
-827
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+5769
-827
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,15 @@ jobs:
4747
activate-environment: true
4848
- run: uv pip install pip
4949
- name: Test with Pytest
50-
run: uv run pytest --log-cli-level=DEBUG -vv -s
50+
run: uv run pytest --log-cli-level=DEBUG -vv -s --cov --cov-branch --cov-report=xml
5151
shell: bash
52+
- name: Upload results to Codecov
53+
uses: codecov/codecov-action@v5
54+
with:
55+
token: ${{ secrets.CODECOV_TOKEN }}
56+
slug: Python-roborock/python-roborock
57+
58+
5259

5360
build:
5461
name: Build Package
@@ -82,15 +89,15 @@ jobs:
8289
git config user.email "actions@github.com"
8390
- name: Test Semantic Release (No-op)
8491
id: test-release
85-
uses: python-semantic-release/python-semantic-release@v10.5.2
92+
uses: python-semantic-release/python-semantic-release@v10.5.3
8693
with:
8794
github_token: ${{ secrets.GITHUB_TOKEN }}
8895
changelog: true
8996
# Use noop mode to test without making changes
9097
no_operation_mode: true
9198
- name: Test Semantic Release (Dry Run)
9299
id: test-release-dry
93-
uses: python-semantic-release/python-semantic-release@v10.5.2
100+
uses: python-semantic-release/python-semantic-release@v10.5.3
94101
with:
95102
github_token: ${{ secrets.GITHUB_TOKEN }}
96103
changelog: true
@@ -122,7 +129,7 @@ jobs:
122129
persist-credentials: false
123130
- name: Python Semantic Release
124131
id: release
125-
uses: python-semantic-release/python-semantic-release@v10.5.2
132+
uses: python-semantic-release/python-semantic-release@v10.5.3
126133
with:
127134
github_token: ${{ secrets.GH_TOKEN }}
128135
changelog: true
@@ -138,7 +145,7 @@ jobs:
138145
verbose: true
139146

140147
- name: Publish package distributions to GitHub Releases
141-
uses: python-semantic-release/publish-action@v10.5.2
148+
uses: python-semantic-release/publish-action@v10.5.3
142149
if: steps.release.outputs.released == 'true'
143150
with:
144151
github_token: ${{ secrets.GITHUB_TOKEN }}

CHANGELOG.md

Lines changed: 767 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
</a>
77
<img src="https://img.shields.io/pypi/pyversions/python-roborock.svg?style=flat-square&logo=python&amp;logoColor=fff" alt="Supported Python versions">
88
<img src="https://img.shields.io/pypi/l/python-roborock.svg?style=flat-square" alt="License">
9+
<a href="https://codecov.io/github/Python-roborock/python-roborock" >
10+
<img src="https://codecov.io/github/Python-roborock/python-roborock/graph/badge.svg?token=KEK4S3FPSZ" alt="Code Coverage"/>
11+
</a>
912
</p>
1013

14+
1115
Roborock library for online and offline control of your vacuums.
1216

1317
## Installation
@@ -16,11 +20,13 @@ Install this via pip (or your favourite package manager):
1620

1721
`pip install python-roborock`
1822

19-
## Functionality
23+
## Example Usage
2024

21-
You can see all of the commands supported [here](https://python-roborock.readthedocs.io/en/latest/api_commands.html)
25+
See [examples/example.py](examples/example.py) for a more full featured example,
26+
or the [API documentation](https://python-roborock.github.io/python-roborock/)
27+
for more details.
2228

23-
## Example Usage
29+
Here is a basic example:
2430

2531
```python
2632
import asyncio
@@ -30,38 +36,45 @@ from roborock.devices.device_manager import create_device_manager, UserParams
3036

3137

3238
async def main():
33-
web_api = RoborockApiClient(username="youremailhere")
34-
# Login via your password
35-
user_data = await web_api.pass_login(password="pass_here")
36-
# Or login via a code
39+
email_address = "youremailhere@example.com"
40+
web_api = RoborockApiClient(username=email_address)
41+
# Send a login code to the above email address
3742
await web_api.request_code()
43+
# Prompt the user to enter the code
3844
code = input("What is the code?")
3945
user_data = await web_api.code_login(code)
4046

4147
# Create a device manager that can discover devices.
42-
user_params = UserParams(
43-
username="youremailhere",
44-
user_data=user_data,
45-
)
48+
user_params = UserParams(username=email_address, user_data=user_data)
4649
device_manager = await create_device_manager(user_params)
4750
devices = await device_manager.get_devices()
4851

49-
# Get all vacuum devices that support the v1 PropertiesApi
52+
# Get all vacuum devices. Each device generation has different capabilities
53+
# and APIs available so to find vacuums we filter by the v1 PropertiesApi.
5054
for device in devices:
5155
if not device.v1_properties:
5256
continue
5357

54-
# Refresh the current device status
58+
# The PropertiesAPI has traits different device commands such as getting
59+
# status, sending clean commands, etc. For this example we send a
60+
# command to refresh the current device status.
5561
status_trait = device.v1_properties.status
5662
await status_trait.refresh()
5763
print(status_trait)
5864

5965
asyncio.run(main())
6066
```
6167

62-
See [examples/example.py](examples/example.py) for a more full featured example
63-
that has performance improvements to cache cloud information to prefer
64-
connections over the local network.
68+
69+
## Functionality
70+
71+
The library interacts with devices through specific API properties based on the device protocol:
72+
73+
* **Standard Vacuums (V1 Protocol)**: Most robot vacuums use this. Interaction is done through `device.v1_properties`, which contains traits like `status`, `consumables`, and `maps`. Use the `command` trait for actions like starting or stopping cleaning.
74+
* **Wet/Dry Vacuums & Washing Machines (A01 Protocol)**: Devices like the Dyad and Zeo use this. Interaction is done through `device.a01_properties` using `query_values()` and `set_value()`.
75+
76+
You can find detailed documentation for [Devices](https://python-roborock.github.io/python-roborock/roborock/devices/device.html) and [Traits](https://python-roborock.github.io/python-roborock/roborock/devices/traits.html).
77+
6578

6679
## Supported devices
6780

@@ -70,6 +83,7 @@ You can find what devices are supported
7083
Please note this may not immediately contain the latest devices.
7184

7285

73-
## Credits
86+
## Acknowledgements
7487

75-
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
88+
* Thanks to [@rovo89](https://github.com/rovo89) for [Login APIs gist](https://gist.github.com/rovo89/dff47ed19fca0dfdda77503e66c2b7c7).
89+
* Thanks to [@PiotrMachowski](https://github.com/PiotrMachowski) for [Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor](https://github.com/PiotrMachowski/Home-Assistant-custom-components-Xiaomi-Cloud-Map-Extractor).

SUPPORTED_FEATURES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
| `is_carpet_shape_type_supported` | | | |
3939
| `is_carpet_show_on_map` | | X | |
4040
| `is_carpet_supported` | X | X | |
41-
| `is_ces2022_supported` | | | |
41+
| `is_ces_2022_supported` | | | |
4242
| `is_clean_count_setting_supported` | | X | |
4343
| `is_clean_direct_status_supported` | | | |
4444
| `is_clean_efficiency_supported` | | | |

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "python-roborock"
3-
version = "3.8.5"
3+
version = "3.19.0"
44
description = "A package to control Roborock vacuums."
55
authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
66
requires-python = ">=3.11, <4"
@@ -44,16 +44,17 @@ dev = [
4444
"pytest",
4545
"pre-commit>=3.5,<5.0",
4646
"mypy",
47-
"ruff==0.14.6",
47+
"ruff==0.14.9",
4848
"codespell",
4949
"pyshark>=0.6,<0.7",
5050
"aioresponses>=0.7.7,<0.8",
5151
"freezegun>=1.5.1,<2",
5252
"pytest-timeout>=2.3.1,<3",
53-
"syrupy>=4.9.1,<5",
54-
"pdoc>=15.0.4,<16",
53+
"syrupy>=4.9.1,<6",
54+
"pdoc>=15.0.4,<17",
5555
"pyyaml>=6.0.3",
5656
"pyshark>=0.6",
57+
"pytest-cov>=7.0.0",
5758
]
5859

5960
[tool.hatch.build.targets.sdist]

roborock/callbacks.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
V = TypeVar("V")
1111

1212

13-
def safe_callback(callback: Callable[[V], None], logger: logging.Logger | None = None) -> Callable[[V], None]:
13+
def safe_callback(
14+
callback: Callable[[V], None], logger: logging.Logger | logging.LoggerAdapter | None = None
15+
) -> Callable[[V], None]:
1416
"""Wrap a callback to catch and log exceptions.
1517
1618
This is useful for ensuring that errors in callbacks do not propagate
@@ -36,7 +38,7 @@ class CallbackMap(Generic[K, V]):
3638
when a value is received for a specific key.
3739
"""
3840

39-
def __init__(self, logger: logging.Logger | None = None) -> None:
41+
def __init__(self, logger: logging.Logger | logging.LoggerAdapter | None = None) -> None:
4042
self._callbacks: dict[K, list[Callable[[V], None]]] = {}
4143
self._logger = logger or _LOGGER
4244

@@ -79,7 +81,7 @@ class CallbackList(Generic[V]):
7981
additional callbacks to the list at any time.
8082
"""
8183

82-
def __init__(self, logger: logging.Logger | None = None) -> None:
84+
def __init__(self, logger: logging.Logger | logging.LoggerAdapter | None = None) -> None:
8385
self._callbacks: list[Callable[[V], None]] = []
8486
self._logger = logger or _LOGGER
8587

@@ -101,7 +103,9 @@ def __call__(self, value: V) -> None:
101103

102104

103105
def decoder_callback(
104-
decoder: Callable[[K], list[V]], callback: Callable[[V], None], logger: logging.Logger | None = None
106+
decoder: Callable[[K], list[V]],
107+
callback: Callable[[V], None],
108+
logger: logging.Logger | logging.LoggerAdapter | None = None,
105109
) -> Callable[[K], None]:
106110
"""Create a callback that decodes messages using a decoder and invokes a callback.
107111
@@ -120,7 +124,7 @@ def wrapper(data: K) -> None:
120124
logger.warning("Failed to decode message: %s", data)
121125
return
122126
for message in messages:
123-
_LOGGER.debug("Decoded message: %s", message)
127+
logger.debug("Decoded message: %s", message)
124128
safe_cb(message)
125129

126130
return wrapper

0 commit comments

Comments
 (0)