node-red-contrib-knx-ultimate 6.2.3-beta.2 → 6.2.3-beta.4

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 (131) hide show
  1. package/CHANGELOG.md +6 -4
  2. package/nodes/commonFunctions.js +46 -2
  3. package/nodes/knxUltimateHueAreaMotion.html +5 -1
  4. package/nodes/knxUltimateHueBattery.html +5 -1
  5. package/nodes/knxUltimateHueButton.html +5 -1
  6. package/nodes/knxUltimateHueCameraMotion.html +5 -1
  7. package/nodes/knxUltimateHueContactSensor.html +5 -1
  8. package/nodes/knxUltimateHueController.html +5 -1
  9. package/nodes/knxUltimateHueHumiditySensor.html +5 -1
  10. package/nodes/knxUltimateHueLight.html +5 -1
  11. package/nodes/knxUltimateHueLightSensor.html +5 -1
  12. package/nodes/knxUltimateHueMotion.html +5 -1
  13. package/nodes/knxUltimateHuePlug.html +5 -1
  14. package/nodes/knxUltimateHueScene.html +5 -1
  15. package/nodes/knxUltimateHueTapDial.html +5 -1
  16. package/nodes/knxUltimateHueTemperatureSensor.html +5 -1
  17. package/nodes/knxUltimateHueZigbeeConnectivity.html +5 -1
  18. package/nodes/knxUltimateHuedevice_software_update.html +5 -1
  19. package/nodes/locales/de/knxUltimateHueAreaMotion.html +1 -1
  20. package/nodes/locales/de/knxUltimateHueBattery.html +1 -1
  21. package/nodes/locales/de/knxUltimateHueButton.html +1 -1
  22. package/nodes/locales/de/knxUltimateHueCameraMotion.html +1 -1
  23. package/nodes/locales/de/knxUltimateHueContactSensor.html +1 -1
  24. package/nodes/locales/de/knxUltimateHueController.html +3 -3
  25. package/nodes/locales/de/knxUltimateHueController.json +7 -4
  26. package/nodes/locales/de/knxUltimateHueHumiditySensor.html +1 -1
  27. package/nodes/locales/de/knxUltimateHueLight.html +1 -1
  28. package/nodes/locales/de/knxUltimateHueLightSensor.html +1 -1
  29. package/nodes/locales/de/knxUltimateHueMotion.html +1 -1
  30. package/nodes/locales/de/knxUltimateHuePlug.html +1 -1
  31. package/nodes/locales/de/knxUltimateHueScene.html +1 -1
  32. package/nodes/locales/de/knxUltimateHueTapDial.html +1 -1
  33. package/nodes/locales/de/knxUltimateHueTemperatureSensor.html +1 -1
  34. package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.html +1 -1
  35. package/nodes/locales/de/knxUltimateHuedevice_software_update.html +1 -1
  36. package/nodes/locales/de/matter-config.html +1 -1
  37. package/nodes/locales/en/knxUltimateHueAreaMotion.html +1 -1
  38. package/nodes/locales/en/knxUltimateHueBattery.html +1 -1
  39. package/nodes/locales/en/knxUltimateHueButton.html +1 -1
  40. package/nodes/locales/en/knxUltimateHueCameraMotion.html +1 -1
  41. package/nodes/locales/en/knxUltimateHueContactSensor.html +1 -1
  42. package/nodes/locales/en/knxUltimateHueController.html +3 -3
  43. package/nodes/locales/en/knxUltimateHueController.json +7 -4
  44. package/nodes/locales/en/knxUltimateHueHumiditySensor.html +1 -1
  45. package/nodes/locales/en/knxUltimateHueLight.html +1 -1
  46. package/nodes/locales/en/knxUltimateHueLightSensor.html +1 -1
  47. package/nodes/locales/en/knxUltimateHueMotion.html +1 -1
  48. package/nodes/locales/en/knxUltimateHuePlug.html +1 -1
  49. package/nodes/locales/en/knxUltimateHueScene.html +1 -1
  50. package/nodes/locales/en/knxUltimateHueTapDial.html +1 -1
  51. package/nodes/locales/en/knxUltimateHueTemperatureSensor.html +1 -1
  52. package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.html +1 -1
  53. package/nodes/locales/en/knxUltimateHuedevice_software_update.html +1 -1
  54. package/nodes/locales/en/matter-config.html +1 -1
  55. package/nodes/locales/es/knxUltimateHueAreaMotion.html +1 -1
  56. package/nodes/locales/es/knxUltimateHueBattery.html +1 -1
  57. package/nodes/locales/es/knxUltimateHueButton.html +1 -1
  58. package/nodes/locales/es/knxUltimateHueCameraMotion.html +1 -1
  59. package/nodes/locales/es/knxUltimateHueContactSensor.html +1 -1
  60. package/nodes/locales/es/knxUltimateHueController.html +3 -3
  61. package/nodes/locales/es/knxUltimateHueController.json +7 -4
  62. package/nodes/locales/es/knxUltimateHueHumiditySensor.html +1 -1
  63. package/nodes/locales/es/knxUltimateHueLight.html +1 -1
  64. package/nodes/locales/es/knxUltimateHueLightSensor.html +1 -1
  65. package/nodes/locales/es/knxUltimateHueMotion.html +1 -1
  66. package/nodes/locales/es/knxUltimateHuePlug.html +1 -1
  67. package/nodes/locales/es/knxUltimateHueScene.html +1 -1
  68. package/nodes/locales/es/knxUltimateHueTapDial.html +1 -1
  69. package/nodes/locales/es/knxUltimateHueTemperatureSensor.html +1 -1
  70. package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.html +1 -1
  71. package/nodes/locales/es/knxUltimateHuedevice_software_update.html +1 -1
  72. package/nodes/locales/es/matter-config.html +1 -1
  73. package/nodes/locales/fr/knxUltimateHueAreaMotion.html +1 -1
  74. package/nodes/locales/fr/knxUltimateHueBattery.html +1 -1
  75. package/nodes/locales/fr/knxUltimateHueButton.html +1 -1
  76. package/nodes/locales/fr/knxUltimateHueCameraMotion.html +1 -1
  77. package/nodes/locales/fr/knxUltimateHueContactSensor.html +1 -1
  78. package/nodes/locales/fr/knxUltimateHueController.html +3 -3
  79. package/nodes/locales/fr/knxUltimateHueController.json +7 -4
  80. package/nodes/locales/fr/knxUltimateHueHumiditySensor.html +1 -1
  81. package/nodes/locales/fr/knxUltimateHueLight.html +1 -1
  82. package/nodes/locales/fr/knxUltimateHueLightSensor.html +1 -1
  83. package/nodes/locales/fr/knxUltimateHueMotion.html +1 -1
  84. package/nodes/locales/fr/knxUltimateHuePlug.html +1 -1
  85. package/nodes/locales/fr/knxUltimateHueScene.html +1 -1
  86. package/nodes/locales/fr/knxUltimateHueTapDial.html +1 -1
  87. package/nodes/locales/fr/knxUltimateHueTemperatureSensor.html +1 -1
  88. package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.html +1 -1
  89. package/nodes/locales/fr/knxUltimateHuedevice_software_update.html +1 -1
  90. package/nodes/locales/fr/matter-config.html +1 -1
  91. package/nodes/locales/it/knxUltimateHueAreaMotion.html +1 -1
  92. package/nodes/locales/it/knxUltimateHueBattery.html +1 -1
  93. package/nodes/locales/it/knxUltimateHueButton.html +1 -1
  94. package/nodes/locales/it/knxUltimateHueCameraMotion.html +1 -1
  95. package/nodes/locales/it/knxUltimateHueContactSensor.html +1 -1
  96. package/nodes/locales/it/knxUltimateHueController.html +3 -3
  97. package/nodes/locales/it/knxUltimateHueController.json +7 -4
  98. package/nodes/locales/it/knxUltimateHueHumiditySensor.html +1 -1
  99. package/nodes/locales/it/knxUltimateHueLight.html +1 -1
  100. package/nodes/locales/it/knxUltimateHueLightSensor.html +1 -1
  101. package/nodes/locales/it/knxUltimateHueMotion.html +1 -1
  102. package/nodes/locales/it/knxUltimateHuePlug.html +1 -1
  103. package/nodes/locales/it/knxUltimateHueScene.html +1 -1
  104. package/nodes/locales/it/knxUltimateHueTapDial.html +1 -1
  105. package/nodes/locales/it/knxUltimateHueTemperatureSensor.html +1 -1
  106. package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.html +1 -1
  107. package/nodes/locales/it/knxUltimateHuedevice_software_update.html +1 -1
  108. package/nodes/locales/it/matter-config.html +1 -1
  109. package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.html +1 -1
  110. package/nodes/locales/zh-CN/knxUltimateHueBattery.html +1 -1
  111. package/nodes/locales/zh-CN/knxUltimateHueButton.html +1 -1
  112. package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.html +1 -1
  113. package/nodes/locales/zh-CN/knxUltimateHueContactSensor.html +1 -1
  114. package/nodes/locales/zh-CN/knxUltimateHueController.html +3 -3
  115. package/nodes/locales/zh-CN/knxUltimateHueController.json +7 -4
  116. package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.html +1 -1
  117. package/nodes/locales/zh-CN/knxUltimateHueLight.html +1 -1
  118. package/nodes/locales/zh-CN/knxUltimateHueLightSensor.html +1 -1
  119. package/nodes/locales/zh-CN/knxUltimateHueMotion.html +1 -1
  120. package/nodes/locales/zh-CN/knxUltimateHuePlug.html +1 -1
  121. package/nodes/locales/zh-CN/knxUltimateHueScene.html +1 -1
  122. package/nodes/locales/zh-CN/knxUltimateHueTapDial.html +1 -1
  123. package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.html +1 -1
  124. package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.html +1 -1
  125. package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.html +1 -1
  126. package/nodes/locales/zh-CN/matter-config.html +1 -1
  127. package/nodes/matter-config.html +67 -6
  128. package/nodes/matter-config.js +51 -0
  129. package/nodes/utils/matterEngine.mjs +111 -2
  130. package/package.json +1 -1
  131. package/resources/hueControllerMigrationDialog.js +45 -40
