@matterbridge/core 3.10.6-dev-20260818-c2c6349 → 3.10.6-dev-20260820-36b52be

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import type { MaybePromise } from '@matter/general';
1
2
  import { FanControlServer } from '@matter/node/behaviors/fan-control';
2
3
  import { FanControl } from '@matter/types/clusters/fan-control';
3
4
  declare const MatterbridgeFanControlServer_base: import("@matter/node").ClusterBehavior.Type<typeof FanControlServer, import("@matter/types").ClusterType.WithSupportedFeatures<FanControl, {
@@ -9,6 +10,8 @@ declare const MatterbridgeFanControlServer_base: import("@matter/node").ClusterB
9
10
  wind: false;
10
11
  }>, import("@matter/types").ClusterType.Concrete, new () => {}, "fanControl">;
11
12
  export declare class MatterbridgeFanControlServer extends MatterbridgeFanControlServer_base {
13
+ #private;
14
+ initialize(): MaybePromise;
12
15
  step(request: FanControl.StepRequest): Promise<void>;
13
16
  }
14
17
  export {};
@@ -1,10 +1,161 @@
1
1
  import { FanControlServer } from '@matter/node/behaviors/fan-control';
2
+ import { Status, StatusResponseError } from '@matter/types';
2
3
  import { FanControl } from '@matter/types/clusters/fan-control';
4
+ import { getEnumDescription } from '@matterbridge/utils/enum';
3
5
  import { MatterbridgeServer } from './matterbridgeServer.js';
