matterbridge 3.3.7-dev-20251109-a306ab9 → 3.3.8-dev-20251114-9b65e59
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 +37 -0
- package/README-SERVICE-OPT.md +4 -4
- package/README.md +4 -0
- package/dist/devices/airConditioner.js +1 -1
- package/dist/devices/batteryStorage.js +1 -1
- package/dist/devices/cooktop.js +1 -1
- package/dist/devices/dishwasher.js +1 -1
- package/dist/devices/evse.js +1 -1
- package/dist/devices/extractorHood.js +1 -1
- package/dist/devices/heatPump.js +1 -1
- package/dist/devices/laundryDryer.js +1 -1
- package/dist/devices/laundryWasher.js +1 -1
- package/dist/devices/microwaveOven.js +1 -1
- package/dist/devices/oven.js +1 -1
- package/dist/devices/refrigerator.js +2 -2
- package/dist/devices/roboticVacuumCleaner.js +1 -1
- package/dist/devices/solarPower.js +1 -1
- package/dist/devices/speaker.js +1 -1
- package/dist/devices/waterHeater.js +1 -1
- package/dist/frontend.js +5 -1
- package/dist/index.js +1 -0
- package/dist/jestutils/export.js +1 -0
- package/dist/{utils → jestutils}/jestHelpers.js +165 -23
- package/dist/matterbridge.js +4 -10
- package/dist/matterbridgeDeviceTypes.js +14 -19
- package/dist/matterbridgeEndpoint.js +22 -22
- package/dist/matterbridgeEndpointHelpers.js +23 -11
- package/dist/matterbridgeEndpointTypes.js +3 -0
- package/dist/matterbridgePlatform.js +17 -0
- package/dist/pluginManager.js +1 -1
- package/dist/utils/error.js +2 -2
- package/npm-shrinkwrap.json +8 -8
- package/package.json +5 -1
- package/scripts/fetch-chip.mjs +100 -0
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,42 @@ Advantages:
|
|
|
28
28
|
- individual plugin isolation in childbridge mode;
|
|
29
29
|
- ability to update the plugin in childbridge mode without restarting matterbridge;
|
|
30
30
|
|
|
31
|
+
## [3.3.8] - 2025-11-15
|
|
32
|
+
|
|
33
|
+
### Development Breaking Changes
|
|
34
|
+
|
|
35
|
+
This will be the last release with the following long deprecated elements:
|
|
36
|
+
|
|
37
|
+
- [platform]: Matterbridge instead of PlatformMatterbridge in the platform constructor (deprecated since 3.0.0).
|
|
38
|
+
- [endpoint]: uniqueStorageKey instead of id in MatterbridgeEndpointOptions (deprecated since months).
|
|
39
|
+
- [endpoint]: endpointId instead of number in MatterbridgeEndpointOptions (deprecated since months).
|
|
40
|
+
|
|
41
|
+
So please update your plugin.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- [endpoint]: Added matterbridgeEndpointTypes.
|
|
46
|
+
- [devices]: Added tests for device types and their revision changes.
|
|
47
|
+
- [clusters]: Added test for clusters and their revision changes.
|
|
48
|
+
- [chip]: Added fetch script to download zcl data from connectedhomeip.
|
|
49
|
+
- [endpoint]: Added createDefaultPowerSourceBatteryClusterServer(). Add Power Source cluster for a generic battery device.
|
|
50
|
+
- [platform]: Added setSchema() to temporarly set the schema for the config editor.
|
|
51
|
+
- [platform]: Added getSchema() to retrieve the schema from the Matterbridge plugin manager.
|
|
52
|
+
- [platform]: Added return value to registerVirtualDevice().
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- [package]: Updated dependencies.
|
|
57
|
+
- [package]: Bumped jestHelpers v.1.0.12.
|
|
58
|
+
- [endpoint]: Changed long deprecated uniqueStorageKey with id in MatterbridgeEndpointOptions.
|
|
59
|
+
- [endpoint]: Changed long deprecated endpointId with number in MatterbridgeEndpointOptions.
|
|
60
|
+
- [endpoint]: Added property originalId in MatterbridgeEndpoint to store the original id passed in MatterbridgeEndpointOptions (since it can be changed for matter.js storage compatibility).
|
|
61
|
+
- [endpoint]: Changed logger level of single device classes.
|
|
62
|
+
|
|
63
|
+
<a href="https://www.buymeacoffee.com/luligugithub">
|
|
64
|
+
<img src="bmc-button.svg" alt="Buy me a coffee" width="80">
|
|
65
|
+
</a>
|
|
66
|
+
|
|
31
67
|
## [3.3.7] - 2025-11-08
|
|
32
68
|
|
|
33
69
|
### Breaking Changes
|
|
@@ -41,6 +77,7 @@ Advantages:
|
|
|
41
77
|
|
|
42
78
|
### Changed
|
|
43
79
|
|
|
80
|
+
- [package]: Updated dependencies.
|
|
44
81
|
- [frontend]: Bumped `frontend` version to 3.3.1.
|
|
45
82
|
- [PluginManager]: Bumped `PluginManager` version to 1.3.0.
|
|
46
83
|
- [DeviceManager]: Bumped `DeviceManager` version to 1.1.0.
|
package/README-SERVICE-OPT.md
CHANGED
|
@@ -162,23 +162,23 @@ sudo systemctl stop matterbridge
|
|
|
162
162
|
### Show Matterbridge status
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
sudo systemctl status matterbridge
|
|
165
|
+
sudo systemctl status matterbridge
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
### Enable Matterbridge to start automatically on boot
|
|
169
169
|
|
|
170
170
|
```bash
|
|
171
|
-
sudo systemctl enable matterbridge
|
|
171
|
+
sudo systemctl enable matterbridge
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
### Disable Matterbridge from starting automatically on boot
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
|
-
sudo systemctl disable matterbridge
|
|
177
|
+
sudo systemctl disable matterbridge
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
### View the log of Matterbridge in real time (this will show the log with colors)
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
|
-
sudo journalctl -u matterbridge
|
|
183
|
+
sudo journalctl -u matterbridge -n 1000 -f --output cat
|
|
184
184
|
```
|
package/README.md
CHANGED
|
@@ -354,6 +354,10 @@ The plugin uses local connection to Daikin Wifi modules. The plugin does not wor
|
|
|
354
354
|
|
|
355
355
|
Matterbridge Roborock Platform Plugin is a dynamic platform plugin for Matterbridge that integrates Roborock vacuums into the Matter ecosystem, enabling control via Apple Home and other Matter-compatible apps.
|
|
356
356
|
|
|
357
|
+
### [Wyze Robot Vacuum](https://github.com/RMCob/matterbridge-wyze-robovac)
|
|
358
|
+
|
|
359
|
+
Matterbridge plugin to control and report status of the Wyze S200 Robot Vacuum
|
|
360
|
+
|
|
357
361
|
## How to install and add a plugin with the frontend (best option)
|
|
358
362
|
|
|
359
363
|
Just open the frontend on the link provided in the log, select a plugin and click install.
|
|
@@ -5,7 +5,7 @@ import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
|
5
5
|
export class AirConditioner extends MatterbridgeEndpoint {
|
|
6
6
|
constructor(name, serial, options = {}) {
|
|
7
7
|
const { localTemperature = 23, occupiedHeatingSetpoint = 21, occupiedCoolingSetpoint = 25, minSetpointDeadBand = 1, minHeatSetpointLimit = 0, maxHeatSetpointLimit = 50, minCoolSetpointLimit = 0, maxCoolSetpointLimit = 50, temperatureDisplayMode = ThermostatUserInterfaceConfiguration.TemperatureDisplayMode.Celsius, keypadLockout = ThermostatUserInterfaceConfiguration.KeypadLockout.NoLockout, scheduleProgrammingVisibility = ThermostatUserInterfaceConfiguration.ScheduleProgrammingVisibility.ScheduleProgrammingPermitted, fanMode = FanControl.FanMode.Off, fanModeSequence = FanControl.FanModeSequence.OffLowMedHighAuto, percentSetting = 0, percentCurrent = 0, } = options;
|
|
8
|
-
super([airConditioner, powerSource], {
|
|
8
|
+
super([airConditioner, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
9
9
|
this.createDefaultIdentifyClusterServer();
|
|
10
10
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Air Conditioner');
|
|
11
11
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
@@ -5,7 +5,7 @@ import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
|
5
5
|
import { deviceEnergyManagement, electricalSensor, batteryStorage, powerSource } from '../matterbridgeDeviceTypes.js';
|
|
6
6
|
export class BatteryStorage extends MatterbridgeEndpoint {
|
|
7
7
|
constructor(name, serial, batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, voltage = null, current = null, power = null, energyImported = null, energyExported = null, absMinPower = 0, absMaxPower = 0) {
|
|
8
|
-
super([batteryStorage, electricalSensor, deviceEnergyManagement], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` }
|
|
8
|
+
super([batteryStorage, electricalSensor, deviceEnergyManagement], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
9
9
|
this.createDefaultIdentifyClusterServer()
|
|
10
10
|
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Solar Power')
|
|
11
11
|
.createDefaultPowerTopologyClusterServer()
|
package/dist/devices/cooktop.js
CHANGED
|
@@ -3,7 +3,7 @@ import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
|
3
3
|
import { createLevelTemperatureControlClusterServer } from './temperatureControl.js';
|
|
4
4
|
export class Cooktop extends MatterbridgeEndpoint {
|
|
5
5
|
constructor(name, serial) {
|
|
6
|
-
super([cooktop, powerSource], {
|
|
6
|
+
super([cooktop, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
7
7
|
this.createDefaultIdentifyClusterServer();
|
|
8
8
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Cooktop');
|
|
9
9
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
@@ -8,7 +8,7 @@ import { MatterbridgeOnOffServer, MatterbridgeServer } from '../matterbridgeBeha
|
|
|
8
8
|
import { createLevelTemperatureControlClusterServer, createNumberTemperatureControlClusterServer } from './temperatureControl.js';
|
|
9
9
|
export class Dishwasher extends MatterbridgeEndpoint {
|
|
10
10
|
constructor(name, serial, currentMode, supportedModes, selectedTemperatureLevel, supportedTemperatureLevels, temperatureSetpoint, minTemperature, maxTemperature, step, operationalState) {
|
|
11
|
-
super([dishwasher, powerSource], {
|
|
11
|
+
super([dishwasher, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
12
12
|
this.createDefaultIdentifyClusterServer();
|
|
13
13
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Dishwasher');
|
|
14
14
|
this.createDefaultPowerSourceWiredClusterServer();
|
package/dist/devices/evse.js
CHANGED
|
@@ -9,7 +9,7 @@ import { MatterbridgeServer } from '../matterbridgeBehaviors.js';
|
|
|
9
9
|
import { deviceEnergyManagement, electricalSensor, evse, powerSource } from '../matterbridgeDeviceTypes.js';
|
|
10
10
|
export class Evse extends MatterbridgeEndpoint {
|
|
11
11
|
constructor(name, serial, currentMode, supportedModes, state, supplyState, faultState, voltage = null, current = null, power = null, energy = null, absMinPower, absMaxPower) {
|
|
12
|
-
super([evse, powerSource, electricalSensor, deviceEnergyManagement], {
|
|
12
|
+
super([evse, powerSource, electricalSensor, deviceEnergyManagement], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
13
13
|
this.createDefaultIdentifyClusterServer()
|
|
14
14
|
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Evse')
|
|
15
15
|
.createDefaultPowerSourceWiredClusterServer()
|
|
@@ -3,7 +3,7 @@ import { extractorHood, powerSource } from '../matterbridgeDeviceTypes.js';
|
|
|
3
3
|
import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
4
4
|
export class ExtractorHood extends MatterbridgeEndpoint {
|
|
5
5
|
constructor(name, serial, hepaCondition = 100, hepaChangeIndication = ResourceMonitoring.ChangeIndication.Ok, hepaInPlaceIndicator = true, hepaLastChangedTime = null, hepaReplacementProductList = [], activatedCarbonCondition = 100, activatedCarbonChangeIndication = ResourceMonitoring.ChangeIndication.Ok, activatedCarbonInPlaceIndicator = true, activatedCarbonLastChangedTime = null, activatedCarbonReplacementProductList = []) {
|
|
6
|
-
super([extractorHood, powerSource], {
|
|
6
|
+
super([extractorHood, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
7
7
|
this.createDefaultIdentifyClusterServer();
|
|
8
8
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Extractor Hood');
|
|
9
9
|
this.createDefaultPowerSourceWiredClusterServer();
|
package/dist/devices/heatPump.js
CHANGED
|
@@ -7,7 +7,7 @@ export class HeatPump extends MatterbridgeEndpoint {
|
|
|
7
7
|
super([heatPump, powerSource, electricalSensor, deviceEnergyManagement], {
|
|
8
8
|
tagList: [{ mfgCode: null, namespaceId: PowerSourceTag.Grid.namespaceId, tag: PowerSourceTag.Grid.tag, label: null }],
|
|
9
9
|
id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
|
|
10
|
-
}
|
|
10
|
+
});
|
|
11
11
|
this.createDefaultIdentifyClusterServer()
|
|
12
12
|
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Heat Pump')
|
|
13
13
|
.createDefaultPowerSourceWiredClusterServer()
|
|
@@ -7,7 +7,7 @@ import { MatterbridgeLaundryWasherModeServer } from './laundryWasher.js';
|
|
|
7
7
|
import { createLevelTemperatureControlClusterServer, createNumberTemperatureControlClusterServer } from './temperatureControl.js';
|
|
8
8
|
export class LaundryDryer extends MatterbridgeEndpoint {
|
|
9
9
|
constructor(name, serial, currentMode, supportedModes, selectedTemperatureLevel, supportedTemperatureLevels, temperatureSetpoint, minTemperature, maxTemperature, step, operationalState) {
|
|
10
|
-
super([laundryDryer, powerSource], {
|
|
10
|
+
super([laundryDryer, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
11
11
|
this.createDefaultIdentifyClusterServer();
|
|
12
12
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Laundry Dryer');
|
|
13
13
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
@@ -9,7 +9,7 @@ import { MatterbridgeOnOffServer, MatterbridgeServer } from '../matterbridgeBeha
|
|
|
9
9
|
import { createLevelTemperatureControlClusterServer, createNumberTemperatureControlClusterServer } from './temperatureControl.js';
|
|
10
10
|
export class LaundryWasher extends MatterbridgeEndpoint {
|
|
11
11
|
constructor(name, serial, currentMode, supportedModes, spinSpeedCurrent, spinSpeeds, numberOfRinses, supportedRinses, selectedTemperatureLevel, supportedTemperatureLevels, temperatureSetpoint, minTemperature, maxTemperature, step, operationalState) {
|
|
12
|
-
super([laundryWasher, powerSource], {
|
|
12
|
+
super([laundryWasher, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
13
13
|
this.createDefaultIdentifyClusterServer();
|
|
14
14
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Laundry Washer');
|
|
15
15
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
@@ -16,7 +16,7 @@ export class MicrowaveOven extends MatterbridgeEndpoint {
|
|
|
16
16
|
{ label: 'Normal', mode: 6, modeTags: [{ value: MicrowaveOvenMode.ModeTag.Normal }] },
|
|
17
17
|
{ label: 'Defrost', mode: 7, modeTags: [{ value: MicrowaveOvenMode.ModeTag.Defrost }] },
|
|
18
18
|
], selectedWattIndex = 5, supportedWatts = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000], cookTime = 60, maxCookTime = 3600) {
|
|
19
|
-
super([microwaveOven, powerSource], {
|
|
19
|
+
super([microwaveOven, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
20
20
|
this.createDefaultIdentifyClusterServer();
|
|
21
21
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Microwave Oven');
|
|
22
22
|
this.createDefaultPowerSourceWiredClusterServer();
|
package/dist/devices/oven.js
CHANGED
|
@@ -9,7 +9,7 @@ import { MatterbridgeServer } from '../matterbridgeBehaviors.js';
|
|
|
9
9
|
import { createLevelTemperatureControlClusterServer } from './temperatureControl.js';
|
|
10
10
|
export class Oven extends MatterbridgeEndpoint {
|
|
11
11
|
constructor(name, serial) {
|
|
12
|
-
super([oven, powerSource], {
|
|
12
|
+
super([oven, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
13
13
|
this.createDefaultIdentifyClusterServer();
|
|
14
14
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Oven');
|
|
15
15
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
@@ -8,7 +8,7 @@ import { MatterbridgeServer } from '../matterbridgeBehaviors.js';
|
|
|
8
8
|
import { createLevelTemperatureControlClusterServer } from './temperatureControl.js';
|
|
9
9
|
export class Refrigerator extends MatterbridgeEndpoint {
|
|
10
10
|
constructor(name, serial) {
|
|
11
|
-
super([refrigerator, powerSource], {
|
|
11
|
+
super([refrigerator, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` }, true);
|
|
12
12
|
this.createDefaultIdentifyClusterServer();
|
|
13
13
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Refrigerator');
|
|
14
14
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
@@ -19,7 +19,7 @@ export class Refrigerator extends MatterbridgeEndpoint {
|
|
|
19
19
|
{ label: 'RapidCool', mode: 2, modeTags: [{ value: RefrigeratorAndTemperatureControlledCabinetMode.ModeTag.RapidCool }] },
|
|
20
20
|
{ label: 'RapidFreeze', mode: 3, modeTags: [{ value: RefrigeratorAndTemperatureControlledCabinetMode.ModeTag.RapidFreeze }] },
|
|
21
21
|
], selectedTemperatureLevel = 2, supportedTemperatureLevels = ['Level 1', 'Level 2', 'Level 3', 'Level 4', 'Level 5'], currentTemperature = 1000) {
|
|
22
|
-
const cabinet = this.addChildDeviceType(name, temperatureControlledCabinetCooler, { tagList }
|
|
22
|
+
const cabinet = this.addChildDeviceType(name, temperatureControlledCabinetCooler, { tagList });
|
|
23
23
|
cabinet.log.logName = name;
|
|
24
24
|
cabinet.createDefaultIdentifyClusterServer();
|
|
25
25
|
createLevelTemperatureControlClusterServer(cabinet, selectedTemperatureLevel, supportedTemperatureLevels);
|
|
@@ -14,7 +14,7 @@ import { powerSource, roboticVacuumCleaner } from '../matterbridgeDeviceTypes.js
|
|
|
14
14
|
import { MatterbridgeServer, MatterbridgeServiceAreaServer } from '../matterbridgeBehaviors.js';
|
|
15
15
|
export class RoboticVacuumCleaner extends MatterbridgeEndpoint {
|
|
16
16
|
constructor(name, serial, mode = undefined, currentRunMode, supportedRunModes, currentCleanMode, supportedCleanModes, currentPhase = null, phaseList = null, operationalState, operationalStateList, supportedAreas, selectedAreas, currentArea, supportedMaps) {
|
|
17
|
-
super([roboticVacuumCleaner, powerSource], {
|
|
17
|
+
super([roboticVacuumCleaner, powerSource], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`, mode });
|
|
18
18
|
this.createDefaultIdentifyClusterServer()
|
|
19
19
|
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Robot Vacuum Cleaner')
|
|
20
20
|
.createDefaultPowerSourceRechargeableBatteryClusterServer(80, PowerSource.BatChargeLevel.Ok, 5900)
|
|
@@ -7,7 +7,7 @@ export class SolarPower extends MatterbridgeEndpoint {
|
|
|
7
7
|
super([solarPower, powerSource, electricalSensor, deviceEnergyManagement], {
|
|
8
8
|
tagList: [{ mfgCode: null, namespaceId: PowerSourceTag.Solar.namespaceId, tag: PowerSourceTag.Solar.tag, label: null }],
|
|
9
9
|
id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}`,
|
|
10
|
-
}
|
|
10
|
+
});
|
|
11
11
|
this.createDefaultIdentifyClusterServer()
|
|
12
12
|
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Solar Power')
|
|
13
13
|
.createDefaultPowerSourceWiredClusterServer()
|
package/dist/devices/speaker.js
CHANGED
|
@@ -10,7 +10,7 @@ export class Speaker extends MatterbridgeEndpoint {
|
|
|
10
10
|
volume = 1;
|
|
11
11
|
if (volume > 254)
|
|
12
12
|
volume = 254;
|
|
13
|
-
super([speakerDevice], {
|
|
13
|
+
super([speakerDevice], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
14
14
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Speaker');
|
|
15
15
|
this.createOnOffClusterServer(!muted);
|
|
16
16
|
this.createLevelControlClusterServer(volume);
|
|
@@ -9,7 +9,7 @@ import { electricalSensor, powerSource, waterHeater } from '../matterbridgeDevic
|
|
|
9
9
|
import { MatterbridgeEndpoint } from '../matterbridgeEndpoint.js';
|
|
10
10
|
export class WaterHeater extends MatterbridgeEndpoint {
|
|
11
11
|
constructor(name, serial, waterTemperature = 50, targetWaterTemperature = 55, minHeatSetpointLimit = 20, maxHeatSetpointLimit = 80, heaterTypes = { immersionElement1: true }, tankPercentage = 90, voltage = null, current = null, power = null, energy = null, absMinPower = 0, absMaxPower = 0) {
|
|
12
|
-
super([waterHeater, powerSource, electricalSensor], {
|
|
12
|
+
super([waterHeater, powerSource, electricalSensor], { id: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` });
|
|
13
13
|
this.createDefaultIdentifyClusterServer()
|
|
14
14
|
.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Water Heater')
|
|
15
15
|
.createDefaultPowerSourceWiredClusterServer()
|
package/dist/frontend.js
CHANGED
|
@@ -1453,7 +1453,11 @@ export class Frontend extends EventEmitter {
|
|
|
1453
1453
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertiseTime: 0, advertising: false } });
|
|
1454
1454
|
}
|
|
1455
1455
|
if (data.params.advertise) {
|
|
1456
|
-
|
|
1456
|
+
const advertiser = serverNode.env.get(DeviceAdvertiser);
|
|
1457
|
+
if (advertiser && advertiser.advertise && typeof advertiser.advertise === 'function')
|
|
1458
|
+
await advertiser.advertise(true);
|
|
1459
|
+
if (advertiser && advertiser.restartAdvertisement && typeof advertiser.restartAdvertisement === 'function')
|
|
1460
|
+
advertiser.restartAdvertisement();
|
|
1457
1461
|
this.log.debug(`*Advertising has been sent for node ${data.params.id}`);
|
|
1458
1462
|
this.wssSendRefreshRequired('matter', { matter: { ...matter, advertising: true } });
|
|
1459
1463
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './matterbridge.js';
|
|
|
4
4
|
export * from './matterbridgeTypes.js';
|
|
5
5
|
export * from './matterbridgeEndpoint.js';
|
|
6
6
|
export * from './matterbridgeEndpointHelpers.js';
|
|
7
|
+
export * from './matterbridgeEndpointTypes.js';
|
|
7
8
|
export * from './matterbridgeBehaviors.js';
|
|
8
9
|
export * from './matterbridgeDeviceTypes.js';
|
|
9
10
|
export * from './matterbridgePlatform.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './jestHelpers.js';
|
|
@@ -1,26 +1,46 @@
|
|
|
1
1
|
import { rmSync } from 'node:fs';
|
|
2
2
|
import { inspect } from 'node:util';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { jest } from '@jest/globals';
|
|
5
4
|
import { AnsiLogger } from 'node-ansi-logger';
|
|
6
|
-
import '@matter/
|
|
7
|
-
import {
|
|
8
|
-
import { DeviceTypeId, VendorId } from '@matter/types';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
5
|
+
import { LogLevel as MatterLogLevel, LogFormat as MatterLogFormat, Environment, Lifecycle } from '@matter/general';
|
|
6
|
+
import { Endpoint, ServerNode, ServerNodeStore } from '@matter/node';
|
|
7
|
+
import { DeviceTypeId, VendorId } from '@matter/types/datatype';
|
|
8
|
+
import { AggregatorEndpoint, RootEndpoint } from '@matter/node/endpoints';
|
|
9
|
+
import { MdnsService } from '@matter/main/protocol';
|
|
10
|
+
import { Matterbridge } from '../matterbridge.js';
|
|
11
|
+
import { MATTER_STORAGE_NAME } from '../matterbridgeTypes.js';
|
|
13
12
|
export let loggerLogSpy;
|
|
13
|
+
export let loggerDebugSpy;
|
|
14
|
+
export let loggerInfoSpy;
|
|
15
|
+
export let loggerNoticeSpy;
|
|
16
|
+
export let loggerWarnSpy;
|
|
17
|
+
export let loggerErrorSpy;
|
|
18
|
+
export let loggerFatalSpy;
|
|
14
19
|
export let consoleLogSpy;
|
|
15
20
|
export let consoleDebugSpy;
|
|
16
21
|
export let consoleInfoSpy;
|
|
17
22
|
export let consoleWarnSpy;
|
|
18
23
|
export let consoleErrorSpy;
|
|
19
|
-
export
|
|
24
|
+
export let addBridgedEndpointSpy;
|
|
25
|
+
export let removeBridgedEndpointSpy;
|
|
26
|
+
export let removeAllBridgedEndpointsSpy;
|
|
27
|
+
export let matterbridge;
|
|
28
|
+
export let environment;
|
|
29
|
+
export let server;
|
|
30
|
+
export let aggregator;
|
|
31
|
+
export let log;
|
|
32
|
+
export async function setupTest(name, debug = false) {
|
|
20
33
|
expect(name).toBeDefined();
|
|
21
34
|
expect(typeof name).toBe('string');
|
|
22
35
|
expect(name.length).toBeGreaterThanOrEqual(4);
|
|
23
36
|
rmSync(path.join('jest', name), { recursive: true, force: true });
|
|
37
|
+
const { jest } = await import('@jest/globals');
|
|
38
|
+
loggerDebugSpy = jest.spyOn(AnsiLogger.prototype, 'debug');
|
|
39
|
+
loggerInfoSpy = jest.spyOn(AnsiLogger.prototype, 'info');
|
|
40
|
+
loggerNoticeSpy = jest.spyOn(AnsiLogger.prototype, 'notice');
|
|
41
|
+
loggerWarnSpy = jest.spyOn(AnsiLogger.prototype, 'warn');
|
|
42
|
+
loggerErrorSpy = jest.spyOn(AnsiLogger.prototype, 'error');
|
|
43
|
+
loggerFatalSpy = jest.spyOn(AnsiLogger.prototype, 'fatal');
|
|
24
44
|
if (debug) {
|
|
25
45
|
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log');
|
|
26
46
|
consoleLogSpy = jest.spyOn(console, 'log');
|
|
@@ -37,8 +57,12 @@ export function setupTest(name, debug = false) {
|
|
|
37
57
|
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
38
58
|
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
39
59
|
}
|
|
60
|
+
addBridgedEndpointSpy = jest.spyOn(Matterbridge.prototype, 'addBridgedEndpoint');
|
|
61
|
+
removeBridgedEndpointSpy = jest.spyOn(Matterbridge.prototype, 'removeBridgedEndpoint');
|
|
62
|
+
removeAllBridgedEndpointsSpy = jest.spyOn(Matterbridge.prototype, 'removeAllBridgedEndpoints');
|
|
40
63
|
}
|
|
41
|
-
export function setDebug(debug) {
|
|
64
|
+
export async function setDebug(debug) {
|
|
65
|
+
const { jest } = await import('@jest/globals');
|
|
42
66
|
if (debug) {
|
|
43
67
|
loggerLogSpy.mockRestore();
|
|
44
68
|
consoleLogSpy.mockRestore();
|
|
@@ -62,20 +86,133 @@ export function setDebug(debug) {
|
|
|
62
86
|
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
expect(
|
|
68
|
-
expect(
|
|
69
|
-
|
|
70
|
-
|
|
89
|
+
export async function createMatterbridgeEnvironment(name) {
|
|
90
|
+
matterbridge = await Matterbridge.loadInstance(false);
|
|
91
|
+
expect(matterbridge).toBeDefined();
|
|
92
|
+
expect(matterbridge).toBeInstanceOf(Matterbridge);
|
|
93
|
+
matterbridge.matterbridgeVersion = '3.3.0';
|
|
94
|
+
matterbridge.bridgeMode = 'bridge';
|
|
95
|
+
matterbridge.rootDirectory = path.join('jest', name);
|
|
96
|
+
matterbridge.homeDirectory = path.join('jest', name);
|
|
97
|
+
matterbridge.matterbridgeDirectory = path.join('jest', name, '.matterbridge');
|
|
98
|
+
matterbridge.matterbridgePluginDirectory = path.join('jest', name, 'Matterbridge');
|
|
99
|
+
matterbridge.matterbridgeCertDirectory = path.join('jest', name, '.mattercert');
|
|
100
|
+
matterbridge.log.logLevel = "debug";
|
|
101
|
+
log = new AnsiLogger({ logName: 'Plugin platform', logTimestampFormat: 4, logLevel: "debug" });
|
|
102
|
+
matterbridge.environment = createTestEnvironment(name);
|
|
103
|
+
expect(matterbridge.environment).toBeDefined();
|
|
104
|
+
expect(matterbridge.environment).toBeInstanceOf(Environment);
|
|
105
|
+
return matterbridge;
|
|
106
|
+
}
|
|
107
|
+
export async function startMatterbridgeEnvironment(port = 5540) {
|
|
108
|
+
await matterbridge.startMatterStorage();
|
|
109
|
+
expect(matterbridge.matterStorageService).toBeDefined();
|
|
110
|
+
expect(matterbridge.matterStorageManager).toBeDefined();
|
|
111
|
+
expect(matterbridge.matterbridgeContext).toBeDefined();
|
|
112
|
+
server = await matterbridge.createServerNode(matterbridge.matterbridgeContext, port);
|
|
113
|
+
expect(server).toBeDefined();
|
|
114
|
+
expect(server).toBeDefined();
|
|
115
|
+
expect(server.lifecycle.isReady).toBeTruthy();
|
|
116
|
+
matterbridge.serverNode = server;
|
|
117
|
+
aggregator = await matterbridge.createAggregatorNode(matterbridge.matterbridgeContext);
|
|
118
|
+
expect(aggregator).toBeDefined();
|
|
119
|
+
matterbridge.aggregatorNode = aggregator;
|
|
120
|
+
expect(await server.add(aggregator)).toBeDefined();
|
|
121
|
+
expect(server.parts.has(aggregator.id)).toBeTruthy();
|
|
122
|
+
expect(server.parts.has(aggregator)).toBeTruthy();
|
|
123
|
+
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
124
|
+
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
125
|
+
await new Promise((resolve) => {
|
|
126
|
+
server.lifecycle.online.on(async () => {
|
|
127
|
+
resolve();
|
|
128
|
+
});
|
|
129
|
+
server.start();
|
|
130
|
+
});
|
|
131
|
+
expect(server.lifecycle.isReady).toBeTruthy();
|
|
132
|
+
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
133
|
+
expect(server.lifecycle.isCommissioned).toBeFalsy();
|
|
134
|
+
expect(server.lifecycle.isPartsReady).toBeTruthy();
|
|
135
|
+
expect(server.lifecycle.hasId).toBeTruthy();
|
|
136
|
+
expect(server.lifecycle.hasNumber).toBeTruthy();
|
|
137
|
+
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
138
|
+
expect(aggregator.lifecycle.isInstalled).toBeTruthy();
|
|
139
|
+
expect(aggregator.lifecycle.isPartsReady).toBeTruthy();
|
|
140
|
+
expect(aggregator.lifecycle.hasId).toBeTruthy();
|
|
141
|
+
expect(aggregator.lifecycle.hasNumber).toBeTruthy();
|
|
142
|
+
await flushAsync();
|
|
143
|
+
return [server, aggregator];
|
|
144
|
+
}
|
|
145
|
+
export function addMatterbridgePlatform(platform, name) {
|
|
146
|
+
if (name)
|
|
147
|
+
platform.config.name = name;
|
|
148
|
+
expect(platform).toBeDefined();
|
|
149
|
+
expect(platform.config.name).toBeDefined();
|
|
150
|
+
expect(platform.config.type).toBeDefined();
|
|
151
|
+
expect(platform.type).toBeDefined();
|
|
152
|
+
expect(platform.config.version).toBeDefined();
|
|
153
|
+
expect(platform.version).toBeDefined();
|
|
154
|
+
expect(platform.config.debug).toBeDefined();
|
|
155
|
+
expect(platform.config.unregisterOnShutdown).toBeDefined();
|
|
156
|
+
matterbridge.plugins._plugins.set(platform.config.name, {
|
|
157
|
+
name: platform.config.name,
|
|
158
|
+
path: './',
|
|
159
|
+
type: platform.type,
|
|
160
|
+
version: platform.version,
|
|
161
|
+
description: 'Plugin ' + platform.config.name,
|
|
162
|
+
author: 'Unknown',
|
|
163
|
+
enabled: true,
|
|
164
|
+
});
|
|
165
|
+
platform['name'] = platform.config.name;
|
|
166
|
+
}
|
|
167
|
+
export async function stopMatterbridgeEnvironment() {
|
|
168
|
+
expect(matterbridge).toBeDefined();
|
|
169
|
+
expect(server).toBeDefined();
|
|
170
|
+
expect(aggregator).toBeDefined();
|
|
171
|
+
await flushAllEndpointNumberPersistence(server);
|
|
172
|
+
await assertAllEndpointNumbersPersisted(server);
|
|
173
|
+
expect(server.lifecycle.isReady).toBeTruthy();
|
|
174
|
+
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
175
|
+
await server.close();
|
|
176
|
+
expect(server.lifecycle.isReady).toBeTruthy();
|
|
177
|
+
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
178
|
+
await matterbridge.stopMatterStorage();
|
|
179
|
+
expect(matterbridge.matterStorageService).not.toBeDefined();
|
|
180
|
+
expect(matterbridge.matterStorageManager).not.toBeDefined();
|
|
181
|
+
expect(matterbridge.matterbridgeContext).not.toBeDefined();
|
|
182
|
+
await flushAsync();
|
|
183
|
+
}
|
|
184
|
+
export async function destroyMatterbridgeEnvironment(cleanupPause = 10, destroyPause = 250) {
|
|
185
|
+
await destroyInstance(matterbridge, cleanupPause, destroyPause);
|
|
186
|
+
await closeMdnsInstance(matterbridge);
|
|
187
|
+
Matterbridge.instance = undefined;
|
|
188
|
+
}
|
|
189
|
+
export async function destroyInstance(matterbridge, cleanupPause = 10, destroyPause = 250) {
|
|
190
|
+
await matterbridge.cleanup('destroying instance...', false, cleanupPause);
|
|
191
|
+
if (destroyPause > 0)
|
|
192
|
+
await flushAsync(undefined, undefined, destroyPause);
|
|
193
|
+
}
|
|
194
|
+
export async function closeMdnsInstance(matterbridge) {
|
|
195
|
+
const mdns = matterbridge.environment.get(MdnsService);
|
|
196
|
+
if (mdns && mdns[Symbol.asyncDispose] && typeof mdns[Symbol.asyncDispose] === 'function')
|
|
197
|
+
await mdns[Symbol.asyncDispose]();
|
|
198
|
+
if (mdns && mdns.close && typeof mdns.close === 'function')
|
|
199
|
+
await mdns.close();
|
|
200
|
+
}
|
|
201
|
+
export function createTestEnvironment(name) {
|
|
202
|
+
expect(name).toBeDefined();
|
|
203
|
+
expect(typeof name).toBe('string');
|
|
204
|
+
expect(name.length).toBeGreaterThanOrEqual(4);
|
|
205
|
+
rmSync(path.join('jest', name), { recursive: true, force: true });
|
|
206
|
+
environment = Environment.default;
|
|
71
207
|
environment.vars.set('log.level', MatterLogLevel.DEBUG);
|
|
72
208
|
environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
73
|
-
environment.vars.set('path.root',
|
|
209
|
+
environment.vars.set('path.root', path.join('jest', name, '.matterbridge', MATTER_STORAGE_NAME));
|
|
74
210
|
environment.vars.set('runtime.signals', false);
|
|
75
211
|
environment.vars.set('runtime.exitcode', false);
|
|
212
|
+
new MdnsService(environment);
|
|
76
213
|
return environment;
|
|
77
214
|
}
|
|
78
|
-
export async function flushAsync(ticks = 3, microTurns = 10, pause =
|
|
215
|
+
export async function flushAsync(ticks = 3, microTurns = 10, pause = 250) {
|
|
79
216
|
for (let i = 0; i < ticks; i++)
|
|
80
217
|
await new Promise((resolve) => setImmediate(resolve));
|
|
81
218
|
for (let i = 0; i < microTurns; i++)
|
|
@@ -119,8 +256,9 @@ export async function assertAllEndpointNumbersPersisted(targetServer) {
|
|
|
119
256
|
return all.length;
|
|
120
257
|
}
|
|
121
258
|
export async function startServerNode(name, port) {
|
|
122
|
-
|
|
259
|
+
server = await ServerNode.create({
|
|
123
260
|
id: name + 'ServerNode',
|
|
261
|
+
environment,
|
|
124
262
|
productDescription: {
|
|
125
263
|
name: name + 'ServerNode',
|
|
126
264
|
deviceType: DeviceTypeId(RootEndpoint.deviceType),
|
|
@@ -143,7 +281,7 @@ export async function startServerNode(name, port) {
|
|
|
143
281
|
});
|
|
144
282
|
expect(server).toBeDefined();
|
|
145
283
|
expect(server.lifecycle.isReady).toBeTruthy();
|
|
146
|
-
|
|
284
|
+
aggregator = new Endpoint(AggregatorEndpoint, {
|
|
147
285
|
id: name + 'AggregatorNode',
|
|
148
286
|
});
|
|
149
287
|
expect(aggregator).toBeDefined();
|
|
@@ -181,7 +319,11 @@ export async function stopServerNode(server) {
|
|
|
181
319
|
await server.close();
|
|
182
320
|
expect(server.lifecycle.isReady).toBeTruthy();
|
|
183
321
|
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
184
|
-
|
|
322
|
+
const mdns = environment.get(MdnsService);
|
|
323
|
+
if (mdns && typeof mdns[Symbol.asyncDispose] === 'function')
|
|
324
|
+
await mdns[Symbol.asyncDispose]();
|
|
325
|
+
if (mdns && typeof mdns.close === 'function')
|
|
326
|
+
await mdns.close();
|
|
185
327
|
await flushAsync();
|
|
186
328
|
}
|
|
187
329
|
export async function addDevice(owner, device, pause = 10) {
|
|
@@ -206,7 +348,7 @@ export async function addDevice(owner, device, pause = 10) {
|
|
|
206
348
|
expect(device.lifecycle.hasId).toBeTruthy();
|
|
207
349
|
expect(device.lifecycle.hasNumber).toBeTruthy();
|
|
208
350
|
expect(device.construction.status).toBe(Lifecycle.Status.Active);
|
|
209
|
-
await flushAsync(
|
|
351
|
+
await flushAsync(undefined, undefined, pause);
|
|
210
352
|
return true;
|
|
211
353
|
}
|
|
212
354
|
export async function deleteDevice(owner, device, pause = 10) {
|
|
@@ -231,6 +373,6 @@ export async function deleteDevice(owner, device, pause = 10) {
|
|
|
231
373
|
expect(device.lifecycle.hasId).toBeTruthy();
|
|
232
374
|
expect(device.lifecycle.hasNumber).toBeTruthy();
|
|
233
375
|
expect(device.construction.status).toBe(Lifecycle.Status.Destroyed);
|
|
234
|
-
await flushAsync(
|
|
376
|
+
await flushAsync(undefined, undefined, pause);
|
|
235
377
|
return true;
|
|
236
378
|
}
|
package/dist/matterbridge.js
CHANGED
|
@@ -171,12 +171,6 @@ export class Matterbridge extends EventEmitter {
|
|
|
171
171
|
}
|
|
172
172
|
return Matterbridge.instance;
|
|
173
173
|
}
|
|
174
|
-
async destroyInstance(timeout = 1000, pause = 250) {
|
|
175
|
-
this.log.info(`Destroy instance...`);
|
|
176
|
-
await this.cleanup('destroying instance...', false, timeout);
|
|
177
|
-
if (pause)
|
|
178
|
-
await wait(pause, 'destroyInstance stop', true);
|
|
179
|
-
}
|
|
180
174
|
async initialize() {
|
|
181
175
|
this.emit('initialize_started');
|
|
182
176
|
if (hasParameter('service'))
|
|
@@ -943,7 +937,7 @@ export class Matterbridge extends EventEmitter {
|
|
|
943
937
|
async shutdownProcessAndFactoryReset() {
|
|
944
938
|
await this.cleanup('shutting down with factory reset...', false);
|
|
945
939
|
}
|
|
946
|
-
async cleanup(message, restart = false,
|
|
940
|
+
async cleanup(message, restart = false, pause = 1000) {
|
|
947
941
|
if (this.initialized && !this.hasCleanupStarted) {
|
|
948
942
|
this.emit('cleanup_started');
|
|
949
943
|
this.hasCleanupStarted = true;
|
|
@@ -984,9 +978,9 @@ export class Matterbridge extends EventEmitter {
|
|
|
984
978
|
}
|
|
985
979
|
}
|
|
986
980
|
this.log.notice(`Stopping matter server nodes in ${this.bridgeMode} mode...`);
|
|
987
|
-
if (
|
|
988
|
-
this.log.debug(`Waiting ${
|
|
989
|
-
await wait(
|
|
981
|
+
if (pause > 0) {
|
|
982
|
+
this.log.debug(`Waiting ${pause}ms for the MessageExchange to finish...`);
|
|
983
|
+
await wait(pause, `Waiting ${pause}ms for the MessageExchange to finish...`, false);
|
|
990
984
|
}
|
|
991
985
|
if (this.bridgeMode === 'bridge') {
|
|
992
986
|
if (this.serverNode) {
|
|
@@ -85,6 +85,7 @@ import { WakeOnLan } from '@matter/types/clusters/wake-on-lan';
|
|
|
85
85
|
import { WaterHeaterManagement } from '@matter/types/clusters/water-heater-management';
|
|
86
86
|
import { WaterHeaterMode } from '@matter/types/clusters/water-heater-mode';
|
|
87
87
|
import { WindowCovering } from '@matter/types/clusters/window-covering';
|
|
88
|
+
import { ScenesManagement } from '@matter/types/clusters/scenes-management';
|
|
88
89
|
export var DeviceClasses;
|
|
89
90
|
(function (DeviceClasses) {
|
|
90
91
|
DeviceClasses["Node"] = "Node";
|
|
@@ -238,7 +239,7 @@ export const pumpDevice = DeviceTypeDefinition({
|
|
|
238
239
|
deviceClass: DeviceClasses.Simple,
|
|
239
240
|
revision: 3,
|
|
240
241
|
requiredServerClusters: [OnOff.Cluster.id, PumpConfigurationAndControl.Cluster.id, Identify.Cluster.id],
|
|
241
|
-
optionalServerClusters: [LevelControl.Cluster.id, Groups.Cluster.id, TemperatureMeasurement.Cluster.id, PressureMeasurement.Cluster.id, FlowMeasurement.Cluster.id],
|
|
242
|
+
optionalServerClusters: [LevelControl.Cluster.id, Groups.Cluster.id, ScenesManagement.Cluster.id, TemperatureMeasurement.Cluster.id, PressureMeasurement.Cluster.id, FlowMeasurement.Cluster.id],
|
|
242
243
|
});
|
|
243
244
|
export const waterValve = DeviceTypeDefinition({
|
|
244
245
|
name: 'MA-waterValve',
|
|
@@ -254,7 +255,7 @@ export const onOffSwitch = DeviceTypeDefinition({
|
|
|
254
255
|
deviceClass: DeviceClasses.Simple,
|
|
255
256
|
revision: 3,
|
|
256
257
|
requiredServerClusters: [Identify.Cluster.id, OnOff.Cluster.id],
|
|
257
|
-
optionalServerClusters: [Groups.Cluster.id],
|
|
258
|
+
optionalServerClusters: [Groups.Cluster.id, ScenesManagement.Cluster.id],
|
|
258
259
|
});
|
|
259
260
|
export const dimmableSwitch = DeviceTypeDefinition({
|
|
260
261
|
name: 'MA-dimmableswitch',
|
|
@@ -262,15 +263,15 @@ export const dimmableSwitch = DeviceTypeDefinition({
|
|
|
262
263
|
deviceClass: DeviceClasses.Simple,
|
|
263
264
|
revision: 3,
|
|
264
265
|
requiredServerClusters: [Identify.Cluster.id, OnOff.Cluster.id, LevelControl.Cluster.id],
|
|
265
|
-
optionalServerClusters: [Groups.Cluster.id],
|
|
266
|
+
optionalServerClusters: [Groups.Cluster.id, ScenesManagement.Cluster.id],
|
|
266
267
|
});
|
|
267
268
|
export const colorTemperatureSwitch = DeviceTypeDefinition({
|
|
268
269
|
name: 'MA-colortemperatureswitch',
|
|
269
270
|
code: 0x0105,
|
|
270
271
|
deviceClass: DeviceClasses.Simple,
|
|
271
272
|
revision: 3,
|
|
272
|
-
requiredServerClusters: [Identify.Cluster.id,
|
|
273
|
-
optionalServerClusters: [Groups.Cluster.id],
|
|
273
|
+
requiredServerClusters: [Identify.Cluster.id, OnOff.Cluster.id, LevelControl.Cluster.id, ColorControl.Cluster.id],
|
|
274
|
+
optionalServerClusters: [Groups.Cluster.id, ScenesManagement.Cluster.id],
|
|
274
275
|
});
|
|
275
276
|
export const genericSwitch = DeviceTypeDefinition({
|
|
276
277
|
name: 'MA-genericswitch',
|
|
@@ -390,7 +391,7 @@ export const doorLockDevice = DeviceTypeDefinition({
|
|
|
390
391
|
deviceClass: DeviceClasses.Simple,
|
|
391
392
|
revision: 3,
|
|
392
393
|
requiredServerClusters: [Identify.Cluster.id, DoorLock.Cluster.id],
|
|
393
|
-
optionalServerClusters: [],
|
|
394
|
+
optionalServerClusters: [ScenesManagement.Cluster.id, Groups.Cluster.id],
|
|
394
395
|
});
|
|
395
396
|
export const coverDevice = DeviceTypeDefinition({
|
|
396
397
|
name: 'MA-windowCovering',
|
|
@@ -496,8 +497,8 @@ export const refrigerator = DeviceTypeDefinition({
|
|
|
496
497
|
code: 0x70,
|
|
497
498
|
deviceClass: DeviceClasses.Simple,
|
|
498
499
|
revision: 2,
|
|
499
|
-
requiredServerClusters: [
|
|
500
|
-
optionalServerClusters: [],
|
|
500
|
+
requiredServerClusters: [],
|
|
501
|
+
optionalServerClusters: [Identify.Cluster.id, RefrigeratorAndTemperatureControlledCabinetMode.Cluster.id, RefrigeratorAlarm.Cluster.id],
|
|
501
502
|
});
|
|
502
503
|
export const airConditioner = DeviceTypeDefinition({
|
|
503
504
|
name: 'MA-airConditioner',
|
|
@@ -505,13 +506,7 @@ export const airConditioner = DeviceTypeDefinition({
|
|
|
505
506
|
deviceClass: DeviceClasses.Simple,
|
|
506
507
|
revision: 2,
|
|
507
508
|
requiredServerClusters: [Identify.Cluster.id, OnOff.Cluster.id, Thermostat.Cluster.id],
|
|
508
|
-
optionalServerClusters: [
|
|
509
|
-
Groups.Cluster.id,
|
|
510
|
-
FanControl.Cluster.id,
|
|
511
|
-
ThermostatUserInterfaceConfiguration.Cluster.id,
|
|
512
|
-
TemperatureMeasurement.Cluster.id,
|
|
513
|
-
RelativeHumidityMeasurement.Cluster.id,
|
|
514
|
-
],
|
|
509
|
+
optionalServerClusters: [Groups.Cluster.id, ScenesManagement.Cluster.id, FanControl.Cluster.id, ThermostatUserInterfaceConfiguration.Cluster.id, TemperatureMeasurement.Cluster.id, RelativeHumidityMeasurement.Cluster.id],
|
|
515
510
|
});
|
|
516
511
|
export const temperatureControlledCabinetCooler = DeviceTypeDefinition({
|
|
517
512
|
name: 'MA-temperaturecontrolledcabinetcooler',
|
|
@@ -550,8 +545,8 @@ export const cookSurface = DeviceTypeDefinition({
|
|
|
550
545
|
code: 0x77,
|
|
551
546
|
deviceClass: DeviceClasses.Simple,
|
|
552
547
|
revision: 1,
|
|
553
|
-
requiredServerClusters: [
|
|
554
|
-
optionalServerClusters: [OnOff.Cluster.id],
|
|
548
|
+
requiredServerClusters: [],
|
|
549
|
+
optionalServerClusters: [TemperatureControl.Cluster.id, TemperatureMeasurement.Cluster.id, OnOff.Cluster.id],
|
|
555
550
|
});
|
|
556
551
|
export const cooktop = DeviceTypeDefinition({
|
|
557
552
|
name: 'MA-cooktop',
|
|
@@ -566,8 +561,8 @@ export const oven = DeviceTypeDefinition({
|
|
|
566
561
|
code: 0x7b,
|
|
567
562
|
deviceClass: DeviceClasses.Simple,
|
|
568
563
|
revision: 2,
|
|
569
|
-
requiredServerClusters: [
|
|
570
|
-
optionalServerClusters: [],
|
|
564
|
+
requiredServerClusters: [],
|
|
565
|
+
optionalServerClusters: [Identify.Cluster.id],
|
|
571
566
|
});
|
|
572
567
|
export const extractorHood = DeviceTypeDefinition({
|
|
573
568
|
name: 'MA-extractorhood',
|
|
@@ -62,7 +62,7 @@ import { FanControlServer } from '@matter/node/behaviors/fan-control';
|
|
|
62
62
|
import { ThermostatUserInterfaceConfigurationServer } from '@matter/node/behaviors/thermostat-user-interface-configuration';
|
|
63
63
|
import { isValidNumber, isValidObject, isValidString } from './utils/isvalid.js';
|
|
64
64
|
import { MatterbridgeServer, MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeLiftTiltWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeSwitchServer, MatterbridgeOperationalStateServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeHepaFilterMonitoringServer, MatterbridgeEnhancedColorControlServer, MatterbridgePowerSourceServer, } from './matterbridgeBehaviors.js';
|
|
65
|
-
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getApparentElectricalPowerMeasurementClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, getDefaultPowerSourceWiredClusterServer, getDefaultPowerSourceReplaceableBatteryClusterServer, getDefaultPowerSourceRechargeableBatteryClusterServer, getDefaultDeviceEnergyManagementClusterServer, getDefaultDeviceEnergyManagementModeClusterServer, } from './matterbridgeEndpointHelpers.js';
|
|
65
|
+
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, createUniqueId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getDefaultOperationalStateClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getApparentElectricalPowerMeasurementClusterServer, lowercaseFirstLetter, updateAttribute, getClusterId, getAttributeId, setAttribute, getAttribute, checkNotLatinCharacters, generateUniqueId, subscribeAttribute, invokeBehaviorCommand, triggerEvent, featuresFor, getDefaultPowerSourceWiredClusterServer, getDefaultPowerSourceReplaceableBatteryClusterServer, getDefaultPowerSourceRechargeableBatteryClusterServer, getDefaultDeviceEnergyManagementClusterServer, getDefaultDeviceEnergyManagementModeClusterServer, getDefaultPowerSourceBatteryClusterServer, } from './matterbridgeEndpointHelpers.js';
|
|
66
66
|
export class MatterbridgeEndpoint extends Endpoint {
|
|
67
67
|
static logLevel = "info";
|
|
68
68
|
mode = undefined;
|
|
@@ -82,14 +82,16 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
82
82
|
hardwareVersion = undefined;
|
|
83
83
|
hardwareVersionString = undefined;
|
|
84
84
|
productUrl = 'https://www.npmjs.com/package/matterbridge';
|
|
85
|
+
tagList = undefined;
|
|
86
|
+
originalId = undefined;
|
|
87
|
+
uniqueStorageKey = undefined;
|
|
85
88
|
name = undefined;
|
|
86
89
|
deviceType = undefined;
|
|
87
|
-
uniqueStorageKey = undefined;
|
|
88
|
-
tagList = undefined;
|
|
89
90
|
deviceTypes = new Map();
|
|
90
91
|
commandHandler = new NamedHandler();
|
|
91
92
|
constructor(definition, options = {}, debug = false) {
|
|
92
93
|
let deviceTypeList = [];
|
|
94
|
+
const originalId = options.id;
|
|
93
95
|
let firstDefinition;
|
|
94
96
|
if (Array.isArray(definition)) {
|
|
95
97
|
firstDefinition = definition[0];
|
|
@@ -127,19 +129,14 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
127
129
|
options.id = generateUniqueId(options.id);
|
|
128
130
|
}
|
|
129
131
|
const optionsV8 = {
|
|
130
|
-
id: options.uniqueStorageKey?.replace(/[ .]/g, ''),
|
|
131
|
-
number: options.endpointId,
|
|
132
|
+
id: options.id?.replace(/[ .]/g, '') || options.uniqueStorageKey?.replace(/[ .]/g, ''),
|
|
133
|
+
number: options.number || options.endpointId,
|
|
132
134
|
descriptor: options.tagList ? { tagList: options.tagList, deviceTypeList } : { deviceTypeList },
|
|
133
135
|
};
|
|
134
|
-
if (options.id !== undefined) {
|
|
135
|
-
optionsV8.id = options.id.replace(/[ .]/g, '');
|
|
136
|
-
}
|
|
137
|
-
if (options.number !== undefined) {
|
|
138
|
-
optionsV8.number = options.number;
|
|
139
|
-
}
|
|
140
136
|
super(endpointV8, optionsV8);
|
|
141
137
|
this.mode = options.mode;
|
|
142
|
-
this.uniqueStorageKey = options.id
|
|
138
|
+
this.uniqueStorageKey = options.id ?? options.uniqueStorageKey;
|
|
139
|
+
this.originalId = originalId;
|
|
143
140
|
this.name = firstDefinition.name;
|
|
144
141
|
this.deviceType = firstDefinition.code;
|
|
145
142
|
this.tagList = options.tagList;
|
|
@@ -150,7 +147,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
150
147
|
}
|
|
151
148
|
else
|
|
152
149
|
this.deviceTypes.set(firstDefinition.code, firstDefinition);
|
|
153
|
-
this.log = new AnsiLogger({ logName:
|
|
150
|
+
this.log = new AnsiLogger({ logName: this.originalId ?? this.uniqueStorageKey ?? 'MatterbridgeEndpoint', logTimestampFormat: 4, logLevel: debug === true ? "debug" : MatterbridgeEndpoint.logLevel });
|
|
154
151
|
this.log.debug(`${YELLOW}new${db} MatterbridgeEndpoint: ${zb}${'0x' + firstDefinition.code.toString(16).padStart(4, '0')}${db}-${zb}${firstDefinition.name}${db} mode: ${CYAN}${this.mode}${db} id: ${CYAN}${optionsV8.id}${db} number: ${CYAN}${optionsV8.number}${db} taglist: ${CYAN}${options.tagList ? debugStringify(options.tagList) : 'undefined'}${db}`);
|
|
155
152
|
this.behaviors.require(MatterbridgeServer, { log: this.log, commandHandler: this.commandHandler });
|
|
156
153
|
}
|
|
@@ -264,10 +261,10 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
264
261
|
for (const tag of options.tagList) {
|
|
265
262
|
this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`);
|
|
266
263
|
}
|
|
267
|
-
child = new MatterbridgeEndpoint(definition, {
|
|
264
|
+
child = new MatterbridgeEndpoint(definition, { id: endpointName, number: options.number, tagList: options.tagList }, debug);
|
|
268
265
|
}
|
|
269
266
|
else {
|
|
270
|
-
child = new MatterbridgeEndpoint(definition, {
|
|
267
|
+
child = new MatterbridgeEndpoint(definition, { id: endpointName, number: options.number }, debug);
|
|
271
268
|
}
|
|
272
269
|
}
|
|
273
270
|
if (Array.isArray(definition)) {
|
|
@@ -303,10 +300,10 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
303
300
|
for (const tag of options.tagList) {
|
|
304
301
|
this.log.debug(`- with tagList: mfgCode ${CYAN}${tag.mfgCode}${db} namespaceId ${CYAN}${tag.namespaceId}${db} tag ${CYAN}${tag.tag}${db} label ${CYAN}${tag.label}${db}`);
|
|
305
302
|
}
|
|
306
|
-
child = new MatterbridgeEndpoint(definition, {
|
|
303
|
+
child = new MatterbridgeEndpoint(definition, { id: endpointName, number: options.number, tagList: options.tagList }, debug);
|
|
307
304
|
}
|
|
308
305
|
else {
|
|
309
|
-
child = new MatterbridgeEndpoint(definition, {
|
|
306
|
+
child = new MatterbridgeEndpoint(definition, { id: endpointName, number: options.number }, debug);
|
|
310
307
|
}
|
|
311
308
|
}
|
|
312
309
|
if (Array.isArray(definition)) {
|
|
@@ -362,7 +359,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
362
359
|
return Array.from(this.parts);
|
|
363
360
|
}
|
|
364
361
|
static serialize(device) {
|
|
365
|
-
if (!device.serialNumber || !device.deviceName || !device.uniqueId)
|
|
362
|
+
if (!device.serialNumber || !device.deviceName || !device.uniqueId || !device.maybeId || !device.maybeNumber)
|
|
366
363
|
return;
|
|
367
364
|
const serialized = {
|
|
368
365
|
pluginName: device.plugin ?? '',
|
|
@@ -374,8 +371,8 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
374
371
|
vendorId: device.vendorId,
|
|
375
372
|
vendorName: device.vendorName,
|
|
376
373
|
deviceTypes: Array.from(device.deviceTypes.values()),
|
|
377
|
-
|
|
378
|
-
|
|
374
|
+
id: device.id,
|
|
375
|
+
number: device.number,
|
|
379
376
|
clusterServersId: [],
|
|
380
377
|
};
|
|
381
378
|
Object.keys(device.behaviors.supported).forEach((behaviorName) => {
|
|
@@ -387,7 +384,7 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
387
384
|
return serialized;
|
|
388
385
|
}
|
|
389
386
|
static deserialize(serializedDevice) {
|
|
390
|
-
const device = new MatterbridgeEndpoint(serializedDevice.deviceTypes, {
|
|
387
|
+
const device = new MatterbridgeEndpoint(serializedDevice.deviceTypes, { id: serializedDevice.id, number: serializedDevice.number }, false);
|
|
391
388
|
device.plugin = serializedDevice.pluginName;
|
|
392
389
|
device.deviceName = serializedDevice.deviceName;
|
|
393
390
|
device.serialNumber = serializedDevice.serialNumber;
|
|
@@ -408,6 +405,10 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
408
405
|
this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Wired), getDefaultPowerSourceWiredClusterServer(wiredCurrentType));
|
|
409
406
|
return this;
|
|
410
407
|
}
|
|
408
|
+
createDefaultPowerSourceBatteryClusterServer(batPercentRemaining = null, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = null, batReplaceability = PowerSource.BatReplaceability.Unspecified) {
|
|
409
|
+
this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery), getDefaultPowerSourceBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplaceability));
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
411
412
|
createDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplacementDescription = 'Battery type', batQuantity = 1, batReplaceability = PowerSource.BatReplaceability.UserReplaceable) {
|
|
412
413
|
this.behaviors.require(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Replaceable), getDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining, batChargeLevel, batVoltage, batReplacementDescription, batQuantity, batReplaceability));
|
|
413
414
|
return this;
|
|
@@ -946,7 +947,6 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
946
947
|
actuatorEnabled: false,
|
|
947
948
|
operatingMode: DoorLock.OperatingMode.Normal,
|
|
948
949
|
supportedOperatingModes: { normal: false, vacation: true, privacy: true, noRemoteLockUnlock: true, passage: true, alwaysSet: 2047 },
|
|
949
|
-
alarmMask: { lockJammed: false, lockFactoryReset: false, lockRadioPowerCycled: false, wrongCodeEntryLimit: false, frontEscutcheonRemoved: false, doorForcedOpen: false },
|
|
950
950
|
});
|
|
951
951
|
return this;
|
|
952
952
|
}
|
|
@@ -78,11 +78,10 @@ import { Pm25ConcentrationMeasurementServer } from '@matter/node/behaviors/pm25-
|
|
|
78
78
|
import { Pm10ConcentrationMeasurementServer } from '@matter/node/behaviors/pm10-concentration-measurement';
|
|
79
79
|
import { RadonConcentrationMeasurementServer } from '@matter/node/behaviors/radon-concentration-measurement';
|
|
80
80
|
import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from '@matter/node/behaviors/total-volatile-organic-compounds-concentration-measurement';
|
|
81
|
-
import { DeviceEnergyManagementServer } from '@matter/node/behaviors/device-energy-management';
|
|
82
81
|
import { deepCopy } from './utils/deepCopy.js';
|
|
83
82
|
import { deepEqual } from './utils/deepEqual.js';
|
|
84
83
|
import { isValidArray } from './utils/isvalid.js';
|
|
85
|
-
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeOperationalStateServer,
|
|
84
|
+
import { MatterbridgeIdentifyServer, MatterbridgeOnOffServer, MatterbridgeLevelControlServer, MatterbridgeColorControlServer, MatterbridgeLiftWindowCoveringServer, MatterbridgeThermostatServer, MatterbridgeFanControlServer, MatterbridgeDoorLockServer, MatterbridgeModeSelectServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeOperationalStateServer, MatterbridgePowerSourceServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeDeviceEnergyManagementModeServer, } from './matterbridgeBehaviors.js';
|
|
86
85
|
export function capitalizeFirstLetter(name) {
|
|
87
86
|
if (!name)
|
|
88
87
|
return name;
|
|
@@ -216,7 +215,7 @@ export function getBehaviourTypeFromClusterServerId(clusterId) {
|
|
|
216
215
|
if (clusterId === TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id)
|
|
217
216
|
return TotalVolatileOrganicCompoundsConcentrationMeasurementServer.with('NumericMeasurement');
|
|
218
217
|
if (clusterId === DeviceEnergyManagement.Cluster.id)
|
|
219
|
-
return
|
|
218
|
+
return MatterbridgeDeviceEnergyManagementServer.with('PowerForecastReporting');
|
|
220
219
|
if (clusterId === DeviceEnergyManagementMode.Cluster.id)
|
|
221
220
|
return MatterbridgeDeviceEnergyManagementModeServer;
|
|
222
221
|
return MatterbridgeIdentifyServer;
|
|
@@ -257,21 +256,21 @@ export async function invokeBehaviorCommand(endpoint, cluster, command, params)
|
|
|
257
256
|
}
|
|
258
257
|
export async function invokeSubscribeHandler(endpoint, cluster, attribute, newValue, oldValue) {
|
|
259
258
|
const event = attribute + '$Changed';
|
|
260
|
-
const
|
|
261
|
-
if (!
|
|
259
|
+
const behaviorId = getBehavior(endpoint, cluster)?.id;
|
|
260
|
+
if (!behaviorId) {
|
|
262
261
|
endpoint.log.error(`invokeSubscribeHandler ${hk}${event}${er} error: cluster not found on endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er}`);
|
|
263
262
|
return false;
|
|
264
263
|
}
|
|
265
264
|
if (endpoint.construction.status !== Lifecycle.Status.Active) {
|
|
266
|
-
endpoint.log.error(`invokeSubscribeHandler ${hk}${
|
|
265
|
+
endpoint.log.error(`invokeSubscribeHandler ${hk}${behaviorId}.${event}${er} error: Endpoint ${or}${endpoint.maybeId}${er}:${or}${endpoint.maybeNumber}${er} is in the ${BLUE}${endpoint.construction.status}${er} state`);
|
|
267
266
|
return false;
|
|
268
267
|
}
|
|
269
268
|
const events = endpoint.events;
|
|
270
|
-
if (!(
|
|
271
|
-
endpoint.log.error(`invokeSubscribeHandler ${hk}${event}${er} error: cluster ${
|
|
269
|
+
if (!(behaviorId in events) || !(event in events[behaviorId])) {
|
|
270
|
+
endpoint.log.error(`invokeSubscribeHandler ${hk}${event}${er} error: cluster ${behaviorId} not found on endpoint ${or}${endpoint.id}${er}:${or}${endpoint.number}${er}`);
|
|
272
271
|
return false;
|
|
273
272
|
}
|
|
274
|
-
await endpoint.act((agent) => agent[
|
|
273
|
+
await endpoint.act((agent) => agent[behaviorId].events[event].emit(newValue, oldValue, { ...agent.context, offline: false }));
|
|
275
274
|
return true;
|
|
276
275
|
}
|
|
277
276
|
export function addRequiredClusterServers(endpoint) {
|
|
@@ -549,6 +548,19 @@ export function getDefaultPowerSourceWiredClusterServer(wiredCurrentType = Power
|
|
|
549
548
|
wiredCurrentType,
|
|
550
549
|
});
|
|
551
550
|
}
|
|
551
|
+
export function getDefaultPowerSourceBatteryClusterServer(batPercentRemaining = null, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = null, batReplaceability = PowerSource.BatReplaceability.Unspecified) {
|
|
552
|
+
return optionsFor(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery), {
|
|
553
|
+
status: PowerSource.PowerSourceStatus.Active,
|
|
554
|
+
order: 0,
|
|
555
|
+
description: 'Primary battery',
|
|
556
|
+
endpointList: [],
|
|
557
|
+
batVoltage,
|
|
558
|
+
batPercentRemaining: batPercentRemaining !== null ? Math.min(Math.max(batPercentRemaining * 2, 0), 200) : null,
|
|
559
|
+
batChargeLevel,
|
|
560
|
+
batReplacementNeeded: false,
|
|
561
|
+
batReplaceability,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
552
564
|
export function getDefaultPowerSourceReplaceableBatteryClusterServer(batPercentRemaining = 100, batChargeLevel = PowerSource.BatChargeLevel.Ok, batVoltage = 1500, batReplacementDescription = 'Battery type', batQuantity = 1, batReplaceability = PowerSource.BatReplaceability.UserReplaceable) {
|
|
553
565
|
return optionsFor(MatterbridgePowerSourceServer.with(PowerSource.Feature.Battery, PowerSource.Feature.Replaceable), {
|
|
554
566
|
status: PowerSource.PowerSourceStatus.Active,
|
|
@@ -690,7 +702,7 @@ export function getDefaultDeviceEnergyManagementClusterServer(esaType = DeviceEn
|
|
|
690
702
|
});
|
|
691
703
|
}
|
|
692
704
|
export function getDefaultDeviceEnergyManagementModeClusterServer(currentMode, supportedModes) {
|
|
693
|
-
return optionsFor(MatterbridgeDeviceEnergyManagementModeServer, {
|
|
705
|
+
return optionsFor(MatterbridgeDeviceEnergyManagementModeServer.with(), {
|
|
694
706
|
supportedModes: supportedModes ?? [
|
|
695
707
|
{ label: 'No Energy Management (Forecast reporting only)', mode: 1, modeTags: [{ value: DeviceEnergyManagementMode.ModeTag.NoOptimization }] },
|
|
696
708
|
{
|
|
@@ -714,7 +726,7 @@ export function getDefaultDeviceEnergyManagementModeClusterServer(currentMode, s
|
|
|
714
726
|
});
|
|
715
727
|
}
|
|
716
728
|
export function getDefaultOperationalStateClusterServer(operationalState = OperationalState.OperationalStateEnum.Stopped) {
|
|
717
|
-
return optionsFor(MatterbridgeOperationalStateServer, {
|
|
729
|
+
return optionsFor(MatterbridgeOperationalStateServer.with(), {
|
|
718
730
|
phaseList: [],
|
|
719
731
|
currentPhase: null,
|
|
720
732
|
countdownTime: null,
|
|
@@ -104,6 +104,20 @@ export class MatterbridgePlatform {
|
|
|
104
104
|
}
|
|
105
105
|
this.matterbridge.plugins.saveConfigFromJson(plugin, config);
|
|
106
106
|
}
|
|
107
|
+
getSchema() {
|
|
108
|
+
const plugin = this.matterbridge.plugins.get(this.name);
|
|
109
|
+
if (!plugin || !isValidObject(plugin.schemaJson)) {
|
|
110
|
+
throw new Error(`Plugin ${this.name} not found`);
|
|
111
|
+
}
|
|
112
|
+
return plugin.schemaJson;
|
|
113
|
+
}
|
|
114
|
+
setSchema(schema) {
|
|
115
|
+
const plugin = this.matterbridge.plugins.get(this.name);
|
|
116
|
+
if (!plugin) {
|
|
117
|
+
throw new Error(`Plugin ${this.name} not found`);
|
|
118
|
+
}
|
|
119
|
+
plugin.schemaJson = schema;
|
|
120
|
+
}
|
|
107
121
|
wssSendRestartRequired(snackbar = true, fixed = false) {
|
|
108
122
|
this.matterbridge.frontend.wssSendRestartRequired(snackbar, fixed);
|
|
109
123
|
}
|
|
@@ -124,12 +138,15 @@ export class MatterbridgePlatform {
|
|
|
124
138
|
if (aggregator) {
|
|
125
139
|
if (aggregator.parts.has(name.replaceAll(' ', '') + ':' + type)) {
|
|
126
140
|
this.log.warn(`Virtual device ${name} already registered. Please use a different name.`);
|
|
141
|
+
return false;
|
|
127
142
|
}
|
|
128
143
|
else {
|
|
129
144
|
await addVirtualDevice(aggregator, name.slice(0, 32), type, callback);
|
|
130
145
|
this.log.info(`Virtual device ${name} created.`);
|
|
146
|
+
return true;
|
|
131
147
|
}
|
|
132
148
|
}
|
|
149
|
+
return false;
|
|
133
150
|
}
|
|
134
151
|
async registerDevice(device) {
|
|
135
152
|
device.plugin = this.name;
|
package/dist/pluginManager.js
CHANGED
|
@@ -776,7 +776,7 @@ export class PluginManager extends EventEmitter {
|
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
778
|
catch (err) {
|
|
779
|
-
|
|
779
|
+
inspectError(this.log, `Failed to load plugin ${plg}${plugin.name}${er}`, err);
|
|
780
780
|
plugin.error = true;
|
|
781
781
|
}
|
|
782
782
|
return undefined;
|
package/dist/utils/error.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
import { RESET } from 'node-ansi-logger';
|
|
3
3
|
export function logError(log, message, error) {
|
|
4
|
-
log.error(`${message}: ${error instanceof Error ? error.message + '\nStack
|
|
4
|
+
log.error(`${message}: ${error instanceof Error ? error.message + ' \nStack: \n' + error.stack : error}`);
|
|
5
5
|
}
|
|
6
6
|
export function inspectError(log, message, error) {
|
|
7
|
-
const errorMessage = error instanceof Error ? `${error.message}\n` : '';
|
|
7
|
+
const errorMessage = error instanceof Error ? `${error.message} \n` : '';
|
|
8
8
|
const inspectedError = inspect(error, { depth: 10, colors: true, showHidden: false });
|
|
9
9
|
log.error(`${message}: ${errorMessage}${RESET}${inspectedError}`);
|
|
10
10
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.8-dev-20251114-9b65e59",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.3.
|
|
9
|
+
"version": "3.3.8-dev-20251114-9b65e59",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.15.6",
|
|
@@ -367,9 +367,9 @@
|
|
|
367
367
|
"license": "MIT"
|
|
368
368
|
},
|
|
369
369
|
"node_modules/bare-events": {
|
|
370
|
-
"version": "2.8.
|
|
371
|
-
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.
|
|
372
|
-
"integrity": "sha512-
|
|
370
|
+
"version": "2.8.2",
|
|
371
|
+
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
|
|
372
|
+
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
|
|
373
373
|
"license": "Apache-2.0",
|
|
374
374
|
"peerDependencies": {
|
|
375
375
|
"bare-abort-controller": "*"
|
|
@@ -1507,9 +1507,9 @@
|
|
|
1507
1507
|
}
|
|
1508
1508
|
},
|
|
1509
1509
|
"node_modules/path-scurry": {
|
|
1510
|
-
"version": "2.0.
|
|
1511
|
-
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.
|
|
1512
|
-
"integrity": "sha512-
|
|
1510
|
+
"version": "2.0.1",
|
|
1511
|
+
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
|
|
1512
|
+
"integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
|
|
1513
1513
|
"license": "BlueOak-1.0.0",
|
|
1514
1514
|
"dependencies": {
|
|
1515
1515
|
"lru-cache": "^11.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.8-dev-20251114-9b65e59",
|
|
4
4
|
"description": "Matterbridge plugin manager for Matter",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -65,6 +65,10 @@
|
|
|
65
65
|
"import": "./dist/utils/export.js",
|
|
66
66
|
"types": "./dist/utils/export.d.ts"
|
|
67
67
|
},
|
|
68
|
+
"./jestutils": {
|
|
69
|
+
"import": "./dist/jestutils/export.js",
|
|
70
|
+
"types": "./dist/jestutils/export.d.ts"
|
|
71
|
+
},
|
|
68
72
|
"./logger": {
|
|
69
73
|
"import": "./dist/logger/export.js",
|
|
70
74
|
"types": "./dist/logger/export.d.ts"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join as pathJoin, sep as pathSep } from 'node:path';
|
|
4
|
+
import https from 'node:https';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fetch the canonical Matter ZCL JSON file from the connectedhomeip repository
|
|
8
|
+
* and store it locally for offline / deterministic revision checks.
|
|
9
|
+
*
|
|
10
|
+
* Environment overrides:
|
|
11
|
+
* - ZCL_OUT: output path for zcl.json (default: chip/zcl.json)
|
|
12
|
+
* - ZCL_BRANCH: connectedhomeip branch to fetch from (default: v1.4-branch)
|
|
13
|
+
*/
|
|
14
|
+
const OUT_PATH = process.env.ZCL_OUT || 'chip/zcl.json';
|
|
15
|
+
// Single branch strategy from online only
|
|
16
|
+
const BRANCH = process.env.ZCL_BRANCH || 'v1.4-branch';
|
|
17
|
+
const ZCL_BASE = `https://raw.githubusercontent.com/project-chip/connectedhomeip/${BRANCH}/src/app/zap-templates/zcl/`;
|
|
18
|
+
const ZCL_JSON_URL = ZCL_BASE + 'zcl.json';
|
|
19
|
+
const XML_BASE = ZCL_BASE + 'data-model/chip/';
|
|
20
|
+
const MANUFACTURERS_URL = ZCL_BASE + 'data-model/manufacturers.xml';
|
|
21
|
+
|
|
22
|
+
function fetchUrl(url) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
https.get(url, (res) => {
|
|
25
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
26
|
+
// Follow redirect
|
|
27
|
+
return resolve(fetchUrl(res.headers.location));
|
|
28
|
+
}
|
|
29
|
+
if (res.statusCode !== 200) {
|
|
30
|
+
return reject(new Error(`Failed to fetch ${url}: ${res.statusCode}`));
|
|
31
|
+
}
|
|
32
|
+
const chunks = [];
|
|
33
|
+
res.on('data', (c) => chunks.push(c));
|
|
34
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
35
|
+
}).on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
await mkdir(dirname(OUT_PATH), { recursive: true });
|
|
41
|
+
// Always fetch online from the specified branch
|
|
42
|
+
const data = await fetchUrl(ZCL_JSON_URL);
|
|
43
|
+
|
|
44
|
+
// Structural sanity check: this is a ZAP ZCL properties file, expected to include xml roots and files
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(data);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
throw new Error(`Downloaded content is not valid JSON: ${e.message}`);
|
|
50
|
+
}
|
|
51
|
+
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.xmlFile)) {
|
|
52
|
+
process.stderr.write('Warning: zcl.json does not contain expected ZAP ZCL properties (xmlFile). Saving anyway for manual inspection.\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await writeFile(OUT_PATH, JSON.stringify(parsed, null, 2));
|
|
56
|
+
process.stderr.write(`Saved ${OUT_PATH}.\n`);
|
|
57
|
+
|
|
58
|
+
// Also fetch manufacturers.xml to chip/manufacturers.xml from the same branch
|
|
59
|
+
try {
|
|
60
|
+
const outManu = 'chip/manufacturers.xml';
|
|
61
|
+
const manuContent = await fetchUrl(MANUFACTURERS_URL);
|
|
62
|
+
await writeFile(outManu, manuContent);
|
|
63
|
+
process.stderr.write('Saved chip/manufacturers.xml.\n');
|
|
64
|
+
} catch (e) {
|
|
65
|
+
process.stderr.write(`Warning: failed to fetch manufacturers.xml (${e.message}).\n`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Always fetch XMLs referenced by zcl.json from the same branch under data-model/chip
|
|
69
|
+
const xmlFiles = Array.isArray(parsed.xmlFile) ? parsed.xmlFile : [];
|
|
70
|
+
if (xmlFiles.length === 0) {
|
|
71
|
+
process.stderr.write('No xmlFile entries found; skipping XML fetch.\n');
|
|
72
|
+
} else {
|
|
73
|
+
const outXmlBase = 'chip/xml';
|
|
74
|
+
await mkdir(outXmlBase, { recursive: true });
|
|
75
|
+
let ok = 0;
|
|
76
|
+
let fail = 0;
|
|
77
|
+
for (const fileName of xmlFiles) {
|
|
78
|
+
const relative = fileName.trim();
|
|
79
|
+
try {
|
|
80
|
+
const url = XML_BASE + relative;
|
|
81
|
+
const content = await fetchUrl(url);
|
|
82
|
+
const outPath = pathJoin(outXmlBase, relative.replaceAll('/', pathSep));
|
|
83
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
84
|
+
await writeFile(outPath, content);
|
|
85
|
+
ok++;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
fail++;
|
|
88
|
+
process.stderr.write(`Failed XML: ${relative} (${e.message})\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
process.stderr.write(`Fetched XML files: ${ok} ok, ${fail} failed. Output under ${outXmlBase}. Branch=${BRANCH}\n`);
|
|
92
|
+
}
|
|
93
|
+
// Ensure success exit unless an unhandled error occurred
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch((err) => {
|
|
98
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
});
|