@@ -1,5 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueContactSensor">
2
- <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中的橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>该节点将 HUE 接触传感器事件映射到 KNX 组地址。</p>
4
4
 
5
5
  开始输入GA字段,KNX设备的名称或组地址,可用设备在输入时开始显示。
@@ -1,5 +1,5 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateHueController">
2
- <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate 视频教程(YouTube 播放列表)</strong></a></p>
2
+ <p><a href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E" target="_blank" rel="noopener noreferrer"><strong>KNX-Ultimate 视频教程(YouTube 播放列表)</strong></a> 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p><strong>HUE Controller</strong> 是面向 Hue API v2 资源的统一节点。</p>
4
4
  <p>选择设备功能,然后使用原有专用 Hue 节点中已经稳定的 KNX 映射和行为设置。</p>
5
5
  <p>设备功能选择器使用可用宽度的三分之一,使编辑器保持紧凑。</p>
@@ -9,8 +9,8 @@
9
9
  <p>对于单灯,<strong>调光</strong>、<strong>可调白光</strong>、<strong>RGB/HSV</strong> 和原生效果部分会根据所选 Hue API v2 资源实时报告的能力显示。当灯具提供 <code>dimming</code>、<code>color_temperature</code> 或 <code>color</code> 时,相应的 KNX 映射会自动出现。</p>
