matterbridge 2.1.0-dev.3 → 2.1.0-dev.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,18 +1,14 @@
1
- import { createHash } from 'crypto';
2
1
  import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } from './logger/export.js';
3
2
  import { bridgedNode } from './matterbridgeDeviceTypes.js';
4
- import { deepCopy, isValidNumber } from './utils/utils.js';
3
+ import { isValidNumber, isValidObject } from './utils/utils.js';
5
4
  import { MatterbridgeBehavior, MatterbridgeBehaviorDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, } from './matterbridgeBehaviors.js';
5
+ import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, } from './matterbridgeEndpointHelpers.js';
6
6
  import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, VendorId } from '@matter/main';
7
7
  import { getClusterNameById, MeasurementType } from '@matter/main/types';
8
8
  import { Descriptor } from '@matter/main/clusters/descriptor';
9
9
  import { PowerSource } from '@matter/main/clusters/power-source';
10
- import { UserLabel } from '@matter/main/clusters/user-label';
11
- import { FixedLabel } from '@matter/main/clusters/fixed-label';
12
- import { BasicInformation } from '@matter/main/clusters/basic-information';
13
10
  import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
14
11
  import { Identify } from '@matter/main/clusters/identify';
15
- import { Groups } from '@matter/main/clusters/groups';
16
12
  import { OnOff } from '@matter/main/clusters/on-off';
17
13
  import { LevelControl } from '@matter/main/clusters/level-control';
18
14
  import { ColorControl } from '@matter/main/clusters/color-control';
@@ -20,39 +16,18 @@ import { WindowCovering } from '@matter/main/clusters/window-covering';
20
16
  import { Thermostat } from '@matter/main/clusters/thermostat';
21
17
  import { FanControl } from '@matter/main/clusters/fan-control';
22
18
  import { DoorLock } from '@matter/main/clusters/door-lock';
23
- import { ModeSelect } from '@matter/main/clusters/mode-select';
24
19
  import { ValveConfigurationAndControl } from '@matter/main/clusters/valve-configuration-and-control';
25
20
  import { PumpConfigurationAndControl } from '@matter/main/clusters/pump-configuration-and-control';
26
21
  import { SmokeCoAlarm } from '@matter/main/clusters/smoke-co-alarm';
27
22
  import { Switch } from '@matter/main/clusters/switch';
28
- import { BooleanState } from '@matter/main/clusters/boolean-state';
29
23
  import { BooleanStateConfiguration } from '@matter/main/clusters/boolean-state-configuration';
30
24
  import { PowerTopology } from '@matter/main/clusters/power-topology';
31
25
  import { ElectricalPowerMeasurement } from '@matter/main/clusters/electrical-power-measurement';
32
26
  import { ElectricalEnergyMeasurement } from '@matter/main/clusters/electrical-energy-measurement';
33
- import { TemperatureMeasurement } from '@matter/main/clusters/temperature-measurement';
34
- import { RelativeHumidityMeasurement } from '@matter/main/clusters/relative-humidity-measurement';
35
- import { PressureMeasurement } from '@matter/main/clusters/pressure-measurement';
36
- import { FlowMeasurement } from '@matter/main/clusters/flow-measurement';
37
- import { IlluminanceMeasurement } from '@matter/main/clusters/illuminance-measurement';
38
- import { OccupancySensing } from '@matter/main/clusters/occupancy-sensing';
39
27
  import { AirQuality } from '@matter/main/clusters/air-quality';
40
- import { CarbonMonoxideConcentrationMeasurement } from '@matter/main/clusters/carbon-monoxide-concentration-measurement';
41
- import { CarbonDioxideConcentrationMeasurement } from '@matter/main/clusters/carbon-dioxide-concentration-measurement';
42
- import { NitrogenDioxideConcentrationMeasurement } from '@matter/main/clusters/nitrogen-dioxide-concentration-measurement';
43
- import { OzoneConcentrationMeasurement } from '@matter/main/clusters/ozone-concentration-measurement';
44
- import { FormaldehydeConcentrationMeasurement } from '@matter/main/clusters/formaldehyde-concentration-measurement';
45
- import { Pm1ConcentrationMeasurement } from '@matter/main/clusters/pm1-concentration-measurement';
46
- import { Pm25ConcentrationMeasurement } from '@matter/main/clusters/pm25-concentration-measurement';
47
- import { Pm10ConcentrationMeasurement } from '@matter/main/clusters/pm10-concentration-measurement';
48
- import { RadonConcentrationMeasurement } from '@matter/main/clusters/radon-concentration-measurement';
49
- import { TotalVolatileOrganicCompoundsConcentrationMeasurement } from '@matter/main/clusters/total-volatile-organic-compounds-concentration-measurement';
50
28
  import { ConcentrationMeasurement } from '@matter/main/clusters/concentration-measurement';
