firmwareloop 0.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tools/flash.ps1 ADDED
@@ -0,0 +1,181 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Flash fallback adapter (Spec §10). Preferred path is Agentic HIL; this
4
+ wrapper exists only for targets Agentic HIL does not support yet.
5
+
6
+ Usage:
7
+ .\tools\flash.ps1 -Backend openocd -Artifact artifacts\build\firmware.elf -Json
8
+ .\tools\flash.ps1 -Backend agentic-hil -Artifact artifacts\build\firmware.elf -Json
9
+
10
+ Hard constraints (Spec §10/§24):
11
+ - artifact must be one of the conventional names under artifacts/build/
12
+ - mass erase / security programming flags are rejected before execution
13
+ - every external process has a timeout
14
+ - returns JSON (firmware-flash-result/v1) and saves the raw log
15
+
16
+ Without a configured backend this exits CONFIG_ERROR - flash is intentionally
17
+ not configured until real hardware integration (Spec §31 step 7).
18
+ #>
19
+ [CmdletBinding()]
20
+ param(
21
+ [Parameter(Mandatory = $true)][ValidateSet('simulator', 'agentic-hil', 'openocd', 'stm32cubeprogrammer', 'esptool', 'jlink', 'vendor')]
22
+ [string]$Backend,
23
+ [string]$Artifact = 'artifacts/build/firmware.elf',
24
+ [switch]$Json,
25
+ [int]$TimeoutMs = 120000
26
+ )
27
+
28
+ Set-StrictMode -Version Latest
29
+ $ErrorActionPreference = 'Stop'
30
+ Import-Module (Join-Path $PSScriptRoot 'common\fw.psm1') -Force
31
+
32
+ $repoRoot = Get-FwRepoRoot
33
+ $artifactPath = if ([System.IO.Path]::IsPathRooted($Artifact)) { $Artifact } else { Join-Path $repoRoot $Artifact }
34
+
35
+ function Exit-WithError {
36
+ param([string]$Class, [string]$Message, [string]$Detail)
37
+ $body = New-FwError -ErrorClass $Class -Message $Message -Detail $Detail
38
+ if ($Json) { Write-FwJson $body -Compact } else { Write-FwJson $body; Write-Error "$Class : $Message" -ErrorAction Continue }
39
+ exit 2
40
+ }
41
+
42
+ # --- artifact guard (Spec §10) -------------------------------------------------
43
+ $allowed = @('firmware.elf', 'firmware.hex', 'firmware.bin')
44
+ $name = Split-Path $artifactPath -Leaf
45
+ if ($name -notin $allowed) {
46
+ Exit-WithError -Class 'ARTIFACT_NOT_FOUND' -Message "artifact name '$name' is not flashable; allowed: $($allowed -join ', ')" -Detail $artifactPath
47
+ }
48
+ if (-not (Test-Path -LiteralPath $artifactPath)) {
49
+ Exit-WithError -Class 'ARTIFACT_NOT_FOUND' -Message 'artifact does not exist; run tools/build.ps1 first.' -Detail $artifactPath
50
+ }
51
+ $full = (Resolve-Path -LiteralPath $artifactPath).Path
52
+ $buildRoot = (Join-Path $repoRoot 'artifacts\build')
53
+ if (-not $full.StartsWith((Resolve-Path -LiteralPath $buildRoot).Path, [System.StringComparison]::OrdinalIgnoreCase)) {
54
+ Exit-WithError -Class 'PERMISSION_DENIED' -Message 'artifact is outside artifacts/build; refusing to flash.' -Detail $full
55
+ }
56
+
57
+ # --- backend dispatch ----------------------------------------------------------
58
+ $stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
59
+ $logFile = Join-Path $repoRoot "artifacts\logs\flash-$stamp.log"
60
+ New-Item -ItemType Directory -Force -Path (Split-Path $logFile) | Out-Null
61
+
62
+ switch ($Backend) {
63
+ 'simulator' {
64
+ Save-FwLog -Path $logFile -Content 'simulator backend: flash validated via firmware build + HIL run (no real programmer involved)'
65
+ $result = [ordered]@{
66
+ schema = 'firmware-flash-result/v1'
67
+ ok = $true
68
+ backend = 'simulator'
69
+ artifact = $full
70
+ artifact_sha256 = (Get-FileHash -LiteralPath $full -Algorithm SHA256).Hash.ToLowerInvariant()
71
+ simulated = $true
72
+ hardware_validated = $false
73
+ log = (Resolve-Path -LiteralPath $logFile).Path
74
+ generated_at = Get-FwTimestamp
75
+ }
76
+ if ($Json) { Write-FwJson ([pscustomobject]$result) -Compact } else { Write-FwJson ([pscustomobject]$result) }
77
+ exit 0
78
+ }
79
+ 'pyocd' {
80
+ $pyocdExe = Join-Path $repoRoot '.venv\Scripts\pyocd.exe'
81
+ if (-not (Test-Path -LiteralPath $pyocdExe)) {
82
+ $cmd = Get-Command pyocd -ErrorAction SilentlyContinue
83
+ if ($cmd) { $pyocdExe = $cmd.Source }
84
+ }
85
+ if (-not (Test-Path -LiteralPath $pyocdExe)) {
86
+ Exit-WithError -Class 'TOOLCHAIN_NOT_FOUND' -Message 'pyocd 未找到。请在 Python 环境中安装 pyocd (`uv pip install pyocd`)。' -Detail 'pyOCD 用于通过 ST-LINK / CMSIS-DAP 烧录 STM32 芯片。'
87
+ }
88
+ $targetChip = 'stm32f103rc'
89
+ $labYaml = Join-Path $repoRoot 'lab\lab.yaml'
90
+ if (Test-Path -LiteralPath $labYaml) {
91
+ $labContent = Get-Content -LiteralPath $labYaml -Raw
92
+ if ($labContent -match 'target_chip:\s*["'']?([^"''\r\n]+)') {
93
+ $targetChip = $matches[1].Trim()
94
+ }
95
+ }
96
+ $res = Invoke-FwProcess -FilePath $pyocdExe -Arguments @('flash', '-t', $targetChip.ToLowerInvariant(), $full) -WorkingDirectory $repoRoot -TimeoutMs $TimeoutMs -StdoutFile $logFile
97
+ if ($res.timed_out) {
98
+ Exit-WithError -Class 'TIMEOUT' -Message "pyocd 烧录超时(${TimeoutMs}ms)。" -Detail $logFile
99
+ }
100
+ if ($res.exit_code -ne 0) {
101
+ Exit-WithError -Class 'FLASH_ERROR' -Message "pyocd 烧录失败:未检测到物理探针或目标 MCU 无响应。请确保 ST-LINK / DAPLink 已插入 USB 接口并正确连接 SWD 信号线(SWCLK/SWDIO/GND/3V3)。" -Detail ($res.stdout + "`n" + $res.stderr)
102
+ }
103
+ $result = [ordered]@{
104
+ schema = 'firmware-flash-result/v1'
105
+ ok = $true
106
+ backend = 'pyocd'
107
+ target = $targetChip
108
+ artifact = $full
109
+ artifact_sha256 = (Get-FileHash -LiteralPath $full -Algorithm SHA256).Hash.ToLowerInvariant()
110
+ log = (Resolve-Path -LiteralPath $logFile).Path
111
+ generated_at = Get-FwTimestamp
112
+ }
113
+ if ($Json) { Write-FwJson ([pscustomobject]$result) -Compact } else { Write-FwJson ([pscustomobject]$result) }
114
+ exit 0
115
+ }
116
+ 'jlink' {
117
+ $jlinkExe = (Get-Command JLink.exe -ErrorAction SilentlyContinue)?.Source
118
+ if (-not $jlinkExe) {
119
+ $candidates = @(
120
+ "C:\Program Files\SEGGER\JLink\JLink.exe",
121
+ "C:\Program Files (x86)\SEGGER\JLink\JLink.exe"
122
+ )
123
+ foreach ($c in $candidates) {
124
+ if (Test-Path -LiteralPath $c) { $jlinkExe = $c; break }
125
+ }
126
+ }
127
+ if (-not $jlinkExe) {
128
+ Exit-WithError -Class 'TOOLCHAIN_NOT_FOUND' -Message '未找到 SEGGER JLink.exe。请安装官方 SEGGER J-Link 驱动软件包。' -Detail '下载地址: https://www.segger.com/downloads/jlink/'
129
+ }
130
+ $cmdScript = Join-Path $repoRoot "artifacts\logs\jlink_flash_$stamp.jlink"
131
+ $scriptContent = "r`nh`nloadfile $full`nr`ng`nq`n"
132
+ Set-Content -LiteralPath $cmdScript -Value $scriptContent -Encoding ASCII
133
+ $targetChip = 'STM32F103C8'
134
+ $res = Invoke-FwProcess -FilePath $jlinkExe -Arguments @('-device', $targetChip, '-if', 'SWD', '-speed', '4000', '-autoconnect', '1', '-CommanderScript', $cmdScript) -WorkingDirectory $repoRoot -TimeoutMs $TimeoutMs -StdoutFile $logFile
135
+ if ($res.timed_out) {
136
+ Exit-WithError -Class 'TIMEOUT' -Message "J-Link 烧录超时(${TimeoutMs}ms)。" -Detail $logFile
137
+ }
138
+ if ($res.exit_code -ne 0) {
139
+ Exit-WithError -Class 'FLASH_ERROR' -Message "J-Link 烧录失败:未连接 J-Link 探针或目标板未供电。" -Detail ($res.stdout + "`n" + $res.stderr)
140
+ }
141
+ $result = [ordered]@{
142
+ schema = 'firmware-flash-result/v1'
143
+ ok = $true
144
+ backend = 'jlink'
145
+ target = $targetChip
146
+ artifact = $full
147
+ artifact_sha256 = (Get-FileHash -LiteralPath $full -Algorithm SHA256).Hash.ToLowerInvariant()
148
+ log = (Resolve-Path -LiteralPath $logFile).Path
149
+ generated_at = Get-FwTimestamp
150
+ }
151
+ if ($Json) { Write-FwJson ([pscustomobject]$result) -Compact } else { Write-FwJson ([pscustomobject]$result) }
152
+ exit 0
153
+ }
154
+ 'agentic-hil' {
155
+ Exit-WithError -Class 'REAL_HARDWARE_REQUIRED' `
156
+ -Message "Agentic HIL 烧录请直接使用 MCP 工具 flash_firmware,或执行 test-plans/real-smoke.yaml。" `
157
+ -Detail "禁止随意使用伪造的 CLI。"
158
+ }
159
+ default {
160
+ Exit-WithError -Class 'CONFIG_ERROR' -Message "烧录后端 '$Backend' 尚未在本机配置。" -Detail '请在 lab/lab.yaml 中配置有效的烧录器后端(pyocd, jlink, stm32cubeprogrammer 等)。'
161
+ }
162
+ }
163
+
164
+ if ($res.timed_out) {
165
+ Exit-WithError -Class 'FLASH_ERROR' -Message 'flash backend timed out and was killed.' -Detail $logFile
166
+ }
167
+ if ($res.exit_code -ne 0) {
168
+ Exit-WithError -Class 'FLASH_ERROR' -Message 'flash backend reported failure.' -Detail ($res.stdout + $res.stderr)
169
+ }
170
+
171
+ $result = [ordered]@{
172
+ schema = 'firmware-flash-result/v1'
173
+ ok = $true
174
+ backend = $Backend
175
+ artifact = $full
176
+ artifact_sha256 = (Get-FileHash -LiteralPath $full -Algorithm SHA256).Hash.ToLowerInvariant()
177
+ log = (Resolve-Path -LiteralPath $logFile).Path
178
+ generated_at = Get-FwTimestamp
179
+ }
180
+ if ($Json) { Write-FwJson ([pscustomobject]$result) -Compact } else { Write-FwJson ([pscustomobject]$result) }
181
+ exit 0