node-red-contrib-knx-ultimate 4.3.3 → 4.3.5

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,19 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 4.3.5** - April 2026<br/>
10
+
11
+ - NEW: **KNX DEVICE** now supports explicit **`dpt = raw`** mode for incoming telegrams: decoding is skipped, `msg.payload` is `null`, and raw bytes remain available in `msg.knx.rawValue`.<br/>
12
+ - FIX: **KNX DEVICE RAW write** now accepts both **`msg.bitlength`** and legacy **`msg.bitlenght`**, with `bitlength` preferred and old flows kept backward compatible.<br/>
13
+ - CHANGE: **KNX DEVICE** in **`dpt = raw`** mode now accepts raw outgoing telegrams only via **`msg.writeraw`**, preventing accidental normal writes with an invalid datapoint.<br/>
14
+ - Docs/help/wiki: updated **KNX DEVICE** help HTML and **Device** wiki pages in all supported languages (**EN/IT/DE/FR/ES/zh-CN**) to document **raw mode** and the preferred **`bitlength`** property.<br/>
15
+
16
+ **Version 4.3.4** - April 2026<br/>
17
+
18
+ - UI: **KNX Function** editor areas now have colored backgrounds (light green for *input→bus*, light yellow for *bus→output*) for easier visual distinction.<br/>
19
+ - UI: **KNX Function** Monaco editor no longer shows red underlines for custom KNX functions (`getGAValue`, `setGAValue`, `toggle`, `self`).<br/>
20
+ - NEW: **KNX Function** : **`let val = await getGAValue(...)`** now automatically sends a `GroupValue_Read` to the KNX bus when the requested group address has no cached value yet, and waits up to 3 seconds for the device to respond before returning `null`. Use `await getGAValue(...)` in your KNX Function code to benefit from this behaviour.<br/>
21
+
9
22
  **Version 4.3.3** - April 2026<br/>
10
23
 
11
24
  - UI: in the **KNX Function helper**, the **Search GA** field is now always visible; if the ETS CSV is not imported, the field is disabled and shows `To enable the search, IMPORT THE ETS FILE`.<br/>
@@ -2066,6 +2066,8 @@ module.exports = (RED) => {
2066
2066
  }
2067
2067
  }
2068
2068
 
