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

Implement introspectable actions #1588

Merged
merged 9 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
22 changes: 22 additions & 0 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,28 @@ If the device has a setting with some pre-defined values, you want to use this.
"""Return the LED brightness."""


Actions
"""""""

Use :meth:`@action <miio.devicestatus.action>` to create :class:`~miio.descriptors.ActionDescriptor`
objects for the device.
This will make all decorated actions accessible through :meth:`~miio.device.Device.actions` for downstream users.

.. code-block:: python

@command()
@action(name="Do Something", some_kwarg_for_downstream="hi there")
def do_something(self):
"""Execute some action on the device."""

.. note::

All keywords arguments not defined in the decorator signature will be available
through the :attr:`~miio.descriptors.ActionDescriptor.extras` variable.

This information can be used to pass information to the downstream users.


.. _adding_tests:

Adding tests
Expand Down
12 changes: 11 additions & 1 deletion miio/device.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from enum import Enum
from inspect import getmembers
from typing import Any, Dict, List, Optional, Union # noqa: F401

import click
Expand Down Expand Up @@ -62,6 +63,7 @@ def __init__(
self.token: Optional[str] = token
self._model: Optional[str] = model
self._info: Optional[DeviceInfo] = None
self._actions: Optional[Dict[str, ActionDescriptor]] = None
timeout = timeout if timeout is not None else self.timeout
self._protocol = MiIOProtocol(
ip, token, start_id, debug, lazy_discover, timeout
Expand Down Expand Up @@ -248,7 +250,15 @@ def status(self) -> DeviceStatus:

def actions(self) -> Dict[str, ActionDescriptor]:
"""Return device actions."""
return {}
if self._actions is None:
self._actions = {}
for action_tuple in getmembers(self, lambda o: hasattr(o, "_action")):
method_name, method = action_tuple
action = method._action
action.method = method # bind the method
self._actions[method_name] = action

return self._actions

def settings(self) -> Dict[str, SettingDescriptor]:
"""Return device settings."""
Expand Down
30 changes: 30 additions & 0 deletions miio/devicestatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)

from .descriptors import (
ActionDescriptor,
BooleanSettingDescriptor,
EnumSettingDescriptor,
NumberSettingDescriptor,
Expand Down Expand Up @@ -232,3 +233,32 @@ def decorator_setting(func):
return func

return decorator_setting


def action(name: str, **kwargs):
"""Syntactic sugar to create ActionDescriptor objects.

The information can be used by users of the library to programmatically find out what
types of actions are available for the device.

The interface is kept minimal, but you can pass any extra keyword arguments.
These extras are made accessible over :attr:`~miio.descriptors.ActionDescriptor.extras`,
and can be interpreted downstream users as they wish.
starkillerOG marked this conversation as resolved.
Show resolved Hide resolved
"""

def decorator_action(func):
property_name = str(func.__name__)
qualified_name = str(func.__qualname__)

descriptor = ActionDescriptor(
id=qualified_name,
name=name,
method_name=property_name,
method=None,
extras=kwargs,
)
func._action = descriptor

return func

return decorator_action
3 changes: 3 additions & 0 deletions miio/integrations/vacuum/roborock/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
command,
)
from miio.device import Device, DeviceInfo
from miio.devicestatus import action
from miio.exceptions import DeviceInfoUnavailableException, UnsupportedFeatureException
from miio.interfaces import FanspeedPresets, VacuumInterface

Expand Down Expand Up @@ -866,12 +867,14 @@ def set_dust_collection_mode(self, mode: DustCollectionMode) -> bool:
return self.send("set_dust_collection_mode", {"mode": mode.value})[0] == "ok"

@command()
@action(name="Start dust collection", icon="mdi:turbine")
def start_dust_collection(self):
"""Activate automatic dust collection."""
self._verify_auto_empty_support()
return self.send("app_start_collect_dust")

@command()
@action(name="Stop dust collection", icon="mdi:turbine")
starkillerOG marked this conversation as resolved.
Show resolved Hide resolved
def stop_dust_collection(self):
"""Abort in progress dust collection."""
self._verify_auto_empty_support()
Expand Down