feat: 链路控制、延迟校准与长测工具,补全文档
构建客户端 / 检查(类型 + 单元测试) (push) Canceled after 0s
构建客户端 / Linux x64(deb + AppImage) (push) Canceled after 0s
构建客户端 / Windows x64(NSIS 安装包) (push) Canceled after 0s
构建客户端 / macOS 通用(.app + .dmg) (push) Canceled after 0s

- 固件:新增 link_control / stream_control,覆盖运行中 Wi-Fi 断线恢复、取帧节拍、
  温控与质量/帧率减载;UDP 发送增加有界重试预算与帧期限,PSRAM JPEG 直发减少拷贝。
- 协议:重组器严格校验分片布局,统计未收齐、超时与完全缺失的帧,处理帧号回绕与设备重启。
- 桌面端:视频改 latest-value 通道并前置帧时间标记,拒绝积压和慢解码的过期画面;新增
  延迟标记页与 --latency-report 校准,完善录制、服务端与退出流程。
- 文档与脚本:新增硬件、开发、延迟、功耗、实时性、稳定性文档和改动记录,重写根 README;
  新增构建、性能、延迟与串口长测脚本,以及长测客户端脚本。
- 构建:固定 esp-1.97.0.0 工具链,分区表随构建复制,更新 sdkconfig 与忽略规则。
This commit is contained in:
2026-10-19 00:19:07 +08:00
parent 4794bed2cf
commit cc6b82d7ec
60 changed files with 4347 additions and 655 deletions
+18 -3
View File
@@ -23,6 +23,7 @@
mod commands;
mod demo;
mod latency;
mod recording;
mod server;
mod state;
@@ -42,6 +43,7 @@ const USAGE: &str = "\
--role <team|referee> 身份。默认看配置文件;没有配置文件时是 referee
--config <路径> 配置文件位置
--latency-report <路径> 打开裁判端屏幕标记校准,写入新的本地 NDJSON 报告
--demo 演示模式:画面与血量都是本地合成的
--list 打印发现的摄像头与装甲板后退出
-h, --help 显示这段帮助
@@ -78,6 +80,7 @@ fn main() {
struct Args {
role: Option<Role>,
config: Option<PathBuf>,
latency_report: Option<PathBuf>,
demo: bool,
list: bool,
help: bool,
@@ -104,6 +107,9 @@ impl Args {
"--config" => {
args.config = it.next().map(PathBuf::from);
}
"--latency-report" => {
args.latency_report = it.next().map(PathBuf::from);
}
other => {
// 见模块文档:macOS 的 .app 启动参数与 shell 里的笔误都可能出现在这里,
// 但报错的代价(窗口起不来)远大于收益。
@@ -124,7 +130,10 @@ fn run() -> Result<(), Box<dyn Error>> {
return Ok(());
}
let config_path = args.config.clone().unwrap_or_else(config::default_config_path);
let config_path = args
.config
.clone()
.unwrap_or_else(config::default_config_path);
// 首次启动时导入旧客户端的配置,并把旧文件改名成 .bak。
let (mut config, notes) = config::load_with_migration(&config_path)?;
@@ -134,6 +143,10 @@ fn run() -> Result<(), Box<dyn Error>> {
if let Some(role) = args.role {
config.role = role;
}
if args.latency_report.is_some() && config.role != Role::Referee {
return Err("Latency calibration requires a referee test configuration".into());
}
let latency_recorder = latency::Recorder::new(args.latency_report.clone())?;
for note in &notes {
log::info!("{note}");
@@ -150,8 +163,7 @@ fn run() -> Result<(), Box<dyn Error>> {
config_path.display()
);
let (state, video_socket, armor_socket) =
AppState::new(config_path, config, notes, args.demo)?;
let (state, video_socket, armor_socket) = AppState::new(config_path, config, notes, args.demo)?;
// 视频端口绑不上时没有 socket,接收线程也就没有意义——界面会显示那条说明。
if let Some(socket) = video_socket {
@@ -219,7 +231,10 @@ fn run() -> Result<(), Box<dyn Error>> {
let app = tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.manage(Arc::clone(&state))
.manage(latency_recorder)
.invoke_handler(tauri::generate_handler![
latency::latency_enabled,
latency::latency_sample,
commands::stream_info,
commands::get_snapshot,
commands::get_config,