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 000000000..5ec7324a6
--- /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 e4eafd8f0..98f457585 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 000000000..f88d74fe3
--- /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 76a984793..3fe9d637b 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 0ae844d52..d132f9639 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,