matterbridge 3.3.3-dev-20251015-19d1be8 → 3.3.3-dev-20251015-706d832

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
@@ -29,6 +29,10 @@ Advantages:
29
29
 
30
30
  - [thread]: Added timestamp to WorkerMessage.
31
31
  - [macOS]: Added the [plist configuration guide](README-MACOS-PLIST.md).
32
+ - [frontend]: Added download diagnostic and download history to Download menu.
33
+ - [frontend]: Added icon to open the cpu and memory usage in System Information panel.
34
+ - [thermostat]: Added thermostatRunningState attribute. Thanks Ludovic BOUÉ (https://github.com/Luligu/matterbridge/pull/410).
35
+ - [ElectricalPowerMeasurement]: Added createApparentElectricalPowerMeasurementClusterServer cluster helper. Thanks Ludovic BOUÉ (https://github.com/Luligu/matterbridge/pull/411).
32
36
 
33
37
  ### Changed
34
38
 
@@ -195,12 +195,12 @@ sudo launchctl disable system/matterbridge
195
195
  sudo tail -n 1000 -f /var/log/matterbridge.log /var/log/matterbridge.err
196
196
  ```
197
197
 
198
- ### Automatically rotate logs (every 5 days or at 100 MB, keep 5 compressed backups)
198
+ ### Optional: automatically rotate logs (every 5 days or at 100 MB, keep 5 compressed backups)
199
199
 
200
200
  ```bash
201
201
  sudo tee /etc/newsyslog.d/matterbridge.conf <<'EOF'
202
- /var/log/matterbridge.log root:wheel 640 5 102400 5 Z
203
- /var/log/matterbridge.err root:wheel 640 5 102400 5 Z
202
+ /var/log/matterbridge.log root:wheel 640 5 102400 5 ZC
203
+ /var/log/matterbridge.err root:wheel 640 5 102400 5 ZC
204
204
  EOF
205
205
  sudo newsyslog -v
206
206
  ```
@@ -2,6 +2,7 @@ if (process.argv.includes('--loader') || process.argv.includes('-loader'))
2
2
  console.log('\u001B[32mCli history loaded.\u001B[40;0m');
3
3
  import { writeFileSync } from 'node:fs';
4
4
  import path from 'node:path';
5
+ import os from 'node:os';
5
6
  export const historySize = 2880;
6
7
  export let historyIndex = 0;
7
8
  export function setHistoryIndex(index) {
@@ -32,6 +33,7 @@ export const history = Array.from({ length: historySize }, () => ({
32
33
  }));
33
34
  export function generateHistoryPage(options = {}) {
34
35
  const pageTitle = options.pageTitle ?? 'Matterbridge CPU & Memory History';
36
+ const hostname = options.hostname ?? os.hostname();
35
37
  const outputPath = path.resolve(options.outputPath ?? path.join(process.cwd(), 'history.html'));
36
38
  const bufferLength = history.length;
37
39
  if (bufferLength === 0) {
@@ -101,7 +103,7 @@ export function generateHistoryPage(options = {}) {
101
103
  }
102
104
  h1 {
103
105
  margin-top: 0;
104
- font-size: clamp(1.6rem, 2.2vw, 2.3rem);
106
+ font-size: clamp(1.4rem, 1.8vw, 2.0rem);
105
107
  font-weight: 700;
106
108
  color: var(--accent);
107
109
  }
@@ -231,7 +233,8 @@ export function generateHistoryPage(options = {}) {
231
233
  <div class="container">
232
234
  <header>
233
235
  <h1>${escapeHtml(pageTitle)}</h1>
234
- <p>Generated ${new Date().toLocaleString()}</p>
236
+ <p>Hostname: ${escapeHtml(hostname)}</p>
237
+ <p>Generated: ${new Date().toLocaleString()}</p>
235
238
  </header>
236
239
 
237
240
  <section class="card">
package/dist/frontend.js CHANGED
@@ -405,41 +405,7 @@ export class Frontend extends EventEmitter {
405
405
  });
406
406
  this.expressApp.get('/api/view-diagnostic', async (req, res) => {
407
407
  this.log.debug('The frontend sent /api/view-diagnostic');
408
- const serverNodes = [];
409
- if (this.matterbridge.bridgeMode === 'bridge') {
410
- if (this.matterbridge.serverNode)
411
- serverNodes.push(this.matterbridge.serverNode);
412
- }
413
- else if (this.matterbridge.bridgeMode === 'childbridge') {
414
- for (const plugin of this.matterbridge.plugins.array()) {
415
- if (plugin.serverNode)
416
- serverNodes.push(plugin.serverNode);
417
- }
418
- }
419
- for (const device of this.matterbridge.devices.array()) {
420
- if (device.serverNode)
421
- serverNodes.push(device.serverNode);
422
- }
423
- const fs = await import('node:fs');
424
- if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE)))
425
- fs.unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE));
426
- const diagnosticDestination = LogDestination({ name: 'diagnostic', level: MatterLogLevel.INFO, format: MatterLogFormat.formats.plain });
427
- diagnosticDestination.write = async (text, _message) => {
428
- await fs.promises.appendFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), text + '\n', { encoding: 'utf8' });
429
- };
430
- Logger.destinations.diagnostic = diagnosticDestination;
431
- if (!diagnosticDestination.context) {
432
- diagnosticDestination.context = Diagnostic.Context();
433
- }
434
- diagnosticDestination.context.run(() => diagnosticDestination.add(Diagnostic.message({
435
- now: Time.now(),
436
- facility: 'Server nodes:',
437
- level: MatterLogLevel.INFO,
438
- prefix: Logger.nestingLevel ? '⎸'.padEnd(Logger.nestingLevel * 2) : '',
439
- values: [...serverNodes],
440
- })));
441
- delete Logger.destinations.diagnostic;
442
- await wait(500);
408
+ await this.generateDiagnostic();
443
409
  try {
444
410
  const fs = await import('node:fs');
445
411
  const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
@@ -451,6 +417,26 @@ export class Frontend extends EventEmitter {
451
417
  res.status(500).send('Error reading diagnostic log file.');
452
418
  }
453
419
  });
420
+ this.expressApp.get('/api/download-diagnostic', async (req, res) => {
421
+ this.log.debug(`The frontend sent /api/download-diagnostic`);
422
+ await this.generateDiagnostic();
423
+ try {
424
+ const fs = await import('node:fs');
425
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), fs.constants.F_OK);
426
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), 'utf8');
427
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), data, 'utf-8');
428
+ }
429
+ catch (error) {
430
+ this.log.debug(`Error in /api/download-diagnostic: ${error instanceof Error ? error.message : error}`);
431
+ }
432
+ res.type('text/plain');
433
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_DIAGNOSTIC_FILE), MATTERBRIDGE_DIAGNOSTIC_FILE, (error) => {
434
+ if (error) {
435
+ this.log.error(`Error downloading file ${MATTERBRIDGE_DIAGNOSTIC_FILE}: ${error instanceof Error ? error.message : error}`);
436
+ res.status(500).send('Error downloading the diagnostic log file');
437
+ }
438
+ });
439
+ });
454
440
  this.expressApp.get('/api/viewhistory', async (req, res) => {
455
441
  this.log.debug('The frontend sent /api/viewhistory');
456
442
  try {
@@ -460,8 +446,28 @@ export class Frontend extends EventEmitter {
460
446
  res.send(data);
461
447
  }
462
448
  catch (error) {
463
- this.log.error(`Error reading history log file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
464
- res.status(500).send('Error reading history log file. Please create the history log before loading it.');
449
+ this.log.error(`Error in /api/viewhistory reading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
450
+ res.status(500).send('Error reading history file.');
451
+ }
452
+ });
453
+ this.expressApp.get('/api/downloadhistory', async (req, res) => {
454
+ this.log.debug(`The frontend sent /api/downloadhistory`);
455
+ try {
456
+ const fs = await import('node:fs');
457
+ await fs.promises.access(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), fs.constants.F_OK);
458
+ const data = await fs.promises.readFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), 'utf8');
459
+ await fs.promises.writeFile(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), data, 'utf-8');
460
+ res.type('text/plain');
461
+ res.download(path.join(os.tmpdir(), MATTERBRIDGE_HISTORY_FILE), MATTERBRIDGE_HISTORY_FILE, (error) => {
462
+ if (error) {
463
+ this.log.error(`Error in /api/downloadhistory downloading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
464
+ res.status(500).send('Error downloading history file');
465
+ }
466
+ });
467
+ }
468
+ catch (error) {
469
+ this.log.error(`Error in /api/downloadhistory reading history file ${MATTERBRIDGE_HISTORY_FILE}: ${error instanceof Error ? error.message : error}`);
470
+ res.status(500).send('Error reading history file.');
465
471
  }