10
10
  <p>Hue Bridge 加载资源期间,灯光编辑器会显示旋转的沙漏,并每 500 毫秒检查一次就绪状态。约 10 秒后编辑器会解除等待,映射选项卡保持隐藏,并显示本地化错误。保存、关闭或切换功能都会取消尚未结束的等待。</p>
11
11
  <p>选择网关后,映射字段可输入 KNX 组地址或从 ETS 导入的名称;兼容的数据点会从该网关加载。</p>
12
- <p>仅当 Node-RED 编辑器在当前流程中检测到至少一个旧版 HUE 节点时,才会显示迁移按钮。每个旧版 HUE 编辑器的弃用提示下方也提供相同的橙色按钮。</p>
13
- <p><strong>转换旧版 HUE 节点</strong>会完全在浏览器中完成转换,不会发送任何流程或节点数据。成功完成本地转换后,浏览器会打开一封发给作者的可编辑邮件草稿,而不会离开 Node-RED,并在新窗口中打开捐赠页面。草稿只包含已转换节点数量和可选备注空间,绝不会自动发送,也不会向 PayPal 链接添加任何流程数据。</p>
12
+ <p>仅当 Node-RED 编辑器在当前流程中检测到至少一个旧版 HUE 节点时,才会显示迁移按钮。每个旧版 HUE 编辑器的弃用提示下方也提供带白色文字的相同高对比度橙色按钮。</p>
13
+ <p><strong>转换旧版 HUE 节点</strong>会完全在浏览器中完成转换,不会发送任何流程或节点数据。成功完成本地转换后,浏览器只会打开一封发给作者的可编辑邮件草稿,而不会离开 Node-RED。草稿只包含已转换节点数量和可选备注空间,绝不会自动发送。最终的 Node-RED 消息会提供一个可选的支持按钮;只有点击该按钮时才会打开捐赠页面。</p>
14
14
  <p>开始之前,请先导出流程备份。编辑器只把检测到的旧版 HUE 节点转换为 HUE Controller 实例。原有属性、配置引用、位置、分组和连线都保持不变。流程会标记为已修改,但不会自动部署:请检查后自行点击 <strong>Deploy</strong>。节点已变化、流程被锁定或发生本地转换错误时,流程不会发生变化。<strong>安全检查:</strong>点击 Deploy 前,请逐一检查所有已修改 HUE 节点的功能、配置引用、输入/输出端口和连线。处理完成后,Node-RED 的固定消息会一直显示,直到点击<strong>确定</strong>。</p>
15
15
  <p>Hue 事件仍然只是状态更新,不会变成新的 Hue 命令。HUE Controller 内置私有的运行时、编辑器、模板和翻译配置,因此不依赖加载已弃用的节点类型。原始 Hue Light 节点保持不变。原有专用 Hue 节点仍会注册以兼容现有流程,但这些节点现已冻结,不再获得新功能或维护更新。Node-RED 会从节点面板中隐藏其特殊的 <code>deprecated</code> 分类;现有实例仍可编辑和部署,颜色比 HUE Controller 更浅,会在画布上标记为 <code>(deprecated)</code>,编辑器顶部也会显示迁移提示。</p>
16
16
 
@@ -8,21 +8,24 @@
8
8
  "legacy_node_notice": "此旧版 HUE 节点已弃用。新流程请使用新的 HUE Controller 节点。",
9
9
  "migration_button": "转换旧版 HUE 节点",
10
10
  "migration_button_title": "转换编辑器中的所有旧版 HUE 节点",
11
- "migration_disclaimer": "转换完全在浏览器中进行,不会发送任何流程或节点数据。随后会打开可编辑的邮件草稿,并在新窗口中打开捐赠页面;不会自动发送任何内容,也不会向 PayPal 链接添加任何流程数据。",
11
+ "migration_video": "视频指南",
12
+ "migration_video_title": "在 YouTube 上观看旧版 HUE 节点转换指南",
13
+ "migration_disclaimer": "转换完全在浏览器中进行,不会发送任何流程或节点数据。随后只会打开可编辑的邮件草稿,不会自动发送任何内容。只有您选择最终消息中的可选支持按钮时,才会打开捐赠页面。",
12
14
  "migration_title": "转换旧版 HUE 节点",
13
15
  "migration_confirm": "将在编辑器中直接转换 {{count}} 个旧版 HUE 节点。",
14
16
  "migration_privacy": "转换完全在此浏览器中进行。不会传输或保留任何流程、节点、配置、凭据、组地址或连线数据。",
15
- "migration_email_notice": "转换后,邮件应用会打开一封收件人为作者的可编辑草稿,而不会离开 Node-RED 页面;浏览器新窗口会打开捐赠页面。草稿只包含已转换节点的数量和可选备注区域;不会自动发送,也不会向 PayPal 链接添加任何流程数据。",
17
+ "migration_email_notice": "转换后,邮件应用会打开一封收件人为作者的可编辑草稿,而不会离开 Node-RED 页面。草稿只包含已转换节点的数量和可选备注区域;不会自动发送。最终的 Node-RED 消息会提供一个支持 KNX Ultimate 的可选按钮。",
16
18
  "migration_email_subject": "KNX Ultimate - 已使用旧版 HUE 转换",
