بنقرة واحدة
upy-autofix
第六步——编排协调层。读取设备日志,解析错误,分级决策后委托上游 skill 修复(generate/select-hw/analyze),最多 3 次尝试。触发:upy-deploy 运行失败后自动进入。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
第六步——编排协调层。读取设备日志,解析错误,分级决策后委托上游 skill 修复(generate/select-hw/analyze),最多 3 次尝试。触发:upy-deploy 运行失败后自动进入。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Analyze MicroPythonOS App ideas directly or when invoked by mpos-plan-app. Use to turn natural-language MPOS App requests into requirements, default app identity, manifest draft, Activity/Service plan, MPOS/LVGL API plan, dependency risk, test/deploy plan, mandatory MicroPythonOS resource links, and a JSON handoff before code generation.
Deploy or preview a MicroPythonOS app on desktop, web, device copy, MPK install, or installer/flash guidance paths. Use when Codex needs to launch a confirmed app for manual preview, copy it to a board with mpremote, validate an MPK on-device, or route firmware install and erase to install.micropythonos.com. Does not own app generation, static lint, packaging, or default smoke testing.
MicroPythonOS 基础开发知识库。提供代码架构、App/MPK 约束、LVGL 编程约定、MPY API reference、官方 docs 专题 reference、AGENTS 本地强约束。mpos-plan-app / mpos-analyze-app / mpos-prepare-deps / mpos-gen-app / mpos-test-app / mpos-package-app / mpos-deploy-app / mpos-publish-app 均依赖此 skill。
Generate, update, and repeatedly repair MicroPythonOS App code after requirements are confirmed. Use after mpos-analyze-app and optionally mpos-prepare-deps to create or modify an internal_filesystem/apps package directory with root MANIFEST.JSON, root icon_64x64.png, assets/*.py entrypoints/dependencies, dependency adapters, and validation results. Always defaults to a two-phase flow: first produce a generation plan and ask for confirmation, then write files only after explicit user confirmation. Supports repeated calls for user feature changes and test-failure repair loops. Does not analyze vague requirements, prepare external dependencies, package MPK files, deploy devices, flash firmware, publish to upystore, or rebuild lvgl_micropython.
Package and validate a single MicroPythonOS App as an MPK release artifact. Use when Codex needs to create a .mpk, validate an MPOS App manifest/icon/package structure, emit one app_index_entry.json fragment, run optional temporary install validation, or prepare AppStore/upystore publishing artifacts without uploading.
Orchestrate a MicroPythonOS App workflow across analyze, dependency preparation, generation, testing, packaging, deployment, and upystore publishing. Use when Codex needs to start from a natural-language app request, continue or resume an interrupted MPOS app task, decide the next mpos-* skill, maintain per-app plan_state.json and activity_log.jsonl under tmp/mpos-plan-app, handle user requirement changes with invalidation confirmation, or run the default path through mpos-publish-app. Does not implement code, download dependencies, test, package, deploy, flash, or upload directly.
| name | upy-autofix |
| description | 第六步——编排协调层。读取设备日志,解析错误,分级决策后委托上游 skill 修复(generate/select-hw/analyze),最多 3 次尝试。触发:upy-deploy 运行失败后自动进入。 |
编排协调层,不是独立修复机。 核心逻辑:triage.py 采集结构化数据 → LLM 读取 JSON + 原始日志 → 分级决策 → 委托上游 skill 修复 → 验证。
脚本只做数据采集 + git 管理 + 硬件信号驱动,不做修复决策。所有判断由 LLM 完成。
新增:硬件信号验证能力 — 软件修复 2 次无效后,autofix 可以主动驱动外设(LED 闪烁/蜂鸣器响/传感器读数/显示器填色),通过自检或用户反馈判定硬件是否正常,避免在坏硬件上无限循环修代码。
upy-deploy Phase 6 判定 FAILdeploy_logs/ 目录下有设备端原始日志文件(run_*.log)triage.py 可用(本 skill 自带的脚本)python G:/MicroPython_Skills/upy-autofix/scripts/triage.py \
--log-dir {deploy_logs路径} \
--port {COM} \
--attempt 1
输出 JSON 到 stdout,LLM 捕获并解析。JSON 结构见 triage.py 文件头部注释。
每个字段都有默认值——脚本已做 try/except,不会因日志格式异常而崩溃。warnings 字段列出所有降级情况。
LLM 同时读取两个信息源:
| 来源 | 作用 | 何时读 |
|---|---|---|
| triage.py JSON | 快速定位:错误类型、P 级别、I2C 状态、attempt 计数 | 每次都读 |
| deploy_logs/*.log 原始日志 | 深度理解:完整 traceback、print 时序、上下文 | JSON 不足以判断时 |
研判顺序:
先看 JSON 的 i2c_ok 字段
false → 硬件问题,跳 Step 5(输出排查指引),不修代码true 或 null(无 I2C 设备)→ 软件问题,继续看 JSON 的 p_level + error_type
unknown → 读原始日志,LLM 独立判断错误类型后走对应路径判断是否需要硬件信号验证(Step 2.5)——任一满足即触发:
attempt >= 2 且同一 error_type 连续出现 → 软件修复无效,怀疑硬件error_type = "NoOutput" 或 "unknown" → 代码跑了但没产出,怀疑沉默外设error_type = "OSError_19" 或 "OSError_110" → 外设通信失败,可能是接线/供电当 Step 2 触发条件命中时,不直接进入软件修复。先做硬件诊断。
LLM 通读 firmware/ 全部 .py 源码 + project-manifest.json:
识别对象:
├── machine.Pin(x) → GPIO 引脚, 外设角色(从变量名/上下文推断)
├── machine.PWM(Pin(x)) → PWM 输出 (LED/蜂鸣器/舵机)
├── machine.I2C(...) → I2C 总线 + 从设备地址
├── machine.SPI(...) → SPI 总线 + CS 引脚
├── machine.UART(...) → UART 总线 + 波特率
├── machine.ADC(Pin(x)) → 模拟输入
└── from xxx import Xxx → 驱动类实例化, __init__ 参数模式
然后按附录 A 的模板,为每个外设生成 sanity_config.json:
生成原则:
python G:/MicroPython_Skills/upy-autofix/scripts/hardware_sanity.py \
--config {project_dir}/sanity_config.json \
--port {COM}
输出 JSON 到 stdout,LLM 捕获。
读 JSON:
├── 全部 PASS → 硬件正常,问题在代码逻辑 → 继续 Step 3 委托修复
│
├── 特定外设 FAIL (I2C 传感器 scan 失败 / 读 WHO_AM_I 不匹配)
│ → 输出该外设定向排查指引 (接线/供电/地址冲突)
│ → 不继续修代码
│
├── 特定外设 FAIL (用户反馈型: LED不亮/蜂鸣器不响)
│ → 输出该外设定向排查指引
│ → 不继续修代码
│
├── 板载 LED 也 FAIL → MCU 供电/复位/USB 线问题
│ → 输出基础排查指引 (换USB线/换供电口/检查EN引脚)
│
└── pending_feedback == true
→ AskUserQuestion 逐条询问(每条一问)
→ 收集回答后重新判定
hardware_sanity.py 对 user_feedback 模式的测试会在结果中标记 _pending_question。LLM 读取后:
对每条 pending:
AskUserQuestion(
question: result._pending_question,
header: "硬件诊断",
options: ["是,正常", "否,没有反应"]
)
用户回答后:
"是" → 该外设 status = "pass"
"否" → 该外设 status = "fail"
反馈汇总后再判定一次:
最多让用户回答 3 个问题。如果有 4+ 个反馈型外设,优先测"最可能故障"的那个(根据 triage error_type 指向)。
LLM 使用 Skill 工具调用上游 skill,打包 error context:
委托 upy-generate 时传入:
委托 upy-select-hw 时传入:
委托 upy-analyze 时传入:
每次修复后的验证路径:
修复完成
↓
可选:Skill("upy-simulate") PC 端快速验证(2-3s,省去串口烧录延迟)
↓
Skill("upy-deploy") 重新烧录运行
↓
再次运行 triage.py(--attempt N+1)
↓
读 JSON:
├─ status="pass" → 成功,输出 PASS 摘要
└─ status="fail" → 回到 Step 2 重新研判(可能升级回退层级)
升级规则:同一策略连续失败 → 向上一级回退(P0 直接改 → P0 委托 generate → P1 委托 select-hw → 需求层面 analyze)。
当 i2c_ok: false(已尝试 software I2C + 降速均无效)时,LLM 直接输出以下中文指引,不修代码:
I2C 总线扫描不到设备,已尝试 software I2C 和低速模式,均无响应。这是硬件连接问题,请按以下顺序排查:
1. 接线检查:
- SDA/SCL/VCC/GND 每根线用万用表通断档确认导通
- VCC 接 3.3V(不是 5V!)
- GND 必须与 MCU 共地
2. 供电检查:
- 模块电源指示灯是否亮?
- VCC 引脚电压是否为 3.3V ± 0.1V?
3. 上拉电阻:
- SDA/SCL 各需 4.7kΩ 上拉到 3.3V
- 部分模块自带,部分没有——检查你的模块
4. 传感器本身:
- 是否发热异常?
- 替换另一块同型号传感器测试
5. 冲突检查:
- 拔掉其他外设,只接这一个传感器测试
排查完成后发送"重新部署"重试。
python G:/MicroPython_Skills/upy-autofix/scripts/triage.py --rollback --log-dir {deploy_logs路径}
然后 LLM 输出中文卡点报告:
自动修复 3 次未成功。
错误类型:{error_type}
3 次尝试:
1. {strategy1} → {result1}
2. {strategy2} → {result2}
3. {strategy3} → {result3}
git 已回滚到修复前状态。
建议手动排查方向:{具体建议}
triage.py 自动将每次修复的历史写入 logs/error_report.json(追加模式),包含:时间戳、MCU 型号、错误类型、traceback、每次尝试的策略和结果、使用的 skill 版本。
LLM 在 3 次全败时额外补充 llm_analysis 字段(根因分析 + 知识盲区标记)。
upy-deploy FAIL
↓
upy-autofix (本 skill)
├── triage.py → 采集数据
├── LLM 研判
├── [新增] hardware_sanity.py → 硬件信号驱动验证
├── 委托 → upy-generate(代码修复)
├── 委托 → upy-select-hw(引脚/地址修复)
├── 委托 → upy-analyze(需求重新分析)
├── 可选验证 → upy-simulate(PC 快速验证)
├── 重新部署 → upy-deploy
└── 失败回流 → CI/CD 反馈到各 skill SKILL.md
upy-deploy:接收 FAIL 判定 + deploy_logs/ 日志upy-generate:委托代码修复upy-select-hw:委托引脚/地址重新分配upy-analyze:委托需求重新分析upy-simulate:可选 PC 验证upy-deploy:修复后重新烧录attempt >= 2 连续同错误 / NoOutput / OSError_19/110 — 不浪费用户时间--snapshot)error_type: "unknown",此时 LLM 必须从原始日志独立判断error_report.json,驱动 CI/CD 持续改进mpremote fs cp 和 resume exec 在 Windows 上进入 raw REPL 模式(发送 Ctrl+C),会杀死正在运行的 main.py。而且被杀死的进程可能未 flush 日志文件,读到空文件会误判"无日志输出"。正确做法:让 main.py 运行到自然结束或崩溃 → 软复位后(程序已停)再 fs cp 抓日志。如需在运行时检查设备状态,用 hardware_sanity.py 的 I2C 扫描(独立于 main.py 进程)LLM 读取 firmware/ 源码识别外设类型后,按以下模板生成 sanity_config.json 中的 test code。
模板结构:
{
"id": "peripheral_name_sanity",
"category": "i2c_sensor|spi_sensor|uart|gpio_out|display|adc|dac|input",
"mode": "self_verify|user_feedback",
"label": "中文名 (引脚/地址)",
"code": "完整 MicroPython 代码, mpremote exec 执行",
"pass_pattern": "SCAN_OK|CHIP_OK|FRAME_OK|ADC_OK|DAC_OK|TEST_DONE",
"fail_pattern": "SCAN_FAIL|CHIP_FAIL|FRAME_FAIL",
"value_key": "TEMP|VOLT|RAW",
"value_range": [min, max],
"question": "仅 user_feedback 模式: AskUserQuestion 的问题",
"timeout_ms": 10000
}
识别信号: __init__(i2c, address=0x??) 或 I2C(0, scl=Pin(x), sda=Pin(y))
自检步骤: i2c.scan() 确认地址 → (可选) 读 WHO_AM_I/CHIP_ID → 读一次数据 → 物理合理范围判定
通用模板:
from machine import I2C, Pin
i2c = I2C({bus_id}, scl=Pin({scl}), sda=Pin({sda}), freq=400000)
addrs = [hex(a) for a in i2c.scan()]
if '{expected_addr}' in addrs:
print('SCAN_OK')
else:
print('SCAN_FAIL:' + str(addrs))
# 读一次数据
from {driver_module} import {DriverClass}
s = {DriverClass}(i2c, address={addr_int})
try:
r = s.{read_method}()
print('VALUE:' + str(r))
except Exception as e:
print('READ_ERR:' + str(e))
示例 — BMP280 (地址 0x76):
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
addrs = [hex(a) for a in i2c.scan()]
if '0x76' in addrs:
print('SCAN_OK')
else:
print('SCAN_FAIL:' + str(addrs))
from bmp280_float import BMP280
s = BMP280(i2c, address=0x76)
data = s.read_compensated_data()
print('TEMP:' + str(data[0]) + ',' + 'PRESS:' + str(data[1]))
pass_pattern: "SCAN_OK", value_key: "TEMP", value_range: [-40, 85]
识别信号: __init__(spi, cs) 或 SPI(1, sck=Pin(x), mosi=Pin(y), miso=Pin(z))
自检步骤: 读 WHO_AM_I / CHIP_ID / DEVICE_ID 寄存器 → 与 datasheet 预期值比对
通用模板:
from machine import SPI, Pin
spi = SPI({bus_id}, sck=Pin({sck}), mosi=Pin({mosi}), miso=Pin({miso}))
cs = Pin({cs}, Pin.OUT)
cs.value(1)
# 读 WHO_AM_I 寄存器
cs.value(0)
spi.write(bytearray([0x80 | {who_am_i_reg}]))
resp = spi.read(1)
cs.value(1)
if resp[0] == {expected_id}:
print('CHIP_OK:' + hex(resp[0]))
else:
print('CHIP_FAIL: expected ' + hex({expected_id}) + ' got ' + hex(resp[0]))
pass_pattern: "CHIP_OK", fail_pattern: "CHIP_FAIL"
示例 — ADXL345:
from machine import SPI, Pin
spi = SPI(1, sck=Pin(10), mosi=Pin(11), miso=Pin(12))
cs = Pin(9, Pin.OUT); cs.value(1)
cs.value(0)
spi.write(bytearray([0x80])) # DEVID register
resp = spi.read(1)
cs.value(1)
if resp[0] == 0xE5:
print('CHIP_OK')
else:
print('CHIP_FAIL: got ' + hex(resp[0]))
pass_pattern: "CHIP_OK", fail_pattern: "CHIP_FAIL"
识别信号: __init__(uart) 或 UART(1, baudrate=9600, tx=Pin(x), rx=Pin(y))
自检步骤: 发查询命令 → 等响应 → 超时判定
通用模板:
from machine import UART, Pin
import time
u = UART({uart_id}, baudrate={baud}, tx=Pin({tx}), rx=Pin({rx}), timeout=2000)
u.write(b'{query_cmd}')
time.sleep(0.5)
resp = u.read()
if resp and len(resp) > 0:
print('FRAME_OK:' + str(resp[:32]))
else:
print('FRAME_FAIL: no response')
pass_pattern: "FRAME_OK", fail_pattern: "FRAME_FAIL"
示例 — PMS7003 (主动模式,等数据帧):
from machine import UART, Pin
import time
u = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16), timeout=5000)
t0 = time.time()
while time.time() - t0 < 5:
if u.any():
d = u.read()
if d and len(d) >= 2 and d[0] == 0x42 and d[1] == 0x4D:
print('FRAME_OK: start bytes valid')
break
else:
print('FRAME_FAIL: no valid frame in 5s')
pass_pattern: "FRAME_OK", fail_pattern: "FRAME_FAIL"
示例 — SIM800/SIM7600 (AT 指令):
from machine import UART, Pin
import time
u = UART(2, baudrate=115200, tx=Pin(17), rx=Pin(16), timeout=3000)
u.write(b'AT\r\n')
time.sleep(1)
resp = u.read()
if resp and b'OK' in resp:
print('FRAME_OK: AT response')
else:
print('FRAME_FAIL: ' + str(resp))
识别信号: __init__(pin: int) → Pin(x, Pin.OUT) 或 PWM(Pin(x))
测试步骤: PWM/电平周期性变化 ×3 → 问用户
通用模板:
from machine import Pin, PWM
import time
p = PWM(Pin({pin}), freq=1000)
for i in range(3):
p.duty_u16(32768); time.sleep(0.3)
p.duty_u16(0); time.sleep(0.3)
p.deinit()
print('TEST_DONE')
pass_pattern: "TEST_DONE", question: "{label} {动作描述}?(y/n)"
变体 — LED:
from machine import Pin, PWM
import time
p = PWM(Pin({pin}), freq=1000)
for _ in range(3):
for d in range(0, 65535, 16384): # 渐亮
p.duty_u16(d); time.sleep(0.05)
p.duty_u16(0); time.sleep(0.2)
p.deinit()
print('TEST_DONE')
question: "LED({pin}引脚) 闪烁了3次吗?"
变体 — 蜂鸣器:
from machine import Pin, PWM
import time
p = PWM(Pin({pin}), freq=1000)
for f in [440, 660, 880]: # A4, E5, A5
p.freq(f); p.duty_u16(32768)
time.sleep(0.25)
p.duty_u16(0)
time.sleep(0.1)
p.deinit()
print('TEST_DONE')
question: "蜂鸣器响了3声吗?"
变体 — 继电器:
from machine import Pin
import time
p = Pin({pin}, Pin.OUT)
for _ in range(3):
p.value(1); time.sleep(0.3)
p.value(0); time.sleep(0.3)
print('TEST_DONE')
question: "听到继电器咔哒声了吗?"
识别信号: __init__(i2c/spi, ...) + .fill() / .show() 方法
测试步骤: 全屏交替填充 → 问用户
通用模板:
from machine import {bus_type}, Pin
{init_code}
# 全屏闪烁
display.fill(1); display.show()
import time; time.sleep(0.5)
display.fill(0); display.show()
time.sleep(0.5)
display.fill(1); display.show()
print('TEST_DONE')
question: "{label} 屏幕闪烁了吗?"
示例 — SSD1306 (I2C):
from machine import I2C, Pin
from ssd1306 import SSD1306
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
display = SSD1306(128, 64, i2c)
display.fill(1); display.show()
import time; time.sleep(0.5)
display.fill(0); display.show()
time.sleep(0.5)
display.fill(1); display.show()
print('TEST_DONE')
示例 — ST7789 (SPI, 彩色):
from machine import SPI, Pin
from st7789 import ST7789
spi = SPI(1, sck=Pin(10), mosi=Pin(11), miso=Pin(12))
dc = Pin(8, Pin.OUT); cs = Pin(9, Pin.OUT); rst = Pin(7, Pin.OUT)
display = ST7789(spi, 240, 240, dc=dc, cs=cs, rst=rst)
import time
for color in [0xF800, 0x07E0, 0x001F]: # 红→绿→蓝
display.fill(color); time.sleep(0.5)
print('TEST_DONE')
question: "屏幕显示红→绿→蓝了吗?"
识别信号: __init__(i2c, address=0x??) + read() 含 channel/gain 参数
自检步骤: 读悬空通道 vs 读 VCC 或固定电压 → 差值判定
通用模板:
from machine import I2C, Pin
i2c = I2C({bus_id}, scl=Pin({scl}), sda=Pin({sda}))
from {driver_module} import {DriverClass}
adc = {DriverClass}(i2c, address={addr})
import time
r1 = adc.read(channel1=0)
time.sleep(0.1)
r2 = adc.read(channel1=0)
diff = abs(r1 - r2)
if diff < 50:
# 读数太稳定可能是悬空读噪声 或 短路
# 尝试读另一个通道
r3 = adc.read(channel1=3)
if abs(r1 - r3) > 100:
print('ADC_OK: ch0=' + str(r1) + ', ch3=' + str(r3))
else:
print('ADC_FAIL: all channels same ~' + str(r1))
else:
print('ADC_OK: fluctuation ' + str(diff))
pass_pattern: "ADC_OK", fail_pattern: "ADC_FAIL"
识别信号: __init__(i2c, address=0x??) + write() + read() (有读回)
自检步骤: write(中值) → read() → 对比
通用模板:
from machine import I2C, Pin
i2c = I2C({bus_id}, scl=Pin({scl}), sda=Pin({sda}))
from {driver_module} import {DriverClass}
dac = {DriverClass}(i2c, address={addr})
dac.write(2048)
import time; time.sleep(0.05)
state = dac.read()
if state and len(state) >= 2:
val = (state[0] << 4) | (state[1] >> 4)
if abs(val - 2048) < 100:
print('DAC_OK: wrote=2048, read=' + str(val))
else:
print('DAC_FAIL: wrote=2048, read=' + str(val))
else:
print('DAC_FAIL: no response')
pass_pattern: "DAC_OK", fail_pattern: "DAC_FAIL"
变体 — 无 read() 的 DAC (如部分 MCP4725 实现):
不测值回读,改为 write(0) → write(2048) → write(0) 接万用表测电压,此时降级为 user_feedback 模式。question: "DAC 输出引脚电压在 write(0) 和 write(2048) 之间有变化吗?"
识别信号: __init__(pin, ...) + 含 callback/idle_state/debounce
测试步骤: 读初值 → 打印 → 等待 → 读终值
通用模板:
from machine import Pin
import time
p = Pin({pin}, Pin.IN, Pin.PULL_UP)
print('INIT_VAL:' + str(p.value()))
time.sleep(6) # 给用户操作时间
print('FINAL_VAL:' + str(p.value()))
question: "请在6秒内{操作描述}(按按钮/旋转编码器/在PIR前移动),REPL 输出的 INIT_VAL 和 FINAL_VAL 值变化了吗?"
示例 — 按钮:
from machine import Pin
import time
p = Pin(5, Pin.IN, Pin.PULL_UP)
print('INIT:' + str(p.value()))
time.sleep(6)
print('FINAL:' + str(p.value()))
print('TEST_DONE')
question: "请在6秒内按下按钮,REPL 中 INIT 和 FINAL 的值变化了吗?"
何时用: 任何 sanity check 的第一步。如果这个失败,不用测其他外设。
from machine import Pin
import time
led = Pin({pin}, Pin.OUT)
for _ in range(4):
led.value(1); time.sleep(0.2)
led.value(0); time.sleep(0.2)
print('LED_OK')
pass_pattern: "LED_OK", question: "板载LED闪烁了吗?"
注意:板载 LED 虽属 GPIO 输出,但它是 MCU 供电/复位/烧录成功的最基本标志。如果 LED_OK 都不打印 → 设备根本没进入 REPL。
LLM 读 firmware/ 源码:
│
├── 找到 Pin(x, Pin.OUT) / PWM(Pin(x)) 且变量名含 "led"
│ → A9: 板载 LED 基础测试 (优先级最高,放 config.tests[0])
│
├── 找到 I2C(0, scl=Pin(x), sda=Pin(y))
│ └── 遍历所有 I2C 驱动类实例
│ → A1: I2C 传感器 (self_verify)
│
├── 找到 SPI(1, sck=Pin(x), ...)
│ └── 遍历所有 SPI 驱动类实例
│ → A2: SPI 传感器 (self_verify)
│
├── 找到 UART(1, baudrate=..., tx=Pin(x), rx=Pin(y))
│ └── 遍历所有 UART 驱动类实例
│ → A3: UART 外设 (self_verify 或 user_feedback)
│
├── 找到 Pin(x, Pin.OUT) / PWM(Pin(x)) 且变量名含 "buzzer"/"relay"/"motor"
│ → A4: GPIO 输出 (user_feedback)
│
├── 找到 I2C/SPI 驱动 含 .fill() / .show() 方法
│ → A5: 显示器 (user_feedback)
│
├── 找到 I2C 驱动 含 .read() + channel/gain 参数
│ → A6: ADC (self_verify)
│
├── 找到 I2C 驱动 含 .write() + .read()
│ → A7: DAC (self_verify)
│
└── 找到 Pin(x, Pin.IN) 含 callback/idle_state
→ A8: 输入器件 (semi_auto)
测试顺序:板载 LED → I2C 传感器 → SPI 传感器 → UART → ADC/DAC → 显示器 → GPIO 输出 → 输入器件。前一个 FAIL 且是基础级别的(LED / 供电相关),后面的不跑了。