diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Dimmer.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Dimmer.vue new file mode 100644 index 00000000..5ec7324a --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/Dimmer.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json index e4eafd8f..98f45758 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -29,5 +29,13 @@ "icon": { "class": "fas fa-lightbulb" } + }, + + "dimmer": { + "name": "Dimmer", + "name_plural": "Dimmers", + "icon": { + "class": "fas fa-gauge" + } } } diff --git a/platypush/entities/dimmers.py b/platypush/entities/dimmers.py new file mode 100644 index 00000000..f88d74fe --- /dev/null +++ b/platypush/entities/dimmers.py @@ -0,0 +1,25 @@ +from sqlalchemy import Column, Integer, ForeignKey, Float + +from .devices import Device, entity_types_registry + + +if not entity_types_registry.get('Dimmer'): + + class Dimmer(Device): + __tablename__ = 'dimmer' + + id = Column( + Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True + ) + min = Column(Float) + max = Column(Float) + step = Column(Float, default=1.0) + value = Column(Float) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + entity_types_registry['Dimmer'] = Dimmer +else: + Dimmer = entity_types_registry['Dimmer'] diff --git a/platypush/plugins/zwave/_base.py b/platypush/plugins/zwave/_base.py index 76a98479..3fe9d637 100644 --- a/platypush/plugins/zwave/_base.py +++ b/platypush/plugins/zwave/_base.py @@ -1,11 +1,15 @@ from abc import ABC, abstractmethod from typing import Any, Dict, Optional, List, Union -from platypush.plugins import action -from platypush.plugins.switch import SwitchPlugin +from platypush.entities import manages +from platypush.entities.dimmers import Dimmer +from platypush.entities.lights import Light +from platypush.entities.switches import Switch +from platypush.plugins import Plugin, action -class ZwaveBasePlugin(SwitchPlugin, ABC): +@manages(Dimmer, Light, Switch) +class ZwaveBasePlugin(Plugin, ABC): """ Base class for Z-Wave plugins. """ @@ -46,7 +50,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def remove_failed_node(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def remove_failed_node( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Remove a failed node from the network. @@ -57,7 +63,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def replace_failed_node(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def replace_failed_node( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Replace a failed node on the network. @@ -68,7 +76,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def replication_send(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def replication_send( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Send node information from the primary to the secondary controller. @@ -79,7 +89,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def request_network_update(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def request_network_update( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Request a network update to a node. @@ -90,7 +102,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def request_node_neighbour_update(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def request_node_neighbour_update( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Request a neighbours list update to a node. @@ -101,7 +115,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_nodes(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[str, Any]: + def get_nodes( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[str, Any]: """ Get the nodes associated to the network. @@ -112,8 +128,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_node_stats(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) \ - -> Dict[str, Any]: + def get_node_stats( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[str, Any]: """ Get the statistics of a node on the network. @@ -124,7 +141,12 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_node_name(self, new_name: str, node_id: Optional[int] = None, node_name: Optional[str] = None): + def set_node_name( + self, + new_name: str, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + ): """ Rename a node on the network. @@ -136,8 +158,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_node_product_name(self, product_name: str, node_id: Optional[int] = None, node_name: Optional[str] = None, - **kwargs): + def set_node_product_name( + self, + product_name: str, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Set the product name of a node. @@ -149,8 +176,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_node_manufacturer_name(self, manufacturer_name: str, node_id: Optional[int] = None, - node_name: Optional[str] = None, **kwargs): + def set_node_manufacturer_name( + self, + manufacturer_name: str, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Set the manufacturer name of a node. @@ -162,8 +194,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_node_location(self, location: str, node_id: Optional[int] = None, node_name: Optional[str] = None, - **kwargs): + def set_node_location( + self, + location: str, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Set the location of a node. @@ -256,9 +293,15 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_value(self, value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, node_id: Optional[int] = None, node_name: Optional[str] = None, - **kwargs) -> Dict[str, Any]: + def get_value( + self, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ) -> Dict[str, Any]: """ Get a value on the network. @@ -272,9 +315,16 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_value(self, data, value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, node_id: Optional[int] = None, node_name: Optional[str] = None, - **kwargs): + def set_value( + self, + data, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Set a value. @@ -289,9 +339,16 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_value_label(self, new_label: str, value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, node_id: Optional[int] = None, - node_name: Optional[str] = None, **kwargs): + def set_value_label( + self, + new_label: str, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Change the label/name of a value. @@ -306,9 +363,15 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def node_add_value(self, value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, node_id: Optional[int] = None, - node_name: Optional[str] = None, **kwargs): + def node_add_value( + self, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Add a value to a node. @@ -322,9 +385,15 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def node_remove_value(self, value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, node_id: Optional[int] = None, - node_name: Optional[str] = None, **kwargs): + def node_remove_value( + self, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Remove a value from a node. @@ -338,8 +407,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def node_heal(self, node_id: Optional[int] = None, node_name: Optional[str] = None, refresh_routes: bool = False, - **kwargs): + def node_heal( + self, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + refresh_routes: bool = False, + **kwargs + ): """ Heal network node by requesting the node to rediscover their neighbours. @@ -351,7 +425,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def node_update_neighbours(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def node_update_neighbours( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Ask a node to update its neighbours table. @@ -362,7 +438,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def node_network_update(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def node_network_update( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Update the controller with network information. @@ -373,7 +451,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def node_refresh_info(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def node_refresh_info( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ): """ Fetch up-to-date information about the node. @@ -384,7 +464,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_dimmers(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[int, Any]: + def get_dimmers( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the dimmers on the network or associated to a node. @@ -395,8 +477,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_node_config(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) \ - -> Dict[int, Any]: + def get_node_config( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the configuration values of a node or of all the nodes on the network. @@ -407,8 +490,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_battery_levels(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) \ - -> Dict[int, Any]: + def get_battery_levels( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the battery levels of a node or of all the nodes on the network. @@ -419,8 +503,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_power_levels(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) \ - -> Dict[int, Any]: + def get_power_levels( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the power levels of this node. @@ -431,7 +516,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_bulbs(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[int, Any]: + def get_bulbs( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the bulbs/LEDs on the network or associated to a node. @@ -442,7 +529,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_switches(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[int, Any]: + def get_switches( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the switches on the network or associated to a node. @@ -453,7 +542,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_sensors(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[int, Any]: + def get_sensors( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the sensors on the network or associated to a node. @@ -464,7 +555,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_doorlocks(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[int, Any]: + def get_doorlocks( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the doorlocks on the network or associated to a node. @@ -475,7 +568,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_usercodes(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) -> Dict[int, Any]: + def get_usercodes( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the usercodes on the network or associated to a node. @@ -486,8 +581,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_thermostats(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) \ - -> Dict[int, Any]: + def get_thermostats( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the thermostats on the network or associated to a node. @@ -498,8 +594,9 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_protections(self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs) \ - -> Dict[int, Any]: + def get_protections( + self, node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs + ) -> Dict[int, Any]: """ Get the protection-compatible devices on the network or associated to a node. @@ -536,7 +633,12 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def remove_scene(self, scene_id: Optional[int] = None, scene_label: Optional[str] = None, **kwargs): + def remove_scene( + self, + scene_id: Optional[int] = None, + scene_label: Optional[str] = None, + **kwargs + ): """ Remove a scene. @@ -547,7 +649,12 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def activate_scene(self, scene_id: Optional[int] = None, scene_label: Optional[str] = None, **kwargs): + def activate_scene( + self, + scene_id: Optional[int] = None, + scene_label: Optional[str] = None, + **kwargs + ): """ Activate a scene. @@ -558,8 +665,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def set_scene_label(self, new_label: str, scene_id: Optional[int] = None, scene_label: Optional[str] = None, - **kwargs): + def set_scene_label( + self, + new_label: str, + scene_id: Optional[int] = None, + scene_label: Optional[str] = None, + **kwargs + ): """ Rename a scene/set the scene label. @@ -571,11 +683,18 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def scene_add_value(self, data: Optional[Any] = None, - value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, scene_id: Optional[int] = None, - scene_label: Optional[str] = None, node_id: Optional[int] = None, - node_name: Optional[str] = None, **kwargs): + def scene_add_value( + self, + data: Optional[Any] = None, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + scene_id: Optional[int] = None, + scene_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Add a value to a scene. @@ -592,10 +711,17 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def scene_remove_value(self, value_id: Optional[int] = None, id_on_network: Optional[str] = None, - value_label: Optional[str] = None, scene_id: Optional[int] = None, - scene_label: Optional[str] = None, node_id: Optional[int] = None, - node_name: Optional[str] = None, **kwargs): + def scene_remove_value( + self, + value_id: Optional[int] = None, + id_on_network: Optional[str] = None, + value_label: Optional[str] = None, + scene_id: Optional[int] = None, + scene_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Remove a value from a scene. @@ -611,19 +737,12 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def get_scene_values(self, scene_id: Optional[int] = None, scene_label: Optional[str] = None, **kwargs) -> dict: - """ - Get the values associated to a scene. - - :param scene_id: Select by scene_id. - :param scene_label: Select by scene label. - :return: value_id -> value (as a dict) mapping. - """ - raise NotImplementedError - - @abstractmethod - @action - def get_scene_values(self, scene_id: Optional[int] = None, scene_label: Optional[str] = None, **kwargs) -> dict: + def get_scene_values( + self, + scene_id: Optional[int] = None, + scene_label: Optional[str] = None, + **kwargs + ) -> dict: """ Get the values associated to a scene. @@ -634,8 +753,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def create_button(self, button_id: Union[int, str], node_id: Optional[int] = None, node_name: Optional[str] = None, - **kwargs): + def create_button( + self, + button_id: Union[int, str], + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Create a handheld button on a device. Only intended for bridge firmware controllers. @@ -647,8 +771,13 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def delete_button(self, button_id: Union[int, str], node_id: Optional[int] = None, node_name: Optional[str] = None, - **kwargs): + def delete_button( + self, + button_id: Union[int, str], + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Delete a button association from a device. Only intended for bridge firmware controllers. @@ -660,8 +789,14 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def add_node_to_group(self, group_index: Optional[int] = None, group_label: Optional[str] = None, - node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def add_node_to_group( + self, + group_index: Optional[int] = None, + group_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Add a node to a group. @@ -674,8 +809,14 @@ class ZwaveBasePlugin(SwitchPlugin, ABC): @abstractmethod @action - def remove_node_from_group(self, group_index: Optional[int] = None, group_label: Optional[str] = None, - node_id: Optional[int] = None, node_name: Optional[str] = None, **kwargs): + def remove_node_from_group( + self, + group_index: Optional[int] = None, + group_label: Optional[str] = None, + node_id: Optional[int] = None, + node_name: Optional[str] = None, + **kwargs + ): """ Remove a node from a group. diff --git a/platypush/plugins/zwave/mqtt/__init__.py b/platypush/plugins/zwave/mqtt/__init__.py index 0ae844d5..d132f963 100644 --- a/platypush/plugins/zwave/mqtt/__init__.py +++ b/platypush/plugins/zwave/mqtt/__init__.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import json import queue @@ -5,6 +6,7 @@ from datetime import datetime from threading import Timer from typing import Optional, List, Any, Dict, Union, Iterable, Mapping, Callable +from platypush.entities.dimmers import Dimmer from platypush.entities.switches import Switch from platypush.message.event.zwave import ZwaveNodeRenamedEvent, ZwaveNodeEvent @@ -464,38 +466,120 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin): return value @staticmethod - def _is_switch(value: Mapping): - return ( - value.get('command_class_name', '').endswith('Switch') if value else False - ) + def _matches_classes(value: Mapping, *names: str): + classes = {command_class_by_name[name] for name in names} + + return value.get('command_class', '') in classes if value else False + + @classmethod + def _is_switch(cls, value: Mapping): + return cls._matches_classes( + value, 'switch_binary', 'switch_toggle_binary', 'switch_all' + ) and not value.get('is_read_only') + + @classmethod + def _is_dimmer(cls, value: Mapping): + return cls._matches_classes( + value, 'switch_multilevel', 'switch_toggle_multilevel' + ) and not value.get('is_read_only') + + def _to_entity_args(self, value: Mapping) -> dict: + if value['id'].endswith('-targetValue'): + current_value_id = '-'.join(value['id'].split('-')[:-1] + ['currentValue']) + value = { + **value, + 'id': current_value_id, + 'label': 'Current Value', + 'is_read_only': False, + 'is_write_only': False, + } + + return { + 'id': value['id'], + 'name': '{node_name} [{value_name}]'.format( + node_name=self._nodes_cache['by_id'][value['node_id']].get( + 'name', f'[Node {value["node_id"]}]' + ), + value_name=value.get('label'), + ), + 'description': value.get('help'), + 'is_read_only': value.get('is_read_only'), + 'is_write_only': value.get('is_write_only'), + 'data': { + 'label': value.get('label'), + 'node_id': value.get('node_id'), + }, + } def transform_entities(self, values: Iterable[Mapping]): entities = [] for value in values: - if self._is_switch(value): - entities.append( - Switch( - id=value['id'], - name='{node_name} [{value_name}]'.format( - node_name=self._nodes_cache['by_id'][value['node_id']].get( - 'name', f'[Node {value["node_id"]}]' - ), - value_name=value["label"], - ), - state=value['data'], - description=value.get('help'), - is_read_only=value.get('is_read_only'), - is_write_only=value.get('is_write_only'), - data={ - 'label': value.get('label'), - 'node_id': value.get('node_id'), - }, - ) - ) + if not value: + continue + + entity_type = None + entity_args = self._to_entity_args(value) + + if self._is_dimmer(value): + entity_type = Dimmer + entity_args['value'] = value['data'] + entity_args['min'] = value['min'] + entity_args['max'] = value['max'] + elif self._is_switch(value): + entity_type = Switch + entity_args['state'] = value['data'] + + if entity_type: + entities.append(entity_type(**entity_args)) return super().transform_entities(entities) # type: ignore + @staticmethod + def _merge_current_and_target_values(values: Iterable[dict]) -> List[dict]: + values_by_id = OrderedDict({v.get('id'): v for v in values}) + + new_values = OrderedDict() + for value in values: + value_id = value.get('id') + if not value_id: + continue + + associated_value_id = None + associated_value = None + value_id_prefix = '-'.join(value_id.split('-')[:-1]) + + if value_id.endswith('-currentValue'): + associated_value_id = value_id_prefix + '-targetValue' + elif value_id.endswith('-targetValue'): + associated_value_id = value_id_prefix + '-currentValue' + if associated_value_id: + associated_value = values_by_id.pop(associated_value_id, None) + + if associated_value: + value = value.copy() + value_id = value_id_prefix + '-currentValue' + value['data'] = ( + value.get('data') + if value.get('id', '').endswith('-currentValue') + else associated_value.get('data') + ) + value['id'] = value['value_id'] = value['id_on_network'] = value_id + value['is_read_only'] = value['is_write_only'] = False + value['label'] = 'Current Value' + value['property_id'] = 'currentValue' + value['last_update'] = ( + max( + value.get('last_update') or 0, + associated_value.get('last_update') or 0, + ) + or None + ) + + new_values[value_id] = value + + return list(new_values.values()) + def _topic_by_value_id(self, value_id: str) -> str: return self.topic_prefix + '/' + '/'.join(value_id.split('-')) @@ -541,7 +625,8 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin): values[value['id_on_network']] = value - self.publish_entities(values.values()) # type: ignore + entity_values = self._merge_current_and_target_values(values.values()) + self.publish_entities(entity_values) # type: ignore return values def _get_group( @@ -1117,7 +1202,8 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin): @action def set_value( self, - data, + *args, + data=None, value_id: Optional[int] = None, id_on_network: Optional[str] = None, value_label: Optional[str] = None, @@ -1137,6 +1223,16 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin): :param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish`` (default: query the default configured device). """ + # Compatibility layer with the .set_value format used by + # the entities frontend + if args: + value_id = args[0] + + id_ = str(value_id or id_on_network or '') + if id_.endswith('-currentValue'): + id_ = '-'.join(id_.split('-')[:-1] + ['targetValue']) + value_id = id_on_network = id_ # type: ignore + value = self._get_value( value_id=value_id, value_label=value_label, @@ -1354,7 +1450,7 @@ class ZwaveMqttPlugin(MqttPlugin, ZwaveBasePlugin): (default: query the default configured device). """ return self._filter_values( - ['switch_binary', 'switch_toggle_binary'], + ['switch_binary', 'switch_toggle_binary', 'switch_all'], filter_callback=lambda value: not value['is_read_only'], node_id=node_id, node_name=node_name,