Skip to content

Commit af739a3

Browse files
committed
Merge branch 'dev'
2 parents 642e696 + 8e1b7aa commit af739a3

File tree

132 files changed

+3447
-1753
lines changed

Some content is hidden

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

132 files changed

+3447
-1753
lines changed

.coveragerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ source = deebot_client
44

55
omit =
66
tests/*
7+
8+
[report]
9+
exclude_lines =
10+
pragma: no cover
11+
if TYPE_CHECKING:

.devcontainer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Configure properties specific to VS Code.
66
"vscode": {
77
"extensions": [
8+
"eamodio.gitlens",
89
"github.vscode-pull-request-github",
910
"ms-python.python",
1011
"ms-python.vscode-pylance",
@@ -61,7 +62,7 @@
6162
// Use 'forwardPorts' to make a list of ports inside the container available locally.
6263
// "forwardPorts": [],
6364
// Use 'postCreateCommand' to run commands after the container is created.
64-
"postCreateCommand": "pip install -r requirements-dev.txt && pre-commit install && pip install -e .",
65+
"postStartCommand": "pip install -r requirements-dev.txt && pre-commit install && pip install -e .",
6566
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
6667
"remoteUser": "vscode",
6768
"runArgs": ["-e", "GIT_EDITOR=code --wait"]

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ body:
4141
validations:
4242
required: true
4343
attributes:
44-
label: On which deebot vacuum you have the issue?
44+
label: On which deebot device (vacuum) you have the issue?
4545
placeholder: Deebot Ozmo 950
4646
- type: input
4747
id: version

.github/workflows/ci.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ on:
44
push:
55
branches:
66
- main
7+
- dev
78
pull_request:
8-
branches:
9-
- main
109

1110
env:
1211
DEFAULT_PYTHON: "3.11"
@@ -16,10 +15,10 @@ jobs:
1615
runs-on: "ubuntu-latest"
1716
name: Check code quality
1817
steps:
19-
- uses: "actions/checkout@v3"
18+
- uses: "actions/checkout@v4"
2019
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
2120
id: python
22-
uses: actions/[email protected].0
21+
uses: actions/[email protected].1
2322
with:
2423
python-version: ${{ env.DEFAULT_PYTHON }}
2524
cache: "pip"
@@ -40,10 +39,10 @@ jobs:
4039
runs-on: "ubuntu-latest"
4140
name: Run tests
4241
steps:
43-
- uses: "actions/checkout@v3"
42+
- uses: "actions/checkout@v4"
4443
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
4544
id: python
46-
uses: actions/[email protected].0
45+
uses: actions/[email protected].1
4746
with:
4847
python-version: ${{ env.DEFAULT_PYTHON }}
4948
cache: "pip"

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ on:
1515
push:
1616
branches:
1717
- main
18+
- dev
1819
pull_request:
19-
# The branches below must be a subset of the branches above
20-
branches:
21-
- main
2220
schedule:
2321
- cron: "20 10 * * 0"
2422

@@ -41,7 +39,7 @@ jobs:
4139

4240
steps:
4341
- name: Checkout repository
44-
uses: actions/checkout@v3
42+
uses: actions/checkout@v4
4543

4644
# Initializes the CodeQL tools for scanning.
4745
- name: Initialize CodeQL

.github/workflows/python-publish.yml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,20 @@ jobs:
2020
id-token: write
2121
steps:
2222
- name: 📥 Checkout the repository
23-
uses: actions/checkout@v3
24-
25-
- name: 🔢 Get release version
26-
id: version
27-
uses: home-assistant/actions/helpers/version@master
28-
29-
- name: 🖊️ Set version number
30-
run: |
31-
sed -i '/version=/c\ version="${{ steps.version.outputs.version }}",' "${{ github.workspace }}/setup.py"
23+
uses: actions/checkout@v4
3224

3325
- name: Set up Python
34-
uses: actions/[email protected].0
26+
uses: actions/[email protected].1
3527
with:
3628
python-version: "3.11"
3729

3830
- name: Install dependencies
3931
run: |
4032
python -m pip install --upgrade pip
41-
pip install setuptools wheel
33+
pip install -q build
4234
4335
- name: 📦 Build package
44-
run: python setup.py sdist bdist_wheel
36+
run: python -m build
4537

4638
- name: 📤 Publish package
4739
uses: pypa/gh-action-pypi-publish@release/v1

.pre-commit-config.yaml

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,26 @@ default_language_version:
99
python: python3.11
1010

1111
repos:
12+
- repo: https://github.com/astral-sh/ruff-pre-commit
13+
rev: v0.1.1
14+
hooks:
15+
- id: ruff
16+
args:
17+
- --fix
1218
- repo: https://github.com/asottile/pyupgrade
13-
rev: v3.9.0
19+
rev: v3.15.0
1420
hooks:
1521
- id: pyupgrade
1622
args:
1723
- --py311-plus
18-
- repo: https://github.com/psf/black
19-
rev: 23.7.0
24+
- repo: https://github.com/psf/black-pre-commit-mirror
25+
rev: 23.10.0
2026
hooks:
2127
- id: black
2228
args:
2329
- --quiet
2430
- repo: https://github.com/codespell-project/codespell
25-
rev: v2.2.5
31+
rev: v2.2.6
2632
hooks:
2733
- id: codespell
2834
args:
@@ -32,40 +38,22 @@ repos:
3238
exclude_types:
3339
- csv
3440
- json
35-
- repo: https://github.com/PyCQA/flake8
36-
rev: 6.0.0
37-
hooks:
38-
- id: flake8
39-
additional_dependencies:
40-
- flake8-docstrings==1.6.0
41-
- pydocstyle==6.1.1
42-
- repo: https://github.com/PyCQA/bandit
43-
rev: 1.7.5
44-
hooks:
45-
- id: bandit
46-
args:
47-
- --quiet
48-
- --format=custom
49-
- --configfile=bandit.yaml
50-
- repo: https://github.com/PyCQA/isort
51-
rev: 5.12.0
52-
hooks:
53-
- id: isort
5441
- repo: https://github.com/pre-commit/pre-commit-hooks
55-
rev: v4.4.0
42+
rev: v4.5.0
5643
hooks:
5744
- id: check-executables-have-shebangs
5845
- id: check-merge-conflict
5946
- id: detect-private-key
6047
- id: no-commit-to-branch
48+
args: [--branch, main, --branch, dev]
6149
- id: requirements-txt-fixer
6250
- repo: https://github.com/pre-commit/mirrors-prettier
63-
rev: v3.0.0
51+
rev: v3.0.3
6452
hooks:
6553
- id: prettier
6654
additional_dependencies:
67-
- prettier@2.8.8
68-
- prettier-plugin-sort-json@1.0.0
55+
- prettier@3.0.3
56+
- prettier-plugin-sort-json@3.0.1
6957
exclude_types:
7058
- python
7159
- repo: https://github.com/adrienverge/yamllint.git

.prettierrc

Lines changed: 0 additions & 3 deletions
This file was deleted.

.prettierrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @type {import("prettier").Config} */
2+
module.exports = {
3+
plugins: [require.resolve("prettier-plugin-sort-json")],
4+
jsonRecursiveSort: true,
5+
};

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Client Library for Deebot Vacuums
1+
# Client Library for Deebot devices (Vacuums)
22

