@matterbridge/core 3.6.2-dev-20260316-0b03ae0 → 3.6.2-dev-20260317-e291a17

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.
@@ -299,6 +299,6 @@ export interface ClosureOptions {
299
299
  }
300
300
  export declare class Closure extends MatterbridgeEndpoint {
301
301
  constructor(name: string, serial: string, options?: ClosureOptions);
302
- getMainState(): ClosureControl.MainState;
302
+ getMainState(): ClosureControl.MainState | undefined;
303
303
  }
304
304
  export {};
@@ -47,6 +47,6 @@ export class Closure extends MatterbridgeEndpoint {
47
47
  });
48
48
  }
49
49
  getMainState() {
50
- return this.getAttribute(ClosureControl.Cluster.id, 'mainState');
50
+ return this.getAttribute(ClosureControlServer, 'mainState');
51
51
  }
52
52
  }
@@ -23,6 +23,6 @@ export interface SoilSensorOptions {
23
23
  export declare class SoilSensor extends MatterbridgeEndpoint {
24
24
  constructor(name: string, serial: string, options?: SoilSensorOptions);
25
25
  setSoilMoistureMeasuredValue(value: number | null): Promise<void>;
26
- getSoilMoistureMeasuredValue(): number | null;
26
+ getSoilMoistureMeasuredValue(): number | null | undefined;
27
27
  }
28
28
  export {};
@@ -47,6 +47,6 @@ export class SoilSensor extends MatterbridgeEndpoint {
47
47
  await this.setAttribute(SoilMeasurement.Cluster.id, 'soilMoistureMeasuredValue', value);
48
48
  }
49
49
  getSoilMoistureMeasuredValue() {
50
- return this.getAttribute(SoilMeasurement.Cluster.id, 'soilMoistureMeasuredValue');
50
+ return this.getAttribute(SoilMeasurement.Cluster, 'soilMoistureMeasuredValue');
51
51
  }
52
52
  }
@@ -4,5 +4,5 @@ export declare class Speaker extends MatterbridgeEndpoint {
4
4
  setMuted(muted: boolean): Promise<void>;
5
5
  isMuted(): boolean;
6
6
  setVolume(level: number): Promise<void>;
7
- getVolume(): number;
7
+ getVolume(): number | null | undefined;
8
8
  }
@@ -28,9 +28,9 @@ export class Speaker extends MatterbridgeEndpoint {
28
28
  level = 1;
29
29
  if (level > 254)
30
30
  level = 254;
31
- await this.setAttribute(LevelControl.Cluster.id, 'currentLevel', level);
31
+ await this.setAttribute(LevelControl.Cluster, 'currentLevel', level);
32
32
  }
33
33
  getVolume() {
34
- return this.getAttribute(LevelControl.Cluster.id, 'currentLevel');
34
+ return this.getAttribute(LevelControl.Cluster, 'currentLevel');
35
35
  }
36
36
  }
@@ -19,10 +19,8 @@ export function createNumberTemperatureControlClusterServer(endpoint, temperatur
19
19
  }