17
19
  "migration_email_body": "Massimo,您好:\n\n我使用了旧版 HUE 节点转换按钮。\n已转换的旧版 HUE 节点:{{count}}。\n\n可选备注:\n",
18
20
  "migration_email_failed": "节点已转换,但无法打开邮件草稿。",
19
- "migration_donation_failed": "节点已转换,但无法打开捐赠页面。",
21
+ "migration_donation_failed": "无法打开捐赠页面。您可以重试。",
20
22
  "migration_deploy_notice": "继续之前,请先导出流程备份。当前编辑器将关闭。只修改旧版 HUE 节点;所有配置节点和连线保持不变。请检查结果,然后自行点击 Deploy。",
21
23
  "migration_review_notice": "安全检查:转换后,请逐一检查流程中所有已修改的 HUE 节点。点击 Deploy 前,请确认所选功能、配置引用、输入/输出端口和连线均正确。",
22
24
  "migration_convert": "转换 HUE 节点",
23
25
  "migration_close": "取消",
26
+ "migration_donate": "支持 KNX Ultimate ❤️",
24
27
  "migration_ok": "确定",
25
- "migration_success": "处理完成。已转换 {{count}} 个旧版 HUE 节点。点击 Deploy 前,请逐一检查所有已修改节点的功能、配置、端口和连线。",
28
+ "migration_success": "处理完成。已转换 {{count}} 个旧版 HUE 节点。点击 Deploy 前,请逐一检查所有已修改节点的功能、配置、端口和连线。如果本次转换为您节省了时间,可以通过下方的可选按钮支持 KNX Ultimate 的持续开发。",
26
29
  "migration_failed": "本地 HUE 迁移失败;流程未被修改:",
27
30
  "migration_none": "未找到旧版 HUE 节点。",
28
31
  "migration_unavailable": "HUE 迁移工具不可用。更新软件包后请重启 Node-RED。",
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>该节点从 HUE 湿度传感器读取相对湿度 (%) 并映射到 KNX。</p>
4
4
 
5
5
  在 GA 字段中开始输入(名称或组地址)以关联 KNX GA;输入时会显示匹配的设备。
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>此节点使您可以控制飞利浦色调灯和分组的灯光,还可以将此灯的状态发送到KNX巴士。</p>
4
4
 
5
5
  **一般的**
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>该节点从 HUE 光照传感器读取 Lux 事件并发布到 KNX。</p>
4
4
 
5
5
  每当环境光变化时会输出 Lux 值。在 GA 字段输入 KNX 设备名或组地址(自动补全)以进行关联。
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>该节点订阅 Hue 运动传感器的事件,并将其同步到 KNX 以及 Node-RED 流程。</p>
4
4
 
5
5
  在 GA 字段输入 KNX 设备名称或组地址即可自动补全;"Hue 传感器”旁的刷新按钮可重新加载 Hue 设备列表。
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  # Hue 插座 / Plug
4
4
 
5
5
  ## 概述
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  **Hue Scene** 节点将 Hue 场景发布到 KNX,并且可以把 Hue 的原始事件发送到 Node-RED 流程。场景字段支持自动补全;在网桥中新增场景后,请点击刷新图标更新列表。
4
4
 
5
5
  ### 选项卡概览
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  **Hue Tap Dial** 节点将 Tap Dial 的旋转服务映射到 KNX,并把原始 Hue 事件发送到 Node-RED 流程。配对新设备后,请点击设备字段旁的刷新图标。
4
4
 
5
5
  ### 选项卡
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>此节点读取 HUE 温度传感器的温度(°C),并将其映射到 KNX。</p>
4
4
 
5
5
  在 GA 字段(名称或组地址)中输入以关联 KNX GA;输入时会显示设备建议。
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>该节点从 HUE 设备读取 Zigbee 连接状态,并发布到 KNX。</p>
4
4
 
5
5
  在 GA 字段输入 KNX 设备名或组地址,输入时会自动联想。
@@ -1,5 +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 节点;完成后会打开一封可编辑的邮件草稿,并在浏览器新窗口中打开捐赠页面。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”。</p>
2
+ <p><strong>已弃用:</strong>此专用 HUE 节点仍可用于现有流程。新项目请使用 <strong>HUE Controller</strong>。旧版节点会在节点面板和画布上标记为 <code>(deprecated)</code>,颜色比 HUE Controller 更浅,编辑器顶部也会显示迁移提示。 此编辑器中带白色文字的高对比度橙色迁移按钮会在本地转换所有旧版 HUE 节点;完成后只会打开一封可编辑的邮件草稿。邮件绝不会自动发送。流程结束后,Node-RED 的固定消息会一直显示,直到您点击“确定”,并提供一个可选支持按钮;只有点击该按钮时才会打开捐赠页面。 开始前,请<a href="https://youtu.be/f0Evf2QFI7c" target="_blank" rel="noopener noreferrer">在 YouTube 上观看说明视频</a>。</p>
3
3
  <p>该节点监控 HUE 设备的软件更新状态,并发布到 KNX。</p>
4
4
 
5
5
  开始在GA字段中键入KNX设备的名称或组地址,可用的设备开始显示
@@ -15,7 +15,7 @@
15
15
 
