"""This module contains descriptors.
The descriptors contain information that can be used to provide generic, dynamic user-interfaces.
If you are a downstream developer, use :func:`~miio.device.Device.sensors()`,
:func:`~miio.device.Device.settings()`, and
:func:`~miio.device.Device.actions()` to access the functionality exposed by the integration developer.
If you are developing an integration, prefer :func:`~miio.devicestatus.sensor`, :func:`~miio.devicestatus.setting`, and
:func:`~miio.devicestatus.action` decorators over creating the descriptors manually.
If needed, you can override the methods listed to add more descriptors to your integration.
"""
from enum import Enum, auto
from typing import Any, Callable, Dict, List, Optional, Type
import attr
[docs]@attr.s(auto_attribs=True)
class ValidSettingRange:
"""Describes a valid input range for a setting."""
min_value: int
max_value: int
step: int = 1
[docs]@attr.s(auto_attribs=True)
class Descriptor:
"""Base class for all descriptors."""
id: str
name: str
[docs]@attr.s(auto_attribs=True)
class ActionDescriptor(Descriptor):
"""Describes a button exposed by the device."""
method_name: Optional[str] = attr.ib(default=None, repr=False)
method: Optional[Callable] = attr.ib(default=None, repr=False)
inputs: Optional[List[Any]] = attr.ib(default=None, repr=True)
extras: Dict = attr.ib(factory=dict, repr=False)
[docs]@attr.s(auto_attribs=True)
class SensorDescriptor(Descriptor):
"""Describes a sensor exposed by the device.
This information can be used by library users to programatically
access information what types of data is available to display to users.
Prefer :meth:`@sensor <miio.devicestatus.sensor>` for constructing these.
"""
property: str
type: type
unit: Optional[str] = None
extras: Dict = attr.ib(factory=dict, repr=False)
[docs]class SettingType(Enum):
Undefined = auto()
Number = auto()
Boolean = auto()
Enum = auto()
[docs]@attr.s(auto_attribs=True, kw_only=True)
class SettingDescriptor(Descriptor):
"""Presents a settable value."""
property: str
unit: Optional[str] = None
type = SettingType.Undefined
setter: Optional[Callable] = attr.ib(default=None, repr=False)
setter_name: Optional[str] = attr.ib(default=None, repr=False)
extras: Dict = attr.ib(factory=dict, repr=False)
[docs] def cast_value(self, value: int):
"""Casts value to the expected type."""
cast_map = {
SettingType.Boolean: bool,
SettingType.Enum: int,
SettingType.Number: int,
}
return cast_map[self.type](int(value))
[docs]@attr.s(auto_attribs=True, kw_only=True)
class BooleanSettingDescriptor(SettingDescriptor):
"""Presents a settable boolean value."""
type: SettingType = SettingType.Boolean
[docs]@attr.s(auto_attribs=True, kw_only=True)
class EnumSettingDescriptor(SettingDescriptor):
"""Presents a settable, enum-based value."""
type: SettingType = SettingType.Enum
choices_attribute: Optional[str] = attr.ib(default=None, repr=False)
choices: Optional[Type[Enum]] = attr.ib(default=None, repr=False)
[docs]@attr.s(auto_attribs=True, kw_only=True)
class NumberSettingDescriptor(SettingDescriptor):
"""Presents a settable, numerical value.
If `range_attribute` is set, the named property that should return
:class:ValidSettingRange will be used to obtain {min,max}_value and step.
"""
min_value: int
max_value: int
step: int
range_attribute: Optional[str] = attr.ib(default=None)
type: SettingType = SettingType.Number