6
+ const lookupStepDirection = ['Increase', 'Decrease'];
7
+ const lookupFanMode = ['Off', 'Low', 'Medium', 'High', 'On', 'Auto', 'Auto', 'Smart'];
8
+ const supportedFanModesBySequence = {
9
+ [FanControl.FanModeSequence.OffLowMedHigh]: new Set([FanControl.FanMode.Off, FanControl.FanMode.Low, FanControl.FanMode.Medium, FanControl.FanMode.High]),
10
+ [FanControl.FanModeSequence.OffLowHigh]: new Set([FanControl.FanMode.Off, FanControl.FanMode.Low, FanControl.FanMode.High]),
11
+ [FanControl.FanModeSequence.OffLowMedHighAuto]: new Set([
12
+ FanControl.FanMode.Off,
13
+ FanControl.FanMode.Low,
14
+ FanControl.FanMode.Medium,
15
+ FanControl.FanMode.High,
16
+ FanControl.FanMode.Auto,
17
+ ]),
18
+ [FanControl.FanModeSequence.OffLowHighAuto]: new Set([FanControl.FanMode.Off, FanControl.FanMode.Low, FanControl.FanMode.High, FanControl.FanMode.Auto]),
19
+ [FanControl.FanModeSequence.OffHighAuto]: new Set([FanControl.FanMode.Off, FanControl.FanMode.High, FanControl.FanMode.Auto]),
20
+ [FanControl.FanModeSequence.OffHigh]: new Set([FanControl.FanMode.Off, FanControl.FanMode.High]),
21
+ };
22
+ const steppedFanModes = [FanControl.FanMode.Low, FanControl.FanMode.Medium, FanControl.FanMode.High];
23
+ function computePercentRanges(sequence) {
24
+ const supported = supportedFanModesBySequence[sequence];
25
+ const modes = steppedFanModes.filter((fanMode) => supported.has(fanMode));
26
+ const ranges = [];
27
+ let start = 1;
28
+ modes.forEach((fanMode, index) => {
29
+ const end = Math.floor((100 * (index + 1)) / modes.length);
30
+ ranges.push({ fanMode, start, end });
31
+ start = end + 1;
32
+ });
33
+ return ranges;
34
+ }
35
+ function percentToFanMode(percent, sequence) {
36
+ const ranges = computePercentRanges(sequence);
37
+ const range = ranges.find((candidate) => percent >= candidate.start && percent <= candidate.end);
38
+ return range?.fanMode ?? FanControl.FanMode.High;
39
+ }
40
+ function resolvePercentSettingForFanMode(fanMode, currentPercentSetting, sequence) {
41
+ if (fanMode === FanControl.FanMode.Off)
42
+ return 0;
43
+ const alreadyInRange = currentPercentSetting !== null && currentPercentSetting !== 0 && percentToFanMode(currentPercentSetting, sequence) === fanMode;
44
+ if (alreadyInRange)
45
+ return currentPercentSetting;
46
+ const range = computePercentRanges(sequence).find((candidate) => candidate.fanMode === fanMode);
47
+ return range ? Math.round((range.start + range.end) / 2) : (currentPercentSetting ?? 0);
48
+ }
49
+ function resolveStepPercentSettingForFanMode(fanMode, direction, currentPercentSetting, sequence) {
50
+ if (fanMode === FanControl.FanMode.Off)
51
+ return 0;
52
+ const alreadyInRange = currentPercentSetting !== null && currentPercentSetting !== 0 && percentToFanMode(currentPercentSetting, sequence) === fanMode;
53
+ if (alreadyInRange)
54
+ return currentPercentSetting;
55
+ const range = computePercentRanges(sequence).find((candidate) => candidate.fanMode === fanMode);
56
+ if (!range)
57
+ return currentPercentSetting ?? 0;
58
+ return direction === FanControl.StepDirection.Increase ? range.start : range.end;
59
+ }
60
+ function speedSettingForPercent(speedMax, percent) {
61
+ return Math.ceil((speedMax * percent) / 100);
62
+ }
4
63
  export class MatterbridgeFanControlServer extends FanControlServer.with(FanControl.Feature.Auto, FanControl.Feature.Step) {
64
+ initialize() {
65
+ const result = super.initialize();
66
+ this.reactTo(this.events.fanMode$Changing, this.#handleFanModeChanging);
67
+ this.reactTo(this.events.percentSetting$Changing, this.#handlePercentSettingChanging);
68
+ this.maybeReactTo(this.events.speedSetting$Changing, this.#handleSpeedSettingChanging);
69
+ return result;
70
+ }
71
+ #handleFanModeChanging(fanMode) {
72
+ const device = this.endpoint.stateOf(MatterbridgeServer);
73
+ if (fanMode === FanControl.FanMode.On) {
74
+ this.state.fanMode = FanControl.FanMode.High;
75
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> fanMode ${lookupFanMode[this.state.fanMode]} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
76
+ return;
77
+ }
78
+ if (fanMode === FanControl.FanMode.Smart) {
79
+ this.state.fanMode = this.features.auto ? FanControl.FanMode.Auto : FanControl.FanMode.High;
80
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> fanMode ${lookupFanMode[this.state.fanMode]} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
81
+ return;
82
+ }
83
+ if (!supportedFanModesBySequence[this.state.fanModeSequence].has(fanMode)) {
84
+ throw new StatusResponseError(`FanMode ${getEnumDescription(FanControl.FanMode, fanMode)} is not supported by FanModeSequence ${getEnumDescription(FanControl.FanModeSequence, this.state.fanModeSequence)}`, Status.ConstraintError);
85
+ }
86
+ if (fanMode === FanControl.FanMode.Off) {
87
+ this.state.percentSetting = 0;
88
+ if (this.features.multiSpeed) {
89
+ this.state.speedSetting = 0;
90
+ }
91
+ this.agent.asLocalActor(() => {
92
+ this.state.percentCurrent = 0;
93
+ if (this.features.multiSpeed) {
94
+ this.state.speedCurrent = 0;
95
+ }
96
+ });
97
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> percentSetting ${this.state.percentSetting} percentCurrent ${this.state.percentCurrent} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
98
+ if (this.features.multiSpeed) {
99
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> speedSetting ${this.state.speedSetting} speedCurrent ${this.state.speedCurrent} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
100
+ }
101
+ }
102
+ else if (fanMode === FanControl.FanMode.Auto) {
103
+ this.state.percentSetting = null;
104
+ if (this.features.multiSpeed) {
105
+ this.state.speedSetting = null;
106
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> speedSetting ${this.state.speedSetting} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
107
+ }
108
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> percentSetting ${this.state.percentSetting} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
109
+ }
110
+ else {
111
+ const resolved = resolvePercentSettingForFanMode(fanMode, this.state.percentSetting, this.state.fanModeSequence);
112
+ if (resolved !== this.state.percentSetting)
113
+ this.state.percentSetting = resolved;
114
+ device.log.debug(`handleFanModeChanging fanMode changed to ${lookupFanMode[fanMode]} >>> percentSetting ${this.state.percentSetting} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
115
+ }
116
+ }
117
+ #handlePercentSettingChanging(percentSetting, oldPercentSetting) {
118
+ const device = this.endpoint.stateOf(MatterbridgeServer);
119
+ if (percentSetting === null) {
120
+ if (this.state.fanMode === FanControl.FanMode.Auto)
121
+ return;
122
+ this.state.percentSetting = oldPercentSetting;
123
+ device.log.debug(`handlePercentSettingChanging percentSetting changed to ${percentSetting} >>> fanMode ${lookupFanMode[this.state.fanMode]} percentSetting ${this.state.percentSetting} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
124
+ return;
125
+ }
126
+ const fanMode = percentSetting === 0 ? FanControl.FanMode.Off : percentToFanMode(percentSetting, this.state.fanModeSequence);
127
+ if (fanMode !== this.state.fanMode)
128
+ this.state.fanMode = fanMode;
129
+ if (this.features.multiSpeed) {
130
+ const state = this.state;
131
+ state.speedSetting = speedSettingForPercent(state.speedMax, percentSetting);
132
+ }
133
+ device.log.debug(`handlePercentSettingChanging percentSetting changed to ${percentSetting} >>> fanMode ${lookupFanMode[this.state.fanMode]} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
134
+ }
135
+ #handleSpeedSettingChanging(speedSetting, oldSpeedSetting) {
136
+ const device = this.endpoint.stateOf(MatterbridgeServer);
137
+ const state = this.state;
138
+ if (speedSetting === null) {
139
+ if (this.state.fanMode === FanControl.FanMode.Auto)
140
+ return;
141
+ state.speedSetting = oldSpeedSetting;
142
+ device.log.debug(`handleSpeedSettingChanging speedSetting changed to ${speedSetting} >>> fanMode ${lookupFanMode[this.state.fanMode]} speedSetting ${state.speedSetting} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
143
+ return;
144
+ }
145
+ const { percentSetting } = this.state;
146
+ if (percentSetting !== null && speedSettingForPercent(state.speedMax, percentSetting) === speedSetting)
147
+ return;
148
+ const percent = speedSetting === 0 ? 0 : Math.min(100, Math.round((speedSetting / state.speedMax) * 100));
149
+ const fanMode = percent === 0 ? FanControl.FanMode.Off : percentToFanMode(percent, this.state.fanModeSequence);
150
+ if (fanMode !== this.state.fanMode)
151
+ this.state.fanMode = fanMode;
152
+ if (percent !== this.state.percentSetting)
153
+ this.state.percentSetting = percent;
154
+ device.log.debug(`handleSpeedSettingChanging speedSetting changed to ${speedSetting} >>> fanMode ${lookupFanMode[this.state.fanMode]} percentSetting ${this.state.percentSetting} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
155
+ }
5
156
  async step(request) {
6
157
  const device = this.endpoint.stateOf(MatterbridgeServer);
7
- device.log.info(`Stepping fan with direction ${request.direction} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
158
+ device.log.info(`Stepping fan with direction ${lookupStepDirection[request.direction]} wrap: ${request.wrap} lowestOff: ${request.lowestOff} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
8
159
  await device.commandHandler.executeHandler('FanControl.step', {
9
160
  command: 'step',
10
161
  request,
@@ -13,23 +164,36 @@ export class MatterbridgeFanControlServer extends FanControlServer.with(FanContr
13
164
  endpoint: this.endpoint,
14
165
  context: this.context,
15
166
  });
16
- const lookupStepDirection = ['Increase', 'Decrease'];
17
- device.log.debug(`MatterbridgeFanControlServer: step called with direction: ${lookupStepDirection[request.direction]} wrap: ${request.wrap} lowestOff: ${request.lowestOff}`);
18
- device.log.debug(`- current percentCurrent: ${this.state.percentCurrent}`);
167
+ const wrap = request.wrap ?? false;
168
+ const lowestOff = request.lowestOff ?? true;
169
+ const currentFanMode = this.state.fanMode;
170
+ const currentPercentSetting = this.state.percentSetting;
171
+ const fanModeSequence = this.state.fanModeSequence;
172
+ const supported = supportedFanModesBySequence[fanModeSequence];
173
+ const stepFanModes = [...(lowestOff ? [FanControl.FanMode.Off] : []), ...steppedFanModes.filter((fanMode) => supported.has(fanMode))];
174
+ let targetFanMode;
19
175
  if (request.direction === FanControl.StepDirection.Increase) {
20
- if (request.wrap && this.state.percentCurrent === 100) {
21
- this.state.percentCurrent = request.lowestOff ? 0 : 10;
22
- }
23
- else
24
- this.state.percentCurrent = Math.min(this.state.percentCurrent + 10, 100);
176
+ const higher = stepFanModes.filter((fanMode) => fanMode > currentFanMode);
177
+ targetFanMode = higher.length > 0 ? Math.min(...higher) : wrap ? Math.min(...stepFanModes) : Math.max(...stepFanModes);
25
178
  }
26
179
  else if (request.direction === FanControl.StepDirection.Decrease) {
27
- if (request.wrap && this.state.percentCurrent === (request.lowestOff ? 0 : 10)) {
28
- this.state.percentCurrent = 100;
29
- }
30
- else
31
- this.state.percentCurrent = Math.max(this.state.percentCurrent - 10, request.lowestOff ? 0 : 10);
180
+ const lower = stepFanModes.filter((fanMode) => fanMode < currentFanMode);
181
+ targetFanMode = lower.length > 0 ? Math.max(...lower) : wrap ? Math.max(...stepFanModes) : Math.min(...stepFanModes);
182
+ }
183
+ else {
184
+ return;
185
+ }
186
+ if (targetFanMode !== currentFanMode)
187
+ this.state.fanMode = targetFanMode;
188
+ const resolvedPercentSetting = resolveStepPercentSettingForFanMode(targetFanMode, request.direction, currentPercentSetting, fanModeSequence);
189
+ if (resolvedPercentSetting !== this.state.percentSetting)
190
+ this.state.percentSetting = resolvedPercentSetting;
191
+ this.state.percentCurrent = resolvedPercentSetting;
192
+ if (this.features.multiSpeed) {
193
+ const state = this.state;
194
+ state.speedCurrent = speedSettingForPercent(state.speedMax, resolvedPercentSetting);
32
195
  }
33
- device.log.debug('Set percentCurrent to:', this.state.percentCurrent);
196
+ device.log.debug(`MatterbridgeFanControlServer: step applied fanMode ${lookupFanMode[this.state.fanMode]}, percentCurrent ${this.state.percentCurrent}` +
197
+ (this.features.multiSpeed ? `, speedCurrent ${this.state.speedCurrent}` : ''));
34
198
  }
35
199
  }
@@ -5,84 +5,96 @@ export class MatterbridgeOnOffServer extends OnOffServer.with(OnOff.Feature.Ligh
5
5
  async on() {
6
6
  const device = this.endpoint.stateOf(MatterbridgeServer);
7
7
  device.log.info(`Switching device on (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
8
- await device.commandHandler.executeHandler('OnOff.on', {
9
- command: 'on',
10
- request: {},
11
- cluster: OnOffServer.id,
12
- attributes: this.state,
13
- endpoint: this.endpoint,
14
- context: this.context,
15
- });
8
+ if (!process.env.MATTERBRIDGE_CHIP_TEST) {
9
+ await device.commandHandler.executeHandler('OnOff.on', {
10
+ command: 'on',
11
+ request: {},
12
+ cluster: OnOffServer.id,
13
+ attributes: this.state,
14
+ endpoint: this.endpoint,
15
+ context: this.context,
16
+ });
17
+ }
16
18
  device.log.debug(`MatterbridgeOnOffServer: on called`);
17
19
  await super.on();
18
20
  }
19
21
  async off() {
20
22
  const device = this.endpoint.stateOf(MatterbridgeServer);
21
23
  device.log.info(`Switching device off (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
22
- await device.commandHandler.executeHandler('OnOff.off', {
23
- command: 'off',
24
- request: {},
25
- cluster: OnOffServer.id,
26
- attributes: this.state,
27
- endpoint: this.endpoint,
28
- context: this.context,
29
- });
24
+ if (!process.env.MATTERBRIDGE_CHIP_TEST) {
25
+ await device.commandHandler.executeHandler('OnOff.off', {
26
+ command: 'off',
27
+ request: {},
28
+ cluster: OnOffServer.id,
29
+ attributes: this.state,
30
+ endpoint: this.endpoint,
31
+ context: this.context,
32
+ });
33
+ }
30
34
  device.log.debug(`MatterbridgeOnOffServer: off called`);
31
35
  await super.off();
32
36
  }
33
37
  async toggle() {
34
38
  const device = this.endpoint.stateOf(MatterbridgeServer);
35
39
  device.log.info(`Toggle device on/off (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
36
- await device.commandHandler.executeHandler('OnOff.toggle', {
37
- command: 'toggle',
38
- request: {},
39
- cluster: OnOffServer.id,
40
- attributes: this.state,
41
- endpoint: this.endpoint,
42
- context: this.context,
43
- });
40
+ if (!process.env.MATTERBRIDGE_CHIP_TEST) {
41
+ await device.commandHandler.executeHandler('OnOff.toggle', {
42
+ command: 'toggle',
43
+ request: {},
44
+ cluster: OnOffServer.id,
45
+ attributes: this.state,
46
+ endpoint: this.endpoint,
47
+ context: this.context,
48
+ });
49
+ }
44
50
  device.log.debug(`MatterbridgeOnOffServer: toggle called`);
45
51
  await super.toggle();
46
52
  }
47
53
  async offWithEffect(request) {
48
54
  const device = this.endpoint.stateOf(MatterbridgeServer);
49
55
  device.log.info(`Switching device off with effect ${request.effectIdentifier} and variant ${request.effectVariant} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
50
- await device.commandHandler.executeHandler('OnOff.offWithEffect', {
51
- command: 'offWithEffect',
52
- request,
53
- cluster: OnOffServer.id,
54
- attributes: this.state,
55
- endpoint: this.endpoint,
56
- context: this.context,
57
- });
56
+ if (!process.env.MATTERBRIDGE_CHIP_TEST) {
57
+ await device.commandHandler.executeHandler('OnOff.offWithEffect', {
58
+ command: 'offWithEffect',
59
+ request,
60
+ cluster: OnOffServer.id,
61
+ attributes: this.state,
62
+ endpoint: this.endpoint,
63
+ context: this.context,
64
+ });
65
+ }
58
66
  device.log.debug(`MatterbridgeOnOffServer: offWithEffect called`);
59
67
  await super.offWithEffect(request);
60
68
  }
61
69
  async onWithRecallGlobalScene() {
62
70
  const device = this.endpoint.stateOf(MatterbridgeServer);
63
71
  device.log.info(`Switching device on with recall global scene (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
64
- await device.commandHandler.executeHandler('OnOff.onWithRecallGlobalScene', {
65
- command: 'onWithRecallGlobalScene',
66
- request: {},
67
- cluster: OnOffServer.id,
68
- attributes: this.state,
69
- endpoint: this.endpoint,
70
- context: this.context,
71
- });
72
+ if (!process.env.MATTERBRIDGE_CHIP_TEST) {
73
+ await device.commandHandler.executeHandler('OnOff.onWithRecallGlobalScene', {
74
+ command: 'onWithRecallGlobalScene',
75
+ request: {},
76
+ cluster: OnOffServer.id,
77
+ attributes: this.state,
78
+ endpoint: this.endpoint,
79
+ context: this.context,
80
+ });
81
+ }
72
82
  device.log.debug(`MatterbridgeOnOffServer: onWithRecallGlobalScene called`);
73
83
  await super.onWithRecallGlobalScene();
74
84
  }
75
85
  async onWithTimedOff(request) {
76
86
  const device = this.endpoint.stateOf(MatterbridgeServer);
77
87
  device.log.info(`Switching device on with timed off control ${JSON.stringify(request.onOffControl)}, offWaitTime ${request.offWaitTime} and onTime ${request.onTime} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
78
- await device.commandHandler.executeHandler('OnOff.onWithTimedOff', {
79
- command: 'onWithTimedOff',
80
- request,
81
- cluster: OnOffServer.id,
82
- attributes: this.state,
83
- endpoint: this.endpoint,
84
- context: this.context,
85
- });
88
+ if (!process.env.MATTERBRIDGE_CHIP_TEST) {
89
+ await device.commandHandler.executeHandler('OnOff.onWithTimedOff', {
90
+ command: 'onWithTimedOff',
91
+ request,
92
+ cluster: OnOffServer.id,
93
+ attributes: this.state,
94
+ endpoint: this.endpoint,
95
+ context: this.context,
96
+ });
97
+ }
86
98
  device.log.debug(`MatterbridgeOnOffServer: onWithTimedOff called`);
87
99
  await super.onWithTimedOff(request);
88
100
  }
package/dist/cli.js CHANGED
@@ -141,6 +141,7 @@ function help() {
141
141
  --loginterfaces: log the network interfaces (usefull for finding the name of the interface to use with -mdnsinterface option)
142
142
  --logstorage: log the node storage
143
143
  --systemcheck: perform a system check (check Node.js version and network interfaces)
144
+ --demo-devices: create demo devices (only in bridge mode)
144
145
 
145
146
  Reset Commands:
146
147
  --reset: remove the commissioning for Matterbridge (bridge mode and childbridge mode). Shutdown Matterbridge before using it!
@@ -0,0 +1,2 @@
1
+ import type { Matterbridge } from './matterbridge.js';
2
+ export declare function createDemoDevices(matterbridge: Matterbridge): Promise<void>;
@@ -1,25 +1,29 @@
1
1
  import { CommonNumberTag } from '@matter/node';
2
+ import { AirQuality } from '@matter/types/clusters/air-quality';
3
+ import { FanControl } from '@matter/types/clusters/fan-control';
4
+ import { PowerSource } from '@matter/types/clusters/power-source';
2
5
  import { EndpointNumber } from '@matter/types/datatype';
6
+ import { Closure } from './devices/closure.js';
3
7
  import { IrrigationSystem } from './devices/irrigationSystem.js';
4
8
  import { getSupportedDeviceType } from './matterbridgeDeviceTypes.js';
5
9
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
6
10
  import { getSemtag } from './matterbridgeEndpointHelpers.js';
7
- export async function createChipTestDevices(matterbridge) {
8
- if (!process.env.MATTERBRIDGE_CHIP_TEST || !process.env.MATTERBRIDGE_CHIP_TEST_DEVICES || matterbridge.bridgeMode !== 'bridge' || !matterbridge.aggregatorNode)
11
+ export async function createDemoDevices(matterbridge) {
12
+ if (matterbridge.bridgeMode !== 'bridge' || !matterbridge.serverNode || !matterbridge.aggregatorNode)
9
13
  return;
10
14
  const serverNode = matterbridge.serverNode;
11
15
  const aggregator = matterbridge.aggregatorNode;
12
16
  if (!serverNode || !aggregator) {
13
- matterbridge.log.error('CHIP test devices can only be created when the server node and aggregator node are available');
17
+ matterbridge.log.error('Demo devices can only be created when the server node and aggregator node are available');
14
18
  return;
15
19
  }
16
20
  let ep;
17
21
  matterbridge.plugins.set({
18
- name: 'matterbridge-chip',
22
+ name: 'matterbridge-demo-devices',
19
23
  path: '',
20
24
  type: 'DynamicPlatform',
21
25
  version: '1.0.0',
22
- description: 'Chip test plugin',
26
+ description: 'Matterbridge demo devices',
23
27
  author: 'Matterbridge',
24
28
  enabled: false,
25
29
  private: false,
@@ -28,8 +32,8 @@ export async function createChipTestDevices(matterbridge) {
28
32
  const registerDevice = async (device, deviceName, serialNumber) => {
29
33
  device.createDefaultBridgedDeviceBasicInformationClusterServer(deviceName, serialNumber);
30
34
  device.addRequiredClusters();
31
- device.plugin = 'matterbridge-chip';
32
- await matterbridge.addBridgedEndpoint('matterbridge-chip', device);
35
+ device.plugin = 'matterbridge-demo-devices';
36
+ await matterbridge.addBridgedEndpoint('matterbridge-demo-devices', device);
33
37
  };
34
38
  const bridgedNode = getSupportedDeviceType('BridgedNode');
35
39
  const powerSource = getSupportedDeviceType('PowerSource');
@@ -157,31 +161,94 @@ export async function createChipTestDevices(matterbridge) {
157
161
  ep.createSmokeOnlySmokeCOAlarmClusterServer();
158
162
  await registerDevice(ep, 'Smoke Alarm', 'SENSOR-07-09-1');
159
163
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('SmokeCOAlarm'), bridgedNode, powerSource], { id: 'COAlarm', number: EndpointNumber(7_09_2) });
160
- ep.createDefaultPowerSourceBatteryClusterServer();
164
+ ep.createDefaultPowerSourceBatteryClusterServer(90, PowerSource.BatChargeLevel.Ok);
161
165
  ep.createCoOnlySmokeCOAlarmClusterServer();
162
166
  await registerDevice(ep, 'CO Alarm', 'SENSOR-07-09-2');
163
167
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('AirQualitySensor'), bridgedNode, powerSource], { id: 'AirQualitySensor', number: EndpointNumber(7_10) });
164
- ep.createDefaultPowerSourceBatteryClusterServer();
168
+ ep.createDefaultPowerSourceBatteryClusterServer(70, PowerSource.BatChargeLevel.Ok);
165
169
  ep.createDefaultTvocMeasurementClusterServer(50, undefined, undefined, undefined, 0, 1000);
166
170
  ep.addOptionalClusterServers();
167
171
  await registerDevice(ep, 'Air Quality Sensor', 'SENSOR-07-10');
168
172
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WaterFreezeDetector'), bridgedNode, powerSource], { id: 'WaterFreezeDetector', number: EndpointNumber(7_11) });
169
- ep.createDefaultPowerSourceBatteryClusterServer();
173
+ ep.createDefaultPowerSourceBatteryClusterServer(50, PowerSource.BatChargeLevel.Ok);
170
174
  ep.createDefaultBooleanStateClusterServer(false);
171
175
  ep.createDefaultBooleanStateConfigurationClusterServer();
172
176
  await registerDevice(ep, 'Water Freeze Detector', 'SENSOR-07-11');
173
177
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('WaterLeakDetector'), bridgedNode, powerSource], { id: 'WaterLeakDetector', number: EndpointNumber(7_12) });
174
- ep.createDefaultPowerSourceBatteryClusterServer();
178
+ ep.createDefaultPowerSourceBatteryClusterServer(20, PowerSource.BatChargeLevel.Warning);
175
179
  ep.createDefaultBooleanStateClusterServer(false);
176
180
  ep.createDefaultBooleanStateConfigurationClusterServer();
177
181
  await registerDevice(ep, 'Water Leak Detector', 'SENSOR-07-12');
178
182
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('RainSensor'), bridgedNode, powerSource], { id: 'RainSensor', number: EndpointNumber(7_13) });
179
- ep.createDefaultPowerSourceRechargeableBatteryClusterServer();
183
+ ep.createDefaultPowerSourceRechargeableBatteryClusterServer(10, PowerSource.BatChargeLevel.Critical);
180
184
  ep.createDefaultBooleanStateClusterServer(false);
181
185
  ep.createDefaultBooleanStateConfigurationClusterServer();
182
186
  await registerDevice(ep, 'Rain Sensor', 'SENSOR-07-13');
183
187
  ep = new MatterbridgeEndpoint([getSupportedDeviceType('SoilSensor'), bridgedNode, powerSource], { id: 'SoilSensor', number: EndpointNumber(7_14) });
184
- ep.createDefaultPowerSourceWiredClusterServer();
188
+ ep.createDefaultPowerSourceWiredClusterServer(PowerSource.WiredCurrentType.Dc);
185
189
  ep.createDefaultSoilMeasurementClusterServer(50);
186
190
  await registerDevice(ep, 'Soil Sensor', 'SENSOR-07-14');
191
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('DoorLock'), bridgedNode, powerSource], { id: 'DoorLock', number: EndpointNumber(8_01) });
192
+ ep.createDefaultPowerSourceBatteryClusterServer();
193
+ await registerDevice(ep, 'Door Lock', 'ENTRY-08-01');
194
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('DoorLockController'), bridgedNode, powerSource], { id: 'DoorLockController', number: EndpointNumber(8_02) });
195
+ await registerDevice(ep, 'Door Lock Controller', 'ENTRY-08-02');
196
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCovering'), bridgedNode, powerSource], { id: 'WindowCovering', number: EndpointNumber(8_03) });
197
+ await registerDevice(ep, 'Window Covering', 'ENTRY-08-03');
198
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('WindowCoveringController'), bridgedNode, powerSource], { id: 'WindowCoveringController', number: EndpointNumber(8_04) });
199
+ await registerDevice(ep, 'Window Covering Controller', 'ENTRY-08-04');
200
+ ep = new Closure('Closure', 'ENTRY-08-05', { id: 'Closure', number: EndpointNumber(8_05) });
201
+ await registerDevice(ep, 'Closure', 'ENTRY-08-05');
202
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('ClosureController'), bridgedNode, powerSource], { id: 'ClosureController', number: EndpointNumber(8_07) });
203
+ await registerDevice(ep, 'Closure Controller', 'ENTRY-08-07');
204
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'Thermostat', number: EndpointNumber(9_01) });
205
+ await registerDevice(ep, 'Thermostat', 'HVAC-09-01');
206
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatHeating', number: EndpointNumber(9_01_1) });
207
+ ep.createDefaultHeatingThermostatClusterServer();
208
+ await registerDevice(ep, 'Thermostat Heating', 'HVAC-09-01-1');
209
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatCooling', number: EndpointNumber(9_01_2) });
210
+ ep.createDefaultCoolingThermostatClusterServer();
211
+ await registerDevice(ep, 'Thermostat Cooling', 'HVAC-09-01-2');
212
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatPresets', number: EndpointNumber(9_01_3) });
213
+ ep.createDefaultPresetsThermostatClusterServer();
214
+ await registerDevice(ep, 'Thermostat Presets', 'HVAC-09-01-3');
215
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatSchedules', number: EndpointNumber(9_01_4) });
216
+ ep.createDefaultSchedulesThermostatClusterServer();
217
+ await registerDevice(ep, 'Thermostat Schedules', 'HVAC-09-01-4');
218
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Thermostat'), bridgedNode, powerSource], { id: 'ThermostatSuggestions', number: EndpointNumber(9_01_5) });
219
+ ep.createDefaultThermostatSuggestionsClusterServer();
220
+ await registerDevice(ep, 'Thermostat Suggestions', 'HVAC-09-01-5');
221
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Fan'), bridgedNode, powerSource], { id: 'Fan', number: EndpointNumber(9_02) });
222
+ await registerDevice(ep, 'Fan OffLowMedHighAuto', 'HVAC-09-02');
223
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Fan'), bridgedNode, powerSource], { id: 'FanOnOff', number: EndpointNumber(9_02_1) });
224
+ ep.createOnOffFanControlClusterServer(FanControl.FanMode.High);
225
+ await registerDevice(ep, 'Fan OffHigh', 'HVAC-09-02-1');
226
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Fan'), bridgedNode, powerSource], { id: 'FanBase', number: EndpointNumber(9_02_2) });
227
+ ep.createBaseFanControlClusterServer(FanControl.FanMode.Low, undefined, 30, 30);
228
+ await registerDevice(ep, 'Fan OffLowMedHigh', 'HVAC-09-02-2');
229
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Fan'), bridgedNode, powerSource], { id: 'FanMultiSpeed', number: EndpointNumber(9_02_3) });
230
+ ep.createMultiSpeedFanControlClusterServer(FanControl.FanMode.Medium, undefined, 50, 50, 10, 5, 5);
231
+ await registerDevice(ep, 'Fan MultiSpeed', 'HVAC-09-02-3');
232
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('Fan'), bridgedNode, powerSource], { id: 'FanComplete', number: EndpointNumber(9_02_4) });
233
+ ep.createCompleteFanControlClusterServer(FanControl.FanMode.Auto, undefined, 60, 60, 10, 5, 5);
234
+ await registerDevice(ep, 'Fan Complete', 'HVAC-09-02-4');
235
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('AirPurifier'), bridgedNode, powerSource], {
236
+ id: 'AirPurifier',
237
+ number: EndpointNumber(9_03),
238
+ });
239
+ ep.createDefaultHepaFilterMonitoringClusterServer(85);
240
+ ep.createDefaultActivatedCarbonFilterMonitoringClusterServer(75);
241
+ ep.addChildDeviceType('AirQualitySensor', getSupportedDeviceType('AirQualitySensor'), { number: EndpointNumber(9_03_1) })
242
+ .createDefaultAirQualityClusterServer(AirQuality.AirQualityEnum.Good)
243
+ .addRequiredClusters();
244
+ ep.addChildDeviceType('Thermostat', getSupportedDeviceType('Thermostat'), { number: EndpointNumber(9_03_2) }).addRequiredClusters();
245
+ ep.addChildDeviceType('TemperatureSensor', getSupportedDeviceType('TemperatureSensor'), { number: EndpointNumber(9_03_3) })
246
+ .createDefaultTemperatureMeasurementClusterServer(2000, -4000, 8500)
247
+ .addRequiredClusters();
248
+ ep.addChildDeviceType('HumiditySensor', getSupportedDeviceType('HumiditySensor'), { number: EndpointNumber(9_03_4) })
249
+ .createDefaultRelativeHumidityMeasurementClusterServer(5000, 0, 10000)
250
+ .addRequiredClusters();
251
+ await registerDevice(ep, 'Air Purifier', 'HVAC-09-03');
252
+ ep = new MatterbridgeEndpoint([getSupportedDeviceType('ThermostatController'), bridgedNode, powerSource], { id: 'ThermostatController', number: EndpointNumber(9_04) });
253
+ await registerDevice(ep, 'Thermostat Controller', 'HVAC-09-04');
187
254
  }
@@ -1,4 +1,5 @@
1
1
  import { ClosureControlServer } from '@matter/node/behaviors/closure-control';
2
+ import { type EndpointNumber } from '@matter/types';
2
3
  import { ClosureControl } from '@matter/types/clusters/closure-control';
3
4
  import { Identify } from '@matter/types/clusters/identify';
4
5
  import type { Semtag } from '@matter/types/globals';
@@ -29,6 +30,8 @@ export interface ClosureOptions {
29
30
  overallCurrentState?: ClosureControl.OverallCurrentState;
30
31
  overallTargetState?: ClosureControl.OverallTargetState;
31
32
  latchControlModes?: ClosureControl.LatchControlModes;
33
+ id?: string;
34
+ number?: EndpointNumber;
32
35
  tagList?: Semtag[];
33
36
  }
34
37
  export declare class Closure extends MatterbridgeEndpoint {
@@ -74,9 +74,10 @@ export class Closure extends MatterbridgeEndpoint {
74
74
  position: ClosureControl.TargetPosition.MoveToFullyClosed,
75
75
  latch: true,
76
76
  speed: ThreeLevelAuto.Auto,
77
- }, latchControlModes = { remoteLatching: true, remoteUnlatching: true }, tagList = [getSemtag(ClosureTag.Covering)], } = options;
77
+ }, latchControlModes = { remoteLatching: true, remoteUnlatching: true }, id, number, tagList = [getSemtag(ClosureTag.Covering)], } = options;
78
78
  super(powerSourceType === 'None' ? [closure] : [closure, powerSource], {
79
- id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
79
+ id: id ?? `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
80
+ number,
80
81
  tagList,
81
82
  });
82
83
  this.createDefaultIdentifyClusterServer(identifyTime, identifyType);
package/dist/frontend.js CHANGED
@@ -4,16 +4,24 @@ import path from 'node:path';
4
4
  import { Diagnostic, Lifecycle, LogDestination, LogFormat as MatterLogFormat, Logger, LogLevel as MatterLogLevel } from '@matter/general';
5
5
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
6
6
  import { getClusterNameById } from '@matter/types/cluster';
7
+ import { AirQuality } from '@matter/types/clusters/air-quality';
7
8
  import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
9
+ import { ClosureControl } from '@matter/types/clusters/closure-control';
8
10
  import { DeviceEnergyManagement } from '@matter/types/clusters/device-energy-management';
11
+ import { FanControl } from '@matter/types/clusters/fan-control';
12
+ import { OperationalState } from '@matter/types/clusters/operational-state';
9
13
  import { PowerSource } from '@matter/types/clusters/power-source';
14
+ import { RvcOperationalState } from '@matter/types/clusters/rvc-operational-state';
15
+ import { SmokeCoAlarm } from '@matter/types/clusters/smoke-co-alarm';
10
16
  import { SoilMeasurement } from '@matter/types/clusters/soil-measurement';
17
+ import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
11
18
  import { CommissioningOptions } from '@matter/types/commissioning';
12
19
  import { FabricIndex } from '@matter/types/datatype';
13
20
  import { BroadcastServer } from '@matterbridge/thread/server';
14
21
  import { MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from '@matterbridge/types';
15
22
  import { isBun } from '@matterbridge/utils/bun';
16
23
  import { getParameter, hasParameter } from '@matterbridge/utils/cli';
24
+ import { getEnumDescription } from '@matterbridge/utils/enum';
17
25
  import { getErrorMessage, inspectError, logError } from '@matterbridge/utils/error';
18
26
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
19
27
  import { logModuleLoaded } from '@matterbridge/utils/loader';
@@ -1117,6 +1125,14 @@ export class Frontend extends EventEmitter {
1117
1125
  attributes += `Cover position: ${attributeValue / 100}% `;
1118
1126
  if (clusterName === 'doorLock' && attributeName === 'lockState')
1119
1127
  attributes += `State: ${attributeValue === 1 ? 'Locked' : 'Not locked'} `;
1128
+ if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && attributeValue === null)
1129
+ attributes += `Position: unknown Latch: unknown SecureState: unknown `;
1130
+ if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && isValidObject(attributeValue)) {
1131
+ const overallCurrentState = attributeValue;
1132
+ attributes += `Position: ${getEnumDescription(ClosureControl.CurrentPosition, overallCurrentState.position, { fallback: 'unknown' })} `;
1133
+ attributes += `Latch: ${overallCurrentState.latch ?? 'unknown'} `;
1134
+ attributes += `SecureState: ${overallCurrentState.secureState ?? 'unknown'} `;
1135
+ }
1120
1136
  if (clusterName === 'thermostat' && attributeName === 'localTemperature' && isValidNumber(attributeValue))
1121
1137
  attributes += `Temperature: ${attributeValue / 100}°C `;
1122
1138
  if (clusterName === 'thermostat' && attributeName === 'occupiedHeatingSetpoint' && isValidNumber(attributeValue))
@@ -1132,13 +1148,14 @@ export class Frontend extends EventEmitter {
1132
1148
  if (supportedMode)
1133
1149
  attributes += `Mode: ${supportedMode.label} `;
1134
1150
  }
1135
- const operationalStateClusters = ['operationalState', 'rvcOperationalState'];
1136
- if (operationalStateClusters.includes(clusterName) && attributeName === 'operationalState')
1137
- attributes += `OpState: ${attributeValue} `;
1151
+ if (clusterName === 'operationalState' && attributeName === 'operationalState')
1152
+ attributes += `OpState: ${getEnumDescription(OperationalState.OperationalStateEnum, attributeValue)} `;
1153
+ if (clusterName === 'rvcOperationalState' && attributeName === 'operationalState')
1154
+ attributes += `OpState: ${getEnumDescription(RvcOperationalState.OperationalState, attributeValue)} `;
1138
1155
  if (clusterName === 'pumpConfigurationAndControl' && attributeName === 'operationMode')
1139
1156
  attributes += `Mode: ${attributeValue} `;
1140
1157
  if (clusterName === 'valveConfigurationAndControl' && attributeName === 'currentState')
1141
- attributes += `State: ${attributeValue} `;
1158
+ attributes += `State: ${getEnumDescription(ValveConfigurationAndControl.ValveState, attributeValue, { fallback: 'unknown' })} `;
1142
1159
  if (clusterName === 'levelControl' && attributeName === 'currentLevel')
1143
1160
  attributes += `Level: ${attributeValue} `;
1144
1161
  if (clusterName === 'colorControl' && attributeName === 'colorMode' && isValidNumber(attributeValue, 0, 2))
@@ -1158,15 +1175,25 @@ export class Frontend extends EventEmitter {
1158
1175
  if (clusterName === 'booleanStateConfiguration' && attributeName === 'alarmsActive' && isValidObject(attributeValue))
1159
1176
  attributes += `Active alarms: ${stringify(attributeValue)} `;
1160
1177
  if (clusterName === 'smokeCoAlarm' && attributeName === 'smokeState')
1161
- attributes += `Smoke: ${attributeValue} `;
1178
+ attributes += `Smoke: ${getEnumDescription(SmokeCoAlarm.AlarmState, attributeValue)} `;
1162
1179
  if (clusterName === 'smokeCoAlarm' && attributeName === 'coState')
1163
- attributes += `Co: ${attributeValue} `;
1180
+ attributes += `Co: ${getEnumDescription(SmokeCoAlarm.AlarmState, attributeValue)} `;
1164
1181
  if (clusterName === 'fanControl' && attributeName === 'fanMode')
1165
- attributes += `Mode: ${attributeValue} `;
1182
+ attributes += `Mode: ${getEnumDescription(FanControl.FanMode, attributeValue)} `;
1166
1183
  if (clusterName === 'fanControl' && attributeName === 'percentCurrent')
1167
- attributes += `Percent: ${attributeValue} `;
1184
+ attributes += `Percent: ${attributeValue}% `;
1168
1185
  if (clusterName === 'fanControl' && attributeName === 'speedCurrent')
1169
1186
  attributes += `Speed: ${attributeValue} `;
1187
+ if (clusterName === 'fanControl' && attributeName === 'rockSetting' && isValidObject(attributeValue))
1188
+ attributes += `Rock: ${stringify(attributeValue)} `;
1189
+ if (clusterName === 'fanControl' && attributeName === 'windSetting' && isValidObject(attributeValue))
1190
+ attributes += `Wind: ${stringify(attributeValue)} `;
1191
+ if (clusterName === 'fanControl' && attributeName === 'airflowDirection')
1192
+ attributes += `Direction: ${getEnumDescription(FanControl.AirflowDirection, attributeValue)} `;
1193
+ if (clusterName === 'hepaFilterMonitoring' && attributeName === 'condition')
1194
+ attributes += `Hepa filter: ${attributeValue}% `;
1195
+ if (clusterName === 'activatedCarbonFilterMonitoring' && attributeName === 'condition')
1196
+ attributes += `Carbon filter: ${attributeValue}% `;
1170
1197
  if (clusterName === 'occupancySensing' && attributeName === 'occupancy' && isValidObject(attributeValue, 1))
1171
1198
  attributes += `Occupancy: ${attributeValue.occupied} `;
1172
1199
  if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue') {
@@ -1176,47 +1203,27 @@ export class Frontend extends EventEmitter {
1176
1203
  attributes += `Illuminance: ${Math.round(Math.max(Math.pow(10, attributeValue / 10000), 0))} lx`;
1177
1204
  }
1178
1205
  if (clusterName === 'airQuality' && attributeName === 'airQuality')
1179
- attributes += `Air quality: ${attributeValue} `;
1206
+ attributes += `Air quality: ${getEnumDescription(AirQuality.AirQualityEnum, attributeValue)} `;
1180
1207
  if (clusterName === 'totalVolatileOrganicCompoundsConcentrationMeasurement' && attributeName === 'measuredValue')
1181
- attributes += `Voc: ${attributeValue} `;
1208
+ appendMeasurement('Voc', getMeasurementText(attributeValue, 1, ''));
1182
1209
  if (clusterName === 'pm1ConcentrationMeasurement' && attributeName === 'measuredValue')
1183
- attributes += `Pm1: ${attributeValue} `;
1210
+ appendMeasurement('Pm1', getMeasurementText(attributeValue, 1, ''));
1184
1211
  if (clusterName === 'pm25ConcentrationMeasurement' && attributeName === 'measuredValue')
1185
- attributes += `Pm2.5: ${attributeValue} `;
1212
+ appendMeasurement('Pm2.5', getMeasurementText(attributeValue, 1, ''));
1186
1213
  if (clusterName === 'pm10ConcentrationMeasurement' && attributeName === 'measuredValue')
1187
- attributes += `Pm10: ${attributeValue} `;
1214
+ appendMeasurement('Pm10', getMeasurementText(attributeValue, 1, ''));
1188
1215
  if (clusterName === 'formaldehydeConcentrationMeasurement' && attributeName === 'measuredValue')
1189
- attributes += `CH₂O: ${attributeValue} `;
1190
- if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue') {
1191
- if (attributeValue === null)
1192
- attributes += `Temperature: unknown `;
1193
- else if (isValidNumber(attributeValue))
1194
- attributes += `Temperature: ${attributeValue / 100} °C `;
1195
- }
1196
- if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue') {
1197
- if (attributeValue === null)
1198
- attributes += `Humidity: unknown `;
1199
- else if (isValidNumber(attributeValue))
1200
- attributes += `Humidity: ${attributeValue / 100}% `;
1201
- }
1202
- if (clusterName === 'pressureMeasurement' && attributeName === 'measuredValue') {
1203
- if (attributeValue === null)
1204
- attributes += `Pressure: unknown `;
1205
- else if (isValidNumber(attributeValue))
1206
- attributes += `Pressure: ${attributeValue} hPa `;
1207
- }
1208
- if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue') {
1209
- if (attributeValue === null)
1210
- attributes += `Flow: unknown `;
1211
- else if (isValidNumber(attributeValue))
1212
- attributes += `Flow: ${attributeValue / 10} m³/h `;
1213
- }
1214
- if (clusterId === SoilMeasurement.id && attributeName === 'soilMoistureMeasuredValue') {
1215
- if (attributeValue === null)
1216
- attributes += `Soil moisture: unknown `;
1217
- else if (isValidNumber(attributeValue))
1218
- attributes += `Soil moisture: ${attributeValue}% `;
1219
- }
1216
+ appendMeasurement('CH₂O', getMeasurementText(attributeValue, 1, ''));
1217
+ if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue')
1218
+ appendMeasurement('Temperature', getMeasurementText(attributeValue, 100, ' °C'));
1219
+ if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue')
1220
+ appendMeasurement('Humidity', getMeasurementText(attributeValue, 100, '%'));
1221
+ if (clusterName === 'pressureMeasurement' && attributeName === 'measuredValue')
1222
+ appendMeasurement('Pressure', getMeasurementText(attributeValue, 1, ' hPa'));
1223
+ if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue')
1224
+ appendMeasurement('Flow', getMeasurementText(attributeValue, 10, ' m³/h'));
1225
+ if (clusterId === SoilMeasurement.id && attributeName === 'soilMoistureMeasuredValue')
1226
+ appendMeasurement('Soil moisture', getMeasurementText(attributeValue, 1, '%'));
1220
1227
  if (clusterName === 'electricalPowerMeasurement' && attributeName === 'voltage')
1221
1228
  appendMeasurement('Voltage', getMeasurementText(attributeValue, 1_000, 'V'));
1222
1229
  if (clusterName === 'electricalPowerMeasurement' && attributeName === 'activeCurrent')
@@ -864,6 +864,7 @@ export class MatterNode extends EventEmitter {
864
864
  { cluster: 'FanControl', attribute: 'fanMode' },
865
865
  { cluster: 'FanControl', attribute: 'fanModeSequence' },
866
866
  { cluster: 'FanControl', attribute: 'percentSetting' },
867
+ { cluster: 'FanControl', attribute: 'percentCurrent' },
867
868
  { cluster: 'ModeSelect', attribute: 'currentMode' },
868
869
  { cluster: 'RvcRunMode', attribute: 'currentMode' },
869
870
  { cluster: 'RvcCleanMode', attribute: 'currentMode' },
@@ -1569,12 +1569,18 @@ export class Matterbridge extends EventEmitter {
1569
1569
  this.aggregatorNode = await this.createAggregatorNode(this.matterbridgeContext);
1570
1570
  await this.serverNode.add(this.aggregatorNode);
1571
1571
  await addVirtualDevices(this, this.aggregatorNode);
1572
- if (process.env.MATTERBRIDGE_CHIP_TEST) {
1572
+ if (process.env.MATTERBRIDGE_DEMO_DEVICES || hasParameter('demo-devices')) {
1573
+ this.log.warn(' *********************************************************************************');
1574
+ this.log.warn(' * MATTERBRIDGE_DEMO_DEVICES environment variable is set. Creating demo devices. *');
1575
+ this.log.warn(' *********************************************************************************');
1576
+ const { createDemoDevices } = await import('./demoDevices.js');
1577
+ await createDemoDevices(this);
1578
+ }
1579
+ if (process.env.MATTERBRIDGE_CHIP_TEST && fs.existsSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'chipTests.js'))) {
1580
+ this.log.warn(' ***********************************************************************************');
1581
+ this.log.warn(' * MATTERBRIDGE_CHIP_TEST environment variable is set. Running chip test app pipe. *');
1582
+ this.log.warn(' ***********************************************************************************');
1573
1583
  const { createChipTestAppPipe } = await import('./chipTests.js');
1574
- if (process.env.MATTERBRIDGE_CHIP_TEST_DEVICES) {
1575
- const { createChipTestDevices } = await import('./chipTestDevices.js');
1576
- await createChipTestDevices(this);
1577
- }
1578
1584
  createChipTestAppPipe(this);
1579
1585
  }
1580
1586
  await this.startPlugins(false, true);
@@ -1887,7 +1893,10 @@ export class Matterbridge extends EventEmitter {
1887
1893
  discriminator = PaseClient.generateRandomDiscriminator(this.environment.get(Crypto));
1888
1894
  }
1889
1895
  let rootEndpoint = ServerNode.RootEndpoint.with(PowerSourceServer.with(PowerSource.Feature.Wired));
1890
- if (process.env.MATTERBRIDGE_CHIP_TEST) {
1896
+ if (process.env.MATTERBRIDGE_CHIP_TEST && fs.existsSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'chipTests.js'))) {
1897
+ this.log.warn(' ****************************************************************************************');
1898
+ this.log.warn(' * MATTERBRIDGE_CHIP_TEST environment variable is set. Running TestEventTrigger server. *');
1899
+ this.log.warn(' ****************************************************************************************');
1891
1900
  const { MatterbridgeGeneralDiagnosticsServer } = await import('./chipTests.js');
1892
1901
  rootEndpoint = rootEndpoint.with(MatterbridgeGeneralDiagnosticsServer);
1893
1902
  }
@@ -2291,6 +2300,7 @@ export class Matterbridge extends EventEmitter {
2291
2300
  { cluster: 'FanControl', attribute: 'fanMode' },
2292
2301
  { cluster: 'FanControl', attribute: 'fanModeSequence' },
2293
2302
  { cluster: 'FanControl', attribute: 'percentSetting' },
2303
+ { cluster: 'FanControl', attribute: 'percentCurrent' },
2294
2304
  { cluster: 'ModeSelect', attribute: 'currentMode' },
2295
2305
  { cluster: 'RvcRunMode', attribute: 'currentMode' },
2296
2306
  { cluster: 'RvcCleanMode', attribute: 'currentMode' },
@@ -9,6 +9,7 @@ import type { DeviceEnergyManagementMode } from '@matter/types/clusters/device-e
9
9
  import { DoorLock } from '@matter/types/clusters/door-lock';
10
10
  import { FanControl } from '@matter/types/clusters/fan-control';
11
11
  import { Identify } from '@matter/types/clusters/identify';
12
+ import type { IlluminanceMeasurement } from '@matter/types/clusters/illuminance-measurement';
12
13
  import type { ModeSelect } from '@matter/types/clusters/mode-select';
13
14
  import { OnOff } from '@matter/types/clusters/on-off';
14
15
  import { OperationalState } from '@matter/types/clusters/operational-state';
@@ -217,11 +218,11 @@ export declare class MatterbridgeEndpoint extends Endpoint {
217
218
  createDefaultOperationalStateClusterServer(operationalState?: OperationalState.OperationalStateEnum): this;
218
219
  createDefaultBooleanStateClusterServer(contact?: boolean): this;
219
220
  createDefaultBooleanStateConfigurationClusterServer(sensorFault?: boolean, currentSensitivityLevel?: number, supportedSensitivityLevels?: number, defaultSensitivityLevel?: number): this;
220
- createDefaultTemperatureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): this;
221
- createDefaultRelativeHumidityMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): this;
222
- createDefaultPressureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): this;
223
- createDefaultIlluminanceMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): this;
224
- createDefaultFlowMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): this;
221
+ createDefaultTemperatureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): this;
222
+ createDefaultRelativeHumidityMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): this;
223
+ createDefaultPressureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): this;
224
+ createDefaultIlluminanceMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number, lightSensorType?: IlluminanceMeasurement.LightSensorType | null): this;
225
+ createDefaultFlowMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): this;
225
226
  createDefaultOccupancySensingClusterServer(occupied?: boolean, holdTime?: number, holdTimeMin?: number, holdTimeMax?: number): this;
226
227
  createDefaultSoilMeasurementClusterServer(soilMoistureMeasuredValue?: number | null, soilMoistureMeasurementLimits?: MeasurementAccuracy): this;
227
228
  createDefaultAirQualityClusterServer(airQuality?: AirQuality.AirQualityEnum): this;
@@ -1175,8 +1175,8 @@ export class MatterbridgeEndpoint extends Endpoint {
1175
1175
  this.behaviors.require(FanControlServer, {
1176
1176
  fanMode,
1177
1177
  fanModeSequence: FanControl.FanModeSequence.OffHigh,
1178
- percentSetting: 0,
1179
- percentCurrent: 0,
1178
+ percentSetting: fanMode === FanControl.FanMode.Off ? 0 : 100,
1179
+ percentCurrent: fanMode === FanControl.FanMode.Off ? 0 : 100,
1180
1180
  });
1181
1181
  return this;
1182
1182
  }
@@ -1487,7 +1487,7 @@ export class MatterbridgeEndpoint extends Endpoint {
1487
1487
  });
