node-red-contrib-knx-ultimate 7.1.4 → 8.0.2

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 (657) hide show
  1. package/CHANGELOG.md +20 -10
  2. package/MIGRATION.md +40 -0
  3. package/README.md +32 -6
  4. package/docs/README.md +34 -0
  5. package/docs/assets/brand/made-in-italy.svg +12 -0
  6. package/examples/Alerter - Device Alert Aggregation.json +5 -2
  7. package/examples/Auto Responder - Read Request Defaults.json +5 -2
  8. package/examples/Garage - Door Controller.json +5 -2
  9. package/examples/Global Context - Expose KNX Values.json +5 -2
  10. package/examples/KNX DateTime - Set Date and Time to Bus.json +111 -89
  11. package/examples/Load Control - Manual Shedding Mode.json +5 -2
  12. package/examples/Logger ETS File and Telegram Count.json +10 -2
  13. package/examples/Scene Controller Recall and Save.json +33 -6
  14. package/examples/Staircase - Timer, Override and Block.json +5 -2
  15. package/examples/WatchDog Connect Disconnect.json +98 -70
  16. package/nodes/commonFunctions.js +0 -587
  17. package/nodes/knxUltimate-config.html +0 -14
  18. package/nodes/knxUltimate-config.js +1 -18
  19. package/nodes/knxUltimate.html +1 -6
  20. package/nodes/knxUltimateIoTBridge.html +0 -14
  21. package/nodes/knxUltimateLegacyMigration.js +75 -0
  22. package/nodes/knxUltimateMultiRouting.html +1 -7
  23. package/nodes/knxUltimateRouterFilter.html +1 -7
  24. package/nodes/knxUltimateUtility.html +0 -6
  25. package/nodes/knxUltimateViewer.html +0 -6
  26. package/nodes/locales/de/knxUltimate-config.html +0 -8
  27. package/nodes/locales/de/knxUltimate.html +0 -8
  28. package/nodes/locales/de/knxUltimateIoTBridge.html +0 -8
  29. package/nodes/locales/de/knxUltimateMultiRouting.html +0 -8
  30. package/nodes/locales/de/knxUltimateRouterFilter.html +0 -8
  31. package/nodes/locales/de/knxUltimateUtility.html +0 -8
  32. package/nodes/locales/de/knxUltimateUtility.json +16 -1
  33. package/nodes/locales/de/knxUltimateViewer.html +0 -8
  34. package/nodes/locales/en/knxUltimate-config.html +0 -8
  35. package/nodes/locales/en/knxUltimate.html +0 -8
  36. package/nodes/locales/en/knxUltimateIoTBridge.html +0 -8
  37. package/nodes/locales/en/knxUltimateMultiRouting.html +0 -8
  38. package/nodes/locales/en/knxUltimateRouterFilter.html +0 -8
  39. package/nodes/locales/en/knxUltimateUtility.html +0 -8
  40. package/nodes/locales/en/knxUltimateUtility.json +16 -1
  41. package/nodes/locales/en/knxUltimateViewer.html +0 -8
  42. package/nodes/locales/es/knxUltimate-config.html +0 -8
  43. package/nodes/locales/es/knxUltimate.html +0 -8
  44. package/nodes/locales/es/knxUltimateIoTBridge.html +0 -8
  45. package/nodes/locales/es/knxUltimateMultiRouting.html +0 -8
  46. package/nodes/locales/es/knxUltimateRouterFilter.html +0 -8
  47. package/nodes/locales/es/knxUltimateUtility.html +0 -8
  48. package/nodes/locales/es/knxUltimateUtility.json +17 -2
  49. package/nodes/locales/es/knxUltimateViewer.html +0 -8
  50. package/nodes/locales/fr/knxUltimate-config.html +0 -8
  51. package/nodes/locales/fr/knxUltimate.html +0 -8
  52. package/nodes/locales/fr/knxUltimateIoTBridge.html +0 -8
  53. package/nodes/locales/fr/knxUltimateMultiRouting.html +0 -8
  54. package/nodes/locales/fr/knxUltimateRouterFilter.html +0 -8
  55. package/nodes/locales/fr/knxUltimateUtility.html +0 -8
  56. package/nodes/locales/fr/knxUltimateUtility.json +16 -1
  57. package/nodes/locales/fr/knxUltimateViewer.html +0 -8
  58. package/nodes/locales/it/knxUltimate-config.html +0 -8
  59. package/nodes/locales/it/knxUltimate.html +0 -8
  60. package/nodes/locales/it/knxUltimateIoTBridge.html +0 -8
  61. package/nodes/locales/it/knxUltimateMultiRouting.html +0 -8
  62. package/nodes/locales/it/knxUltimateRouterFilter.html +0 -8
  63. package/nodes/locales/it/knxUltimateUtility.html +0 -8
  64. package/nodes/locales/it/knxUltimateUtility.json +16 -1
  65. package/nodes/locales/it/knxUltimateViewer.html +0 -8
  66. package/nodes/locales/zh-CN/knxUltimate-config.html +0 -8
  67. package/nodes/locales/zh-CN/knxUltimate.html +0 -8
  68. package/nodes/locales/zh-CN/knxUltimateIoTBridge.html +0 -8
  69. package/nodes/locales/zh-CN/knxUltimateMultiRouting.html +0 -8
  70. package/nodes/locales/zh-CN/knxUltimateRouterFilter.html +0 -8
  71. package/nodes/locales/zh-CN/knxUltimateUtility.html +0 -8
  72. package/nodes/locales/zh-CN/knxUltimateUtility.json +16 -1
  73. package/nodes/locales/zh-CN/knxUltimateViewer.html +0 -8
  74. package/nodes/plugins/knxUltimate-v8-migration-recovery-plugin.html +140 -0
  75. package/package.json +22 -69
  76. package/resources/11f26b4500.js +2 -2
  77. package/resources/KNXAIChatAdapterMappings.js +2 -416
  78. package/resources/configNodeEditorSelection.js +2 -32
  79. package/resources/hueControllerEditorSelection.js +2 -32
  80. package/resources/hueControllerMigrationDialog.js +2 -254
  81. package/resources/hueControllerProfiles.js +2 -9319
  82. package/resources/knxUtilityProfiles.js +2041 -2082
  83. package/resources/legacyMigrationNotice.js +23 -111
  84. package/resources/matterQrScanner.js +2 -98
  85. package/resources/upgradeToV8.js +94 -26
  86. package/scripts/check-legacy-flows.js +142 -0
  87. package/examples/Hue Area Motion - Zone Presence.json +0 -77
  88. package/examples/Hue Battery - Level Status.json +0 -77
  89. package/examples/Hue Button - Short and Dim Commands.json +0 -83
  90. package/examples/Hue Camera Motion - Presence.json +0 -77
  91. package/examples/Hue Config - Bridge Template.json +0 -27
  92. package/examples/Hue Contact Sensor - Open Closed.json +0 -75
  93. package/examples/Hue Device Software Update - Availability.json +0 -77
  94. package/examples/Hue Humidity Sensor - Relative Humidity.json +0 -77
  95. package/examples/Hue Light - Switch and Brightness.json +0 -67
  96. package/examples/Hue Light Sensor - Lux to KNX.json +0 -77
  97. package/examples/Hue Motion - Presence Sensor.json +0 -76
  98. package/examples/Hue Plug - Switch and State.json +0 -63
  99. package/examples/Hue Scene - Recall from KNX.json +0 -63
  100. package/examples/Hue Tap Dial - Relative Dimming.json +0 -76
  101. package/examples/Hue Temperature Sensor - Celsius to KNX.json +0 -77
  102. package/examples/Hue Zigbee Connectivity - Online Status.json +0 -77
  103. package/examples/Matter Controller - Semantic Flow Input.json +0 -248
  104. package/nodes/hue-config.html +0 -378
  105. package/nodes/hue-config.js +0 -846
  106. package/nodes/icons/node-alerter-icon.png +0 -0
  107. package/nodes/icons/node-homekit-icon.png +0 -0
  108. package/nodes/icons/node-homekit-icon.svg +0 -10
  109. package/nodes/icons/node-hue-icon.svg +0 -10
  110. package/nodes/icons/node-knx-icon.png +0 -0
  111. package/nodes/icons/node-logger-icon.png +0 -0
  112. package/nodes/icons/node-logger2-icon.png +0 -0
  113. package/nodes/icons/node-logger2-icon.svg +0 -32
  114. package/nodes/icons/node-matter-bridge-icon.svg +0 -6
  115. package/nodes/icons/node-matter-controller-icon.svg +0 -6
  116. package/nodes/icons/node-matter-icon.svg +0 -12
  117. package/nodes/icons/node-scene-icon.png +0 -0
  118. package/nodes/icons/node-watchdog-icon.png +0 -0
  119. package/nodes/knxUltimateAI.html +0 -1826
  120. package/nodes/knxUltimateAI.js +0 -16797
  121. package/nodes/knxUltimateAIHomeAssistant.html +0 -54
  122. package/nodes/knxUltimateAIHomeAssistant.js +0 -152
  123. package/nodes/knxUltimateAlerter.html +0 -267
  124. package/nodes/knxUltimateAlerter.js +0 -300
  125. package/nodes/knxUltimateAutoResponder.html +0 -128
  126. package/nodes/knxUltimateAutoResponder.js +0 -264
  127. package/nodes/knxUltimateDateTime.html +0 -579
  128. package/nodes/knxUltimateDateTime.js +0 -282
  129. package/nodes/knxUltimateGarage.html +0 -337
  130. package/nodes/knxUltimateGarage.js +0 -504
  131. package/nodes/knxUltimateGlobalContext.html +0 -145
  132. package/nodes/knxUltimateGlobalContext.js +0 -208
  133. package/nodes/knxUltimateHATranslator.html +0 -85
  134. package/nodes/knxUltimateHATranslator.js +0 -83
  135. package/nodes/knxUltimateHueAreaMotion.html +0 -670
  136. package/nodes/knxUltimateHueAreaMotion.js +0 -218
  137. package/nodes/knxUltimateHueBattery.html +0 -646
  138. package/nodes/knxUltimateHueBattery.js +0 -182
  139. package/nodes/knxUltimateHueButton.html +0 -804
  140. package/nodes/knxUltimateHueButton.js +0 -292
  141. package/nodes/knxUltimateHueCameraMotion.html +0 -671
  142. package/nodes/knxUltimateHueCameraMotion.js +0 -218
  143. package/nodes/knxUltimateHueContactSensor.html +0 -609
  144. package/nodes/knxUltimateHueContactSensor.js +0 -188
  145. package/nodes/knxUltimateHueController.html +0 -1765
  146. package/nodes/knxUltimateHueController.js +0 -28
  147. package/nodes/knxUltimateHueHumiditySensor.html +0 -641
  148. package/nodes/knxUltimateHueHumiditySensor.js +0 -201
  149. package/nodes/knxUltimateHueLight.html +0 -2516
  150. package/nodes/knxUltimateHueLight.js +0 -1582
  151. package/nodes/knxUltimateHueLightSensor.html +0 -636
  152. package/nodes/knxUltimateHueLightSensor.js +0 -177
  153. package/nodes/knxUltimateHueMotion.html +0 -634
  154. package/nodes/knxUltimateHueMotion.js +0 -182
  155. package/nodes/knxUltimateHuePlug.html +0 -576
  156. package/nodes/knxUltimateHuePlug.js +0 -341
  157. package/nodes/knxUltimateHueScene.html +0 -967
  158. package/nodes/knxUltimateHueScene.js +0 -349
  159. package/nodes/knxUltimateHueTapDial.html +0 -640
  160. package/nodes/knxUltimateHueTapDial.js +0 -268
  161. package/nodes/knxUltimateHueTemperatureSensor.html +0 -639
  162. package/nodes/knxUltimateHueTemperatureSensor.js +0 -184
  163. package/nodes/knxUltimateHueZigbeeConnectivity.html +0 -648
  164. package/nodes/knxUltimateHueZigbeeConnectivity.js +0 -182
  165. package/nodes/knxUltimateHuedevice_software_update.html +0 -650
  166. package/nodes/knxUltimateHuedevice_software_update.js +0 -182
  167. package/nodes/knxUltimateLoadControl.html +0 -425
  168. package/nodes/knxUltimateLoadControl.js +0 -443
  169. package/nodes/knxUltimateLogger.html +0 -197
  170. package/nodes/knxUltimateLogger.js +0 -224
  171. package/nodes/knxUltimateMatterBridge.html +0 -689
  172. package/nodes/knxUltimateMatterBridge.js +0 -464
  173. package/nodes/knxUltimateMatterControllerDevice.html +0 -2851
  174. package/nodes/knxUltimateMatterControllerDevice.js +0 -1747
  175. package/nodes/knxUltimateSceneController.html +0 -529
  176. package/nodes/knxUltimateSceneController.js +0 -437
  177. package/nodes/knxUltimateStaircase.html +0 -346
  178. package/nodes/knxUltimateStaircase.js +0 -463
  179. package/nodes/knxUltimateWatchDog.html +0 -241
  180. package/nodes/knxUltimateWatchDog.js +0 -230
  181. package/nodes/locales/de/hue-config.html +0 -33
  182. package/nodes/locales/de/hue-config.json +0 -21
  183. package/nodes/locales/de/knxUltimateAI.html +0 -13
  184. package/nodes/locales/de/knxUltimateAI.json +0 -231
  185. package/nodes/locales/de/knxUltimateAIHomeAssistant.html +0 -11
  186. package/nodes/locales/de/knxUltimateAlerter.html +0 -159
  187. package/nodes/locales/de/knxUltimateAlerter.json +0 -27
  188. package/nodes/locales/de/knxUltimateAutoResponder.html +0 -96
  189. package/nodes/locales/de/knxUltimateAutoResponder.json +0 -8
  190. package/nodes/locales/de/knxUltimateDateTime.html +0 -45
  191. package/nodes/locales/de/knxUltimateDateTime.json +0 -32
  192. package/nodes/locales/de/knxUltimateGarage.html +0 -79
  193. package/nodes/locales/de/knxUltimateGarage.json +0 -51
  194. package/nodes/locales/de/knxUltimateGlobalContext.html +0 -76
  195. package/nodes/locales/de/knxUltimateGlobalContext.json +0 -24
  196. package/nodes/locales/de/knxUltimateHATranslator.html +0 -25
  197. package/nodes/locales/de/knxUltimateHATranslator.json +0 -8
  198. package/nodes/locales/de/knxUltimateHueAreaMotion.html +0 -44
  199. package/nodes/locales/de/knxUltimateHueAreaMotion.json +0 -31
  200. package/nodes/locales/de/knxUltimateHueBattery.html +0 -37
  201. package/nodes/locales/de/knxUltimateHueBattery.json +0 -30
  202. package/nodes/locales/de/knxUltimateHueButton.html +0 -52
  203. package/nodes/locales/de/knxUltimateHueButton.json +0 -34
  204. package/nodes/locales/de/knxUltimateHueCameraMotion.html +0 -38
  205. package/nodes/locales/de/knxUltimateHueCameraMotion.json +0 -30
  206. package/nodes/locales/de/knxUltimateHueContactSensor.html +0 -35
  207. package/nodes/locales/de/knxUltimateHueContactSensor.json +0 -26
  208. package/nodes/locales/de/knxUltimateHueController.html +0 -690
  209. package/nodes/locales/de/knxUltimateHueController.json +0 -64
  210. package/nodes/locales/de/knxUltimateHueHumiditySensor.html +0 -38
  211. package/nodes/locales/de/knxUltimateHueHumiditySensor.json +0 -30
  212. package/nodes/locales/de/knxUltimateHueLight.html +0 -121
  213. package/nodes/locales/de/knxUltimateHueLight.json +0 -94
  214. package/nodes/locales/de/knxUltimateHueLightSensor.html +0 -38
  215. package/nodes/locales/de/knxUltimateHueLightSensor.json +0 -19
  216. package/nodes/locales/de/knxUltimateHueMotion.html +0 -41
  217. package/nodes/locales/de/knxUltimateHueMotion.json +0 -27
  218. package/nodes/locales/de/knxUltimateHuePlug.html +0 -50
  219. package/nodes/locales/de/knxUltimateHuePlug.json +0 -33
  220. package/nodes/locales/de/knxUltimateHueScene.html +0 -45
  221. package/nodes/locales/de/knxUltimateHueScene.json +0 -41
  222. package/nodes/locales/de/knxUltimateHueTapDial.html +0 -40
  223. package/nodes/locales/de/knxUltimateHueTapDial.json +0 -28
  224. package/nodes/locales/de/knxUltimateHueTemperatureSensor.html +0 -38
  225. package/nodes/locales/de/knxUltimateHueTemperatureSensor.json +0 -30
  226. package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.html +0 -39
  227. package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.json +0 -30
  228. package/nodes/locales/de/knxUltimateHuedevice_software_update.html +0 -36
  229. package/nodes/locales/de/knxUltimateHuedevice_software_update.json +0 -30
  230. package/nodes/locales/de/knxUltimateLoadControl.html +0 -69
  231. package/nodes/locales/de/knxUltimateLoadControl.json +0 -28
  232. package/nodes/locales/de/knxUltimateLogger.html +0 -126
  233. package/nodes/locales/de/knxUltimateLogger.json +0 -31
  234. package/nodes/locales/de/knxUltimateMatterBridge.html +0 -65
  235. package/nodes/locales/de/knxUltimateMatterBridge.json +0 -123
  236. package/nodes/locales/de/knxUltimateMatterControllerDevice.html +0 -57
  237. package/nodes/locales/de/knxUltimateMatterControllerDevice.json +0 -146
  238. package/nodes/locales/de/knxUltimateSceneController.html +0 -98
  239. package/nodes/locales/de/knxUltimateSceneController.json +0 -32
  240. package/nodes/locales/de/knxUltimateStaircase.html +0 -77
  241. package/nodes/locales/de/knxUltimateStaircase.json +0 -62
  242. package/nodes/locales/de/knxUltimateWatchDog.html +0 -178
  243. package/nodes/locales/de/knxUltimateWatchDog.json +0 -27
  244. package/nodes/locales/de/matter-config.html +0 -47
  245. package/nodes/locales/de/matter-config.json +0 -47
  246. package/nodes/locales/de/matterbridge-config.html +0 -45
  247. package/nodes/locales/de/matterbridge-config.json +0 -25
  248. package/nodes/locales/en/hue-config.html +0 -31
  249. package/nodes/locales/en/hue-config.json +0 -21
  250. package/nodes/locales/en/knxUltimateAI.html +0 -13
  251. package/nodes/locales/en/knxUltimateAI.json +0 -234
  252. package/nodes/locales/en/knxUltimateAIHomeAssistant.html +0 -11
  253. package/nodes/locales/en/knxUltimateAlerter.html +0 -164
  254. package/nodes/locales/en/knxUltimateAlerter.json +0 -27
  255. package/nodes/locales/en/knxUltimateAutoResponder.html +0 -94
  256. package/nodes/locales/en/knxUltimateAutoResponder.json +0 -8
  257. package/nodes/locales/en/knxUltimateDateTime.html +0 -67
  258. package/nodes/locales/en/knxUltimateDateTime.json +0 -32
  259. package/nodes/locales/en/knxUltimateGarage.html +0 -79
  260. package/nodes/locales/en/knxUltimateGarage.json +0 -51
  261. package/nodes/locales/en/knxUltimateGlobalContext.html +0 -124
  262. package/nodes/locales/en/knxUltimateGlobalContext.json +0 -24
  263. package/nodes/locales/en/knxUltimateHATranslator.html +0 -25
  264. package/nodes/locales/en/knxUltimateHATranslator.json +0 -8
  265. package/nodes/locales/en/knxUltimateHueAreaMotion.html +0 -44
  266. package/nodes/locales/en/knxUltimateHueAreaMotion.json +0 -31
  267. package/nodes/locales/en/knxUltimateHueBattery.html +0 -37
  268. package/nodes/locales/en/knxUltimateHueBattery.json +0 -30
  269. package/nodes/locales/en/knxUltimateHueButton.html +0 -52
  270. package/nodes/locales/en/knxUltimateHueButton.json +0 -34
  271. package/nodes/locales/en/knxUltimateHueCameraMotion.html +0 -38
  272. package/nodes/locales/en/knxUltimateHueCameraMotion.json +0 -30
  273. package/nodes/locales/en/knxUltimateHueContactSensor.html +0 -35
  274. package/nodes/locales/en/knxUltimateHueContactSensor.json +0 -26
  275. package/nodes/locales/en/knxUltimateHueController.html +0 -691
  276. package/nodes/locales/en/knxUltimateHueController.json +0 -64
  277. package/nodes/locales/en/knxUltimateHueHumiditySensor.html +0 -38
  278. package/nodes/locales/en/knxUltimateHueHumiditySensor.json +0 -30
  279. package/nodes/locales/en/knxUltimateHueLight.html +0 -124
  280. package/nodes/locales/en/knxUltimateHueLight.json +0 -94
  281. package/nodes/locales/en/knxUltimateHueLightSensor.html +0 -38
  282. package/nodes/locales/en/knxUltimateHueLightSensor.json +0 -19
  283. package/nodes/locales/en/knxUltimateHueMotion.html +0 -41
  284. package/nodes/locales/en/knxUltimateHueMotion.json +0 -27
  285. package/nodes/locales/en/knxUltimateHuePlug.html +0 -50
  286. package/nodes/locales/en/knxUltimateHuePlug.json +0 -33
  287. package/nodes/locales/en/knxUltimateHueScene.html +0 -45
  288. package/nodes/locales/en/knxUltimateHueScene.json +0 -41
  289. package/nodes/locales/en/knxUltimateHueTapDial.html +0 -40
  290. package/nodes/locales/en/knxUltimateHueTapDial.json +0 -28
  291. package/nodes/locales/en/knxUltimateHueTemperatureSensor.html +0 -38
  292. package/nodes/locales/en/knxUltimateHueTemperatureSensor.json +0 -30
  293. package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.html +0 -39
  294. package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.json +0 -30
  295. package/nodes/locales/en/knxUltimateHuedevice_software_update.html +0 -36
  296. package/nodes/locales/en/knxUltimateHuedevice_software_update.json +0 -30
  297. package/nodes/locales/en/knxUltimateLoadControl.html +0 -76
  298. package/nodes/locales/en/knxUltimateLoadControl.json +0 -28
  299. package/nodes/locales/en/knxUltimateLogger.html +0 -161
  300. package/nodes/locales/en/knxUltimateLogger.json +0 -31
  301. package/nodes/locales/en/knxUltimateMatterBridge.html +0 -65
  302. package/nodes/locales/en/knxUltimateMatterBridge.json +0 -123
  303. package/nodes/locales/en/knxUltimateMatterControllerDevice.html +0 -57
  304. package/nodes/locales/en/knxUltimateMatterControllerDevice.json +0 -146
  305. package/nodes/locales/en/knxUltimateSceneController.html +0 -117
  306. package/nodes/locales/en/knxUltimateSceneController.json +0 -32
  307. package/nodes/locales/en/knxUltimateStaircase.html +0 -77
  308. package/nodes/locales/en/knxUltimateStaircase.json +0 -62
  309. package/nodes/locales/en/knxUltimateWatchDog.html +0 -244
  310. package/nodes/locales/en/knxUltimateWatchDog.json +0 -27
  311. package/nodes/locales/en/matter-config.html +0 -47
  312. package/nodes/locales/en/matter-config.json +0 -48
  313. package/nodes/locales/en/matterbridge-config.html +0 -45
  314. package/nodes/locales/en/matterbridge-config.json +0 -25
  315. package/nodes/locales/es/hue-config.html +0 -31
  316. package/nodes/locales/es/hue-config.json +0 -21
  317. package/nodes/locales/es/knxUltimateAI.html +0 -13
  318. package/nodes/locales/es/knxUltimateAI.json +0 -231
  319. package/nodes/locales/es/knxUltimateAIHomeAssistant.html +0 -11
  320. package/nodes/locales/es/knxUltimateAlerter.html +0 -164
  321. package/nodes/locales/es/knxUltimateAlerter.json +0 -27
  322. package/nodes/locales/es/knxUltimateAutoResponder.html +0 -81
  323. package/nodes/locales/es/knxUltimateAutoResponder.json +0 -8
  324. package/nodes/locales/es/knxUltimateDateTime.html +0 -45
  325. package/nodes/locales/es/knxUltimateDateTime.json +0 -32
  326. package/nodes/locales/es/knxUltimateGarage.html +0 -79
  327. package/nodes/locales/es/knxUltimateGarage.json +0 -51
  328. package/nodes/locales/es/knxUltimateGlobalContext.html +0 -128
  329. package/nodes/locales/es/knxUltimateGlobalContext.json +0 -24
  330. package/nodes/locales/es/knxUltimateHATranslator.html +0 -25
  331. package/nodes/locales/es/knxUltimateHATranslator.json +0 -8
  332. package/nodes/locales/es/knxUltimateHueAreaMotion.html +0 -44
  333. package/nodes/locales/es/knxUltimateHueAreaMotion.json +0 -31
  334. package/nodes/locales/es/knxUltimateHueBattery.html +0 -37
  335. package/nodes/locales/es/knxUltimateHueBattery.json +0 -30
  336. package/nodes/locales/es/knxUltimateHueButton.html +0 -52
  337. package/nodes/locales/es/knxUltimateHueButton.json +0 -34
  338. package/nodes/locales/es/knxUltimateHueCameraMotion.html +0 -38
  339. package/nodes/locales/es/knxUltimateHueCameraMotion.json +0 -30
  340. package/nodes/locales/es/knxUltimateHueContactSensor.html +0 -35
  341. package/nodes/locales/es/knxUltimateHueContactSensor.json +0 -26
  342. package/nodes/locales/es/knxUltimateHueController.html +0 -691
  343. package/nodes/locales/es/knxUltimateHueController.json +0 -64
  344. package/nodes/locales/es/knxUltimateHueHumiditySensor.html +0 -38
  345. package/nodes/locales/es/knxUltimateHueHumiditySensor.json +0 -30
  346. package/nodes/locales/es/knxUltimateHueLight.html +0 -124
  347. package/nodes/locales/es/knxUltimateHueLight.json +0 -94
  348. package/nodes/locales/es/knxUltimateHueLightSensor.html +0 -38
  349. package/nodes/locales/es/knxUltimateHueLightSensor.json +0 -19
  350. package/nodes/locales/es/knxUltimateHueMotion.html +0 -41
  351. package/nodes/locales/es/knxUltimateHueMotion.json +0 -27
  352. package/nodes/locales/es/knxUltimateHuePlug.html +0 -50
  353. package/nodes/locales/es/knxUltimateHuePlug.json +0 -33
  354. package/nodes/locales/es/knxUltimateHueScene.html +0 -45
  355. package/nodes/locales/es/knxUltimateHueScene.json +0 -41
  356. package/nodes/locales/es/knxUltimateHueTapDial.html +0 -40
  357. package/nodes/locales/es/knxUltimateHueTapDial.json +0 -28
  358. package/nodes/locales/es/knxUltimateHueTemperatureSensor.html +0 -38
  359. package/nodes/locales/es/knxUltimateHueTemperatureSensor.json +0 -30
  360. package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.html +0 -39
  361. package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.json +0 -30
  362. package/nodes/locales/es/knxUltimateHuedevice_software_update.html +0 -36
  363. package/nodes/locales/es/knxUltimateHuedevice_software_update.json +0 -30
  364. package/nodes/locales/es/knxUltimateLoadControl.html +0 -76
  365. package/nodes/locales/es/knxUltimateLoadControl.json +0 -28
  366. package/nodes/locales/es/knxUltimateLogger.html +0 -162
  367. package/nodes/locales/es/knxUltimateLogger.json +0 -31
  368. package/nodes/locales/es/knxUltimateMatterBridge.html +0 -65
  369. package/nodes/locales/es/knxUltimateMatterBridge.json +0 -123
  370. package/nodes/locales/es/knxUltimateMatterControllerDevice.html +0 -57
  371. package/nodes/locales/es/knxUltimateMatterControllerDevice.json +0 -146
  372. package/nodes/locales/es/knxUltimateSceneController.html +0 -115
  373. package/nodes/locales/es/knxUltimateSceneController.json +0 -32
  374. package/nodes/locales/es/knxUltimateStaircase.html +0 -77
  375. package/nodes/locales/es/knxUltimateStaircase.json +0 -62
  376. package/nodes/locales/es/knxUltimateWatchDog.html +0 -230
  377. package/nodes/locales/es/knxUltimateWatchDog.json +0 -27
  378. package/nodes/locales/es/matter-config.html +0 -47
  379. package/nodes/locales/es/matter-config.json +0 -47
  380. package/nodes/locales/es/matterbridge-config.html +0 -45
  381. package/nodes/locales/es/matterbridge-config.json +0 -25
  382. package/nodes/locales/fr/hue-config.html +0 -31
  383. package/nodes/locales/fr/hue-config.json +0 -21
  384. package/nodes/locales/fr/knxUltimateAI.html +0 -13
  385. package/nodes/locales/fr/knxUltimateAI.json +0 -231
  386. package/nodes/locales/fr/knxUltimateAIHomeAssistant.html +0 -11
  387. package/nodes/locales/fr/knxUltimateAlerter.html +0 -164
  388. package/nodes/locales/fr/knxUltimateAlerter.json +0 -27
  389. package/nodes/locales/fr/knxUltimateAutoResponder.html +0 -92
  390. package/nodes/locales/fr/knxUltimateAutoResponder.json +0 -8
  391. package/nodes/locales/fr/knxUltimateDateTime.html +0 -45
  392. package/nodes/locales/fr/knxUltimateDateTime.json +0 -32
  393. package/nodes/locales/fr/knxUltimateGarage.html +0 -79
  394. package/nodes/locales/fr/knxUltimateGarage.json +0 -51
  395. package/nodes/locales/fr/knxUltimateGlobalContext.html +0 -122
  396. package/nodes/locales/fr/knxUltimateGlobalContext.json +0 -24
  397. package/nodes/locales/fr/knxUltimateHATranslator.html +0 -25
  398. package/nodes/locales/fr/knxUltimateHATranslator.json +0 -8
  399. package/nodes/locales/fr/knxUltimateHueAreaMotion.html +0 -44
  400. package/nodes/locales/fr/knxUltimateHueAreaMotion.json +0 -31
  401. package/nodes/locales/fr/knxUltimateHueBattery.html +0 -37
  402. package/nodes/locales/fr/knxUltimateHueBattery.json +0 -30
  403. package/nodes/locales/fr/knxUltimateHueButton.html +0 -52
  404. package/nodes/locales/fr/knxUltimateHueButton.json +0 -34
  405. package/nodes/locales/fr/knxUltimateHueCameraMotion.html +0 -38
  406. package/nodes/locales/fr/knxUltimateHueCameraMotion.json +0 -30
  407. package/nodes/locales/fr/knxUltimateHueContactSensor.html +0 -35
  408. package/nodes/locales/fr/knxUltimateHueContactSensor.json +0 -26
  409. package/nodes/locales/fr/knxUltimateHueController.html +0 -691
  410. package/nodes/locales/fr/knxUltimateHueController.json +0 -64
  411. package/nodes/locales/fr/knxUltimateHueHumiditySensor.html +0 -38
  412. package/nodes/locales/fr/knxUltimateHueHumiditySensor.json +0 -30
  413. package/nodes/locales/fr/knxUltimateHueLight.html +0 -124
  414. package/nodes/locales/fr/knxUltimateHueLight.json +0 -86
  415. package/nodes/locales/fr/knxUltimateHueLightSensor.html +0 -38
  416. package/nodes/locales/fr/knxUltimateHueLightSensor.json +0 -19
  417. package/nodes/locales/fr/knxUltimateHueMotion.html +0 -41
  418. package/nodes/locales/fr/knxUltimateHueMotion.json +0 -27
  419. package/nodes/locales/fr/knxUltimateHuePlug.html +0 -50
  420. package/nodes/locales/fr/knxUltimateHuePlug.json +0 -33
  421. package/nodes/locales/fr/knxUltimateHueScene.html +0 -45
  422. package/nodes/locales/fr/knxUltimateHueScene.json +0 -41
  423. package/nodes/locales/fr/knxUltimateHueTapDial.html +0 -40
  424. package/nodes/locales/fr/knxUltimateHueTapDial.json +0 -28
  425. package/nodes/locales/fr/knxUltimateHueTemperatureSensor.html +0 -38
  426. package/nodes/locales/fr/knxUltimateHueTemperatureSensor.json +0 -30
  427. package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.html +0 -39
  428. package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.json +0 -30
  429. package/nodes/locales/fr/knxUltimateHuedevice_software_update.html +0 -36
  430. package/nodes/locales/fr/knxUltimateHuedevice_software_update.json +0 -30
  431. package/nodes/locales/fr/knxUltimateLoadControl.html +0 -76
  432. package/nodes/locales/fr/knxUltimateLoadControl.json +0 -28
  433. package/nodes/locales/fr/knxUltimateLogger.html +0 -160
  434. package/nodes/locales/fr/knxUltimateLogger.json +0 -31
  435. package/nodes/locales/fr/knxUltimateMatterBridge.html +0 -65
  436. package/nodes/locales/fr/knxUltimateMatterBridge.json +0 -123
  437. package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +0 -57
  438. package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +0 -146
  439. package/nodes/locales/fr/knxUltimateSceneController.html +0 -117
  440. package/nodes/locales/fr/knxUltimateSceneController.json +0 -32
  441. package/nodes/locales/fr/knxUltimateStaircase.html +0 -77
  442. package/nodes/locales/fr/knxUltimateStaircase.json +0 -62
  443. package/nodes/locales/fr/knxUltimateWatchDog.html +0 -228
  444. package/nodes/locales/fr/knxUltimateWatchDog.json +0 -27
  445. package/nodes/locales/fr/matter-config.html +0 -47
  446. package/nodes/locales/fr/matter-config.json +0 -47
  447. package/nodes/locales/fr/matterbridge-config.html +0 -45
  448. package/nodes/locales/fr/matterbridge-config.json +0 -25
  449. package/nodes/locales/it/hue-config.html +0 -33
  450. package/nodes/locales/it/hue-config.json +0 -21
  451. package/nodes/locales/it/knxUltimateAI.html +0 -13
  452. package/nodes/locales/it/knxUltimateAI.json +0 -234
  453. package/nodes/locales/it/knxUltimateAIHomeAssistant.html +0 -11
  454. package/nodes/locales/it/knxUltimateAlerter.html +0 -159
  455. package/nodes/locales/it/knxUltimateAlerter.json +0 -27
  456. package/nodes/locales/it/knxUltimateAutoResponder.html +0 -96
  457. package/nodes/locales/it/knxUltimateAutoResponder.json +0 -8
  458. package/nodes/locales/it/knxUltimateDateTime.html +0 -67
  459. package/nodes/locales/it/knxUltimateDateTime.json +0 -32
  460. package/nodes/locales/it/knxUltimateGarage.html +0 -79
  461. package/nodes/locales/it/knxUltimateGarage.json +0 -51
  462. package/nodes/locales/it/knxUltimateGlobalContext.html +0 -81
  463. package/nodes/locales/it/knxUltimateGlobalContext.json +0 -24
  464. package/nodes/locales/it/knxUltimateHATranslator.html +0 -24
  465. package/nodes/locales/it/knxUltimateHATranslator.json +0 -8
  466. package/nodes/locales/it/knxUltimateHueAreaMotion.html +0 -44
  467. package/nodes/locales/it/knxUltimateHueAreaMotion.json +0 -31
  468. package/nodes/locales/it/knxUltimateHueBattery.html +0 -37
  469. package/nodes/locales/it/knxUltimateHueBattery.json +0 -30
  470. package/nodes/locales/it/knxUltimateHueButton.html +0 -52
  471. package/nodes/locales/it/knxUltimateHueButton.json +0 -34
  472. package/nodes/locales/it/knxUltimateHueCameraMotion.html +0 -38
  473. package/nodes/locales/it/knxUltimateHueCameraMotion.json +0 -30
  474. package/nodes/locales/it/knxUltimateHueContactSensor.html +0 -35
  475. package/nodes/locales/it/knxUltimateHueContactSensor.json +0 -26
  476. package/nodes/locales/it/knxUltimateHueController.html +0 -690
  477. package/nodes/locales/it/knxUltimateHueController.json +0 -64
  478. package/nodes/locales/it/knxUltimateHueHumiditySensor 2.html +0 -11
  479. package/nodes/locales/it/knxUltimateHueHumiditySensor.html +0 -38
  480. package/nodes/locales/it/knxUltimateHueHumiditySensor.json +0 -30
  481. package/nodes/locales/it/knxUltimateHueLight.html +0 -124
  482. package/nodes/locales/it/knxUltimateHueLight.json +0 -94
  483. package/nodes/locales/it/knxUltimateHueLightSensor.html +0 -38
  484. package/nodes/locales/it/knxUltimateHueLightSensor.json +0 -19
  485. package/nodes/locales/it/knxUltimateHueMotion.html +0 -41
  486. package/nodes/locales/it/knxUltimateHueMotion.json +0 -27
  487. package/nodes/locales/it/knxUltimateHuePlug.html +0 -50
  488. package/nodes/locales/it/knxUltimateHuePlug.json +0 -33
  489. package/nodes/locales/it/knxUltimateHueScene.html +0 -45
  490. package/nodes/locales/it/knxUltimateHueScene.json +0 -41
  491. package/nodes/locales/it/knxUltimateHueTapDial.html +0 -40
  492. package/nodes/locales/it/knxUltimateHueTapDial.json +0 -28
  493. package/nodes/locales/it/knxUltimateHueTemperatureSensor.html +0 -38
  494. package/nodes/locales/it/knxUltimateHueTemperatureSensor.json +0 -30
  495. package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.html +0 -39
  496. package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.json +0 -30
  497. package/nodes/locales/it/knxUltimateHuedevice_software_update.html +0 -35
  498. package/nodes/locales/it/knxUltimateHuedevice_software_update.json +0 -30
  499. package/nodes/locales/it/knxUltimateLoadControl.html +0 -76
  500. package/nodes/locales/it/knxUltimateLoadControl.json +0 -28
  501. package/nodes/locales/it/knxUltimateLogger.html +0 -132
  502. package/nodes/locales/it/knxUltimateLogger.json +0 -31
  503. package/nodes/locales/it/knxUltimateMatterBridge.html +0 -65
  504. package/nodes/locales/it/knxUltimateMatterBridge.json +0 -123
  505. package/nodes/locales/it/knxUltimateMatterControllerDevice.html +0 -57
  506. package/nodes/locales/it/knxUltimateMatterControllerDevice.json +0 -146
  507. package/nodes/locales/it/knxUltimateSceneController.html +0 -101
  508. package/nodes/locales/it/knxUltimateSceneController.json +0 -32
  509. package/nodes/locales/it/knxUltimateStaircase.html +0 -77
  510. package/nodes/locales/it/knxUltimateStaircase.json +0 -62
  511. package/nodes/locales/it/knxUltimateWatchDog.html +0 -194
  512. package/nodes/locales/it/knxUltimateWatchDog.json +0 -27
  513. package/nodes/locales/it/matter-config.html +0 -47
  514. package/nodes/locales/it/matter-config.json +0 -48
  515. package/nodes/locales/it/matterbridge-config.html +0 -45
  516. package/nodes/locales/it/matterbridge-config.json +0 -25
  517. package/nodes/locales/zh-CN/hue-config.html +0 -33
  518. package/nodes/locales/zh-CN/hue-config.json +0 -21
  519. package/nodes/locales/zh-CN/knxUltimateAI.html +0 -13
  520. package/nodes/locales/zh-CN/knxUltimateAI.json +0 -231
  521. package/nodes/locales/zh-CN/knxUltimateAIHomeAssistant.html +0 -11
  522. package/nodes/locales/zh-CN/knxUltimateAlerter.html +0 -159
  523. package/nodes/locales/zh-CN/knxUltimateAlerter.json +0 -27
  524. package/nodes/locales/zh-CN/knxUltimateAutoResponder.html +0 -94
  525. package/nodes/locales/zh-CN/knxUltimateAutoResponder.json +0 -8
  526. package/nodes/locales/zh-CN/knxUltimateDateTime.html +0 -45
  527. package/nodes/locales/zh-CN/knxUltimateDateTime.json +0 -32
  528. package/nodes/locales/zh-CN/knxUltimateGarage.html +0 -79
  529. package/nodes/locales/zh-CN/knxUltimateGarage.json +0 -51
  530. package/nodes/locales/zh-CN/knxUltimateGlobalContext.html +0 -77
  531. package/nodes/locales/zh-CN/knxUltimateGlobalContext.json +0 -24
  532. package/nodes/locales/zh-CN/knxUltimateHATranslator.html +0 -25
  533. package/nodes/locales/zh-CN/knxUltimateHATranslator.json +0 -8
  534. package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.html +0 -44
  535. package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.json +0 -31
  536. package/nodes/locales/zh-CN/knxUltimateHueBattery.html +0 -37
  537. package/nodes/locales/zh-CN/knxUltimateHueBattery.json +0 -30
  538. package/nodes/locales/zh-CN/knxUltimateHueButton.html +0 -52
  539. package/nodes/locales/zh-CN/knxUltimateHueButton.json +0 -34
  540. package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.html +0 -38
  541. package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.json +0 -30
  542. package/nodes/locales/zh-CN/knxUltimateHueContactSensor.html +0 -35
  543. package/nodes/locales/zh-CN/knxUltimateHueContactSensor.json +0 -26
  544. package/nodes/locales/zh-CN/knxUltimateHueController.html +0 -693
  545. package/nodes/locales/zh-CN/knxUltimateHueController.json +0 -64
  546. package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.html +0 -38
  547. package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.json +0 -30
  548. package/nodes/locales/zh-CN/knxUltimateHueLight.html +0 -126
  549. package/nodes/locales/zh-CN/knxUltimateHueLight.json +0 -86
  550. package/nodes/locales/zh-CN/knxUltimateHueLightSensor.html +0 -38
  551. package/nodes/locales/zh-CN/knxUltimateHueLightSensor.json +0 -9
  552. package/nodes/locales/zh-CN/knxUltimateHueMotion.html +0 -41
  553. package/nodes/locales/zh-CN/knxUltimateHueMotion.json +0 -27
  554. package/nodes/locales/zh-CN/knxUltimateHuePlug.html +0 -50
  555. package/nodes/locales/zh-CN/knxUltimateHuePlug.json +0 -33
  556. package/nodes/locales/zh-CN/knxUltimateHueScene.html +0 -45
  557. package/nodes/locales/zh-CN/knxUltimateHueScene.json +0 -41
  558. package/nodes/locales/zh-CN/knxUltimateHueTapDial.html +0 -40
  559. package/nodes/locales/zh-CN/knxUltimateHueTapDial.json +0 -28
  560. package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.html +0 -38
  561. package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.json +0 -30
  562. package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.html +0 -39
  563. package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.json +0 -30
  564. package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.html +0 -36
  565. package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.json +0 -30
  566. package/nodes/locales/zh-CN/knxUltimateLoadControl.html +0 -76
  567. package/nodes/locales/zh-CN/knxUltimateLoadControl.json +0 -28
  568. package/nodes/locales/zh-CN/knxUltimateLogger.html +0 -126
  569. package/nodes/locales/zh-CN/knxUltimateLogger.json +0 -31
  570. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +0 -65
  571. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +0 -123
  572. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +0 -57
  573. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +0 -146
  574. package/nodes/locales/zh-CN/knxUltimateSceneController.html +0 -98
  575. package/nodes/locales/zh-CN/knxUltimateSceneController.json +0 -32
  576. package/nodes/locales/zh-CN/knxUltimateStaircase.html +0 -77
  577. package/nodes/locales/zh-CN/knxUltimateStaircase.json +0 -62
  578. package/nodes/locales/zh-CN/knxUltimateWatchDog.html +0 -178
  579. package/nodes/locales/zh-CN/knxUltimateWatchDog.json +0 -27
  580. package/nodes/locales/zh-CN/matter-config.html +0 -47
  581. package/nodes/locales/zh-CN/matter-config.json +0 -47
  582. package/nodes/locales/zh-CN/matterbridge-config.html +0 -45
  583. package/nodes/locales/zh-CN/matterbridge-config.json +0 -25
  584. package/nodes/matter-config.html +0 -578
  585. package/nodes/matter-config.js +0 -387
  586. package/nodes/matterbridge-config.html +0 -198
  587. package/nodes/matterbridge-config.js +0 -261
  588. package/nodes/plugins/knxUltimate-cerebrum-runtime-plugin.js +0 -79
  589. package/nodes/plugins/knxUltimate-migration-notice-plugin.html +0 -44
  590. package/nodes/plugins/knxUltimateAI-vue/assets/app.css +0 -1
  591. package/nodes/plugins/knxUltimateAI-vue/assets/app.js +0 -13
  592. package/nodes/plugins/knxUltimateAI-vue/assets/chunk-html2canvas.esm.js +0 -5
  593. package/nodes/plugins/knxUltimateAI-vue/assets/chunk-index.es.js +0 -5
  594. package/nodes/plugins/knxUltimateAI-vue/assets/chunk-jspdf.es.min.js +0 -79
  595. package/nodes/plugins/knxUltimateAI-vue/assets/chunk-purify.es.js +0 -3
  596. package/nodes/plugins/knxUltimateAI-vue/index.html +0 -13
  597. package/nodes/utils/HueEventStream.mjs +0 -176
  598. package/nodes/utils/colorManipulators/XYFromRGB_Supergiovane.js +0 -113
  599. package/nodes/utils/colorManipulators/hueColorConverter.js +0 -521
  600. package/nodes/utils/http.js +0 -291
  601. package/nodes/utils/hueControllerProfileAdapter.js +0 -104
  602. package/nodes/utils/hueControllerProfiles/runtime/area_motion.js +0 -221
  603. package/nodes/utils/hueControllerProfiles/runtime/button.js +0 -295
  604. package/nodes/utils/hueControllerProfiles/runtime/camera_motion.js +0 -221
  605. package/nodes/utils/hueControllerProfiles/runtime/contact.js +0 -191
  606. package/nodes/utils/hueControllerProfiles/runtime/device_power.js +0 -185
  607. package/nodes/utils/hueControllerProfiles/runtime/device_software_update.js +0 -185
  608. package/nodes/utils/hueControllerProfiles/runtime/humidity.js +0 -204
  609. package/nodes/utils/hueControllerProfiles/runtime/light.js +0 -1585
  610. package/nodes/utils/hueControllerProfiles/runtime/light_level.js +0 -180
  611. package/nodes/utils/hueControllerProfiles/runtime/motion.js +0 -185
  612. package/nodes/utils/hueControllerProfiles/runtime/plug.js +0 -344
  613. package/nodes/utils/hueControllerProfiles/runtime/relative_rotary.js +0 -271
  614. package/nodes/utils/hueControllerProfiles/runtime/scene.js +0 -352
  615. package/nodes/utils/hueControllerProfiles/runtime/temperature.js +0 -187
  616. package/nodes/utils/hueControllerProfiles/runtime/zigbee_connectivity.js +0 -185
  617. package/nodes/utils/hueControllerResourceCatalog.js +0 -71
  618. package/nodes/utils/hueEngine.mjs +0 -469
  619. package/nodes/utils/knxAiCamera.js +0 -302
  620. package/nodes/utils/knxAiCatalogRetrieval.js +0 -349
  621. package/nodes/utils/knxAiCerebrum.js +0 -406
  622. package/nodes/utils/knxAiChatContext.js +0 -496
  623. package/nodes/utils/knxAiEventHistory.js +0 -521
  624. package/nodes/utils/knxAiHomeMemory.js +0 -1021
  625. package/nodes/utils/knxAiScheduler.js +0 -445
  626. package/nodes/utils/knxAiSemanticContext.js +0 -662
  627. package/nodes/utils/knxAiTelegramVoice.js +0 -545
  628. package/nodes/utils/knxAiWebAccess.js +0 -631
  629. package/nodes/utils/lightEngines/canonicalLight.js +0 -75
  630. package/nodes/utils/lightEngines/hueLightEngine.js +0 -51
  631. package/nodes/utils/lightEngines/index.js +0 -25
  632. package/nodes/utils/lightEngines/knxLightCommand.js +0 -79
  633. package/nodes/utils/lightEngines/lightEngine.js +0 -46
  634. package/nodes/utils/lightEngines/matterHueShim.js +0 -59
  635. package/nodes/utils/lightEngines/matterLightEngine.js +0 -117
  636. package/nodes/utils/matterBridgeDeviceFactory.mjs +0 -780
  637. package/nodes/utils/matterBridgeEngine.mjs +0 -297
  638. package/nodes/utils/matterControllerCommandGate.js +0 -47
  639. package/nodes/utils/matterControllerProfiles/doorLock.js +0 -258
  640. package/nodes/utils/matterControllerProfiles/fan.js +0 -19
  641. package/nodes/utils/matterControllerProfiles/index.js +0 -34
  642. package/nodes/utils/matterControllerProfiles/mappedEndpoint.js +0 -217
  643. package/nodes/utils/matterControllerProfiles/switch.js +0 -13
  644. package/nodes/utils/matterControllerProfiles/thermostat.js +0 -19
  645. package/nodes/utils/matterControllerProfiles/universal.js +0 -462
  646. package/nodes/utils/matterControllerProfiles/windowCovering.js +0 -19
  647. package/nodes/utils/matterControllerSemanticInput.js +0 -212
  648. package/nodes/utils/matterEngine.mjs +0 -894
  649. package/nodes/utils/matterEnvironmentLock.mjs +0 -32
  650. package/nodes/utils/matterKnxConverter.js +0 -204
  651. package/nodes/utils/matterStorageBackup.js +0 -100
  652. package/resources/HA-icon.png +0 -0
  653. package/resources/dim.png +0 -0
  654. package/resources/rgb.png +0 -0
  655. package/resources/tunablewhite.png +0 -0
  656. package/resources/zxing-browser-0.2.0.min.js +0 -1
  657. package/resources/zxing-browser-LICENSE.txt +0 -21
