@matter/examples 0.13.0-alpha.0-20250413-d5a27700d → 0.13.0-alpha.0-20250418-8cfc0b832

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.
Files changed (33) hide show
  1. package/dist/esm/controller/ControllerNode.js +3 -2
  2. package/dist/esm/controller/ControllerNode.js.map +1 -1
  3. package/dist/esm/device-air-quality-sensor/AirQualitySensorDeviceNode.js +68 -0
  4. package/dist/esm/device-air-quality-sensor/AirQualitySensorDeviceNode.js.map +6 -0
  5. package/dist/esm/device-onoff-advanced/DeviceNodeFull.js +1 -0
  6. package/dist/esm/device-onoff-advanced/DeviceNodeFull.js.map +1 -1
  7. package/dist/esm/device-robotic-vacuum-cleaner/RoboticVacuumCleanerDevice.js +138 -0
  8. package/dist/esm/device-robotic-vacuum-cleaner/RoboticVacuumCleanerDevice.js.map +6 -0
  9. package/dist/esm/device-robotic-vacuum-cleaner/RvcDeviceLogic.js +368 -0
  10. package/dist/esm/device-robotic-vacuum-cleaner/RvcDeviceLogic.js.map +6 -0
  11. package/dist/esm/device-robotic-vacuum-cleaner/behaviors/CustomRvcCleanModeServer.js +35 -0
  12. package/dist/esm/device-robotic-vacuum-cleaner/behaviors/CustomRvcCleanModeServer.js.map +6 -0
  13. package/dist/esm/device-robotic-vacuum-cleaner/behaviors/CustomRvcOperationalStateServer.js +60 -0
  14. package/dist/esm/device-robotic-vacuum-cleaner/behaviors/CustomRvcOperationalStateServer.js.map +6 -0
  15. package/dist/esm/device-robotic-vacuum-cleaner/behaviors/CustomRvcRunModeServer.js +34 -0
  16. package/dist/esm/device-robotic-vacuum-cleaner/behaviors/CustomRvcRunModeServer.js.map +6 -0
  17. package/dist/esm/device-smoke-co-alarm/SmokeCOAlarmDeviceNode.js +41 -0
  18. package/dist/esm/device-smoke-co-alarm/SmokeCOAlarmDeviceNode.js.map +6 -0
  19. package/package.json +9 -5
  20. package/src/controller/ControllerNode.ts +3 -2
  21. package/src/controller/README.md +3 -3
  22. package/src/device-air-quality-sensor/AirQualitySensorDeviceNode.ts +87 -0
  23. package/src/device-air-quality-sensor/README.md +14 -0
  24. package/src/device-onoff-advanced/DeviceNodeFull.ts +2 -1
  25. package/src/device-robotic-vacuum-cleaner/README.md +22 -0
  26. package/src/device-robotic-vacuum-cleaner/RoboticVacuumCleanerDevice.ts +158 -0
  27. package/src/device-robotic-vacuum-cleaner/RvcDeviceLogic.ts +421 -0
  28. package/src/device-robotic-vacuum-cleaner/behaviors/CustomRvcCleanModeServer.ts +39 -0
  29. package/src/device-robotic-vacuum-cleaner/behaviors/CustomRvcOperationalStateServer.ts +79 -0
  30. package/src/device-robotic-vacuum-cleaner/behaviors/CustomRvcRunModeServer.ts +39 -0
  31. package/src/device-smoke-co-alarm/README.md +14 -0
  32. package/src/device-smoke-co-alarm/SmokeCOAlarmDeviceNode.ts +60 -0
  33. package/src/tsconfig.json +3 -0