2069
+ const isRawMode = typeof _inputDpt === 'string' && _inputDpt.trim().toLowerCase() === 'raw'
2070
+
2069
2071
  const errorMessage = {
2070
2072
  topic: _outputtopic,
2071
2073
  payload: 'UNKNOWN, PLEASE IMPORT THE ETS FILE!',
@@ -2088,83 +2090,18 @@ module.exports = (RED) => {
2088
2090
 
2089
2091
  // Resolve DPT and convert value if available
2090
2092
  if (_Rawvalue !== null) {
2091
- try {
2092
- sInputDpt = _inputDpt === null ? tryToFigureOutDataPointFromRawValue(_Rawvalue) : _inputDpt
2093
- } catch (error) {
2094
- // Here comes if no datapoint has beeen found
2095
- node.sysLogger?.error(
2096
- 'buildInputMessage: Error returning from tryToFigureOutDataPointFromRawValue. Device ' +
2097
- _srcGA +
2098
- ' Destination ' +
2099
- _destGA +
2100
- ' Event ' +
2101
- _event +
2102
- " GA's Datapoint " +
2103
- (_inputDpt === null
2104
- ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
2105
- : _inputDpt) +
2106
- ' Devicename ' +
2107
- _devicename +
2108
- ' Topic ' +
2109
- _outputtopic +
2110
- ' ' +
2111
- error.message
2112
- )
2113
- errorMessage.payload = 'UNKNOWN: ERROR tryToFigureOutDataPointFromRawValue:' + error.message
2114
- return errorMessage
2115
- }
2116
-
2117
- try {
2118
- var dpt = dptlib.resolve(sInputDpt)
2119
- } catch (error) {
2120
- node.sysLogger?.error(
2121
- 'buildInputMessage: Error returning from dptlib.resolve(sInputDpt). Device ' +
2122
- _srcGA +
2123
- ' Destination ' +
2124
- _destGA +
2125
- ' Event ' +
2126
- _event +
2127
- " GA's Datapoint " +
2128
- (_inputDpt === null
2129
- ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
2130
- : _inputDpt) +
2131
- ' Devicename ' +
2132
- _devicename +
2133
- ' Topic ' +
2134
- _outputtopic +
2135
- ' ' +
2136
- error.message
2137
- )
2138
- errorMessage.payload = 'UNKNOWN: ERROR dptlib.resolve:' + error.messages
2139
- return errorMessage
2140
- }
2141
-
2142
- if (dpt !== null && _Rawvalue !== null) {
2093
+ if (isRawMode) {
2094
+ sInputDpt = 'raw'
2095
+ sPayloadmeasureunit = ''
2096
+ sDptdesc = 'Raw value'
2097
+ sPayloadsubtypevalue = ''
2098
+ } else {
2143
2099
  try {
2144
- jsValue = dptlib.fromBuffer(_Rawvalue, dpt)
2145
- if (jsValue === null) {
2146
- node.sysLogger?.warn(
2147
- 'buildInputMessage: received a wrong datagram form KNX BUS, from device ' +
2148
- _srcGA +
2149
- ' Destination ' +
2150
- _destGA +
2151
- ' Event ' +
2152
- _event +
2153
- " GA's Datapoint " +
2154
- (_inputDpt === null
2155
- ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
2156
- : _inputDpt) +
2157
- ' Devicename ' +
2158
- _devicename +
2159
- ' Topic ' +
2160
- _outputtopic +
2161
- ' NodeID ' +
2162
- _oNode.id || ''
2163
- )
2164
- }
2100
+ sInputDpt = _inputDpt === null ? tryToFigureOutDataPointFromRawValue(_Rawvalue) : _inputDpt
2165
2101
  } catch (error) {
2102
+ // Here comes if no datapoint has beeen found
2166
2103
  node.sysLogger?.error(
2167
- 'buildInputMessage: Error returning from DPT decoding. Device ' +
2104
+ 'buildInputMessage: Error returning from tryToFigureOutDataPointFromRawValue. Device ' +
2168
2105
  _srcGA +
2169
2106
  ' Destination ' +
2170
2107
  _destGA +
@@ -2179,34 +2116,112 @@ module.exports = (RED) => {
2179
2116
  ' Topic ' +
2180
2117
  _outputtopic +
2181
2118
  ' ' +
2182
- error.message +
2183
- ' NodeID ' +
2184
- _oNode.id || ''
2119
+ error.message
2185
2120
  )
2186
- errorMessage.payload = 'UNKNOWN: ERROR dptlib.fromBuffer:' + error.stack
2121
+ errorMessage.payload = 'UNKNOWN: ERROR tryToFigureOutDataPointFromRawValue:' + error.message
2187
2122
  return errorMessage
2188
2123
  }
2189
- }
2190
2124
 
2191
- // 19/01/2023 FORMATTING THE OUTPUT PAYLOAD (ROUND, ETC) BASED ON THE NODE CONFIG
2192
- //* ********************************************************
2193
- jsValue = payloadRounder.Manipulate(_oNode, jsValue)
2194
- //* ********************************************************
2125
+ try {
2126
+ var dpt = dptlib.resolve(sInputDpt)
2127
+ } catch (error) {
2128
+ node.sysLogger?.error(
2129
+ 'buildInputMessage: Error returning from dptlib.resolve(sInputDpt). Device ' +
2130
+ _srcGA +
2131
+ ' Destination ' +
2132
+ _destGA +
2133
+ ' Event ' +
2134
+ _event +
2135
+ " GA's Datapoint " +
2136
+ (_inputDpt === null
2137
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
2138
+ : _inputDpt) +
2139
+ ' Devicename ' +
2140
+ _devicename +
2141
+ ' Topic ' +
2142
+ _outputtopic +
2143
+ ' ' +
2144
+ error.message
2145
+ )
2146
+ errorMessage.payload = 'UNKNOWN: ERROR dptlib.resolve:' + error.messages
2147
+ return errorMessage
2148
+ }
2195
2149
 
2196
- if (dpt.subtype !== undefined) {
2197
- sPayloadmeasureunit = dpt.subtype.unit !== undefined ? dpt.subtype.unit : 'unknown'
2198
- sDptdesc = dpt.subtype.desc !== undefined ? dpt.subtype.desc.charAt(0).toUpperCase() + dpt.subtype.desc.slice(1) : 'unknown'
2199
- if (dpt.subtype.enc !== undefined) {
2150
+ if (dpt !== null && _Rawvalue !== null) {
2200
2151
  try {
2201
- if (!jsValue) sPayloadsubtypevalue = dpt.subtype.enc[0]
2202
- if (jsValue) sPayloadsubtypevalue = dpt.subtype.enc[1]
2152
+ jsValue = dptlib.fromBuffer(_Rawvalue, dpt)
2153
+ if (jsValue === null) {
2154
+ node.sysLogger?.warn(
2155
+ 'buildInputMessage: received a wrong datagram form KNX BUS, from device ' +
2156
+ _srcGA +
2157
+ ' Destination ' +
2158
+ _destGA +
2159
+ ' Event ' +
2160
+ _event +
2161
+ " GA's Datapoint " +
2162
+ (_inputDpt === null
2163
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
2164
+ : _inputDpt) +
2165
+ ' Devicename ' +
2166
+ _devicename +
2167
+ ' Topic ' +
2168
+ _outputtopic +
2169
+ ' NodeID ' +
2170
+ _oNode.id || ''
2171
+ )
2172
+ }
2203
2173
  } catch (error) {
2204
- // Don't care
2174
+ node.sysLogger?.error(
2175
+ 'buildInputMessage: Error returning from DPT decoding. Device ' +
2176
+ _srcGA +
2177
+ ' Destination ' +
2178
+ _destGA +
2179
+ ' Event ' +
2180
+ _event +
2181
+ " GA's Datapoint " +
2182
+ (_inputDpt === null
2183
+ ? "THE ETS FILE HAS NOT BEEN IMPORTED, SO I'M TRYING TO FIGURE OUT WHAT DATAPOINT BELONGS THIS GROUP ADDRESS. DON'T BLAME ME IF I'M WRONG, INSTEAD, IMPORT THE ETS FILE!"
2184
+ : _inputDpt) +
2185
+ ' Devicename ' +
2186
+ _devicename +
2187
+ ' Topic ' +
2188
+ _outputtopic +
2189
+ ' ' +
2190
+ error.message +
2191
+ ' NodeID ' +
2192
+ _oNode.id || ''
2193
+ )
2194
+ errorMessage.payload = 'UNKNOWN: ERROR dptlib.fromBuffer:' + error.stack
2195
+ return errorMessage
2196
+ }
2197
+ }
2198
+
2199
+ // 19/01/2023 FORMATTING THE OUTPUT PAYLOAD (ROUND, ETC) BASED ON THE NODE CONFIG
2200
+ //* ********************************************************
2201
+ jsValue = payloadRounder.Manipulate(_oNode, jsValue)
2202
+ //* ********************************************************
2203
+
2204
+ if (dpt.subtype !== undefined) {
2205
+ sPayloadmeasureunit = dpt.subtype.unit !== undefined ? dpt.subtype.unit : 'unknown'
2206
+ sDptdesc = dpt.subtype.desc !== undefined ? dpt.subtype.desc.charAt(0).toUpperCase() + dpt.subtype.desc.slice(1) : 'unknown'
2207
+ if (dpt.subtype.enc !== undefined) {
2208
+ try {
2209
+ if (!jsValue) sPayloadsubtypevalue = dpt.subtype.enc[0]
2210
+ if (jsValue) sPayloadsubtypevalue = dpt.subtype.enc[1]
2211
+ } catch (error) {
2212
+ // Don't care
2213
+ }
2205
2214
  }
2206
2215
  }
2207
2216
  }
2208
2217
  } else {
2209
2218
  // Don't care, it's a READ REQUEST
2219
+ if (isRawMode) {
2220
+ sInputDpt = 'raw'
2221
+ sPayloadmeasureunit = ''
2222
+ sDptdesc = 'Raw value'
2223
+ sPayloadsubtypevalue = ''
2224
+ }
2210
2225
  }
2211
2226
 
2212
2227
  try {
@@ -1,5 +1,25 @@
1
1
  <!-- <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/jquery.searchableSelect.js"></script> -->
2
2
 
3
+ <style>
4
+ /* Monaco editor - first area (input to bus): light green */
5
+ #sendMsgToKNXCode-editor .monaco-editor,
6
+ #sendMsgToKNXCode-editor .monaco-editor-background,
7
+ #sendMsgToKNXCode-editor .monaco-editor .margin,
8
+ #sendMsgToKNXCode-editor .monaco-editor .overflow-guard,
9
+ #sendMsgToKNXCode-editor .monaco-editor .lines-content,
10
+ #sendMsgToKNXCode-editor .monaco-editor .editor-scrollable {
11
+ background-color: #e8f5e9 !important;
12
+ }
13
+ /* Monaco editor - second area (bus to output): light yellow */
14
+ #receiveMsgFromKNXCode-editor .monaco-editor,
15
+ #receiveMsgFromKNXCode-editor .monaco-editor-background,
16
+ #receiveMsgFromKNXCode-editor .monaco-editor .margin,
17
+ #receiveMsgFromKNXCode-editor .monaco-editor .overflow-guard,
18
+ #receiveMsgFromKNXCode-editor .monaco-editor .lines-content,
19
+ #receiveMsgFromKNXCode-editor .monaco-editor .editor-scrollable {
20
+ background-color: #fffde7 !important;
21
+ }
22
+ </style>
3
23
 
4
24
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
5
25
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/KNXSendSnippets.js"></script>
@@ -225,6 +245,15 @@
225
245
  }
226
246
  ];
227
247
  const globalScope = typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : {});
248
+ const rawDptOption = { value: 'raw', text: 'raw Raw telegram (no decode)' }
249
+
250
+ function appendRawDptOption($select) {
251
+ if (!$select || !$select.length) return
252
+ if ($select.find("option[value='raw']").length > 0) return
253
+ $select.prepend($("<option></option>")
254
+ .attr("value", rawDptOption.value)
255
+ .text(rawDptOption.text))
256
+ }
228
257
 
229
258
  function knxUltimateDptsGetHelp(_dpt, _forceClose) {
230
259
  const detailsContainer = $("#dptDetailsContainer")
@@ -233,6 +262,39 @@
233
262
  }
234
263
  const serverId = $("#node-input-server").val()
235
264
  if (serverId === "_ADD_" || serverId === '' || _dpt === null || _dpt === '') return
265
+ if (String(_dpt).trim().toLowerCase() === 'raw') {
266
+ const helplinkContainer = $("#sampleCodeEditor")
267
+ try {
268
+ if (node.sampleEditor) {
269
+ node.sampleEditor.destroy()
270
+ delete node.sampleEditor
271
+ }
272
+ } catch (error) { }
273
+ $("#example-editor").empty()
274
+ helplinkContainer.empty()
275
+ node.sampleEditor = RED.editor.createEditor({
276
+ id: 'example-editor',
277
+ mode: 'ace/mode/javascript',
278
+ value: `// KNX-Ultimate set as RAW NODE
279
+ // Incoming telegrams skip datapoint decoding.
280
+ // msg.payload will be null and the raw telegram bytes are available in msg.knx.rawValue.
281
+ // For outgoing telegrams, use msg.writeraw = Buffer.from("0730", "hex")
282
+ // and optionally msg.bitlength for 1-bit / 2-bit / 4-bit datapoints.
283
+ return msg;`
284
+ })
285
+ try {
286
+ node.sampleEditor.setReadOnly(true)
287
+ node.sampleEditor.setShowPrintMargin(false)
288
+ } catch (error) { }
289
+ $("<div>", {
290
+ class: "dpt-details-link"
291
+ })
292
+ .append($("<span>").text("RAW mode keeps the telegram undecoded and exposes the bytes in "))
293
+ .append($("<code>").text("msg.knx.rawValue"))
294
+ .append($("<span>").text("."))
295
+ .appendTo(helplinkContainer)
296
+ return
297
+ }
236
298
  $.getJSON("knxUltimateDptsGetHelp?dpt=" + _dpt + "&serverId=" + serverId + "&" + { _: new Date().getTime() }, (data) => {
237
299
  const helplinkContainer = $("#sampleCodeEditor")
238
300
  try {
@@ -336,15 +398,29 @@
336
398
  }
337
399
  if (typeof monaco !== 'undefined' && !globalScope.knxFunctionMonacoCompletionProvider) {
338
400
  try {
339
- const suggestions = knxFunctionHelperItems.map(item => ({
401
+ const functionSuggestions = knxFunctionHelperItems.map(item => ({
340
402
  label: item.label,
341
403
  kind: monaco.languages.CompletionItemKind.Function,
342
404
  insertText: item.snippet,
343
405
  insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
344
406
  documentation: item.doc
345
407
  }));
408
+
346
409
  globalScope.knxFunctionMonacoCompletionProvider = monaco.languages.registerCompletionItemProvider('javascript', {
347
- provideCompletionItems: () => ({ suggestions })
410
+ provideCompletionItems: () => ({ suggestions: functionSuggestions })
411
+ });
412
+
413
+ monaco.languages.typescript.javascriptDefaults.addExtraLib([
414
+ '/** Read the cached value of a KNX group address. */',
415
+ 'declare function getGAValue(address: string, dpt?: string): any;',
416
+ '/** Send a value to a KNX group address. */',
417
+ 'declare function setGAValue(address: string, value: any, dpt?: string): void;',
418
+ '/** Toggle (invert) this node\'s current value on the KNX bus. */',
419
+ 'declare function toggle(): void;',
420
+ ].join('\n'), 'knx-ultimate-globals.d.ts');
421
+ monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
422
+ noSemanticValidation: true,
423
+ noSyntaxValidation: false
348
424
  });
349
425
  } catch (error) { }
350
426
  }
@@ -499,6 +575,27 @@
499
575
  value: node.receiveMsgFromKNXCode
500
576
  });
501
577
  applyEditorOptions(node.receiveMsgFromKNXCodeEditor);
578
+ if (typeof monaco !== 'undefined') {
579
+ try {
580
+ if (!globalScope._knxEditorThemesDefined) {
581
+ monaco.editor.defineTheme('knx-send-theme', {
582
+ base: 'vs', inherit: true, rules: [],
583
+ colors: { 'editor.background': '#e8f5e9', 'editorGutter.background': '#e8f5e9' }
584
+ });
585
+ monaco.editor.defineTheme('knx-receive-theme', {
586
+ base: 'vs', inherit: true, rules: [],
587
+ colors: { 'editor.background': '#fffde7', 'editorGutter.background': '#fffde7' }
588
+ });
589
+ globalScope._knxEditorThemesDefined = true;
590
+ }
591
+ if (typeof node.sendMsgToKNXCodeEditor.updateOptions === 'function') {
592
+ node.sendMsgToKNXCodeEditor.updateOptions({ theme: 'knx-send-theme' });
593
+ }
594
+ if (typeof node.receiveMsgFromKNXCodeEditor.updateOptions === 'function') {
595
+ node.receiveMsgFromKNXCodeEditor.updateOptions({ theme: 'knx-receive-theme' });
596
+ }
597
+ } catch (e) { }
598
+ }
502
599
  node.activeCodeEditor = null;
503
600
  attachFocusHandlers(node.sendMsgToKNXCodeEditor);
504
601
  attachFocusHandlers(node.receiveMsgFromKNXCodeEditor);
@@ -666,11 +763,13 @@
666
763
  refreshSecureGAs();
667
764
  $.getJSON("knxUltimateDpts?serverId=" + $("#node-input-server").val() + "&_=" + new Date().getTime(), (data) => {
668
765
  $("#node-input-dpt").empty();
766
+ appendRawDptOption($("#node-input-dpt"));
669
767
  data.forEach(dpt => {
670
768
  $("#node-input-dpt").append($("<option></option>")
671
769
  .attr("value", dpt.value)
672
770
  .text(dpt.text))
673
771
  });
772
+ if (node.dpt === undefined || node.dpt === '') node.dpt = '1.001'
674
773
  $("#node-input-dpt").val(node.dpt);
675
774
  // Load help sample
676
775
  knxUltimateDptsGetHelp(node.dpt, true);
@@ -136,6 +136,7 @@ module.exports = function (RED) {
136
136
  }
137
137
  const mode = (req.body && req.body.mode ? String(req.body.mode) : 'read').toLowerCase()
138
138
  const grpaddr = targetNode.topic
139
+ const isRawMode = typeof targetNode.dpt === 'string' && targetNode.dpt.trim().toLowerCase() === 'raw'
139
140
  if (mode !== 'read') {
140
141
  if (!grpaddr || String(grpaddr).trim() === '') {
141
142
  res.status(400).json({ error: 'Group address not set' })
@@ -145,6 +146,10 @@ module.exports = function (RED) {
145
146
  res.status(400).json({ error: 'Datapoint not defined for this node' })
146
147
  return
147
148
  }
149
+ if (isRawMode) {
150
+ res.status(400).json({ error: 'Manual write is not available when the node datapoint is set to raw' })
151
+ return
152
+ }
148
153
  }
149
154
 
150
155
  if (mode === 'read') {
@@ -299,7 +304,7 @@ module.exports = function (RED) {
299
304
  const payloadRounder = require('./utils/payloadManipulation')
300
305
  const dptlib = require('knxultimate').dptlib
301
306
 
302
- function knxUltimate (config) {
307
+ function knxUltimate(config) {
303
308
  RED.nodes.createNode(this, config)
304
309
  const node = this
305
310
  node.serverKNX = RED.nodes.getNode(config.server) || undefined
@@ -365,7 +370,7 @@ module.exports = function (RED) {
365
370
  // Get the Group Address from various sources
366
371
  if (config.setTopicType === undefined || config.setTopicType === 'str') {
367
372
  node.topic = config.topic
368
- node.dpt = config.dpt || '1.001'
373
+ node.dpt = (config.dpt === undefined || config.dpt === '') ? '1.001' : config.dpt
369
374
  } else if (config.setTopicType === 'flow') {
370
375
  try {
371
376
  node.topic = node.context().flow.get(config.topic)
@@ -467,6 +472,7 @@ module.exports = function (RED) {
467
472
  if (node.listenallga === true) return
468
473
  if (typeof node.topic !== 'string' || node.topic === '') return
469
474
  if (typeof node.dpt !== 'string' || node.dpt === '' || node.dpt === 'auto') return
475
+ if (node.dpt.trim().toLowerCase() === 'raw') return
470
476
 
471
477
  node.serverKNX.sendKNXTelegramToKNXEngine({
472
478
  grpaddr: node.topic,
@@ -500,36 +506,61 @@ module.exports = function (RED) {
500
506
  }
501
507
 
502
508
  // Used in the KNX Function TAB
503
- const getGAValue = function getGAValue (_ga = undefined, _dpt = undefined) {
504
- try {
505
- if (_ga === undefined) return
506
- // The GA can have the devicename as well, separated by a blank space (1/1/0 light table ovest),
507
- // I must take the GA only
508
- const blankSpacePosition = _ga.indexOf(' ')
509
- if (blankSpacePosition > -1) _ga = _ga.substring(0, blankSpacePosition)
510
- // Is there a GA in the server's exposedGAs?
511
- const found = node.serverKNX.exposedGAs.find(a => a.ga === _ga)
509
+ const getGAValue = async function getGAValue(_ga = undefined, _dpt = undefined) {
510
+ if (_ga === undefined) return null
511
+ // Strip devicename if present (e.g. "1/1/1 Light name" → "1/1/1")
512
+ const blankSpacePosition = _ga.indexOf(' ')
513
+ if (blankSpacePosition > -1) _ga = _ga.substring(0, blankSpacePosition)
514
+
515
+ const tryDecode = (rawValue, dpt) => {
516
+ try {
517
+ if (rawValue === null || rawValue === undefined) return undefined
518
+ return dptlib.fromBuffer(rawValue, dptlib.resolve(dpt))
519
+ } catch (_e) { return undefined }
520
+ }
521
+
522
+ const sendRead = () => {
523
+ try {
524
+ node.serverKNX.sendKNXTelegramToKNXEngine({
525
+ grpaddr: _ga, payload: '', dpt: '', outputtype: 'read', nodecallerid: node.id
526
+ })
527
+ RED.log.warn('getGAValue: sent groupvalue_read for ' + _ga)
528
+ } catch (e) {
529
+ RED.log.error('getGAValue: failed to send read for ' + _ga + ': ' + e.message)
530
+ }
531
+ }
532
+
533
+ // Check cache first
534
+ let found = node.serverKNX.exposedGAs.find(a => a.ga === _ga)
535
+ if (found !== undefined) {
536
+ const dptFinal = _dpt || found.dpt
537
+ if (!dptFinal) {
538
+ RED.log.error('getGAValue: no DPT for ' + _ga + '. Provide it as second parameter.')
539
+ return null
540
+ }
541
+ const val = tryDecode(found.rawValue, dptFinal)
542
+ if (val !== undefined) return val
543
+ // rawValue present but decode failed (bad format) — fall through to read
544
+ RED.log.warn('getGAValue: cached rawValue for ' + _ga + ' could not be decoded, sending read request')
545
+ }
546
+
547
+ // Not cached or decode failed: send read and poll up to 3 seconds
548
+ sendRead()
549
+ for (let i = 0; i < 30; i++) {
550
+ await new Promise(resolve => setTimeout(resolve, 100))
551
+ found = node.serverKNX.exposedGAs.find(a => a.ga === _ga)
512
552
  if (found !== undefined) {
513
- if (_dpt === undefined && found.dpt === undefined) {
514
- const errM = 'getGaValue: node ID:' + node.id + ' ' + 'No CSV file imported. Please provide the dpt manually'
515
- RED.log.error(errM)
516
- if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM)
517
- return
518
- };
519
- return dptlib.fromBuffer(found.rawValue, dptlib.resolve(_dpt || found.dpt))
520
- } else {
521
- const errM = 'getGaValue: node ID:' + node.id + ' ' + 'Group Address not yet read, try later.'
522
- RED.log.error(errM)
523
- if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM)
553
+ const dptFinal = _dpt || found.dpt
554
+ if (!dptFinal) return null
555
+ const val = tryDecode(found.rawValue, dptFinal)
556
+ if (val !== undefined) return val
524
557
  }
525
- } catch (error) {
526
- const errM = 'getGaValue: node ID:' + node.id + ' ' + error.stack
527
- RED.log.error(errM)
528
- if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM)
529
558
  }
559
+ RED.log.warn('getGAValue: no response from KNX bus for ' + _ga + ' within 3 seconds')
560
+ return null
530
561
  }
531
562
  // Used in the KNX Function TAB
532
- const setGAValue = function setGAValue (_ga = undefined, _value = undefined, _dpt = undefined) {
563
+ const setGAValue = function setGAValue(_ga = undefined, _value = undefined, _dpt = undefined) {
533
564
  try {
534
565
  if (_ga === undefined) return
535
566
  // The GA can have the devicename as well, separated by a blank space (1/1/0 light table ovest),
@@ -556,7 +587,7 @@ module.exports = function (RED) {
556
587
  }
557
588
  }
558
589
  // Used in the KNX Function TAB
559
- const self = function self (_value) {
590
+ const self = function self(_value) {
560
591
  try {
561
592
  node.serverKNX.sendKNXTelegramToKNXEngine({
562
593
  grpaddr: node.topic, payload: _value, dpt: node.dpt, outputtype: 'write', nodecallerid: node.id
@@ -568,7 +599,7 @@ module.exports = function (RED) {
568
599
  }
569
600
  }
570
601
  // Used in the KNX Function TAB
571
- const toggle = function toggle () {
602
+ const toggle = function toggle() {
572
603
  if (node.currentPayload === true || node.currentPayload === false) {
573
604
  try {
574
605
  node.serverKNX.sendKNXTelegramToKNXEngine({
@@ -583,7 +614,7 @@ module.exports = function (RED) {
583
614
  }
584
615
 
585
616
  // This function is called by the knx-ultimate config node, to output a msg.payload.
586
- node.handleSend = (msg) => {
617
+ node.handleSend = async (msg) => {
587
618
  // 27/03/2020 can i merge the last input msg arrived, with the output?
588
619
  try {
589
620
  if (node.passthrough === 'yes') {
@@ -613,8 +644,8 @@ module.exports = function (RED) {
613
644
  // -+++++++++++++++++++++++++++++++++++++++++++
614
645
  if (node.receiveMsgFromKNXCode !== undefined) {
615
646
  try {
616
- const receiveMsgFromKNXCode = new Function('msg', 'getGAValue', 'node', 'RED', 'self', 'toggle', 'setGAValue', node.receiveMsgFromKNXCode)
617
- msg = receiveMsgFromKNXCode(msg, getGAValue, node, RED, self, toggle, setGAValue)
647
+ const receiveMsgFromKNXCode = (new (Object.getPrototypeOf(async function () { }).constructor)('msg', 'getGAValue', 'node', 'RED', 'self', 'toggle', 'setGAValue', node.receiveMsgFromKNXCode))
648
+ msg = await receiveMsgFromKNXCode(msg, getGAValue, node, RED, self, toggle, setGAValue)
618
649
  } catch (error) {
619
650
  RED.log.error('knxUltimate: receiveMsgFromKNXCode: node ID:' + node.id + ' ' + error.message)
620
651
  if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`receiveMsgFromKNXCode: node id ${node.id} ` || ' ' + error.stack)
@@ -631,7 +662,7 @@ module.exports = function (RED) {
631
662
  if (msg !== undefined) node.send(msg)
632
663
  }
633
664
 
634
- node.on('input', (msg) => {
665
+ node.on('input', async (msg) => {
635
666
  if (typeof msg === 'undefined') return
636
667
  if (!node.serverKNX) return // 29/08/2019 Server not instantiate
637
668
 
@@ -679,8 +710,8 @@ module.exports = function (RED) {
679
710
  // -+++++++++++++++++++++++++++++++++++++++++++
680
711
  if (node.sendMsgToKNXCode !== undefined) {
681
712
  try {
682
- const sendMsgToKNXCode = new Function('msg', 'getGAValue', 'node', 'RED', 'self', 'toggle', 'setGAValue', node.sendMsgToKNXCode)
683
- msg = sendMsgToKNXCode(msg, getGAValue, node, RED, self, toggle, setGAValue)
713
+ const sendMsgToKNXCode = (new (Object.getPrototypeOf(async function () { }).constructor)('msg', 'getGAValue', 'node', 'RED', 'self', 'toggle', 'setGAValue', node.sendMsgToKNXCode))
714
+ msg = await sendMsgToKNXCode(msg, getGAValue, node, RED, self, toggle, setGAValue)
684
715
  if (msg === undefined) return
685
716
  } catch (error) {
686
717
  RED.log.error('knxUltimate: sendMsgToKNXCode: node ID:' + node.id + ' ' + error.message)
@@ -886,8 +917,11 @@ module.exports = function (RED) {
886
917
  // connection.writeRaw('1/0/0', Buffer.from('0730', 'hex'))
887
918
  if (msg.hasOwnProperty('writeraw') && msg.hasOwnProperty('writeraw') !== null) {
888
919
  try {
889
- if (msg.hasOwnProperty('bitlenght') && msg.bitlenght !== null) {
890
- node.serverKNX.knxConnection.writeRaw(grpaddr, msg.writeraw, msg.bitlenght)
920
+ const rawBitlength = msg.hasOwnProperty('bitlength') && msg.bitlength !== null
921
+ ? msg.bitlength
922
+ : (msg.hasOwnProperty('bitlenght') && msg.bitlenght !== null ? msg.bitlenght : null)
923
+ if (rawBitlength !== null) {
924
+ node.serverKNX.knxConnection.writeRaw(grpaddr, msg.writeraw, rawBitlength)
891
925
  } else {
892
926
  node.serverKNX.knxConnection.writeRaw(grpaddr, msg.writeraw)
893
927
  }
@@ -902,6 +936,20 @@ module.exports = function (RED) {
902
936
  return
903
937
  }
904
938
 
939
+ if (typeof dpt === 'string' && dpt.trim().toLowerCase() === 'raw') {
940
+ node.setNodeStatus({
941
+ fill: 'red',
942
+ shape: 'dot',
943
+ text: 'RAW mode accepts only msg.writeraw for outgoing telegrams',
944
+ payload: '',
945
+ GA: grpaddr,
946
+ dpt: '',
947
+ devicename: ''
948
+ })
949
+ node.sysLogger?.error(`node id: ${node.id} RAW mode accepts only msg.writeraw for outgoing telegrams (${grpaddr})`)
950
+ return
951
+ }
952
+
905
953
  if (outputtype == 'response') {
906
954
  try {
907
955
  node.currentPayload = msg.payload// 31/12/2019 Set the current value (because, if the node is a virtual device, then it'll never fire "GroupValue_Write" in the server node, causing the currentPayload to never update)
@@ -960,9 +1008,9 @@ module.exports = function (RED) {
960
1008
  })
961
1009
 
962
1010
  // On each deploy, add the node to the server list
963
- if (node.serverKNX) {
964
- node.serverKNX.addClient(node)
965
- if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`addClient: node id ${node.id}` || '' + ` with topic ${node.topic || ''} has been added to the server.`)
1011
+ if (node.serverKNX) {
1012
+ node.serverKNX.addClient(node)
1013
+ if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`addClient: node id ${node.id}` || '' + ` with topic ${node.topic || ''} has been added to the server.`)
966
1014
  // 05/11/2021 if the node is set to read from bus, issue a read.
967
1015
  // "node-input-initialread0": "No",
968
1016
  // "node-input-initialread1": "Leggi dal BUS KNX",
@@ -975,11 +1023,11 @@ module.exports = function (RED) {
975
1023
  const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ".
976
1024
  node.emit('input', { readstatus: true })
977
1025
  }, 3000)
978
- }
979
- }
1026
+ }
1027
+ }
980
1028
 
981
- // Start periodic send timer (optional)
982
- startPeriodicSendTimer()
983
- }
984
- RED.nodes.registerType('knxUltimate', knxUltimate)
1029
+ // Start periodic send timer (optional)
1030
+ startPeriodicSendTimer()
1031
+ }
1032
+ RED.nodes.registerType('knxUltimate', knxUltimate)
985
1033
  }
@@ -112,7 +112,7 @@ if (msg.payload === false && getGAValue('0/0/11','1.001') === false){ return; }
112
112
  - **event (string)**: `GroupValue_Write`, `GroupValue_Response`, `Update_NoWrite` (nur interner Wert, kein BUS-Senden)
113
113
  - **readstatus (boolean)**: Leseauftrag an den BUS
114
114
  - **dpt (string)**: z. B. `1.001`
115
- - **writeraw (buffer)** + **bitlenght (int)**: RAW-Senden; `bitlenght` in Bit
115
+ - **writeraw (buffer)** + **bitlength (int)**: RAW-Senden; `bitlength` in Bit (`bitlenght` wird aus Kompatibilitaetsgruenden weiterhin akzeptiert)
116
116
  - **resetRBE (boolean)**: RBE-Filter zurücksetzen
117
117
  - **setConfig (json)**: GA/DPT des Nodes per Nachricht ändern
118
118
 
@@ -181,7 +181,8 @@ Für Read statt `event` bitte `msg.readstatus = true` verwenden.
181
181
 
182
182
  - **msg.readstatus = true**: Read an den BUS
183
183
  - **msg.dpt**: z. B. `1.001` (auch `9`, `"9"`, `"DPT9.001"`)
184
- - **msg.writeraw** + **msg.bitlenght**: RAW-Senden; ignoriert den am Node gesetzten DPT
184
+ - **msg.dpt = raw**: eingehende Telegramme bleiben undekodiert; `msg.payload` ist `null` und die Bytes stehen in `msg.knx.rawValue`. Zum Senden von RAW-Telegrammen `msg.writeraw` verwenden
185
+ - **msg.writeraw** + **msg.bitlength**: RAW-Senden; ignoriert den am Node gesetzten DPT
185
186
  - **msg.resetRBE = true**: RBE-Filter zurücksetzen
186
187
 
187
188
  ## Konfiguration per Nachricht ändern
@@ -227,7 +228,7 @@ msg.readstatus = true; return msg;
227
228
  ```javascript
228
229
 
229
230
  msg.writeraw = Buffer.from('01','hex');
230
- msg.bitlenght = 1; return msg;
231
+ msg.bitlength = 1; return msg;
231
232
  // Temperatur (DPT9): 18.4 °C = <0730>
232
233
  // msg.writeraw = Buffer.from('0730','hex'); return msg;
233
234
  ```
@@ -159,8 +159,9 @@ if (msg.payload === false && getGAValue('0/0/11','1.001') === false){
159
159
  - **event (string)**: `_GroupValue_Write_` / `_GroupValue_Response_` / `_Update_NoWrite_` (`_Update_NoWrite_` updates the internal value only)
160
160
  - **readstatus (boolean)**: issue a read request to the KNX bus (pass `_true_` every time: `msg.readstatus = true`)
161
161
  - **dpt (string)**: for example `"1.001"` (sets the datapoint)
162
- - **writeraw (buffer)**: sends a raw buffer value to the bus (use with **bitlenght**)
163
- - **bitlenght (int)**: bit length of **writeraw**
162
+ - **dpt = raw**: keeps incoming telegrams undecoded; `msg.payload` will be `null` and the bytes will be available in `msg.knx.rawValue`. Use `msg.writeraw` to send raw telegrams
163
+ - **writeraw (buffer)**: sends a raw buffer value to the bus (use with **bitlength**)
164
+ - **bitlength (int)**: bit length of **writeraw** (`bitlenght` is still accepted for backward compatibility)
164
165
  - **resetRBE (boolean)**: resets the internal RBE filters (`msg.resetRBE = true`)
165
166
  - **setConfig (json)**: programmatically change the node Group Address and Datapoint (see details)
166
167
 
@@ -263,8 +264,9 @@ msg = {
263
264
  - **event (string)**: `_GroupValue_Write_` / `_GroupValue_Response_` / `_Update_NoWrite_` (`_Update_NoWrite_` updates the internal value only)
264
265
  - **readstatus (boolean)**: issue a read request to the KNX bus (pass `_true_` every time: `msg.readstatus = true`)
265
266
  - **dpt (string)**: for example `"1.001"` (sets the datapoint)
266
- - **writeraw (buffer)**: sends a raw buffer value to the bus (use with **bitlenght**)
267
- - **bitlenght (int)**: bit length of **writeraw**
267
+ - **dpt = raw**: keeps incoming telegrams undecoded; `msg.payload` will be `null` and the bytes will be available in `msg.knx.rawValue`. Use `msg.writeraw` to send raw telegrams
268
+ - **writeraw (buffer)**: sends a raw buffer value to the bus (use with **bitlength**)
269
+ - **bitlength (int)**: bit length of **writeraw** (`bitlenght` is still accepted for backward compatibility)
268
270
  - **resetRBE (boolean)**: resets the internal RBE filters (`msg.resetRBE = true`)
269
271
  - **setConfig (json)**: programmatically change the node Group Address and Datapoint (see details)
270
272
 
@@ -436,7 +438,7 @@ Issue a "Read" command to the BUS.
436
438
  **msg.dpt** <br/>
437
439
  For example "1.001". Sets the <b>Datapoint</b>. (You can write it in these formats: 9 , "9" , "9.001" or "DPT9.001")
438
440
 
439
- **msg.writeraw ** <br/>**msg.bitlenght** <br/>
441
+ **msg.writeraw ** <br/>**msg.bitlength** <br/>
440
442
  Writes RAW data to the KNX BUS. Please see below an example.<br/>
441
443
 
442
444
  **msg.resetRBE** <br/> pass msg.resetRBE = true to a device node, to reset both input and output RBE filter on that particular node.<br/>
@@ -500,23 +502,23 @@ return msg;
500
502
 
501
503
  **SEND RAW VALUE TO THE BUS** <br/>
502
504
 
503
- To send a RAW buffer value to the KNX bus, use the _ **writeraw ** _ and _**bitlenght** _ properties of the msg input.<br/>
505
+ To send a RAW buffer value to the KNX bus, use the _ **writeraw ** _ and _**bitlength** _ properties of the msg input.<br/>
504
506
  In this case, the _Datapoint_ you set in the property window will be ignored.<br/>
505
507
  Slap a function node in front of knx-ultimate and paste his code:
506
508
 
507
509
  ```javascript
508
510
 
509
511
  // If you encode the values by yourself, you can write raw buffers with writeraw.
510
- // The bitlenght is necessary for datapoint types where the bitlenght does not equal the buffers bytelenght * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlenght 2) and 3 (bitlenght 4).
512
+ // The bitlength is necessary for datapoint types where the bitlength does not equal the buffer bytelength * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlength 2) and 3 (bitlength 4).
511
513
  // Write raw buffer to a groupaddress with dpt 1 (e.g light on = value true = Buffer<01>) with a bitlength of 1
512
514
  msg.writeraw = Buffer.from('01', 'hex'); // Put '00' instead of '01' to switch off the light.
513
- msg.bitlenght = 1;
515
+ msg.bitlength = 1;
514
516
  return msg;
515
517
 
516
518
  // Temperature sample (uncomment below and comment above)
517
519
  // Write raw buffer to a groupaddress with dpt 9 (e.g temperature 18.4 °C = Buffer<0730>) without bitlength
518
520
  // msg.writeraw = Buffer.from('0730', 'hex');
519
- // msg.bitlenght = 1;
521
+ // msg.bitlength = 1;
520
522
  // return msg;
521
523
 
522
524
  ```
@@ -159,7 +159,8 @@ if (msg.payload === false && getGAValue('0/0/11','1.001') === false){
159
159
  - **event (string)**: `GroupValue_Write` / `GroupValue_Response` / `Update_NoWrite` (`Update_NoWrite` actualiza el valor interno y no envía al BUS)
160
160
  - **readstatus (boolean)**: emite una solicitud de lectura al bus (use siempre `true`: `msg.readstatus = true`)
161
161
  - **dpt (string)**: por ejemplo `1.001` (datapoint)
162
- - **writeraw (buffer)** + **bitlenght (int)**: envío RAW al BUS (ver ejemplo)
162
+ - **dpt = raw**: mantiene los telegramas entrantes sin decodificar; `msg.payload` sera `null` y los bytes estaran disponibles en `msg.knx.rawValue`. Para enviar RAW use `msg.writeraw`
163
+ - **writeraw (buffer)** + **bitlength (int)**: envio RAW al BUS (ver ejemplo); `bitlenght` sigue siendo aceptado por compatibilidad
163
164
  - **resetRBE (boolean)**: restablece filtros RBE internos (`msg.resetRBE = true`)
164
165
  - **setConfig (json)**: cambia programáticamente GA y DPT (ver detalles)
165
166
 
@@ -257,7 +258,8 @@ msg = {
257
258
  - **event (string)**: `GroupValue_Write` / `GroupValue_Response` / `Update_NoWrite`
258
259
  - **readstatus (boolean)**: solicitud de lectura al bus (`msg.readstatus = true`)
259
260
  - **dpt (string)**: por ejemplo `1.001`
260
- - **writeraw (buffer)** + **bitlenght (int)**: envío RAW (ver ejemplo)
261
+ - **dpt = raw**: mantiene los telegramas entrantes sin decodificar; `msg.payload` sera `null` y los bytes estaran disponibles en `msg.knx.rawValue`. Para enviar RAW use `msg.writeraw`
262
+ - **writeraw (buffer)** + **bitlength (int)**: envio RAW (ver ejemplo); `bitlenght` sigue siendo aceptado por compatibilidad
261
263
  - **resetRBE (boolean)**: restablece los filtros RBE (`msg.resetRBE = true`)
262
264
  - **setConfig (json)**: cambia GA y DPT (ver detalles)
263
265
 
@@ -418,7 +420,7 @@ PRECAUCIÓN: en el caso de _msg.event = "update \ _nowrite" _ Todos los nodos co
418
420
 
419
421
  Si desea emitir un comando "Leer", use "ReadStatus" en su lugar (ver más abajo). <BR/> ** msg.readstatus = true** <br/>
420
422
  Emita un comando de "leer" al bus. ** msg.dpt** <br/>
421
- Por ejemplo "1.001". Establece el punto de datos <b> </b>. (Puede escribirlo en estos formatos: 9, "9", "9.001" o "DPT9.001") ** msg.writeraw ** <br/>**msg.bitlenght** <br/>
423
+ Por ejemplo "1.001". Establece el punto de datos <b> </b>. (Puede escribirlo en estos formatos: 9, "9", "9.001" o "DPT9.001") ** msg.writeraw ** <br/>**msg.bitlength** <br/>
422
424
  Escribe datos sin procesar en el bus KNX. Consulte a continuación un ejemplo. <br/> ** msg.resetrbe** <br/> pase msg.resetrbe = true a un nodo de dispositivo, para restablecer tanto el filtro de entrada como de salida RBE en ese nodo particular. <br/>
423
425
 
424
426
  <br/>
@@ -478,23 +480,23 @@ return msg;
478
480
 
479
481
  ** Enviar valor bruto al bus** <br/>
480
482
 
481
- Para enviar un valor de búfer en bruto al bus KNX, use el _ ** escritorw ** _ y _**bitLenght** _ Propiedades de la entrada de MSG. <br/>
483
+ Para enviar un valor de buffer RAW al bus KNX, use las propiedades _ **writeraw** _ y _**bitlength** _ de la entrada MSG. <br/>
482
484
  En este caso, se ignorará el _datapoint_ que establece en la ventana de la propiedad. <br/>
483
485
  Softe un nodo de función frente a KNX-Ulimate y pegue su código:
484
486
 
485
487
  ```javascript
486
488
 
487
489
  // If you encode the values by yourself, you can write raw buffers with writeraw.
488
- // The bitlenght is necessary for datapoint types where the bitlenght does not equal the buffers bytelenght * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlenght 2) and 3 (bitlenght 4).
490
+ // The bitlength is necessary for datapoint types where the bitlength does not equal the buffer bytelength * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlength 2) and 3 (bitlength 4).
489
491
  // Write raw buffer to a groupaddress with dpt 1 (e.g light on = value true = Buffer<01>) with a bitlength of 1
490
492
  msg.writeraw = Buffer.from('01', 'hex'); // Put '00' instead of '01' to switch off the light.
491
- msg.bitlenght = 1;
493
+ msg.bitlength = 1;
492
494
  return msg;
493
495
 
494
496
  // Temperature sample (uncomment below and comment above)
495
497
  // Write raw buffer to a groupaddress with dpt 9 (e.g temperature 18.4 °C = Buffer<0730>) without bitlength
496
498
  // msg.writeraw = Buffer.from('0730', 'hex');
497
- // msg.bitlenght = 1;
499
+ // msg.bitlength = 1;
498
500
  // return msg;
499
501
 
500
502
  ```
@@ -159,7 +159,8 @@ if (msg.payload === false && getGAValue('0/0/11','1.001') === false){
159
159
  - **event (string)**: `GroupValue_Write` / `GroupValue_Response` / `Update_NoWrite` (`Update_NoWrite` met à jour la valeur interne et n'envoie rien au BUS)
160
160
  - **readstatus (boolean)**: demande de lecture au bus (utiliser toujours `true`: `msg.readstatus = true`)
161
161
  - **dpt (string)**: par exemple `1.001` (datapoint)
162
- - **writeraw (buffer)** + **bitlenght (int)**: envoi RAW au BUS (voir exemple)
162
+ - **dpt = raw**: conserve les telegrammes entrants sans decodage ; `msg.payload` sera `null` et les octets seront disponibles dans `msg.knx.rawValue`. Pour envoyer du RAW, utilisez `msg.writeraw`
163
+ - **writeraw (buffer)** + **bitlength (int)**: envoi RAW au BUS (voir exemple) ; `bitlenght` reste accepte pour compatibilite
163
164
  - **resetRBE (boolean)**: réinitialise les filtres RBE (`msg.resetRBE = true`)
164
165
  - **setConfig (json)**: modifier GA et DPT par message (voir détails)
165
166
 
@@ -257,7 +258,8 @@ msg = {
257
258
  - **event (string)**: `GroupValue_Write` / `GroupValue_Response` / `Update_NoWrite`
258
259
  - **readstatus (boolean)**: demande de lecture au bus (`msg.readstatus = true`)
259
260
  - **dpt (string)**: par exemple `1.001`
260
- - **writeraw (buffer)** + **bitlenght (int)**: envoi RAW au BUS (voir exemple)
261
+ - **dpt = raw**: conserve les telegrammes entrants sans decodage ; `msg.payload` sera `null` et les octets seront disponibles dans `msg.knx.rawValue`. Pour envoyer du RAW, utilisez `msg.writeraw`
262
+ - **writeraw (buffer)** + **bitlength (int)**: envoi RAW au BUS (voir exemple) ; `bitlenght` reste accepte pour compatibilite
261
263
  - **resetRBE (boolean)**: réinitialise les filtres RBE (`msg.resetRBE = true`)
262
264
  - **setConfig (json)**: modifier GA et DPT (voir détails)
263
265
 
@@ -418,7 +420,7 @@ ATTENTION: Dans le cas de _msg.event = "Update \ _Nowrite" _ Tous les nœuds ave
418
420
 
419
421
  Si vous souhaitez émettre une commande "lire", veuillez utiliser "ReadStatus" à la place (voir ci-dessous). <br/> ** msg.readstatus = true** <br/>
420
422
  Émettez une commande "lire" au bus. ** msg.dpt** <br/>
421
- Par exemple "1.001". Définit le <b> datapoint </b>. (Vous pouvez l'écrire dans ces formats: 9, "9", "9.001" ou "DPT9.001") ** msg.writeraw ** <br/>**msg.bitlenght** <br/>
423
+ Par exemple "1.001". Définit le <b> datapoint </b>. (Vous pouvez l'écrire dans ces formats: 9, "9", "9.001" ou "DPT9.001") ** msg.writeraw ** <br/>**msg.bitlength** <br/>
422
424
  Écrit des données brutes au bus KNX. Veuillez voir ci-dessous un exemple. <br/> ** MSG.RESETRBE** <BR/> PASS MSG.RESETRBE = VRAI À un nœud de périphérique, pour réinitialiser à la fois l'entrée et la sortie du filtre RBE sur ce nœud particulier. <br/>
423
425
 
424
426
  <br/>
@@ -478,23 +480,23 @@ return msg;
478
480
 
479
481
  **Envoyez la valeur brute au bus** <br/>
480
482
 
481
- Pour envoyer une valeur de tampon brute au bus KNX, utilisez les propriétés _ ** writerraw ** _ et _**bitlenght** _ de l'entrée msg. <br/>
483
+ Pour envoyer une valeur RAW au bus KNX, utilisez les proprietes _ **writeraw** _ et _**bitlength** _ de l'entree msg. <br/>
482
484
  Dans ce cas, le _datapoint_ que vous définissez dans la fenêtre de propriété sera ignoré. <br/>
483
485
  Grapez un nœud de fonction devant KNX-ultime et collez son code:
484
486
 
485
487
  ```javascript
486
488
 
487
489
  // If you encode the values by yourself, you can write raw buffers with writeraw.
488
- // The bitlenght is necessary for datapoint types where the bitlenght does not equal the buffers bytelenght * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlenght 2) and 3 (bitlenght 4).
490
+ // The bitlength is necessary for datapoint types where the bitlength does not equal the buffer bytelength * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlength 2) and 3 (bitlength 4).
489
491
  // Write raw buffer to a groupaddress with dpt 1 (e.g light on = value true = Buffer<01>) with a bitlength of 1
490
492
  msg.writeraw = Buffer.from('01', 'hex'); // Put '00' instead of '01' to switch off the light.
491
- msg.bitlenght = 1;
493
+ msg.bitlength = 1;
492
494
  return msg;
493
495
 
494
496
  // Temperature sample (uncomment below and comment above)
495
497
  // Write raw buffer to a groupaddress with dpt 9 (e.g temperature 18.4 °C = Buffer<0730>) without bitlength
496
498
  // msg.writeraw = Buffer.from('0730', 'hex');
497
- // msg.bitlenght = 1;
499
+ // msg.bitlength = 1;
498
500
  // return msg;
499
501
 
500
502
  ```
@@ -141,7 +141,7 @@ if (msg.payload === false && getGAValue('0/0/11','1.001') === false){
141
141
  - **event (string)**: `GroupValue_Write` (scrive sul BUS), `GroupValue_Response` (risponde sul BUS), `Update_NoWrite` (non invia al BUS, aggiorna solo il valore interno del nodo)
142
142
  - **readstatus (boolean)**: invia un "Read" al BUS (usa sempre `true`)
143
143
  - **dpt (string)**: per es. `1.001` (imposta il Datapoint)
144
- - **writeraw (buffer)** + **bitlenght (int)**: invio RAW verso il BUS (vedi esempio); `bitlenght` è la lunghezza in bit del dato RAW
144
+ - **writeraw (buffer)** + **bitlength (int)**: invio RAW verso il BUS (vedi esempio); `bitlength` è la lunghezza in bit del dato RAW (`bitlenght` resta accettato per retrocompatibilità)
145
145
  - **resetRBE (boolean)**: resetta i filtri RBE interni (`msg.resetRBE = true`)
146
146
  - **setConfig (json)**: cambia via msg il GA e il DPT del nodo (vedi sotto)
147
147
 
@@ -230,7 +230,8 @@ Se vuoi emettere un "read”, usa `msg.readstatus = true`.
230
230
 
231
231
  - **msg.readstatus = true**: emette un comando di lettura verso il BUS
232
232
  - **msg.dpt**: per es. `1.001` (accetta anche `9`, `"9"`, `"DPT9.001"`)
233
- - **msg.writeraw** + **msg.bitlenght**: invio RAW al BUS (vedi esempio sotto); se usi `writeraw`, il DPT impostato nel nodo viene ignorato
233
+ - **msg.dpt = raw**: lascia i telegrammi in ingresso non decodificati; `msg.payload` sara' `null` e i byte saranno disponibili in `msg.knx.rawValue`. Per inviare RAW usa `msg.writeraw`
234
+ - **msg.writeraw** + **msg.bitlength**: invio RAW al BUS (vedi esempio sotto); se usi `writeraw`, il DPT impostato nel nodo viene ignorato
234
235
  - **msg.resetRBE = true**: resetta i filtri RBE di input e output del nodo
235
236
 
236
237
  ## Cambiare la configurazione via msg
@@ -285,7 +286,7 @@ Inserisci un function node prima del nodo KNX-Ultimate e usa questo codice:
285
286
 
286
287
  // Esempio DPT 1: luce ON = Buffer<01> con bitlength = 1
287
288
  msg.writeraw = Buffer.from('01', 'hex');
288
- msg.bitlenght = 1;
289
+ msg.bitlength = 1;
289
290
  return msg;
290
291
 
291
292
  // Esempio temperatura (DPT 9): 18.4 °C = Buffer<0730>
@@ -112,7 +112,7 @@ if (msg.payload === false && getGAValue('0/0/11','1.001') === false){ return; }
112
112
  - **event (string)**:`GroupValue_Write` / `GroupValue_Response` / `Update_NoWrite`(`Update_NoWrite` 仅更新内部值,不发送)
113
113
  - **readstatus (boolean)**:向总线发起读取(使用 `msg.readstatus = true`)
114
114
  - **dpt (string)**:如 `1.001`
115
- - **writeraw (buffer)** + **bitlenght (int)**:发送 RAW,`bitlenght` 为比特长度
115
+ - **writeraw (buffer)** + **bitlength (int)**:发送 RAW,`bitlength` 为比特长度(为兼容旧流程,`bitlenght` 仍然可用)
116
116
  - **resetRBE (boolean)**:重置内部 RBE 过滤(`msg.resetRBE = true`)
117
117
  - **setConfig (json)**:通过消息修改节点的 GA 与 DPT
118
118
 
@@ -181,7 +181,8 @@ msg = {
181
181
 
182
182
  - **msg.readstatus = true**:向总线发起读取
183
183
  - **msg.dpt**:如 `1.001`(也接受 `9`、`"9"`、`"DPT9.001"`)
184
- - **msg.writeraw** + **msg.bitlenght**:发送 RAW;会忽略节点上设置的 DPT
184
+ - **msg.dpt = raw**:保持收到的报文不做解码;`msg.payload` 将为 `null`,原始字节可在 `msg.knx.rawValue` 中获取。发送 RAW 时请使用 `msg.writeraw`
185
+ - **msg.writeraw** + **msg.bitlength**:发送 RAW;会忽略节点上设置的 DPT
185
186
  - **msg.resetRBE = true**:重置 RBE 过滤
186
187
 
187
188
  ## 通过消息修改配置
@@ -229,7 +230,7 @@ msg.readstatus = true; return msg;
229
230
 
230
231
  // DPT1 示例:开灯 = Buffer<01>,bitlength = 1
231
232
  msg.writeraw = Buffer.from('01','hex');
232
- msg.bitlenght = 1; return msg;
233
+ msg.bitlength = 1; return msg;
233
234
  // 温度(DPT9)示例:18.4 °C = <0730>
234
235
  // msg.writeraw = Buffer.from('0730','hex'); return msg;
235
236
  ```
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "4.3.3",
6
+ "version": "4.3.5",
7
7
  "description": "Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control, ETS group address importer, and KNX routing between interfaces. Easy to use and highly configurable.",
8
8
  "files": [
9
9
  "nodes/",