16
16
  无需手动输入二维码 payload:点击**摄像头**可实时扫描,点击**图片**可从本地照片读取。支持浅色背景上的标准深色二维码以及深色背景上的反白二维码。解码完全在浏览器中进行;读取到有效的 Matter 二维码后,编辑器会填写配对码并立即自动开始配对。如需设置可选设备名称,请在扫描前填写。手动输入的配对码仍需点击**配对**才会开始。实时使用摄像头要求通过 HTTPS 或从 `localhost` 打开编辑器;若条件不满足,编辑器会说明此限制,而图片加载功能仍然可用。
17
17
 
18
- 配对期间,阻塞式等待面板会覆盖编辑器并阻止进一步点击,直到操作成功完成或失败。
18
+ 配对期间,阻塞式面板会覆盖编辑器并阻止进一步点击,直到操作成功完成或失败。进度条跟随 matter.js 的实际阶段,并以英文显示当前操作。当设备公开产品身份时,面板还会显示产品名称、Vendor ID 和 Product ID。
19
19
 
20
20
  如果设备是全新的且仅支持蓝牙配对,请先用厂商 App 或其他 Matter 控制器(Alexa、Google Home、Apple Home)配对,然后使用其**"与其他中枢共享/配对"**功能为 KNX-Ultimate 生成新的配对码。这样设备可以同时加入多个 fabric。
21
21
 
@@ -22,6 +22,11 @@
22
22
  var $devicesBody = $("#matter-devices-body");
23
23
  var $refreshDevices = $("#matterRefreshDevices");
24
24
  var $pairSpinner = $("#matterPairSpinner");
25
+ var $pairProgress = $("#matter-pairing-progress");
26
+ var $pairProgressBar = $("#matter-pairing-progress-bar");
27
+ var $pairProgressPercent = $("#matter-pairing-progress-percent");
28
+ var $pairProgressDescription = $("#matter-pairing-progress-description");
29
+ var $pairProgressDevice = $("#matter-pairing-progress-device");
25
30
  var $storageExport = $('#matter-storage-export');
26
31
  var $storageImport = $('#matter-storage-import');
27
32
  var $storageFile = $('#matter-storage-file');
@@ -35,6 +40,8 @@
35
40
  var qrScannerControls = null;
36
41
  var qrScannerStarting = false;
37
42
  var qrUtils = window.KNXUltimateMatterQrScanner;
43
+ var pairingProgressTimer = null;
44
+ var pairingOperationId = '';
38
45
 
39
46
  function qrText(key) {
40
47
  return RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties." + key);
@@ -183,7 +190,10 @@
183
190
  this.value = '';
184
191
  scanQrImage(file);
185
192
  });
186
- node._matterQrCleanup = stopQrCamera;
193
+ node._matterQrCleanup = function () {
194
+ stopQrCamera();
195
+ stopPairingProgress();
196
+ };
187
197
 
188
198
  function storageExportUrl() {
189
199
  var adminRoot = (RED.settings && typeof RED.settings.httpAdminRoot === 'string') ? RED.settings.httpAdminRoot : '/';
@@ -209,6 +219,44 @@
209
219
  });
210
220
  }
211
221
 
222
+ function setPairingProgress(data) {
223
+ if (!data) return;
224
+ var percent = Number(data.percent);
225
+ if (!isFinite(percent)) percent = 0;
226
+ percent = Math.max(0, Math.min(100, Math.round(percent)));
227
+ $pairProgress.attr('aria-valuenow', percent);
228
+ $pairProgressBar.css('width', percent + '%');
229
+ $pairProgressPercent.text(percent + '%');
230
+ if (data.message) $pairProgressDescription.text(data.message);
231
+ if (data.device && data.device.productName) {
232
+ var details = data.device.productName;
233
+ if (data.device.vendorId !== undefined) details += ' · VID ' + data.device.vendorId;
234
+ if (data.device.productId !== undefined) details += ' · PID ' + data.device.productId;
235
+ $pairProgressDevice.text(details).show();
236
+ }
237
+ }
238
+
239
+ function stopPairingProgress() {
240
+ if (pairingProgressTimer !== null) clearInterval(pairingProgressTimer);
241
+ pairingProgressTimer = null;
242
+ pairingOperationId = '';
243
+ }
244
+
245
+ function startPairingProgress(operationId) {
246
+ stopPairingProgress();
247
+ pairingOperationId = operationId;
248
+ $pairProgressDevice.empty().hide();
249
+ setPairingProgress({ percent: 2, message: 'Preparing Matter commissioning…' });
250
+ pairingProgressTimer = setInterval(function () {
251
+ var activeOperationId = pairingOperationId;
252
+ if (!activeOperationId) return;
253
+ $.getJSON('KNXUltimateMatterPairProgress?serverId=' + encodeURIComponent(node.id) + '&operationId=' + encodeURIComponent(activeOperationId) + '&_=' + Date.now(), function (data) {
254
+ if (activeOperationId !== pairingOperationId || !data || data.operationId !== activeOperationId) return;
255
+ setPairingProgress(data);
256
+ });
257
+ }, 400);
258
+ }
259
+
212
260
  function renderDevices(devices) {
213
261
  $devicesBody.empty();
214
262
  if (!Array.isArray(devices) || devices.length === 0) {
@@ -293,15 +341,20 @@
293
341
  return;
294
342
  }
295
343
  $pairButton.prop('disabled', true);
344
+ var operationId = 'matter-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10);
345
+ var pairingSucceeded = false;
346
+ startPairingProgress(operationId);
296
347
  $pairSpinner.show();
297
348
  $.ajax({
298
- url: 'KNXUltimateMatterPair?serverId=' + node.id + '&code=' + encodeURIComponent(code) + (desiredName !== '' ? '&name=' + encodeURIComponent(desiredName) : ''),
349
+ url: 'KNXUltimateMatterPair?serverId=' + encodeURIComponent(node.id) + '&operationId=' + encodeURIComponent(operationId) + '&code=' + encodeURIComponent(code) + (desiredName !== '' ? '&name=' + encodeURIComponent(desiredName) : ''),
299
350
  dataType: 'json',
300
351
  timeout: 180000
301
352
  }).done(function (data) {
302
353
  if (data && data.error) {
303
354
  notifyPairingError(data.error);
304
355
  } else {
356
+ pairingSucceeded = true;
357
+ setPairingProgress({ percent: 100, message: 'Matter commissioning completed successfully.' });
305
358
  var pairedName = data.name || data.productName || data.nodeId;
306
359
  RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.pairing_ok") + ' ' + pairedName + ' (Node ID ' + data.nodeId + ')', { type: 'success' });
307
360
  if (data.renameError) RED.notify(data.renameError, { type: 'warning', fixed: true });
@@ -314,8 +367,10 @@
314
367
  try { msg = jqXHR.responseJSON.error; } catch (e) { msg = errorThrown || textStatus; }
315
368
  notifyPairingError(msg);
316
369
  }).always(function () {
370
+ stopPairingProgress();
317
371
  $pairButton.prop('disabled', false);
318
- $pairSpinner.hide();
372
+ if (pairingSucceeded) setTimeout(function () { $pairSpinner.hide(); }, 350);
373
+ else $pairSpinner.hide();
319
374
  });
320
375
  });