@@ -36,654 +36,1248 @@
36
36
  const PROFILE_FACTORIES = {
37
37
  "alerter": function (RED) {
38
38
  RED.nodes.registerType('knxUltimateAlerter', {
39
- category: "KNX Ultimate",
40
- color: '#C7E9C0',
41
- defaults: {
42
- server: { type: "knxUltimate-config", required: false },
43
- name: { value: "" },
44
- rules: { value: [] },
45
- whentostart: { value: "ifnewalert" },
46
- timerinterval: { value: "2" },
47
- initialreadGAInRules: { value: "1" },
48
- },
49
- inputs: 1,
50
- outputs: 3,
51
- outputLabels: function (index) {
52
- if (index === 0) return "Emits a message for each alerted device, at selectable intervals.";
53
- if (index === 1) return "Emits a unique message containing all alerted devices.";
54
- if (index === 2) return "Emits a message containing only the last alerted device.";
55
- },
56
- icon: "node-alerter-icon.svg",
57
- label: function () {
58
- return (this.outputRBE == true ? "|rbe| " : "") + (this.name || this.topic || "KNX Alerter") + (this.inputRBE == true ? " |rbe|" : "")
59
- },
60
- paletteLabel: "KNX Alerter",
61
- oneditprepare: function () {
62
- // Go to the help panel
63
- try {
64
- RED.sidebar.show("help");
65
- } catch (error) { }
66
-
67
- var node = this;
68
- var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
39
+ category: 'KNX Ultimate',
40
+ color: '#C7E9C0',
41
+ defaults: {
42
+ server: { type: 'knxUltimate-config', required: false },
43
+ name: { value: '' },
44
+ rules: { value: [] },
45
+ whentostart: { value: 'ifnewalert' },
46
+ timerinterval: { value: '2' },
47
+ initialreadGAInRules: { value: '1' }
48
+ },
49
+ inputs: 1,
50
+ outputs: 3,
51
+ outputLabels: function (index) {
52
+ if (index === 0) return 'Emits a message for each alerted device, at selectable intervals.'
53
+ if (index === 1) return 'Emits a unique message containing all alerted devices.'
54
+ if (index === 2) return 'Emits a message containing only the last alerted device.'
55
+ },
56
+ icon: 'node-alerter-icon.svg',
57
+ label: function () {
58
+ return (this.outputRBE == true ? '|rbe| ' : '') + (this.name || this.topic || 'KNX Alerter') + (this.inputRBE == true ? ' |rbe|' : '')
59
+ },
60
+ paletteLabel: 'KNX Alerter',
61
+ oneditprepare: function () {
62
+ // Go to the help panel
63
+ try {
64
+ RED.sidebar.show('help')
65
+ } catch (error) { }
69
66
 
70
- // 19/02/2020 Used to get the server sooner als deploy.
71
- $("#node-input-server").off('.knxUtilityProfile').on('change.knxUtilityProfile', function () {
72
- try {
73
- oNodeServer = RED.nodes.node($(this).val());
74
- } catch (error) { }
75
- });
67
+ const node = this
68
+ let oNodeServer = RED.nodes.node($('#node-input-server').val()) // Store the config-node
76
69
 
70
+ // 19/02/2020 Used to get the server sooner als deploy.
71
+ $('#node-input-server').off('.knxUtilityProfile').on('change.knxUtilityProfile', function () {
72
+ try {
73
+ oNodeServer = RED.nodes.node($(this).val())
74
+ } catch (error) { }
75
+ })
77
76
 
77
+ // Scene configuration
78
+ function resizeRule (rule) {
78
79
 
80
+ }
81
+ $('#node-input-rule-container').css('min-height', '350px').css('min-width', '450px').editableList({
82
+ addItem: function (container, i, opt) { // row, index, data
83
+ // opt.r is: { topic: rowRuleTopic, devicename: rowRuleDeviceName, longdevicename: rowRuleLongDeviceName}
79
84
 
80
- // Scene configuration
81
- function resizeRule(rule) {
85
+ const rule = opt.r || { topic: '', devicename: '', longdevicename: '' }
86
+ if (!opt.hasOwnProperty('i')) {
87
+ opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString()
88
+ }
89
+ container.css({
90
+ overflow: 'hidden',
91
+ whiteSpace: 'nowrap'
92
+ })
93
+ const row = $('<div class="form-row"/>').appendTo(container)
82
94
 
83
- }
84
- $("#node-input-rule-container").css('min-height', '350px').css('min-width', '450px').editableList({
85
- addItem: function (container, i, opt) { // row, index, data
95
+ const oTopicField = $('<input/>', { class: 'rowRuleTopic', type: 'text', placeholder: 'GA or devicename', style: 'width:20%; margin-left: 5px; text-align: left;' }).appendTo(row)
96
+ const finalspan = $('<span/>', { style: '' }).appendTo(row)
97
+ finalspan.append(' <span class="node-input-rule-index"></span> ')
98
+ const orowRuleDeviceName = $('<input/>', { maxlength: '14', class: 'rowRuleDeviceName', type: 'text', style: 'width:30%; margin-left: 0px; text-align: left;font-style: italic;', placeholder: 'Name (max 14 chars)' }).appendTo(row)
99
+ const orowRuleLongDeviceName = $('<input/>', { class: 'rowRuleLongDeviceName', type: 'text', style: 'width:45%; margin-left: 0px; text-align: left;', placeholder: 'Long name' }).appendTo(row)
86
100
 
87
- // opt.r is: { topic: rowRuleTopic, devicename: rowRuleDeviceName, longdevicename: rowRuleLongDeviceName}
101
+ oTopicField.on('change', function () {
102
+ resizeRule(container)
103
+ })
88
104
 
89
- var rule = opt.r || { topic: '', devicename: '', longdevicename: '' };
90
- if (!opt.hasOwnProperty('i')) {
91
- opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString();
92
- }
93
- container.css({
94
- overflow: 'hidden',
95
- whiteSpace: 'nowrap'
96
- });
97
- var row = $('<div class="form-row"/>').appendTo(container);
98
-
99
- var oTopicField = $("<input/>", { class: "rowRuleTopic", type: "text", placeholder: "GA or devicename", style: "width:20%; margin-left: 5px; text-align: left;" }).appendTo(row);
100
- var finalspan = $('<span/>', { style: "" }).appendTo(row);
101
- finalspan.append(' <span class="node-input-rule-index"></span> ');
102
- var orowRuleDeviceName = $('<input/>', { maxlength: "14", class: "rowRuleDeviceName", type: "text", style: "width:30%; margin-left: 0px; text-align: left;font-style: italic;", placeholder: "Name (max 14 chars)" }).appendTo(row);
103
- var orowRuleLongDeviceName = $('<input/>', { class: "rowRuleLongDeviceName", type: "text", style: "width:45%; margin-left: 0px; text-align: left;", placeholder: "Long name" }).appendTo(row);
104
-
105
- oTopicField.on("change", function () {
106
- resizeRule(container);
107
- });
108
-
109
-
110
- // Autocomplete suggestion with ETS csv File
111
- oTopicField.autocomplete({
112
- minLength: 0,
113
- source: function (request, response) {
114
- if (!oNodeServer || !oNodeServer.id) { response([]); return; }
115
- $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
116
- response($.map(data, function (value, key) {
117
- var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
118
- if (htmlUtilsfullCSVSearch(sSearch, request.term + " 1.")) {
119
- return {
120
- label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
121
- value: value.ga // Value
122
- }
123
- } else {
124
- return null;
125
- }
126
- }));
127
- });
128
- }, select: function (event, ui) {
129
- // Sets Datapoint and device name automatically
130
- var sDevName = ui.item.label.split("#")[1].trim();
131
- try {
132
- sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
133
- orowRuleDeviceName.val(sDevName.substr(0, 14));
134
- orowRuleLongDeviceName.val(sDevName);
135
- } catch (error) {
136
- }
137
-
138
- }
139
- });
140
- oTopicField.on('focus.knxUltimateAlerter click.knxUltimateAlerter', function () {
141
- try {
142
- $(this).autocomplete('search', '');
143
- } catch (error) { /* empty */ }
144
- });
145
- try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting(oTopicField, oNodeServer.id); } catch (e) {}
146
-
147
- oTopicField.val(rule.topic);
148
- orowRuleDeviceName.val(rule.devicename);
149
- orowRuleLongDeviceName.val(rule.longdevicename);
150
- oTopicField.change();
151
-
152
- },
153
- removeItem: function (opt) {
154
-
155
- },
156
- resizeItem: resizeRule,
157
- sortItems: function (rules) {
158
- },
159
- sortable: true,
160
- removable: true
161
- });
162
-
163
- // 10/03/2020 For each rule, create a row
164
- for (var i = 0; i < (this.rules || []).length; i++) {
165
- var rule = this.rules[i];
166
- $("#node-input-rule-container").editableList('addItem', { r: rule, i: i });
105
+ // Autocomplete suggestion with ETS csv File
106
+ oTopicField.autocomplete({
107
+ minLength: 0,
108
+ source: function (request, response) {
109
+ if (!oNodeServer || !oNodeServer.id) { response([]); return }
110
+ $.getJSON('knxUltimatecsv?nodeID=' + oNodeServer.id, (data) => {
111
+ response($.map(data, function (value, key) {
112
+ const sSearch = (value.ga + ' (' + value.devicename + ') DPT' + value.dpt)
113
+ if (htmlUtilsfullCSVSearch(sSearch, request.term + ' 1.')) {
114
+ return {
115
+ label: value.ga + ' # ' + value.devicename + ' # ' + value.dpt, // Label for Display
116
+ value: value.ga // Value
117
+ }
118
+ } else {
119
+ return null
120
+ }
121
+ }))
122
+ })
123
+ },
124
+ select: function (event, ui) {
125
+ // Sets Datapoint and device name automatically
126
+ let sDevName = ui.item.label.split('#')[1].trim()
127
+ try {
128
+ sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim()
129
+ orowRuleDeviceName.val(sDevName.substr(0, 14))
130
+ orowRuleLongDeviceName.val(sDevName)
131
+ } catch (error) {
167
132
  }
133
+ }
134
+ })
135
+ oTopicField.on('focus.knxUltimateAlerter click.knxUltimateAlerter', function () {
136
+ try {
137
+ $(this).autocomplete('search', '')
138
+ } catch (error) { /* empty */ }
139
+ })
140
+ try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting(oTopicField, oNodeServer.id) } catch (e) {}
168
141
 
142
+ oTopicField.val(rule.topic)
143
+ orowRuleDeviceName.val(rule.devicename)
144
+ orowRuleLongDeviceName.val(rule.longdevicename)
145
+ oTopicField.change()
146
+ },
147
+ removeItem: function (opt) {
169
148
 
170
- },
171
- oneditsave: function () {
172
- // Return to the info tab
173
- try {
174
- RED.sidebar.show("info");
175
- } catch (error) { }
176
-
177
- var node = this;
178
-
179
- var rules = $("#node-input-rule-container").editableList('items');
180
- node.rules = [];
181
- rules.each(function (i) {
182
- var rule = $(this);
183
- var rowRuleTopic = rule.find(".rowRuleTopic").val();
184
- var rowRuleDeviceName = rule.find(".rowRuleDeviceName").val();
185
- var rowRuleLongDeviceName = rule.find(".rowRuleLongDeviceName").val();
186
- node.rules.push({ topic: rowRuleTopic, devicename: rowRuleDeviceName, longdevicename: rowRuleLongDeviceName });
187
- });
188
- },
189
- oneditcancel: function () {
190
- $("#node-input-server").off('.knxUtilityProfile');
191
- try { RED.sidebar.show('info'); } catch (error) { }
192
- },
193
- oneditresize: function (size) {
194
- const list = $("#node-input-rule-container");
195
- const form = $('#dialog-form');
196
- const listTop = list.offset();
197
- const formTop = form.offset();
198
- if (!size || !listTop || !formTop) return;
199
- // The fragment is nested in Utility, so measure the actual list
200
- // offset instead of assuming it is a direct child of dialog-form.
201
- list.editableList('height', Math.max(180, size.height - (listTop.top - formTop.top) - 45));
202
- }
149
+ },
150
+ resizeItem: resizeRule,
151
+ sortItems: function (rules) {
152
+ },
153
+ sortable: true,
154
+ removable: true
155
+ })
156
+
157
+ // 10/03/2020 For each rule, create a row
158
+ for (let i = 0; i < (this.rules || []).length; i++) {
159
+ const rule = this.rules[i]
160
+ $('#node-input-rule-container').editableList('addItem', { r: rule, i })
161
+ }
162
+ },
163
+ oneditsave: function () {
164
+ // Return to the info tab
165
+ try {
166
+ RED.sidebar.show('info')
167
+ } catch (error) { }
168
+
169
+ const node = this
170
+
171
+ const rules = $('#node-input-rule-container').editableList('items')
172
+ node.rules = []
173
+ rules.each(function (i) {
174
+ const rule = $(this)
175
+ const rowRuleTopic = rule.find('.rowRuleTopic').val()
176
+ const rowRuleDeviceName = rule.find('.rowRuleDeviceName').val()
177
+ const rowRuleLongDeviceName = rule.find('.rowRuleLongDeviceName').val()
178
+ node.rules.push({ topic: rowRuleTopic, devicename: rowRuleDeviceName, longdevicename: rowRuleLongDeviceName })
203
179
  })
180
+ },
181
+ oneditcancel: function () {
182
+ $('#node-input-server').off('.knxUtilityProfile')
183
+ try { RED.sidebar.show('info') } catch (error) { }
184
+ },
185
+ oneditresize: function (size) {
186
+ const list = $('#node-input-rule-container')
187
+ const form = $('#dialog-form')
188
+ const listTop = list.offset()
189
+ const formTop = form.offset()
190
+ if (!size || !listTop || !formTop) return
191
+ // The fragment is nested in Utility, so measure the actual list
192
+ // offset instead of assuming it is a direct child of dialog-form.
193
+ list.editableList('height', Math.max(180, size.height - (listTop.top - formTop.top) - 45))
194
+ }
195
+ })
204
196
  },
