node-red-contrib-knx-ultimate 4.0.11 → 4.0.12
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 +4 -0
- package/nodes/commonFunctions.js +7 -1
- package/nodes/knxUltimate-config.html +11 -7
- package/nodes/knxUltimate-config.js +33 -8
- package/nodes/knxUltimate.js +12 -8
- package/nodes/knxUltimateAlerter.js +13 -15
- package/nodes/knxUltimateAutoResponder.js +7 -7
- package/nodes/knxUltimateGlobalContext.js +7 -7
- package/nodes/knxUltimateHueBattery.js +9 -13
- package/nodes/knxUltimateHueButton.js +7 -7
- package/nodes/knxUltimateHueCameraMotion.js +9 -13
- package/nodes/knxUltimateHueContactSensor.js +9 -13
- package/nodes/knxUltimateHueHumiditySensor.js +9 -13
- package/nodes/knxUltimateHueLight.js +48 -14
- package/nodes/knxUltimateHueLightSensor.js +9 -13
- package/nodes/knxUltimateHueMotion.js +9 -13
- package/nodes/knxUltimateHuePlug.js +7 -7
- package/nodes/knxUltimateHueScene.js +7 -7
- package/nodes/knxUltimateHueTapDial.js +9 -13
- package/nodes/knxUltimateHueTemperatureSensor.js +9 -13
- package/nodes/knxUltimateHueZigbeeConnectivity.js +9 -13
- package/nodes/knxUltimateHuedevice_software_update.js +10 -16
- package/nodes/knxUltimateLoadControl.js +7 -7
- package/nodes/knxUltimateLogger.js +7 -7
- package/nodes/knxUltimateSceneController.js +7 -7
- package/nodes/knxUltimateViewer.js +15 -15
- package/nodes/knxUltimateWatchDog.js +7 -7
- package/nodes/locales/en/knxUltimate-config.html +1 -1
- package/nodes/locales/en/knxUltimate-config.json +7 -3
- package/nodes/locales/es/knxUltimate-config.html +1 -1
- package/nodes/locales/es/knxUltimate-config.json +7 -3
- package/nodes/locales/es/knxUltimateLogger.html +1 -1
- package/nodes/locales/es/knxUltimateSceneController.html +1 -1
- package/nodes/locales/es/knxUltimateWatchDog.html +1 -1
- package/nodes/locales/fr/knxUltimate-config.html +1 -1
- package/nodes/locales/fr/knxUltimate-config.json +7 -3
- package/nodes/locales/it/knxUltimate-config.html +1 -1
- package/nodes/locales/it/knxUltimate-config.json +7 -3
- package/package.json +2 -2
- package/tutorial/knxUltimate-AllNodes-Presentazione.md +190 -0
- package/tutorial/knxUltimateHueBattery-teleprompter.txt +5 -1
- package/tutorial/knxUltimateHueBattery.md +8 -5
- package/tutorial/knxUltimateHueCameraMotion-teleprompter.txt +44 -0
- package/tutorial/knxUltimateHueCameraMotion.md +45 -0
- package/tutorial/knxUltimateHueHumiditySensor-teleprompter.txt +43 -0
- package/tutorial/knxUltimateHueHumiditySensor.md +45 -0
- package/tutorial/knxUltimateHueLightSensor-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHueLightSensor.md +5 -5
- package/tutorial/knxUltimateHueMotion-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHueMotion.md +8 -4
- package/tutorial/knxUltimateHuePlug-teleprompter.txt +48 -0
- package/tutorial/knxUltimateHuePlug.md +49 -0
- package/tutorial/knxUltimateHueScene-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHueTapDial-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHueTapDial.md +8 -4
- package/tutorial/knxUltimateHueTemperatureSensor-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHueTemperatureSensor.md +5 -4
- package/tutorial/knxUltimateHueZigbeeConnectivity-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHueZigbeeConnectivity.md +4 -3
- package/tutorial/knxUltimateHuedevice_software_update-teleprompter.txt +4 -0
- package/tutorial/knxUltimateHuedevice_software_update.md +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 4.0.12** - October 2025<br/>
|
|
10
|
+
- KNX Config node: replaced the "errors only" status filter with a configurable status throttle (0/1/3/5/10/30 s) that emits only the latest status after the chosen delay, preventing editor memory growth when many nodes update.<br/>
|
|
11
|
+
- HUE Light node: the "Keep brightness" option now restores the last active dim level when toggled via KNX On/Off instead of forcing 100%.<br/>
|
|
12
|
+
|
|
9
13
|
**Version 4.0.11** - October 2025<br/>
|
|
10
14
|
- HUE nodes: hardened KNX telegram handling with a shared safe-send guard so editor events no longer ceases to function, when the KNX gateway is offline.<br/>
|
|
11
15
|
- HUE Contact Sensor node: placeholders now leverage i18n translations with graceful fallback when translation keys are missing.<br/>
|
package/nodes/commonFunctions.js
CHANGED
|
@@ -74,10 +74,16 @@ module.exports = (RED) => {
|
|
|
74
74
|
RED.httpAdmin.get('/knxultimateCheckHueConnected', (req, res) => {
|
|
75
75
|
try {
|
|
76
76
|
const serverId = RED.nodes.getNode(req.query.serverId); // Retrieve node.id of the config node.
|
|
77
|
+
if (!serverId) {
|
|
78
|
+
res.json({ ready: false });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
77
81
|
if (serverId.hueAllResources === null || serverId.hueAllResources === undefined) {
|
|
78
82
|
(async function main() {
|
|
79
83
|
try {
|
|
80
|
-
|
|
84
|
+
if (typeof serverId.loadResourcesFromHUEBridge === 'function') {
|
|
85
|
+
await serverId.loadResourcesFromHUEBridge();
|
|
86
|
+
}
|
|
81
87
|
} catch (error) {
|
|
82
88
|
RED.log.error(`Errore RED.httpAdmin.get('/knxultimateCheckHueConnected' ${error.stack}`);
|
|
83
89
|
}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
tunnelUserPassword: { value: "" },
|
|
29
29
|
tunnelUserId: { value: "" },
|
|
30
30
|
autoReconnect: { value: "yes" },
|
|
31
|
-
|
|
31
|
+
statusUpdateThrottle: { value: "0" }
|
|
32
32
|
},
|
|
33
33
|
credentials: {
|
|
34
34
|
keyringFilePassword: { type: "password" }
|
|
@@ -1148,13 +1148,17 @@
|
|
|
1148
1148
|
</div>
|
|
1149
1149
|
|
|
1150
1150
|
<div class="form-row">
|
|
1151
|
-
<label for="node-config-input-
|
|
1152
|
-
<i class="fa fa-
|
|
1153
|
-
<span data-i18n="knxUltimate-config.advanced.
|
|
1151
|
+
<label for="node-config-input-statusUpdateThrottle">
|
|
1152
|
+
<i class="fa fa-clock-o"></i>
|
|
1153
|
+
<span data-i18n="knxUltimate-config.advanced.status_throttle"></span>
|
|
1154
1154
|
</label>
|
|
1155
|
-
<select id="node-config-input-
|
|
1156
|
-
<option value="
|
|
1157
|
-
<option value="
|
|
1155
|
+
<select id="node-config-input-statusUpdateThrottle" style="width:40%;">
|
|
1156
|
+
<option value="0" data-i18n="knxUltimate-config.advanced.status_throttle_none"></option>
|
|
1157
|
+
<option value="1" data-i18n="knxUltimate-config.advanced.status_throttle_1s"></option>
|
|
1158
|
+
<option value="3" data-i18n="knxUltimate-config.advanced.status_throttle_3s"></option>
|
|
1159
|
+
<option value="5" data-i18n="knxUltimate-config.advanced.status_throttle_5s"></option>
|
|
1160
|
+
<option value="10" data-i18n="knxUltimate-config.advanced.status_throttle_10s"></option>
|
|
1161
|
+
<option value="30" data-i18n="knxUltimate-config.advanced.status_throttle_30s"></option>
|
|
1158
1162
|
</select>
|
|
1159
1163
|
</div>
|
|
1160
1164
|
</p>
|
|
@@ -24,8 +24,6 @@ const loggerClass = require('./utils/sysLogger')
|
|
|
24
24
|
const payloadRounder = require("./utils/payloadManipulation");
|
|
25
25
|
const utils = require('./utils/utils');
|
|
26
26
|
|
|
27
|
-
const STATUS_DISPLAY_ALLOWED_COLORS = new Set(['red', 'yellow']);
|
|
28
|
-
|
|
29
27
|
// DATAPONT MANIPULATION HELPERS
|
|
30
28
|
// ####################
|
|
31
29
|
const sortBy = (field) => (a, b) => {
|
|
@@ -220,12 +218,39 @@ module.exports = (RED) => {
|
|
|
220
218
|
node.autoReconnect = true;
|
|
221
219
|
}
|
|
222
220
|
node.ignoreTelegramsWithRepeatedFlag = config.ignoreTelegramsWithRepeatedFlag === undefined ? false : config.ignoreTelegramsWithRepeatedFlag;
|
|
223
|
-
const
|
|
224
|
-
node.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
const throttleSecondsRaw = Number(config.statusUpdateThrottle);
|
|
222
|
+
node.statusUpdateThrottleMs = Number.isFinite(throttleSecondsRaw) && throttleSecondsRaw > 0
|
|
223
|
+
? throttleSecondsRaw * 1000
|
|
224
|
+
: 0;
|
|
225
|
+
node.applyStatusUpdate = (targetNode, status) => {
|
|
226
|
+
try {
|
|
227
|
+
if (!targetNode || typeof targetNode.status !== 'function') return;
|
|
228
|
+
const throttle = node.statusUpdateThrottleMs;
|
|
229
|
+
if (!throttle) {
|
|
230
|
+
targetNode.status(status);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!targetNode.__knxStatusThrottle) {
|
|
234
|
+
targetNode.__knxStatusThrottle = { pending: undefined, timer: null };
|
|
235
|
+
}
|
|
236
|
+
const tracker = targetNode.__knxStatusThrottle;
|
|
237
|
+
tracker.pending = status;
|
|
238
|
+
if (tracker.timer) return;
|
|
239
|
+
tracker.timer = setTimeout(() => {
|
|
240
|
+
try {
|
|
241
|
+
if (tracker.pending !== undefined) {
|
|
242
|
+
targetNode.status(tracker.pending);
|
|
243
|
+
}
|
|
244
|
+
} catch (timerError) {
|
|
245
|
+
node.sysLogger?.warn('Unable to apply throttled status: ' + timerError.message);
|
|
246
|
+
} finally {
|
|
247
|
+
tracker.pending = undefined;
|
|
248
|
+
tracker.timer = null;
|
|
249
|
+
}
|
|
250
|
+
}, throttle);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
node.sysLogger?.warn('applyStatusUpdate error: ' + error.message);
|
|
253
|
+
}
|
|
229
254
|
};
|
|
230
255
|
// 24/07/2021 KNX Secure checks...
|
|
231
256
|
node.keyringFileXML = typeof config.keyringFileXML === "undefined" || config.keyringFileXML.trim() === "" ? "" : config.keyringFileXML;
|
package/nodes/knxUltimate.js
CHANGED
|
@@ -11,8 +11,17 @@ module.exports = function (RED) {
|
|
|
11
11
|
RED.nodes.createNode(this, config);
|
|
12
12
|
const node = this;
|
|
13
13
|
node.serverKNX = RED.nodes.getNode(config.server) || undefined;
|
|
14
|
+
const pushStatus = (status) => {
|
|
15
|
+
const provider = node.serverKNX;
|
|
16
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
17
|
+
provider.applyStatusUpdate(node, status);
|
|
18
|
+
} else {
|
|
19
|
+
node.status(status);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
14
23
|
if (node.serverKNX === undefined) {
|
|
15
|
-
|
|
24
|
+
pushStatus({ fill: 'red', shape: 'dot', text: '[THE GATEWAY NODE HAS BEEN DISABLED]' });
|
|
16
25
|
return;
|
|
17
26
|
}
|
|
18
27
|
|
|
@@ -22,7 +31,7 @@ module.exports = function (RED) {
|
|
|
22
31
|
fill, shape, text, payload, GA, dpt, devicename,
|
|
23
32
|
}) => {
|
|
24
33
|
try {
|
|
25
|
-
if (node.serverKNX === null) {
|
|
34
|
+
if (node.serverKNX === null) { pushStatus({ fill: 'red', shape: 'dot', text: '[NO GATEWAY SELECTED]' }); return; }
|
|
26
35
|
if (node.icountMessageInWindow == -999) return; // Locked out, doesn't change status.
|
|
27
36
|
const dDate = new Date();
|
|
28
37
|
// 30/08/2019 Display only the things selected in the config
|
|
@@ -31,12 +40,7 @@ module.exports = function (RED) {
|
|
|
31
40
|
dpt = (typeof dpt === 'undefined' || dpt == '') ? '' : ` DPT${dpt}`;
|
|
32
41
|
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
|
|
33
42
|
const statusText = `${GA + payload + (node.listenallga === true ? ` ${devicename}` : '')} (day ${dDate.getDate()}, ${dDate.toLocaleTimeString()}) ${text}`;
|
|
34
|
-
|
|
35
|
-
? node.serverKNX.shouldDisplayStatus(fill)
|
|
36
|
-
: true;
|
|
37
|
-
if (shouldUpdateStatus) {
|
|
38
|
-
node.status({ fill, shape, text: statusText });
|
|
39
|
-
}
|
|
43
|
+
pushStatus({ fill, shape, text: statusText });
|
|
40
44
|
// 16/02/2020 signal errors to the server
|
|
41
45
|
if (fill.toUpperCase() === 'RED') {
|
|
42
46
|
if (node.serverKNX) {
|
|
@@ -11,8 +11,18 @@ module.exports = function (RED) {
|
|
|
11
11
|
RED.nodes.createNode(this, config);
|
|
12
12
|
const node = this;
|
|
13
13
|
node.serverKNX = RED.nodes.getNode(config.server) || undefined;
|
|
14
|
+
const pushStatus = (status) => {
|
|
15
|
+
if (status === undefined || status === null) return;
|
|
16
|
+
const provider = node.serverKNX;
|
|
17
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
18
|
+
provider.applyStatusUpdate(node, status);
|
|
19
|
+
} else {
|
|
20
|
+
node.status(status);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
14
24
|
if (node.serverKNX === undefined) {
|
|
15
|
-
|
|
25
|
+
pushStatus({ fill: 'red', shape: 'dot', text: '[THE GATEWAY NODE HAS BEEN DISABLED]' });
|
|
16
26
|
return;
|
|
17
27
|
}
|
|
18
28
|
node.name = config.name || 'KNX Alerter';
|
|
@@ -33,14 +43,6 @@ module.exports = function (RED) {
|
|
|
33
43
|
node.whentostart = config.whentostart === undefined ? 'ifnewalert' : config.whentostart;
|
|
34
44
|
node.timerinterval = (config.timerinterval === undefined || config.timerinterval == '') ? '2' : config.timerinterval;
|
|
35
45
|
|
|
36
|
-
const shouldDisplayStatus = (color) => {
|
|
37
|
-
const provider = node.serverKNX;
|
|
38
|
-
if (provider && typeof provider.shouldDisplayStatus === 'function') {
|
|
39
|
-
return provider.shouldDisplayStatus(color);
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
46
|
if (config.initialreadGAInRules === undefined) {
|
|
45
47
|
node.initialread = 1;
|
|
46
48
|
} else {
|
|
@@ -61,9 +63,7 @@ module.exports = function (RED) {
|
|
|
61
63
|
devicename = devicename || '';
|
|
62
64
|
dpt = (typeof dpt === 'undefined' || dpt == '') ? '' : ' DPT' + dpt;
|
|
63
65
|
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
|
|
64
|
-
|
|
65
|
-
node.status({ fill, shape, text: GA + payload + (node.listenallga === true ? ' ' + devicename : '') + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ' ' + text });
|
|
66
|
-
}
|
|
66
|
+
pushStatus({ fill, shape, text: GA + payload + (node.listenallga === true ? ' ' + devicename : '') + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ' ' + text });
|
|
67
67
|
} catch (error) {
|
|
68
68
|
}
|
|
69
69
|
};
|
|
@@ -76,9 +76,7 @@ module.exports = function (RED) {
|
|
|
76
76
|
devicename = devicename || '';
|
|
77
77
|
dpt = (typeof dpt === 'undefined' || dpt == '') ? '' : ' DPT' + dpt;
|
|
78
78
|
try {
|
|
79
|
-
|
|
80
|
-
node.status({ fill, shape, text: GA + payload + (node.listenallga === true ? ' ' + devicename : '') + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ' ' + text });
|
|
81
|
-
}
|
|
79
|
+
pushStatus({ fill, shape, text: GA + payload + (node.listenallga === true ? ' ' + devicename : '') + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ' ' + text });
|
|
82
80
|
} catch (error) {
|
|
83
81
|
}
|
|
84
82
|
};
|
|
@@ -52,19 +52,19 @@ module.exports = function (RED) {
|
|
|
52
52
|
node.commandText = []; // Raw list Respond To
|
|
53
53
|
node.timerSaveExposedGAs = null;
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const pushStatus = (status) => {
|
|
56
|
+
if (!status) return;
|
|
56
57
|
const provider = node.serverKNX;
|
|
57
|
-
if (provider && typeof provider.
|
|
58
|
-
|
|
58
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
59
|
+
provider.applyStatusUpdate(node, status);
|
|
60
|
+
} else {
|
|
61
|
+
node.status(status);
|
|
59
62
|
}
|
|
60
|
-
return true;
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
const updateStatus = (status) => {
|
|
64
66
|
if (!status) return;
|
|
65
|
-
|
|
66
|
-
node.status(status);
|
|
67
|
-
}
|
|
67
|
+
pushStatus(status);
|
|
68
68
|
};
|
|
69
69
|
if (node.serverKNX === null) { updateStatus({ fill: 'red', shape: 'dot', text: '[NO GATEWAY SELECTED]' }); return; }
|
|
70
70
|
|
|
@@ -57,19 +57,19 @@ module.exports = function (RED) {
|
|
|
57
57
|
node.exposedGAs = []
|
|
58
58
|
node.timerExposedGAs = null
|
|
59
59
|
|
|
60
|
-
const
|
|
60
|
+
const pushStatus = (status) => {
|
|
61
|
+
if (!status) return;
|
|
61
62
|
const provider = node.serverKNX;
|
|
62
|
-
if (provider && typeof provider.
|
|
63
|
-
|
|
63
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
64
|
+
provider.applyStatusUpdate(node, status);
|
|
65
|
+
} else {
|
|
66
|
+
node.status(status);
|
|
64
67
|
}
|
|
65
|
-
return true;
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
const updateStatus = (status) => {
|
|
69
71
|
if (!status) return;
|
|
70
|
-
|
|
71
|
-
node.status(status);
|
|
72
|
-
}
|
|
72
|
+
pushStatus(status);
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
// Used to call the status update from the config node.
|
|
@@ -33,19 +33,19 @@ module.exports = function (RED) {
|
|
|
33
33
|
node.outputs = 1;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
const pushStatus = (status) => {
|
|
37
|
+
if (!status) return;
|
|
37
38
|
const provider = node.serverKNX;
|
|
38
|
-
if (provider && typeof provider.
|
|
39
|
-
|
|
39
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
40
|
+
provider.applyStatusUpdate(node, status);
|
|
41
|
+
} else {
|
|
42
|
+
node.status(status);
|
|
40
43
|
}
|
|
41
|
-
return true;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
const updateStatus = (status) => {
|
|
45
47
|
if (!status) return;
|
|
46
|
-
|
|
47
|
-
node.status(status);
|
|
48
|
-
}
|
|
48
|
+
pushStatus(status);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const safeSendToKNX = (telegram, context = 'write') => {
|
|
@@ -70,9 +70,7 @@ module.exports = function (RED) {
|
|
|
70
70
|
const dDate = new Date();
|
|
71
71
|
payload = typeof payload === "object" ? JSON.stringify(payload) : payload.toString();
|
|
72
72
|
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
73
|
-
|
|
74
|
-
node.status({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
75
|
-
}
|
|
73
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
76
74
|
} catch (error) { }
|
|
77
75
|
};
|
|
78
76
|
// Used to call the status update from the HUE config node.
|
|
@@ -82,9 +80,7 @@ module.exports = function (RED) {
|
|
|
82
80
|
const dDate = new Date();
|
|
83
81
|
payload = typeof payload === "object" ? JSON.stringify(payload) : payload.toString();
|
|
84
82
|
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
85
|
-
|
|
86
|
-
node.status({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
87
|
-
}
|
|
83
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
88
84
|
} catch (error) { }
|
|
89
85
|
};
|
|
90
86
|
|
|
@@ -37,19 +37,19 @@ module.exports = function (RED) {
|
|
|
37
37
|
if (node.dimSend === 'down') node.dimSend = { decr_incr: 0, data: 3 };
|
|
38
38
|
if (node.dimSend === 'stop') node.dimSend = { decr_incr: 0, data: 0 };
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const pushStatus = (status) => {
|
|
41
|
+
if (!status) return;
|
|
41
42
|
const provider = node.serverKNX;
|
|
42
|
-
if (provider && typeof provider.
|
|
43
|
-
|
|
43
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
44
|
+
provider.applyStatusUpdate(node, status);
|
|
45
|
+
} else {
|
|
46
|
+
node.status(status);
|
|
44
47
|
}
|
|
45
|
-
return true;
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
const updateStatus = (status) => {
|
|
49
51
|
if (!status) return;
|
|
50
|
-
|
|
51
|
-
node.status(status);
|
|
52
|
-
}
|
|
52
|
+
pushStatus(status);
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const safeSendToKNX = (telegram, context = 'write') => {
|
|
@@ -35,19 +35,19 @@ module.exports = function (RED) {
|
|
|
35
35
|
node.outputs = 1;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const pushStatus = (status) => {
|
|
39
|
+
if (!status) return;
|
|
39
40
|
const provider = node.serverKNX;
|
|
40
|
-
if (provider && typeof provider.
|
|
41
|
-
|
|
41
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
42
|
+
provider.applyStatusUpdate(node, status);
|
|
43
|
+
} else {
|
|
44
|
+
node.status(status);
|
|
42
45
|
}
|
|
43
|
-
return true;
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
const updateStatus = (status) => {
|
|
47
49
|
if (!status) return;
|
|
48
|
-
|
|
49
|
-
node.status(status);
|
|
50
|
-
}
|
|
50
|
+
pushStatus(status);
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
const safeSendToKNX = (telegram, context = 'write') => {
|
|
@@ -69,9 +69,7 @@ module.exports = function (RED) {
|
|
|
69
69
|
const dDate = new Date();
|
|
70
70
|
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString();
|
|
71
71
|
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
72
|
-
|
|
73
|
-
node.status({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
74
|
-
}
|
|
72
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
75
73
|
} catch (error) { /* empty */ }
|
|
76
74
|
};
|
|
77
75
|
|
|
@@ -81,9 +79,7 @@ module.exports = function (RED) {
|
|
|
81
79
|
const dDate = new Date();
|
|
82
80
|
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString();
|
|
83
81
|
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
84
|
-
|
|
85
|
-
node.status({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
86
|
-
}
|
|
82
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
87
83
|
} catch (error) { /* empty */ }
|
|
88
84
|
};
|
|
89
85
|
|
|
@@ -25,19 +25,19 @@ module.exports = function (RED) {
|
|
|
25
25
|
node.hueDevice = config.hueDevice
|
|
26
26
|
node.initializingAtStart = false
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const pushStatus = (status) => {
|
|
29
|
+
if (!status) return;
|
|
29
30
|
const provider = node.serverKNX;
|
|
30
|
-
if (provider && typeof provider.
|
|
31
|
-
|
|
31
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
32
|
+
provider.applyStatusUpdate(node, status);
|
|
33
|
+
} else {
|
|
34
|
+
node.status(status);
|
|
32
35
|
}
|
|
33
|
-
return true;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const updateStatus = (status) => {
|
|
37
39
|
if (!status) return;
|
|
38
|
-
|
|
39
|
-
node.status(status);
|
|
40
|
-
}
|
|
40
|
+
pushStatus(status);
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
const safeSendToKNX = (telegram, context = 'write') => {
|
|
@@ -62,9 +62,7 @@ module.exports = function (RED) {
|
|
|
62
62
|
const dDate = new Date();
|
|
63
63
|
payload = typeof payload === "object" ? JSON.stringify(payload) : payload.toString();
|
|
64
64
|
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
65
|
-
|
|
66
|
-
node.status({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
67
|
-
}
|
|
65
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
68
66
|
} catch (error) { }
|
|
69
67
|
};
|
|
70
68
|
// Used to call the status update from the HUE config node.
|
|
@@ -74,9 +72,7 @@ module.exports = function (RED) {
|
|
|
74
72
|
const dDate = new Date();
|
|
75
73
|
payload = typeof payload === "object" ? JSON.stringify(payload) : payload.toString();
|
|
76
74
|
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
77
|
-
|
|
78
|
-
node.status({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
79
|
-
}
|
|
75
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
80
76
|
} catch (error) { }
|
|
81
77
|
};
|
|
82
78
|
|
|
@@ -30,19 +30,19 @@ module.exports = function (RED) {
|
|
|
30
30
|
node.enableNodePINS = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes');
|
|
31
31
|
node.outputs = node.enableNodePINS ? 1 : 0;
|
|
32
32
|
|
|
33
|
-
const
|
|
33
|
+
const pushStatus = (status) => {
|
|
34
|
+
if (!status) return;
|
|
34
35
|
const provider = node.serverKNX;
|
|
35
|
-
if (provider && typeof provider.
|
|
36
|
-
|
|
36
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
37
|
+
provider.applyStatusUpdate(node, status);
|
|
38
|
+
} else {
|
|
39
|
+
node.status(status);
|
|
37
40
|
}
|
|
38
|
-
return true;
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
const updateStatus = (status) => {
|
|
42
44
|
if (!status) return;
|
|
43
|
-
|
|
44
|
-
node.status(status);
|
|
45
|
-
}
|
|
45
|
+
pushStatus(status);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const safeSendToKNX = (telegram, context = 'write') => {
|
|
@@ -64,9 +64,7 @@ module.exports = function (RED) {
|
|
|
64
64
|
const dDate = new Date();
|
|
65
65
|
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString();
|
|
66
66
|
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
67
|
-
|
|
68
|
-
node.status({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
69
|
-
}
|
|
67
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') });
|
|
70
68
|
} catch (error) { /* empty */ }
|
|
71
69
|
};
|
|
72
70
|
|
|
@@ -76,9 +74,7 @@ module.exports = function (RED) {
|
|
|
76
74
|
const dDate = new Date();
|
|
77
75
|
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString();
|
|
78
76
|
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`;
|
|
79
|
-
|
|
80
|
-
node.status({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
81
|
-
}
|
|
77
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') });
|
|
82
78
|
} catch (error) { /* empty */ }
|
|
83
79
|
};
|
|
84
80
|
|
|
@@ -226,6 +226,7 @@ module.exports = function (RED) {
|
|
|
226
226
|
node.formatnegativevalue = "leave";
|
|
227
227
|
node.formatdecimalsvalue = 2;
|
|
228
228
|
node.currentHUEDevice = undefined; // At start, this value is filled by a call to HUE api. It stores a value representing the current light status.
|
|
229
|
+
node.lastKnownBrightness = undefined; // Stores the latest non-zero brightness to honour "keep brightness" behaviour when toggling via KNX.
|
|
229
230
|
node.HUEDeviceWhileDaytime = null;// This retains the HUE device status while daytime, to be restored after nighttime elapsed.
|
|
230
231
|
node.HUELightsBelongingToGroupWhileDaytime = null; // Array contains all light belonging to the grouped_light (if grouped_light is selected)
|
|
231
232
|
node.DayTime = true;
|
|
@@ -235,19 +236,19 @@ module.exports = function (RED) {
|
|
|
235
236
|
node.timerCheckForFastLightSwitch = null;
|
|
236
237
|
node.HSVObject = null; //{ h, s, v };// Store the current light calculated HSV
|
|
237
238
|
|
|
238
|
-
const
|
|
239
|
+
const pushStatus = (status) => {
|
|
240
|
+
if (!status) return;
|
|
239
241
|
const provider = node.serverKNX;
|
|
240
|
-
if (provider && typeof provider.
|
|
241
|
-
|
|
242
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
243
|
+
provider.applyStatusUpdate(node, status);
|
|
244
|
+
} else {
|
|
245
|
+
node.status(status);
|
|
242
246
|
}
|
|
243
|
-
return true;
|
|
244
247
|
};
|
|
245
248
|
|
|
246
249
|
const updateStatus = (status) => {
|
|
247
250
|
if (!status) return;
|
|
248
|
-
|
|
249
|
-
node.status(status);
|
|
250
|
-
}
|
|
251
|
+
pushStatus(status);
|
|
251
252
|
};
|
|
252
253
|
|
|
253
254
|
const safeSendToKNX = (telegram, context = 'write') => {
|
|
@@ -307,6 +308,9 @@ module.exports = function (RED) {
|
|
|
307
308
|
const handleLightSwitch = (msg) => {
|
|
308
309
|
let state = {};
|
|
309
310
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch));
|
|
311
|
+
if (node.lastKnownBrightness === undefined && node.currentHUEDevice?.dimming?.brightness > 0) {
|
|
312
|
+
node.lastKnownBrightness = node.currentHUEDevice.dimming.brightness;
|
|
313
|
+
}
|
|
310
314
|
|
|
311
315
|
if (config.restoreDayMode === "setDayByFastSwitchLightSingle" || config.restoreDayMode === "setDayByFastSwitchLightALL") {
|
|
312
316
|
if (node.DayTime === false) {
|
|
@@ -343,6 +347,7 @@ module.exports = function (RED) {
|
|
|
343
347
|
state = { on: { on: true }, dimming: node.HUEDeviceWhileDaytime.dimming, color: node.HUEDeviceWhileDaytime.color, color_temperature: node.HUEDeviceWhileDaytime.color_temperature };
|
|
344
348
|
if (node.HUEDeviceWhileDaytime.color_temperature !== undefined && node.HUEDeviceWhileDaytime.color_temperature.mirek === null) delete state.color_temperature;
|
|
345
349
|
queueHueCommand(state);
|
|
350
|
+
if (state.dimming?.brightness > 0) node.lastKnownBrightness = state.dimming.brightness;
|
|
346
351
|
reportHueStatus("Restore light status");
|
|
347
352
|
node.HUEDeviceWhileDaytime = null;
|
|
348
353
|
} else if (node.isGrouped_light === true && node.HUELightsBelongingToGroupWhileDaytime !== null) {
|
|
@@ -357,13 +362,14 @@ module.exports = function (RED) {
|
|
|
357
362
|
for (let index = 0; index < node.HUELightsBelongingToGroupWhileDaytime.length; index++) {
|
|
358
363
|
const element = node.HUELightsBelongingToGroupWhileDaytime[index].light[0];
|
|
359
364
|
if (bAtLeastOneIsOn === true) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
if (element.color_temperature !== undefined && element.color_temperature.mirek === null) delete state.color_temperature;
|
|
365
|
-
node.serverHue.hueManager.writeHueQueueAdd(element.id, state, "setLight");
|
|
365
|
+
state = { on: element.on, dimming: element.dimming, color: element.color, color_temperature: element.color_temperature };
|
|
366
|
+
} else {
|
|
367
|
+
state = { on: { on: true }, dimming: element.dimming, color: element.color, color_temperature: element.color_temperature };
|
|
366
368
|
}
|
|
369
|
+
if (element.color_temperature !== undefined && element.color_temperature.mirek === null) delete state.color_temperature;
|
|
370
|
+
node.serverHue.hueManager.writeHueQueueAdd(element.id, state, "setLight");
|
|
371
|
+
if (state.dimming?.brightness > 0) node.lastKnownBrightness = state.dimming.brightness;
|
|
372
|
+
}
|
|
367
373
|
reportHueStatus("Resuming all group's light");
|
|
368
374
|
node.HUELightsBelongingToGroupWhileDaytime = null;
|
|
369
375
|
return;
|
|
@@ -410,6 +416,7 @@ module.exports = function (RED) {
|
|
|
410
416
|
delete state.color_temperature;
|
|
411
417
|
}
|
|
412
418
|
queueHueCommand(state);
|
|
419
|
+
if (typeof dbright === "number" && dbright > 0) node.lastKnownBrightness = dbright;
|
|
413
420
|
reportHueStatus(JSON.stringify(msg.payload));
|
|
414
421
|
return;
|
|
415
422
|
}
|
|
@@ -429,12 +436,30 @@ module.exports = function (RED) {
|
|
|
429
436
|
delete state.color_temperature;
|
|
430
437
|
}
|
|
431
438
|
queueHueCommand(state);
|
|
439
|
+
if (typeof bBright === "number" && bBright > 0) node.lastKnownBrightness = bBright;
|
|
432
440
|
reportHueStatus(JSON.stringify(msg.payload));
|
|
433
441
|
return;
|
|
434
442
|
}
|
|
435
443
|
if (node.currentHUEDevice.dimming !== undefined) {
|
|
436
|
-
|
|
444
|
+
let targetBrightness;
|
|
445
|
+
if (brightnessChoosen !== undefined && brightnessChoosen !== null && brightnessChoosen !== "") {
|
|
446
|
+
const parsed = Number(brightnessChoosen);
|
|
447
|
+
if (!Number.isNaN(parsed)) targetBrightness = parsed;
|
|
448
|
+
}
|
|
449
|
+
if (targetBrightness === undefined) {
|
|
450
|
+
if (typeof node.lastKnownBrightness === "number" && node.lastKnownBrightness > 0) {
|
|
451
|
+
targetBrightness = node.lastKnownBrightness;
|
|
452
|
+
} else if (typeof node.currentHUEDevice.dimming.brightness === "number" && node.currentHUEDevice.dimming.brightness > 0) {
|
|
453
|
+
targetBrightness = node.currentHUEDevice.dimming.brightness;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (targetBrightness === undefined || targetBrightness <= 0) {
|
|
457
|
+
targetBrightness = 100;
|
|
458
|
+
}
|
|
459
|
+
state = { dimming: { brightness: targetBrightness }, on: { on: true } };
|
|
460
|
+
if (targetBrightness > 0) node.lastKnownBrightness = targetBrightness;
|
|
437
461
|
queueHueCommand(state);
|
|
462
|
+
if (typeof targetBrightness === "number" && targetBrightness > 0) node.lastKnownBrightness = targetBrightness;
|
|
438
463
|
reportHueStatus(JSON.stringify(msg.payload));
|
|
439
464
|
return;
|
|
440
465
|
}
|
|
@@ -443,6 +468,9 @@ module.exports = function (RED) {
|
|
|
443
468
|
reportHueStatus(JSON.stringify(msg.payload));
|
|
444
469
|
}
|
|
445
470
|
} else {
|
|
471
|
+
if (node.currentHUEDevice?.dimming?.brightness > 0) {
|
|
472
|
+
node.lastKnownBrightness = node.currentHUEDevice.dimming.brightness;
|
|
473
|
+
}
|
|
446
474
|
state = { on: { on: false } };
|
|
447
475
|
queueHueCommand(state);
|
|
448
476
|
reportHueStatus(JSON.stringify(msg.payload));
|
|
@@ -584,6 +612,7 @@ module.exports = function (RED) {
|
|
|
584
612
|
case config.GALightBrightness:
|
|
585
613
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness));
|
|
586
614
|
state = { dimming: { brightness: msg.payload } };
|
|
615
|
+
if (msg.payload > 0) node.lastKnownBrightness = msg.payload;
|
|
587
616
|
if (node.currentHUEDevice === undefined) {
|
|
588
617
|
// Grouped light
|
|
589
618
|
state.on = { on: msg.payload > 0 };
|
|
@@ -1332,6 +1361,11 @@ module.exports = function (RED) {
|
|
|
1332
1361
|
} else {
|
|
1333
1362
|
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && (receivedHUEObject.on === undefined || (receivedHUEObject.on !== undefined && receivedHUEObject.on.on === true))) node.updateKNXLightState(receivedHUEObject.dimming.brightness > 0);
|
|
1334
1363
|
node.updateKNXBrightnessState(receivedHUEObject.dimming.brightness);
|
|
1364
|
+
if (receivedHUEObject.dimming.brightness > 0) {
|
|
1365
|
+
node.lastKnownBrightness = receivedHUEObject.dimming.brightness;
|
|
1366
|
+
} else if (node.currentHUEDevice?.dimming?.brightness > 0) {
|
|
1367
|
+
node.lastKnownBrightness = node.currentHUEDevice.dimming.brightness;
|
|
1368
|
+
}
|
|
1335
1369
|
// If the brightness reaches zero, the hue lamp "on" property must be set to zero as well
|
|
1336
1370
|
if (receivedHUEObject.dimming.brightness === 0 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true) {
|
|
1337
1371
|
node.serverHue.hueManager.writeHueQueueAdd(
|