@matterbridge/core 3.10.8-dev-20260829-e779e7d → 3.10.8-dev-20260831-3e81235

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.
@@ -1,7 +1,8 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { ClosureTag, ClosureWindowTag, CommodityTariffChronologyTag, CommodityTariffCommodityTag, CommodityTariffFlowTag, CommonNumberTag, CommonPositionTag, ElectricalMeasurementTag, PowerSourceTag, RefrigeratorTag, } from '@matter/node';
3
+ import { ClosureCoveringTag, ClosurePanelTag, ClosureTag, ClosureWindowTag, CommodityTariffChronologyTag, CommodityTariffCommodityTag, CommodityTariffFlowTag, CommonNumberTag, CommonPositionTag, ElectricalMeasurementTag, PowerSourceTag, RefrigeratorTag, } from '@matter/node';
4
4
  import { AirQuality } from '@matter/types/clusters/air-quality';
5
+ import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
5
6
  import { DoorLock } from '@matter/types/clusters/door-lock';
6
7
  import { FanControl } from '@matter/types/clusters/fan-control';
7
8
  import { PowerSource } from '@matter/types/clusters/power-source';
@@ -354,13 +355,19 @@ export async function createDemoDevices(matterbridge) {
354
355
  await registerDevice(ep, 'Window Covering Tilt', 'ENTRY-08-03-1');
355
356
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCoveringController'), bridgedNode, powerSource], { id: 'WindowCoveringController', number: EndpointNumber(8_04) });
356
357
  await registerDevice(ep, 'Window Covering Controller', 'ENTRY-08-04');
357
- ep = new Closure('Closure', 'ENTRY-08-05', { id: 'Closure', number: EndpointNumber(8_05), movementDuration: 2000, calibrationDuration: 2000 });
358
+ ep = new Closure('Closure', 'ENTRY-08-05', {
359
+ id: 'Closure',
360
+ number: EndpointNumber(8_05),
361
+ movementDuration: 2000,
362
+ });
358
363
  await registerDevice(ep, 'Closure', 'ENTRY-08-05');
359
364
  ep = new Closure('Closure Pedestrian', 'ENTRY-08-05-1', {
360
365
  id: 'ClosurePedestrian',
361
366
  number: EndpointNumber(8_05_1),
362
367
  movementDuration: 2000,
363
368
  calibrationDuration: 2000,
369
+ motionLatching: true,
370
+ speed: true,
364
371
  pedestrian: true,
365
372
  tagList: [getSemtag(ClosureTag.Gate)],
366
373
  });
@@ -370,6 +377,8 @@ export async function createDemoDevices(matterbridge) {
370
377
  number: EndpointNumber(8_05_2),
371
378
  movementDuration: 2000,
372
379
  calibrationDuration: 2000,
380
+ motionLatching: true,
381
+ speed: true,
373
382
  ventilation: true,
374
383
  tagList: [getSemtag(ClosureTag.Window), getSemtag(ClosureWindowTag.Facade)],
375
384
  });
@@ -379,6 +388,8 @@ export async function createDemoDevices(matterbridge) {
379
388
  number: EndpointNumber(8_05_3),
380
389
  movementDuration: 2000,
381
390
  calibrationDuration: 2000,
391
+ motionLatching: true,
392
+ speed: true,
382
393
  calibration: true,
383
394
  tagList: [getSemtag(ClosureTag.GarageDoor)],
384
395
  });
@@ -388,12 +399,48 @@ export async function createDemoDevices(matterbridge) {
388
399
  number: EndpointNumber(8_05_4),
389
400
  movementDuration: 2000,
390
401
  calibrationDuration: 2000,
402
+ motionLatching: true,
403
+ speed: true,
391
404
  ventilation: true,
392
405
  pedestrian: true,
393
406
  calibration: true,
394
407
  tagList: [getSemtag(ClosureTag.Door)],
395
408
  });
396
409
  await registerDevice(ep, 'Closure Complete', 'ENTRY-08-05-4');