205
197
  "autoresponder": function (RED) {
206
198
  RED.nodes.registerType('knxUltimateAutoResponder', {
207
- category: "KNX Ultimate",
208
- color: '#C7E9C0',
209
- defaults: {
210
- server: { type: "knxUltimate-config", required: true },
211
- name: { value: "Auto responder", required: false },
212
- commandText: {
213
- value: '[]', required: false,
214
- validate: function (value) {
215
- try {
216
- const entries = JSON.parse(value);
217
- return Array.isArray(entries) && entries.every((entry) => (
218
- entry && typeof entry === 'object' &&
199
+ category: 'KNX Ultimate',
200
+ color: '#C7E9C0',
201
+ defaults: {
202
+ server: { type: 'knxUltimate-config', required: true },
203
+ name: { value: 'Auto responder', required: false },
204
+ commandText: {
205
+ value: '[]',
206
+ required: false,
207
+ validate: function (value) {
208
+ try {
209
+ const entries = JSON.parse(value)
210
+ return Array.isArray(entries) && entries.every((entry) => (
211
+ entry && typeof entry === 'object' &&
219
212
  typeof entry.ga === 'string' && entry.ga.trim() !== '' &&
220
213
  Object.prototype.hasOwnProperty.call(entry, 'default')
221
- ));
222
- } catch (error) { return false; }
223
- }
224
- }
225
- },
226
- inputs: 0,
227
- outputs: 0,
228
- icon: "node-knx-icon.svg",
229
- label: function () {
230
- return (this.name || "KNX Auto Responder");
231
- },
232
- paletteLabel: "KNX Auto Responder",
233
- oneditprepare: function () {
234
- // Go to the help panel
235
- try {
236
- RED.sidebar.show("help");
237
- } catch (error) { }
238
-
239
- var node = this;
240
-
241
- $("#node-input-commandText").typedInput({
242
- type: "json",
243
- types: ["json"]
244
- })
245
-
246
- try {
247
- if (node.commandText !== undefined) {
248
- $("#node-input-commandText").typedInput('value', node.commandText)
249
- }
250
- } catch (error) { }
251
-
252
- },
253
- oneditsave: function () {
254
- // Return to the info tab
255
- try {
256
- RED.sidebar.show("info");
257
- } catch (error) { }
258
-
259
- try {
260
- this.commandText = $("#node-input-commandText").typedInput('value')
261
- } catch (error) { }
262
-
263
- },
264
- oneditcancel: function () {
265
- // Return to the info tab
266
- try {
267
- RED.sidebar.show("info");
268
- } catch (error) { }
214
+ ))
215
+ } catch (error) { return false }
216
+ }
217
+ }
218
+ },
219
+ inputs: 0,
220
+ outputs: 0,
221
+ icon: 'node-knx-icon.svg',
222
+ label: function () {
223
+ return (this.name || 'KNX Auto Responder')
224
+ },
225
+ paletteLabel: 'KNX Auto Responder',
226
+ oneditprepare: function () {
227
+ // Go to the help panel
228
+ try {
229
+ RED.sidebar.show('help')
230
+ } catch (error) { }
269
231
 
232
+ const node = this
270
233
 
271
- }
272
- })
234
+ $('#node-input-commandText').typedInput({
235
+ type: 'json',
236
+ types: ['json']
237
+ })
238
+
239
+ try {
240
+ if (node.commandText !== undefined) {
241
+ $('#node-input-commandText').typedInput('value', node.commandText)
242
+ }
243
+ } catch (error) { }
244
+ },
245
+ oneditsave: function () {
246
+ // Return to the info tab
247
+ try {
248
+ RED.sidebar.show('info')
249
+ } catch (error) { }
250
+
251
+ try {
252
+ this.commandText = $('#node-input-commandText').typedInput('value')
253
+ } catch (error) { }
254
+ },
255
+ oneditcancel: function () {
256
+ // Return to the info tab
257
+ try {
258
+ RED.sidebar.show('info')
259
+ } catch (error) { }
260
+ }
261
+ })
273
262
  },
274
263
  "datetime": function (RED) {
275
264
  const KNX_ULTIMATE_DATETIME_KEYWORDS = {
276
- datetime: [/date\s*\/\s*time/i, /date\s*time/i, /datetime/i, /data\s*\/\s*ora/i, /data\s*ora/i],
277
- date: [/\bdate\b/i, /\bdata\b/i, /\bdatum\b/i, /\bfecha\b/i],
278
- time: [/\btime\b/i, /\bora\b/i, /\bheure\b/i, /\bzeit\b/i, /\bhora\b/i, /\borologio\b/i, /\bclock\b/i]
279
- }
265
+ datetime: [/date\s*\/\s*time/i, /date\s*time/i, /datetime/i, /data\s*\/\s*ora/i, /data\s*ora/i],
266
+ date: [/\bdate\b/i, /\bdata\b/i, /\bdatum\b/i, /\bfecha\b/i],
267
+ time: [/\btime\b/i, /\bora\b/i, /\bheure\b/i, /\bzeit\b/i, /\bhora\b/i, /\borologio\b/i, /\bclock\b/i]
268
+ }
280
269
 
281
- const KNX_ULTIMATE_DATETIME_STOPWORDS = new Set([
282
- 'date', 'datetime', 'time', 'data', 'ora', 'orologio', 'clock', 'bus', 'knx', 'set', 'sync', 'sincro', 'synchronization'
283
- ])
284
-
285
- const knxDateTimeNormalizeTokens = (value) => {
286
- const str = (value || '').toString().toLowerCase()
287
- const cleaned = str
288
- .replace(/dpt\s*\d+(\.\d+)?/g, ' ')
289
- .replace(/[^a-z0-9]+/g, ' ')
290
- .trim()
291
- if (!cleaned) return []
292
- return cleaned
293
- .split(/\s+/)
294
- .map((t) => t.trim())
295
- .filter((t) => t.length > 1 && !KNX_ULTIMATE_DATETIME_STOPWORDS.has(t))
296
- }
270
+ const KNX_ULTIMATE_DATETIME_STOPWORDS = new Set([
271
+ 'date', 'datetime', 'time', 'data', 'ora', 'orologio', 'clock', 'bus', 'knx', 'set', 'sync', 'sincro', 'synchronization'
272
+ ])
273
+
274
+ const knxDateTimeNormalizeTokens = (value) => {
275
+ const str = (value || '').toString().toLowerCase()
276
+ const cleaned = str
277
+ .replace(/dpt\s*\d+(\.\d+)?/g, ' ')
278
+ .replace(/[^a-z0-9]+/g, ' ')
279
+ .trim()
280
+ if (!cleaned) return []
281
+ return cleaned
282
+ .split(/\s+/)
283
+ .map((t) => t.trim())
284
+ .filter((t) => t.length > 1 && !KNX_ULTIMATE_DATETIME_STOPWORDS.has(t))
285
+ }
297
286
 
298
- const knxDateTimeTokenSimilarity = (aTokens, bTokens) => {
299
- if (!Array.isArray(aTokens) || !Array.isArray(bTokens) || aTokens.length === 0 || bTokens.length === 0) return 0
300
- const a = new Set(aTokens)
301
- const b = new Set(bTokens)
302
- let inter = 0
303
- a.forEach((t) => { if (b.has(t)) inter += 1 })
304
- const union = a.size + b.size - inter
305
- return union > 0 ? inter / union : 0
306
- }
287
+ const knxDateTimeTokenSimilarity = (aTokens, bTokens) => {
288
+ if (!Array.isArray(aTokens) || !Array.isArray(bTokens) || aTokens.length === 0 || bTokens.length === 0) return 0
289
+ const a = new Set(aTokens)
290
+ const b = new Set(bTokens)
291
+ let inter = 0
292
+ a.forEach((t) => { if (b.has(t)) inter += 1 })
293
+ const union = a.size + b.size - inter
294
+ return union > 0 ? inter / union : 0
295
+ }
307
296
 
308
- const knxDateTimeParseGA = (ga) => {
309
- const parts = (ga || '').toString().trim().split('/')
310
- if (parts.length !== 3) return null
311
- const nums = parts.map((p) => Number(p))
312
- if (nums.some((n) => !Number.isInteger(n) || n < 0)) return null
313
- return nums
314
- }
297
+ const knxDateTimeParseGA = (ga) => {
298
+ const parts = (ga || '').toString().trim().split('/')
299
+ if (parts.length !== 3) return null
300
+ const nums = parts.map((p) => Number(p))
301
+ if (nums.some((n) => !Number.isInteger(n) || n < 0)) return null
302
+ return nums
303
+ }
315
304
 
316
- const knxDateTimeCompareGA = (a, b) => {
317
- const aa = knxDateTimeParseGA(a)
318
- const bb = knxDateTimeParseGA(b)
319
- if (!aa && !bb) return 0
320
- if (!aa) return 1
321
- if (!bb) return -1
322
- for (let i = 0; i < 3; i++) {
323
- if (aa[i] !== bb[i]) return aa[i] - bb[i]
324
- }
325
- return 0
305
+ const knxDateTimeCompareGA = (a, b) => {
306
+ const aa = knxDateTimeParseGA(a)
307
+ const bb = knxDateTimeParseGA(b)
308
+ if (!aa && !bb) return 0
309
+ if (!aa) return 1
310
+ if (!bb) return -1
311
+ for (let i = 0; i < 3; i++) {
312
+ if (aa[i] !== bb[i]) return aa[i] - bb[i]
326
313
  }
314
+ return 0
315
+ }
327
316
 
328
- const knxGetKnxUltimateConfigs = () => {
329
- const configs = []
330
- try {
331
- if (RED && RED.nodes) {
332
- if (typeof RED.nodes.eachConfig === 'function') {
333
- RED.nodes.eachConfig((cfg) => {
334
- if (cfg && cfg.type === 'knxUltimate-config') configs.push(cfg)
335
- })
336
- } else if (typeof RED.nodes.eachNode === 'function') {
337
- RED.nodes.eachNode((n) => {
338
- if (n && n.type === 'knxUltimate-config') configs.push(n)
339
- })
340
- }
341
- if (configs.length === 0 && typeof RED.nodes.filterNodes === 'function') {
342
- try {
343
- const filtered = RED.nodes.filterNodes({ type: 'knxUltimate-config' })
344
- if (Array.isArray(filtered)) filtered.forEach((n) => configs.push(n))
345
- } catch (error) { /* ignore */ }
346
- }
317
+ const knxGetKnxUltimateConfigs = () => {
318
+ const configs = []
319
+ try {
320
+ if (RED && RED.nodes) {
321
+ if (typeof RED.nodes.eachConfig === 'function') {
322
+ RED.nodes.eachConfig((cfg) => {
323
+ if (cfg && cfg.type === 'knxUltimate-config') configs.push(cfg)
324
+ })
325
+ } else if (typeof RED.nodes.eachNode === 'function') {
326
+ RED.nodes.eachNode((n) => {
327
+ if (n && n.type === 'knxUltimate-config') configs.push(n)
328
+ })
347
329
  }
348
- } catch (error) { /* ignore */ }
349
- return configs
330
+ if (configs.length === 0 && typeof RED.nodes.filterNodes === 'function') {
331
+ try {
332
+ const filtered = RED.nodes.filterNodes({ type: 'knxUltimate-config' })
333
+ if (Array.isArray(filtered)) filtered.forEach((n) => configs.push(n))
334
+ } catch (error) { /* ignore */ }
335
+ }
336
+ }
337
+ } catch (error) { /* ignore */ }
338
+ return configs
339
+ }
340
+
341
+ const knxFetchGroupAddresses = (serverId) => {
342
+ return new Promise((resolve) => {
343
+ if (!serverId) return resolve([])
344
+ $.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
345
+ resolve(Array.isArray(data) ? data : [])
346
+ }).fail(() => resolve([]))
347
+ })
348
+ }
349
+
350
+ const knxScoreEntry = (entry, kind, baseTokens) => {
351
+ if (!entry) return -1
352
+ const dpt = (entry.dpt || '').toString()
353
+ const name = (entry.devicename || '').toString()
354
+ const tokens = knxDateTimeNormalizeTokens(name)
355
+
356
+ let score = 0
357
+
358
+ if (kind === 'datetime') {
359
+ if (dpt === '19.001') score += 60
360
+ else if (dpt.startsWith('19.')) score += 45
361
+ KNX_ULTIMATE_DATETIME_KEYWORDS.datetime.forEach((re) => { if (re.test(name)) score += 20 })
362
+ KNX_ULTIMATE_DATETIME_KEYWORDS.time.forEach((re) => { if (re.test(name)) score += 6 })
363
+ KNX_ULTIMATE_DATETIME_KEYWORDS.date.forEach((re) => { if (re.test(name)) score += 6 })
364
+ } else if (kind === 'date') {
365
+ if (dpt === '11.001') score += 60
366
+ else if (dpt.startsWith('11.')) score += 45
367
+ KNX_ULTIMATE_DATETIME_KEYWORDS.date.forEach((re) => { if (re.test(name)) score += 18 })
368
+ } else if (kind === 'time') {
369
+ if (dpt === '10.001') score += 60
370
+ else if (dpt.startsWith('10.')) score += 45
371
+ KNX_ULTIMATE_DATETIME_KEYWORDS.time.forEach((re) => { if (re.test(name)) score += 18 })
350
372
  }
351
373
 
352
- const knxFetchGroupAddresses = (serverId) => {
353
- return new Promise((resolve) => {
354
- if (!serverId) return resolve([])
355
- $.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
356
- resolve(Array.isArray(data) ? data : [])
357
- }).fail(() => resolve([]))
358
- })
374
+ if (Array.isArray(baseTokens) && baseTokens.length > 0) {
375
+ score += Math.round(knxDateTimeTokenSimilarity(tokens, baseTokens) * 30)
359
376
  }
360
377
 
361
- const knxScoreEntry = (entry, kind, baseTokens) => {
362
- if (!entry) return -1
363
- const dpt = (entry.dpt || '').toString()
364
- const name = (entry.devicename || '').toString()
365
- const tokens = knxDateTimeNormalizeTokens(name)
366
-
367
- let score = 0
368
-
369
- if (kind === 'datetime') {
370
- if (dpt === '19.001') score += 60
371
- else if (dpt.startsWith('19.')) score += 45
372
- KNX_ULTIMATE_DATETIME_KEYWORDS.datetime.forEach((re) => { if (re.test(name)) score += 20 })
373
- KNX_ULTIMATE_DATETIME_KEYWORDS.time.forEach((re) => { if (re.test(name)) score += 6 })
374
- KNX_ULTIMATE_DATETIME_KEYWORDS.date.forEach((re) => { if (re.test(name)) score += 6 })
375
- } else if (kind === 'date') {
376
- if (dpt === '11.001') score += 60
377
- else if (dpt.startsWith('11.')) score += 45
378
- KNX_ULTIMATE_DATETIME_KEYWORDS.date.forEach((re) => { if (re.test(name)) score += 18 })
379
- } else if (kind === 'time') {
380
- if (dpt === '10.001') score += 60
381
- else if (dpt.startsWith('10.')) score += 45
382
- KNX_ULTIMATE_DATETIME_KEYWORDS.time.forEach((re) => { if (re.test(name)) score += 18 })
383
- }
378
+ return score
379
+ }
384
380
 
385
- if (Array.isArray(baseTokens) && baseTokens.length > 0) {
386
- score += Math.round(knxDateTimeTokenSimilarity(tokens, baseTokens) * 30)
381
+ const knxPickBest = (entries, kind, baseTokens) => {
382
+ if (!Array.isArray(entries) || entries.length === 0) return null
383
+ let best = null
384
+ let bestScore = -1
385
+ entries.forEach((e) => {
386
+ const s = knxScoreEntry(e, kind, baseTokens)
387
+ if (s > bestScore) {
388
+ bestScore = s
389
+ best = e
390
+ } else if (s === bestScore && best) {
391
+ const cmp = knxDateTimeCompareGA(e.ga, best.ga)
392
+ if (cmp < 0) best = e
387
393
  }
394
+ })
395
+ return best
396
+ }
388
397
 
389
- return score
398
+ const knxSuggestFromCsv = (csvRows) => {
399
+ const rows = Array.isArray(csvRows) ? csvRows : []
400
+ const datetimeRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('19.')))
401
+ const dateRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('11.')))
402
+ const timeRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('10.')))
403
+
404
+ const bestDateTime = knxPickBest(datetimeRows, 'datetime', [])
405
+ const baseTokens = bestDateTime ? knxDateTimeNormalizeTokens(bestDateTime.devicename || '') : []
406
+
407
+ const bestDate = knxPickBest(dateRows, 'date', baseTokens)
408
+ const bestTime = knxPickBest(timeRows, 'time', baseTokens)
409
+
410
+ return {
411
+ dateTime: bestDateTime,
412
+ date: bestDate,
413
+ time: bestTime
390
414
  }
415
+ }
391
416
 
