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.
Files changed (77) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/nodes/commonFunctions.js +40 -3
  3. package/nodes/knxUltimate-config.js +379 -335
  4. package/nodes/knxUltimate.html +8 -9
  5. package/nodes/knxUltimate.js +5 -5
  6. package/nodes/knxUltimateAI.js +1 -1
  7. package/nodes/knxUltimateHueLight.html +3 -3
  8. package/nodes/knxUltimateHueLight.js +1 -1
  9. package/nodes/knxUltimateMatterBridge.html +321 -247
  10. package/nodes/knxUltimateMatterBridge.js +48 -1
  11. package/nodes/knxUltimateMatterControllerDevice.html +2119 -0
  12. package/nodes/knxUltimateMatterControllerDevice.js +1701 -0
  13. package/nodes/locales/de/knxUltimateMatterBridge.html +6 -2
  14. package/nodes/locales/de/knxUltimateMatterBridge.json +14 -1
  15. package/nodes/locales/de/knxUltimateMatterControllerDevice.html +27 -0
  16. package/nodes/locales/de/knxUltimateMatterControllerDevice.json +97 -0
  17. package/nodes/locales/de/matter-config.json +5 -1
  18. package/nodes/locales/de/matterbridge-config.json +1 -0
  19. package/nodes/locales/en/knxUltimateMatterBridge.html +6 -2
  20. package/nodes/locales/en/knxUltimateMatterBridge.json +14 -1
  21. package/nodes/locales/en/knxUltimateMatterControllerDevice.html +27 -0
  22. package/nodes/locales/en/knxUltimateMatterControllerDevice.json +97 -0
  23. package/nodes/locales/en/matter-config.json +6 -1
  24. package/nodes/locales/en/matterbridge-config.json +1 -0
  25. package/nodes/locales/es/knxUltimateMatterBridge.html +6 -2
  26. package/nodes/locales/es/knxUltimateMatterBridge.json +14 -1
  27. package/nodes/locales/es/knxUltimateMatterControllerDevice.html +27 -0
  28. package/nodes/locales/es/knxUltimateMatterControllerDevice.json +97 -0
  29. package/nodes/locales/es/matter-config.json +5 -1
  30. package/nodes/locales/es/matterbridge-config.json +1 -0
  31. package/nodes/locales/fr/knxUltimateMatterBridge.html +6 -2
  32. package/nodes/locales/fr/knxUltimateMatterBridge.json +14 -1
  33. package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +27 -0
  34. package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +89 -0
  35. package/nodes/locales/fr/matter-config.json +5 -1
  36. package/nodes/locales/fr/matterbridge-config.json +1 -0
  37. package/nodes/locales/it/knxUltimateMatterBridge.html +6 -2
  38. package/nodes/locales/it/knxUltimateMatterBridge.json +14 -1
  39. package/nodes/locales/it/knxUltimateMatterControllerDevice.html +27 -0
  40. package/nodes/locales/it/knxUltimateMatterControllerDevice.json +97 -0
  41. package/nodes/locales/it/matter-config.json +6 -1
  42. package/nodes/locales/it/matterbridge-config.json +1 -0
  43. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +6 -2
  44. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +14 -1
  45. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +27 -0
  46. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +89 -0
  47. package/nodes/locales/zh-CN/matter-config.json +5 -1
  48. package/nodes/locales/zh-CN/matterbridge-config.json +1 -0
  49. package/nodes/matter-config.html +58 -6
  50. package/nodes/matter-config.js +7 -2
  51. package/nodes/matterbridge-config.html +1 -0
  52. package/nodes/utils/lightEngines/canonicalLight.js +75 -0
  53. package/nodes/utils/lightEngines/hueLightEngine.js +51 -0
  54. package/nodes/utils/lightEngines/index.js +25 -0
  55. package/nodes/utils/lightEngines/knxLightCommand.js +79 -0
  56. package/nodes/utils/lightEngines/lightEngine.js +46 -0
  57. package/nodes/utils/lightEngines/matterHueShim.js +59 -0
  58. package/nodes/utils/lightEngines/matterLightEngine.js +117 -0
  59. package/nodes/utils/matterEngine.mjs +185 -14
  60. package/nodes/utils/matterKnxConverter.js +1 -1
  61. package/package.json +11 -3
  62. package/resources/KNXSendSnippets.js +53 -53
  63. package/resources/htmlUtils.js +6 -4
  64. package/nodes/knxUltimateMatterDevice.html +0 -632
  65. package/nodes/knxUltimateMatterDevice.js +0 -308
  66. package/nodes/locales/de/knxUltimateMatterDevice.html +0 -56
  67. package/nodes/locales/de/knxUltimateMatterDevice.json +0 -109
  68. package/nodes/locales/en/knxUltimateMatterDevice.html +0 -56
  69. package/nodes/locales/en/knxUltimateMatterDevice.json +0 -109
  70. package/nodes/locales/es/knxUltimateMatterDevice.html +0 -56
  71. package/nodes/locales/es/knxUltimateMatterDevice.json +0 -109
  72. package/nodes/locales/fr/knxUltimateMatterDevice.html +0 -56
  73. package/nodes/locales/fr/knxUltimateMatterDevice.json +0 -109
  74. package/nodes/locales/it/knxUltimateMatterDevice.html +0 -56
  75. package/nodes/locales/it/knxUltimateMatterDevice.json +0 -109
  76. package/nodes/locales/zh-CN/knxUltimateMatterDevice.html +0 -56
  77. 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)