@project-chip/matter-node.js-examples 0.7.5-alpha.0-20240222-8696097f → 0.8.0-alpha.0-20240309-64eaef67

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. package/README.md +44 -0
  2. package/dist/esm/examples/BridgedDeviceNode.js +147 -0
  3. package/dist/esm/examples/BridgedDeviceNode.js.map +7 -0
  4. package/dist/esm/examples/{BridgedDevicesNode.js → BridgedDevicesNodeLegacy.js} +2 -2
  5. package/dist/esm/examples/BridgedDevicesNodeLegacy.js.map +7 -0
  6. package/dist/esm/examples/ComposedDeviceNode.js +116 -127
  7. package/dist/esm/examples/ComposedDeviceNode.js.map +3 -3
  8. package/dist/esm/examples/ComposedDeviceNodeLegacy.js +138 -0
  9. package/dist/esm/examples/ComposedDeviceNodeLegacy.js.map +7 -0
  10. package/dist/esm/examples/ControllerNode.js +24 -20
  11. package/dist/esm/examples/ControllerNode.js.map +2 -2
  12. package/dist/esm/examples/ControllerNodeLegacy.js +227 -0
  13. package/dist/esm/examples/ControllerNodeLegacy.js.map +7 -0
  14. package/dist/esm/examples/DeviceNode.js +118 -159
  15. package/dist/esm/examples/DeviceNode.js.map +2 -2
  16. package/dist/esm/examples/DeviceNodeFull.js +253 -0
  17. package/dist/esm/examples/DeviceNodeFull.js.map +7 -0
  18. package/dist/esm/examples/DeviceNodeFullLegacy.js +171 -0
  19. package/dist/esm/examples/DeviceNodeFullLegacy.js.map +7 -0
  20. package/dist/esm/examples/IlluminatedRollerShade.js +85 -0
  21. package/dist/esm/examples/IlluminatedRollerShade.js.map +7 -0
  22. package/dist/esm/examples/LightDevice.js +34 -0
  23. package/dist/esm/examples/LightDevice.js.map +7 -0
  24. package/dist/esm/examples/MultiDeviceNode.js +133 -134
  25. package/dist/esm/examples/MultiDeviceNode.js.map +3 -3
  26. package/dist/esm/examples/MultiDeviceNodeLegacy.js +146 -0
  27. package/dist/esm/examples/MultiDeviceNodeLegacy.js.map +7 -0
  28. package/dist/esm/examples/SensorDeviceNode.js +167 -0
  29. package/dist/esm/examples/SensorDeviceNode.js.map +7 -0
  30. package/dist/esm/examples/cluster/DummyThreadNetworkCommissioningServer.js +115 -0
  31. package/dist/esm/examples/cluster/DummyThreadNetworkCommissioningServer.js.map +7 -0
  32. package/dist/esm/examples/cluster/DummyWifiNetworkCommissioningServer.js +115 -0
  33. package/dist/esm/examples/cluster/DummyWifiNetworkCommissioningServer.js.map +7 -0
  34. package/dist/esm/examples/cluster/{DummyWifiNetworkCommissioningClusterServer.js → DummyWifiNetworkCommissioningServerLegacy.js} +16 -14
  35. package/dist/esm/examples/cluster/DummyWifiNetworkCommissioningServerLegacy.js.map +7 -0
  36. package/dist/esm/examples/cluster/MyFancyOwnFunctionality.js +110 -0
  37. package/dist/esm/examples/cluster/MyFancyOwnFunctionality.js.map +7 -0
  38. package/dist/esm/tutorial/example01.js +5 -0
  39. package/dist/esm/tutorial/example01.js.map +7 -0
  40. package/dist/esm/tutorial/example02.js +7 -0
  41. package/dist/esm/tutorial/example02.js.map +7 -0
  42. package/dist/esm/tutorial/example03.js +15 -0
  43. package/dist/esm/tutorial/example03.js.map +7 -0
  44. package/dist/esm/tutorial/example04.js +10 -0
  45. package/dist/esm/tutorial/example04.js.map +7 -0
  46. package/dist/esm/tutorial/example05.js +14 -0
  47. package/dist/esm/tutorial/example05.js.map +7 -0
  48. package/package.json +9 -6
  49. package/src/examples/BridgedDeviceNode.ts +260 -0
  50. package/src/examples/{BridgedDevicesNode.ts → BridgedDevicesNodeLegacy.ts} +6 -1
  51. package/src/examples/ComposedDeviceNode.ts +173 -223
  52. package/src/examples/ComposedDeviceNodeLegacy.ts +252 -0
  53. package/src/examples/ControllerNode.ts +28 -22
  54. package/src/examples/ControllerNodeLegacy.ts +354 -0
  55. package/src/examples/DeviceNode.ts +173 -273
  56. package/src/examples/DeviceNodeFull.ts +440 -0
  57. package/src/examples/DeviceNodeFullLegacy.ts +307 -0
  58. package/src/examples/IlluminatedRollerShade.ts +130 -0
  59. package/src/examples/LightDevice.ts +60 -0
  60. package/src/examples/MultiDeviceNode.ts +184 -236
  61. package/src/examples/MultiDeviceNodeLegacy.ts +267 -0
  62. package/src/examples/SensorDeviceNode.ts +236 -0
  63. package/src/examples/cluster/DummyThreadNetworkCommissioningServer.ts +156 -0
  64. package/src/examples/cluster/DummyWifiNetworkCommissioningServer.ts +153 -0
  65. package/src/examples/cluster/{DummyWifiNetworkCommissioningClusterServer.ts → DummyWifiNetworkCommissioningServerLegacy.ts} +16 -13
  66. package/src/examples/cluster/MyFancyOwnFunctionality.ts +185 -0
  67. package/src/tsconfig.json +9 -1
  68. package/src/tutorial/example01.ts +6 -0
  69. package/src/tutorial/example02.ts +9 -0
  70. package/src/tutorial/example03.ts +19 -0
  71. package/src/tutorial/example04.ts +13 -0
  72. package/src/tutorial/example05.ts +19 -0
  73. package/dist/esm/examples/BridgedDevicesNode.js.map +0 -7
  74. package/dist/esm/examples/cluster/DummyWifiNetworkCommissioningClusterServer.js.map +0 -7