410
+ const closurePanelRoller = new Closure('Closure Panel Roller', 'ENTRY-08-06-1', {
411
+ id: 'ClosurePanelRoller',
412
+ number: EndpointNumber(8_06_1),
413
+ movementDuration: 2000,
414
+ signaturePosition: 50_00,
415
+ tagList: [getSemtag(ClosureTag.Covering)],
416
+ });
417
+ closurePanelRoller.addPanel('Roller', [getSemtag(ClosurePanelTag.Lift)], 'lift', { number: EndpointNumber(8_06_2), movementDuration: 2000 });
418
+ ep = closurePanelRoller;
419
+ await registerDevice(ep, 'Closure Panel Roller', 'ENTRY-08-06-1');
420
+ const closurePanelVenetian = new Closure('Closure Panel Venetian', 'ENTRY-08-06-3', {
421
+ id: 'ClosurePanelVenetian',
422
+ number: EndpointNumber(8_06_3),
423
+ movementDuration: 2000,
424
+ signaturePosition: 20_00,
425
+ tagList: [getSemtag(ClosureTag.Covering), getSemtag(ClosureCoveringTag.Venetian)],
426
+ });
427
+ closurePanelVenetian.addPanel('Venetian', [getSemtag(ClosurePanelTag.Tilt)], 'tilt', { number: EndpointNumber(8_06_4), movementDuration: 2000 });
428
+ ep = closurePanelVenetian;
429
+ await registerDevice(ep, 'Closure Panel Venetian', 'ENTRY-08-06-3');
430
+ const closurePanelSmartGlass = new Closure('Closure Panel Smart-Glass', 'ENTRY-08-06-5', {
431
+ id: 'ClosurePanelSmartGlass',
432
+ number: EndpointNumber(8_06_5),
433
+ movementDuration: 2000,
434
+ signaturePosition: 10_00,
435
+ tagList: [getSemtag(ClosureTag.Window)],
436
+ });
437
+ closurePanelSmartGlass.addPanel('Smart-Glass', [getSemtag(ClosurePanelTag.Lift, 'Opacity')], 'modulation', {
438
+ number: EndpointNumber(8_06_6),
439
+ modulationType: ClosureDimension.ModulationType.Opacity,
440
+ movementDuration: 2000,
441
+ });
442
+ ep = closurePanelSmartGlass;
443
+ await registerDevice(ep, 'Closure Panel Smart-Glass', 'ENTRY-08-06-5');
397
444
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('ClosureController'), bridgedNode, powerSource], { id: 'ClosureController', number: EndpointNumber(8_07) });
398
445
  await registerDevice(ep, 'Closure Controller', 'ENTRY-08-07');
399
446
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'Thermostat', number: EndpointNumber(9_01) });
@@ -1,3 +1,4 @@
1
+ import type { MaybePromise } from '@matter/general';
1
2
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
2
3
  import { type EndpointNumber } from '@matter/types';
3
4
  import { ClosureControl } from '@matter/types/clusters/closure-control';
