@matterbridge/core 3.10.8-dev-20260831-e73fc65 → 3.10.8-dev-20260901-971f7bf

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.
@@ -122,11 +122,10 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.with(DoorLock.Fea
122
122
  context: this.context,
123
123
  });
124
124
  device.log.debug(`MatterbridgeDoorLockServer: setCredential called for userIndex ${request.userIndex} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
125
- const auth = this.auth;
126
- if (auth.isDuplicateCredential(request.credential.credentialType, request.credentialData, request.credential.credentialIndex)) {
125
+ if (this.auth.isDuplicateCredential(request.credential.credentialType, request.credentialData, request.credential.credentialIndex)) {
127
126
  throw new DoorLock.DuplicateError(`MatterbridgeDoorLockServer: credential data duplicates another credential of the same type (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
128
127
  }
129
- if (request.operationType === DoorLock.DataOperationType.Add && auth.findCredential(request.credential.credentialType, request.credential.credentialIndex)) {
128
+ if (request.operationType === DoorLock.DataOperationType.Add && this.auth.findCredential(request.credential.credentialType, request.credential.credentialIndex)) {
130
129
  throw new DoorLock.OccupiedError(`MatterbridgeDoorLockServer: add operation targets an occupied credential index (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
131
130
  }
132
131
  return await super.setCredential(request);
@@ -1,6 +1,6 @@
1
1
  import { Bytes } from '@matter/general';
2
2
  import { ThermostatServer } from '@matter/node/behaviors/thermostat';
3
- import { StatusResponse } from '@matter/types';
3
+ import { Status, StatusResponseError } from '@matter/types';
4
4
  import { Thermostat } from '@matter/types/clusters/thermostat';
5
5
  import { MatterbridgeServer } from './matterbridgeServer.js';
6
6
  export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermostat.Feature.Cooling, Thermostat.Feature.Heating, Thermostat.Feature.AutoMode, Thermostat.Feature.Presets, Thermostat.Feature.MatterScheduleConfiguration, Thermostat.Feature.ThermostatSuggestions) {
@@ -30,18 +30,18 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
30
30
  return;
31
31
  const device = this.endpoint.stateOf(MatterbridgeServer);
32
32
  if (remainingSuggestions.length !== currentSuggestions.length) {
33
- device.log.info(`Removing ${currentSuggestions.length - remainingSuggestions.length} thermostat suggestion(s) referencing removed preset(s) (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
33
+ device.log.info(`MatterbridgeThermostatServer: removing ${currentSuggestions.length - remainingSuggestions.length} thermostat suggestion(s) referencing removed preset(s) (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
34
34
  this.state.thermostatSuggestions = remainingSuggestions;
35
35
  }
36
36
  if (clearCurrentSuggestion) {
37
- device.log.info(`Clearing current thermostat suggestion referencing a removed preset (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
37
+ device.log.info(`MatterbridgeThermostatServer: clearing current thermostat suggestion referencing a removed preset (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
38
38
  this.state.currentThermostatSuggestion = null;
39
39
  this.state.thermostatSuggestionNotFollowingReason = null;
40
40
  }
41
41
  }
42
42
  async setpointRaiseLower(request) {
43
43
  const device = this.endpoint.stateOf(MatterbridgeServer);
44
- device.log.info(`Setting setpoint by ${request.amount} in mode ${request.mode} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
44
+ device.log.info(`MatterbridgeThermostatServer: setting setpoint by ${request.amount} in mode ${request.mode} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
45
45
  await device.commandHandler.executeHandler('Thermostat.setpointRaiseLower', {
46
46
  command: 'setpointRaiseLower',
47
47
  request,
@@ -51,13 +51,13 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
51
51
  context: this.context,
52
52
  });
53
53
  const lookupSetpointAdjustMode = ['Heat', 'Cool', 'Both'];
54
- device.log.debug(`MatterbridgeThermostatServer: setpointRaiseLower called with mode: ${lookupSetpointAdjustMode[request.mode]} amount: ${request.amount / 10}`);
54
+ device.log.debug(`MatterbridgeThermostatServer: setpointRaiseLower called with mode: ${lookupSetpointAdjustMode[request.mode]} amount: ${request.amount / 10} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
55
55
  await super.setpointRaiseLower(request);
56
56
  }
57
57
  async setActivePresetRequest(request) {
58
58
  const device = this.endpoint.stateOf(MatterbridgeServer);
59
59
  const presetHandle = request.presetHandle ? `0x${Buffer.from(request.presetHandle).toString('hex')}` : 'null';
60
- device.log.info(`Setting preset to ${presetHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
60
+ device.log.info(`MatterbridgeThermostatServer: setting preset to ${presetHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
61
61
  await device.commandHandler.executeHandler('Thermostat.setActivePresetRequest', {
62
62
  command: 'setActivePresetRequest',
63
63
  request,
@@ -66,15 +66,15 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
66
66
  endpoint: this.endpoint,
67
67
  context: this.context,
68
68
  });
69
- device.log.debug(`MatterbridgeThermostatServer: setActivePresetRequest called with presetHandle: ${presetHandle}`);
69
+ device.log.debug(`MatterbridgeThermostatServer: setActivePresetRequest called with presetHandle: ${presetHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
70
70
  await super.setActivePresetRequest(request);
71
71
  const activePresetHandle = this.state.activePresetHandle ? `0x${Buffer.from(this.state.activePresetHandle).toString('hex')}` : 'null';
72
- device.log.debug(`MatterbridgeThermostatServer: setActivePresetRequest completed with activePresetHandle: ${activePresetHandle} occupiedHeatingSetpoint: ${this.state.occupiedHeatingSetpoint} occupiedCoolingSetpoint: ${this.state.occupiedCoolingSetpoint}`);
72
+ device.log.debug(`MatterbridgeThermostatServer: setActivePresetRequest completed with activePresetHandle: ${activePresetHandle} occupiedHeatingSetpoint: ${this.state.occupiedHeatingSetpoint} occupiedCoolingSetpoint: ${this.state.occupiedCoolingSetpoint} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
73
73
  }
74
74
  async setActiveScheduleRequest(request) {
75
75
  const device = this.endpoint.stateOf(MatterbridgeServer);
76
76
  const scheduleHandle = `0x${Buffer.from(request.scheduleHandle).toString('hex')}`;
77
- device.log.info(`Setting schedule to ${scheduleHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
77
+ device.log.info(`MatterbridgeThermostatServer: setting schedule to ${scheduleHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
78
78
  await device.commandHandler.executeHandler('Thermostat.setActiveScheduleRequest', {
79
79
  command: 'setActiveScheduleRequest',
80
80
  request,
@@ -85,10 +85,10 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
85
85
  });
86
86
  const schedule = this.state.schedules.find((s) => s.scheduleHandle !== null && Bytes.areEqual(s.scheduleHandle, request.scheduleHandle));
87
87
  if (schedule === undefined) {
88
- throw new StatusResponse.InvalidCommandError('Requested ScheduleHandle not found');
88
+ throw new StatusResponseError(`MatterbridgeThermostatServer: requested ScheduleHandle not found (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidCommand);
89
89
  }
90
90
  this.state.activeScheduleHandle = Uint8Array.from(request.scheduleHandle);
91
- device.log.debug(`MatterbridgeThermostatServer: setActiveScheduleRequest completed with activeScheduleHandle: ${scheduleHandle}`);
91
+ device.log.debug(`MatterbridgeThermostatServer: setActiveScheduleRequest completed with activeScheduleHandle: ${scheduleHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
92
92
  }
93
93
  removeExpiredThermostatSuggestions() {
94
94
  const now = Math.floor(Date.now() / 1000);
@@ -112,7 +112,7 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
112
112
  async addThermostatSuggestion(request) {
113
113
  const device = this.endpoint.stateOf(MatterbridgeServer);
114
114
  const presetHandle = `0x${Buffer.from(request.presetHandle).toString('hex')}`;
115
- device.log.info(`Adding thermostat suggestion for preset ${presetHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
115
+ device.log.info(`MatterbridgeThermostatServer: adding thermostat suggestion for preset ${presetHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
116
116
  await device.commandHandler.executeHandler('Thermostat.addThermostatSuggestion', {
117
117
  command: 'addThermostatSuggestion',
118
118
  request,
@@ -122,17 +122,17 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
122
122
  context: this.context,
123
123
  });
124
124
  if (this.state.presets.find((p) => p.presetHandle !== null && Bytes.areEqual(p.presetHandle, request.presetHandle)) === undefined) {
125
- throw new StatusResponse.NotFoundError('Requested PresetHandle not found');
125
+ throw new StatusResponseError(`MatterbridgeThermostatServer: requested PresetHandle not found (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
126
126
  }
127
127
  this.removeExpiredThermostatSuggestions();
128
128
  this.reEvaluateCurrentThermostatSuggestion();
129
129
  if (this.state.thermostatSuggestions.length >= this.state.maxThermostatSuggestions) {
130
- throw new StatusResponse.ResourceExhaustedError('Maximum number of thermostat suggestions reached');
130
+ throw new StatusResponseError(`MatterbridgeThermostatServer: maximum number of thermostat suggestions reached (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ResourceExhausted);
131
131
  }
132
132
  const currentTime = Math.floor(Date.now() / 1000);
133
133
  const effectiveTime = request.effectiveTime ?? currentTime;
134
134
  if (effectiveTime > currentTime + 24 * 60 * 60) {
135
- throw new StatusResponse.InvalidCommandError('EffectiveTime cannot be more than 24 hours in the future');
135
+ throw new StatusResponseError(`MatterbridgeThermostatServer: requested EffectiveTime is more than 24 hours in the future (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidCommand);
136
136
  }
137
137
  const usedUniqueIds = new Set(this.state.thermostatSuggestions.map((s) => s.uniqueId));
138
138
  let uniqueId = 0;
@@ -146,12 +146,12 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
146
146
  };
147
147
  this.state.thermostatSuggestions = [...this.state.thermostatSuggestions, suggestion];
148
148
  this.reEvaluateCurrentThermostatSuggestion();
149
- device.log.debug(`MatterbridgeThermostatServer: addThermostatSuggestion completed with uniqueId: ${uniqueId}`);
149
+ device.log.debug(`MatterbridgeThermostatServer: addThermostatSuggestion completed with uniqueId: ${uniqueId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
150
150
  return { uniqueId };
151
151
  }
152
152
  async removeThermostatSuggestion(request) {
153
153
  const device = this.endpoint.stateOf(MatterbridgeServer);
154
- device.log.info(`Removing thermostat suggestion ${request.uniqueId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
154
+ device.log.info(`MatterbridgeThermostatServer: removing thermostat suggestion ${request.uniqueId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
155
155
  await device.commandHandler.executeHandler('Thermostat.removeThermostatSuggestion', {
156
156
  command: 'removeThermostatSuggestion',
157
157
  request,
@@ -162,11 +162,11 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
162
162
  });
163
163
  const suggestion = this.state.thermostatSuggestions.find((s) => s.uniqueId === request.uniqueId);
164
164
  if (suggestion === undefined) {
165
- throw new StatusResponse.NotFoundError('Requested UniqueID not found');
165
+ throw new StatusResponseError(`MatterbridgeThermostatServer: requested UniqueID not found (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.NotFound);
166
166
  }
167
167
  this.state.thermostatSuggestions = this.state.thermostatSuggestions.filter((s) => s.uniqueId !== request.uniqueId);
168
168
  this.removeExpiredThermostatSuggestions();
169
169
  this.reEvaluateCurrentThermostatSuggestion();
170
- device.log.debug(`MatterbridgeThermostatServer: removeThermostatSuggestion completed for uniqueId: ${request.uniqueId}`);
170
+ device.log.debug(`MatterbridgeThermostatServer: removeThermostatSuggestion completed for uniqueId: ${request.uniqueId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
171
171
  }
172
172
  }
@@ -1,4 +1,5 @@
1
- import { type WindowCoveringBaseServer, WindowCoveringServer } from '@matter/node/behaviors/window-covering';
1
+ import { type Timer } from '@matter/general';
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, {
4
5
  lift: true;
@@ -7,7 +8,9 @@ declare const MatterbridgeWindowCoveringServer_base: import("@matter/node").Clus
7
8
  tilt: true;
8
9
  }>, import("@matter/types").ClusterType.Concrete, typeof WindowCoveringBaseServer.Internal, "windowCovering">;
9
10
  export declare class MatterbridgeWindowCoveringServer extends MatterbridgeWindowCoveringServer_base {
10
- protected internal: WindowCoveringBaseServer.Internal;
11
+ #private;
12
+ readonly state: MatterbridgeWindowCoveringServer.State;
13
+ protected internal: MatterbridgeWindowCoveringServer.Internal;
11
14
  lookupMovementStatus: string[];
12
15
  private getMovementStatusLabel;
13
16
  initialize(): void;
@@ -16,6 +19,17 @@ export declare class MatterbridgeWindowCoveringServer extends MatterbridgeWindow
16
19
  stopMotion(): Promise<void>;
17
20
  goToLiftPercentage(request: WindowCovering.GoToLiftPercentageRequest): Promise<void>;
18
21
  goToTiltPercentage(request: WindowCovering.GoToTiltPercentageRequest): Promise<void>;
19
- handleMovement(): void;
22
+ [Symbol.asyncDispose](): Promise<void>;
23
+ }
24
+ export declare namespace MatterbridgeWindowCoveringServer {
25
+ class Internal extends WindowCoveringBaseServer.Internal {
26
+ liftMovementTimer?: Timer;
27
+ liftMovementTarget: number;
28
+ tiltMovementTimer?: Timer;
29
+ tiltMovementTarget: number;
30
+ }
31
+ class State extends WindowCoveringBaseServer.State {
32
+ movementDuration: number;
33
+ }
20
34
  }
21
35
  export {};
@@ -1,4 +1,5 @@
1
- import { WindowCoveringServer } from '@matter/node/behaviors/window-covering';
1
+ import { Millis, Time } from '@matter/general';
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';
4
5
  export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt) {
@@ -8,13 +9,73 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
8
9
  }
9
10
  initialize() {
10
11
  const device = this.endpoint.stateOf(MatterbridgeServer);
11
- device.log.info(`Initializing MatterbridgeWindowCoveringServer (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
12
+ device.log.info(`MatterbridgeWindowCoveringServer: initializing (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
12
13
  this.internal.disableOperationalModeHandling = true;
14
+ if (process.env.MATTERBRIDGE_CHIP_TEST) {
15
+ this.state.movementDuration = 3000;
16
+ }
13
17
  super.initialize();
18
+ if (this.features.positionAwareLift)
19
+ this.reactTo(this.events.currentPositionLiftPercent100ths$Changing, this.#syncLiftCurrentPositionPercentage);
20
+ if (this.features.positionAwareTilt)
21
+ this.reactTo(this.events.currentPositionTiltPercent100ths$Changing, this.#syncTiltCurrentPositionPercentage);
22
+ }
23
+ #syncLiftCurrentPositionPercentage(percent100ths) {
24
+ this.state.currentPositionLiftPercentage = percent100ths === null ? percent100ths : Math.floor(percent100ths / 100);
25
+ }
26
+ #syncTiltCurrentPositionPercentage(percent100ths) {
27
+ this.state.currentPositionTiltPercentage = percent100ths === null ? percent100ths : Math.floor(percent100ths / 100);
28
+ }
29
+ #computeMovementStatus(target, current) {
30
+ if (current === null || target === null || current === target)
31
+ return WindowCovering.MovementStatus.Stopped;
32
+ return current < target ? WindowCovering.MovementStatus.Closing : WindowCovering.MovementStatus.Opening;
33
+ }
34
+ #updateGlobalOperationalStatus() {
35
+ const { lift, tilt } = this.state.operationalStatus;
36
+ this.state.operationalStatus.global = lift === WindowCovering.MovementStatus.Stopped ? tilt : lift;
37
+ }
38
+ #startLiftMovement(targetPercent100ths) {
39
+ this.internal.liftMovementTimer?.stop();
40
+ this.internal.liftMovementTimer = undefined;
41
+ if (this.state.movementDuration <= 0)
42
+ return;
43
+ const status = this.#computeMovementStatus(targetPercent100ths, this.state.currentPositionLiftPercent100ths);
44
+ this.state.operationalStatus.lift = status;
45
+ this.#updateGlobalOperationalStatus();
46
+ if (status === WindowCovering.MovementStatus.Stopped)
47
+ return;
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
+ }
57
+ #startTiltMovement(targetPercent100ths) {
58
+ this.internal.tiltMovementTimer?.stop();
59
+ this.internal.tiltMovementTimer = undefined;
60
+ if (this.state.movementDuration <= 0)
61
+ return;
62
+ const status = this.#computeMovementStatus(targetPercent100ths, this.state.currentPositionTiltPercent100ths);
63
+ this.state.operationalStatus.tilt = status;
64
+ this.#updateGlobalOperationalStatus();
65
+ if (status === WindowCovering.MovementStatus.Stopped)
66
+ return;
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();
14
75
  }
15
76
  async upOrOpen() {
16
77
  const device = this.endpoint.stateOf(MatterbridgeServer);
17
- device.log.info(`Opening cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
78
+ device.log.info(`MatterbridgeWindowCoveringServer: opening cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
18
79
  await device.commandHandler.executeHandler('WindowCovering.upOrOpen', {
19
80
  command: 'upOrOpen',
20
81
  request: {},
@@ -23,13 +84,17 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
23
84
  endpoint: this.endpoint,
24
85
  context: this.context,
25
86
  });
26
- device.log.debug(`MatterbridgeWindowCoveringServer: upOrOpen called`);
87
+ device.log.debug(`MatterbridgeWindowCoveringServer: upOrOpen called (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
27
88
  await super.upOrOpen();
28
- device.log.debug(`MatterbridgeWindowCoveringServer: upOrOpen result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} status global ${this.getMovementStatusLabel(this.state.operationalStatus.global)} lift ${this.getMovementStatusLabel(this.state.operationalStatus.lift)} tilt ${this.getMovementStatusLabel(this.state.operationalStatus.tilt)}`);
89
+ if (this.features.positionAwareLift && this.state.targetPositionLiftPercent100ths !== null)
90
+ this.#startLiftMovement(this.state.targetPositionLiftPercent100ths);
91
+ if (this.features.positionAwareTilt && this.state.targetPositionTiltPercent100ths !== null)
92
+ this.#startTiltMovement(this.state.targetPositionTiltPercent100ths);
93
+ device.log.debug(`MatterbridgeWindowCoveringServer: upOrOpen result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} 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})`);
29
94
  }
30
95
  async downOrClose() {
31
96
  const device = this.endpoint.stateOf(MatterbridgeServer);
32
- device.log.info(`Closing cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
97
+ device.log.info(`MatterbridgeWindowCoveringServer: closing cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
33
98
  await device.commandHandler.executeHandler('WindowCovering.downOrClose', {
34
99
  command: 'downOrClose',
35
100
  request: {},
@@ -38,13 +103,17 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
38
103
  endpoint: this.endpoint,
39
104
  context: this.context,
40
105
  });
41
- device.log.debug(`MatterbridgeWindowCoveringServer: downOrClose called`);
106
+ device.log.debug(`MatterbridgeWindowCoveringServer: downOrClose called (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
42
107
  await super.downOrClose();
43
- device.log.debug(`MatterbridgeWindowCoveringServer: downOrClose result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} status global ${this.getMovementStatusLabel(this.state.operationalStatus.global)} lift ${this.getMovementStatusLabel(this.state.operationalStatus.lift)} tilt ${this.getMovementStatusLabel(this.state.operationalStatus.tilt)}`);
108
+ if (this.features.positionAwareLift && this.state.targetPositionLiftPercent100ths !== null)
109
+ this.#startLiftMovement(this.state.targetPositionLiftPercent100ths);
110
+ if (this.features.positionAwareTilt && this.state.targetPositionTiltPercent100ths !== null)
111
+ this.#startTiltMovement(this.state.targetPositionTiltPercent100ths);
112
+ device.log.debug(`MatterbridgeWindowCoveringServer: downOrClose result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} 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})`);
44
113
  }
45
114
  async stopMotion() {
46
115
  const device = this.endpoint.stateOf(MatterbridgeServer);
47
- device.log.info(`Stopping cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
116
+ device.log.info(`MatterbridgeWindowCoveringServer: stopping cover (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
48
117
  await device.commandHandler.executeHandler('WindowCovering.stopMotion', {
49
118
  command: 'stopMotion',
50
119
  request: {},
@@ -53,13 +122,24 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
53
122
  endpoint: this.endpoint,
54
123
  context: this.context,
55
124
  });
56
- device.log.debug(`MatterbridgeWindowCoveringServer: stopMotion called`);
125
+ device.log.debug(`MatterbridgeWindowCoveringServer: stopMotion called (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
57
126
  await super.stopMotion();
58
- device.log.debug(`MatterbridgeWindowCoveringServer: stopMotion result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} status global ${this.getMovementStatusLabel(this.state.operationalStatus.global)} lift ${this.getMovementStatusLabel(this.state.operationalStatus.lift)} tilt ${this.getMovementStatusLabel(this.state.operationalStatus.tilt)}`);
127
+ this.internal.liftMovementTimer?.stop();
128
+ this.internal.liftMovementTimer = undefined;
129
+ this.internal.tiltMovementTimer?.stop();
130
+ this.internal.tiltMovementTimer = undefined;
131
+ if (this.state.movementDuration > 0) {
132
+ if (this.features.positionAwareLift)
133
+ this.state.targetPositionLiftPercent100ths = this.state.currentPositionLiftPercent100ths;
134
+ if (this.features.positionAwareTilt)
135
+ this.state.targetPositionTiltPercent100ths = this.state.currentPositionTiltPercent100ths;
136
+ this.state.operationalStatus = { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped };
137
+ }
138
+ device.log.debug(`MatterbridgeWindowCoveringServer: stopMotion result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} 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})`);
59
139
  }
60
140
  async goToLiftPercentage(request) {
61
141
  const device = this.endpoint.stateOf(MatterbridgeServer);
62
- device.log.info(`Setting cover lift percentage to ${request.liftPercent100thsValue} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
142
+ device.log.info(`MatterbridgeWindowCoveringServer: setting cover lift percentage to ${request.liftPercent100thsValue} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
63
143
  await device.commandHandler.executeHandler('WindowCovering.goToLiftPercentage', {
64
144
  command: 'goToLiftPercentage',
65
145
  request,
@@ -68,13 +148,15 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
68
148
  endpoint: this.endpoint,
69
149
  context: this.context,
70
150
  });
71
- device.log.debug(`MatterbridgeWindowCoveringServer: goToLiftPercentage with ${request.liftPercent100thsValue}`);
151
+ device.log.debug(`MatterbridgeWindowCoveringServer: goToLiftPercentage with ${request.liftPercent100thsValue} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
72
152
  await super.goToLiftPercentage(request);
73
- device.log.debug(`MatterbridgeWindowCoveringServer: goToLiftPercentage result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} status global ${this.getMovementStatusLabel(this.state.operationalStatus.global)} lift ${this.getMovementStatusLabel(this.state.operationalStatus.lift)} tilt ${this.getMovementStatusLabel(this.state.operationalStatus.tilt)}`);
153
+ if (this.state.targetPositionLiftPercent100ths !== null)
154
+ this.#startLiftMovement(this.state.targetPositionLiftPercent100ths);
155
+ device.log.debug(`MatterbridgeWindowCoveringServer: goToLiftPercentage result target ${this.state.targetPositionLiftPercent100ths} current ${this.state.currentPositionLiftPercent100ths} 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})`);
74
156
  }
75
157
  async goToTiltPercentage(request) {
76
158
  const device = this.endpoint.stateOf(MatterbridgeServer);
77
- device.log.info(`Setting cover tilt percentage to ${request.tiltPercent100thsValue} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
159
+ device.log.info(`MatterbridgeWindowCoveringServer: setting cover tilt percentage to ${request.tiltPercent100thsValue} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
78
160
  await device.commandHandler.executeHandler('WindowCovering.goToTiltPercentage', {
79
161
  command: 'goToTiltPercentage',
80
162
  request,
@@ -83,10 +165,30 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
83
165
  endpoint: this.endpoint,
84
166
  context: this.context,
85
167
  });
86
- device.log.debug(`MatterbridgeWindowCoveringServer: goToTiltPercentage with ${request.tiltPercent100thsValue}`);
168
+ device.log.debug(`MatterbridgeWindowCoveringServer: goToTiltPercentage with ${request.tiltPercent100thsValue} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
87
169
  await super.goToTiltPercentage(request);
88
- 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)}`);
170
+ if (this.state.targetPositionTiltPercent100ths !== null)
171
+ this.#startTiltMovement(this.state.targetPositionTiltPercent100ths);
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})`);
89
173
  }
90
- handleMovement() {
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]?.();
91
180
  }
92
181
  }
182
+ (function (MatterbridgeWindowCoveringServer) {
183
+ class Internal extends WindowCoveringBaseServer.Internal {
184
+ liftMovementTimer;
185
+ liftMovementTarget = 0;
186
+ tiltMovementTimer;
187
+ tiltMovementTarget = 0;
188
+ }
189
+ MatterbridgeWindowCoveringServer.Internal = Internal;
190
+ class State extends WindowCoveringBaseServer.State {
191
+ movementDuration = 0;
192
+ }
193
+ MatterbridgeWindowCoveringServer.State = State;
194
+ })(MatterbridgeWindowCoveringServer || (MatterbridgeWindowCoveringServer = {}));
@@ -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';
@@ -10,6 +10,7 @@ import { PowerTopology } from '@matter/types/clusters/power-topology';
10
10
  import { ResourceMonitoring } from '@matter/types/clusters/resource-monitoring';
11
11
  import { RvcCleanMode } from '@matter/types/clusters/rvc-clean-mode';
12
12
  import { RvcRunMode } from '@matter/types/clusters/rvc-run-mode';
13
+ import { Thermostat } from '@matter/types/clusters/thermostat';
13
14
  import { EndpointNumber } from '@matter/types/datatype';
14
15
  import { getErrorMessage } from '@matterbridge/utils/error';
15
16
  import { AirConditioner } from './devices/airConditioner.js';
@@ -266,8 +267,33 @@ export async function createDemoDevices(matterbridge) {
266
267
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('PumpController'), bridgedNode, powerSource], { id: 'PumpController', number: EndpointNumber(6_05) });
267
268
  ep.createDefaultPowerSourceWiredClusterServer();
268
269
  await registerDevice(ep, 'Pump Controller', 'SWITCH-06-05');
269
- 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) });
270
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();
271
297
  await registerDevice(ep, 'Generic Switch', 'SWITCH-06-06');
272
298
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('ContactSensor'), bridgedNode, powerSource], { id: 'ContactSensor', number: EndpointNumber(7_01) });
273
299
  ep.createDefaultPowerSourceBatteryClusterServer();
@@ -348,11 +374,15 @@ export async function createDemoDevices(matterbridge) {
348
374
  await registerDevice(ep, 'Door Lock User PIN Schedules', 'ENTRY-08-01-2');
349
375
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('DoorLockController'), bridgedNode, powerSource], { id: 'DoorLockController', number: EndpointNumber(8_02) });
350
376
  await registerDevice(ep, 'Door Lock Controller', 'ENTRY-08-02');
351
- ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCovering', number: EndpointNumber(8_03) });
352
- await registerDevice(ep, 'Window Covering', 'ENTRY-08-03');
377
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCoveringLift', number: EndpointNumber(8_03) });
378
+ ep.createDefaultWindowCoveringClusterServer();
379
+ await registerDevice(ep, 'Window Covering Lift', 'ENTRY-08-03');
353
380
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCoveringTilt', number: EndpointNumber(8_03_1) });
354
- ep.createDefaultLiftTiltWindowCoveringClusterServer();
381
+ ep.createDefaultTiltWindowCoveringClusterServer();
355
382
  await registerDevice(ep, 'Window Covering Tilt', 'ENTRY-08-03-1');
383
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCoveringLiftTilt', number: EndpointNumber(8_03_2) });
384
+ ep.createDefaultLiftTiltWindowCoveringClusterServer();
385
+ await registerDevice(ep, 'Window Covering LiftTilt', 'ENTRY-08-03-2');
356
386
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCoveringController'), bridgedNode, powerSource], { id: 'WindowCoveringController', number: EndpointNumber(8_04) });
357
387
  await registerDevice(ep, 'Window Covering Controller', 'ENTRY-08-04');
358
388
  ep = new Closure('Closure', 'ENTRY-08-05', {
@@ -443,8 +473,9 @@ export async function createDemoDevices(matterbridge) {
443
473
  await registerDevice(ep, 'Closure Panel Smart-Glass', 'ENTRY-08-06-5');
444
474
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('ClosureController'), bridgedNode, powerSource], { id: 'ClosureController', number: EndpointNumber(8_07) });
445
475
  await registerDevice(ep, 'Closure Controller', 'ENTRY-08-07');
446
- ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'Thermostat', number: EndpointNumber(9_01) });
447
- await registerDevice(ep, 'Thermostat', 'HVAC-09-01');
476
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatAuto', number: EndpointNumber(9_01) });
477
+ ep.createDefaultThermostatClusterServer(23, 21, 25, 2, 0, 47, 3, 50);
478
+ await registerDevice(ep, 'Thermostat Auto', 'HVAC-09-01');
448
479
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatHeating', number: EndpointNumber(9_01_1) });
449
480
  ep.createDefaultHeatingThermostatClusterServer();
450
481
  await registerDevice(ep, 'Thermostat Heating', 'HVAC-09-01-1');
@@ -458,7 +489,13 @@ export async function createDemoDevices(matterbridge) {
458
489
  ep.createDefaultSchedulesThermostatClusterServer();
459
490
  await registerDevice(ep, 'Thermostat Schedules', 'HVAC-09-01-4');
460
491
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatSuggestions', number: EndpointNumber(9_01_5) });
461
- ep.createDefaultThermostatSuggestionsClusterServer();
492
+ ep.createDefaultThermostatSuggestionsClusterServer(23, 21, 25, 0, 0, 50, 0, 50, undefined, undefined, undefined, undefined, null, [
493
+ { presetHandle: Uint8Array.from([0]), presetScenario: Thermostat.PresetScenario.Occupied, name: 'Occupied', coolingSetpoint: 2500, heatingSetpoint: 2100, builtIn: true },
494
+ { presetHandle: Uint8Array.from([1]), presetScenario: Thermostat.PresetScenario.Unoccupied, name: 'Unoccupied', coolingSetpoint: 2700, heatingSetpoint: 1900, builtIn: true },
495
+ ], [
496
+ { presetScenario: Thermostat.PresetScenario.Occupied, numberOfPresets: 4, presetTypeFeatures: { automatic: false, supportsNames: true } },
497
+ { presetScenario: Thermostat.PresetScenario.Unoccupied, numberOfPresets: 4, presetTypeFeatures: { automatic: false, supportsNames: true } },
498
+ ]);
462
499
  await registerDevice(ep, 'Thermostat Suggestions', 'HVAC-09-01-5');
463
500
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('Fan'), bridgedNode, powerSource], { id: 'Fan', number: EndpointNumber(9_02) });
464
501
  await registerDevice(ep, 'Fan OffLowMedHighAuto', 'HVAC-09-02');
@@ -36,6 +36,8 @@ export declare namespace MatterbridgeClosureControlServer {
36
36
  movementDuration: number;
37
37
  calibrationDuration: number;
38
38
  signaturePosition: number;
39
+ ventilationPosition: number;
40
+ pedestrianPosition: number;
39
41
  }
40
42
  }
41
43
  export interface ClosureOptions {
@@ -51,6 +53,8 @@ export interface ClosureOptions {
51
53
  movementDuration?: number;
52
54
  calibrationDuration?: number;
53
55
  signaturePosition?: number;
56
+ ventilationPosition?: number;
57
+ pedestrianPosition?: number;
54
58
  motionLatching?: boolean;
55
59
  speed?: boolean;
56
60
  ventilation?: boolean;
@@ -64,10 +68,17 @@ export declare class Closure extends MatterbridgeEndpoint {
64
68
  constructor(name: string, serial: string, options?: ClosureOptions);
65
69
  getMainState(): ClosureControl.MainState | undefined;
66
70
  getSignaturePosition(): number | undefined;
71
+ getVentilationPosition(): number | undefined;
72
+ getPedestrianPosition(): number | undefined;
67
73
  setState(currentState: ClosureControl.OverallCurrentState, targetState: ClosureControl.OverallTargetState, mainState?: ClosureControl.MainState, countdownTime?: number, currentErrorList?: ClosureControl.ClosureError[]): Promise<void>;
68
74
  setFullyClosed(): Promise<void>;
69
75
  setFullOpened(): Promise<void>;
70
76
  setPartiallyOpened(): Promise<void>;
77
+ setOpenedAtSignature(): Promise<void>;
78
+ setOpenedForVentilation(): Promise<void>;
79
+ setOpenedForPedestrian(): Promise<void>;
80
+ targetPositionToCurrentPosition(position: ClosureControl.TargetPosition): ClosureControl.CurrentPosition;
81
+ targetPositionToTargetPercent(position: ClosureControl.TargetPosition): number;
71
82
  triggerOperationalError(errorState?: ClosureControl.ClosureError[]): Promise<void>;
72
83
  triggerMovementCompleted(): Promise<void>;
73
84
  triggerSecureStateChanged(secureValue: boolean): Promise<void>;
@@ -175,12 +175,14 @@ export class MatterbridgeClosureControlServer extends MatterbridgeClosureControl
175
175
  movementDuration = 0;
176
176
  calibrationDuration = 0;
177
177
  signaturePosition = 50_00;
178
+ ventilationPosition = 50_00;
179
+ pedestrianPosition = 50_00;
178
180
  }
179
181
  MatterbridgeClosureControlServer.State = State;
180
182
  })(MatterbridgeClosureControlServer || (MatterbridgeClosureControlServer = {}));
181
183
  export class Closure extends MatterbridgeEndpoint {
182
184
  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;
185
+ 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
186
  super(powerSourceType === 'None' ? [closure] : [closure, powerSource], {
185
187
  id: id ?? `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
186
188
  number,
@@ -223,6 +225,8 @@ export class Closure extends MatterbridgeEndpoint {
223
225
  movementDuration,
224
226
  calibrationDuration,
225
227
  signaturePosition,
228
+ ventilationPosition,
229
+ pedestrianPosition,
226
230
  };
227
231
  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
232
  }
@@ -232,23 +236,31 @@ export class Closure extends MatterbridgeEndpoint {
232
236
  getSignaturePosition() {
233
237
  return this.getAttribute(MatterbridgeClosureControlServer, 'signaturePosition');
234
238
  }
239
+ getVentilationPosition() {
240
+ return this.getAttribute(MatterbridgeClosureControlServer, 'ventilationPosition');
241
+ }
242
+ getPedestrianPosition() {
243
+ return this.getAttribute(MatterbridgeClosureControlServer, 'pedestrianPosition');
244
+ }
235
245
  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;
246
+ const features = this.featuresOf(MatterbridgeClosureControlServer.id);
247
+ const supportsCurrentPosition = (currentState.position !== ClosureControl.CurrentPosition.OpenedForVentilation || features.ventilation) &&
248
+ (currentState.position !== ClosureControl.CurrentPosition.OpenedForPedestrian || features.pedestrian);
249
+ const supportsTargetPosition = (targetState.position !== ClosureControl.TargetPosition.MoveToVentilationPosition || features.ventilation) &&
250
+ (targetState.position !== ClosureControl.TargetPosition.MoveToPedestrianPosition || features.pedestrian);
239
251
  await this.setAttribute(ClosureControl, 'countdownTime', countdownTime);
240
252
  await this.setAttribute(ClosureControl, 'mainState', mainState);
241
253
  await this.setAttribute(ClosureControl, 'currentErrorList', currentErrorList);
242
254
  await this.setAttribute(ClosureControl, 'overallCurrentState', {
243
- position: currentState.position,
244
- ...(supportsMotionLatching ? { latch: currentState.latch } : null),
245
- ...(supportsSpeed ? { speed: currentState.speed } : null),
255
+ ...(supportsCurrentPosition ? { position: currentState.position } : null),
256
+ ...(features.motionLatching ? { latch: currentState.latch } : null),
257
+ ...(features.speed ? { speed: currentState.speed } : null),
246
258
  secureState: currentState.secureState,
247
259
  });
248
260
  await this.setAttribute(ClosureControl, 'overallTargetState', {
249
- position: targetState.position,
250
- ...(supportsMotionLatching ? { latch: targetState.latch } : null),
251
- ...(supportsSpeed ? { speed: targetState.speed } : null),
261
+ ...(supportsTargetPosition ? { position: targetState.position } : null),
262
+ ...(features.motionLatching ? { latch: targetState.latch } : null),
263
+ ...(features.speed ? { speed: targetState.speed } : null),
252
264
  });
253
265
  }
254
266
  async setFullyClosed() {
@@ -287,6 +299,64 @@ export class Closure extends MatterbridgeEndpoint {
287
299
  speed: ThreeLevelAuto.Auto,
288
300
  });
289
301
  }
302
+ async setOpenedAtSignature() {
303
+ await this.setState({
304
+ position: ClosureControl.CurrentPosition.OpenedAtSignature,
305
+ latch: false,
306
+ speed: ThreeLevelAuto.Auto,
307
+ secureState: false,
308
+ }, {
309
+ position: ClosureControl.TargetPosition.MoveToSignaturePosition,
310
+ latch: false,
311
+ speed: ThreeLevelAuto.Auto,
312
+ });
313
+ }
314
+ async setOpenedForVentilation() {
315
+ await this.setState({
316
+ position: ClosureControl.CurrentPosition.OpenedForVentilation,
317
+ latch: false,
318
+ speed: ThreeLevelAuto.Auto,
319
+ secureState: false,
320
+ }, {
321
+ position: ClosureControl.TargetPosition.MoveToVentilationPosition,
322
+ latch: false,
323
+ speed: ThreeLevelAuto.Auto,
324
+ });
325
+ }
326
+ async setOpenedForPedestrian() {
327
+ await this.setState({
328
+ position: ClosureControl.CurrentPosition.OpenedForPedestrian,
329
+ latch: false,
330
+ speed: ThreeLevelAuto.Auto,
331
+ secureState: false,
332
+ }, {
333
+ position: ClosureControl.TargetPosition.MoveToPedestrianPosition,
334
+ latch: false,
335
+ speed: ThreeLevelAuto.Auto,
336
+ });
337
+ }
338
+ targetPositionToCurrentPosition(position) {
339
+ if (position === ClosureControl.TargetPosition.MoveToFullyClosed)
340
+ return ClosureControl.CurrentPosition.FullyClosed;
341
+ if (position === ClosureControl.TargetPosition.MoveToFullyOpen)
342
+ return ClosureControl.CurrentPosition.FullyOpened;
343
+ if (position === ClosureControl.TargetPosition.MoveToPedestrianPosition)
344
+ return ClosureControl.CurrentPosition.OpenedForPedestrian;
345
+ if (position === ClosureControl.TargetPosition.MoveToVentilationPosition)
346
+ return ClosureControl.CurrentPosition.OpenedForVentilation;
347
+ return ClosureControl.CurrentPosition.OpenedAtSignature;
348
+ }
349
+ targetPositionToTargetPercent(position) {
350
+ if (position === ClosureControl.TargetPosition.MoveToFullyOpen)
351
+ return 0;
352
+ if (position === ClosureControl.TargetPosition.MoveToFullyClosed)
353
+ return 100_00;
354
+ if (position === ClosureControl.TargetPosition.MoveToPedestrianPosition)
355
+ return this.getPedestrianPosition() ?? 50_00;
356
+ if (position === ClosureControl.TargetPosition.MoveToVentilationPosition)
357
+ return this.getVentilationPosition() ?? 50_00;
358
+ return this.getSignaturePosition() ?? 50_00;
359
+ }
290
360
  async triggerOperationalError(errorState = []) {
291
361
  await this.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Error);
292
362
  await this.setAttribute(ClosureControl, 'currentErrorList', errorState);
@@ -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
  }
@@ -407,7 +407,16 @@ export class MatterNode extends EventEmitter {
407
407
  this.log.warn(`Invalid discriminator ${discriminator} for server node ${storeId}. Discriminator must be between 0 and 4095 (0xFFF). Generating a random discriminator...`);
408
408
  discriminator = PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
409
409
  }
410
- const rootEndpoint = hasParameter('root-power-source') ? ServerNode.RootEndpoint.with(PowerSourceServer.with(PowerSource.Feature.Wired)) : ServerNode.RootEndpoint;
410
+ let rootEndpoint;
411
+ if (hasParameter('root-power-source') || process.env.MATTERBRIDGE_CHIP_TEST) {
412
+ this.log.warn(' ****************************************************************************************');
413
+ this.log.warn(' * Adding the PowerSource cluster server to the root endpoint. *');
414
+ this.log.warn(' ****************************************************************************************');
415
+ rootEndpoint = ServerNode.RootEndpoint.with(PowerSourceServer.with(PowerSource.Feature.Wired));
416
+ }
417
+ else {
418
+ rootEndpoint = ServerNode.RootEndpoint;
419
+ }
411
420
  const serverNode = await ServerNode.create(rootEndpoint, {
412
421
  id: storeId,
413
422
  environment: this.environment,
@@ -1893,7 +1893,16 @@ export class Matterbridge extends EventEmitter {
1893
1893
  this.log.warn(`Invalid discriminator ${discriminator} for server node ${storeId}. Discriminator must be between 0 and 4095 (0xFFF). Generating a random discriminator...`);
1894
1894
  discriminator = PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
1895
1895
  }
1896
- let rootEndpoint = hasParameter('root-power-source') ? ServerNode.RootEndpoint.with(PowerSourceServer.with(PowerSource.Feature.Wired)) : ServerNode.RootEndpoint;
1896
+ let rootEndpoint;
1897
+ if (hasParameter('root-power-source') || process.env.MATTERBRIDGE_CHIP_TEST) {
1898
+ this.log.warn(' ****************************************************************************************');
1899
+ this.log.warn(' * Adding the PowerSource cluster server to the root endpoint. *');
1900
+ this.log.warn(' ****************************************************************************************');
1901
+ rootEndpoint = ServerNode.RootEndpoint.with(PowerSourceServer.with(PowerSource.Feature.Wired));
1902
+ }
1903
+ else {
1904
+ rootEndpoint = ServerNode.RootEndpoint;
1905
+ }
1897
1906
  if (process.env.MATTERBRIDGE_CHIP_TEST && fs.existsSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'chipTests.js'))) {
1898
1907
  this.log.warn(' ****************************************************************************************');
1899
1908
  this.log.warn(' * MATTERBRIDGE_CHIP_TEST environment variable is set. Running TestEventTrigger server. *');
@@ -171,8 +171,9 @@ export declare class MatterbridgeEndpoint extends Endpoint {
171
171
  createCtColorControlClusterServer(colorTemperatureMireds?: number, colorTempPhysicalMinMireds?: number, colorTempPhysicalMaxMireds?: number): this;
172
172
  configureColorControlMode(colorMode: ColorControl.ColorMode): Promise<void>;
173
173
  configureEnhancedColorControlMode(colorMode: ColorControl.EnhancedColorMode): Promise<void>;
174
- createDefaultWindowCoveringClusterServer(positionPercent100ths?: number, type?: WindowCovering.WindowCoveringType, endProductType?: WindowCovering.EndProductType): this;
175
- createDefaultLiftTiltWindowCoveringClusterServer(positionLiftPercent100ths?: number, positionTiltPercent100ths?: number, type?: WindowCovering.WindowCoveringType, endProductType?: WindowCovering.EndProductType): this;
174
+ createDefaultWindowCoveringClusterServer(positionPercent100ths?: number | null, type?: WindowCovering.WindowCoveringType, endProductType?: WindowCovering.EndProductType, movementDuration?: number): this;
175
+ createDefaultLiftTiltWindowCoveringClusterServer(positionLiftPercent100ths?: number | null, positionTiltPercent100ths?: number | null, type?: WindowCovering.WindowCoveringType, endProductType?: WindowCovering.EndProductType, movementDuration?: number): this;
176
+ createDefaultTiltWindowCoveringClusterServer(positionTiltPercent100ths?: number | null, type?: WindowCovering.WindowCoveringType, endProductType?: WindowCovering.EndProductType, movementDuration?: number): this;
176
177
  setWindowCoveringTargetAsCurrentAndStopped(): Promise<void>;
177
178
  setWindowCoveringCurrentTargetStatus(current: number, target: number, status: WindowCovering.MovementStatus): Promise<void>;
178
179
  setWindowCoveringStatus(status: WindowCovering.MovementStatus): Promise<void>;
@@ -825,7 +825,7 @@ export class MatterbridgeEndpoint extends Endpoint {
825
825
  await this.setAttribute(ColorControl.id, 'enhancedColorMode', colorMode, this.log);
826
826
  }
827
827
  }
828
- createDefaultWindowCoveringClusterServer(positionPercent100ths, type = WindowCovering.WindowCoveringType.Rollershade, endProductType = WindowCovering.EndProductType.RollerShade) {
828
+ createDefaultWindowCoveringClusterServer(positionPercent100ths = 0, type = WindowCovering.WindowCoveringType.Rollershade, endProductType = WindowCovering.EndProductType.RollerShade, movementDuration = 0) {
829
829
  this.behaviors.require(MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
830
830
  type,
831
831
  numberOfActuationsLift: 0,
@@ -841,12 +841,14 @@ export class MatterbridgeEndpoint extends Endpoint {
841
841
  operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
842
842
  endProductType,
843
843
  mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
844
- targetPositionLiftPercent100ths: positionPercent100ths ?? 0,
845
- currentPositionLiftPercent100ths: positionPercent100ths ?? 0,
844
+ targetPositionLiftPercent100ths: positionPercent100ths,
845
+ currentPositionLiftPercent100ths: positionPercent100ths,
846
+ currentPositionLiftPercentage: positionPercent100ths === null ? null : Math.floor(positionPercent100ths / 100),
847
+ movementDuration,
846
848
  });
847
849
  return this;
848
850
  }
849
- createDefaultLiftTiltWindowCoveringClusterServer(positionLiftPercent100ths, positionTiltPercent100ths, type = WindowCovering.WindowCoveringType.TiltBlindLift, endProductType = WindowCovering.EndProductType.InteriorBlind) {
851
+ createDefaultLiftTiltWindowCoveringClusterServer(positionLiftPercent100ths = 0, positionTiltPercent100ths = 0, type = WindowCovering.WindowCoveringType.TiltBlindLift, endProductType = WindowCovering.EndProductType.InteriorBlind, movementDuration = 0) {
850
852
  this.behaviors.require(MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt), {
851
853
  type,
852
854
  numberOfActuationsLift: 0,
@@ -863,10 +865,36 @@ export class MatterbridgeEndpoint extends Endpoint {
863
865
  operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
864
866
  endProductType,
865
867
  mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
866
- targetPositionLiftPercent100ths: positionLiftPercent100ths ?? 0,
867
- currentPositionLiftPercent100ths: positionLiftPercent100ths ?? 0,
868
- targetPositionTiltPercent100ths: positionTiltPercent100ths ?? 0,
869
- currentPositionTiltPercent100ths: positionTiltPercent100ths ?? 0,
868
+ targetPositionLiftPercent100ths: positionLiftPercent100ths,
869
+ currentPositionLiftPercent100ths: positionLiftPercent100ths,
870
+ currentPositionLiftPercentage: positionLiftPercent100ths === null ? null : Math.floor(positionLiftPercent100ths / 100),
871
+ targetPositionTiltPercent100ths: positionTiltPercent100ths,
872
+ currentPositionTiltPercent100ths: positionTiltPercent100ths,
873
+ currentPositionTiltPercentage: positionTiltPercent100ths === null ? null : Math.floor(positionTiltPercent100ths / 100),
874
+ movementDuration,
875
+ });
876
+ return this;
877
+ }
878
+ createDefaultTiltWindowCoveringClusterServer(positionTiltPercent100ths = 0, type = WindowCovering.WindowCoveringType.TiltBlindTiltOnly, endProductType = WindowCovering.EndProductType.InteriorVenetianBlind, movementDuration = 0) {
879
+ this.behaviors.require(MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt), {
880
+ type,
881
+ numberOfActuationsTilt: 0,
882
+ configStatus: {
883
+ operational: true,
884
+ onlineReserved: false,
885
+ liftMovementReversed: false,
886
+ liftPositionAware: false,
887
+ tiltPositionAware: true,
888
+ liftEncoderControlled: false,
889
+ tiltEncoderControlled: false,
890
+ },
891
+ operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
892
+ endProductType,
893
+ mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
894
+ targetPositionTiltPercent100ths: positionTiltPercent100ths,
895
+ currentPositionTiltPercent100ths: positionTiltPercent100ths,
896
+ currentPositionTiltPercentage: positionTiltPercent100ths === null ? null : Math.floor(positionTiltPercent100ths / 100),
897
+ movementDuration,
870
898
  });
871
899
  return this;
872
900
  }
@@ -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-20260831-e73fc65",
3
+ "version": "3.10.8-dev-20260901-971f7bf",
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-20260831-e73fc65",
136
- "@matterbridge/thread": "3.10.8-dev-20260831-e73fc65",
137
- "@matterbridge/types": "3.10.8-dev-20260831-e73fc65",
138
- "@matterbridge/utils": "3.10.8-dev-20260831-e73fc65",
135
+ "@matterbridge/dgram": "3.10.8-dev-20260901-971f7bf",
136
+ "@matterbridge/thread": "3.10.8-dev-20260901-971f7bf",
137
+ "@matterbridge/types": "3.10.8-dev-20260901-971f7bf",
138
+ "@matterbridge/utils": "3.10.8-dev-20260901-971f7bf",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.7.0",