matterbridge 2.1.0-dev.4 → 2.1.0-dev.6

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.
package/CHANGELOG.md CHANGED
@@ -16,12 +16,12 @@ Tamer (https://github.com/tammeryousef1006) has created the Matterbridge Discord
16
16
  ### Breaking Changes
17
17
 
18
18
  Starting from v. 2.0.0 Matterbridge is running only in mode edge (no parameter needed and no badge in the frontend).
19
- The legacy old api have been removed.
19
+ With this release v. 2.1.0, the legacy old api of matter.js have been completely removed from Matterbridge and from all plugins.
20
+ For this reason there is no compatibility for old versions of the plugins.
21
+ You need to update all plugins you use and Matterbridge in the same moment.
22
+ I suggest to first update all plugins without restarting and then to update Matterbridge so when it restarts, all versions will be the latest.
20
23
 
21
- The frontend has a new dark and light mode. The dark mode is now the default mode.
22
- It is possible to change the mode (Classic, Dark or Light) in Settings, Matterbridge settings.
23
-
24
- ## [2.1.0.dev.4] - 2025-01-28
24
+ ## [2.1.0.dev.6] - 2025-01-29
25
25
 
26
26
  ### Added
27
27
 
@@ -31,10 +31,10 @@ It is possible to change the mode (Classic, Dark or Light) in Settings, Matterbr
31
31
 
32
32
  ### Changed
33
33
 
34
+ - [package]: Removed legacy imports.
34
35
  - [package]: Update dependencies.
35
- - [package]: Update matter.js to 0.12.1.
36
36
  - [package]: Update matter.js to 0.12.0.
37
- - [package]: Removed legacy imports.
37
+ - [package]: Update matter.js to 0.12.1.
38
38
 
39
39
  ### Fixed
40
40
 
package/dist/frontend.js CHANGED
@@ -830,7 +830,7 @@ export class Frontend {
830
830
  return '';
831
831
  };
832
832
  let attributes = '';
833
- device.forEachAttribute((clusterName, attributeName, attributeValue) => {
833
+ device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
834
834
  if (typeof attributeValue === 'undefined')
835
835
  return;
836
836
  if (clusterName === 'onOff' && attributeName === 'onOff')
@@ -881,12 +881,20 @@ export class Frontend {
881
881
  attributes += `Speed: ${attributeValue} `;
882
882
  if (clusterName === 'occupancySensing' && attributeName === 'occupancy' && isValidObject(attributeValue, 1))
883
883
  attributes += `Occupancy: ${attributeValue.occupied} `;
884
- if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue')
885
- attributes += `Illuminance: ${attributeValue} `;
884
+ if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue' && isValidNumber(attributeValue))
885
+ attributes += `Illuminance: ${Math.round(Math.max(Math.pow(10, attributeValue / 10000), 0))} `;
886
886
  if (clusterName === 'airQuality' && attributeName === 'airQuality')
887
887
  attributes += `Air quality: ${attributeValue} `;
888
- if (clusterName === 'tvocMeasurement' && attributeName === 'measuredValue')
888
+ if (clusterName === 'totalVolatileOrganicCompoundsConcentrationMeasurement' && attributeName === 'measuredValue')
889
889
  attributes += `Voc: ${attributeValue} `;
890
+ if (clusterName === 'pm1ConcentrationMeasurement' && attributeName === 'measuredValue')
891
+ attributes += `Pm1: ${attributeValue} `;
892
+ if (clusterName === 'pm25ConcentrationMeasurement' && attributeName === 'measuredValue')
893
+ attributes += `Pm2.5: ${attributeValue} `;
894
+ if (clusterName === 'pm10ConcentrationMeasurement' && attributeName === 'measuredValue')
895
+ attributes += `Pm10: ${attributeValue} `;
896
+ if (clusterName === 'formaldehydeConcentrationMeasurement' && attributeName === 'measuredValue')
897
+ attributes += `CH₂O: ${attributeValue} `;
890
898
  if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue' && isValidNumber(attributeValue))
891
899
  attributes += `Temperature: ${attributeValue / 100}°C `;
892
900
  if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue' && isValidNumber(attributeValue))
@@ -0,0 +1 @@
1
+ export * from '@matter/node/behaviors';
@@ -0,0 +1 @@
1
+ export * from '@matter/types/clusters';
@@ -0,0 +1 @@
1
+ export * from '@matter/node/devices';
@@ -0,0 +1 @@
1
+ export { AggregatorEndpoint, ElectricalSensorEndpoint, PowerSourceEndpoint, BridgedNodeEndpoint, RootEndpoint, DeviceEnergyManagementEndpoint, OtaProviderEndpoint, OtaRequestorEndpoint } from '@matter/node/endpoints';
@@ -1,6 +1,3 @@
1
1
  export * from '@matter/main';
2
- export { AggregatorEndpoint } from '@matter/main/endpoints';
3
- export * from '@matter/main/devices';
4
- export * from '@matter/main/behaviors';
5
- export { FabricAction, MdnsService, PaseClient, logEndpoint } from '@matter/main/protocol';
2
+ export { SemanticNamespace, ClosureTag, CompassDirectionTag, CompassLocationTag, DirectionTag, ElectricalMeasurementTag, LaundryTag, LevelTag, LocationTag, NumberTag, PositionTag, PowerSourceTag, RefrigeratorTag, RoomAirConditionerTag, SwitchesTag, } from '@matter/main';
6
3
  export { AttributeElement, ClusterElement, ClusterModel, CommandElement, EventElement, FieldElement } from '@matter/main/model';
@@ -0,0 +1 @@
1
+ export * from '@matter/types';
@@ -1,14 +1,12 @@
1
1
  import { AnsiLogger, BLUE, CYAN, YELLOW, db, debugStringify, er, hk, or, zb } from './logger/export.js';
2
2
  import { bridgedNode } from './matterbridgeDeviceTypes.js';
3
- import { deepCopy, isValidNumber } from './utils/utils.js';
3
+ import { isValidNumber, isValidObject } from './utils/utils.js';
4
4
  import { MatterbridgeBehavior, MatterbridgeBehaviorDevice, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, } from './matterbridgeBehaviors.js';
5
- import { addClusterServers, addOptionalClusterServers, addRequiredClusterServers, capitalizeFirstLetter, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, lowercaseFirstLetter, optionsFor, } from './matterbridgeEndpointHelpers.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
10
  import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
13
11
  import { Identify } from '@matter/main/clusters/identify';
14
12
  import { OnOff } from '@matter/main/clusters/on-off';
@@ -26,13 +24,10 @@ import { BooleanStateConfiguration } from '@matter/main/clusters/boolean-state-c
26
24
  import { PowerTopology } from '@matter/main/clusters/power-topology';
27
25
  import { ElectricalPowerMeasurement } from '@matter/main/clusters/electrical-power-measurement';
28
26
  import { ElectricalEnergyMeasurement } from '@matter/main/clusters/electrical-energy-measurement';
29
- import { OccupancySensing } from '@matter/main/clusters/occupancy-sensing';
30
27
  import { AirQuality } from '@matter/main/clusters/air-quality';
31
28
  import { ConcentrationMeasurement } from '@matter/main/clusters/concentration-measurement';
32
29
  import { DescriptorServer } from '@matter/main/behaviors/descriptor';
33
30
  import { PowerSourceServer } from '@matter/main/behaviors/power-source';
34
- import { UserLabelServer } from '@matter/main/behaviors/user-label';
35
- import { FixedLabelServer } from '@matter/main/behaviors/fixed-label';
36
31
  import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors/bridged-device-basic-information';
37
32
  import { GroupsServer } from '@matter/main/behaviors/groups';
38
33
  import { ScenesManagementServer } from '@matter/main/behaviors/scenes-management';
@@ -164,50 +159,14 @@ export class MatterbridgeEndpoint extends Endpoint {
164
159
  return undefined;
165
160
  return this.behaviors.optionsFor(behavior);
166
161
  }
167
- getAttribute(clusterId, attribute, log) {
168
- const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
169
- if (this.construction.status !== Lifecycle.Status.Active) {
170
- 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`);
171
- return undefined;
172
- }
173
- const state = this.state;
174
- if (!(clusterName in state)) {
175
- 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}`);
176
- return undefined;
177
- }
178
- attribute = lowercaseFirstLetter(attribute);
179
- if (!(attribute in state[clusterName])) {
180
- 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}`);
181
- return undefined;
182
- }
183
- const value = state[clusterName][attribute];
184
- 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}`);
185
- return value;
162
+ getAttribute(cluster, attribute, log) {
163
+ return getAttribute(this, cluster, attribute, log);
186
164
  }
187
165
  async setAttribute(clusterId, attribute, value, log) {
188
- const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
189
- if (this.construction.status !== Lifecycle.Status.Active) {
190
- 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`);
191
- return false;
192
- }
193
- const state = this.state;
194
- if (!(clusterName in state)) {
195
- 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}`);
196
- return false;
197
- }
198
- attribute = lowercaseFirstLetter(attribute);
199
- if (!(attribute in state[clusterName])) {
200
- 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}`);
201
- return false;
202
- }
203
- let oldValue = state[clusterName][attribute];
204
- if (typeof oldValue === 'object')
205
- oldValue = deepCopy(oldValue);
206
- await this.setStateOf(this.behaviors.supported[clusterName], { [attribute]: value });
207
- log?.info(`${db}Set endpoint ${or}${this.id}${db}:${or}${this.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
208
- `from ${YELLOW}${oldValue !== null && typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
209
- `to ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
210
- 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);
211
170
  }
212
171
  async subscribeAttribute(clusterId, attribute, listener, log) {
213
172
  const clusterName = lowercaseFirstLetter(getClusterNameById(clusterId));
@@ -247,31 +206,11 @@ export class MatterbridgeEndpoint extends Endpoint {
247
206
  addClusterServers(this, serverList);
248
207
  }
249
208
  async addFixedLabel(label, value) {
250
- if (!this.hasClusterServer(FixedLabel.Cluster.id)) {
251
- this.log.debug(`addFixedLabel: add cluster ${hk}FixedLabel${db}:${hk}fixedLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
252
- this.behaviors.require(FixedLabelServer, {
253
- labelList: [{ label, value }],
254
- });
255
- return this;
256
- }
257
- this.log.debug(`addFixedLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
258
- const labelList = (this.getAttribute(FixedLabel.Cluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
259
- labelList.push({ label, value });
260
- await this.setAttribute(FixedLabel.Cluster.id, 'labelList', labelList, this.log);
209
+ await addFixedLabel(this, label, value);
261
210
  return this;
262
211
  }
263
212
  async addUserLabel(label, value) {
264
- if (!this.hasClusterServer(UserLabel.Cluster.id)) {
265
- this.log.debug(`addUserLabel: add cluster ${hk}UserLabel${db}:${hk}userLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
266
- this.behaviors.require(UserLabelServer, {
267
- labelList: [{ label, value }],
268
- });
269
- return this;
270
- }
271
- this.log.debug(`addUserLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
272
- const labelList = (this.getAttribute(UserLabel.Cluster.id, 'labelList', this.log) ?? []).filter((entryLabel) => entryLabel.label !== label);
273
- labelList.push({ label, value });
274
- await this.setAttribute(UserLabel.Cluster.id, 'labelList', labelList, this.log);
213
+ await addUserLabel(this, label, value);
275
214
  return this;
276
215
  }
277
216
  addCommandHandler(command, handler) {
@@ -298,9 +237,15 @@ export class MatterbridgeEndpoint extends Endpoint {
298
237
  forEachAttribute(callback) {
299
238
  for (const [clusterName, clusterAttributes] of Object.entries(this.state)) {
300
239
  for (const [attributeName, attributeValue] of Object.entries(clusterAttributes)) {
301
- if (typeof attributeValue === 'undefined')
240
+ const clusterId = getClusterId(this, clusterName);
241
+ if (clusterId === undefined) {
242
+ continue;
243
+ }
244
+ const attributeId = getAttributeId(this, clusterName, attributeName);
245
+ if (attributeId === undefined) {
302
246
  continue;
303
- callback(clusterName, attributeName, attributeValue);
247
+ }
248
+ callback(clusterName, clusterId, attributeName, attributeId, attributeValue);
304
249
  }
305
250
  }
306
251
  }
@@ -694,7 +639,6 @@ export class MatterbridgeEndpoint extends Endpoint {
694
639
  await this.setAttribute(ColorControl.Cluster.id, 'colorMode', colorMode, this.log);
695
640
  await this.setAttribute(ColorControl.Cluster.id, 'enhancedColorMode', colorMode, this.log);
696
641
  }
697
- return this;
698
642
  }
699
643
  createDefaultWindowCoveringClusterServer(positionPercent100ths) {
700
644
  this.behaviors.require(MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift), {
@@ -718,7 +662,7 @@ export class MatterbridgeEndpoint extends Endpoint {
718
662
  }
719
663
  async setWindowCoveringTargetAsCurrentAndStopped() {
720
664
  const position = this.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.log);
721
- if (position !== null) {
665
+ if (isValidNumber(position, 0, 10000)) {
722
666
  await this.setAttribute(WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position, this.log);
723
667
  await this.setAttribute(WindowCovering.Cluster.id, 'operationalStatus', {
724
668
  global: WindowCovering.MovementStatus.Stopped,
@@ -748,8 +692,10 @@ export class MatterbridgeEndpoint extends Endpoint {
748
692
  }
749
693
  getWindowCoveringStatus() {
750
694
  const status = this.getAttribute(WindowCovering.Cluster.id, 'operationalStatus', this.log);
751
- this.log.debug(`Get WindowCovering operationalStatus: ${status.global}`);
752
- 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
+ }
753
699
  }
754
700
  async setWindowCoveringTargetAndCurrentPosition(position) {
755
701
  await this.setAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', position, this.log);
@@ -1037,76 +983,28 @@ export class MatterbridgeEndpoint extends Endpoint {
1037
983
  });
1038
984
  return this;
1039
985
  }
1040
- getDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1041
- return optionsFor(TemperatureMeasurementServer, {
1042
- measuredValue,
1043
- minMeasuredValue: null,
1044
- maxMeasuredValue: null,
1045
- tolerance: 0,
1046
- });
1047
- }
1048
986
  createDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
1049
- this.behaviors.require(TemperatureMeasurementServer, this.getDefaultTemperatureMeasurementClusterServer(measuredValue));
987
+ this.behaviors.require(TemperatureMeasurementServer, getDefaultTemperatureMeasurementClusterServer(measuredValue));
1050
988
  return this;
1051
989
  }
1052
- getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1053
- return optionsFor(RelativeHumidityMeasurementServer, {
1054
- measuredValue,
1055
- minMeasuredValue: null,
1056
- maxMeasuredValue: null,
1057
- tolerance: 0,
1058
- });
1059
- }
1060
990
  createDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
1061
- this.behaviors.require(RelativeHumidityMeasurementServer, this.getDefaultRelativeHumidityMeasurementClusterServer(measuredValue));
991
+ this.behaviors.require(RelativeHumidityMeasurementServer, getDefaultRelativeHumidityMeasurementClusterServer(measuredValue));
1062
992
  return this;
1063
993
  }
1064
- getDefaultPressureMeasurementClusterServer(measuredValue = 1000) {
1065
- return optionsFor(PressureMeasurementServer, {
1066
- measuredValue,
1067
- minMeasuredValue: null,
1068
- maxMeasuredValue: null,
1069
- tolerance: 0,
1070
- });
1071
- }
1072
994
  createDefaultPressureMeasurementClusterServer(measuredValue = 1000) {
1073
- this.behaviors.require(PressureMeasurementServer, this.getDefaultPressureMeasurementClusterServer(measuredValue));
995
+ this.behaviors.require(PressureMeasurementServer, getDefaultPressureMeasurementClusterServer(measuredValue));
1074
996
  return this;
1075
997
  }
1076
- getDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) {
1077
- return optionsFor(IlluminanceMeasurementServer, {
1078
- measuredValue,
1079
- minMeasuredValue: null,
1080
- maxMeasuredValue: null,
1081
- tolerance: 0,
1082
- });
1083
- }
1084
998
  createDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) {
1085
- this.behaviors.require(IlluminanceMeasurementServer, this.getDefaultIlluminanceMeasurementClusterServer(measuredValue));
999
+ this.behaviors.require(IlluminanceMeasurementServer, getDefaultIlluminanceMeasurementClusterServer(measuredValue));
1086
1000
  return this;
1087
1001
  }
1088
- getDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1089
- return optionsFor(FlowMeasurementServer, {
1090
- measuredValue,
1091
- minMeasuredValue: null,
1092
- maxMeasuredValue: null,
1093
- tolerance: 0,
1094
- });
1095
- }
1096
1002
  createDefaultFlowMeasurementClusterServer(measuredValue = 0) {
1097
- this.behaviors.require(FlowMeasurementServer, this.getDefaultFlowMeasurementClusterServer(measuredValue));
1003
+ this.behaviors.require(FlowMeasurementServer, getDefaultFlowMeasurementClusterServer(measuredValue));
1098
1004
  return this;
1099
1005
  }
1100
- getDefaultOccupancySensingClusterServer(occupied = false) {
1101
- return optionsFor(OccupancySensingServer, {
1102
- occupancy: { occupied },
1103
- occupancySensorType: OccupancySensing.OccupancySensorType.Pir,
1104
- occupancySensorTypeBitmap: { pir: true, ultrasonic: false, physicalContact: false },
1105
- pirOccupiedToUnoccupiedDelay: 30,
1106
- });
1107
- }
1108
1006
  createDefaultOccupancySensingClusterServer(occupied = false) {
1109
- this.behaviors.require(OccupancySensingServer, this.getDefaultOccupancySensingClusterServer(occupied));
1007
+ this.behaviors.require(OccupancySensingServer, getDefaultOccupancySensingClusterServer(occupied));
1110
1008
  return this;
1111
1009
  }
1112
1010
  createDefaultAirQualityClusterServer(airQuality = AirQuality.AirQualityEnum.Unknown) {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'crypto';
2
- import { CYAN, db, hk, zb } from './logger/export.js';
2
+ import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from './logger/export.js';
3
3
  import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, } from './matterbridgeBehaviors.js';
4
+ import { Lifecycle } from '@matter/main';
4
5
  import { getClusterNameById } from '@matter/main/types';
5
6
  import { PowerSource } from '@matter/main/clusters/power-source';
6
7
  import { UserLabel } from '@matter/main/clusters/user-label';
@@ -72,6 +73,7 @@ import { Pm25ConcentrationMeasurementServer } from '@matter/main/behaviors/pm25-
72
73
  import { Pm10ConcentrationMeasurementServer } from '@matter/main/behaviors/pm10-concentration-measurement';
73
74
  import { RadonConcentrationMeasurementServer } from '@matter/main/behaviors/radon-concentration-measurement';
74
75
  import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/main/behaviors/total-volatile-organic-compounds-concentration-measurement';
76
+ import { deepCopy, deepEqual, isValidArray } from './utils/utils.js';
75
77
  export function capitalizeFirstLetter(name) {
76
78
  if (!name)
77
79
  return name;
@@ -305,6 +307,38 @@ export function addClusterServers(endpoint, serverList) {
305
307
  if (serverList.includes(TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id))
306
308
  endpoint.createDefaultTvocMeasurementClusterServer();
307
309
  }
310
+ export async function addFixedLabel(endpoint, label, value) {
311
+ if (!endpoint.hasClusterServer(FixedLabel.Cluster.id)) {
312
+ endpoint.log.debug(`addFixedLabel: add cluster ${hk}FixedLabel${db}:${hk}fixedLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
313
+ endpoint.behaviors.require(FixedLabelServer, {
314
+ labelList: [{ label, value }],
315
+ });
316
+ return;
317
+ }
318
+ endpoint.log.debug(`addFixedLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
319
+ let labelList = endpoint.getAttribute(FixedLabel.Cluster.id, 'labelList', endpoint.log);
320
+ if (isValidArray(labelList)) {
321
+ labelList = labelList.filter((entry) => entry.label !== label);
322
+ labelList.push({ label, value });
323
+ await endpoint.setAttribute(FixedLabel.Cluster.id, 'labelList', labelList, endpoint.log);
324
+ }
325
+ }
326
+ export async function addUserLabel(endpoint, label, value) {
327
+ if (!endpoint.hasClusterServer(UserLabel.Cluster.id)) {
328
+ endpoint.log.debug(`addUserLabel: add cluster ${hk}UserLabel${db}:${hk}userLabel${db} with label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
329
+ endpoint.behaviors.require(UserLabelServer, {
330
+ labelList: [{ label, value }],
331
+ });
332
+ return;
333
+ }
334
+ endpoint.log.debug(`addUserLabel: add label ${CYAN}${label}${db} value ${CYAN}${value}${db}`);
335
+ let labelList = endpoint.getAttribute(UserLabel.Cluster.id, 'labelList', endpoint.log);
336
+ if (isValidArray(labelList)) {
337
+ labelList = labelList.filter((entry) => entry.label !== label);
338
+ labelList.push({ label, value });
339
+ await endpoint.setAttribute(UserLabel.Cluster.id, 'labelList', labelList, endpoint.log);
340
+ }
341
+ }
308
342
  export function optionsFor(type, options) {
309
343
  return options;
310
344
  }
@@ -312,5 +346,168 @@ export function getClusterId(endpoint, cluster) {
312
346
  return endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.id;
313
347
  }
314
348
  export function getAttributeId(endpoint, cluster, attribute) {
315
- return endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.children?.find((child) => child.name === capitalizeFirstLetter(attribute))?.id;
349
+ if (attribute === 'attributeList')
350
+ return 0xfffb;
351
+ else if (attribute === 'featureMap')
352
+ return 0xfffc;
353
+ else if (attribute === 'eventList')
354
+ return 0xfffa;
355
+ else if (attribute === 'generatedCommandList')
356
+ return 0xfff8;
357
+ else if (attribute === 'acceptedCommandList')
358
+ return 0xfff9;
359
+ else if (attribute === 'clusterRevision')
360
+ return 0xfffd;
361
+ else {
362
+ if (endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.type === 'ConcentrationMeasurement') {
363
+ if (attribute === 'measuredValue')
364
+ return 0x0;
365
+ else if (attribute === 'minMeasuredValue')
366
+ return 0x1;
367
+ else if (attribute === 'maxMeasuredValue')
368
+ return 0x2;
369
+ else if (attribute === 'peakMeasuredValue')
370
+ return 0x3;
371
+ else if (attribute === 'peakMeasuredValueWindow')
372
+ return 0x4;
373
+ else if (attribute === 'averageMeasuredValue')
374
+ return 0x5;
375
+ else if (attribute === 'averageMeasuredValueWindow')
376
+ return 0x6;
377
+ else if (attribute === 'uncertainty')
378
+ return 0x7;
379
+ else if (attribute === 'measurementUnit')
380
+ return 0x8;
381
+ else if (attribute === 'measurementMedium')
382
+ return 0x9;
383
+ else if (attribute === 'levelValue')
384
+ return 0xa;
385
+ }
386
+ return endpoint.behaviors.supported[lowercaseFirstLetter(cluster)]?.schema?.children?.find((child) => child.name === capitalizeFirstLetter(attribute))?.id;
387
+ }
388
+ }
389
+ export function getAttribute(endpoint, cluster, attribute, log) {
390
+ const clusterName = getBehavior(endpoint, cluster)?.id;
391
+ if (!clusterName) {
392
+ endpoint.log.error(`getAttribute ${hk}${attribute}${er} error: cluster not found on endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
393
+ return undefined;
394
+ }
395
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
396
+ endpoint.log.error(`getAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
397
+ return undefined;
398
+ }
399
+ const state = endpoint.state;
400
+ attribute = lowercaseFirstLetter(attribute);
401
+ if (!(attribute in state[clusterName])) {
402
+ endpoint.log.error(`getAttribute error: Attribute ${hk}${attribute}${er} not found on Cluster ${'0x' + getClusterId(endpoint, clusterName)?.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
403
+ return undefined;
404
+ }
405
+ let value = state[clusterName][attribute];
406
+ if (typeof value === 'object')
407
+ value = deepCopy(value);
408
+ log?.info(`${db}Get endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} value ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
409
+ return value;
410
+ }
411
+ export async function setAttribute(endpoint, cluster, attribute, value, log) {
412
+ const clusterName = getBehavior(endpoint, cluster)?.id;
413
+ if (!clusterName) {
414
+ endpoint.log.error(`setAttribute ${hk}${attribute}${er} error: cluster not found on endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
415
+ return false;
416
+ }
417
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
418
+ endpoint.log.error(`setAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
419
+ return false;
420
+ }
421
+ const state = endpoint.state;
422
+ attribute = lowercaseFirstLetter(attribute);
423
+ if (!(attribute in state[clusterName])) {
424
+ endpoint.log.error(`setAttribute error: Attribute ${hk}${attribute}${er} not found on cluster ${'0x' + getClusterId(endpoint, clusterName)?.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
425
+ return false;
426
+ }
427
+ let oldValue = state[clusterName][attribute];
428
+ if (typeof oldValue === 'object')
429
+ oldValue = deepCopy(oldValue);
430
+ await endpoint.setStateOf(endpoint.behaviors.supported[clusterName], { [attribute]: value });
431
+ log?.info(`${db}Set endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
432
+ `from ${YELLOW}${oldValue !== null && typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
433
+ `to ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
434
+ return true;
435
+ }
436
+ export async function updateAttribute(endpoint, cluster, attribute, value, log) {
437
+ const clusterName = getBehavior(endpoint, cluster)?.id;
438
+ if (!clusterName) {
439
+ endpoint.log.error(`updateAttribute ${hk}${attribute}${er} error: cluster not found on endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
440
+ return false;
441
+ }
442
+ if (endpoint.construction.status !== Lifecycle.Status.Active) {
443
+ endpoint.log.error(`updateAttribute ${hk}${clusterName}.${attribute}${er} error: Endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
444
+ return false;
445
+ }
446
+ const state = endpoint.state;
447
+ attribute = lowercaseFirstLetter(attribute);
448
+ if (!(attribute in state[clusterName])) {
449
+ endpoint.log.error(`updateAttribute error: Attribute ${hk}${attribute}${er} not found on cluster ${'0x' + getClusterId(endpoint, clusterName)?.toString(16).padStart(4, '0')}:${clusterName} on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
450
+ return false;
451
+ }
452
+ let oldValue = state[clusterName][attribute];
453
+ if (typeof oldValue === 'object') {
454
+ if (deepEqual(oldValue, value))
455
+ return false;
456
+ oldValue = deepCopy(oldValue);
457
+ }
458
+ else if (oldValue === value)
459
+ return false;
460
+ await endpoint.setStateOf(endpoint.behaviors.supported[clusterName], { [attribute]: value });
461
+ log?.info(`${db}Update endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
462
+ `from ${YELLOW}${oldValue !== null && typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
463
+ `to ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
464
+ return true;
465
+ }
466
+ export function getDefaultTemperatureMeasurementClusterServer(measuredValue = 0) {
467
+ return optionsFor(TemperatureMeasurementServer, {
468
+ measuredValue,
469
+ minMeasuredValue: null,
470
+ maxMeasuredValue: null,
471
+ tolerance: 0,
472
+ });
473
+ }
474
+ export function getDefaultRelativeHumidityMeasurementClusterServer(measuredValue = 0) {
475
+ return optionsFor(RelativeHumidityMeasurementServer, {
476
+ measuredValue,
477
+ minMeasuredValue: null,
478
+ maxMeasuredValue: null,
479
+ tolerance: 0,
480
+ });
481
+ }
482
+ export function getDefaultPressureMeasurementClusterServer(measuredValue = 1000) {
483
+ return optionsFor(PressureMeasurementServer, {
484
+ measuredValue,
485
+ minMeasuredValue: null,
486
+ maxMeasuredValue: null,
487
+ tolerance: 0,
488
+ });
489
+ }
490
+ export function getDefaultIlluminanceMeasurementClusterServer(measuredValue = 0) {
491
+ return optionsFor(IlluminanceMeasurementServer, {
492
+ measuredValue,
493
+ minMeasuredValue: null,
494
+ maxMeasuredValue: null,
495
+ tolerance: 0,
496
+ });
497
+ }
498
+ export function getDefaultFlowMeasurementClusterServer(measuredValue = 0) {
499
+ return optionsFor(FlowMeasurementServer, {
500
+ measuredValue,
501
+ minMeasuredValue: null,
502
+ maxMeasuredValue: null,
503
+ tolerance: 0,
504
+ });
505
+ }
506
+ export function getDefaultOccupancySensingClusterServer(occupied = false) {
507
+ return optionsFor(OccupancySensingServer, {
508
+ occupancy: { occupied },
509
+ occupancySensorType: OccupancySensing.OccupancySensorType.Pir,
510
+ occupancySensorTypeBitmap: { pir: true, ultrasonic: false, physicalContact: false },
511
+ pirOccupiedToUnoccupiedDelay: 30,
512
+ });
316
513
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "2.1.0-dev.4",
3
+ "version": "2.1.0-dev.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "2.1.0-dev.4",
9
+ "version": "2.1.0-dev.6",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.12.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "2.1.0-dev.4",
3
+ "version": "2.1.0-dev.6",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",
@@ -56,6 +56,26 @@
56
56
  "import": "./dist/matter/export.js",
57
57
  "types": "./dist/matter/export.d.ts"
58
58
  },
59
+ "./matter/devices": {
60
+ "import": "./dist/matter/devices.js",
61
+ "types": "./dist/matter/devices.d.ts"
62
+ },
63
+ "./matter/clusters": {
64
+ "import": "./dist/matter/clusters.js",
65
+ "types": "./dist/matter/clusters.d.ts"
66
+ },
67
+ "./matter/behaviors": {
68
+ "import": "./dist/matter/behaviors.js",
69
+ "types": "./dist/matter/behaviors.d.ts"
70
+ },
71
+ "./matter/endpoints": {
72
+ "import": "./dist/matter/endpoints.js",
73
+ "types": "./dist/matter/endpoints.d.ts"
74
+ },
75
+ "./matter/types": {
76
+ "import": "./dist/matter/types.js",
77
+ "types": "./dist/matter/types.d.ts"
78
+ },
59
79
  "./cluster": {
60
80
  "import": "./dist/cluster/export.js",
61
81
  "types": "./dist/cluster/export.d.ts"