node-red-contrib-knx-ultimate 6.0.9 → 6.0.10
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,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
-
**Version 6.0.
|
|
9
|
+
**Version 6.0.10** - July 2026<br/>
|
|
10
10
|
|
|
11
|
+
- **Matter nodes — crash containment**: asynchronous bridge state updates, controller client callbacks, engine errors, startup/watchdog timers and the controller command queue are now guarded so rejected promises and EventEmitter errors are reported instead of becoming uncaught exceptions or unhandled rejections capable of stopping Node-RED.<br/>
|
|
11
12
|
- **Matter Controller — post-commission naming**: assigning a name immediately after pairing now waits for matter.js to finish exposing the root `BasicInformation` cluster. Root endpoint 0 is resolved explicitly, preventing the misleading `BasicInformation cluster not found` warning seen when re-adding devices such as Shelly Plug.<br/>
|
|
12
13
|
- **Control Matter from KNX — profile deploy crash**: Door Lock and multi-purpose profiles now expose the KNX status callback before registering with `knxUltimate-config`, preventing `_Node.setNodeStatus is not a function` during deploy.<br/>
|
|
13
14
|
- **Control Matter from KNX — flow PIN persistence**: the **Node Input/Output PINs** selection is no longer overwritten from the KNX gateway presence when reopening the editor. The saved choice remains authoritative and `inputs`/`outputs` are updated coherently on save.<br/>
|
|
@@ -170,6 +170,24 @@ module.exports = function (RED) {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
const reportMatterError = (context, error) => {
|
|
174
|
+
const message = error?.message || String(error)
|
|
175
|
+
node.setNodeStatus({ fill: 'red', shape: 'dot', text: `${context} ${message}`, payload: '' })
|
|
176
|
+
try { RED.log.error(`knxUltimateMatterBridge: ${context}: ${message}`) } catch (logError) { /* empty */ }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const safeSetDeviceState = async (fn, value, context) => {
|
|
180
|
+
try {
|
|
181
|
+
if (!node.serverMatterBridge || typeof node.serverMatterBridge.setDeviceState !== 'function') {
|
|
182
|
+
throw new Error('Matter bridge unavailable')
|
|
183
|
+
}
|
|
184
|
+
return await node.serverMatterBridge.setDeviceState(node.matterDeviceId, fn, value)
|
|
185
|
+
} catch (error) {
|
|
186
|
+
reportMatterError(context, error)
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
173
191
|
const clearCoverStatusTimer = () => {
|
|
174
192
|
if (coverStatusTimer !== null) {
|
|
175
193
|
clearTimeout(coverStatusTimer)
|
|
@@ -227,7 +245,7 @@ module.exports = function (RED) {
|
|
|
227
245
|
clearCoverArrivalTimer()
|
|
228
246
|
|
|
229
247
|
if (node.coverUpdateMode === 'optimistic') {
|
|
230
|
-
|
|
248
|
+
safeSetDeviceState('position', requestedPosition, 'Matter position update error')
|
|
231
249
|
return
|
|
232
250
|
}
|
|
233
251
|
|
|
@@ -241,7 +259,7 @@ module.exports = function (RED) {
|
|
|
241
259
|
}
|
|
242
260
|
coverArrivalTimer = setTimeout(() => {
|
|
243
261
|
coverArrivalTimer = null
|
|
244
|
-
|
|
262
|
+
safeSetDeviceState('position', requestedPosition, 'Matter position fallback error')
|
|
245
263
|
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'No exact KNX confirmation in time, assuming arrived at', payload: requestedPosition })
|
|
246
264
|
}, COVER_ARRIVAL_FALLBACK_MS)
|
|
247
265
|
}
|
|
@@ -341,7 +359,7 @@ module.exports = function (RED) {
|
|
|
341
359
|
try {
|
|
342
360
|
const value = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(route.dpt))
|
|
343
361
|
if (value === undefined || value === null) return
|
|
344
|
-
|
|
362
|
+
safeSetDeviceState(route.fn, value, 'KNX->Matter error')
|
|
345
363
|
if (node.deviceType === 'windowcovering' && route.fn === 'position') { clearCoverStatusTimer(); clearCoverArrivalTimer() }
|
|
346
364
|
node.setNodeStatus({ fill: 'blue', shape: 'dot', text: `KNX->Matter ${route.fn}`, payload: value })
|
|
347
365
|
} catch (error) {
|
|
@@ -391,7 +409,7 @@ module.exports = function (RED) {
|
|
|
391
409
|
|
|
392
410
|
// Flow input pin: updates the Matter state of this device without going through the KNX bus.
|
|
393
411
|
// msg.payload = { function: 'onoff'|'level'|'position'|'temperature'|..., value: ... }
|
|
394
|
-
node.on('input', (msg, send, done) => {
|
|
412
|
+
node.on('input', async (msg, send, done) => {
|
|
395
413
|
if (!node.enableNodePINS) {
|
|
396
414
|
if (done) done()
|
|
397
415
|
return
|
|
@@ -401,7 +419,8 @@ module.exports = function (RED) {
|
|
|
401
419
|
const fn = (payload.function || payload.fn || '').toString().trim()
|
|
402
420
|
if (fn === '' || payload.value === undefined) throw new Error('msg.payload must be { function, value }')
|
|
403
421
|
if (node.serverMatterBridge === undefined) throw new Error('No Matter bridge selected')
|
|
404
|
-
|
|
422
|
+
const updated = await safeSetDeviceState(fn, payload.value, 'Flow->Matter error')
|
|
423
|
+
if (!updated) throw new Error('Matter state update failed')
|
|
405
424
|
node.setNodeStatus({ fill: 'green', shape: 'dot', text: `Flow->Matter ${fn}`, payload: payload.value })
|
|
406
425
|
if (done) done()
|
|
407
426
|
} catch (error) {
|
package/nodes/matter-config.js
CHANGED
|
@@ -44,7 +44,10 @@ module.exports = (RED) => {
|
|
|
44
44
|
// Matter clients registered on this shared controller connection.
|
|
45
45
|
try {
|
|
46
46
|
if (!client || typeof fn !== 'function') return
|
|
47
|
-
fn()
|
|
47
|
+
const result = fn()
|
|
48
|
+
if (result && typeof result.catch === 'function') {
|
|
49
|
+
result.catch((error) => node.sysLogger?.warn(`Matter client ${label} async error: ${error.message}`))
|
|
50
|
+
}
|
|
48
51
|
} catch (error) {
|
|
49
52
|
node.sysLogger?.warn(`Matter client ${label} error: ${error.message}`)
|
|
50
53
|
}
|
|
@@ -132,6 +135,11 @@ module.exports = (RED) => {
|
|
|
132
135
|
})
|
|
133
136
|
})
|
|
134
137
|
|
|
138
|
+
// EventEmitter treats an unhandled "error" event as a process exception.
|
|
139
|
+
node.matterManager.on('error', (error) => {
|
|
140
|
+
node.sysLogger?.error(`Matter controller engine error: ${error?.message || error}`)
|
|
141
|
+
})
|
|
142
|
+
|
|
135
143
|
try {
|
|
136
144
|
await node.matterManager.Connect()
|
|
137
145
|
} catch (error) {
|
|
@@ -150,8 +158,12 @@ module.exports = (RED) => {
|
|
|
150
158
|
node.sysLogger?.error(`Errore matter-config: node.startWatchdogTimer: ${error.message}`)
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
try {
|
|
162
|
+
await node.startWatchdogTimer()
|
|
163
|
+
} catch (error) {
|
|
164
|
+
node.sysLogger?.error(`matter-config: watchdog reschedule error: ${error.message}`)
|
|
165
|
+
}
|
|
166
|
+
})().catch((error) => node.sysLogger?.error(`matter-config: watchdog callback error: ${error.message}`))
|
|
155
167
|
}, 60000)
|
|
156
168
|
}
|
|
157
169
|
|
|
@@ -165,11 +177,11 @@ module.exports = (RED) => {
|
|
|
165
177
|
node.sysLogger?.error(`matter-config: startup error: ${error.message}`)
|
|
166
178
|
}
|
|
167
179
|
try {
|
|
168
|
-
node.startWatchdogTimer()
|
|
180
|
+
await node.startWatchdogTimer()
|
|
169
181
|
} catch (error) {
|
|
170
182
|
node.sysLogger?.error(`matter-config: watchdog start error: ${error.message}`)
|
|
171
183
|
}
|
|
172
|
-
})()
|
|
184
|
+
})().catch((error) => node.sysLogger?.error(`matter-config: startup callback error: ${error.message}`))
|
|
173
185
|
}, 5000)
|
|
174
186
|
|
|
175
187
|
// Functions called from the nodes and the admin endpoints ----------------------------------------
|
|
@@ -53,7 +53,10 @@ module.exports = (RED) => {
|
|
|
53
53
|
const safeClientCall = (client, fn, label) => {
|
|
54
54
|
try {
|
|
55
55
|
if (!client || typeof fn !== 'function') return
|
|
56
|
-
fn()
|
|
56
|
+
const result = fn()
|
|
57
|
+
if (result && typeof result.catch === 'function') {
|
|
58
|
+
result.catch((error) => node.sysLogger?.warn(`Matter bridge client ${label} async error: ${error.message}`))
|
|
59
|
+
}
|
|
57
60
|
} catch (error) {
|
|
58
61
|
node.sysLogger?.warn(`Matter bridge client ${label} error: ${error.message}`)
|
|
59
62
|
}
|
|
@@ -80,6 +83,10 @@ module.exports = (RED) => {
|
|
|
80
83
|
|
|
81
84
|
const bindEngineEvents = (engine) => {
|
|
82
85
|
engine.removeAllListeners()
|
|
86
|
+
// An EventEmitter "error" without a listener terminates Node-RED.
|
|
87
|
+
engine.on('error', (error) => {
|
|
88
|
+
node.sysLogger?.error(`Matter bridge engine error: ${error?.message || error}`)
|
|
89
|
+
})
|
|
83
90
|
// Matter -> KNX: a controller (Alexa...) sent a command to a bridged device.
|
|
84
91
|
// Route it to the device node that owns that Matter device.
|
|
85
92
|
engine.on('command', (command) => {
|
|
@@ -109,7 +116,7 @@ module.exports = (RED) => {
|
|
|
109
116
|
} catch (error) {
|
|
110
117
|
node.sysLogger?.warn(`matterbridge-config: reconcile error: ${error.message}`)
|
|
111
118
|
}
|
|
112
|
-
})()
|
|
119
|
+
})().catch((error) => node.sysLogger?.warn(`matterbridge-config: reconcile callback error: ${error.message}`))
|
|
113
120
|
}, 1500)
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -160,7 +167,7 @@ module.exports = (RED) => {
|
|
|
160
167
|
(async () => {
|
|
161
168
|
if (closing) return
|
|
162
169
|
await ensureEngineStarted()
|
|
163
|
-
})()
|
|
170
|
+
})().catch((error) => node.sysLogger?.error(`matterbridge-config: startup callback error: ${error.message}`))
|
|
164
171
|
}, 5000)
|
|
165
172
|
|
|
166
173
|
// Functions called from the device nodes -----------------------------------------
|
|
@@ -37,7 +37,11 @@ class classMatter extends EventEmitter {
|
|
|
37
37
|
this.exitAllQueues = false
|
|
38
38
|
this._api = null // Lazy loaded matter.js exports
|
|
39
39
|
this._logThrottle = new Map()
|
|
40
|
-
if (startQueue)
|
|
40
|
+
if (startQueue) {
|
|
41
|
+
// The queue is intentionally fire-and-forget, but its Promise must always be
|
|
42
|
+
// observed so an unexpected loop failure cannot become an unhandled rejection.
|
|
43
|
+
this.handleQueue().catch((error) => this._log('error', `classMatter: queue loop: ${error.message}`))
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
_log = (level, message) => {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=20.18.1"
|
|
5
5
|
},
|
|
6
|
-
"version": "6.0.
|
|
6
|
+
"version": "6.0.10",
|
|
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, KNX AI for diagnosticsand KNX routing between interfaces. Easy to use and highly configurable.",
|
|
8
8
|
"files": [
|
|
9
9
|
"nodes/",
|