node-red-contrib-knx-ultimate 6.2.2 → 6.2.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/nodes/knxUltimateAI.js +7 -1
  3. package/nodes/knxUltimateHueAreaMotion.html +15 -4
  4. package/nodes/knxUltimateHueBattery.html +15 -4
  5. package/nodes/knxUltimateHueButton.html +15 -4
  6. package/nodes/knxUltimateHueCameraMotion.html +15 -4
  7. package/nodes/knxUltimateHueContactSensor.html +15 -4
  8. package/nodes/knxUltimateHueController.html +1386 -0
  9. package/nodes/knxUltimateHueController.js +28 -0
  10. package/nodes/knxUltimateHueHumiditySensor.html +15 -4
  11. package/nodes/knxUltimateHueLight.html +15 -4
  12. package/nodes/knxUltimateHueLightSensor.html +15 -4
  13. package/nodes/knxUltimateHueMotion.html +15 -4
  14. package/nodes/knxUltimateHuePlug.html +15 -4
  15. package/nodes/knxUltimateHueScene.html +18 -7
  16. package/nodes/knxUltimateHueTapDial.html +15 -4
  17. package/nodes/knxUltimateHueTemperatureSensor.html +15 -4
  18. package/nodes/knxUltimateHueZigbeeConnectivity.html +15 -4
  19. package/nodes/knxUltimateHuedevice_software_update.html +15 -4
  20. package/nodes/locales/de/knxUltimateHueAreaMotion.html +1 -1
  21. package/nodes/locales/de/knxUltimateHueBattery.html +1 -0
  22. package/nodes/locales/de/knxUltimateHueButton.html +1 -0
  23. package/nodes/locales/de/knxUltimateHueCameraMotion.html +1 -0
  24. package/nodes/locales/de/knxUltimateHueContactSensor.html +1 -0
  25. package/nodes/locales/de/knxUltimateHueController.html +669 -0
  26. package/nodes/locales/de/knxUltimateHueController.json +48 -0
  27. package/nodes/locales/de/knxUltimateHueHumiditySensor.html +1 -0
  28. package/nodes/locales/de/knxUltimateHueLight.html +1 -0
  29. package/nodes/locales/de/knxUltimateHueLightSensor.html +1 -0
  30. package/nodes/locales/de/knxUltimateHueMotion.html +1 -0
  31. package/nodes/locales/de/knxUltimateHuePlug.html +1 -0
  32. package/nodes/locales/de/knxUltimateHueScene.html +1 -0
  33. package/nodes/locales/de/knxUltimateHueTapDial.html +1 -0
  34. package/nodes/locales/de/knxUltimateHueTemperatureSensor.html +1 -0
  35. package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.html +1 -0
  36. package/nodes/locales/de/knxUltimateHuedevice_software_update.html +1 -0
  37. package/nodes/locales/en/knxUltimateHueAreaMotion.html +1 -1
  38. package/nodes/locales/en/knxUltimateHueBattery.html +1 -0
  39. package/nodes/locales/en/knxUltimateHueButton.html +1 -0
  40. package/nodes/locales/en/knxUltimateHueCameraMotion.html +1 -0
  41. package/nodes/locales/en/knxUltimateHueContactSensor.html +1 -0
  42. package/nodes/locales/en/knxUltimateHueController.html +672 -0
  43. package/nodes/locales/en/knxUltimateHueController.json +48 -0
  44. package/nodes/locales/en/knxUltimateHueHumiditySensor.html +1 -0
  45. package/nodes/locales/en/knxUltimateHueLight.html +1 -0
  46. package/nodes/locales/en/knxUltimateHueLightSensor.html +1 -0
  47. package/nodes/locales/en/knxUltimateHueMotion.html +1 -0
  48. package/nodes/locales/en/knxUltimateHuePlug.html +1 -0
  49. package/nodes/locales/en/knxUltimateHueScene.html +1 -0
  50. package/nodes/locales/en/knxUltimateHueTapDial.html +1 -0
  51. package/nodes/locales/en/knxUltimateHueTemperatureSensor.html +1 -0
  52. package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.html +1 -0
  53. package/nodes/locales/en/knxUltimateHuedevice_software_update.html +1 -0
  54. package/nodes/locales/es/knxUltimateHueAreaMotion.html +1 -1
  55. package/nodes/locales/es/knxUltimateHueBattery.html +1 -0
  56. package/nodes/locales/es/knxUltimateHueButton.html +1 -0
  57. package/nodes/locales/es/knxUltimateHueCameraMotion.html +1 -0
  58. package/nodes/locales/es/knxUltimateHueContactSensor.html +1 -0
  59. package/nodes/locales/es/knxUltimateHueController.html +672 -0
  60. package/nodes/locales/es/knxUltimateHueController.json +48 -0
  61. package/nodes/locales/es/knxUltimateHueHumiditySensor.html +1 -0
  62. package/nodes/locales/es/knxUltimateHueLight.html +1 -0
  63. package/nodes/locales/es/knxUltimateHueLightSensor.html +1 -0
  64. package/nodes/locales/es/knxUltimateHueMotion.html +1 -0
  65. package/nodes/locales/es/knxUltimateHuePlug.html +1 -0
  66. package/nodes/locales/es/knxUltimateHueScene.html +1 -0
  67. package/nodes/locales/es/knxUltimateHueTapDial.html +1 -0
  68. package/nodes/locales/es/knxUltimateHueTemperatureSensor.html +1 -0
  69. package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.html +1 -0
  70. package/nodes/locales/es/knxUltimateHuedevice_software_update.html +1 -0
  71. package/nodes/locales/fr/knxUltimateHueAreaMotion.html +1 -1
  72. package/nodes/locales/fr/knxUltimateHueBattery.html +1 -0
  73. package/nodes/locales/fr/knxUltimateHueButton.html +1 -0
  74. package/nodes/locales/fr/knxUltimateHueCameraMotion.html +1 -0
  75. package/nodes/locales/fr/knxUltimateHueContactSensor.html +1 -0
  76. package/nodes/locales/fr/knxUltimateHueController.html +672 -0
  77. package/nodes/locales/fr/knxUltimateHueController.json +48 -0
  78. package/nodes/locales/fr/knxUltimateHueHumiditySensor.html +1 -0
  79. package/nodes/locales/fr/knxUltimateHueLight.html +1 -0
  80. package/nodes/locales/fr/knxUltimateHueLightSensor.html +1 -0
  81. package/nodes/locales/fr/knxUltimateHueMotion.html +1 -0
  82. package/nodes/locales/fr/knxUltimateHuePlug.html +1 -0
  83. package/nodes/locales/fr/knxUltimateHueScene.html +1 -0
  84. package/nodes/locales/fr/knxUltimateHueTapDial.html +1 -0
  85. package/nodes/locales/fr/knxUltimateHueTemperatureSensor.html +1 -0
  86. package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.html +1 -0
  87. package/nodes/locales/fr/knxUltimateHuedevice_software_update.html +1 -0
  88. package/nodes/locales/it/knxUltimateHueAreaMotion.html +1 -1
  89. package/nodes/locales/it/knxUltimateHueBattery.html +1 -0
  90. package/nodes/locales/it/knxUltimateHueButton.html +1 -0
  91. package/nodes/locales/it/knxUltimateHueCameraMotion.html +1 -0
  92. package/nodes/locales/it/knxUltimateHueContactSensor.html +1 -0
  93. package/nodes/locales/it/knxUltimateHueController.html +672 -0
  94. package/nodes/locales/it/knxUltimateHueController.json +48 -0
  95. package/nodes/locales/it/knxUltimateHueHumiditySensor.html +1 -0
  96. package/nodes/locales/it/knxUltimateHueLight.html +1 -0
  97. package/nodes/locales/it/knxUltimateHueLightSensor.html +1 -0
  98. package/nodes/locales/it/knxUltimateHueMotion.html +1 -0
  99. package/nodes/locales/it/knxUltimateHuePlug.html +1 -0
  100. package/nodes/locales/it/knxUltimateHueScene.html +1 -0
  101. package/nodes/locales/it/knxUltimateHueTapDial.html +1 -0
  102. package/nodes/locales/it/knxUltimateHueTemperatureSensor.html +1 -0
  103. package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.html +1 -0
  104. package/nodes/locales/it/knxUltimateHuedevice_software_update.html +1 -0
  105. package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.html +1 -1
  106. package/nodes/locales/zh-CN/knxUltimateHueBattery.html +1 -0
  107. package/nodes/locales/zh-CN/knxUltimateHueButton.html +1 -0
  108. package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.html +1 -0
  109. package/nodes/locales/zh-CN/knxUltimateHueContactSensor.html +1 -0
  110. package/nodes/locales/zh-CN/knxUltimateHueController.html +674 -0
  111. package/nodes/locales/zh-CN/knxUltimateHueController.json +48 -0
  112. package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.html +1 -0
  113. package/nodes/locales/zh-CN/knxUltimateHueLight.html +1 -0
  114. package/nodes/locales/zh-CN/knxUltimateHueLightSensor.html +1 -0
  115. package/nodes/locales/zh-CN/knxUltimateHueMotion.html +1 -0
  116. package/nodes/locales/zh-CN/knxUltimateHuePlug.html +1 -0
  117. package/nodes/locales/zh-CN/knxUltimateHueScene.html +1 -0
  118. package/nodes/locales/zh-CN/knxUltimateHueTapDial.html +1 -0
  119. package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.html +1 -0
  120. package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.html +1 -0
  121. package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.html +1 -0
  122. package/nodes/utils/hueControllerProfileAdapter.js +104 -0
  123. package/nodes/utils/hueControllerProfiles/runtime/area_motion.js +221 -0
  124. package/nodes/utils/hueControllerProfiles/runtime/button.js +295 -0
  125. package/nodes/utils/hueControllerProfiles/runtime/camera_motion.js +221 -0
  126. package/nodes/utils/hueControllerProfiles/runtime/contact.js +191 -0
  127. package/nodes/utils/hueControllerProfiles/runtime/device_power.js +185 -0
  128. package/nodes/utils/hueControllerProfiles/runtime/device_software_update.js +185 -0
  129. package/nodes/utils/hueControllerProfiles/runtime/humidity.js +204 -0
  130. package/nodes/utils/hueControllerProfiles/runtime/light.js +1583 -0
  131. package/nodes/utils/hueControllerProfiles/runtime/light_level.js +180 -0
  132. package/nodes/utils/hueControllerProfiles/runtime/motion.js +185 -0
  133. package/nodes/utils/hueControllerProfiles/runtime/plug.js +344 -0
  134. package/nodes/utils/hueControllerProfiles/runtime/relative_rotary.js +271 -0
  135. package/nodes/utils/hueControllerProfiles/runtime/scene.js +352 -0
  136. package/nodes/utils/hueControllerProfiles/runtime/temperature.js +187 -0
  137. package/nodes/utils/hueControllerProfiles/runtime/zigbee_connectivity.js +185 -0
  138. package/package.json +5 -2
  139. package/resources/hueControllerMigration.js +199 -0
  140. package/resources/hueControllerMigrationDialog.js +189 -0
  141. package/resources/hueControllerProfiles.js +9290 -0
