@matterbridge/core 3.10.5-dev-20260809-ffe76d7 → 3.10.5-dev-20260809-30130bb

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.
@@ -10,11 +10,13 @@ declare const MatterbridgeThermostatServer_base: import("@matter/node").ClusterB
10
10
  occupancy: false;
11
11
  presets: true;
12
12
  setback: false;
13
- thermostatSuggestions: false;
13
+ thermostatSuggestions: true;
14
14
  }>, import("@matter/types").ClusterType.Concrete, typeof import("@matter/node/behaviors/thermostat").ThermostatBaseServer.Internal, "thermostat">;
15
15
  export declare class MatterbridgeThermostatServer extends MatterbridgeThermostatServer_base {
16
16
  setpointRaiseLower(request: Thermostat.SetpointRaiseLowerRequest): Promise<void>;
17
17
  setActivePresetRequest(request: Thermostat.SetActivePresetRequest): Promise<void>;
18
18
  setActiveScheduleRequest(request: Thermostat.SetActiveScheduleRequest): Promise<void>;
19
+ addThermostatSuggestion(request: Thermostat.AddThermostatSuggestionRequest): Promise<Thermostat.AddThermostatSuggestionResponse>;
20
+ removeThermostatSuggestion(request: Thermostat.RemoveThermostatSuggestionRequest): Promise<void>;
19
21
  }
20
22
  export {};
@@ -3,7 +3,7 @@ import { ThermostatServer } from '@matter/node/behaviors/thermostat';
3
3
  import { StatusResponse } from '@matter/types';
4
4
  import { Thermostat } from '@matter/types/clusters/thermostat';
5
5
  import { MatterbridgeServer } from './matterbridgeServer.js';
