@matterbridge/core 3.10.8-dev-20260901-c4963a1 → 3.10.8-dev-20260902-361500d

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,3 +1,4 @@
1
+ import { type Timer } from '@matter/general';
1
2
  import { WindowCoveringBaseServer, WindowCoveringServer } from '@matter/node/behaviors/window-covering';
2
3
  import { WindowCovering } from '@matter/types/clusters/window-covering';
3
4
  declare const MatterbridgeWindowCoveringServer_base: import("@matter/node").ClusterBehavior.Type<typeof WindowCoveringServer, import("@matter/types").ClusterType.WithSupportedFeatures<WindowCovering, {
@@ -18,11 +19,14 @@ export declare class MatterbridgeWindowCoveringServer extends MatterbridgeWindow
18
19
  stopMotion(): Promise<void>;
19
20
  goToLiftPercentage(request: WindowCovering.GoToLiftPercentageRequest): Promise<void>;
20
21
  goToTiltPercentage(request: WindowCovering.GoToTiltPercentageRequest): Promise<void>;
22
+ [Symbol.asyncDispose](): Promise<void>;
21
23
  }
22
24
  export declare namespace MatterbridgeWindowCoveringServer {
23
25
  class Internal extends WindowCoveringBaseServer.Internal {
24
- liftMovementTimer?: NodeJS.Timeout;
25
- tiltMovementTimer?: NodeJS.Timeout;
26
+ liftMovementTimer?: Timer;
27
+ liftMovementTarget: number;
28
+ tiltMovementTimer?: Timer;
29
+ tiltMovementTarget: number;
26
30
  }
27
31
  class State extends WindowCoveringBaseServer.State {
28
32
  movementDuration: number;
@@ -1,3 +1,4 @@
1
+ import { Millis, Time } from '@matter/general';
1
2
  import { WindowCoveringBaseServer, WindowCoveringServer } from '@matter/node/behaviors/window-covering';
2
3
  import { WindowCovering } from '@matter/types/clusters/window-covering';
3
4
  import { MatterbridgeServer } from './matterbridgeServer.js';
@@ -35,7 +36,7 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
35
36
  this.state.operationalStatus.global = lift === WindowCovering.MovementStatus.Stopped ? tilt : lift;
36
37
  }
37
38
  #startLiftMovement(targetPercent100ths) {
38
- clearTimeout(this.internal.liftMovementTimer);
39
+ this.internal.liftMovementTimer?.stop();
39
40
  this.internal.liftMovementTimer = undefined;
40
41
  if (this.state.movementDuration <= 0)
41
42
  return;
@@ -44,19 +45,17 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
44
45
  this.#updateGlobalOperationalStatus();
45
46
  if (status === WindowCovering.MovementStatus.Stopped)
46
47
  return;
47
- this.internal.liftMovementTimer = setTimeout(() => {
48
- this.internal.liftMovementTimer = undefined;
49
- void this.#completeLiftMovement(targetPercent100ths);
50
- }, this.state.movementDuration);
51
- }
52
- #completeLiftMovement = async (targetPercent100ths) => {
53
- const windowCovering = this.endpoint;
54
- await windowCovering.setAttribute(WindowCovering, 'currentPositionLiftPercent100ths', targetPercent100ths);
55
- const tilt = windowCovering.getAttribute(WindowCovering, 'operationalStatus')?.tilt ?? WindowCovering.MovementStatus.Stopped;
56
- await windowCovering.setAttribute(WindowCovering, 'operationalStatus', { global: tilt, lift: WindowCovering.MovementStatus.Stopped, tilt });
57
- };
48
+ this.internal.liftMovementTarget = targetPercent100ths;
49
+ this.internal.liftMovementTimer = Time.getTimer('WindowCovering lift movement complete', Millis(this.state.movementDuration), this.callback(this.#completeLiftMovement, { lock: true })).start();
50
+ }
51
+ #completeLiftMovement() {
52
+ this.internal.liftMovementTimer = undefined;
53
+ this.state.currentPositionLiftPercent100ths = this.internal.liftMovementTarget;
54
+ this.state.operationalStatus.lift = WindowCovering.MovementStatus.Stopped;
55
+ this.#updateGlobalOperationalStatus();
56
+ }
58
57
  #startTiltMovement(targetPercent100ths) {
59
- clearTimeout(this.internal.tiltMovementTimer);
58
+ this.internal.tiltMovementTimer?.stop();
60
59
  this.internal.tiltMovementTimer = undefined;
61
60
  if (this.state.movementDuration <= 0)
62
61
  return;
@@ -65,17 +64,15 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
65
64
  this.#updateGlobalOperationalStatus();
66
65
  if (status === WindowCovering.MovementStatus.Stopped)
67
66
  return;