1488
1488
  return this;
1489
1489
  }
1490
- createDefaultBooleanStateConfigurationClusterServer(sensorFault = false, currentSensitivityLevel = 0, supportedSensitivityLevels = 2, defaultSensitivityLevel = 0) {
1490
+ createDefaultBooleanStateConfigurationClusterServer(sensorFault = false, currentSensitivityLevel = 1, supportedSensitivityLevels = 3, defaultSensitivityLevel = 1) {
1491
1491
  this.behaviors.require(MatterbridgeBooleanStateConfigurationServer.with(BooleanStateConfiguration.Feature.Visual, BooleanStateConfiguration.Feature.Audible, BooleanStateConfiguration.Feature.AlarmSuppress, BooleanStateConfiguration.Feature.SensitivityLevel, BooleanStateConfiguration.Feature.FaultEvents).enable({
1492
1492
  events: { alarmsStateChanged: true, sensorFault: true },
1493
1493
  }), {
@@ -1502,24 +1502,24 @@ export class MatterbridgeEndpoint extends Endpoint {
1502
1502
  });
1503
1503
  return this;
1504
1504
  }
1505
- createDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1506
- this.behaviors.require(TemperatureMeasurementServer, getDefaultTemperatureMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue));
1505
+ createDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1506
+ this.behaviors.require(TemperatureMeasurementServer, getDefaultTemperatureMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue, tolerance));
1507
1507
  return this;