321
376
 
@@ -496,9 +551,15 @@
496
551
 
497
552
  <div id="matterPairSpinner" role="alertdialog" aria-modal="true" aria-labelledby="matter-pairing-overlay-message"
498
553
  style="display:none; position:fixed; inset:0; z-index:100000; background:rgba(20, 24, 28, 0.72); cursor:wait;">
499
- <div style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); min-width:280px; max-width:80vw; padding:28px 34px; border-radius:8px; background:#fff; color:#333; text-align:center; box-shadow:0 8px 30px rgba(0,0,0,0.4);">
500
- <i class="fa fa-circle-notch fa-spin" aria-hidden="true" style="display:block; margin-bottom:16px; color:#1b7d33; font-size:54px;"></i>
501
- <strong id="matter-pairing-overlay-message" data-i18n="matter-config.properties.pairing_wait" style="font-size:16px;"></strong>
554
+ <div style="position:absolute; top:50%; left:50%; transform:translate(-50%, -50%); width:460px; max-width:80vw; padding:28px 34px; border-radius:8px; background:#fff; color:#333; text-align:center; box-shadow:0 8px 30px rgba(0,0,0,0.4);">
555
+ <strong id="matter-pairing-overlay-message" data-i18n="matter-config.properties.pairing_wait" style="display:block; margin-bottom:18px; font-size:16px;"></strong>
556
+ <div id="matter-pairing-progress" role="progressbar" aria-label="Matter commissioning progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
557
+ style="position:relative; width:100%; height:20px; overflow:hidden; border-radius:10px; background:#66717a; box-shadow:inset 0 1px 3px rgba(0,0,0,0.22);">
558
+ <div id="matter-pairing-progress-bar" style="width:0; height:100%; border-radius:10px; background:#1b7d33; transition:width 250ms ease;"></div>
559
+ <span id="matter-pairing-progress-percent" style="position:absolute; inset:0; color:#fff; font-size:12px; font-weight:bold; line-height:20px; text-shadow:0 1px 2px #000;">0%</span>
560
+ </div>
561
+ <div id="matter-pairing-progress-description" aria-live="polite" style="min-height:20px; margin-top:14px; font-size:14px; line-height:20px;">Preparing Matter commissioning…</div>
562
+ <div id="matter-pairing-progress-device" style="display:none; margin-top:7px; color:#666; font-size:12px;"></div>
502
563
  </div>
503
564
  </div>
504
565
 
