<# .SYNOPSIS Attach the USB Uno Q board to your LOCAL self-host stack (Windows 11). .DESCRIPTION PowerShell twin of connect-board.sh. Runs adb ON the Windows host so Docker Desktop's host.docker.internal reaches the forwarded port. Forwards the tunnels and registers the board with the containerized API; in LOCAL_MODE the API auto-binds the board to your team in the browser — no claim code. .EXAMPLE .\connect-board.ps1 # attach once .\connect-board.ps1 -Watch # re-attach on every (re)connect (leave running) .NOTES Env overrides: WEB_URL, FLEET_SECRET, KIT_ID, NODE_URL, SERIAL If Windows blocks the script, run it as: powershell -NoProfile -ExecutionPolicy Bypass -File .\connect-board.ps1 (or just double-click connect-board.bat). #> param([switch]$Watch) $ErrorActionPreference = 'Stop' $Web = if ($env:WEB_URL) { $env:WEB_URL } else { 'http://localhost:8090' } $Api = "$Web/api" $Secret = if ($env:FLEET_SECRET) { $env:FLEET_SECRET } else { 'apess2026' } $KitId = if ($env:KIT_ID) { $env:KIT_ID } else { 'crimson-node' } # How the API *container* reaches the board: adb binds the Windows host loopback, # and Docker Desktop maps host.docker.internal to the Windows host. $NodeUrl = if ($env:NODE_URL) { $env:NODE_URL } else { 'http://host.docker.internal:8080' } $Ports = @(8080, 9999) function Log($m) { Write-Host "[connect] $m" -ForegroundColor Cyan } function Ok($m) { Write-Host " [OK] $m" -ForegroundColor Green } function Warn($m) { Write-Host " [!] $m" -ForegroundColor Yellow } if (-not (Get-Command adb -ErrorAction SilentlyContinue)) { Write-Host "connect-board: adb not found on PATH. Install Android platform-tools and reopen the terminal." -ForegroundColor Red exit 127 } function Get-Serial { if ($env:SERIAL) { return $env:SERIAL } foreach ($line in (& adb devices)) { if ($line -match '^(\S+)\s+device$') { return $Matches[1] } } return $null } function Connect-Once { $serial = Get-Serial if (-not $serial) { Warn 'no board attached over USB'; return $false } Ok "board $serial attached" # 1 - forward tunnels (they vanish on re-plug) $existing = (& adb -s $serial forward --list) -join "`n" foreach ($p in $Ports) { if ($existing -notmatch "tcp:$p") { & adb -s $serial forward "tcp:$p" "tcp:$p" | Out-Null } } Ok "tunnels forwarded ($($Ports -join ' '))" # 2 - wait for the board daemon (App Lab app auto-starts on boot) $n = 0 while ($true) { try { Invoke-WebRequest -Uri 'http://127.0.0.1:8080/health' -TimeoutSec 2 -UseBasicParsing | Out-Null; break } catch { $n++ if ($n -gt 90) { Warn 'board daemon never came up'; return $false } Start-Sleep -Seconds 2 } } Ok 'board daemon healthy' # 3 - register with the LOCAL stack (LOCAL_MODE auto-binds it in the browser) $body = @{ kitId = $KitId; claimCode = 'local'; url = $NodeUrl; token = 'open-lan' } | ConvertTo-Json -Compress try { $r = Invoke-RestMethod -Uri "$Api/nodes/self-register" -Method Post ` -Headers @{ 'x-fleet-secret' = $Secret; 'content-type' = 'application/json' } ` -Body $body -TimeoutSec 5 if ($r.url) { Ok 'registered with the local stack' Log 'attached — it auto-connects in the browser (no code needed)' return $true } Warn "register returned an unexpected response: $($r | ConvertTo-Json -Compress)" return $false } catch { Warn "register failed — is the stack up? ($Web) · $($_.Exception.Message)" return $false } } if ($Watch) { Log 'watching for the board — will attach on every (re)connect (Ctrl-C to stop)' while ($true) { & adb wait-for-device | Out-Null Start-Sleep -Seconds 3 # let Linux + the App Lab app finish booting if (-not (Connect-Once)) { Warn 'attach incomplete; will retry on next reconnect' } while (Get-Serial) { Start-Sleep -Seconds 2 } Log 'board disconnected — waiting for re-plug' } } else { [void](Connect-Once) }