matterbridge 1.6.7-dev.1 → 1.6.7-dev.3

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
@@ -18,7 +18,7 @@ Tamer (https://github.com/tammeryousef1006) has created the Matterbridge Discord
18
18
  In this release some device types and the OnOff, LevelControl and ColorControl have been updated to be fully compliant with Matter 1.3 specifications.
19
19
  It is possible that some controllers see them as new devices or need time to read the new clusters. It can be useful after the upgrade to power off the controller, wait a few minutes and power it on again.
20
20
 
21
- ## [1.6.7-dev.1] - 2024-12-??
21
+ ## [1.6.7-dev.3] - 2024-12-15
22
22
 
23
23
  ### Added
24
24
 
@@ -27,12 +27,17 @@ It is possible that some controllers see them as new devices or need time to rea
27
27
  ### Changed
28
28
 
29
29
  - [Docker]: Add matterbridge-hass to Dockerfile for latest and main builds.
30
+ - [edge]: Various fixes to edge mode.
30
31
  - [package]: Update dependencies.
31
32
 
32
33
  ### Fixed
33
34
 
34
35
  - [Device]: Fix addChildDeviceType methods to include debug parameter in MatterbridgeDevice instantiation.
35
36
 
37
+ <a href="https://www.buymeacoffee.com/luligugithub">
38
+ <img src="./yellow-button.png" alt="Buy me a coffee" width="120">
39
+ </a>
40
+
36
41
  ## [1.6.6] - 2024-12-12
37
42
 
38
43
  ### Added
package/README.md CHANGED
@@ -226,14 +226,18 @@ It exposes:
226
226
  - a light with onOff
227
227
  - a light with onOff and levelControl (dimmer)
228
228
  - a light with onOff, levelControl and colorControl (with XY, HS and CT) clusters
229
- - a light with onOff, levelControl and colorControl (with HS only) clusters
230
- - a light with onOff, levelControl and colorControl (with XY only) clusters
229
+ - a light with onOff, levelControl and colorControl (with HS and CT) clusters
230
+ - a light with onOff, levelControl and colorControl (with XY and CT) clusters
231
231
  - a light with onOff, levelControl and colorControl (with CT only) clusters
232
232
  - an outlet (plug) with onOff cluster
233
233
  - a cover with windowCovering cluster
234
234
  - a lock with doorLock cluster
235
- - a thermo with thermostat cluster and 3 sub endpoints with flowMeasurement cluster, temperatureMeasurement cluster
235
+ - a thermo autoMode (i.e. with Auto Heat and Cool features) with thermostat cluster and 3 sub endpoints with flowMeasurement cluster, temperatureMeasurement cluster
236
236
  and relativeHumidityMeasurement cluster (to show how to create a composed device with sub endpoints)
237
+ - a thermo heat only with two external temperature sensors (tagged like Indoor and Outdoor)
238
+ - a thermo cool only
239
+ - an airConditioner device
240
+ - an airPurifier device with temperature and humidity sensor (supported by Apple Home)
237
241
  - a fan with FanControl cluster
238
242
  - a rainSensor device
239
243
  - a waterFreezeDetector device
@@ -1,4 +1,5 @@
1
1
  export * from '@matter/main';
2
+ export { AttributeElement, ClusterElement, ClusterModel, CommandElement, EventElement, FieldElement } from '@matter/main/model';
2
3
  export * from '../matterbridgeDeviceTypes.js';
3
4
  export * from '../matterbridgeEndpoint.js';
4
5
  export * from '../matterbridgeBehaviors.js';
@@ -419,6 +419,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
419
419
  }
420
420
  createDefaultIdentifyClusterServer(identifyTime = 0, identifyType = Identify.IdentifyType.None) {
421
421
  this.addClusterServer(this.getDefaultIdentifyClusterServer(identifyTime, identifyType));
422
+ return this;
422
423
  }
