node-red-contrib-knx-ultimate 5.2.1 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +13 -10
  2. package/nodes/commonFunctions.js +9 -9
  3. package/nodes/knxUltimateMatterBridge.html +212 -299
  4. package/nodes/knxUltimateMatterBridge.js +166 -309
  5. package/nodes/knxUltimateMatterDevice.html +46 -0
  6. package/nodes/locales/de/knxUltimateMatterBridge.html +13 -24
  7. package/nodes/locales/de/knxUltimateMatterBridge.json +4 -1
  8. package/nodes/locales/de/matterbridge-config.html +34 -0
  9. package/nodes/locales/de/matterbridge-config.json +20 -0
  10. package/nodes/locales/en/knxUltimateMatterBridge.html +14 -25
  11. package/nodes/locales/en/knxUltimateMatterBridge.json +4 -1
  12. package/nodes/locales/en/matterbridge-config.html +34 -0
  13. package/nodes/locales/en/matterbridge-config.json +20 -0
  14. package/nodes/locales/es/knxUltimateMatterBridge.html +13 -24
  15. package/nodes/locales/es/knxUltimateMatterBridge.json +4 -1
  16. package/nodes/locales/es/matterbridge-config.html +34 -0
  17. package/nodes/locales/es/matterbridge-config.json +20 -0
  18. package/nodes/locales/fr/knxUltimateMatterBridge.html +13 -24
  19. package/nodes/locales/fr/knxUltimateMatterBridge.json +4 -1
  20. package/nodes/locales/fr/matterbridge-config.html +34 -0
  21. package/nodes/locales/fr/matterbridge-config.json +20 -0
  22. package/nodes/locales/it/knxUltimateMatterBridge.html +13 -24
  23. package/nodes/locales/it/knxUltimateMatterBridge.json +4 -1
  24. package/nodes/locales/it/matterbridge-config.html +34 -0
  25. package/nodes/locales/it/matterbridge-config.json +20 -0
  26. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +13 -24
  27. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +4 -1
  28. package/nodes/locales/zh-CN/matterbridge-config.html +34 -0
  29. package/nodes/locales/zh-CN/matterbridge-config.json +20 -0
  30. package/nodes/matterbridge-config.html +121 -0
  31. package/nodes/matterbridge-config.js +220 -0
  32. package/package.json +2 -1
@@ -1,11 +1,11 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateMatterBridge">
2
- # Matter Bridge (BETA)
2
+ # Matter Bridge device (BETA)
3
3
 
4
4
  > Questo nodo è in **BETA**: funziona, ma alcuni dettagli potrebbero cambiare tra una release e l'altra.
5
5
 
6
6
  ## Panoramica
7
7
 
8
- Il nodo Matter Bridge espone gli **indirizzi di gruppo KNX come dispositivi Matter**: Alexa, Google Home, Apple Home (o qualunque controller Matter) associano questo bridge **una sola volta** e vedono tutti i dispositivi KNX configurati, con i nomi che hai scelto, pronti per il controllo da app e vocale.
8
+ Ogni nodo Matter Bridge device espone **un dispositivo KNX come dispositivo Matter**: i controller abbinati (Alexa, Google Home, Apple Home...) lo vedono, con il nome che hai scelto, pronto per il controllo da app e vocale. Puntalo a un nodo di configurazione **Bridge Matter** (il bridge vero e proprio, abbinato una sola volta - il QR di abbinamento vive lì) e aggiungi quanti nodi device vuoi, ovunque nei tuoi flow.
9
9
 
10
10
  È la direzione opposta del nodo *Matter Device*: lì KNX controlla un dispositivo Matter, qui i controller Matter controllano KNX.
11
11
 
@@ -13,15 +13,13 @@ Il nodo Matter Bridge espone gli **indirizzi di gruppo KNX come dispositivi Matt
13
13
 
14
14
  |Campo|Descrizione|
15
15
  |--|--|
16
- | GW KNX | Gateway KNX usato per i telegrammi. **Opzionale**: senza gateway il bridge funziona in modalità solo-flow — abilita i PIN del nodo e piloti ogni dispositivo con i messaggi del flow |
17
- | Nome del bridge Matter | Come viene chiamato il bridge stesso nelle app Matter |
18
- | Porta | Porta UDP del server Matter (default 5540). Un solo nodo bridge per istanza Node-RED. |
19
- | Dispositivi | I dispositivi virtuali KNX esposti su Matter (vedi sotto) |
20
- | Leggi i GA di stato all'avvio | Invia una `GroupValue_Read` a ogni GA di stato all'avvio, per popolare gli attributi Matter |
16
+ | Bridge Matter | Il nodo di configurazione Bridge Matter a cui appartiene questo dispositivo |
17
+ | GW KNX | Gateway KNX usato per i telegrammi. **Opzionale**: senza, il dispositivo funziona in modalità solo-flow tramite i PIN del nodo. Selezionato automaticamente se il progetto ha un solo gateway |
18
+ | Nome | Quello che Alexa & Co. mostrano e usano per i comandi vocali |
19
+ | Tipo dispositivo | Il tipo di dispositivo Matter (vedi sotto); determina quali campi indirizzo di gruppo compaiono |
20
+ | Leggi lo stato all'avvio | Invia una `GroupValue_Read` ai GA di stato all'avvio, per popolare gli attributi Matter |
21
21
 
22
- ## Dispositivi
23
-
24
- Ogni riga è un dispositivo mostrato da Alexa & Co. Scegli il **tipo**, dagli il **nome** che l'assistente userà, poi compila gli indirizzi di gruppo (con autocompletamento ETS):
22
+ ## Tipi di dispositivo e indirizzi di gruppo
25
23
 
