@matterbridge/core 3.10.8-dev-20260901-971f7bf → 3.10.8-dev-20260903-4ab4a0f

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.
@@ -21,4 +21,5 @@ export * from './smokeCoAlarmServer.js';
21
21
  export * from './switchServer.js';
22
22
  export * from './thermostatServer.js';
23
23
  export * from './valveConfigurationAndControlServer.js';
24
+ export * from './waterTankLevelMonitoringServer.js';
24
25
  export * from './windowCoveringServer.js';
@@ -21,4 +21,5 @@ export * from './smokeCoAlarmServer.js';
21
21
  export * from './switchServer.js';
22
22
  export * from './thermostatServer.js';
23
23
  export * from './valveConfigurationAndControlServer.js';
24
+ export * from './waterTankLevelMonitoringServer.js';
24
25
  export * from './windowCoveringServer.js';
@@ -0,0 +1,11 @@
1
+ import { WaterTankLevelMonitoringServer } from '@matter/node/behaviors/water-tank-level-monitoring';
2
+ import type { WaterTankLevelMonitoring } from '@matter/types/clusters/water-tank-level-monitoring';
3
+ declare const MatterbridgeWaterTankLevelMonitoringServer_base: import("@matter/node").ClusterBehavior.Type<typeof WaterTankLevelMonitoringServer, import("@matter/types").ClusterType.WithSupportedFeatures<WaterTankLevelMonitoring, {
4
+ condition: true;
5
+ replacementProductList: true;
6
+ warning: true;
7
+ }>, import("@matter/types").ClusterType.Concrete, new () => {}, "waterTankLevelMonitoring">;
8
+ export declare class MatterbridgeWaterTankLevelMonitoringServer extends MatterbridgeWaterTankLevelMonitoringServer_base {
9
+ resetCondition(): Promise<void>;
10
+ }
11
+ export {};
@@ -0,0 +1,21 @@
1
+ import { WaterTankLevelMonitoringServer } from '@matter/node/behaviors/water-tank-level-monitoring';
2
+ import { ResourceMonitoring } from '@matter/types/clusters/resource-monitoring';
3
+ import { MatterbridgeServer } from './matterbridgeServer.js';
4
+ export class MatterbridgeWaterTankLevelMonitoringServer extends WaterTankLevelMonitoringServer.with(ResourceMonitoring.Feature.Condition, ResourceMonitoring.Feature.Warning, ResourceMonitoring.Feature.ReplacementProductList) {
5
+ async resetCondition() {
6
+ const device = this.endpoint.stateOf(MatterbridgeServer);
7
+ device.log.info(`MatterbridgeWaterTankLevelMonitoringServer: resetting condition (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
8
+ await device.commandHandler.executeHandler('WaterTankLevelMonitoring.resetCondition', {
9
+ command: 'resetCondition',
10
+ request: {},
11
+ cluster: MatterbridgeWaterTankLevelMonitoringServer.id,
12
+ attributes: this.state,
13
+ endpoint: this.endpoint,
14
+ context: this.context,
15
+ });
16
+ this.state.condition = 100;
17
+ this.state.changeIndication = ResourceMonitoring.ChangeIndication.Ok;
18
+ this.state.lastChangedTime = Math.floor(new Date().getTime() / 1000);
19
+ device.log.debug(`MatterbridgeWaterTankLevelMonitoringServer: resetCondition called (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
20
+ }
21
+ }
@@ -133,7 +133,11 @@ export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(
133
133
  this.state.targetPositionLiftPercent100ths = this.state.currentPositionLiftPercent100ths;
134
134
  if (this.features.positionAwareTilt)
135
135
  this.state.targetPositionTiltPercent100ths = this.state.currentPositionTiltPercent100ths;
136
- this.state.operationalStatus = { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped };
136
+ this.state.operationalStatus = {
137
+ global: WindowCovering.MovementStatus.Stopped,
138
+ ...(this.features.lift ? { lift: WindowCovering.MovementStatus.Stopped } : {}),
139
+ ...(this.features.tilt ? { tilt: WindowCovering.MovementStatus.Stopped } : {}),
140
+ };
137
141
  }
138
142
  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})`);
139
143
  }
@@ -375,13 +375,13 @@ export async function createDemoDevices(matterbridge) {
375
375
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('DoorLockController'), bridgedNode, powerSource], { id: 'DoorLockController', number: EndpointNumber(8_02) });
376
376
  await registerDevice(ep, 'Door Lock Controller', 'ENTRY-08-02');
377
377
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCoveringLift', number: EndpointNumber(8_03) });
378
- ep.createDefaultWindowCoveringClusterServer();
378
+ ep.createDefaultWindowCoveringClusterServer(100_00, undefined, undefined, 10_000);
379
379
  await registerDevice(ep, 'Window Covering Lift', 'ENTRY-08-03');
380
380
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCoveringTilt', number: EndpointNumber(8_03_1) });
381
- ep.createDefaultTiltWindowCoveringClusterServer();
381
+ ep.createDefaultTiltWindowCoveringClusterServer(100_00, undefined, undefined, 10_000);
382
382
  await registerDevice(ep, 'Window Covering Tilt', 'ENTRY-08-03-1');
383
383
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCoveringLiftTilt', number: EndpointNumber(8_03_2) });
384
- ep.createDefaultLiftTiltWindowCoveringClusterServer();
384
+ ep.createDefaultLiftTiltWindowCoveringClusterServer(100_00, 100_00, undefined, undefined, 10_000);
385
385
  await registerDevice(ep, 'Window Covering LiftTilt', 'ENTRY-08-03-2');
386
386
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCoveringController'), bridgedNode, powerSource], { id: 'WindowCoveringController', number: EndpointNumber(8_04) });
387
387
  await registerDevice(ep, 'Window Covering Controller', 'ENTRY-08-04');
@@ -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,19 +18,21 @@ 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;
@@ -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;
@@ -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 {
package/dist/export.d.ts CHANGED
@@ -19,6 +19,7 @@ export * from './behaviors/smokeCoAlarmServer.js';
19
19
  export * from './behaviors/switchServer.js';
20
20
  export * from './behaviors/thermostatServer.js';
21
21
  export * from './behaviors/valveConfigurationAndControlServer.js';
22
+ export * from './behaviors/waterTankLevelMonitoringServer.js';
22
23
  export * from './behaviors/windowCoveringServer.js';
23
24
  export { addVirtualDevice } from './helpers.js';
24
25
  export * from './matterbridgeAccessoryPlatform.js';
package/dist/export.js CHANGED
@@ -21,6 +21,7 @@ export * from './behaviors/smokeCoAlarmServer.js';
21
21
  export * from './behaviors/switchServer.js';
22
22
  export * from './behaviors/thermostatServer.js';
23
23
  export * from './behaviors/valveConfigurationAndControlServer.js';
24
+ export * from './behaviors/waterTankLevelMonitoringServer.js';
24
25
  export * from './behaviors/windowCoveringServer.js';
25
26
  export { addVirtualDevice } from './helpers.js';
26
27
  export * from './matterbridgeAccessoryPlatform.js';
package/dist/frontend.js CHANGED
@@ -1200,6 +1200,8 @@ export class Frontend extends EventEmitter {
1200
1200
  attributes += `Hepa filter: ${attributeValue}% `;
1201
1201
  if (clusterName === 'activatedCarbonFilterMonitoring' && attributeName === 'condition')
1202
1202
  attributes += `Carbon filter: ${attributeValue}% `;
1203
+ if (clusterName === 'waterTankLevelMonitoring' && attributeName === 'condition')
1204
+ attributes += `Water tank: ${attributeValue}% `;
1203
1205
  if (clusterName === 'occupancySensing' && attributeName === 'occupancy' && isValidObject(attributeValue, 1))
1204
1206
  attributes += `Occupancy: ${attributeValue.occupied} `;
1205
1207
  if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue') {
@@ -104,6 +104,7 @@ import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-confi
104
104
  import { WakeOnLan } from '@matter/types/clusters/wake-on-lan';
105
105
  import { WaterHeaterManagement } from '@matter/types/clusters/water-heater-management';
106
106
  import { WaterHeaterMode } from '@matter/types/clusters/water-heater-mode';
107
+ import { WaterTankLevelMonitoring } from '@matter/types/clusters/water-tank-level-monitoring';
107
108
  import { WebRtcTransportProvider } from '@matter/types/clusters/web-rtc-transport-provider';
108
109
  import { WebRtcTransportRequestor } from '@matter/types/clusters/web-rtc-transport-requestor';
109
110
  import { WindowCovering } from '@matter/types/clusters/window-covering';
@@ -1276,6 +1277,7 @@ export const supportedClusters = [
1276
1277
  WakeOnLan,
1277
1278
  WaterHeaterManagement,
1278
1279
  WaterHeaterMode,
1280
+ WaterTankLevelMonitoring,
1279
1281
  WebRtcTransportProvider,
1280
1282
  WebRtcTransportRequestor,
1281
1283
  WindowCovering,
@@ -207,6 +207,7 @@ export declare class MatterbridgeEndpoint extends Endpoint {
207
207
  }, airflowDirection?: FanControl.AirflowDirection): this;
208
208
  createDefaultHepaFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
209
209
  createDefaultActivatedCarbonFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
210
+ createDefaultWaterTankLevelMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
210
211
  createDefaultDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType, autoRelockTime?: number, numberOfWeekDaySchedulesSupportedPerUser?: number, numberOfYearDaySchedulesSupportedPerUser?: number, numberOfHolidaySchedulesSupported?: number): this;
211
212
  createUserPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType, autoRelockTime?: number, minPinCodeLength?: number, maxPinCodeLength?: number, numberOfWeekDaySchedulesSupportedPerUser?: number, numberOfYearDaySchedulesSupportedPerUser?: number, numberOfHolidaySchedulesSupported?: number): this;
212
213
  createDefaultModeSelectClusterServer(description: string, supportedModes: ModeSelect.ModeOption[], currentMode?: number, startUpMode?: number): this;
@@ -81,6 +81,7 @@ import { MatterbridgeSmokeCoAlarmServer } from './behaviors/smokeCoAlarmServer.j
81
81
  import { MatterbridgeSwitchServer } from './behaviors/switchServer.js';
82
82
  import { MatterbridgeThermostatServer } from './behaviors/thermostatServer.js';
83
83
  import { MatterbridgeValveConfigurationAndControlServer } from './behaviors/valveConfigurationAndControlServer.js';
84
+ import { MatterbridgeWaterTankLevelMonitoringServer } from './behaviors/waterTankLevelMonitoringServer.js';
84
85
  import { MatterbridgeWindowCoveringServer } from './behaviors/windowCoveringServer.js';
85
86
  import { CommandHandler, } from './matterbridgeEndpointCommandHandler.js';
86
87
  import { addClusterClients, addClusterServers, addFixedLabel, addOptionalClusterClients, addOptionalClusterServers, addRequiredClusterClients, addRequiredClusterServers, addUserLabel, checkNotLatinCharacters, createUniqueId, defaultFor, featuresFor, generateUniqueId, getApparentElectricalPowerMeasurementClusterServer, getAttribute, getAttributeId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getCluster, getClusterId, getDefaultDeviceEnergyManagementClusterServer, getDefaultDeviceEnergyManagementModeClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultOperationalStateClusterServer, getDefaultPowerSourceBatteryClusterServer, getDefaultPowerSourceRechargeableBatteryClusterServer, getDefaultPowerSourceReplaceableBatteryClusterServer, getDefaultPowerSourceWiredClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultSoilMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getExportedElectricalEnergyMeasurementClusterServer, getImportedElectricalEnergyMeasurementClusterServer, invokeBehaviorCommand, lowercaseFirstLetter, setAttribute, setCluster, subscribeAttribute, triggerEvent, updateAttribute, } from './matterbridgeEndpointHelpers.js';
@@ -831,14 +832,13 @@ export class MatterbridgeEndpoint extends Endpoint {
831
832
  numberOfActuationsLift: 0,
832
833
  configStatus: {
833
834
  operational: true,
834
- onlineReserved: false,
835
835
  liftMovementReversed: false,
836
836
  liftPositionAware: true,
837
837
  tiltPositionAware: false,
838
838
  liftEncoderControlled: false,
839
839
  tiltEncoderControlled: false,
840
840
  },
841
- operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
841
+ operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped },
842
842
  endProductType,
843
843
  mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
844
844
  targetPositionLiftPercent100ths: positionPercent100ths,
@@ -855,7 +855,6 @@ export class MatterbridgeEndpoint extends Endpoint {
855
855
  numberOfActuationsTilt: 0,
856
856
  configStatus: {
857
857
  operational: true,
858
- onlineReserved: false,
859
858
  liftMovementReversed: false,
860
859
  liftPositionAware: true,
861
860
  tiltPositionAware: true,
@@ -881,14 +880,13 @@ export class MatterbridgeEndpoint extends Endpoint {
881
880
  numberOfActuationsTilt: 0,
882
881
  configStatus: {
883
882
  operational: true,
884
- onlineReserved: false,
885
883
  liftMovementReversed: false,
886
884
  liftPositionAware: false,
887
885
  tiltPositionAware: true,
888
886
  liftEncoderControlled: false,
889
887
  tiltEncoderControlled: false,
890
888
  },
891
- operationalStatus: { global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
889
+ operationalStatus: { global: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped },
892
890
  endProductType,
893
891
  mode: { motorDirectionReversed: false, calibrationMode: false, maintenanceMode: false, ledFeedback: false },
894
892
  targetPositionTiltPercent100ths: positionTiltPercent100ths,
@@ -899,39 +897,44 @@ export class MatterbridgeEndpoint extends Endpoint {
899
897
  return this;
900
898
  }
901
899
  async setWindowCoveringTargetAsCurrentAndStopped() {
902
- const position = this.getAttribute(WindowCovering, 'currentPositionLiftPercent100ths', this.log);
903
- if (isValidNumber(position, 0, 10000)) {
904
- await this.setAttribute(WindowCovering, 'targetPositionLiftPercent100ths', position, this.log);
905
- await this.setAttribute(WindowCovering, 'operationalStatus', {
906
- global: WindowCovering.MovementStatus.Stopped,
907
- lift: WindowCovering.MovementStatus.Stopped,
908
- tilt: WindowCovering.MovementStatus.Stopped,
909
- }, this.log);
900
+ const { lift, tilt } = featuresFor(this, WindowCovering);
901
+ if (lift) {
902
+ const position = this.getAttribute(WindowCovering, 'currentPositionLiftPercent100ths', this.log);
903
+ if (isValidNumber(position, 0, 10000)) {
904
+ await this.setAttribute(WindowCovering, 'targetPositionLiftPercent100ths', position, this.log);
905
+ }
906
+ this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`);
910
907
  }
