@matterbridge/core 3.10.13-dev-20261004-0c9c4ee → 3.10.13-dev-20261007-320f739

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/dist/cli.js CHANGED
@@ -100,6 +100,11 @@ async function main() {
100
100
  const { Matterbridge } = await import('./matterbridge.js');
101
101
  instance = await Matterbridge.loadInstance(true);
102
102
  log.debug(`***Matterbridge.loadInstance(true) exited`);
103
+ if (hasParameter('experimental')) {
104
+ log.debug(`***Starting Backend thread...`);
105
+ manager.runThread('Backend');
106
+ log.debug(`***Started Backend thread`);
107
+ }
103
108
  if (!instance || instance.shutdown) {
104
109
  await shutdown();
105
110
  }
@@ -1,4 +1,4 @@
1
- import type { BaseDevice } from '@matterbridge/types';
1
+ import type { ApiDevice, BaseDevice } from '@matterbridge/types';
2
2
  import { LogLevel } from 'node-ansi-logger';
3
3
  import type { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
4
4
  export declare function toBaseDevice(device: MatterbridgeEndpoint | BaseDevice): BaseDevice;
@@ -21,6 +21,7 @@ export declare class DeviceManager {
21
21
  private toBaseDevice;
22
22
  array(): MatterbridgeEndpoint[];
23
23
  baseArray(pluginName?: string): BaseDevice[];
24
+ apiDeviceArray(pluginName?: string): ApiDevice[];
24
25
  [Symbol.iterator](): IterableIterator<MatterbridgeEndpoint>;
25
26
  forEach(callback: (device: MatterbridgeEndpoint) => Promise<void>): Promise<void>;
26
27
  set logLevel(logLevel: LogLevel);
@@ -3,6 +3,7 @@ import { dev } from '@matterbridge/types';
3
3
  import { hasParameter } from '@matterbridge/utils/cli';
4
4
  import { logModuleLoaded } from '@matterbridge/utils/loader';
5
5
  import { AnsiLogger, BLUE, CYAN, db, debugStringify, er } from 'node-ansi-logger';
6
+ import { getBatteryLevel, getClusterTextFromDevice, getPowerSource, getReachability, getServerNodeData } from './matterNodeHelpers.js';
6
7
  logModuleLoaded('Device Manager');
7
8
  export function toBaseDevice(device) {
8
9
  return {
@@ -87,6 +88,9 @@ export class DeviceManager {
87
88
  case 'devices_basearray':
88
89
  this.server.respond({ ...msg, result: { devices: this.baseArray(msg.params.pluginName) } });
89
90
  break;
91
+ case 'devices_apidevicearray':
92
+ this.server.respond({ ...msg, result: { devices: this.apiDeviceArray(msg.params.pluginName) } });
93
+ break;
90
94
  default:
91
95
  if (this.verbose)
92
96
  this.log.debug(`Unknown broadcast message ${CYAN}${msg.type}${db} from ${CYAN}${msg.src}${db}`);
@@ -138,6 +142,31 @@ export class DeviceManager {
138
142
  }
139
143
  return devices;
140
144
  }
145
+ apiDeviceArray(pluginName) {
146
+ const devices = [];
147
+ for (const device of this._devices.values()) {
148
+ if (pluginName && pluginName !== device.plugin)
149
+ continue;
150
+ if (!device.plugin || !device.deviceType || !device.name || !device.deviceName || !device.serialNumber || !device.uniqueId || !device.lifecycle.isReady)
151
+ continue;
152
+ devices.push({
153
+ pluginName: device.plugin,
154
+ type: device.name + ' (0x' + device.deviceType.toString(16).padStart(4, '0') + ')',
155
+ endpoint: device.number,
156
+ name: device.deviceName,
157
+ serial: device.serialNumber,
158
+ productUrl: device.productUrl,
159
+ configUrl: device.configUrl,
160
+ uniqueId: device.uniqueId,
161
+ reachable: getReachability(device),
162
+ powerSource: getPowerSource(device),
163
+ batteryLevel: getBatteryLevel(device),
164
+ matter: device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
165
+ cluster: getClusterTextFromDevice(device),
166
+ });
167
+ }
168
+ return devices;
169
+ }
141
170
  [Symbol.iterator]() {
142
171
  return this._devices.values();
143
172
  }
@@ -27,6 +27,7 @@ export declare class Frontend extends EventEmitter<FrontendEvents> {
27
27
  private serverFetchTimeout;
28
28
  private readonly debug;
29
29
  private readonly verbose;
30
+ private readonly diagnostic;
30
31
  readonly readOnly: boolean;
31
32
  readonly shellyBoard: boolean;
32
33
  shellySysUpdate: boolean;
@@ -42,13 +43,9 @@ export declare class Frontend extends EventEmitter<FrontendEvents> {
42
43
  start(port?: number): Promise<void>;
43
44
  stop(): Promise<void>;
44
45
  getApiSettings(): ApiSettings;
45
- private getReachability;
46
- private getPowerSource;
47
- private getBatteryLevel;
48
- private getClusterTextFromDevice;
49
46
  getApiPlugins(): ApiPlugin[];
50
47
  getApiDevices(pluginName?: string): ApiDevice[];
51
- getClusters(pluginName: string, endpointNumber: number, serialNumber?: string, uniqueId?: string): ApiClusters | undefined;
48
+ getApiCluster(pluginName: string, endpointNumber: number, serialNumber?: string, uniqueId?: string): ApiClusters | undefined;
52
49
  generateDiagnostic(): Promise<void>;
53
50
  private wsMessageHandler;
54
51
  wssSendLogMessage(level: string, time: string, name: string, message: string): void;
package/dist/frontend.js CHANGED
@@ -1,28 +1,15 @@
1
1
  import EventEmitter from 'node:events';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { Diagnostic, Lifecycle, LogDestination, LogFormat as MatterLogFormat, Logger, LogLevel as MatterLogLevel } from '@matter/general';
4
+ import { Diagnostic, LogDestination, LogFormat as MatterLogFormat, Logger, LogLevel as MatterLogLevel } from '@matter/general';
5
5
  import { DeviceAdvertiser, DeviceCommissioner, FabricManager } from '@matter/protocol';
6
- import { getClusterNameById } from '@matter/types/cluster';
7
- import { AirQuality } from '@matter/types/clusters/air-quality';
8
- import { BridgedDeviceBasicInformation } from '@matter/types/clusters/bridged-device-basic-information';
9
- import { ClosureControl } from '@matter/types/clusters/closure-control';
10
- import { DeviceEnergyManagement } from '@matter/types/clusters/device-energy-management';
11
- import { FanControl } from '@matter/types/clusters/fan-control';
12
- import { OperationalState } from '@matter/types/clusters/operational-state';
13
- import { PowerSource } from '@matter/types/clusters/power-source';
14
- import { RvcOperationalState } from '@matter/types/clusters/rvc-operational-state';
15
- import { SmokeCoAlarm } from '@matter/types/clusters/smoke-co-alarm';
16
- import { SoilMeasurement } from '@matter/types/clusters/soil-measurement';
17
- import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
18
6
  import { CommissioningOptions } from '@matter/types/commissioning';
19
7
  import { FabricIndex } from '@matter/types/datatype';
20
- import { ThreeLevelAuto } from '@matter/types/globals';
21
8
  import { BroadcastServer } from '@matterbridge/thread/server';
22
9
  import { MATTER_LOGGER_FILE, MATTER_STORAGE_DIR, MATTERBRIDGE_DIAGNOSTIC_FILE, MATTERBRIDGE_HISTORY_FILE, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, } from '@matterbridge/types';
23
- import { isBun } from '@matterbridge/utils/bun';
10
+ import { isBun, memoryFootprint } from '@matterbridge/utils/bun';
24
11
  import { getParameter, hasParameter } from '@matterbridge/utils/cli';
25
- import { getEnumDescription } from '@matterbridge/utils/enum';
12
+ import { writeDiagnostic } from '@matterbridge/utils/diagnostic';
26
13
  import { getErrorMessage, inspectError, logError } from '@matterbridge/utils/error';
27
14
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
28
15
  import { logModuleLoaded } from '@matterbridge/utils/loader';
@@ -31,7 +18,8 @@ import { fireAndForget, wait, withTimeout } from '@matterbridge/utils/wait';
31
18
  import { AnsiLogger, bgHex, CYAN, db, debugStringify, er, GREEN, nf, nt, rs, stringify, UNDERLINE, UNDERLINEOFF, wr, YELLOW } from 'node-ansi-logger';
32
19
  import { cliEmitter, lastOsCpuUsage, lastProcessCpuUsage } from './cliEmitter.js';
33
20
  import { generateHistoryPage } from './cliHistory.js';
34
- import { capitalizeFirstLetter, getAttribute } from './matterbridgeEndpointHelpers.js';
21
+ import { capitalizeFirstLetter } from './matterbridgeEndpointHelpers.js';
22
+ import { deleteAdvertisingNode, getBatteryLevel, getClusterTextFromDevice, getPowerSource, getReachability, getServerNodeData, setAdvertisingNode } from './matterNodeHelpers.js';
35
23
  logModuleLoaded('Frontend');
36
24
  export class Frontend extends EventEmitter {
37
25
  matterbridge;
@@ -50,6 +38,7 @@ export class Frontend extends EventEmitter {
50
38
  serverFetchTimeout = 2000;
51
39
  debug = hasParameter('debug') || hasParameter('verbose');
52
40
  verbose = hasParameter('verbose');
41
+ diagnostic = hasParameter('diagnostic');
53
42
  readOnly = hasParameter('readonly') || hasParameter('shelly');
54
43
  shellyBoard = hasParameter('shelly');
55
44
  shellySysUpdate = false;
@@ -942,7 +931,7 @@ export class Frontend extends EventEmitter {
942
931
  this.matterbridge.systemInformation.processUptime = formatUptime(Math.floor(process.uptime()));
943
932
  this.matterbridge.systemInformation.cpuUsage = formatPercent(lastOsCpuUsage);
944
933
  this.matterbridge.systemInformation.processCpuUsage = formatPercent(lastProcessCpuUsage);
945
- this.matterbridge.systemInformation.rss = formatBytes(process.memoryUsage().rss);
934
+ this.matterbridge.systemInformation.rss = formatBytes(memoryFootprint());
946
935
  this.matterbridge.systemInformation.heapTotal = formatBytes(process.memoryUsage().heapTotal);
947
936
  this.matterbridge.systemInformation.heapUsed = formatBytes(process.memoryUsage().heapUsed);
948
937
  const info = {
@@ -989,274 +978,6 @@ export class Frontend extends EventEmitter {
989
978
  };
990
979
  return { systemInformation: this.matterbridge.systemInformation, matterbridgeInformation: info };
991
980
  }
992
- getReachability(device) {
993
- if (this.matterbridge.hasCleanupStarted)
994
- return false;
995
- if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
996
- return false;
997
- if (device.hasClusterServer(BridgedDeviceBasicInformation.id))
998
- return device.getAttribute(BridgedDeviceBasicInformation, 'reachable');
999
- if (device.mode === 'server' && device.serverNode?.state.basicInformation.reachable !== undefined)
1000
- return device.serverNode.state.basicInformation.reachable;
1001
- if (this.matterbridge.bridgeMode === 'childbridge')
1002
- return true;
1003
- return false;
1004
- }
1005
- getPowerSource(endpoint) {
1006
- if (this.matterbridge.hasCleanupStarted)
1007
- return undefined;
1008
- if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1009
- return undefined;
1010
- const powerSource = (device) => {
1011
- const featureMap = device.getAttribute(PowerSource.id, 'featureMap');
1012
- if (featureMap.wired) {
1013
- const wiredCurrentType = device.getAttribute(PowerSource.id, 'wiredCurrentType');
1014
- return ['ac', 'dc'][wiredCurrentType];
1015
- }
1016
- if (featureMap.battery) {
1017
- const batChargeLevel = device.getAttribute(PowerSource.id, 'batChargeLevel');
1018
- return ['ok', 'warning', 'critical'][batChargeLevel];
1019
- }
1020
- return undefined;
1021
- };
1022
- if (endpoint.hasClusterServer(PowerSource.id))
1023
- return powerSource(endpoint);
1024
- for (const child of endpoint.getChildEndpoints()) {
1025
- if (child.hasClusterServer(PowerSource.id))
1026
- return powerSource(child);
1027
- }
1028
- return undefined;
1029
- }
1030
- getBatteryLevel(endpoint) {
1031
- if (this.matterbridge.hasCleanupStarted)
1032
- return undefined;
1033
- if (!endpoint.lifecycle.isReady || endpoint.construction.status !== Lifecycle.Status.Active)
1034
- return undefined;
1035
- const batteryLevel = (device) => {
1036
- const featureMap = device.getAttribute(PowerSource.id, 'featureMap');
1037
- if (featureMap.battery) {
1038
- const batChargeLevel = device.getAttribute(PowerSource.id, 'batPercentRemaining');
1039
- return isValidNumber(batChargeLevel) ? batChargeLevel / 2 : undefined;
1040
- }
1041
- return undefined;
1042
- };
1043
- if (endpoint.hasClusterServer(PowerSource.id))
1044
- return batteryLevel(endpoint);
1045
- for (const child of endpoint.getChildEndpoints()) {
1046
- if (child.hasClusterServer(PowerSource.id))
1047
- return batteryLevel(child);
1048
- }
1049
- return undefined;
1050
- }
1051
- getClusterTextFromDevice(device) {
1052
- if (this.matterbridge.hasCleanupStarted)
1053
- return '';
1054
- if (!device.lifecycle.isReady || device.construction.status !== Lifecycle.Status.Active)
1055
- return '';
1056
- const getUserLabel = (device) => {
1057
- const labelList = getAttribute(device, 'userLabel', 'labelList');
1058
- if (labelList) {
1059
- const composed = labelList.find((entry) => entry.label === 'composed');
1060
- if (composed)
1061
- return 'Composed: ' + composed.value;
1062
- }
1063
- return '';
1064
- };
1065
- const getFixedLabel = (device) => {
1066
- const labelList = getAttribute(device, 'fixedLabel', 'labelList');
1067
- if (labelList) {
1068
- const composed = labelList.find((entry) => entry.label === 'composed');
1069
- if (composed)
1070
- return 'Composed: ' + composed.value;
1071
- }
1072
- return '';
1073
- };
1074
- let attributes = '';
1075
- let supportedModes = [];
1076
- const getMeasurementText = (value, scale, unit) => {
1077
- if (value === null)
1078
- return 'unknown';
1079
- if (isValidNumber(value) || typeof value === 'bigint')
1080
- return `${Number(value) / scale}${unit}`;
1081
- return '';
1082
- };
1083
- const getEnergyText = (value) => {
1084
- if (value === null)
1085
- return 'unknown';
1086
- if (isValidObject(value) && 'energy' in value && (isValidNumber(value.energy) || typeof value.energy === 'bigint'))
1087
- return `${Number(value.energy) / 1_000_000}kWh`;
1088
- return '';
1089
- };
1090
- const appendMeasurement = (label, value) => {
1091
- if (value)
1092
- attributes += `${label}: ${value} `;
1093
- };
1094
- device.forEachAttribute((clusterName, clusterId, attributeName, attributeId, attributeValue) => {
1095
- if (typeof attributeValue === 'undefined' || attributeValue === undefined)
1096
- return;
1097
- if (clusterName === 'descriptor' && attributeName === 'clientList' && isValidArray(attributeValue, 1))
1098
- attributes += `Client cluster(s): [${attributeValue.map((id) => getClusterNameById(id)).join(', ')}] `;
1099
- if (clusterName === 'binding' && attributeName === 'binding' && isValidArray(attributeValue)) {
1100
- if (attributeValue.length === 0)
1101
- attributes += `Bound cluster(s): none `;
1102
- else {
1103
- const targets = attributeValue;
1104
- const formatted = targets.map((target) => {
1105
- const parts = [];
1106
- if (target.node !== undefined)
1107
- parts.push(`node: ${target.node}`);
1108
- if (target.group !== undefined)
1109
- parts.push(`group: ${target.group}`);
1110
- if (target.endpoint !== undefined)
1111
- parts.push(`endpoint: ${target.endpoint}`);
1112
- if (target.cluster !== undefined)
1113
- parts.push(`cluster: ${getClusterNameById(target.cluster)}`);
1114
- if (target.fabricIndex !== undefined)
1115
- parts.push(`fabricIndex: ${target.fabricIndex}`);
1116
- return `[${parts.join(', ')}]`;
1117
- });
1118
- attributes += `Bound cluster(s): ${formatted.join(' ')} `;
1119
- }
1120
- }
1121
- if (clusterName === 'onOff' && attributeName === 'onOff')
1122
- attributes += `OnOff: ${attributeValue} `;
1123
- if (clusterName === 'switch' && attributeName === 'currentPosition')
1124
- attributes += `Position: ${attributeValue} `;
1125
- if (clusterName === 'windowCovering' && attributeName === 'currentPositionLiftPercent100ths' && isValidNumber(attributeValue, 0, 10000))
1126
- attributes += `Cover position: ${attributeValue / 100}% `;
1127
- if (clusterName === 'doorLock' && attributeName === 'lockState')
1128
- attributes += `State: ${attributeValue === 1 ? 'Locked' : 'Not locked'} `;
1129
- if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && attributeValue === null)
1130
- attributes += `Position: unknown Latch: unknown Speed: unknown SecureState: unknown `;
1131
- if (clusterName === 'closureControl' && attributeName === 'overallCurrentState' && isValidObject(attributeValue)) {
1132
- const overallCurrentState = attributeValue;
1133
- attributes += `Position: ${getEnumDescription(ClosureControl.CurrentPosition, overallCurrentState.position, { fallback: 'unknown' })} `;
1134
- if (overallCurrentState.latch !== undefined)
1135
- attributes += `Latch: ${overallCurrentState.latch} `;
1136
- if (overallCurrentState.speed !== undefined)
1137
- attributes += `Speed: ${getEnumDescription(ThreeLevelAuto, overallCurrentState.speed)} `;
1138
- attributes += `SecureState: ${overallCurrentState.secureState ?? 'unknown'} `;
1139
- }
1140
- if (clusterName === 'thermostat' && attributeName === 'localTemperature' && isValidNumber(attributeValue))
1141
- attributes += `Temperature: ${attributeValue / 100}°C `;
1142
- if (clusterName === 'thermostat' && attributeName === 'occupiedHeatingSetpoint' && isValidNumber(attributeValue))
1143
- attributes += `Heat to: ${attributeValue / 100}°C `;
1144
- if (clusterName === 'thermostat' && attributeName === 'occupiedCoolingSetpoint' && isValidNumber(attributeValue))
1145
- attributes += `Cool to: ${attributeValue / 100}°C `;
1146
- const modeClusters = new Set(['modeSelect', 'rvcRunMode', 'rvcCleanMode', 'laundryWasherMode', 'ovenMode', 'microwaveOvenMode', 'deviceEnergyManagementMode']);
1147
- if (modeClusters.has(clusterName) && attributeName === 'supportedModes') {
1148
- supportedModes = attributeValue;
1149
- }
1150
- if (modeClusters.has(clusterName) && attributeName === 'currentMode') {
1151
- const supportedMode = supportedModes.find((mode) => mode.mode === attributeValue);
1152
- if (supportedMode)
1153
- attributes += `Mode: ${supportedMode.label} `;
1154
- }
1155
- if (clusterName === 'operationalState' && attributeName === 'operationalState')
1156
- attributes += `OpState: ${getEnumDescription(OperationalState.OperationalStateEnum, attributeValue)} `;
1157
- if (clusterName === 'rvcOperationalState' && attributeName === 'operationalState')
1158
- attributes += `OpState: ${getEnumDescription(RvcOperationalState.OperationalState, attributeValue)} `;
1159
- if (clusterName === 'pumpConfigurationAndControl' && attributeName === 'operationMode')
1160
- attributes += `Mode: ${attributeValue} `;
1161
- if (clusterName === 'valveConfigurationAndControl' && attributeName === 'currentState')
1162
- attributes += `State: ${getEnumDescription(ValveConfigurationAndControl.ValveState, attributeValue, { fallback: 'unknown' })} `;
1163
- if (clusterName === 'levelControl' && attributeName === 'currentLevel')
1164
- attributes += `Level: ${attributeValue} `;
1165
- if (clusterName === 'applicationBasic' && attributeName === 'applicationName')
1166
- attributes += `App: ${attributeValue} `;
1167
- if (clusterName === 'colorControl' && attributeName === 'colorMode' && isValidNumber(attributeValue, 0, 2))
1168
- attributes += `Mode: ${['HS', 'XY', 'CT'][attributeValue]} `;
1169
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentHue' && isValidNumber(attributeValue))
1170
- attributes += `Hue: ${Math.round(attributeValue)} `;
1171
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 0 && attributeName === 'currentSaturation' && isValidNumber(attributeValue))
1172
- attributes += `Saturation: ${Math.round(attributeValue)} `;
1173
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentX' && isValidNumber(attributeValue))
1174
- attributes += `X: ${Math.round(attributeValue / 655.36) / 100} `;
1175
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 1 && attributeName === 'currentY' && isValidNumber(attributeValue))
1176
- attributes += `Y: ${Math.round(attributeValue / 655.36) / 100} `;
1177
- if (clusterName === 'colorControl' && getAttribute(device, 'colorControl', 'colorMode') === 2 && attributeName === 'colorTemperatureMireds' && isValidNumber(attributeValue))
1178
- attributes += `ColorTemp: ${Math.round(attributeValue)} `;
1179
- if (clusterName === 'booleanState' && attributeName === 'stateValue')
1180
- attributes += `Contact: ${attributeValue} `;
1181
- if (clusterName === 'booleanStateConfiguration' && attributeName === 'alarmsActive' && isValidObject(attributeValue))
1182
- attributes += `Active alarms: ${stringify(attributeValue)} `;
1183
- if (clusterName === 'smokeCoAlarm' && attributeName === 'smokeState')
1184
- attributes += `Smoke: ${getEnumDescription(SmokeCoAlarm.AlarmState, attributeValue)} `;
1185
- if (clusterName === 'smokeCoAlarm' && attributeName === 'coState')
1186
- attributes += `Co: ${getEnumDescription(SmokeCoAlarm.AlarmState, attributeValue)} `;
1187
- if (clusterName === 'fanControl' && attributeName === 'fanMode')
1188
- attributes += `Mode: ${getEnumDescription(FanControl.FanMode, attributeValue)} `;
1189
- if (clusterName === 'fanControl' && attributeName === 'percentCurrent')
1190
- attributes += `Percent: ${attributeValue}% `;
1191
- if (clusterName === 'fanControl' && attributeName === 'speedCurrent')
1192
- attributes += `Speed: ${attributeValue} `;
1193
- if (clusterName === 'fanControl' && attributeName === 'rockSetting' && isValidObject(attributeValue))
1194
- attributes += `Rock: ${stringify(attributeValue)} `;
1195
- if (clusterName === 'fanControl' && attributeName === 'windSetting' && isValidObject(attributeValue))
1196
- attributes += `Wind: ${stringify(attributeValue)} `;
1197
- if (clusterName === 'fanControl' && attributeName === 'airflowDirection')
1198
- attributes += `Direction: ${getEnumDescription(FanControl.AirflowDirection, attributeValue)} `;
1199
- if (clusterName === 'hepaFilterMonitoring' && attributeName === 'condition')
1200
- attributes += `Hepa filter: ${attributeValue}% `;
1201
- if (clusterName === 'activatedCarbonFilterMonitoring' && attributeName === 'condition')
1202
- attributes += `Carbon filter: ${attributeValue}% `;
1203
- if (clusterName === 'waterTankLevelMonitoring' && attributeName === 'condition')
1204
- attributes += `Water tank: ${attributeValue}% `;
1205
- if (clusterName === 'temperatureAlarm' && attributeName === 'state' && isValidObject(attributeValue))
1206
- attributes += `Temp alarm: ${stringify(attributeValue)} `;
1207
- if (clusterName === 'occupancySensing' && attributeName === 'occupancy' && isValidObject(attributeValue, 1))
1208
- attributes += `Occupancy: ${attributeValue.occupied} `;
1209
- if (clusterName === 'illuminanceMeasurement' && attributeName === 'measuredValue') {
1210
- if (attributeValue === null)
1211
- attributes += `Illuminance: unknown `;
1212
- else if (isValidNumber(attributeValue))
1213
- attributes += `Illuminance: ${Math.round(Math.max(Math.pow(10, attributeValue / 10000), 0))} lx`;
1214
- }
1215
- if (clusterName === 'airQuality' && attributeName === 'airQuality')
1216
- attributes += `Air quality: ${getEnumDescription(AirQuality.AirQualityEnum, attributeValue)} `;
1217
- if (clusterName === 'totalVolatileOrganicCompoundsConcentrationMeasurement' && attributeName === 'measuredValue')
1218
- appendMeasurement('Voc', getMeasurementText(attributeValue, 1, ''));
1219
- if (clusterName === 'pm1ConcentrationMeasurement' && attributeName === 'measuredValue')
1220
- appendMeasurement('Pm1', getMeasurementText(attributeValue, 1, ''));
1221
- if (clusterName === 'pm25ConcentrationMeasurement' && attributeName === 'measuredValue')
1222
- appendMeasurement('Pm2.5', getMeasurementText(attributeValue, 1, ''));
1223
- if (clusterName === 'pm10ConcentrationMeasurement' && attributeName === 'measuredValue')
1224
- appendMeasurement('Pm10', getMeasurementText(attributeValue, 1, ''));
1225
- if (clusterName === 'formaldehydeConcentrationMeasurement' && attributeName === 'measuredValue')
1226
- appendMeasurement('CH₂O', getMeasurementText(attributeValue, 1, ''));
1227
- if (clusterName === 'temperatureMeasurement' && attributeName === 'measuredValue')
1228
- appendMeasurement('Temperature', getMeasurementText(attributeValue, 100, ' °C'));
1229
- if (clusterName === 'relativeHumidityMeasurement' && attributeName === 'measuredValue')
1230
- appendMeasurement('Humidity', getMeasurementText(attributeValue, 100, '%'));
1231
- if (clusterName === 'pressureMeasurement' && attributeName === 'measuredValue')
1232
- appendMeasurement('Pressure', getMeasurementText(attributeValue, 1, ' hPa'));
1233
- if (clusterName === 'flowMeasurement' && attributeName === 'measuredValue')
1234
- appendMeasurement('Flow', getMeasurementText(attributeValue, 10, ' m³/h'));
1235
- if (clusterId === SoilMeasurement.id && attributeName === 'soilMoistureMeasuredValue')
1236
- appendMeasurement('Soil moisture', getMeasurementText(attributeValue, 1, '%'));
1237
- if (clusterName === 'electricalPowerMeasurement' && attributeName === 'voltage')
1238
- appendMeasurement('Voltage', getMeasurementText(attributeValue, 1_000, 'V'));
1239
- if (clusterName === 'electricalPowerMeasurement' && attributeName === 'activeCurrent')
1240
- appendMeasurement('Current', getMeasurementText(attributeValue, 1_000, 'A'));
1241
- if (clusterName === 'electricalPowerMeasurement' && attributeName === 'activePower')
1242
- appendMeasurement('Power', getMeasurementText(attributeValue, 1_000_000, 'kW'));
1243
- if (clusterName === 'electricalPowerMeasurement' && attributeName === 'frequency')
1244
- appendMeasurement('Frequency', getMeasurementText(attributeValue, 1_000, 'Hz'));
1245
- if (clusterName === 'electricalEnergyMeasurement' && attributeName === 'cumulativeEnergyImported')
1246
- appendMeasurement('Imported', getEnergyText(attributeValue));
1247
- if (clusterName === 'electricalEnergyMeasurement' && attributeName === 'cumulativeEnergyExported')
1248
- appendMeasurement('Exported', getEnergyText(attributeValue));
1249
- if (clusterName === 'deviceEnergyManagement' && attributeName === 'esaCanGenerate')
1250
- attributes += `ESA can generate: ${attributeValue} `;
1251
- if (clusterName === 'deviceEnergyManagement' && attributeName === 'esaState')
1252
- attributes += `ESA state: ${DeviceEnergyManagement.EsaState[attributeValue]} `;
1253
- if (clusterName === 'fixedLabel' && attributeName === 'labelList')
1254
- attributes += `${getFixedLabel(device)} `;
1255
- if (clusterName === 'userLabel' && attributeName === 'labelList')
1256
- attributes += `${getUserLabel(device)} `;
1257
- });
1258
- return attributes.trimStart().trimEnd();
1259
- }
1260
981
  getApiPlugins() {
1261
982
  if (this.matterbridge.hasCleanupStarted)
1262
983
  return [];
@@ -1291,7 +1012,7 @@ export class Frontend extends EventEmitter {
1291
1012
  hasWhiteList: plugin.configJson?.whiteList !== undefined,
1292
1013
  hasBlackList: plugin.configJson?.blackList !== undefined,
1293
1014
  frontendPath: plugin.frontendPath,
1294
- matter: plugin.serverNode ? this.matterbridge.getServerNodeData(plugin.serverNode) : undefined,
1015
+ matter: plugin.serverNode ? getServerNodeData(plugin.serverNode) : undefined,
1295
1016
  });
1296
1017
  }
1297
1018
  return plugins;
@@ -1314,23 +1035,23 @@ export class Frontend extends EventEmitter {
1314
1035
  productUrl: device.productUrl,
1315
1036
  configUrl: device.configUrl,
1316
1037
  uniqueId: device.uniqueId,
1317
- reachable: this.getReachability(device),
1318
- powerSource: this.getPowerSource(device),
1319
- batteryLevel: this.getBatteryLevel(device),
1320
- matter: device.mode === 'server' && device.serverNode ? this.matterbridge.getServerNodeData(device.serverNode) : undefined,
1321
- cluster: this.getClusterTextFromDevice(device),
1038
+ reachable: getReachability(device),
1039
+ powerSource: getPowerSource(device),
1040
+ batteryLevel: getBatteryLevel(device),
1041
+ matter: device.mode === 'server' && device.serverNode ? getServerNodeData(device.serverNode) : undefined,
1042
+ cluster: getClusterTextFromDevice(device),
1322
1043
  });
1323
1044
  }
1324
1045
  return devices;
1325
1046
  }
1326
- getClusters(pluginName, endpointNumber, serialNumber, uniqueId) {
1047
+ getApiCluster(pluginName, endpointNumber, serialNumber, uniqueId) {
1327
1048
  if (this.matterbridge.hasCleanupStarted)
1328
1049
  return;
1329
1050
  const endpoint = this.matterbridge.devices
1330
1051
  .array()
1331
1052
  .find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber && (!serialNumber || d.serialNumber === serialNumber) && (!uniqueId || d.uniqueId === uniqueId));
1332
1053
  if (!endpoint?.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1333
- this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber} (serial: ${serialNumber ?? 'N/A'}, uniqueId: ${uniqueId ?? 'N/A'})`);
1054
+ this.log.error(`getApiCluster: no device found for plugin ${pluginName} and endpoint number ${endpointNumber} (serial: ${serialNumber ?? 'N/A'}, uniqueId: ${uniqueId ?? 'N/A'})`);
1334
1055
  return;
1335
1056
  }
1336
1057
  const deviceTypes = [];
@@ -1359,7 +1080,7 @@ export class Frontend extends EventEmitter {
1359
1080
  const childEndpoints = endpoint.getChildEndpoints();
1360
1081
  childEndpoints.forEach((childEndpoint) => {
1361
1082
  if (!childEndpoint.maybeId || !childEndpoint.maybeNumber) {
1362
- this.log.error(`getClusters: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1083
+ this.log.error(`getApiCluster: no child endpoint found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1363
1084
  return;
1364
1085
  }
1365
1086
  const deviceTypes = [];
@@ -1746,7 +1467,7 @@ export class Frontend extends EventEmitter {
1746
1467
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: `Unknown server node id ${localData.params.id} in /api/matter` });
1747
1468
  return;
1748
1469
  }
1749
- const matter = this.matterbridge.getServerNodeData(serverNode);
1470
+ const matter = getServerNodeData(serverNode);
1750
1471
  this.log.debug(`*Server node ${serverNode.id}: commissioned ${serverNode.state.commissioning.commissioned} upTime ${serverNode.state.generalDiagnostics.upTime}.`);
1751
1472
  if (data.params.server) {
1752
1473
  this.log.debug(`*Sending data for node ${data.params.id}`);
@@ -1754,13 +1475,13 @@ export class Frontend extends EventEmitter {
1754
1475
  }
1755
1476
  if (data.params.startCommission) {
1756
1477
  await serverNode.env.get(DeviceCommissioner)?.allowBasicCommissioning();
1757
- this.matterbridge.advertisingNodes.set(serverNode.id, Date.now());
1478
+ setAdvertisingNode(serverNode.id, Date.now());
1758
1479
  this.log.debug(`*Allow commissioning has been sent for node ${data.params.id}`);
1759
1480
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: Date.now(), advertising: true } });
1760
1481
  }
1761
1482
  if (data.params.stopCommission) {
1762
1483
  await serverNode.env.get(DeviceCommissioner)?.endCommissioning();
1763
- this.matterbridge.advertisingNodes.delete(serverNode.id);
1484
+ deleteAdvertisingNode(serverNode.id);
1764
1485
  this.log.debug(`*End commissioning has been sent for node ${data.params.id}`);
1765
1486
  this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
1766
1487
  }
@@ -1780,13 +1501,24 @@ export class Frontend extends EventEmitter {
1780
1501
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: matter });
1781
1502
  }
1782
1503
  else if (data.method === '/api/settings') {
1783
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: this.getApiSettings() });
1504
+ const start = performance.now();
1505
+ const settings = this.getApiSettings();
1506
+ if (this.diagnostic)
1507
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiSettings() took ${(performance.now() - start).toFixed(2)} ms`);
1508
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: settings });
1784
1509
  }
1785
1510
  else if (data.method === '/api/plugins') {
1786
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: this.getApiPlugins() });
1511
+ const start = performance.now();
1512
+ const plugins = this.getApiPlugins();
1513
+ if (this.diagnostic)
1514
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiPlugins() took ${(performance.now() - start).toFixed(2)} ms`);
1515
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: plugins });
1787
1516
  }
1788
1517
  else if (data.method === '/api/devices') {
1518
+ const start = performance.now();
1789
1519
  const devices = this.getApiDevices(isValidString(data.params.pluginName) ? data.params.pluginName : undefined);
1520
+ if (this.diagnostic)
1521
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiDevices() took ${(performance.now() - start).toFixed(2)} ms`);
1790
1522
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: devices });
1791
1523
  }