392
- const knxPickBest = (entries, kind, baseTokens) => {
393
- if (!Array.isArray(entries) || entries.length === 0) return null
394
- let best = null
395
- let bestScore = -1
396
- entries.forEach((e) => {
397
- const s = knxScoreEntry(e, kind, baseTokens)
398
- if (s > bestScore) {
399
- bestScore = s
400
- best = e
401
- } else if (s === bestScore && best) {
402
- const cmp = knxDateTimeCompareGA(e.ga, best.ga)
403
- if (cmp < 0) best = e
417
+ const knxAutoConfigureDateTimeNode = async (node, { updateDom = false, preferExistingServer = true, canApply = () => true } = {}) => {
418
+ try {
419
+ if (!node || !canApply()) return
420
+
421
+ const hasAnyGA = !!((node.gaDateTime || '').trim() || (node.gaDate || '').trim() || (node.gaTime || '').trim())
422
+ if (hasAnyGA) return
423
+
424
+ // If server already selected and it has ETS rows, reuse it.
425
+ const currentServerId = preferExistingServer ? (node.server || '') : ''
426
+ if (currentServerId && currentServerId !== '_ADD_') {
427
+ const rows = await knxFetchGroupAddresses(currentServerId)
428
+ if (rows.length > 0) {
429
+ const suggestions = knxSuggestFromCsv(rows)
430
+ return knxApplySuggestions(node, currentServerId, suggestions, { updateDom, canApply })
404
431
  }
432
+ }
433
+
434
+ // Otherwise, select the first knxUltimate-config that has an ETS CSV imported (non-empty parsed GA list).
435
+ const configs = knxGetKnxUltimateConfigs()
436
+ if (configs.length === 0) return
437
+
438
+ // Fast path: config node already carries an ETS file/path in its `csv` property.
439
+ for (let i = 0; i < configs.length; i++) {
440
+ const cfg = configs[i]
441
+ const id = cfg && cfg.id ? cfg.id : null
442
+ const csvHint = cfg && typeof cfg.csv === 'string' ? cfg.csv.trim() : ''
443
+ if (!id || !csvHint) continue
444
+ const rows = await knxFetchGroupAddresses(id)
445
+ if (rows.length === 0) continue
446
+ const suggestions = knxSuggestFromCsv(rows)
447
+ return knxApplySuggestions(node, id, suggestions, { updateDom, canApply })
448
+ }
449
+
450
+ const maxCandidates = Math.min(10, configs.length)
451
+ const checks = configs.slice(0, maxCandidates).map((cfg) => {
452
+ const id = cfg && cfg.id ? cfg.id : null
453
+ return knxFetchGroupAddresses(id).then((rows) => ({ id, rows }))
405
454
  })
406
- return best
455
+ const results = await Promise.all(checks)
456
+
457
+ let selected = null
458
+ for (let i = 0; i < results.length; i++) {
459
+ if (results[i] && results[i].id && Array.isArray(results[i].rows) && results[i].rows.length > 0) {
460
+ selected = results[i]
461
+ break
462
+ }
463
+ }
464
+ if (!selected) return
465
+
466
+ const suggestions = knxSuggestFromCsv(selected.rows)
467
+ return knxApplySuggestions(node, selected.id, suggestions, { updateDom, canApply })
468
+ } catch (error) {
469
+ try { console.warn('knxUltimateDateTime auto-config failed', error) } catch (e) { /* ignore */ }
407
470
  }
471
+ }
472
+
473
+ const knxAutoConfigureDateTimeNodeForServer = async (node, serverId, { updateDom = false, overwrite = false, canApply = () => true } = {}) => {
474
+ try {
475
+ if (!node || !serverId || !canApply()) return
476
+ const rows = await knxFetchGroupAddresses(serverId)
477
+ if (!Array.isArray(rows) || rows.length === 0) return
478
+ const suggestions = knxSuggestFromCsv(rows)
479
+ return knxApplySuggestions(node, serverId, suggestions, { updateDom, overwrite, canApply })
480
+ } catch (error) {
481
+ try { console.warn('knxUltimateDateTime auto-config for server failed', error) } catch (e) { /* ignore */ }
482
+ }
483
+ }
408
484
 
409
- const knxSuggestFromCsv = (csvRows) => {
410
- const rows = Array.isArray(csvRows) ? csvRows : []
411
- const datetimeRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('19.')))
412
- const dateRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('11.')))
413
- const timeRows = rows.filter((r) => (r && typeof r.dpt === 'string' && r.dpt.startsWith('10.')))
485
+ const knxApplySuggestions = (node, serverId, suggestions, { updateDom = false, overwrite = false, canApply = () => true } = {}) => {
486
+ // The Utility wrapper can replace this fragment while ETS requests are in
487
+ // flight. Never let a late response update its successor's fields.
488
+ if (!node || !serverId || !canApply()) return
489
+ if (!suggestions) return
414
490
 
415
- const bestDateTime = knxPickBest(datetimeRows, 'datetime', [])
416
- const baseTokens = bestDateTime ? knxDateTimeNormalizeTokens(bestDateTime.devicename || '') : []
491
+ // Avoid repeating the same automation multiple times.
492
+ if (node._knxDateTimeAutoConfigured === true && overwrite !== true) return
417
493
 
418
- const bestDate = knxPickBest(dateRows, 'date', baseTokens)
419
- const bestTime = knxPickBest(timeRows, 'time', baseTokens)
494
+ node.server = serverId
420
495
 
421
- return {
422
- dateTime: bestDateTime,
423
- date: bestDate,
424
- time: bestTime
425
- }
496
+ if (suggestions.dateTime && suggestions.dateTime.ga && (overwrite || !(node.gaDateTime || '').trim())) {
497
+ node.gaDateTime = suggestions.dateTime.ga
498
+ node.nameDateTime = suggestions.dateTime.devicename || ''
499
+ }
500
+ if (suggestions.date && suggestions.date.ga && (overwrite || !(node.gaDate || '').trim())) {
501
+ node.gaDate = suggestions.date.ga
502
+ node.nameDate = suggestions.date.devicename || ''
503
+ }
504
+ if (suggestions.time && suggestions.time.ga && (overwrite || !(node.gaTime || '').trim())) {
505
+ node.gaTime = suggestions.time.ga
506
+ node.nameTime = suggestions.time.devicename || ''
507
+ }
508
+
509
+ node._knxDateTimeAutoConfigured = true
510
+ node._knxDateTimeAutoConfiguredServer = serverId
511
+
512
+ if (updateDom) {
513
+ try {
514
+ $('#node-input-server').val(serverId).trigger('change')
515
+ if (node.gaDateTime) $('#node-input-gaDateTime').val(node.gaDateTime)
516
+ if (node.nameDateTime) $('#node-input-nameDateTime').val(node.nameDateTime)
517
+ if (node.gaDate) $('#node-input-gaDate').val(node.gaDate)
518
+ if (node.nameDate) $('#node-input-nameDate').val(node.nameDate)
519
+ if (node.gaTime) $('#node-input-gaTime').val(node.gaTime)
520
+ if (node.nameTime) $('#node-input-nameTime').val(node.nameTime)
521
+ } catch (error) { /* ignore */ }
522
+ } else {
523
+ try {
524
+ if (RED && RED.nodes && typeof RED.nodes.dirty === 'function') RED.nodes.dirty(true)
525
+ } catch (error) { /* ignore */ }
526
+ try { if (RED && RED.view && typeof RED.view.redraw === 'function') RED.view.redraw() } catch (error) { /* ignore */ }
527
+ }
528
+ }
529
+
530
+ const knxDateTimeOptionalNumber = (toggle) => function (value) {
531
+ let enabled = this[toggle] === undefined || this[toggle] === true || this[toggle] === 'true'
532
+ // The wrapper validates the saved node while its draft is open. Read the
533
+ // active checkbox only in that case; unrelated nodes use their saved flag.
534
+ if (this._utilityEditor && typeof $ === 'function') {
535
+ const field = $('#node-input-' + toggle)
536
+ if (field.length) enabled = field.is(':checked')
426
537
  }
538
+ return !enabled || RED.validators.number()(value)
539
+ }
427
540
 
428
- const knxAutoConfigureDateTimeNode = async (node, { updateDom = false, preferExistingServer = true, canApply = () => true } = {}) => {
541
+ RED.nodes.registerType('knxUltimateDateTime', {
542
+ category: 'KNX Ultimate',
543
+ color: '#C7E9C0',
544
+ defaults: {
545
+ server: { type: 'knxUltimate-config', required: true },
546
+ name: { value: '' },
547
+ outputtopic: { value: '' },
548
+ gaDateTime: { value: '' },
549
+ nameDateTime: { value: '' },
550
+ dptDateTime: { value: '19.001' },
551
+ gaDate: { value: '' },
552
+ nameDate: { value: '' },
553
+ dptDate: { value: '11.001' },
554
+ gaTime: { value: '' },
555
+ nameTime: { value: '' },
556
+ dptTime: { value: '10.001' },
557
+ sendOnDeploy: { value: true },
558
+ sendOnDeployDelay: { value: 30, validate: knxDateTimeOptionalNumber('sendOnDeploy') },
559
+ periodicSend: { value: true },
560
+ periodicSendInterval: { value: 60, validate: knxDateTimeOptionalNumber('periodicSend') },
561
+ periodicSendUnit: { value: 'm' }
562
+ },
563
+ inputs: 0,
564
+ outputs: 0,
565
+ icon: 'node-knx-icon.svg',
566
+ label: function () {
567
+ return this.name || 'KNX DateTime'
568
+ },
569
+ paletteLabel: function () {
429
570
  try {
430
- if (!node || !canApply()) return
431
-
432
- const hasAnyGA = !!((node.gaDateTime || '').trim() || (node.gaDate || '').trim() || (node.gaTime || '').trim())
433
- if (hasAnyGA) return
434
-
435
- // If server already selected and it has ETS rows, reuse it.
436
- const currentServerId = preferExistingServer ? (node.server || '') : ''
437
- if (currentServerId && currentServerId !== '_ADD_') {
438
- const rows = await knxFetchGroupAddresses(currentServerId)
439
- if (rows.length > 0) {
440
- const suggestions = knxSuggestFromCsv(rows)
441
- return knxApplySuggestions(node, currentServerId, suggestions, { updateDom, canApply })
571
+ return RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.paletteLabel') || 'DateTime'
572
+ } catch (error) {
573
+ return 'DateTime'
574
+ }
575
+ },
576
+ onadd: function () {
577
+ // Auto-select a KNX gateway (first one with ETS CSV imported) and prefill coherent group addresses.
578
+ // This runs when the node is dragged from the palette. Best-effort, non-blocking.
579
+ const node = this
580
+ setTimeout(() => {
581
+ knxAutoConfigureDateTimeNode(node, {
582
+ updateDom: false,
583
+ preferExistingServer: true,
584
+ canApply: () => node.utilityType === 'datetime'
585
+ })
586
+ }, 50)
587
+ },
588
+ button: {
589
+ enabled: function () {
590
+ return !this.changed
591
+ },
592
+ visible: function () {
593
+ return true
594
+ },
595
+ onclick: function () {
596
+ const node = this
597
+ $.ajax({
598
+ type: 'POST',
599
+ url: 'knxUltimateUtility/sendNow',
600
+ data: { id: node.id },
601
+ success: function (response) {
602
+ const queued = response && response.queued === true
603
+ const message = queued
604
+ ? (RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.notifyQueued') || 'Queued (gateway not connected yet)')
605
+ : (RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.notifySent') || 'Sent to KNX')
606
+ RED.notify(message, 'success')
607
+ },
608
+ error: function (xhr) {
609
+ let message = 'Error'
610
+ try {
611
+ if (xhr && xhr.responseJSON && xhr.responseJSON.error) message = xhr.responseJSON.error
612
+ } catch (error) { /* ignore */ }
613
+ RED.notify(message, 'error')
442
614
  }
443
- }
615
+ })
616
+ }
617
+ },
618
+ oneditprepare: function () {
619
+ const node = this
620
+ if (node._knxDateTimeEditorSession) node._knxDateTimeEditorSession.active = false
621
+ const editorSession = { active: true }
622
+ node._knxDateTimeEditorSession = editorSession
623
+ const $knxServerInput = $('#node-input-server')
624
+ const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none'])
625
+ const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map())
626
+
627
+ try { RED.sidebar.show('help') } catch (error) { /* ignore */ }
628
+
629
+ const resolveKnxServerValue = () => {
630
+ const domValue = $knxServerInput.val()
631
+ if (domValue !== undefined && domValue !== null && domValue !== '') return domValue
632
+ if (node.server !== undefined && node.server !== null && node.server !== '') return node.server
633
+ return ''
634
+ }
444
635
 
445
- // Otherwise, select the first knxUltimate-config that has an ETS CSV imported (non-empty parsed GA list).
446
- const configs = knxGetKnxUltimateConfigs()
447
- if (configs.length === 0) return
448
-
449
- // Fast path: config node already carries an ETS file/path in its `csv` property.
450
- for (let i = 0; i < configs.length; i++) {
451
- const cfg = configs[i]
452
- const id = cfg && cfg.id ? cfg.id : null
453
- const csvHint = cfg && typeof cfg.csv === 'string' ? cfg.csv.trim() : ''
454
- if (!id || !csvHint) continue
455
- const rows = await knxFetchGroupAddresses(id)
456
- if (rows.length === 0) continue
457
- const suggestions = knxSuggestFromCsv(rows)
458
- return knxApplySuggestions(node, id, suggestions, { updateDom, canApply })
459
- }
636
+ const addressFields = ['gaDateTime', 'nameDateTime', 'dptDateTime', 'gaDate', 'nameDate', 'dptDate', 'gaTime', 'nameTime', 'dptTime']
637
+ const addressSnapshot = () => JSON.stringify(addressFields.map((field) => $('#node-input-' + field).val() || ''))
638
+
639
+ const hasKnxServerSelected = () => {
640
+ const val = resolveKnxServerValue()
641
+ return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)))
642
+ }
460
643
 
461
- const maxCandidates = Math.min(10, configs.length)
462
- const checks = configs.slice(0, maxCandidates).map((cfg) => {
463
- const id = cfg && cfg.id ? cfg.id : null
464
- return knxFetchGroupAddresses(id).then((rows) => ({ id, rows }))
644
+ const fetchGroupAddresses = (serverId) => {
645
+ if (!serverId) return Promise.resolve([])
646
+ if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId))
647
+ return new Promise((resolve) => {
648
+ $.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
649
+ const list = Array.isArray(data) ? data : []
650
+ KNX_GA_CACHE.set(serverId, list)
651
+ resolve(list)
652
+ }).fail(() => resolve([]))
465
653
  })
466
- const results = await Promise.all(checks)
654
+ }
655
+
656
+ const setupGA = (gaSelector, nameSelector, dptSelector, allowedPrefixes, defaultDpt) => {
657
+ const $gaInput = $(gaSelector)
658
+ const $nameInput = $(nameSelector)
659
+ const $dptInput = $(dptSelector)
660
+ if (!$gaInput.length) return
467
661
 
468
- let selected = null
469
- for (let i = 0; i < results.length; i++) {
470
- if (results[i] && results[i].id && Array.isArray(results[i].rows) && results[i].rows.length > 0) {
471
- selected = results[i]
472
- break
662
+ if ($dptInput.length && (!$dptInput.val() || $dptInput.val() === '')) $dptInput.val(defaultDpt)
663
+
664
+ const sourceFn = (request, response) => {
665
+ if (!hasKnxServerSelected()) {
666
+ response([])
667
+ return
473
668
  }
669
+ const serverId = resolveKnxServerValue()
670
+ fetchGroupAddresses(serverId).then((data) => {
671
+ if (!editorSession.active || resolveKnxServerValue() !== serverId) return
672
+ const items = []
673
+ data.forEach((entry) => {
674
+ const dpt = entry.dpt || ''
675
+ const allowed = allowedPrefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix))
676
+ if (!allowed) return
677
+ const devName = entry.devicename || ''
678
+ const searchStr = `${entry.ga} (${devName}) DPT${dpt}`
679
+ if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return
680
+ items.push({
681
+ label: `${entry.ga} # ${devName} # ${dpt}`,
682
+ value: entry.ga,
683
+ ga: entry.ga
684
+ })
685
+ })
686
+ response(items)
687
+ })
474
688
  }
475
- if (!selected) return
476
689
 
477
- const suggestions = knxSuggestFromCsv(selected.rows)
478
- return knxApplySuggestions(node, selected.id, suggestions, { updateDom, canApply })
479
- } catch (error) {
480
- try { console.warn('knxUltimateDateTime auto-config failed', error) } catch (e) { /* ignore */ }
690
+ if ($gaInput.data('knx-ga-initialised')) {
691
+ $gaInput.autocomplete('option', 'source', sourceFn)
692
+ } else {
693
+ $gaInput
694
+ .autocomplete({
695
+ minLength: 0,
696
+ source: sourceFn,
697
+ select: (event, ui) => {
698
+ let deviceName = ''
699
+ try {
700
+ deviceName = ui.item.label.split('#')[1].trim()
701
+ } catch (error) { deviceName = '' }
702
+ if ($nameInput.length) {
703
+ $nameInput.val(deviceName || '')
704
+ }
705
+ try {
706
+ const parts = ui.item.label.split('#')
707
+ const dptFromLabel = parts.length >= 3 ? parts[2].trim() : ''
708
+ $dptInput.val(dptFromLabel || defaultDpt)
709
+ } catch (error) {
710
+ $dptInput.val(defaultDpt)
711
+ }
712
+ }
713
+ })
714
+ .on('focus.knxUltimateDateTime click.knxUltimateDateTime', function () {
715
+ const currentValue = $(this).val() || ''
716
+ try { $(this).autocomplete('search', `${currentValue} exactmatch`) } catch (error) { /* ignore */ }
717
+ })
718
+ $gaInput.data('knx-ga-initialised', true)
719
+ }
720
+
721
+ try {
722
+ if (hasKnxServerSelected()) {
723
+ const srv = RED.nodes.node(resolveKnxServerValue())
724
+ if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id)
725
+ }
726
+ } catch (error) { /* ignore */ }
481
727
  }
482
- }
483
728
 
484
- const knxAutoConfigureDateTimeNodeForServer = async (node, serverId, { updateDom = false, overwrite = false, canApply = () => true } = {}) => {
485
- try {
486
- if (!node || !serverId || !canApply()) return
487
- const rows = await knxFetchGroupAddresses(serverId)
488
- if (!Array.isArray(rows) || rows.length === 0) return
489
- const suggestions = knxSuggestFromCsv(rows)
490
- return knxApplySuggestions(node, serverId, suggestions, { updateDom, overwrite, canApply })
491
- } catch (error) {
492
- try { console.warn('knxUltimateDateTime auto-config for server failed', error) } catch (e) { /* ignore */ }
729
+ const refresh = () => {
730
+ setupGA('#node-input-gaDateTime', '#node-input-nameDateTime', '#node-input-dptDateTime', ['19.'], '19.001')
731
+ setupGA('#node-input-gaDate', '#node-input-nameDate', '#node-input-dptDate', ['11.'], '11.001')
732
+ setupGA('#node-input-gaTime', '#node-input-nameTime', '#node-input-dptTime', ['10.'], '10.001')
493
733
  }
494
- }
495
734
 
496
- const knxApplySuggestions = (node, serverId, suggestions, { updateDom = false, overwrite = false, canApply = () => true } = {}) => {
497
- // The Utility wrapper can replace this fragment while ETS requests are in
498
- // flight. Never let a late response update its successor's fields.
499
- if (!node || !serverId || !canApply()) return
500
- if (!suggestions) return
735
+ $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
736
+ KNX_GA_CACHE.clear()
737
+ refresh()
738
+ try {
739
+ const sid = resolveKnxServerValue()
740
+ if (!sid || sid === '_ADD_') return
741
+ const hasAnyGAInUi = !!(
742
+ ($('#node-input-gaDateTime').val() || '').toString().trim() ||
743
+ ($('#node-input-gaDate').val() || '').toString().trim() ||
744
+ ($('#node-input-gaTime').val() || '').toString().trim()
745
+ )
746
+ // If the current values were auto-filled, allow overwrite when server changes.
747
+ const shouldOverwrite = node._knxDateTimeAutoConfigured === true && node._knxDateTimeAutoConfiguredServer && node._knxDateTimeAutoConfiguredServer !== sid
748
+ const beforeRequest = addressSnapshot()
749
+ // Otherwise fill only empty fields (do not override manual config).
750
+ knxAutoConfigureDateTimeNodeForServer(node, sid, {
751
+ updateDom: true,
752
+ overwrite: shouldOverwrite || !hasAnyGAInUi,
753
+ canApply: () => editorSession.active && resolveKnxServerValue() === sid && addressSnapshot() === beforeRequest
754
+ })
755
+ } catch (error) { /* ignore */ }
756
+ })
501
757
 
502
- // Avoid repeating the same automation multiple times.
503
- if (node._knxDateTimeAutoConfigured === true && overwrite !== true) return
758
+ refresh()
504
759
 
505
- node.server = serverId
760
+ // Auto-select server + fill GAs only for a brand new node (all GAs empty).
761
+ const initialServer = resolveKnxServerValue()
762
+ const initialAddresses = addressSnapshot()
763
+ editorSession.timer = setTimeout(() => {
764
+ knxAutoConfigureDateTimeNode(node, {
765
+ updateDom: true,
766
+ preferExistingServer: true,
767
+ canApply: () => editorSession.active && resolveKnxServerValue() === initialServer && addressSnapshot() === initialAddresses
768
+ })
769
+ }, 50)
506
770
 
507
- if (suggestions.dateTime && suggestions.dateTime.ga && (overwrite || !(node.gaDateTime || '').trim())) {
508
- node.gaDateTime = suggestions.dateTime.ga
509
- node.nameDateTime = suggestions.dateTime.devicename || ''
771
+ const syncUi = () => {
772
+ const sendOnDeploy = $('#node-input-sendOnDeploy').is(':checked')
773
+ $('.knx-datetime-deploy-options').toggle(sendOnDeploy)
774
+ const periodicSend = $('#node-input-periodicSend').is(':checked')
775
+ $('.knx-datetime-periodic-options').toggle(periodicSend)
510
776
  }
511
- if (suggestions.date && suggestions.date.ga && (overwrite || !(node.gaDate || '').trim())) {
512
- node.gaDate = suggestions.date.ga
513
- node.nameDate = suggestions.date.devicename || ''
777
+
778
+ $('#node-input-sendOnDeploy').on('change.knxUtilityProfile', syncUi)
779
+ $('#node-input-periodicSend').on('change.knxUtilityProfile', syncUi)
780
+ syncUi()
781
+ },
782
+ oneditcancel: function () {
783
+ const session = this._knxDateTimeEditorSession
784
+ if (session) {
785
+ session.active = false
786
+ clearTimeout(session.timer)
514
787
  }
515
- if (suggestions.time && suggestions.time.ga && (overwrite || !(node.gaTime || '').trim())) {
516
- node.gaTime = suggestions.time.ga
517
- node.nameTime = suggestions.time.devicename || ''
788
+ $('#node-input-server').off('.knxUtilityProfile')
789
+ $('#node-input-sendOnDeploy').off('.knxUtilityProfile')
790
+ $('#node-input-periodicSend').off('.knxUtilityProfile')
791
+ }
792
+ })
793
+ },
794
+ "watchdog": function (RED) {
795
+ const utilityFieldValue = (node, key) => {
796
+ if (node._utilityEditor && typeof $ === 'function') {
797
+ const field = $('#node-input-' + key)
798
+ if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val()
799
+ }
800
+ return node[key]
801
+ }
802
+ const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
803
+ if (!enabled(this)) return true
804
+ const numeric = Number(value)
805
+ return value !== null && value !== undefined && String(value).trim() !== '' &&
806
+ Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric))
807
+ }
808
+
809
+ RED.nodes.registerType('knxUltimateWatchDog', {
810
+ category: 'KNX Ultimate',
811
+ color: '#C7E9C0',
812
+ defaults: {
813
+ server: { type: 'knxUltimate-config', required: true },
814
+ topic: { value: '12/0/0' },
815
+ maxRetry: { value: 6, validate: utilityNumber(0, true) }, // Zero reports failure on the first unsuccessful check.
816
+ retryInterval: { value: 10, validate: utilityNumber(0.001) },
817
+ name: { value: '' },
818
+ autoStart: { value: true },
819
+ listenToKnxUltimateNodeErrors: { value: true },
820
+ checkLevel: { value: 'Ethernet' }
821
+ },
822
+ inputs: 1,
823
+ outputs: 1,
824
+ outputLabels: ['Output'],
825
+ icon: 'node-watchdog-icon.svg',
826
+ label: function () {
827
+ return ((this.name || 'KNX Watchdog') + ' ' + (this.checkLevel == 'Ethernet' ? 'Gateway IP' : this.topic))
828
+ },
829
+ paletteLabel: 'KNX WatchDog',
830
+ oneditprepare: function () {
831
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
832
+ const editorSession = { active: true }
833
+ this._knxUtilityProfileSession = editorSession
834
+ // Ignore responses belonging to a closed editor or a previous gateway.
835
+ const profileGetJSON = (url, callback) => {
836
+ const serverId = $('#node-input-server').val()
837
+ return $.getJSON(url, (data) => {
838
+ if (editorSession.active && $('#node-input-server').val() === serverId) callback(data)
839
+ })
518
840
  }
841
+ const node = this
842
+ const $knxServerInput = $('#node-input-server')
843
+ const $gaInput = $('#node-input-topic')
844
+ const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none'])
519
845
 
520
- node._knxDateTimeAutoConfigured = true
521
- node._knxDateTimeAutoConfiguredServer = serverId
846
+ // Go to the help panel
847
+ try {
848
+ RED.sidebar.show('help')
849
+ } catch (error) { }
850
+
851
+ $('#advancedOptionsAccordion').accordion({
852
+ header: 'h3',
853
+ heightStyle: 'content',
854
+ collapsible: true,
855
+ active: false
856
+ })
522
857
 
523
- if (updateDom) {
524
- try {
525
- $('#node-input-server').val(serverId).trigger('change')
526
- if (node.gaDateTime) $('#node-input-gaDateTime').val(node.gaDateTime)
527
- if (node.nameDateTime) $('#node-input-nameDateTime').val(node.nameDateTime)
528
- if (node.gaDate) $('#node-input-gaDate').val(node.gaDate)
529
- if (node.nameDate) $('#node-input-nameDate').val(node.nameDate)
530
- if (node.gaTime) $('#node-input-gaTime').val(node.gaTime)
531
- if (node.nameTime) $('#node-input-nameTime').val(node.nameTime)
532
- } catch (error) { /* ignore */ }
533
- } else {
534
- try {
535
- if (RED && RED.nodes && typeof RED.nodes.dirty === 'function') RED.nodes.dirty(true)
536
- } catch (error) { /* ignore */ }
537
- try { if (RED && RED.view && typeof RED.view.redraw === 'function') RED.view.redraw() } catch (error) { /* ignore */ }
858
+ const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map())
859
+
860
+ const resolveKnxServerValue = () => {
861
+ const domValue = $knxServerInput.val()
862
+ if (domValue !== undefined && domValue !== null) {
863
+ return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue
864
+ }
865
+ if (node.server !== undefined && node.server !== null && !KNX_EMPTY_VALUES.has(String(node.server))) {
866
+ return node.server
867
+ }
868
+ return ''
538
869
  }
539
- }
540
870
 
541
- const knxDateTimeOptionalNumber = (toggle) => function (value) {
542
- let enabled = this[toggle] === undefined || this[toggle] === true || this[toggle] === 'true';
543
- // The wrapper validates the saved node while its draft is open. Read the
544
- // active checkbox only in that case; unrelated nodes use their saved flag.
545
- if (this._utilityEditor && typeof $ === 'function') {
546
- const field = $('#node-input-' + toggle)
547
- if (field.length) enabled = field.is(':checked')
871
+ const fetchGroupAddresses = (serverId) => {
872
+ if (!serverId) return Promise.resolve([])
873
+ if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId))
874
+ return new Promise((resolve) => {
875
+ profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
876
+ const list = Array.isArray(data) ? data : []
877
+ KNX_GA_CACHE.set(serverId, list)
878
+ resolve(list)
879
+ }).fail(() => resolve([]))
880
+ })
548
881
  }
549
- return !enabled || RED.validators.number()(value)
550
- }
551
882
 
552
- RED.nodes.registerType('knxUltimateDateTime', {
553
- category: 'KNX Ultimate',
554
- color: '#C7E9C0',
555
- defaults: {
556
- server: { type: 'knxUltimate-config', required: true },
557
- name: { value: '' },
558
- outputtopic: { value: '' },
559
- gaDateTime: { value: '' },
560
- nameDateTime: { value: '' },
561
- dptDateTime: { value: '19.001' },
562
- gaDate: { value: '' },
563
- nameDate: { value: '' },
564
- dptDate: { value: '11.001' },
565
- gaTime: { value: '' },
566
- nameTime: { value: '' },
567
- dptTime: { value: '10.001' },
568
- sendOnDeploy: { value: true },
569
- sendOnDeployDelay: { value: 30, validate: knxDateTimeOptionalNumber('sendOnDeploy') },
570
- periodicSend: { value: true },
571
- periodicSendInterval: { value: 60, validate: knxDateTimeOptionalNumber('periodicSend') },
572
- periodicSendUnit: { value: 'm' }
573
- },
574
- inputs: 0,
575
- outputs: 0,
576
- icon: 'node-knx-icon.svg',
577
- label: function () {
578
- return this.name || 'KNX DateTime'
579
- },
580
- paletteLabel: function () {
581
- try {
582
- return RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.paletteLabel') || 'DateTime'
583
- } catch (error) {
584
- return 'DateTime'
883
+ const ensureGaAutocomplete = () => {
884
+ const serverId = resolveKnxServerValue()
885
+ if (!serverId) {
886
+ if ($gaInput.data('ui-autocomplete')) {
887
+ $gaInput.autocomplete('disable')
888
+ }
889
+ return
585
890
  }
586
- },
587
- onadd: function () {
588
- // Auto-select a KNX gateway (first one with ETS CSV imported) and prefill coherent group addresses.
589
- // This runs when the node is dragged from the palette. Best-effort, non-blocking.
590
- const node = this
591
- setTimeout(() => {
592
- knxAutoConfigureDateTimeNode(node, {
593
- updateDom: false,
594
- preferExistingServer: true,
595
- canApply: () => node.utilityType === 'datetime'
891
+
892
+ const sourceFn = (request, response) => {
893
+ fetchGroupAddresses(serverId).then((data) => {
894
+ if (!editorSession.active || resolveKnxServerValue() !== serverId) return
895
+ const items = [];
896
+ (data || []).forEach((entry) => {
897
+ const dpt = typeof entry.dpt === 'string' ? entry.dpt : ''
898
+ if (!dpt.startsWith('1.')) return // Watchdog only accepts boolean GAs
899
+ const devName = entry.devicename || ''
900
+ const searchStr = `${entry.ga} (${devName}) DPT${dpt}`
901
+ if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return
902
+ items.push({
903
+ label: `${entry.ga} # ${devName} # ${dpt}`,
904
+ value: entry.ga
905
+ })
906
+ })
907
+ response(items)
596
908
  })
597
- }, 50)
598
- },
599
- button: {
600
- enabled: function () {
601
- return !this.changed
602
- },
603
- visible: function () {
604
- return true
605
- },
606
- onclick: function () {
607
- const node = this
608
- $.ajax({
609
- type: 'POST',
610
- url: 'knxUltimateUtility/sendNow',
611
- data: { id: node.id },
612
- success: function (response) {
613
- const queued = response && response.queued === true
614
- const message = queued
615
- ? (RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.notifyQueued') || 'Queued (gateway not connected yet)')
616
- : (RED._('node-red-contrib-knx-ultimate/knxUltimateDateTime:knxUltimateDateTime.notifySent') || 'Sent to KNX')
617
- RED.notify(message, 'success')
618
- },
619
- error: function (xhr) {
620
- let message = 'Error'
621
- try {
622
- if (xhr && xhr.responseJSON && xhr.responseJSON.error) message = xhr.responseJSON.error
623
- } catch (error) { /* ignore */ }
624
- RED.notify(message, 'error')
909
+ }
910
+
911
+ if ($gaInput.data('knx-watchdog-ga')) {
912
+ $gaInput.autocomplete('option', 'source', sourceFn)
913
+ $gaInput.autocomplete('enable')
914
+ } else {
915
+ $gaInput.autocomplete({
916
+ minLength: 0,
917
+ source: sourceFn,
918
+ select: function (event, ui) {
919
+ event.preventDefault()
920
+ $(this).val(ui.item.value)
921
+ const parts = ui.item.label.split('#')
922
+ let deviceName = (parts[1] || '').trim().replace(/^\)/, '').trim()
923
+ if (deviceName.indexOf('/') > -1) {
924
+ deviceName = deviceName.split('/').pop().trim()
925
+ }
926
+ const $nameInput = $('#node-input-name')
927
+ if (deviceName && $nameInput.length) {
928
+ $nameInput.val(deviceName)
929
+ }
625
930
  }
931
+ }).on('focus.knxUltimateWatchDog click.knxUltimateWatchDog', function () {
932
+ const currentValue = $(this).val() || ''
933
+ try {
934
+ $(this).autocomplete('search', currentValue ? `${currentValue} exactmatch` : '')
935
+ } catch (error) { }
626
936
  })
937
+ $gaInput.data('knx-watchdog-ga', true)
627
938
  }
628
- },
629
- oneditprepare: function () {
630
- const node = this
631
- if (node._knxDateTimeEditorSession) node._knxDateTimeEditorSession.active = false
632
- const editorSession = { active: true }
633
- node._knxDateTimeEditorSession = editorSession
634
- const $knxServerInput = $('#node-input-server')
635
- const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none'])
636
- const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map())
637
-
638
- try { RED.sidebar.show('help') } catch (error) { /* ignore */ }
639
-
640
- const resolveKnxServerValue = () => {
641
- const domValue = $knxServerInput.val()
642
- if (domValue !== undefined && domValue !== null && domValue !== '') return domValue
643
- if (node.server !== undefined && node.server !== null && node.server !== '') return node.server
644
- return ''
645
- }
646
-
647
- const addressFields = ['gaDateTime', 'nameDateTime', 'dptDateTime', 'gaDate', 'nameDate', 'dptDate', 'gaTime', 'nameTime', 'dptTime']
648
- const addressSnapshot = () => JSON.stringify(addressFields.map((field) => $('#node-input-' + field).val() || ''))
649
939
 
650
- const hasKnxServerSelected = () => {
651
- const val = resolveKnxServerValue()
652
- return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)))
653
- }
940
+ try {
941
+ const srv = RED.nodes.node(serverId)
942
+ if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id)
943
+ } catch (error) { }
944
+ }
654
945
 
655
- const fetchGroupAddresses = (serverId) => {
656
- if (!serverId) return Promise.resolve([])
657
- if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId))
658
- return new Promise((resolve) => {
659
- $.getJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
660
- const list = Array.isArray(data) ? data : []
661
- KNX_GA_CACHE.set(serverId, list)
662
- resolve(list)
663
- }).fail(() => resolve([]))
664
- })
946
+ $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
947
+ KNX_GA_CACHE.clear()
948
+ ensureGaAutocomplete()
949
+ })
950
+ ensureGaAutocomplete()
951
+
952
+ const syncDivHost = () => {
953
+ const level = $('#node-input-checkLevel').val() || node.checkLevel || 'Ethernet'
954
+ if (level === 'Ethernet') {
955
+ $('#divHost').hide()
956
+ } else {
957
+ $('#divHost').show()
665
958
  }
959
+ }
666
960
 
667
- const setupGA = (gaSelector, nameSelector, dptSelector, allowedPrefixes, defaultDpt) => {
668
- const $gaInput = $(gaSelector)
669
- const $nameInput = $(nameSelector)
670
- const $dptInput = $(dptSelector)
671
- if (!$gaInput.length) return
961
+ $('#node-input-checkLevel').on('change.knxUtilityProfile', function () {
962
+ syncDivHost()
963
+ })
964
+ syncDivHost()
965
+ },
966
+ oneditcancel: function () {
967
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
968
+ $('#node-input-server').off('.knxUtilityProfile')
969
+ try { RED.sidebar.show('info') } catch (error) { }
970
+ },
971
+ oneditsave: function () {
972
+ // Return to the info tab
973
+ try {
974
+ RED.sidebar.show('info')
975
+ } catch (error) { }
976
+ }
672
977
 
673
- if ($dptInput.length && (!$dptInput.val() || $dptInput.val() === '')) $dptInput.val(defaultDpt)
978
+ })
979
+ },
980
+ "globalcontext": function (RED) {
981
+ const utilityFieldValue = (node, key) => {
982
+ if (node._utilityEditor && typeof $ === 'function') {
983
+ const field = $('#node-input-' + key)
984
+ if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val()
985
+ }
986
+ return node[key]
987
+ }
988
+ const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
989
+ if (!enabled(this)) return true
990
+ const numeric = Number(value)
991
+ return value !== null && value !== undefined && String(value).trim() !== '' &&
992
+ Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric))
993
+ }
674
994
 