911
- this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths and targetPositionLiftPercent100ths to ${position} and operationalStatus to Stopped.`);
912
- if (this.hasAttributeServer(WindowCovering, 'currentPositionTiltPercent100ths')) {
908
+ if (tilt) {
913
909
  const position = this.getAttribute(WindowCovering, 'currentPositionTiltPercent100ths', this.log);
914
910
  if (isValidNumber(position, 0, 10000)) {
915
911
  await this.setAttribute(WindowCovering, 'targetPositionTiltPercent100ths', position, this.log);
916
912
  }
917
913
  this.log.debug(`Set WindowCovering currentPositionTiltPercent100ths and targetPositionTiltPercent100ths to ${position} and operationalStatus to Stopped.`);
918
914
  }
915
+ await this.setAttribute(WindowCovering, 'operationalStatus', {
916
+ global: WindowCovering.MovementStatus.Stopped,
917
+ ...(lift ? { lift: WindowCovering.MovementStatus.Stopped } : {}),
918
+ ...(tilt ? { tilt: WindowCovering.MovementStatus.Stopped } : {}),
919
+ }, this.log);
919
920
  }
920
921
  async setWindowCoveringCurrentTargetStatus(current, target, status) {
921
922
  await this.setAttribute(WindowCovering, 'currentPositionLiftPercent100ths', current, this.log);
922
923
  await this.setAttribute(WindowCovering, 'targetPositionLiftPercent100ths', target, this.log);
924
+ const { lift, tilt } = featuresFor(this, WindowCovering);
923
925
  await this.setAttribute(WindowCovering, 'operationalStatus', {
924
926
  global: status,
925
- lift: status,
926
- tilt: status,
927
+ ...(lift ? { lift: status } : {}),
928
+ ...(tilt ? { tilt: status } : {}),
927
929
  }, this.log);
928
930
  this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${current}, targetPositionLiftPercent100ths: ${target} and operationalStatus: ${status}.`);