33
[![PyPI - Downloads](https://img.shields.io/pypi/dw/deebot-client?style=for-the-badge)](https://pypi.org/project/deebot-client)
44
<a href="https://www.buymeacoffee.com/edenhaus" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-black.png" width="150px" height="35px" alt="Buy Me A Coffee" style="height: 35px !important;width: 150px !important;" ></a>
@@ -31,7 +31,7 @@ from deebot_client.events import BatteryEvent
3131
from deebot_client.models import Configuration
3232
from deebot_client.mqtt_client import MqttClient, MqttConfiguration
3333
from deebot_client.util import md5
34-
from deebot_client.vacuum_bot import VacuumBot
34+
from deebot_client.device import Device
3535

3636
device_id = md5(str(time.time()))
3737
account_id = "your email or phonenumber (cn)"
@@ -52,7 +52,7 @@ async def main():
5252

5353
devices_ = await api_client.get_devices()
5454

55-
bot = VacuumBot(devices_[0], authenticator)
55+
bot = Device(devices_[0], authenticator)
5656

5757
mqtt_config = MqttConfiguration(config=config)
5858
mqtt = MqttClient(mqtt_config, authenticator)

bandit.yaml

Lines changed: 0 additions & 20 deletions
This file was deleted.

deebot_client/api_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""Api client module."""
22
from typing import Any
33

4+
from deebot_client.hardware.deebot import get_static_device_info
5+
46
from .authentication import Authenticator
57
from .const import PATH_API_APPSVR_APP, PATH_API_PIM_PRODUCT_IOT_MAP
68
from .exceptions import ApiError
79
from .logging_filter import get_logger
8-
from .models import DeviceInfo
10+
from .models import ApiDeviceInfo, DeviceInfo
911

1012
_LOGGER = get_logger(__name__)
1113

@@ -27,9 +29,11 @@ async def get_devices(self) -> list[DeviceInfo]:
2729

2830
if resp.get("code", None) == 0:
2931
devices: list[DeviceInfo] = []
32+
device: ApiDeviceInfo
3033
for device in resp["devices"]:
3134
if device.get("company") == "eco-ng":
32-
devices.append(DeviceInfo(device))
35+
static_device_info = get_static_device_info(device["class"])
36+
devices.append(DeviceInfo(device, static_device_info))
3337
else:
3438
_LOGGER.debug("Skipping device as it is not supported: %s", device)
3539
return devices

deebot_client/authentication.py

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Authentication module."""
22
import asyncio
3-
import time
43
from collections.abc import Callable, Coroutine, Mapping
4+
from http import HTTPStatus
5+
import time
56
from typing import Any
67
from urllib.parse import urljoin
78

@@ -16,9 +17,9 @@
1617
_LOGGER = get_logger(__name__)
1718

1819
_CLIENT_KEY = "1520391301804"
19-
_CLIENT_SECRET = "6c319b2a5cd3e66e39159c2e28f2fce9"
20+
_CLIENT_SECRET = "6c319b2a5cd3e66e39159c2e28f2fce9" # noqa: S105
2021
_AUTH_CLIENT_KEY = "1520391491841"
21-
_AUTH_CLIENT_SECRET = "77ef58ce3afbe337da74aa8c5ab963a9"
22+
_AUTH_CLIENT_SECRET = "77ef58ce3afbe337da74aa8c5ab963a9" # noqa: S105
2223
_USER_LOGIN_URL_FORMAT = (
2324
"https://gl-{country}-api.ecovacs.{tld}/v1/private/{country}/{lang}/{deviceId}/{appCode}/"
2425
"{appVersion}/{channel}/{deviceType}/user/login"
@@ -252,7 +253,7 @@ async def post(
252253
timeout=60,
253254
ssl=self._config.verify_ssl,
254255
) as res:
255-
if res.status == 200:
256+
if res.status == HTTPStatus.OK:
256257
response_data: dict[str, Any] = await res.json()
257258
_LOGGER.debug(
258259
"Success calling api %s, response=%s",
@@ -271,14 +272,14 @@ async def post(
271272
message=str(res.reason),
272273
headers=res.headers,
273274
)
274-
except asyncio.TimeoutError as ex:
275+
except TimeoutError as ex:
275276
_LOGGER.warning(
276277
"Timeout reached on api path: %s%s", path, json.get("cmdName", "")
277278
)
278279
raise ApiError("Timeout reached") from ex
279280
except ClientResponseError as ex:
280281
_LOGGER.debug("Error: %s", logger_requst_params, exc_info=True)
281-
if ex.status == 502:
282+
if ex.status == HTTPStatus.BAD_GATEWAY:
282283
seconds_to_sleep = 10
283284
_LOGGER.info(
284285
"Retry calling API due 502: Unfortunately the ecovacs api is unreliable. Retrying in %d seconds",
@@ -319,23 +320,19 @@ def __init__(
319320
async def authenticate(self, force: bool = False) -> Credentials:
320321
"""Authenticate on ecovacs servers."""
321322
async with self._lock:
322-
should_login = False
323-
if self._credentials is None or force:
324-
_LOGGER.debug("No cached credentials, performing login")
325-
should_login = True
326-
elif self._credentials.expires_at < time.time():
327-
_LOGGER.debug("Credentials have expired, performing login")
328-
should_login = True
329-
330-
if should_login:
323+
if (
324+
self._credentials is None
325+
or force
326+
or self._credentials.expires_at < time.time()
327+
):
328+
_LOGGER.debug("Performing login")
331329
self._credentials = await self._auth_client.login()
332330
self._cancel_refresh_task()
333-
self._create_refresh_task()
331+
self._create_refresh_task(self._credentials)
334332

335333
for on_changed in self._on_credentials_changed:
336334
create_task(self._tasks, on_changed(self._credentials))
337335

338-
assert self._credentials is not None
339336
return self._credentials
340337

341338
def subscribe(
@@ -375,7 +372,7 @@ def _cancel_refresh_task(self) -> None:
375372
if self._refresh_handle and not self._refresh_handle.cancelled():
376373
self._refresh_handle.cancel()
377374

378-
def _create_refresh_task(self) -> None:
375+
def _create_refresh_task(self, credentials: Credentials) -> None:
379376
# refresh at 99% of validity
380377
def refresh() -> None:
381378
_LOGGER.debug("Refresh token")
@@ -384,14 +381,11 @@ async def async_refresh() -> None:
384381
try:
385382
await self.authenticate(True)
386383
except Exception: # pylint: disable=broad-except
387-
_LOGGER.error(
388-
"An exception occurred during refreshing token", exc_info=True
389-
)
384+
_LOGGER.exception("An exception occurred during refreshing token")
390385

391386
create_task(self._tasks, async_refresh())
392387
self._refresh_handle = None
393388

394-
assert self._credentials is not None
395-
validity = (self._credentials.expires_at - time.time()) * 0.99
389+
validity = (credentials.expires_at - time.time()) * 0.99
396390

397391
self._refresh_handle = asyncio.get_event_loop().call_later(validity, refresh)

0 commit comments

Comments
 (0)