Skip to content

Commit

Permalink
Add enum for standardized vacuum identifier names (#1732)
Browse files Browse the repository at this point in the history
This adds an enum of "standardized" identifier names for vacuums to be
used by downstreams like homeassistant, and converts viomivacuum
integration to use them.
  • Loading branch information
rytilahti authored Feb 11, 2023
1 parent 313707d commit b4b7f1a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 18 deletions.
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

0 comments on commit b4b7f1a

Please sign in to comment.