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

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 (50) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/nodes/commonFunctions.js +93 -0
  3. package/nodes/icons/node-matter-icon.svg +12 -0
  4. package/nodes/knxUltimateMatterBridge.html +429 -0
  5. package/nodes/knxUltimateMatterBridge.js +456 -0
  6. package/nodes/knxUltimateMatterDevice.html +586 -0
  7. package/nodes/knxUltimateMatterDevice.js +308 -0
  8. package/nodes/locales/de/knxUltimateMatterBridge.html +64 -0
  9. package/nodes/locales/de/knxUltimateMatterBridge.json +80 -0
  10. package/nodes/locales/de/knxUltimateMatterDevice.html +56 -0
  11. package/nodes/locales/de/knxUltimateMatterDevice.json +109 -0
  12. package/nodes/locales/de/matter-config.html +25 -0
  13. package/nodes/locales/de/matter-config.json +26 -0
  14. package/nodes/locales/en/knxUltimateMatterBridge.html +64 -0
  15. package/nodes/locales/en/knxUltimateMatterBridge.json +80 -0
  16. package/nodes/locales/en/knxUltimateMatterDevice.html +56 -0
  17. package/nodes/locales/en/knxUltimateMatterDevice.json +109 -0
  18. package/nodes/locales/en/matter-config.html +25 -0
  19. package/nodes/locales/en/matter-config.json +26 -0
  20. package/nodes/locales/es/knxUltimateMatterBridge.html +64 -0
  21. package/nodes/locales/es/knxUltimateMatterBridge.json +80 -0
  22. package/nodes/locales/es/knxUltimateMatterDevice.html +56 -0
  23. package/nodes/locales/es/knxUltimateMatterDevice.json +109 -0
  24. package/nodes/locales/es/matter-config.html +25 -0
  25. package/nodes/locales/es/matter-config.json +26 -0
  26. package/nodes/locales/fr/knxUltimateMatterBridge.html +64 -0
  27. package/nodes/locales/fr/knxUltimateMatterBridge.json +80 -0
  28. package/nodes/locales/fr/knxUltimateMatterDevice.html +56 -0
  29. package/nodes/locales/fr/knxUltimateMatterDevice.json +109 -0
  30. package/nodes/locales/fr/matter-config.html +25 -0
  31. package/nodes/locales/fr/matter-config.json +26 -0
  32. package/nodes/locales/it/knxUltimateMatterBridge.html +64 -0
  33. package/nodes/locales/it/knxUltimateMatterBridge.json +80 -0
  34. package/nodes/locales/it/knxUltimateMatterDevice.html +56 -0
  35. package/nodes/locales/it/knxUltimateMatterDevice.json +109 -0
  36. package/nodes/locales/it/matter-config.html +25 -0
  37. package/nodes/locales/it/matter-config.json +26 -0
  38. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +64 -0
  39. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +80 -0
  40. package/nodes/locales/zh-CN/knxUltimateMatterDevice.html +56 -0
  41. package/nodes/locales/zh-CN/knxUltimateMatterDevice.json +109 -0
  42. package/nodes/locales/zh-CN/matter-config.html +25 -0
  43. package/nodes/locales/zh-CN/matter-config.json +26 -0
  44. package/nodes/matter-config.html +226 -0
  45. package/nodes/matter-config.js +242 -0
  46. package/nodes/utils/matterBridgeDeviceFactory.mjs +511 -0
  47. package/nodes/utils/matterBridgeEngine.mjs +290 -0
  48. package/nodes/utils/matterEngine.mjs +475 -0
  49. package/nodes/utils/matterKnxConverter.js +204 -0
  50. package/package.json +7 -2