1508
1508
  }
1509
- createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1510
- this.behaviors.require(RelativeHumidityMeasurementServer, getDefaultRelativeHumidityMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue));
1509
+ createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1510
+ this.behaviors.require(RelativeHumidityMeasurementServer, getDefaultRelativeHumidityMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue, tolerance));
1511
1511
  return this;
1512
1512
  }
1513
- createDefaultPressureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1514
- this.behaviors.require(PressureMeasurementServer, getDefaultPressureMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue));
1513
+ createDefaultPressureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1514
+ this.behaviors.require(PressureMeasurementServer, getDefaultPressureMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue, tolerance));
1515
1515
  return this;
1516
1516
  }
1517
- createDefaultIlluminanceMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1518
- this.behaviors.require(IlluminanceMeasurementServer, getDefaultIlluminanceMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue));
1517
+ createDefaultIlluminanceMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0, lightSensorType = null) {
1518
+ this.behaviors.require(IlluminanceMeasurementServer, getDefaultIlluminanceMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue, tolerance, lightSensorType));
1519
1519
  return this;
1520
1520
  }
1521
- createDefaultFlowMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1522
- this.behaviors.require(FlowMeasurementServer, getDefaultFlowMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue));
1521
+ createDefaultFlowMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1522
+ this.behaviors.require(FlowMeasurementServer, getDefaultFlowMeasurementClusterServer(measuredValue, minMeasuredValue, maxMeasuredValue, tolerance));
1523
1523
  return this;
