node-red-contrib-knx-ultimate 4.1.18 → 4.1.21

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.
@@ -1,6 +1,36 @@
1
1
  // KNX Router Filter - filters RAW telegram objects between KNX Multi Routing nodes
2
2
  const normalizeText = (value) => String(value || '').trim()
3
3
 
4
+ const normalizeHex = (value) => {
5
+ if (value === undefined || value === null) return ''
6
+ const s = String(value).trim()
7
+ if (!s) return ''
8
+ return s.replace(/^0x/i, '').replace(/[^0-9a-fA-F]/g, '')
9
+ }
10
+
11
+ const parseBoolean = (value, fallback) => {
12
+ if (value === undefined || value === null) return fallback
13
+ if (typeof value === 'boolean') return value
14
+ if (typeof value === 'number') return value !== 0
15
+ const s = String(value).trim().toLowerCase()
16
+ if (s === 'true' || s === '1' || s === 'yes' || s === 'on') return true
17
+ if (s === 'false' || s === '0' || s === 'no' || s === 'off') return false
18
+ return fallback
19
+ }
20
+
21
+ const normalizeMultiline = (value) => {
22
+ if (value === undefined || value === null) return ''
23
+ if (Array.isArray(value)) return value.map(v => String(v)).join('\n')
24
+ return String(value)
25
+ }
26
+
27
+ let _knxultimateCache = null
28
+ const getKnxultimate = () => {
29
+ if (_knxultimateCache) return _knxultimateCache
30
+ _knxultimateCache = require('knxultimate')
31
+ return _knxultimateCache
32
+ }
33
+
4
34
  const splitPatterns = (raw) => {
5
35
  const s = normalizeText(raw)
6
36
  if (!s) return []
@@ -162,6 +192,82 @@ const getPayloadObject = (msg) => {
162
192
  return msg.payload
163
193
  }
164
194
 
195
+ const getKnxObject = (msg) => {
196
+ const payload = getPayloadObject(msg)
197
+ if (payload && payload.knx && typeof payload.knx === 'object' && !Buffer.isBuffer(payload.knx)) return payload.knx
198
+ if (msg && msg.knx && typeof msg.knx === 'object' && !Buffer.isBuffer(msg.knx)) return msg.knx
199
+ return null
200
+ }
201
+
202
+ const getCemiHexFromMsg = (msg) => {
203
+ const knx = getKnxObject(msg)
204
+ if (!knx) return ''
205
+ const cemi = knx.cemi
206
+ if (!cemi) return ''
207
+ if (typeof cemi === 'string') return cemi
208
+ if (typeof cemi === 'object' && !Buffer.isBuffer(cemi) && cemi.hex) return cemi.hex
209
+ return ''
210
+ }
211
+
212
+ const setCemiHexOnMsg = (msg, cemiHex) => {
213
+ const knx = getKnxObject(msg)
214
+ if (!knx) return false
215
+ if (knx.cemi && typeof knx.cemi === 'object' && !Buffer.isBuffer(knx.cemi) && 'hex' in knx.cemi) {
216
+ knx.cemi.hex = cemiHex
217
+ return true
218
+ }
219
+ knx.cemi = { hex: cemiHex }
220
+ return true
221
+ }
222
+
223
+ const isIndividualAddressString = (value) => /^\d{1,2}\.\d{1,2}\.\d{1,3}$/.test(String(value || '').trim())
224
+ const isGroupAddressString = (value) => /^\d{1,2}\/\d{1,2}\/\d{1,3}$/.test(String(value || '').trim())
225
+
226
+ const syncCemiWithKnxFields = (msg) => {
227
+ const knx = getKnxObject(msg)
228
+ if (!knx) return false
229
+ const cemiHex = getCemiHexFromMsg(msg)
230
+ const clean = normalizeHex(cemiHex)
231
+ if (!clean || clean.length % 2 !== 0) return false
232
+
233
+ let cemi
234
+ try {
235
+ const { KNXTunnelingRequest } = getKnxultimate()
236
+ cemi = KNXTunnelingRequest.parseCEMIMessage(Buffer.from(clean, 'hex'), 0)
237
+ } catch (e) {
238
+ return false
239
+ }
240
+ if (!cemi || !cemi.control) return false
241
+
242
+ let updated = false
243
+ try {
244
+ if (isIndividualAddressString(knx.source)) {
245
+ const { KNXAddress } = getKnxultimate()
246
+ cemi.srcAddress = KNXAddress.createFromString(String(knx.source).trim(), KNXAddress.TYPE_INDIVIDUAL)
247
+ updated = true
248
+ }
249
+ } catch (e) { /* ignore */ }
250
+
251
+ try {
252
+ if (isGroupAddressString(knx.destination)) {
253
+ const { KNXAddress } = getKnxultimate()
254
+ cemi.dstAddress = KNXAddress.createFromString(String(knx.destination).trim(), KNXAddress.TYPE_GROUP)
255
+ try { cemi.control.addressType = 1 } catch (e2) { /* ignore */ }
256
+ updated = true
257
+ }
258
+ } catch (e) { /* ignore */ }
259
+
260
+ if (!updated) return false
261
+ try {
262
+ const outHex = cemi.toBuffer().toString('hex')
263
+ setCemiHexOnMsg(msg, outHex)
264
+ try { knx.cemi.hopCount = knx.cemi.hopCount } catch (e) { /* ignore */ }
265
+ return true
266
+ } catch (e) {
267
+ return false
268
+ }
269
+ }
270
+
165
271
  const attachFilterMeta = (msg, meta) => {
166
272
  try {
167
273
  const payload = getPayloadObject(msg)
@@ -185,24 +291,24 @@ module.exports = function (RED) {
185
291
  const node = this
186
292
 
187
293
  node.name = config.name || 'KNX Router Filter'
188
- node.allowWrite = config.allowWrite !== undefined ? (config.allowWrite === true || config.allowWrite === 'true') : true
189
- node.allowResponse = config.allowResponse !== undefined ? (config.allowResponse === true || config.allowResponse === 'true') : true
190
- node.allowRead = config.allowRead !== undefined ? (config.allowRead === true || config.allowRead === 'true') : true
294
+ node.allowWrite = parseBoolean(config.allowWrite, true)
295
+ node.allowResponse = parseBoolean(config.allowResponse, true)
296
+ node.allowRead = parseBoolean(config.allowRead, true)
191
297
 
192
298
  node.gaMode = config.gaMode || 'off' // off|allow|block
193
299
  node.gaPatterns = config.gaPatterns || ''
194
300
  node.srcMode = config.srcMode || 'off' // off|allow|block
195
301
  node.srcPatterns = config.srcPatterns || ''
196
302
 
197
- node.rewriteGA = config.rewriteGA !== undefined ? (config.rewriteGA === true || config.rewriteGA === 'true') : false
303
+ node.rewriteGA = parseBoolean(config.rewriteGA, false)
198
304
  node.gaRewriteRules = config.gaRewriteRules || ''
199
- node.rewriteSource = config.rewriteSource !== undefined ? (config.rewriteSource === true || config.rewriteSource === 'true') : false
305
+ node.rewriteSource = parseBoolean(config.rewriteSource, false)
200
306
  node.srcRewriteRules = config.srcRewriteRules || ''
201
307
 
202
- const gaRegexes = compileAddressPatterns({ raw: node.gaPatterns, kind: 'ga' })
203
- const srcRegexes = compileAddressPatterns({ raw: node.srcPatterns, kind: 'src' })
204
- const gaRewrite = compileRewriteRules({ raw: node.gaRewriteRules, kind: 'ga' })
205
- const srcRewrite = compileRewriteRules({ raw: node.srcRewriteRules, kind: 'src' })
308
+ let gaRegexes = compileAddressPatterns({ raw: node.gaPatterns, kind: 'ga' })
309
+ let srcRegexes = compileAddressPatterns({ raw: node.srcPatterns, kind: 'src' })
310
+ let gaRewrite = compileRewriteRules({ raw: node.gaRewriteRules, kind: 'ga' })
311
+ let srcRewrite = compileRewriteRules({ raw: node.srcRewriteRules, kind: 'src' })
206
312
 
207
313
  let passed = 0
208
314
  let dropped = 0
@@ -251,6 +357,91 @@ module.exports = function (RED) {
251
357
  applyStatus(msg, { fill: 'grey', shape: 'dot', text: `pass ${passed} / drop ${dropped}` })
252
358
  }
253
359
 
360
+ let configStatusTimer = null
361
+ const setConfigStatus = (msg, text) => {
362
+ try {
363
+ if (configStatusTimer) clearTimeout(configStatusTimer)
364
+ applyStatus(msg, { fill: 'blue', shape: 'ring', text: text || 'Config changed' })
365
+ configStatusTimer = setTimeout(() => setCountersStatus(null), 2000)
366
+ } catch (e) { /* ignore */ }
367
+ }
368
+
369
+ const rebuildCompiledRules = () => {
370
+ gaRegexes = compileAddressPatterns({ raw: node.gaPatterns, kind: 'ga' })
371
+ srcRegexes = compileAddressPatterns({ raw: node.srcPatterns, kind: 'src' })
372
+ gaRewrite = compileRewriteRules({ raw: node.gaRewriteRules, kind: 'ga' })
373
+ srcRewrite = compileRewriteRules({ raw: node.srcRewriteRules, kind: 'src' })
374
+ }
375
+
376
+ const applySetConfig = (msg) => {
377
+ const sc = msg && msg.setConfig && typeof msg.setConfig === 'object' && !Buffer.isBuffer(msg.setConfig) ? msg.setConfig : null
378
+ if (!sc) return false
379
+
380
+ const changedKeys = []
381
+ const markChanged = (key) => { if (!changedKeys.includes(key)) changedKeys.push(key) }
382
+
383
+ if (Object.prototype.hasOwnProperty.call(sc, 'name')) {
384
+ node.name = normalizeText(sc.name) || node.name
385
+ markChanged('name')
386
+ }
387
+ if (Object.prototype.hasOwnProperty.call(sc, 'allowWrite')) {
388
+ node.allowWrite = parseBoolean(sc.allowWrite, node.allowWrite)
389
+ markChanged('allowWrite')
390
+ }
391
+ if (Object.prototype.hasOwnProperty.call(sc, 'allowResponse')) {
392
+ node.allowResponse = parseBoolean(sc.allowResponse, node.allowResponse)
393
+ markChanged('allowResponse')
394
+ }
395
+ if (Object.prototype.hasOwnProperty.call(sc, 'allowRead')) {
396
+ node.allowRead = parseBoolean(sc.allowRead, node.allowRead)
397
+ markChanged('allowRead')
398
+ }
399
+ if (Object.prototype.hasOwnProperty.call(sc, 'gaMode')) {
400
+ const m = normalizeText(sc.gaMode).toLowerCase()
401
+ if (m === 'off' || m === 'allow' || m === 'block') {
402
+ node.gaMode = m
403
+ markChanged('gaMode')
404
+ }
405
+ }
406
+ if (Object.prototype.hasOwnProperty.call(sc, 'gaPatterns')) {
407
+ node.gaPatterns = normalizeMultiline(sc.gaPatterns)
408
+ markChanged('gaPatterns')
409
+ }
410
+ if (Object.prototype.hasOwnProperty.call(sc, 'srcMode')) {
411
+ const m = normalizeText(sc.srcMode).toLowerCase()
412
+ if (m === 'off' || m === 'allow' || m === 'block') {
413
+ node.srcMode = m
414
+ markChanged('srcMode')
415
+ }
416
+ }
417
+ if (Object.prototype.hasOwnProperty.call(sc, 'srcPatterns')) {
418
+ node.srcPatterns = normalizeMultiline(sc.srcPatterns)
419
+ markChanged('srcPatterns')
420
+ }
421
+ if (Object.prototype.hasOwnProperty.call(sc, 'rewriteGA')) {
422
+ node.rewriteGA = parseBoolean(sc.rewriteGA, node.rewriteGA)
423
+ markChanged('rewriteGA')
424
+ }
425
+ if (Object.prototype.hasOwnProperty.call(sc, 'gaRewriteRules')) {
426
+ node.gaRewriteRules = normalizeMultiline(sc.gaRewriteRules)
427
+ markChanged('gaRewriteRules')
428
+ }
429
+ if (Object.prototype.hasOwnProperty.call(sc, 'rewriteSource')) {
430
+ node.rewriteSource = parseBoolean(sc.rewriteSource, node.rewriteSource)
431
+ markChanged('rewriteSource')
432
+ }
433
+ if (Object.prototype.hasOwnProperty.call(sc, 'srcRewriteRules')) {
434
+ node.srcRewriteRules = normalizeMultiline(sc.srcRewriteRules)
435
+ markChanged('srcRewriteRules')
436
+ }
437
+
438
+ if (changedKeys.length > 0) {
439
+ rebuildCompiledRules()
440
+ setConfigStatus(msg, `Config changed: ${changedKeys.join(', ')}`)
441
+ }
442
+ return true
443
+ }
444
+
254
445
  const extractFields = (msg) => {
255
446
  const payload = msg && msg.payload !== undefined ? msg.payload : null
256
447
  const knx = (payload && payload.knx) ? payload.knx : (msg && msg.knx ? msg.knx : null)
@@ -279,6 +470,11 @@ module.exports = function (RED) {
279
470
 
280
471
  node.on('input', function (msg) {
281
472
  try {
473
+ if (msg && Object.prototype.hasOwnProperty.call(msg, 'setConfig')) {
474
+ applySetConfig(msg)
475
+ return
476
+ }
477
+
282
478
  const fields = extractFields(msg)
283
479
 
284
480
  // If message doesn't look like a KNX raw telegram, pass it through unchanged.
@@ -341,8 +537,11 @@ module.exports = function (RED) {
341
537
  }
342
538
 
343
539
  if (anyRewrite) {
540
+ let cemiSynced = false
541
+ try { cemiSynced = syncCemiWithKnxFields(msg) } catch (e) { cemiSynced = false }
344
542
  meta = Object.assign({}, meta, {
345
543
  rewritten: true,
544
+ cemiSynced,
346
545
  original: Object.assign({}, meta.original || {}, {
347
546
  destination: beforeGA,
348
547
  source: beforeSrc
@@ -360,6 +559,13 @@ module.exports = function (RED) {
360
559
  }
361
560
  })
362
561
 
562
+ node.on('close', function () {
563
+ try {
564
+ if (configStatusTimer) clearTimeout(configStatusTimer)
565
+ configStatusTimer = null
566
+ } catch (e) { /* ignore */ }
567
+ })
568
+
363
569
  setCountersStatus(null)
364
570
  }
365
571
 
@@ -4,6 +4,12 @@ Dieser Node dient dazu, **mehrere KNX-Ultimate-Gateways** (mehrere `knxUltimate-
4
4
  Er gibt für jedes Telegramm vom KNX-Bus des ausgewählten Gateways ein Objekt mit **RAW-Telegramm-Informationen** (APDU + cEMI-Hex + Adressen) aus.
5
5
  Außerdem kann er die gleichen RAW-Objekte am Eingang annehmen und an das ausgewählte Gateway weiterleiten.
6
6
 
7
+ ## Server KNX/IP Modus
8
+ Setze **Modus** auf **Server KNX/IP**, um einen eingebetteten KNXnet/IP-Tunneling-Server (UDP) zu starten. Eingehende Client-Telegramme werden im gleichen RAW-Format ausgegeben.
9
+ Der Node akzeptiert außerdem RAW-Telegramm-Objekte am Eingang und injiziert sie an die verbundenen Tunneling-Clients.
10
+
11
+ **Wichtig (Advertise host):** KNXnet/IP-Clients senden die Daten an die IP, die der Server in der CONNECT_RESPONSE ankündigt. Wenn der Client als *connected* angezeigt wird, der Server aber keine Telegramme empfängt, setze **Advertise host** auf die LAN-IP des Servers, die vom Client erreichbar ist (insbesondere bei Docker/VM oder Multi-Homing).
12
+
7
13
  ## Output-Nachrichtenformat
8
14
  `msg.payload` enthält:
9
15
  - `knx.event`: `GroupValue_Write` / `GroupValue_Response` / `GroupValue_Read`
@@ -15,6 +21,16 @@ Außerdem kann er die gleichen RAW-Objekte am Eingang annehmen und an das ausgew
15
21
  - `knx.echoed`: `true`, wenn vom Gateway „echoed“
16
22
  - `knxMultiRouting.gateway`: Gateway-Metadaten (`id`, `name`, `physAddr`)
17
23
 
24
+ ## Routing counter (hop count)
25
+ MultiRouting kann den KNX Routing Counter (Hop Count) aus `knx.cemi.hex` nutzen, um Telegramm-Loops zu verhindern.
26
+ - **Respect routing counter (drop if 0)**: Telegramme mit Routing Counter `0` werden nicht weitergeleitet.
27
+ - **Decrement routing counter when routing**: Der Node dekrementiert den Routing Counter beim Weiterleiten. Erreicht er `0`, wird das Telegramm verworfen.
28
+
29
+ Der aktuelle Wert steht in `knx.routingCounter` (und in `knx.cemi.hopCount`, wenn `knx.cemi` ein Objekt ist).
30
+
31
+ ## Telegramme umschreiben
32
+ Wenn du `knx.source` / `knx.destination` im Flow umschreibst, musst du auch `knx.cemi.hex` konsistent halten. Empfehlung: setze **KNX Router Filter** zwischen MultiRouting-Nodes; er hält `knx.cemi.hex` beim Umschreiben automatisch konsistent.
33
+
18
34
  ## Hinweise
19
35
  - Beim Weiterleiten an ein anderes Gateway **ändert sich die Source-PA** (es wird die phys. Adresse des sendenden Gateways). Nutze `knx.source` und/oder `knxMultiRouting.gateway`, um Loops zu filtern.
20
36
  - Die Option **„Drop messages already tagged for this gateway“** hilft, einfache Loops zu verhindern, wenn mehrere Router miteinander verbunden sind.
@@ -2,10 +2,24 @@
2
2
  "knxUltimateMultiRouting": {
3
3
  "title": "KNX Multi Routing",
4
4
  "properties": {
5
+ "mode": "Mode",
6
+ "modeGateway": "Gateway / Routing",
7
+ "modeServer": "Server KNX/IP",
5
8
  "server": "Gateway",
6
9
  "name": "Name",
7
10
  "outputtopic": "Output topic",
8
- "dropIfSameGateway": "Drop messages already tagged for this gateway"
11
+ "dropIfSameGateway": "Drop messages already tagged for this gateway",
12
+ "respectRoutingCounter": "Respect routing counter (drop if 0)",
13
+ "decrementRoutingCounter": "Decrement routing counter when routing",
14
+ "tunnelTitle": "KNX/IP Server",
15
+ "tunnelListenHost": "Listen host",
16
+ "tunnelListenPort": "Listen port",
17
+ "tunnelAdvertiseHost": "Advertise host",
18
+ "tunnelAdvertiseHostPlaceholder": "(optional) Host/IP to advertise to clients",
19
+ "tunnelAssignedIndividualAddress": "Assigned individual address",
20
+ "tunnelGatewayId": "Gateway id (tag)",
21
+ "tunnelGatewayIdPlaceholder": "(auto) Used as knxMultiRouting.gateway.id",
22
+ "tunnelMaxSessions": "Max sessions"
9
23
  },
10
24
  "outputs": {
11
25
  "raw": "RAW telegrams"
@@ -24,6 +24,8 @@ Optionales Umschreiben von:
24
24
  - Ziel-Gruppenadresse (`knx.destination`)
25
25
  - Source-PA (`knx.source`)
26
26
 
27
+ Beim Umschreiben aktualisiert der Node auch `knx.cemi.hex`, damit es zu den umgeschriebenen Werten von `knx.source`/`knx.destination` passt (wenn `knx.cemi.hex` vorhanden ist).
28
+
27
29
  Regeln werden von oben nach unten ausgewertet (first match wins).
28
30
 
29
31
  Beispiele:
@@ -33,5 +35,41 @@ Beispiele:
33
35
  ## Metadaten
34
36
  Der Node fügt `msg.payload.knxRouterFilter` hinzu:
35
37
  - verworfen: `{ dropped: true, reason: 'event'|'ga'|'source', ... }`
36
- - durchgelassen: `{ dropped: false, rewritten: <bool>, rewrite: { ... }, original: { ... } }`
38
+ - durchgelassen: `{ dropped: false, rewritten: <bool>, cemiSynced: <bool>, rewrite: { ... }, original: { ... } }`
39
+
40
+ ## msg.setConfig
41
+ Du kannst die Node-Konfiguration zur Laufzeit ändern, indem du ein `msg.setConfig` Objekt an den Eingang sendest.
42
+ Unterstützte Keys: `allowWrite`, `allowResponse`, `allowRead`, `gaMode`, `gaPatterns`, `srcMode`, `srcPatterns`, `rewriteGA`, `gaRewriteRules`, `rewriteSource`, `srcRewriteRules`.
43
+ Die Konfiguration bleibt bis zum nächsten `msg.setConfig` oder bis Redeploy/Neustart erhalten. Konfigurationsnachrichten werden nicht weitergeleitet.
44
+
45
+ Bedeutung der Properties:
46
+ - `allowWrite`: erlaubt `GroupValue_Write` Telegramme.
47
+ - `allowResponse`: erlaubt `GroupValue_Response` Telegramme.
48
+ - `allowRead`: erlaubt `GroupValue_Read` Telegramme.
49
+ - `gaMode`: Filtermodus für die Ziel-GA (`off` = kein Filter, `allow` = nur Matches durchlassen, `block` = Matches verwerfen).
50
+ - `gaPatterns`: Ziel-GA Patterns für `gaMode` (eine pro Zeile, unterstützt `*` und `re:<regex>`).
51
+ - `srcMode`: Filtermodus für die Source-PA (`off`/`allow`/`block`).
52
+ - `srcPatterns`: Source-Patterns für `srcMode` (eine pro Zeile, unterstützt `*` und `re:<regex>`).
53
+ - `rewriteGA`: aktiviert Umschreiben von `knx.destination` für durchgelassene Telegramme.
54
+ - `gaRewriteRules`: Umschreibregeln für Ziel-GA (`von => nach`, first match wins; unterstützt `*` und `re:<regex>`).
55
+ - `rewriteSource`: aktiviert Umschreiben von `knx.source` für durchgelassene Telegramme.
56
+ - `srcRewriteRules`: Umschreibregeln für Source-PA (`von => nach`, first match wins; unterstützt `*` und `re:<regex>`).
57
+
58
+ Beispiel:
59
+ ```js
60
+ msg.setConfig = {
61
+ allowWrite: true,
62
+ allowResponse: true,
63
+ allowRead: true,
64
+ gaMode: "allow",
65
+ gaPatterns: "1/1/*\n1/2/3",
66
+ srcMode: "off",
67
+ srcPatterns: "",
68
+ rewriteGA: true,
69
+ gaRewriteRules: "5/5/1 => 1/1/1",
70
+ rewriteSource: true,
71
+ srcRewriteRules: "15.*.* => 1.1.254"
72
+ };
73
+ return msg;
74
+ ```
37
75
  </script>
@@ -4,6 +4,12 @@ This node is used to **bridge multiple KNX Ultimate gateways** (multiple `knxUlt
4
4
  It outputs **RAW telegram information** (APDU + cEMI hex + addresses) for every telegram received from the KNX bus of the selected gateway.
5
5
  It can also accept those RAW telegram objects on its input and forward them to the selected gateway.
6
6
 
7
+ ## Server KNX/IP mode
8
+ Set **Mode** to **Server KNX/IP** to start an embedded KNXnet/IP tunneling server (UDP). Incoming client telegrams are emitted as the same RAW format used by MultiRouting.
9
+ The node also accepts RAW telegram objects on its input and injects them to the connected tunneling client(s).
10
+
11
+ **Important (Advertise host):** KNXnet/IP clients will send data to the IP advertised by the server in the CONNECT_RESPONSE. If the client shows *connected* but the server receives no telegrams, set **Advertise host** to the server LAN IP reachable by the client (especially when running Node-RED in Docker/VM or on a multi-homed host).
12
+
7
13
  ## Output message format
8
14
  `msg.payload` contains:
9
15
  - `knx.event`: `GroupValue_Write` / `GroupValue_Response` / `GroupValue_Read`
@@ -15,6 +21,16 @@ It can also accept those RAW telegram objects on its input and forward them to t
15
21
  - `knx.echoed`: `true` if echoed by the gateway
16
22
  - `knxMultiRouting.gateway`: gateway meta (`id`, `name`, `physAddr`)
17
23
 
24
+ ## Routing counter (hop count)
25
+ MultiRouting can use the KNX routing counter (hop count) found in `knx.cemi.hex` to prevent telegram loops.
26
+ - **Respect routing counter (drop if 0)**: telegrams with routing counter `0` are not forwarded.
27
+ - **Decrement routing counter when routing**: the node decrements the routing counter while forwarding. If it reaches `0`, the telegram is dropped.
28
+
29
+ The current value is exposed as `knx.routingCounter` (and as `knx.cemi.hopCount` when `knx.cemi` is an object).
30
+
31
+ ## Rewriting telegrams
32
+ If you rewrite `knx.source` / `knx.destination` in your flow, you must also keep `knx.cemi.hex` coherent. Recommended: place **KNX Router Filter** between MultiRouting nodes: it will keep `knx.cemi.hex` in sync when rewriting.
33
+
18
34
  ## Notes
19
35
  - When forwarded to another gateway, the **source physical address will change** (it becomes the sender gateway address). Use `knx.source` and/or `knxMultiRouting.gateway` to filter loops.
20
36
  - The option **Drop messages already tagged for this gateway** helps prevent simple loops when you connect multiple routers together.
@@ -2,10 +2,24 @@
2
2
  "knxUltimateMultiRouting": {
3
3
  "title": "KNX Multi Routing",
4
4
  "properties": {
5
+ "mode": "Mode",
6
+ "modeGateway": "Gateway / Routing",
7
+ "modeServer": "Server KNX/IP",
5
8
  "server": "Gateway",
6
9
  "name": "Name",
7
10
  "outputtopic": "Output topic",
8
- "dropIfSameGateway": "Drop messages already tagged for this gateway"
11
+ "dropIfSameGateway": "Drop messages already tagged for this gateway",
12
+ "respectRoutingCounter": "Respect routing counter (drop if 0)",
13
+ "decrementRoutingCounter": "Decrement routing counter when routing",
14
+ "tunnelTitle": "KNX/IP Server",
15
+ "tunnelListenHost": "Listen host",
16
+ "tunnelListenPort": "Listen port",
17
+ "tunnelAdvertiseHost": "Advertise host",
18
+ "tunnelAdvertiseHostPlaceholder": "(optional) Host/IP to advertise to clients",
19
+ "tunnelAssignedIndividualAddress": "Assigned individual address",
20
+ "tunnelGatewayId": "Gateway id (tag)",
21
+ "tunnelGatewayIdPlaceholder": "(auto) Used as knxMultiRouting.gateway.id",
22
+ "tunnelMaxSessions": "Max sessions"
9
23
  },
10
24
  "outputs": {
11
25
  "raw": "RAW telegrams"
@@ -24,6 +24,8 @@ You can optionally rewrite:
24
24
  - destination Group Address (`knx.destination`)
25
25
  - source physical address (`knx.source`)
26
26
 
27
+ When rewriting, the node also updates `knx.cemi.hex` to match the rewritten `knx.source`/`knx.destination` (when `knx.cemi.hex` is present).
28
+
27
29
  Rewrite rules are evaluated top-to-bottom, first match wins.
28
30
 
29
31
  Syntax examples:
@@ -33,5 +35,41 @@ Syntax examples:
33
35
  ## Metadata
34
36
  The node adds `msg.payload.knxRouterFilter`:
35
37
  - dropped messages: `{ dropped: true, reason: 'event'|'ga'|'source', ... }`
36
- - passed messages: `{ dropped: false, rewritten: <bool>, rewrite: { ... }, original: { ... } }`
38
+ - passed messages: `{ dropped: false, rewritten: <bool>, cemiSynced: <bool>, rewrite: { ... }, original: { ... } }`
39
+
40
+ ## msg.setConfig
41
+ You can change the node configuration at runtime by sending a `msg.setConfig` object to the input.
42
+ Supported keys: `allowWrite`, `allowResponse`, `allowRead`, `gaMode`, `gaPatterns`, `srcMode`, `srcPatterns`, `rewriteGA`, `gaRewriteRules`, `rewriteSource`, `srcRewriteRules`.
43
+ The new configuration is retained until next `msg.setConfig` or until redeploy/restart. Configuration messages are not forwarded.
44
+
45
+ Meaning of the properties:
46
+ - `allowWrite`: allow `GroupValue_Write` telegrams.
47
+ - `allowResponse`: allow `GroupValue_Response` telegrams.
48
+ - `allowRead`: allow `GroupValue_Read` telegrams.
49
+ - `gaMode`: destination GA filter mode (`off` = no filter, `allow` = allow only matching, `block` = drop matching).
50
+ - `gaPatterns`: destination GA patterns used by `gaMode` (one per line, supports `*` and `re:<regex>`).
51
+ - `srcMode`: source (physical address) filter mode (`off`/`allow`/`block`).
52
+ - `srcPatterns`: source patterns used by `srcMode` (one per line, supports `*` and `re:<regex>`).
53
+ - `rewriteGA`: enable rewrite of `knx.destination` on passed telegrams.
54
+ - `gaRewriteRules`: rewrite rules for destination GA (`from => to`, first match wins; supports `*` and `re:<regex>`).
55
+ - `rewriteSource`: enable rewrite of `knx.source` on passed telegrams.
56
+ - `srcRewriteRules`: rewrite rules for source PA (`from => to`, first match wins; supports `*` and `re:<regex>`).
57
+
58
+ Example:
59
+ ```js
60
+ msg.setConfig = {
61
+ allowWrite: true,
62
+ allowResponse: true,
63
+ allowRead: true,
64
+ gaMode: "allow",
65
+ gaPatterns: "1/1/*\n1/2/3",
66
+ srcMode: "off",
67
+ srcPatterns: "",
68
+ rewriteGA: true,
69
+ gaRewriteRules: "5/5/1 => 1/1/1",
70
+ rewriteSource: true,
71
+ srcRewriteRules: "15.*.* => 1.1.254"
72
+ };
73
+ return msg;
74
+ ```
37
75
  </script>
@@ -4,6 +4,12 @@ Este nodo se utiliza para **interconectar varios gateways KNX Ultimate** (varios
4
4
  En la salida emite un objeto con información **RAW del telegrama** (APDU + cEMI en hex + direcciones) para cada telegrama recibido del bus KNX del gateway seleccionado.
5
5
  En la entrada puede recibir esos mismos objetos RAW y reenviarlos al bus KNX del gateway seleccionado.
6
6
 
7
+ ## Modo servidor KNX/IP
8
+ Establece **Modo** en **Server KNX/IP** para iniciar un servidor KNXnet/IP tunneling (UDP) integrado. Los telegramas recibidos de los clientes se emiten en el mismo formato RAW.
9
+ El nodo también acepta objetos RAW en la entrada y los inyecta hacia los clientes tunneling conectados.
10
+
11
+ **Importante (Advertise host):** los clientes KNXnet/IP enviarán los datos a la IP anunciada por el servidor en la CONNECT_RESPONSE. Si el cliente muestra *conectado* pero el servidor no recibe telegramas, configura **Advertise host** con la IP LAN del servidor alcanzable por el cliente (especialmente si Node-RED se ejecuta en Docker/VM o en un host con múltiples interfaces).
12
+
7
13
  ## Formato del mensaje de salida
8
14
  `msg.payload` contiene:
9
15
  - `knx.event`: `GroupValue_Write` / `GroupValue_Response` / `GroupValue_Read`
@@ -15,6 +21,16 @@ En la entrada puede recibir esos mismos objetos RAW y reenviarlos al bus KNX del
15
21
  - `knx.echoed`: `true` si el gateway lo ha “echoed”
16
22
  - `knxMultiRouting.gateway`: metadatos del gateway (`id`, `name`, `physAddr`)
17
23
 
24
+ ## Routing counter (hop count)
25
+ MultiRouting puede usar el routing counter (hop count) presente en `knx.cemi.hex` para evitar bucles de telegramas.
26
+ - **Respect routing counter (drop if 0)**: los telegramas con routing counter `0` no se reenvían.
27
+ - **Decrement routing counter when routing**: el nodo decrementa el routing counter durante el reenvío. Si llega a `0`, el telegrama se descarta.
28
+
29
+ El valor actual se expone como `knx.routingCounter` (y como `knx.cemi.hopCount` cuando `knx.cemi` es un objeto).
30
+
31
+ ## Reescritura de telegramas
32
+ Si en tu flow reescribes `knx.source` / `knx.destination`, también debes mantener `knx.cemi.hex` coherente. Recomendado: coloca **KNX Router Filter** entre nodos MultiRouting: sincronizará automáticamente `knx.cemi.hex` durante la reescritura.
33
+
18
34
  ## Notas
19
35
  - Al reenviar a otro gateway, la **dirección física de origen cambia** (pasa a ser la del gateway que envía). Usa `knx.source` y/o `knxMultiRouting.gateway` para filtrar bucles.
20
36
  - La opción **“Drop messages already tagged for this gateway”** ayuda a prevenir bucles simples cuando conectas varios routers entre sí.
@@ -2,10 +2,24 @@
2
2
  "knxUltimateMultiRouting": {
3
3
  "title": "KNX Multi Routing",
4
4
  "properties": {
5
+ "mode": "Mode",
6
+ "modeGateway": "Gateway / Routing",
7
+ "modeServer": "Server KNX/IP",
5
8
  "server": "Gateway",
6
9
  "name": "Name",
7
10
  "outputtopic": "Output topic",
8
- "dropIfSameGateway": "Drop messages already tagged for this gateway"
11
+ "dropIfSameGateway": "Drop messages already tagged for this gateway",
12
+ "respectRoutingCounter": "Respect routing counter (drop if 0)",
13
+ "decrementRoutingCounter": "Decrement routing counter when routing",
14
+ "tunnelTitle": "KNX/IP Server",
15
+ "tunnelListenHost": "Listen host",
16
+ "tunnelListenPort": "Listen port",
17
+ "tunnelAdvertiseHost": "Advertise host",
18
+ "tunnelAdvertiseHostPlaceholder": "(optional) Host/IP to advertise to clients",
19
+ "tunnelAssignedIndividualAddress": "Assigned individual address",
20
+ "tunnelGatewayId": "Gateway id (tag)",
21
+ "tunnelGatewayIdPlaceholder": "(auto) Used as knxMultiRouting.gateway.id",
22
+ "tunnelMaxSessions": "Max sessions"
9
23
  },
10
24
  "outputs": {
11
25
  "raw": "RAW telegrams"
@@ -24,6 +24,8 @@ Puedes reescribir opcionalmente:
24
24
  - destino (Group Address) `knx.destination`
25
25
  - origen (dirección física) `knx.source`
26
26
 
27
+ Durante la reescritura, el nodo también actualiza `knx.cemi.hex` para reflejar los valores reescritos de `knx.source`/`knx.destination` (cuando `knx.cemi.hex` está presente).
28
+
27
29
  Las reglas se evalúan de arriba hacia abajo (la primera coincidencia gana).
28
30
 
29
31
  Ejemplos:
@@ -33,5 +35,41 @@ Ejemplos:
33
35
  ## Metadatos
34
36
  El nodo añade `msg.payload.knxRouterFilter`:
35
37
  - descartados: `{ dropped: true, reason: 'event'|'ga'|'source', ... }`
36
- - pasados: `{ dropped: false, rewritten: <bool>, rewrite: { ... }, original: { ... } }`
38
+ - pasados: `{ dropped: false, rewritten: <bool>, cemiSynced: <bool>, rewrite: { ... }, original: { ... } }`
39
+
40
+ ## msg.setConfig
41
+ Puedes cambiar la configuración del nodo en tiempo de ejecución enviando un objeto `msg.setConfig` a la entrada.
42
+ Claves soportadas: `allowWrite`, `allowResponse`, `allowRead`, `gaMode`, `gaPatterns`, `srcMode`, `srcPatterns`, `rewriteGA`, `gaRewriteRules`, `rewriteSource`, `srcRewriteRules`.
43
+ La configuración se mantiene hasta el siguiente `msg.setConfig` o hasta redeploy/reinicio. Los mensajes de configuración no se reenvían.
44
+
45
+ Significado de las propiedades:
46
+ - `allowWrite`: permite telegramas `GroupValue_Write`.
47
+ - `allowResponse`: permite telegramas `GroupValue_Response`.
48
+ - `allowRead`: permite telegramas `GroupValue_Read`.
49
+ - `gaMode`: modo de filtro para la GA de destino (`off` = sin filtro, `allow` = permitir solo coincidencias, `block` = descartar coincidencias).
50
+ - `gaPatterns`: patrones GA de destino usados por `gaMode` (uno por línea, soporta `*` y `re:<regex>`).
51
+ - `srcMode`: modo de filtro para el source (dirección física) (`off`/`allow`/`block`).
52
+ - `srcPatterns`: patrones source usados por `srcMode` (uno por línea, soporta `*` y `re:<regex>`).
53
+ - `rewriteGA`: habilita la reescritura de `knx.destination` en los telegramas que pasan.
54
+ - `gaRewriteRules`: reglas de reescritura GA (`de => a`, primera coincidencia gana; soporta `*` y `re:<regex>`).
55
+ - `rewriteSource`: habilita la reescritura de `knx.source` en los telegramas que pasan.
56
+ - `srcRewriteRules`: reglas de reescritura source (`de => a`, primera coincidencia gana; soporta `*` y `re:<regex>`).
57
+
58
+ Ejemplo:
59
+ ```js
60
+ msg.setConfig = {
61
+ allowWrite: true,
62
+ allowResponse: true,
63
+ allowRead: true,
64
+ gaMode: "allow",
65
+ gaPatterns: "1/1/*\n1/2/3",
66
+ srcMode: "off",
67
+ srcPatterns: "",
68
+ rewriteGA: true,
69
+ gaRewriteRules: "5/5/1 => 1/1/1",
70
+ rewriteSource: true,
71
+ srcRewriteRules: "15.*.* => 1.1.254"
72
+ };
73
+ return msg;
74
+ ```
37
75
  </script>
@@ -4,6 +4,12 @@ Ce nœud sert à **interconnecter plusieurs gateways KNX Ultimate** (plusieurs `
4
4
  Il émet en sortie un objet contenant des informations **RAW** (APDU + cEMI hex + adresses) pour chaque télégramme reçu sur le bus KNX du gateway sélectionné.
5
5
  Il peut aussi accepter ces objets RAW en entrée et les transmettre vers le bus KNX du gateway sélectionné.
6
6
 
7
+ ## Mode serveur KNX/IP
8
+ Réglez **Mode** sur **Server KNX/IP** pour démarrer un serveur KNXnet/IP tunneling (UDP) intégré. Les télégrammes reçus des clients sont émis au même format RAW.
9
+ Le nœud accepte aussi en entrée des objets RAW et les injecte vers les clients tunneling connectés.
10
+
11
+ **Important (Advertise host) :** les clients KNXnet/IP enverront les données à l’IP annoncée par le serveur dans la CONNECT_RESPONSE. Si le client indique *connecté* mais que le serveur ne reçoit aucun télégramme, définissez **Advertise host** sur l’IP LAN du serveur accessible par le client (surtout si Node-RED tourne dans Docker/VM ou sur une machine multi-homée).
12
+
7
13
  ## Format du message en sortie
8
14
  `msg.payload` contient :
9
15
  - `knx.event` : `GroupValue_Write` / `GroupValue_Response` / `GroupValue_Read`
@@ -15,6 +21,16 @@ Il peut aussi accepter ces objets RAW en entrée et les transmettre vers le bus
15
21
  - `knx.echoed` : `true` si le gateway l’a « echoed »
16
22
  - `knxMultiRouting.gateway` : métadonnées du gateway (`id`, `name`, `physAddr`)
17
23
 
24
+ ## Routing counter (hop count)
25
+ MultiRouting peut utiliser le routing counter (hop count) présent dans `knx.cemi.hex` pour éviter les boucles de télégrammes.
26
+ - **Respect routing counter (drop if 0)** : les télégrammes avec routing counter `0` ne sont pas transmis.
27
+ - **Decrement routing counter when routing** : le nœud décrémente le routing counter lors du transfert. S’il atteint `0`, le télégramme est supprimé.
28
+
29
+ La valeur courante est exposée via `knx.routingCounter` (et via `knx.cemi.hopCount` lorsque `knx.cemi` est un objet).
30
+
31
+ ## Réécriture des télégrammes
32
+ Si vous réécrivez `knx.source` / `knx.destination` dans votre flow, vous devez aussi maintenir `knx.cemi.hex` cohérent. Recommandé : placez **KNX Router Filter** entre les nœuds MultiRouting : il maintient automatiquement `knx.cemi.hex` cohérent lors d’une réécriture.
33
+
18
34
  ## Notes
19
35
  - Lors d’un transfert vers un autre gateway, l’**adresse physique source change** (elle devient celle du gateway émetteur). Utilisez `knx.source` et/ou `knxMultiRouting.gateway` pour filtrer les boucles.
20
36
  - L’option **« Drop messages already tagged for this gateway »** aide à prévenir des boucles simples lorsque plusieurs routeurs sont interconnectés.