675
- const sourceFn = (request, response) => {
676
- if (!hasKnxServerSelected()) {
677
- response([])
678
- return
679
- }
680
- const serverId = resolveKnxServerValue()
995
+ RED.nodes.registerType('knxUltimateGlobalContext', {
996
+ category: 'KNX Ultimate',
997
+ color: '#C7E9C0',
998
+ defaults: {
999
+ server: { type: 'knxUltimate-config', required: true },
1000
+ name: { value: 'KNXGlobalContext', validate: function (value) { return /^[a-zA-Z]+$/.test(value || '') } },
1001
+ exposeAsVariable: { value: 'exposeAsVariableREADWRITE', required: false },
1002
+ writeExecutionInterval: { value: 1000, validate: utilityNumber(1) },
1003
+ contextStorage: { value: '' }
1004
+ },
1005
+ inputs: 0,
1006
+ outputs: 0,
1007
+ icon: 'node-knx-icon.svg',
1008
+ label: function () {
1009
+ return (this.name)
1010
+ },
1011
+ paletteLabel: 'KNX Global Context',
1012
+ oneditprepare: function () {
1013
+ // Go to the help panel
1014
+ try {
1015
+ RED.sidebar.show('help')
1016
+ } catch (error) { }
1017
+ },
1018
+ oneditsave: function () {
1019
+ // Return to the info tab
1020
+ try {
1021
+ RED.sidebar.show('info')
1022
+ } catch (error) { }
1023
+ },
1024
+ oneditcancel: function () {
1025
+ // Return to the info tab
1026
+ try {
1027
+ RED.sidebar.show('info')
1028
+ } catch (error) { }
1029
+ }
1030
+ })
1031
+ },
1032
+ "logger": function (RED) {
1033
+ const utilityFieldValue = (node, key) => {
1034
+ if (node._utilityEditor && typeof $ === 'function') {
1035
+ const field = $('#node-input-' + key)
1036
+ if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val()
1037
+ }
1038
+ return node[key]
1039
+ }
1040
+ const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
1041
+ if (!enabled(this)) return true
1042
+ const numeric = Number(value)
1043
+ return value !== null && value !== undefined && String(value).trim() !== '' &&
1044
+ Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric))
1045
+ }
1046
+
1047
+ RED.nodes.registerType('knxUltimateLogger', {
1048
+ category: 'KNX Ultimate',
1049
+ color: '#C7E9C0',
1050
+ defaults: {
1051
+ server: { type: 'knxUltimate-config', required: true },
1052
+ topic: { value: '' },
1053
+ intervalCreateETSXML: { value: 15, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'autoStartTimerCreateETSXML') !== false) },
1054
+ name: { value: '' },
1055
+ autoStartTimerCreateETSXML: { value: true },
1056
+ maxRowsInETSXML: { value: 0, validate: utilityNumber(0, true) },
1057
+ saveMode: { value: 'emit' },
1058
+ filePath: { value: '' },
1059
+ autoStartTimerTelegramCounter: { value: false },
1060
+ intervalTelegramCount: { value: 60, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'autoStartTimerTelegramCounter') === true) }
1061
+
1062
+ },
1063
+ inputs: 1,
1064
+ outputs: 2,
1065
+ outputLabels: ['ETS Diag file', 'Telegram Count'],
1066
+ icon: 'node-logger-icon.svg',
1067
+ label: function () {
1068
+ return ((this.name || 'KNX Logger') + ' ' + this.topic)
1069
+ },
1070
+ paletteLabel: 'KNX Logger',
1071
+ oneditprepare: function () {
1072
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1073
+ const editorSession = { active: true }
1074
+ this._knxUtilityProfileSession = editorSession
1075
+ // Ignore responses belonging to a closed editor or a previous gateway.
1076
+ const profileGetJSON = (url, callback) => {
1077
+ const serverId = $('#node-input-server').val()
1078
+ return $.getJSON(url, (data) => {
1079
+ if (editorSession.active && $('#node-input-server').val() === serverId) callback(data)
1080
+ })
1081
+ }
1082
+ // Go to the help panel
1083
+ try {
1084
+ RED.sidebar.show('help')
1085
+ } catch (error) { }
1086
+
1087
+ $('#mlxETSFileAccordion').accordion({
1088
+ header: 'h3',
1089
+ heightStyle: 'content',
1090
+ collapsible: true,
1091
+ active: false
1092
+ })
1093
+
1094
+ const nodeId = this.id
1095
+ const resolveAdminRoot = () => {
1096
+ const raw = (RED.settings && typeof RED.settings.httpAdminRoot === 'string') ? RED.settings.httpAdminRoot : '/'
1097
+ const trimmed = String(raw || '/').trim()
1098
+ if (trimmed === '' || trimmed === '/') return ''
1099
+ return '/' + trimmed.replace(/^\/+|\/+$/g, '')
1100
+ }
1101
+ const resolveAccessToken = () => {
1102
+ try {
1103
+ const tokens = (RED.settings && typeof RED.settings.get === 'function') ? RED.settings.get('auth-tokens') : null
1104
+ const token = tokens && typeof tokens.access_token === 'string' ? tokens.access_token.trim() : ''
1105
+ return token
1106
+ } catch (error) {
1107
+ return ''
1108
+ }
1109
+ }
1110
+
1111
+ const toggleFilePath = () => {
1112
+ const isEmitSave = $('#node-input-saveMode').val() === 'emit_save'
1113
+ const $filePathRow = $('#knx-logger-filePath-row')
1114
+ if (isEmitSave) {
1115
+ $filePathRow.show()
1116
+ } else {
1117
+ $filePathRow.hide()
1118
+ }
1119
+ }
1120
+ $('#node-input-saveMode').on('change.knxUtilityProfile', toggleFilePath)
1121
+
1122
+ const currentFilePath = this.filePath || ''
1123
+ $('#node-input-filePath').val(currentFilePath)
1124
+ toggleFilePath()
1125
+
1126
+ $('#knx-logger-downloadButton').on('click.knxUtilityProfile', function (evt) {
1127
+ evt.preventDefault()
1128
+ const filePathVal = $('#node-input-filePath').val() || ''
1129
+ if (!filePathVal) {
1130
+ try {
1131
+ const msg = (RED._ && RED._('node-red-contrib-knx-ultimate/knxUltimateLogger:knxUltimateLogger.noFilePath')) || 'File path is empty'
1132
+ RED.notify(msg, 'warning')
1133
+ } catch (error) {
1134
+ alert('File path is empty')
1135
+ }
1136
+ return
1137
+ }
1138
+ const adminRoot = resolveAdminRoot()
1139
+ const targetBase = adminRoot + '/knxUltimateUtility/logger/download'
1140
+ const params = new URLSearchParams()
1141
+ if (nodeId) params.set('nodeId', nodeId)
1142
+ params.set('_', String(Date.now()))
1143
+ const accessToken = resolveAccessToken()
1144
+ if (accessToken) params.set('access_token', accessToken)
1145
+ const target = targetBase + '?' + params.toString()
1146
+ const wnd = window.open(target, '_blank', 'noopener,noreferrer')
1147
+ try { if (wnd && typeof wnd.focus === 'function') wnd.focus() } catch (e) { }
1148
+ })
1149
+ },
1150
+ oneditcancel: function () {
1151
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1152
+ $('#node-input-server').off('.knxUtilityProfile')
1153
+ try { RED.sidebar.show('info') } catch (error) { }
1154
+ },
1155
+ oneditsave: function () {
1156
+ // Return to the info tab
1157
+ try {
1158
+ RED.sidebar.show('info')
1159
+ } catch (error) { }
1160
+
1161
+ this.filePath = $('#node-input-filePath').val() || ''
1162
+ }
1163
+
1164
+ })
1165
+ },
1166
+ "staircase": function (RED) {
1167
+ const utilityFieldValue = (node, key) => {
1168
+ if (node._utilityEditor && typeof $ === 'function') {
1169
+ const field = $('#node-input-' + key)
1170
+ if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val()
1171
+ }
1172
+ return node[key]
1173
+ }
1174
+ const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
1175
+ if (!enabled(this)) return true
1176
+ const numeric = Number(value)
1177
+ return value !== null && value !== undefined && String(value).trim() !== '' &&
1178
+ Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric))
1179
+ }
1180
+
1181
+ RED.nodes.registerType('knxUltimateStaircase', {
1182
+ category: 'KNX Ultimate',
1183
+ color: '#C7E9C0',
1184
+ defaults: {
1185
+ server: { type: 'knxUltimate-config', required: true },
1186
+ name: { value: '' },
1187
+ outputtopic: { value: '' },
1188
+ gaTrigger: { value: '', required: true },
1189
+ nameTrigger: { value: '' },
1190
+ dptTrigger: { value: '1.001' },
1191
+ gaOutput: { value: '', required: true },
1192
+ nameOutput: { value: '' },
1193
+ dptOutput: { value: '1.001' },
1194
+ gaStatus: { value: '' },
1195
+ nameStatus: { value: '' },
1196
+ dptStatus: { value: '1.001' },
1197
+ gaOverride: { value: '' },
1198
+ nameOverride: { value: '' },
1199
+ dptOverride: { value: '1.001' },
1200
+ gaBlock: { value: '' },
1201
+ nameBlock: { value: '' },
1202
+ dptBlock: { value: '1.001' },
1203
+ timerSeconds: { value: 120, required: true, validate: utilityNumber(1) },
1204
+ extendMode: { value: 'restart' },
1205
+ triggerOffCancels: { value: 'yes' },
1206
+ preWarnEnable: { value: false },
1207
+ preWarnSeconds: { value: 15, validate: utilityNumber(0, false, (node) => utilityFieldValue(node, 'preWarnEnable') === true) },
1208
+ preWarnMode: { value: 'status' },
1209
+ preWarnFlashMs: { value: 400, validate: utilityNumber(100, false, (node) => utilityFieldValue(node, 'preWarnEnable') === true && utilityFieldValue(node, 'preWarnMode') === 'flash') },
1210
+ blockAction: { value: 'off' },
1211
+ emitEvents: { value: false }
1212
+ },
1213
+ inputs: 1,
1214
+ outputs: 1,
1215
+ icon: 'node-knx-icon.svg',
1216
+ label: function () {
1217
+ return this.name || 'KNX Staircase'
1218
+ },
1219
+ paletteLabel: 'KNX Staircase',
1220
+ oneditprepare: function () {
1221
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1222
+ const editorSession = { active: true }
1223
+ this._knxUtilityProfileSession = editorSession
1224
+ // Ignore responses belonging to a closed editor or a previous gateway.
1225
+ const profileGetJSON = (url, callback) => {
1226
+ const serverId = $('#node-input-server').val()
1227
+ return $.getJSON(url, (data) => {
1228
+ if (editorSession.active && $('#node-input-server').val() === serverId) callback(data)
1229
+ })
1230
+ }
1231
+ const node = this
1232
+ const $knxServerInput = $('#node-input-server')
1233
+ try { RED.sidebar.show('help') } catch (error) { /* ignore */ }
1234
+ const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none'])
1235
+
1236
+ const resolveKnxServerValue = () => {
1237
+ const domValue = $knxServerInput.val()
1238
+ if (domValue !== undefined && domValue !== null) return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue
1239
+ if (node.server !== undefined && node.server !== null && node.server !== '') return node.server
1240
+ return ''
1241
+ }
1242
+
1243
+ const hasKnxServerSelected = () => {
1244
+ const val = resolveKnxServerValue()
1245
+ return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)))
1246
+ }
1247
+
1248
+ const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map())
1249
+
1250
+ const fetchGroupAddresses = (serverId) => {
1251
+ if (!serverId) return Promise.resolve([])
1252
+ if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId))
1253
+ return new Promise((resolve) => {
1254
+ profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
1255
+ const list = Array.isArray(data) ? data : []
1256
+ KNX_GA_CACHE.set(serverId, list)
1257
+ resolve(list)
1258
+ }).fail(() => resolve([]))
1259
+ })
1260
+ }
1261
+
1262
+ const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
1263
+ const $gaInput = $(gaSelector)
1264
+ const $nameInput = $(nameSelector)
1265
+ const $dptInput = $(dptSelector)
1266
+ if (!$gaInput.length) return
1267
+
1268
+ const ensureAutocomplete = () => {
1269
+ const sourceFn = (request, response) => {
1270
+ if (!hasKnxServerSelected()) {
1271
+ response([])
1272
+ return
1273
+ }
1274
+ const serverId = resolveKnxServerValue()
681
1275
  fetchGroupAddresses(serverId).then((data) => {
682
1276
  if (!editorSession.active || resolveKnxServerValue() !== serverId) return
683
1277
  const items = []
684
1278
  data.forEach((entry) => {
685
1279
  const dpt = entry.dpt || ''
686
- const allowed = allowedPrefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix))
1280
+ const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix))
687
1281
  if (!allowed) return
688
1282
  const devName = entry.devicename || ''
689
1283
  const searchStr = `${entry.ga} (${devName}) DPT${dpt}`
@@ -709,26 +1303,30 @@
709
1303
  let deviceName = ''
710
1304
  try {
711
1305
  deviceName = ui.item.label.split('#')[1].trim()
1306
+ deviceName = deviceName.replace(/^\)/, '').trim()
712
1307
  } catch (error) { deviceName = '' }
713
1308
  if ($nameInput.length) {
714
- $nameInput.val(deviceName || '')
1309
+ if (deviceName && deviceName !== '') {
1310
+ $nameInput.val(deviceName)
1311
+ } else if (!$nameInput.val()) {
1312
+ $nameInput.val('')
1313
+ }
715
1314
  }
716
1315
  try {
717
1316
  const parts = ui.item.label.split('#')
718
1317
  const dptFromLabel = parts.length >= 3 ? parts[2].trim() : ''
719
- $dptInput.val(dptFromLabel || defaultDpt)
720
- } catch (error) {
721
- $dptInput.val(defaultDpt)
722
- }
1318
+ if (dptFromLabel !== '') {
1319
+ $dptInput.val(dptFromLabel)
1320
+ }
1321
+ } catch (error) { /* ignore */ }
723
1322
  }
724
1323
  })
725
- .on('focus.knxUltimateDateTime click.knxUltimateDateTime', function () {
1324
+ .on('focus.knxUltimateStaircase click.knxUltimateStaircase', function () {
726
1325
  const currentValue = $(this).val() || ''
727
1326
  try { $(this).autocomplete('search', `${currentValue} exactmatch`) } catch (error) { /* ignore */ }
728
1327
  })
729
1328
  $gaInput.data('knx-ga-initialised', true)
730
1329
  }
731
-
732
1330
  try {
733
1331
  if (hasKnxServerSelected()) {
734
1332
  const srv = RED.nodes.node(resolveKnxServerValue())
@@ -737,1599 +1335,960 @@
737
1335
  } catch (error) { /* ignore */ }
738
1336
  }
739
1337
 
740
- const refresh = () => {
741
- setupGA('#node-input-gaDateTime', '#node-input-nameDateTime', '#node-input-dptDateTime', ['19.'], '19.001')
742
- setupGA('#node-input-gaDate', '#node-input-nameDate', '#node-input-dptDate', ['11.'], '11.001')
743
- setupGA('#node-input-gaTime', '#node-input-nameTime', '#node-input-dptTime', ['10.'], '10.001')
744
- }
745
-
746
- $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
747
- KNX_GA_CACHE.clear()
748
- refresh()
749
- try {
750
- const sid = resolveKnxServerValue()
751
- if (!sid || sid === '_ADD_') return
752
- const hasAnyGAInUi = !!(
753
- ($('#node-input-gaDateTime').val() || '').toString().trim() ||
754
- ($('#node-input-gaDate').val() || '').toString().trim() ||
755
- ($('#node-input-gaTime').val() || '').toString().trim()
756
- )
757
- // If the current values were auto-filled, allow overwrite when server changes.
758
- const shouldOverwrite = node._knxDateTimeAutoConfigured === true && node._knxDateTimeAutoConfiguredServer && node._knxDateTimeAutoConfiguredServer !== sid
759
- const beforeRequest = addressSnapshot()
760
- // Otherwise fill only empty fields (do not override manual config).
761
- knxAutoConfigureDateTimeNodeForServer(node, sid, {
762
- updateDom: true,
763
- overwrite: shouldOverwrite || !hasAnyGAInUi,
764
- canApply: () => editorSession.active && resolveKnxServerValue() === sid && addressSnapshot() === beforeRequest
765
- })
766
- } catch (error) { /* ignore */ }
767
- })
768
-
769
- refresh()
1338
+ ensureAutocomplete()
1339
+ }
770
1340
 
771
- // Auto-select server + fill GAs only for a brand new node (all GAs empty).
772
- const initialServer = resolveKnxServerValue()
773
- const initialAddresses = addressSnapshot()
774
- editorSession.timer = setTimeout(() => {
775
- knxAutoConfigureDateTimeNode(node, {
776
- updateDom: true,
777
- preferExistingServer: true,
778
- canApply: () => editorSession.active && resolveKnxServerValue() === initialServer && addressSnapshot() === initialAddresses
779
- })
780
- }, 50)
1341
+ const BINARY_PREFIX = ['1.']
1342
+ const ANY_ANALOG_PREFIX = ['1.', '2.', '5.', '6.', '7.', '8.', '9.', '12.', '13.', '14.', '16.', '20.']
781
1343
 
782
- const syncUi = () => {
783
- const sendOnDeploy = $('#node-input-sendOnDeploy').is(':checked')
784
- $('.knx-datetime-deploy-options').toggle(sendOnDeploy)
785
- const periodicSend = $('#node-input-periodicSend').is(':checked')
786
- $('.knx-datetime-periodic-options').toggle(periodicSend)
1344
+ const refreshKnxBindings = () => {
1345
+ if (!hasKnxServerSelected()) {
1346
+ return
787
1347
  }
788
1348
 
789
- $('#node-input-sendOnDeploy').on('change.knxUtilityProfile', syncUi)
790
- $('#node-input-periodicSend').on('change.knxUtilityProfile', syncUi)
791
- syncUi()
792
- },
793
- oneditcancel: function () {
794
- const session = this._knxDateTimeEditorSession
795
- if (session) {
796
- session.active = false
797
- clearTimeout(session.timer)
798
- }
799
- $('#node-input-server').off('.knxUtilityProfile')
800
- $('#node-input-sendOnDeploy').off('.knxUtilityProfile')
801
- $('#node-input-periodicSend').off('.knxUtilityProfile')
1349
+ getGroupAddress('#node-input-gaTrigger', '#node-input-nameTrigger', '#node-input-dptTrigger', BINARY_PREFIX)
1350
+ getGroupAddress('#node-input-gaOutput', '#node-input-nameOutput', '#node-input-dptOutput', ANY_ANALOG_PREFIX)
1351
+ getGroupAddress('#node-input-gaStatus', '#node-input-nameStatus', '#node-input-dptStatus', ANY_ANALOG_PREFIX)
1352
+ getGroupAddress('#node-input-gaOverride', '#node-input-nameOverride', '#node-input-dptOverride', BINARY_PREFIX)
1353
+ getGroupAddress('#node-input-gaBlock', '#node-input-nameBlock', '#node-input-dptBlock', BINARY_PREFIX)
802
1354
  }
803
- })
804
- },
805
- "watchdog": function (RED) {
806
- const utilityFieldValue = (node, key) => {
807
- if (node._utilityEditor && typeof $ === 'function') {
808
- const field = $('#node-input-' + key);
809
- if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
810
- }
811
- return node[key];
812
- };
813
- const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
814
- if (!enabled(this)) return true;
815
- const numeric = Number(value);
816
- return value !== null && value !== undefined && String(value).trim() !== '' &&
817
- Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
818
- };
819
1355
 
820
- RED.nodes.registerType('knxUltimateWatchDog', {
821
- category: "KNX Ultimate",
822
- color: '#C7E9C0',
823
- defaults: {
824
- server: { type: "knxUltimate-config", required: true },
825
- topic: { value: "12/0/0" },
826
- maxRetry: { value: 6, validate: utilityNumber(0, true) }, // Zero reports failure on the first unsuccessful check.
827
- retryInterval: { value: 10, validate: utilityNumber(0.001) },
828
- name: { value: "" },
829
- autoStart: { value: true },
830
- listenToKnxUltimateNodeErrors: { value: true },
831
- checkLevel: { value: "Ethernet" }
832
- },
833
- inputs: 1,
834
- outputs: 1,
835
- outputLabels: ["Output"],
836
- icon: "node-watchdog-icon.svg",
837
- label: function () {
838
- return ((this.name || "KNX Watchdog") + " " + (this.checkLevel == "Ethernet" ? "Gateway IP" : this.topic));
839
- },
840
- paletteLabel: "KNX WatchDog",
841
- oneditprepare: function () {
842
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
843
- const editorSession = { active: true };
844
- this._knxUtilityProfileSession = editorSession;
845
- // Ignore responses belonging to a closed editor or a previous gateway.
846
- const profileGetJSON = (url, callback) => {
847
- const serverId = $('#node-input-server').val();
848
- return $.getJSON(url, (data) => {
849
- if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
850
- });
851
- };
852
- const node = this;
853
- const $knxServerInput = $("#node-input-server");
854
- const $gaInput = $("#node-input-topic");
855
- const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
856
-
857
- // Go to the help panel
858
- try {
859
- RED.sidebar.show("help");
860
- } catch (error) { }
861
-
862
- $("#advancedOptionsAccordion").accordion({
863
- header: "h3",
864
- heightStyle: "content",
865
- collapsible: true,
866
- active: false
867
- });
868
-
869
- const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
870
-
871
- const resolveKnxServerValue = () => {
872
- const domValue = $knxServerInput.val();
873
- if (domValue !== undefined && domValue !== null) {
874
- return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue;
875
- }
876
- if (node.server !== undefined && node.server !== null && !KNX_EMPTY_VALUES.has(String(node.server))) {
877
- return node.server;
878
- }
879
- return '';
880
- };
881
-
882
- const fetchGroupAddresses = (serverId) => {
883
- if (!serverId) return Promise.resolve([]);
884
- if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
885
- return new Promise((resolve) => {
886
- profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
887
- const list = Array.isArray(data) ? data : [];
888
- KNX_GA_CACHE.set(serverId, list);
889
- resolve(list);
890
- }).fail(() => resolve([]));
891
- });
892
- };
893
-
894
- const ensureGaAutocomplete = () => {
895
- const serverId = resolveKnxServerValue();
896
- if (!serverId) {
897
- if ($gaInput.data('ui-autocomplete')) {
898
- $gaInput.autocomplete('disable');
899
- }
900
- return;
901
- }
902
-
903
- const sourceFn = (request, response) => {
904
- fetchGroupAddresses(serverId).then((data) => {
905
- if (!editorSession.active || resolveKnxServerValue() !== serverId) return;
906
- const items = [];
907
- (data || []).forEach((entry) => {
908
- const dpt = typeof entry.dpt === 'string' ? entry.dpt : '';
909
- if (!dpt.startsWith('1.')) return; // Watchdog only accepts boolean GAs
910
- const devName = entry.devicename || '';
911
- const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
912
- if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
913
- items.push({
914
- label: `${entry.ga} # ${devName} # ${dpt}`,
915
- value: entry.ga
916
- });
917
- });
918
- response(items);
919
- });
920
- };
921
-
922
- if ($gaInput.data('knx-watchdog-ga')) {
923
- $gaInput.autocomplete('option', 'source', sourceFn);
924
- $gaInput.autocomplete('enable');
925
- } else {
926
- $gaInput.autocomplete({
927
- minLength: 0,
928
- source: sourceFn,
929
- select: function (event, ui) {
930
- event.preventDefault();
931
- $(this).val(ui.item.value);
932
- const parts = ui.item.label.split('#');
933
- let deviceName = (parts[1] || '').trim().replace(/^\)/, '').trim();
934
- if (deviceName.indexOf('/') > -1) {
935
- deviceName = deviceName.split('/').pop().trim();
936
- }
937
- const $nameInput = $('#node-input-name');
938
- if (deviceName && $nameInput.length) {
939
- $nameInput.val(deviceName);
940
- }
941
- }
942
- }).on('focus.knxUltimateWatchDog click.knxUltimateWatchDog', function () {
943
- const currentValue = $(this).val() || '';
944
- try {
945
- $(this).autocomplete('search', currentValue ? `${currentValue} exactmatch` : '');
946
- } catch (error) { }
947
- });
948
- $gaInput.data('knx-watchdog-ga', true);
949
- }
950
-
951
- try {
952
- const srv = RED.nodes.node(serverId);
953
- if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
954
- } catch (error) { }
955
- };
956
-
957
- $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
958
- KNX_GA_CACHE.clear();
959
- ensureGaAutocomplete();
960
- });
961
- ensureGaAutocomplete();
962
-
963
- const syncDivHost = () => {
964
- const level = $("#node-input-checkLevel").val() || node.checkLevel || "Ethernet";
965
- if (level === "Ethernet") {
966
- $("#divHost").hide();
967
- } else {
968
- $("#divHost").show();
969
- }
970
- };
1356
+ $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
1357
+ KNX_GA_CACHE.clear()
1358
+ refreshKnxBindings()
1359
+ })
971
1360
 
972
- $("#node-input-checkLevel").on('change.knxUtilityProfile', function () {
973
- syncDivHost();
974
- });
975
- syncDivHost();
976
- },
977
- oneditcancel: function () {
978
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
979
- $('#node-input-server').off('.knxUtilityProfile');
980
- try { RED.sidebar.show('info'); } catch (error) { }
981
- },
982
- oneditsave: function () {
983
- // Return to the info tab
984
- try {
985
- RED.sidebar.show("info");
986
- } catch (error) { }
1361
+ refreshKnxBindings()
987
1362
 
1363
+ const $preWarnRows = $('.knx-staircase-prewarn')
1364
+ const $flashRow = $('.knx-staircase-prewarn-flash')
988
1365
 
989
- }
1366
+ function syncPreWarnMode () {
1367
+ const enabled = $('#node-input-preWarnEnable').is(':checked')
1368
+ $preWarnRows.toggle(enabled)
1369
+ if (!enabled) {
1370
+ $flashRow.hide()
1371
+ return
1372
+ }
1373
+ const mode = $('#node-input-preWarnMode').val()
1374
+ $flashRow.toggle(mode === 'flash')
1375
+ }
990
1376
 
991
- })
1377
+ $('#node-input-preWarnEnable').on('change.knxUtilityProfile', syncPreWarnMode)
1378
+ $('#node-input-preWarnMode').on('change.knxUtilityProfile', syncPreWarnMode)
1379
+ syncPreWarnMode()
1380
+ },
1381
+ oneditcancel: function () {
1382
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1383
+ $('#node-input-server').off('.knxUtilityProfile')
1384
+ try { RED.sidebar.show('info') } catch (error) { }
1385
+ }
1386
+ })
992
1387
  },
993
- "globalcontext": function (RED) {
1388
+ "garage": function (RED) {
994
1389
  const utilityFieldValue = (node, key) => {
995
1390
  if (node._utilityEditor && typeof $ === 'function') {
996
- const field = $('#node-input-' + key);
997
- if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
1391
+ const field = $('#node-input-' + key)
1392
+ if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val()
998
1393
  }
999
- return node[key];
1000
- };
1394
+ return node[key]
1395
+ }
1001
1396
  const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
1002
- if (!enabled(this)) return true;
1003
- const numeric = Number(value);
1397
+ if (!enabled(this)) return true
1398
+ const numeric = Number(value)
1004
1399
  return value !== null && value !== undefined && String(value).trim() !== '' &&
1005
- Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
1006
- };
1007
-
1008
- RED.nodes.registerType('knxUltimateGlobalContext', {
1009
- category: "KNX Ultimate",
1010
- color: '#C7E9C0',
1011
- defaults: {
1012
- server: { type: "knxUltimate-config", required: true },
1013
- name: { value: "KNXGlobalContext", validate: function (value) { return /^[a-zA-Z]+$/.test(value || ''); } },
1014
- exposeAsVariable: { value: "exposeAsVariableREADWRITE", required: false },
1015
- writeExecutionInterval: { value: 1000, validate: utilityNumber(1) },
1016
- contextStorage: { value: "" }
1017
- },
1018
- inputs: 0,
1019
- outputs: 0,
1020
- icon: "node-knx-icon.svg",
1021
- label: function () {
1022
- return (this.name);
1023
- },
1024
- paletteLabel: "KNX Global Context",
1025
- oneditprepare: function () {
1026
- // Go to the help panel
1027
- try {
1028
- RED.sidebar.show("help");
1029
- } catch (error) { }
1030
-
1031
-
1032
-
1033
- },
1034
- oneditsave: function () {
1035
- // Return to the info tab
1036
- try {
1037
- RED.sidebar.show("info");
1038
- } catch (error) { }
1400
+ Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric))
1401
+ }
1039
1402
 
1403
+ RED.nodes.registerType('knxUltimateGarage', {
1404
+ category: 'KNX Ultimate',
1405
+ color: '#C7E9C0',
1406
+ defaults: {
1407
+ server: { type: 'knxUltimate-config', required: true },
1408
+ name: { value: '' },
1409
+ outputtopic: { value: '' },
1410
+ gaCommand: { value: '', required: true },
1411
+ nameCommand: { value: '' },
1412
+ dptCommand: { value: '1.001' },
1413
+ gaImpulse: { value: '' },
1414
+ nameImpulse: { value: '' },
1415
+ dptImpulse: { value: '1.017' },
1416
+ gaHoldOpen: { value: '' },
1417
+ nameHoldOpen: { value: '' },
1418
+ dptHoldOpen: { value: '1.001' },
1419
+ gaDisable: { value: '' },
1420
+ nameDisable: { value: '' },
1421
+ dptDisable: { value: '1.001' },
1422
+ gaPhotocell: { value: '' },
1423
+ namePhotocell: { value: '' },
1424
+ dptPhotocell: { value: '1.001' },
1425
+ gaMoving: { value: '' },
1426
+ nameMoving: { value: '' },
1427
+ dptMoving: { value: '1.001' },
1428
+ gaObstruction: { value: '' },
1429
+ nameObstruction: { value: '' },
1430
+ dptObstruction: { value: '1.001' },
1431
+ autoCloseEnable: { value: true },
1432
+ autoCloseSeconds: { value: 120, validate: utilityNumber(0, false, (node) => utilityFieldValue(node, 'autoCloseEnable') !== false) },
1433
+ emitEvents: { value: false }
1434
+ },
1435
+ inputs: 1,
1436
+ outputs: 1,
1437
+ icon: 'node-knx-icon.svg',
1438
+ label: function () {
1439
+ return this.name || 'KNX Garage'
1440
+ },
1441
+ paletteLabel: 'KNX Garage',
1442
+ oneditprepare: function () {
1443
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1444
+ const editorSession = { active: true }
1445
+ this._knxUtilityProfileSession = editorSession
1446
+ // Ignore responses belonging to a closed editor or a previous gateway.
1447
+ const profileGetJSON = (url, callback) => {
1448
+ const serverId = $('#node-input-server').val()
1449
+ return $.getJSON(url, (data) => {
1450
+ if (editorSession.active && $('#node-input-server').val() === serverId) callback(data)
1451
+ })
1452
+ }
1453
+ const node = this
1454
+ const $knxServerInput = $('#node-input-server')
1455
+ try { RED.sidebar.show('help') } catch (error) { /* ignore */ }
1456
+ const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none'])
1457
+
1458
+ const resolveKnxServerValue = () => {
1459
+ const domValue = $knxServerInput.val()
1460
+ if (domValue !== undefined && domValue !== null) return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue
1461
+ if (node.server !== undefined && node.server !== null && node.server !== '') return node.server
1462
+ return ''
1463
+ }
1040
1464
 
1465
+ const hasKnxServerSelected = () => {
1466
+ const val = resolveKnxServerValue()
1467
+ return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)))
1468
+ }
1041
1469
 