1792
1524
  else if (data.method === '/api/clusters') {
@@ -1798,15 +1530,18 @@ export class Frontend extends EventEmitter {
1798
1530
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter endpoint in /api/clusters' });
1799
1531
  return;
1800
1532
  }
1801
- const response = this.getClusters(data.params.plugin, data.params.endpoint, data.params.serialNumber, data.params.uniqueId);
1802
- if (response) {
1533
+ const start = performance.now();
1534
+ const clusters = this.getApiCluster(data.params.plugin, data.params.endpoint, data.params.serialNumber, data.params.uniqueId);
1535
+ if (this.diagnostic)
1536
+ writeDiagnostic(`Frontend:${data.sender}`, `getApiCluster() took ${(performance.now() - start).toFixed(2)} ms`);
1537
+ if (clusters) {
1803
1538
  sendResponse({
1804
1539
  id: data.id,
1805
1540
  method: data.method,
1806
1541
  src: 'Matterbridge',
1807
1542
  dst: data.src,
1808
1543
  success: true,
1809
- response,
1544
+ response: clusters,
1810
1545
  });
1811
1546
  }
1812
1547
  else {
@@ -1823,8 +1558,11 @@ export class Frontend extends EventEmitter {
1823
1558
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/devices' });
1824
1559
  return;
1825
1560
  }
1826
- const selectDeviceValues = !plugin.platform ? [] : plugin.platform.getSelectDevices().toSorted((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1827
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDeviceValues });
1561
+ const start = performance.now();
1562
+ const selectDevices = !plugin.platform ? [] : plugin.platform.getSelectDevices().toSorted((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1563
+ if (this.diagnostic)
1564
+ writeDiagnostic(`Frontend:${data.sender}`, `getSelectDevices() took ${(performance.now() - start).toFixed(2)} ms`);
1565
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectDevices });
1828
1566
  }
1829
1567
  else if (data.method === '/api/select/entities') {
1830
1568
  if (!isValidString(data.params.plugin, 10)) {
@@ -1836,8 +1574,11 @@ export class Frontend extends EventEmitter {
1836
1574
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Plugin not found in /api/select/entities' });
1837
1575
  return;
1838
1576
  }
1839
- const selectEntityValues = !plugin.platform ? [] : plugin.platform.getSelectEntities().toSorted((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1840
- sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntityValues });
1577
+ const start = performance.now();
1578
+ const selectEntities = !plugin.platform ? [] : plugin.platform.getSelectEntities().toSorted((keyA, keyB) => keyA.name.localeCompare(keyB.name));
1579
+ if (this.diagnostic)
1580
+ writeDiagnostic(`Frontend:${data.sender}`, `getSelectEntities() took ${(performance.now() - start).toFixed(2)} ms`);
1581
+ sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, success: true, response: selectEntities });
1841
1582
  }
1842
1583
  else if (data.method === '/api/action') {
1843
1584
  const localData = data;
@@ -1,11 +1,11 @@
1
1
  import '@matter/nodejs';
2
2
  import EventEmitter from 'node:events';
3
3
  import { type Diagnostic, type StorageContext, type StorageManager, StorageService } from '@matter/general';
4
- import { Endpoint, ServerNode, type SessionsBehavior } from '@matter/node';
4
+ import { Endpoint, ServerNode } from '@matter/node';
5
5
  import { AggregatorEndpoint } from '@matter/node/endpoints/aggregator';
6
- import { type DeviceCertification, type ExposedFabricInformation, MdnsService } from '@matter/protocol';
6
+ import { type DeviceCertification, MdnsService } from '@matter/protocol';
7
7
  import { DeviceTypeId, VendorId } from '@matter/types';
8
- import type { ApiMatter, PluginName, SanitizedExposedFabricInformation, SanitizedSession, SharedMatterbridge, WorkerMessage } from '@matterbridge/types';
8
+ import type { PluginName, SharedMatterbridge, WorkerMessage } from '@matterbridge/types';
9
9
  import type { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
10
10
  import { type Plugin, PluginManager } from './pluginManager.js';
11
11
  interface MatterEvents {
@@ -42,7 +42,6 @@ export declare class MatterNode extends EventEmitter<MatterEvents> {
42
42
  aggregatorDeviceType: DeviceTypeId;
43
43
  aggregatorSerialNumber: string | undefined;
44
44
  aggregatorUniqueId: string | undefined;
45
- advertisingNodes: Map<string, number>;
46
45
  readonly pluginManager: PluginManager;
47
46
  readonly dependantMatterNodes: Map<string, MatterNode>;
48
47
  private readonly server;
@@ -60,7 +59,6 @@ export declare class MatterNode extends EventEmitter<MatterEvents> {
60
59
  stopMatterStorage(): Promise<void>;
61
60
  createServerNodeContext(storeId: string, deviceName: string, deviceType: DeviceTypeId, vendorId: VendorId, vendorName: string, productId: number, productName: string, serialNumber?: string, uniqueId?: string, softwareVersion?: number, softwareVersionString?: string, hardwareVersion?: number, hardwareVersionString?: string, productLabel?: string, productUrl?: string, configurationVersion?: number): Promise<StorageContext>;
62
61
  createServerNode(port?: number, passcode?: number, discriminator?: number): Promise<ServerNode>;
63
- getServerNodeData(serverNode: ServerNode): ApiMatter;
64
62
  startServerNode(timeout?: number): Promise<void>;
65
63
  stopServerNode(timeout?: number): Promise<void>;
66
64
  createAggregatorNode(): Promise<Endpoint<AggregatorEndpoint>>;
@@ -73,11 +71,8 @@ export declare class MatterNode extends EventEmitter<MatterEvents> {
73
71
  removeAllBridgedEndpoints(pluginName: string, delay?: number): Promise<void>;
74
72
  addVirtualEndpoint(pluginName: string, name: string, type: 'light' | 'outlet' | 'switch' | 'mounted_switch', callback: () => Promise<void>): Promise<boolean>;
75
73
  subscribeAttributeChanged(plugin: Plugin, device: MatterbridgeEndpoint): Promise<void>;
76
- sanitizeFabricInformations(fabricInfo: ExposedFabricInformation[]): SanitizedExposedFabricInformation[];
77
- sanitizeSessionInformation(sessions: SessionsBehavior.Session[]): SanitizedSession[];
78
74
  setServerReachability(reachable: boolean): Promise<void>;
79
75
  setAggregatorReachability(aggregatorNode: Endpoint<AggregatorEndpoint>, reachable: boolean): Promise<void>;
80
- getVendorIdName: (vendorId: number | undefined) => string;
81
76
  yieldToNode(timeout?: number): Promise<void>;
82
77
  }
83
78
  export {};