matterbridge 3.0.3-dev-20250518-e0f532c → 3.0.3-dev-20250519-6aad062

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
@@ -10,10 +10,22 @@ If you like this project and find it useful, please consider giving it a star on
10
10
 
11
11
  ## [3.0.3] - 2025-05-18
12
12
 
13
+ ### New plugins
14
+
15
+ [Dyson robot](https://github.com/thoukydides/matterbridge-dyson-robot)
16
+
17
+ A Matterbridge plugin that connects Dyson robot vacuums and air treatment devices.
18
+ to the Matter smart home ecosystem via their local MQTT APIs.
19
+
20
+ [Aeg robot](https://github.com/thoukydides/matterbridge-aeg-robot)
21
+
22
+ AEG RX 9 / Electrolux Pure i9 robot vacuum plugin for Matterbridge.
23
+
13
24
  ### Added
14
25
 
15
26
  - [virtual] Added virtual devices configuration mode in the Matterbridge Settings: 'Disabled', 'Light', 'Outlet', 'Switch', 'Mounted_switch'. Switch is not supported by Alexa. Mounted Switch is not supported by Apple.
16
27
  - [deviceTypes] Added evse, waterHeater, solarPower, batteryStorage and heatPump device type.
28
+ - [waterHeater] Added WaterHeater class to create a Water Heater Device Type in one line of code (thanks https://github.com/lboue).
17
29
 
18
30
  ### Changed
19
31
 
@@ -21,6 +33,7 @@ If you like this project and find it useful, please consider giving it a star on
21
33
  - [export]: Removed long deprecated Matter exports from matterbridge. Use matterbridge/matter.
22
34
  - [matterbridge]: Refactored initialize() and cleanup() methods.
23
35
  - [matterbridge]: Updated -help informations.
36
+ - [rvc]: Added the parameters in the RoboticVacuumCleaner class constructor.
24
37
 
25
38
  ### Fixed
26
39
 
package/README.md CHANGED
@@ -353,6 +353,15 @@ The Eve app only shows the history when the plugins run like an AccessoryPlatfor
353
353
 
354
354
  A matterbridge plugin that allows connecting Loxone devices to Matter.
355
355
 
356
+ ### [Dyson robot](https://github.com/thoukydides/matterbridge-dyson-robot)
357
+
358
+ A Matterbridge plugin that connects Dyson robot vacuums and air treatment devices.
359
+ to the Matter smart home ecosystem via their local MQTT APIs.
360
+
361
+ ### [Aeg robot](https://github.com/thoukydides/matterbridge-aeg-robot)
362
+
363
+ AEG RX 9 / Electrolux Pure i9 robot vacuum plugin for Matterbridge.
364
+
356
365
  ## How to install and add a plugin with the frontend (best option)
357
366
 
358
367
  Just open the frontend on the link provided in the log, select a plugin and click install.
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ export * from './matterbridgePlatform.js';
11
11
  export * from './matterbridgeAccessoryPlatform.js';
12
12
  export * from './matterbridgeDynamicPlatform.js';
13
13
  export * from './roboticVacuumCleaner.js';
14
+ export * from './waterHeater.js';
14
15
  export { addVirtualDevice } from './helpers.js';
15
16
  const log = new AnsiLogger({ logName: 'Main', logTimestampFormat: 4, logLevel: hasParameter('debug') ? "debug" : "info" });
16
17
  async function main() {
@@ -289,7 +289,15 @@ export class Matterbridge extends EventEmitter {
289
289
  await fs.access(pairingFilePath, fs.constants.R_OK);
290
290
  const pairingFileContent = await fs.readFile(pairingFilePath, 'utf8');
291
291
  const pairingFileJson = JSON.parse(pairingFileContent);
292
- if (pairingFileJson.passcode && pairingFileJson.discriminator) {
292
+ if (isValidNumber(pairingFileJson.vendorId))
293
+ this.aggregatorVendorId = VendorId(pairingFileJson.vendorId);
294
+ if (isValidString(pairingFileJson.vendorName, 3))
295
+ this.aggregatorVendorName = pairingFileJson.vendorName;
296
+ if (isValidNumber(pairingFileJson.productId))
297
+ this.aggregatorProductId = VendorId(pairingFileJson.productId);
298
+ if (isValidString(pairingFileJson.productName, 3))
299
+ this.aggregatorProductName = pairingFileJson.productName;
300
+ if (isValidNumber(pairingFileJson.passcode) && isValidNumber(pairingFileJson.discriminator)) {
293
301
  this.passcode = pairingFileJson.passcode;
294
302
  this.discriminator = pairingFileJson.discriminator;
295
303
  this.log.info(`Pairing file ${CYAN}${pairingFilePath}${nf} found. Using passcode ${CYAN}${this.passcode}${nf} and discriminator ${CYAN}${this.discriminator}${nf} from pairing file.`);
@@ -1072,8 +1080,8 @@ export class Matterbridge extends EventEmitter {
1072
1080
  await this.matterbridgeContext?.clearAll();
1073
1081
  this.log.info('Matter storage reset done! Remove the bridge from the controller.');
1074
1082
  }
1075
- await withTimeout(this.stopMatterStorage(), 10000, false);
1076
- await withTimeout(this.frontend.stop(), 10000, false);
1083
+ await this.stopMatterStorage();
1084
+ await this.frontend.stop();
1077
1085
  try {
1078
1086
  Logger.removeLogger('matterfilelogger');
1079
1087
  }
@@ -1923,23 +1931,21 @@ export class Matterbridge extends EventEmitter {
1923
1931
  async createDirectory(path, name) {
1924
1932
  try {
1925
1933
  await fs.access(path);
1934
+ this.log.debug(`Directory ${name} already exists at path: ${path}`);
1926
1935
  }
1927
1936
  catch (err) {
1928
- if (err instanceof Error) {
1929
- const nodeErr = err;
1930
- if (nodeErr.code === 'ENOENT') {
1931
- try {
1932
- await fs.mkdir(path, { recursive: true });
1933
- this.log.info(`Created ${name}: ${path}`);
1934
- }
1935
- catch (err) {
1936
- this.log.error(`Error creating dir ${name} path ${path}: ${err}`);
1937
- }
1937
+ if (err.code === 'ENOENT') {
1938
+ try {
1939
+ await fs.mkdir(path, { recursive: true });
1940
+ this.log.info(`Created ${name}: ${path}`);
1938
1941
  }
1939
- else {
1940
- this.log.error(`Error accessing dir ${name} path ${path}: ${err}`);
1942
+ catch (err) {
1943
+ this.log.error(`Error creating dir ${name} path ${path}: ${err}`);
1941
1944
  }
1942
1945
  }
1946
+ else {
1947
+ this.log.error(`Error accessing dir ${name} path ${path}: ${err}`);
1948
+ }
1943
1949
  }
1944
1950
  }
1945
1951
  }
@@ -12,6 +12,7 @@ import { ModeBase } from '@matter/main/clusters/mode-base';
12
12
  import { RvcRunMode } from '@matter/main/clusters/rvc-run-mode';
13
13
  import { RvcOperationalState } from '@matter/main/clusters/rvc-operational-state';
14
14
  import { ServiceArea } from '@matter/main/clusters/service-area';
15
+ import { WaterHeaterManagement } from '@matter/main/clusters/water-heater-management';
15
16
  import { IdentifyServer } from '@matter/main/behaviors/identify';
16
17
  import { OnOffServer } from '@matter/main/behaviors/on-off';
17
18
  import { LevelControlServer } from '@matter/main/behaviors/level-control';
@@ -29,6 +30,8 @@ import { RvcRunModeServer } from '@matter/main/behaviors/rvc-run-mode';
29
30
  import { RvcCleanModeServer } from '@matter/main/behaviors/rvc-clean-mode';
30
31
  import { RvcOperationalStateServer } from '@matter/main/behaviors/rvc-operational-state';
31
32
  import { ServiceAreaServer } from '@matter/main/behaviors/service-area';
33
+ import { WaterHeaterModeServer } from '@matter/main/behaviors/water-heater-mode';
34
+ import { WaterHeaterManagementServer } from '@matter/main/behaviors/water-heater-management';
32
35
  export class MatterbridgeServerDevice {
33
36
  log;
34
37
  commandHandler;
@@ -174,6 +177,14 @@ export class MatterbridgeServerDevice {
174
177
  this.log.info(`Selecting areas ${newAreas} (endpoint ${this.endpointId}.${this.endpointNumber})`);
175
178
  this.commandHandler.executeHandler('selectAreas', { request: { newAreas }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
176
179
  }
180
+ boost({ boostInfo }) {
181
+ this.log.info(`Boost (endpoint ${this.endpointId}.${this.endpointNumber})`);
182
+ this.commandHandler.executeHandler('boost', { request: { boostInfo }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
183
+ }
184
+ cancelBoost() {
185
+ this.log.info(`Cancel boost (endpoint ${this.endpointId}.${this.endpointNumber})`);
186
+ this.commandHandler.executeHandler('cancelBoost', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
187
+ }
177
188
  }
178
189
  export class MatterbridgeServer extends Behavior {
179
190
  static id = 'matterbridge';
@@ -538,3 +549,31 @@ export class MatterbridgeServiceAreaServer extends ServiceAreaServer {
538
549
  return { status: ServiceArea.SelectAreasStatus.Success, statusText: 'Succesfully selected new areas' };
539
550
  }
540
551
  }
552
+ export class MatterbridgeWaterHeaterManagementServer extends WaterHeaterManagementServer {
553
+ boost({ boostInfo }) {
554
+ const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
555
+ device.boost({ boostInfo });
556
+ device.log.info(`MatterbridgeWaterHeaterManagementServer boost called with: ${JSON.stringify(boostInfo)}`);
557
+ this.state.boostState = WaterHeaterManagement.BoostState.Active;
558
+ }
559
+ cancelBoost() {
560
+ const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
561
+ device.cancelBoost();
562
+ device.log.info(`MatterbridgeWaterHeaterManagementServer cancelBoost called`);
563
+ this.state.boostState = WaterHeaterManagement.BoostState.Inactive;
564
+ }
565
+ }
566
+ export class MatterbridgeWaterHeaterModeServer extends WaterHeaterModeServer {
567
+ changeToMode({ newMode }) {
568
+ const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
569
+ const supported = this.state.supportedModes.find((mode) => mode.mode === newMode);
570
+ if (!supported) {
571
+ device.log.error(`MatterbridgeWaterHeaterModeServer changeToMode called with unsupported newMode: ${newMode}`);
572
+ return { status: ModeBase.ModeChangeStatus.UnsupportedMode, statusText: 'Unsupported mode' };
573
+ }
574
+ device.changeToMode({ newMode });
575
+ this.state.currentMode = newMode;
576
+ device.log.info(`MatterbridgeWaterHeaterModeServer changeToMode called with newMode ${newMode} => ${supported.label}`);
577
+ return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Success' };
578
+ }
579
+ }
@@ -3,15 +3,15 @@ import { roboticVacuumCleaner } from './matterbridgeDeviceTypes.js';
3
3
  import { MatterbridgeRvcCleanModeServer, MatterbridgeRvcOperationalStateServer, MatterbridgeRvcRunModeServer, MatterbridgeServiceAreaServer } from './matterbridgeBehaviors.js';
4
4
  import { PowerSource, RvcRunMode, RvcCleanMode, RvcOperationalState } from '@matter/main/clusters';
5
5
  export class RoboticVacuumCleaner extends MatterbridgeEndpoint {
6
- constructor(name, serial) {
6
+ constructor(name, serial, currentRunMode, supportedRunModes, currentCleanMode, supportedCleanModes, currentPhase = null, phaseList = null, operationalState, operationalStateList, supportedAreas, selectedAreas, currentArea) {
7
7
  super(roboticVacuumCleaner, { uniqueStorageKey: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` }, true);
8
8
  this.createDefaultIdentifyClusterServer()
9
9
  .createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Robot Vacuum Cleaner')
10
10
  .createDefaultPowerSourceRechargeableBatteryClusterServer(80, PowerSource.BatChargeLevel.Ok, 5900)
11
- .createDefaultRvcRunModeClusterServer()
12
- .createDefaultRvcCleanModeClusterServer()
13
- .createDefaultRvcOperationalStateClusterServer()
14
- .createDefaultServiceAreaClusterServer();
11
+ .createDefaultRvcRunModeClusterServer(currentRunMode, supportedRunModes)
12
+ .createDefaultRvcCleanModeClusterServer(currentCleanMode, supportedCleanModes)
13
+ .createDefaultRvcOperationalStateClusterServer(phaseList, currentPhase, operationalStateList, operationalState)
14
+ .createDefaultServiceAreaClusterServer(supportedAreas, selectedAreas, currentArea);
15
15
  }
16
16
  createDefaultRvcRunModeClusterServer(currentMode, supportedModes) {
17
17
  this.behaviors.require(MatterbridgeRvcRunModeServer, {
@@ -0,0 +1,45 @@
1
+ import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
2
+ import { waterHeater } from './matterbridgeDeviceTypes.js';
3
+ import { MatterbridgeWaterHeaterManagementServer, MatterbridgeWaterHeaterModeServer } from './matterbridgeBehaviors.js';
4
+ import { WaterHeaterManagement, WaterHeaterMode } from '@matter/main/clusters';
5
+ export class WaterHeater extends MatterbridgeEndpoint {
6
+ constructor(name, serial, waterTemperature = 50, minHeatSetpointLimit = 20, maxHeatSetpointLimit = 80, heaterTypes = { immersionElement1: true }) {
7
+ super(waterHeater, { uniqueStorageKey: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` }, true);
8
+ this.createDefaultIdentifyClusterServer()
9
+ .createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Water Heater')
10
+ .createDefaultPowerSourceWiredClusterServer()
11
+ .createDefaultHeatingThermostatClusterServer(waterTemperature, waterTemperature, minHeatSetpointLimit, maxHeatSetpointLimit)
12
+ .createDefaultWaterHeaterManagementClusterServer(heaterTypes)
13
+ .createDefaultWaterHeaterModeClusterServer();
14
+ }
15
+ createDefaultWaterHeaterManagementClusterServer(heaterTypes, heatDemand, tankPercentage, boostState) {
16
+ this.behaviors.require(MatterbridgeWaterHeaterManagementServer.with(WaterHeaterManagement.Feature.TankPercent), {
17
+ heaterTypes: heaterTypes ?? { immersionElement1: true },
18
+ heatDemand: heatDemand ?? {},
19
+ tankPercentage: tankPercentage ?? 100,
20
+ boostState: boostState ?? WaterHeaterManagement.BoostState.Inactive,
21
+ });
22
+ return this;
23
+ }
24
+ createDefaultWaterHeaterModeClusterServer(currentMode, supportedModes) {
25
+ this.behaviors.require(MatterbridgeWaterHeaterModeServer, {
26
+ supportedModes: supportedModes ?? [
27
+ { label: 'Auto', mode: 1, modeTags: [{ value: WaterHeaterMode.ModeTag.Auto }] },
28
+ { label: 'Quick', mode: 2, modeTags: [{ value: WaterHeaterMode.ModeTag.Quick }] },
29
+ { label: 'Quiet', mode: 3, modeTags: [{ value: WaterHeaterMode.ModeTag.Quiet }] },
30
+ { label: 'LowNoise', mode: 4, modeTags: [{ value: WaterHeaterMode.ModeTag.LowNoise }] },
31
+ { label: 'LowEnergy', mode: 5, modeTags: [{ value: WaterHeaterMode.ModeTag.LowEnergy }] },
32
+ { label: 'Vacation', mode: 6, modeTags: [{ value: WaterHeaterMode.ModeTag.Vacation }] },
33
+ { label: 'Min', mode: 7, modeTags: [{ value: WaterHeaterMode.ModeTag.Min }] },
34
+ { label: 'Max', mode: 8, modeTags: [{ value: WaterHeaterMode.ModeTag.Max }] },
35
+ { label: 'Night', mode: 9, modeTags: [{ value: WaterHeaterMode.ModeTag.Night }] },
36
+ { label: 'Day', mode: 10, modeTags: [{ value: WaterHeaterMode.ModeTag.Day }] },
37
+ { label: 'Off', mode: 11, modeTags: [{ value: WaterHeaterMode.ModeTag.Off }] },
38
+ { label: 'Manual', mode: 12, modeTags: [{ value: WaterHeaterMode.ModeTag.Manual }] },
39
+ { label: 'Timed', mode: 13, modeTags: [{ value: WaterHeaterMode.ModeTag.Timed }] },
40
+ ],
41
+ currentMode: currentMode ?? 1,
42
+ });
43
+ return this;
44
+ }
45
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.3-dev-20250518-e0f532c",
3
+ "version": "3.0.3-dev-20250519-6aad062",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge",
9
- "version": "3.0.3-dev-20250518-e0f532c",
9
+ "version": "3.0.3-dev-20250519-6aad062",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@matter/main": "0.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge",
3
- "version": "3.0.3-dev-20250518-e0f532c",
3
+ "version": "3.0.3-dev-20250519-6aad062",
4
4
  "description": "Matterbridge plugin manager for Matter",
5
5
  "author": "https://github.com/Luligu",
6
6
  "license": "Apache-2.0",