@airnexus/node-red-contrib-matter-airnexus 0.2.4-airnexus.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/ARCHITECTURE.md +327 -0
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/icons/matter-device-icon.svg +3 -0
- package/matter-bridge.html +263 -0
- package/matter-bridge.js +475 -0
- package/matter-device.html +138 -0
- package/matter-device.js +880 -0
- package/matter-pairing.html +54 -0
- package/matter-pairing.js +275 -0
- package/package.json +41 -0
- package/utils.js +30 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.matter-qr-tooltip {
|
|
3
|
+
position: absolute;
|
|
4
|
+
background: white;
|
|
5
|
+
border: 1px solid #999;
|
|
6
|
+
padding: 10px;
|
|
7
|
+
border-radius: 5px;
|
|
8
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
9
|
+
z-index: 1000;
|
|
10
|
+
display: none;
|
|
11
|
+
text-align: center;
|
|
12
|
+
max-width: 400px;
|
|
13
|
+
}
|
|
14
|
+
.matter-qr-tooltip pre {
|
|
15
|
+
font-family: monospace;
|
|
16
|
+
font-size: 6px;
|
|
17
|
+
line-height: 1;
|
|
18
|
+
margin: 5px 0;
|
|
19
|
+
}
|
|
20
|
+
.matter-qr-tooltip .manual-code {
|
|
21
|
+
font-size: 14px;
|
|
22
|
+
font-weight: bold;
|
|
23
|
+
margin-top: 10px;
|
|
24
|
+
}
|
|
25
|
+
</style>
|
|
26
|
+
|
|
27
|
+
<script type="text/javascript">
|
|
28
|
+
RED.nodes.registerType('matter-dynamic-bridge', {
|
|
29
|
+
category: 'config',
|
|
30
|
+
defaults: {
|
|
31
|
+
name: {value: "Matter Bridge"},
|
|
32
|
+
vendorId: {value: "65521", required: true, validate: RED.validators.number()},
|
|
33
|
+
productId: {value: "32768", required: true, validate: RED.validators.number()},
|
|
34
|
+
vendorName: {value: "Node-RED"},
|
|
35
|
+
productName: {value: "Dynamic Matter Bridge"},
|
|
36
|
+
networkInterface: {value: ""},
|
|
37
|
+
storageLocation: {value: ""},
|
|
38
|
+
port: {value: "5540", validate: RED.validators.number()},
|
|
39
|
+
logLevel: {value: "INFO"}
|
|
40
|
+
},
|
|
41
|
+
label: function() {
|
|
42
|
+
return this.name || "Matter Bridge";
|
|
43
|
+
},
|
|
44
|
+
labelStyle: function() {
|
|
45
|
+
return this.name ? "node_label_italic" : "";
|
|
46
|
+
},
|
|
47
|
+
info: function() {
|
|
48
|
+
// Show QR code in node info panel
|
|
49
|
+
var info = "<p>Matter Bridge for dynamic devices.</p>";
|
|
50
|
+
|
|
51
|
+
if (this.id) {
|
|
52
|
+
info += '<div id="bridge-info-qr-' + this.id + '"></div>';
|
|
53
|
+
|
|
54
|
+
// Load QR code async
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
$.getJSON('_matterbridge/qrcode/' + node.id, function(data) {
|
|
57
|
+
if (data.qrCode && !data.commissioned) {
|
|
58
|
+
var html = '<h3>Matter Pairing Code</h3>';
|
|
59
|
+
if (data.qrSvg) {
|
|
60
|
+
html += '<div style="text-align: center;">' + data.qrSvg + '</div>';
|
|
61
|
+
} else {
|
|
62
|
+
html += '<pre style="font-family: monospace; font-size: 6px; line-height: 1;">' + data.qrCode + '</pre>';
|
|
63
|
+
}
|
|
64
|
+
html += '<p><b>Manual Code: ' + data.manualCode + '</b></p>';
|
|
65
|
+
$('#bridge-info-qr-' + node.id).html(html);
|
|
66
|
+
} else if (data.commissioned) {
|
|
67
|
+
$('#bridge-info-qr-' + node.id).html('<p><i>Bridge is commissioned</i></p>');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}, 100);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return info;
|
|
74
|
+
},
|
|
75
|
+
oneditprepare: function() {
|
|
76
|
+
var node = this;
|
|
77
|
+
|
|
78
|
+
// Create QR tooltip element
|
|
79
|
+
if (!$('#matter-qr-tooltip').length) {
|
|
80
|
+
$('body').append('<div id="matter-qr-tooltip" class="matter-qr-tooltip"></div>');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Setup QR display on hover
|
|
84
|
+
var configNodes = $(".red-ui-palette-node-config-list .red-ui-palette-node");
|
|
85
|
+
configNodes.each(function() {
|
|
86
|
+
var configNode = $(this);
|
|
87
|
+
if (configNode.data('node-id') === node.id) {
|
|
88
|
+
configNode.hover(
|
|
89
|
+
function(e) {
|
|
90
|
+
// Mouse enter - show QR code
|
|
91
|
+
$.getJSON('_matterbridge/qrcode/' + node.id, function(data) {
|
|
92
|
+
if (data.qrCode && !data.commissioned) {
|
|
93
|
+
var tooltip = $('#matter-qr-tooltip');
|
|
94
|
+
var html = '<h4>Matter QR Code</h4>';
|
|
95
|
+
if (data.qrSvg) {
|
|
96
|
+
html += data.qrSvg;
|
|
97
|
+
} else {
|
|
98
|
+
html += '<pre>' + data.qrCode + '</pre>';
|
|
99
|
+
}
|
|
100
|
+
html += '<div class="manual-code">Code: ' + data.manualCode + '</div>';
|
|
101
|
+
tooltip.html(html);
|
|
102
|
+
tooltip.css({
|
|
103
|
+
left: e.pageX + 20,
|
|
104
|
+
top: e.pageY - 100
|
|
105
|
+
}).show();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
function() {
|
|
110
|
+
// Mouse leave - hide QR code
|
|
111
|
+
$('#matter-qr-tooltip').hide();
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Load network interfaces
|
|
118
|
+
$.getJSON('_matterbridge/interfaces', function(data) {
|
|
119
|
+
$("#node-config-input-networkInterface").empty();
|
|
120
|
+
$("#node-config-input-networkInterface").append('<option value="">Default</option>');
|
|
121
|
+
data.forEach(function(iface) {
|
|
122
|
+
$("#node-config-input-networkInterface").append('<option value="' + iface + '">' + iface + '</option>');
|
|
123
|
+
});
|
|
124
|
+
$("#node-config-input-networkInterface").val($("#node-config-input-networkInterface").data('current'));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Store current value
|
|
128
|
+
$("#node-config-input-networkInterface").data('current', this.networkInterface);
|
|
129
|
+
|
|
130
|
+
// Load QR code if node exists (simplified - only show if not commissioned)
|
|
131
|
+
if (node.id) {
|
|
132
|
+
$.getJSON('_matterbridge/qrcode/' + node.id, function(data) {
|
|
133
|
+
if (data.qrCode && !data.commissioned) {
|
|
134
|
+
if (data.qrSvg) {
|
|
135
|
+
$("#matter-qr-code").html(data.qrSvg);
|
|
136
|
+
} else {
|
|
137
|
+
$("#matter-qr-code").html('<pre style="font-family: monospace; font-size: 6px; line-height: 1;">' + data.qrCode + '</pre>');
|
|
138
|
+
}
|
|
139
|
+
$("#matter-manual-code").text("Manual Code: " + data.manualCode);
|
|
140
|
+
$("#matter-qr-container").show();
|
|
141
|
+
} else if (data.commissioned) {
|
|
142
|
+
$("#matter-commissioned-info").show();
|
|
143
|
+
$("#matter-qr-container").hide();
|
|
144
|
+
}
|
|
145
|
+
}).fail(function() {
|
|
146
|
+
$("#matter-qr-container").hide();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<script type="text/x-red" data-template-name="matter-dynamic-bridge">
|
|
154
|
+
<div id="matter-qr-container" style="text-align: center; padding: 10px; display: none;">
|
|
155
|
+
<h3>Matter Pairing Code</h3>
|
|
156
|
+
<div id="matter-qr-code" style="text-align: center; margin: 10px 0;"></div>
|
|
157
|
+
<div id="matter-manual-code" style="margin-top: 10px;"></div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div id="matter-commissioned-info" style="display: none; background: #e8f4f8; border: 1px solid #bee5eb; border-radius: 8px; padding: 20px; margin: 10px 0;">
|
|
161
|
+
<h3 style="margin-top: 0; color: #0c5460; text-align: center;">Device Commissioned</h3>
|
|
162
|
+
<p style="color: #0c5460; margin-bottom: 15px; text-align: center;">This bridge is already paired with a Matter controller.</p>
|
|
163
|
+
<div style="background: white; border-radius: 6px; padding: 15px;">
|
|
164
|
+
<p style="margin-top: 0; font-weight: bold; color: #333;">To add to another ecosystem:</p>
|
|
165
|
+
<ol style="margin: 10px 0 0 0; padding-left: 25px; color: #555;">
|
|
166
|
+
<li style="margin-bottom: 8px;">Open the existing controller app (e.g., Apple Home)</li>
|
|
167
|
+
<li style="margin-bottom: 8px;">Find this bridge device</li>
|
|
168
|
+
<li style="margin-bottom: 8px;">Use "Turn On Pairing Mode" or similar option</li>
|
|
169
|
+
<li style="margin-bottom: 0;">Pair with the new controller</li>
|
|
170
|
+
</ol>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div class="form-row">
|
|
175
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
176
|
+
<input type="text" id="node-config-input-name" placeholder="Matter Bridge">
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div class="form-row">
|
|
180
|
+
<label for="node-config-input-vendorId"><i class="fa fa-building"></i> Vendor ID</label>
|
|
181
|
+
<input type="text" id="node-config-input-vendorId" placeholder="65521">
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div class="form-row">
|
|
185
|
+
<label for="node-config-input-productId"><i class="fa fa-barcode"></i> Product ID</label>
|
|
186
|
+
<input type="text" id="node-config-input-productId" placeholder="32768">
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="form-row">
|
|
190
|
+
<label for="node-config-input-vendorName"><i class="fa fa-building-o"></i> Vendor Name</label>
|
|
191
|
+
<input type="text" id="node-config-input-vendorName" placeholder="Node-RED">
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div class="form-row">
|
|
195
|
+
<label for="node-config-input-productName"><i class="fa fa-cube"></i> Product Name</label>
|
|
196
|
+
<input type="text" id="node-config-input-productName" placeholder="Dynamic Matter Bridge">
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="form-row">
|
|
200
|
+
<label for="node-config-input-networkInterface"><i class="fa fa-sitemap"></i> Network Interface</label>
|
|
201
|
+
<select id="node-config-input-networkInterface" style="width:70%;">
|
|
202
|
+
<option value="">Default</option>
|
|
203
|
+
</select>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="form-row">
|
|
207
|
+
<label for="node-config-input-storageLocation"><i class="fa fa-folder"></i> Storage Location</label>
|
|
208
|
+
<input type="text" id="node-config-input-storageLocation" placeholder="Leave empty for default">
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="form-row">
|
|
212
|
+
<label for="node-config-input-port"><i class="fa fa-plug"></i> Port</label>
|
|
213
|
+
<input type="text" id="node-config-input-port" placeholder="5540">
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class="form-row">
|
|
217
|
+
<label for="node-config-input-logLevel"><i class="fa fa-file-text"></i> Log Level</label>
|
|
218
|
+
<select id="node-config-input-logLevel" style="width:70%;">
|
|
219
|
+
<option value="FATAL">Fatal</option>
|
|
220
|
+
<option value="ERROR">Error</option>
|
|
221
|
+
<option value="WARN">Warning</option>
|
|
222
|
+
<option value="INFO" selected>Info</option>
|
|
223
|
+
<option value="DEBUG">Debug</option>
|
|
224
|
+
</select>
|
|
225
|
+
</div>
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<script type="text/x-red" data-help-name="matter-dynamic-bridge">
|
|
229
|
+
<p>Matter Bridge configuration node for dynamic Matter devices.</p>
|
|
230
|
+
|
|
231
|
+
<h3>Configuration</h3>
|
|
232
|
+
<dl class="message-properties">
|
|
233
|
+
<dt>Name <span class="property-type">string</span></dt>
|
|
234
|
+
<dd>Display name for the bridge</dd>
|
|
235
|
+
|
|
236
|
+
<dt>Vendor ID <span class="property-type">number</span></dt>
|
|
237
|
+
<dd>Vendor ID (default: 65521 for testing)</dd>
|
|
238
|
+
|
|
239
|
+
<dt>Product ID <span class="property-type">number</span></dt>
|
|
240
|
+
<dd>Product ID (default: 32768)</dd>
|
|
241
|
+
|
|
242
|
+
<dt>Network Interface <span class="property-type">string</span></dt>
|
|
243
|
+
<dd>Network interface to use for Matter communication</dd>
|
|
244
|
+
|
|
245
|
+
<dt>Storage Location <span class="property-type">string</span></dt>
|
|
246
|
+
<dd>Custom storage location for Matter data</dd>
|
|
247
|
+
|
|
248
|
+
<dt>Port <span class="property-type">number</span></dt>
|
|
249
|
+
<dd>Port number for Matter server (default: 5540)</dd>
|
|
250
|
+
</dl>
|
|
251
|
+
|
|
252
|
+
<h3>Details</h3>
|
|
253
|
+
<p>This node creates a Matter bridge that can host multiple dynamic Matter devices.</p>
|
|
254
|
+
|
|
255
|
+
<h3>Multi-Admin Support</h3>
|
|
256
|
+
<p>The bridge supports Matter Multi-Admin, allowing you to connect it to multiple ecosystems simultaneously:</p>
|
|
257
|
+
<ul>
|
|
258
|
+
<li><b>First pairing:</b> Use the QR code shown when the bridge starts</li>
|
|
259
|
+
<li><b>Additional pairings:</b> Use your existing controller (HomeKit, Alexa, etc.) to enable pairing mode</li>
|
|
260
|
+
<li>You can pair with HomeKit, Alexa, Google Home, SmartThings, and other Matter controllers</li>
|
|
261
|
+
<li>All controllers will have local access to your devices</li>
|
|
262
|
+
</ul>
|
|
263
|
+
</script>
|