@@ -19,6 +20,7 @@ declare const MatterbridgeClosureControlServerBase: import("@matter/node").Clust
19
20
  export declare class MatterbridgeClosureControlServer extends MatterbridgeClosureControlServerBase {
20
21
  readonly state: MatterbridgeClosureControlServer.State;
21
22
  protected internal: MatterbridgeClosureControlServer.Internal;
23
+ initialize(): MaybePromise;
22
24
  moveTo: (request: ClosureControl.MoveToRequest) => Promise<void>;
23
25
  private completeMoveTo;
24
26
  stop: () => Promise<void>;
@@ -33,6 +35,7 @@ export declare namespace MatterbridgeClosureControlServer {
33
35
  class State extends MatterbridgeClosureControlServerBase.State {
34
36
  movementDuration: number;
35
37
  calibrationDuration: number;
38
+ signaturePosition: number;
36
39
  }
37
40
  }
38
41
  export interface ClosureOptions {
@@ -47,6 +50,9 @@ export interface ClosureOptions {
47
50
  latchControlModes?: ClosureControl.LatchControlModes;
48
51
  movementDuration?: number;
49
52
  calibrationDuration?: number;
53
+ signaturePosition?: number;
54
+ motionLatching?: boolean;
55
+ speed?: boolean;
50
56
  ventilation?: boolean;
51
57
  pedestrian?: boolean;
52
58
  calibration?: boolean;
@@ -57,6 +63,7 @@ export interface ClosureOptions {
57
63
  export declare class Closure extends MatterbridgeEndpoint {
58
64
  constructor(name: string, serial: string, options?: ClosureOptions);
59
65
  getMainState(): ClosureControl.MainState | undefined;
66
+ getSignaturePosition(): number | undefined;
60
67
  setState(currentState: ClosureControl.OverallCurrentState, targetState: ClosureControl.OverallTargetState, mainState?: ClosureControl.MainState, countdownTime?: number, currentErrorList?: ClosureControl.ClosureError[]): Promise<void>;
61
68
  setFullyClosed(): Promise<void>;
62
69
  setFullOpened(): Promise<void>;
@@ -18,6 +18,13 @@ const targetToCurrentPosition = {
18
18
  };
19
19
  const MatterbridgeClosureControlServerBase = ClosureControlServer.with(ClosureControl.Feature.Positioning, ClosureControl.Feature.MotionLatching, ClosureControl.Feature.Speed, ClosureControl.Feature.Calibration);
20
20
  export class MatterbridgeClosureControlServer extends MatterbridgeClosureControlServerBase {
21
+ initialize() {
22
+ if (process.env.MATTERBRIDGE_CHIP_TEST) {
23
+ this.state.movementDuration = 2000;
24
+ this.state.calibrationDuration = 2000;
25
+ }
26
+ return super.initialize();
27
+ }
21
28
  moveTo = async (request) => {
22
29
  const device = this.endpoint.stateOf(MatterbridgeServer);
23
30
  device.log.info(`MoveTo (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
@@ -31,38 +38,40 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
31
38
  if (request.position !== undefined && !(request.position in targetToCurrentPosition)) {
32
39
  throw new StatusResponse.ConstraintErrorError('ClosureControl.moveTo Position is not a valid TargetPositionEnum value');
33
40
  }
34
- if (request.latch !== undefined && typeof request.latch !== 'boolean') {
41
+ if (request.position === undefined && request.latch === undefined && request.speed === undefined) {
42
+ throw new StatusResponse.InvalidCommandError('ClosureControl.moveTo requires at least one of position, latch, or speed to be present');
43
+ }
44
+ if (request.position === undefined && (!this.features.motionLatching || request.latch === undefined) && (!this.features.speed || request.speed === undefined))
45
+ return;
46
+ if (this.features.motionLatching && request.latch !== undefined && typeof request.latch !== 'boolean') {
35
47
  throw new StatusResponse.ConstraintErrorError('ClosureControl.moveTo Latch is not a boolean value');
36
48
  }
37
- if (request.speed !== undefined && !(request.speed in ThreeLevelAuto)) {
49
+ if (this.features.speed && request.speed !== undefined && !(request.speed in ThreeLevelAuto)) {
38
50
  throw new StatusResponse.ConstraintErrorError('ClosureControl.moveTo Speed is not a valid ThreeLevelAutoEnum value');
39
51
  }
40
- if (request.position === undefined && request.latch === undefined && request.speed === undefined) {
41
- throw new StatusResponse.InvalidCommandError('ClosureControl.moveTo requires at least one of position, latch, or speed to be present');
42
- }
43
52
  if (![ClosureControl.MainState.Moving, ClosureControl.MainState.WaitingForMotion, ClosureControl.MainState.Stopped].includes(this.state.mainState)) {
44
53
  throw new StatusResponse.InvalidInStateError('ClosureControl.moveTo is only allowed while Moving, WaitingForMotion, or Stopped');
45
54
  }
46
55
  let currentState = this.state.overallCurrentState;
47
- if (currentState?.latch === true && request.position !== undefined && request.latch !== false) {
56
+ if (this.features.motionLatching && currentState?.latch === true && request.position !== undefined && request.latch !== false) {
48
57
  throw new StatusResponse.InvalidInStateError('ClosureControl.moveTo position changes require latch false while the closure is latched');
49
58
  }
50
59
  const previousTarget = this.state.overallTargetState ?? {};
51
60
  const nextTarget = {
52
61
  ...previousTarget,
53
62
  ...(request?.position !== undefined ? { position: request.position } : null),
54
- ...(request?.latch !== undefined ? { latch: request.latch } : null),
55
- speed: request?.speed ?? ThreeLevelAuto.Auto,
63
+ ...(this.features.motionLatching && request?.latch !== undefined ? { latch: request.latch } : null),
64
+ ...(this.features.speed ? { speed: request?.speed ?? ThreeLevelAuto.Auto } : null),
56
65
  };
57
66
  this.state.overallTargetState = nextTarget;
58
- if (currentState !== null) {
67
+ if (this.features.speed && currentState !== null) {
59
68
  currentState = { ...currentState, speed: nextTarget.speed };
60
69
  this.state.overallCurrentState = currentState;
61
70
  }
62
71
  const isAtTarget = currentState !== null &&
63
72
  (nextTarget.position === undefined || nextTarget.position === currentState.position) &&
64
- (nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
65
- nextTarget.speed === currentState.speed;
73
+ (!this.features.motionLatching || nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
74
+ (!this.features.speed || nextTarget.speed === currentState.speed);
66
75
  this.state.mainState = isAtTarget ? ClosureControl.MainState.Stopped : ClosureControl.MainState.Moving;
67
76
  clearTimeout(this.internal.movementTimer);
68
77
  if (isAtTarget) {
@@ -90,8 +99,13 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
90
99
  position = mappedPosition;
91
100
  }
92
101
  const latch = targetState.latch ?? previousState.latch;
93
- const secureState = latch === true;
94
- await closure.setState({ position, latch, speed: targetState.speed, secureState }, targetState, ClosureControl.MainState.Stopped, 0);
102
+ const secureState = this.features.motionLatching ? latch === true : position === ClosureControl.CurrentPosition.FullyClosed;
103
+ await closure.setState({
104
+ position,
105
+ ...(this.features.motionLatching ? { latch } : null),
106
+ ...(this.features.speed ? { speed: targetState.speed } : null),
107
+ secureState,
108
+ }, targetState, ClosureControl.MainState.Stopped, 0);
95
109
  if (secureState !== previousState.secureState) {
96
110
  await closure.triggerSecureStateChanged(secureState);
97
111
  }
@@ -160,21 +174,13 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
160
174
  class State extends MatterbridgeClosureControlServerBase.State {
161
175
  movementDuration = 0;
162
176
  calibrationDuration = 0;
177
+ signaturePosition = 50_00;
163
178
  }
164
179
  MatterbridgeClosureControlServer.State = State;
165
180
  })(MatterbridgeClosureControlServer || (MatterbridgeClosureControlServer = {}));
166
181
  export class Closure extends MatterbridgeEndpoint {
167
182
  constructor(name, serial, options = {}) {
168
- const { identifyTime = 0, identifyType = Identify.IdentifyType.None, powerSourceType = 'Wired', countdownTime = 0, mainState = ClosureControl.MainState.Stopped, currentErrorList = [], overallCurrentState = {
169
- position: ClosureControl.CurrentPosition.FullyClosed,
170
- latch: true,
171
- speed: ThreeLevelAuto.Auto,
172
- secureState: true,
173
- }, overallTargetState = {
174
- position: ClosureControl.TargetPosition.MoveToFullyClosed,
175
- latch: true,
176
- speed: ThreeLevelAuto.Auto,
177
- }, latchControlModes = { remoteLatching: true, remoteUnlatching: true }, movementDuration = 0, calibrationDuration = 0, ventilation = false, pedestrian = false, calibration = false, id, number, tagList = [getSemtag(ClosureTag.Covering)], } = options;
183
+ const { identifyTime = 0, identifyType = Identify.IdentifyType.None, powerSourceType = 'Wired', countdownTime = 0, mainState = ClosureControl.MainState.Stopped, currentErrorList = [], overallCurrentState, overallTargetState, latchControlModes, movementDuration = 0, calibrationDuration = 0, signaturePosition = 50_00, motionLatching = false, speed = false, ventilation = false, pedestrian = false, calibration = false, id, number, tagList = [getSemtag(ClosureTag.Covering)], } = options;
178
184
  super(powerSourceType === 'None' ? [closure] : [closure, powerSource], {
179
185
  id: id ?? `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
180
186
  number,
@@ -202,25 +208,48 @@ export class Closure extends MatterbridgeEndpoint {
202
208
  countdownTime,
203
209
  mainState,
204
210
  currentErrorList,
205
- overallCurrentState,
206
- overallTargetState,
207
- latchControlModes,
211
+ overallCurrentState: {
212
+ position: overallCurrentState?.position ?? ClosureControl.CurrentPosition.FullyClosed,
213
+ ...(motionLatching ? { latch: overallCurrentState?.latch ?? true } : null),
214
+ ...(speed ? { speed: overallCurrentState?.speed ?? ThreeLevelAuto.Auto } : null),
215
+ secureState: overallCurrentState?.secureState ?? true,
216
+ },
217
+ overallTargetState: {
218
+ position: overallTargetState?.position ?? ClosureControl.TargetPosition.MoveToFullyClosed,
219
+ ...(motionLatching ? { latch: overallTargetState?.latch ?? true } : null),
220
+ ...(speed ? { speed: overallTargetState?.speed ?? ThreeLevelAuto.Auto } : null),
221
+ },
222
+ ...(motionLatching ? { latchControlModes: latchControlModes ?? { remoteLatching: true, remoteUnlatching: true } } : null),
208
223
  movementDuration,
209
224
  calibrationDuration,
225
+ signaturePosition,
210
226
  };
211
- this.behaviors.require(calibration || ventilation || pedestrian
212
- ? MatterbridgeClosureControlServer.with(ClosureControl.Feature.Positioning, ClosureControl.Feature.MotionLatching, ClosureControl.Feature.Speed, ...(calibration ? [ClosureControl.Feature.Calibration] : []), ...(ventilation ? [ClosureControl.Feature.Ventilation] : []), ...(pedestrian ? [ClosureControl.Feature.Pedestrian] : []))
213
- : MatterbridgeClosureControlServer, closureControlOptions);
227
+ this.behaviors.require(MatterbridgeClosureControlServer.with(ClosureControl.Feature.Positioning, ...(motionLatching ? [ClosureControl.Feature.MotionLatching] : []), ...(speed ? [ClosureControl.Feature.Speed] : []), ...(calibration ? [ClosureControl.Feature.Calibration] : []), ...(ventilation ? [ClosureControl.Feature.Ventilation] : []), ...(pedestrian ? [ClosureControl.Feature.Pedestrian] : [])), closureControlOptions);
214
228
  }
215
229
  getMainState() {
216
230
  return this.getAttribute(ClosureControlServer, 'mainState');
217
231
  }
232
+ getSignaturePosition() {
233
+ return this.getAttribute(MatterbridgeClosureControlServer, 'signaturePosition');
234
+ }
218
235
  async setState(currentState, targetState, mainState = ClosureControl.MainState.Stopped, countdownTime = 0, currentErrorList = []) {
236
+ const featureMap = this.getAttribute(ClosureControl.id, 'featureMap');
237
+ const supportsMotionLatching = featureMap?.motionLatching === true;
238
+ const supportsSpeed = featureMap?.speed === true;
219
239
  await this.setAttribute(ClosureControl, 'countdownTime', countdownTime);
220
240
  await this.setAttribute(ClosureControl, 'mainState', mainState);
221
241
  await this.setAttribute(ClosureControl, 'currentErrorList', currentErrorList);
222
- await this.setAttribute(ClosureControl, 'overallCurrentState', currentState);
223
- await this.setAttribute(ClosureControl, 'overallTargetState', targetState);
242
+ await this.setAttribute(ClosureControl, 'overallCurrentState', {
243
+ position: currentState.position,
244
+ ...(supportsMotionLatching ? { latch: currentState.latch } : null),
245
+ ...(supportsSpeed ? { speed: currentState.speed } : null),
246
+ secureState: currentState.secureState,
247
+ });
248
+ await this.setAttribute(ClosureControl, 'overallTargetState', {
249
+ position: targetState.position,
250
+ ...(supportsMotionLatching ? { latch: targetState.latch } : null),
251
+ ...(supportsSpeed ? { speed: targetState.speed } : null),
252
+ });
224
253
  }
225
254
  async setFullyClosed() {
226
255
  await this.setState({
@@ -270,7 +299,7 @@ export class Closure extends MatterbridgeEndpoint {
270
299
  await this.triggerEvent(ClosureControl, 'secureStateChanged', { secureValue });
271
300
  }
272
301
  addPanel(name, tagList, dimensionType, options = {}) {
273
- const panel = this.addChildDeviceType(name, closurePanel, { tagList });
302
+ const panel = this.addChildDeviceType(name, closurePanel, { number: options.number, tagList });
274
303
  createClosureDimensionClusterServer(panel, dimensionType, options);
275
304
  return panel;
276
305
  }
@@ -1,8 +1,10 @@
1
+ import type { MaybePromise } from '@matter/general';
1
2
  import { ClosureDimensionServer } from '@matter/node/behaviors/closure-dimension';
2
3
  import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
4
+ import type { EndpointNumber } from '@matter/types/datatype';
3
5
  import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
4
6
  export type ClosureDimensionType = 'lift' | 'tilt' | 'modulation';
5
- declare const MatterbridgeClosureDimensionServer_base: import("@matter/node").ClusterBehavior.Type<typeof ClosureDimensionServer, import("@matter/types").ClusterType.WithSupportedFeatures<ClosureDimension, {
7
+ declare const MatterbridgeClosureDimensionServerBase: import("@matter/node").ClusterBehavior.Type<typeof ClosureDimensionServer, import("@matter/types").ClusterType.WithSupportedFeatures<ClosureDimension, {
6
8
  limitation: false;
7
9
  modulation: false;
8
10
  motionLatching: true;
@@ -12,16 +14,33 @@ declare const MatterbridgeClosureDimensionServer_base: import("@matter/node").Cl
12
14
  translation: false;
13
15
  unit: false;
14
16
  }>, import("@matter/types").ClusterType.Concrete, new () => {}, "closureDimension">;
15
- export declare class MatterbridgeClosureDimensionServer extends MatterbridgeClosureDimensionServer_base {
17
+ export declare class MatterbridgeClosureDimensionServer extends MatterbridgeClosureDimensionServerBase {
18
+ readonly state: MatterbridgeClosureDimensionServer.State;
19
+ protected internal: MatterbridgeClosureDimensionServer.Internal;
20
+ initialize(): MaybePromise;
16
21
  setTarget: (request: ClosureDimension.SetTargetRequest) => Promise<void>;
17
22
  step: (request: ClosureDimension.StepRequest) => Promise<void>;
23
+ private scheduleMovement;
24
+ private completeMovement;
25
+ }
26
+ export declare namespace MatterbridgeClosureDimensionServer {
27
+ class Internal extends MatterbridgeClosureDimensionServerBase.Internal {
28
+ movementTimer?: NodeJS.Timeout;
29
+ }
30
+ class State extends MatterbridgeClosureDimensionServerBase.State {
31
+ movementDuration: number;
32
+ }
18
33
  }
19
34
  export interface ClosurePanelOptions {
35
+ number?: EndpointNumber;
20
36
  currentState?: ClosureDimension.DimensionState;
21
37
  targetState?: ClosureDimension.DimensionState;
22
38
  resolution?: number;
23
39
  stepValue?: number;
40
+ motionLatching?: boolean;
41
+ speed?: boolean;
24
42
  latchControlModes?: ClosureDimension.LatchControlModes;
43
+ movementDuration?: number;
25
44
  translationDirection?: ClosureDimension.TranslationDirection;
26
45
  rotationAxis?: ClosureDimension.RotationAxis;
27
46
  overflow?: ClosureDimension.Overflow;
@@ -5,7 +5,14 @@ import { ClosureControl } from '@matter/types/clusters/closure-control';
5
5
  import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
6
6
  import { ThreeLevelAuto } from '@matter/types/globals';
7
7
  import { MatterbridgeServer } from '../behaviors/matterbridgeServer.js';
8
- export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.MotionLatching, ClosureDimension.Feature.Speed) {
8
+ const MatterbridgeClosureDimensionServerBase = ClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.MotionLatching, ClosureDimension.Feature.Speed);
9
+ export class MatterbridgeClosureDimensionServer extends MatterbridgeClosureDimensionServerBase {
10
+ initialize() {
11
+ if (process.env.MATTERBRIDGE_CHIP_TEST) {
12
+ this.state.movementDuration = 2000;
13
+ }
14
+ return super.initialize();
15
+ }
9
16
  setTarget = async (request) => {
10
17
  const device = this.endpoint.stateOf(MatterbridgeServer);
11
18
  device.log.info(`SetTarget (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
@@ -22,11 +29,16 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
22
29
  if (request.position !== undefined && (request.position < 0 || request.position > 10000)) {
23
30
  throw new StatusResponse.ConstraintErrorError('ClosureDimension.setTarget position must be between 0 and 10000');
24
31
  }
32
+ const hasSupportedField = request.position !== undefined || (this.features.motionLatching && request.latch !== undefined) || (this.features.speed && request.speed !== undefined);
33
+ if (!hasSupportedField)
34
+ return;
25
35
  const latchControlModes = this.state.latchControlModes;
26
- if (request.latch !== undefined && ((request.latch && !latchControlModes?.remoteLatching) || (!request.latch && !latchControlModes?.remoteUnlatching))) {
36
+ if (this.features.motionLatching &&
37
+ request.latch !== undefined &&
38
+ ((request.latch && !latchControlModes?.remoteLatching) || (!request.latch && !latchControlModes?.remoteUnlatching))) {
27
39
  throw new StatusResponse.InvalidInStateError('ClosureDimension.setTarget latch change requires manual intervention per LatchControlModes');
28
40
  }
29
- if (request.speed !== undefined && (request.speed < ThreeLevelAuto.Auto || request.speed > ThreeLevelAuto.High)) {
41
+ if (this.features.speed && request.speed !== undefined && (request.speed < ThreeLevelAuto.Auto || request.speed > ThreeLevelAuto.High)) {
30
42
  throw new StatusResponse.ConstraintErrorError('ClosureDimension.setTarget speed must be a valid ThreeLevelAutoEnum value');
31
43
  }
32
44
  const associatedMainState = this.endpoint.owner?.maybeStateOf(ClosureControlServer)?.mainState;
@@ -41,7 +53,7 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
41
53
  throw new StatusResponse.InvalidInStateError('ClosureDimension.setTarget is not allowed while the associated ClosureControl is Disengaged, Protected, Calibrating, SetupRequired, or Error');
42
54
  }
43
55
  const currentState = this.state.currentState;
44
- if (request.position !== undefined && currentState?.latch === true && request.latch !== false) {
56
+ if (this.features.motionLatching && request.position !== undefined && currentState?.latch === true && request.latch !== false) {
45
57
  throw new StatusResponse.InvalidInStateError('ClosureDimension.setTarget position changes require latch false while the closure is latched');
46
58
  }
47
59
  const previousTarget = this.state.targetState ?? {};
@@ -49,16 +61,17 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
49
61
  const nextTarget = {
50
62
  ...previousTarget,
51
63
  ...(request?.position !== undefined ? { position: Math.round(request.position / resolution) * resolution } : null),
52
- ...(request?.latch !== undefined ? { latch: request.latch } : null),
53
- speed: request?.speed ?? ThreeLevelAuto.Auto,
64
+ ...(this.features.motionLatching && request?.latch !== undefined ? { latch: request.latch } : null),
65
+ ...(this.features.speed ? { speed: request?.speed ?? ThreeLevelAuto.Auto } : null),
54
66
  };
55
67
  const matchesCurrentState = currentState !== null &&
56
68
  (nextTarget.position === undefined || nextTarget.position === currentState.position) &&
57
- (nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
58
- nextTarget.speed === currentState.speed;
69
+ (!this.features.motionLatching || nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
70
+ (!this.features.speed || nextTarget.speed === currentState.speed);
59
71
  if (matchesCurrentState)
60
72
  return;
61
73
  this.state.targetState = nextTarget;
74
+ this.scheduleMovement(nextTarget, currentState);
62
75
  };
63
76
  step = async (request) => {
64
77
  const device = this.endpoint.stateOf(MatterbridgeServer);
@@ -76,11 +89,11 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
76
89
  if (request.numberOfSteps < 1) {
77
90
  throw new StatusResponse.ConstraintErrorError('ClosureDimension.step numberOfSteps must be at least 1');
78
91
  }
79
- if (request.speed !== undefined && (request.speed < ThreeLevelAuto.Auto || request.speed > ThreeLevelAuto.High)) {
92
+ if (this.features.speed && request.speed !== undefined && (request.speed < ThreeLevelAuto.Auto || request.speed > ThreeLevelAuto.High)) {
80
93
  throw new StatusResponse.ConstraintErrorError('ClosureDimension.step speed must be a valid ThreeLevelAutoEnum value');
81
94
  }
82
95
  const currentState = this.state.currentState;
83
- if (currentState?.latch === true) {
96
+ if (this.features.motionLatching && currentState?.latch === true) {
84
97
  throw new StatusResponse.InvalidInStateError('ClosureDimension.step is not allowed while the closure is latched');
85
98
  }
86
99
  const associatedMainState = this.endpoint.owner?.maybeStateOf(ClosureControlServer)?.mainState;
@@ -102,36 +115,82 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
102
115
  let nextPosition = isIncrease ? currentPosition + delta : currentPosition - delta;
103
116
  nextPosition = Math.max(0, Math.min(10000, nextPosition));
104
117
  const previousTarget = this.state.targetState ?? {};
105
- this.state.targetState = {
118
+ const nextTarget = {
106
119
  ...previousTarget,
107
120
  position: nextPosition,
108
- ...(request.speed !== undefined ? { speed: request.speed } : null),
121
+ ...(this.features.speed && request.speed !== undefined ? { speed: request.speed } : null),
109
122
  };
123
+ this.state.targetState = nextTarget;
124
+ this.scheduleMovement(nextTarget, currentState);
125
+ };
126
+ scheduleMovement(targetState, currentState) {
127
+ clearTimeout(this.internal.movementTimer);
128
+ if (currentState === null || this.state.movementDuration <= 0) {
129
+ this.internal.movementTimer = undefined;
130
+ return;
131
+ }
132
+ const previousState = currentState;
133
+ this.internal.movementTimer = setTimeout(() => {
134
+ this.internal.movementTimer = undefined;
135
+ void this.completeMovement(targetState, previousState);
136
+ }, this.state.movementDuration);
137
+ }
138
+ completeMovement = async (targetState, previousState) => {
139
+ const endpoint = this.endpoint;
140
+ const position = targetState.position ?? previousState.position;
141
+ const latch = targetState.latch ?? previousState.latch;
142
+ const speed = targetState.speed ?? previousState.speed;
143
+ await endpoint.setAttribute(ClosureDimensionServer, 'currentState', {
144
+ position,
145
+ ...(this.features.motionLatching ? { latch } : null),
146
+ ...(this.features.speed ? { speed } : null),
147
+ });
110
148
  };
111
149
  }
150
+ (function (MatterbridgeClosureDimensionServer) {
151
+ class Internal extends MatterbridgeClosureDimensionServerBase.Internal {
152
+ movementTimer;
153
+ }
154
+ MatterbridgeClosureDimensionServer.Internal = Internal;
155
+ class State extends MatterbridgeClosureDimensionServerBase.State {
156
+ movementDuration = 0;
157
+ }
158
+ MatterbridgeClosureDimensionServer.State = State;
159
+ })(MatterbridgeClosureDimensionServer || (MatterbridgeClosureDimensionServer = {}));
112
160
  export function createClosureDimensionClusterServer(endpoint, dimensionType, options) {
161
+ const motionLatching = options.motionLatching ?? false;
162
+ const speed = options.speed ?? false;
113
163
  const commonOptions = {
114
- currentState: options.currentState ?? { position: 0, latch: true, speed: ThreeLevelAuto.Auto },
115
- targetState: options.targetState ?? { position: 0, latch: true, speed: ThreeLevelAuto.Auto },
116
- resolution: Math.max(1, options.resolution ?? 1),
117
- stepValue: Math.max(1, options.stepValue ?? 1),
118
- latchControlModes: options.latchControlModes ?? { remoteLatching: true, remoteUnlatching: true },
164
+ currentState: {
165
+ position: options.currentState?.position ?? 0,
166
+ ...(motionLatching ? { latch: options.currentState?.latch ?? true } : null),
167
+ ...(speed ? { speed: options.currentState?.speed ?? ThreeLevelAuto.Auto } : null),
168
+ },
169
+ targetState: {
170
+ position: options.targetState?.position ?? 0,
171
+ ...(motionLatching ? { latch: options.targetState?.latch ?? true } : null),
172
+ ...(speed ? { speed: options.targetState?.speed ?? ThreeLevelAuto.Auto } : null),
173
+ },
174
+ resolution: Math.max(1, options.resolution ?? 100),
175
+ stepValue: Math.max(1, options.stepValue ?? 100),
176
+ ...(motionLatching ? { latchControlModes: options.latchControlModes ?? { remoteLatching: true, remoteUnlatching: true } } : null),
177
+ movementDuration: options.movementDuration ?? 0,
119
178
  };
120
179
  if (dimensionType === 'lift') {
121
- endpoint.behaviors.require(MatterbridgeClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.MotionLatching, ClosureDimension.Feature.Speed, ClosureDimension.Feature.Translation), {
180
+ endpoint.behaviors.require(MatterbridgeClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.Translation, ...(motionLatching ? [ClosureDimension.Feature.MotionLatching] : []), ...(speed ? [ClosureDimension.Feature.Speed] : [])), {
122
181
  ...commonOptions,
123
182
  translationDirection: options.translationDirection ?? ClosureDimension.TranslationDirection.Downward,
124
183
  });
125
184
  }
126
185
  else if (dimensionType === 'tilt') {
127
- endpoint.behaviors.require(MatterbridgeClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.MotionLatching, ClosureDimension.Feature.Speed, ClosureDimension.Feature.Rotation), {
186
+ endpoint.behaviors.require(MatterbridgeClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.Rotation, ...(motionLatching ? [ClosureDimension.Feature.MotionLatching] : []), ...(speed ? [ClosureDimension.Feature.Speed] : [])), {
128
187
  ...commonOptions,
129
188
  rotationAxis: options.rotationAxis ?? ClosureDimension.RotationAxis.CenteredHorizontal,
130
189
  overflow: options.overflow ?? ClosureDimension.Overflow.NoOverflow,
131
190
  });
132
191
  }
133
192
  else {
134
- endpoint.behaviors.require(MatterbridgeClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.MotionLatching, ClosureDimension.Feature.Speed, ClosureDimension.Feature.Modulation), {
193
+ endpoint.behaviors.require(MatterbridgeClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.Modulation, ...(motionLatching ? [ClosureDimension.Feature.MotionLatching] : []), ...(speed ? [ClosureDimension.Feature.Speed] : [])), {
135
194
  ...commonOptions,
136
195
  modulationType: options.modulationType ?? ClosureDimension.ModulationType.SlatsOrientation,
137
196
  });
package/dist/frontend.js CHANGED
@@ -1131,8 +1131,10 @@ export class Frontend extends EventEmitter {
1131
1131
  if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && isValidObject(attributeValue)) {
1132
1132
  const overallCurrentState = attributeValue;
1133
1133
  attributes += `Position: ${getEnumDescription(ClosureControl.CurrentPosition, overallCurrentState.position, { fallback: 'unknown' })} `;
1134
- attributes += `Latch: ${overallCurrentState.latch ?? 'unknown'} `;
1135
- attributes += `Speed: ${getEnumDescription(ThreeLevelAuto, overallCurrentState.speed, { fallback: 'unknown' })} `;
1134
+ if (overallCurrentState.latch !== undefined)
1135
+ attributes += `Latch: ${overallCurrentState.latch} `;
1136
+ if (overallCurrentState.speed !== undefined)
1137
+ attributes += `Speed: ${getEnumDescription(ThreeLevelAuto, overallCurrentState.speed)} `;
1136
1138
  attributes += `SecureState: ${overallCurrentState.secureState ?? 'unknown'} `;
1137
1139
  }
1138
1140
  if (clusterName === 'thermostat' && attributeName === 'localTemperature' && isValidNumber(attributeValue))
@@ -90,7 +90,9 @@ export declare class MatterbridgeEndpoint extends Endpoint {
90
90
  getDeviceTypes(): DeviceTypeDefinition[];
91
91
  hasClusterServer(cluster: Behavior.Type | ClusterType | ClusterId | string): boolean;
92
92
  hasAttributeServer(cluster: Behavior.Type | ClusterType | ClusterId | string, attribute: string): boolean;
93
- getClusterServerOptions(cluster: Behavior.Type | ClusterType | ClusterId | string): Record<string, boolean | number | bigint | string | object | null> | undefined;
93
+ getClusterServerOptions<T extends Behavior.Type>(cluster: T): Partial<Behavior.StateOf<T>> | undefined;
94
+ getClusterServerOptions<T extends ClusterType>(cluster: T): Partial<ClusterAttributesOf<T>> | undefined;
95
+ getClusterServerOptions(cluster: ClusterId | string): Record<string, boolean | number | bigint | string | object | null> | undefined;
94
96
  getAttribute<T extends Behavior.Type, A extends keyof Behavior.StateOf<T>>(cluster: T, attribute: A, log?: AnsiLogger): Behavior.StateOf<T>[A] | undefined;
95
97
  getAttribute<T extends ClusterType, A extends keyof ClusterAttributesOf<T>>(cluster: T, attribute: A, log?: AnsiLogger): ClusterAttributesOf<T>[A] | undefined;
96
98
  getAttribute(cluster: ClusterId | string, attribute: string, log?: AnsiLogger): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.8-dev-20260829-e779e7d",
3
+ "version": "3.10.8-dev-20260831-3e81235",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -132,16 +132,16 @@
132
132
  },
133
133
  "dependencies": {
134
134
  "@matter/main": "0.17.9",
135
- "@matterbridge/dgram": "3.10.8-dev-20260829-e779e7d",
136
- "@matterbridge/thread": "3.10.8-dev-20260829-e779e7d",
137
- "@matterbridge/types": "3.10.8-dev-20260829-e779e7d",
138
- "@matterbridge/utils": "3.10.8-dev-20260829-e779e7d",
135
+ "@matterbridge/dgram": "3.10.8-dev-20260831-3e81235",
136
+ "@matterbridge/thread": "3.10.8-dev-20260831-3e81235",
137
+ "@matterbridge/types": "3.10.8-dev-20260831-3e81235",
138
+ "@matterbridge/utils": "3.10.8-dev-20260831-3e81235",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
- "express-rate-limit": "8.6.2",
142
- "multer": "2.2.0",
143
- "node-ansi-logger": "3.3.0",
144
- "node-persist-manager": "2.1.0",
141
+ "express-rate-limit": "8.7.0",
142
+ "multer": "2.3.0",
143
+ "node-ansi-logger": "3.3.1",
144
+ "node-persist-manager": "2.1.1",
145
145
  "ws": "8.21.3"
146
146
  }
147
147
  }