forked from platypush/platypush
Defined set
as a base method for all plugins that implement writeable entities
This commit is contained in:
parent
4365352331
commit
575635fd6b
7 changed files with 102 additions and 53 deletions
|
@ -1,9 +1,30 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Optional
|
||||
from typing_extensions import override
|
||||
|
||||
from . import EntityManager
|
||||
|
||||
|
||||
class SwitchEntityManager(EntityManager, ABC):
|
||||
class WriteableEntityManager(EntityManager, ABC):
|
||||
"""
|
||||
Base class for integrations that support entities whose values can be set.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
Set the value of an entity.
|
||||
|
||||
:param entity: The entity to set the value for. It's usually the ID of
|
||||
the entity provided by the plugin.
|
||||
:param value: The value to set the entity to.
|
||||
:param attribute: The name of the attribute to set for the entity, if
|
||||
required by the integration.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SwitchEntityManager(WriteableEntityManager, ABC):
|
||||
"""
|
||||
Base class for integrations that support binary switches.
|
||||
"""
|
||||
|
@ -23,31 +44,19 @@ class SwitchEntityManager(EntityManager, ABC):
|
|||
"""Toggle the state of a device (on->off or off->on)"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class MultiLevelSwitchEntityManager(EntityManager, ABC):
|
||||
"""
|
||||
Base class for integrations that support dimmers/multi-level/enum switches.
|
||||
|
||||
Don't extend this class directly. Instead, use on of the available
|
||||
intermediate abstract classes - like ``DimmerEntityManager`` or
|
||||
``EnumSwitchEntityManager``.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def set_value( # pylint: disable=redefined-builtin
|
||||
self, device=None, property=None, *, data=None, **__
|
||||
):
|
||||
"""Set a value"""
|
||||
raise NotImplementedError()
|
||||
@override
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
method = self.on if value else self.off
|
||||
return method(entity, **kwargs)
|
||||
|
||||
|
||||
class DimmerEntityManager(MultiLevelSwitchEntityManager, ABC):
|
||||
class DimmerEntityManager(WriteableEntityManager, ABC):
|
||||
"""
|
||||
Base class for integrations that support dimmers/multi-level switches.
|
||||
"""
|
||||
|
||||
|
||||
class EnumSwitchEntityManager(MultiLevelSwitchEntityManager, ABC):
|
||||
class EnumSwitchEntityManager(WriteableEntityManager, ABC):
|
||||
"""
|
||||
Base class for integrations that support switches with a pre-defined,
|
||||
enum-like set of possible values.
|
||||
|
|
|
@ -36,6 +36,7 @@ class BluetoothEvent(Event):
|
|||
connected=connected,
|
||||
paired=paired,
|
||||
blocked=blocked,
|
||||
trusted=trusted,
|
||||
service_uuids=service_uuids or [],
|
||||
**kwargs
|
||||
)
|
||||
|
|
|
@ -33,6 +33,7 @@ from platypush.utils import camel_case_to_snake_case
|
|||
from ._mappers import DeviceMapper, device_mappers
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
class SmartthingsPlugin(
|
||||
RunnablePlugin,
|
||||
DimmerEntityManager,
|
||||
|
@ -817,18 +818,18 @@ class SmartthingsPlugin(
|
|||
|
||||
:param device: Device ID or name.
|
||||
:param level: Level, usually a percentage value between 0 and 1.
|
||||
:param kwarsg: Extra arguments that should be passed to :meth:`.execute`.
|
||||
:param kwargs: Extra arguments that should be passed to :meth:`.execute`.
|
||||
"""
|
||||
return self.set_value(device, Capability.switch_level, level, **kwargs)
|
||||
|
||||
def _set_value( # pylint: disable=redefined-builtin
|
||||
self, device: str, property: Optional[str] = None, data=None, **kwargs
|
||||
self, device: str, property: Optional[str] = None, value: Any = None, **kwargs
|
||||
):
|
||||
if not property:
|
||||
device, property = self._to_device_and_property(device)
|
||||
|
||||
assert property, 'No property name specified'
|
||||
assert data is not None, 'No value specified'
|
||||
assert value is not None, 'No value specified'
|
||||
entity_id = f'{device}:{property}'
|
||||
entity = self._entities_by_id.get(entity_id)
|
||||
assert entity, f'No such entity ID: {entity_id}'
|
||||
|
@ -837,13 +838,15 @@ class SmartthingsPlugin(
|
|||
iter([m for m in device_mappers if m.attribute == property]), None
|
||||
)
|
||||
|
||||
assert mapper, f'No mappers found to set {property}={data} on device "{device}"'
|
||||
assert (
|
||||
mapper
|
||||
), f'No mappers found to set {property}={value} on device "{device}"'
|
||||
assert (
|
||||
mapper.set_command
|
||||
), f'The property "{property}" on the device "{device}" cannot be set'
|
||||
|
||||
command = (
|
||||
mapper.set_command(data)
|
||||
mapper.set_command(value)
|
||||
if callable(mapper.set_command)
|
||||
else mapper.set_command
|
||||
)
|
||||
|
@ -852,16 +855,20 @@ class SmartthingsPlugin(
|
|||
device,
|
||||
mapper.capability,
|
||||
command,
|
||||
args=mapper.set_value_args(data), # type: ignore
|
||||
args=mapper.set_value_args(value), # type: ignore
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return self.status(device)
|
||||
|
||||
@action
|
||||
# pylint: disable=redefined-builtin,arguments-differ
|
||||
def set_value(
|
||||
self, device: str, *_, property: Optional[str] = None, data=None, **kwargs
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
super().set(entity, value, attribute, **kwargs)
|
||||
return self.set_value(entity, property=attribute, value=value, **kwargs)
|
||||
|
||||
@action
|
||||
def set_value( # pylint: disable=redefined-builtin
|
||||
self, device: str, property: Optional[str] = None, value=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Set the value of a device. It is compatible with the generic
|
||||
|
@ -871,10 +878,11 @@ class SmartthingsPlugin(
|
|||
``device_id:property``.
|
||||
:param property: Name of the property to be set. If not specified here
|
||||
then it should be specified on the ``device`` level.
|
||||
:param data: Value to be set.
|
||||
:param value: Value to set.
|
||||
"""
|
||||
assert device, 'No device specified'
|
||||
try:
|
||||
return self._set_value(device, property, data, **kwargs)
|
||||
return self._set_value(device, property, value, **kwargs)
|
||||
except Exception as e:
|
||||
self.logger.exception(e)
|
||||
raise AssertionError(e) from e
|
||||
|
|
|
@ -1104,10 +1104,21 @@ class SwitchbotPlugin(
|
|||
return self._run('post', 'scenes', scenes[0]['id'], 'execute')
|
||||
|
||||
@action
|
||||
# pylint: disable=redefined-builtin,arguments-differ
|
||||
def set_value(self, device: str, *_, property=None, data=None, **__):
|
||||
# pylint: disable=redefined-builtin
|
||||
def set_value(
|
||||
self, device: str, property: Optional[str] = None, value: Any = None, **__
|
||||
):
|
||||
"""
|
||||
Set the value of a property of a device.
|
||||
|
||||
:param device: Device name or ID, or entity (external) ID.
|
||||
:param property: Property to set. It should be present if you are
|
||||
passing a root device ID to ``device`` and not an atomic entity in
|
||||
the format ``<device_id>:<property_name>``.
|
||||
:param value: Value to set.
|
||||
"""
|
||||
entity = self._to_entity(device, property)
|
||||
assert entity, f'No such entity: "{device}"'
|
||||
assert entity, f'No such device: "{device}"'
|
||||
|
||||
dt = entity.data.get('device_type')
|
||||
assert dt, f'Could not infer the device type for "{device}"'
|
||||
|
@ -1117,7 +1128,11 @@ class SwitchbotPlugin(
|
|||
assert setter_class, f'No setters found for device type "{device_type}"'
|
||||
|
||||
setter = setter_class(entity)
|
||||
return setter(property=property, value=data)
|
||||
return setter(property=property, value=value)
|
||||
|
||||
@action
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
return self.set_value(entity, property=attribute, value=value, **kwargs)
|
||||
|
||||
def _to_entity(
|
||||
self,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import enum
|
||||
from typing import Collection
|
||||
from typing import Any, Collection, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from bleak.backends.device import BLEDevice
|
||||
|
@ -98,28 +98,32 @@ class SwitchbotBluetoothPlugin(BluetoothBlePlugin, EnumSwitchEntityManager):
|
|||
loop = get_or_create_event_loop()
|
||||
return loop.run_until_complete(self._run(device, Command.OFF))
|
||||
|
||||
@override
|
||||
@action
|
||||
def set_value(self, device: str, *_, data: str, **__):
|
||||
def set_value(self, device: Optional[str] = None, value: Optional[str] = None, **_):
|
||||
"""
|
||||
Entity-compatible ``set_value`` method to send a command to a device.
|
||||
Send a command to a device as a value.
|
||||
|
||||
:param device: Device name or address
|
||||
:param data: Command to send. Possible values are:
|
||||
:param entity: Device name or address
|
||||
:param value: Command to send. Possible values are:
|
||||
|
||||
- ``on``: Press the button and remain in the pressed state.
|
||||
- ``off``: Release a previously pressed button.
|
||||
- ``press``: Press and release the button.
|
||||
|
||||
"""
|
||||
if data == 'on':
|
||||
assert device, 'No device specified'
|
||||
if value == 'on':
|
||||
self.on(device)
|
||||
if data == 'off':
|
||||
if value == 'off':
|
||||
self.off(device)
|
||||
if data == 'press':
|
||||
if value == 'press':
|
||||
self.press(device)
|
||||
|
||||
self.logger.warning('Unknown command for SwitchBot "%s": "%s"', device, data)
|
||||
self.logger.warning('Unknown command for SwitchBot "%s": "%s"', device, value)
|
||||
|
||||
@override
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
return self.set_value(entity, value, **kwargs)
|
||||
|
||||
@override
|
||||
def transform_entities(
|
||||
|
|
|
@ -13,6 +13,7 @@ from typing import (
|
|||
Type,
|
||||
Union,
|
||||
)
|
||||
from typing_extensions import override
|
||||
|
||||
from platypush.entities import (
|
||||
DimmerEntityManager,
|
||||
|
@ -48,6 +49,7 @@ from platypush.plugins import RunnablePlugin
|
|||
from platypush.plugins.mqtt import MqttPlugin, action
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
class ZigbeeMqttPlugin(
|
||||
RunnablePlugin,
|
||||
MqttPlugin,
|
||||
|
@ -56,7 +58,7 @@ class ZigbeeMqttPlugin(
|
|||
LightEntityManager,
|
||||
SensorEntityManager,
|
||||
SwitchEntityManager,
|
||||
): # lgtm [py/missing-call-to-init]
|
||||
):
|
||||
"""
|
||||
This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and
|
||||
`zigbee2mqtt <https://www.zigbee2mqtt.io/>`_.
|
||||
|
@ -616,7 +618,6 @@ class ZigbeeMqttPlugin(
|
|||
"genLevelCtrl",
|
||||
"touchlink",
|
||||
"lightingColorCtrl",
|
||||
"manuSpecificUbisysDimmerSetup"
|
||||
],
|
||||
"output": [
|
||||
"genOta"
|
||||
|
@ -925,7 +926,7 @@ class ZigbeeMqttPlugin(
|
|||
if not exposes:
|
||||
return {}
|
||||
|
||||
# If the device has no queriable properties, don't specify a reply
|
||||
# If the device has no queryable properties, don't specify a reply
|
||||
# topic to listen on
|
||||
req = self._build_device_get_request(exposes)
|
||||
reply_topic = self._topic(device)
|
||||
|
@ -1072,9 +1073,9 @@ class ZigbeeMqttPlugin(
|
|||
return properties
|
||||
|
||||
@action
|
||||
# pylint: disable=redefined-builtin,arguments-differ
|
||||
# pylint: disable=redefined-builtin
|
||||
def set_value(
|
||||
self, device: str, *_, property: Optional[str] = None, data=None, **kwargs
|
||||
self, device: str, property: Optional[str] = None, data=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Entity-compatible way of setting a value on a node.
|
||||
|
@ -1094,6 +1095,11 @@ class ZigbeeMqttPlugin(
|
|||
|
||||
self.device_set(dev, property, data, **kwargs)
|
||||
|
||||
@override
|
||||
@action
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
return self.set_value(entity, data=value, property=attribute, **kwargs)
|
||||
|
||||
@action
|
||||
def device_check_ota_updates(self, device: str, **kwargs) -> dict:
|
||||
"""
|
||||
|
@ -1578,13 +1584,13 @@ class ZigbeeMqttPlugin(
|
|||
assert device_info, f'No such device: {name}'
|
||||
name = self._preferred_name(device_info)
|
||||
|
||||
prop = self._get_properties(device_info).get(prop)
|
||||
prop_info = self._get_properties(device_info).get(prop)
|
||||
option = self._get_options(device_info).get(prop)
|
||||
if option:
|
||||
return name, option
|
||||
|
||||
assert prop, f'No such property on device {name}: {prop}'
|
||||
return name, prop
|
||||
assert prop_info, f'No such property on device {name}: {prop}'
|
||||
return name, prop_info
|
||||
|
||||
@staticmethod
|
||||
def _is_read_only(feature: dict) -> bool:
|
||||
|
|
|
@ -325,7 +325,7 @@ class ZwaveBasePlugin(
|
|||
|
||||
@abstractmethod
|
||||
@action
|
||||
def set_value( # pylint: disable=arguments-differ
|
||||
def set_value(
|
||||
self,
|
||||
data,
|
||||
value_id: Optional[int] = None,
|
||||
|
@ -347,6 +347,12 @@ class ZwaveBasePlugin(
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@action
|
||||
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||
return self.set_value(
|
||||
value_id=entity, id_on_network=entity, data=value, **kwargs
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
@action
|
||||
def set_value_label(
|
||||
|
|
Loading…
Reference in a new issue