@matter/create 0.13.0-alpha.0-20250415-475996bb5 → 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.
- package/dist/templates/controller/ControllerNode.ts +3 -2
- package/dist/templates/device-air-quality-sensor/AirQualitySensorDeviceNode.ts +87 -0
- package/dist/templates/device-onoff-advanced/DeviceNodeFull.ts +2 -1
- package/dist/templates/device-robotic-vacuum-cleaner/RoboticVacuumCleanerDevice.ts +158 -0
- package/dist/templates/device-robotic-vacuum-cleaner/RvcDeviceLogic.ts +421 -0
- package/dist/templates/device-robotic-vacuum-cleaner/behaviors/CustomRvcCleanModeServer.ts +39 -0
- package/dist/templates/device-robotic-vacuum-cleaner/behaviors/CustomRvcOperationalStateServer.ts +79 -0
- package/dist/templates/device-robotic-vacuum-cleaner/behaviors/CustomRvcRunModeServer.ts +39 -0
- package/dist/templates/device-smoke-co-alarm/SmokeCOAlarmDeviceNode.ts +60 -0
- package/dist/templates/index.json +38 -14
- package/package.json +2 -2
|
@@ -28,6 +28,7 @@ if (environment.vars.get("ble")) {
|
|
|
28
28
|
Ble.get = singleton(
|
|
29
29
|
() =>
|
|
30
30
|
new NodeJsBle({
|
|
31
|
+
environment,
|
|
31
32
|
hciId: environment.vars.number("ble.hci.id"),
|
|
32
33
|
}),
|
|
33
34
|
);
|
|
@@ -84,11 +85,11 @@ class ControllerNode {
|
|
|
84
85
|
environment.vars.number("longDiscriminator") ??
|
|
85
86
|
(await controllerStorage.get("longDiscriminator", 3840));
|
|
86
87
|
if (longDiscriminator > 4095) throw new Error("Discriminator value must be less than 4096");
|
|
87
|
-
setupPin = environment.vars.number("
|
|
88
|
+
setupPin = environment.vars.number("passcode") ?? (await controllerStorage.get("passcode", 20202021));
|
|
88
89
|
}
|
|
89
90
|
if ((shortDiscriminator === undefined && longDiscriminator === undefined) || setupPin === undefined) {
|
|
90
91
|
throw new Error(
|
|
91
|
-
"Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with
|
|
92
|
+
"Please specify the longDiscriminator of the device to commission with -longDiscriminator or provide a valid passcode with --passcode=xxxxxx",
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
95
|
|
|
@@ -0,0 +1,87 @@
|
|
|
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 Air Quality Sensor Matter device with temperature, humidity, PMx and TVOC
|
|
10
|
+
* measurements. It just defines initial static values, so it won't change over time.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Endpoint, ServerNode } from "@matter/main";
|
|
14
|
+
import { Pm1ConcentrationMeasurementServer } from "@matter/main/behaviors/pm1-concentration-measurement";
|
|
15
|
+
import { Pm10ConcentrationMeasurementServer } from "@matter/main/behaviors/pm10-concentration-measurement";
|
|
16
|
+
import { Pm25ConcentrationMeasurementServer } from "@matter/main/behaviors/pm25-concentration-measurement";
|
|
17
|
+
import { RelativeHumidityMeasurementServer } from "@matter/main/behaviors/relative-humidity-measurement";
|
|
18
|
+
import { TemperatureMeasurementServer } from "@matter/main/behaviors/temperature-measurement";
|
|
19
|
+
import { TotalVolatileOrganicCompoundsConcentrationMeasurementServer } from "@matter/main/behaviors/total-volatile-organic-compounds-concentration-measurement";
|
|
20
|
+
import { AirQuality } from "@matter/main/clusters/air-quality";
|
|
21
|
+
import { ConcentrationMeasurement } from "@matter/main/clusters/concentration-measurement";
|
|
22
|
+
import { AirQualitySensorDevice } from "@matter/main/devices/air-quality-sensor";
|
|
23
|
+
|
|
24
|
+
// Define the endpoint for the device. This is the "thing/device" that will appear on the network.
|
|
25
|
+
const airQualityEndpoint = new Endpoint(
|
|
26
|
+
AirQualitySensorDevice.with(
|
|
27
|
+
TemperatureMeasurementServer,
|
|
28
|
+
RelativeHumidityMeasurementServer,
|
|
29
|
+
Pm1ConcentrationMeasurementServer.with(ConcentrationMeasurement.Feature.NumericMeasurement),
|
|
30
|
+
Pm25ConcentrationMeasurementServer.with(ConcentrationMeasurement.Feature.NumericMeasurement),
|
|
31
|
+
Pm10ConcentrationMeasurementServer.with(ConcentrationMeasurement.Feature.NumericMeasurement),
|
|
32
|
+
TotalVolatileOrganicCompoundsConcentrationMeasurementServer.with(
|
|
33
|
+
ConcentrationMeasurement.Feature.NumericMeasurement,
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
{
|
|
37
|
+
id: "airQuality",
|
|
38
|
+
airQuality: {
|
|
39
|
+
airQuality: AirQuality.AirQualityEnum.Poor,
|
|
40
|
+
},
|
|
41
|
+
relativeHumidityMeasurement: {
|
|
42
|
+
tolerance: 0,
|
|
43
|
+
minMeasuredValue: 0,
|
|
44
|
+
maxMeasuredValue: 100,
|
|
45
|
+
},
|
|
46
|
+
temperatureMeasurement: {
|
|
47
|
+
tolerance: 0,
|
|
48
|
+
minMeasuredValue: 0,
|
|
49
|
+
maxMeasuredValue: 100,
|
|
50
|
+
},
|
|
51
|
+
pm1ConcentrationMeasurement: {
|
|
52
|
+
measuredValue: 12.34,
|
|
53
|
+
measurementUnit: ConcentrationMeasurement.MeasurementUnit.Ppm,
|
|
54
|
+
measurementMedium: ConcentrationMeasurement.MeasurementMedium.Air,
|
|
55
|
+
},
|
|
56
|
+
pm10ConcentrationMeasurement: {
|
|
57
|
+
measuredValue: 12.34,
|
|
58
|
+
measurementUnit: ConcentrationMeasurement.MeasurementUnit.Ppm,
|
|
59
|
+
measurementMedium: ConcentrationMeasurement.MeasurementMedium.Air,
|
|
60
|
+
},
|
|
61
|
+
pm25ConcentrationMeasurement: {
|
|
62
|
+
measuredValue: 12.34,
|
|
63
|
+
measurementUnit: ConcentrationMeasurement.MeasurementUnit.Ppm,
|
|
64
|
+
measurementMedium: ConcentrationMeasurement.MeasurementMedium.Air,
|
|
65
|
+
},
|
|
66
|
+
totalVolatileOrganicCompoundsConcentrationMeasurement: {
|
|
67
|
+
measuredValue: 12.34,
|
|
68
|
+
measurementUnit: ConcentrationMeasurement.MeasurementUnit.Ppm,
|
|
69
|
+
measurementMedium: ConcentrationMeasurement.MeasurementMedium.Air,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Physical devices appear as "nodes" on a Matter network. As a device implementer you use a NodeServer to bring a
|
|
75
|
+
// device online.
|
|
76
|
+
//
|
|
77
|
+
// Note there are a large number of options to NodeServer that we are allowing to take default values here. See
|
|
78
|
+
// IlluminatedRollerShade.ts for a more detailed example.
|
|
79
|
+
const node = await ServerNode.create();
|
|
80
|
+
|
|
81
|
+
// Nodes are a composition of endpoints. Add a single endpoint to the node, our example socket device.
|
|
82
|
+
await node.add(airQualityEndpoint);
|
|
83
|
+
// Our device is now built, so we can bring the node online.
|
|
84
|
+
//
|
|
85
|
+
// Note that you may serve multiple nodes from a single process. We only have one, however, so we can use the run()
|
|
86
|
+
// method of the node.
|
|
87
|
+
await node.run();
|
|
@@ -74,6 +74,7 @@ if (environment.vars.get("ble.enable")) {
|
|
|
74
74
|
Ble.get = singleton(
|
|
75
75
|
() =>
|
|
76
76
|
new NodeJsBle({
|
|
77
|
+
environment,
|
|
77
78
|
hciId: environment.vars.number("ble.hciId"),
|
|
78
79
|
}),
|
|
79
80
|
);
|
|
@@ -381,7 +382,7 @@ logEndpoint(EndpointServer.forEndpoint(server));
|
|
|
381
382
|
/**
|
|
382
383
|
* In order to start the node and announce it into the network we start the node. This method resolves when the Matter
|
|
383
384
|
* node enters his online state. Alternatively, we could also use `await server.run()` which
|
|
384
|
-
* resolves when the node goes offline again, but we want to execute code
|
|
385
|
+
* resolves when the node goes offline again, but we want to execute code afterward, so we use start() here
|
|
385
386
|
*/
|
|
386
387
|
await server.start();
|
|
387
388
|
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
// This demonstrates bringing a "Robotic Vacuum Cleaner" device online with matter.js.
|
|
9
|
+
|
|
10
|
+
import { AreaNamespaceTag, ServerNode } from "@matter/main";
|
|
11
|
+
import { ServiceAreaServer } from "@matter/main/behaviors/service-area";
|
|
12
|
+
import { ModeBase } from "@matter/main/clusters/mode-base";
|
|
13
|
+
import { RvcCleanMode } from "@matter/main/clusters/rvc-clean-mode";
|
|
14
|
+
import { RvcOperationalState } from "@matter/main/clusters/rvc-operational-state";
|
|
15
|
+
import { RvcRunMode } from "@matter/main/clusters/rvc-run-mode";
|
|
16
|
+
import { RoboticVacuumCleanerDevice } from "@matter/main/devices/robotic-vacuum-cleaner";
|
|
17
|
+
import { CleaningAreas, RvcDeviceCleanMode, RvcDeviceLogic, RvcDeviceRunModes } from "./RvcDeviceLogic.js";
|
|
18
|
+
import { CustomRvcCleanModeServer } from "./behaviors/CustomRvcCleanModeServer.js";
|
|
19
|
+
import { CustomRvcOperationalStateServer } from "./behaviors/CustomRvcOperationalStateServer.js";
|
|
20
|
+
import { CustomRvcRunModeServer } from "./behaviors/CustomRvcRunModeServer.js";
|
|
21
|
+
|
|
22
|
+
// Physical devices appear as "nodes" on a Matter network. As a device implementer you use a NodeServer to bring a
|
|
23
|
+
// device online.
|
|
24
|
+
//
|
|
25
|
+
// Note there are a large number of options to NodeServer that we are allowing to take default values here. See
|
|
26
|
+
// IlluminatedRollerShade.ts for a more detailed example.
|
|
27
|
+
const node = await ServerNode.create();
|
|
28
|
+
|
|
29
|
+
// Nodes are a composition of endpoints. Add a single endpoint to the node, our example RoboticVacuumCleanerDevice.
|
|
30
|
+
// THe RVC device has some requirements where multiple clusters need to work together. We are using own Cluster logic
|
|
31
|
+
// for this - please see in the respective Behavior files.
|
|
32
|
+
// The below options also show the available Run Modes, Operational States and Service Areas that the device exposes
|
|
33
|
+
// and their data structures.
|
|
34
|
+
|
|
35
|
+
await node.add(
|
|
36
|
+
RoboticVacuumCleanerDevice.with(
|
|
37
|
+
CustomRvcCleanModeServer,
|
|
38
|
+
CustomRvcOperationalStateServer,
|
|
39
|
+
CustomRvcRunModeServer,
|
|
40
|
+
ServiceAreaServer,
|
|
41
|
+
RvcDeviceLogic,
|
|
42
|
+
),
|
|
43
|
+
{
|
|
44
|
+
id: "robotic-vacuum-cleaner",
|
|
45
|
+
rvcCleanMode: {
|
|
46
|
+
supportedModes: [
|
|
47
|
+
{
|
|
48
|
+
label: "Vacuuming",
|
|
49
|
+
mode: RvcDeviceCleanMode.Vacuuming,
|
|
50
|
+
modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Mopping",
|
|
54
|
+
mode: RvcDeviceCleanMode.Mopping,
|
|
55
|
+
modeTags: [{ value: RvcCleanMode.ModeTag.Mop }],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
// We start with this mode. It will be persisted
|
|
59
|
+
currentMode: RvcDeviceCleanMode.Vacuuming,
|
|
60
|
+
},
|
|
61
|
+
rvcRunMode: {
|
|
62
|
+
supportedModes: [
|
|
63
|
+
{
|
|
64
|
+
label: "Auto-Cleaning",
|
|
65
|
+
mode: RvcDeviceRunModes.AutoCleaning,
|
|
66
|
+
modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }, { value: ModeBase.ModeTag.Auto }],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: "Quick-Cleaning",
|
|
70
|
+
mode: RvcDeviceRunModes.QuickCleaning,
|
|
71
|
+
modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }, { value: ModeBase.ModeTag.Quick }],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: "Mapping",
|
|
75
|
+
mode: RvcDeviceRunModes.Mapping,
|
|
76
|
+
modeTags: [{ value: RvcRunMode.ModeTag.Mapping }],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: "Idle",
|
|
80
|
+
mode: RvcDeviceRunModes.Idle,
|
|
81
|
+
modeTags: [{ value: RvcRunMode.ModeTag.Idle }],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
// We start with this mode. It will be persisted
|
|
85
|
+
currentMode: RvcDeviceRunModes.Idle,
|
|
86
|
+
},
|
|
87
|
+
rvcOperationalState: {
|
|
88
|
+
operationalStateList: [
|
|
89
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Running },
|
|
90
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Paused },
|
|
91
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Error },
|
|
92
|
+
{ operationalStateId: RvcOperationalState.OperationalState.SeekingCharger },
|
|
93
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Charging },
|
|
94
|
+
{ operationalStateId: RvcOperationalState.OperationalState.Docked },
|
|
95
|
+
],
|
|
96
|
+
operationalState: RvcOperationalState.OperationalState.Docked,
|
|
97
|
+
},
|
|
98
|
+
serviceArea: {
|
|
99
|
+
supportedAreas: [
|
|
100
|
+
{
|
|
101
|
+
areaId: CleaningAreas.Kitchen,
|
|
102
|
+
mapId: null,
|
|
103
|
+
areaInfo: {
|
|
104
|
+
locationInfo: {
|
|
105
|
+
locationName: "Kitchen",
|
|
106
|
+
floorNumber: 0,
|
|
107
|
+
areaType: AreaNamespaceTag.Kitchen.tag,
|
|
108
|
+
},
|
|
109
|
+
landmarkInfo: null,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
areaId: CleaningAreas.LivingRoom,
|
|
114
|
+
mapId: null,
|
|
115
|
+
areaInfo: {
|
|
116
|
+
locationInfo: {
|
|
117
|
+
locationName: "Living Room",
|
|
118
|
+
floorNumber: 0,
|
|
119
|
+
areaType: AreaNamespaceTag.LivingRoom.tag,
|
|
120
|
+
},
|
|
121
|
+
landmarkInfo: null,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
areaId: CleaningAreas.Bathroom,
|
|
126
|
+
mapId: null,
|
|
127
|
+
areaInfo: {
|
|
128
|
+
locationInfo: {
|
|
129
|
+
locationName: "Bathroom",
|
|
130
|
+
floorNumber: 0,
|
|
131
|
+
areaType: AreaNamespaceTag.Bathroom.tag,
|
|
132
|
+
},
|
|
133
|
+
landmarkInfo: null,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
areaId: CleaningAreas.Bedroom,
|
|
138
|
+
mapId: null,
|
|
139
|
+
areaInfo: {
|
|
140
|
+
locationInfo: {
|
|
141
|
+
locationName: "Bedroom",
|
|
142
|
+
floorNumber: 0,
|
|
143
|
+
areaType: AreaNamespaceTag.Bedroom.tag,
|
|
144
|
+
},
|
|
145
|
+
landmarkInfo: null,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
currentArea: null,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Our device is now built, so we can bring the node online.
|
|
155
|
+
//
|
|
156
|
+
// Note that you may serve multiple nodes from a single process. We only have one, however, so we can use the run()
|
|
157
|
+
// method of the node.
|
|
158
|
+
await node.run();
|
|
@@ -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
|
+
}
|
package/dist/templates/device-robotic-vacuum-cleaner/behaviors/CustomRvcOperationalStateServer.ts
ADDED
|
@@ -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,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();
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
{
|
|
2
|
-
"typescriptVersion": "~5.8.
|
|
2
|
+
"typescriptVersion": "~5.8.3",
|
|
3
3
|
"nodeTypesVersion": "^22.14.1",
|
|
4
4
|
"templates": [
|
|
5
5
|
{
|
|
6
6
|
"name": "controller",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
9
|
-
"@matter/nodejs-ble": "~0.13.0-alpha.0-
|
|
10
|
-
"@project-chip/matter.js": "~0.13.0-alpha.0-
|
|
8
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832",
|
|
9
|
+
"@matter/nodejs-ble": "~0.13.0-alpha.0-20250418-8cfc0b832",
|
|
10
|
+
"@project-chip/matter.js": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
11
11
|
},
|
|
12
12
|
"description": "Controller example to commission and connect devices",
|
|
13
13
|
"entrypoint": "ControllerNode.ts"
|
|
14
14
|
},
|
|
15
|
+
{
|
|
16
|
+
"name": "device-air-quality-sensor",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
19
|
+
},
|
|
20
|
+
"description": "Air quality sensor example",
|
|
21
|
+
"entrypoint": "AirQualitySensorDeviceNode.ts"
|
|
22
|
+
},
|
|
15
23
|
{
|
|
16
24
|
"name": "device-bridge-onoff",
|
|
17
25
|
"dependencies": {
|
|
18
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
26
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
19
27
|
},
|
|
20
28
|
"description": "Bridge for multiple OnOff light/sockets with a CLI command execution interface",
|
|
21
29
|
"entrypoint": "BridgedDevicesNode.ts"
|
|
@@ -23,7 +31,7 @@
|
|
|
23
31
|
{
|
|
24
32
|
"name": "device-composed-onoff",
|
|
25
33
|
"dependencies": {
|
|
26
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
34
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
27
35
|
},
|
|
28
36
|
"description": "Composed device for multiple OnOff light/sockets with a CLI command execution interface",
|
|
29
37
|
"entrypoint": "ComposedDeviceNode.ts"
|
|
@@ -31,7 +39,7 @@
|
|
|
31
39
|
{
|
|
32
40
|
"name": "device-composed-wc-light",
|
|
33
41
|
"dependencies": {
|
|
34
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
42
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
35
43
|
},
|
|
36
44
|
"description": "Composed device with Window covering and a light endpoint that logs changes",
|
|
37
45
|
"entrypoint": "IlluminatedRollerShade.ts"
|
|
@@ -39,7 +47,7 @@
|
|
|
39
47
|
{
|
|
40
48
|
"name": "device-measuring-socket",
|
|
41
49
|
"dependencies": {
|
|
42
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
50
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
43
51
|
},
|
|
44
52
|
"description": "Socket device that reports random Energy and Power measurements",
|
|
45
53
|
"entrypoint": "MeasuredSocketDevice.ts"
|
|
@@ -59,9 +67,9 @@
|
|
|
59
67
|
{
|
|
60
68
|
"name": "device-onoff-advanced",
|
|
61
69
|
"dependencies": {
|
|
62
|
-
"@matter/nodejs": "~0.13.0-alpha.0-
|
|
63
|
-
"@matter/nodejs-ble": "~0.13.0-alpha.0-
|
|
64
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
70
|
+
"@matter/nodejs": "~0.13.0-alpha.0-20250418-8cfc0b832",
|
|
71
|
+
"@matter/nodejs-ble": "~0.13.0-alpha.0-20250418-8cfc0b832",
|
|
72
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
65
73
|
},
|
|
66
74
|
"description": "OnOff light/socket device with BLE support and advanced API usage",
|
|
67
75
|
"entrypoint": "DeviceNodeFull.ts"
|
|
@@ -69,15 +77,23 @@
|
|
|
69
77
|
{
|
|
70
78
|
"name": "device-onoff-light",
|
|
71
79
|
"dependencies": {
|
|
72
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
80
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
73
81
|
},
|
|
74
82
|
"description": "OnOff light example which logs the state changes to the console",
|
|
75
83
|
"entrypoint": "LightDevice.ts"
|
|
76
84
|
},
|
|
85
|
+
{
|
|
86
|
+
"name": "device-robotic-vacuum-cleaner",
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
89
|
+
},
|
|
90
|
+
"description": "Robotic Vacuum Cleaner Example",
|
|
91
|
+
"entrypoint": "RoboticVacuumCleanerDevice.ts"
|
|
92
|
+
},
|
|
77
93
|
{
|
|
78
94
|
"name": "device-sensor",
|
|
79
95
|
"dependencies": {
|
|
80
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
96
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
81
97
|
},
|
|
82
98
|
"description": "Temperature/Humidity sensor with a CLI command interface to get the value",
|
|
83
99
|
"entrypoint": "SensorDeviceNode.ts"
|
|
@@ -85,10 +101,18 @@
|
|
|
85
101
|
{
|
|
86
102
|
"name": "device-simple",
|
|
87
103
|
"dependencies": {
|
|
88
|
-
"@matter/main": "~0.13.0-alpha.0-
|
|
104
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
89
105
|
},
|
|
90
106
|
"description": "A simple on/off device",
|
|
91
107
|
"entrypoint": "main.ts"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "device-smoke-co-alarm",
|
|
111
|
+
"dependencies": {
|
|
112
|
+
"@matter/main": "~0.13.0-alpha.0-20250418-8cfc0b832"
|
|
113
|
+
},
|
|
114
|
+
"description": "Smoke CO Alarm Example",
|
|
115
|
+
"entrypoint": "SmokeCOAlarmDeviceNode.ts"
|
|
92
116
|
}
|
|
93
117
|
]
|
|
94
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter/create",
|
|
3
|
-
"version": "0.13.0-alpha.0-
|
|
3
|
+
"version": "0.13.0-alpha.0-20250418-8cfc0b832",
|
|
4
4
|
"description": "Matter.js skeleton project generator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/project-chip/matter.js#readme",
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@matter/tools": "0.13.0-alpha.0-
|
|
35
|
+
"@matter/tools": "0.13.0-alpha.0-20250418-8cfc0b832",
|
|
36
36
|
"@types/node": "^22.14.1",
|
|
37
37
|
"@types/tar-stream": "^3.1.3"
|
|
38
38
|
},
|