sloray d9a27b69f3
构建客户端 / 检查(类型 + 单元测试) (push) Waiting to run
构建客户端 / Linux x64(deb + AppImage) (push) Blocked by required conditions
构建客户端 / Windows x64(NSIS 安装包) (push) Blocked by required conditions
构建客户端 / macOS 通用(.app + .dmg) (push) Blocked by required conditions
fix(ci): 版本校验在浅克隆下必然误报
- fix(ci): checkout 改成 fetch-depth: 0——没有标签、提交数恒为 1 时,
  version.mjs --check 算出来的版本一定是 0.0.0+1,于是每次推送都红
- fix(version): CHANGELOG 按标签区间重算全部历史,不再只渲染当前版本,
  否则每次发布都会把上一版那一段覆盖掉
- fix(build): 固件包文件名用算出来的版本,不再用 BUILD-INFO.json 里那套旧编号
2026-10-19 02:42:53 +08:00

ESP32-S3 摄像头图传系统

基于 Rust + ESP-IDF 的局域网实时图传系统。ESP32-S3 采集摄像头输出的 JPEG,通过 UDP 向桌面客户端推流;桌面端提供队伍端与裁判端界面,并接收 ESP32-C3 装甲板的血量广播。

功能与架构

  • 摄像头自动探测、JPEG 采集、UDP 分片发送与完整帧重组。
  • 局域网广播发现与订阅,单块图传板最多向 4 个客户端发送画面。
  • 运行中 Wi-Fi 断开后自动重连,重新获得 IP 后重建 UDP socket 并恢复订阅。
  • 使用 MAC 派生的稳定设备 ID;两块图传板可烧录同一固件,在客户端绑定队伍。
  • Tauri v2 + React 桌面端:视频显示、双方血量、设备绑定与录制。
  • 独立 ESP32-C3 装甲板固件:四路受击检测、WS2812 灯带及血量上报。
ESP32-S3 图传板 A ─┐                   ┌─ 队伍客户端
                  ├─ 同一局域网 / UDP ─┼─ 裁判客户端
ESP32-S3 图传板 B ─┤                   └─ 队伍客户端
ESP32-C3 装甲主板 ─┘

图传使用 UDP 5600,装甲板血量使用 UDP 5601。板卡与客户端需在可互相通信、允许广播的网络中;关闭 AP 客户端隔离,并允许客户端程序通过防火墙。

项目结构

路径 用途
src/ ESP32-S3 图传固件,根目录 Cargo 项目
components/ 本地相机驱动及 C/Rust 接口封装
protocol/ 共享 UDP 协议与帧重组,支持主机测试
armor/ 独立 ESP32-C3 装甲板 workspace
desktop/ Tauri + React 客户端及独立网络核心 core/
scripts/ 构建、烧录与探测脚本
reference/armor/ 早期 C 版装甲板工程

各子项目的构建目标不同。以下编译、烧录流程针对根目录的 ESP32-S3 图传固件。

硬件要求

项目 当前配置
开发板 Freenove ESP32-S3-WROOM CAM
模组 ESP32-S3-WROOM-1-N16R8,16 MB Flash + 8 MB Octal PSRAM
摄像头 OV5640;驱动包含 OV2640 / OV3660 支持,接线需核对
网络 2.4 GHz Wi-Fi
连接 支持数据传输的 USB 线、可识别的串口及稳定供电

默认采集 VGA(640 × 480)、彩色 JPEG quality 12,按传感器节拍发送。相机引脚、Flash 分区和 PSRAM 配置见 HARDWARE.md。换板型前必须核对接线,避免占用 Flash / PSRAM 引脚。

ESP Rust 工具链入门

本项目使用 Rust std 和 ESP-IDF。ESP32-S3 属于 Xtensa 架构,需要通过 espup 安装 Espressif Rust 工具链;普通 rustup target add 不能替代这一步。根目录固定选择名为 esp-1.97.0.0 的工具链,避免浮动版本影响构建。

1. 安装基础依赖与 Rust 工具

