node-red-contrib-knx-ultimate 2.4.6 → 2.4.8

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 CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 2.4.7** - March 2024<br/>
10
+ - WARNING: this version uses the Node-Red plugin system; the Node-Red version must be **equals or major than 3.1.1**<br/>
11
+ - Fixed [this](https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/338).<br/>
12
+
9
13
  **Version 2.4.6** - Feb 2024<br/>
10
14
  - WARNING: this version uses the Node-Red plugin system; the Node-Red version must be **equals or major than 3.1.1**<br/>
11
15
  - Changed the KNX Engine keep alive CONNECTIONSTATUS_REQUEST interval from 60 to 30 secs, to allow some KNX/IP interfaces (not strictly following the KNX standard) to work properly.<br/>
@@ -0,0 +1,321 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('knxUltimateGarageDoorBarrierOpener', {
3
+ category: "KNX Ultimate",
4
+ color: '#C7E9C0',
5
+ defaults: {
6
+ //buttonState: {value: true},
7
+ server: { type: "knxUltimate-config", required: true },
8
+ name: { value: "" },
9
+ topic: { value: "" },
10
+ GACommand: { value: "" },
11
+ GAStatus: { value: "" },
12
+ GAMoving: { value: "" },
13
+ GAImpulse: { value: "" },
14
+ GABlocking: { value: "" },
15
+ GAObstructionDetected: { value: "" },
16
+ valueStatusWhenOpen: { value: true },
17
+ valueCommandToOpen: { value: true },
18
+ blockingTimeout: { value: 0 },
19
+ onCommandReceivedWhileMoving: { value: "ignore" },
20
+ timeToFullyOpen: { value: 20, required: true, validate: RED.validators.number() },
21
+ timeToFullyClosed: { value: 20, required: true, validate: RED.validators.number() }
22
+ },
23
+ inputs: 0,
24
+ outputs: 1,
25
+ icon: "node-knx-icon.svg",
26
+ label: function () {
27
+ return this.name || "Garage/Door/Barrier opener"
28
+ },
29
+ paletteLabel: "KNX Garage opener",
30
+ // button: {
31
+ // enabled: function() {
32
+ // // return whether or not the button is enabled, based on the current
33
+ // // configuration of the node
34
+ // return !this.changed
35
+ // },
36
+ // visible: function() {
37
+ // // return whether or not the button is visible, based on the current
38
+ // // configuration of the node
39
+ // return this.hasButton
40
+ // },
41
+ // //toggle: "buttonState",
42
+ // onclick: function() {}
43
+ // },
44
+ oneditprepare: function () {
45
+ var node = this;
46
+ var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
47
+
48
+
49
+ function getGroupAddressSimple(_sourceWidgetAutocomplete, _additionalSearchTerm) {
50
+ $(_sourceWidgetAutocomplete).autocomplete({
51
+ minLength: 1,
52
+ source: function (request, response) {
53
+ //$.getJSON("csv", request, function( data, status, xhr ) {
54
+ $.getJSON("knxUltimatecsv?nodeID=" + $("#node-input-server").val() + "&" + { _: new Date().getTime() }, (data) => {
55
+ response(
56
+ $.map(data, function (value, key) {
57
+ var sSearch = value.ga + " (" + value.devicename + ") DPT" + value.dpt;
58
+ for (let index = 0; index < _additionalSearchTerm.length; index++) {
59
+ const sDPT = _additionalSearchTerm[index];
60
+ if (fullSearch(sSearch, request.term + " " + sDPT)) {
61
+ return {
62
+ label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
63
+ value: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
64
+ };
65
+ }
66
+ };
67
+ })
68
+ );
69
+ });
70
+ },
71
+ select: function (event, ui) {
72
+ // Sets Datapoint and device name automatically
73
+ const sDevName = ui.item.value.trim();
74
+ $(_sourceWidgetAutocomplete).val(sDevName);
75
+ },
76
+ });
77
+ }
78
+
79
+ // 31/03/2020 Search Helper
80
+ function fullSearch(sourceText, searchString) {
81
+ // This searches for all words in a string
82
+ var aSearchWords = searchString.toLowerCase().split(" ");
83
+ var i = 0;
84
+ for (let index = 0; index < aSearchWords.length; index++) {
85
+ if (sourceText.toLowerCase().indexOf(aSearchWords[index]) > -1) i += 1;
86
+ }
87
+ return i == aSearchWords.length;
88
+ }
89
+
90
+ if (oNodeServer === undefined) {
91
+ // Hide the UI
92
+ $("#divMain").hide();
93
+ } else {
94
+ $("#divMain").show();
95
+ $("#tabs").tabs();
96
+ getGroupAddressSimple("#node-input-GACommand", ["1."]);
97
+ getGroupAddressSimple("#node-input-GAStatus", ["1."]);
98
+ getGroupAddressSimple("#node-input-GAMoving", ["1."]);
99
+ getGroupAddressSimple("#node-input-GAImpulse", ["1."]);
100
+ getGroupAddressSimple("#node-input-GAObstructionDetected", ["1."]);
101
+ getGroupAddressSimple("#node-input-GABlocking", ["1."]);
102
+ }
103
+
104
+
105
+
106
+
107
+
108
+ },
109
+ oneditsave: function () {
110
+
111
+ },
112
+ oneditcancel: function () {
113
+
114
+ }
115
+ })
116
+
117
+ </script>
118
+
119
+ <script type="text/html" data-template-name="knxUltimateGarageDoorBarrierOpener">
120
+
121
+
122
+ <div class="form-row">
123
+ <b>KNX Garage/Door/Barrier opener</b>
124
+ <br />
125
+ <br />
126
+
127
+ <label for="node-input-server">
128
+ <i class="fa fa-circle-o"></i> Gateway
129
+ </label>
130
+ <input type="text" id="node-input-server" />
131
+ </div>
132
+
133
+ <div id="divMain">
134
+ <div class="form-row">
135
+ <label for="node-input-name">
136
+ <i class="fa fa-tag"></i> Name
137
+ </label>
138
+ <input type="text" id="node-input-name" />
139
+ </div>
140
+ <div class="form-row">
141
+ <label for="node-input-topic">
142
+ <i class="fa fa-tasks"></i> Topic
143
+ </label>
144
+ <input type="text" id="node-input-topic" />
145
+ </div>
146
+
147
+ <div id="tabs">
148
+ <ul>
149
+ <li><a href="#tabs-1"><i class="fa-solid fa-toggle-on"></i> Group Address Input</a></li>
150
+ <li><a href="#tabs-2"><i class="fa-solid fa-long-arrow-right"></i> Group Address Output</a></li>
151
+ <li><a href="#tabs-3"><i class="fa-solid fa-code-merge"></i> Timing/Behaviours</a></li>
152
+ </ul>
153
+ <div id="tabs-1">
154
+ <p>
155
+ <div class="form-row">
156
+ <label for="node-input-GAImpulse">
157
+ <i class="fa fa-question-circle"></i> Impulse
158
+ </label>
159
+ <input type="text" id="node-input-GAImpulse" placeholder="Type name or group address" />
160
+ </div>
161
+ <div class="form-row">
162
+ <label for="node-input-GACommand">
163
+ <i class="fa fa-play-circle-o"></i> Command
164
+ </label>
165
+ <input type="text" id="node-input-GACommand" placeholder="Type name or group address" />
166
+ </div>
167
+ <div class="form-row" id="node-input-valueCommandToOpen">
168
+ <label style="width:85%" for="node-input-valueCommandToOpen">
169
+ <i class="fa fa-sign-in"></i> Behaviour
170
+ </label>
171
+ <select id="node-input-valueCommandToOpen">
172
+ <option value=true>True = open / False = close</option>
173
+ <option value=false>True = close / False = open</option>
174
+ </select>
175
+
176
+ </div>
177
+ <div class="form-row">
178
+ <label for="node-input-GAStatus">
179
+ <i class="fa fa-question-circle"></i> Status
180
+ </label>
181
+ <input type="text" id="node-input-GAStatus" placeholder="Type name or group address" />
182
+ </div>
183
+ <div class="form-row" id="node-input-valueStatusWhenOpen">
184
+ <label style="width:85%" for="node-input-valueStatusWhenOpen">
185
+ <i class="fa fa-sign-in"></i> Behaviour
186
+ </label>
187
+ <select id="node-input-valueStatusWhenOpen">
188
+ <option value=true>True = open / False = closed</option>
189
+ <option value=false>True = closed / False = open</option>
190
+ </select>
191
+ </div>
192
+ <div class="form-row">
193
+ <label for="node-input-GABlocking">
194
+ <i class="fa fa-play-circle-o"></i> Blocking
195
+ </label>
196
+ <input type="text" id="node-input-GABlocking" placeholder="Type name or group address" />
197
+ </div>
198
+ <div class="form-row">
199
+ <label for="node-input-blockingTimeout">
200
+ <i class="fa fa-play-circle-o"></i> Blocking timeout
201
+ </label>
202
+ <input type="text" id="node-input-blockingTimeout" placeholder="In seconds" />
203
+ </div>
204
+ </p>
205
+ </div>
206
+ <div id="tabs-2">
207
+ <p>
208
+ <div class="form-row">
209
+ <label for="node-input-GAObstructionDetected">
210
+ <i class="fa fa-question-circle"></i> Obstruction
211
+ </label>
212
+ <input type="text" id="node-input-GAObstructionDetected" placeholder="Type name or group address" />
213
+ </div>
214
+ <div class="form-row">
215
+ <label for="node-input-GAMoving">
216
+ <i class="fa fa-question-circle"></i> Moving
217
+ </label>
218
+ <input type="text" id="node-input-GAMoving" placeholder="Type name or group address" />
219
+ </div>
220
+ </p>
221
+ </div>
222
+ <div id="tabs-3">
223
+ <p>
224
+ <div class="form-row">
225
+ <label style="width:150px;" for="node-input-onCommandReceivedWhileMoving">
226
+ <i class="fa-solid fa-down-left-and-up-right-to-center"></i> While moving
227
+ </label>
228
+ <select id="node-input-onCommandReceivedWhileMoving">
229
+ <option value="ignore">Ignore commands received</option>
230
+ <option value="obey">Stop moving then obey to new commad</option>
231
+ </select>
232
+ </div>
233
+ <div class="form-row">
234
+ <label style="width:150px;" for="node-input-timeToFullyOpen">
235
+ <i class="fa-regular fa-door-open"></i> Fully open time (s)
236
+ </label>
237
+ <input type="text" id="node-input-timeToFullyOpen" placeholder="In seconds" />
238
+ </div>
239
+ <div class="form-row">
240
+ <label style="width:150px;" for="node-input-timeToFullyClosed">
241
+ <i class="fa-regular fa-door-closed"></i> Fully closed time (s)
242
+ </label>
243
+ <input type="text" id="node-input-timeToFullyClosed" placeholder="In seconds" />
244
+ </div>
245
+ </p>
246
+ </div>
247
+ </div>
248
+
249
+
250
+
251
+
252
+
253
+ </div>
254
+
255
+ </script>
256
+ <script type="text/markdown" data-help-name="knxUltimateGarageDoorBarrierOpener">
257
+ <p>This node lets you control a garage/door/barrier, controlled with an impulse command (for example: push to open, push the same button to close, push the same button to stop the movement).
258
+
259
+ **General**
260
+ |Property|Description|
261
+ |--|--|
262
+ | Gateway | Select the KNX gateway to be used |
263
+ | Name | Self explaining. The node name. |
264
+ | Topic | The msg output topic. |
265
+
266
+ **Group Address Input**
267
+ These GA are transmitted by your devices to the node.
268
+ |Property|Description|
269
+ |--|--|
270
+ | Impulse | The KNX Group Address where you connected the impulsive command (for example: push to open, push the same button to close, push the same button to stop the movement) |
271
+ | Command | The KNX Group Address to command the movement. Pass **true** to open, **false** to close the door.|
272
+ | Invert command value | Invert the command boolean values, that becomes **true** to close, **false** to open the door.|
273
+ | Status | The KNX Group Address of the door status. It's usually the magnetic sensor mounted on the door to detect open/closed position. Must be **true** if the door is open, otherwise **flase**. It's used by the node to make coherent movements. **If leaved blank**, the status is computed using full open/full closed intervals specified in the **Timing/Behaviours** page. |
274
+ | Invert status value | Invert the status boolean values, that becomes **true** if the door is closed, otherwise **flase**. |
275
+ | Blocking | The KNX Group Address for blocking the movement. Pass **true** to open the door and block it in open status, **false** to close the door and block it in closed status.|
276
+ | Blocking timeout| Timeout, in seconds, after the blocking is restored; the door will not move and will wait further commands. Set 0 to never restoring from blocked status. Default is 0.|
277
+
278
+ **Group Address Output**
279
+ These GA are computed ad transmitted to the KNX bus by the node itself.
280
+ |Property|Description|
281
+ |--|--|
282
+ | Obstruction | The KNX Group Address of the obstruction detection. This GA is wrote by the node, whenever it detects an obstruction during movement. It's **true** if an obstruction is detected, otherwise **flase**.|
283
+ | Moving | The KNX Group Address of the moving status. This GA is wrote by the node, whenever the garage/door/barrier is moving. It's **true** while moving, otherwise **false**.|
284
+
285
+ **Timing/Behaviours**
286
+ |Property|Description|
287
+ |--|--|
288
+ | While moving | Define the behaviour while the door is moving to reach it's end position (fully open or fully closed). For example, the door can stop and reverse the direction, or you can choose to ignore sent commands until the door reaches it's end position. |
289
+ | Fully open time | The time (in seconds) of a full movement from fully closed position to fully open position. |
290
+ | Fully closed time | The time (in seconds) of a full movement from fully open position to fully closed position. |
291
+
292
+
293
+ <br/>
294
+
295
+ ### Outputs
296
+
297
+ 1. Standard output
298
+ : payload (string|number|object) : Pin 1 is the standard output of the command.
299
+
300
+ 2. Errors
301
+ : error (object) : Pin 2 Contains the detailed error message.
302
+
303
+ ### Details
304
+
305
+ `msg.payload` is used as the payload of the published message, in JSON ogbect format.
306
+
307
+ ```json
308
+ msg = {
309
+ topic: the topic you selected
310
+ payload: {
311
+ }
312
+ }
313
+ ````
314
+
315
+ <br/><a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/-SamplesHome" target="_blank"><i class="fa fa-code"></i> Samples</a>
316
+
317
+ [Find it useful?](https://www.paypal.me/techtoday)
318
+
319
+
320
+
321
+ </script>
@@ -0,0 +1,144 @@
1
+ /* eslint-disable max-len */
2
+ const dptlib = require("../KNXEngine/src/dptlib");
3
+ const _ = require('lodash');
4
+ const KNXUtils = require('../KNXEngine/src/protocol/KNXUtils');
5
+ const payloadRounder = require('./utils/payloadManipulation');
6
+
7
+ module.exports = function (RED) {
8
+
9
+ function knxUltimateGarageDoorBarrierOpener(config) {
10
+ RED.nodes.createNode(this, config);
11
+ const node = this;
12
+ node.server = RED.nodes.getNode(config.server);
13
+ // 11/11/2021 Is the node server disabled by the flow "disable" command?
14
+ if (node.server === null) {
15
+ node.status({ fill: 'red', shape: 'dot', text: '[THE GATEWAY NODE HAS BEEN DISABLED]' });
16
+ return;
17
+ }
18
+
19
+ node.topic = config.topic;
20
+ node.dpt = '1.001';
21
+ node.notifyreadrequest = false;
22
+ node.notifyreadrequestalsorespondtobus = 'false';
23
+ node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = '';
24
+ node.notifyresponse = false;
25
+ node.notifywrite = true;
26
+ node.initialread = false;
27
+ node.listenallga = true; // Don't remove
28
+ node.outputtype = 'write';
29
+ node.sysLogger = require('./utils/sysLogger.js').get({ loglevel: node.server.loglevel || 'error' }); // 08/04/2021 new logger to adhere to the loglevel selected in the config-window
30
+
31
+ // From KNX to the node
32
+ node.GACommand = config.GACommand === undefined ? "" : config.GACommand;
33
+ node.GAImpulse = config.GAImpulse === undefined ? "" : config.GAImpulse;
34
+ node.GABlocking = config.GABlocking === undefined ? "" : config.GABlocking;
35
+
36
+ // From the node to KNX
37
+ node.GAStatus = config.GAStatus === undefined ? "" : config.GAStatus;
38
+ node.GAMoving = config.GAMoving === undefined ? "" : config.GAMoving;
39
+ node.GAObstructionDetected = config.GAObstructionDetected === undefined ? "" : config.GAObstructionDetected;
40
+
41
+ node.valueStatusWhenOpen = config.valueStatusWhenOpen;
42
+ node.valueCommandToOpen = config.valueCommandToOpen;
43
+ node.onCommandReceivedWhileMoving = config.onCommandReceivedWhileMoving;
44
+ node.timeToFullyOpen = config.timeToFullyOpen === undefined ? 20 : config.timeToFullyOpen;
45
+ node.timeToFullyClosed = config.timeToFullyClosed === undefined ? 20 : config.timeToFullyClosed;
46
+ node.blockingTimeout = config.blockingTimeout === undefined ? 0 : config.blockingTimeout;
47
+ node.statusClosed = !node.valueStatusWhenOpen; // Door status (closed/open)
48
+ node.timerMovement = null;
49
+
50
+ // Validate the Address
51
+ try {
52
+ KNXUtils.validateKNXAddress(node.GACommand, true);
53
+ } catch (error) {
54
+ node.setNodeStatus({
55
+ fill: 'red', shape: 'dot', text: error.message, payload: '', GA: node.GACommand, dpt: '', devicename: '',
56
+ });
57
+ return;
58
+ }
59
+ try {
60
+ KNXUtils.validateKNXAddress(node.GAImpulse, true);
61
+ } catch (error) {
62
+ node.setNodeStatus({
63
+ fill: 'red', shape: 'dot', text: error.message, payload: '', GA: node.GAImpulse, dpt: '', devicename: '',
64
+ });
65
+ return;
66
+ }
67
+
68
+ // Used to call the status update from the config node.
69
+ node.setNodeStatus = ({
70
+ fill, shape, text, payload, GA, dpt, devicename,
71
+ }) => {
72
+ try {
73
+ if (node.server == null) { node.status({ fill: 'red', shape: 'dot', text: '[NO GATEWAY SELECTED]' }); return; }
74
+ const 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
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
80
+ node.status({ fill, shape, text: `${GA + payload + (node.listenallga === true ? ` ${devicename}` : '')} (${dDate.getDate()}, ${dDate.toLocaleTimeString()} ${text}` });
81
+ } catch (error) { /* empty */ }
82
+ };
83
+
84
+ // This function is called by the knx-ultimate config node, to output a msg.payload.
85
+ node.handleSend = (msg) => {
86
+ try {
87
+ switch (msg.knx.destination) {
88
+ case config.GACommand:
89
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve('1.001'));
90
+ if (msg.payload) {
91
+ let status = '';
92
+ if (msg.payload !== node.statusClosed) {
93
+ status = 'Moving to open position';
94
+ } else {
95
+ status = 'Moving to closed position';
96
+ }
97
+ node.setNodeStatus({
98
+ fill: 'yellow', shape: 'dot', text: status, payload: '',
99
+ });
100
+ node.server.writeQueueAdd({
101
+ grpaddr: node.GAImpulse, payload: true, dpt: '1.001', outputtype: 'write', nodecallerid: node.id
102
+ });
103
+ if (node.timerMovement !== null) clearTimeout(node.timerMovement);
104
+ node.timerMovement = setTimeout(() => {
105
+
106
+ }, node.timeToFullyOpen);
107
+ }
108
+
109
+ break;
110
+ case config.GArepeatStatus:
111
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptrepeat));
112
+ node.toggleGArepeat = msg.payload.decr_incr === 1;
113
+ node.setNodeStatus({
114
+ fill: 'green', shape: 'dot', text: 'KNX->HUE Repeat Status', payload: msg.payload,
115
+ });
116
+ break;
117
+ default:
118
+ break;
119
+ }
120
+ } catch (error) {
121
+ node.setNodeStatus({
122
+ fill: 'red', shape: 'dot', text: `KNX->HUE error ${error.message}`, payload: '',
123
+ });
124
+ }
125
+ // node.send(msg);
126
+ };
127
+
128
+ node.on('input', (msg) => {
129
+ if (typeof msg === 'undefined') return;
130
+ if (!node.server) return; // 29/08/2019 Server not instantiate
131
+ });
132
+
133
+ node.on('close', (done) => {
134
+ if (node.server) {
135
+ node.server.removeClient(node);
136
+ try {
137
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`knxUltimateGarageDoorBarrierOpener: Close: node id ${node.id} with topic ${node.topic || ''} has been removed from the server.`);
138
+ } catch (error) { }
139
+ }
140
+ done();
141
+ });
142
+ }
143
+ RED.nodes.registerType('knxUltimateGarageDoorBarrierOpener', knxUltimateGarageDoorBarrierOpener);
144
+ };
@@ -211,6 +211,7 @@ module.exports = function (RED) {
211
211
  node.currentHUEDevice.dimming.brightness = Math.round(dbright, 0);
212
212
  node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness);
213
213
  state = dbright > 0 ? { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } } : { on: { on: false } };
214
+ //state = { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } };
214
215
  } else if (temperatureChoosen !== undefined) {
215
216
  // Kelvin
216
217
  const mirek = hueColorConverter.ColorConverter.kelvinToMirek(temperatureChoosen);
@@ -219,8 +220,10 @@ module.exports = function (RED) {
219
220
  node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness);
220
221
  // Kelvin temp
221
222
  state = brightnessChoosen > 0 ? { on: { on: true }, dimming: { brightness: brightnessChoosen }, color_temperature: { mirek: mirek } } : { on: { on: false } };
223
+ //state = { on: { on: true }, dimming: { brightness: brightnessChoosen }, color_temperature: { mirek: mirek } };
222
224
  } else if (brightnessChoosen !== undefined) {
223
225
  state = brightnessChoosen > 0 ? { on: { on: true }, dimming: { brightness: brightnessChoosen } } : { on: { on: false } };
226
+ //state = { on: { on: true }, dimming: { brightness: brightnessChoosen } };
224
227
  } else {
225
228
  state = { on: { on: true } };
226
229
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "2.4.6",
6
+ "version": "2.4.8",
7
7
  "description": "Control your KNX intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.",
8
8
  "dependencies": {
9
9
  "binary-parser": "2.2.1",