26
24
  |Tipo|Indirizzi di gruppo|
27
25
  |--|--|
@@ -36,29 +34,20 @@ Ogni riga è un dispositivo mostrato da Alexa & Co. Scegli il **tipo**, dagli il
36
34
  | Rilevatore fumo/CO | GA stato allarme fumo + GA stato allarme CO opzionale (DPT 1.005): notifiche critiche sul telefono |
37
35
  | Rilevatore allagamento | GA stato allagamento (DPT 1.005) |
38
36
  | Sensore qualità aria (CO2) | GA stato CO2 in ppm (DPT 9.008); la classe qualità aria (buona/discreta/moderata/scarsa...) è derivata automaticamente |
39
- | Robot aspirapolvere | **Solo-flow**: nessun indirizzo di gruppo. Abilita i PIN del nodo: i comandi dell'assistente ("avvia pulizia", pausa/riprendi/torna alla base) arrivano sull'output come `rvcmode`/`rvccommand`; riporta lo stato con `msg.payload = { device, function: "rvcstate", value: "running"\|"docked"\|"charging"\|"paused"\|"error" }` e la modalità con `function: "rvcmode", value: "cleaning"\|"idle"`. Integra il tuo Roomba/Roborock con qualsiasi nodo Node-RED ed esponilo ad Alexa/Apple |
37
+ | Robot aspirapolvere | **Solo-flow**: nessun indirizzo di gruppo. Abilita i PIN del nodo: i comandi dell'assistente ("avvia pulizia", pausa/riprendi/torna alla base) arrivano sull'output come `rvcmode`/`rvccommand`; riporta lo stato con `msg.payload = { function: "rvcstate", value: "running"\|"docked"\|"charging"\|"paused"\|"error" }` e la modalità con `function: "rvcmode", value: "cleaning"\|"idle"` |
40
38
 
41
39
  - **GA comando**: scritto sul bus KNX quando l'assistente invia un comando.
42
40
  - **GA stato**: letto dal bus per tenere aggiornati gli attributi Matter (e le app).
43
41
 
44
- ## Abbinamento
45
-
46
- 1. Fai il **deploy**, attendi qualche secondo, poi riapri il nodo.
47
- 2. Il pannello di abbinamento mostra il **QR code** e il **codice manuale**: scansiona o digita in Alexa / Google Home / Apple Home ("aggiungi dispositivo Matter").
48
- 3. Più controller possono essere abbinati allo stesso bridge (multi-fabric Matter).
49
-
50
- Il pulsante **Reset abbinamento** rimuove tutti i controller abbinati e riavvia l'advertising di abbinamento.
51
-
52
42
  ## PIN del nodo
53
43
 
54
44
  Se abiliti i PIN input/output del nodo:
55
45
 
56
- - **Input**: aggiorna lo stato Matter di un dispositivo virtuale dal flow, senza passare dal bus KNX: `msg.payload = { device: "Luce cucina", function: "onoff", value: true }` (`device` accetta il nome o l'id interno; `function` è una tra `onoff`, `level`, `position`, `temperature`, `humidity`, `illuminance`, `occupancy`, `contact`, `currenttemp`, `setpoint`). Utile per esporre ad Alexa & Co. valori calcolati nel flow (es. un sensore virtuale).
57
- - **Output**: ogni comando ricevuto da un controller Matter viene inoltrato al flow: `msg.topic` = nome del dispositivo, `msg.payload` = valore, `msg.matter` = il comando grezzo. I dispositivi senza GA di comando diventano **dispositivi solo-flow**: il comando arriva al tuo flow e decidi tu cosa farne.
46
+ - **Input**: aggiorna lo stato Matter dal flow, senza passare dal bus KNX: `msg.payload = { function: "onoff", value: true }` (`function` è una tra `onoff`, `level`, `rgb`, `colortemp`, `position`, `temperature`, `humidity`, `illuminance`, `occupancy`, `contact`, `currenttemp`, `setpoint`, `fanspeed`, `smoke`, `co`, `leak`, `co2`, `rvcstate`, `rvcmode`). Utile per esporre ad Alexa & Co. valori calcolati nel flow (es. un sensore virtuale).
47
+ - **Output**: ogni comando ricevuto da un controller Matter viene inoltrato al flow: `msg.topic` = nome del dispositivo, `msg.payload` = valore, `msg.matter` = il comando grezzo. Un dispositivo senza GA di comando diventa un **dispositivo solo-flow**.
58
48
 
59
49
  ## Note
60
50
 
61
- - L'host Node-RED deve avere **IPv6 link-local** attivo (requisito standard Matter) ed essere raggiungibile dal controller sulla rete locale.
62
- - L'identità del bridge è salvata in `knxultimatestorage/matter` nella directory utente di Node-RED: i re-deploy NON richiedono un nuovo abbinamento.
63
- - Rinomina e aggiunta di dispositivi vengono recepite automaticamente dai controller; se rimuovi un dispositivo, sparisce dalle app.
51
+ - L'identità Matter del dispositivo è legata a questo nodo: eliminando il nodo e creandone uno nuovo, le app vedono un dispositivo nuovo di zecca.
52
+ - I nodi device aggiunti/rinominati/rimossi vengono recepiti dai controller abbinati in pochi secondi, senza riabbinare il bridge.
64
53
  </script>
@@ -69,7 +69,10 @@
69
69
  "reset_button": "Reset abbinamento",
70
70
  "reset_confirm": "Rimuovere TUTTI i controller abbinati e riavviare l'abbinamento? I dispositivi spariranno da Alexa/Google/Apple.",
71
71
  "reset_ok": "Abbinamento resettato. Il bridge è di nuovo in attesa di abbinamento."
72
- }
72
+ },
73
+ "bridge": "Bridge Matter",
74
+ "device_name": "Nome (mostrato da Alexa & Co.)",
75
+ "device_help": "Scegli il tipo di dispositivo e dagli il nome che userà l'assistente vocale, poi compila gli indirizzi di gruppo. Il gateway KNX è opzionale: senza, abilita i PIN del nodo e piloti il dispositivo dal flow."
73
76
  },