1042
- },
1043
- oneditcancel: function () {
1044
- // Return to the info tab
1045
- try {
1046
- RED.sidebar.show("info");
1047
- } catch (error) { }
1470
+ const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map())
1471
+
1472
+ const fetchGroupAddresses = (serverId) => {
1473
+ if (!serverId) return Promise.resolve([])
1474
+ if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId))
1475
+ return new Promise((resolve) => {
1476
+ profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
1477
+ const list = Array.isArray(data) ? data : []
1478
+ KNX_GA_CACHE.set(serverId, list)
1479
+ resolve(list)
1480
+ }).fail(() => resolve([]))
1481
+ })
1482
+ }
1048
1483
 
1484
+ const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
1485
+ const $gaInput = $(gaSelector)
1486
+ const $nameInput = $(nameSelector)
1487
+ const $dptInput = $(dptSelector)
1488
+ if (!$gaInput.length) return
1049
1489
 
1490
+ const ensureAutocomplete = () => {
1491
+ const sourceFn = (request, response) => {
1492
+ if (!hasKnxServerSelected()) {
1493
+ response([])
1494
+ return
1495
+ }
1496
+ const serverId = resolveKnxServerValue()
1497
+ fetchGroupAddresses(serverId).then((data) => {
1498
+ if (!editorSession.active || resolveKnxServerValue() !== serverId) return
1499
+ const items = []
1500
+ data.forEach((entry) => {
1501
+ const dpt = entry.dpt || ''
1502
+ const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix))
1503
+ if (!allowed) return
1504
+ const devName = entry.devicename || ''
1505
+ const searchStr = `${entry.ga} (${devName}) DPT${dpt}`
1506
+ if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return
1507
+ items.push({
1508
+ label: `${entry.ga} # ${devName} # ${dpt}`,
1509
+ value: entry.ga,
1510
+ dpt
1511
+ })
1512
+ })
1513
+ response(items)
1514
+ })
1050
1515
  }
1051
- })
1052
- },
1053
- "logger": function (RED) {
1054
- const utilityFieldValue = (node, key) => {
1055
- if (node._utilityEditor && typeof $ === 'function') {
1056
- const field = $('#node-input-' + key);
1057
- if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
1058
- }
1059
- return node[key];
1060
- };
1061
- const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
1062
- if (!enabled(this)) return true;
1063
- const numeric = Number(value);
1064
- return value !== null && value !== undefined && String(value).trim() !== '' &&
1065
- Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
1066
- };
1067
1516
 
1068
- RED.nodes.registerType('knxUltimateLogger', {
1069
- category: "KNX Ultimate",
1070
- color: '#C7E9C0',
1071
- defaults: {
1072
- server: { type: "knxUltimate-config", required: true },
1073
- topic: { value: "" },
1074
- intervalCreateETSXML: { value: 15, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'autoStartTimerCreateETSXML') !== false) },
1075
- name: { value: "" },
1076
- autoStartTimerCreateETSXML: { value: true },
1077
- maxRowsInETSXML: { value: 0, validate: utilityNumber(0, true) },
1078
- saveMode: { value: "emit" },
1079
- filePath: { value: "" },
1080
- autoStartTimerTelegramCounter: { value: false },
1081
- intervalTelegramCount: { value: 60, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'autoStartTimerTelegramCounter') === true) }
1082
-
1083
- },
1084
- inputs: 1,
1085
- outputs: 2,
1086
- outputLabels: ["ETS Diag file", "Telegram Count"],
1087
- icon: "node-logger-icon.svg",
1088
- label: function () {
1089
- return ((this.name || "KNX Logger") + " " + this.topic);
1090
- },
1091
- paletteLabel: "KNX Logger",
1092
- oneditprepare: function () {
1093
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1094
- const editorSession = { active: true };
1095
- this._knxUtilityProfileSession = editorSession;
1096
- // Ignore responses belonging to a closed editor or a previous gateway.
1097
- const profileGetJSON = (url, callback) => {
1098
- const serverId = $('#node-input-server').val();
1099
- return $.getJSON(url, (data) => {
1100
- if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
1101
- });
1102
- };
1103
- // Go to the help panel
1104
- try {
1105
- RED.sidebar.show("help");
1106
- } catch (error) { }
1107
-
1108
- $("#mlxETSFileAccordion").accordion({
1109
- header: "h3",
1110
- heightStyle: "content",
1111
- collapsible: true,
1112
- active: false
1113
- });
1114
-
1115
- const nodeId = this.id;
1116
- const resolveAdminRoot = () => {
1117
- const raw = (RED.settings && typeof RED.settings.httpAdminRoot === "string") ? RED.settings.httpAdminRoot : "/";
1118
- const trimmed = String(raw || "/").trim();
1119
- if (trimmed === "" || trimmed === "/") return "";
1120
- return "/" + trimmed.replace(/^\/+|\/+$/g, "");
1121
- };
1122
- const resolveAccessToken = () => {
1517
+ if ($gaInput.data('knx-ga-initialised')) {
1518
+ $gaInput.autocomplete('option', 'source', sourceFn)
1519
+ } else {
1520
+ $gaInput
1521
+ .autocomplete({
1522
+ minLength: 0,
1523
+ source: sourceFn,
1524
+ select: (event, ui) => {
1525
+ let deviceName = ''
1123
1526
  try {
1124
- const tokens = (RED.settings && typeof RED.settings.get === "function") ? RED.settings.get("auth-tokens") : null;
1125
- const token = tokens && typeof tokens.access_token === "string" ? tokens.access_token.trim() : "";
1126
- return token;
1127
- } catch (error) {
1128
- return "";
1527
+ deviceName = ui.item.label.split('#')[1].trim()
1528
+ deviceName = deviceName.replace(/^\)/, '').trim()
1529
+ } catch (error) { deviceName = '' }
1530
+ if ($nameInput.length) {
1531
+ if (deviceName && deviceName !== '') {
1532
+ $nameInput.val(deviceName)
1533
+ } else if (!$nameInput.val()) {
1534
+ $nameInput.val('')
1535
+ }
1129
1536
  }
1130
- };
1537
+ try {
1538
+ const parts = ui.item.label.split('#')
1539
+ const dptFromLabel = parts.length >= 3 ? parts[2].trim() : ''
1540
+ if (dptFromLabel !== '') {
1541
+ $dptInput.val(dptFromLabel)
1542
+ }
1543
+ } catch (error) { /* ignore */ }
1544
+ }
1545
+ })
1546
+ .on('focus.knxUltimateGarage click.knxUltimateGarage', function () {
1547
+ const currentValue = $(this).val() || ''
1548
+ try { $(this).autocomplete('search', `${currentValue} exactmatch`) } catch (error) { /* ignore */ }
1549
+ })
1550
+ $gaInput.data('knx-ga-initialised', true)
1551
+ }
1552
+ try {
1553
+ if (hasKnxServerSelected()) {
1554
+ const srv = RED.nodes.node(resolveKnxServerValue())
1555
+ if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id)
1556
+ }
1557
+ } catch (error) { /* ignore */ }
1558
+ }
1131
1559
 
1132
- const toggleFilePath = () => {
1133
- const isEmitSave = $("#node-input-saveMode").val() === "emit_save";
1134
- const $filePathRow = $("#knx-logger-filePath-row");
1135
- if (isEmitSave) {
1136
- $filePathRow.show();
1137
- } else {
1138
- $filePathRow.hide();
1139
- }
1140
- };
1141
- $("#node-input-saveMode").on("change.knxUtilityProfile", toggleFilePath);
1142
-
1143
- const currentFilePath = this.filePath || "";
1144
- $("#node-input-filePath").val(currentFilePath);
1145
- toggleFilePath();
1146
-
1147
- $("#knx-logger-downloadButton").on("click.knxUtilityProfile", function (evt) {
1148
- evt.preventDefault();
1149
- const filePathVal = $("#node-input-filePath").val() || "";
1150
- if (!filePathVal) {
1151
- try {
1152
- const msg = (RED._ && RED._("node-red-contrib-knx-ultimate/knxUltimateLogger:knxUltimateLogger.noFilePath")) || "File path is empty";
1153
- RED.notify(msg, "warning");
1154
- } catch (error) {
1155
- alert("File path is empty");
1156
- }
1157
- return;
1158
- }
1159
- const adminRoot = resolveAdminRoot();
1160
- const targetBase = adminRoot + "/knxUltimateUtility/logger/download";
1161
- const params = new URLSearchParams();
1162
- if (nodeId) params.set("nodeId", nodeId);
1163
- params.set("_", String(Date.now()));
1164
- const accessToken = resolveAccessToken();
1165
- if (accessToken) params.set("access_token", accessToken);
1166
- const target = targetBase + "?" + params.toString();
1167
- const wnd = window.open(target, "_blank", "noopener,noreferrer");
1168
- try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
1169
- });
1170
- },
1171
- oneditcancel: function () {
1172
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1173
- $('#node-input-server').off('.knxUtilityProfile');
1174
- try { RED.sidebar.show('info'); } catch (error) { }
1175
- },
1176
- oneditsave: function () {
1177
- // Return to the info tab
1178
- try {
1179
- RED.sidebar.show("info");
1180
- } catch (error) { }
1560
+ ensureAutocomplete()
1561
+ }
1181
1562
 
1182
- this.filePath = $("#node-input-filePath").val() || "";
1563
+ const BINARY_PREFIX = ['1.']
1183
1564
 
1565
+ const refreshKnxBindings = () => {
1566
+ if (!hasKnxServerSelected()) return
1184
1567
 
1185
- }
1568
+ getGroupAddress('#node-input-gaCommand', '#node-input-nameCommand', '#node-input-dptCommand', BINARY_PREFIX)
1569
+ getGroupAddress('#node-input-gaImpulse', '#node-input-nameImpulse', '#node-input-dptImpulse', BINARY_PREFIX)
1570
+ getGroupAddress('#node-input-gaHoldOpen', '#node-input-nameHoldOpen', '#node-input-dptHoldOpen', BINARY_PREFIX)
1571
+ getGroupAddress('#node-input-gaDisable', '#node-input-nameDisable', '#node-input-dptDisable', BINARY_PREFIX)
1572
+ getGroupAddress('#node-input-gaPhotocell', '#node-input-namePhotocell', '#node-input-dptPhotocell', BINARY_PREFIX)
1573
+ getGroupAddress('#node-input-gaMoving', '#node-input-nameMoving', '#node-input-dptMoving', BINARY_PREFIX)
1574
+ getGroupAddress('#node-input-gaObstruction', '#node-input-nameObstruction', '#node-input-dptObstruction', BINARY_PREFIX)
1575
+ }
1186
1576
 
1577
+ $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
1578
+ KNX_GA_CACHE.clear()
1579
+ refreshKnxBindings()
1187
1580
  })
1188
- },
1189
- "staircase": function (RED) {
1190
- const utilityFieldValue = (node, key) => {
1191
- if (node._utilityEditor && typeof $ === 'function') {
1192
- const field = $('#node-input-' + key);
1193
- if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
1581
+ if (hasKnxServerSelected()) refreshKnxBindings()
1582
+
1583
+ const syncAutoClose = () => {
1584
+ const enabled = $('#node-input-autoCloseEnable').is(':checked')
1585
+ const $seconds = $('#node-input-autoCloseSeconds').closest('.form-row')
1586
+ $seconds.toggle(enabled)
1587
+ }
1588
+
1589
+ $('#node-input-autoCloseEnable').on('change.knxUtilityProfile', syncAutoClose)
1590
+ syncAutoClose()
1591
+ },
1592
+ oneditcancel: function () {
1593
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1594
+ $('#node-input-server').off('.knxUtilityProfile')
1595
+ try { RED.sidebar.show('info') } catch (error) { }
1194
1596
  }
1195
- return node[key];
1196
- };
1197
- const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
1198
- if (!enabled(this)) return true;
1199
- const numeric = Number(value);
1200
- return value !== null && value !== undefined && String(value).trim() !== '' &&
1201
- Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
1202
- };
1597
+ })
1598
+ },
1599
+ "scenecontroller": function (RED) {
1600
+ RED.nodes.registerType('knxUltimateSceneController', {
1601
+ category: 'KNX Ultimate',
1602
+ color: '#C7E9C0',
1603
+ defaults: {
1604
+ server: { type: 'knxUltimate-config', required: true },
1605
+ name: { value: '' },
1606
+ outputtopic: { value: '' },
1607
+ topic: { value: '' },
1608
+ dpt: { value: '' },
1609
+ topicTrigger: { value: 'true' },
1610
+ topicSave: { value: '' },
1611
+ dptSave: { value: '' },
1612
+ topicSaveTrigger: { value: 'true' },
1613
+
1614
+ rules: { value: [] }
1615
+ },
1616
+ inputs: 1,
1617
+ outputs: 1,
1618
+ icon: 'node-scene-icon.svg',
1619
+ label: function () {
1620
+ return (this.outputRBE == true ? '|rbe| ' : '') + (this.name || this.topic || 'KNX Scene Controller') + (this.inputRBE == true ? ' |rbe|' : '')
1621
+ },
1622
+ paletteLabel: 'KNX Scene Controller',
1623
+ oneditprepare: function () {
1624
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1625
+ const editorSession = { active: true }
1626
+ this._knxUtilityProfileSession = editorSession
1627
+ // Ignore responses belonging to a closed editor or a previous gateway.
1628
+ const profileGetJSON = (url, callback) => {
1629
+ const serverId = $('#node-input-server').val()
1630
+ return $.getJSON(url, (data) => {
1631
+ if (editorSession.active && $('#node-input-server').val() === serverId) callback(data)
1632
+ })
1633
+ }
1203
1634
 
1204
- RED.nodes.registerType('knxUltimateStaircase', {
1205
- category: 'KNX Ultimate',
1206
- color: '#C7E9C0',
1207
- defaults: {
1208
- server: { type: 'knxUltimate-config', required: true },
1209
- name: { value: '' },
1210
- outputtopic: { value: '' },
1211
- gaTrigger: { value: '', required: true },
1212
- nameTrigger: { value: '' },
1213
- dptTrigger: { value: '1.001' },
1214
- gaOutput: { value: '', required: true },
1215
- nameOutput: { value: '' },
1216
- dptOutput: { value: '1.001' },
1217
- gaStatus: { value: '' },
1218
- nameStatus: { value: '' },
1219
- dptStatus: { value: '1.001' },
1220
- gaOverride: { value: '' },
1221
- nameOverride: { value: '' },
1222
- dptOverride: { value: '1.001' },
1223
- gaBlock: { value: '' },
1224
- nameBlock: { value: '' },
1225
- dptBlock: { value: '1.001' },
1226
- timerSeconds: { value: 120, required: true, validate: utilityNumber(1) },
1227
- extendMode: { value: 'restart' },
1228
- triggerOffCancels: { value: 'yes' },
1229
- preWarnEnable: { value: false },
1230
- preWarnSeconds: { value: 15, validate: utilityNumber(0, false, (node) => utilityFieldValue(node, 'preWarnEnable') === true) },
1231
- preWarnMode: { value: 'status' },
1232
- preWarnFlashMs: { value: 400, validate: utilityNumber(100, false, (node) => utilityFieldValue(node, 'preWarnEnable') === true && utilityFieldValue(node, 'preWarnMode') === 'flash') },
1233
- blockAction: { value: 'off' },
1234
- emitEvents: { value: false }
1235
- },
1236
- inputs: 1,
1237
- outputs: 1,
1238
- icon: 'node-knx-icon.svg',
1239
- label: function () {
1240
- return this.name || 'KNX Staircase';
1241
- },
1242
- paletteLabel: 'KNX Staircase',
1243
- oneditprepare: function () {
1244
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1245
- const editorSession = { active: true };
1246
- this._knxUtilityProfileSession = editorSession;
1247
- // Ignore responses belonging to a closed editor or a previous gateway.
1248
- const profileGetJSON = (url, callback) => {
1249
- const serverId = $('#node-input-server').val();
1250
- return $.getJSON(url, (data) => {
1251
- if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
1252
- });
1253
- };
1254
- const node = this;
1255
- const $knxServerInput = $('#node-input-server');
1256
- try { RED.sidebar.show('help'); } catch (error) { /* ignore */ }
1257
- const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
1258
-
1259
- const resolveKnxServerValue = () => {
1260
- const domValue = $knxServerInput.val();
1261
- if (domValue !== undefined && domValue !== null) return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue;
1262
- if (node.server !== undefined && node.server !== null && node.server !== '') return node.server;
1263
- return '';
1264
- };
1265
-
1266
- const hasKnxServerSelected = () => {
1267
- const val = resolveKnxServerValue();
1268
- return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)));
1269
- };
1270
-
1271
- const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
1272
-
1273
- const fetchGroupAddresses = (serverId) => {
1274
- if (!serverId) return Promise.resolve([]);
1275
- if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
1276
- return new Promise((resolve) => {
1277
- profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
1278
- const list = Array.isArray(data) ? data : [];
1279
- KNX_GA_CACHE.set(serverId, list);
1280
- resolve(list);
1281
- }).fail(() => resolve([]));
1282
- });
1283
- };
1284
-
1285
- const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
1286
- const $gaInput = $(gaSelector);
1287
- const $nameInput = $(nameSelector);
1288
- const $dptInput = $(dptSelector);
1289
- if (!$gaInput.length) return;
1290
-
1291
- const ensureAutocomplete = () => {
1292
- const sourceFn = (request, response) => {
1293
- if (!hasKnxServerSelected()) {
1294
- response([]);
1295
- return;
1296
- }
1297
- const serverId = resolveKnxServerValue();
1298
- fetchGroupAddresses(serverId).then((data) => {
1299
- if (!editorSession.active || resolveKnxServerValue() !== serverId) return;
1300
- const items = [];
1301
- data.forEach((entry) => {
1302
- const dpt = entry.dpt || '';
1303
- const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix));
1304
- if (!allowed) return;
1305
- const devName = entry.devicename || '';
1306
- const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
1307
- if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
1308
- items.push({
1309
- label: `${entry.ga} # ${devName} # ${dpt}`,
1310
- value: entry.ga,
1311
- ga: entry.ga
1312
- });
1313
- });
1314
- response(items);
1315
- });
1316
- };
1317
-
1318
- if ($gaInput.data('knx-ga-initialised')) {
1319
- $gaInput.autocomplete('option', 'source', sourceFn);
1320
- } else {
1321
- $gaInput
1322
- .autocomplete({
1323
- minLength: 0,
1324
- source: sourceFn,
1325
- select: (event, ui) => {
1326
- let deviceName = '';
1327
- try {
1328
- deviceName = ui.item.label.split('#')[1].trim();
1329
- deviceName = deviceName.replace(/^\)/, '').trim();
1330
- } catch (error) { deviceName = ''; }
1331
- if ($nameInput.length) {
1332
- if (deviceName && deviceName !== '') {
1333
- $nameInput.val(deviceName);
1334
- } else if (!$nameInput.val()) {
1335
- $nameInput.val('');
1336
- }
1337
- }
1338
- try {
1339
- const parts = ui.item.label.split('#');
1340
- const dptFromLabel = parts.length >= 3 ? parts[2].trim() : '';
1341
- if (dptFromLabel !== '') {
1342
- $dptInput.val(dptFromLabel);
1343
- }
1344
- } catch (error) { /* ignore */ }
1345
- }
1346
- })
1347
- .on('focus.knxUltimateStaircase click.knxUltimateStaircase', function () {
1348
- const currentValue = $(this).val() || '';
1349
- try { $(this).autocomplete('search', `${currentValue} exactmatch`); } catch (error) { /* ignore */ }
1350
- });
1351
- $gaInput.data('knx-ga-initialised', true);
1352
- }
1353
- try {
1354
- if (hasKnxServerSelected()) {
1355
- const srv = RED.nodes.node(resolveKnxServerValue());
1356
- if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
1357
- }
1358
- } catch (error) { /* ignore */ }
1359
- };
1635
+ const dptFields = []
1636
+ const dptRequests = new Map()
1637
+ const loadDptOptions = (field, savedValue, track = true) => {
1638
+ if (track) {
1639
+ dptFields.push(field)
1640
+ // Preserve persisted values even when Done is pressed before
1641
+ // the asynchronous DPT catalog has arrived.
1642
+ field.append($('<option></option>').attr('value', savedValue || '').text(savedValue || ''))
1643
+ field.val(savedValue || '')
1644
+ }
1645
+ const serverId = $('#node-input-server').val()
1646
+ if (!dptRequests.has(serverId)) {
1647
+ dptRequests.set(serverId, new Promise((resolve) => {
1648
+ profileGetJSON('knxUltimateDpts?serverId=' + encodeURIComponent(serverId || ''), resolve)
1649
+ }))
1650
+ }
1651
+ dptRequests.get(serverId).then((data) => {
1652
+ if (!editorSession.active || $('#node-input-server').val() !== serverId) return
1653
+ const selected = field.val()
1654
+ field.empty().append($('<option></option>').attr('value', '').text(''))
1655
+ const rows = Array.isArray(data) ? data : []
1656
+ rows.forEach((dpt) => field.append($('<option></option>').attr('value', dpt.value).text(dpt.text)))
1657
+ if (selected && !rows.some((dpt) => String(dpt.value) === String(selected))) {
1658
+ field.append($('<option></option>').attr('value', selected).text(selected))
1659
+ }
1660
+ field.val(selected || '')
1661
+ })
1662
+ }
1663
+ // Go to the help panel
1664
+ try {
1665
+ RED.sidebar.show('help')
1666
+ } catch (error) { }
1360
1667
 
1361
- ensureAutocomplete();
1362
- };
1668
+ const node = this
1669
+ let oNodeServer = RED.nodes.node($('#node-input-server').val()) // Store the config-node
1363
1670
 
1364
- const BINARY_PREFIX = ['1.'];
1365
- const ANY_ANALOG_PREFIX = ['1.', '2.', '5.', '6.', '7.', '8.', '9.', '12.', '13.', '14.', '16.', '20.'];
1671
+ // 19/02/2020 Used to get the server sooner als deploy.
1672
+ $('#node-input-server').off('.knxUtilityProfile').on('change.knxUtilityProfile', function () {
1673
+ try {
1674
+ oNodeServer = RED.nodes.node($(this).val())
1675
+ dptFields.forEach((field) => loadDptOptions(field, field.val(), false))
1676
+ } catch (error) { }
1677
+ })
1366
1678
 
1367
- const refreshKnxBindings = () => {
1368
- if (!hasKnxServerSelected()) {
1369
- return;
1679
+ // DPT of Scene Recall
1680
+ // ########################
1681
+ loadDptOptions($('#node-input-dpt'), this.dpt)
1682
+
1683
+ // Autocomplete suggestion with ETS csv File
1684
+ $('#node-input-topic').autocomplete({
1685
+ minLength: 0,
1686
+ source: function (request, response) {
1687
+ // profileGetJSON("csv", request, function( data, status, xhr ) {
1688
+ if (!oNodeServer || !oNodeServer.id) { response([]); return }
1689
+ profileGetJSON('knxUltimatecsv?nodeID=' + oNodeServer.id, (data) => {
1690
+ response($.map(data, function (value, key) {
1691
+ const sSearch = (value.ga + ' (' + value.devicename + ') DPT' + value.dpt)
1692
+ if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1693
+ return {
1694
+ label: value.ga + ' # ' + value.devicename + ' # ' + value.dpt, // Label for Display
1695
+ value: value.ga // Value
1696
+ }
1697
+ } else {
1698
+ return null
1699
+ }
1700
+ }))
1701
+ })
1702
+ },
1703
+ select: function (event, ui) {
1704
+ // Sets Datapoint and device name automatically
1705
+ let sDevName = ui.item.label.split('#')[1].trim()
1706
+ try {
1707
+ sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim()
1708
+ } catch (error) {
1370
1709
  }
1371
-
1372
- getGroupAddress('#node-input-gaTrigger', '#node-input-nameTrigger', '#node-input-dptTrigger', BINARY_PREFIX);
1373
- getGroupAddress('#node-input-gaOutput', '#node-input-nameOutput', '#node-input-dptOutput', ANY_ANALOG_PREFIX);
1374
- getGroupAddress('#node-input-gaStatus', '#node-input-nameStatus', '#node-input-dptStatus', ANY_ANALOG_PREFIX);
1375
- getGroupAddress('#node-input-gaOverride', '#node-input-nameOverride', '#node-input-dptOverride', BINARY_PREFIX);
1376
- getGroupAddress('#node-input-gaBlock', '#node-input-nameBlock', '#node-input-dptBlock', BINARY_PREFIX);
1377
- };
1378
-
1379
- $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
1380
- KNX_GA_CACHE.clear();
1381
- refreshKnxBindings();
1382
- });
1383
-
1384
- refreshKnxBindings();
1385
-
1386
- const $preWarnRows = $('.knx-staircase-prewarn');
1387
- const $flashRow = $('.knx-staircase-prewarn-flash');
1388
-
1389
- function syncPreWarnMode() {
1390
- const enabled = $('#node-input-preWarnEnable').is(':checked');
1391
- $preWarnRows.toggle(enabled);
1392
- if (!enabled) {
1393
- $flashRow.hide();
1394
- return;
1710
+ $('#node-input-name').val('Recall: ' + sDevName + '/' + $('#node-input-name').val().split('/')[1])
1711
+ const optVal = $("#node-input-dpt option:contains('" + ui.item.label.split('#')[2].trim() + "')").attr('value')
1712
+ const $dptSelect = $('#node-input-dpt')
1713
+ if (optVal !== undefined && optVal !== null) {
1714
+ $dptSelect.val(optVal).trigger('change')
1715
+ } else {
1716
+ $dptSelect.trigger('change')
1395
1717
  }
1396
- const mode = $('#node-input-preWarnMode').val();
1397
- $flashRow.toggle(mode === 'flash');
1398
1718
  }
1399
-
1400
- $('#node-input-preWarnEnable').on('change.knxUtilityProfile', syncPreWarnMode);
1401
- $('#node-input-preWarnMode').on('change.knxUtilityProfile', syncPreWarnMode);
1402
- syncPreWarnMode();
1403
- },
1404
- oneditcancel: function () {
1405
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1406
- $('#node-input-server').off('.knxUtilityProfile');
1407
- try { RED.sidebar.show('info'); } catch (error) { }
1719
+ }).focus(function () {
1720
+ $(this).autocomplete('search', $(this).val() + 'exactmatch')
1721
+ })
1722
+ try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($('#node-input-topicSave'), oNodeServer.id) } catch (e) {}
1723
+ try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($('#node-input-topic'), oNodeServer.id) } catch (e) {}
1724
+
1725
+ // 19/03/2020 Adjust trigger value accordingly
1726
+ $('#node-input-dpt').on('change.knxUtilityProfile', function () {
1727
+ if ($(this).val() !== null) {
1728
+ if ($(this).val().indexOf('3.007') > -1) {
1729
+ // It's a DIM. Suggest the right value
1730
+ $('#node-input-topicTrigger').val('{decr_incr:1, data:5}')
1731
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT3007'),
1732
+ {
1733
+ modal: true,
1734
+ fixed: true,
1735
+ type: 'info',
1736
+ buttons: [
1737
+ {
1738
+ text: 'OK',
1739
+ click: function (e) {
1740
+ myNotification.close()
1741
+ }
1742
+ }]
1743
+ })
1744
+ } else if ($(this).val().indexOf('18.001') > -1) {
1745
+ // It's a scene actuator. Suggest the right value
1746
+ $('#node-input-topicTrigger').val('{save_recall:0, scenenumber:2}')
1747
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT18001'),
1748
+ {
1749
+ modal: true,
1750
+ fixed: true,
1751
+ type: 'info',
1752
+ buttons: [
1753
+ {
1754
+ text: 'OK',
1755
+ click: function (e) {
1756
+ myNotification.close()
1757
+ }
1758
+ }]
1759
+ })
1760
+ } else if ($(this).val().indexOf('232.600') > -1) {
1761
+ // It's a scene actuator. Suggest the right value
1762
+ $('#node-input-topicTrigger').val('{red:0, green:0, blue:0}')
1763
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT232600'),
1764
+ {
1765
+ modal: true,
1766
+ fixed: true,
1767
+ type: 'info',
1768
+ buttons: [
1769
+ {
1770
+ text: 'OK',
1771
+ click: function (e) {
1772
+ myNotification.close()
1773
+ }
1774
+ }]
1775
+ })
1776
+ } else if ($(this).val().indexOf('251.600') > -1) {
1777
+ // It's a scene actuator. Suggest the right value
1778
+ $('#node-input-topicTrigger').val('{red:0, green:0, blue:0, white:0, mR:1, mG:1, mB:1, mW:1}')
1779
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT251600'),
1780
+ {
1781
+ modal: true,
1782
+ fixed: true,
1783
+ type: 'info',
1784
+ buttons: [
1785
+ {
1786
+ text: 'OK',
1787
+ click: function (e) {
1788
+ myNotification.close()
1789
+ }
1790
+ }]
1791
+ })
1408
1792
  }
