#!/usr/bin/env node /** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Environment, LogDestination, LogFormat, Logger, LogLevel } from "@matter/general"; import { createFileLogger } from "@matter/nodejs"; import "@matter/nodejs-ble"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { MatterNode } from "./MatterNode.js"; import { Shell } from "./shell/Shell.js"; import { initializeWebPlumbing } from "./web_plumbing.js"; const PROMPT = "matter> "; const DEFAULT_WEBSOCKET_PORT = 3000; const logger = Logger.get("Shell"); let theShell: Shell; if (process.stdin?.isTTY) Logger.format = LogFormat.ANSI; let theNode: MatterNode; export function setLogLevel(identifier: string, level: string): void { let logLevel = LogLevel.INFO; switch (level) { case "fatal": logLevel = LogLevel.FATAL; break; case "error": logLevel = LogLevel.ERROR; break; case "warn": logLevel = LogLevel.WARN; break; case "debug": logLevel = LogLevel.DEBUG; break; } Logger.destinations[identifier].level = logLevel; } /** * @file Top level application for Matter Node. */ async function main() { const yargsInstance = yargs() .command( "* [node-num] [node-type]", "Matter Node Shell", yargs => { return yargs .positional("node-num", { describe: "Node number for storage", default: 0, type: "number", }) .positional("node-type", { describe: "Type of the node", choices: ["controller"], default: "controller", type: "string", }) .options({ "ble-enable": { description: "Enable BLE support.", type: "boolean", default: false, alias: "b", }, bleHciId: { description: "HCI ID of the BLE adapter to use. The provided value will be persisted for future runs.", type: "number", default: 0, }, factoryReset: { description: "Factory-Reset storage of this node.", default: false, type: "boolean", }, netInterface: { description: "Network interface to use for MDNS announcements and scanning.", type: "string", default: undefined, }, logfile: { description: "Logfile to use to log to. By Default debug loglevel is logged to the file. The provided value will be persisted for future runs.", type: "string", default: undefined, }, webSocketInterface: { description: "Enable WebSocket interface", type: "boolean", default: false, }, webSocketPort: { description: "WebSocket and HTTP server port", type: "number", default: DEFAULT_WEBSOCKET_PORT, }, webServer: { description: "Enable Web server when using WebSocket interface", type: "boolean", default: false, }, }); }, async argv => { if (argv.help) { return; } const { nodeNum, bleEnable, bleHciId, nodeType, factoryReset, netInterface, logfile, webSocketInterface, webSocketPort, webServer, } = argv; theNode = new MatterNode(nodeNum, netInterface); await theNode.initialize(factoryReset); if (logfile !== undefined) { await theNode.Store.set("LogFile", logfile); } if (await theNode.Store.has("LogFile")) { const storedLogFileName = await theNode.Store.get("LogFile"); if (storedLogFileName !== undefined) { Logger.destinations.file = LogDestination({ write: await createFileLogger(storedLogFileName), level: LogLevel(await theNode.Store.get("LoglevelFile", LogLevel.DEBUG)), format: LogFormat("plain"), }); } } setLogLevel("default", await theNode.Store.get("LogLevel", "info")); if (webSocketInterface) { Logger.format = LogFormat.PLAIN; initializeWebPlumbing(theNode, nodeNum, webSocketPort, webServer); // set up but wait for connect to create Shell } else { theShell = new Shell(theNode, nodeNum, PROMPT, process.stdin, process.stdout); } if (bleEnable) { Environment.default.vars.set("ble.enable", true); } if (bleHciId !== undefined) { Environment.default.vars.set("ble.hci.id", bleHciId); } console.log(`Started Node #${nodeNum} (Type: ${nodeType}) ${bleEnable ? "with" : "without"} BLE`); if (!webSocketInterface) { theShell.start(theNode.storageLocation); } }, ) .version(false) .scriptName("shell") .strict(); await yargsInstance.wrap(yargsInstance.terminalWidth()).parse(hideBin(process.argv)); } process.on("message", function (message) { console.log(`Message to shell.ts: ${message}`); switch (message) { case "exit": exit().catch(error => logger.error("Error during exit", error)); } }); export async function exit(code = 0) { process.exitCode = code; Environment.default.runtime.cancel(); } const runtime = Environment.default.runtime; runtime.add(main()); void runtime.inactive.then(() => process.exit(process.exitCode ?? 0));