node-red-contrib-knx-ultimate 2.1.53 → 2.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -2
- package/nodes/knxUltimateHueBattery.html +3 -2
- package/nodes/knxUltimateHueButton.html +2 -1
- package/nodes/knxUltimateHueLightSensor.html +3 -1
- package/nodes/knxUltimateHueMotion.html +3 -4
- package/nodes/knxUltimateHueScene.html +57 -28
- package/nodes/knxUltimateHueScene.js +67 -60
- package/nodes/knxUltimateHueTapDial.html +3 -1
- package/nodes/knxUltimateHueTemperatureSensor.html +2 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
|
-
<b>Version 2.1.
|
|
11
|
-
-
|
|
10
|
+
<b>Version 2.1.55</b> - October 2023<br/>
|
|
11
|
+
- HUE Scene: removed non existant scene 0.<br/>
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
<b>Version 2.1.54</b> - October 2023<br/>
|
|
15
|
+
- HUE Scene: NEW: you can now use also the Datapoint 18.001, to recall a scene via a KNX scene pushbutton.<br/>
|
|
12
16
|
</p>
|
|
13
17
|
<p>
|
|
14
18
|
<b>Version 2.1.52</b> - October 2023<br/>
|
|
@@ -195,6 +195,8 @@
|
|
|
195
195
|
|
|
196
196
|
<br/>
|
|
197
197
|
|
|
198
|
+
<p>
|
|
199
|
+
<b>KNX</b>
|
|
198
200
|
</p>
|
|
199
201
|
|
|
200
202
|
<div class="form-row">
|
|
@@ -210,8 +212,7 @@
|
|
|
210
212
|
<input type="text" id="node-input-namebatterysensor" style="width:200px;margin-left: 5px; text-align: left;">
|
|
211
213
|
</div>
|
|
212
214
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
|
|
215
216
|
|
|
216
217
|
<br/>
|
|
217
218
|
<br/>
|
|
@@ -194,6 +194,8 @@
|
|
|
194
194
|
|
|
195
195
|
<br/>
|
|
196
196
|
|
|
197
|
+
<p>
|
|
198
|
+
<b>KNX</b>
|
|
197
199
|
</p>
|
|
198
200
|
|
|
199
201
|
<div class="form-row">
|
|
@@ -209,7 +211,7 @@
|
|
|
209
211
|
<input type="text" id="node-input-namelightsensor" style="width:200px;margin-left: 5px; text-align: left;">
|
|
210
212
|
</div>
|
|
211
213
|
|
|
212
|
-
|
|
214
|
+
|
|
213
215
|
|
|
214
216
|
|
|
215
217
|
<br/>
|
|
@@ -194,6 +194,8 @@
|
|
|
194
194
|
|
|
195
195
|
<br/>
|
|
196
196
|
|
|
197
|
+
<p>
|
|
198
|
+
<b>KNX</b>
|
|
197
199
|
</p>
|
|
198
200
|
|
|
199
201
|
<div class="form-row">
|
|
@@ -209,10 +211,7 @@
|
|
|
209
211
|
<input type="text" id="node-input-namemotion" style="width:200px;margin-left: 5px; text-align: left;">
|
|
210
212
|
</div>
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<br/>
|
|
214
|
+
s<br/>
|
|
216
215
|
<br/>
|
|
217
216
|
<br/>
|
|
218
217
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
namescene: { value: "" },
|
|
12
12
|
GAscene: { value: "" },
|
|
13
13
|
dptscene: { value: "" },
|
|
14
|
+
valscene: { value: 0 }, // the scene number or true/false
|
|
14
15
|
|
|
15
16
|
hueDevice: { value: "" },
|
|
16
17
|
hueSceneRecallType: { value: "active" }
|
|
@@ -41,6 +42,14 @@
|
|
|
41
42
|
var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
|
|
42
43
|
var oNodeServerHue = RED.nodes.node($("#node-input-serverHue").val()); // Store the config-node
|
|
43
44
|
|
|
45
|
+
for (let index = 1; index < 64; index++) {
|
|
46
|
+
$("#node-input-valscene").append($("<option></option>")
|
|
47
|
+
.attr("value", index)
|
|
48
|
+
.text("Scene n. " + index)
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
$("#node-input-valscene").val(node.valscene)
|
|
52
|
+
|
|
44
53
|
// 19/02/2020 Used to get the server sooner als deploy.
|
|
45
54
|
$("#node-input-server").change(function () {
|
|
46
55
|
try {
|
|
@@ -69,13 +78,14 @@
|
|
|
69
78
|
// ########################
|
|
70
79
|
$.getJSON('knxUltimateDpts', (data) => {
|
|
71
80
|
data.forEach(dpt => {
|
|
72
|
-
if (dpt.value.startsWith("1.")) {
|
|
81
|
+
if (dpt.value.startsWith("1.") || dpt.value.startsWith("18.")) {
|
|
73
82
|
$("#node-input-dptscene").append($("<option></option>")
|
|
74
83
|
.attr("value", dpt.value)
|
|
75
84
|
.text(dpt.text))
|
|
76
85
|
}
|
|
77
86
|
});
|
|
78
87
|
$("#node-input-dptscene").val(this.dptscene)
|
|
88
|
+
ShowHideValScene();
|
|
79
89
|
})
|
|
80
90
|
|
|
81
91
|
// Autocomplete suggestion with ETS csv File
|
|
@@ -86,11 +96,13 @@
|
|
|
86
96
|
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
87
97
|
response($.map(data, function (value, key) {
|
|
88
98
|
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
89
|
-
if (fullSearch(sSearch, request.term
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
if (fullSearch(sSearch, request.term)) {
|
|
100
|
+
if (value.dpt.startsWith("1.") || value.dpt.startsWith("18.")) {
|
|
101
|
+
return {
|
|
102
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
103
|
+
value: value.ga // Value
|
|
104
|
+
}
|
|
105
|
+
} else { return null; }
|
|
94
106
|
} else {
|
|
95
107
|
return null;
|
|
96
108
|
}
|
|
@@ -111,8 +123,22 @@
|
|
|
111
123
|
});
|
|
112
124
|
// ########################
|
|
113
125
|
|
|
114
|
-
|
|
115
|
-
|
|
126
|
+
function ShowHideValScene() {
|
|
127
|
+
if ($("#node-input-dptscene").val() !== null) {
|
|
128
|
+
// Adapt the scene value/true
|
|
129
|
+
if ($("#node-input-dptscene").val().startsWith("1.")) {
|
|
130
|
+
$("#divValScene").hide();
|
|
131
|
+
//$("#node-input-valscene").attr("disabled", "disabled");
|
|
132
|
+
} else {
|
|
133
|
+
$("#divValScene").show()
|
|
134
|
+
//$("#node-input-valscene").removeAttr("disabled");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
$("#node-input-dptscene").on("change", function () {
|
|
139
|
+
ShowHideValScene()
|
|
140
|
+
});
|
|
141
|
+
ShowHideValScene();
|
|
116
142
|
|
|
117
143
|
// Autocomplete suggestion with HUE
|
|
118
144
|
$("#node-input-name").autocomplete({
|
|
@@ -137,10 +163,7 @@
|
|
|
137
163
|
$('#node-input-hueDevice').val(ui.item.hueDevice);
|
|
138
164
|
}
|
|
139
165
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// ########################
|
|
143
|
-
|
|
166
|
+
// ########################
|
|
144
167
|
|
|
145
168
|
},
|
|
146
169
|
oneditsave: function () {
|
|
@@ -163,7 +186,7 @@
|
|
|
163
186
|
<br />
|
|
164
187
|
<br />
|
|
165
188
|
<p align="center">
|
|
166
|
-
<i class="
|
|
189
|
+
<i class="fa fa-star-and-crescent fa-bounce fa-8x"></i>
|
|
167
190
|
</p>
|
|
168
191
|
<br />
|
|
169
192
|
<label for="node-input-server" >
|
|
@@ -188,7 +211,7 @@
|
|
|
188
211
|
|
|
189
212
|
<div class="form-row">
|
|
190
213
|
<label for="node-input-hueDevice" >
|
|
191
|
-
<i class="fa fa-play-circle"></i> Hue
|
|
214
|
+
<i class="fa fa-play-circle"></i> Hue Scene</label>
|
|
192
215
|
<input type="text" id="node-input-name" placeholder="Enter your hue device name" />
|
|
193
216
|
<input type="hidden" id="node-input-hueDevice" />
|
|
194
217
|
</div>
|
|
@@ -205,23 +228,28 @@
|
|
|
205
228
|
|
|
206
229
|
<br/>
|
|
207
230
|
|
|
231
|
+
<p>
|
|
232
|
+
<b>KNX</b>
|
|
208
233
|
</p>
|
|
209
234
|
|
|
210
|
-
|
|
211
|
-
|
|
235
|
+
<div class="form-row">
|
|
236
|
+
<label for="node-input-namescene" style="width:100px;"><i class="fa fa-play-circle-o"></i> Recall</label>
|
|
212
237
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
<label for="node-input-GAscene" style="width:20px;">GA</label>
|
|
239
|
+
<input type="text" id="node-input-GAscene" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
240
|
+
|
|
241
|
+
<label for="node-input-dptscene" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
|
|
242
|
+
<select id="node-input-dptscene" style="width:140px;"></select>
|
|
243
|
+
|
|
244
|
+
<label for="node-input-namescene" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
|
|
245
|
+
<input type="text" id="node-input-namescene" style="width:200px;margin-left: 5px; text-align: left;">
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div class="form-row" id="divValScene" hidden>
|
|
249
|
+
<label for="node-input-valscene" style="width:100px;"></label>
|
|
250
|
+
<label for="node-input-valscene" style="width:20px;">#</label>
|
|
251
|
+
<select id="node-input-valscene" style="width:180px;margin-left: 5px; text-align: left;"></select>
|
|
223
252
|
</div>
|
|
224
|
-
|
|
225
253
|
|
|
226
254
|
<br/>
|
|
227
255
|
<br/>
|
|
@@ -246,7 +274,8 @@ Start typing in the GA field, the name or group address of your KNX device, the
|
|
|
246
274
|
|
|
247
275
|
|Property|Description|
|
|
248
276
|
|--|--|
|
|
249
|
-
|
|
|
277
|
+
| Recall | Choose your group address to be used for recalling the HUE scene. In case of Datapoint 1.x, send *true* to that group address to recall the scene, *false* to switch off all lights belonging to the scene. |
|
|
278
|
+
| # | Select the KNX scene number. Visible only with datapoint 18.001. |
|
|
250
279
|
|
|
251
280
|
<br/>
|
|
252
281
|
|
|
@@ -1,71 +1,78 @@
|
|
|
1
|
-
const dptlib = require('./../KNXEngine/src/dptlib')
|
|
1
|
+
const dptlib = require('./../KNXEngine/src/dptlib');
|
|
2
2
|
|
|
3
3
|
module.exports = function (RED) {
|
|
4
4
|
function knxUltimateHueScene(config) {
|
|
5
|
-
RED.nodes.createNode(this, config)
|
|
6
|
-
const node = this
|
|
7
|
-
node.server = RED.nodes.getNode(config.server)
|
|
8
|
-
node.serverHue = RED.nodes.getNode(config.serverHue)
|
|
9
|
-
node.topic = node.name
|
|
10
|
-
node.name = config.name === undefined ? 'Hue' : config.name
|
|
11
|
-
node.dpt = ''
|
|
12
|
-
node.notifyreadrequest = false
|
|
13
|
-
node.notifyreadrequestalsorespondtobus = 'false'
|
|
14
|
-
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
15
|
-
node.notifyresponse = false
|
|
16
|
-
node.notifywrite = true
|
|
17
|
-
node.initialread = true
|
|
18
|
-
node.listenallga = true // Don't remove
|
|
19
|
-
node.outputtype = 'write'
|
|
20
|
-
node.outputRBE = false // Apply or not RBE to the output (Messages coming from flow)
|
|
21
|
-
node.inputRBE = false // Apply or not RBE to the input (Messages coming from BUS)
|
|
22
|
-
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
23
|
-
node.passthrough = 'no'
|
|
24
|
-
node.formatmultiplyvalue = 1
|
|
25
|
-
node.formatnegativevalue = 'leave'
|
|
26
|
-
node.formatdecimalsvalue = 2
|
|
5
|
+
RED.nodes.createNode(this, config);
|
|
6
|
+
const node = this;
|
|
7
|
+
node.server = RED.nodes.getNode(config.server);
|
|
8
|
+
node.serverHue = RED.nodes.getNode(config.serverHue);
|
|
9
|
+
node.topic = node.name;
|
|
10
|
+
node.name = config.name === undefined ? 'Hue' : config.name;
|
|
11
|
+
node.dpt = '';
|
|
12
|
+
node.notifyreadrequest = false;
|
|
13
|
+
node.notifyreadrequestalsorespondtobus = 'false';
|
|
14
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = '';
|
|
15
|
+
node.notifyresponse = false;
|
|
16
|
+
node.notifywrite = true;
|
|
17
|
+
node.initialread = true;
|
|
18
|
+
node.listenallga = true; // Don't remove
|
|
19
|
+
node.outputtype = 'write';
|
|
20
|
+
node.outputRBE = false; // Apply or not RBE to the output (Messages coming from flow)
|
|
21
|
+
node.inputRBE = false; // Apply or not RBE to the input (Messages coming from BUS)
|
|
22
|
+
node.currentPayload = ''; // Current value for the RBE input and for the .previouspayload msg
|
|
23
|
+
node.passthrough = 'no';
|
|
24
|
+
node.formatmultiplyvalue = 1;
|
|
25
|
+
node.formatnegativevalue = 'leave';
|
|
26
|
+
node.formatdecimalsvalue = 2;
|
|
27
27
|
|
|
28
28
|
// Used to call the status update from the config node.
|
|
29
29
|
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
30
30
|
|
|
31
|
-
}
|
|
31
|
+
};
|
|
32
32
|
// Used to call the status update from the HUE config node.
|
|
33
33
|
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
34
|
-
if (payload === undefined) return
|
|
35
|
-
const dDate = new Date()
|
|
36
|
-
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
37
|
-
node.status({ fill, shape, text: text + ' ' + payload + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ')' })
|
|
38
|
-
}
|
|
34
|
+
if (payload === undefined) return;
|
|
35
|
+
const dDate = new Date();
|
|
36
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString();
|
|
37
|
+
node.status({ fill, shape, text: text + ' ' + payload + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ')' });
|
|
38
|
+
};
|
|
39
39
|
|
|
40
40
|
// This function is called by the knx-hue config node
|
|
41
41
|
node.handleSend = msg => {
|
|
42
|
-
let state = {}
|
|
42
|
+
let state = {};
|
|
43
43
|
try {
|
|
44
44
|
switch (msg.knx.destination) {
|
|
45
45
|
case config.GAscene:
|
|
46
|
-
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptscene))
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptscene));
|
|
47
|
+
if (config.dptscene.startsWith("1.")) {
|
|
48
|
+
if (msg.payload === true) {
|
|
49
|
+
state = { recall: { action: config.hueSceneRecallType } };
|
|
50
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene');
|
|
51
|
+
} else {
|
|
52
|
+
// Turn off all light belonging to the scene
|
|
53
|
+
(async () => {
|
|
54
|
+
try {
|
|
55
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'stopScene');
|
|
56
|
+
} catch (error) {
|
|
57
|
+
RED.log.error('KNXUltimateHUEConfig: classHUE: handleQueue: stopScene: ' + error.message);
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
}
|
|
50
61
|
} else {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.log('KNXUltimateHUEConfig: classHUE: handleQueue: stopScene: ' + error.message)
|
|
57
|
-
}
|
|
58
|
-
})()
|
|
62
|
+
if (Number(config.valscene) === msg.payload.scenenumber && msg.payload.save_recall === 0) {
|
|
63
|
+
state = { recall: { action: config.hueSceneRecallType } };
|
|
64
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene');
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
|
-
node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload })
|
|
61
|
-
break
|
|
67
|
+
node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload });
|
|
68
|
+
break;
|
|
62
69
|
default:
|
|
63
|
-
break
|
|
70
|
+
break;
|
|
64
71
|
}
|
|
65
72
|
} catch (error) {
|
|
66
|
-
node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
73
|
+
node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
|
|
67
74
|
}
|
|
68
|
-
}
|
|
75
|
+
};
|
|
69
76
|
|
|
70
77
|
node.handleSendHUE = _event => {
|
|
71
78
|
try {
|
|
@@ -90,33 +97,33 @@ module.exports = function (RED) {
|
|
|
90
97
|
// }
|
|
91
98
|
}
|
|
92
99
|
} catch (error) {
|
|
93
|
-
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
100
|
+
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' });
|
|
94
101
|
}
|
|
95
|
-
}
|
|
102
|
+
};
|
|
96
103
|
|
|
97
104
|
// On each deploy, unsubscribe+resubscribe
|
|
98
105
|
if (node.server) {
|
|
99
|
-
node.server.removeClient(node)
|
|
100
|
-
node.server.addClient(node)
|
|
106
|
+
node.server.removeClient(node);
|
|
107
|
+
node.server.addClient(node);
|
|
101
108
|
}
|
|
102
109
|
if (node.serverHue) {
|
|
103
|
-
node.serverHue.removeClient(node)
|
|
104
|
-
node.serverHue.addClient(node)
|
|
110
|
+
node.serverHue.removeClient(node);
|
|
111
|
+
node.serverHue.addClient(node);
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
node.on('input', function (msg) {
|
|
108
115
|
|
|
109
|
-
})
|
|
116
|
+
});
|
|
110
117
|
|
|
111
118
|
node.on('close', function (done) {
|
|
112
119
|
if (node.server) {
|
|
113
|
-
node.server.removeClient(node)
|
|
120
|
+
node.server.removeClient(node);
|
|
114
121
|
}
|
|
115
122
|
if (node.serverHue) {
|
|
116
|
-
node.serverHue.removeClient(node)
|
|
123
|
+
node.serverHue.removeClient(node);
|
|
117
124
|
}
|
|
118
|
-
done()
|
|
119
|
-
})
|
|
125
|
+
done();
|
|
126
|
+
});
|
|
120
127
|
}
|
|
121
|
-
RED.nodes.registerType('knxUltimateHueScene', knxUltimateHueScene)
|
|
122
|
-
}
|
|
128
|
+
RED.nodes.registerType('knxUltimateHueScene', knxUltimateHueScene);
|
|
129
|
+
};
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
<br/>
|
|
196
196
|
|
|
197
197
|
<p>
|
|
198
|
-
<b>
|
|
198
|
+
<b>KNX</b>
|
|
199
199
|
</p>
|
|
200
200
|
|
|
201
201
|
<div class="form-row">
|
|
@@ -211,8 +211,6 @@
|
|
|
211
211
|
<input type="text" id="node-input-nametemperaturesensor" style="width:200px;margin-left: 5px; text-align: left;">
|
|
212
212
|
</div>
|
|
213
213
|
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
214
|
|
|
217
215
|
<br/>
|
|
218
216
|
<br/>
|
|
@@ -235,7 +233,7 @@ Start typing in the GA field, the name or group address of your KNX device, the
|
|
|
235
233
|
| HUE Bridge | Select the HUE Bridge to be used |
|
|
236
234
|
| Hue Sensor | HUE sensor to be used. The avaiable buttons start showing up while you're typing.|
|
|
237
235
|
|
|
238
|
-
**
|
|
236
|
+
**KNX**
|
|
239
237
|
|Property|Description|
|
|
240
238
|
|--|--|
|
|
241
239
|
| Temp | temperature value, in Celsius degrees |
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=16.0.0"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.1.
|
|
6
|
+
"version": "2.1.55",
|
|
7
7
|
"description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable. With integrated Philips HUE devices handling.",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"binary-parser": "2.2.1",
|