先安装 Rust / rustup、Git、Python 3(建议 3.11)、CMake 与 Ninja,确保命令在 PATH 中。

  • Windows:使用 PowerShell 7.5 或更新版本,安装 Visual Studio Build Tools 的“使用 C++ 的桌面开发”和 Windows SDK,使用 MSVC Rust 主机工具链。仓库和工具链尽量使用较短、无空格和中文的路径。
  • Linux(Debian / Ubuntu):安装 build-essential pkg-config libudev-dev libssl-dev python3 python3-venv cmake ninja-build git curl。
  • macOS:安装 Xcode Command Line Tools(xcode-select --install),以及 Python、CMake 和 Ninja。

以下命令显式使用 +stable,即使在仓库内执行,也不会要求事先已有 esp:

rustup toolchain install stable
cargo +stable install espup --locked
cargo +stable install ldproxy --locked
cargo +stable install espflash --version 4.4.0 --locked
espup install --name esp-1.97.0.0 --toolchain-version 1.97.0.0 --targets esp32s3 --std

也可以从各工具的官方 releases 下载预编译程序。espup --std 安装本项目所需的 Rust / LLVM 工具,ESP-IDF 的 C 工具链由后续构建准备。

Linux / macOS 每次新开终端后执行:

. "$HOME/export-esp.sh"

Windows 安装后重新打开 PowerShell,让环境变量生效;若当前终端仍找不到 LLVM,可执行 . "$HOME/export-esp.ps1"。检查结果:

rustup toolchain list
rustc +esp-1.97.0.0 --version
cargo +esp-1.97.0.0 --version
espflash --version

官方入门资料:The Rust on ESP Book、espup、espflash。

2. 准备 ESP-IDF

默认使用 ESP-IDF v5.5.5。首次构建时,esp-idf-sys 下载 SDK、对应 C 工具链及 Python 依赖,默认缓存到用户目录的 .espressif/;需要网络、磁盘空间及缓存目录写入权限。

已有匹配版本 SDK 时可在终端设置 IDF_PATH,不要提交个人绝对路径:

# Windows:替换为实际 SDK 源码目录。
$env:IDF_PATH = 'C:\Espressif\frameworks\esp-idf-v5.5.5'
# Linux / macOS:替换为实际 SDK 源码目录。
export IDF_PATH="$HOME/esp/esp-idf-v5.5.5"

使用自动下载时,清除指向其他 SDK 的旧 IDF_PATH。缓存位置等高级设置见 esp-idf-sys 构建选项。

配置 Wi-Fi 与摄像头

在根目录复制 .env.example 为 .env,再用编辑器填写;已有 .env 时不要覆盖。

# Windows PowerShell
Copy-Item .env.example .env
# Linux / macOS
cp .env.example .env
WIFI_SSID='你的2.4G网络名称'
WIFI_PASS='你的Wi-Fi密码'