@@ -1,14 +1,13 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * @license
4
- * Copyright 2022 The node-matter Authors
3
+ * Copyright 2022-2024 Matter.js Authors
5
4
  * SPDX-License-Identifier: Apache-2.0
6
5
  */
7
6
 
8
7
  /**
9
8
  * This example shows how to create a new device node that is composed of multiple devices.
10
- * It creates multiple endpoints on the server. When you want to add a composed devices to a Aggregator you need to
11
- * add all endpoints of the composed device to an "ComposedDevice" instance! (not shown in this example).
9
+ * It creates multiple endpoints on the server. For information on how to add a composed device to a bridge please
10
+ * refer to the bridge example!
12
11
  * It can be used as CLI script and starting point for your own device node implementation.
13
12
  */
14
13
 
@@ -16,232 +15,183 @@
16
15
  * Import needed modules from @project-chip/matter-node.js
17
16
  */
18
17
  // Include this first to auto-register Crypto, Network and Time Node.js implementations
19
- import { CommissioningServer, MatterServer } from "@project-chip/matter-node.js";
20
-
21
- import { VendorId } from "@project-chip/matter-node.js/datatype";
22
- import { DeviceTypes, OnOffLightDevice, OnOffPluginUnitDevice } from "@project-chip/matter-node.js/device";
23
- import { Format, Level, Logger } from "@project-chip/matter-node.js/log";
24
- import { QrCode } from "@project-chip/matter-node.js/schema";
25
- import { StorageBackendDisk, StorageManager } from "@project-chip/matter-node.js/storage";
26
- import { Time } from "@project-chip/matter-node.js/time";
27
- import {
28
- commandExecutor,
29
- getIntParameter,
30
- getParameter,
31
- hasParameter,
32
- requireMinNodeVersion,
33
- } from "@project-chip/matter-node.js/util";
34
-
35
- const logger = Logger.get("Device");
18
+ import "@project-chip/matter-node.js";
19
+
20
+ import { requireMinNodeVersion } from "@project-chip/matter-node.js/util";
21
+ import { DeviceTypeId, VendorId } from "@project-chip/matter.js/datatype";
22
+ import { logEndpoint } from "@project-chip/matter.js/device";
23
+ import { OnOffLightDevice } from "@project-chip/matter.js/devices/OnOffLightDevice";
24
+ import { OnOffPlugInUnitDevice } from "@project-chip/matter.js/devices/OnOffPlugInUnitDevice";
25
+ import { Endpoint, EndpointServer } from "@project-chip/matter.js/endpoint";
26
+ import { Environment, StorageService } from "@project-chip/matter.js/environment";
27
+ import { ServerNode } from "@project-chip/matter.js/node";
28
+ import { Time } from "@project-chip/matter.js/time";
29
+ import { execSync } from "child_process";
36
30
 