423
424
  getDefaultGroupsClusterServer() {
424
425
  return ClusterServer(GroupsCluster, {
@@ -647,6 +648,48 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
647
648
  createDefaultOnOffClusterServer(onOff = false, globalSceneControl = false, onTime = 0, offWaitTime = 0, startUpOnOff = null) {
648
649
  this.addClusterServer(this.getDefaultOnOffClusterServer(onOff, globalSceneControl, onTime, offWaitTime, startUpOnOff));
649
650
  }
651
+ getDeadFrontOnOffClusterServer(onOff = false) {
652
+ return ClusterServer(OnOffCluster.with(OnOff.Feature.DeadFrontBehavior), {
653
+ onOff,
654
+ }, {
655
+ on: async (data) => {
656
+ this.log.debug('Matter command: on');
657
+ await this.commandHandler.executeHandler('on', data);
658
+ },
659
+ off: async (data) => {
660
+ this.log.debug('Matter command: off');
661
+ await this.commandHandler.executeHandler('off', data);
662
+ },
663
+ toggle: async (data) => {
664
+ this.log.debug('Matter command: toggle');
665
+ await this.commandHandler.executeHandler('toggle', data);
666
+ },
667
+ }, {});
668
+ }
669
+ createDeadFrontOnOffClusterServer(onOff = false) {
670
+ this.addClusterServer(this.getDeadFrontOnOffClusterServer(onOff));
671
+ }
672
+ getOnOffClusterServer(onOff = false) {
673
+ return ClusterServer(OnOffCluster, {
674
+ onOff,
675
+ }, {
676
+ on: async (data) => {
677
+ this.log.debug('Matter command: on');
678
+ await this.commandHandler.executeHandler('on', data);
679
+ },
680
+ off: async (data) => {
681
+ this.log.debug('Matter command: off');
682
+ await this.commandHandler.executeHandler('off', data);
683
+ },
684
+ toggle: async (data) => {
685
+ this.log.debug('Matter command: toggle');
686
+ await this.commandHandler.executeHandler('toggle', data);
687
+ },
688
+ }, {});
689
+ }
690
+ createOnOffClusterServer(onOff = false) {
691
+ this.addClusterServer(this.getOnOffClusterServer(onOff));
692
+ }
650
693
  getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel = null, startUpCurrentLevel = null) {
651
694
  return ClusterServer(LevelControlCluster.with(LevelControl.Feature.OnOff, LevelControl.Feature.Lighting), {
652
695
  currentLevel,
@@ -92,8 +92,8 @@ export const fanDevice = DeviceTypeDefinition({
92
92
  code: 0x2b,
93
93
  deviceClass: DeviceClasses.Simple,
94
94
  revision: 2,
95
- requiredServerClusters: [Identify.Cluster.id, FanControl.Cluster.id],
96
- optionalServerClusters: [Groups.Cluster.id],
95
+ requiredServerClusters: [Identify.Cluster.id, Groups.Cluster.id, FanControl.Cluster.id],
96
+ optionalServerClusters: [],
97
97
  });
98
98
  export const thermostatDevice = DeviceTypeDefinition({
99
99
  name: 'MA-thermostat',
@@ -77,45 +77,6 @@ export class MatterbridgeEdge extends Matterbridge {
77
77
  this.matterbridgeContext = undefined;
78
78
  this.log.info('Matter node storage closed');
79
79
  }
80
- createMatterServer(storageManager) {
81
- if (hasParameter('debug'))
82
- this.log.warn('createMatterServer() => mock MatterServer.addCommissioningServer()');
83
- const matterServer = {
84
- addCommissioningServer: (commissioningServer, nodeOptions) => {
85
- if (hasParameter('debug'))
86
- this.log.warn('MatterServer.addCommissioningServer() => do nothing');
87
- },
88
- };
89
- return matterServer;
90
- }
91
- async startMatterServer() {
92
- if (hasParameter('debug'))
93
- this.log.warn('startMatterServer() => do nothing');
94
- }
95
- async stopMatterServer() {
96
- if (hasParameter('debug'))
97
- this.log.warn('stopMatterServer() => ...');
98
- this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
99
- if (this.bridgeMode === 'bridge') {
100
- const serverNode = this.csToServerNode.get('Matterbridge')?.serverNode;
101
- if (serverNode) {
102
- await this.stopServerNode(serverNode);
103
- this.log.info(`Stopped matter server node Matterbridge`);
104
- }
105
- }
106
- if (this.bridgeMode === 'childbridge') {
107
- this.plugins.forEach(async (plugin) => {
108
- const serverNode = this.csToServerNode.get(plugin.name)?.serverNode;
109
- if (serverNode) {
110
- await this.stopServerNode(serverNode);
111
- this.log.info(`Stopped matter server node ${plugin.name}`);
112
- }
113
- });
114
- }
115
- this.log.info('Stopped matter server nodes');
116
- await this.environment.get(MdnsService)[Symbol.asyncDispose]();
117
- this.log.info('Stopped MdnsService');
118
- }
119
80
  async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
120
81
  if (!this.matterStorageService)
121
82
  throw new Error('No storage service initialized');
@@ -195,6 +156,14 @@ export class MatterbridgeEdge extends Matterbridge {
195
156
  this.matterbridgeSessionInformations = [];
196
157
  this.matterbridgePaired = true;
197
158
  }
159
+ if (this.bridgeMode === 'childbridge') {
160
+ const plugin = this.plugins.get(storeId);
161
+ if (plugin) {
162
+ plugin.fabricInformations = sanitizedFabrics;
163
+ plugin.sessionInformations = [];
164
+ plugin.paired = true;
165
+ }
166
+ }
198
167
  };
199
168
  serverNode.lifecycle.commissioned.on(() => this.log.notice(`Server node for ${storeId} was initially commissioned successfully!`));
200
169
  serverNode.lifecycle.decommissioned.on(() => this.log.notice(`Server node for ${storeId} was fully decommissioned successfully!`));
@@ -213,6 +182,19 @@ export class MatterbridgeEdge extends Matterbridge {
213
182
  this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
214
183
  this.log.notice(`Manual pairing code: ${manualPairingCode}`);
215
184
  }
185
+ if (this.bridgeMode === 'childbridge') {
186
+ const plugin = this.plugins.get(storeId);
187
+ if (plugin) {
188
+ plugin.qrPairingCode = qrPairingCode;
189
+ plugin.manualPairingCode = manualPairingCode;
190
+ plugin.fabricInformations = [];
191
+ plugin.sessionInformations = [];
192
+ plugin.paired = false;
193
+ plugin.connected = false;
194
+ this.log.notice(`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`);
195
+ this.log.notice(`Manual pairing code: ${manualPairingCode}`);
196
+ }
197
+ }
216
198
  }
217
199
  else {
218
200
  this.log.notice(`Server node for ${storeId} is already commissioned. Waiting for controllers to connect ...`);
@@ -255,6 +237,12 @@ export class MatterbridgeEdge extends Matterbridge {
255
237
  if (this.bridgeMode === 'bridge') {
256
238
  this.matterbridgeSessionInformations = sanitizedSessions;
257
239
  }
240
+ if (this.bridgeMode === 'childbridge') {
241
+ const plugin = this.plugins.get(storeId);
242
+ if (plugin) {
243
+ plugin.sessionInformations = sanitizedSessions;
244
+ }
245
+ }
258
246
  };
259
247
  serverNode.events.sessions.opened.on((session) => {
260
248
  this.log.notice(`Session opened on server node for ${storeId}: ${debugStringify(session)}`);
@@ -300,8 +288,27 @@ export class MatterbridgeEdge extends Matterbridge {
300
288
  await aggregatorNode?.add(device);
301
289
  }
302
290
  else if (this.bridgeMode === 'childbridge') {
291
+ if (plugin.type === 'AccessoryPlatform') {
292
+ if (!plugin.locked && device.deviceName && device.vendorId && device.productId && device.vendorName && device.productName) {
293
+ plugin.locked = true;
294
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, device.deviceName, DeviceTypeId(device.deviceType), device.vendorId, device.vendorName, device.productId, device.productName);
295
+ plugin.commissioningServer = (await this.createServerNode(plugin.storageContext, this.port++, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined));
296
+ this.log.debug(`Adding matterbridge endpoint to server node for plugin ${plg}${plugin.name}${db}`);
297
+ await plugin.commissioningServer.add(device);
298
+ this.csToServerNode.set(plugin.name, { commissioningServer: plugin.commissioningServer, serverNode: plugin.commissioningServer });
299
+ }
300
+ }
303
301
  if (plugin.type === 'DynamicPlatform') {
304
- this.log.info(`Adding ${pluginName}:${device.deviceName} to ${pluginName} aggregator node`);
302
+ if (!plugin.locked) {
303
+ plugin.locked = true;
304
+ plugin.storageContext = await this.createServerNodeContext(plugin.name, 'Matterbridge', bridge.code, this.aggregatorVendorId, 'Matterbridge', this.aggregatorProductId, plugin.description);
305
+ plugin.commissioningServer = (await this.createServerNode(plugin.storageContext, this.port++, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined));
306
+ plugin.aggregator = (await this.createAggregatorNode(plugin.storageContext));
307
+ this.log.debug(`Adding matter aggregator node to server node for plugin ${plg}${plugin.name}${db}`);
308
+ await plugin.commissioningServer.add(plugin.aggregator);
309
+ this.csToServerNode.set(plugin.name, { commissioningServer: plugin.commissioningServer, serverNode: plugin.commissioningServer });
310
+ this.agToAggregatorEndpoint.set(plugin.name, { aggregator: plugin.aggregator, aggregatorNode: plugin.aggregator });
311
+ }
305
312
  const aggregatorNode = this.agToAggregatorEndpoint.get(pluginName)?.aggregatorNode;
306
313
  await aggregatorNode?.add(device);
307
314
  }
@@ -365,6 +372,45 @@ export class MatterbridgeEdge extends Matterbridge {
365
372
  await this.removeBridgedEndpoint(pluginName, device);
366
373
  }
367
374
  }
375
+ createMatterServer(storageManager) {
376
+ if (hasParameter('debug'))
377
+ this.log.warn('createMatterServer() => mock MatterServer.addCommissioningServer()');
378
+ const matterServer = {
379
+ addCommissioningServer: (commissioningServer, nodeOptions) => {
380
+ if (hasParameter('debug'))
381
+ this.log.warn('MatterServer.addCommissioningServer() => do nothing');
382
+ },
383
+ };
384
+ return matterServer;
385
+ }
386
+ async startMatterServer() {
387
+ if (hasParameter('debug'))
388
+ this.log.warn('startMatterServer() => do nothing');
389
+ }
390
+ async stopMatterServer() {
391
+ if (hasParameter('debug'))
392
+ this.log.warn('stopMatterServer() => ...');
393
+ this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
394
+ if (this.bridgeMode === 'bridge') {
395
+ const serverNode = this.csToServerNode.get('Matterbridge')?.serverNode;
396
+ if (serverNode) {
397
+ await this.stopServerNode(serverNode);
398
+ this.log.info(`Stopped matter server node Matterbridge`);
399
+ }
400
+ }
401
+ if (this.bridgeMode === 'childbridge') {
402
+ for (const plugin of this.plugins.array()) {
403
+ const serverNode = this.csToServerNode.get(plugin.name)?.serverNode;
404
+ if (serverNode) {
405
+ await this.stopServerNode(serverNode);
406
+ this.log.info(`Stopped matter server node ${plugin.name}`);
407
+ }
408
+ }
409
+ }
410
+ this.log.info('Stopped matter server nodes');
411
+ await this.environment.get(MdnsService)[Symbol.asyncDispose]();
412
+ this.log.info('Stopped MdnsService');
413
+ }
368
414
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
369
415
  if (hasParameter('debug'))
370
416
  this.log.warn(`createCommissioningServerContext() for ${pluginName} => createServerNodeContext()`);
@@ -165,7 +165,7 @@ export class MatterbridgeEndpoint extends Endpoint {
165
165
  if (clusterId === FlowMeasurement.Cluster.id)
166
166
  return FlowMeasurementServer;
167
167
  if (clusterId === BooleanState.Cluster.id)
168
- return BooleanStateServer;
168
+ return BooleanStateServer.enable({ events: { stateChange: true } });
169
169
  if (clusterId === BooleanStateConfiguration.Cluster.id)
170
170
  return MatterbridgeBooleanStateConfigurationServer;
171
171
  if (clusterId === OccupancySensing.Cluster.id)
@@ -658,17 +658,14 @@ export class MatterbridgeEndpoint extends Endpoint {
658
658
  identifyType,
659
659
  }, {
660
660
  identify: async (data) => {
661
- this.log.debug('Matter command: Identify');
662
- await this.commandHandler.executeHandler('identify', data);
663
661
  },
664
662
  triggerEffect: async (data) => {
665
- this.log.debug('Matter command: TriggerEffect');
666
- await this.commandHandler.executeHandler('triggerEffect', data);
667
663
  },
668
664
  });
669
665
  }
670
666
  createDefaultIdentifyClusterServer(identifyTime = 0, identifyType = Identify.IdentifyType.None) {
671
667
  this.addClusterServer(this.getDefaultIdentifyClusterServer(identifyTime, identifyType));
668
+ return this;
672
669
  }
673
670
  getDefaultGroupsClusterServer() {
674
671
  return ClusterServer(GroupsCluster, {
@@ -858,6 +855,36 @@ export class MatterbridgeEndpoint extends Endpoint {
858
855
  createDefaultOnOffClusterServer(onOff = false, globalSceneControl = false, onTime = 0, offWaitTime = 0, startUpOnOff = null) {
859
856
  this.addClusterServer(this.getDefaultOnOffClusterServer(onOff, globalSceneControl, onTime, offWaitTime, startUpOnOff));
860
857
  }
858
+ getOnOffClusterServer(onOff = false) {
859
+ return ClusterServer(OnOffCluster, {
860
+ onOff,
861
+ }, {
862
+ on: async (data) => {
863
+ },
864
+ off: async (data) => {
865
+ },
866
+ toggle: async (data) => {
867
+ },
868
+ }, {});
869
+ }
870
+ createOnOffClusterServer(onOff = false) {
871
+ this.addClusterServer(this.getOnOffClusterServer(onOff));
872
+ }
873
+ getDeadFrontOnOffClusterServer(onOff = false) {
874
+ return ClusterServer(OnOffCluster.with(OnOff.Feature.DeadFrontBehavior), {
875
+ onOff,
876
+ }, {
877
+ on: async (data) => {
878
+ },
879
+ off: async (data) => {
880
+ },
881
+ toggle: async (data) => {
882
+ },
883
+ }, {});
884
+ }
885
+ createDeadFrontOnOffClusterServer(onOff = false) {
886
+ this.addClusterServer(this.getDeadFrontOnOffClusterServer(onOff));
887
+ }
861
888
  getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel = null, startUpCurrentLevel = null) {
862
889
  return ClusterServer(LevelControlCluster.with(LevelControl.Feature.OnOff, LevelControl.Feature.Lighting), {
863
890
  currentLevel,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "1.6.7-dev.1",
3
+ "version": "1.6.7-dev.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "1.6.7-dev.1",
9
+ "version": "1.6.7-dev.3",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.11.9",
@@ -1952,9 +1952,9 @@
1952
1952
  }
1953
1953
  },
1954
1954
  "node_modules/text-decoder": {
1955
- "version": "1.2.2",
1956
- "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.2.tgz",
1957
- "integrity": "sha512-/MDslo7ZyWTA2vnk1j7XoDVfXsGk3tp+zFEJHJGm0UjIlQifonVFwlVbQDFh8KJzTBnT8ie115TYqir6bclddA==",
1955
+ "version": "1.2.3",
1956
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
1957
+ "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
1958
1958
  "license": "Apache-2.0",
1959
1959
  "dependencies": {
1960
1960
  "b4a": "^1.6.4"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "1.6.7-dev.1",
3
+ "version": "1.6.7-dev.3",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",