466
472
  });
467
473
  this.expressApp.get('/api/shellyviewsystemlog', async (req, res) => {
@@ -999,6 +1005,44 @@ export class Frontend extends EventEmitter {
999
1005
  });
1000
1006
  return { plugin: endpoint.plugin, deviceName: endpoint.deviceName, serialNumber: endpoint.serialNumber, number: endpoint.number, id: endpoint.id, deviceTypes, clusters };
1001
1007
  }
1008
+ async generateDiagnostic() {
1009
+ this.log.debug('Generating diagnostic...');
1010
+ const serverNodes = [];
1011
+ if (this.matterbridge.bridgeMode === 'bridge') {
1012
+ if (this.matterbridge.serverNode)
1013
+ serverNodes.push(this.matterbridge.serverNode);
1014
+ }
1015
+ else if (this.matterbridge.bridgeMode === 'childbridge') {
1016
+ for (const plugin of this.matterbridge.plugins.array()) {
1017
+ if (plugin.serverNode)
1018
+ serverNodes.push(plugin.serverNode);
1019
+ }
1020
+ }
1021
+ for (const device of this.matterbridge.devices.array()) {
1022
+ if (device.serverNode)
1023
+ serverNodes.push(device.serverNode);
1024
+ }
1025
+ const fs = await import('node:fs');
1026
+ if (fs.existsSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE)))
1027
+ fs.unlinkSync(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE));
1028
+ const diagnosticDestination = LogDestination({ name: 'diagnostic', level: MatterLogLevel.INFO, format: MatterLogFormat.formats.plain });
1029
+ diagnosticDestination.write = async (text, _message) => {
1030
+ await fs.promises.appendFile(path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_DIAGNOSTIC_FILE), text + '\n', { encoding: 'utf8' });
1031
+ };
1032
+ Logger.destinations.diagnostic = diagnosticDestination;
1033
+ if (!diagnosticDestination.context) {
1034
+ diagnosticDestination.context = Diagnostic.Context();
1035
+ }
1036
+ diagnosticDestination.context.run(() => diagnosticDestination.add(Diagnostic.message({
1037
+ now: Time.now(),
1038
+ facility: 'Server nodes:',
1039
+ level: MatterLogLevel.INFO,
1040
+ prefix: Logger.nestingLevel ? '⎸'.padEnd(Logger.nestingLevel * 2) : '',
1041
+ values: [...serverNodes],
1042
+ })));
1043
+ delete Logger.destinations.diagnostic;
1044
+ await wait(500);
1045
+ }
1002
1046
  async wsMessageHandler(client, message) {
1003
1047
  let data;
1004
1048
  const sendResponse = (data) => {
@@ -1286,8 +1330,12 @@ export class Frontend extends EventEmitter {
1286
1330
  await this.matterbridge.shutdownProcessAndFactoryReset();
1287
1331
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1288
1332
  }
1289
- else if (data.method === '/api/generatehistorypage') {
1290
- generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge on ${this.matterbridge.systemInformation.hostname} Cpu & Memory History` });
1333
+ else if (data.method === '/api/viewhistorypage') {
1334
+ generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge Cpu & Memory History` });
1335
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1336
+ }
1337
+ else if (data.method === '/api/downloadhistorypage') {
1338
+ generateHistoryPage({ outputPath: path.join(this.matterbridge.matterbridgeDirectory, MATTERBRIDGE_HISTORY_FILE), pageTitle: `Matterbridge Cpu & Memory History` });
1291
1339
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true });
1292
1340
  }