37
31
  requireMinNodeVersion(16);
38
32
 
39
- /** Configure logging */
40
- switch (getParameter("loglevel")) {
41
- case "fatal":
42
- Logger.defaultLogLevel = Level.FATAL;
43
- break;
44
- case "error":
45
- Logger.defaultLogLevel = Level.ERROR;
46
- break;
47
- case "warn":
48
- Logger.defaultLogLevel = Level.WARN;
49
- break;
50
- case "info":
51
- Logger.defaultLogLevel = Level.INFO;
52
- break;
53
- }
33
+ /** Initialize configuration values */
34
+ const { isSocket, deviceName, vendorName, passcode, discriminator, vendorId, productName, productId, port, uniqueId } =
35
+ await getConfiguration();
54
36
 
55
- switch (getParameter("logformat")) {
56
- case "plain":
57
- Logger.format = Format.PLAIN;
58
- break;
59
- case "html":
60
- Logger.format = Format.HTML;
61
- break;
62
- default:
63
- if (process.stdin?.isTTY) Logger.format = Format.ANSI;
64
- }
37
+ /**
38
+ * Create a Matter ServerNode, which contains the Root Endpoint and all relevant data and configuration
39
+ */
40
+ const server = await ServerNode.create({
41
+ // Required: Give the Node a unique ID which is used to store the state of this node
42
+ id: uniqueId,
43
+
44
+ // Provide Network relevant configuration like the port
45
+ // Optional when operating only one device on a host, Default port is 5540
46
+ network: {
47
+ port,
48
+ },
49
+
50
+ // Provide Commissioning relevant settings
51
+ // Optional for development/testing purposes
52
+ commissioning: {
53
+ passcode,
54
+ discriminator,
55
+ },
56
+
57
+ // Provide Node announcement settings
58
+ // Optional: If Ommitted some development defaults are used
59
+ productDescription: {
60
+ name: deviceName,
61
+ deviceType: DeviceTypeId(isSocket[0] ? OnOffPlugInUnitDevice.deviceType : OnOffLightDevice.deviceType),
62
+ },
63
+
64
+ // Provide defaults for the BasicInformation cluster on the Root endpoint
65
+ // Optional: If Omitted some development defaults are used
66
+ basicInformation: {
67
+ vendorName,
68
+ vendorId: VendorId(vendorId),
69
+ nodeLabel: productName,
70
+ productName,
71
+ productLabel: productName,
72
+ productId,
73
+ serialNumber: `matterjs-${uniqueId}`,
74
+ uniqueId,
75
+ },
76
+ });
65
77
 
