@matterbridge/core 3.10.14-dev-20261013-e87a0c8 → 3.10.14-dev-20261014-2811fdc
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/dist/backend.d.ts +2 -2
- package/dist/backend.js +11 -5
- package/dist/backendWsServer.js +14 -6
- package/dist/behaviors/weriftSession.d.ts +3 -0
- package/dist/behaviors/weriftSession.js +43 -13
- package/dist/demoDevices.js +2 -0
- package/dist/deviceManager.d.ts +1 -1
- package/dist/deviceManager.js +3 -3
- package/dist/devices/batteryStorage.d.ts +1 -0
- package/dist/devices/batteryStorage.js +1 -1
- package/dist/devices/intercom.d.ts +11 -1
- package/dist/devices/intercom.js +13 -2
- package/dist/frontend.d.ts +2 -2
- package/dist/frontend.js +32 -77
- package/dist/matterNode.js +1 -1
- package/dist/matterNodeHelpers.d.ts +4 -2
- package/dist/matterNodeHelpers.js +49 -9
- package/dist/matterbridge.js +1 -1
- package/dist/matterbridgeEndpoint.d.ts +1 -1
- package/dist/matterbridgeEndpoint.js +2 -2
- package/dist/matterbridgeEndpointHelpers.d.ts +1 -1
- package/dist/matterbridgeEndpointHelpers.js +5 -1
- package/dist/matterbridgePlatform.js +22 -7
- package/package.json +5 -5
package/dist/backend.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export declare class Backend extends EventEmitter<BackendEvents> {
|
|
|
34
34
|
stop(): Promise<void>;
|
|
35
35
|
getApiSettings(): Promise<ApiSettings>;
|
|
36
36
|
getApiPlugins(): Promise<ApiPlugin[]>;
|
|
37
|
-
getApiDevices(pluginName?: string): Promise<ApiDevice[]>;
|
|
38
|
-
getApiCluster(pluginName: string,
|
|
37
|
+
getApiDevices(pluginName?: string, extended?: boolean): Promise<ApiDevice[]>;
|
|
38
|
+
getApiCluster(pluginName: string, number: number, serialNumber: string, uniqueId: string): Promise<ApiClusters>;
|
|
39
39
|
generateDiagnostic(): Promise<void>;
|
|
40
40
|
getApiMatter(id: string): Promise<ApiMatter | undefined>;
|
|
41
41
|
}
|
package/dist/backend.js
CHANGED
|
@@ -341,19 +341,25 @@ export class Backend extends EventEmitter {
|
|
|
341
341
|
writeDiagnostic('Backend', `getApiPlugins() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
342
342
|
return response.result.plugins;
|
|
343
343
|
}
|
|
344
|
-
async getApiDevices(pluginName) {
|
|
344
|
+
async getApiDevices(pluginName, extended = false) {
|
|
345
345
|
const start = performance.now();
|
|
346
|
-
const response = await this.server.fetch({ type: 'devices_apidevicearray', src: 'frontend', dst: 'devices', params: { pluginName } });
|
|
346
|
+
const response = await this.server.fetch({ type: 'devices_apidevicearray', src: 'frontend', dst: 'devices', params: { pluginName, extended } });
|
|
347
347
|
if (this.diagnostic)
|
|
348
348
|
writeDiagnostic('Backend', `getApiDevices() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
349
349
|
return response.result.devices;
|
|
350
350
|
}
|
|
351
|
-
async getApiCluster(pluginName,
|
|
351
|
+
async getApiCluster(pluginName, number, serialNumber, uniqueId) {
|
|
352
352
|
const start = performance.now();
|
|
353
|
-
this.log.debug(`Retrieving API cluster for plugin: ${pluginName},
|
|
353
|
+
this.log.debug(`Retrieving API cluster for plugin: ${pluginName}, number: ${number}, serial: ${serialNumber}, uniqueId: ${uniqueId}`);
|
|
354
|
+
const response = await this.server.fetch({
|
|
355
|
+
type: 'platform_get_apiclusters',
|
|
356
|
+
src: 'frontend',
|
|
357
|
+
dst: 'platform',
|
|
358
|
+
params: { pluginName, number, serialNumber, uniqueId },
|
|
359
|
+
});
|
|
354
360
|
if (this.diagnostic)
|
|
355
361
|
writeDiagnostic('Backend', `getApiCluster() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
356
|
-
return
|
|
362
|
+
return response.result.clusters;
|
|
357
363
|
}
|
|
358
364
|
async generateDiagnostic() {
|
|
359
365
|
const start = performance.now();
|
package/dist/backendWsServer.js
CHANGED
|
@@ -209,22 +209,30 @@ export class BackendWsServer {
|
|
|
209
209
|
}
|
|
210
210
|
else if (data.method === '/api/devices') {
|
|
211
211
|
const start = performance.now();
|
|
212
|
-
const devices = await this.backend.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
212
|
+
const devices = await this.backend.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined, data.params.extended);
|
|
213
213
|
if (this.diagnostic)
|
|
214
214
|
writeDiagnostic(`Frontend:${data.sender}`, `getApiDevices() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
215
215
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
|
|
216
216
|
}
|
|
217
217
|
else if (data.method === '/api/clusters') {
|
|
218
|
-
if (!isValidString(data.params.
|
|
219
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter
|
|
218
|
+
if (!isValidString(data.params.pluginName, 10)) {
|
|
219
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/clusters' });
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
|
-
if (!isValidNumber(data.params.
|
|
223
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter
|
|
222
|
+
if (!isValidNumber(data.params.number, 1)) {
|
|
223
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter number in /api/clusters' });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (!isValidString(data.params.serialNumber, 1)) {
|
|
227
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter serialNumber in /api/clusters' });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (!isValidString(data.params.uniqueId, 1)) {
|
|
231
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter uniqueId in /api/clusters' });
|
|
224
232
|
return;
|
|
225
233
|
}
|
|
226
234
|
const start = performance.now();
|
|
227
|
-
const clusters = await this.backend.getApiCluster(data.params.
|
|
235
|
+
const clusters = await this.backend.getApiCluster(data.params.pluginName, data.params.number, data.params.serialNumber, data.params.uniqueId);
|
|
228
236
|
if (this.diagnostic)
|
|
229
237
|
writeDiagnostic(`Frontend:${data.sender}`, `getApiCluster() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
230
238
|
if (clusters) {
|
|
@@ -44,6 +44,9 @@ export declare class WeriftWebRtcSession {
|
|
|
44
44
|
private getPreferredInjectableAudioCodec;
|
|
45
45
|
private static readonly SUPPORTED_VIDEO_RESOLUTIONS;
|
|
46
46
|
private static readonly DEFAULT_BITRATE_KBPS;
|
|
47
|
+
private static readonly DEFAULT_TEST_VIDEO_FPS;
|
|
48
|
+
private static readonly MAX_TEST_VIDEO_FPS;
|
|
49
|
+
private getTestVideoFps;
|
|
47
50
|
private getConfiguredVideoBitrate;
|
|
48
51
|
private getConfiguredVideoResolution;
|
|
49
52
|
private getConfiguredVideoSource;
|
|
@@ -146,6 +146,19 @@ export class WeriftWebRtcSession {
|
|
|
146
146
|
}
|
|
147
147
|
static SUPPORTED_VIDEO_RESOLUTIONS = ['640x480', '1280x720', '1920x1080'];
|
|
148
148
|
static DEFAULT_BITRATE_KBPS = 1000;
|
|
149
|
+
static DEFAULT_TEST_VIDEO_FPS = 30;
|
|
150
|
+
static MAX_TEST_VIDEO_FPS = 60;
|
|
151
|
+
getTestVideoFps() {
|
|
152
|
+
const raw = process.env.MATTERBRIDGE_TEST_VIDEO_FPS?.trim();
|
|
153
|
+
if (!raw)
|
|
154
|
+
return WeriftWebRtcSession.DEFAULT_TEST_VIDEO_FPS;
|
|
155
|
+
const fps = Number(raw);
|
|
156
|
+
if (!/^\d+$/.test(raw) || fps < 1 || fps > WeriftWebRtcSession.MAX_TEST_VIDEO_FPS) {
|
|
157
|
+
this.log.warn(`Invalid MATTERBRIDGE_TEST_VIDEO_FPS "${raw}" (supported: 1-${WeriftWebRtcSession.MAX_TEST_VIDEO_FPS}); falling back to ${WeriftWebRtcSession.DEFAULT_TEST_VIDEO_FPS}`);
|
|
158
|
+
return WeriftWebRtcSession.DEFAULT_TEST_VIDEO_FPS;
|
|
159
|
+
}
|
|
160
|
+
return fps;
|
|
161
|
+
}
|
|
149
162
|
getConfiguredVideoBitrate() {
|
|
150
163
|
const configured = this.options.videoBitrate;
|
|
151
164
|
if (configured === undefined)
|
|
@@ -185,13 +198,12 @@ export class WeriftWebRtcSession {
|
|
|
185
198
|
}
|
|
186
199
|
buildFfmpegVideoInputArgs(videoSource) {
|
|
187
200
|
if (videoSource === 'test') {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
return testPatternInput;
|
|
201
|
+
const resolution = this.getConfiguredVideoResolution();
|
|
202
|
+
const bitrateKbps = this.getConfiguredVideoBitrate();
|
|
203
|
+
const fps = this.getTestVideoFps();
|
|
204
|
+
const description = `synthetic moving test pattern (${resolution}, ${fps}fps)`;
|
|
205
|
+
this.log.debug(`Test pattern params: resolution=${resolution}, fps=${fps}, description="${description}", bitrateKbps=${bitrateKbps}`);
|
|
206
|
+
return { args: ['-re', '-f', 'lavfi', '-i', `testsrc=size=${resolution}:rate=${fps}`], description, bitrateKbps, keyFrameInterval: fps * 2 };
|
|
195
207
|
}
|
|
196
208
|
const device = this.options.videoSourceDevice;
|
|
197
209
|
if (!device) {
|
|
@@ -203,17 +215,17 @@ export class WeriftWebRtcSession {
|
|
|
203
215
|
if (videoSource === 'rtsp') {
|
|
204
216
|
const description = `RTSP camera (${device}, ${resolution})`;
|
|
205
217
|
this.log.debug(`RTSP capture params: url=${device}, resolution=${resolution}, description="${description}", bitrateKbps=${bitrateKbps}`);
|
|
206
|
-
return { args: ['-rtsp_transport', 'tcp', '-i', device, '-vf', `scale=${resolution.replace('x', ':')}`], description, bitrateKbps };
|
|
218
|
+
return { args: ['-rtsp_transport', 'tcp', '-i', device, '-vf', `scale=${resolution.replace('x', ':')}`], description, bitrateKbps, keyFrameInterval: 20 };
|
|
207
219
|
}
|
|
208
220
|
const description = `local webcam (${device}, ${resolution})`;
|
|
209
221
|
this.log.debug(`Webcam capture params: device=${device}, resolution=${resolution}, description="${description}", bitrateKbps=${bitrateKbps}`);
|
|
210
222
|
switch (process.platform) {
|
|
211
223
|
case 'linux':
|
|
212
|
-
return { args: ['-f', 'v4l2', '-video_size', resolution, '-framerate', '30', '-i', device], description, bitrateKbps };
|
|
224
|
+
return { args: ['-f', 'v4l2', '-video_size', resolution, '-framerate', '30', '-i', device], description, bitrateKbps, keyFrameInterval: 20 };
|
|
213
225
|
case 'darwin':
|
|
214
|
-
return { args: ['-f', 'avfoundation', '-video_size', resolution, '-framerate', '30', '-i', device], description, bitrateKbps };
|
|
226
|
+
return { args: ['-f', 'avfoundation', '-video_size', resolution, '-framerate', '30', '-i', device], description, bitrateKbps, keyFrameInterval: 20 };
|
|
215
227
|
case 'win32':
|
|
216
|
-
return { args: ['-f', 'dshow', '-video_size', resolution, '-framerate', '30', '-i', `video=${device}`], description, bitrateKbps };
|
|
228
|
+
return { args: ['-f', 'dshow', '-video_size', resolution, '-framerate', '30', '-i', `video=${device}`], description, bitrateKbps, keyFrameInterval: 20 };
|
|
217
229
|
default:
|
|
218
230
|
this.log.error(`Webcam capture via ffmpeg is not supported on platform "${process.platform}"; not injecting a video track`);
|
|
219
231
|
return undefined;
|
|
@@ -252,9 +264,27 @@ export class WeriftWebRtcSession {
|
|
|
252
264
|
});
|
|
253
265
|
this.peerConnection.addTrack(track);
|
|
254
266
|
const bitrate = `${videoInput.bitrateKbps}k`;
|
|
267
|
+
const keyFrameInterval = String(videoInput.keyFrameInterval);
|
|
255
268
|
const encoderArgs = selectedMimeType === 'video/h264'
|
|
256
|
-
? [
|
|
257
|
-
|
|
269
|
+
? [
|
|
270
|
+
'-c:v',
|
|
271
|
+
'libx264',
|
|
272
|
+
'-preset',
|
|
273
|
+
'ultrafast',
|
|
274
|
+
'-tune',
|
|
275
|
+
'zerolatency',
|
|
276
|
+
'-pix_fmt',
|
|
277
|
+
'yuv420p',
|
|
278
|
+
'-g',
|
|
279
|
+
keyFrameInterval,
|
|
280
|
+
'-b:v',
|
|
281
|
+
bitrate,
|
|
282
|
+
'-maxrate',
|
|
283
|
+
bitrate,
|
|
284
|
+
'-bufsize',
|
|
285
|
+
bitrate,
|
|
286
|
+
]
|
|
287
|
+
: ['-c:v', 'libvpx', '-deadline', 'realtime', '-cpu-used', '4', '-pix_fmt', 'yuv420p', '-g', keyFrameInterval, '-b:v', bitrate, '-maxrate', bitrate, '-bufsize', bitrate];
|
|
258
288
|
const ffmpegArgs = [
|
|
259
289
|
'-hide_banner',
|
|
260
290
|
'-loglevel',
|
package/dist/demoDevices.js
CHANGED
|
@@ -883,6 +883,7 @@ export async function createDemoDevices(matterbridge) {
|
|
|
883
883
|
id: 'Intercom',
|
|
884
884
|
number: EndpointNumber(16_04),
|
|
885
885
|
identifyType: Identify.IdentifyType.VisibleIndicator,
|
|
886
|
+
switchOptions: { number: EndpointNumber(16_04_2) },
|
|
886
887
|
weriftOfferOptions: { offerVideo: false, offerAudio: true, videoSource: 'none', audioSource: 'test' },
|
|
887
888
|
});
|
|
888
889
|
await registerDevice(ep, 'Intercom I', 'CAMERA-16-04');
|
|
@@ -890,6 +891,7 @@ export async function createDemoDevices(matterbridge) {
|
|
|
890
891
|
id: 'IntercomII',
|
|
891
892
|
number: EndpointNumber(16_04_1),
|
|
892
893
|
identifyType: Identify.IdentifyType.VisibleIndicator,
|
|
894
|
+
switchOptions: { number: EndpointNumber(16_04_1_2) },
|
|
893
895
|
weriftOfferOptions: { offerVideo: false, offerAudio: true, videoSource: 'none', audioSource: 'test' },
|
|
894
896
|
});
|
|
895
897
|
await registerDevice(ep, 'Intercom II', 'CAMERA-16-04-1');
|
package/dist/deviceManager.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class DeviceManager {
|
|
|
20
20
|
private toBaseDevice;
|
|
21
21
|
array(): MatterbridgeEndpoint[];
|
|
22
22
|
baseArray(pluginName?: string): BaseDevice[];
|
|
23
|
-
apiDeviceArray(pluginName?: string): ApiDevice[];
|
|
23
|
+
apiDeviceArray(pluginName?: string, extended?: boolean): ApiDevice[];
|
|
24
24
|
[Symbol.iterator](): IterableIterator<MatterbridgeEndpoint>;
|
|
25
25
|
forEach(callback: (device: MatterbridgeEndpoint) => Promise<void>): Promise<void>;
|
|
26
26
|
set logLevel(logLevel: LogLevel);
|
package/dist/deviceManager.js
CHANGED
|
@@ -71,7 +71,7 @@ export class DeviceManager {
|
|
|
71
71
|
this.server.respond({ ...msg, result: { devices: this.baseArray(msg.params.pluginName) } });
|
|
72
72
|
break;
|
|
73
73
|
case 'devices_apidevicearray':
|
|
74
|
-
this.server.respond({ ...msg, result: { devices: this.apiDeviceArray(msg.params.pluginName) } });
|
|
74
|
+
this.server.respond({ ...msg, result: { devices: this.apiDeviceArray(msg.params.pluginName, msg.params.extended) } });
|
|
75
75
|
break;
|
|
76
76
|
default:
|
|
77
77
|
if (this.verbose)
|
|
@@ -124,12 +124,12 @@ export class DeviceManager {
|
|
|
124
124
|
}
|
|
125
125
|
return devices;
|
|
126
126
|
}
|
|
127
|
-
apiDeviceArray(pluginName) {
|
|
127
|
+
apiDeviceArray(pluginName, extended) {
|
|
128
128
|
const devices = [];
|
|
129
129
|
for (const device of this._devices.values()) {
|
|
130
130
|
if (pluginName && pluginName !== device.plugin)
|
|
131
131
|
continue;
|
|
132
|
-
const apiDevice = toApiDevice(device);
|
|
132
|
+
const apiDevice = toApiDevice(device, extended);
|
|
133
133
|
if (apiDevice)
|
|
134
134
|
devices.push(apiDevice);
|
|
135
135
|
}
|
|
@@ -9,6 +9,7 @@ export interface BatteryStorageOptions {
|
|
|
9
9
|
tagList?: Semtag[];
|
|
10
10
|
batPercentRemaining?: number;
|
|
11
11
|
batChargeLevel?: PowerSource.BatChargeLevel;
|
|
12
|
+
batCapacity?: number;
|
|
12
13
|
voltage?: number | bigint | null;
|
|
13
14
|
current?: number | bigint | null;
|
|
14
15
|
power?: number | bigint | null;
|
|
@@ -31,7 +31,7 @@ export class BatteryStorage extends MatterbridgeEndpoint {
|
|
|
31
31
|
const battery = this.addChildDeviceType('Battery', [powerSource, electricalSensor], {
|
|
32
32
|
tagList: [getSemtag(PowerSourceTag.Battery)],
|
|
33
33
|
})
|
|
34
|
-
.createDefaultPowerSourceRechargeableBatteryClusterServer(options.batPercentRemaining ?? 100, options.batChargeLevel ?? PowerSource.BatChargeLevel.Ok, 24_000)
|
|
34
|
+
.createDefaultPowerSourceRechargeableBatteryClusterServer(options.batPercentRemaining ?? 100, options.batChargeLevel ?? PowerSource.BatChargeLevel.Ok, 24_000, PowerSource.BatReplaceability.Unspecified, options.batCapacity ?? 200_000, null, null, [])
|
|
35
35
|
.createDefaultPowerTopologyClusterServer()
|
|
36
36
|
.createDefaultElectricalEnergyMeasurementClusterServer(options.energyImported ?? null, options.energyExported ?? null);
|
|
37
37
|
battery.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.DirectCurrent), getDefaultElectricalPowerMeasurementClusterServer(options.voltage ?? null, options.current ?? null, options.power ?? null));
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { CameraAvStreamManagement } from '@matter/types/clusters/camera-av-stream-management';
|
|
2
2
|
import { Identify } from '@matter/types/clusters/identify';
|
|
3
|
-
import {
|
|
3
|
+
import type { EndpointNumber } from '@matter/types/datatype';
|
|
4
|
+
import { type Semtag, StreamUsage } from '@matter/types/globals';
|
|
4
5
|
import type { WeriftOfferOptions } from '../behaviors/weriftSession.js';
|
|
5
6
|
import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
6
7
|
import type { MatterbridgeEndpointOptions } from '../matterbridgeEndpointTypes.js';
|
|
8
|
+
export interface IntercomSwitchOptions {
|
|
9
|
+
name?: string;
|
|
10
|
+
tagList?: Semtag[];
|
|
11
|
+
number?: EndpointNumber;
|
|
12
|
+
identifyTime?: number;
|
|
13
|
+
identifyType?: Identify.IdentifyType;
|
|
14
|
+
}
|
|
7
15
|
export interface IntercomOptions extends MatterbridgeEndpointOptions {
|
|
8
16
|
identifyTime?: number;
|
|
9
17
|
identifyType?: Identify.IdentifyType;
|
|
@@ -16,7 +24,9 @@ export interface IntercomOptions extends MatterbridgeEndpointOptions {
|
|
|
16
24
|
speakerCapabilities?: CameraAvStreamManagement.AudioCapabilities;
|
|
17
25
|
twoWayTalkSupport?: CameraAvStreamManagement.TwoWayTalkSupportType;
|
|
18
26
|
weriftOfferOptions?: WeriftOfferOptions;
|
|
27
|
+
switchOptions?: IntercomSwitchOptions;
|
|
19
28
|
}
|
|
20
29
|
export declare class Intercom extends MatterbridgeEndpoint {
|
|
21
30
|
constructor(name: string, serial: string, options?: IntercomOptions);
|
|
31
|
+
addSwitch(name: string, options?: IntercomSwitchOptions): MatterbridgeEndpoint;
|
|
22
32
|
}
|
package/dist/devices/intercom.js
CHANGED
|
@@ -5,11 +5,11 @@ import { createDefaultIntercomCameraAvStreamManagementClusterServer } from '../b
|
|
|
5
5
|
import { addChimeClient, addWebRtcTransportProviderClient, addWebRtcTransportRequestorClient } from '../behaviors/clients.js';
|
|
6
6
|
import { createDefaultWebRtcTransportProviderClusterServer } from '../behaviors/webRtcTransportProviderServer.js';
|
|
7
7
|
import { createDefaultWebRtcTransportRequestorClusterServer } from '../behaviors/webRtcTransportRequestorServer.js';
|
|
8
|
-
import { intercom, powerSource } from '../matterbridgeDeviceTypes.js';
|
|
8
|
+
import { genericSwitch, intercom, powerSource } from '../matterbridgeDeviceTypes.js';
|
|
9
9
|
import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
10
10
|
export class Intercom extends MatterbridgeEndpoint {
|
|
11
11
|
constructor(name, serial, options = {}) {
|
|
12
|
-
const { identifyTime = 0, identifyType = Identify.IdentifyType.None, powerSourceType = 'Wired', maxContentBufferSize = 65_536, maxNetworkBandwidth = 128_000, supportedStreamUsages = [StreamUsage.LiveView], streamUsagePriorities = supportedStreamUsages, microphoneCapabilities = { maxNumberOfChannels: 1, supportedCodecs: [CameraAvStreamManagement.AudioCodec.Opus], supportedSampleRates: [48000], supportedBitDepths: [16] }, speakerCapabilities = { maxNumberOfChannels: 1, supportedCodecs: [CameraAvStreamManagement.AudioCodec.Opus], supportedSampleRates: [48000], supportedBitDepths: [16] }, twoWayTalkSupport = CameraAvStreamManagement.TwoWayTalkSupportType.FullDuplex, weriftOfferOptions, id, number, tagList, mode, } = options;
|
|
12
|
+
const { identifyTime = 0, identifyType = Identify.IdentifyType.None, powerSourceType = 'Wired', maxContentBufferSize = 65_536, maxNetworkBandwidth = 128_000, supportedStreamUsages = [StreamUsage.LiveView], streamUsagePriorities = supportedStreamUsages, microphoneCapabilities = { maxNumberOfChannels: 1, supportedCodecs: [CameraAvStreamManagement.AudioCodec.Opus], supportedSampleRates: [48000], supportedBitDepths: [16] }, speakerCapabilities = { maxNumberOfChannels: 1, supportedCodecs: [CameraAvStreamManagement.AudioCodec.Opus], supportedSampleRates: [48000], supportedBitDepths: [16] }, twoWayTalkSupport = CameraAvStreamManagement.TwoWayTalkSupportType.FullDuplex, weriftOfferOptions, switchOptions = {}, id, number, tagList, mode, } = options;
|
|
13
13
|
super(powerSourceType === 'None' ? [intercom] : [intercom, powerSource], {
|
|
14
14
|
id: id ?? `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
|
|
15
15
|
number,
|
|
@@ -51,5 +51,16 @@ export class Intercom extends MatterbridgeEndpoint {
|
|
|
51
51
|
addWebRtcTransportRequestorClient(this);
|
|
52
52
|
addChimeClient(this);
|
|
53
53
|
this.addRequiredClusters();
|
|
54
|
+
const { name: switchName = 'Switch', tagList: switchTagList = [], number: switchNumber, identifyTime: switchIdentifyTime = 0, identifyType: switchIdentifyType = Identify.IdentifyType.None, } = switchOptions;
|
|
55
|
+
this.addSwitch(switchName, { tagList: switchTagList, number: switchNumber, identifyTime: switchIdentifyTime, identifyType: switchIdentifyType });
|
|
56
|
+
}
|
|
57
|
+
addSwitch(name, options = {}) {
|
|
58
|
+
const { tagList = [], number, identifyTime = 0, identifyType = Identify.IdentifyType.None } = options;
|
|
59
|
+
const switchChild = this.addChildDeviceType(name, genericSwitch, { number, ...(tagList.length > 0 ? { tagList } : {}) });
|
|
60
|
+
switchChild.log.logName = name;
|
|
61
|
+
switchChild.createDefaultIdentifyClusterServer(identifyTime, identifyType);
|
|
62
|
+
switchChild.createDefaultMomentarySwitchClusterServer();
|
|
63
|
+
switchChild.addRequiredClusters();
|
|
64
|
+
return switchChild;
|
|
54
65
|
}
|
|
55
66
|
}
|
package/dist/frontend.d.ts
CHANGED
|
@@ -40,8 +40,8 @@ export declare class Frontend extends EventEmitter<FrontendEvents> {
|
|
|
40
40
|
stop(): Promise<void>;
|
|
41
41
|
getApiSettings(): ApiSettings;
|
|
42
42
|
getApiPlugins(): ApiPlugin[];
|
|
43
|
-
getApiDevices(pluginName?: string): ApiDevice[];
|
|
44
|
-
getApiCluster(pluginName: string, endpointNumber: number, serialNumber
|
|
43
|
+
getApiDevices(pluginName?: string, extended?: boolean): ApiDevice[];
|
|
44
|
+
getApiCluster(pluginName: string, endpointNumber: number, serialNumber: string, uniqueId: string): ApiClusters | undefined;
|
|
45
45
|
generateDiagnostic(): Promise<void>;
|
|
46
46
|
private wsMessageHandler;
|
|
47
47
|
wssSendLogMessage(level: string, time: string, name: string, message: string): void;
|
package/dist/frontend.js
CHANGED
|
@@ -15,10 +15,9 @@ import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/fo
|
|
|
15
15
|
import { logModuleLoaded } from '@matterbridge/utils/loader';
|
|
16
16
|
import { isValidArray, isValidBoolean, isValidNumber, isValidObject, isValidString } from '@matterbridge/utils/validate';
|
|
17
17
|
import { fireAndForget, wait, withTimeout } from '@matterbridge/utils/wait';
|
|
18
|
-
import { AnsiLogger, bgHex, CYAN, db, debugStringify, er, GREEN, nf, nt, rs,
|
|
18
|
+
import { AnsiLogger, bgHex, CYAN, db, debugStringify, er, GREEN, nf, nt, rs, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from 'node-ansi-logger';
|
|
19
19
|
import { generateHistoryPage } from './cliHistory.js';
|
|
20
|
-
import {
|
|
21
|
-
import { deleteAdvertisingNode, getBatteryLevel, getClusterTextFromDevice, getPowerSource, getReachability, getServerNodeData, setAdvertisingNode } from './matterNodeHelpers.js';
|
|
20
|
+
import { deleteAdvertisingNode, getBatteryLevel, getClusterTextFromDevice, getPowerSource, getReachability, getServerNodeData, setAdvertisingNode, toApiClusters, } from './matterNodeHelpers.js';
|
|
22
21
|
logModuleLoaded('Frontend');
|
|
23
22
|
function sendDownloadError(res, error, message) {
|
|
24
23
|
if (error.code === 'ECONNABORTED' || res.headersSent)
|
|
@@ -40,10 +39,10 @@ export class Frontend extends EventEmitter {
|
|
|
40
39
|
webSocketServer;
|
|
41
40
|
server;
|
|
42
41
|
serverFetchTimeout = 2000;
|
|
43
|
-
debug =
|
|
44
|
-
verbose =
|
|
45
|
-
diagnostic =
|
|
46
|
-
readOnly = hasParameter('readonly')
|
|
42
|
+
debug = hasAnyParameter('debug', 'debug-frontend', 'verbose', 'verbose-frontend');
|
|
43
|
+
verbose = hasAnyParameter('verbose', 'verbose-frontend');
|
|
44
|
+
diagnostic = hasAnyParameter('diagnostic', 'diagnostic-frontend');
|
|
45
|
+
readOnly = hasParameter('readonly');
|
|
47
46
|
restartRequired = false;
|
|
48
47
|
fixedRestartRequired = false;
|
|
49
48
|
updateRequired = false;
|
|
@@ -531,7 +530,7 @@ export class Frontend extends EventEmitter {
|
|
|
531
530
|
this.log.debug('The frontend sent /api/devices');
|
|
532
531
|
if (!this.validateReq(req, res))
|
|
533
532
|
return;
|
|
534
|
-
res.json(this.getApiDevices());
|
|
533
|
+
res.json(this.getApiDevices(undefined, true));
|
|
535
534
|
});
|
|
536
535
|
this.expressApp.get('/api/view-mblog', async (req, res) => {
|
|
537
536
|
this.log.debug('The frontend sent /api/view-mblog');
|
|
@@ -1007,7 +1006,7 @@ export class Frontend extends EventEmitter {
|
|
|
1007
1006
|
}
|
|
1008
1007
|
return plugins;
|
|
1009
1008
|
}
|
|
1010
|
-
getApiDevices(pluginName) {
|
|
1009
|
+
getApiDevices(pluginName, extended = false) {
|
|
1011
1010
|
if (this.matterbridge.hasCleanupStarted)
|
|
1012
1011
|
return [];
|
|
1013
1012
|
const devices = [];
|
|
@@ -1026,11 +1025,11 @@ export class Frontend extends EventEmitter {
|
|
|
1026
1025
|
productUrl: device.productUrl,
|
|
1027
1026
|
configUrl: device.configUrl,
|
|
1028
1027
|
uniqueId: device.uniqueId,
|
|
1029
|
-
reachable: getReachability(device),
|
|
1030
|
-
powerSource: getPowerSource(device),
|
|
1031
|
-
batteryLevel: getBatteryLevel(device),
|
|
1032
|
-
matter: device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
|
|
1033
|
-
cluster: getClusterTextFromDevice(device),
|
|
1028
|
+
reachable: extended ? getReachability(device) : true,
|
|
1029
|
+
powerSource: extended ? getPowerSource(device) : undefined,
|
|
1030
|
+
batteryLevel: extended ? getBatteryLevel(device) : undefined,
|
|
1031
|
+
matter: extended && device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
|
|
1032
|
+
cluster: extended ? getClusterTextFromDevice(device) : '',
|
|
1034
1033
|
});
|
|
1035
1034
|
}
|
|
1036
1035
|
return devices;
|
|
@@ -1040,64 +1039,12 @@ export class Frontend extends EventEmitter {
|
|
|
1040
1039
|
return;
|
|
1041
1040
|
const endpoint = this.matterbridge.devices
|
|
1042
1041
|
.array()
|
|
1043
|
-
.find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber &&
|
|
1044
|
-
if (!endpoint?.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
|
|
1045
|
-
this.log.error(`getApiCluster: no device found for plugin ${pluginName} and endpoint number ${endpointNumber} (serial: ${serialNumber
|
|
1042
|
+
.find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber && d.serialNumber === serialNumber && d.uniqueId === uniqueId);
|
|
1043
|
+
if (!endpoint?.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber || !endpoint.uniqueId) {
|
|
1044
|
+
this.log.error(`getApiCluster: no device found for plugin ${pluginName} and endpoint number ${endpointNumber} (serial: ${serialNumber}, uniqueId: ${uniqueId})`);
|
|
1046
1045
|
return;
|
|
1047
1046
|
}
|
|
1048
|
-
|
|
1049
|
-
const clusters = [];
|
|
1050
|
-
endpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1051
|
-
deviceTypes.push(d.deviceType);
|
|
1052
|
-
});
|
|
1053
|
-
endpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1054
|
-
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1055
|
-
return;
|
|
1056
|
-
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1057
|
-
return;
|
|
1058
|
-
clusters.push({
|
|
1059
|
-
endpoint: endpoint.number.toString(),
|
|
1060
|
-
number: endpoint.number,
|
|
1061
|
-
id: 'main',
|
|
1062
|
-
deviceTypes,
|
|
1063
|
-
clusterName: capitalizeFirstLetter(clusterName),
|
|
1064
|
-
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
1065
|
-
attributeName,
|
|
1066
|
-
attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
|
|
1067
|
-
attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
|
|
1068
|
-
attributeLocalValue: attributeValue,
|
|
1069
|
-
});
|
|
1070
|
-
});
|
|
1071
|
-
const childEndpoints = endpoint.getChildEndpoints();
|
|
1072
|
-
childEndpoints.forEach((childEndpoint) => {
|
|
1073
|
-
if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
|
|
1074
|
-
this.log.error(`getApiCluster: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
const deviceTypes = [];
|
|
1078
|
-
childEndpoint.state.descriptor.deviceTypeList.forEach((d) => {
|
|
1079
|
-
deviceTypes.push(d.deviceType);
|
|
1080
|
-
});
|
|
1081
|
-
childEndpoint.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
1082
|
-
if (typeof attributeValue === 'undefined' || attributeValue === undefined)
|
|
1083
|
-
return;
|
|
1084
|
-
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
1085
|
-
return;
|
|
1086
|
-
clusters.push({
|
|
1087
|
-
endpoint: childEndpoint.number.toString(),
|
|
1088
|
-
number: childEndpoint.number,
|
|
1089
|
-
id: childEndpoint.id,
|
|
1090
|
-
deviceTypes,
|
|
1091
|
-
clusterName: capitalizeFirstLetter(clusterName),
|
|
1092
|
-
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
1093
|
-
attributeName,
|
|
1094
|
-
attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
|
|
1095
|
-
attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
|
|
1096
|
-
attributeLocalValue: attributeValue,
|
|
1097
|
-
});
|
|
1098
|
-
});
|
|
1099
|
-
});
|
|
1100
|
-
return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
|
|
1047
|
+
return toApiClusters(endpoint, this.log);
|
|
1101
1048
|
}
|
|
1102
1049
|
async generateDiagnostic() {
|
|
1103
1050
|
this.log.debug('Generating diagnostic...');
|
|
@@ -1493,24 +1440,32 @@ export class Frontend extends EventEmitter {
|
|
|
1493
1440
|
}
|
|
1494
1441
|
else if (data.method === '/api/devices') {
|
|
1495
1442
|
const start = performance.now();
|
|
1496
|
-
const devices = this.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
|
|
1443
|
+
const devices = this.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined, data.params.extended);
|
|
1497
1444
|
if (this.diagnostic)
|
|
1498
1445
|
writeDiagnostic(`Frontend:${data.sender}`, `getApiDevices() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
1499
1446
|
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
|
|
1500
1447
|
}
|
|
1501
1448
|
else if (data.method === '/api/clusters') {
|
|
1502
|
-
if (!isValidString(data.params.
|
|
1503
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter
|
|
1449
|
+
if (!isValidString(data.params.pluginName, 10)) {
|
|
1450
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter pluginName in /api/clusters' });
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
if (!isValidNumber(data.params.number, 1)) {
|
|
1454
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter number in /api/clusters' });
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (!isValidString(data.params.serialNumber, 1)) {
|
|
1458
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter serialNumber in /api/clusters' });
|
|
1504
1459
|
return;
|
|
1505
1460
|
}
|
|
1506
|
-
if (!
|
|
1507
|
-
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter
|
|
1461
|
+
if (!isValidString(data.params.uniqueId, 1)) {
|
|
1462
|
+
sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter uniqueId in /api/clusters' });
|
|
1508
1463
|
return;
|
|
1509
1464
|
}
|
|
1510
1465
|
const start = performance.now();
|
|
1511
|
-
const clusters = this.getApiCluster(data.params.
|
|
1466
|
+
const clusters = this.getApiCluster(data.params.pluginName, data.params.number, data.params.serialNumber, data.params.uniqueId);
|
|
1512
1467
|
if (this.diagnostic)
|
|
1513
|
-
writeDiagnostic(`Frontend:${data.sender}`, `getApiCluster() took ${(performance.now() - start).toFixed(2)} ms`);
|
|
1468
|
+
writeDiagnostic(`Frontend:${data.sender}`, `getApiCluster(${data.params.pluginName}, ${data.params.number}, ${data.params.serialNumber}, ${data.params.uniqueId}) took ${(performance.now() - start).toFixed(2)} ms`);
|
|
1514
1469
|
if (clusters) {
|
|
1515
1470
|
sendResponse({
|
|
1516
1471
|
id: data.id,
|
package/dist/matterNode.js
CHANGED
|
@@ -97,7 +97,7 @@ export class MatterNode extends EventEmitter {
|
|
|
97
97
|
this.environment.vars.set('path.root', path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR));
|
|
98
98
|
this.environment.vars.set('runtime.signals', false);
|
|
99
99
|
this.environment.vars.set('runtime.exitcode', false);
|
|
100
|
-
this.environment.vars.set('endpoint.validation', '
|
|
100
|
+
this.environment.vars.set('endpoint.validation', 'warn');
|
|
101
101
|
if (this.mdnsInterface)
|
|
102
102
|
this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
|
|
103
103
|
if (this.verbose)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ServerNode, type SessionsBehavior } from '@matter/node';
|
|
2
2
|
import type { ExposedFabricInformation } from '@matter/protocol';
|
|
3
|
-
import type { ApiDevice, ApiMatter, BaseDevice, SanitizedExposedFabricInformation, SanitizedSession } from '@matterbridge/types';
|
|
3
|
+
import type { ApiClusters, ApiDevice, ApiMatter, BaseDevice, SanitizedExposedFabricInformation, SanitizedSession } from '@matterbridge/types';
|
|
4
|
+
import { type AnsiLogger } from 'node-ansi-logger';
|
|
4
5
|
import type { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
6
|
+
export declare function toApiClusters(endpoint: MatterbridgeEndpoint, log: AnsiLogger): ApiClusters | undefined;
|
|
5
7
|
export declare function toBaseDevice(device: MatterbridgeEndpoint | BaseDevice): BaseDevice;
|
|
6
|
-
export declare function toApiDevice(device: MatterbridgeEndpoint): ApiDevice | undefined;
|
|
8
|
+
export declare function toApiDevice(device: MatterbridgeEndpoint, extended?: boolean): ApiDevice | undefined;
|
|
7
9
|
export declare function getReachability(device: MatterbridgeEndpoint): boolean;
|
|
8
10
|
export declare function getPowerSource(endpoint: MatterbridgeEndpoint): 'ac' | 'dc' | 'ok' | 'warning' | 'critical' | undefined;
|
|
9
11
|
export declare function getBatteryLevel(endpoint: MatterbridgeEndpoint): number | undefined;
|
|
@@ -11,15 +11,55 @@ import { OperationalState } from '@matter/types/clusters/operational-state';
|
|
|
11
11
|
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
12
12
|
import { RvcOperationalState } from '@matter/types/clusters/rvc-operational-state';
|
|
13
13
|
import { SmokeCoAlarm } from '@matter/types/clusters/smoke-co-alarm';
|
|
14
|
-
import { SoilMeasurement } from '@matter/types/clusters/soil-measurement';
|
|
15
14
|
import { UserLabel } from '@matter/types/clusters/user-label';
|
|
16
15
|
import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
|
|
17
16
|
import { ThreeLevelAuto } from '@matter/types/globals';
|
|
18
17
|
import { getEnumDescription } from '@matterbridge/utils/enum';
|
|
19
18
|
import { isValidArray, isValidNumber, isValidObject } from '@matterbridge/utils/validate';
|
|
20
19
|
import { stringify } from 'node-ansi-logger';
|
|
21
|
-
import { featuresFor, getAttribute } from './matterbridgeEndpointHelpers.js';
|
|
20
|
+
import { capitalizeFirstLetter, featuresFor, getAttribute, getSnapshot } from './matterbridgeEndpointHelpers.js';
|
|
22
21
|
const advertisingNodes = new Map();
|
|
22
|
+
export function toApiClusters(endpoint, log) {
|
|
23
|
+
if (!endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber || !endpoint.uniqueId)
|
|
24
|
+
return undefined;
|
|
25
|
+
const deviceTypes = getSnapshot(endpoint.state.descriptor.deviceTypeList).map((deviceType) => deviceType.deviceType);
|
|
26
|
+
const clusters = [];
|
|
27
|
+
for (const current of [endpoint, ...endpoint.getChildEndpoints()]) {
|
|
28
|
+
if (!current.maybeId || !current.maybeNumber) {
|
|
29
|
+
log.error(`getApiCluster: no child endpoint found for plugin ${endpoint.plugin} and endpoint number ${endpoint.number}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const currentDeviceTypes = current === endpoint ? deviceTypes : getSnapshot(current.state.descriptor.deviceTypeList).map((deviceType) => deviceType.deviceType);
|
|
33
|
+
current.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
|
|
34
|
+
if (attributeValue === undefined)
|
|
35
|
+
return;
|
|
36
|
+
if (clusterName === 'EveHistory' && ['configDataGet', 'configDataSet', 'historyStatus', 'historyEntries', 'historyRequest', 'historySetTime', 'rLoc'].includes(attributeName))
|
|
37
|
+
return;
|
|
38
|
+
clusters.push({
|
|
39
|
+
endpoint: current.number.toString(),
|
|
40
|
+
number: current.number,
|
|
41
|
+
id: current === endpoint ? 'main' : current.id,
|
|
42
|
+
deviceTypes: currentDeviceTypes,
|
|
43
|
+
clusterName: capitalizeFirstLetter(clusterName),
|
|
44
|
+
clusterId: '0x' + clusterId.toString(16).padStart(2, '0'),
|
|
45
|
+
attributeName,
|
|
46
|
+
attributeId: '0x' + attributeId.toString(16).padStart(2, '0'),
|
|
47
|
+
attributeValue: typeof attributeValue === 'object' ? stringify(attributeValue) : attributeValue.toString(),
|
|
48
|
+
attributeLocalValue: getSnapshot(attributeValue),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
plugin: endpoint.plugin,
|
|
54
|
+
deviceName: endpoint.deviceName,
|
|
55
|
+
serialNumber: endpoint.serialNumber,
|
|
56
|
+
uniqueId: endpoint.uniqueId,
|
|
57
|
+
number: endpoint.number,
|
|
58
|
+
id: endpoint.id,
|
|
59
|
+
deviceTypes,
|
|
60
|
+
clusters,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
23
63
|
export function toBaseDevice(device) {
|
|
24
64
|
return {
|
|
25
65
|
mode: device.mode,
|
|
@@ -48,7 +88,7 @@ export function toBaseDevice(device) {
|
|
|
48
88
|
id: device.id,
|
|
49
89
|
};
|
|
50
90
|
}
|
|
51
|
-
export function toApiDevice(device) {
|
|
91
|
+
export function toApiDevice(device, extended = false) {
|
|
52
92
|
if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
|
|
53
93
|
return undefined;
|
|
54
94
|
return {
|
|
@@ -61,11 +101,11 @@ export function toApiDevice(device) {
|
|
|
61
101
|
productUrl: device.productUrl,
|
|
62
102
|
configUrl: device.configUrl,
|
|
63
103
|
uniqueId: device.uniqueId,
|
|
64
|
-
reachable: getReachability(device),
|
|
65
|
-
powerSource: getPowerSource(device),
|
|
66
|
-
batteryLevel: getBatteryLevel(device),
|
|
67
|
-
matter: device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
|
|
68
|
-
cluster: getClusterTextFromDevice(device),
|
|
104
|
+
reachable: extended ? getReachability(device) : true,
|
|
105
|
+
powerSource: extended ? getPowerSource(device) : undefined,
|
|
106
|
+
batteryLevel: extended ? getBatteryLevel(device) : undefined,
|
|
107
|
+
matter: extended && device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
|
|
108
|
+
cluster: extended ? getClusterTextFromDevice(device) : '',
|
|
69
109
|
};
|
|
70
110
|
}
|
|
71
111
|
export function getReachability(device) {
|
|
@@ -301,7 +341,7 @@ export function getClusterTextFromDevice(device) {
|
|
|
301
341
|
appendMeasurement('Pressure', getMeasurementText(attributeValue, 1, ' hPa'));
|
|
302
342
|
if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue')
|
|
303
343
|
appendMeasurement('Flow', getMeasurementText(attributeValue, 10, ' m³/h'));
|
|
304
|
-
if (
|
|
344
|
+
if (clusterName === 'soilMeasurement' && attributeName === 'soilMoistureMeasuredValue')
|
|
305
345
|
appendMeasurement('Soil moisture', getMeasurementText(attributeValue, 1, '%'));
|
|
306
346
|
if (clusterName === 'electricalPowerMeasurement' && attributeName === 'voltage')
|
|
307
347
|
appendMeasurement('Voltage', getMeasurementText(attributeValue, 1_000, 'V'));
|
package/dist/matterbridge.js
CHANGED
|
@@ -460,7 +460,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
460
460
|
this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, MATTER_STORAGE_DIR));
|
|
461
461
|
this.environment.vars.set('runtime.signals', false);
|
|
462
462
|
this.environment.vars.set('runtime.exitcode', false);
|
|
463
|
-
this.environment.vars.set('endpoint.validation', '
|
|
463
|
+
this.environment.vars.set('endpoint.validation', 'warn');
|
|
464
464
|
this.registerProcessHandlers();
|
|
465
465
|
try {
|
|
466
466
|
this.log.debug(`Creating node storage manager: ${CYAN}${NODE_STORAGE_DIR}${db}`);
|
|
@@ -151,7 +151,7 @@ export declare class MatterbridgeEndpoint extends Endpoint {
|
|
|
151
151
|
createDefaultPowerSourceWiredClusterServer(wiredCurrentType?: PowerSource.WiredCurrentType): this;
|
|
152
152
|
createDefaultPowerSourceBatteryClusterServer(batPercentRemaining?: null | number, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: null | number, batReplaceability?: PowerSource.BatReplaceability): this;
|
|
153
153
|
createDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplacementDescription?: string, batQuantity?: number, batReplaceability?: PowerSource.BatReplaceability): this;
|
|
154
|
-
createDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplaceability?: PowerSource.BatReplaceability): this;
|
|
154
|
+
createDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplaceability?: PowerSource.BatReplaceability, batCapacity?: number, batTimeToFullCharge?: number | null, batChargingCurrent?: number | null, activeBatChargeFaults?: PowerSource.BatChargeFault[]): this;
|
|
155
155
|
createDefaultBasicInformationClusterServer(deviceName: string, serialNumber: string, vendorId?: number, vendorName?: string, productId?: number, productName?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string, productLabel?: string, productUrl?: string, configurationVersion?: number): this;
|
|
156
156
|
createDefaultBridgedDeviceBasicInformationClusterServer(deviceName: string, serialNumber: string, vendorId?: number, vendorName?: string, productName?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string, productLabel?: string, productUrl?: string, configurationVersion?: number, productId?: number): this;
|
|
157
157
|
createDefaultPowerTopologyClusterServer(feature?: PowerTopology.Feature, availableEndpoints?: EndpointNumber[], activeEndpoints?: EndpointNumber[]): this;
|
|
@@ -539,8 +539,8 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
539
539
|
this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Replaceable), getDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplacementDescription, batQuantity, batReplaceability));
|
|
540
540
|
return this;
|
|
541
541
|
}
|
|
542
|
-
createDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplaceability = PowerSource.BatReplaceability.Unspecified) {
|
|
543
|
-
this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable), getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplaceability));
|
|
542
|
+
createDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplaceability = PowerSource.BatReplaceability.Unspecified, batCapacity, batTimeToFullCharge, batChargingCurrent, activeBatChargeFaults) {
|
|
543
|
+
this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable), getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplaceability, batCapacity, batTimeToFullCharge, batChargingCurrent, activeBatChargeFaults));
|
|
544
544
|
return this;
|
|
545
545
|
}
|
|
546
546
|
createDefaultBasicInformationClusterServer(deviceName, serialNumber, vendorId = 0xfff1, vendorName = 'Matterbridge', productId = 0x8000, productName = 'Matterbridge device', softwareVersion = 1, softwareVersionString = '1.0.0', hardwareVersion = 1, hardwareVersionString = '1.0.0', productLabel = 'Matter Endpoint', productUrl = 'https://matterbridge.io', configurationVersion = 1) {
|
|
@@ -81,7 +81,7 @@ export declare function getDefaultPowerSourceReplaceableBatteryClusterServer(bat
|
|
|
81
81
|
replaceable: true;
|
|
82
82
|
wired: false;
|
|
83
83
|
}>, typeof MatterbridgePowerSourceServer>>;
|
|
84
|
-
export declare function getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplaceability?: PowerSource.BatReplaceability): Partial<import("@matter/node").ClusterState.Type<ClusterType.WithSupportedFeatures<PowerSource, {
|
|
84
|
+
export declare function getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining?: number | null, batChargeLevel?: PowerSource.BatChargeLevel, batVoltage?: number | null, batReplaceability?: PowerSource.BatReplaceability, batCapacity?: number, batTimeToFullCharge?: number | null, batChargingCurrent?: number | null, activeBatChargeFaults?: PowerSource.BatChargeFault[]): Partial<import("@matter/node").ClusterState.Type<ClusterType.WithSupportedFeatures<PowerSource, {
|
|
85
85
|
battery: true;
|
|
86
86
|
rechargeable: true;
|
|
87
87
|
replaceable: false;
|
|
@@ -927,7 +927,7 @@ export function getDefaultPowerSourceReplaceableBatteryClusterServer(batPercentR
|
|
|
927
927
|
batQuantity,
|
|
928
928
|
});
|
|
929
929
|
}
|
|
930
|
-
export function getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplaceability = PowerSource.BatReplaceability.Unspecified) {
|
|
930
|
+
export function getDefaultPowerSourceRechargeableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplaceability = PowerSource.BatReplaceability.Unspecified, batCapacity, batTimeToFullCharge, batChargingCurrent, activeBatChargeFaults) {
|
|
931
931
|
return optionsFor(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Rechargeable), {
|
|
932
932
|
status: PowerSource.PowerSourceStatus.Active,
|
|
933
933
|
order: 0,
|
|
@@ -943,6 +943,10 @@ export function getDefaultPowerSourceRechargeableBatteryClusterServer(batPercent
|
|
|
943
943
|
activeBatFaults: [],
|
|
944
944
|
batChargeState: PowerSource.BatChargeState.IsNotCharging,
|
|
945
945
|
batFunctionalWhileCharging: true,
|
|
946
|
+
...(batCapacity !== undefined ? { batCapacity } : {}),
|
|
947
|
+
...(batTimeToFullCharge !== undefined ? { batTimeToFullCharge } : {}),
|
|
948
|
+
...(batChargingCurrent !== undefined ? { batChargingCurrent } : {}),
|
|
949
|
+
...(activeBatChargeFaults !== undefined ? { activeBatChargeFaults } : {}),
|
|
946
950
|
});
|
|
947
951
|
}
|
|
948
952
|
export function getDefaultElectricalEnergyMeasurementClusterServer(energyImported = null, energyExported = null) {
|
|
@@ -11,7 +11,7 @@ import { NodeStorageManager } from 'node-persist-manager';
|
|
|
11
11
|
import { bridgedNode } from './matterbridgeDeviceTypes.js';
|
|
12
12
|
import { assertMatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
13
13
|
import { checkNotLatinCharacters } from './matterbridgeEndpointHelpers.js';
|
|
14
|
-
import { toApiDevice } from './matterNodeHelpers.js';
|
|
14
|
+
import { toApiClusters, toApiDevice } from './matterNodeHelpers.js';
|
|
15
15
|
logModuleLoaded('MatterbridgePlatform');
|
|
16
16
|
const MATTERBRIDGE_PLATFORM_BRAND = Symbol('MatterbridgePlatform.brand');
|
|
17
17
|
export function isMatterbridgePlatform(value) {
|
|
@@ -130,26 +130,41 @@ export class MatterbridgePlatform {
|
|
|
130
130
|
this.log.debug(`Received broadcast request ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}: ${debugStringify(msg)}${db}`);
|
|
131
131
|
switch (msg.type) {
|
|
132
132
|
case 'platform_get_apidevices':
|
|
133
|
-
if (msg.params.
|
|
133
|
+
if (msg.params.pluginName !== this.name)
|
|
134
134
|
return;
|
|
135
135
|
this.#server.respond({
|
|
136
136
|
...msg,
|
|
137
137
|
result: {
|
|
138
138
|
devices: this.getDevices()
|
|
139
|
-
.map(toApiDevice)
|
|
139
|
+
.map((device) => toApiDevice(device, msg.params.extended))
|
|
140
140
|
.filter((device) => device !== undefined),
|
|
141
141
|
},
|
|
142
142
|
});
|
|
143
143
|
break;
|
|
144
|
+
case 'platform_get_apiclusters': {
|
|
145
|
+
if (msg.params.pluginName !== this.name)
|
|
146
|
+
return;
|
|
147
|
+
const { number, serialNumber, uniqueId } = msg.params;
|
|
148
|
+
const endpoint = this.getDevices().find((device) => device.maybeNumber === number && device.serialNumber === serialNumber && device.uniqueId === uniqueId);
|
|
149
|
+
const clusters = endpoint ? toApiClusters(endpoint, this.log) : undefined;
|
|
150
|
+
if (!clusters) {
|
|
151
|
+
const error = `getApiCluster: no device found for plugin ${this.name} and endpoint number ${number} (serial: ${serialNumber}, uniqueId: ${uniqueId})`;
|
|
152
|
+
this.log.error(error);
|
|
153
|
+
this.#server.respond({ ...msg, error });
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
this.#server.respond({ ...msg, result: { clusters } });
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
144
159
|
case 'platform_get_apiselectdevices':
|
|
145
|
-
if (msg.params.
|
|
160
|
+
if (msg.params.pluginName !== this.name)
|
|
146
161
|
return;
|
|
147
|
-
this.#server.respond({ ...msg, result: { devices: this.getSelectDevices() } });
|
|
162
|
+
this.#server.respond({ ...msg, result: { devices: this.getSelectDevices().toSorted((keyA, keyB) => keyA.deviceName.localeCompare(keyB.deviceName)) } });
|
|
148
163
|
break;
|
|
149
164
|
case 'platform_get_apiselectentities':
|
|
150
|
-
if (msg.params.
|
|
165
|
+
if (msg.params.pluginName !== this.name)
|
|
151
166
|
return;
|
|
152
|
-
this.#server.respond({ ...msg, result: { entities: this.getSelectEntities() } });
|
|
167
|
+
this.#server.respond({ ...msg, result: { entities: this.getSelectEntities().toSorted((keyA, keyB) => keyA.name.localeCompare(keyB.name)) } });
|
|
153
168
|
break;
|
|
154
169
|
default:
|
|
155
170
|
if (this.#verbose)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterbridge/core",
|
|
3
|
-
"version": "3.10.14-dev-
|
|
3
|
+
"version": "3.10.14-dev-20261014-2811fdc",
|
|
4
4
|
"description": "Matterbridge core library",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"homepage": "https://matterbridge.io/",
|
|
@@ -136,10 +136,10 @@
|
|
|
136
136
|
},
|
|
137
137
|
"dependencies": {
|
|
138
138
|
"@matter/main": "0.17.9",
|
|
139
|
-
"@matterbridge/dgram": "3.10.14-dev-
|
|
140
|
-
"@matterbridge/thread": "3.10.14-dev-
|
|
141
|
-
"@matterbridge/types": "3.10.14-dev-
|
|
142
|
-
"@matterbridge/utils": "3.10.14-dev-
|
|
139
|
+
"@matterbridge/dgram": "3.10.14-dev-20261014-2811fdc",
|
|
140
|
+
"@matterbridge/thread": "3.10.14-dev-20261014-2811fdc",
|
|
141
|
+
"@matterbridge/types": "3.10.14-dev-20261014-2811fdc",
|
|
142
|
+
"@matterbridge/utils": "3.10.14-dev-20261014-2811fdc",
|
|
143
143
|
"escape-html": "1.0.3",
|
|
144
144
|
"express": "5.2.1",
|
|
145
145
|
"express-rate-limit": "8.7.1",
|