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 abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Optional
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from . import EntityManager
|
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.
|
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)"""
|
"""Toggle the state of a device (on->off or off->on)"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@override
|
||||||
class MultiLevelSwitchEntityManager(EntityManager, ABC):
|
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||||
"""
|
method = self.on if value else self.off
|
||||||
Base class for integrations that support dimmers/multi-level/enum switches.
|
return method(entity, **kwargs)
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
class DimmerEntityManager(MultiLevelSwitchEntityManager, ABC):
|
class DimmerEntityManager(WriteableEntityManager, ABC):
|
||||||
"""
|
"""
|
||||||
Base class for integrations that support dimmers/multi-level switches.
|
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,
|
Base class for integrations that support switches with a pre-defined,
|
||||||
enum-like set of possible values.
|
enum-like set of possible values.
|
||||||
|
|
|
@ -36,6 +36,7 @@ class BluetoothEvent(Event):
|
||||||
connected=connected,
|
connected=connected,
|
||||||
paired=paired,
|
paired=paired,
|
||||||
blocked=blocked,
|
blocked=blocked,
|
||||||
|
trusted=trusted,
|
||||||
service_uuids=service_uuids or [],
|
service_uuids=service_uuids or [],
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ from platypush.utils import camel_case_to_snake_case
|
||||||
from ._mappers import DeviceMapper, device_mappers
|
from ._mappers import DeviceMapper, device_mappers
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-ancestors
|
||||||
class SmartthingsPlugin(
|
class SmartthingsPlugin(
|
||||||
RunnablePlugin,
|
RunnablePlugin,
|
||||||
DimmerEntityManager,
|
DimmerEntityManager,
|
||||||
|
@ -817,18 +818,18 @@ class SmartthingsPlugin(
|
||||||
|
|
||||||
:param device: Device ID or name.
|
:param device: Device ID or name.
|
||||||
:param level: Level, usually a percentage value between 0 and 1.
|
: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)
|
return self.set_value(device, Capability.switch_level, level, **kwargs)
|
||||||
|
|
||||||
def _set_value( # pylint: disable=redefined-builtin
|
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:
|
if not property:
|
||||||
device, property = self._to_device_and_property(device)
|
device, property = self._to_device_and_property(device)
|
||||||
|
|
||||||
assert property, 'No property name specified'
|
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_id = f'{device}:{property}'
|
||||||
entity = self._entities_by_id.get(entity_id)
|
entity = self._entities_by_id.get(entity_id)
|
||||||
assert entity, f'No such entity ID: {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
|
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 (
|
assert (
|
||||||
mapper.set_command
|
mapper.set_command
|
||||||
), f'The property "{property}" on the device "{device}" cannot be set'
|
), f'The property "{property}" on the device "{device}" cannot be set'
|
||||||
|
|
||||||
command = (
|
command = (
|
||||||
mapper.set_command(data)
|
mapper.set_command(value)
|
||||||
if callable(mapper.set_command)
|
if callable(mapper.set_command)
|
||||||
else mapper.set_command
|
else mapper.set_command
|
||||||
)
|
)
|
||||||
|
@ -852,16 +855,20 @@ class SmartthingsPlugin(
|
||||||
device,
|
device,
|
||||||
mapper.capability,
|
mapper.capability,
|
||||||
command,
|
command,
|
||||||
args=mapper.set_value_args(data), # type: ignore
|
args=mapper.set_value_args(value), # type: ignore
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.status(device)
|
return self.status(device)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
# pylint: disable=redefined-builtin,arguments-differ
|
def set(self, entity: str, value: Any, attribute: Optional[str] = None, **kwargs):
|
||||||
def set_value(
|
super().set(entity, value, attribute, **kwargs)
|
||||||
self, device: str, *_, property: Optional[str] = None, data=None, **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
|
Set the value of a device. It is compatible with the generic
|
||||||
|
@ -871,10 +878,11 @@ class SmartthingsPlugin(
|
||||||
``device_id:property``.
|
``device_id:property``.
|
||||||
:param property: Name of the property to be set. If not specified here
|
:param property: Name of the property to be set. If not specified here
|
||||||
then it should be specified on the ``device`` level.
|
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:
|
try:
|
||||||
return self._set_value(device, property, data, **kwargs)
|
return self._set_value(device, property, value, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
raise AssertionError(e) from e
|
raise AssertionError(e) from e
|
||||||
|
|
|
@ -1104,10 +1104,21 @@ class SwitchbotPlugin(
|
||||||
return self._run('post', 'scenes', scenes[0]['id'], 'execute')
|
return self._run('post', 'scenes', scenes[0]['id'], 'execute')
|
||||||
|
|
||||||
@action
|
@action
|
||||||
# pylint: disable=redefined-builtin,arguments-differ
|
# pylint: disable=redefined-builtin
|
||||||
def set_value(self, device: str, *_, property=None, data=None, **__):
|
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)
|
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')
|
dt = entity.data.get('device_type')
|
||||||
assert dt, f'Could not infer the device type for "{device}"'
|
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}"'
|
assert setter_class, f'No setters found for device type "{device_type}"'
|
||||||
|
|
||||||
setter = setter_class(entity)
|
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(
|
def _to_entity(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import enum
|
import enum
|
||||||
from typing import Collection
|
from typing import Any, Collection, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from bleak.backends.device import BLEDevice
|
from bleak.backends.device import BLEDevice
|
||||||
|
@ -98,28 +98,32 @@ class SwitchbotBluetoothPlugin(BluetoothBlePlugin, EnumSwitchEntityManager):
|
||||||
loop = get_or_create_event_loop()
|
loop = get_or_create_event_loop()
|
||||||
return loop.run_until_complete(self._run(device, Command.OFF))
|
return loop.run_until_complete(self._run(device, Command.OFF))
|
||||||
|
|
||||||
@override
|
|
||||||
@action
|
@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 entity: Device name or address
|
||||||
:param data: Command to send. Possible values are:
|
:param value: Command to send. Possible values are:
|
||||||
|
|
||||||
- ``on``: Press the button and remain in the pressed state.
|
- ``on``: Press the button and remain in the pressed state.
|
||||||
- ``off``: Release a previously pressed button.
|
- ``off``: Release a previously pressed button.
|
||||||
- ``press``: Press and release the button.
|
- ``press``: Press and release the button.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if data == 'on':
|
assert device, 'No device specified'
|
||||||
|
if value == 'on':
|
||||||
self.on(device)
|
self.on(device)
|
||||||
if data == 'off':
|
if value == 'off':
|
||||||
self.off(device)
|
self.off(device)
|
||||||
if data == 'press':
|
if value == 'press':
|
||||||
self.press(device)
|
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
|
@override
|
||||||
def transform_entities(
|
def transform_entities(
|
||||||
|
|
|
@ -13,6 +13,7 @@ from typing import (
|
||||||
Type,
|
Type,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from platypush.entities import (
|
from platypush.entities import (
|
||||||
DimmerEntityManager,
|
DimmerEntityManager,
|
||||||
|
@ -48,6 +49,7 @@ from platypush.plugins import RunnablePlugin
|
||||||
from platypush.plugins.mqtt import MqttPlugin, action
|
from platypush.plugins.mqtt import MqttPlugin, action
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-ancestors
|
||||||
class ZigbeeMqttPlugin(
|
class ZigbeeMqttPlugin(
|
||||||
RunnablePlugin,
|
RunnablePlugin,
|
||||||
MqttPlugin,
|
MqttPlugin,
|
||||||
|
@ -56,7 +58,7 @@ class ZigbeeMqttPlugin(
|
||||||
LightEntityManager,
|
LightEntityManager,
|
||||||
SensorEntityManager,
|
SensorEntityManager,
|
||||||
SwitchEntityManager,
|
SwitchEntityManager,
|
||||||
): # lgtm [py/missing-call-to-init]
|
):
|
||||||
"""
|
"""
|
||||||
This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and
|
This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and
|
||||||
`zigbee2mqtt <https://www.zigbee2mqtt.io/>`_.
|
`zigbee2mqtt <https://www.zigbee2mqtt.io/>`_.
|
||||||
|
@ -616,7 +618,6 @@ class ZigbeeMqttPlugin(
|
||||||
"genLevelCtrl",
|
"genLevelCtrl",
|
||||||
"touchlink",
|
"touchlink",
|
||||||
"lightingColorCtrl",
|
"lightingColorCtrl",
|
||||||
"manuSpecificUbisysDimmerSetup"
|
|
||||||
],
|
],
|
||||||
"output": [
|
"output": [
|
||||||
"genOta"
|
"genOta"
|
||||||
|
@ -925,7 +926,7 @@ class ZigbeeMqttPlugin(
|
||||||
if not exposes:
|
if not exposes:
|
||||||
return {}
|
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
|
# topic to listen on
|
||||||
req = self._build_device_get_request(exposes)
|
req = self._build_device_get_request(exposes)
|
||||||
reply_topic = self._topic(device)
|
reply_topic = self._topic(device)
|
||||||
|
@ -1072,9 +1073,9 @@ class ZigbeeMqttPlugin(
|
||||||
return properties
|
return properties
|
||||||
|
|
||||||
@action
|
@action
|
||||||
# pylint: disable=redefined-builtin,arguments-differ
|
# pylint: disable=redefined-builtin
|
||||||
def set_value(
|
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.
|
Entity-compatible way of setting a value on a node.
|
||||||
|
@ -1094,6 +1095,11 @@ class ZigbeeMqttPlugin(
|
||||||
|
|
||||||
self.device_set(dev, property, data, **kwargs)
|
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
|
@action
|
||||||
def device_check_ota_updates(self, device: str, **kwargs) -> dict:
|
def device_check_ota_updates(self, device: str, **kwargs) -> dict:
|
||||||
"""
|
"""
|
||||||
|
@ -1578,13 +1584,13 @@ class ZigbeeMqttPlugin(
|
||||||
assert device_info, f'No such device: {name}'
|
assert device_info, f'No such device: {name}'
|
||||||
name = self._preferred_name(device_info)
|
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)
|
option = self._get_options(device_info).get(prop)
|
||||||
if option:
|
if option:
|
||||||
return name, option
|
return name, option
|
||||||
|
|
||||||
assert prop, f'No such property on device {name}: {prop}'
|
assert prop_info, f'No such property on device {name}: {prop}'
|
||||||
return name, prop
|
return name, prop_info
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_read_only(feature: dict) -> bool:
|
def _is_read_only(feature: dict) -> bool:
|
||||||
|
|
|
@ -325,7 +325,7 @@ class ZwaveBasePlugin(
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@action
|
@action
|
||||||
def set_value( # pylint: disable=arguments-differ
|
def set_value(
|
||||||
self,
|
self,
|
||||||
data,
|
data,
|
||||||
value_id: Optional[int] = None,
|
value_id: Optional[int] = None,
|
||||||
|
@ -347,6 +347,12 @@ class ZwaveBasePlugin(
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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
|
@abstractmethod
|
||||||
@action
|
@action
|
||||||
def set_value_label(
|
def set_value_label(
|
||||||
|
|
Loading…
Reference in a new issue