1524
1524
  }
1525
1525
  createDefaultOccupancySensingClusterServer(occupied = false, holdTime = 30, holdTimeMin = 1, holdTimeMax = 300) {
@@ -143,11 +143,11 @@ export declare function getDefaultDeviceEnergyManagementModeClusterServer(curren
143
143
  onOff: false;
144
144
  }>, typeof MatterbridgeDeviceEnergyManagementModeServer>>;
145
145
  export declare function getDefaultOperationalStateClusterServer(operationalState?: OperationalState.OperationalStateEnum): Partial<import("@matter/node").ClusterState.Type<ClusterType.WithSupportedFeatures<OperationalState, {}>, typeof MatterbridgeOperationalStateServer>>;
146
- export declare function getDefaultTemperatureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): Partial<import("@matter/node").ClusterState.Type<TemperatureMeasurement, typeof ClusterBehavior>>;
147
- export declare function getDefaultRelativeHumidityMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): Partial<import("@matter/node").ClusterState.Type<RelativeHumidityMeasurement, typeof ClusterBehavior>>;
148
- export declare function getDefaultPressureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): Partial<import("@matter/node").ClusterState.Type<PressureMeasurement, typeof ClusterBehavior>>;
149
- export declare function getDefaultIlluminanceMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): Partial<import("@matter/node").ClusterState.Type<IlluminanceMeasurement, typeof ClusterBehavior>>;
150
- export declare function getDefaultFlowMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null): Partial<import("@matter/node").ClusterState.Type<FlowMeasurement, typeof ClusterBehavior>>;
146
+ export declare function getDefaultTemperatureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): Partial<import("@matter/node").ClusterState.Type<TemperatureMeasurement, typeof ClusterBehavior>>;
147
+ export declare function getDefaultRelativeHumidityMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): Partial<import("@matter/node").ClusterState.Type<RelativeHumidityMeasurement, typeof ClusterBehavior>>;
148
+ export declare function getDefaultPressureMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): Partial<import("@matter/node").ClusterState.Type<PressureMeasurement, typeof ClusterBehavior>>;
149
+ export declare function getDefaultIlluminanceMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number, lightSensorType?: IlluminanceMeasurement.LightSensorType | null): Partial<import("@matter/node").ClusterState.Type<IlluminanceMeasurement, typeof ClusterBehavior>>;
150
+ export declare function getDefaultFlowMeasurementClusterServer(measuredValue?: number | null, minMeasuredValue?: number | null, maxMeasuredValue?: number | null, tolerance?: number): Partial<import("@matter/node").ClusterState.Type<FlowMeasurement, typeof ClusterBehavior>>;
151
151
  export declare function getDefaultOccupancySensingClusterServer(occupied?: boolean, holdTime?: number, holdTimeMin?: number, holdTimeMax?: number): Partial<import("@matter/node").ClusterState.Type<ClusterType.WithSupportedFeatures<OccupancySensing, {
152
152
  activeInfrared: false;
153
153
  occupancyEvent: false;
@@ -1055,44 +1055,45 @@ export function getDefaultOperationalStateClusterServer(operationalState = Opera
1055
1055
  operationalError: { errorStateId: OperationalState.ErrorState.NoError, errorStateDetails: 'Fully operational' },
1056
1056
  });