74
77
  "common": {
75
78
  "ga": "GA",
@@ -0,0 +1,34 @@
1
+ <script type="text/markdown" data-help-name="matterbridge-config">
2
+ # Bridge Matter (BETA)
3
+
4
+ > Questo nodo è in **BETA**: funziona, ma alcuni dettagli potrebbero cambiare tra una release e l'altra.
5
+
6
+ ## Panoramica
7
+
8
+ Questo nodo di configurazione è il **bridge Matter vero e proprio**: esegue il server Matter che Alexa, Google Home, Apple Home (o qualunque controller Matter) associano **una sola volta**. Ogni nodo **Matter Bridge device** nei tuoi flow punta qui e compare nelle app come un dispositivo del bridge.
9
+
10
+ ## Configurazione
11
+
12
+ |Campo|Descrizione|
13
+ |--|--|
14
+ | Nome | Il nome di questo nodo di configurazione in Node-RED |
15
+ | Nome del bridge Matter | Come viene chiamato il bridge stesso nelle app Matter |
16
+ | Porta | Porta UDP del server Matter (default 5540). Ogni bridge richiede la propria porta, quindi puoi eseguire **più bridge indipendenti** |
17
+
18
+ ## Abbinamento
19
+
20
+ 1. Fai il **deploy**, attendi qualche secondo, poi riapri questo nodo.
21
+ 2. Il pannello di abbinamento mostra il **QR code** e il **codice manuale**: scansiona o digita in Alexa / Google Home / Apple Home ("aggiungi dispositivo Matter").
22
+ 3. Più controller possono essere abbinati allo stesso bridge (multi-fabric Matter).
23
+
24
+ Il pulsante **Reset abbinamento** rimuove tutti i controller abbinati e riavvia l'advertising di abbinamento.
25
+
26
+ ## Identità e archiviazione
27
+
28
+ L'identità del bridge è legata a questo nodo di configurazione ed è salvata in `knxultimatestorage/matter` nella directory utente di Node-RED: i re-deploy (anche cambiando porta o nome) **NON** richiedono un nuovo abbinamento. Solo eliminando questo nodo di configurazione e creandone uno nuovo cambia l'identità — in quel caso rimuovi il vecchio bridge dall'app Matter e riabbina.
29
+
30
+ ## Note
31
+
32
+ - L'host Node-RED deve avere **IPv6 link-local** attivo (requisito standard Matter) ed essere raggiungibile dai controller sulla rete locale.
33
+ - I nodi device aggiunti/rinominati/rimossi vengono recepiti dai controller abbinati in pochi secondi, senza riabbinare.
34
+ </script>
@@ -0,0 +1,20 @@
1
+ {
2
+ "matterbridge-config": {
3
+ "properties": {
4
+ "title": "Bridge Matter",
5
+ "intro": "Questo nodo di configurazione è il bridge Matter vero e proprio: Alexa, Google Home, Apple Home (o qualunque controller Matter) lo associano una volta sola e vedono tutti i dispositivi KNX che gli colleghi. Aggiungi i nodi \"Matter Bridge device\" nei tuoi flow e puntali qui. Fai prima il deploy, poi riapri per ottenere il codice di abbinamento.",
6
+ "node-config-input-name": "Nome",
7
+ "bridge_name": "Nome del bridge Matter",
8
+ "port": "Porta",
9
+ "pairing_title": "Abbinamento (Alexa, Google Home, Apple Home...)",
10
+ "not_running": "Il bridge Matter non è ancora in esecuzione. Fai il deploy, attendi qualche secondo e clicca aggiorna.",
11
+ "commissioned": "Bridge abbinato a questi controller:",
12
+ "awaiting": "In attesa di abbinamento. Scansiona il QR code o inserisci il codice manuale nella tua app Matter:",
13
+ "qr_link": "Apri il QR code nel browser",
14
+ "deploy_first": "Fai prima il deploy di questo nodo di configurazione, poi clicca aggiorna.",
15
+ "reset_button": "Reset abbinamento",
16
+ "reset_confirm": "Rimuovere TUTTI i controller abbinati e riavviare l'abbinamento? I dispositivi spariranno da Alexa/Google/Apple.",
17
+ "reset_ok": "Abbinamento resettato. Il bridge è di nuovo in attesa di abbinamento."
18
+ }
19
+ }
20
+ }
@@ -1,11 +1,11 @@
1
1
  <script type="text/markdown" data-help-name="knxUltimateMatterBridge">
2
- # Matter Bridge (BETA)
2
+ # Matter Bridge device (BETA)
3
3
 
4
4
  > 此节点处于 **BETA** 阶段:可以正常工作,但细节可能在版本之间发生变化。
5
5
 
6
6
  ## 概述
7
7
 
8
- Matter Bridge 节点将 **KNX 组地址作为 Matter 设备**公开:Alexa、Google Home、Apple Home(或任何 Matter 控制器)只需配对此桥接器**一次**,即可看到您配置的所有 KNX 设备(使用您输入的名称),随时可通过 App 和语音控制。
8
+ 每个 Matter Bridge device 节点将**一个 KNX 设备公开为 Matter 设备**:已配对的控制器(Alexa、Google Home、Apple Home...)会以您输入的名称看到它,随时可通过 App 和语音控制。将它指向一个 **Matter 桥接器**配置节点(真正的桥接器,只需配对一次 - 配对二维码在那里),然后在流程中随意添加任意数量的设备节点。
9
9
 
10
10
  这与 *Matter Device* 节点方向相反:那里是 KNX 控制 Matter 设备,这里是 Matter 控制器控制 KNX。
11
11
 
@@ -13,15 +13,13 @@ Matter Bridge 节点将 **KNX 组地址作为 Matter 设备**公开:Alexa、Go
13
13
 
14
14
  |字段|说明|
15
15
  |--|--|
16
- | KNX 网关 | 用于报文的 KNX 网关。**可选**:没有网关时桥接器以纯 Flow 模式工作——启用节点引脚并用 Flow 消息驱动每个设备 |
17
- | Matter 桥接器名称 | 桥接器本身在 Matter App 中显示的名称 |
18
- | 端口 | Matter 服务器的 UDP 端口(默认 5540)。每个 Node-RED 实例只能有一个桥接器节点。 |
19
- | 设备 | Matter 上公开的 KNX 虚拟设备(见下文) |
20
- | 启动时读取状态组地址 | 启动时向每个状态 GA 发送 `GroupValue_Read`,以填充 Matter 属性 |
16
+ | Matter 桥接器 | 此设备所属的 Matter 桥接器配置节点 |
17
+ | KNX 网关 | 用于报文的 KNX 网关。**可选**:没有网关时,设备通过节点引脚以纯 Flow 模式工作。项目中只有一个网关时会自动选择 |
18
+ | 名称 | Alexa 等显示并用于语音命令的名称 |
19
+ | 设备类型 | Matter 设备类型(见下文);决定显示哪些组地址字段 |
20
+ | 启动时读取状态 | 启动时向状态 GA 发送 `GroupValue_Read`,以填充 Matter 属性 |
21
21
 
22
- ## 设备
23
-
24
- 每一行都是 Alexa 等显示的一个设备。选择**类型**,输入助手将使用的**名称**,然后填写组地址(带 ETS 自动补全):
22
+ ## 设备类型与组地址
25
23
 
26
24
  |类型|组地址|
27
25
  |--|--|
@@ -36,29 +34,20 @@ Matter Bridge 节点将 **KNX 组地址作为 Matter 设备**公开:Alexa、Go
36
34
  | 烟雾/CO 报警器 | 烟雾报警状态 GA + 可选 CO 报警状态 GA (DPT 1.005):手机上的紧急通知 |
37
35
  | 漏水探测器 | 漏水状态 GA (DPT 1.005) |
38
36
  | 空气质量传感器 (CO2) | CO2 状态 GA,ppm (DPT 9.008);空气质量等级(好/一般/中等/差...)自动推导 |
39
- | 扫地机器人 | **仅 Flow**:无组地址。启用节点引脚:助手的命令("开始清扫"、暂停/继续/回充)作为 `rvcmode`/`rvccommand` 到达输出;用 `msg.payload = { device, function: "rvcstate", value: "running"\|"docked"\|"charging"\|"paused"\|"error" }` 报告状态,用 `function: "rvcmode", value: "cleaning"\|"idle"` 报告模式。用任何 Node-RED 节点集成您的 Roomba/Roborock 并公开给 Alexa/Apple |
37
+ | 扫地机器人 | **仅 Flow**:无组地址。启用节点引脚:助手的命令("开始清扫"、暂停/继续/回充)作为 `rvcmode`/`rvccommand` 到达输出;用 `msg.payload = { function: "rvcstate", value: "running"\|"docked"\|"charging"\|"paused"\|"error" }` 报告状态,用 `function: "rvcmode", value: "cleaning"\|"idle"` 报告模式 |
40
38
 
41
39
  - **命令 GA**:助手发送命令时写入 KNX 总线。
42
40
  - **状态 GA**:从总线读取,保持 Matter 属性(和 App)更新。
43
41
 
44
- ## 配对
45
-
46
- 1. **部署**,等待几秒,然后重新打开节点。
47
- 2. 配对面板显示**二维码**和**手动配对码**:在 Alexa / Google Home / Apple Home 中扫描或输入("添加 Matter 设备")。
48
- 3. 多个控制器可以与同一个桥接器配对(Matter 多 fabric)。
49
-
50
- **重置配对**按钮会移除所有已配对的控制器并重新开始配对广播。
51
-
52
42
  ## 节点引脚
53
43
 
54
44
  如果启用节点的输入/输出引脚:
55
45
 
56
- - **输入**:从 Flow 更新虚拟设备的 Matter 状态,无需经过 KNX 总线:`msg.payload = { device: "厨房灯", function: "onoff", value: true }`(`device` 接受名称或内部 id;`function` 为 `onoff`、`level`、`position`、`temperature`、`humidity`、`illuminance`、`occupancy`、`contact`、`currenttemp`、`setpoint` 之一)。适合把 Flow 中计算的值(如虚拟传感器)公开给 Alexa 等。
57
- - **输出**:从 Matter 控制器收到的每个命令都转发到 Flow:`msg.topic` = 设备名称,`msg.payload` = 值,`msg.matter` = 原始命令。没有命令 GA 的设备成为**纯 Flow 设备**:命令到达您的 Flow,由您决定如何处理。
46
+ - **输入**:从 Flow 更新 Matter 状态,无需经过 KNX 总线:`msg.payload = { function: "onoff", value: true }`(`function` 为 `onoff`、`level`、`rgb`、`colortemp`、`position`、`temperature`、`humidity`、`illuminance`、`occupancy`、`contact`、`currenttemp`、`setpoint`、`fanspeed`、`smoke`、`co`、`leak`、`co2`、`rvcstate`、`rvcmode` 之一)。适合把 Flow 中计算的值(如虚拟传感器)公开给 Alexa 等。
47
+ - **输出**:从 Matter 控制器收到的每个命令都转发到 Flow:`msg.topic` = 设备名称,`msg.payload` = 值,`msg.matter` = 原始命令。没有命令 GA 的设备成为**纯 Flow 设备**。
58
48
 
59
49
  ## 说明
60
50
 
61
- - Node-RED 主机必须启用 **IPv6 link-local**(Matter 标准要求),并且控制器可在本地网络访问到它。
62
- - 桥接器身份保存在 Node-RED 用户目录的 `knxultimatestorage/matter` 中:重新部署**不**需要重新配对。
63
- - 设备重命名和新增会被控制器自动识别;移除的设备会从 App 中消失。
51
+ - 设备的 Matter 身份与此节点绑定:删除节点并新建一个后,App 会看到一个全新的设备。
52
+ - 添加/重命名/移除的设备节点会在几秒内被已配对的控制器识别,无需重新配对桥接器。
64
53
  </script>
@@ -69,7 +69,10 @@
69
69
  "reset_button": "重置配对",
70
70
  "reset_confirm": "移除所有已配对的控制器并重新开始配对?设备将从 Alexa/Google/Apple 中消失。",
71
71
  "reset_ok": "配对已重置。桥接器重新进入配对模式。"
72
- }
72
+ },
73
+ "bridge": "Matter 桥接器",
74
+ "device_name": "名称(Alexa 等显示)",
75
+ "device_help": "选择设备类型并输入语音助手将使用的名称,然后填写组地址。KNX 网关是可选的:没有网关时,启用节点引脚并从流程驱动设备。"
73
76
  },
