node-red-contrib-knx-ultimate 1.3.24 → 1.3.28
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 +20 -0
- package/img/wiki/LoadControlSimple.png +0 -0
- package/img/wiki/viewer1.png +0 -0
- package/img/wiki/viewer2.png +0 -0
- package/nodes/icons/node-eye-icon.svg +10 -0
- package/nodes/knxUltimate-config.js +7 -6
- package/nodes/knxUltimateLoadControl.js +60 -53
- package/nodes/knxUltimateViewer.html +71 -0
- package/nodes/knxUltimateViewer.js +123 -0
- package/nodes/locales/de/knxUltimateLoadControl.json +2 -2
- package/nodes/locales/de/knxUltimateViewer.html +14 -0
- package/nodes/locales/de/knxUltimateViewer.json +7 -0
- package/nodes/locales/en-US/knxUltimateLoadControl.json +2 -2
- package/nodes/locales/en-US/knxUltimateViewer.html +14 -0
- package/nodes/locales/en-US/knxUltimateViewer.json +7 -0
- package/nodes/locales/it/knxUltimateLoadControl.json +2 -2
- package/nodes/locales/it/knxUltimateViewer.html +14 -0
- package/nodes/locales/it/knxUltimateViewer.json +7 -0
- package/nodes/locales/zh-CN/knxUltimateLoadControl.json +2 -2
- package/nodes/locales/zh-CN/knxUltimateViewer.html +14 -0
- package/nodes/locales/zh-CN/knxUltimateViewer.json +7 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
[](https://www.paypal.me/techtoday)
|
|
4
4
|
|
|
5
5
|
<br/>
|
|
6
|
+
|
|
7
|
+
# CHANGELOG
|
|
8
|
+
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 1.3.28</b> - February 2022<br/>
|
|
11
|
+
- NEW: KNX Viewer: this node allow you to see all datapints and values in a dashboard wirget. https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/knxUltimateViewer<br/>
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
<b>Version 1.3.27</b> - February 2022<br/>
|
|
15
|
+
- Load Control: minor fixes + issue a KNX read of Watt values, in case the GA doesn't automatically send a power value on change.<br/>
|
|
16
|
+
</p>
|
|
17
|
+
<p>
|
|
18
|
+
<b>Version 1.3.26</b> - February 2022<br/>
|
|
19
|
+
- FIX: fix a crash occurring it the KNX Gateway is set to "emulate" (that means, don't write to the bus).<br/>
|
|
20
|
+
</p>
|
|
21
|
+
<p>
|
|
22
|
+
<b>Version 1.3.25</b> - February 2022<br/>
|
|
23
|
+
- FIX: Load Control: measure unit was Wh. Corrected in W. Thank @Mauro of VivereSmart Facebook group https://www.facebook.com/groups/viveresmart<br/>
|
|
24
|
+
- Load Control: Added more info to the output message. Updated the online help.<br/>
|
|
25
|
+
</p>
|
|
6
26
|
<p>
|
|
7
27
|
<b>Version 1.3.24</b> - February 2022<br/>
|
|
8
28
|
- NEW: Load Control node: switch off your device if you're exceeding the Watt limit of your house.<br/>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg width="40px" height="60px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
|
3
|
+
<!-- Generated by Pixelmator Pro 2.2 -->
|
|
4
|
+
<g id="group">
|
|
5
|
+
<g id="group-1">
|
|
6
|
+
<path id="Percorso" d="M34 256 L60.2 282.2 C168.2 390.2 343.9 390.2 451.9 282.2 L478 256 451.8 229.8 C343.8 121.8 168.1 121.8 60.1 229.8 Z M256 382.2 C180.2 382.2 104.4 353.3 46.7 295.6 L13.8 262.7 C10.1 259 10.1 253 13.8 249.2 L46.7 216.3 C162.1 100.9 349.9 100.9 465.3 216.3 L498.2 249.2 C501.9 252.9 501.9 258.9 498.2 262.7 L465.3 295.6 C407.6 353.3 331.8 382.2 256 382.2 Z" fill="#ffffff" fill-opacity="1" stroke="none"/>
|
|
7
|
+
<path id="Percorso-1" d="M256 183.5 C216 183.5 183.5 216 183.5 256 183.5 296 216 328.5 256 328.5 296 328.5 328.5 296 328.5 256 328.5 216 296 183.5 256 183.5 Z M256 347.5 C205.5 347.5 164.5 306.4 164.5 256 164.5 205.5 205.6 164.5 256 164.5 306.4 164.5 347.5 205.6 347.5 256 347.5 306.5 306.5 347.5 256 347.5 Z" fill="#ffffff" fill-opacity="1" stroke="none"/>
|
|
8
|
+
</g>
|
|
9
|
+
</g>
|
|
10
|
+
</svg>
|
|
@@ -1115,15 +1115,16 @@ return msg;`, "helplink": "https://github.com/Supergiovane/node-red-contrib-knx-
|
|
|
1115
1115
|
}
|
|
1116
1116
|
|
|
1117
1117
|
// 26/12/2021 If the KNXEngine is busy waiting for telegram's ACK, exit
|
|
1118
|
-
if (
|
|
1119
|
-
node.
|
|
1120
|
-
|
|
1121
|
-
if (node.
|
|
1118
|
+
if (node.host.toUpperCase() !== "EMULATE") {
|
|
1119
|
+
if (!node.knxConnection._getClearToSend()) {
|
|
1120
|
+
node.lockHandleTelegramQueue = false; // Unlock the function
|
|
1121
|
+
if (node.telegramsQueue.length > 0) {
|
|
1122
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.warn("knxUltimate-config: handleTelegramQueue: the KNXEngine is busy or is waiting for a telegram ACK with seqNumner " + node.knxConnection._getSeqNumber() + ". Delay handling queue.");
|
|
1123
|
+
}
|
|
1124
|
+
return;
|
|
1122
1125
|
}
|
|
1123
|
-
return;
|
|
1124
1126
|
}
|
|
1125
1127
|
|
|
1126
|
-
|
|
1127
1128
|
// Retrieving oKNXMessage { grpaddr, payload,dpt,outputtype (write or response),nodecallerid (node caller)}. 06/03/2020 "Read" request does have the lower priority in the queue, so firstly, i search for "read" telegrams and i move it on the top of the queue pile.
|
|
1128
1129
|
var aTelegramsFiltered = [];
|
|
1129
1130
|
aTelegramsFiltered = node.telegramsQueue.filter(a => a.outputtype !== "read");
|
|
@@ -21,6 +21,10 @@ module.exports = function (RED) {
|
|
|
21
21
|
node.inputRBE = "false"
|
|
22
22
|
node.isLoadControlNode = true; // Signal to config node, that this is a Load Control node
|
|
23
23
|
node.initialread = true;
|
|
24
|
+
node.formatmultiplyvalue = 1;
|
|
25
|
+
node.formatnegativevalue = "zero";
|
|
26
|
+
node.formatdecimalsvalue = 0;
|
|
27
|
+
|
|
24
28
|
node.sheddingStage = 0;
|
|
25
29
|
node.timerIncreaseShedding = null;
|
|
26
30
|
node.timerDecreaseShedding = null;
|
|
@@ -28,7 +32,7 @@ module.exports = function (RED) {
|
|
|
28
32
|
node.sheddingRestoreDelay = config.sheddingRestoreDelay !== undefined ? config.sheddingRestoreDelay * 1000 : 60000;
|
|
29
33
|
|
|
30
34
|
node.totalWatt = 0; // Current total watt consumption
|
|
31
|
-
node.wattLimit = config.wattLimit === undefined ? 3000 : config.wattLimit;
|
|
35
|
+
node.wattLimit = config.wattLimit === undefined ? 3000 : Number(config.wattLimit);
|
|
32
36
|
node.deviceList = [];
|
|
33
37
|
for (let index = 1; index < 6; index++) {
|
|
34
38
|
// Eval, the magic. Fill in the device list. DEFINITION DEVICELIST
|
|
@@ -69,17 +73,13 @@ module.exports = function (RED) {
|
|
|
69
73
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
// Used to call the status update from
|
|
73
|
-
node.setLocalStatus = ({ fill, shape
|
|
76
|
+
// Used to call the status update from this node
|
|
77
|
+
node.setLocalStatus = ({ fill = "green", shape = "ring", text = "" }) => {
|
|
78
|
+
if (text !== "") text += ".";
|
|
74
79
|
var dDate = new Date();
|
|
75
|
-
// 30/08/2019 Display only the things selected in the config
|
|
76
|
-
_GA = (typeof _GA == "undefined" || GA == "") ? "" : "(" + GA + ") ";
|
|
77
|
-
_devicename = devicename || "";
|
|
78
|
-
_dpt = (typeof dpt == "undefined" || dpt == "") ? "" : " DPT" + dpt;
|
|
79
80
|
try {
|
|
80
|
-
node.status({ fill: fill, shape: shape, text:
|
|
81
|
+
node.status({ fill: fill, shape: shape, text: text + " Shed:" + node.sheddingStage + " Power:" + node.totalWatt + "W" + " Limit:" + node.wattLimit + "W (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" });
|
|
81
82
|
} catch (error) {
|
|
82
|
-
node.status({ fill: fill, shape: shape, text: _GA + payload + ((true === true) ? " " + _devicename : "") + (false === true ? _dpt : "") + (true === true ? " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" : "") + " " + text });
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
}
|
|
@@ -88,9 +88,10 @@ module.exports = function (RED) {
|
|
|
88
88
|
node.handleSend = msg => {
|
|
89
89
|
|
|
90
90
|
// Update the Total Watt?
|
|
91
|
-
if (msg.topic === node.topic) {
|
|
91
|
+
if (msg.topic === node.topic && msg.payload !== "" && msg.payload !== null && msg.payload !== undefined) {
|
|
92
92
|
node.totalWatt = msg.payload;
|
|
93
|
-
//
|
|
93
|
+
// Update current consumption
|
|
94
|
+
node.setLocalStatus({ fill: "blue" });
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -108,7 +109,7 @@ module.exports = function (RED) {
|
|
|
108
109
|
// monitorVal: null
|
|
109
110
|
|
|
110
111
|
var oRow = node.deviceList[i];
|
|
111
|
-
if (msg.topic === oRow.monitorGA) {
|
|
112
|
+
if (msg.topic === oRow.monitorGA && msg.payload !== null && msg.payload !== undefined) {
|
|
112
113
|
oRow.monitorVal = msg.payload;
|
|
113
114
|
//node.setLocalStatus({ fill: "blue", shape: "dot", text: "Updated", payload: oRow.monitorVal, GA: msg.topic, dpt: "", devicename: oRow.monitorName });
|
|
114
115
|
}
|
|
@@ -121,21 +122,24 @@ module.exports = function (RED) {
|
|
|
121
122
|
// 03/02/2022 perform a read on all GA in the list
|
|
122
123
|
node.initialReadAllDevicesInRules = () => {
|
|
123
124
|
if (node.server) {
|
|
125
|
+
// Read status of the Total Power GA
|
|
126
|
+
node.server.writeQueueAdd({ grpaddr: node.topic, payload: "", dpt: "", outputtype: "read", nodecallerid: node.id });
|
|
127
|
+
|
|
124
128
|
for (var i = 0; i < node.deviceList.length; i++) {
|
|
125
129
|
let grpaddr = node.deviceList[i].monitorGA;
|
|
126
|
-
if (grpaddr !== undefined && grpaddr !== "") {
|
|
130
|
+
if (grpaddr !== undefined && grpaddr !== "" && grpaddr !== null) {
|
|
127
131
|
try {
|
|
128
132
|
// Check if it's a group address
|
|
129
133
|
let ret = Address.KNXAddress.createFromString(grpaddr, Address.KNXAddress.TYPE_GROUP);
|
|
130
|
-
node.setLocalStatus({ fill: "grey", shape: "dot", text: "Read
|
|
134
|
+
//node.setLocalStatus({ fill: "grey", shape: "dot", text: "Read Power from BUS" });
|
|
131
135
|
node.server.writeQueueAdd({ grpaddr: grpaddr, payload: "", dpt: "", outputtype: "read", nodecallerid: node.id });
|
|
132
136
|
} catch (error) {
|
|
133
|
-
node.setLocalStatus({ fill: "grey", shape: "dot", text: "Not a KNX GA " + error.message
|
|
137
|
+
node.setLocalStatus({ fill: "grey", shape: "dot", text: "Not a KNX GA " + error.message });
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
} else {
|
|
138
|
-
node.setLocalStatus({ fill: "red", shape: "ring", text: "No gateway selected. Unable to read from KNX bus"
|
|
142
|
+
node.setLocalStatus({ fill: "red", shape: "ring", text: "No gateway selected. Unable to read from KNX bus" });
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -146,16 +150,18 @@ module.exports = function (RED) {
|
|
|
146
150
|
// Increase shedding timer (Switch off devices)
|
|
147
151
|
if (node.timerIncreaseShedding !== null) clearInterval(node.timerIncreaseShedding);
|
|
148
152
|
node.timerIncreaseShedding = setInterval(() => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
node.
|
|
158
|
-
|
|
153
|
+
if (node.server) {
|
|
154
|
+
// Issue a READ request to the main Watt GA
|
|
155
|
+
// The devices should automatically send a value on change, but... you know....
|
|
156
|
+
if (node.topic !== undefined && node.topic !== null && node.topic !== "") node.server.writeQueueAdd({ grpaddr: node.topic, payload: "", dpt: "", outputtype: "read", nodecallerid: node.id });
|
|
157
|
+
|
|
158
|
+
// Check consumption
|
|
159
|
+
if (node.totalWatt > node.wattLimit) {
|
|
160
|
+
// Start increasing shedding!
|
|
161
|
+
if (node.sheddingStage < node.deviceList.length) {
|
|
162
|
+
node.increaseShedding();
|
|
163
|
+
node.startTimerDecreaseShedding(); // Reset the decreasing timer from beginning
|
|
164
|
+
}
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
167
|
}, node.sheddingCheckInterval);
|
|
@@ -173,6 +179,7 @@ module.exports = function (RED) {
|
|
|
173
179
|
// Start decreasing shedding!
|
|
174
180
|
if (node.sheddingStage > 0) {
|
|
175
181
|
node.decreaseShedding();
|
|
182
|
+
node.startTimerIncreaseShedding(); // Reset the increasing timer from beginning
|
|
176
183
|
}
|
|
177
184
|
}
|
|
178
185
|
|
|
@@ -200,13 +207,12 @@ module.exports = function (RED) {
|
|
|
200
207
|
const oRow = node.deviceList[iRowIndex];
|
|
201
208
|
if (oRow.ga !== undefined && oRow.ga !== "" && oRow.ga !== null) {
|
|
202
209
|
// Check if the device is in use. If not, turn off the device and further increase the shedding stage to turn off the next one.
|
|
203
|
-
node.setLocalStatus({ fill: "red", shape: "dot", text: "
|
|
210
|
+
node.setLocalStatus({ fill: "red", shape: "dot", text: "OFF " + oRow.name });
|
|
204
211
|
node.server.writeQueueAdd({ grpaddr: oRow.ga, payload: false, dpt: oRow.dpt, outputtype: "write", nodecallerid: node.id });
|
|
205
212
|
} else {
|
|
206
|
-
node.setLocalStatus({ fill: "
|
|
213
|
+
node.setLocalStatus({ fill: "grey", shape: "dot", text: "No GA defined" });
|
|
207
214
|
}
|
|
208
|
-
|
|
209
|
-
node.send({ topic: node.name || node.topic, payload: "Shedding stage " + node.sheddingStage });
|
|
215
|
+
node.send({ topic: node.name || node.topic, operation: "Increase Shedding", device: oRow.name || "", ga: oRow.ga || "", totalPowerConsumption: node.totalWatt, wattLimit: node.wattLimit, payload: node.sheddingStage });
|
|
210
216
|
// Go furhter ?
|
|
211
217
|
if (oRow.monitorGA !== undefined && oRow.monitorGA !== "" && oRow.monitorGA !== null) {
|
|
212
218
|
// Minimum consumption must be at lease xx Watt
|
|
@@ -229,33 +235,35 @@ module.exports = function (RED) {
|
|
|
229
235
|
// monitorVal: null
|
|
230
236
|
if (node.sheddingStage <= 0) {
|
|
231
237
|
node.sheddingStage = 0;
|
|
232
|
-
node.setLocalStatus({ fill: "green", shape: "dot", text: "All loads are
|
|
238
|
+
node.setLocalStatus({ fill: "green", shape: "dot", text: "All loads are ON" });
|
|
233
239
|
return;
|
|
234
240
|
}
|
|
235
241
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
242
|
+
node.sheddingStage--;
|
|
243
|
+
let iRowIndex = node.sheddingStage; // Array is base 0
|
|
244
|
+
if (iRowIndex < 0) iRowIndex = 0;
|
|
245
|
+
if (iRowIndex > node.deviceList.length - 1) return;
|
|
246
|
+
|
|
240
247
|
const oRow = node.deviceList[iRowIndex];
|
|
241
|
-
if (oRow.
|
|
242
|
-
if (oRow.
|
|
248
|
+
if (oRow.ga !== undefined && oRow.ga !== "" && oRow.ga !== null) {
|
|
249
|
+
if (oRow.autoRestore === true) {
|
|
243
250
|
// Check if the device is in use. If not, turn off the device and further increase the shedding stage to turn off the next one.
|
|
244
|
-
node.setLocalStatus({ fill: "green", shape: "dot", text: "
|
|
251
|
+
node.setLocalStatus({ fill: "green", shape: "dot", text: "ON " + oRow.name });
|
|
245
252
|
node.server.writeQueueAdd({ grpaddr: oRow.ga, payload: true, dpt: oRow.dpt, outputtype: "write", nodecallerid: node.id });
|
|
246
253
|
} else {
|
|
247
|
-
|
|
254
|
+
// Cannot auto switch on the load.
|
|
255
|
+
node.setLocalStatus({ fill: "yellow", shape: "dot", text: "Auto Restore disabled " + oRow.name });
|
|
248
256
|
}
|
|
249
257
|
} else {
|
|
250
|
-
//
|
|
251
|
-
node.setLocalStatus({ fill: "
|
|
258
|
+
// No load GA defined
|
|
259
|
+
node.setLocalStatus({ fill: "grey", shape: "dot", text: "No Load GA defined" });
|
|
252
260
|
}
|
|
253
|
-
node.send({ topic: node.name || node.topic,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
node.send({ topic: node.name || node.topic, operation: "Decrease Shedding", device: oRow.name || "", ga: oRow.ga || "", totalPowerConsumption: node.totalWatt, wattLimit: node.wattLimit, payload: node.sheddingStage });
|
|
262
|
+
|
|
263
|
+
if (node.sheddingStage < 0) {
|
|
264
|
+
node.sheddingStage = 0;
|
|
257
265
|
setTimeout(() => {
|
|
258
|
-
node.setLocalStatus({ fill: "green", shape: "dot", text: "All loads have been restored"
|
|
266
|
+
node.setLocalStatus({ fill: "green", shape: "dot", text: "All loads have been restored" });
|
|
259
267
|
}, 1000);
|
|
260
268
|
}
|
|
261
269
|
|
|
@@ -278,22 +286,21 @@ module.exports = function (RED) {
|
|
|
278
286
|
if (oRow.autoRestore === true) node.server.writeQueueAdd({ grpaddr: oRow.ga, payload: true, dpt: oRow.dpt, outputtype: "write", nodecallerid: node.id });
|
|
279
287
|
}
|
|
280
288
|
setTimeout(() => {
|
|
281
|
-
node.setLocalStatus({ fill: "green", shape: "dot", text: "All loads have been restored"
|
|
289
|
+
node.setLocalStatus({ fill: "green", shape: "dot", text: "All loads have been restored" });
|
|
282
290
|
// Restart shedding timer
|
|
283
291
|
node.startTimerIncreaseShedding();
|
|
284
292
|
}, 1000);
|
|
285
|
-
node.send({ topic: node.name || node.topic,
|
|
293
|
+
node.send({ topic: node.name || node.topic, operation: "Reset", payload: node.sheddingStage });
|
|
286
294
|
}
|
|
287
295
|
|
|
288
296
|
// Disable the shedding node
|
|
289
297
|
if (msg.hasOwnProperty("disable")) {
|
|
290
298
|
if (node.timerDecreaseShedding !== null) clearInterval(node.timerDecreaseShedding);
|
|
291
299
|
if (node.timerIncreaseShedding !== null) clearInterval(node.timerIncreaseShedding);
|
|
292
|
-
node.sheddingStage = 0;
|
|
293
300
|
setTimeout(() => {
|
|
294
|
-
node.setLocalStatus({ fill: "grey", shape: "dot", text: "Disabled"
|
|
301
|
+
node.setLocalStatus({ fill: "grey", shape: "dot", text: "Disabled" });
|
|
295
302
|
}, 1000);
|
|
296
|
-
node.send({ topic: node.name || node.topic,
|
|
303
|
+
node.send({ topic: node.name || node.topic, operation: "Disabled", payload: node.sheddingStage });
|
|
297
304
|
}
|
|
298
305
|
|
|
299
306
|
// Disable the shedding node
|
|
@@ -301,11 +308,11 @@ module.exports = function (RED) {
|
|
|
301
308
|
if (node.timerDecreaseShedding !== null) clearInterval(node.timerDecreaseShedding);
|
|
302
309
|
if (node.timerIncreaseShedding !== null) clearInterval(node.timerIncreaseShedding);
|
|
303
310
|
setTimeout(() => {
|
|
304
|
-
node.setLocalStatus({ fill: "green", shape: "dot", text: "Enabled"
|
|
311
|
+
node.setLocalStatus({ fill: "green", shape: "dot", text: "Enabled" });
|
|
305
312
|
// Restart shedding timer
|
|
306
313
|
node.startTimerIncreaseShedding();
|
|
307
314
|
}, 1000);
|
|
308
|
-
node.send({ topic: node.name || node.topic,
|
|
315
|
+
node.send({ topic: node.name || node.topic, operation: "Enabled", payload: node.sheddingStage });
|
|
309
316
|
}
|
|
310
317
|
|
|
311
318
|
// 24/04/2021 if payload is read or the output type is set to "read", do a read
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('knxUltimateViewer', {
|
|
3
|
+
category: "KNX Ultimate",
|
|
4
|
+
color: '#C7E9C0',
|
|
5
|
+
defaults: {
|
|
6
|
+
//buttonState: {value: true},
|
|
7
|
+
server: { type: "knxUltimate-config", required: true },
|
|
8
|
+
name: { value: "KNXViewer", validate: RED.validators.regex(/^[a-z]+$/i) }
|
|
9
|
+
},
|
|
10
|
+
inputs: 0,
|
|
11
|
+
outputs: 1,
|
|
12
|
+
icon: "node-eye-icon.svg",
|
|
13
|
+
label: function () {
|
|
14
|
+
return (this.name);
|
|
15
|
+
},
|
|
16
|
+
paletteLabel: "KNX Viewer",
|
|
17
|
+
// button: {
|
|
18
|
+
// enabled: function() {
|
|
19
|
+
// // return whether or not the button is enabled, based on the current
|
|
20
|
+
// // configuration of the node
|
|
21
|
+
// return !this.changed
|
|
22
|
+
// },
|
|
23
|
+
// visible: function() {
|
|
24
|
+
// // return whether or not the button is visible, based on the current
|
|
25
|
+
// // configuration of the node
|
|
26
|
+
// return this.hasButton
|
|
27
|
+
// },
|
|
28
|
+
// //toggle: "buttonState",
|
|
29
|
+
// onclick: function() {}
|
|
30
|
+
// },
|
|
31
|
+
oneditprepare: function () {
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
},
|
|
35
|
+
oneditsave: function () {
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
},
|
|
39
|
+
oneditcancel: function () {
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<script type="text/x-red" data-template-name="knxUltimateViewer">
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
<div class="form-row">
|
|
50
|
+
<b><span data-i18n="knxUltimateViewer.title"></span></b>  <span style="color:red"
|
|
51
|
+
data-i18n="[html]knxUltimateViewer.helplink"></span>
|
|
52
|
+
<br />
|
|
53
|
+
<label for="node-input-server">
|
|
54
|
+
<img
|
|
55
|
+
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img>
|
|
56
|
+
<span data-i18n="knxUltimateViewer.advanced.node-input-server"></span>
|
|
57
|
+
</label>
|
|
58
|
+
<input type="text" id="node-input-server" />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<label for="node-input-name">
|
|
63
|
+
<i class="fa fa-tag"></i>
|
|
64
|
+
<span data-i18n="knxUltimateViewer.node-input-name"></span>
|
|
65
|
+
</label>
|
|
66
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]knxUltimateViewer.node-input-name" />
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
</script>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
|
|
3
|
+
function knxUltimateViewer(config) {
|
|
4
|
+
RED.nodes.createNode(this, config)
|
|
5
|
+
var node = this
|
|
6
|
+
node.server = RED.nodes.getNode(config.server)
|
|
7
|
+
node.topic = node.name;
|
|
8
|
+
node.name = config.name === undefined ? "KNXGlobalContext" : config.name;
|
|
9
|
+
node.outputtopic = node.name;
|
|
10
|
+
node.dpt = "";
|
|
11
|
+
node.notifyreadrequest = false
|
|
12
|
+
node.notifyreadrequestalsorespondtobus = "false";
|
|
13
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = "";
|
|
14
|
+
node.notifyresponse = true;
|
|
15
|
+
node.notifywrite = true;
|
|
16
|
+
node.initialread = false
|
|
17
|
+
node.listenallga = true;
|
|
18
|
+
node.outputtype = "write";
|
|
19
|
+
node.outputRBE = false // Apply or not RBE to the output (Messages coming from flow)
|
|
20
|
+
node.inputRBE = false // Apply or not RBE to the input (Messages coming from BUS)
|
|
21
|
+
node.currentPayload = "" // Current value for the RBE input and for the .previouspayload msg
|
|
22
|
+
node.passthrough = "no";
|
|
23
|
+
node.formatmultiplyvalue = 1;
|
|
24
|
+
node.formatnegativevalue = "leave";
|
|
25
|
+
node.formatdecimalsvalue = 2;
|
|
26
|
+
|
|
27
|
+
node.exposedGAs = [];
|
|
28
|
+
|
|
29
|
+
// Used to call the status update from the config node.
|
|
30
|
+
node.setNodeStatus = ({ fill, shape, text, payload, GA, dpt, devicename }) => {
|
|
31
|
+
if (node.server == null) { node.status({ fill: "red", shape: "dot", text: "[NO GATEWAY SELECTED]" }); return; }
|
|
32
|
+
GA = GA === undefined ? "" : GA;
|
|
33
|
+
payload = payload === undefined ? "" : payload;
|
|
34
|
+
let dDate = new Date();
|
|
35
|
+
node.status({ fill: fill, shape: shape, text: GA + " " + payload + " " + text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
40
|
+
node.handleSend = msg => {
|
|
41
|
+
try {
|
|
42
|
+
var oGa = node.exposedGAs.find(ga => ga.address === msg.knx.destination);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
let dDate = new Date();
|
|
47
|
+
if (oGa === undefined) {
|
|
48
|
+
node.exposedGAs.push({ address: msg.knx.destination, dpt: msg.knx.dpt, payload: msg.payload, devicename: msg.devicename || "Import ETS file", lastupdate: + dDate.getDate() + ", " + dDate.toLocaleTimeString() });
|
|
49
|
+
} else {
|
|
50
|
+
oGa.dpt = msg.knx.dpt;
|
|
51
|
+
oGa.payload = msg.payload;
|
|
52
|
+
oGa.devicename = msg.devicename || "Import ETS file";
|
|
53
|
+
oGa.lastupdate = dDate.getDate() + ", " + dDate.toLocaleTimeString()
|
|
54
|
+
}
|
|
55
|
+
// Output the payload
|
|
56
|
+
node.createPayload();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
node.createPayload = () => {
|
|
60
|
+
let sHead = `<table>
|
|
61
|
+
<thead>
|
|
62
|
+
<tr>
|
|
63
|
+
<th> GA </th>
|
|
64
|
+
<th> Value </th>
|
|
65
|
+
<th> DPT </th>
|
|
66
|
+
<th> Day, time </th>
|
|
67
|
+
<th> Name </th>
|
|
68
|
+
</tr>
|
|
69
|
+
</thead>
|
|
70
|
+
<tbody>`;
|
|
71
|
+
let sFooter = `</tbody>
|
|
72
|
+
</table>`;
|
|
73
|
+
let sPayload = "";
|
|
74
|
+
|
|
75
|
+
const aSorted = node.exposedGAs.sort((a, b) => {
|
|
76
|
+
if( a.address !== undefined && b.address !== undefined ) {
|
|
77
|
+
return a.address > b.address ? 1 : -1;
|
|
78
|
+
} else {
|
|
79
|
+
return a.address !== undefined ? 1 : -1
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
for (let index = 0; index < aSorted.length; index++) {
|
|
84
|
+
const element = aSorted[index];
|
|
85
|
+
sPayload += `<tr>
|
|
86
|
+
<td>` + element.address + `</td>`;
|
|
87
|
+
if (typeof element.payload === "boolean" && element.payload === true) {
|
|
88
|
+
sPayload += "<td><b><font color=green>True</font></b></td>";
|
|
89
|
+
} else if (typeof element.payload === "boolean" && element.payload === false) {
|
|
90
|
+
sPayload += "<td><font color=red>False</font></td>";
|
|
91
|
+
} else {
|
|
92
|
+
sPayload += "<td>" + element.payload + "</td>";
|
|
93
|
+
}
|
|
94
|
+
sPayload += "<td>" + element.dpt + "</td>"
|
|
95
|
+
sPayload += "<td>" + element.lastupdate + "</td>";
|
|
96
|
+
sPayload += "<td><font size=2pt>" + element.devicename + "</font></td></tr>";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
node.send({ topic: node.name, payload: sHead + sPayload + sFooter });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
node.on("input", function (msg) {
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
node.on("close", function (done) {
|
|
108
|
+
if (node.server) {
|
|
109
|
+
node.server.removeClient(node);
|
|
110
|
+
}
|
|
111
|
+
done();
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// On each deploy, unsubscribe+resubscribe
|
|
115
|
+
if (node.server) {
|
|
116
|
+
node.server.removeClient(node);
|
|
117
|
+
node.server.addClient(node);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
RED.nodes.registerType("knxUltimateViewer", knxUltimateViewer)
|
|
123
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"node-input-server": "Gateway",
|
|
8
8
|
"node-input-name": "NoNameme",
|
|
9
9
|
"node-input-dpt": "Datapoint",
|
|
10
|
-
"node-input-topic": "Monitor
|
|
10
|
+
"node-input-topic": "Monitor W",
|
|
11
11
|
"node-input-controlGA": "Load",
|
|
12
12
|
"node-input-monitorGA": "In use",
|
|
13
|
-
"node-input-wattLimit": "Limit
|
|
13
|
+
"node-input-wattLimit": "Limit W",
|
|
14
14
|
"node-input-sheddingCheckInterval": "Delay switch off (s)",
|
|
15
15
|
"node-input-sheddingRestoreDelay": "Delay switch on (s)",
|
|
16
16
|
"node-input-autoRestore": "Automatic recovery"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script type="text/x-red" data-help-name="knxUltimate-config">
|
|
2
|
+
<h1>KNX Ultimate - Nodo Global Context</h1>
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/de-knxUltimateViewer" target="_blank"><i class="fa fa-info-circle"></i>  Beispiel</a>
|
|
7
|
+
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.paypal.me/techtoday" target="_blank"><img src='https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square' width='30%'></a>
|
|
12
|
+
|
|
13
|
+
</p>
|
|
14
|
+
</script>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"knxUltimateViewer": {
|
|
3
|
+
"helplink" : " <i class=\"fa fa-question-circle\"></i> <a target=\"_blank\" href=\"https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/de-knxUltimateViewer\"><u>Esempio</u></a>",
|
|
4
|
+
"title": "KNX Viewer",
|
|
5
|
+
"node-input-name": "Name"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"node-input-server": "Gateway",
|
|
8
8
|
"node-input-name": "NoNameme",
|
|
9
9
|
"node-input-dpt": "Datapoint",
|
|
10
|
-
"node-input-topic": "Monitor
|
|
10
|
+
"node-input-topic": "Monitor W",
|
|
11
11
|
"node-input-controlGA": "Load",
|
|
12
12
|
"node-input-monitorGA": "In use",
|
|
13
|
-
"node-input-wattLimit": "Limit
|
|
13
|
+
"node-input-wattLimit": "Limit W",
|
|
14
14
|
"node-input-sheddingCheckInterval": "Delay switch off (s)",
|
|
15
15
|
"node-input-sheddingRestoreDelay": "Delay switch on (s)",
|
|
16
16
|
"node-input-autoRestore": "Automatic recovery"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script type="text/x-red" data-help-name="knxUltimate-config">
|
|
2
|
+
<h1>KNX Ultimate - Nodo Global Context</h1>
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/knxUltimateViewer" target="_blank"><i class="fa fa-info-circle"></i>  Sample</a>
|
|
7
|
+
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.paypal.me/techtoday" target="_blank"><img src='https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square' width='30%'></a>
|
|
12
|
+
|
|
13
|
+
</p>
|
|
14
|
+
</script>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"knxUltimateViewer": {
|
|
3
|
+
"helplink" : " <i class=\"fa fa-question-circle\"></i> <a target=\"_blank\" href=\"https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/knxUltimateViewer\"><u>Esempio</u></a>",
|
|
4
|
+
"title": "KNX Viewer",
|
|
5
|
+
"node-input-name": "Name"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"node-input-server": "Gateway",
|
|
8
8
|
"node-input-name": "Nome",
|
|
9
9
|
"node-input-dpt": "Datapoint",
|
|
10
|
-
"node-input-topic": "Monitor
|
|
10
|
+
"node-input-topic": "Monitor W",
|
|
11
11
|
"node-input-controlGA": "Carico",
|
|
12
12
|
"node-input-monitorGA": "In uso",
|
|
13
|
-
"node-input-wattLimit": "Soglia
|
|
13
|
+
"node-input-wattLimit": "Soglia W",
|
|
14
14
|
"node-input-sheddingCheckInterval": "Ritardo distacco (s)",
|
|
15
15
|
"node-input-sheddingRestoreDelay": "Ritardo ripristino (s)",
|
|
16
16
|
"node-input-autoRestore": "Ripristino automatico"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script type="text/x-red" data-help-name="knxUltimate-config">
|
|
2
|
+
<h1>KNX Ultimate - Nodo Global Context</h1>
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/it-knxUltimateViewer" target="_blank"><i class="fa fa-info-circle"></i>  Esempio</a>
|
|
7
|
+
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.paypal.me/techtoday" target="_blank"><img src='https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square' width='30%'></a>
|
|
12
|
+
|
|
13
|
+
</p>
|
|
14
|
+
</script>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"knxUltimateViewer": {
|
|
3
|
+
"helplink" : " <i class=\"fa fa-question-circle\"></i> <a target=\"_blank\" href=\"https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/it-knxUltimateViewer\"><u>Esempio</u></a>",
|
|
4
|
+
"title": "KNX Viewer",
|
|
5
|
+
"node-input-name": "Nome"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"node-input-server": "Gateway",
|
|
8
8
|
"node-input-name": "NoNameme",
|
|
9
9
|
"node-input-dpt": "Datapoint",
|
|
10
|
-
"node-input-topic": "Monitor
|
|
10
|
+
"node-input-topic": "Monitor W",
|
|
11
11
|
"node-input-controlGA": "Load",
|
|
12
12
|
"node-input-monitorGA": "In use",
|
|
13
|
-
"node-input-wattLimit": "Limit
|
|
13
|
+
"node-input-wattLimit": "Limit W",
|
|
14
14
|
"node-input-sheddingCheckInterval": "Delay switch off (s)",
|
|
15
15
|
"node-input-sheddingRestoreDelay": "Delay switch on (s)",
|
|
16
16
|
"node-input-autoRestore": "Automatic recovery"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script type="text/x-red" data-help-name="knxUltimate-config">
|
|
2
|
+
<h1>KNX Ultimate - Nodo Global Context</h1>
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/cn-knxUltimateViewer" target="_blank"><i class="fa fa-info-circle"></i>  Sample</a>
|
|
7
|
+
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.paypal.me/techtoday" target="_blank"><img src='https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square' width='30%'></a>
|
|
12
|
+
|
|
13
|
+
</p>
|
|
14
|
+
</script>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"knxUltimateViewer": {
|
|
3
|
+
"helplink" : " <i class=\"fa fa-question-circle\"></i> <a target=\"_blank\" href=\"https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/cn-knxUltimateViewer\"><u>Esempio</u></a>",
|
|
4
|
+
"title": "KNX Viewer",
|
|
5
|
+
"node-input-name": "节点名称"
|
|
6
|
+
}
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-knx-ultimate",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.28",
|
|
4
4
|
"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.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"fs": "0.0.1-security",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"knxUltimate-config": "/nodes/knxUltimate-config.js",
|
|
29
29
|
"knxUltimateGlobalContext": "/nodes/knxUltimateGlobalContext.js",
|
|
30
30
|
"knxUltimateAlerter": "/nodes/knxUltimateAlerter.js",
|
|
31
|
-
"knxUltimateLoadControl":"/nodes/knxUltimateLoadControl.js"
|
|
31
|
+
"knxUltimateLoadControl":"/nodes/knxUltimateLoadControl.js",
|
|
32
|
+
"knxUltimateViewer":"/nodes/knxUltimateViewer.js"
|
|
32
33
|
}
|
|
33
34
|
},
|
|
34
35
|
"repository": {
|