51
29
  import { DescriptorServer } from '@matter/main/behaviors/descriptor';
52
30
  import { PowerSourceServer } from '@matter/main/behaviors/power-source';
53
- import { UserLabelServer } from '@matter/main/behaviors/user-label';
54
- import { FixedLabelServer } from '@matter/main/behaviors/fixed-label';
55
- import { BasicInformationServer } from '@matter/main/behaviors/basic-information';
56
31
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
57
32
  import { GroupsServer } from '@matter/main/behaviors/groups';
58
33
  import { ScenesManagementServer } from '@matter/main/behaviors/scenes-management';
@@ -79,242 +54,6 @@ import { Pm25ConcentrationMeasurementServer } from '@matter/main/behaviors/pm25-
79
54
  import { Pm10ConcentrationMeasurementServer } from '@matter/main/behaviors/pm10-concentration-measurement';
80
55
  import { RadonConcentrationMeasurementServer } from '@matter/main/behaviors/radon-concentration-measurement';
81
56
  import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/main/behaviors/total-volatile-organic-compounds-concentration-measurement';
82
- function capitalizeFirstLetter(name) {
83
- if (!name)
84
- return name;
85
- return name.charAt(0).toUpperCase() + name.slice(1);
86
- }
87
- function lowercaseFirstLetter(name) {
88
- if (!name)
89
- return name;
90
- return name.charAt(0).toLowerCase() + name.slice(1);
91
- }
92
- function createUniqueId(param1, param2, param3, param4) {
93
- const hash = createHash('md5');
94
- hash.update(param1 + param2 + param3 + param4);
95
- return hash.digest('hex');
96
- }
97
- function getBehaviourTypesFromClusterServerIds(clusterServerList) {
98
- const behaviorTypes = [];
99
- clusterServerList.forEach((clusterId) => {
100
- behaviorTypes.push(getBehaviourTypeFromClusterServerId(clusterId));
101
- });
102
- return behaviorTypes;
103
- }
104
- function getBehaviourTypesFromClusterClientIds(clusterClientList) {
105
- const behaviorTypes = [];
106
- clusterClientList.forEach((clusterId) => {
107
- });
108
- return behaviorTypes;
109
- }
110
- function getBehaviourTypeFromClusterServerId(clusterId) {
111
- if (clusterId === PowerSource.Cluster.id)
112
- return PowerSourceServer.with(PowerSource.Feature.Wired);
113
- if (clusterId === UserLabel.Cluster.id)
114
- return UserLabelServer;
115
- if (clusterId === FixedLabel.Cluster.id)
116
- return FixedLabelServer;
117
- if (clusterId === BasicInformation.Cluster.id)
118
- return BasicInformationServer;
119
- if (clusterId === BridgedDeviceBasicInformation.Cluster.id)
120
- return BridgedDeviceBasicInformationServer;
121
- if (clusterId === Identify.Cluster.id)
122
- return MatterbridgeIdentifyServer;
123
- if (clusterId === Groups.Cluster.id)
124
- return GroupsServer;
125
- if (clusterId === OnOff.Cluster.id)
126
- return MatterbridgeOnOffServer.with('Lighting');
127
- if (clusterId === LevelControl.Cluster.id)
128
- return MatterbridgeLevelControlServer.with('OnOff', 'Lighting');
129
- if (clusterId === ColorControl.Cluster.id)
130
- return MatterbridgeColorControlServer;
131
- if (clusterId === WindowCovering.Cluster.id)
132
- return MatterbridgeWindowCoveringServer.with('Lift', 'PositionAwareLift');
133
- if (clusterId === Thermostat.Cluster.id)
134
- return MatterbridgeThermostatServer.with('AutoMode', 'Heating', 'Cooling');
135
- if (clusterId === FanControl.Cluster.id)
136
- return MatterbridgeFanControlServer;
137
- if (clusterId === DoorLock.Cluster.id)
138
- return MatterbridgeDoorLockServer;
139
- if (clusterId === ModeSelect.Cluster.id)
140
- return MatterbridgeModeSelectServer;
141
- if (clusterId === ValveConfigurationAndControl.Cluster.id)
142
- return MatterbridgeValveConfigurationAndControlServer.with('Level');
143
- if (clusterId === PumpConfigurationAndControl.Cluster.id)
144
- return PumpConfigurationAndControlServer.with('ConstantSpeed');
145
- if (clusterId === SmokeCoAlarm.Cluster.id)
146
- return MatterbridgeSmokeCoAlarmServer.with('SmokeAlarm', 'CoAlarm');
147
- if (clusterId === Switch.Cluster.id)
148
- return SwitchServer.with('MomentarySwitch', 'MomentarySwitchRelease', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress');
149
- if (clusterId === BooleanState.Cluster.id)
150
- return BooleanStateServer.enable({ events: { stateChange: true } });
151
- if (clusterId === BooleanStateConfiguration.Cluster.id)
152
- return MatterbridgeBooleanStateConfigurationServer;
153
- if (clusterId === PowerTopology.Cluster.id)
154
- return PowerTopologyServer.with('TreeTopology');
155
- if (clusterId === ElectricalPowerMeasurement.Cluster.id)
156
- return ElectricalPowerMeasurementServer.with('AlternatingCurrent');
157
- if (clusterId === ElectricalEnergyMeasurement.Cluster.id)
158
- return ElectricalEnergyMeasurementServer.with('ImportedEnergy', 'ExportedEnergy', 'CumulativeEnergy');
159
- if (clusterId === TemperatureMeasurement.Cluster.id)
160
- return TemperatureMeasurementServer;
161
- if (clusterId === RelativeHumidityMeasurement.Cluster.id)
162
- return RelativeHumidityMeasurementServer;
163
- if (clusterId === PressureMeasurement.Cluster.id)
164
- return PressureMeasurementServer;
165
- if (clusterId === FlowMeasurement.Cluster.id)
166
- return FlowMeasurementServer;
167
- if (clusterId === IlluminanceMeasurement.Cluster.id)
168
- return IlluminanceMeasurementServer;
169
- if (clusterId === OccupancySensing.Cluster.id)
170
- return OccupancySensingServer;
171
- if (clusterId === AirQuality.Cluster.id)
172
- return AirQualityServer.with('Fair', 'Moderate', 'VeryPoor', 'ExtremelyPoor');
173
- if (clusterId === CarbonMonoxideConcentrationMeasurement.Cluster.id)
174
- return CarbonMonoxideConcentrationMeasurementServer.with('NumericMeasurement');
175
- if (clusterId === CarbonDioxideConcentrationMeasurement.Cluster.id)
176
- return CarbonDioxideConcentrationMeasurementServer.with('NumericMeasurement');
177
- if (clusterId === NitrogenDioxideConcentrationMeasurement.Cluster.id)
178
- return NitrogenDioxideConcentrationMeasurementServer.with('NumericMeasurement');
179
- if (clusterId === OzoneConcentrationMeasurement.Cluster.id)
180
- return OzoneConcentrationMeasurementServer.with('NumericMeasurement');
181
- if (clusterId === FormaldehydeConcentrationMeasurement.Cluster.id)
182
- return FormaldehydeConcentrationMeasurementServer.with('NumericMeasurement');
183
- if (clusterId === Pm1ConcentrationMeasurement.Cluster.id)
184
- return Pm1ConcentrationMeasurementServer.with('NumericMeasurement');
185
- if (clusterId === Pm25ConcentrationMeasurement.Cluster.id)
186
- return Pm25ConcentrationMeasurementServer.with('NumericMeasurement');
187
- if (clusterId === Pm10ConcentrationMeasurement.Cluster.id)
188
- return Pm10ConcentrationMeasurementServer.with('NumericMeasurement');
189
- if (clusterId === RadonConcentrationMeasurement.Cluster.id)
190
- return RadonConcentrationMeasurementServer.with('NumericMeasurement');
191
- if (clusterId === TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id)
192
- return TotalVolatileOrganicCompoundsConcentrationMeasurementServer.with('NumericMeasurement');
193
- return MatterbridgeIdentifyServer;
194
- }
195
- function getBehaviourTypeFromClusterClientId(clusterId) {
196
- }
197
- function getBehavior(endpoint, cluster) {
198
- let behavior;
199
- if (typeof cluster === 'string') {
200
- behavior = endpoint.behaviors.supported[lowercaseFirstLetter(cluster)];
201
- }
202
- else if (typeof cluster === 'number') {
203
- behavior = endpoint.behaviors.supported[lowercaseFirstLetter(getClusterNameById(cluster))];
204
- }
205
- else if (typeof cluster === 'object') {
206
- behavior = endpoint.behaviors.supported[lowercaseFirstLetter(cluster.name)];
207
- }
208
- else if (typeof cluster === 'function') {
209
- behavior = cluster;
210
- }
211
- return behavior;
212
- }
213
- function addRequiredClusterServers(endpoint) {
214
- const requiredServerList = [];
215
- endpoint.log.debug(`addRequiredClusterServers for ${CYAN}${endpoint.maybeId}${db}`);
216
- Array.from(endpoint.deviceTypes.values()).forEach((deviceType) => {
217
- endpoint.log.debug(`- for deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
218
- deviceType.requiredServerClusters.forEach((clusterId) => {
219
- if (!requiredServerList.includes(clusterId) && !endpoint.hasClusterServer(clusterId)) {
220
- requiredServerList.push(clusterId);
221
- endpoint.log.debug(`- cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
222
- }
223
- });
224
- });
225
- addClusterServers(endpoint, requiredServerList);
226
- }
227
- function addOptionalClusterServers(endpoint) {
228
- const optionalServerList = [];
229
- endpoint.log.debug(`addOptionalClusterServers for ${CYAN}${endpoint.maybeId}${db}`);
230
- Array.from(endpoint.deviceTypes.values()).forEach((deviceType) => {
231
- endpoint.log.debug(`- for deviceType: ${zb}${'0x' + deviceType.code.toString(16).padStart(4, '0')}${db}-${zb}${deviceType.name}${db}`);
232
- deviceType.optionalServerClusters.forEach((clusterId) => {
233
- if (!optionalServerList.includes(clusterId) && !endpoint.hasClusterServer(clusterId)) {
234
- optionalServerList.push(clusterId);
235
- endpoint.log.debug(`- cluster: ${hk}${'0x' + clusterId.toString(16).padStart(4, '0')}${db}-${hk}${getClusterNameById(clusterId)}${db}`);
236
- }
237
- });
238
- });
239
- addClusterServers(endpoint, optionalServerList);
240
- }
241
- function addClusterServers(endpoint, serverList) {
242
- if (serverList.includes(PowerSource.Cluster.id))
243
- endpoint.createDefaultPowerSourceWiredClusterServer();
244
- if (serverList.includes(Identify.Cluster.id))
245
- endpoint.createDefaultIdentifyClusterServer();
246
- if (serverList.includes(Groups.Cluster.id))
247
- endpoint.createDefaultGroupsClusterServer();
248
- if (serverList.includes(OnOff.Cluster.id))
249
- endpoint.createDefaultOnOffClusterServer();
250
- if (serverList.includes(LevelControl.Cluster.id))
251
- endpoint.createDefaultLevelControlClusterServer();
252
- if (serverList.includes(ColorControl.Cluster.id))
253
- endpoint.createDefaultColorControlClusterServer();
254
- if (serverList.includes(WindowCovering.Cluster.id))
255
- endpoint.createDefaultWindowCoveringClusterServer();
256
- if (serverList.includes(Thermostat.Cluster.id))
257
- endpoint.createDefaultThermostatClusterServer();
258
- if (serverList.includes(FanControl.Cluster.id))
259
- endpoint.createDefaultFanControlClusterServer();
260
- if (serverList.includes(DoorLock.Cluster.id))
261
- endpoint.createDefaultDoorLockClusterServer();
262
- if (serverList.includes(ValveConfigurationAndControl.Cluster.id))
263
- endpoint.createDefaultValveConfigurationAndControlClusterServer();
264
- if (serverList.includes(PumpConfigurationAndControl.Cluster.id))
265
- endpoint.createDefaultPumpConfigurationAndControlClusterServer();
266
- if (serverList.includes(SmokeCoAlarm.Cluster.id))
267
- endpoint.createDefaultSmokeCOAlarmClusterServer();
268
- if (serverList.includes(Switch.Cluster.id))
269
- endpoint.createDefaultSwitchClusterServer();
270
- if (serverList.includes(BooleanState.Cluster.id))
271
- endpoint.createDefaultBooleanStateClusterServer();
272
- if (serverList.includes(BooleanStateConfiguration.Cluster.id))
273
- endpoint.createDefaultBooleanStateConfigurationClusterServer();
274
- if (serverList.includes(PowerTopology.Cluster.id))
275
- endpoint.createDefaultPowerTopologyClusterServer();
276
- if (serverList.includes(ElectricalPowerMeasurement.Cluster.id))
277
- endpoint.createDefaultElectricalPowerMeasurementClusterServer();
278
- if (serverList.includes(ElectricalEnergyMeasurement.Cluster.id))
279
- endpoint.createDefaultElectricalEnergyMeasurementClusterServer();
280
- if (serverList.includes(TemperatureMeasurement.Cluster.id))
281
- endpoint.createDefaultTemperatureMeasurementClusterServer();
282
- if (serverList.includes(RelativeHumidityMeasurement.Cluster.id))
283
- endpoint.createDefaultRelativeHumidityMeasurementClusterServer();
284
- if (serverList.includes(PressureMeasurement.Cluster.id))
285
- endpoint.createDefaultPressureMeasurementClusterServer();
286
- if (serverList.includes(FlowMeasurement.Cluster.id))
287
- endpoint.createDefaultFlowMeasurementClusterServer();
288
- if (serverList.includes(IlluminanceMeasurement.Cluster.id))
289
- endpoint.createDefaultIlluminanceMeasurementClusterServer();
290
- if (serverList.includes(OccupancySensing.Cluster.id))
291
- endpoint.createDefaultOccupancySensingClusterServer();
292
- if (serverList.includes(AirQuality.Cluster.id))
293
- endpoint.createDefaultAirQualityClusterServer();
294
- if (serverList.includes(CarbonMonoxideConcentrationMeasurement.Cluster.id))
295
- endpoint.createDefaultCarbonMonoxideConcentrationMeasurementClusterServer();
296
- if (serverList.includes(CarbonDioxideConcentrationMeasurement.Cluster.id))
297
- endpoint.createDefaultCarbonDioxideConcentrationMeasurementClusterServer();
298
- if (serverList.includes(NitrogenDioxideConcentrationMeasurement.Cluster.id))
299
- endpoint.createDefaultNitrogenDioxideConcentrationMeasurementClusterServer();
300
- if (serverList.includes(OzoneConcentrationMeasurement.Cluster.id))
301
- endpoint.createDefaultOzoneConcentrationMeasurementClusterServer();
302
- if (serverList.includes(FormaldehydeConcentrationMeasurement.Cluster.id))
303
- endpoint.createDefaultFormaldehydeConcentrationMeasurementClusterServer();
304
- if (serverList.includes(Pm1ConcentrationMeasurement.Cluster.id))
305
- endpoint.createDefaultPm1ConcentrationMeasurementClusterServer();
306
- if (serverList.includes(Pm25ConcentrationMeasurement.Cluster.id))
307
- endpoint.createDefaultPm25ConcentrationMeasurementClusterServer();
308
- if (serverList.includes(Pm10ConcentrationMeasurement.Cluster.id))
309
- endpoint.createDefaultPm10ConcentrationMeasurementClusterServer();
310
- if (serverList.includes(RadonConcentrationMeasurement.Cluster.id))
311
- endpoint.createDefaultRadonConcentrationMeasurementClusterServer();
312
- if (serverList.includes(TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id))
313
- endpoint.createDefaultTvocMeasurementClusterServer();
314
- }
315
- export function optionsFor(type, options) {
316
- return options;
317
- }
318
57
  export class MatterbridgeEndpoint extends Endpoint {
319
58
  static bridgeMode = '';
320
59
  static logLevel = "info";
@@ -337,7 +76,6 @@ export class MatterbridgeEndpoint extends Endpoint {
337
76
  deviceType;
338
77
  uniqueStorageKey = undefined;
339
78
  tagList = undefined;
340
- subType = '';
341
79
  deviceTypes = new Map();
342
80
  commandHandler = new NamedHandler();
343
81
  constructor(definition, options = {}, debug = false) {
@@ -412,7 +150,8 @@ export class MatterbridgeEndpoint extends Endpoint {
412
150
  if (!behavior || !this.behaviors.supported[behavior.id])
413
151
  return false;
414
152
  const options = this.behaviors.optionsFor(behavior);
415
- return lowercaseFirstLetter(attribute) in options;
153
+ const defaults = this.behaviors.defaultsFor(behavior);
154
+ return lowercaseFirstLetter(attribute) in options || lowercaseFirstLetter(attribute) in defaults;
416
155
  }
417
156
  getClusterServerOptions(cluster) {
418
157
  const behavior = getBehavior(this, cluster);
@@ -420,50 +159,14 @@ export class MatterbridgeEndpoint extends Endpoint {
420
159
  return undefined;
421
160
  return this.behaviors.optionsFor(behavior);
422
161
  }
423
- getAttribute(clusterId, attribute, log) {
424
- const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
425
- if (this.construction.status !== Lifecycle.Status.Active) {
426
- this.log.error(`getAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${this.maybeId}${er}:${or}${this.maybeNumber}${er} is in the ${BLUE}${this.construction.status}${er} state`);
427
- return undefined;
428
- }
429
- const state = this.state;
430
- if (!(clusterName in state)) {
431
- this.log.error(`getAttribute error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${this.id}${er}:${or}${this.number}${er}`);
432
- return undefined;
433
- }
434
- attribute = lowercaseFirstLetter(attribute);
435
- if (!(attribute in state[clusterName])) {
436
- this.log.error(`getAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${this.id}${er}:${or}${this.number}${er}`);
437
- return undefined;
438
- }
439
- const value = state[clusterName][attribute];
440
- log?.info(`${db}Get endpoint ${or}${this.id}${db}:${or}${this.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} value ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
441
- return value;
162
+ getAttribute(cluster, attribute, log) {
163
+ return getAttribute(this, cluster, attribute, log);
442
164
  }
443
165
  async setAttribute(clusterId, attribute, value, log) {
444
- const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
445
- if (this.construction.status !== Lifecycle.Status.Active) {
446
- this.log.error(`setAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${this.maybeId}${er}:${or}${this.maybeNumber}${er} is in the ${BLUE}${this.construction.status}${er} state`);
447
- return false;
448
- }
449
- const state = this.state;
450
- if (!(clusterName in state)) {
451
- this.log.error(`setAttribute ${hk}${attribute}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${this.id}${er}:${or}${this.number}${er}`);
452
- return false;
453
- }
454
- attribute = lowercaseFirstLetter(attribute);
455
- if (!(attribute in state[clusterName])) {
456
- this.log.error(`setAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${this.id}${er}:${or}${this.number}${er}`);
457
- return false;
458
- }
459
- let oldValue = state[clusterName][attribute];
460
- if (typeof oldValue === 'object')
461
- oldValue = deepCopy(oldValue);
462
- await this.setStateOf(this.behaviors.supported[clusterName], { [attribute]: value });
463
- log?.info(`${db}Set endpoint ${or}${this.id}${db}:${or}${this.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
464
- `from ${YELLOW}${oldValue !== null && typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
465
- `to ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
466
- return true;
166
+ return await setAttribute(this, clusterId, attribute, value, log);
167
+ }
168
+ async updateAttribute(cluster, attribute, value, log) {
169
+ return await updateAttribute(this, cluster, attribute, value, log);
467
170
  }
468
171
  async subscribeAttribute(clusterId, attribute, listener, log) {
469
172
  const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
@@ -484,52 +187,30 @@ export class MatterbridgeEndpoint extends Endpoint {
484
187
  log?.info(`${db}Subscribed endpoint ${or}${this.id}${db}:${or}${this.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db}`);
485
188
  return true;
486
189
  }
487
- async triggerEvent(clusterId, event, payload, log, endpoint) {
488
- if (!endpoint)
489
- endpoint = this;
190
+ async triggerEvent(clusterId, event, payload, log) {
490
191
  const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
491
- if (endpoint.construction.status !== Lifecycle.Status.Active) {
492
- this.log.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
192
+ if (this.construction.status !== Lifecycle.Status.Active) {
193
+ this.log.error(`triggerEvent ${hk}${clusterName}.${event}${er} error: Endpoint ${or}${this.maybeId}${er}:${or}${this.maybeNumber}${er} is in the ${BLUE}${this.construction.status}${er} state`);
493
194
  return false;
494
195
  }
495
- const events = endpoint.events;
196
+ const events = this.events;
496
197
  if (!(clusterName in events) || !(event in events[clusterName])) {
497
- this.log.error(`triggerEvent ${hk}${event}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
198
+ this.log.error(`triggerEvent ${hk}${event}${er} error: Cluster ${'0x' + clusterId.toString(16).padStart(4, '0')}:${clusterName} not found on endpoint ${or}${this.id}${er}:${or}${this.number}${er}`);
498
199
  return false;
499
200
  }
500
- await endpoint.act((agent) => agent[clusterName].events[event].emit(payload, agent.context));
501
- log?.info(`${db}Trigger event ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${event}${db} with ${debugStringify(payload)}${db} on endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} `);
201
+ await this.act((agent) => agent[clusterName].events[event].emit(payload, agent.context));
202
+ log?.info(`${db}Trigger event ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${event}${db} with ${debugStringify(payload)}${db} on endpoint ${or}${this.id}${db}:${or}${this.number}${db} `);
502
203
  return true;
503
204
  }
504
205
  addClusterServers(serverList) {
505
206
  addClusterServers(this, serverList);
506
207
  }
507
208
  async addFixedLabel(label, value) {
508
- if (!this.hasClusterServer(FixedLabel.Cluster.id)) {
509
- this.log.debug(`addFixedLabel: add cluster ${hk}FixedLabel${db}:${hk}fixedLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
510
- this.behaviors.require(FixedLabelServer, {
511
- labelList: [{ label, value }],
512
- });
513
- return this;
514
- }
515
- this.log.debug(`addFixedLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
516
- const labelList = (this.getAttribute(FixedLabel.Cluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
517
- labelList.push({ label, value });
518
- await this.setAttribute(FixedLabel.Cluster.id, 'labelList', labelList, this.log);
209
+ await addFixedLabel(this, label, value);
519
210
  return this;
520
211
  }
521
212
  async addUserLabel(label, value) {
522
- if (!this.hasClusterServer(UserLabel.Cluster.id)) {
523
- this.log.debug(`addUserLabel: add cluster ${hk}UserLabel${db}:${hk}userLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
524
- this.behaviors.require(UserLabelServer, {
525
- labelList: [{ label, value }],
526
- });
527
- return this;
528
- }
529
- this.log.debug(`addUserLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
530
- const labelList = (this.getAttribute(UserLabel.Cluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
531
- labelList.push({ label, value });
532
- await this.setAttribute(UserLabel.Cluster.id, 'labelList', labelList, this.log);
213
+ await addUserLabel(this, label, value);
533
214
  return this;
534
215
  }
535
216
  addCommandHandler(command, handler) {
@@ -553,6 +234,21 @@ export class MatterbridgeEndpoint extends Endpoint {
553
234
  getAllClusterServerNames() {
554
235
  return Object.keys(this.behaviors.supported);
555
236
  }
237
+ forEachAttribute(callback) {
238
+ for (const [clusterName, clusterAttributes] of Object.entries(this.state)) {
239
+ for (const [attributeName, attributeValue] of Object.entries(clusterAttributes)) {
240
+ const clusterId = getClusterId(this, clusterName);
241
+ if (clusterId === undefined) {
242
+ continue;
243
+ }
244
+ const attributeId = getAttributeId(this, clusterName, attributeName);
245
+ if (attributeId === undefined) {
246
+ continue;
247
+ }
248
+ callback(clusterName, clusterId, attributeName, attributeId, attributeValue);
249
+ }
250
+ }
251
+ }
556
252
  addChildDeviceType(endpointName, definition, options = {}, debug = false) {
557
253
  this.log.debug(`addChildDeviceType: ${CYAN}${endpointName}${db}`);
558
254
  let alreadyAdded = false;
@@ -943,7 +639,6 @@ export class MatterbridgeEndpoint extends Endpoint {
943
639
  await this.setAttribute(ColorControl.Cluster.id, 'colorMode', colorMode, this.log);
944
640
  await this.setAttribute(ColorControl.Cluster.id, 'enhancedColorMode', colorMode, this.log);
945
641
  }
946
- return this;
947
642
  }
948
643
  createDefaultWindowCoveringClusterServer(positionPercent100ths) {
949
644
  this.behaviors.require(MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
@@ -967,7 +662,7 @@ export class MatterbridgeEndpoint extends Endpoint {
967
662
  }
968
663
  async setWindowCoveringTargetAsCurrentAndStopped() {
969
664
  const position = this.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.log);
970
- if (position !== null) {
665
+ if (isValidNumber(position, 0, 10000)) {
971
666
  await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position, this.log);
972
667
  await this.setAttribute(WindowCovering.Cluster.id, 'operationalStatus', {
973
668
  global: WindowCovering.MovementStatus.Stopped,
@@ -997,8 +692,10 @@ export class MatterbridgeEndpoint extends Endpoint {
997
692
  }
998
693
  getWindowCoveringStatus() {
999
694
  const status = this.getAttribute(WindowCovering.Cluster.id, 'operationalStatus', this.log);
1000
- this.log.debug(`Get WindowCovering operationalStatus: ${status.global}`);
1001
- return status.global;
695
+ if (isValidObject(status, 3) && 'global' in status && typeof status.global === 'number') {
696
+ this.log.debug(`Get WindowCovering operationalStatus: ${status.global}`);
697
+ return status.global;
698
+ }
1002
699
  }
1003
700
  async setWindowCoveringTargetAndCurrentPosition(position) {
1004
701
  await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', position, this.log);
@@ -1286,76 +983,28 @@ export class MatterbridgeEndpoint extends Endpoint {
1286
983
  });
1287
984
  return this;
1288
985
  }
1289
- getDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1290
- return optionsFor(TemperatureMeasurementServer, {
1291
- measuredValue,
1292
- minMeasuredValue: null,
1293
- maxMeasuredValue: null,
1294
- tolerance: 0,
1295
- });
1296
- }
1297
986
  createDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1298
- this.behaviors.require(TemperatureMeasurementServer, this.getDefaultTemperatureMeasurementClusterServer(measuredValue));
987
+ this.behaviors.require(TemperatureMeasurementServer, getDefaultTemperatureMeasurementClusterServer(measuredValue));
1299
988
  return this;
1300
989
  }
1301
- getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1302
- return optionsFor(RelativeHumidityMeasurementServer, {
1303
- measuredValue,
1304
- minMeasuredValue: null,
1305
- maxMeasuredValue: null,
1306
- tolerance: 0,
1307
- });
1308
- }
1309
990
  createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1310
- this.behaviors.require(RelativeHumidityMeasurementServer, this.getDefaultRelativeHumidityMeasurementClusterServer(measuredValue));
991
+ this.behaviors.require(RelativeHumidityMeasurementServer, getDefaultRelativeHumidityMeasurementClusterServer(measuredValue));
1311
992
  return this;
1312
993
  }
1313
- getDefaultPressureMeasurementClusterServer(measuredValue = 1000) {
1314
- return optionsFor(PressureMeasurementServer, {
1315
- measuredValue,
1316
- minMeasuredValue: null,
1317
- maxMeasuredValue: null,
1318
- tolerance: 0,
1319
- });
1320
- }
1321
994
  createDefaultPressureMeasurementClusterServer(measuredValue = 1000) {
1322
- this.behaviors.require(PressureMeasurementServer, this.getDefaultPressureMeasurementClusterServer(measuredValue));
995
+ this.behaviors.require(PressureMeasurementServer, getDefaultPressureMeasurementClusterServer(measuredValue));
1323
996
  return this;
1324
997
  }
1325
- getDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) {
1326
- return optionsFor(IlluminanceMeasurementServer, {
1327
- measuredValue,
1328
- minMeasuredValue: null,
1329
- maxMeasuredValue: null,
1330
- tolerance: 0,
1331
- });
1332
- }
1333
998
  createDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) {
1334
- this.behaviors.require(IlluminanceMeasurementServer, this.getDefaultIlluminanceMeasurementClusterServer(measuredValue));
999
+ this.behaviors.require(IlluminanceMeasurementServer, getDefaultIlluminanceMeasurementClusterServer(measuredValue));
1335
1000
  return this;
1336
1001
  }
1337
- getDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1338
- return optionsFor(FlowMeasurementServer, {
1339
- measuredValue,
1340
- minMeasuredValue: null,
1341
- maxMeasuredValue: null,
1342
- tolerance: 0,
1343
- });
1344
- }
1345
1002
  createDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1346
- this.behaviors.require(FlowMeasurementServer, this.getDefaultFlowMeasurementClusterServer(measuredValue));
1003
+ this.behaviors.require(FlowMeasurementServer, getDefaultFlowMeasurementClusterServer(measuredValue));
1347
1004
  return this;
1348
1005
  }
1349
- getDefaultOccupancySensingClusterServer(occupied = false) {
1350
- return optionsFor(OccupancySensingServer, {
1351
- occupancy: { occupied },
1352
- occupancySensorType: OccupancySensing.OccupancySensorType.Pir,
1353
- occupancySensorTypeBitmap: { pir: true, ultrasonic: false, physicalContact: false },
1354
- pirOccupiedToUnoccupiedDelay: 30,
1355
- });
1356
- }
1357
1006
  createDefaultOccupancySensingClusterServer(occupied = false) {
1358
- this.behaviors.require(OccupancySensingServer, this.getDefaultOccupancySensingClusterServer(occupied));
1007
+ this.behaviors.require(OccupancySensingServer, getDefaultOccupancySensingClusterServer(occupied));
1359
1008
  return this;
1360
1009
  }
1361
1010
  createDefaultAirQualityClusterServer(airQuality = AirQuality.AirQualityEnum.Unknown) {