ワンクリックで
upy-generate
第四步——业务代码生成。读取 scaffold 阶段的 project-manifest.json,下载驱动、生成 DI 架构的业务代码、Mock 层、单元测试。触发:upy-scaffold 完成后自动进入。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
第四步——业务代码生成。读取 scaffold 阶段的 project-manifest.json,下载驱动、生成 DI 架构的业务代码、Mock 层、单元测试。触发:upy-scaffold 完成后自动进入。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | upy-generate |
| description | 第四步——业务代码生成。读取 scaffold 阶段的 project-manifest.json,下载驱动、生成 DI 架构的业务代码、Mock 层、单元测试。触发:upy-scaffold 完成后自动进入。 |
本项目生成的代码遵循 以单元测试为核心的嵌入式开发 方法论:
main.py(DI 装配入口)外,所有代码文件不 import machine。驱动通过工厂函数注入,任务函数通过参数接收驱动对象。import Mock 运行测试,无需任何硬件。test/pc/ 目录下的单元测试运行在 CPython,不依赖 MicroPython 运行时。lib/ 下的基础库(logger、time_helper 等)提供 CPython fallback 以支持此能力。给定 project-manifest.json(phase: scaffold),完成驱动下载、任务代码生成、DI 装配、测试生成。代码理解和生成由 LLM 直接完成,不再依赖正则脚本。
python --version
python -c "import requests; print('requests OK')"
python -c "import flake8; print('flake8 OK')"
python -c "import pylint; print('pylint OK')"
无外部依赖(Python 3 标准库 + requests + flake8 + pylint)。
python G:/MicroPython_Skills/upy-generate/scripts/download_drivers.py --project-dir {project_dir}
脚本从 upypi / GitHub 拉取驱动 .py 文件到 firmware/lib/,同时尝试拉取 README.md 和 code/main.py 作为参考材料:
| 文件 | 命名 | 用途 |
|---|---|---|
| 驱动 .py | lib/<driver>.py | 驱动源码,理解 API |
| README.md (upypi) | lib/<name>_README.md | 接线图、API 说明、使用流程 |
| code/main.py (upypi) | lib/<name>_example.py | 可运行示例,展示真实调用模式 |
| README.md (GitHub) | lib/<name>_README.md | 仓库文档 |
upypi / GitHub 源文件可能包含 \r\r\n 等非标准换行符,会导致 CPython 解析失败。下载后扫描 lib/*.py,若 compile 失败则自动修复:
cd {project_dir} && python -c "
import os, sys
for fn in os.listdir('firmware/lib'):
if not fn.endswith('.py'): continue
path = os.path.join('firmware/lib', fn)
try:
with open(path, 'rb') as f: data = f.read()
compile(data.decode('utf-8'), path, 'exec')
except (SyntaxError, IndentationError, UnicodeDecodeError):
# Normalize line endings and remove blank lines between backslash continuations
text = data.decode('utf-8', errors='ignore')
text = text.replace('\r\n', '\n').replace('\r', '\n')
lines = text.splitlines(keepends=True)
result = []; i = 0
while i < len(lines):
line = lines[i]; result.append(line)
if line.rstrip('\n').endswith('\\\\'):
i += 1
while i < len(lines) and lines[i].strip('\n').strip() == '': i += 1
else: i += 1
text = ''.join(result)
with open(path, 'w', encoding='utf-8') as f: f.write(text)
print(f'[FIX] {fn} line endings normalized')
"
---
## Phase 2: LLM 理解驱动 → 生成工厂 + Mock
**对 manifest.devices 中的每个 device,逐一执行:**
### 2A: 阅读理解(Mock API 的唯一来源)
**Mock 的方法签名、参数、返回值类型必须从下载到 `firmware/lib/` 的驱动源码中直接提取 —— 不依赖任何外部的 api_ref 字符串或预置数据库。**
读取以下文件(如果存在):
firmware/lib/.py ← 驱动源码(Mock API 的权威来源) firmware/lib/_README.md ← 使用文档 firmware/lib/_example.py ← 示例代码
从源码中理解:
- **主驱动类是哪个**:如果有多个类(如 `SSD1306` 基类 + `SSD1306_I2C` 子类),选 I2C/SPI 子类;没有子类则选第一个非 Exception 类
- **__init__ 参数**:区分 I2C 地址、Pin 脚号、配置参数
- **公开方法(Mock 覆盖目标)**:所有不以 `_` 开头的方法,逐一记录方法签名(参数名、默认值)和返回值类型。这些将直接决定 Mock 类的方法列表和默认返回值
- **是否是自建 I2C 的驱动**:`self.i2c = I2C(...)` → 需要修改驱动以支持外部传入 i2c
example.py 可以告诉你真实的调用方式:
```python
# 从 example 知道:BMP280(i2c=i2c, address=addr)、read_compensated_data() 返回 (temp, press, hum)
如果驱动在 __init__ 中自己创建 I2C(如 SHT30 的 self.i2c = I2C(scl=Pin(...), sda=Pin(...))),直接修改 lib/<driver>.py:
def __init__(self, ...) 末尾加 , i2c=Noneself.i2c = I2C(...) 包在 if i2c is not None: self.i2c = i2c else: ... 里self.i2c_addr 等后续赋值留在 if/else 外面(两种分支都需要)drivers/<name>_driver/__init__.py工厂文件是 DI 链条的第一环,导出 create_<name> 和 scan_<name>_i2c 两个函数,行为必须一致可靠。
参考来源:下载的 lib/<name>_example.py(即驱动包的 code/main.py)展示了真实的 I2C 地址、通信速率配置和调用方式,LLM 必须阅读该文件后再生成工厂代码。
# -*- coding: utf-8 -*-
# @Generated : upy-generate
# @File : drivers/<name>_driver/__init__.py
# @License : MIT
try:
from lib.<module> import <DriverClass>
except ImportError:
<DriverClass> = None
# Default I2C address from driver source / example.py
_<NAME>_DEFAULT_ADDR = <default_addr>
def create_<name>(i2c, address=None):
"""Create <DriverClass> instance. Returns driver object or None on failure."""
if i2c is None:
return None
try:
obj = <DriverClass>(i2c=i2c, address=address if address is not None else _<NAME>_DEFAULT_ADDR)
return obj
except Exception as e:
from lib.logger import warning
warning('Driver <NAME> init failed: {}'.format(e))
print('[DRIVER] <NAME> init failed:', e)
return None
def scan_<name>_i2c(i2c, address=None):
"""Scan I2C bus for <NAME>. Returns True if device found at address.
Does NOT create a driver instance — only checks if the I2C address
responds on the bus. Used by upy-deploy-test for hardware connectivity check.
NOTE: Some sensors (e.g. GT911) have software-configurable addresses
that cannot be detected via passive scan. For these, consult the
downloaded example.py — scan may return False until the device is
initialized, or require a different detection strategy.
"""
if i2c is None:
return False
addr = address if address is not None else _<NAME>_DEFAULT_ADDR
return addr in i2c.scan()
i2c, address=None,扫描函数独立于驱动实例pin_num,内部创建 Pin(pin_num, Pin.OUT)。GPIO 器件没有现成驱动,需要 LLM 从头写合理的封装(on/off/toggle/value),Mock 同样覆盖这些方法。GPIO 器件不需要 scan 函数spi, cs_pin,内部创建 Pin(cs_pin, Pin.OUT)i2c, address=Nonedrivers/<name>_driver/mock.pyMock 的方法列表和签名直接从 Phase 2A 阅读驱动源码的结果中提取,覆盖驱动所有公开方法,默认返回值与驱动返回类型匹配:
# -*- coding: utf-8 -*-
# @Generated : upy-generate
# @File : drivers/<name>_driver/mock.py
# @License : MIT
class Mock<Name>:
"""Mock <NAME> — same API as real driver, returns canned data."""
def __init__(self, **kwargs):
self._<method1> = kwargs.get('<method1>', <default>)
...
def <method1>(self):
return self._<method1>
...
默认值规则:
0True(<典型值>),如 (25.0, 60.0)self._xxx,方法体直接 pass如果 manifest.scaffold_mode 为 async:
默认规则:驱动本身不改(保持同步,async 化在封装层完成),工厂函数返回同步驱动对象不变。task 函数形态和 main.py 入口由 scaffold 骨架阶段决定,generate 阶段沿用。
例外 —— 驱动中存在阻塞式方法时必须修改驱动源码:
I2S 外设等硬件阻塞操作:查阅 MicroPython asyncio 官方文档确认是否有异步替代 API。若有(如 I2S.readinto() 的异步版本),在工厂封装层包装;若没有,需在驱动中增加非阻塞轮询路径。
time.sleep() / time.sleep_ms() 等延时:驱动中如果有延时等待(如传感器上电稳定等待、复位后延时),必须修改:
time.sleep(n) 替换为 await asyncio.sleep(n) 或 await asyncio.sleep_ms(n)async def轮询等待 I2C 响应等忙等待:改为 await asyncio.sleep_ms() 让步给事件循环。
修改驱动后,更新对应 Mock 的方法签名(async def 匹配),task 协程中用 await 调用。
读取 manifest.requirements.description(用户需求描述)和 manifest.devices 列表 → 生成 task 文件。
约束(LLM 必须在生成代码中满足):
纯函数 + DI:task 是纯 Python 函数,不 import machine,所有硬件通过参数传入驱动对象
独立异常处理:每个传感器/器件的读写操作独立 try/except,一个挂了不影响其他
print() 与 lib.logger 强制双写:install_rotating() 会 monkey-patch 所有 logger 输出(包括 debug()/info()/warning()/error())重定向到 /log/run_*.log 文件,REPL 完全看不到。因此 print() 是 REPL 唯一可见通道,必须严格遵守以下规则:
必须双写(logger + print 同时存在)的场景:
debug(msg) + print(msg)debug(msg) + print(msg)info(msg) + print(msg)warning/error(msg) + print(msg)warning/info(msg) + print(msg)不可双写(只用一处)的场景:
debug()print()日志消息需带模块标识前缀(如 [sensor]、[display]、[alarm]),具体格式由 LLM 自主决定
温度键冲突处理:当同时有温湿度传感器和气压传感器时,气压传感器的温度写入 pressure_temp,避免覆盖主温度
无硬编码阈值:所有阈值、间隔从 conf.py 导入
装饰器:timer 模式用 @timed_function,async 模式用 @timed_coro
task 日志插入点(LLM 必须在对应位置插入,级别和具体措辞可自主调整):
| 位置 | 级别 | 双写 | 内容要求 |
|---|---|---|---|
| 传感器读取成功 | debug | 必须 | 含传感器名 + 实际读值。debug(msg) + print(msg) 缺一不可 |
| 传感器读取失败 | warning | 必须 | 含传感器名 + 异常信息。warning(msg) + print(msg) |
| 报警触发 | warning | 必须 | 含参数名 + 当前值 + 阈值。warning(msg) + print(msg) |
| 报警恢复 | info | 必须 | 含参数名 + 当前值。info(msg) + print(msg) |
| 显示更新 | debug | 必须 | 含当前显示内容摘要。debug(msg) + print(msg) |
| 显示更新失败 | warning | 必须 | 含异常信息。warning(msg) + print(msg) |
| 未预期 Exception | error/exception | 必须 | 含上下文(哪个 task、在处理什么数据)。logger + print 双写 |
| 纯内部状态(GC 统计、计数器递增等) | debug | 可选 | 用户不关心的内部细节,logger 记录即可,可不 print |
LLM 自主决定:
读取 scaffold 生成的 firmware/conf.py,按项目需求补充业务常量。
约束:
LOG_DIR = "/log"
LOG_MAX_FILES = 4
LOG_LINES_PER_FILE = 150
日志消息原则(LLM 生成所有日志消息时遵守):
[sensor]、[alarm]),方便在日志文件中 grep/搜索debug=开发调试细节(可默认关闭)、info=关键流程节点、warning=可恢复异常、error/critical=不可恢复错误LLM 自主决定: 根据 manifest.requirements.description 推断需要的常量名和值(告警阈值、校准值、显示间隔等),日志消息中的时间戳格式和模块标识前缀。
读取所有工厂文件 + task 文件 + conf.py → 生成 main.py。
约束:
import time; time.sleep(3) # Boot delay: allow mpremote to reconnect after reset
这确保 deploy/autofix 的持久会话在 main.py 输出第一行日志之前就已连接,不会遗漏任何启动输出。
machine.I2C/Pin → 工厂 create_xxx() → 驱动对象 → task 函数参数,完整串联from lib.logger import install_rotating, getLogger, info, warning, error, setLevel, DEBUG, INFO
from conf import LOG_DIR, LOG_MAX_FILES, LOG_LINES_PER_FILE, LOG_LEVEL
# 安装轮转日志 —— 所有 logger 输出写入 /log/run_*.log
# fmt 参数由 LLM 自主决定,不指定则使用默认 "%(levelname)s:%(name)s:%(message)s"
install_rotating(LOG_DIR, max_files=LOG_MAX_FILES, lines_per_file=LOG_LINES_PER_FILE)
# 激活 conf.LOG_LEVEL —— install_rotating() 后 logger 输出只进文件,
# setLevel() 控制哪些级别真正写入(DEBUG 全量 / INFO 跳过 debug)
if LOG_LEVEL == 'DEBUG':
setLevel(DEBUG)
else:
setLevel(INFO)
_log = getLogger("main")
补一句 print() 输出固件名称+版本到 REPL,再 info(...) 写入日志。
为什么 print() + 轮转日志两者都要:
print() → REPL 实时输出,mpremote 连接时即时可见/log/run_*.log,REPL 断开后仍可通过 mpremote fs 读取回溯G:\MicroPython_Claude_Assistant\device\main.py(第 13-24 行)scan_<name>_i2c(i2c),结果用 info/warning + print 双写main.py 日志插入点(LLM 必须在对应位置插入,级别和具体措辞可自主调整):
| 位置 | 级别 | 内容要求 |
|---|---|---|
| 固件启动 | info | 项目名 + 版本号 + 板名 |
| 轮转日志安装 | debug/info | LOG_DIR 路径 |
| I2C 总线初始化 | info | bus id + SCL/SDA pin 号 |
| 各驱动创建成功 | info | 驱动名 + I2C 地址 |
| 各驱动创建失败 | warning | 驱动名 + 异常信息 |
| I2C 扫描结果 | info/warning | 器件名 + found/missing |
| 调度器/事件循环启动 | info | tick_ms 或等效参数 |
| 调度器主循环异常 | error | 致命错误,含 traceback |
LLM 自主决定: import 组织、初始化顺序、install_rotating(fmt=...) 参数、启动日志具体措辞、GPIO/SPI 器件的创建方式。
test/pc/test_<task>.py约束(LLM 必须在生成代码中满足):
from drivers.<name>_driver.mock import Mock<Name>sys.path.insert(0, 'firmware') 确保能在 CPython 下导入 firmware/ 下的模块LLM 自主决定: 测试类命名、Mock 参数配置、断言内容和数量、是否扩展更多边界用例。
test/device/test_smoke.py只测硬件可用性,不测业务逻辑。
只用 MicroPython unittest 支持的 assert 方法(assertTrue、assertEqual、assertIsNotNone、assertRaises),禁止使用 assertIsInstance、assertIn、assertNotIn、assertGreater 等 CPython 扩展。
from machine import I2C, Pin
import unittest
from drivers.<name>_driver import scan_<name>_i2c
class TestSmoke(unittest.TestCase):
def test_i2c_bus_scan(self):
i2c = I2C(<bus_id>, scl=Pin(<scl>), sda=Pin(<sda>))
devices = i2c.scan()
self.assertTrue(len(devices) > 0, "No I2C devices found on bus")
def test_<name>_present(self):
i2c = I2C(<bus_id>, scl=Pin(<scl>), sda=Pin(<sda>))
self.assertTrue(scan_<name>_i2c(i2c),
"<NAME> not found at expected address")
LLM 自主决定: 为每个 I2C 器件生成对应的 test_<name>_present 方法,添加 GPIO/SPI 器件的硬件测试。Pin 脚号从 manifest 读取。
PC 端测试(test/pc/)会遇到 MicroPython 特有模块在 CPython 不存在的问题。生成代码时应遵守架构约束(除 main.py 外不 import machine),但如果 lib/ 下的文件(如 logger、time_helper)使用了 micropython.const、time.ticks_ms 等 MPY 专有 API,需要确认它们有 CPython fallback。如果发现缺失,顺手补上。
cd {project_dir} && python -m flake8 firmware/ --extend-exclude=firmware/lib --max-line-length=120 2>&1
项目目录下需存在 .pylintrc,忽略 MicroPython 特有模块的 import-error:
[MASTER]
ignore-paths=^firmware/lib/.*\.py$
ignore-patterns=test_
[MESSAGES CONTROL]
disable=
import-error, # machine/micropython 等 MPY 模块在 CPython 不存在
no-member, # MPY 动态注入成员 pylint 无法推断
no-name-in-module,
c-extension-no-member
[TYPECHECK]
ignored-modules=
machine,micropython,pyb,esp,esp32,espnow,rp2,mimxrt,zephyr,wipy,stm,
neopixel,network,bluetooth,framebuf,uctypes,cryptolib,deflate,btree,
vfs,openamp,lcd160cr,WM8960,
uasyncio,uselect,utime,ujson,uos,ustruct,ure,uzlib,uhashlib,
ubinascii,ucollections,urandom,uerrno,uheapq,uselect,ussl,usocket
[FORMAT]
max-line-length=120
cd {project_dir} && python -m pylint firmware/ --rcfile=.pylintrc 2>&1
有错直接修,修完重新验证,直到 pass。
这是关键拦截点:CPython 有大量内置模块(logging、collections、typing、pathlib 等),MicroPython 没有。PC 端测试通过不代表设备上能运行。
扫描 firmware/ 下所有 .py 文件(排除 firmware/lib/),提取顶层 import X 和 from X import Y 中的 X,逐一检查是否在已知 MicroPython 可用模块白名单中:
MicroPython 内置 + stdlib 白名单(63 个):
sys, os, time, machine, micropython, gc, math, cmath, struct, json,
binascii, collections, errno, hashlib, io, platform, random, re, select,
socket, ssl, array, network, bluetooth, framebuf, uctypes, cryptolib,
deflate, btree, vfs, openamp, lcd160cr, neopixel, esp, esp32, espnow,
rp2, mimxrt, zephyr, wipy, stm, uasyncio, uarray, ubinascii, ucollections,
ucryptolib, uctypes, uerrno, uhashlib, uheapq, uio, ujson, uos, uplatform,
urandom, ure, uselect, usocket, ussl, ustruct, utime, uzlib, threading
LLM 执行步骤:
firmware/lib/ 外部驱动):cd {project_dir} && find firmware -name "*.py" -not -path "firmware/lib/*" | sort
逐个文件读取,提取顶层 import X 和 from X import Y 中的模块名 X(忽略 . 开头的相对导入)
对每个 X 做三层判断:
X 在白名单中?
→ 跳过(MPY 原生支持)
X 不在白名单,但 firmware/ 下存在 X.py 或 X/__init__.py?
→ 错误:应改为相对导入。示例:
文件 firmware/tasks/my_task.py 中有 import logging
→ firmware/lib/logger/logging.py 存在
→ 修复为 from lib.logger import logging 或相对路径导入
X 不在白名单,firmware/ 下也不存在?
→ 警告:MicroPython 可能无此模块。LLM 判断是否需要替代方案
常见陷阱:
import typing → MicroPython 不支持,删除类型注解导入
import pathlib → 改用 os.path / os.mkdir 等
import dataclasses → 改用普通类
import collections → 只用 list/dict(MPY 有 ucollections 但功能受限)
import logging → 用本项目的 lib.logger.logging
发现可自动修复的导入错误(同项目内有对应 .py 文件)→ 直接改,改完标注 [FIX]
发现不可自动修复的(无替代模块)→ 打印 warning,记录到审查清单,Phase 8 最终审查时处理
此检查在 flake8/pylint 之后执行,因为 pylint 已配置 ignored-modules 白名单跳过 MPY 模块的 import-error,但那是为了 CPython lint 通过——本检查是反过来,在 CPython 能通过但 MPY 不通过的情况做拦截。
这是关键拦截点:Phase 4 补充 conf.py 时可能加入常量,但 Phase 3 已生成的 task 文件未必引用。结果是配置项定义了但无人使用——"看起来有配置,实际不生效"。
LLM 执行步骤:
conf.py 中所有模块级 UPPER_CASE = value 常量名(排除 __ 开头的 Python 内置变量):cd {project_dir} && python -c "
import re, sys
with open('firmware/conf.py', 'r', encoding='utf-8') as f:
text = f.read()
# Match module-level CONSTANT = value
consts = set(re.findall(r'^([A-Z][A-Z_0-9]+)\s*=', text, re.MULTILINE))
print('\n'.join(sorted(consts)))
"
firmware/ 下所有 .py 文件(排除 firmware/lib/ 外部驱动和 firmware/conf.py 自身),检查是否被引用:引用形式:
import conf → 后续 conf.CONSTANT_NAME ✓
from conf import X → 直接使用 X ✓
from conf import (X, Y) → X, Y 被使用 ✓
死配置 (定义于 conf.py 但未被任何 firmware/*.py 引用):
BUZZER_ALARM_DURATION_MS → 无文件使用
BUZZER_ALARM_INTERVAL_MS → 无文件使用
死配置的语义是否明确需要?
├─ 是(如 BUZZER_ALARM_DURATION_MS 表示"蜂鸣器应脉冲而非长响")
│ → 修 task 文件:在对应逻辑中引入此常量,让配置生效
│
└─ 否(如某个 LLM 自行发明的、无实际用途的常量)
→ 从 conf.py 中删除,保持配置干净
典型陷阱(LLM 必须注意):
LOG_DIR、SAMPLE_INTERVAL_MS 等)不会被误判——它们一定被 main.py 或模板文件引用conf.py 中仅被 test/ 目录引用的常量不算死配置——测试也算使用main.py DI 装配阶段使用,不在 task 文件中出现——这正常Phase 2-7 完成后,LLM 执行最终审查,逐项核验:
firmware/lib/*.py 驱动源码,确认工厂 __init__.py 的 import 和构造函数调用与真实驱动一致manifest.requirements.description,确认每个需求点都有对应的 task 处理manifest.output 中列出的输出器件(display/buzzer/led)都有对应的 task 和 main.py 装配test/pc/test_*.py 是否覆盖正常 + 异常(传感器报错)+ 边界(传感器为 None)三种情况test/device/test_smoke.py 是否只用 MPY unittest 允许的 assert 方法firmware/lib/ 下的外部驱动是否有 CPython 特有的绝对导入(如 import typing、import logging)——这些在设备上会 ImportErrorcd {project_dir} && python -c "
import json, os
from datetime import datetime, timezone
path = 'project-manifest.json'
with open(path, 'r', encoding='utf-8') as f:
m = json.load(f)
m['phase'] = 'generate'
m['generate'] = m.get('generate', {})
m['generate']['generated_at'] = datetime.now(timezone.utc).isoformat()
with open(path, 'w', encoding='utf-8') as f:
json.dump(m, f, ensure_ascii=False, indent=2)
print('[OK] manifest phase → generate')
"
---
## 参考文档
生成代码时遇到 API 不确定的情况,查阅以下权威文档:
| 文档 | URL | 用途 |
|------|-----|------|
| MicroPython 官方文档 | https://docs.micropython.org/en/latest/index.html | 所有 MPY 标准库 API 参考 |
| asyncio 协程 | https://docs.micropython.org/en/latest/library/asyncio.html | async 模式:`create_task`、`sleep_ms`、`gather`、`Event`、`Queue` 等 |
| _thread 多线程 | https://docs.micropython.org/en/latest/library/_thread.html | _thread 模式:`start_new_thread`、`allocate_lock`、`exit` 等 |
| unittest 源码 | https://github.com/micropython/micropython-lib/blob/master/python-stdlib/unittest | MPY unittest 支持的 assert 方法参考(仅 `assertTrue`/`assertEqual`/`assertIsNotNone`/`assertRaises`) |
---
## 架构约束(生成代码必须遵守)
- **鸭子类型 DI**:task 函数接收对象,不关心是真实驱动还是 Mock
- **除 main.py 外不 import machine**:所有 .py 文件不直接 import machine.Pin/I2C/SPI/UART
- **除 lib/ 外不使用 typing 泛型**:类型注解只用 MPY 内置类型
- **独立 try/except**:每个 task_tick 独立异常处理,一个传感器挂了不影响其他
- **阈值在 conf.py**:不硬编码数值
- **Mock 方法签名与真实驱动一致**:通过阅读驱动源码和 example.py 确认
- **所有 I2C 驱动工厂必须导出 `scan_<name>_i2c(i2c, address)` 函数**:不依赖驱动实例,仅扫描 I2C 总线检查地址是否存在,为后续调试/部署 skill 提供硬件连通性判断依据。软件可配置地址的器件(如 GT911)需查阅 example.py 确定检测策略
- **日志写入设备文件系统**:main.py 启动时安装轮转日志(`lib.logger.install_rotating`)。关键流程节点 `print()` + `lib.logger` 双写(REPL 即时可见 + 设备端持久化);纯调试细节可只用 `print()`。即使 REPL 断开,日志仍可通过 mpremote 读取设备端文件
---
## 与其他 skill 的关系
- ← `upy-scaffold`:输入骨架 + manifest
- → `upy-deploy-test`:上传代码到设备并验证
- → `upy-wiring`:引脚分配表 → 接线图
- → `upy-diagram`:代码结构 → 架构图
---
## 强约束
- **可以修改 lib/ 下的驱动源码**(仅限:①添加 DI 支持如 i2c=None 参数 ②修复非标准换行符影响 CPython 编译)
- **不生成全局 firmware/mock/ 目录**
- **async 模式下驱动默认不改**,但如果驱动存在阻塞式方法(`time.sleep`、I2S 阻塞操作等),必须修改为异步版本并查阅 asyncio 官方文档确认 API;异步化也可在封装层处理
- **生成结束自动 flake8 + pylint 验证 + PC 测试运行**,不通过不结束
- **lib/ 下文件需保证 CPython 兼容**:`micropython.const`、`time.ticks_ms` 等 MPY 专有 API 要有 fallback(logger、time_helper 等 scaffold 文件可能遗漏)
- **设备端测试只用 MPY unittest assert 子集**:`assertTrue`、`assertEqual`、`assertIsNotNone`、`assertRaises`,禁止 `assertIn`/`assertIsInstance` 等
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.