@matter-server/ws-controller 0.2.0-alpha.0-00000000-000000000

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 (95) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +11 -0
  3. package/dist/esm/controller/AttributeDataCache.d.ts +49 -0
  4. package/dist/esm/controller/AttributeDataCache.d.ts.map +1 -0
  5. package/dist/esm/controller/AttributeDataCache.js +154 -0
  6. package/dist/esm/controller/AttributeDataCache.js.map +6 -0
  7. package/dist/esm/controller/ControllerCommandHandler.d.ts +118 -0
  8. package/dist/esm/controller/ControllerCommandHandler.d.ts.map +1 -0
  9. package/dist/esm/controller/ControllerCommandHandler.js +1015 -0
  10. package/dist/esm/controller/ControllerCommandHandler.js.map +6 -0
  11. package/dist/esm/controller/LegacyDataInjector.d.ts +95 -0
  12. package/dist/esm/controller/LegacyDataInjector.d.ts.map +1 -0
  13. package/dist/esm/controller/LegacyDataInjector.js +196 -0
  14. package/dist/esm/controller/LegacyDataInjector.js.map +6 -0
  15. package/dist/esm/controller/MatterController.d.ts +59 -0
  16. package/dist/esm/controller/MatterController.d.ts.map +1 -0
  17. package/dist/esm/controller/MatterController.js +212 -0
  18. package/dist/esm/controller/MatterController.js.map +6 -0
  19. package/dist/esm/controller/Nodes.d.ts +62 -0
  20. package/dist/esm/controller/Nodes.d.ts.map +1 -0
  21. package/dist/esm/controller/Nodes.js +85 -0
  22. package/dist/esm/controller/Nodes.js.map +6 -0
  23. package/dist/esm/controller/TestNodeCommandHandler.d.ts +84 -0
  24. package/dist/esm/controller/TestNodeCommandHandler.d.ts.map +1 -0
  25. package/dist/esm/controller/TestNodeCommandHandler.js +225 -0
  26. package/dist/esm/controller/TestNodeCommandHandler.js.map +6 -0
  27. package/dist/esm/data/VendorIDs.d.ts +7 -0
  28. package/dist/esm/data/VendorIDs.d.ts.map +1 -0
  29. package/dist/esm/data/VendorIDs.js +1237 -0
  30. package/dist/esm/data/VendorIDs.js.map +6 -0
  31. package/dist/esm/example/send-command.d.ts +7 -0
  32. package/dist/esm/example/send-command.d.ts.map +1 -0
  33. package/dist/esm/example/send-command.js +60 -0
  34. package/dist/esm/example/send-command.js.map +6 -0
  35. package/dist/esm/index.d.ts +21 -0
  36. package/dist/esm/index.d.ts.map +1 -0
  37. package/dist/esm/index.js +26 -0
  38. package/dist/esm/index.js.map +6 -0
  39. package/dist/esm/model/ModelMapper.d.ts +34 -0
  40. package/dist/esm/model/ModelMapper.d.ts.map +1 -0
  41. package/dist/esm/model/ModelMapper.js +62 -0
  42. package/dist/esm/model/ModelMapper.js.map +6 -0
  43. package/dist/esm/package.json +3 -0
  44. package/dist/esm/server/ConfigStorage.d.ts +29 -0
  45. package/dist/esm/server/ConfigStorage.d.ts.map +1 -0
  46. package/dist/esm/server/ConfigStorage.js +84 -0
  47. package/dist/esm/server/ConfigStorage.js.map +6 -0
  48. package/dist/esm/server/Converters.d.ts +53 -0
  49. package/dist/esm/server/Converters.d.ts.map +1 -0
  50. package/dist/esm/server/Converters.js +343 -0
  51. package/dist/esm/server/Converters.js.map +6 -0
  52. package/dist/esm/server/WebSocketControllerHandler.d.ts +21 -0
  53. package/dist/esm/server/WebSocketControllerHandler.d.ts.map +1 -0
  54. package/dist/esm/server/WebSocketControllerHandler.js +767 -0
  55. package/dist/esm/server/WebSocketControllerHandler.js.map +6 -0
  56. package/dist/esm/types/CommandHandler.d.ts +258 -0
  57. package/dist/esm/types/CommandHandler.d.ts.map +1 -0
  58. package/dist/esm/types/CommandHandler.js +6 -0
  59. package/dist/esm/types/CommandHandler.js.map +6 -0
  60. package/dist/esm/types/WebServer.d.ts +12 -0
  61. package/dist/esm/types/WebServer.d.ts.map +1 -0
  62. package/dist/esm/types/WebServer.js +6 -0
  63. package/dist/esm/types/WebServer.js.map +6 -0
  64. package/dist/esm/types/WebSocketMessageTypes.d.ts +478 -0
  65. package/dist/esm/types/WebSocketMessageTypes.d.ts.map +1 -0
  66. package/dist/esm/types/WebSocketMessageTypes.js +77 -0
  67. package/dist/esm/types/WebSocketMessageTypes.js.map +6 -0
  68. package/dist/esm/util/matterVersion.d.ts +12 -0
  69. package/dist/esm/util/matterVersion.d.ts.map +1 -0
  70. package/dist/esm/util/matterVersion.js +32 -0
  71. package/dist/esm/util/matterVersion.js.map +6 -0
  72. package/dist/esm/util/network.d.ts +14 -0
  73. package/dist/esm/util/network.d.ts.map +1 -0
  74. package/dist/esm/util/network.js +63 -0
  75. package/dist/esm/util/network.js.map +6 -0
  76. package/package.json +45 -0
  77. package/src/controller/AttributeDataCache.ts +194 -0
  78. package/src/controller/ControllerCommandHandler.ts +1256 -0
  79. package/src/controller/LegacyDataInjector.ts +314 -0
  80. package/src/controller/MatterController.ts +265 -0
  81. package/src/controller/Nodes.ts +115 -0
  82. package/src/controller/TestNodeCommandHandler.ts +305 -0
  83. package/src/data/VendorIDs.ts +1234 -0
  84. package/src/example/send-command.ts +82 -0
  85. package/src/index.ts +33 -0
  86. package/src/model/ModelMapper.ts +87 -0
  87. package/src/server/ConfigStorage.ts +112 -0
  88. package/src/server/Converters.ts +483 -0
  89. package/src/server/WebSocketControllerHandler.ts +917 -0
  90. package/src/tsconfig.json +7 -0
  91. package/src/types/CommandHandler.ts +270 -0
  92. package/src/types/WebServer.ts +14 -0
  93. package/src/types/WebSocketMessageTypes.ts +525 -0
  94. package/src/util/matterVersion.ts +45 -0
  95. package/src/util/network.ts +85 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * Simple CLI script to send a command via WebSocket and log responses.
