Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enum for standardized vacuum identifier names #1732

Merged
merged 5 commits into from
Feb 11, 2023
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
26 changes: 19 additions & 7 deletions miio/devicestatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
SettingDescriptor,
SettingType,
)
from .identifiers import StandardIdentifier

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -153,7 +154,7 @@ def __cli_output__(self) -> str:
if isinstance(entry, SettingDescriptor):
out += "[RW] "

out += f"{entry.name}: {value}"
out += f"{entry.name} ({entry.id}): {value}"

if entry.unit is not None:
out += f" {entry.unit}"
Expand All @@ -180,8 +181,19 @@ def __getattr__(self, item):
return getattr(self._embedded[embed], prop)


def _get_qualified_name(func, id_: Optional[Union[str, StandardIdentifier]]):
"""Return qualified name for a descriptor identifier."""
if id_ is not None and isinstance(id_, StandardIdentifier):
return str(id_.value)
return id_ or str(func.__qualname__)


def sensor(
name: str, *, id: Optional[str] = None, unit: Optional[str] = None, **kwargs
name: str,
*,
id: Optional[Union[str, StandardIdentifier]] = None,
unit: Optional[str] = None,
**kwargs,
):
"""Syntactic sugar to create SensorDescriptor objects.

Expand All @@ -195,7 +207,7 @@ def sensor(

def decorator_sensor(func):
property_name = str(func.__name__)
qualified_name = id or str(func.__qualname__)
qualified_name = _get_qualified_name(func, id)

def _sensor_type_for_return_type(func):
rtype = get_type_hints(func).get("return")
Expand Down Expand Up @@ -223,7 +235,7 @@ def _sensor_type_for_return_type(func):
def setting(
name: str,
*,
id: Optional[str] = None,
id: Optional[Union[str, StandardIdentifier]] = None,
setter: Optional[Callable] = None,
setter_name: Optional[str] = None,
unit: Optional[str] = None,
Expand All @@ -250,7 +262,7 @@ def setting(

def decorator_setting(func):
property_name = str(func.__name__)
qualified_name = id or str(func.__qualname__)
qualified_name = _get_qualified_name(func, id)

if setter is None and setter_name is None:
raise Exception("setter_name needs to be defined")
Expand Down Expand Up @@ -293,7 +305,7 @@ def decorator_setting(func):
return decorator_setting


def action(name: str, *, id: Optional[str] = None, **kwargs):
def action(name: str, *, id: Optional[Union[str, StandardIdentifier]] = None, **kwargs):
"""Syntactic sugar to create ActionDescriptor objects.

The information can be used by users of the library to programmatically find out what
Expand All @@ -306,7 +318,7 @@ def action(name: str, *, id: Optional[str] = None, **kwargs):

def decorator_action(func):
property_name = str(func.__name__)
qualified_name = id or str(func.__qualname__)
qualified_name = _get_qualified_name(func, id)