66
- const storageLocation = getParameter("store") ?? ".device-node";
67
- const storage = new StorageBackendDisk(storageLocation, hasParameter("clearstorage"));
68
- logger.info(`Storage location: ${storageLocation} (Directory)`);
69
- logger.info(
70
- 'Use the parameter "-store NAME" to specify a different storage location, use -clearstorage to start with an empty storage.',
71
- );
72
-
73
- class ComposedDevice {
74
- private matterServer: MatterServer | undefined;
75
-
76
- async start() {
77
- logger.info(`node-matter`);
78
-
79
- /**
80
- * Initialize the storage system.
81
- *
82
- * The storage manager is then also used by the Matter server, so this code block in general is required,
83
- * but you can choose a different storage backend as long as it implements the required API.
84
- */
85
-
86
- const storageManager = new StorageManager(storage);
87
- await storageManager.initialize();
88
-
89
- /**
90
- * Collect all needed data
91
- *
92
- * This block makes sure to collect all needed data from cli or storage. Replace this with where ever your data
93
- * come from.
94
- *
95
- * Note: This example also uses the initialized storage system to store the device parameter data for convenience
96
- * and easy reuse. When you also do that be careful to not overlap with Matter-Server own contexts
97
- * (so maybe better not ;-)).
98
- */
99
-
100
- const deviceStorage = storageManager.createContext("Device");
101
-
102
- if (deviceStorage.has("isSocket")) {
103
- logger.info("Device type found in storage. -type parameter is ignored.");
104
- }
105
- const isSocket = deviceStorage.get("isSocket", getParameter("type") === "socket");
106
- const deviceName = "Matter composed device";
107
- const deviceType =
108
- getParameter("type") === "socket" ? DeviceTypes.ON_OFF_PLUGIN_UNIT.code : DeviceTypes.ON_OFF_LIGHT.code;
109
- const vendorName = "matter-node.js";
110
- const passcode = getIntParameter("passcode") ?? deviceStorage.get("passcode", 20202021);
111
- const discriminator = getIntParameter("discriminator") ?? deviceStorage.get("discriminator", 3840);
112
- // product name / id and vendor id should match what is in the device certificate
113
- const vendorId = getIntParameter("vendorid") ?? deviceStorage.get("vendorid", 0xfff1);
114
- const productName = `node-matter OnOff-Bridge`;
115
- const productId = getIntParameter("productid") ?? deviceStorage.get("productid", 0x8000);
116
-
117
- const netInterface = getParameter("netinterface");
118
- const port = getIntParameter("port") ?? 5540;
119
-
120
- const uniqueId = getIntParameter("uniqueid") ?? deviceStorage.get("uniqueid", Time.nowMs());
121
-
122
- deviceStorage.set("passcode", passcode);
123
- deviceStorage.set("discriminator", discriminator);
124
- deviceStorage.set("vendorid", vendorId);
125
- deviceStorage.set("productid", productId);
126
- deviceStorage.set("isSocket", isSocket);
127
- deviceStorage.set("uniqueid", uniqueId);
128
-
129
- /**
130
- * Create Matter Server and CommissioningServer Node
131
- *
132
- * To allow the device to be announced, found, paired and operated we need a MatterServer instance and add a
133
- * commissioningServer to it and add the just created device instance to it.
134
- * The CommissioningServer node defines the port where the server listens for the UDP packages of the Matter protocol
135
- * and initializes deice specific certificates and such.
136
- *
137
- * The below logic also adds command handlers for commands of clusters that normally are handled internally
138
- * like testEventTrigger (General Diagnostic Cluster) that can be implemented with the logic when these commands
139
- * are called.
140
- */
141
-
142
- this.matterServer = new MatterServer(storageManager, { mdnsInterface: netInterface });
143
-
144
- const commissioningServer = new CommissioningServer({
145
- port,
146
- deviceName,
147
- deviceType,
148
- passcode,
149
- discriminator,
150
- basicInformation: {
151
- vendorName,
152
- vendorId: VendorId(vendorId),
153
- nodeLabel: productName,
154
- productName,
155
- productLabel: productName,
156
- productId,
157
- serialNumber: `node-matter-${uniqueId}`,
158
- },
159
- });
160
-
161
- /**
162
- * Create Device instance and add needed Listener
163
- *
164
- * Create an instance of the matter device class you want to use.
165
- * This example uses the OnOffLightDevice or OnOffPluginUnitDevice depending on the value of the type parameter.
166
- * To execute the on/off scripts defined as parameters a listener for the onOff attribute is registered via the
167
- * device specific API.
168
- *
169
- * The below logic also adds command handlers for commands of clusters that normally are handled device internally
170
- * like identify that can be implemented with the logic when these commands are called.
171
- */
172
-
173
- const numDevices = getIntParameter("num") || 2;
174
- for (let i = 1; i <= numDevices; i++) {
175
- const onOffDevice =
176
- getParameter(`type${i}`) === "socket" ? new OnOffPluginUnitDevice() : new OnOffLightDevice();
177
- onOffDevice.addFixedLabel("orientation", getParameter(`orientation${i}`) ?? `orientation ${i}`);
178
-
179
- onOffDevice.addOnOffListener(on => commandExecutor(on ? `on${i}` : `off${i}`)?.());
180
- onOffDevice.addCommandHandler("identify", async ({ request: { identifyTime } }) =>
181
- console.log(
182
- `Identify called for OnOffDevice ${onOffDevice.name} with id: ${i} and identifyTime: ${identifyTime}`,
183
- ),
184
- );
185
-
186
- commissioningServer.addDevice(onOffDevice);
187
- }
78
+ /**
79
+ * Matter Nodes are a composition of endpoints. Create and add a single multiple endpoint to the node to make it a
80
+ * composed device. This example uses the OnOffLightDevice or OnOffPlugInUnitDevice depending on the value of the type
81
+ * parameter. It also assigns each Endpoint a unique ID to store the endpoint number for it in the storage to restore
82
+ * the device on restart.
83
+ *
84
+ * In this case we directly use the default command implementation from matter.js. Check out the DeviceNodeFull example
85
+ * to see how to customize the command handlers.
86
+ */
188
87
 
189
- await this.matterServer.addCommissioningServer(commissioningServer);
190
-
191
- /**
192
- * Start the Matter Server
193
- *
194
- * After everything was plugged together we can start the server. When not delayed announcement is set for the
195
- * CommissioningServer node then this command also starts the announcement of the device into the network.
196
- */
197
-
198
- await this.matterServer.start();
199
-
200
- /**
201
- * Print Pairing Information
202
- *
203
- * If the device is not already commissioned (this info is stored in the storage system) then get and print the
204
- * pairing details. This includes the QR code that can be scanned by the Matter app to pair the device.
205
- */
206
-
207
- logger.info("Listening");
208
- if (!commissioningServer.isCommissioned()) {
209
- const pairingData = commissioningServer.getPairingCode();
210
- const { qrPairingCode, manualPairingCode } = pairingData;
211
-
212
- console.log(QrCode.get(qrPairingCode));
213
- console.log(
214
- `QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`,
215
- );
216
- console.log(`Manual pairing code: ${manualPairingCode}`);
217
- } else {
218
- console.log("Device is already commissioned. Waiting for controllers to connect ...");
88
+ for (let idx = 0; idx < isSocket.length; idx++) {
89
+ const i = idx + 1;
90
+ const isASocket = isSocket[idx]; // Is the Device we add a Socket or a Light?
91
+ const endpoint = new Endpoint(isASocket ? OnOffPlugInUnitDevice : OnOffLightDevice, { id: `onoff-${i}` });
92
+ await server.add(endpoint);
93
+
94
+ /**
95
+ * Register state change handlers of the endpoint for identify and onoff states to react to the commands.
96
+ *
97
+ * If the code in these change handlers fail then the change is also rolled back and not executed and an error is
98
+ * reported back to the controller.
99
+ */
100
+ let isIdentifying = false;
101
+ endpoint.events.identify.identifyTime$Change.on(value => {
102
+ // identifyTime is set when an identify command is called and then decreased every second while indentify logic runs.
103
+ if (value > 0 && !isIdentifying) {
104
+ isIdentifying = true;
105
+ console.log(`OnOff ${i}: Run identify logic, ideally blink a light every 0.5s ...`);
106
+ } else if (value === 0) {
107
+ isIdentifying = false;
108
+ console.log(`OnOff ${i}: Stop identify logic ...`);
219
109
  }
220
- }
110
+ });
221
111
 
222
- async stop() {
223
- await this.matterServer?.close();
224
- }
112
+ endpoint.events.onOff.onOff$Change.on(value => {
113
+ executeCommand(value ? `on${i}` : `off${i}`);
114
+ console.log(`OnOff ${i} is now ${value ? "ON" : "OFF"}`);
115
+ });
225
116
  }
226
117
 
227
- const device = new ComposedDevice();
228
- device
229
- .start()
230
- .then(() => {
231
- /* done */
232
- })
233
- .catch(err => console.error(err));
234
-
235
- process.on("SIGINT", () => {
236
- // Clean up on CTRL-C
237
- device
238
- .stop()
239
- .then(() => {
240
- // Pragmatic way to make sure the storage is correctly closed before the process ends.
241
- storage
242
- .close()
243
- .then(() => process.exit(0))
244
- .catch(err => console.error(err));
245
- })
246
- .catch(err => console.error(err));
247
- });
118
+ /**
119
+ * Log the endpoint structure for debugging reasons and to allow to verify anything is correct
120
+ */
121
+ logEndpoint(EndpointServer.forEndpoint(server));
122
+
123
+ /**
124
+ * In order to start the node and announce it into the network we use the run method which resolves when the node goes
125
+ * offline again because we do not need anything more here. See the Full example for other starting options.
126
+ * The QR Code is printed automatically.
127
+ */
128
+ await server.run();
129
+
130
+ /*********************************************************************************************************
131
+ * Convenience Methods
132
+ *********************************************************************************************************/
133
+
134
+ /**
135
+ * Defines a shell command from an environment variable and execute it and log the response
136
+ */
137
+ function executeCommand(scriptParamName: string) {
138
+ const script = Environment.default.vars.string(scriptParamName);
139
+ if (script === undefined) return undefined;
140
+ console.log(`${scriptParamName}: ${execSync(script).toString().slice(0, -1)}`);
141
+ }
142
+
143
+ async function getConfiguration() {
144
+ const environment = Environment.default;
145
+
146
+ const storageService = environment.get(StorageService);
147
+ console.log(`Storage location: ${storageService.location} (Directory)`);
148
+ console.log(
149
+ 'Use the parameter "--storage-path=NAME-OR-PATH" to specify a different storage location in this directory, use --storage-clear to start with an empty storage.',
150
+ );
151
+ const deviceStorage = (await storageService.open("device")).createContext("data");
152
+
153
+ const isSocket = Array<boolean>();
154
+ const numDevices = environment.vars.number("num") || 2;
155
+ if (deviceStorage.has("isSocket")) {
156
+ console.log(`Device types found in storage. --type parameter is ignored.`);
157
+ deviceStorage.get<Array<boolean>>("isSocket").forEach(type => isSocket.push(type));
158
+ }
159
+ for (let i = 1; i < numDevices; i++) {
160
+ if (isSocket[i - 1] !== undefined) continue;
161
+ isSocket.push(environment.vars.string(`type${i}`) === "socket");
162
+ }
163
+
164
+ const deviceName = "Matter test device";
165
+ const vendorName = "matter-node.js";
166
+ const passcode = environment.vars.number("passcode") ?? deviceStorage.get("passcode", 20202021);
167
+ const discriminator = environment.vars.number("discriminator") ?? deviceStorage.get("discriminator", 3840);
168
+ // product name / id and vendor id should match what is in the device certificate
169
+ const vendorId = environment.vars.number("vendorid") ?? deviceStorage.get("vendorid", 0xfff1);
170
+ const productName = `node-matter OnOff ${isSocket ? "Socket" : "Light"}`;
171
+ const productId = environment.vars.number("productid") ?? deviceStorage.get("productid", 0x8000);
172
+
173
+ const port = environment.vars.number("port") ?? 5540;
174
+
175
+ const uniqueId = environment.vars.string("uniqueid") ?? deviceStorage.get("uniqueid", Time.nowMs().toString());
176
+
177
+ // Persist basic data to keep them also on restart
178
+ deviceStorage.set("passcode", passcode);
179
+ deviceStorage.set("discriminator", discriminator);
180
+ deviceStorage.set("vendorid", vendorId);
181
+ deviceStorage.set("productid", productId);
182
+ deviceStorage.set("isSocket", isSocket);
183
+ deviceStorage.set("uniqueid", uniqueId);
184
+
185
+ return {
186
+ isSocket,
187
+ deviceName,
188
+ vendorName,
189
+ passcode,
190
+ discriminator,
191
+ vendorId,
192
+ productName,
193
+ productId,
194
+ port,
195
+ uniqueId,
196
+ };
197
+ }
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @license
4
+ * Copyright 2022-2024 Matter.js Authors
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+
8
+ /**
9
+ * IMPORTANT: This example uses a Legacy API which will be deprecated in the future.
10
+ * It is just still here to support developers in converting their code to the new API!
11
+ */
12
+
13
+ /**
14
+ * This example shows how to create a new device node that is composed of multiple devices.
15
+ * It creates multiple endpoints on the server. When you want to add a composed devices to a Aggregator you need to
16
+ * add all endpoints of the composed device to an "ComposedDevice" instance! (not shown in this example).
17
+ * It can be used as CLI script and starting point for your own device node implementation.
18
+ */
19
+
20
+ /**
21
+ * Import needed modules from @project-chip/matter-node.js
22
+ */
23
+ // Include this first to auto-register Crypto, Network and Time Node.js implementations
24
+ import { CommissioningServer, MatterServer } from "@project-chip/matter-node.js";
25
+
26
+ import { VendorId } from "@project-chip/matter-node.js/datatype";
27
+ import { DeviceTypes, OnOffLightDevice, OnOffPluginUnitDevice } from "@project-chip/matter-node.js/device";
28
+ import { Format, Level, Logger } from "@project-chip/matter-node.js/log";
29
+ import { QrCode } from "@project-chip/matter-node.js/schema";
30
+ import { StorageBackendDisk, StorageManager } from "@project-chip/matter-node.js/storage";
31
+ import { Time } from "@project-chip/matter-node.js/time";
32
+ import {
33
+ commandExecutor,
34
+ getIntParameter,
35
+ getParameter,
36
+ hasParameter,
37
+ requireMinNodeVersion,
38
+ } from "@project-chip/matter-node.js/util";
39
+
40
+ const logger = Logger.get("Device");
41
+
42
+ requireMinNodeVersion(16);
43
+
44
+ /** Configure logging */
45
+ switch (getParameter("loglevel")) {
46
+ case "fatal":
47
+ Logger.defaultLogLevel = Level.FATAL;
48
+ break;
49
+ case "error":
50
+ Logger.defaultLogLevel = Level.ERROR;
51
+ break;
52
+ case "warn":
53
+ Logger.defaultLogLevel = Level.WARN;
54
+ break;
55
+ case "info":
56
+ Logger.defaultLogLevel = Level.INFO;
57
+ break;
58
+ }
59
+
60
+ switch (getParameter("logformat")) {
61
+ case "plain":
62
+ Logger.format = Format.PLAIN;
63
+ break;
64
+ case "html":
65
+ Logger.format = Format.HTML;
66
+ break;
67
+ default:
68
+ if (process.stdin?.isTTY) Logger.format = Format.ANSI;
69
+ }
70
+
71
+ const storageLocation = getParameter("store") ?? ".device-node";
72
+ const storage = new StorageBackendDisk(storageLocation, hasParameter("clearstorage"));
73
+ logger.info(`Storage location: ${storageLocation} (Directory)`);
74
+ logger.info(
75
+ 'Use the parameter "-store NAME" to specify a different storage location, use -clearstorage to start with an empty storage.',
76
+ );
77
+
78
+ class ComposedDevice {
79
+ private matterServer: MatterServer | undefined;
80
+
81
+ async start() {
82
+ logger.info(`node-matter`);
83
+
84
+ /**
85
+ * Initialize the storage system.
86
+ *
87
+ * The storage manager is then also used by the Matter server, so this code block in general is required,
88
+ * but you can choose a different storage backend as long as it implements the required API.
89
+ */
90
+
91
+ const storageManager = new StorageManager(storage);
92
+ await storageManager.initialize();
93
+
94
+ /**
95
+ * Collect all needed data
96
+ *
97
+ * This block makes sure to collect all needed data from cli or storage. Replace this with where ever your data
98
+ * come from.
99
+ *
100
+ * Note: This example also uses the initialized storage system to store the device parameter data for convenience
101
+ * and easy reuse. When you also do that be careful to not overlap with Matter-Server own contexts
102
+ * (so maybe better not ;-)).
103
+ */
104
+
105
+ const deviceStorage = storageManager.createContext("Device");
106
+
107
+ if (deviceStorage.has("isSocket")) {
108
+ logger.info("Device type found in storage. -type parameter is ignored.");
109
+ }
110
+ const isSocket = deviceStorage.get("isSocket", getParameter("type") === "socket");
111
+ const deviceName = "Matter composed device";
112
+ const deviceType =
113
+ getParameter("type") === "socket" ? DeviceTypes.ON_OFF_PLUGIN_UNIT.code : DeviceTypes.ON_OFF_LIGHT.code;
114
+ const vendorName = "matter-node.js";
115
+ const passcode = getIntParameter("passcode") ?? deviceStorage.get("passcode", 20202021);
116
+ const discriminator = getIntParameter("discriminator") ?? deviceStorage.get("discriminator", 3840);
117
+ // product name / id and vendor id should match what is in the device certificate
118
+ const vendorId = getIntParameter("vendorid") ?? deviceStorage.get("vendorid", 0xfff1);
119
+ const productName = `node-matter OnOff-Bridge`;
120
+ const productId = getIntParameter("productid") ?? deviceStorage.get("productid", 0x8000);
121
+
122
+ const netInterface = getParameter("netinterface");
123
+ const port = getIntParameter("port") ?? 5540;
124
+
125
+ const uniqueId = getIntParameter("uniqueid") ?? deviceStorage.get("uniqueid", Time.nowMs());
126
+
127
+ deviceStorage.set("passcode", passcode);
128
+ deviceStorage.set("discriminator", discriminator);
129
+ deviceStorage.set("vendorid", vendorId);
130
+ deviceStorage.set("productid", productId);
131
+ deviceStorage.set("isSocket", isSocket);
132
+ deviceStorage.set("uniqueid", uniqueId);
133
+
134
+ /**
135
+ * Create Matter Server and CommissioningServer Node
136
+ *
137
+ * To allow the device to be announced, found, paired and operated we need a MatterServer instance and add a
138
+ * commissioningServer to it and add the just created device instance to it.
139
+ * The CommissioningServer node defines the port where the server listens for the UDP packages of the Matter protocol
140
+ * and initializes deice specific certificates and such.
141
+ *
142
+ * The below logic also adds command handlers for commands of clusters that normally are handled internally
143
+ * like testEventTrigger (General Diagnostic Cluster) that can be implemented with the logic when these commands
144
+ * are called.
145
+ */
146
+
147
+ this.matterServer = new MatterServer(storageManager, { mdnsInterface: netInterface });
148
+
149
+ const commissioningServer = new CommissioningServer({
150
+ port,
151
+ deviceName,
152
+ deviceType,
153
+ passcode,
154
+ discriminator,
155
+ basicInformation: {
156
+ vendorName,
157
+ vendorId: VendorId(vendorId),
158
+ nodeLabel: productName,
159
+ productName,
160
+ productLabel: productName,
161
+ productId,
162
+ serialNumber: `node-matter-${uniqueId}`,
163
+ },
164
+ });
165
+
166
+ /**
167
+ * Create Device instance and add needed Listener
168
+ *
169
+ * Create an instance of the matter device class you want to use.
170
+ * This example uses the OnOffLightDevice or OnOffPluginUnitDevice depending on the value of the type parameter.
171
+ * To execute the on/off scripts defined as parameters a listener for the onOff attribute is registered via the
172
+ * device specific API.
173
+ *
174
+ * The below logic also adds command handlers for commands of clusters that normally are handled device internally
175
+ * like identify that can be implemented with the logic when these commands are called.
176
+ */
177
+
178
+ const numDevices = getIntParameter("num") || 2;
179
+ for (let i = 1; i <= numDevices; i++) {
180
+ const onOffDevice =
181
+ getParameter(`type${i}`) === "socket" ? new OnOffPluginUnitDevice() : new OnOffLightDevice();
182
+ onOffDevice.addFixedLabel("orientation", getParameter(`orientation${i}`) ?? `orientation ${i}`);
183
+
184
+ onOffDevice.addOnOffListener(on => commandExecutor(on ? `on${i}` : `off${i}`)?.());
185
+ onOffDevice.addCommandHandler("identify", async ({ request: { identifyTime } }) =>
186
+ console.log(
187
+ `Identify called for OnOffDevice ${onOffDevice.name} with id: ${i} and identifyTime: ${identifyTime}`,
188
+ ),
189
+ );
190
+
191
+ commissioningServer.addDevice(onOffDevice);
192
+ }
193
+
194
+ await this.matterServer.addCommissioningServer(commissioningServer);
195
+
196
+ /**
197
+ * Start the Matter Server
198
+ *
199
+ * After everything was plugged together we can start the server. When not delayed announcement is set for the
200
+ * CommissioningServer node then this command also starts the announcement of the device into the network.
201
+ */
202
+
203
+ await this.matterServer.start();
204
+
205
+ /**
206
+ * Print Pairing Information
207
+ *
208
+ * If the device is not already commissioned (this info is stored in the storage system) then get and print the
209
+ * pairing details. This includes the QR code that can be scanned by the Matter app to pair the device.
210
+ */
211
+
212
+ logger.info("Listening");
213
+ if (!commissioningServer.isCommissioned()) {
214
+ const pairingData = commissioningServer.getPairingCode();
215
+ const { qrPairingCode, manualPairingCode } = pairingData;
216
+
217
+ console.log(QrCode.get(qrPairingCode));
218
+ console.log(
219
+ `QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`,
220
+ );
221
+ console.log(`Manual pairing code: ${manualPairingCode}`);
222
+ } else {
223
+ console.log("Device is already commissioned. Waiting for controllers to connect ...");
224
+ }
225
+ }
226
+
227
+ async stop() {
228
+ await this.matterServer?.close();
229
+ }
230
+ }
231
+
232
+ const device = new ComposedDevice();
233
+ device
234
+ .start()
235
+ .then(() => {
236
+ /* done */
237
+ })
238
+ .catch(err => console.error(err));
239
+
240
+ process.on("SIGINT", () => {
241
+ // Clean up on CTRL-C
242
+ device
243
+ .stop()
244
+ .then(() => {
245
+ // Pragmatic way to make sure the storage is correctly closed before the process ends.
246
+ storage
247
+ .close()
248
+ .then(() => process.exit(0))
249
+ .catch(err => console.error(err));
250
+ })
251
+ .catch(err => console.error(err));
252
+ });