1057
1057
  }
1058
- export function getDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1058
+ export function getDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1059
1059
  return optionsFor(TemperatureMeasurementServer, {
1060
1060
  measuredValue,
1061
1061
  minMeasuredValue,
1062
1062
  maxMeasuredValue,
1063
- tolerance: 0,
1063
+ tolerance,
1064
1064
  });
1065
1065
  }
1066
- export function getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1066
+ export function getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1067
1067
  return optionsFor(RelativeHumidityMeasurementServer, {
1068
1068
  measuredValue,
1069
1069
  minMeasuredValue,
1070
1070
  maxMeasuredValue,
1071
- tolerance: 0,
1071
+ tolerance,
1072
1072
  });
1073
1073
  }
1074
- export function getDefaultPressureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1074
+ export function getDefaultPressureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1075
1075
  return optionsFor(PressureMeasurementServer, {
1076
1076
  measuredValue,
1077
1077
  minMeasuredValue,
1078
1078
  maxMeasuredValue,
1079
- tolerance: 0,
1079
+ tolerance,
1080
1080
  });
1081
1081
  }
1082
- export function getDefaultIlluminanceMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1082
+ export function getDefaultIlluminanceMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0, lightSensorType = null) {
1083
1083
  return optionsFor(IlluminanceMeasurementServer, {
1084
1084
  measuredValue,
1085
1085
  minMeasuredValue,
1086
1086
  maxMeasuredValue,
1087
- tolerance: 0,
1087
+ tolerance,
1088
+ lightSensorType,
1088
1089
  });
1089
1090
  }