1293
1341
  else if (data.method === '/api/matter') {
@@ -1,5 +1,5 @@
1
1
  import { Endpoint, Lifecycle, MutableEndpoint, NamedHandler, SupportedBehaviors, UINT16_MAX, UINT32_MAX, VendorId } from '@matter/main';
2
- import { getClusterNameById, MeasurementType } from '@matter/main/types';
2
+ import { getClusterNameById } from '@matter/main/types';
3
3
  import { Descriptor } from '@matter/main/clusters/descriptor';
4
4
  import { PowerSource } from '@matter/main/clusters/power-source';
5
5
  import { BridgedDeviceBasicInformation } from '@matter/main/clusters/bridged-device-basic-information';
@@ -60,7 +60,7 @@ import { ThermostatUserInterfaceConfigurationServer } from '@matter/main/behavio
60
60
  import { AnsiLogger, CYAN, YELLOW, db, debugStringify, hk, or, zb } from './logger/export.js';
61
61
  import { isValidNumber, isValidObject, isValidString } from './utils/export.js';
62
62
  import { MatterbridgeServer, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeLiftTiltWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeHepaFilterMonitoringServer, MatterbridgeEnhancedColorControlServer, } from './matterbridgeBehaviors.js';
63
- import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, } from './matterbridgeEndpointHelpers.js';
63
+ import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getApparentElectricalPowerMeasurementClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, } from './matterbridgeEndpointHelpers.js';
64
64
  export class MatterbridgeEndpoint extends Endpoint {
65
65
  static logLevel = "info";
66
66
  mode = undefined;
@@ -782,8 +782,17 @@ export class MatterbridgeEndpoint extends Endpoint {
782
782
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
783
783
  localTemperature: localTemperature * 100,
784
784
  ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
785
- systemMode: Thermostat.SystemMode.Auto,
786
785
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
786
+ systemMode: Thermostat.SystemMode.Auto,
787
+ thermostatRunningState: {
788
+ heat: false,
789
+ cool: false,
790
+ fan: false,
791
+ heatStage2: false,
792
+ coolStage2: false,
793
+ fanStage2: false,
794
+ fanStage3: false,
795
+ },
787
796
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
788
797
  minHeatSetpointLimit: minHeatSetpointLimit * 100,
789
798
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
@@ -806,8 +815,17 @@ export class MatterbridgeEndpoint extends Endpoint {
806
815
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
807
816
  localTemperature: localTemperature * 100,
808
817
  ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
809
- systemMode: Thermostat.SystemMode.Heat,
810
818
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.HeatingOnly,
819
+ systemMode: Thermostat.SystemMode.Heat,
820
+ thermostatRunningState: {
821
+ heat: false,
822
+ cool: false,
823
+ fan: false,
824
+ heatStage2: false,
825
+ coolStage2: false,
826
+ fanStage2: false,
827
+ fanStage3: false,
828
+ },
811
829
  occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
812
830
  minHeatSetpointLimit: minHeatSetpointLimit * 100,
813
831
  maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
@@ -822,8 +840,17 @@ export class MatterbridgeEndpoint extends Endpoint {
822
840
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Cooling, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
823
841
  localTemperature: localTemperature * 100,
824
842
  ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
825
- systemMode: Thermostat.SystemMode.Cool,
826
843
  controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingOnly,
844
+ systemMode: Thermostat.SystemMode.Cool,
845
+ thermostatRunningState: {
846
+ heat: false,
847
+ cool: false,
848
+ fan: false,
849
+ heatStage2: false,
850
+ coolStage2: false,
851
+ fanStage2: false,
852
+ fanStage3: false,
853
+ },
827
854
  occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
828
855
  minCoolSetpointLimit: minCoolSetpointLimit * 100,
829
856
  maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
@@ -1170,59 +1197,15 @@ export class MatterbridgeEndpoint extends Endpoint {
1170
1197
  return this;
1171
1198
  }
1172
1199
  createDefaultElectricalEnergyMeasurementClusterServer(energyImported = null, energyExported = null) {
1173
- this.behaviors.require(ElectricalEnergyMeasurementServer.with(ElectricalEnergyMeasurement.Feature.ImportedEnergy, ElectricalEnergyMeasurement.Feature.ExportedEnergy, ElectricalEnergyMeasurement.Feature.CumulativeEnergy), {
1174
- accuracy: {
1175
- measurementType: MeasurementType.ElectricalEnergy,
1176
- measured: true,
1177
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1178
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1179
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1180
- },
1181
- cumulativeEnergyReset: null,
1182
- cumulativeEnergyImported: energyImported !== null && energyImported >= 0 ? { energy: energyImported } : null,
1183
- cumulativeEnergyExported: energyExported !== null && energyExported >= 0 ? { energy: energyExported } : null,
1184
- });
1200
+ this.behaviors.require(ElectricalEnergyMeasurementServer.with(ElectricalEnergyMeasurement.Feature.ImportedEnergy, ElectricalEnergyMeasurement.Feature.ExportedEnergy, ElectricalEnergyMeasurement.Feature.CumulativeEnergy), getDefaultElectricalEnergyMeasurementClusterServer(energyImported, energyExported));
1185
1201
  return this;
1186
1202
  }
1187
1203
  createDefaultElectricalPowerMeasurementClusterServer(voltage = null, current = null, power = null, frequency = null) {
1188
- this.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), {
1189
- powerMode: ElectricalPowerMeasurement.PowerMode.Ac,
1190
- numberOfMeasurementTypes: 4,
1191
- accuracy: [
1192
- {
1193
- measurementType: MeasurementType.Voltage,
1194
- measured: true,
1195
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1196
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1197
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1198
- },
1199
- {
1200
- measurementType: MeasurementType.ActiveCurrent,
1201
- measured: true,
1202
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1203
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1204
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1205
- },
1206
- {
1207
- measurementType: MeasurementType.ActivePower,
1208
- measured: true,
1209
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1210
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1211
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1212
- },
1213
- {
1214
- measurementType: MeasurementType.Frequency,
1215
- measured: true,
1216
- minMeasuredValue: Number.MIN_SAFE_INTEGER,
1217
- maxMeasuredValue: Number.MAX_SAFE_INTEGER,
1218
- accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
1219
- },
1220
- ],
1221
- voltage: voltage,
1222
- activeCurrent: current,
1223
- activePower: power,
1224
- frequency: frequency,
1225
- });
1204
+ this.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), getDefaultElectricalPowerMeasurementClusterServer(voltage, current, power, frequency));
1205
+ return this;
1206
+ }
1207
+ createApparentElectricalPowerMeasurementClusterServer(voltage = null, apparentCurrent = null, apparentPower = null, frequency = null) {
1208
+ this.behaviors.require(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), getApparentElectricalPowerMeasurementClusterServer(voltage, apparentCurrent, apparentPower, frequency));
1226
1209
  return this;
