diff --git a/platypush/backend/http/static/css/source/common/elements.scss b/platypush/backend/http/static/css/source/common/elements.scss
index f07a9d043b..d78c2be46f 100644
--- a/platypush/backend/http/static/css/source/common/elements.scss
+++ b/platypush/backend/http/static/css/source/common/elements.scss
@@ -11,6 +11,10 @@
text-align: right !important;
}
+.clickable {
+ cursor: pointer;
+}
+
a:focus {
outline: none;
}
diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/zwave/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/zwave/index.scss
new file mode 100644
index 0000000000..872ab07cf9
--- /dev/null
+++ b/platypush/backend/http/static/css/source/webpanel/plugins/zwave/index.scss
@@ -0,0 +1,195 @@
+@import 'common/vars';
+@import 'webpanel/plugins/zwave/vars';
+
+.fa.fa-zwave:before {
+ content: ' ';
+ background: url('/static/img/icons/z-wave-logo.png');
+ background-size: 1em 1em;
+ width: 1em;
+ height: 1em;
+ display: inline-block;
+}
+
+.zwave-container {
+ height: 100%;
+ padding: 0 .5em;
+ background: $container-bg;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .no-items {
+ padding: 2em;
+ font-size: 1.5em;
+ color: $no-items-color;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .view-options {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ padding: 1em 0;
+
+ .view-selector {
+ display: inline-flex;
+ }
+
+ .buttons {
+ display: inline-flex;
+ }
+
+ select {
+ width: 100%;
+ border-radius: 1em;
+ }
+ }
+
+ .btn-default {
+ border: 0;
+ padding: 0 1em;
+
+ &:hover {
+ border: $default-border-2;
+ border-radius: 1em;
+ }
+ }
+
+
+ .buttons {
+ text-align: right;
+ }
+
+ .view {
+ min-width: 400pt;
+ max-width: 750pt;
+ background: $view-bg;
+ border: $view-border;
+ border-radius: 1.5em;
+ box-shadow: $view-box-shadow;
+ }
+
+ .item {
+ &.selected {
+ box-shadow: $selected-item-box-shadow;
+ }
+
+ .name {
+ padding: 1em;
+ cursor: pointer;
+ text-transform: uppercase;
+ letter-spacing: .06em;
+
+ &.selected {
+ border-radius: 1.5em;
+ }
+ }
+
+ &:hover {
+ background: $hover-bg;
+ }
+
+ &:not(:last-child) {
+ border-bottom: $item-border;
+ }
+
+ &:first-child {
+ border-radius: 1.5em 1.5em 0 0;
+ }
+
+ &:last-child {
+ border-radius: 0 0 1.5em 1.5em;
+ }
+ }
+
+ .params {
+ background: $params-bg;
+ padding-bottom: 1em;
+
+ .section {
+ display: flex;
+ flex-direction: column;
+ padding: 0 1em;
+
+ &:not(:first-child) {
+ padding-top: 1em;
+ }
+
+ .header {
+ display: flex;
+ align-items: center;
+ font-weight: bold;
+ border-bottom: $param-section-header-border;
+ }
+ }
+
+ .row {
+ display: flex;
+ align-items: center;
+ border-radius: 1em;
+ padding: .3em;
+
+ &:nth-child(even) {
+ background: $param-even-row-bg;
+ }
+
+ &:nth-child(odd) {
+ background: $param-odd-row-bg;
+ }
+
+ &:hover {
+ background: $hover-bg;
+ }
+ }
+
+ .param-name {
+ display: inline-flex;
+ width: 40%;
+ margin-left: 1%;
+ vertical-align: top;
+ letter-spacing: .04em;
+ }
+
+ .param-value {
+ display: inline-block;
+ width: 58%;
+ text-align: right;
+ }
+ }
+
+ .modal {
+ .section {
+ .header {
+ background: none;
+ padding: .5em 0;
+ }
+
+ .body {
+ padding: 0;
+ }
+ }
+
+ .network-info {
+ min-width: 600pt;
+ }
+ }
+
+ .error {
+ color: $error-color;
+ }
+
+ .node {
+ .actions {
+ .row {
+ cursor: pointer;
+ }
+ }
+
+ form {
+ margin-bottom: 0;
+ }
+ }
+}
+
diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/zwave/vars.scss b/platypush/backend/http/static/css/source/webpanel/plugins/zwave/vars.scss
new file mode 100644
index 0000000000..798334a201
--- /dev/null
+++ b/platypush/backend/http/static/css/source/webpanel/plugins/zwave/vars.scss
@@ -0,0 +1,13 @@
+$container-bg: #f1f1f1;
+$view-bg: white;
+$view-border: 1px solid #d8d8d8;
+$view-box-shadow: 1px 2px 2px #ccc;
+$item-border: 1px solid #dddddd;
+$no-items-color: #555555;
+$params-bg: white;
+$param-even-row-bg: #ededed;
+$param-odd-row-bg: white;
+$param-section-header-border: 1px solid #e8e8e8;
+$selected-item-box-shadow: 0 2px 4px 0 #bbb;
+$error-color: #aa0000;
+
diff --git a/platypush/backend/http/static/img/icons/z-wave-logo.png b/platypush/backend/http/static/img/icons/z-wave-logo.png
new file mode 100644
index 0000000000..df9c955059
Binary files /dev/null and b/platypush/backend/http/static/img/icons/z-wave-logo.png differ
diff --git a/platypush/backend/http/static/js/plugins/light.hue/index.js b/platypush/backend/http/static/js/plugins/light.hue/index.js
index 52fc3e6c65..d903a07f35 100644
--- a/platypush/backend/http/static/js/plugins/light.hue/index.js
+++ b/platypush/backend/http/static/js/plugins/light.hue/index.js
@@ -132,7 +132,7 @@ Vue.component('light-hue', {
);
this.selectedScene = event.id;
- groups = {}
+ const groups = {};
for (const lightId of Object.values(this.scenes[this.selectedScene].lights)) {
this.lights[lightId].state.on = true;
@@ -148,7 +148,7 @@ Vue.component('light-hue', {
groups[groupId].lights.push(lightId);
- if (groups[groupId].lights.length == Object.values(group.lights).length) {
+ if (groups[groupId].lights.length === Object.values(group.lights).length) {
groups[groupId].all_on = true;
}
}
@@ -164,8 +164,8 @@ Vue.component('light-hue', {
},
collapsedToggled: function(event) {
- if (event.type == this.selectedProperties.type
- && event.id == this.selectedProperties.id) {
+ if (event.type === this.selectedProperties.type
+ && event.id === this.selectedProperties.id) {
this.selectedProperties = {
type: undefined,
id: undefined,
diff --git a/platypush/backend/http/static/js/plugins/zwave/group.js b/platypush/backend/http/static/js/plugins/zwave/group.js
new file mode 100644
index 0000000000..0410c1afdb
--- /dev/null
+++ b/platypush/backend/http/static/js/plugins/zwave/group.js
@@ -0,0 +1,24 @@
+Vue.component('zwave-group', {
+ template: '#tmpl-zwave-group',
+ props: ['group','nodes','bus','selected'],
+
+ methods: {
+ onGroupClicked: function() {
+ this.bus.$emit('groupClicked', {
+ groupId: this.group.index,
+ });
+ },
+
+ removeFromGroup: async function(nodeId) {
+ if (!confirm('Are you sure that you want to remove this node from ' + this.group.label + '?')) {
+ return;
+ }
+
+ await request('zwave.remove_node_from_group', {
+ node_id: nodeId,
+ group_index: this.group.index,
+ });
+ },
+ },
+});
+
diff --git a/platypush/backend/http/static/js/plugins/zwave/index.js b/platypush/backend/http/static/js/plugins/zwave/index.js
new file mode 100644
index 0000000000..48aed6dc3a
--- /dev/null
+++ b/platypush/backend/http/static/js/plugins/zwave/index.js
@@ -0,0 +1,457 @@
+Vue.component('zwave', {
+ template: '#tmpl-zwave',
+ props: ['config'],
+
+ data: function() {
+ return {
+ bus: new Vue({}),
+ status: {},
+ views: {},
+ nodes: {},
+ groups: {},
+ scenes: {},
+ values: {},
+ switches: new Set(),
+ dimmers: new Set(),
+ sensors: new Set(),
+ batteryLevels: new Set(),
+ powerLevels: new Set(),
+ bulbs: new Set(),
+ doorlocks: new Set(),
+ usercodes: new Set(),
+ thermostats: new Set(),
+ protections: new Set(),
+ commandRunning: false,
+ selected: {
+ view: 'nodes',
+ nodeId: undefined,
+ groupId: undefined,
+ sceneId: undefined,
+ valueId: undefined,
+ },
+ loading: {
+ status: false,
+ nodes: false,
+ groups: false,
+ scenes: false,
+ values: false,
+ },
+ modal: {
+ networkInfo: {
+ visible: false,
+ },
+ group: {
+ visible: false,
+ },
+ },
+ };
+ },
+
+ computed: {
+ networkDropdownItems: function() {
+ const self = this;
+ return [
+ {
+ text: 'Start Network',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.start_network');
+ self.commandRunning = false;
+ },
+ },
+
+ {
+ text: 'Stop Network',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.start_network');
+ self.commandRunning = false;
+ },
+ },
+
+ {
+ text: 'Switch All On',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.switch_all', {state: true});
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+ {
+ text: 'Switch All Off',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.switch_all', {state: false});
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+ {
+ text: 'Cancel Command',
+ click: async function() {
+ await request('zwave.cancel_command');
+ },
+ },
+
+ {
+ text: 'Kill Command',
+ click: async function() {
+ await request('zwave.kill_command');
+ },
+ },
+
+ {
+ text: 'Set Controller Name',
+ disabled: this.commandRunning,
+ click: async function() {
+ const name = prompt('Controller name');
+ if (!name) {
+ return;
+ }
+
+ self.commandRunning = true;
+ await request('zwave.set_controller_name', {name: name});
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+
+ {
+ text: 'Receive Configuration From Primary',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.receive_configuration');
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+ {
+ text: 'Create New Primary',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.create_new_primary');
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+ {
+ text: 'Transfer Primary Role',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.transfer_primary_role');
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+ {
+ text: 'Heal Network',
+ disabled: this.commandRunning,
+ click: async function() {
+ self.commandRunning = true;
+ await request('zwave.heal');
+ self.commandRunning = false;
+ self.refresh();
+ },
+ },
+
+ {
+ text: 'Soft Reset',
+ disabled: this.commandRunning,
+ click: async function() {
+ if (!confirm('Are you sure that you want to do a device soft reset? Network information will not be lost')) {
+ return;
+ }
+
+ await request('zwave.soft_reset');
+ },
+ },
+
+ {
+ text: 'Hard Reset',
+ disabled: this.commandRunning,
+ click: async function() {
+ if (!confirm('Are you sure that you want to do a device soft reset? ALL network information will be lost!')) {
+ return;
+ }
+
+ await request('zwave.hard_reset');
+ },
+ },
+ ]
+ },
+ },
+
+ methods: {
+ refreshNodes: async function () {
+ this.loading.nodes = true;
+ this.loading.values = true;
+
+ this.nodes = await request('zwave.get_nodes');
+ this.loading.nodes = false;
+
+ this.values = Object.values(this.nodes).reduce((values, node) => {
+ values = {
+ ...Object.values(node.values).reduce((values, value) => {
+ values[value.value_id] = {
+ node_id: node.node_id,
+ ...value,
+ };
+
+ return values;
+ }, {}),
+ ...values
+ };
+
+ return values;
+ }, {});
+
+ this.loading.values = false;
+ },
+
+ refreshGroups: async function () {
+ this.loading.groups = true;
+ this.groups = Object.values(await request('zwave.get_groups'))
+ .filter((group) => group.index)
+ .reduce((groups, group) => {
+ groups[group.index] = group;
+ return groups;
+ }, {});
+
+ if (Object.keys(this.groups).length) {
+ Vue.set(this.views, 'groups', true);
+ }
+
+ this.loading.groups = false;
+ },
+
+ refreshScenes: async function () {
+ this.loading.scenes = true;
+ this.scenes = Object.values(await request('zwave.get_scenes'))
+ .filter((scene) => scene.scene_id)
+ .reduce((scenes, scene) => {
+ scenes[scene.scene_id] = scene;
+ return scenes;
+ }, {});
+
+ this.loading.scenes = false;
+ },
+
+ refreshSwitches: async function () {
+ this.switches = new Set(Object.values(await request('zwave.get_switches'))
+ .filter((sw) => sw.id_on_network).map((sw) => sw.value_id));
+
+ if (this.switches.size) {
+ Vue.set(this.views, 'switches', true);
+ }
+ },
+
+ refreshDimmers: async function () {
+ this.dimmers = new Set(Object.values(await request('zwave.get_dimmers'))
+ .filter((dimmer) => dimmer.id_on_network).map((dimmer) => dimmer.value_id));
+
+ if (this.dimmers.size) {
+ Vue.set(this.views, 'dimmers', true);
+ }
+ },
+
+ refreshSensors: async function () {
+ this.sensors = new Set(Object.values(await request('zwave.get_sensors'))
+ .filter((sensor) => sensor.id_on_network).map((sensor) => sensor.value_id));
+
+ if (this.sensors.size) {
+ Vue.set(this.views, 'sensors', true);
+ }
+ },
+
+ refreshBatteryLevels: async function () {
+ this.batteryLevels = new Set(Object.values(await request('zwave.get_battery_levels'))
+ .filter((battery) => battery.id_on_network).map((battery) => battery.value_id));
+
+ if (this.batteryLevels.size) {
+ Vue.set(this.views, 'batteryLevels', true);
+ }
+ },
+
+ refreshPowerLevels: async function () {
+ this.powerLevels = new Set(Object.values(await request('zwave.get_power_levels'))
+ .filter((power) => power.id_on_network).map((power) => power.value_id));
+
+ if (this.powerLevels.size) {
+ Vue.set(this.views, 'powerLevels', true);
+ }
+ },
+
+ refreshBulbs: async function () {
+ this.bulbs = new Set(Object.values(await request('zwave.get_bulbs'))
+ .filter((bulb) => bulb.id_on_network).map((bulb) => bulb.value_id));
+
+ if (this.bulbs.size) {
+ Vue.set(this.views, 'bulbs', true);
+ }
+ },
+
+ refreshDoorlocks: async function () {
+ this.doorlocks = new Set(Object.values(await request('zwave.get_doorlocks'))
+ .filter((lock) => lock.id_on_network).map((lock) => lock.value_id));
+
+ if (this.doorlocks.size) {
+ Vue.set(this.views, 'doorlocks', true);
+ }
+ },
+
+ refreshUsercodes: async function () {
+ this.doorlocks = new Set(Object.values(await request('zwave.get_usercodes'))
+ .filter((code) => code.id_on_network).map((code) => code.value_id));
+
+ if (this.usercodes.size) {
+ Vue.set(this.views, 'usercodes', true);
+ }
+ },
+
+ refreshThermostats: async function () {
+ this.thermostats = new Set(Object.values(await request('zwave.get_thermostats'))
+ .filter((th) => th.id_on_network).map((th) => th.value_id));
+
+ if (this.thermostats.size) {
+ Vue.set(this.views, 'thermostats', true);
+ }
+ },
+
+ refreshProtections: async function () {
+ this.protections = new Set(Object.values(await request('zwave.get_protections'))
+ .filter((p) => p.id_on_network).map((p) => p.value_id));
+
+ if (this.protections.size) {
+ Vue.set(this.views, 'protections', true);
+ }
+ },
+
+ refreshStatus: async function() {
+ this.loading.status = true;
+ this.status = await request('zwave.status');
+ this.loading.status = false;
+ },
+
+ refresh: function () {
+ this.views = {
+ nodes: true,
+ scenes: true,
+ };
+
+ this.refreshNodes();
+ this.refreshGroups();
+ this.refreshScenes();
+ this.refreshSwitches();
+ this.refreshDimmers();
+ this.refreshSensors();
+ this.refreshBulbs();
+ this.refreshDoorlocks();
+ this.refreshUsercodes();
+ this.refreshThermostats();
+ this.refreshProtections();
+ this.refreshBatteryLevels();
+ this.refreshPowerLevels();
+ this.refreshStatus();
+ },
+
+ onNodeUpdate: function(event) {
+ Vue.set(this.nodes, event.node.node_id, event.node);
+ },
+
+ onViewChange: function(event) {
+ Vue.set(this.selected, 'view', event.target.value);
+ },
+
+ onNodeClicked: function(event) {
+ Vue.set(this.selected, 'nodeId', event.nodeId === this.selected.nodeId ? undefined : event.nodeId);
+ },
+
+ onGroupClicked: function(event) {
+ Vue.set(this.selected, 'groupId', event.groupId === this.selected.groupId ? undefined : event.groupId);
+ },
+
+ onNetworkInfoModalOpen: function() {
+ this.refreshStatus();
+ this.modal.networkInfo.visible = true;
+ },
+
+ onCommandEvent: function(event) {
+ if (event.error && event.error.length) {
+ createNotification({
+ text: event.state_description + ': ' + event.error_description,
+ error: true,
+ });
+ }
+ },
+
+ openNetworkCommandsDropdown: function() {
+ openDropdown(this.$refs.networkCommandsDropdown);
+ },
+
+ addNode: async function() {
+ this.commandRunning = true;
+ await request('zwave.add_node');
+ this.commandRunning = false;
+ },
+
+ addToGroup: async function(nodeId, groupId) {
+ this.commandRunning = true;
+ await request('zwave.add_node_to_group', {
+ node_id: nodeId,
+ group_index: groupId,
+ });
+
+ this.commandRunning = false;
+ this.refreshGroups();
+ },
+
+ removeNode: async function() {
+ this.commandRunning = true;
+ await request('zwave.remove_node');
+ this.commandRunning = false;
+ },
+ },
+
+ created: function() {
+ const self = this;
+ this.bus.$on('nodeClicked', this.onNodeClicked);
+ this.bus.$on('groupClicked', this.onGroupClicked);
+ this.bus.$on('openAddToGroupModal', () => {self.modal.group.visible = true});
+
+ registerEventHandler(this.refreshGroups, 'platypush.message.event.zwave.ZwaveNodeGroupEvent');
+ registerEventHandler(this.refreshScenes, 'platypush.message.event.zwave.ZwaveNodeSceneEvent');
+ registerEventHandler(this.refreshNodes, 'platypush.message.event.zwave.ZwaveNodeRemovedEvent');
+ registerEventHandler(this.onCommandEvent, 'platypush.message.event.zwave.ZwaveCommandEvent');
+
+ registerEventHandler(this.refreshStatus,
+ 'platypush.message.event.zwave.ZwaveNetworkReadyEvent',
+ 'platypush.message.event.zwave.ZwaveNetworkStoppedEvent',
+ 'platypush.message.event.zwave.ZwaveNetworkErrorEvent',
+ 'platypush.message.event.zwave.ZwaveNetworkResetEvent');
+
+ registerEventHandler(this.onNodeUpdate,
+ 'platypush.message.event.zwave.ZwaveNodeEvent',
+ 'platypush.message.event.zwave.ZwaveNodeAddedEvent',
+ 'platypush.message.event.zwave.ZwaveNodeRenamedEvent',
+ 'platypush.message.event.zwave.ZwaveNodeReadyEvent');
+ },
+
+ mounted: function() {
+ this.refresh();
+ },
+});
+
diff --git a/platypush/backend/http/static/js/plugins/zwave/node.js b/platypush/backend/http/static/js/plugins/zwave/node.js
new file mode 100644
index 0000000000..9dc88a4477
--- /dev/null
+++ b/platypush/backend/http/static/js/plugins/zwave/node.js
@@ -0,0 +1,96 @@
+Vue.component('zwave-node', {
+ template: '#tmpl-zwave-node',
+ props: ['node','bus','selected'],
+ data: function() {
+ return {
+ editMode: {
+ name: false,
+ },
+ };
+ },
+
+ methods: {
+ onNodeClicked: function() {
+ this.bus.$emit('nodeClicked', {
+ nodeId: this.node.node_id,
+ });
+ },
+
+ removeFailedNode: async function() {
+ if (!confirm('Are you sure that you want to remove this node?')) {
+ return;
+ }
+
+ await request('zwave.remove_node', {
+ node_id: this.node.node_id,
+ });
+ },
+
+ replaceFailedNode: async function() {
+ if (!confirm('Are you sure that you want to replace this node?')) {
+ return;
+ }
+
+ await request('zwave.replace_node', {
+ node_id: this.node.node_id,
+ });
+ },
+
+ replicationSend: async function() {
+ await request('zwave.replication_send', {
+ node_id: this.node.node_id,
+ });
+ },
+
+ requestNetworkUpdate: async function() {
+ await request('zwave.request_network_update', {
+ node_id: this.node.node_id,
+ });
+ },
+
+ requestNeighbourUpdate: async function() {
+ await request('zwave.request_node_neighbour_update', {
+ node_id: this.node.node_id,
+ });
+ },
+
+ disableForm: function(form) {
+ form.querySelector('input,button').readOnly = true;
+ },
+
+ enableForm: function(form) {
+ form.querySelector('input,button').readOnly = false;
+ },
+
+ onEditMode: function(mode) {
+ Vue.set(this.editMode, mode, true);
+ const form = this.$refs[mode + 'Form'];
+ const input = form.querySelector('input[type=text]');
+
+ setTimeout(() => {
+ input.focus();
+ input.select();
+ }, 10);
+ },
+
+ editName: async function(event) {
+ this.disableForm(event.target);
+ const name = event.target.querySelector('input[name=name]').value;
+
+ await request('zwave.set_node_name', {
+ node_id: this.node.node_id,
+ new_name: name,
+ });
+
+ this.editMode.name = false;
+ this.enableForm(event.target);
+ },
+
+ heal: async function(event) {
+ await request('zwave.node_heal', {
+ node_id: this.node.node_id,
+ });
+ },
+ },
+});
+
diff --git a/platypush/backend/http/templates/nav.html b/platypush/backend/http/templates/nav.html
index 9c10552752..72a80ff1a3 100644
--- a/platypush/backend/http/templates/nav.html
+++ b/platypush/backend/http/templates/nav.html
@@ -19,6 +19,7 @@
'switches': 'fa fa-toggle-on',
'tts': 'fa fa-comment',
'tts.google': 'fa fa-comment',
+ 'zwave': 'fa fa-zwave',
}
%}
diff --git a/platypush/backend/http/templates/plugins/zwave/group.html b/platypush/backend/http/templates/plugins/zwave/group.html
new file mode 100644
index 0000000000..f2ae849dca
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/zwave/group.html
@@ -0,0 +1,54 @@
+
+
+
+
diff --git a/platypush/backend/http/templates/plugins/zwave/index.html b/platypush/backend/http/templates/plugins/zwave/index.html
new file mode 100644
index 0000000000..5f0478c215
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/zwave/index.html
@@ -0,0 +1,99 @@
+{% include 'plugins/zwave/node.html' %}
+{% include 'plugins/zwave/group.html' %}
+
+
+
diff --git a/platypush/backend/http/templates/plugins/zwave/modals/group.html b/platypush/backend/http/templates/plugins/zwave/modals/group.html
new file mode 100644
index 0000000000..668bb309da
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/zwave/modals/group.html
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/platypush/backend/http/templates/plugins/zwave/modals/network.html b/platypush/backend/http/templates/plugins/zwave/modals/network.html
new file mode 100644
index 0000000000..1cc7fce988
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/zwave/modals/network.html
@@ -0,0 +1,35 @@
+
+
+
+ Loading status...
+
+
+
+
+
+
diff --git a/platypush/backend/http/templates/plugins/zwave/node.html b/platypush/backend/http/templates/plugins/zwave/node.html
new file mode 100644
index 0000000000..ea59ba14bc
--- /dev/null
+++ b/platypush/backend/http/templates/plugins/zwave/node.html
@@ -0,0 +1,186 @@
+
+
+
+
diff --git a/platypush/plugins/zwave/__init__.py b/platypush/plugins/zwave/__init__.py
index 2b88c357d7..a08606fe08 100644
--- a/platypush/plugins/zwave/__init__.py
+++ b/platypush/plugins/zwave/__init__.py
@@ -52,6 +52,22 @@ class ZwavePlugin(Plugin):
backend = self._get_backend()
backend.stop_network()
+ @action
+ def status(self) -> Dict[str, Any]:
+ """
+ Get the status of the controller.
+ :return: dict
+ """
+ backend = self._get_backend()
+ network = self._get_network()
+ controller = self._get_controller()
+
+ return {
+ 'device': backend.device,
+ 'state': network.state_str,
+ 'stats': controller.stats,
+ }
+
@action
def add_node(self, do_security=False):
"""
@@ -189,7 +205,7 @@ class ZwavePlugin(Plugin):
'node_id': node.node_id,
'home_id': node.home_id,
'capabilities': list(node.capabilities),
- 'command_classes': list(node.command_classes),
+ 'command_classes': [node.get_command_class_as_string(cc) for cc in node.command_classes],
'device_type': node.device_type,
'groups': {
group_id: cls.group_to_dict(group)
@@ -367,13 +383,13 @@ class ZwavePlugin(Plugin):
self._get_controller().kill_command()
@action
- def set_controller_name(self, node_name: str):
+ def set_controller_name(self, name: str):
"""
Set the name of the controller on the network.
- :param node_name: New controller name.
+ :param name: New controller name.
"""
- self._get_controller().name = node_name
+ self._get_controller().name = name
self.write_config()
@action
@@ -613,9 +629,9 @@ class ZwavePlugin(Plugin):
return self._get_values('power_levels', node_id=node_id, node_name=node_name)
@action
- def get_rgb_bulbs(self, node_id: Optional[int] = None, node_name: Optional[str] = None) -> Dict[int, Any]:
+ def get_bulbs(self, node_id: Optional[int] = None, node_name: Optional[str] = None) -> Dict[int, Any]:
"""
- Get the RGB bulbs/LEDs on the network or associated to a node.
+ Get the bulbs/LEDs on the network or associated to a node.
:param node_id: Select node by node_id.
:param node_name: Select node by name.