From 8dd1d364adaa348da382d31c8cbe69f688ff4fff Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 24 Nov 2020 16:42:38 +0100 Subject: [PATCH 01/23] feat: multicast api --- README.md | 49 +++++++++++++++++++++++++++++++++++------------ lib/Gateway.js | 30 ++++++++++++++++++++++++++++- lib/MqttClient.js | 8 +++++++- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f328620cd1a..b44752ec40b 100644 --- a/README.md +++ b/README.md @@ -35,26 +35,26 @@ Fully configurable Zwave to MQTT **Gateway** and **Control Panel**. - [Zwave To MQTT](#zwave-to-mqtt) - [📖 Table of contents](#-table-of-contents) - - [:electric_plug: Installation](#-installation) - - [DOCKER :tada: way](#docker--way) + - [:electric_plug: Installation](#electric_plug-installation) + - [DOCKER :tada: way](#docker-tada-way) - [Kubernetes way](#kubernetes-way) - [NodeJS or PKG version](#nodejs-or-pkg-version) - [Reverse Proxy Setup](#reverse-proxy-setup) - - [:nerd_face: Development](#-development) + - [:nerd_face: Development](#nerd_face-development) - [Developing against a different backend](#developing-against-a-different-backend) - - [:wrench: Usage](#-usage) + - [:wrench: Usage](#wrench-usage) - [Zwave](#zwave) - [MQTT](#mqtt) - [Gateway](#gateway) - [Special topics](#special-topics) - [Gateway values table](#gateway-values-table) - - [:file_folder: Nodes Management](#-nodes-management) + - [:file_folder: Nodes Management](#file_folder-nodes-management) - [Add a node](#add-a-node) - [Remove a node](#remove-a-node) - [Replace failed node (NOT IMPLEMENTED YET)](#replace-failed-node-not-implemented-yet) - [Remove a failed node](#remove-a-failed-node) - - [:star: Features](#️-features) - - [:robot: Home Assistant integration (BETA)](#-home-assistant-integration-beta) + - [:star: Features](#star-features) + - [:robot: Home Assistant integration (BETA)](#robot-home-assistant-integration-beta) - [Components management](#components-management) - [Rediscover Node](#rediscover-node) - [Edit existing component](#edit-existing-component) @@ -64,13 +64,14 @@ Fully configurable Zwave to MQTT **Gateway** and **Control Panel**. - [Thermostats](#thermostats) - [Fans](#fans) - [Thermostats with Fans](#thermostats-with-fans) - - [:gift: MQTT APIs](#-mqtt-apis) + - [:gift: MQTT APIs](#gift-mqtt-apis) - [Zwave Events](#zwave-events) - [Zwave APIs](#zwave-apis) - [Custom APIs](#custom-apis) - [Set values](#set-values) + - [Multicast](#multicast) - [Broadcast](#broadcast) - - [:camera: Screenshots](#-screenshots) + - [:camera: Screenshots](#camera-screenshots) - [Settings](#settings) - [Control Panel](#control-panel) - [Groups associations](#groups-associations) @@ -79,9 +80,9 @@ Fully configurable Zwave to MQTT **Gateway** and **Control Panel**. - [Debug](#debug) - [Health check endpoints](#health-check-endpoints) - [Environment variables](#environment-variables) - - [:question: FAQ](#-faq) - - [:pray: Thanks](#-thanks) - - [:pencil: TODOs](#-todos) + - [:question: FAQ](#question-faq) + - [:pray: Thanks](#pray-thanks) + - [:pencil: TODOs](#pencil-todos) - [:bowtie: Author](#bowtie-author) ## :electric_plug: Installation @@ -728,6 +729,30 @@ I will set the Heating setpoint of the node with id `4` located in the `office` `zwave/office/nodeID_4/thermostat_setpoint/heating` +### Multicast + +You can send Multicast requests to _all values with a specific suffix_ of a _group_ of nodes in the network. + +Multicast API is accessible from: + +`/_CLIENTS/ZWAVE_GATEWAY-/multicast//set` + +- `value_topic_suffix`: the suffix of the topic of the value I want to control using multicast. + +It works like the set value API without the node name and location properties. +If the API is correctly called the same payload of the request will be published +to the topic without `/set` suffix. + +Example of multicast command (gateway configured as `named topics`): + +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/thermostat_setpoint/heating/set` + +Payload: `{nodes: [5, 7, 9], value: 25.5}` + +Nodes **5, 7, 9** with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: + +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/thermostat_setpoint/heating` + ### Broadcast You can send broadcast values to _all values with a specific suffix_ in the network. diff --git a/lib/Gateway.js b/lib/Gateway.js index 7b7e77ed487..3264cd1594a 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -141,6 +141,7 @@ function init (config, zwave, mqtt) { if (mqtt) { mqtt.on('writeRequest', onWriteRequest.bind(this)) mqtt.on('broadcastRequest', onBroadRequest.bind(this)) + mqtt.on('multicastRequest', onMulticastRequest.bind(this)) mqtt.on('apiCall', onApiRequest.bind(this)) mqtt.on('hassStatus', onHassStatus.bind(this)) mqtt.on('brokerStatus', onBrokerStatus.bind(this)) @@ -289,7 +290,7 @@ function onValueChanged (valueId, node, changed) { data = { time: Date.now(), value: tmpVal } } - if (!valueId.read_only && !this.topicValues[topic]) { + if (valueId.writeable && !this.topicValues[topic]) { const levels = topic.split('/').length if (this.topicLevels.indexOf(levels) < 0) { @@ -449,6 +450,33 @@ function onWriteRequest (parts, payload) { } } +function onMulticastRequest (parts, payload) { + const topic = parts.join('/') + const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) + + const nodes = payload.nodes + + if (!nodes || nodes.length === 0) { + debug('No nodes found in multicast request to ' + topic) + return + } + + if (values.length > 0) { + // all values are the same type just different node,parse the Payload by using the first one + payload = this.parsePayload( + payload, + this.topicValues[values[0]], + this.topicValues[values[0]].conf + ) + for (let i = 0; i < values.length; i++) { + const valueId = this.topicValues[values[i]] + if (nodes.indexOf(valueId.nodeId) >= 0) { + this.zwave.writeValue(valueId, payload.value) + } + } + } +} + /** * Checks if an operation is valid, it must exist and must contains * only numbers and operators diff --git a/lib/MqttClient.js b/lib/MqttClient.js index 8730515bd36..f5aef646102 100644 --- a/lib/MqttClient.js +++ b/lib/MqttClient.js @@ -22,7 +22,7 @@ const BROADCAST_PREFIX = '_BROADCAST' const NAME_PREFIX = 'ZWAVE_GATEWAY-' -const ACTIONS = ['broadcast', 'api'] +const ACTIONS = ['broadcast', 'api', 'multicast'] const HASS_WILL = 'homeassistant/status' @@ -217,6 +217,12 @@ function onMessageReceived (topic, payload) { case 1: // api this.emit('apiCall', parts.join('/'), parts[3], payload) break + case 2: // multicast + this.emit('multicastRequest', parts.slice(3), payload) + // publish back to give a feedback the action is received + // same topic without /set suffix + this.publish(parts.join('/'), payload) + break default: debug('Unknown action received', action, topic) } From fa2679f5c967d60756d4c496ec44b50a85e5d684 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 24 Nov 2020 16:48:40 +0100 Subject: [PATCH 02/23] docs: fix lint --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b44752ec40b..c70a1fd3d3f 100644 --- a/README.md +++ b/README.md @@ -731,7 +731,7 @@ I will set the Heating setpoint of the node with id `4` located in the `office` ### Multicast -You can send Multicast requests to _all values with a specific suffix_ of a _group_ of nodes in the network. +You can send Multicast requests to _all values with a specific suffix_ of a _group_ of nodes in the network. Multicast API is accessible from: From af42de69c9937d34757b85e7b6fd125633bc6f55 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 25 Nov 2020 14:34:23 +0100 Subject: [PATCH 03/23] fix: await writes to zwave broadcast and multicast requests --- lib/Gateway.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 3264cd1594a..bcfa4ae4b9c 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -424,7 +424,7 @@ async function onApiRequest (topic, apiName, payload) { } } -function onBroadRequest (parts, payload) { +async function onBroadRequest (parts, payload) { const topic = parts.join('/') const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) @@ -436,21 +436,21 @@ function onBroadRequest (parts, payload) { this.topicValues[values[0]].conf ) for (let i = 0; i < values.length; i++) { - this.zwave.writeValue(this.topicValues[values[i]], payload) + await this.zwave.writeValue(this.topicValues[values[i]], payload) } } } -function onWriteRequest (parts, payload) { +async function onWriteRequest (parts, payload) { const valueId = this.topicValues[parts.join('/')] if (valueId) { payload = this.parsePayload(payload, valueId, valueId.conf) - this.zwave.writeValue(valueId, payload) + await this.zwave.writeValue(valueId, payload) } } -function onMulticastRequest (parts, payload) { +async function onMulticastRequest (parts, payload) { const topic = parts.join('/') const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) @@ -461,6 +461,11 @@ function onMulticastRequest (parts, payload) { return } + if (payload.value === undefined) { + debug('No value found to write in multicast request') + return + } + if (values.length > 0) { // all values are the same type just different node,parse the Payload by using the first one payload = this.parsePayload( @@ -471,7 +476,7 @@ function onMulticastRequest (parts, payload) { for (let i = 0; i < values.length; i++) { const valueId = this.topicValues[values[i]] if (nodes.indexOf(valueId.nodeId) >= 0) { - this.zwave.writeValue(valueId, payload.value) + await this.zwave.writeValue(valueId, payload.value) } } } From b9fbecc7fd36821862c8cdc22c91876eacf1271c Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 25 Nov 2020 14:36:05 +0100 Subject: [PATCH 04/23] fix: lint issues --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 268bddaebc3..d9454fb69c7 100644 --- a/package.json +++ b/package.json @@ -208,4 +208,4 @@ "last 2 versions", "not ie <= 8" ] -} \ No newline at end of file +} From badad4e87202af9e2a48dfd675cb2e8755cc1372 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 25 Nov 2020 14:41:18 +0100 Subject: [PATCH 05/23] fix: use parsed payload in write value --- lib/Gateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index bcfa4ae4b9c..914d2d1a91a 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -476,7 +476,7 @@ async function onMulticastRequest (parts, payload) { for (let i = 0; i < values.length; i++) { const valueId = this.topicValues[values[i]] if (nodes.indexOf(valueId.nodeId) >= 0) { - await this.zwave.writeValue(valueId, payload.value) + await this.zwave.writeValue(valueId, payload) } } } From 9ce6fffab69d6212677c0c88da130bf832fb7585 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 25 Nov 2020 16:42:59 +0100 Subject: [PATCH 06/23] docs: json payload --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c70a1fd3d3f..a39f98bf80f 100644 --- a/README.md +++ b/README.md @@ -747,7 +747,7 @@ Example of multicast command (gateway configured as `named topics`): `zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/thermostat_setpoint/heating/set` -Payload: `{nodes: [5, 7, 9], value: 25.5}` +Payload: `{ "nodes": [5, 7, 9], "value": 25.5}` Nodes **5, 7, 9** with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: From fbeccbd0602cdb0ac186be3cad622c8db349e3d6 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Fri, 11 Dec 2020 09:36:06 +0100 Subject: [PATCH 07/23] feat: use native multicast and broadcast from zwave-js --- lib/Gateway.js | 63 +++++++++++++++++++++------------------------- lib/MqttClient.js | 8 +++--- lib/ZwaveClient.js | 37 +++++++++++++++++++++++++++ lib/utils.js | 19 ++++++++++++++ 4 files changed, 89 insertions(+), 38 deletions(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 504fdd2baa5..eeaa24a1eb1 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -438,21 +438,23 @@ async function onApiRequest (topic, apiName, payload) { } } -async function onBroadRequest (parts, payload) { - const topic = parts.join('/') - const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) - - if (values.length > 0) { - // all values are the same type just different node,parse the Payload by using the first one - payload = this.parsePayload( - payload, - this.topicValues[values[0]], - this.topicValues[values[0]].conf - ) - for (let i = 0; i < values.length; i++) { - await this.zwave.writeValue(this.topicValues[values[i]], payload) - } +async function onBroadRequest (payload) { + const value = payload.value + const valueId = payload + + if (payload.value === undefined) { + debug('No value found in broadcast request') + return } + + const error = utils.isValueId(valueId) + + if (typeof error === 'string') { + debug('Invalid valueId: ' + error) + return + } + + await this.zwave.writeBroadcast(valueId, value) } async function onWriteRequest (parts, payload) { @@ -464,36 +466,29 @@ async function onWriteRequest (parts, payload) { } } -async function onMulticastRequest (parts, payload) { - const topic = parts.join('/') - const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) - +async function onMulticastRequest (payload) { const nodes = payload.nodes + const valueId = payload + const value = payload.value if (!nodes || nodes.length === 0) { - debug('No nodes found in multicast request to ' + topic) + debug('No nodes found in multicast request') return } - if (payload.value === undefined) { - debug('No value found to write in multicast request') + const error = utils.isValueId(valueId) + + if (typeof error === 'string') { + debug('Invalid valueId: ' + error) return } - if (values.length > 0) { - // all values are the same type just different node,parse the Payload by using the first one - payload = this.parsePayload( - payload, - this.topicValues[values[0]], - this.topicValues[values[0]].conf - ) - for (let i = 0; i < values.length; i++) { - const valueId = this.topicValues[values[i]] - if (nodes.indexOf(valueId.nodeId) >= 0) { - await this.zwave.writeValue(valueId, payload) - } - } + if (payload.value === undefined) { + debug('No value found in multicast request') + return } + + await this.zwave.writeMulticast(nodes, valueId, value) } /** diff --git a/lib/MqttClient.js b/lib/MqttClient.js index 393dc618130..230b82c8e46 100644 --- a/lib/MqttClient.js +++ b/lib/MqttClient.js @@ -221,8 +221,8 @@ function onMessageReceived (topic, payload) { switch (action) { case 0: // broadcast - this.emit('broadcastRequest', parts.slice(3), payload) - // publish back to give a feedback the action is received + this.emit('broadcastRequest', payload) + // publish back to give a feedback the action has been received // same topic without /set suffix this.publish(parts.join('/'), payload) break @@ -230,8 +230,8 @@ function onMessageReceived (topic, payload) { this.emit('apiCall', parts.join('/'), parts[3], payload) break case 2: // multicast - this.emit('multicastRequest', parts.slice(3), payload) - // publish back to give a feedback the action is received + this.emit('multicastRequest', payload) + // publish back to give a feedback the action has been received // same topic without /set suffix this.publish(parts.join('/'), payload) break diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index 1f1c63f6548..69edc872e87 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -1886,6 +1886,43 @@ ZwaveClient.prototype.callApi = async function (apiName, ...args) { return result } +/** + * Send broadcast write request + * + * @param {Object} valueId Zwave valueId object + * @param {Integer|String} value The value to send + */ +ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { + if (this.driverReady) { + try { + const broadcastNode = this.driver.controller.getBroadcastNode() + + await broadcastNode.setValue(valueId, value) + } catch (error) { + debug(`Error while sending broadcast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}`) + } + } +} + +/** + * Send multicast write request to a group of nodes + * + * @param {number[]} nodes Array of nodes ids + * @param {Object} valueId Zwave valueId object + * @param {Integer|String} value The value to send + */ +ZwaveClient.prototype.writeMulticast = async function (nodes, valueId, value) { + if (this.driverReady) { + try { + const multicastGroup = this.driver.controller.getMulticastGroup(nodes) + + await multicastGroup.setValue(valueId, value) + } catch (error) { + debug(`Error while sending multicast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}`) + } + } +} + /** * Set a value of a specific zwave valueId * diff --git a/lib/utils.js b/lib/utils.js index 5a2cafb3cb8..ca85a8fba6f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -16,5 +16,24 @@ module.exports = { num2hex (num) { const hex = num >= 0 ? num.toString(16) : 'XXXX' return '0x' + '0'.repeat(4 - hex.length) + hex + }, + isValueId (v) { + if (typeof v.commandClass !== 'number' || v.commandClass < 0) { + return 'invalid `commandClass`' + } + + if (v.endpoint !== undefined && v.endpoint < 0) { + return 'invalid `endpoint`' + } + + if (v.property === undefined || (typeof v.property !== 'string' && typeof v.property !== 'number')) { + return 'invalid `property`' + } + + if (v.propertyKey !== undefined && (typeof v.propertyKey !== 'string' && typeof v.propertyKey !== 'number')) { + return 'invalid `propertyKey`' + } + + return true } } From 64dbcd5902d30b2fc5320011f9d0b92efa235379 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Fri, 11 Dec 2020 09:56:48 +0100 Subject: [PATCH 08/23] docs: multicast and broadcast --- README.md | 69 +++++++++++++++++++++++++++++++++------------- lib/ZwaveClient.js | 4 +-- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 426c2e66817..10fc0e2876c 100644 --- a/README.md +++ b/README.md @@ -740,27 +740,51 @@ I will set the Heating setpoint of the node with id `4` located in the `office` ### Multicast -You can send Multicast requests to _all values with a specific suffix_ of a _group_ of nodes in the network. +You can send Multicast requests to _all values_ of a _group_ of nodes in the network. Multicast API is accessible from: -`/_CLIENTS/ZWAVE_GATEWAY-/multicast//set` +`/_CLIENTS/ZWAVE_GATEWAY-/multicast//set` -- `value_topic_suffix`: the suffix of the topic of the value I want to control using multicast. +- `optional_id`: an optional id to identify this specific multicast request. -It works like the set value API without the node name and location properties. -If the API is correctly called the same payload of the request will be published +When the request is received the same payload of the request will be published to the topic without `/set` suffix. -Example of multicast command (gateway configured as `named topics`): +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/set` -`zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/thermostat_setpoint/heating/set` +The `payload` must have all the information of a [valueId](https://zwave-js.github.io/node-zwave-js/#/api/valueid?id=valueid): -Payload: `{ "nodes": [5, 7, 9], "value": 25.5}` +```js +interface ValueID { + commandClass: CommandClasses; + endpoint?: number; + property: string | number; + propertyKey?: string | number; +} +``` + +And needs also a `value` and `nodes` property: + +- `value`: `number|string|boolean` the value you want to write +- `nodes`: `number[]` the array of nodes ids that will receive the multicast -Nodes **5, 7, 9** with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: +__EXAMPLE__: -`zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/thermostat_setpoint/heating` +Topic: `zwave/_CLIENTS/ZWAVE_GATEWAY-test/multicast/set` + +Payload: + +```json +{ + "commandClass": 37, + "property": "targetValue", + "value": true, + "nodes": [5, 7, 9] +} +``` + +Nodes **5, 7, 9** with command class `37` (Binary Switch) and property `targetValue` will be set to `true` ### Broadcast @@ -768,23 +792,30 @@ You can send broadcast values to _all values with a specific suffix_ in the netw Broadcast API is accessible from: -`/_CLIENTS/ZWAVE_GATEWAY-/broadcast//set` +`/_CLIENTS/ZWAVE_GATEWAY-/broadcast//set` -- `value_topic_suffix`: the suffix of the topic of the value I want to control using broadcast. +- `optional_id`: an optional id to identify this specific broadcast. -It works like the set value API without the node name and location properties. -If the API is correctly called the same payload of the request will be published +When the request is received the same payload of the request will be published to the topic without `/set` suffix. -Example of broadcast command (gateway configured as `named topics`): +The `payload` is the same as a multicast request (check previous section) without the `nodes` array. + +__EXAMPLE__: -`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating/set` +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/set` -Payload: `25.5` +Payload: -All nodes with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: +```json +{ + "commandClass": 38, + "property": "targetValue", + "value": 50 +} +``` -`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating` +All nodes with command class `38` (Multilevel Switch) will receive a write request to property `targetValue` with a value of `50`. ## :camera: Screenshots diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index 69edc872e87..85a50410b3f 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -1899,7 +1899,7 @@ ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { await broadcastNode.setValue(valueId, value) } catch (error) { - debug(`Error while sending broadcast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}`) + debug(`Error while sending broadcast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) } } } @@ -1918,7 +1918,7 @@ ZwaveClient.prototype.writeMulticast = async function (nodes, valueId, value) { await multicastGroup.setValue(valueId, value) } catch (error) { - debug(`Error while sending multicast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}`) + debug(`Error while sending multicast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) } } } From 854b4fa21d9a31565ba39d685ba828d52c04ff01 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:02:30 +0100 Subject: [PATCH 09/23] fix: replace debug with logger --- lib/Gateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 9c160678c98..4825892cd49 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -497,7 +497,7 @@ async function onMulticastRequest (payload) { const value = payload.value if (!nodes || nodes.length === 0) { - debug('No nodes found in multicast request') + logger.error('No nodes found in multicast request') return } From 42cccfb65eab022e7af625f3c63f411cdef3b5d6 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:02:43 +0100 Subject: [PATCH 10/23] fix: replace debug with logger --- lib/Gateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 4825892cd49..75d2b76b200 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -468,7 +468,7 @@ async function onBroadRequest (payload) { const valueId = payload if (payload.value === undefined) { - debug('No value found in broadcast request') + logger.error('No value found in broadcast request') return } From ddd63c6b04baa8da91dd78a8b97bb8e287d1a1c8 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:02:52 +0100 Subject: [PATCH 11/23] fix: replace debug with logger --- lib/Gateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 75d2b76b200..33ca7693be4 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -475,7 +475,7 @@ async function onBroadRequest (payload) { const error = utils.isValueId(valueId) if (typeof error === 'string') { - debug('Invalid valueId: ' + error) + logger.error('Invalid valueId: ' + error) return } From 0d1cb481b53e5811ed380460af436fd63e7711aa Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:03:00 +0100 Subject: [PATCH 12/23] fix: replace debug with logger --- lib/Gateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 33ca7693be4..237b27d8e3d 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -504,7 +504,7 @@ async function onMulticastRequest (payload) { const error = utils.isValueId(valueId) if (typeof error === 'string') { - debug('Invalid valueId: ' + error) + logger.error('Invalid valueId: ' + error) return } From 0e880b8661aaa56c41d91566a0d5ea4bb8a540f4 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:03:08 +0100 Subject: [PATCH 13/23] fix: replace debug with logger --- lib/Gateway.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 237b27d8e3d..55d7512c27a 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -509,7 +509,7 @@ async function onMulticastRequest (payload) { } if (payload.value === undefined) { - debug('No value found in multicast request') + logger.error('No value found in multicast request') return } From cafd301be4ca95744970f71ebff984af035918d3 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:03:17 +0100 Subject: [PATCH 14/23] fix: replace debug with logger --- lib/ZwaveClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index a0c4dc82074..66ca92550a6 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -2220,7 +2220,7 @@ ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { await broadcastNode.setValue(valueId, value) } catch (error) { - debug(`Error while sending broadcast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) + logger.error(`Error while sending broadcast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) } } } From 4b63eed45bdf8992b8c6654c529c6301ce8979e8 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:03:26 +0100 Subject: [PATCH 15/23] fix: replace debug with logger --- lib/ZwaveClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index 66ca92550a6..13297431c41 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -2239,7 +2239,7 @@ ZwaveClient.prototype.writeMulticast = async function (nodes, valueId, value) { await multicastGroup.setValue(valueId, value) } catch (error) { - debug(`Error while sending multicast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) + logger.error(`Error while sending multicast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) } } } From c686a0f4641c6b5d22187dc4ef00fc362084220a Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:14:02 +0100 Subject: [PATCH 16/23] fix: types --- lib/ZwaveClient.js | 20 ++++++++++++++------ lib/utils.js | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index 13297431c41..d60a955d395 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -2210,8 +2210,8 @@ ZwaveClient.prototype.callApi = async function (apiName, ...args) { /** * Send broadcast write request * - * @param {Object} valueId Zwave valueId object - * @param {Integer|String} value The value to send + * @param {import('zwave-js').ValueID} valueId Zwave valueId object + * @param {unknown} value The value to send */ ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { if (this.driverReady) { @@ -2220,7 +2220,11 @@ ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { await broadcastNode.setValue(valueId, value) } catch (error) { - logger.error(`Error while sending broadcast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) + logger.error( + `Error while sending broadcast ${value} to CC ${valueId.commandClass} ${ + valueId.property + } ${valueId.propertyKey || ''}: ${error.message}` + ) } } } @@ -2229,8 +2233,8 @@ ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { * Send multicast write request to a group of nodes * * @param {number[]} nodes Array of nodes ids - * @param {Object} valueId Zwave valueId object - * @param {Integer|String} value The value to send + * @param {import('zwave-js').ValueID} valueId Zwave valueId object + * @param {unknown} value The value to send */ ZwaveClient.prototype.writeMulticast = async function (nodes, valueId, value) { if (this.driverReady) { @@ -2239,7 +2243,11 @@ ZwaveClient.prototype.writeMulticast = async function (nodes, valueId, value) { await multicastGroup.setValue(valueId, value) } catch (error) { - logger.error(`Error while sending multicast ${value} to CC ${valueId.commandClass} ${valueId.property} ${valueId.propertyKey || ''}: ${error.message}`) + logger.error( + `Error while sending multicast ${value} to CC ${valueId.commandClass} ${ + valueId.property + } ${valueId.propertyKey || ''}: ${error.message}` + ) } } } diff --git a/lib/utils.js b/lib/utils.js index 6e43f079596..12166579fdf 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -50,6 +50,12 @@ function joinProps (...props) { return ret } +/** + * Checks if an object is a valueId, returns error otherwise + * + * @param {*} v the object + * @returns {boolean|string} Returns true if it's a valid valueId, an error string otherwise + */ function isValueId (v) { if (typeof v.commandClass !== 'number' || v.commandClass < 0) { return 'invalid `commandClass`' @@ -57,13 +63,19 @@ function isValueId (v) { if (v.endpoint !== undefined && v.endpoint < 0) { return 'invalid `endpoint`' } - if (v.property === undefined || (typeof v.property !== 'string' && typeof v.property !== 'number')) { + if ( + v.property === undefined || + (typeof v.property !== 'string' && typeof v.property !== 'number') + ) { return 'invalid `property`' } - if (v.propertyKey !== undefined && (typeof v.propertyKey !== 'string' && typeof v.propertyKey !== 'number')) { + if ( + v.propertyKey !== undefined && + typeof v.propertyKey !== 'string' && typeof v.propertyKey !== 'number' + ) { return 'invalid `propertyKey`' } - return true + return true } /** From 22aed8c308f21c89228d42b4d006caa61e70a363 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:16:34 +0100 Subject: [PATCH 17/23] fix: types --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 12166579fdf..27a610f89bc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -53,7 +53,7 @@ function joinProps (...props) { /** * Checks if an object is a valueId, returns error otherwise * - * @param {*} v the object + * @param {import('zwave-js').ValueID} v the object * @returns {boolean|string} Returns true if it's a valid valueId, an error string otherwise */ function isValueId (v) { From f611bccc1a3a85b37a3ad53613809a87a239652c Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Sat, 23 Jan 2021 11:20:38 +0100 Subject: [PATCH 18/23] fix: lint issues --- lib/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 27a610f89bc..a40dd5360bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -71,7 +71,8 @@ function isValueId (v) { } if ( v.propertyKey !== undefined && - typeof v.propertyKey !== 'string' && typeof v.propertyKey !== 'number' + typeof v.propertyKey !== 'string' && + typeof v.propertyKey !== 'number' ) { return 'invalid `propertyKey`' } From de8847bc7eec39f32d76c4d37c5a1c0f6545eddd Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Mar 2021 08:57:38 +0100 Subject: [PATCH 19/23] fix: lint issues and missing fallbacks --- lib/Gateway.js | 44 +++++++++++++++++++++++++++++--------------- lib/MqttClient.js | 2 +- lib/ZwaveClient.js | 15 ++++++++++++--- types/index.d.ts | 12 +++++++++++- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/lib/Gateway.js b/lib/Gateway.js index 39ab89b936b..a06cf55a62d 100755 --- a/lib/Gateway.js +++ b/lib/Gateway.js @@ -495,23 +495,37 @@ async function onApiRequest (topic, apiName, payload) { * @param {string[]} parts * @param {any} payload */ -function onBroadRequest (parts, payload) { - const topic = parts.join('/') - const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) - - if (payload.value === undefined) { - logger.error('No value found in broadcast request') - return - } +async function onBroadRequest (parts, payload) { + if (parts.length > 0) { + // multiple writes (back compatibility mode) + const topic = parts.join('/') + const values = Object.keys(this.topicValues).filter(t => t.endsWith(topic)) + if (values.length > 0) { + // all values are the same type just different node,parse the Payload by using the first one + payload = this.parsePayload( + payload, + this.topicValues[values[0]], + this.topicValues[values[0]].conf + ) + for (let i = 0; i < values.length; i++) { + await this.zwave.writeValue(this.topicValues[values[i]], payload) + } + } + } else { + // try real zwave broadcast + if (payload.value === undefined) { + logger.error('No value found in broadcast request') + return + } - const error = utils.isValueId(valueId) + const error = utils.isValueId(payload) - if (typeof error === 'string') { - logger.error('Invalid valueId: ' + error) - return + if (typeof error === 'string') { + logger.error('Invalid valueId: ' + error) + return + } + await this.zwave.writeBroadcast(payload, payload.value) } - - await this.zwave.writeBroadcast(valueId, value) } /** @@ -520,7 +534,7 @@ function onBroadRequest (parts, payload) { * @param {string[]} parts * @param {any} payload */ -function onWriteRequest (parts, payload) { +async function onWriteRequest (parts, payload) { const valueId = this.topicValues[parts.join('/')] if (valueId) { diff --git a/lib/MqttClient.js b/lib/MqttClient.js index 9c4ea8ceb4e..b6b61ab294d 100644 --- a/lib/MqttClient.js +++ b/lib/MqttClient.js @@ -229,7 +229,7 @@ function onMessageReceived (topic, payload) { switch (action) { case 0: // broadcast - this.emit('broadcastRequest', payload) + this.emit('broadcastRequest', parts.slice(3), payload) // publish back to give a feedback the action has been received // same topic without /set suffix this.publish(parts.join('/'), payload) diff --git a/lib/ZwaveClient.js b/lib/ZwaveClient.js index bd77fa4d4ec..ea11e268db4 100644 --- a/lib/ZwaveClient.js +++ b/lib/ZwaveClient.js @@ -12,7 +12,7 @@ const { CommandClass, libVersion } = require('zwave-js') -const { CommandClasses, Duration } = require('@zwave-js/core') +const { CommandClasses, Duration, ZWaveErrorCodes } = require('@zwave-js/core') const utils = reqlib('/lib/utils.js') const EventEmitter = require('events') const jsonStore = reqlib('/lib/jsonStore.js') @@ -76,7 +76,9 @@ const allowedApis = [ 'beginFirmwareUpdate', 'abortFirmwareUpdate', 'sendCommand', - 'writeValue' + 'writeValue', + 'writeBroadcast', + 'writeMulticast' ] const ZWAVEJS_LOG_FILE = utils.joinPath(storeDir, `zwavejs_${process.pid}.log`) @@ -2529,17 +2531,24 @@ ZwaveClient.prototype.writeBroadcast = async function (valueId, value) { */ ZwaveClient.prototype.writeMulticast = async function (nodes, valueId, value) { if (this.driverReady) { + let fallback = false try { const multicastGroup = this.driver.controller.getMulticastGroup(nodes) - await multicastGroup.setValue(valueId, value) } catch (error) { + fallback = error.code === ZWaveErrorCodes.CC_NotSupported logger.error( `Error while sending multicast ${value} to CC ${valueId.commandClass} ${ valueId.property } ${valueId.propertyKey || ''}: ${error.message}` ) } + // try single writes requests + if (fallback) { + for (const n of nodes) { + await this.writeValue({ ...valueId, nodeId: n }, value) + } + } } } diff --git a/types/index.d.ts b/types/index.d.ts index 6b25bfeeeb0..21dd8599f30 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -227,6 +227,10 @@ export interface MqttClient extends EventEmitter { event: 'broadcastRequest', listener: (parts: string[], payload: any) => void ): this + on( + event: 'multicastRequest', + listener: (payload: any) => void + ): this on( event: 'apiCall', listener: (topic: string, apiNema: string, payload: any) => void @@ -390,7 +394,13 @@ export interface ZwaveClient extends EventEmitter { apiName: string, ...args: any ): Promise<{ success: boolean; message: string; result: any; args: any[] }> - writeValue(valueId: Z2MValueId, value: number | string): Promise + writeBroadcast(valueId: Z2MValueId, value: unknown): Promise + writeMulticast( + nodes: number[], + valueId: Z2MValueId, + value: unknown + ): Promise + writeValue(valueId: Z2MValueId, value: unknown): Promise sendCommand( ctx: { nodeId: number; endpoint: number; commandClass: number }, command: string, From 103575d3d27eb072a06e5dc03aa9d81967de992e Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Mar 2021 09:37:03 +0100 Subject: [PATCH 20/23] fix: broadcast/multicast docs --- docs/guide/mqtt.md | 55 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/docs/guide/mqtt.md b/docs/guide/mqtt.md index 132b0aabf5e..1a6df83c75f 100644 --- a/docs/guide/mqtt.md +++ b/docs/guide/mqtt.md @@ -80,6 +80,8 @@ This are the available apis: - `beginFirmwareUpdate(nodeId, fileName, data, target)`: Starts a firmware update of a node. The `fileName` is used to check the extension (used to detect the firmware file type) and data is a `Buffer` - `abortFirmwareUpdate(nodeId)`: Aborts a firmware update - `writeValue(valueId, value)`: Write a specific value to a [valueId](https://zwave-js.github.io/node-zwave-js/#/api/valueid?id=valueid) +- `writeBroadcast(valueId, value)`: Send a broadcast request to all nodes that support [valueId](https://zwave-js.github.io/node-zwave-js/#/api/valueid?id=valueid) +- `writeMulticast(nodes, valueId, value)`: Send a multicast request to all `nodes` provided that support [valueId](https://zwave-js.github.io/node-zwave-js/#/api/valueid?id=valueid) - `sendCommand(ctx, command, args)`: Send a custom command. - `ctx`:context to get the instance to send the command (`{ nodeId: number, endpoint: number, commandClass: number }`) - `command`: the command name. Check available commands by selecting a CC [here](https://zwave-js.github.io/node-zwave-js/#/api/CCs/index) @@ -169,27 +171,58 @@ I will set the Heating setpoint of the node with id `4` located in the `office` ## Broadcast -You can send broadcast values to _all values with a specific suffix_ in the network. +You can send 2 kind of broadcast requests: -Broadcast API is accessible from: +1. Send it to values to _all values with a specific suffix_ in the network. -`/_CLIENTS/ZWAVE_GATEWAY-/broadcast//set` +Topic: `/_CLIENTS/ZWAVE_GATEWAY-/broadcast//set` - `value_topic_suffix`: the suffix of the topic of the value I want to control using broadcast. - It works like the set value API without the node name and location properties. - If the API is correctly called the same payload of the request will be published - to the topic without `/set` suffix. +It works like the set value API without the node name and location properties. +If the API is correctly called the same payload of the request will be published +to the topic without `/set` suffix. - Example of broadcast command (gateway configured as `named topics`): +Example of broadcast command (gateway configured as `named topics`): - `zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating/set` +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating/set` - Payload: `25.5` +Payload: `25.5` - All nodes with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: +All nodes with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: - `zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating` +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating` + +1. Send a real zwave [broadcast](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=getbroadcastnode) request + +Topic: `/_CLIENTS/ZWAVE_GATEWAY-/broadcast/set` +Payload: + +```js +{ + "commandClass": 38, + "endpoint": 0, + "property": "targetValue", + "value": 80 +} +``` + +## Multicast + +Send a [multicast](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=getmulticastgroup) request to all nodes specified in the array in the payload. If this fails because it's not supported a fallback will try to send multiple single requests: + +Topic: `/_CLIENTS/ZWAVE_GATEWAY-/multicast/set` +Payload: + +```js +{ + "nodes": [2, 3, 4, 6] + "commandClass": 38, + "endpoint": 0, + "property": "targetValue", + "value": 80 +} +``` ## Special topics From 4100f38994c3504901ba524dd7a05390bdb13fb7 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Mar 2021 09:39:20 +0100 Subject: [PATCH 21/23] fix: lint issues --- types/index.d.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 21dd8599f30..ae2a16ca5be 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -227,10 +227,7 @@ export interface MqttClient extends EventEmitter { event: 'broadcastRequest', listener: (parts: string[], payload: any) => void ): this - on( - event: 'multicastRequest', - listener: (payload: any) => void - ): this + on(event: 'multicastRequest', listener: (payload: any) => void): this on( event: 'apiCall', listener: (topic: string, apiNema: string, payload: any) => void From c35ecb48cced9c2b88d0057cf3748f4fb61b748c Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Wed, 10 Mar 2021 10:18:11 +0100 Subject: [PATCH 22/23] fix: docs nits --- docs/guide/mqtt.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/guide/mqtt.md b/docs/guide/mqtt.md index 1a6df83c75f..74396cbdd72 100644 --- a/docs/guide/mqtt.md +++ b/docs/guide/mqtt.md @@ -171,10 +171,13 @@ I will set the Heating setpoint of the node with id `4` located in the `office` ## Broadcast -You can send 2 kind of broadcast requests: +You can send two kind of broadcast requests: 1. Send it to values to _all values with a specific suffix_ in the network. +> [!NOTE] +> This creates a LOT of traffic and can have a significant performance impact. + Topic: `/_CLIENTS/ZWAVE_GATEWAY-/broadcast//set` - `value_topic_suffix`: the suffix of the topic of the value I want to control using broadcast. @@ -185,13 +188,13 @@ to the topic without `/set` suffix. Example of broadcast command (gateway configured as `named topics`): -`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating/set` +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/38/0/targetValue/set` Payload: `25.5` -All nodes with command class `thermostat_setpoint` and value `heating` will be set to `25.5` and I will get the same value on the topic: +All nodes with a valueId **Command class** `38` (Multilevel Switch), **Endpoint** `0` will receive a write request of value `25.5` to **property** `targetValue` and will get the same value (as feedback) on the topic: -`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/thermostat_setpoint/heating` +`zwave/_CLIENTS/ZWAVE_GATEWAY-test/broadcast/38/0/targetValue` 1. Send a real zwave [broadcast](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=getbroadcastnode) request @@ -209,7 +212,10 @@ Payload: ## Multicast -Send a [multicast](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=getmulticastgroup) request to all nodes specified in the array in the payload. If this fails because it's not supported a fallback will try to send multiple single requests: +Send a [multicast](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=getmulticastgroup) request to all nodes specified in the array in the payload. If this fails because it's not supported a fallback will try to send multiple single requests + +> [!NOTE] +> Multicast requests have no delay between individual nodes reactions Topic: `/_CLIENTS/ZWAVE_GATEWAY-/multicast/set` Payload: From 19a8339d38a320069c58fb17d9da8ce8ebbaba14 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 15 Mar 2021 09:07:57 +0100 Subject: [PATCH 23/23] docs: fix docs/guide/mqtt.md --- docs/guide/mqtt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/mqtt.md b/docs/guide/mqtt.md index 74396cbdd72..ffaa86021fb 100644 --- a/docs/guide/mqtt.md +++ b/docs/guide/mqtt.md @@ -173,7 +173,7 @@ I will set the Heating setpoint of the node with id `4` located in the `office` You can send two kind of broadcast requests: -1. Send it to values to _all values with a specific suffix_ in the network. +1. Send it to _all values with a specific suffix_ in the network. > [!NOTE] > This creates a LOT of traffic and can have a significant performance impact.