1409
- });
1410
- },
1411
- "garage": function (RED) {
1412
- const utilityFieldValue = (node, key) => {
1413
- if (node._utilityEditor && typeof $ === 'function') {
1414
- const field = $('#node-input-' + key);
1415
- if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
1416
- }
1417
- return node[key];
1418
- };
1419
- const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
1420
- if (!enabled(this)) return true;
1421
- const numeric = Number(value);
1422
- return value !== null && value !== undefined && String(value).trim() !== '' &&
1423
- Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
1424
- };
1425
-
1426
- RED.nodes.registerType('knxUltimateGarage', {
1427
- category: 'KNX Ultimate',
1428
- color: '#C7E9C0',
1429
- defaults: {
1430
- server: { type: 'knxUltimate-config', required: true },
1431
- name: { value: '' },
1432
- outputtopic: { value: '' },
1433
- gaCommand: { value: '', required: true },
1434
- nameCommand: { value: '' },
1435
- dptCommand: { value: '1.001' },
1436
- gaImpulse: { value: '' },
1437
- nameImpulse: { value: '' },
1438
- dptImpulse: { value: '1.017' },
1439
- gaHoldOpen: { value: '' },
1440
- nameHoldOpen: { value: '' },
1441
- dptHoldOpen: { value: '1.001' },
1442
- gaDisable: { value: '' },
1443
- nameDisable: { value: '' },
1444
- dptDisable: { value: '1.001' },
1445
- gaPhotocell: { value: '' },
1446
- namePhotocell: { value: '' },
1447
- dptPhotocell: { value: '1.001' },
1448
- gaMoving: { value: '' },
1449
- nameMoving: { value: '' },
1450
- dptMoving: { value: '1.001' },
1451
- gaObstruction: { value: '' },
1452
- nameObstruction: { value: '' },
1453
- dptObstruction: { value: '1.001' },
1454
- autoCloseEnable: { value: true },
1455
- autoCloseSeconds: { value: 120, validate: utilityNumber(0, false, (node) => utilityFieldValue(node, 'autoCloseEnable') !== false) },
1456
- emitEvents: { value: false }
1457
- },
1458
- inputs: 1,
1459
- outputs: 1,
1460
- icon: 'node-knx-icon.svg',
1461
- label: function () {
1462
- return this.name || 'KNX Garage';
1463
- },
1464
- paletteLabel: 'KNX Garage',
1465
- oneditprepare: function () {
1466
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1467
- const editorSession = { active: true };
1468
- this._knxUtilityProfileSession = editorSession;
1469
- // Ignore responses belonging to a closed editor or a previous gateway.
1470
- const profileGetJSON = (url, callback) => {
1471
- const serverId = $('#node-input-server').val();
1472
- return $.getJSON(url, (data) => {
1473
- if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
1474
- });
1475
- };
1476
- const node = this;
1477
- const $knxServerInput = $('#node-input-server');
1478
- try { RED.sidebar.show('help'); } catch (error) { /* ignore */ }
1479
- const KNX_EMPTY_VALUES = new Set(['', '_ADD_', '__NONE__', 'none']);
1480
-
1481
- const resolveKnxServerValue = () => {
1482
- const domValue = $knxServerInput.val();
1483
- if (domValue !== undefined && domValue !== null) return KNX_EMPTY_VALUES.has(String(domValue)) ? '' : domValue;
1484
- if (node.server !== undefined && node.server !== null && node.server !== '') return node.server;
1485
- return '';
1486
- };
1487
-
1488
- const hasKnxServerSelected = () => {
1489
- const val = resolveKnxServerValue();
1490
- return !(val === undefined || val === null || KNX_EMPTY_VALUES.has(String(val)));
1491
- };
1492
-
1493
- const KNX_GA_CACHE = node._knxGaCache || (node._knxGaCache = new Map());
1494
-
1495
- const fetchGroupAddresses = (serverId) => {
1496
- if (!serverId) return Promise.resolve([]);
1497
- if (KNX_GA_CACHE.has(serverId)) return Promise.resolve(KNX_GA_CACHE.get(serverId));
1498
- return new Promise((resolve) => {
1499
- profileGetJSON(`knxUltimatecsv?nodeID=${serverId}&_=${Date.now()}`, (data) => {
1500
- const list = Array.isArray(data) ? data : [];
1501
- KNX_GA_CACHE.set(serverId, list);
1502
- resolve(list);
1503
- }).fail(() => resolve([]));
1504
- });
1505
- };
1506
-
1507
- const getGroupAddress = (gaSelector, nameSelector, dptSelector, prefixes) => {
1508
- const $gaInput = $(gaSelector);
1509
- const $nameInput = $(nameSelector);
1510
- const $dptInput = $(dptSelector);
1511
- if (!$gaInput.length) return;
1512
-
1513
- const ensureAutocomplete = () => {
1514
- const sourceFn = (request, response) => {
1515
- if (!hasKnxServerSelected()) {
1516
- response([]);
1517
- return;
1793
+ }
1794
+ })
1795
+ // ########################
1796
+
1797
+ // DPT of Scene Save
1798
+ // ########################
1799
+ loadDptOptions($('#node-input-dptSave'), this.dptSave)
1800
+
1801
+ // Autocomplete suggestion with ETS csv File
1802
+ $('#node-input-topicSave').autocomplete({
1803
+ minLength: 0,
1804
+ source: function (request, response) {
1805
+ if (!oNodeServer || !oNodeServer.id) { response([]); return }
1806
+ profileGetJSON('knxUltimatecsv?nodeID=' + oNodeServer.id, (data) => {
1807
+ response($.map(data, function (value, key) {
1808
+ const sSearch = (value.ga + ' (' + value.devicename + ') DPT' + value.dpt)
1809
+ if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1810
+ return {
1811
+ label: value.ga + ' # ' + value.devicename + ' # ' + value.dpt, // Label for Display
1812
+ value: value.ga // Value
1813
+ }
1814
+ } else {
1815
+ return null
1518
1816
  }
1519
- const serverId = resolveKnxServerValue();
1520
- fetchGroupAddresses(serverId).then((data) => {
1521
- if (!editorSession.active || resolveKnxServerValue() !== serverId) return;
1522
- const items = [];
1523
- data.forEach((entry) => {
1524
- const dpt = entry.dpt || '';
1525
- const allowed = prefixes.some((prefix) => prefix === '' || dpt.startsWith(prefix));
1526
- if (!allowed) return;
1527
- const devName = entry.devicename || '';
1528
- const searchStr = `${entry.ga} (${devName}) DPT${dpt}`;
1529
- if (!htmlUtilsfullCSVSearch(searchStr, request.term || '')) return;
1530
- items.push({
1531
- label: `${entry.ga} # ${devName} # ${dpt}`,
1532
- value: entry.ga,
1533
- dpt
1534
- });
1535
- });
1536
- response(items);
1537
- });
1538
- };
1539
-
1540
- if ($gaInput.data('knx-ga-initialised')) {
1541
- $gaInput.autocomplete('option', 'source', sourceFn);
1542
- } else {
1543
- $gaInput
1544
- .autocomplete({
1545
- minLength: 0,
1546
- source: sourceFn,
1547
- select: (event, ui) => {
1548
- let deviceName = '';
1549
- try {
1550
- deviceName = ui.item.label.split('#')[1].trim();
1551
- deviceName = deviceName.replace(/^\)/, '').trim();
1552
- } catch (error) { deviceName = ''; }
1553
- if ($nameInput.length) {
1554
- if (deviceName && deviceName !== '') {
1555
- $nameInput.val(deviceName);
1556
- } else if (!$nameInput.val()) {
1557
- $nameInput.val('');
1558
- }
1817
+ }))
1818
+ })
1819
+ },
1820
+ select: function (event, ui) {
1821
+ // Sets Datapoint and device name automatically
1822
+ let sDevName = ui.item.label.split('#')[1].trim()
1823
+ try {
1824
+ sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim()
1825
+ } catch (error) {
1826
+ }
1827
+ $('#node-input-name').val($('#node-input-name').val().split('/')[0] + '/Save: ' + sDevName)
1828
+ const optVal = $("#node-input-dptSave option:contains('" + ui.item.label.split('#')[2].trim() + "')").attr('value')
1829
+ const $dptSelect = $('#node-input-dptSave')
1830
+ if (optVal !== undefined && optVal !== null) {
1831
+ $dptSelect.val(optVal).trigger('change')
1832
+ } else {
1833
+ $dptSelect.trigger('change')
1834
+ }
1835
+ }
1836
+ }).focus(function () {
1837
+ $(this).autocomplete('search', $(this).val() + 'exactmatch')
1838
+ })
1839
+
1840
+ // 02/04/2020 Adjust trigger value accordingly
1841
+ $('#node-input-dptSave').on('change.knxUtilityProfile', function () {
1842
+ if ($(this).val() !== null) {
1843
+ if ($(this).val().indexOf('3.007') > -1) {
1844
+ // It's a DIM. Suggest the right value
1845
+ $('#node-input-topicSaveTrigger').val('{decr_incr:1, data:5}')
1846
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT3007'),
1847
+ {
1848
+ modal: true,
1849
+ fixed: true,
1850
+ type: 'info',
1851
+ buttons: [
1852
+ {
1853
+ text: 'OK',
1854
+ click: function (e) {
1855
+ myNotification.close()
1559
1856
  }
1560
- try {
1561
- const parts = ui.item.label.split('#');
1562
- const dptFromLabel = parts.length >= 3 ? parts[2].trim() : '';
1563
- if (dptFromLabel !== '') {
1564
- $dptInput.val(dptFromLabel);
1565
- }
1566
- } catch (error) { /* ignore */ }
1567
- }
1568
- })
1569
- .on('focus.knxUltimateGarage click.knxUltimateGarage', function () {
1570
- const currentValue = $(this).val() || '';
1571
- try { $(this).autocomplete('search', `${currentValue} exactmatch`); } catch (error) { /* ignore */ }
1572
- });
1573
- $gaInput.data('knx-ga-initialised', true);
1574
- }
1575
- try {
1576
- if (hasKnxServerSelected()) {
1577
- const srv = RED.nodes.node(resolveKnxServerValue());
1578
- if (srv && srv.id) KNX_enableSecureFormatting($gaInput, srv.id);
1579
- }
1580
- } catch (error) { /* ignore */ }
1581
- };
1582
-
1583
- ensureAutocomplete();
1584
- };
1585
-
1586
- const BINARY_PREFIX = ['1.'];
1587
-
1588
- const refreshKnxBindings = () => {
1589
- if (!hasKnxServerSelected()) return;
1590
-
1591
- getGroupAddress('#node-input-gaCommand', '#node-input-nameCommand', '#node-input-dptCommand', BINARY_PREFIX);
1592
- getGroupAddress('#node-input-gaImpulse', '#node-input-nameImpulse', '#node-input-dptImpulse', BINARY_PREFIX);
1593
- getGroupAddress('#node-input-gaHoldOpen', '#node-input-nameHoldOpen', '#node-input-dptHoldOpen', BINARY_PREFIX);
1594
- getGroupAddress('#node-input-gaDisable', '#node-input-nameDisable', '#node-input-dptDisable', BINARY_PREFIX);
1595
- getGroupAddress('#node-input-gaPhotocell', '#node-input-namePhotocell', '#node-input-dptPhotocell', BINARY_PREFIX);
1596
- getGroupAddress('#node-input-gaMoving', '#node-input-nameMoving', '#node-input-dptMoving', BINARY_PREFIX);
1597
- getGroupAddress('#node-input-gaObstruction', '#node-input-nameObstruction', '#node-input-dptObstruction', BINARY_PREFIX);
1598
- };
1599
-
1600
- $knxServerInput.off('.knxUtilityProfile').on('change.knxUtilityProfile', () => {
1601
- KNX_GA_CACHE.clear();
1602
- refreshKnxBindings();
1603
- });
1604
- if (hasKnxServerSelected()) refreshKnxBindings();
1605
-
1606
- const syncAutoClose = () => {
1607
- const enabled = $('#node-input-autoCloseEnable').is(':checked');
1608
- const $seconds = $('#node-input-autoCloseSeconds').closest('.form-row');
1609
- $seconds.toggle(enabled);
1610
- };
1611
-
1612
- $('#node-input-autoCloseEnable').on('change.knxUtilityProfile', syncAutoClose);
1613
- syncAutoClose();
1614
- },
1615
- oneditcancel: function () {
1616
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1617
- $('#node-input-server').off('.knxUtilityProfile');
1618
- try { RED.sidebar.show('info'); } catch (error) { }
1857
+ }]
1858
+ })
1859
+ } else if ($(this).val().indexOf('18.001') > -1) {
1860
+ // It's a scene actuator. Suggest the right value
1861
+ $('#node-input-topicSaveTrigger').val('{save_recall:1, scenenumber:2}')
1862
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT18001'),
1863
+ {
1864
+ modal: true,
1865
+ fixed: true,
1866
+ type: 'info',
1867
+ buttons: [
1868
+ {
1869
+ text: 'OK',
1870
+ click: function (e) {
1871
+ myNotification.close()
1872
+ }
1873
+ }]
1874
+ })
1875
+ } else if ($(this).val().indexOf('232.600') > -1) {
1876
+ // It's a scene actuator. Suggest the right value
1877
+ $('#node-input-topicSaveTrigger').val('{red:0, green:0, blue:0}')
1878
+ var myNotification = RED.notify(node._('knxUltimateSceneController.advanced.notify-DPT232600'),
1879
+ {
1880
+ modal: true,
1881
+ fixed: true,
1882
+ type: 'info',
1883
+ buttons: [
1884
+ {
1885
+ text: 'OK',
1886
+ click: function (e) {
1887
+ myNotification.close()
1888
+ }
1889
+ }]
1890
+ })
1619
1891
  }
1620
- });
1621
- },
1622
- "scenecontroller": function (RED) {
1623
- RED.nodes.registerType('knxUltimateSceneController', {
1624
- category: "KNX Ultimate",
1625
- color: '#C7E9C0',
1626
- defaults: {
1627
- server: { type: "knxUltimate-config", required: true },
1628
- name: { value: "" },
1629
- outputtopic: { value: "" },
1630
- topic: { value: "" },
1631
- dpt: { value: "" },
1632
- topicTrigger: { value: "true" },
1633
- topicSave: { value: "" },
1634
- dptSave: { value: "" },
1635
- topicSaveTrigger: { value: "true" },
1636
-
1637
- rules: { value: [] }
1638
- },
1639
- inputs: 1,
1640
- outputs: 1,
1641
- icon: "node-scene-icon.svg",
1642
- label: function () {
1643
- return (this.outputRBE == true ? "|rbe| " : "") + (this.name || this.topic || "KNX Scene Controller") + (this.inputRBE == true ? " |rbe|" : "")
1644
- },
1645
- paletteLabel: "KNX Scene Controller",
1646
- oneditprepare: function () {
1647
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
1648
- const editorSession = { active: true };
1649
- this._knxUtilityProfileSession = editorSession;
1650
- // Ignore responses belonging to a closed editor or a previous gateway.
1651
- const profileGetJSON = (url, callback) => {
1652
- const serverId = $('#node-input-server').val();
1653
- return $.getJSON(url, (data) => {
1654
- if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
1655
- });
1656
- };
1657
-
1658
- const dptFields = [];
1659
- const dptRequests = new Map();
1660
- const loadDptOptions = (field, savedValue, track = true) => {
1661
- if (track) {
1662
- dptFields.push(field);
1663
- // Preserve persisted values even when Done is pressed before
1664
- // the asynchronous DPT catalog has arrived.
1665
- field.append($('<option></option>').attr('value', savedValue || '').text(savedValue || ''));
1666
- field.val(savedValue || '');
1667
- }
1668
- const serverId = $('#node-input-server').val();
1669
- if (!dptRequests.has(serverId)) {
1670
- dptRequests.set(serverId, new Promise((resolve) => {
1671
- profileGetJSON('knxUltimateDpts?serverId=' + encodeURIComponent(serverId || ''), resolve);
1672
- }));
1673
- }
1674
- dptRequests.get(serverId).then((data) => {
1675
- if (!editorSession.active || $('#node-input-server').val() !== serverId) return;
1676
- const selected = field.val();
1677
- field.empty().append($('<option></option>').attr('value', '').text(''));
1678
- const rows = Array.isArray(data) ? data : [];
1679
- rows.forEach((dpt) => field.append($('<option></option>').attr('value', dpt.value).text(dpt.text)));
1680
- if (selected && !rows.some((dpt) => String(dpt.value) === String(selected))) {
1681
- field.append($('<option></option>').attr('value', selected).text(selected));
1682
- }
1683
- field.val(selected || '');
1684
- });
1685
- };
1686
- // Go to the help panel
1687
- try {
1688
- RED.sidebar.show("help");
1689
- } catch (error) { }
1892
+ }
1893
+ })
1894
+ // ########################
1690
1895
 
1691
- var node = this;
1692
- var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
1896
+ // Scene configuration
1897
+ function resizeRule (rule) { }
1898
+ $('#node-input-rule-container').css('min-height', '150px').css('min-width', '450px').editableList({
1899
+ addItem: function (container, i, opt) { // row, index, data
1900
+ // opt.r is: { topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt:rowRuleDPT, send: rowRuleSend}
1693
1901
 
1694
- // 19/02/2020 Used to get the server sooner als deploy.
1695
- $("#node-input-server").off(".knxUtilityProfile").on("change.knxUtilityProfile", function () {
1696
- try {
1697
- oNodeServer = RED.nodes.node($(this).val());
1698
- dptFields.forEach((field) => loadDptOptions(field, field.val(), false));
1699
- } catch (error) { }
1700
- });
1701
-
1702
-
1703
-
1704
- // DPT of Scene Recall
1705
- // ########################
1706
- loadDptOptions($("#node-input-dpt"), this.dpt);
1707
-
1708
- // Autocomplete suggestion with ETS csv File
1709
- $("#node-input-topic").autocomplete({
1710
- minLength: 0,
1711
- source: function (request, response) {
1712
- //profileGetJSON("csv", request, function( data, status, xhr ) {
1713
- if (!oNodeServer || !oNodeServer.id) { response([]); return; }
1714
- profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
1715
- response($.map(data, function (value, key) {
1716
- var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
1717
- if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1718
- return {
1719
- label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
1720
- value: value.ga // Value
1721
- }
1722
- } else {
1723
- return null;
1724
- }
1725
- }));
1726
- });
1727
- }, select: function (event, ui) {
1728
- // Sets Datapoint and device name automatically
1729
- var sDevName = ui.item.label.split("#")[1].trim();
1730
- try {
1731
- sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
1732
- } catch (error) {
1733
- }
1734
- $('#node-input-name').val("Recall: " + sDevName + "/" + $('#node-input-name').val().split("/")[1]);
1735
- var optVal = $("#node-input-dpt option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
1736
- var $dptSelect = $("#node-input-dpt");
1737
- if (optVal !== undefined && optVal !== null) {
1738
- $dptSelect.val(optVal).trigger('change');
1739
- } else {
1740
- $dptSelect.trigger('change');
1741
- }
1742
- }
1743
- }).focus(function () {
1744
- $(this).autocomplete('search', $(this).val() + 'exactmatch');
1745
- });
1746
- try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-topicSave"), oNodeServer.id); } catch (e) {}
1747
- try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting($("#node-input-topic"), oNodeServer.id); } catch (e) {}
1748
-
1749
- // 19/03/2020 Adjust trigger value accordingly
1750
- $("#node-input-dpt").on("change.knxUtilityProfile", function () {
1751
- if ($(this).val() !== null) {
1752
- if ($(this).val().indexOf("3.007") > -1) {
1753
- // It's a DIM. Suggest the right value
1754
- $("#node-input-topicTrigger").val("{decr_incr:1, data:5}");
1755
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT3007"),
1756
- {
1757
- modal: true,
1758
- fixed: true,
1759
- type: 'info',
1760
- buttons: [
1761
- {
1762
- text: "OK",
1763
- click: function (e) {
1764
- myNotification.close();
1765
- }
1766
- }]
1767
- })
1768
- } else if ($(this).val().indexOf("18.001") > -1) {
1769
- // It's a scene actuator. Suggest the right value
1770
- $("#node-input-topicTrigger").val("{save_recall:0, scenenumber:2}");
1771
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT18001"),
1772
- {
1773
- modal: true,
1774
- fixed: true,
1775
- type: 'info',
1776
- buttons: [
1777
- {
1778
- text: "OK",
1779
- click: function (e) {
1780
- myNotification.close();
1781
- }
1782
- }]
1783
- })
1784
-
1785
- } else if ($(this).val().indexOf("232.600") > -1) {
1786
- // It's a scene actuator. Suggest the right value
1787
- $("#node-input-topicTrigger").val("{red:0, green:0, blue:0}");
1788
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT232600"),
1789
- {
1790
- modal: true,
1791
- fixed: true,
1792
- type: 'info',
1793
- buttons: [
1794
- {
1795
- text: "OK",
1796
- click: function (e) {
1797
- myNotification.close();
1798
- }
1799
- }]
1800
- })
1801
-
1802
- } else if ($(this).val().indexOf("251.600") > -1) {
1803
- // It's a scene actuator. Suggest the right value
1804
- $("#node-input-topicTrigger").val("{red:0, green:0, blue:0, white:0, mR:1, mG:1, mB:1, mW:1}");
1805
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT251600"),
1806
- {
1807
- modal: true,
1808
- fixed: true,
1809
- type: 'info',
1810
- buttons: [
1811
- {
1812
- text: "OK",
1813
- click: function (e) {
1814
- myNotification.close();
1815
- }
1816
- }]
1817
- })
1902
+ if (!opt.hasOwnProperty('r')) {
1903
+ opt.r = {}
1904
+ }
1905
+ const rule = opt.r
1906
+ if (!opt.hasOwnProperty('i')) {
1907
+ opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString()
1908
+ }
1909
+ container.css({
1910
+ overflow: 'hidden',
1911
+ whiteSpace: 'nowrap'
1912
+ })
1818
1913
 
1819
- }
1914
+ const row = $('<div class="form-row"/>').appendTo(container)
1915
+ const row2 = $('<div class="form-row"/>', { style: 'padding-top: 5px; padding-left: 5px;' }).appendTo(container)
1820
1916
 
1821
- }
1822
- });
1823
- // ########################
1824
-
1825
-
1826
-
1827
- // DPT of Scene Save
1828
- // ########################
1829
- loadDptOptions($("#node-input-dptSave"), this.dptSave);
1830
-
1831
- // Autocomplete suggestion with ETS csv File
1832
- $("#node-input-topicSave").autocomplete({
1833
- minLength: 0,
1834
- source: function (request, response) {
1835
- if (!oNodeServer || !oNodeServer.id) { response([]); return; }
1836
- profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
1837
- response($.map(data, function (value, key) {
1838
- var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
1839
- if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1840
- return {
1841
- label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
1842
- value: value.ga // Value
1843
- }
1844
- } else {
1845
- return null;
1846
- }
1847
- }));
1848
- });
1849
- }, select: function (event, ui) {
1850
- // Sets Datapoint and device name automatically
1851
- var sDevName = ui.item.label.split("#")[1].trim();
1852
- try {
1853
- sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
1854
- } catch (error) {
1855
- }
1856
- $('#node-input-name').val($('#node-input-name').val().split("/")[0] + "/Save: " + sDevName);
1857
- var optVal = $("#node-input-dptSave option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
1858
- var $dptSelect = $("#node-input-dptSave");
1859
- if (optVal !== undefined && optVal !== null) {
1860
- $dptSelect.val(optVal).trigger('change');
1861
- } else {
1862
- $dptSelect.trigger('change');
1863
- }
1864
- }
1865
- }).focus(function () {
1866
- $(this).autocomplete('search', $(this).val() + 'exactmatch');
1867
- });
1868
-
1869
- // 02/04/2020 Adjust trigger value accordingly
1870
- $("#node-input-dptSave").on("change.knxUtilityProfile", function () {
1871
- if ($(this).val() !== null) {
1872
- if ($(this).val().indexOf("3.007") > -1) {
1873
- // It's a DIM. Suggest the right value
1874
- $("#node-input-topicSaveTrigger").val("{decr_incr:1, data:5}");
1875
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT3007"),
1876
- {
1877
- modal: true,
1878
- fixed: true,
1879
- type: 'info',
1880
- buttons: [
1881
- {
1882
- text: "OK",
1883
- click: function (e) {
1884
- myNotification.close();
1885
- }
1886
- }]
1887
- })
1888
- } else if ($(this).val().indexOf("18.001") > -1) {
1889
- // It's a scene actuator. Suggest the right value
1890
- $("#node-input-topicSaveTrigger").val("{save_recall:1, scenenumber:2}");
1891
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT18001"),
1892
- {
1893
- modal: true,
1894
- fixed: true,
1895
- type: 'info',
1896
- buttons: [
1897
- {
1898
- text: "OK",
1899
- click: function (e) {
1900
- myNotification.close();
1901
- }
1902
- }]
1903
- })
1904
-
1905
- } else if ($(this).val().indexOf("232.600") > -1) {
1906
- // It's a scene actuator. Suggest the right value
1907
- $("#node-input-topicSaveTrigger").val("{red:0, green:0, blue:0}");
1908
- var myNotification = RED.notify(node._("knxUltimateSceneController.advanced.notify-DPT232600"),
1909
- {
1910
- modal: true,
1911
- fixed: true,
1912
- type: 'info',
1913
- buttons: [
1914
- {
1915
- text: "OK",
1916
- click: function (e) {
1917
- myNotification.close();
1918
- }
1919
- }]
1920
- })
1917
+ const oTopicField = $('<input/>', { class: 'rowRuleTopic', type: 'text', placeholder: '1/1/1, wait or device name', style: 'width:140px; margin-left: 5px; text-align: left;' }).appendTo(row)
1918
+ const oDPTField = $('<select/>', { class: 'rowRuleDPT', type: 'text', style: 'width:160px; margin-left: 5px; text-align: left;' }).appendTo(row)
1919
+ const finalspan = $('<span/>', { style: '' }).appendTo(row)
1920
+ finalspan.append(' &#8594; <span class="node-input-rule-index"></span> ')
1921
+ const oSendField = $('<input/>', { class: 'rowRuleSend', type: 'text', placeholder: 'Value', style: 'width:150px; margin-left: 5px; text-align: left;' }).appendTo(row)
1922
+ const orowRuleDeviceName = $('<input/>', { class: 'rowRuleDeviceName', type: 'text', style: 'width:95%; margin-left: 0px; text-align: left;font-style: italic;', placeholder: 'Device Name' }).appendTo(row2)
1921
1923
 
1922
- }
1924
+ oTopicField.on('change.knxUtilityProfile', function () {
1925
+ resizeRule(container)
1926
+ })
1923
1927
 
1928
+ loadDptOptions(oDPTField, rule.dpt)
1929
+
1930
+ // Autocomplete suggestion with ETS csv File
1931
+ oTopicField.autocomplete({
1932
+ minLength: 0,
1933
+ source: function (request, response) {
1934
+ if (!oNodeServer || !oNodeServer.id) { response([]); return }
1935
+ profileGetJSON('knxUltimatecsv?nodeID=' + oNodeServer.id, (data) => {
1936
+ response($.map(data, function (value, key) {
1937
+ const sSearch = (value.ga + ' (' + value.devicename + ') DPT' + value.dpt)
1938
+ if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1939
+ return {
1940
+ label: value.ga + ' # ' + value.devicename + ' # ' + value.dpt, // Label for Display
1941
+ value: value.ga // Value
1942
+ }
1943
+ } else {
1944
+ return null
1924
1945
  }
1925
- });
1926
- // ########################
1927
-
1928
-
1929
- // Scene configuration
1930
- function resizeRule(rule) { }
1931
- $("#node-input-rule-container").css('min-height', '150px').css('min-width', '450px').editableList({
1932
- addItem: function (container, i, opt) { // row, index, data
1933
- // opt.r is: { topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt:rowRuleDPT, send: rowRuleSend}
1934
-
1935
- if (!opt.hasOwnProperty('r')) {
1936
- opt.r = {};
1937
- }
1938
- var rule = opt.r;
1939
- if (!opt.hasOwnProperty('i')) {
1940
- opt._i = Math.floor((0x99999 - 0x10000) * Math.random()).toString();
1941
- }
1942
- container.css({
1943
- overflow: 'hidden',
1944
- whiteSpace: 'nowrap'
1945
- });
1946
-
1947
-
1948
- var row = $('<div class="form-row"/>').appendTo(container);
1949
- var row2 = $('<div class="form-row"/>', { style: "padding-top: 5px; padding-left: 5px;" }).appendTo(container);
1950
-
1951
- var oTopicField = $("<input/>", { class: "rowRuleTopic", type: "text", placeholder: "1/1/1, wait or device name", style: "width:140px; margin-left: 5px; text-align: left;" }).appendTo(row);
1952
- var oDPTField = $('<select/>', { class: "rowRuleDPT", type: "text", style: "width:160px; margin-left: 5px; text-align: left;" }).appendTo(row);
1953
- var finalspan = $('<span/>', { style: "" }).appendTo(row);
1954
- finalspan.append(' &#8594; <span class="node-input-rule-index"></span> ');
1955
- var oSendField = $('<input/>', { class: "rowRuleSend", type: "text", placeholder: "Value", style: "width:150px; margin-left: 5px; text-align: left;" }).appendTo(row);
1956
- var orowRuleDeviceName = $('<input/>', { class: "rowRuleDeviceName", type: "text", style: "width:95%; margin-left: 0px; text-align: left;font-style: italic;", placeholder: "Device Name" }).appendTo(row2);
1957
-
1958
- oTopicField.on("change.knxUtilityProfile", function () {
1959
- resizeRule(container);
1960
- });
1961
-
1962
- loadDptOptions(oDPTField, rule.dpt);
1963
-
1964
- // Autocomplete suggestion with ETS csv File
1965
- oTopicField.autocomplete({
1966
- minLength: 0,
1967
- source: function (request, response) {
1968
- if (!oNodeServer || !oNodeServer.id) { response([]); return; }
1969
- profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
1970
- response($.map(data, function (value, key) {
1971
- var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
1972
- if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
1973
- return {
1974
- label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
1975
- value: value.ga // Value
1976
- }
1977
- } else {
1978
- return null;
1979
- }
1980
- }));
1981
- });
1982
- }, select: function (event, ui) {
1983
- // Sets Datapoint and device name automatically
1984
- var sDevName = ui.item.label.split("#")[1].trim();
1985
- try {
1986
- sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
1987
- orowRuleDeviceName.val(sDevName);
1988
- } catch (error) {
1989
- }
1990
- var optVal = $(".rowRuleDPT option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
1991
- var $dptSelect = oDPTField;
1992
- if (optVal !== undefined && optVal !== null) {
1993
- $dptSelect.val(optVal).trigger('change');
1994
- } else {
1995
- $dptSelect.trigger('change');
1996
- }
1997
- }
1998
- });
1999
- oTopicField.on('focus.knxUltimateSceneController click.knxUltimateSceneController', function () {
2000
- try {
2001
- $(this).autocomplete('search', '');
2002
- } catch (error) { /* empty */ }
2003
- });
2004
- try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting(oTopicField, oNodeServer.id); } catch (e) {}
2005
-
2006
- oTopicField.val(rule.topic);
2007
- oSendField.val(rule.send);
2008
- orowRuleDeviceName.val(rule.devicename);
2009
- oTopicField.change();
2010
- },
2011
- removeItem: function (opt) {
2012
- },
2013
- resizeItem: resizeRule,
2014
- sortItems: function (rules) {
2015
- },
2016
- sortable: true,
2017
- removable: true
2018
- });
2019
-
2020
-
2021
- // 10/03/2020 For each rule, create a row
2022
- for (var i = 0; i < (this.rules || []).length; i++) {
2023
- var rule = this.rules[i];
2024
- $("#node-input-rule-container").editableList('addItem', { r: rule, i: i });
1946
+ }))
1947
+ })
1948
+ },
1949
+ select: function (event, ui) {
1950
+ // Sets Datapoint and device name automatically
1951
+ let sDevName = ui.item.label.split('#')[1].trim()
1952
+ try {
1953
+ sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim()
1954
+ orowRuleDeviceName.val(sDevName)
1955
+ } catch (error) {
1956
+ }
1957
+ const optVal = $(".rowRuleDPT option:contains('" + ui.item.label.split('#')[2].trim() + "')").attr('value')
1958
+ const $dptSelect = oDPTField
1959
+ if (optVal !== undefined && optVal !== null) {
1960
+ $dptSelect.val(optVal).trigger('change')
1961
+ } else {
1962
+ $dptSelect.trigger('change')
2025
1963
  }
1964
+ }
1965
+ })
1966
+ oTopicField.on('focus.knxUltimateSceneController click.knxUltimateSceneController', function () {
1967
+ try {
1968
+ $(this).autocomplete('search', '')
1969
+ } catch (error) { /* empty */ }
1970
+ })
1971
+ try { if (oNodeServer && oNodeServer.id) KNX_enableSecureFormatting(oTopicField, oNodeServer.id) } catch (e) {}
2026
1972
 
