预览、调试抓帧、背压与统计
preview 是数据流,不是订阅 RPC
应用调用 img.flush() 后,preview producer 在需要发送时将当前图像编码为 JPEG,以 preview.frame 数据帧发给当前 user stream owner。没有额外的 preview.subscribe 方法,也不通过 print 输出 Base64 图像。
import sensor
import time
sensor.reset()
try:
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)
while True:
img = sensor.snapshot()
img.flush()
time.sleep_ms(200)
finally:
sensor.shutdown()
无限循环由主机显式中断;finally 提供应用资源释放路径。这个设备端示例不自动保证帧一定到达或保持某个帧率,主机仍需验证完整 JPEG 与 session/epoch。

图 18:先判断发送资格和队列条件,再编码、提交图像。 无有效 owner 时不为主机编码 preview;相机是否继续采集是用户程序自己的行为。
图像帧长什么样
以下为概念性 metadata;实际 seq、route、时间戳、sessionId、vmEpoch 均由当前发送快照产生:
{
"sid": "preview", "seq": 40, "channel": "preview.frame",
"type": "data", "encoding": "binary", "route": "usb",
"contentType": "image/jpeg", "width": 160, "height": 120,
"ts_ms": 123456,
"sessionId": "89abcdef0123456789abcdef01234567", "vmEpoch": 1
}
payload 直接从 JPEG 的二进制字节开始,p 是编码后 JPEG 字节数,不是 width × height,也不是 source RGB565 帧缓冲大小。主机不应把 JPEG payload 交给 JSON/UTF-8 parser。
seq 可帮助观察帧序列,但丢帧不应按“缺哪一帧就补传哪一帧”理解。主机可保留最新完整图像用于渲染,旧 session/epoch 的图像必须丢弃,不能在软复位后闪回上一程序的画面。
Preview、摄像头与 LCD 三条图像路径
属性 |
|
|
|
|---|---|---|---|
触发 |
应用 |
主机摄像头抓帧请求。 |
主机 LCD 抓屏请求。 |
来源 |
应用提交的 image。 |
摄像头 snapshot。 |
显示层最后提交的 RGB565 帧。 |
线上类型 |
独立 |
原 RPC |
原 RPC |
执行 |
应用持有图像并提交 preview。 |
VM 队列初始化/使用摄像头。 |
VM 队列读取显示缓冲并编码,不调用摄像头。 |
交付 |
允许丢帧,最新待发帧优先。 |
按请求 seq/channel/method、会话与 epoch 关联响应。 |
相同 RPC 关联规则,无图像时返回错误。 |

图 19:单次抓图由原 RPC 响应交付,不等待另一个图像 channel。 Core 对外统一为 capture({ source: 'camera' | 'screen', format: 'jpeg' });两种来源不会相互回退。成功结果是 { data, mimeType, metadata },metadata 包含尺寸;失败仍可为 JSON error,客户端须先检查 encoding/contentType。
LCD 抓屏不初始化屏幕、不重绘、不启动摄像头,只读取最后有效 RGB565 显示帧。没有 LCD 返回 NOT_SUPPORTED,未初始化或没有有效显示帧返回 DISPLAY_NOT_READY。读取及编码在 VM 任务中与显示操作、软复位清理串行执行,VM 忙时也可能返回 VM_BUSY / VM_TIMEOUT。可选 JPEG quality 为 1–100。它反映软件显示帧而非物理面板回读,不能证明背光或屏幕硬件正常。
使用统一 Core 真机 CLI 的 --screen-output 显示测试图案并保存 JPEG;具体命令与验收要求见 通信验收、闭环测试与故障定位。
队列优先级与丢帧
控制、会话、REPL、日志、preview 对应不同 TX 类。普通小帧队列预算有界,控制流量有预留预算;不能用不读取主机数据的方式验证“所有输出永不丢失”。
preview 只保留最新待发帧,可替换尚未开始发送的旧图。已经发送到一半的字节不能撤回,也不能把两张 JPEG 的尾巴拼成一张。preview 与大图 debug response 共用有限特殊缓冲:最多三个按需分配的 256 KiB 缓冲,不代表永远可容纳三个任意大图或允许 256 KiB 的主机命令。

图 20:降低积压时延比交付每一张预览更重要。 若需要保存全部图像,应使用设备端存储或专门的视频/记录接口,并独立设计其容量、回放与验收;preview 不承担这个保证。
UART 的帧期限随 baud 与总字节数计算;USB/TCP 也有有界等待。底层 write 可只接受一部分字节或返回 WOULD_BLOCK,TX task 必须保留未发送后缀并在期限内继续。帧发送失败或 socket 错误可能使 TCP link 关闭,而 RX 半帧超时只重置仍在线连接的 parser,不应一概认为设备重启。
日志通道与系统日志 UI
IDE 可以将 log.idf、RPC 诊断和连接事件合并显示为“系统日志”,但这个 UI 面板不是 firmware 的第九个 channel。repl.stdout 则主要显示应用/REPL 文本,channel 的真实来源与限制见 架构、执行上下文与标准输出。
系统日志的格式、颜色、过滤器可以由主机选择,不能反过来改变固件所有权和物理路由。无 owner 的默认/回退日志、启动 console 原始文本与当前 session 业务输出,应在诊断记录中分开标识。
如何看待统计计数
debug.info 的 scope:transport.stats 与 transport.state 可在用户 Python 忙时查询,用于观察队列、RX 拒绝、发送超时、preview 丢弃和后端状态。必须区分 endpoint/link 统计、全局统计、业务完成标记:
未读的 USJ 默认输出积压可增加全局 drop;这不等于活动 TCP 上的特定 RPC 已失败。
某个 RPC 已拿到完整关联响应,不代表整段应用业务完成。
preview 丢弃是允许的实时行为,不自动否定文件传输,但错误 owner/epoch 的图像被接受属于协议错误。
reset/generation 变化可能改变统计范围;应把采样时刻、端点和会话身份与结果一起记录。
诊断时保持有界,只记录必要的计数与状态;不要在错误路径中再制造大量打印以填满同一 TX 队列。
帧缓冲的生命周期
LCD 缓存在显示初始化时分配,反初始化时释放。SPI 彩色数据传输完成后绘图函数才返回,后续绘图不会覆盖仍被 DMA 借用的数据。摄像头 snapshot 缓存在首次抓帧时分配,由 Python 与 Core 摄像头抓图共用;相同尺寸的请求复用同一个指针。实际像素格式/尺寸变化、摄像头关闭/重置或 VM 软重置会释放它。两者都不会在开机时预占整帧内存。分配优先使用 PSRAM,不可用时保留原有通用堆回退;仅配置启用不能证明 PSRAM 初始化成功。
地址只在本次分配的生命周期内稳定,不保证重启或重新配置后相同。Python snapshot 图像借用该缓存,若要跨下一次抓帧保留图像,应先复制;Core 在主机端返回独立接收的 JPEG。摄像头驱动 DMA 缓冲和 JPEG 编码工作缓冲有各自的生命周期,这两块缓存不是图像功能的全部内存预算。