node-red-contrib-knx-ultimate 5.2.5 → 6.0.0
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 +9 -0
- package/nodes/commonFunctions.js +40 -3
- package/nodes/knxUltimate-config.js +379 -335
- package/nodes/knxUltimate.html +8 -9
- package/nodes/knxUltimate.js +5 -5
- package/nodes/knxUltimateAI.js +1 -1
- package/nodes/knxUltimateHueLight.html +3 -3
- package/nodes/knxUltimateHueLight.js +1 -1
- package/nodes/knxUltimateMatterBridge.html +321 -247
- package/nodes/knxUltimateMatterBridge.js +48 -1
- package/nodes/knxUltimateMatterControllerDevice.html +2119 -0
- package/nodes/knxUltimateMatterControllerDevice.js +1701 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/de/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/de/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/de/matter-config.json +5 -1
- package/nodes/locales/de/matterbridge-config.json +1 -0
- package/nodes/locales/en/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/en/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/en/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/en/matter-config.json +6 -1
- package/nodes/locales/en/matterbridge-config.json +1 -0
- package/nodes/locales/es/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/es/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/es/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/es/matter-config.json +5 -1
- package/nodes/locales/es/matterbridge-config.json +1 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/fr/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +89 -0
- package/nodes/locales/fr/matter-config.json +5 -1
- package/nodes/locales/fr/matterbridge-config.json +1 -0
- package/nodes/locales/it/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/it/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/it/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/it/matter-config.json +6 -1
- package/nodes/locales/it/matterbridge-config.json +1 -0
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +89 -0
- package/nodes/locales/zh-CN/matter-config.json +5 -1
- package/nodes/locales/zh-CN/matterbridge-config.json +1 -0
- package/nodes/matter-config.html +58 -6
- package/nodes/matter-config.js +7 -2
- package/nodes/matterbridge-config.html +1 -0
- package/nodes/utils/lightEngines/canonicalLight.js +75 -0
- package/nodes/utils/lightEngines/hueLightEngine.js +51 -0
- package/nodes/utils/lightEngines/index.js +25 -0
- package/nodes/utils/lightEngines/knxLightCommand.js +79 -0
- package/nodes/utils/lightEngines/lightEngine.js +46 -0
- package/nodes/utils/lightEngines/matterHueShim.js +59 -0
- package/nodes/utils/lightEngines/matterLightEngine.js +117 -0
- package/nodes/utils/matterEngine.mjs +185 -14
- package/nodes/utils/matterKnxConverter.js +1 -1
- package/package.json +11 -3
- package/resources/KNXSendSnippets.js +53 -53
- package/resources/htmlUtils.js +6 -4
- package/nodes/knxUltimateMatterDevice.html +0 -632
- package/nodes/knxUltimateMatterDevice.js +0 -308
- package/nodes/locales/de/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/de/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/en/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/en/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/es/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/es/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/fr/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/fr/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/it/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/it/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/zh-CN/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/zh-CN/knxUltimateMatterDevice.json +0 -109
|
@@ -77,6 +77,12 @@ module.exports = function (RED) {
|
|
|
77
77
|
// The Matter device id is the (stable) Node-RED node id, so the endpoint survives re-deploys.
|
|
78
78
|
node.matterDeviceId = node.id
|
|
79
79
|
node.invertPosition = config.invertPosition === true || config.invertPosition === 'true'
|
|
80
|
+
node.turnOnBehavior = config.turnOnBehavior || 'ignoreLevelAfterOn'
|
|
81
|
+
node.ignoreLevelAfterOnMs = Number(config.ignoreLevelAfterOnMs)
|
|
82
|
+
if (!Number.isFinite(node.ignoreLevelAfterOnMs) || node.ignoreLevelAfterOnMs < 0) node.ignoreLevelAfterOnMs = 800
|
|
83
|
+
node.coverUpdateMode = config.coverUpdateMode || 'optimistic'
|
|
84
|
+
node.coverStatusTimeoutMs = Number(config.coverStatusTimeoutMs)
|
|
85
|
+
if (!Number.isFinite(node.coverStatusTimeoutMs) || node.coverStatusTimeoutMs < 0) node.coverStatusTimeoutMs = 3000
|
|
80
86
|
node.readStatusAtStartup = config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes'
|
|
81
87
|
node.enableNodePINS = config.enableNodePINS === 'yes'
|
|
82
88
|
node.inputs = node.enableNodePINS ? 1 : 0
|
|
@@ -101,6 +107,8 @@ module.exports = function (RED) {
|
|
|
101
107
|
node.formatdecimalsvalue = 2
|
|
102
108
|
|
|
103
109
|
let initialReadTimer = null
|
|
110
|
+
let suppressLevelCommandsUntil = 0
|
|
111
|
+
let coverStatusTimer = null
|
|
104
112
|
|
|
105
113
|
const formatTs = (date) => {
|
|
106
114
|
const d = date instanceof Date ? date : new Date(date)
|
|
@@ -141,6 +149,13 @@ module.exports = function (RED) {
|
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
|
|
152
|
+
const clearCoverStatusTimer = () => {
|
|
153
|
+
if (coverStatusTimer !== null) {
|
|
154
|
+
clearTimeout(coverStatusTimer)
|
|
155
|
+
coverStatusTimer = null
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
// Precompute the routing tables for this single device:
|
|
145
160
|
// statusRoutes: GA -> [{ fn, dpt }] (KNX -> Matter)
|
|
146
161
|
// commandRoutes: fn -> { ga, dpt } (Matter -> KNX)
|
|
@@ -158,13 +173,21 @@ module.exports = function (RED) {
|
|
|
158
173
|
commandRoutes.set(fnDef.fn, { ga, dpt })
|
|
159
174
|
}
|
|
160
175
|
})
|
|
176
|
+
const hasStatusRouteFor = (fn) => {
|
|
177
|
+
for (const routes of statusRoutes.values()) {
|
|
178
|
+
if (routes.some((route) => route.fn === fn)) return true
|
|
179
|
+
}
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
node.knxUltimateAcceptedGAs = Array.from(statusRoutes.keys())
|
|
161
183
|
|
|
162
184
|
// The device definition consumed by the bridge engine (via the config node).
|
|
163
185
|
node.getMatterDef = () => ({
|
|
164
186
|
id: node.matterDeviceId,
|
|
165
187
|
type: node.deviceType,
|
|
166
188
|
name: node.name,
|
|
167
|
-
invertPosition: node.invertPosition === true
|
|
189
|
+
invertPosition: node.invertPosition === true,
|
|
190
|
+
coverUpdateMode: node.coverUpdateMode
|
|
168
191
|
})
|
|
169
192
|
|
|
170
193
|
// Reflects the bridge (config node) status on this device node.
|
|
@@ -209,7 +232,29 @@ module.exports = function (RED) {
|
|
|
209
232
|
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: `No KNX gateway: enable the node PINs to handle ${command.fn}` })
|
|
210
233
|
return
|
|
211
234
|
}
|
|
235
|
+
if (command.fn === 'onoff' && command.value === true && node.turnOnBehavior === 'ignoreLevelAfterOn') {
|
|
236
|
+
suppressLevelCommandsUntil = Date.now() + node.ignoreLevelAfterOnMs
|
|
237
|
+
}
|
|
238
|
+
if (command.fn === 'level' && node.turnOnBehavior === 'ignoreLevelAfterOn' && Date.now() < suppressLevelCommandsUntil) {
|
|
239
|
+
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Ignored Matter level after On', payload: command.value })
|
|
240
|
+
return
|
|
241
|
+
}
|
|
212
242
|
safeSendToKNX({ grpaddr: route.ga, payload: command.value, dpt: route.dpt, outputtype: 'write' }, 'write')
|
|
243
|
+
if (node.deviceType === 'windowcovering' && node.coverUpdateMode === 'optimistic' && node.serverMatterBridge) {
|
|
244
|
+
let optimisticPosition
|
|
245
|
+
if (command.fn === 'position') optimisticPosition = command.value
|
|
246
|
+
if (command.fn === 'updown') optimisticPosition = command.value === true ? 100 : 0
|
|
247
|
+
if (optimisticPosition !== undefined) {
|
|
248
|
+
node.serverMatterBridge.setDeviceState(node.matterDeviceId, 'position', optimisticPosition)
|
|
249
|
+
clearCoverStatusTimer()
|
|
250
|
+
if (node.coverStatusTimeoutMs > 0 && hasStatusRouteFor('position')) {
|
|
251
|
+
coverStatusTimer = setTimeout(() => {
|
|
252
|
+
coverStatusTimer = null
|
|
253
|
+
node.setNodeStatus({ fill: 'yellow', shape: 'ring', text: 'Waiting for KNX cover status', payload: optimisticPosition })
|
|
254
|
+
}, node.coverStatusTimeoutMs)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
213
258
|
node.setNodeStatus({ fill: 'green', shape: 'dot', text: `Matter->KNX ${command.fn}`, payload: command.value })
|
|
214
259
|
} catch (error) {
|
|
215
260
|
node.setNodeStatus({ fill: 'red', shape: 'dot', text: `Matter->KNX error ${error.message}` })
|
|
@@ -228,6 +273,7 @@ module.exports = function (RED) {
|
|
|
228
273
|
const value = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(route.dpt))
|
|
229
274
|
if (value === undefined || value === null) return
|
|
230
275
|
node.serverMatterBridge.setDeviceState(node.matterDeviceId, route.fn, value)
|
|
276
|
+
if (node.deviceType === 'windowcovering' && route.fn === 'position') clearCoverStatusTimer()
|
|
231
277
|
node.setNodeStatus({ fill: 'blue', shape: 'dot', text: `KNX->Matter ${route.fn}`, payload: value })
|
|
232
278
|
} catch (error) {
|
|
233
279
|
node.setNodeStatus({ fill: 'red', shape: 'dot', text: `KNX->Matter error ${error.message}` })
|
|
@@ -298,6 +344,7 @@ module.exports = function (RED) {
|
|
|
298
344
|
node.on('close', (done) => {
|
|
299
345
|
try {
|
|
300
346
|
if (initialReadTimer !== null) clearTimeout(initialReadTimer)
|
|
347
|
+
clearCoverStatusTimer()
|
|
301
348
|
} catch (error) { /* empty */ }
|
|
302
349
|
try {
|
|
303
350
|
if (node.serverKNX) node.serverKNX.removeClient(node)
|