20
20
  export class MatterbridgeLevelTemperatureControlServer extends TemperatureControlServer.with(TemperatureControl.Feature.TemperatureLevel) {
21
21
  initialize() {
22
- if (this.state.supportedTemperatureLevels.length >= 2) {
23
- const device = this.endpoint.stateOf(MatterbridgeServer);
24
- device.log.info(`MatterbridgeLevelTemperatureControlServer initialized with selectedTemperatureLevel ${this.state.selectedTemperatureLevel} and supportedTemperatureLevels: ${this.state.supportedTemperatureLevels.join(', ')}`);
25
- }
22
+ const device = this.endpoint.stateOf(MatterbridgeServer);
23
+ device.log.info(`MatterbridgeLevelTemperatureControlServer initialized with selectedTemperatureLevel ${this.state.selectedTemperatureLevel} and supportedTemperatureLevels: ${this.state.supportedTemperatureLevels.join(', ')}`);
26
24
  }
27
25
  setTemperature(request) {
28
26
  const device = this.endpoint.stateOf(MatterbridgeServer);
package/dist/frontend.js CHANGED
@@ -1123,12 +1123,14 @@ export class Frontend extends EventEmitter {
1123
1123
  }
1124
1124
  return devices;
1125
1125
  }
1126
- getClusters(pluginName, endpointNumber) {
1126
+ getClusters(pluginName, endpointNumber, serialNumber, uniqueId) {
1127
1127
  if (this.matterbridge.hasCleanupStarted)
1128
1128
  return;
1129
- const endpoint = this.matterbridge.devices.array().find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber);
1129
+ const endpoint = this.matterbridge.devices
1130
+ .array()
1131
+ .find((d) => d.plugin === pluginName && d.maybeNumber === endpointNumber && (!serialNumber || d.serialNumber === serialNumber) && (!uniqueId || d.uniqueId === uniqueId));
1130
1132
  if (!endpoint || !endpoint.plugin || !endpoint.maybeNumber || !endpoint.maybeId || !endpoint.deviceName || !endpoint.serialNumber) {
1131
- this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber}`);
1133
+ this.log.error(`getClusters: no device found for plugin ${pluginName} and endpoint number ${endpointNumber} (serial: ${serialNumber ?? 'N/A'}, uniqueId: ${uniqueId ?? 'N/A'})`);
1132
1134
  return;
1133
1135
  }
1134
1136
  const deviceTypes = [];
@@ -1592,7 +1594,7 @@ export class Frontend extends EventEmitter {
1592
1594
  sendResponse({ id: data.id, method: data.method, src: 'Matterbridge', dst: data.src, error: 'Wrong parameter endpoint in /api/clusters' });
1593
1595
  return;
1594
1596
  }
1595
- const response = this.getClusters(data.params.plugin, data.params.endpoint);
1597
+ const response = this.getClusters(data.params.plugin, data.params.endpoint, data.params.serialNumber, data.params.uniqueId);
1596
1598
  if (response) {
1597
1599
  sendResponse({
1598
1600
  id: data.id,
@@ -25,6 +25,13 @@ import { Semtag } from '@matter/types/globals';
25
25
  import { AnsiLogger, LogLevel } from 'node-ansi-logger';
26
26
  import { DeviceTypeDefinition } from './matterbridgeDeviceTypes.js';
27
27
  import { CommandHandlerFunction, MatterbridgeEndpointCommands, MatterbridgeEndpointOptions, SerializedMatterbridgeEndpoint } from './matterbridgeEndpointTypes.js';
28
+ type BehaviorCommandName<T extends Behavior.Type> = {
29
+ [K in keyof CommandsOfBehavior<T>]: K;
30
+ }[keyof CommandsOfBehavior<T>] & string;
31
+ type CommandsOfBehavior<T extends Behavior.Type> = {
32
+ [K in keyof InstanceType<T> as InstanceType<T>[K] extends (...args: infer _P) => infer _R ? K : never]: InstanceType<T>[K] extends (...args: infer P) => infer R ? (input: P[0], context?: ActionContext) => Promise<Awaited<R>> : never;
33
+ };
34
+ type BehaviorCommandParams<T extends Behavior.Type, C extends BehaviorCommandName<T>> = CommandsOfBehavior<T>[C] extends (input: infer P, context?: ActionContext) => Promise<unknown> ? P : never;
28
35
  export declare function isMatterbridgeEndpoint(value: unknown): value is MatterbridgeEndpoint;
29
36
  export declare function assertMatterbridgeEndpoint(value: unknown, context?: string): asserts value is MatterbridgeEndpoint;
30
37
  export declare class MatterbridgeEndpoint extends Endpoint {
@@ -58,10 +65,18 @@ export declare class MatterbridgeEndpoint extends Endpoint {
58
65
  hasClusterServer(cluster: Behavior.Type | ClusterType | ClusterId | string): boolean;
59
66
  hasAttributeServer(cluster: Behavior.Type | ClusterType | ClusterId | string, attribute: string): boolean;
60
67
  getClusterServerOptions(cluster: Behavior.Type | ClusterType | ClusterId | string): Record<string, boolean | number | bigint | string | object | null> | undefined;
61
- getAttribute(cluster: Behavior.Type | ClusterType | ClusterId | string, attribute: string, log?: AnsiLogger): any;
62
- setAttribute(clusterId: Behavior.Type | ClusterType | ClusterId | string, attribute: string, value: boolean | number | bigint | string | object | null, log?: AnsiLogger): Promise<boolean>;
63
- updateAttribute(cluster: Behavior.Type | ClusterType | ClusterId | string, attribute: string, value: boolean | number | bigint | string | object | null, log?: AnsiLogger): Promise<boolean>;
64
- subscribeAttribute(cluster: Behavior.Type | ClusterType | ClusterId | string, attribute: string, listener: (newValue: any, oldValue: any, context: ActionContext) => void, log?: AnsiLogger): Promise<boolean>;
68
+ getAttribute<T extends Behavior.Type, A extends keyof Behavior.StateOf<T>>(cluster: T, attribute: A, log?: AnsiLogger): Behavior.StateOf<T>[A] | undefined;
69
+ getAttribute<T extends ClusterType, A extends keyof ClusterType.AttributeValues<T>>(cluster: T, attribute: A, log?: AnsiLogger): ClusterType.AttributeValues<T>[A] | undefined;
70
+ getAttribute(cluster: ClusterId | string, attribute: string, log?: AnsiLogger): any;
71
+ setAttribute<T extends Behavior.Type, A extends keyof Behavior.StateOf<T>>(clusterId: T, attribute: A, value: Behavior.StateOf<T>[A], log?: AnsiLogger): Promise<boolean>;
72
+ setAttribute<T extends ClusterType, A extends keyof ClusterType.AttributeValues<T>>(clusterId: T, attribute: A, value: ClusterType.AttributeValues<T>[A], log?: AnsiLogger): Promise<boolean>;
73
+ setAttribute(clusterId: ClusterId | string, attribute: string, value: boolean | number | bigint | string | object | null, log?: AnsiLogger): Promise<boolean>;
74
+ updateAttribute<T extends Behavior.Type, A extends keyof Behavior.StateOf<T>>(cluster: T, attribute: A, value: Behavior.StateOf<T>[A], log?: AnsiLogger): Promise<boolean>;
75
+ updateAttribute<T extends ClusterType, A extends keyof ClusterType.AttributeValues<T>>(cluster: T, attribute: A, value: ClusterType.AttributeValues<T>[A], log?: AnsiLogger): Promise<boolean>;
76
+ updateAttribute(cluster: ClusterId | string, attribute: string, value: boolean | number | bigint | string | object | null, log?: AnsiLogger): Promise<boolean>;
77
+ subscribeAttribute<T extends Behavior.Type, A extends keyof Behavior.StateOf<T>>(cluster: T, attribute: A, listener: (newValue: Behavior.StateOf<T>[A], oldValue: Behavior.StateOf<T>[A], context: ActionContext) => void, log?: AnsiLogger): Promise<boolean>;
78
+ subscribeAttribute<T extends ClusterType, A extends keyof ClusterType.AttributeValues<T>>(cluster: T, attribute: A, listener: (newValue: ClusterType.AttributeValues<T>[A], oldValue: ClusterType.AttributeValues<T>[A], context: ActionContext) => void, log?: AnsiLogger): Promise<boolean>;
79
+ subscribeAttribute(cluster: ClusterId | string, attribute: string, listener: (newValue: any, oldValue: any, context: ActionContext) => void, log?: AnsiLogger): Promise<boolean>;
65
80
  setCluster<T extends Behavior.Type>(cluster: T, value: Behavior.StateOf<T>, log?: AnsiLogger): Promise<boolean>;
66
81
  setCluster<T extends ClusterType>(cluster: T, value: ClusterType.AttributeValues<T>, log?: AnsiLogger): Promise<boolean>;
67
82
  setCluster(cluster: ClusterId | string, value: Record<string, boolean | number | bigint | string | object | undefined | null>, log?: AnsiLogger): Promise<boolean>;
@@ -74,11 +89,7 @@ export declare class MatterbridgeEndpoint extends Endpoint {
74
89
  addUserLabel(label: string, value: string): Promise<this>;
75
90
  addCommandHandler(command: keyof MatterbridgeEndpointCommands, handler: CommandHandlerFunction): this;
76
91
  executeCommandHandler(command: keyof MatterbridgeEndpointCommands, request?: Record<string, boolean | number | bigint | string | object | null>, cluster?: string, attributes?: Record<string, boolean | number | bigint | string | object | null>, endpoint?: MatterbridgeEndpoint): Promise<void>;
77
- invokeBehaviorCommand<T extends Behavior.Type, C extends keyof {
78
- [K in keyof InstanceType<T> as InstanceType<T>[K] extends (...args: unknown[]) => unknown ? K : never]: InstanceType<T>[K];
79
- }>(cluster: T, command: C, params?: {
80
- [K in keyof InstanceType<T> as InstanceType<T>[K] extends (...args: unknown[]) => unknown ? K : never]: InstanceType<T>[K];
81
- }[C] extends (...args: infer P) => unknown ? P[0] : never): Promise<void>;
92
+ invokeBehaviorCommand<T extends Behavior.Type, C extends BehaviorCommandName<T>>(cluster: T, command: C, params?: BehaviorCommandParams<T, C>): Promise<void>;
82
93
  invokeBehaviorCommand<T extends ClusterType, C extends keyof ClusterType.CommandsOf<T>>(cluster: T, command: C, params?: ClusterType.CommandsOf<T>[C] extends {
83
94
  requestSchema: infer S extends import('@matter/types/tlv').TlvSchema<unknown>;
84
95
  } ? import('@matter/types/tlv').TypeFromSchema<S> : never): Promise<void>;
@@ -191,3 +202,4 @@ export declare class MatterbridgeEndpoint extends Endpoint {
191
202
  createDefaultRadonConcentrationMeasurementClusterServer(measuredValue?: number | null, measurementUnit?: ConcentrationMeasurement.MeasurementUnit, measurementMedium?: ConcentrationMeasurement.MeasurementMedium): this;
192
203
  createDefaultNitrogenDioxideConcentrationMeasurementClusterServer(measuredValue?: number | null, measurementUnit?: ConcentrationMeasurement.MeasurementUnit, measurementMedium?: ConcentrationMeasurement.MeasurementMedium): this;
193
204
  }
205
+ export {};
@@ -825,7 +825,7 @@ export class MatterbridgeEndpoint extends Endpoint {
825
825
  this.log.debug(`Set WindowCovering currentPositionTiltPercent100ths: ${tiltPosition} and targetPositionTiltPercent100ths: ${tiltPosition}.`);
826
826
  }
827
827
  }
828
- createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedHeatingSetpoint = undefined, unoccupiedCoolingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined) {
828
+ createDefaultThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 0, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedHeatingSetpoint = undefined, unoccupiedCoolingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined) {
829
829
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
830
830
  localTemperature: localTemperature * 100,
831
831
  externalMeasuredIndoorTemperature: localTemperature * 100,
@@ -887,7 +887,7 @@ export class MatterbridgeEndpoint extends Endpoint {
887
887
  });
888
888
  return this;
889
889
  }
890
- createDefaultPresetsThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 2, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedHeatingSetpoint = undefined, unoccupiedCoolingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined, activePresetHandle = undefined, presetsList = undefined, presetTypes = undefined) {
890
+ createDefaultPresetsThermostatClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 0, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedHeatingSetpoint = undefined, unoccupiedCoolingSetpoint = undefined, occupied = undefined, outdoorTemperature = undefined, activePresetHandle = undefined, presetsList = undefined, presetTypes = undefined) {
891
891
  this.behaviors.require(MatterbridgePresetThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : []), Thermostat.Feature.Presets), {
892
892
  localTemperature: localTemperature * 100,
893
893
  externalMeasuredIndoorTemperature: localTemperature * 100,
@@ -23,6 +23,7 @@ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
23
23
  import { MatterbridgeEndpointCommands } from './matterbridgeEndpointTypes.js';
24
24
  export declare function capitalizeFirstLetter(name: string): string;
25
25
  export declare function lowercaseFirstLetter(name: string): string;
26
+ export declare function getSnapshot<T>(value: T): T;
26
27
  export declare function checkNotLatinCharacters(deviceName: string): boolean;
27
28
  export declare function generateUniqueId(deviceName: string): string;
28
29
  export declare function createUniqueId(param1: string, param2: string, param3: string, param4: string): string;
@@ -79,7 +79,6 @@ import { UserLabel } from '@matter/types/clusters/user-label';
79
79
  import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
80
80
  import { WindowCovering } from '@matter/types/clusters/window-covering';
81
81
  import { MeasurementType } from '@matter/types/globals';
82
- import { deepCopy } from '@matterbridge/utils/deep-copy';
83
82
  import { deepEqual } from '@matterbridge/utils/deep-equal';
84
83
  import { isValidArray } from '@matterbridge/utils/validate';
85
84
  import { BLUE, CYAN, db, debugStringify, er, hk, or, YELLOW, zb } from 'node-ansi-logger';
@@ -94,6 +93,36 @@ export function lowercaseFirstLetter(name) {
94
93
  return name;
95
94
  return name.charAt(0).toLowerCase() + name.slice(1);
96
95
  }
96
+ export function getSnapshot(value) {
97
+ if (typeof value !== 'object' || value === null)
98
+ return value;
99
+ if (Buffer.isBuffer(value))
100
+ return Buffer.from(value);
101
+ if (value instanceof Uint8Array)
102
+ return Uint8Array.from(value);
103
+ if (Array.isArray(value)) {
104
+ return value.map((entry) => getSnapshot(entry));
105
+ }
106
+ if (value instanceof Date)
107
+ return new Date(value.getTime());
108
+ if (value instanceof RegExp)
109
+ return new RegExp(value.source, value.flags);
110
+ if (value instanceof Map) {
111
+ const mapCopy = new Map();
112
+ for (const [key, entry] of value.entries()) {
113
+ mapCopy.set(getSnapshot(key), getSnapshot(entry));
114
+ }
115
+ return mapCopy;
116
+ }
117
+ if (value instanceof Set) {
118
+ return new Set(Array.from(value, (entry) => getSnapshot(entry)));
119
+ }
120
+ const plainObject = {};
121
+ for (const [key, entry] of Object.entries(value)) {
122
+ plainObject[key] = getSnapshot(entry);
123
+ }
124
+ return plainObject;
125
+ }
97
126
  export function checkNotLatinCharacters(deviceName) {
98
127
  const nonLatinRegexList = [
99
128
  /[\u0400-\u04FF\u0500-\u052F]/,
@@ -463,7 +492,7 @@ export function getAttribute(endpoint, cluster, attribute, log) {
463
492
  }
464
493
  let value = state[clusterName][attribute];
465
494
  if (typeof value === 'object')
466
- value = deepCopy(value);
495
+ value = getSnapshot(value);
467
496
  log?.info(`${db}Get endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} value ${YELLOW}${value !== null && typeof value === 'object' ? debugStringify(value) : value}${db}`);
468
497
  return value;
469
498
  }
@@ -485,7 +514,7 @@ export async function setAttribute(endpoint, cluster, attribute, value, log) {
485
514
  }
486
515
  let oldValue = state[clusterName][attribute];
487
516
  if (typeof oldValue === 'object')
488
- oldValue = deepCopy(oldValue);
517
+ oldValue = getSnapshot(oldValue);
489
518
  await endpoint.setStateOf(endpoint.behaviors.supported[clusterName], { [attribute]: value });
490
519
  log?.info(`${db}Set endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} attribute ${hk}${capitalizeFirstLetter(clusterName)}${db}.${hk}${attribute}${db} ` +
491
520
  `from ${YELLOW}${oldValue !== null && typeof oldValue === 'object' ? debugStringify(oldValue) : oldValue}${db} ` +
@@ -512,7 +541,7 @@ export async function updateAttribute(endpoint, cluster, attribute, value, log)
512
541
  if (typeof oldValue === 'object') {
513
542
  if (deepEqual(oldValue, value))
514
543
  return false;
515
- oldValue = deepCopy(oldValue);
544
+ oldValue = getSnapshot(oldValue);
516
545
  }
517
546
  else if (oldValue === value)
518
547
  return false;
@@ -567,7 +596,7 @@ export function getCluster(endpoint, cluster, log) {
567
596
  return undefined;
568
597
  }
569
598
  const state = endpoint.state;
570
- const value = deepCopy(state[clusterName]);
599
+ const value = getSnapshot(state[clusterName]);
571
600
  log?.info(`${db}Get endpoint ${or}${endpoint.id}${db}:${or}${endpoint.number}${db} cluster ${hk}${capitalizeFirstLetter(clusterName)}${db} state ${debugStringify(value)}}`);
572
601
  return value;
573
602
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.6.2-dev-20260316-0b03ae0",
3
+ "version": "3.6.2-dev-20260317-e291a17",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -122,10 +122,10 @@
122
122
  ],
123
123
  "dependencies": {
124
124
  "@matter/main": "0.16.10",
125
- "@matterbridge/dgram": "3.6.2-dev-20260316-0b03ae0",
126
- "@matterbridge/thread": "3.6.2-dev-20260316-0b03ae0",
127
- "@matterbridge/types": "3.6.2-dev-20260316-0b03ae0",
128
- "@matterbridge/utils": "3.6.2-dev-20260316-0b03ae0",
125
+ "@matterbridge/dgram": "3.6.2-dev-20260317-e291a17",
126
+ "@matterbridge/thread": "3.6.2-dev-20260317-e291a17",
127
+ "@matterbridge/types": "3.6.2-dev-20260317-e291a17",
128
+ "@matterbridge/utils": "3.6.2-dev-20260317-e291a17",
129
129
  "express": "5.2.1",
130
130
  "multer": "2.1.1",
131
131
  "node-ansi-logger": "3.2.0",