@@ -0,0 +1,421 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { Behavior, Logger, Node, Time, Timer } from "@matter/main";
7
+ import { ServiceAreaServer } from "@matter/main/behaviors/service-area";
8
+ import { RvcOperationalState } from "@matter/main/clusters/rvc-operational-state";
9
+ import { CustomRvcCleanModeServer } from "./behaviors/CustomRvcCleanModeServer.js";
10
+ import { CustomRvcOperationalStateServer } from "./behaviors/CustomRvcOperationalStateServer.js";
11
+ import { CustomRvcRunModeServer } from "./behaviors/CustomRvcRunModeServer.js";
12
+
13
+ const logger = Logger.get("RvcDeviceLogic");
14
+
15
+ /** Define the effective run modes for our robotic vacuum cleaner */
16
+ export enum RvcDeviceRunModes {
17
+ /** Do nothing, wait for user action. */
18
+ Idle = 0,
19
+
20
+ /** Cleans every 3h for 3 cycles of 10 minutes with 3 minutes recharging in between. */
21
+ AutoCleaning = 1,
22
+
23
+ /** Cleans once for 10 minutes, recharge afterward, then switch to Idle. */
24
+ QuickCleaning = 2,
25
+
26
+ /** Does one mapping round of 10 minutes and recharges afterward, then switch to AutoCleaning mode. */
27
+ Mapping = 3,
28
+ }
29
+
30
+ export enum RvcDeviceCleanMode {
31
+ Vacuuming = 0,
32
+ Mopping = 1,
33
+ }
34
+
35
+ export enum CleaningAreas {
36
+ Kitchen = 0,
37
+ LivingRoom = 1,
38
+ Bedroom = 2,
39
+ Bathroom = 3,
40
+ }
41
+
42
+ /**
43
+ * Defines the desired operational states of the device.
44
+ */
45
+ enum DesiredOperationalDeviceState {
46
+ Idle = 0,
47
+ Paused = 1,
48
+ Working = 2,
49
+ }
50
+
51
+ enum DetailOperationalDeviceState {
52
+ SeekingCharger = 0,
53
+ Docked = 1,
54
+ Charging = 2,
55
+ Cleaning = 3,
56
+ Mapping = 4,
57
+ }
58
+
59
+ // We simulate cleaning rounds of 10 minutes
60
+ const CLEANING_MAPPING_ROUND_TIME = 10 * 60_000;
61
+
62
+ /**
63
+ * This class implements the Logic of our fictional robotic vacuum cleaner device and should demonstrate how the
64
+ * different clusters work together and how their data depend on each other.
65
+ * It is added as a custom Behavior to the device and can so easily access the other behaviors and clusters.
66
+ */
67
+ export class RvcDeviceLogic extends Behavior {
68
+ static override readonly id = "rvcDeviceLogic";
69
+ static override readonly early = true;
70
+
71
+ declare internal: RvcDeviceLogic.Internal;
72
+
73
+ override async initialize() {
74
+ // Delay setting up all the listeners to make sure we have a clean state
75
+ this.reactTo((this.endpoint as Node).lifecycle.partsReady, this.#initializeNode);
76
+
77
+ // Timers to simulate a device state machine
78
+ this.internal.cleaningTimer = Time.getTimer("CleaningTimer", CLEANING_MAPPING_ROUND_TIME, () =>
79
+ this.#handleCleaningDone(),
80
+ );
81
+ this.internal.mappingTimer = Time.getTimer("MappingTimer", CLEANING_MAPPING_ROUND_TIME, () =>
82
+ this.#handleMappingDone(),
83
+ );
84
+ this.internal.rechargingTimer = Time.getTimer("RechargingTimer", 2 * 60_000, () => this.#handleRechargeDone());
85
+ this.internal.autoWaitTimer = Time.getTimer("AutoWaitTimer", 180 * 60_000, () => this.#handleAutoWaitDone());
86
+ this.internal.seekingChargerTimer = Time.getTimer("SeekingChargerTimer", 30_000, () => this.#handleDocking());
87
+ }
88
+
89
+ async #initializeNode() {
90
+ // React to actions from the controller when changing the run mode or triggering a pause, resume or go home commands.
91
+ // `this.agent.load()` ensures that the behavior is initialized and ready to use.
92
+ const runMode = await this.agent.load(CustomRvcRunModeServer);
93
+ this.reactTo(runMode.events.currentMode$Changed, this.#handleRvcModeChange, {
94
+ offline: true,
95
+ });
96
+
97
+ // React to command calls from the controller when changing the operational state.
98
+ const operationalState = await this.agent.load(CustomRvcOperationalStateServer);
99
+ this.reactTo(operationalState.events.pauseTriggered, this.#handlePause);
100
+ this.reactTo(operationalState.events.resumeTriggered, this.#handleResume);
101
+ this.reactTo(operationalState.events.goHomeTriggered, this.#handleGoHome);
102
+
103
+ // React to the service area changes from the controller.
104
+ const serviceArea = await this.agent.load(ServiceAreaServer);
105
+ this.reactTo(serviceArea.events.selectedAreas$Changed, this.#selectedAreasChange);
106
+ }
107
+
108
+ /**
109
+ * When the run mode is changed, start the relevant timers based on the mode and clean mode
110
+ */
111
+ async #handleRvcModeChange(newMode: RvcDeviceRunModes, oldMode: RvcDeviceRunModes) {
112
+ logger.info(`RVC mode changed from ${RvcDeviceRunModes[oldMode]} to ${RvcDeviceRunModes[newMode]}`);
113
+ this.internal.runMode = newMode;
114
+ switch (newMode) {
115
+ case RvcDeviceRunModes.Idle:
116
+ await this.#returnToIdleMode();
117
+ break;
118
+ case RvcDeviceRunModes.AutoCleaning:
119
+ case RvcDeviceRunModes.QuickCleaning:
120
+ await this.#startCleaningMode(newMode);
121
+ break;
122
+ case RvcDeviceRunModes.Mapping:
123
+ await this.#startMappingMode();
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Returns the device to idle mode by switching the desired operational state to idle.
129
+ * This means that also after recharging nothing happens.
130
+ * This stops all timers and sets the device to seeking charger mode if it is cleaning or mapping.
131
+ */
132
+ async #returnToIdleMode() {
133
+ this.internal.autoWaitTimer.stop();
134
+ this.internal.mappingTimer.stop();
135
+ this.internal.cleaningTimer.stop();
136
+ await this.endpoint.setStateOf(ServiceAreaServer, {
137
+ currentArea: null,
138
+ });
139
+ this.internal.cleaningRoundsLeft = 0;
140
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Idle;
141
+ if (
142
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Cleaning ||
143
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Mapping
144
+ ) {
145
+ logger.info(
146
+ `Switching to Idle mode. Cancelling ${DetailOperationalDeviceState[this.internal.detailOperationalState]} and returning to dock now ...`,
147
+ );
148
+ this.internal.detailOperationalState = DetailOperationalDeviceState.SeekingCharger;
149
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
150
+ operationalState: RvcOperationalState.OperationalState.SeekingCharger,
151
+ });
152
+ this.internal.seekingChargerTimer.start();
153
+ } else {
154
+ logger.info("Switch to Idle mode");
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Starts the cleaning mode.
160
+ * If the device is not docked, it will wait until it is docked to start cleaning.
161
+ * If the cleaning ist started in AutoCleaning mode, it will do three rounds of cleaning, in QuickCleaning mode
162
+ * just one round. If action was paused it will continue with the planned rounds.
163
+ */
164
+ async #startCleaningMode(mode: RvcDeviceRunModes) {
165
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Working;
166
+ if (
167
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Docked ||
168
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Cleaning ||
169
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Mapping
170
+ ) {
171
+ this.internal.detailOperationalState = DetailOperationalDeviceState.Cleaning;
172
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
173
+ operationalState: RvcOperationalState.OperationalState.Running,
174
+ });
175
+ if (this.internal.cleaningRoundsLeft === 0) {
176
+ this.internal.cleaningRoundsLeft = mode === RvcDeviceRunModes.AutoCleaning ? 3 : 1;
177
+ }
178
+ this.internal.cleaningTimer.start();
179
+ await this.#startServiceAreaChange();
180
+ const cleanMode = this.endpoint.stateOf(CustomRvcCleanModeServer).currentMode;
181
+ logger.info(
182
+ `Starting ${RvcDeviceCleanMode[cleanMode]} ${RvcDeviceRunModes[mode]} (${this.internal.cleaningRoundsLeft} round(s)) ...`,
183
+ );
184
+ } else {
185
+ logger.info(
186
+ `Delayed start of cleaning with mode ${RvcDeviceRunModes[mode]} until docked (currently ${DetailOperationalDeviceState[this.internal.detailOperationalState]})`,
187
+ );
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Starts the mapping mode.
193
+ * If the device is not docked, it will wait until it is docked to start mapping.
194
+ */
195
+ async #startMappingMode() {
196
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Working;
197
+ if (this.internal.detailOperationalState === DetailOperationalDeviceState.Docked) {
198
+ this.internal.detailOperationalState = DetailOperationalDeviceState.Mapping;
199
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
200
+ operationalState: RvcOperationalState.OperationalState.Running,
201
+ });
202
+ this.internal.mappingTimer.start();
203
+ await this.#startServiceAreaChange();
204
+ logger.info(`Starting mapping ...`);
205
+ } else {
206
+ logger.info(
207
+ `Delayed start of mapping until docked (currently ${DetailOperationalDeviceState[this.internal.detailOperationalState]})`,
208
+ );
209
+ }
210
+ }
211
+
212
+ /** Handles a Pause command and pauses the current action, but remembers the current mode. */
213
+ async #handlePause() {
214
+ if (this.internal.desiredOperationalState === DesiredOperationalDeviceState.Working) {
215
+ const cleanMode = this.endpoint.stateOf(CustomRvcCleanModeServer).currentMode;
216
+ logger.info(
217
+ `Pausing current action ${RvcDeviceCleanMode[cleanMode]} ${RvcDeviceRunModes[this.internal.runMode]}...`,
218
+ );
219
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Paused;
220
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
221
+ operationalState: RvcOperationalState.OperationalState.Paused,
222
+ });
223
+ this.internal.mappingTimer.stop();
224
+ this.internal.cleaningTimer.stop();
225
+ }
226
+ }
227
+
228
+ /** Handles a Resume command and resumes the current action in the previous mode. */
229
+ async #handleResume() {
230
+ if (this.internal.desiredOperationalState === DesiredOperationalDeviceState.Paused) {
231
+ const cleanMode = this.endpoint.stateOf(CustomRvcCleanModeServer).currentMode;
232
+ logger.info(
233
+ `Resuming current action ${RvcDeviceCleanMode[cleanMode]} ${RvcDeviceRunModes[this.internal.runMode]}...`,
234
+ );
235
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Working;
236
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
237
+ operationalState: RvcOperationalState.OperationalState.Running,
238
+ });
239
+ switch (this.internal.runMode) {
240
+ case RvcDeviceRunModes.AutoCleaning:
241
+ case RvcDeviceRunModes.QuickCleaning:
242
+ await this.#startCleaningMode(this.internal.runMode);
243
+ break;
244
+ case RvcDeviceRunModes.Mapping:
245
+ await this.#startMappingMode();
246
+ break;
247
+ }
248
+ }
249
+ }
250
+
251
+ /** Handles a GoHome command and returns the device to the dock and cancels all activities. */
252
+ async #handleGoHome() {
253
+ if (this.internal.desiredOperationalState !== DesiredOperationalDeviceState.Idle) {
254
+ this.internal.cleaningRoundsLeft = 0;
255
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Idle;
256
+ if (
257
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Cleaning ||
258
+ this.internal.detailOperationalState === DetailOperationalDeviceState.Mapping
259
+ ) {
260
+ this.internal.cleaningTimer.stop();
261
+ this.internal.mappingTimer.stop();
262
+ await this.endpoint.setStateOf(ServiceAreaServer, {
263
+ currentArea: null,
264
+ });
265
+ const cleanMode = this.endpoint.stateOf(CustomRvcCleanModeServer).currentMode;
266
+ logger.info(
267
+ `Returning to dock from ${RvcDeviceCleanMode[cleanMode]} ${DetailOperationalDeviceState[this.internal.detailOperationalState]} ...`,
268
+ );
269
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
270
+ operationalState: RvcOperationalState.OperationalState.SeekingCharger,
271
+ });
272
+ this.internal.seekingChargerTimer.start();
273
+ }
274
+ }
275
+ }
276
+
277
+ /** Handles the end of one cleaning round and sends the device back to the charging station. */
278
+ async #handleCleaningDone() {
279
+ this.internal.cleaningRoundsLeft--;
280
+ logger.info(
281
+ `Cleaning run done. ${this.internal.cleaningRoundsLeft} rounds left. Returning to dock for recharge...`,
282
+ );
283
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
284
+ operationalState: RvcOperationalState.OperationalState.SeekingCharger,
285
+ });
286
+ this.internal.seekingChargerTimer.start();
287
+ }
288
+
289
+ /** Handles the end of one mapping round and sends the device back to the charging station. */
290
+ async #handleMappingDone() {
291
+ logger.info(
292
+ `Mapping run done. ${this.internal.cleaningRoundsLeft} rounds left. Returning to dock for recharge...`,
293
+ );
294
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
295
+ operationalState: RvcOperationalState.OperationalState.SeekingCharger,
296
+ });
297
+ this.internal.seekingChargerTimer.start();
298
+ }
299
+
300
+ /** handles the return of the device to the charger and starts the charging cycle. */
301
+ async #handleDocking() {
302
+ logger.info(
303
+ `Docked successfully. Recharging now ... ${this.internal.cleaningRoundsLeft} cleaning rounds left.`,
304
+ );
305
+ this.internal.detailOperationalState = DetailOperationalDeviceState.Charging;
306
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
307
+ operationalState: RvcOperationalState.OperationalState.Charging,
308
+ });
309
+ this.internal.rechargingTimer.start();
310
+ if (this.internal.cleaningRoundsLeft === 0) {
311
+ logger.info("Current actions done, switch to Idle Mode ...");
312
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Idle;
313
+ await this.endpoint.setStateOf(CustomRvcRunModeServer, {
314
+ currentMode: RvcDeviceRunModes.Idle,
315
+ });
316
+ }
317
+ }
318
+
319
+ /** Handles the end of the recharging cycle and starts the next cleaning round or goes to idle mode. */
320
+ async #handleRechargeDone() {
321
+ if (this.internal.desiredOperationalState === DesiredOperationalDeviceState.Working) {
322
+ if (this.internal.cleaningRoundsLeft > 0) {
323
+ const cleanMode = this.endpoint.stateOf(CustomRvcCleanModeServer).currentMode;
324
+ logger.info(
325
+ `Recharging done. Executing next ${RvcDeviceCleanMode[cleanMode]} ${DetailOperationalDeviceState[this.internal.detailOperationalState]} round ...`,
326
+ );
327
+ await this.#startCleaningMode(this.internal.runMode);
328
+ return;
329
+ } else if (this.internal.runMode === RvcDeviceRunModes.AutoCleaning) {
330
+ this.internal.autoWaitTimer.start();
331
+ logger.info(`Recharging done. Waiting for next cleaning round in Auto mode ...`);
332
+ } else {
333
+ logger.info(`Recharging done. Switching to Idle mode ...`);
334
+ this.internal.desiredOperationalState = DesiredOperationalDeviceState.Idle;
335
+ await this.endpoint.setStateOf(CustomRvcRunModeServer, {
336
+ currentMode: RvcDeviceRunModes.Idle,
337
+ });
338
+ }
339
+ } else {
340
+ logger.info(`Recharging done. Switching to Idle mode ...`);
341
+ }
342
+ this.internal.detailOperationalState = DetailOperationalDeviceState.Docked;
343
+ await this.endpoint.setStateOf(CustomRvcOperationalStateServer, {
344
+ operationalState: RvcOperationalState.OperationalState.Docked,
345
+ });
346
+ }
347
+
348
+ /** Handles passing the time of the Auto Clean waiting time and starts a new cleaning round. */
349
+ async #handleAutoWaitDone() {
350
+ logger.info(`Auto wait done. Starting next cleaning round ...`);
351
+ await this.#startCleaningMode(this.internal.runMode);
352
+ }
353
+
354
+ /** Logs changed elected areas. */
355
+ #selectedAreasChange(newAreas: number[]) {
356
+ logger.info(
357
+ "Selected areas changed to",
358
+ newAreas.map(area => CleaningAreas[area]),
359
+ );
360
+ }
361
+
362
+ /** Initialize the service area change timer to change the current area while cleaning or mapping. */
363
+ async #startServiceAreaChange() {
364
+ this.internal.serviceAreaChangeTimer?.stop();
365
+
366
+ const selectedAreas = this.endpoint.stateOf(ServiceAreaServer).selectedAreas;
367
+ const numberOfAreas = selectedAreas.length || this.endpoint.stateOf(ServiceAreaServer).supportedAreas.length;
368
+
369
+ this.internal.serviceAreaChangeTimer = Time.getPeriodicTimer(
370
+ "ServiceAreaChangeTimer",
371
+ CLEANING_MAPPING_ROUND_TIME / numberOfAreas + 1000, // Two Areas by default
372
+ () => this.#changeCurrentServiceArea(),
373
+ ).start();
374
+ await this.#changeCurrentServiceArea();
375
+ }
376
+
377
+ /**
378
+ * During Cleaning otr Mapping we simulate that we also work through the selected areas, so we need to change the
379
+ * current area.
380
+ */
381
+ async #changeCurrentServiceArea() {
382
+ const serviceAreaState = this.endpoint.stateOf(ServiceAreaServer);
383
+ const selectedAreas = serviceAreaState.selectedAreas.length
384
+ ? serviceAreaState.selectedAreas
385
+ : serviceAreaState.supportedAreas;
386
+ const currentArea = serviceAreaState.currentArea ?? 0;
387
+ const nextArea = (currentArea + 1) % selectedAreas.length;
388
+ await this.endpoint.setStateOf(ServiceAreaServer, {
389
+ currentArea: nextArea,
390
+ });
391
+ logger.info(`Changed service area to ${CleaningAreas[nextArea]}`);
392
+ }
393
+
394
+ /** When the Endpoint is closed stop all timer. */
395
+ override async [Symbol.asyncDispose]() {
396
+ this.internal.cleaningTimer.stop();
397
+ this.internal.mappingTimer.stop();
398
+ this.internal.rechargingTimer.stop();
399
+ this.internal.autoWaitTimer.stop();
400
+ this.internal.seekingChargerTimer.stop();
401
+ this.internal.serviceAreaChangeTimer?.stop();
402
+
403
+ await super[Symbol.asyncDispose]?.();
404
+ }
405
+ }
406
+
407
+ /** Internal state of the RvcDeviceLogic behavior to store all the timers and such. */
408
+ export namespace RvcDeviceLogic {
409
+ export class Internal {
410
+ desiredOperationalState: DesiredOperationalDeviceState = DesiredOperationalDeviceState.Idle;
411
+ detailOperationalState: DetailOperationalDeviceState = DetailOperationalDeviceState.Docked;
412
+ runMode: RvcDeviceRunModes = RvcDeviceRunModes.Idle;
413
+ cleaningTimer!: Timer;
414
+ mappingTimer!: Timer;
415
+ serviceAreaChangeTimer!: Timer;
416
+ rechargingTimer!: Timer;
417
+ autoWaitTimer!: Timer;
418
+ seekingChargerTimer!: Timer;
419
+ cleaningRoundsLeft: number = 0;
420
+ }
421
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ModeUtils } from "@matter/main/behaviors/mode-base";
8
+ import { RvcCleanModeServer } from "@matter/main/behaviors/rvc-clean-mode";
9
+ import { ModeBase } from "@matter/main/clusters/mode-base";
10
+ import { RvcOperationalState } from "@matter/main/clusters/rvc-operational-state";
11
+ import { CustomRvcOperationalStateServer } from "./CustomRvcOperationalStateServer.js";
12
+
13
+ /**
14
+ * Custom implementation of the RvcCleanModeServer behavior.
15
+ * This special class only prevents changing the clean mode when the device is in the running state.
16
+ * That demonstrates how to handle such cases and is completely fiktional.
17
+ */
18
+ export class CustomRvcCleanModeServer extends RvcCleanModeServer {
19
+ override initialize() {
20
+ super.initialize();
21
+ }
22
+
23
+ /** Change more implementation. We do not call super.changeToMode() here because we want to add own validations. */
24
+ override changeToMode({ newMode }: ModeBase.ChangeToModeRequest): ModeBase.ChangeToModeResponse {
25
+ const result = ModeUtils.assertModeChange(this.state.supportedModes, this.state.currentMode, newMode);
26
+ if (result.status !== ModeBase.ModeChangeStatus.Success) {
27
+ return result;
28
+ }
29
+ const state = this.endpoint.stateOf(CustomRvcOperationalStateServer).operationalState;
30
+ if (state === RvcOperationalState.OperationalState.Running) {
31
+ return {
32
+ status: ModeBase.ModeChangeStatus.InvalidInMode,
33
+ statusText: "Can not switch operative modes while device is operating.",
34
+ };
35
+ }
36
+ this.state.currentMode = newMode; // Update the new mode, needed because we do not use super.changeToMode()
37
+ return result;
38
+ }
39
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { AsyncObservable } from "@matter/main";
7
+ import { OperationalStateUtils } from "@matter/main/behaviors/operational-state";
8
+ import { RvcOperationalStateServer } from "@matter/main/behaviors/rvc-operational-state";
9
+ import { OperationalState } from "@matter/main/clusters/operational-state";
10
+ import { RvcOperationalState } from "@matter/main/clusters/rvc-operational-state";
11
+
12
+ /**
13
+ * This is a custom implementation of {@link RvcOperationalStateServer} that implements the needed methods goHome,
14
+ * pause and resume, that are optional and so can not be implemented in the base class.
15
+ * It also, adds custom events to the server that can be used by the device logic to more easily react on the needed
16
+ * actions.
17
+ */
18
+ export class CustomRvcOperationalStateServer extends RvcOperationalStateServer {
19
+ declare events: CustomRvcOperationalStateServer.Events;
20
+
21
+ override async goHome(): Promise<RvcOperationalState.OperationalCommandResponse> {
22
+ const result = OperationalStateUtils.assertRvcGoHome(this.state.operationalState);
23
+ if (
24
+ result.commandResponseState.errorStateId === OperationalState.ErrorState.NoError &&
25
+ this.state.operationalState !== RvcOperationalState.OperationalState.SeekingCharger
26
+ ) {
27
+ await this.events.goHomeTriggered.emit();
28
+ }
29
+ return result;
30
+ }
31
+
32
+ override async pause(): Promise<RvcOperationalState.OperationalCommandResponse> {
33
+ const result = OperationalStateUtils.assertPause(this.state.operationalState);
34
+ if (
35
+ result.commandResponseState.errorStateId === OperationalState.ErrorState.NoError &&
36
+ this.state.operationalState !== RvcOperationalState.OperationalState.Paused
37
+ ) {
38
+ if (
39
+ this.state.operationalState === RvcOperationalState.OperationalState.Charging ||
40
+ this.state.operationalState === RvcOperationalState.OperationalState.Docked
41
+ ) {
42
+ return {
43
+ commandResponseState: {
44
+ errorStateId: OperationalState.ErrorState.CommandInvalidInState,
45
+ },
46
+ };
47
+ }
48
+ await this.events.pauseTriggered.emit();
49
+ }
50
+ return result;
51
+ }
52
+
53
+ override async resume(): Promise<RvcOperationalState.OperationalCommandResponse> {
54
+ const result = OperationalStateUtils.assertResume(this.state.operationalState);
55
+ if (
56
+ result.commandResponseState.errorStateId === OperationalState.ErrorState.NoError &&
57
+ this.state.operationalState !== RvcOperationalState.OperationalState.Paused
58
+ ) {
59
+ if (this.state.operationalState === RvcOperationalState.OperationalState.SeekingCharger) {
60
+ return {
61
+ commandResponseState: {
62
+ errorStateId: OperationalState.ErrorState.CommandInvalidInState,
63
+ },
64
+ };
65
+ }
66
+ } else {
67
+ await this.events.resumeTriggered.emit();
68
+ }
69
+ return result;
70
+ }
71
+ }
72
+
73
+ export namespace CustomRvcOperationalStateServer {
74
+ export class Events extends RvcOperationalStateServer.Events {
75
+ resumeTriggered = AsyncObservable();
76
+ pauseTriggered = AsyncObservable();
77
+ goHomeTriggered = AsyncObservable();
78
+ }
79
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ModeUtils } from "@matter/main/behaviors/mode-base";
8
+ import { RvcRunModeServer } from "@matter/main/behaviors/rvc-run-mode";
9
+ import { ModeBase } from "@matter/main/clusters/mode-base";
10
+ import { RvcDeviceRunModes } from "../RvcDeviceLogic.js";
11
+
12
+ /**
13
+ * This is a custom implementation of {@link RvcRunModeServer} that makes sure that the Run Mode can not be changed
14
+ * when the device is not in the Idle mode. SO a change from Mapping to Cleaning or such is not allowed without being
15
+ * Idle in between which mainly means that the current operation was stopped.
16
+ * Additionally, the run mode is set to Idle on start of the device to make sure potentially old states sre reset.
17
+ */
18
+ export class CustomRvcRunModeServer extends RvcRunModeServer {
19
+ override initialize() {
20
+ super.initialize();
21
+ this.state.currentMode = RvcDeviceRunModes.Idle; // Always start in Idle mode
22
+ }
23
+
24
+ /** Change more implementation. We do not call super.changeToMode() here because we want to add own validations. */
25
+ override changeToMode({ newMode }: ModeBase.ChangeToModeRequest): ModeBase.ChangeToModeResponse {
26
+ const result = ModeUtils.assertModeChange(this.state.supportedModes, this.state.currentMode, newMode);
27
+ if (result.status !== ModeBase.ModeChangeStatus.Success) {
28
+ return result;
29
+ }
30
+ if (newMode !== RvcDeviceRunModes.Idle && this.state.currentMode !== RvcDeviceRunModes.Idle) {
31
+ return {
32
+ status: ModeBase.ModeChangeStatus.InvalidInMode,
33
+ statusText: `Can not switch operative modes while in a non-idle state (${this.state.currentMode})`,
34
+ };
35
+ }
36
+ this.state.currentMode = newMode; // Update the new mode, needed because we do not use super.changeToMode()
37
+ return result; // Is Success
38
+ }
39
+ }
@@ -0,0 +1,14 @@
1
+ # Smoke CO Alarm Example
2
+
3
+ This example shows how to build a simple Smoke&CO Alarm Sensor Matter device. It just defines initial static values,
4
+ so it won't change over time.
5
+
6
+ ## Usage
7
+
8
+ For general documentation about the CLI parameters or environment variables that can be used for matter.js please refer to the [Examples README](../../../examples/README.md#cli-usage).
9
+
10
+ Execute the following command in the examples root directory
11
+
12
+ ```bash
13
+ npm run matter-smoke-co-alarm
14
+ ```
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @license
4
+ * Copyright 2022-2025 Matter.js Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+
8
+ /**
9
+ * This example shows how to create a simple Smoke&CO Sensor Matter device. It just defines initial static values,
10
+ * so it won't change over time.
11
+ */
12
+
13
+ import { Endpoint, ServerNode } from "@matter/main";
14
+ import { CarbonDioxideConcentrationMeasurementServer } from "@matter/main/behaviors/carbon-dioxide-concentration-measurement";
15
+ import { SmokeCoAlarmServer } from "@matter/main/behaviors/smoke-co-alarm";
16
+ import { ConcentrationMeasurement } from "@matter/main/clusters/concentration-measurement";
17
+ import { SmokeCoAlarmDevice } from "@matter/main/devices/smoke-co-alarm";
18
+
19
+ // Define the endpoint for the device. This is the "thing/device" that will appear on the network.
20
+ const smokeCoAlarmEndpoint = new Endpoint(
21
+ SmokeCoAlarmDevice.with(
22
+ CarbonDioxideConcentrationMeasurementServer.with("NumericMeasurement", "PeakMeasurement", "AverageMeasurement"),
23
+ SmokeCoAlarmServer.with("SmokeAlarm", "CoAlarm"),
24
+ ),
25
+ {
26
+ id: "smokeCoAlarm",
27
+ smokeCoAlarm: {
28
+ /*
29
+ The following values are set as defaults by the SmokeCoAlarmServer class. You can override them here.
30
+ expressedState: SmokeCoAlarm.ExpressedState.Normal,
31
+ smokeState: SmokeCoAlarm.AlarmState.Normal,
32
+ coState: SmokeCoAlarm.AlarmState.Normal,
33
+ batteryAlert: SmokeCoAlarm.AlarmState.Normal,
34
+ testInProgress: false,
35
+ hardwareFaultAlert: false,
36
+ endOfServiceAlert: SmokeCoAlarm.EndOfService.Normal,
37
+ */
38
+ },
39
+ carbonDioxideConcentrationMeasurement: {
40
+ measuredValue: 12.34,
41
+ measurementUnit: ConcentrationMeasurement.MeasurementUnit.Ppm,
42
+ measurementMedium: ConcentrationMeasurement.MeasurementMedium.Air,
43
+ },
44
+ },
45
+ );
46
+
47
+ // Physical devices appear as "nodes" on a Matter network. As a device implementer you use a NodeServer to bring a
48
+ // device online.
49
+ //
50
+ // Note there are a large number of options to NodeServer that we are allowing to take default values here. See
51
+ // IlluminatedRollerShade.ts for a more detailed example.
52
+ const node = await ServerNode.create();
53
+
54
+ // Nodes are a composition of endpoints. Add a single endpoint to the node, our example socket device.
55
+ await node.add(smokeCoAlarmEndpoint);
56
+ // Our device is now built, so we can bring the node online.
57
+ //
58
+ // Note that you may serve multiple nodes from a single process. We only have one, however, so we can use the run()
59
+ // method of the node.
60
+ await node.run();
package/src/tsconfig.json CHANGED
@@ -9,6 +9,9 @@
9
9
  {
10
10
  "path": "../../main/src"
11
11
  },
12
+ {
13
+ "path": "../../matter.js/src"
14
+ },
12
15
  {
13
16
  "path": "../../nodejs-ble/src"
14
17
  },