6
- export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermostat.Feature.Cooling, Thermostat.Feature.Heating, Thermostat.Feature.AutoMode, Thermostat.Feature.Presets, Thermostat.Feature.MatterScheduleConfiguration) {
6
+ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermostat.Feature.Cooling, Thermostat.Feature.Heating, Thermostat.Feature.AutoMode, Thermostat.Feature.Presets, Thermostat.Feature.MatterScheduleConfiguration, Thermostat.Feature.ThermostatSuggestions) {
7
7
  async setpointRaiseLower(request) {
8
8
  const device = this.endpoint.stateOf(MatterbridgeServer);
9
9
  device.log.info(`Setting setpoint by ${request.amount} in mode ${request.mode} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
@@ -55,4 +55,63 @@ export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermost
55
55
  this.state.activeScheduleHandle = Uint8Array.from(request.scheduleHandle);
56
56
  device.log.debug(`MatterbridgeThermostatServer: setActiveScheduleRequest completed with activeScheduleHandle: ${scheduleHandle}`);
57
57
  }
58
+ async addThermostatSuggestion(request) {
59
+ const device = this.endpoint.stateOf(MatterbridgeServer);
60
+ const presetHandle = `0x${Buffer.from(request.presetHandle).toString('hex')}`;
61
+ device.log.info(`Adding thermostat suggestion for preset ${presetHandle} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
62
+ await device.commandHandler.executeHandler('Thermostat.addThermostatSuggestion', {
63
+ command: 'addThermostatSuggestion',
64
+ request,
65
+ cluster: ThermostatServer.id,
66
+ attributes: this.state,
67
+ endpoint: this.endpoint,
68
+ context: this.context,
69
+ });
70
+ if (this.state.presets.find((p) => p.presetHandle !== null && Bytes.areEqual(p.presetHandle, request.presetHandle)) === undefined) {
71
+ throw new StatusResponse.NotFoundError('Requested PresetHandle not found');
72
+ }
73
+ if (this.state.thermostatSuggestions.length >= this.state.maxThermostatSuggestions) {
74
+ throw new StatusResponse.ResourceExhaustedError('Maximum number of thermostat suggestions reached');
75
+ }
76
+ const currentTime = Math.floor(Date.now() / 1000);
77
+ const effectiveTime = request.effectiveTime ?? currentTime;
78
+ if (effectiveTime > currentTime + 24 * 60 * 60) {
79
+ throw new StatusResponse.InvalidCommandError('EffectiveTime cannot be more than 24 hours in the future');
80
+ }
81
+ const usedUniqueIds = new Set(this.state.thermostatSuggestions.map((s) => s.uniqueId));
82
+ let uniqueId = 0;
83
+ while (usedUniqueIds.has(uniqueId))
84
+ uniqueId++;
85
+ const suggestion = {
86
+ uniqueId,
87
+ presetHandle: Uint8Array.from(request.presetHandle),
88
+ effectiveTime,
89
+ expirationTime: effectiveTime + request.expirationInMinutes * 60,
90
+ };
91
+ this.state.thermostatSuggestions = [...this.state.thermostatSuggestions, suggestion];
92
+ device.log.debug(`MatterbridgeThermostatServer: addThermostatSuggestion completed with uniqueId: ${uniqueId}`);
93
+ return { uniqueId };
94
+ }
95
+ async removeThermostatSuggestion(request) {
96
+ const device = this.endpoint.stateOf(MatterbridgeServer);
97
+ device.log.info(`Removing thermostat suggestion ${request.uniqueId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
98
+ await device.commandHandler.executeHandler('Thermostat.removeThermostatSuggestion', {
99
+ command: 'removeThermostatSuggestion',
100
+ request,
101
+ cluster: ThermostatServer.id,
102
+ attributes: this.state,
103
+ endpoint: this.endpoint,
104
+ context: this.context,
105
+ });
106
+ const suggestion = this.state.thermostatSuggestions.find((s) => s.uniqueId === request.uniqueId);
107
+ if (suggestion === undefined) {
108
+ throw new StatusResponse.NotFoundError('Requested UniqueID not found');
109
+ }
110
+ this.state.thermostatSuggestions = this.state.thermostatSuggestions.filter((s) => s.uniqueId !== request.uniqueId);
111
+ if (this.state.currentThermostatSuggestion?.uniqueId === request.uniqueId) {
112
+ this.state.currentThermostatSuggestion = null;
113
+ this.state.thermostatSuggestionNotFollowingReason = null;
114
+ }
115
+ device.log.debug(`MatterbridgeThermostatServer: removeThermostatSuggestion completed for uniqueId: ${request.uniqueId}`);
116
+ }
58
117
  }
@@ -174,8 +174,9 @@ export declare class MatterbridgeEndpoint extends Endpoint {
174
174
  setWindowCoveringTargetAndCurrentPosition(liftPosition: number, tiltPosition?: number): Promise<void>;
175
175
  createDefaultThermostatClusterServer(localTemperature?: number, occupiedHeatingSetpoint?: number, occupiedCoolingSetpoint?: number, minSetpointDeadBand?: number, minHeatSetpointLimit?: number, maxHeatSetpointLimit?: number, minCoolSetpointLimit?: number, maxCoolSetpointLimit?: number, unoccupiedHeatingSetpoint?: number, unoccupiedCoolingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null): this;
176
176
  createDefaultHeatingThermostatClusterServer(localTemperature?: number, occupiedHeatingSetpoint?: number, minHeatSetpointLimit?: number, maxHeatSetpointLimit?: number, unoccupiedHeatingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null): this;
177
- createDefaultPresetsThermostatClusterServer(localTemperature?: number, occupiedHeatingSetpoint?: number, occupiedCoolingSetpoint?: number, minSetpointDeadBand?: number, minHeatSetpointLimit?: number, maxHeatSetpointLimit?: number, minCoolSetpointLimit?: number, maxCoolSetpointLimit?: number, unoccupiedHeatingSetpoint?: number, unoccupiedCoolingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null, activePresetHandle?: Uint8Array | null, presets?: Thermostat.Preset[], presetTypes?: Thermostat.PresetType[]): this;
177
+ createDefaultPresetsThermostatClusterServer(localTemperature?: number, occupiedHeatingSetpoint?: number, occupiedCoolingSetpoint?: number, minSetpointDeadBand?: number, minHeatSetpointLimit?: number, maxHeatSetpointLimit?: number, minCoolSetpointLimit?: number, maxCoolSetpointLimit?: number, unoccupiedHeatingSetpoint?: number, unoccupiedCoolingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null, activePresetHandle?: Uint8Array | null, presets?: Thermostat.Preset[], presetTypes?: Thermostat.PresetType[] | null): this;
178
178
  createDefaultSchedulesThermostatClusterServer(localTemperature?: number, occupiedHeatingSetpoint?: number, occupiedCoolingSetpoint?: number, minSetpointDeadBand?: number, minHeatSetpointLimit?: number, maxHeatSetpointLimit?: number, minCoolSetpointLimit?: number, maxCoolSetpointLimit?: number, unoccupiedHeatingSetpoint?: number, unoccupiedCoolingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null, activeScheduleHandle?: Uint8Array | null, schedules?: Thermostat.Schedule[], scheduleTypes?: Thermostat.ScheduleType[], numberOfScheduleTransitions?: number, numberOfScheduleTransitionPerDay?: number | null): this;
179
+ createDefaultThermostatSuggestionsClusterServer(localTemperature?: number, occupiedHeatingSetpoint?: number, occupiedCoolingSetpoint?: number, minSetpointDeadBand?: number, minHeatSetpointLimit?: number, maxHeatSetpointLimit?: number, minCoolSetpointLimit?: number, maxCoolSetpointLimit?: number, unoccupiedHeatingSetpoint?: number, unoccupiedCoolingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null, activePresetHandle?: Uint8Array | null, presets?: Thermostat.Preset[], presetTypes?: Thermostat.PresetType[] | null, thermostatSuggestions?: Thermostat.ThermostatSuggestion[], currentThermostatSuggestion?: Thermostat.ThermostatSuggestion | null, thermostatSuggestionNotFollowingReason?: Thermostat.ThermostatSuggestionNotFollowingReason | null): this;
179
180
  createDefaultCoolingThermostatClusterServer(localTemperature?: number, occupiedCoolingSetpoint?: number, minCoolSetpointLimit?: number, maxCoolSetpointLimit?: number, unoccupiedCoolingSetpoint?: number, occupied?: boolean, outdoorTemperature?: number | null): this;
180
181
  createDefaultThermostatUserInterfaceConfigurationClusterServer(temperatureDisplayMode?: ThermostatUserInterfaceConfiguration.TemperatureDisplayMode, keypadLockout?: ThermostatUserInterfaceConfiguration.KeypadLockout, scheduleProgrammingVisibility?: ThermostatUserInterfaceConfiguration.ScheduleProgrammingVisibility): this;
181
182
  createDefaultFanControlClusterServer(fanMode?: FanControl.FanMode, fanModeSequence?: FanControl.FanModeSequence, percentSetting?: number, percentCurrent?: number): this;
@@ -1059,6 +1059,66 @@ export class MatterbridgeEndpoint extends Endpoint {
1059
1059
  });
1060
1060
  return this;
1061
1061
  }
1062
+ createDefaultThermostatSuggestionsClusterServer(localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 0, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedHeatingSetpoint, unoccupiedCoolingSetpoint, occupied, outdoorTemperature, activePresetHandle = null, presets = [], presetTypes = [
1063
+ { presetScenario: Thermostat.PresetScenario.Occupied, numberOfPresets: 2, presetTypeFeatures: { automatic: false, supportsNames: true } },
1064
+ { presetScenario: Thermostat.PresetScenario.Unoccupied, numberOfPresets: 2, presetTypeFeatures: { automatic: false, supportsNames: true } },
1065
+ ], thermostatSuggestions = [], currentThermostatSuggestion = null, thermostatSuggestionNotFollowingReason = null) {
1066
+ this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : []), Thermostat.Feature.Presets, Thermostat.Feature.ThermostatSuggestions), {
1067
+ localTemperature: localTemperature * 100,
1068
+ externalMeasuredIndoorTemperature: localTemperature * 100,
1069
+ ...(outdoorTemperature !== undefined ? { outdoorTemperature: outdoorTemperature !== null ? outdoorTemperature * 100 : outdoorTemperature } : {}),
1070
+ systemMode: Thermostat.SystemMode.Auto,
1071
+ controlSequenceOfOperation: Thermostat.ControlSequenceOfOperation.CoolingAndHeating,
1072
+ occupiedHeatingSetpoint: occupiedHeatingSetpoint * 100,
1073
+ minHeatSetpointLimit: minHeatSetpointLimit * 100,
1074
+ maxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1075
+ absMinHeatSetpointLimit: minHeatSetpointLimit * 100,
1076
+ absMaxHeatSetpointLimit: maxHeatSetpointLimit * 100,
1077
+ occupiedCoolingSetpoint: occupiedCoolingSetpoint * 100,
1078
+ minCoolSetpointLimit: minCoolSetpointLimit * 100,
1079
+ maxCoolSetpointLimit: maxCoolSetpointLimit * 100,
1080
+ absMinCoolSetpointLimit: minCoolSetpointLimit * 100,
1081
+ absMaxCoolSetpointLimit: maxCoolSetpointLimit * 100,
1082
+ minSetpointDeadBand: minSetpointDeadBand * 10,
1083
+ thermostatRunningMode: Thermostat.ThermostatRunningMode.Off,
1084
+ ...(occupied !== undefined ? { unoccupiedHeatingSetpoint: unoccupiedHeatingSetpoint !== undefined ? unoccupiedHeatingSetpoint * 100 : 1900 } : {}),
1085
+ ...(occupied !== undefined ? { unoccupiedCoolingSetpoint: unoccupiedCoolingSetpoint !== undefined ? unoccupiedCoolingSetpoint * 100 : 2700 } : {}),
1086
+ ...(occupied !== undefined ? { occupancy: { occupied } } : {}),
1087
+ ...(occupied !== undefined ? { externallyMeasuredOccupancy: true } : {}),
1088
+ numberOfPresets: Math.max(Array.isArray(presets) ? presets.length : 0, Array.isArray(presetTypes) ? Math.max(0, ...presetTypes.map((pt) => pt.numberOfPresets ?? 0)) : 0, 1),
1089
+ activePresetHandle: activePresetHandle ? Uint8Array.from(activePresetHandle) : null,
1090
+ presets: (presets ?? []).map((p) => ({
1091
+ presetHandle: p.presetHandle ? Uint8Array.from(p.presetHandle) : null,
1092
+ presetScenario: p.presetScenario,
1093
+ name: p.name,
1094
+ coolingSetpoint: p.coolingSetpoint,
1095
+ heatingSetpoint: p.heatingSetpoint,
1096
+ builtIn: p.builtIn ?? true,
1097
+ })),
1098
+ presetTypes: (presetTypes ?? []).map((pt) => ({
1099
+ ...pt,
1100
+ numberOfPresets: pt.numberOfPresets ?? 0,
1101
+ presetTypeFeatures: pt.presetTypeFeatures ?? { automatic: false, supportsNames: true },
1102
+ })),
1103
+ maxThermostatSuggestions: Math.max(Array.isArray(thermostatSuggestions) ? thermostatSuggestions.length : 0, 5),
1104
+ thermostatSuggestions: (thermostatSuggestions ?? []).map((s) => ({
1105
+ uniqueId: s.uniqueId,
1106
+ presetHandle: Uint8Array.from(s.presetHandle),
1107
+ effectiveTime: s.effectiveTime,
1108
+ expirationTime: s.expirationTime,
1109
+ })),
1110
+ currentThermostatSuggestion: currentThermostatSuggestion
1111
+ ? {
1112
+ uniqueId: currentThermostatSuggestion.uniqueId,
1113
+ presetHandle: Uint8Array.from(currentThermostatSuggestion.presetHandle),
1114
+ effectiveTime: currentThermostatSuggestion.effectiveTime,
1115
+ expirationTime: currentThermostatSuggestion.expirationTime,
1116
+ }
1117
+ : null,
1118
+ thermostatSuggestionNotFollowingReason: thermostatSuggestionNotFollowingReason ?? null,
1119
+ });
1120
+ return this;
1121
+ }
1062
1122
  createDefaultCoolingThermostatClusterServer(localTemperature = 23, occupiedCoolingSetpoint = 25, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, unoccupiedCoolingSetpoint, occupied, outdoorTemperature) {
1063
1123
  this.behaviors.require(MatterbridgeThermostatServer.with(Thermostat.Feature.Cooling, ...(occupied !== undefined ? [Thermostat.Feature.Occupancy] : [])), {
1064
1124
  localTemperature: localTemperature * 100,
@@ -458,6 +458,8 @@ export type CommandHandlerDataMap = {
458
458
  'setpointRaiseLower': CommandHandlerData<'Thermostat.setpointRaiseLower'>;
459
459
  'setActivePresetRequest': CommandHandlerData<'Thermostat.setActivePresetRequest'>;
460
460
  'setActiveScheduleRequest': CommandHandlerData<'Thermostat.setActiveScheduleRequest'>;
461
+ 'addThermostatSuggestion': CommandHandlerData<'Thermostat.addThermostatSuggestion'>;
462
+ 'removeThermostatSuggestion': CommandHandlerData<'Thermostat.removeThermostatSuggestion'>;
461
463
  'Thermostat.setpointRaiseLower': {
462
464
  command: 'setpointRaiseLower';
463
465
  request: Thermostat.SetpointRaiseLowerRequest;
@@ -479,6 +481,20 @@ export type CommandHandlerDataMap = {
479
481
  attributes: ClusterAttributeValues<(typeof Thermostat)['attributes']>;
480
482
  endpoint: MatterbridgeEndpoint;
481
483
  };
484
+ 'Thermostat.addThermostatSuggestion': {
485
+ command: 'addThermostatSuggestion';
486
+ request: Thermostat.AddThermostatSuggestionRequest;
487
+ cluster: 'thermostat';
488
+ attributes: ClusterAttributeValues<(typeof Thermostat)['attributes']>;
489
+ endpoint: MatterbridgeEndpoint;
490
+ };
491
+ 'Thermostat.removeThermostatSuggestion': {
492
+ command: 'removeThermostatSuggestion';
493
+ request: Thermostat.RemoveThermostatSuggestionRequest;
494
+ cluster: 'thermostat';
495
+ attributes: ClusterAttributeValues<(typeof Thermostat)['attributes']>;
496
+ endpoint: MatterbridgeEndpoint;
497
+ };
482
498
  'step': CommandHandlerData<'FanControl.step'>;
483
499
  'FanControl.step': {
484
500
  command: 'step';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.10.5-dev-20260809-ffe76d7",
3
+ "version": "3.10.5-dev-20260809-30130bb",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -132,10 +132,10 @@
132
132
  },
133
133
  "dependencies": {
134
134
  "@matter/main": "0.17.9",
135
- "@matterbridge/dgram": "3.10.5-dev-20260809-ffe76d7",
136
- "@matterbridge/thread": "3.10.5-dev-20260809-ffe76d7",
137
- "@matterbridge/types": "3.10.5-dev-20260809-ffe76d7",
138
- "@matterbridge/utils": "3.10.5-dev-20260809-ffe76d7",
135
+ "@matterbridge/dgram": "3.10.5-dev-20260809-30130bb",
136
+ "@matterbridge/thread": "3.10.5-dev-20260809-30130bb",
137
+ "@matterbridge/types": "3.10.5-dev-20260809-30130bb",
138
+ "@matterbridge/utils": "3.10.5-dev-20260809-30130bb",
139
139
  "escape-html": "1.0.3",
140
140
  "express": "5.2.1",
141
141
  "express-rate-limit": "8.6.2",