Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions roborock/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
from .code_mappings import *
from .containers import *
from .dyad import *
from .mower import *
from .v1 import *
from .zeo import *
3 changes: 3 additions & 0 deletions roborock/data/mower/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Dataclasses for Roborock mower devices."""

from .mower_containers import *
43 changes: 43 additions & 0 deletions roborock/data/mower/mower_containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Data containers for Roborock mower devices."""

from dataclasses import dataclass, field
from typing import Any

from roborock.data.containers import RoborockBase
from roborock.roborock_message import RoborockMowerDataProtocol


@dataclass
class MowerStatus(RoborockBase):
"""Core mower status backed by mower DPS updates."""

error_code: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.ERROR_CODE})
battery: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.BATTERY})
mow_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_TYPE})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i assume these will be enums in the future

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! Didn't want to get too carried away in the first PR (trying to keep it semi-manageable especially as I am just going off RR_API code). The ones that make sense to be enums will be enums

mow_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_STATE})
mapping_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MAPPING_TYPE})
mapping_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MAPPING_STATE})
ota_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.OTA_STATE})
charge_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.CHARGE_STATE})
dock_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.DOCK_STATE})
charge_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.CHARGE_TYPE})
pend_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.PEND_TYPE})
remote_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.REMOTE_STATE})
mow_start_type: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_START_TYPE})
mow_eff_mode: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_EFF_MODE})
mow_height: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_HEIGHT})
mow_direction_angle: int | None = field(
default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_DIRECTION_ANGLE}
)
mow_pattern: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_PATTERN})
mow_conf_mode: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_CONF_MODE})
offline_status: Any | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.OFFLINE_STATUS})
mow_progress: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.MOW_PROGRESS})
blade_lifespan: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.BLADE_LIFESPAN})
fc_state: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.FC_STATE})
gps_coordinate: Any | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.GPS_COORDINATE})
off_dock_no_task_status: int | None = field(
default=None, metadata={"dps": RoborockMowerDataProtocol.OFF_DOCK_NO_TASK_STATUS}
)
afs_status: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.AFS_STATUS})
network_channel: int | None = field(default=None, metadata={"dps": RoborockMowerDataProtocol.NETWORK_CHANNEL})
39 changes: 39 additions & 0 deletions roborock/roborock_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,45 @@ def _missing_(cls: type[Self], key) -> Self:
raise ValueError(f"{key} not a valid key for Data Protocol")


class RoborockMowerDataProtocol(RoborockEnum):
UNKNOWN = 0
ERROR_CODE = 120
BATTERY = 121
MOW_TYPE = 122
MOW_STATE = 123
MAPPING_TYPE = 124
MAPPING_STATE = 125
OTA_STATE = 126
CHARGE_STATE = 127
DOCK_STATE = 128
CHARGE_TYPE = 129
PEND_TYPE = 130
REMOTE_STATE = 131
MOW_START_TYPE = 132
MOW_EFF_MODE = 133
MOW_HEIGHT = 134
MOW_DIRECTION_ANGLE = 135
MOW_PATTERN = 136
MOW_CONF_MODE = 137
OFFLINE_STATUS = 138
MOW_PROGRESS = 139
BLADE_LIFESPAN = 140
FC_STATE = 141
GPS_COORDINATE = 142
OFF_DOCK_NO_TASK_STATUS = 143
AFS_STATUS = 144
NETWORK_CHANNEL = 145
START = 201
DOCK = 202
PAUSE = 203
RESUME = 204
STOP = 205

@classmethod
def _missing_(cls: type[Self], key) -> Self:
raise ValueError(f"{key} not a valid key for Mower Data Protocol")


class RoborockDyadDataProtocol(RoborockEnum):
DRYING_STATUS = 134
START = 200
Expand Down
74 changes: 74 additions & 0 deletions tests/data/test_mower_containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Test mower data containers."""

import dataclasses

from roborock.data import MowerStatus
from roborock.roborock_message import RoborockMowerDataProtocol


def test_mower_status_from_dict() -> None:
status = MowerStatus.from_dict(
{
"error_code": 0,
"battery": 82,
"mow_state": 3,
"mowHeight": 70,
"gpsCoordinate": {"latitude": 1, "longitude": 2},
"unknown": "ignored",
}
)

assert status.error_code == 0
assert status.battery == 82
assert status.mow_state == 3
assert status.mow_height == 70
assert status.gps_coordinate == {"latitude": 1, "longitude": 2}


def test_mower_status_dps_metadata() -> None:
dps_by_field = {
field.name: field.metadata["dps"]
for field in dataclasses.fields(MowerStatus)
if field.metadata and "dps" in field.metadata
}

assert dps_by_field == {
"error_code": RoborockMowerDataProtocol.ERROR_CODE,
"battery": RoborockMowerDataProtocol.BATTERY,
"mow_type": RoborockMowerDataProtocol.MOW_TYPE,
"mow_state": RoborockMowerDataProtocol.MOW_STATE,
"mapping_type": RoborockMowerDataProtocol.MAPPING_TYPE,
"mapping_state": RoborockMowerDataProtocol.MAPPING_STATE,
"ota_state": RoborockMowerDataProtocol.OTA_STATE,
"charge_state": RoborockMowerDataProtocol.CHARGE_STATE,
"dock_state": RoborockMowerDataProtocol.DOCK_STATE,
"charge_type": RoborockMowerDataProtocol.CHARGE_TYPE,
"pend_type": RoborockMowerDataProtocol.PEND_TYPE,
"remote_state": RoborockMowerDataProtocol.REMOTE_STATE,
"mow_start_type": RoborockMowerDataProtocol.MOW_START_TYPE,
"mow_eff_mode": RoborockMowerDataProtocol.MOW_EFF_MODE,
"mow_height": RoborockMowerDataProtocol.MOW_HEIGHT,
"mow_direction_angle": RoborockMowerDataProtocol.MOW_DIRECTION_ANGLE,
"mow_pattern": RoborockMowerDataProtocol.MOW_PATTERN,
"mow_conf_mode": RoborockMowerDataProtocol.MOW_CONF_MODE,
"offline_status": RoborockMowerDataProtocol.OFFLINE_STATUS,
"mow_progress": RoborockMowerDataProtocol.MOW_PROGRESS,
"blade_lifespan": RoborockMowerDataProtocol.BLADE_LIFESPAN,
"fc_state": RoborockMowerDataProtocol.FC_STATE,
"gps_coordinate": RoborockMowerDataProtocol.GPS_COORDINATE,
"off_dock_no_task_status": RoborockMowerDataProtocol.OFF_DOCK_NO_TASK_STATUS,
"afs_status": RoborockMowerDataProtocol.AFS_STATUS,
"network_channel": RoborockMowerDataProtocol.NETWORK_CHANNEL,
}


def test_mower_command_dps_are_not_status_fields() -> None:
status_dps = {
field.metadata["dps"] for field in dataclasses.fields(MowerStatus) if field.metadata and "dps" in field.metadata
}

assert RoborockMowerDataProtocol.START not in status_dps
assert RoborockMowerDataProtocol.DOCK not in status_dps
assert RoborockMowerDataProtocol.PAUSE not in status_dps
assert RoborockMowerDataProtocol.RESUME not in status_dps
assert RoborockMowerDataProtocol.STOP not in status_dps
Loading