9
+ *
10
+ * Usage: npx ts-node send-command.ts <url> <command> [args-json]
11
+ *
12
+ * Example:
13
+ * npx ts-node send-command.ts ws://localhost:5580/ws server_info
14
+ * npx ts-node send-command.ts ws://localhost:5580/ws get_node '{"node_id": 1}'
15
+ */
16
+
17
+ import WebSocket from "ws";
18
+
19
+ const [, , url, command, argsJson] = process.argv;
20
+
21
+ if (!url || !command) {
22
+ console.error("Usage: send-command <url> <command> [args-json]");
23
+ console.error("");
24
+ console.error("Examples:");
25
+ console.error(" npx ts-node send-command.ts ws://localhost:5580/ws server_info");
26
+ console.error(" npx ts-node send-command.ts ws://localhost:5580/ws get_node '{\"node_id\": 1}'");
27
+ console.error(" npx ts-node send-command.ts ws://localhost:5580/ws start_listening");
28
+ process.exit(1);
29
+ }
30
+
31
+ let args: unknown = undefined;
32
+ if (argsJson) {
33
+ try {
34
+ args = JSON.parse(argsJson);
35
+ } catch (e) {
36
+ console.error("Error parsing args JSON:", (e as Error).message);
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ console.log(`Connecting to ${url}...`);
42
+
43
+ const ws = new WebSocket(url);
44
+
45
+ ws.on("open", () => {
46
+ console.log("Connected!");
47
+
48
+ const message = {
49
+ message_id: `cmd-${Date.now()}`,
50
+ command,
51
+ ...(args !== undefined && { args }),
52
+ };
53
+
54
+ console.log("Sending:", JSON.stringify(message, null, 2));
55
+ ws.send(JSON.stringify(message));
56
+ });
57
+
58
+ ws.on("message", (data: WebSocket.Data) => {
59
+ try {
60
+ const parsed = JSON.parse(data.toString());
61
+ console.log("\n--- Received ---");
62
+ console.log(JSON.stringify(parsed, null, 2));
63
+ } catch {
64
+ console.log("\n--- Received (raw) ---");
65
+ console.log(data.toString());
66
+ }
67
+ });
68
+
69
+ ws.on("error", (error: Error) => {
70
+ console.error("WebSocket error:", error.message);
71
+ });
72
+
73
+ ws.on("close", (code: number, reason: Buffer) => {
74
+ console.log(`\nDisconnected (code: ${code}, reason: ${reason.toString() || "none"})`);
75
+ process.exit(0);
76
+ });
77
+
78
+ // Handle Ctrl+C gracefully
79
+ process.on("SIGINT", () => {
80
+ console.log("\nClosing connection...");
81
+ ws.close();
82
+ });
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * @matter-server/ws-controller - Matter controller Websocket library
9
+ */
10
+
11
+ // Export controller components
12
+ export * from "./controller/ControllerCommandHandler.js";
13
+ export * from "./controller/LegacyDataInjector.js";
14
+ export * from "./controller/MatterController.js";
15
+
16
+ // Export model
17
+ export * from "./model/ModelMapper.js";
18
+
19
+ // Export server handlers and types
20
+ export * from "./server/ConfigStorage.js";
21
+ export * from "./server/Converters.js";
22
+ export * from "./server/WebSocketControllerHandler.js";
23
+ export * from "./types/WebServer.js";
24
+
25
+ // Export message types
26
+ export * from "./types/CommandHandler.js";
27
+ export * from "./types/WebSocketMessageTypes.js";
28
+
29
+ // Export utilities
30
+ export * from "./util/matterVersion.js";
31
+
32
+ // Re-Export classes from matter.js
33
+ export { Crypto, Environment, LogDestination, LogFormat, LogLevel, Logger } from "@matter/main";
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ClusterId } from "@matter/main";
8
+ import {
9
+ AcceptedCommandList,
10
+ AttributeList,
11
+ AttributeModel,
12
+ ClusterModel,
13
+ ClusterRevision,
14
+ CommandModel,
15
+ EventModel,
16
+ FeatureMap,
17
+ GeneratedCommandList,
18
+ MatterModel,
19
+ } from "@matter/model";
20
+
21
+ type AttributeDetails = { readonly [key: string]: AttributeModel | undefined };
22
+
23
+ /** Metadata for Global attributes */
24
+ export const GlobalAttributes: AttributeDetails = Object.freeze({
25
+ clusterRevision: ClusterRevision,
26
+ featureMap: FeatureMap,
27
+ attributeList: AttributeList,
28
+ acceptedCommandList: AcceptedCommandList,
29
+ generatedCommandList: GeneratedCommandList,
30
+ });
31
+
32
+ /**
33
+ * Metadata for all clusters collected in an optimized form for direct access with the incoming websocket requests.
34
+ * All names are just lowercased to prevent differences in camelize and decamelize handling.
35
+ */
36
+ export type ClusterMapEntry = {
37
+ readonly clusterId: ClusterId;
38
+ readonly model: ClusterModel;
39
+ readonly commands: { readonly [key: string]: CommandModel | undefined };
40
+ readonly attributes: AttributeDetails;
41
+ readonly events: { readonly [key: string]: EventModel | undefined };
42
+ };
43
+
44
+ export type ClusterMapType = {
45
+ readonly [key: string]: ClusterMapEntry | undefined;
46
+ };
47
+
48
+ // Build the cluster map at module load time
49
+ const clusterMapBuilder: { [key: string]: ClusterMapEntry | undefined } = {};
50
+
51
+ MatterModel.standard.clusters.forEach(cluster => {
52
+ if (cluster.id === undefined) {
53
+ return;
54
+ } // Skip clusters without an ID
55
+ const aces = cluster.allAces;
56
+ const commands: { [key: string]: CommandModel | undefined } = {};
57
+ const attributes: { [key: string]: AttributeModel | undefined } = {};
58
+ const events: { [key: string]: EventModel | undefined } = {};
59
+
60
+ aces.forEach(ace => {
61
+ const name = ace.name.toLowerCase();
62
+ if (ace instanceof CommandModel) {
63
+ commands[name] = ace;
64
+ commands[ace.id] = ace;
65
+ } else if (ace instanceof AttributeModel) {
66
+ attributes[name] = ace;
67
+ attributes[ace.id] = ace;
68
+ } else if (ace instanceof EventModel) {
69
+ events[name] = ace;
70
+ events[ace.id] = ace;
71
+ }
72
+ });
73
+
74
+ const clusterData: ClusterMapEntry = Object.freeze({
75
+ clusterId: ClusterId(cluster.id),
76
+ model: cluster,
77
+ commands: Object.freeze(commands),
78
+ attributes: Object.freeze(attributes),
79
+ events: Object.freeze(events),
80
+ });
81
+
82
+ clusterMapBuilder[cluster.name.toLowerCase()] = clusterData;
83
+ clusterMapBuilder[cluster.id] = clusterData;
84
+ });
85
+
86
+ /** Readonly map of all clusters frozen after initialization */
87
+ export const ClusterMap: ClusterMapType = Object.freeze(clusterMapBuilder);
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Environment, Logger, StorageContext, StorageManager, StorageService } from "@matter/main";
8
+
9
+ const logger = new Logger("ConfigStorage");
10
+
11
+ interface ConfigData {
12
+ fabricLabel: string;
13
+ nextNodeId: number; // formally wrong, should be bigint
14
+ wifiSsid?: string;
15
+ wifiCredentials?: string;
16
+ threadDataset?: string;
17
+ }
18
+
19
+ export class ConfigStorage {
20
+ #env: Environment;
21
+ #storageService?: StorageService;
22
+ #storage?: StorageManager;
23
+ #configStore?: StorageContext;
24
+ readonly #data: ConfigData = {
25
+ nextNodeId: 2, // 2 because controller is Node Id 1
26
+ fabricLabel: "HomeAssistant",
27
+ wifiSsid: undefined,
28
+ wifiCredentials: undefined,
29
+ threadDataset: undefined,
30
+ };
31
+
32
+ static async create(env: Environment) {
33
+ const instance = new ConfigStorage(env);
34
+ await instance.open();
35
+ return instance;
36
+ }
37
+
38
+ constructor(env: Environment) {
39
+ this.#env = env;
40
+ }
41
+
42
+ get service() {
43
+ if (this.#storageService === undefined) {
44
+ throw new Error("Storage not open");
45
+ }
46
+ return this.#storageService;
47
+ }
48
+
49
+ async open() {
50
+ this.#storageService = this.#env.get(StorageService);
51
+ // Use the parameter "--storage-path=NAME-OR-PATH" to specify a different storage location
52
+ // in this directory, use --storage-clear to start with an empty storage.
53
+ // Or Env vars like MATTER_STORAGE_PATH and MATTER_STORAGE_CLEAR
54
+ logger.info(`Storage location: ${this.#storageService.location} (Directory)`);
55
+ this.#storage = await this.#storageService.open("config");
56
+ this.#configStore = this.#storage.createContext("values");
57
+
58
+ const fabricLabel = (await this.#configStore.has("fabricLabel"))
59
+ ? await this.#configStore.get<string>("fabricLabel")
60
+ : (this.#env.vars.string("fabricLabel") ?? this.#data.fabricLabel);
61
+ const nextNodeId = await this.#configStore.get<number>("nextNodeId", this.#data.nextNodeId);
62
+
63
+ const wifiSsid = (await this.#configStore.has("wifiSsid"))
64
+ ? await this.#configStore.get<string>("wifiSsid", "")
65
+ : undefined;
66
+ const wifiCredentials = (await this.#configStore.has("wifiCredentials"))
67
+ ? await this.#configStore.get<string>("wifiCredentials", "")
68
+ : undefined;
69
+ const threadDataset = (await this.#configStore.has("threadDataset"))
70
+ ? await this.#configStore.get<string>("threadDataset", "")
71
+ : undefined;
72
+ await this.set({ fabricLabel, nextNodeId, wifiSsid, wifiCredentials, threadDataset });
73
+ }
74
+
75
+ get fabricLabel() {
76
+ return this.#data.fabricLabel;
77
+ }
78
+ get nextNodeId() {
79
+ return this.#data.nextNodeId;
80
+ }
81
+ get wifiSsid() {
82
+ return this.#data.wifiSsid;
83
+ }
84
+ get wifiCredentials() {
85
+ return this.#data.wifiCredentials;
86
+ }
87
+ get threadDataset() {
88
+ return this.#data.threadDataset;
89
+ }
90
+
91
+ async set(data: Partial<ConfigData>) {
92
+ if (!this.#configStore) {
93
+ throw new Error("Storage not open");
94
+ }
95
+
96
+ for (const key of Object.keys(data)) {
97
+ if (!(key in this.#data)) {
98
+ throw new Error(`Invalid key: ${key}`);
99
+ }
100
+ // @ts-expect-error key is a valid key and TS make sure about the type
101
+ this.#data[key] = data[key];
102
+ logger.info(`Set config key ${key} to ${data[key as keyof ConfigData]}`);
103
+ await this.#configStore.set(key, data[key as keyof ConfigData]);
104
+ }
105
+ }
106
+
107
+ async close() {
108
+ if (this.#storage) {
109
+ await this.#storage.close();
110
+ }
111
+ }
112
+ }