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.
- package/CHANGELOG.md +20 -10
- package/MIGRATION.md +40 -0
- package/README.md +32 -6
- package/docs/README.md +34 -0
- package/docs/assets/brand/made-in-italy.svg +12 -0
- package/examples/Alerter - Device Alert Aggregation.json +5 -2
- package/examples/Auto Responder - Read Request Defaults.json +5 -2
- package/examples/Garage - Door Controller.json +5 -2
- package/examples/Global Context - Expose KNX Values.json +5 -2
- package/examples/KNX DateTime - Set Date and Time to Bus.json +111 -89
- package/examples/Load Control - Manual Shedding Mode.json +5 -2
- package/examples/Logger ETS File and Telegram Count.json +10 -2
- package/examples/Scene Controller Recall and Save.json +33 -6
- package/examples/Staircase - Timer, Override and Block.json +5 -2
- package/examples/WatchDog Connect Disconnect.json +98 -70
- package/nodes/commonFunctions.js +0 -587
- package/nodes/knxUltimate-config.html +0 -14
- package/nodes/knxUltimate-config.js +1 -18
- package/nodes/knxUltimate.html +1 -6
- package/nodes/knxUltimateIoTBridge.html +0 -14
- package/nodes/knxUltimateLegacyMigration.js +75 -0
- package/nodes/knxUltimateMultiRouting.html +1 -7
- package/nodes/knxUltimateRouterFilter.html +1 -7
- package/nodes/knxUltimateUtility.html +0 -6
- package/nodes/knxUltimateViewer.html +0 -6
- package/nodes/locales/de/knxUltimate-config.html +0 -8
- package/nodes/locales/de/knxUltimate.html +0 -8
- package/nodes/locales/de/knxUltimateIoTBridge.html +0 -8
- package/nodes/locales/de/knxUltimateMultiRouting.html +0 -8
- package/nodes/locales/de/knxUltimateRouterFilter.html +0 -8
- package/nodes/locales/de/knxUltimateUtility.html +0 -8
- package/nodes/locales/de/knxUltimateUtility.json +16 -1
- package/nodes/locales/de/knxUltimateViewer.html +0 -8
- package/nodes/locales/en/knxUltimate-config.html +0 -8
- package/nodes/locales/en/knxUltimate.html +0 -8
- package/nodes/locales/en/knxUltimateIoTBridge.html +0 -8
- package/nodes/locales/en/knxUltimateMultiRouting.html +0 -8
- package/nodes/locales/en/knxUltimateRouterFilter.html +0 -8
- package/nodes/locales/en/knxUltimateUtility.html +0 -8
- package/nodes/locales/en/knxUltimateUtility.json +16 -1
- package/nodes/locales/en/knxUltimateViewer.html +0 -8
- package/nodes/locales/es/knxUltimate-config.html +0 -8
- package/nodes/locales/es/knxUltimate.html +0 -8
- package/nodes/locales/es/knxUltimateIoTBridge.html +0 -8
- package/nodes/locales/es/knxUltimateMultiRouting.html +0 -8
- package/nodes/locales/es/knxUltimateRouterFilter.html +0 -8
- package/nodes/locales/es/knxUltimateUtility.html +0 -8
- package/nodes/locales/es/knxUltimateUtility.json +17 -2
- package/nodes/locales/es/knxUltimateViewer.html +0 -8
- package/nodes/locales/fr/knxUltimate-config.html +0 -8
- package/nodes/locales/fr/knxUltimate.html +0 -8
- package/nodes/locales/fr/knxUltimateIoTBridge.html +0 -8
- package/nodes/locales/fr/knxUltimateMultiRouting.html +0 -8
- package/nodes/locales/fr/knxUltimateRouterFilter.html +0 -8
- package/nodes/locales/fr/knxUltimateUtility.html +0 -8
- package/nodes/locales/fr/knxUltimateUtility.json +16 -1
- package/nodes/locales/fr/knxUltimateViewer.html +0 -8
- package/nodes/locales/it/knxUltimate-config.html +0 -8
- package/nodes/locales/it/knxUltimate.html +0 -8
- package/nodes/locales/it/knxUltimateIoTBridge.html +0 -8
- package/nodes/locales/it/knxUltimateMultiRouting.html +0 -8
- package/nodes/locales/it/knxUltimateRouterFilter.html +0 -8
- package/nodes/locales/it/knxUltimateUtility.html +0 -8
- package/nodes/locales/it/knxUltimateUtility.json +16 -1
- package/nodes/locales/it/knxUltimateViewer.html +0 -8
- package/nodes/locales/zh-CN/knxUltimate-config.html +0 -8
- package/nodes/locales/zh-CN/knxUltimate.html +0 -8
- package/nodes/locales/zh-CN/knxUltimateIoTBridge.html +0 -8
- package/nodes/locales/zh-CN/knxUltimateMultiRouting.html +0 -8
- package/nodes/locales/zh-CN/knxUltimateRouterFilter.html +0 -8
- package/nodes/locales/zh-CN/knxUltimateUtility.html +0 -8
- package/nodes/locales/zh-CN/knxUltimateUtility.json +16 -1
- package/nodes/locales/zh-CN/knxUltimateViewer.html +0 -8
- package/nodes/plugins/knxUltimate-v8-migration-recovery-plugin.html +140 -0
- package/package.json +22 -69
- package/resources/11f26b4500.js +2 -2
- package/resources/KNXAIChatAdapterMappings.js +2 -416
- package/resources/configNodeEditorSelection.js +2 -32
- package/resources/hueControllerEditorSelection.js +2 -32
- package/resources/hueControllerMigrationDialog.js +2 -254
- package/resources/hueControllerProfiles.js +2 -9319
- package/resources/knxUtilityProfiles.js +2041 -2082
- package/resources/legacyMigrationNotice.js +23 -111
- package/resources/matterQrScanner.js +2 -98
- package/resources/upgradeToV8.js +94 -26
- package/scripts/check-legacy-flows.js +142 -0
- package/examples/Hue Area Motion - Zone Presence.json +0 -77
- package/examples/Hue Battery - Level Status.json +0 -77
- package/examples/Hue Button - Short and Dim Commands.json +0 -83
- package/examples/Hue Camera Motion - Presence.json +0 -77
- package/examples/Hue Config - Bridge Template.json +0 -27
- package/examples/Hue Contact Sensor - Open Closed.json +0 -75
- package/examples/Hue Device Software Update - Availability.json +0 -77
- package/examples/Hue Humidity Sensor - Relative Humidity.json +0 -77
- package/examples/Hue Light - Switch and Brightness.json +0 -67
- package/examples/Hue Light Sensor - Lux to KNX.json +0 -77
- package/examples/Hue Motion - Presence Sensor.json +0 -76
- package/examples/Hue Plug - Switch and State.json +0 -63
- package/examples/Hue Scene - Recall from KNX.json +0 -63
- package/examples/Hue Tap Dial - Relative Dimming.json +0 -76
- package/examples/Hue Temperature Sensor - Celsius to KNX.json +0 -77
- package/examples/Hue Zigbee Connectivity - Online Status.json +0 -77
- package/examples/Matter Controller - Semantic Flow Input.json +0 -248
- package/nodes/hue-config.html +0 -378
- package/nodes/hue-config.js +0 -846
- package/nodes/icons/node-alerter-icon.png +0 -0
- package/nodes/icons/node-homekit-icon.png +0 -0
- package/nodes/icons/node-homekit-icon.svg +0 -10
- package/nodes/icons/node-hue-icon.svg +0 -10
- package/nodes/icons/node-knx-icon.png +0 -0
- package/nodes/icons/node-logger-icon.png +0 -0
- package/nodes/icons/node-logger2-icon.png +0 -0
- package/nodes/icons/node-logger2-icon.svg +0 -32
- package/nodes/icons/node-matter-bridge-icon.svg +0 -6
- package/nodes/icons/node-matter-controller-icon.svg +0 -6
- package/nodes/icons/node-matter-icon.svg +0 -12
- package/nodes/icons/node-scene-icon.png +0 -0
- package/nodes/icons/node-watchdog-icon.png +0 -0
- package/nodes/knxUltimateAI.html +0 -1826
- package/nodes/knxUltimateAI.js +0 -16797
- package/nodes/knxUltimateAIHomeAssistant.html +0 -54
- package/nodes/knxUltimateAIHomeAssistant.js +0 -152
- package/nodes/knxUltimateAlerter.html +0 -267
- package/nodes/knxUltimateAlerter.js +0 -300
- package/nodes/knxUltimateAutoResponder.html +0 -128
- package/nodes/knxUltimateAutoResponder.js +0 -264
- package/nodes/knxUltimateDateTime.html +0 -579
- package/nodes/knxUltimateDateTime.js +0 -282
- package/nodes/knxUltimateGarage.html +0 -337
- package/nodes/knxUltimateGarage.js +0 -504
- package/nodes/knxUltimateGlobalContext.html +0 -145
- package/nodes/knxUltimateGlobalContext.js +0 -208
- package/nodes/knxUltimateHATranslator.html +0 -85
- package/nodes/knxUltimateHATranslator.js +0 -83
- package/nodes/knxUltimateHueAreaMotion.html +0 -670
- package/nodes/knxUltimateHueAreaMotion.js +0 -218
- package/nodes/knxUltimateHueBattery.html +0 -646
- package/nodes/knxUltimateHueBattery.js +0 -182
- package/nodes/knxUltimateHueButton.html +0 -804
- package/nodes/knxUltimateHueButton.js +0 -292
- package/nodes/knxUltimateHueCameraMotion.html +0 -671
- package/nodes/knxUltimateHueCameraMotion.js +0 -218
- package/nodes/knxUltimateHueContactSensor.html +0 -609
- package/nodes/knxUltimateHueContactSensor.js +0 -188
- package/nodes/knxUltimateHueController.html +0 -1765
- package/nodes/knxUltimateHueController.js +0 -28
- package/nodes/knxUltimateHueHumiditySensor.html +0 -641
- package/nodes/knxUltimateHueHumiditySensor.js +0 -201
- package/nodes/knxUltimateHueLight.html +0 -2516
- package/nodes/knxUltimateHueLight.js +0 -1582
- package/nodes/knxUltimateHueLightSensor.html +0 -636
- package/nodes/knxUltimateHueLightSensor.js +0 -177
- package/nodes/knxUltimateHueMotion.html +0 -634
- package/nodes/knxUltimateHueMotion.js +0 -182
- package/nodes/knxUltimateHuePlug.html +0 -576
- package/nodes/knxUltimateHuePlug.js +0 -341
- package/nodes/knxUltimateHueScene.html +0 -967
- package/nodes/knxUltimateHueScene.js +0 -349
- package/nodes/knxUltimateHueTapDial.html +0 -640
- package/nodes/knxUltimateHueTapDial.js +0 -268
- package/nodes/knxUltimateHueTemperatureSensor.html +0 -639
- package/nodes/knxUltimateHueTemperatureSensor.js +0 -184
- package/nodes/knxUltimateHueZigbeeConnectivity.html +0 -648
- package/nodes/knxUltimateHueZigbeeConnectivity.js +0 -182
- package/nodes/knxUltimateHuedevice_software_update.html +0 -650
- package/nodes/knxUltimateHuedevice_software_update.js +0 -182
- package/nodes/knxUltimateLoadControl.html +0 -425
- package/nodes/knxUltimateLoadControl.js +0 -443
- package/nodes/knxUltimateLogger.html +0 -197
- package/nodes/knxUltimateLogger.js +0 -224
- package/nodes/knxUltimateMatterBridge.html +0 -689
- package/nodes/knxUltimateMatterBridge.js +0 -464
- package/nodes/knxUltimateMatterControllerDevice.html +0 -2851
- package/nodes/knxUltimateMatterControllerDevice.js +0 -1747
- package/nodes/knxUltimateSceneController.html +0 -529
- package/nodes/knxUltimateSceneController.js +0 -437
- package/nodes/knxUltimateStaircase.html +0 -346
- package/nodes/knxUltimateStaircase.js +0 -463
- package/nodes/knxUltimateWatchDog.html +0 -241
- package/nodes/knxUltimateWatchDog.js +0 -230
- package/nodes/locales/de/hue-config.html +0 -33
- package/nodes/locales/de/hue-config.json +0 -21
- package/nodes/locales/de/knxUltimateAI.html +0 -13
- package/nodes/locales/de/knxUltimateAI.json +0 -231
- package/nodes/locales/de/knxUltimateAIHomeAssistant.html +0 -11
- package/nodes/locales/de/knxUltimateAlerter.html +0 -159
- package/nodes/locales/de/knxUltimateAlerter.json +0 -27
- package/nodes/locales/de/knxUltimateAutoResponder.html +0 -96
- package/nodes/locales/de/knxUltimateAutoResponder.json +0 -8
- package/nodes/locales/de/knxUltimateDateTime.html +0 -45
- package/nodes/locales/de/knxUltimateDateTime.json +0 -32
- package/nodes/locales/de/knxUltimateGarage.html +0 -79
- package/nodes/locales/de/knxUltimateGarage.json +0 -51
- package/nodes/locales/de/knxUltimateGlobalContext.html +0 -76
- package/nodes/locales/de/knxUltimateGlobalContext.json +0 -24
- package/nodes/locales/de/knxUltimateHATranslator.html +0 -25
- package/nodes/locales/de/knxUltimateHATranslator.json +0 -8
- package/nodes/locales/de/knxUltimateHueAreaMotion.html +0 -44
- package/nodes/locales/de/knxUltimateHueAreaMotion.json +0 -31
- package/nodes/locales/de/knxUltimateHueBattery.html +0 -37
- package/nodes/locales/de/knxUltimateHueBattery.json +0 -30
- package/nodes/locales/de/knxUltimateHueButton.html +0 -52
- package/nodes/locales/de/knxUltimateHueButton.json +0 -34
- package/nodes/locales/de/knxUltimateHueCameraMotion.html +0 -38
- package/nodes/locales/de/knxUltimateHueCameraMotion.json +0 -30
- package/nodes/locales/de/knxUltimateHueContactSensor.html +0 -35
- package/nodes/locales/de/knxUltimateHueContactSensor.json +0 -26
- package/nodes/locales/de/knxUltimateHueController.html +0 -690
- package/nodes/locales/de/knxUltimateHueController.json +0 -64
- package/nodes/locales/de/knxUltimateHueHumiditySensor.html +0 -38
- package/nodes/locales/de/knxUltimateHueHumiditySensor.json +0 -30
- package/nodes/locales/de/knxUltimateHueLight.html +0 -121
- package/nodes/locales/de/knxUltimateHueLight.json +0 -94
- package/nodes/locales/de/knxUltimateHueLightSensor.html +0 -38
- package/nodes/locales/de/knxUltimateHueLightSensor.json +0 -19
- package/nodes/locales/de/knxUltimateHueMotion.html +0 -41
- package/nodes/locales/de/knxUltimateHueMotion.json +0 -27
- package/nodes/locales/de/knxUltimateHuePlug.html +0 -50
- package/nodes/locales/de/knxUltimateHuePlug.json +0 -33
- package/nodes/locales/de/knxUltimateHueScene.html +0 -45
- package/nodes/locales/de/knxUltimateHueScene.json +0 -41
- package/nodes/locales/de/knxUltimateHueTapDial.html +0 -40
- package/nodes/locales/de/knxUltimateHueTapDial.json +0 -28
- package/nodes/locales/de/knxUltimateHueTemperatureSensor.html +0 -38
- package/nodes/locales/de/knxUltimateHueTemperatureSensor.json +0 -30
- package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.html +0 -39
- package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.json +0 -30
- package/nodes/locales/de/knxUltimateHuedevice_software_update.html +0 -36
- package/nodes/locales/de/knxUltimateHuedevice_software_update.json +0 -30
- package/nodes/locales/de/knxUltimateLoadControl.html +0 -69
- package/nodes/locales/de/knxUltimateLoadControl.json +0 -28
- package/nodes/locales/de/knxUltimateLogger.html +0 -126
- package/nodes/locales/de/knxUltimateLogger.json +0 -31
- package/nodes/locales/de/knxUltimateMatterBridge.html +0 -65
- package/nodes/locales/de/knxUltimateMatterBridge.json +0 -123
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +0 -57
- package/nodes/locales/de/knxUltimateMatterControllerDevice.json +0 -146
- package/nodes/locales/de/knxUltimateSceneController.html +0 -98
- package/nodes/locales/de/knxUltimateSceneController.json +0 -32
- package/nodes/locales/de/knxUltimateStaircase.html +0 -77
- package/nodes/locales/de/knxUltimateStaircase.json +0 -62
- package/nodes/locales/de/knxUltimateWatchDog.html +0 -178
- package/nodes/locales/de/knxUltimateWatchDog.json +0 -27
- package/nodes/locales/de/matter-config.html +0 -47
- package/nodes/locales/de/matter-config.json +0 -47
- package/nodes/locales/de/matterbridge-config.html +0 -45
- package/nodes/locales/de/matterbridge-config.json +0 -25
- package/nodes/locales/en/hue-config.html +0 -31
- package/nodes/locales/en/hue-config.json +0 -21
- package/nodes/locales/en/knxUltimateAI.html +0 -13
- package/nodes/locales/en/knxUltimateAI.json +0 -234
- package/nodes/locales/en/knxUltimateAIHomeAssistant.html +0 -11
- package/nodes/locales/en/knxUltimateAlerter.html +0 -164
- package/nodes/locales/en/knxUltimateAlerter.json +0 -27
- package/nodes/locales/en/knxUltimateAutoResponder.html +0 -94
- package/nodes/locales/en/knxUltimateAutoResponder.json +0 -8
- package/nodes/locales/en/knxUltimateDateTime.html +0 -67
- package/nodes/locales/en/knxUltimateDateTime.json +0 -32
- package/nodes/locales/en/knxUltimateGarage.html +0 -79
- package/nodes/locales/en/knxUltimateGarage.json +0 -51
- package/nodes/locales/en/knxUltimateGlobalContext.html +0 -124
- package/nodes/locales/en/knxUltimateGlobalContext.json +0 -24
- package/nodes/locales/en/knxUltimateHATranslator.html +0 -25
- package/nodes/locales/en/knxUltimateHATranslator.json +0 -8
- package/nodes/locales/en/knxUltimateHueAreaMotion.html +0 -44
- package/nodes/locales/en/knxUltimateHueAreaMotion.json +0 -31
- package/nodes/locales/en/knxUltimateHueBattery.html +0 -37
- package/nodes/locales/en/knxUltimateHueBattery.json +0 -30
- package/nodes/locales/en/knxUltimateHueButton.html +0 -52
- package/nodes/locales/en/knxUltimateHueButton.json +0 -34
- package/nodes/locales/en/knxUltimateHueCameraMotion.html +0 -38
- package/nodes/locales/en/knxUltimateHueCameraMotion.json +0 -30
- package/nodes/locales/en/knxUltimateHueContactSensor.html +0 -35
- package/nodes/locales/en/knxUltimateHueContactSensor.json +0 -26
- package/nodes/locales/en/knxUltimateHueController.html +0 -691
- package/nodes/locales/en/knxUltimateHueController.json +0 -64
- package/nodes/locales/en/knxUltimateHueHumiditySensor.html +0 -38
- package/nodes/locales/en/knxUltimateHueHumiditySensor.json +0 -30
- package/nodes/locales/en/knxUltimateHueLight.html +0 -124
- package/nodes/locales/en/knxUltimateHueLight.json +0 -94
- package/nodes/locales/en/knxUltimateHueLightSensor.html +0 -38
- package/nodes/locales/en/knxUltimateHueLightSensor.json +0 -19
- package/nodes/locales/en/knxUltimateHueMotion.html +0 -41
- package/nodes/locales/en/knxUltimateHueMotion.json +0 -27
- package/nodes/locales/en/knxUltimateHuePlug.html +0 -50
- package/nodes/locales/en/knxUltimateHuePlug.json +0 -33
- package/nodes/locales/en/knxUltimateHueScene.html +0 -45
- package/nodes/locales/en/knxUltimateHueScene.json +0 -41
- package/nodes/locales/en/knxUltimateHueTapDial.html +0 -40
- package/nodes/locales/en/knxUltimateHueTapDial.json +0 -28
- package/nodes/locales/en/knxUltimateHueTemperatureSensor.html +0 -38
- package/nodes/locales/en/knxUltimateHueTemperatureSensor.json +0 -30
- package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.html +0 -39
- package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.json +0 -30
- package/nodes/locales/en/knxUltimateHuedevice_software_update.html +0 -36
- package/nodes/locales/en/knxUltimateHuedevice_software_update.json +0 -30
- package/nodes/locales/en/knxUltimateLoadControl.html +0 -76
- package/nodes/locales/en/knxUltimateLoadControl.json +0 -28
- package/nodes/locales/en/knxUltimateLogger.html +0 -161
- package/nodes/locales/en/knxUltimateLogger.json +0 -31
- package/nodes/locales/en/knxUltimateMatterBridge.html +0 -65
- package/nodes/locales/en/knxUltimateMatterBridge.json +0 -123
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +0 -57
- package/nodes/locales/en/knxUltimateMatterControllerDevice.json +0 -146
- package/nodes/locales/en/knxUltimateSceneController.html +0 -117
- package/nodes/locales/en/knxUltimateSceneController.json +0 -32
- package/nodes/locales/en/knxUltimateStaircase.html +0 -77
- package/nodes/locales/en/knxUltimateStaircase.json +0 -62
- package/nodes/locales/en/knxUltimateWatchDog.html +0 -244
- package/nodes/locales/en/knxUltimateWatchDog.json +0 -27
- package/nodes/locales/en/matter-config.html +0 -47
- package/nodes/locales/en/matter-config.json +0 -48
- package/nodes/locales/en/matterbridge-config.html +0 -45
- package/nodes/locales/en/matterbridge-config.json +0 -25
- package/nodes/locales/es/hue-config.html +0 -31
- package/nodes/locales/es/hue-config.json +0 -21
- package/nodes/locales/es/knxUltimateAI.html +0 -13
- package/nodes/locales/es/knxUltimateAI.json +0 -231
- package/nodes/locales/es/knxUltimateAIHomeAssistant.html +0 -11
- package/nodes/locales/es/knxUltimateAlerter.html +0 -164
- package/nodes/locales/es/knxUltimateAlerter.json +0 -27
- package/nodes/locales/es/knxUltimateAutoResponder.html +0 -81
- package/nodes/locales/es/knxUltimateAutoResponder.json +0 -8
- package/nodes/locales/es/knxUltimateDateTime.html +0 -45
- package/nodes/locales/es/knxUltimateDateTime.json +0 -32
- package/nodes/locales/es/knxUltimateGarage.html +0 -79
- package/nodes/locales/es/knxUltimateGarage.json +0 -51
- package/nodes/locales/es/knxUltimateGlobalContext.html +0 -128
- package/nodes/locales/es/knxUltimateGlobalContext.json +0 -24
- package/nodes/locales/es/knxUltimateHATranslator.html +0 -25
- package/nodes/locales/es/knxUltimateHATranslator.json +0 -8
- package/nodes/locales/es/knxUltimateHueAreaMotion.html +0 -44
- package/nodes/locales/es/knxUltimateHueAreaMotion.json +0 -31
- package/nodes/locales/es/knxUltimateHueBattery.html +0 -37
- package/nodes/locales/es/knxUltimateHueBattery.json +0 -30
- package/nodes/locales/es/knxUltimateHueButton.html +0 -52
- package/nodes/locales/es/knxUltimateHueButton.json +0 -34
- package/nodes/locales/es/knxUltimateHueCameraMotion.html +0 -38
- package/nodes/locales/es/knxUltimateHueCameraMotion.json +0 -30
- package/nodes/locales/es/knxUltimateHueContactSensor.html +0 -35
- package/nodes/locales/es/knxUltimateHueContactSensor.json +0 -26
- package/nodes/locales/es/knxUltimateHueController.html +0 -691
- package/nodes/locales/es/knxUltimateHueController.json +0 -64
- package/nodes/locales/es/knxUltimateHueHumiditySensor.html +0 -38
- package/nodes/locales/es/knxUltimateHueHumiditySensor.json +0 -30
- package/nodes/locales/es/knxUltimateHueLight.html +0 -124
- package/nodes/locales/es/knxUltimateHueLight.json +0 -94
- package/nodes/locales/es/knxUltimateHueLightSensor.html +0 -38
- package/nodes/locales/es/knxUltimateHueLightSensor.json +0 -19
- package/nodes/locales/es/knxUltimateHueMotion.html +0 -41
- package/nodes/locales/es/knxUltimateHueMotion.json +0 -27
- package/nodes/locales/es/knxUltimateHuePlug.html +0 -50
- package/nodes/locales/es/knxUltimateHuePlug.json +0 -33
- package/nodes/locales/es/knxUltimateHueScene.html +0 -45
- package/nodes/locales/es/knxUltimateHueScene.json +0 -41
- package/nodes/locales/es/knxUltimateHueTapDial.html +0 -40
- package/nodes/locales/es/knxUltimateHueTapDial.json +0 -28
- package/nodes/locales/es/knxUltimateHueTemperatureSensor.html +0 -38
- package/nodes/locales/es/knxUltimateHueTemperatureSensor.json +0 -30
- package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.html +0 -39
- package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.json +0 -30
- package/nodes/locales/es/knxUltimateHuedevice_software_update.html +0 -36
- package/nodes/locales/es/knxUltimateHuedevice_software_update.json +0 -30
- package/nodes/locales/es/knxUltimateLoadControl.html +0 -76
- package/nodes/locales/es/knxUltimateLoadControl.json +0 -28
- package/nodes/locales/es/knxUltimateLogger.html +0 -162
- package/nodes/locales/es/knxUltimateLogger.json +0 -31
- package/nodes/locales/es/knxUltimateMatterBridge.html +0 -65
- package/nodes/locales/es/knxUltimateMatterBridge.json +0 -123
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +0 -57
- package/nodes/locales/es/knxUltimateMatterControllerDevice.json +0 -146
- package/nodes/locales/es/knxUltimateSceneController.html +0 -115
- package/nodes/locales/es/knxUltimateSceneController.json +0 -32
- package/nodes/locales/es/knxUltimateStaircase.html +0 -77
- package/nodes/locales/es/knxUltimateStaircase.json +0 -62
- package/nodes/locales/es/knxUltimateWatchDog.html +0 -230
- package/nodes/locales/es/knxUltimateWatchDog.json +0 -27
- package/nodes/locales/es/matter-config.html +0 -47
- package/nodes/locales/es/matter-config.json +0 -47
- package/nodes/locales/es/matterbridge-config.html +0 -45
- package/nodes/locales/es/matterbridge-config.json +0 -25
- package/nodes/locales/fr/hue-config.html +0 -31
- package/nodes/locales/fr/hue-config.json +0 -21
- package/nodes/locales/fr/knxUltimateAI.html +0 -13
- package/nodes/locales/fr/knxUltimateAI.json +0 -231
- package/nodes/locales/fr/knxUltimateAIHomeAssistant.html +0 -11
- package/nodes/locales/fr/knxUltimateAlerter.html +0 -164
- package/nodes/locales/fr/knxUltimateAlerter.json +0 -27
- package/nodes/locales/fr/knxUltimateAutoResponder.html +0 -92
- package/nodes/locales/fr/knxUltimateAutoResponder.json +0 -8
- package/nodes/locales/fr/knxUltimateDateTime.html +0 -45
- package/nodes/locales/fr/knxUltimateDateTime.json +0 -32
- package/nodes/locales/fr/knxUltimateGarage.html +0 -79
- package/nodes/locales/fr/knxUltimateGarage.json +0 -51
- package/nodes/locales/fr/knxUltimateGlobalContext.html +0 -122
- package/nodes/locales/fr/knxUltimateGlobalContext.json +0 -24
- package/nodes/locales/fr/knxUltimateHATranslator.html +0 -25
- package/nodes/locales/fr/knxUltimateHATranslator.json +0 -8
- package/nodes/locales/fr/knxUltimateHueAreaMotion.html +0 -44
- package/nodes/locales/fr/knxUltimateHueAreaMotion.json +0 -31
- package/nodes/locales/fr/knxUltimateHueBattery.html +0 -37
- package/nodes/locales/fr/knxUltimateHueBattery.json +0 -30
- package/nodes/locales/fr/knxUltimateHueButton.html +0 -52
- package/nodes/locales/fr/knxUltimateHueButton.json +0 -34
- package/nodes/locales/fr/knxUltimateHueCameraMotion.html +0 -38
- package/nodes/locales/fr/knxUltimateHueCameraMotion.json +0 -30
- package/nodes/locales/fr/knxUltimateHueContactSensor.html +0 -35
- package/nodes/locales/fr/knxUltimateHueContactSensor.json +0 -26
- package/nodes/locales/fr/knxUltimateHueController.html +0 -691
- package/nodes/locales/fr/knxUltimateHueController.json +0 -64
- package/nodes/locales/fr/knxUltimateHueHumiditySensor.html +0 -38
- package/nodes/locales/fr/knxUltimateHueHumiditySensor.json +0 -30
- package/nodes/locales/fr/knxUltimateHueLight.html +0 -124
- package/nodes/locales/fr/knxUltimateHueLight.json +0 -86
- package/nodes/locales/fr/knxUltimateHueLightSensor.html +0 -38
- package/nodes/locales/fr/knxUltimateHueLightSensor.json +0 -19
- package/nodes/locales/fr/knxUltimateHueMotion.html +0 -41
- package/nodes/locales/fr/knxUltimateHueMotion.json +0 -27
- package/nodes/locales/fr/knxUltimateHuePlug.html +0 -50
- package/nodes/locales/fr/knxUltimateHuePlug.json +0 -33
- package/nodes/locales/fr/knxUltimateHueScene.html +0 -45
- package/nodes/locales/fr/knxUltimateHueScene.json +0 -41
- package/nodes/locales/fr/knxUltimateHueTapDial.html +0 -40
- package/nodes/locales/fr/knxUltimateHueTapDial.json +0 -28
- package/nodes/locales/fr/knxUltimateHueTemperatureSensor.html +0 -38
- package/nodes/locales/fr/knxUltimateHueTemperatureSensor.json +0 -30
- package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.html +0 -39
- package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.json +0 -30
- package/nodes/locales/fr/knxUltimateHuedevice_software_update.html +0 -36
- package/nodes/locales/fr/knxUltimateHuedevice_software_update.json +0 -30
- package/nodes/locales/fr/knxUltimateLoadControl.html +0 -76
- package/nodes/locales/fr/knxUltimateLoadControl.json +0 -28
- package/nodes/locales/fr/knxUltimateLogger.html +0 -160
- package/nodes/locales/fr/knxUltimateLogger.json +0 -31
- package/nodes/locales/fr/knxUltimateMatterBridge.html +0 -65
- package/nodes/locales/fr/knxUltimateMatterBridge.json +0 -123
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +0 -57
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +0 -146
- package/nodes/locales/fr/knxUltimateSceneController.html +0 -117
- package/nodes/locales/fr/knxUltimateSceneController.json +0 -32
- package/nodes/locales/fr/knxUltimateStaircase.html +0 -77
- package/nodes/locales/fr/knxUltimateStaircase.json +0 -62
- package/nodes/locales/fr/knxUltimateWatchDog.html +0 -228
- package/nodes/locales/fr/knxUltimateWatchDog.json +0 -27
- package/nodes/locales/fr/matter-config.html +0 -47
- package/nodes/locales/fr/matter-config.json +0 -47
- package/nodes/locales/fr/matterbridge-config.html +0 -45
- package/nodes/locales/fr/matterbridge-config.json +0 -25
- package/nodes/locales/it/hue-config.html +0 -33
- package/nodes/locales/it/hue-config.json +0 -21
- package/nodes/locales/it/knxUltimateAI.html +0 -13
- package/nodes/locales/it/knxUltimateAI.json +0 -234
- package/nodes/locales/it/knxUltimateAIHomeAssistant.html +0 -11
- package/nodes/locales/it/knxUltimateAlerter.html +0 -159
- package/nodes/locales/it/knxUltimateAlerter.json +0 -27
- package/nodes/locales/it/knxUltimateAutoResponder.html +0 -96
- package/nodes/locales/it/knxUltimateAutoResponder.json +0 -8
- package/nodes/locales/it/knxUltimateDateTime.html +0 -67
- package/nodes/locales/it/knxUltimateDateTime.json +0 -32
- package/nodes/locales/it/knxUltimateGarage.html +0 -79
- package/nodes/locales/it/knxUltimateGarage.json +0 -51
- package/nodes/locales/it/knxUltimateGlobalContext.html +0 -81
- package/nodes/locales/it/knxUltimateGlobalContext.json +0 -24
- package/nodes/locales/it/knxUltimateHATranslator.html +0 -24
- package/nodes/locales/it/knxUltimateHATranslator.json +0 -8
- package/nodes/locales/it/knxUltimateHueAreaMotion.html +0 -44
- package/nodes/locales/it/knxUltimateHueAreaMotion.json +0 -31
- package/nodes/locales/it/knxUltimateHueBattery.html +0 -37
- package/nodes/locales/it/knxUltimateHueBattery.json +0 -30
- package/nodes/locales/it/knxUltimateHueButton.html +0 -52
- package/nodes/locales/it/knxUltimateHueButton.json +0 -34
- package/nodes/locales/it/knxUltimateHueCameraMotion.html +0 -38
- package/nodes/locales/it/knxUltimateHueCameraMotion.json +0 -30
- package/nodes/locales/it/knxUltimateHueContactSensor.html +0 -35
- package/nodes/locales/it/knxUltimateHueContactSensor.json +0 -26
- package/nodes/locales/it/knxUltimateHueController.html +0 -690
- package/nodes/locales/it/knxUltimateHueController.json +0 -64
- package/nodes/locales/it/knxUltimateHueHumiditySensor 2.html +0 -11
- package/nodes/locales/it/knxUltimateHueHumiditySensor.html +0 -38
- package/nodes/locales/it/knxUltimateHueHumiditySensor.json +0 -30
- package/nodes/locales/it/knxUltimateHueLight.html +0 -124
- package/nodes/locales/it/knxUltimateHueLight.json +0 -94
- package/nodes/locales/it/knxUltimateHueLightSensor.html +0 -38
- package/nodes/locales/it/knxUltimateHueLightSensor.json +0 -19
- package/nodes/locales/it/knxUltimateHueMotion.html +0 -41
- package/nodes/locales/it/knxUltimateHueMotion.json +0 -27
- package/nodes/locales/it/knxUltimateHuePlug.html +0 -50
- package/nodes/locales/it/knxUltimateHuePlug.json +0 -33
- package/nodes/locales/it/knxUltimateHueScene.html +0 -45
- package/nodes/locales/it/knxUltimateHueScene.json +0 -41
- package/nodes/locales/it/knxUltimateHueTapDial.html +0 -40
- package/nodes/locales/it/knxUltimateHueTapDial.json +0 -28
- package/nodes/locales/it/knxUltimateHueTemperatureSensor.html +0 -38
- package/nodes/locales/it/knxUltimateHueTemperatureSensor.json +0 -30
- package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.html +0 -39
- package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.json +0 -30
- package/nodes/locales/it/knxUltimateHuedevice_software_update.html +0 -35
- package/nodes/locales/it/knxUltimateHuedevice_software_update.json +0 -30
- package/nodes/locales/it/knxUltimateLoadControl.html +0 -76
- package/nodes/locales/it/knxUltimateLoadControl.json +0 -28
- package/nodes/locales/it/knxUltimateLogger.html +0 -132
- package/nodes/locales/it/knxUltimateLogger.json +0 -31
- package/nodes/locales/it/knxUltimateMatterBridge.html +0 -65
- package/nodes/locales/it/knxUltimateMatterBridge.json +0 -123
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +0 -57
- package/nodes/locales/it/knxUltimateMatterControllerDevice.json +0 -146
- package/nodes/locales/it/knxUltimateSceneController.html +0 -101
- package/nodes/locales/it/knxUltimateSceneController.json +0 -32
- package/nodes/locales/it/knxUltimateStaircase.html +0 -77
- package/nodes/locales/it/knxUltimateStaircase.json +0 -62
- package/nodes/locales/it/knxUltimateWatchDog.html +0 -194
- package/nodes/locales/it/knxUltimateWatchDog.json +0 -27
- package/nodes/locales/it/matter-config.html +0 -47
- package/nodes/locales/it/matter-config.json +0 -48
- package/nodes/locales/it/matterbridge-config.html +0 -45
- package/nodes/locales/it/matterbridge-config.json +0 -25
- package/nodes/locales/zh-CN/hue-config.html +0 -33
- package/nodes/locales/zh-CN/hue-config.json +0 -21
- package/nodes/locales/zh-CN/knxUltimateAI.html +0 -13
- package/nodes/locales/zh-CN/knxUltimateAI.json +0 -231
- package/nodes/locales/zh-CN/knxUltimateAIHomeAssistant.html +0 -11
- package/nodes/locales/zh-CN/knxUltimateAlerter.html +0 -159
- package/nodes/locales/zh-CN/knxUltimateAlerter.json +0 -27
- package/nodes/locales/zh-CN/knxUltimateAutoResponder.html +0 -94
- package/nodes/locales/zh-CN/knxUltimateAutoResponder.json +0 -8
- package/nodes/locales/zh-CN/knxUltimateDateTime.html +0 -45
- package/nodes/locales/zh-CN/knxUltimateDateTime.json +0 -32
- package/nodes/locales/zh-CN/knxUltimateGarage.html +0 -79
- package/nodes/locales/zh-CN/knxUltimateGarage.json +0 -51
- package/nodes/locales/zh-CN/knxUltimateGlobalContext.html +0 -77
- package/nodes/locales/zh-CN/knxUltimateGlobalContext.json +0 -24
- package/nodes/locales/zh-CN/knxUltimateHATranslator.html +0 -25
- package/nodes/locales/zh-CN/knxUltimateHATranslator.json +0 -8
- package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.html +0 -44
- package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.json +0 -31
- package/nodes/locales/zh-CN/knxUltimateHueBattery.html +0 -37
- package/nodes/locales/zh-CN/knxUltimateHueBattery.json +0 -30
- package/nodes/locales/zh-CN/knxUltimateHueButton.html +0 -52
- package/nodes/locales/zh-CN/knxUltimateHueButton.json +0 -34
- package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.html +0 -38
- package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.json +0 -30
- package/nodes/locales/zh-CN/knxUltimateHueContactSensor.html +0 -35
- package/nodes/locales/zh-CN/knxUltimateHueContactSensor.json +0 -26
- package/nodes/locales/zh-CN/knxUltimateHueController.html +0 -693
- package/nodes/locales/zh-CN/knxUltimateHueController.json +0 -64
- package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.html +0 -38
- package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.json +0 -30
- package/nodes/locales/zh-CN/knxUltimateHueLight.html +0 -126
- package/nodes/locales/zh-CN/knxUltimateHueLight.json +0 -86
- package/nodes/locales/zh-CN/knxUltimateHueLightSensor.html +0 -38
- package/nodes/locales/zh-CN/knxUltimateHueLightSensor.json +0 -9
- package/nodes/locales/zh-CN/knxUltimateHueMotion.html +0 -41
- package/nodes/locales/zh-CN/knxUltimateHueMotion.json +0 -27
- package/nodes/locales/zh-CN/knxUltimateHuePlug.html +0 -50
- package/nodes/locales/zh-CN/knxUltimateHuePlug.json +0 -33
- package/nodes/locales/zh-CN/knxUltimateHueScene.html +0 -45
- package/nodes/locales/zh-CN/knxUltimateHueScene.json +0 -41
- package/nodes/locales/zh-CN/knxUltimateHueTapDial.html +0 -40
- package/nodes/locales/zh-CN/knxUltimateHueTapDial.json +0 -28
- package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.html +0 -38
- package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.json +0 -30
- package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.html +0 -39
- package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.json +0 -30
- package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.html +0 -36
- package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.json +0 -30
- package/nodes/locales/zh-CN/knxUltimateLoadControl.html +0 -76
- package/nodes/locales/zh-CN/knxUltimateLoadControl.json +0 -28
- package/nodes/locales/zh-CN/knxUltimateLogger.html +0 -126
- package/nodes/locales/zh-CN/knxUltimateLogger.json +0 -31
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +0 -65
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +0 -123
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +0 -57
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +0 -146
- package/nodes/locales/zh-CN/knxUltimateSceneController.html +0 -98
- package/nodes/locales/zh-CN/knxUltimateSceneController.json +0 -32
- package/nodes/locales/zh-CN/knxUltimateStaircase.html +0 -77
- package/nodes/locales/zh-CN/knxUltimateStaircase.json +0 -62
- package/nodes/locales/zh-CN/knxUltimateWatchDog.html +0 -178
- package/nodes/locales/zh-CN/knxUltimateWatchDog.json +0 -27
- package/nodes/locales/zh-CN/matter-config.html +0 -47
- package/nodes/locales/zh-CN/matter-config.json +0 -47
- package/nodes/locales/zh-CN/matterbridge-config.html +0 -45
- package/nodes/locales/zh-CN/matterbridge-config.json +0 -25
- package/nodes/matter-config.html +0 -578
- package/nodes/matter-config.js +0 -387
- package/nodes/matterbridge-config.html +0 -198
- package/nodes/matterbridge-config.js +0 -261
- package/nodes/plugins/knxUltimate-cerebrum-runtime-plugin.js +0 -79
- package/nodes/plugins/knxUltimate-migration-notice-plugin.html +0 -44
- package/nodes/plugins/knxUltimateAI-vue/assets/app.css +0 -1
- package/nodes/plugins/knxUltimateAI-vue/assets/app.js +0 -13
- package/nodes/plugins/knxUltimateAI-vue/assets/chunk-html2canvas.esm.js +0 -5
- package/nodes/plugins/knxUltimateAI-vue/assets/chunk-index.es.js +0 -5
- package/nodes/plugins/knxUltimateAI-vue/assets/chunk-jspdf.es.min.js +0 -79
- package/nodes/plugins/knxUltimateAI-vue/assets/chunk-purify.es.js +0 -3
- package/nodes/plugins/knxUltimateAI-vue/index.html +0 -13
- package/nodes/utils/HueEventStream.mjs +0 -176
- package/nodes/utils/colorManipulators/XYFromRGB_Supergiovane.js +0 -113
- package/nodes/utils/colorManipulators/hueColorConverter.js +0 -521
- package/nodes/utils/http.js +0 -291
- package/nodes/utils/hueControllerProfileAdapter.js +0 -104
- package/nodes/utils/hueControllerProfiles/runtime/area_motion.js +0 -221
- package/nodes/utils/hueControllerProfiles/runtime/button.js +0 -295
- package/nodes/utils/hueControllerProfiles/runtime/camera_motion.js +0 -221
- package/nodes/utils/hueControllerProfiles/runtime/contact.js +0 -191
- package/nodes/utils/hueControllerProfiles/runtime/device_power.js +0 -185
- package/nodes/utils/hueControllerProfiles/runtime/device_software_update.js +0 -185
- package/nodes/utils/hueControllerProfiles/runtime/humidity.js +0 -204
- package/nodes/utils/hueControllerProfiles/runtime/light.js +0 -1585
- package/nodes/utils/hueControllerProfiles/runtime/light_level.js +0 -180
- package/nodes/utils/hueControllerProfiles/runtime/motion.js +0 -185
- package/nodes/utils/hueControllerProfiles/runtime/plug.js +0 -344
- package/nodes/utils/hueControllerProfiles/runtime/relative_rotary.js +0 -271
- package/nodes/utils/hueControllerProfiles/runtime/scene.js +0 -352
- package/nodes/utils/hueControllerProfiles/runtime/temperature.js +0 -187
- package/nodes/utils/hueControllerProfiles/runtime/zigbee_connectivity.js +0 -185
- package/nodes/utils/hueControllerResourceCatalog.js +0 -71
- package/nodes/utils/hueEngine.mjs +0 -469
- package/nodes/utils/knxAiCamera.js +0 -302
- package/nodes/utils/knxAiCatalogRetrieval.js +0 -349
- package/nodes/utils/knxAiCerebrum.js +0 -406
- package/nodes/utils/knxAiChatContext.js +0 -496
- package/nodes/utils/knxAiEventHistory.js +0 -521
- package/nodes/utils/knxAiHomeMemory.js +0 -1021
- package/nodes/utils/knxAiScheduler.js +0 -445
- package/nodes/utils/knxAiSemanticContext.js +0 -662
- package/nodes/utils/knxAiTelegramVoice.js +0 -545
- package/nodes/utils/knxAiWebAccess.js +0 -631
- package/nodes/utils/lightEngines/canonicalLight.js +0 -75
- package/nodes/utils/lightEngines/hueLightEngine.js +0 -51
- package/nodes/utils/lightEngines/index.js +0 -25
- package/nodes/utils/lightEngines/knxLightCommand.js +0 -79
- package/nodes/utils/lightEngines/lightEngine.js +0 -46
- package/nodes/utils/lightEngines/matterHueShim.js +0 -59
- package/nodes/utils/lightEngines/matterLightEngine.js +0 -117
- package/nodes/utils/matterBridgeDeviceFactory.mjs +0 -780
- package/nodes/utils/matterBridgeEngine.mjs +0 -297
- package/nodes/utils/matterControllerCommandGate.js +0 -47
- package/nodes/utils/matterControllerProfiles/doorLock.js +0 -258
- package/nodes/utils/matterControllerProfiles/fan.js +0 -19
- package/nodes/utils/matterControllerProfiles/index.js +0 -34
- package/nodes/utils/matterControllerProfiles/mappedEndpoint.js +0 -217
- package/nodes/utils/matterControllerProfiles/switch.js +0 -13
- package/nodes/utils/matterControllerProfiles/thermostat.js +0 -19
- package/nodes/utils/matterControllerProfiles/universal.js +0 -462
- package/nodes/utils/matterControllerProfiles/windowCovering.js +0 -19
- package/nodes/utils/matterControllerSemanticInput.js +0 -212
- package/nodes/utils/matterEngine.mjs +0 -894
- package/nodes/utils/matterEnvironmentLock.mjs +0 -32
- package/nodes/utils/matterKnxConverter.js +0 -204
- package/nodes/utils/matterStorageBackup.js +0 -100
- package/resources/HA-icon.png +0 -0
- package/resources/dim.png +0 -0
- package/resources/rgb.png +0 -0
- package/resources/tunablewhite.png +0 -0
- package/resources/zxing-browser-0.2.0.min.js +0 -1
- package/resources/zxing-browser-LICENSE.txt +0 -21
|
@@ -1,894 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
|
-
import { EventEmitter } from 'events'
|
|
3
|
-
import dns from 'dns/promises'
|
|
4
|
-
import { setTimeout as pleaseWait } from 'timers/promises'
|
|
5
|
-
|
|
6
|
-
// Last 4 hex chars of an id-ish string (serial, uniqueId, mDNS name...), uppercased.
|
|
7
|
-
// Used to disambiguate identical device names (Shelly encode the MAC in the serial).
|
|
8
|
-
const shortHexSuffix = (value) => {
|
|
9
|
-
if (value === undefined || value === null) return ''
|
|
10
|
-
const hex = String(value).replace(/[^0-9a-fA-F]/g, '')
|
|
11
|
-
if (hex.length >= 4) return hex.slice(-4).toUpperCase()
|
|
12
|
-
const alnum = String(value).replace(/[^0-9a-zA-Z]/g, '')
|
|
13
|
-
return alnum.length >= 4 ? alnum.slice(-4).toUpperCase() : alnum.toUpperCase()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const normalizeHostForCompare = (value) => {
|
|
17
|
-
if (value === undefined || value === null) return ''
|
|
18
|
-
return String(value).trim().toLowerCase().replace(/^\[/, '').replace(/\]$/, '').split('%')[0]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Wrapper around the matter.js CommissioningController.
|
|
22
|
-
// It mirrors the contract of hueEngine.mjs: an EventEmitter with a command queue,
|
|
23
|
-
// emitting 'connected', 'disconnected', 'event' (attribute changes), 'matterEvent' (cluster events),
|
|
24
|
-
// 'nodeState' and 'structureChanged'.
|
|
25
|
-
class classMatter extends EventEmitter {
|
|
26
|
-
constructor (_storagePath, _instanceId, _fabricLabel, _sysLogger, {
|
|
27
|
-
startQueue = true,
|
|
28
|
-
commandTimeoutMs = 15000,
|
|
29
|
-
queuePollMs = 150,
|
|
30
|
-
queueMaxConcurrent = 32
|
|
31
|
-
} = {}) {
|
|
32
|
-
super()
|
|
33
|
-
this.matterConnectionStatus = 'disconnected'
|
|
34
|
-
this.storagePath = _storagePath
|
|
35
|
-
this.instanceId = _instanceId
|
|
36
|
-
this.fabricLabel = _fabricLabel || 'KNX-Ultimate'
|
|
37
|
-
this.sysLogger = _sysLogger
|
|
38
|
-
this.controller = null
|
|
39
|
-
this.pairedNodes = new Map() // nodeId (string) -> PairedNode
|
|
40
|
-
this.commandQueue = []
|
|
41
|
-
// Commands remain strictly ordered per Matter node, but different nodes use
|
|
42
|
-
// independent lanes. An unreachable device must never head-of-line block every
|
|
43
|
-
// other endpoint sharing this controller/fabric.
|
|
44
|
-
this.commandQueueInFlight = new Map()
|
|
45
|
-
this.queueMaxLength = 2000
|
|
46
|
-
this.queueMaxConcurrent = Math.max(1, Number(queueMaxConcurrent) || 32)
|
|
47
|
-
this.commandTimeoutMs = Math.max(10, Number(commandTimeoutMs) || 15000)
|
|
48
|
-
this.queuePollMs = Math.max(10, Number(queuePollMs) || 150)
|
|
49
|
-
this.exitAllQueues = false
|
|
50
|
-
this._api = null // Lazy loaded matter.js exports
|
|
51
|
-
this._logThrottle = new Map()
|
|
52
|
-
if (startQueue) {
|
|
53
|
-
// The queue is intentionally fire-and-forget, but its Promise must always be
|
|
54
|
-
// observed so an unexpected loop failure cannot become an unhandled rejection.
|
|
55
|
-
this.handleQueue().catch((error) => this._log('error', `classMatter: queue loop: ${error.message}`))
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
_log = (level, message) => {
|
|
60
|
-
try {
|
|
61
|
-
const logger = this.sysLogger
|
|
62
|
-
if (logger && typeof logger[level] === 'function') return logger[level](message)
|
|
63
|
-
if (logger && typeof logger.log === 'function') return logger.log(message)
|
|
64
|
-
} catch (error) { /* empty */ }
|
|
65
|
-
if (level === 'error') console.error(message)
|
|
66
|
-
else if (level === 'warn') console.warn(message)
|
|
67
|
-
else console.log(message)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_logThrottled = (level, throttleKey, message, intervalMs = 30000) => {
|
|
71
|
-
const now = Date.now()
|
|
72
|
-
const entry = this._logThrottle.get(throttleKey) || { last: 0, suppressed: 0 }
|
|
73
|
-
if (this._logThrottle.size > 1000) this._logThrottle.clear()
|
|
74
|
-
if (entry.last === 0 || (now - entry.last) >= intervalMs) {
|
|
75
|
-
const suffix = entry.suppressed > 0 ? ` (suppressed ${entry.suppressed} similar)` : ''
|
|
76
|
-
entry.last = now
|
|
77
|
-
entry.suppressed = 0
|
|
78
|
-
this._logThrottle.set(throttleKey, entry)
|
|
79
|
-
this._log(level, `${message}${suffix}`)
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
entry.suppressed += 1
|
|
83
|
-
this._logThrottle.set(throttleKey, entry)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
_loadApi = async () => {
|
|
87
|
-
if (this._api !== null) return this._api
|
|
88
|
-
const { Environment, StorageService, Logger, LogLevel } = await import('@matter/main')
|
|
89
|
-
const { Seconds } = await import('@matter/general')
|
|
90
|
-
const { CommissioningController } = await import('@project-chip/matter.js')
|
|
91
|
-
const { ControllerCommissioningFlow } = await import('@matter/protocol')
|
|
92
|
-
const { NodeStates } = await import('@project-chip/matter.js/device')
|
|
93
|
-
const { ManualPairingCodeCodec, QrPairingCodeCodec, NodeId } = await import('@matter/main/types')
|
|
94
|
-
const { BasicInformation, GeneralCommissioning } = await import('@matter/main/clusters')
|
|
95
|
-
this._api = {
|
|
96
|
-
Environment, StorageService, Logger, LogLevel, Seconds, CommissioningController, ControllerCommissioningFlow, NodeStates, ManualPairingCodeCodec, QrPairingCodeCodec, NodeId, BasicInformation, GeneralCommissioning
|
|
97
|
-
}
|
|
98
|
-
return this._api
|
|
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
|
-
|
|
180
|
-
_resolveTargetHosts = async (_targetHost) => {
|
|
181
|
-
const host = normalizeHostForCompare(_targetHost)
|
|
182
|
-
if (host === '') return []
|
|
183
|
-
const ret = new Set([host])
|
|
184
|
-
try {
|
|
185
|
-
const entries = await dns.lookup(host, { all: true })
|
|
186
|
-
entries.forEach((entry) => {
|
|
187
|
-
const address = normalizeHostForCompare(entry.address)
|
|
188
|
-
if (address !== '') ret.add(address)
|
|
189
|
-
})
|
|
190
|
-
} catch (error) { /* Host may already be an IP or only resolvable by Matter/mDNS. */ }
|
|
191
|
-
return Array.from(ret)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
_deviceHasTargetHost = (device, targetHosts) => {
|
|
195
|
-
if (!Array.isArray(targetHosts) || targetHosts.length === 0) return false
|
|
196
|
-
try {
|
|
197
|
-
return (device.addresses || []).some((address) => targetHosts.includes(normalizeHostForCompare(address.ip)))
|
|
198
|
-
} catch (error) {
|
|
199
|
-
return false
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
_discoverCommissionableDeviceAtHost = async (identifierData, discoveryCapabilities, targetHost) => {
|
|
204
|
-
const targetHosts = await this._resolveTargetHosts(targetHost)
|
|
205
|
-
if (targetHosts.length === 0) return undefined
|
|
206
|
-
const devices = await this.controller.discoverCommissionableDevices(
|
|
207
|
-
identifierData,
|
|
208
|
-
discoveryCapabilities,
|
|
209
|
-
undefined,
|
|
210
|
-
this._api.Seconds(20)
|
|
211
|
-
)
|
|
212
|
-
return devices.find((device) => this._deviceHasTargetHost(device, targetHosts))
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
_buildKnownAddressDiscoveryAttempts = (identifierData, discoveryCapabilities, targetHost) => {
|
|
216
|
-
const host = normalizeHostForCompare(targetHost)
|
|
217
|
-
if (host === '') return []
|
|
218
|
-
// Matter devices normally listen for commissioning on UDP/5540. Shelly's HTTP RPC
|
|
219
|
-
// gives us the device IP, not its mDNS service port, so try the standard PASE port
|
|
220
|
-
// before falling back to mDNS-filtered discovery.
|
|
221
|
-
return [{
|
|
222
|
-
identifierData,
|
|
223
|
-
discoveryCapabilities,
|
|
224
|
-
knownAddress: {
|
|
225
|
-
ip: host,
|
|
226
|
-
port: 5540,
|
|
227
|
-
type: 'udp'
|
|
228
|
-
}
|
|
229
|
-
}, {
|
|
230
|
-
identifierData,
|
|
231
|
-
discoveryCapabilities,
|
|
232
|
-
knownAddress: {
|
|
233
|
-
ip: host,
|
|
234
|
-
port: 5540
|
|
235
|
-
}
|
|
236
|
-
}]
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
_allocateCommissionNodeId = () => {
|
|
240
|
-
const commissioned = new Set()
|
|
241
|
-
try {
|
|
242
|
-
this.controller.getCommissionedNodes().forEach((nodeId) => commissioned.add(nodeId.toString()))
|
|
243
|
-
} catch (error) { /* empty */ }
|
|
244
|
-
try {
|
|
245
|
-
if (this.controller.nodeId !== undefined) commissioned.add(this.controller.nodeId.toString())
|
|
246
|
-
} catch (error) { /* empty */ }
|
|
247
|
-
for (let index = 0; index < 100; index++) {
|
|
248
|
-
const nodeId = this._api.NodeId.randomOperationalNodeId(this.controller.crypto)
|
|
249
|
-
if (!commissioned.has(nodeId.toString())) return nodeId
|
|
250
|
-
}
|
|
251
|
-
throw new Error('Unable to allocate a free Matter Node ID for commissioning')
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
nodeStateToString = (state) => {
|
|
255
|
-
try {
|
|
256
|
-
const { NodeStates } = this._api
|
|
257
|
-
switch (state) {
|
|
258
|
-
case NodeStates.Connected: return 'connected'
|
|
259
|
-
case NodeStates.Disconnected: return 'disconnected'
|
|
260
|
-
case NodeStates.Reconnecting: return 'reconnecting'
|
|
261
|
-
case NodeStates.WaitingForDeviceDiscovery: return 'waitingfordiscovery'
|
|
262
|
-
default: return `unknown (${state})`
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
return `unknown (${state})`
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
Connect = async () => {
|
|
270
|
-
const api = await this._loadApi()
|
|
271
|
-
try {
|
|
272
|
-
// Quiet down the very verbose default matter.js console logging.
|
|
273
|
-
api.Logger.level = api.LogLevel.ERROR
|
|
274
|
-
} catch (error) { /* empty */ }
|
|
275
|
-
const { withEnvironmentLock } = await import('./matterEnvironmentLock.mjs')
|
|
276
|
-
// Serialize with the Matter Bridge engine: both write Environment.default.vars
|
|
277
|
-
// ('storage.path') before creating their matter.js node, and that Environment is a
|
|
278
|
-
// process-wide singleton. See matterEnvironmentLock.mjs for why this matters.
|
|
279
|
-
await withEnvironmentLock(async () => {
|
|
280
|
-
const environment = api.Environment.default
|
|
281
|
-
environment.vars.set('storage.path', this.storagePath)
|
|
282
|
-
this.controller = new api.CommissioningController({
|
|
283
|
-
environment: { environment, id: this.instanceId },
|
|
284
|
-
autoConnect: false,
|
|
285
|
-
adminFabricLabel: this.fabricLabel
|
|
286
|
-
})
|
|
287
|
-
await this.controller.start()
|
|
288
|
-
})
|
|
289
|
-
this.matterConnectionStatus = 'connected'
|
|
290
|
-
this.emit('connected')
|
|
291
|
-
// Connect all already commissioned nodes and hook their events.
|
|
292
|
-
const nodeIds = this.controller.getCommissionedNodes()
|
|
293
|
-
for (let index = 0; index < nodeIds.length; index++) {
|
|
294
|
-
try {
|
|
295
|
-
await this._attachNode(nodeIds[index])
|
|
296
|
-
} catch (error) {
|
|
297
|
-
this._log('error', `classMatter: Connect: cannot attach node ${nodeIds[index]}: ${error.message}`)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
_attachNode = async (_nodeId) => {
|
|
303
|
-
const key = _nodeId.toString()
|
|
304
|
-
const node = await this.controller.getNode(_nodeId)
|
|
305
|
-
this.pairedNodes.set(key, node)
|
|
306
|
-
if (node.__knxUltimateHooked !== true) {
|
|
307
|
-
node.__knxUltimateHooked = true
|
|
308
|
-
node.events.attributeChanged.on((data) => {
|
|
309
|
-
try {
|
|
310
|
-
this.emit('event', {
|
|
311
|
-
nodeId: key,
|
|
312
|
-
endpointId: Number(data.path.endpointId),
|
|
313
|
-
clusterId: Number(data.path.clusterId),
|
|
314
|
-
attributeId: Number(data.path.attributeId),
|
|
315
|
-
attributeName: data.path.attributeName,
|
|
316
|
-
value: data.value
|
|
317
|
-
})
|
|
318
|
-
} catch (error) {
|
|
319
|
-
this._logThrottled('error', 'matter:attrchanged', `classMatter: attributeChanged handler: ${error.message}`)
|
|
320
|
-
}
|
|
321
|
-
})
|
|
322
|
-
node.events.eventTriggered.on((data) => {
|
|
323
|
-
try {
|
|
324
|
-
this.emit('matterEvent', {
|
|
325
|
-
nodeId: key,
|
|
326
|
-
endpointId: Number(data.path.endpointId),
|
|
327
|
-
clusterId: Number(data.path.clusterId),
|
|
328
|
-
eventId: Number(data.path.eventId),
|
|
329
|
-
eventName: data.path.eventName,
|
|
330
|
-
events: data.events
|
|
331
|
-
})
|
|
332
|
-
} catch (error) {
|
|
333
|
-
this._logThrottled('error', 'matter:eventtriggered', `classMatter: eventTriggered handler: ${error.message}`)
|
|
334
|
-
}
|
|
335
|
-
})
|
|
336
|
-
node.events.stateChanged.on((state) => {
|
|
337
|
-
try {
|
|
338
|
-
this.emit('nodeState', key, this.nodeStateToString(state))
|
|
339
|
-
} catch (error) { /* empty */ }
|
|
340
|
-
})
|
|
341
|
-
node.events.structureChanged.on(() => {
|
|
342
|
-
try {
|
|
343
|
-
this.emit('structureChanged', key)
|
|
344
|
-
} catch (error) { /* empty */ }
|
|
345
|
-
})
|
|
346
|
-
node.events.initializedFromRemote?.on(() => {
|
|
347
|
-
try {
|
|
348
|
-
this.emit('nodeInitialized', key)
|
|
349
|
-
} catch (error) { /* empty */ }
|
|
350
|
-
})
|
|
351
|
-
}
|
|
352
|
-
try {
|
|
353
|
-
node.connect() // Establishes connection and auto-subscribes to all attributes/events
|
|
354
|
-
} catch (error) {
|
|
355
|
-
this._log('warn', `classMatter: _attachNode connect ${key}: ${error.message}`)
|
|
356
|
-
}
|
|
357
|
-
return node
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Commission (pair) a new device using an 11-digit manual pairing code or a QR code string (MT:...)
|
|
361
|
-
commission = async (_pairingCode, _options = {}) => {
|
|
362
|
-
if (this.controller === null) throw new Error('Matter controller not started')
|
|
363
|
-
const api = await this._loadApi()
|
|
364
|
-
const code = (_pairingCode || '').toString().trim()
|
|
365
|
-
if (code === '') throw new Error('Empty pairing code')
|
|
366
|
-
let passcode
|
|
367
|
-
let identifierData
|
|
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
|
-
})
|
|
375
|
-
if (code.toUpperCase().startsWith('MT:')) {
|
|
376
|
-
const qr = api.QrPairingCodeCodec.decode(code)[0]
|
|
377
|
-
passcode = qr.passcode
|
|
378
|
-
identifierData = { longDiscriminator: qr.discriminator }
|
|
379
|
-
} else {
|
|
380
|
-
const manual = api.ManualPairingCodeCodec.decode(code.replace(/[^0-9]/g, ''))
|
|
381
|
-
passcode = manual.passcode
|
|
382
|
-
identifierData = { shortDiscriminator: manual.shortDiscriminator }
|
|
383
|
-
}
|
|
384
|
-
let nodeId
|
|
385
|
-
try {
|
|
386
|
-
const commissioningNodeId = this._allocateCommissionNodeId()
|
|
387
|
-
this._log('info', `classMatter: commission: using Node ID ${commissioningNodeId.toString()} for new device`)
|
|
388
|
-
let discovery = {
|
|
389
|
-
identifierData,
|
|
390
|
-
discoveryCapabilities
|
|
391
|
-
}
|
|
392
|
-
const targetHost = (_options?.targetHost || '').toString().trim()
|
|
393
|
-
if (targetHost !== '') {
|
|
394
|
-
this._reportCommissioningProgress(onProgress, {
|
|
395
|
-
phase: 'discovery',
|
|
396
|
-
percent: 10,
|
|
397
|
-
message: `Looking for the Matter device at ${targetHost}…`
|
|
398
|
-
})
|
|
399
|
-
const commissionableDevice = await this._discoverCommissionableDeviceAtHost(identifierData, discoveryCapabilities, targetHost)
|
|
400
|
-
discovery = commissionableDevice !== undefined
|
|
401
|
-
? {
|
|
402
|
-
commissionableDevice,
|
|
403
|
-
discoveryCapabilities
|
|
404
|
-
}
|
|
405
|
-
: undefined
|
|
406
|
-
}
|
|
407
|
-
const discoveryAttempts = discovery !== undefined
|
|
408
|
-
? [discovery]
|
|
409
|
-
: this._buildKnownAddressDiscoveryAttempts(identifierData, discoveryCapabilities, targetHost)
|
|
410
|
-
let lastError
|
|
411
|
-
for (const discoveryAttempt of discoveryAttempts) {
|
|
412
|
-
try {
|
|
413
|
-
this._reportCommissioningProgress(onProgress, {
|
|
414
|
-
phase: 'pase',
|
|
415
|
-
percent: 15,
|
|
416
|
-
message: 'Discovering the device and establishing a secure PASE session…'
|
|
417
|
-
})
|
|
418
|
-
nodeId = await this.controller.commissionNode({
|
|
419
|
-
commissioning: {
|
|
420
|
-
nodeId: commissioningNodeId,
|
|
421
|
-
regulatoryLocation: api.GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor
|
|
422
|
-
},
|
|
423
|
-
discovery: discoveryAttempt,
|
|
424
|
-
passcode
|
|
425
|
-
}, {
|
|
426
|
-
connectNodeAfterCommissioning: false,
|
|
427
|
-
commissioningFlowImpl: this._createProgressCommissioningFlow(onProgress)
|
|
428
|
-
})
|
|
429
|
-
lastError = undefined
|
|
430
|
-
break
|
|
431
|
-
} catch (error) {
|
|
432
|
-
lastError = error
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
if (nodeId === undefined) {
|
|
436
|
-
if (targetHost !== '' && discoveryAttempts.length > 0) {
|
|
437
|
-
throw new Error(`No commissionable Matter device matching this pairing code could be commissioned at ${targetHost}. Direct PASE on UDP/5540 and mDNS discovery both failed.${lastError ? ` Last error: ${lastError.message || lastError}` : ''}`)
|
|
438
|
-
}
|
|
439
|
-
throw lastError || new Error('Matter commissioning failed')
|
|
440
|
-
}
|
|
441
|
-
} catch (error) {
|
|
442
|
-
// The 11-digit MANUAL code carries only the 4-bit short discriminator, so several
|
|
443
|
-
// identical devices (e.g. same-model Shelly) look alike to discovery: it can reach
|
|
444
|
-
// the WRONG one — typically the first, already-commissioned device — and fail with a
|
|
445
|
-
// "node ID already commissioned" conflict. Point the user to the QR code.
|
|
446
|
-
const msg = String((error && error.message) || error)
|
|
447
|
-
const isManual = !code.toUpperCase().startsWith('MT:')
|
|
448
|
-
if (/already commissioned|can not be reused/i.test(msg)) {
|
|
449
|
-
throw new Error(`${msg} — the commissioner reached a device that is ALREADY paired to this controller.${isManual ? ' The MANUAL code can reach the wrong identical device (short discriminator): use the QR code (MT:...) to target the exact one.' : ''} If you really mean this device, factory-reset it (or unpair that node) and try again. Pair one device at a time.`)
|
|
450
|
-
}
|
|
451
|
-
if (isManual) {
|
|
452
|
-
throw new Error(`${msg} — tip: the MANUAL code can't tell identical devices apart (short discriminator). Use the QR code (MT:...) for a precise match, pair one device at a time, and factory-reset the device if it is already commissioned elsewhere.`)
|
|
453
|
-
}
|
|
454
|
-
throw error
|
|
455
|
-
}
|
|
456
|
-
try {
|
|
457
|
-
this._reportCommissioningProgress(onProgress, {
|
|
458
|
-
phase: 'attaching',
|
|
459
|
-
percent: 98,
|
|
460
|
-
message: 'Loading the commissioned device structure…'
|
|
461
|
-
})
|
|
462
|
-
await this._attachNode(nodeId)
|
|
463
|
-
} catch (error) {
|
|
464
|
-
// Pairing succeeded: never report it as failed just because the first connection
|
|
465
|
-
// attempt errored. The node will be attached again at the next controller start.
|
|
466
|
-
this._log('warn', `classMatter: commission: node ${nodeId} paired but not yet attached: ${error.message}`)
|
|
467
|
-
}
|
|
468
|
-
this._reportCommissioningProgress(onProgress, {
|
|
469
|
-
phase: 'complete',
|
|
470
|
-
percent: 100,
|
|
471
|
-
message: 'Matter commissioning completed successfully.'
|
|
472
|
-
})
|
|
473
|
-
return nodeId.toString()
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Decommission (unpair) a device. Falls back to forced removal if the device is unreachable.
|
|
477
|
-
removeNode = async (_nodeIdString) => {
|
|
478
|
-
if (this.controller === null) throw new Error('Matter controller not started')
|
|
479
|
-
const api = await this._loadApi()
|
|
480
|
-
const nodeId = api.NodeId(BigInt(_nodeIdString))
|
|
481
|
-
const node = this.pairedNodes.get(_nodeIdString)
|
|
482
|
-
try {
|
|
483
|
-
if (node !== undefined) {
|
|
484
|
-
await node.decommission()
|
|
485
|
-
} else {
|
|
486
|
-
await this.controller.removeNode(nodeId, true)
|
|
487
|
-
}
|
|
488
|
-
} catch (error) {
|
|
489
|
-
this._log('warn', `classMatter: removeNode: decommission failed (${error.message}), forcing removal`)
|
|
490
|
-
await this.controller.removeNode(nodeId, false)
|
|
491
|
-
}
|
|
492
|
-
this.pairedNodes.delete(_nodeIdString)
|
|
493
|
-
this.commandQueue = this.commandQueue.filter((el) => el.nodeId !== _nodeIdString)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Returns [{ nodeId, name, vendorName, productName, connectionState }]
|
|
497
|
-
getCommissionedNodesDetails = () => {
|
|
498
|
-
if (this.controller === null) return []
|
|
499
|
-
const retArray = []
|
|
500
|
-
let details = []
|
|
501
|
-
try {
|
|
502
|
-
details = this.controller.getCommissionedNodesDetails()
|
|
503
|
-
} catch (error) {
|
|
504
|
-
this._log('warn', `classMatter: getCommissionedNodesDetails: ${error.message}`)
|
|
505
|
-
}
|
|
506
|
-
details.forEach((detail) => {
|
|
507
|
-
try {
|
|
508
|
-
const key = detail.nodeId.toString()
|
|
509
|
-
const node = this.pairedNodes.get(key)
|
|
510
|
-
let basicInfo = detail.basicInformationData || detail.deviceData?.basicInformation || {}
|
|
511
|
-
try {
|
|
512
|
-
if (node !== undefined && node.basicInformation !== undefined) basicInfo = node.basicInformation
|
|
513
|
-
} catch (error) { /* empty */ }
|
|
514
|
-
let connectionState = 'disconnected'
|
|
515
|
-
try {
|
|
516
|
-
if (node !== undefined) connectionState = this.nodeStateToString(node.connectionState)
|
|
517
|
-
} catch (error) { /* empty */ }
|
|
518
|
-
// Name: prefer the user-assigned label; otherwise fall back to the model and
|
|
519
|
-
// append a short unique suffix so identical devices (e.g. several Shelly of the
|
|
520
|
-
// same model) are distinguishable. Shelly expose their MAC in the serial number,
|
|
521
|
-
// so its last 4 hex chars are effectively the "last 4 of the MAC" other apps show.
|
|
522
|
-
const userLabel = basicInfo.nodeLabel || basicInfo.productLabel
|
|
523
|
-
const baseName = userLabel || basicInfo.productName || detail.advertisedName || `Node ${key}`
|
|
524
|
-
let displayName = baseName
|
|
525
|
-
if (!userLabel) {
|
|
526
|
-
const idSource = basicInfo.serialNumber || basicInfo.uniqueId || detail.advertisedName || key
|
|
527
|
-
const suffix = shortHexSuffix(idSource)
|
|
528
|
-
if (suffix && !baseName.toUpperCase().includes(suffix)) displayName = `${baseName} (${suffix})`
|
|
529
|
-
}
|
|
530
|
-
retArray.push({
|
|
531
|
-
nodeId: key,
|
|
532
|
-
name: displayName,
|
|
533
|
-
vendorName: basicInfo.vendorName || '',
|
|
534
|
-
productName: basicInfo.productName || '',
|
|
535
|
-
serialNumber: basicInfo.serialNumber || '',
|
|
536
|
-
connectionState
|
|
537
|
-
})
|
|
538
|
-
} catch (error) {
|
|
539
|
-
this._log('warn', `classMatter: getCommissionedNodesDetails item: ${error.message}`)
|
|
540
|
-
}
|
|
541
|
-
})
|
|
542
|
-
return retArray
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
_walkEndpoints = (endpoint, _callback) => {
|
|
546
|
-
_callback(endpoint)
|
|
547
|
-
try {
|
|
548
|
-
const parts = endpoint.parts
|
|
549
|
-
if (parts !== undefined) {
|
|
550
|
-
parts.forEach((child) => {
|
|
551
|
-
this._walkEndpoints(child, _callback)
|
|
552
|
-
})
|
|
553
|
-
}
|
|
554
|
-
} catch (error) { /* empty */ }
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
_getAllEndpoints = (node) => {
|
|
558
|
-
const endpoints = []
|
|
559
|
-
try {
|
|
560
|
-
node.getDevices().forEach((device) => {
|
|
561
|
-
this._walkEndpoints(device, (ep) => {
|
|
562
|
-
if (ep.number !== undefined && !endpoints.some((e) => e.number === ep.number)) endpoints.push(ep)
|
|
563
|
-
})
|
|
564
|
-
})
|
|
565
|
-
} catch (error) {
|
|
566
|
-
this._log('warn', `classMatter: _getAllEndpoints: ${error.message}`)
|
|
567
|
-
}
|
|
568
|
-
return endpoints
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
_findEndpoint = (node, _endpointId) => {
|
|
572
|
-
// getDevices() can expose only functional/bridged endpoints while the root endpoint
|
|
573
|
-
// lives behind getRootEndpoint(). Resolve endpoint 0 explicitly for node clusters
|
|
574
|
-
// such as BasicInformation and GeneralCommissioning.
|
|
575
|
-
if (Number(_endpointId) === 0 && typeof node?.getRootEndpoint === 'function') {
|
|
576
|
-
try {
|
|
577
|
-
const root = node.getRootEndpoint()
|
|
578
|
-
if (root !== undefined) return root
|
|
579
|
-
} catch (error) { /* The legacy endpoint structure may still be initializing. */ }
|
|
580
|
-
}
|
|
581
|
-
return this._getAllEndpoints(node).find((ep) => Number(ep.number) === Number(_endpointId))
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
_findClusterClient = (node, _endpointId, _clusterId) => {
|
|
585
|
-
try {
|
|
586
|
-
const endpoint = this._findEndpoint(node, _endpointId)
|
|
587
|
-
if (endpoint === undefined) return undefined
|
|
588
|
-
return endpoint.getAllClusterClients().find((cc) => Number(cc.id) === Number(_clusterId))
|
|
589
|
-
} catch (error) {
|
|
590
|
-
this._logThrottled('warn', 'matter:findcluster', `classMatter: _findClusterClient: ${error.message}`)
|
|
591
|
-
return undefined
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
_findAttribute = (clusterClient, attributeNameOrId) => {
|
|
596
|
-
if (clusterClient === undefined || attributeNameOrId === undefined || attributeNameOrId === null) return undefined
|
|
597
|
-
if (typeof attributeNameOrId === 'string' && clusterClient.attributes[attributeNameOrId] !== undefined) {
|
|
598
|
-
return clusterClient.attributes[attributeNameOrId]
|
|
599
|
-
}
|
|
600
|
-
return Object.values(clusterClient.attributes).find((attribute) => Number(attribute?.id) === Number(attributeNameOrId))
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Returns the full structure of a commissioned node: endpoints, clusters, attributes (with cached values) and commands.
|
|
604
|
-
// Used by the editor UI to let the user pick the mapping targets.
|
|
605
|
-
getNodeStructure = (_nodeIdString) => {
|
|
606
|
-
const node = this.pairedNodes.get(_nodeIdString)
|
|
607
|
-
if (node === undefined) throw new Error(`Matter node ${_nodeIdString} unknown or not yet connected`)
|
|
608
|
-
const retEndpoints = []
|
|
609
|
-
this._getAllEndpoints(node).forEach((endpoint) => {
|
|
610
|
-
let deviceTypes = []
|
|
611
|
-
try {
|
|
612
|
-
deviceTypes = endpoint.getDeviceTypes().map((dt) => dt.name)
|
|
613
|
-
} catch (error) { /* empty */ }
|
|
614
|
-
const clusters = []
|
|
615
|
-
let clusterClients = []
|
|
616
|
-
try {
|
|
617
|
-
clusterClients = endpoint.getAllClusterClients()
|
|
618
|
-
} catch (error) {
|
|
619
|
-
this._logThrottled('warn', 'matter:structure:clients', `classMatter: getNodeStructure getAllClusterClients: ${error.message}`)
|
|
620
|
-
}
|
|
621
|
-
clusterClients.forEach((cc) => {
|
|
622
|
-
try {
|
|
623
|
-
const attributes = []
|
|
624
|
-
Object.keys(cc.attributes).forEach((attrName) => {
|
|
625
|
-
try {
|
|
626
|
-
if (/^\d+$/.test(attrName)) return // Skip numeric aliases
|
|
627
|
-
if (typeof cc.isAttributeSupportedByName === 'function' && !cc.isAttributeSupportedByName(attrName)) return
|
|
628
|
-
// Skip the global meta attributes (FeatureMap, AttributeList, ClusterRevision...):
|
|
629
|
-
// they are useless for KNX mappings and only confuse the user.
|
|
630
|
-
const exposeFeatureMap = [47, 768].includes(Number(cc.id)) && attrName === 'featureMap'
|
|
631
|
-
if (Number(cc.attributes[attrName].id) >= 65528 && !exposeFeatureMap) return
|
|
632
|
-
let value
|
|
633
|
-
try {
|
|
634
|
-
value = cc.attributes[attrName].getLocal()
|
|
635
|
-
} catch (error) { value = undefined }
|
|
636
|
-
attributes.push({
|
|
637
|
-
name: attrName,
|
|
638
|
-
id: Number(cc.attributes[attrName].id),
|
|
639
|
-
value: this._jsonSafe(value),
|
|
640
|
-
writable: cc.attributes[attrName]?.attribute?.schema?.writable === true
|
|
641
|
-
})
|
|
642
|
-
} catch (error) { /* empty */ }
|
|
643
|
-
})
|
|
644
|
-
const commands = []
|
|
645
|
-
Object.keys(cc.commands).forEach((cmdName) => {
|
|
646
|
-
try {
|
|
647
|
-
if (/^\d+$/.test(cmdName)) return
|
|
648
|
-
if (typeof cc.isCommandSupportedByName === 'function' && !cc.isCommandSupportedByName(cmdName)) return
|
|
649
|
-
commands.push({ name: cmdName })
|
|
650
|
-
} catch (error) { /* empty */ }
|
|
651
|
-
})
|
|
652
|
-
clusters.push({
|
|
653
|
-
id: Number(cc.id),
|
|
654
|
-
name: cc.name,
|
|
655
|
-
attributes,
|
|
656
|
-
commands
|
|
657
|
-
})
|
|
658
|
-
} catch (error) {
|
|
659
|
-
this._logThrottled('warn', 'matter:structure', `classMatter: getNodeStructure cluster error: ${error.message}`)
|
|
660
|
-
}
|
|
661
|
-
})
|
|
662
|
-
try {
|
|
663
|
-
retEndpoints.push({
|
|
664
|
-
endpointId: Number(endpoint.number),
|
|
665
|
-
name: endpoint.name,
|
|
666
|
-
deviceTypes,
|
|
667
|
-
clusters
|
|
668
|
-
})
|
|
669
|
-
} catch (error) {
|
|
670
|
-
this._logThrottled('warn', 'matter:structure:endpoint', `classMatter: getNodeStructure endpoint error: ${error.message}`)
|
|
671
|
-
}
|
|
672
|
-
})
|
|
673
|
-
return { nodeId: _nodeIdString, endpoints: retEndpoints }
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
_jsonSafe = (value) => {
|
|
677
|
-
try {
|
|
678
|
-
return JSON.parse(JSON.stringify(value, (k, v) => (typeof v === 'bigint' ? v.toString() : v)))
|
|
679
|
-
} catch (error) {
|
|
680
|
-
return undefined
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Read an attribute value. By default returns the locally cached (subscribed) value.
|
|
685
|
-
readAttribute = async (_nodeIdString, _endpointId, _clusterId, _attributeName, _requestFromRemote = false) => {
|
|
686
|
-
const node = this.pairedNodes.get(_nodeIdString)
|
|
687
|
-
if (node === undefined) throw new Error(`Matter node ${_nodeIdString} unknown or not yet connected`)
|
|
688
|
-
const clusterClient = this._findClusterClient(node, _endpointId, _clusterId)
|
|
689
|
-
if (clusterClient === undefined) throw new Error(`Cluster ${_clusterId} not found on endpoint ${_endpointId}`)
|
|
690
|
-
const attribute = this._findAttribute(clusterClient, _attributeName)
|
|
691
|
-
if (attribute === undefined) throw new Error(`Attribute ${_attributeName} not found in cluster ${clusterClient.name}`)
|
|
692
|
-
return attribute.get(_requestFromRemote)
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// Synchronous read of the locally cached attribute value (undefined if not yet received).
|
|
696
|
-
getCachedAttribute = (_nodeIdString, _endpointId, _clusterId, _attributeName) => {
|
|
697
|
-
try {
|
|
698
|
-
const node = this.pairedNodes.get(_nodeIdString)
|
|
699
|
-
if (node === undefined) return undefined
|
|
700
|
-
const clusterClient = this._findClusterClient(node, _endpointId, _clusterId)
|
|
701
|
-
if (clusterClient === undefined) return undefined
|
|
702
|
-
const attribute = this._findAttribute(clusterClient, _attributeName)
|
|
703
|
-
if (attribute === undefined) return undefined
|
|
704
|
-
return attribute.getLocal()
|
|
705
|
-
} catch (error) {
|
|
706
|
-
return undefined
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
renameNode = async (_nodeIdString, _label) => {
|
|
711
|
-
const label = (_label || '').toString().trim()
|
|
712
|
-
if (label === '') throw new Error('Empty Matter device name')
|
|
713
|
-
if (label.length > 64) throw new Error('Matter device name is too long')
|
|
714
|
-
const node = this.pairedNodes.get(_nodeIdString)
|
|
715
|
-
if (node === undefined) throw new Error(`Matter node ${_nodeIdString} unknown or not yet connected`)
|
|
716
|
-
// commission() attaches the PairedNode before its first wildcard read/subscription
|
|
717
|
-
// has necessarily built the legacy endpoint structure. A user-supplied name is
|
|
718
|
-
// applied immediately after commissioning, so wait briefly for BasicInformation
|
|
719
|
-
// instead of reporting a misleading permanent-cluster warning.
|
|
720
|
-
const deadline = Date.now() + 10000
|
|
721
|
-
let clusterClient
|
|
722
|
-
do {
|
|
723
|
-
try {
|
|
724
|
-
if (typeof node.getRootClusterClient === 'function') clusterClient = node.getRootClusterClient(this._api.BasicInformation)
|
|
725
|
-
} catch (error) { /* Endpoint structure is still initializing. */ }
|
|
726
|
-
if (clusterClient === undefined) clusterClient = this._findClusterClient(node, 0, 40) // BasicInformation on the root endpoint
|
|
727
|
-
if (clusterClient !== undefined) break
|
|
728
|
-
await pleaseWait(100)
|
|
729
|
-
} while (Date.now() < deadline)
|
|
730
|
-
if (clusterClient === undefined) throw new Error(`BasicInformation cluster was not available on node ${_nodeIdString} after initialization`)
|
|
731
|
-
const attribute = clusterClient.attributes.nodeLabel
|
|
732
|
-
if (attribute === undefined) throw new Error(`nodeLabel attribute not found on node ${_nodeIdString}`)
|
|
733
|
-
await attribute.set(label)
|
|
734
|
-
return label
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
_enqueueCommand = (command) => {
|
|
738
|
-
if (!command) return false
|
|
739
|
-
if (!Array.isArray(this.commandQueue)) this.commandQueue = []
|
|
740
|
-
this.commandQueue.unshift(command)
|
|
741
|
-
if (this.commandQueue.length > this.queueMaxLength) {
|
|
742
|
-
this.commandQueue.splice(this.queueMaxLength)
|
|
743
|
-
this._logThrottled('warn', 'matter:queue:overflow', `Matter command queue overflow: capped at ${this.queueMaxLength}`, 60000)
|
|
744
|
-
}
|
|
745
|
-
return true
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
_isNodeCommissioned = (nodeId) => {
|
|
749
|
-
const key = String(nodeId)
|
|
750
|
-
try {
|
|
751
|
-
if (this.controller && typeof this.controller.getCommissionedNodes === 'function') {
|
|
752
|
-
return this.controller.getCommissionedNodes().some((commissionedNodeId) => commissionedNodeId.toString() === key)
|
|
753
|
-
}
|
|
754
|
-
} catch (error) { /* Fall back to the attached-node map while the controller changes state. */ }
|
|
755
|
-
return this.pairedNodes.has(key)
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Add a command or attribute write to the queue.
|
|
759
|
-
// _item: { nodeId, endpointId, clusterId, kind: 'command'|'attributeWrite', name, args }
|
|
760
|
-
writeMatterQueueAdd = async (_item) => {
|
|
761
|
-
const nodeId = _item?.nodeId === undefined || _item?.nodeId === null ? '' : String(_item.nodeId)
|
|
762
|
-
const item = { ..._item, nodeId }
|
|
763
|
-
if (nodeId === '' || !this._isNodeCommissioned(nodeId)) {
|
|
764
|
-
const error = new Error(`Matter node ${nodeId || '(empty)'} is no longer commissioned`)
|
|
765
|
-
error.code = 'MATTER_NODE_NOT_COMMISSIONED'
|
|
766
|
-
// Defer the notification so a legacy fire-and-forget caller cannot overwrite
|
|
767
|
-
// the red orphan-device status with its immediate optimistic "command sent" state.
|
|
768
|
-
queueMicrotask(() => this.emit('commandError', { item, error: error.message, code: error.code }))
|
|
769
|
-
throw error
|
|
770
|
-
}
|
|
771
|
-
return this._enqueueCommand(item)
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
deleteMatterQueue = async (_nodeIdString) => {
|
|
775
|
-
const nodeId = String(_nodeIdString)
|
|
776
|
-
this.commandQueue = this.commandQueue.filter((el) => String(el.nodeId) !== nodeId)
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
_withCommandTimeout = async (operation) => {
|
|
780
|
-
let timer
|
|
781
|
-
try {
|
|
782
|
-
return await Promise.race([
|
|
783
|
-
Promise.resolve().then(operation),
|
|
784
|
-
new Promise((resolve, reject) => {
|
|
785
|
-
timer = setTimeout(() => {
|
|
786
|
-
const error = new Error(`Matter command timed out after ${this.commandTimeoutMs} ms`)
|
|
787
|
-
error.code = 'MATTER_COMMAND_TIMEOUT'
|
|
788
|
-
reject(error)
|
|
789
|
-
}, this.commandTimeoutMs)
|
|
790
|
-
timer.unref?.()
|
|
791
|
-
})
|
|
792
|
-
])
|
|
793
|
-
} finally {
|
|
794
|
-
if (timer !== undefined) clearTimeout(timer)
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
processQueueItem = async (_item = undefined) => {
|
|
799
|
-
let item = _item ?? null
|
|
800
|
-
try {
|
|
801
|
-
if (this.matterConnectionStatus !== 'connected') return
|
|
802
|
-
if (item === null) item = this.commandQueue.pop()
|
|
803
|
-
if (!item) return
|
|
804
|
-
const node = this.pairedNodes.get(item.nodeId)
|
|
805
|
-
if (node === undefined || !this._isNodeCommissioned(item.nodeId)) {
|
|
806
|
-
const error = new Error(`Matter node ${item.nodeId} is no longer commissioned`)
|
|
807
|
-
error.code = 'MATTER_NODE_NOT_COMMISSIONED'
|
|
808
|
-
throw error
|
|
809
|
-
}
|
|
810
|
-
const clusterClient = this._findClusterClient(node, item.endpointId, item.clusterId)
|
|
811
|
-
if (clusterClient === undefined) throw new Error(`Cluster ${item.clusterId} not found on endpoint ${item.endpointId} of node ${item.nodeId}`)
|
|
812
|
-
if (item.kind === 'attributeWrite') {
|
|
813
|
-
const attribute = clusterClient.attributes[item.name]
|
|
814
|
-
if (attribute === undefined) throw new Error(`Attribute ${item.name} not found in cluster ${clusterClient.name}`)
|
|
815
|
-
await this._withCommandTimeout(() => attribute.set(item.args))
|
|
816
|
-
} else {
|
|
817
|
-
const command = clusterClient.commands[item.name]
|
|
818
|
-
if (typeof command !== 'function') throw new Error(`Command ${item.name} not found in cluster ${clusterClient.name}`)
|
|
819
|
-
await this._withCommandTimeout(() => command(item.args))
|
|
820
|
-
}
|
|
821
|
-
} catch (error) {
|
|
822
|
-
const target = item ? `${item.nodeId}/${item.endpointId}/${item.clusterId}/${item.name}` : 'unknown'
|
|
823
|
-
if (!error.code && item) {
|
|
824
|
-
let connectionState = 'unknown'
|
|
825
|
-
try {
|
|
826
|
-
const targetNode = this.pairedNodes.get(item.nodeId)
|
|
827
|
-
if (targetNode?.connectionState !== undefined) connectionState = this.nodeStateToString(targetNode.connectionState)
|
|
828
|
-
} catch (stateError) { /* empty */ }
|
|
829
|
-
if (connectionState !== 'unknown' && connectionState !== 'connected') {
|
|
830
|
-
error.code = 'MATTER_NODE_UNAVAILABLE'
|
|
831
|
-
} else if (/disconnected|reconnecting|unreachable|no session|peer communication|network.*(?:failed|error)|timed? ?out/i.test(error.message || '')) {
|
|
832
|
-
error.code = 'MATTER_NODE_UNAVAILABLE'
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
this._logThrottled('error', `matter:queue:error:${target}`, `classMatter: processQueueItem (${target}): ${error.message}`, 30000)
|
|
836
|
-
this.emit('commandError', { item, error: error.message, code: error.code })
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
_takeNextQueueItem = () => {
|
|
841
|
-
if (!Array.isArray(this.commandQueue) || this.commandQueue.length === 0) return undefined
|
|
842
|
-
// New items are unshifted, therefore the oldest FIFO item is at the end.
|
|
843
|
-
// Walk backwards until a device lane that is not already busy is found.
|
|
844
|
-
for (let index = this.commandQueue.length - 1; index >= 0; index--) {
|
|
845
|
-
const item = this.commandQueue[index]
|
|
846
|
-
const nodeId = String(item?.nodeId ?? '')
|
|
847
|
-
if (this.commandQueueInFlight.has(nodeId)) continue
|
|
848
|
-
return this.commandQueue.splice(index, 1)[0]
|
|
849
|
-
}
|
|
850
|
-
return undefined
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
_dispatchQueueItems = () => {
|
|
854
|
-
if (this.matterConnectionStatus !== 'connected') return
|
|
855
|
-
while (this.commandQueueInFlight.size < this.queueMaxConcurrent) {
|
|
856
|
-
const item = this._takeNextQueueItem()
|
|
857
|
-
if (!item) return
|
|
858
|
-
const nodeId = String(item.nodeId)
|
|
859
|
-
const laneToken = {}
|
|
860
|
-
this.commandQueueInFlight.set(nodeId, laneToken)
|
|
861
|
-
Promise.resolve(this.processQueueItem(item))
|
|
862
|
-
.catch((error) => this._logThrottled('error', `matter:queue:lane:${nodeId}`, `classMatter: queue lane ${nodeId}: ${error.message}`, 30000))
|
|
863
|
-
.finally(() => {
|
|
864
|
-
if (this.commandQueueInFlight.get(nodeId) === laneToken) this.commandQueueInFlight.delete(nodeId)
|
|
865
|
-
})
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
handleQueue = async () => {
|
|
870
|
-
do {
|
|
871
|
-
if (this.matterConnectionStatus === 'connected' && this.commandQueue && this.commandQueue.length > 0) {
|
|
872
|
-
this._dispatchQueueItems()
|
|
873
|
-
}
|
|
874
|
-
await pleaseWait(this.queuePollMs)
|
|
875
|
-
} while (!this.exitAllQueues)
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
close = async () => {
|
|
879
|
-
this.exitAllQueues = true
|
|
880
|
-
this.commandQueue = []
|
|
881
|
-
this.commandQueueInFlight.clear()
|
|
882
|
-
this._logThrottle.clear()
|
|
883
|
-
this.matterConnectionStatus = 'disconnected'
|
|
884
|
-
try {
|
|
885
|
-
if (this.controller !== null) await this.controller.close()
|
|
886
|
-
} catch (error) {
|
|
887
|
-
this._log('warn', `classMatter: close: ${error.message}`)
|
|
888
|
-
}
|
|
889
|
-
this.controller = null
|
|
890
|
-
this.pairedNodes.clear()
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
export { classMatter }
|