# 可选,省略时使用固件默认值。
#CAM_RES=vga
#CAM_QUALITY=12
#CAM_QUALITY_MAX=0
#CAM_MAX_FPS=0
#CAM_CLOCK_PROFILE=default
#CAM_FB_COUNT=3
#CAM_GRAYSCALE=0
#CAM_EXPOSURE_EV=0
#CAM_AE_LEVEL=-1
#CAM_SHUTTER_LINES=0
#CAM_EXPOSURE_MAX_LINES=0
#CAM_GAIN_CEILING=0
#CAM_HTS=0
#UDP_PACKET_GAP_US=0
#UDP_RETRY_BUDGET_US=8000
#CAM_THERMAL_LIMIT_C=75
#CAM_VSYNC_INVERT=1
#WIFI_AUTH=wpa2
变量 默认值 说明
WIFI_SSID 无,必填 2.4 GHz 网络名称,最多 32 字节
WIFI_PASS 空字符串 Wi-Fi 密码;开放网络可留空
CAM_RES vga qqvga / qvga / cif / hvga / vga / svga / xga / sxga / uxga
CAM_QUALITY 12 0–63,越小通常画质越好、JPEG 越大
CAM_QUALITY_MAX 0 0 固定画质;设为 CAM_QUALITY–63 时按发送回压在两者之间调节压缩,保留彩色和分辨率。拥塞时每 5 秒增大最多 3 档,连续稳定 15 秒才恢复 1 档
CAM_MAX_FPS 0 取帧与发送的最高节拍,1–60;0 不限帧。仅限制消费节拍,不修改传感器时钟
CAM_CLOCK_PROFILE default default 保留驱动时钟;vga30 使用 OV5640 VGA 约 30.09 fps PLL 配置,保持 HTS/VTS 和读出窗口,SYSCLK 61 MHz / PCLK 15.25 MHz。切换后需重新核对曝光行数
CAM_FB_COUNT 3 PSRAM 帧缓冲数,允许 2 或 3。当前交付选三缓冲,保留发送突发时的采集余量;双缓冲曾在短测降低部分回显延迟,但用户长程运行反馈峰值丢帧明显,保留为试验选项
CAM_GRAYSCALE 0 1 使用传感器灰度效果,保留硬件 JPEG;0 彩色
CAM_EXPOSURE_EV 0 OV5640 自动曝光目标偏置:-1 / -2 约对应 −1.1 / −1.9 EV;不是标定后的绝对 EV,也不保证快门缩短
CAM_AE_LEVEL 未设置 可选 OV5640 驱动 AE 档位,−5–5;仅与 CAM_EXPOSURE_EV=0 同用。-1 将目标从 55 小幅降至 45,目标比值约 −0.29 EV,保留自动曝光/增益;未设置时按 EV 参数处理。实际快门和亮度变化取决于场景
CAM_SHUTTER_LINES 0 OV5640 固定快门,单位为传感器曝光行;0 自动曝光,非零保持自动增益。默认 VGA / HTS 2060 下 220 行估算约 10 ms;数值不能超过当前 VTS
CAM_EXPOSURE_MAX_LINES 0 OV5640 自动曝光上限,0 保留驱动默认。非零保持 AEC/AGC 联动并禁用夜景延帧,不能与固定快门同时使用。默认 VGA 下 440 行约 20 ms;vga30 下 592 行约 20 ms。实际曝光由场景和抗频闪步长决定
CAM_GAIN_CEILING 0 OV5640 自动增益上限,0 保留驱动默认;1–63,单位为倍,实际上限略低于整数档。短曝光遇到室内弱光时可提高,但暗部噪点和 JPEG 负载可能增加
CAM_HTS 0 OV5640 水平总长度试验参数,0 使用驱动默认。缩短消隐会影响彩色稳定性,本机 1500 / 1800 出现绿紫闪烁,已撤回;几何检查通过不代表可用
UDP_PACKET_GAP_US 0 成功提交视频数据报之间的最小间隔;0–5000 μs,须为调度 tick 的整数倍(当前为 1000 μs)。配合有界帧等待,避免集中突发写入队列
UDP_RETRY_BUDGET_US 8000 每帧、所有分片及订阅者共用的让出调度重试预算,0–25000 μs,须为 tick 整数倍;0 为非阻塞失败即弃。总发送检查期限为 25 ms,采集帧龄期限为 100 ms;分片间隔也计入期限,不会额外延长
CAM_THERMAL_LIMIT_C 75 芯片内部温度达到该值时减半帧率(向上取整),下降 5°C 后恢复;40–95,0 禁用。原先不限帧时降至 8 fps
CAM_VSYNC_INVERT 1 驱动反相 VSYNC;硬件已反相时填 0
WIFI_AUTH 自动选择 none / wep / wpa / wpa2 / wpawpa2 / wpa3 / wpa2wpa3 / wapi
WIFI_TX_POWER_DBM SDK 默认上限 可选整数 2–20 dBm;当前交付请求 15 dBm,本板驱动回读 15.00 dBm;启动及重连时应用,拥塞和温控逻辑不降低此上限

.env 使用简单的 KEY=value;值含空格、# 或 $ 时用单引号包围,不使用变量展开或命令替换。脚本会加载 .env,直接 cargo build 不会自动读取它。上述参数在编译时写入固件,修改后需要重新编译和烧录。.env 已被 Git 忽略;固件也包含凭据,分享前应重新配置。

