From 08fec46af930c3dd94f5f554a572b8df2f665564 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 15:27:31 +0800 Subject: [PATCH] fix: auto-detect devicepod uart ports --- .../device-pod-cli/assets/device-host-cli.mjs | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/skills/device-pod-cli/assets/device-host-cli.mjs b/skills/device-pod-cli/assets/device-host-cli.mjs index 8be040c6..900a3cbd 100644 --- a/skills/device-pod-cli/assets/device-host-cli.mjs +++ b/skills/device-pod-cli/assets/device-host-cli.mjs @@ -1362,7 +1362,7 @@ async function runKeilDownload(profile, payload = {}) { } async function runKeilDownloadWithUartCapture(profile, uv4Path, uv4Args, cwd, payload = {}) { - const uart = resolveUart(profile, payload['capture-uart'] || payload.captureUart, payload); + const uart = await resolveUart(profile, payload['capture-uart'] || payload.captureUart, payload); const captureDurationMs = Number(payload['capture-duration-ms'] || payload.captureDurationMs || payload['duration-ms'] || payload.durationMs || 8000); const timeoutMs = Number(payload.timeoutMs || 300000); const logDir = path.join(stateRoot(profile.__workspaceRoot), 'serial'); @@ -1551,12 +1551,56 @@ async function serialPorts() { ok('io-probe.ports', { configured: profile.ioInterface?.uart || [], ports, stderr: result.stderr, exitCode: result.exitCode }); } -function resolveUart(profile, uartId, options = {}) { +async function resolveSerialPortNames() { + const script = "[System.IO.Ports.SerialPort]::GetPortNames() | Sort-Object | ConvertTo-Json -Compress"; + const result = await runPowerShell(script, 15000); + if (result.exitCode !== 0) return { ports: [], error: shortTail(result.stderr || result.stdout) }; + const text = result.stdout.trim(); + if (!text) return { ports: [], error: null }; + try { + const parsed = JSON.parse(text); + const ports = (Array.isArray(parsed) ? parsed : [parsed]).map((item) => String(item || '').trim()).filter(Boolean); + return { ports, error: null }; + } catch (error) { + return { ports: [], error: `unable to parse serial port list: ${error instanceof Error ? error.message : String(error)}` }; + } +} + +function sameSerialPort(left, right) { + return String(left || '').trim().toUpperCase() === String(right || '').trim().toUpperCase(); +} + +async function resolveUartPort(configuredPort, options = {}) { + if (options.port) return { port: String(options.port), source: 'explicit-option', configuredPort: configuredPort || null, availablePorts: null, resolutionWarning: null }; + const configured = String(configuredPort || '').trim(); + const discovery = await resolveSerialPortNames(); + const availablePorts = discovery.ports; + if (configured && availablePorts.some((port) => sameSerialPort(port, configured))) { + return { port: configured, source: 'profile', configuredPort: configured, availablePorts, resolutionWarning: null }; + } + if (availablePorts.length > 0) { + return { + port: availablePorts[0], + source: configured ? 'host-auto-detected-profile-missing' : 'host-auto-detected', + configuredPort: configured || null, + availablePorts, + resolutionWarning: configured ? `configured UART port ${configured} is not present on this host; using ${availablePorts[0]}` : null, + }; + } + return { port: configured, source: configured ? 'profile-unverified' : 'missing', configuredPort: configured || null, availablePorts, resolutionWarning: discovery.error || 'no serial ports detected on this host' }; +} + +async function resolveUart(profile, uartId, options = {}) { const normalized = String(uartId || 'uart/1').trim().replace(/\\/gu, '/').replace(/\s*\/\s*/gu, '/').replace(/^\/+/, ''); const item = (profile.ioInterface?.uart || []).find((entry) => entry.id === normalized); if (!item) throw new Error(`uart profile not found: ${normalized}`); const resolved = { ...item }; - if (options.port) resolved.port = String(options.port); + const portResolution = await resolveUartPort(resolved.port, options); + resolved.port = portResolution.port; + resolved.resolvedPortSource = portResolution.source; + resolved.configuredPort = portResolution.configuredPort; + resolved.availablePorts = portResolution.availablePorts; + if (portResolution.resolutionWarning) resolved.resolutionWarning = portResolution.resolutionWarning; const baudRate = options['baud-rate'] || options.baudRate; if (baudRate !== undefined) { const parsed = Number(baudRate); @@ -1568,7 +1612,7 @@ function resolveUart(profile, uartId, options = {}) { async function serialRead(uartId, options) { const profile = loadProfile(); - const uart = resolveUart(profile, uartId, options); + const uart = await resolveUart(profile, uartId, options); const durationMs = Number(options['duration-ms'] || options.durationMs || 1000); const script = [ '$ErrorActionPreference = \'Stop\';', @@ -1593,7 +1637,7 @@ async function serialRead(uartId, options) { async function serialReadAfterLaunchFlash(uartId, options) { const profile = loadProfile(); - const uart = resolveUart(profile, uartId, options); + const uart = await resolveUart(profile, uartId, options); const tools = await toolInfo(profile); if (!tools.pyocd.found) throw new Error('pyOCD executable not found'); const debug = profile.debugInterface || {}; @@ -1660,7 +1704,7 @@ async function serialReadAfterLaunchFlash(uartId, options) { async function serialWrite(uartId, options) { const profile = loadProfile(); - const uart = resolveUart(profile, uartId, options); + const uart = await resolveUart(profile, uartId, options); const message = options.hex ? Buffer.from(String(options._[0] || ''), 'hex').toString('binary') : String(options._[0] || ''); const encoded = Buffer.from(message, options.hex ? 'binary' : 'utf8').toString('base64'); const script = [ @@ -1759,7 +1803,7 @@ function serialLineEnding(options) { async function serialJsonRpc(uartId, options) { const profile = loadProfile(); - const uart = resolveUart(profile, uartId, options); + const uart = await resolveUart(profile, uartId, options); const request = buildJsonRpcRequest(options); const ending = serialLineEnding(options); const requestText = `${JSON.stringify(request)}${ending.text}`;