74
77
  "common": {
75
78
  "ga": "GA",
@@ -0,0 +1,34 @@
1
+ <script type="text/markdown" data-help-name="matterbridge-config">
2
+ # Matter 桥接器 (BETA)
3
+
4
+ > 此节点处于 **BETA** 阶段:可以正常工作,但细节可能在版本之间发生变化。
5
+
6
+ ## 概述
7
+
8
+ 此配置节点就是 **Matter 桥接器本身**:它运行 Matter 服务器,Alexa、Google Home、Apple Home(或任何 Matter 控制器)只需配对**一次**。流程中的每个 **Matter Bridge device** 节点都指向这里,并在 App 中显示为桥接器的一个设备。
9
+
10
+ ## 配置
11
+
12
+ |字段|说明|
13
+ |--|--|
14
+ | 名称 | 此配置节点在 Node-RED 中的名称 |
15
+ | Matter 桥接器名称 | 桥接器本身在 Matter App 中显示的名称 |
16
+ | 端口 | Matter 服务器的 UDP 端口(默认 5540)。每个桥接器需要独立的端口,因此可以运行**多个独立的桥接器** |
17
+
18
+ ## 配对
19
+
20
+ 1. **部署**,等待几秒,然后重新打开此节点。
21
+ 2. 配对面板显示**二维码**和**手动配对码**:在 Alexa / Google Home / Apple Home 中扫描或输入("添加 Matter 设备")。
22
+ 3. 多个控制器可以与同一个桥接器配对(Matter 多 fabric)。
23
+
24
+ **重置配对**按钮会移除所有已配对的控制器并重新开始配对广播。
25
+
26
+ ## 身份与存储
27
+
28
+ 桥接器身份与此配置节点绑定,保存在 Node-RED 用户目录的 `knxultimatestorage/matter` 中:重新部署(即使更改端口或名称)**不**需要重新配对。只有删除此配置节点并新建一个才会改变身份——此时请从 Matter App 中移除旧桥接器并重新配对。
29
+
30
+ ## 说明
31
+
32
+ - Node-RED 主机必须启用 **IPv6 link-local**(Matter 标准要求),并且控制器可在本地网络访问到它。
33
+ - 添加/重命名/移除的设备节点会在几秒内被已配对的控制器识别,无需重新配对。
34
+ </script>
@@ -0,0 +1,20 @@
1
+ {
2
+ "matterbridge-config": {
3
+ "properties": {
4
+ "title": "Matter 桥接器",
5
+ "intro": "此配置节点就是 Matter 桥接器本身:Alexa、Google Home、Apple Home(或任何 Matter 控制器)配对一次即可看到你挂接的所有 KNX 设备。在流程中添加 \"Matter Bridge device\" 节点并指向这里。先部署,然后重新打开获取配对码。",
6
+ "node-config-input-name": "名称",
7
+ "bridge_name": "Matter 桥接器名称",
8
+ "port": "端口",
9
+ "pairing_title": "配对(Alexa、Google Home、Apple Home...)",
10
+ "not_running": "Matter 桥接器尚未运行。请先部署,等待几秒后点击刷新。",
11
+ "commissioned": "桥接器已与以下控制器配对:",
12
+ "awaiting": "等待配对。请在您的 Matter App 中扫描二维码或输入手动配对码:",
13
+ "qr_link": "在浏览器中打开二维码",
14
+ "deploy_first": "请先部署此配置节点,然后点击刷新。",
15
+ "reset_button": "重置配对",
16
+ "reset_confirm": "移除所有已配对的控制器并重新开始配对?设备将从 Alexa/Google/Apple 中消失。",
17
+ "reset_ok": "配对已重置。桥接器重新进入配对模式。"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,121 @@
1
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
2
+
3
+ <script type="text/javascript">
4
+ RED.nodes.registerType('matterbridge-config', {
5
+ category: 'config',
6
+ defaults: {
7
+ name: { value: '' },
8
+ bridgeDeviceName: { value: 'KNX-Ultimate Bridge' },
9
+ bridgePort: { value: 5540, required: true, validate: RED.validators.number() }
10
+ },
11
+ oneditprepare: function () {
12
+ try { RED.sidebar.show('help'); } catch (error) { }
13
+
14
+ var node = this;
15
+ var $pairingBody = $('#mbc-pairing-body');
16
+ var $refreshPairing = $('#mbc-refresh-pairing');
17
+ var $resetButton = $('#mbc-factory-reset');
18
+
19
+ var t = function (key) {
20
+ return RED._('node-red-contrib-knx-ultimate/matterbridge-config:matterbridge-config.properties.' + key);
21
+ };
22
+
23
+ function loadPairingInfo() {
24
+ $pairingBody.html('<i class="fa fa-circle-notch fa-spin"></i>');
25
+ $.getJSON('KNXUltimateMatterBridgeInfo?configId=' + node.id + '&_=' + Date.now(), function (info) {
26
+ $pairingBody.empty();
27
+ if (info.error) {
28
+ $pairingBody.append($('<p style="color:#999;"></p>').text(info.error));
29
+ return;
30
+ }
31
+ if (!info.running) {
32
+ $pairingBody.append($('<p style="color:#999;"></p>').text(t('not_running')));
33
+ return;
34
+ }
35
+ if (info.commissioned) {
36
+ $pairingBody.append($('<p style="color:#1b7d33;"></p>').text(t('commissioned') + ' (' + info.fabrics.length + ')'));
37
+ info.fabrics.forEach(function (f) {
38
+ $pairingBody.append($('<div style="margin-left:10px;"></div>').text('• ' + (f.label || 'Controller') + ' (vendor ' + f.vendorId + ')'));
39
+ });
40
+ $resetButton.show();
41
+ return;
42
+ }
43
+ $pairingBody.append($('<p style="color:#b8860b;"></p>').text(t('awaiting')));
44
+ if (info.manualPairingCode) {
45
+ $pairingBody.append($('<p></p>').append($('<b style="font-size:18px; letter-spacing:2px;"></b>').text(info.manualPairingCode)));
46
+ }
47
+ if (info.qrAscii) {
48
+ $pairingBody.append($('<pre style="font-family:monospace; line-height:1.05; font-size:7px; display:inline-block; padding:6px; background:#fff; color:#000;"></pre>').text(info.qrAscii));
49
+ }
50
+ if (info.qrPairingCode) {
51
+ var url = 'https://project-chip.github.io/connectedhomeip/qrcode.html?data=' + encodeURIComponent(info.qrPairingCode);
52
+ $pairingBody.append($('<p></p>').append($('<a target="_blank"></a>').attr('href', url).text(t('qr_link'))));
53
+ }
54
+ $resetButton.hide();
55
+ }).fail(function () {
56
+ $pairingBody.empty();
57
+ $pairingBody.append($('<p style="color:#999;"></p>').text(t('deploy_first')));
58
+ });
59
+ }
60
+
61
+ $refreshPairing.on('click', function () { loadPairingInfo(); });
62
+ $resetButton.on('click', function () {
63
+ if (!window.confirm(t('reset_confirm'))) return;
64
+ $resetButton.prop('disabled', true);
65
+ $.getJSON('KNXUltimateMatterBridgeReset?configId=' + node.id, function (data) {
66
+ if (data && data.error) RED.notify(data.error, { type: 'error' });
67
+ else RED.notify(t('reset_ok'), { type: 'success' });
68
+ loadPairingInfo();
69
+ }).always(function () { $resetButton.prop('disabled', false); });
70
+ });
71
+
72
+ loadPairingInfo();
73
+ },
74
+ oneditsave: function () {
75
+ try { RED.sidebar.show('info'); } catch (error) { }
76
+ },
77
+ label: function () {
78
+ return this.name || this.bridgeDeviceName || 'Matter Bridge';
79
+ }
80
+ });
81
+ </script>
82
+
83
+ <script type="text/html" data-template-name="matterbridge-config">
84
+ <div class="form-row" style="margin-bottom:10px;">
85
+ <span style="color:#ff0000"><i class="fa fa-youtube"></i></span>&nbsp;<a target="_blank" href="https://www.youtube.com/playlist?list=PL9Yh1bjbLAYrU8PsVhW4xzEug2WtVFv3E"><b>KNX-Ultimate video tutorials (YouTube playlist)</b></a>
86
+ </div>
87
+
88
+ <div class="form-row">
89
+ <b><span data-i18n="matterbridge-config.properties.title"></span></b>
90
+ <p><span data-i18n="matterbridge-config.properties.intro"></span></p>
91
+ </div>
92
+
93
+ <div class="form-row">
94
+ <label for="node-config-input-name">
95
+ <i class="fa fa-tag"></i> <span data-i18n="matterbridge-config.properties.node-config-input-name"></span>
96
+ </label>
97
+ <input type="text" id="node-config-input-name">
98
+ </div>
99
+
100
+ <div class="form-row">
101
+ <label for="node-config-input-bridgeDeviceName" style="width:180px;">
102
+ <i class="fa fa-share-alt"></i> <span data-i18n="matterbridge-config.properties.bridge_name"></span>
103
+ </label>
104
+ <input type="text" id="node-config-input-bridgeDeviceName" style="width:220px;">
105
+ <label for="node-config-input-bridgePort" style="width:60px; text-align:right;"><span data-i18n="matterbridge-config.properties.port"></span></label>
106
+ <input type="number" id="node-config-input-bridgePort" style="width:90px;">
107
+ </div>
108
+
109
+ <hr/>
110
+
111
+ <div class="form-row">
112
+ <b><i class="fa fa-qrcode"></i>&nbsp;<span data-i18n="matterbridge-config.properties.pairing_title"></span></b>
113
+ <button type="button" id="mbc-refresh-pairing" class="red-ui-button" style="margin-left:6px; color:#1b7d33; border-color:#1b7d33;">
114
+ <i class="fa fa-refresh"></i>
115
+ </button>
116
+ <button type="button" id="mbc-factory-reset" class="red-ui-button" style="margin-left:6px; color:#c00; border-color:#c00; display:none;">
117
+ <i class="fa fa-trash"></i>&nbsp;<span data-i18n="matterbridge-config.properties.reset_button"></span>
118
+ </button>
119
+ </div>
120
+ <div class="form-row" id="mbc-pairing-body" style="text-align:center;"></div>
121
+ </script>
@@ -0,0 +1,220 @@
1
+ /* eslint-disable max-len */
2
+ const path = require('path')
3
+
4
+ // 10/09/2024 Setup the color logger
5
+ const loggerSetup = (options) => {
6
+ const clog = require('node-color-log').createNamedLogger(options.setPrefix)
7
+ clog.setLevel(options.loglevel)
8
+ clog.setDate(() => (new Date()).toLocaleString())
9
+ return clog
10
+ }
11
+
12
+ // The running Matter servers, keyed by config node id. Kept OUTSIDE the node lifecycle:
13
+ // a re-deploy must NOT restart the Matter server, otherwise the paired controllers
14
+ // resume their session without re-reading the structure and never see device changes.
15
+ // The devices are reconciled live instead; the server is closed only when the config
16
+ // node is deleted/disabled.
17
+ const runningBridges = new Map()
18
+
19
+ module.exports = (RED) => {
20
+ function matterBridgeConfig (config) {
21
+ RED.nodes.createNode(this, config)
22
+ const node = this
23
+ node.loglevel = config.loglevel !== undefined ? config.loglevel : 'error'
24
+ node.sysLogger = null
25
+ try {
26
+ node.sysLogger = loggerSetup({ loglevel: node.loglevel, setPrefix: 'matterbridge-config.js' })
27
+ } catch (error) { console.log(error.stack) }
28
+
29
+ node.name = config.name === undefined || config.name === '' ? 'Matter Bridge' : config.name
30
+ node.bridgePort = Number(config.bridgePort) || 5540
31
+ node.bridgeDeviceName = config.bridgeDeviceName === undefined || config.bridgeDeviceName === '' ? 'KNX-Ultimate Bridge' : config.bridgeDeviceName
32
+ node.matterInstanceId = `knxultimate-bridge-${node.id.replace(/[^a-zA-Z0-9]/g, '')}`
33
+ node.matterStoragePath = path.join(RED.settings.userDir || '.', 'knxultimatestorage', 'matter')
34
+
35
+ node.matterBridge = null
36
+ node.deviceClients = new Map() // matterDeviceId -> device node
37
+ let startupTimer = null
38
+ let reconcileTimer = null
39
+ let closing = false
40
+
41
+ Object.defineProperty(node, 'linkStatus', {
42
+ get: function () {
43
+ return node.matterBridge?.bridgeStatus === 'running' ? 'running' : 'stopped'
44
+ }
45
+ })
46
+
47
+ const safeClientCall = (client, fn, label) => {
48
+ try {
49
+ if (!client || typeof fn !== 'function') return
50
+ fn()
51
+ } catch (error) {
52
+ node.sysLogger?.warn(`Matter bridge client ${label} error: ${error.message}`)
53
+ }
54
+ }
55
+
56
+ // Desired device definitions built from the currently registered device nodes.
57
+ const buildDeviceDefs = () => {
58
+ const defs = []
59
+ node.deviceClients.forEach((client) => {
60
+ try {
61
+ const def = typeof client.getMatterDef === 'function' ? client.getMatterDef() : undefined
62
+ if (def && def.id && def.type) defs.push(def)
63
+ } catch (error) { /* empty */ }
64
+ })
65
+ return defs
66
+ }
67
+
68
+ // Notify every registered device node that the bridge status changed.
69
+ const notifyClientsStatus = () => {
70
+ node.deviceClients.forEach((client) => {
71
+ safeClientCall(client, () => client.handleBridgeStatus(), 'handleBridgeStatus')
72
+ })
73
+ }
74
+
75
+ const bindEngineEvents = (engine) => {
76
+ engine.removeAllListeners()
77
+ // Matter -> KNX: a controller (Alexa...) sent a command to a bridged device.
78
+ // Route it to the device node that owns that Matter device.
79
+ engine.on('command', (command) => {
80
+ try {
81
+ const client = node.deviceClients.get(command.deviceId)
82
+ if (client === undefined) return
83
+ safeClientCall(client, () => client.handleMatterCommand(command), 'handleMatterCommand')
84
+ } catch (error) {
85
+ node.sysLogger?.warn(`matterbridge-config: command routing error: ${error.message}`)
86
+ }
87
+ })
88
+ engine.on('commissioned', () => notifyClientsStatus())
89
+ engine.on('decommissioned', () => notifyClientsStatus())
90
+ engine.on('fabricsChanged', () => notifyClientsStatus())
91
+ engine.on('online', () => notifyClientsStatus())
92
+ }
93
+
94
+ // Reconcile the exposed devices with the currently registered device nodes.
95
+ const scheduleReconcile = () => {
96
+ if (reconcileTimer !== null) clearTimeout(reconcileTimer)
97
+ reconcileTimer = setTimeout(() => {
98
+ (async () => {
99
+ try {
100
+ if (node.matterBridge === null || node.matterBridge.bridgeStatus !== 'running') return
101
+ await node.matterBridge.reconcileDevices(buildDeviceDefs())
102
+ notifyClientsStatus()
103
+ } catch (error) {
104
+ node.sysLogger?.warn(`matterbridge-config: reconcile error: ${error.message}`)
105
+ }
106
+ })()
107
+ }, 1500)
108
+ }
109
+
110
+ const ensureEngineStarted = async () => {
111
+ try {
112
+ const { classMatterBridge } = await import('./utils/matterBridgeEngine.mjs')
113
+ const existing = runningBridges.get(node.id)
114
+
115
+ if (existing !== undefined && existing.bridgeStatus === 'running' &&
116
+ Number(existing.options.port) === node.bridgePort &&
117
+ existing.options.deviceName === node.bridgeDeviceName) {
118
+ // Re-deploy: reuse the running Matter server and reconcile the devices live.
119
+ node.matterBridge = existing
120
+ bindEngineEvents(existing)
121
+ await existing.reconcileDevices(buildDeviceDefs())
122
+ notifyClientsStatus()
123
+ return
124
+ }
125
+
126
+ if (existing !== undefined) {
127
+ // Port or bridge name changed: a full restart is unavoidable.
128
+ try {
129
+ existing.removeAllListeners()
130
+ await existing.close()
131
+ } catch (error) { /* empty */ }
132
+ runningBridges.delete(node.id)
133
+ }
134
+
135
+ const engine = new classMatterBridge(node.matterStoragePath, node.matterInstanceId, {
136
+ info: (m) => node.sysLogger?.info(m),
137
+ warn: (m) => node.sysLogger?.warn(m),
138
+ error: (m) => node.sysLogger?.error(m),
139
+ debug: (m) => node.sysLogger?.debug(m)
140
+ }, { port: node.bridgePort, deviceName: node.bridgeDeviceName })
141
+ runningBridges.set(node.id, engine)
142
+ node.matterBridge = engine
143
+ bindEngineEvents(engine)
144
+ await engine.start(buildDeviceDefs())
145
+ notifyClientsStatus()
146
+ } catch (error) {
147
+ node.sysLogger?.error(`matterbridge-config: ensureEngineStarted error: ${error.message}`)
148
+ }
149
+ }
150
+
151
+ // The Matter server is started shortly after boot, giving the device nodes the
152
+ // time to register themselves during flow initialization.
153
+ startupTimer = setTimeout(() => {
154
+ (async () => {
155
+ if (closing) return
156
+ await ensureEngineStarted()
157
+ })()
158
+ }, 5000)
159
+
160
+ // Functions called from the device nodes -----------------------------------------
161
+ node.registerDevice = (client) => {
162
+ try {
163
+ if (!client || !client.matterDeviceId) return
164
+ node.deviceClients.set(client.matterDeviceId, client)
165
+ scheduleReconcile()
166
+ } catch (error) {
167
+ node.sysLogger?.warn(`matterbridge-config: registerDevice error: ${error.message}`)
168
+ }
169
+ }
170
+
171
+ node.unregisterDevice = (client) => {
172
+ try {
173
+ if (!client || !client.matterDeviceId) return
174
+ node.deviceClients.delete(client.matterDeviceId)
175
+ scheduleReconcile()
176
+ } catch (error) { /* empty */ }
177
+ }
178
+
179
+ node.setDeviceState = async (deviceId, fn, value) => {
180
+ if (node.matterBridge === null) return false
181
+ return node.matterBridge.setDeviceState(deviceId, fn, value)
182
+ }
183
+
184
+ node.getPairingInfo = () => {
185
+ if (node.matterBridge === null) return { running: false }
186
+ return node.matterBridge.getPairingInfo()
187
+ }
188
+
189
+ node.factoryResetBridge = async () => {
190
+ if (node.matterBridge === null) throw new Error('Matter bridge not started')
191
+ await node.matterBridge.factoryReset()
192
+ notifyClientsStatus()
193
+ }
194
+ // END functions called from the device nodes -------------------------------------
195
+
196
+ node.on('close', (removed, done) => {
197
+ closing = true
198
+ try {
199
+ if (startupTimer !== null) clearTimeout(startupTimer)
200
+ if (reconcileTimer !== null) clearTimeout(reconcileTimer)
201
+ } catch (error) { /* empty */ }
202
+ try {
203
+ if (node.matterBridge !== null) node.matterBridge.removeAllListeners()
204
+ } catch (error) { /* empty */ }
205
+ (async () => {
206
+ try {
207
+ if (removed === true && node.matterBridge !== null) {
208
+ await node.matterBridge.close()
209
+ runningBridges.delete(node.id)
210
+ }
211
+ } catch (error) { /* empty */ }
212
+ node.matterBridge = null
213
+ node.deviceClients.clear()
214
+ node.sysLogger = null
215
+ done()
216
+ })()
217
+ })
218
+ }
219
+ RED.nodes.registerType('matterbridge-config', matterBridgeConfig)
220
+ }