@@ -0,0 +1,48 @@
1
+ {
2
+ "common": {
3
+ "knx_gw": "KNX 网关",
4
+ "hue_bridge": "Hue Bridge"
5
+ },
6
+ "knxUltimateHueController": {
7
+ "device_function": "设备功能",
8
+ "legacy_node_notice": "此旧版 HUE 节点已弃用。新流程请使用新的 HUE Controller 节点。",
9
+ "migration_button": "转换旧版 HUE 流程",
10
+ "migration_button_title": "粘贴 Node-RED 流程并转换其中的旧版 HUE 节点",
11
+ "migration_title": "转换旧版 HUE 流程",
12
+ "migration_privacy": "粘贴从 Node-RED 导出的流程。转换仅在本地进行,不会修改当前打开的流程。",
13
+ "migration_credentials_notice": "Node-RED 会特意从导出的流程中排除密钥和密码。转换器无法读取或重新创建这些凭据。",
14
+ "migration_reuse_configs": "复用现有 Hue 和 KNX 配置节点(推荐)",
15
+ "migration_reuse_configs_help": "重新导入同一个 Node-RED 实例时请保持选中。结果会省略被引用的配置节点,从而保留其中已存储的凭据。",
16
+ "migration_input": "原始流程 JSON",
17
+ "migration_input_placeholder": "在此粘贴完整的 Node-RED 流程导出内容",
18
+ "migration_convert": "转换",
19
+ "migration_copy": "复制转换后的流程",
20
+ "migration_output": "转换后的流程 JSON",
21
+ "migration_close": "关闭",
22
+ "migration_summary": "已转换 {{count}} 个旧版 HUE 节点。",
23
+ "migration_summary_reused": "将复用 {{count}} 个被引用的配置节点及其现有凭据。",
24
+ "migration_configs_included_warning": "配置节点已包含在结果中,但不含导出的凭据;请在导入后重新输入密钥和密码。",
25
+ "migration_none": "未找到旧版 HUE 节点。",
26
+ "migration_invalid": "流程 JSON 无效:",
27
+ "migration_copy_success": "已复制转换后的流程。",
28
+ "migration_copy_failed": "剪贴板访问被阻止。已选中转换后的 JSON,请手动复制。",
29
+ "migration_unavailable": "HUE 迁移工具不可用。更新软件包后请重启 Node-RED。",
30
+ "types": {
31
+ "light": "灯 / 灯组",
32
+ "plug": "插座 / 输出",
33
+ "button": "按钮",
34
+ "relative_rotary": "Tap dial",
35
+ "motion": "运动",
36
+ "area_motion": "区域运动",
37
+ "camera_motion": "摄像机运动",
38
+ "contact": "接触传感器",
39
+ "light_level": "光照度",
40
+ "temperature": "温度",
41
+ "humidity": "湿度",
42
+ "scene": "场景",
43
+ "device_power": "电池",
44
+ "zigbee_connectivity": "Zigbee 连接",
45
+ "device_software_update": "设备软件更新"
46
+ }
47
+ }
48
+ }
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueHumiditySensor">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>该节点从 HUE 湿度传感器读取相对湿度 (%) 并映射到 KNX。</p>
3
4
 
