matterbridge 3.0.5-dev-20250526-422f029 → 3.0.5-dev-20250528-9314890
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 +3 -0
- package/dist/cli.js +67 -11
- package/dist/laundryWasher.js +28 -25
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,9 @@ If you like this project and find it useful, please consider giving it a star on
|
|
|
12
12
|
|
|
13
13
|
### Added
|
|
14
14
|
|
|
15
|
+
- [cli]: Added takeHeapSnapshot() and triggerGarbageCollection().
|
|
16
|
+
- [LaundryWasher]: Add LaundryWasher class and Jest test.
|
|
17
|
+
|
|
15
18
|
### Changed
|
|
16
19
|
|
|
17
20
|
### Fixed
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { inspect } from 'node:util';
|
|
|
7
7
|
export const cliEmitter = new EventEmitter();
|
|
8
8
|
export let instance;
|
|
9
9
|
let session;
|
|
10
|
+
let snapshotInterval;
|
|
10
11
|
let memoryCheckInterval;
|
|
11
12
|
let prevCpus;
|
|
12
13
|
export let lastCpuUsage = 0;
|
|
@@ -99,17 +100,26 @@ async function stopCpuMemoryCheck() {
|
|
|
99
100
|
}
|
|
100
101
|
async function startInspector() {
|
|
101
102
|
const { Session } = await import('node:inspector');
|
|
102
|
-
|
|
103
|
+
const { mkdirSync } = await import('node:fs');
|
|
104
|
+
log.debug(`***Starting heap sampling...`);
|
|
105
|
+
mkdirSync('heap_profile', { recursive: true });
|
|
103
106
|
try {
|
|
104
107
|
session = new Session();
|
|
105
108
|
session.connect();
|
|
106
109
|
await new Promise((resolve, reject) => {
|
|
107
110
|
session?.post('HeapProfiler.startSampling', (err) => (err ? reject(err) : resolve()));
|
|
108
111
|
});
|
|
109
|
-
log.debug(
|
|
112
|
+
log.debug(`***Started heap sampling`);
|
|
113
|
+
const interval = getIntParameter('snapshotinterval');
|
|
114
|
+
if (interval && interval >= 30000) {
|
|
115
|
+
log.debug(`***Started heap snapshot interval of ${CYAN}${interval}${db} ms`);
|
|
116
|
+
snapshotInterval = setInterval(async () => {
|
|
117
|
+
await takeHeapSnapshot();
|
|
118
|
+
}, interval);
|
|
119
|
+
}
|
|
110
120
|
}
|
|
111
121
|
catch (err) {
|
|
112
|
-
log.error(
|
|
122
|
+
log.error(`***Failed to start heap sampling: ${err instanceof Error ? err.message : err}`);
|
|
113
123
|
session?.disconnect();
|
|
114
124
|
session = undefined;
|
|
115
125
|
return;
|
|
@@ -117,9 +127,15 @@ async function startInspector() {
|
|
|
117
127
|
}
|
|
118
128
|
async function stopInspector() {
|
|
119
129
|
const { writeFileSync } = await import('node:fs');
|
|
120
|
-
|
|
130
|
+
const path = await import('node:path');
|
|
131
|
+
log.debug(`***Stopping heap sampling...`);
|
|
132
|
+
if (snapshotInterval) {
|
|
133
|
+
log.debug(`***Clearing heap snapshot interval...`);
|
|
134
|
+
clearInterval(snapshotInterval);
|
|
135
|
+
await takeHeapSnapshot();
|
|
136
|
+
}
|
|
121
137
|
if (!session) {
|
|
122
|
-
log.error('No active inspector session.');
|
|
138
|
+
log.error('***No active inspector session.');
|
|
123
139
|
return;
|
|
124
140
|
}
|
|
125
141
|
try {
|
|
@@ -127,16 +143,56 @@ async function stopInspector() {
|
|
|
127
143
|
session?.post('HeapProfiler.stopSampling', (err, result) => (err ? reject(err) : resolve(result)));
|
|
128
144
|
});
|
|
129
145
|
const profile = JSON.stringify(result.profile);
|
|
130
|
-
|
|
131
|
-
|
|
146
|
+
const filename = path.join('heap_profile', `Heap-profile-${new Date().toISOString().replace(/[:]/g, '-')}.heapprofile`);
|
|
147
|
+
writeFileSync(filename, profile);
|
|
148
|
+
log.debug(`***Heap sampling profile saved to ${CYAN}${filename}${db}`);
|
|
132
149
|
}
|
|
133
150
|
catch (err) {
|
|
134
|
-
log.error(
|
|
151
|
+
log.error(`***Failed to stop heap sampling: ${err instanceof Error ? err.message : err}`);
|
|
135
152
|
}
|
|
136
153
|
finally {
|
|
137
154
|
session.disconnect();
|
|
138
155
|
session = undefined;
|
|
139
|
-
log.debug(
|
|
156
|
+
log.debug(`***Stopped heap sampling`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function takeHeapSnapshot() {
|
|
160
|
+
const { writeFileSync } = await import('node:fs');
|
|
161
|
+
const path = await import('node:path');
|
|
162
|
+
const filename = path.join('heap_profile', `Heap-snapshot-${new Date().toISOString().replace(/[:]/g, '-')}.heapsnapshot`);
|
|
163
|
+
if (!session) {
|
|
164
|
+
log.error('No active inspector session.');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const chunks = [];
|
|
168
|
+
const chunksListener = (notification) => {
|
|
169
|
+
chunks.push(Buffer.from(notification.params.chunk));
|
|
170
|
+
};
|
|
171
|
+
session.on('HeapProfiler.addHeapSnapshotChunk', chunksListener);
|
|
172
|
+
await new Promise((resolve) => {
|
|
173
|
+
session?.post('HeapProfiler.takeHeapSnapshot', (err) => {
|
|
174
|
+
if (!err) {
|
|
175
|
+
session?.off('HeapProfiler.addHeapSnapshotChunk', chunksListener);
|
|
176
|
+
writeFileSync(filename, Buffer.concat(chunks));
|
|
177
|
+
log.debug(`***Heap sampling snapshot saved to ${CYAN}${filename}${db}`);
|
|
178
|
+
triggerGarbageCollection();
|
|
179
|
+
resolve();
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
session?.off('HeapProfiler.addHeapSnapshotChunk', chunksListener);
|
|
183
|
+
log.error(`***Failed to take heap snapshot: ${err instanceof Error ? err.message : err}`);
|
|
184
|
+
resolve();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function triggerGarbageCollection() {
|
|
190
|
+
if (typeof global.gc === 'function') {
|
|
191
|
+
global.gc();
|
|
192
|
+
log.debug('***Manual garbage collection triggered via global.gc().');
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
log.debug('***Garbage collection is not exposed. Start Node.js with --expose-gc to enable manual GC.');
|
|
140
196
|
}
|
|
141
197
|
}
|
|
142
198
|
function registerHandlers() {
|
|
@@ -172,11 +228,11 @@ async function update() {
|
|
|
172
228
|
}
|
|
173
229
|
async function start() {
|
|
174
230
|
log.debug('Received start memory check event');
|
|
175
|
-
startCpuMemoryCheck();
|
|
231
|
+
await startCpuMemoryCheck();
|
|
176
232
|
}
|
|
177
233
|
async function stop() {
|
|
178
234
|
log.debug('Received stop memory check event');
|
|
179
|
-
stopCpuMemoryCheck();
|
|
235
|
+
await stopCpuMemoryCheck();
|
|
180
236
|
}
|
|
181
237
|
async function main() {
|
|
182
238
|
log.debug(`Cli main() started`);
|
package/dist/laundryWasher.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { OperationalState } from '@matter/main/clusters/operational-state';
|
|
2
1
|
import { LaundryWasherControls } from '@matter/main/clusters/laundry-washer-controls';
|
|
3
2
|
import { LaundryWasherMode } from '@matter/main/clusters/laundry-washer-mode';
|
|
4
3
|
import { TemperatureControl } from '@matter/main/clusters/temperature-control';
|
|
@@ -10,25 +9,19 @@ import { laundryWasher } from './matterbridgeDeviceTypes.js';
|
|
|
10
9
|
import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
|
|
11
10
|
import { MatterbridgeOnOffServer, MatterbridgeServer } from './matterbridgeBehaviors.js';
|
|
12
11
|
export class LaundryWasher extends MatterbridgeEndpoint {
|
|
13
|
-
constructor(name, serial) {
|
|
12
|
+
constructor(name, serial, currentMode, supportedModes, spinSpeedCurrent, spinSpeeds, numberOfRinses, supportedRinses, selectedTemperatureLevel, supportedTemperatureLevels, temperatureSetpoint, minTemperature, maxTemperature, step, operationalState) {
|
|
14
13
|
super(laundryWasher, { uniqueStorageKey: `${name.replaceAll(' ', '')}-${serial.replaceAll(' ', '')}` }, true);
|
|
15
14
|
this.createDefaultIdentifyClusterServer();
|
|
16
15
|
this.createDefaultBasicInformationClusterServer(name, serial, 0xfff1, 'Matterbridge', 0x8000, 'Matterbridge Laundry Washer');
|
|
17
16
|
this.createDefaultPowerSourceWiredClusterServer();
|
|
18
|
-
this.createDeadFrontOnOffClusterServer();
|
|
19
|
-
this.
|
|
20
|
-
this.createDefaultLaundryWasherControlsClusterServer();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
spinSpeeds,
|
|
27
|
-
spinSpeedCurrent,
|
|
28
|
-
supportedRinses,
|
|
29
|
-
numberOfRinses,
|
|
30
|
-
});
|
|
31
|
-
return this;
|
|
17
|
+
this.createDeadFrontOnOffClusterServer(true);
|
|
18
|
+
this.createDefaultLaundryWasherModeClusterServer(currentMode, supportedModes);
|
|
19
|
+
this.createDefaultLaundryWasherControlsClusterServer(spinSpeedCurrent, spinSpeeds, numberOfRinses, supportedRinses);
|
|
20
|
+
if (temperatureSetpoint)
|
|
21
|
+
this.createNumberTemperatureControlClusterServer(temperatureSetpoint, minTemperature, maxTemperature, step);
|
|
22
|
+
else
|
|
23
|
+
this.createLevelTemperatureControlClusterServer(selectedTemperatureLevel, supportedTemperatureLevels);
|
|
24
|
+
this.createDefaultOperationalStateClusterServer(operationalState);
|
|
32
25
|
}
|
|
33
26
|
createDefaultLaundryWasherModeClusterServer(currentMode = 2, supportedModes = [
|
|
34
27
|
{ label: 'Delicate', mode: 1, modeTags: [{ value: LaundryWasherMode.ModeTag.Delicate }] },
|
|
@@ -42,7 +35,16 @@ export class LaundryWasher extends MatterbridgeEndpoint {
|
|
|
42
35
|
});
|
|
43
36
|
return this;
|
|
44
37
|
}
|
|
45
|
-
|
|
38
|
+
createDefaultLaundryWasherControlsClusterServer(spinSpeedCurrent = 2, spinSpeeds = ['400', '800', '1200', '1600'], numberOfRinses = LaundryWasherControls.NumberOfRinses.Normal, supportedRinses = [LaundryWasherControls.NumberOfRinses.None, LaundryWasherControls.NumberOfRinses.Normal, LaundryWasherControls.NumberOfRinses.Max, LaundryWasherControls.NumberOfRinses.Extra]) {
|
|
39
|
+
this.behaviors.require(LaundryWasherControlsServer.with(LaundryWasherControls.Feature.Spin, LaundryWasherControls.Feature.Rinse), {
|
|
40
|
+
spinSpeeds,
|
|
41
|
+
spinSpeedCurrent,
|
|
42
|
+
supportedRinses,
|
|
43
|
+
numberOfRinses,
|
|
44
|
+
});
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
createLevelTemperatureControlClusterServer(selectedTemperatureLevel = 1, supportedTemperatureLevels = ['Cold', 'Warm', 'Hot', '30°', '40°', '60°', '80°']) {
|
|
46
48
|
this.behaviors.require(MatterbridgeLevelTemperatureControlServer.with(TemperatureControl.Feature.TemperatureLevel), {
|
|
47
49
|
selectedTemperatureLevel,
|
|
48
50
|
supportedTemperatureLevels,
|
|
@@ -59,11 +61,11 @@ export class LaundryWasher extends MatterbridgeEndpoint {
|
|
|
59
61
|
return this;
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
|
-
class MatterbridgeLevelTemperatureControlServer extends TemperatureControlServer.with(TemperatureControl.Feature.TemperatureLevel) {
|
|
64
|
+
export class MatterbridgeLevelTemperatureControlServer extends TemperatureControlServer.with(TemperatureControl.Feature.TemperatureLevel) {
|
|
63
65
|
initialize() {
|
|
64
66
|
if (this.state.supportedTemperatureLevels.length >= 2) {
|
|
65
67
|
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
66
|
-
device.log.info(
|
|
68
|
+
device.log.info(`MatterbridgeLevelTemperatureControlServer initialized with selectedTemperatureLevel ${this.state.selectedTemperatureLevel} and supportedTemperatureLevels: ${this.state.supportedTemperatureLevels.join(', ')}`);
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
setTemperature(request) {
|
|
@@ -77,10 +79,10 @@ class MatterbridgeLevelTemperatureControlServer extends TemperatureControlServer
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
|
-
class MatterbridgeNumberTemperatureControlServer extends TemperatureControlServer.with(TemperatureControl.Feature.TemperatureNumber) {
|
|
82
|
+
export class MatterbridgeNumberTemperatureControlServer extends TemperatureControlServer.with(TemperatureControl.Feature.TemperatureNumber, TemperatureControl.Feature.TemperatureStep) {
|
|
81
83
|
initialize() {
|
|
82
84
|
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
83
|
-
device.log.info(
|
|
85
|
+
device.log.info(`MatterbridgeNumberTemperatureControlServer initialized with temperatureSetpoint ${this.state.temperatureSetpoint} minTemperature ${this.state.minTemperature} maxTemperature ${this.state.maxTemperature} step ${this.state.step}`);
|
|
84
86
|
}
|
|
85
87
|
setTemperature(request) {
|
|
86
88
|
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
@@ -93,10 +95,10 @@ class MatterbridgeNumberTemperatureControlServer extends TemperatureControlServe
|
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
|
-
class MatterbridgeLaundryWasherModeServer extends LaundryWasherModeServer {
|
|
98
|
+
export class MatterbridgeLaundryWasherModeServer extends LaundryWasherModeServer {
|
|
97
99
|
initialize() {
|
|
98
100
|
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
99
|
-
device.log.info(`
|
|
101
|
+
device.log.info(`MatterbridgeLaundryWasherModeServer initialized: currentMode is ${this.state.currentMode}`);
|
|
100
102
|
this.reactTo(this.agent.get(MatterbridgeOnOffServer).events.onOff$Changed, this.handleOnOffChange);
|
|
101
103
|
}
|
|
102
104
|
handleOnOffChange(onOff) {
|
|
@@ -110,12 +112,13 @@ class MatterbridgeLaundryWasherModeServer extends LaundryWasherModeServer {
|
|
|
110
112
|
const device = this.endpoint.stateOf(MatterbridgeServer).deviceCommand;
|
|
111
113
|
const supportedMode = this.state.supportedModes.find((supportedMode) => supportedMode.mode === request.newMode);
|
|
112
114
|
if (supportedMode) {
|
|
113
|
-
device.log.info(`
|
|
115
|
+
device.log.info(`MatterbridgeLaundryWasherModeServer: changeToMode called with mode ${supportedMode.mode} => ${supportedMode.label}`);
|
|
116
|
+
device.changeToMode({ newMode: request.newMode });
|
|
114
117
|
this.state.currentMode = request.newMode;
|
|
115
118
|
return { status: ModeBase.ModeChangeStatus.Success, statusText: 'Success' };
|
|
116
119
|
}
|
|
117
120
|
else {
|
|
118
|
-
device.log.error(`
|
|
121
|
+
device.log.error(`MatterbridgeLaundryWasherModeServer: changeToMode called with invalid mode ${request.newMode}`);
|
|
119
122
|
return { status: ModeBase.ModeChangeStatus.InvalidInMode, statusText: 'Invalid mode' };
|
|
120
123
|
}
|
|
121
124
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge",
|
|
3
|
-
"version": "3.0.5-dev-
|
|
3
|
+
"version": "3.0.5-dev-20250528-9314890",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge",
|
|
9
|
-
"version": "3.0.5-dev-
|
|
9
|
+
"version": "3.0.5-dev-20250528-9314890",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@matter/main": "0.13.0",
|
package/package.json
CHANGED