@@ -0,0 +1,26 @@
1
+ {
2
+ "matter-config": {
3
+ "properties": {
4
+ "title": "Matter 控制器",
5
+ "intro": "此节点是一个 Matter 控制器(拥有自己的 fabric)。在此配对您的 Matter 设备,然后使用 \"Matter Device\" 节点将其映射到 KNX 组地址。请先部署该节点,然后回到这里配对设备。",
6
+ "node-config-input-name": "名称",
7
+ "fabric_label": "Fabric 标签",
8
+ "pairing_title": "配对新设备",
9
+ "pairing_help": "输入 11 位手动配对码(例如 3497-011-2332)或二维码内容(MT:....)。设备必须可通过 IP 网络访问(WiFi/以太网或通过边界路由器的 Thread)。如果设备已与其他控制器(Alexa、Google、Apple)配对,请使用其\"与其他中枢共享/配对\"功能获取新的配对码。",
10
+ "pairing_code": "配对码",
11
+ "pair_button": "配对",
12
+ "devices_title": "已配对设备",
13
+ "col_name": "名称",
14
+ "col_product": "产品",
15
+ "col_state": "状态",
16
+ "no_devices": "尚未配对任何设备。",
17
+ "unpair_confirm": "移除(解除配对)设备",
18
+ "unpair_ok": "设备已移除。",
19
+ "deploy_first": "请先部署此配置节点,然后重试。",
20
+ "pairing_code_missing": "请输入配对码。",
21
+ "pairing_wait": "正在配对,最多可能需要一分钟...",
22
+ "pairing_failed": "配对失败",
23
+ "pairing_ok": "设备配对成功!"
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,226 @@
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('matter-config', {
5
+ category: 'config',
6
+ defaults: {
7
+ name: { value: "Matter Controller" },
8
+ fabricLabel: { value: "KNX-Ultimate" }
9
+ },
10
+ oneditprepare: function () {
11
+ // Go to the help panel
12
+ try {
13
+ RED.sidebar.show("help");
14
+ } catch (error) { }
15
+
16
+ var node = this;
17
+ var $pairButton = $("#matterPairButton");
18
+ var $pairingCode = $("#matter-pairing-code");
19
+ var $devicesBody = $("#matter-devices-body");
20
+ var $refreshDevices = $("#matterRefreshDevices");
21
+ var $pairSpinner = $("#matterPairSpinner");
22
+
23
+ function renderDevices(devices) {
24
+ $devicesBody.empty();
25
+ if (!Array.isArray(devices) || devices.length === 0) {
26
+ $devicesBody.append('<tr><td colspan="5" style="text-align:center; color:#999;">' + RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.no_devices") + '</td></tr>');
27
+ return;
28
+ }
29
+ devices.forEach(function (dev) {
30
+ var stateColor = dev.connectionState === 'connected' ? '#1b7d33' : '#c00';
31
+ var $tr = $('<tr></tr>');
32
+ $tr.append($('<td></td>').text(dev.name || ''));
33
+ $tr.append($('<td></td>').text((dev.vendorName || '') + (dev.productName ? ' ' + dev.productName : '')));
34
+ $tr.append($('<td style="font-family:monospace;"></td>').text(dev.nodeId));
35
+ $tr.append($('<td></td>').append($('<span></span>').css('color', stateColor).text(dev.connectionState)));
36
+ var $unpair = $('<button type="button" class="red-ui-button" style="color:#c00; border-color:#c00;"><i class="fa fa-trash"></i></button>');
37
+ $unpair.on('click', function () {
38
+ var confirmMsg = RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.unpair_confirm") + ' "' + (dev.name || dev.nodeId) + '"?';
39
+ if (!window.confirm(confirmMsg)) return;
40
+ $unpair.prop('disabled', true);
41
+ $.getJSON('KNXUltimateMatterUnpair?serverId=' + node.id + '&nodeId=' + encodeURIComponent(dev.nodeId), function (data) {
42
+ if (data && data.error) {
43
+ RED.notify(data.error, { type: 'error' });
44
+ } else {
45
+ RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.unpair_ok"), { type: 'success' });
46
+ }
47
+ loadDevices();
48
+ }).fail(function (jqXHR, textStatus, errorThrown) {
49
+ RED.notify('Unpair failed: ' + (errorThrown || textStatus), { type: 'error' });
50
+ $unpair.prop('disabled', false);
51
+ });
52
+ });
53
+ $tr.append($('<td style="text-align:right;"></td>').append($unpair));
54
+ $devicesBody.append($tr);
55
+ });
56
+ }
57
+
58
+ function loadDevices() {
59
+ $devicesBody.empty();
60
+ $devicesBody.append('<tr><td colspan="5" style="text-align:center;"><i class="fa fa-circle-notch fa-spin"></i></td></tr>');
61
+ $.getJSON('KNXUltimateMatterGetNodes?serverId=' + node.id + '&_=' + Date.now(), function (data) {
62
+ if (data && data.error) {
63
+ $devicesBody.empty();
64
+ $devicesBody.append('<tr><td colspan="5" style="text-align:center; color:#c00;">' + data.error + '</td></tr>');
65
+ return;
66
+ }
67
+ renderDevices(data && data.devices ? data.devices : []);
68
+ }).fail(function () {
69
+ $devicesBody.empty();
70
+ $devicesBody.append('<tr><td colspan="5" style="text-align:center; color:#999;">' + RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.deploy_first") + '</td></tr>');
71
+ });
72
+ }
73
+
74
+ $refreshDevices.on('click', function () {
75
+ loadDevices();
76
+ });
77
+
78
+ $pairButton.on('click', function () {
79
+ var code = ($pairingCode.val() || '').trim();
80
+ if (code === '') {
81
+ RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.pairing_code_missing"), { type: 'warning' });
82
+ return;
83
+ }
84
+ $pairButton.prop('disabled', true);
85
+ $pairSpinner.show();
86
+ var myNotification = RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.pairing_wait"), {
87
+ modal: false,
88
+ fixed: true,
89
+ type: 'info'
90
+ });
91
+ $.ajax({
92
+ url: 'KNXUltimateMatterPair?serverId=' + node.id + '&code=' + encodeURIComponent(code),
93
+ dataType: 'json',
94
+ timeout: 180000
95
+ }).done(function (data) {
96
+ if (data && data.error) {
97
+ RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.pairing_failed") + ': ' + data.error, { type: 'error', timeout: 8000 });
98
+ } else {
99
+ RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.pairing_ok") + ' (Node ID ' + data.nodeId + ')', { type: 'success' });
100
+ $pairingCode.val('');
101
+ loadDevices();
102
+ }
103
+ }).fail(function (jqXHR, textStatus, errorThrown) {
104
+ var msg = '';
105
+ try { msg = jqXHR.responseJSON.error; } catch (e) { msg = errorThrown || textStatus; }
106
+ RED.notify(RED._("node-red-contrib-knx-ultimate/matter-config:matter-config.properties.pairing_failed") + ': ' + msg, { type: 'error', timeout: 8000 });
107
+ }).always(function () {
108
+ if (myNotification) myNotification.close();
109
+ $pairButton.prop('disabled', false);
110
+ $pairSpinner.hide();
111
+ });
112
+ });
113
+
114
+ loadDevices();
115
+ },
116
+ oneditsave: function () {
117
+ // Return to the info tab
118
+ try {
119
+ RED.sidebar.show("info");
120
+ } catch (error) { }
121
+ },
122
+ label: function () {
123
+ return this.name || "Matter Controller";
124
+ }
125
+ });
126
+ </script>
127
+
128
+ <script type="text/html" data-template-name="matter-config">
129
+ <div class="form-row" style="margin-bottom:10px;">
130
+ <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>
131
+ </div>
132
+
133
+ <div class="form-row">
134
+ <b><span data-i18n="matter-config.properties.title"></span></b>
135
+ <p align="center" style="margin:8px 0;">
136
+ <svg width="70" height="77" viewBox="0 8 40 44" xmlns="http://www.w3.org/2000/svg" aria-label="Matter" style="vertical-align:middle;">
137
+ <g fill="currentColor" stroke="none">
138
+ <circle cx="20" cy="30" r="4.5"/>
139
+ <g>
140
+ <rect x="18.4" y="9" width="3.2" height="11"/>
141
+ <path d="M 14.5 18.5 L 25.5 18.5 L 20 25.5 Z"/>
142
+ </g>
143
+ <g transform="rotate(120 20 30)">
144
+ <rect x="18.4" y="9" width="3.2" height="11"/>
145
+ <path d="M 14.5 18.5 L 25.5 18.5 L 20 25.5 Z"/>
146
+ </g>
147
+ <g transform="rotate(240 20 30)">
148
+ <rect x="18.4" y="9" width="3.2" height="11"/>
149
+ <path d="M 14.5 18.5 L 25.5 18.5 L 20 25.5 Z"/>
150
+ </g>
151
+ </g>
152
+ </svg>
153
+ <br/>
154
+ <span style="font-size:18px; font-weight:600; letter-spacing:1px;">matter</span>
155
+ </p>
156
+ <p><span data-i18n="matter-config.properties.intro"></span></p>
157
+ </div>
158
+
159
+ <div class="form-row">
160
+ <label for="node-config-input-name">
161
+ <i class="fa fa-tag"></i>
162
+ <span data-i18n="matter-config.properties.node-config-input-name"></span>
163
+ </label>
164
+ <input type="text" id="node-config-input-name">
165
+ </div>
166
+
167
+ <div class="form-row">
168
+ <label for="node-config-input-fabricLabel">
169
+ <i class="fa fa-id-card"></i>
170
+ <span data-i18n="matter-config.properties.fabric_label"></span>
171
+ </label>
172
+ <input type="text" id="node-config-input-fabricLabel" placeholder="KNX-Ultimate">
173
+ </div>
174
+
175
+ <hr/>
176
+
177
+ <div class="form-row">
178
+ <b><i class="fa fa-qrcode"></i>&nbsp;<span data-i18n="matter-config.properties.pairing_title"></span></b>
179
+ <p><span data-i18n="matter-config.properties.pairing_help"></span></p>
180
+ </div>
181
+
182
+ <div class="form-row">
183
+ <label for="matter-pairing-code">
184
+ <i class="fa fa-key"></i>
185
+ <span data-i18n="matter-config.properties.pairing_code"></span>
186
+ </label>
187
+ <input type="text" id="matter-pairing-code" placeholder="3497-011-2332 or MT:Y.K90...." style="width:250px;">
188
+ <button type="button" id="matterPairButton" class="red-ui-button" style="margin-left:6px; color:#1b7d33; border-color:#1b7d33;">
189
+ <i class="fa fa-link"></i>&nbsp;<span data-i18n="matter-config.properties.pair_button"></span>
190
+ </button>
191
+ <span id="matterPairSpinner" style="margin-left:6px; display:none; color:#1b7d33;">
192
+ <i class="fa fa-circle-notch fa-spin"></i>
193
+ </span>
194
+ </div>
195
+
196
+ <hr/>
197
+
198
+ <div class="form-row">
199
+ <b><i class="fa fa-microchip"></i>&nbsp;<span data-i18n="matter-config.properties.devices_title"></span></b>
200
+ <button type="button" id="matterRefreshDevices" class="red-ui-button" style="margin-left:6px; color:#1b7d33; border-color:#1b7d33;">
201
+ <i class="fa fa-refresh"></i>
202
+ </button>
203
+ </div>
204
+
205
+ <div class="form-row">
206
+ <table style="width:100%; border-collapse:collapse;" class="red-ui-editableList">
207
+ <thead>
208
+ <tr style="border-bottom:1px solid #ccc; text-align:left;">
209
+ <th data-i18n="matter-config.properties.col_name"></th>
210
+ <th data-i18n="matter-config.properties.col_product"></th>
211
+ <th>Node ID</th>
212
+ <th data-i18n="matter-config.properties.col_state"></th>
213
+ <th></th>
214
+ </tr>
215
+ </thead>
216
+ <tbody id="matter-devices-body"></tbody>
217
+ </table>
218
+ </div>
219
+
220
+ <br/>
221
+ <br/>
222
+ <br/>
223
+ <br/>
224
+ <br/>
225
+ <br/>
226
+ </script>
@@ -0,0 +1,242 @@
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
+ module.exports = (RED) => {
13
+ function matterConfig (config) {
14
+ RED.nodes.createNode(this, config)
15
+ const node = this
16
+ node.nodeClients = [] // Stores the registered clients
17
+ node.loglevel = config.loglevel !== undefined ? config.loglevel : 'error'
18
+ node.sysLogger = null
19
+ node.matterManager = null
20
+ node.timerMatterConfigCheckState = null
21
+ try {
22
+ node.sysLogger = loggerSetup({ loglevel: node.loglevel, setPrefix: 'matter-config.js' })
23
+ } catch (error) { console.log(error.stack) }
24
+ node.name = config.name === undefined || config.name === '' ? 'Matter Controller' : config.name
25
+ node.fabricLabel = config.fabricLabel === undefined || config.fabricLabel === '' ? 'KNX-Ultimate' : config.fabricLabel
26
+ // Stable, filesystem-safe identity of the controller (fabric), bound to this config node.
27
+ node.matterInstanceId = `knxultimate-matter-${node.id.replace(/[^a-zA-Z0-9]/g, '')}`
28
+ node.matterStoragePath = path.join(RED.settings.userDir || '.', 'knxultimatestorage', 'matter')
29
+
30
+ // Helper like the hue-config one
31
+ Object.defineProperty(node, 'linkStatus', {
32
+ get: function () {
33
+ return node.matterManager?.matterConnectionStatus ?? 'disconnected'
34
+ }
35
+ })
36
+
37
+ const safeClientCall = (client, fn, label) => {
38
+ try {
39
+ if (!client || typeof fn !== 'function') return
40
+ fn()
41
+ } catch (error) {
42
+ node.sysLogger?.warn(`Matter client ${label} error: ${error.message}`)
43
+ }
44
+ }
45
+
46
+ node.initMatterConnection = async () => {
47
+ try {
48
+ if (node.matterManager !== null) await node.matterManager.close()
49
+ } catch (error) { /* empty */ }
50
+ try {
51
+ const { classMatter } = await import('./utils/matterEngine.mjs')
52
+ node.matterManager = new classMatter(node.matterStoragePath, node.matterInstanceId, node.fabricLabel, node.sysLogger)
53
+ } catch (error) {
54
+ node.sysLogger?.error(`Errore matter-config: node.initMatterConnection: ${error.message}`)
55
+ throw (error)
56
+ }
57
+
58
+ // Attribute changes coming from the Matter devices
59
+ node.matterManager.on('event', (_event) => {
60
+ node.nodeClients.forEach((_oClient) => {
61
+ try {
62
+ if (_oClient.handleSendMatter !== undefined) _oClient.handleSendMatter(_event)
63
+ } catch (error) {
64
+ node.sysLogger?.error(`Errore node.matterManager.on(event): ${error.message}`)
65
+ }
66
+ })
67
+ })
68
+
69
+ // Cluster events (button presses etc.)
70
+ node.matterManager.on('matterEvent', (_event) => {
71
+ node.nodeClients.forEach((_oClient) => {
72
+ try {
73
+ if (_oClient.handleMatterClusterEvent !== undefined) _oClient.handleMatterClusterEvent(_event)
74
+ } catch (error) {
75
+ node.sysLogger?.error(`Errore node.matterManager.on(matterEvent): ${error.message}`)
76
+ }
77
+ })
78
+ })
79
+
80
+ // A paired node completed the initial sync: let the clients do their initial read
81
+ node.matterManager.on('nodeInitialized', (_nodeId) => {
82
+ node.nodeClients.forEach((_oClient) => {
83
+ if (_oClient.matterNodeId === _nodeId) {
84
+ safeClientCall(_oClient, () => _oClient.handleMatterNodeInitialized(), 'handleMatterNodeInitialized')
85
+ }
86
+ })
87
+ })
88
+
89
+ // Connection state of a single paired device changed
90
+ node.matterManager.on('nodeState', (_nodeId, _state) => {
91
+ node.nodeClients.forEach((_oClient) => {
92
+ if (_oClient.matterNodeId === _nodeId) {
93
+ safeClientCall(_oClient, () => _oClient.setNodeStatusMatter({
94
+ fill: _state === 'connected' ? 'green' : 'yellow',
95
+ shape: _state === 'connected' ? 'dot' : 'ring',
96
+ text: _state,
97
+ payload: ''
98
+ }), 'setNodeStatusMatter')
99
+ if (_state === 'connected') {
100
+ safeClientCall(_oClient, () => _oClient.handleMatterNodeInitialized(), 'handleMatterNodeInitialized')
101
+ }
102
+ }
103
+ })
104
+ })
105
+
106
+ node.matterManager.on('connected', () => {
107
+ node.nodeClients.forEach((_oClient) => {
108
+ safeClientCall(_oClient, () => _oClient.setNodeStatusMatter({
109
+ fill: 'green',
110
+ shape: 'ring',
111
+ text: 'Controller ready',
112
+ payload: ''
113
+ }), 'setNodeStatusMatter')
114
+ })
115
+ })
116
+
117
+ node.matterManager.on('disconnected', () => {
118
+ node.nodeClients.forEach((_oClient) => {
119
+ safeClientCall(_oClient, () => _oClient.setNodeStatusMatter({
120
+ fill: 'red',
121
+ shape: 'ring',
122
+ text: 'Matter Disconnected',
123
+ payload: ''
124
+ }), 'setNodeStatusMatter')
125
+ })
126
+ })
127
+
128
+ try {
129
+ await node.matterManager.Connect()
130
+ } catch (error) {
131
+ node.sysLogger?.error(`Errore matter-config: Connect: ${error.message}`)
132
+ }
133
+ }
134
+
135
+ node.startWatchdogTimer = async () => {
136
+ if (node.timerMatterConfigCheckState !== null) clearTimeout(node.timerMatterConfigCheckState)
137
+ node.timerMatterConfigCheckState = setTimeout(() => {
138
+ (async () => {
139
+ if (node.linkStatus === 'disconnected') {
140
+ try {
141
+ await node.initMatterConnection()
142
+ } catch (error) {
143
+ node.sysLogger?.error(`Errore matter-config: node.startWatchdogTimer: ${error.message}`)
144
+ }
145
+ }
146
+ await node.startWatchdogTimer()
147
+ })()
148
+ }, 60000)
149
+ }
150
+
151
+ setTimeout(() => {
152
+ (async () => {
153
+ try {
154
+ await node.initMatterConnection()
155
+ } catch (error) {
156
+ // Never let a failed startup (missing dependency, corrupted storage...) become
157
+ // an unhandled rejection: the watchdog will retry the connection later.
158
+ node.sysLogger?.error(`matter-config: startup error: ${error.message}`)
159
+ }
160
+ try {
161
+ node.startWatchdogTimer()
162
+ } catch (error) {
163
+ node.sysLogger?.error(`matter-config: watchdog start error: ${error.message}`)
164
+ }
165
+ })()
166
+ }, 5000)
167
+
168
+ // Functions called from the nodes and the admin endpoints ----------------------------------------
169
+ node.getCommissionedNodesDetails = () => {
170
+ if (node.matterManager === null) return []
171
+ return node.matterManager.getCommissionedNodesDetails()
172
+ }
173
+
174
+ node.getNodeStructure = (_nodeId) => {
175
+ if (node.matterManager === null) throw new Error('Matter controller not started')
176
+ return node.matterManager.getNodeStructure(_nodeId)
177
+ }
178
+
179
+ node.commission = async (_pairingCode) => {
180
+ if (node.matterManager === null) throw new Error('Matter controller not started')
181
+ return node.matterManager.commission(_pairingCode)
182
+ }
183
+
184
+ node.removeCommissionedNode = async (_nodeId) => {
185
+ if (node.matterManager === null) throw new Error('Matter controller not started')
186
+ return node.matterManager.removeNode(_nodeId)
187
+ }
188
+ // END functions called from the nodes -------------------------------------------------------------
189
+
190
+ node.addClient = (_Node) => {
191
+ try {
192
+ if (node.nodeClients.filter((x) => x.id === _Node.id).length !== 0) return
193
+ node.nodeClients.push(_Node)
194
+ if (node.linkStatus !== 'connected') {
195
+ safeClientCall(_Node, () => _Node.setNodeStatusMatter({
196
+ fill: 'grey',
197
+ shape: 'ring',
198
+ text: 'Waiting for Matter controller'
199
+ }), 'setNodeStatusMatter')
200
+ return
201
+ }
202
+ safeClientCall(_Node, () => _Node.setNodeStatusMatter({
203
+ fill: 'green',
204
+ shape: 'ring',
205
+ text: 'Ready'
206
+ }), 'setNodeStatusMatter')
207
+ // If the paired node already has values in cache, do the initial read straight away
208
+ safeClientCall(_Node, () => _Node.handleMatterNodeInitialized(), 'handleMatterNodeInitialized')
209
+ } catch (error) {
210
+ node.sysLogger?.error(`matter-config: addClient error: ${error.message}`)
211
+ }
212
+ }
213
+
214
+ node.removeClient = (_Node) => {
215
+ try {
216
+ node.nodeClients = node.nodeClients.filter((x) => x.id !== _Node.id)
217
+ } catch (error) {
218
+ /* empty */
219
+ }
220
+ }
221
+
222
+ node.on('close', (done) => {
223
+ try {
224
+ node.nodeClients = []
225
+ if (node.timerMatterConfigCheckState !== null) clearTimeout(node.timerMatterConfigCheckState);
226
+ (async () => {
227
+ try {
228
+ if (node.matterManager !== null) await node.matterManager.close()
229
+ node.matterManager = null
230
+ node.sysLogger = null
231
+ done()
232
+ } catch (error) {
233
+ done()
234
+ }
235
+ })()
236
+ } catch (error) {
237
+ done()
238
+ }
239
+ })
240
+ }
241
+ RED.nodes.registerType('matter-config', matterConfig)
242
+ }