929
931
  }
930
932
  async setWindowCoveringStatus(status) {
933
+ const { lift, tilt } = featuresFor(this, WindowCovering);
931
934
  await this.setAttribute(WindowCovering, 'operationalStatus', {
932
935
  global: status,
933
- lift: status,
934
- tilt: status,
936
+ ...(lift ? { lift: status } : {}),
937
+ ...(tilt ? { tilt: status } : {}),
935
938
  }, this.log);
936
939
  this.log.debug(`Set WindowCovering operationalStatus: ${status}`);
937
940
  }
@@ -947,7 +950,7 @@ export class MatterbridgeEndpoint extends Endpoint {
947
950
  await this.setAttribute(WindowCovering, 'currentPositionLiftPercent100ths', liftPosition, this.log);
948
951
  await this.setAttribute(WindowCovering, 'targetPositionLiftPercent100ths', liftPosition, this.log);
949
952
  this.log.debug(`Set WindowCovering currentPositionLiftPercent100ths: ${liftPosition} and targetPositionLiftPercent100ths: ${liftPosition}.`);
950
- if (tiltPosition && this.hasAttributeServer(WindowCovering, 'currentPositionTiltPercent100ths')) {
953
+ if (tiltPosition && featuresFor(this, WindowCovering).tilt) {
951
954
  await this.setAttribute(WindowCovering, 'currentPositionTiltPercent100ths', tiltPosition, this.log);
952
955
  await this.setAttribute(WindowCovering, 'targetPositionTiltPercent100ths', tiltPosition, this.log);
953
956
  this.log.debug(`Set WindowCovering currentPositionTiltPercent100ths: ${tiltPosition} and targetPositionTiltPercent100ths: ${tiltPosition}.`);
@@ -1282,6 +1285,17 @@ export class MatterbridgeEndpoint extends Endpoint {
1282
1285
  });
1283
1286
  return this;
1284
1287
  }
1288
+ createDefaultWaterTankLevelMonitoringClusterServer(condition = 100, changeIndication = ResourceMonitoring.ChangeIndication.Ok, inPlaceIndicator = true, lastChangedTime = null, replacementProductList = []) {
1289
+ this.behaviors.require(MatterbridgeWaterTankLevelMonitoringServer.with(ResourceMonitoring.Feature.Condition, ResourceMonitoring.Feature.Warning, ResourceMonitoring.Feature.ReplacementProductList), {
1290
+ condition,
1291
+ degradationDirection: ResourceMonitoring.DegradationDirection.Down,
1292
+ replacementProductList,
1293
+ changeIndication,
1294
+ inPlaceIndicator,
1295
+ lastChangedTime,
1296
+ });
1297
+ return this;
1298
+ }
1285
1299
  createDefaultDoorLockClusterServer(lockState = DoorLock.LockState.Locked, lockType = DoorLock.LockType.DeadBolt, autoRelockTime = 0, numberOfWeekDaySchedulesSupportedPerUser, numberOfYearDaySchedulesSupportedPerUser, numberOfHolidaySchedulesSupported) {
1286
1300
  this.behaviors.require(MatterbridgeDoorLockServer.with(...(numberOfWeekDaySchedulesSupportedPerUser !== undefined ? [DoorLock.Feature.WeekDayAccessSchedules] : []), ...(numberOfYearDaySchedulesSupportedPerUser !== undefined ? [DoorLock.Feature.YearDayAccessSchedules] : []), ...(numberOfHolidaySchedulesSupported !== undefined ? [DoorLock.Feature.HolidaySchedules] : [])).enable({
1287
1301
  events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
@@ -37,6 +37,7 @@ import type { TimeSynchronization } from '@matter/types/clusters/time-synchroniz
37
37
  import type { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
38
38
  import type { WaterHeaterManagement } from '@matter/types/clusters/water-heater-management';
39
39
  import type { WaterHeaterMode } from '@matter/types/clusters/water-heater-mode';
40
+ import type { WaterTankLevelMonitoring } from '@matter/types/clusters/water-tank-level-monitoring';
40
41
  import type { WindowCovering } from '@matter/types/clusters/window-covering';
41
42
  import type { ClosureControl } from './clusters/closure-control.js';
42
43
  import type { ClosureDimension } from './clusters/closure-dimension.js';
@@ -991,6 +992,13 @@ export type CommandHandlerDataMap = {
991
992
  attributes: ClusterAttributeValues<(typeof ActivatedCarbonFilterMonitoring)['attributes']>;
992
993
  endpoint: MatterbridgeEndpoint;
993
994
  };
995
+ 'WaterTankLevelMonitoring.resetCondition': {
996
+ command: 'resetCondition';
997
+ request: {};
998
+ cluster: 'waterTankLevelMonitoring';
999
+ attributes: ClusterAttributeValues<(typeof WaterTankLevelMonitoring)['attributes']>;
1000
+ endpoint: MatterbridgeEndpoint;
1001
+ };
994
1002
  'CommodityPrice.getDetailedPriceRequest': {
995
1003
  command: 'getDetailedPriceRequest';
996
1004
  request: CommodityPrice.GetDetailedPriceRequest;
@@ -48,6 +48,7 @@ import { ThermostatClient } from '@matter/node/behaviors/thermostat';
48
48
  import { ThermostatUserInterfaceConfigurationServer } from '@matter/node/behaviors/thermostat-user-interface-configuration';
49
49
  import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/node/behaviors/total-volatile-organic-compounds-concentration-measurement';
50
50
  import { UserLabelServer } from '@matter/node/behaviors/user-label';
51
+ import { WaterTankLevelMonitoringServer } from '@matter/node/behaviors/water-tank-level-monitoring';
51
52
  import { WindowCoveringClient } from '@matter/node/behaviors/window-covering';
52
53
  import { getClusterNameById } from '@matter/types/cluster';
53
54
  import { ActivatedCarbonFilterMonitoring } from '@matter/types/clusters/activated-carbon-filter-monitoring';
@@ -104,6 +105,7 @@ import { ThermostatUserInterfaceConfiguration } from '@matter/types/clusters/the
104
105
  import { TotalVolatileOrganicCompoundsConcentrationMeasurement } from '@matter/types/clusters/total-volatile-organic-compounds-concentration-measurement';
105
106
  import { UserLabel } from '@matter/types/clusters/user-label';
106
107
  import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
108
+ import { WaterTankLevelMonitoring } from '@matter/types/clusters/water-tank-level-monitoring';
107
109
  import { WindowCovering } from '@matter/types/clusters/window-covering';
108
110
  import { NodeId } from '@matter/types/datatype';
109
111
  import { MeasurementType } from '@matter/types/globals';
@@ -331,6 +333,8 @@ export function getBehaviourTypeFromClusterServerId(clusterId) {
331
333
  return HepaFilterMonitoringServer.with('Condition', 'Warning', 'ReplacementProductList');
332
334
  if (clusterId === ActivatedCarbonFilterMonitoring.id)
333
335
  return ActivatedCarbonFilterMonitoringServer.with('Condition', 'Warning', 'ReplacementProductList');
336
+ if (clusterId === WaterTankLevelMonitoring.id)
337
+ return WaterTankLevelMonitoringServer.with('Condition', 'Warning', 'ReplacementProductList');
334
338
  if (clusterId === CarbonMonoxideConcentrationMeasurement.id)
335
339
  return CarbonMonoxideConcentrationMeasurementServer.with('NumericMeasurement');
336
340
  if (clusterId === CarbonDioxideConcentrationMeasurement.id)
@@ -439,6 +443,11 @@ export async function invokeBehaviorCommand(endpoint, cluster, command, params)
439
443
  return;
440
444
  }
441
445
  void behavior?.['state'];
446
+ if (endpoint.behaviors.supported[behaviorId]?.lockOnInvoke) {
447
+ const transaction = agent.context.transaction;
448
+ await transaction.addResources(behavior);
449
+ await transaction.begin();
450
+ }
442
451
  const injectedSubject = { kind: 'node', id: NodeId(100) };
443
452
  const patchedContext = new Proxy(agent.context, { get: (t, k) => (k === 'fabric' ? 1 : k === 'subject' ? injectedSubject : Reflect.get(t, k, t)) });
444
453
  Object.defineProperty(behavior, 'context', { configurable: true, value: patchedContext });
@@ -564,6 +573,8 @@ export function addClusterServers(endpoint, serverList) {
564
573
  endpoint.createDefaultHepaFilterMonitoringClusterServer();
565
574
  if (serverList.includes(ActivatedCarbonFilterMonitoring.id))
566
575
  endpoint.createDefaultActivatedCarbonFilterMonitoringClusterServer();
576
+ if (serverList.includes(WaterTankLevelMonitoring.id))
577
+ endpoint.createDefaultWaterTankLevelMonitoringClusterServer();
567
578
  if (serverList.includes(CarbonMonoxideConcentrationMeasurement.id))
568
579
  endpoint.createDefaultCarbonMonoxideConcentrationMeasurementClusterServer();
569
580
  if (serverList.includes(CarbonDioxideConcentrationMeasurement.id))
@@ -46,12 +46,15 @@ export async function getServerBehaviorFromClusterId(clusterId, features) {
46
46
  const base = mod[`${name}Server`];
47
47
  if (!base || !ClusterBehavior.isType(base) || isClientBehavior(base))
48
48
  return undefined;
49
+ const knownFeatureNames = new Set(Object.keys(base.cluster.features ?? {}).map((key) => pascalCase(key)));
49
50
  const featureNames = (Array.isArray(features)
50
51
  ? features
51
52
  : Object.entries(features ?? {})
52
53
  .filter(([, enabled]) => enabled)
53
- .map(([key]) => key)).map((key) => pascalCase(key));
54
- return featureNames.length > 0 ? base.with(...featureNames) : base;
54
+ .map(([key]) => key))
55
+ .map((key) => pascalCase(key))
56
+ .filter((key) => knownFeatureNames.has(key));
57
+ return features === undefined ? base : base.with(...featureNames);
55
58
  }
56
59
  export async function getClientBehaviorFromClusterId(clusterId) {
57
60
  const name = getClusterNameById(clusterId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.8-dev-20260901-971f7bf",
3
+ "version": "3.10.8-dev-20260903-4ab4a0f",
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-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",
135
+ "@matterbridge/dgram": "3.10.8-dev-20260903-4ab4a0f",
136
+ "@matterbridge/thread": "3.10.8-dev-20260903-4ab4a0f",
137
+ "@matterbridge/types": "3.10.8-dev-20260903-4ab4a0f",
138
+ "@matterbridge/utils": "3.10.8-dev-20260903-4ab4a0f",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.7.0",