Skip to content

Commit

Permalink
Merge branch 'DeebotUniverse:dev' into feature/CommandOta
Browse files Browse the repository at this point in the history
  • Loading branch information
MVladislav authored Dec 6, 2023
2 parents 06a39d9 + 8c131fc commit 994fe61
Show file tree
Hide file tree
Showing 65 changed files with 697 additions and 316 deletions.
4 changes: 4 additions & 0 deletions .devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Configure properties specific to VS Code.
"vscode": {
"extensions": [
"charliermarsh.ruff",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"ms-python.python",
Expand All @@ -15,6 +16,9 @@
],
// Set *default* container specific settings.json values on container create.
"settings": {
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
},
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: "actions/checkout@v4"
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.1
uses: actions/setup-python@v5.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache: "pip"
Expand All @@ -42,7 +42,7 @@ jobs:
- uses: "actions/checkout@v4"
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v4.7.1
uses: actions/setup-python@v5.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache: "pip"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4.7.1
uses: actions/setup-python@v5.0.0
with:
python-version: "3.11"

Expand Down
11 changes: 4 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ci:
autofix_prs: false
skip:
# This steps run in the ci workflow. Keep in sync
- mypy
Expand All @@ -10,23 +11,19 @@ default_language_version:

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.5
rev: v0.1.6
hooks:
- id: ruff
args:
- --fix
# - --unsafe-fixes
- id: ruff-format
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
- id: pyupgrade
args:
- --py311-plus
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.11.0
hooks:
- id: black
args:
- --quiet
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
Expand Down
12 changes: 5 additions & 7 deletions deebot_client/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
class ApiClient:
"""Api client."""

def __init__(self, authenticator: Authenticator):
def __init__(self, authenticator: Authenticator) -> None:
self._authenticator = authenticator

async def get_devices(self) -> list[DeviceInfo]:
Expand All @@ -38,9 +38,8 @@ async def get_devices(self) -> list[DeviceInfo]:
_LOGGER.debug("Skipping device as it is not supported: %s", device)
return devices
_LOGGER.error("Failed to get devices: %s", resp)
raise ApiError(
f"failure {resp.get('error', '')} ({resp.get('errno', '')}) on getting devices"
)
msg = f"failure {resp.get('error', '')} ({resp.get('errno', '')}) on getting devices"
raise ApiError(msg)

async def get_product_iot_map(self) -> dict[str, Any]:
"""Get product iot map."""
Expand All @@ -55,6 +54,5 @@ async def get_product_iot_map(self) -> dict[str, Any]:
result[entry["classid"]] = entry["product"]
return result
_LOGGER.error("Failed to get product iot map")
raise ApiError(
f"failure {resp['error']} ({resp['errno']}) on getting product iot map"
)
msg = f"failure {resp['error']} ({resp['errno']}) on getting product iot map"
raise ApiError(msg)
18 changes: 9 additions & 9 deletions deebot_client/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
config: Configuration,
account_id: str,
password_hash: str,
):
) -> None:
self._config = config
self._account_id = account_id
self._password_hash = password_hash
Expand Down Expand Up @@ -107,17 +107,16 @@ async def __do_auth_response(
content_type = res.headers.get(hdrs.CONTENT_TYPE, "").lower()
json = await res.json(content_type=content_type)
_LOGGER.debug("got %s", json)
# todo better error handling # pylint: disable=fixme
# TODO better error handling # pylint: disable=fixme
if json["code"] == "0000":
data: dict[str, Any] = json["data"]
return data
if json["code"] in ["1005", "1010"]:
raise InvalidAuthenticationError

_LOGGER.error("call to %s failed with %s", url, json)
raise AuthenticationError(
f"failure code {json['code']} ({json['msg']}) for call {url}"
)
msg = f"failure code {json['code']} ({json['msg']}) for call {url}"
raise AuthenticationError(msg)

async def __call_login_api(
self, account_id: str, password_hash: str
Expand Down Expand Up @@ -204,9 +203,10 @@ async def __call_login_by_it_token(
continue

_LOGGER.error("call to %s failed with %s", _PATH_USERS_USER, resp)
raise AuthenticationError(
msg = (
f"failure {resp['error']} ({resp['errno']}) for call {_PATH_USERS_USER}"
)
raise AuthenticationError(msg)

raise AuthenticationError("failed to login with token")

Expand Down Expand Up @@ -302,7 +302,7 @@ def __init__(
config: Configuration,
account_id: str,
password_hash: str,
):
) -> None:
self._auth_client = _AuthClient(
config,
account_id,
Expand All @@ -317,7 +317,7 @@ def __init__(
self._refresh_handle: asyncio.TimerHandle | None = None
self._tasks: set[asyncio.Future[Any]] = set()

async def authenticate(self, force: bool = False) -> Credentials:
async def authenticate(self, *, force: bool = False) -> Credentials:
"""Authenticate on ecovacs servers."""
async with self._lock:
if (
Expand Down Expand Up @@ -379,7 +379,7 @@ def refresh() -> None:

async def async_refresh() -> None:
try:
await self.authenticate(True)
await self.authenticate(force=True)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("An exception occurred during refreshing token")

Expand Down
2 changes: 1 addition & 1 deletion deebot_client/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class CapabilityClean:
action: CapabilityCleanAction
continuous: CapabilitySetEnable[ContinuousCleaningEvent]
count: CapabilitySet[CleanCountEvent, int] | None = None
log: CapabilityEvent[CleanLogEvent]
log: CapabilityEvent[CleanLogEvent] | None = None
preference: CapabilitySetEnable[CleanPreferenceEvent] | None = None
work_mode: CapabilitySetTypes[WorkModeEvent, WorkMode] | None = None

Expand Down
15 changes: 8 additions & 7 deletions deebot_client/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from abc import ABC, abstractmethod
import asyncio
from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Any, final

from deebot_client.events import AvailabilityEvent
Expand Down Expand Up @@ -54,7 +55,7 @@ def name(cls) -> str:
@classmethod
@abstractmethod
def data_type(cls) -> DataType:
"""Data type.""" # noqa: D401
"""Data type."""

@abstractmethod
def _get_payload(self) -> dict[str, Any] | list[Any] | str:
Expand Down Expand Up @@ -214,7 +215,7 @@ def _handle_response(
_LOGGER.info(
'Device is offline. Could not execute command "%s"', self.name
)
event_bus.notify(AvailabilityEvent(False))
event_bus.notify(AvailabilityEvent(available=False))
return CommandResult(HandlingState.FAILED)
case 500:
if self._is_available_check:
Expand Down Expand Up @@ -244,7 +245,7 @@ class InitParam:
class CommandMqttP2P(Command, ABC):
"""Command which can handle mqtt p2p messages."""

_mqtt_params: dict[str, InitParam | None]
_mqtt_params: MappingProxyType[str, InitParam | None]

@abstractmethod
def handle_mqtt_p2p(self, event_bus: EventBus, response: dict[str, Any]) -> None:
Expand Down Expand Up @@ -274,13 +275,13 @@ def _pop_or_raise(name: str, type_: type, data: dict[str, Any]) -> Any:
try:
value = data.pop(name)
except KeyError as err:
raise DeebotError(f'"{name}" is missing in {data}') from err
msg = f'"{name}" is missing in {data}'
raise DeebotError(msg) from err
try:
return type_(value)
except ValueError as err:
raise DeebotError(
f'Could not convert "{value}" of {name} into {type_}'
) from err
msg = f'Could not convert "{value}" of {name} into {type_}'
raise DeebotError(msg) from err


class SetCommand(CommandWithMessageHandling, CommandMqttP2P, ABC):
Expand Down
5 changes: 3 additions & 2 deletions deebot_client/commands/json/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Commands module."""
"""Json commands module."""
from deebot_client.command import Command, CommandMqttP2P

from .advanced_mode import GetAdvancedMode, SetAdvancedMode
Expand Down Expand Up @@ -173,7 +173,8 @@
# fmt: on

COMMANDS: dict[str, type[Command]] = {
cmd.name: cmd for cmd in _COMMANDS # type: ignore[misc]
cmd.name: cmd # type: ignore[misc]
for cmd in _COMMANDS
}

COMMANDS_WITH_MQTT_P2P_HANDLING: dict[str, type[CommandMqttP2P]] = {
Expand Down
2 changes: 1 addition & 1 deletion deebot_client/commands/json/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ class GetBattery(OnBattery, JsonCommandWithMessageHandling):

name = "getBattery"

def __init__(self, is_available_check: bool = False) -> None:
def __init__(self, *, is_available_check: bool = False) -> None:
super().__init__()
self._is_available_check = is_available_check
1 change: 0 additions & 1 deletion deebot_client/commands/json/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ def _handle_body_data_dict(
:return: A message response
"""

status: State | None = None
state = data.get("state")
if data.get("trigger") == "alert":
Expand Down
4 changes: 2 additions & 2 deletions deebot_client/commands/json/clean_count.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Clean count command module."""

from types import MappingProxyType
from typing import Any

from deebot_client.command import InitParam
Expand All @@ -23,7 +24,6 @@ def _handle_body_data_dict(
:return: A message response
"""

event_bus.notify(CleanCountEvent(count=data["count"]))
return HandlingResult.success()

Expand All @@ -33,7 +33,7 @@ class SetCleanCount(JsonSetCommand):

name = "setCleanCount"
get_command = GetCleanCount
_mqtt_params = {"count": InitParam(int)}
_mqtt_params = MappingProxyType({"count": InitParam(int)})

def __init__(self, count: int) -> None:
super().__init__({"count": count})
9 changes: 5 additions & 4 deletions deebot_client/commands/json/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Base commands."""
from abc import ABC, abstractmethod
from datetime import datetime
from types import MappingProxyType
from typing import Any

from deebot_client.command import (
Expand Down Expand Up @@ -53,7 +54,7 @@ class ExecuteCommand(JsonCommandWithMessageHandling, ABC):
"""Command, which is executing something (ex. Charge)."""

@classmethod
def _handle_body(cls, event_bus: EventBus, body: dict[str, Any]) -> HandlingResult:
def _handle_body(cls, _: EventBus, body: dict[str, Any]) -> HandlingResult:
"""Handle message->body and notify the correct event subscribers.
:return: A message response
Expand Down Expand Up @@ -90,15 +91,15 @@ def _handle_body_data_dict(
:return: A message response
"""
event: EnableEvent = cls.event_type(bool(data["enable"])) # type: ignore
event: EnableEvent = cls.event_type(bool(data["enable"])) # type: ignore[call-arg, assignment]
event_bus.notify(event)
return HandlingResult.success()


class SetEnableCommand(JsonSetCommand, ABC):
"""Abstract set enable command."""

_mqtt_params = {"enable": InitParam(bool)}
_mqtt_params = MappingProxyType({"enable": InitParam(bool)})

def __init__(self, enable: bool) -> None:
def __init__(self, enable: bool) -> None: # noqa: FBT001
super().__init__({"enable": 1 if enable else 0})
3 changes: 2 additions & 1 deletion deebot_client/commands/json/efficiency.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Efficiency mode command module."""
from types import MappingProxyType
from typing import Any

from deebot_client.command import InitParam
Expand Down Expand Up @@ -31,7 +32,7 @@ class SetEfficiencyMode(JsonSetCommand):

name = "setEfficiency"
get_command = GetEfficiencyMode
_mqtt_params = {"efficiency": InitParam(EfficiencyMode)}
_mqtt_params = MappingProxyType({"efficiency": InitParam(EfficiencyMode)})

def __init__(self, efficiency: EfficiencyMode | str) -> None:
if isinstance(efficiency, str):
Expand Down
1 change: 1 addition & 0 deletions deebot_client/commands/json/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def _handle_body_data_dict(
205: "IonSterilizeExhausted",
206: "IonSterilizeAbnormal",
207: "IonSterilizeFault",
312: "Please replace the Dust Bag.",
404: "Recipient unavailable",
500: "Request Timeout",
601: "ERROR_ClosedAIVISideAbnormal",
Expand Down
3 changes: 2 additions & 1 deletion deebot_client/commands/json/fan_speed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""(fan) speed commands."""
from types import MappingProxyType
from typing import Any

from deebot_client.command import InitParam
Expand Down Expand Up @@ -31,7 +32,7 @@ class SetFanSpeed(JsonSetCommand):

name = "setSpeed"
get_command = GetFanSpeed
_mqtt_params = {"speed": InitParam(FanSpeedLevel)}
_mqtt_params = MappingProxyType({"speed": InitParam(FanSpeedLevel)})

def __init__(self, speed: FanSpeedLevel | str) -> None:
if isinstance(speed, str):
Expand Down
3 changes: 2 additions & 1 deletion deebot_client/commands/json/life_span.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Life span commands."""
from types import MappingProxyType
from typing import Any

from deebot_client.command import CommandMqttP2P, InitParam
Expand Down Expand Up @@ -45,7 +46,7 @@ class ResetLifeSpan(ExecuteCommand, CommandMqttP2P):
"""Reset life span command."""

name = "resetLifeSpan"
_mqtt_params = {"type": InitParam(LifeSpan, "life_span")}
_mqtt_params = MappingProxyType({"type": InitParam(LifeSpan, "life_span")})

def __init__(self, life_span: LifeSpan) -> None:
super().__init__({"type": life_span.value})
Expand Down
Loading

0 comments on commit 994fe61

Please sign in to comment.