68
- this.internal.tiltMovementTimer = setTimeout(() => {
69
- this.internal.tiltMovementTimer = undefined;
70
- void this.#completeTiltMovement(targetPercent100ths);
71
- }, this.state.movementDuration);
72
- }
73
- #completeTiltMovement = async (targetPercent100ths) => {
74
- const windowCovering = this.endpoint;
75
- await windowCovering.setAttribute(WindowCovering, 'currentPositionTiltPercent100ths', targetPercent100ths);
76
- const lift = windowCovering.getAttribute(WindowCovering, 'operationalStatus')?.lift ?? WindowCovering.MovementStatus.Stopped;
77
- await windowCovering.setAttribute(WindowCovering, 'operationalStatus', { global: lift, lift, tilt: WindowCovering.MovementStatus.Stopped });
78
- };
67
+ this.internal.tiltMovementTarget = targetPercent100ths;
68
+ this.internal.tiltMovementTimer = Time.getTimer('WindowCovering tilt movement complete', Millis(this.state.movementDuration), this.callback(this.#completeTiltMovement, { lock: true })).start();
69
+ }
70
+ #completeTiltMovement() {
71
+ this.internal.tiltMovementTimer = undefined;
72
+ this.state.currentPositionTiltPercent100ths = this.internal.tiltMovementTarget;
73
+ this.state.operationalStatus.tilt = WindowCovering.MovementStatus.Stopped;
74
+ this.#updateGlobalOperationalStatus();
75
+ }
79
76
  async upOrOpen() {
80
77
  const device = this.endpoint.stateOf(MatterbridgeServer);
81
78
  device.log.info(`MatterbridgeWindowCoveringServer: opening cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
@@ -127,9 +124,9 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
127
124
  });
128
125
  device.log.debug(`MatterbridgeWindowCoveringServer: stopMotion called (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
129
126
  await super.stopMotion();
130
- clearTimeout(this.internal.liftMovementTimer);
127
+ this.internal.liftMovementTimer?.stop();
131
128
  this.internal.liftMovementTimer = undefined;
132
- clearTimeout(this.internal.tiltMovementTimer);
129
+ this.internal.tiltMovementTimer?.stop();
133
130
  this.internal.tiltMovementTimer = undefined;
134
131
  if (this.state.movementDuration > 0) {
135
132
  if (this.features.positionAwareLift)
@@ -174,11 +171,20 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
174
171
  this.#startTiltMovement(this.state.targetPositionTiltPercent100ths);
175
172
  device.log.debug(`MatterbridgeWindowCoveringServer: goToTiltPercentage result target ${this.state.targetPositionTiltPercent100ths} current ${this.state.currentPositionTiltPercent100ths} status global ${this.getMovementStatusLabel(this.state.operationalStatus.global)} lift ${this.getMovementStatusLabel(this.state.operationalStatus.lift)} tilt ${this.getMovementStatusLabel(this.state.operationalStatus.tilt)} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
176
173
  }
174
+ async [Symbol.asyncDispose]() {
175
+ this.internal.liftMovementTimer?.stop();
176
+ this.internal.liftMovementTimer = undefined;
177
+ this.internal.tiltMovementTimer?.stop();
178
+ this.internal.tiltMovementTimer = undefined;
179
+ await super[Symbol.asyncDispose]?.();
180
+ }
177
181
  }
178
182
  (function (MatterbridgeWindowCoveringServer) {
179
183
  class Internal extends WindowCoveringBaseServer.Internal {
180
184
  liftMovementTimer;
185
+ liftMovementTarget = 0;
181
186
  tiltMovementTimer;
187
+ tiltMovementTarget = 0;
182
188
  }
183
189
  MatterbridgeWindowCoveringServer.Internal = Internal;
184
190
  class State extends WindowCoveringBaseServer.State {
@@ -1,6 +1,6 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { ClosureCoveringTag, ClosurePanelTag, 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, SwitchesTag, } from '@matter/node';
4
4
  import { AirQuality } from '@matter/types/clusters/air-quality';
5
5
  import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
6
6
  import { DoorLock } from '@matter/types/clusters/door-lock';
@@ -267,8 +267,33 @@ export async function createDemoDevices(matterbridge) {
267
267
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('PumpController'), bridgedNode, powerSource], { id: 'PumpController', number: EndpointNumber(6_05) });
268
268
  ep.createDefaultPowerSourceWiredClusterServer();
269
269
  await registerDevice(ep, 'Pump Controller', 'SWITCH-06-05');
270
- ep = new MatterbridgeEndpoint([getSupportedDeviceType('GenericSwitch'), bridgedNode, powerSource], { id: 'GenericSwitch', number: EndpointNumber(6_06) });
270
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Aggregator'), bridgedNode, powerSource], { id: 'GenericSwitch', number: EndpointNumber(6_06) });
271
271
  ep.createDefaultPowerSourceBatteryClusterServer();
272
+ await ep.addFixedLabel('composed', 'GenericSwitch');
273
+ ep.addChildDeviceType('Button1', getSupportedDeviceType('GenericSwitch'), {
274
+ number: EndpointNumber(6_06_1),
275
+ tagList: [getSemtag(SwitchesTag.On), getSemtag(CommonNumberTag.One)],
276
+ })
277
+ .createDefaultMomentarySwitchClusterServer()
278
+ .addRequiredClusters();
279
+ ep.addChildDeviceType('Button2', getSupportedDeviceType('GenericSwitch'), {
280
+ number: EndpointNumber(6_06_2),
281
+ tagList: [getSemtag(SwitchesTag.Off), getSemtag(CommonNumberTag.Two)],
282
+ })
283
+ .createDefaultMomentarySwitchClusterServer()
284
+ .addRequiredClusters();
285
+ ep.addChildDeviceType('Button3', getSupportedDeviceType('GenericSwitch'), {
286
+ number: EndpointNumber(6_06_3),
287
+ tagList: [getSemtag(SwitchesTag.Up), getSemtag(CommonNumberTag.Three)],
288
+ })
289
+ .createDefaultMomentarySwitchClusterServer()
290
+ .addRequiredClusters();
291
+ ep.addChildDeviceType('Button4', getSupportedDeviceType('GenericSwitch'), {
292
+ number: EndpointNumber(6_06_4),
293
+ tagList: [getSemtag(SwitchesTag.Down), getSemtag(CommonNumberTag.Four)],
294
+ })
295
+ .createDefaultMomentarySwitchClusterServer()
296
+ .addRequiredClusters();
272
297
  await registerDevice(ep, 'Generic Switch', 'SWITCH-06-06');
273
298
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('ContactSensor'), bridgedNode, powerSource], { id: 'ContactSensor', number: EndpointNumber(7_01) });
274
299
  ep.createDefaultPowerSourceBatteryClusterServer();
@@ -1,4 +1,4 @@
1
- import type { MaybePromise } from '@matter/general';
1
+ import { type MaybePromise, type Timer } from '@matter/general';
2
2
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
3
3
  import { type EndpointNumber } from '@matter/types';
4
4
  import { ClosureControl } from '@matter/types/clusters/closure-control';
@@ -18,24 +18,28 @@ declare const MatterbridgeClosureControlServerBase: import("@matter/node").Clust
18
18
  ventilation: false;
19
19
  }>, import("@matter/types").ClusterType.Concrete, new () => {}, "closureControl">;
20
20
  export declare class MatterbridgeClosureControlServer extends MatterbridgeClosureControlServerBase {
21
+ #private;
21
22
  readonly state: MatterbridgeClosureControlServer.State;
22
23
  protected internal: MatterbridgeClosureControlServer.Internal;
23
24
  initialize(): MaybePromise;
24
25
  moveTo: (request: ClosureControl.MoveToRequest) => Promise<void>;
25
- private completeMoveTo;
26
26
  stop: () => Promise<void>;
27
27
  calibrate: () => Promise<void>;
28
- private completeCalibrate;
28
+ [Symbol.asyncDispose](): Promise<void>;
29
29
  }
30
30
  export declare namespace MatterbridgeClosureControlServer {
31
31
  class Internal extends MatterbridgeClosureControlServerBase.Internal {
32
- movementTimer?: NodeJS.Timeout;
33
- calibrateTimer?: NodeJS.Timeout;
32
+ movementTimer?: Timer;
33
+ movementTargetState: ClosureControl.OverallTargetState;
34
+ movementPreviousState: ClosureControl.OverallCurrentState;
35
+ calibrateTimer?: Timer;
34
36
  }
35
37
  class State extends MatterbridgeClosureControlServerBase.State {
36
38
  movementDuration: number;
37
39
  calibrationDuration: number;
38
40
  signaturePosition: number;
41
+ ventilationPosition: number;
42
+ pedestrianPosition: number;
39
43
  }
40
44
  }
41
45
  export interface ClosureOptions {
@@ -51,6 +55,8 @@ export interface ClosureOptions {
51
55
  movementDuration?: number;
52
56
  calibrationDuration?: number;
53
57
  signaturePosition?: number;
58
+ ventilationPosition?: number;
59
+ pedestrianPosition?: number;
54
60
  motionLatching?: boolean;
55
61
  speed?: boolean;
56
62
  ventilation?: boolean;
@@ -64,10 +70,17 @@ export declare class Closure extends MatterbridgeEndpoint {
64
70
  constructor(name: string, serial: string, options?: ClosureOptions);
65
71
  getMainState(): ClosureControl.MainState | undefined;
66
72
  getSignaturePosition(): number | undefined;
73
+ getVentilationPosition(): number | undefined;
74
+ getPedestrianPosition(): number | undefined;
67
75
  setState(currentState: ClosureControl.OverallCurrentState, targetState: ClosureControl.OverallTargetState, mainState?: ClosureControl.MainState, countdownTime?: number, currentErrorList?: ClosureControl.ClosureError[]): Promise<void>;
68
76
  setFullyClosed(): Promise<void>;
69
77
  setFullOpened(): Promise<void>;
70
78
  setPartiallyOpened(): Promise<void>;
79
+ setOpenedAtSignature(): Promise<void>;
80
+ setOpenedForVentilation(): Promise<void>;
81
+ setOpenedForPedestrian(): Promise<void>;
82
+ targetPositionToCurrentPosition(position: ClosureControl.TargetPosition): ClosureControl.CurrentPosition;
83
+ targetPositionToTargetPercent(position: ClosureControl.TargetPosition): number;
71
84
  triggerOperationalError(errorState?: ClosureControl.ClosureError[]): Promise<void>;
72
85
  triggerMovementCompleted(): Promise<void>;
73
86
  triggerSecureStateChanged(secureValue: boolean): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { Millis, Time } from '@matter/general';
1
2
  import { ClosureTag } from '@matter/node';
2
3
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
3
4
  import { StatusResponse } from '@matter/types';
@@ -73,25 +74,24 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
73
74
  (!this.features.motionLatching || nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
74
75
  (!this.features.speed || nextTarget.speed === currentState.speed);
75
76
  this.state.mainState = isAtTarget ? ClosureControl.MainState.Stopped : ClosureControl.MainState.Moving;
76
- clearTimeout(this.internal.movementTimer);
77
+ this.internal.movementTimer?.stop();
78
+ this.internal.movementTimer = undefined;
77
79
  if (isAtTarget) {
78
- this.internal.movementTimer = undefined;
79
80
  this.state.countdownTime = 0;
80
81
  }
81
82
  else if (currentState === null || this.state.movementDuration <= 0) {
82
- this.internal.movementTimer = undefined;
83
83
  }
84
84
  else {
85
85
  this.state.countdownTime = this.state.movementDuration / 1000;
86
- const previousState = currentState;
87
- this.internal.movementTimer = setTimeout(() => {
88
- this.internal.movementTimer = undefined;
89
- void this.completeMoveTo(nextTarget, previousState);
90
- }, this.state.movementDuration);
86
+ this.internal.movementTargetState = nextTarget;
87
+ this.internal.movementPreviousState = currentState;
88
+ this.internal.movementTimer = Time.getTimer('ClosureControl movement complete', Millis(this.state.movementDuration), this.callback(this.#completeMoveTo, { lock: true })).start();
91
89
  }
92
90
  };
93
- completeMoveTo = async (targetState, previousState) => {
94
- const closure = this.endpoint;
91
+ #completeMoveTo() {
92
+ this.internal.movementTimer = undefined;
93
+ const targetState = this.internal.movementTargetState;
94
+ const previousState = this.internal.movementPreviousState;
95
95
  let position = previousState.position;
96
96
  if (targetState.position !== undefined && targetState.position !== null) {
97
97
  const mappedPosition = targetToCurrentPosition[targetState.position];
@@ -100,17 +100,25 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
100
100
  }
101
101
  const latch = targetState.latch ?? previousState.latch;
102
102
  const secureState = this.features.motionLatching ? latch === true : position === ClosureControl.CurrentPosition.FullyClosed;
103
- await closure.setState({
103
+ this.state.countdownTime = 0;
104
+ this.state.mainState = ClosureControl.MainState.Stopped;
105
+ this.state.currentErrorList = [];
106
+ this.state.overallCurrentState = {
104
107
  position,
105
108
  ...(this.features.motionLatching ? { latch } : null),
106
109
  ...(this.features.speed ? { speed: targetState.speed } : null),
107
110
  secureState,
108
- }, targetState, ClosureControl.MainState.Stopped, 0);
111
+ };
112
+ this.state.overallTargetState = {
113
+ position: targetState.position,
114
+ ...(this.features.motionLatching ? { latch: targetState.latch } : null),
115
+ ...(this.features.speed ? { speed: targetState.speed } : null),
116
+ };
109
117
  if (secureState !== previousState.secureState) {
110
- await closure.triggerSecureStateChanged(secureState);
118
+ this.events.secureStateChanged.emit({ secureValue: secureState }, this.context);
111
119
  }
112
- await closure.triggerMovementCompleted();
113
- };
120
+ this.events.movementCompleted.emit(undefined, this.context);
121
+ }
114
122
  stop = async () => {
115
123
  const device = this.endpoint.stateOf(MatterbridgeServer);
116
124
  device.log.info(`Stop (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
@@ -121,9 +129,9 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
121
129
  attributes: this.state,
122
130
  endpoint: this.endpoint,
123
131
  });
124
- clearTimeout(this.internal.movementTimer);
132
+ this.internal.movementTimer?.stop();
125
133
  this.internal.movementTimer = undefined;
126
- clearTimeout(this.internal.calibrateTimer);
134
+ this.internal.calibrateTimer?.stop();
127
135
  this.internal.calibrateTimer = undefined;
128
136
  if ([ClosureControl.MainState.Moving, ClosureControl.MainState.WaitingForMotion, ClosureControl.MainState.Calibrating].includes(this.state.mainState)) {
129
137
  this.state.mainState = ClosureControl.MainState.Stopped;
@@ -147,27 +155,33 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
147
155
  throw new StatusResponse.InvalidInStateError('ClosureControl.calibrate is only allowed while Stopped or SetupRequired');
148
156
  }
149
157
  this.state.mainState = ClosureControl.MainState.Calibrating;
150
- clearTimeout(this.internal.calibrateTimer);
158
+ this.internal.calibrateTimer?.stop();
151
159
  if (this.state.calibrationDuration <= 0) {
152
160
  this.internal.calibrateTimer = undefined;
153
161
  }
154
162
  else {
155
163
  this.state.countdownTime = this.state.calibrationDuration / 1000;
156
- this.internal.calibrateTimer = setTimeout(() => {
157
- this.internal.calibrateTimer = undefined;
158
- void this.completeCalibrate();
159
- }, this.state.calibrationDuration);
164
+ this.internal.calibrateTimer = Time.getTimer('ClosureControl calibrate complete', Millis(this.state.calibrationDuration), this.callback(this.#completeCalibrate, { lock: true })).start();
160
165
  }
161
166
  };
162
- completeCalibrate = async () => {
163
- const closure = this.endpoint;
164
- await closure.setAttribute(ClosureControl, 'countdownTime', 0);
165
- await closure.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Stopped);
166
- };
167
+ #completeCalibrate() {
168
+ this.internal.calibrateTimer = undefined;
169
+ this.state.countdownTime = 0;
170
+ this.state.mainState = ClosureControl.MainState.Stopped;
171
+ }
172
+ async [Symbol.asyncDispose]() {
173
+ this.internal.movementTimer?.stop();
174
+ this.internal.movementTimer = undefined;
175
+ this.internal.calibrateTimer?.stop();
176
+ this.internal.calibrateTimer = undefined;
177
+ await super[Symbol.asyncDispose]?.();
178
+ }
167
179
  }
168
180
  (function (MatterbridgeClosureControlServer) {
169
181
  class Internal extends MatterbridgeClosureControlServerBase.Internal {
170
182
  movementTimer;
183
+ movementTargetState = {};
184
+ movementPreviousState = { position: ClosureControl.CurrentPosition.FullyClosed, secureState: true };
171
185
  calibrateTimer;
172
186
  }
173
187
  MatterbridgeClosureControlServer.Internal = Internal;
@@ -175,12 +189,14 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
175
189
  movementDuration = 0;
176
190
  calibrationDuration = 0;
177
191
  signaturePosition = 50_00;
192
+ ventilationPosition = 50_00;
193
+ pedestrianPosition = 50_00;
178
194
  }
179
195
  MatterbridgeClosureControlServer.State = State;
180
196
  })(MatterbridgeClosureControlServer || (MatterbridgeClosureControlServer = {}));
181
197
  export class Closure extends MatterbridgeEndpoint {
182
198
  constructor(name, serial, 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;
199
+ 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, ventilationPosition = 50_00, pedestrianPosition = 50_00, motionLatching = false, speed = false, ventilation = false, pedestrian = false, calibration = false, id, number, tagList = [getSemtag(ClosureTag.Covering)], } = options;
184
200
  super(powerSourceType === 'None' ? [closure] : [closure, powerSource], {
185
201
  id: id ?? `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
186
202
  number,
@@ -223,6 +239,8 @@ export class Closure extends MatterbridgeEndpoint {
223
239
  movementDuration,
224
240
  calibrationDuration,
225
241
  signaturePosition,
242
+ ventilationPosition,
243
+ pedestrianPosition,
226
244
  };
227
245
  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);
228
246
  }
@@ -232,23 +250,31 @@ export class Closure extends MatterbridgeEndpoint {
232
250
  getSignaturePosition() {
233
251
  return this.getAttribute(MatterbridgeClosureControlServer, 'signaturePosition');
234
252
  }
253
+ getVentilationPosition() {
254
+ return this.getAttribute(MatterbridgeClosureControlServer, 'ventilationPosition');
255
+ }
256
+ getPedestrianPosition() {
257
+ return this.getAttribute(MatterbridgeClosureControlServer, 'pedestrianPosition');
258
+ }
235
259
  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;
260
+ const features = this.featuresOf(MatterbridgeClosureControlServer.id);
261
+ const supportsCurrentPosition = (currentState.position !== ClosureControl.CurrentPosition.OpenedForVentilation || features.ventilation) &&
262
+ (currentState.position !== ClosureControl.CurrentPosition.OpenedForPedestrian || features.pedestrian);
263
+ const supportsTargetPosition = (targetState.position !== ClosureControl.TargetPosition.MoveToVentilationPosition || features.ventilation) &&
264
+ (targetState.position !== ClosureControl.TargetPosition.MoveToPedestrianPosition || features.pedestrian);
239
265
  await this.setAttribute(ClosureControl, 'countdownTime', countdownTime);
240
266
  await this.setAttribute(ClosureControl, 'mainState', mainState);
241
267
  await this.setAttribute(ClosureControl, 'currentErrorList', currentErrorList);
242
268
  await this.setAttribute(ClosureControl, 'overallCurrentState', {
243
- position: currentState.position,
244
- ...(supportsMotionLatching ? { latch: currentState.latch } : null),
245
- ...(supportsSpeed ? { speed: currentState.speed } : null),
269
+ ...(supportsCurrentPosition ? { position: currentState.position } : null),
270
+ ...(features.motionLatching ? { latch: currentState.latch } : null),
271
+ ...(features.speed ? { speed: currentState.speed } : null),
246
272
  secureState: currentState.secureState,
247
273
  });
248
274
  await this.setAttribute(ClosureControl, 'overallTargetState', {
249
- position: targetState.position,
250
- ...(supportsMotionLatching ? { latch: targetState.latch } : null),
251
- ...(supportsSpeed ? { speed: targetState.speed } : null),
275
+ ...(supportsTargetPosition ? { position: targetState.position } : null),
276
+ ...(features.motionLatching ? { latch: targetState.latch } : null),
277
+ ...(features.speed ? { speed: targetState.speed } : null),
252
278
  });
253
279
  }
254
280
  async setFullyClosed() {
@@ -287,6 +313,64 @@ export class Closure extends MatterbridgeEndpoint {
287
313
  speed: ThreeLevelAuto.Auto,
288
314
  });
289
315
  }
316
+ async setOpenedAtSignature() {
317
+ await this.setState({
318
+ position: ClosureControl.CurrentPosition.OpenedAtSignature,
319
+ latch: false,
320
+ speed: ThreeLevelAuto.Auto,
321
+ secureState: false,
322
+ }, {
323
+ position: ClosureControl.TargetPosition.MoveToSignaturePosition,
324
+ latch: false,
325
+ speed: ThreeLevelAuto.Auto,
326
+ });
327
+ }
328
+ async setOpenedForVentilation() {
329
+ await this.setState({
330
+ position: ClosureControl.CurrentPosition.OpenedForVentilation,
331
+ latch: false,
332
+ speed: ThreeLevelAuto.Auto,
333
+ secureState: false,
334
+ }, {
335
+ position: ClosureControl.TargetPosition.MoveToVentilationPosition,
336
+ latch: false,
337
+ speed: ThreeLevelAuto.Auto,
338
+ });
339
+ }
340
+ async setOpenedForPedestrian() {
341
+ await this.setState({
342
+ position: ClosureControl.CurrentPosition.OpenedForPedestrian,
343
+ latch: false,
344
+ speed: ThreeLevelAuto.Auto,
345
+ secureState: false,
346
+ }, {
347
+ position: ClosureControl.TargetPosition.MoveToPedestrianPosition,
348
+ latch: false,
349
+ speed: ThreeLevelAuto.Auto,
350
+ });
351
+ }
352
+ targetPositionToCurrentPosition(position) {
353
+ if (position === ClosureControl.TargetPosition.MoveToFullyClosed)
354
+ return ClosureControl.CurrentPosition.FullyClosed;
355
+ if (position === ClosureControl.TargetPosition.MoveToFullyOpen)
356
+ return ClosureControl.CurrentPosition.FullyOpened;
357
+ if (position === ClosureControl.TargetPosition.MoveToPedestrianPosition)
358
+ return ClosureControl.CurrentPosition.OpenedForPedestrian;
359
+ if (position === ClosureControl.TargetPosition.MoveToVentilationPosition)
360
+ return ClosureControl.CurrentPosition.OpenedForVentilation;
361
+ return ClosureControl.CurrentPosition.OpenedAtSignature;
362
+ }
363
+ targetPositionToTargetPercent(position) {
364
+ if (position === ClosureControl.TargetPosition.MoveToFullyOpen)
365
+ return 0;
366
+ if (position === ClosureControl.TargetPosition.MoveToFullyClosed)
367
+ return 100_00;
368
+ if (position === ClosureControl.TargetPosition.MoveToPedestrianPosition)
369
+ return this.getPedestrianPosition() ?? 50_00;
370
+ if (position === ClosureControl.TargetPosition.MoveToVentilationPosition)
371
+ return this.getVentilationPosition() ?? 50_00;
372
+ return this.getSignaturePosition() ?? 50_00;
373
+ }
290
374
  async triggerOperationalError(errorState = []) {
291
375
  await this.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Error);
292
376
  await this.setAttribute(ClosureControl, 'currentErrorList', errorState);
@@ -1,4 +1,4 @@
1
- import type { MaybePromise } from '@matter/general';
1
+ import { type MaybePromise, type Timer } from '@matter/general';
2
2
  import { ClosureDimensionServer } from '@matter/node/behaviors/closure-dimension';
3
3
  import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
4
4
  import type { EndpointNumber } from '@matter/types/datatype';
@@ -15,17 +15,19 @@ declare const MatterbridgeClosureDimensionServerBase: import("@matter/node").Clu
15
15
  unit: false;
16
16
  }>, import("@matter/types").ClusterType.Concrete, new () => {}, "closureDimension">;
17
17
  export declare class MatterbridgeClosureDimensionServer extends MatterbridgeClosureDimensionServerBase {
18
+ #private;
18
19
  readonly state: MatterbridgeClosureDimensionServer.State;
19
20
  protected internal: MatterbridgeClosureDimensionServer.Internal;
20
21
  initialize(): MaybePromise;
21
22
  setTarget: (request: ClosureDimension.SetTargetRequest) => Promise<void>;
22
23
  step: (request: ClosureDimension.StepRequest) => Promise<void>;
23
- private scheduleMovement;
24
- private completeMovement;
24
+ [Symbol.asyncDispose](): Promise<void>;
25
25
  }
26
26
  export declare namespace MatterbridgeClosureDimensionServer {
27
27
  class Internal extends MatterbridgeClosureDimensionServerBase.Internal {
28
- movementTimer?: NodeJS.Timeout;
28
+ movementTimer?: Timer;
29
+ movementTargetState: ClosureDimension.DimensionState;
30
+ movementPreviousState: ClosureDimension.DimensionState;
29
31
  }
30
32
  class State extends MatterbridgeClosureDimensionServerBase.State {
31
33
  movementDuration: number;
@@ -1,3 +1,4 @@
1
+ import { Millis, Time } from '@matter/general';
1
2
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
2
3
  import { ClosureDimensionServer } from '@matter/node/behaviors/closure-dimension';
3
4
  import { StatusResponse } from '@matter/types';
@@ -71,7 +72,7 @@ export class MatterbridgeClosureDimensionServer extends MatterbridgeClosureDimen
71
72
  if (matchesCurrentState)
72
73
  return;
73
74
  this.state.targetState = nextTarget;
74
- this.scheduleMovement(nextTarget, currentState);
75
+ this.#scheduleMovement(nextTarget, currentState);
75
76
  };
76
77
  step = async (request) => {
77
78
  const device = this.endpoint.stateOf(MatterbridgeServer);
@@ -121,35 +122,41 @@ export class MatterbridgeClosureDimensionServer extends MatterbridgeClosureDimen
121
122
  ...(this.features.speed && request.speed !== undefined ? { speed: request.speed } : null),
122
123
  };
123
124
  this.state.targetState = nextTarget;
124
- this.scheduleMovement(nextTarget, currentState);
125
+ this.#scheduleMovement(nextTarget, currentState);
125
126
  };
126
- scheduleMovement(targetState, currentState) {
127
- clearTimeout(this.internal.movementTimer);
128
- if (currentState === null || this.state.movementDuration <= 0) {
129
- this.internal.movementTimer = undefined;
127
+ #scheduleMovement(targetState, currentState) {
128
+ this.internal.movementTimer?.stop();
129
+ this.internal.movementTimer = undefined;
130
+ if (currentState === null || this.state.movementDuration <= 0)
130
131
  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);
132
+ this.internal.movementTargetState = targetState;
133
+ this.internal.movementPreviousState = currentState;
134
+ this.internal.movementTimer = Time.getTimer('ClosureDimension movement complete', Millis(this.state.movementDuration), this.callback(this.#completeMovement, { lock: true })).start();
137
135
  }
138
- completeMovement = async (targetState, previousState) => {
139
- const endpoint = this.endpoint;
136
+ #completeMovement() {
137
+ this.internal.movementTimer = undefined;
138
+ const targetState = this.internal.movementTargetState;
139
+ const previousState = this.internal.movementPreviousState;
140
140
  const position = targetState.position ?? previousState.position;
141
141
  const latch = targetState.latch ?? previousState.latch;
142
142
  const speed = targetState.speed ?? previousState.speed;
143
- await endpoint.setAttribute(ClosureDimensionServer, 'currentState', {
143
+ this.state.currentState = {
144
144
  position,
145
145
  ...(this.features.motionLatching ? { latch } : null),
146
146
  ...(this.features.speed ? { speed } : null),
147
- });
148
- };
147
+ };
148
+ }
149
+ async [Symbol.asyncDispose]() {
150
+ this.internal.movementTimer?.stop();
151
+ this.internal.movementTimer = undefined;
152
+ await super[Symbol.asyncDispose]?.();
153
+ }
149
154
  }
150
155
  (function (MatterbridgeClosureDimensionServer) {
151
156
  class Internal extends MatterbridgeClosureDimensionServerBase.Internal {
152
157
  movementTimer;
158
+ movementTargetState = {};
159
+ movementPreviousState = {};
153
160
  }
154
161
  MatterbridgeClosureDimensionServer.Internal = Internal;
155
162
  class State extends MatterbridgeClosureDimensionServerBase.State {
@@ -62,6 +62,21 @@ export interface ElectricalEnergyTariffOptions {
62
62
  currentPrice?: CommodityPrice.CommodityPriceStruct | null;
63
63
  localGenerationAvailable?: boolean | null;
64
64
  currentConditions?: ElectricalGridConditions.ElectricalGridConditionsStruct | null;
65
+ startDate?: number | null;
66
+ dayEntries?: CommodityTariff.DayEntry[] | null;
67
+ dayPatterns?: CommodityTariff.DayPattern[] | null;
68
+ calendarPeriods?: CommodityTariff.CalendarPeriod[] | null;
69
+ individualDays?: CommodityTariff.Day[] | null;
70
+ currentDay?: CommodityTariff.Day | null;
71
+ nextDay?: CommodityTariff.Day | null;
72
+ currentDayEntry?: CommodityTariff.DayEntry | null;
73
+ currentDayEntryDate?: number | null;
74
+ nextDayEntry?: CommodityTariff.DayEntry | null;
75
+ nextDayEntryDate?: number | null;
76
+ tariffComponents?: CommodityTariff.TariffComponent[] | null;
77
+ tariffPeriods?: CommodityTariff.TariffPeriod[] | null;
78
+ currentTariffComponents?: CommodityTariff.TariffComponent[] | null;
79
+ nextTariffComponents?: CommodityTariff.TariffComponent[] | null;
65
80
  tagList?: Semtag[];
66
81
  }
67
82
  export declare class ElectricalUtilityMeter extends MatterbridgeEndpoint {
@@ -106,7 +106,7 @@ export class ElectricalUtilityMeter extends MatterbridgeEndpoint {
106
106
  return tariff;
107
107
  }
108
108
  configureElectricalEnergyTariffClusters(endpoint, options) {
109
- const { tariffLabel = null, providerName = null, tariffUnit = TariffUnit.KWh, currency = null, currentPrice = null, localGenerationAvailable = null, currentConditions = null, } = options;
109
+ const { tariffLabel = null, providerName = null, tariffUnit = TariffUnit.KWh, currency = null, currentPrice = null, localGenerationAvailable = null, currentConditions = null, startDate = null, dayEntries = null, dayPatterns = null, calendarPeriods = null, individualDays = null, currentDay = null, nextDay = null, currentDayEntry = null, currentDayEntryDate = null, nextDayEntry = null, nextDayEntryDate = null, tariffComponents = null, tariffPeriods = null, currentTariffComponents = null, nextTariffComponents = null, } = options;
110
110
  endpoint.behaviors.require(MatterbridgeCommodityPriceServer, {
111
111
  tariffUnit,
112
112
  currency,
@@ -125,21 +125,21 @@ export class ElectricalUtilityMeter extends MatterbridgeEndpoint {
125
125
  endpoint.behaviors.require(MatterbridgeCommodityTariffServer, {
126
126
  tariffInfo,
127
127
  tariffUnit: tariffInfo === null ? null : tariffUnit,
128
- startDate: null,
129
- dayEntries: null,
130
- dayPatterns: null,
131
- calendarPeriods: null,
132
- individualDays: null,
133
- tariffComponents: null,
134
- tariffPeriods: null,
135
- currentDay: null,
136
- nextDay: null,
137
- currentDayEntry: null,
138
- currentDayEntryDate: null,
139
- nextDayEntry: null,
140
- nextDayEntryDate: null,
141
- currentTariffComponents: null,
142
- nextTariffComponents: null,
128
+ startDate: tariffInfo === null ? null : startDate,
129
+ dayEntries: tariffInfo === null ? null : dayEntries,
130
+ dayPatterns: tariffInfo === null ? null : dayPatterns,
131
+ calendarPeriods: tariffInfo === null ? null : calendarPeriods,
132
+ individualDays: tariffInfo === null ? null : individualDays,
133
+ tariffComponents: tariffInfo === null ? null : tariffComponents,
134
+ tariffPeriods: tariffInfo === null ? null : tariffPeriods,
135
+ currentDay: tariffInfo === null ? null : currentDay,
136
+ nextDay: tariffInfo === null ? null : nextDay,
137
+ currentDayEntry: tariffInfo === null ? null : currentDayEntry,
138
+ currentDayEntryDate: tariffInfo === null ? null : currentDayEntryDate,
139
+ nextDayEntry: tariffInfo === null ? null : nextDayEntry,
140
+ nextDayEntryDate: tariffInfo === null ? null : nextDayEntryDate,
141
+ currentTariffComponents: tariffInfo === null ? null : currentTariffComponents,
142
+ nextTariffComponents: tariffInfo === null ? null : nextTariffComponents,
143
143
  });
144
144
  endpoint.behaviors.require(ElectricalGridConditionsServer, { localGenerationAvailable, currentConditions });
145
145
  }
@@ -208,7 +208,7 @@ export function featuresFor(endpoint, cluster) {
208
208
  const supportedBehavior = endpoint.behaviors.supported[lowercaseFirstLetter(behaviorId)];
209
209
  if (!supportedBehavior || !ClusterBehavior.isType(supportedBehavior))
210
210
  return {};
211
- return supportedBehavior.features ?? {};
211
+ return supportedBehavior.features;
212
212
  }
213
213
  export async function internalFor(endpoint, cluster) {
214
214
  const behaviorId = getBehavior(endpoint, cluster)?.id;
@@ -9,4 +9,5 @@ export declare function snakeCase(name: string): string;
9
9
  export declare function pascalCase(name: string): string;
10
10
  export declare function camelCase(name: string): string;
11
11
  export declare function getServerBehaviorFromClusterId(clusterId: ClusterId, features?: Record<string, boolean> | string[]): Promise<Behavior.Type | undefined>;
12
+ export declare function getClientBehaviorFromClusterId(clusterId: ClusterId): Promise<Behavior.Type | undefined>;
12
13
  export declare function createClusterServer(endpoint: MatterbridgeEndpoint, clusterId: ClusterId, options?: CreateClusterServerOptions): Promise<MatterbridgeEndpoint>;
@@ -1,4 +1,4 @@
1
- import { ClusterBehavior } from '@matter/node';
1
+ import { ClusterBehavior, isClientBehavior } from '@matter/node';
2
2
  import { getClusterNameById } from '@matter/types/cluster';
3
3
  import { logModuleLoaded } from '@matterbridge/utils/loader';
4
4
  import { db, hk } from 'node-ansi-logger';
@@ -44,7 +44,7 @@ export async function getServerBehaviorFromClusterId(clusterId, features) {
44
44
  return undefined;
45
45
  }
46
46
  const base = mod[`${name}Server`];
47
- if (!base || !ClusterBehavior.isType(base))
47
+ if (!base || !ClusterBehavior.isType(base) || isClientBehavior(base))
48
48
  return undefined;
49
49
  const featureNames = (Array.isArray(features)
50
50
  ? features
@@ -53,6 +53,22 @@ export async function getServerBehaviorFromClusterId(clusterId, features) {
53
53
  .map(([key]) => key)).map((key) => pascalCase(key));
54
54
  return featureNames.length > 0 ? base.with(...featureNames) : base;
55
55
  }
56
+ export async function getClientBehaviorFromClusterId(clusterId) {
57
+ const name = getClusterNameById(clusterId);
58
+ if (name.includes('Unknown cluster'))
59
+ return undefined;
60
+ let mod;
61
+ try {
62
+ mod = (await import(`@matter/node/behaviors/${snakeCase(name)}`));
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ const base = mod[`${name}Client`];
68
+ if (!base || !ClusterBehavior.isType(base) || !isClientBehavior(base))
69
+ return undefined;
70
+ return base;
71
+ }
56
72
  export async function createClusterServer(endpoint, clusterId, options) {
57
73
  const type = await getServerBehaviorFromClusterId(clusterId, options?.features);
58
74
  if (!type) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.8-dev-20260901-c4963a1",
3
+ "version": "3.10.8-dev-20260902-361500d",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -132,10 +132,10 @@
132
132
  },
133
133
  "dependencies": {
134
134
  "@matter/main": "0.17.9",
135
- "@matterbridge/dgram": "3.10.8-dev-20260901-c4963a1",
136
- "@matterbridge/thread": "3.10.8-dev-20260901-c4963a1",
137
- "@matterbridge/types": "3.10.8-dev-20260901-c4963a1",
138
- "@matterbridge/utils": "3.10.8-dev-20260901-c4963a1",
135
+ "@matterbridge/dgram": "3.10.8-dev-20260902-361500d",
136
+ "@matterbridge/thread": "3.10.8-dev-20260902-361500d",
137
+ "@matterbridge/types": "3.10.8-dev-20260902-361500d",
138
+ "@matterbridge/utils": "3.10.8-dev-20260902-361500d",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.7.0",