@@ -22,6 +22,13 @@ module.exports = (RED) => {
22
22
  node.matterManager = null
23
23
  node.timerMatterConfigCheckState = null
24
24
  node.storageOperation = null
25
+ node.commissioningProgress = {
26
+ active: false,
27
+ operationId: '',
28
+ sequence: 0,
29
+ percent: 0,
30
+ message: 'Waiting for commissioning to start.'
31
+ }
25
32
  try {
26
33
  node.sysLogger = loggerSetup({ loglevel: node.loglevel, setPrefix: 'matter-config.js' })
27
34
  } catch (error) { console.log(error.stack) }
@@ -247,6 +254,50 @@ module.exports = (RED) => {
247
254
  return node.matterManager.commission(_pairingCode, _options)
248
255
  }
249
256
 
257
+ node.beginCommissioningProgress = (_operationId) => {
258
+ if (node.commissioningProgress.active === true) return false
259
+ node.commissioningProgress = {
260
+ active: true,
261
+ operationId: String(_operationId || ''),
262
+ sequence: node.commissioningProgress.sequence + 1,
263
+ percent: 2,
264
+ phase: 'starting',
265
+ message: 'Preparing Matter commissioning…',
266
+ updatedAt: Date.now()
267
+ }
268
+ return true
269
+ }
270
+
271
+ node.reportCommissioningProgress = (_operationId, _progress = {}) => {
272
+ if (node.commissioningProgress.operationId !== String(_operationId || '')) return false
273
+ const numericPercent = Number(_progress.percent)
274
+ const previousPercent = Number(node.commissioningProgress.percent) || 0
275
+ node.commissioningProgress = {
276
+ ...node.commissioningProgress,
277
+ ..._progress,
278
+ active: true,
279
+ operationId: node.commissioningProgress.operationId,
280
+ sequence: node.commissioningProgress.sequence + 1,
281
+ percent: Number.isFinite(numericPercent) ? Math.max(previousPercent, Math.min(100, Math.max(0, numericPercent))) : previousPercent,
282
+ updatedAt: Date.now()
283
+ }
284
+ return true
285
+ }
286
+
287
+ node.endCommissioningProgress = (_operationId, _progress = {}) => {
288
+ if (node.commissioningProgress.operationId !== String(_operationId || '')) return false
289
+ node.reportCommissioningProgress(_operationId, _progress)
290
+ node.commissioningProgress.active = false
291
+ return true
292
+ }
293
+
294
+ node.getCommissioningProgress = (_operationId) => {
295
+ if (node.commissioningProgress.operationId !== String(_operationId || '')) {
296
+ return { active: false, percent: 0, message: 'Waiting for commissioning to start.' }
297
+ }
298
+ return { ...node.commissioningProgress }
299
+ }
300
+
250
301
  node.removeCommissionedNode = async (_nodeId) => {
251
302
  if (node.matterManager === null) throw new Error('Matter controller not started')
252
303
  return node.matterManager.removeNode(_nodeId)
@@ -88,15 +88,95 @@ class classMatter extends EventEmitter {
88
88
  const { Environment, StorageService, Logger, LogLevel } = await import('@matter/main')
89
89
  const { Seconds } = await import('@matter/general')
90
90
  const { CommissioningController } = await import('@project-chip/matter.js')
91
+ const { ControllerCommissioningFlow } = await import('@matter/protocol')
91
92
  const { NodeStates } = await import('@project-chip/matter.js/device')
92
93
  const { ManualPairingCodeCodec, QrPairingCodeCodec, NodeId } = await import('@matter/main/types')
93
94
  const { BasicInformation, GeneralCommissioning } = await import('@matter/main/clusters')
94
95
  this._api = {
95
- Environment, StorageService, Logger, LogLevel, Seconds, CommissioningController, NodeStates, ManualPairingCodeCodec, QrPairingCodeCodec, NodeId, BasicInformation, GeneralCommissioning
96
+ Environment, StorageService, Logger, LogLevel, Seconds, CommissioningController, ControllerCommissioningFlow, NodeStates, ManualPairingCodeCodec, QrPairingCodeCodec, NodeId, BasicInformation, GeneralCommissioning
96
97
  }
97
98
  return this._api
98
99
  }
99
100
 
101
+ _reportCommissioningProgress = (_callback, progress) => {
102
+ if (typeof _callback !== 'function') return
103
+ try {
104
+ _callback(progress)
105
+ } catch (error) {
106
+ this._log('warn', `classMatter: commissioning progress callback: ${error.message}`)
107
+ }
108
+ }
109
+
110
+ _createProgressCommissioningFlow = (_callback) => {
111
+ const BaseFlow = this._api.ControllerCommissioningFlow
112
+ const report = (progress) => this._reportCommissioningProgress(_callback, progress)
113
+ const descriptions = {
114
+ GetInitialData: { percent: 22, message: 'Reading commissioning information from the device…' },
115
+ 'GeneralCommissioning.ArmFailsafe': { percent: 30, message: 'Arming the device fail-safe timer…' },
116
+ 'GeneralCommissioning.ConfigureRegulatoryInformation': { percent: 36, message: 'Applying regulatory configuration…' },
117
+ 'TimeSynchronization.SynchronizeTime': { percent: 42, message: 'Synchronizing the device clock…' },
118
+ 'OperationalCredentials.DeviceAttestation': { percent: 50, message: 'Verifying device identity and attestation…' },
119
+ 'OperationalCredentials.Certificates': { percent: 62, message: 'Installing operational credentials…' },
120
+ AccessControl: { percent: 72, message: 'Configuring Matter access control…' },
121
+ 'NetworkCommissioning.Validate': { percent: 76, message: 'Checking the device network configuration…' },
122
+ 'NetworkCommissioning.Wifi': { percent: 78, message: 'Connecting the device to the Wi-Fi network…' },
123
+ 'NetworkCommissioning.Thread': { percent: 78, message: 'Connecting the device to the Thread network…' },
124
+ Reconnect: { percent: 84, message: 'Reconnecting through the operational CASE session…' },
125
+ 'GeneralCommissioning.Complete': { percent: 92, message: 'Completing commissioning on the device…' },
126
+ 'OperationalCredentials.UpdateFabricLabel': { percent: 96, message: 'Applying the Matter fabric label…' },
127
+ 'AdditionalLogic.AddDefaultOtaProvider': { percent: 97, message: 'Configuring the default OTA provider…' }
128
+ }
129
+
130
+ return class ProgressCommissioningFlow extends BaseFlow {
131
+ constructor (...args) {
132
+ super(...args)
133
+ this.commissioningSteps.forEach((step) => {
134
+ const stepLogic = step.stepLogic
135
+ step.stepLogic = async () => {
136
+ const description = descriptions[step.name] || {
137
+ percent: 20,
138
+ message: `Running Matter commissioning step ${step.stepNumber}.${step.subStepNumber}: ${step.name}…`
139
+ }
140
+ const device = this.collectedCommissioningData?.productName
141
+ ? {
142
+ productName: this.collectedCommissioningData.productName,
143
+ vendorId: this.collectedCommissioningData.vendorId,
144
+ productId: this.collectedCommissioningData.productId
145
+ }
146
+ : undefined
147
+ report({
148
+ phase: 'commissioning',
149
+ step: `${step.stepNumber}.${step.subStepNumber}`,
150
+ stepName: step.name,
151
+ percent: description.percent,
152
+ message: description.message,
153
+ device
154
+ })
155
+ const result = await stepLogic()
156
+ // GetInitialData is the first point where the commissionee may have exposed
157
+ // its product identity. Publish it immediately instead of waiting for the
158
+ // following step to make the editor show it.
159
+ if (step.name === 'GetInitialData' && this.collectedCommissioningData?.productName) {
160
+ report({
161
+ phase: 'commissioning',
162
+ step: `${step.stepNumber}.${step.subStepNumber}`,
163
+ stepName: step.name,
164
+ percent: 28,
165
+ message: 'Device identified. Preparing secure commissioning…',
166
+ device: {
167
+ productName: this.collectedCommissioningData.productName,
168
+ vendorId: this.collectedCommissioningData.vendorId,
169
+ productId: this.collectedCommissioningData.productId
170
+ }
171
+ })
172
+ }
173
+ return result
174
+ }
175
+ })
176
+ }
177
+ }
178
+ }
179
+
100
180
  _resolveTargetHosts = async (_targetHost) => {
101
181
  const host = normalizeHostForCompare(_targetHost)
102
182
  if (host === '') return []
@@ -286,6 +366,12 @@ class classMatter extends EventEmitter {
286
366
  let passcode
287
367
  let identifierData
288
368
  const discoveryCapabilities = { onIpNetwork: true }
369
+ const onProgress = _options?.onProgress
370
+ this._reportCommissioningProgress(onProgress, {
371
+ phase: 'preparing',
372
+ percent: 5,
373
+ message: 'Validating the Matter pairing code…'
374
+ })
289
375
  if (code.toUpperCase().startsWith('MT:')) {
290
376
  const qr = api.QrPairingCodeCodec.decode(code)[0]
291
377
  passcode = qr.passcode
@@ -305,6 +391,11 @@ class classMatter extends EventEmitter {
305
391
  }
306
392
  const targetHost = (_options?.targetHost || '').toString().trim()
307
393
  if (targetHost !== '') {
394
+ this._reportCommissioningProgress(onProgress, {
395
+ phase: 'discovery',
396
+ percent: 10,
397
+ message: `Looking for the Matter device at ${targetHost}…`
398
+ })
308
399
  const commissionableDevice = await this._discoverCommissionableDeviceAtHost(identifierData, discoveryCapabilities, targetHost)
309
400
  discovery = commissionableDevice !== undefined
310
401
  ? {
@@ -319,6 +410,11 @@ class classMatter extends EventEmitter {
319
410
  let lastError
320
411
  for (const discoveryAttempt of discoveryAttempts) {
321
412
  try {
413
+ this._reportCommissioningProgress(onProgress, {
414
+ phase: 'pase',
415
+ percent: 15,
416
+ message: 'Discovering the device and establishing a secure PASE session…'
417
+ })
322
418
  nodeId = await this.controller.commissionNode({
323
419
  commissioning: {
324
420
  nodeId: commissioningNodeId,
@@ -326,7 +422,10 @@ class classMatter extends EventEmitter {
326
422
  },
327
423
  discovery: discoveryAttempt,
328
424
  passcode
329
- }, { connectNodeAfterCommissioning: false })
425
+ }, {
426
+ connectNodeAfterCommissioning: false,
427
+ commissioningFlowImpl: this._createProgressCommissioningFlow(onProgress)
428
+ })
330
429
  lastError = undefined
331
430
  break
332
431
  } catch (error) {
@@ -355,12 +454,22 @@ class classMatter extends EventEmitter {
355
454
  throw error
356
455
  }
357
456
  try {
457
+ this._reportCommissioningProgress(onProgress, {
458
+ phase: 'attaching',
459
+ percent: 98,
460
+ message: 'Loading the commissioned device structure…'
461
+ })
358
462
  await this._attachNode(nodeId)
359
463
  } catch (error) {
360
464
  // Pairing succeeded: never report it as failed just because the first connection
361
465
  // attempt errored. The node will be attached again at the next controller start.
362
466
  this._log('warn', `classMatter: commission: node ${nodeId} paired but not yet attached: ${error.message}`)
363
467
  }
468
+ this._reportCommissioningProgress(onProgress, {
469
+ phase: 'complete',
470
+ percent: 100,
471
+ message: 'Matter commissioning completed successfully.'
472
+ })
364
473
  return nodeId.toString()
365
474
  }
366
475
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=20.18.1"
5
5
  },
6
- "version": "6.2.3-beta.2",
6
+ "version": "6.2.3-beta.4",
7
7
  "description": "KNX Ultimate is the most advanced KNX integration for Node-RED, providing secure KNX/IP communication, routing, ETS project import, Philips Hue, Matter Controller and Matter Bridge (control matter device via KNX and expose KNX GA via Matter), MQTT, diagnostics with AI, virtual devices, and powerful automation nodes. Build professional, reliable, and scalable smart home and building automation projects with minimal effort.",
8
8
  "files": [
9
9
  "nodes/",