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.
- package/CHANGELOG.md +21 -0
- package/nodes/commonFunctions.js +93 -0
- package/nodes/icons/node-matter-icon.svg +12 -0
- package/nodes/knxUltimateMatterBridge.html +429 -0
- package/nodes/knxUltimateMatterBridge.js +456 -0
- package/nodes/knxUltimateMatterDevice.html +586 -0
- package/nodes/knxUltimateMatterDevice.js +308 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +64 -0
- package/nodes/locales/de/knxUltimateMatterBridge.json +80 -0
- package/nodes/locales/de/knxUltimateMatterDevice.html +56 -0
- package/nodes/locales/de/knxUltimateMatterDevice.json +109 -0
- package/nodes/locales/de/matter-config.html +25 -0
- package/nodes/locales/de/matter-config.json +26 -0
- package/nodes/locales/en/knxUltimateMatterBridge.html +64 -0
- package/nodes/locales/en/knxUltimateMatterBridge.json +80 -0
- package/nodes/locales/en/knxUltimateMatterDevice.html +56 -0
- package/nodes/locales/en/knxUltimateMatterDevice.json +109 -0
- package/nodes/locales/en/matter-config.html +25 -0
- package/nodes/locales/en/matter-config.json +26 -0
- package/nodes/locales/es/knxUltimateMatterBridge.html +64 -0
- package/nodes/locales/es/knxUltimateMatterBridge.json +80 -0
- package/nodes/locales/es/knxUltimateMatterDevice.html +56 -0
- package/nodes/locales/es/knxUltimateMatterDevice.json +109 -0
- package/nodes/locales/es/matter-config.html +25 -0
- package/nodes/locales/es/matter-config.json +26 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.html +64 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.json +80 -0
- package/nodes/locales/fr/knxUltimateMatterDevice.html +56 -0
- package/nodes/locales/fr/knxUltimateMatterDevice.json +109 -0
- package/nodes/locales/fr/matter-config.html +25 -0
- package/nodes/locales/fr/matter-config.json +26 -0
- package/nodes/locales/it/knxUltimateMatterBridge.html +64 -0
- package/nodes/locales/it/knxUltimateMatterBridge.json +80 -0
- package/nodes/locales/it/knxUltimateMatterDevice.html +56 -0
- package/nodes/locales/it/knxUltimateMatterDevice.json +109 -0
- package/nodes/locales/it/matter-config.html +25 -0
- package/nodes/locales/it/matter-config.json +26 -0
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +64 -0
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +80 -0
- package/nodes/locales/zh-CN/knxUltimateMatterDevice.html +56 -0
- package/nodes/locales/zh-CN/knxUltimateMatterDevice.json +109 -0
- package/nodes/locales/zh-CN/matter-config.html +25 -0
- package/nodes/locales/zh-CN/matter-config.json +26 -0
- package/nodes/matter-config.html +226 -0
- package/nodes/matter-config.js +242 -0
- package/nodes/utils/matterBridgeDeviceFactory.mjs +511 -0
- package/nodes/utils/matterBridgeEngine.mjs +290 -0
- package/nodes/utils/matterEngine.mjs +475 -0
- package/nodes/utils/matterKnxConverter.js +204 -0
- 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> <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> <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> <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> <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
|
+
}
|