@matterbridge/core 3.10.10-dev-20260915-57739b4 → 3.10.10-dev-20260917-79954cd
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/behaviors/cameraAvSettingsUserLevelManagementServer.d.ts +22 -2
- package/dist/behaviors/cameraAvSettingsUserLevelManagementServer.js +172 -2
- package/dist/behaviors/cameraAvStreamManagementServer.js +6 -0
- package/dist/behaviors/colorControlServer.js +16 -2
- package/dist/behaviors/levelControlServer.js +11 -2
- package/dist/behaviors/matterbridgeServer.d.ts +2 -0
- package/dist/behaviors/matterbridgeServer.js +9 -1
- package/dist/behaviors/onOffServer.d.ts +2 -0
- package/dist/behaviors/onOffServer.js +14 -1
- package/dist/behaviors/webRtcTransportProviderServer.d.ts +1 -0
- package/dist/behaviors/webRtcTransportProviderServer.js +41 -5
- package/dist/behaviors/weriftSession.d.ts +6 -0
- package/dist/behaviors/weriftSession.js +7 -0
- package/dist/chipTests.d.ts +1 -0
- package/dist/chipTests.js +65 -1
- package/dist/cli.js +1 -0
- package/dist/demoDevices.js +1 -0
- package/dist/devices/camera.d.ts +1 -0
- package/dist/devices/camera.js +2 -2
- package/dist/frontend.js +1 -0
- package/dist/matterNode.js +11 -4
- package/dist/matterbridge.js +16 -4
- package/dist/matterbridgeEndpoint.d.ts +5 -5
- package/dist/matterbridgeEndpoint.js +10 -10
- package/dist/pluginManager.js +5 -1
- package/package.json +5 -5
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { CameraAvSettingsUserLevelManagementServer } from '@matter/node/behaviors/camera-av-settings-user-level-management';
|
|
2
|
+
import { type Viewport } from '@matter/types';
|
|
2
3
|
import { CameraAvSettingsUserLevelManagement } from '@matter/types/clusters/camera-av-settings-user-level-management';
|
|
4
|
+
import type { CameraAvStreamManagement } from '@matter/types/clusters/camera-av-stream-management';
|
|
3
5
|
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
4
6
|
declare const MatterbridgeCameraAvSettingsUserLevelManagementServer_base: import("@matter/node").ClusterBehavior.Type<typeof CameraAvSettingsUserLevelManagementServer, import("@matter/types").ClusterType.WithSupportedFeatures<CameraAvSettingsUserLevelManagement, {
|
|
5
|
-
digitalPtz:
|
|
7
|
+
digitalPtz: true;
|
|
6
8
|
mechanicalPan: true;
|
|
7
|
-
mechanicalPresets:
|
|
9
|
+
mechanicalPresets: true;
|
|
8
10
|
mechanicalTilt: true;
|
|
9
11
|
mechanicalZoom: true;
|
|
10
12
|
}>, import("@matter/types").ClusterType.Concrete, new () => {}, "cameraAvSettingsUserLevelManagement">;
|
|
11
13
|
export declare class MatterbridgeCameraAvSettingsUserLevelManagementServer extends MatterbridgeCameraAvSettingsUserLevelManagementServer_base {
|
|
12
14
|
readonly endpoint: MatterbridgeEndpoint;
|
|
15
|
+
protected internal: MatterbridgeCameraAvSettingsUserLevelManagementServer.Internal;
|
|
16
|
+
initialize(): void;
|
|
17
|
+
protected syncDptzStreams(allocatedVideoStreams: CameraAvStreamManagement.VideoStream[]): void;
|
|
18
|
+
protected syncDptzViewport(viewport: Viewport): void;
|
|
13
19
|
mptzSetPosition(request: CameraAvSettingsUserLevelManagement.MptzSetPositionRequest): void;
|
|
14
20
|
mptzRelativeMove(request: CameraAvSettingsUserLevelManagement.MptzRelativeMoveRequest): void;
|
|
21
|
+
mptzMoveToPreset(request: CameraAvSettingsUserLevelManagement.MptzMoveToPresetRequest): void;
|
|
22
|
+
mptzSavePreset(request: CameraAvSettingsUserLevelManagement.MptzSavePresetRequest): void;
|
|
23
|
+
mptzRemovePreset(request: CameraAvSettingsUserLevelManagement.MptzRemovePresetRequest): void;
|
|
24
|
+
private generatePresetId;
|
|
25
|
+
private dptzStreamEntry;
|
|
26
|
+
private applyDptzViewport;
|
|
27
|
+
dptzSetViewport(request: CameraAvSettingsUserLevelManagement.DptzSetViewportRequest): void;
|
|
28
|
+
dptzRelativeMove(request: CameraAvSettingsUserLevelManagement.DptzRelativeMoveRequest): void;
|
|
29
|
+
}
|
|
30
|
+
export declare namespace MatterbridgeCameraAvSettingsUserLevelManagementServer {
|
|
31
|
+
class Internal {
|
|
32
|
+
lastGeneratedPresetId: number;
|
|
33
|
+
}
|
|
15
34
|
}
|
|
16
35
|
export interface CameraAvSettingsUserLevelManagementClusterOptions {
|
|
17
36
|
panMin: number;
|
|
@@ -20,6 +39,7 @@ export interface CameraAvSettingsUserLevelManagementClusterOptions {
|
|
|
20
39
|
tiltMax: number;
|
|
21
40
|
zoomMax: number;
|
|
22
41
|
mptzPosition: CameraAvSettingsUserLevelManagement.Mptz;
|
|
42
|
+
maxPresets: number;
|
|
23
43
|
}
|
|
24
44
|
export declare function createDefaultCameraAvSettingsUserLevelManagementClusterServer(endpoint: MatterbridgeEndpoint, options: CameraAvSettingsUserLevelManagementClusterOptions): MatterbridgeEndpoint;
|
|
25
45
|
export {};
|
|
@@ -1,11 +1,45 @@
|
|
|
1
1
|
import { CameraAvSettingsUserLevelManagementServer } from '@matter/node/behaviors/camera-av-settings-user-level-management';
|
|
2
2
|
import { Status, StatusResponseError } from '@matter/types';
|
|
3
3
|
import { CameraAvSettingsUserLevelManagement } from '@matter/types/clusters/camera-av-settings-user-level-management';
|
|
4
|
+
import { MatterbridgeCameraAvStreamManagementServer } from './cameraAvStreamManagementServer.js';
|
|
4
5
|
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
5
6
|
function clamp(value, min, max) {
|
|
6
7
|
return Math.min(Math.max(value, min), max);
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
+
function viewportWidth(viewport) {
|
|
10
|
+
return viewport.x2 - viewport.x1;
|
|
11
|
+
}
|
|
12
|
+
function viewportHeight(viewport) {
|
|
13
|
+
return viewport.y2 - viewport.y1;
|
|
14
|
+
}
|
|
15
|
+
function sameAspectRatio(width, height, referenceWidth, referenceHeight) {
|
|
16
|
+
return width * referenceHeight === referenceWidth * height;
|
|
17
|
+
}
|
|
18
|
+
function viewportAt(x1, y1, width, height, sensorWidth, sensorHeight) {
|
|
19
|
+
const left = clamp(x1, 0, sensorWidth - width);
|
|
20
|
+
const top = clamp(y1, 0, sensorHeight - height);
|
|
21
|
+
return { x1: left, y1: top, x2: left + width, y2: top + height };
|
|
22
|
+
}
|
|
23
|
+
export class MatterbridgeCameraAvSettingsUserLevelManagementServer extends CameraAvSettingsUserLevelManagementServer.with(CameraAvSettingsUserLevelManagement.Feature.MechanicalPan, CameraAvSettingsUserLevelManagement.Feature.MechanicalTilt, CameraAvSettingsUserLevelManagement.Feature.MechanicalZoom, CameraAvSettingsUserLevelManagement.Feature.MechanicalPresets, CameraAvSettingsUserLevelManagement.Feature.DigitalPtz) {
|
|
24
|
+
initialize() {
|
|
25
|
+
const streamManagement = this.agent.get(MatterbridgeCameraAvStreamManagementServer);
|
|
26
|
+
this.syncDptzStreams(streamManagement.state.allocatedVideoStreams);
|
|
27
|
+
this.reactTo(streamManagement.events.allocatedVideoStreams$Changed, this.syncDptzStreams);
|
|
28
|
+
this.reactTo(streamManagement.events.viewport$Changed, this.syncDptzViewport);
|
|
29
|
+
}
|
|
30
|
+
syncDptzStreams(allocatedVideoStreams) {
|
|
31
|
+
const allocatedIds = allocatedVideoStreams.map((stream) => stream.videoStreamId);
|
|
32
|
+
const currentIds = this.state.dptzStreams.map((entry) => entry.videoStreamId);
|
|
33
|
+
if (allocatedIds.length === currentIds.length && allocatedIds.every((id, index) => id === currentIds[index]))
|
|
34
|
+
return;
|
|
35
|
+
const viewport = this.agent.get(MatterbridgeCameraAvStreamManagementServer).state.viewport;
|
|
36
|
+
this.state.dptzStreams = allocatedIds.map((videoStreamId) => this.state.dptzStreams.find((entry) => entry.videoStreamId === videoStreamId) ?? { videoStreamId, viewport });
|
|
37
|
+
}
|
|
38
|
+
syncDptzViewport(viewport) {
|
|
39
|
+
if (this.state.dptzStreams.length === 0)
|
|
40
|
+
return;
|
|
41
|
+
this.state.dptzStreams = this.state.dptzStreams.map((entry) => ({ videoStreamId: entry.videoStreamId, viewport }));
|
|
42
|
+
}
|
|
9
43
|
mptzSetPosition(request) {
|
|
10
44
|
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
11
45
|
const { pan, tilt, zoom } = request;
|
|
@@ -42,11 +76,147 @@ export class MatterbridgeCameraAvSettingsUserLevelManagementServer extends Camer
|
|
|
42
76
|
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: moved mechanical PTZ position by pan ${request.panDelta ?? 0}°, tilt ${request.tiltDelta ?? 0}°, zoom ${request.zoomDelta ?? 0} to pan ${pan}°, tilt ${tilt}°, zoom ${zoom} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
43
77
|
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'mptzRelativeMove', request, this.context);
|
|
44
78
|
}
|
|
79
|
+
mptzMoveToPreset(request) {
|
|
80
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
81
|
+
const { presetId } = request;
|
|
82
|
+
if (presetId < 1 || presetId > this.state.maxPresets) {
|
|
83
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: presetId ${presetId} is outside of the supported range [1, ${this.state.maxPresets}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
84
|
+
}
|
|
85
|
+
const preset = this.state.mptzPresets.find((entry) => entry.presetId === presetId);
|
|
86
|
+
if (preset === undefined) {
|
|
87
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: presetId ${presetId} is not present in mptzPresets (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
88
|
+
}
|
|
89
|
+
this.state.mptzPosition = preset.settings;
|
|
90
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: moved mechanical PTZ position to preset ${presetId} "${preset.name}": pan ${preset.settings.pan}°, tilt ${preset.settings.tilt}°, zoom ${preset.settings.zoom} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
91
|
+
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'mptzMoveToPreset', request, this.context);
|
|
92
|
+
}
|
|
93
|
+
mptzSavePreset(request) {
|
|
94
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
95
|
+
const { presetId, name } = request;
|
|
96
|
+
if (name.length > 32) {
|
|
97
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: name "${name}" exceeds the maximum length of 32 characters (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
98
|
+
}
|
|
99
|
+
if (presetId !== undefined && (presetId < 1 || presetId > this.state.maxPresets)) {
|
|
100
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: presetId ${presetId} is outside of the supported range [1, ${this.state.maxPresets}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
101
|
+
}
|
|
102
|
+
if (presetId !== undefined && this.state.mptzPresets.some((entry) => entry.presetId === presetId)) {
|
|
103
|
+
this.state.mptzPresets = this.state.mptzPresets.map((entry) => (entry.presetId === presetId ? { presetId, name, settings: this.state.mptzPosition } : entry));
|
|
104
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: updated preset ${presetId} "${name}" with the current mechanical PTZ position (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
105
|
+
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'mptzSavePreset', request, this.context);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (this.state.mptzPresets.length >= this.state.maxPresets) {
|
|
109
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: mptzPresets already holds the maximum of ${this.state.maxPresets} presets (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ResourceExhausted);
|
|
110
|
+
}
|
|
111
|
+
const newPresetId = presetId ?? this.generatePresetId();
|
|
112
|
+
this.state.mptzPresets = [...this.state.mptzPresets, { presetId: newPresetId, name, settings: this.state.mptzPosition }];
|
|
113
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: saved preset ${newPresetId} "${name}" with the current mechanical PTZ position (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
114
|
+
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'mptzSavePreset', request, this.context);
|
|
115
|
+
}
|
|
116
|
+
mptzRemovePreset(request) {
|
|
117
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
118
|
+
const { presetId } = request;
|
|
119
|
+
if (presetId < 1 || presetId > this.state.maxPresets) {
|
|
120
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: presetId ${presetId} is outside of the supported range [1, ${this.state.maxPresets}] (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
|
|
121
|
+
}
|
|
122
|
+
if (!this.state.mptzPresets.some((entry) => entry.presetId === presetId)) {
|
|
123
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: presetId ${presetId} is not present in mptzPresets (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
124
|
+
}
|
|
125
|
+
this.state.mptzPresets = this.state.mptzPresets.filter((entry) => entry.presetId !== presetId);
|
|
126
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: removed preset ${presetId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
127
|
+
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'mptzRemovePreset', request, this.context);
|
|
128
|
+
}
|
|
129
|
+
generatePresetId() {
|
|
130
|
+
let presetId = (this.internal.lastGeneratedPresetId % this.state.maxPresets) + 1;
|
|
131
|
+
while (this.state.mptzPresets.some((entry) => entry.presetId === presetId)) {
|
|
132
|
+
presetId = (presetId % this.state.maxPresets) + 1;
|
|
133
|
+
}
|
|
134
|
+
this.internal.lastGeneratedPresetId = presetId;
|
|
135
|
+
return presetId;
|
|
136
|
+
}
|
|
137
|
+
dptzStreamEntry(videoStreamId) {
|
|
138
|
+
const entry = this.state.dptzStreams.find((stream) => stream.videoStreamId === videoStreamId);
|
|
139
|
+
if (entry === undefined) {
|
|
140
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: video stream ${videoStreamId} is not present in dptzStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
141
|
+
}
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
applyDptzViewport(videoStreamId, viewport) {
|
|
145
|
+
this.state.dptzStreams = this.state.dptzStreams.map((entry) => (entry.videoStreamId === videoStreamId ? { videoStreamId, viewport } : entry));
|
|
146
|
+
}
|
|
147
|
+
dptzSetViewport(request) {
|
|
148
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
149
|
+
const { videoStreamId, viewport } = request;
|
|
150
|
+
const entry = this.dptzStreamEntry(videoStreamId);
|
|
151
|
+
const { videoSensorParams, minViewportResolution } = this.agent.get(MatterbridgeCameraAvStreamManagementServer).state;
|
|
152
|
+
const width = viewportWidth(viewport);
|
|
153
|
+
const height = viewportHeight(viewport);
|
|
154
|
+
if (width < minViewportResolution.width || height < minViewportResolution.height) {
|
|
155
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: viewport ${width}x${height} is smaller than the minimum viewport resolution ${minViewportResolution.width}x${minViewportResolution.height} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
156
|
+
}
|
|
157
|
+
if (viewport.x2 > videoSensorParams.sensorWidth ||
|
|
158
|
+
viewport.y2 > videoSensorParams.sensorHeight ||
|
|
159
|
+
width > videoSensorParams.sensorWidth ||
|
|
160
|
+
height > videoSensorParams.sensorHeight) {
|
|
161
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: viewport ${width}x${height} at (${viewport.x1}, ${viewport.y1}) does not fit the sensor ${videoSensorParams.sensorWidth}x${videoSensorParams.sensorHeight} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
162
|
+
}
|
|
163
|
+
if (!sameAspectRatio(width, height, viewportWidth(entry.viewport), viewportHeight(entry.viewport))) {
|
|
164
|
+
throw new StatusResponseError(`MatterbridgeCameraAvSettingsUserLevelManagementServer: viewport ${width}x${height} does not match the aspect ratio of video stream ${videoStreamId} (${viewportWidth(entry.viewport)}x${viewportHeight(entry.viewport)}) (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.DynamicConstraintError);
|
|
165
|
+
}
|
|
166
|
+
this.applyDptzViewport(videoStreamId, viewport);
|
|
167
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: set the viewport of video stream ${videoStreamId} to (${viewport.x1}, ${viewport.y1})-(${viewport.x2}, ${viewport.y2}) (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
168
|
+
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'dptzSetViewport', request, this.context);
|
|
169
|
+
}
|
|
170
|
+
dptzRelativeMove(request) {
|
|
171
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
172
|
+
const { videoStreamId, deltaX, deltaY, zoomDelta } = request;
|
|
173
|
+
const entry = this.dptzStreamEntry(videoStreamId);
|
|
174
|
+
const { videoSensorParams, minViewportResolution } = this.agent.get(MatterbridgeCameraAvStreamManagementServer).state;
|
|
175
|
+
const { sensorWidth, sensorHeight } = videoSensorParams;
|
|
176
|
+
const current = entry.viewport;
|
|
177
|
+
let x1 = current.x1 + (deltaX ?? 0);
|
|
178
|
+
let y1 = current.y1 + (deltaY ?? 0);
|
|
179
|
+
let width = viewportWidth(current);
|
|
180
|
+
let height = viewportHeight(current);
|
|
181
|
+
if (zoomDelta !== undefined && zoomDelta !== 0) {
|
|
182
|
+
const scaledWidth = Math.round((width * (100 - zoomDelta)) / 100);
|
|
183
|
+
const scaledHeight = Math.round((height * (100 - zoomDelta)) / 100);
|
|
184
|
+
x1 += Math.round((width - scaledWidth) / 2);
|
|
185
|
+
y1 += Math.round((height - scaledHeight) / 2);
|
|
186
|
+
width = scaledWidth;
|
|
187
|
+
height = scaledHeight;
|
|
188
|
+
}
|
|
189
|
+
const streamWidth = viewportWidth(current);
|
|
190
|
+
const streamHeight = viewportHeight(current);
|
|
191
|
+
if (!sameAspectRatio(width, height, streamWidth, streamHeight)) {
|
|
192
|
+
const clippedHeight = Math.round((width * streamHeight) / streamWidth);
|
|
193
|
+
if (clippedHeight <= height)
|
|
194
|
+
height = clippedHeight;
|
|
195
|
+
else
|
|
196
|
+
width = Math.round((height * streamWidth) / streamHeight);
|
|
197
|
+
}
|
|
198
|
+
width = Math.max(width, minViewportResolution.width);
|
|
199
|
+
height = Math.max(height, minViewportResolution.height);
|
|
200
|
+
width = Math.min(width, sensorWidth);
|
|
201
|
+
height = Math.min(height, sensorHeight);
|
|
202
|
+
const viewport = viewportAt(x1, y1, width, height, sensorWidth, sensorHeight);
|
|
203
|
+
this.applyDptzViewport(videoStreamId, viewport);
|
|
204
|
+
device.log.info(`MatterbridgeCameraAvSettingsUserLevelManagementServer: moved the viewport of video stream ${videoStreamId} by deltaX ${deltaX ?? 0}, deltaY ${deltaY ?? 0}, zoomDelta ${zoomDelta ?? 0} to (${viewport.x1}, ${viewport.y1})-(${viewport.x2}, ${viewport.y2}) (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
205
|
+
this.endpoint.emitCommand(CameraAvSettingsUserLevelManagement, 'dptzRelativeMove', request, this.context);
|
|
206
|
+
}
|
|
45
207
|
}
|
|
208
|
+
(function (MatterbridgeCameraAvSettingsUserLevelManagementServer) {
|
|
209
|
+
class Internal {
|
|
210
|
+
lastGeneratedPresetId = 0;
|
|
211
|
+
}
|
|
212
|
+
MatterbridgeCameraAvSettingsUserLevelManagementServer.Internal = Internal;
|
|
213
|
+
})(MatterbridgeCameraAvSettingsUserLevelManagementServer || (MatterbridgeCameraAvSettingsUserLevelManagementServer = {}));
|
|
46
214
|
export function createDefaultCameraAvSettingsUserLevelManagementClusterServer(endpoint, options) {
|
|
47
|
-
endpoint.behaviors.require(MatterbridgeCameraAvSettingsUserLevelManagementServer.with(CameraAvSettingsUserLevelManagement.Feature.MechanicalPan, CameraAvSettingsUserLevelManagement.Feature.MechanicalTilt, CameraAvSettingsUserLevelManagement.Feature.MechanicalZoom), {
|
|
215
|
+
endpoint.behaviors.require(MatterbridgeCameraAvSettingsUserLevelManagementServer.with(CameraAvSettingsUserLevelManagement.Feature.MechanicalPan, CameraAvSettingsUserLevelManagement.Feature.MechanicalTilt, CameraAvSettingsUserLevelManagement.Feature.MechanicalZoom, CameraAvSettingsUserLevelManagement.Feature.MechanicalPresets, CameraAvSettingsUserLevelManagement.Feature.DigitalPtz), {
|
|
48
216
|
...options,
|
|
49
217
|
movementState: CameraAvSettingsUserLevelManagement.PhysicalMovement.Idle,
|
|
218
|
+
mptzPresets: [],
|
|
219
|
+
dptzStreams: [],
|
|
50
220
|
});
|
|
51
221
|
return endpoint;
|
|
52
222
|
}
|
|
@@ -185,6 +185,9 @@ export class MatterbridgeCameraAvStreamManagementServer extends MatterbridgeCame
|
|
|
185
185
|
if (!this.state.allocatedVideoStreams.some((stream) => stream.videoStreamId === request.videoStreamId)) {
|
|
186
186
|
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: video stream ${request.videoStreamId} is not present in allocatedVideoStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
187
187
|
}
|
|
188
|
+
if (this.state.allocatedVideoStreams.some((stream) => stream.videoStreamId === request.videoStreamId && stream.referenceCount > 0)) {
|
|
189
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: video stream ${request.videoStreamId} is in use (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidInState);
|
|
190
|
+
}
|
|
188
191
|
this.state.allocatedVideoStreams = this.state.allocatedVideoStreams.filter((stream) => stream.videoStreamId !== request.videoStreamId);
|
|
189
192
|
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated video stream ${request.videoStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
190
193
|
this.endpoint.emitCommand(CameraAvStreamManagement, 'videoStreamDeallocate', request, this.context);
|
|
@@ -248,6 +251,9 @@ export class MatterbridgeCameraAvStreamManagementServer extends MatterbridgeCame
|
|
|
248
251
|
if (!this.state.allocatedAudioStreams.some((stream) => stream.audioStreamId === request.audioStreamId)) {
|
|
249
252
|
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: audio stream ${request.audioStreamId} is not present in allocatedAudioStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
250
253
|
}
|
|
254
|
+
if (this.state.allocatedAudioStreams.some((stream) => stream.audioStreamId === request.audioStreamId && stream.referenceCount > 0)) {
|
|
255
|
+
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: audio stream ${request.audioStreamId} is in use (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidInState);
|
|
256
|
+
}
|
|
251
257
|
this.state.allocatedAudioStreams = this.state.allocatedAudioStreams.filter((stream) => stream.audioStreamId !== request.audioStreamId);
|
|
252
258
|
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated audio stream ${request.audioStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
253
259
|
this.endpoint.emitCommand(CameraAvStreamManagement, 'audioStreamDeallocate', request, this.context);
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { ColorControlServer } from '@matter/node/behaviors/color-control';
|
|
2
2
|
import { ColorControl } from '@matter/types/clusters/color-control';
|
|
3
|
-
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
3
|
+
import { isSoftwareUpdateBoot, MatterbridgeServer } from './matterbridgeServer.js';
|
|
4
4
|
export class MatterbridgeColorControlServer extends ColorControlServer.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.Xy, ColorControl.Feature.ColorTemperature, ColorControl.Feature.EnhancedHue) {
|
|
5
5
|
initialize() {
|
|
6
6
|
if (process.env.MATTERBRIDGE_CHIP_TEST)
|
|
7
7
|
this.state.managedTransitionTimeHandling = true;
|
|
8
|
-
|
|
8
|
+
const result = super.initialize();
|
|
9
|
+
if (this.features.colorTemperature && !isSoftwareUpdateBoot(this.env)) {
|
|
10
|
+
const startUpMireds = this.state.startUpColorTemperatureMireds ?? null;
|
|
11
|
+
if (startUpMireds !== null) {
|
|
12
|
+
const crop = (mireds) => Math.min(Math.max(mireds, this.minimumColorTemperatureMireds), this.maximumColorTemperatureMireds);
|
|
13
|
+
const currentMireds = crop(this.state.colorTemperatureMireds);
|
|
14
|
+
const targetMireds = crop(startUpMireds);
|
|
15
|
+
if (targetMireds !== currentMireds) {
|
|
16
|
+
this.state.colorMode = ColorControl.ColorMode.ColorTemperatureMireds;
|
|
17
|
+
this.state.enhancedColorMode = ColorControl.EnhancedColorMode.ColorTemperatureMireds;
|
|
18
|
+
this.state.colorTemperatureMireds = targetMireds;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
9
23
|
}
|
|
10
24
|
async moveToHue(request) {
|
|
11
25
|
if (process.env.MATTERBRIDGE_CHIP_TEST) {
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { LevelControlServer } from '@matter/node/behaviors/level-control';
|
|
2
2
|
import { LevelControl } from '@matter/types/clusters/level-control';
|
|
3
|
-
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
3
|
+
import { isSoftwareUpdateBoot, MatterbridgeServer } from './matterbridgeServer.js';
|
|
4
4
|
export class MatterbridgeLevelControlServer extends LevelControlServer {
|
|
5
5
|
initialize() {
|
|
6
6
|
if (process.env.MATTERBRIDGE_CHIP_TEST)
|
|
7
7
|
this.state.managedTransitionTimeHandling = true;
|
|
8
|
-
|
|
8
|
+
const result = super.initialize();
|
|
9
|
+
if (this.features.lighting && !isSoftwareUpdateBoot(this.env)) {
|
|
10
|
+
const state = this.state;
|
|
11
|
+
const startUpCurrentLevel = state.startUpCurrentLevel ?? null;
|
|
12
|
+
const currentLevel = state.currentLevel;
|
|
13
|
+
const targetLevel = startUpCurrentLevel === 0 ? this.minLevel : (startUpCurrentLevel ?? currentLevel);
|
|
14
|
+
if (targetLevel !== currentLevel)
|
|
15
|
+
state.currentLevel = targetLevel;
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
9
18
|
}
|
|
10
19
|
async moveToLevel(request) {
|
|
11
20
|
if (process.env.MATTERBRIDGE_CHIP_TEST) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { Environment } from '@matter/general';
|
|
1
2
|
import { Behavior } from '@matter/node';
|
|
2
3
|
import type { AnsiLogger } from 'node-ansi-logger';
|
|
3
4
|
import type { CommandHandler } from '../matterbridgeEndpointCommandHandler.js';
|
|
5
|
+
export declare function isSoftwareUpdateBoot(env: Environment): boolean;
|
|
4
6
|
export declare class MatterbridgeServer extends Behavior {
|
|
5
7
|
static readonly id = "matterbridge";
|
|
6
8
|
state: MatterbridgeServer.State;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import { Behavior } from '@matter/node';
|
|
1
|
+
import { Behavior, ServerNode } from '@matter/node';
|
|
2
|
+
import { GeneralDiagnosticsBehavior } from '@matter/node/behaviors/general-diagnostics';
|
|
3
|
+
import { GeneralDiagnostics } from '@matter/types/clusters/general-diagnostics';
|
|
4
|
+
export function isSoftwareUpdateBoot(env) {
|
|
5
|
+
const rootEndpoint = env.get(ServerNode);
|
|
6
|
+
if (!rootEndpoint.behaviors.has(GeneralDiagnosticsBehavior))
|
|
7
|
+
return false;
|
|
8
|
+
return rootEndpoint.stateOf(GeneralDiagnosticsBehavior).bootReason === GeneralDiagnostics.BootReason.SoftwareUpdateCompleted;
|
|
9
|
+
}
|
|
2
10
|
export class MatterbridgeServer extends Behavior {
|
|
3
11
|
static id = 'matterbridge';
|
|
4
12
|
initialize() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MaybePromise } from '@matter/general';
|
|
1
2
|
import { OnOffServer } from '@matter/node/behaviors/on-off';
|
|
2
3
|
import { OnOff } from '@matter/types/clusters/on-off';
|
|
3
4
|
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
@@ -16,6 +17,7 @@ declare const MatterbridgeOnOffServer_base: import("@matter/node").ClusterBehavi
|
|
|
16
17
|
}>, import("@matter/types").ClusterType.Concrete, typeof import("@matter/node/behaviors/on-off").OnOffBaseServer.Internal, "onOff">;
|
|
17
18
|
export declare class MatterbridgeOnOffServer extends MatterbridgeOnOffServer_base {
|
|
18
19
|
readonly endpoint: MatterbridgeEndpoint;
|
|
20
|
+
initialize(): MaybePromise;
|
|
19
21
|
on(): Promise<void>;
|
|
20
22
|
off(): Promise<void>;
|
|
21
23
|
toggle(): Promise<void>;
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { OnOffServer } from '@matter/node/behaviors/on-off';
|
|
2
2
|
import { OnOff } from '@matter/types/clusters/on-off';
|
|
3
|
-
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
3
|
+
import { isSoftwareUpdateBoot, MatterbridgeServer } from './matterbridgeServer.js';
|
|
4
4
|
export class MatterbridgeOnOffServer extends OnOffServer.with(OnOff.Feature.Lighting) {
|
|
5
|
+
initialize() {
|
|
6
|
+
const result = super.initialize();
|
|
7
|
+
if (this.features.lighting && !isSoftwareUpdateBoot(this.env)) {
|
|
8
|
+
const startUpOnOff = this.state.startUpOnOff ?? null;
|
|
9
|
+
if (startUpOnOff !== null) {
|
|
10
|
+
const currentOnOff = this.state.onOff;
|
|
11
|
+
const targetOnOff = startUpOnOff === OnOff.StartUpOnOff.Toggle ? !currentOnOff : startUpOnOff === OnOff.StartUpOnOff.On;
|
|
12
|
+
if (targetOnOff !== currentOnOff)
|
|
13
|
+
this.state.onOff = targetOnOff;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
5
18
|
async on() {
|
|
6
19
|
if (process.env.MATTERBRIDGE_CHIP_TEST) {
|
|
7
20
|
await super.on();
|
|
@@ -24,6 +24,7 @@ export declare namespace MatterbridgeWebRtcTransportProviderServer {
|
|
|
24
24
|
class Internal {
|
|
25
25
|
nextWebRtcSessionId: number;
|
|
26
26
|
sessions: Map<number, WeriftWebRtcSession>;
|
|
27
|
+
trickledSessions: Set<number>;
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
export declare function createDefaultWebRtcTransportProviderClusterServer(endpoint: MatterbridgeEndpoint, weriftOfferOptions?: WeriftOfferOptions): MatterbridgeEndpoint;
|
|
@@ -32,6 +32,15 @@ export class MatterbridgeWebRtcTransportProviderServer extends WebRtcTransportPr
|
|
|
32
32
|
});
|
|
33
33
|
}, DEFERRED_INVOKE_DELAY_MS);
|
|
34
34
|
}
|
|
35
|
+
async #trickleLocalIceCandidates(requestorEndpoint, webRtcSessionId, webRtcPeer) {
|
|
36
|
+
const iceCandidates = webRtcPeer.localIceCandidates;
|
|
37
|
+
if (iceCandidates.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
await requestorEndpoint.commandsOf(WebRtcTransportRequestorClient).iceCandidates({ webRtcSessionId, iceCandidates });
|
|
40
|
+
this.endpoint
|
|
41
|
+
.stateOf(MatterbridgeServer)
|
|
42
|
+
.log.info(`MatterbridgeWebRtcTransportProviderServer: invoked ICECandidates with ${iceCandidates.length} local candidate(s) for session ${webRtcSessionId} on the peer's WebRtcTransportRequestor`);
|
|
43
|
+
}
|
|
35
44
|
async #resolvePeerRequestorEndpoint(peerNodeId, fabricIndex, peerEndpointId) {
|
|
36
45
|
try {
|
|
37
46
|
const serverNode = Node.forEndpoint(this.endpoint);
|
|
@@ -208,6 +217,19 @@ export class MatterbridgeWebRtcTransportProviderServer extends WebRtcTransportPr
|
|
|
208
217
|
}
|
|
209
218
|
return true;
|
|
210
219
|
}
|
|
220
|
+
#updateStreamReferenceCounts(videoStreams, audioStreams, change) {
|
|
221
|
+
if (!this.endpoint.behaviors.has(MatterbridgeCameraAvStreamManagementServer))
|
|
222
|
+
return;
|
|
223
|
+
const state = this.agent.get(MatterbridgeCameraAvStreamManagementServer).state;
|
|
224
|
+
for (const stream of state.allocatedVideoStreams) {
|
|
225
|
+
if (videoStreams?.includes(stream.videoStreamId))
|
|
226
|
+
stream.referenceCount = Math.max(0, stream.referenceCount + change);
|
|
227
|
+
}
|
|
228
|
+
for (const stream of state.allocatedAudioStreams) {
|
|
229
|
+
if (audioStreams?.includes(stream.audioStreamId))
|
|
230
|
+
stream.referenceCount = Math.max(0, stream.referenceCount + change);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
211
233
|
async solicitOffer(request) {
|
|
212
234
|
this.#validateNoConflictingStreamFields(request);
|
|
213
235
|
let videoStreams;
|
|
@@ -252,6 +274,7 @@ export class MatterbridgeWebRtcTransportProviderServer extends WebRtcTransportPr
|
|
|
252
274
|
this.endpoint.emitCommand(WebRtcTransportProvider, 'solicitOffer', request, this.context);
|
|
253
275
|
return { webRtcSessionId, deferredOffer: false, ...this.#echoDeprecatedStreamIds(request, videoStreams, audioStreams) };
|
|
254
276
|
}
|
|
277
|
+
this.#updateStreamReferenceCounts(videoStreams, audioStreams, 1);
|
|
255
278
|
const webRtcPeer = new WeriftWebRtcSession(webRtcSessionId, this.state.weriftOfferOptions);
|
|
256
279
|
this.internal.sessions.set(webRtcSessionId, webRtcPeer);
|
|
257
280
|
const sdp = await webRtcPeer.createOffer({
|
|
@@ -309,11 +332,11 @@ export class MatterbridgeWebRtcTransportProviderServer extends WebRtcTransportPr
|
|
|
309
332
|
fabricIndex,
|
|
310
333
|
},
|
|
311
334
|
];
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
return { webRtcSessionId, ...this.#echoDeprecatedStreamIds(request, videoStreams, audioStreams) };
|
|
335
|
+
if (this.state.currentSessions.length > MAX_CONCURRENT_SESSIONS) {
|
|
336
|
+
this.state.currentSessions = this.state.currentSessions.filter((session) => session.id !== webRtcSessionId);
|
|
337
|
+
throw new StatusResponseError(`MatterbridgeWebRtcTransportProviderServer.provideOffer: cannot sustain more than MAX_CONCURRENT_SESSIONS (${MAX_CONCURRENT_SESSIONS}) concurrent webRTC sessions (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ResourceExhausted);
|
|
316
338
|
}
|
|
339
|
+
this.#updateStreamReferenceCounts(videoStreams, audioStreams, 1);
|
|
317
340
|
}
|
|
318
341
|
else if (!this.state.currentSessions.some((session) => session.id === webRtcSessionId)) {
|
|
319
342
|
throw new StatusResponseError(`MatterbridgeWebRtcTransportProviderServer.provideOffer: webRTC session ${webRtcSessionId} is not present in currentSessions (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
@@ -387,15 +410,27 @@ export class MatterbridgeWebRtcTransportProviderServer extends WebRtcTransportPr
|
|
|
387
410
|
`with ${String(error)} (endpoint ${endpointLabel})`);
|
|
388
411
|
}
|
|
389
412
|
}));
|
|
413
|
+
const session = this.state.currentSessions.find((candidateSession) => candidateSession.id === request.webRtcSessionId);
|
|
414
|
+
if (session && !this.internal.trickledSessions.has(request.webRtcSessionId)) {
|
|
415
|
+
this.internal.trickledSessions.add(request.webRtcSessionId);
|
|
416
|
+
this.#invokeDeferred(async () => {
|
|
417
|
+
const requestorEndpoint = await this.#resolvePeerRequestorEndpoint(session.peerNodeId, session.fabricIndex, session.peerEndpointId);
|
|
418
|
+
if (requestorEndpoint)
|
|
419
|
+
await this.#trickleLocalIceCandidates(requestorEndpoint, request.webRtcSessionId, webRtcPeer);
|
|
420
|
+
}, `ICECandidates for session ${request.webRtcSessionId}`);
|
|
421
|
+
}
|
|
390
422
|
}
|
|
391
423
|
this.endpoint.emitCommand(WebRtcTransportProvider, 'provideIceCandidates', request, this.context);
|
|
392
424
|
}
|
|
393
425
|
async endSession(request) {
|
|
394
426
|
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
395
|
-
|
|
427
|
+
const session = this.state.currentSessions.find((session) => session.id === request.webRtcSessionId);
|
|
428
|
+
if (!session) {
|
|
396
429
|
throw new StatusResponseError(`MatterbridgeWebRtcTransportProviderServer.endSession: webRTC session ${request.webRtcSessionId} is not present in currentSessions (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
397
430
|
}
|
|
431
|
+
this.#updateStreamReferenceCounts(session.videoStreams, session.audioStreams, -1);
|
|
398
432
|
this.state.currentSessions = this.state.currentSessions.filter((session) => session.id !== request.webRtcSessionId);
|
|
433
|
+
this.internal.trickledSessions.delete(request.webRtcSessionId);
|
|
399
434
|
const webRtcPeer = this.internal.sessions.get(request.webRtcSessionId);
|
|
400
435
|
if (webRtcPeer) {
|
|
401
436
|
this.internal.sessions.delete(request.webRtcSessionId);
|
|
@@ -413,6 +448,7 @@ export class MatterbridgeWebRtcTransportProviderServer extends WebRtcTransportPr
|
|
|
413
448
|
class Internal {
|
|
414
449
|
nextWebRtcSessionId = 0;
|
|
415
450
|
sessions = new Map();
|
|
451
|
+
trickledSessions = new Set();
|
|
416
452
|
}
|
|
417
453
|
MatterbridgeWebRtcTransportProviderServer.Internal = Internal;
|
|
418
454
|
})(MatterbridgeWebRtcTransportProviderServer || (MatterbridgeWebRtcTransportProviderServer = {}));
|
|
@@ -29,9 +29,15 @@ export declare class WeriftWebRtcSession {
|
|
|
29
29
|
private static readonly DIAGNOSTICS_INTERVAL_MS;
|
|
30
30
|
private lastDiagnosticsCounters?;
|
|
31
31
|
private readonly dtlsTransportsWithStateLogging;
|
|
32
|
+
private readonly gatheredIceCandidates;
|
|
32
33
|
private static parseIcePortRange;
|
|
33
34
|
private static parseIceAdditionalHostAddresses;
|
|
34
35
|
constructor(webRtcSessionId: number, options: WeriftOfferOptions);
|
|
36
|
+
get localIceCandidates(): {
|
|
37
|
+
candidate: string;
|
|
38
|
+
sdpMid: string | null;
|
|
39
|
+
sdpmLineIndex: number | null;
|
|
40
|
+
}[];
|
|
35
41
|
private summarizeSdp;
|
|
36
42
|
private getFreeUdpPort;
|
|
37
43
|
private getPreferredInjectableVideoCodec;
|
|
@@ -21,6 +21,7 @@ export class WeriftWebRtcSession {
|
|
|
21
21
|
static DIAGNOSTICS_INTERVAL_MS = 10_000;
|
|
22
22
|
lastDiagnosticsCounters;
|
|
23
23
|
dtlsTransportsWithStateLogging = new Set();
|
|
24
|
+
gatheredIceCandidates = [];
|
|
24
25
|
static parseIcePortRange() {
|
|
25
26
|
const raw = process.env.MATTERBRIDGE_ICE_PORT_RANGE?.trim();
|
|
26
27
|
if (!raw)
|
|
@@ -55,6 +56,9 @@ export class WeriftWebRtcSession {
|
|
|
55
56
|
});
|
|
56
57
|
this.peerConnection.onIceCandidate.subscribe((candidate) => {
|
|
57
58
|
this.log.debug(candidate ? `Gathered local ICE candidate: ${candidate.candidate}` : 'ICE candidate gathering completed');
|
|
59
|
+
if (candidate) {
|
|
60
|
+
this.gatheredIceCandidates.push({ candidate: candidate.candidate, sdpMid: candidate.sdpMid ?? null, sdpmLineIndex: candidate.sdpMLineIndex ?? null });
|
|
61
|
+
}
|
|
58
62
|
});
|
|
59
63
|
this.peerConnection.iceConnectionStateChange.subscribe((state) => {
|
|
60
64
|
this.log.info(`ICE connection state: ${state}`);
|
|
@@ -89,6 +93,9 @@ export class WeriftWebRtcSession {
|
|
|
89
93
|
});
|
|
90
94
|
}
|
|
91
95
|
}
|
|
96
|
+
get localIceCandidates() {
|
|
97
|
+
return [...this.gatheredIceCandidates];
|
|
98
|
+
}
|
|
92
99
|
summarizeSdp(sdp) {
|
|
93
100
|
const mediaKinds = sdp
|
|
94
101
|
.split('\n')
|
package/dist/chipTests.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export declare class MatterbridgeGeneralDiagnosticsServer extends GeneralDiagnos
|
|
|
7
7
|
testEventTrigger({ eventTrigger, enableKey }: GeneralDiagnostics.TestEventTriggerRequest): Promise<void>;
|
|
8
8
|
payloadTestRequest(request: GeneralDiagnostics.PayloadTestRequest): ReturnType<GeneralDiagnosticsServer['payloadTestRequest']>;
|
|
9
9
|
}
|
|
10
|
+
export declare function createChipTestRestartFlag(matterbridge: Matterbridge): void;
|
|
10
11
|
export declare function createChipTestAppPipe(matterbridge: Matterbridge): void;
|
package/dist/chipTests.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { closeSync, constants, existsSync, openSync, readSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { closeSync, constants, existsSync, openSync, readFileSync, readSync, unlinkSync } from 'node:fs';
|
|
3
3
|
import { Seconds, Time } from '@matter/general';
|
|
4
4
|
import { BasicInformationServer } from '@matter/node/behaviors/basic-information';
|
|
5
5
|
import { BridgedDeviceBasicInformationServer } from '@matter/node/behaviors/bridged-device-basic-information';
|
|
@@ -30,6 +30,8 @@ import { MatterbridgeRvcOperationalStateServer, MatterbridgeRvcRunModeServer } f
|
|
|
30
30
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
31
31
|
import { featuresFor } from './matterbridgeEndpointHelpers.js';
|
|
32
32
|
const chipTestAppPipePath = '/tmp/matterbridge-chip-test-app-pipe';
|
|
33
|
+
const chipTestRestartFlagPath = '/tmp/matterbridge-chip-test-restart-flag';
|
|
34
|
+
const chipTestRestartFlagText = 'restart';
|
|
33
35
|
const chipTestValidEventTrigger = 0xfffffffffff10000n;
|
|
34
36
|
const smokeCoAlarmWarningSmokeAlarmTrigger = 0x005c000000000090n;
|
|
35
37
|
const smokeCoAlarmCriticalSmokeAlarmTrigger = 0x005c00000000009cn;
|
|
@@ -101,6 +103,9 @@ export const chipTestEnableKey = Uint8Array.from({ length: 16 }, (_, index) => i
|
|
|
101
103
|
const smokeCoAlarmChipTestEnableKey = Uint8Array.from([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
|
|
102
104
|
let chipTestMatterbridge;
|
|
103
105
|
let closeChipTestAppPipe;
|
|
106
|
+
let chipTestRestartFlagTimer;
|
|
107
|
+
let chipTestRestartPending = false;
|
|
108
|
+
let chipTestRestartFlagWarned = false;
|
|
104
109
|
let electricalPowerMeasurementFakeLoadTimer;
|
|
105
110
|
let chipTestActiveEndpointId;
|
|
106
111
|
export class MatterbridgeGeneralDiagnosticsServer extends GeneralDiagnosticsServer {
|
|
@@ -130,6 +135,65 @@ export class MatterbridgeGeneralDiagnosticsServer extends GeneralDiagnosticsServ
|
|
|
130
135
|
return super.payloadTestRequest(request);
|
|
131
136
|
}
|
|
132
137
|
}
|
|
138
|
+
function removeChipTestRestartFlag(log) {
|
|
139
|
+
try {
|
|
140
|
+
if (existsSync(chipTestRestartFlagPath))
|
|
141
|
+
unlinkSync(chipTestRestartFlagPath);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
log.error(`Failed to remove CHIP test restart flag ${chipTestRestartFlagPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function createChipTestRestartFlag(matterbridge) {
|
|
148
|
+
if (chipTestRestartPending) {
|
|
149
|
+
const onOnline = (nodeId) => {
|
|
150
|
+
if (nodeId !== 'Matterbridge')
|
|
151
|
+
return;
|
|
152
|
+
matterbridge.off('online', onOnline);
|
|
153
|
+
removeChipTestRestartFlag(matterbridge.log);
|
|
154
|
+
chipTestRestartPending = false;
|
|
155
|
+
matterbridge.log.notice(`CHIP test restart completed, cleared restart flag ${chipTestRestartFlagPath}`);
|
|
156
|
+
};
|
|
157
|
+
matterbridge.on('online', onOnline);
|
|
158
|
+
}
|
|
159
|
+
if (chipTestRestartFlagTimer)
|
|
160
|
+
return;
|
|
161
|
+
matterbridge.log.notice(`CHIP test restart flag monitor watching ${chipTestRestartFlagPath}`);
|
|
162
|
+
chipTestRestartFlagTimer = setInterval(() => {
|
|
163
|
+
const instance = chipTestMatterbridge;
|
|
164
|
+
if (!instance || chipTestRestartPending || !existsSync(chipTestRestartFlagPath))
|
|
165
|
+
return;
|
|
166
|
+
let flagText;
|
|
167
|
+
try {
|
|
168
|
+
flagText = readFileSync(chipTestRestartFlagPath, 'utf8').trim();
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
instance.log.error(`Failed to read CHIP test restart flag ${chipTestRestartFlagPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (flagText !== chipTestRestartFlagText) {
|
|
175
|
+
if (!chipTestRestartFlagWarned) {
|
|
176
|
+
chipTestRestartFlagWarned = true;
|
|
177
|
+
instance.log.warn(`Ignoring unsupported CHIP test restart flag content "${flagText}": only "${chipTestRestartFlagText}" is implemented`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
chipTestRestartFlagWarned = false;
|
|
182
|
+
chipTestRestartPending = true;
|
|
183
|
+
instance.log.notice(`CHIP test restart requested via ${chipTestRestartFlagPath}, restarting Matterbridge...`);
|
|
184
|
+
instance.restartProcess().catch((error) => {
|
|
185
|
+
chipTestRestartPending = false;
|
|
186
|
+
instance.log.error(`Failed to restart Matterbridge for the CHIP test restart flag: ${error instanceof Error ? error.message : String(error)}`);
|
|
187
|
+
});
|
|
188
|
+
}, 250);
|
|
189
|
+
chipTestRestartFlagTimer.unref();
|
|
190
|
+
cliEmitter.once('shutdown', () => {
|
|
191
|
+
if (chipTestRestartFlagTimer)
|
|
192
|
+
clearInterval(chipTestRestartFlagTimer);
|
|
193
|
+
chipTestRestartFlagTimer = undefined;
|
|
194
|
+
removeChipTestRestartFlag(matterbridge.log);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
133
197
|
function isChipTestEnableKey(keyData) {
|
|
134
198
|
return equalsBytes(keyData, chipTestEnableKey) || equalsBytes(keyData, smokeCoAlarmChipTestEnableKey);
|
|
135
199
|
}
|
package/dist/cli.js
CHANGED
|
@@ -180,6 +180,7 @@ function help() {
|
|
|
180
180
|
--fixed_delay [seconds]: set a fixed delay in seconds before starting Matterbridge (default 120)
|
|
181
181
|
--no-ansi: disable ANSI color output in the logs
|
|
182
182
|
--reset-sessions: reset sessions and resumption records on shutdown (use only if your controller has issue reconnecting on restart)
|
|
183
|
+
--configuration-version: increment by one the configuration version of all the server nodes when they go online (add a value to set it instead, i.e. --configuration-version 5)
|
|
183
184
|
`);
|
|
184
185
|
process.exit(0);
|
|
185
186
|
}
|
package/dist/demoDevices.js
CHANGED
|
@@ -160,6 +160,7 @@ export async function createDemoDevices(matterbridge) {
|
|
|
160
160
|
author: 'https://github.com/Luligu',
|
|
161
161
|
enabled: false,
|
|
162
162
|
private: true,
|
|
163
|
+
configurationVersion: 1,
|
|
163
164
|
registeredDevices: 0,
|
|
164
165
|
configJson: demoPluginConfig,
|
|
165
166
|
schemaJson: demoPluginSchema,
|
package/dist/devices/camera.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface CameraOptions extends MatterbridgeEndpointOptions {
|
|
|
30
30
|
tiltMax?: number;
|
|
31
31
|
zoomMax?: number;
|
|
32
32
|
mptzPosition?: CameraAvSettingsUserLevelManagement.Mptz;
|
|
33
|
+
maxPresets?: number;
|
|
33
34
|
weriftOfferOptions?: WeriftOfferOptions;
|
|
34
35
|
}
|
|
35
36
|
export declare class Camera extends MatterbridgeEndpoint {
|
package/dist/devices/camera.js
CHANGED
|
@@ -17,7 +17,7 @@ export class Camera extends MatterbridgeEndpoint {
|
|
|
17
17
|
{ resolution: { width: 640, height: 480 }, maxFrameRate: 10, imageCodec: CameraAvStreamManagement.ImageCodec.Jpeg, requiresEncodedPixels: false },
|
|
18
18
|
{ resolution: { width: 1280, height: 720 }, maxFrameRate: 10, imageCodec: CameraAvStreamManagement.ImageCodec.Jpeg, requiresEncodedPixels: false },
|
|
19
19
|
{ resolution: { width: 1920, height: 1080 }, maxFrameRate: 10, imageCodec: CameraAvStreamManagement.ImageCodec.Jpeg, requiresEncodedPixels: false },
|
|
20
|
-
], panMin = -170, panMax = 170, tiltMin = -20, tiltMax = 90, zoomMax = 10, mptzPosition = { pan: 0, tilt: 0, zoom: 1 }, weriftOfferOptions, id, number, tagList, mode, } = options;
|
|
20
|
+
], panMin = -170, panMax = 170, tiltMin = -20, tiltMax = 90, zoomMax = 10, mptzPosition = { pan: 0, tilt: 0, zoom: 1 }, maxPresets = 5, weriftOfferOptions, id, number, tagList, mode, } = options;
|
|
21
21
|
super(powerSourceType === 'None' ? [camera] : [camera, powerSource], { id: id ?? `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`, number, tagList, mode });
|
|
22
22
|
if (identifyType !== Identify.IdentifyType.None) {
|
|
23
23
|
this.createDefaultIdentifyClusterServer(identifyTime, identifyType);
|
|
@@ -56,7 +56,7 @@ export class Camera extends MatterbridgeEndpoint {
|
|
|
56
56
|
snapshotCapabilities,
|
|
57
57
|
});
|
|
58
58
|
if (ptz)
|
|
59
|
-
createDefaultCameraAvSettingsUserLevelManagementClusterServer(this, { panMin, panMax, tiltMin, tiltMax, zoomMax, mptzPosition });
|
|
59
|
+
createDefaultCameraAvSettingsUserLevelManagementClusterServer(this, { panMin, panMax, tiltMin, tiltMax, zoomMax, mptzPosition, maxPresets });
|
|
60
60
|
createDefaultWebRtcTransportProviderClusterServer(this, weriftOfferOptions);
|
|
61
61
|
addWebRtcTransportRequestorClient(this);
|
|
62
62
|
this.addRequiredClusters();
|
package/dist/frontend.js
CHANGED
|
@@ -1268,6 +1268,7 @@ export class Frontend extends EventEmitter {
|
|
|
1268
1268
|
name: plugin.name,
|
|
1269
1269
|
version: plugin.version,
|
|
1270
1270
|
private: plugin.private,
|
|
1271
|
+
configurationVersion: plugin.configurationVersion,
|
|
1271
1272
|
description: plugin.description,
|
|
1272
1273
|
author: plugin.author,
|
|
1273
1274
|
homepage: plugin.homepage,
|
package/dist/matterNode.js
CHANGED
|
@@ -10,7 +10,7 @@ import { BridgedDeviceBasicInformationServer } from '@matter/node/behaviors/brid
|
|
|
10
10
|
import { PowerSourceServer } from '@matter/node/behaviors/power-source';
|
|
11
11
|
import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
|
|
12
12
|
import { MdnsService, PaseClient } from '@matter/protocol';
|
|
13
|
-
import { DeviceTypeId, VendorId } from '@matter/types';
|
|
13
|
+
import { DeviceTypeId, EndpointNumber, VendorId } from '@matter/types';
|
|
14
14
|
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
15
15
|
import { BroadcastServer } from '@matterbridge/thread/server';
|
|
16
16
|
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg } from '@matterbridge/types';
|
|
@@ -19,7 +19,7 @@ import { copyDirectory } from '@matterbridge/utils/copy-dir';
|
|
|
19
19
|
import { getErrorMessage, inspectError } from '@matterbridge/utils/error';
|
|
20
20
|
import { logModuleLoaded } from '@matterbridge/utils/loader';
|
|
21
21
|
import { isValidInteger, isValidString, parseVersionString } from '@matterbridge/utils/validate';
|
|
22
|
-
import { wait, withTimeout } from '@matterbridge/utils/wait';
|
|
22
|
+
import { fireAndForget, wait, withTimeout } from '@matterbridge/utils/wait';
|
|
23
23
|
import { AnsiLogger, BLUE, CYAN, db, debugStringify, er, nf, or, zb } from 'node-ansi-logger';
|
|
24
24
|
import { NodeStorageManager } from 'node-persist-manager';
|
|
25
25
|
import { toBaseDevice } from './deviceManager.js';
|
|
@@ -471,7 +471,7 @@ export class MatterNode extends EventEmitter {
|
|
|
471
471
|
status: PowerSource.PowerSourceStatus.Active,
|
|
472
472
|
order: 0,
|
|
473
473
|
description: 'AC Power',
|
|
474
|
-
endpointList: [],
|
|
474
|
+
endpointList: [EndpointNumber(0)],
|
|
475
475
|
wiredCurrentType: PowerSource.WiredCurrentType.Ac,
|
|
476
476
|
},
|
|
477
477
|
}
|
|
@@ -490,6 +490,13 @@ export class MatterNode extends EventEmitter {
|
|
|
490
490
|
});
|
|
491
491
|
serverNode.lifecycle.online.on(() => {
|
|
492
492
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
493
|
+
this.log.info(`Configuration version for server node ${storeId} is ${serverNode.state.basicInformation.configurationVersion}`);
|
|
494
|
+
if (hasParameter('configuration-version')) {
|
|
495
|
+
let configurationVersion = getIntParameter('configuration-version') ?? serverNode.state.basicInformation.configurationVersion + 1;
|
|
496
|
+
configurationVersion = Math.min(Math.max(configurationVersion, 1), UINT32_MAX);
|
|
497
|
+
fireAndForget(serverNode.setStateOf(BasicInformationServer, { configurationVersion }), this.log, `Failed to set configuration version for server node ${storeId}`);
|
|
498
|
+
this.log.notice(`Configuration version for server node ${storeId} is now ${configurationVersion}`);
|
|
499
|
+
}
|
|
493
500
|
if (!serverNode.lifecycle.isCommissioned) {
|
|
494
501
|
this.log.notice(`Server node for ${storeId} is not commissioned. Pair to commission ...`);
|
|
495
502
|
this.advertisingNodes.set(storeId, Date.now());
|
|
@@ -640,7 +647,7 @@ export class MatterNode extends EventEmitter {
|
|
|
640
647
|
if (!plugin.locked) {
|
|
641
648
|
plugin.locked = true;
|
|
642
649
|
this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
|
|
643
|
-
this.matterStorageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridge.matterbridgeVersion), this.matterbridge.matterbridgeVersion, parseVersionString(this.matterbridge.systemInformation.osRelease), this.matterbridge.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io',
|
|
650
|
+
this.matterStorageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridge.matterbridgeVersion), this.matterbridge.matterbridgeVersion, parseVersionString(this.matterbridge.systemInformation.osRelease), this.matterbridge.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io', plugin.configurationVersion);
|
|
644
651
|
this.serverNode = await this.createServerNode(this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
645
652
|
this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
|
|
646
653
|
this.aggregatorNode = await this.createAggregatorNode();
|
package/dist/matterbridge.js
CHANGED
|
@@ -12,7 +12,7 @@ import { PowerSourceServer } from '@matter/node/behaviors/power-source';
|
|
|
12
12
|
import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
|
|
13
13
|
import { PaseClient } from '@matter/protocol';
|
|
14
14
|
import { PowerSource } from '@matter/types/clusters/power-source';
|
|
15
|
-
import { DeviceTypeId, VendorId } from '@matter/types/datatype';
|
|
15
|
+
import { DeviceTypeId, EndpointNumber, VendorId } from '@matter/types/datatype';
|
|
16
16
|
import { ManualPairingCodeCodec } from '@matter/types/schema';
|
|
17
17
|
import { BroadcastServer } from '@matterbridge/thread/server';
|
|
18
18
|
import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from '@matterbridge/types';
|
|
@@ -1248,6 +1248,10 @@ export class Matterbridge extends EventEmitter {
|
|
|
1248
1248
|
this.log.info('Unregistering all devices and shutting down...');
|
|
1249
1249
|
if (this.serverNode)
|
|
1250
1250
|
await this.serverNode.setStateOf(BasicInformationServer, { configurationVersion: this.serverNode.state.basicInformation.configurationVersion + 1 });
|
|
1251
|
+
for (const device of this.devices.array()) {
|
|
1252
|
+
if (device.serverNode)
|
|
1253
|
+
await device.serverNode.setStateOf(BasicInformationServer, { configurationVersion: device.serverNode.state.basicInformation.configurationVersion + 1 });
|
|
1254
|
+
}
|
|
1251
1255
|
for (const plugin of this.plugins.array()) {
|
|
1252
1256
|
if (plugin.error || !plugin.enabled)
|
|
1253
1257
|
continue;
|
|
@@ -1570,8 +1574,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
1570
1574
|
this.log.warn(' ***********************************************************************************');
|
|
1571
1575
|
this.log.warn(' * MATTERBRIDGE_CHIP_TEST environment variable is set. Running chip test app pipe. *');
|
|
1572
1576
|
this.log.warn(' ***********************************************************************************');
|
|
1573
|
-
const { createChipTestAppPipe } = await import('./chipTests.js');
|
|
1577
|
+
const { createChipTestAppPipe, createChipTestRestartFlag } = await import('./chipTests.js');
|
|
1574
1578
|
createChipTestAppPipe(this);
|
|
1579
|
+
createChipTestRestartFlag(this);
|
|
1575
1580
|
}
|
|
1576
1581
|
await this.startPlugins(false, true);
|
|
1577
1582
|
this.log.debug('Starting start matter interval in bridge mode...');
|
|
@@ -1943,7 +1948,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
1943
1948
|
status: PowerSource.PowerSourceStatus.Active,
|
|
1944
1949
|
order: 0,
|
|
1945
1950
|
description: 'AC Power',
|
|
1946
|
-
endpointList: [],
|
|
1951
|
+
endpointList: [EndpointNumber(0)],
|
|
1947
1952
|
wiredCurrentType: PowerSource.WiredCurrentType.Ac,
|
|
1948
1953
|
},
|
|
1949
1954
|
}
|
|
@@ -1964,6 +1969,13 @@ export class Matterbridge extends EventEmitter {
|
|
|
1964
1969
|
});
|
|
1965
1970
|
serverNode.lifecycle.online.on(() => {
|
|
1966
1971
|
this.log.notice(`Server node for ${storeId} is online`);
|
|
1972
|
+
this.log.info(`Configuration version for server node ${storeId} is ${serverNode.state.basicInformation.configurationVersion}`);
|
|
1973
|
+
if (hasParameter('configuration-version')) {
|
|
1974
|
+
let configurationVersion = getIntParameter('configuration-version') ?? serverNode.state.basicInformation.configurationVersion + 1;
|
|
1975
|
+
configurationVersion = Math.min(Math.max(configurationVersion, 1), UINT32_MAX);
|
|
1976
|
+
fireAndForget(serverNode.setStateOf(BasicInformationServer, { configurationVersion }), this.log, `Failed to set configuration version for server node ${storeId}`);
|
|
1977
|
+
this.log.notice(`Configuration version for server node ${storeId} is now ${configurationVersion}`);
|
|
1978
|
+
}
|
|
1967
1979
|
if (serverNode.lifecycle.isCommissioned) {
|
|
1968
1980
|
this.log.notice(`Server node for ${storeId} is already commissioned.`);
|
|
1969
1981
|
this.advertisingNodes.delete(storeId);
|
|
@@ -2075,7 +2087,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
2075
2087
|
if (!plugin.locked) {
|
|
2076
2088
|
plugin.locked = true;
|
|
2077
2089
|
this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} server node...`);
|
|
2078
|
-
plugin.storageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridgeVersion), this.matterbridgeVersion, parseVersionString(this.systemInformation.osRelease), this.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io',
|
|
2090
|
+
plugin.storageContext = await this.createServerNodeContext(plugin.name, plugin.description, this.aggregatorDeviceType, this.aggregatorVendorId, this.aggregatorVendorName, this.aggregatorProductId, this.aggregatorProductName, undefined, undefined, parseVersionString(this.matterbridgeVersion), this.matterbridgeVersion, parseVersionString(this.systemInformation.osRelease), this.systemInformation.osRelease, plugin.description, plugin.homepage ?? 'https://matterbridge.io', plugin.configurationVersion);
|
|
2079
2091
|
plugin.serverNode = await this.createServerNode(plugin.storageContext, this.port ? this.port++ : undefined, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
|
|
2080
2092
|
this.log.debug(`Creating dynamic plugin ${plg}${plugin.name}${db} aggregator node...`);
|
|
2081
2093
|
plugin.aggregatorNode = await this.createAggregatorNode(plugin.storageContext);
|
|
@@ -172,11 +172,11 @@ export declare class MatterbridgeEndpoint extends Endpoint {
|
|
|
172
172
|
createOffOnlyOnOffClusterServer(onOff?: boolean): this;
|
|
173
173
|
createDefaultLevelControlClusterServer(currentLevel?: number, minLevel?: number, maxLevel?: number, onLevel?: number | null, startUpCurrentLevel?: number | null): this;
|
|
174
174
|
createLevelControlClusterServer(currentLevel?: number, onLevel?: number | null): this;
|
|
175
|
-
createDefaultColorControlClusterServer(currentX?: number, currentY?: number, currentHue?: number, currentSaturation?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number): this;
|
|
176
|
-
createEnhancedColorControlClusterServer(currentX?: number, currentY?: number, enhancedCurrentHue?: number, currentSaturation?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number): this;
|
|
177
|
-
createXyColorControlClusterServer(currentX?: number, currentY?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number): this;
|
|
178
|
-
createHsColorControlClusterServer(currentHue?: number, currentSaturation?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number): this;
|
|
179
|
-
createCtColorControlClusterServer(colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number): this;
|
|
175
|
+
createDefaultColorControlClusterServer(currentX?: number, currentY?: number, currentHue?: number, currentSaturation?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number, startUpColorTemperatureMireds?: number | null): this;
|
|
176
|
+
createEnhancedColorControlClusterServer(currentX?: number, currentY?: number, enhancedCurrentHue?: number, currentSaturation?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number, startUpColorTemperatureMireds?: number | null): this;
|
|
177
|
+
createXyColorControlClusterServer(currentX?: number, currentY?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number, startUpColorTemperatureMireds?: number | null): this;
|
|
178
|
+
createHsColorControlClusterServer(currentHue?: number, currentSaturation?: number, colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number, startUpColorTemperatureMireds?: number | null): this;
|
|
179
|
+
createCtColorControlClusterServer(colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number, startUpColorTemperatureMireds?: number | null): this;
|
|
180
180
|
configureColorControlMode(colorMode: ColorControl.ColorMode): Promise<void>;
|
|
181
181
|
configureEnhancedColorControlMode(colorMode: ColorControl.EnhancedColorMode): Promise<void>;
|
|
182
182
|
createDefaultWindowCoveringClusterServer(positionPercent100ths?: number | null, type?: WindowCovering.WindowCoveringType, endProductType?: WindowCovering.EndProductType, movementDuration?: number): this;
|
|
@@ -720,7 +720,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
720
720
|
});
|
|
721
721
|
return this;
|
|
722
722
|
}
|
|
723
|
-
createDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
|
|
723
|
+
createDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500, startUpColorTemperatureMireds = null) {
|
|
724
724
|
this.behaviors.require(MatterbridgeColorControlServer.with(ColorControl.Feature.Xy, ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), {
|
|
725
725
|
colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation,
|
|
726
726
|
enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation,
|
|
@@ -737,12 +737,12 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
737
737
|
colorTempPhysicalMinMireds,
|
|
738
738
|
colorTempPhysicalMaxMireds,
|
|
739
739
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
740
|
-
startUpColorTemperatureMireds
|
|
740
|
+
startUpColorTemperatureMireds,
|
|
741
741
|
remainingTime: 0,
|
|
742
742
|
});
|
|
743
743
|
return this;
|
|
744
744
|
}
|
|
745
|
-
createEnhancedColorControlClusterServer(currentX = 0, currentY = 0, enhancedCurrentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
|
|
745
|
+
createEnhancedColorControlClusterServer(currentX = 0, currentY = 0, enhancedCurrentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500, startUpColorTemperatureMireds = null) {
|
|
746
746
|
this.behaviors.require(MatterbridgeColorControlServer.with(ColorControl.Feature.Xy, ColorControl.Feature.HueSaturation, ColorControl.Feature.EnhancedHue, ColorControl.Feature.ColorTemperature), {
|
|
747
747
|
colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation,
|
|
748
748
|
enhancedColorMode: ColorControl.EnhancedColorMode.EnhancedCurrentHueAndCurrentSaturation,
|
|
@@ -760,12 +760,12 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
760
760
|
colorTempPhysicalMinMireds,
|
|
761
761
|
colorTempPhysicalMaxMireds,
|
|
762
762
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
763
|
-
startUpColorTemperatureMireds
|
|
763
|
+
startUpColorTemperatureMireds,
|
|
764
764
|
remainingTime: 0,
|
|
765
765
|
});
|
|
766
766
|
return this;
|
|
767
767
|
}
|
|
768
|
-
createXyColorControlClusterServer(currentX = 0, currentY = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
|
|
768
|
+
createXyColorControlClusterServer(currentX = 0, currentY = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500, startUpColorTemperatureMireds = null) {
|
|
769
769
|
this.behaviors.require(MatterbridgeColorControlServer.with(ColorControl.Feature.Xy, ColorControl.Feature.ColorTemperature), {
|
|
770
770
|
colorMode: ColorControl.ColorMode.CurrentXAndCurrentY,
|
|
771
771
|
enhancedColorMode: ColorControl.EnhancedColorMode.CurrentXAndCurrentY,
|
|
@@ -780,12 +780,12 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
780
780
|
colorTempPhysicalMinMireds,
|
|
781
781
|
colorTempPhysicalMaxMireds,
|
|
782
782
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
783
|
-
startUpColorTemperatureMireds
|
|
783
|
+
startUpColorTemperatureMireds,
|
|
784
784
|
remainingTime: 0,
|
|
785
785
|
});
|
|
786
786
|
return this;
|
|
787
787
|
}
|
|
788
|
-
createHsColorControlClusterServer(currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
|
|
788
|
+
createHsColorControlClusterServer(currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500, startUpColorTemperatureMireds = null) {
|
|
789
789
|
this.behaviors.require(MatterbridgeColorControlServer.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), {
|
|
790
790
|
colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation,
|
|
791
791
|
enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation,
|
|
@@ -800,12 +800,12 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
800
800
|
colorTempPhysicalMinMireds,
|
|
801
801
|
colorTempPhysicalMaxMireds,
|
|
802
802
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
803
|
-
startUpColorTemperatureMireds
|
|
803
|
+
startUpColorTemperatureMireds,
|
|
804
804
|
remainingTime: 0,
|
|
805
805
|
});
|
|
806
806
|
return this;
|
|
807
807
|
}
|
|
808
|
-
createCtColorControlClusterServer(colorTemperatureMireds = 250, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
|
|
808
|
+
createCtColorControlClusterServer(colorTemperatureMireds = 250, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500, startUpColorTemperatureMireds = null) {
|
|
809
809
|
this.behaviors.require(MatterbridgeColorControlServer.with(ColorControl.Feature.ColorTemperature), {
|
|
810
810
|
colorMode: ColorControl.ColorMode.ColorTemperatureMireds,
|
|
811
811
|
enhancedColorMode: ColorControl.EnhancedColorMode.ColorTemperatureMireds,
|
|
@@ -818,7 +818,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
818
818
|
colorTempPhysicalMinMireds,
|
|
819
819
|
colorTempPhysicalMaxMireds,
|
|
820
820
|
coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
|
|
821
|
-
startUpColorTemperatureMireds
|
|
821
|
+
startUpColorTemperatureMireds,
|
|
822
822
|
remainingTime: 0,
|
|
823
823
|
});
|
|
824
824
|
return this;
|
package/dist/pluginManager.js
CHANGED
|
@@ -357,6 +357,7 @@ export class PluginManager extends EventEmitter {
|
|
|
357
357
|
author: plugin.author,
|
|
358
358
|
enabled: plugin.enabled,
|
|
359
359
|
private: plugin.private,
|
|
360
|
+
configurationVersion: plugin.configurationVersion,
|
|
360
361
|
};
|
|
361
362
|
}
|
|
362
363
|
toApiPlugin(plugin) {
|
|
@@ -368,6 +369,7 @@ export class PluginManager extends EventEmitter {
|
|
|
368
369
|
path: plugin.path,
|
|
369
370
|
type: plugin.type,
|
|
370
371
|
private: plugin.private,
|
|
372
|
+
configurationVersion: plugin.configurationVersion,
|
|
371
373
|
latestVersion: plugin.latestVersion,
|
|
372
374
|
devVersion: plugin.devVersion,
|
|
373
375
|
homepage: plugin.homepage,
|
|
@@ -432,7 +434,7 @@ export class PluginManager extends EventEmitter {
|
|
|
432
434
|
}
|
|
433
435
|
const pluginsArray = await this.matterbridge.nodeContext.get('plugins', []);
|
|
434
436
|
for (const plugin of pluginsArray)
|
|
435
|
-
this._plugins.set(plugin.name, { ...plugin, private: plugin.private ?? false });
|
|
437
|
+
this._plugins.set(plugin.name, { ...plugin, private: plugin.private ?? false, configurationVersion: plugin.configurationVersion ?? 1 });
|
|
436
438
|
this.log.debug(`Loaded ${BLUE}${pluginsArray.length}${db} plugins from storage`);
|
|
437
439
|
return pluginsArray;
|
|
438
440
|
}
|
|
@@ -454,6 +456,7 @@ export class PluginManager extends EventEmitter {
|
|
|
454
456
|
enabled: plugin.enabled,
|
|
455
457
|
private: plugin.private,
|
|
456
458
|
tarballPath: plugin.tarballPath,
|
|
459
|
+
configurationVersion: plugin.configurationVersion,
|
|
457
460
|
});
|
|
458
461
|
}
|
|
459
462
|
await this.matterbridge.nodeContext.set('plugins', plugins);
|
|
@@ -823,6 +826,7 @@ export class PluginManager extends EventEmitter {
|
|
|
823
826
|
type: 'AnyPlatform',
|
|
824
827
|
version: packageJson.version,
|
|
825
828
|
private: packageJson.private || false,
|
|
829
|
+
configurationVersion: 1,
|
|
826
830
|
description: packageJson.description,
|
|
827
831
|
author: this.getAuthor(packageJson),
|
|
828
832
|
homepage: this.getHomepage(packageJson),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterbridge/core",
|
|
3
|
-
"version": "3.10.10-dev-
|
|
3
|
+
"version": "3.10.10-dev-20260917-79954cd",
|
|
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.10-dev-
|
|
140
|
-
"@matterbridge/thread": "3.10.10-dev-
|
|
141
|
-
"@matterbridge/types": "3.10.10-dev-
|
|
142
|
-
"@matterbridge/utils": "3.10.10-dev-
|
|
139
|
+
"@matterbridge/dgram": "3.10.10-dev-20260917-79954cd",
|
|
140
|
+
"@matterbridge/thread": "3.10.10-dev-20260917-79954cd",
|
|
141
|
+
"@matterbridge/types": "3.10.10-dev-20260917-79954cd",
|
|
142
|
+
"@matterbridge/utils": "3.10.10-dev-20260917-79954cd",
|
|
143
143
|
"escape-html": "1.0.3",
|
|
144
144
|
"express": "5.2.1",
|
|
145
145
|
"express-rate-limit": "8.7.0",
|