160 lines
5.4 KiB
PowerShell
160 lines
5.4 KiB
PowerShell
<#
|
|
serial_capture.ps1 - Capture a serial boot log with serial-mcp-server CLI.
|
|
|
|
Workflow (deterministic boot-log capture after flashing):
|
|
1. Open COM via serial-mcp-server `read` CAPTURE mode and WAIT for the first
|
|
byte (up to -InitialTimeoutMs). This starts BEFORE flashing.
|
|
2. Flash the target (download.bat) -> MCU resets and prints the boot log.
|
|
3. The running capture picks up the first byte, then collects for -DurationMs.
|
|
4. The process exits and writes a single structured JSON file at -OutFile.
|
|
|
|
Known trap (Adancurusul/serial-mcp-server, fixed here by retry):
|
|
When COM is released by a previous process and reopened immediately,
|
|
tokio-serial read returns 0 bytes and the project's capture.rs turns that
|
|
into a fatal `UnexpectedEof` error (serial stream returned zero bytes).
|
|
This is transient: a short backoff then reopening succeeds. This script
|
|
detects that error, backs off, and restarts the capture.
|
|
|
|
Usage:
|
|
powershell -ExecutionPolicy Bypass -File serial_capture.ps1 `
|
|
-OutFile C:\temp\boot.json `
|
|
[-Port COM6] [-BaudRate 115200] `
|
|
[-DurationMs 20000] [-InitialTimeoutMs 60000] `
|
|
[-IdleTimeoutMs 1500] [-MaxBytes 65536] `
|
|
[-FlashCmd "D:\...\download.bat"] `
|
|
[-MaxRetries 3] [-RetryDelayMs 1500]
|
|
|
|
Modes:
|
|
-FlashCmd: orchestrates the whole sequence in ONE call: (retry-stable)
|
|
start capture, run the flash/download command, then wait for capture to
|
|
finish. Use this to avoid tool-call gaps letting the capture timeout before
|
|
flashing.
|
|
No -FlashCmd: starts the capture, waits for it to be stable (survives
|
|
-StabilityCheckMs), prints the PID, and returns. Caller flashes, then calls
|
|
with -WaitOnly to reap.
|
|
|
|
Exit codes:
|
|
0 = capture completed (ok or clean timeout)
|
|
1 = capture failed to start after retries
|
|
2 = capture error status in JSON
|
|
|
|
NOTE: ASCII-only on purpose - project source files contain Chinese and must
|
|
never be rewritten by encoding-sensitive PowerShell. Keep this script ASCII.
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$OutFile,
|
|
|
|
[string]$Port = "COM6",
|
|
[int]$BaudRate = 115200,
|
|
[int]$DurationMs = 20000,
|
|
[int]$InitialTimeoutMs = 60000,
|
|
[int]$IdleTimeoutMs = 1500,
|
|
[int]$MaxBytes = 65536,
|
|
[string]$FlashCmd = "",
|
|
[int]$MaxRetries = 3,
|
|
[int]$RetryDelayMs = 1500,
|
|
[int]$StabilityCheckMs = 1500
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$exe = "$env:LOCALAPPDATA\serial-mcp-server\serial-mcp-server.exe"
|
|
if (-not (Test-Path -LiteralPath $exe)) {
|
|
Write-Output "CAPTURE_ERROR: serial-mcp-server binary not found: $exe"
|
|
exit 1
|
|
}
|
|
|
|
$outDir = Split-Path -Parent $OutFile
|
|
if ($outDir -and -not (Test-Path -LiteralPath $outDir)) {
|
|
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
|
|
}
|
|
|
|
$baseArgs = @(
|
|
"read",
|
|
"--port", $Port,
|
|
"--baud", ([string]$BaudRate),
|
|
"--duration-ms", ([string]$DurationMs),
|
|
"--start-trigger", "first-byte",
|
|
"--initial-timeout-ms", ([string]$InitialTimeoutMs),
|
|
"--idle-timeout-ms", ([string]$IdleTimeoutMs),
|
|
"--max-bytes", ([string]$MaxBytes),
|
|
"--json"
|
|
)
|
|
|
|
function Start-CaptureProcess {
|
|
param([int]$attempt)
|
|
$out = $OutFile
|
|
$err = "$OutFile.err"
|
|
if (Test-Path -LiteralPath $out) { Remove-Item -LiteralPath $out -Force }
|
|
if (Test-Path -LiteralPath $err) { Remove-Item -LiteralPath $err -Force }
|
|
if ($attempt -gt 0) {
|
|
Write-Host "RETRY: capture attempt $attempt (backoff $RetryDelayMs ms)"
|
|
Start-Sleep -Milliseconds $RetryDelayMs
|
|
}
|
|
$p = Start-Process -FilePath $exe -ArgumentList $baseArgs `
|
|
-RedirectStandardOutput $out -RedirectStandardError $err `
|
|
-WindowStyle Hidden -PassThru
|
|
if ($p -eq $null -or $p.HasExited) {
|
|
Write-Output "CAPTURE_ERROR: process failed to start (attempt $attempt)"
|
|
return $null
|
|
}
|
|
return $p
|
|
}
|
|
|
|
function Test-CaptureErrored {
|
|
$err = "$OutFile.err"
|
|
if (Test-Path -LiteralPath $err) {
|
|
$e = Get-Content -LiteralPath $err -Raw -ErrorAction SilentlyContinue
|
|
if ($e -match "UnexpectedEof|zero bytes|Error:") { return $true }
|
|
}
|
|
return $false
|
|
}
|
|
|
|
$proc = $null
|
|
for ($i = 0; $i -le $MaxRetries; $i++) {
|
|
$proc = Start-CaptureProcess -attempt $i
|
|
if ($proc -eq $null) { continue }
|
|
|
|
# Wait a moment: if the process died with EOF/error immediately, retry.
|
|
Start-Sleep -Milliseconds $StabilityCheckMs
|
|
if (-not $proc.HasExited) { break } # stable, keep it
|
|
if (-not (Test-CaptureErrored)) { break } # exited cleanly, no error
|
|
Write-Host "CAPTURE_EOF: transient EOF on attempt $i, retrying..."
|
|
$proc = $null
|
|
}
|
|
|
|
if ($proc -eq $null) {
|
|
Write-Output "CAPTURE_ERROR: could not start a stable capture after retries"
|
|
exit 1
|
|
}
|
|
|
|
Write-Output "CAPTURE_PID: $($proc.Id)"
|
|
|
|
if ($FlashCmd) {
|
|
if (-not (Test-Path -LiteralPath $FlashCmd)) {
|
|
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
|
|
Write-Output "CAPTURE_ERROR: flash command not found: $FlashCmd"
|
|
exit 1
|
|
}
|
|
Write-Output "FLASH_START: $FlashCmd"
|
|
& $FlashCmd
|
|
$flashRc = $LASTEXITCODE
|
|
Write-Output "FLASH_RC: $flashRc"
|
|
$proc.WaitForExit()
|
|
Write-Output "CAPTURE_DONE"
|
|
if (Test-Path -LiteralPath $OutFile) {
|
|
$content = Get-Content -LiteralPath $OutFile -Raw -ErrorAction SilentlyContinue
|
|
if ($content -match '"status":\s*"error"') {
|
|
Write-Output "CAPTURE_ERROR: capture returned error status"
|
|
Write-Output $content
|
|
exit 2
|
|
}
|
|
}
|
|
exit 0
|
|
}
|
|
|
|
exit 0
|