fix(packaging): 修复 Windows 安装与固件打包并完善客户端文案
- fix(installer): 修复低完整性产物无法写入临时目录的问题,并在 CI 打包前后检查完整性标签 - fix(firmware): 从本次 Cargo ELF 打包并同步配套镜像,修正短目录说明与 LLVM 搜索顺序 - fix(installer): 修复中文完成页和页眉遮挡,侧栏使用等比缩放的原始 Logo - style(client): 应用图标增加圆角,统一“裁判系统客户端”命名 - fix(config): 安装提示改用可选本地热点配置,排除 Wi-Fi 凭据 - docs: 润色项目说明并统一裁判系统术语 - test(packaging): 补充 Windows 完整性和固件打包回归,覆盖外部输出目录、旧镜像与重复生成 验证:Windows NSIS 构建及原生页面预览;通用与本地安装配置编译;固件 release 构建、重复打包和客户端离线校验;6 项固件打包回归与 Windows 权限回归;git diff --check。未执行实机烧录。
This commit is contained in:
+17
-44
@@ -53,8 +53,7 @@ armor/
|
||||
| 复位(新一局) | 长按 BOOT ≥500 ms → 满血、清计数 | `Button` + `Armor::reset` |
|
||||
| 打印状态 | 短按 BOOT → 立刻打一行状态并重发上报 | `ButtonEvent::Click` |
|
||||
|
||||
**整车一条血量。** 板子算账、裁判端只显示:四条上报的 `hp` 相同,`index` 分别是 0..3,
|
||||
界面按 `(team, index)` 收齐四条后显示同一个数值。
|
||||
整车共用一条血量,由板子计算,裁判端只负责显示。四条上报的 `hp` 相同,`index` 分别为 0..3;界面按 `(team, index)` 收齐四条后显示同一个数值。
|
||||
|
||||
参考实现使用全局 50 ms 冷却,一路受击会抑制其余路在该窗口内的击打。当前四路分别冷却,由 `cooldown_is_per_plate_not_global` 测试验证。
|
||||
|
||||
@@ -66,28 +65,21 @@ armor/
|
||||
magic "MVC1"(4) | type(1)=5 | version(2) | sender_id(4) | ... | team(1) | index(1) | hp(2)
|
||||
```
|
||||
|
||||
- **血量一变立刻发**,没变就按 200 ms 心跳重发。裁判端靠心跳判断板子还在线,一次
|
||||
丢包不该让界面退回"未接入"。
|
||||
- `sender_id` 用 MAC 派生的板子 id(`protocol::id_from_mac`),不是 0 —— 0 是裁判
|
||||
终端的保留 id,抓包时一块装甲板发的包不该长得像裁判端发的。
|
||||
- 血量变化时立即发送,未变化时按 200 ms 心跳重发。裁判端通过心跳判断板子是否在线,避免一次丢包就将界面切回“未接入”。
|
||||
- `sender_id` 使用 MAC 派生的板子 id(`protocol::id_from_mac`),不使用 0。0 是裁判终端的保留 id,需避免将装甲板的数据包误认为裁判端数据。
|
||||
- 四条 `index` 的 `hp` 相同(整车一条血),裁判端按 `(team, index)` 收齐四条才显示。
|
||||
- 广播而不是单播:桌面端电脑的 IP 是 DHCP 给的,会变。图传板用 Hello 广播绕开这件事,
|
||||
装甲板沿用同一个约定。
|
||||
- 上报使用广播而非单播,因为桌面电脑通过 DHCP 获取的 IP 可能变化。图传板使用 Hello 广播处理这一问题,装甲板沿用相同约定。
|
||||
|
||||
### 桌面端怎么收
|
||||
|
||||
`desktop/core/src/armor.rs` 里的 `ArmorHealthStore`(两种身份都启动):
|
||||
|
||||
- 绑 `0.0.0.0:5601` 收广播,按 `(team, index)` 存最后一次读数。
|
||||
- **四条都在 1.5 s 内收到**才交给界面显示;否则回退占位符。心跳 200 ms,1.5 s 是连丢
|
||||
7 个包,偶发丢包不会让读数来回跳。
|
||||
- 四条上报都在 1.5 s 内收到时才交给界面显示,否则显示占位符。心跳间隔为 200 ms,1.5 s 对应连丢 7 个包;偶发丢包不会让读数反复切换。
|
||||
- 用户端也要显示双方血条,所以两端都会占 5601。
|
||||
- 血量变化会打日志(`armor: board 0x… team 0 hp 100 -> 97`):窗口是给别人看的,
|
||||
抓日志的人也得能看见掉血。
|
||||
- 血量变化会写入日志(`armor: board 0x… team 0 hp 100 -> 97`),便于不查看窗口时也能核对扣血。
|
||||
|
||||
**界面显示血量数字。** `TeamHealthDto` 带 `hp` 与 `critical`:数值就在队伍名旁边,低于
|
||||
30 或归零时它与血条一起转红。归零**不写"已击毁"**——那一刻图传中央会给出
|
||||
“胜利 / 落败”的结论(见 [桌面客户端](../desktop/client.md))。
|
||||
界面在队伍名旁显示血量数字。`TeamHealthDto` 提供 `hp` 与 `critical`,血量低于 30 或归零时,数字与血条一起转红。归零时不显示“已击毁”,图传中央会显示“胜利 / 落败”(见 [桌面客户端](../desktop/client.md))。
|
||||
|
||||
## 构建
|
||||
|
||||
@@ -107,9 +99,7 @@ ARMOR_THRESHOLD=1200 scripts/build_armor.sh # 换受击阈值(看日志里
|
||||
|
||||
产物:`armor/target/riscv32imc-esp-espidf/release/armor`。
|
||||
|
||||
`ARMOR_TEAM` 是**编译期**注入的:换队伍重刷固件即可,不用先擦 NVS。NVS 里另外记着
|
||||
`team` 与 `color`(命名空间 `armor`,与参考实现同名),两者不一致时以 `ARMOR_TEAM`
|
||||
为准并覆盖,日志里会说一声。
|
||||
`ARMOR_TEAM` 在编译期注入,更换队伍后重新烧录固件即可,无需先擦除 NVS。NVS 另存有 `team` 与 `color`,命名空间为 `armor`,与参考实现同名。两者不一致时,以 `ARMOR_TEAM` 为准覆盖 NVS,并记录日志。
|
||||
|
||||
本机已有的 ESP-IDF 与工具链通过 `armor/.cargo/config.toml` 指定(`IDF_PATH` /
|
||||
`ESP_IDF_TOOLS_INSTALL_DIR`),不需要先 `activate` ESP-IDF。删掉那两行就交给
|
||||
@@ -124,9 +114,7 @@ esp-idf-sys 自己下载它钉住的版本。
|
||||
| v6.0.1(eim 装的) | `/Users/peng/.espressif/v6.0.1/esp-idf` | Xtensa + **RISC-V(`esp-15.2.0_20251204`)** |
|
||||
| v5.5.5(图传板在用) | `/Users/peng/.espressif/esp-idf/v5.5.5` | 只有 Xtensa |
|
||||
|
||||
C3 是 **RISC-V**,而 v5.5.5 那套缺 `riscv32-esp-elf`(它要的是 `esp-14.2.0_20260121`,
|
||||
本机没有,而 GitHub release 又下不动)。所以装甲板固件指向 v6.0.1 —— 它的工具链齐全,
|
||||
且 `esp-idf-sys` 0.38.1 支持的版本范围是 4.4.4+、5.0–5.5、**6.0–6.2**。
|
||||
C3 使用 RISC-V,而本机 v5.5.5 环境缺少 `riscv32-esp-elf`,所需版本为 `esp-14.2.0_20260121`;本机未安装,GitHub release 下载也未成功。因此装甲板固件使用工具链齐全的 v6.0.1。`esp-idf-sys` 0.38.1 支持的版本范围为 4.4.4+、5.0–5.5、6.0–6.2。
|
||||
|
||||
两套 IDF 共存没有冲突:各自 `.cargo/config.toml` 里的 `IDF_PATH` 是每套 crate 自己钉的。
|
||||
|
||||
@@ -143,10 +131,7 @@ cd armor/armor-protocol && cargo test # 22 个测试
|
||||
cd protocol && cargo test # 线路格式往返(含 ArmorHealth)
|
||||
```
|
||||
|
||||
(`armor-protocol/.cargo/config.toml` 把 target 覆盖回 host:它坐在 `armor/` 目录树
|
||||
里,否则会继承 RISC-V 目标,链接必然失败。这与 `protocol/`、`desktop/` 的做法一致。
|
||||
换到其他宿主平台时,配置里钉住的 `aarch64-apple-darwin` 不适用,按
|
||||
[开发与验证](../development/firmware.md) 用 `--target <宿主三元组> --config 'unstable.build-std=[]'` 覆盖。)
|
||||
`armor-protocol/.cargo/config.toml` 将 target 覆盖为 host,否则它会从上层 `armor/` 继承 RISC-V 目标,导致链接失败。这与 `protocol/`、`desktop/` 的做法一致。更换宿主平台时,固定的 `aarch64-apple-darwin` 不再适用,需按[开发与验证](../development/firmware.md)用 `--target <宿主三元组> --config 'unstable.build-std=[]'` 覆盖。
|
||||
|
||||
## 烧录与实测
|
||||
|
||||
@@ -159,7 +144,7 @@ scripts/flash_armor.sh --no-monitor # 只烧不抓
|
||||
|
||||
烧的是 ELF(`espflash` 自己从 ELF 里读分区表与 bootloader),芯片 `esp32c3`。
|
||||
|
||||
不打开裁判端界面,也可以单独确认板子在广播、血量对不对:
|
||||
不打开裁判端界面,也可单独检查板子的广播和血量:
|
||||
|
||||
```bash
|
||||
python3 scripts/armor_probe.py 15
|
||||
@@ -212,8 +197,7 @@ W (306504) armor: BOOT long press: hp reset to 100 (new round)
|
||||
|
||||
### 1. ESP32-C3 的 RMT 只有 96 个 symbol 的通道内存
|
||||
|
||||
C3 的 RMT 只有一个 block,总计 96 个 symbol,按 48 为单位分给两个 TX 通道。`mem_block_symbols = 64`
|
||||
会被向上取整到 96,也就是一个通道吃光全部内存,第二个 `rmt_new_tx_channel()` 就会失败:
|
||||
C3 的 RMT 只有一个 block,共 96 个 symbol,按 48 为单位分配给两个 TX 通道。`mem_block_symbols = 64` 会向上取整到 96,使一个通道占满全部内存,第二个 `rmt_new_tx_channel()` 因此失败:
|
||||
|
||||
```text
|
||||
E rmt: rmt_tx_register_to_group(168): no free tx channels
|
||||
@@ -221,10 +205,7 @@ E rmt: rmt_tx_register_to_group(168): no free tx channels
|
||||
|
||||
C3 只有 2 个 TX 通道,四路灯带无法各占一个。因此只创建一个通道,使用全部 96 个 symbol,再通过 `rmt_tx_switch_gpio()` 依次切换四个 GPIO。每帧为 3 珠 × 24 bit + reset,共 73 个 symbol,每路发送约 90 µs。
|
||||
|
||||
**为什么不用 `esp-idf-hal` 的 `TxChannelDriver`。** 它把 `rmt_channel_handle_t` 藏在
|
||||
结构体里(`as_ptr()` 是 `pub(crate)`),拿不到句柄就调不了 `rmt_tx_switch_gpio()`。
|
||||
所以 `leds.rs` 直接用 sys 绑定:通道、官方 copy encoder、transmit 都是 IDF 原生的那
|
||||
几个函数,用法与参考实现的 `led_strip_encoder.c` 一一对应。
|
||||
未使用 `esp-idf-hal` 的 `TxChannelDriver`,是因为它未公开结构体中的 `rmt_channel_handle_t`(`as_ptr()` 为 `pub(crate)`),无法取得句柄调用 `rmt_tx_switch_gpio()`。因此 `leds.rs` 直接使用 sys 绑定,通道、官方 copy encoder 和 transmit 均调用 IDF 原生函数,用法与参考实现的 `led_strip_encoder.c` 一一对应。
|
||||
|
||||
参考实现每 10 ms 刷新四路同色灯带。这里四路颜色独立,只有内容变化时才执行 `disable → switch → enable → transmit`;每秒另做一次全量刷新,避免灯带因掉电、静电等丢帧后一直保持错误状态。
|
||||
|
||||
@@ -232,10 +213,7 @@ C3 只有 2 个 TX 通道,四路灯带无法各占一个。因此只创建一
|
||||
|
||||
一次击打的波形只有几十微秒宽,oneshot 轮询容易错过尖峰,造成轻敲漏判。采样方式与参考实现一致:四通道合计 40 kHz,每通道 10 kHz;每帧为 10 ms、共 400 个采样点,主循环按帧处理。
|
||||
|
||||
`esp-idf-hal` 0.47 的 `adc::continuous` 多通道入口 `EmptyAdcChannels::chain` 的约束是
|
||||
`A: Adc` 且 `O: AdcChannels<AdcUnit = A>`:`ADC1` 满足前者、`ADCU1` 满足后者,同一个
|
||||
类型参数凑不齐,四路 ADC1 通道拼不出来。所以 `piezo.rs` 同样直接用 sys 绑定,与参考
|
||||
实现逐行对应。
|
||||
`esp-idf-hal` 0.47 的 `adc::continuous` 多通道入口 `EmptyAdcChannels::chain` 要求同时满足 `A: Adc` 和 `O: AdcChannels<AdcUnit = A>`。`ADC1` 满足前者,`ADCU1` 满足后者,但同一个类型参数无法同时满足,因而不能组合四路 ADC1 通道。`piezo.rs` 因此也直接使用 sys 绑定,与参考实现逐行对应。
|
||||
|
||||
采样点的通道号由硬件结果自带(`type2.channel()`),映射不依赖"结果按 pattern 顺序
|
||||
轮转"这个假设。
|
||||
@@ -261,12 +239,9 @@ factory 划了 2 MB。
|
||||
|
||||
### 7. 判断"连上没有"要问 IDF,不要信 esp-idf-svc 的事件缓存
|
||||
|
||||
`BlockingWifi::is_connected()/is_up()/wait_netif_up()` 读的是 esp-idf-svc 自己维护的
|
||||
WiFi 事件状态。实测在 **IDF v6.0.1 + esp-idf-svc 0.53** 上它会停在"已连接":日志说
|
||||
一切正常、`get_ip_info()` 却是 `0.0.0.0`,而 IDF 的 `esp_wifi_sta_get_ap_info()` 明明
|
||||
说没关联。后果是 `join()` 假装连上了、看门狗永远不会重试,板子静默失联。
|
||||
`BlockingWifi::is_connected()/is_up()/wait_netif_up()` 读取 esp-idf-svc 维护的 WiFi 事件状态。实测在 IDF v6.0.1 + esp-idf-svc 0.53 上,缓存状态会停在“已连接”:日志显示正常,但 `get_ip_info()` 为 `0.0.0.0`,IDF 的 `esp_wifi_sta_get_ap_info()` 也显示未关联。这使 `join()` 误判连接成功,看门狗不再重试,板子失联却没有相应提示。
|
||||
|
||||
所以固件里一律用 IDF 的 API 判断:
|
||||
因此,固件统一通过 IDF API 判断连接状态:
|
||||
|
||||
- 关联:`esp_wifi_sta_get_ap_info()`
|
||||
- 地址:`esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")` + `esp_netif_get_ip_info()`
|
||||
@@ -284,9 +259,7 @@ status: hp=0 hits=37 dead=true plates[front=37 rear=0 left=0 right=0]
|
||||
ap=SR-602-2G4 rssi=-29 ch=6 ip=192.168.1.117 reports=1745 errs=0
|
||||
```
|
||||
|
||||
- `peak[...]` 是**这一秒内每路 ADC 的峰值**,用来校准阈值:没敲时的底噪要明显低于
|
||||
阈值,正常敲击要明显高于它(实测底噪 ~400–500,敲击 2000–4095)。只报"有没有过阈值"
|
||||
没法判断阈值该调到哪。
|
||||
- `peak[...]` 记录这一秒内每路 ADC 的峰值,用于校准阈值:未敲击时的底噪应明显低于阈值,正常敲击应明显高于阈值(实测底噪 ~400–500,敲击 2000–4095)。仅记录是否超过阈值,无法判断应如何调整。
|
||||
- `thr=` 是当前阈值,`ARMOR_THRESHOLD=1200 scripts/build_armor.sh` 可以改。
|
||||
- `ap=` / `ip=` / `reports=` 用于区分未联网和已发送但对端未显示的情况。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user