1973
+ oTopicField.val(rule.topic)
1974
+ oSendField.val(rule.send)
1975
+ orowRuleDeviceName.val(rule.devicename)
1976
+ oTopicField.change()
1977
+ },
1978
+ removeItem: function (opt) {
1979
+ },
1980
+ resizeItem: resizeRule,
1981
+ sortItems: function (rules) {
1982
+ },
1983
+ sortable: true,
1984
+ removable: true
1985
+ })
2027
1986
 
2028
- },
2029
- oneditcancel: function () {
2030
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
2031
- $('#node-input-server').off('.knxUtilityProfile');
2032
- try { RED.sidebar.show('info'); } catch (error) { }
2033
- },
2034
- oneditsave: function () {
2035
- // Return to the info tab
2036
- try {
2037
- RED.sidebar.show("info");
2038
- } catch (error) { }
2039
-
2040
- var node = this;
2041
-
2042
- var rules = $("#node-input-rule-container").editableList('items');
2043
- node.rules = [];
2044
- rules.each(function (i) {
2045
- var rule = $(this);
2046
- var rowRuleTopic = rule.find(".rowRuleTopic").val();
2047
- var rowRuleDPT = rule.find(".rowRuleDPT").val();
2048
- var rowRuleSend = rule.find(".rowRuleSend").val();
2049
- var rowRuleDeviceName = rule.find(".rowRuleDeviceName").val();
2050
- node.rules.push({ topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt: rowRuleDPT, send: rowRuleSend });
2051
- });
2052
- },
2053
- oneditresize: function (size) {
2054
- const list = $('#node-input-rule-container');
2055
- const listTop = list.offset();
2056
- const formTop = $('#dialog-form').offset();
2057
- if (!size || !listTop || !formTop) return;
2058
- list.editableList('height', Math.max(180, size.height - (listTop.top - formTop.top) - 45));
2059
- }
1987
+ // 10/03/2020 For each rule, create a row
1988
+ for (let i = 0; i < (this.rules || []).length; i++) {
1989
+ const rule = this.rules[i]
1990
+ $('#node-input-rule-container').editableList('addItem', { r: rule, i })
1991
+ }
1992
+ },
1993
+ oneditcancel: function () {
1994
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
1995
+ $('#node-input-server').off('.knxUtilityProfile')
1996
+ try { RED.sidebar.show('info') } catch (error) { }
1997
+ },
1998
+ oneditsave: function () {
1999
+ // Return to the info tab
2000
+ try {
2001
+ RED.sidebar.show('info')
2002
+ } catch (error) { }
2003
+
2004
+ const node = this
2005
+
2006
+ const rules = $('#node-input-rule-container').editableList('items')
2007
+ node.rules = []
2008
+ rules.each(function (i) {
2009
+ const rule = $(this)
2010
+ const rowRuleTopic = rule.find('.rowRuleTopic').val()
2011
+ const rowRuleDPT = rule.find('.rowRuleDPT').val()
2012
+ const rowRuleSend = rule.find('.rowRuleSend').val()
2013
+ const rowRuleDeviceName = rule.find('.rowRuleDeviceName').val()
2014
+ node.rules.push({ topic: rowRuleTopic, devicename: rowRuleDeviceName, dpt: rowRuleDPT, send: rowRuleSend })
2060
2015
  })
2016
+ },
2017
+ oneditresize: function (size) {
2018
+ const list = $('#node-input-rule-container')
2019
+ const listTop = list.offset()
2020
+ const formTop = $('#dialog-form').offset()
2021
+ if (!size || !listTop || !formTop) return
2022
+ list.editableList('height', Math.max(180, size.height - (listTop.top - formTop.top) - 45))
2023
+ }
2024
+ })
2061
2025
  },
2062
2026
  "loadcontrol": function (RED) {
2063
2027
  const utilityFieldValue = (node, key) => {
2064
2028
  if (node._utilityEditor && typeof $ === 'function') {
2065
- const field = $('#node-input-' + key);
2066
- if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val();
2029
+ const field = $('#node-input-' + key)
2030
+ if (field.length) return field.is(':checkbox') ? field.is(':checked') : field.val()
2067
2031
  }
2068
- return node[key];
2069
- };
2032
+ return node[key]
2033
+ }
2070
2034
  const utilityNumber = (minimum, integer = false, enabled = () => true) => function (value) {
2071
- if (!enabled(this)) return true;
2072
- const numeric = Number(value);
2035
+ if (!enabled(this)) return true
2036
+ const numeric = Number(value)
2073
2037
  return value !== null && value !== undefined && String(value).trim() !== '' &&
2074
- Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric));
2075
- };
2038
+ Number.isFinite(numeric) && numeric >= minimum && (!integer || Number.isInteger(numeric))
2039
+ }
2076
2040
 
2077
2041
  RED.nodes.registerType('knxUltimateLoadControl', {
2078
- category: "KNX Ultimate",
2079
- color: '#C7E9C0',
2080
- defaults: {
2081
- server: { type: "knxUltimate-config", required: false },
2082
- name: { value: "" },
2083
- controlMode: { value: "auto" },
2084
- topic: { value: "" },
2085
- dpt: { value: "" },
2086
- wattLimit: { value: 3000, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
2087
- sheddingCheckInterval: { value: 15, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
2088
- sheddingRestoreDelay: { value: 60, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
2089
- GA1: { value: "" },
2090
- DPT1: { value: "" },
2091
- Name1: { value: "" },
2092
- autoRestore1: { value: true },
2093
- MonitorGA1: { value: "" },
2094
- MonitorDPT1: { value: "" },
2095
- MonitorName1: { value: "" },
2096
- GA2: { value: "" },
2097
- DPT2: { value: "" },
2098
- Name2: { value: "" },
2099
- autoRestore2: { value: true },
2100
- MonitorGA2: { value: "" },
2101
- MonitorDPT2: { value: "" },
2102
- MonitorName2: { value: "" },
2103
- GA3: { value: "" },
2104
- DPT3: { value: "" },
2105
- Name3: { value: "" },
2106
- autoRestore3: { value: true },
2107
- MonitorGA3: { value: "" },
2108
- MonitorDPT3: { value: "" },
2109
- MonitorName3: { value: "" },
2110
- GA4: { value: "" },
2111
- DPT4: { value: "" },
2112
- Name4: { value: "" },
2113
- autoRestore4: { value: true },
2114
- MonitorGA4: { value: "" },
2115
- MonitorDPT4: { value: "" },
2116
- MonitorName4: { value: "" },
2117
- GA5: { value: "" },
2118
- DPT5: { value: "" },
2119
- Name5: { value: "" },
2120
- autoRestore5: { value: true },
2121
- MonitorGA5: { value: "" },
2122
- MonitorDPT5: { value: "" },
2123
- MonitorName5: { value: "" }
2124
- },
2125
- inputs: 1,
2126
- outputs: 1,
2127
- icon: "node-alerter-icon.svg",
2128
- label: function () {
2129
- return (this.name || this.topic || "KNX Load Control");
2130
- },
2131
- paletteLabel: "KNX Load Control",
2132
- oneditprepare: function () {
2133
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
2134
- const editorSession = { active: true };
2135
- this._knxUtilityProfileSession = editorSession;
2136
- // Ignore responses belonging to a closed editor or a previous gateway.
2137
- const profileGetJSON = (url, callback) => {
2138
- const serverId = $('#node-input-server').val();
2139
- return $.getJSON(url, (data) => {
2140
- if (editorSession.active && $('#node-input-server').val() === serverId) callback(data);
2141
- });
2142
- };
2143
-
2144
- const dptFields = [];
2145
- const dptRequests = new Map();
2146
- const loadDptOptions = (field, savedValue, track = true) => {
2147
- if (track) {
2148
- dptFields.push(field);
2149
- // Preserve persisted values even when Done is pressed before
2150
- // the asynchronous DPT catalog has arrived.
2151
- field.append($('<option></option>').attr('value', savedValue || '').text(savedValue || ''));
2152
- field.val(savedValue || '');
2153
- }
2154
- const serverId = $('#node-input-server').val();
2155
- if (!dptRequests.has(serverId)) {
2156
- dptRequests.set(serverId, new Promise((resolve) => {
2157
- profileGetJSON('knxUltimateDpts?serverId=' + encodeURIComponent(serverId || ''), resolve);
2158
- }));
2159
- }
2160
- dptRequests.get(serverId).then((data) => {
2161
- if (!editorSession.active || $('#node-input-server').val() !== serverId) return;
2162
- const selected = field.val();
2163
- field.empty().append($('<option></option>').attr('value', '').text(''));
2164
- const rows = Array.isArray(data) ? data : [];
2165
- rows.forEach((dpt) => field.append($('<option></option>').attr('value', dpt.value).text(dpt.text)));
2166
- if (selected && !rows.some((dpt) => String(dpt.value) === String(selected))) {
2167
- field.append($('<option></option>').attr('value', selected).text(selected));
2168
- }
2169
- field.val(selected || '');
2170
- });
2171
- };
2172
- // Go to the help panel
2173
- try {
2174
- RED.sidebar.show("help");
2175
- } catch (error) { }
2042
+ category: 'KNX Ultimate',
2043
+ color: '#C7E9C0',
2044
+ defaults: {
2045
+ server: { type: 'knxUltimate-config', required: false },
2046
+ name: { value: '' },
2047
+ controlMode: { value: 'auto' },
2048
+ topic: { value: '' },
2049
+ dpt: { value: '' },
2050
+ wattLimit: { value: 3000, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
2051
+ sheddingCheckInterval: { value: 15, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
2052
+ sheddingRestoreDelay: { value: 60, validate: utilityNumber(0.001, false, (node) => utilityFieldValue(node, 'controlMode') !== 'msg') },
2053
+ GA1: { value: '' },
2054
+ DPT1: { value: '' },
2055
+ Name1: { value: '' },
2056
+ autoRestore1: { value: true },
2057
+ MonitorGA1: { value: '' },
2058
+ MonitorDPT1: { value: '' },
2059
+ MonitorName1: { value: '' },
2060
+ GA2: { value: '' },
2061
+ DPT2: { value: '' },
2062
+ Name2: { value: '' },
2063
+ autoRestore2: { value: true },
2064
+ MonitorGA2: { value: '' },
2065
+ MonitorDPT2: { value: '' },
2066
+ MonitorName2: { value: '' },
2067
+ GA3: { value: '' },
2068
+ DPT3: { value: '' },
2069
+ Name3: { value: '' },
2070
+ autoRestore3: { value: true },
2071
+ MonitorGA3: { value: '' },
2072
+ MonitorDPT3: { value: '' },
2073
+ MonitorName3: { value: '' },
2074
+ GA4: { value: '' },
2075
+ DPT4: { value: '' },
2076
+ Name4: { value: '' },
2077
+ autoRestore4: { value: true },
2078
+ MonitorGA4: { value: '' },
2079
+ MonitorDPT4: { value: '' },
2080
+ MonitorName4: { value: '' },
2081
+ GA5: { value: '' },
2082
+ DPT5: { value: '' },
2083
+ Name5: { value: '' },
2084
+ autoRestore5: { value: true },
2085
+ MonitorGA5: { value: '' },
2086
+ MonitorDPT5: { value: '' },
2087
+ MonitorName5: { value: '' }
2088
+ },
2089
+ inputs: 1,
2090
+ outputs: 1,
2091
+ icon: 'node-alerter-icon.svg',
2092
+ label: function () {
2093
+ return (this.name || this.topic || 'KNX Load Control')
2094
+ },
2095
+ paletteLabel: 'KNX Load Control',
2096
+ oneditprepare: function () {
2097
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
2098
+ const editorSession = { active: true }
2099
+ this._knxUtilityProfileSession = editorSession
2100
+ // Ignore responses belonging to a closed editor or a previous gateway.
2101
+ const profileGetJSON = (url, callback) => {
2102
+ const serverId = $('#node-input-server').val()
2103
+ return $.getJSON(url, (data) => {
2104
+ if (editorSession.active && $('#node-input-server').val() === serverId) callback(data)
2105
+ })
2106
+ }
2176
2107
 
2177
- var node = this;
2178
- var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
2108
+ const dptFields = []
2109
+ const dptRequests = new Map()
2110
+ const loadDptOptions = (field, savedValue, track = true) => {
2111
+ if (track) {
2112
+ dptFields.push(field)
2113
+ // Preserve persisted values even when Done is pressed before
2114
+ // the asynchronous DPT catalog has arrived.
2115
+ field.append($('<option></option>').attr('value', savedValue || '').text(savedValue || ''))
2116
+ field.val(savedValue || '')
2117
+ }
2118
+ const serverId = $('#node-input-server').val()
2119
+ if (!dptRequests.has(serverId)) {
2120
+ dptRequests.set(serverId, new Promise((resolve) => {
2121
+ profileGetJSON('knxUltimateDpts?serverId=' + encodeURIComponent(serverId || ''), resolve)
2122
+ }))
2123
+ }
2124
+ dptRequests.get(serverId).then((data) => {
2125
+ if (!editorSession.active || $('#node-input-server').val() !== serverId) return
2126
+ const selected = field.val()
2127
+ field.empty().append($('<option></option>').attr('value', '').text(''))
2128
+ const rows = Array.isArray(data) ? data : []
2129
+ rows.forEach((dpt) => field.append($('<option></option>').attr('value', dpt.value).text(dpt.text)))
2130
+ if (selected && !rows.some((dpt) => String(dpt.value) === String(selected))) {
2131
+ field.append($('<option></option>').attr('value', selected).text(selected))
2132
+ }
2133
+ field.val(selected || '')
2134
+ })
2135
+ }
2136
+ // Go to the help panel
2137
+ try {
2138
+ RED.sidebar.show('help')
2139
+ } catch (error) { }
2179
2140
 
2180
- // 19/02/2020 Used to get the server sooner als deploy.
2181
- $("#node-input-server").off(".knxUtilityProfile").on("change.knxUtilityProfile", function () {
2182
- try {
2183
- oNodeServer = RED.nodes.node($(this).val());
2184
- dptFields.forEach((field) => loadDptOptions(field, field.val(), false));
2185
- } catch (error) { }
2186
- });
2141
+ const node = this
2142
+ let oNodeServer = RED.nodes.node($('#node-input-server').val()) // Store the config-node
2187
2143
 
2144
+ // 19/02/2020 Used to get the server sooner als deploy.
2145
+ $('#node-input-server').off('.knxUtilityProfile').on('change.knxUtilityProfile', function () {
2146
+ try {
2147
+ oNodeServer = RED.nodes.node($(this).val())
2148
+ dptFields.forEach((field) => loadDptOptions(field, field.val(), false))
2149
+ } catch (error) { }
2150
+ })
2188
2151
 
2152
+ loadDptOptions($('#node-input-dpt'), this.dpt)
2153
+ for (let index = 1; index < 6; index++) {
2154
+ loadDptOptions($('#node-input-DPT' + index), this['DPT' + index])
2155
+ loadDptOptions($('#node-input-MonitorDPT' + index), this['MonitorDPT' + index])
2156
+ }
2189
2157
 
2190
- loadDptOptions($('#node-input-dpt'), this.dpt);
2191
- for (let index = 1; index < 6; index++) {
2192
- loadDptOptions($('#node-input-DPT' + index), this['DPT' + index]);
2193
- loadDptOptions($('#node-input-MonitorDPT' + index), this['MonitorDPT' + index]);
2194
- }
2158
+ const toggleAutoFields = () => {
2159
+ const mode = ($('#node-input-controlMode').val() || 'auto').toString()
2160
+ if (mode === 'msg') {
2161
+ $('.knx-lc-auto-only').hide()
2162
+ } else {
2163
+ $('.knx-lc-auto-only').show()
2164
+ }
2165
+ }
2195
2166
 
2196
- const toggleAutoFields = () => {
2197
- const mode = ($("#node-input-controlMode").val() || "auto").toString();
2198
- if (mode === "msg") {
2199
- $(".knx-lc-auto-only").hide();
2200
- } else {
2201
- $(".knx-lc-auto-only").show();
2167
+ $('#node-input-controlMode').on('change.knxUtilityProfile', toggleAutoFields)
2168
+
2169
+ // Autocomplete suggestion with ETS csv File
2170
+ this.autoComplete = (_Name, _DPT) => {
2171
+ const paramAutoComplete = {
2172
+ minLength: 0,
2173
+ source: function (request, response) {
2174
+ if (!oNodeServer || !oNodeServer.id) { response([]); return }
2175
+ profileGetJSON('knxUltimatecsv?nodeID=' + oNodeServer.id, (data) => {
2176
+ response($.map(data, function (value, key) {
2177
+ const sSearch = (value.ga + ' (' + value.devicename + ') DPT' + value.dpt)
2178
+ if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
2179
+ return {
2180
+ label: value.ga + ' # ' + value.devicename + ' # ' + value.dpt, // Label for Display
2181
+ value: value.ga // Value
2202
2182
  }
2203
- };
2204
-
2205
- $("#node-input-controlMode").on("change.knxUtilityProfile", toggleAutoFields);
2206
-
2207
- // Autocomplete suggestion with ETS csv File
2208
- this.autoComplete = (_Name, _DPT) => {
2209
- let paramAutoComplete = {
2210
- minLength: 0,
2211
- source: function (request, response) {
2212
- if (!oNodeServer || !oNodeServer.id) { response([]); return; }
2213
- profileGetJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
2214
- response($.map(data, function (value, key) {
2215
- var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
2216
- if (htmlUtilsfullCSVSearch(sSearch, request.term)) {
2217
- return {
2218
- label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
2219
- value: value.ga // Value
2220
- }
2221
- } else {
2222
- return null;
2223
- }
2224
- }));
2225
- });
2226
- }, select: function (event, ui) {
2227
- // Sets Datapoint and device name automatically
2228
- var sDevName = ui.item.label.split("#")[1].trim();
2229
- try {
2230
- sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
2231
- } catch (error) {
2232
- }
2233
- if (_Name === "name") {
2234
- $("#node-input-" + _Name).val("Load Control for " + sDevName);
2235
- } else {
2236
- $("#node-input-" + _Name).val(sDevName);
2237
- }
2238
-
2239
- var optVal = $("#node-input-dpt option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
2240
- var $dptSelect = $("#node-input-" + _DPT);
2241
- if (optVal !== undefined && optVal !== null) {
2242
- $dptSelect.val(optVal).trigger('change');
2243
- } else {
2244
- $dptSelect.trigger('change');
2245
- }
2246
- }
2247
- };
2248
- return paramAutoComplete;
2249
- };
2250
-
2251
- $("#node-input-topic").autocomplete(this.autoComplete("name", "dpt")).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
2252
- try {
2253
- $(this).autocomplete('search', '');
2254
- } catch (error) { /* empty */ }
2255
- });
2256
- try { var srv = RED.nodes.node($("#node-input-server").val()); if (srv && srv.id) KNX_enableSecureFormatting($("#node-input-topic"), srv.id); } catch (e) {}
2257
- for (let index = 1; index < 6; index++) {
2258
- $("#node-input-GA" + index).autocomplete(this.autoComplete("Name" + index, "DPT" + index)).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
2259
- try {
2260
- $(this).autocomplete('search', '');
2261
- } catch (error) { /* empty */ }
2262
- });
2263
- $("#node-input-MonitorGA" + index).autocomplete(this.autoComplete("MonitorName" + index, "MonitorDPT" + index)).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
2264
- try {
2265
- $(this).autocomplete('search', '');
2266
- } catch (error) { /* empty */ }
2267
- });
2268
- try { var srv2 = RED.nodes.node($("#node-input-server").val()); if (srv2 && srv2.id) KNX_enableSecureFormatting($("#node-input-GA" + index), srv2.id); } catch (e) {}
2269
- try { var srv3 = RED.nodes.node($("#node-input-server").val()); if (srv3 && srv3.id) KNX_enableSecureFormatting($("#node-input-MonitorGA" + index), srv3.id); } catch (e) {}
2270
- }
2271
-
2272
- toggleAutoFields();
2273
-
2274
-
2275
- },
2276
- oneditcancel: function () {
2277
- if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false;
2278
- $('#node-input-server').off('.knxUtilityProfile');
2279
- try { RED.sidebar.show('info'); } catch (error) { }
2280
- },
2281
- oneditsave: function () {
2282
- // Return to the info tab
2283
- try {
2284
- RED.sidebar.show("info");
2285
- } catch (error) { }
2286
-
2287
-
2183
+ } else {
2184
+ return null
2185
+ }
2186
+ }))
2187
+ })
2288
2188
  },
2289
- oneditresize: function (size) {
2189
+ select: function (event, ui) {
2190
+ // Sets Datapoint and device name automatically
2191
+ let sDevName = ui.item.label.split('#')[1].trim()
2192
+ try {
2193
+ sDevName = sDevName.substr(sDevName.indexOf(')') + 1).trim()
2194
+ } catch (error) {
2195
+ }
2196
+ if (_Name === 'name') {
2197
+ $('#node-input-' + _Name).val('Load Control for ' + sDevName)
2198
+ } else {
2199
+ $('#node-input-' + _Name).val(sDevName)
2200
+ }
2290
2201
 
2202
+ const optVal = $("#node-input-dpt option:contains('" + ui.item.label.split('#')[2].trim() + "')").attr('value')
2203
+ const $dptSelect = $('#node-input-' + _DPT)
2204
+ if (optVal !== undefined && optVal !== null) {
2205
+ $dptSelect.val(optVal).trigger('change')
2206
+ } else {
2207
+ $dptSelect.trigger('change')
2208
+ }
2291
2209
  }
2210
+ }
2211
+ return paramAutoComplete
2212
+ }
2213
+
2214
+ $('#node-input-topic').autocomplete(this.autoComplete('name', 'dpt')).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
2215
+ try {
2216
+ $(this).autocomplete('search', '')
2217
+ } catch (error) { /* empty */ }
2292
2218
  })
2219
+ try { const srv = RED.nodes.node($('#node-input-server').val()); if (srv && srv.id) KNX_enableSecureFormatting($('#node-input-topic'), srv.id) } catch (e) {}
2220
+ for (let index = 1; index < 6; index++) {
2221
+ $('#node-input-GA' + index).autocomplete(this.autoComplete('Name' + index, 'DPT' + index)).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
2222
+ try {
2223
+ $(this).autocomplete('search', '')
2224
+ } catch (error) { /* empty */ }
2225
+ })
2226
+ $('#node-input-MonitorGA' + index).autocomplete(this.autoComplete('MonitorName' + index, 'MonitorDPT' + index)).on('focus.knxUltimateLoadControl click.knxUltimateLoadControl', function () {
2227
+ try {
2228
+ $(this).autocomplete('search', '')
2229
+ } catch (error) { /* empty */ }
2230
+ })
2231
+ try { const srv2 = RED.nodes.node($('#node-input-server').val()); if (srv2 && srv2.id) KNX_enableSecureFormatting($('#node-input-GA' + index), srv2.id) } catch (e) {}
2232
+ try { const srv3 = RED.nodes.node($('#node-input-server').val()); if (srv3 && srv3.id) KNX_enableSecureFormatting($('#node-input-MonitorGA' + index), srv3.id) } catch (e) {}
2233
+ }
2234
+
2235
+ toggleAutoFields()
2236
+ },
2237
+ oneditcancel: function () {
2238
+ if (this._knxUtilityProfileSession) this._knxUtilityProfileSession.active = false
2239
+ $('#node-input-server').off('.knxUtilityProfile')
2240
+ try { RED.sidebar.show('info') } catch (error) { }
2241
+ },
2242
+ oneditsave: function () {
2243
+ // Return to the info tab
2244
+ try {
2245
+ RED.sidebar.show('info')
2246
+ } catch (error) { }
2247
+ },
2248
+ oneditresize: function (size) {
2249
+
2250
+ }
2251
+ })
2293
2252
  },
2294
2253
  "hatranslator": function (RED) {
2295
- const defaultTranslations = 'on:true\noff:false\nactive:true\ninactive:false\nopen:true\nclosed:false\nclose:false\n1:true\n0:false\ntrue:true\nfalse:false\nhome:true\nnot_home:false';
2254
+ const defaultTranslations = 'on:true\noff:false\nactive:true\ninactive:false\nopen:true\nclosed:false\nclose:false\n1:true\n0:false\ntrue:true\nfalse:false\nhome:true\nnot_home:false'
2296
2255
 
2297
2256
  RED.nodes.registerType('knxUltimateHATranslator', {
2298
- category: 'KNX Ultimate',
2299
- defaults: {
2300
- name: { value: '' },
2301
- payloadPropName: { value: 'payload', required: false },
2302
- haTranslationTable: { value: defaultTranslations, required: false }
2303
- },
2304
- inputs: 1,
2305
- outputs: 1,
2306
- icon: 'node-ha-icon.svg',
2307
- label: function () { return this.name || 'HA -> KNX'; },
2308
- color: '#AED6F1',
2309
- paletteLabel: 'Home Assistant translator',
2310
- oneditprepare: function () {
2311
- this.editor = RED.editor.createEditor({
2312
- id: 'node-input-editorcommandText',
2313
- mode: 'ace/mode/text',
2314
- value: this.haTranslationTable === undefined || this.haTranslationTable === null ? defaultTranslations : this.haTranslationTable
2315
- });
2316
- },
2317
- oneditsave: function () {
2318
- // The Utility collects drafts before switching function. Cleanup is a
2319
- // separate step, so repeated collection must leave Ace usable.
2320
- if (this.editor) this.haTranslationTable = this.editor.getValue();
2321
- },
2322
- oneditresize: function () {
2323
- if (this.editor) this.editor.resize();
2324
- },
2325
- oneditcancel: function () {
2326
- // Called for both Done and Cancel, and whenever the profile unmounts.
2327
- // Clear the reference first so cleanup remains idempotent.
2328
- const editor = this.editor;
2329
- delete this.editor;
2330
- if (editor) editor.destroy();
2331
- }
2332
- });
2257
+ category: 'KNX Ultimate',
2258
+ defaults: {
2259
+ name: { value: '' },
2260
+ payloadPropName: { value: 'payload', required: false },
2261
+ haTranslationTable: { value: defaultTranslations, required: false }
2262
+ },
2263
+ inputs: 1,
2264
+ outputs: 1,
2265
+ icon: 'node-ha-icon.svg',
2266
+ label: function () { return this.name || 'HA -> KNX' },
2267
+ color: '#AED6F1',
2268
+ paletteLabel: 'Home Assistant translator',
2269
+ oneditprepare: function () {
2270
+ this.editor = RED.editor.createEditor({
2271
+ id: 'node-input-editorcommandText',
2272
+ mode: 'ace/mode/text',
2273
+ value: this.haTranslationTable === undefined || this.haTranslationTable === null ? defaultTranslations : this.haTranslationTable
2274
+ })
2275
+ },
2276
+ oneditsave: function () {
2277
+ // The Utility collects drafts before switching function. Cleanup is a
2278
+ // separate step, so repeated collection must leave Ace usable.
2279
+ if (this.editor) this.haTranslationTable = this.editor.getValue()
2280
+ },
2281
+ oneditresize: function () {
2282
+ if (this.editor) this.editor.resize()
2283
+ },
2284
+ oneditcancel: function () {
2285
+ // Called for both Done and Cancel, and whenever the profile unmounts.
2286
+ // Clear the reference first so cleanup remains idempotent.
2287
+ const editor = this.editor
2288
+ delete this.editor
2289
+ if (editor) editor.destroy()
2290
+ }
2291
+ })
2333
2292
  }
2334
2293
  }
2335
2294