1090
- export function getDefaultFlowMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
1091
+ export function getDefaultFlowMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null, tolerance = 0) {
1091
1092
  return optionsFor(FlowMeasurementServer, {
1092
1093
  measuredValue,
1093
1094
  minMeasuredValue,
1094
1095
  maxMeasuredValue,
1095
- tolerance: 0,
1096
+ tolerance,
1096
1097
  });
1097
1098
  }
1098
1099
  export function getDefaultOccupancySensingClusterServer(occupied = false, holdTime = 30, holdTimeMin = 1, holdTimeMax = 300) {
@@ -428,7 +428,7 @@ export class PluginManager extends EventEmitter {
428
428
  }
429
429
  async loadFromStorage() {
430
430
  if (!this.matterbridge.nodeContext) {
431
- throw new Error('loadFromStorage: node context is not available.');
431
+ throw new Error('PluginManager.loadFromStorage: node context is not available.');
432
432
  }
433
433
  const pluginsArray = await this.matterbridge.nodeContext.get('plugins', []);
434
434
  for (const plugin of pluginsArray)
@@ -438,10 +438,12 @@ export class PluginManager extends EventEmitter {
438
438
  }
439
439
  async saveToStorage() {
440
440
  if (!this.matterbridge.nodeContext) {
441
- throw new Error('loadFromStorage: node context is not available.');
441
+ throw new Error('PluginManager.saveToStorage: node context is not available.');
442
442
  }
443
443
  const plugins = [];
444
444
  for (const plugin of this.array()) {
445
+ if (process.env.MATTERBRIDGE_DEMO_DEVICES && plugin.name === 'matterbridge-demo-devices')
446
+ continue;
445
447
  plugins.push({
446
448
  name: plugin.name,
447
449
  path: plugin.path,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.6-dev-20260818-c2c6349",
3
+ "version": "3.10.6-dev-20260820-36b52be",
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.6-dev-20260818-c2c6349",
136
- "@matterbridge/thread": "3.10.6-dev-20260818-c2c6349",
137
- "@matterbridge/types": "3.10.6-dev-20260818-c2c6349",
138
- "@matterbridge/utils": "3.10.6-dev-20260818-c2c6349",
135
+ "@matterbridge/dgram": "3.10.6-dev-20260820-36b52be",
136
+ "@matterbridge/thread": "3.10.6-dev-20260820-36b52be",
137
+ "@matterbridge/types": "3.10.6-dev-20260820-36b52be",
138
+ "@matterbridge/utils": "3.10.6-dev-20260820-36b52be",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.6.2",
@@ -1,2 +0,0 @@
1
- import type { Matterbridge } from './matterbridge.js';
2
- export declare function createChipTestDevices(matterbridge: Matterbridge): Promise<void>;