node-red-contrib-knx-ultimate 4.3.2 → 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,24 @@
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
+
22
+ **Version 4.3.3** - April 2026<br/>
23
+
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/>
25
+ - In the KNX DEVICE NODE, added the "toggle with status" function snippet.<br/>
26
+
9
27
  **Version 4.3.2** - April 2026<br/>
10
28
 
11
29
  - Docs/help/wiki: updated **KNX AI** help HTML and wiki pages in all supported languages (EN/IT/DE/FR/ES/zh-CN) to reflect the latest LLM/Ollama UX changes.<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);
@@ -516,6 +613,27 @@
516
613
  insertTextIntoEditor(editor, sanitizedValue);
517
614
  });
518
615
 
616
+ const $knxFunctionHelperInput = $("#node-input-knxFunctionHelperGAList");
617
+ const $knxFunctionHelperInsertButton = $("#btn-insert-knxFunctionGA");
618
+ let knxFunctionHelperDefaultPlaceholder = $knxFunctionHelperInput.attr('placeholder') || '';
619
+ if (!knxFunctionHelperDefaultPlaceholder) {
620
+ try {
621
+ knxFunctionHelperDefaultPlaceholder = RED._("node-red-contrib-knx-ultimate/knxUltimate:knxUltimate.placeholder.search") || '';
622
+ } catch (error) { }
623
+ }
624
+ const knxFunctionHelperNoEtsPlaceholder = 'To enable the search, IMPORT THE ETS FILE';
625
+ const refreshKnxFunctionHelperState = (hasEtsCsv) => {
626
+ $("#divknxFunctionHelperGAList").show();
627
+ $knxFunctionHelperInput.prop('disabled', !hasEtsCsv);
628
+ $knxFunctionHelperInsertButton.prop('disabled', !hasEtsCsv);
629
+ if (hasEtsCsv) {
630
+ $knxFunctionHelperInput.attr('placeholder', knxFunctionHelperDefaultPlaceholder);
631
+ } else {
632
+ $knxFunctionHelperInput.val('');
633
+ $knxFunctionHelperInput.attr('placeholder', knxFunctionHelperNoEtsPlaceholder);
634
+ }
635
+ };
636
+
519
637
  const configureSnippetPicker = (snippets, inputSelector, datalistSelector, applySnippet) => {
520
638
  const inputEl = $(inputSelector)
521
639
  const datalistEl = $(datalistSelector)
@@ -628,15 +746,13 @@
628
746
  try {
629
747
  if (typeof oNodeServer.csv !== "undefined" && oNodeServer.csv !== "") {
630
748
  $("#isETSFileLoaded").val("si");
631
- $("#divknxFunctionHelperGAList").show();
632
749
  } else {
633
750
  $("#isETSFileLoaded").val("no");
634
- $("#divknxFunctionHelperGAList").hide();
635
751
  }
636
752
  } catch (error) {
637
753
  $("#isETSFileLoaded").val("no");
638
- $("#divknxFunctionHelperGAList").hide();
639
754
  }
755
+ refreshKnxFunctionHelperState($("#isETSFileLoaded").val() === "si");
640
756
  if (oNodeServer.knxSecureSelected) {
641
757
  $("#divknxsecure").show();
642
758
  } else {
@@ -647,11 +763,13 @@
647
763
  refreshSecureGAs();
648
764
  $.getJSON("knxUltimateDpts?serverId=" + $("#node-input-server").val() + "&_=" + new Date().getTime(), (data) => {
649
765
  $("#node-input-dpt").empty();
766
+ appendRawDptOption($("#node-input-dpt"));
650
767
  data.forEach(dpt => {
651
768
  $("#node-input-dpt").append($("<option></option>")
652
769
  .attr("value", dpt.value)
653
770
  .text(dpt.text))
654
771
  });
772
+ if (node.dpt === undefined || node.dpt === '') node.dpt = '1.001'
655
773
  $("#node-input-dpt").val(node.dpt);
656
774
  // Load help sample
657
775
  knxUltimateDptsGetHelp(node.dpt, true);