matterbridge 1.2.22 → 1.3.0

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +40 -8
  3. package/dist/cluster/AirQualityCluster.d.ts.map +1 -0
  4. package/dist/cluster/AirQualityCluster.js.map +1 -0
  5. package/dist/cluster/BooleanStateConfigurationCluster.d.ts +2200 -0
  6. package/dist/cluster/BooleanStateConfigurationCluster.d.ts.map +1 -0
  7. package/dist/cluster/BooleanStateConfigurationCluster.js +388 -0
  8. package/dist/cluster/BooleanStateConfigurationCluster.js.map +1 -0
  9. package/dist/cluster/BridgedDeviceBasicInformationCluster.d.ts.map +1 -0
  10. package/dist/cluster/BridgedDeviceBasicInformationCluster.js.map +1 -0
  11. package/dist/cluster/CarbonMonoxideConcentrationMeasurementCluster.d.ts +396 -0
  12. package/dist/cluster/CarbonMonoxideConcentrationMeasurementCluster.d.ts.map +1 -0
  13. package/dist/cluster/CarbonMonoxideConcentrationMeasurementCluster.js +30 -0
  14. package/dist/cluster/CarbonMonoxideConcentrationMeasurementCluster.js.map +1 -0
  15. package/dist/cluster/ConcentrationMeasurementCluster.d.ts +524 -0
  16. package/dist/cluster/ConcentrationMeasurementCluster.d.ts.map +1 -0
  17. package/dist/cluster/ConcentrationMeasurementCluster.js +282 -0
  18. package/dist/cluster/ConcentrationMeasurementCluster.js.map +1 -0
  19. package/dist/cluster/DeviceEnergyManagementCluster.d.ts +7851 -0
  20. package/dist/cluster/DeviceEnergyManagementCluster.d.ts.map +1 -0
  21. package/dist/cluster/DeviceEnergyManagementCluster.js +1634 -0
  22. package/dist/cluster/DeviceEnergyManagementCluster.js.map +1 -0
  23. package/dist/cluster/DeviceEnergyManagementModeCluster.d.ts +68 -0
  24. package/dist/cluster/DeviceEnergyManagementModeCluster.d.ts.map +1 -0
  25. package/dist/cluster/DeviceEnergyManagementModeCluster.js +49 -0
  26. package/dist/cluster/DeviceEnergyManagementModeCluster.js.map +1 -0
  27. package/dist/cluster/ElectricalEnergyMeasurementCluster.d.ts +4978 -0
  28. package/dist/cluster/ElectricalEnergyMeasurementCluster.d.ts.map +1 -0
  29. package/dist/cluster/ElectricalEnergyMeasurementCluster.js +510 -0
  30. package/dist/cluster/ElectricalEnergyMeasurementCluster.js.map +1 -0
  31. package/dist/cluster/ElectricalPowerMeasurementCluster.d.ts +3250 -0
  32. package/dist/cluster/ElectricalPowerMeasurementCluster.d.ts.map +1 -0
  33. package/dist/cluster/ElectricalPowerMeasurementCluster.js +675 -0
  34. package/dist/cluster/ElectricalPowerMeasurementCluster.js.map +1 -0
  35. package/dist/cluster/FanControlCluster.d.ts +1583 -0
  36. package/dist/cluster/FanControlCluster.d.ts.map +1 -0
  37. package/dist/cluster/FanControlCluster.js +492 -0
  38. package/dist/cluster/FanControlCluster.js.map +1 -0
  39. package/dist/cluster/MeasurementAccuracy.d.ts +63 -0
  40. package/dist/cluster/MeasurementAccuracy.d.ts.map +1 -0
  41. package/dist/cluster/MeasurementAccuracy.js +47 -0
  42. package/dist/cluster/MeasurementAccuracy.js.map +1 -0
  43. package/dist/cluster/MeasurementAccuracyRange.d.ts +134 -0
  44. package/dist/cluster/MeasurementAccuracyRange.d.ts.map +1 -0
  45. package/dist/cluster/MeasurementAccuracyRange.js +103 -0
  46. package/dist/cluster/MeasurementAccuracyRange.js.map +1 -0
  47. package/dist/cluster/MeasurementType.d.ts +68 -0
  48. package/dist/cluster/MeasurementType.d.ts.map +1 -0
  49. package/dist/cluster/MeasurementType.js +69 -0
  50. package/dist/cluster/MeasurementType.js.map +1 -0
  51. package/dist/cluster/PowerTopologyCluster.d.ts +355 -0
  52. package/dist/cluster/PowerTopologyCluster.d.ts.map +1 -0
  53. package/dist/cluster/PowerTopologyCluster.js +138 -0
  54. package/dist/cluster/PowerTopologyCluster.js.map +1 -0
  55. package/dist/cluster/SmokeCoAlarmCluster.d.ts +1599 -0
  56. package/dist/cluster/SmokeCoAlarmCluster.d.ts.map +1 -0
  57. package/dist/cluster/SmokeCoAlarmCluster.js +603 -0
  58. package/dist/cluster/SmokeCoAlarmCluster.js.map +1 -0
  59. package/dist/cluster/TvocCluster.d.ts.map +1 -0
  60. package/dist/cluster/TvocCluster.js.map +1 -0
  61. package/dist/index.d.ts +7 -2
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +8 -2
  64. package/dist/index.js.map +1 -1
  65. package/dist/matterbridge.d.ts +63 -1
  66. package/dist/matterbridge.d.ts.map +1 -1
  67. package/dist/matterbridge.js +72 -26
  68. package/dist/matterbridge.js.map +1 -1
  69. package/dist/matterbridgeDevice.d.ts +692 -30
  70. package/dist/matterbridgeDevice.d.ts.map +1 -1
  71. package/dist/matterbridgeDevice.js +429 -73
  72. package/dist/matterbridgeDevice.js.map +1 -1
  73. package/dist/matterbridgeDeviceV8.d.ts +3 -5
  74. package/dist/matterbridgeDeviceV8.d.ts.map +1 -1
  75. package/dist/matterbridgeDeviceV8.js +62 -14
  76. package/dist/matterbridgeDeviceV8.js.map +1 -1
  77. package/dist/matterbridgeV8.d.ts +69 -5
  78. package/dist/matterbridgeV8.d.ts.map +1 -1
  79. package/dist/matterbridgeV8.js +409 -139
  80. package/dist/matterbridgeV8.js.map +1 -1
  81. package/frontend/build/asset-manifest.json +3 -3
  82. package/frontend/build/index.html +1 -1
  83. package/frontend/build/static/js/{main.23829a0f.js → main.cbfc6c9b.js} +3 -3
  84. package/frontend/build/static/js/{main.23829a0f.js.map → main.cbfc6c9b.js.map} +1 -1
  85. package/package.json +5 -5
  86. package/dist/AirQualityCluster.d.ts.map +0 -1
  87. package/dist/AirQualityCluster.js.map +0 -1
  88. package/dist/BridgedDeviceBasicInformationCluster.d.ts.map +0 -1
  89. package/dist/BridgedDeviceBasicInformationCluster.js.map +0 -1
  90. package/dist/TvocCluster.d.ts.map +0 -1
  91. package/dist/TvocCluster.js.map +0 -1
  92. /package/dist/{AirQualityCluster.d.ts → cluster/AirQualityCluster.d.ts} +0 -0
  93. /package/dist/{AirQualityCluster.js → cluster/AirQualityCluster.js} +0 -0
  94. /package/dist/{BridgedDeviceBasicInformationCluster.d.ts → cluster/BridgedDeviceBasicInformationCluster.d.ts} +0 -0
  95. /package/dist/{BridgedDeviceBasicInformationCluster.js → cluster/BridgedDeviceBasicInformationCluster.js} +0 -0
  96. /package/dist/{TvocCluster.d.ts → cluster/TvocCluster.d.ts} +0 -0
  97. /package/dist/{TvocCluster.js → cluster/TvocCluster.js} +0 -0
  98. /package/frontend/build/static/js/{main.23829a0f.js.LICENSE.txt → main.cbfc6c9b.js.LICENSE.txt} +0 -0