运行时断线恢复不重置摄像头、曝光或增益。首次尝试立即发起,失败后按 1、2、4、8、16、30 秒退避;关联与 DHCP 分别有 15 秒期限。恢复 IP 后丢弃旧采集帧、重建 socket 并重新广播,由客户端自动续订。启动时首次联网仍使用扫描与认证模式探测流程。实现依据 ESP-IDF 断线事件处理说明。

编译固件

在仓库根目录执行:

# Windows PowerShell
$env:CARGO_TARGET_DIR = 'C:\s3vt'
.\scripts\build.ps1
# Linux / macOS
./scripts/build.sh

脚本执行 cargo build --release --locked,目标为 xtensa-esp32s3-espidf。首次编译包含 Rust 标准库、ESP-IDF 和相机组件,耗时较长。

Windows 的 ESP-IDF 构建输出路径需不超过 88 字符,因此示例使用短目录 C:\s3vt(需可写,且不与其他项目共用)。subst 不能绕过该检查。下表的 <TARGET_DIR> 在 Windows 示例中为 C:/s3vt,Linux / macOS 默认是仓库的 target。每次新开 Windows 终端都要设置相同的 CARGO_TARGET_DIR。

产物 位置
固件 ELF <TARGET_DIR>/xtensa-esp32s3-espidf/release/esp32(没有 .exe 后缀)
Bootloader <TARGET_DIR>/xtensa-esp32s3-espidf/release/bootloader.bin
分区表 BIN <TARGET_DIR>/xtensa-esp32s3-espidf/release/partition-table.bin
分区表源文件 根目录 partitions.csv

ELF 供 espflash 烧录与符号解析,不能当作 BIN 直接写入 Flash。Bootloader、分区表与 App 应来自同一次构建。

烧录与串口验证

1. 查找串口

espflash list-ports

Windows 常见 COM5;Linux 为 /dev/ttyACM0 或 /dev/ttyUSB0;macOS 为 /dev/cu.usbmodem...。关闭其他串口监视器,Linux 需具备串口访问权限。

2. 烧录 ELF

本项目使用自定义分区表,显式传入 partitions.csv 和本次构建的 Bootloader。将示例端口替换为实际端口:

$targetDir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { 'target' }
espflash flash --chip esp32s3 --port COM5 --flash-size 16mb --flash-mode dio --flash-freq 40mhz --partition-table partitions.csv --bootloader "$targetDir/xtensa-esp32s3-espidf/release/bootloader.bin" --monitor "$targetDir/xtensa-esp32s3-espidf/release/esp32"
target_dir="${CARGO_TARGET_DIR:-target}"
espflash flash --chip esp32s3 --port /dev/ttyACM0 --flash-size 16mb --flash-mode dio --flash-freq 40mhz --partition-table partitions.csv --bootloader "$target_dir/xtensa-esp32s3-espidf/release/bootloader.bin" --monitor "$target_dir/xtensa-esp32s3-espidf/release/esp32"

连接失败时按住 BOOT,点按 RESET / EN,松开 BOOT 后重试。正常启动应看到相机识别、Wi-Fi 获取 IP 和 ESP32 camera is live 日志。没有客户端时,0 clients subscribed 属于正常状态。

仅查看日志:

espflash monitor --port COM5

3. 导出 BIN

espflash save-image --merge 可导出从 0x0 写入的合并镜像。完整命令、独立镜像偏移与测试方法见 DEVELOPMENT.md。

保留的 scripts/flash.sh 针对特定坏 Flash 模组,依赖 macOS 环境,会修改分区并重编译。普通板卡使用上述标准流程;坏板限制见 HARDWARE.md。

客户端与装甲板

桌面端入口如下。安装依赖、主机 target 设置与功能说明见 desktop/README.md:

cd desktop
pnpm install
pnpm app