1227
1210
  }
1228
1211
  createDefaultTemperatureMeasurementClusterServer(measuredValue = null, minMeasuredValue = null, maxMeasuredValue = null) {
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from 'node-ansi-logger';
3
3
  import { Lifecycle } from '@matter/main';
4
- import { getClusterNameById } from '@matter/main/types';
4
+ import { getClusterNameById, MeasurementType } from '@matter/main/types';
5
5
  import { PowerSource } from '@matter/main/clusters/power-source';
6
6
  import { UserLabel } from '@matter/main/clusters/user-label';
7
7
  import { FixedLabel } from '@matter/main/clusters/fixed-label';
@@ -601,3 +601,97 @@ export function getDefaultOccupancySensingClusterServer(occupied = false, holdTi
601
601
  holdTimeLimits: { holdTimeMin, holdTimeMax, holdTimeDefault: holdTime },
602
602
  });
603
603
  }
604
+ export function getDefaultElectricalEnergyMeasurementClusterServer(energyImported = null, energyExported = null) {
605
+ return optionsFor(ElectricalEnergyMeasurementServer.with(ElectricalEnergyMeasurement.Feature.ImportedEnergy, ElectricalEnergyMeasurement.Feature.ExportedEnergy, ElectricalEnergyMeasurement.Feature.CumulativeEnergy), {
606
+ accuracy: {
607
+ measurementType: MeasurementType.ElectricalEnergy,
608
+ measured: true,
609
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
610
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
611
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
612
+ },
613
+ cumulativeEnergyReset: null,
614
+ cumulativeEnergyImported: energyImported !== null && energyImported >= 0 ? { energy: energyImported } : null,
615
+ cumulativeEnergyExported: energyExported !== null && energyExported >= 0 ? { energy: energyExported } : null,
616
+ });
617
+ }
618
+ export function getDefaultElectricalPowerMeasurementClusterServer(voltage = null, current = null, power = null, frequency = null) {
619
+ return optionsFor(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), {
620
+ powerMode: ElectricalPowerMeasurement.PowerMode.Ac,
621
+ numberOfMeasurementTypes: 4,
622
+ accuracy: [
623
+ {
624
+ measurementType: MeasurementType.Voltage,
625
+ measured: true,
626
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
627
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
628
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
629
+ },
630
+ {
631
+ measurementType: MeasurementType.ActiveCurrent,
632
+ measured: true,
633
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
634
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
635
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
636
+ },
637
+ {
638
+ measurementType: MeasurementType.ActivePower,
639
+ measured: true,
640
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
641
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
642
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
643
+ },
644
+ {
645
+ measurementType: MeasurementType.Frequency,
646
+ measured: true,
647
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
648
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
649
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
650
+ },
651
+ ],
652
+ voltage: voltage,
653
+ activeCurrent: current,
654
+ activePower: power,
655
+ frequency: frequency,
656
+ });
657
+ }
658
+ export function getApparentElectricalPowerMeasurementClusterServer(voltage = null, apparentCurrent = null, apparentPower = null, frequency = null) {
659
+ return optionsFor(ElectricalPowerMeasurementServer.with(ElectricalPowerMeasurement.Feature.AlternatingCurrent), {
660
+ powerMode: ElectricalPowerMeasurement.PowerMode.Ac,
661
+ numberOfMeasurementTypes: 4,
662
+ accuracy: [
663
+ {
664
+ measurementType: MeasurementType.Voltage,
665
+ measured: true,
666
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
667
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
668
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
669
+ },
670
+ {
671
+ measurementType: MeasurementType.ApparentCurrent,
672
+ measured: true,
673
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
674
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
675
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
676
+ },
677
+ {
678
+ measurementType: MeasurementType.ApparentPower,
679
+ measured: true,
680
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
681
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
682
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
683
+ },
684
+ {
685
+ measurementType: MeasurementType.Frequency,
686
+ measured: true,
687
+ minMeasuredValue: Number.MIN_SAFE_INTEGER,
688
+ maxMeasuredValue: Number.MAX_SAFE_INTEGER,
689
+ accuracyRanges: [{ rangeMin: Number.MIN_SAFE_INTEGER, rangeMax: Number.MAX_SAFE_INTEGER, fixedMax: 1 }],
690
+ },
691
+ ],
692
+ voltage: voltage,
693
+ apparentCurrent: apparentCurrent,
694
+ apparentPower: apparentPower,
695
+ frequency: frequency,
696
+ });
697
+ }