matterbridge 3.4.0-dev-20251120-5724e43 → 3.4.0-dev-20251123-62db0d7
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 +3 -2
- package/dist/broadcastServer.js +4 -0
- package/dist/jestutils/jestHelpers.js +8 -1
- package/dist/matterbridgePlatform.js +20 -29
- package/dist/pluginManager.js +36 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/scripts/fetch-chip.mjs +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -46,12 +46,13 @@ Removed the following long deprecated elements:
|
|
|
46
46
|
- [doorLock]: Added autoRelockTime attribute with default 0.
|
|
47
47
|
- [DevContainer]: Added instructions for testing a plugin with a paired controller when using DevContainer.
|
|
48
48
|
- [platform]: Made internal use methods and properties hard-private.
|
|
49
|
-
- [platform]: Added size(), getDeviceByName(), getDeviceByUniqueId(), getDeviceBySerialNumber(), getDeviceById(), getDeviceByOriginalId(), getDeviceByNumber() and hasDeviceUniqueId().
|
|
50
|
-
- [platform]: Added isReady, isLoaded, isStarted and isConfigured.
|
|
49
|
+
- [platform]: Added size(), getDeviceByName(), getDeviceByUniqueId(), getDeviceBySerialNumber(), getDeviceById(), getDeviceByOriginalId(), getDeviceByNumber() and hasDeviceUniqueId() methods.
|
|
50
|
+
- [platform]: Added isReady, isLoaded, isStarted and isConfigured properties.
|
|
51
51
|
|
|
52
52
|
### Changed
|
|
53
53
|
|
|
54
54
|
- [package]: Updated dependencies.
|
|
55
|
+
- [platform]: Bumped MatterbridgePlatform v.1.4.0.
|
|
55
56
|
- [deviceManager]: Bumped DeviceManager v.1.1.1.
|
|
56
57
|
- [pluginManager]: Bumped PluginManager v.1.3.1.
|
|
57
58
|
- [broadcastServer]: Bumped BroadcastServer v.1.0.3.
|
package/dist/broadcastServer.js
CHANGED
|
@@ -88,6 +88,10 @@ export class BroadcastServer extends EventEmitter {
|
|
|
88
88
|
this.log.debug(`Fetch response: ${debugStringify(msg)}`);
|
|
89
89
|
resolve(msg);
|
|
90
90
|
}
|
|
91
|
+
else if (this.isWorkerResponse(msg, message.type) && msg.id !== message.id) {
|
|
92
|
+
if (this.verbose)
|
|
93
|
+
this.log.debug(`Fetch received unrelated response: ${debugStringify(msg)}`);
|
|
94
|
+
}
|
|
91
95
|
};
|
|
92
96
|
this.on('broadcast_message', responseHandler);
|
|
93
97
|
this.request(message);
|
|
@@ -184,7 +184,7 @@ export async function createMatterbridgeEnvironment(name) {
|
|
|
184
184
|
matterbridge = await Matterbridge.loadInstance(false);
|
|
185
185
|
expect(matterbridge).toBeDefined();
|
|
186
186
|
expect(matterbridge).toBeInstanceOf(Matterbridge);
|
|
187
|
-
matterbridge.matterbridgeVersion = '3.
|
|
187
|
+
matterbridge.matterbridgeVersion = '3.4.0';
|
|
188
188
|
matterbridge.bridgeMode = 'bridge';
|
|
189
189
|
matterbridge.rootDirectory = path.join('jest', name);
|
|
190
190
|
matterbridge.homeDirectory = path.join('jest', name);
|
|
@@ -310,6 +310,13 @@ export function createTestEnvironment(name) {
|
|
|
310
310
|
new MdnsService(environment);
|
|
311
311
|
return environment;
|
|
312
312
|
}
|
|
313
|
+
export async function destroyTestEnvironment() {
|
|
314
|
+
const mdns = environment.get(MdnsService);
|
|
315
|
+
if (mdns && typeof mdns[Symbol.asyncDispose] === 'function')
|
|
316
|
+
await mdns[Symbol.asyncDispose]();
|
|
317
|
+
if (mdns && typeof mdns.close === 'function')
|
|
318
|
+
await mdns.close();
|
|
319
|
+
}
|
|
313
320
|
export async function flushAsync(ticks = 3, microTurns = 10, pause = 250) {
|
|
314
321
|
for (let i = 0; i < ticks; i++)
|
|
315
322
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -18,7 +18,7 @@ export class MatterbridgePlatform {
|
|
|
18
18
|
name = '';
|
|
19
19
|
type = 'AnyPlatform';
|
|
20
20
|
version = '1.0.0';
|
|
21
|
-
storage;
|
|
21
|
+
#storage;
|
|
22
22
|
context;
|
|
23
23
|
isReady = false;
|
|
24
24
|
isLoaded = false;
|
|
@@ -48,7 +48,7 @@ export class MatterbridgePlatform {
|
|
|
48
48
|
throw new Error('Platform: the plugin name is missing or invalid.');
|
|
49
49
|
}
|
|
50
50
|
this.log.debug(`Creating storage for plugin ${this.config.name} in ${path.join(this.matterbridge.matterbridgeDirectory, this.config.name)}`);
|
|
51
|
-
this
|
|
51
|
+
this.#storage = new NodeStorageManager({
|
|
52
52
|
dir: path.join(this.matterbridge.matterbridgeDirectory, this.config.name),
|
|
53
53
|
writeQueue: false,
|
|
54
54
|
expiredInterval: undefined,
|
|
@@ -56,13 +56,13 @@ export class MatterbridgePlatform {
|
|
|
56
56
|
forgiveParseErrors: true,
|
|
57
57
|
});
|
|
58
58
|
this.log.debug(`Creating context for plugin ${this.config.name}`);
|
|
59
|
-
this.#contextReady = this
|
|
59
|
+
this.#contextReady = this.#storage.createStorage('context').then((context) => {
|
|
60
60
|
this.context = context;
|
|
61
61
|
this.log.debug(`Created context for plugin ${this.config.name}`);
|
|
62
62
|
return;
|
|
63
63
|
});
|
|
64
64
|
this.log.debug(`Loading selectDevice for plugin ${this.config.name}`);
|
|
65
|
-
this.#selectDeviceContextReady = this
|
|
65
|
+
this.#selectDeviceContextReady = this.#storage.createStorage('selectDevice').then(async (context) => {
|
|
66
66
|
const selectDevice = await context.get('selectDevice', []);
|
|
67
67
|
for (const device of selectDevice)
|
|
68
68
|
this.#selectDevices.set(device.serial, device);
|
|
@@ -70,7 +70,7 @@ export class MatterbridgePlatform {
|
|
|
70
70
|
return;
|
|
71
71
|
});
|
|
72
72
|
this.log.debug(`Loading selectEntity for plugin ${this.config.name}`);
|
|
73
|
-
this.#selectEntityContextReady = this
|
|
73
|
+
this.#selectEntityContextReady = this.#storage.createStorage('selectEntity').then(async (context) => {
|
|
74
74
|
const selectEntity = await context.get('selectEntity', []);
|
|
75
75
|
for (const entity of selectEntity)
|
|
76
76
|
this.#selectEntities.set(entity.name, entity);
|
|
@@ -91,7 +91,7 @@ export class MatterbridgePlatform {
|
|
|
91
91
|
this.#registeredEndpoints.clear();
|
|
92
92
|
await this.context?.close();
|
|
93
93
|
this.context = undefined;
|
|
94
|
-
await this
|
|
94
|
+
await this.#storage?.close();
|
|
95
95
|
this.#server.close();
|
|
96
96
|
if (this.#verbose)
|
|
97
97
|
this.log.debug(`Destroyed MatterbridgePlatform for plugin ${this.config.name}`);
|
|
@@ -122,25 +122,13 @@ export class MatterbridgePlatform {
|
|
|
122
122
|
this.log.debug(`The plugin ${CYAN}${config.name}${db} doesn't override onConfigChanged. Received new config.`);
|
|
123
123
|
}
|
|
124
124
|
saveConfig(config) {
|
|
125
|
-
|
|
126
|
-
if (!plugin) {
|
|
127
|
-
throw new Error(`Plugin ${this.name} not found`);
|
|
128
|
-
}
|
|
129
|
-
this.matterbridge.plugins.saveConfigFromJson(plugin, config);
|
|
125
|
+
this.#server.request({ type: 'plugins_saveconfigfromjson', src: 'platform', dst: 'plugins', params: { name: this.name, config } });
|
|
130
126
|
}
|
|
131
|
-
getSchema() {
|
|
132
|
-
|
|
133
|
-
if (!plugin || !isValidObject(plugin.schemaJson)) {
|
|
134
|
-
throw new Error(`Plugin ${this.name} not found`);
|
|
135
|
-
}
|
|
136
|
-
return plugin.schemaJson;
|
|
127
|
+
async getSchema() {
|
|
128
|
+
return (await this.#server.fetch({ type: 'plugins_getschema', src: 'platform', dst: 'plugins', params: { name: this.name } })).response.schema;
|
|
137
129
|
}
|
|
138
130
|
setSchema(schema) {
|
|
139
|
-
|
|
140
|
-
if (!plugin) {
|
|
141
|
-
throw new Error(`Plugin ${this.name} not found`);
|
|
142
|
-
}
|
|
143
|
-
plugin.schemaJson = schema;
|
|
131
|
+
this.#server.request({ type: 'plugins_setschema', src: 'platform', dst: 'plugins', params: { name: this.name, schema } });
|
|
144
132
|
}
|
|
145
133
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
146
134
|
this.#server.request({ type: 'frontend_restartrequired', src: 'platform', dst: 'frontend', params: { snackbar, fixed } });
|
|
@@ -183,7 +171,7 @@ export class MatterbridgePlatform {
|
|
|
183
171
|
if (this.matterbridge.bridgeMode === 'bridge') {
|
|
184
172
|
aggregator = this.matterbridge.aggregatorNode;
|
|
185
173
|
}
|
|
186
|
-
else if (this.matterbridge.bridgeMode === 'childbridge') {
|
|
174
|
+
else if (this.matterbridge.bridgeMode === 'childbridge' && this.type === 'DynamicPlatform') {
|
|
187
175
|
aggregator = this.matterbridge.plugins.get(this.name)?.aggregatorNode;
|
|
188
176
|
}
|
|
189
177
|
if (aggregator) {
|
|
@@ -197,6 +185,7 @@ export class MatterbridgePlatform {
|
|
|
197
185
|
return true;
|
|
198
186
|
}
|
|
199
187
|
}
|
|
188
|
+
this.log.warn(`Virtual device ${name} not created. Virtual devices are only supported in bridge mode and childbridge mode with a DynamicPlatform.`);
|
|
200
189
|
return false;
|
|
201
190
|
}
|
|
202
191
|
async registerDevice(device) {
|
|
@@ -250,13 +239,13 @@ export class MatterbridgePlatform {
|
|
|
250
239
|
this.#registeredEndpoints.clear();
|
|
251
240
|
}
|
|
252
241
|
async saveSelects() {
|
|
253
|
-
if (this
|
|
242
|
+
if (this.#storage) {
|
|
254
243
|
this.log.debug(`Saving ${this.#selectDevices.size} selectDevice...`);
|
|
255
|
-
const selectDevice = await this
|
|
244
|
+
const selectDevice = await this.#storage.createStorage('selectDevice');
|
|
256
245
|
await selectDevice.set('selectDevice', Array.from(this.#selectDevices.values()));
|
|
257
246
|
await selectDevice.close();
|
|
258
247
|
this.log.debug(`Saving ${this.#selectEntities.size} selectEntity...`);
|
|
259
|
-
const selectEntity = await this
|
|
248
|
+
const selectEntity = await this.#storage.createStorage('selectEntity');
|
|
260
249
|
await selectEntity.set('selectEntity', Array.from(this.#selectEntities.values()));
|
|
261
250
|
await selectEntity.close();
|
|
262
251
|
}
|
|
@@ -393,11 +382,13 @@ export class MatterbridgePlatform {
|
|
|
393
382
|
}
|
|
394
383
|
return true;
|
|
395
384
|
}
|
|
385
|
+
async clearEndpointNumbers() {
|
|
386
|
+
const context = await this.#storage.createStorage('endpointNumbers');
|
|
387
|
+
await context.set('endpointMap', []);
|
|
388
|
+
}
|
|
396
389
|
async checkEndpointNumbers() {
|
|
397
|
-
if (!this.storage)
|
|
398
|
-
return -1;
|
|
399
390
|
this.log.debug('Checking endpoint numbers...');
|
|
400
|
-
const context = await this
|
|
391
|
+
const context = await this.#storage.createStorage('endpointNumbers');
|
|
401
392
|
const separator = '|.|';
|
|
402
393
|
const endpointMap = new Map(await context.get('endpointMap', []));
|
|
403
394
|
for (const device of this.getDevices()) {
|
package/dist/pluginManager.js
CHANGED
|
@@ -51,6 +51,7 @@ export class PluginManager extends EventEmitter {
|
|
|
51
51
|
this.server.respond({ ...msg, response: { plugin: this.toApiPlugin(plugin) } });
|
|
52
52
|
}
|
|
53
53
|
else {
|
|
54
|
+
this.log.debug(`***Plugin ${plg}${msg.params.name}${db} not found in plugins_get`);
|
|
54
55
|
this.server.respond({ ...msg, response: { plugin: undefined } });
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -158,6 +159,41 @@ export class PluginManager extends EventEmitter {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
break;
|
|
162
|
+
case 'plugins_getschema':
|
|
163
|
+
{
|
|
164
|
+
const plugin = this.get(msg.params.name);
|
|
165
|
+
if (plugin) {
|
|
166
|
+
this.server.respond({ ...msg, response: { schema: plugin.schemaJson } });
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.server.respond({ ...msg, response: { schema: undefined } });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case 'plugins_setschema':
|
|
174
|
+
{
|
|
175
|
+
const plugin = this.get(msg.params.name);
|
|
176
|
+
if (plugin) {
|
|
177
|
+
plugin.schemaJson = msg.params.schema;
|
|
178
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.server.respond({ ...msg, response: { success: false } });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case 'plugins_saveconfigfromjson':
|
|
186
|
+
{
|
|
187
|
+
const plugin = this.get(msg.params.name);
|
|
188
|
+
if (plugin) {
|
|
189
|
+
this.saveConfigFromJson(plugin, msg.params.config, msg.params.restartRequired);
|
|
190
|
+
this.server.respond({ ...msg, response: { success: true } });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.server.respond({ ...msg, response: { success: false } });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
161
197
|
default:
|
|
162
198
|
if (this.verbose)
|
|
163
199
|
this.log.debug(`Unknown broadcast message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.4.0-dev-
|
|
3
|
+
"version": "3.4.0-dev-20251123-62db0d7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.4.0-dev-
|
|
9
|
+
"version": "3.4.0-dev-20251123-62db0d7",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.15.6",
|
package/package.json
CHANGED
package/scripts/fetch-chip.mjs
CHANGED
|
@@ -11,13 +11,14 @@ import https from 'node:https';
|
|
|
11
11
|
* - ZCL_OUT: output path for zcl.json (default: chip/zcl.json)
|
|
12
12
|
* - ZCL_BRANCH: connectedhomeip branch to fetch from (default: v1.4-branch)
|
|
13
13
|
*/
|
|
14
|
-
const OUT_PATH = process.env.ZCL_OUT || 'chip/zcl.json';
|
|
15
14
|
// Single branch strategy from online only
|
|
16
|
-
const BRANCH = process.env.ZCL_BRANCH || 'v1.4-branch';
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
15
|
+
const BRANCH = process.env.ZCL_BRANCH || 'v1.4.2-branch';
|
|
16
|
+
const OUT_ZCL_PATH = process.env.ZCL_OUT || `chip/${BRANCH}/zcl.json`;
|
|
17
|
+
const OUT_MANUFACTURERS_PATH = process.env.MANUFACTURERS_OUT || `chip/${BRANCH}/manufacturers.xml`;
|
|
18
|
+
const ZCL_BASE_URL = `https://raw.githubusercontent.com/project-chip/connectedhomeip/${BRANCH}/src/app/zap-templates/zcl/`;
|
|
19
|
+
const ZCL_JSON_URL = ZCL_BASE_URL + 'zcl.json';
|
|
20
|
+
const MANUFACTURERS_URL = ZCL_BASE_URL + 'data-model/manufacturers.xml';
|
|
21
|
+
const XML_BASE_URL = ZCL_BASE_URL + 'data-model/chip/';
|
|
21
22
|
|
|
22
23
|
function fetchUrl(url) {
|
|
23
24
|
return new Promise((resolve, reject) => {
|
|
@@ -37,7 +38,7 @@ function fetchUrl(url) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
async function main() {
|
|
40
|
-
await mkdir(dirname(
|
|
41
|
+
await mkdir(dirname(OUT_ZCL_PATH), { recursive: true });
|
|
41
42
|
// Always fetch online from the specified branch
|
|
42
43
|
const data = await fetchUrl(ZCL_JSON_URL);
|
|
43
44
|
|
|
@@ -52,15 +53,14 @@ async function main() {
|
|
|
52
53
|
process.stderr.write('Warning: zcl.json does not contain expected ZAP ZCL properties (xmlFile). Saving anyway for manual inspection.\n');
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
await writeFile(
|
|
56
|
-
process.stderr.write(`Saved ${
|
|
56
|
+
await writeFile(OUT_ZCL_PATH, JSON.stringify(parsed, null, 2));
|
|
57
|
+
process.stderr.write(`Saved ${OUT_ZCL_PATH}. Branch=${BRANCH}\n`);
|
|
57
58
|
|
|
58
59
|
// Also fetch manufacturers.xml to chip/manufacturers.xml from the same branch
|
|
59
60
|
try {
|
|
60
|
-
const outManu = 'chip/manufacturers.xml';
|
|
61
61
|
const manuContent = await fetchUrl(MANUFACTURERS_URL);
|
|
62
|
-
await writeFile(
|
|
63
|
-
process.stderr.write(
|
|
62
|
+
await writeFile(OUT_MANUFACTURERS_PATH, manuContent);
|
|
63
|
+
process.stderr.write(`Saved ${OUT_MANUFACTURERS_PATH}. Branch=${BRANCH}\n`);
|
|
64
64
|
} catch (e) {
|
|
65
65
|
process.stderr.write(`Warning: failed to fetch manufacturers.xml (${e.message}).\n`);
|
|
66
66
|
}
|
|
@@ -70,14 +70,14 @@ async function main() {
|
|
|
70
70
|
if (xmlFiles.length === 0) {
|
|
71
71
|
process.stderr.write('No xmlFile entries found; skipping XML fetch.\n');
|
|
72
72
|
} else {
|
|
73
|
-
const outXmlBase =
|
|
73
|
+
const outXmlBase = `chip/${BRANCH}/xml`;
|
|
74
74
|
await mkdir(outXmlBase, { recursive: true });
|
|
75
75
|
let ok = 0;
|
|
76
76
|
let fail = 0;
|
|
77
77
|
for (const fileName of xmlFiles) {
|
|
78
78
|
const relative = fileName.trim();
|
|
79
79
|
try {
|
|
80
|
-
const url =
|
|
80
|
+
const url = XML_BASE_URL + relative;
|
|
81
81
|
const content = await fetchUrl(url);
|
|
82
82
|
const outPath = pathJoin(outXmlBase, relative.replaceAll('/', pathSep));
|
|
83
83
|
await mkdir(dirname(outPath), { recursive: true });
|