4
5
  在 GA 字段中开始输入(名称或组地址)以关联 KNX GA;输入时会显示匹配的设备。
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueLight">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>此节点使您可以控制飞利浦色调灯和分组的灯光,还可以将此灯的状态发送到KNX巴士。</p>
3
4
 
4
5
  **一般的**
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueLightSensor">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>该节点从 HUE 光照传感器读取 Lux 事件并发布到 KNX。</p>
3
4
 
4
5
  每当环境光变化时会输出 Lux 值。在 GA 字段输入 KNX 设备名或组地址(自动补全)以进行关联。
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueMotion">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>该节点订阅 Hue 运动传感器的事件,并将其同步到 KNX 以及 Node-RED 流程。</p>
3
4
 
4
5
  在 GA 字段输入 KNX 设备名称或组地址即可自动补全;"Hue 传感器”旁的刷新按钮可重新加载 Hue 设备列表。
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHuePlug">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  # Hue 插座 / Plug
3
4
 
4
5
  ## 概述
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueScene">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  **Hue Scene** 节点将 Hue 场景发布到 KNX,并且可以把 Hue 的原始事件发送到 Node-RED 流程。场景字段支持自动补全;在网桥中新增场景后,请点击刷新图标更新列表。
3
4
 
4
5
  ### 选项卡概览
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueTapDial">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  **Hue Tap Dial** 节点将 Tap Dial 的旋转服务映射到 KNX,并把原始 Hue 事件发送到 Node-RED 流程。配对新设备后,请点击设备字段旁的刷新图标。
3
4
 
