From c3d2bfffddcd263eb4f86f4e3d9f39d20cf2263d Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jul 2026 10:23:53 +0200 Subject: [PATCH] fix: bootstrap serial monitor on UART open --- tools/hwlab-node.py | 16 +++++++++++++++- tools/hwpod-node.test.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/tools/hwlab-node.py b/tools/hwlab-node.py index b55377a5..f6ce01ea 100644 --- a/tools/hwlab-node.py +++ b/tools/hwlab-node.py @@ -811,7 +811,21 @@ class NodeOpsExecutor: command.extend([*(["-p", port] if port else []), *(["-b", str(baud)] if baud else [])]) output = self._spawn_output(command, serial_dir, timeout_ms) body = self._parse_json(output.get("stdout")) - return {**output, "ok": output.get("ok") and body.get("success") is True, "bindingSource": "serial-monitor-cli", "port": port or None, "baudRate": baud, "response": body or None} + server_start = None + if action == "start" and self._serial_server_unreachable(body): + server_start = self._spawn_output([*command_base, "server", "start"], serial_dir, timeout_ms) + server_body = self._parse_json(server_start.get("stdout")) + server_start = {**server_start, "response": server_body or None} + if server_start.get("ok") and server_body.get("success") is True: + output = self._spawn_output(command, serial_dir, timeout_ms) + body = self._parse_json(output.get("stdout")) + return {**output, "ok": output.get("ok") and body.get("success") is True, "bindingSource": "serial-monitor-cli", "port": port or None, "baudRate": baud, "response": body or None, "serverStart": server_start} + + def _serial_server_unreachable(self, body: dict) -> bool: + data = body.get("data") if isinstance(body.get("data"), dict) else {} + request = data.get("request") if isinstance(data.get("request"), dict) else {} + diagnosis = data.get("diagnosis") if isinstance(data.get("diagnosis"), dict) else {} + return body.get("success") is False and (request.get("code") == "ECONNREFUSED" or diagnosis.get("status") == "not_running") def _uart_write(self, args: dict) -> dict: data = str(args.get("data") or "") diff --git a/tools/hwpod-node.test.ts b/tools/hwpod-node.test.ts index 97bc8dc7..d154e95b 100644 --- a/tools/hwpod-node.test.ts +++ b/tools/hwpod-node.test.ts @@ -396,6 +396,40 @@ print(json.dumps({"results": results, "commands": executor.commands})) assert.deepEqual(result.results.map((item: any) => item.status), ["completed", "completed", "completed"]); }); +test("python hwpod-node starts an unreachable serial-monitor server before opening UART", () => { + const script = ` +import importlib.util, json, logging +spec = importlib.util.spec_from_file_location("hwlab_node", "tools/hwlab-node.py") +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) +class Executor(module.NodeOpsExecutor): + def __init__(self): + super().__init__({"nodeId": "node-test"}, logging.getLogger("test")) + self.commands = [] + def _spawn_output(self, command, cwd, timeout_ms): + self.commands.append(command) + if command[-2:] == ["server", "start"]: + body = {"success": True} + elif len(self.commands) == 1: + body = {"success": False, "data": {"request": {"code": "ECONNREFUSED"}, "diagnosis": {"status": "not_running"}}} + else: + body = {"success": True} + return {"ok": True, "stdout": json.dumps(body), "stderr": "", "exitCode": 0} +executor = Executor() +result = executor._execute_op({"opId": "open", "op": "io.uart.open", "args": {"workspacePath": ".", "port": "COM4", "baudRate": 921600, "serialMonitorCommand": ["serial-monitor"]}}) +print(json.dumps({"result": result, "commands": executor.commands})) +`; + const completed = spawnSync("python3", ["-c", script], { cwd: process.cwd(), encoding: "utf8" }); + assert.equal(completed.status, 0, completed.stderr); + const result = JSON.parse(completed.stdout); + assert.equal(result.result.status, "completed"); + assert.deepEqual(result.commands, [ + ["serial-monitor", "monitor", "start", "-p", "COM4", "-b", "921600"], + ["serial-monitor", "server", "start"], + ["serial-monitor", "monitor", "start", "-p", "COM4", "-b", "921600"], + ]); +}); + test("hwpod-node resolves Git for Windows when service PATH omits git", async () => { const result = await resolveHwpodNodeCommand("git", { platform: "win32",