descriptor = ActionDescriptor(
id=qualified_name,
Expand Down
28 changes: 28 additions & 0 deletions miio/identifiers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from enum import Enum


class StandardIdentifier(Enum):
"""Base class for standardized descriptor identifiers."""


class VacuumId(StandardIdentifier):
"""Vacuum-specific standardized descriptor identifiers.

TODO: this is a temporary solution, and might be named to 'Vacuum' later on.
"""

# Actions
Start = "vacuum:start-sweep"
Stop = "vacuum:stop-sweeping"
Pause = "vacuum:pause-sweeping"
ReturnHome = "battery:start-charge"
Locate = "identify:identify"

# Settings
FanSpeed = "vacuum:fan-speed" # TODO: invented name
FanSpeedPreset = "vacuum:mode"

# Sensors
State = "vacuum:status"
ErrorMessage = "vacuum:fault"
Battery = "battery:level"
17 changes: 10 additions & 7 deletions miio/integrations/viomi/viomi/viomivacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from miio.device import Device
from miio.devicestatus import action, sensor, setting
from miio.exceptions import DeviceException
from miio.identifiers import VacuumId
from miio.integrations.roborock.vacuum.vacuumcontainers import ( # TODO: remove roborock import
ConsumableStatus,
DNDStatus,
Expand Down Expand Up @@ -303,7 +304,7 @@ def __init__(self, data):
self.data = data

@property
@sensor("Vacuum state")
@sensor("Vacuum state", id=VacuumId.State)
def vacuum_state(self) -> VacuumState:
"""Return simplified vacuum state."""

Expand Down Expand Up @@ -364,7 +365,7 @@ def error_code(self) -> int:
return self.data["err_state"]

@property
@sensor("Error", icon="mdi:alert")
@sensor("Error", icon="mdi:alert", id=VacuumId.ErrorMessage)
def error(self) -> Optional[str]:
"""String presentation for the error code."""
if self.vacuum_state != VacuumState.Error:
Expand All @@ -373,7 +374,7 @@ def error(self) -> Optional[str]:
return ERROR_CODES.get(self.error_code, f"Unknown error {self.error_code}")

@property
@sensor("Battery", unit="%", device_class="battery")
@sensor("Battery", unit="%", device_class="battery", id=VacuumId.Battery)
def battery(self) -> int:
"""Battery in percentage."""
return self.data["battary_life"]
Expand Down Expand Up @@ -402,6 +403,7 @@ def clean_area(self) -> float:
choices=ViomiVacuumSpeed,
setter_name="set_fan_speed",
icon="mdi:fan",
id=VacuumId.FanSpeedPreset,
)
def fanspeed(self) -> ViomiVacuumSpeed:
"""Current fan speed."""
Expand Down Expand Up @@ -698,6 +700,7 @@ def status(self) -> ViomiVacuumStatus:
return status

@command()
@action("Return home", id=VacuumId.ReturnHome)
def home(self):
"""Return to home."""
self.send("set_charge", [1])
Expand All @@ -710,7 +713,7 @@ def set_power(self, on: bool):
return self.stop()

@command()
@action("Start cleaning")
@action("Start cleaning", id=VacuumId.Start)
def start(self):
"""Start cleaning."""
# params: [edge, 1, roomIds.length, *list_of_room_ids]
Expand Down Expand Up @@ -759,7 +762,7 @@ def start_with_room(self, rooms):
)

@command()
@action("Pause cleaning")
@action("Pause cleaning", id=VacuumId.Pause)
def pause(self):
"""Pause cleaning."""
# params: [edge_state, 0]
Expand All @@ -770,7 +773,7 @@ def pause(self):
self.send("set_mode", self._cache["edge_state"] + [2])

@command()
@action("Stop cleaning")
@action("Stop cleaning", id=VacuumId.Stop)
def stop(self):
"""Validate that Stop cleaning."""
# params: [edge_state, 0]
Expand Down Expand Up @@ -1078,7 +1081,7 @@ def carpet_mode(self, mode: ViomiCarpetTurbo):
return self.send("set_carpetturbo", [mode.value])

@command()
@action("Find robot")
@action("Find robot", id=VacuumId.Locate)
def find(self):
"""Find the robot."""
return self.send("set_resetpos", [1])
14 changes: 10 additions & 4 deletions miio/tests/test_devicestatus.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from enum import Enum

import pytest
Expand Down Expand Up @@ -321,7 +322,12 @@ def sensor_returning_none(self):
return None

status = Status()
assert (
status.__cli_output__
== "sensor_without_unit: 1\nsensor_with_unit: 2 V\n[RW] setting_without_unit: 3\n[RW] setting_with_unit: 4 V\n"
)
expected_regex = [
"sensor_without_unit (.+?): 1",
"sensor_with_unit (.+?): 2 V",
r"\[RW\] setting_without_unit (.+?): 3",
r"\[RW\] setting_with_unit (.+?): 4 V",
]

for idx, line in enumerate(status.__cli_output__.splitlines()):
assert re.match(expected_regex[idx], line) is not None