装甲板是独立的 ESP32-C3 / RISC-V 工程,不能使用 S3 的 ELF。ARMOR_TEAM=0/1 在编译期决定红 / 蓝队伍。接线、规则与构建烧录见 armor/README.md。

常见问题

现象 检查方法
提示缺少 esp-1.97.0.0 按上述命令安装指定名称的工具链,重新打开终端后检查版本
espup 查询 GitHub 版本返回 403 对上述固定版本安装命令追加 --skip-version-parse,或稍后重试
找不到 ldproxy / libclang 安装链接工具;Unix 加载 export-esp.sh,Windows 重开终端
ESP-IDF 路径或版本错误 清除旧 IDF_PATH 或指向 v5.5.5 SDK
Windows 路径过长 缩短仓库和工具链路径,见开发说明
Wi-Fi 连不上 核对 2.4 GHz、凭据和认证方式,再重新编译烧录
相机无输出 / 初始化失败 核对板型接线、供电与 Octal PSRAM
NO-EOI、几帧后停止 确认 CONFIG_CAMERA_PSRAM_DMA=n
在线但没有画面 检查客户端订阅、UDP 5600、防火墙及 AP 隔离
开启 VPN / 虚拟网卡后设备在线但无画面 使用支持按已发现设备 IP 定向续订的客户端;仅发送 255.255.255.255 广播可能走错网卡
改 C 组件后无变化 清理 esp-idf-sys 构建缓存后重编,见开发说明
主机项目被编为 Xtensa 显式指定宿主 target,见各子项目说明

用 python scripts/udp_probe.py 10 可独立检查视频协议;装甲板用 python scripts/armor_probe.py 15。这些脚本需要板卡在线,构建通过不代表已完成硬件验证。

测量帧率和丢帧可运行以下命令,将 IP 换成板卡实际地址。对照测试时先退出桌面客户端,避免额外订阅者增加无线负载:

python scripts/perf_probe.py 192.168.1.139 --seconds 30 --json perf.json

脚本定向续订,支持保留 VPN / TUN 的环境。输出包含完整帧率、帧损失率、接收间隔和组装耗时;这些时间不等于摄像头到屏幕的端到端延迟。

运动图传保持 CAM_GRAYSCALE=0,优先使用自动曝光上限和自动增益协同控制拖影。室内强制 −1 EV / 固定短快门在本机过暗,已撤回;提高增益上限也会增加噪点。需要限流时可设置 CAM_MAX_FPS=15,追求帧率时使用 0;可启用有界 JPEG 压缩调节应对发送回压,分片间隔按网络实测选择。灰度效果仍保留硬件 JPEG,不是改为 RGB565 或降低 JPEG 采样位数。75°C 是主动减载阈值,温度来自 ESP 芯片内部,不能代表摄像头或环境温度,也不是芯片损坏温度规格。五分钟彩色对照与此前中止的灰度长测见 STABILITY.md。

版本号与改动日志

版本号和 CHANGELOG.md 不手写,从 git 历史算:

node scripts/version.mjs --write   # 计算并写回 version.json / CHANGELOG / 客户端版本
node scripts/version.mjs --check   # 只校验(CI 与发布前用)
  • 版本号由提交类型决定:BREAKING CHANGE: → major,feat → minor,fix/perf → patch。
  • 构建号 = git rev-list --count HEAD,单调、不撞车。
  • 同一次计算还产出 version.json(固件 build.rs 读它)、CHANGELOG.json、 desktop/src/lib/version.ts。

固件与客户端的版本是联动过的:板子每 2 秒广播的 Hello 里带着语义化版本、构建号与 它编译进去的线路协议号;客户端读出来显示在设置与裁判端每一格下面,协议对不上时直接说明 原因("板子是协议 2,客户端是协议 1,画面会被丢弃,请重新烧录"),而不是只表现为没有画面。

细节见 desktop/README.md 与 protocol/README.md。

文档索引

S
Description
笑赛图传方案及裁判系统
Readme
16 MiB
Languages
C 41.7%
Rust 28.2%
TypeScript 14.2%
CSS 6.7%
C++ 3.9%
Other 5.3%