4
5
  ### 选项卡
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueTemperatureSensor">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>此节点读取 HUE 温度传感器的温度(°C),并将其映射到 KNX。</p>
3
4
 
4
5
  在 GA 字段(名称或组地址)中输入以关联 KNX GA;输入时会显示设备建议。
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueZigbeeConnectivity">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>该节点从 HUE 设备读取 Zigbee 连接状态,并发布到 KNX。</p>
3
4
 
4
5
  在 GA 字段输入 KNX 设备名或组地址,输入时会自动联想。
@@ -1,4 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHuedevice_software_update">
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会打开与 HUE Controller 相同的完整流程转换器。</p>
2
3
  <p>该节点监控 HUE 设备的软件更新状态,并发布到 KNX。</p>
3
4
 
4
5
  开始在GA字段中键入KNX设备的名称或组地址,可用的设备开始显示
@@ -0,0 +1,104 @@
1
+ 'use strict'
2
+
3
+ // These are the canonical runtime implementations owned by HUE Controller.
4
+ // They deliberately keep the familiar "module receives RED, then calls
5
+ // registerType" shape: that lets us preserve the well-tested lifecycle of each
6
+ // dedicated Hue implementation without registering fifteen more public nodes.
7
+ //
8
+ // None of these paths is listed under `node-red.nodes` in package.json. Node-RED
9
+ // therefore never loads them as palette nodes, and deleting a legacy entry point
10
+ // cannot remove code required by HUE Controller.
11
+ const RUNTIME_MODULES = Object.freeze({
12
+ light: './hueControllerProfiles/runtime/light',
13
+ plug: './hueControllerProfiles/runtime/plug',
14
+ button: './hueControllerProfiles/runtime/button',
15
+ relative_rotary: './hueControllerProfiles/runtime/relative_rotary',
16
+ motion: './hueControllerProfiles/runtime/motion',
17
+ area_motion: './hueControllerProfiles/runtime/area_motion',
18
+ camera_motion: './hueControllerProfiles/runtime/camera_motion',
19
+ contact: './hueControllerProfiles/runtime/contact',
20
+ light_level: './hueControllerProfiles/runtime/light_level',
21
+ temperature: './hueControllerProfiles/runtime/temperature',
22
+ humidity: './hueControllerProfiles/runtime/humidity',
23
+ scene: './hueControllerProfiles/runtime/scene',
24
+ device_power: './hueControllerProfiles/runtime/device_power',
25
+ zigbee_connectivity: './hueControllerProfiles/runtime/zigbee_connectivity',
26
+ device_software_update: './hueControllerProfiles/runtime/device_software_update'
27
+ })
28
+
29
+ // A constructor closes over the RED instance used while loading its module.
30
+ // Cache by RED first (rather than globally) so tests, embedded runtimes and future
31
+ // hot-reload scenarios never accidentally share constructors across runtimes.
32
+ // WeakMap also means an abandoned RED instance is not kept alive by this adapter.
33
+ const constructors = new WeakMap()
34
+
35
+ const getConstructorCache = (RED) => {
36
+ let cache = constructors.get(RED)
37
+ if (!cache) {
38
+ cache = new Map()
39
+ constructors.set(RED, cache)
40
+ }
41
+ return cache
42
+ }
43
+
44
+ const captureRuntimeConstructor = (RED, controllerType) => {
45
+ const modulePath = RUNTIME_MODULES[controllerType]
46
+ if (!modulePath) return undefined
47
+
48
+ const cache = getConstructorCache(RED)
49
+ if (cache.has(controllerType)) return cache.get(controllerType)
50
+
51
+ let capturedConstructor
52
+
53
+ // The private modules believe they are registering an ordinary node. The
54
+ // facade delegates every real RED service through the prototype chain but
55
+ // intercepts only registerType, turning registration into constructor capture.
56
+ // The real Node-RED registry is never touched here.
57
+ const redFacade = Object.create(RED)
58
+ redFacade.nodes = Object.create(RED.nodes)
59
+ redFacade.nodes.registerType = (_type, constructor) => {
60
+ capturedConstructor = constructor
61
+ }
62
+
63
+ // CommonJS itself caches the module source; our per-RED cache stores the
64
+ // constructor produced by invoking its exported registration function.
65
+ require(modulePath)(redFacade)
66
+ if (typeof capturedConstructor !== 'function') {
67
+ throw new Error(`Unable to load HUE Controller runtime for ${controllerType}`)
68
+ }
69
+
70
+ cache.set(controllerType, capturedConstructor)
71
+ return capturedConstructor
72
+ }
73
+
74
+ const normalizeControllerType = (config = {}) => {
75
+ // `hueControllerType` is the authoritative new field. The resource suffix is
76
+ // a compatibility safety net for imported or partially migrated flows.
77
+ const configuredType = String(config.hueControllerType || '').trim()
78
+ if (RUNTIME_MODULES[configuredType]) return configuredType
79
+
80
+ const resourceType = String(config.hueDevice || '').split('#')[1] || ''
81
+ if (RUNTIME_MODULES[resourceType]) return resourceType
82
+
83
+ // grouped_light intentionally falls through to light: both resource kinds
84
+ // use the same mature light runtime and there is no separate grouped profile.
85
+ return 'light'
86
+ }
87
+
88
+ const setupHueControllerRuntime = (RED, node, config) => {
89
+ const controllerType = normalizeControllerType(config)
90
+ const RuntimeConstructor = captureRuntimeConstructor(RED, controllerType)
91
+
92
+ // Execute the selected constructor *on the Controller instance*. Its own
93
+ // RED.nodes.createNode call installs all normal Node-RED lifecycle hooks,
94
+ // while the persisted public type remains knxUltimateHueController.
95
+ RuntimeConstructor.call(node, config)
96
+ node.hueControllerType = controllerType
97
+ }
98
+
99
+ module.exports = {
100
+ RUNTIME_MODULES,
101
+ captureRuntimeConstructor,
102
+ normalizeControllerType,
103
+ setupHueControllerRuntime
104
+ }
@@ -0,0 +1,221 @@
1
+ // Canonical private runtime profile for HUE Controller: area_motion.
2
+ // This module is shipped with the Controller but is intentionally absent from
3
+ // package.json, so Node-RED never registers it as a separate palette node.
4
+ module.exports = function (RED) {
5
+ function knxUltimateHueAreaMotion (config) {
6
+ RED.nodes.createNode(this, config)
7
+ const node = this
8
+ node.serverKNX = RED.nodes.getNode(config.server) || undefined
9
+ node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
10
+ node.topic = node.name
11
+ node.name = config.name === undefined ? 'Hue' : config.name
12
+ node.dpt = ''
13
+ node.notifyreadrequest = true
14
+ node.notifyreadrequestalsorespondtobus = 'false'
15
+ node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
16
+ node.notifyresponse = false
17
+ node.notifywrite = true
18
+ node.initialread = true
19
+ node.listenallga = true
20
+ node.outputtype = 'write'
21
+ node.outputRBE = 'false'
22
+ node.inputRBE = 'false'
23
+ node.currentPayload = ''
24
+ node.passthrough = 'no'
25
+ node.formatmultiplyvalue = 1
26
+ node.formatnegativevalue = 'leave'
27
+ node.formatdecimalsvalue = 2
28
+ node.hueDevice = config.hueDevice
29
+ node.dptAreaMotion = config.dptAreaMotion
30
+ node.motionDPT = (config.dptAreaMotion && config.dptAreaMotion !== '') ? config.dptAreaMotion : '1.001'
31
+ node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
32
+ node.currentDeviceValue = false
33
+ node.enableNodePINS = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes')
34
+ node.outputs = node.enableNodePINS ? 1 : 0
35
+
36
+ if (!node.serverKNX && node.enableNodePINS !== 'yes') {
37
+ node.enableNodePINS = 'yes'
38
+ node.outputs = 1
39
+ }
40
+
41
+ const pushStatus = (status) => {
42
+ if (!status) return
43
+ const provider = node.serverKNX
44
+ if (provider && typeof provider.applyStatusUpdate === 'function') {
45
+ provider.applyStatusUpdate(node, status)
46
+ } else {
47
+ node.status(status)
48
+ }
49
+ }
50
+
51
+ const updateStatus = (status) => {
52
+ if (!status) return
53
+ pushStatus(status)
54
+ }
55
+
56
+ const formatTs = (date) => {
57
+ const d = date instanceof Date ? date : new Date(date)
58
+ const provider = node.serverKNX
59
+ if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
60
+ return `${d.getDate()}, ${d.toLocaleTimeString()}`
61
+ }
62
+
63
+ const safeSendToKNX = (telegram, context = 'write') => {
64
+ try {
65
+ if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
66
+ const now = new Date()
67
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
68
+ return
69
+ }
70
+ node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
71
+ } catch (error) {
72
+ updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
73
+ }
74
+ }
75
+
76
+ node.setNodeStatus = ({ fill, shape, text, payload }) => {
77
+ try {
78
+ if (payload === undefined) payload = ''
79
+ const dDate = new Date()
80
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
81
+ node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
82
+ pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
83
+ } catch (error) { }
84
+ }
85
+
86
+ node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
87
+ try {
88
+ if (payload === undefined) payload = ''
89
+ const dDate = new Date()
90
+ payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
91
+ node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
92
+ pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
93
+ } catch (error) { }
94
+ }
95
+
96
+ const mapMotionValue = (_event) => {
97
+ const motion = _event?.motion
98
+ if (!motion) return undefined
99
+
100
+ const readBoolean = (candidate) => {
101
+ if (typeof candidate === 'boolean') return candidate
102
+ if (candidate && typeof candidate.value === 'boolean') return candidate.value
103
+ return undefined
104
+ }
105
+
106
+ let value = readBoolean(motion.motion)
107
+ if (value === undefined && motion.motion_report) {
108
+ value = readBoolean(motion.motion_report.motion)
109
+ }
110
+ if (value === undefined && motion.motion_report && typeof motion.motion_report.motion === 'boolean') {
111
+ value = motion.motion_report.motion
112
+ }
113
+ if (value === undefined && typeof motion.motion_valid === 'boolean') {
114
+ value = motion.motion_valid
115
+ }
116
+ if (value === undefined) {
117
+ value = readBoolean(motion.motion_valid)
118
+ }
119
+ return value
120
+ }
121
+
122
+ node.handleSend = (msg) => {
123
+ if (!msg || !msg.knx) return
124
+ if (msg.knx.event === 'GroupValue_Read') {
125
+ switch (msg.knx.destination) {
126
+ case config.GAareaMotion:
127
+ node.sendResponseToKNX(node.currentDeviceValue)
128
+ break
129
+ default:
130
+ break
131
+ }
132
+ }
133
+ }
134
+
135
+ node.handleSendHUE = (_event) => {
136
+ try {
137
+ if (_event.id !== config.hueDevice) return
138
+ const motionValue = mapMotionValue(_event)
139
+ if (motionValue === undefined) return
140
+
141
+ const knxMsgPayload = {
142
+ topic: config.GAareaMotion,
143
+ dpt: node.motionDPT,
144
+ payload: motionValue === true || motionValue === 1 || motionValue === '1' ? true : Boolean(motionValue),
145
+ name: node.name,
146
+ event: 'motion',
147
+ rawEvent: _event
148
+ }
149
+
150
+ if (knxMsgPayload.topic) {
151
+ safeSendToKNX({
152
+ grpaddr: knxMsgPayload.topic,
153
+ payload: knxMsgPayload.payload,
154
+ dpt: knxMsgPayload.dpt,
155
+ outputtype: 'write'
156
+ }, 'write')
157
+ }
158
+ node.currentDeviceValue = knxMsgPayload.payload
159
+
160
+ updateStatus({
161
+ fill: 'green',
162
+ shape: 'dot',
163
+ text: `HUE->KNX ${knxMsgPayload.payload} (${formatTs(new Date())})`
164
+ })
165
+
166
+ if (node.enableNodePINS) {
167
+ node.send(knxMsgPayload)
168
+ }
169
+ node.setNodeStatusHue({
170
+ fill: 'blue',
171
+ shape: 'ring',
172
+ text: 'HUE->KNX',
173
+ payload: knxMsgPayload.payload
174
+ })
175
+ } catch (error) {
176
+ updateStatus({
177
+ fill: 'red',
178
+ shape: 'dot',
179
+ text: `HUE->KNX error ${error.message} (${formatTs(new Date())})`
180
+ })
181
+ }
182
+ }
183
+
184
+ node.sendResponseToKNX = (_level) => {
185
+ if (!node.serverKNX) return
186
+ const knxMsgPayload = {
187
+ topic: config.GAareaMotion,
188
+ dpt: node.motionDPT,
189
+ payload: !!(_level === true || _level === 1 || _level === '1')
190
+ }
191
+ if (knxMsgPayload.topic) {
192
+ safeSendToKNX({
193
+ grpaddr: knxMsgPayload.topic,
194
+ payload: knxMsgPayload.payload,
195
+ dpt: knxMsgPayload.dpt,
196
+ outputtype: 'response'
197
+ }, 'response')
198
+ }
199
+ }
200
+
201
+ if (node.serverKNX) {
202
+ node.serverKNX.removeClient(node)
203
+ node.serverKNX.addClient(node)
204
+ }
205
+ if (node.serverHue) {
206
+ node.serverHue.removeClient(node)
207
+ node.serverHue.addClient(node)
208
+ }
209
+
210
+ node.on('close', (done) => {
211
+ if (node.serverKNX) {
212
+ node.serverKNX.removeClient(node)
213
+ }
214
+ if (node.serverHue) {
215
+ node.serverHue.removeClient(node)
216
+ }
217
+ done()
218
+ })
219
+ }
220
+ RED.nodes.registerType('knxUltimateHueAreaMotion', knxUltimateHueAreaMotion)
221
+ }