@matterbridge/core 3.10.5-dev-20260812-3e239dd → 3.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
2
2
  import { ClosureControl } from '@matter/types/clusters/closure-control';
3
+ import { Identify } from '@matter/types/clusters/identify';
3
4
  import type { Semtag } from '@matter/types/globals';
4
5
  import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
5
6
  import { type ClosureDimensionType, type ClosurePanelOptions } from './closurePanel.js';
@@ -19,6 +20,9 @@ export declare class MatterbridgeClosureControlServer extends MatterbridgeClosur
19
20
  stop: () => Promise<void>;
20
21
  }
21
22
  export interface ClosureOptions {
23
+ identifyTime?: number;
24
+ identifyType?: Identify.IdentifyType;
25
+ powerSourceType?: 'Rechargeable' | 'Replaceable' | 'Battery' | 'Wired' | 'None';
22
26
  countdownTime?: number;
23
27
  mainState?: ClosureControl.MainState;
24
28
  currentErrorList?: ClosureControl.ClosureError[];
@@ -30,6 +34,13 @@ export interface ClosureOptions {
30
34
  export declare class Closure extends MatterbridgeEndpoint {
31
35
  constructor(name: string, serial: string, options?: ClosureOptions);
32
36
  getMainState(): ClosureControl.MainState | undefined;
33
- addPanel(name: string, tagList: Semtag[], dimensionType: ClosureDimensionType, options?: Omit<ClosurePanelOptions, 'tagList'>): MatterbridgeEndpoint;
37
+ setState(currentState: ClosureControl.OverallCurrentState, targetState: ClosureControl.OverallTargetState, mainState?: ClosureControl.MainState, countdownTime?: number, currentErrorList?: ClosureControl.ClosureError[]): Promise<void>;
38
+ setFullyClosed(): Promise<void>;
39
+ setFullOpened(): Promise<void>;
40
+ setPartiallyOpened(): Promise<void>;
41
+ triggerOperationalError(errorState?: ClosureControl.ClosureError[]): Promise<void>;
42
+ triggerMovementCompleted(): Promise<void>;
43
+ triggerSecureStateChanged(secureValue: boolean): Promise<void>;
44
+ addPanel(name: string, tagList: Semtag[], dimensionType: ClosureDimensionType, options?: ClosurePanelOptions): MatterbridgeEndpoint;
34
45
  }
35
46
  export {};
@@ -1,9 +1,13 @@
1
+ import { ClosureTag } from '@matter/node';
1
2
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
3
+ import { StatusResponse } from '@matter/types';
2
4
  import { ClosureControl } from '@matter/types/clusters/closure-control';
5
+ import { Identify } from '@matter/types/clusters/identify';
3
6
  import { ThreeLevelAuto } from '@matter/types/globals';
4
7
  import { MatterbridgeServer } from '../behaviors/matterbridgeServer.js';
5
- import { closure, closurePanel } from '../matterbridgeDeviceTypes.js';
8
+ import { closure, closurePanel, powerSource } from '../matterbridgeDeviceTypes.js';
6
9
  import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
10
+ import { getSemtag } from '../matterbridgeEndpointHelpers.js';
7
11
  import { createClosureDimensionClusterServer } from './closurePanel.js';
8
12
  export class MatterbridgeClosureControlServer extends ClosureControlServer.with(ClosureControl.Feature.Positioning, ClosureControl.Feature.MotionLatching, ClosureControl.Feature.Speed) {
9
13
  moveTo = async (request) => {
@@ -16,6 +20,16 @@ export class MatterbridgeClosureControlServer extends ClosureControlServer.with(
16
20
  attributes: this.state,
17
21
  endpoint: this.endpoint,
18
22
  });
23
+ if (request.position === undefined && request.latch === undefined && request.speed === undefined) {
24
+ throw new StatusResponse.InvalidCommandError('ClosureControl.moveTo requires at least one of position, latch, or speed to be present');
25
+ }
26
+ if (![ClosureControl.MainState.Moving, ClosureControl.MainState.WaitingForMotion, ClosureControl.MainState.Stopped].includes(this.state.mainState)) {
27
+ throw new StatusResponse.InvalidInStateError('ClosureControl.moveTo is only allowed while Moving, WaitingForMotion, or Stopped');
28
+ }
29
+ let currentState = this.state.overallCurrentState;
30
+ if (currentState?.latch === true && request.position !== undefined && request.latch !== false) {
31
+ throw new StatusResponse.InvalidInStateError('ClosureControl.moveTo position changes require latch false while the closure is latched');
32
+ }
19
33
  const previousTarget = this.state.overallTargetState ?? {};
20
34
  const nextTarget = {
21
35
  ...previousTarget,
@@ -24,7 +38,10 @@ export class MatterbridgeClosureControlServer extends ClosureControlServer.with(
24
38
  speed: request?.speed ?? ThreeLevelAuto.Auto,
25
39
  };
26
40
  this.state.overallTargetState = nextTarget;
27
- const currentState = this.state.overallCurrentState;
41
+ if (currentState !== null) {
42
+ currentState = { ...currentState, speed: nextTarget.speed };
43
+ this.state.overallCurrentState = currentState;
44
+ }
28
45
  const isAtTarget = currentState !== null &&
29
46
  (nextTarget.position === undefined || nextTarget.position === currentState.position) &&
30
47
  (nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
@@ -41,35 +58,111 @@ export class MatterbridgeClosureControlServer extends ClosureControlServer.with(
41
58
  attributes: this.state,
42
59
  endpoint: this.endpoint,
43
60
  });
44
- this.state.mainState = ClosureControl.MainState.Stopped;
61
+ if ([ClosureControl.MainState.Moving, ClosureControl.MainState.WaitingForMotion, ClosureControl.MainState.Calibrating].includes(this.state.mainState)) {
62
+ this.state.mainState = ClosureControl.MainState.Stopped;
63
+ }
45
64
  };
46
65
  }
47
66
  export class Closure extends MatterbridgeEndpoint {
48
67
  constructor(name, serial, options = {}) {
49
- super([closure], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`, tagList: options.tagList });
50
- this.createDefaultIdentifyClusterServer();
68
+ const { identifyTime = 0, identifyType = Identify.IdentifyType.None, powerSourceType = 'Wired', countdownTime = 0, mainState = ClosureControl.MainState.Stopped, currentErrorList = [], overallCurrentState = {
69
+ position: ClosureControl.CurrentPosition.FullyClosed,
70
+ latch: true,
71
+ speed: ThreeLevelAuto.Auto,
72
+ secureState: true,
73
+ }, overallTargetState = {
74
+ position: ClosureControl.TargetPosition.MoveToFullyClosed,
75
+ latch: true,
76
+ speed: ThreeLevelAuto.Auto,
77
+ }, latchControlModes = { remoteLatching: true, remoteUnlatching: true }, tagList = [getSemtag(ClosureTag.Covering)], } = options;
78
+ super(powerSourceType === 'None' ? [closure] : [closure, powerSource], {
79
+ id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
80
+ tagList,
81
+ });
82
+ this.createDefaultIdentifyClusterServer(identifyTime, identifyType);
51
83
  this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Closure');
84
+ switch (powerSourceType) {
85
+ case 'Rechargeable':
86
+ this.createDefaultPowerSourceRechargeableBatteryClusterServer();
87
+ break;
88
+ case 'Replaceable':
89
+ this.createDefaultPowerSourceReplaceableBatteryClusterServer();
90
+ break;
91
+ case 'Battery':
92
+ this.createDefaultPowerSourceBatteryClusterServer();
93
+ break;
94
+ case 'Wired':
95
+ this.createDefaultPowerSourceWiredClusterServer();
96
+ break;
97
+ case 'None':
98
+ break;
99
+ }
52
100
  this.behaviors.require(MatterbridgeClosureControlServer, {
53
- countdownTime: options.countdownTime ?? 0,
54
- mainState: options.mainState ?? ClosureControl.MainState.Stopped,
55
- currentErrorList: [],
56
- overallCurrentState: options.overallCurrentState ?? {
57
- position: ClosureControl.CurrentPosition.FullyClosed,
58
- latch: true,
59
- speed: ThreeLevelAuto.Auto,
60
- secureState: true,
61
- },
62
- overallTargetState: options.overallTargetState ?? {
63
- position: ClosureControl.TargetPosition.MoveToFullyClosed,
64
- latch: true,
65
- speed: ThreeLevelAuto.Auto,
66
- },
67
- latchControlModes: options.latchControlModes ?? { remoteLatching: true, remoteUnlatching: true },
101
+ countdownTime,
102
+ mainState,
103
+ currentErrorList,
104
+ overallCurrentState,
105
+ overallTargetState,
106
+ latchControlModes,
68
107
  });
69
108
  }
70
109
  getMainState() {
71
110
  return this.getAttribute(ClosureControlServer, 'mainState');
72
111
  }
112
+ async setState(currentState, targetState, mainState = ClosureControl.MainState.Stopped, countdownTime = 0, currentErrorList = []) {
113
+ await this.setAttribute(ClosureControl, 'countdownTime', countdownTime);
114
+ await this.setAttribute(ClosureControl, 'mainState', mainState);
115
+ await this.setAttribute(ClosureControl, 'currentErrorList', currentErrorList);
116
+ await this.setAttribute(ClosureControl, 'overallCurrentState', currentState);
117
+ await this.setAttribute(ClosureControl, 'overallTargetState', targetState);
118
+ }
119
+ async setFullyClosed() {
120
+ await this.setState({
121
+ position: ClosureControl.CurrentPosition.FullyClosed,
122
+ latch: true,
123
+ speed: ThreeLevelAuto.Auto,
124
+ secureState: true,
125
+ }, {
126
+ position: ClosureControl.TargetPosition.MoveToFullyClosed,
127
+ latch: true,
128
+ speed: ThreeLevelAuto.Auto,
129
+ });
130
+ }
131
+ async setFullOpened() {
132
+ await this.setState({
133
+ position: ClosureControl.CurrentPosition.FullyOpened,
134
+ latch: false,
135
+ speed: ThreeLevelAuto.Auto,
136
+ secureState: false,
137
+ }, {
138
+ position: ClosureControl.TargetPosition.MoveToFullyOpen,
139
+ latch: false,
140
+ speed: ThreeLevelAuto.Auto,
141
+ });
142
+ }
143
+ async setPartiallyOpened() {
144
+ await this.setState({
145
+ position: ClosureControl.CurrentPosition.PartiallyOpened,
146
+ latch: false,
147
+ speed: ThreeLevelAuto.Auto,
148
+ secureState: false,
149
+ }, {
150
+ position: null,
151
+ latch: false,
152
+ speed: ThreeLevelAuto.Auto,
153
+ });
154
+ }
155
+ async triggerOperationalError(errorState = []) {
156
+ await this.setAttribute(ClosureControl, 'mainState', ClosureControl.MainState.Error);
157
+ await this.setAttribute(ClosureControl, 'currentErrorList', errorState);
158
+ await this.triggerEvent(ClosureControl, 'operationalError', { errorState });
159
+ }
160
+ async triggerMovementCompleted() {
161
+ await this.triggerEvent(ClosureControl, 'movementCompleted', undefined);
162
+ }
163
+ async triggerSecureStateChanged(secureValue) {
164
+ await this.triggerEvent(ClosureControl, 'secureStateChanged', { secureValue });
165
+ }
73
166
  addPanel(name, tagList, dimensionType, options = {}) {
74
167
  const panel = this.addChildDeviceType(name, closurePanel, { tagList });
75
168
  createClosureDimensionClusterServer(panel, dimensionType, options);
@@ -1,7 +1,6 @@
1
1
  import { ClosureDimensionServer } from '@matter/node/behaviors/closure-dimension';
2
2
  import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
3
- import type { Semtag } from '@matter/types/globals';
4
- import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
3
+ import type { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
5
4
  export type ClosureDimensionType = 'lift' | 'tilt' | 'modulation';
6
5
  declare const MatterbridgeClosureDimensionServer_base: import("@matter/node").ClusterBehavior.Type<typeof ClosureDimensionServer, import("@matter/types").ClusterType.WithSupportedFeatures<ClosureDimension, {
7
6
  limitation: false;
@@ -27,10 +26,6 @@ export interface ClosurePanelOptions {
27
26
  rotationAxis?: ClosureDimension.RotationAxis;
28
27
  overflow?: ClosureDimension.Overflow;
29
28
  modulationType?: ClosureDimension.ModulationType;
30
- tagList?: Semtag[];
31
- }
32
- export declare class ClosurePanel extends MatterbridgeEndpoint {
33
- constructor(name: string, serial: string, dimensionType: ClosureDimensionType, options?: ClosurePanelOptions);
34
29
  }
35
30
  export declare function createClosureDimensionClusterServer(endpoint: MatterbridgeEndpoint, dimensionType: ClosureDimensionType, options: ClosurePanelOptions): MatterbridgeEndpoint;
36
31
  export {};
@@ -1,9 +1,10 @@
1
+ import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
1
2
  import { ClosureDimensionServer } from '@matter/node/behaviors/closure-dimension';
3
+ import { StatusResponse } from '@matter/types';
4
+ import { ClosureControl } from '@matter/types/clusters/closure-control';
2
5
  import { ClosureDimension } from '@matter/types/clusters/closure-dimension';
3
6
  import { ThreeLevelAuto } from '@matter/types/globals';
4
7
  import { MatterbridgeServer } from '../behaviors/matterbridgeServer.js';
5
- import { closurePanel } from '../matterbridgeDeviceTypes.js';
6
- import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
7
8
  export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.with(ClosureDimension.Feature.Positioning, ClosureDimension.Feature.MotionLatching, ClosureDimension.Feature.Speed) {
8
9
  setTarget = async (request) => {
9
10
  const device = this.endpoint.stateOf(MatterbridgeServer);
@@ -15,13 +16,48 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
15
16
  attributes: this.state,
16
17
  endpoint: this.endpoint,
17
18
  });
19
+ if (request.position === undefined && request.latch === undefined && request.speed === undefined) {
20
+ throw new StatusResponse.InvalidCommandError('ClosureDimension.setTarget requires at least one of position, latch, or speed to be present');
21
+ }
22
+ if (request.position !== undefined && (request.position < 0 || request.position > 10000)) {
23
+ throw new StatusResponse.ConstraintErrorError('ClosureDimension.setTarget position must be between 0 and 10000');
24
+ }
25
+ const latchControlModes = this.state.latchControlModes;
26
+ if (request.latch !== undefined && ((request.latch && !latchControlModes?.remoteLatching) || (!request.latch && !latchControlModes?.remoteUnlatching))) {
27
+ throw new StatusResponse.InvalidInStateError('ClosureDimension.setTarget latch change requires manual intervention per LatchControlModes');
28
+ }
29
+ if (request.speed !== undefined && (request.speed < ThreeLevelAuto.Auto || request.speed > ThreeLevelAuto.High)) {
30
+ throw new StatusResponse.ConstraintErrorError('ClosureDimension.setTarget speed must be a valid ThreeLevelAutoEnum value');
31
+ }
32
+ const associatedMainState = this.endpoint.owner?.maybeStateOf(ClosureControlServer)?.mainState;
33
+ if (associatedMainState !== undefined &&
34
+ [
35
+ ClosureControl.MainState.Disengaged,
36
+ ClosureControl.MainState.Protected,
37
+ ClosureControl.MainState.Calibrating,
38
+ ClosureControl.MainState.SetupRequired,
39
+ ClosureControl.MainState.Error,
40
+ ].includes(associatedMainState)) {
41
+ throw new StatusResponse.InvalidInStateError('ClosureDimension.setTarget is not allowed while the associated ClosureControl is Disengaged, Protected, Calibrating, SetupRequired, or Error');
42
+ }
43
+ const currentState = this.state.currentState;
44
+ if (request.position !== undefined && currentState?.latch === true && request.latch !== false) {
45
+ throw new StatusResponse.InvalidInStateError('ClosureDimension.setTarget position changes require latch false while the closure is latched');
46
+ }
18
47
  const previousTarget = this.state.targetState ?? {};
48
+ const resolution = this.state.resolution;
19
49
  const nextTarget = {
20
50
  ...previousTarget,
21
- ...(request?.position !== undefined ? { position: request.position } : null),
51
+ ...(request?.position !== undefined ? { position: Math.round(request.position / resolution) * resolution } : null),
22
52
  ...(request?.latch !== undefined ? { latch: request.latch } : null),
23
53
  speed: request?.speed ?? ThreeLevelAuto.Auto,
24
54
  };
55
+ const matchesCurrentState = currentState !== null &&
56
+ (nextTarget.position === undefined || nextTarget.position === currentState.position) &&
57
+ (nextTarget.latch === undefined || nextTarget.latch === currentState.latch) &&
58
+ nextTarget.speed === currentState.speed;
59
+ if (matchesCurrentState)
60
+ return;
25
61
  this.state.targetState = nextTarget;
26
62
  };
27
63
  step = async (request) => {
@@ -34,37 +70,51 @@ export class MatterbridgeClosureDimensionServer extends ClosureDimensionServer.w
34
70
  attributes: this.state,
35
71
  endpoint: this.endpoint,
36
72
  });
73
+ if (request.direction < ClosureDimension.StepDirection.Decrease || request.direction > ClosureDimension.StepDirection.Increase) {
74
+ throw new StatusResponse.ConstraintErrorError('ClosureDimension.step direction must be a valid StepDirectionEnum value');
75
+ }
76
+ if (request.numberOfSteps < 1) {
77
+ throw new StatusResponse.ConstraintErrorError('ClosureDimension.step numberOfSteps must be at least 1');
78
+ }
79
+ if (request.speed !== undefined && (request.speed < ThreeLevelAuto.Auto || request.speed > ThreeLevelAuto.High)) {
80
+ throw new StatusResponse.ConstraintErrorError('ClosureDimension.step speed must be a valid ThreeLevelAutoEnum value');
81
+ }
82
+ const currentState = this.state.currentState;
83
+ if (currentState?.latch === true) {
84
+ throw new StatusResponse.InvalidInStateError('ClosureDimension.step is not allowed while the closure is latched');
85
+ }
86
+ const associatedMainState = this.endpoint.owner?.maybeStateOf(ClosureControlServer)?.mainState;
87
+ if (associatedMainState !== undefined &&
88
+ [
89
+ ClosureControl.MainState.Disengaged,
90
+ ClosureControl.MainState.Protected,
91
+ ClosureControl.MainState.Calibrating,
92
+ ClosureControl.MainState.SetupRequired,
93
+ ClosureControl.MainState.Error,
94
+ ].includes(associatedMainState)) {
95
+ throw new StatusResponse.InvalidInStateError('ClosureDimension.step is not allowed while the associated ClosureControl is Disengaged, Protected, Calibrating, SetupRequired, or Error');
96
+ }
37
97
  const stepValue = this.state.stepValue;
38
98
  const numberOfSteps = request.numberOfSteps;
39
99
  const delta = stepValue * numberOfSteps;
40
100
  const isIncrease = request.direction === ClosureDimension.StepDirection.Increase;
41
- const previousCurrent = this.state.currentState ?? {};
42
- const previousTarget = this.state.targetState ?? {};
43
- const currentPosition = typeof previousCurrent.position === 'number' ? previousCurrent.position : typeof previousTarget.position === 'number' ? previousTarget.position : 0;
101
+ const currentPosition = typeof currentState?.position === 'number' ? currentState.position : 0;
44
102
  let nextPosition = isIncrease ? currentPosition + delta : currentPosition - delta;
45
103
  nextPosition = Math.max(0, Math.min(10000, nextPosition));
46
- const speed = request?.speed ?? previousCurrent.speed ?? previousTarget.speed ?? ThreeLevelAuto.Auto;
47
- this.state.currentState = {
48
- ...previousCurrent,
104
+ const previousTarget = this.state.targetState ?? {};
105
+ this.state.targetState = {
106
+ ...previousTarget,
49
107
  position: nextPosition,
50
- speed,
108
+ ...(request.speed !== undefined ? { speed: request.speed } : null),
51
109
  };
52
- this.state.targetState = { ...previousTarget, position: nextPosition, speed };
53
110
  };
54
111
  }
55
- export class ClosurePanel extends MatterbridgeEndpoint {
56
- constructor(name, serial, dimensionType, options = {}) {
57
- super([closurePanel], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`, tagList: options.tagList });
58
- this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Closure Panel');
59
- createClosureDimensionClusterServer(this, dimensionType, options);
60
- }
61
- }
62
112
  export function createClosureDimensionClusterServer(endpoint, dimensionType, options) {
63
113
  const commonOptions = {
64
114
  currentState: options.currentState ?? { position: 0, latch: true, speed: ThreeLevelAuto.Auto },
65
115
  targetState: options.targetState ?? { position: 0, latch: true, speed: ThreeLevelAuto.Auto },
66
- resolution: options.resolution ?? 1,
67
- stepValue: options.stepValue ?? 1,
116
+ resolution: Math.max(1, options.resolution ?? 1),
117
+ stepValue: Math.max(1, options.stepValue ?? 1),
68
118
  latchControlModes: options.latchControlModes ?? { remoteLatching: true, remoteUnlatching: true },
69
119
  };
70
120
  if (dimensionType === 'lift') {
@@ -993,7 +993,7 @@ export class MatterbridgeEndpoint extends Endpoint {
993
993
  ...(occupied !== undefined ? { occupancy: { occupied } } : {}),
994
994
  ...(occupied !== undefined ? { externallyMeasuredOccupancy: true } : {}),
995
995
  numberOfPresets: Math.max(Array.isArray(presets) ? presets.length : 0, Array.isArray(presetTypes) ? Math.max(0, ...presetTypes.map((pt) => pt.numberOfPresets ?? 0)) : 0, 1),
996
- activePresetHandle: activePresetHandle ? Uint8Array.from([activePresetHandle]) : null,
996
+ activePresetHandle: activePresetHandle ? Uint8Array.from(activePresetHandle) : null,
997
997
  presets: (presets ?? []).map((p) => ({
998
998
  presetHandle: p.presetHandle ? Uint8Array.from(p.presetHandle) : null,
999
999
  presetScenario: p.presetScenario,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.5-dev-20260812-3e239dd",
3
+ "version": "3.10.5",
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.5-dev-20260812-3e239dd",
136
- "@matterbridge/thread": "3.10.5-dev-20260812-3e239dd",
137
- "@matterbridge/types": "3.10.5-dev-20260812-3e239dd",
138
- "@matterbridge/utils": "3.10.5-dev-20260812-3e239dd",
135
+ "@matterbridge/dgram": "3.10.5",
136
+ "@matterbridge/thread": "3.10.5",
137
+ "@matterbridge/types": "3.10.5",
138
+ "@matterbridge/utils": "3.10.5",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.6.2",