@@ -32,36 +32,48 @@ import { DeviceTypeId, FabricIndex, VendorId } from '@project-chip/matter-node.j
32
32
  import { Format, Level, Logger, createFileLogger } from '@project-chip/matter-node.js/log';
33
33
  import { Environment, StorageService } from '@project-chip/matter.js/environment';
34
34
  import { ServerNode } from '@project-chip/matter.js/node';
35
- import { logEndpoint } from '@project-chip/matter-node.js/device';
35
+ import { DeviceTypes } from '@project-chip/matter-node.js/device';
36
36
  import { QrCode } from '@project-chip/matter-node.js/schema';
37
37
  import { FabricAction } from '@project-chip/matter-node.js/fabric';
38
- import { Endpoint, EndpointServer } from '@project-chip/matter.js/endpoint';
38
+ import { Endpoint } from '@project-chip/matter.js/endpoint';
39
39
  import { AggregatorEndpoint } from '@project-chip/matter.js/endpoints/AggregatorEndpoint';
40
- import { IdentifyServer } from '@project-chip/matter.js/behavior/definitions/identify';
41
- import { OnOffServer } from '@project-chip/matter.js/behavior/definitions/on-off';
42
- import { GroupsServer } from '@project-chip/matter.js/behavior/definitions/groups';
43
- import { ScenesServer } from '@project-chip/matter.js/behavior/definitions/scenes';
44
40
  import { BridgedDeviceBasicInformationServer } from '@project-chip/matter.js/behavior/definitions/bridged-device-basic-information';
41
+ import { SwitchServer } from '@project-chip/matter.js/behavior/definitions/switch';
45
42
  import { OnOffLightDevice } from '@project-chip/matter.js/devices/OnOffLightDevice';
46
- import { MutableEndpoint } from '@project-chip/matter.js/endpoint/type';
47
- import { SupportedBehaviors } from '@project-chip/matter.js/endpoint/properties';
43
+ import { GenericSwitchDevice } from '@project-chip/matter.js/devices/GenericSwitchDevice';
44
+ import { AnsiLogger, UNDERLINE, UNDERLINEOFF, db, er, nf } from 'node-ansi-logger';
45
+ import { NodeStorageManager } from 'node-persist-manager';
48
46
  import EventEmitter from 'events';
49
47
  import path from 'path';
50
48
  import { promises as fs } from 'fs';
49
+ import { MatterbridgeDeviceV8 } from './matterbridgeDeviceV8.js';
50
+ import { pathToFileURL } from 'url';
51
+ import { shelly_config, somfytahoma_config, zigbee2mqtt_config } from './defaultConfigSchema.js';
52
+ const plg = '\u001B[38;5;33m';
53
+ const dev = '\u001B[38;5;79m';
54
+ const typ = '\u001B[38;5;207m';
55
+ const log = Logger.get('Matterbridge');
51
56
  /**
52
57
  * Represents the Matterbridge application.
53
58
  */
