matterbridge 1.6.6-dev.9 → 1.6.7-dev.1

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
@@ -4,37 +4,62 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  If you like this project and find it useful, please consider giving it a star on GitHub at https://github.com/Luligu/matterbridge and sponsoring it.
6
6
 
7
- ### Home Assistant Community Add-ons
7
+ ### Home Assistant
8
8
 
9
- The Home Assistant Community Add-ons and plugins are not verified to work with Matterbridge. I strongly advise against using them. If you do use them and encounter an issue (which is likely because some do not meet the Matterbridge guidelines), please do not open an issue in the Matterbridge repository.
10
-
11
- If you want to run Matterbridge in Home Assistant please use the official add-on https://github.com/Luligu/matterbridge-home-assistant-addon that also has Ingress and side panel.
9
+ If you want to run Matterbridge in Home Assistant please use the official add-on https://github.com/Luligu/matterbridge-home-assistant-addon that also has Ingress and side panel.
10
+ It is also available the official Matterbridge Home Assistant plugin https://github.com/Luligu/matterbridge-hass.
12
11
 
13
12
  ### Discord
14
13
 
15
14
  Tamer (https://github.com/tammeryousef1006) has created the Matterbridge Discord group: https://discord.gg/QX58CDe6hd.
16
15
 
17
- ## [1.6.6] - 2024-12-06
16
+ ### Breaking Changes
17
+
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
+ 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
+
21
+ ## [1.6.7-dev.1] - 2024-12-??
22
+
23
+ ### Added
24
+
25
+ - [readme]: Update README to clarify Node.js installation instructions and emphasize LTS version.
26
+
27
+ ### Changed
28
+
29
+ - [Docker]: Add matterbridge-hass to Dockerfile for latest and main builds.
30
+ - [package]: Update dependencies.
31
+
32
+ ### Fixed
33
+
34
+ - [Device]: Fix addChildDeviceType methods to include debug parameter in MatterbridgeDevice instantiation.
35
+
36
+ ## [1.6.6] - 2024-12-12
18
37
 
19
38
  ### Added
20
39
 
21
- - [frontend]: Add the possibility to install a specific version or the dev of any plugin (i.e. you can install matterbridge-hass@dev or matterbridge-hass@0.0.3).
40
+ - [frontend]: Added the possibility to install a specific version or the dev of any plugin (i.e. you can install matterbridge-hass@dev or matterbridge-hass@0.0.3).
22
41
  It is also possible to use the install plugin to install a specific version of matterbridge (i.e. you can install matterbridge@dev or matterbridge@1.6.5)
23
- - [frontend]: Add the possibility to set the matter discriminator for commissioning (you can always override passing **-discriminator [DISCRIMINATOR]** on the command line).
24
- - [frontend]: Add the possibility to set the matter passcode for commissioning (you can always override passing **-passcode [PASSCODE]** on the command line).
25
- - [frontend]: Add the possibility to set the matter port for commissioning (you can always override passing **-port [PORT]** on the command line).
26
- - [deviceTypes]: Add device type airConditioner (not supported by the Apple Home).
27
- - [docker]: Add matterbridge-hass to docker dev.
42
+ - [frontend]: Added the possibility to set the matter discriminator for commissioning (you can always override passing **-discriminator [DISCRIMINATOR]** on the command line).
43
+ - [frontend]: Added the possibility to set the matter passcode for commissioning (you can always override passing **-passcode [PASSCODE]** on the command line).
44
+ - [frontend]: Added the possibility to set the matter port for commissioning (you can always override passing **-port [PORT]** on the command line).
45
+ - [deviceTypes]: Added the device type airConditioner (not supported by the Apple Home).
46
+ - [docker]: Added matterbridge-hass to docker dev.
28
47
  - [platform]: Added validateDeviceWhiteBlackList and validateEntityBlackList to be used consistently by all plugins.
48
+ - [/api/devices]: Added productUrl and configUrl.
29
49
 
30
50
  ### Changed
31
51
 
32
52
  - [package]: Update matter.js to 0.11.9-alpha.0-20241206-22f23333.
33
53
  - [package]: Update matter.js to 0.11.9-alpha.0-20241207-b604cfa44
54
+ - [package]: Update matter.js to 0.11.9-alpha.0-20241209-06a8040e1
55
+ - [package]: Update matter.js to 0.11.9
34
56
  - [plugin]: Removed check on package types since we are moving to production plugins.
35
57
  - [package]: Set required node version to 18, 20 and 22.
36
58
  - [package]: Update dependencies.
37
- - [levelControl]: Set default to OnOff.Feature.Lighting.
59
+ - [onOff]: Set default to OnOff.Feature.Lighting.
60
+ - [levelControl]: Set default to LevelControl.Feature.Lighting.
61
+ - [colorControl]: Set default cluster helpers to have ColorTemperature.
62
+ - [lightSensor]: Refactor lightSensor removing Group optional cluster server.
38
63
  - [jest]: Update Jest tests.
39
64
 
40
65
  ### Fixed
package/README-DEV.md CHANGED
@@ -71,9 +71,9 @@ I added some error messages when a plugin has wrong imports or configurations an
71
71
 
72
72
  I'm working with matter.js team to define the strategy for the migration of Matterbridge to the new API.
73
73
 
74
- - First phase: crete MatterbridgeEdge class: completed 90%
75
- - Second phase: create MatterbridgeEndpoint and MatterbridgeBehaviors classes: completed 90%
76
- - Third phase: modifiy all plugins to support both normal and edge mode of Matterbridge: completed 80%
74
+ - First phase: create MatterbridgeEdge class: completed 95%
75
+ - Second phase: create MatterbridgeEndpoint and MatterbridgeBehaviors classes: completed 95%
76
+ - Third phase: modifiy all plugins to support both normal and edge mode of Matterbridge: completed 90%
77
77
  - Fourth phase: remove all old api code from Matterbridge and all plugins...
78
78
 
79
79
  ## How to create your plugin
package/README.md CHANGED
@@ -44,7 +44,13 @@ A special thank to Apollon77 for his incredible work.
44
44
 
45
45
  ## Prerequisites
46
46
 
47
- To run Matterbridge, you need either a [Node.js](https://nodejs.org/en/download/package-manager) environment or [Docker](https://docs.docker.com/get-started/get-docker/) installed on your system.
47
+ To run Matterbridge, you need either a [Node.js](https://nodejs.org/en) environment or [Docker](https://docs.docker.com/get-started/get-docker/) installed on your system.
48
+
49
+ If you don't have Node.js already install, please use this method to install it on a debian device: https://github.com/nodesource/distributions.
50
+ The supported versions of node are 18, 20 and 22. Please install node 22 LTS.
51
+ Nvm is not a good choice and should not be used for production.
52
+
53
+ If you don't have Docker already install, please use this method to install it on a debian device: https://docs.docker.com/desktop/setup/install/linux/debian/.
48
54
 
49
55
  ## Installation
50
56
 
@@ -372,7 +378,7 @@ Then, from the dots menu in the frontend, download the `matterbridge.log` and `m
372
378
 
373
379
  # Known general issues
374
380
 
375
- ## Session XYZ does not exist
381
+ ## Session XYZ does not exist or Cannot find a session for ID XYZ
376
382
 
377
383
  This message may appear after Matterbridge restarts, indicating that the controller is still using a session from the previous connection that has since been closed.
378
384
  After some time, the controller will reconnect.
@@ -60,6 +60,7 @@ export class Matterbridge extends EventEmitter {
60
60
  bridgeMode: '',
61
61
  restartMode: '',
62
62
  edge: hasParameter('edge'),
63
+ readOnly: hasParameter('readonly'),
63
64
  profile: getParameter('profile'),
64
65
  loggerLevel: "info",
65
66
  fileLogger: false,
@@ -201,8 +202,9 @@ export class Matterbridge extends EventEmitter {
201
202
  throw new Error('Fatal error creating node storage manager and context for matterbridge');
202
203
  }
203
204
  this.port = getIntParameter('port') ?? (await this.nodeContext.get('matterport', 5540)) ?? 5540;
204
- this.passcode = getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
205
- this.discriminator = getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
205
+ this.passcode = this.passcode ?? getIntParameter('passcode') ?? (await this.nodeContext.get('matterpasscode'));
206
+ this.discriminator = this.discriminator ?? getIntParameter('discriminator') ?? (await this.nodeContext.get('matterdiscriminator'));
207
+ this.log.debug(`Initializing commissioning server for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
206
208
  if (hasParameter('logger')) {
207
209
  const level = getParameter('logger');
208
210
  if (level === 'debug') {
@@ -2335,7 +2337,7 @@ export class Matterbridge extends EventEmitter {
2335
2337
  });
2336
2338
  this.expressApp.get('/api/devices', (req, res) => {
2337
2339
  this.log.debug('The frontend sent /api/devices');
2338
- const data = [];
2340
+ const devices = [];
2339
2341
  this.devices.forEach(async (device) => {
2340
2342
  const pluginName = device.plugin ?? 'Unknown';
2341
2343
  if (this.edge)
@@ -2346,21 +2348,26 @@ export class Matterbridge extends EventEmitter {
2346
2348
  let serial = device.getClusterServer(BasicInformationCluster)?.attributes.serialNumber?.getLocal();
2347
2349
  if (!serial)
2348
2350
  serial = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.serialNumber?.getLocal() ?? 'Unknown';
2351
+ let productUrl = device.getClusterServer(BasicInformationCluster)?.attributes.productUrl?.getLocal();
2352
+ if (!productUrl)
2353
+ productUrl = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.productUrl?.getLocal() ?? 'Unknown';
2349
2354
  let uniqueId = device.getClusterServer(BasicInformationCluster)?.attributes.uniqueId?.getLocal();
2350
2355
  if (!uniqueId)
2351
2356
  uniqueId = device.getClusterServer(BridgedDeviceBasicInformationCluster)?.attributes.uniqueId?.getLocal() ?? 'Unknown';
2352
2357
  const cluster = this.getClusterTextFromDevice(device);
2353
- data.push({
2358
+ devices.push({
2354
2359
  pluginName,
2355
2360
  type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
2356
2361
  endpoint: device.number,
2357
2362
  name,
2358
2363
  serial,
2364
+ productUrl,
2365
+ configUrl: device.configUrl,
2359
2366
  uniqueId,
2360
2367
  cluster: cluster,
2361
2368
  });
2362
2369
  });
2363
- res.json(data);
2370
+ res.json(devices);
2364
2371
  });
2365
2372
  this.expressApp.get('/api/devices_clusters/:selectedPluginName/:selectedDeviceEndpoint', (req, res) => {
2366
2373
  const selectedPluginName = req.params.selectedPluginName;
@@ -23,6 +23,10 @@ export class MatterbridgeBehaviorDevice {
23
23
  this.log.info(`Identifying device for ${identifyTime} seconds`);
24
24
  this.commandHandler.executeHandler('identify', { request: { identifyTime }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
25
25
  }
26
+ triggerEffect({ effectIdentifier, effectVariant }) {
27
+ this.log.info(`Triggering effect ${effectIdentifier} variant ${effectVariant}`);
28
+ this.commandHandler.executeHandler('triggerEffect', { request: { effectIdentifier, effectVariant }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
29
+ }
26
30
  on() {
27
31
  this.log.info(`Switching device on (endpoint ${this.endpointId}.${this.endpointNumber})`);
28
32
  this.commandHandler.executeHandler('on', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
@@ -125,6 +129,11 @@ export class MatterbridgeIdentifyServer extends IdentifyServer {
125
129
  device.identify({ identifyTime });
126
130
  super.identify({ identifyTime });
127
131
  }
132
+ triggerEffect({ effectIdentifier, effectVariant }) {
133
+ const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
134
+ device.triggerEffect({ effectIdentifier, effectVariant });
135
+ super.triggerEffect({ effectIdentifier, effectVariant });
136
+ }
128
137
  }
129
138
  export class MatterbridgeOnOffServer extends OnOffServer {
130
139
  async on() {
@@ -12,6 +12,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
12
12
  static logLevel = "info";
13
13
  log;
14
14
  plugin = undefined;
15
+ configUrl = undefined;
15
16
  serialNumber = undefined;
16
17
  deviceName = undefined;
17
18
  uniqueId = undefined;
@@ -80,7 +81,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
80
81
  this.log.debug(`addChildDeviceType: ${CYAN}${endpointName}${db}`);
81
82
  let child = this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName);
82
83
  if (!child) {
83
- child = new MatterbridgeDevice(deviceTypes, { uniqueStorageKey: endpointName });
84
+ child = new MatterbridgeDevice(deviceTypes, { uniqueStorageKey: endpointName }, debug);
84
85
  if ('tagList' in options) {
85
86
  for (const tag of options.tagList) {
86
87
  this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`);
@@ -104,7 +105,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
104
105
  this.log.debug(`addChildDeviceTypeWithClusterServer: ${CYAN}${endpointName}${db}`);
105
106
  let child = this.getChildEndpoints().find((endpoint) => endpoint.uniqueStorageKey === endpointName);
106
107
  if (!child) {
107
- child = new MatterbridgeDevice(deviceTypes, { uniqueStorageKey: endpointName });
108
+ child = new MatterbridgeDevice(deviceTypes, { uniqueStorageKey: endpointName }, debug);
108
109
  if ('tagList' in options) {
109
110
  for (const tag of options.tagList) {
110
111
  this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`);
@@ -458,6 +459,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
458
459
  vendorName: vendorName.slice(0, 32),
459
460
  productId: productId,
460
461
  productName: productName.slice(0, 32),
462
+ productUrl: 'https://www.npmjs.com/package/matterbridge',
461
463
  productLabel: deviceName.slice(0, 64),
462
464
  nodeLabel: deviceName.slice(0, 32),
463
465
  serialNumber: serialNumber.slice(0, 32),
@@ -502,6 +504,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
502
504
  vendorId: vendorId !== undefined ? VendorId(vendorId) : undefined,
503
505
  vendorName: vendorName.slice(0, 32),
504
506
  productName: productName.slice(0, 32),
507
+ productUrl: 'https://www.npmjs.com/package/matterbridge',
505
508
  productLabel: deviceName.slice(0, 64),
506
509
  nodeLabel: deviceName.slice(0, 32),
507
510
  serialNumber: serialNumber.slice(0, 32),
@@ -644,12 +647,14 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
644
647
  createDefaultOnOffClusterServer(onOff = false, globalSceneControl = false, onTime = 0, offWaitTime = 0, startUpOnOff = null) {
645
648
  this.addClusterServer(this.getDefaultOnOffClusterServer(onOff, globalSceneControl, onTime, offWaitTime, startUpOnOff));
646
649
  }
647
- getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 0, maxLevel = 254, onLevel = null) {
648
- return ClusterServer(LevelControlCluster.with(LevelControl.Feature.OnOff), {
650
+ getDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel = null, startUpCurrentLevel = null) {
651
+ return ClusterServer(LevelControlCluster.with(LevelControl.Feature.OnOff, LevelControl.Feature.Lighting), {
649
652
  currentLevel,
650
653
  minLevel,
651
654
  maxLevel,
652
655
  onLevel,
656
+ remainingTime: 0,
657
+ startUpCurrentLevel,
653
658
  options: {
654
659
  executeIfOff: false,
655
660
  coupleColorTempToLevel: false,
@@ -683,8 +688,8 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
683
688
  },
684
689
  });
685
690
  }
686
- createDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 0, maxLevel = 254, onLevel = null) {
687
- this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel, minLevel, maxLevel, onLevel));
691
+ createDefaultLevelControlClusterServer(currentLevel = 254, minLevel = 1, maxLevel = 254, onLevel = null, startUpCurrentLevel = null) {
692
+ this.addClusterServer(this.getDefaultLevelControlClusterServer(currentLevel, minLevel, maxLevel, onLevel, startUpCurrentLevel));
688
693
  }
689
694
  getDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
690
695
  return ClusterServer(ColorControlCluster.with(ColorControl.Feature.Xy, ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), {
@@ -703,6 +708,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
703
708
  colorTempPhysicalMinMireds,
704
709
  colorTempPhysicalMaxMireds,
705
710
  coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
711
+ remainingTime: 0,
706
712
  startUpColorTemperatureMireds: null,
707
713
  }, {
708
714
  moveToColor: async (data) => {
@@ -757,17 +763,23 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
757
763
  createDefaultColorControlClusterServer(currentX = 0, currentY = 0, currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
758
764
  this.addClusterServer(this.getDefaultColorControlClusterServer(currentX, currentY, currentHue, currentSaturation, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds));
759
765
  }
760
- getXyColorControlClusterServer(currentX = 0, currentY = 0) {
761
- return ClusterServer(ColorControlCluster.with(ColorControl.Feature.Xy), {
766
+ getXyColorControlClusterServer(currentX = 0, currentY = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
767
+ return ClusterServer(ColorControlCluster.with(ColorControl.Feature.Xy, ColorControl.Feature.ColorTemperature), {
762
768
  colorMode: ColorControl.ColorMode.CurrentXAndCurrentY,
763
769
  enhancedColorMode: ColorControl.EnhancedColorMode.CurrentXAndCurrentY,
764
- colorCapabilities: { xy: true, hueSaturation: false, colorLoop: false, enhancedHue: false, colorTemperature: false },
770
+ colorCapabilities: { xy: true, hueSaturation: false, colorLoop: false, enhancedHue: false, colorTemperature: true },
765
771
  options: {
766
772
  executeIfOff: false,
767
773
  },
768
774
  numberOfPrimaries: null,
769
775
  currentX,
770
776
  currentY,
777
+ colorTemperatureMireds,
778
+ colorTempPhysicalMinMireds,
779
+ colorTempPhysicalMaxMireds,
780
+ coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
781
+ startUpColorTemperatureMireds: null,
782
+ remainingTime: 0,
771
783
  }, {
772
784
  moveToColor: async (data) => {
773
785
  this.log.debug('Matter command: moveToColor request:', data.request, 'attributes.currentX:', data.attributes.currentX.getLocal(), 'attributes.currentY:', data.attributes.currentY.getLocal());
@@ -782,22 +794,38 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
782
794
  stopMoveStep: async () => {
783
795
  this.log.error('Matter command: stopMoveStep not implemented');
784
796
  },
797
+ moveToColorTemperature: async ({ request, attributes, endpoint }) => {
798
+ this.log.debug('Matter command: moveToColorTemperature request:', request, 'attributes.colorTemperatureMireds:', attributes.colorTemperatureMireds.getLocal());
799
+ this.commandHandler.executeHandler('moveToColorTemperature', { request, attributes, endpoint });
800
+ },
801
+ moveColorTemperature: async () => {
802
+ this.log.error('Matter command: moveColorTemperature not implemented');
803
+ },
804
+ stepColorTemperature: async () => {
805
+ this.log.error('Matter command: stepColorTemperature not implemented');
806
+ },
785
807
  }, {});
786
808
  }
787
- createXyColorControlClusterServer(currentX = 0, currentY = 0) {
788
- this.addClusterServer(this.getXyColorControlClusterServer(currentX, currentY));
809
+ createXyColorControlClusterServer(currentX = 0, currentY = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
810
+ this.addClusterServer(this.getXyColorControlClusterServer(currentX, currentY, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds));
789
811
  }
790
- getHsColorControlClusterServer(currentHue = 0, currentSaturation = 0) {
791
- return ClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation), {
812
+ getHsColorControlClusterServer(currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
813
+ return ClusterServer(ColorControlCluster.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.ColorTemperature), {
792
814
  colorMode: ColorControl.ColorMode.CurrentHueAndCurrentSaturation,
793
815
  enhancedColorMode: ColorControl.EnhancedColorMode.CurrentHueAndCurrentSaturation,
794
- colorCapabilities: { xy: false, hueSaturation: true, colorLoop: false, enhancedHue: false, colorTemperature: false },
816
+ colorCapabilities: { xy: false, hueSaturation: true, colorLoop: false, enhancedHue: false, colorTemperature: true },
795
817
  options: {
796
818
  executeIfOff: false,
797
819
  },
798
820
  numberOfPrimaries: null,
799
821
  currentHue,
800
822
  currentSaturation,
823
+ colorTemperatureMireds,
824
+ colorTempPhysicalMinMireds,
825
+ colorTempPhysicalMaxMireds,
826
+ coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
827
+ startUpColorTemperatureMireds: null,
828
+ remainingTime: 0,
801
829
  }, {
802
830
  moveToHue: async ({ request, attributes, endpoint }) => {
803
831
  this.log.debug('Matter command: moveToHue request:', request, 'attributes.currentHue:', attributes.currentHue.getLocal());
@@ -826,10 +854,20 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
826
854
  stopMoveStep: async () => {
827
855
  this.log.error('Matter command: stopMoveStep not implemented');
828
856
  },
857
+ moveToColorTemperature: async ({ request, attributes, endpoint }) => {
858
+ this.log.debug('Matter command: moveToColorTemperature request:', request, 'attributes.colorTemperatureMireds:', attributes.colorTemperatureMireds.getLocal());
859
+ this.commandHandler.executeHandler('moveToColorTemperature', { request, attributes, endpoint });
860
+ },
861
+ moveColorTemperature: async () => {
862
+ this.log.error('Matter command: moveColorTemperature not implemented');
863
+ },
864
+ stepColorTemperature: async () => {
865
+ this.log.error('Matter command: stepColorTemperature not implemented');
866
+ },
829
867
  }, {});
830
868
  }
831
- createHsColorControlClusterServer(currentHue = 0, currentSaturation = 0) {
832
- this.addClusterServer(this.getHsColorControlClusterServer(currentHue, currentSaturation));
869
+ createHsColorControlClusterServer(currentHue = 0, currentSaturation = 0, colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
870
+ this.addClusterServer(this.getHsColorControlClusterServer(currentHue, currentSaturation, colorTemperatureMireds, colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds));
833
871
  }
834
872
  getCtColorControlClusterServer(colorTemperatureMireds = 500, colorTempPhysicalMinMireds = 147, colorTempPhysicalMaxMireds = 500) {
835
873
  return ClusterServer(ColorControlCluster.with(ColorControl.Feature.ColorTemperature), {
@@ -844,6 +882,7 @@ export class MatterbridgeDevice extends extendPublicHandlerMethods(Device) {
844
882
  colorTempPhysicalMinMireds,
845
883
  colorTempPhysicalMaxMireds,
846
884
  coupleColorTempToLevelMinMireds: colorTempPhysicalMinMireds,
885
+ remainingTime: 0,
847
886
  startUpColorTemperatureMireds: null,
848
887
  }, {
849
888
  stopMoveStep: async () => {
@@ -116,7 +116,6 @@ export const lightSensor = DeviceTypeDefinition({
116
116
  deviceClass: DeviceClasses.Simple,
117
117
  revision: 3,
118
118
  requiredServerClusters: [Identify.Cluster.id, IlluminanceMeasurement.Cluster.id],
119
- optionalClientClusters: [Groups.Cluster.id],
120
119
  });
121
120
  export const occupancySensor = DeviceTypeDefinition({
122
121
  name: 'MA-occupancysensor',
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import os from 'os';
3
3
  import { randomBytes } from 'crypto';
4
- import { rs, GREEN, debugStringify, er, zb, nf } from 'node-ansi-logger';
4
+ import { rs, GREEN, debugStringify, er, zb, nf, db } from 'node-ansi-logger';
5
5
  import { Matterbridge } from './matterbridge.js';
6
6
  import { bridge } from './matterbridgeDeviceTypes.js';
7
7
  import { dev, plg } from './matterbridgeTypes.js';
@@ -9,7 +9,7 @@ import { copyDirectory, getParameter, hasParameter } from './utils/utils.js';
9
9
  import { DeviceTypeId, LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, VendorId, EndpointServer } from '@matter/main';
10
10
  import { ServerNode, Endpoint as EndpointNode, Environment, StorageService } from '@matter/main';
11
11
  import { BasicInformationCluster } from '@matter/main/clusters';
12
- import { FabricAction, MdnsService } from '@matter/main/protocol';
12
+ import { FabricAction, MdnsService, PaseClient } from '@matter/main/protocol';
13
13
  import { GenericSwitchDevice } from '@matter/main/devices';
14
14
  import { AggregatorEndpoint } from '@matter/main/endpoints';
15
15
  import { BridgedDeviceBasicInformationServer, SwitchServer } from '@matter/main/behaviors';
@@ -45,6 +45,11 @@ export class MatterbridgeEdge extends Matterbridge {
45
45
  this.environment.vars.set('path.root', path.join(this.matterbridgeDirectory, this.matterStorageName));
46
46
  this.environment.vars.set('runtime.signals', false);
47
47
  this.environment.vars.set('runtime.exitcode', false);
48
+ this.port = 5540;
49
+ this.passcode = PaseClient.generateRandomPasscode();
50
+ this.discriminator = PaseClient.generateRandomDiscriminator();
51
+ if (hasParameter('debug'))
52
+ console.log(`Initializing server node for Matterbridge... on port ${this.port} with passcode ${this.passcode} and discriminator ${this.discriminator}`);
48
53
  await super.initialize();
49
54
  if (this.mdnsInterface)
50
55
  this.environment.vars.set('mdns.networkInterface', this.mdnsInterface);
@@ -74,7 +79,7 @@ export class MatterbridgeEdge extends Matterbridge {
74
79
  }
75
80
  createMatterServer(storageManager) {
76
81
  if (hasParameter('debug'))
77
- this.log.warn('createMatterServer() => mock matterServer');
82
+ this.log.warn('createMatterServer() => mock MatterServer.addCommissioningServer()');
78
83
  const matterServer = {
79
84
  addCommissioningServer: (commissioningServer, nodeOptions) => {
80
85
  if (hasParameter('debug'))
@@ -85,9 +90,11 @@ export class MatterbridgeEdge extends Matterbridge {
85
90
  }
86
91
  async startMatterServer() {
87
92
  if (hasParameter('debug'))
88
- this.log.warn('createMatterServer() => do nothing');
93
+ this.log.warn('startMatterServer() => do nothing');
89
94
  }
90
95
  async stopMatterServer() {
96
+ if (hasParameter('debug'))
97
+ this.log.warn('stopMatterServer() => ...');
91
98
  this.log.info(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
92
99
  if (this.bridgeMode === 'bridge') {
93
100
  const serverNode = this.csToServerNode.get('Matterbridge')?.serverNode;
@@ -143,7 +150,7 @@ export class MatterbridgeEdge extends Matterbridge {
143
150
  }
144
151
  async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
145
152
  const storeId = await storageContext.get('storeId');
146
- this.log.info(`Creating server node for ${storeId}...`);
153
+ this.log.info(`Creating server node for ${storeId} on port ${port} with passcode ${passcode} and discriminator ${discriminator}...`);
147
154
  this.log.debug(`- deviceName: ${await storageContext.get('deviceName')}`);
148
155
  this.log.debug(`- deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
149
156
  this.log.debug(`- serialNumber: ${await storageContext.get('serialNumber')}`);
@@ -284,7 +291,7 @@ export class MatterbridgeEdge extends Matterbridge {
284
291
  async addBridgedEndpoint(pluginName, device) {
285
292
  const plugin = this.plugins.get(pluginName);
286
293
  if (!plugin) {
287
- this.log.error(`Error adding bridged device ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
294
+ this.log.error(`Error adding bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.id}${er}) plugin ${plg}${pluginName}${er} not found`);
288
295
  return;
289
296
  }
290
297
  if (this.bridgeMode === 'bridge') {
@@ -304,11 +311,59 @@ export class MatterbridgeEdge extends Matterbridge {
304
311
  if (plugin.addedDevices !== undefined)
305
312
  plugin.addedDevices++;
306
313
  this.devices.set(device);
307
- this.log.info(`Added and registered bridged device (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
314
+ this.log.info(`Added and registered bridged endpoint (${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${dev}${device.id}${nf}) for plugin ${plg}${pluginName}${nf}`);
308
315
  }
309
316
  async removeBridgedEndpoint(pluginName, device) {
317
+ this.log.debug(`Removing bridged endpoint ${dev}${device.deviceName}${db} (${zb}${device.name}${db}) for plugin ${plg}${pluginName}${db}`);
318
+ const plugin = this.plugins.get(pluginName);
319
+ if (!plugin) {
320
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: plugin not found`);
321
+ return;
322
+ }
323
+ if (this.bridgeMode === 'bridge') {
324
+ const aggregatoreNode = this.agToAggregatorEndpoint.get('Matterbridge')?.aggregatorNode;
325
+ if (!aggregatoreNode) {
326
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: matterAggregator node not found`);
327
+ return;
328
+ }
329
+ await device.delete();
330
+ this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
331
+ if (plugin.registeredDevices !== undefined)
332
+ plugin.registeredDevices--;
333
+ if (plugin.addedDevices !== undefined)
334
+ plugin.addedDevices--;
335
+ }
336
+ else if (this.bridgeMode === 'childbridge') {
337
+ if (plugin.type === 'AccessoryPlatform') {
338
+ }
339
+ else if (plugin.type === 'DynamicPlatform') {
340
+ const aggregatoreNode = this.agToAggregatorEndpoint.get(pluginName)?.aggregatorNode;
341
+ if (!aggregatoreNode) {
342
+ this.log.error(`Error removing bridged endpoint ${dev}${device.deviceName}${er} (${zb}${device.name}${er}) for plugin ${plg}${pluginName}${er}: aggregator not found`);
343
+ return;
344
+ }
345
+ await device.delete();
346
+ }
347
+ this.log.info(`Removed bridged endpoint(${plugin.registeredDevices}/${plugin.addedDevices}) ${dev}${device.deviceName}${nf} (${zb}${device.name}${nf}) for plugin ${plg}${pluginName}${nf}`);
348
+ if (plugin.registeredDevices !== undefined)
349
+ plugin.registeredDevices--;
350
+ if (plugin.addedDevices !== undefined)
351
+ plugin.addedDevices--;
352
+ if (plugin.registeredDevices === 0 && plugin.addedDevices === 0) {
353
+ const serverNode = this.csToServerNode.get(pluginName)?.serverNode;
354
+ if (serverNode)
355
+ await this.stopServerNode(serverNode);
356
+ this.csToServerNode.delete(pluginName);
357
+ this.log.info(`Removed server node for plugin ${plg}${pluginName}${nf}`);
358
+ }
359
+ }
360
+ this.devices.remove(device);
310
361
  }
311
362
  async removeAllBridgedEndpoints(pluginName) {
363
+ this.log.debug(`Removing all bridged endpoints for plugin ${plg}${pluginName}${db}`);
364
+ for (const device of this.devices.array().filter((device) => device.plugin === pluginName)) {
365
+ await this.removeBridgedEndpoint(pluginName, device);
366
+ }
312
367
  }
313
368
  async createCommissioningServerContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
314
369
  if (hasParameter('debug'))
@@ -330,7 +385,7 @@ export class MatterbridgeEdge extends Matterbridge {
330
385
  if (hasParameter('debug'))
331
386
  this.log.warn(`createCommisioningServer() for ${pluginName} => createServerNode()`);
332
387
  const port = this.port;
333
- const serverNode = await this.createServerNode(context, this.port++, this.passcode ? this.passcode++ : 20242025, this.discriminator ? this.discriminator++ : 3840);
388
+ const serverNode = await this.createServerNode(context, this.port++, this.passcode ? this.passcode++ : undefined, this.discriminator ? this.discriminator++ : undefined);
334
389
  const commissioningServer = {
335
390
  getPort: () => port,
336
391
  addDevice: async (device) => {
@@ -363,11 +418,11 @@ export class MatterbridgeEdge extends Matterbridge {
363
418
  name: 'MA-aggregator',
364
419
  addBridgedDevice: (device) => {
365
420
  if (hasParameter('debug'))
366
- this.log.warn('Aggregator.addBridgedDevice() => not inplemented');
421
+ this.log.error('****Aggregator.addBridgedDevice() => not inplemented');
367
422
  },
368
423
  removeBridgedDevice: (device) => {
369
424
  if (hasParameter('debug'))
370
- this.log.warn('Aggregator.removeBridgedDevice() => not inplemented');
425
+ this.log.error('****Aggregator.removeBridgedDevice() => not inplemented');
371
426
  },
372
427
  };
373
428
  this.agToAggregatorEndpoint.set(pluginName, { aggregator, aggregatorNode });