@matterbridge/core 3.10.10-dev-20260914-784e019 → 3.10.10-dev-20260917-9fdf275
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.d.ts +13 -8
- package/dist/behaviors/cameraAvStreamManagementServer.js +63 -13
- 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/snapshot.d.ts +1 -0
- package/dist/behaviors/snapshot.js +5 -0
- package/dist/behaviors/webRtcTransportProviderServer.d.ts +1 -0
- package/dist/behaviors/webRtcTransportProviderServer.js +47 -11
- package/dist/behaviors/weriftSession.d.ts +10 -3
- package/dist/behaviors/weriftSession.js +41 -27
- package/dist/chipTests.d.ts +1 -0
- package/dist/chipTests.js +155 -2
- package/dist/demoDevices.js +61 -29
- package/dist/devices/camera.d.ts +1 -0
- package/dist/devices/camera.js +2 -2
- package/dist/devices/snapshotCamera.d.ts +3 -0
- package/dist/devices/snapshotCamera.js +3 -1
- package/dist/matterNode.js +2 -2
- package/dist/matterbridge.js +4 -3
- package/dist/matterbridgeEndpoint.d.ts +5 -5
- package/dist/matterbridgeEndpoint.js +10 -10
- package/package.json +6 -6
|
@@ -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
|
}
|
|
@@ -3,12 +3,8 @@ import { StreamUsage } from '@matter/types';
|
|
|
3
3
|
import type { Viewport } from '@matter/types';
|
|
4
4
|
import { CameraAvStreamManagement } from '@matter/types/clusters/camera-av-stream-management';
|
|
5
5
|
import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
resolution: CameraAvStreamManagement.VideoResolution;
|
|
9
|
-
}
|
|
10
|
-
export declare function cameraColorTestJpegForResolution(requestedResolution: CameraAvStreamManagement.VideoResolution): CameraColorTestJpeg;
|
|
11
|
-
declare const MatterbridgeCameraAvStreamManagementServer_base: import("@matter/node").ClusterBehavior.Type<typeof CameraAvStreamManagementServer, import("@matter/types").ClusterType.WithSupportedFeatures<CameraAvStreamManagement, {
|
|
6
|
+
export type SnapshotSource = 'test' | 'webcam' | 'rtsp';
|
|
7
|
+
declare const MatterbridgeCameraAvStreamManagementServerBase: import("@matter/node").ClusterBehavior.Type<typeof CameraAvStreamManagementServer, import("@matter/types").ClusterType.WithSupportedFeatures<CameraAvStreamManagement, {
|
|
12
8
|
audio: true;
|
|
13
9
|
highDynamicRange: false;
|
|
14
10
|
imageControl: true;
|
|
@@ -21,9 +17,10 @@ declare const MatterbridgeCameraAvStreamManagementServer_base: import("@matter/n
|
|
|
21
17
|
video: true;
|
|
22
18
|
watermark: false;
|
|
23
19
|
}>, import("@matter/types").ClusterType.Concrete, new () => {}, "cameraAvStreamManagement">;
|
|
24
|
-
export declare class MatterbridgeCameraAvStreamManagementServer extends
|
|
20
|
+
export declare class MatterbridgeCameraAvStreamManagementServer extends MatterbridgeCameraAvStreamManagementServerBase {
|
|
25
21
|
#private;
|
|
26
22
|
readonly endpoint: MatterbridgeEndpoint;
|
|
23
|
+
readonly state: MatterbridgeCameraAvStreamManagementServer.State;
|
|
27
24
|
initialize(): void;
|
|
28
25
|
setStreamPriorities(request: CameraAvStreamManagement.SetStreamPrioritiesRequest): void;
|
|
29
26
|
videoStreamAllocate(request: CameraAvStreamManagement.VideoStreamAllocateRequest): CameraAvStreamManagement.VideoStreamAllocateResponse;
|
|
@@ -32,7 +29,13 @@ export declare class MatterbridgeCameraAvStreamManagementServer extends Matterbr
|
|
|
32
29
|
audioStreamDeallocate(request: CameraAvStreamManagement.AudioStreamDeallocateRequest): void;
|
|
33
30
|
snapshotStreamAllocate(request: CameraAvStreamManagement.SnapshotStreamAllocateRequest): Promise<CameraAvStreamManagement.SnapshotStreamAllocateResponse>;
|
|
34
31
|
snapshotStreamDeallocate(request: CameraAvStreamManagement.SnapshotStreamDeallocateRequest): Promise<void>;
|
|
35
|
-
captureSnapshot(request: CameraAvStreamManagement.CaptureSnapshotRequest): CameraAvStreamManagement.CaptureSnapshotResponse
|
|
32
|
+
captureSnapshot(request: CameraAvStreamManagement.CaptureSnapshotRequest): Promise<CameraAvStreamManagement.CaptureSnapshotResponse>;
|
|
33
|
+
}
|
|
34
|
+
export declare namespace MatterbridgeCameraAvStreamManagementServer {
|
|
35
|
+
class State extends MatterbridgeCameraAvStreamManagementServerBase.State {
|
|
36
|
+
snapshotSource?: SnapshotSource;
|
|
37
|
+
snapshotSourceDevice?: string;
|
|
38
|
+
}
|
|
36
39
|
}
|
|
37
40
|
export interface CameraAvStreamManagementClusterOptions {
|
|
38
41
|
maxContentBufferSize: number;
|
|
@@ -76,6 +79,8 @@ export interface SnapshotCameraAvStreamManagementClusterOptions {
|
|
|
76
79
|
maxNetworkBandwidth: number;
|
|
77
80
|
supportedStreamUsages: StreamUsage[];
|
|
78
81
|
streamUsagePriorities: StreamUsage[];
|
|
82
|
+
snapshotSource?: SnapshotSource;
|
|
83
|
+
snapshotSourceDevice?: string;
|
|
79
84
|
}
|
|
80
85
|
export declare function createDefaultSnapshotCameraAvStreamManagementClusterServer(endpoint: MatterbridgeEndpoint, options: SnapshotCameraAvStreamManagementClusterOptions): MatterbridgeEndpoint;
|
|
81
86
|
export {};
|
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
1
|
import { CameraAvStreamManagementServer } from '@matter/node/behaviors/camera-av-stream-management';
|
|
2
|
+
import { WebRtcTransportProviderServer } from '@matter/node/behaviors/web-rtc-transport-provider';
|
|
3
3
|
import { Status, StatusResponseError, StreamUsage, ThreeLevelAuto } from '@matter/types';
|
|
4
4
|
import { CameraAvStreamManagement } from '@matter/types/clusters/camera-av-stream-management';
|
|
5
5
|
import { MatterbridgeServer } from './matterbridgeServer.js';
|
|
6
|
-
|
|
6
|
+
import { captureSnapshot, TEST_SNAPSHOT_SOURCE } from './snapshot.js';
|
|
7
7
|
const AUDIO_STREAM_BIT_DEPTHS = [8, 16, 24, 32];
|
|
8
8
|
function numericEnumValues(enumObject) {
|
|
9
9
|
return Object.values(enumObject).filter((value) => typeof value === 'number');
|
|
10
10
|
}
|
|
11
11
|
const AUDIO_CODECS = numericEnumValues(CameraAvStreamManagement.AudioCodec);
|
|
12
12
|
const VIDEO_CODECS = numericEnumValues(CameraAvStreamManagement.VideoCodec);
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
'1280x720': { data: readFileSync(new URL('../../assets/camera-color-test-1280-720.jpeg', import.meta.url)), resolution: { width: 1280, height: 720 } },
|
|
16
|
-
'1920x1080': { data: readFileSync(new URL('../../assets/camera-color-test-1920-1080.jpeg', import.meta.url)), resolution: { width: 1920, height: 1080 } },
|
|
17
|
-
};
|
|
18
|
-
export function cameraColorTestJpegForResolution(requestedResolution) {
|
|
19
|
-
return cameraColorTestJpegs[`${requestedResolution.width}x${requestedResolution.height}`] ?? cameraColorTestJpegs[DEFAULT_CAMERA_COLOR_TEST_RESOLUTION];
|
|
13
|
+
function hasWeriftOfferOptions(state) {
|
|
14
|
+
return 'weriftOfferOptions' in state && typeof state.weriftOfferOptions === 'object' && state.weriftOfferOptions !== null;
|
|
20
15
|
}
|
|
21
|
-
|
|
16
|
+
const MatterbridgeCameraAvStreamManagementServerBase = CameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Video, CameraAvStreamManagement.Feature.Audio, CameraAvStreamManagement.Feature.Snapshot, CameraAvStreamManagement.Feature.ImageControl);
|
|
17
|
+
export class MatterbridgeCameraAvStreamManagementServer extends MatterbridgeCameraAvStreamManagementServerBase {
|
|
22
18
|
#isAutoAllocateSkipped() {
|
|
23
19
|
return process.env.MATTERBRIDGE_SKIP_AUTO_ALLOCATE_CAMERA_AV_STREAM_MANAGEMENT === '1';
|
|
24
20
|
}
|
|
@@ -189,6 +185,9 @@ export class MatterbridgeCameraAvStreamManagementServer extends CameraAvStreamMa
|
|
|
189
185
|
if (!this.state.allocatedVideoStreams.some((stream) => stream.videoStreamId === request.videoStreamId)) {
|
|
190
186
|
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: video stream ${request.videoStreamId} is not present in allocatedVideoStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
191
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
|
+
}
|
|
192
191
|
this.state.allocatedVideoStreams = this.state.allocatedVideoStreams.filter((stream) => stream.videoStreamId !== request.videoStreamId);
|
|
193
192
|
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated video stream ${request.videoStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
194
193
|
this.endpoint.emitCommand(CameraAvStreamManagement, 'videoStreamDeallocate', request, this.context);
|
|
@@ -252,6 +251,9 @@ export class MatterbridgeCameraAvStreamManagementServer extends CameraAvStreamMa
|
|
|
252
251
|
if (!this.state.allocatedAudioStreams.some((stream) => stream.audioStreamId === request.audioStreamId)) {
|
|
253
252
|
throw new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: audio stream ${request.audioStreamId} is not present in allocatedAudioStreams (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
|
|
254
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
|
+
}
|
|
255
257
|
this.state.allocatedAudioStreams = this.state.allocatedAudioStreams.filter((stream) => stream.audioStreamId !== request.audioStreamId);
|
|
256
258
|
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated audio stream ${request.audioStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
257
259
|
this.endpoint.emitCommand(CameraAvStreamManagement, 'audioStreamDeallocate', request, this.context);
|
|
@@ -328,7 +330,34 @@ export class MatterbridgeCameraAvStreamManagementServer extends CameraAvStreamMa
|
|
|
328
330
|
device.log.info(`MatterbridgeCameraAvStreamManagementServer: deallocated snapshot stream ${request.snapshotStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
329
331
|
this.endpoint.emitCommand(CameraAvStreamManagement, 'snapshotStreamDeallocate', request, this.context);
|
|
330
332
|
}
|
|
331
|
-
|
|
333
|
+
#resolveSnapshotSource() {
|
|
334
|
+
const endpoint = `(endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`;
|
|
335
|
+
const fail = (reason) => {
|
|
336
|
+
this.endpoint.stateOf(MatterbridgeServer).log.error(`MatterbridgeCameraAvStreamManagementServer: cannot capture snapshot: ${reason} ${endpoint}`);
|
|
337
|
+
return new StatusResponseError(`MatterbridgeCameraAvStreamManagementServer: cannot capture snapshot: ${reason} ${endpoint}`, Status.Failure);
|
|
338
|
+
};
|
|
339
|
+
const sourceFor = (source, device, sourceOption, deviceOption) => {
|
|
340
|
+
if (source === 'test')
|
|
341
|
+
return TEST_SNAPSHOT_SOURCE;
|
|
342
|
+
if (device)
|
|
343
|
+
return device;
|
|
344
|
+
throw fail(`${sourceOption}=${source} requires ${deviceOption} to be set`);
|
|
345
|
+
};
|
|
346
|
+
const { snapshotSource, snapshotSourceDevice } = this.state;
|
|
347
|
+
if (snapshotSource !== undefined)
|
|
348
|
+
return sourceFor(snapshotSource, snapshotSourceDevice, 'snapshotSource', 'snapshotSourceDevice');
|
|
349
|
+
if (!this.endpoint.behaviors.has(WebRtcTransportProviderServer)) {
|
|
350
|
+
throw fail('no snapshotSource is configured and the endpoint has no WebRtcTransportProvider cluster to read the video source from');
|
|
351
|
+
}
|
|
352
|
+
const state = this.endpoint.stateOf(WebRtcTransportProviderServer);
|
|
353
|
+
if (!hasWeriftOfferOptions(state))
|
|
354
|
+
throw fail('the WebRtcTransportProvider cluster state has no weriftOfferOptions');
|
|
355
|
+
const { videoSource, videoSourceDevice } = state.weriftOfferOptions;
|
|
356
|
+
if (videoSource === 'none')
|
|
357
|
+
throw fail(`weriftOfferOptions.videoSource=${videoSource} provides no video source`);
|
|
358
|
+
return sourceFor(videoSource, videoSourceDevice, 'weriftOfferOptions.videoSource', 'videoSourceDevice');
|
|
359
|
+
}
|
|
360
|
+
async captureSnapshot(request) {
|
|
332
361
|
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
333
362
|
const { snapshotStreamId } = request;
|
|
334
363
|
const stream = this.state.allocatedSnapshotStreams.find((s) => snapshotStreamId === null || s.snapshotStreamId === snapshotStreamId);
|
|
@@ -337,15 +366,34 @@ export class MatterbridgeCameraAvStreamManagementServer extends CameraAvStreamMa
|
|
|
337
366
|
}
|
|
338
367
|
device.log.info(`MatterbridgeCameraAvStreamManagementServer: capturing snapshot ${snapshotStreamId ?? 'auto'} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
339
368
|
device.log.debug(`MatterbridgeCameraAvStreamManagementServer: captureSnapshot called with snapshotStreamId ${request.snapshotStreamId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
340
|
-
const
|
|
369
|
+
const src = this.#resolveSnapshotSource();
|
|
370
|
+
const { width, height } = request.requestedResolution;
|
|
371
|
+
let captured;
|
|
372
|
+
try {
|
|
373
|
+
captured = await captureSnapshot({ src, width, height });
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
const message = `MatterbridgeCameraAvStreamManagementServer: snapshot capture failed: ${error instanceof Error ? error.message : String(error)} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`;
|
|
377
|
+
device.log.error(message);
|
|
378
|
+
throw new StatusResponseError(message, Status.Failure);
|
|
379
|
+
}
|
|
380
|
+
const resolution = { width: captured.width, height: captured.height === -1 ? Math.round((captured.width * height) / width) : captured.height };
|
|
381
|
+
device.log.info(`MatterbridgeCameraAvStreamManagementServer: captured snapshot ${captured.byteLength}B at ${resolution.width}x${resolution.height}, quality ${captured.quality}${captured.downgraded ? ' (downgraded)' : ''} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
341
382
|
this.endpoint.emitCommand(CameraAvStreamManagement, 'captureSnapshot', request, this.context);
|
|
342
383
|
return {
|
|
343
|
-
data,
|
|
384
|
+
data: captured.data,
|
|
344
385
|
imageCodec: CameraAvStreamManagement.ImageCodec.Jpeg,
|
|
345
386
|
resolution,
|
|
346
387
|
};
|
|
347
388
|
}
|
|
348
389
|
}
|
|
390
|
+
(function (MatterbridgeCameraAvStreamManagementServer) {
|
|
391
|
+
class State extends MatterbridgeCameraAvStreamManagementServerBase.State {
|
|
392
|
+
snapshotSource = undefined;
|
|
393
|
+
snapshotSourceDevice = undefined;
|
|
394
|
+
}
|
|
395
|
+
MatterbridgeCameraAvStreamManagementServer.State = State;
|
|
396
|
+
})(MatterbridgeCameraAvStreamManagementServer || (MatterbridgeCameraAvStreamManagementServer = {}));
|
|
349
397
|
export function createDefaultCameraAvStreamManagementClusterServer(endpoint, options) {
|
|
350
398
|
endpoint.behaviors.require(MatterbridgeCameraAvStreamManagementServer.with(CameraAvStreamManagement.Feature.Video, CameraAvStreamManagement.Feature.Audio, CameraAvStreamManagement.Feature.Snapshot, CameraAvStreamManagement.Feature.ImageControl), {
|
|
351
399
|
...options,
|
|
@@ -408,6 +456,8 @@ export function createDefaultSnapshotCameraAvStreamManagementClusterServer(endpo
|
|
|
408
456
|
maxEncodedPixelRate: options.maxEncodedPixelRate,
|
|
409
457
|
snapshotCapabilities: options.snapshotCapabilities,
|
|
410
458
|
allocatedSnapshotStreams: [],
|
|
459
|
+
snapshotSource: options.snapshotSource,
|
|
460
|
+
snapshotSourceDevice: options.snapshotSourceDevice,
|
|
411
461
|
});
|
|
412
462
|
return endpoint;
|
|
413
463
|
}
|
|
@@ -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();
|
|
@@ -7,6 +7,7 @@ const MAX_BYTES = 64000;
|
|
|
7
7
|
const QUALITIES = [6, 10, 14, 18, 24, 31];
|
|
8
8
|
const FALLBACK_WIDTH = 640;
|
|
9
9
|
const WEBCAM_WARMUP_SECONDS = 1;
|
|
10
|
+
export const TEST_SNAPSHOT_SOURCE = 'test';
|
|
10
11
|
const CAPTURE_TIMEOUT_MS = 10_000;
|
|
11
12
|
const TERMINATION_GRACE_MS = 1_000;
|
|
12
13
|
const log = new AnsiLogger({ logName: 'Snapshot', logLevel: "debug", logNameColor: MAGENTA, logTimestampFormat: 4 });
|
|
@@ -56,6 +57,10 @@ export async function captureSnapshot(request) {
|
|
|
56
57
|
throw new Error(`Snapshot capture from ${redactedSrc} exceeds ${maxBytes}B at minimum quality/resolution (${last.length}B at quality ${lastQuality})`);
|
|
57
58
|
}
|
|
58
59
|
function buildInputArgs(src, width, height) {
|
|
60
|
+
if (src === TEST_SNAPSHOT_SOURCE) {
|
|
61
|
+
const size = height === -1 ? '' : `=size=${width}x${height}`;
|
|
62
|
+
return ['-f', 'lavfi', '-i', `testsrc${size}`];
|
|
63
|
+
}
|
|
59
64
|
if (src.startsWith('rtsp://')) {
|
|
60
65
|
return ['-rtsp_transport', 'tcp', '-i', src];
|
|
61
66
|
}
|
|
@@ -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;
|