54
59
  export class MatterbridgeV8 extends EventEmitter {
55
60
  environment = Environment.default;
56
- matterbridgeVersion = '2.0.0';
57
- osVersion = '10.0.22631';
61
+ matterbridgeVersion = '';
62
+ osVersion = '';
58
63
  matterbridgeDirectory = '';
59
64
  matterbridgePluginDirectory = '';
60
65
  globalModulesDirectory = '';
66
+ matterbridgeLogFile = '';
67
+ registeredPlugins = [];
68
+ // Node storage
69
+ nodeStorage;
70
+ nodeContext;
71
+ // Matter storage
61
72
  matterStorageService;
62
73
  matterStorageManager;
63
74
  matterStorageContext;
64
75
  matterServerNode;
76
+ matterAggregator;
65
77
  matterLogger;
66
78
  constructor() {
67
79
  super();
@@ -72,13 +84,36 @@ export class MatterbridgeV8 extends EventEmitter {
72
84
  return matterbridge;
73
85
  }
74
86
  async initialize() {
75
- this.matterLogger = Logger.get('Matterbridge');
87
+ // Set up the temporary Matterbridge environment
88
+ this.matterbridgeVersion = '2.0.0';
89
+ this.osVersion = '10.0.22631';
76
90
  this.matterbridgeDirectory = 'C:\\Users\\lligu\\.matterbridge';
77
91
  this.matterbridgePluginDirectory = 'C:\\Users\\lligu\\Matterbridge';
78
- await this.deleteMatterLogfile('matterbridge.log');
92
+ this.globalModulesDirectory = 'C:\\Users\\lligu\\AppData\\Roaming\\npm\\node_modules';
93
+ this.matterbridgeLogFile = 'matterbridge.log';
94
+ this.matterLogger = Logger.get('Matterbridge');
95
+ await this.deleteMatterLogfile(this.matterbridgeLogFile);
96
+ await this.setupMatterFileLogger(this.matterbridgeLogFile);
97
+ this.matterLogger?.notice(`Starting Matterbridge v${this.matterbridgeVersion} on Node.js ${process.version} (${process.platform} ${process.arch})`);
79
98
  this.setupMatterVars(Level.DEBUG, Format.ANSI);
80
99
  await this.setupMatterStorage();
81
- await this.setupMatterFileLogger('matterbridge.log');
100
+ await this.setupNodeStorage();
101
+ // Get the plugins from node storage
102
+ if (!this.nodeStorage)
103
+ throw new Error('No node storage initialized');
104
+ if (!this.nodeContext)
105
+ throw new Error('No node storage context initialized');
106
+ this.registeredPlugins = await this.nodeContext.get('plugins', []);
107
+ for (const plugin of this.registeredPlugins) {
108
+ plugin.nodeContext = await this.nodeStorage.createStorage(plugin.name);
109
+ await plugin.nodeContext.set('name', plugin.name);
110
+ await plugin.nodeContext.set('type', plugin.type);
111
+ await plugin.nodeContext.set('path', plugin.path);
112
+ await plugin.nodeContext.set('version', plugin.version);
113
+ await plugin.nodeContext.set('description', plugin.description);
114
+ await plugin.nodeContext.set('author', plugin.author);
115
+ this.matterLogger?.notice(`Created node storage context for plugin ${plugin.name}`);
116
+ }
82
117
  }
83
118
  setupMatterVars(level, format) {
84
119
  this.environment.vars.set('log.level', level);
@@ -91,16 +126,22 @@ export class MatterbridgeV8 extends EventEmitter {
91
126
  this.matterStorageService = this.environment.get(StorageService);
92
127
  this.matterLogger?.notice(`Storage service created: ${this.matterStorageService.location}`);
93
128
  this.matterStorageManager = await this.matterStorageService.open('Matterbridge');
94
- this.matterLogger?.notice('Storage manager created');
129
+ this.matterLogger?.notice('Storage manager "Matterbridge" created');
95
130
  this.matterStorageContext = this.matterStorageManager.createContext('persist');
96
- this.matterLogger?.notice('Storage context created');
131
+ this.matterLogger?.notice('Storage context "Matterbridge.persist" created');
132
+ }
133
+ async setupNodeStorage() {
134
+ this.nodeStorage = new NodeStorageManager({ dir: path.join(this.matterbridgeDirectory, 'storage'), logging: false });
135
+ this.matterLogger?.notice(`Created node storage manager: ${path.join(this.matterbridgeDirectory, 'storage')}`);
136
+ this.nodeContext = await this.nodeStorage.createStorage('matterbridge');
137
+ this.matterLogger?.notice('Created node storage context "matterbridge"');
97
138
  }
98
139
  async deleteMatterLogfile(filename) {
99
140
  try {
100
141
  await fs.unlink(path.join(this.matterbridgeDirectory, filename));
101
142
  }
102
143
  catch (err) {
103
- console.error(`Error deleting old log file: ${err}`);
144
+ this.matterLogger?.error(`Error deleting old log file: ${err}`);
104
145
  }
105
146
  }
106
147
  async setupMatterFileLogger(filename) {
@@ -110,11 +151,11 @@ export class MatterbridgeV8 extends EventEmitter {
110
151
  this.matterLogger?.notice('File logger created: ' + path.join(this.matterbridgeDirectory, filename));
111
152
  }
112
153
  /**
113
- * Creates a commissioning server storage context.
154
+ * Creates a server node storage context.
114
155
  *
115
156
  * @param pluginName - The name of the plugin.
116
157
  * @param deviceName - The name of the device.
117
- * @param deviceType - The type of the device.
158
+ * @param deviceType - The deviceType of the device.
118
159
  * @param vendorId - The vendor ID.
119
160
  * @param vendorName - The vendor name.
120
161
  * @param productId - The product ID.
@@ -127,17 +168,16 @@ export class MatterbridgeV8 extends EventEmitter {
127
168
  * @param hardwareVersionString - The hardware version string of the device (optional).
128
169
  * @returns The storage context for the commissioning server.
129
170
  */
130
- async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName) {
171
+ async createServerNodeContext(pluginName, deviceName, deviceType, vendorId, vendorName, productId, productName, serialNumber) {
131
172
  if (!this.matterLogger)
132
- return;
173
+ throw new Error('No logger initialized');
133
174
  const log = this.matterLogger;
134
- if (!this.matterStorageService || !this.matterStorageManager) {
135
- log.error('No storage manager initialized');
136
- return;
137
- }
138
- log.debug(`Creating commissioning server storage context for ${pluginName}...`);
139
- const storageContext = this.matterStorageManager.createContext('persist');
140
- const random = 'AG' + CryptoNode.getRandomData(8).toHex();
175
+ if (!this.matterStorageService)
176
+ throw new Error('No storage service initialized');
177
+ log.notice(`Creating server node storage context "${pluginName}.persist" for ${pluginName}...`);
178
+ const storageManager = await this.matterStorageService.open(pluginName);
179
+ const storageContext = storageManager.createContext('persist');
180
+ const random = 'SN' + CryptoNode.getRandomData(8).toHex();
141
181
  await storageContext.set('storeId', pluginName);
142
182
  await storageContext.set('deviceName', deviceName);
143
183
  await storageContext.set('deviceType', deviceType);
@@ -147,36 +187,30 @@ export class MatterbridgeV8 extends EventEmitter {
147
187
  await storageContext.set('productName', productName.slice(0, 32));
148
188
  await storageContext.set('nodeLabel', productName.slice(0, 32));
149
189
  await storageContext.set('productLabel', productName.slice(0, 32));
150
- await storageContext.set('serialNumber', await storageContext.get('serialNumber', random));
190
+ await storageContext.set('serialNumber', await storageContext.get('serialNumber', serialNumber ? serialNumber.slice(0, 32) : random));
151
191
  await storageContext.set('uniqueId', await storageContext.get('uniqueId', random));
152
192
  await storageContext.set('softwareVersion', this.matterbridgeVersion && this.matterbridgeVersion.includes('.') ? parseInt(this.matterbridgeVersion.split('.')[0], 10) : 1);
153
193
  await storageContext.set('softwareVersionString', this.matterbridgeVersion ?? '1.0.0');
154
194
  await storageContext.set('hardwareVersion', this.osVersion && this.osVersion.includes('.') ? parseInt(this.osVersion.split('.')[0], 10) : 1);
155
195
  await storageContext.set('hardwareVersionString', this.osVersion ?? '1.0.0');
156
- log.debug(`Created commissioning server storage context for ${pluginName}:`);
196
+ log.debug(`Created server node storage context "${pluginName}.persist" for ${pluginName}:`);
157
197
  log.debug(`- deviceName: ${await storageContext.get('deviceName')} deviceType: ${await storageContext.get('deviceType')}(0x${(await storageContext.get('deviceType'))?.toString(16).padStart(4, '0')})`);
158
198
  log.debug(`- serialNumber: ${await storageContext.get('serialNumber')} uniqueId: ${await storageContext.get('uniqueId')}`);
159
199
  log.debug(`- softwareVersion: ${await storageContext.get('softwareVersion')} softwareVersionString: ${await storageContext.get('softwareVersionString')}`);
160
200
  log.debug(`- hardwareVersion: ${await storageContext.get('hardwareVersion')} hardwareVersionString: ${await storageContext.get('hardwareVersionString')}`);
161
- log.notice(`Created commissioning server storage context for ${pluginName}`);
162
201
  return storageContext;
163
202
  }
164
- async startServerNode(port = 5080, passcode = 20202021, discriminator = 3840) {
203
+ async createServerNode(storageContext, port = 5540, passcode = 20242025, discriminator = 3850) {
165
204
  if (!this.matterLogger)
166
- return;
205
+ throw new Error('No logger initialized');
167
206
  const log = this.matterLogger;
168
- log.notice('Starting Matterbridge');
169
- this.matterStorageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', AggregatorEndpoint.deviceType, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
170
- if (!this.matterStorageContext) {
171
- log.error('Error creating storage context for Matterbridge');
172
- return;
173
- }
207
+ log.notice(`Creating server node for ${await storageContext.get('storeId')}`);
174
208
  /**
175
209
  * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
176
210
  */
177
- this.matterServerNode = await ServerNode.create({
211
+ const serverNode = await ServerNode.create({
178
212
  // Required: Give the Node a unique ID which is used to store the state of this node
179
- id: await this.matterStorageContext.get('storeId'),
213
+ id: await storageContext.get('storeId'),
180
214
  // Provide Network relevant configuration like the port
181
215
  // Optional when operating only one device on a host, Default port is 5540
182
216
  network: {
@@ -191,114 +225,42 @@ export class MatterbridgeV8 extends EventEmitter {
191
225
  // Provide Node announcement settings
192
226
  // Optional: If Ommitted some development defaults are used
193
227
  productDescription: {
194
- name: await this.matterStorageContext.get('deviceName'),
195
- deviceType: DeviceTypeId(await this.matterStorageContext.get('deviceType')),
228
+ name: await storageContext.get('deviceName'),
229
+ deviceType: DeviceTypeId(await storageContext.get('deviceType')),
196
230
  },
197
231
  // Provide defaults for the BasicInformation cluster on the Root endpoint
198
232
  // Optional: If Omitted some development defaults are used
199
233
  basicInformation: {
200
- vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
201
- vendorName: await this.matterStorageContext.get('vendorName'),
202
- productId: await this.matterStorageContext.get('productId'),
203
- productName: await this.matterStorageContext.get('productName'),
204
- productLabel: await this.matterStorageContext.get('productName'),
205
- nodeLabel: await this.matterStorageContext.get('productName'),
206
- serialNumber: await this.matterStorageContext.get('serialNumber'),
207
- uniqueId: await this.matterStorageContext.get('uniqueId'),
208
- softwareVersion: await this.matterStorageContext.get('softwareVersion'),
209
- softwareVersionString: await this.matterStorageContext.get('softwareVersionString'),
210
- hardwareVersion: await this.matterStorageContext.get('hardwareVersion'),
211
- hardwareVersionString: await this.matterStorageContext.get('hardwareVersionString'),
212
- },
213
- });
214
- /**
215
- * Matter Nodes are a composition of endpoints. Create and add a single endpoint to the node. This example uses the
216
- * OnOffLightDevice or OnOffPlugInUnitDevice depending on the value of the type parameter. It also assigns this Part a
217
- * unique ID to store the endpoint number for it in the storage to restore the device on restart.
218
- * In this case we directly use the default command implementation from matter.js. Check out the DeviceNodeFull example
219
- * to see how to customize the command handlers.
220
- */
221
- const lightEndpoint = new Endpoint(OnOffLightDevice.with(BridgedDeviceBasicInformationServer), {
222
- id: 'OnOffLight',
223
- bridgedDeviceBasicInformation: {
224
- vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
225
- vendorName: await this.matterStorageContext.get('vendorName'),
226
- productName: 'Light',
227
- productLabel: 'Light',
228
- nodeLabel: 'Light',
229
- serialNumber: '0x123456789',
230
- uniqueId: '0x123456789',
231
- reachable: true,
232
- },
233
- });
234
- /**
235
- * Register state change handlers and events of the node for identify and onoff states to react to the commands.
236
- * If the code in these change handlers fail then the change is also rolled back and not executed and an error is
237
- * reported back to the controller.
238
- */
239
- lightEndpoint.events.identify.startIdentifying.on(() => log.notice('Run identify logic, ideally blink a light every 0.5s ...'));
240
- lightEndpoint.events.identify.stopIdentifying.on(() => log.notice('Stop identify logic ...'));
241
- lightEndpoint.events.onOff.onOff$Changed.on((value) => log.notice(`OnOff is now ${value ? 'ON' : 'OFF'}`));
242
- const OnOffSwitchDeviceDefinition = MutableEndpoint({
243
- name: 'OnOffSwitch',
244
- deviceType: 0x103,
245
- deviceRevision: 2,
246
- requirements: {
247
- server: {
248
- mandatory: {},
249
- optional: {},
250
- },
251
- client: {
252
- optional: {},
253
- mandatory: {},
254
- },
255
- },
256
- behaviors: SupportedBehaviors(IdentifyServer, GroupsServer, ScenesServer, OnOffServer, BridgedDeviceBasicInformationServer),
257
- });
258
- console.log('OnOffSwitchDeviceDefinition\n', OnOffSwitchDeviceDefinition);
259
- const switchEndpoint = new Endpoint(OnOffSwitchDeviceDefinition, {
260
- id: 'OnOffSwitch',
261
- onOff: {
262
- onOff: false,
263
- },
264
- bridgedDeviceBasicInformation: {
265
- vendorId: VendorId(await this.matterStorageContext.get('vendorId')),
266
- vendorName: await this.matterStorageContext.get('vendorName'),
267
- productName: 'Switch',
268
- productLabel: 'Switch',
269
- nodeLabel: 'Switch',
270
- serialNumber: '0x123456789S',
271
- uniqueId: '0x123456789S',
272
- reachable: true,
234
+ vendorId: VendorId(await storageContext.get('vendorId')),
235
+ vendorName: await storageContext.get('vendorName'),
236
+ productId: await storageContext.get('productId'),
237
+ productName: await storageContext.get('productName'),
238
+ productLabel: await storageContext.get('productName'),
239
+ nodeLabel: await storageContext.get('productName'),
240
+ serialNumber: await storageContext.get('serialNumber'),
241
+ uniqueId: await storageContext.get('uniqueId'),
242
+ softwareVersion: await storageContext.get('softwareVersion'),
243
+ softwareVersionString: await storageContext.get('softwareVersionString'),
244
+ hardwareVersion: await storageContext.get('hardwareVersion'),
245
+ hardwareVersionString: await storageContext.get('hardwareVersionString'),
273
246
  },
274
247
  });
275
- // await this.matterServerNode.add(endpoint);
276
- // const mbV8 = new MatterbridgeDeviceV8(DeviceTypes.OnOffLight);
277
- // const endpoint = mbV8.getBridgedNodeEndpointV8();
278
- const aggregator = new Endpoint(AggregatorEndpoint, { id: 'aggregator' });
279
- await this.matterServerNode.add(aggregator);
280
- await aggregator.add(lightEndpoint);
281
- await aggregator.add(switchEndpoint);
282
- /**
283
- * Log the endpoint structure for debugging reasons and to allow to verify anything is correct
284
- */
285
- logEndpoint(EndpointServer.forEndpoint(this.matterServerNode));
286
248
  /**
287
249
  * This event is triggered when the device is initially commissioned successfully.
288
250
  * This means: It is added to the first fabric.
289
251
  */
290
- this.matterServerNode.lifecycle.commissioned.on(() => log.notice('Server was initially commissioned successfully!'));
252
+ serverNode.lifecycle.commissioned.on(() => log.notice('Server was initially commissioned successfully!'));
291
253
  /** This event is triggered when all fabrics are removed from the device, usually it also does a factory reset then. */
292
- this.matterServerNode.lifecycle.decommissioned.on(() => log.notice('Server was fully decommissioned successfully!'));
254
+ serverNode.lifecycle.decommissioned.on(() => log.notice('Server was fully decommissioned successfully!'));
293
255
  /** This event is triggered when the device went online. This means that it is discoverable in the network. */
294
- this.matterServerNode.lifecycle.online.on(() => log.notice('Server is online'));
256
+ serverNode.lifecycle.online.on(() => log.notice('Server is online'));
295
257
  /** This event is triggered when the device went offline. it is not longer discoverable or connectable in the network. */
296
- this.matterServerNode.lifecycle.offline.on(() => log.notice('Server is offline'));
258
+ serverNode.lifecycle.offline.on(() => log.notice('Server is offline'));
297
259
  /**
298
260
  * This event is triggered when a fabric is added, removed or updated on the device. Use this if more granular
299
261
  * information is needed.
300
262
  */
301
- this.matterServerNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
263
+ serverNode.events.commissioning.fabricsChanged.on((fabricIndex, fabricAction) => {
302
264
  let action = '';
303
265
  switch (fabricAction) {
304
266
  case FabricAction.Added:
@@ -318,19 +280,17 @@ export class MatterbridgeV8 extends EventEmitter {
318
280
  * This event is triggered when an operative new session was opened by a Controller.
319
281
  * It is not triggered for the initial commissioning process, just afterwards for real connections.
320
282
  */
321
- this.matterServerNode.events.sessions.opened.on((session) => log.notice('Session opened', session));
283
+ serverNode.events.sessions.opened.on((session) => log.notice('Session opened', session));
322
284
  /**
323
285
  * This event is triggered when an operative session is closed by a Controller or because the Device goes offline.
324
286
  */
325
- this.matterServerNode.events.sessions.closed.on((session) => log.notice('Session closed', session));
287
+ serverNode.events.sessions.closed.on((session) => log.notice('Session closed', session));
326
288
  /** This event is triggered when a subscription gets added or removed on an operative session. */
327
- this.matterServerNode.events.sessions.subscriptionsChanged.on((session) => {
289
+ serverNode.events.sessions.subscriptionsChanged.on((session) => {
328
290
  log.notice('Session subscriptions changed', session);
329
291
  log.notice('Status of all sessions', this.matterServerNode?.state.sessions.sessions);
330
292
  });
331
- await this.matterServerNode.bringOnline();
332
- console.log('lightEndpoint\n', lightEndpoint);
333
- console.log('switchEndpoint\n', switchEndpoint);
293
+ return serverNode;
334
294
  }
335
295
  showServerNodeQR() {
336
296
  if (!this.matterServerNode || !this.matterLogger)
@@ -356,12 +316,322 @@ export class MatterbridgeV8 extends EventEmitter {
356
316
  return;
357
317
  await this.matterServerNode.close();
358
318
  }
319
+ async createAggregator(storageContext) {
320
+ if (!this.matterLogger)
321
+ throw new Error('No logger initialized');
322
+ const log = this.matterLogger;
323
+ log.notice(`Creating ${await storageContext.get('storeId')} aggregator `);
324
+ const aggregator = new Endpoint(AggregatorEndpoint, { id: `${await storageContext.get('storeId')} aggregator` });
325
+ return aggregator;
326
+ }
327
+ async startBridge() {
328
+ if (!this.matterLogger)
329
+ throw new Error('No logger initialized');
330
+ const log = this.matterLogger;
331
+ const storageContext = await this.createServerNodeContext('Matterbridge', 'Matterbridge', AggregatorEndpoint.deviceType, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Aggregator');
332
+ this.matterServerNode = await this.createServerNode(storageContext);
333
+ this.matterAggregator = await this.createAggregator(storageContext);
334
+ log.notice(`Adding ${await storageContext.get('storeId')} aggregator to ${await storageContext.get('storeId')} server node`);
335
+ await this.matterServerNode.add(this.matterAggregator);
336
+ for (const plugin of this.registeredPlugins) {
337
+ if (!plugin.enabled) {
338
+ log.info(`Plugin ${plg}${plugin.name}${nf} not enabled`);
339
+ continue;
340
+ }
341
+ plugin.error = false;
342
+ plugin.loaded = false;
343
+ plugin.started = false;
344
+ plugin.configured = false;
345
+ plugin.connected = undefined;
346
+ plugin.qrPairingCode = undefined;
347
+ plugin.manualPairingCode = undefined;
348
+ this.loadPlugin(plugin, true, 'Matterbridge is starting'); // No await do it asyncronously
349
+ }
350
+ log.notice(`Adding lightEndpoint1 to ${await storageContext.get('storeId')} aggregator`);
351
+ const lightEndpoint1 = new Endpoint(OnOffLightDevice.with(BridgedDeviceBasicInformationServer), {
352
+ id: 'OnOffLight',
353
+ bridgedDeviceBasicInformation: {
354
+ vendorId: VendorId(await storageContext.get('vendorId')),
355
+ vendorName: await storageContext.get('vendorName'),
356
+ productName: 'Light',
357
+ productLabel: 'Light',
358
+ nodeLabel: 'Light',
359
+ serialNumber: '0x123456789',
360
+ uniqueId: '0x123456789',
361
+ reachable: true,
362
+ },
363
+ });
364
+ this.matterAggregator.add(lightEndpoint1);
365
+ log.notice(`Adding switchEnpoint2 to ${await storageContext.get('storeId')} aggregator`);
366
+ const switchEnpoint2 = new Endpoint(GenericSwitchDevice.with(BridgedDeviceBasicInformationServer, SwitchServer.with('MomentarySwitch', 'MomentarySwitchLongPress', 'MomentarySwitchMultiPress', 'MomentarySwitchRelease')), {
367
+ id: 'GenericSwitch',
368
+ bridgedDeviceBasicInformation: {
369
+ vendorId: VendorId(await storageContext.get('vendorId')),
370
+ vendorName: await storageContext.get('vendorName'),
371
+ productName: 'GenericSwitch',
372
+ productLabel: 'GenericSwitch',
373
+ nodeLabel: 'GenericSwitch',
374
+ serialNumber: '0x123456739',
375
+ uniqueId: '0x123456739',
376
+ reachable: true,
377
+ },
378
+ switch: {
379
+ numberOfPositions: 2,
380
+ currentPosition: 0,
381
+ multiPressMax: 2,
382
+ },
383
+ });
384
+ this.matterAggregator.add(switchEnpoint2);
385
+ switchEnpoint2.events.identify.startIdentifying.on(() => log.notice('Run identify logic, ideally blink a light every 0.5s ...'));
386
+ switchEnpoint2.events.switch.currentPosition$Changed.on(() => log.notice('Run identify logic, ideally blink a light every 0.5s ...'));
387
+ // switchEnpoint2.events.switch.emit('initialPress', { newPosition: 1 }, ActionContext.agentFor(switchEnpoint2) );
388
+ log.notice(`Adding matterbridge device to ${await storageContext.get('storeId')} aggregator`);
389
+ const matterbridgeDevice3 = new MatterbridgeDeviceV8(DeviceTypes.TEMPERATURE_SENSOR, { uniqueStorageKey: 'TemperatureSensor' });
390
+ this.matterAggregator.add(matterbridgeDevice3);
391
+ log.notice(`Starting ${await storageContext.get('storeId')} server node`);
392
+ await this.matterServerNode.bringOnline();
393
+ /*
394
+ logEndpoint(EndpointServer.forEndpoint(this.matterServerNode));
395
+ console.log('matterbridgeDevice3\n', matterbridgeDevice3);
396
+ console.log('matterbridgeDevice3.events\n', matterbridgeDevice3.events);
397
+ console.log('matterbridgeDevice3.events.identify\n', matterbridgeDevice3.eventsOf(IdentifyServer));
398
+ console.log('matterbridgeDevice3.state\n', matterbridgeDevice3.state);
399
+ console.log('matterbridgeDevice3.state.temperatureMeasurement\n', matterbridgeDevice3.stateOf(TemperatureMeasurementServer));
400
+ // matterbridgeDevice3.eventsOf(IdentifyServer);
401
+ // matterbridgeDevice3.events.identify.startIdentifying.on(() => log.notice('Run identify logic, ideally blink a light every 0.5s ...'));
402
+ */
403
+ this.showServerNodeQR();
404
+ }
405
+ async startChildbridge() {
406
+ //
407
+ }
408
+ async startController() {
409
+ //
410
+ }
411
+ /**
412
+ * Adds a bridged device to the Matterbridge.
413
+ * @param pluginName - The name of the plugin.
414
+ * @param device - The bridged device to add.
415
+ * @returns {Promise<void>} - A promise that resolves when the storage process is started.
416
+ */
417
+ async addBridgedDevice(pluginName, device) {
418
+ log.info(`Adding bridged device ${dev}${device.deviceName}${nf} for plugin ${plg}${pluginName}${nf}`);
419
+ }
420
+ /**
421
+ * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
422
+ * @param plugin - The plugin to load.
423
+ * @param start - Optional flag indicating whether to start the plugin after loading. Default is false.
424
+ * @param message - Optional message to pass to the plugin when starting.
425
+ * @returns A Promise that resolves to the loaded MatterbridgePlatform instance.
426
+ * @throws An error if the plugin is not enabled, already loaded, or fails to load.
427
+ */
428
+ async loadPlugin(plugin, start = false, message = '') {
429
+ if (!plugin.enabled) {
430
+ return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not enabled`));
431
+ }
432
+ if (plugin.platform) {
433
+ return Promise.resolve(plugin.platform);
434
+ }
435
+ log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
436
+ try {
437
+ // Load the package.json of the plugin
438
+ const packageJson = JSON.parse(await fs.readFile(plugin.path, 'utf8'));
439
+ // Resolve the main module path relative to package.json
440
+ const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
441
+ // Dynamically import the plugin
442
+ const pluginUrl = pathToFileURL(pluginEntry);
443
+ log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
444
+ const pluginInstance = await import(pluginUrl.href);
445
+ log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
446
+ // Call the default export function of the plugin, passing this MatterBridge instance, the log and the config
447
+ if (pluginInstance.default) {
448
+ const config = await this.loadPluginConfig(plugin);
449
+ const log = new AnsiLogger({ logName: plugin.description, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logDebug: true });
450
+ const platform = pluginInstance.default(this, log, config);
451
+ platform.name = packageJson.name;
452
+ platform.config = config;
453
+ platform.version = packageJson.version;
454
+ plugin.name = packageJson.name;
455
+ plugin.description = packageJson.description;
456
+ plugin.version = packageJson.version;
457
+ plugin.author = packageJson.author;
458
+ plugin.type = platform.type;
459
+ plugin.platform = platform;
460
+ plugin.loaded = true;
461
+ plugin.registeredDevices = 0;
462
+ plugin.addedDevices = 0;
463
+ // Save the updated plugin data in the node storage
464
+ // await this.nodeContext?.set<RegisteredPlugin[]>('plugins', await this.getBaseRegisteredPlugins());
465
+ // await this.getPluginLatestVersion(plugin);
466
+ log.info(`Loaded plugin ${plg}${plugin.name}${nf} type ${typ}${platform.type} ${db}(entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
467
+ if (start)
468
+ this.startPlugin(plugin, message); // No await do it asyncronously
469
+ return Promise.resolve(platform);
470
+ }
471
+ else {
472
+ plugin.error = true;
473
+ return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} does not provide a default export`));
474
+ }
475
+ }
476
+ catch (err) {
477
+ plugin.error = true;
478
+ return Promise.reject(new Error(`Failed to load plugin ${plg}${plugin.name}${er}: ${err}`));
479
+ }
480
+ }
481
+ /**
482
+ * Starts a plugin.
483
+ *
484
+ * @param {RegisteredPlugin} plugin - The plugin to start.
485
+ * @param {string} [message] - Optional message to pass to the plugin's onStart method.
486
+ * @param {boolean} [configure=false] - Indicates whether to configure the plugin after starting.
487
+ * @returns {Promise<void>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
488
+ */
489
+ async startPlugin(plugin, message, configure = false) {
490
+ if (!plugin.loaded || !plugin.platform) {
491
+ return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded or no platform`));
492
+ }
493
+ if (plugin.started) {
494
+ log.debug(`Plugin ${plg}${plugin.name}${db} already started`);
495
+ return Promise.resolve();
496
+ }
497
+ log.info(`Starting plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
498
+ try {
499
+ plugin.platform
500
+ .onStart(message)
501
+ .then(() => {
502
+ plugin.started = true;
503
+ log.info(`Started plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
504
+ if (configure)
505
+ this.configurePlugin(plugin); // No await do it asyncronously
506
+ return Promise.resolve();
507
+ })
508
+ .catch((err) => {
509
+ plugin.error = true;
510
+ return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
511
+ });
512
+ }
513
+ catch (err) {
514
+ plugin.error = true;
515
+ return Promise.reject(new Error(`Failed to start plugin ${plg}${plugin.name}${er}: ${err}`));
516
+ }
517
+ }
518
+ /**
519
+ * Configures a plugin.
520
+ *
521
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
522
+ * @returns {Promise<void>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
523
+ */
524
+ async configurePlugin(plugin) {
525
+ if (!plugin.loaded || !plugin.started || !plugin.platform) {
526
+ return Promise.reject(new Error(`Plugin ${plg}${plugin.name}${er} not loaded (${plugin.loaded}) or not started (${plugin.started}) or not platform (${plugin.platform?.name})`));
527
+ }
528
+ if (plugin.configured) {
529
+ log.info(`Plugin ${plg}${plugin.name}${nf} already configured`);
530
+ return Promise.resolve();
531
+ }
532
+ log.info(`Configuring plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
533
+ try {
534
+ plugin.platform
535
+ .onConfigure()
536
+ .then(() => {
537
+ plugin.configured = true;
538
+ log.info(`Configured plugin ${plg}${plugin.name}${db} type ${typ}${plugin.type}${db}`);
539
+ // this.savePluginConfig(plugin);
540
+ return Promise.resolve();
541
+ })
542
+ .catch((err) => {
543
+ plugin.error = true;
544
+ return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
545
+ });
546
+ }
547
+ catch (err) {
548
+ plugin.error = true;
549
+ return Promise.reject(new Error(`Failed to configure plugin ${plg}${plugin.name}${er}: ${err}`));
550
+ }
551
+ }
552
+ /**
553
+ * Loads the configuration for a plugin.
554
+ * If the configuration file exists, it reads the file and returns the parsed JSON data.
555
+ * If the configuration file does not exist, it creates a new file with default configuration and returns it.
556
+ * If any error occurs during file access or creation, it logs an error and rejects the promise with the error.
557
+ *
558
+ * @param plugin - The plugin for which to load the configuration.
559
+ * @returns A promise that resolves to the loaded or created configuration.
560
+ */
561
+ async loadPluginConfig(plugin) {
562
+ const configFile = path.join(this.matterbridgeDirectory, `${plugin.name}.config.json`);
563
+ try {
564
+ await fs.access(configFile);
565
+ const data = await fs.readFile(configFile, 'utf8');
566
+ const config = JSON.parse(data);
567
+ // this.log.debug(`Config file found: ${configFile}.\nConfig:${rs}\n`, config);
568
+ log.debug(`Config file found: ${configFile}.`);
569
+ /* The first time a plugin is added to the system, the config file is created with the plugin name and type "".*/
570
+ config.name = plugin.name;
571
+ config.type = plugin.type;
572
+ return config;
573
+ }
574
+ catch (err) {
575
+ if (err instanceof Error) {
576
+ const nodeErr = err;
577
+ if (nodeErr.code === 'ENOENT') {
578
+ let config;
579
+ if (plugin.name === 'matterbridge-zigbee2mqtt')
580
+ config = zigbee2mqtt_config;
581
+ else if (plugin.name === 'matterbridge-somfy-tahoma')
582
+ config = somfytahoma_config;
583
+ else if (plugin.name === 'matterbridge-shelly')
584
+ config = shelly_config;
585
+ else
586
+ config = { name: plugin.name, type: plugin.type, unregisterOnShutdown: false };
587
+ try {
588
+ await this.writeFile(configFile, JSON.stringify(config, null, 2));
589
+ log.debug(`Created config file: ${configFile}.`);
590
+ // this.log.debug(`Created config file: ${configFile}.\nConfig:${rs}\n`, config);
591
+ return config;
592
+ }
593
+ catch (err) {
594
+ log.error(`Error creating config file ${configFile}: ${err}`);
595
+ return config;
596
+ }
597
+ }
598
+ else {
599
+ log.error(`Error accessing config file ${configFile}: ${err}`);
600
+ return {};
601
+ }
602
+ }
603
+ log.error(`Error loading config file ${configFile}: ${err}`);
604
+ return {};
605
+ }
606
+ }
607
+ /**
608
+ * Writes data to a file.
609
+ *
610
+ * @param {string} filePath - The path of the file to write to.
611
+ * @param {string} data - The data to write to the file.
612
+ * @returns {Promise<void>} - A promise that resolves when the data is successfully written to the file.
613
+ */
614
+ async writeFile(filePath, data) {
615
+ // Write the data to a file
616
+ await fs
617
+ .writeFile(`${filePath}`, data, 'utf8')
618
+ .then(() => {
619
+ log.debug(`Successfully wrote to ${filePath}`);
620
+ })
621
+ .catch((error) => {
622
+ log.error(`Error writing to ${filePath}:`, error);
623
+ });
624
+ }
359
625
  }
360
626
  // node dist/matterbridgeV8.js MatterbridgeV8
361
627
  if (process.argv.includes('MatterbridgeV8')) {
362
628
  const matterbridge = await MatterbridgeV8.create();
363
- await matterbridge.startServerNode(5070, 20242025, 3950);
364
- matterbridge.showServerNodeQR();
629
+ if (process.argv.includes('-bridge'))
630
+ await matterbridge.startBridge();
631
+ if (process.argv.includes('-childbridge'))
632
+ await matterbridge.startChildbridge();
633
+ if (process.argv.includes('-controller'))
634
+ await matterbridge.startController();
365
635
  process.on('SIGINT', async function () {
366
636
  console.log('Caught interrupt signal');
367
637
  await matterbridge.stopServerNode();