26 Commits

Author SHA1 Message Date
wandering1 0d5cd1c8d1 chore(skills): 删除 template-skill 占位模板(零引用出厂噪音) 2026-11-05 10:11:35 +08:00
wandering1 c44f985afb docs: 谐波B行按板端前端真实衰减关闭——结论现状化与登记
CI / 静态门禁 (pre-commit) (push) Failing after 5m19s
CI / 主机侧单测 (MSVC) (push) Failing after 44s
- 执行手册:用户裁定删除 §8.6–§8.10 尾部(顺序/分诊/还原/归档节);
  谐波 B 行结论只留现状(删万用表分诊、三点拟合、嫌疑解除等过程叙述)
- 定案:250 Hz 读数 −4.4 % = IA 前端差分滤波(C35 100 nF + R19/R21
  各 1 k,一阶低通 fc ≈ 796 Hz)真实衰减,板端读数忠实、量算皆对;
  工频 50 Hz 仅 −0.2 % 不受影响;选择性成立;谐波产品化需板改
- 通道映射 §8.2 新增前端 RC 段:逐阶外推(21 次 −39.6 %,改 10 nF
  后 −0.9 %)、改板优先于软件修正的理由、证伪条件(250 Hz 附加滞后 ≈17°)
- 数据可信.md:§2.7 谐波行同步;任务卡打勾保留并标注四项未闭合
  (叠加轮/削顶低频档/测频改码档/模型仪器证伪);README 顺位表 ✅
- 登记簿 §3 新增「IA 前端差分滤波限制谐波带宽」条目(语义标识)
- 证据:ia_rc_model.py 输出归档 boot_log_data 并入 manifest(LF 重算
  md5);速查图生成器脚注同步并重跑(脚本与产物同笔)
- check_doc_links --all 通过(3206 处、无法判定 0)
2026-11-05 09:34:27 +08:00
wandering1 dc09217834 docs: 六轮动态验证收尾——削顶低频档按裁定关闭、记录回填与证据归档
- 削顶步 2(UB 注 DC −1.00 V)2026-11-04 已做:唯一 [ERR] rail edge=0x2
  sticky=0x2、UB 钉 0 码、撤注后 rail=0x002 恒持 ≥30 min = sticky 锁存实证
- 步 3(2.0 Hz 低频档)与复位复核经用户裁定 2026-11-04 关闭:直流档已闭合
  判据链,sticky 唯一清除路径 = adc_health_rail_init()(实现背书,不占台架)
- 相位 0°/90° 段 WAVE09 录波 fft 重算 Δφ = 90.05° PASS(判据 ≤2°);
  测频 332 行复证「如实报不可测」;谐波 A/C 过、B 行 −4.4 % 待万用表分诊
- 执行手册 ADC前端注入验证流程.md 各轮记录表回填 + 削顶卡步 3 行关闭注;
  实测结论新开 ADC通道映射说明.md §8.2(§8.1 静态轮加澄清句)
- 数据可信.md §2.7 / §4.6 / §5 任务卡现状化(削顶转 ✅、未闭合仅剩谐波 B)
- 证据归档 boot_log_data:6 份 txt 摘录 + 3 份录波段原件(*.BIN 被全局
  gitignore 挡,段原件是相位判据本体,force-add 入库)+ md5 清单
2026-11-04 22:16:35 +08:00
wandering1 87fa0276ad docs: 六轮验证文档剔除104读数通道+字母编号黑话清扫
CI / 静态门禁 (pre-commit) (push) Failing after 3m46s
CI / 主机侧单测 (MSVC) (push) Successful in 29s
注入流程/数据可信/故障录波设计说明三份文档与速查图
全部剔除104作为量测工具(YC交叉、0x401D/0x4012观察项、
§8.6第7步整行;总耗时150改135 min);104仍是固件消费者
与送检参照,架构表述保留。

注入流程全文清扫:A档/B档/定量档/定性档/段档/标量档等
字母编号与黑话清零;波形轮注简化为三句;通用动作段重写
为编号四步。

数据可信属主同步:§4.6前提表、§4.6.2三行判据、§4.6.3
读数点对比表、DoD证据归档口径。

内存内容分配策略.md:22修复编号改名事故(STM32L4被误替换
成「出口与记录」),按git原文恢复。

速查图生成器gen_ac_rounds_svg.py三处文案+脚注同步改并重跑,
脚本与产物同笔提交。
2026-11-04 19:04:31 +08:00
wandering1 740bcbcc37 Merge pull request 'fix(nand-ftl): 传输失败不再误标坏块 + format 不再被脏缓存挡死(#79)' (#81) from fix/nand-ftl-format-deadlock into master
CI / 静态门禁 (pre-commit) (push) Has been cancelled
CI / 主机侧单测 (MSVC) (push) Has been cancelled
Reviewed-on: #81
2026-11-04 19:01:36 +08:00
super_agent_bot 5538af829b fix(nand-ftl): 传输失败不再误标坏块 + format 不再被脏缓存挡死(#79)
CI / 静态门禁 (pre-commit) (push) Failing after 43s
CI / 主机侧单测 (MSVC) (push) Failing after 45s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 44s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 43s
根因(真机定案,问题 #79):旧 dhara_nand_erase/prog() 把一切失败一律
mark_bad,而 mark_block_bad 每次**立即持久化**板级 BBT(不可撤销)——
一次传输层/等忙抖动就把好块永久记成坏块。台架 129 个好块(1016–1142
连续段)被固化,journal prepare_head() 连续 8 次撞 is_bad → TOO_BAD →
Cache flush FAIL → reformat 又被同一页挡住 → 整卷禁用、重启不恢复。

- 修复①:只有芯片明确回 E_FAIL/P_FAIL(状态位证据)才标坏;
  传输/超时失败只报 DHARA_E_BAD_BLOCK 让 dhara 重试,不标坏
- 修复②:nand_ftl_format() 尽力刷一次脏缓存,失败即丢弃并继续
  dhara_map_clear()(格式化语义本就是销毁映射;旧实现第一步返回,
  恢复动作在同一页死锁,f_mkfs 全在旧映射上必失败)
- C4 还债:新增跨调用失败类快照 nand_ftl_fault_get/clear(20 B,
  resume/读/刷/同步四点位记账);defaultTask 探针按类分流,
  BSP_ERR_NO_SPACE 跳过重格式化(容量问题不该用 mkfs 解)
- 哨兵与诊断:init 解锁块保护(A0h)后补回读校验(与 B0h ECC 同族
  缺口,实测本板 A0h=0,非本次根因,留作哨兵);gd5f_bbt_load()
  合并后坏块数 > 出厂扫描时打「BBT after merge」,现场可区分
  真坏与误标固化
- 测试:主机单测 149→178 全绿;新增 format 死锁(TC-NANDPORT-800..
  813b)、传输失败不标坏(207 语义翻转 + 208/505);牙齿检查过——
  把修复回退即见红
- 真机:恢复动作 A 重建 BBT 清除已固化误标(3/3 PASSED,BBT
  version=71);产品镜像复烧 [FS] FATFS mounted、配置出厂模板
  落盘、0 条 flush FAIL——#79 真机闭环(旧卷内容随 map clear
  作废,台架卷为可弃数据)
- 文档:存储架构说明 §10.4 口径修正 + 新增 §10.4.1/§10.4.2(页级
  失败升级链与误标持久化机理),存储维护与容量恢复 §2 补板级 BBT
  独立持久化注记 + §2.1(动作 A 此前静默失效),登记簿 C4 销项
- Keil 0 错 0 警(Code=255552,与烧录验证镜像一致);doc-links 213
  文档通过
2026-11-04 18:58:13 +08:00
wandering1 1f94015434 Merge pull request 'refactor(time_sync)!: 退役 TCP :8888 对时任务,timeSyncTask 转纯 SNTP 校时节拍宿主(#78)' (#80) from remove-8888-timesync-a6657322808f into master
CI / 静态门禁 (pre-commit) (push) Failing after 5m15s
CI / 主机侧单测 (MSVC) (push) Successful in 39s
Reviewed-on: #80
2026-11-04 18:26:40 +08:00
super_agent_bot 3a2f9b21fb refactor(time_sync)!: 退役 TCP :8888 对时任务,timeSyncTask 转纯 SNTP 校时节拍宿主(#78)
CI / 静态门禁 (pre-commit) (push) Failing after 43s
CI / 主机侧单测 (MSVC) (push) Failing after 45s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 45s
SNTP 客户端已随 #73/PR #77 上线合入(merge 2b848c4a),用户裁定
8888 对时任务退出历史舞台(gitea:77 线程原话,跟踪 Issue #78)。

生产侧(App/sync/time_sync.c v2.0,任务入口名不变,freertos.c 零改动):
- 删除 :8888 监听、TIME 包重组、sys_clock 写入、OPER 落痕、
  net_drop_conn 队头自救整条链(TIME_SYNC_PORT 宏随退役删除)
- 任务体收敛为:就绪屏障(100ms 分片、osFlagsNoClear)→
  200ms 校时节拍 task_wd_beat → time_sntp_run(net_get_generation())
  → 分片延时;喂狗节奏(TASK_WD_TIMESYNC=5,required_mask)不变
- 时钟写入口自此 = SNTP / UI 手工 / 104 C_CS,双写竞态(后写者赢)
  随之消失(墙钟说明 §15 退役公告、§16 现势口径)

工具与测试:
- 删除 test/host/time_sync_server.py(对时源)与 e02_probe.py
  (队头阻塞探针,载体随监听退役;判据脚本 v5_log_gap_report.py
  保留供旧日志解读)
- time_sync 主机单测重写 v2.0:33 断言覆盖分片延时 / 屏障 /
  SNTP 节拍与网络代次当轮交接 / 未出网守门;stub 的
  net_socket.h 裁剪至 net_get_generation 作编译期绊线
- 覆盖率基线 --update-baseline 收口(time_sync.c 101→20 行为刻意
  缩减,全仓 48/48 套绿)

文档与图:墙钟说明 §15 改退役公告(教训保留:CH395F FIN 丢未读
缓冲)、§16 单一网络来源;产品需求 FR-TIME-2 退役注记、§6.3 重写;
架构总览 / 软件架构规格 / 网络配置 / 网络自愈 §9.3 / BSD 指南 /
看门狗说明心跳与白名单行 / 实机测试指南 / 部署环境 / 继保规划 /
UI补齐时基口径 / 页面补齐规划 SNTP 页行 / 任务规划 README 叶数与
暂缓项行(SNTP 叶已绑、UI_PAGE_MAP_COUNT=31 口径)等约 30 处现势
口径同步;arch / net_stack 两图生成器与 SVG 同笔重跑。

台账:ram_baseline --update + check_mem_numbers --write + 体积凭证
(Code 254,648→253,428,−1,220 B)。静态链复测发现 timeSyncTask
1,904 B / iec104Task 1,600 B 低于 ISR 硬线:master 2b848c4a 同判据
timeSyncTask 即 2,056 B(本 PR 只减不增,−152 B 旧任务自身帧),
双 FAIL 为 #74/#77 后既有债;抬栈属 Ask-first 未擅自执行,登记簿
新增「静态链硬线双 FAIL」待台架复采,复测快照入 docs/内存管理/data。

验证:keil-build 0 错 0 警;time_sync/sntp 主机单测绿;
ci_local 全绿;doc-links --all 0。
2026-11-04 18:07:03 +08:00
wandering1 2b848c4a0b Merge pull request 'feat(sntp): 板端 SNTP 校时接入——coreSNTP 序列化器 + [sntp] 段 + 设置页(#73)' (#77) from impl-sntp-sync-ca4c69f49d3e into master
CI / 静态门禁 (pre-commit) (push) Failing after 5m45s
CI / 主机侧单测 (MSVC) (push) Failing after 44s
Reviewed-on: #77
2026-11-04 16:46:30 +08:00
super_agent_bot ad7338365c feat(sntp): 板端 SNTP 校时接入——coreSNTP 序列化器 + [sntp] 段 + 设置页(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 6m14s
CI / 主机侧单测 (MSVC) (push) Successful in 54s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m35s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 47s
- vendored coreSNTP v2.0.0 序列化器(MIT,仅 serializer+LICENSE,零堆),
  48 字节报文编解码在库,节奏与准入判据在本方(RFC 4330 §10 调研口径)
- App/sync/time_sntp.c 挂 timeSyncTask 的 8888 事件循环,等待切片 ≤50 ms,
  不饿任务狗与 IWDG;懒建 UDP socket,代际变化/禁用/死错误码即关闭归还
- 防伪 = OriginTimestamp 逐位等于发出时 T1(含低 16 位随机小数位);
  mode/stratum/闰秒指示/KoD 按 RFC 拒收;DENY·RSTR 换服务器、RATE 只退避
- 主备轮换 + 失败指数退避(5 s 起、60 s 封顶)+ 首轮 1~5 分钟随机错峰;
  成功恢复 poll_min 周期(1~1440 分,出厂默认 60,默认不启用不发包)
- 钟面 = 本地时间:UTC+8 固定偏移落 sys_clock_set_unix(§1.1 时基裁定,
  第三个写入口,装置仍不存时区);|offset|<2 s 只记日志不步进,冷启动或
  更大偏差步进并回写 SD2506,留痕 [SNTP] step 与 syslog OPER
- 首个校验通过的同步置 SYS_EVT_TIMESYNC_READY(就绪位,消费方按需 NoClear)
- [sntp] 配置段(enable/server1/server2/poll_min)属主 sntp_cfg.c,
  注册在 net_cfg_load();UI 页 31「SNTP 设置」(密码叶):启用开关 +
  主/备服务器 octet 编辑 + 周期(分) + 保存,复用 net_cfg_save_request 链
- 测试与门禁:test/host/sntp 新套件(报文/防伪/KoD/退避周期/阶跃域/配置
  注册表)、net_cfg 与 time_sync 套件补 fake、黄金参考 66→74 张零漂移、
  字符集 228 字、Keil 0 错 0 警(RO +5,848 B 含字体)、覆盖率基线回填
- 文档:墙钟维护说明 §16、网络配置说明 [sntp] 节、配置文件设计说明变更记录、
  产品需求 FR-TIME-3 与 8.3/9.1.2 台账、UI 补齐规划/UI补齐 31 叶口径、
  登记簿销项并登记「SNTP 校时真机判据」等台架(PC 时间源 192.168.31.161/.13)
2026-11-04 16:20:22 +08:00
wandering1 c1beec50e7 Merge pull request 'fix(serial-assistant): daemon 重启自动重开口 + 自动重连 + 日志缺口标记(#75)' (#76) from improve-serial-assistant-8e959e85113d into master
CI / 静态门禁 (pre-commit) (push) Failing after 45s
CI / 主机侧单测 (MSVC) (push) Failing after 43s
Reviewed-on: #76
2026-11-04 11:39:09 +08:00
super_agent_bot dfd1528409 fix(serial-assistant): daemon 重启自动重开口 + 自动重连 + 日志缺口标记(#75)
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m19s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 44s
CI / 静态门禁 (pre-commit) (push) Failing after 4m51s
CI / 主机侧单测 (MSVC) (push) Failing after 43s
按 Gitea issue #75 R1–R5 修采集层「daemon 死了采集静默停摆」族问题:
旧行为下 daemon 崩溃/被杀/读错丢口后,新实例起得来但口不开
(run() 先 _save_meta 把现场覆成 null,且无任何重开路径),
serial.log 缺口无边界,wait-idle 计满还良性返回 no_data,
把「采集器死」伪装成「板子静默」。

- R1 启动自动重开:meta.json 的 port 字段升格为「开口意图」现场
  (缺口期不清空,显式 close/stop 清除);run() 先读旧 meta 再覆写;
  重开在串口线程内执行(串口对象唯一持有者不变量),首次 = 出生 +1.5s。
  restart 特判:cmd_start(force) 在 stop 前抓 /status 现场、新 daemon
  就绪后 POST /open 开回来(stop 语义不变,结果进返回 JSON 的 reopen 键)。
- R2 失败自动重连:_port_error 后退避重试(REOPEN_SETTLE_S=1.5 起步、
  倍增、REOPEN_BACKOFF_MAX_S=30 封顶)直到成功或显式 close;
  逐次失败原文进 daemon.log(dlog=stderr),不刷 serial.log。
- R3 缺口标记:serial.log 落 [SYS] 行——daemon started(新旧 pid +
  重开现场)/ GAP-START(丢口与崩溃恢复两种,窗口起点下限声明)/
  GAP-END(窗口秒数 + 尝试次数 + 字节不可找回)/ GAP-RETRY(退避期
  仅首次 + 之后每分钟 ≤1 条,口径定死并写入设计说明 §6.1)。
- R4 状态可见:status 新增 auto_reopen{enabled,pending,attempts,
  last_error,next_retry_in_ms};port 新增 desired(意图)与 ready
  (实际)并列,缺口期 name/baud 显示待重开参数;wait-idle 入口加
  无口门:口未开秒回 ok:false + 现场,不再计满返回良性 no_data。
- R5:VERSION=1.2.0-skill;selftest 新增 T10–T15 共 22 项无头用例
  (重开意图判定 / 退避倍增封顶 / 缺口行格式 / 无口门 / auto_reopen
  三态 / restart 捕获),合计 45 项全绿;SKILL.md 与使用指南、
  设计说明 §6.1+§13 同步(机理只在设计说明详述一处)。

兼容性:JSON 只增字段、退出码形状不变;run_boot_test.py 正常路径
不受影响,其 wait-idle 撞无口时按采集错误 exit 2 归因(正是工单
要的区分)。

验证:selftest 45/45 PASS;COM3 台架三场景全过(崩溃恢复自动重开
+ GAP 标记、显式 close 后 restart 不重开、wait-idle 快速失败),
transcript 见工单汇报。
2026-11-04 11:10:04 +08:00
wandering1 f8c9296392 Merge pull request 'feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73)' (#74) from udp-queue-impl-ddb7c192c16e into master
CI / 静态门禁 (pre-commit) (push) Failing after 5m10s
CI / 主机侧单测 (MSVC) (push) Failing after 43s
Reviewed-on: #74
2026-11-04 10:11:24 +08:00
super_agent_bot c9c9dc8b0d docs(测试): 阶段 16 按两轮镜像分别登记尺寸,产品 Code 对齐 249,120(#73)
CI / 静态门禁 (pre-commit) (pull_request) Failing after 5m3s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 43s
恒剥改造让产品构建 `Code` 从 249,124 变成 249,120,`docs/测试说明/板级测试/CH395F_Test_Guide.md`
§12.6 里两处跟着真机重算:

- 构建实测段不再只写一个镜像——第一轮的 `phase16c.hex`(836,489 B,Code 259,768 / RO 37,244)与
  全绿那轮的 `phase16d.hex`(836,608 B,Code 259,764 / RO 37,288 / RW 2,060 / ZI 151,596)分开登记,
  并写明第二轮与前一轮的差异来源:生产代码少一次形态比较(−4 B)+ 断言文案改写(RO +44);
  产品态那行改成 `Code 249,120`,并说明与 249,124 的 −4 B 属生产代码变化、与阶段 16 无关。
- 「实测结果」那行补上板端串口原文里的构建戳 `Nov  4 2026 08:40:28`。
- 2026-11-03 夜那条进度里的「1603 之后仍未测」改成「当时未测(次日已补全)」,避免读起来像现状。
- `docs/测试说明/单元测试/主机单测覆盖率口径.md` 第 4 轮的真机回归条目,把「head `1b5e5d4` + 剥头修正」
  改成剥头修正实际所在的提交 `b5671e8`。
2026-11-04 09:45:34 +08:00
super_agent_bot fe4f0db01c chore(台账): 恒剥修正让 Code 少 4 B,机器回填内存台账与同码凭证(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 4m45s
CI / 主机侧单测 (MSVC) (push) Successful in 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 5m16s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 43s
剥头判据简化(去掉一次形态判断与一次比较)之后,产品构型 `Code` 从 249,124 变到
249,120,`ER_IROM1` 278,960 → 278,956、ROM 合计 281,012 → 281,008;`RW` / `ZI`
一字未动。台账里那些数字是受管区,按仓库约定不手改:

- `python scripts/check_mem_numbers.py --write` 回填 `docs/内存管理/RAM使用分析与优化方案.md`,
  `--scan` 复跑 exit 0;
- `python scripts/ram_baseline.py --write` 回填 `docs/内存管理/RAM预算台账.md`,
  `--gate` 现在 **PASS:RW_IRAM1 72.2 % / RW_CCM 87.9 %(阈值 92 %)**;
- `python scripts/ram_baseline.py --update` 追加 2026-11-04 趋势行,并写出本轮
  体积同码凭证 `scripts/size_attestation.json`(索引指纹 2f112c1679d6…,1468 个文件)。
2026-11-04 09:25:49 +08:00
super_agent_bot f10bb8df65 docs(net/udp): TRAP-NET-22 追加真机定案,网络与测试文档同步(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 5m30s
CI / 主机侧单测 (MSVC) (push) Successful in 50s
CI / 静态门禁 (pre-commit) (pull_request) Has been cancelled
CI / 主机侧单测 (MSVC) (pull_request) Has been cancelled
- `docs/问题记录/CH395F_Trap_Records.md`:TRAP-NET-22 追加 2026-11-04 段——现象
  (客户端形态 48 B 读成 56 B、首字节 0x38)、根因(**帧格式在 OPEN_SOCKET 那一刻
  由 DesIP 定死**,运行时改 DesIP 不改它)、修法(恒剥 + 源地址一律取信息头),以及
  这条最该记的教训:手册一句话读错,就写错一层契约,而且主机侧 240 轮随机长跑会
  照着同一个错模型绿过去。
- `docs/网络/BSD_Socket_API_使用指南.md` §6.0/§6.3:形态表与"客户端不剥头"条款
  按定案重写,`sendto` 显式目的地址 = 切发送去向,不再承诺帧格式变化。
- `docs/网络/BSD_Socket_API_函数调用关系.md` §4.11:`net_udp.c` 一表的收包路径
  说明同步。
- `docs/测试说明/单元测试/主机单测覆盖率口径.md` 第 4 轮:26 组 UDP-1~26、全套
  677/677、UDP-26 的来由(覆盖率门禁照出兜底路径没人走,补测试而不是降基线)、
  以及真机第一轮 33/47 到第二轮 77/77 的经过。
- `docs/测试说明/板级测试/CH395F_Test_Guide.md` §12.6:阶段 16 的实测结果表
  (2026-11-04 08:42,板端 `77/77`、PC 端 `27/27`,台架必验 ①~⑤ 全部成立)+
  关键串口原文 + 唯一未量项(等待期 CPU 占用 / IWDG 余量的定量读数)如实留白。

登记簿销项按仓库约定不进本分支(分支不动 `docs/任务规划/**`)。
2026-11-04 09:15:53 +08:00
super_agent_bot 66725c0fa3 test(net/udp): 阶段 16 判据文字按真机定案改写,PC 对端不再被 ICMP 拒收打爆(#73)
板级 1603 的标题写的是 "48-byte reply, no header strip in client mode",判据本身
(读回 48 字节且逐字节等于发出去的载荷)在真机上就是对的——错的是那句解释。标题
与两处注释按定案改写,判据一字不动:改判据等于把测出来的事实抹掉。

PC 对端 `test/host/udp_queue_bench.py` 修了一条会把台架自己打死的毛病:Windows 上
对端端口没人听时,发出去的 UDP 会收回 ICMP port-unreachable,CPython 把它变成
`ConnectionResetError [WinError 10054]` 挂在下一次 recv 上(3.14 既没有
`socket.SIO_UDP_CONNRESET` 也拒绝那个数字 ioctl,只能接住这个异常)。原来的写法是
一接到就立刻重发,run #3 从第 148 秒起以约 100 包/秒灌进 CH395F,日志刷出
940,949 字节的「TC 0x02: 无回显,重发」——**板子的真实行为被自己的对端淹掉了**,
看起来却像板子不回包。现在一轮之内跑满预算、按次数限流打印。

回路自检 `test/host/udp_queue_bench_loopback.py` 改后 **27/27 PASSED**。
2026-11-04 09:15:52 +08:00
super_agent_bot 0d469a835a test(net/udp): 主机单测按真机改写 UDP-4/17/22/24,补残包组 UDP-26(#73)
剥头判据改了,测试就得跟着改——但这一条提交里真正值钱的是"为什么会错到":
UDP-4 当时写的是"客户端形态整包都是载荷、源地址取对端记账",UDP-24 的 240 轮
定种子随机长跑也照同一个模型脚本报假件。**随机长跑全绿恰恰不能证明模型对**:
它扫的是我自己写的那套模型,模型错了它一样绿。只有真机那次 48→56 的读数把这件事
钉死。

- UDP-4 改写为"客户端形态同样剥头",源地址来自信息头(脚本里特意让头里的源
  `119.28.220.215` ≠ 记账对端 `119.28.220.214`,这样"取错地方"就必然失败),
  并补 UDP-4h 断言 `addrlen`/family 与服务器形态一字不差——一条读路径就该一个契约。
- UDP-17h/m、UDP-22c、UDP-24 的客户端轮同样按定案重写(先 `reset_recv_len` 再
  宣布长度,假件的收包脚本必须可重排,这是长跑教过的第二条)。
- 改完判据后覆盖率门禁照出 `net_udp_src_from_peer()` 一行没人走了。**没有把基线
  调低**,而是补 UDP-26(9 条)钉住"`<8` 字节残包"那一侧:整包原样交付、一个字
  节都不许剥、源地址退到对端记账;没记账时交出全零这个"我不知道",而不是包里的
  字节——它是记账那本账如今唯一的读者。

全套 **677/677 绿**(改造前 470);`net_udp.c` 回到 237/239 行、14/14 函数,
`net_socket.c` 966/969 行、59/59 函数,全仓 8372/9290(90.1 %)、47/47 套,
`scripts/test_coverage_baseline.json` 与改动前一字未变(即无回退)。
2026-11-04 09:15:52 +08:00
super_agent_bot b5671e8c02 fix(net/udp): 收包剥头只看芯片宣布的字节数,与 udp_mode 无关(#73)
真机第一轮(2026-11-04 08:16,阶段 16 台架)把本轮最大的一条设计错误照了出来:
TC-NET-1603 在客户端形态下发 48 字节应答,板子读到的是 `n=56`、首字节 `0x38`
——56 正是信息头的长度字段,也就是**头还在**,而我把它的 8 字节当成了载荷。

设计时按手册 §9.2.4 读出一句"客户端模式不带信息头",就以为运行时把 DesIP 从
全 F 改成具体对端会让头消失。真机给的定案是:**加不加 8 字节信息头,是在
OPEN_SOCKET 那一刻由当时的 DesIP 定死的**,之后再改 DesIP 只改发送去向与芯片
的收包源过滤,不改收包帧格式。本层 `net_bind` 一律以 DesIP=0xFFFFFFFF 开
Socket,所以对本层的 UDP socket 而言头恒在、恒剥。

- `net_udp_datagram_read` 的剥头条件由 `udp_mode == SERVER && avail >= 8` 改为
  **只看 `avail >= 8`**;源地址一律从信息头里取——头里的源 IP/端口是本包的真实
  来源,比"我把包发给了谁"这本账更准(NAT 后的时间服务器就是两种地址不一致的
  那种)。
- 不足 8 字节的残包保持既有定案(TC-NETSOCK-440o):无从判头,整包按载荷交付,
  源地址退到对端记账。这也是 `net_udp_src_from_peer()` 如今唯一的读者。
- `udp_mode` 语义收窄为"芯片的对端过滤 + 发送去向",**不描述收包帧格式**;
  `net_types.h` 的 UDP 形态块、两个模式宏的注释、`net_sendto` / `net_recvfrom`
  的函数说明一并按定案改写。字段本身没动,仍复用原有的 2 字节 padding,RAM 零增长。

产品构型 Keil 构建 0 Error 0 Warning,`Code=249120 RO=29836 RW=2052 ZI=149996`,
与改造前的产品基线同尺寸。判据文字与真机证据见同批的测试、文档提交。
2026-11-04 09:15:17 +08:00
super_agent_bot 1b5e5d424c test(net/udp): 主机单测补 UDP-25 两条 socket 并存的隔离(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 45s
CI / 主机侧单测 (MSVC) (push) Failing after 50s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 45s
前 24 组全都只用一条 UDP socket,漏掉的正是队列化真正要保的那半句承诺:
**等待是一条 socket 自己的事**——串行的是命令,不是等待者。

UDP-25 用 13 条断言钉住两条并存 socket 的四件事:fdB 来包不得结算也不得通知
fdA 的 parked 收包(连它的调用缓冲都不许被写一个字节);fdA 自己的包到达时按
自己那一包的表头结算并恰好通知一次;fdB parked 时 fdA 自己 recvfrom 照常拿到
数据;关停在等的 fdB 必须当场结算、槽位与出参槽都不残留。

写这组时踩到假件的一个真坑,值得记下来:假件的 get_recv_len 是"每次读消耗一格、
最后一格永远重复"的脚本队列,本组前面给同一 fd 留过旧长度,不清就会拿**旧长度配
新内容**(现象是 UDP-25j 读出 12 字节而不是 7 字节),所以每条用例前用
ch395f_fake_reset_recv_len(fd) 归零再宣布——这是测法问题,不是被测代码的问题。

全套 667/667 绿(改造前 470,本次 +13);net_udp.c 仍 237/239 行 14/14 函数,
net_socket.c 966/969 行 59/59 函数,模块级覆盖率无回退。
2026-11-04 01:28:01 +08:00
super_agent_bot f4ed8a4db2 test(net/udp): 阶段 16 补 TC-NET-1610 免对端自检,断线时也有板端证据(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 4m18s
CI / 主机侧单测 (MSVC) (push) Successful in 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 3m49s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 45s
第一次上台架就是被「没有对端」卡住的:peer 脚本被 Windows 的 ICMP 10054 打死、
梯子错位,TC-NET-1601 之后一步都测不到,整趟真机证据直接归零。板子的 10 pin 杜邦线
又断了(#73 讨论区已记),台架短期内还会反复断,所以把「不需要 PC 发一个包也能确认」
的那一半补进阶段 16。

板端 test/board/net_test_task.c 新增 udp_bench_peer_free_check(),17 条断言
(TC-NET-1610a~q),执行位置在 1601 之前、编号留在段尾:编号是给台账用的,不该因为
断线重跑而漂。四类判据:

- 形态记账:新建 DGRAM socket 与 bind :6003 之后都读 NET_UDP_MODE_SERVER;
- 参数与出参守卫:未 bind 的 net_sendto 报 -5 NOTCONN、流式 net_recv 读 DGRAM 报
  -3 INVAL(绕开剥头就会静默吃 8 字节)、to_ms==0 报 -3 INVAL、src_addr 缺 addrlen
  或容量只有 4 字节报 -3、空缓冲 DONTWAIT 当场报 -7 WOULDBLOCK(不登记挂起);
- 「阻塞收包已改成挂起登记」:net_recvfrom_to(2000) 返回 -2,实测睡眠落在
  2000~4500 ms——等待睡在任务通知上,不在 SPI 总线上自旋。预算取 2 s,压在产品
  IWDG(2.5 s)与 netTask 喂狗窗口之下,万一退回旧版自旋,会先被看门狗抓到;
- 关闭与复用:net_close 后同一槽重新 bind :6003 不被上一条命毒化(DONTWAIT 仍 -7),
  set_peer(fd, NULL) 在本就是 SERVER 时是无副作用的 0 返回(不许重开芯片 socket)。

代价只有 flash:bench 态 Code 257,388 → 259,768、RO 34,136 → 37,244(全是断言字符串),
RW/ZI 一字未增(复用那块 1,600 B 收发缓冲),RW_IRAM1 仍 96,232 B / 73.4%。
产品态(四行宏回默认重新构建)Code=249,124 / RW=2,052 / ZI=149,996,与本阶段之前
逐字节一致,阶段 16 在产品里被预处理干净。两态均 0 error 0 warning(2026-11-04 01:06)。

文档同步:CH395F_Test_Guide §12.6 用例表补 TC-NET-1610 行与「没有对端也要有证据」一段;
ch395f_test.h 的 RAM 台账补这轮增减数字与其来源;主机单测覆盖率口径 第 4 轮记明这半
「已编过、仍未在真机上跑过」——断线期间它一条也测不到,不能算证据。
2026-11-04 01:11:01 +08:00
super_agent_bot 9608b7a8ea test(net/udp): 加 240 轮定种子随机长跑 UDP-24,配套假件补可重排收包脚本(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 45s
CI / 主机侧单测 (MSVC) (push) Failing after 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 46s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
真机台架被断电和断线卡住,这一段只推进纯软件侧:把 UDP 数据面的随机序列
扫起来,并修掉随机长跑逼出的两条"测法自误"。

- ns_s7_udp.c 新增 UDP-24:固定种子 20261104 的 240 轮随机交互,每轮随机
  载荷长度 1~112(112 = 假件收包流 128 B 上限减 8 字节信息头)、源端口、
  源 IP、SERVER/CLIENT 形态、显式对端 sendto 与按记账对端 sendto,每 17 轮
  close + 重 bind 一次。断言收在六族不变量上(长度等于芯片宣布值、既不吞
  信息头也不丢真字节、源地址按形态分别取自表头/对端记账、udp_mode 与命令
  一致、close/重 bind 后出参槽与挂起槽不残留、每次 sendto 交给芯片恰好 len
  字节),并要求 ≥90% 轮次端到端干净(实测 240/240)。
- 长跑逼出的两处都是测法问题,不是被测码 bug,但只有长跑才逼得出:
  1) 连续 sendto 不注入 SENBUF_FREE 时,第二次发送会合法转挂起,而本套的
     挂起 send 替身从不完成,于是 FATAL 掐死整套件(连汇总行都没有)。改法
     照 440i/440j:set_sint_sticky(SENBUF_FREE) + push_glob + g_ns_int_pin_set=0,
     注释点名 TRAP-NET-04,背靠背发送的真机面留给 TC-NET-1609。
  2) 假件 ch395f_get_recv_len 是"按调用序号取、耗尽后重复最后一个"的脚本且
     容量有限,240 轮只追加不清空,序号撞满容量后每轮读到同一个陈旧长度
     (症状:len 在随机而 n 恒为 28,前 24 轮全绿,最容易误判成产品 bug)。
     补加法助手 ch395f_fake_reset_recv_len(sock),只归零该 sock 的脚本长度与
     序号,既有单轮用例不调用它、行为一字未变。
- 覆盖率口径第 4 轮同步:用例面记到 24 组、全套 654/654(改造前 470),
  新增一条"随机长跑逼出两条测法自误"的教训(长跑对假件的第一要求是
  状态可重排,不是能追加)。

验证:net_socket 套件 654/654 绿;全量覆盖率 47/47 退出码 0,net_udp.c
237/239 行 99.2%、14/14 函数,net_socket.c 966/969 行 99.7%、59/59 函数,
总行 8372/9290(90.1%),无 REGRESS;cppcheck(--hook-stage manual)退出码 0;
check_doc_links --all 通过(213 个文档,无法判定 0);pre-commit 对本批文件
全绿。基线 JSON 无变化(本轮只加测试,未动生产码),故不随本提交改动。
2026-11-04 00:46:08 +08:00
super_agent_bot fc0d3e4b5c test(net/udp): 阶段 16 台架加固 + 真机首跑证据回填(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 5m39s
CI / 主机侧单测 (MSVC) (push) Successful in 30s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m15s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 47s
第一次上真机把台架的两处软肋照出来了,都不是被测代码的问题:

- peer 死在 Windows 的 ICMP 10054:板子还没 bind 时那一下端口不可达会被记在套接句上,
  下一次 recvfrom 才抛出来。本机 CPython 3.14.7 既没有 socket.SIO_UDP_CONNRESET 也不接受
  它的数值形式,所以主力防线改成把 10054 当「这一轮没收到」继续重试,并在回路自检里加
  冷端口探针——探针走脚本真正在用的 _recv_maybe,另拿裸套接句证明这台机器确实会回 ICMP,
  否则那条判定就没有牙。
- 单步预算错位:板端每步等 25 s,peer 每步只等 8 s,一步超时后整条梯子全灭。现在 peer
  每步都重发、单步预算 --step-budget 默认 35 s(小于 26 s 打警告);板端 udp_bench_expect
  按载荷第 0 字节的步号自愈,比本步小的旧包丢掉继续等(上限 UDP_BENCH_STALE_MAX = 8),
  比本步大的直接报出来。DBG_WARN 在本工程不存在、字符串字面量必须纯 ASCII,两处已改。

真机已拿到的证据:TC-NET-1601a/b/c(建 socket、bind :6001、net_udp_get_mode 回 SERVER)与
TC-NET-1602(服务端形态收到 20 B、addrlen/sin_family/源端口全对、逐字节回显、形态翻成
CLIENT)全绿——剥 8 字节信息头与源地址回填在真芯片上成立;②③④⑤ 因板子当晚两次失电
(19:19、21:0x,SWD No target connected + ping 不通)未测,指南 §12.6 与覆盖率口径第 4 轮
的状态条据此改写。产品态(宏回默认)重新构建 0 Error 0 Warning,Code 249,124 / RO 29,836 /
RW 2,052 / ZI 149,996 与本阶段之前逐字节一致,阶段 16 在产品里被预处理干净。
2026-11-03 21:28:36 +08:00
super_agent_bot b0a6394aa1 fix(test/udp): peer 换源端口上报方式并补 PC 回路自检(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 5m4s
CI / 主机侧单测 (MSVC) (push) Successful in 44s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m48s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
- test/host/udp_queue_bench.py:Windows 上未 connect 的 UDP socket 调 getsockname()
  直接抛 WinError 10022(请求一个未连接套接句的名称),脚本在第一条用例就崩。
  源端口改为首次收到回显时打印——它本来就是「板端剥头/源地址判定对不对」的直接读数。
- 新增 test/host/udp_queue_bench_loopback.py:不用板子的 peer 自检,在 127.0.0.1 上放
  一个按板端阶段 16 同样三条规矩说话的假板子(原样回显、tag 0x04 只回 64 B、>1KB 按
  1024 拆片),先把步骤表 / 模式函数 / 分片预期验掉。2026-11-03 首跑 27/27 绿,
  实测拆片形状 [1024, 1] 与 [1024, 448]。
- CH395F_Test_Guide.md §12.6:运行方法补「先跑自检」这一步;>1024 B 的 UDP 拆片从
  「多收到一片就 FAIL」改成「实测观测量」(拼接后逐字节一致才算绿),并说清这是既有
  NET_SEND_CHUNK_MAX 行为、不是本轮引入;脚本表补一行。
- 板子自 19:19 起未再上电(ping 不通、SWD No target、串口静默),阶段 16 真机那一趟
  仍未跑,PR #74 的合并前提不变。
2026-11-03 20:46:15 +08:00
super_agent_bot 8a81fa9e41 test(net/udp): 阶段 16 UDP 数据面真机回归台架(板端 + PC peer),台账补实测数字(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 5m27s
CI / 主机侧单测 (MSVC) (push) Successful in 33s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 7m6s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 48s
- 板端 net_layer_udp_bench_test_main:TC-NET-1601~1609,逐步覆盖主机测替代不了的
  5 条台架必验——形态切换与 bind 后首包、48 B 应答在客户端形态的载荷完整(TRAP-NET-22
  的真机对照)、截断包排空、挂起等待期间的 IWDG 余量、DesIP 先于 OPEN_SOCKET 的顺序。
  每步都有 25 s 预算,PC 缺席时报 [FAIL] 而不是把 testTask 钉死。
- PC peer test/host/udp_queue_bench.py:板端先 bind 当服务端,对端地址从收到的包里学
  (PC 是 DHCP,TEST_PC_PEER_IP 那个固定地址不在本机上);对 >1024 B 的回显额外监听
  第二片,把 net_send_locked 分块在 UDP 上的后果量出来。
- ch395f_test.h:阶段 16 宏默认关(与其它阶段一致),RAM 台账补 2026-11-03 实测——
  台架镜像 RW_IRAM1 96,232 B / 73.4%,阶段 16 自身代价 1,600 B;产品镜像 RW+ZI 与改造前逐字节相同。
- 文档:CH395F_Test_Guide §12.6 跑法与判据 + Python 脚本表加一行;覆盖率口径第 4 轮
  「台架必验」指向本阶段的可执行形态,并写明尚未在真机上跑过。
2026-11-03 20:18:24 +08:00
super_agent_bot fff961d803 feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73)
CI / 静态门禁 (pre-commit) (push) Failing after 49s
CI / 主机侧单测 (MSVC) (push) Failing after 30s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
前置来自 #57 SNTP 调研:旧 net_sendto / net_recvfrom 由调用方直调
SPI 且无限自旋收包,既违约「netTask 是芯片唯一访问者」,又把 IWDG
预算吃在自旋里。

改造面
- 新增 Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译):
  bind/set_peer/sendto/recvfrom 四条执行体 + 挂起收包表 + tick;
  net_msg_type_t 加四型,公开 API 一律「入队 → 睡在通知上」。
- 源地址出参不搭 net_msg_t(按值拷贝,AGENTS 禁加回传字段):改住
  net_udp.c 的 per-fd 槽,netTask 就地写回调用方内存,通知值只带
  结果码;每条出口都清槽,泄漏 = 该 fd 永久 BUSY。
- 发送复用 net_send_locked,不写第二份分块逻辑;「会话是否已死」
  收成一条判据 net_send_state_down()(TCP 看 ESTABLISHED、UDP 看
  UDP_OPEN),原先 4 处各写一遍。
- 顺带修掉真缺陷(新条 TRAP-NET-22):旧 sendto 把 DesIP 写成具体
  IP,芯片静默切 UDP Client,而旧 recvfrom 无条件剥 8 字节 → SNTP
  这类 48 B 应答丢载荷前 8 字节并交出垃圾源地址。加 udp_mode(塞进
  原有 padding,sizeof 不变)、bind 恒 SERVER、显式对端 sendto 切
  CLIENT、set_peer(fd, NULL) 轻量回 SERVER,仅 SERVER 且 avail >= 8
  才剥头。
- 截断定案:一次调用交付一个数据报,buf 装不下的尾巴用 64 B 栈上
  scratch 排空(不用 clear_recv_buf——它连后面已到达的包一起丢)。
- 取消点接齐:net_init 重建 cancel_all、net_socket 分配 fd clear、
  net_close settle_close + clear、重 bind 前先结算等包者,
  net_process_messages 第 5 个 tick 位挂 net_udp_tick。
- 收紧两处契约(原实现无生产调用者):UDP 未 bind 一律 -5;
  net_recv / _nb / _to 拒 DGRAM(绕过剥头就是静默吃 8 字节);
  NET_ERR_NOT_IN_NETTASK(-12) 退役为历史常量并写明原因。

验证
- 主机单测 test/host/net_socket 全套 616/616 绿(改造前 470):新
  分片 ns_s7_udp.c 20 组,改写 440 / 555 / 560 组;覆盖率
  net_udp.c 234/236(99.2%)、net_socket.c 956/969(98.7%),
  scripts/test_coverage_baseline.json 随本提交登记新文件。
- 测试抓到 4 个被测码真 bug:自等守卫误用调用侧谓词、芯片长度被读
  两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。
- Keil ARMCC 0 Error(s) 0 Warning(s);net_udp.o Code 1,358 / ZI 224
  (8 fd × 28 B,其余模块零增长),全仓 RW_IRAM1 72.2%;内存台账
  受管区按门禁提示用 --write / --update 机器回填,未手改数字。
- pre-commit 全量钩子(含 doc-links、coding-standard、mem-budget、
  generated-fresh、bsp-err、registration、nested-comment)全绿。

限度
- 本轮无板子:真机回归 5 条(两端互通、set_peer(NULL) 能否恢复收
  广播、截断排空后下一包完整性、挂起 recv 期间 IWDG 余量、bind 写
  DesIP 与 open 先后的真芯片效果)已写进覆盖率口径「台架必验」。
- scripts/stack_report.py 退出码 1 是 10-26 起挂在账上的 iec104Task
  栈债(margin 1,016 < 1,024,见 docs/代码规范/pre-commit使用说明.md:
  122);本轮只让 net_bind 的 UDP 分支多一层静态帧(1,544 → 1,600),
  netTask margin 1,888 与 timeSyncTask 1,152 不受影响。抬栈要过
  mem-budget 台账,不在本分支自行处理。
- 登记簿销项按仓库约定留给主工作树,本分支不动 docs/任务规划/**。
2026-11-03 18:44:01 +08:00
160 changed files with 14246 additions and 3043 deletions
+10 -3
View File
@@ -33,7 +33,7 @@ python -m venv ".dsh\skills\serial-assistant\venv"
& $PY $TOOL log --tail 40 # 读日志;--grep P [-i] [--regex](先过滤后截尾)
& $PY $TOOL clear # 清空日志(代际 +1,会话边界)
& $PY $TOOL wait-idle # 等 RX 静默:--settle-ms 8000 --max-ms 30000 --first-timeout-ms 12000
& $PY $TOOL status # 全状态 JSON(daemon 版本/端口/计数/rx_age_ms/log.path)
& $PY $TOOL status # 全状态 JSON(daemon 版本/端口 desired+ready/计数/rx_age_ms/log.path/auto_reopen)
& $PY $TOOL start|stop|restart # daemon 生命周期
& $PY $TOOL selftest # 无头自检(行拆分/解码/截头/wait 判据,无需硬件)
```
@@ -52,10 +52,17 @@ python -m venv ".dsh\skills\serial-assistant\venv"
- **一台机只有一个 daemon**(HTTP 口 `SO_EXCLUSIVEADDRUSE` 独占绑定)。多个项目的
serial_tool 副本协议兼容、可互连:`status` 的 `daemon.version` 辨归属
(本 skill 版 = `1.1.0-skill`),`log.path` 辨日志落点。
(本 skill 版 = `1.2.0-skill`),`log.path` 辨日志落点。
- **`open` 报"拒绝访问/端口被占用"**:先 `status` 查是不是残留旧 daemon 占着口 →
`stop`(或 `taskkill /F /PID`)→ 重新 `open`。
- **`wait-idle` 判据**:`no_data` = 本次等待期间零字节;首字节到达前不启动静默计时;
- **`wait-idle` 判据**:`no_data` = 本次等待期间零字节且**口开着**;首字节到达前不启动静默计时;
返回的 `rx_bytes` 是本次增量。`settle-ms` 必须大于固件的自然静默间隙(常见 3~4s)。
**v1.2:口未开 → 秒回 `ok:false`(exit 1,带 auto-reopen 现场),不再计满返回 `no_data`**。
- **自动重开(v1.2,issue #75)**:daemon 崩溃/被杀/读错丢口 → 新实例按 `meta.json` 现场**自动重开**
(串口线程内执行;退避 1.5s 倍增封顶 30s;`status` 的 `auto_reopen={enabled,pending,attempts,
last_error,next_retry_in_ms}` 可观测,`port.desired`=意图、`ready`=实际)。显式 `close`/`stop`
=清意图(重启后不再自动开);`restart` 免操心(stop 前抓现场、就绪后自动开回,结果在返回 JSON
的 `reopen` 键)。serial.log 缺口边界有 `[SYS] … GAP-START …` / `GAP-END …` 行:**窗口内字节不可
找回**;`GAP-START` 无 `GAP-END` = 采集器未恢复(查 `auto_reopen`/daemon.log),别当成板子静默。
- **`open` 不带 `--dtr/--rts` = 保持当前线路状态**;显式给才翻转。
- 日志 20MB 上限自动按行边界截头保尾 10MB;无换行残行 64KB 封顶。
@@ -23,6 +23,11 @@
start / stop / restart daemon 生命周期(其余命令会自动拉起 daemon)
selftest 无头自检(行拆分/解码回退/截头算法,无需硬件)
daemon 自动重开(v1.2,issue #75):崩溃/机器重启后自动拉起的新 daemon 读旧 meta
现场自动重开口;读写出错按退避自动重连(1.5s 起步、倍增、封顶 30s);serial.log
落 GAP-START / GAP-END 缺口标记;显式 close/stop 清除该意图(不再重开)。
wait-idle 在口未开且重开未成时快速失败(ok:false),不再伪装成 no_data。
退出码:0=成功 1=领域错误 2=daemon 不可达/超时 3=用法错误
"""
import argparse
@@ -46,7 +51,7 @@ from pathlib import Path
import serial
import serial.tools.list_ports
VERSION = "1.1.0-skill"
VERSION = "1.2.0-skill"
SCRIPT = Path(__file__).resolve()
TOOL_DIR = SCRIPT.parent
@@ -81,7 +86,9 @@ CAP_BYTES = 20 * 1024 * 1024 # 日志体积上限
KEEP_BYTES = 10 * 1024 * 1024 # 超限后保留的尾部
PARTIAL_FLUSH_S = 0.2 # 残行冲刷窗口(秒)
PARTIAL_MAX_BYTES = 64 * 1024 # 无换行残行的内存封顶(超限即当一行落地,见 LineSplitter.feed)
REOPEN_SETTLE_S = 1.5 # 重开端口退避(CH340 释放后立即重开的 EOF 陷阱)
REOPEN_SETTLE_S = 1.5 # 重开端口退避(CH340 释放后立即重开的 EOF 陷阱);R2 自动重连的起步间隔
REOPEN_BACKOFF_MAX_S = 30.0 # R2 自动重开退避封顶(1.5s 起步倍增:1.5/3/6/12/24/30…)
GAP_NOTE_INTERVAL_S = 60.0 # R3 退避期 serial.log 的 GAP-RETRY 节奏:首次必记 + 之后 ≤1 条/分
DEFAULT_TIMEOUT_S = 5.0 # CLI→daemon 硬超时(秒;wait-idle 未显式给时按 --max-ms 推导)
DAEMON_LOG_MAX = 1024 * 1024 # daemon.log 超此体积 → 拉起新实例前只保尾部
DAEMON_LOG_KEEP = 256 * 1024
@@ -134,6 +141,93 @@ def port_hint(configured, names):
return cands[0] if len(cands) == 1 else None
# ------------------------- 自动重开判定 / 退避 / 缺口标记行(R1–R3,纯函数,selftest 直测)
def plan_startup_reopen(meta, default_baud=115200):
"""R1 启动自动重开的意图判定:旧 meta.port 现场非空 = 上一任「开着口死的」。
显式 `close`/`stop` 已把该字段清成 null → None(不重开);崩溃、被 taskkill、
机器重启等现场仍在 → 返回按 meta 的重开参数。"""
if not isinstance(meta, dict):
return None
p = meta.get("port")
if not isinstance(p, dict) or not p.get("name"):
return None
try:
baud = int(p.get("baud") or default_baud)
except (TypeError, ValueError):
baud = default_baud
return {"port": str(p["name"]), "baud": baud,
"dtr": bool(p.get("dtr")), "rts": bool(p.get("rts"))}
def restart_reopen_plan(st):
"""R1 restart 专用:POST /stop **之前**从 /status 抓开口现场(stop 自身语义不变、
照清意图;CLI 在新 daemon 就绪后按这份现场显式 /open 回来)。旧 daemon 没口 /
已 close → None;重开缺口期(desired 未 ready)name 仍在 → 同样恢复。"""
if not isinstance(st, dict):
return None
p = st.get("port") or {}
if not p.get("name"):
return None
out = {"port": p["name"]}
try:
if p.get("baud"):
out["baud"] = int(p["baud"])
except (TypeError, ValueError):
pass
for k in ("dtr", "rts"):
if p.get(k) is not None:
out[k] = bool(p[k])
return out
def next_backoff(cur):
"""R2 自动重开退避序列:REOPEN_SETTLE_S 起步、倍增、REOPEN_BACKOFF_MAX_S 封顶。"""
return min(cur * 2.0, REOPEN_BACKOFF_MAX_S)
def sys_daemon_started(version, pid, prev_pid, pending):
"""R3① daemon 启动边界行:新 pid、上一任 pid、自动重开结论(pending 现场 / none)。"""
mode = ("pending %s@%s" % (pending["port"], pending["baud"]) if pending else "none")
return "daemon started v%s pid=%s prev_pid=%s auto-reopen %s" % (
version, pid, prev_pid if prev_pid is not None else "none", mode)
def sys_gap_start_lost(msg):
"""R3② 缺口起(运行中丢口):保留 serial error 原文,再落缺口声明。"""
return ("serial error: %s (GAP-START: port released; bytes in this window are "
"unrecoverable; auto-reopen retrying, backoff %.1fs doubling to %.0fs cap)"
% (msg, REOPEN_SETTLE_S, REOPEN_BACKOFF_MAX_S))
def sys_gap_start_recovery(prev_pid, port_name):
"""R3②' 缺口起(崩溃/重启恢复):真实缺口起点在「上一任死掉」与此刻之间、不可
确知 → 行内声明窗口是下限,别把恢复耗时当成全部丢失时长。"""
return ("GAP-START after daemon restart: previous daemon (pid=%s) died holding %s; "
"gap began no later than now (exact start unknown); bytes in this window "
"are unrecoverable; auto-reopen pending"
% (prev_pid if prev_pid is not None else "none", port_name))
def sys_gap_end(name, baud, gen, gap_s, attempts):
"""R3③ 缺口止(重开成功):含窗口秒数、尝试次数、gen 与不可找回声明。"""
return ("GAP-END reopened %s@%s (gen=%s) after %.1fs, %d attempt(s); bytes lost "
"inside the gap window are unrecoverable" % (name, baud, gen, gap_s, attempts))
def sys_gap_retry(attempt, err, next_s):
"""R3 退避期节奏行:首次必记、之后每分钟 ≤1 条;逐次失败原文只进 daemon.log。"""
return ("GAP-RETRY attempt %d failed: %s (next retry in %.1fs; while the gap is "
"open serial.log notes at most 1/min, full details in daemon.log)"
% (attempt, err, next_s))
def dlog(msg):
"""daemon 自身取证(自动重开逐次尝试原文等)→ stderr = state/daemon.log,
不刷 serial.log(R3 降噪口径)。"""
print("daemon %s: %s" % (VERSION, msg), file=sys.stderr, flush=True)
def load_meta():
try:
with open(META_FILE, "r", encoding="utf-8") as f:
@@ -277,6 +371,17 @@ class Daemon:
self.rts = False
self.port_since = None
self.ser = None
# ---- 自动重开/重连簿记(R1/R2/R4,issue #75)----
self.desired = False # 意图:该不该开着口(open 成功=置;显式 close/stop=清)
self._reopen_params = None # 待重开现场 {"port","baud","dtr","rts"}
self._reopen_backoff = REOPEN_SETTLE_S
self._reopen_next_at = 0.0 # 下次自动重开尝试时刻(0 = 未排期)
self._reopen_attempts = 0 # 本缺口期尝试次数(成功即清零)
self._reopen_last_error = None
self._prev_pid = None # 上一任 daemon pid(启动时读旧 meta 记入)
self._gap_start_ts = None # GAP-START 时刻(GAP-END 窗口秒数按它计)
self._gap_attempts = 0
self._last_gap_note_ts = 0.0 # GAP-RETRY 上一条 serial.log 时刻
self.last_released = 0.0
self.last_data_at = 0.0
self.born = time.time()
@@ -306,13 +411,27 @@ class Daemon:
def sys_line(self, text):
return line_at(time.time(), "SYS", text)
def _open_identity(self):
"""当前「应然开口现场」:实开的口 > 待重开参数 > None。meta 与 status 都由它
表达意图——重开缺口期现场必须留在 meta.port 里,否则下一次崩溃/重启后无人
知道「这台机器本来该开着 COMx」。"""
if self.port_name:
return {"name": self.port_name, "baud": self.port_baud,
"dtr": self.dtr, "rts": self.rts}
p = self._reopen_params
if self.desired and p and p.get("port"):
return {"name": p["port"], "baud": p.get("baud"),
"dtr": bool(p.get("dtr")), "rts": bool(p.get("rts"))}
return None
def _save_meta(self):
ident = self._open_identity()
save_meta({
"pid": os.getpid(),
"version": VERSION,
"gen": self.gen,
"port": ({"name": self.port_name, "baud": self.port_baud,
"dtr": self.dtr, "rts": self.rts} if self.port_name else None),
"port": ({"name": ident["name"], "baud": ident["baud"],
"dtr": ident["dtr"], "rts": ident["rts"]} if ident else None),
"since": self.port_since,
})
@@ -378,6 +497,10 @@ class Daemon:
if time.time() - self.last_data_at > PARTIAL_FLUSH_S:
for ts, raw in splitter.flush_partial():
self._rx_line(ts, raw)
elif self.desired and time.time() >= self._reopen_next_at:
# R1/R2:「该开着口」却没开着(启动恢复 / 丢口重连)→ 在本线程按退避
# 自动重开(串口对象唯一持有者不变量不破;旧实现此处纯空转永不重连)
self._auto_reopen_attempt()
try:
cmd = self.cmd_q.get_nowait()
except queue.Empty:
@@ -398,6 +521,7 @@ class Daemon:
self.emit(line_at(ts, "RX", text))
def _port_error(self, msg):
gap = False
with self.lock:
if self.ser is not None:
try:
@@ -409,7 +533,32 @@ class Daemon:
self.error = msg
self.errors += 1
self.last_released = time.time()
self.emit(self.sys_line("serial error: %s (port released, reopen with 'open')" % msg))
if self.desired and self.port_name:
# R2:丢口不再躺平等手工 'open'——快照现场,串口线程按退避自动重开
# (USB 转串口掉线/重枚举是台架常态,旧实现 CH340 几秒后回来也不重连)
self._reopen_params = {"port": self.port_name, "baud": self.port_baud,
"dtr": self.dtr, "rts": self.rts}
self._reopen_backoff = REOPEN_SETTLE_S
self._reopen_next_at = self.last_released + REOPEN_SETTLE_S
self._reopen_attempts = 0
self._reopen_last_error = None
self._gap_start_ts = self.last_released
self._gap_attempts = 0
self._last_gap_note_ts = 0.0
gap = True
self.emit(self.sys_line(sys_gap_start_lost(msg) if gap
else "serial error: %s (port released)" % msg))
def _reset_reopen_state_locked(self):
"""重开簿记清零(意图 desired 本身由调用方负责置/清)。"""
self._reopen_params = None
self._reopen_backoff = REOPEN_SETTLE_S
self._reopen_next_at = 0.0
self._reopen_attempts = 0
self._reopen_last_error = None
self._gap_start_ts = None
self._gap_attempts = 0
self._last_gap_note_ts = 0.0
def _close_port_locked(self):
if self.ser is not None:
@@ -424,6 +573,8 @@ class Daemon:
self.state = "stopped"
self.error = None
self.last_released = time.time()
self.desired = False # 显式 close/stop = 清除重开意图(R1:此后不再自动重开)
self._reset_reopen_state_locked()
self.splitter_gen += 1
# -- 命令执行(均在串口线程) --
@@ -493,33 +644,15 @@ class Daemon:
time.sleep(wait)
break
try:
# 先构造后 open:dtr/rts 状态写入 DCB 初始值(避免开端口瞬间的复位脉冲)
ser = serial.Serial()
ser.port = name
ser.baudrate = baud
ser.timeout = 0
ser.dtr = dtr
ser.rts = rts
ser.open()
except serial.SerialException as e:
msg = str(e)
names = sorted(self._list_port_names())
if name not in names:
# Windows 对不存在的 COM 口常报 Access Denied(而非 FileNotFound),
# 故以端口枚举结果为准,避免把"不存在"误报成"被占用"
err = "port not found: %s (available: %s)" % (name, ", ".join(names) or "(none)")
hint = port_hint(name, names)
if hint:
err += " [hint: only candidate is %s -> set portName in %s]" % (hint, CONFIG_FILE)
elif "Access is denied" in msg or "busy" in msg.lower():
err = "port busy: %s (held by another process)" % name
else:
err = "port open failed: %s (%s)" % (name, msg)
ser, err = self._open_port(name, baud, dtr, rts)
if ser is None:
cmd.finish({"ok": False, "error": err})
return
with self.lock:
gap = self._gap_start_ts is not None
gap_s = time.time() - self._gap_start_ts if gap else 0.0
gap_att = self._gap_attempts
self.ser = ser
self.port_name = name
self.port_baud = baud
@@ -530,20 +663,35 @@ class Daemon:
self.error = None
self.last_data_at = time.time()
self.splitter_gen += 1 # 新会话:丢掉上一段会话遗留的残行
# R1:显式 open 成功 = 写入重开意图(此后再崩溃,新 daemon 认这份现场)
self.desired = True
self._reset_reopen_state_locked()
self._reopen_params = {"port": name, "baud": baud, "dtr": dtr, "rts": rts}
self.emit(self.sys_line("port opened %s %d (gen=%d)" % (name, baud, self.gen)))
if gap:
# 手工 open 抢在退避重试前把口拉回来了 → 缺口窗口同样要收口(R3③)
self.emit(self.sys_line(sys_gap_end(name, baud, self.gen, gap_s, gap_att)))
self._save_meta()
cmd.finish({"ok": True, "port": name, "baud": baud, "dtr": dtr, "rts": rts,
"gen": self.gen, "since": self.port_since})
def _cmd_close(self, cmd):
with self.lock:
if self.ser is None:
cmd.finish({"ok": True, "note": "port not open"})
return
self._close_port_locked()
self.emit(self.sys_line("port closed"))
had_intent = self.desired
was_open = self.ser is not None
if was_open:
self._close_port_locked()
else:
# 「重开缺口中已释放」或「从未开过」:显式 close 同样清除重开意图
# (否则缺口期 close 后一旦崩溃/机器重启,新 daemon 还会自作主张重开)
self.desired = False
self._reset_reopen_state_locked()
if was_open:
self.emit(self.sys_line("port closed"))
elif had_intent:
self.emit(self.sys_line("port not open; auto-reopen intent cleared"))
self._save_meta()
cmd.finish({"ok": True})
cmd.finish({"ok": True} if was_open else {"ok": True, "note": "port not open"})
def _cmd_send(self, cmd):
p = cmd.params
@@ -560,7 +708,13 @@ class Daemon:
with self.lock:
ser = self.ser
if ser is None:
cmd.finish({"ok": False, "error": "port not open (run 'open' first)"})
if self.desired:
ar = self.auto_reopen_status()
cmd.finish({"ok": False,
"error": "port not open; auto-reopen pending (attempts=%s last_error=%s)"
% (ar["attempts"], ar["last_error"])})
else:
cmd.finish({"ok": False, "error": "port not open (run 'open' first)"})
return
try:
ser.write(data)
@@ -581,18 +735,124 @@ class Daemon:
except Exception:
return []
# -- 自动重开/重连(R1/R2;一律在串口线程执行,主/HTTP 线程不碰 Serial 对象) --
def _open_port(self, name, baud, dtr, rts):
"""开串口原语(仅串口线程调用)→ (ser|None, err|None)。
先构造后 open:dtr/rts 状态写入 DCB 初始值(避免开端口瞬间的复位脉冲)。"""
try:
ser = serial.Serial()
ser.port = name
ser.baudrate = baud
ser.timeout = 0
ser.dtr = dtr
ser.rts = rts
ser.open()
return ser, None
except serial.SerialException as e:
return None, self._classify_open_error(name, str(e))
@staticmethod
def _classify_open_error(name, msg):
"""pyserial 异常原文 → 可操作错误文本(不存在 → 被占 → 其它),手工 open 与自动重开共用。"""
names = sorted(Daemon._list_port_names())
if name not in names:
# Windows 对不存在的 COM 口常报 Access Denied(而非 FileNotFound),
# 故以端口枚举结果为准,避免把"不存在"误报成"被占用"
err = "port not found: %s (available: %s)" % (name, ", ".join(names) or "(none)")
hint = port_hint(name, names)
if hint:
err += " [hint: only candidate is %s -> set portName in %s]" % (hint, CONFIG_FILE)
elif "Access is denied" in msg or "busy" in msg.lower():
err = "port busy: %s (held by another process)" % name
else:
err = "port open failed: %s (%s)" % (name, msg)
return err
def _auto_reopen_attempt(self):
"""自动重开退避循环的一次尝试(串口线程):成功 = 写现场 + GAP-END 收口;
失败 = 倍增退避(R2)。逐次失败原文进 daemon.log;serial.log 只按
「首次 + 之后每分钟 ≤1 条」记 GAP-RETRY(R3 口径)。"""
if self.ser is not None:
return
p = self._reopen_params or {}
name = p.get("port")
if not name:
self.desired = False
self._reopen_next_at = 0.0
return
baud = int(p.get("baud") or self.cfg.get("baudRate") or 115200)
ser, err = self._open_port(name, baud, bool(p.get("dtr")), bool(p.get("rts")))
now = time.time()
if ser is not None:
with self.lock:
gap = self._gap_start_ts is not None
gap_s = now - self._gap_start_ts if gap else 0.0
gap_att = max(self._gap_attempts, self._reopen_attempts, 1)
self.ser = ser
self.port_name = name
self.port_baud = baud
self.dtr = bool(p.get("dtr"))
self.rts = bool(p.get("rts"))
self.port_since = now_str()
self.state = "active"
self.error = None
self.last_data_at = now
self.splitter_gen += 1 # 新会话:残行归零
self._reset_reopen_state_locked()
self._reopen_params = {"port": name, "baud": baud,
"dtr": self.dtr, "rts": self.rts}
self.emit(self.sys_line(sys_gap_end(name, baud, self.gen, gap_s, gap_att)))
self._save_meta()
dlog("auto-reopen OK %s@%d: %d attempt(s), window %.1fs"
% (name, baud, gap_att, gap_s))
return
with self.lock:
self._reopen_attempts += 1
self._gap_attempts += 1
self._reopen_last_error = err
self._reopen_backoff = next_backoff(self._reopen_backoff)
self._reopen_next_at = now + self._reopen_backoff
att = self._reopen_attempts
note_due = att == 1 or (now - self._last_gap_note_ts) >= GAP_NOTE_INTERVAL_S
if note_due:
self._last_gap_note_ts = now
next_s = self._reopen_backoff
dlog("auto-reopen attempt %d failed (next in %.1fs): %s" % (att, next_s, err))
if note_due:
self.emit(self.sys_line(sys_gap_retry(att, err, next_s)))
# -- 查询 --
def auto_reopen_status(self):
"""R4:自动重开/重连的对外现场(status() 持锁调用,本函数自身不加锁)。"""
ready = self.ser is not None and self.state != "error"
pending = bool(self.desired) and not ready
nxt = None
if pending and self._reopen_next_at > 0:
nxt = int(max(0.0, (self._reopen_next_at - time.time()) * 1000))
return {"enabled": bool(self.desired), "pending": pending,
"attempts": int(self._reopen_attempts),
"last_error": self._reopen_last_error,
"next_retry_in_ms": nxt}
def status(self):
with self.lock:
ident = self._open_identity()
port = {"ready": self.ser is not None and self.state != "error",
"name": self.port_name, "baud": self.port_baud,
"dtr": self.dtr, "rts": self.rts, "since": self.port_since}
# R4:desired = 意图(该不该开着),ready = 实际;缺口期 desired=true / ready=false
"desired": self.desired,
"name": ident["name"] if ident else None,
"baud": ident["baud"] if ident else None,
"dtr": ident["dtr"] if ident else self.dtr,
"rts": ident["rts"] if ident else self.rts,
"since": self.port_since}
counters = {"rx_bytes": self.rx_bytes, "tx_bytes": self.tx_bytes,
"rx_lines": self.rx_lines, "tx_lines": self.tx_lines,
"errors": self.errors}
state, err = self.state, self.error
gen = self.gen
ar = self.auto_reopen_status()
last = None
if self.last_rx_at:
last = {"ts": datetime.fromtimestamp(self.last_rx_ts).astimezone()
@@ -604,7 +864,7 @@ class Daemon:
"daemon": {"pid": os.getpid(), "version": VERSION,
"uptime_s": int(time.time() - self.born),
"exclusive_bind": getattr(self.server, "exclusive_bound", False)},
"port": port, "counters": counters, "last_rx": last,
"port": port, "auto_reopen": ar, "counters": counters, "last_rx": last,
# rx_age_ms:距最后一个 RX 字节的毫秒数(null = 这个 daemon 还没收过
# 任何字节)。判"板子是不是根本没在说话"不必再自己减时间戳(2026-10-10)
"rx_age_ms": last["age_ms"] if last else None,
@@ -624,7 +884,22 @@ class Daemon:
settle 计时,改由 first_timeout_s 兜底(默认 = max_s)。
3. 返回的 rx_bytes 改为**本次等待期间的增量**(旧实现回的是 daemon 累计
值,调用方打出来的 WAIT: rx_bytes=1246761 毫无意义)。
v1.2(issue #75 根因 4):口未开且自动重开未成时**快速失败** ok:false(error
带 auto-reopen 现场),不再计满返回 no_data——no_data 从此只表示「口开着且
本次零字节」。
"""
# R4 无口门:先判「有没有口可采」,再谈静默判据
if not (self.ser is not None and self.state != "error"):
pending = bool(getattr(self, "desired", False))
now = time.time()
na = getattr(self, "_reopen_next_at", 0.0)
nxt = int((na - now) * 1000) if (pending and na > now) else None
return {"ok": False,
"error": "port not open; auto-reopen %s (attempts=%s last_error=%s "
"next_retry_in_ms=%s) -- no_data requires an open port"
% ("pending" if pending else "disabled",
getattr(self, "_reopen_attempts", 0),
getattr(self, "_reopen_last_error", None), nxt)}
t0 = time.time()
initial = self.rx_bytes
last = initial
@@ -654,7 +929,25 @@ class Daemon:
def run(self):
STATE_DIR.mkdir(parents=True, exist_ok=True)
# R1(issue #75 根因 1):先读旧 meta **再**覆写。旧实现 run() 第一件事就是
# _save_meta(),把上一任「开着口死的」现场覆成 port:null——重启瞬间抹掉现场,
# 且启动流程无任何重开动作。
prev = load_meta()
self._prev_pid = prev.get("pid")
params = plan_startup_reopen(prev, self.cfg.get("baudRate") or 115200)
if params:
self.desired = True
self._reopen_params = params
self._reopen_backoff = REOPEN_SETTLE_S
# 开口动作落在串口线程(唯一持有者不变量):born+1.5s 后首次尝试,
# 沿用 CH340「释放后立即重开」的 REOPEN_SETTLE_S 退避口径
self._reopen_next_at = self.born + REOPEN_SETTLE_S
self._gap_start_ts = time.time() # 窗口下限 = 此刻;真实起点见 GAP-START 文字
self._save_meta()
self.emit(self.sys_line(sys_daemon_started(VERSION, os.getpid(),
self._prev_pid, params)))
if params:
self.emit(self.sys_line(sys_gap_start_recovery(self._prev_pid, params["port"])))
threading.Thread(target=self.writer_loop, name="writer", daemon=True).start()
threading.Thread(target=self.serial_loop, name="serial", daemon=True).start()
port = self.cfg["httpPort"]
@@ -1059,7 +1352,15 @@ def cmd_start(args, force=False):
st = http_request("GET", "/status", timeout=1)
return {"ok": True, "already_running": True,
"pid": st.get("daemon", {}).get("pid")}
reopen_after = None
if alive:
# R1(restart 特判,issue #75):stop 语义不变——关端口 + 清重开意图;但
# 「重启」不该把采集现场弄丢 → stop **之前**先从 /status 抓开口现场,
# 新 daemon 就绪后再显式 /open 回来(命令仍走串口线程执行)。
try:
reopen_after = restart_reopen_plan(http_request("GET", "/status", timeout=2))
except (urllib.error.URLError, OSError, ValueError):
pass
# restart:先优雅停(关端口),再强杀,确认真正退出
try:
http_request("POST", "/stop", timeout=5)
@@ -1086,10 +1387,19 @@ def cmd_start(args, force=False):
raise ToolError("daemon start timeout (no response within %.0fs)" % args.timeout, 2)
try:
st = http_request("GET", "/status", timeout=1)
return {"ok": True, "already_running": False,
"pid": st.get("daemon", {}).get("pid")}
result = {"ok": True, "already_running": False,
"pid": st.get("daemon", {}).get("pid")}
except (urllib.error.URLError, OSError):
return {"ok": True, "started": True}
result = {"ok": True, "started": True}
if reopen_after:
# 把 restart 前记住的开口现场开回来。失败不把 restart 翻成 ok:false
# (旧进程确实被干净换掉了),现场进 result["reopen"] 由调用方判读。
try:
ro = http_request("POST", "/open", reopen_after, timeout=10)
except (urllib.error.URLError, OSError) as e:
ro = {"ok": False, "error": str(e)}
result["reopen"] = {"requested": reopen_after, "result": ro}
return result
def cmd_stop(args):
@@ -1171,6 +1481,13 @@ def run_selftest():
class _Cnt:
def __init__(self, n):
self.rx_bytes = n
# v1.2 起 wait-idle 先过「无口门」(R4):假对象要伪装成「口正常开着」
self.ser = object()
self.state = "active"
self.desired = True
self._reopen_attempts = 0
self._reopen_last_error = None
self._reopen_next_at = 0.0
r = Daemon.wait_idle(_Cnt(500000), 0.2, 0.6, 0.3) # 常驻 daemon:累计值非 0
check("T5a 零字节判 no_data", r["reason"] == "no_data" and r["rx_bytes"] == 0
@@ -1304,6 +1621,141 @@ def run_selftest():
and port_hint("COM7", ["COM6", "COM9"]) is None
and port_hint("COM6", ["COM6"]) is None)
# T10 R1 启动重开意图判定:崩溃现场→重开;显式 close/stop、首启→不重开
m10 = {"pid": 11, "port": {"name": "COM3", "baud": 921600, "dtr": True, "rts": False},
"since": "2026-11-04T00:00:00.000+08:00"}
check("T10a 崩溃现场(meta.port 非空)→ 重开参数",
plan_startup_reopen(m10, 115200)
== {"port": "COM3", "baud": 921600, "dtr": True, "rts": False},
repr(plan_startup_reopen(m10, 115200)))
check("T10b 显式 close/stop 后(port:null)不重开",
plan_startup_reopen({"port": None}, 115200) is None)
check("T10c 首次启动(无 meta)不重开",
plan_startup_reopen({}, 115200) is None and plan_startup_reopen(None, 115200) is None)
check("T10d baud 缺失/坏值 → 按配置回退",
plan_startup_reopen({"port": {"name": "COM4"}}, 460800)
== {"port": "COM4", "baud": 460800, "dtr": False, "rts": False}
and plan_startup_reopen({"port": {"name": "COM4", "baud": "x"}}, 460800)["baud"] == 460800)
check("T10e port 记录残缺(无 name / 非 dict)不重开",
plan_startup_reopen({"port": {"baud": 1}}, 115200) is None
and plan_startup_reopen({"port": "COM3"}, 115200) is None)
# T11 R2 退避计划:1.5s 起步、倍增、封顶 30s
seq11 = []
b11 = REOPEN_SETTLE_S
for _ in range(7):
seq11.append(b11)
b11 = next_backoff(b11)
check("T11a 退避倍增封顶(1.5→3→6→12→24→30→30)",
seq11 == [1.5, 3.0, 6.0, 12.0, 24.0, 30.0, 30.0], repr(seq11))
check("T11b 封顶边界不越界",
next_backoff(24.0) == 30.0 and next_backoff(16.0) == 30.0 and next_backoff(30.0) == 30.0,
"16→%s" % next_backoff(16.0))
# T12 R3 缺口标记行格式(口径详述 = 设计说明 §6「自动重开与日志缺口标记」)
t12a = sys_daemon_started("1.2.0-skill", 100, 99, {"port": "COM3", "baud": 115200})
check("T12a 启动边界行:新旧 pid + 自动重开中",
"pid=100" in t12a and "prev_pid=99" in t12a and "auto-reopen pending COM3@115200" in t12a,
t12a)
t12b = sys_daemon_started("v", 1, None, None)
check("T12b 边界行:无需重开 + prev_pid 未知",
"prev_pid=none" in t12b and "auto-reopen none" in t12b, t12b)
t12c = sys_gap_start_lost("read: boom")
check("T12c 缺口起(丢口):错误原文 + GAP-START + 不可找回",
t12c.startswith("serial error: read: boom") and "GAP-START" in t12c
and "unrecoverable" in t12c, t12c)
t12d = sys_gap_start_recovery(99, "COM3")
check("T12d 缺口起(崩溃恢复):prev pid + 窗口下限声明",
"GAP-START" in t12d and "pid=99" in t12d and "COM3" in t12d
and "exact start unknown" in t12d and "unrecoverable" in t12d, t12d)
t12e = sys_gap_end("COM3", 115200, 7, 12.34, 5)
check("T12e 缺口止:窗口秒数 + 尝试次数 + gen + 不可找回",
"GAP-END" in t12e and "COM3@115200" in t12e and "gen=7" in t12e
and "12.3s" in t12e and "5 attempt(s)" in t12e and "unrecoverable" in t12e, t12e)
t12f = sys_gap_retry(2, "port busy: COM3 (held by another process)", 3.0)
check("T12f GAP-RETRY:尝试数 + 下次间隔 + 1/min 口径 + daemon.log 指针",
"attempt 2" in t12f and "3.0s" in t12f and "1/min" in t12f and "daemon.log" in t12f,
t12f)
t12g = line_at(1000.0, "SYS", t12e)
check("T12g 缺口行仍走 [SYS] 前缀(行格式不变量)",
t12g.startswith("[SYS] ") and "GAP-END" in t12g, t12g[:48])
# T13 R4 wait-idle 无口门:快速失败,不再计满返回 no_data
class _NoPort:
ser = None
state = "stopped"
t13_0 = time.time()
r13 = Daemon.wait_idle(_NoPort(), 0.2, 1.0, 0.3)
fast13 = (time.time() - t13_0) < 0.2
check("T13a 无口快速失败(ok:false、非 no_data、秒回)",
r13["ok"] is False and r13.get("reason") is None and fast13
and "port not open" in r13["error"] and "auto-reopen disabled" in r13["error"],
repr(r13))
class _Pending(_NoPort):
desired = True
_reopen_attempts = 3
_reopen_last_error = "port busy: COM3 (held by another process)"
_reopen_next_at = time.time() + 2.0
r13b = Daemon.wait_idle(_Pending(), 0.2, 1.0, 0.3)
check("T13b 失败文本带 auto-reopen 现场",
"pending" in r13b["error"] and "attempts=3" in r13b["error"]
and "port busy" in r13b["error"] and "next_retry_in_ms=" in r13b["error"],
repr(r13b))
# T14 R4 status.auto_reopen 现场(正常在开 / 缺口进行 / 显式关闭后)
class _AROk:
ser = object()
state = "active"
desired = True
_reopen_attempts = 0
_reopen_last_error = None
_reopen_next_at = 0.0
s14a = Daemon.auto_reopen_status(_AROk())
check("T14a 正常在开:enabled true / pending false",
s14a["enabled"] and not s14a["pending"] and s14a["attempts"] == 0
and s14a["next_retry_in_ms"] is None, repr(s14a))
class _ARGap(_AROk):
ser = None
state = "error"
_reopen_attempts = 2
_reopen_last_error = "port not found: COM3 (available: (none))"
_reopen_next_at = time.time() + 1.5
s14b = Daemon.auto_reopen_status(_ARGap())
check("T14b 缺口中:pending + 倒计时 + last_error",
s14b["enabled"] and s14b["pending"] and s14b["attempts"] == 2
and 0 < s14b["next_retry_in_ms"] <= 1600 and "port not found" in s14b["last_error"],
repr(s14b))
class _AROff:
ser = None
state = "stopped"
desired = False
_reopen_attempts = 0
_reopen_last_error = None
_reopen_next_at = 0.0
s14c = Daemon.auto_reopen_status(_AROff())
check("T14c 显式 close 后:enabled false(台架场景 2 断言点)",
s14c["enabled"] is False and s14c["pending"] is False, repr(s14c))
# T15 R1 restart 现场捕获(cmd_start force 路径的纯判定部分)
check("T15a restart 捕获:开着的口 → 记全现场",
restart_reopen_plan({"port": {"name": "COM3", "baud": 115200,
"dtr": False, "rts": True}})
== {"port": "COM3", "baud": 115200, "dtr": False, "rts": True})
check("T15b restart 捕获:重开缺口期(name 在/baud 缺)→ 仍恢复",
restart_reopen_plan({"port": {"name": "COM3", "baud": None, "desired": True}})
== {"port": "COM3"})
check("T15c restart 捕获:已 close / 旧 daemon 不可达 → 不重开",
restart_reopen_plan({"port": {"name": None}}) is None
and restart_reopen_plan({}) is None and restart_reopen_plan(None) is None)
width = 46
all_ok = True
for name, ok, detail in results:
-6
View File
@@ -1,6 +0,0 @@
---
name: template-skill
description: Replace with description of the skill and when Claude should use it.
---
# Insert instructions below
+11 -11
View File
@@ -360,19 +360,19 @@ repos:
files: ^(App/fs/vol_idle_sync\.(c|h)|App/task/default_task\.c|test/host/vol_idle_sync/.*|test/host/common/unit_test_harness\.py)$
pass_filenames: false
# 7g) 时间同步(time_sync)主机侧单元测试(2026-10-10 加)
# 背景:time_sync.c 是 TCP 时间同步协议状态机,也是探针窗口心跳缺口那项看门狗风险的**全部
# 逻辑贡献者**——"listen 重建 -> 800ms~1s 心跳间隔"由本模块的分片延时/退避/
# 结算等待组成,板端只能看到"是否复位"这一结果,看不到各段延时是否如设计。本钩子
# 用 MSVC 离线编译真实源文件(#include 纳入)+ 可脚本化的网络/事件桩(含 longjmp
# 跳出 for(;;) 任务循环),把分片喂狗节奏、监听退避、报文重组(半包/粘连/EOF/真错/
# 预算耗尽/tick 回绕)、重听门控逐条钉死。
# files 含被测源与其头、被测的真实看门狗头,以及 harness 本身。
# 7g) 时间同步(time_sync)主机侧单元测试(2026-10-10 加,2026-11-04 随 :8888 退役收缩)
# 背景::8888 退役后 time_sync.c 只剩 timeSyncTask 载体编排——就绪屏障 + 200ms 校时节拍
# + SNTP 客户端调用。探针窗口心跳缺口那项看门狗风险的逻辑贡献者(分片延时节奏、屏障
# 等待态喂心跳)仍在本模块,板端只能看到"是否复位"这一结果,看不到各段延时是否如设计;
# 本钩子用 MSVC 离线编译真实源文件(#include 纳入)+ 可脚本化的事件/代次/SNTP 桩
# (含 longjmp 跳出 for(;;) 任务循环),把分片喂狗节奏、屏障分片、代次下传逐条钉死。
# 历史(监听退避、TIME 报文重组、30s 预算、tick 回绕用例)随 :8888 退役删除,见 git 历史。
# files 含被测源与其头、节拍契约头 time_sntp.h、被测的真实看门狗头,以及 harness 本身。
- id: time-sync-unit-test
name: 时间同步单测(主机侧)
entry: python test/host/time_sync/run_unit_test.py
language: system
files: ^(App/sync/time_sync\.(c|h)|App/util/task_watchdog\.h|test/host/time_sync/.*|test/host/common/unit_test_harness\.py)$
files: ^(App/sync/time_sync\.(c|h)|App/sync/time_sntp\.h|App/util/task_watchdog\.h|test/host/time_sync/.*|test/host/common/unit_test_harness\.py)$
pass_filenames: false
# 7h) ADC 采样有效性校验主机侧单元测试(2026-10-11 加)
@@ -672,7 +672,7 @@ repos:
# 疑似 MSBuild 节点争用;此后 3 次干净树连跑均绿。判据:单次失败先复跑一次再看,
# 连续两次失败才当真(不许直接 --no-verify 绕过)。
- id: ui-golden
name: UI 像素回归(菜单 8 状态 + 内容页 66 张 黄金参考)
name: UI 像素回归(菜单 8 状态 + 内容页 74 张 黄金参考)
entry: python scripts/check_ui_golden.py
language: system
files: ^(Drivers/BSP/UI/|App/task/ui_task\.(c|h)$|simulator/(tests|sim|src)/|simulator/main\.c$|simulator/CMakeLists\.txt$|simulator/lv_conf\.h$|scripts/check_ui_golden\.py$|\.pre-commit-config\.yaml$)
@@ -682,7 +682,7 @@ repos:
# 背景:大窗实测——靠预置键序盲走到深页时,9 帧里只有 3 帧真是它声称的态;
# KEY 的 ACK 是空应答、不携带状态,所以**画面是唯一事实源**。判据脚本
# scripts/board_ui_nav.py 据此改成闭环:每按一键重新认屏再决定下一个键。
# 本钩子跑它的**快检档** --selftest-quick:只核"认屏认得对"(接线当时 18 张、现 66 张黄金参考 +
# 本钩子跑它的**快检档** --selftest-quick:只核"认屏认得对"(接线当时 18 张、现 74 张黄金参考 +
# 真机留档帧,目录不在版本控制里、缺就整段跳过),秒级、无构建产物依赖。
# ⚠ 不挂完整 --selftest:第三段闭环需要 lvgl_sim.exe(构建产物,新工作树里没有)
# —— 正是同一天 mem-numbers 缺 .map 那课的形状;脚本对缺 exe 报 FAIL 并给出
+1 -1
View File
@@ -16,7 +16,7 @@
- **采集**:TPAFE5160(计量级 8 路,并行总线)+ 片内 ADC1(9 路,DMA)→ TIM3 1 kHz 节拍 + 原子快照 → adcTask → 语义取数与有效性校验 → 全周傅氏相量 → **测频与遥测快照(`meas` 200 ms 双区,104 与 UI 共用同一份上游)**+ 保护判据(protectTask 20 ms 判据节拍)→ 104 遥信/遥测上送
- **存储**:GD5F SPI NAND → dhara FTL → FatFs → `/log` 运行/自检等七本记录 + SOE、`/config.ini` 配置、FTP 根目录
- **网络**:CH395F → netTask 串行 socket 栈 → FTP(21) / 对时(8888) / RemoDispBus 远程显示(7003) / IEC 60870-5-104 上行(工厂默认 2404)
- **网络**:CH395F → netTask 串行 socket 栈 → FTP(21) / RemoDispBus 远程显示(7003) / IEC 60870-5-104 上行(工厂默认 2404);对时 = timeSyncTask 的 SNTP 客户端(UDP 123 主动出网,:8888 推送监听已退役)
- **人机**:LVGL 160×160 单色菜单(uiTask),**与 PC 模拟器同一份 UI 源码**,远端可看图 + 注键
**目录地图**(只列顶层——稳定边界;细结构自己 `ls`,别背路径;**仓库根不放一次性产物**——提交信息草稿 / 临时改写脚本 / 门禁输出与日志一律进 `ci-artifacts/`(已忽略,口径见 `.gitignore` 尾注),根目录只留工程配置与两份生成器输入 `ui_menu_tree.txt`、`ui_cn_chars.txt`):
+1 -1
View File
@@ -27,7 +27,7 @@ extern osEventFlagsId_t sys_evtHandle;
#define SYS_EVT_NET_READY (1u << 0) /* netTask:net_init + PHY 稳定完成 */
#define SYS_EVT_STORAGE_READY (1u << 1) /* defaultTask:FTL/FatFS 挂载完成 */
#define SYS_EVT_FTP_READY (1u << 2) /* ftpTask:lftpd_start 完成后置位;暂无消费者(预留) */
#define SYS_EVT_TIMESYNC_READY (1u << 3) /* 预留:timeSyncTask 在跑但不置本位,生产者消费者都没有 */
#define SYS_EVT_TIMESYNC_READY (1u << 3) /* SNTP 首个校验通过的同步完成(App/sync/time_sntp.c 置位;就绪位,osFlagsNoClear 消费) */
#define SYS_EVT_NET_RECONFIG (1u << 4) /* UI:请求立即应用新网口配置(netTask 消费后重建网络) */
#define SYS_EVT_CFG_SAVE (1u << 5) /* UI:网口配置保存请求(defaultTask = 配置引擎属主,消费并写盘) */
#define SYS_EVT_CONSOLE_TX (1u << 6) /* 控制台单写者:生产者入环形缓冲后置位,defaultTask 消费并发送
+1 -1
View File
@@ -48,7 +48,7 @@ extern "C" {
* 同款先例):
* ca 1..65535:0 为非法公共地址(104 半字地址域,点表无 0 号站)
* port 1024..65535:拒绝把监听端口配到 1..1023 系统保留段,避免误配顶掉
* 系统服务;与 8888/7003 带内相撞属配置者责任(启动锚点
* 系统服务;与 7003 带内相撞属配置者责任(启动锚点
* "i104: listening on %u" 打印实际端口,可核) */
#define IEC104_COMM_CA_MIN 1u
#define IEC104_COMM_CA_MAX 65535u
+7 -6
View File
@@ -7,16 +7,18 @@
| 文件 | 作用 |
|---|---|
| `time_sync.c/h` | **时间同步任务**:等待网络就绪 → 与时间源交互 → 校准墙钟 |
| `time_sync.c/h` | **时间同步任务载体**:等待网络就绪 → 200 ms 节拍驱动 SNTP 客户端(:8888 私有推送已于 2026-11-04 退役,理由见墙钟维护说明 §15) |
| `time_sntp.c/h` | **SNTP 校时客户端**(2026-11-04 落地):由 time_sync 任务循环驱动的 UDP/123 轮询,编解码用 `Lib/coreSNTP/` 序列化器,配置读 `[sntp]` 段(属主 `../../Drivers/BSP/NETCFG/sntp_cfg.c`);单测 `test/host/sntp/` |
## 工作流
```
timeSyncTask 启动
↓ osEventFlagsWait(SYS_EVT_NET_READY, ...|osFlagsNoClear) // 状态位不可消费!
time_sync 主循环 → 与时间源通信 → sys_clock 校准
↓
置 SYS_EVT_TIMESYNC_READY
time_sync 主循环(200 ms 节拍,长等待分片 ≤50ms 逐片喂心跳)
↘ 每轮 time_sntp_run(gen):到点主动向 [sntp] 服务器发 48 B 查询
(单轮等待切片 ≤50 ms),校验通过按策略步进/只记日志,
首个通过的同步置 SYS_EVT_TIMESYNC_READY;网络代次变化自弃旧 socket
```
> ⚠️ 等待**就绪位**(`SYS_EVT_NET_READY`)必须 `osFlagsNoClear`——它是状态位,
@@ -24,6 +26,5 @@ time_sync 主循环 → 与时间源通信 → sys_clock 校准
## 相关文档
- `docs/子系统设计/系统墙钟时间维护说明.md` — 墙钟基准 / tick 回绕防护
- `test/host/time_sync_server.py` — PC 端对时推送服务(测试用)
- `docs/子系统设计/系统墙钟时间维护说明.md` — 墙钟基准 / tick 回绕防护 / §15 :8888 退役注记 / §16 SNTP
- `docs/架构设计/项目启动顺序管理.md` — 就绪屏障
+624
View File
@@ -0,0 +1,624 @@
/*
* 模块名称:Time SNTP — 板端 SNTP 客户端(状态机实现)
* 模块功能:见 time_sntp.h。节拍由 timeSyncTask 主循环驱动(每 ≤200ms 一次),
* 报文编解码用 vendor coreSNTP serializer(仅 serializer 层——
* client 层要求自带 transport 接口与定时器,本仓 UDP 已队列化在
* net_socket,节奏循环留在任务里,见 #57 调研 §4.1)
* 适用平台:STM32407ZGTx + CH395F;模拟器/主机单测经 stub 编译
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(SNTP 客户端接入,#57 调研结论:
* RFC 4330 §10 节奏、§5.3 防欺骗判据、§5.5 步进策略、
* 钟面 = 本地时间(UTC+8 固定,§1.1 裁定))
*/
#include <string.h>
#include "time_sntp.h"
#include "core_sntp_serializer.h"
#include "net_socket.h"
#include "sntp_cfg.h"
#include "sys_clock.h"
#include "sys_events.h"
#include "sys_log.h"
/* 深调用链模块(与 time_sync.c 同任务栈预算口径,见其文件头注释):
* 日志行缓冲收到 128 B(默认 192) */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[SNTP]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define SNTP_SERVER_PORT 123U
/* 应答超时:基础 5s,失败逐次翻倍封顶 60s(RFC 4330 §10:间隔递增;
* 翻倍节奏 = "超时即退避",下一次请求最早在 timeout_ms 之后发出) */
#define SNTP_RESP_TIMEOUT_BASE_MS 5000U
#define SNTP_RESP_TIMEOUT_MAX_MS 60000U
/* 首次请求随机散布 1~5 分钟(RFC 4330 §10 避免同批设备上电撞服务器) */
#define SNTP_FIRST_DELAY_MIN_MS 60000U
#define SNTP_FIRST_DELAY_SPAN_MS 240000U
/* 等应答期的单次收片粒度(看门狗 100ms 抽样窗约束,同 time_sync.c 第 8b 轮口径) */
#define SNTP_RECV_SLICE_MS 50U
/* 步进策略(调研 §5.5):|钟差| < 2s 只记录不步进(防 RTC 秒抖动来回拨针) */
#define SNTP_STEP_DEADBAND_MS 2000
/* 内部钟面 = 本地时间(#56 裁定 §1.1:sys_clock 存"钟面秒",104 写 UTC 的
* 8 小时差已有文档口径)——SNTP 给的是 UTC,落钟面前加固定东八区偏移;
* 全仓无时区存储,本常量与 UI 手工对时同口径(:8888 推送已退役) */
#define SNTP_TZ_LOCAL_OFFSET_SECS 28800U
/* 合理性下限:2020-01-01 UTC;上限 = sys_clock 钟面域 0x7FFFFFFF 扣除时区 */
#define SNTP_UNIX_MIN_VALID 1577836800U
#define SNTP_UNIX_MAX_UTC (0x7FFFFFFFUL - SNTP_TZ_LOCAL_OFFSET_SECS)
/* 收包缓冲:48B 报文 + 富余(>48 的扩展按 BSD 语义截断,serializer 只读前 48) */
#define SNTP_RX_BUF_SIZE 64U
/* 状态机 */
#define SNTP_ST_IDLE 0 /* 停用 / 从未启用 */
#define SNTP_ST_WAIT_DUE 1 /* 等下一次请求窗口 */
#define SNTP_ST_SEND 2 /* 本轮应发请求 */
#define SNTP_ST_WAIT_RESP 3 /* 等应答(每次 run 收一个 ≤50ms 片) */
/* 静态变量定义区 */
static struct {
uint8_t state; /* SNTP_ST_* */
int fd; /* UDP socket(-1 = 未开) */
uint32_t gen; /* 打开 socket 时的网络代次 */
uint32_t due_ms; /* 下一次请求的 tick 锚点(回绕安全比较) */
uint32_t timeout_ms; /* 当前应答超时(失败翻倍,成功复位) */
uint32_t deadline_ms; /* 本轮应答截止 tick */
uint8_t srv; /* 0 = 主 server1,1 = 备 server2 */
uint8_t no_srv_logged; /* "未配置服务器"只警一次(配置态,不是故障) */
uint8_t ready_posted; /* SYS_EVT_TIMESYNC_READY 已投(一次性) */
uint32_t sync_count; /* 校验通过次数(含死区内未步进) */
int32_t last_off_ms; /* 最近一次钟差(int64 截断,诊断用) */
SntpTimestamp_t t1; /* 本轮请求时间(serializer 要求跨收发保存) */
uint8_t pkt[SNTP_PACKET_BASE_SIZE]; /* 请求报文(48B,静态省栈) */
} s_ctx;
static uint32_t s_rng; /* xorshift32:散布/防欺骗随机数(非密钥用途) */
static uint8_t s_rng_seeded;
/* 私有函数区 */
/*
* 函数功能:PRNG 播种一次(芯片 96 位 UID 折叠 ^ tick ^ 当前钟面)
* 入口参数:无
* 返回值:无
* 限定条件:任务上下文
* 函数说明:F407 软件上无可用硬件 RNG 通路(HAL_RNG 在 hal_conf 关闭),
* 此处只需要"不可预测的 16 位低分数 + 上电散布",UID 折叠足够;
* 非 MCU 构建(宿主单测/模拟器)不碰 0x1FFF7A10,用固定盐——
* 防欺骗强度依赖的是应答 echo 回 T1(含随机低 16 位)的攻击者
* 不可知性,真机上由 UID+tick 提供
*/
static void s_rng_seed_once(void) {
uint32_t seed;
if (s_rng_seeded != 0u) {
return;
}
#ifdef STM32F407XX
{
const volatile uint32_t *uid = (const volatile uint32_t *)0x1FFF7A10UL;
seed = uid[0] ^ (uid[1] * 2654435761UL) ^ uid[2];
}
#else
seed = 0x9E3779B9UL;
#endif
seed ^= (uint32_t)xTaskGetTickCount();
seed ^= sys_clock_get();
if (seed == 0u) {
seed = 0xA5A5A5A5u;
}
s_rng = seed;
s_rng_seeded = 1u;
}
/*
* 函数功能:xorshift32 随机数
* 入口参数:无
* 返回值:伪随机 uint32(播种前兜底返回黄金比例常数,不会为 0) uint32_t
*/
static uint32_t s_rng_next(void) {
uint32_t x = s_rng;
if (x == 0u) {
x = 0x9E3779B9u;
}
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
s_rng = x;
return x;
}
/*
* 函数功能:回绕安全的"到点"判断
* 入口参数:now - 当前 tick;due - 目标 tick uint32_t
* 返回值:1 - 已到/已过;0 - 未到 uint8_t
*/
static uint8_t s_tick_due(uint32_t now, uint32_t due) {
return ((int32_t)(now - due) >= 0) ? 1u : 0u;
}
/*
* 函数功能:IPv4 四字节是否全零(未配置)
* 入口参数:p_ip - 4 字节 const uint8_t *
* 返回值:1 - 全零;0 - 已配置 uint8_t
*/
static uint8_t s_ip_is_zero(const uint8_t *p_ip) {
return ((p_ip[0] | p_ip[1] | p_ip[2] | p_ip[3]) == 0u) ? 1u : 0u;
}
/*
* 函数功能:取当前应使用的服务器 IP(按 s_ctx.srv 选主/备,缺位自动落另一侧)
* 入口参数:无
* 返回值:4 字节 IP 指针;主备都未配置返回 NULL const uint8_t *
*/
static const uint8_t *s_server_ip(const sntp_cfg_t *p_cfg) {
const uint8_t *pri = p_cfg->server_pri;
const uint8_t *bak = p_cfg->server_bak;
if (s_ctx.srv == 0u) {
if (s_ip_is_zero(pri) == 0u) {
return pri;
}
s_ctx.srv = 1u;
}
if (s_ip_is_zero(bak) == 0u) {
return bak;
}
if (s_ip_is_zero(pri) == 0u) {
s_ctx.srv = 0u;
return pri;
}
return NULL;
}
/*
* 函数功能:主备切换(仅两侧都配置时有效;失败退避时调用)
* 入口参数:p_cfg - 配置副本 const sntp_cfg_t *
* 返回值:无
*/
static void s_server_toggle(const sntp_cfg_t *p_cfg) {
if ((s_ip_is_zero(p_cfg->server_pri) == 0u) &&
(s_ip_is_zero(p_cfg->server_bak) == 0u)) {
s_ctx.srv = (s_ctx.srv == 0u) ? 1u : 0u;
}
}
/*
* 函数功能:当前 UTC 秒(钟面有效才返回)
* 入口参数:p_utc - 输出 UTC 秒 int64_t *
* 返回值:1 - 钟面有效;0 - 未初始化/早于合理性下限 uint8_t
*/
static uint8_t s_utc_now(int64_t *p_utc) {
uint32_t face = sys_clock_get();
if ((face == UINT32_MAX) ||
((uint64_t)face < (uint64_t)SNTP_UNIX_MIN_VALID + SNTP_TZ_LOCAL_OFFSET_SECS)) {
return 0u;
}
*p_utc = (int64_t)face - (int64_t)SNTP_TZ_LOCAL_OFFSET_SECS;
return 1u;
}
/*
* 函数功能:取当前 SNTP 时间戳(1900 纪元秒 + 毫秒分数)
* 入口参数:p_ts - 输出 SntpTimestamp_t *
* 返回值:无
* 函数说明:钟面无效(冷启动)时用"纪元差 + 开机秒 + 随机扰动"构造非零且
* 每次上电唯一的时间戳——serializer 只要求 T1 非零防欺骗,冷启动
* 路径的成功处理不使用 clockOffset(直接采用服务器 T3),乱值无害
*/
static void s_ts_now(SntpTimestamp_t *p_ts) {
int64_t utc;
uint32_t ms;
ms = (uint32_t)xTaskGetTickCount();
if (s_utc_now(&utc) != 0u) {
/* uint32 加和天然回绕 = SNTP era 进位语义(2036 年后仍成立) */
p_ts->seconds = (uint32_t)(utc + (int64_t)SNTP_TIME_AT_UNIX_EPOCH_SECS);
}
else {
p_ts->seconds = SNTP_TIME_AT_UNIX_EPOCH_SECS + (ms / 1000u) + (s_rng_next() & 0xFFFFu);
}
p_ts->fractions = (ms % 1000u) * (1000u * SNTP_FRACTION_VALUE_PER_MICROSECOND);
}
/*
* 函数功能:打开并绑定 UDP socket(客户端侧动态端口)
* 入口参数:gen - 当前网络代次 uint32_t
* 返回值:0 - 成功;-1 - 失败(fd 保持 -1) int
* 函数说明:UDP bind 恒开芯片服务器形态 Socket(PR #74 后语义),
* 端口 0 = 动态分配;发送用带目的地址的 net_sendto,不经
* net_udp_set_peer 轻切换(每包显式对端,源过滤未证不依赖)
*/
static int s_socket_open(uint32_t gen) {
struct net_sockaddr_in addr;
int s;
s = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
if (s < 0) {
DBG_ERROR("net_socket FAIL errno=%d", net_get_errno());
return -1;
}
(void)memset(&addr, 0, sizeof(addr));
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons(0u); /* 0 = 动态本地端口 */
addr.sin_addr.s_addr = NET_INADDR_ANY;
if (net_bind(s, (struct net_sockaddr *)&addr, (int)sizeof(addr)) != 0) {
DBG_ERROR("udp bind FAIL errno=%d", net_get_errno());
net_close(s);
return -1;
}
s_ctx.fd = s;
s_ctx.gen = gen;
return 0;
}
/*
* 函数功能:关闭 UDP socket(代次变化 / 配置停用 / 死 fd 时调用)
* 入口参数:无
* 返回值:无
*/
static void s_socket_close(void) {
if (s_ctx.fd >= 0) {
net_close(s_ctx.fd);
s_ctx.fd = -1;
}
}
/*
* 函数功能:一次失败收口——超时翻倍(封顶)并重排下一次请求
* 入口参数:now - 当前 tick uint32_t
* 返回值:无
* 函数说明:状态回 WAIT_DUE;socket 保留(轻问题不重开,代次变化才重开)
*/
static void s_fail_backoff(uint32_t now) {
uint32_t t = s_ctx.timeout_ms * 2u;
if (t > SNTP_RESP_TIMEOUT_MAX_MS) {
t = SNTP_RESP_TIMEOUT_MAX_MS;
}
s_ctx.timeout_ms = t;
s_ctx.due_ms = now + t;
s_ctx.state = SNTP_ST_WAIT_DUE;
}
/*
* 函数功能:int64 毫秒钟差截断到 int32(日志/诊断口)
* 入口参数:off - 毫秒钟差 int64_t
* 返回值:截断后的 int32 毫秒 int32_t
*/
static int32_t s_off_clamp(int64_t off) {
if (off > 2147483647LL) {
return 2147483647;
}
if (off < -2147483647LL) {
return -2147483647;
}
return (int32_t)off;
}
/*
* 函数功能:校验通过的应答落地——步进策略 + 节奏复位 + 就绪事件
* 入口参数:p_resp - 解析出的应答数据;now - 当前 tick uint32_t
* 返回值:无
* 函数说明:见文件头步进策略注释;**任何校验通过都算一次成功同步**
* (含死区内未步进),复位退避并按 poll_min 重排下一轮;
* 首个成功投 SYS_EVT_TIMESYNC_READY(状态位,不消费);
* 每次实际步进落一条 quiet 操作记录("clock set by SNTP",
* 与 104 同口径,失败不落痕)
*/
static void s_handle_success(const SntpResponseData_t *p_resp, uint32_t now) {
int64_t utc;
int64_t new_utc;
int32_t off_ms = s_off_clamp(p_resp->clockOffsetMs);
uint8_t clock_valid;
s_ctx.sync_count++;
s_ctx.last_off_ms = off_ms;
s_ctx.timeout_ms = SNTP_RESP_TIMEOUT_BASE_MS;
s_ctx.due_ms = now + (uint32_t)sntp_cfg_poll_min() * 60000u;
s_ctx.state = SNTP_ST_WAIT_DUE;
if ((s_ctx.ready_posted == 0u) && (sys_evtHandle != NULL)) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_TIMESYNC_READY);
s_ctx.ready_posted = 1u;
}
clock_valid = s_utc_now(&utc);
if (clock_valid != 0u) {
int64_t off = p_resp->clockOffsetMs;
if ((off <= (int64_t)SNTP_STEP_DEADBAND_MS) &&
(off >= -(int64_t)SNTP_STEP_DEADBAND_MS)) {
/* 死区:本地钟足够准,不拨针也不写 RTC(防 SD2506 无谓磨损) */
DBG_INFO("off=%ldms within deadband, no step", (long)off_ms);
return;
}
/* 四元组中点估计:把"服务器秒"折算到本地应有的 UTC 秒 */
{
int64_t off_s = (off >= 0) ? ((off + 500) / 1000) : ((off - 500) / 1000);
new_utc = utc + off_s;
}
}
else {
/* 冷启动(钟面未初始化/早于 2020):钟差不可信,直接采用服务器 T3 */
uint32_t t3_secs = 0u;
uint32_t t3_us = 0u;
if (Sntp_ConvertToUnixTime(&p_resp->serverTime, &t3_secs, &t3_us) != SntpSuccess) {
DBG_ERROR("unix convert FAIL");
return;
}
new_utc = (int64_t)t3_secs;
}
if ((new_utc < (int64_t)SNTP_UNIX_MIN_VALID) || (new_utc > (int64_t)SNTP_UNIX_MAX_UTC)) {
DBG_ERROR("target unix out of domain, skip step");
return;
}
(void)sys_clock_set_unix((uint32_t)(new_utc + (int64_t)SNTP_TZ_LOCAL_OFFSET_SECS));
syslog_post_quiet(SYSLOG_OPER, "clock set by SNTP");
DBG_INFO("step offset=%ldms face=%lu", (long)off_ms,
(unsigned long)(uint32_t)(new_utc + (int64_t)SNTP_TZ_LOCAL_OFFSET_SECS));
}
/*
* 函数功能:收包校验 + 反欺骗判据 + 解析落地(WAIT_RESP 态的单包处理)
* 入口参数:p_buf - 载荷;len - 长度;p_src - 源地址;now - tick uint32_t
* 返回值:1 - 本轮已收口(成功或按失败处理,调用方可停止收片);
* 0 - 该包丢弃,继续等到 deadline uint8_t
* 函数说明:判据顺序 = 长度 → 源地址(IP+端口必须 = 本轮请求目的)→
* stratum≤15 → LI≠3(服务器未同步告警态)→ serializer 校验
* (mode=4、origin 与 T1 含随机低 16 位精确匹配、时间戳非零、
* KoD 分类)。调研 §5.3:LI=3 / stratum>15 serializer 不拦,
* 必须在这里补;KoD 与畸形应答按失败退避(KoD 语义里
* ChangeServer 换服务器,RATE 保留当前服务器)
*/
static uint8_t s_handle_packet(const uint8_t *p_buf, int len,
const struct net_sockaddr_in *p_src,
const uint8_t *p_srv_ip, uint32_t now) {
const sntp_cfg_t *cfg = sntp_cfg_get();
SntpResponseData_t resp;
SntpTimestamp_t t4;
SntpStatus_t st;
if (len < (int)SNTP_PACKET_BASE_SIZE) {
DBG_INFO("WARN: short resp len=%d, drop", len);
return 0u;
}
if ((memcmp(&p_src->sin_addr.s_addr, p_srv_ip, 4u) != 0) ||
(p_src->sin_port != net_htons(SNTP_SERVER_PORT))) {
/* 防欺骗第一道:应答源必须是我们刚发过请求的那个 IP:123 */
DBG_INFO("WARN: resp from unexpected src, drop");
return 0u;
}
if ((p_buf[1] & 0x7FU) > 15u) {
DBG_INFO("WARN: stratum=%u invalid, drop", (unsigned)(p_buf[1] & 0x7FU));
s_fail_backoff(now);
return 1u;
}
if ((p_buf[0] >> 6) == 3u) {
/* LI=3:服务器自己没同步,时间不可用;再等多久也一样 → 直接失败 */
DBG_INFO("WARN: server not synced (LI=3)");
s_server_toggle(cfg);
s_fail_backoff(now);
return 1u;
}
s_ts_now(&t4);
st = Sntp_DeserializeResponse(&s_ctx.t1, &t4, p_buf, (size_t)len, &resp);
switch (st) {
case SntpSuccess:
s_handle_success(&resp, now);
return 1u;
case SntpRejectedResponseChangeServer:
case SntpRejectedResponseOtherCode:
DBG_INFO("WARN: kiss-of-death code=%lu, switch server",
(unsigned long)resp.rejectedResponseCode);
s_server_toggle(cfg);
s_fail_backoff(now);
return 1u;
case SntpRejectedResponseRetryWithBackoff:
DBG_INFO("WARN: kiss-of-death RATE, backoff");
s_fail_backoff(now);
return 1u;
case SntpInvalidResponse:
default:
/* originate 不匹配 / 时间戳为零 / mode 错 = 伪造或损坏:丢弃继续等 */
DBG_INFO("WARN: invalid resp st=%d, drop", (int)st);
return 0u;
}
}
/*
* 函数功能:发起一轮请求(SEND 态执行体:选服务器 → 开 socket → 序列化 → 发送)
* 入口参数:net_gen - 网络代次;now - 当前 tick uint32_t
* 返回值:无
* 函数说明:成功 → 状态进 WAIT_RESP;任一失败 → 主备切换(适用时)+
* s_fail_backoff 退避重排;服务器未配置 → 停在 WAIT_DUE 只警一次,
* 等下一次 run(配置补齐即恢复,不产生退避——没发过请求不算失败)
*/
static void s_send_round(uint32_t net_gen, uint32_t now) {
const sntp_cfg_t *cfg = sntp_cfg_get();
const uint8_t *srv_ip;
struct net_sockaddr_in dst;
srv_ip = s_server_ip(cfg);
if (srv_ip == NULL) {
if (s_ctx.no_srv_logged == 0u) {
DBG_INFO("WARN: enabled but no server configured");
s_ctx.no_srv_logged = 1u;
}
return;
}
s_ctx.no_srv_logged = 0u;
if (s_ctx.fd < 0) {
if (s_socket_open(net_gen) != 0) {
s_fail_backoff(now);
return;
}
}
s_ts_now(&s_ctx.t1);
if (Sntp_SerializeRequest(&s_ctx.t1, s_rng_next(), s_ctx.pkt,
sizeof(s_ctx.pkt)) != SntpSuccess) {
/* T1 为 0 才会失败(ts 构造保证非零)——真到这儿是构造性 bug */
DBG_ERROR("serialize FAIL");
s_fail_backoff(now);
return;
}
(void)memset(&dst, 0, sizeof(dst));
dst.sin_family = NET_AF_INET;
dst.sin_port = net_htons(SNTP_SERVER_PORT);
(void)memcpy(&dst.sin_addr.s_addr, srv_ip, 4u);
if (net_sendto(s_ctx.fd, s_ctx.pkt, (int)sizeof(s_ctx.pkt), 0,
(struct net_sockaddr *)&dst, (int)sizeof(dst))
!= (int)sizeof(s_ctx.pkt)) {
DBG_ERROR("sendto FAIL errno=%d", net_get_errno());
s_server_toggle(cfg);
s_fail_backoff(now);
return;
}
s_ctx.deadline_ms = now + s_ctx.timeout_ms;
s_ctx.state = SNTP_ST_WAIT_RESP;
/* 发送轮不占收片时间,立即让出回主循环 */
}
/* 函数定义区 */
/**
* 函数功能:SNTP 客户端单步驱动——由 timeSyncTask 主循环每轮调用一次
* 入口参数:net_gen - 当前网络代次(time_sync 循环持有) uint32_t
* 返回值:无
* 限定条件:仅 timeSyncTask 上下文;单次调用最多停留一片 SNTP_RECV_SLICE_MS,
* 看门狗喂点由调用方节奏保证;配置热读 [sntp] RAM 副本,无需重启
*/
void time_sntp_run(uint32_t net_gen) {
const sntp_cfg_t *cfg;
uint32_t now = (uint32_t)xTaskGetTickCount();
s_rng_seed_once();
cfg = sntp_cfg_get();
/* 网络代次变化:旧 socket 随芯片复位失效,弃掉重排首轮散布 */
if (net_gen != s_ctx.gen) {
s_socket_close();
s_ctx.gen = net_gen;
s_ctx.state = SNTP_ST_IDLE;
s_ctx.no_srv_logged = 0u;
}
if (cfg->enable == 0u) {
if (s_ctx.state != SNTP_ST_IDLE) {
s_socket_close();
s_ctx.state = SNTP_ST_IDLE;
}
return;
}
if (s_ctx.state == SNTP_ST_IDLE) {
s_ctx.state = SNTP_ST_WAIT_DUE;
s_ctx.timeout_ms = SNTP_RESP_TIMEOUT_BASE_MS;
s_ctx.due_ms = now + SNTP_FIRST_DELAY_MIN_MS +
(s_rng_next() % SNTP_FIRST_DELAY_SPAN_MS);
}
switch (s_ctx.state) {
case SNTP_ST_WAIT_DUE:
if (s_tick_due(now, s_ctx.due_ms) != 0u) {
s_send_round(net_gen, now);
}
return;
case SNTP_ST_SEND:
s_send_round(net_gen, now);
return;
case SNTP_ST_WAIT_RESP: {
uint8_t rxbuf[SNTP_RX_BUF_SIZE];
struct net_sockaddr_in src;
int src_len = (int)sizeof(src);
int n;
(void)memset(&src, 0, sizeof(src));
n = net_recvfrom_to(s_ctx.fd, rxbuf, (int)sizeof(rxbuf),
SNTP_RECV_SLICE_MS,
(struct net_sockaddr *)&src, &src_len);
if (n > 0) {
const uint8_t *srv_ip = s_server_ip(cfg);
if (srv_ip == NULL) {
s_fail_backoff(now);
return;
}
(void)s_handle_packet(rxbuf, n, &src, srv_ip, now);
return;
}
if (n == -2) {
/* 本片等满无数据:看总截止 */
if (s_tick_due(now, s_ctx.deadline_ms) != 0u) {
DBG_INFO("WARN: resp timeout, tmo=%lu ms", (unsigned long)s_ctx.timeout_ms);
s_server_toggle(cfg);
s_fail_backoff(now);
}
return;
}
if (n == -1) {
int e = net_get_errno();
DBG_ERROR("recvfrom FAIL errno=%d", e);
if ((e == NET_ERR_BADF) || (e == NET_ERR_CONNRESET) ||
(e == NET_ERR_INVAL)) {
/* fd 已死(重建取消/非法):关掉等代次/下轮重开 */
s_socket_close();
s_fail_backoff(now);
}
/* 其余瞬时错误:继续等到 deadline,超时自然收口 */
return;
}
/* n == 0:UDP 语义下不应出现(EOF 是 TCP 概念),防御收口 */
s_fail_backoff(now);
return;
}
default:
s_ctx.state = SNTP_ST_IDLE;
return;
}
}
/**
* 函数功能:累计校验通过的同步次数(诊断口)
* 入口参数:无
* 返回值:同步成功次数 uint32_t
* 限定条件:任意上下文只读
*/
uint32_t time_sntp_sync_count(void) {
return s_ctx.sync_count;
}
/**
* 函数功能:最近一次校验通过应答的毫秒钟差(诊断口,int64 截断值)
* 入口参数:无
* 返回值:(T2-T1+T3-T4)/2 毫秒,截断到 int32;从未成功为 0 int32_t
* 限定条件:任意上下文只读;仅日志/排障用,不作判据
*/
int32_t time_sntp_last_offset_ms(void) {
return s_ctx.last_off_ms;
}
+53
View File
@@ -0,0 +1,53 @@
/*
* 模块名称:Time SNTP — 板端 SNTP 客户端(UDP 48 字节报文)
* 模块功能:按 [sntp] 段配置向主/备 SNTP 服务器发送 RFC 4330 请求、校验应答
* (originate 精确匹配 + 源地址 + mode/stratum/LI/KoD)、计算钟差并
* 按阈值步进本地墙钟(sys_clock_set_unix,钟面 = 本地时间 UTC+8),
* 首次成功后置 SYS_EVT_TIMESYNC_READY;报文编解码用 vendor 的
* coreSNTP serializer(Lib/coreSNTP,MIT)
* 适用平台:STM32F407ZGTx + CH395F(timeSyncTask 内运行,模拟器不编)
* 作者:王建锋
* 创建日期:2026-11-04
*/
#ifndef TIME_SNTP_H
#define TIME_SNTP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/*
* 函数功能:SNTP 客户端节拍入口(幂等、非阻塞——除等待应答期一次 ≤50ms 收片)
* 入口参数:net_gen - 调用方本轮读取的网络代次(net_get_generation()) uint32_t
* 返回值:无
* 限定条件:**仅 timeSyncTask 上下文**调用(看门狗心跳由调用方在片间喂,
* 本模块单次调用最长驻留 = 一个 50ms 收片,满足 100ms 抽样窗约束)
* 函数说明:内部状态机 停用 → 到期 → 发送 → 等应答 → 步进/退避;配置
* (sntp_cfg_get())每轮现读,代次变化自动弃旧 Socket 重开
*/
void time_sntp_run(uint32_t net_gen);
/*
* 函数功能:取累计成功同步次数(诊断/测试锚点)
* 入口参数:无
* 返回值:成功次数(含死区内的"未步进但校验通过") uint32_t
* 限定条件:无
*/
uint32_t time_sntp_sync_count(void);
/*
* 函数功能:取最近一次校验通过应答的钟差(诊断锚点)
* 入口参数:无
* 返回值:钟差毫秒,截断到 int32;正 = 本地钟落后服务器;从未成功 = 0 int32_t
* 限定条件:无
*/
int32_t time_sntp_last_offset_ms(void);
#ifdef __cplusplus
}
#endif
#endif /* TIME_SNTP_H */
+42 -220
View File
@@ -1,7 +1,7 @@
/*
* 模块名称:Time Sync — PC 本地时间推送接收(TCP)
* 模块功能:在独立任务 timeSyncTask 中以 TCP Server 方式监听端口,接收 PC 推送的本地时间包,
* 调用 sys_clock_set_unix() 更新系统墙钟并写回 SD2506 RTC
* 模块名称:Time Sync — timeSyncTask 载体(网络校时节拍宿主)
* 模块功能:独立任务 timeSyncTask:等网络就绪后按固定节拍驱动 SNTP 校时客户端
* (App/sync/time_sntp.c),长等待一律分片并逐片喂任务心跳
* 适用平台:STM32F407ZGTx + CH395F
* 作者:王建锋
* 创建日期:2026-08-27
@@ -10,53 +10,45 @@
* v1.1 2026-08-28 王建锋 改为 TCP Server,迁入独立任务 timeSyncTask,
* 所有 socket 操作经 net_socket 线程安全 API 走消息队列,
* netTask 仅负责 net_poll/net_process_messages,多任务并发安全
* v1.3 2026-10-27 DTU 项目组 道G task-50:对时**成功应用**后投一条 quiet
* 操作记录("clock set by 8888")——task-49 盘点盘出
* "104 对时留痕、本端口不留"的同类不对称,Lead 批准补齐;
* 被拒不落痕;epoch 时标语义未裁定,本改动不碰、不补
* 任何时区补偿。
* v1.2 2026-10-02 王建锋 第 8b 轮消抖:等待片长 200ms→50ms,并把各处长
* 等待(500/2000/200ms)统一走 time_sync_delay_fed()
* 按 ≤50ms 分片、每片喂心跳——消除"心跳滑出两个连续
* 100ms 抽样窗"的节拍抖动(第 7 轮台架 snap=0x17)
* v1.3 2026-10-27 DTU 项目组 道G task-50:对时**成功应用**后投一条 quiet
* 操作记录("clock set by 8888")——task-49 盘点盘出
* "104 对时留痕、本端口不留"的同类不对称,Lead 批准补齐
* v1.4 2026-11-04 DTU 项目组 SNTP 客户端接入(#57 调研落地):accept 轮询
* 片内驱动 time_sntp_run()(48B UDP 请求/应答状态机
* 在 App/sync/time_sntp.c,节奏/退避/步进策略见该文件)
* v2.0 2026-11-04 DTU 项目组 私有 TCP :8888 对时退役(SNTP 上线后退出历史
* 舞台):监听重建、"TIME" 包重组、OPER 留痕、头阻塞
* 收口整条链随退——历史协议与退役理由见
* docs/子系统设计/系统墙钟时间维护说明.md §15 退役注记;
* PC 推送工具 time_sync_server.py 与探针 e02_probe.py
* 同日删除。本任务收缩为纯节拍宿主:NET_READY 屏障 →
* 200ms 循环驱动 time_sntp_run()。心跳分片纪律
* (v1.2,第 8b 轮消抖)与屏障喂心跳(2026-10-02
* 台架修复)原样保留。
*/
#include <string.h>
#include "time_sync.h"
#include "time_sntp.h"
#include "net_socket.h"
#include "sys_clock.h"
#include "sys_events.h"
#include "sys_log.h"
#include "task_watchdog.h"
/* 深调用链模块(timeSyncTask 栈仅 2048 B,本任务链 = 建监听 → net_bind → CH395F):
* 日志行缓冲收到 128 B(默认 192),本模块最长行渲染后 < 128 → 不截断 */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[TIME_SYNC]"
#include "dbg_log.h"
/* 监听端口:PC 推送端(TCP 客户端)需连到本端口 */
#define TIME_SYNC_PORT 8888
/*
* 头阻塞修复 + 挂起请求(pending)化(timeSync 侧;lftpd 侧见 lftpd_inet.c):
* 旧实现用单次 30s 阻塞 net_recv(默认 NET_RECV_TIMEOUT_MS)等时间包——等待期间
* netTask(全部 socket 操作的唯一执行者)被独占,其余任务的网络操作(远端显示 /
* FTP)整体停摆(历史审计遗留问题)。
* 2026-09-22 先落地分片有界读(5ms 片),同日 pending 化后改为"挂起请求":
* 每次等待 = 一次挂起登记(netTask 零占用,完成按主循环节拍检查),调用方
* 单次超时 = 剩余总预算(总等待 ≤ TIME_SYNC_TOTAL_BUDGET_MS,语义与旧 30s
* 一致);等待期间调用方在自己上下文睡任务通知,旧分片协议(片长/片间让出)退役。
*/
#define TIME_SYNC_TOTAL_BUDGET_MS 30000U
/* 看门狗分片等待:单次 net_recv_to 的等待片长上限。须显著小于看门狗抽样窗
/* 看门狗分片等待:单次延时的分片上限。须显著小于看门狗抽样窗
* (task_watchdog.c WD_POLL_PERIOD_MS=100ms)——片长 ≤50ms 时每片前喂的心跳
* 必然落在同一个 100ms 抽样窗内,消除"心跳恰好滑过两个连续窗口"的边界抖动
* (第 7 轮台架稳态 snap=0x17 即抽样窗 vs 心跳节拍的良性错位,第 8b 轮消抖)。
* 语义上仅把一次长等待拆成多次短等待,总预算不变。 */
* 语义上仅把一次长等待拆成多次短等待,总时长不变。 */
#define TIME_SYNC_WAIT_SLICE_MS 50U
/* 主循环节拍:每轮驱动一次 SNTP 状态机后的等待时长,与 :8888 时代的
* accept 轮询片同值(time_sntp_run 对单次驻留另有 ≤50ms 收片上限,
* 与本延时片叠加不破 100ms 抽样窗,心跳纪律不变)。 */
#define TIME_SYNC_TICK_MS 200U
/*
* 函数功能:可喂心跳的延时(把长等待切成 ≤50ms 短片,每片喂一次心跳)
* 入口参数:ms - 总延时(ms) uint32_t
@@ -77,147 +69,16 @@ static void time_sync_delay_fed(uint32_t ms) {
}
}
/*
* 函数功能:创建并监听 TCP 时间同步服务端口
* 入口参数:无
* 返回值:监听 Socket 描述符(>=0),失败返回 -1
* 限定条件:net_init() 已成功;可在非 netTask 上下文调用
* 函数说明:net_socket/net_bind/net_listen 均为线程安全 API,内部经消息队列由 netTask 串行处理。
* 单连接模式下监听 Socket 在收到连接后会转为数据通道,连接关闭后即被释放,
* 因此每次调用都会新建监听 Socket(与 lftpd 同模式),失败自动重试。
*/
static int time_sync_open_listener(void) {
int retry;
for (retry = 0; retry < 5; retry++) {
task_wd_beat(TASK_WD_TIMESYNC); /* 每次重试前喂心跳(见下方 500ms/2000ms 延时) */
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (s < 0) {
DBG_ERROR("time_sync: net_socket FAIL");
time_sync_delay_fed(500U);
continue;
}
struct net_sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons(TIME_SYNC_PORT);
addr.sin_addr.s_addr = NET_INADDR_ANY;
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
DBG_ERROR("time_sync: bind port %d FAIL", TIME_SYNC_PORT);
net_close(s);
time_sync_delay_fed(2000U);
continue;
}
if (net_listen(s) != 0) {
DBG_ERROR("time_sync: listen FAIL");
net_close(s);
time_sync_delay_fed(2000U);
continue;
}
DBG_INFO("time_sync: TCP listen on :%d (sock=%d)", TIME_SYNC_PORT, s);
return s;
}
return -1;
}
/*
* 函数功能:处理一个已建立的时间同步连接
* 入口参数:conn_sock - 已建立的连接 Socket 描述符(单连接模式下即监听 Socket 自身)
* 返回值:无
* 函数说明:pending 读取 PC 发来的时间包("TIME"+uint32 大端 本地时间),解析后
* 写回 RTC。**不在此 close**(单连接模式下 conn_sock 即
* 监听 fd,close 连监听一起毁、重建窗 ~0.3-1s;net 层 auto_relisten
* 已把同 fd 收回监听,外层直接续 accept——lftpd 样板)。每次等待 =
* 一次挂起请求(netTask 零占用),单次超时 = 剩余总预算
* (≤30s,语义与旧 30s 阻塞一致);跨调用累积,容忍时间包被 TCP 分段送达
*/
static void time_sync_handle_conn(int conn_sock) {
uint8_t buf[16];
int got = 0;
TickType_t t0 = xTaskGetTickCount();
while (got < (int)sizeof(buf)) {
/* 单次预算 = 剩余总预算(pending:等待期 netTask 零占用,调用方
* 在自己上下文睡任务通知,旧片间 vTaskDelay(1) 让出退役)
* ⚠ 看门狗约束:本任务在 required_mask 内,而单次 net_recv_to 可睡满
* 30s 预算 —— 等待期必须周期性喂心跳,否则"对端连上但不发时间包"这种
* 完全正常的场景会被判为任务卡死(见设计说明 §4.4 长耗时白名单)。
* 做法:把长等待切成 WD 窗口内的短片(片长 ≤200ms),每片先喂心跳。 */
TickType_t elapsed = xTaskGetTickCount() - t0;
uint32_t rem = (elapsed >= (TickType_t)pdMS_TO_TICKS(TIME_SYNC_TOTAL_BUDGET_MS)) ? 0U
: (uint32_t)((TickType_t)TIME_SYNC_TOTAL_BUDGET_MS - elapsed);
if (rem == 0U) {
DBG_INFO("time_sync: no TIME packet in 30s, drop conn");
break; /* 总预算耗尽(语义同旧 30s 超时) */
}
task_wd_beat(TASK_WD_TIMESYNC);
if (rem > TIME_SYNC_WAIT_SLICE_MS) {
rem = TIME_SYNC_WAIT_SLICE_MS; /* 分片:等待期保持心跳节奏 */
}
int n = net_recv_to(conn_sock, buf + got, (int)(sizeof(buf) - got), rem);
if (n > 0) {
got += n;
if (got >= 8) {
break; /* 时间包收齐,解析 */
}
continue; /* 包未收齐(分段送达),继续等余下字节 */
}
if (n == 0) {
break; /* 对端关闭(EOF) */
}
if (n == -2) {
continue; /* 本片预算等满无数据 → 下一片重算剩余预算 */
}
break; /* 真错误 */
}
if (got >= 8) {
if (buf[0] == 'T' && buf[1] == 'I' && buf[2] == 'M' && buf[3] == 'E') {
uint32_t epoch = ((uint32_t)buf[4] << 24) |
((uint32_t)buf[5] << 16) |
((uint32_t)buf[6] << 8) |
((uint32_t)buf[7]);
if (sys_clock_set_unix(epoch) == 0) {
/* 操作留痕(道G task-50):对时改的是装置时钟——与 104 的
* "clock set by 104"(App/task/iec104_serv.c 对时处)同类同口径,
* 补齐 task-49 盘出的不对称。**被拒不投**:失败不得记成"时钟被改过";
* 连续两次成功各投一条——每次对时是独立事件,无合并语义。
* quiet 口:本任务栈 2048 B(见本文件 DBG_LINE_BUF_MAX 段),
* syslog_post 的两个满态留痕分支各内联约 216 B 行缓冲,不选它。
* ⚠ 记录里的时标语义(本端口写钟面、104 写 UTC 的 8 小时差)
* 未裁定——留痕按现有通道原样落,不做任何时区补偿。 */
syslog_post_quiet(SYSLOG_OPER, "clock set by 8888");
}
DBG_INFO("time_sync: wall clock updated (epoch=%lu)", (unsigned long)epoch);
}
} else {
/* 头阻塞收口(docs/网络/网络自愈架构设计.md §5.2 E13):本层放弃会话 → 主动拆除。
* 挂起但 TCP 仍活的客户端(PC 内核自动 ACK keepalive,芯片僵尸判不死)
* 会永久占住单连接监听;EOF 路径 drop 幂等返 0(net 层已 relisten)。
* 模拟器多连接后端 conn≠ls,由 sim 实现按数据 fd 关闭。 */
(void)net_drop_conn(conn_sock);
}
/* 原 net_close(conn_sock) 已删——单连接模式 conn==监听 fd,
* close 毁监听(AGENTS 模块约定:EOF 时应用层不得 close);net 层
* auto_relisten 已把同 fd 收回 LISTENING,外层循环直接续 accept。
* 模拟器多连接后端 conn≠ls,由调用方(外层)负责关数据 fd。 */
}
/*
* 函数功能:时间同步任务主体(独立 FreeRTOS 任务)
* 入口参数:argument - 未使用
* 返回值:无
* 限定条件:net_init() 已由 netTask 完成;netTask 正在处理消息队列
* 函数说明:等待网络栈就绪后,循环:建监听 -> 等连接 -> 处理 -> **同 fd 续
* accept**(会话收尾不 close,监听跨会话常驻,仅网络
* 代次变化时重建)。全部 socket 操作经消息队列由 netTask 串行执行,
* 与本任务/其它网络任务并发安全。
* 函数说明:等待网络栈就绪后,按 TIME_SYNC_TICK_MS 节拍驱动 SNTP 校时客户端
* (time_sntp_run 内部:UDP socket 懒建/代际变化自弃旧归还,
* 等待应答期单次驻留 ≤50ms)。心跳 = 每轮循环头一次 + 延时片每片
* 一次;本任务在 IWDG required_mask 内,节拍上没有任何超过抽样窗
* 的不喂窗口。全部 socket 操作经消息队列由 netTask 串行执行。
*/
void time_sync_task(void *argument) {
(void)argument;
@@ -225,13 +86,12 @@ void time_sync_task(void *argument) {
task_wd_bind(TASK_WD_TIMESYNC); /* FTL 心跳路由:登记本任务句柄(入口一次) */
/* 等网络栈真正就绪(取代拍脑袋的 8s 魔法延时),确保 netTask 已开始处理消息;
* osFlagsNoClear:就绪位是状态,不能被本任务消费掉(否则其它等待者永不唤醒) */
* osFlagsNoClear:就绪位是状态,不能被本任务消费掉(否则其它等待者永不唤醒)。
* 2026-10-02 台架修复:等待改 100ms 分片 + 每片喂心跳——IWDG 必需掩码含
* TS 位,原 osWaitForever 无心跳 → 启动初期(存储已就绪、网络未就绪)
* 掩码 800ms 不可达 → 停喂 → 复位循环(2026-10-02 台架根因之一)。
* 100ms 分片 = 抽样窗,纯任务睡眠零队列开销。 */
if (sys_evtHandle != NULL) {
/* osFlagsNoClear:就绪位是状态,不能被本任务消费掉(否则其它等待者永不唤醒)。
* 2026-10-02 台架修复:等待改 100ms 分片 + 每片喂心跳——IWDG 必需掩码含
* TS 位,原 osWaitForever 无心跳 → 启动初期(存储已就绪、网络未就绪)
* 掩码 800ms 不可达 → 停喂 → 复位循环(2026-10-02 台架根因之一)。
* 100ms 分片 = 抽样窗,纯任务睡眠零队列开销。 */
for (;;) {
uint32_t r = osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
osFlagsWaitAny | osFlagsNoClear, 100u);
@@ -242,53 +102,15 @@ void time_sync_task(void *argument) {
}
}
/* 监听 fd 跨会话常驻:仅首次/网络代次变化/被毁时重建 */
int ls = -1;
for (;;) {
/* 心跳:本任务在 required_mask 内。以下每一处可能长于 100ms 抽样窗的
* 阻塞点(监听重试 2s、accept 轮询 200ms/次、收尾 50ms)都就近补心跳。 */
/* 心跳:本任务在 required_mask 内。time_sntp_run 的收片驻留 ≤50ms
* 落在两次心跳之间(循环头 + 下面延时片每片前置),抽样窗不破。 */
task_wd_beat(TASK_WD_TIMESYNC);
/* 记录本次监听对应的网络代次;代次变化(网络重建)则放弃旧监听重建 */
uint32_t gen = net_get_generation();
/* 网络代次随参数下传:断网/重建由 time_sntp_run 内部自理(关闭旧
* socket 归还),代次变化后下一轮自然在新网上重新发起。 */
time_sntp_run(net_get_generation());
if (ls < 0) {
ls = time_sync_open_listener();
if (ls < 0) {
DBG_ERROR("time_sync: open listener FAIL, retry later");
time_sync_delay_fed(2000U);
continue;
}
}
/* 等待 PC 连接:net_accept 在无连接时立即返回 -1,循环重试同一监听 Socket */
int conn = -1;
do {
conn = net_accept(ls, NULL, NULL);
if (conn < 0) {
if (net_get_generation() != gen) {
net_close(ls); /* 网络重建:旧监听失效,回外层重建 */
ls = -1;
break;
}
time_sync_delay_fed(200U);
}
} while (conn < 0);
if (conn < 0) {
DBG_INFO("time_sync: network re-init, relisten");
continue;
}
time_sync_handle_conn(conn);
/* 会话收尾不 close(单连接模式 conn==ls,close 连监听
* 一起毁);net 层 auto_relisten 已把同 fd 收回 LISTENING,下轮循环
* 直接续 accept。模拟器多连接后端(conn≠ls)才关数据 fd。 */
if (conn != ls) {
net_close(conn);
}
time_sync_delay_fed(50U);
time_sync_delay_fed(TIME_SYNC_TICK_MS);
}
}
+6 -8
View File
@@ -1,13 +1,14 @@
/*
* 模块名称:Time Sync — PC 本地时间推送接收(TCP)
* 模块功能:以 TCP Server 方式监听 TIME_SYNC_PORT,接收 PC 推送的本地时间包,
* 更新系统墙钟并写回 SD2506 RTC
* 模块名称:Time Sync — timeSyncTask 载体(网络校时节拍宿主)
* 模块功能:timeSyncTask 任务入口:等网络就绪后按固定节拍驱动 SNTP 校时客户端
* 适用平台:STM32F407ZGTx + CH395F
* 作者:王建锋
* 创建日期:2026-08-27
* 修改记录:
* v1.0 2026-08-27 王建锋 创建初始版本(UDP 推送)
* v1.1 2026-08-28 王建锋 改为 TCP Server,迁入独立任务 timeSyncTask
* v2.0 2026-11-04 DTU 项目组 TCP :8888 对时退役,TIME_SYNC_PORT 宏随退,
* 任务收缩为 SNTP 节拍宿主(细节见 time_sync.c 文件头)
*/
#ifndef TIME_SYNC_H
@@ -19,16 +20,13 @@
extern "C" {
#endif
/* 监听端口:PC 推送端(TCP 客户端)需连到本端口 */
#define TIME_SYNC_PORT 8888
/*
* 函数功能:时间同步任务主体(独立 FreeRTOS 任务)
* 入口参数:argument - FreeRTOS 传入参数(未使用)
* 返回值:无
* 限定条件:net_init() 已由 netTask 完成;网络消息队列由 netTask 处理
* 函数说明:以 TCP Server 方式监听 TIME_SYNC_PORT,接收 PC 推送的本地时间包,
* 经消息队列(由 netTask 串行执行)完成 socket 操作,更新系统墙钟。
* 函数说明:等网络就绪后按 200ms 节拍驱动 SNTP 校时客户端(App/sync/time_sntp.c),
* socket 操作经消息队列(由 netTask 串行执行)完成。
* 该任务与 FTP 等其它网络任务并发安全。
*/
void time_sync_task(void *argument);
+45 -18
View File
@@ -48,6 +48,7 @@
#include "main.h"
#include "ff.h"
#include "nand_ftl.h"
#include "bsp_err.h" /* BSP_ERR_NO_SPACE / BSP_ERR_CRC:卷健康探针按失败类分流(C4) */
#include "net_cfg.h"
#include "cfg_file.h"
#include "run_par.h" /* 运行参数段落盘(「数据可信」这一项,配置引擎属主 = 本任务) */
@@ -523,30 +524,56 @@ static FRESULT storage_bring_up(FATFS *fs) {
FS_VOL_PROBE(hfr); /* 两击确认 */
}
if (hfr != FR_OK) {
/* C4 消费点(2026-11-04,问题 #79):探针只拿到 FRESULT,失败类在
* disk_read/disk_write 返回时就被 FatFs 契约抹掉了。这里把 FTL 留下的
* 跨调用快照读出来,**先判"重格式化有没有意义"再决定动作**——
* 这正是 存储架构说明 §10.4 那条一直只写在文档里、没有消费者的判据。
*
* 为什么必须先判:BSP_ERR_NO_SPACE 是 journal/映射层块池见底(介质健康),
* 重格式化只会把问题推回去(文档原文);而 BSP_ERR_CRC 才是"页级太坏,
* 换页/reformat 可能救"。两类都走 reformat 的话,块池见底会被反复擦盘。 */
nand_ftl_fault_t fault;
nand_ftl_fault_get(&fault);
if (fault.active != 0u) {
DBG_ERROR("[FS] probe failed (fr=%d), FTL last fault: op=%u class=%ld lpn=%lu count=%lu",
(int)hfr, (unsigned)fault.op, (long)fault.cls,
(unsigned long)fault.lpn, (unsigned long)fault.count);
}
/* ⚠ FR_INVALID_NAME 绝不可入本集合:它是路径名语法错误(纯 RAM 判定、
* 不碰盘),与卷健康无关(曾误入 → "无限重格式化"事故) */
if (hfr == FR_DENIED || hfr == FR_INVALID_OBJECT ||
hfr == FR_INT_ERR || hfr == FR_DISK_ERR ||
hfr == FR_NO_FILE || hfr == FR_NO_PATH ||
hfr == FR_NO_FILESYSTEM) {
DBG_INFO("[FS] volume FAT invalid (fr=%d), reformatting...",
(int)hfr);
f_mount(NULL, "", 0);
/* 格式化前清空 FTL map 树(同首启路径:新卷从空树写起) */
(void)nand_ftl_format();
{
static uint8_t fs_work2[FF_MAX_SS];
MKFS_PARM opts2 = { FM_FAT32, 0, 0, 0, 0 };
fr = f_mkfs("", &opts2, fs_work2, sizeof(fs_work2));
if (fr == FR_OK) {
fr = f_mount(fs, "", 1);
}
}
if (fr == FR_OK) {
DBG_INFO("[FS] volume reformatted OK");
/* 按失败类分流:块池见底 ≠ 介质坏。前者擦卷救不回来,只上报
* (后续应走容量恢复,见 存储维护与容量恢复.md),后者才 reformat。 */
if ((fault.active != 0u) && (fault.cls == (int32_t)BSP_ERR_NO_SPACE)) {
DBG_ERROR("[FS] volume probe failed: FTL out of space, "
"reformat skipped (needs capacity recovery, not mkfs)");
syslog_post(SYSLOG_SELFTEST, "storage full");
fr = FR_INT_ERR;
} else {
DBG_ERROR("[FS] volume repair failed (fr=%d)", (int)fr);
syslog_post(SYSLOG_SELFTEST, "storage fault");
DBG_INFO("[FS] volume FAT invalid (fr=%d), reformatting...",
(int)hfr);
f_mount(NULL, "", 0);
/* 格式化前清空 FTL map 树(同首启路径:新卷从空树写起) */
(void)nand_ftl_format();
{
static uint8_t fs_work2[FF_MAX_SS];
MKFS_PARM opts2 = { FM_FAT32, 0, 0, 0, 0 };
fr = f_mkfs("", &opts2, fs_work2, sizeof(fs_work2));
if (fr == FR_OK) {
fr = f_mount(fs, "", 1);
}
}
if (fr == FR_OK) {
DBG_INFO("[FS] volume reformatted OK");
} else {
DBG_ERROR("[FS] volume repair failed (fr=%d)", (int)fr);
syslog_post(SYSLOG_SELFTEST, "storage fault");
}
}
} else {
/* 其余错误(写保护/卷锁/参数类)不重格式化——只上报,
@@ -685,7 +712,7 @@ static void wave_store_drain(void) {
}
}
if ((why == NULL) && (s_wscan_done == 0u)) {
FRESULT rr;
FRESULT rr = FR_OK;
fr = f_opendir(&s_wdir, WAVE_ST_DIR);
if (fr == FR_OK) {
+52
View File
@@ -8,6 +8,11 @@
* 2026-07-16 王建锋 创建初始版本
* 2026-07-17 王建锋 切换硬件 SPI,修正擦除地址,参考 NuttX 驱动
* 2026-08-27 补充函数注释(代码规范 V1.0);修正工厂坏块扫描为首页;增加 BBT 持久化
* 2026-11-04 问题 #79 取证配套:① init 解锁块保护(A0h)后补回读校验——
* 与 2026-10-23 修过的 ECC(B0h)同族的"只写不回读"缺口
* (实测本板 A0h 已为 0,写保护非本次根因,校验留作哨兵);
* ② `gd5f_bbt_load()` 合并后若坏块数 > 出厂扫描数则打一条
* "BBT after merge",让现场一眼区分真坏与误标固化。
*/
/* 头文件包含区 */
@@ -839,6 +844,39 @@ int32_t gd5f2gq5ue_init(void) {
DBG_ERROR("Unlock block protection failed: %d", ret);
return ret;
}
/* 回读 A0h 校验保护位**真正清零**(2026-11-04,问题 #79 取证发现)。
*
* 为什么必须校验:本项与 2026-10-23 修过的 ECC(B0h) 是同一条缺陷的另一半——
* `gd5f_set_feature()` 只写不回读,"写了但没生效"对上层完全不可见。
* A0h 没清干净的后果比 ECC 没开更直接:**整片只读**。
*
* 台架现场(11-04 17:40 本次启动,`repair-nand-volume` 取证):
* BBT scan: 0 bad blocks found ← 介质出厂坏块为 0
* 读操作全部正常(Map resumed / FAT 探针能读到内容)
* 写操作**全部**失败:LPN=211(块 3)与 LPN=15(块 0)两个不同块
* 连 nand_ftl_format() 后的 map sync 都失败 → storage disabled
* "读正常 + 写全废 + 跨多个无关块 + 出厂坏块为 0" 这组特征排除了
* "某一页/某一块真坏"(那不会跨块、也不会让全新 map sync 失败),
* 指向芯片处于**写保护态**。
*
* 判据:A0h bit0-3 (BP0..BP3) 与非易失的 BP 位组合决定保护范围;
* 读到非 0 即保护未解除,必须**明确失败**而不是继续跑(继续跑会让
* 上层把"写保护"误判成"介质损坏",进而反复格式化——正是本单现场)。
*/
{
uint8_t prot = 0;
ret = gd5f_private_get_feature(GD5F_REG_PROTECT, &prot);
if (ret != GD5F_OK) {
DBG_ERROR("Read A0h failed: %d", ret);
return ret;
}
if (prot != 0u) {
DBG_ERROR("Block protection NOT cleared (A0h=0x%02X), NAND is read-only",
prot);
return GD5F_ERROR;
}
}
DBG_INFO("Locating BBT pool...");
ret = gd5f_private_bbt_locate_pool();
@@ -854,6 +892,20 @@ int32_t gd5f2gq5ue_init(void) {
DBG_ERROR("Load persisted BBT failed: %d (use scanned BBT)", ret);
}
/* 【#79】持久化 BBT 若把连续段判坏(出厂扫描 0 坏块、底层写全部成功),
* 那是运行期误标被固化后的结果,不是介质坏。此处只打一条汇总,
* 让下次现场一眼能区分"真坏"与"误标"。 */
{
uint32_t bad = 0;
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS; b++) {
if ((s_bbt[b >> 3] >> (b & 7)) & 1) bad++;
}
if (bad > 0) {
DBG_ERROR("BBT after merge: %lu bad blocks (factory scan was 0)",
(unsigned long)bad);
}
}
DBG_INFO("NAND init OK");
return GD5F_OK;
}
+162 -12
View File
@@ -25,10 +25,31 @@
* 单一真源 + 两条负数组编译期守卫:越界上界依赖
* log2_ppb,换料时只改 GD5F_* 而忘改字面量会**静默**
* 把守卫算大 = 越界守卫失效,现改为直接编译错
* 2026-11-04 王建锋 问题 #79(台架 LPN=211 恒 TOO_BAD 致 f_mount 失败、
* 整卷禁用)两处修复:
* ① `nand_ftl_format()` 不再被脏缓存 flush 失败挡住——
* 旧实现先 flush、失败即 return,导致**恢复动作自己在
* 同一页上死锁**(map 从未清过,f_mkfs 在旧映射上必失败)。
* 格式化语义本就是销毁映射,故尽力刷一次后无条件丢缓存;
* ② 补 C4 跨调用失败类快照(`nand_ftl_fault_get/clear`),
* 让 disk_initialize/读/刷/同步的失败归类能穿过 FatFs 的
* RES_ERROR 边界,供 defaultTask 探针按类分流(见 存储架构
* 说明 §10.4);③ .c 自包含 include 本模块头(主机侧单测
* 直接 #include 本文件,缺头则类型不可见)。
* ④(真机取证后定案)`dhara_nand_erase/prog()` 不再把
* **传输层/等忙失败**当坏块证据:只有芯片明确回 E_FAIL /
* P_FAIL 才标坏。真凶即在此——非坏类失败被标坏后由
* mark_block_bad 立即持久化(不可撤销),台架 BBT 遂固化
* 129 个误标块(1016–1142 连续段),journal prepare_head()
* 连续 8 次 is_bad → TOO_BAD → 整卷禁用;已固化的误标由
* 恢复动作 A 重建 BBT 清除(见 存储维护与容量恢复 §2.1)。
*/
/* 头文件包含区 */
#include <string.h>
#include "nand_ftl.h" /* 本模块公开契约:失败快照类型(C4)与 nand_ftl_* 原型。
* 必须自包含——旧版靠调用方先 include 本头,主机侧单测
* 直接 #include "nand_ftl.c" 时类型不可见(实测编译错)。 */
#include "gd5f2gq5ue.h"
#include "ff.h"
#include "diskio.h"
@@ -125,10 +146,19 @@ static uint8_t s_copy_buf[FTL_PAGE_SIZE];
/* 初始化状态 */
static uint8_t s_initialized;
/* 失败快照(C4):把"最近一次存储失败"的归类带出函数边界。
* 为什么需要:disk_read/disk_write 对 FatFs 只能回 RES_ERROR(FatFs 契约),
* 失败类在 return 那一刻就被抹掉,上层因此无法区分「页级 TOO_BAD,重格式化可解」
* 与「块池见底 / 映射损坏,重格式化只会把问题推回去」。
* 多写者纪律:defaultTask 与 testTask 都会碰存储,故语义是"最近一次"而非账本;
* 并发下可能被另一路覆盖——诊断用途可接受,记在此处以免日后误当权威计数。 */
static nand_ftl_fault_t s_fault;
/* ======================== 私有函数声明 ======================== */
static bsp_err_t ftl_flush_cache(void);
static bsp_err_t ftl_read_page(dhara_sector_t lpn);
static void ftl_fault_note(uint32_t op, bsp_err_t cls, dhara_sector_t lpn);
/* SPI 原语从 gd5f2gq5ue.h 获取 */
@@ -184,6 +214,52 @@ static bsp_err_t ftl_class_from_dhara(dhara_error_t err) {
}
}
/* ======================== 失败快照(C4) ======================== */
/*
* 函数功能:记录一次存储失败到跨调用快照
* 入口参数:op - 失败的操作(NAND_FTL_OP_*)
* cls - 失败类(ftl_class_from_dhara 的结果)
* lpn - 涉及逻辑页号(未知传 NAND_FTL_LPN_NONE)
* 返回值:无 void
* 限定条件:可在任何上下文调用;本函数只写几个字,不加锁(见下方顺序说明)
* 函数说明:**不覆盖已有的非 NONE 归类除非新失败更具体**——不,刻意选择
* "后写覆盖":快照回答的是"最近一次失败是什么",最靠近处置点的现场才有用。
* count 单独累计,用来回答另一个问题:"是不是一直在撞同一页"(本单现场正是
* LPN=211 反复失败 15 次)。两者分开存,避免为了统计把最近现场挤掉。
*
* 写入顺序是有意的:先写内容字段,最后置 active=1(写侧无锁,读侧可能
* 在任意指令边界被抢占)。这样读到 active=1 时内容字段必然已经写好;
* 最坏情况是读到旧内容 —— 诊断用途可接受,且不会比"没有快照"更差。
*/
static void ftl_fault_note(uint32_t op, bsp_err_t cls, dhara_sector_t lpn) {
s_fault.op = op;
s_fault.cls = (int32_t)cls;
s_fault.lpn = (uint32_t)lpn;
if (s_fault.count < 0xFFFFFFFFu) {
s_fault.count++;
}
s_fault.active = 1u;
}
void nand_ftl_fault_get(nand_ftl_fault_t *p_out) {
if (p_out == NULL) {
return;
}
/* 整结构拷贝:调用方拿到的是一致快照,不会一半新一半旧 */
*p_out = s_fault;
}
void nand_ftl_fault_clear(void) {
/* 先清 active 再清内容:反序会让读侧看到 active=0 但 count 仍是旧值,
* 虽然无害,但"active=0 时其余字段无意义"这条契约更该由写法保证 */
s_fault.active = 0u;
s_fault.op = NAND_FTL_OP_NONE;
s_fault.cls = (int32_t)BSP_OK;
s_fault.lpn = NAND_FTL_LPN_NONE;
s_fault.count = 0u;
}
/* ======================== 页面缓存管理 ======================== */
static bsp_err_t ftl_flush_cache(void) {
@@ -196,6 +272,7 @@ static bsp_err_t ftl_flush_cache(void) {
bsp_err_t cls = ftl_class_from_dhara(err); /* 只在失败分支需要(cppcheck variableScope) */
DBG_ERROR("Cache flush FAIL: LPN=%lu dhara=%s class=%d",
s_cached_lpn, ftl_err_name(err), cls);
ftl_fault_note(NAND_FTL_OP_FLUSH, cls, s_cached_lpn);
return cls;
}
@@ -210,6 +287,7 @@ static bsp_err_t ftl_read_page(dhara_sector_t lpn) {
bsp_err_t cls = ftl_class_from_dhara(err); /* 只在失败分支需要(cppcheck variableScope) */
DBG_ERROR("Page read FAIL: LPN=%lu dhara=%s class=%d",
lpn, ftl_err_name(err), cls);
ftl_fault_note(NAND_FTL_OP_READ, cls, lpn);
return cls;
}
@@ -243,9 +321,24 @@ int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t
}
ret = gd5f_block_erase(ftl_phy_block(b));
if (ret == GD5F_ERASE_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, b);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
if (ret != GD5F_OK) {
if (ret == GD5F_ERASE_FAIL) {
/* 芯片明确回 E_FAIL:该块确实擦不净,标坏是正确处置 */
DBG_ERROR("Erase failed with E_FAIL: block=%lu", (unsigned long)b);
dhara_nand_mark_bad(n, b);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
} else {
/* 【#79 核心修复】传输层/等忙失败(GD5F_ERROR / GD5F_WAIT_EXPIRED)
* 不是坏块证据:芯片根本没回 E_FAIL,把块标坏等于把"线抖了一下"
* 固化成"这块永久报废",并立即写进持久化 BBT(不可撤销)。
* 这类失败只报 BAD_BLOCK 让 dhara 重试,**不标坏**。
* 真机取证(2026-11-04 17:53):出厂扫描 0 坏块、底层 prog/erase
* 全部成功,而持久化 BBT 把 1016–1142 连续段判坏,prepare_head()
* 连续 8 次撞 is_bad → DHARA_E_TOO_BAD → 整卷禁用。 */
DBG_ERROR("Erase transport/timeout failure (not a bad block): "
"block=%lu ret=%d", (unsigned long)b, ret);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
}
return -1;
}
return 0;
@@ -264,15 +357,27 @@ int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *d
gd5f_write_enable();
ret = gd5f_program_load(0, data, FTL_PAGE_SIZE);
if (ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
/* 【#79】装载失败是传输层问题(SPI/DMA/等忙),芯片未回 P_FAIL,
* 不得标坏块;只报 BAD_BLOCK 让 dhara 重试。 */
DBG_ERROR("Program load transport failure (not a bad block): "
"page=%lu ret=%d", (unsigned long)phy_pg, ret);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
ret = gd5f_program_exec(phy_pg);
if (ret == GD5F_PROGRAM_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
if (ret != GD5F_OK) {
if (ret == GD5F_PROGRAM_FAIL) {
/* 芯片明确回 P_FAIL:页确实写不进去,标坏是正确处置 */
DBG_ERROR("Program failed with P_FAIL: page=%lu", (unsigned long)phy_pg);
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
} else {
/* 【#79】传输层/等忙失败不是坏块证据,不标坏,只重试 */
DBG_ERROR("Program transport/timeout failure (not a bad block): "
"page=%lu ret=%d", (unsigned long)phy_pg, ret);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
}
return -1;
}
@@ -431,6 +536,14 @@ DSTATUS disk_initialize(BYTE pdrv) {
s_cached_lpn = (dhara_sector_t)-1; /* -1 表示缓存未加载;LPN 0 合法,不能用 0 作哨兵 */
s_cache_dirty = 0;
/* 失败快照初值:一次上电从"无失败"起算。刻意放在 map_init 之后、
* resume 之前——resume 失败本身就是要被记进快照的第一件事。 */
s_fault.active = 0u;
s_fault.op = NAND_FTL_OP_NONE;
s_fault.cls = (int32_t)BSP_OK;
s_fault.lpn = NAND_FTL_LPN_NONE;
s_fault.count = 0u;
{
dhara_error_t err = DHARA_E_NONE;
if (dhara_map_resume(&s_map, &err) < 0) {
@@ -453,6 +566,9 @@ DSTATUS disk_initialize(BYTE pdrv) {
DBG_ERROR("Map resume failed (dhara=%s class=%d), volume kept, "
"storage unavailable", ftl_err_name(err),
ftl_class_from_dhara(err));
ftl_fault_note(NAND_FTL_OP_MAP_RESUME,
ftl_class_from_dhara(err),
(dhara_sector_t)NAND_FTL_LPN_NONE);
return STA_NOINIT;
}
} else {
@@ -598,6 +714,8 @@ DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
dhara_error_t err;
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync failed: %d", err);
ftl_fault_note(NAND_FTL_OP_SYNC, ftl_class_from_dhara(err),
(dhara_sector_t)NAND_FTL_LPN_NONE);
return RES_ERROR;
}
}
@@ -634,20 +752,52 @@ int nand_ftl_format(void) {
}
}
/* 处置开始:清掉历史失败快照,此后新失败重新记账
* (否则一次旧失败会永久污染"本次格式化到底成没成"的判定) */
nand_ftl_fault_clear();
/* 【本单核心修复 2026-11-04,问题 #79】
* 旧实现先 `if (ftl_flush_cache() != BSP_OK) return GD5F_ERROR;`——而
* ftl_flush_cache() 在失败分支**不清 s_cache_dirty**(那是刻意的:写不进去
* 就不能假装写成功了),于是只要脏页落在"写不动的那个 LPN"上,
* 本函数就在第一道闸门返回,**dhara_map_clear() 永远执行不到**。
*
* 台架现场(11-04 08:16 与 08:42 两次独立捕获,head/tail/epoch 一字不变):
* 探针写 /fschk.tmp → disk_write → flush LPN=211 失败(TOO_BAD)
* → 重试同页再次失败 → 判卷损坏 → 走本函数 → 又被同一页挡住
* → f_mkfs 在旧映射上跑 → 失败 → 3 轮重试耗尽 → storage disabled。
* 每次启动必现、断电重启不恢复,因为 s_cache_dirty 与 LPN=211 的脏数据
* 在**同一次启动内**恒存,而每轮启动都会重新踩上同一条路。
*
* 为什么"跳过 flush 直接丢缓存"在这里是正确的:
* 本函数的语义是**销毁整卷逻辑映射**(头文件:「破坏性:整卷逻辑地址→
* 物理映射作废」,只允许显式命令/恢复流程调用;现存两个调用点
* ——首启无卷与 FAT 探针判卷损坏——都紧接着 f_mkfs 重建卷)。既然这份
* 脏页映射注定被扔掉,为它挡住格式化既救不回数据,又让**恢复动作本身
* 永久失效**。故:尽力刷一次(成功则顺带落盘、日志更干净),失败就
* 丢缓存并继续——这正是"格式化"该有的优先级。
*
* ⚠ 不得把本改动挪到普通写路径:disk_write/disk_read 里的 flush 失败
* 必须继续向上报错(那是"这次写没落地"的真相),只有本函数有权丢弃缓存。
*/
if (ftl_flush_cache() != BSP_OK) {
return GD5F_ERROR;
DBG_ERROR("format: dirty cache at LPN=%lu undiscardable, "
"dropping it (map is being destroyed anyway)", s_cached_lpn);
}
/* 无条件失效页面缓存:格式化后旧映射作废,绝不能让它再被当成有效缓存 */
s_cache_dirty = 0;
s_cached_lpn = (dhara_sector_t)-1;
dhara_map_clear(&s_map);
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync after clear failed: %d", err);
DBG_ERROR("Map sync after clear failed: %s class=%d",
ftl_err_name(err), ftl_class_from_dhara(err));
ftl_fault_note(NAND_FTL_OP_SYNC, ftl_class_from_dhara(err),
(dhara_sector_t)NAND_FTL_LPN_NONE);
return GD5F_ERROR;
}
s_cache_dirty = 0;
s_cached_lpn = (dhara_sector_t)-1;
DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map));
return GD5F_OK;
}
+61
View File
@@ -19,6 +19,40 @@ extern "C" {
#include <stdint.h>
/*
* 失败快照(C4):把"最近一次存储失败的归类"从失败点带出来。
*
* 为什么要单独成一个结构而不是复用 bsp_err_t:调用方需要的不只是失败类,
* 还要能回答"这次失败值不值得再试一次"(重试同一 LPN 会不会又撞同一页)。
* 故带上 LPN 与 op,便于 run.log/wdev.log 里一条日志说清现场。
*
* 字段:
* active - 1 = 快照可用(自上次 nand_ftl_fault_clear() 以来发生过失败)
* op - 失败发生的操作(NAND_FTL_OP_*),0 = 无
* cls - 失败类(bsp_err_t:BSP_ERR_CRC / BSP_ERR_NO_SPACE / BSP_ERR_IO …)
* lpn - 失败涉及的逻辑页号(未知时填 NAND_FTL_LPN_NONE)
* count - 自上次 clear 以来累计的失败次数(饱和不减,用于判"反复撞同一页")
*
* 尺寸:5 × uint32 = **20 B**,无隐式填充(字段全为 4 字节宽,任何编译器布局一致)。
* 不引入任何动态分配。
*/
#define NAND_FTL_LPN_NONE ((uint32_t)0xFFFFFFFFu)
#define NAND_FTL_OP_NONE ((uint32_t)0u)
#define NAND_FTL_OP_MAP_RESUME ((uint32_t)1u) /* disk_initialize 的 dhara_map_resume */
#define NAND_FTL_OP_READ ((uint32_t)2u) /* ftl_read_page(FatFs 读路径) */
#define NAND_FTL_OP_FLUSH ((uint32_t)3u) /* ftl_flush_cache(写回路径) */
#define NAND_FTL_OP_SYNC ((uint32_t)4u) /* disk_ioctl(CTRL_SYNC) 的 map sync */
typedef struct {
uint32_t active; /* 1 = 快照可用(自上次 nand_ftl_fault_clear() 以来发生过失败) */
uint32_t op; /* 失败发生的操作(NAND_FTL_OP_*),0 = 无 */
int32_t cls; /* 失败类(bsp_err_t:BSP_ERR_CRC / BSP_ERR_NO_SPACE / BSP_ERR_IO …)。
* 写死 int32_t 宽度:bsp_err_t 是 int,跨编译器布局才稳定 */
uint32_t lpn; /* 失败涉及的逻辑页号(未知时 = NAND_FTL_LPN_NONE) */
uint32_t count; /* 自上次 clear 以来累计失败次数(饱和不减,用于判"反复撞同一页") */
} nand_ftl_fault_t;
/*
* 函数功能:初始化 FTL(等价于 disk_initialize(0):SPI 起链 + dhara_map_resume)
* 入口参数:无
@@ -53,6 +87,33 @@ int nand_ftl_format(void);
*/
void nand_ftl_deinit(void);
/*
* 函数功能:取"最近一次存储失败"的跨调用快照(C4,2026-11-04 补还)
* 入口参数:p_out - 快照输出 nand_ftl_fault_t* 可为 NULL(=只查询是否有失败)
* 返回值:无 void(新 API 不把"有没有失败"塞进返回值:本函数是纯查询,
* 判定一律读 p_out->active)
* 限定条件:任何任务上下文可读;**仅诊断用途**,不得据此自动清卷/重格式化
* 函数说明:为什么需要它——disk_read/disk_write 对 FatFs 只能回 RES_ERROR
* (FatFs 契约),失败类在函数返回时就被抹掉,上层因此无法区分
* 「页级 TOO_BAD,重格式化可解」与「块池见底 / 映射损坏,重格式化只会把问题
* 推回去」。本快照把 nand_ftl.c 就地分类的结果留在 RAM 里,供
* defaultTask 的卷健康探针按类分支(消费点见 storage_bring_up())。
* ⚠ 多写者:defaultTask 与 testTask 都可能触发写入失败,故快照是"最近一次"
* 语义、非累计账本;并发下可能被另一路覆盖(诊断场景可接受)。
* 成功路径**不清**它(失败证据要留存到显式 clears),语义同 dbg latch。
*/
void nand_ftl_fault_get(nand_ftl_fault_t *p_out);
/*
* 函数功能:清除失败快照(把 active 置 0)
* 入口参数:无
* 返回值:无
* 限定条件:仅"已按快照处置完毕"的调用方清除(当前 = defaultTask 卷修复路径)
* 函数说明:不清就会让一次历史失败永久污染后续判定;清早了又会丢掉现场。
* 调用点因此只有一处,见 storage_bring_up() 的重格式化分支。
*/
void nand_ftl_fault_clear(void);
#ifdef __cplusplus
}
#endif
+25 -6
View File
@@ -518,13 +518,32 @@ int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:判"本 Socket 的发送会话是否已不可继续"(TCP/UDP 统一判据)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 出口参数:无
* 返回值:1 - 不可继续(应报 CONNRESET/NOTCONN);0 - 可继续 int
* 限定条件:p_sock 非空;任意上下文(纯读控制块)
* 函数说明:TCP 的门是 ESTABLISHED,UDP 的门是 UDP_OPEN(芯片侧由 net_bind 打开)。
* 判据只写这一处:net_send_locked 的入口门与两处中途检查、
* net_send_pending_tick 的 ① 都用它——两条发送路径各自复述一遍
* "state != ESTABLISHED"正是 UDP 接入时最容易漏改的形态。
*/
int net_send_state_down(const net_sock_t *p_sock) {
if (p_sock->type == NET_SOCK_DGRAM) {
return (p_sock->state != NET_SOCK_STATE_UDP_OPEN) ? 1 : 0;
}
return (p_sock->state != NET_SOCK_STATE_ESTABLISHED) ? 1 : 0;
}
/*
* 函数功能:挂起 send 推进与完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:1. 逐 fd 按序:①断连 → 以已发字节数结束(剩余字节不可能送达)
* 函数说明:1. 逐 fd 按序:①会话不可继续(TCP 断连 / UDP 芯片侧被关)→ 以已发字节数结束
* (剩余字节不可能送达)
* 2. ②推进:发送缓冲空闲则写下一块(每轮至多一块——写完即清 send_ready,
* 下一块须等 SENBUF_FREE 中断重新置位,由 net_poll 刷新)
* 3. ③全部写完 → 以总长度结束;④无进展超时 → 以已发字节数结束
@@ -553,13 +572,13 @@ void net_send_pending_tick(net_sock_t *p_socks) {
continue;
}
/* ① 断连:本会话已结束 → 剩余字节不可能送达,按已发字节数结束。
* 判据 `state != ESTABLISHED`(同快速路径):state 离开 ESTABLISHED 后
* 即便再回到 ESTABLISHED,也是**新会话**(relisten + 新 CONNECT)——
* 旧会话的剩余字节绝不能投到新连接上。session_dead 一并覆盖
/* ① 会话已不可继续 → 剩余字节不可能送达,按已发字节数结束。
* 判据见 net_send_state_down(TCP:state 离开 ESTABLISHED 后即便再回到
* ESTABLISHED 也是**新会话**,旧会话剩余字节绝不能投到新连接上;
* UDP:芯片侧 Socket 被关/复位即不可继续)。session_dead 一并覆盖
* (net_types.h:TIMEOUT/DISCONNECT 两条路径都同时置 CLOSED,此处为
* 与 recv 侧判据对齐的冗余保险) */
if ((p_sock->state != NET_SOCK_STATE_ESTABLISHED) || (p_sock->session_dead != 0U)) {
if ((net_send_state_down(p_sock) != 0) || (p_sock->session_dead != 0U)) {
DBG_ERROR("sock%d send pending aborted (%d/%d): session down",
i, p->sent, p->len);
(void)net_set_errno(NET_ERR_CONNRESET);
+12 -1
View File
@@ -163,7 +163,8 @@ int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:逐 fd 按序判定:①断连(state 非 ESTABLISHED / session_dead)→ 以已发
* 函数说明:逐 fd 按序判定:①会话不可继续(net_send_state_down:TCP 看
* ESTABLISHED / UDP 看 UDP_OPEN;session_dead 一并覆盖)→ 以已发
* 字节数结束;②推进——发送缓冲空闲则写下一块(每轮至多一块,写完即清
* send_ready,下一块等 SENBUF_FREE 由 net_poll 重新置位);③全部写完 →
* 以总长度结束;④无进展超时 → 以已发字节数结束。结果均为**已发字节数**
@@ -171,6 +172,16 @@ int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
*/
void net_send_pending_tick(net_sock_t *p_socks);
/*
* 函数功能:判"本 Socket 的发送会话是否已不可继续"(TCP/UDP 统一判据)
* 入口参数:p_sock - Socket 控制块指针 net_sock_t *
* 返回值:1 - 不可继续;0 - 可继续 int
* 限定条件:p_sock 非空;纯读控制块,任意上下文
* 函数说明:TCP 门 = ESTABLISHED,UDP 门 = UDP_OPEN。快速路径(net_send_locked)
* 与挂起路径(net_send_pending_tick)共用,判据只此一处。
*/
int net_send_state_down(const net_sock_t *p_sock);
/*
* 函数功能:清除本 fd 的挂起 send(不通知;"新 fd 不得继承"与 close 后的幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
File diff suppressed because it is too large Load Diff
+181 -37
View File
@@ -39,11 +39,31 @@ typedef enum {
NET_MSG_LISTEN,
NET_MSG_ACCEPT,
NET_MSG_DROP_CONN, /* 主动拆会话、保监听(见 net_drop_conn) */
/* UDP 队列化改造(2026-11-03,工单前置 = SNTP 客户端 #57):UDP 三个碰芯片的
* 动作从"调用方上下文直调 SPI"改为交 netTask 串行执行,字段用法:
* NET_MSG_UDP_BIND - sockfd + len=本地端口(主机序,0 = 执行体内取动态号)
* 恒 DesIP=0xFFFFFFFF 服务器模式(bind 不选对端,
* 客户端模式见 NET_MSG_UDP_PEER / SENDTO 的 addr)
* NET_MSG_UDP_SENDTO - buf/len=载荷 + addr=本包目的地址(按值拷入,**入参**方向)
* NET_MSG_UDP_RECVFROM - buf/len=载荷 + timeout_ms=等待预算 + flags
* ⚠ 源地址出参(src_addr / addrlen 两个指针)**不走本结构**:结构按值拷入
* 队列,指针若在 int 字段里过桥,64 位主机单测构建会被截断,且违反
* AGENTS「勿给 net_msg_t 加回传用字段」。改走 net_udp.c 的 per-fd 挂起槽
* (调用方在入队前登记自己的指针,netTask 完成后写进调用方内存 + 通知值回码),
* 与 TCP 挂起 recv 把数据直接写进调用方 buf 是同一个模型。
* 详见 docs/网络/BSD_Socket_API_使用指南.md §6 */
NET_MSG_UDP_BIND,
NET_MSG_UDP_SENDTO,
NET_MSG_UDP_RECVFROM,
/* 静默回收认领 + 手动自愈 API 退役(docs/网络/网络自愈架构设计.md §4.1、§4.2):
* NET_MSG_RESYNC_LISTEN / NET_MSG_RELISTEN 已删——
* 静默回收由对账⑤自动认领(net_reconcile_tick),重建由 reconciler
* 形态③④/D10 单一
* 所有者负责;手动自愈 API 面退役(生产调用者本就为零) */
/* UDP 模式切换(追加在枚举末尾:既有成员编号是主机单测/板测的钉值对象,
* 不在中间插入)——addr 按值携带对端则切客户端模式,addrlen == 0 则回
* 服务器模式;不重开 Socket,只写 DesIP/DesPort,语义见 net_udp_set_peer */
NET_MSG_UDP_PEER
} net_msg_type_t;
typedef struct {
@@ -331,60 +351,145 @@ int net_setblocking(int sockfd, int on);
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
/*
* 函数功能:发送 UDP 数据
* 函数功能:发送 UDP 数据报(**走消息队列**,任意任务可调用)
* 入口参数:sockfd - socket 描述符
* buf - 数据缓冲区
* len - 数据长度
* flags - 通常为 0
* dest_addr - 目标地址
* addrlen - 地址长度
* len - 数据长度(>0)
* flags - 目前不使用,传 0(与 net_send 同)
* dest_addr - 本包目的地址;NULL = 沿用本 Socket 当前 DesIP
* addrlen - 地址长度(NULL 目的时传 0 即可)
* 返回值:实际发送字节数 int > 0
* -1 失败,精确码经本任务错误码槽给出(net_get_errno()):
* NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_TIMEDOUT /
* NET_ERR_NOT_IN_NETTASK(调用上下文不对,见「限定条件」)
* 限定条件:**仅限本层执行任务(net_init 的调用者,NET 层 = netTask)调用**;
* socket 已创建且为 DGRAM;指针非空
* 函数说明:1. 设置目标地址后调用 CH395F 发送
* 2. ⚠ 本接口**不走消息队列**:它在调用方上下文里直接 net_poll +
* 写芯片缓冲,与 net_send/net_recv/net_connect 那一族(入队交 netTask
* 串行执行)不是同一条路径。别的任务调用它 = 与本层的 net_poll 抢
* 同一根 SPI2(TRAP-NET-14 的根因形态),故加上下文守卫直接拒掉,
* 回 NET_ERR_NOT_IN_NETTASK。**该码重试永远无意义**(既非忙也非超时):
* 要么把调用挪进执行任务,要么改用走队列的 net_send——没有第三条。
* 3. 「把它改成走消息队列」是已知后续项,未做的原因:UDP 的目的地址
* 与源地址回传都要穿过 net_msg_t,而 net_msg_t 按值拷入队列、
* 错误码只搭任务通知值回来(AGENTS 明文「勿给 net_msg_t 加回传字段」),
* 真要串行化得先设计 per-task 结果槽并配真机回归(2026-10-24 定案:
* 本轮只把危险面关掉,串行化仍未做)。
* 4. 全仓当前**无生产调用者**(只有 test/board 与模拟器),正因如此
* 这条守卫要在有人开始用之前就在。
* NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_TIMEDOUT / NET_ERR_BUSY
* 限定条件:socket 已创建(DGRAM 用法须先 net_bind 或 net_udp_set_peer 打开芯片侧
* Socket);net_init() 已执行;**任意任务上下文**(含 netTask 自身,
* netTask 内直接执行不入队——自投递给自身 = 死锁)
* 函数说明:1. 目的地址是**入参**,按值拷进 net_msg_t.addr 随消息进 netTask,
* 与 net_connect 传地址同一条通道;结果(已发字节数/错误码)
* 搭任务通知值回来(AGENTS「勿给 net_msg_t 加回传字段」)。
* 2. 带显式目的地址 = 芯片 DesIP/DesPort 切到该对端 → 本 Socket 记账进入
* **UDP 客户端形态**(p_sock->udp_mode = NET_UDP_MODE_CLIENT):此后
* `dest_addr = NULL` 的发送就发往它。**收包帧格式不受影响**——信息头是
* OPEN_SOCKET 那一刻按当时的 DesIP 定死的,本层 Socket 一律由 net_bind
* 在全 F 形态下开出,所以恒带 8 字节信息头、net_recvfrom 恒剥(真机实测
* 见 TRAP-NET-22 的 2026-11-04 追加段)。运行时改窄 DesIP 会不会让芯片
* 按源过滤,台架未测出结论,多源服务别依赖它。
* 要回服务器形态(任意源可发、下一次 NULL 发送不再沿用旧对端)调
* net_udp_set_peer(sockfd, NULL)。
* 3. dest_addr=NULL 时不改 DesIP:服务器模式下 DesIP=0xFFFFFFFF 即
* 向 255.255.255.255 广播(板级 UDP 广播测试的用法),模式不变。
* 4. 分块上限 NET_SEND_CHUNK_MAX(1KB 发送 FIFO,TRAP-NET-16),
* 门控 SENBUF_FREE(TRAP-NET-04);单块等待超 NET_SEND_SPIN_MS
* 登记挂起 send、由 net_send_pending_tick 续传 → **netTask 不被
* 一次大报发送占住**(与 TCP 发送同一套机制)。
* 5. 短写:返回值 < len 表示未全部交付(停滞预算 NET_SEND_TIMEOUT_MS
* 用尽),调用方须检查返回值——同 net_send 契约。
* 6. 手册 §9.2.4:UDP 一包最大 1472 字节;本层不做长度上限校验
* (超出只会分多块写、对端收不到完整包),调用方自负。
*/
int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen);
/*
* 函数功能:接收 UDP 数据
* 函数功能:接收 UDP 数据报(含源地址;**走消息队列**,任意任务可调用)
* 入口参数:sockfd - socket 描述符
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0;NET_MSG_DONTWAIT 单次强制非阻塞
* src_addr - 输出:发送方地址
* addrlen - 输入输出:地址长度
* 返回值:实际接收字节数(0 = 无数据可报的边界形态见说明) int >= 0
* len - 缓冲区大小(>0)
* flags - 0 = 按 socket 的 blocking 属性等待;NET_MSG_DONTWAIT = 单次非阻塞
* src_addr - 输出:发送方地址(可为 NULL)
* addrlen - 输入输出:src_addr 结构容量/实际长度(可为 NULL)
* 返回值:实际接收字节数 int >= 0
* -1 失败,精确码经本任务错误码槽给出(net_get_errno()):
* NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_WOULDBLOCK /
* NET_ERR_NOT_IN_NETTASK(调用上下文不对,见「限定条件」)
* 限定条件:**仅限本层执行任务(net_init 的调用者,NET 层 = netTask)调用**;
* socket 已创建且为 DGRAM;指针非空
* 函数说明:1. 读取 CH395F 接收缓冲区,获取对端地址
* 2. 阻塞语义与 net_recv 同:blocking 属性下无数据
* 无限等待,DONTWAIT / 非阻塞属性立即 NET_ERR_WOULDBLOCK;
* 本接口为轮询自旋实现(历史遗留,UDP 专用),受 IWDG 监控的
* 任务同样禁止对其无限等待
* NET_ERR_TIMEDOUT / NET_ERR_CONNRESET(close/重建取消)/ NET_ERR_BUSY
* 限定条件:socket 已创建且为 DGRAM;net_init() 已执行;**任意任务上下文**
* (netTask 内直接执行不入队;netTask 内的阻塞等待被登记守卫拒绝 →
* 须用 NET_MSG_DONTWAIT)
* 函数说明:1. 与 net_recv 同一条路径(入队 → netTask 串行执行芯片事务 →
* 结果搭通知值回来),**不再是**"仅限执行任务、自旋 net_poll"的
* 历史形态;非 netTask 调用不再回 NET_ERR_NOT_IN_NETTASK。
* 2. 一次调用 = 一个数据报:芯片缓冲内该包**全部读空**(信息头形态下
* 手册要求一次性读出),载荷超出 len 的部分按 BSD 语义截断丢弃,
* 不会留下半包污染下一次解析。
* 3. **恒剥 8 字节信息头,源地址恒取自头部**([2-3] 源端口芯片小端、
* [4-7] 源 IP 线序字节 → 本层 s_addr 网络序约定,见 net_inet_addr 注释,
* 2026-10-24 定案同族),**与 udp_mode 无关**:头是 OPEN_SOCKET 那一刻
* 按当时的 DesIP 定死的,本层 Socket 一律由 net_bind 在全 F 形态下开出,
* 之后把 DesIP 改成具体对端并不会让头消失(2026-11-04 08:16 真机
* TC-NET-1603 实测 48 B 载荷可读 56 B、首字节 0x38 = 头里的长度字段;
* 定案见 TRAP-NET-22 追加段)。只有不足 8 字节的残包无从判头,整包按
* 载荷交付、源地址退到本 Socket 的对端记账。
* ⚠ 曾按"客户端形态不剥头"实现过一版,真机第一轮就把 8 字节头当载荷
* 交给了应用——不要再把剥头条件改回跟形态挂钩。
* 4. 阻塞语义与 net_recv 同构:无数据 + 阻塞 → **登记挂起、netTask
* 零占用返回**,数据到点断连由 net_udp_tick 按 1ms 节拍结束;
* 不再是自旋(旧形态会吃满 IWDG 预算并违反 netTask 契约)。
* 5. ⚠ IWDG required_mask 内任务禁止无限等待形态:用
* net_recvfrom_to() 显式预算或 net_select()(同 net_recv 契约)。
*/
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen);
/*
* 函数功能:接收 UDP 数据报(**带调用方指定超时**,对应 net_recv_to 的 UDP 版)
* 入口参数:sockfd - socket 描述符
* buf/len - 接收缓冲与容量
* flags - 目前不使用,传 0(超时由 timeout_ms 表达)
* timeout_ms - 本次等待预算(毫秒,>0)
* src_addr - 输出:发送方地址(可为 NULL)
* addrlen - 输入输出:src_addr 容量/实际长度(可为 NULL)
* 返回值:>0 实际字节数;-1 = 真错误(errno 可查);**-2 = 超时无数据**
* 限定条件:非 netTask 上下文(netTask 内须用 net_recvfrom + NET_MSG_DONTWAIT)
* 函数说明:1. 与 net_recv_to 同一条路径与同一套超时收口:挂起登记带 deadline,
* 到点由 net_udp_tick 以超时结束,对外统一 -2(不靠 errno 区分,
* 无跨任务 errno 竞态)。
* 2. 受 IWDG 监控的任务(含未来的 SNTP/对时任务)**必须**用本接口或
* net_select,不得用 net_recvfrom 的无限等待形态。
* 3. timeout_ms = 0 视为无效参数(无限等待请用 net_recvfrom)。
*/
int net_recvfrom_to(int sockfd, void *buf, int len, uint32_t timeout_ms,
struct net_sockaddr *src_addr, int *addrlen);
/*
* 函数功能:设置/查询 UDP Socket 的芯片对端形态(客户端 ↔ 服务器,见 net_types.h
* NET_UDP_MODE_* 与手册 §9.2.4)——**只管发送去向与对端记账,不改收包帧格式**
* 入口参数:sockfd - socket 描述符(须为 DGRAM)
* peer - 非 NULL = 客户端形态:DesIP/DesPort 设为该对端,此后
* `dest_addr = NULL` 的 net_sendto 就发往它;
* NULL = 服务器形态:DesIP=0xFFFFFFFF、DesPort=0,不带地址的
* 发送不再沿用旧对端(net_bind 的默认形态)
* 返回值:0 - 成功 int
* -1 - 失败(NET_ERR_BADF / NET_ERR_INVAL / NET_ERR_BUSY,errno 可查)
* 限定条件:socket 已创建且为 DGRAM 且已 net_bind(芯片侧 Socket 已打开);
* 任意任务上下文(走消息队列,netTask 内直接执行)
* 函数说明:1. 存在的理由:带目的地址的 net_sendto 发一次就把 DesIP 静默改到了
* 那个对端(TFTP 式"收到谁的包回给谁"必须这么回),回完就得显式切回
* 服务器形态,否则下一次不带地址的发送会悄悄发给上一个对端。
* 本接口是**轻切换**:只写 DesIP/DesPort 两个寄存器、不重开 Socket,
* 因此接收缓冲里已经到的应答不会被清掉(SNTP 这类请求-应答靠它)。
* 2. **两种形态的收包帧格式相同**:本层 Socket 恒在全 F 形态下 open,
* 芯片就恒前置 8 字节信息头,运行时改 DesIP 不改变这一点(真机
* TC-NET-1603 实测,见 TRAP-NET-22 追加段),net_recvfrom 因此恒剥头、
* 恒从头部取源地址。udp_mode 只用于发送去向与诊断判读。
* 3. ⚠ 手册把"只收该 IP 的包"写在客户端形态名下,但**运行时**把 DesIP
* 改窄之后芯片还过不过滤别的源,台架没有测出结论(阶段 16 的 1604 只
* 验到"切回服务器形态后换源端口的新包能收到")。要做多源服务就保持
* 服务器形态、每次发送都带显式目的地址,别把过滤当已证事实。
* 4. 切换不改 local_port,也不影响已登记的挂起 recv;挂起期间切模式由
* tick 在结算时按当前寄存器状态处理(同一包不会两解)。
*/
int net_udp_set_peer(int sockfd, const struct net_sockaddr_in *peer);
/*
* 函数功能:查询 UDP Socket 当前芯片模式
* 入口参数:sockfd - socket 描述符
* 返回值:NET_UDP_MODE_SERVER / NET_UDP_MODE_CLIENT uint8_t
* -1 - 描述符无效或非 DGRAM int
* 限定条件:任意上下文(纯 RAM 读,不发起芯片事务)
* 函数说明:诊断/单测/上层"要不要切回服务器模式"的判读入口
*/
int net_udp_get_mode(int sockfd);
/*
* 函数功能:关闭 socket
* 入口参数:sockfd - socket 描述符
@@ -598,6 +703,18 @@ int net_getpeername(int sockfd, struct net_sockaddr *addr, int *addrlen);
* 内部 API(需在 netTask 上下文中调用,绕过消息队列)
*/
/*
* 函数功能:取下一个动态端口并推进游标(49152~65535 回绕,内部使用)
* 入口参数:无
* 返回值:分配到的端口(主机序) uint16_t
* 限定条件:单线程语义——TCP 绑定路径(调用方上下文)与 UDP 绑定执行体
* (netTask)都经此取号,簿记只住 net_socket.c 一处
* 函数说明:原逻辑内联在 net_bind 里;UDP 队列化后绑定动作挪进 netTask 执行,
* 两处都要用同一游标,故抽成函数(同一事实只在一处)。
* 不做"是否已被其他 Socket 占用"检查——与抽取前行为一致。
*/
uint16_t net_dynamic_port_next(void);
/*
* 函数功能:发送数据(内部版本,直接操作,不经过消息队列)
* 入口参数:p_sock - Socket 控制块指针
@@ -634,6 +751,33 @@ int net_close_sock(net_sock_t *p_sock);
* 限定条件:必须在 netTask 上下文中调用
*/
int net_listen_locked(int sockfd);
/*
* 函数功能:判当前上下文是否本层执行任务(netTask / 硬件层的 testTask)
* 入口参数:无
* 返回值:1 - 是本层执行任务(或 net_init 前未捕获);0 - 明确不是 uint8_t
* 限定条件:net_init 之后判据才有效;任意上下文可读
* 函数说明:队列化接口用本判据决定"直接执行"还是"投递给 netTask"——执行任务
* 自己不能往自己的队列里投(要等自己回来泵,队列满即自等死锁);
* 挂起登记前也用它确认"完成动作有人做"。判据与留痕见定义处注释。
*/
uint8_t net_ctx_is_owner_task(void);
/*
* 函数功能:发送数据(内部锁定版本,TCP 与 UDP 共用同一份分块实现)
* 入口参数:p_sock - Socket 控制块指针
* buf - 数据缓冲区
* len - 数据长度
* caller - 挂起 send 的完成通知目标(执行任务自身传 NULL 即拒登记)
* 返回值:实际发送字节数(>= 0);NET_PENDING_REGISTERED = 已挂起;-1 = 错误
* 限定条件:必须在 netTask 上下文中调用
* 函数说明:UDP 队列化后 net_udp_sendto_exec 也走本函数——分块粒度
* (NET_SEND_CHUNK_MAX)、SENBUF_FREE 门控、末块不等、停滞超时、
* 挂起 send 表全部只有一份(TRAP-NET-16 的复发形态就是"两条路径
* 各写一套分块自旋",故不复制)。状态门按 type 区分:TCP 须
* ESTABLISHED,UDP 须 UDP_OPEN。
*/
int net_send_locked(net_sock_t *p_sock, const void *buf, int len, TaskHandle_t caller);
#endif /* NET_NO_OS */
#ifdef __cplusplus
+39
View File
@@ -50,6 +50,31 @@ extern "C" {
#define NET_MSG_DONTWAIT 0x01 /* 非阻塞模式(单次覆盖 socket 的 blocking 属性;
* 具名入口见 net_recv_nb() / net_setblocking()) */
/*
* UDP 芯片对端形态(仅 type == NET_SOCK_DGRAM 有意义;手册 §9.2.4:目的 IP 决定
* 芯片的收/发对端约束。两种形态的差别**只在过滤与发送去向**,收包帧格式不在其中,
* 见下第 3 条——这一条是 2026-11-04 真机量出来的,不是推的。)
*
* 服务器形态 DesIP = 0xFFFFFFFF(net_bind 的 UDP 分支一律开在这个形态):
* 芯片不按源过滤,可与任意远端通讯;CH395F 在**每个**收到的数据报前添加
* 8 字节信息头([0-1] 数据长度、[2-3] 源端口(芯片小端)、[4-7] 源 IP
* (线序字节,首段在低地址)),且手册要求一次性把该包全部读出。
* 客户端形态 DesIP = 具体 IP:芯片只收该 IP 的包,发送也固定走它。
*
* ⚠ 信息头是否在,取决于**开 Socket 那一刻**的 DesIP,不取决于之后的 DesIP:
* 运行时把 DesIP 从 0xFFFFFFFF 改成具体 IP,头并不会消失。2026-11-04 08:16
* 真机 TC-NET-1603 实测——客户端形态下对端发来 48 B,芯片报可读 56 B、首字节
* 0x38(=56,正是信息头的长度字段)。所以本层 recvfrom **恒剥 8 字节**、
* 恒从信息头取源地址;曾经"按 udp_mode 决定剥不剥"的写法会把 8 字节头当载荷
* 交给应用(SNTP 48 字节应答只剩 40 字节且前 8 字节是头),那正是
* docs/问题记录/CH395F_Trap_Records.md「TRAP-NET-22」记录的老缺陷的镜像面:
* 改造前恒剥头会在真客户端 Socket 上吃 8 字节,改造初版按运行时形态判剥不剥
* 则在服务器形态开出的 Socket 上多交 8 字节。两头都要靠"开 Socket 时定形态"
* 这一条事实来对齐,本字段只记账发送去向与源过滤。
*/
#define NET_UDP_MODE_SERVER 0U /* DesIP=0xFFFFFFFF:芯片不按源过滤收包,发送走信息头/显式目的 */
#define NET_UDP_MODE_CLIENT 1U /* DesIP=具体 IP:芯片只收该对端、发送也走它;信息头仍在(帧格式看 udp_mode 字段注释) */
/*
* 网络字节序转换宏
*/
@@ -167,6 +192,20 @@ typedef struct {
net_sock_state_t state; /* Socket 状态 */
uint8_t type; /* SOCK_STREAM / SOCK_DGRAM */
uint8_t in_use; /* 是否在用标志 */
/* UDP 芯片对端形态(NET_UDP_MODE_SERVER / _CLIENT,宏注释见本文件 UDP 模式定义处)。
* 仅 type == NET_SOCK_DGRAM 有意义;置位/清零路径:net_bind 的 UDP 分支 →
* SERVER(DesIP=0xFFFFFFFF);net_sendto 带显式目的地址、net_udp_set_peer(非
* NULL) → CLIENT(DesIP=该 IP);net_udp_set_peer(NULL) → 回 SERVER。
* 新 fd / close 由整块 memset 归零 = SERVER,与 bind 默认同值,故不得继承上一条。
* ⚠ 这个字段**只描述芯片的对端过滤与发送去向,不描述收包帧格式**:本层 Socket
* 一律在 DesIP=0xFFFFFFFF 时开出,芯片就固定给每个收到的数据报前置 8 字节信息
* 头,运行时改 DesIP 不会让头消失(2026-11-04 08:16 真机 TC-NET-1603 实测
* n=56 / 首字节 0x38,见 TRAP-NET-22 追加段)。因此 recvfrom 一律剥头、一律从
* 头里取源地址;此处 CLIENT 的对端簿记(复用 remote_ip_arr[4] + remote_port,
* 与 TCP 同一对字段)只用于发送去向与不足 8 字节残包的源地址兜底。
* 位置选择:紧接 in_use 之后,落在 type/in_use 之后原有的 2 字节填充里
* (本结构 8 个实例,净增 RAM = 0,见 AGENTS RAM 红线)。 */
uint8_t udp_mode;
uint16_t local_port; /* 本地端口(主机序) */
uint16_t remote_port; /* 远端端口(主机序) */
uint32_t remote_ip; /* 远端 IP(网络字节序) */
+774
View File
@@ -0,0 +1,774 @@
/*
* 模块名称:UDP 数据面执行器与挂起槽(netTask 串行化)
* 文件说明:UDP 队列化改造(2026-11-03)的执行侧。公开 API 在 net_socket.c 里只做
* "校验参数 → 装 net_msg_t 入队 → 等任务通知值",真正的芯片事务全部
* 在本文件的 *_exec 函数里执行(netTask 上下文,与 TCP 同一根 SPI 的
* 唯一持有者)。源地址出参经本文件的 per-fd 槽位回到调用方内存。
* 架构位置:docs/网络/BSD_Socket_API_使用指南.md §6(UDP 两种芯片模式)+
* docs/网络/Net层阻塞模型架构设计.md(挂起模型与通知值回传)
* 依赖:net_socket.h(net_get_sock / net_poll / net_set_errno / 动态端口)、
* net_pending.h(NET_PENDING_REGISTERED / NET_SEND_CHUNK_MAX / 挂起 send 表)、
* ch395f.h(芯片命令)、main.h(HAL_GetTick)
* 作者:super_agent_bot
* 创建日期:2026-11-03
* 修改记录:
* 2026-11-03 super_agent_bot 创建:UDP 队列化改造(SNTP 客户端的前置)
*/
#include <stddef.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "net_types.h"
#include "net_socket.h"
#include "net_udp.h"
#include "net_pending.h"
#include "ch395f.h"
#include "dbg_log.h"
#include "main.h"
/*
* 私有常量定义区
*/
/* 服务器模式信息头长度(手册 §9.2.4:[0-1] 数据长度、[2-3] 源端口、[4-7] 源 IP) */
#define NET_UDP_HDR_LEN 8U
/* 源端口在信息头内的偏移 */
#define NET_UDP_HDR_PORT_OFF 2U
/* 源 IP 在信息头内的偏移 */
#define NET_UDP_HDR_IP_OFF 4U
/* 截断数据报的排空粒度(字节):读空不丢后续包,只在调用方缓冲偏小时走这条路 */
#define NET_UDP_DRAIN_CHUNK 64U
/*
* 私有类型定义区
*/
/*
* 一次 UDP 接收请求的槽位(每 fd 至多一个)
* armed = 1:调用方已登记源地址出参(消息可能还在队列里,也可能正在执行)
* pending = 1:已登记挂起等待(数据/到点/关闭三者之一将结束它)
* 两个标志的差别是"谁欠调用方一个回执":armed 期间回执还没发;pending 期间
* 连即时回执都还没生成,结束动作由 net_udp_tick 负责。
*/
typedef struct {
uint8_t armed; /* 1 = 槽位被占用(禁止第二个接收请求入队) */
uint8_t pending; /* 1 = 挂起等待中 */
struct net_sockaddr *src_addr; /* 调用方源地址出参(可为 NULL) */
int *addrlen; /* 调用方长度出参(可为 NULL) */
void *buf; /* 载荷缓冲(调用方持有,须有效至回执) */
int len; /* 载荷缓冲容量 */
uint32_t deadline; /* 结束时刻(0 = 无限等待,同 net_pending 约定) */
TaskHandle_t caller; /* 回执目标 */
} net_udp_req_t;
/*
* 私有变量定义区
*/
/* per-fd 接收槽位表:与 net_pending(TCP recv)/ net_send_pending 各自独立,
* 同一 fd 上三者互不阻塞(UDP 只用本表 + send 表) */
static net_udp_req_t s_net_udp[NET_MAX_SOCKETS];
/*
* 私有函数声明区
*/
static void net_udp_src_from_header(net_sock_t *p_sock, const uint8_t *hdr,
struct net_sockaddr *src_addr, int *addrlen);
static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_addr,
int *addrlen);
static int net_udp_datagram_read(int sockfd, net_sock_t *p_sock, void *buf, int len,
struct net_sockaddr *src_addr, int *addrlen,
int avail);
static void net_udp_req_complete(int sockfd, int result);
/*
* 公共函数实现(调用方上下文)
*/
/*
* 函数功能:登记本次接收的源地址出参(调用方上下文,入队前)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* src_addr - 源地址出参指针(可为 NULL) struct net_sockaddr *
* addrlen - 出参长度指针(可为 NULL) int *
* 出口参数:无
* 返回值:0 - 成功;-1 - 失败(NET_ERR_BADF / NET_ERR_BUSY) int
* 限定条件:任意上下文
* 函数说明:临界区只包"查占位"三步,不碰芯片;占位成功后本 fd 的第二个接收
* 请求被拒(BUSY)。同一 fd 的并发接收在数据报语义上无法区分归属,
* 拒绝比交错投递安全(TCP 挂起 recv 同一守卫,见 net_pending_register)。
*/
int net_udp_outparam_arm(int sockfd, struct net_sockaddr *src_addr, int *addrlen) {
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
taskENTER_CRITICAL();
if (s_net_udp[sockfd].armed != 0U) {
taskEXIT_CRITICAL();
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
s_net_udp[sockfd].armed = 1U;
s_net_udp[sockfd].src_addr = src_addr;
s_net_udp[sockfd].addrlen = addrlen;
taskEXIT_CRITICAL();
return 0;
}
/*
* 函数功能:归还源地址出参槽(调用方上下文,入队失败这条出口)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无
* 限定条件:任意上下文;仅在未拿到 netTask 回执的失败路径调用
* 函数说明:netTask 的结算路径自己清槽(net_udp_req_complete),本函数只服务
* 调用方侧"消息没投出去"的清理,防槽位泄漏把该 fd 永久钉成 BUSY。
*/
void net_udp_outparam_disarm(int sockfd) {
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
taskENTER_CRITICAL();
s_net_udp[sockfd].armed = 0U;
s_net_udp[sockfd].pending = 0U;
s_net_udp[sockfd].src_addr = NULL;
s_net_udp[sockfd].addrlen = NULL;
s_net_udp[sockfd].buf = NULL;
s_net_udp[sockfd].len = 0;
s_net_udp[sockfd].deadline = 0U;
s_net_udp[sockfd].caller = NULL;
taskEXIT_CRITICAL();
}
/*
* 函数功能:清除本 fd 的 UDP 接收槽(不通知)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无
* 限定条件:任意上下文(netTask 的 close/net_socket 分配路径调用)
* 函数说明:与 net_pending_clear 同一契约——"新 fd 不得继承上一条连接的挂起",
* 调用方侧通知已在各 settle/cancel 路径发过,这里只做幂等归零。
*/
void net_udp_clear(int sockfd) {
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
(void)memset(&s_net_udp[sockfd], 0, sizeof(s_net_udp[sockfd]));
}
/*
* 公共函数实现(netTask 上下文)
*/
/*
* 函数功能:UDP 绑定/打开芯片侧 Socket(服务器模式)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* local_port - 本地(源)端口,主机序;0 = 分配动态端口 uint16_t
* 出口参数:无
* 返回值:0 - 成功;-1 - 失败(NET_ERR_BADF/INVAL/BUSY,errno 已记) int
* 限定条件:netTask 上下文;控制块在用且为 DGRAM
* 函数说明:1. 命令顺序按手册 §9.2.4:SET_PROTO_TYPE → SET_SOUR_PORT →
* SET_DES_IP(0xFFFFFFFF) → SET_DES_PORT(0) → OPEN_SOCKET。
* 目的地址必须在 OPEN 之前下发——open 会复位端口/IP 寄存器
* (docs/网络/BSD_Socket_API_函数调用关系.md §4)。
* 2. DesIP=0xFFFFFFFF 即服务器模式:收任意来源,芯片给每个包加
* 8 字节信息头(net_types.h UDP 模式宏注释)。
* 3. BSD 的 bind 只表达本地地址,**不选对端**,故本函数恒为服务器模式;
* 客户端模式由 net_sendto(带目的地址) 或 net_udp_set_peer 进入。
* 4. 重绑(同一 fd 二次 bind)会重开芯片侧 Socket:这是"从客户端模式
* 回到服务器模式"的完整形态(比 net_udp_set_peer 更重,连缓冲与
* 端口一起重来),接收缓冲内未读数据随之作废——调用方自选。
* 作废不等于可以不吭声:重开之前先把本 fd 的挂起收包按 CONNRESET
* 结算并通知,否则"另一个任务重绑 + 本任务无限等包"= 永久睡死。
* 5. 动态端口在 netTask 内取号(原在调用方上下文改 s_dynamic_port)。
*/
int net_udp_bind_exec(int sockfd, uint16_t local_port) {
/* 控制块 */
net_sock_t *p_sock = net_get_sock(sockfd);
/* open_socket 返回状态 */
uint8_t status;
/* 最终端口(主机序) */
uint16_t port = local_port;
/* 服务器模式 DesIP(线序字节:首段在低地址) */
static const uint8_t s_des_ip_any[4] = {0xFFU, 0xFFU, 0xFFU, 0xFFU};
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
if (p_sock->type != NET_SOCK_DGRAM) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
/* 重开芯片侧会丢掉接收缓冲里已到的数据报:本 fd 若正有人在等包,必须在动手
* 之前先给一个明确的了断(CONNRESET + 通知)。少了这一步,"A 任务 net_recvfrom
* 无限等 + B 任务重新 bind 同一 fd"就是永久睡死——本函数末尾的 net_udp_clear
* 只归零状态、不通知,那是兜底,不是结算。 */
net_udp_req_complete(sockfd, NET_ERR_CONNRESET);
/* net_init 已为全部 8 个 Socket 分配缓冲;此处仅对 Socket 4~7 兜底重设,
* 布局与 net_init 一致(固定 6 块/Socket,与 MSS 解耦) */
if (sockfd >= 4) {
/* 每 Socket 块数 = 接收 + 发送 */
uint8_t blocks_per_socket = (uint8_t)(CH395F_RECV_BLOCKS + CH395F_SEND_BLOCKS);
/* 本 Socket 接收缓冲起始块 */
uint8_t recv_start = (uint8_t)(sockfd * blocks_per_socket);
/* 发送缓冲起始块(接收块之后) */
uint8_t send_start = (uint8_t)(recv_start + CH395F_RECV_BLOCKS);
ch395f_set_send_buf((uint8_t)sockfd, send_start, (uint8_t)CH395F_SEND_BLOCKS);
ch395f_set_recv_buf((uint8_t)sockfd, recv_start, (uint8_t)CH395F_RECV_BLOCKS);
}
/* 协议类型 = UDP */
ch395f_set_proto_type((uint8_t)sockfd, CH395F_PROTO_TYPE_UDP);
/* 端口 0 = 请本层分配 */
if (port == 0U) {
port = net_dynamic_port_next();
}
p_sock->local_port = port;
ch395f_set_sour_port((uint8_t)sockfd, port);
/* DesIP=0xFFFFFFFF + DesPort=0:服务器模式(不限对端) */
ch395f_set_des_ip((uint8_t)sockfd, (uint8_t *)s_des_ip_any);
ch395f_set_des_port((uint8_t)sockfd, 0U);
status = ch395f_open_socket((uint8_t)sockfd);
if (status != CH395F_ERR_SUCCESS) {
DBG_ERROR("udp sock%d open FAIL status=0x%02X", sockfd, status);
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 打开即可用(UDP 无监听态) */
p_sock->state = NET_SOCK_STATE_UDP_OPEN;
/* 模式与对端簿记复位:重开后的芯片就是服务器模式,软件账本必须同步 */
p_sock->udp_mode = NET_UDP_MODE_SERVER;
(void)memset(p_sock->remote_ip_arr, 0, sizeof(p_sock->remote_ip_arr));
p_sock->remote_port = 0U;
p_sock->remote_ip = 0U;
/* 重开不得继承上一条连接的电平 */
p_sock->readable = 0U;
/* 本 fd 的接收槽兜底归零(上方的 req_complete 已把"重开前还在等包"的调用方结算掉) */
net_udp_clear(sockfd);
return 0;
}
/*
* 函数功能:切换 UDP 芯片模式(改 DesIP/DesPort,不重开 Socket)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* peer - 非 NULL = 客户端模式对端;NULL = 回服务器模式 const struct net_sockaddr_in *
* 出口参数:无
* 返回值:0 - 成功;-1 - 失败(NET_ERR_BADF/INVAL/NOTCONN,errno 已记) int
* 限定条件:netTask 上下文;控制块在用、type == DGRAM、state == UDP_OPEN
* 函数说明:语义见 net_socket.h net_udp_set_peer。open 之后改这两个寄存器
* 是否立即生效已有实证:net_sendto 一直是"发之前才写 DesIP",
* 芯片据此就换模式(docs/问题记录/CH395F_Trap_Records.md「UDP 模式」条)。
*/
int net_udp_set_peer_exec(int sockfd, const struct net_sockaddr_in *peer) {
/* 控制块 */
net_sock_t *p_sock = net_get_sock(sockfd);
/* 目的 IP 线序字节 */
uint8_t ip_arr[4];
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
if (p_sock->type != NET_SOCK_DGRAM) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
if (p_sock->state != NET_SOCK_STATE_UDP_OPEN) {
/* 芯片侧 Socket 还没打开:先 net_bind(否则 open 会把这里设的值复位) */
return NET_ERR_RETURN_1(NET_ERR_NOTCONN);
}
if (peer != NULL) {
/* 客户端模式:对端 IP 按内存顺位取(s_addr 已是网络序 = 首段在低地址) */
(void)memcpy(ip_arr, &peer->sin_addr.s_addr, sizeof(ip_arr));
ch395f_set_des_ip((uint8_t)sockfd, ip_arr);
ch395f_set_des_port((uint8_t)sockfd, net_ntohs(peer->sin_port));
p_sock->udp_mode = NET_UDP_MODE_CLIENT;
(void)memcpy(p_sock->remote_ip_arr, ip_arr, sizeof(ip_arr));
p_sock->remote_port = net_ntohs(peer->sin_port);
p_sock->remote_ip = peer->sin_addr.s_addr;
} else {
/* 服务器模式:DesIP=0xFFFFFFFF、DesPort=0(与 bind 同值) */
ip_arr[0] = 0xFFU;
ip_arr[1] = 0xFFU;
ip_arr[2] = 0xFFU;
ip_arr[3] = 0xFFU;
ch395f_set_des_ip((uint8_t)sockfd, ip_arr);
ch395f_set_des_port((uint8_t)sockfd, 0U);
p_sock->udp_mode = NET_UDP_MODE_SERVER;
(void)memset(p_sock->remote_ip_arr, 0, sizeof(p_sock->remote_ip_arr));
p_sock->remote_port = 0U;
p_sock->remote_ip = 0U;
}
return 0;
}
/*
* 函数功能:UDP 发送执行体(分块 + 有界自旋 + 挂起 send 续传)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 载荷(调用方内存) const void *
* len - 载荷长度(>0) int
* dest - 本包目的地址;NULL = 不改 DesIP const struct net_sockaddr_in *
* caller - 调用方任务句柄(挂起 send 通知目标) TaskHandle_t
* 出口参数:无
* 返回值:实际发送字节数(>= 0);NET_PENDING_REGISTERED;-1 = 失败 int
* 限定条件:netTask 上下文;控制块在用且已 bind(state == UDP_OPEN)
* 函数说明:目的地址簿记 + 模式置位在本函数做完,之后把分块/等待/挂起全部
* 交给 net_send_locked——分块粒度、SENBUF_FREE 门控、末块不等、
* 短写与挂起 send 表都与 TCP 共用同一份实现,不留第二份自旋
* (TRAP-NET-16 的复发形态正是"两条路径各写一套分块")。
*/
int net_udp_sendto_exec(int sockfd, const void *buf, int len,
const struct net_sockaddr_in *dest, TaskHandle_t caller) {
/* 控制块 */
const net_sock_t *p_sock = net_get_sock(sockfd);
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
if ((buf == NULL) || (len <= 0)) {
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
if (p_sock->state != NET_SOCK_STATE_UDP_OPEN) {
return NET_ERR_RETURN_1(NET_ERR_NOTCONN);
}
if (dest != NULL) {
/* 带显式目的地址:芯片 DesIP 切到该 IP → 客户端模式(硬件行为,如实记账) */
struct net_sockaddr_in peer;
int rc;
(void)memset(&peer, 0, sizeof(peer));
peer.sin_family = NET_AF_INET;
peer.sin_port = dest->sin_port;
peer.sin_addr.s_addr = dest->sin_addr.s_addr;
rc = net_udp_set_peer_exec(sockfd, &peer);
if (rc != 0) {
return rc;
}
}
/* 分块发送(含挂起 send 登记)与 TCP 同一实现。
* 必须走 net_send_locked 而不是 net_send_sock:后者把 caller 写成自己
* (netTask),挂起登记会被"执行者自等待"守卫拒成 BUSY,真正的调用方
* 就永远等不到续传完成的通知。 */
return net_send_locked(net_get_sock(sockfd), buf, len, caller);
}
/*
* 函数功能:UDP 接收执行体(即时读 / 登记挂起 / 立即拒)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 调用方缓冲 void *
* len - 缓冲容量(>0) int
* flags - NET_MSG_DONTWAIT = 单次非阻塞 int
* to_ms - 等待预算(0 = 无限等待) uint32_t
* caller - 调用方任务句柄(挂起完成通知目标) TaskHandle_t
* 出口参数:无(源地址经槽位里的调用方指针直接写回)
* 返回值:实际接收字节数(> 0);NET_PENDING_REGISTERED;-1 = 失败(errno 已记) int
* 限定条件:netTask 上下文;控制块在用、type == DGRAM、state == UDP_OPEN
* 函数说明:1. 一次调用 = 一个完整数据报(剥头 + 截断排空),见 net_udp_datagram_read。
* 2. 无数据 + 阻塞 → 登记挂起(复用本 fd 已 arm 的槽位),netTask 零占用
* 返回;完成条件由 net_udp_tick 按 1ms 主循环节拍判定。
* 3. 执行任务自身(caller == netTask)的阻塞等待被登记守卫拒绝——
* tick 就跑在执行任务里,自等自通知 = 死锁(同 net_pending_register)。
* 4. 槽位是调用方在 arm 之后才把消息投进来的,所以本函数**每一条负向
* 出口都必须归还槽位**(含参数/形态拒绝):漏一条,这条 fd 就永久
* BUSY,且报错还是从下一条 recvfrom 才冒出来(2026-11-03 补测抓到,
* 当时 NOTCONN 这条没清)。唯一清不掉的是 fd 越界(p_sock == NULL),
* 那时连槽都不存在。
*/
int net_udp_recvfrom_exec(int sockfd, void *buf, int len, int flags,
uint32_t to_ms, TaskHandle_t caller) {
/* 控制块 */
net_sock_t *p_sock = net_get_sock(sockfd);
/* 本 fd 接收槽 */
net_udp_req_t *p;
/* 芯片侧可用字节 */
int avail;
if (p_sock == NULL) {
return NET_ERR_RETURN_1(NET_ERR_BADF);
}
/* ⚠ 取槽指针必须排在 fd 合法性检查之后:s_net_udp[] 只有 NET_MAX_SOCKETS 项,
* 在越界 fd 上先取地址 = 越界指针(哪怕还没解引用,后续登记就是野写)。 */
p = &s_net_udp[sockfd];
if (p_sock->type != NET_SOCK_DGRAM) {
/* 面向流的 recv 才是 TCP 的入口 */
net_udp_clear(sockfd);
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
if ((buf == NULL) || (len <= 0)) {
net_udp_clear(sockfd);
return NET_ERR_RETURN_1(NET_ERR_INVAL);
}
if (p_sock->state != NET_SOCK_STATE_UDP_OPEN) {
/* 芯片侧 Socket 还没开:先 net_bind */
net_udp_clear(sockfd);
return NET_ERR_RETURN_1(NET_ERR_NOTCONN);
}
/* 刷新状态机与可读/可写标志(与 net_recv_locked 同一入口动作) */
net_poll();
avail = (int)ch395f_get_recv_len((uint8_t)sockfd);
if (avail > 0) {
/* 数据已在:读出这一个数据报并立即回执 */
int rd = net_udp_datagram_read(sockfd, p_sock, buf, len, p->src_addr, p->addrlen,
avail);
net_udp_clear(sockfd);
return rd;
}
/* 非阻塞:DONTWAIT 单次覆盖或 blocking 属性为 0(与 TCP recv 同判据) */
if (((flags & NET_MSG_DONTWAIT) != 0) || (p_sock->blocking == 0U)) {
net_udp_clear(sockfd);
return NET_ERR_RETURN_1(NET_ERR_WOULDBLOCK);
}
/* 执行任务自身:无他人可完成本挂起 → 拒绝(自等死锁守卫,同
* net_pending_register 的 `caller == s_owner_task` 判据)。
* ⚠ 这里**不能**用 net_ctx_is_owner_task():那个谓词是给"调用方选路"用的,
* 而本函数按设计就跑在执行任务里——用它等于把每一条经队列投进来的正常
* 请求都当成自等拒掉(2026-11-03 主机单测 UDP-8a 当场抓到)。
* 执行侧要问的是"这条请求的调用方是不是正在执行的我",与"我在不在执行
* 任务里"是两个问题;直接路径由 net_recvfrom_impl 传进当前句柄,仍然命中。 */
if ((caller == NULL) || (caller == xTaskGetCurrentTaskHandle())) {
net_udp_clear(sockfd);
return NET_ERR_RETURN_1(NET_ERR_BUSY);
}
/* 登记挂起:载荷与出参已在槽内,补上预算与通知目标 */
p->pending = 1U;
p->buf = buf;
p->len = len;
p->deadline = (to_ms == 0U) ? 0U : (HAL_GetTick() + to_ms);
p->caller = caller;
return NET_PENDING_REGISTERED;
}
/*
* 函数功能:挂起 UDP 接收完成检查(netTask 每轮调用一次,非阻塞)
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:逐 fd 按序 ①fd 失效/不再是已打开的 UDP → CONNRESET ②数据到达 →
* 读包 + 回填源地址 + 字节数 ③到点(仅显式预算)→ TIMEDOUT。
* 三条出口共用 net_udp_req_complete(必通知,漏通知 = 调用方永久挂死)。
*/
void net_udp_tick(net_sock_t *p_socks) {
/* 当前 tick */
uint32_t now = HAL_GetTick();
/* 循环索引 */
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
/* 本 fd 接收槽 */
net_udp_req_t *p = &s_net_udp[i];
/* 本 fd 控制块 */
net_sock_t *p_sock = &p_socks[i];
if (p->pending == 0U) {
continue;
}
/* ① fd 已失效或本 Socket 不再是打开的 UDP(close/重建/被复位) */
if ((p_sock->in_use == 0U) ||
(p_sock->type != NET_SOCK_DGRAM) ||
(p_sock->state != NET_SOCK_STATE_UDP_OPEN)) {
net_udp_req_complete(i, NET_ERR_CONNRESET);
continue;
}
/* ② 数据到达:芯片缓冲有包即完成(readable 只是省 SPI 的门,
* 电平型 RECV_OK 每轮重读会重新置位,事件未收到也能自愈) */
if (p_sock->readable != 0U) {
int avail = (int)ch395f_get_recv_len((uint8_t)i);
if (avail > 0) {
int rd = net_udp_datagram_read(i, p_sock, p->buf, p->len,
p->src_addr, p->addrlen, avail);
net_udp_req_complete(i, rd);
continue;
}
/* 可读标志是陈旧电平(包已被别的路径读空):清掉,别每轮白问一次 SPI */
p_sock->readable = 0U;
}
/* ③ 到点(仅显式预算;deadline = 0 表示无限等待,不参与判定) */
if ((p->deadline != 0U) && ((int32_t)(now - p->deadline) >= 0)) {
net_udp_req_complete(i, NET_ERR_TIMEDOUT);
}
}
}
/*
* 函数功能:取消全部挂起 UDP 接收并逐一通知(网络重建/复位路径)
* 入口参数:err - 通知的错误码 int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
*/
void net_udp_cancel_all(int err) {
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
net_udp_req_complete(i, err);
}
}
/*
* 函数功能:关闭前结算本 fd 的挂起 UDP 接收(有数据先送达,无数据则取消)
* 入口参数:sockfd - 将被关闭的描述符 (0~7) int
* p_sock - 将被关闭的控制块指针 net_sock_t *
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文;须在 ch395f 关闭/复位该 Socket 之前调用
* 函数说明:与 net_pending_settle_close 同一契约——关闭不丢已到达数据。
* 送达走同一条剥头/截断路径,源地址照常回填,调用方拿到的是一个
* 完整数据报而不是被关闭流程截走的半包。
*/
void net_udp_settle_close(int sockfd, net_sock_t *p_sock) {
/* 本 fd 接收槽 */
net_udp_req_t *p;
/* 芯片侧可用字节 */
int avail;
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
p = &s_net_udp[sockfd];
if (p->pending == 0U) {
return;
}
avail = (int)ch395f_get_recv_len((uint8_t)sockfd);
if (avail > 0) {
int rd = net_udp_datagram_read(sockfd, p_sock, p->buf, p->len,
p->src_addr, p->addrlen, avail);
net_udp_req_complete(sockfd, rd);
return;
}
net_udp_req_complete(sockfd, NET_ERR_CONNRESET);
}
/*
* 私有函数实现
*/
/*
* 函数功能:统一出口——通知调用方并归还本 fd 接收槽
* 入口参数:sockfd - Socket 描述符 (0~7) int
* result - 结果原值(> 0 字节数;负 = 错误码,搭通知值回传) int
* 出口参数:无
* 返回值:无(槽位未在位时静默返回,防重复通知)
* 限定条件:netTask 上下文
* 函数说明:与 net_pending_complete 同构:通知值 32 位整体即结果,
* 错误码由调用方侧 NET_ERR_RETURN_1 记入**自己**的 errno 槽位,
* 不存在跨任务 errno 竞态。
*/
static void net_udp_req_complete(int sockfd, int result) {
/* 本 fd 接收槽 */
net_udp_req_t *p;
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return;
}
p = &s_net_udp[sockfd];
if (p->pending == 0U) {
return;
}
xTaskNotify(p->caller, (uint32_t)result, eSetValueWithOverwrite);
net_udp_clear(sockfd);
}
/*
* 函数功能:从 8 字节信息头回填源地址(本层收包的正常路径)
* 入口参数:p_sock - 控制块(本函数不用,保留与 peer 版同形签名) net_sock_t *
* hdr - 已读出的 8 字节信息头 const uint8_t *
* src_addr - 调用方出参(可为 NULL) struct net_sockaddr *
* addrlen - 调用方出参长度(可为 NULL) int *
* 出口参数:src_addr - 填充发送方地址;*addrlen = sizeof(sockaddr_in)
* 返回值:无
* 限定条件:由 net_udp_datagram_read 在剥头分支(avail >= 8)调用——本层 Socket
* 一律开在服务器形态,信息头与 udp_mode 无关,故 CLIENT 形态同样走这里,
* 头里的源 IP/端口就是本包的真实来源
* 函数说明:头布局(手册 §9.2.4)[0-1] 数据长度、[2-3] 源端口(芯片小端)、
* [4-7] 源 IP(线序字节,[4] = 第一段)。s_addr 直接按"首段落在低字节"
* 组装即得本层网络序约定(2026-10-24 定案:全仓 s_addr 只此一族,
* 与 net_inet_addr / net_getpeername 同族,可直接相等比较),
* **不能再加一次换端**。
*/
static void net_udp_src_from_header(net_sock_t *p_sock, const uint8_t *hdr,
struct net_sockaddr *src_addr, int *addrlen) {
/* 可写判定:两个出参都到位且调用方给的容量足够 */
int writable;
(void)p_sock;
writable = ((src_addr != NULL) && (addrlen != NULL) &&
(*addrlen >= (int)sizeof(struct net_sockaddr_in))) ? 1 : 0;
if (writable == 0) {
return;
}
{
/* IPv4 视图 */
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
p_addr_in->sin_family = NET_AF_INET;
/* 源端口:芯片小端 → 主机序 → 本层网络序字段 */
p_addr_in->sin_port = net_htons((uint16_t)((uint16_t)hdr[NET_UDP_HDR_PORT_OFF] |
((uint16_t)hdr[NET_UDP_HDR_PORT_OFF + 1U] << 8)));
p_addr_in->sin_addr.s_addr = ((uint32_t)hdr[NET_UDP_HDR_IP_OFF]) |
((uint32_t)hdr[NET_UDP_HDR_IP_OFF + 1U] << 8) |
((uint32_t)hdr[NET_UDP_HDR_IP_OFF + 2U] << 16) |
((uint32_t)hdr[NET_UDP_HDR_IP_OFF + 3U] << 24);
*addrlen = (int)sizeof(struct net_sockaddr_in);
}
}
/*
* 函数功能:从控制块固定对端回填源地址(不足 8 字节残包的兜底路径)
* 入口参数:p_sock - 控制块(remote_ip_arr/remote_port = 本 Socket 的对端) net_sock_t *
* src_addr - 调用方出参(可为 NULL) struct net_sockaddr *
* addrlen - 调用方出参长度(可为 NULL) int *
* 出口参数:src_addr - 填充对端地址;*addrlen = sizeof(sockaddr_in)
* 返回值:无
* 限定条件:仅由 net_udp_datagram_read 在残包分支(avail < 8)调用
* 函数说明:本层 Socket 一律开在服务器形态、每个数据报都带信息头,正常包都从
* 头里取源地址;只有无从判头的残包才退到"源地址=本 Socket 的对端记账",
* 至少比报 NULL 或报上一个包的源更接近真相(TC-NETSOCK-440o 定案)。
*/
static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_addr,
int *addrlen) {
int writable;
writable = ((src_addr != NULL) && (addrlen != NULL) &&
(*addrlen >= (int)sizeof(struct net_sockaddr_in))) ? 1 : 0;
if (writable == 0) {
return;
}
{
/* IPv4 视图 */
struct net_sockaddr_in *p_addr_in = (struct net_sockaddr_in *)src_addr;
p_addr_in->sin_family = NET_AF_INET;
p_addr_in->sin_port = net_htons(p_sock->remote_port);
/* 对端字节已是线序数组,按内存顺位拷进 s_addr 即网络序整数 */
(void)memcpy(&p_addr_in->sin_addr.s_addr, p_sock->remote_ip_arr,
sizeof(p_addr_in->sin_addr.s_addr));
*addrlen = (int)sizeof(struct net_sockaddr_in);
}
}
/*
* 函数功能:从芯片缓冲读出**一个完整数据报**(剥头 + 截断排空 + 清可读标志)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* p_sock - 控制块(只在不足 8 字节的残包上用来取对端记账当源地址) net_sock_t *
* buf - 调用方缓冲 void *
* len - 缓冲容量(> 0) int
* src_addr - 源地址出参(可为 NULL) struct net_sockaddr *
* addrlen - 源地址出参长度(可为 NULL) int *
* avail - 调用方刚从芯片读到的可读字节数(> 0) int
* 出口参数:buf - 载荷(最多 len 字节,多余部分丢弃);src_addr - 发送方地址
* 返回值:交付调用方的字节数(>= 0) int
* 限定条件:netTask 上下文;avail 必须是本函数调用前刚读到的 ch395f_get_recv_len()
* 函数说明:1. **剥头与 udp_mode 无关,只看开 Socket 时的形态**:本层所有 DGRAM
* Socket 都由 net_bind 在 `DesIP = 0xFFFFFFFF`(服务器形态)下开出来,
* 芯片据此给**每一个**收到的数据报前置 8 字节信息头,此后无论把 DesIP
* 改成具体对端(显式 dest_addr 的 net_sendto / net_udp_set_peer)都不
* 再改变这一帧格式。2026-11-04 08:16 真机 TC-NET-1603 实测:客户端
* 形态下发来 48 B,芯片给出 n=56、首字节 0x38(=56,正是信息头的长度
* 字段)——运行时改 DesIP 不会让头消失。按 udp_mode 决定剥不剥,就会
* 把 8 字节头当载荷交给应用(SNTP 48 B 应答只剩 40 B 且首 8 字节是头),
* 这正是 TRAP-NET-22 记录的老缺陷;定案见该条 2026-11-04 追加段。
* 源地址同理一律从头里取:头里的源 IP/端口是**本包**的真实来源,比
* 对端记账更准(对端记账只用于发送与芯片收包过滤)。
* 2. 手册要求"一次性将数据全部读出";且载荷超出 len 时必须把剩余部分
* 读空——留半包在芯片缓冲里,下一次收包会从上一包的尾巴开始解析,
* 源地址与载荷从此全部错位(旧实现只读 min(可用, len),正是这个缺陷)。
* 3. 不足 8 字节的残包:无从判头,整包按载荷交付、源地址取对端记账
* (保持既有定案 TC-NETSOCK-440o,不猜芯片行为)。
* 4. 长度由调用方传入、本函数**不再自己读第二次**:一次收包的剥头量、
* 载荷量、排空量必须由同一个数字算出。读两次等于多一次 SPI,而且
* 两次之间芯片缓冲可能已经变了(下一包已到),拿后一次的小值去剥
* 前一次的包,头与载荷从此错位——正是本节第 2 条要避免的那类缺陷。
*/
static int net_udp_datagram_read(int sockfd, net_sock_t *p_sock, void *buf, int len,
struct net_sockaddr *src_addr, int *addrlen,
int avail) {
/* 本包载荷字节数(剥头后) */
int payload;
/* 交付调用方的字节数 */
int copy;
/* 需排空的剩余字节 */
int left;
if (avail <= 0) {
return 0;
}
if ((uint32_t)avail >= NET_UDP_HDR_LEN) {
/* 信息头暂存(只在剥头这一支用得到,放在本分支栈上) */
uint8_t hdr[NET_UDP_HDR_LEN];
/* 剥头:读即消费芯片缓冲 */
ch395f_read_recv_buf((uint8_t)sockfd, hdr, (uint16_t)NET_UDP_HDR_LEN);
net_udp_src_from_header(p_sock, hdr, src_addr, addrlen);
payload = avail - (int)NET_UDP_HDR_LEN;
} else {
/* 不足 8 字节的残包:无头可剥,源地址取固定对端 */
net_udp_src_from_peer(p_sock, src_addr, addrlen);
payload = avail;
}
copy = (payload > len) ? len : payload;
if (copy > 0) {
ch395f_read_recv_buf((uint8_t)sockfd, (uint8_t *)buf, (uint16_t)copy);
}
/* 截断剩余:读空丢弃,保证"一次调用 = 一个数据报"。
* 暂存放本函数栈上(仅 netTask 上下文使用,netTask 栈 960 字);
* 不用 ch395f_clear_recv_buf——它清的是整个接收缓冲,会连带丢掉
* 后续已到、调用方还没读的数据报。 */
left = payload - copy;
if (left > 0) {
uint8_t scratch[NET_UDP_DRAIN_CHUNK];
/* 与 TCP 短写同一口径记 ERROR:静默丢字节最难查。
* ⚠ 本仓无 DBG_WARN 宏(dbg_log.h 只有 ERROR/INFO/DEBUG),勿写 DBG_WARN */
DBG_ERROR("sock%d udp datagram truncated %d->%d, %d bytes drained",
sockfd, payload, copy, left);
while (left > 0) {
uint16_t n = (left > (int)NET_UDP_DRAIN_CHUNK)
? (uint16_t)NET_UDP_DRAIN_CHUNK
: (uint16_t)left;
ch395f_read_recv_buf((uint8_t)sockfd, scratch, n);
left -= (int)n;
}
}
/* 本包已消费完:清可读电平,否则 net_select 会永久报可读 */
p_sock->readable = 0U;
p_sock->recv_bytes += (uint32_t)copy;
return copy;
}
+190
View File
@@ -0,0 +1,190 @@
/*
* 模块名称:UDP 数据面执行器与挂起槽(netTask 串行化)
* 文件说明:UDP 三个碰芯片的动作(bind 打开、sendto、recvfrom)在 netTask 上下文
* 里的实际执行体,加上 per-fd 的"一次接收请求"槽位(载荷缓冲 + 源地址
* 出参 + 等待预算)。公开 API(net_sendto / net_recvfrom /
* net_recvfrom_to / net_udp_set_peer / net_udp_get_mode)住在
* net_socket.h,那边只负责参数校验、装消息入队、等通知值回结果。
* 为什么单独成模块:源地址(src_addr/addrlen)是**出参指针**,不能塞进按值拷入
* 队列的 net_msg_t(AGENTS 明文「勿给 net_msg_t 加回传用字段」;
* 指针过 int 桥在 64 位主机单测构建还会被截断)。所以出参走本模块的
* per-fd 槽:调用方入队前登记自己的指针,netTask 结算时把源地址写进
* 调用方内存、错误码/字节数搭任务通知值回去——与 TCP 挂起 recv 把数据
* 直接写进调用方 buf 是同一个模型(docs/网络/Net层阻塞模型架构设计.md)。
* 依赖:net_types.h(net_sock_t / NET_UDP_MODE_*)、ch395f.h(芯片事务)、
* net_socket.c(net_get_sock / net_poll / 消息分派 / 钩子调用点)
* 限定条件:除 net_udp_outparam_arm / net_udp_outparam_disarm / net_udp_clear(
* 纯 RAM,任意上下文)外,全部函数须 netTask 上下文
* 关联文档:docs/网络/BSD_Socket_API_使用指南.md §6(UDP 两种芯片模式与队列化)
* 作者:super_agent_bot
* 创建日期:2026-11-03
* 修改记录:
* 2026-11-03 super_agent_bot 创建:UDP 队列化改造(工单前置 = SNTP 客户端)
*/
#ifndef NET_UDP_H
#define NET_UDP_H
#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"
#include "net_types.h"
/*
* 函数功能:登记本次接收的源地址出参(调用方上下文,入队**前**调用)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* src_addr - 源地址出参指针(可为 NULL = 不关心来源) struct net_sockaddr *
* addrlen - 出参长度指针(可为 NULL) int *
* 出口参数:无
* 返回值:0 - 登记成功 int
* -1 - 失败(NET_ERR_BADF / NET_ERR_BUSY:本 fd 已有在途接收)
* 限定条件:任意上下文(临界区内检查并占位,与 netTask 的读改不竞态)
* 函数说明:占位成功后本 fd 的第二次接收请求会被拒(BUSY)——同一 fd 并发
* recvfrom 在数据报语义上无法区分归属,拒绝比交错投递安全。
* 调用方若入队失败必须 net_udp_outparam_disarm 归还槽位。
*/
int net_udp_outparam_arm(int sockfd, struct net_sockaddr *src_addr, int *addrlen);
/*
* 函数功能:归还源地址出参槽(调用方上下文,入队失败/取消后的清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无(越界或空槽静默返回)
* 限定条件:任意上下文;仅在**未收到 netTask 回执**的失败路径调用
* 函数说明:netTask 结算路径自己清槽(幂等),本函数只服务调用方侧的
* "消息没投递出去"这一条出口,防槽位泄漏导致该 fd 永久 BUSY。
*/
void net_udp_outparam_disarm(int sockfd);
/*
* 函数功能:UDP 绑定/打开芯片侧 Socket(netTask 上下文执行体,恒服务器模式)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* local_port - 本地(源)端口,主机序;0 = 由本函数分配动态端口 uint16_t
* 出口参数:无
* 返回值:0 - 成功 int
* -1 - 失败(NET_ERR_BADF 描述符非法 / NET_ERR_INVAL 非 DGRAM /
* NET_ERR_BUSY 芯片 open 被拒;errno 已记)
* 限定条件:netTask 上下文;sockfd 对应控制块在用且为 DGRAM
* 函数说明:1. 序列按手册 §9.2.4:Socket 4~7 兜底重设收发缓冲 → SET_PROTO_TYPE(UDP)
* → SET_SOUR_PORT → SET_DES_IP(0xFFFFFFFF) → SET_DES_PORT(0) → OPEN_SOCKET。
* 2. open 之后写端口/IP 会被 open 复位,故目的地址必须在 open 前下发
* (docs/网络/BSD_Socket_API_函数调用关系.md §4 调用链表)。
* 3. DesIP 恒取 0xFFFFFFFF = **服务器模式**:BSD 的 bind 只表达本地地址、
* 不选对端,客户端模式由 net_udp_set_peer_exec / sendto(带目的地址) 进入。
* 4. 动态端口分配在本函数内做(原在调用方上下文的 net_bind 里写
* s_dynamic_port——芯片事务既然挪进 netTask,端口簿记一并挪,
* 避免两个上下文改同一个静态量;取号游标经 net_dynamic_port_next 共用)。
* 5. 顺带本 fd 生命周期收口:清挂起接收槽、udp_mode 置 SERVER、清对端
* 簿记、清 readable(重开不得继承上一条连接的残留电平)。
*/
int net_udp_bind_exec(int sockfd, uint16_t local_port);
/*
* 函数功能:设置芯片 DesIP/DesPort 并切换 UDP 模式(不重开 Socket,netTask 执行体)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* peer - 非 NULL = 客户端模式(对端);NULL = 服务器模式 const struct net_sockaddr_in *
* 出口参数:无
* 返回值:0 - 成功 int
* -1 - 失败(NET_ERR_BADF / NET_ERR_INVAL,errno 已记)
* 限定条件:netTask 上下文;控制块在用、type == DGRAM、芯片侧 Socket 已打开
* 函数说明:公开语义见 net_socket.h net_udp_set_peer。只写两个寄存器
* (SET_DES_IP + SET_DES_PORT,约数十微秒),不 CLOSE/OPEN,
* 故接收缓冲里已到的数据不丢。
*/
int net_udp_set_peer_exec(int sockfd, const struct net_sockaddr_in *peer);
/*
* 函数功能:UDP 发送执行体(netTask 上下文,分块 + 有界自旋 + 挂起续传)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 载荷(调用方内存,须有效至完成) const void *
* len - 载荷长度(>0) int
* dest - 本包目的地址;NULL = 不改 DesIP const struct net_sockaddr_in *
* caller - 调用方任务句柄(挂起 send 的完成通知目标) TaskHandle_t
* 出口参数:无
* 返回值:实际发送字节数(>= 0);NET_PENDING_REGISTERED = 已登记挂起 send;
* -1 - 失败(NET_ERR_INVAL / NET_ERR_BADF,errno 已记) int
* 限定条件:netTask 上下文
* 函数说明:1. dest 非 NULL → 先 SET_DES_IP/SET_DES_PORT,并把 udp_mode 置
* CLIENT、对端簿记写入 remote_ip_arr/remote_port(芯片静默进
* 客户端模式是硬件事实,本层必须如实记账,否则收包剥头判错)。
* 2. 分块与等待语义与 net_send_locked 完全同源(NET_SEND_CHUNK_MAX /
* NET_SEND_SPIN_MS / NET_SEND_TIMEOUT_MS / 末块不等 SENBUF_FREE /
* 短写留痕),复用挂起 send 表推进——**不复制一份 UDP 专用自旋**,
* 两条路径分块粒度漂移就是 TRAP-NET-16 的复发形态。
* 3. 载荷指针在挂起期间由调用方持有(调用方阻塞在 net_wait_result,
* 栈与静态缓冲均有效),与 TCP 挂起 send 同一条铁律。
*/
int net_udp_sendto_exec(int sockfd, const void *buf, int len,
const struct net_sockaddr_in *dest, TaskHandle_t caller);
/*
* 函数功能:UDP 接收执行体(netTask 上下文:即时读 / 登记挂起 / 立即拒)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* buf - 调用方缓冲 void *
* len - 缓冲容量(>0) int
* flags - NET_MSG_DONTWAIT = 单次非阻塞 int
* to_ms - 等待预算(0 = 无限等待,须 blocking 属性) uint32_t
* caller - 调用方任务句柄(挂起完成通知目标) TaskHandle_t
* 出口参数:无(源地址经 net_udp_outparam_arm 登记的指针直接写回调用方内存)
* 返回值:实际接收字节数(> 0);NET_ERR_RETURN_1(WOULDBLOCK) = 非阻塞无数据;
* NET_PENDING_REGISTERED = 已登记挂起;-1 = 失败 int
* 限定条件:netTask 上下文;控制块在用且为 DGRAM
* 函数说明:1. 判据 = ch395f_get_recv_len()(芯片事实),readable 仅作省 SPI 的
* 门(同 net_pending_tick:事件未收到时 net_poll 每轮重读电平型 SINT
* 会重新置位,U15 探针同源结论)。
* 2. 服务器模式(udp_mode == SERVER)且本包 >= 8 字节 → 先读 8 字节
* 信息头解析源地址再读载荷;客户端模式 → 整包都是载荷,源地址 =
* 本 Socket 固定对端(remote_ip_arr/remote_port)。
* 3. **一个数据报一次读完**:可用字节 > len 时读满 len 后把剩余部分
* 读空丢弃(BSD 截断语义;手册 §9.2.4 对服务器模式要求一次性全部
* 读出)。旧实现只读 min(可用, len) 把半包留在芯片缓冲里,下一次
* 收包会把上一包的尾巴当成新包头的起点——整条链路的源地址从此错位。
* 4. 读空后清 readable(旧实现从不清,net_select 会永久报可读)。
* 5. 无数据 + 阻塞 → 登记挂起(同 fd 已有挂起 / 执行任务自身 → 拒绝,
* BUSY),netTask 零占用返回;完成条件由 net_udp_tick 判定。
*/
int net_udp_recvfrom_exec(int sockfd, void *buf, int len, int flags,
uint32_t to_ms, TaskHandle_t caller);
/*
* 函数功能:挂起 UDP 接收完成检查(netTask 每轮 net_process_messages 调用一次,非阻塞)
* 入口参数:p_socks - Socket 控制块表首指针(net_socket.c 的 s_net_socks) net_sock_t *
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文
* 函数说明:逐 fd 按序判定:①fd 已关闭/已非 UDP → 以 NET_ERR_CONNRESET 结束(必通知)
* ②数据到达 → 读包(含剥头/截断/清 readable)+ 回填源地址 + 以字节数结束
* ③到点(仅显式预算)→ 以 NET_ERR_TIMEDOUT 结束(调用方 net_recvfrom_to
* 映射为对外 -2)。三条出口都必须经统一出口通知,漏通知 = 调用方永久挂死。
*/
void net_udp_tick(net_sock_t *p_socks);
/*
* 函数功能:清除本 fd 的 UDP 接收槽(不通知;"新 fd 不得继承"与 close 后幂等清理)
* 入口参数:sockfd - Socket 描述符 (0~7) int
* 出口参数:无
* 返回值:无(越界静默返回)
* 限定条件:任意上下文(netTask 的 close/socket 分配路径调用)
*/
void net_udp_clear(int sockfd);
/*
* 函数功能:取消全部挂起 UDP 接收并逐一通知(网络重建/复位路径)
* 入口参数:err - 通知的错误码 int
* 出口参数:无
* 返回值:无
* 限定条件:netTask 上下文;须在控制块清零与芯片复位之前调用
*/
void net_udp_cancel_all(int err);
/*
* 函数功能:关闭前结算本 fd 的挂起 UDP 接收(有数据先送达,无数据则取消)
* 入口参数:sockfd - 将被关闭的描述符 (0~7) int
* p_sock - 将被关闭的控制块指针(与 sockfd 对应) net_sock_t *
* 出口参数:无
* 返回值:无(无在位挂起时静默返回)
* 限定条件:netTask 上下文;须在 ch395f 关闭/复位该 Socket 之前调用
* 函数说明:与 net_pending_settle_close 同一契约——关闭不丢已到达数据
*/
void net_udp_settle_close(int sockfd, net_sock_t *p_sock);
#endif /* NET_UDP_H */
+44 -1
View File
@@ -32,6 +32,8 @@
#include "ftp_cfg.h"
#include "sntp_cfg.h"
/* 深调用链模块(net_cfg_load/save 在 defaultTask 启动链与保存链上):
* 日志行缓冲收到 128 B(默认 192),本模块最长行渲染后 < 128 → 不截断 */
#define DBG_LINE_BUF_MAX 128U
@@ -80,6 +82,16 @@ static const cfg_key_def_t s_net_keys[] = {
{ "mac", CFG_VAL_MAC, offsetof(net_cfg_t, mac), 6u },
};
/* [sntp] 段键表(sntp_cfg_t 布局内偏移;数据本体在 sntp_cfg.c——分层门禁
* layer-deps 要求段描述符的组装集中在本文件:NETCFG 里只有 net_cfg.c /
* ftp_cfg.c 在反向依赖基线上,[sntp] 是新增模块,不再往基线添条目) */
static const cfg_key_def_t s_sntp_keys[] = {
{ "enable", CFG_VAL_UINT, offsetof(sntp_cfg_t, enable), 1u },
{ "server1", CFG_VAL_IP4, offsetof(sntp_cfg_t, server_pri), 4u },
{ "server2", CFG_VAL_IP4, offsetof(sntp_cfg_t, server_bak), 4u },
{ "poll_min", CFG_VAL_UINT, offsetof(sntp_cfg_t, poll_min), 2u },
};
/* 函数定义区 */
/*
@@ -122,6 +134,35 @@ static void s_net_register(void) {
s_done = 1u;
}
/*
* 函数功能:向 cfg_file 引擎注册 [sntp] 段(幂等)
* 入口参数:无
* 返回值:无
* 限定条件:load/save 前调用(net_cfg_load 内统一编排)
* 函数说明:数据本体与工厂默认由 sntp_cfg.c 出(sntp_cfg_data/defaults);
* 无变更回调——SNTP 客户端每轮直读 RAM 副本,保存后的"立即生效"
* 走既有 SYS_EVT_NET_RECONFIG(网络重建 → 代次变化 → 客户端重开
* Socket)链,本段无需单独挂接
*/
static void s_sntp_register(void) {
static uint8_t s_done;
cfg_section_reg_t reg;
if (s_done != 0u) {
return;
}
(void)memset(&reg, 0, sizeof(reg));
reg.name = "sntp";
reg.p_data = sntp_cfg_data();
reg.p_defaults = sntp_cfg_defaults();
reg.data_size = sizeof(sntp_cfg_t);
reg.p_keys = s_sntp_keys;
reg.key_count = 4u;
reg.p_cb = NULL;
(void)cfg_file_register(&reg);
s_done = 1u;
}
/*
* 函数功能:判断 6 字节 MAC 是否全零
* 入口参数:p_mac - MAC 指针(不可为 NULL) const uint8_t *
@@ -148,8 +189,10 @@ static bool s_mac_is_zero(const uint8_t *p_mac) {
*/
int net_cfg_load(void) {
s_net_register();
/* [ftp] 段一并注册(所有受管段必须先注册、后 load,引擎按注册表解析) */
/* [ftp] / [sntp] 段一并注册(所有受管段必须先注册、后 load,引擎按注册表解析);
* [sntp] 描述符在本文件组装——见 s_sntp_register 分层门禁注 */
ftp_cfg_register();
s_sntp_register();
/* 旧档 NCFG 迁移已于 2026-10-10 删除(net_cfg_legacy 移除;理由见文件头 v1.3)。
* 若卷上仍残留 /cfg/netcfg.bin:不读、不删、不迁移 → 配置回落工厂默认 */
+103
View File
@@ -0,0 +1,103 @@
/*
* 模块名称:SNTP 配置核心 (SNTP Configuration Core)
* 模块功能:RAM 配置副本 + 工厂默认 + 读写出口(INI [sntp] 段:
* enable/server1/server2/poll_min);加载/保存编排复用 [network]
* 段的既有链路(net_cfg_load/save + UI 保存行请求链)。段描述符
* 由 net_cfg.c 的 s_sntp_register() 组装注册——分层门禁 layer-deps
* 要求本模块不 include 配置引擎头(App/cfg/cfg_file.h),只留
* 数据本体与访问器
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(SNTP 客户端接入,#57 调研结论:
* 出厂默认关闭、服务器 IPv4 字面量、周期 1~1440 分钟)
* v1.1 2026-11-04 王建锋 分层门禁口径改造:注册编排移出本模块(见 .h 同名注)
*/
/* 头文件包含区 */
#include <string.h>
#include "sntp_cfg.h"
/* 静态变量定义区 */
/* RAM 配置副本:以工厂默认值静态初始化(落 .data),自启动即有效——
* 不依赖 cfg_file_load 是否成功(与 net_cfg s_cfg 同一口径)。
* 命名带 s_sntp_ 前缀:net_cfg.c 与本文件会在同一 TU 里被主机单测并入
* (test/host/net_cfg),裸 s_cfg/s_defaults 会同名重定义 */
static sntp_cfg_t s_sntp_cfg = {
.enable = SNTP_CFG_DEF_ENABLE,
.server_pri = SNTP_CFG_DEF_SERVER,
.server_bak = SNTP_CFG_DEF_SERVER,
.poll_min = SNTP_CFG_DEF_POLL_MIN,
};
/* 工厂默认(引擎注册 p_defaults 指向;与 s_sntp_cfg 初值共用宏单一来源) */
static const sntp_cfg_t s_sntp_defaults = {
.enable = SNTP_CFG_DEF_ENABLE,
.server_pri = SNTP_CFG_DEF_SERVER,
.server_bak = SNTP_CFG_DEF_SERVER,
.poll_min = SNTP_CFG_DEF_POLL_MIN,
};
/* 函数定义区 */
/*
* 函数功能:取 [sntp] 段 RAM 副本(引擎注册专用写入口)
* 入口参数:无
* 返回值:RAM 配置副本指针(恒非空) sntp_cfg_t *
* 限定条件:仅 net_cfg.c 组段描述符用;业务读走 sntp_cfg_get(),
* UI 编辑走 sntp_cfg_set()
*/
sntp_cfg_t *sntp_cfg_data(void) {
return &s_sntp_cfg;
}
/*
* 函数功能:取工厂默认值(引擎注册 p_defaults 指向)
* 入口参数:无
* 返回值:工厂默认结构指针(只读,恒非空) const sntp_cfg_t *
* 限定条件:仅 net_cfg.c 组段描述符用
*/
const sntp_cfg_t *sntp_cfg_defaults(void) {
return &s_sntp_defaults;
}
/*
* 函数功能:取 [sntp] 段 RAM 副本(只读)
* 入口参数:无
* 返回值:RAM 配置副本指针(恒非空) const sntp_cfg_t *
* 限定条件:无
*/
const sntp_cfg_t *sntp_cfg_get(void) {
return &s_sntp_cfg;
}
/*
* 函数功能:更新 RAM 副本(不写盘)
* 入口参数:p_new - 新配置(NULL 忽略) const sntp_cfg_t *
* 返回值:无
* 限定条件:UI 编辑路径(net_cfg_set 同口径:整结构体一次拷贝,
* 写盘走 net_cfg_save_request 整档链,属主 defaultTask)
*/
void sntp_cfg_set(const sntp_cfg_t *p_new) {
if (p_new != NULL) {
(void)memcpy(&s_sntp_cfg, p_new, sizeof(s_sntp_cfg));
}
}
/*
* 函数功能:取轮询周期(带外自愈读出口)
* 入口参数:无
* 返回值:合法范围内 = 存储值;0 或 >1440 = 工厂默认 60 uint16_t
* 限定条件:无
*/
uint16_t sntp_cfg_poll_min(void) {
uint16_t v = s_sntp_cfg.poll_min;
if ((v < SNTP_CFG_POLL_MIN_MIN) || (v > SNTP_CFG_POLL_MIN_MAX)) {
return SNTP_CFG_DEF_POLL_MIN;
}
return v;
}
+97
View File
@@ -0,0 +1,97 @@
/*
* 模块名称:SNTP 配置([sntp] 段)
* 模块功能:板端 SNTP 客户端配置——RAM 副本 + 工厂默认 + 读写出口
* ([sntp] 段:enable / server1 / server2 / poll_min);段描述符
* 由 net_cfg.c 在 net_cfg_load 里组好交引擎(分层门禁 layer-deps:
* 本模块不 include 引擎头);消费者(App/sync/time_sntp.c)与 UI 页
* 经 sntp_cfg_get() 只读访问,配置引擎属主仍为 defaultTask
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(SNTP 客户端接入,#57 调研结论)
* v1.1 2026-11-04 王建锋 分层门禁口径:sntp_cfg_register 改为
* sntp_cfg_data/sntp_cfg_defaults 数据口,
* 段描述符编排移 net_cfg.c(本模块不再依赖引擎头)
*/
#ifndef SNTP_CFG_H
#define SNTP_CFG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/* 轮询周期合法范围(分钟);范围外的存储值读取时回落工厂默认(同 [comm]
* 频段自愈口径,见 iec104_comm.c 带外值自愈) */
#define SNTP_CFG_POLL_MIN_MIN 1u
#define SNTP_CFG_POLL_MIN_MAX 1440u
/* 工厂默认:enable=0(出厂不启用,调研 §6 产品口径)、服务器留空
* (0.0.0.0 = 未配置,客户端跳过该服务器)、poll_min=60(1 小时) */
#define SNTP_CFG_DEF_ENABLE 0u
#define SNTP_CFG_DEF_SERVER { 0u, 0u, 0u, 0u }
#define SNTP_CFG_DEF_POLL_MIN 60u
typedef struct {
uint8_t enable; /* 1 = 启用 SNTP 客户端;0 = 停用(默认) */
uint8_t server_pri[4]; /* 主 SNTP 服务器 IPv4(全零 = 未配置) */
uint8_t server_bak[4]; /* 备 SNTP 服务器 IPv4(全零 = 无备) */
uint16_t poll_min; /* 成功后的轮询周期(分钟,1~1440) */
} sntp_cfg_t;
/*
* 函数功能:取 [sntp] 段 RAM 副本(引擎注册专用写入口)
* 入口参数:无
* 返回值:RAM 配置副本指针(恒非空,静态常驻) sntp_cfg_t *
* 限定条件:仅供 net_cfg.c 组 [sntp] 段描述符交配置引擎(net_cfg_load 编排);
* 分层门禁 layer-deps:本模块不 include cfg_file.h,段注册集中在
* net_cfg.c(同层基线文件)完成
*/
sntp_cfg_t *sntp_cfg_data(void);
/*
* 函数功能:取工厂默认值(引擎注册 p_defaults 指向)
* 入口参数:无
* 返回值:工厂默认结构指针(只读,恒非空) const sntp_cfg_t *
* 限定条件:同上,net_cfg.c 组段描述符专用
*/
const sntp_cfg_t *sntp_cfg_defaults(void);
/*
* 函数功能:取 [sntp] 段 RAM 副本(只读)
* 入口参数:无
* 返回值:恒非 NULL(静态常驻,load 前初值 = 工厂默认) const sntp_cfg_t *
* 限定条件:无
* 函数说明:defaultTask poll 重载瞬间可能读到撕裂值——IPv4 4 字节 / uint16
* 在本平台是单条 LDR 指令天然原子,enable 单字节同理;即便撕裂,
* SNTP 客户端最坏多用一次旧值,下一轮自愈(配置变更本就罕见)
*/
const sntp_cfg_t *sntp_cfg_get(void);
/*
* 函数功能:更新 RAM 副本(不写盘)
* 入口参数:p_new - 新配置(NULL 忽略) const sntp_cfg_t *
* 返回值:无
* 限定条件:UI 编辑路径专用(net_cfg_set 同口径);写盘须走
* net_cfg_save_request 整档链(属主 defaultTask)
*/
void sntp_cfg_set(const sntp_cfg_t *p_new);
/*
* 函数功能:取轮询周期(带外自愈读出口)
* 入口参数:无
* 返回值:合法范围内 = 存储值;0 或 >1440 = 工厂默认 60 uint16_t
* 限定条件:无
* 函数说明:不改写 RAM 副本(UI 显示的仍是用户写的原值,避免"保存后
* 数字自己变了"的困惑);消费方(time_sntp)用本口而非裸字段
*/
uint16_t sntp_cfg_poll_min(void);
#ifdef __cplusplus
}
#endif
#endif /* SNTP_CFG_H */
+200 -176
View File
@@ -437,6 +437,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x9f, 0x52, 0x2a, 0x7d, 0x48, 0xa9, 0x11, 0x26,
0x60,
/* U+52A1 "务" */
0x10, 0x7, 0xf1, 0x44, 0x7, 0x3, 0x19, 0x90,
0xc2, 0x3, 0xfc, 0x10, 0x82, 0x10, 0x82, 0x21,
0x80,
/* U+52A8 "动" */
0x1, 0xe, 0x20, 0x4, 0x1, 0xff, 0x92, 0x42,
0x50, 0x4a, 0x89, 0x89, 0x3f, 0x44, 0x8, 0x82,
@@ -515,6 +520,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0xc8, 0x9, 0x7d, 0x28, 0xa5, 0xf5, 0x0, 0xc0,
0x70,
/* U+5668 "器" */
0x7b, 0xc9, 0x49, 0xef, 0x2, 0x40, 0x45, 0xff,
0xc2, 0x81, 0x8c, 0xc0, 0x6f, 0x79, 0x29, 0x3d,
0xe0,
/* U+56DE "回" */
0xff, 0xe0, 0x18, 0x6, 0x79, 0x92, 0x64, 0x99,
0xe6, 0x1, 0x80, 0x7f, 0xf8, 0x4,
@@ -549,6 +559,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x52, 0xaa, 0x76, 0x40, 0xcc, 0x57, 0x35, 0x81,
0x10,
/* U+5907 "备" */
0x10, 0x7, 0xf1, 0x44, 0x7, 0x3, 0x19, 0x80,
0xcf, 0xe1, 0x24, 0x3f, 0x84, 0x90, 0xfe, 0x10,
0x40,
/* U+590D "复" */
0x3f, 0xe8, 0x2, 0xff, 0x10, 0x23, 0xfc, 0x40,
0x8f, 0xf0, 0x80, 0x3f, 0xca, 0x10, 0x3c, 0x38,
@@ -800,6 +815,10 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
0x3f, 0xc8, 0x12, 0x4, 0xff, 0x20, 0x48, 0x13,
0xfc, 0x81, 0x40, 0x50, 0x18, 0x1c,
/* U+670D "服" */
0x7b, 0xe9, 0x45, 0x28, 0xbd, 0x64, 0xa0, 0x97,
0xde, 0xaa, 0x55, 0x4a, 0x49, 0x56, 0x6c, 0x80,
/* U+671F "期" */
0x48, 0x9, 0x3f, 0xf4, 0xa4, 0x97, 0x9e, 0x92,
0x5e, 0x4a, 0x4f, 0xfd, 0x20, 0x25, 0x24, 0xc3,
@@ -1515,24 +1534,24 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 898, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 915, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 932, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 949, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 963, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 979, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 995, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1012, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1028, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1045, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1061, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 1073, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1089, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 949, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 966, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 980, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 996, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1012, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1029, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1045, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1062, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1078, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 1090, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1106, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1123, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1140, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1157, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1174, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1191, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1205, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1222, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1191, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1208, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1225, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1239, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1256, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1273, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
@@ -1540,29 +1559,29 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 1307, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1324, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1341, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1358, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1374, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1391, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1408, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1358, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1375, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1392, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1409, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1425, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1442, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1459, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1473, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1490, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1507, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1459, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1476, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1493, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1510, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1524, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1541, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1558, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1575, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1591, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1608, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1625, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1639, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1656, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1673, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1687, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1704, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1721, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1575, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1592, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1609, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1626, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1642, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1659, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1676, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1690, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1707, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1724, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 1738, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1755, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 1772, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
@@ -1583,132 +1602,136 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 2027, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2044, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2061, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2078, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2094, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 2107, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2124, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2140, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2157, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2171, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2188, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2205, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2222, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2239, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2256, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2273, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2290, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2307, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2324, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2341, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2358, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2375, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2392, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2408, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2424, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2441, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2458, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2475, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2492, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2509, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2526, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2543, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2560, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2577, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2594, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2611, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2628, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2645, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2662, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2679, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2693, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2710, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2727, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2744, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 2759, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2776, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2793, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2810, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2078, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2095, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2112, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2129, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2145, .adv_w = 192, .box_w = 9, .box_h = 11, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 2158, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2175, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2191, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2208, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2222, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2238, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2255, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2272, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2289, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2306, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2323, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2340, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2357, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2374, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2391, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2408, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2425, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2442, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2459, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2475, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2491, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2508, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2525, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2542, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2559, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2576, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2593, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2610, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2627, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2644, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2661, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2678, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2695, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2712, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2729, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2746, .adv_w = 192, .box_w = 10, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2760, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2777, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2794, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2811, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 2826, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2843, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2859, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2876, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2843, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2860, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2877, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2893, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2910, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2927, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2944, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2961, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2978, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2995, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3012, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3029, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3046, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3063, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3080, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3097, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3114, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3131, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3148, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3162, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3179, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3196, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3213, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3230, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3247, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3264, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3281, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3298, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3315, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3332, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3349, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3366, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3383, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3400, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3417, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3434, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3451, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3468, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3485, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3502, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3519, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3536, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3553, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3570, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3587, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3604, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3621, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3638, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3655, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3672, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3689, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3706, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3723, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3740, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2910, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2926, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2943, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2960, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2977, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 2994, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3011, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3028, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3045, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3062, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3079, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3096, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3113, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3130, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3147, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3164, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3181, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3198, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3215, .adv_w = 192, .box_w = 9, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3229, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3246, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3263, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3280, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3297, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3314, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3331, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3348, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3365, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3382, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3399, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3416, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3433, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3450, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3467, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3484, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3501, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3518, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3535, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3552, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3569, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3586, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3603, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3620, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3637, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3654, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3671, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3688, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3705, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3722, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3739, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3756, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3773, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3790, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3807, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3824, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3841, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3858, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3875, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3892, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3907, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3922, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3937, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3954, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3971, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3988, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4005, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4022, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3807, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3823, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3840, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3857, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3874, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3891, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3908, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3925, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3942, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 3959, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3974, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 3989, .adv_w = 192, .box_w = 10, .box_h = 12, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 4004, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4021, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4038, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4055, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4072, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4089, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4106, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4123, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4140, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 4141, .adv_w = 192, .box_w = 2, .box_h = 6, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 4143, .adv_w = 165, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1} /* U+25B6 */
{.bitmap_index = 4089, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4105, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4122, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4139, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4156, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4173, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4190, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = -2},
{.bitmap_index = 4207, .adv_w = 192, .box_w = 2, .box_h = 4, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 4208, .adv_w = 192, .box_w = 2, .box_h = 6, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 4210, .adv_w = 165, .box_w = 8, .box_h = 10, .ofs_x = 1, .ofs_y = -1} /* U+25B6 */
};
/*---------------------
@@ -1719,31 +1742,32 @@ static const uint16_t unicode_list_1[] = {
0x0, 0x1, 0x2, 0x2ca0, 0x2ca9, 0x2caa, 0x2cad, 0x2cdb,
0x2d2b, 0x2d2c, 0x2d44, 0x2d96, 0x2ded, 0x2dee, 0x2dfc, 0x2e7d,
0x2e81, 0x2edc, 0x3005, 0x300c, 0x3011, 0x3013, 0x3039, 0x309a,
0x30a6, 0x30d6, 0x30ea, 0x30ed, 0x3148, 0x31b6, 0x31da, 0x322b,
0x323f, 0x3262, 0x326c, 0x3271, 0x3276, 0x3283, 0x328f, 0x3297,
0x32a8, 0x32cc, 0x32cf, 0x32ea, 0x3308, 0x357e, 0x3580, 0x3594,
0x35d0, 0x35e0, 0x362b, 0x367f, 0x37ad, 0x37b6, 0x37d1, 0x39f0,
0x39f7, 0x39f8, 0x3a3a, 0x3a3e, 0x3a66, 0x3c92, 0x3cc7, 0x3cd8,
0x3d14, 0x3d2f, 0x3d46, 0x3d96, 0x3da0, 0x3da2, 0x3daf, 0x3db5,
0x3dda, 0x3df2, 0x3df3, 0x3df5, 0x3ea1, 0x3edb, 0x3f02, 0x3f0f,
0x40d7, 0x40eb, 0x4135, 0x4136, 0x4144, 0x4145, 0x420e, 0x4229,
0x4245, 0x4247, 0x4249, 0x436d, 0x43d6, 0x43de, 0x43e5, 0x43e8,
0x4410, 0x4414, 0x4480, 0x4485, 0x4496, 0x44de, 0x45a0, 0x45a8,
0x45bf, 0x45ca, 0x45cc, 0x45da, 0x461f, 0x4685, 0x46a7, 0x46c1,
0x46d7, 0x4760, 0x48c1, 0x49c0, 0x49c1, 0x4a03, 0x4a1b, 0x4a55,
0x4ae2, 0x4b00, 0x4b82, 0x4be1, 0x4beb, 0x4c28, 0x4ca5, 0x4cc9,
0x4f59, 0x50e7, 0x50e8, 0x5156, 0x5177, 0x518c, 0x53c8, 0x53d5,
0x53ec, 0x5571, 0x5594, 0x55ab, 0x5685, 0x56a1, 0x570c, 0x570e,
0x57da, 0x5872, 0x58ab, 0x5937, 0x596b, 0x5979, 0x598f, 0x59cc,
0x5b1b, 0x5b9b, 0x5d5f, 0x5d68, 0x5d7f, 0x5d8d, 0x5d94, 0x5df1,
0x5e0e, 0x5fba, 0x608a, 0x6122, 0x614f, 0x61a3, 0x6217, 0x66ec,
0x6765, 0x6a06, 0x6a41, 0x6a44, 0x6a4f, 0x6a50, 0x6a5e, 0x6a82,
0x6a85, 0x6a8f, 0x6a97, 0x6a9b, 0x6bbf, 0x6bc5, 0x6c2a, 0x6c93,
0x6e0f, 0x6e1d, 0x6e33, 0x6e67, 0x6e70, 0x6e71, 0x6e74, 0x6ea0,
0x6ea1, 0x6eba, 0x6ef3, 0x6f05, 0x6f88, 0x6fed, 0x7067, 0x706d,
0x706f, 0x733f, 0x7345, 0x73a1, 0x73b9, 0x741f, 0x7488, 0x748d,
0x7494, 0x7498, 0x74d2, 0x7504, 0x753c, 0x7596, 0x7602, 0x7715,
0x7731, 0x7828, 0x7836, 0x792c, 0x7d78, 0xddac, 0xddba
0x30a6, 0x30d6, 0x30ea, 0x30ed, 0x3141, 0x3148, 0x31b6, 0x31da,
0x322b, 0x323f, 0x3262, 0x326c, 0x3271, 0x3276, 0x3283, 0x328f,
0x3297, 0x32a8, 0x32cc, 0x32cf, 0x32ea, 0x3308, 0x3508, 0x357e,
0x3580, 0x3594, 0x35d0, 0x35e0, 0x362b, 0x367f, 0x37a7, 0x37ad,
0x37b6, 0x37d1, 0x39f0, 0x39f7, 0x39f8, 0x3a3a, 0x3a3e, 0x3a66,
0x3c92, 0x3cc7, 0x3cd8, 0x3d14, 0x3d2f, 0x3d46, 0x3d96, 0x3da0,
0x3da2, 0x3daf, 0x3db5, 0x3dda, 0x3df2, 0x3df3, 0x3df5, 0x3ea1,
0x3edb, 0x3f02, 0x3f0f, 0x40d7, 0x40eb, 0x4135, 0x4136, 0x4144,
0x4145, 0x420e, 0x4229, 0x4245, 0x4247, 0x4249, 0x436d, 0x43d6,
0x43de, 0x43e5, 0x43e8, 0x4410, 0x4414, 0x4480, 0x4485, 0x4496,
0x44de, 0x45a0, 0x45a8, 0x45ad, 0x45bf, 0x45ca, 0x45cc, 0x45da,
0x461f, 0x4685, 0x46a7, 0x46c1, 0x46d7, 0x4760, 0x48c1, 0x49c0,
0x49c1, 0x4a03, 0x4a1b, 0x4a55, 0x4ae2, 0x4b00, 0x4b82, 0x4be1,
0x4beb, 0x4c28, 0x4ca5, 0x4cc9, 0x4f59, 0x50e7, 0x50e8, 0x5156,
0x5177, 0x518c, 0x53c8, 0x53d5, 0x53ec, 0x5571, 0x5594, 0x55ab,
0x5685, 0x56a1, 0x570c, 0x570e, 0x57da, 0x5872, 0x58ab, 0x5937,
0x596b, 0x5979, 0x598f, 0x59cc, 0x5b1b, 0x5b9b, 0x5d5f, 0x5d68,
0x5d7f, 0x5d8d, 0x5d94, 0x5df1, 0x5e0e, 0x5fba, 0x608a, 0x6122,
0x614f, 0x61a3, 0x6217, 0x66ec, 0x6765, 0x6a06, 0x6a41, 0x6a44,
0x6a4f, 0x6a50, 0x6a5e, 0x6a82, 0x6a85, 0x6a8f, 0x6a97, 0x6a9b,
0x6bbf, 0x6bc5, 0x6c2a, 0x6c93, 0x6e0f, 0x6e1d, 0x6e33, 0x6e67,
0x6e70, 0x6e71, 0x6e74, 0x6ea0, 0x6ea1, 0x6eba, 0x6ef3, 0x6f05,
0x6f88, 0x6fed, 0x7067, 0x706d, 0x706f, 0x733f, 0x7345, 0x73a1,
0x73b9, 0x741f, 0x7488, 0x748d, 0x7494, 0x7498, 0x74d2, 0x7504,
0x753c, 0x7596, 0x7602, 0x7715, 0x7731, 0x7828, 0x7836, 0x792c,
0x7d78, 0xddac, 0xddba
};
/*Collect the unicode lists and glyph_id offsets*/
@@ -1754,12 +1778,12 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
},
{
.range_start = 9654, .range_length = 1, .glyph_id_start = 319,
.range_start = 9654, .range_length = 1, .glyph_id_start = 323,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
},
{
.range_start = 8544, .range_length = 56763, .glyph_id_start = 96,
.unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 223, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
.unicode_list = unicode_list_1, .glyph_id_ofs_list = NULL, .list_length = 227, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
}
};
+2 -2
View File
@@ -5,8 +5,8 @@
* 注入 reader 取自 sys_log(/log/oper.log,容量上限 512 条,满时删除重建);
* 空日志显示单条伪行"无记录";向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 记录口径(本文件只读显示、不实现写入方;权威表 App/log/README.md §五类记录):
* 触发判据 = 人工/远端操作留痕:定值保存、强制复归、对时(8888 与 104)、清除记录;
* 写入点 = App/task/default_task.c(定值保存)、App/protocol/iec104/iec104_pts.c(复归与整定写入)、App/task/iec104_serv.c 与 App/sync/time_sync.c(对时)、App/log/sys_log_mcu.c(清除记录留痕,OPER 自身除外);
* 触发判据 = 人工/远端操作留痕:定值保存、强制复归、对时(SNTP 与 104)、清除记录;
* 写入点 = App/task/default_task.c(定值保存)、App/protocol/iec104/iec104_pts.c(复归与整定写入)、App/task/iec104_serv.c 与 App/sync/time_sntp.c(对时)、App/log/sys_log_mcu.c(清除记录留痕,OPER 自身除外);
* "记录类型 = 日志 ID 本身"(一类记录一个文件,记录体内没有类型字段位,
* Lead 裁定 2026-10-26)——本页只读 oper.log 一个文件,不做跨文件混排。
* 台架待补(不在本批,已登记 docs/任务规划/登记簿.md):真 NAND 上真实记录的翻页读取、
+2
View File
@@ -26,6 +26,7 @@
#include "ui_page_runlog.h"
#include "ui_page_runreport.h"
#include "ui_page_selftest.h"
#include "ui_page_sntp.h"
#include "ui_page_soe.h"
#include "ui_page_start.h"
#include "ui_page_view_param.h"
@@ -58,6 +59,7 @@ const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT] = {
{ 27u, &ui_page_clear_desc }, /* node 27 清除记录 */
{ 29u, &ui_page_comm_desc }, /* node 29 通讯设置 */
{ 30u, &ui_page_net_cfg_desc }, /* node 30 网口设置 */
{ 31u, &ui_page_sntp_desc }, /* node 31 SNTP设置 */
{ 33u, &ui_page_soe_desc }, /* node 33 SOE记录 */
{ 34u, &ui_page_accident_desc }, /* node 34 事故记录 */
{ 35u, &ui_page_oper_desc }, /* node 35 操作记录 */
+1 -1
View File
@@ -25,7 +25,7 @@ typedef struct {
const content_page_def_t *desc; /* 页面描述符(Flash const) */
} ui_page_entry_t;
#define UI_PAGE_MAP_COUNT 30u /* 绑定了内容页的叶子数 */
#define UI_PAGE_MAP_COUNT 31u /* 绑定了内容页的叶子数 */
extern const ui_page_entry_t g_ui_page_map[UI_PAGE_MAP_COUNT];
+431
View File
@@ -0,0 +1,431 @@
/*
* 模块名称:UI Page SNTP (SNTP设置内容页)
* 模块功能:SNTP设置叶子节点的内容页数据 + 行为——5 行列表(启用/主服务器/
* 备服务器/周期(分) + 保存行,5 行每页共 1 页);
* 行槽顶 = 24+14i、选中白带 x=2..156 高 13、名 x=4 / 值 x=54;
* 值取自 sntp_cfg RAM 副本([sntp] 段,defaultTask 上电自 Flash
* 加载,net_cfg_load 统一注册);
* 编辑模式:ENTER 进选中编辑行(地址行 = 字节光标反显,周期行 =
* 整值反显),↑↓ 调值、←→ 移字节光标 / 粗调周期、ENTER 确认退出;
* 启用行为开关行:可编辑态 ENTER 直接切换(无字节光标);
* 权限:edit_capable=1——查看密码 → 只读(编辑/保存键无效),
* 修改密码 → 可编辑;保存行 ENTER → 投递保存请求
* (net_cfg_save_request 整档链:属主任务 defaultTask 写盘;
* [sntp] 消费方每轮直读 RAM 副本 + 网络代次重建自动收口,
* 无需独立 apply 通道);保存行值随交接状态显示
* ENTER/WAIT/OK/ERR;
* 向框架 ui_content_page 提供页面描述符(零 LVGL 对象)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(板端 SNTP 客户端接入,#57 调研结论;
* 布局/编辑语义复刻 ui_page_net_cfg.c 先例)
*/
/* 头文件包含区 */
#include <string.h>
#include "ui_page_sntp.h"
#include "net_cfg.h" /* net_cfg_fmt_ipv4 + 整档保存请求链(save request/state) */
#include "sntp_cfg.h"
#include "ui_tokens.h"
/* 私有宏定义区 */
/*
* 布局取值:行骨架用 ui_tokens.h 的列表页令牌(UI_LIST_TOP / UI_ROW_PITCH /
* UI_ROW_BAND_H / UI_BUF_STRLEN),本文件只留本页私有的两列 x 与行数
* (与网口设置页同栅格:值列 ≥54,IPv4 定长 15 字符 90px 止于 143,在
* 选中白带 x=2..156 内)。
*/
#define SN_ROW_COUNT 5u /* 1 开关行 + 2 地址行 + 1 周期行 + 1 保存行 */
#define SN_EN_ROW 0u /* 启用(开关行) */
#define SN_ADDR_ROW_MIN 1u /* 地址行范围 1..2(主/备服务器) */
#define SN_ADDR_ROW_MAX 2u
#define SN_POLL_ROW 3u /* 周期行(分钟) */
#define SN_SAVE_ROW 4u /* 保存行下标 */
#define SN_LEFT_X 4 /* 左列 x(名) */
#define SN_RIGHT_X 54 /* 右列 x(值) */
/* 周期编辑步进与合法带(与 sntp_cfg.h getter 带一致;带外读回 60 自愈) */
#define SN_POLL_MIN 1u
#define SN_POLL_MAX 1440u
#define SN_POLL_BIG_STEP 60u
/* 静态变量定义区 */
/* 页面运行态(每次开页经 init_cb 重置;约 2 B) */
static uint8_t s_editing; /* 1 = 编辑中(被编辑行 = 当前选中行) */
static uint8_t s_cursor; /* 地址行编辑活动字节(0..3) */
/* 函数定义区 */
/*
* 函数功能:判断两区域是否相交(与 ui_content_page.c / ui_page_net_cfg.c 同实现)
* 入口参数:a - 区域 1 const lv_area_t *
* b - 区域 2 const lv_area_t *
* 返回值:true - 相交;false - 不相交
* 限定条件:无
*/
static bool sn_areas_intersect(const lv_area_t *a, const lv_area_t *b) {
int32_t x1 = (a->x1 > b->x1) ? a->x1 : b->x1;
int32_t y1 = (a->y1 > b->y1) ? a->y1 : b->y1;
int32_t x2 = (a->x2 < b->x2) ? a->x2 : b->x2;
int32_t y2 = (a->y2 < b->y2) ? a->y2 : b->y2;
return (x1 <= x2 && y1 <= y2);
}
/*
* 函数功能:生成指定地址行的值串(与 refresh 值列同源,编辑光标定位与行文本一致)
* 入口参数:row - 地址行下标(1..2) uint16_t
* p_buf - 输出缓冲(≥ UI_BUF_STRLEN) char *
* 返回值:无
* 限定条件:p_buf 非空
* 函数说明:"192.168.001.100"(每段 3 位补零定长,字节 i 恒在字符 4i 起),
* 与网口设置页 IP 行同格式(net_cfg_fmt_ipv4)
*/
static void sn_fmt_addr(uint16_t row, char *p_buf) {
const sntp_cfg_t *cfg = sntp_cfg_get();
const uint8_t *field = (row == SN_ADDR_ROW_MIN) ? cfg->server_pri : cfg->server_bak;
net_cfg_fmt_ipv4(field, p_buf, UI_BUF_STRLEN);
}
/*
* 函数功能:动态行刷新(逐行取名/值)
* 入口参数:row - 行下标(0..4) uint16_t
* name - 名缓冲(≥ UI_BUF_STRLEN,框架栈缓冲) char *
* value - 值缓冲(≥ UI_BUF_STRLEN,框架栈缓冲) char *
* 返回值:无
* 限定条件:页面打开(框架绘制链内逐行调用)
* 函数说明:周期行显示 RAM 副本原值(不做带外回显换算——带外值经
* sntp_cfg_poll_min() 读回 60 属消费侧自愈口径,UI 忠实显示
* 用户写入值,避免"保存后数字自己变了");
* 保存行值随保存交接状态(net_cfg_save_state)显示 ENTER/WAIT/OK/ERR
*/
static void sn_refresh(uint16_t row, char *name, char *value) {
const sntp_cfg_t *cfg = sntp_cfg_get();
name[0] = '\0';
value[0] = '\0';
switch (row) {
case SN_EN_ROW:
(void)memcpy(name, "启用", sizeof("启用"));
if (cfg->enable != 0u) {
(void)memcpy(value, "开启", sizeof("开启"));
}
else {
(void)memcpy(value, "关闭", sizeof("关闭"));
}
break;
case SN_ADDR_ROW_MIN:
(void)memcpy(name, "主服务器", sizeof("主服务器"));
sn_fmt_addr(SN_ADDR_ROW_MIN, value);
break;
case SN_ADDR_ROW_MAX:
(void)memcpy(name, "备服务器", sizeof("备服务器"));
sn_fmt_addr(SN_ADDR_ROW_MAX, value);
break;
case SN_POLL_ROW:
(void)memcpy(name, "周期(分)", sizeof("周期(分)"));
(void)lv_snprintf(value, UI_BUF_STRLEN, "%u", (unsigned)cfg->poll_min);
break;
case SN_SAVE_ROW:
(void)memcpy(name, "保存", sizeof("保存"));
switch (net_cfg_save_state()) {
case NET_CFG_SAVE_ST_WAIT:
(void)memcpy(value, "WAIT", sizeof("WAIT"));
break;
case NET_CFG_SAVE_ST_OK:
(void)memcpy(value, "OK", sizeof("OK"));
break;
case NET_CFG_SAVE_ST_FAIL:
(void)memcpy(value, "ERR", sizeof("ERR"));
break;
default:
(void)memcpy(value, "ENTER", sizeof("ENTER"));
break;
}
break;
default:
break;
}
}
/*
* 函数功能:切换启用开关(只改 RAM 副本)
* 入口参数:无
* 返回值:无
* 限定条件:可编辑权限(key_cb 已把门)
*/
static void sn_toggle_enable(void) {
sntp_cfg_t cfg = *sntp_cfg_get();
cfg.enable = (cfg.enable != 0u) ? 0u : 1u;
sntp_cfg_set(&cfg);
}
/*
* 函数功能:调整被编辑地址行的活动字节(0..255 环绕,实时落 RAM 副本)
* 入口参数:row - 地址行下标(1..2) uint16_t
* delta - 调整方向(>0 = +1,<0 = -1) int8_t
* 返回值:无
* 限定条件:编辑模式(s_cursor 有效)
* 函数说明:只改 RAM 副本;写盘须显式经保存行投递 net_cfg_save_request
* (net_cfg_adjust_octet 同口径)
*/
static void sn_adjust_octet(uint16_t row, int8_t delta) {
sntp_cfg_t cfg = *sntp_cfg_get();
uint8_t *field = (row == SN_ADDR_ROW_MIN) ? cfg.server_pri : cfg.server_bak;
field[s_cursor] = (uint8_t)((field[s_cursor] + ((delta > 0) ? 1u : 255u)) % 256u);
sntp_cfg_set(&cfg);
}
/*
* 函数功能:调整周期(分钟)值(实时落 RAM 副本)
* 入口参数:step - 步长(有符号;±1 在 1..1440 带内环绕,±60 触带钳位) int16_t
* 返回值:无
* 限定条件:编辑模式(选中周期行)
*/
static void sn_adjust_poll(int16_t step) {
sntp_cfg_t cfg = *sntp_cfg_get();
int32_t v = (int32_t)cfg.poll_min;
if (v < (int32_t)SN_POLL_MIN || v > (int32_t)SN_POLL_MAX) {
v = (int32_t)SNTP_CFG_DEF_POLL_MIN; /* 带外起点先落回默认再步进 */
}
if (step == 1) {
v = (v >= (int32_t)SN_POLL_MAX) ? (int32_t)SN_POLL_MIN : (v + 1);
}
else if (step == -1) {
v = (v <= (int32_t)SN_POLL_MIN) ? (int32_t)SN_POLL_MAX : (v - 1);
}
else {
v += step; /* ±60 粗调:钳带不环绕(大步进环绕会跨带跳变,失序) */
if (v < (int32_t)SN_POLL_MIN) {
v = (int32_t)SN_POLL_MIN;
}
if (v > (int32_t)SN_POLL_MAX) {
v = (int32_t)SN_POLL_MAX;
}
}
cfg.poll_min = (uint16_t)v;
sntp_cfg_set(&cfg);
}
/*
* 函数功能:页面自绘(编辑模式:选中行值内活动区域反显)
* 入口参数:layer - 绘制层 lv_layer_t *
* clip - 重绘区 const lv_area_t *
* 返回值:无
* 限定条件:页面打开(框架绘制链末尾调用)
* 函数说明:非编辑模式无附加绘制(行/带/页脚全由框架);地址行编辑 = 活动
* 字节反显(同网口设置页),周期行编辑 = 整值反显;
* 1bpp:black=暗底 white=亮字
*/
static void sn_draw_cb(lv_layer_t *layer, const lv_area_t *clip) {
char value[UI_BUF_STRLEN];
lv_area_t box;
uint32_t start;
uint32_t seg_len;
int32_t slot_y;
int32_t x0;
int32_t w;
uint16_t sel;
if (s_editing == 0u || ui_content_page_is_editable() == false) {
return;
}
sel = ui_content_page_selected_row();
if (sel == SN_ADDR_ROW_MIN || sel == SN_ADDR_ROW_MAX) {
sn_fmt_addr(sel, value);
start = (uint32_t)s_cursor * 4u; /* 字节 i 字符偏移 4i(%03u + 点) */
seg_len = 3u;
}
else if (sel == SN_POLL_ROW) {
(void)lv_snprintf(value, UI_BUF_STRLEN, "%u",
(unsigned)sntp_cfg_get()->poll_min);
start = 0u;
seg_len = (uint32_t)strlen(value); /* 周期行:整值反显 */
}
else {
return; /* 防御:开关行/保存行不进编辑态,无光标可画 */
}
slot_y = UI_LIST_TOP + (int32_t)sel * UI_ROW_PITCH;
x0 = SN_RIGHT_X + (int32_t)lv_text_get_width(value, start, LV_FONT_DEFAULT, 0);
w = (int32_t)lv_text_get_width(value + start, seg_len, LV_FONT_DEFAULT, 0);
box.x1 = x0 - 1;
box.x2 = x0 + w;
box.y1 = slot_y;
box.y2 = slot_y + UI_ROW_BAND_H - 1;
if (!sn_areas_intersect(&box, clip)) {
return; /* 编辑光标不在重绘区:跳过(culling) */
}
/* 反显:黑底白字(与选中行白带黑字相反,方能在带内凸显活动段) */
lv_draw_rect_dsc_t fd;
lv_draw_rect_dsc_init(&fd);
fd.base.layer = layer;
fd.bg_color = UI_COL_BG;
fd.bg_opa = LV_OPA_COVER;
lv_draw_rect(layer, &fd, &box);
{
lv_draw_label_dsc_t ld;
char seg[8]; /* 段文本:地址 3 字符 / 周期 ≤4 字符,+ '\0' */
/* 只绘制"段文本"本身(防 LVGL 按 coords 宽折行溢出,见网口设置页同注释) */
memcpy(seg, value + start, seg_len);
seg[seg_len] = '\0';
lv_draw_label_dsc_init(&ld);
ld.base.layer = layer;
ld.text = seg;
ld.text_static = 0u; /* 栈缓冲(同帧有效) */
ld.color = UI_COL_FG;
ld.ofs_x = 1; /* box.x1 = x0-1,+1 = x0(与框架行文本原点一致) */
ld.ofs_y = 0; /* 与行文本垂直对齐(text_dy = 0) */
lv_draw_label(layer, &ld, &box);
}
}
/*
* 函数功能:页面按键处理(列表页 + 编辑模式)
* 入口参数:key - LVGL 键值 lv_key_t
* 返回值:true - 页已处理(框架跳过内置语义);false - 未处理
* 限定条件:页面打开;经框架键分发调用(uiTask 上下文)
* 函数说明: 1. 编辑模式(地址行/周期行):地址行 ↑↓ 调活动字节(0..255
* 环绕)、←→ 移字节光标(首↔末环绕);周期行 ↑↓ ±1 分带内环绕、
* ←→ ±60 分钳带;ENTER 确认退出编辑(值已实时落 RAM 副本);
* ESC 恒关页(框架保证,未保存调整留在 RAM,下次开页重显、
* 不写盘、重启失效——网口设置页同口径)
* 2. 正常模式:ENTER 启用行 → 直接切换开关;ENTER 地址行/周期行 →
* 进编辑模式;ENTER 保存行 → 投递整档保存请求
* net_cfg_save_request(属主任务 defaultTask 写盘,[sntp] 段
* 随档落盘;消费方每轮直读 RAM 副本,网络代次重建自动收口);
* ↑↓ 落框架移选、← 落框架关页;只读态 ENTER 一律忽略
*/
static bool sn_key_cb(lv_key_t key) {
uint16_t sel = ui_content_page_selected_row();
if (s_editing != 0u) {
if (sel < SN_ADDR_ROW_MIN || sel > SN_POLL_ROW) {
s_editing = 0u; /* 防御:编辑中选中行越界(编辑态移选被拦截,不应发生) */
return false;
}
switch (key) {
case LV_KEY_UP:
if (sel == SN_POLL_ROW) {
sn_adjust_poll(1);
}
else {
sn_adjust_octet(sel, 1);
}
ui_content_page_invalidate();
return true;
case LV_KEY_DOWN:
if (sel == SN_POLL_ROW) {
sn_adjust_poll(-1);
}
else {
sn_adjust_octet(sel, -1);
}
ui_content_page_invalidate();
return true;
case LV_KEY_LEFT:
if (sel == SN_POLL_ROW) {
sn_adjust_poll(-(int16_t)SN_POLL_BIG_STEP);
}
else {
s_cursor = (uint8_t)((s_cursor + 3u) % 4u); /* 光标左移,首绕末 */
}
ui_content_page_invalidate();
return true;
case LV_KEY_RIGHT:
if (sel == SN_POLL_ROW) {
sn_adjust_poll((int16_t)SN_POLL_BIG_STEP);
}
else {
s_cursor = (uint8_t)((s_cursor + 1u) % 4u); /* 光标右移,末绕首 */
}
ui_content_page_invalidate();
return true;
case LV_KEY_ENTER:
s_editing = 0u; /* 确认(值已在 RAM 副本);退出编辑 */
ui_content_page_invalidate();
return true;
default:
return false; /* ESC 框架恒关页;其余键落内置忽略 */
}
}
if (key == LV_KEY_ENTER && ui_content_page_is_editable() != false) {
if (sel == SN_EN_ROW) {
sn_toggle_enable();
ui_content_page_invalidate();
return true;
}
if (sel >= SN_ADDR_ROW_MIN && sel <= SN_POLL_ROW) {
s_editing = 1u;
s_cursor = 0u;
ui_content_page_invalidate();
return true;
}
if (sel == SN_SAVE_ROW) {
(void)net_cfg_save_request(); /* 投递整档保存请求:属主任务写盘 */
ui_content_page_invalidate();
return true;
}
}
return false;
}
/*
* 函数功能:重置页面运行态
* 入口参数:无
* 返回值:无
* 限定条件:框架 ui_content_page_open 内调用(开页后一次)
* 函数说明:页面模块静态态不随屏/容器建毁自动复位,须自重置(编辑态/光标)
*/
static void sn_init_cb(void) {
s_editing = 0u;
s_cursor = 0u;
}
/*
* 页面描述符(外部链接:生成物 ui_page_registry.c 按菜单树 #page=sntp 绑定引用):
* 列表页(5 行 1 页,动态值)+ 可编辑(edit_capable,还须修改权限)
* + ← 拦截(编辑模式移字节光标/粗调周期;ESC 恒不可拦截)
*/
const content_page_def_t ui_page_sntp_desc = {
.id = "sntp",
.title = "SNTP设置",
.selectable = 1u,
.key_intercept = 1u,
.edit_capable = 1u,
.top_y = UI_LIST_TOP,
.pitch = UI_ROW_PITCH,
.band_h = UI_ROW_BAND_H,
.text_dy = 0,
.left_x = SN_LEFT_X,
.right_x = SN_RIGHT_X,
.rows_per_page = SN_ROW_COUNT,
.row_count = SN_ROW_COUNT,
.refresh = sn_refresh,
.init_cb = sn_init_cb,
.draw_cb = sn_draw_cb,
.key_cb = sn_key_cb,
};
+30
View File
@@ -0,0 +1,30 @@
/*
* 模块名称:UI Page SNTP (SNTP设置内容页)
* 模块功能:SNTP设置叶子节点的内容页描述符(5 行列表:启用/主服务器/
* 备服务器/周期(分) + 保存行;可编辑页:修改权限下 ENTER 进编辑、
* 地址行 ↑↓ 调字节 ←→ 移光标、周期行 ↑↓ ±1 ←→ ±60、
* 保存行 ENTER 投递整档保存请求,属主任务写盘)
* 适用平台:STM32F407ZGTx / PC 模拟器(两端共用)
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(板端 SNTP 客户端接入)
*/
#ifndef UI_PAGE_SNTP_H
#define UI_PAGE_SNTP_H
#include "ui_content_page.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 页面描述符(生成物 ui_page_registry.c 按菜单树 #page=sntp 注记绑定引用;.id = "sntp") */
extern const content_page_def_t ui_page_sntp_desc;
#ifdef __cplusplus
}
#endif
#endif /* UI_PAGE_SNTP_H */
+19
View File
@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+845
View File
@@ -0,0 +1,845 @@
/*
* coreSNTP
* Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @file core_sntp_serializer.c
* @brief Implementation of the Serializer API of the coreSNTP library.
*/
/* Standard includes. */
#include <string.h>
#include <stdbool.h>
#include <assert.h>
/* Include API header. */
#include "core_sntp_serializer.h"
#include "core_sntp_config_defaults.h"
/**
* @brief The version of SNTP supported by the coreSNTP library by complying
* with the SNTPv4 specification defined in [RFC 4330](https://tools.ietf.org/html/rfc4330).
*/
#define SNTP_VERSION ( 4U )
/**
* @brief The bit mask for the "Mode" information in the first byte of an SNTP packet.
* The "Mode" field occupies bits 0-2 of the byte.
* @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
* for more information.
*/
#define SNTP_MODE_BITS_MASK ( 0x07U )
/**
* @brief The value indicating a "client" in the "Mode" field of an SNTP packet.
* @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
* for more information.
*/
#define SNTP_MODE_CLIENT ( 3U )
/**
* @brief The value indicating a "server" in the "Mode" field of an SNTP packet.
* @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
* for more information.
*/
#define SNTP_MODE_SERVER ( 4U )
/**
* @brief The position of the least significant bit of the "Leap Indicator" field
* in first byte of an SNTP packet. The "Leap Indicator" field occupies bits 6-7 of the byte.
* @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
* for more information.
*/
#define SNTP_LEAP_INDICATOR_LSB_POSITION ( 6 )
/**
* @brief Value of Stratum field in SNTP packet representing a Kiss-o'-Death message
* from server.
*/
#define SNTP_KISS_OF_DEATH_STRATUM ( 0U )
/**
* @brief The position of least significant bit of the "Version" information
* in the first byte of an SNTP packet. "Version" field occupies bits 3-5 of
* the byte.
* @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
* for more information.
*/
#define SNTP_VERSION_LSB_POSITION ( 3 )
/**
* @brief The integer value of the Kiss-o'-Death ASCII code, "DENY", used
* for comparison with data in an SNTP response.
* @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8)
* for more information.
*/
#define KOD_CODE_DENY_UINT_VALUE ( 0x44454e59U )
/**
* @brief The integer value of the Kiss-o'-Death ASCII code, "RSTR", used
* for comparison with data in an SNTP response.
* @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8)
* for more information.
*/
#define KOD_CODE_RSTR_UINT_VALUE ( 0x52535452U )
/**
* @brief The integer value of the Kiss-o'-Death ASCII code, "RATE", used
* for comparison with data in an SNTP response.
* @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8)
* for more information.
*/
#define KOD_CODE_RATE_UINT_VALUE ( 0x52415445U )
/**
* @brief Macro to represent exactly half of the total number of seconds in an NTP era.
* As 32 bits are used to represent the "seconds" part of an SNTP timestamp, the half of
* the total range of seconds in an NTP era is 2^31 seconds, which represents ~68 years.
*
* @note This macro represents the edge case of calculating the client system clock-offset
* relative to server time as the library ASSUMES that the client and server times are within
* 2^31 seconds apart in duration. This is done to support calculating clock-offset for the
* cases when server and client systems are in adjacent NTP eras, which can occur when NTP time
* wraps around in 2036, and the relative NTP era presence of client and server times is
* determined based on comparing first order difference values between all possible NTP era
* configurations of the systems.
*/
#define CLOCK_OFFSET_MAX_TIME_DIFFERENCE ( ( ( ( int64_t ) INT32_MAX + 1 ) * 1000 ) )
/**
* @brief Macro to represent the total number of milliseconds that are represented in an
* NTP era period. This macro represents a duration of ~136 years.
*
* @note Rationale for calculation: The "seconds" part of an NTP timestamp is represented in
* unsigned 32 bit width, thus, the total range of seconds it represents is 2^32,
* i.e. (UINT32_MAX + 1).
*/
#define TOTAL_MILLISECONDS_IN_NTP_ERA ( ( ( int64_t ) UINT32_MAX + 1 ) * 1000 )
/**
* @brief Structure representing an SNTP packet header.
* For more information on SNTP packet format, refer to
* [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4).
*
* @note This does not include extension fields for authentication data
* for secure SNTP communication. Authentication data follows the
* packet header represented by this structure.
*/
typedef struct SntpPacket
{
/**
* @brief Bits 6-7 leap indicator, bits 3-5 are version number, bits 0-2 are mode
*/
uint8_t leapVersionMode;
/**
* @brief stratum
*/
uint8_t stratum;
/**
* @brief poll interval
*/
uint8_t poll;
/**
* @brief precision
*/
uint8_t precision;
/**
* @brief root delay
*/
uint32_t rootDelay;
/**
* @brief root dispersion
*/
uint32_t rootDispersion;
/**
* @brief reference ID
*/
uint32_t refId;
/**
* @brief reference time
*/
SntpTimestamp_t refTime;
/**
* @brief origin timestamp
*/
SntpTimestamp_t originTime;
/**
* @brief receive timestamp
*/
SntpTimestamp_t receiveTime;
/**
* @brief transmit timestamp
*/
SntpTimestamp_t transmitTime;
} SntpPacket_t;
/**
* @brief Utility macro to fill 32-bit integer in word-sized
* memory in network byte (or Big Endian) order.
*
* @param[out] pWordMemory Pointer to the word-sized memory in which
* the 32-bit integer will be filled.
* @param[in] data The 32-bit integer to fill in the @p wordMemory
* in network byte order.
*
* @note This utility ensures that data is filled in memory
* in expected network byte order, as an assignment operation
* (like *pWordMemory = word) can cause undesired side-effect
* of network-byte ordering getting reversed on Little Endian platforms.
*/
static void fillWordMemoryInNetworkOrder( uint32_t * pWordMemory,
uint32_t data )
{
uint8_t * pByteMemory = ( uint8_t * ) pWordMemory;
assert( pWordMemory != NULL );
pByteMemory[ 0 ] = ( uint8_t ) ( data >> 24 );
pByteMemory[ 1 ] = ( uint8_t ) ( ( data >> 16 ) & 0x000000FFU );
pByteMemory[ 2 ] = ( uint8_t ) ( ( data >> 8 ) & 0x000000FFU );
pByteMemory[ 3 ] = ( uint8_t ) ( ( data ) & 0x000000FFU );
}
/**
* @brief Utility macro to generate a 32-bit integer from memory containing
* integer in network (or Big Endian) byte order.
* @param[in] ptr Pointer to the memory containing 32-bit integer in network
* byte order.
*
* @return The host representation of the 32-bit integer in the passed word
* memory.
*/
static uint32_t readWordFromNetworkByteOrderMemory( const uint32_t * ptr )
{
const uint8_t * pMemStartByte = ( const uint8_t * ) ptr;
assert( ptr != NULL );
return ( uint32_t ) ( ( ( ( uint32_t ) pMemStartByte[ 0 ] ) << 24 ) |
( 0x00FF0000U & ( ( ( uint32_t ) pMemStartByte[ 1 ] ) << 16 ) ) |
( 0x0000FF00U & ( ( ( uint32_t ) pMemStartByte[ 2 ] ) << 8 ) ) |
( ( uint32_t ) pMemStartByte[ 3 ] ) );
}
/**
* @brief Utility to return absolute (or positively signed) value of an signed
* 64 bit integer.
*
* @param[in] value The integer to return the absolute value of.
*
* @return The absolute value of @p value.
*/
static int64_t absoluteOf( int64_t value )
{
return ( value >= 0 ) ? value : ( ( int64_t ) 0 - value );
}
/**
* @brief Utility to determine whether a timestamp represents a zero
* timestamp value.
*
* @note This utility is used to determine whether a timestamp value is
* invalid. According to the SNTPv4 specification, a zero timestamp value
* is considered invalid.
*
* @param[in] pTime The timestamp whose value is to be inspected for
* zero value.
*
* @return `true` if the timestamp is zero; otherwise `false`.
*/
static bool isZeroTimestamp( const SntpTimestamp_t * pTime )
{
bool isSecondsZero = ( pTime->seconds == 0U ) ? true : false;
bool isFractionsZero = ( pTime->fractions == 0U ) ? true : false;
return( isSecondsZero && isFractionsZero );
}
/**
* @brief Utility to convert the "fractions" part of an SNTP timestamp to milliseconds
* duration of time.
* @param[in] fractions The fractions value.
*
* @return The milliseconds equivalent of the @p fractions value.
*/
static uint32_t fractionsToMs( uint32_t fractions )
{
return( fractions / ( 1000U * SNTP_FRACTION_VALUE_PER_MICROSECOND ) );
}
/**
* @brief Utility to safely calculate difference between server and client timestamps and
* return the difference in the resolution of milliseconds as a signed 64 bit integer.
* The calculated value represents the effective subtraction as ( @p serverTimeSec - @p clientTimeSec ).
*
* @note This utility SUPPORTS the cases of server and client timestamps being in different NTP eras,
* and ASSUMES that the server and client systems are within 68 years of each other.
* To handle the case of different NTP eras, this function calculates difference values for all
* possible combinations of NTP eras of server and client times (i.e. 1. both timestamps in same era,
* 2. server timestamp one era ahead, and 3. client timestamp being one era ahead), and determines
* the NTP era configuration by choosing the difference value of the smallest absolute value.
*
* @param[in] pServerTime The server timestamp.
* @param[in] pClientTime The client timestamp.
*
* @return The calculated difference between server and client times as a signed 64 bit integer.
*/
static int64_t safeTimeDifference( const SntpTimestamp_t * pServerTime,
const SntpTimestamp_t * pClientTime )
{
int64_t eraAdjustedDiff = 0;
/* Convert the timestamps into 64 bit signed integer values of milliseconds. */
int64_t serverTime = ( ( int64_t ) pServerTime->seconds * 1000 ) + ( int64_t ) fractionsToMs( pServerTime->fractions );
int64_t clientTime = ( ( int64_t ) pClientTime->seconds * 1000 ) + ( int64_t ) fractionsToMs( pClientTime->fractions );
/* The difference between the 2 timestamps is calculated by determining the whether the timestamps
* are present in the same NTP era or adjacent NTP eras (i.e. the NTP timestamp overflow case). */
/* First, calculate the first order time difference assuming that server and client times
* are in the same NTP era. */
int64_t diffWithNoEraAdjustment = serverTime - clientTime;
/* Store the absolute value of the time difference which will be used for comparison with
* different cases of relative NTP era configuration of client and server times. */
int64_t absSameEraDiff = absoluteOf( diffWithNoEraAdjustment );
/* If the absolute difference value is 2^31 seconds, it means that the server and client times are
* away by exactly half the range of SNTP timestamp "second" values representable in unsigned 32 bits.
* In such a case, irrespective of whether the 2 systems exist in the same or adjacent NTP eras, the
* time difference calculated between the systems will ALWAYS yield the same value when viewed from
* all NTP era configurations of the times.
* For such a case, we will ASSUME that the server time is AHEAD of client time, and thus, generate
* a positive clock-offset value.
*/
if( absSameEraDiff == CLOCK_OFFSET_MAX_TIME_DIFFERENCE )
{
/* It does not matter whether server and client are in the same era for this
* special case as the difference value for both same and adjacent eras will yield
* the same absolute value of 2^31.*/
eraAdjustedDiff = CLOCK_OFFSET_MAX_TIME_DIFFERENCE;
}
else
{
/* Determine if server time belongs to an NTP era different than the client time, and accordingly
* choose the 64 bit representation of the first order difference to account for the era.
* The logic for determining the relative era presence of the timestamps is by calculating the
* first order difference (of "Server Time - Client Time") for all the 3 different era combinations
* (1. both timestamps in same era, 2. server time one era ahead, 3. client time one era ahead)
* and choosing the NTP era configuration that has the smallest first order difference value.
*/
int64_t diffWithServerEraAdjustment = serverTime + TOTAL_MILLISECONDS_IN_NTP_ERA -
clientTime; /* This helps determine whether server is an
* era ahead of client time. */
int64_t diffWithClientEraAdjustment = serverTime -
( TOTAL_MILLISECONDS_IN_NTP_ERA + clientTime ); /* This helps determine whether server is an
* era behind of client time. */
/* Store the absolute value equivalents of all the time difference configurations
* for easier comparison to smallest value from them. */
int64_t absServerEraAheadDiff = absoluteOf( diffWithServerEraAdjustment );
int64_t absClientEraAheadDiff = absoluteOf( diffWithClientEraAdjustment );
/* Determine the correct relative era of client and server times by checking which era
* configuration of difference value represents the least difference. */
if( ( absSameEraDiff <= absServerEraAheadDiff ) && ( absSameEraDiff <= absClientEraAheadDiff ) )
{
/* Both server and client times are in the same era. */
eraAdjustedDiff = diffWithNoEraAdjustment;
}
/* Check if server time is an NTP era ahead of client time. */
else if( absServerEraAheadDiff < absSameEraDiff )
{
/* Server time is in NTP era 1 while client time is in NTP era 0. */
eraAdjustedDiff = diffWithServerEraAdjustment;
}
/* Now, we know that the client time is an era ahead of server time. */
else
{
/* Server time is in NTP era 0 while client time is in NTP era 1. */
eraAdjustedDiff = diffWithClientEraAdjustment;
}
}
return eraAdjustedDiff;
}
/**
* @brief Utility to calculate clock offset of system relative to the
* server using the on-wire protocol specified in the NTPv4 specification.
* For more information on on-wire protocol, refer to
* [RFC 5905 Section 8](https://tools.ietf.org/html/rfc5905#section-8).
*
* @note The following diagram explains the calculation of the clock
* offset:
*
* T2 T3
* --------------------------------- <----- *SNTP/NTP server*
* /\ \
* / \
* Request* / \ *Response*
* / \/
* --------------------------------- <----- *SNTP client*
* T1 T4
*
* The four most recent timestamps, T1 through T4, are used to compute
* the clock offset of SNTP client relative to the server where:
*
* T1 = Client Request Transmit Time
* T2 = Server Receive Time (of client request)
* T3 = Server Response Transmit Time
* T4 = Client Receive Time (of server response)
*
* Clock Offset = T(NTP/SNTP server) - T(SNTP client)
* = [( T2 - T1 ) + ( T3 - T4 )]
* ---------------------------
* 2
*
* @note Both NTPv4 and SNTPv4 specifications suggest calculating the
* clock offset value, if possible. As the timestamp format uses 64 bit
* integer and there exist 2 orders of arithmetic calculations on the
* timestamp values (subtraction followed by addition as shown in the
* diagram above), the clock offset for the system can be calculated
* ONLY if the value can be represented in 62 significant bits and 2 sign
* bits i.e. if the system clock is within 34 years (in the future or past)
* of the server time.
*
* @param[in] pClientTxTime The system time of sending the SNTP request.
* This is the same as "T1" in the above diagram.
* @param[in] pServerRxTime The server time of receiving the SNTP request
* packet from the client. This is the same as "T2" in the above diagram.
* @param[in] pServerTxTime The server time of sending the SNTP response
* packet. This is the same as "T3" in the above diagram.
* @param[in] pClientRxTime The system time of receiving the SNTP response
* from the server. This is the same as "T4" in the above diagram.
* @param[out] pClockOffset This will be filled with the calculated offset value
* of the system clock relative to the server time with the assumption that the
* system clock is within 68 years of server time.
*/
static void calculateClockOffset( const SntpTimestamp_t * pClientTxTime,
const SntpTimestamp_t * pServerRxTime,
const SntpTimestamp_t * pServerTxTime,
const SntpTimestamp_t * pClientRxTime,
int64_t * pClockOffset )
{
/* Variable for storing the first-order difference between timestamps. */
int64_t firstOrderDiffSend = 0;
int64_t firstOrderDiffRecv = 0;
assert( pClientTxTime != NULL );
assert( pServerRxTime != NULL );
assert( pServerTxTime != NULL );
assert( pClientRxTime != NULL );
assert( pClockOffset != NULL );
/* Perform first order difference of timestamps on the network send path i.e. T2 - T1.
* Note: The calculated difference value will always represent years in the range of
*[-68 years, +68 years]. */
firstOrderDiffSend = safeTimeDifference( pServerRxTime, pClientTxTime );
/* Perform first order difference of timestamps on the network receive path i.e. T3 - T4.
* Note: The calculated difference value will always represent years in the range of
*[-68 years, +68 years]. */
firstOrderDiffRecv = safeTimeDifference( pServerTxTime, pClientRxTime );
/* Now calculate the system clock-offset relative to server time as the average of the
* first order difference of timestamps in both directions of network path.
* Note: This will ALWAYS represent offset in the range of [-68 years, +68 years]. */
*pClockOffset = ( firstOrderDiffSend + firstOrderDiffRecv ) / 2;
}
/**
* @brief Parse a SNTP response packet by determining whether it is a rejected
* or accepted response to an SNTP request, and accordingly, populate the
* @p pParsedResponse parameter with the parsed data.
*
* @note If the server has rejected the request with the a Kiss-o'-Death message,
* then this function will set the associated rejection code in the output parameter
* while setting the remaining members to zero.
* If the server has accepted the time request, then the function will set the
* rejectedResponseCode member of the output parameter to #SNTP_KISS_OF_DEATH_CODE_NONE,
* and set the other the members with appropriate data extracted from the response
* packet.
*
* @param[in] pResponsePacket The SNTP response packet from server to parse.
* @param[in] pRequestTxTime The system time (in SNTP timestamp format) of
* sending the SNTP request to server.
* @param[in] pResponseRxTime The system time (in SNTP timestamp format) of
* receiving the SNTP response from server.
* @param[out] pParsedResponse The parameter that will be populated with data
* parsed from the response packet, @p pResponsePacket.
*
* @return This function returns one of the following:
* - #SntpSuccess if the server response does not represent a Kiss-o'-Death
* message.
* - #SntpRejectedResponseChangeServer if the server rejected with a code
* indicating that client cannot be retry requests to it.
* - #SntpRejectedResponseRetryWithBackoff if the server rejected with a code
* indicating that client should back-off before retrying request.
* - #SntpRejectedResponseOtherCode if the server rejected with a code
* other than "DENY", "RSTR" and "RATE".
*/
static SntpStatus_t parseValidSntpResponse( const SntpPacket_t * pResponsePacket,
const SntpTimestamp_t * pRequestTxTime,
const SntpTimestamp_t * pResponseRxTime,
SntpResponseData_t * pParsedResponse )
{
SntpStatus_t status = SntpSuccess;
assert( pResponsePacket != NULL );
assert( pResponseRxTime != NULL );
assert( pParsedResponse != NULL );
/* Clear the output parameter memory to zero. */
( void ) memset( pParsedResponse, 0, sizeof( *pParsedResponse ) );
/* Determine if the server has accepted or rejected the request for time. */
if( pResponsePacket->stratum == SNTP_KISS_OF_DEATH_STRATUM )
{
/* Server has sent a Kiss-o'-Death message i.e. rejected the request. */
/* Extract the kiss-code sent by the server from the "Reference ID" field
* of the SNTP packet. */
pParsedResponse->rejectedResponseCode =
readWordFromNetworkByteOrderMemory( &pResponsePacket->refId );
/* Determine the return code based on the Kiss-o'-Death code. */
switch( pParsedResponse->rejectedResponseCode )
{
case KOD_CODE_DENY_UINT_VALUE:
case KOD_CODE_RSTR_UINT_VALUE:
status = SntpRejectedResponseChangeServer;
break;
case KOD_CODE_RATE_UINT_VALUE:
status = SntpRejectedResponseRetryWithBackoff;
break;
default:
status = SntpRejectedResponseOtherCode;
break;
}
}
else
{
/* Server has responded successfully to the time request. */
SntpTimestamp_t serverRxTime;
/* Map of integer value to SntpLeapSecondInfo_t enumeration type for the
* "Leap Indicator" field in the first byte of an SNTP packet.
* Note: This map is used to not violate MISRA Rule 10.5 when directly
* converting an integer to enumeration type.
*/
const SntpLeapSecondInfo_t leapIndicatorTypeMap[] =
{
NoLeapSecond,
LastMinuteHas61Seconds,
LastMinuteHas59Seconds,
AlarmServerNotSynchronized
};
/* Set the Kiss-o'-Death code value to NULL as server has responded favorably
* to the time request. */
pParsedResponse->rejectedResponseCode = SNTP_KISS_OF_DEATH_CODE_NONE;
/* Fill the output parameter with the server time which is the
* "transmit" time in the response packet. */
pParsedResponse->serverTime.seconds =
readWordFromNetworkByteOrderMemory( &pResponsePacket->transmitTime.seconds );
pParsedResponse->serverTime.fractions =
readWordFromNetworkByteOrderMemory( &pResponsePacket->transmitTime.fractions );
/* Extract information of any upcoming leap second from the response. */
pParsedResponse->leapSecondType = leapIndicatorTypeMap[
( pResponsePacket->leapVersionMode >> SNTP_LEAP_INDICATOR_LSB_POSITION ) ];
/* Store the "receive" time in SNTP response packet in host order. */
serverRxTime.seconds =
readWordFromNetworkByteOrderMemory( &pResponsePacket->receiveTime.seconds );
serverRxTime.fractions =
readWordFromNetworkByteOrderMemory( &pResponsePacket->receiveTime.fractions );
/* Calculate system clock offset relative to server time, if possible, within
* the 64 bit integer width of the SNTP timestamp. */
calculateClockOffset( pRequestTxTime,
&serverRxTime,
&pParsedResponse->serverTime,
pResponseRxTime,
&pParsedResponse->clockOffsetMs );
}
return status;
}
SntpStatus_t Sntp_SerializeRequest( SntpTimestamp_t * pRequestTime,
uint32_t randomNumber,
void * pBuffer,
size_t bufferSize )
{
SntpStatus_t status = SntpSuccess;
if( pRequestTime == NULL )
{
status = SntpErrorBadParameter;
}
else if( pBuffer == NULL )
{
status = SntpErrorBadParameter;
}
else if( bufferSize < SNTP_PACKET_BASE_SIZE )
{
status = SntpErrorBufferTooSmall;
}
/* Zero timestamps for client request time is not allowed to protect against
* attack spoofing server response containing zero value for "originate timestamp".
* Note: In SNTP/NTP communication, the "originate timestamp" of a valid server response
* matches the "transmit timestamp" in corresponding client request packet. */
else if( isZeroTimestamp( pRequestTime ) == true )
{
status = SntpErrorBadParameter;
}
else
{
/* MISRA Ref 11.5.1 [Void pointer assignment] */
/* More details at: https://github.com/FreeRTOS/coreSNTP/blob/main/MISRA.md#rule-115 */
/* coverity[misra_c_2012_rule_11_5_violation] */
SntpPacket_t * pRequestPacket = ( SntpPacket_t * ) pBuffer;
/* Fill the buffer with zero as most fields are zero for a standard SNTP
* request packet.*/
( void ) memset( pBuffer, 0, sizeof( SntpPacket_t ) );
/* Set the first byte of the request packet for "Version" and "Mode" fields */
pRequestPacket->leapVersionMode = 0U /* Leap Indicator */ |
( SNTP_VERSION << SNTP_VERSION_LSB_POSITION ) /* Version Number */ |
SNTP_MODE_CLIENT /* Mode */;
/* Add passed random number to non-significant bits of the fractions part
* of the transmit timestamp.
* This is suggested by the SNTPv4 (and NTPv4) specification(s)
* to protect against replay attacks. Refer to RFC 4330 Section 3 for
* more information.
* Adding random bits to the least significant 16 bits of the fractions
* part of the timestamp affects only ~15 microseconds of information
* (calculated as 0xFFFF * 232 picoseconds).
*/
pRequestTime->fractions = ( pRequestTime->fractions
| ( randomNumber >> 16 ) );
/* Update the request buffer with request timestamp in network byte order. */
fillWordMemoryInNetworkOrder( &pRequestPacket->transmitTime.seconds,
pRequestTime->seconds );
fillWordMemoryInNetworkOrder( &pRequestPacket->transmitTime.fractions,
pRequestTime->fractions );
}
return status;
}
SntpStatus_t Sntp_DeserializeResponse( const SntpTimestamp_t * pRequestTime,
const SntpTimestamp_t * pResponseRxTime,
const void * pResponseBuffer,
size_t bufferSize,
SntpResponseData_t * pParsedResponse )
{
SntpStatus_t status = SntpSuccess;
/* MISRA Ref 11.5.1 [Void pointer assignment] */
/* More details at: https://github.com/FreeRTOS/coreSNTP/blob/main/MISRA.md#rule-115 */
/* coverity[misra_c_2012_rule_11_5_violation] */
const SntpPacket_t * pResponsePacket = ( const SntpPacket_t * ) pResponseBuffer;
if( ( pRequestTime == NULL ) || ( pResponseRxTime == NULL ) ||
( pResponseBuffer == NULL ) || ( pParsedResponse == NULL ) )
{
status = SntpErrorBadParameter;
}
else if( bufferSize < SNTP_PACKET_BASE_SIZE )
{
status = SntpErrorBufferTooSmall;
}
/* Zero timestamps for client request time is not allowed to protect against
* attack spoofing server response containing zero value for "originate timestamp".
* Note: In SNTP/NTP communication, the "originate timestamp" of a valid server response
* matches the "transmit timestamp" in corresponding client request packet. */
else if( isZeroTimestamp( pRequestTime ) == true )
{
status = SntpErrorBadParameter;
}
/* Check if the packet represents a server in the "Mode" field. */
else if( ( pResponsePacket->leapVersionMode & SNTP_MODE_BITS_MASK ) != SNTP_MODE_SERVER )
{
status = SntpInvalidResponse;
}
/* Check if any of the timestamps in the response packet are zero, which is invalid.
* Note: This is done to protect against a nuanced server spoofing attack where if the
* SNTP client resets its internal state of "Client transmit timestamp" (OR "originate
* timestamp" from server perspective) to zero as a protection against replay attack, an
* an attacker with this knowledge of the client operation can spoof a server response
* containing the "originate timestamp" as zero. Thus, to protect against such attack,
* a server response packet with any zero timestamp is rejected. */
else if( ( isZeroTimestamp( &pResponsePacket->originTime ) == true ) ||
( isZeroTimestamp( &pResponsePacket->receiveTime ) == true ) ||
( isZeroTimestamp( &pResponsePacket->transmitTime ) == true ) )
{
status = SntpInvalidResponse;
}
/* Validate that the server has sent the client's request timestamp in the
* "originate" timestamp field of the response. */
else if( ( pRequestTime->seconds !=
readWordFromNetworkByteOrderMemory( &pResponsePacket->originTime.seconds ) ) ||
( pRequestTime->fractions !=
readWordFromNetworkByteOrderMemory( &pResponsePacket->originTime.fractions ) ) )
{
status = SntpInvalidResponse;
}
else
{
/* As the response packet is valid, parse more information from it and
* populate the output parameter. */
status = parseValidSntpResponse( pResponsePacket,
pRequestTime,
pResponseRxTime,
pParsedResponse );
}
return status;
}
SntpStatus_t Sntp_CalculatePollInterval( uint16_t clockFreqTolerance,
uint16_t desiredAccuracy,
uint32_t * pPollInterval )
{
SntpStatus_t status = SntpSuccess;
if( ( clockFreqTolerance == 0U ) || ( desiredAccuracy == 0U ) || ( pPollInterval == NULL ) )
{
status = SntpErrorBadParameter;
}
else
{
uint32_t exactIntervalForAccuracy = 0U;
uint8_t log2PollInterval = 0U;
/* Calculate the poll interval required for achieving the exact desired clock accuracy
* with the following formulae:
*
* System Clock Drift Rate ( microseconds / second ) = Clock Frequency Tolerance (in PPM )
* Maximum Clock Drift ( milliseconds ) = Desired Accuracy ( milliseconds )
*
* Poll Interval ( seconds ) = Maximum Clock Drift
* ---------------------------
* System Clock Drift Rate
*
* = Maximum Drift ( milliseconds ) * 1000 ( microseconds / millisecond )
* ------------------------------------------------------------------------
* System Clock Drift Rate ( microseconds / second )
*
* = Desired Accuracy * 1000
* ------------------------------
* Clock Frequency Tolerance
*/
exactIntervalForAccuracy = ( ( uint32_t ) desiredAccuracy * 1000U ) / clockFreqTolerance;
/* Check if calculated poll interval value falls in the supported range of seconds. */
if( exactIntervalForAccuracy == 0U )
{
/* Poll interval value is less than 1 second, and is not supported by the function. */
status = SntpZeroPollInterval;
}
else
{
/* To calculate the log 2 value of the exact poll interval value, first determine the highest
* bit set in the value. */
while( exactIntervalForAccuracy != 0U )
{
log2PollInterval++;
exactIntervalForAccuracy /= 2U;
}
/* Convert the highest bit in the exact poll interval value to the nearest integer
* value lower or equal to the log2 of the exact poll interval value. */
log2PollInterval--;
/* Calculate the poll interval as the closest exponent of 2 value that achieves
* equal or higher accuracy than the desired accuracy. */
*pPollInterval = ( ( ( uint32_t ) 1U ) << log2PollInterval );
}
}
return status;
}
SntpStatus_t Sntp_ConvertToUnixTime( const SntpTimestamp_t * pSntpTime,
uint32_t * pUnixTimeSecs,
uint32_t * pUnixTimeMicrosecs )
{
SntpStatus_t status = SntpSuccess;
if( ( pSntpTime == NULL ) || ( pUnixTimeSecs == NULL ) || ( pUnixTimeMicrosecs == NULL ) )
{
status = SntpErrorBadParameter;
}
if( status == SntpSuccess )
{
/* Handle case when SNTP timestamp is in SNTP era 0 time range
* (i.e. time before 7 Feb 2036 6:28:15 UTC). */
if( pSntpTime->seconds >= SNTP_TIME_AT_UNIX_EPOCH_SECS )
{
*pUnixTimeSecs = pSntpTime->seconds - SNTP_TIME_AT_UNIX_EPOCH_SECS;
}
else
{
/* Handle case when timestamp represents date in SNTP era 1
* (i.e. time from 7 Feb 2036 6:28:16 UTC onwards). */
*pUnixTimeSecs = UNIX_TIME_SECS_AT_SNTP_ERA_1_SMALLEST_TIME + pSntpTime->seconds;
}
/* Convert SNTP fractions to microseconds for UNIX time. */
*pUnixTimeMicrosecs = pSntpTime->fractions / SNTP_FRACTION_VALUE_PER_MICROSECOND;
}
return status;
}
+24
View File
@@ -0,0 +1,24 @@
/*
* 模块名称:coreSNTP 定制配置(本仓 glue 文件,非 vendor 原文)
* 模块功能:core_sntp_config_defaults.h 在**未**定义 SNTP_DO_NOT_USE_CUSTOM_CONFIG
* 时会包含本文件;本仓使用 vendor 库的 serializer 层,默认值即所需
* ——日志宏 LogError/LogWarn/LogInfo/LogDebug 保持 defaults 的编译期
* 空实现:serializer 的失败路径都已有应用层 DBG 判读(time_sntp.c
* 在调用点按返回码记录),库内再开日志只增栈/Flash 不增信息量。
* 错误/丢弃类留痕统一放调用方(App/sync/time_sntp.c),
* 避免 vendor 文件与本仓日志栈耦合。
* 适用平台:STM32F407ZGTx / 主机单测(test/host/sntp)
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(SNTP 客户端接入 #57)
*/
#ifndef CORE_SNTP_CONFIG_H
#define CORE_SNTP_CONFIG_H
/* 本文件刻意不覆盖任何默认宏;如将来需要库内日志,在此映射:
* #define LogError(message) ...
* (注意 vendor 调用形态为 LogError(("..."))——参数自带外括号) */
#endif /* CORE_SNTP_CONFIG_H */
@@ -0,0 +1,144 @@
/*
* coreSNTP
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @file core_sntp_config_defaults.h
* @brief This file represents the default values for the configuration macros
* of the coreSNTP library.
*
* @note This file SHOULD NOT be modified. If custom values are needed for
* any configuration macro, a core_sntp_config.h file should be provided to
* the SNTP library to override the default values defined in this file.
* To build the library with the core_sntp_config.h file, make sure to
* not set the SNTP_DO_NOT_USE_CUSTOM_CONFIG preprocessor macro.
*/
#ifndef CORE_SNTP_CONFIG_DEFAULTS_H_
#define CORE_SNTP_CONFIG_DEFAULTS_H_
/* The macro definition for SNTP_DO_NOT_USE_CUSTOM_CONFIG is for Doxygen
* documentation only. */
/**
* @brief Define this macro to build the SNTP library without the custom config
* file core_sntp_config.h.
*
* Without the custom config, the SNTP library builds with
* default values of config macros defined in core_sntp_config_defaults.h file.
*
* If a custom config is provided, then SNTP_DO_NOT_USE_CUSTOM_CONFIG should not
* be defined.
*/
#ifdef DOXYGEN
#define SNTP_DO_NOT_USE_CUSTOM_CONFIG
#endif
/* SNTP_DO_NOT_USE_CUSTOM_CONFIG allows building the SNTP library
* without a custom config. If a custom config is provided, the
* SNTP_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
#ifndef SNTP_DO_NOT_USE_CUSTOM_CONFIG
#include "core_sntp_config.h"
#endif
/**
* @brief Macro that is called in the SNTP library for logging "Error" level
* messages.
*
* To enable error level logging in the SNTP library, this macro should be mapped to the
* application-specific logging implementation that supports error logging.
*
* @note This logging macro is called in the SNTP library with parameters wrapped in
* double parentheses to be ISO C89/C90 standard compliant. For a reference
* POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
* logging-stack in demos folder of the
* [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C).
*
* <b>Default value</b>: Error logging is turned off, and no code is generated for calls
* to the macro in the SNTP library on compilation.
*/
#ifndef LogError
#define LogError( message )
#endif
/**
* @brief Macro that is called in the SNTP library for logging "Warning" level
* messages.
*
* To enable warning level logging in the SNTP library, this macro should be mapped to the
* application-specific logging implementation that supports warning logging.
*
* @note This logging macro is called in the SNTP library with parameters wrapped in
* double parentheses to be ISO C89/C90 standard compliant. For a reference
* POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
* logging-stack in demos folder of the
* [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
*
* <b>Default value</b>: Warning logs are turned off, and no code is generated for calls
* to the macro in the SNTP library on compilation.
*/
#ifndef LogWarn
#define LogWarn( message )
#endif
/**
* @brief Macro that is called in the SNTP library for logging "Info" level
* messages.
*
* To enable info level logging in the SNTP library, this macro should be mapped to the
* application-specific logging implementation that supports info logging.
*
* @note This logging macro is called in the SNTP library with parameters wrapped in
* double parentheses to be ISO C89/C90 standard compliant. For a reference
* POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
* logging-stack in demos folder of the
* [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
*
* <b>Default value</b>: Info logging is turned off, and no code is generated for calls
* to the macro in the SNTP library on compilation.
*/
#ifndef LogInfo
#define LogInfo( message )
#endif
/**
* @brief Macro that is called in the SNTP library for logging "Debug" level
* messages.
*
* To enable debug level logging from SNTP library, this macro should be mapped to the
* application-specific logging implementation that supports debug logging.
*
* @note This logging macro is called in the SNTP library with parameters wrapped in
* double parentheses to be ISO C89/C90 standard compliant. For a reference
* POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
* logging-stack in demos folder of the
* [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
*
* <b>Default value</b>: Debug logging is turned off, and no code is generated for calls
* to the macro in the SNTP library on compilation.
*/
#ifndef LogDebug
#define LogDebug( message )
#endif
#endif /* ifndef CORE_SNTP_CONFIG_DEFAULTS_H_ */
+521
View File
@@ -0,0 +1,521 @@
/*
* coreSNTP
* Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @file core_sntp_serializer.h
* @brief API for serializing SNTP request packets and, and de-serializing SNTP
* response packets.
* This API layer adheres to the SNTPv4 specification defined in
* [RFC 4330](https://tools.ietf.org/html/rfc4330).
*/
#ifndef CORE_SNTP_SERIALIZER_H_
#define CORE_SNTP_SERIALIZER_H_
/* Standard include. */
#include <stdint.h>
#include <stddef.h>
/* *INDENT-OFF* */
#ifdef __cplusplus
extern "C" {
#endif
/* *INDENT-ON* */
/**
* @ingroup sntp_constants
* @brief The base packet size of request and response of the (S)NTP protocol.
* @note This is the packet size without any authentication headers for security
* mechanism. If the application uses a security mechanism for communicating with
* an (S)NTP server, it can add authentication data after the SNTP packet is
* serialized with the @ref Sntp_SerializeRequest API function.
*/
#define SNTP_PACKET_BASE_SIZE ( 48U )
/**
* @ingroup sntp_constants
* @brief Number of SNTP timestamp fractions in 1 microsecond.
*
* The fraction's part of an SNTP timestamp is 32-bits wide, thereby, giving a
* resolution of 2^(-32) seconds ~ 232 picoseconds.
*
* @note The application can use this value to convert microseconds part of system
* time into SNTP timestamp fractions. For example, if the microseconds
* part of system time is n microseconds, the fractions value to be used for the
* SNTP timestamp part will be n * SNTP_FRACTION_VALUE_PER_MICROSECOND.
*/
#define SNTP_FRACTION_VALUE_PER_MICROSECOND ( 4295U )
/**
* @ingroup sntp_constants
* @brief The seconds part of SNTP time at the UNIX epoch time, which represents
* an offset of 70 years (in seconds) between SNTP epoch and UNIX epoch time.
* SNTP uses 1st Jan 1900 UTC as the epoch time, whereas UNIX standard uses
* 1st Jan 1970 UTC as the epoch time, thereby, causing an offset of 70 years
* between them.
*
* Difference of 70 years = ((70 * 365) + 17 leap days) * 24 * 3600 seconds
*
* @note If your system follows UNIX time, the application can use this value to
* convert seconds part of a system time to seconds part of the equivalent SNTP
* time. For example, if the seconds part of system time is n seconds, the seconds
* value to be used for the SNTP timestamp will be n + SNTP_TO_UNIX_OFFSET_SECS.
*/
#define SNTP_TIME_AT_UNIX_EPOCH_SECS ( 2208988800U )
/**
* @ingroup sntp_constants
* @brief The seconds value of SNTP timestamp for the largest UNIX time when
* using signed 32-bit integer for seconds.
* The largest time representable with a 32-bit signed integer in UNIX time
* is 19 Jan 2038 3h 14m 7s UTC. However, as the SNTP time overflows at
* 7 Feb 2036 6h 28m 16s UTC, therefore, the SNTP time for the largest UNIX time
* represents the time duration between the 2 timestamps.
*
* SNTP Time at Largest Time Duration in the range
* Signed 32-bit UNIX time = [7 Feb 2036 6:28:16, 19 Jan 2038 3:14:07]
*/
#define SNTP_TIME_AT_LARGEST_UNIX_TIME_SECS ( 61505151U )
/**
* @ingroup sntp_constants
* @brief The UNIX time (in seconds) at the smallest SNTP time in era 1,
* i.e. UNIX time at 7 Feb 2036 6:28:16 UTC/
*
* Time Duration = 7 Feb 6:28:16 UTC (SNTP Era 1 Epoch) -
* 1 Jan 1970 0:0:0 UTC (UNIX epoch)
* = 66 years, 37 days, 6 hours, 28 minutes and 16 seconds
* = ((66 * 365) + 16 leap days) * 24 * 3600) + (6 * 3600)
* + (28 * 60) + 16
*/
#define UNIX_TIME_SECS_AT_SNTP_ERA_1_SMALLEST_TIME ( 2085978496U )
/**
* @ingroup sntp_constants
* @brief The fixed-length of any Kiss-o'-Death message ASCII code sent
* in an SNTP server response.
* @note An SNTP server sends a Kiss-o'-Death message to reject a time request
* from the client. For more information on the Kiss-o'-Death codes, refer to the
* [SNTPv4 specification Section 8](https://tools.ietf.org/html/rfc4330#section-8).
*/
#define SNTP_KISS_OF_DEATH_CODE_LENGTH ( 4U )
/**
* @ingroup sntp_constants
* @brief The value for the #SntpResponseData_t.rejectedResponseCode member
* when that the server response packet does not contain a Kiss-o'-Death
* message, and therefore, does not have a "kiss code".
* The server sends a "kiss-code" only when it rejects an SNTP request
* with a Kiss-o'-Death message.
*/
#define SNTP_KISS_OF_DEATH_CODE_NONE ( 0U )
/**
* @ingroup sntp_enum_types
* @brief Enumeration of status codes that can be returned
* by the coreSNTP Library API.
*/
typedef enum SntpStatus
{
/**
* @brief Successful operation of an SNTP API.
*/
SntpSuccess,
/**
* @brief Invalid parameter passed to an API function.
*/
SntpErrorBadParameter,
/**
* @brief Server sent a Kiss-o'-Death message to reject the request for time.
* This status can be returned by the @ref Sntp_ReceiveTimeResponse API.
*/
SntpRejectedResponse,
/**
* @brief Server sent a Kiss-o'-Death message with non-retryable code (i.e. DENY or RSTR).
*/
SntpRejectedResponseChangeServer,
/**
* @brief Server sent a Kiss-o'-Death message with a RATE code, which means that
* client should back-off before retrying.
*/
SntpRejectedResponseRetryWithBackoff,
/**
* @brief Server sent a Kiss-o'-Death message with a code, specific to the server.
* Application can inspect the ASCII kiss-code from @ref Sntp_DeserializeResponse API.
*/
SntpRejectedResponseOtherCode,
/**
* @brief Application provided insufficient buffer space for serializing
* or de-serializing an SNTP packet.
* The minimum size of an SNTP packet is #SNTP_PACKET_BASE_SIZE
* bytes. */
SntpErrorBufferTooSmall,
/**
* @brief Server response failed validation checks for expected data in SNTP packet.
*/
SntpInvalidResponse,
/**
* @brief Poll interval value is under 1 second which cannot be calculated
* by @ref Sntp_CalculatePollInterval.
*/
SntpZeroPollInterval,
/**
* @brief The user-defined DNS resolution interface, @ref SntpResolveDns_t, failed to resolve
* address for a time server. This status is returned by the @ref Sntp_SendTimeRequest API.
*/
SntpErrorDnsFailure,
/**
* @brief Networking operation of sending or receiving SNTP packet through the user-defined UDP
* transport interface, @ref UdpTransportInterface_t, failed.
* This status is returned by either of @ref Sntp_SendTimeRequest OR @ref Sntp_ReceiveTimeResponse
* APIs.
*/
SntpErrorNetworkFailure,
/**
* @brief Time server is not authenticated from the authentication data in its response.
* This status can be returned by the user-supplied definition of the
* @ref SntpValidateServerAuth_t authentication interface.
*/
SntpServerNotAuthenticated,
/**
* @brief Failure from the user-supplied authentication interface, @ref SntpAuthenticationInterface_t,
* in either generating authentication data for SNTP request OR validating the authentication
* data in SNTP response from server.
*/
SntpErrorAuthFailure,
/**
* @brief A timeout occurred in sending time request packet over the network to a server through the
* @ref Sntp_SendTimeRequest API.
*/
SntpErrorSendTimeout,
/**
* @brief A timeout has occurred in receiving server response with the @ref Sntp_ReceiveTimeResponse
* API.
*/
SntpErrorResponseTimeout,
/**
* @brief No SNTP packet for server response is received from the network by the
* @ref Sntp_ReceiveTimeResponse API.
*/
SntpNoResponseReceived,
/**
* @brief The SNTP context passed to @ref Sntp_SendTimeRequest or @ref Sntp_ReceiveTimeResponse APIs is
* is uninitialized.
*/
SntpErrorContextNotInitialized
} SntpStatus_t;
/**
* @ingroup sntp_enum_types
* @brief Enumeration for leap second information that an SNTP server can
* send its response to a time request. An SNTP server sends information about
* whether there is an upcoming leap second adjustment in the last day of the
* current month.
*
* @note A leap second is an adjustment made in atomic clock time because Earth's rotation
* can be inconsistent. Leap seconds are usually incorporated as an extra second insertion
* or second deletion in the last minute before midnight i.e. in the minute of 23h:59m UTC
* on the last day of June or December. For more information on leap seconds, refer to
* https://www.nist.gov/pml/time-and-frequency-division/leap-seconds-faqs.
*/
typedef enum SntpLeapSecondInfo
{
NoLeapSecond = 0x00, /**< @brief There is no upcoming leap second adjustment. */
LastMinuteHas61Seconds = 0x01, /**< @brief A leap second should be inserted in the last minute before midnight. */
LastMinuteHas59Seconds = 0x02, /**< @brief A leap second should be deleted from the last minute before midnight. */
AlarmServerNotSynchronized = 0x03 /**< @brief An alarm condition meaning that server's time is not synchronized
* to an upstream NTP (or SNTP) server. */
} SntpLeapSecondInfo_t;
/**
* @ingroup sntp_struct_types
* @brief Structure representing an SNTP timestamp.
*
* @note The SNTP timestamp uses 1st January 1900 0h 0m 0s Coordinated Universal Time (UTC)
* as the primary epoch i.e. the timestamp represents current time as the amount of time since
* the epoch time.
* Refer to the [SNTPv4 specification](https://tools.ietf.org/html/rfc4330#section-3) for more
* information of the SNTP timestamp format.
*/
typedef struct SntpTimestamp
{
uint32_t seconds; /**< @brief Number of seconds since epoch time. */
uint32_t fractions; /**< @brief The fractions part of the SNTP timestamp with resolution
* of 2^(-32) ~ 232 picoseconds. */
} SntpTimestamp_t;
/**
* @ingroup sntp_struct_types
* @brief Structure representing data parsed from an SNTP response from server
* as well as data of arithmetic calculations derived from the response.
*/
typedef struct SntpResponse
{
/**
* @brief The timestamp sent by the server.
*/
SntpTimestamp_t serverTime;
/**
* @brief The information of an upcoming leap second in the
* server response.
*/
SntpLeapSecondInfo_t leapSecondType;
/**
* @brief If a server responded with Kiss-o'-Death message to reject
* time request, this is the fixed length ASCII code sequence for the
* rejection.
*
* The Kiss-o'-Death code is always #SNTP_KISS_OF_DEATH_CODE_LENGTH
* bytes long.
*
* @note If the server does not send a Kiss-o'-Death message in its
* response, this value will be #SNTP_KISS_OF_DEATH_CODE_NONE.
*/
uint32_t rejectedResponseCode;
/**
* @brief The offset (in milliseconds) of the system clock relative to the server time
* calculated from timestamps in the client SNTP request and server SNTP response packets.
* If the the system time is BEHIND the server time, then the clock-offset value is > 0.
* If the system time is AHEAD of the server time, then the clock-offset value is < 0.
*
* @note This information can be used to synchronize the system clock with a "slew",
* "step" OR combination of the two clock correction methodologies depending on the degree
* of system clock drift (represented by the clock-offset) and the application's
* tolerance for system clock error.
*
* @note The library calculates the clock-offset value using the On-Wire
* protocol suggested by the NTPv4 specification. For more information,
* refer to https://tools.ietf.org/html/rfc5905#section-8.
*
* @note The library ASSUMES that the server and client systems are within
* ~68 years of each other clock, whether in the same NTP era or across adjacent
* NTP eras. Thus, the client and system times MUST be within ~68 years (or
* 2^31 seconds exactly) of each other for correct calculation of clock-offset.
*
* @note When the server and client times are exactly 2^31 (or INT32_MAX + 1 )
* seconds apart, the library ASSUMES that the server time is ahead of the client
* time, and return the clock-offset value of INT32_MAX.
*/
int64_t clockOffsetMs;
} SntpResponseData_t;
/**
* @brief Serializes an SNTP request packet to use for querying a
* time server.
*
* This function will fill only #SNTP_PACKET_BASE_SIZE bytes of data in the
* passed buffer.
*
* @param[in, out] pRequestTime The current time of the system, expressed as time
* since the SNTP epoch (i.e. 0h of 1st Jan 1900 ). This time will be serialized
* in the SNTP request packet. The function will use this parameter to return the
* timestamp serialized in the SNTP request. To protect against attacks spoofing
* server responses, the timestamp MUST NOT be zero in value.
* @param[in] randomNumber A random number (generated by a True Random Generator)
* for use in the SNTP request packet to protect against replay attacks as suggested
* by SNTPv4 specification. For more information, refer to
* [RFC 4330 Section 3](https://tools.ietf.org/html/rfc4330#section-3).
* @param[out] pBuffer The buffer that will be populated with the serialized
* SNTP request packet.
* @param[in] bufferSize The size of the @p pBuffer buffer. It should be at least
* #SNTP_PACKET_BASE_SIZE bytes in size.
*
* @note It is recommended to use a True Random Generator (TRNG) to generate
* the random number.
* @note The application MUST save the @p pRequestTime value for de-serializing
* the server response with @ref Sntp_DeserializeResponse API.
*
* @return This function returns one of the following:
* - #SntpSuccess when serialization operation is successful.
* - #SntpErrorBadParameter if an invalid parameter is passed.
* - #SntpErrorBufferTooSmall if the buffer does not have the minimum size
* for serializing an SNTP request packet.
*/
/* @[define_sntp_serializerequest] */
SntpStatus_t Sntp_SerializeRequest( SntpTimestamp_t * pRequestTime,
uint32_t randomNumber,
void * pBuffer,
size_t bufferSize );
/* @[define_sntp_serializerequest] */
/**
* @brief De-serializes an SNTP packet received from a server as a response
* to a SNTP request.
*
* This function will parse only the #SNTP_PACKET_BASE_SIZE bytes of data
* in the passed buffer.
*
* @note If the server has sent a Kiss-o'-Death message to reject the associated
* time request, the API function will return the appropriate return code and,
* also, provide the ASCII code (of fixed length, #SNTP_KISS_OF_DEATH_CODE_LENGTH bytes)
* in the #SntpResponseData_t.rejectedResponseCode member of @p pParsedResponse parameter,
* parsed from the response packet.
* The application SHOULD respect the server rejection and take appropriate action
* based on the rejection code.
* If the server response represents an accepted SNTP client request, then the API
* function will set the #SntpResponseData_t.rejectedResponseCode member of
* @p pParsedResponse parameter to #SNTP_KISS_OF_DEATH_CODE_NONE.
*
* @note If the server has positively responded with its clock time, then this API
* function will calculate the clock-offset. For the clock-offset to be correctly
* calculated, the system clock MUST be within ~68 years (or 2^31 seconds) of the server
* time mentioned. This function supports clock-offset calculation when server and client
* timestamps are in adjacent NTP eras, with one system is in NTP era 0 (i.e. before 7 Feb 2036
* 6h:28m:14s UTC) and another system in NTP era 1 (on or after 7 Feb 2036 6h:28m:14s UTC).
*
* @note In the special case when the server and client times are exactly 2^31 seconds apart,
* the library ASSUMES that the server time is ahead of the client time, and returns the
* positive clock-offset value of INT32_MAX seconds.
*
* @param[in] pRequestTime The system time used in the SNTP request packet
* that is associated with the server response. This MUST be the same as the
* time returned by the @ref Sntp_SerializeRequest API. To protect against attacks
* spoofing server responses, this timestamp MUST NOT be zero in value.
* @param[in] pResponseRxTime The time of the system, expressed as time since the
* SNTP epoch (i.e. 0h of 1st Jan 1900 ), at receiving SNTP response from server.
* This time will be used to calculate system clock offset relative to server.
* @param[in] pResponseBuffer The buffer containing the SNTP response from the
* server.
* @param[in] bufferSize The size of the @p pResponseBuffer containing the SNTP
* response. It MUST be at least #SNTP_PACKET_BASE_SIZE bytes
* long for a valid SNTP response.
* @param[out] pParsedResponse The information parsed from the SNTP response packet.
* If possible to calculate without overflow, it also contains the system clock
* offset relative to the server time.
*
* @return This function returns one of the following:
* - #SntpSuccess if the de-serialization operation is successful.
* - #SntpErrorBadParameter if an invalid parameter is passed.
* - #SntpErrorBufferTooSmall if the buffer does not have the minimum size
* required for a valid SNTP response packet.
* - #SntpInvalidResponse if the response fails sanity checks expected in an
* SNTP response.
* - #SntpRejectedResponseChangeServer if the server rejected with a code
* indicating that client cannot be retry requests to it.
* - #SntpRejectedResponseRetryWithBackoff if the server rejected with a code
* indicating that client should back-off before retrying request.
* - #SntpRejectedResponseOtherCode if the server rejected with a code that
* application can inspect in the @p pParsedResponse parameter.
*/
/* @[define_sntp_deserializeresponse] */
SntpStatus_t Sntp_DeserializeResponse( const SntpTimestamp_t * pRequestTime,
const SntpTimestamp_t * pResponseRxTime,
const void * pResponseBuffer,
size_t bufferSize,
SntpResponseData_t * pParsedResponse );
/* @[define_sntp_deserializeresponse] */
/**
* @brief Utility to calculate the poll interval of sending periodic time queries
* to servers to achieve a desired system clock accuracy for a given
* frequency tolerance of the system clock.
*
* For example, from the SNTPv4 specification, "if the frequency tolerance
* is 200 parts per million (PPM) and the required accuracy is one minute,
* the maximum timeout is about 3.5 days". In this example, the system
* clock frequency tolerance is 200 PPM and the desired accuracy is
* 60000 milliseconds (or 1 minute) for which this API function
* will return the maximum poll interval value as 2^18 seconds (or ~3 days).
*
* @note The poll interval returned is a power of 2, which is the
* standard way to represent the value. According to the SNTPv4 specification
* Best Practices, an SNTP client SHOULD NOT have a poll interval less than 15 seconds.
* https://tools.ietf.org/html/rfc4330#section-10. This API function DOES NOT
* support poll interval calculation less than 1 second.
*
* @param[in] clockFreqTolerance The frequency tolerance of system clock
* in parts per million (PPM) units. This parameter MUST be non-zero.
* @param[in] desiredAccuracy The acceptable maximum drift, in milliseconds,
* for the system clock. The maximum value (0xFFFF) represents ~1 minute of
* desired clock accuracy. This parameter MUST be non-zero.
* @param[out] pPollInterval This is filled with the poll interval, in seconds
* calculated as the closest power of 2 value that will achieve either the
* exact desired or higher clock accuracy @p desiredAccuracy, for the given clock
* frequency tolerance, @p clockFreqTolerance.
*
* @return Returns one of the following:
* - #SntpSuccess if calculation is successful.
* - #SntpErrorBadParameter for an invalid parameter passed to the function.
* - #SntpZeroPollInterval if calculated poll interval is less than 1 second.
*/
/* @[define_sntp_calculatepollinterval] */
SntpStatus_t Sntp_CalculatePollInterval( uint16_t clockFreqTolerance,
uint16_t desiredAccuracy,
uint32_t * pPollInterval );
/* @[define_sntp_calculatepollinterval] */
/**
* @brief Utility to convert SNTP timestamp (that uses 1st Jan 1900 as the epoch) to
* UNIX timestamp (that uses 1st Jan 1970 as the epoch).
*
* Refer to (this image)[docs/doxygen/images/Ntp_To_Unix_Time.png].
*
* @note The function also correctly handles SNTP era overflow (from 7 Feb 2036 6h 28m 16s,
* i.e., SNTP era 1) to ensure accurate conversion across SNTP eras.
*
* @param[in] pSntpTime The SNTP timestamp to convert to UNIX time.
* @param[out] pUnixTimeSecs This will be filled with the seconds part of the
* UNIX time equivalent of the SNTP time, @p pSntpTime.
* @param[out] pUnixTimeMicrosecs This will be filled with the microseconds part
* of the UNIX time equivalent of the SNTP time, @p pSntpTime.
*
* @return Returns one of the following:
* - #SntpSuccess if conversion to UNIX time is successful
* - #SntpErrorBadParameter if any of the passed parameters are NULL.
*/
/* @[define_sntp_converttounixtime] */
SntpStatus_t Sntp_ConvertToUnixTime( const SntpTimestamp_t * pSntpTime,
uint32_t * pUnixTimeSecs,
uint32_t * pUnixTimeMicrosecs );
/* @[define_sntp_converttounixtime] */
/* *INDENT-OFF* */
#ifdef __cplusplus
}
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_SNTP_SERIALIZER_H_ */
+31 -1
View File
@@ -337,7 +337,7 @@
<MiscControls>--diag_suppress=1,68,111,188,191,546,1293</MiscControls>
<Define>USE_HAL_DRIVER,STM32F407xx,NDEBUG</Define>
<Undefine />
<IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/ScreenLink;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/algo;../App/cfg;../App/fs;../App/log;../App/sync;../App/task;../App/protocol/iec104;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib</IncludePath>
<IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/ScreenLink;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/algo;../App/cfg;../App/fs;../App/log;../App/sync;../App/task;../App/protocol/iec104;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib;../Lib/coreSNTP/include</IncludePath>
</VariousControls>
</Cads>
<Aads>
@@ -952,6 +952,11 @@
<FileType>1</FileType>
<FilePath>..\App\sync\time_sync.c</FilePath>
</File>
<File>
<FileName>time_sntp.c</FileName>
<FileType>1</FileType>
<FilePath>..\App\sync\time_sntp.c</FilePath>
</File>
<File>
<FileName>default_task.c</FileName>
<FileType>1</FileType>
@@ -1673,6 +1678,11 @@
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NET\net_pending.c</FilePath>
</File>
<File>
<FileName>net_udp.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NET\net_udp.c</FilePath>
</File>
<File>
<FileName>net_cfg.c</FileName>
<FileType>1</FileType>
@@ -1683,6 +1693,11 @@
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NETCFG\ftp_cfg.c</FilePath>
</File>
<File>
<FileName>sntp_cfg.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\NETCFG\sntp_cfg.c</FilePath>
</File>
<File>
<FileName>nand_ftl.c</FileName>
<FileType>1</FileType>
@@ -2354,6 +2369,16 @@
</File>
</Files>
</Group>
<Group>
<GroupName>Lib/coreSNTP</GroupName>
<Files>
<File>
<FileName>core_sntp_serializer.c</FileName>
<FileType>1</FileType>
<FilePath>..\Lib\coreSNTP\core_sntp_serializer.c</FilePath>
</File>
</Files>
</Group>
<Group>
<GroupName>Test</GroupName>
<Files>
@@ -3711,6 +3736,11 @@
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_page_wave_play.c</FilePath>
</File>
<File>
<FileName>ui_page_sntp.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\UI\ui_page_sntp.c</FilePath>
</File>
</Files>
</Group>
<Group>
+2 -5
View File
@@ -122,7 +122,7 @@ STM32F4-Base/
│ ├── algo/ # 采集与算法层(语义取数与有效性 adc_input / 全周傅氏相量 phasor+freq / 保护判据 prot_set+prot_elm)
│ ├── cfg/ # 配置引擎(cfg_file 解析/持久化/迁移 + 存储后端)
│ ├── fs/ # 卷锁(vol_lock,syslog/cfg/lftpd 三条路径 try 语义)
│ ├── sync/ # 时间同步(time_sync 任务)
│ ├── sync/ # 时间同步(time_sync 任务载体 + time_sntp SNTP 客户端)
│ ├── log/ # 日志(sys_log 运行/自检记录、console_log 控制台单写者)
│ ├── task/ # FreeRTOS 任务实现
│ │ ├── default_task.c/h # 存储挂载 + 配置引擎属主 + syslog 写者
@@ -175,9 +175,7 @@ STM32F4-Base/
│ └── host/ # 主机端脚本(PC 侧;串口回归入口在 .dsh/skills/serial-boot-test/)
│ ├── ch395f_socket_test.py # Socket 通信测试
│ ├── ftp_test.py / ftp_up_dl_test.py / ftp_regression.py / ftp_integrity_test.py / ftp_capture.py
│ ├── e02_probe.py # 队头阻塞专项探针
│ ├── latency_peer.py # 链路时延 PC 对端
│ └── time_sync_server.py # 时间同步推送服务
│ └── latency_peer.py # 链路时延 PC 对端
├── docs/ # 项目文档(全量索引见 docs/README.md)
│ ├── README.md # 文档索引:目录结构 + 推荐阅读顺序 + 全量清单
│ ├── 任务规划/ # 统一待办登记簿(README 排序权威 + 拆分文件)
@@ -325,7 +323,6 @@ UI 改动回归门禁(菜单像素回归 + 内容页黄金参考 + 池水位
| `ftp_regression.py` | FTP 功能回归(会话流程全序列) |
| `ftp_integrity_test.py` | FTP 传输完整性压测(多轮 SHA-256 校验) |
| `ftp_capture.py` | FTP 流量抓取 |
| `time_sync_server.py` | 时间同步推送服务(PC → 板子) |
### 串口调试
+1 -1
View File
@@ -139,7 +139,7 @@ docs/
| [`ADC输入层单元测试说明.md`](测试说明/单元测试/ADC输入层单元测试说明.md) | adc_input 语义通道映射 / ADC1 顺序扫描(**Rank↔通道判据**)/ poll 契约 / HAL 故障路径,70 用例 |
| [`相量层单元测试说明.md`](测试说明/单元测试/相量层单元测试说明.md) | phasor 全周傅氏系数表 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区交换,57 用例 |
| [`卷锁单元测试说明.md`](测试说明/单元测试/卷锁单元测试说明.md) | vol_lock 创建 / 获取 / 释放契约(**fail-open 不变量**:创建失败仍返回 1 且零阻塞)/ 幂等 / 超时透传,33 断言点 |
| [`时间同步单元测试说明.md`](测试说明/单元测试/时间同步单元测试说明.md) | time_sync 分片喂狗延时 / 监听退避 / 时间包重组 / 任务主循环(探针窗口心跳缺口的全部逻辑贡献者),80 断言点 |
| [`时间同步单元测试说明.md`](测试说明/单元测试/时间同步单元测试说明.md) | time_sync v2.0:分片喂狗延时 / 就绪屏障(100 ms 分片·NoClear)/ SNTP 节拍与网络代次交接 / 未出网守门(:8888 退役后的任务载体),33 断言点 |
| [`主机单测覆盖率口径.md`](测试说明/单元测试/主机单测覆盖率口径.md) | **跨全部 16 套 主机侧纯逻辑单测的覆盖率**:`scripts/check_test_coverage.py` 用法 / 统计面与生成物例外 / 基线增量门禁(只拦回退)/ **为什么本机拿不到分支覆盖** / 九条限制与盲区 / 未挂 pre-commit 的三条理由与待粘 YAML |
**板级测试/**(上板测试:模块测试指南 + 实机实施流程;动手写测试前先读)
+1 -1
View File
@@ -694,7 +694,7 @@ typedef struct {
| 4 | 未标定时页面明确标识(不把码值当伏特) | 目检 + 文本断言(`eng=0` 时带标记) |
| 5 | 首帧前显示 `--` 而非 `0.000` | 模拟器:不 publish 直接开页,断言文本 |
| 6 | 定值整定全链闭环 | 就地改 → 保存 → 重启 → 值保持 + 校验和自检通过 + 主站 C_RD 一致。**半收口(2026-10-26)**:模拟器侧「改 → 保存 → OK → 回首屏新值已在帧里」已由像素 + 文本断言覆盖(黄金用例 `sparam_save` / `sparam_landed`,越界侧 `sparam_oob`);**重启保持与主站 C_RD 一致只能上板**——模拟器没有 NAND,`apply()` 到 `prot_set_set_sealed()` 就是终点,这条差距已登记在 `docs/测试说明/专项验证/模拟器差距清单.md`;另注意 40 项里有 13 项 `comm_addr=0`(纯本地项),主站侧根本读不到,不能拿 C_RD 当它们的判据 |
| 7 | 像素回归稳定 | `ui-golden` 连续两次运行零差异。**已验(2026-10-26,当时下限为 27 页)**:27 页连跑两次逐字节一致(含整定 9 态)——**集合现已扩至 66 张**,**双次一致已验(2026-10-29:同一批 66 张连跑三次逐字节一致,池水位 5,448 B;菜单 8 状态连跑三次亦一致,5,368 B)**;轮询定时器不影响——无头模式不调 `lv_timer_handler()`(§9.2 注 6) |
| 7 | 像素回归稳定 | `ui-golden` 连续两次运行零差异。**已验(2026-10-26,当时下限为 27 页)**:27 页连跑两次逐字节一致(含整定 9 态)——**集合现已扩至 74 张**,**双次一致已验(2026-10-29:同一批 66 张连跑三次逐字节一致,池水位 5,448 B;菜单 8 状态连跑三次亦一致,5,368 B)**;轮询定时器不影响——无头模式不调 `lv_timer_handler()`(§9.2 注 6) |
| 9 | 「写不进去」与「改了不动作」两条事实互不冒充 | 反向文本断言(`check_content_pages.py` 的 `ABSENT`:查看页与只读态不得出现「未标定」徽标与「保存」行)+ 台架:未制造该态——要它必须改坏 /prot_set.bin(破坏产品配置存储,用户裁定 2026-10-26 不做),故 LOCK 态目前只有代码级证据与三重反证(ENTER 被正常消费、遥信 YX31=OFF、开机 CORRUPT=0),**没有像素证据** |
| 8 | 主机单测覆盖数据模块 | `test/host/ui_data` 全绿并挂 pre-commit 钩子 |
+4 -4
View File
@@ -26,7 +26,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
| 步 | 脚本/命令 | 门禁内容 | 预期输出 |
|---|---|---|---|
| 1 | `simulator/tests/check_menu_rows.py` | 菜单 8 状态:选中行白带 / 非选行黑底白字 / 框体 1px 边框 / 往返像素一致(MENU-STATE-03==MENU-STATE-01、MENU-STATE-06==MENU-STATE-01、MENU-STATE-07==MENU-STATE-02)+ MENU-STATE-00 池基线 | `PASS: 8 状态全部断言通过(8 图,池基线 5368 B)` |
| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,正向 444 条 + 反向 45 条,独立于 `ref/`)→ ② 66 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 66 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) |
| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,正向 484 条 + 反向 45 条,独立于 `ref/`)→ ② 74 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 74 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) |
| 3 | `keil-build` skill(`keil_build.py`) | MCU 端编译(UI 源码双端共用,改一端必须过另一端) | 退出码 0 = BUILD_OK |
| 4 | `scripts/check_coding_standard.py` | 代码规范 V1.0 机械检查 | PASS |
@@ -52,7 +52,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
| `POOL_AFTER_ROUNDTRIP` | check_content_pages.py | **5448 B** | 开页-关页往返后水位 = 基线 + 一次性残留 80 B |
> 水位历史:4784 →(2026-09-14 待机模块常驻 +656)5440 / 5520 →(2026-10-11 裁剪 LVGL 控件类 −72)5368 / 5448。
> 后一次漂移已核实为**有意且更优**:像素逐字节未变(**裁剪当时黄金参考只有 11 页**,在裁剪**前**生成、裁剪后全部一致;该集合现已扩至 66 张),
> 后一次漂移已核实为**有意且更优**:像素逐字节未变(**裁剪当时黄金参考只有 11 页**,在裁剪**前**生成、裁剪后全部一致;该集合现已扩至 74 张),
> 且一次性残留仍是 80 B(5448 − 5368)→ 泄漏特征未变,只是 `lv_theme_apply` 不再为已不存在的控件类登记样式条目。
**漂移处理**:菜单/页面结构或样式变更导致门禁失败时,先确认新值合理(残留分解见
@@ -61,7 +61,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
## 四、黄金参考(入库)与目检 PNG(本地产物)
`simulator/tests/ref/*.bin`(像素黄金参考,66 张 × 3200 B)**随仓库入库**;派生的 `.png`
`simulator/tests/ref/*.bin`(像素黄金参考,74 张 × 3200 B)**随仓库入库**;派生的 `.png`
目检图仍是本地产物(`.gitignore`)。入库的理由:比对是**逐字节**的,基线不入库时任何新克隆
或新工作树跑 `ui-golden` 都必然 FAIL(缺基准),这条门禁就只在"有老副本的机器"上真实存在。
可复现性实测(2026-10-22):两份各自独立构建的 `lvgl_sim`(不同构建目录、相隔 11 天的代码)
@@ -154,7 +154,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
### 7.3 用法与门禁档位
```powershell
# 认屏快检:66 张黄金参考(+ 若有台架留档帧),秒级,不需要构建产物
# 认屏快检:74 张黄金参考(+ 若有台架留档帧),秒级,不需要构建产物
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest-quick
# 完整自检:再加全绑定页闭环导航(需模拟器可执行文件,约 30 s)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

+1 -1
View File
@@ -453,7 +453,7 @@ extern const content_page_def_t ui_page_device_info_desc; /* 描述符(Flash
## 八、验证与回归
- **门禁**(`ui-verify` skill / `UI_自动化测试流程.md`):
0) 模拟器构建 0 警告 → 1) 菜单 8 状态像素回归 + 池基线 → 2) 内容页文本断言 + 66 张黄金参考逐字节比对
0) 模拟器构建 0 警告 → 1) 菜单 8 状态像素回归 + 池基线 → 2) 内容页文本断言 + 74 张黄金参考逐字节比对
(网口设置 4 态 + 密码页 4 态等)+ 开页-关页往返水位 → 3) Keil 构建 0 错 0 警 → 4) 代码规范检查
- **门禁常量**(脚本头部):`MENU_POOL_BASELINE = 5368 B`、`POOL_AFTER_ROUNDTRIP = 5448 B`
- **失败处置**:像素不一致 = 代码 bug(修代码,**禁止**刷参考掩盖);**有意**改视觉 →
+3 -2
View File
@@ -81,7 +81,7 @@
| 26 | 手动录波 | 操作页(确认) | `wave_ring_request` 手动触发经 `ui_data_wave_ops_t` 注入 | ✅ 已落地(`ui_page_wave_manual.c`,2026-10-29 录波 UI 道;配套新增「录波回放」叶 node 43 = `ui_page_wave_play.c`,不在本表 27 叶之列) |
| 27 | 清除记录 | 操作页(选类型 + 确认) | `sys_log`(**缺删除 API**) | 🔶 需补 API |
| 29 | 通讯设置 | 可编辑列表(4~6 项) | 104 公共地址/端口/主站(当前固定) | 🔶 需 `comm_cfg` |
| 31 | SNTP设置 | 可编辑列表(3 项) | SNTP 客户端 | ❌ 未实现(产品口径亦未定) |
| 31 | SNTP设置 | 可编辑列表(3 项) | SNTP 客户端 | ✅ 已落地(`ui_page_sntp.c`,2026-11-04 板端 SNTP 接入立项;形态较原规划多 1 行 = 启用/主服务器/备服务器/周期(分) + 保存行,编辑语义复刻网口设置页) |
| 33 | SOE记录 | 动态列表(每记录 2 行) | `/log/soe.log` 二进制 64B | ✅ 已落地(`ui_page_soe.c`) |
| 34 | 事故记录 | 动态列表 | /log/accid.log(SYSLOG_ACCIDENT,写入点 protect_task.c) | ✅ 已落地(ui_page_accident.c,2026-10-28) |
| 35 | 操作记录 | 动态列表 | /log/oper.log(SYSLOG_OPER,写入点 4 处:定值保存 / 复归 / 对时 / 清除记录) | ✅ 已落地(ui_page_oper.c,2026-10-28) |
@@ -94,6 +94,7 @@
**2026-10-28 复核**:本表 27 叶中 **22 叶已落地**(本批 +6:记录 5 页与运行报告,见 docs/任务规划/UI补齐.md §2 与 docs/任务规划/登记簿.md §7);未落地的 5 叶 = 遥信类型 / 遥信防抖 / 双点遥信(无开入硬件)+ 手动录波(无录波模块)+ SNTP设置(产品口径未定),取舍与重开条件见 docs/任务规划/UI补齐.md §5。口径闭合:本表 22 叶 + 立项前就已绑定、不在本表内的 6 叶(装置信息 / 交流量 / 直流量 / 网口设置 / 自检记录 / 运行记录)= 现值已绑 28 叶(= UI_PAGE_MAP_COUNT);本表未落地 5 叶 = 菜单树未绑 5 叶(33 − 28)。复核方法:按缩进判叶子(有更深缩进后继者即非叶子),再按行内 #page= 数已绑。**2026-10-29 补充**:「手动录波」已随故障录波独立立项(docs/子系统设计/故障录波设计说明.md;原卡片登记簿 2026-10-30 销项并入该文 §10),上面那行数字仍是 2026-10-28 当日复核值,未随立项改动。
**2026-10-29 二补(录波 UI 道落地)**:本表「手动录波」转已落地(23 叶);菜单树另新增「录波回放」叶(node 43,不在本表立项快照内)——现值 43 节点 / 34 叶 / 已绑 30 / 未绑 4(= 遥信 3 + SNTP 1,UI_PAGE_MAP_COUNT = 30),口径复核方法与上一段相同
**2026-11-04 复核(板端 SNTP 接入立项,#57 调研结论)**:本表「SNTP设置」转已落地(24 叶)——`ui_page_sntp.c` 绑 `#page=sntp`,[sntp] 段经 `net_cfg_load` 注册、SNTP 客户端在 timeSyncTask 轮询(`App/sync/time_sntp.c`);现值 43 节点 / 34 叶 / 已绑 31 / 未绑 3(= 遥信 3,UI_PAGE_MAP_COUNT = 31),口径复核方法与上一段相同
---
@@ -205,7 +206,7 @@ ui_page_ac / ui_page_yx / ui_page_setlist ...
9. **`ui_w_editor`**(§3.2)→ **定值整定 4 页**(8/9/10/11)。
10. **通讯设置**(29):需先建 `comm_cfg`(`[comm]` 段:104 公共地址/端口/主站地址),照 `net_cfg` 的"RAM 副本 + 保存请求交接"范式。
11. **SNTP设置**(31):**建议只做页面不接线**,或直接推迟到 SNTP 立项——当前对时走 8888 端口私有协议,SNTP 是另一条链。
11. **SNTP设置**(31)——已落地(2026-11-04):页面读写 `[sntp]` 段 RAM 副本(`ui_page_sntp.c`,PR 链 #73/#77);原「当前对时走 8888 端口私有协议」口径作废——8888 已退役(#78),SNTP 即对时唯一网络来源。
**退出判据**:就地改一条定值 → 保存 → 掉电重启 → 值保持且校验和自检通过;主站 C_RD 读到同一值。
+2 -2
View File
@@ -9,8 +9,8 @@
<text x="251" y="139" font-size="13" fill="#1e293b" text-anchor="middle">net_send() / net_recv() / net_close()(线程安全封装)</text>
<rect x="468" y="116" width="431" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="684" y="139" font-size="13" fill="#1e293b" text-anchor="middle">net_select() / net_poll_events() / net_set_event_cb()</text>
<rect x="44" y="164" width="601" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="344" y="187" font-size="13" fill="#1e293b" text-anchor="middle">消费方:lftpd(FTP :21)· time_sync(对时 :8888)· ui_net_sink(远程显示 :7003)</text>
<rect x="44" y="164" width="620" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="354" y="187" font-size="13" fill="#1e293b" text-anchor="middle">消费方:lftpd(FTP :21)· time_sntp(SNTP 出向对时)· ui_net_sink(远程显示 :7003)</text>
<rect x="26" y="262" width="1148" height="140" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.45"/>
<text x="44" y="288" font-size="17" font-weight="700" fill="#7c3aed">消息队列</text>
<text x="124" y="288" font-size="12" fill="#64748b">osMessageQueue · CubeMX 生成,在 MX_FREERTOS_Init() 中创建</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

+38 -37
View File
@@ -1,24 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1060" height="680" viewBox="0 0 1060 680" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
<rect x="46.0" y="106.0" width="314.0" height="246.0" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="58.0" y="132.0" font-size="15" font-weight="700" fill="#0f172a">幅值轮</text>
<text x="348.0" y="132.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#2563eb">先做 · 主用例</text>
<text x="348.0" y="132.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#2563eb">先做 · 三档幅度</text>
<rect x="58.0" y="142.0" width="290.0" height="86.0" rx="6" fill="#ffffff" stroke="#cbd5e1" stroke-width="1"/>
<line x1="62.0" y1="185.0" x2="344.0" y2="185.0" stroke="#94a3b8" stroke-width="0.8" stroke-dasharray="3 3"/>
<line x1="68.0" y1="161.2" x2="338.0" y2="161.2" stroke="#2563eb" stroke-width="1" stroke-dasharray="5 4"/>
<line x1="68.0" y1="208.8" x2="338.0" y2="208.8" stroke="#2563eb" stroke-width="1" stroke-dasharray="5 4"/>
<polyline points="68.0,185.0 69.9,182.9 71.9,180.7 73.8,178.6 75.8,176.6 77.7,174.6 79.7,172.7 81.6,170.9 83.5,169.3 85.5,167.7 87.4,166.3 89.4,165.1 91.3,164.0 93.3,163.1 95.2,162.3 97.1,161.8 99.1,161.4 101.0,161.3 103.0,161.3 104.9,161.5 106.8,161.9 108.8,162.5 110.7,163.3 112.7,164.2 114.6,165.4 116.6,166.7 118.5,168.1 120.4,169.7 122.4,171.4 124.3,173.2 126.3,175.1 128.2,177.1 130.2,179.2 132.1,181.3 134.0,183.4 136.0,185.5 137.9,187.7 139.9,189.8 141.8,191.9 143.8,193.9 145.7,195.9 147.6,197.7 149.6,199.5 151.5,201.1 153.5,202.6 155.4,204.0 157.4,205.2 159.3,206.3 161.2,207.1 163.2,207.8 165.1,208.3 167.1,208.6 169.0,208.8 170.9,208.7 172.9,208.4 174.8,208.0 176.8,207.3 178.7,206.5 180.7,205.5 182.6,204.3 184.5,203.0 186.5,201.5 188.4,199.9 190.4,198.2 192.3,196.3 194.3,194.4 196.2,192.4 198.1,190.3 200.1,188.2 202.0,186.1 204.0,183.9 205.9,181.8 207.9,179.7 209.8,177.6 211.7,175.6 213.7,173.7 215.6,171.8 217.6,170.1 219.5,168.5 221.5,167.0 223.4,165.7 225.3,164.5 227.3,163.5 229.2,162.7 231.2,162.0 233.1,161.6 235.1,161.3 237.0,161.2 238.9,161.4 240.9,161.7 242.8,162.2 244.8,162.9 246.7,163.7 248.6,164.8 250.6,166.0 252.5,167.4 254.5,168.9 256.4,170.5 258.4,172.3 260.3,174.1 262.2,176.1 264.2,178.1 266.1,180.2 268.1,182.3 270.0,184.5 272.0,186.6 273.9,188.7 275.8,190.8 277.8,192.9 279.7,194.9 281.7,196.8 283.6,198.6 285.6,200.3 287.5,201.9 289.4,203.3 291.4,204.6 293.3,205.8 295.3,206.7 297.2,207.5 299.2,208.1 301.1,208.5 303.0,208.7 305.0,208.7 306.9,208.6 308.9,208.2 310.8,207.7 312.7,206.9 314.7,206.0 316.6,204.9 318.6,203.7 320.5,202.3 322.5,200.7 324.4,199.1 326.3,197.3 328.3,195.4 330.2,193.4 332.2,191.4 334.1,189.3 336.1,187.1 338.0,185.0" fill="none" stroke="#2563eb" stroke-width="2" stroke-linejoin="round"/>
<text x="68.0" y="151.0" font-size="10.5" fill="#64748b">峰 ±1.414 V · 基波 RMS 1.000 V</text>
<text x="68.0" y="151.0" font-size="10.5" fill="#64748b">峰 ±1.414 V · 基波有效值 1.000 V</text>
<text x="58.0" y="244.0" font-size="11" font-weight="700" fill="#2563eb">注入</text>
<text x="94.0" y="244.0" font-size="11.5" fill="#64748b">UA(R31)端子侧,正弦 50.000 Hz、1.000 V</text>
<text x="94.0" y="260.0" font-size="11.5" fill="#64748b">rms</text>
<text x="94.0" y="244.0" font-size="11.5" fill="#64748b">UA(R31)端子侧,正弦 50.000 Hz,三档 Vpp</text>
<text x="94.0" y="260.0" font-size="11.5" fill="#64748b"> 2.828 / 1.414 / 0.566</text>
<text x="58.0" y="280.0" font-size="11" font-weight="700" fill="#2563eb">判据</text>
<text x="94.0" y="280.0" font-size="11.5" fill="#0f172a">ua_mv 中位数 ≈ 万用表 × 1000,偏差 ≤2 %</text>
<text x="58.0" y="300.0" font-size="11" font-weight="700" fill="#2563eb">出口</text>
<text x="94.0" y="300.0" font-size="11.5" fill="#64748b">meas: ua_mv 为主;104 0x4011 只作交叉读</text>
<text x="94.0" y="316.0" font-size="11.5" fill="#64748b">数</text>
<text x="94.0" y="280.0" font-size="11.5" fill="#0f172a">每档 ua_mv ≈ 万用表 × 1000,偏差 ≤2 %</text>
<text x="58.0" y="300.0" font-size="11" font-weight="700" fill="#2563eb">读数</text>
<text x="94.0" y="300.0" font-size="11.5" fill="#64748b">meas: ua_mv 为主(1 mV 分辨率)</text>
<rect x="372.0" y="106.0" width="314.0" height="246.0" rx="10" fill="#f0fdfa" stroke="#0d9488" stroke-width="2"/>
<text x="384.0" y="132.0" font-size="15" font-weight="700" fill="#0f172a">波形轮</text>
<text x="674.0" y="132.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#0d9488">真 RMS ≠ 基波</text>
<text x="674.0" y="132.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#0d9488">真有效值 ≠ 基波</text>
<rect x="384.0" y="142.0" width="290.0" height="86.0" rx="6" fill="#ffffff" stroke="#cbd5e1" stroke-width="1"/>
<line x1="394.0" y1="161.2" x2="664.0" y2="161.2" stroke="#94a3b8" stroke-width="0.9" stroke-dasharray="5 4"/>
<polyline points="400.0,185.0 400.7,182.5 401.3,180.0 402.0,177.6 402.6,175.3 403.3,173.0 403.9,170.9 404.6,169.0 405.2,167.2 405.9,165.7 406.6,164.3 407.2,163.2 407.9,162.3 408.5,161.7 409.2,161.3 409.8,161.2 410.5,161.4 411.1,161.8 411.8,162.5 412.5,163.5 413.1,164.6 413.8,166.0 414.4,167.7 415.1,169.5 415.7,171.4 416.4,173.6 417.0,175.8 417.7,178.2 418.4,180.6 419.0,183.1 419.7,185.6 420.3,188.1 421.0,190.6 421.6,193.0 422.3,195.3 422.9,197.5 423.6,199.6 424.3,201.5 424.9,203.2 425.6,204.7 426.2,206.0 426.9,207.0 427.5,207.9 428.2,208.4 428.8,208.7 429.5,208.7 430.2,208.5 430.8,208.0 431.5,207.3 432.1,206.3 432.8,205.0 433.4,203.6 434.1,201.9 434.7,200.1 435.4,198.0 436.1,195.9 436.7,193.6 437.4,191.2 438.0,188.7 438.7,186.3 439.3,183.7 440.0,181.3 440.6,178.8 441.3,176.4 441.9,174.1 442.6,172.0 443.3,169.9 443.9,168.1 444.6,166.4 445.2,165.0 445.9,163.7 446.5,162.7 447.2,162.0 447.8,161.5 448.5,161.3 449.2,161.3 449.8,161.6 450.5,162.1 451.1,163.0 451.8,164.0 452.4,165.3 453.1,166.8 453.7,168.5 454.4,170.4 455.1,172.5 455.7,174.7 456.4,177.0 457.0,179.4 457.7,181.9 458.3,184.4 459.0,186.9 459.6,189.4 460.3,191.8 461.0,194.2 461.6,196.4 462.3,198.6 462.9,200.5 463.6,202.3 464.2,204.0 464.9,205.4 465.5,206.5 466.2,207.5 466.9,208.2 467.5,208.6 468.2,208.8 468.8,208.7 469.5,208.3 470.1,207.7 470.8,206.8 471.4,205.7 472.1,204.3 472.8,202.8 473.4,201.0 474.1,199.1 474.7,197.0 475.4,194.7 476.0,192.4 476.7,190.0 477.3,187.5 478.0,185.0" fill="none" stroke="#0d9488" stroke-width="1.8" stroke-linejoin="round"/>
@@ -27,17 +26,17 @@
<text x="529.0" y="226.0" font-size="10.5" text-anchor="middle" fill="#64748b">方波</text>
<polyline points="580.0,185.0 580.7,183.4 581.3,181.8 582.0,180.2 582.6,178.6 583.3,177.0 583.9,175.4 584.6,173.8 585.2,172.2 585.9,170.6 586.6,169.0 587.2,167.4 587.9,165.8 588.5,164.2 589.2,162.6 589.8,161.4 590.5,163.0 591.1,164.6 591.8,166.2 592.5,167.8 593.1,169.4 593.8,171.0 594.4,172.6 595.1,174.2 595.7,175.8 596.4,177.4 597.0,179.0 597.7,180.6 598.4,182.2 599.0,183.8 599.7,185.4 600.3,187.0 601.0,188.6 601.6,190.2 602.3,191.8 602.9,193.4 603.6,195.0 604.3,196.6 604.9,198.2 605.6,199.8 606.2,201.4 606.9,203.0 607.5,204.6 608.2,206.2 608.8,207.8 609.5,208.2 610.2,206.6 610.8,205.0 611.5,203.4 612.1,201.8 612.8,200.2 613.4,198.6 614.1,197.0 614.7,195.4 615.4,193.8 616.1,192.2 616.7,190.6 617.4,189.0 618.0,187.4 618.7,185.8 619.3,184.2 620.0,182.6 620.6,181.0 621.3,179.4 621.9,177.8 622.6,176.2 623.3,174.6 623.9,173.0 624.6,171.4 625.2,169.8 625.9,168.2 626.5,166.6 627.2,165.0 627.8,163.4 628.5,161.8 629.2,162.2 629.8,163.8 630.5,165.4 631.1,167.0 631.8,168.6 632.4,170.2 633.1,171.8 633.7,173.4 634.4,175.0 635.1,176.6 635.7,178.2 636.4,179.8 637.0,181.4 637.7,183.0 638.3,184.6 639.0,186.2 639.6,187.8 640.3,189.4 641.0,191.0 641.6,192.6 642.3,194.2 642.9,195.8 643.6,197.4 644.2,199.0 644.9,200.6 645.5,202.2 646.2,203.8 646.9,205.4 647.5,207.0 648.2,208.6 648.8,207.4 649.5,205.8 650.1,204.2 650.8,202.6 651.4,201.0 652.1,199.4 652.8,197.8 653.4,196.2 654.1,194.6 654.7,193.0 655.4,191.4 656.0,189.8 656.7,188.2 657.3,186.6 658.0,185.0" fill="none" stroke="#0d9488" stroke-width="1.8" stroke-linejoin="round"/>
<text x="619.0" y="226.0" font-size="10.5" text-anchor="middle" fill="#64748b">三角</text>
<text x="394.0" y="151.0" font-size="10.5" fill="#64748b">同峰值对齐(不是同 Vrms)</text>
<text x="394.0" y="151.0" font-size="10.5" fill="#64748b">同设 Vpp 2.828 V(不是同有效值)</text>
<text x="384.0" y="244.0" font-size="11" font-weight="700" fill="#0d9488">注入</text>
<text x="420.0" y="244.0" font-size="11.5" fill="#64748b">同峰值 1.414 V 改方波 / 三角(面板有效值各</text>
<text x="420.0" y="260.0" font-size="11.5" fill="#64748b">异)</text>
<text x="420.0" y="244.0" font-size="11.5" fill="#64748b">同设 Vpp 2.828 V 改方波 / 三角(面板有效</text>
<text x="420.0" y="260.0" font-size="11.5" fill="#64748b">值各异)</text>
<text x="384.0" y="280.0" font-size="11" font-weight="700" fill="#0d9488">判据</text>
<text x="420.0" y="280.0" font-size="11.5" fill="#0f172a">读数只跟基波:方波 ≈1278、三角 ≈817,±2 %</text>
<text x="384.0" y="300.0" font-size="11" font-weight="700" fill="#0d9488">出口</text>
<text x="384.0" y="300.0" font-size="11" font-weight="700" fill="#0d9488">读数</text>
<text x="420.0" y="300.0" font-size="11.5" fill="#64748b">meas: ua_mv;抬头记万用表响应类型</text>
<rect x="698.0" y="106.0" width="314.0" height="246.0" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-width="2"/>
<text x="710.0" y="132.0" font-size="15" font-weight="700" fill="#0f172a">相位轮</text>
<text x="1000.0" y="132.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#16a34a">定量 · 取段</text>
<text x="1000.0" y="132.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#16a34a">定量 · 录波</text>
<rect x="710.0" y="142.0" width="290.0" height="86.0" rx="6" fill="#ffffff" stroke="#cbd5e1" stroke-width="1"/>
<line x1="714.0" y1="185.0" x2="996.0" y2="185.0" stroke="#94a3b8" stroke-width="0.8" stroke-dasharray="3 3"/>
<polyline points="720.0,185.0 721.9,183.4 723.9,181.8 725.8,180.2 727.8,178.6 729.7,177.1 731.7,175.6 733.6,174.1 735.5,172.7 737.5,171.4 739.4,170.1 741.4,168.9 743.3,167.7 745.3,166.7 747.2,165.7 749.1,164.8 751.1,164.0 753.0,163.3 755.0,162.7 756.9,162.2 758.8,161.8 760.8,161.5 762.7,161.3 764.7,161.2 766.6,161.3 768.6,161.4 770.5,161.7 772.4,162.0 774.4,162.5 776.3,163.1 778.3,163.7 780.2,164.5 782.2,165.4 784.1,166.3 786.0,167.4 788.0,168.5 789.9,169.7 791.9,170.9 793.8,172.3 795.8,173.7 797.7,175.1 799.6,176.6 801.6,178.1 803.5,179.7 805.5,181.3 807.4,182.9 809.4,184.5 811.3,186.1 813.2,187.7 815.2,189.3 817.1,190.8 819.1,192.4 821.0,193.9 822.9,195.4 824.9,196.8 826.8,198.2 828.8,199.5 830.7,200.7 832.7,201.9 834.6,203.0 836.5,204.0 838.5,204.9 840.4,205.8 842.4,206.5 844.3,207.1 846.3,207.7 848.2,208.1 850.1,208.4 852.1,208.6 854.0,208.7 856.0,208.7 857.9,208.6 859.9,208.4 861.8,208.1 863.7,207.7 865.7,207.1 867.6,206.5 869.6,205.8 871.5,204.9 873.5,204.0 875.4,203.0 877.3,201.9 879.3,200.7 881.2,199.5 883.2,198.2 885.1,196.8 887.1,195.4 889.0,193.9 890.9,192.4 892.9,190.8 894.8,189.3 896.8,187.7 898.7,186.1 900.6,184.5 902.6,182.9 904.5,181.3 906.5,179.7 908.4,178.1 910.4,176.6 912.3,175.1 914.2,173.7 916.2,172.3 918.1,170.9 920.1,169.7 922.0,168.5 924.0,167.4 925.9,166.3 927.8,165.4 929.8,164.5 931.7,163.7 933.7,163.1 935.6,162.5 937.6,162.0 939.5,161.7 941.4,161.4 943.4,161.3 945.3,161.2 947.3,161.3 949.2,161.5 951.2,161.8 953.1,162.2 955.0,162.7 957.0,163.3 958.9,164.0 960.9,164.8 962.8,165.7 964.7,166.7 966.7,167.7 968.6,168.9 970.6,170.1 972.5,171.4 974.5,172.7 976.4,174.1 978.3,175.6 980.3,177.1 982.2,178.6 984.2,180.2 986.1,181.8 988.1,183.4 990.0,185.0" fill="none" stroke="#16a34a" stroke-width="2" stroke-linejoin="round"/>
@@ -45,16 +44,17 @@
<text x="720.0" y="226.0" font-size="10.5" font-weight="700" fill="#16a34a">UA 0° 实线</text>
<text x="798.0" y="226.0" font-size="10.5" font-weight="700" fill="#e11d48">UN +90° 红虚线</text>
<text x="710.0" y="244.0" font-size="11" font-weight="700" fill="#16a34a">注入</text>
<text x="746.0" y="244.0" font-size="11.5" fill="#64748b">UA(R31)与 UN(R28)同源同幅 1.000 Vrms</text>
<text x="710.0" y="264.0" font-size="11" font-weight="700" fill="#16a34a">判据</text>
<text x="746.0" y="264.0" font-size="11.5" fill="#0f172a">同段内相位差 ≈0°,B 通道改 90° 应跟到 ≈90</text>
<text x="746.0" y="280.0" font-size="11.5" fill="#0f172a">°,±2°</text>
<text x="710.0" y="300.0" font-size="11" font-weight="700" fill="#16a34a">出口</text>
<text x="746.0" y="300.0" font-size="11.5" fill="#64748b">wave_read.py fft 定量;phasor(hb) 只核</text>
<text x="746.0" y="316.0" font-size="11.5" fill="#64748b">象限</text>
<text x="746.0" y="244.0" font-size="11.5" fill="#64748b">UA(R31)与 UN(R28)同源同幅 Vpp 2.828 </text>
<text x="746.0" y="260.0" font-size="11.5" fill="#64748b">V</text>
<text x="710.0" y="280.0" font-size="11" font-weight="700" fill="#16a34a">判据</text>
<text x="746.0" y="280.0" font-size="11.5" fill="#0f172a">同段内相位差 ≈0°,B 通道改 90° 应跟到 ≈90</text>
<text x="746.0" y="296.0" font-size="11.5" fill="#0f172a">°,±2°</text>
<text x="710.0" y="316.0" font-size="11" font-weight="700" fill="#16a34a">读数</text>
<text x="746.0" y="316.0" font-size="11.5" fill="#64748b">wave_read.py fft 定量;phasor(hb) 只核</text>
<text x="746.0" y="332.0" font-size="11.5" fill="#64748b">象限</text>
<rect x="46.0" y="366.0" width="314.0" height="246.0" rx="10" fill="#fffbeb" stroke="#d97706" stroke-width="2"/>
<text x="58.0" y="392.0" font-size="15" font-weight="700" fill="#0f172a">测频轮</text>
<text x="348.0" y="392.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#d97706">A 档无数 = 过</text>
<text x="348.0" y="392.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#d97706">测不出数 = 过</text>
<rect x="58.0" y="402.0" width="290.0" height="86.0" rx="6" fill="#ffffff" stroke="#cbd5e1" stroke-width="1"/>
<line x1="62.0" y1="445.0" x2="344.0" y2="445.0" stroke="#94a3b8" stroke-width="0.8" stroke-dasharray="3 3"/>
<polyline points="73.0,445.0 73.8,443.6 74.6,442.3 75.4,440.9 76.2,439.6 77.0,438.3 77.8,437.0 78.7,435.8 79.5,434.5 80.3,433.3 81.1,432.2 81.9,431.0 82.7,430.0 83.5,428.9 84.3,428.0 85.1,427.0 85.9,426.2 86.7,425.4 87.5,424.7 88.4,424.0 89.2,423.4 90.0,422.9 90.8,422.4 91.6,422.0 92.4,421.7 93.2,421.5 94.0,421.3 94.8,421.2 95.6,421.2 96.4,421.3 97.2,421.5 98.1,421.7 98.9,422.0 99.7,422.4 100.5,422.9 101.3,423.4 102.1,424.0 102.9,424.7 103.7,425.4 104.5,426.2 105.3,427.0 106.1,428.0 106.9,428.9 107.7,430.0 108.6,431.0 109.4,432.2 110.2,433.3 111.0,434.5 111.8,435.8 112.6,437.0 113.4,438.3 114.2,439.6 115.0,440.9 115.8,442.3 116.6,443.6 117.4,445.0 118.3,446.4 119.1,447.7 119.9,449.1 120.7,450.4 121.5,451.7 122.3,453.0 123.1,454.2 123.9,455.5 124.7,456.7 125.5,457.8 126.3,459.0 127.1,460.0 127.9,461.1 128.8,462.0 129.6,463.0 130.4,463.8 131.2,464.6 132.0,465.3 132.8,466.0 133.6,466.6 134.4,467.1 135.2,467.6 136.0,468.0 136.8,468.3 137.6,468.5 138.5,468.7 139.3,468.8 140.1,468.8 140.9,468.7 141.7,468.5 142.5,468.3 143.3,468.0 144.1,467.6 144.9,467.1 145.7,466.6 146.5,466.0 147.3,465.3 148.2,464.6 149.0,463.8 149.8,463.0 150.6,462.0 151.4,461.1 152.2,460.0 153.0,459.0" fill="none" stroke="#d97706" stroke-width="1.8" stroke-linejoin="round"/>
@@ -66,10 +66,10 @@
<text x="58.0" y="504.0" font-size="11" font-weight="700" fill="#d97706">注入</text>
<text x="94.0" y="504.0" font-size="11.5" fill="#64748b">UA 同幅值轮接法,扫 45 / 50 / 55 Hz</text>
<text x="58.0" y="524.0" font-size="11" font-weight="700" fill="#d97706">判据</text>
<text x="94.0" y="524.0" font-size="11.5" fill="#0f172a">A 档如实报不可测:valid=0、UI 显 --;报 5</text>
<text x="94.0" y="540.0" font-size="11.5" fill="#0f172a">0.00 反而是 bug</text>
<text x="58.0" y="560.0" font-size="11" font-weight="700" fill="#d97706">出口</text>
<text x="94.0" y="560.0" font-size="11.5" fill="#64748b">freq(hb) 0.01 Hz;UI Hz、104 0x401D</text>
<text x="94.0" y="524.0" font-size="11.5" fill="#0f172a">如实报不可测(现状固件):valid=0、UI 显 -</text>
<text x="94.0" y="540.0" font-size="11.5" fill="#0f172a">-;报 50.00 反而是 bug</text>
<text x="58.0" y="560.0" font-size="11" font-weight="700" fill="#d97706">读数</text>
<text x="94.0" y="560.0" font-size="11.5" fill="#64748b">freq(hb) 0.01 Hz;UI Hz</text>
<rect x="372.0" y="366.0" width="314.0" height="246.0" rx="10" fill="#fff1f2" stroke="#e11d48" stroke-width="2"/>
<text x="384.0" y="392.0" font-size="15" font-weight="700" fill="#0f172a">削顶轮</text>
<text x="674.0" y="392.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#e11d48">负向 · 单独日志</text>
@@ -81,26 +81,27 @@
<text x="420.0" y="504.0" font-size="11.5" fill="#64748b">UB(R52):DC −1.00 V 保持 ≥5 s,或 2.0 </text>
<text x="420.0" y="520.0" font-size="11.5" fill="#64748b">Hz 正弦</text>
<text x="384.0" y="540.0" font-size="11" font-weight="700" fill="#e11d48">判据</text>
<text x="420.0" y="540.0" font-size="11.5" fill="#0f172a">连续 20 拍贴底 → rail / edge 锁存 + 104 </text>
<text x="420.0" y="556.0" font-size="11.5" fill="#0f172a">该路 IV 清零;50 Hz 正弦不锁存 = 预期</text>
<text x="384.0" y="576.0" font-size="11" font-weight="700" fill="#e11d48">出口</text>
<text x="420.0" y="576.0" font-size="11.5" fill="#64748b">cal: rail / edge;取段再跑 shape 对表</text>
<text x="420.0" y="540.0" font-size="11.5" fill="#0f172a">连续 20 拍贴底 → rail / edge 锁存;50 Hz</text>
<text x="420.0" y="556.0" font-size="11.5" fill="#0f172a"> 正弦不锁存 = 预期</text>
<text x="384.0" y="576.0" font-size="11" font-weight="700" fill="#e11d48">读数</text>
<text x="420.0" y="576.0" font-size="11.5" fill="#64748b">cal: rail / edge;录波再跑 shape 对表</text>
<rect x="698.0" y="366.0" width="314.0" height="246.0" rx="10" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
<text x="710.0" y="392.0" font-size="15" font-weight="700" fill="#0f172a">谐波轮</text>
<text x="1000.0" y="392.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#7c3aed">定量 · 取段</text>
<text x="1000.0" y="392.0" font-size="10.5" text-anchor="end" font-weight="700" fill="#7c3aed">定量 · 录波</text>
<rect x="710.0" y="402.0" width="290.0" height="86.0" rx="6" fill="#ffffff" stroke="#cbd5e1" stroke-width="1"/>
<line x1="714.0" y1="445.0" x2="996.0" y2="445.0" stroke="#94a3b8" stroke-width="0.8" stroke-dasharray="3 3"/>
<polyline points="720.0,445.0 721.9,442.2 723.9,439.5 725.8,437.1 727.8,435.0 729.7,433.4 731.7,432.3 733.6,431.6 735.5,431.3 737.5,431.3 739.4,431.4 741.4,431.5 743.3,431.5 745.3,431.3 747.2,430.9 749.1,430.2 751.1,429.2 753.0,428.1 755.0,426.8 756.9,425.7 758.8,424.7 760.8,424.0 762.7,423.7 764.7,423.8 766.6,424.4 768.6,425.5 770.5,427.0 772.4,428.8 774.4,430.7 776.3,432.7 778.3,434.5 780.2,436.0 782.2,437.2 784.1,438.0 786.0,438.5 788.0,438.6 789.9,438.4 791.9,438.0 793.8,437.7 795.8,437.4 797.7,437.4 799.6,437.7 801.6,438.4 803.5,439.4 805.5,440.8 807.4,442.5 809.4,444.4 811.3,446.3 813.2,448.1 815.2,449.7 817.1,451.0 819.1,451.9 821.0,452.5 822.9,452.6 824.9,452.5 826.8,452.2 828.8,451.8 830.7,451.5 832.7,451.4 834.6,451.6 836.5,452.2 838.5,453.1 840.4,454.5 842.4,456.1 844.3,458.0 846.3,459.9 848.2,461.8 850.1,463.5 852.1,464.9 854.0,465.8 856.0,466.3 857.9,466.3 859.9,465.8 861.8,465.0 863.7,464.0 865.7,462.8 867.6,461.6 869.6,460.5 871.5,459.6 873.5,458.9 875.4,458.6 877.3,458.5 879.3,458.5 881.2,458.7 883.2,458.7 885.1,458.6 887.1,458.2 889.0,457.3 890.9,456.1 892.9,454.3 894.8,452.2 896.8,449.7 898.7,446.9 900.6,444.0 902.6,441.2 904.5,438.6 906.5,436.3 908.4,434.4 910.4,433.0 912.3,432.1 914.2,431.5 916.2,431.3 918.1,431.3 920.1,431.4 922.0,431.5 924.0,431.5 925.9,431.2 927.8,430.7 929.8,429.9 931.7,428.8 933.7,427.6 935.6,426.4 937.6,425.3 939.5,424.4 941.4,423.8 943.4,423.7 945.3,424.0 947.3,424.8 949.2,426.0 951.2,427.6 953.1,429.4 955.0,431.4 957.0,433.3 958.9,435.0 960.9,436.5 962.8,437.5 964.7,438.2 966.7,438.5 968.6,438.5 970.6,438.3 972.5,437.9 974.5,437.6 976.4,437.4 978.3,437.4 980.3,437.9 982.2,438.7 984.2,439.8 986.1,441.4 988.1,443.1 990.0,445.0" fill="none" stroke="#7c3aed" stroke-width="2" stroke-linejoin="round"/>
<text x="720.0" y="411.0" font-size="10.5" fill="#64748b">基波 + 2 次 + 5 次</text>
<text x="710.0" y="504.0" font-size="11" font-weight="700" fill="#7c3aed">注入</text>
<text x="746.0" y="504.0" font-size="11.5" fill="#64748b">IA(R19):基波 0.800 + 二次 0.160 + 五</text>
<text x="746.0" y="520.0" font-size="11.5" fill="#64748b">次 0.160 Vrms</text>
<text x="746.0" y="504.0" font-size="11.5" fill="#64748b">IA(R19):分三注,基波 Vpp 2.263、二五次</text>
<text x="746.0" y="520.0" font-size="11.5" fill="#64748b">各 Vpp 0.453</text>
<text x="710.0" y="540.0" font-size="11" font-weight="700" fill="#7c3aed">判据</text>
<text x="746.0" y="540.0" font-size="11.5" fill="#0f172a">Ia2 / Ia5 各 ≈0.160 Vrms,±2 %(最坏峰值</text>
<text x="746.0" y="556.0" font-size="11.5" fill="#0f172a"> 1.58 V 留余量)</text>
<text x="710.0" y="576.0" font-size="11" font-weight="700" fill="#7c3aed">出口</text>
<text x="746.0" y="540.0" font-size="11.5" fill="#0f172a">Ia2 / Ia5 各读数 ≈0.160 V,±2 %(最坏峰</text>
<text x="746.0" y="556.0" font-size="11.5" fill="#0f172a">值 1.58 V 留余量)</text>
<text x="710.0" y="576.0" font-size="11" font-weight="700" fill="#7c3aed">读数</text>
<text x="746.0" y="576.0" font-size="11.5" fill="#64748b">wave_read.py fft --ch 4;UI Ia2 / Ia5 </text>
<text x="746.0" y="592.0" font-size="11.5" fill="#64748b">目检旁证</text>
<line x1="46.0" y1="634.0" x2="1014.0" y2="634.0" stroke="#cbd5e1" stroke-width="1"/>
<text x="46.0" y="656.0" font-size="13" fill="#64748b">顺序与耗时见 §8.6(约 135 min 一趟 session);[ERR] 判读见 §8.7,失败先查 §8.8 分诊表。波形缩略图只表形状,不代表真实幅度刻度。</text>
<text x="46.0" y="656.0" font-size="13" fill="#64748b">六轮已跑完(2026-11-02 / 11-04):实测结论见 docs/硬件平台/ADC通道映射说明.md §8.2,谐波 B 行已归因板端前端 RC(fc≈796 Hz)。波形缩略图只表</text>
<text x="46.0" y="675.0" font-size="13" fill="#64748b">形状,不代表真实幅度刻度。</text>
</svg>

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

+75 -75
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1560" height="1192" viewBox="0 0 1560 1192" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
<rect x="0" y="0" width="1560" height="1192" fill="#ffffff"/>
<svg xmlns="http://www.w3.org/2000/svg" width="1560" height="1240" viewBox="0 0 1560 1240" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
<rect x="0" y="0" width="1560" height="1240" fill="#ffffff"/>
<text x="26" y="40" font-size="20" font-weight="700" fill="#0f172a">STM32F4-Base 项目分层架构</text>
<text x="26" y="62" font-size="13" fill="#64748b">STM32F407ZGTx @168MHz · FreeRTOS V10.3.1 (CMSIS-RTOS2) · 上层只依赖相邻下层</text>
<rect x="26" y="78" width="1508" height="92" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-opacity="0.45"/>
@@ -7,7 +7,7 @@
<text x="134" y="104" font-size="12" fill="#64748b">顶层共享配置 · 被 UI 与基础服务共用</text>
<rect x="44" y="116" width="489" height="38" rx="8" fill="#ffffff" stroke="#2563eb" stroke-opacity="0.55"/>
<text x="288" y="139" font-size="13" fill="#1e293b" text-anchor="middle">net_cfg(网口配置:IP/掩码/网关/MAC · /config.ini [network] 段)</text>
<rect x="26" y="214" width="741" height="284" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.45"/>
<rect x="26" y="214" width="741" height="332" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-opacity="0.45"/>
<text x="44" y="240" font-size="17" font-weight="700" fill="#7c3aed">② UI 层</text>
<text x="129" y="240" font-size="12" fill="#64748b">Drivers/BSP/UI · 一份显存三出(模拟器/屏板/远程)</text>
<rect x="44" y="252" width="162" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
@@ -32,7 +32,7 @@
<text x="330" y="419" font-size="13" fill="#1e293b" text-anchor="middle">ui_font_cn_12(中文字体)</text>
<rect x="444" y="396" width="172" height="38" rx="8" fill="#ffffff" stroke="#7c3aed" stroke-opacity="0.55"/>
<text x="530" y="419" font-size="13" fill="#1e293b" text-anchor="middle">ui_deco(浅 V 装饰)</text>
<rect x="793" y="214" width="741" height="284" rx="12" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.45"/>
<rect x="793" y="214" width="741" height="332" rx="12" fill="#f0fdfa" stroke="#0d9488" stroke-opacity="0.45"/>
<text x="811" y="240" font-size="17" font-weight="700" fill="#0d9488">③ 基础服务层</text>
<text x="935" y="240" font-size="12" fill="#64748b">应用侧服务 / 编排</text>
<rect x="811" y="252" width="170" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
@@ -55,77 +55,77 @@
<text x="960" y="467" font-size="13" fill="#1e293b" text-anchor="middle">cfg_file · prot_set_store(配置引擎)</text>
<rect x="1119" y="444" width="148" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1193" y="467" font-size="13" fill="#1e293b" text-anchor="middle">lftpd(FTP :21)</text>
<rect x="1277" y="444" width="227" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="1391" y="467" font-size="13" fill="#1e293b" text-anchor="middle">time_sync(TCP 对时 :8888)</text>
<rect x="26" y="542" width="1508" height="140" rx="12" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
<text x="44" y="568" font-size="17" font-weight="700" fill="#16a34a">④ 中间件层</text>
<text x="151" y="568" font-size="12" fill="#64748b">通用库 / 协议栈</text>
<rect x="44" y="580" width="675" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="381" y="603" font-size="13" fill="#1e293b" text-anchor="middle">net_socket / net_select / net_events / net_listener / net_pending(BSD API + 阻塞模型)</text>
<rect x="729" y="580" width="147" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="803" y="603" font-size="13" fill="#1e293b" text-anchor="middle">FatFs(文件系统)</text>
<rect x="887" y="580" width="186" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="980" y="603" font-size="13" fill="#1e293b" text-anchor="middle">nand_ftl(dhara FTL)</text>
<rect x="1083" y="580" width="164" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1165" y="603" font-size="13" fill="#1e293b" text-anchor="middle">LVGL v9.3(图形库)</text>
<rect x="1258" y="580" width="177" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1347" y="603" font-size="13" fill="#1e293b" text-anchor="middle">sys_clock(墙钟基准)</text>
<rect x="44" y="628" width="219" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="153" y="651" font-size="13" fill="#1e293b" text-anchor="middle">task_watchdog(IWDG 判定)</text>
<rect x="26" y="726" width="1508" height="92" rx="12" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.45"/>
<text x="44" y="752" font-size="17" font-weight="700" fill="#d97706">⑤ 驱动层</text>
<text x="134" y="752" font-size="12" fill="#64748b">Drivers/BSP · 芯片时序/中断</text>
<rect x="44" y="764" width="178" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="133" y="787" font-size="13" fill="#1e293b" text-anchor="middle">ch395f(SPI2 + DMA)</text>
<rect x="232" y="764" width="163" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="314" y="787" font-size="13" fill="#1e293b" text-anchor="middle">gd5f2gq5ue(SPI1)</text>
<rect x="406" y="764" width="195" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="503" y="787" font-size="13" fill="#1e293b" text-anchor="middle">tpafe5160(并行 16 位)</text>
<rect x="611" y="764" width="133" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="677" y="787" font-size="13" fill="#1e293b" text-anchor="middle">sd2506(I2C1)</text>
<rect x="754" y="764" width="171" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="840" y="787" font-size="13" fill="#1e293b" text-anchor="middle">rs485(UART4 + DE)</text>
<rect x="935" y="764" width="388" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="1130" y="787" font-size="13" fill="#1e293b" text-anchor="middle">sb_link · sb_proto(屏板链路 · USART1 700 kbaud)</text>
<rect x="26" y="862" width="1508" height="92" rx="12" fill="#f1f5f9" stroke="#475569" stroke-opacity="0.45"/>
<text x="44" y="888" font-size="17" font-weight="700" fill="#475569">⑥ 内核 / HAL</text>
<text x="176" y="888" font-size="12" fill="#64748b">调度 · 外设抽象</text>
<rect x="44" y="900" width="329" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="208" y="923" font-size="13" fill="#1e293b" text-anchor="middle">FreeRTOS V10.3.1(CMSIS-RTOS2 · heap_4)</text>
<rect x="383" y="900" width="189" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="478" y="923" font-size="13" fill="#1e293b" text-anchor="middle">STM32 HAL(时基 TIM7)</text>
<rect x="582" y="900" width="144" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="655" y="923" font-size="13" fill="#1e293b" text-anchor="middle">CMSIS / 启动文件</text>
<rect x="26" y="998" width="1508" height="140" rx="12" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
<text x="44" y="1024" font-size="17" font-weight="700" fill="#e11d48">⑦ 硬件</text>
<text x="117" y="1024" font-size="12" fill="#64748b">STM32F407ZGTx @168MHz</text>
<rect x="44" y="1036" width="179" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="133" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">CH395F 以太网(SPI2)</text>
<rect x="233" y="1036" width="201" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="334" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">GD5F2GQ5UE NAND(SPI1)</text>
<rect x="445" y="1036" width="182" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="536" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">TPAFE5160 ADC(并行)</text>
<rect x="637" y="1036" width="163" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="718" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">SD2506 RTC(I2C1)</text>
<rect x="810" y="1036" width="202" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="912" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">RS-485(UART4 · 未接线)</text>
<rect x="1023" y="1036" width="219" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="1133" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">屏板(USART1 · 700 kbaud)</text>
<rect x="1253" y="1036" width="249" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="1377" y="1059" font-size="13" fill="#1e293b" text-anchor="middle">RS232#1 日志(UART5 · 115200)</text>
<rect x="44" y="1084" width="77" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="82" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">6x LED</text>
<rect x="811" y="492" width="355" height="38" rx="8" fill="#ffffff" stroke="#0d9488" stroke-opacity="0.55"/>
<text x="988" y="515" font-size="13" fill="#1e293b" text-anchor="middle">time_sntp(SNTP 对时,UDP 出向;:8888 已退役)</text>
<rect x="26" y="590" width="1508" height="140" rx="12" fill="#f0fdf4" stroke="#16a34a" stroke-opacity="0.45"/>
<text x="44" y="616" font-size="17" font-weight="700" fill="#16a34a">④ 中间件层</text>
<text x="151" y="616" font-size="12" fill="#64748b">通用库 / 协议栈</text>
<rect x="44" y="628" width="675" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="381" y="651" font-size="13" fill="#1e293b" text-anchor="middle">net_socket / net_select / net_events / net_listener / net_pending(BSD API + 阻塞模型)</text>
<rect x="729" y="628" width="147" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="803" y="651" font-size="13" fill="#1e293b" text-anchor="middle">FatFs(文件系统)</text>
<rect x="887" y="628" width="186" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="980" y="651" font-size="13" fill="#1e293b" text-anchor="middle">nand_ftl(dhara FTL)</text>
<rect x="1083" y="628" width="164" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1165" y="651" font-size="13" fill="#1e293b" text-anchor="middle">LVGL v9.3(图形库)</text>
<rect x="1258" y="628" width="177" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="1347" y="651" font-size="13" fill="#1e293b" text-anchor="middle">sys_clock(墙钟基准)</text>
<rect x="44" y="676" width="219" height="38" rx="8" fill="#ffffff" stroke="#16a34a" stroke-opacity="0.55"/>
<text x="153" y="699" font-size="13" fill="#1e293b" text-anchor="middle">task_watchdog(IWDG 判定)</text>
<rect x="26" y="774" width="1508" height="92" rx="12" fill="#fffbeb" stroke="#d97706" stroke-opacity="0.45"/>
<text x="44" y="800" font-size="17" font-weight="700" fill="#d97706">⑤ 驱动层</text>
<text x="134" y="800" font-size="12" fill="#64748b">Drivers/BSP · 芯片时序/中断</text>
<rect x="44" y="812" width="178" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="133" y="835" font-size="13" fill="#1e293b" text-anchor="middle">ch395f(SPI2 + DMA)</text>
<rect x="232" y="812" width="163" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="314" y="835" font-size="13" fill="#1e293b" text-anchor="middle">gd5f2gq5ue(SPI1)</text>
<rect x="406" y="812" width="195" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="503" y="835" font-size="13" fill="#1e293b" text-anchor="middle">tpafe5160(并行 16 位)</text>
<rect x="611" y="812" width="133" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="677" y="835" font-size="13" fill="#1e293b" text-anchor="middle">sd2506(I2C1)</text>
<rect x="754" y="812" width="171" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="840" y="835" font-size="13" fill="#1e293b" text-anchor="middle">rs485(UART4 + DE)</text>
<rect x="935" y="812" width="388" height="38" rx="8" fill="#ffffff" stroke="#d97706" stroke-opacity="0.55"/>
<text x="1130" y="835" font-size="13" fill="#1e293b" text-anchor="middle">sb_link · sb_proto(屏板链路 · USART1 700 kbaud)</text>
<rect x="26" y="910" width="1508" height="92" rx="12" fill="#f1f5f9" stroke="#475569" stroke-opacity="0.45"/>
<text x="44" y="936" font-size="17" font-weight="700" fill="#475569">⑥ 内核 / HAL</text>
<text x="176" y="936" font-size="12" fill="#64748b">调度 · 外设抽象</text>
<rect x="44" y="948" width="329" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="208" y="971" font-size="13" fill="#1e293b" text-anchor="middle">FreeRTOS V10.3.1(CMSIS-RTOS2 · heap_4)</text>
<rect x="383" y="948" width="189" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="478" y="971" font-size="13" fill="#1e293b" text-anchor="middle">STM32 HAL(时基 TIM7)</text>
<rect x="582" y="948" width="144" height="38" rx="8" fill="#ffffff" stroke="#475569" stroke-opacity="0.55"/>
<text x="655" y="971" font-size="13" fill="#1e293b" text-anchor="middle">CMSIS / 启动文件</text>
<rect x="26" y="1046" width="1508" height="140" rx="12" fill="#fff1f2" stroke="#e11d48" stroke-opacity="0.45"/>
<text x="44" y="1072" font-size="17" font-weight="700" fill="#e11d48">⑦ 硬件</text>
<text x="117" y="1072" font-size="12" fill="#64748b">STM32F407ZGTx @168MHz</text>
<rect x="44" y="1084" width="179" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="133" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">CH395F 以太网(SPI2)</text>
<rect x="233" y="1084" width="201" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="334" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">GD5F2GQ5UE NAND(SPI1)</text>
<rect x="445" y="1084" width="182" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="536" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">TPAFE5160 ADC(并行)</text>
<rect x="637" y="1084" width="163" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="718" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">SD2506 RTC(I2C1)</text>
<rect x="810" y="1084" width="202" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="912" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">RS-485(UART4 · 未接线)</text>
<rect x="1023" y="1084" width="219" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="1133" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">屏板(USART1 · 700 kbaud)</text>
<rect x="1253" y="1084" width="249" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="1377" y="1107" font-size="13" fill="#1e293b" text-anchor="middle">RS232#1 日志(UART5 · 115200)</text>
<rect x="44" y="1132" width="77" height="38" rx="8" fill="#ffffff" stroke="#e11d48" stroke-opacity="0.55"/>
<text x="82" y="1155" font-size="13" fill="#1e293b" text-anchor="middle">6x LED</text>
<line x1="780.0" y1="176.0" x2="780.0" y2="199.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 206.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="396.5" y1="504.0" x2="396.5" y2="527.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 396.5 534.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="1163.5" y1="504.0" x2="1163.5" y2="527.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 1163.5 534.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="780.0" y1="688.0" x2="780.0" y2="711.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 718.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="780.0" y1="824.0" x2="780.0" y2="847.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 854.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="780.0" y1="960.0" x2="780.0" y2="983.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 990.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<text x="26" y="1174" font-size="12" fill="#94a3b8">上层只依赖相邻下层(单向);UI 层与基础服务层同级并列,均使用顶层 net_cfg。跨层调用细节见 docs/架构设计/项目架构总览.md §1.2</text>
<line x1="396.5" y1="552.0" x2="396.5" y2="575.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 396.5 582.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="1163.5" y1="552.0" x2="1163.5" y2="575.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 1163.5 582.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="780.0" y1="736.0" x2="780.0" y2="759.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 766.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="780.0" y1="872.0" x2="780.0" y2="895.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 902.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<line x1="780.0" y1="1008.0" x2="780.0" y2="1031.0" stroke="#94a3b8" stroke-width="2.5"/>
<path d="M 780.0 1038.0 l -6 -9 h 12 z" fill="#94a3b8"/>
<text x="26" y="1222" font-size="12" fill="#94a3b8">上层只依赖相邻下层(单向);UI 层与基础服务层同级并列,均使用顶层 net_cfg。跨层调用细节见 docs/架构设计/项目架构总览.md §1.2</text>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

+34 -24
View File
@@ -126,7 +126,8 @@ flowchart LR
end
DEV -- "USB:ST-Link 下载器" --> TGT
DEV -- "USB→RS-232 适配器:RS232#1 @115200 调试日志" --> TGT
TGT -- "以太网 CH395F<br/>FTP:21 / 对时:8888 / 远程显示:7003" --> RMT
TGT -- "以太网 CH395F<br/>FTP:21 / 远程显示:7003" --> RMT
TGT -. "对时:SNTP 客户端 UDP 123 出向(:8888 已退役)" .-> NTP["现场/公网 NTP 源"]
```
| 环境 | 角色 | 必需性 |
@@ -153,7 +154,7 @@ flowchart LR
1. **开关柜现场部署**:设备上电 → 按 `/config.ini` 出厂 IP 组网 → 主站连工厂默认 :2404 总召 →
现场用屏板看测量、按按键翻菜单;死机由看门狗自恢复(无需到场)。
2. **远程运维**:PC 经 :7003 看同一份 UI、注键操作;FTP 取 `/log`(运行/自检/SOE 记录)
与 `/config.ini`;改 IP 后 2~5 s 生效不重启;PC 推 :8888 对时。
与 `/config.ini`;改 IP 后 2~5 s 生效不重启;SNTP 出向对时(原 PC 推 :8888 已退役)。
3. **脱离 PC 独立带屏运行**:串口屏板直连 USART1(700 kbaud),主机 275 ms 召唤、
≤200 ms 回 4208 B 帧(3200 B 显存 + 状态灯掩码 + info)——无 PC 也能看与按(§5.16)。
4. **保护运行(等标定解锁)**:1 kHz 采样 → 全周傅氏相量 → 20 ms 判据节拍 → 动作只落
@@ -250,7 +251,7 @@ flowchart LR
|---|---|---|---|
| P0 | FR-NET 网络通信(§5.1) | ✅ | CH395F + BSD 抽象 + 单连接自愈 |
| P0 | FR-STO 存储(§5.2) | ✅ | SPI NAND→dhara→FatFs,卷自愈 |
| P0 | FR-TIME 时间(§5.3) | ✅ | SD2506 墙钟 + :8888 对时(2038 已知限) |
| P0 | FR-TIME 时间(§5.3) | ✅ | SD2506 墙钟 + SNTP 对时(原 :8888 已退役;2038 已知限) |
| P0 | FR-CFG 配置引擎(§5.4) | ✅ | INI 段键表 + 外科保存 + 免重启生效 |
| P0 | FR-LOG 记录与日志(§5.5) | ✅ | 定长记录 + 控制台单写者 |
| P0 | FR-WD 看门狗(§5.7) | ✅ | 心跳位图 + 复位原因 + Fault 现场 |
@@ -319,10 +320,16 @@ flowchart LR
- 实现:`sys_clock_init()` 读一次 RTC 建基准(无效时按默认值自动校时);base+delta 法;30 天惰性再基准化(防 49.7 天 tick 回绕);`sys_clock_get_str()` → `"YYYY-MM-DD HH:MM:SS"`。
- 判据:日志时间戳真实正确;主机侧单测 100 断言点通过。
**FR-TIME-2 网络对时**
**FR-TIME-2 网络对时**(已退役 2026-11-04,由 FR-TIME-3 接替)
- 需求:PC 主动通过 TCP :8888 推送时间到设备,设备写回 RTC。
- 实现:`timeSyncTask` 等 `NET_READY` → TCP listen :8888 → 收 PC 推送 → 写 RTC;30 s 无包则掉线重建监听。
- 判据:对时后设备墙钟更新、日志时间戳正确。
- 退役:**已由 FR-TIME-3 SNTP 取代并退役**——`:8888` 监听、8 字节 "TIME" 协议、PC 推送器随
工单 #78 删除;网络校时唯一入口 = SNTP(FR-TIME-3)。历史实现见 git 历史与
`docs/子系统设计/系统墙钟时间维护说明.md` §15。
**FR-TIME-3 SNTP 校时** ✅(2026-11-04)
- 需求:设备作 SNTP(RFC 4330)客户端从配置的 IPv4 服务器经 UDP/123 取时写回 RTC;使能、主/备服务器、轮询周期可配(UI「SNTP 设置」页或 `/config.ini` `[sntp]` 段,保存后随下一轮生效)。
- 实现:`timeSyncTask` 200 ms 校时节拍驱动(`time_sync.c` v2.0,:8888 退役后任务即纯宿主;≤50 ms 收片切片,不饿任务狗与 IWDG);48 字节报文由 vendored coreSNTP 序列化器(`Lib/coreSNTP/`)编解码,节奏与判据在本方;防伪 = OriginTimestamp 须逐位等于发出的 T1(含随机小数位),mode / stratum / 闰秒指示 / Kiss-o'-Death 按 RFC 拒绝;主备轮换 + 失败指数退避(5 s 起、60 s 封顶)+ 首轮 1~5 分钟随机;成功时 |offset| < 2 s 只记日志不阶跃,更大偏差阶跃写回 SD2506——钟面 = 本地时间(UTC+8,沿用本地钟面时基口径,取舍见 docs/任务规划/UI补齐.md §4 时基口径注);首个校验通过的同步置 `SYS_EVT_TIMESYNC_READY`。
- 判据:主机单测 `test/host/sntp`(报文格式 / 伪造拒收 / KoD 轮换 / 退避与周期 / 阶跃域守卫);真机 = 使能并配置服务器后时钟与时间源一致、`[SNTP] step offset=` 日志在场(台架窗口统一做)。
> **已知限制**:uint32 epoch → **2038 年**上限。
@@ -613,7 +620,7 @@ flowchart LR
上电次数 + 本次复位原因快照)与 iec104_serv 的四个只读观测计数,经 info 帧下发
(口径 docs/UI设计/UI_数据供给设计说明.md §4.4)。
- 判据:五类记录各有写入点、可翻最近 20 条、空记录明确显示"无记录"、共用 reader——**软件面已满足**
(空态与四个范围标签由 66 张像素黄金钉住;模拟器侧这些文件没有写入方,只拍到空态)。
(空态与四个范围标签由 74 张像素黄金钉住;模拟器侧这些文件没有写入方,只拍到空态)。
真记录翻页、掉电后保持、清除记录后归零、boot_count 自增与四态判定**待台架复核**
(docs/任务规划/登记簿.md §7)。
@@ -1193,7 +1200,7 @@ for (i = 1; i < LED_COUNT; i++) /* LE
| 服务 | 协议 / 端口 | 用途 | 核对方法 |
|---|---|---|---|
| FTP | TCP **21** | 文件上传/下载(根目录 `/`,NAND 存储) | 登录后任意 FTP 客户端列目录/读写(鉴权见 §7.6) |
| 墙钟对时 | TCP **8888** | PC 主动推送时间到设备 | 对时后核对设备时间 / 日志时间戳 |
| 墙钟对时(SNTP 客户端) | UDP 出向 **123**(主/备服务器 `[sntp]` 段,enable=0 出厂默认) | 设备主动取 NTP 源时间写 RTC(:8888 推送监听**已删除**,2026-11-04,§6.3) | 使能后核对设备时间 / 日志时间戳与 `[SNTP] step offset=` 留痕 |
| 远程显示(RemoDispBus) | TCP **7003** | 远端看图(160×160 1bpp)+ 远端导航按键 | DTU-RemoteLCD 或任意 TCP 客户端连 7003 收帧 |
| IEC 60870-5-104 上行 | TCP **工厂默认 2404** | 被控站接入:遥测/遥信上送、总召、遥控/定值下发(点表见 §6.8) | 任意 104 主站连 2404,STARTDT+总召往返(→ `docs/子系统设计/IEC104上行设计说明.md`) |
@@ -1249,11 +1256,13 @@ for (i = 1; i < LED_COUNT; i++) /* LE
> 详见 `docs/UI设计/RemoDispBus协议与接收设计说明.md`。MCU 与模拟器**共用同一份** `ui_net_sink.c`。
### 6.3 对时协议
### 6.3 对时协议(SNTP 客户端)
- **载体**:TCP :8888,PC 主动推送,设备接收并写 RTC。
- **行为**:`timeSyncTask` 监听 → 收包 → 写回 RTC;30 s 无包则掉线重建监听(约 4 s)。
- **待补充**:推送报文的具体帧格式(当前实现细节见 `App/sync/time_sync.c` 与 `test/host/time_sync_server.py`)。
- **载体**:设备作 **SNTP(RFC 4330)客户端**,向 `[sntp]` 段配置的主/备服务器 UDP/123 出向取时,写回 RTC。
- **行为**:`timeSyncTask` 200 ms 节拍到点发 48 字节请求,应答经 OriginTimestamp 逐位防伪校验后按阈值步进;主备轮换 + 失败指数退避(5 s 起、60 s 封顶)。
- **退役**:原 **TCP :8888 PC 推送**(8 字节 `"TIME"` 帧)已于 2026-11-04 退役删除(工单 #78),
帧格式与实现细节见 git 历史;协议口径与教训沉淀于 `docs/子系统设计/系统墙钟时间维护说明.md` §15/§16。
- **详规**:报文编解码用 vendored coreSNTP 序列化器(`Lib/coreSNTP/`),判据见 §5.3 FR-TIME-3。
### 6.4 FTP 服务
@@ -1404,7 +1413,7 @@ mac=00:00:00:00:00:00
| 指标 | 基线值 | 来源 |
|---|---|---|
| M3 压力档帧周期 | med 60.1 / p95 66.4 / max 69.3 ms(零尖峰) | `docs/测试说明/专项验证/链路延迟测试说明.md` |
| 队列响应性(挂起客户端) | 92.5 ms(pending 一期) | `test/host/e02_probe.py` |
| 队列响应性(挂起客户端) | 92.5 ms(pending 一期) | 台架实测(探针 `e02_probe.py` 已删除——2026-11-04 随 :8888 退役,正文见 git 历史) |
| FTP 传输速率 | 约 49 / 60 KB/s(上传/下载) | 128 KB 实测 |
| 网络重建生效 | 2~5 s(含等链路 ≤3 s) | 配置设计 |
| UI 刷新 | 10 ms 循环 | `ui_task_func` |
@@ -1539,10 +1548,11 @@ pre-commit run --all-files
> 图里的节点数、绑页与密码门读数由 `tools/arch_diagram/gen_menu_tree_svg.py` 现算自真值 `ui_menu_tree.txt`(改菜单树 → 同步脚本内 `NODE_SRC` 重跑);级联框在屏上怎么摆(几何)→ `docs/UI设计/菜单页面设计.md` §4.1。
**规模**:菜单节点 **9** 个(含根)+ 叶子节点 **34** 个 = **43** 节点;最大深度 **4 级(主界面 / 一级 / 二级 / 三级菜单)**。
**绑定现状**(2026-10-29 复扫):**已绑内容页 30 叶 / 未绑 4 叶**(页面注册表 `UI_PAGE_MAP_COUNT` = 30,
与已绑叶数一致);未绑 4 叶 = 无开入硬件 3 + SNTP 1(取舍与重开条件见
**绑定现状**(2026-11-04 复扫):**已绑内容页 31 叶 / 未绑 3 叶**(页面注册表 `UI_PAGE_MAP_COUNT` = 31,
与已绑叶数一致);未绑 3 叶 = 无开入硬件 3(取舍与重开条件见
docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5 页 + 运行报告)已于 2026-10-28 落地绑页,
「手动录波」与「录波回放」两叶于 2026-10-29 随录波道绑页(判据见 docs/子系统设计/故障录波设计说明.md §4.1 与 §8.1)。
「手动录波」与「录波回放」两叶于 2026-10-29 随录波道绑页(判据见 docs/子系统设计/故障录波设计说明.md §4.1 与 §8.1),
「SNTP 设置」叶于 2026-11-04 随 SNTP 客户端立项绑页(判据见 §5.3 FR-TIME-3 与 docs/网络/BSD_Socket_API_使用指南.md §6.1)。
**根页(主界面)7 个一级条目**:装置信息 / 实时数据 / 参数定值 / 三遥设置 / 装置维护 / 通讯参数 / 记录查询。
@@ -1576,7 +1586,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
### 8.3 内容页
**已绑 30 页**(按分组;逐页形态见 [`docs/UI设计/页面补齐规划.md`](../UI设计/页面补齐规划.md)):
**已绑 31 页**(按分组;逐页形态见 [`docs/UI设计/页面补齐规划.md`](../UI设计/页面补齐规划.md)):
| 分组 | 页面 | 形态 |
|---|---|---|
@@ -1586,21 +1596,21 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| 参数定值 | 整定 4 页(参数 / 定值 / 控制字 / 软压板) | 可编辑(写值 + 置脏 + 保存反馈) |
| 三遥设置 | 遥测死区 / 遥测系数 | 可编辑(运行参数段,`0x8030~0x804F`) |
| 装置维护 | 时钟设置 / 强制复归 / 清除记录 / 手动录波 | 操作页(执行类必须出真反馈,不得"点了没效果";手动录波触发后回读同一份计数,判据见 docs/子系统设计/故障录波设计说明.md §8.2) |
| 通讯参数 | 通讯设置 / 网口设置 | 可编辑(`[comm]` / `[network]` 段,保存后按各自生效时机) |
| 通讯参数 | 通讯设置 / 网口设置 / SNTP 设置 | 可编辑(`[comm]` / `[network]` / `[sntp]` 段,保存后按各自生效时机;SNTP 页改后立即随下一轮生效,判据见 §5.3 FR-TIME-3) |
| 记录查询 | SOE 记录 / 自检记录 / 运行记录 | 记录列表(共用 reader) |
| 记录查询 | 事故记录 / 操作记录 / 保护告警 / 保护启动 / 遥控记录 | 记录列表(每记录 2 行、空态显「无记录」;写入点与判据见 App/log/README.md §五类记录) |
| 记录查询 | 运行报告 | 只读列表 13 行分 2 页;四个数逐个挂范围标签(自电池周期 / 本次开机),跨开机累计按裁定不做 |
| 记录查询 | 录波回放 | 单通道波形页(阶梯线自绘、8 路切换、单位徽标、削顶标记、空态与不可读态显式文案);显示契约见 docs/子系统设计/故障录波设计说明.md §8.1 |
**未绑 4 叶**(2026-10-28:记录 5 页与运行报告页已落地绑页,原「当前执行 6 项」清零;2026-10-29:
录波道落地,「手动录波」叶子随之一并绑页):
遥信类型 / 遥信防抖 / 双点遥信 = 等开入硬件(§9.1.1「出口与开入」);SNTP 设置 = 等 SNTP 对时(§9.1.2「SNTP 对时」)。
**未绑 3 叶**(2026-10-28:记录 5 页与运行报告页已落地绑页,原「当前执行 6 项」清零;2026-10-29:
录波道落地,「手动录波」叶子随之一并绑页;2026-11-04:SNTP 客户端立项落地,「SNTP 设置」叶绑页):
遥信类型 / 遥信防抖 / 双点遥信 = 等开入硬件(§9.1.1「出口与开入」)。
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),`ESC` 返回菜单屏。
- 菜单导航状态**不压层**,返回即原样还原;同一时刻至多一个内容页打开。
- 节点→页映射在逻辑层 `menu_content_page_for(node)`;未绑叶子进入空白终点框(不伪造数据)。
- 数据供给走**注入通道**(rt / par / info / 遥测死区与系数第四表),页面不直接触算法层;
像素黄金用例当前 **66 张**(菜单 8 状态另计;2026-10-28 记录 5 页与运行报告 2 页落地 +7 张;2026-10-29 录波 UI 道 6 张:手动录波 3 态 + 录波回放 3 态)。
像素黄金用例当前 **74 张**(菜单 8 状态另计;2026-10-28 记录 5 页与运行报告 2 页落地 +7 张;2026-10-29 录波 UI 道 6 张:手动录波 3 态 + 录波回放 3 态;2026-11-04 SNTP 设置页 +8 张)。
### 8.4 密码与权限
@@ -1664,7 +1674,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| **多规约栈** | 17 | 12 种 | 1 种(104) | +11 种(101 / 102 / 103 / CDT / ModBus / Whm_GB / 61850 / CAN + 子站侧 3 类) |
| **国密算法** | 18 | `Comm/Ecc/` 全套 | 0 | SM2 / SM3 / ECDSA |
| **软压板铺开** | 11 | 9 面(表容量 32) | 1 面 | +8 面 |
| **SNTP 对时** | 17 | 参考对时走 SNTP | 私有 TCP 8888 + 104 `C_CS` | SNTP 客户端 |
| **SNTP 对时** | 17 | 参考对时走 SNTP | **SNTP 客户端已落地**(2026-11-04:coreSNTP 序列化器 + `[sntp]` 配置 + UI 设置页 + timeSyncTask 接入,判据 §5.3 FR-TIME-3);另有 104 `C_CS`(私有 TCP 8888 已随 #78 退役删除) | 台架真机对时判据(现场时间源,docs/任务规划/登记簿.md) |
| **固有参数** | 1 | `8001H~800AH` 字符串类参数 | 未实现(无法短浮点上送) | 随送检点表设计 |
| **遥测增强** | 4、5 | k=1~9 谐波方和根 + 4 点平均 | 仅基波轨 | 谐波 / 等周波调频 / 零序自产外接切换 三条算法轨 |
| **屏板链路对齐** | 20 | **串口屏板通道**(**700000 8N1 / USART1 TTL 直连、无 DE**,帧 A 6 B 召唤、帧 B **4208 B** 应答:显存 3200 B + **状态灯 4 B 位掩码** + 4 B info)+ **7003 四命令**(INIT **16 B**,单色口径)+ 续传 / 拒绝握手 / 链路监视 / F1·F2 | **串口通道已交付**(4208 B 应答 / 状态灯掩码 / 键上行 / 双出口并发,屏板链路设计说明 §9.1);7003 四命令(INIT **8 B**);无续传、无 `bNeedFlash` 拒绝、无 1 分钟监视、键值 6 个 | 7003 报文口径 6 项(`参考产品复刻明细.md` §11 第 2~9 条);串口侧验收尾巴见屏板链路设计说明 §9.2 |
@@ -1748,7 +1758,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
| 5 | UI 功能 | 远端操作 | 菜单 7 项导航、内容页、密码(查看 `000` / 修改 `123`)、网口设置保存后立即生效 |
| 6 | 对时 | PC 推送 8888 | 设备墙钟更新、日志时间戳正确 |
| 6 | 对时 | SNTP 配置服务器对时(原 PC 推送 8888 已退役) | 设备墙钟更新、日志时间戳正确 |
| 7 | 掉电保持 | 写文件 / 改网口配置 / 改定值 → 重启 | 数据仍在(`/config.ini`、`/prot_set.bin` 按新值生效) |
| 8 | LED 状态 | 目检 | 6 路 LED 随系统状态变化(低电平点亮) |
| 9 | 104 总召与变位 | PC 主站连工厂默认 :2404 → STARTDT → 总召 | 四组数据回全、`actterm` 正常;制造变位后 COT=3 上送 |
+9 -9
View File
@@ -31,12 +31,12 @@
## 1. UI 补齐(执行清单已于 2026-10-28 清空)
菜单树共 34 叶(节点行合计 43):按行内 #page= 实测**已绑 30 叶、未绑 4 叶**(2026-10-29 复扫,
判定规则与重跑命令见 docs/任务规划/UI补齐.md §1「叶数判定与复核」)。未绑的 4 叶因缺少开入硬件
或 SNTP 产品定义而不做(原因与重开条件见 docs/任务规划/UI补齐.md §5)——**手动录波与录波回放两叶
菜单树共 34 叶(节点行合计 43):按行内 #page= 实测**已绑 31 叶、未绑 3 叶**(2026-11-04 SNTP 设置
叶随 #73 立项转绑 +1,判定规则与重跑命令见 docs/任务规划/UI补齐.md §1「叶数判定与复核」)。未绑的 3 叶
因缺少开入硬件不做(原因与重开条件见 docs/任务规划/UI补齐.md §5)——**手动录波与录波回放两叶
已随录波道绑页**,「不做」清单里的录波那一项已销。
**口径闭合**:未绑 4 = 待做 0(§1 清单已清空)+ 不做 4(遥信 3 / SNTP 1);**43 是节点行数、不是叶数**(= 9 个菜单节点 + 34 叶)。
已绑叶数以生成物 UI_PAGE_MAP_COUNT 为准:不剔 ui_menu_tree.txt 文件头的注释示例行时,按 #page= 文本计数会得 31(= 30 + 1 行示例)。
**口径闭合**:未绑 3 = 待做 0(§1 清单已清空)+ 不做 3(遥信 3);**43 是节点行数、不是叶数**(= 9 个菜单节点 + 34 叶)。
已绑叶数以生成物 UI_PAGE_MAP_COUNT 为准:不剔 ui_menu_tree.txt 文件头的注释示例行时,按 #page= 文本计数会得 32(= 31 + 1 行示例)。
**UI 补齐执行清单已于 2026-10-28 清空**:记录 5 页(事故 / 操作 / 保护告警 / 保护启动 / 遥控)与
运行报告页同日落地并绑页,内容页黄金参考 53 → 60 张(runreport 分 2 页,第 2 页单独拍一张;判据 = simulator/tests/ref/ 入库 shot_*.bin 实数,张数 = 页面个数 + 分页多拍的那一张;**该集合 2026-10-29 已扩至 66 张**,多出的是录波回放页 6 态)。
@@ -60,19 +60,19 @@ docs/测试说明/专项验证/模拟器差距清单.md §2.3「开机账本 boo
| **1** | **UI 补齐** | **清单已清空(2026-10-28)** | 记录 5 页与运行报告页已落地并绑页;余下 5 叶属 §5「当前不做」,重开条件见 docs/任务规划/UI补齐.md §5;上板判据见 docs/任务规划/登记簿.md §7 |
| **2** | **net 层跨上下文收口** | **下一顺位(未开工)** | UI 清单已清空,切换条件已满足;**开工需用户点头**——换主线是产品决策,不由本批顺带做掉 |
| **3** | 其余结构与可靠性评估 | 排队 | net 收口后;逐项见 `登记簿.md`,观察项不为了“清清单”而强做 |
| **4** | 数据可信(采集数据可信度) | 动态波形验证**手边可做**;变比确认与两点标定等外部资源 | 信号发生器动态波形验证(不依赖外部资源)→ 端子/子板变比确认 → 标准源两点标定;**排序调整(2026-10-29):动态验证让位给故障录波数据面**,段文件落地后判据升级为 PC 独立重算对表(docs/子系统设计/故障录波设计说明.md §11)(已知电压注入已于 2026-10-27 台架闭环并销项);外部资源到位可插入优先处理 |
| **4** | 数据可信(采集数据可信度) | 动态波形验证 **✅ 2026-11-04 闭环(六轮)**;变比确认与两点标定等外部资源 | 信号发生器动态波形验证(不依赖外部资源,✅ 2026-11-04)→ 端子/子板变比确认 → 标准源两点标定;**排序调整(2026-10-29):动态验证让位给故障录波数据面**,段文件落地后判据升级为 PC 独立重算对表(docs/子系统设计/故障录波设计说明.md §11)(已知电压注入已于 2026-10-27 台架闭环并销项);外部资源到位可插入优先处理 |
| **5** | 标准源送检与第三方 104 主站验收 | 等待标定完成 | 两点标定完成且保护 VALID 已置位;送检窗口前 2 日冻结特性 |
| **6** | 保护元件与采集增强 | 等待标准源或硬件改版 | 保护先解除未标定闭锁;三相同时刻类功能等 V0.2 硬件 |
| **7** | 验证窗口事项 | 逢条件顺带 | CI runner、PC peer、CH340、负向故障注入条件到位时执行 |
| **8** | 独立模块与暂缓项 | 当前不排 | 开入开出、SNTP、104 文件传输单独立项;RS-485 与内存优化按各自重启条件 |
| **8** | 独立模块与暂缓项 | 当前不排 | 开入开出、104 文件传输单独立项(SNTP 已单独立项并于 2026-11-04 落地 #73,:8888 随 #78 退役);RS-485 与内存优化按各自重启条件 |
| **独立** | **故障录波(1 kHz · 8 路原始码值 · 单通道回放)** | **代码面已交付;2026-10-31 远程半轮过六项、2026-11-02 回放页枚举缺陷已修复闭环,余注量对表与人到场项** | 用户裁定 2026-10-29 **录波先行**:数据面最小闭环 → 手动与保护启动触发 → 回放页。三块的**代码侧判据已收口**(主机单测 113 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器字节级互验、手动录波与录波回放两叶已绑页、内容页黄金参考扩至 66 张);**2026-10-31 远程半轮已过六项**(真 FAT 落盘、触发→落盘全部落在 2 s 封存超时窗内、冷却恢复、轮转、PC 侧坏段拒收、冻结窗内 104 并发);同轮发现的缺陷「回放页段表枚举恒空」**已于 2026-11-02 远程修复并板级闭环**(根因 = 读侧 `wave_store_seg_path()` 返回值判错,顺带修键处理缺失效;回放页真段/切段/切道已实测,登记簿条目已销项,证据见设计说明 §10「回放页枚举恒空缺陷修复记录」);**没跑的是注量对表那半轮**(注 1 V / 50 Hz、与 `meas:` 行和 104 浮点遥测对表、回放页放坏段与空态画面)加 P 触发 / bench T 源,全部结果与清单见 `docs/子系统设计/故障录波设计说明.md` §10「板级远程半轮记录」。**板级判据、留痕名清单与执行顺序图的属主 = docs/子系统设计/故障录波设计说明.md §9 与 §10**(原卡片登记簿 2026-10-30 随销项并入后删除);**与 UI 道互斥**(菜单树与中文字库是整文件生成物,同刻只允许一条道写)——本波按此串行执行:UI 道在数据面与接线冻结之后才动生成物 |
### 2.1 关键依赖
- **UI**:写路径、整定 4 页、装置维护 3 页与遥测死区/系数 2 页已收口(含上板判据与真机回读);
菜单树、页面注册表与字库都是生成物,同刻只允许一条道写。
- **送检**:已知电压注入(✅ 2026-10-27 闭环)→〔信号发生器动态波形验证 —— 手边可做,与后两关并行、不阻塞送检〕
→ 端子/子板变比确认 → 标准源两点标定 → 保护解除闭锁 → 送检。
- **送检**:已知电压注入(✅ 2026-10-27 闭环)→ 信号发生器动态波形验证(✅ 2026-11-04 闭环,未闭合
四项标注在 数据可信.md §5 任务卡内)→ 端子/子板变比确认 → 标准源两点标定 → 保护解除闭锁 → 送检。
- **保护扩展**:标准源未到位前不新增无法验证的判据;跨转换器相位、功率、对称分量类
功能必须等 V0.2 硬件。
- **结构债**:UI 完成后先做 net 收口;RS-485 白名单、内存优化、仓库体积等观察项不抢占
+18 -14
View File
@@ -8,20 +8,22 @@
## 1. 目标与范围
菜单树共 **34 叶**(节点行合计 43,缩进即层级):按行内 `#page=` 实测**已绑 30 叶、未绑 4 叶**,
页面映射表 `UI_PAGE_MAP_COUNT` = 30(与已绑叶数一致)。未绑 4 叶 = §5 明确不做的 **4 叶**
(遥信 3 / SNTP 1),与下面分类表逐项对得上;2026-10-29 录波 UI 道把「手动录波」从不做转为
落地并新增「录波回放」叶,绑 +2(手动转绑 + 新叶)/ 叶 +1;2026-10-28 记录 5 页与运行报告页落地后,
菜单树共 **34 叶**(节点行合计 43,缩进即层级):按行内 `#page=` 实测**已绑 31 叶、未绑 3 叶**,
页面映射表 `UI_PAGE_MAP_COUNT` = 31(与已绑叶数一致)。未绑 3 叶 = §5 明确不做的 **遥信 3 叶**,
与下面分类表逐项对得上;2026-10-29 录波 UI 道把「手动录波」从不做转为
落地并新增「录波回放」叶,绑 +2(手动转绑 + 新叶);2026-11-04 SNTP 客户端立项(Issue #57 拍板
选型、#73 拍板节奏),SNTP 设置叶转绑定(+1);2026-10-28 记录 5 页与运行报告页落地后,
原「需补配置或写入方」的 6 叶缺口清零并按销项删行(该行内容见本节末与 §3)。
| 分类 | 数量 | 当前怎么处理 |
|---|---:|---|
| 无开入硬件 | 3 | 不做:遥信类型、遥信防抖、双点遥信 |
| 缺独立模块或产品定义 | 1 | 不做:SNTP 设置(手动录波已于 2026-10-29 随故障录波 UI 道落地转绑定,不再列不做) |
(原「缺独立模块或产品定义:SNTP 设置」行已于 2026-11-04 随 SNTP 客户端立项转绑定销项;手动录波更早,2026-10-29 随故障录波 UI 道转绑。)
现有基础:`ui_w_editor` / `ui_w_setlist` / `ui_page_action` 已就绪;`ui_data` 已扩到**四通道**
(rt / par / info + 遥测死区与系数用的第四张注入表);像素黄金用例与字符集门禁已存在,内容页黄金
现有 **66 张**(菜单 8 状态另计;2026-10-29 录波 UI 道 +6 张:手动录波 3 态 + 录波回放 3 态)。**装置维护 3 页、遥测死区/系数 2 页、通讯设置页、记录 5 页与
现有 **74 张**(菜单 8 状态另计;2026-10-29 录波 UI 道 +6 张:手动录波 3 态 + 录波回放 3 态;2026-11-04 SNTP 设置页 +8 张)。**装置维护 3 页、遥测死区/系数 2 页、通讯设置页、SNTP 设置页、记录 5 页与
运行报告页都是把这套基础接上真实消费者做出来的,不另发明一套机制。**
逐页形态见 `docs/UI设计/页面补齐规划.md`;数据契约见
`docs/UI设计/UI_数据供给设计说明.md`。本文件保留范围、完成判据与不做的取舍口径(执行清单已于 2026-10-28 清空)。
@@ -31,11 +33,11 @@
1. **节点行** = `ui_menu_tree.txt` 里非空、非 `#` 开头的行(当前 **43** 行,含根「主界面」)。
2. **缩进即层级**(每级 2 空格;Tab 会被 `gen_menu.py` 直接拒绝):某节点行之后再没有更深缩进的行 = **叶**
(当前 **34** 叶);有更深缩进后继者 = 菜单节点(当前 **9** 个)。等价于生成物口径 `ui_menu_tab_has_children(node) == false`。
3. **绑定**:叶行行尾带 ` #page=` = 已绑(当前 **30** 叶);不带的 = 未绑(当前 **4** 叶)。
4. **闭合**:`未绑 = 待做 + 不做` —— 当前待做 **0**(§2 清单已清空)+ 不做 **4**(遥信 3 / SNTP 1)= **4**,
3. **绑定**:叶行行尾带 ` #page=` = 已绑(当前 **31** 叶);不带的 = 未绑(当前 **3** 叶)。
4. **闭合**:`未绑 = 待做 + 不做` —— 当前待做 **0**(§2 清单已清空)+ 不做 **3**(遥信 3)= **3**,
与上面的分类表、§5「当前不做」表逐项对得上。
5. **别拿 `grep -c '#page='` 当已绑叶数**:2026-10-28 实测它得 **29**,比真值多 1 —— 文件头第 5 行是规则说明里的
` #page=<模块名>` 示例行(不是节点行),必须先剔注释/示例行;剔完的 30 才等于生成物 `UI_PAGE_MAP_COUNT`。
` #page=<模块名>` 示例行(不是节点行),必须先剔注释/示例行;剔完的数才等于生成物 `UI_PAGE_MAP_COUNT`(现 31)。
复核命令(复用生成器自己的解析器 `tools/menu_gen/gen_menu.py` 的 `parse_tree`,不另写一套计数逻辑,免得与生成物脱钩):
@@ -49,7 +51,7 @@
三个已踩过的错算法(每种都同时给**当时**与**2026-10-28 重跑**的产物——错法不会自己失效,只是错数跟着已绑叶数一起漂):
**把 42 个节点行当叶子**(多算 9 个菜单节点:当时已绑 21 叶 → 得「未绑 21」,2026-10-28 同一错法得「未绑 14」= 42 − 28);
**`grep -c '#page='` 当时得 23**(当时已绑 22 叶,多的那 1 正是文件头注释第 5 行的 `#page=<模块名>` 示例字样,必须先剔注释行;
2026-10-28 同一命令得 29 = 当时已绑 28 + 该示例行 1;2026-10-29 现值 31 = 已绑 30 + 示例行 1);**按叶子名去重得 29**(`参数`/`定值`/`控制字`/`软压板` 在「整定」「查看」下各一份,
2026-10-28 同一命令得 29 = 当时已绑 28 + 该示例行 1;2026-10-29 当时值 31 = 已绑 30 + 示例行 1;2026-11-04 现值 32 = 已绑 31 + 示例行 1);**按叶子名去重得 29**(`参数`/`定值`/`控制字`/`软压板` 在「整定」「查看」下各一份,
4 对同名不能合并;2026-10-28 重跑仍是 29 = 33 叶 − 4 对同名)。
(21 / 23 两个错数分属 2026-10-27「已绑 21 叶」与「已绑 22 叶」两个时点的实测,只用来说明错法,不代表现值;
去重 29 与上面 grep 现值 29 **只是撞号**:一个是「合并同名叶」、一个是「不剔注释行」,都与真值 30(2026-10-29 现值)无关。)
@@ -97,8 +99,11 @@
**单写者约束**:`ui_menu_tree.txt`、页面注册表、字库生成物不得由多个分支同时修改。
`MDK-ARM/*.uvprojx` 的新源文件注册按双端注册规则处理。
**时基口径未定,勿顺手统一**:面板时钟页与 8888 对时把「本地钟面当 epoch」写入,而 104 `C_CS` 按 CP56Time2a 的
UTC 写入,后写者赢 ⇒ 装置墙钟与主机本地时刻恒差 8 小时。用户裁定前**不要改动 RTC/epoch 语义**,也不要把
**时基口径已裁定,勿顺手改语义**:口径 = 「先本地为准,内部真源是本地钟面」(用户裁定 2026-10-27,
`../子系统设计/系统墙钟时间维护说明.md` §1.1);当时冲突双方「面板时钟页 vs 8888 对时 vs 104 `C_CS` 写 UTC」中的
8888 已于 2026-11-04 退役(写入口现为 SNTP / UI 手工 / 104 `C_CS`,一律先换算成本地钟面再落库)。唯一待决的是
104 **上行** `CP56Time2a` 是否转 UTC(送检前单独定,前引
`../子系统设计/系统墙钟时间维护说明.md` §1.1 末)。裁定前**不要改动 RTC/epoch 语义**,也不要把
时钟设置页的「回读 RTC」判据改成按某一时区补偿——那等于替产品做掉一个未定的决定。
**保护闭锁必须可见**:整定页可以修改定值,但当前产品在 UA/UN/IA 未标定时保护判据闭锁。
@@ -112,7 +117,6 @@ UTC 写入,后写者赢 ⇒ 装置墙钟与主机本地时刻恒差 8 小时
| 项目 | 不做原因 | 重开条件 |
|---|---|---|
| 遥信类型 / 防抖 / 双点 3 页 | V0.1 无开入硬件,配置没有消费者 | 开入固件立项后,与开入开出一起做 |
| SNTP 设置 | 当前使用 8888 私有对时协议,SNTP 链路与产品口径均未建立 | SNTP 客户端立项 |
| net 层与其他结构债 | 当前只做 UI,避免两条主线互相拖慢 | UI 当前清单完成后,按 README §2 切换 |
| RTC 时基统一 | 两套写入口径并存(面板与 8888 写本地钟面、104 写 UTC),恒差 8 小时;统一任一侧都要动 104 上行语义或 UI 判据 | 用户裁定时基方向后,作为跨模块变更单独立项 |
| 104 上行 UTC 换算(原「RTC 时基统一」) | 内部口径已由用户裁定(2026-10-27:本地钟面为真源,各写入口先换算再落库,见 §4 注),「恒差 8 小时」的后写者赢阶段已结束;剩下的只是 104 上行是否按标准转 UTC,动了要改 104 上行语义 | 送检/第三方主站验收方向定下来后,作为跨模块变更单独立项(墙钟说明 §1.1 末) |
| 主站地址白名单 | CH395F 单连接 socket 上「拒绝即 close+重 listen」等于给扫描风暴开门,且该值现在没有任何既有消费点 | 有真实消费场景或多连接后端立项时再评 |
+41 -39
View File
@@ -26,7 +26,7 @@
|---|---|---|
| 1 通路活 | 9 路都在转换、没串道、极性没反? | ✅ 2026-10-27 已证 |
| 2 静态引脚电压 | 注 1 V 直流,读数是不是 1 V? | ✅ 2026-10-27 已证(六项判据全 PASS,§2.6) |
| **3 动态引脚波形** | **注 1 V 正弦,算出来还是不是 1 V?** | **⬜ 一次也没做过(§2.7 → 方案见 §4.6)** |
| **3 动态引脚波形** | **注 Vpp 2.828 V 正弦(板上读数 ≈1 V),算出来还是不是 1 V?** | **✅ 2026-11-02 / 11-04 已跑两轮、六轮全部闭合**(削顶 2 Hz 档与复位复核经用户裁定 2026-11-04 关闭;谐波 B 行超差已归因板端前端 RC fc≈796 Hz → §4.6 任务卡;实测结论 `docs/硬件平台/ADC通道映射说明.md` §8.2) |
| 4 端子二次值 | 引脚上这 1 V 对应端子侧几 V? | 🟡 等子板原理图或实物 |
| 5 精度等级验收 | 额定工况下整条链误差够几级? | 🟡 等标准源 |
@@ -44,9 +44,9 @@
| 你想做什么 | 直接看 |
|---|---|
| 判断现在这些数能信到什么程度 | §1 这张图 + §2.6(静态证到哪)+ §2.7(动态没证什么) |
| 判断现在这些数能信到什么程度 | §1 这张图 + §2.6(静态证到哪)+ §2.7(动态证到哪) |
| 改采集 / 算法 / 品质位相关代码 | §2.1 §2.2 §2.3(口径)+ §4.1~§4.5(设计依据) |
| 上台架做正弦验证 | §4.6(接法、六轮判据、读数出口、坑)+ §5 的「信号发生器动态波形验证」卡 |
| 上台架做正弦验证 | ✅ 已闭环(2026-11-04);复跑看 §4.6(接法、六轮判据、数字从哪儿读、坑)+ §5 的「信号发生器动态波形验证」卡 |
| 看还差谁、按什么顺序做 | §5 任务卡 + §6 顺序图 |
| 确认某件事是**故意不做**而不是漏了 | §9;有没有踩坑风险看 §8 |
@@ -137,12 +137,12 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交
> `eng` 升 1 的条件是电气 8 路 VALID **全置位**(`App/app/ui_data_feed.c` 的 `feed_eng()`)。
> **本节编号与承诺边界口径勿删**——`adc_cal.*` / `meas.*` / `ui_data*` / `simulator/main.c` 的头注释都指到这里。
### 2.7 动态验证:正弦波一次也没采过
### 2.7 动态验证:已跑两轮(2026-11-02 / 11-04),六轮实测结论在 `docs/硬件平台/ADC通道映射说明.md` §8.2
**一句话**:整条计算链现在只在**PC 合成数据**上验过,从没在**真实交流波形**上验过。
不是"精度差一点",是"这一档完全空白"。
**一句话**:这一档**不再是空白**——真实交流波形已经台架两轮验证(幅值/波形/相位/测频/谐波/削顶直流档都过了);
本节先留"当时为什么判空白"的证据链,下面按下游的表已按两轮实测更新。
证据(逐条可查,全部是各套件自己的自陈,不是推测):
当时的证据(逐条可查,全部是各套件自己的自陈,不是推测——正是它们说明 PC 套件自己闭不了这一档):
| 出处 | 原话要点 |
|---|---|
@@ -153,19 +153,18 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交
| `docs/测试说明/专项验证/ADC前端注入验证流程.md` | 「**只验直流**:零点 / 线性 / 通道映射 / 极性。不验交流有效值——台架无信号源」 |
| `App/task/adc_task.c` 心跳块 | `adc:` 是当拍瞬时码值、`meas:` 是 200 ms 快照——**没有一行能给出波形形状** |
按下游列一遍"现在能不能信":
按下游列一遍"现在能不能信"(2026-11-04 按台架两轮更新;数只详述在 `docs/硬件平台/ADC通道映射说明.md` §8.2,此处不抄第二份):
| 下游 | 现在 | 为什么 |
|---|---|---|
| 基波有效值(RMS) | 🟡 算法在 PC 上证过(幅值 ≤1 %、49/51 Hz 下 ≤1.5 %),**经真 ADC 后一次没测** | 1 V 正弦进 ADC 后 `fAmSQ` 是否 = 1 V,无实测 |
| 相位 | ❌ 从未在真信号上比过;且代码里**没有跨通道相位符号约定** | 只许比同片内通道(§9 的硬件限制) |
| 频率 | ❌ **恒不启动** | 幅值门 50 V 是二次值口径,引脚域 ≤5 V 恒不过(§2.3 #1)→ UI `Hz` 行恒 `--`、104 `0x401D` 恒 IV |
| 削顶 / 品质 | 🟡 位图逻辑对,真削顶路径没跑过 | `rail:` 那条 `[ERR]` 至今只在直流饱和里起过 |
| 谐波 C04 / C05 | ❌ 实例建了,**无消费者、无出口** | `prot_elm.c` 无引用,`meas` 只算 `harm=1` |
| 基波有效值 | ✅ 过(±2 % 内,三档幅值两点复现) | 台架幅值轮 + 波形轮(基波分量口径)实证;端子二次值仍不归这档(§4.6.5) |
| 相位 | ✅ 同片内过(录波档 ≤2° 判据) | 同段 UA/UN 两路实测(0° 与 90° 两工况);**跨转换器仍禁比**——无同刻性是 V0.1 硬件边界(§9),不是待修缺陷 |
| 频率 | ✅ 「如实报不可测」成立(这就是现状判据) | 含满幅注入段全不可测、UI 恒 `--`;**45~55 Hz 精度仍未验**——要验须降 `FREQ_AMP_MIN` 单跑一轮 → §7 #8 |
| 削顶 / 品质 | ✅ DC −1.00 V 档锁存 + sticky 实证;2.0 Hz 低频档与复位复核经用户裁定 2026-11-04 关闭 | `[ERR] rail: edge=0x2 sticky=0x2` 唯一一条、撤注恒持;执行见 §4.6 任务卡 |
| 谐波 C04 / C05 | 🟡 选择性轨已证(单注 100/250 Hz 分别只进对应桶、纯基波两桶为 0);**仍无消费者、无处上送** | 250 Hz→`Ia5` 行 −4.4 % **已按板端前端真实衰减关闭(2026-11-04)**:IA 前端差分滤波 C35 = 100 nF + R19/R21 各 1 k 构成一阶低通 fc ≈ 796 Hz,板端读数忠实、工频不受影响;谐波产品化需板改(模型与数据 `docs/硬件平台/ADC通道映射说明.md` §8.2,执行手册谐波记录 B 行);接到哪、给谁用归 §9 的谐波轨立项 |
| 保护判据 | 🔒 闭锁中 | §4.3 |
→ **下一跳就是 §4.6**:把一根真正的 50 Hz 正弦从**已经验证过的那个注入点**灌进去,
一次把上表里"手边设备能改"的几行改掉。
→ 两轮已按 **§4.6** 跑完(从**已验证过的那个注入点**灌正弦);六轮全部闭合(谐波 B 行超差已归因板端前端 RC,见 §2.7 谐波行;削顶 2 Hz 低频档与复位复核经用户裁定 2026-11-04 关闭,见 §2.7 削顶行与执行手册削顶卡步 3 行)。
---
@@ -242,7 +241,7 @@ M4 的六条退出判据(逐条对齐现状,别整格打勾):
- 与"自检失败 / 采样故障闭锁"同构(`PROT_HF_LOCK_MASK` 那一套),**不新增机制**。
- 现有台架 `/serial-boot-test` 的期望会因此变化——**这是有意的**,任务卡里同步更新判据。
- 台架调试要旁路只能走编译期开关 `DTU_BENCH`,不进 cfg、不进 104。
⚠ **`DTU_BENCH` 只旁路这一处**:它**不**旁路 §2.3 #1 的测频幅值门,也不产生任何读数出口。
⚠ **`DTU_BENCH` 只旁路这一处**:它**不**旁路 §2.3 #1 的测频幅值门,也不产生任何新的读数通道。
### 4.4 落盘:拆两段,不碰引擎
@@ -300,7 +299,7 @@ typedef struct {
| 注入点已经验证过,且就在那儿 | `docs/测试说明/专项验证/ADC前端注入验证流程.md` §3 的注入点清单(串联电阻端子侧 = `U5` 排针);③ 段 1:1 已拿到台架证据(§2.6) |
| **不知道 ④ 变比也能验算法** | 注入点在 ④ **之前**,发生器给的电压就是引脚电压,算法链要证的正是"引脚域里算得对不对" |
| 幅度小两个数量级 | 只需 ±2 V 以内的正弦(TPAFE 侧判据上限);端子二次值验证要上 100 V / 220 V,风险与设备都在那一级 |
| 现有静态验证的读数出口可直接复用 | 串口心跳 + 104 YC,零固件改动(§4.6.3) |
| 现有静态验证的读数的地方可直接复用 | 串口心跳,零固件改动(§4.6.3) |
**这一跳证的是功能性**(算得对不对、品质报得实不实、节拍稳不稳),**不是计量**(误差几级)——
发生器不当"标准"的界线见 §4.6.5 与 §7 #9。
@@ -313,34 +312,37 @@ typedef struct {
> **轮次名一律用语义短语(用户裁定 2026-10-30:清退"字母+数字"判据号)**:D1 → 幅值轮、D2 → 波形轮、
> D3 → 相位轮、D4 → 测频轮、D5 → 削顶轮、D6 → 谐波轮;本族旧号作废不得再取走
> (与二极管位号、网络自愈决策记录、链路延迟缺陷编号只是同形,各不相干)。
>
> **注入幅度一律按峰峰值 Vpp 设定(用户裁定 2026-11-04)**:发生器面板的有效值显示不当设定基准
> (同一个 Vpp 换波形后有效值显示跟着变,切波形瞬间还残留上一波形的值);万用表 AC 档与板上读数
> 天然是有效值刻度,正弦下 有效值 = Vpp ÷ 2.828,换算着比。
| 轮次 | 验什么 | 接法 | 读哪 | 过判据 |
|---|---|---|---|---|
| **幅值轮** | 基波幅值计量 | UA(R31 / `AIN1`)注 1.000 Vrms、50.000 Hz 正弦 | `meas:` 的 `ua_mv`;或 104 `0x4011` | 读数 = 万用表 AC 档读数,偏差 ≤2 %(与静态同档)。⚠ 别用 `phasor(hb)` 行比幅值——那是 ×0.1 定点,注 1 V 只剩 10 格(§4.6.3) |
| **幅值轮** | 基波幅值计量 | UA(R31 / `AIN1`)注 Vpp 2.828 V(= 峰值 ±1.414 V)、50.000 Hz 正弦 | `meas:` 的 `ua_mv` | 读数 = 万用表 AC 档读数,偏差 ≤2 %(与静态同档)。⚠ 别用 `phasor(hb)` 行比幅值——那是 ×0.1 定点,注 1 V 只剩 10 格(§4.6.3) |
| **波形轮** | 读数是否只跟基波走 | 同幅值改方波、三角 | 同上 | 期望值 = **基波分量有效值**(同峰值下的基波 / 峰值比:方波 ≈ **0.90**,闭式 4/π ÷ √2 = 0.900、20 点整周期 DFT 实算 0.904;三角 ≈ **0.573**,闭式 8/π² ÷ √2 = 0.573、实算 0.578),**不是**真有效值(方波 = 1.000、三角 = 0.583 峰值);偏差 ≤2 %。算术与逐波形对照表见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的波形卡。顺带把"这台仪表读的是基波"这条口径钉进文档——用户拿真有效值表去对会以为差了 10 % |
| **相位轮** | 相位 | UA 与 UN 两路**同片**(都走 TPAFE)注同幅、同相 | **定量档**:录波段文件 `test/host/wave_read.py fft`(浮点重算基波相位;段内表序 `UA` = 0、`UN` = 3);**定性档**:`phasor(hb)` 各行 `Re=` / `Im=` | **定量档**:同段内两路相位差 ≤2°,把发生器相位改 90° 后差值跟到 ≈90°。**定性档只判"跟随"**(差值落进 80°~100°)——`phasor` 出口是 ×0.1 定点,1.000 Vrms 时每格 0.1 V,角度量化最坏 ≈ atan(0.1 / 0.707) ≈ 8°,判不动 2°(§4.6.3)。⚠ 段与发生器**无绝对相位锁定**(触发瞬间钉在段内点 159),只能比**同段内两路**的相对差,跨段比相位或拿发生器相位当绝对基准都不成立。**只许比同片内**——跨转换器无同刻性(§9),STM32 那五路还有 1 拍乒乓滞后 |
| **测频轮** | 测频 | 同幅值轮的 1 V/50 Hz | `freq(hb):` 与 104 `0x401D` | **预期就是"不出数"**(幅值门 50 V 在引脚域恒不过,§2.3 #1)→ 这条的判据是"如实报不可测"(`freq_valid=0`、UI `Hz` 显示 `--`、104 上送 IV),**不是**报出一个错误的 50.00。想真验 45~55 Hz 精度(PC 侧判据 0.02 Hz)须临时把 `FREQ_AMP_MIN` 降到引脚域量级(如 0.5 V)单跑一轮 → §7 #8 + §8 风险行 |
| **削顶轮** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注 **DC −1.00 V 并保持 ≥5 s**;或 1.000 Vrms 而频率 **2.0 Hz** 的正弦——两档都能造出"连续贴同一侧轨带"。⚠ **注 50 Hz 同幅正弦造不出锁存,且这是预期而非故障**:1 kHz 下一个周波只 20 点、正半周必落中带,而 `adc_health_rail_watch()` 任何一拍回中带就把计数清零(`App/algo/adc_health.c`),连续贴底最长 ≈ 209/360 × 20 ≈ 11 拍 < 20。TPAFE 侧在 ±2 V 判据上限内造不出这条(轨带 ±32112 码 ≈ ±4.9 V),只有 STM32 那五路能做 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;104 `0x4012`;段文件档另加 `wave_read.py shape` 的三把尺子(`--strict` / 再加 `--clip-superset` / 不带),逐条配法见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的削顶卡 | 负半周钉下轨 → **连续 20 拍同侧后该路贴轨位锁存**(`ADC_RAIL_N_RUN = 20`,`App/algo/adc_health.h:174`)、`edge` 起沿、104 上该路 IV 且值清零。锁存另有**释放沿前提**:该通道此前须出现过中带样本 → **先上电见基线摆动、再开发生器输出**,开机就注会测出假不通过。撤掉注入后 `rail` 仍非 0 是 sticky 语义(只有 `adc_health_rail_init()` / 复位清)——**复位后仍不清才是 bug**。⚠ 这条会**故意造出 `[ERR]`**,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 人工判读、排在最后单独一轮(§8 第一行) |
| **谐波轮** | 谐波轨 | 基波 + 20 % 二次 + 20 % 五次(IA 上注)。⚠ 峰值预算:1.000 Vrms 基波 + 两个 20 % 分量的最坏对齐 ≈ 1.98 V 已顶 ±2 V 红线 → 基波用 **0.800 Vrms**、二次与五次各 0.160 Vrms | **定量档**:段文件 `test/host/wave_read.py fft --ch 4 --all`(`IA` = 段内表序 4,直接给基波与 2 / 5 次谐波 RMS,单位 mV);**旁证**:UI 交流量页 `Ia2` / `Ia5`(1 mV)与 `phasor(hb) C04-IA-F2` / `C05-IA-F5`(×0.1 定点,只证这根轨不是恒 0) | 谐波轨读数 = 注入分量(**±2 %,走定量档**;周期行档判不动:0.160 Vrms 在 ×0.1 出口只有 1~2 格)。⚠ 这两根轨**目前没有任何下游消费者**(`prot_elm.c` 无引用、`meas` 只算 `harm=1`),所以谐波轮只证"算得对",**不证"有人用"**——出口归 §9 的谐波轨立项 |
| **相位轮** | 相位 | UA 与 UN 两路**同片**(都走 TPAFE)注同幅、同相 | **录波档**:录波段文件 `test/host/wave_read.py fft`(浮点重算基波相位;段内表序 `UA` = 0、`UN` = 3);**心跳档**:`phasor(hb)` 各行 `Re=` / `Im=` | **录波档**:同段内两路相位差 ≤2°,把发生器相位改 90° 后差值跟到 ≈90°。**心跳档只判"跟随"**(差值落进 80°~100°)——`phasor` 串口行是 ×0.1 定点,Vpp 2.828 V(板上读数 ≈1000 mV)时每格 0.1 V,角度量化最坏 ≈ atan(0.1 / 0.707) ≈ 8°,判不动 2°(§4.6.3)。⚠ 段与发生器**无绝对相位锁定**(触发瞬间钉在段内点 159),只能比**同段内两路**的相对差,跨段比相位或拿发生器相位当绝对基准都不成立。**只许比同片内**——跨转换器无同刻性(§9),STM32 那五路还有 1 拍乒乓滞后 |
| **测频轮** | 测频 | 同幅值轮的 Vpp 2.828 V / 50 Hz | `freq(hb):` | **预期就是"不出数"**(幅值门 50 V 在引脚域恒不过,§2.3 #1)→ 这条的判据是"如实报不可测"(`freq_valid=0`、UI `Hz` 显示 `--`),**不是**报出一个错误的 50.00。想真验 45~55 Hz 精度(PC 侧判据 0.02 Hz)须临时把 `FREQ_AMP_MIN` 降到引脚域量级(如 0.5 V)单跑一轮 → §7 #8 + §8 风险行 |
| **削顶轮** | 削顶与品质路由(**负向用例**) | 给 UB(R52 / `AIN2`)注 **DC −1.00 V 并保持 ≥5 s**;或 Vpp 2.828 V 而频率 **2.0 Hz** 的正弦——两档都能造出"连续贴同一侧轨带"。⚠ **注 50 Hz 同幅正弦造不出锁存,且这是预期而非故障**:1 kHz 下一个周波只 20 点、正半周必落中带,而 `adc_health_rail_watch()` 任何一拍回中带就把计数清零(`App/algo/adc_health.c`),连续贴底最长 ≈ 209/360 × 20 ≈ 11 拍 < 20。TPAFE 侧在 ±2 V 判据上限内造不出这条(轨带 ±32112 码 ≈ ±4.9 V),只有 STM32 那五路能做 | `cal:` 行的 `rail=` / `edge=`;`rail: edge=…` 的 `[ERR]` 行;录波档另加 `wave_read.py shape` 的三把尺子(`--strict` / 再加 `--clip-superset` / 不带),逐条配法见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 的削顶卡 | 负半周钉下轨 → **连续 20 拍同侧后该路贴轨位锁存**(`ADC_RAIL_N_RUN = 20`,`App/algo/adc_health.h:174`)、`edge` 起沿。锁存另有**释放沿前提**:该通道此前须出现过中带样本 → **先上电见基线摆动、再开发生器输出**,开机就注会测出假不通过。撤掉注入后 `rail` 仍非 0 是 sticky 语义(只有 `adc_health_rail_init()` / 复位清)——**复位后仍不清才是 bug**。⚠ 这条会**故意造出 `[ERR]`**,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 人工判读、排在最后单独一轮(§8 第一行) |
| **谐波轮** | 谐波轨 | 基波 + 20 % 二次 + 20 % 五次(IA 上注)。⚠ 峰值预算:Vpp 2.828 V 基波 + 两个 20 % 分量的最坏对齐峰值 ≈ 1.98 V 已顶 ±2 V 红线 → 基波用 **Vpp 2.263 V**、二次与五次各 Vpp 0.453 V | **录波档**:段文件 `test/host/wave_read.py fft --ch 4 --all`(`IA` = 段内表序 4,直接给基波与 2 / 5 次谐波有效值,单位 mV);**旁证**:UI 交流量页 `Ia2` / `Ia5`(1 mV)与 `phasor(hb) C04-IA-F2` / `C05-IA-F5`(×0.1 定点,只证这根轨不是恒 0) | 谐波轨读数 = 注入分量(**±2 %,走录波档**;心跳档判不动:读数 0.160 V 在 ×0.1 串口行上只有 1~2 格)。⚠ 这两根轨**目前没有任何下游消费者**(`prot_elm.c` 无引用、`meas` 只算 `harm=1`),所以谐波轮只证"算得对",**不证"有人用"**——接到哪、给谁用,归 §9 的谐波轨立项 |
**先做幅值 / 波形 / 削顶三轮**(纯读数、无跨通道约定),相位 / 谐波次之,测频轮视 §7 #8 的裁定。每条的两档跑法(周期行标量档 /
录波段文件定量档)、触发动作、耗时排布与失败分诊在 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.6。
**先做幅值 / 波形 / 削顶三轮**(纯读数、无跨通道约定),相位 / 谐波次之,测频轮视 §7 #8 的裁定。每条的两档跑法(心跳档 /
录波档)与触发动作在 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.4–§8.5 的注入卡里(耗时排布与失败分诊两节已随 2026-11-04 轮次收尾删除,将来复跑照卡自行排程)。
#### 4.6.3 读数出口(零固件改动)
#### 4.6.3 数字能从哪儿读(零固件改动)
| 出口 | 有什么 | 坑 |
| 读数的地方 | 有什么 | 坑 |
|---|---|---|
| 串口每 10 s 心跳(`App/task/adc_task.c` 心跳块) | `adc:`(9 路码值)、`cal:`(三路 mV + `vl`/`cl`/`rail`/`edge` 位图)、`phasor(hb)`(逐实例 `Re` / `Im` / `rms`)、`freq(hb)`、`meas:`(快照轨) | MicroLIB 无 `%f`,全是定点:`phasor` ×0.1(**1 V 注入只剩 10 格**)、`freq` ×0.01 Hz、`cal:` / `meas:` ×1000(1 mV)。10 s 一行的单点瞬时值对 50 Hz 等价于随机相位抽样 |
| 104 YC(现成 PC 主站 `test/host/iec104_master.py`) | `0x4011`~`0x4018` 八个电气量 + `0x401D` 频率,**浮点无量化**,还带品质 | UB/UC/IB/IC/IN 恒 IV、值清零 → 那五路只能回读串口码值域 |
| UI 交流量页 | `Ua` / `U0` / `Ia` / `Ia2` / `Ia5` / `Hz` / 标定徽标 共 7 行 | 160×160 单色;`--` **同时**表示"没数"和"有数但不可信" → 不能当判据,只能当旁证 |
| **段文件样本出口(代码侧已落地,2026-10-29)** | 每次触发一段:`/wave/WAVE00.BIN` 起共 100 个号轮转(FTP 根下可见),8 路 × 320 点原始码值加封存瞬间的标定与品质快照;PC 侧 `test/host/wave_read.py` 提供 dump / fft / shape / plot 四个子命令(bench 工具,纯标准库,自带 `--selftest`) | 「比对派生标量」已可升级成「PC 独立重算 vs 板上读数」对表(判据与执行步骤见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §4,格式属主见 `docs/子系统设计/故障录波设计说明.md` §6.2 与 §6.3)。**但板级那一轮还没跑**(注 1 V / 50 Hz、与 `meas:` 与 104 YC 对表、CRC 不符必须拒收的实测,见 `docs/子系统设计/故障录波设计说明.md` §10);示波器仍旧只作旁证、不当判据。段内点序:触发瞬间落在段内点 159(预触发 160 点的最后一点),段的绝对相位与发生器无锁定关系,可核对的是相对相位与极性 |
| **仍然没有的(2026-10-29 更新)** | 没有命令控制台、也没有「把窗里 20 / 40 个点导出来」的定点 dump 子命令——**但样本出口本身已成立**(上一行的段文件:一次 320 点 / 8 路,比原来规划的 20 / 40 点窗更宽) | 现场取回一律走现成 FTP(21 端口),形状就地看回放页单通道、或在 PC 侧用同一脚本出图;示波器只对照发生器、不当判据。**判据已升级为「PC 独立重算 vs 板上读数」对表**,对表的执行轮次待台架(见 `docs/子系统设计/故障录波设计说明.md` §11) |
| **段文件(原始样本,代码侧已落地,2026-10-29)** | 每次触发一段:`/wave/WAVE00.BIN` 起共 100 个号轮转(FTP 根下可见),8 路 × 320 点原始码值加封存瞬间的标定与品质快照;PC 侧 `test/host/wave_read.py` 提供 dump / fft / shape / plot 四个子命令(bench 工具,纯标准库,自带 `--selftest`) | 「比对派生标量」已可升级成「PC 独立重算 vs 板上读数」对表(判据与执行步骤见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §4,格式属主见 `docs/子系统设计/故障录波设计说明.md` §6.2 与 §6.3)。**板级注入对表已随交流六轮跑过两轮**(2026-11-02 / 11-04,注 Vpp 2.828 V / 50 Hz、fft 与 `meas:` 互合,结论 `docs/硬件平台/ADC通道映射说明.md` §8.2;CRC 不符必须拒收的实测更早、2026-10-31 远程半轮已绿),余下板级判据(周波数 / 极性等逐项)见 `docs/子系统设计/故障录波设计说明.md` §10;示波器仍旧只作旁证、不当判据。段内点序:触发瞬间落在段内点 159(预触发 160 点的最后一点),段的绝对相位与发生器无锁定关系,可核对的是相对相位与极性 |
| **仍然没有的(2026-10-29 更新)** | 没有命令控制台、也没有「把窗里 20 / 40 个点导出来」的定点 dump 子命令——**但原始样本这条通道本身已成立**(上一行的段文件:一次 320 点 / 8 路,比原来规划的 20 / 40 点窗更宽) | 现场取回一律走现成 FTP(21 端口),形状就地看回放页单通道、或在 PC 侧用同一脚本出图;示波器只对照发生器、不当判据。**判据已升级为「PC 独立重算 vs 板上读数」对表**,对表的执行轮次待台架(见 `docs/子系统设计/故障录波设计说明.md` §11) |
#### 4.6.4 接法与红线(照抄既有红线,不新发明)
- 注入点:串联电阻的**端子侧**焊盘(`U5` 排针),逐路清单见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §3;
引脚侧没有串阻保护,必须外串 **10 k** 限流。
- 幅度按**峰值**算,别看 Vrms 数字:1.000 Vrms 正弦峰值已 1.414 V;TPAFE 侧按判据只到 **±2 V**(满量程 ±5 V),
- 幅度按**峰峰值 / 峰值**核,别看有效值数字:Vpp 2.828 V 正弦峰值已 ±1.414 V;TPAFE 侧按判据只到 **±2 V 峰值(= Vpp 4 V)**(满量程 ±5 V),
STM32 那五路是 **0~3.3 V 单极性**——给它们注交流只能是削顶轮那种"故意削顶"的用例,峰值同样不得超 3.3 V。
- 发生器与板**必须共地**(否则量到的是两地之间的差),且不形成地环:优先单点共地、必要时发生器浮地。
- **不许**因为"反正发生器便宜"就往端子侧上额定电压——那是 §5 第二张卡(要变比)与 §9(要标准源)的范围。
@@ -352,7 +354,7 @@ typedef struct {
| 端子二次值 | 注入点在 ④ 之前,算出来还是引脚域 | §5「端子/子板变比确认」 |
| 精度等级(0.5 级 / 1 级) | 发生器自己的幅值误差本身就是要被校的量,不能冒充标准源 | §5「两点标定与精度验收」+ §7 #1 |
| 跨转换器相位 / 功率 / 对称分量 | 两片转换器无共同采样触发(V0.1 硬件缺陷)+ STM32 侧 1 拍乒乓滞后;量纲链路口径见通道映射说明的链路分段一节 | §9(本波不做矢量类运算) |
| THD / 谐波方和根 | 固件里只有两根谐波轨,没有合成出口 | §9(挂谐波轨立项) |
| THD / 谐波方和根 | 固件里只有两根谐波轨,没有合成后的数送出去 | §9(挂谐波轨立项) |
| 温升、EMC、长时间漂移 | 单轮台架不覆盖 | 送检与型式试验 |
---
@@ -366,14 +368,14 @@ typedef struct {
| 标识(任务名)| DoD(可验证验收判据) | 硬依赖 | 资源 | 写范围 | 状态 |
|---|---|---|---|---|---|
| **信号发生器动态波形验证** | 按 §4.6.2 跑完六轮(测频轮允许以"如实报不可测"通过),逐条记实测 / 判据 / 结论;执行步骤与判据表**新增到** `docs/测试说明/专项验证/ADC前端注入验证流程.md`(直流节之后开一节"交流"),实测结论在 `docs/硬件平台/ADC通道映射说明.md` §8.1 之后新开一节;证据(串口日志 + 104 落库)归档 `docs/测试说明/测试数据/boot_log_data/` | 静态注入闭环(✅ 2026-10-27)+ 一台能出 50 Hz 正弦、幅度可调到 1 Vrms 的交流信号发生器(**不需要标准源、不需要端子加压**) | 🔴 台架 + 手边发生器 | `docs/测试说明/`、`docs/硬件平台/` | ⬜ |
| **信号发生器动态波形验证** | 按 §4.6.2 跑完六轮(测频轮允许以"如实报不可测"通过),逐条记实测 / 判据 / 结论;执行步骤与判据表**新增到** `docs/测试说明/专项验证/ADC前端注入验证流程.md`(直流节之后开一节"交流"),实测结论在 `docs/硬件平台/ADC通道映射说明.md` §8.1 之后新开一节;证据(串口日志 + 录波段文件)归档 `docs/测试说明/测试数据/boot_log_data/` | 静态注入闭环(✅ 2026-10-27)+ 一台能出 50 Hz 正弦、幅度可调到 Vpp 2.828 V 的交流信号发生器(**不需要标准源、不需要端子加压**) | 🔴 台架 + 手边发生器 | `docs/测试说明/`、`docs/硬件平台/` | ✅ **已验证(2026-11-02 / 11-04 两轮、六轮全部闭合;用户裁定 2026-11-04 打勾保留本行)**:实测已回填执行手册各轮记录表、结论新开 `docs/硬件平台/ADC通道映射说明.md` §8.2、证据已归档 `boot_log_data`。**未闭合项(均已裁定不做或另登记)**:① 谐波叠加工况(基波+二次+五次同段)未跑——发生器无谐波叠加/ARB 功能,属设备缺口;算法侧由 `test/host/phasor/phasor_test.c` TC-PHASOR-006 覆盖,待有谐波功能标准源复跑;② 削顶 2 Hz 低频档与 sticky 复位复核——用户裁定 2026-11-04 关闭;③ 测频"降幅值门证 0.02 Hz 测准"的改码档未做(现状档以如实报不可测通过);④ 谐波 B 行归因一阶低通模型(fc≈796 Hz)未做绝对仪器证伪(预测 250 Hz 额外滞后 ≈17°,示波器到手复核);谐波功能产品化需板改(C35 换小 + 清点各路容值),已登记 `docs/任务规划/登记簿.md` §3 |
| **端子/子板变比确认** | 给出"端子电压 → ADC 引脚电压"的比值(万用表同时量端子与引脚,或查子板原理图后实测核对),写入 `docs/硬件平台/ADC通道映射说明.md`;据此判定 `eng` 能否置 1;数据缺失则本波只交付引脚电压并登记缺口 | 已知电压注入验证(✅);**与动态验证无依赖关系**,可并行 | 🟡 子板实物或原理图 | `docs/硬件平台/` | ⬜ |
| **两点标定与精度验收** | 电压 4 路(UA/UB/UC/UN)+ 电流 4 路(IA/IB/IC/IN)各做两点标定(零点 + 满度,温度不纳入),写入 `[adc_cal]` 并置 `VALID`;误差 ≤ 目标精度(待定:0.5 级 / 1 级 → §7 #1);证据归档 | 端子/子板变比确认;削顶五路的处置结论(削顶轮) | 🟡 标准源 / 继保测试仪 | `docs/算法/`、`docs/测试说明/测试数据/boot_log_data/` | ⬜ |
**为什么把动态验证排在第一**(不是因为它最重要,而是因为它把后面两张卡踩实):
1. 它**不依赖任何外部资源**,而另两张卡都在等东西;等待期正好做它。
2. 它复用刚闭环的注入点与读数出口,**边际成本几乎为零**。
2. 它复用刚闭环的注入点与读数的地方,**边际成本几乎为零**。
3. 它会先把削顶轮那类"预期内的 `[ERR]`"暴露在台架规程里并写清判读,避免第二、三张卡跑到一半
才发现"自动化测试把这轮读成 FAIL"。
4. 它顺手把"发生器能证 / 不能证"的界线钉住,两点标定开跑时判据不会串档。
@@ -434,9 +436,9 @@ flowchart TD
| 风险 | 触发条件 | 缓解 |
|---|---|---|
| **削顶负向用例被当成固件 bug** | 做削顶轮时 STM32 那五路必然钉下轨 → `rail: edge=…` 是 `[ERR]` 行,而 `/serial-boot-test` 的 forbidden 标记含 `[ERR]` → 整轮被判 FAIL | 判读规则写进流程文档:"做削顶轮的这一轮 `[ERR] rail:` 属预期,只人工判读、不计入 `/serial-boot-test` 结论",或把这五路排在最后单独一轮并事先声明。同时别忘 `cl=0xE6` 是**来源派生**(§2.6),不是本轮新削出来的 |
| **拿发生器的精度当标准用** | 用发生器自报 Vrms 当基准值去比 | 基准 = 万用表 AC 档实测(§7 #9);发生器只当"波形干净、频率可锁"的信号源,它自己的幅值误差正是要被校的量 |
| **拿发生器的精度当标准用** | 用发生器自报的有效值数字当基准值去比 | 基准 = 万用表 AC 档实测(§7 #9);发生器只当"波形干净、频率可锁"的信号源,它自己的幅值误差正是要被校的量 |
| **临时改了 `FREQ_AMP_MIN` 忘了还原** | 走 §7 #8 的 B 方案 | 改动落在产品头文件里、会编进固件。单列一条收尾判据:还原后 `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/algo/freq.h` 必须为空;并在 §7 #8 那行留一句"本轮台架已还原" |
| **峰值当成有效值,顶到量程外** | 只看 Vrms 数字:1.000 Vrms 峰值已 1.414 V | 接线前按**峰值**核一遍:TPAFE 侧 ≤±2 V、STM32 侧峰值必须 <3.3 V(且负半周本就钉轨,只作削顶轮用) |
| **峰值当成有效值,顶到量程外** | 只看面板有效值数字:正弦有效值 1.000 V = Vpp 2.828 V,峰值已 ±1.414 V | 接线前按**峰值**核一遍:TPAFE 侧 ≤±2 V、STM32 侧峰值必须 <3.3 V(且负半周本就钉轨,只作削顶轮用) |
| **发生器与板不共地 / 地环** | 两台设备各自接电网地 | 单点共地,必要时发生器浮地;读数漂或出现与幅度无关的偏置先查这里 |
| **非整 50 Hz 注入被当成 `meas` 抖动 bug** | 注 49.5 / 50.5 Hz | `meas` 是**瞬时基波有效值透传、200 ms 是上送节拍不是平均窗**(§4.5)→ 快照轨会有拍频式周期间跳动,属预期。测频腿要扫频时读 `freq(hb)`,别读 `meas:` |
| **`cfg` 键数顶格** | 后续给 `[adc_cal]` 加键 | 已按 8+1 / 8 拆段;加键前先看 `CFG_FILE_MAX_KEYS`,必要时再拆 |
@@ -452,9 +454,9 @@ flowchart TD
| 不做 | 理由 / 归属 |
|---|---|
| THD / 谐波方和根真有效值、快照 4 点平均 | 挂 `docs/算法/继电保护功能复现规划.md` 的「遥测谐波轨(可选)」一起做;快照保持"基波有效值 + 直取相量"口径(§4.5) |
| **谐波轨的消费出口**(C04 / C05 目前无人消费) | 谐波轮只证"算得对";接线到判据或上送属谐波轨立项范围,本波不动 |
| **谐波轨被谁消费**(C04 / C05 目前无人消费) | 谐波轮只证"算得对";接线到判据或上送属谐波轨立项范围,本波不动 |
| 端子级额定电压、绝缘耐压、温升 | 要标准源 / 型式试验条件;本波所有注入都在 `U5` 排针侧 ±2 V 以内 |
| 录波(原始码值段文件) | **已交付代码面**(2026-10-29 立项并当日落地;判据属主 `docs/子系统设计/故障录波设计说明.md` §10,原卡片登记簿 2026-10-30 销项并入)本卡「动态波形验证」的样本出口**已**由录波段文件承担(§4.6.3 出口表已改),排序也随之调整(`docs/子系统设计/故障录波设计说明.md` §11);**没跑的是板级那一轮**(§10) |
| 录波(原始码值段文件) | **已交付代码面**(2026-10-29 立项并当日落地;判据属主 `docs/子系统设计/故障录波设计说明.md` §10,原卡片登记簿 2026-10-30 销项并入)本卡「动态波形验证」的原始样本**已**由录波段文件承担(§4.6.3 读数地点表已改),排序也随之调整(`docs/子系统设计/故障录波设计说明.md` §11);**没跑的是板级那一轮**(§10) |
| 104 文件传输(规约侧读文件) | 仍独立待排;现场与台架取回一律走现成 FTP(21 端口),规约侧文件服务等送检或第三方交接需求再议 |
| 等周波调频 | 与标定正交、无标准源时判据不可达,见 `docs/算法/继电保护功能复现规划.md` §3.1 |
| 功率 / 相位跨相比较 / 功率因数 / 对称分量 | **V0.1 硬件禁止**:跨转换器无同一时刻性,`docs/硬件平台/ADC通道映射说明.md` §7 通道归属行 |
@@ -472,9 +474,9 @@ flowchart TD
|---|---|
| **`docs/测试说明/专项验证/ADC前端注入验证流程.md`** | **执行步骤的属主**。本文只写"证到哪、判据是什么、为什么这么排";怎么接表笔、读哪一行、填哪张表一律在那篇里。动态验证的**步骤**同样落那篇(新开"交流"节),本文 §4.6 只留判据 |
| **通道映射说明** | `docs/硬件平台/ADC通道映射说明.md` §7.1(①②③④ 链路分段)与 §8.1(静态六项实测)是**量纲与实测记录的属主**;本文 §2.1 / §2.6 是它的**消费者**,不另立口径,动态实测结论也回写该文件 |
| **`docs/子系统设计/故障录波设计说明.md`** | 录波段文件是本卡「动态波形验证」的**样本出口**,数据面代码侧已落地(2026-10-29:环与段格式在 `App/algo/wave_ring.c` / `App/algo/wave_store.c`,PC 侧 `test/host/wave_read.py`);判据从「比对派生标量」升级为「PC 独立重算 vs 板上读数」对表的**执行轮次待台架**,动态验证的排序已因此让位给录波数据面(用户裁定 2026-10-29,理由与三处口径变更见 docs/子系统设计/故障录波设计说明.md §11) |
| **`docs/子系统设计/故障录波设计说明.md`** | 录波段文件是本卡「动态波形验证」的**原始样本来源**,数据面代码侧已落地(2026-10-29:环与段格式在 `App/algo/wave_ring.c` / `App/algo/wave_store.c`,PC 侧 `test/host/wave_read.py`);判据从「比对派生标量」升级为「PC 独立重算 vs 板上读数」对表的**执行轮次待台架**,动态验证的排序已因此让位给录波数据面(用户裁定 2026-10-29,理由与三处口径变更见 docs/子系统设计/故障录波设计说明.md §11) |
| **`docs/算法/采集与算法层设计说明.md`** | 算法与判据正文(全周傅氏、测频、保护元件);本文只规定"这些算法的信度到什么程度" |
| **`docs/子系统设计/IEC104上行设计说明.md`** | 动态验证的**读数出口**(`0x4011`~`0x4018` + `0x401D`);送检验收索引在 `docs/任务规划/登记簿.md` §7 |
| **`docs/子系统设计/IEC104上行设计说明.md`** | 104 上行协议本体(点表 `0x4011`~`0x4018` + `0x401D`、品质位绑定)——送检与验收的参照;**不是本验证轮的读数通道**;送检验收索引在 `docs/任务规划/登记簿.md` §7 |
| **送检验收** | 标准源送检依赖本文「两点标定与精度验收」;顺序不可倒(先变比 → 再标定 → 才解卡 → 才送检,§6) |
| **UI 补齐** | 已交付的 `meas_snap_t` + `adc_cal_flags()` 供 `ui_data_rt_t` 填 `eng`:现为 0 = 引脚电压,变比确认拿到变比后才升 1 = 二次值。**UI 不必等本文任何一张卡** |
| **双转换器无共同采样触发(V0.2 硬件改版)** | 本波的 `CLIPPED` 位就是这条硬件缺陷在软件里的可见形态;V0.2 改版后该位恒 0,页面与 104 绑定零改动 |
+4 -2
View File
@@ -21,13 +21,14 @@
| # | 事项 | 发现 | 证据与现状 | 出处 |
|---|---|---|---|---|
| dhara 重试自旋残留 | **dhara 重试自旋残留**——`try_recover()` 的 `DHARA_MAX_RETRIES`(8) 自旋在坏块密集时仍可能长时间占用 defaultTask(喂狗点) | 2026-10-02 | 低优先:现有 `vol_lock` + 喂狗节奏缓解;若台架再出"存储侧喂狗饥饿"再排期 | `Lib/dhara/map.c` |
| 可选全序列卷锁 | **可选全序列卷锁**——现卷锁仅覆盖 `syslog` / `cfg` / `lftpd` 三条路径(`vol_lock_wait(0)` try 语义),其它潜在访问点(如 UI 读日志)未纳入 | 2026-10-02 | 待评估:先确认是否真有第二写者;无则关闭本项 | `App/fs/vol_lock.{c,h}` |
| 可选全序列卷锁 | **可选全序列卷锁**——现卷锁仅覆盖 `syslog` / `cfg` / `lftpd` 三条路径(`vol_lock_wait(0)` try 语义),其它潜在访问点(如 UI 读日志)未纳入。⚠ 原本挂在本题下的「dhara 失败类快照(C4)」待还项**已于 2026-11-04 由问题 #79 还清**(`nand_ftl_fault_get/clear` + 探针按类分流,见 `../架构设计/STM32F4-Base存储架构说明.md` §10.4 已闭环段)——本题现只剩**卷锁覆盖面**本身,不再被 C4 阻塞 | 2026-10-02;C4 拆分 2026-11-04 | 待评估:先确认是否真有第二写者;无则关闭本项 | `App/fs/vol_lock.{c,h}` |
## 3. 硬件(V0.1 缺陷 → V0.2 改版项)
| # | 事项 | 发现 | 证据与现状 | 出处 |
|---|---|---|---|---|
| 双转换器无共同采样触发 | **8 路电气量分属两片转换器(V0.1 硬件缺陷,需下一版硬件修改)**——UA/UN/IA 走 TPAFE5160(16 位、8 通道同步、±5 V 双极性),UB/UC/IB/IC/IN 走 STM32 ADC(12 位、顺序扫描、0~3.3 V 单极性):① 两片转换器无共同采样触发 + STM32 侧顺序扫描(9 通道一轮 ≈ 67 µs)→ 跨转换器通道**无同一时刻性、相位关系不确定**,主板侧功率/功率因数/相角/对称分量类运算不成立;② 同族电气量精度与量程不一致(LSB ≈ 152.6 µV vs ≈ 806 µV,且 STM32 侧前端无中点偏置、负半周削顶);③ 语义与硬件归属交叉,固件映射/UI 通道序/自检注入须逐通道查表绕开 | 2026-10-10 定性更正(**撤销** 2026-10-09 建档时"有意分配、不要修正"的说法) | **V0.2 改版要求**:8 路电气量 UA/UB/UC/UN/IA/IB/IC/IN **全部接 TPAFE5160**,与 TPAFE 上 5 路备用路(AIN5/13/7/15/12)对调串联电阻在转换器一侧的目的地——元件数量与料号不变,属走线级改动。**V0.1 现板软件口径**:主板侧只做逐通道独立监视,禁止跨转换器做相位/功率/功率因数/矢量类运算(此类量取子板计量模块结果)。固件侧消费者已落地(ADC 采集链路,2026-10-10,按语义映射表取数)→ V0.2 改版只改映射表内容,上层语义与代码不动 | `../硬件平台/ADC通道映射说明.md` §1/§7;`../硬件平台/V0.1_网表详细分析报告.md` §1.4 V10、§6-3 |
| IA 前端差分滤波限制谐波带宽 | **IA 通道差分滤波电容 C35 = 100 nF 构成一阶低通 fc ≈ 796 Hz,谐波产品路线的硬件前置未清**——250 Hz 注入读数 −4.4 % 即前端真实衰减(板端读数忠实;R19/R21 各 1 k `docs/硬件平台/V0.1.tel`:49 + C35 跨接差分对,拓扑 FR-ADC-5 `docs/产品需求/产品需求设计文档.md`);工频测量不受影响,外推 5 次 −4.6 %、9 次 −13 %、15 次 −27 %、21 次(1050 Hz)−39.6 % | 2026-11-04(模型与实测数据 `docs/测试说明/测试数据/boot_log_data/20261104_ac_ia_rc_model.txt`) | **改版/改板方向**:C35 换 10 nF(fc≈8 kHz、21 次仅 −0.9 %;改板优先于软件逐阶修正——容差与温漂使修正表不可信,理由详 `docs/硬件平台/ADC通道映射说明.md` §8.2)+ **清点其余各路差分滤波电容容值**(网表无容值标注,C35 = 100 nF 系用户口头确认,其余各路未定);将来有示波器可按预测复核(250 Hz 差分分量额外滞后 ≈17°)。详述 `docs/硬件平台/ADC通道映射说明.md` §8.2 | `docs/测试说明/专项验证/ADC前端注入验证流程.md` 谐波记录 B 行;`docs/硬件平台/ADC通道映射说明.md` §8.2 |
## 4. 测试与验证基础设施
@@ -42,6 +43,7 @@
| rs485_task 白名单缺口 | **KNOWN_GAPS 白名单清理**——现存唯一缺口 = `App/task/rs485_task.c`(RS-485 接线门禁未满足、刻意不接线,随 RS-485 采集链路 暂缓,见 `暂缓-RS485.md`) | 白名单不清 = "写了不编译"盲区仍在 | 随 RS-485 采集链路 一并出白名单(`--strict` 供审计复跑) | 结构债 | 报告 N 族残余(报告已删) |
| 缩堆迁 CCM 后半(暂缓) | **内存优化簇剩余**:原报告缩堆建议的后半(缩堆 56→32 KB → 迁 CCM)· 同类三项 | ⏸ **整簇暂缓**(2026-10-15 用户裁定):没有功能在等这块预算,而搬迁要动 scatter、要逐条过 DMA 可达性清单、缩堆还要上板复测。⚠ 代价落在"以后加任务 / 抬栈 / 加堆"那一刻——CCM 只剩 7,904 B、FreeRTOS 堆 `min_ever` 8,464 B,届时 `mem-budget` 会先报警 | 重开条件见 `../内存管理/RAM使用分析与优化方案.md` §4。⚠ **原报告另有一条已判定不可行**(CMSIS-RTOS2 `freertos_os2.h` 有 `#if (configMAX_PRIORITIES != 56) #error`,改小直接编译失败),不再列入 | **暂缓** | 报告 R 族剩余/§5.5(报告已删) |
| 仓库体积膨胀观察 | **仓库膨胀**:`.git` **233 MB**(2026-10-25 实测)+ 手册图片 36.3 MB(受控文件合计仅 79.3 MB,`.git` 已是它的 3 倍) | clone/备份慢,且持续恶化 | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**(三项都不解决历史膨胀)。**已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评 | **仓库决策** | 报告 D 族 |
| 静态链硬线双 FAIL | **timeSyncTask / iec104Task 静态最坏链跌破 ISR 硬线**(判据 = margin ≥ max(1024 B, cfg×30%),静态 margin 是唯一拦提交项但尚未挂 pre-commit 钩子,口径见 `../内存管理/RAM使用分析与优化方案.md` §4.0.7 第③条)——2026-11-04 复测:master 2b848c4a timeSyncTask **2056 B(余 504)**、iec104Task **1600 B(余 960)**,双 FAIL;#78 合入后 timeSyncTask **1904 B(余 656)**,只减不增(−152 B = 旧 8888 任务自身帧)。两链同尾 = `ch395f SPI 故障收尾 → dbg_console_drain 幻影环`(`../内存管理/RAM使用分析与优化方案.md` §4.0.7「休眠环」在 #74 UDP / #77 SNTP 出向链接入后被重新展开) | 抬栈属 Ask-first:先台架复采 `[STACK]` 水位(scripts/stack_gate.py 三档判据),拿真实余量再定抬栈或接受现状;幻影环是 armlink 记账非设备故障,**不得写成优化收益也不得当作已越线事故** | ⏳ 待台架复采 | #78 复测留痕 `docs/内存管理/data/stack_report_20261104.md`;`../内存管理/RAM使用分析与优化方案.md` §2.3 |
---
@@ -67,6 +69,7 @@
| **网络负向场景台架验证** | 拔线、SPI 故障注入、远显压力条件 | PHY 拔线、芯片级 `net_init` 失败、短写压测 | 本文件 §1 |
| **CI 远端闭环未验证** | runner 与第二台机器 | 验证远端两 job 闭环和不同工具路径 | 本文件 §4 |
| **UI 六页真机判据(记录 5 页 + 运行报告)** | 台架:NAND 里有真实事件记录、SD2506 电池域可控、104 主站可连 | 上板复核六件事:真记录翻页与掉电后保持、清除记录后对应页归零、上电一次看 `boot_count` +1、拔 RTC 电池后 `seq_state` 落 RESTARTED、IWDG 复位后 `rst_cause` 落 IWDG、主站连一次看 `conn_up` +1(模拟器侧这些记录文件无写入方、无 `boot_stats`、无 104 协议栈,黄金只拍得到空态「无记录」与固定假数据的显示口径) | 六个页面模块文件头「台架待补」段 + `../测试说明/专项验证/模拟器差距清单.md` §2.3 开机账本一行 |
| **SNTP 校时真机判据** | 台架:板与 PC SNTP 服务器(192.168.31.161 / .13)同网段可达 | `[sntp]` 使能 + 配服务器后上板复核:`[SNTP] step offset=` 步进日志、钟面与时间源一致(本地 UTC+8 口径)、`poll_min` 周期到点重对、拔线后指数退避与主备轮换、恢复后回到周期 | `../子系统设计/系统墙钟时间维护说明.md` §16 |
**送检顺序不可倒过来**:产品构建当前要求 UA/UN/IA 的标定 VALID 全部置位才走保护判据。
未完成标准源标定就送检,保护会表现为不动作。保持该闭锁,不用非标准源标定走捷径。
@@ -81,7 +84,6 @@
| **开入开出与遥信配置 3 页** | 固件和 V0.1 硬件均无可配开入对象;出口还涉及 GPIO 安全电平、保持/脉冲和回读自检 | 开入开出作为独立模块立项,再与 UI 页面同批 |
| **故障录波** | 独立子系统,不是补一个页面——**2026-10-29 已立项**:设计与板级判据属主 docs/子系统设计/故障录波设计说明.md(§10;原卡片登记簿 2026-10-30 销项并入后删除) | 已解除暂缓;执行按该文 §10 顺序图,UI 道互斥 |
| **104 文件传输(规约侧读文件)** | 录波取回走现成 FTP 已够用,规约侧文件服务是另一件事 | 送检或第三方交接需求明确后单独立项 |
| **SNTP 设置** | 当前对时走 8888 私有协议,SNTP 客户端与产品口径未定 | SNTP 客户端立项 |
| **RS-485 整簇** | 用户决定暂缓 | `暂缓-RS485.md` |
## 9. 开放问题
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 277,384 B (270.88 KB) | 1,048,576 B (1024 KB) | 26.5% |
| RW_IRAM1 | 94,368 B (92.16 KB) | 131,072 B (128 KB) | 72.0% |
| ER_IROM1 | 283,512 B (276.87 KB) | 1,048,576 B (1024 KB) | 27.0% |
| RW_IRAM1 | 94,736 B (92.52 KB) | 131,072 B (128 KB) | 72.3% |
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
| **Flash (ROM 合计)** | **279,436 B (272.9 KB)** | 1,048,576 B (1024 KB) | **26.6%** |
| **Flash (ROM 合计)** | **285,584 B (278.9 KB)** | 1,048,576 B (1024 KB) | **27.2%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -125,7 +125,7 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
| ftpTask | `1024*4` | 4KB | lftpd FTP 服务 | 740 B(余 81%)/ 静态 1216 B |
| testTask | `2048*4` | 8KB | 测试套件(产品构建不创建;存储套件深链需要) | 未创建 / 静态 496 B |
| defaultTask | `1280*4` | 5KB | FatFS 挂载/探针 + 配置引擎 + syslog + 栈水位探针 | 2152 B(余 41%)/ 静态 **3016 B**(栈余量收口时抬栈 1024→1280,2026-10-12) |
| timeSyncTask | `640*4` | 2.5KB | TCP 时间同步 | 616 B(余 50%)/ 静态 **1264 B**(栈余量收口时抬栈 512→640,2026-10-12) |
| timeSyncTask | `640*4` | 2.5KB | 网络校时节拍宿主(SNTP 客户端;:8888 监听 2026-11-04 退役 #78,任务入口不变) | 616 B(余 50%,2026-10-12 实测)/ 静态 **1904 B**(2026-11-04 #78 交付态复测,`docs/内存管理/data/stack_report_20261104.md`;链尾 `time_sntp_run→net_sendto→ch395f_spi_fault_set→控制台 drain 幻影环`,§4.0.7 同族记账。**master 2b848c4a 同判据实测 2056 B = FAIL**,本 PR 只减不增(−152 B = 旧任务自身帧)——FAIL 属 #77/#74 后既有债,与 iec104Task 1600 B 同源,见登记簿「静态链硬线双 FAIL」;台架 `[STACK]` 水位待复采后再定抬栈与否,未据静态值擅自抬栈) |
| adcTask | `640*4` | 2.5KB | TPAFE5160 采样消费 + 全周傅氏相量 + 有效性校验(TIM3 1 kHz 节拍,ADC 采集链路→全周傅氏相量→「采样有效性校验」 2026-10-11) | **952 / 2560**(余 1608 B / 62%,台架 `/serial-boot-test` 实测 2026-10-11);静态 **1320 B**(2026-10-12 复测,保护判据接线后) |
| iec104Task | `640*4` | 2.5KB | IEC104 上行服务(默认 2404 监听 + link 状态机,IEC 104 上行 2026-10-12) | 未上板 / 静态 **1456 B**(拆链→重建监听→net_bind→SPI 故障→日志尾,日志主导;512 words 时余 592 B < ISR 底线 → 抬至 640,余 1104 B / 43%) |
| protectTask | `512*4` | 2KB | 保护判据节拍(prot_elm_run 消费者 + 「自检失败闭锁」采样闭锁,保护任务 2026-10-12) | 未上板 / 静态 **872 B**(float 判据 + phasor 快照 + 日志尾,余 1176 B / 57%) |
+6 -6
View File
@@ -15,19 +15,19 @@
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---|
| ER_IROM1 (Flash) | 277,384 B | 1,048,576 B | 26.5% | 参考 |
| ER_IROM1 (Flash) | 283,512 B | 1,048,576 B | 27.0% | 参考 |
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
| RW_IRAM1 | 94,368 B | 131,072 B | 72.0% | PASS |
| RW_IRAM1 | 94,736 B | 131,072 B | 72.3% | PASS |
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|---|---|---|---|
| 网络 | 13,702 B | 12 KB | ⚠ 超 1.4 KB |
| 网络 | 13,958 B | 12 KB | ⚠ 超 1.6 KB |
| 存储 | 7,072 B | 12 KB | OK |
| UI | 36,473 B | 16 KB | ⚠ 超 19.6 KB |
| UI | 36,475 B | 16 KB | ⚠ 超 19.6 KB |
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
| 应用 | 26,333 B | 8 KB | ⚠ 超 17.7 KB |
| 其它 | 10,780 B | — | 参考 |
| 应用 | 26,433 B | 8 KB | ⚠ 超 17.8 KB |
| 其它 | 10,792 B | — | 参考 |
<!-- MEM_BUDGET:END -->
- **门禁**:pre-commit `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)——
@@ -0,0 +1,97 @@
# Stack budget report
- Source: `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm` (Keil static call graph)
- Linker run: Wed Nov 04 17:43:34 2026
- Global maximum stack usage: **3040 B** (any call tree)
Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be >= max(1024 B ISR, 30% of cfg).
| Task | Entry | Cfg (B) | Static max (B) | Runtime peak (B) | Worst (B) | Margin (B) | Margin % | Verdict |
|---|---|---|---|---|---|---|---|---|
| defaultTask | `StartDefaultTask` | 5120 | 3040 | - | 3040 | 2080 | 40% | PASS |
| netTask | `StartNetTask` | 3840 | 1936 | - | 1936 | 1904 | 49% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 1600 | - | 1600 | 2496 | 60% | PASS |
| uiTask | `StartUiTask` | 16384 | 1632 | - | 1632 | 14752 | 90% | PASS |
| testTask | `start_test_task` | 8192 | 744 | - | 744 | 7448 | 90% | PASS |
| timeSyncTask | `time_sync_task` | 2560 | 1904 | - | 1904 | 656 | 25% | FAIL |
| adcTask | `adc_task_func` | 2560 | 1424 | - | 1424 | 1136 | 44% | PASS |
| iec104Task | `iec104_serv_task` | 2560 | 1600 | - | 1600 | 960 | 37% | FAIL |
| protectTask | `protect_task` | 2048 | 880 | - | 880 | 1168 | 57% | PASS |
`Static max` = armlink worst-case call-tree depth; `Runtime peak` = measured high-water mark (boot + whatever workload ran before the sample); `Worst` = max of the two, which is what the verdict is computed from.
## Worst call chain per task
### defaultTask: 3040 B (own frame 0 B)
```
StartDefaultTask -> default_task_func -> storage_bring_up -> net_cfg_load -> cfg_file_load -> s_write_template -> s_write_doc -> cfg_store_write_atomic -> f_rename -> sync_fs -> disk_ioctl -> ftl_flush_cache -> dhara_map_write -> prepare_write -> auto_gc -> dhara_map_gc -> try_recover -> raw_gc -> trace_path -> dhara_journal_read_meta -> dhara_nand_read -> gd5f_read_from_cache -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### netTask: 1936 B (own frame 0 B)
```
StartNetTask -> net_task_func -> net_process_messages -> net_close_locked -> net_udp_settle_close -> net_udp_datagram_read -> ch395f_read_recv_buf -> ch395f_spi_dma_transfer -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### ftpTask: 1600 B (own frame 24 B)
```
StartFtpTask -> lftpd_start -> lftpd_inet_listen -> net_bind -> net_udp_bind_exec -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### uiTask: 1632 B (own frame 0 B)
```
StartUiTask -> ui_task_func -> ui_port_service_keys -> lv_refr_now -> lv_display_refr_timer -> refr_invalid_areas -> refr_area -> refr_configured_layer -> refr_obj_and_children -> refr_obj -> lv_obj_redraw -> refr_obj (Cycle)
```
### testTask: 744 B (own frame 224 B)
```
start_test_task -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### timeSyncTask: 1904 B (own frame 0 B)
```
time_sync_task -> time_sntp_run -> s_send_round -> net_sendto -> net_udp_sendto_exec -> net_send_locked -> ch395f_write_send_buf -> ch395f_spi_dma_transfer -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### adcTask: 1424 B (own frame 384 B)
```
adc_task_func -> adc_health_report -> syslog_post -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### iec104Task: 1600 B (own frame 160 B)
```
iec104_serv_task -> s_serv_poll -> s_service_link -> s_drop_conn -> s_bind_listen -> net_bind -> net_udp_bind_exec -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### protectTask: 880 B (own frame 160 B)
```
protect_task -> s_log_active_edge -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_UART5_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
## Frame-size ranking (own frame, top 15)
| Bytes | Function |
|---|---|
| 624 | `lv_draw_sw_arc` |
| 400 | `ui_task_wave_seg_win` |
| 400 | `draw_line_skew` |
| 384 | `adc_task_func` |
| 384 | `sb_link_poll` |
| 352 | `lv_draw_sw_triangle` |
| 352 | `net_stack_bring_up` |
| 352 | `net_udp_datagram_read` |
| 344 | `ui_w_editor_draw` |
| 328 | `lv_obj_redraw` |
| 328 | `view_frame_draw_cb` |
| 324 | `s_transform` |
| 320 | `wd_stall_evidence` |
| 312 | `nc_draw_cb` |
| 312 | `sn_draw_cb` |
@@ -19,7 +19,7 @@
关键事实:
1. **CCM 对 CPU 是全速内存**。Cortex-M4 无 语义取数与有效性 cache,"CCM 是慢内存"的直觉只适用于带 cache 的核(M7/出口与记录/H7 的非 cacheable 旁路),**在本芯片上不成立**——任何"按速度分配区域"的论证对本文档无效。
1. **CCM 对 CPU 是全速内存**。Cortex-M4 无 L1 cache,"CCM 是慢内存"的直觉只适用于带 cache 的核(M7/L4/H7 的非 cacheable 旁路),**在本芯片上不成立**——任何"按速度分配区域"的论证对本文档无效。
2. **DMA 对 CCM 地址的传输静默不落地**。2026-09-21 首轮 CCM 实验实证:以 CCM 上栈缓冲(dhara meta 132B / is_free 64B)为 DMA 源/目的时传输不生效,引发 FTL 重格式化循环与幻影坏块。此后 DMA 阈值提至 1024B 消除。**工程规则按"DMA 不可达 CCM"执行,不论数据手册如何表述。**
3. CCM 存在性已用 `ST-LINK_CLI -r32 0x10000000` 本机实测可读写;**克隆片可能缺 SRAM3,换料必须重测并回退布局**。
4. 工程不进入 Stop/Standby 低功耗模式,两区域掉电保留行为无差异,不构成分配判据。
@@ -2,11 +2,11 @@
> **模块名称**:故障录波——采样源沿用 TIM3 1 kHz 节拍,专用录波环(`App/algo/wave_ring.c`,已落地)、触发、落盘(defaultTask 是文件系统唯一写者)、取回(FTP 21 端口)、现场回放(UI 单通道页)。
> **文档定位**:本波的**设计属主**——口径、通道集、数据形态、触发模型、文件格式、预算与所有权边界、显示契约、二期不返工的结构约束在此定稿。**板级判据与本波执行顺序的属主即本文 §9 与 §10**——原任务规划下的《故障录波》卡片登记簿已于 2026-10-30 随销项并入本文后删除(沿革见 §14);排期顺位在 `docs/任务规划/README.md` §2.0「独立」行。
> **状态**:设计已定稿,**数据面 + 触发与落盘接线 + UI 两个页面(手动录波、录波回放)的代码面已交付(2026-10-29)**——主机单测 113 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器两份独立实现字节级互验通过。**没做完的只有板级判据那一轮**(注量对表、FTP 拉回、与 `meas:` 和 104 浮点 YC 对表、CRC 不符必须拒收的实测、回放页放一段真波、两个待台架核数值),逐条列在本文 §10 板级各行。三条已裁定口径见 §1.2。**2026-10-31 远程半轮**已把其中不需要到场与注入的六样跑过(落盘道、触发时序、冷却恢复、轮转、PC 侧坏段拒收、104 并发)——结果与回放页新缺陷「段表枚举恒空」全在 §10「板级远程半轮记录」小节;**该缺陷已于 2026-11-02 远程修复并板级闭环**(根因 = 读侧 `wave_store_seg_path()` 返回值判错 + 键处理缺失效,证据与前后对拍见 §10「回放页枚举恒空缺陷修复记录」)。
> **状态**:设计已定稿,**数据面 + 触发与落盘接线 + UI 两个页面(手动录波、录波回放)的代码面已交付(2026-10-29)**——主机单测 113 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器两份独立实现字节级互验通过。**没做完的只有板级判据那一轮**(注量对表、FTP 拉回、与 `meas:` 对表、CRC 不符必须拒收的实测、回放页放一段真波、两个待台架核数值),逐条列在本文 §10 板级各行。三条已裁定口径见 §1.2。**2026-10-31 远程半轮**已把其中不需要到场与注入的六样跑过(落盘道、触发时序、冷却恢复、轮转、PC 侧坏段拒收、104 并发)——结果与回放页新缺陷「段表枚举恒空」全在 §10「板级远程半轮记录」小节;**该缺陷已于 2026-11-02 远程修复并板级闭环**(根因 = 读侧 `wave_store_seg_path()` 返回值判错 + 键处理缺失效,证据与前后对拍见 §10「回放页枚举恒空缺陷修复记录」)。
> **相关文档**:
> `docs/子系统设计/ADC采样节拍设计说明.md`(节拍与时序账的属主;本文 §3 的数据源就是它)
> `docs/硬件平台/ADC通道映射说明.md`(网名 `AINn` 不等于板级 `ADCn`,也不等于转换器通道号;链路分段与削顶归属)
> `docs/任务规划/数据可信.md` §4.6(动态波形验证判据;录波落地后它的读数出口升级,见 §11)
> `docs/任务规划/数据可信.md` §4.6(动态波形验证判据;录波落地后它的读数通路升级,见 §11)
> `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1(FatFs 两条实证陷阱与 8.3 短名限制)
> `docs/内存管理/内存内容分配策略.md` §6 与 R6 规则(新增静态缓冲大于 4 KB 的前置动作)
> `docs/UI设计/内容页架构.md`、`docs/UI设计/RemoDispBus协议与接收设计说明.md`(回放页与远端看图的能力边界)
@@ -311,7 +311,7 @@ flowchart TD
- **实际落地形态(逐字)**:打印点在 `App/task/adc_task.c`:782 行,格式串 `"wave: seg=%lu armed=%u src=%s pts=%u err=%lu ovf=%lu drop=%lu"`——**字段名与顺序就是上面的模板**(台架与 PC 脚本按行名 grep,动它就断脚本);`src` 取 M(手动)/ P(保护)/ T(定时自抓)/ `-`(本次上电还没触发过);`armed` 只在 FREEZE 与 SEALED 为 1(COOL 期只挡新触发不录新段,画进 armed 会被台架误读);`seg` 是累计**成功回执**的段数、不是轮转号;`err` = 落盘失败 + SEALED 滞留超时自弃(含 §5.2 第四条的 ring abandoned 判废),由环内计数收口,落盘道不数第二份账。`err` 与 `ovf` 的**已知容差**(lead 裁定 2026-10-29,不修只入档):这两个计数是跨上下文的读-改-自增(`ovf` 由任意上下文的拒绝路径加、`err` 由 adcTask 的滞留自弃与 defaultTask 的落盘失败分别加),本层双端编译、不许引 FreeRTOS / CMSIS 的临界区内在函数,所以并发拒绝或并发失败时**理论上可能少计一次**。定性为**观测计数的已知容差**:只用于趋势与判读,**不作为任何判据的唯一依据**——要坐实「冷却期内确实来了第二次触发」,同时看 `armed` 与那一刻 `/log/start.log` 的时刻。契约原文见 `App/algo/wave_ring.h` 文件头「观测计数的已知容差」段;
- **落盘道失败留痕名清单(2026-10-30 并入原卡片登记簿;台架判读按名索引,不新增第二种形态)**:单板上一律一行 `[WAVE] seg drop: <原因> (idx=… written=…)`,统一打印点 `App/task/default_task.c`:816——`crc selftest FAIL` / `mkdir /wave` / `opendir /wave` / `readdir /wave` / `seg_path` / `pack_start` / `f_open` / `pack_next` / `f_write short` / `pack stall` / `f_close` / `len mismatch` / **`ring abandoned`**(最后一条即 §5.2 第四条陷阱的处置留痕,代码与主机单测面已有、板级未测,观察项见 §10);另有一条非失败的慢段留痕 `[WAVE][WARN] seg held <毫秒> ms (idx=…)`(`App/task/default_task.c`:831)——搬得慢不是错,真失败才走 `seg drop`。
- 只在原先静默的黑洞处补锚点,别重复已有步骤——每条 DBG 日志约 90 B Flash 加 216 B 栈(`AGENTS.md` 的日志与工具链节);
- 该行已同步进 `docs/测试说明/专项验证/ADC前端注入验证流程.md` 的读数出口表(2026-10-29;同一张表还登记了段文件出口与 PC 侧读段工具)。
- 该行已同步进 `docs/测试说明/专项验证/ADC前端注入验证流程.md` 的读数地点表(2026-10-29;同一张表还登记了录波段文件与 PC 侧读段工具)。
---
@@ -322,7 +322,7 @@ flowchart TD
| 主机侧纯逻辑单测(新增,进 `test/host/`) | ① 环:预触发与后触发点数、冻结与解冻、冷却丢弃计数;② 段格式编解码往返,含 CRC 不符、零长度、字节数不符三条失败路径;③ 触发状态机三条来路(手动 / 保护 / bench)。**环与格式必须是纯逻辑**(不碰寄存器、不碰 RTOS)才能这样测——这是 `App/algo/` 既有分层纪律 |
| PC 侧工具的定性 | `test/host/wave_read.py` 是 **bench 工具**(`test/host/` 下的 loose .py,先例 `test/host/ftp_capture.py`),**不是**主机单测套件 → 不建 `run_unit_test.py`、不加 pre-commit 专属钩子(专属钩子默认不补,按 Lead 裁定 2026-10-27);它自带 `--selftest` 就是它的自带回归,跑法与四个子命令见 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §4 |
| 跨工具链分叉(实证,写进来是为了下一个模块别重踩) | 两份录波 .c 只 include 自身头(链上只有 stdint.h)时,ARMCC 报 22 条 identifier "NULL" is undefined;而主机 MSVC / gcc 会传递性把 stddef.h 拉进来,**主机单测完全照不出这个分叉** → 新增模块需要用 NULL 就自己 include stddef.h(与 `App/algo/sample_ring.c` 同口径,别塞公共头) |
| 板级 | 信号发生器注 1 V / 50 Hz → 手动一段 → FTP 拉回 → PC 脚本量出周波数、幅值、极性与注入一致;再把 PC 重算的基波有效值与板上 `meas:` 与 104 浮点 YC 对表。**判据只量四样:周波数、幅值、极性、钉轨路数——不比绝对相位**(理由见 §4.2「段内点序」:触发瞬间在工频周期内任意落点,段字节里没有绝对时间参考;真要比相位必须让发生器给同步触发,本波不做)。**钉轨对表的合格判据 = 头位图 ⊇ 实测钉轨路数,且差异必须能由注入条件解释**——因为 `clip_mask` 是按来源派生的链路可信性标记(§6.2 偏移 24 行),不是本段实测值:只加直流或小幅注入时那五路本就不该钉轨,硬取等式会假红。**同段另起一条强观察项**:V0.1 下这五路若在做**交流注入**时反而没钉轨,先怀疑接线或该路根本没被驱动——这是告警面,不是失败面(等式仍作为默认尺子留在 PC 侧 `shape --strict` 里,因为它有这个信号价值;要放行差异用那条判据的解释权,不靠改尺子)。**§5.2 第四条的板级观察项(代码与单测面已有、板级未测)**:bench 连续自抓期间人为把卷占住 2 s 以上(例如同一段跑 FTP 大文件读),看串口 `wave:` 行的 `err` 是否加一、是否只出现**一条** `[ERR] … seg drop: ring abandoned` 留痕(不是每拍刷屏)、且段号不推进(下一段仍写同号文件) |
| 板级 | 信号发生器注 1 V / 50 Hz → 手动一段 → FTP 拉回 → PC 脚本量出周波数、幅值、极性与注入一致;再把 PC 重算的基波有效值与板上 `meas:` 对表。**判据只量四样:周波数、幅值、极性、钉轨路数——不比绝对相位**(理由见 §4.2「段内点序」:触发瞬间在工频周期内任意落点,段字节里没有绝对时间参考;真要比相位必须让发生器给同步触发,本波不做)。**钉轨对表的合格判据 = 头位图 ⊇ 实测钉轨路数,且差异必须能由注入条件解释**——因为 `clip_mask` 是按来源派生的链路可信性标记(§6.2 偏移 24 行),不是本段实测值:只加直流或小幅注入时那五路本就不该钉轨,硬取等式会假红。**同段另起一条强观察项**:V0.1 下这五路若在做**交流注入**时反而没钉轨,先怀疑接线或该路根本没被驱动——这是告警面,不是失败面(等式仍作为默认尺子留在 PC 侧 `shape --strict` 里,因为它有这个信号价值;要放行差异用那条判据的解释权,不靠改尺子)。**§5.2 第四条的板级观察项(代码与单测面已有、板级未测)**:bench 连续自抓期间人为把卷占住 2 s 以上(例如同一段跑 FTP 大文件读),看串口 `wave:` 行的 `err` 是否加一、是否只出现**一条** `[ERR] … seg drop: ring abandoned` 留痕(不是每拍刷屏)、且段号不推进(下一段仍写同号文件) |
| 板级·触发来路(2026-10-30 并入原卡「手动触发与保护启动接线」板级段) | 真机面板按一次「手动录波」出一段;造一次过流,段号与该时刻 `/log/start.log` 事件对齐;冷却期内二次触发只加 `ovf` 不重录(**同时看 `armed` 与那一刻 start.log 的时刻,不要只盯 `ovf` 数字**——容差见 §9);冻结窗口内 104 上送与保护判据不受影响(这条是「丢录波样本不丢采集」的验收) |
| 板级·回放页(2026-10-30 并入原卡「现场单通道回放页」板级段) | 真机上放一段真波(不是模拟器合成节拍);远端看图能拍到该页做留证(只作留证,不是波形通路,§8.4);**空态与 CRC 失败态绝不允许画成一条直线**——用坏段在真机复现一次。**2026-10-31 远程半轮进展见下「板级远程半轮记录」小节**:PC 侧坏段拒收已过;回放页坏段/空态画面的测试面**已于 2026-11-02 枚举缺陷修复后打开**(真机已验证真段显示 + 切段切道,见下「回放页枚举恒空缺陷修复记录」小节),坏段/空态两画面本身仍是到场轮要拍的项 |
| UI | `ui-verify` 五步;回放页入 `ui-golden` 黄金参考(**参考图一旦入库只能改代码对齐,禁止刷参考掩盖**);新叶走 `gen_menu.py` 重生成 + 字库链 |
@@ -380,8 +380,8 @@ try 锁纪律)照常读出并显示 ⇒ try 锁读径正常;`manual_req` 作
「0 段缓存」,回放页从此假显「无录波段」、直到下一次 `manual_req` 才重试;失败应不钉表、
下键重试,与 soe_store 读者同纪律。
**本半轮没跑的(人到场轮清单)**:注量对表(周波数 / 幅值 / 极性 / 钉轨路数)、`meas:` 与 104
浮点 YC 数值对表、两个待台架核数值(封存超时窗 §4.2 兜底行与冷却时长 `App/algo/wave_ring.h`)、P 触发与 start.log 对齐(还得先解标定闭锁)、
**本半轮没跑的(人到场轮清单)**:注量对表(周波数 / 幅值 / 极性 / 钉轨路数)、`meas:`
浮点数值对表、两个待台架核数值(封存超时窗 §4.2 兜底行与冷却时长 `App/algo/wave_ring.h`)、P 触发与 start.log 对齐(还得先解标定闭锁)、
bench T 源与卷占用 2 s 的 `ring abandoned` 观察(需 bench 构建)、~~回放页坏段画面与空态画面
(等枚举缺陷修复后才有测试面)~~ **(2026-11-02 枚举缺陷修复后测试面已开,见下小节)**、
`task_prof` 读路径耗时峰值。**执行动作侧**(到场的人做什么、
@@ -447,7 +447,7 @@ bench 锁全程持有;三次烧录(埋点 / 诊断加强 / 根因修复)
| 旧口径 | 新口径 | 原因 |
|---|---|---|
| 「录波通道必须先有工程量,排在本波之后」 | 录波录**原始码值**,不等工程量;解释靠头里的系数快照 | §3.2:现在根本没有工程量,等下去等于永远不动 |
| 动态波形验证的读数出口 = 派生标量(`meas:` / 104 / `phasor(hb)`) | 派生标量仍是**判据**,但样本改从录波段导出 → 判据升级为「PC 独立重算 vs 板上读数」对表 | 录波段就是现成的样本出口,不必再造一条串口通路 |
| 动态波形验证读数的地方 = 派生标量(`meas:` / 104 / `phasor(hb)`) | 派生标量仍是**判据**,但样本改从录波段导出 → 判据升级为「PC 独立重算 vs 板上读数」对表 | 录波段就是现成的样本来源,不必再造一条串口通路 |
| 动态波形验证「手边可做、排在最前」 | 排在录波 P1 之后(用户裁定 2026-10-29:录波先行) | 先有样本通路,验证才不留遗憾 |
(2026-10-30 更新:原卡片登记簿已销项并入本文,§10 尾有本波执行顺序图;排期顺位在 `docs/任务规划/README.md` §2.0「独立」行。)
@@ -223,7 +223,7 @@ static HAL_StatusTypeDef iwdg_deferred_start(const IWDG_HandleTypeDef *a_h) { (v
|---|---|---|---|
| NAND 初始化 / `f_mkfs` / `dhara_map_resume` / `f_mount` | 启动段 | 数百 ms ~ 数秒 | **① 启动段不启动狗**(§4.1) |
| PHY 链路等待(60×50 ms) | `net_stack_bring_up` | 最长 3 s | **② 分片心跳**:循环内每轮 `task_wd_beat` |
| `time_sync_handle_conn` 的 30 s 总预算 | `App/sync/time_sync.c` | 单次可睡满 30 s | **② 切片**:`TIME_SYNC_WAIT_SLICE_MS=200` 夹住每次 `net_recv_to` 的 `rem`,每片先喂位(总预算语义不变) |
| `time_sntp_run` 的单次响应等待(UDP `net_recvfrom_to`) | `App/sync/time_sntp.c` | 单次调用 ≤1 片 `SNTP_RECV_SLICE_MS=50` | **② 切片**:响应总预算(5 s 起指数放大)拆成 50 ms 片,任务 200 ms 节拍每轮喂位(旧 `time_sync_handle_conn` 30 s 预算行随 :8888 退役于 2026-11-04 删除,见 `../子系统设计/系统墙钟时间维护说明.md` §15) |
| 就绪屏障等待(NET/STORAGE READY) | net / ui / ftp / timesync 入口 | 不定 | **② 分片**:等待按 100 ms 分片并喂位 |
| 卷锁等待(FatFs 多任务) | `lftpd_io.c`、`App/fs/vol_lock.h` | 不定 | **② 分片**:100 ms 分片阻塞 + 每片 `lftpd_task_wd_beat()` |
| 卷锁 try 失败(defaultTask / uiTask **从不等锁**,取不到就跳过本轮) | `App/fs/vol_lock.c` 观测计数 | 不定 | **④ 只计数、不在失败分支留日志**:锁层记 5 态(`acquired` / `try_timeout` / `blk_timeout` / `failopen`,16 B 静态),读口 `vol_lock_get_stats()` **不取锁**、任意上下文可调。为什么不许在 try 失败处补一条日志——唯一能把事写下来的通道(`/log/wdev.log`、`run.log`)自己也要取这把锁,而卷锁**非递归**,被卷忙挡住时既写不出还会自取死锁;一次性留痕交给 syslog 唯一写者按差值 latch 补投(道G task-17,接口口径见 `App/fs/vol_lock.h`) |
@@ -376,7 +376,7 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗
| netTask | 主循环;`net_stack_bring_up` 的 PHY 等待循环内;就绪屏障等待分片;入口 `task_wd_bind()` |
| ftpTask | 主循环 = lftpd 事件循环:`lftpd.c` 事件循环每轮、RETR/STOR 每分片;`lftpd_inet.c` 连接/数据等待每轮每片;`lftpd_io.c` 卷锁等待每片;入口 `task_wd_bind()` |
| uiTask | 主循环;就绪屏障等待分片;入口 `task_wd_bind()` |
| timeSyncTask | 主循环;监听重试(500 ms/2 s)前;accept 轮询;`time_sync_handle_conn` 的 **200 ms 切片**;屏障等待分片;入口 `task_wd_bind()` |
| timeSyncTask | 主循环(200 ms 校时节拍,每轮首行喂位;`time_sntp_run` 单次 ≤50 ms 片 + `time_sync_delay_fed` 50 ms 分片);屏障等待 100 ms 分片;入口 `task_wd_bind()`(旧「监听重试 / accept 轮询 / handle_conn 200 ms 切片」三处埋点随 :8888 退役于 2026-11-04 删除,墙钟说明 §15) |
| adcTask | 主循环每 1 ms 节拍 `task_wd_beat(TASK_WD_ADC)`;**不做 NAND I/O → 不登记 FTL 心跳路由**(无需 `task_wd_bind()`) |
| testTask | **不埋点、不登记、不入掩码** |
| NAND FTL | `gd5f2gq5ue.c`:长操作入口 `nand_ftl_wd_beat()` + `nand_ftl_wd_feed()`;`gd5f_wait_busy` 每 100 ms 双管(路由到调用者任务) |
@@ -24,8 +24,9 @@
### 1.1 时基口径:内部一律**本地钟面**(用户裁定 2026-10-27)
**裁定原文:「时基先本地为准」。** 定的是**冲突时谁赢**:面板时钟页与 8888 对时把「本地钟面当 epoch」写入,
而 104 的 `C_CS NA`(对时命令)携的是 UTC,两条写入口**后写者赢** ⇒ 装置墙钟与主机本地时刻恒差 8 小时。
**裁定原文:「时基先本地为准」。** 定的是**冲突时谁赢**:(当时的)面板时钟页与 8888 对时把「本地钟面当
epoch」写入,而 104 的 `C_CS NA`(对时命令)携的是 UTC,两条写入口**后写者赢** ⇒ 装置墙钟与主机本地时刻
恒差 8 小时(8888 已于 2026-11-04 退役,写入口现为 SNTP / UI / 104,见 §15、§16)。
本裁定结束「后写者赢」:**内部真源 = 本地钟面**,任何来源写入前都要先换算成本地钟面再落库。
这条同时也纠正了**本文自己的矛盾**(这才是 8 小时的根):§2 曾写 epoch = 「自 1970-01-01 00:00:00 **UTC**
@@ -298,8 +299,8 @@ uint32_t now = sys_clock_get(); /* Unix 秒,可用于 FatFS 文件时间、
| 项目 | 现状 | 可扩展方向 |
|------|------|------------|
| 时间范围 | 受 `uint32_t` 限制至 2038 | 改用 `uint64_t`/`time_t` 扩展 |
| 运行期校准 | 30 天惰性再基准化已实现(§7):纯自我参照、零 I2C;**有意不**周期性重读 RTC 重锚——RTC 漂移(±20ppm ≈ ±1.7 s/天)远大于 Tick 漂移,重锚反而降低精度;时间纠偏由时间同步(§15)负责 |
| 网络校时 | 已实现(PC 推送 / TCP,见第 15 节) | SNTP/NTP 公网校时仍可作为后续扩展 |
| 运行期校准 | 30 天惰性再基准化已实现(§7):纯自我参照、零 I2C;**有意不**周期性重读 RTC 重锚——RTC 漂移(±20ppm ≈ ±1.7 s/天)远大于 Tick 漂移,重锚反而降低精度;时间纠偏由时间同步(§16)负责 |
| 网络校时 | **SNTP 客户端 / UDP 123(§16,2026-11-04 落地)为唯一网络校时来源**;PC 推送 / TCP 8888 同日退役(§15) | NTP 公网源需现场路由,口径不变;104 `C_CS` 写入口径见 §1.1 末待决项 |
| 时区 | **口径已定:内部一律本地钟面,不存时区**(用户裁定 2026-10-27,见 §1.1) | 「API 层加偏移参数」这条旧建议**作废**;唯一待决的是 104 上行是否要转 UTC(§1.1 末,送检前定) |
### 13.1 用户 SRAM 的占用分配(共享资源,用之前先在这里登记)
@@ -333,85 +334,57 @@ SD2506 有 **70 字节电池保持用户 SRAM**(寄存器 `2CH~71H`,`Drivers
| `Drivers/BSP/SD2506/sd2506.c` / `.h` | SD2506 RTC I2C 驱动(`sd2506_get_time` / `sd2506_set_time`) |
| `App/app/app_main.c` | 初始化调用顺序:`sd2506_init()` → `boot_stats_init()`(用户 SRAM 上电账本,§13.1)→ `sys_clock_init()` |
| `App/util/boot_stats.c` / `.h` | 用户 SRAM 上电账本 + 本次开机复位原因快照(占用见 §13.1;单测 `test/host/boot_stats/`) |
| `App/sync/time_sync.c` / `time_sync.h` | 时间同步实现(TCP Server,`timeSyncTask`) |
| `test/host/time_sync_server.py` | PC 端时间推送客户端(TCP) |
| `App/sync/time_sync.c` / `time_sync.h` | `timeSyncTask` 实现(网络校时节拍宿主;TCP :8888 推送监听 2026-11-04 退役,§15);单测 `test/host/time_sync/` |
| `App/sync/time_sntp.c` / `time_sntp.h` | SNTP 校时客户端(§16,同任务内由 200 ms 校时节拍驱动);单测 `test/host/sntp/` |
| `Drivers/BSP/NETCFG/sntp_cfg.c` / `.h` | `[sntp]` 配置段属主(注册在 `net_cfg_load()`) |
| `Lib/coreSNTP/` | vendored coreSNTP v2.0.0 序列化器(MIT,仅 serializer + LICENSE;48 字节报文编解码) |
---
## 15. 时间同步(PC 推送 / TCP)
## 15. 时间同步(PC 推送 / TCP :8888)——已退役(2026-11-04)
除 RTC 自身走时与开发期校时宏外,系统支持由 **PC 通过 TCP 主动推送本地墙钟** 来同步板子时间,使板子时间与开发电脑一致(无需公网 NTP)。
> **状态:已退役。** SNTP 客户端上线(§16,PR #77)当日,`:8888` 推送监听、8 字节 "TIME" 协议、
> PC 端推送器 `test/host/time_sync_server.py` 与探针 `test/host/e02_probe.py` 一并删除,网络校时
> **唯一入口 = §16 SNTP**。历史实现(协议帧、监听/退避状态机、约 80 断言点的主机单测、真机验收日志)
> 以 git 历史为准(`App/sync/time_sync.c` v1.x 与本节旧版);旧验证锚点
> `[TIME_SYNC] time_sync: TCP listen on :8888 (sock=1)` 仅供解读旧日志(`scripts/v5_log_gap_report.py`
> 的探针窗口分类器仍识别 `no TIME packet`,属历史取证,不改)。
### 15.1 模块与角色
- **当初定位**:开发期让板子时间对齐 PC(无需公网 NTP)——PC 周期推固定 8 字节帧
`"TIME"(54 49 4D 45) + uint32 大端"本地 Unix 时间戳"(与电脑显示一致)`,板端校验魔术字后走
`sys_clock_set_unix()` 同一收口写回 SD2506(§1.1 口径的第二个历史写入口)。
- **退役理由**:① SNTP 落地后它只剩"开发期工具"定位,与 SNTP 双写并存时现场钟面**后写者赢**、
节奏不可控;② 单连接监听被 `time_sync_handle_conn()` 全程占用,一条连接不结算(30 s 预算/
半包等待)其他对时全排队——head-of-line 风险,为喂狗才做的 200 ms 分片只是缓解;③ 依赖 PC 上
Python 推送器在场,现场不可持续;真机对时判据已由 UI 对时页 + `[SNTP]` 日志/OPER 痕覆盖。
- **仍然有效的教训**:CH395F 单连接 TCP 服务在收到 **FIN 时会丢弃该 socket 内尚未读走的缓冲数据**
(旧推送器因此必须 sendall 后保持读至 EOF/2 s 超时)。`lftpd`、`ui_net_sink` 与今后任何常驻
TCP 服务同样受此约束(先结算再断开);UDP 校时(§16)无此坑。
- **Socket 预算变化**:`alloc_socket()` 从 0 分配、硬件 Socket 号=下标的机制不变;常驻监听由
21/8888/7003 三个减为 21/7003 两个,SNTP 的 1 个 UDP Socket 仍懒创建、按代次/开关归还(§16)。
- **TCP 而非 UDP 的旧论证**(选 TCP 的队列/串行化背景)已被 2026-11-03 UDP 并入消息队列
(`docs/网络/BSD_Socket_API_使用指南.md` §9.0)与本文第 16 节的落地取代,不再单独成立。
| 角色 | 提供方 | 说明 |
|------|--------|------|
| 时间同步服务端(板子) | `App/sync/time_sync.c` / `time_sync.h` | TCP Server,监听 `TIME_SYNC_PORT`(默认 8888) |
| 同步任务 | `timeSyncTask`(FreeRTOS 任务) | 独立任务,由 `Src/freertos.c` 创建(优先级 `osPriorityNormal`,栈 2KB) |
| 时间推送端(PC) | `test/host/time_sync_server.py` | Python TCP 客户端,周期推送本机本地时间 |
| 落盘接口 | `sys_clock_set_unix()` | 收到时间包后调用,内部转 `sd2506_time_t` 并写回 SD2506 |
---
### 15.2 协议
## 16. SNTP 校时(客户端,2026-11-04 落地)
PC 每轮发送固定 8 字节帧:
```
字节 0..3 : 魔术字 "TIME"(0x54 0x49 0x4D 0x45)
字节 4..7 : 本地 Unix 时间戳,uint32,大端(PC 本地时间,含时区,与电脑显示一致)
```
板子 `time_sync_handle_conn()` 读取后校验魔术字,取大端 uint32 调用 `sys_clock_set_unix(epoch)` 更新运行基准并写回 RTC。
### 15.3 任务流程
`timeSyncTask` 启动后先等待 `SYS_EVT_NET_READY` 事件标志(取代旧版 8s 魔法延时;网络不就绪则一直等待),进入循环:
```c
/* osFlagsNoClear:就绪位是"状态"不是"事件",任何消费者都不得把它清掉
* (否则其它等待者永不唤醒——2026-09-21 FTP 永不监听事故的根因) */
osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY, osFlagsWaitAny | osFlagsNoClear, osWaitForever);
for (;;) {
ls = time_sync_open_listener(); /* net_socket + net_bind + net_listen,失败重试 */
conn = net_accept(ls, NULL, NULL); /* 阻塞等待 PC 连接(无连接立即返回 -1,循环重试) */
time_sync_handle_conn(conn); /* net_recv 读到 8 字节 → sys_clock_set_unix → net_close */
/* conn(单连接模式下即 ls)被 net_close 释放,下次循环重建监听 */
}
```
> 单连接模式下监听 Socket 收到连接后即转为数据通道,连接关闭后被释放;因此每轮循环都**重新创建监听 Socket**(与 `lftpd` 同模式),与 FTP 不冲突。
### 15.4 为什么用 TCP 而非 UDP
- 所有 socket 操作(`net_socket` / `net_bind` / `net_listen` / `net_accept` / `net_recv` / `net_close`)均走 `net_socket` 的**线程安全 API**,内部经消息队列由 `netTask` 串行执行;
- netTask 因此只需做 `net_poll()` + `net_process_messages()`,不掺杂任何应用层网络逻辑;
- **TCP 的全部 API 已经是基于消息队列的**(已在 `net_socket.c` 实现),而 UDP 的 `net_bind` / `net_sendto` / `net_recvfrom` 绕过队列。选用 TCP 即可让时间同步作为独立任务运行,且**无需改动 `net_socket.c` 的 UDP 路径**。
### 15.5 Socket 分配与冲突
- `net_socket()` 从 0 开始分配第一个空闲 Socket 控制块(`alloc_socket()`),并把硬件 Socket 号设为该下标;调用时的第三个参数(0)仅为协议占位,不影响分配。
- 当前启动顺序:FTP 任务(`NET_READY` + `STORAGE_READY` 屏障满足后,无魔法延时)先监听 → 占用一个 Socket(单连接模式下监听通道复用为数据通道);时间同步任务(`NET_READY` 后)后监听 → 占用另一个空闲 Socket。二者不冲突。
- 单连接模式允许**任意** Socket 作为"监听 + 数据复用"通道,且总共 8 个 Socket,FTP 与时间同步各用 1 个,余量充足。
### 15.6 客户端实现要点(避坑)
PC 端 `test/host/time_sync_server.py` 发送 8 字节后**必须保持连接打开**,等待板子读取(或短延时)后再 `close()`:
- 若发送后立刻关闭,CH395F 在收到 FIN 时会**丢弃接收缓冲里尚未被板子读出的 8 字节**;
- 板子 `net_recv` 经消息队列异步执行(有 ~10ms+ 调度延迟),等它去读时连接已 DISCONNECT、缓冲已清空,`net_recv` 走 CLOSED 分支返回 0,时间包丢失、同步失败;
- 客户端改为 `sendall` 后 `recv` 直到板子主动关闭(EOF)或 2s 超时,即可保证板子先读到数据。
### 15.7 验证
板子日志应出现:
```
[TIME_SYNC] time_sync: TCP listen on :8888 (sock=1)
[NET] sock1 standalone accept
[NET] recv sock1 len=8
[CLK] sys_clock_set: wall clock updated to <epoch>
[CLK] sys_clock_set: SD2506 RTC written
[TIME_SYNC] time_sync: wall clock updated (epoch=<epoch>)
```
PC 端 `python test/host/time_sync_server.py` 每 `interval` 秒推送一次,epoch 随电脑本地时间递增即表示同步正常。
- 模块与任务骨架:`App/sync/time_sntp.c/.h` 由 `timeSyncTask`(`App/sync/time_sync.c` v2.0,:8888
退役后该任务即纯校时节拍宿主)驱动——屏障等 `SYS_EVT_NET_READY`(100 ms 分片喂心跳)→ 主循环
每 200 ms 一轮:`task_wd_beat` → `time_sntp_run(net_get_generation())`(应答收片 ≤50 ms,不饿任务狗;
代次变化由 `time_sntp_run` 内部弃旧 Socket 自重建)→ `time_sync_delay_fed(200)` 分片续心跳;
48 字节报文编解码 = vendored coreSNTP 序列化器(`Lib/coreSNTP/`,MIT,零堆),节奏与准入判据在本方。
- 配置:`/config.ini` `[sntp]` 段(`enable` / `server1` / `server2` / `poll_min`,属主 `Drivers/BSP/NETCFG/sntp_cfg.c`,
注册在 `net_cfg_load()`);现场改法 = UI「通讯参数 → SNTP 设置」页(密码叶)保存后随下一轮生效,或 FTP 改文件(4 s 落定)。
出厂默认**不启用**、服务器为空——不配不发任何 UDP 包。
- 时钟面换算:SNTP 给的是 UTC,落库前加固定东八区偏移(`SNTP_TZ_LOCAL_OFFSET_SECS 28800`)写 `sys_clock_set_unix()`
(连带回写 SD2506)——这正是 §1.1 裁定「任何来源写入前先换算成本地钟面」的第三个写入口;装置仍不存时区。
- 判据(RFC 4330 §10 口径):OriginTimestamp 逐位等于发出时的 T1(含低 16 位随机小数位,防伪造/防重放);
mode=4、stratum 1~15、闰秒指示 ≠3、服务器 Kiss-o'-Death 按 DENY/RSTR(换服务器)与 RATE(退避)分流;
响应源地址必须等于请求目的地址。失败:超时指数退避(5 s 起、60 s 封顶)+ 主备轮换;成功:恢复 `poll_min` 周期。
- 步进策略:|offset| < 2 s 只记日志不步进;≥ 2 s 或冷启动直接步进并留痕 `[SNTP] step offset=`、`syslog` OPER
「clock set by SNTP」;首个校验通过的同步置 `SYS_EVT_TIMESYNC_READY`(就绪位,不覆盖后续步进语义)。
- 与 8888 的关系(历史):2026-11-04 当日 SNTP 落地在前(PR #77)、:8888 清除在后,中间两路短暂
并存、同一 `sys_clock_set_unix` 收口**后写者赢**;退役后本模块是**唯一网络校时入口**,
`sys_clock_set_unix` 现存三个写入口 = SNTP / UI 手工对时 / 104 `C_CS`(口径见 §1.1)。
- 主机单测:`test/host/sntp/`(报文格式、防伪拒收、KoD、退避与周期、阶跃域守卫、配置注册表)。
+37 -5
View File
@@ -197,6 +197,37 @@ write=1
[adc_cal] 9 键 / [adc_cal_ofs] 8 键 / [run_par] 9 键 / [comm] 2 键);段/键上限
的机器钉 = `test/host/cfg_file` TC-CONFIG-056/057。
### 3.7 `[sntp]` 段:SNTP 校时参数(2026-11-04 新增,板端 SNTP 客户端接入,#57 调研结论)
板端 SNTP 客户端的持久载体(消费方 = `App/sync/time_sntp.c`,挂在 timeSyncTask
既有循环上,48 字节报文走 PR #74 后的 UDP socket 通道;数据本体 =
`Drivers/BSP/NETCFG/sntp_cfg.h/.c`(纯段数据 + 读写出口,不依赖引擎头——分层门禁
layer-deps 口径),段描述符由 `net_cfg.c` 的 `s_sntp_register()` 组装,注册编排 =
`net_cfg_load()` 统一调用——与 [ftp] 同路径,「先注册后 load」成立):
| 键 | 含义 | 工厂默认 | getter 合法带(带外自愈回默认) |
|---|---|---|---|
| `enable` | 校时开关 | **0(出厂关闭)** | 0/1,非 1 一律视为关 |
| `server1` | 主 SNTP 服务器 | **0.0.0.0(留空)** | 点分 IPv4 字面量(引擎 `s_decode_ip4` 解析;**无 DNS**) |
| `server2` | 备 SNTP 服务器 | 0.0.0.0 | 同上;主服务器超时/拒绝(KoD DENY·RSTR)自动切换 |
| `poll_min` | 轮询周期(分钟) | 60 | 1 ~ 1440(带外读回 60,**不写回 RAM**,`sntp_cfg_poll_min()`) |
- **零足迹口径**:出厂默认关闭 + 服务器留空 → 不开 socket、不发一个字节(与
「FTP 未配置不监听」一致);配了可达时服务器的现场才启用。
- **生效链路**:页面保存行 → `SYS_EVT_CFG_SAVE` 整档外科落盘(引擎属主 defaultTask
不变);客户端每轮直接读 RAM 副本,**无段回调、不新增事件位**。网络重建
(代次变化)时 UDP socket 随芯片复位被弃用,客户端检测 `net_get_generation()`
变化后关闭旧 fd 并**重新 1~5 分钟随机散布**首轮(RFC 4330 §10 口径)——
即 §4.2 自愈机制在 UDP 客户端侧的自然延伸(表内 TCP 三服务之外)。
- **时钟面口径**:校验通过的服务器时间折算为 UTC 后**恒加 28800 s**(本地
UTC+8,单一「本地钟面」口径,#56 裁决,见
`docs/子系统设计/系统墙钟时间维护说明.md` §1.1;工程不存时区)再交
`sys_clock_set_unix()`(内部含 SD2506 RTC 写回);`|offset| ≤ 2 s` 只记录
不拨针(防 RTC 无谓磨损)。冷启动(钟面无效)直接采用服务器 T3。
防欺骗/退避/KoD 细则 → `App/sync/time_sntp.c` 文件头 +
`test/host/sntp`(TC 命名即判据)。
- 容量台账:本段上线后 `/config.ini` 受管段 = **7/8**(前 6 段见 §3.6,本段 4 键)。
---
## 4. 生效链路
@@ -232,7 +263,7 @@ sequenceDiagram
**机制**:`请求 → netTask 重建 → 代次号 → 服务自愈`
**约束与对策**:网络重建会复位 CH395F,所有监听 Socket 随之失效;而三个 TCP 服务(FTP :21 / 对时 :8888 / 远程显示 :7003)的监听由各自任务持有。为此引入"网络代次号"——各服务检测到代次变化后主动重建监听,在新 IP 上恢复服务。
**约束与对策**:网络重建会复位 CH395F,所有监听 Socket 随之失效;而两个常驻 TCP 服务(FTP :21 / 远程显示 :7003;对时 :8888 已退役,2026-11-04)的监听由各自任务持有。为此引入"网络代次号"——各服务检测到代次变化后主动重建监听,在新 IP 上恢复服务。
```mermaid
sequenceDiagram
@@ -240,7 +271,7 @@ sequenceDiagram
participant CFG as net_cfg
participant NT as netTask
participant NS as net_socket
participant SVC as FTP/对时/远程显示
participant SVC as FTP/远程显示/对时(SNTP 出向)
UI->>CFG: 保存行 ENTER → net_cfg_save()(写盘)
CFG->>NT: net_cfg_request_apply() → 回调置 SYS_EVT_NET_RECONFIG
@@ -250,12 +281,12 @@ sequenceDiagram
SVC->>SVC: 轮询发现代次变化 → 关闭旧监听 → 重建监听(新 IP)
```
**三个服务的自愈点**:
**各服务的自愈点**:
| 服务 | 端口 | 检测位置 | 自愈方式 |
|---|---|---|---|
| FTP | 21 | `lftpd_inet_accept()` | 代次变化 → 返回 -1 → `lftpd_start` 外层循环重建监听 |
| 对时 | 8888 | `time_sync_task()` 的 accept 循环 | 代次变化 → 关旧监听 → 回外层重建 |
| 对时(SNTP 客户端) | UDP 出向(无监听) | `time_sntp_run(net_gen)` 每轮比对代次 | 代次变化 → 弃旧 Socket 归还 → 下轮到点懒重建(2026-11-04 起原 :8888 监听行随退役删除) |
| 远程显示 | 7003 | `ui_net_sink_poll()` | 代次变化 → 关旧监听/客户端 → 重建 |
**关键设计点**:
@@ -302,7 +333,8 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| 文件 | 职责 |
|---|---|
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 配置核心:RAM 副本 + 工厂默认 + 加载/保存编排 + MAC 影子 + IPv4 格式化([network] 段注册) |
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 配置核心:RAM 副本 + 工厂默认 + 加载/保存编排 + MAC 影子 + IPv4 格式化([network] 段注册;`net_cfg_load()` 一并注册 [ftp]/[sntp]) |
| `Drivers/BSP/NETCFG/sntp_cfg.h/.c` | [sntp] 段核心:RAM 副本 + 工厂默认 + 注册 + `poll_min` 带外自愈出口(`App/sync/time_sntp.c` 每轮只读) |
| `App/cfg/cfg_file.h/.c` | 通用配置文件引擎(双端共享核心:解析/校验/外科保存/模板/沉降轮询) |
| `App/cfg/cfg_file_store.h` | 存储后端接口(带路径的文件操作) |
| `App/cfg/cfg_file_store_mcu.c` | MCU 后端:FatFS `/config.ini`(`/config.TMP` + `f_rename` 原子写) |
@@ -156,6 +156,7 @@ mac=00:00:00:00:00:00
| `App/util/sys_clock.c/.h` | 修改 | MCU | 新增 `sys_clock_get_time()`(日历时间获取器);改归 ④中间件层,自 `App/` 移入 `App/util/` |
| `Drivers/BSP/NETCFG/net_cfg.h/.c` | 重构 | 双端 | `net_cfg_*` API 不变;load/save 走 cfg_file;注册 `[network]` 段 |
| `App/protocol/iec104/iec104_comm.h/.c` | 新增(2026-10-26 道G task-8) | 双端(模拟器编译但无消费路径) | 配对模块注册 `[comm]` 段(104 ca/端口,ca 1..65535 / port 1024..65535 合法带 getter 自愈);内核前 `iec104_comm_init()` 注册(同 [adc_cal]/[run_par] 先注册后 load 契约);生效 = 改后重启(键表与理由 → `docs/子系统设计/网络配置说明.md` §3.6);**不走 scripts 白名单**——`.c` 按 new-source-file 口径同笔进 uvprojx + `simulator/CMakeLists.txt` |
| `Drivers/BSP/NETCFG/sntp_cfg.h/.c` | **新增**(2026-11-04 板端 SNTP 接入) | 双端(模拟器编译但无 sync 消费路径) | 注册 `[sntp]` 段(enable / server1 / server2 IPv4 字面量 / poll_min 1..1440 getter 带外自愈,iec104_comm 先例);`net_cfg_load()` 内与 [ftp] 同编排注册;消费方 `App/sync/time_sntp.c`(MCU 单端链接);键表与生效口径 → `docs/子系统设计/网络配置说明.md` §3.7;new-source-file 口径 uvprojx + `simulator/CMakeLists.txt` 双登记 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c` | **已删除**(2026-10-10;曾于 P3 新增) | — | 旧 NCFG blob 解析 + 迁移;随"不保留旧档兼容"整体移除(见 §9.1) |
| `Drivers/BSP/NETCFG/net_cfg_store.h` / `net_cfg_store_mcu.c` | **删除** | — | 由 `cfg_file_store.*` 取代 |
| `simulator/sim/net_cfg_store_sim.c` | **删除** | 模拟器 | 由 `cfg_file_store_sim.c` 取代 |
@@ -589,3 +590,4 @@ void adc_cfg_init(void)
| 2026-10-04 | **P0-1 真机独立复跑 PASS(二次确认,代码零改动)**——`@build` 0/0(增量,无源文件新于镜像)→ `@test` **VERDICT PASS(rc=0)**:`[2026-10-04 00:01:21][CFG] load: OK (523B, heap free 8840B)` + `[NET_CFG] load: OK ip=192.168.1.100`,Forbidden `FAIL`/`ERROR`/`HardFault`/`[ERR]` 零命中,`selftest: no fault record`(先清掉 2026-10-02/03 故障注入窗口遗留的 67 条台账)→ ST-LINK 全片回读 `No difference found`(`0x08000000~0x08040F9B`)。**本次为 `待处理内容.md` §4.1「待验证」唯一未闭环项(P0-1)的收口**,该节现已清空 |
| 2026-10-10 | v1.14:**删除旧档 NCFG 迁移(D10 撤销)**——`Drivers/BSP/NETCFG/net_cfg_legacy.{c,h}` 整体移除、`cfg_store_legacy_path()` 撤销(MCU / 模拟器后端与主机单测 `test/host/cfg_file/cfg_file_test.c` 同步删除),`net_cfg_load` 不再调用 `net_cfg_legacy_migrate()`;旧档 `/cfg/netcfg.bin` 若存在则被**忽略**(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 **+600 B**(该链整体 5312 → 2776 B,另三个杠杆见 `docs/内存管理/RAM使用分析与优化方案.md`),而本仓库不存在携带旧档的设备。连带影响:`App/util` 的 CRC-16/MODBUS 失去唯一调用者(现为零调用者);模拟器 `--cfgtest` 不再覆盖迁移场景;§2 D10 / §3.5 / §4.2 / §5.1 / §6.2 / §6.4 / §7 / §9 / §11.1 同步 |
| 2026-10-26 | v1.15:**新增 `[comm]` 段(道G task-8,「UI 补齐」波次里的非 UI 前置项)**——`App/protocol/iec104/iec104_comm.h/.c` 配对模块(104 公共地址 / 监听端口 2 键,getter 合法带自愈,adc_cal 先例);内核前 `iec104_comm_init()` 注册([adc_cal] / [run_par] 同「先注册后 load」契约);生效 = **改后重启**(不挂回调、不新增事件位,区别于 [network] 免重启重建);段占位 5→6 / 8,键表与主站地址不做理由 → `docs/子系统设计/网络配置说明.md` §3.6;机器钉 = `test/host/cfg_file` TC-CONFIG-056/057(段/键上限)+ `test/host/iec104` TC-IEC104-031~033(模板/重载/带外/ca 端到端)
| 2026-11-04 | v1.16:**新增 `[sntp]` 段(板端 SNTP 客户端接入,#57 调研结论落地)**——`Drivers/BSP/NETCFG/sntp_cfg.h/.c`(enable / server1 / server2 / poll_min 4 键;出厂默认**关闭 + 服务器留空 + 60 分钟** = "未配置零网络足迹"口径;`poll_min` 带外自愈 1..1440 → 60 不写回 RAM);数据本体 = `Drivers/BSP/NETCFG/sntp_cfg.h/.c`(不依赖引擎头,分层门禁 layer-deps 口径),段描述符由 `net_cfg.c` 的 `s_sntp_register()` 组装,注册编排 = `net_cfg_load()`(与 [ftp] 同编排,先注册后 load);消费方 = `App/sync/time_sntp.c`(挂 timeSyncTask 既有循环,vendor `Lib/coreSNTP` serializer 做 48B 报文编解码/防欺骗校验);保存立即生效 = 每轮直读 RAM 副本 + 网络代次重建弃 UDP socket 重散布,**无段回调、不新增事件位**;段占位 6→7 / 8;键表与生效细则 → `docs/子系统设计/网络配置说明.md` §3.7;机器钉 = `test/host/net_cfg` TC-NETCFG-005/006b..d/007(段注册契约)+ `test/host/sntp`(RFC 4330 §10 节奏 / §5.3 防欺骗 / §5.5 步进策略 / poll 钳位)
@@ -643,7 +643,7 @@ LPN 与原始码的位置**就地分类并打印(`ftl_err_name()` 给可读名
| dhara 错误 | 归类 | 处置上为什么不同 |
|---|---|---|
| `ECC` / `TOO_BAD` | `BSP_ERR_CRC` | 位翻转不可纠或页太坏:**是页级问题**,读重试 / 由 dhara 换页,不代表卷坏 |
| `ECC` / `TOO_BAD` | `BSP_ERR_CRC` | 位翻转不可纠或页太坏:**是页级问题**,读重试 / 由 dhara 换页,不代表卷坏(**但见下方 10.4.1:页级失败会因重试语义升级成整卷禁用**) |
| `JOURNAL_FULL` / `MAP_FULL` | `BSP_ERR_NO_SPACE` | 介质还健康,是 **journal / 映射层块池见底**:该走容量恢复(`../测试说明/专项验证/存储维护与容量恢复.md`),此时重格式化只是把问题推回去 |
| `BAD_BLOCK` / `RECOVER` / `NOT_FOUND` / `CORRUPT_MAP` / 越界 | `BSP_ERR_IO` | 映射或介质**完整性**故障:保卷 + 上报,不许自愈重格式化 |
@@ -651,7 +651,80 @@ LPN 与原始码的位置**就地分类并打印(`ftl_err_name()` 给可读名
checkpoint(`DHARA_E_TOO_BAD`)才算真空白卷",本表则保证失败**可读**——两者合起来
才是"别把瞬态毛刺当卷损坏去 `f_mkfs`"(旧行为:单次 I2C/SPI 抖动 → 清 map → 整卷
重格式化 → 静默销毁全部用户数据)。
- **仍未闭环**:分类目前**只进日志**;`disk_read`/`disk_write` 对 FatFs 仍只能回 `RES_ERROR`
(那是 FatFs 的契约)。要让 defaultTask 的卷健康探针**按类分支**,还需要一个跨调用
存活的失败类快照(`nand_ftl_last_error()`,8 B;代价是要先定多写者纪律——
defaultTask 与 testTask 都会碰存储)→ 即登记簿条目「可选全序列卷锁」的待还项。
- **已闭环(2026-11-04,问题 #79)**:分类不再只进日志。`nand_ftl.c` 新增跨调用
失败快照 `nand_ftl_fault_get()` / `nand_ftl_fault_clear()`(`nand_ftl_fault_t`,
20 B:`active`/`op`/`cls`/`lpn`/`count`),在 `disk_initialize` 的 resume 失败、
`ftl_read_page`、`ftl_flush_cache`、`disk_ioctl(CTRL_SYNC)` 四个失败点记账;
defaultTask 的卷健康探针在 `storage_bring_up()` 里读它并按类分流:
`BSP_ERR_NO_SPACE` → **跳过重格式化**(只上报 `storage full`,留待容量恢复),
其余类 → 才 reformat。多写者纪律:语义是"最近一次"而非累计账本,并发下可能被
另一路覆盖(诊断用途可接受),故不加锁、不做权威计数。
### 10.4.1 「页级 TOO_BAD」为什么会升级成「整卷禁用」(2026-11-04 台架实证,问题 #79)
本表上一版只写了「`TOO_BAD` 是页级问题,不代表卷坏」。这句话在**单次读写**的尺度上
成立,但在**连续重试**的尺度上会误导——台架现场正是这条误解造成的:
```
[NAND_FTL] Map resumed: 14094/97504 sectors used ← 映射本身健康
[ERR][NAND_FTL] Cache flush FAIL: LPN=211 dhara=TOO_BAD class=-7
[ERR] [FS] volume FAT invalid (fr=1), reformatting...
[ERR] [FS] volume repair failed (fr=1)
[ERR] [FS] f_mount failed (fr=1) ← 3 轮重试
[ERR] [FS] storage retries exhausted (fr=1), storage disabled
```
两个独立的放大机制叠在一起:
1. **`ftl_flush_cache()` 失败不清 `s_cache_dirty`**(刻意的:写不进去不能假装写成功)
⇒ 缓存里那一页仍是脏的,**下一次任何存储访问都会重试同一个 LPN**。15 条
`Cache flush FAIL` 就是这么来的,11 秒内撞 15 次。
2. **`nand_ftl_format()` 旧实现先 flush、失败即 `return GD5F_ERROR`** ⇒ 本应"销毁
映射重建卷"的恢复动作,被**同一页**在第一步挡住,`dhara_map_clear()` 从未执行;
接着 `f_mkfs` 在**旧映射**上跑,必然失败。每轮启动都重新踩同一条路,故
**重启不恢复**。
- **判据修正**:`TOO_BAD` 仍归类 `BSP_ERR_CRC`(归类没错),但"不代表卷坏"必须加上
限定——**不代表介质坏,不代表卷内容好**。单个 LPN 反复写不进去,配合"脏缓存不清 +
格式化被挡"这两条,足以让整卷在本次启动内不可用。
- **处置修正**:`nand_ftl_format()` 现在尽力刷一次缓存,失败则**丢弃缓存并继续**
`dhara_map_clear()`——格式化语义本就是销毁整卷映射(见 `nand_ftl.h` 该函数注释
「破坏性」),为一份注定作废的脏页挡住恢复动作没有任何收益。⚠ 该"丢弃"权限
**只在 format 内**;`disk_read`/`disk_write` 的 flush 失败必须继续向上报 `RES_ERROR`。
- **与 10.4 表的关系**:格式化的**该不该做**由失败类决定(§10.4 快照分流),
格式化的**做得成做不成**不再被脏缓存劫持(本节)。两者一起才是完整判据。
### 10.4.2 上游真凶定案:误标坏块被持久化(2026-11-04 真机闭环,问题 #79)
10.4.1 的两条放大机制修掉后真机**并未恢复**——`format` 这次确实清了 map,但紧随的
`Map sync after clear failed: TOO_BAD` 说明**全新的 map 也落不了盘**。逐层加临时
诊断(prog/erase 原语失败打印 → is_bad 命中打印 → BBT 合并后打印)后定案:
| 观测 | 结论 |
|---|---|
| `[NAND] BBT scan: 0 bad blocks found` | 出厂无坏块 |
| prog/erase 原语 `[DIAG]` **零输出** | 底层写**全部成功**,TOO_BAD 不来自当次 I/O 失败 |
| `BBT: 129/2048 blocks bad`(合并持久化 BBT 后) | 坏块全部来自**持久化 BBT 的 OR 叠加** |
| 误标是 1016–1142 **连续段**;`Map head=65024`>>6 = **1016** 正中段首 | 不是随机位老化形态 |
| `is_bad hit` 连续命中 1016..1142 | `prepare_head()` 连续 8 次撞坏 → `DHARA_E_TOO_BAD`(journal.c:820-832) |
**机理链**:某次台架操作(断电 / ST-LINK reset 打在写窗口内)造成**传输层/等忙失败**
→ 旧 `dhara_nand_erase/prog()` 把一切失败都 `dhara_nand_mark_bad()` →
`gd5f2gq5ue_mark_block_bad()` **每次都立即持久化 BBT**(轮转池 + 版本号 + CRC,
不可撤销)→ 误标固化 → 此后每轮启动 journal 写头爬进误标段即 TOO_BAD。这解释了
「11-03 20:54 还好的、11-04 起每轮必现、重启不恢复」的全部特征。
- **修复**:只有芯片明确回 `E_FAIL` / `P_FAIL`(状态位证据)才标坏;传输层/超时
只报 `DHARA_E_BAD_BLOCK` 让 dhara 重试,**不标坏**。回归用例
TC-NANDPORT-207/208/505(主机侧,178 条全绿;把修复回退即见红)。
- **哨兵**:`gd5f2gq5ue_init()` 解锁块保护(A0h)后补回读校验(与 B0h ECC 同族
缺口;实测本板 A0h=0,写保护非本次根因,校验留作哨兵);`gd5f_bbt_load()` 合并后
坏块数 > 0 且出厂扫描为 0 时打 `BBT after merge: %lu bad blocks (factory scan
was 0)`——现场一眼区分真坏与误标。
- **存量误标的处置**:代码修复只防未来。已固化的 129 个误标用恢复动作 A
(`TEST_SUITE_STORAGE` + `ENABLE_STORAGE_RECOVERY_TESTS`,重建 BBT + format)
清除:`BBT rebuilt and persisted` → `Storage Recovery: 3/3 PASSED` →
持久化 BBT version=71。产品镜像复烧后 `[FS] FATFS mounted`、
`[CFG] load: file missing, factory template generated`,0 条 flush FAIL。
⚠ 动作 A 语义 = 卷内容全失(旧卷随 map clear 作废),本次台架卷为可弃数据。
@@ -42,7 +42,7 @@
| `ftpTask` | 1024 / 4096 | Normal | 等网络**且**存储(`WaitAll`)→ `lftpd_start("/", 21)` → 单客户端会话循环 |
| `testTask` | 2048 / 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**(未选套件时立即 `vTaskDelete` 回收其 8 KB CCM 栈) |
| `uiTask` | 4096 / 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60 s 切待机屏)/`ui_net_sink` → 10 ms 刷新 + 数据注入 |
| `timeSyncTask` | 640 / 2560 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC |
| `timeSyncTask` | 640 / 2560 | Normal | 等网络 → 200 ms 校时节拍 `time_sntp_run()`(SNTP 客户端 UDP 出向 → 写 RTC;:8888 推送监听已退役 2026-11-04) |
| `adcTask` | 640 / 2560 | AboveNormal | **采集主线唯一消费者**:每拍 `adc_input_poll()`(TPAFE 快照 + ADC1 DMA 帧)→ `adc_health` 有效性 → `phasor_run()` 相量 → `freq_run()` 测频 → 每 200 ms `meas_tick()` 发布遥测快照 |
| `iec104Task` | 640 / 2560 | Normal | 等网络 → TCP 工厂默认 :2404 监听 → 104 链路状态机(总召/变位/周期/遥控/定值/对时)+ 断链重听 + 网络代次自愈 |
| `protectTask` | 512 / 2048 | Normal | **20 ms 判据节拍**:采样故障闭锁判定 → 定值校验和巡检 → `prot_elm_run()` 元件判据 → 动作边沿投递 SOE(**不做 NAND I/O**) |
@@ -115,7 +115,7 @@ sequenceDiagram
N-->>F: set SYS_EVT_NET_READY
F->>F: wait NET_READY|STORAGE_READY(All|NoClear)→ lftpd_start("/", 21)
U->>U: wait NET_READY(Any|NoClear)→ ui_port_init → ui_menu_init → ui_net_sink_start(7003)
I->>I: wait NET_READY(Any|NoClear)→ TCP listen :2404(默认)/ :8888
I->>I: wait NET_READY(Any|NoClear)→ TCP listen :2404(默认)/ 200ms SNTP 校时节拍
```
> **顺序铁律**:① 配置段注册必须**先于** `cfg_file_load`(否则 FTP 上传的段不生效);
+6 -6
View File
@@ -43,9 +43,9 @@
| 录波链路(三任务交接) | adcTask `wave_ring_push()/tick()`(与相量主线同拍**并行**、互不阻塞)→ 触发冻结、后触发计满封段 → defaultTask 见 `wave_ring_sealed()` 游标分次搬进 `/wave` → uiTask 单通道回放页;落盘回执才解冻。取回 = FTP :21 |
| SOE 链路 | protectTask 动作/复归边沿 `soe_store_post()`(纯 RAM:取时标 + 组 64B 记录 + 队列,**零文件系统**)→ defaultTask(`/log` 唯一写者,`syslog_drain` 尾部挂接)落 `/log/soe.log` |
| `lftpd` / `net_cfg` / 测试 → `FatFs` → `nand_ftl` → `gd5f2gq5ue` | 文件、配置、录波段与 SOE 落 NAND |
| `ui_net_sink` / `time_sync` → `net_socket` | 远程显示 :7003 / 对时 :8888 共用同一 TCP 栈;屏板链路走 USART1 串口(不经 TCP),与 :7003 是同一份显存的两个出口(**串口为主**) |
| `ui_net_sink` / `time_sync` → `net_socket` | 远程显示 :7003 走 TCP;对时 = `time_sntp` 经同一消息队列走 **UDP 出向**(:8888 监听已退役,2026-11-04,墙钟说明 §15);屏板链路走 USART1 串口(不经 TCP),与 :7003 是同一份显存的两个出口(**串口为主**) |
| `ui_menu` → `ui_content_page` → `ui_page_*` | 框架 + 页面插件(描述符解耦;节点→页面绑定由注册表 `ui_page_registry.c` 生成,见 §6) |
| `time_sync` / `sys_clock` → `sd2506` | 墙钟基准与写回 RTC |
| `time_sntp` / `sys_clock` → `sd2506` | 墙钟基准与写回 RTC(对时写钟入口,:8888 退役后唯一网络来源) |
| `ui_port` / `ui_page_*` → `LVGL` | UI 全部绘制走 LVGL(1bpp 离屏渲染到内存帧缓冲) |
### 1.3 端到端链路鸟瞰(另一视角:一个电气量走到底)
@@ -72,7 +72,7 @@
| `ftpTask` | 4096 | Normal | 等网络**且**存储(`WaitAll`)→ `lftpd_start("/", 21)` → 单客户端会话循环(断开后经 `net_listener` 重建监听);录波段文件的取回通道 | `StartFtpTask` |
| `testTask` | 8192 | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**:CubeMX 生成区无条件建后,`MX_FREERTOS_Init()` 在未选套件时(`!TEST_SUITE_SELECTED`)立即 `vTaskDelete` 回收其 8KB CCM 栈(2026-10-04) | `start_test_task` |
| `uiTask` | 16384 | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60s 切待机屏)/`ui_net_sink` → 10ms 刷新:`ui_data_feed_tick()` 三帧装配 + 屏板链路驱动(组帧 B / 出键 / 离线判定 / 状态灯位)+ 录波回放页 | `ui_task_func` |
| `timeSyncTask` | 2560 | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC | `time_sync_task` |
| `timeSyncTask` | 2560 | Normal | 等网络 → 200 ms 校时节拍 `time_sntp_run()`(SNTP 客户端,UDP 出向;写 RTC 经 `sys_clock_set_unix`。:8888 推送监听已退役 2026-11-04) | `time_sync_task` |
| `adcTask` | 2560 | AboveNormal | 纯消费者 + **唯一发布者**:`osThreadFlagsWait` 等 TIM3 1kHz 唤醒(ISR 内 `adc_tick_irq_notify` 置位)→ `adc_input_poll()`(TPAFE 原子快照 + STM32 ADC1 DMA 双缓冲)→ 标定折算 → 相量 → 测频;录波支路 `wave_ring_push()/tick()`;**每 200ms `meas_tick()` 发布遥测快照**(双区无锁,104 与 UI 共用同一份上游) | `adc_task_func` |
| `iec104Task` | 2560 | Normal | 等网络 → IEC 60870-5-104 上行(工厂默认 :2404 监听 + link 状态机;遥测读 meas 快照、遥信读 `prot_elm` 元件态,COV 死区/变位上送;`[comm]`/`[run_par]` 配置;IEC 104 上行/第二轮)。任何构建都创建,纳入 IWDG `required_mask` | `iec104_serv_task` |
| `protectTask` | 2048 | Normal | 保护判据 20ms 节拍:`prot_elm_run()` + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检 + 动作/复归边沿 SOE 投递(保护任务);⚠ **当前处于「未标定闭锁」态**——`adc_cal_flags()` 无 VALID 时判据停走,两点标定写入 `[adc_cal]` 后自动解卡(用户裁定 2026-10-25:保持闭锁,不用万用表走捷径);纳入 IWDG `required_mask`(产品 `0x1F7`) | `protect_task` |
@@ -155,7 +155,7 @@ sequenceDiagram
F->>F: wait NET_READY|STORAGE_READY(All|NoClear)→ lftpd_start("/", 21)<br/>→ 串口输出:waiting for connection...
U->>U: wait NET_READY(Any|NoClear)→ ui_port_init → ui_menu_init → ui_net_sink_start(7003)<br/>→ 10ms 循环:ui_data_feed_tick + 屏板组帧/出键/灯位
T->>T: wait NET_READY(Any|NoClear)→ TCP listen :8888
T->>T: wait NET_READY(Any|NoClear)→ 200ms 校时节拍 time_sntp_run()(SNTP 客户端 UDP 出向)
I->>I: wait NET_READY(Any|NoClear)→ IEC104 listen 默认 :2404(遥测读 meas 快照 / 遥信读 prot_elm)
P->>P: 20ms 判据节拍 prot_elm_run()(不依赖网络;未标定即闭锁停走)+ SOE 边沿投递
C->>C: wait TIM3 1kHz 唤醒(ISR adc_tick_irq_notify)→ adc_input_poll() 纯消费<br/>→ 标定→相量→测频 → wave_ring_push/tick → 每 200ms meas_tick() 发布快照
@@ -217,7 +217,7 @@ flowchart TB
NS --> Q["netMsgQueue<br/>(串行化到 netTask)"]
Q --> NT["netTask<br/>net_poll() + net_process_messages()"]
NS --> C1["lftpd<br/>FTP :21"]
NS --> C2["time_sync<br/>对时 :8888"]
NS --> C2["time_sync 任务<br/>time_sntp SNTP 客户端<br/>UDP 123 出向(:8888 已退役)"]
NS --> C3["ui_net_sink<br/>RemoDispBus :7003"]
NS --> C4["iec104_serv<br/>104 上行默认 :2404"]
C3 -.->|"回送 1bpp 帧 + 接收远端按键"| CLIENT["DTU-RemoteLCD 客户端"]
@@ -321,7 +321,7 @@ STM32F4-Base/
│ ├── cfg/ 配置引擎:cfg_file(静态态单任务收敛于 defaultTask)、store(MCU=FreeRTOS 堆)、
│ │ prot_set_store(/prot_set.bin 原子写,保护定值持久化)
│ ├── fs/ 卷锁 vol_lock(syslog / cfg / lftpd 三条路径 try 语义)、vol_idle_sync
│ ├── sync/ time_sync(TCP 对时服务)
│ ├── sync/ time_sync(timeSyncTask 载体)、time_sntp(SNTP 校时客户端,UDP)
│ └── util/ sys_clock(墙钟)、task_watchdog(IWDG 判定状态机)、boot_stats、sha256、task_prof
├── Drivers/BSP/ 纯手工驱动/服务(不受 CubeMX 保护)
│ ├── CH395F/ 以太网 MAC+PHY(SPI2 + DMA)
@@ -1,49 +1,22 @@
# ADC 前端注入验证流程(台架)
> **这篇是什么**:已知电压注入验证的**执行侧**——判据与实测记录在 `docs/硬件平台/ADC通道映射说明.md` §8.1,
> 这边写怎么接表笔、读哪一行日志、填哪张表、结果往哪回写。
> **这篇是什么**:已知电压注入验证的**执行侧**——怎么写表笔、读哪行日志、填哪张表、结果往哪回写。判据与实测记录在属主文档:直流轮 `docs/硬件平台/ADC通道映射说明.md` §8.1、交流轮 `docs/任务规划/数据可信.md` §4.6.2;**本文只写怎么跑**,分叉时判据从属主。证不到什么见 `docs/任务规划/数据可信.md` §4.6.1 与 §4.6.5,本文不重抄。
>
> **本文分两个轮次**:§1~§7 = **直流轮次**(零点 / 线性 / 通道映射 / 极性,2026-10-27 六项 PASS,
> 证的是"一条水平线的高度";**不需要**示波器、信号源、标准源,**只需要**一块万用表 + 一个电位器 + 串口日志)。
> §8 = **交流(正弦)动态验证轮次**(六轮:幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波,证的是"给定电压算得对不对、品质报得实不实、节拍稳不稳")——
> 一台信号发生器接在本文 §3 的同一批注入点上就能跑,**同样不用等标准源**;
> 等标准源的只是**精度等级**那一步(`docs/任务规划/数据可信.md` §5 的「两点标定与精度验收」)。
> V0.1 有五路单极性削顶(`UB/UC/IB/IC/IN`),它们在交流轮次里只能作"预期削顶"的负向用例。
>
> **判据的属主是 `docs/任务规划/数据可信.md` §4.6.2**(六轮各验什么、过判据是多少),本文 §8 只写**怎么跑**。
> 两边于 2026-10-29 对齐;若将来再分叉,分两类裁定:**「验什么、算不算过」从
> `docs/任务规划/数据可信.md` §4.6.2**,**「期望值怎么算、怎么接线怎么跑」从本文 §8**。
> 这一跳为什么不用等标准源、又证不到什么,见其 §4.6.1 与 §4.6.5,本文不重抄。
---
> **两个轮次**:§1~§7 = **直流轮次**(2026-10-27 六项 PASS,只需万用表 + 电位器 + 串口日志);§8 = **交流轮次**(幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波),一台信号发生器接 §3 同一批注入点就能跑,等标准源的只有**精度等级**。V0.1 五路单极性削顶(`UB/UC/IB/IC/IN`)在交流轮次只作"预期削顶"负向用例。
## 1. 为什么非做不可
下游已经全部按"工程量"消费:保护判据、104 遥测、UI 交流量页读的都是 `adc_cal_apply()` 的输出。系数错
一倍,三处一起错,且**没有任何一处会报错**。"通路活"(9 通道转换成功、零超时、互异非饱和、跨次开机
读数不同)不证明读数对。所以这一步的性质是**给整条链路的量纲盖章**,不是"补充测试"。
下游全部按"工程量"消费(保护判据、104 遥测、UI 交流量页读 `adc_cal_apply()` 输出)。系数错一倍,三处一起错,**没有任何一处会报错**。这一步是**给整条链路量纲盖章**,不是"补充测试"。
## 2. 前置事实:这个前端不是分压器
2026-10-27 按 `docs/硬件平台/V0.1.tel` 逐通道追线,以 UA 为例(图由
`tools/arch_diagram/gen_adc_frontend_svg.py` 生成,改图请改脚本重跑,勿手改 SVG):
2026-10-27 按 `docs/硬件平台/V0.1.tel` 逐通道追线(图由 `tools/arch_diagram/gen_adc_frontend_svg.py` 生成,改图改脚本重跑):
![UA 通道前端:端子 → R31 → ADC7 → TPAFE AIN_7P;C44 / R32 在差分负端,不在信号通路上](../../images/ADC前端电路.svg)
`ADC7` 这个节点上**只有** R31、C44、U3.61 三样东西,**没有对地电阻**。所谓"1k 到地"是 R32,
它接在 TPAFE 的**负输入脚 AIN_7GND**(芯片手册 pin62)上,是把差分负端经 1k 拴到 AGND,
属于差分滤波,不是信号分压。STM32 侧更直接(图 B):`AIN2 —[R52 1k]— ADC17 —[C111]— AGND`,
同样没有分流电阻,连 R32 那个接地电阻都没有。
`ADC7` 节点只有 R31、C44、U3.61,**无对地电阻**;"1k 到地"是 R32 接 TPAFE 负输入脚 AIN_7GND,属差分滤波非信号分压。STM32 侧同样无分流电阻。结论:① **不存在 2:1 分压**(`docs/任务规划/数据可信.md` §2.1 已改引脚侧系数 152.59 / 805.66),只需确认转换器 LSB 与逐通道映射;② **端子侧斜率 = 引脚侧斜率**,端子侧注入安全且能直接验 LSB。
**由此得到两条可直接用的结论**:
1. **不存在 2:1 分压**。旧口径"板级系数 305.18 / 1611.3 µV/码"(按 2:1 折算到 `AINn` 侧的条件值)
在本板上不成立;`docs/任务规划/数据可信.md` §2.1 已于 2026-10-27 改为引脚侧系数(152.59 / 805.66)。
所以已知电压注入验证只需**确认转换器 LSB 与逐通道映射**,不必再判别两种读法。
2. **端子侧斜率 = 引脚侧斜率**。所以在**端子侧**注入既安全(有 1k 串阻护着芯片)又能直接验 LSB,
不必去碰 LQFP 引脚。
期望斜率(由转换器满量程事实推出,与前端无关):
期望斜率(由转换器满量程推出):
| 转换器 | 满量程 | LSB | **期望斜率** |
| ---------- | ----------------------- | ---------------- | -------------- |
@@ -52,7 +25,7 @@
## 3. 注入点清单
串联电阻的两只焊盘就是两个注入层级:**1 脚 = 端子侧(靠 U5)**,**2 脚 = 引脚侧(靠转换器)**。
串阻两只焊盘 = 两个注入层级:**1 脚 = 端子侧(靠 U5)**,**2 脚 = 引脚侧(靠转换器)**。
| 语义 | 串阻 | 端子侧节点 | 引脚侧节点 | 转换器脚 | 期望斜率 |
| -- | --- | --------------- | ------- | ----------- | ------------- |
@@ -66,60 +39,39 @@
| IN | R48 | `AIN11`(U5.C10) | `ADC11` | U17.36(PA2) | 1241.2 |
| 温度 | R47 | `V_ZL2`(U5.C11) | `ADC14` | U17.41(PA5) | 不验(非线性,不参与标定) |
> 引脚侧没有串阻保护,注入前**必须外串 10 k 限流**。除非端子侧结果对不上、需要分离
> "转换器的错"和"前端的错",否则不要用引脚侧。
> 引脚侧无串阻保护,注入前**必须外串 10 k 限流**;除非端子侧对不上需分离"转换器的错"和"前端的错",否则不用引脚侧。
## 4. 读数出口(固件只有下面这些周期行,没有命令控制台)
## 4. 数字能从哪儿读(固件只有下面这些周期行,没有命令控制台)
> **样本出口已落地(代码侧,2026-10-29)**:故障录波的数据面已进仓(格式与判据属主见 docs/子系统设计/故障录波设计说明.md §6.2 与 §6.3、板级判据见 §10、口径变更见该文 §11;原卡片登记簿 2026-10-30 销项并入后删除)——每次触发一段,段文件直接给出 8 路 × 320 点原始码值,动态验证的判据因此**可以**从「比对下表这些派生标量」升级为「PC 独立重算 vs 板上读数」对表。下表仍是固件唯一的**周期行**出口(段文件不是周期行,只在触发时产生),两种出口对表用。
> **录波段文件能力已落地(2026-10-29)**:每次触发写一段,段文件直接给 8 路 × 320 点原始码值(格式属主 `docs/子系统设计/故障录波设计说明.md` §6.2/§6.3、板级判据 §10),判据可从「比对派生标量」升级为「PC 独立重算 vs 板上读数」互相对表。下表是固件**周期性**打印的全部输出行(段文件只在触发时产生,不在其中),两类数字互为对照。
| 日志行 | 频率 | 内容 | 用途 |
| ----------------------------------------------------------- | ------ | ------------------------ | ------------------- |
| `adc: UA=.. UB=.. UC=.. UN=.. IA=.. IB=.. IC=.. IN=.. T=..` | 每 10 s | **9 路原始码值** | 本次验证的主读数 |
| `cal: ua_mv=.. un_mv=.. ia_mv=.. vl=.. cl=..` | 每 10 s | UA/UN/IA 折算后毫伏 + 标定/削顶位图 | 旁证:码值 → 电压是否自洽 |
| `first: UA=.. … T=..` | 每次开机一次 | 9 路原始码值 | 与 `adc:` 行互校(同序同格式) |
| `frames=.. idle=.. adc1_err=.. tgap=..` | 每 10 s | 采集健康计数 | 步骤 4 的节奏判据 |
| `phasor(hb) C0x-…: Re=.. Im=.. rms=..`(逐实例)、`freq(hb): f=.. valid=..` | 每 10 s | 基波相量分量与测频读数(×0.1 / ×0.01 定点) | **交流轮次的主读数**(直流轮次只作旁证) |
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | 每 10 s | 200 ms 遥测快照轨(基波有效值 mV) | 交流轮次的幅值比对(1 mV 分辨率,比 phasor 行细) |
| **段文件** `/wave/WAVE00.BIN` 起共 100 个号轮转(FTP 根下可见,与 /log 平级) | 每次触发一段(手动 / 保护启动 / bench 定时) | 8 路 × 320 点原始码值 + 封存瞬间的标定与品质快照(布局见 docs/子系统设计/故障录波设计说明.md §6.2) | **样本出口**:交流轮次的主对表源——预触发 160 点 + 后触发 160 点,触发瞬间落在段内点 159 |
| PC 侧脚本 `test/host/wave_read.py`(不是固件日志,bench 工具,纯标准库) | 手动跑;`--selftest` 是它的自带回归 | 四个子命令:dump(逐字段与双 CRC)/ fft(全周傅氏重算基波有效值与相位)/ shape(削顶与形态判定,可 `--strict`)/ plot(出 SVG 或 PNG,PNG 用 `-o` 落文件) | 把段文件还原成周波数、幅值、相位、削顶判定与图 = 「PC 独立重算」这条判据的执行体;CRC 不符或零长度它必须**拒绝**而不是画出东西 |
| 日志行 | 频率 | 内容 | 用途 |
|---|---|---|---|
| `adc: UA=.. UB=.. UC=.. UN=.. IA=.. IB=.. IC=.. IN=.. T=..` | 每 10 s | **9 路原始码值** | **直流轮(§1~§7)主要读的数** |
| `cal: ua_mv=.. un_mv=.. ia_mv=.. vl=.. cl=..` | 每 10 s | UA/UN/IA 毫伏 + 标定/削顶位图 | 旁证:码值→电压自洽 |
| `first: UA=.. … T=..` | 每次开机一次 | 9 路原始码值 | 与 `adc:` 互校(同序同格式) |
| `frames=.. idle=.. adc1_err=.. tgap=..` | 每 10 s | 采集健康计数 | 判节奏用(判据在 §5.5,即步骤 4) |
| `phasor(hb) C0x-…: Re=.. Im=.. rms=..`、`freq(hb): f=.. valid=..` | 每 10 s | 基波相量与测频(×0.1 / ×0.01 定点) | **交流轮(§8)主要读的数**(直流轮只旁证) |
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | 每 10 s | 200 ms 遥测快照轨(基波有效值 mV) | 交流幅值比对(1 mV 分辨率) |
| **段文件** `/wave/WAVE00.BIN` 起 100 号轮转(FTP 根下,与 /log 平级) | 每次触发一段 | 8 路 × 320 点 + 封存瞬间标定与品质快照(`docs/子系统设计/故障录波设计说明.md` §6.2) | **录波档要用的原始样本**:预触发 160 + 后触发 160 点,触发瞬间在段内点 159 |
| PC 侧 `test/host/wave_read.py`(纯标准库) | 手动跑;`--selftest` 自带回归 | 四子命令:dump(逐字段双 CRC)/ fft(全周傅氏重算基波有效值与相位)/ shape(削顶与形态,可 `--strict`)/ plot(SVG/PNG,PNG 用 `-o`) | 「PC 独立重算」执行体;CRC 不符或零长度必须**拒绝** |
> ⚠ 直流轮次用不到后两行,但**它们一直都在**。交流轮次(判据见 `docs/任务规划/数据可信.md` §4.6)
> 必须用 `meas:` 或 104 YC 比幅值,别用 `phasor(hb)` 行——那行是 ×0.1 定点,注 1 V 只剩 10 格。
> ⚠ 交流轮次(判据 `docs/任务规划/数据可信.md` §4.6)必须用 `meas:` 比幅值,别用 `phasor(hb)`——那行 ×0.1 定点,注 1 V 只剩 10 格。
`adc:` 行是 2026-10-27 新增的(`App/task/adc_task.c` 心跳块),此前只有 `cal:` 的三路,
验 8 个通道要复位二十余次。现在**全程不必复位**。
**「码」是什么**:ADC 原始整数,**不是物理量**——电压 = 码 × LSB:
**「码」是什么**:ADC 输出的**原始整数**,中文常写「码值」/「AD 码」,英文对应 raw code / counts。
它表示"多少个最小刻度(LSB)",**本身不是物理量**——电压 = 码 × LSB,而两片转换器的 LSB 不同:
| 转换器 | 位数与极性 | 码的范围 | 1 码 = | 换算示例 |
| 转换器 | 位数与极性 | 码范围 | 1 码 | 示例 |
|---|---|---|---|---|
| TPAFE5160 | 16 位双极性 | −32768 ~ +32767(共 65536 码 = 10 V 跨度) | 152.59 µV | 11430 码 × 152.59 µV ≈ **1.744 V** |
| STM32 ADC1 | 12 位单极性 | 0 ~ 4095(共 4096 码 = 3.3 V 跨度) | 805.66 µV | 1000 码 × 805.66 µV ≈ **0.806 V** |
| TPAFE5160 | 16 位双极性 | −32768~+32767(65536 码 = 10 V) | 152.59 µV | 11430 码 ≈ **1.744 V** |
| STM32 ADC1 | 12 位单极性 | 0~4095(4096 码 = 3.3 V) | 805.66 µV | 1000 码 ≈ **0.806 V** |
同一个整数落在两把刻度尺上代表不同电压(图由 `tools/arch_diagram/gen_adc_code_scale_svg.py`
生成,改图请改脚本重跑,勿手改 SVG):
![两把刻度尺:TPAFE 16 位双极性 1 码 152.59 µV;STM32 12 位单极性 1 码 805.66 µV](../../images/ADC码值刻度.svg)
![两把刻度尺:TPAFE5160 16 位双极性 −32768~+32767、1 码 152.59 µV、11430 码≈1.744 V;STM32 ADC1 12 位单极性 0~4095、1 码 805.66 µV、1000 码≈0.806 V](../../images/ADC码值刻度.svg)
**两把尺子不能混用**(同样"1000"差 5.3 倍)。§5 各表每格必须写明哪一路(UA/UB/… 属 TPAFE,T 属 STM32),抄错一行全盘作废。本文一律用"码"不用"伏":固件 `adc:` 行打印的就是码;**标定还没做,把码换成伏正是本次要验证的事**。判据:**TPAFE ≤ 30 码、STM32 ≤ 5 码**(≈4.6 mV / ≈4.0 mV)。
**两把尺子不能混用**:同样是"1000",在 TPAFE 上是 0.153 V,在 STM32 上是 0.806 V,差 5.3 倍。
所以 §5 各步的记录表里每一格都必须写明是哪一路(UA/UB/… 属 TPAFE,T 属 STM32),
抄错一行全盘结论作废。
⚠ **TPAFE 档 2026-10-27 由 ≤10 放宽到 ≤30**:首次实测残差 UA −14 / UN −15 / IA −13(≈2 mV)。原 ≤10 比实测噪声还紧,必然误判。残差若 >30 码 → 前端有真实失调,回前端查,不得再放宽。
本文一律用"码"而不是"伏",有两个理由:① 固件 `adc:` 行打印的就是码,抄数不用心算;
② **标定还没做,把码换成伏正是本次要验证的事**——先换算再用,等于默认了尚未验证的系数。
判据按转换器分两档:**TPAFE 侧 ≤ 30 码、STM32 侧 ≤ 5 码**——即短接后读数离 0 不超过
30 / 5 个刻度,折算 **≈4.6 mV / ≈4.0 mV**。两档数值差 6 倍是因为两种刻度差 5.3 倍
(TPAFE 152.59 µV/码、STM32 805.66 µV/码),统一到物理量后都在 4~5 mV 量级。
⚠ **TPAFE 档于 2026-10-27 由 ≤10 放宽到 ≤30**:首次实测三路残差 UA −14 / UN −15 / IA −13
(≈2 mV),按 16 位 /10 V 跨度算只有 0.02% FS。原来的 ≤10(≈1.5 mV)定判据时**没有实测依据**,
比实测噪声还紧,必然误判失败。**这不是"把判据改成能过"**——若残差继续增大(>30 码 / ≈4.6 mV),
说明前端确有真实失调(R32 侧接地断、TPAFE 失调),那时必须回前端查,不得再放宽。
⚠ `adc:` 行某路取数失败时该槽打 0,所以"9 路全 0"要读作"没数据",判据是同一份心跳里
`frames` 是否还在涨。
⚠ `adc:` 行某路取数失败时该槽打 0,"9 路全 0"读作"没数据",判据看同一心跳 `frames` 是否还在涨。
## 5. 步骤
@@ -129,25 +81,17 @@
| 量哪里 | 期望 | 验的是什么 |
|---|---|---|
| **串阻两端**(1 脚 ↔ 2 脚,即端子侧焊盘 ↔ 引脚侧焊盘) | **≈ 1 kΩ** | 串阻焊好、没虚焊 |
| **注入点 ↔ AGND**(TPAFE 侧,UA/UN/IA) | **≈ 1 MΩ 量级** | 无意外短路;**不是 1 kΩ** |
| **注入点 ↔ AGND**(STM32 侧,UB/UC/IB/IC/IN) | 高阻,不定量 | 无设计直流通路,只作短路排查 |
| **串阻两端**(1 脚 ↔ 2 脚) | **≈ 1 kΩ** | 串阻焊好、没虚焊 |
| **注入点 ↔ AGND**(TPAFE 侧 UA/UN/IA) | **≈ 1 MΩ 量级** | 无意外短路;**不是 1 kΩ** |
| **注入点 ↔ AGND**(STM32 侧 UB/UC/IB/IC/IN) | 高阻,不定量 | 无设计直流通路,只作短路排查 |
### 5.2 步骤 1:零点(这一步是整套测试的分水岭)
### 5.2 步骤 1:零点(这一关先过:零点都不干净,后面每一步的数都不可信)
**做什么**(8 路逐个重复,每路约 1 分钟):
**做什么**(8 路逐个,每路约 1 min):断电把该路**注入点短接 AGND**(短接线先 0 Ω 档确认直通)→ 上电等 `adc:` 行 → 抄进下表 → 断电拆线换下一路。
1. 断电,把该路**注入点短接到 AGND**(短接线之前先用 0 Ω 档确认该点与 AGND 直通);
2. 上电,等 `adc:` 行出现(每 10 s 一行,9 路码值都在里面);
3. 把该路码值抄进**下面的零点记录表**;
4. 断电拆短接线,换下一路。
**记录**(现场填;判据只看"短接后"列,"空载"列留证):
**记录**(现场填;判据只看"短接后"那一列,"空载"列是留证用):
> ⚠ **本表行序 = `adc:` 日志的列序**(UA UB UC UN IA IB IC IN),照着日志**从左往右**抄,
> 不要按"TPAFE 三路 / STM32 五路"重新分组。2026-10-27 现场实测就栽在这里:按语义分组抄,
> 结果 UN/IA 填成了 STM32 的 ~1000、UB/UC 填成了 TPAFE 的 ~11450——而 UN/IA 属 TPAFE、
> UB/UC 属 STM32,这两个量级在物理上不可能。**"转换器"列就是给这一眼准备的**。
> ⚠ **本表行序 = `adc:` 日志列序**(UA UB UC UN IA IB IC IN),照日志**从左往右**抄,别按"TPAFE 三路 / STM32 五路"分组。2026-10-27 就栽在这里:按语义分组抄,UN/IA 填成 STM32 ~1000、UB/UC 填成 TPAFE ~11450——量级物理上不可能。**"转换器"列就是给这一眼准备的**。
| 通道 | 转换器 | 端子侧节点 | 空载码值(浮空) | 短接 AGND 后码值 | 判据 |
| --- | --- | --- | --- | --- | --- |
@@ -160,26 +104,15 @@
| IC | STM32 | `AIN10`(U5.C9) | 991~1092 | 0 | ≤ 5 |
| IN | STM32 | `AIN11`(U5.C10) | 979~1065 |1~2 | ≤ 5 |
**为什么这一步最关键**:空载(输入浮空)读数比判据高两个数量级,最可能的解释是输入悬空后
漏电流在滤波电容上建立的平衡点——短接 AGND 后应塌回 0。但如果**短接后仍读 11430**,那就是
真实偏置,且超过零点补偿上限 `ADC_CAL_B_MAX`(8192 码)——`adc_cal_set()` 会直接拒掉、
回退 ROM 系数、永远置不了 `VALID`,保护也就永远解不了锁。**那种情况必须回前端查**
(R32 那侧接地是否断、TPAFE 是否失调),不能靠软件补偿。
**2026-10-27 实测:8 路全部 PASS。** 短接后 **0~5 码**(TPAFE ≤4.6 mV、STM32 ≤4.0 mV),远小于零点补偿上限 `ADC_CAL_B_MAX`(8192 码),**可正常标定、不触发回退**。
**2026-10-27 实测结论:8 路全部 PASS。** 短接后落在 **0~5 码**(TPAFE ≤4.6 mV、STM32 ≤4.0 mV),
全部远小于零点补偿上限 `ADC_CAL_B_MAX`(8192 码),**可以正常标定、不会触发回退**。
空载 → 短接的塌陷幅度:TPAFE 约 11430 → 0~4(三个数量级),STM32 约 1000 → 0~5(两个数量级),
两者都证明短接真实到位、输入前端没有意外直流通路。
⚠ **IB 最贴边**:2~5 码正好触上限(1.6~4.0 mV,物理没问题),**八路里余量最小**;日后 IB 零点变大第一个查 R49 那侧(`AIN9` / U5.A8)。
⚠ **IB 最贴边**:2~5 码,上限 5 码正好触到。折算 1.6~4.0 mV,物理上没问题,但**它是八路里
余量最小的一路**;若日后 IB 零点继续变大,第一个该查的是 R49 那侧(`AIN9` / U5.A8)。
### 5.3 步骤 2:三点线性(用 UA,免复位)
### 5.3 步骤 2:三点线性(用 UA 做,免复位)
3.3 V 轨经 10 k 电位器分压作源,逐点注入,**每点同时记两个数**:万用表读注入点电压、`adc:` 行读码值。
3.3 V 轨经 10 k 电位器分压作源,逐点注入。**每点同时记两个数**:万用表读注入点实际电压、
`adc:` 行读对应码值。
**记录**(UA 一路,三点;"目标"列是调电位器的靶子,"实测"列填万用表读数):
**记录**(UA 三点;"目标"列是靶子,"实测"列填万用表):
| 目标注入 | 实测电压(万用表) | 码值 | 反算 µV/码 | TPAFE 期望码 | STM32 期望码 |
| -------- | --------- | -- | ------- | --------- | --------- |
@@ -189,26 +122,17 @@
拟合 R² = 0.9999997 斜率 = 6544.2 码/V 偏差 = −0.14 % 截距 = −7.2 码(≈ −1.1 mV)
反算 LSB = 152.81 µV/码,与 TPAFE 期望 152.59 差 0.14 %——**与斜率偏差同源,是同一个数**。
反算 LSB = 152.81 µV/码,与 TPAFE 期望 152.59 差 0.14 %——**与斜率偏差同源**。期望码两列并列:同样三点也可复验 STM32 侧某路,两列差 5.3 倍,一眼确认表笔接的是哪片转换器。
期望码两列并列,是因为同样的三个注入点也可能拿去复验 STM32 侧某一路(UA 对不上时换路交叉验证);
两列数值差 5.3 倍,一眼能确认手上这支表笔当前接的是哪一片转换器。
判据:三点拟合 **R² ≥ 0.999**、斜率偏差 **≤ 2 %**。偏差 > 2 % **先查前端**(焊点、串阻实际值、电位器负载效应),不要用系数凑。
判据:三点线性拟合 **R² ≥ 0.999**,斜率与期望值偏差 **≤ 2 %**。
偏差 > 2 % 时**先查前端**(焊点、串阻实际值、电位器负载效应),不要用系数去凑——
把硬件的错固化成"标定值"是最坏的结果。
**2026-10-27 实测结论:PASS。** R² = 0.9999997(判据 ≥0.999)、斜率偏差 −0.14 %(判据 ≤2 %)。
三条残差 −2.0 / +3.5 / −1.4 码,落在读数噪声内,**无系统性弯曲**——即前端在这三个点上确实是
线性的,不存在未识别的分压元件。这是"③ 段变比 = 1:1"在台架上的第一次直接证据。
**2026-10-27 实测:PASS。** R² = 0.9999997、斜率偏差 −0.14 %。
### 5.4 步骤 3:极性(只 TPAFE 三路)
TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,负半周测不了。给 UA/UN/IA 各加一个
**−1.0 V** 点。
TPAFE 双极性补码可注负压;STM32 单极性负半周测不了。UA/UN/IA 各加 **−1.0 V** 点。
**记录**(三路各两个点):
**记录**(三路各两点):
| 通道 | +1.0 V 码值 | −1.0 V 码值 | 对称性偏差 |
| -- | --------- | --------- | ----- |
@@ -216,40 +140,26 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,
| UN | 6549 | −6568 | 0.29 % |
| IA | 6548 | −6566 | 0.27 % |
判据:−1.0 V 点码值为**负**且落在 **−6554** 附近,幅值与 +1.0 V 点对称(差值 ≤ 2 %)
→ 符号位与补码解释正确。这一步专门补"连极性都没证过"这个缺口。
判据:−1.0 V 点码值**为负**且落在 **−6554** 附近,与 +1.0 V 对称(差 ≤ 2 %)→ 符号位与补码解释正确。
**2026-10-27 实测结论:PASS。** 三路 −1.0 V 点全部为负,落在 −6566~−6568(相对 −6554 偏
−0.18 %~−0.21 %);对称性偏差 0.27 %~0.34 %,远小于 2 % 判据。**补码符号位解释正确**。
**2026-10-27 实测:PASS。** 三路 −1.0 V 全负,落在 −6566~−6568;对称性 0.27 %~0.34 %,远小于 2 %。
### 5.5 步骤 4:节奏(注入期间顺带看)
注入期间 `frames=` 应仍**严格 1000 Hz 递增**(10 s 间隔 +10000 左右),`idle=0`,
`adc1_err=0`。这一条替代了原方案里"用示波器看 U3.14 BUSY 有脉冲"的判据——不需要示波器。
注入期间 `frames=` 应仍**严格 1000 Hz 递增**(10 s +10000 左右),`idle=0`、`adc1_err=0`。
**2026-10-27 实测结论:PASS。** 注入**前**(20:58 那次开机)与注入**后**(21:29)各取两行相隔
10 s 的心跳,两边都是 **Δ9998 码 / ≈10.0 s ≈ 1000.1 Hz**,`idle=0`、`adc1_rc=0`、`adc1_err=0`:
**2026-10-27 实测:PASS。** 注入**前**(20:58)与**后**(21:29)各取两行相隔 10 s 心跳,两边都是 **Δ9998 码 / ≈10.0 s ≈ 1000.1 Hz**,`idle=0`、`adc1_rc=0`、`adc1_err=0`:
| 时点 | `frames=` 两行 | Δ | 折算 |
|---|---|---|---|
| 注入前(20:58) | 29997 → 39995 | 9998 | 1000.5 Hz |
| 注入后(21:29) | 1819639 → 1829637 | 9998 | 1000.1 Hz |
⚠ **口径说明**:这两次是"注入**前后**",不是"注入**进行中**"——已知电压注入验证判据 ⑤ 要的正是"注入前后
`tpafe5160_frame_seq()` 仍严格 1000 Hz 递增",故按此口径已达标。若日后要连"进行中"也钉住,
在任一注入过程里再抓两行即可(注入不改变 TIM3 硬件触发,理论上无影响,但没测过就不算证过)。
`adc1_gap` / `tgap` / `skip` 是**本次开机以来的累计值**(548 / 363 / 183),10 s 内只增 3 / 2 / 1,
占比约 0.03 %,属正常漏检量级,不是停摆。
### 5.6 步骤 5:回写
1. 把实测斜率、零点、映射结果写进 `docs/硬件平台/ADC通道映射说明.md` §8,把"尚未执行"改成
实测值 + 日期;
2. 若实测确认无分压,同步在 `docs/硬件平台/ADC通道映射说明.md` §7.1 的链路分段表把第 ③ 段
(`AINn` ↔ 转换器引脚)从"待台架实测"改为实测结论;
3. `docs/任务规划/数据可信.md` 的已知电压注入验证行按"完成即删"销项;判据与实测证据的属主是
`docs/硬件平台/ADC通道映射说明.md` §8.1。
1. 实测斜率、零点、映射写进 `docs/硬件平台/ADC通道映射说明.md` §8("尚未执行"改实测值 + 日期);
2. 确认无分压后,把 `docs/硬件平台/ADC通道映射说明.md` §7.1 量纲链路的第 3 段(`AINn` 焊盘 ↔ 转换器引脚)改成实测结论;
3. `docs/任务规划/数据可信.md` 已知电压注入行按"完成即删"销项;判据与证据属主 `docs/硬件平台/ADC通道映射说明.md` §8.1。
## 6. 判据汇总
@@ -262,438 +172,252 @@ TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,
| 极性 | −1.0 V → −6554 码 | −6566 / −6568 / −6566,对称性 ≤0.34 % | PASS | 补码符号处理错 |
| 节奏 | 注入期间仍 1000 Hz | 注入前/后各 **Δ9998 / 10 s ≈ 1000 Hz**,`idle=0`、`adc1_err=0` | PASS | 注入改变了输入共模,触发转换异常 |
**总体结论(2026-10-27)**:**六项全部 PASS**。合起来证明了三件事:
① ③ 段(`AINn` ↔ 转换器引脚)**变比确为 1:1**,无隐藏分压(斜率偏差 −0.14 %);
② 8 路语义 ↔ 物理端子**一一对应**(零点步骤逐路代偿);
③ TPAFE 补码符号位解释正确(对称性 ≤0.34 %)。
已知电压注入验证的判据 ①②③④⑤⑥ 至此全部闭环(判据与实测记录 → `docs/硬件平台/ADC通道映射说明.md` §8.1)。
⚠ **两项残留口径**(不影响本轮 PASS,但别当成已验证):**串扰**未单独证(见上表"映射"行);
**节奏**只测了注入前后、没测注入进行中。两项若日后怀疑,各自的补做方法已写在对应小节。
**总体结论(2026-10-27)**:**六项全部 PASS**——结论一:从焊盘到转换器引脚那一段变比 1:1、无隐藏分压;结论二:8 路语义 ↔ 物理端子一一对应;结论三:TPAFE 补码符号位正确。全部闭环(→ `docs/硬件平台/ADC通道映射说明.md` §8.1)。⚠ **两项残留**(不影响 PASS):**串扰**未单独证;**节奏**只测注入前后。
## 7. 安全红线
1. **STM32 侧严禁超过 3.3 V**(VDDA + 0.3 V 即击穿),电位器上限就接 3.3 V 轨,不要接 5 V。
2. **TPAFE 侧按判据只到 ±2 V**,虽芯片容 ±5 V,但判据不需要更大。
3. **端子侧不许直接上额定电压**(220 V / 100 V 二次值)。本次是直流低压注入,
与"加压试保护"是两件事,后者要等端子/子板变比确认(`docs/硬件平台/ADC通道映射说明.md` §7.1 链路分段表 ④ 行)之后单独排。
4. **引脚侧注入必须外串 10 k**,且先接线后上电、先断电后拆线。
5. 注入源与板子**共地**;电位器一端接 3.3 V、一端接 AGND、中间抽头串 10 k 到注入点。
1. **STM32 侧严禁超过 3.3 V**(VDDA + 0.3 V 即击穿),电位器上限接 3.3 V 轨。
2. **TPAFE 侧按判据只到 ±2 V**(芯片容 ±5 V 但判据不需要更大)。
3. **端子侧不许直接上额定电压**(220 V / 100 V 二次值);"加压试保护"另排(`docs/硬件平台/ADC通道映射说明.md` §7.1 ④ 行)。
4. **引脚侧注入必须外串 10 k**,先接线后上电、先断电后拆线。
5. 注入源与板子**共地**;电位器一端 3.3 V、一端 AGND、抽头串 10 k 到注入点。
---
## 8. 交流(正弦)动态验证轮次(六轮:幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波)
> **判据的属主是 `docs/任务规划/数据可信.md` §4.6.2**(六轮各验什么、过判据是多少),本节只写**怎么跑**:
> 接哪个点、发生器怎么设、读哪一行的哪个字段、期望值怎么算、结果往哪回写。两边于 2026-10-29 对齐;将来
> 若再分叉,按两类裁定——**「验什么、算不算过」从 `docs/任务规划/数据可信.md` §4.6.2,「期望值怎么算、
> 怎么接线怎么跑」从本节**。为什么这一跳不用等标准源、以及它证不到什么,看其 §4.6.1 与 §4.6.5,本节不重抄。
>
> **引用写法**:本节凡引用判据文档的小节,一律写成「文件名 + 小节号」的紧邻形式;只写 §8.x 的是本文自己的节。
> **引用写法**:引用判据文档小节一律「路径 + 小节号」紧邻;只写 §8.x 的是本文自己的节。本节只写**怎么跑**(接哪个点、发生器怎么设、读哪行哪个字段、期望值怎么算、结果往哪回写)。
先下图建立全局轮廓——每一轮注什么波形、验什么、读哪个出口,再进 §8.5 逐轮执行卡:
![六轮一图速查:注入信号形状 → 接法与设定 → 判据一句话 → 数字从哪儿读;过判据属主在 数据可信.md §4.6.2](../../images/交流六轮速查.svg)
![六轮一图速查:注入信号形状 → 接法与设定 → 判据一句话 → 主读数出口;过判据属主在 数据可信.md §4.6.2](../../images/交流六轮速查.svg)
### 8.1 动手前先弄清楚:数字从哪儿读、能读到多细(决定今天判得动哪几轮)
### 8.1 动手前先把出口分辨率算清楚(决定今天能判到哪一档)
这轮能读到数字的地方共 5 处——3 处是**串口周期打印的日志行**、1 处是**板子屏幕**、1 处是**触发录波段文件后在 PC 上重算**。分辨率天差地别,各自判得动的轮次也不同,下表逐处列清:
这一轮和直流轮次**不一样:样本出口已经有了**。2026-10-29 故障录波的数据面并入主干,一次触发就是一段
`/wave/WAVE<NN>.BIN`(`NN` = 00~99 轮转号;8 路 × 320 点原始码值 + 封存瞬间的标定与品质快照),PC 侧 `test/host/wave_read.py` 把它还原成
周波数、幅值、**相位**、谐波与削顶判定。所以本轮每条用例都有两个档:**周期行标量档**(只读串口 / 104 / UI,
快,不用 FTP)与**段文件对表档**(多一次触发 + 一次取段 + 跑脚本,判得到位)。各出口的实际分辨率如下——
这张表决定每条用例走哪个档:
| 出口 | 打印处 | 分辨率 | 能判 | 判不动 |
| 在哪儿读 | 看到的形态 | 数字精度 | 判得动什么 | 判不动什么(为什么) |
|---|---|---|---|---|
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | `App/task/adc_task.c:605` | **1 mV**(整数毫伏,只发 UA/UN/IA) | 幅值轮、波形轮的幅值 ≤2 % | 相位、谐波、其余五路 |
| 104 YC `0x4011`~`0x4018` + `0x401D`(短浮点 + 品质) | `App/protocol/iec104/iec104_pts.c:70` | 3 位有效数字 ≈ 0.01 V | 单个静态工作点的幅值交叉读数、品质 IV | **小步进**(每点 0.5 V / 0.005 A 变化死区)、相位、谐波 |
| `phasor(hb) C0x-…-F1: Re=.. Im=.. rms=.. (x0.1)` | `App/task/adc_task.c:209` | **0.1 V,且向下截断** | 有数没数、量级、象限 | ⚠ **相位轮的 ≤2° 与谐波轮的 ±2 %**:注 1.000 Vrms 时 `rms` 只有 10 格,20 % 的谐波只有 2 格 |
| `freq(hb): f=.. valid=.. (x0.01)` | `App/task/adc_task.c:224` | 0.01 Hz | 测频轮的读数与新鲜度 | — |
| UI 交流量页 `Ua/U0/Ia/Ia2/Ia5`(3 位小数)、`Hz`(2 位) | `Drivers/BSP/UI/ui_page_ac.c:47` | **1 mV**(人工目检 + 拍照留证) | 谐波轮谐波幅值、幅值轮交叉旁证、测频轮的 `Hz` 显 `--` | 相位(页面上没有角度) |
| 录波段文件 `/wave/WAVE<NN>.BIN`(`NN` = 00~99,100 个号轮转;FTP 根下,与 `/log` 平级)+ `test/host/wave_read.py`(`dump` / `fft` / `shape` / `plot`,纯标准库) | **已落地**(2026-10-29 并入 `v2-on-ui`;格式属主 `docs/子系统设计/故障录波设计说明.md` §6.2 与 §6.3) | 一次触发 8 路 × 320 点原始码值;`fft` 给基波 RMS、相位、2 次与 5 次谐波 RMS(mV) | **相位轮的 ≤2° 与谐波轮的 ±2 % 现在判得动**:PC 独立重算 vs 板上读数对表 | 段与发生器**无绝对相位锁定**(触发点在段内点 159)→ 只能比同段内相对相位;320 ms 快照不等于长时间稳定性 |
| **串口心跳行** `meas:`(打印处 `App/task/adc_task.c:605`,200 ms 一行) | `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | **1 mV**(整数毫伏;电气量只发 UA/UN/IA 三路) | 幅值轮、波形轮的幅值 ≤2 % | 相位、谐波(这行没这两个数);其余五路(这行不发) |
| **串口心跳行** `phasor(hb)`(打印处 `App/task/adc_task.c:209`) | `phasor(hb) C0x-…-F1: Re=.. Im=.. rms=.. (x0.1)` | **0.1 V、向下截断**(×0.1 定点) | 有数没数、量级、象限 | ⚠ 相位 ≤2°、谐波 ±2 % 想都别想:注 Vpp 2.828 V(板上 ≈1000 mV)时 `rms` 满打满算 10 格,20 % 谐波只占 2 格 |
| **串口心跳行** `freq(hb)`(打印处 `App/task/adc_task.c:224`) | `freq(hb): f=.. valid=.. (x0.01)` | 0.01 Hz | 测频轮的读数和新鲜度(`valid`) | — |
| **板子屏幕** 主界面→实时数据→交流量(代码 `Drivers/BSP/UI/ui_page_ac.c:47`) | 页 7 行 `Ua/U0/Ia/Ia2/Ia5/Hz/量纲`;量值行 3 位小数、Hz 行 2 位 | **1 mV**(靠人眼看屏 + 拍照留证) | 谐波轮 `Ia2`/`Ia5` 幅值;幅值轮交叉旁证;测频轮 `Hz` 显 `--` | 相位(页面上没有角度) |
| **录波段文件 + PC 重算**:把 `/wave/WAVE<NN>.BIN` 段取回本机用 `test/host/wave_read.py` 算(四子命令见 §4 表;格式属主 `docs/子系统设计/故障录波设计说明.md` §6.2/§6.3) | 段内 8 路 × 320 点原始码 + 封存瞬间的标定与品质快照 | `fft` 浮点重算:基波有效值、相位、2 次与 5 次谐波有效值(mV) | 相位 ≤2°、谐波 ±2 %——**只有这条路判得动**;顺带 PC 独立重算与板上数字互相对表 | 段与发生器**没有绝对相位锁**(触发点固定在段内点 159)→ 只能比同一段内的相对相位;一段 320 ms,不代表长时间稳定 |
**所以每条用例按两个档摆**(出口从"缺样本"变成"有段文件"的经过与口径变更,见 `docs/子系统设计/故障录波设计说明.md` §11;原卡片登记簿 2026-10-30 销项并入该文 §10):
> **2026-11-02 / 11-04 两轮已跑、六轮全部闭合**。实测结论只详述在 `docs/硬件平台/ADC通道映射说明.md` §8.2,此处不抄第二份。
| 档 | 条目 | 说明 |
|---|---|---|
| **周期行就判得动** | 幅值、波形、测频、削顶四轮(削顶轮的接法要按 §8.5 改,原因写在那张卡里) | 判据在 `meas:` / 104 / `freq(hb)` / `rail:` 上分辨率都够,不取段也能收工 |
| **段文件档才判到位** | 相位轮、谐波轮 | 段文件已落地 → 浮点重算,≤2° 与 ±2 % 都是**定量**判据;周期行退为旁证,只在没取到段时走定性档。触发动作与命令写在 §8.5 的相位卡 / 谐波卡 |
| **要改代码的退路** | 相位轮抬倍数、测频轮 B 档 | 段文件落地后相位轮这条基本不必走;测频轮 B 档(放宽 `FREQ_AMP_MIN`)仍是唯一路子。两者都要重编 + 烧录 + 按 §8.9 还原,**动手前先问用户** |
**六轮用例先定「什么证据才算过」——这张快查表逐轮指定证据档**(档位重划的经过见 `docs/子系统设计/故障录波设计说明.md` §11):
| 证据档 | 哪些轮用 | 过关要求 | 定这个档的原因 |
|---|---|---|---|
| **心跳档**——串口周期行看够,**不用录波** | 幅值、波形、测频、削顶 | 串口周期行(`meas:` / `freq(hb)` / `rail:` 位图)达判据即算过 | 判据阈值 ≥ 周期行分辨率(±2 % 对 1 mV、位图对 1 bit),录波无增益 |
| **录波档**——**必须录一段波**才算定量 | 相位、谐波 | 面板触发手动录波 → FTP 把段文件取回 → `wave_read.py fft` PC 浮点重算,与板上读数对表达判据 | 板上周期行是 ×0.1 截断定点(上限表「判不动」列),≤2°、±2 % 只有重算才判得动;谐波轮就算拿 UI 的 1 mV 也不行——**屏上那个数本身就是板子 DFT 算出来的答案**,系数表用错行、索引错位这类算术错会原样透传到屏上,必须靠 PC 独立重算当第二把尺子(段文件还顺带核对频位:100 Hz 确落 2 次 bin);**没录段文件只能退回「心跳档」记,定量判据不算过** |
| **改码退路**(默认不走) | 相位抬打印倍数、测频放宽 `FREQ_AMP_MIN` | 仅当录波档不可用(段取不回、PC 跑不了脚本)才考虑;做则重编 + 烧录 + 把临时补丁全量还原并核对 | 动了固件就不是「零改动验证」了——**动手前必须问用户** |
### 8.2 设备清单与开机前置门
| 项 | 要求 | 为什么 / 怎么核 |
|---|---|---|
| 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设到 2 Hz;幅度 0~3 Vpp | **只要求"波形干净、幅度稳、频率锁得住"**,它的刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档是给削顶轮改法 A 用的 |
| ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 |
| 万用表 AC 档 | 能读到 mV;**记明型号与响应类型**(真 RMS / 均值响应) | 基准就是它;波形轮那一步两种表的读数都与基波期望差很多,不记响应类型就说不清是谁错 |
| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `/keil-build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 |
| `[adc_cal]` 状态 | **保持未标定**(`eng=0`),别为"读数好看"先写标定值 | 现在 `meas:` / `phasor` 的量纲是 **ADC 引脚电压 V**(`App/algo/meas.h` 文件头的 A-lite 口径);标定一生效就换成二次值,本节全部期望值作废 |
| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `/serial-boot-test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 |
| 网口(可选) | 要读 104 那一路就插网线、PC 与板同网段(出厂 192.168.31.100),跑现成主站 `test/host/iec104_master.py`(`--ip` / `--port` / `--addr`) | 104 是浮点无量化 + 带品质,是 `meas:` 之外的第二条腿 |
| FTP 客户端 | 走现成 21 端口取段文件(出厂 `[ftp]` 账号,连续 5 次失败锁 60 s) | 段文件是相位 / 谐波轮定量档的样本出口;段文件名 `/wave/WAVE<NN>.BIN`(00~99 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 |
| 录波触发路径 | 产品构建里唯一**免改代码**的路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算一次触发,但保护判据现处**未标定闭锁**态(AGENTS.md「现状」),别指望它自动来段;bench 定时自抓在双层编译门 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0 之内(默认 0,`App/task/adc_task.c:153`),开它就走 §8.9 的还原判据 |
| 共地 | 发生器 LO/GND 与板 AGND **单点**相连 | 不共地量到的是两地之差;现象见 §8.8 |
| 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设 2 Hz;幅度 0~3 Vpp | 只要求"波形干净、幅度稳、频率锁得住",刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档给削顶轮的直流削顶档 |
| ⚠ 发生器端接 | 接板子时设**高阻负载**,不按 50 Ω 端接 | 50 Ω 端接而实际高阻 → 面板值与真实电压差 2 倍;台架"差一倍"最常见来源 |
| 万用表 AC 档 | 能读 mV;**记明型号与响应类型**(真有效值 / 均值)——判别法:表身或说明书没印 "True RMS" 就当均值档;拿发生器注一个 Vpp 2.828 V 方波到 AC 档,读数 ≈1.41 V = 真有效值档、≈1.57 V = 均值档(差 11 %,一眼分辨) | 基准就是它;波形轮两种表读数都与基波期望差很多,不记响应类型说不清谁错 |
| 板子 | 产品构建(`test/board/test_config.h` 四 `TEST_SUITE_*` 全注释)+ `/keil-build` 0 错 0 警 + 烧录 `-Rst -Run` + 回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 |
| `[adc_cal]` 状态 | **保持未标定**(`eng=0`),别为"读数好看"先写标定值 | 现在 `meas:` / `phasor` 量纲是 **ADC 引脚电压 V**——未标定态的设计本来就不折算(`App/algo/meas.h`);标定一生效换二次值,本节全部期望值作废 |
| 串口 | 全程抓 RX 落盘:`serial-assistant` skill(`open` → `clear` → 跑 → 读日志 / `log --tail`) | 日志文件就是证据本体;**长跑别用 `/serial-boot-test`**——它绑一轮启动语义(静默 8 s、总超时 30 s、forbidden 含 `[ERR]`,削顶轮故意造 `[ERR]`),必超时或误判;daemon 写 `state/serial.log`(20 MB 截头保尾 10 MB,长跑防截断 → `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集) |
| FTP 客户端 | 走 21 端口取段文件(出厂 `[ftp]` 账号,连续 5 次失败锁 60 s) | 相位 / 谐波的定量判据只能拿段文件来算;`/wave/WAVE<NN>.BIN`(00~99 轮转),**取回本机后立刻改名归档**,否则下次触发盖掉 |
| 录波触发路径 | 产品构建唯一**免改代码**路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算触发,但保护判据现处**未标定闭锁**(AGENTS.md「现状」),别指望自动来段;bench 自抓在 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0(默认 0,`App/task/adc_task.c:153`),开它属临时措施,跑完撤销重编 |
| 共地 | 发生器 LO/GND 与板 AGND **单点**相连 | 不共地量到的是两地之差,读数带与幅值无关的偏移或漂移 |
**开机前置门**——接发生器**之前**日志就该长这样,不满足先回 §5 步骤 5.5 查节奏,别往下做:
**开机前置门**——插上网线、开了串口采集、**还没接发生器**之前,串口日志就该通过下面 8 条;任何一条不过,先回 §5.5 处理,别带着脏链往下跑:
- `adc:` 行 10 s 一行,同一心跳里 `frames` 在涨("9 路全 0"要读成"没数据",§4 尾注);`idle=0`、`adc1_err=0`;
- `meas:` 的 `v=1`、`seq` 每 10 s 约 +500(200 ms 一帧;不涨 = 发布闸门停);
- `cal:` 的 `cl=0xE6` —— **这是来源派生**(V0.1 那五路本就触底,见 `docs/任务规划/数据可信.md` §2.6),既不是本轮战果也不是本轮故障;
- 四个位图 `vl` / `cl` / `rail` / `edge` 同一个位序(语义通道枚举 `App/algo/adc_input.h:38`):bit0 `UA`、bit1 `UB`、
bit2 `UC`、bit3 `UN`、bit4 `IA`、bit5 `IB`、bit6 `IC`、bit7 `IN`、bit8 `TEMP` → `0xE6` = bit1/2/5/6/7 = `UB/UC/IB/IC/IN`;
- **先上电、在基线里看到 `adc: UB` 有正常摆动,再开发生器输出**:贴轨锁存要求该通道"此前出现过中带样本"(释放沿前提,
`App/algo/adc_health.c` 的 `adc_health_rail_watch()`),开机就注大信号会让削顶轮的锁存**装不上**,测出假不通过。
- 开机各有一条 `first:` 与 `phasor(first)` / `freq(first)` 单发行(`App/task/adc_task.c:478` 与 `App/task/adc_task.c:201`
的 `tag` 参数),与 10 s 一轮的 `hb` 行是两拨——数心跳时别把 `first` 计进去;
- `phasor` 行**只要有数就打**:打印条件只有 `phasor_get() == 0`,不带任何 valid 位(`App/task/adc_task.c:209`)→
「phasor 有数」既不说明标定可用、也不说明这一路没在削顶,判读必须配 `vl` / `cl` / `rail` 三个位图一起看;
- 注入正弦**不会**点亮 `health:` 或 `sampling fault:` 那几行:`adc_health` 的位只吃序号差、错误计数增量与 tick 差
(`App/algo/adc_health.c:91-189`),与波形幅值无关;它们若亮是节拍 / 丢帧问题,别归因给发生器(§8.8 同一条)。
- `wave:` 行(`App/task/adc_task.c:782`)基线:`armed=0`、`err=0`、`ovf=0`、`drop=0`,`seg=` 是本开机以来的成段数(**可以是 0**)。
本轮每触发一段就核 `seg` +1 且那三个计数不涨;涨了先按 §8.8 的落盘行分诊,别接着往上下注入。
| # | 看什么 | 正常该是这样 | 为什么容易看错 / 不满足怎么办 |
|---|---|---|---|
| 1 | `adc:` 行 | 10 s 一行;`frames` 在涨;`idle=0`、`adc1_err=0` | **九路数值全 0 不等于"没数据"**(没输入当然读 0,判据是 `frames` 涨不涨,§4 尾注);`frames` 不涨或 `adc1_err≠0` → §5.5 |
| 2 | `meas:` 行 | `v=1`;`seq` 每 10 s 约 +500(200 ms 一条) | `seq` 不涨 = 发布闸门停 → §5.5 |
| 3 | `cal:` 行的 `cl=0xE6` | **开机就该是这个数,不是故障** | 这是来源派生静态掩码:V0.1 硬件上 `UB/UC/IB/IC/IN` 五路本就触底(`docs/任务规划/数据可信.md` §2.6)。非战果非故障,别去"修"它。位序(`App/algo/adc_input.h:38`):bit0~8 = `UA UB UC UN IA IB IC IN TEMP`,`0xE6` = bit1/2/5/6/7 恰好这五路 |
| 4 | **开机与注入的先后** | 先上电 → 在串口看到 `adc: UB` 数值有正常摆动 → **再**开发生器输出 | 顺序反了会埋雷:贴轨锁存要求该通道"此前出现过中带样本"(`App/algo/adc_health.c` `adc_health_rail_watch()`),开机就注大信号,削顶轮的锁存**装不上**,测出假"不锁存" |
| 5 | 开机单发行 | `first:`、`phasor(first)`、`freq(first)` 各只一条(`App/task/adc_task.c:478`/`:201`) | 数心跳周期时把它们剔掉,别算进"每 10 s 一行"里 |
| 6 | `phasor(hb)` 行 | 有数就打出来 | **有数 ≠ 数有效**:这行只查 `phasor_get()==0` 不看 valid 位(`App/task/adc_task.c:209`),判读必须配着 `vl`/`cl`/`rail` 一起看 |
| 7 | `health:` / `sampling fault:` | 不该出现 | 这些位只吃序号差/错误计数/tick 差(`App/algo/adc_health.c:91-189`),注入多大正弦都不该点亮;亮了 = 节拍/丢帧问题,先修节拍链再谈读数 |
| 8 | `wave:` 行(`App/task/adc_task.c:782`) | 基线 `armed=0`、`err=0`、`ovf=0`、`drop=0`;`seg=` 成段数(**0 也正常**,从没触发过就该是 0) | 后面每触发一段核一次:`seg` +1 且那三个计数**不涨**;`err`/`ovf` 非零才是失败(`drop` 触发后涨一截属设计内:SEALED 窗口丢点、一拍加 8,`App/algo/wave_ring.c:50`,涨完即冻结) |
### 8.3 接线的四条(与直流轮次比,差别只在第 2、3 条)
1. **注入点与直流轮次完全相同**:§3 清单的**端子侧**焊盘(`U5` 排针,串阻的 1 脚侧)。③ 段 1:1 已由直流轮次实测
(斜率偏差 −0.14 %,§6),所以端子侧电压就是引脚电压,不用再算变比。引脚侧仍须外串 10 k(§7 第 4 条)。
2. **幅度按峰值核,别看面板上的 Vrms**:1.000 Vrms 正弦峰值已 1.414 V。
1. **夹子接哪儿**:跟直流轮同一个位置——§3 清单的**端子侧**焊盘(`U5` 排针,串阻 1 脚侧)。接这里的好处是**读数与设定值不用换算**:从焊盘到 ADC 引脚这一段是 1:1(直流轮已实测盖章,斜率偏差 −0.14 %,见 §6),端子侧电压就是引脚电压。⚠ 换个位置注(直接进引脚)才需要外串 10 k(§7 第 4 条),照本条接就不用。
2. **幅度一律按峰峰值 Vpp 设定**,对表时注意换算:发生器设定 = Vpp;万用表 AC 档与板上读数天然是有效值刻度,正弦下 有效值 = Vpp ÷ 2.828。
| 侧 | 判据上限(§7 红线 2) | 允许的最大正弦 Vrms | 本节用量 |
| 侧 | 判据上限(§7 红线 2) | 允许的最大正弦 Vpp | 本节用量 |
|---|---|---|---|
| TPAFE(`UA` / `UN` / `IA`) | ±2 V 峰值 | 1.414 Vrms | **1.000 Vrms**(峰值 1.414 V) |
| STM32(`UB` / `UC` / `IB` / `IC` / `IN`) | 0~3.3 V 单极性 | 无正向意义 | 只在削顶轮用:注交流 = **故意触底**,负半周本就钉 0 |
| TPAFE(`UA`/`UN`/`IA`) | 峰值 ±2 V | 4 V | **2.828 V**(= 峰值 ±1.414 V) |
| STM32(`UB`/`UC`/`IB`/`IC`/`IN`) | 0~3.3 V 单极性 | 无正向意义 | 只在削顶轮用:注交流 = **故意触底**,负半周本就钉 0 |
3. **一次只注一路**(相位轮除外,它要 `UA` 与 `UN` 两路同源),换表笔前先关输出。端子侧串阻是 **1 k**(R31 / R28 / R19 / R52 …),
STM32 那五路负半周经**片内**钳位 / ESD 路径灌向 VDDA——网表 `docs/硬件平台/V0.1.tel` :228-241 逐条实证这些节点只有
「电容 .2 + 串阻 .2 + MCU 脚」三件,**无外置钳位二极管、无隔直电容**,所以这条电流走的是芯片内部路径;
1.000 Vrms 时峰值电流约 (1.414 − 0.3) / 1 k ≈ **1.1 mA**(按片内钳位压降 0.3 V 估、仓内无实测,只用来论证量级),
但**别往上加幅度**——这种注入只允许出现在削顶轮这类"故意削顶"的用例里。
4. **电流通道也是注电压**:`IA` 的注入点同样是电压进 R19 端子侧。这一轮证的是"给定引脚电压算得对不对",
不是"一次侧 1 A 怎么办"——后者要变比,属 `docs/任务规划/数据可信.md` §5 的端子 / 子板变比确认。
四条接线画在一张图上(发生器 → 端子侧焊盘是一条主干,万用表并接、共地是两条必须存在的旁路):
```mermaid
flowchart LR
G["信号发生器<br/>输出端接 = 高阻<br/>(按 50 Ω 端接读数差 2 倍)"] -->|"注 UA / UN / IA:端子侧焊盘<br/>(1:1 已由直流轮次实测)"| R31["串阻 1 k 的 1 脚侧<br/>R31 / R28 / R19 …"]
R31 --> FE["前端分压 / 滤波"] --> U5["U5 排针 → 转换器引脚<br/>(TPAFE AIN_x 或 STM32 脚)"]
M["万用表 AC 档<br/>记响应类型"] -.->|"并接同一注入点<br/>量真实加到板上的电压"| R31
G -.->|"LO/GND 与板 AGND<br/>单点共地"| AGND["板 AGND"]
AGND -->|"不共地量到的是两地之差"| U5
G -.->|"注 UB 等 STM32 那五路(仅削顶轮)<br/>负半周经片内钳位 ≈1.1 mA"| R52["R52 端子侧"] --> STM["STM32 ADC 脚<br/>(无外置钳位 / 隔直电容)"]
```
3. **一次只注一路**(相位轮除外,它要 `UA` 与 `UN` 两路同源),换表笔前先关输出。端子侧串阻 **1 k**(R31/R28/R19/R52…)。STM32 五路负半周经**片内**钳位灌向 VDDA(网表 `docs/硬件平台/V0.1.tel` :228-241 实证:无外置钳位二极管、无隔直电容);Vpp 2.828 V 时峰值电流 ≈ **1.1 mA**(量级估算),**别往上加幅度**——这种注入只允许出现在削顶轮。
4. **电流通道也是注电压**:`IA` 注入点同样是电压进 R19 端子侧。这一轮证"给定引脚电压算得对不对",不是"一次侧 1 A 怎么办"——后者要变比,属 `docs/任务规划/数据可信.md` §5 的端子 / 子板变比确认。
### 8.4 记录表抬头(每条用例一份,与日志一起归档)
```text
日期 ____ / 操作人 ____ / 板子序列号 ____
固件 commit ________(/keil-build 0 错误 0 警告:是 / 否)
发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____
万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地
注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω)
[adc_cal] = 未标定(eng=0)—— 必须是这个状态
基线(未注时):meas: ua_mv=____ un_mv=____ ia_mv=____ rail=0x____ cl=0x____
段文件(走定量档的用例):文件名 ________ 触发方式 = 手动 / 保护 / bench 取回时刻 ________
脚本:test/host/wave_read.py(先跑 --selftest:通过 / 否) fft / shape / dump 输出与 plot 图归档在 ________
```
这份抬头只回答一个问题:**这组数字是在什么条件下量出来的**——照下表从右边写的地方**抄**过来,不用你判断任何数;缺任何一项,这页记录三个月后就没法复核。
| 要抄的信息 | 从哪儿抄、为什么它要紧 |
|---|---|
| 日期、操作人 | 王建锋 |
| 板子序列号 + 固件版本 | `fw build: Nov 4 2026 19:05:57 suite=NONE dstr=off`(2026-11-04 补测轮整行照抄;11-02 首轮为 `fw build: Nov 2 2026 14:21:24 suite=NONE dstr=off`) |
| 发生器型号 + 端接 | VICTOR 2026H(端接必须设"高阻",设成 50 Ω 输出会被砍一半,所有期望值作废) |
| 万用表型号 + 响应类型 | FNIRST-DMT-99 + 真有效值档(判别法见 §8.2 设备表) |
| 标定状态 | cal: ua_mv=-7 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000 |
| 基线读数 | meas: seq=300 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0 |
### 8.5 逐条执行卡
**每条卡的通用动作**:接好 → 开输出 → 等 ≥30 s(相量窗 + `meas` 200 ms 轨稳定)→ 取**连续 ≥10 行**心跳 →
先确认 `frames` 还在涨、`adc1_err=0`,再核数 → 关输出 → 填表。"连续 ≥10 行"不是保守:`meas:` 是
**瞬时基波有效值透传**、200 ms 是上送节拍不是平均窗(`docs/任务规划/数据可信.md` §4.5),
单行读数带拍频式跳动,必须看一片取**中间水平(中位数)**才能定数。
**通用动作(六张卡都按这个走)**:
通用动作主干与两处分岔(采集链没清 → 回头修;偏差没进 → 先复核查再判):
1. 接好线、开发生器输出,等 ≥30 s 读数稳定;
2. 从串口取**连续 ≥10 行**心跳,先确认 `frames` 在涨、`adc1_err=0`(采不到算不得数),再核对卡片要求的读数——数字要取这 ≥10 行的**中位数**,别拿单行(`meas:` 是瞬时基波有效值透传、200 ms 是上送节拍非平均窗,`docs/任务规划/数据可信.md` §4.5,单行带拍频式跳动);
3. 这张卡的判据要录波取证的:抄完数后,从面板第 26 叶「手动录波」触发一段,把录波页屏上显示的文件名(`WAVE<nn>`)抄进该轮记录表;分析前在本机跑一次 `test/host/wave_read.py --selftest`,**不通过,分析结果不算证据**(不赶时间,六轮跑完统一取回再做);
4. 关掉发生器输出,把数填进该卡记录表。
```mermaid
flowchart TD
W["接好本路,开机前置门已过(§8.2)"] --> ON["开发生器输出"]
ON --> ST["等 ≥30 s:相量窗与 meas 轨稳定"]
ST --> HB["取连续 ≥10 行心跳"]
HB --> Q1{"frames 在涨、adc1_err = 0 ?"}
Q1 -->|"否:是节拍 / 丢帧问题"| TRI["先查 §8.8 分诊<br/>别归因给波形"]
TRI -.->|"修复后重来"| ST
Q1 -->|是| MED["10 行读数排序<br/>取第 5、6 个的平均 = 中位数"]
MED --> Q2{"与期望的偏差在判据内?"}
Q2 -->|是| OK["记通过"]
Q2 -->|"否"| CHK["先复核查:发生器端接、<br/>万用表响应类型、共地"]
CHK -->|"复查仍超"| BAD["记不通过,进 §8.8 分诊"]
CHK -->|"查出问题改完重测"| ST
OK --> OFF["关输出,填记录表"]
BAD --> OFF
OFF --> NEXT["下一条 / 下一轮"]
```
**两种情况先修再测,别往下走**:采集链没清(`frames` 不涨 / `adc1_err≠0`)→ 按 §8.2 前置门逐项查采集链;偏差没进判据 → 先复查三件事(端接 / 万用表响应类型 / 共地),改完重测,复查仍超才谈固件问题。
为什么这一步是"中位数"而不是单行或平均——机理与操作:
#### 幅值轮(先做:三档幅值的偏差都在 ±2 % 内)
![为什么连取 10 行取中位数:窗滑动让读数拍频式上下跳(左);中位数贴着密集簇,平均被毛刺拖走(右)](../../images/交流定数取中位数.svg)
#### 幅值轮:基波幅值(主用例,先做)
一句话:发生器注 50.000 Hz 正弦,**Vpp 2.828 / 1.414 / 0.566 V 三档挨个来**,每档从串口 `meas:` 行读 `ua_mv`,和并接在同一注入点上的万用表读数比——**每档偏差 ≤2 % 就算通过**。
| 项 | 内容 |
|---|---|
| 接法 | 发生器 → `UA`(R31 / `AIN1` / U5.A3)端子侧;万用表 AC 档**并同一注入点**——量真实加到板上的电压,不是面板值 |
| 设定 | 正弦、50.000 Hz、**1.000 Vrms**(峰值 1.414 V) |
| 主读数 | `meas: ua_mv=`(1 mV) |
| 期望与判据 | `ua_mv` ≈ 万用表读数 × 1000,偏差 **≤2 %**(与静态同档) |
| 交叉读数 | 104 `0x4011`(短浮点)应 ≈ 万用表读数,单位是**引脚电压域**(`eng=0`,不是二次值) |
| 坑 ① | **别用 `phasor(hb)` 比幅值**——×0.1 定点,1 V 只剩 10 格(§4 尾注同一条) |
| 坑 ② | 104 每点 0.5 V(电流 0.005 A)变化死区,生效值在 `App/algo/run_par.c` 的 `[run_par]` 副本:1.000 → 1.100 V **不会**上报变化,那是死区不是坏了;连续扫幅度只看 `meas:` |
| 坑 ③ | **`cal: ua_mv` 与 `meas: ua_mv` 同名不同物**:前者是当拍瞬时码折算(`App/task/adc_task.c:587`),后者是 200 ms 快照里那一周波的基波有效值(`App/task/adc_task.c:605`),50 Hz 下二者可以差很多——`App/task/adc_task.c:595-599` 的原注就是**勿互为判据**,幅值轮只认 `meas:` |
| 设定 | 正弦、50.000 Hz,三档挨个来:**Vpp 2.828 V → 1.414 V → 0.566 V**(档位不用拧精确,记万用表实际读数就行) |
| 读哪个数 | `meas: ua_mv=`(1 mV) |
| 通过判据 | 每档 `ua_mv` ≈ 万用表读数 × 1000,偏差 **≤2 %**(与静态轮同档) |
记录表(记录表每一行 = 连续 10 行心跳 `ua_mv` 的**中位数**:排序后取第 5、6 个的平均;
不取单行、不取峰值——单行带拍频跳动,毛刺行会拖偏算术平均。表里"序号"是一次重测):
记录表(每行 = 连续 10 行心跳 `ua_mv` 的**中位数**:排序取第 5、6 个平均;不取单行、不取峰值。"序号"是一次重测):
| 序号 | 万用表 AC (V) | `meas: ua_mv` | 期望 = 万用表 × 1000 | 偏差 % | 结论 |
|---|---|---|---|---|---|
| 1 | | | | | |
| 2 | | | | | |
| 3 | | | | | |
**加做(3 min,非判据)**:0.500 / 0.200 Vrms 两点看是否**等比**——不是验精度,是看有没有隐藏的触底或死区。
0.2 Vrms 那点 104 必然被死区吞掉,只读 `meas:`。
| 1 | 0.998 | 996 | 998 | -0.20 | PASS |
| 2 | 0.499 | 498 | 499 | -0.20 | PASS |
| 3 | 0.248 | 249 | 248 | +0.40 | PASS |
#### 波形轮:读数是否只跟基波走(同峰值改波形)
按**峰值对齐**(不是按 Vrms 对齐):三者峰值都取 1.414 V,发生器面板 Vrms 分别约 正弦 1.000 V、方波 1.414 V、
三角 0.816 V。期望的**基波有效值**(全周傅氏基波口径:`App/algo/phasor.c:3` 的系数已预乘 √2/N、`fAmSQ` 直接就是
有效值;下表的 20 点整周期 DFT 值就是按固件同一个窗算的):
三个波形都按**峰峰值对齐**:发生器统一设 **Vpp 2.828 V**(峰值 ±1.414 V)。期望的**基波有效值**(全周傅氏基波口径:`App/algo/phasor.c:3` 系数已预乘 √2/N、`fAmSQ` 直接就是有效值;下表 20 点整周期 DFT 值按固件同一个窗算):
| 波形 | 基波有效值 / 峰值 | 峰值 1.414 V 时的期望 `ua_mv` | 真有效值 / 峰值(对照) |
| 波形 | 基波有效值 / 峰值 | Vpp 2.828 V 时的期望 `ua_mv` | 真有效值 / 峰值(对照) |
|---|---|---|---|
| 正弦 | 0.707 | 1000 | 0.707 |
| 方波 | 0.904(闭式 4/π/√2 = 0.900) | ≈1278 | 1.000 |
| 三角 | 0.578(闭式 8/π²/√2 = 0.573) | ≈817 | 0.583 |
- 判据:读数 = **基波**期望 ±2 %,**不是**真有效值。方波那栏最直观——真 RMS 表会读 ≈1414 mV、
均值响应表读 ≈1570 mV,而板上读 ≈1278 mV,**这正是"这台仪表读的是基波"的证据,不是 bug**;
所以抬头里必须写清万用表响应类型(判据文档同一条:拿真有效值表去对会以为差了 10 %)。
- 系数的来路:方波基波幅值 = 4/π × 峰值、三角 = 8/π² × 峰值,再除 √2 得基波有效值 → 闭式 **0.900 / 0.573**,
20 点整周期 DFT 实算 **0.904 / 0.578**;`docs/任务规划/数据可信.md` §4.6.2 的"波形轮"行与本节同值,算术以本节为准。
- 判据:读数 = **基波**期望 ±2 %,**不是**真有效值。方波栏:真有效值表 ≈1414 mV、均值响应表 ≈1570 mV、板上 ≈1278 mV——**正是"读的是基波"的证据,不是 bug**;抬头必须写清万用表响应类型(判据文档同一条)。
- 系数算术以本节为准(4/π 与 8/π² 除 √2 得基波有效值;`docs/任务规划/数据可信.md` §4.6.2 引本表同值)。
记录表:
| 波形 | 峰值设定 (V) | 面板 Vrms | 万用表 AC 实测 | `ua_mv` | 期望 | 偏差 % | 结论 |
|---|---|---|---|---|---|---|---|
| 方波 | 1.414 | | | | ≈1278 | | |
| 三角 | 1.414 | | | | ≈817 | | |
| 波形 | 设定 Vpp (V) | 万用表 AC 实测 (V,有效值档) | `ua_mv` | 期望 | 偏差 % | 结论 |
|---|---|---|---|---|---|---|
| 方波 | 2.828 | 1.407 | 1269 | ≈1278 | -0.70 | PASS |
| 三角 | 2.828 | 0.814 | 807 | ≈817 | -1.22 | PASS |
#### 相位轮:相位(只许比同片内)
| 项 | 内容 |
|---|---|
| 接法 | `UA`(R31)与 `UN`(R28)**都走 TPAFE**,同幅 1.000 Vrms、同相——发生器单路输出分两路即天然同相;双通道机型则两通道都设 0° |
| 读数 | `phasor(hb) C02-UA-F1: Re=.. Im=..` 与 `C03-UN-F1: Re=.. Im=..`,各取 `atan2(Im, Re)` 再比差值 |
| 判据(§4.6.2) | **定量档**:同相时同段内两路相位差 ≤2°,把发生器(或 B 通道)改 90° 后差值跟到 ≈90°;**定性档只判跟随**(差值落进 80°~100°,容差写进记录表) |
| ⚠ 分辨率 | 出口只有 ×0.1 且向下截断:1.000 Vrms 时 Re/Im 各只有 ±10 格,**一格就是 0.1 V** → 角度量化误差最坏 ≈ atan(0.1 / 0.707) ≈ 8°,**≤2° 今天用串口判不出来** |
| 三个档 | ① **段文件定量档(首选,零改代码)**:同一次注入触发一段,`test/host/wave_read.py fft` 重算基波相位,比**同段内** `UA`(表序 0)与 `UN`(表序 3)的相位差——浮点重算,不受 ×0.1 量化限制,≤2° 判得动,动作见下面第 2 段;② **定性档**:没取到段时退而用,同相 ≈0°、改 90° 后差值落进 80°~100° 即算跟随,容差写进记录表;③ **抬倍数退路**:临时把 `log_phasors()` 的倍数改成 ×1000 并改掉行尾 `(x0.1)`,跑完按 §8.9 还原——段文件档已落地,这条基本不必走,要走仍**先问用户** |
| 红线 | **只许比同片内**:跨转换器无同刻性(V0.1 硬件缺陷)+ STM32 那五路另有 1 拍乒乓滞后 → `docs/硬件平台/ADC通道映射说明.md` §7 |
| 接法 | `UA`(R31)与 `UN`(R28)**都走 TPAFE**,同幅 Vpp 2.828 V(峰值 ±1.414 V)、同相(发生器单路分两路即天然同相;双通道机型两通道都设 0°) |
| 读数 | `phasor(hb) C02-UA-F1` 与 `C03-UN-F1`,各取 `atan2(Im, Re)` 再比差值 |
| 判据(`docs/任务规划/数据可信.md` §4.6.2) | **录波档**:同段内两路相位差 ≤2°,改 90° 后差值 ≈90°;**心跳档只判跟随**(差值落 80°~100°,容差写记录表) |
| ⚠ 分辨率 | 串口行的 ×0.1 向下截断:Vpp 2.828 V(板上 ≈1000 mV)时 Re/Im 各 ±10 格,**一格 0.1 V** → 角度量化误差最坏 ≈ 8°,**≤2° 串口判不出来** |
| 怎么取证 | ① **录波档(首选,零改代码)**:动作见下「录波档的具体动作」六步,比同段内 `UA`(表序 0)与 `UN`(表序 3)相位差,浮点重算不受 ×0.1 量化限制、≤2° 判得动;② **心跳档**:没录到段文件时退用,同相 ≈0°、改 90° 后差值落 80°~100° 即算跟随;③ **抬倍数退路**:临时 `log_phasors()` 倍数改 ×1000 并改行尾 `(x0.1)`,跑完撤销补丁并重编 + 重烧——录波档可用时基本不必走,要走**先问用户** |
| 红线 | **只许比同片内**:跨转换器无同刻性(V0.1 硬件缺陷)+ STM32 五路另有 1 拍乒乓滞后 → `docs/硬件平台/ADC通道映射说明.md` §7 |
记录表(定性档;定量档记脚本输出,归档口径见 §8.10 第 1 条):
记录表(按心跳档抄屏;录了波的轮次直接记脚本输出):
| 发生器相位设定 | C02-UA `Re` / `Im` | C03-UN `Re` / `Im` | 两路 `atan2` 之差 | 结论 |
|---|---|---|---|---|
| 0° / 0° | | | | |
| 0° / 90° | | | | |
| 发生器相位设定 | C02-UA `Re`/`Im` | C03-UN `Re`/`Im` | 录波文件名|两路 `atan2` 之差 | 结论 |
|---|---|---|---|---|---|
| 0° / 0° | -8 / -5 | -8 / -5 | WAVE06|心跳档两路象限全同 → Δ=0.0°(×0.1 判不动 ≤2°);**录波档 WAVE06 段实测 Δφ = 0.07°**(fft:UA 995.79 mV ∠+18.23° / UN 995.70 mV ∠+18.16°) | **PASS**(录波档 ≤2°) |
| 0° / 90° | 7 / -6 | -6 / -7 | WAVE09 | 心跳档:UA `atan2(-6,7)`=−40.6°、UN `atan2(-7,-6)`=−130.6° → 差 **+90.0°**(落 80°~100°,跟随成立);**录波档 WAVE09 段(2026-11-04 19:15:32 触发)16 个整周波全过**:UA 995.89 mV ∠−122.50°→−122.66°、UN 996.84 mV ∠+147.46°→+147.29° → Δφ(UA−UN)= **90.05°**,与 90° 设定偏差 **0.05°**,逐周波漂移 ≤0.01° | **PASS**(录波档 ≤2°;心跳档与录波档两口径互合) |
**段文件定量档的具体动作(相位轮首选,零改代码)**
**录波档的具体动作(相位轮首选,零改代码)**
1. `UA`(R31)与 `UN`(R28)两路接同一发生器输出(同相),注 **1.000 Vrms / 50.000 Hz**;
2. 稳定 ≥30 s 后,在 UI「手动录波」确认页触发一次;成段有**秒级**延迟(落盘在 defaultTask,卷空闲后一次心跳搬完整段)
→ 等串口 `wave:` 行的 `seg=` +1、`armed` 回 0 再去取文件;
3. FTP 取 `/wave/WAVE<NN>.BIN`(本轮那一段的确切号),**取完立刻改名**——100 个号轮转会被覆盖;
4. 主机侧跑:`& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --all`
—— 每个整周波给一行:基波 RMS、相位、2 次与 5 次谐波 RMS(单位 mV,系数镜像 `App/algo/phasor.c`);
5. 判据:`UA`(`--ch 0`)与 `UN`(`--ch 3`)在**同一周波**内的相位差——同相注入应 ≈0°,把发生器(或 B 通道)改 90° 后应 ≈90°,
容差 ±2°(`docs/任务规划/数据可信.md` §4.6.2"相位轮"行);
6. ⚠ 段的**绝对**相位与发生器没有锁定关系(触发瞬间固定落在段内点 159),所以**只能比同段内两路的相对差**;
跨段比相位、或拿发生器相位当绝对基准都不成立(段内点序见 `docs/子系统设计/故障录波设计说明.md` §6.2);
7. 交叉旁证:同一段先 `dump`(逐字段 + 双 CRC16,看封存瞬间的标定与品质快照),再 `plot --ch 0`(CRC 不符或零长度它
**必须拒收**而不是画出半张图);板上 `phasor(hb)` 的 `Re` / `Im` 象限应与脚本给的象限一致(×0.1 只够核象限)。
1. `UA`(R31)与 `UN`(R28)两路接同一发生器输出(同相),注 **Vpp 2.828 V / 50.000 Hz**;稳定 ≥30 s 后在 UI「手动录波」确认页触发一次;成段有**秒级**延迟(落盘在 defaultTask,卷空闲后搬段)→ 等 `wave:` 行 `seg=` +1、`armed` 回 0 再取文件;
2. FTP 取 `/wave/WAVE<NN>.BIN`,**取完立刻改名**(100 号轮转会被覆盖);
3. 跑 `& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --all`——每个整周波给一行:基波有效值、相位、2 次与 5 次谐波有效值(mV,系数镜像 `App/algo/phasor.c`);
4. 判据:`UA`(`--ch 0`)与 `UN`(`--ch 3`)在**同一周波**内相位差——同相 ≈0°,改 90° 后 ≈90°,容差 ±2°(`docs/任务规划/数据可信.md` §4.6.2);
5. ⚠ 段**绝对**相位与发生器无锁定(触发瞬间落在段内点 159)→ **只能比同段内相对差**,跨段比相位不成立(段内点序 `docs/子系统设计/故障录波设计说明.md` §6.2);
6. 交叉旁证:同一段先 `dump`(逐字段 + 双 CRC16),再 `plot --ch 0`(CRC 不符或零长度**必须拒收**);板上 `phasor(hb)` 象限应与脚本一致(×0.1 只够核象限)。
#### 测频轮(默认 A 档 = 判"如实报不可测")
#### 测频轮(现状:测不出频率才是对的)
一句话:给 `UA` 注与幅值轮同样的 Vpp 2.828 V / 50.000 Hz 正弦,看串口 `freq(hb)` 行——**固件现状就不该出数**,出数反而是算错了。
| 项 | 内容 |
|---|---|
| 接法与设定 | 同幅值轮(`UA`,1.000 Vrms / 50.000 Hz) |
| 读数 | `freq(hb): f=.. valid=..`;UI 交流量页 `Hz`;104 `0x401D` |
| 期望(A 档 = 现状) | **就是不出数**:幅值门 `FREQ_AMP_MIN 50.0f`(`App/algo/freq.h:62`,`App/algo/freq.c:231` 拿它与基波有效值比)在引脚电压域恒不过 → `valid=0`、`f=0`、UI `Hz` 显 `--`、104 `0x401D` 上送 IV |
| 接法与设定 | 同幅值轮(`UA`,Vpp 2.828 V / 50.000 Hz) |
| 读数 | `freq(hb): f=.. valid=..`;UI 交流量页 `Hz` |
| 期望(现状固件) | **就是不出数**:幅值门 `FREQ_AMP_MIN 50.0f`(`App/algo/freq.h:62`,`App/algo/freq.c:231` 拿它与基波有效值比)在引脚电压域恒不过 → `valid=0`、`f=0`、UI `Hz` 显 `--` |
| 判据 | **如实报不可测**,而不是报一个看起来对的 50.00。若门限没改却报出 50.00 → 反而是 bug,停下查 `App/algo/freq.c` |
| B 档(要用户先点头) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`/keil-build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**,收尾判据见 §8.9 |
| 坑 | 非整 50 Hz 时 `meas:` 会拍频式跳动,属预期 → **扫频读 `freq(hb)`,别读 `meas:`**(`docs/任务规划/数据可信.md` §8 风险表同一条);测频的取源顺序是先 `UA`(相量实例 C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——本卡注 `UA` 走的就是 UA 源;若哪天只注 `IA`,测频会跟着 IA 走,别以为它锁在 UA |
记录表(A 档):
| 频率设定 (Hz) | `freq(hb)` f | valid | UI `Hz` | 104 `0x401D` 品质 | 结论 |
|---|---|---|---|---|---|
| 50.000 | | | | | |
#### 削顶轮:触底与品质路由(负向用例)
- **判据**(`docs/任务规划/数据可信.md` §4.6.2"削顶轮"行):给 `UB`(R52 / `AIN2` / U5.B7)注 **DC −1.00 V 保持 ≥5 s**
或 **1.000 Vrms / 2.0 Hz** 的正弦 → 负半周钉下轨 → 连续 20 拍同侧后该路贴轨位锁存、`edge` 起沿、104 该路 IV 且值清零。
- ⚠ **注 50 Hz 同幅正弦造不出这个锁存,这是实现约束不是固件缺功能**:锁存条件是"连续 `ADC_RAIL_N_RUN = 20` 拍
贴同一侧轨带"(`App/algo/adc_health.h:174`),而 `adc_health_rail_watch()` 里**任何一拍回到中带就把两侧计数清零**;
1 kHz 下一个 50 Hz 周波只有 20 个点、正半周必然落在中带 → 连续贴底最长 ≈ 209°/360° × 20 ≈ **11 拍 < 20**。
- **轨带数值**(`App/algo/adc_cal.c:452` 的 `adc_cal_rail_ths()`,TEMP 不监视):STM32 侧贴底 = 码 ≤ **81**(2 % FS ≈ 65 mV)、
贴顶 = 码 ≥ 4013;TPAFE 侧 = ±32112 码(≈ ±4.9 V)→ **TPAFE 侧这一条在 ±2 V 判据上限内根本造不出来**,
只有 STM32 那五路能做。
- **改法 A(首选,零固件改动)**:发生器切 **DC 输出 −1.00 V** 注 `UB` 端子侧,保持 ≥5 s。期望:`cal:` 的 `rail=0x002`、
`edge=0x002`、一条 `[ERR] rail: edge=0x2 sticky=0x2 (code-domain, 20-run+release)`(`App/task/adc_task.c:428`)、
104 `0x4012` 该路 IV 且值清零、`adc: UB` 码值贴 0。撤掉注入后 **sticky 位不随输入恢复而清**(锁存语义,
只有 `adc_health_rail_init()` / 复位清)→ 复位一次并把"是否随复位清"记进表:这条实测行为是本轮的产出之一。
- **改法 B**:发生器 1.000 Vrms 但**频率设 2.0 Hz** → 负半周 250 ms = 250 拍贴底 ≥ 20 → 锁存;
顺带证"贴轨检测与工频无关"这条口径。
- **50 Hz 档照跑,判据就是"不锁存"**:记下 `adc: UB` 负半周钉 0 而 `rail` 仍 `0x000` —— 这一档证的是
"锁存要连续 20 拍同侧"这条实现约束本身,不是失败,也不是战果。
- **反向假阳性**:反过来注一个峰值 <65 mV(<46 mVrms)的 50 Hz 小信号,会让 `UB` 一路贴底而**锁存**——
那是"信号小到进不了中带",不是削顶。读 `rail` 时要把这条一起排除。
- **与 `cl` 区分**:`cl=0xE6` 是**来源派生**的静态触底掩码(`docs/任务规划/数据可信.md` §2.6),削顶轮要的是动态
`rail=` / `edge=`;别拿 `cl` 当本轮战果。
| 另有一档(需先取得同意) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`/keil-build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**(撤销补丁 + 重编 + 重烧,`git diff` 核对干净)。这一档要改固件重烧,动它之前先问过你;不做也不影响上面那条判据成立 |
| 坑 | 非整 50 Hz 时 `meas:` 拍频式跳动属预期 → **扫频读 `freq(hb)` 别读 `meas:`**(`docs/任务规划/数据可信.md` §8 同一条);取源先 `UA`(C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——注 `UA` 走 UA 源;若只注 `IA`,测频跟 IA 走 |
记录表:
| 步 | 设定 | `rail` | `edge` | `[ERR] rail:` 行数 | 104 `0x4012` | 复位后 `rail` | 结论 |
|---|---|---|---|---|---|---|---|
| 1 | 50.000 Hz / 1.000 Vrms 正弦 | | | | | 不适用 | |
| 2 | DC −1.00 V(改法 A) | | | | | | |
| 3 | 2.0 Hz / 1.000 Vrms(改法 B) | | | | | | |
| 频率设定 (Hz) | `freq(hb)` f | valid | UI `Hz` | 结论 |
|---|---|---|---|---|
| 50.000 | 0 | 0 | 未记(期望 `--`,与 fv 同源,无补测价值) | **PASS**:冻结窗口 229 行 `freq(hb)` **全部** `f=0.00 valid=0 src=0`(含 UA 满幅 Vpp 2.828 V 注入时段)——幅值门 `FREQ_AMP_MIN=50.0f`(`App/algo/freq.h:62`、`App/algo/freq.c:231`)拿**电压域有效值**比(`phasor(hb)` rms=9 = ×0.1 V 刻度),引脚域 ≈1 V 恒不过门 → **如实报不可测**,与现状判据逐字一致。**2026-11-04 复证**:19:10–20:01 采集窗 332 行 `freq(hb)` 依旧**全部** `f=0 valid=0`(含 UA+UN 双路满幅 90° 注入段与 IA 各注段)——判据在补测轮同姿势复现 |
**段文件档的两把尺子(现在能把"来源派生 vs 实测"一次核清)**
#### 削顶轮:触底与品质路由(负向用例)
`shape` 拿**实测**贴轨判定与段头 `clip_mask` 对表;而段头那一位**来自通道来源、从不实测**(`test/host/wave_read.py`
文件头的实证与本卡上面的 `cl=0xE6` 同源,两处互证)→ 三种注入分别配三把尺子:
- **判据**(`docs/任务规划/数据可信.md` §4.6.2"削顶轮"行):给 `UB`(R52 / `AIN2` / U5.B7)注 **DC −1.00 V 保持 ≥5 s** 或 **Vpp 2.828 V / 2.0 Hz** 正弦 → 负半周钉下轨 → 连续 20 拍同侧后该路贴轨位锁存、`edge` 起沿。
- ⚠ **50 Hz 同幅正弦造不出锁存,是实现约束不是固件缺功能**:锁存条件"连续 `ADC_RAIL_N_RUN = 20` 拍贴同一侧轨带"(`App/algo/adc_health.h:174`),`adc_health_rail_watch()` 里**任何一拍回中带就清零**;1 kHz 下一个 50 Hz 周波只有 20 点、正半周必落中带 → 连续贴底最长 ≈ 209°/360° × 20 ≈ **11 拍 < 20**。
- **轨带数值**(`App/algo/adc_cal.c:452` `adc_cal_rail_ths()`,TEMP 不监视):STM32 贴底 = 码 ≤ **81**(2 % FS ≈ 65 mV)、贴顶 = 码 ≥ 4013;TPAFE = ±32112 码(≈ ±4.9 V)→ **TPAFE 侧在 ±2 V 判据上限内造不出来**,只有 STM32 五路能做。
- **直流削顶档(首选,零固件改动)**:发生器切 **DC −1.00 V** 注 `UB` 端子侧,保持 ≥5 s。期望:`rail=0x002`、`edge=0x002`、一条 `[ERR] rail: edge=0x2 sticky=0x2 (code-domain, 20-run+release)`(`App/task/adc_task.c:428`)、`adc: UB` 贴 0。撤掉注入后 **sticky 位不随输入恢复而清**(只有 `adc_health_rail_init()` / 复位才清)→ 复位一次记录"是否随复位清",本轮战果之一。
- **2 Hz 低频削顶档**:发生器 Vpp 2.828 V 但**频率设 2.0 Hz** → 负半周 250 ms = 250 拍贴底 ≥ 20 → 锁存;顺带证"贴轨检测与工频无关"。
- **反向假阳性**:注 Vpp <130 mV(峰值 <65 mV)的 50 Hz 小信号会让 `UB` 贴底**锁存**——"信号太小进不了中带",不是削顶。读 `rail` 时一并排除(`cl=0xE6` 静态掩码另见 §8.2 前置门第 3 条,别当本轮战果)。
记录表:
| 步 | 设定 | `rail` | `edge` | `[ERR] rail:` 行数 | 复位后 `rail` | 结论 |
|---|---|---|---|---|---|---|
| 1 | 50.000 Hz / Vpp 2.828 V 正弦 | 0x000 | 0x000 | 0 | 未测 | ✓ 不锁存(正弦连续贴底 ≈11 拍 < 20,本步证"连续 20 拍"约束本身,非战果非失败) |
| 2 | DC −1.00 V ≥5 s(直流削顶档) | **0x002** | **0x002** | **1** | 未复核(用户裁定 2026-11-04 不做) | **2026-11-04 19:31:42 已做(注 `UB`)**:串口唯一一条 `[ERR] rail: edge=0x2 sticky=0x2 (code-domain, 20-run+release)`;`adc: UB` 由 ≈1005 码钉到 **0**;**撤注入后 rail=0x002 edge=0x002 恒持到日志尾(20:01+,≥30 min)= sticky 锁存语义实证**。"是否随复位清"未验——2026-11-04 用户裁定不复核("sticky 只有复位清"本为 `App/algo/adc_health.c` 的实现语义,不占台架时间) |
| 3 | 2.0 Hz / Vpp 2.828 V(低频削顶档) | 0x002(已被步 2 锁存掩蔽) | 0x002 | 0(无新增) | 未记 | **本窗未见 2.0 Hz 注入痕迹**:19:36:41 触发的录波段(WAVE11,锁存态下取)八路波形全 ≈0、`UB` 恒钉零码 = 撤注后取段,不是 2 Hz 工况。此步**用户裁定 2026-11-04 不补做、关闭**(直流档已证锁存与 sticky,低频档的附加信息"贴轨检测与工频无关"一并裁不要)。将来若复跑削顶轮(如标定态),**必须先复位清掉步 2 的 sticky 再做**(否则锁存态掩蔽,判不出 2 Hz 自己能否起沿) |
**段文件档的两个量具("来源派生 vs 实测"一次分清)**
`shape` 拿**实测**贴底与段头 `clip_mask` 对表;段头 bit **来源派生、从不实测**(`test/host/wave_read.py` 文件头与 `cl=0xE6` 同源互证)→ 三种注入各配一个量具:
| 注入 | 命令 | 期望 |
|---|---|---|
| 改法 A(`UB` 注 DC −1.00 V 保持 ≥5 s) | `shape WAVE<NN>.BIN --strict` | 实测贴底落进 bit1;段头静态已含 bit1 → 等式成立。**实测冒出段头没有的位**才算失败 = 有别的路被推到轨上,先查接线 |
| 极小注入 / 未驱动通道 | 同上再加 `--clip-superset` | 换成"段头覆盖实测"这把尺子:容忍"封存了但本轮没贴轨";实测有、段头没有仍然失败 |
| 原接法(50 Hz 正弦注 `UB`) | `shape WAVE<NN>.BIN`(不带 `--strict`) | 只读逐通道贴轨游程:正弦过零必回中带,`--min-run` 默认 4 也可能判不出贴轨——**别把"没判"读成 bug** |
| 直流削顶档(`UB` DC −1.00 V ≥5 s) | `shape WAVE<NN>.BIN --strict` | 实测贴底落 bit1;段头已含 bit1 → 等式成立。**实测冒出段头没有的 bit** = 失败,先查接线 |
| 极小注入 / 非驱动通道 | 同上 + `--clip-superset` | "段头覆盖实测":容"封存了但没贴底";实测有、段头没有仍判失败 |
| 原接法(50 Hz 注 `UB`) | `shape WAVE<NN>.BIN` | 只读贴底 run:正弦过零必回中带,`--min-run` 默认 4 判不出——**别把"判不出"读成 bug** |
`--min-run`(连续几点算贴轨)与固件的"连续 20 拍"是**两把不同的尺子**,别互相换算;固件侧结论仍以 `rail:` / `edge=`
位图为准(本卡上半段)。
`--min-run` 与固件"连续 20 拍"是**两个量具**,别互换;固件侧结论以 `rail:`/`edge=` 为准(本卡上半)。
#### 谐波轮:谐波轨(**段文件 `fft` 定量档 + UI 目检旁证**)
#### 谐波轮:谐波卡(录波档 fft + UI 目检旁证)
| 项 | 内容 |
|---|---|
| 接法 | 发生器 → `IA`(R19 / `AIN8` / U5.B5)端子侧 |
| 设定 | 基波 + 20 % 二次 + 20 % 五次(发生器有谐波叠加 / ARB 模式就用它) |
| ⚠ 峰值预算 | 1.000 Vrms 基波 + 两个 20 % 分量的最坏对齐峰值 ≈ 1.414 + 0.283 + 0.283 = **1.98 V,已顶到 ±2 V 判据上限**。所以基波改 **0.800 Vrms**、二次与五次各 0.160 Vrms,最坏峰值 ≈ 1.58 V 留余量;期望值随之换算 |
| 主读数 | UI 交流量页 `Ia2` / `Ia5` 两行——**3 位小数 = 1 mV**(`Drivers/BSP/UI/ui_page_ac.c:47`),目检 + 拍照;远端可走 7003 看图截屏 |
| 期望与判据 | 期望 ≈ 各分量注入值(0.160 Vrms)±2 % |
| 旁证 | `phasor(hb) C04-IA-F2` / `C05-IA-F5` 的 `rms=`:×0.1 只有 1~2 格,**只能证明这根轨不是恒 0**,判不了 ±2 %(§8.1) |
| 没有谐波叠加功能时 | 分三次单注:只注 0.160 Vrms / 100 Hz → `Ia2` 有数、`Ia5` 应 ≈0;只注 0.160 Vrms / 250 Hz → `Ia5` 有数、`Ia2` 应 ≈0;再注纯基波 → 两行都 ≈0。这一档证的是"两根轨各认各的次",**不证叠加工况**,结论里要写明 |
| 口径 | 这两根轨**目前没有任何下游消费者**(`App/algo/prot_elm.c` 无引用、`meas` 只算基波)→ 谐波轮只证"算得对",不证"有人用";出口归属 `docs/任务规划/数据可信.md` §9 的谐波轨立项 |
| ⚠ 峰值预算 | 基波 Vpp 2.828 V + 2 个 20 % 分量,最坏对齐峰值 ≈ 1.414 + 0.283 + 0.283 = **1.98 V,已顶到 ±2 V 判据上限**。所以基波改 **Vpp 2.263 V**、二次与五次各 Vpp 0.453 V,最坏峰值 ≈ 1.58 V 留余量;期望值随之换算 |
| 读哪个数 | UI 交流量页 `Ia2`/`Ia5`(3 位小数)——**1 mV**(人工目检 + 拍照留证,`Drivers/BSP/UI/ui_page_ac.c:47`);谐波幅值、交叉旁证 |
| 期望与判据 | 板上读数(有效值刻度)≈ 各分量 0.160 V(对应设定 Vpp 0.453 V)±2 % |
| 旁证 | `phasor(hb) C04-IA-F2`/`C05-IA-F5` 的 `rms=`:×0.1 只 1~2 格,**只能证"该轨非恒 0"**,判不了 ±2 %(§8.1) |
| 无谐波叠加功能时 | 三次单注:只注 Vpp 0.453 V / 100 Hz → `Ia2` 有数(读数 ≈0.160 V)、`Ia5` 应 ≈0;只注 Vpp 0.453 V / 250 Hz → `Ia5` 有数、`Ia2` 应 ≈0;再注纯基波 Vpp 2.263 V → 两轨都 ≈0。这一档证"两轨各认各的次",**不证叠加工况**,结论里写清 |
| 口径 | 这两轨**当前无下游消费者**(`App/algo/prot_elm.c` 无引用、`meas` 只算基波)→ 谐波轮只证"算得对",不证"有人用";这两轨归谁用,见 `docs/任务规划/数据可信.md` §9 谐波轨立项 |
记录表:
| 注入分量 | 发生器设定 | UI 读数 (V) | 期望 (V) | 偏差 % | `phasor` 旁证 | 结论 |
|---|---|---|---|---|---|---|
| 基波 0.800 Vrms + 二次 0.160 + 五次 0.160 | | `Ia2=` / `Ia5=` | 0.160 / 0.160 | | | |
| A 只注二次 | 100.00 Hz / Vpp 0.453 V | `Ia=0` / `Ia2=0.158` / `Ia5=0` | 0.160 / ≈0 | **−1.25 %** | —(A/B 步落在 18:22–18:28 日志盲区,心跳旁证未留) | **过**(±2 % 内;选择性成立——100 Hz 没漏进基波 / 五次桶) |
| B 只注五次 | 250.00 Hz / Vpp 0.453 V | `Ia=0` / `Ia2=0` / `Ia5=0.153`(2026-11-04 复测;11-02 初读 0.152) | ≈0 / 0.160 | **−4.4 %**(超 ±2 %) | —(两轮均未留 250 Hz 心跳旁证——带外信号 `meas:` 判不了,见上卡旁证行) | **过——已按「板端真实衰减」关闭(2026-11-04)**:−4.4 % = IA 前端差分滤波(C35 = 100 nF + R19/R21 各 1 k,一阶低通 fc ≈ 796 Hz)在 250 Hz 的真实衰减,**板端读数忠实,量与算都没错**(模型与数据 `docs/硬件平台/ADC通道映射说明.md` §8.2);工频 50 Hz 仅 −0.2 %,不受影响;选择性成立(基波 / 二次桶 = 0);谐波功能要板改(C35 换 10 nF)——登记 `docs/任务规划/登记簿.md` §3 |
| C 纯基波 | 50.00 Hz / Vpp 0.453 V(实际按 0.453 注,Vpp 2.263 那档未做) | `Ia=0.160` / `Ia2=0` / `Ia5=0`(2026-11-04 复测;11-02 初读 0.159) | ≈0 / ≈0 | 基波 **0.0 %**(对 0.160 读数;11-02 为 −0.6 %) | **有**:18:33:43 起串口可见窗 `C01 rms=1 / C04 rms=0 / C05 rms=0`,与 C 步工况吻合;2026-11-04 复测窗串口 `ia_mv=159↔160` 同刻印证屏显 | **过**(两谐波轨零值经串口旁证补认;基波读数两轮 ≤0.6 %,与幅值轮同档一致) |
| 叠加(原行,**本轮不做**) | 基波 + 二次 + 五次同段 | — | — | — | — | 发生器无谐波叠加 / ARB 功能 → 本轮不做·设备缺口·TC-PHASOR-006 单测覆盖(`test/host/phasor/phasor_test.c`,3 条谱线各 ±1 %,实跑 57/57 PASSED) |
**段文件定量档(谐波现在判得到位)**
**录波档动作(谐波现在判得到位)**
同一次注入(基波 0.800 Vrms + 二次 0.160 + 五次 0.160,峰值预算见上表)触发一段、FTP 取回后跑:
同一次注入(叠加档设想值:基波 Vpp 2.263 V + 二次与五次各 Vpp 0.453 V,峰值预算见上表;本轮发生器无叠加功能、未跑此档)触发一段,FTP 取回跑:
```text
& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --ch 4 --all
```
`fft` 逐整周波给**基波 RMS + 2 次与 5 次谐波 RMS**(mV,浮点重算),±2 % 在这条档是定量判据;`--ch` 是段内通道表序,
`IA` = 4(表序 `UA UB UC UN IA IB IC IN`,TEMP 不录,`App/algo/wave_ring.h:216`)。UI 目检与 7003 截图降为**旁证**——
它证的是"板上 `Ia2` / `Ia5` 与脚本算的是同一件事"(差值应在显示位数之内),不是替代判据。THD 与方和根仍不在本轮,
口径见 `docs/任务规划/数据可信.md` §4.6.5。
### 8.6 顺序与耗时(一次台架 session 跑完)
| 序 | 做什么 | 预计 | 备注 |
|---|---|---|---|
| 0 | 前置门 + 基线 30 s | 10 min | §8.2,不满足就停 |
| 1 | 幅值轮(含 0.5 / 0.2 Vrms 加做) | 15 min | 主用例 |
| 2 | 波形轮(方波 / 三角,峰值对齐) | 15 min | 抬头里记万用表响应类型 |
| 3 | 谐波轮:段文件 `fft` 定量档 + UI 目检旁证(同一次注入触发一段) | 25 min | 命令见 §8.5 的谐波卡 |
| 4 | 相位轮:段文件相对相位档(`UA` / `UN` 同源,再移 90°) | 20 min | 定性档只在没取到段时退用 |
| 5 | 测频轮 A 档 | 5 min | 判"如实报不可测" |
| 6 | 削顶轮:50 Hz 档 + 改法 A + 改法 B + 复位复观(每档各触发一段) | 30 min | **单独一份日志**,判读规则见 §8.7 |
| 7 | 同一份接线下把 104 交叉读数复跑一遍 | 15 min | `test/host/iec104_master.py` |
| 8 | 取段与脚本产物归档(FTP `get` + `dump` / `fft` / `shape` / `plot`) | 15 min | 每段取完立刻改名,100 号轮转会覆盖 |
| — | (需用户点头)测频轮 B 档 / 相位轮抬倍数档 | +40 min | 各含一次重编、烧录与还原;段文件落地后相位轮这条基本不必走 |
合计约 **135 min**(不含"需用户点头"那两档)。取段与跑脚本集中在末尾第 8 行统一做,中间各条只负责"触发 + 让
`seg=` +1",免得一次 session 里反复插拔网线。
### 8.7 `[ERR]` 的判读规则(削顶轮单独跑、单独归档)
- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `/serial-boot-test` 的 forbidden 标记含
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `/serial-boot-test` 的标记校验当结论**:改人工判读,并在归档文件名与
抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。
- 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 →
出现 Fault 立即停线取现场(`docs/子系统设计/看门狗设计说明.md` §10),别再继续注。
- 每次开机 `rail:` 边沿只打一条、`SYSLOG` 也只留痕一次("adc code rail fault" 进 `selftest.log`)——这是设计不是漏报;
要看第二次边沿就复位一次。
### 8.8 失败分诊(先查这一列,别急着改固件)
| 现象 | 先查 | 依据 |
|---|---|---|
| 与万用表差约 2 倍 | 发生器输出端接设成了 50 Ω | §8.2 表第 2 行 |
| 差约 5.3 倍,或数字带 6553.6 / 1241.2 的痕迹 | 拿错刻度,或把 `adc:` 的码值当 mV 读 | §2 两把尺子、§4「码是什么」 |
| `phasor` 行 `rms` 只有 10 格量级 | 正常(×0.1),不是 bug | §4 尾注、§8.1 |
| 非整 50 Hz 时 `meas:` 来回跳 | 预期:瞬时透传、200 ms 不是平均窗 → 改读 `freq(hb)` | `docs/任务规划/数据可信.md` §8 风险表 |
| 104 读数不动 | 每点 0.5 V / 0.005 A 变化死区(生效值 `App/algo/run_par.c`) | §8.5 的幅值卡坑 ② |
| 50 Hz 正弦注 `UB` 但 `rail` 不锁存 | **预期**(连续贴底不足 20 拍)→ 改法 A / B | §8.5 的削顶卡 |
| 改 DC 也不锁存 | 释放沿前提没满足:开机后该通道没出现过中带样本 → 先建基线再注入 | `App/algo/adc_health.c`、§8.2 前置门末条 |
| 撤掉注入后 `rail` 仍非 0 | 锁存语义(sticky),只 `adc_health_rail_init()` / 复位清 → 复位后仍不清**才是** bug | §8.5 的削顶卡改法 A |
| 读数带与幅度无关的偏置或漂移 | 共地 / 地环 | §8.2 末行 |
| `meas: seq` 不涨 | 发布闸门停 → 先查采集链,别判交流 | §8.2 前置门 |
| `health:` 或 `sampling fault:` 亮 | 与注入的波形无关:`adc_health` 的位只吃序号差、错误计数增量与 tick 差(`App/algo/adc_health.c:91-189`)→ 查节拍 / 丢帧,别查发生器 | §8.2 前置门 |
| 9 路全 0 | 读作"没数据",不是"输入为零" | §4 尾注 |
| `wave_read.py` 报错退出 / 拒绝出图 | **这是正确行为**:CRC 不符或零长度它必须拒收而不是画出东西 → 重取一份、核对是不是搬到一半的半成品;仍拒收就 `dump` 看两处 CRC16 哪个不符 | `test/host/wave_read.py` 文件头 |
| `shape --strict` 说实测与段头不等 | 先分清尺子:段头 `CLIPPED` 位来自**通道来源、从不实测**;DC 偏置 / 极小注入 / 未驱动通道应改用 `--clip-superset` | §8.5 的削顶卡段文件档 |
| 触发后 `seg=` 不涨或 `drop` / `ovf` 涨 | 落盘道在 defaultTask 卷空闲后才搬段,先等几秒;仍不涨查 `wave:` 行三计数与 `/wave` 目录可写(`[ftp]` 写权限) | `App/task/adc_task.c:782` |
### 8.9 收尾判据(走过临时改动的一条都不许留)
| 临时改动 | 收尾判据 |
|---|---|
| 测频轮 B 档:`FREQ_AMP_MIN` 50.0f → 0.5f(`App/algo/freq.h:62`) | 还原 + `/keil-build` 0 错误 0 警告 + **`git diff` 里 `App/algo/freq.h` 必须为空** + 在 `docs/任务规划/数据可信.md` §7 #8 那行留一句"本轮台架已还原" |
| 相位轮改倍数:`log_phasors()` 的 `* 10.0f` → `* 1000.0f`(`App/task/adc_task.c:209`) | 同上,`git diff` 里 `App/task/adc_task.c` 必须为空 |
| 为 bench 定时自抓开过编译门(`DTU_BENCH` + `WAVE_BENCH_AUTOCAP` 非 0,`App/task/adc_task.c:153`) | 还原成默认 0 + `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/task/adc_task.c` 必须为空(产品镜像里这段必须零存在) |
| 只动接线与发生器、没动代码 | `git status` 除归档目录外干净 |
| 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 |
### 8.10 归档与回写(跑完必做,缺一算没收尾)
1. **证据归档** → `docs/测试说明/测试数据/boot_log_data/`,沿用现有命名惯例(日期_主题_类型.txt):
`20261104_ac_amp_meas_log.txt`、`20261104_ac_clip_rail_log.txt`、`20261104_ac_iec104_log.txt`;UI 目检照片同目录
(该目录若不收图,就在抬头里写清照片存放位置)。**走段文件定量档的用例另加两样**:取回的段原件(原始字节,
`20261104_ac_phase_wave<nn>.BIN`)与脚本输出(`fft` / `shape` / `dump` 三份文本 + `plot` 的图)——定量档的证据本体是
**那一段原始码值**,日志只是旁证;段原件一旦覆盖就再也拿不回来,先改名再触发下一段。
2. **实测结论回写** → `docs/硬件平台/ADC通道映射说明.md`,在 §8.1(直流轮次)之后**新开一节**——
任务卡 `docs/任务规划/数据可信.md` §5 的 DoD 就是这么分的。本文只留"怎么跑",**不抄实测数**。
3. **本文回写** → 只在 §8.1 那张表下加一行指针级别的句子("YYYY-MM-DD 已按本节跑通,实测见 …");
不在本文建第二份结论表,否则两份必然漂移(`docs/文档规范.md` 同一事实只在一处详述)。
4. **判据文档的一致性维护** → 本文 §8 的期望值算术、接法与出口分档已写进
`docs/任务规划/数据可信.md` §4.6.2(2026-10-29:波形轮的逐波形系数、削顶轮的 DC / 2 Hz 接法与 50 Hz 档的
"不锁存属预期"、相位轮与谐波轮的"定量档走段文件、定性档走 `phasor(hb)`")。今后两边若再分叉,按开头引导块的
两类裁定处理(语义从那节、算术与步骤从本文),**别在本文另立第二份判据表**。
5. **销项** → `docs/任务规划/数据可信.md` §2.7 与 §6 卡片表里这一条按登记簿「完成即销项」处理,属 Ask first,
须用户明确指示后再做。
### 8.11 这一轮证不到(别让读数长得像证据)
`docs/任务规划/数据可信.md` §4.6.5 那张五行表整条适用(端子二次值 / 精度等级 / 跨转换器相位与功率与对称分量 /
THD 与谐波方和根 / 温升 EMC 长时间漂移)只适用,本节按出口已落地改三条现状(2026-10-29 录波数据面并入主干之后):
**样本出口已成立**(`/wave/WAVE<NN>.BIN` + `test/host/wave_read.py`)→ 相位轮 / 谐波轮走定量档时结论是**形状级**,不再只是标量级;
但**板级那一轮对表本身还没跑过**(判据见 `docs/子系统设计/故障录波设计说明.md` §10),本轮跑出来的就是第一批实测;
段与发生器**没有绝对相位锁定**(触发瞬间只钉在段内点 159)→ 能核的是同段内两路的**相对**相位与极性,
全局相角基准仍证不到;一次触发只有 320 点 = 320 ms,**不等于长时间稳定性**。示波器仍旧只作旁证、不进判据。
再补一条「别把台架做成功能性重复」的边界:**算法本体的精度已经在纯数字域钉住了**——`test/host/phasor/phasor_test.c`
的 TC-PHASOR-003 与 TC-PHASOR-004(基波 = 峰值 / √2 容差 1 %、注入相角容差 0.5°)和 TC-PHASOR-006(2 / 3 / 5 次
谐波 1 %),以及 `test/host/freq/freq_test.c` 的扫频判据 J1(45~55 Hz 内 ≤0.02 Hz),都是把已知正弦**直接喂进
`sample_ring`**,绕过了 `adc_cal` 的折算(那里的幅值是工程量口径,不是引脚 µV)。所以 §8 这一轮真正要证的只有
「信号走到 `sample_ring` 之前」那一段:发生器刻度与输出端接、前端串阻与滤波电容的幅频、两片转换器的同刻性、
码到 mV 的折算、出口的量化。这一轮过了**不等于**「算法精度已实测验证」,结论里别那么写。
`fft` 每整周波给基波与 2 次/5 次谐波有效值(mV,浮点重算),±2 % 是定量判据;`--ch` = 段内通道表序,`IA` = 4(`UA UB UC UN IA IB IC IN`,TEMP 不录,`App/algo/wave_ring.h:216`)。UI 目检只是**旁证**(证"板上与脚本算的是同一个东西"),不是替代判据。THD 与根均方不在本轮(`docs/任务规划/数据可信.md` §4.6.5)。
@@ -17,9 +17,25 @@
## 2. 为什么"容量"需要单独恢复(根因)
- 健康满容量 = **96064 pages(187 MB)**(`No valid map found, creating fresh` 分支下的满值)。
> ⚠ 2026-11-04 台架待核(问题 #79):三次捕获都打 `capacity: 97504 pages = 190 MB`
> (比本基线大 1440 页),且**故障前后数值一致**(不是被扣损的结果)。两种可能未定:
> ① 台架那块板的 `bb_last/bb_current` 与"全新初始化"不同口径;② 本文基线陈旧。
> 判定方法:台架跑一次动作 B(全片擦除 + 断电重启,走 `dhara_map_clear` 全新分支)
> 后读同一行的 `capacity:`——若仍是 97504,则本文基线应改。(另:本行是**故障本身
> 无关**的证据,见 架构说明 §10.4.1:容量正常 ⇒ 不是块池耗尽。)
> **2026-11-04 更新(问题 #79)**:当日跑的是动作 A(非 B),BBT 重建 0 坏块 +
> format 后容量仍报 97504=190 MB;且 129 个误标坏块存在期间读数也是 97504
> (误标只毁 journal 写头,不改本行读数)。基线改判**仍需动作 B**,未跑,本行保留。
- dhara 的坏块计数 `bb_current` / `bb_last` **持久化写在 journal checkpoint 头里**
(`Lib/dhara/journal.c` 的 `hdr_set_bb_current/hdr_set_bb_last`,随 checkpoint 一起落盘)。
容量按 `good_blocks = num_blocks − max(bb_last, bb_current) − 1` 递减。
- **⚠ 还有一处独立于 dhara 计数的坏块持久化**:板级 BBT(`gd5f2gq5ue.c` 的
`s_bbt[]` 轮转池,`mark_block_bad` 每次**立即落盘、不可撤销**)。2026-11-04 的
台架故障根因即在此:传输层失败被旧 `dhara_nand_erase/prog()` 误当坏块证据标坏,
129 个好块固化进 BBT,journal 写头爬进误标段 → `prepare_head()` 连续 8 次
`is_bad` → `TOO_BAD` → 整卷禁用(机理见 架构说明 §10.4.2)。这种"误标卷"的**处置
就是动作 A**(重扫出厂坏块会覆盖误标并持久化);A 后若 `BBT after merge` 再报
非零增量,看的是新增标坏日志 `Erase/Program ... (not a bad block)` 有没有出现。
- `dhara_journal_clear()`(即 `nand_ftl_format()` 走的路)只做 `bb_last = bb_current`,**不归零**——
所以动作 A 清不掉被扣掉的容量,断电重扫也一样(计数从 checkpoint 读回)。
- 只有让下次启动 `dhara_map_resume` **失败**、走 `dhara_map_clear` 的**全新初始化**分支
@@ -27,6 +43,23 @@
- 记账的触发:一次 `program verify` 失败(日志 `[ERR][NAND_FTL] program verify: data mismatch, blk N`)
就让 dhara 多记一个坏块;破坏性套件(写完擦、坏块注入、格式化)会累积这个数字。
### 2.1 动作 A 在 2026-11-04 之前是**静默失效**的(问题 #79,已修)
`nand_ftl_format()` 旧实现的第一步是 `if (ftl_flush_cache() != BSP_OK) return GD5F_ERROR;`,
而 `ftl_flush_cache()` 失败时刻意**不清** `s_cache_dirty`。于是只要脏页落在"写不动的那个
LPN"上,格式化就在第一道闸门返回,`dhara_map_clear()` **从未执行**——动作 A 名义上跑了,
实际上一个映射都没清,紧随其后的 `f_mkfs` 仍在旧映射上工作、必然失败。
- **现场特征**:`Cache flush FAIL: LPN=<固定值> dhara=TOO_BAD` 连打十余条 →
`reformatting...` → `volume repair failed (fr=1)` → `storage disabled`,且**每次启动必现**。
看到这条链就别再连续重试动作 A,先确认脏缓存是否正卡在坏页上。
- **修复后判据**:跑动作 A/B 时若出现新增日志
`format: dirty cache at LPN=<n> undiscardable, dropping it (map is being destroyed anyway)`
→ 说明确实撞上了"脏页卡坏页",但**格式化已经继续执行**(这是预期行为,不是新故障)。
其后应能看到 `NAND FTL formatted, capacity: ...`。
- 语义边界:丢弃脏缓存的权限**只在 format 内**。`disk_read`/`disk_write` 的 flush 失败
必须继续向上报 `RES_ERROR`——那是"这次写没落地"的真相。
## 3. 操作步骤(以容量恢复为例)
1. `test/board/test_config.h`:取消注释 `TEST_SUITE_STORAGE`;
@@ -39,7 +39,7 @@ MCU 侧真实处理只占 6.7ms,任务拾取/调度约 2.3ms(2026-09-22 重
|---|---|---|
| 远端显示 RemoDispBus | 7003 | `ui_net_sink`(真实 uiTask 生产路径) |
| FTP(lftpd) | 21 | 控制 + PASV 数据通道(数据通道复用监听 Socket,CH395F 单连接模型) |
| 对时 | 8888 | `time_sync` |
| 对时(本文实测时在场;2026-11-04 随 #78 退役,对时改 SNTP 出向) | 8888 | `time_sync`(历史) |
| 时延测试链路(M2) | 8080 | Phase 13 测试协议(服务器驱动) |
客户端 = DTU-RemtoeLCD(`remo_disp_server.py`,与本工程同级的独立项目,目录名含历史拼写 `Remtoe`)。
@@ -503,7 +503,7 @@ uint16_t ch395f_link_change_count(void);
| 2 | `App/log/sys_log_mcu.c:527~529` | `rc==-1` 打 `DBG_ERROR("drop %lu entries …")`,但该分支 `done=0` → 整批其实**全部回投**、一条没丢 → 文案与行为相反,且持续写故障时每轮 drain 都重投重试一遍 | TC-SYSLOG-036 |
| 3 | `App/log/sys_log_mcu.c:616` vs `627~628` | 守卫写成 `desc_len <= SYSLOG_DESC_MAX` 就 `-1`,而 `dl` 恒 ≤ 28 → 截断分支**永不可达**。无害(宁可拒绝不越界),但"传小缓冲能拿到截断内容"的直觉是错的 | TC-SYSLOG-098 / 057 |
| 4 | `Drivers/BSP/NET/net_socket.c:2825` / `:2869` / `:3080` | **同一文件两套 IP 字节序约定,`inet_addr` 与 `ntoa`/`getsockname` 不互逆**:`net_inet_addr` 小端组装后又 `NET_HTONL` → `0xC0A80164`;`net_inet_ntoa` 交换后高段在前 → 只能正确还原 `getsockname` 那族(`0x6401A8C0`);`net_connect_locked:1571` 用 `>>24` 取首段 → 只配 `inet_addr` 那族。**为什么长期不被看见**:`ntoa` 自己 `:2865~2867` 那段 E13 修复注释里的例子「192.168.1.100 → 0xC0A80164 → "192.168.1.100"」取的是**另一族**的值(交换宏下实算 `"100.1.168.192"`)。前提已核清:`Drivers/BSP/NET/net_types.h`:56 是 `#ifdef __BIG_ENDIAN` 才恒等,STM32 与 x86/MSVC 都走交换分支 → **不存在"主机语义≠目标语义"**。当前无调用者跨族(`lftpd.c`:864 走 `getpeername→ntoa` 同族恰好正确),属埋在库 API 面上的地雷;统一必须跨 `inet_addr`/`ntoa`/`getsockname`/`connect` 解析/`lftpd.c`:548 一起过 | TC-NETSOCK-100b / 100c / 100d、107f、430h(钉的是**具体数值**,统一时会五条一起红并互相指认,不会被误读成单点回归) |
| 5 | `Drivers/BSP/NET/net_socket.c:2408` / `:2537` | **UDP 两个公开 API 绕过 netTask 串行化**:`net_sendto`/`net_recvfrom` 直接 `net_poll()`(`:2482`/`:2501`/`:2563`)+ `ch395f_write_send_buf`(`:2487`)/`ch395f_read_recv_buf`(`:2575`/`:2604`),**全程不经 `netMsgQueue`** → 与 AGENTS「所有 `net_*` 经 netTask 串行执行」及 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-14 正面冲突。全仓无生产调用者(只有 `test/board` 与模拟器)所以未爆。建议:改走消息队列,或在 `net_socket.h` 显式标注"未串行化,仅限 netTask" | TC-NETSOCK-440 |
| 5 | `Drivers/BSP/NET/net_socket.c:2408` / `:2537` | **UDP 两个公开 API 绕过 netTask 串行化**:`net_sendto`/`net_recvfrom` 直接 `net_poll()`(`:2482`/`:2501`/`:2563`)+ `ch395f_write_send_buf`(`:2487`)/`ch395f_read_recv_buf`(`:2575`/`:2604`),**全程不经 `netMsgQueue`** → 与 AGENTS「所有 `net_*` 经 netTask 串行执行」及 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-14 正面冲突。全仓无生产调用者(只有 `test/board` 与模拟器)所以未爆。建议:改走消息队列,或在 `net_socket.h` 显式标注"未串行化,仅限 netTask"。**处置(2026-11-03):已按前者落地**——UDP 全面走 `netMsgQueue`,出参经 per-fd 槽回传,见本文「第 4 轮:UDP 数据面队列化(2026-11-03)」 | TC-NETSOCK-440 |
| 6 | `App/log/soe_store.c` 函数头 ④ | 注释写"打开/写入结构性错误 → `unlink` 重试一次(同 run.log)",实现里 `f_open` 失败**直接 return -1、不重试**(只有 `f_write` 失败才重试);且 `run.log` 侧 `s_append_rec` 的重试码集合还含 `FR_INVALID_NAME`/`FR_NO_PATH` → 两套自愈集合不一致。属"注释夸大实现"还是"实现少一半"待裁 | TC-SOESTORE-022 |
| 7 | `App/cfg/cfg_file_store_mcu.c:50` | 函数头写"未挂载时 `f_open` 返回 `FR_NO_SYSTEM`"——**`FRESULT` 里没有这个码**(`Lib/FatFs/ff.h` 共 20 个码,未挂载实际是 `FR_NOT_READY`/`FR_NO_FILESYSTEM`)。只在注释里不参与编译,危害是后人照它写判据会写出永不成立的分支 | —(注释面) |
| 8 | `App/log/console_log.c:317` vs `:285` | 冷却起点用 `s_con_cool_until = (uint32_t)HAL_GetTick()`,而判据拿 `!= 0U` 当哨兵 → 故障恰好落在 `HAL_GetTick()==0` 那一拍(开机头 1 ms,或 49.7 天回绕到 0)时**冷却等于没记**,下一轮 drain 立刻再耗一次 100 ms 块超时。撞的正是控制台域那条已登记的"背靠背块超时拖长主循环 → 喂狗节奏饥饿"。建议:另用一个"在冷却"标志,别拿 tick 值当哨兵 | TC-CONLOG-081 / 082(按现状钉) |
@@ -526,18 +526,18 @@ uint16_t ch395f_link_change_count(void);
| 6 soe_store 注释夸大实现 | **已修(改注释,实现不动)**:打开失败**任何错误码都不 unlink** 是有意的——soe.log 是事件序列,「打开失败就删文件」等于销毁事件证据;两侧资料块各写一份理由并**互指对方文件与测试号**,要求同步修改 | TC-SOESTORE-022 |
| 7 FR_NO_SYSTEM 不存在 | **已修**:改成实际会返回的 `FR_NOT_READY(3)` / `FR_NO_FILESYSTEM(13)`,并写明「本层对任何非 FR_OK 一律转 -1,别去枚举错误码集合」,唯一例外指向自愈触发集 | 注释面(无行为改动) |
| 4 IP 两套字节序 | **已修(统一到 POSIX 网络序)**:改 `net_inet_addr`(`&s_addr[0] == 第一段`,小端上 192.168.1.100 = `0x6401A8C0`)+ `net_connect_locked`/`net_sendto` 的 IP 数组改按内存字节顺位拆装(**进与出同时换族 → 芯片侧数组净效果不变**,由 550h 直接钉住)+ 模拟器 `net_socket_sim.c` 逐字同语义;`net_inet_ntoa` **函数体一字未动**,只把它注释里那个取了废弃族值的例子改对(就是那条隐蔽原因)。lftpd 两处经算术核对**确认无需改**(PASV 227 的双重反转仍成立),只加「勿顺手改对」的注释 | TC-NETSOCK-100 组(新增往返不变量 100g/100h、边界 100i/100j、截断 100k)、550h |
| 5 UDP 绕过 netTask | **危险面已结构性关掉,串行化改造仍未做**(按 Lead 定案,未自升级为改队列):两个 UDP 入口加**执行任务上下文守卫**,非 netTask 调用回新码 `NET_ERR_NOT_IN_NETTASK = -12`(住 `net_types.h`,与 BUSY/TIMEDOUT 的本质差别 = **重试永远无意义**,不参与任何可重试判定);守卫位于参数校验之后、任何芯片事务之前;模拟器**刻意不加**并标注为有意的不对称(单线程 + Winsock 自身线程安全、没有那根 SPI)。**附带关掉第二条更隐蔽的危险**:非 netTask 在 recvfrom 里自旋期间,netTask 可经 close 清零控制块并把同一 sockfd 分给新会话 → 旧自旋者消费新会话数据报,这条路现在断了 | TC-NETSOCK-560 组(含「零芯片事务」与「守卫不吞正常错误路径」) |
| 5 UDP 绕过 netTask | **2026-10-24 那轮只关掉了危险面,串行化改造本身已于 2026-11-03 落地**:两个 UDP 入口不再"非 netTask 就拒",而是与 TCP 同构走 `netMsgQueue`(执行体拆到新文件 `Drivers/BSP/NET/net_udp.c`);`NET_ERR_NOT_IN_NETTASK = -12` 保留为**历史码**——全仓已无运行时返回点,只剩注释说明它为什么不再可能发生。**顺带修掉一条当年看不见的问题**:`net_sendto` 为发一个包把 DesIP 从 `0xFFFFFFFF` 改成具体 IP,芯片就此静默切到 UDP Client 形态(收包**不再带 8 字节信息头**),而 `net_recvfrom` 仍按服务器形态无条件剥 8 字节 → 请求-响应式协议(SNTP 48 字节应答)会**丢掉载荷前 8 字节**并交出垃圾源地址。现已按形态分路。当年守卫顺带关掉的"自旋期间 netTask 把同一 sockfd 分给新会话 → 旧自旋者消费新会话数据报",现由"只有 netTask 读芯片 + 每 fd 一个排他接收槽"结构性排除 | `ns_s7_udp.c` UDP-1~14 组 + 改写后的 TC-NETSOCK-440 / 555 / 560 组("非 netTask 调用零芯片事务"仍逐条钉住) |
| 8 冷却哨兵与 tick 撞值 | **已修**:新增独立 `s_con_cooling` 判定「是否在冷却」,`s_con_cool_until` 只存截止时刻(无符号差跨回绕的写法保留) | TC-CONLOG-080~084、107、108 |
| 9 环空位判据在临界区外 | **已修**:「复核空位 + 整行拷贝 + 推进 head」收进同一个临界区(最长 192 B、168 MHz 下亚微秒;`vTaskDelay` 与 DWT/tick 判据一律留区外,区内绝不睡眠)→ 多生产者结构性安全,写者仍唯一。**原「这条主机测不出来」的结论本轮作废**:桩里记临界区嵌套深度并把第二生产者精确塞进第一个生产者的临界区,再把环形缓冲的宏名成函数调用使每一次碰环都可断言 | TC-CONLOG-160~167(**167 是正控**,证明 160~166 不是空转) |
| 10 未初始化被误当僵尸通道 | **已修**:drain 的失败处理先分诊——`Instance == NULL` 视为通道不可用(不 reinit、不计 tx_fault、不进冷却、只记 dropped);只有确认超时/僵尸通道才走软复位自愈 + 冷却 | TC-CONLOG-103~106 |
| (撤销)非阻塞 recv 看不见 EOF | **误报,未改任何码**:`net_recv_locked:1885` 的非阻塞分支里 `CLOSED || session_dead` 已判 EOF(0),只有会话存活才 WOULDBLOCK。顺带核实 `net_recvfrom` **不存在** EOF 误判面(UDP 无连接,无数据只有 WOULDBLOCK 或继续自旋两条出口) | —(记一次「描述得很合理」的误报:修之前先读码,误报比漏报更贵) |
| (撤销)非阻塞 recv 看不见 EOF | **误报,未改任何码**:`net_recv_locked:1885` 的非阻塞分支里 `CLOSED || session_dead` 已判 EOF(0),只有会话存活才 WOULDBLOCK。顺带核实 `net_recvfrom` **不存在** EOF 误判面(UDP 无连接,无数据只有 WOULDBLOCK 或继续自旋两条出口;2026-11-03 队列化后"继续自旋"改为"登记挂起等 tick 结算",本条结论不变) | —(记一次「描述得很合理」的误报:修之前先读码,误报比漏报更贵) |
**修复过程中顺带照出的两条(原表没有,一并修掉)**:
- `syslog_post` 的 `queue full, drop` / `preboot full, drop` **无节流**——1 号修好之后「整批回投 → 队列常驻满」成为必然场景,不修等于**用一个新刷屏换掉一个静默丢**。已按同一「正常→故障」纪律加两个独立抑制位(**丢弃语义一字未改**,满则丢照旧)。TC-SYSLOG-041b/041d/041e/041f、105、106、107、107b、107c。
- 自愈触发集里混着 `FR_INVALID_NAME` / `FR_NO_PATH` / `FR_NO_FILE` / `FR_INVALID_OBJECT`——**比原报告的 6 号严重**:这类码下 unlink 自己的文件必然同样失败(同名同因),后果是**每轮排空删一次自己的历史日志且永远修不好**,正是 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 第 10 条严禁的归集。已新增代码侧唯一入口 `s_is_selfheal_code()`:触发集收敛为 `{FR_DISK_ERR, FR_INT_ERR, FR_NO_FILESYSTEM, FR_DENIED}`,逐码依据(含 `Lib/FatFs/ff.c` 行号)与显式排除清单写在资料块里;`soe_store.c` 侧同源但**取更保守子集**。TC-SYSLOG-101/101b/102/103/103b、TC-SOESTORE-022b。
**本轮刻意没做(后续项,含理由)**:① UDP 改走 `netMsgQueue`(需先设计 per-task 结果槽:`net_msg_t` 按值拷入队列、错误码只搭任务通知值回传,AGENTS 明文禁止给 `net_msg_t` 加回传字段,且必须配真机回归);
**本轮刻意没做(后续项,含理由)**:① UDP 改走 `netMsgQueue`(当年列的前置约束——`net_msg_t` 按值拷入队列、错误码只搭任务通知值回传、AGENTS 明文禁止给 `net_msg_t` 加回传字段,**至今成立**)→ **已于 2026-11-03 落地**:出参槽放进新模块 `net_udp.c` 内部,`net_msg_t` 一字未加;**真机回归仍待做**,见本文「第 4 轮:UDP 数据面队列化(2026-11-03)」的台架清单;
② `simulator/sim/sim_syslog.c:171` 那条与 3 号**同源的不可达截断支**(本轮编译验证不到它——模拟器走 CMake+VS,不在 `/keil-build` 覆盖内,不交没编译过的改动);③ `syslog_post` 的**丢弃计数**(属新增对外可见信息,本轮没有实测频率支撑阈值);
④ `dbg_console_tx_timeout()` 的**读端接线**(现无生产调用者;候选位置 `task_watchdog.c` 的 `STALL snap=… req=… h=… mg=… cf=… seg=…` 是**被解析的证据行**,加字段要连解析端与台架判据一起改,本批不碰台架);⑤ 给 104 加来源地址日志(现网 `iec104_serv.c` 根本不打印来源 IP、无 `getpeername/ntoa`,**没有输出点可看**)。
@@ -657,7 +657,7 @@ uint16_t ch395f_link_change_count(void);
⚠ 结构体成员访问本身(`u->SR`/`u->DR`)钩不到,别绕进去。
3. **跑飞保险要挂在被测模块的天然计数点上**:`net_send_locked`/`net_sendto`/`net_recvfrom` 的等待环里
**没有 `osDelay`**,只给 `osDelay` 加保险挡不住这些死循环(实测第一次跑就真卡死)。挂在 `net_poll()`
每轮必读的 `HAL_GPIO_ReadPin`(INT#)上,附带让 `NET_SEND_SPIN_MS` 这类有界自旋预算**第一次可量测**。
每轮必读的 `HAL_GPIO_ReadPin`(INT#)上,附带让 `NET_SEND_SPIN_MS` 这类有界自旋预算**第一次可量测**(2026-11-03 更新:UDP 收包已不再在调用方自旋,改为登记挂起、由 `net_udp_tick` 结算——同段那条"泵 50 轮仍无通知就 FATAL"的纪律因此成了主力把关:漏一次 `xTaskNotify` 当场红,而不是慢慢卡死)。
同一纪律的另一半:假内核对 `xTaskNotifyWait(portMAX_DELAY)` 泵 50 轮仍无通知就 `exit(2)` 打 `FATAL` ——
它当场抓出一处把 `net_close` 返回语义判错的用例,而不是让门禁卡死。
4. **未插桩全绿、插桩后 `0xC0000005`** 基本都是测试自己的越界读:假 UART 的上线字节日志是二进制缓冲没补
@@ -710,12 +710,33 @@ uint16_t ch395f_link_change_count(void);
- 本轮改动面:`App/log/sys_log_mcu.c`(条目结构 + 投递拷贝)、`App/log/sys_log.h`(参数契约)、`test/host/sys_log_mcu/sys_log_mcu_test.c`(新增 2 条断言 + `MQ_MAXMSG`)、`scripts/archive/id_gate/id_registry.json`(游标 `TC-SYSLOG-108`)。
- **台架与临时改动已全部还原**:钩子、负向对照、套件宏逐一反向删除(`git status` 归零),设备上 `BENCH.STS` / `BENCH2.STS` / `B5FILL.STS` 与含钩子记录的 `SELFTEST.LOG` 已删除,产品固件重建后重烧并回读校验通过。
### 第 4 轮:UDP 数据面队列化(2026-11-03)
前置背景:`#57` SNTP 调研把"UDP 先串行化"列为 SNTP 落地的第一步——`net_sendto` / `net_recvfrom` 直调 SPI 且自旋收包,既违反"netTask 唯一芯片访问者",又把 IWDG 预算吃在自旋上。
- **改造面**:新增 `Drivers/BSP/NET/net_udp.c/.h`(真机专属,模拟器不编译)承接 4 个执行体与挂起 recv;`net_msg_type_t` 加 `NET_MSG_UDP_BIND / UDP_SENDTO / UDP_RECVFROM / UDP_PEER` 四型;公开 API 一律"入队 → 等通知"。`net_init` 重建、`net_socket` 分配 fd、`net_close`、掉线与对账复位四处接上 `net_udp_cancel_all / clear / settle_close`,`net_process_messages` 的第 5 个 tick 位挂 `net_udp_tick`。
- **源地址怎么回来(AGENTS 硬约束)**:`net_msg_t` 按值拷入队列、明文禁止加"回传用"字段 → 出参指针**不进消息**,改住 `net_udp.c` 的 per-fd 槽:调用方 `arm`(临界区只包"查占位 + 占位 + 记 owner"三步)、入队失败立刻 `disarm`、netTask 执行体经该指针就地写回调用方自己的内存,通知值只带结果码。互斥 + 泄漏 = 该 fd 永久 `BUSY`,所以执行体**每条出口**都清(UDP-7d / UDP-9e 钉住)。
- **不写第二份分块逻辑**:UDP 发送复用 `net_send_locked`(1KB 分块 + 挂起 send 都在里面)。为此把"会话是否已死"收成一条判据 `net_send_state_down()`(`net_pending.c`):TCP 看 `ESTABLISHED`、UDP 看 `UDP_OPEN`;原先 `net_send_locked` 两处 + `net_send_pending_tick` + `net_pending_tick` 各写一遍,共 4 处。
- **顺带修掉的形态缺陷**(真隐患):`net_sendto` 为发一个包把 DesIP 从 `0xFFFFFFFF` 写成具体 IP,芯片静默切到 UDP Client 形态(收包**不带** 8 字节信息头),而旧 `recvfrom` 无条件剥 8 字节 → 请求-响应式协议(SNTP 48 B 应答)会丢载荷前 8 字节并交出垃圾源地址。**修法**:`net_sock_t` 加 `udp_mode`(塞进原有 2 字节 padding,`sizeof(net_sock_t)` 不变)、`bind` 恒 SERVER、显式对端 `sendto` 切 CLIENT、`net_udp_set_peer(fd, NULL)` 轻量回 SERVER(不重开 Socket 因此不清芯片缓冲)、只在 SERVER 且 `avail >= 8` 时剥头(残包不猜头,TC-NETSOCK-440o 契约不变)。
- **截断语义定案**:一次调用交付一个数据报,`buf` 装不下的尾巴**必须排空**——`ch395f_read_recv_buf` 是按长度推进读指针的消费式读,不排空则下一次 `recvfrom` 交出上一个包的残料。排空用 64 B 栈上 scratch 循环读,**不用** `ch395f_clear_recv_buf`:它清整个接收缓冲,会把后面已经到达的包一起丢。
- **用例面**:新分片 `test/host/net_socket/ns_s7_udp.c` 共 26 组 UDP-1~26(bind 的 5 条芯片命令**顺序**、未 bind 一律 -5、服务器形态剥头逐字节、**客户端形态同样剥头**(UDP-4 的判据 2026-11-04 由真机改写过:运行时改 DesIP 不改收包帧格式,见 TRAP-NET-22 追加段)、`sendto` 切形态、挂起收包在后续 poll 轮到达、预算超时按调用方 deadline 计、close 与重建结算不许静默丢通知、出参槽互斥与泄漏、截断排空、netTask 自等守卫、`net_init` 重建取消、流式 API 拒 `DGRAM`、非 netTask 调用零芯片事务、执行体与公开入口的参数守卫(越界 fd / `STREAM` / 未 bind / 零长 / 出参容量不足——拒时必须零芯片事务)、"队列未绑定"与"队列满"两条失败出口不许漏槽、`net_udp_set_peer` 轻/重两种回服务器模式(重开丢缓冲 vs 不重开)、挂起 send 由 `net_send_pending` 结算且登记时不许抢发第二份回执、`net_udp_tick` 的会话作废与 stale-readable 兜底、重 bind 必须把还在等包的调用方按 -8 结算、`bind` 遇满队列必须 BUSY 且不给 fd 投毒、**芯片 `open` 回 `ERR_BUSY` 时精确码必须沿通知值回到等待方**(真芯片会这么拒)、**队列里"别人的失败"排在我这条之前时不许污染我的回执**(同 545 在 TCP 侧的钉子,UDP bind 侧补一条)、`net_recvfrom_to` 的"真失败"与"超时"两条出口分开、**netTask 直调路径(板级用例走的那条)的 `set_peer` / `recvfrom` 正例与三种拒绝**、**UDP-24 = 240 轮定种子(`20261104`)随机长跑**:每轮随机载荷长度 1~112(112 = 假件收包流 128 B 上限减掉 8 字节信息头,不是魔数)、随机源端口与源 IP、随机 SERVER/CLIENT 形态、随机"显式对端 `sendto`"与"按记账对端 `sendto`",每 17 轮 `close` + 重 bind 一次;断言收在六族不变量上(读到的长度必须等于芯片宣布值、载荷既不吞信息头也不丢真字节、源地址**恒**取自表头(客户端形态同样——真机定案)、`udp_mode` 与命令严格一致、`close`/重 bind 后出参槽与挂起槽都不残留、每次 `sendto` 交给芯片的字节数恰好 = `len`),并要求 ≥90% 轮次端到端干净(实测 240/240)——**形态切换 × 挂起结算 × 关闭重建这三件事的交叉面,逐条用例排不出组合,只能靠随机扫**、**UDP-25 = 两条 UDP socket 并存的隔离**(前 24 组全都只用一条 socket,漏掉的正是队列化真正要保的那半句:等待是**一条 socket 自己的事**——fdB 来包不得结算、不得通知 fdA 的 parked 收包,fdA 的调用缓冲一个字节都不能动;反过来 fdB parked 时 fdA 自己 `recvfrom` 必须照常拿到自己那一包,串行的是命令不是等待者;关停在等的那条必须当场结算,不许留槽)、**UDP-26 = 芯片宣布不足 8 字节的残包**(无从判头的那一侧:整包原样交付、一个字节都不许剥、源地址退到对端记账,没记账时交出全零这个"我不知道"而不是包里的字节——这也是 `net_udp_src_from_peer()` 唯一的读者:真机定案把剥头判据改成"只看长度、与形态无关"之后,覆盖率门禁照出这条兜底一行没人走,就补在这一侧,而不是去把基线调低);改写 TC-NETSOCK-440(补 bind 前置 + 新增 440p~440u)、555(表头逐字节按形态分路)、560 组("拒在 netTask 外"→"经队列后非 netTask 仍零 SPI")、500h-i / 506 / 545。全套 **677/677 绿**(改造前 470)。
- **假件新增(一律可加不可改)**:`g_fx.wlen[]` + `g_fx.last_write[]` 捕获 `ch395f_write_send_buf` 的**真实字节**(此前假件忽略 `p_data`,UDP 截断与排空无从断言);`ch395f_fake_set_write_delay(fd, n)` 让 SENBUF_FREE 第 n 次才到(钉"排空必须在下一次写之前完成");`g_chip.read_calls` 供"读了几个 scratch 块"断言。`g_fake` / `g_chip` 既有字段一字未动。
- **两条教训(都是被测码真 bug,不是测法自误)**:① **执行侧自等守卫不能复用调用侧谓词**——`net_udp_recvfrom_exec` 用 `net_ctx_is_owner_task()` 判自等,而它在 netTask 内恒真 → 每一条经队列投进来的正常 recvfrom 都被拒;正确判据是 `caller == xTaskGetCurrentTaskHandle()`(对齐 `net_pending.c` 的 `caller == s_owner_task`)。抓它的是 UDP-8a。② **芯片长度只能读一次并传参**——`net_udp_datagram_read` 内部又调一次 `ch395f_get_recv_len`,于是"剥几字节"与"读几字节"不同源;假件的 recv_len 队列**每次调用弹一个值**,这条设计当场把双读暴露出来。现由调用方读一次 `avail` 传入三个调用点。
- **补测阶段又抓到两条(同一批缺口分析顺手挖的)**:③ **槽指针必须在 fd 合法性检查之后取**——`net_udp_recvfrom_exec` 原先先算 `&s_net_udp[sockfd]` 再判 `p_sock == NULL`,越界 fd 立刻得到一个越界槽指针(哪怕当下没解引用,后面登记就是野写)。④ **作废不等于可以不吭声**——`net_udp_bind_exec` 重开芯片 Socket 会丢掉缓冲里的包,却只 `net_udp_clear()`:A 任务正阻塞在 `net_recvfrom`、B 任务重 bind 同一个 fd,A 就永远睡死。现在重开之前先按 `NET_ERR_CONNRESET` 结算并通知(UDP-20 钉住)。教训同 `net_pending`:**任何"取消挂起"的动作都必须走 complete 那条路,clear 只是归零**。
- **随机长跑把两条"测法自误"逼了出来(不是被测码 bug,但只有长跑才逼得出)**:⑤ **连续 `sendto` 必须先把 SENBUF_FREE 钉成电平**——不注入 SENBUF_FREE 时,第二次发送会**合法地**转挂起(`net_send_locked` 自旋 2 个 tick 就 `net_send_pending_register`),而本套的挂起 send 替身从不完成,于是 `FATAL: blocking wait still got no receipt after 50 rounds` 直接掐死整个套件、连汇总行都没有(正确姿势照 440i/440j:`ch395f_fake_set_sint_sticky(fd, CH395F_SINT_STAT_SENBUF_FREE)` + `push_glob(1<<(fd+4))` + `g_ns_int_pin_set=0`,注释里点名 TRAP-NET-04,并写明"背靠背发送的真机面留给 TC-NET-1609")。⑥ **假件的 `ch395f_get_recv_len` 是"按调用序号取、耗尽后重复最后一个"的脚本且容量有限**——240 轮只追加不清空,序号撞满容量后每轮都读到同一个陈旧长度(症状:`len` 在随机而 `n` 恒为 28,前 24 轮却全绿,最容易误判成产品 bug)。补了加法假件助手 `ch395f_fake_reset_recv_len(sock)`(只把该 sock 的脚本长度与序号归零,既有单轮用例不调用它、行为一字未变),soak 每轮开头调一次。教训:**长跑用例对假件的第一要求是"状态可重排",不是"能追加"**。
- **本轮覆盖率**(收尾那次全仓跑数,`--update-baseline` 已把数字连同本轮提交一起落盘):`net_udp.c` 237/239 行(99.2%)、函数 **14/14**;`net_socket.c` 966/969(99.7%)、函数 **59/59**;`net_pending.c` 173/199(86.9%);全仓 line 8372/9290(90.1%)、47/47 套绿,无回退。剩两处未覆盖:`net_udp.c` 的槽越界兜底(调用方已挡,属防御性代码)与 `net_socket.c` 错误码槽重查竞态(真并发才走得到,改造前后同为那 2 行)。
- **台架必验(登记于本轮,2026-11-04 已全部跑完,见下一条)**:① 两端真机互通,服务器形态收发 + 客户端形态往返,重点看 48 B 这类小应答**载荷完整**且源地址正确;② `sendto` 切形态后 `net_udp_set_peer(fd, NULL)` 能否真恢复收其它源的包——手册只写 DesIP 语义,没写切换时机;③ 截断包排空后下一个包是否完整(scratch 循环在真芯片读指针上的行为);④ 挂起 recv 等待期间的 IWDG 余量;⑤ bind 在 open **之前**写 DesIP 的顺序在真芯片上的实际效果(TRAP-NET-14 的 UDP 侧对照)。
- **真机回归结果(2026-11-04 08:42,PR #74 分支 head 走到 `b5671e8`(剥头修正)之后)**:板端 `=== Phase 16: 77/77 PASSED (failed=0) ===`、PC peer `=== UDP Bench PC Peer: 27/27 PASSED (failed=0) ===`,①~⑤ **全部成立**,逐条串口原文见 `docs/测试说明/板级测试/CH395F_Test_Guide.md` §12.6 的「实测结果」表。**第一轮(08:16)其实是 33/47**:`TC-NET-1603` 报 `tag 0x02 got 0x38 (n=56)`,把本轮最大的一条设计错误照了出来——**芯片加不加 8 字节信息头是在 OPEN_SOCKET 那一刻由 DesIP 定死的,运行时把 DesIP 改成具体对端并不会让头消失**,而 UDP-4/UDP-24 当时按"客户端形态不剥头"写死了假件脚本,240 轮随机全绿也没发现。修法:`net_udp_datagram_read` 的剥头条件改为**只看 `avail >= 8`、与 `udp_mode` 无关**(`udp_mode` 退化为发送去向 + 对端记账),主机侧 UDP-4 与 UDP-24 客户端轮按真机重写(UDP-4 补一条 `addrlen`/family 断言,改完判据后覆盖率门禁又照出兜底那侧一行没人走,补 UDP-26 残包组 → 全套 **677/677**),板级 1603 的判据文字同步改写;定案登记在 TRAP-NET-22 的 2026-11-04 追加段。产品镜像尺寸 `Code=249120 RO=29836 RW=2052 ZI=149996`(比改造前基线少 4 B,`RW/ZI` 一字未增)。
- **这 5 条已落成可执行的板级阶段**:阶段 16(`docs/测试说明/板级测试/CH395F_Test_Guide.md` §12.6,板端 `test/board/net_test_task.c` 的 `net_layer_udp_bench_test_main`,PC 侧 `test/host/udp_queue_bench.py`),TC-NET-1601~1609 与 ①~⑤ 的对应关系写在表里。**真机跑了四轮才跑完整**:2026-11-03 夜第一次上台架,① 的上半段已见证据——`TC-NET-1601` 建 socket/bind `:6001`/形态回 SERVER、`TC-NET-1602` 服务端形态收到 20 B 且 `addrlen`/`sin_family`/源端口全对、回显逐字节一致并把形态翻成 CLIENT;②③④⑤ 未测,板子当晚两次失电(SWD `No target connected`、ping 不通,19:19 与 21:0x 各一次),两趟还各暴露一个**台架**缺陷(peer 被 Windows 的 ICMP 10054 打死;单步预算 8 s < 板端 25 s park 导致整条梯子错位,另有一版 peer 在失败后瞬间狂重发,单个日志刷到 940,949 B),修法与守卫见上面那份指南。2026-11-04 08:16 第一次跑完整梯子是 **33/47**,唯一的真缺陷就是上面那条剥头判据,14 条 FAIL 全是它的级联;修完 08:42 第二轮 **77/77**,跑完立刻把板子恢复成产品镜像(`Code=249120`,串口 `[PROF2]` 心跳连续、`netn=0`、`[ERR][WD] task stall` 零条)并释放台架锁。
- **同一轮补的免对端那半(2026-11-04)**:第一次上台架就是被"没有对端"卡住的——peer 脚本被 Windows 的 ICMP 10054 打死、梯子错位,1601 之后一步都测不到,整趟证据归零。故阶段 16 加了 **TC-NET-1610**:17 条断言不需要 PC 发一个包,把「形态记账(新建 socket 与 bind 都读 SERVER、`sendto` 显式对端才切 CLIENT)」「参数与出参守卫(未 bind 报 -5、流式 API 读 `DGRAM` 报 -3、`to_ms == 0` 报 -3、`src_addr` 缺 `addrlen` 或容量只有 4 字节报 -3、空缓冲 `DONTWAIT` 报 -7)」「`net_recvfrom_to(2000)` 返回 **-2** 且实测睡眠落在 2~4.5 s(等待睡在任务通知上,不在 SPI 总线上自旋)」「`net_close` 后同一槽重新 bind 不被上一条命毒化」四类在真芯片上一次确认。执行位置在 1601 之前、编号留在段尾(编号是台账用的,不等于执行顺序)。代价只有 flash:Code +2,380 B、RO +3,108 B(全是断言字符串),`RW/ZI` 一字未增——它复用那块 1,600 B 收发缓冲。**ARMCC 已编过(bench 与产品两态都 0 error 0 warning),并在 2026-11-04 08:42 那轮真机上 17 条全绿**(`TC-NET-1610a..q`,含 `1610i: park slept 2002ms against a 2000ms budget` 这条"睡在通知上"的硬证据)——它也正是那一轮板子断线时唯一还能自证的段落,先跑完再等对端的设计目的达到了。
- 本轮改动面:`Drivers/BSP/NET/net_udp.c` 与 `Drivers/BSP/NET/net_udp.h`(新)、`net_socket.c` / `net_socket.h`、`net_pending.c` / `net_pending.h`、`net_types.h`、`MDK-ARM/STM32F407-Demo.uvprojx`、`scripts/check_registration.py`、`test/host/common/ch395f_fake.c` 与 `test/host/common/ch395f_fake.h`、`test/host/net_socket/`(含新分片);文档 → `docs/网络/BSD_Socket_API_使用指南.md` §6.0 / §6.1 / §6.2 / §6.3 / §9.0、`docs/网络/BSD_Socket_API_函数调用关系.md` §4.11、`docs/问题记录/CH395F_Trap_Records.md`(TRAP-NET-14 UDP 段 + 新条)。
### P3|**明确不做**(把理由写死,免得下次又被列回待办:29 个 `.c` = 4,814 行)
| 类别 | 体量 | 为什么不做 | 那它靠什么把关 |
|---|---|---|---|
| 芯片驱动(寄存器/时序) | `ch395f.c`(801)、`gd5f2gq5ue.c`(689)、`sd2506.c`(264)、`tpafe5160.c`(157)、`rs485.c`(79) = **1,990 行** | 把 HAL 与寄存器假到底 ≈ **重写一遍驱动**,测的是自己的桩而不是硬件行为(SPI 分频必须 8、DMA 流、I2C 时序在桩里怎么编都"对") | 板级套件(`board-test-suite` skill)+ `/serial-boot-test` 真机日志 + 4 个 Fault 现场;陷阱逐条登记在 `docs/问题记录/CH395F_Trap_Records.md`、`docs/问题记录/GD5F2GQ5UE_Trap_Records.md`、`docs/问题记录/SD2506_Trap_Records.md` |
| UI 侧 | 17 个 `.c` = **1,942 行** | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 66 张像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` |
| UI 侧 | 18 个 `.c` = **2,373 行**(2026-11-04 随 `ui_page_sntp.c` 从 17 个/1,942 行递增) | 与 PC 模拟器是**同一份源码**,行为回归已由 `ui-golden`(菜单 8 状态 + 内容页 74 张像素黄金参考)覆盖;再写单测是第三份重复投资 | `ui-verify` skill 五步 + `docs/UI设计/UI_自动化测试流程.md` |
| 任务编排 | `default_task.c`(281)、`adc_task.c`(171)、`net_task.c`(123)、`protect_task.c`(90)、`ui_task.c`(71)、`rs485_task.c`(11) = **747 行** | 每个只有 4~5 个函数,主体是"按顺序调别人";真逻辑在被调模块里(那些已测或在 P1/P2)。给编排层写单测最容易写成"断言我调了几次"的自证 | 真机启动锚点计数 + IWDG `required_mask`(`/serial-boot-test`)+ `docs/架构设计/项目启动顺序管理.md` |
| 内核前装配 | `app_main.c`(135) | 被测对象本身就是"`osKernelStart` 之前不许调内核 API"的时序,主机侧无法忠实复现(PC 上没有那个约束) | 同上 + `cubemx-survival` 审计 |
@@ -1,93 +1,75 @@
# 时间同步单元测试说明(主机侧)
> **模块**:Time Sync(`App/sync/time_sync.c` + `App/sync/time_sync.h`)
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证 TCP 时间同步协议
> 状态机(分片喂狗延时 / 监听建立与退避 / 时间包重组 / 任务主循环),零硬件、零烧录、
> 毫秒级、不占 MCU 的 RAM。
> **状态**:✅ 已落地(80 断言点,`/W4` 0 告警,已挂 pre-commit 钩子)
> **日期**:2026-10-10
> **模块**:Time Sync(`App/sync/time_sync.c` + `App/sync/time_sync.h`)——timeSyncTask 载体
> **测试类型**:**主机侧纯逻辑单元测试**——在 PC 上离线验证任务编排逻辑(分片喂狗延时 /
> 就绪屏障等待 / SNTP 节拍驱动 / 网络代次交接),零硬件、零烧录、毫秒级、不占 MCU 的 RAM。
> **状态**:✅ v2.0(:8888 退役后,33 断言点,已挂 pre-commit 钩子)
> **日期**:2026-11-04(v2.0;v1.0 2026-10-10 的 TCP :8888 协议状态机套随 #78 退役重写)
> **关联文档**:
> [`../../../AGENTS.md`](../../../AGENTS.md)(平台约束与看门狗铁律)、
> [`测试开发标准流程.md`](../流程与规范/测试开发标准流程.md)(测试分层与门禁总览)、
> [`看门狗单元测试说明.md`](看门狗单元测试说明.md)(喂狗位图判定状态机)、
> [`卷锁单元测试说明.md`](卷锁单元测试说明.md)(同批落地的姊妹篇)、
> [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(`required_mask` / 抽样窗 / 长耗时白名单)、
> [`../../任务规划/README.md`](../../任务规划/README.md)(登记条目「探针窗口心跳缺口」)、
> 历史审计(netTask 头阻塞;属主文档已于 2026-10-15 删除,现状见 [`../../网络/Net层阻塞模型架构设计.md`](../../网络/Net层阻塞模型架构设计.md))
> [`../../子系统设计/系统墙钟时间维护说明.md`](../../子系统设计/系统墙钟时间维护说明.md)(§15 退役历史 / §16 SNTP 现行设计)、
> [`../../子系统设计/看门狗设计说明.md`](../../子系统设计/看门狗设计说明.md)(`required_mask` / 抽样窗 / 长耗时白名单)
---
## 1. 概述
### 1.1 为什么单独做主机侧单测
### 1.1 为什么退役后仍要单独做主机侧单测
本模块是**两件已知风险的当事方**,而两件风险的成因**全部是本模块的纯逻辑**:
:8888 监听、`TIME` 包重组状态机、监听退避重试随 2026-11-04 退役删除(旧套 80 断言点的
主体),但任务本身**不能删**——它是 timeSyncTask 的载体,SNTP 校时客户端
(`App/sync/time_sntp.c`,设计见墙钟说明 §16)挂在它的 200 ms 节拍下运行。留存的纯逻辑
仍然全是「误复位」当事方,且真机上只能"跑起来看复位不复位"间接观察:
1. **探针窗口心跳缺口(看门狗余量)**:登记簿条目记录"time_sync 监听重建期间心跳缺口 800ms~1s,
距 IWDG 最坏超时 1701ms 余量仅约 0.7s"。该缺口的每一段都由本模块产生——
`time_sync_delay_fed()` 的**分片喂狗节奏**、监听重试退避(500 / 2000ms)、
`accept` 轮询退避(200ms)、收尾延时(50ms),以及"就绪屏障等待态也要喂心跳"。
真机上只能靠"跑起来看复位不复位"间接观察;主机侧可**逐片断言**心跳次数与片长。
2. **头阻塞**:旧实现用单次 30s 阻塞 `net_recv` 等时间包,独占 `netTask`,
使其余任务的网络操作整体停摆。现改为"分片有界读 + 总预算",本单测钉死其**分片语义**
(单次等待片长恒 ≤ 50ms、总预算 30s 不因分片而改变)。
1. **心跳节奏**:`time_sync_delay_fed()` 的分片喂心跳(片长 ≤ 50 ms)与主循环每轮首行喂位;
就绪屏障等待(100 ms 分片)未就绪时每轮也要补心跳——这是 2026-10-02 台架根因的回归防线。
2. **SNTP 宿主的节拍契约**:每轮恰好调一次 `time_sntp_run(当前代次)`;网络代次变化必须
**原样、当轮**传给客户端(代次交接是 `time_sntp_run` 内部弃旧 Socket 重建的唯一触发)。
3. **网络未就绪绝不出网**:屏障通过之前不得读代次、不得驱动 SNTP。
次要理由:**时间包重组状态机**(累积到 8 字节才解析、TCP 分段送达、EOF / 真错误 /
本片超时三种返回码分支、tick 回绕)是纯逻辑,板端套件无法精确构造
"分段 + 预算耗尽 + tick 回绕"这类组合。
旧套钉死的「头阻塞回归防线」(单次等待片长恒 ≤ 50 ms、总预算不因分片改变)语义由
SNTP 模块自己的主机套 `test/host/sntp/` 与本文的延时分片用例共同接力。
### 1.2 被测对象与桩
测试单元**原样 `#include` 生产源文件**,平台依赖由 7 个桩头替身 + 1 个真实头组成:
测试单元**原样 `#include` 生产源文件**,平台依赖由 5 个桩头替身 + 3 个真实头组成:
```
test/host/time_sync/time_sync_test.c
├── #include "FreeRTOS.h" / "task.h" / "cmsis_os2.h" ← 桩
├── #include "net_socket.h" / "sys_clock.h" /
│ "sys_events.h" / "dbg_log.h" ← 桩
├── #include "net_socket.h" / "sys_events.h" ← 桩
├── #include "task_watchdog.h" ← 真实头(App/util/)
├── #include "time_sntp.h" ← 真实头(App/sync/,签名对表)
└── #include "time_sync.c" ← 生产源文件(App/sync/),原样纳入
```
| 头文件 | 来源 | 桩提供的能力 |
|---|---|---|
| `FreeRTOS.h` | 桩 | `TickType_t` = `uint32_t`;`configTICK_RATE_HZ` = 1000(故 `pdMS_TO_TICKS(ms) == ms`) |
| `task.h` | 桩 | `vTaskDelay`(记录每片时长)、`xTaskGetTickCount`(返回可控 tick) |
| `cmsis_os2.h` | 桩 | `osEventFlagsWait`(按脚本返回就绪 / 超时) |
| `task.h` | 桩 | `vTaskDelay`(记录每片时长);tick 时钟桩保留(生产侧现不再读 tick,留给未来用例) |
| `cmsis_os2.h` | 桩 | `osEventFlagsWait`(按脚本返回就绪 / 超时,可第 N 次 `longjmp` 跳出) |
| `sys_events.h` | 桩 | `sys_evtHandle` + `SYS_EVT_NET_READY` 等位定义 |
| `sys_clock.h` | 桩 | `sys_clock_set_unix`(记录 epoch 与调用次数) |
| `dbg_log.h` | 桩 | `DBG_ERROR` / `DBG_INFO` 计数(`g_tstest_err_count` / `g_tstest_info_count`) |
| `net_socket.h` | 桩 | `socket` / `bind` / `listen` / `accept` / `close` / `recv_to` / `get_generation` / `htons` **全部脚本化** |
| `net_socket.h` | 桩 | **只剩 `net_get_generation`**(脚本化代次序列)——故意裁剪:生产代码若再引入监听类 API 会在本套编译/链接期即失败(绊线),旧 :8888 形态不会无声复活 |
| `task_watchdog.h` | **真实**(`App/util/`) | 提供 `task_wd_id_t` / `TASK_WD_TIMESYNC`;`task_wd_beat` / `task_wd_bind` 由测试文件实现(记录心跳节奏与位号) |
| `time_sntp.h` | **真实**(`App/sync/`) | 契约头(仅 timeSyncTask 上下文、单次 ≤50 ms 收片、调用方负责片间喂心跳);`time_sntp_run` 由测试文件实现(记录每轮收到的代次 + 可第 N 次 `longjmp`) |
| `time_sync.h` | **真实**(`App/sync/`) | 只 `#include <stdint.h>`,无平台依赖 |
`include_dirs` 顺序为 **`stub` → `App/util` → `App/sync` → 本目录**:桩目录必须排在
前面,否则会被真实头抢先命中。
> ⚠ `dbg_log.h` 桩**不得**定义 `DBG_LINE_BUF_MAX` / `DBG_TAG`——生产源(`time_sync.c:28-29`)
> 已先定义它们,桩再定义会触发 C4005 宏重定义告警。
### 1.3 关键契约(生产代码侧)
### 1.3 关键约定:分片喂心跳与报文重组契约
**(一)心跳必须落在同一个 100ms 抽样窗内。**
看门狗抽样窗为 `WD_POLL_PERIOD_MS = 100ms`(`App/util/task_watchdog.c`)。若一次长等待
(如 2000ms 退避)期间不喂心跳,或心跳节奏恰好滑过两个连续抽样窗,该任务就会被判"卡死"。
故 `time_sync_delay_fed()` 把任意长等待切成 **≤ `TIME_SYNC_WAIT_SLICE_MS` = 50ms** 的短片,
**每片前**喂一次心跳——语义等价于一次长延时,仅把心跳节奏钉在窗内(第 8b 轮台架消抖)。
**(二)`net_recv_to()` 的四态返回。**
`time_sync_handle_conn()` 的重组循环依赖该契约:
| 返回 | 含义 | 本模块动作 |
|---|---|---|
| `> 0` | 收到 `n` 字节 | 累加;`>= 8` 即收齐并解析,否则继续等余下字节 |
| `0` | 对端关闭(EOF) | 退出循环,**不解析** |
| `-1` | 真错误 | 退出循环,**不解析** |
| `-2` | 本片预算等满、无数据 | 重算剩余预算,**下一片**继续 |
**(三)总预算不因分片而改变。**
单次 `net_recv_to` 的等待片长 ≤ 50ms,但**跨片累积**仍以 `t0 = xTaskGetTickCount()`
为基准、总预算 `TIME_SYNC_TOTAL_BUDGET_MS` = 30000ms。tick 回绕由无符号减法天然处理。
- **心跳必须落在同一个 100 ms 抽样窗内**:看门狗抽样窗 `WD_POLL_PERIOD_MS = 100 ms`
(`App/util/task_watchdog.c`)。`time_sync_delay_fed()` 把任意长等待切成
≤ `TIME_SYNC_WAIT_SLICE_MS` = **50 ms** 短片,每片前喂一次心跳。
- **主循环节拍**:`task_wd_beat` → `time_sntp_run(net_get_generation())` →
`time_sync_delay_fed(TIME_SYNC_TICK_MS=200)`。SNTP 单次调用最多停留一片 50 ms
(`SNTP_RECV_SLICE_MS`,契约见 `time_sntp.h`),节拍间隙由载体任务喂位覆盖。
- **就绪屏障**:`osEventFlagsWait(sys_evtHandle, SYS_EVT_NET_READY,
osFlagsWaitAny | osFlagsNoClear, 100)`——就绪位**不得被消费**(`osFlagsNoClear`,
见 AGENTS「RTOS 与内存」);`sys_evtHandle == NULL`(模拟器)跳过屏障。
### 1.4 测试环境
@@ -114,67 +96,50 @@ test/host/time_sync/time_sync_test.c
| 项 | 说明 |
|---|---|
| **触发范围** | `App/sync/time_sync.c` / `time_sync.h`、`App/util/task_watchdog.h`,或 `test/host/time_sync/**`、`test/host/common/unit_test_harness.py` 变动 |
| **触发范围** | `App/sync/time_sync.c` / `time_sync.h` / `time_sntp.h`、`App/util/task_watchdog.h`,或 `test/host/time_sync/**`、`test/host/common/unit_test_harness.py` 变动 |
| **门禁层级** | **主机侧纯逻辑单测**(介于静态门禁与板端测试套件之间):主机侧、秒级、纯逻辑 |
| **pre-commit 钩子** | `time-sync-unit-test`(本地钩子,`.pre-commit-config.yaml` §7g) |
| **汇总行** | `=== Time Sync Tests: 80/80 PASSED (failed=0) ===` |
| **与板端测试的关系** | 互补:本单测保证**延时节奏与重组逻辑**符合设计;板端保证**真实 TCP 时序 + IWDG 不误复位**(该心跳缺口的实际余量只能由板端长跑判定) |
| **汇总行** | `=== Time Sync Tests: 33/33 PASSED (failed=0) ===` |
| **与板端测试的关系** | 互补:本单测保证**节拍编排与代次交接**符合设计;板端保证**真实 UDP/SNTP 时序 + IWDG 不误复位** |
---
## 2. 测试用例
> 命名 `TC-TS-NNN`(TS = Time Sync)。断言数(80)多于用例数(27),因为每个用例通常
> 包含 2~9 条子断言(后缀 `b` / `c` …)。桩状态在每用例前由 `fakes_reset()` 复位。
> **编号停发口径**:本文档不再使用 `TC-TIMESYNC-NNN`(新条目一律语义短语,
> [`../../编号规范.md`](../../编号规范.md) §0);旧编号族已随退役整批作废,不复用。
> 断言用**语义标签**命名。桩状态在每用例前由 `fakes_reset()` 复位。
### 2.1 `time_sync_delay_fed` — 分片喂心跳延时(TC-TIMESYNC-001..006)
### 2.1 `time_sync_delay_fed` — 分片喂心跳延时(`tc_delay_fed`)
| 用例 | 断言 |
| 场景 | 断言 |
|---|---|
| **TC-TIMESYNC-001** | `delay_fed(0)` 既不睡眠也不喂狗(零开销快路径) |
| **TC-TIMESYNC-002** | 恰好一片(50ms):1 次 `vTaskDelay(50)` + 1 次心跳 |
| **TC-TIMESYNC-003** | 非整片(120ms)切成 **50 / 50 / 20** 三片,每片喂一次心跳 |
| **TC-TIMESYNC-004** | 长延时(2000ms)= **40 片 × 50ms**,每片喂心跳(监听退避路径) |
| **TC-TIMESYNC-005** | 不足一片(49ms)单片处理,**不**被拆成 0 |
| **TC-TIMESYNC-006** | 总预算 30s = **600 片**,片长上限**恒** 50ms |
| 0 ms | 不睡眠不喂狗(零开销快路径) |
| 恰一片 50 ms | 1 次 `vTaskDelay(50)` + 1 次心跳 |
| 非整片 120 ms | 切成 **50 / 50 / 20** 三片,每片喂一次心跳 |
| 200 ms 节拍 | **4 片 × 50 ms**,每片喂心跳 |
| 不足一片 49 ms | 单片处理,**不**被拆成 0 |
| 30 s 长等待 | **600 片**,片长上限**恒** 50 ms |
### 2.2 `time_sync_open_listener` — 监听建立与退避(TC-TIMESYNC-010..015)
### 2.2 `time_sync_task` — 任务主循环(`tc_task`)
| 用例 | 断言 |
生产 `time_sync_task` 是 `for(;;)`,桩在脚本耗尽时 `longjmp` 跳出后断言副作用
(§4.1)。四个场景:
| 场景 | 断言 |
|---|---|
| **TC-TIMESYNC-010** | 首次成功:`socket` / `bind` / `listen` 各 1 次;端口经 `net_htons(8888)`;`sin_family` = `NET_AF_INET`;成功路径**无退避延时** |
| **TC-TIMESYNC-011** | `bind` 失败 → **关闭该 socket** + 退避 2000ms(40 片)+ 留痕 `DBG_ERROR`,随后重试成功 |
| **TC-TIMESYNC-012** | `listen` 失败 → 关闭该 socket + 退避 2000ms,随后重试成功 |
| **TC-TIMESYNC-013** | `socket` 连续失败 → **5 次后返回 -1**;5 条 `DBG_ERROR`;5 × 500ms 退避(50 片) |
| **TC-TIMESYNC-014** | 第 3 次重试成功并返回**新**描述符;恰好尝试 3 次;两次失败退避 500ms × 2 |
| **TC-TIMESYNC-015** | 心跳总次数 = 每轮重试(5)+ 每片延时(50);**心跳位恒为 `TASK_WD_TIMESYNC`** |
| **A · NULL 屏障 + 单轮**(`sys_evtHandle == NULL`,第 2 次读代次时跳出) | 跳过屏障直接进循环;代次读 2 次、`time_sntp_run` 1 次且收到代次 = 1;延时 4 片 × 50 ms;心跳 6 次(轮首 + 4 片 + 跳出前轮首);全部打 `TASK_WD_TIMESYNC` 位 |
| **B · 代次 1→2 当轮交接**(第 3 次读代次时跳出) | `time_sntp_run` 收到代次**逐轮 = 1、2**(代次变化当轮原样送达,客户端内部弃旧 Socket 自重建);延时 8 片;心跳 11 次 =(1+4)×2+1 |
| **C · 就绪屏障 2 次超时 + 1 次就绪**(跳出屏障后立即读代次失败跳出) | `osEventFlagsWait` 3 次、超时参 **100 ms**、选项 `osFlagsWaitAny \| osFlagsNoClear`(**就绪位不被消费**);未就绪两轮各补 1 次心跳 + 循环首行 1 次 = 3;`task_wd_bind` 已先登记心跳路由;SNTP 未跑(网络未就绪绝不出网) |
| **D · 屏障永不就绪**(第 4 次等待时跳出) | 等待 4 次全超时、循环未挂死(2026-10-02 台架根因回归防线);每次超时后各喂 1 次心跳 + 首行 = 3;**代次 0 读、SNTP 0 跑** |
### 2.3 `time_sync_handle_conn` — 时间包重组状态机(TC-TIMESYNC-020..029)
### 2.3 SNTP 驻留不拖垮心跳(`tc_sntp_dwell_then_continue`)
| 用例 | 断言 |
|---|---|
| **TC-TIMESYNC-020** | 8 字节一次收齐 → **大端**解析 epoch + 更新墙钟 1 次 + 关连接;首次请求 16 字节;单次等待片长 = 50ms;留痕 `wall clock updated` |
| **TC-TIMESYNC-021** | 分段送达(4+4)→ 累积后解析正确;读了两次;第二次**只请求剩余 12 字节**;每片 50ms |
| **TC-TIMESYNC-022** | 一次到达 16 字节 → **只取前 8 字节**;一次读够即停 |
| **TC-TIMESYNC-023** | 前缀非 `"TIME"` → **不更新墙钟**,但仍关闭连接 |
| **TC-TIMESYNC-024** | 对端关闭(`n == 0`)→ 立即退出、不解析;关闭连接 |
| **TC-TIMESYNC-025** | 真错误(`n == -1`)→ 立即退出、不解析 |
| **TC-TIMESYNC-026** | 只收到 4 字节后 EOF → **不足 8 字节不解析**;期间继续等待后续字节 |
| **TC-TIMESYNC-027** | 30s 预算耗尽 → 不再读(只读 1 次)+ 留痕 `no TIME packet in 30s, drop conn` + 关连接 |
| **TC-TIMESYNC-028** | 剩余预算不足一片 → **末片按剩余(20ms)切分** |
| **TC-TIMESYNC-029** | **32 位 tick 回绕** → 预算计算仍正确(无符号减法) |
`time_sntp_run` 第 2 次调用时跳出:第 1 轮完整走完(首行心跳 + 4 延时片)后第 2 轮
再次进入客户端——**每轮节拍都回到 SNTP**,驻留(应答收片 ≤ 50 ms)之后循环照常续转,
心跳位恒为 `TASK_WD_TIMESYNC`。
### 2.4 `time_sync_task` — 任务主循环(TC-TIMESYNC-040..044)
| 用例 | 断言 |
|---|---|
| **TC-TIMESYNC-040** | `sys_evtHandle == NULL` → **跳过就绪屏障**;`accept` 轮询 3 次(2 次无连接 + 1 次成功);收到时间包并更新墙钟;延时片数 = 4+4+1(200+200+50ms)且**全部按 50ms 分片**;心跳次数 = 主循环 1 + 退避 8 + 收连接 1 + 收尾 1 + 次轮 1 = **13**;所有心跳均打 `TASK_WD_TIMESYNC` 位 |
| **TC-TIMESYNC-041** | 就绪屏障轮询 3 次(2 次未就绪 + 1 次就绪);屏障等待按 **100ms 分片**;等待 `SYS_EVT_NET_READY` 位;`osFlagsWaitAny \| osFlagsNoClear`(**就绪位不被消费**);未就绪的两次各喂一次心跳;仍先 `task_wd_bind` 登记心跳路由 |
| **TC-TIMESYNC-042** | 屏障等待 4 次均未就绪 → **循环未挂死**(跳出并继续);每次未就绪后各喂一次心跳(2026-10-02 台架根因的**回归防线**) |
| **TC-TIMESYNC-043** | 网络代次变化 → **关闭旧监听**并重监听;留痕 `network re-init, relisten`(另含 2 条 listen 成功);每次循环只轮询一次 `accept` 即放弃 |
| **TC-TIMESYNC-044** | 监听建立失败 → 内部重试 5 次后放弃;6 条 `DBG_ERROR`(5 条 `net_socket FAIL` + 1 条 `retry later`);退避 5×500ms + 2000ms = **90 片**;心跳次数 = 2 + 5 + 50 + 40 = **97**;所有心跳均打 `TIMESYNC` 位 |
**当前结果**:80/80 PASSED(`failed=0`)。
**当前结果**:33/33 PASSED(`failed=0`)。
---
@@ -182,11 +147,11 @@ test/host/time_sync/time_sync_test.c
1. **纯延时类**:直接调 `time_sync_delay_fed(ms)`,断言 `s_delay_calls`(片数)与
`s_delays[]`(每片时长)以及 `s_beat_calls`(心跳数)。
2. **脚本化网络**:给 `s_gen_seq[]` / `s_evt_seq[]` / `recv_step_t` 数组填序列,
再驱动被测函数。
3. **跳出任务循环**:生产 `time_sync_task` 是 `for(;;)`,无法自然返回。桩在脚本耗尽时
调 **`longjmp(s_jmp, N)`** 强行跳出(见 §4.1),跳出后正常断言。
4. `CHECK(cond, "描述")` 断言;在 `main()` 里注册(并同步更新本文档 §2)。
2. **脚本化节拍**:给 `s_gen_seq[]` / `s_evt_seq[]` 数组填序列,再驱动任务循环。
3. **跳出任务循环**:桩在脚本化调用计数达到预设值时调 **`longjmp(s_jmp, N)`** 强行跳出
(见 §4.1),跳出后正常断言;SNTP 侧用 `s_sntp_run_longjmp_at`,代次侧用
`s_gen_longjmp_at`,屏障侧用 `s_evt_longjmp_at`。
4. `CHECK(cond, "语义标签")` 断言;在 `main()` 里注册(并同步更新本文档 §2)。
---
@@ -196,36 +161,30 @@ test/host/time_sync/time_sync_test.c
`time_sync_task` 是永不返回的任务循环。本单测在 `main()` 里 `setjmp` 设跳转点,
桩在"脚本化调用计数达到预设值"时 `longjmp` 回来,然后断言循环期间产生的副作用
(心跳次数 / 延时片数 / `accept` 次数 / 墙钟更新)。
(心跳次数 / 延时片数 / SNTP 轮次与代次)。
**取舍**:这比"把循环体抽成可测函数"更贴近真实——**测的就是任务主体本身**,
不需要为了可测性改动生产代码结构。代价是**必须精确设定跳出点**:跳出点设错
(多一次或少一次桩调用)会导致断言数不符,`TC-TIMESYNC-040` 的跳出点曾因此修正。
(多一次或少一次桩调用)会导致断言数不符。
### 4.2 ⚠ C 的"暂定定义"会静默合并同名静态量
本文件曾出现一个**无任何编译告警**的缺陷:文件作用域下两处 `static int s_bind_calls;`
历史教训(2026-10-10,v1.0 套):文件作用域下两处 `static int s_bind_calls;`
(一处给 `task_wd_bind` 桩、一处给 `net_bind` 桩)被 C 的**暂定定义(tentative
definition)规则静默合并为同一个对象**——于是 `task_wd_bind` 与 `net_bind` 共用一个计数器,
8 条断言失败(72/80)。
definition)规则静默合并为同一个对象**,8 条断言失败且**无任何编译告警**。
**教训**:同一编译单元内**所有** `static` 变量名必须唯一,哪怕是不同桩的"各自的"计数器。
修复手法是把看门狗侧改名为 `s_wd_bind_calls` / `s_wd_bind_id_bad`,并加注释说明。
扩展用例时**务必**先 `grep` 目标名字是否已被占用。
**教训**:同一编译单元内**所有** `static` 变量名必须唯一。扩展用例时**务必**先
`grep` 目标名字是否已被占用。
### 4.3 桩不模拟真实 TCP 时序
### 4.3 桩不模拟真实 UDP/SNTP 时序
`net_recv_to` / `net_accept` 桩按脚本返回,**不**模拟真实的分段到达时刻、重传、窗口、
CH395F 缓冲区边界。因此本单测能保证的是"**给定返回码序列,状态机反应正确**",
**不能**保证真实链路上的时序行为——后者属板端与 PC 对端(`test/host/time_sync_server.py`)职责。
`net_recvfrom_to` 等由 `test/host/sntp/` 套覆盖报文级行为;本套的 `time_sntp_run` 桩
只记录"被调了几次、收到什么代次"。因此本套能保证的是"**给定节拍与脚本,任务编排正确**",
**不能**保证真实链路的 SNTP 时序——后者属 `test/host/sntp/` 与板端职责
(真机台架对时判据已登记,见墙钟说明 §16 与 #73)。
### 4.4 探针窗口心跳缺口的实际余量仍由板端判定
### 4.4 心跳缺口的实际余量仍由板端判定
本单测把"每片喂心跳、片长 ≤ 50ms、退避值正确"这些**逻辑前提**钉死,但该缺口关注的
是**端到端心跳缺口 vs IWDG 超时**——这还取决于真实调度延迟、`netTask` 排队时长、
Flash 写耗时等主机侧无法建模的因素。故:
- 本单测 ✅ 保证"逻辑上不会出现超过 50ms 的**无喂狗**窗口";
- 板端 ✅ 保证"实测缺口 + 调度抖动仍小于 1701ms"。
两者都过,这个心跳缺口才能闭环。
本单测把"每片喂心跳、片长 ≤ 50 ms、屏障每轮补心跳"这些**逻辑前提**钉死,但端到端
心跳缺口 vs IWDG 最坏超时(1701 ms)还取决于真实调度延迟与 `netTask` 排队时长,
主机侧无法建模——板端 V5 长跑继续兜底(`../板级测试/实机测试实施指南.md` §7)。
@@ -1125,6 +1125,130 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠
---
## 12.6 阶段 16:BSD UDP 数据面真机回归(NET 层,需 PC peer)
**动机**:PR #74 把 UDP 数据面并入 `netMsgQueue` 串行执行(见
`docs/网络/BSD_Socket_API_函数调用关系.md` §4.11),并顺手处理「DesIP 形态与剥头判据不一致」
这条登记在 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-22 的疑点。
主机单测(`test/host/net_socket/ns_s7_udp.c`,`docs/测试说明/单元测试/主机单测覆盖率口径.md` 第 4 轮)
把逻辑面钉死了,但芯片行为——DesIP 生效时机、UDP 的 SINT 触发、读指针上的残包排空——在桩里怎么编都"对"。
**这句话在 2026-11-04 被兑现了:桩里全绿的"按形态决定剥不剥",真机第一轮就翻车**(见下面「实测把设计改写了」)。
下面 5 条就是那轮登记簿留下的「台架必验」的可执行形态。
**架构**:板端一个 UDP socket,先 `net_bind(:6001)` 做服务端,再从收到的包里学对端地址回显。
**必须板端先听**:PC 侧是 DHCP(本轮实测 192.168.31.161),`net_test_task.h` 里那个
`TEST_PC_PEER_IP` 192.168.31.2 并不在本机上,让板端当客户端就连不上;板端当服务端则从不断言
"对端是谁"才收,对端从实测包文学来,PC 换 IP 也不用改固件。PC 侧跑
`test/host/udp_queue_bench.py` 逐步激励,用**三个不同源端口的 socket** 分批发("切形态之后还能不能收别人的包"
那一步必须有区分度才有意义)。
| 用例 | 内容 | 通过标准 | 必验 |
|------|------|----------|------|
| TC-NET-1600 | 载荷样式自检(fill/match 自洽) | 篡改第 9 字节 → match 返回 10;换 tag → 返回 1 | — |
| TC-NET-1601 | 新建 DGRAM socket + `net_bind(:6001)` | `fd` 有效、bind 返回 0、`net_udp_get_mode` = SERVER | ⑤ |
| TC-NET-1602 | bind 后首包(20 B,服务端形态) | 长度/样式/`addrlen`/`sin_family` 全对、学到 PC 源端口;回显后 mode = CLIENT(带地址的 `sendto` 记了账) | ① |
| TC-NET-1603 | **48 B 应答落在客户端形态**(SNTP 尺寸) | 必须读到 **48 B** 且 `buf[0]` 是载荷首字节——芯片此时**仍在前置 8 字节信息头**(真机定案,见下),判据若改成"客户端形态不剥"就会读成 56 B、首字节 0x38;回显走 `dest_addr = NULL`(用记账对端那条路) | ① |
| TC-NET-1604 | `net_udp_set_peer(fd, NULL)` 回服务端形态 | 返回 0、mode = SERVER,且**不重开**芯片 socket;随后**换新源端口**发来的包必须收到,再回显给这个新端口 | ② |
| TC-NET-1605 | 截断与残包排空 | 200 B 的包用 64 B 缓冲只读到 64;紧接着 30 B 的下一个包必须整包到达(残包没排干净,这里会读到上一条的尾巴) | ③ |
| TC-NET-1606 | 超时出口 | 空窗 `net_recvfrom_to(..., 1000)` 返回 **-2**(不是 -9),实耗 1000~4000 ms;下一步照常收发(槽没被毒化) | — |
| TC-NET-1607 | 挂起等待期间的 IWDG 余量 | PC 迟到约 12 s 才发;板端 `net_recvfrom_to(..., 30000)` 醒来看见 24 B 正确载荷,实测等待落在 7~25 s,`net_get_generation()` 不变(没重建没复位)。硬证据在串口日志:这段 `[ERR][WD] task stall` 零条、`[PROF]` 心跳连续 | ④ |
| TC-NET-1608 | 关旧 socket → 新开 → 重 bind :6002 | 新 socket 的首包必到(DesIP 要在 `OPEN_SOCKET` **之前**写才生效) | ⑤ |
| TC-NET-1609 | 分块边界 1 / 20 / 1024 / 1025 / 1472 | 每条往返字节完全一致 | — |
| TC-NET-1610 | **免对端自检**(17 条,编号留在段尾、执行放在 1601 之前,是为了断线重跑时编号不漂) | PC 一个包都不发也能确认:新建 DGRAM socket 读到 SERVER;未 bind 时 `net_sendto` 报 -5、流式 `net_recv` 读 DGRAM 报 -3;经队列 `bind :6003` 成功且 mode = SERVER;空缓冲 `DONTWAIT` 报 -7;`net_recvfrom_to(2000)` 返回 **-2** 且实测睡眠 2~4.5 s(等待睡在任务通知上,没在 SPI 总线上自旋);`to_ms == 0` 报 -3;`src_addr` 缺 `addrlen`、容量只有 4 字节都报 -3;`set_peer(fd, NULL)` 在本就是 SERVER 时返回 0 且无副作用;`net_close` 后同一槽重新 bind :6003 不被上一条命毒化 | — |
**实测把设计改写了(2026-11-04,第一轮 33/47)**:第一轮真机跑出来
`TC-NET-1603: tag 0x02 got 0x38 (n=56)` —— 带显式目的地址的 `net_sendto` 把 DesIP 改成 PC 之后,
芯片**照旧**给每个到达包前置 8 字节信息头(`0x38` = 56 就是头里的长度字段),设计时按手册 §9.2.4
写的"客户端形态不带信息头"其实说的是**开 Socket 那一刻**的 DesIP,运行时再改不算。于是
`net_udp_datagram_read` 的剥头判据从 `udp_mode == SERVER && avail >= 8` 改成**只看 `avail >= 8`**,
`udp_mode` 退化为"发送去向 + 对端记账";主机侧 UDP-4 与 UDP-24 soak 的客户端轮也跟着按真机重写
(假件当时按"客户端不剥头"脚本化,240 轮全绿也照样没发现这件事——TRAP-NET-22 教训④)。
改完的第二轮就是下面的全绿。
**实测结果(2026-11-04 08:42,板端 `fw build: Nov 4 2026 08:40:28`,`phase16d.hex`,PC = DHCP 192.168.31.161)**:
```
板端 === Phase 16: 77/77 PASSED (failed=0) ===
PC 端 === UDP Bench PC Peer: 27/27 PASSED (failed=0) ===
```
| 必验 | 实测证据(串口原文) | 结论 |
|------|----------------------|------|
| ① 服务端形态首包 + 剥头 + 学到源端口 | `TC-NET-1602: tag 0x01 20B from port 62073 OK`、`src addrlen=16`、`src family=2`、`1602d: echo 20B rc=20`;48 B 在客户端形态 `1603a: 48B intact in client form, header stripped (n=48)` | ✅ 载荷完整,源地址逐字节对 |
| ② 切回服务端形态后换源仍收得到 | `1604a: set_peer(NULL) rc=0`、`1604b: mode back to SERVER (0)`、`1604c: datagram from a NEW source port received (n=20)`(源端口 55351 ≠ 上一条 62073)、`1604d: echo to the new source rc=20` | ✅ 轻切换(不重开)确实恢复了多源接收 |
| ③ 截断包排空后下一个包完整 | `1605`:200 B 用 64 B 缓冲读到 64,紧接 30 B 的 `0x05` 整包到达并回显 `rc=30`;`1606` 空窗探针之后仍正常 | ✅ 残包排空有效 |
| ④ 挂起等待不吃 IWDG 预算 | `1606b: waited 1001ms with a 1000ms budget`(-2 出口);`1607a/b/c/d/e`:迟到包唤醒,`waited 11385ms (expect 7000..25000)`、`gen 1 -> 1`、载荷样式完好、回显 `rc=24`;整段日志 `[ERR][WD] task stall` **零条**,`[WD] armed: timeout ~2498ms (worst ~1701ms), miss_tol=800ms` 仍在线 | ✅ 睡在通知上,不是自旋 |
| ⑤ DesIP 写在 open 之前 + 重开后首包 | `1608a/b/c`(新 socket bind :6002 = 0、mode SERVER)、`1608d` 首包到达;`1609a..e`:1 / 20 / 1024 / 1025 / 1472 B 全部往返一致,`0x0A 1024B`、`0x0B 1025B`、`0x0C 1472B` 各条 `echo rc=` 等于原长 | ✅ 顺序与重建路径成立 |
PC 侧同时量到 >1 KB 回显被芯片发送缓冲拆开:`TC 0x0B: >1KB 回显按芯片发送缓冲在 1024 处拆包(实测 [1024, 1])`、
`TC 0x0C: …(实测 [1024, 448])`——这是 UDP 上共用 `net_send_locked` 的**已知语义**(见下「限度」第一条),
peer 脚本按"多片拼接"判定,不是缺陷。
**没有对端也要有证据**:1601 之后每一步都在等 PC 的包——网线没插好、防火墙静默丢包、peer 脚本没起,
任何一条都会让整条阶梯停在 1602(2026-11-03 首跑就是这么停的)。TC-NET-1610 只依赖芯片能把 socket
open 起来,形态记账(SERVER/CLIENT)、参数与出参守卫、"阻塞收包已改成挂起登记"这三件事在完全不互通
时也能一次确认;断线的时候,串口日志里的 `TC-NET-1610*` 就是判断"板子这半边是不是好的"的唯一依据。
**运行方法**:
1. 先在 PC 上自检 peer 脚本(不用板子):`python test/host/udp_queue_bench_loopback.py`,绿了再往下走。
2. `test/board/test_config.h` 开 `TEST_SUITE_CH395F`;`test/board/ch395f_test.h` **只**开
`ENABLE_PHASE16_TESTS`,并把默认的 `ENABLE_PHASE11_TESTS` / `ENABLE_PHASE14_TESTS` 注掉——
它们会带进 `TEST_LATENCY_PROBE`,污染要证的 UDP 面。
3. `/keil-build` 后烧录(`.dsh/skills/stlink-flash/scripts/stlink_flash.py flash`,`-Rst -Run` 必带)。
4. PC 先起 peer:`python test/host/udp_queue_bench.py --board 192.168.31.100`,再给板子上电/复位;
peer 首步每 4 s 重发,能盖住板子启动那段时间。
5. 判据是**两侧都绿**:板端串口 `=== Phase 16: N/N PASSED (failed=0) ===`,PC 端
`=== UDP Bench PC Peer: N/N PASSED (failed=0) ===`。PC 绿板红 = 板端断言没对上激励;
板端绿 PC 红 = 回显内容或分片不对。Windows 防火墙静默丢包会让首步一直"无回显"(§14 已记)。
6. 跑完把这四行宏恢复成产品态重新构建 + 烧录(`test-suite-switch` 钩子也会拦住带测试宏的提交)。
**限度与观测点**:
- `net_send_locked` 的分块上限 `NET_SEND_CHUNK_MAX = 1024` 是为 TCP 流设计的;UDP 上它会把一条
超过 1024 B 的 `sendto` 拆成多次 `WRITE_SEND_BUF`,芯片就发成**多个数据报**(UDP 没有可拼的流语义)。
这是既有行为、不是本轮引入(改造前的直调 `net_sendto` 就是同一个分块循环,只是那时 UDP 根本没有多任务
调用者)。peer 把它当**观测量**而不是错误:TC-NET-1609 的 1025 / 1472 两条要求「所有片拼回来与发出的
载荷逐字节一致」,并打印实测分片形状;只有拼接后长度或内容不对才判 [FAIL]。
产品侧结论:**UDP 单包保持在 1024 B 以内**(#57 的 SNTP 是 48 B,碰不到这条)。
- peer 脚本自己有 PC 侧回路自检,不用板子:`python test/host/udp_queue_bench_loopback.py`——它在
127.0.0.1 上放一个按上面三条规矩说话的假板子(同样剥 tag 0x04 到 64 B、同样 1024 拆片),把步骤表、
模式函数、分片预期先验一遍。2026-11-03 首跑 `27/27 PASSED`(实测拆片形状 `[1024, 1]` / `[1024, 448]`)。
它只证明**对端脚本**可用,不证明板端行为——真机那一趟仍然一条都不能省。
- TC-NET-1607 用 30 s 预算版的挂起等待,不用无限版:同一套 park/唤醒机制,但无人值守的台架不该留一个
能把任务钉死的等待。无限版(`timeout_ms = 0` → deadline = 0)的语义由主机单测 UDP-6 钉住。
- 本阶段构建实测(含 TC-NET-1610 免对端自检,`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`):**第一轮的镜像**
构建于 2026-11-04 01:06(`phase16c.hex`,836,489 B),Code 259,768 / RO 37,244 / RW 2,060 / ZI 151,596;
**第二轮(全绿那次)的镜像**构建于 08:40:28(`phase16d.hex`,836,608 B),带的是恒剥修正后的生产代码
(`net_udp.c` 少一次形态比较,Code −4)与改写后的断言文案(RO +44),
Code 259,764 / RO 37,288 / RW 2,060 / ZI 151,596。台架态 `RW_IRAM1` **96,232 B = 73.4%**(距 92% 红线余
24,354 B),`RW_CCM` 57,376 B / 87.9% 与产品一致。免对端自检那 17 条只加 Code(+2,380) 与 RO(+3,108,全是断言
字符串),RW/ZI 一字未增——它复用 `s_udp_buf`,没带新缓冲。产品态(宏回默认后重新构建)Code **249,120** /
RW 2,052 / ZI 149,996,比恒剥改造之前的 249,124 少 4 B——那 4 B 是剥头判据少一次形态比较(生产代码变化),
与阶段 16 无关;**阶段 16 的代码在产品里被预处理干净,对产品态零成本**。台账数字以
`test/board/ch395f_test.h` 那段为准,受管区数字由 `scripts/ram_baseline.py --write` 机器回填。
- **真机进度(2026-11-03 夜)**:第一次上台架已跑通**前半段**——板端
`TC-NET-1601a/b/c`(建 socket、bind :6001、`net_udp_get_mode` 回 SERVER)与
`TC-NET-1602`(服务端形态收到 20 B、`addrlen`/`sin_family`/源端口都对、逐字节一致,回显后模式
翻成 CLIENT)**全绿**,PC peer 侧同样收到那 20 B 回显;这就是队列化后的 UDP 收包路径在真芯片上
的第一份证据(剥 8 字节信息头 + 源地址回填 + 回显发送全部正确)。**当时 1603 之后未测**(次日已补全,见上面「实测结果」):板子在
21:0x 再次失电(SWD `No target connected`、ping 不通、ST-LINK 探针仍在),与 19:19 那次同一现象。
- 首跑抓到两处**台架**缺陷(都不是被测代码的问题,已改掉并各自有守卫):
1. peer 死在 `WinError 10054`——板子还没 bind 时那一下 ICMP 端口不可达会被 Windows 记在套接句上,
下一次 `recvfrom` 才抛出来;本机 CPython 3.14.7 既没有 `socket.SIO_UDP_CONNRESET` 也不接受它的
数值,所以主力防线改成把 10054 当「这一轮没收到」继续重试(`udp_queue_bench.py` 的
`new_peer_socket` / `_recv_after` / `_recv_maybe`),并由
`test/host/udp_queue_bench_loopback.py` 的冷端口探针守着(用脚本真正在用的那条路判定,
同时拿裸套接句证明这台机器确实会回 ICMP,否则探针没有牙)。
2. **一步超时→整条梯子错位**:板端每步等 25 s,peer 每步只等 8 s,于是 1602 之后每一步都踩在板子
的上一步预算里,日志看起来像"全灭"。现在两侧各自加固:peer 每步都重发且单步预算 `--step-budget`
默认 35 s(小于 26 s 会打警告);板端 `udp_bench_expect` 认载荷第 0 字节的步号,比本步小的旧包
丢掉继续等(上限 `UDP_BENCH_STALE_MAX = 8`),比本步大的直接报出来。
3. 顺带一条纪律:跑台架期间**不要用 serial-assistant 守护进程取证**——它重启后不会重开 COM3
(`status` 里 `ready:false`、`rx_bytes:0`),会把整段板端日志丢光;直接 `pyserial` 落盘更稳。
---
## 13. Python 测试脚本参考
| 命令 | 模式 | 说明 |
@@ -1134,6 +1258,8 @@ WAIT_CLOSE 阶段已内置二段补发(只补一次),生产 net 层则靠
| `tcp_file --ip ... --port 8080 --size 20480` | PC 作文件接收 | 测试大文件传输(阶段 7) |
| `udp_client --ip ... --port 60000` | PC 作 UDP Client | 测试 UDP Echo(阶段 3) |
| `udp_server --port 8082` | PC 作 UDP Server | 接收 CH395F UDP 数据 |
| `python test/host/udp_queue_bench.py --board <板 IP>` | PC 作 UDP 对端(独立脚本,不在上面那个脚本的模式里) | 阶段 16 BSD UDP 数据面回归:逐步换源端口、切形态、截断、空窗、迟到包(§12.6) |
| `python test/host/udp_queue_bench_loopback.py` | 不用板子的 peer 回路自检(本机假板子) | 上阶段 16 台架前先验 peer 的步骤表与分片预期(§12.6 第 1 步) |
| `dhcp_server ...` | DHCP Server | 内置 DHCP(阶段 4,需管理员权限) |
### 通用参数
@@ -243,7 +243,9 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L
- **30s 周期重试**,不自驻留死循环;
- 恢复后各 TCP 服务(FTP 21 等)**据代次号重建监听**,功能正常。
**验证手段**:`test/host/e02_probe.py`(网络探针)+ UI 网口设置页改 IP。
**验证手段**:FTP 客户端连 :21 + UI 网口设置页改 IP(观察代次号重建监听)。原网络探针
`e02_probe.py` **已删除**(2026-11-04 随 :8888 退役,正文见 git 历史)——它当时挂的是 :8888
time_sync 触发形态,退役后该触发场景不再存在。
**2026-10-03 台架结论**:
- **正常路径 ✅**:V5 长跑(30 min,FTP 512 KB 上/下传)**120/120 PASS、零 IWDG 复位**,
@@ -276,7 +276,7 @@ static void phaseN_run(void) {
| **`test/host/nand_port/run_unit_test.py`** | `test/host/nand_port/` | **主机侧纯逻辑单测**:离线编译真实 `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` + 真实 dhara,以 **RAM 假芯片**驱动,149 断言钉死 is_free 整页扫描与六个入口的越界守卫 |
| **`scripts/ci_local.py`** | `scripts/` | **本地跑与 CI 等价的门禁**(静态门禁 + 主机侧纯逻辑单测):自动选用装了 pre-commit 的解释器;`.map` 自适应跳过 `mem-numbers`;退出码 0/1/2 与 CI 对齐。见 §9.3 |
| `.serialtest/config.json` | 仓库 | **串口端口单一配置源**(当前 COM6 / 115200);换端口只改这个文件 |
| `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` / `time_sync_server.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) |
| `test/host/ftp_*.py`(4 个)/ `ch395f_socket_test.py` | `test/host/` | PC 端交互测试工具(FTP 客户端 / socket 回显;原对时推送 `time_sync_server.py` 已随 :8888 退役删除,2026-11-04) |
| `simulator/tests/*.py`(4 个) | `simulator/tests/` | LVGL 模拟器回归脚本(见 §5) |
### 4.2 主机侧纯逻辑单测(纯逻辑,无硬件)
@@ -373,7 +373,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
| 层 | 载体 | 抓住什么 |
|---|---|---|
| 文本层 | `check_content_pages.py` 字形模板匹配(310 条、覆盖当时 37 页,解析字体 cmap / glyph_dsc / bitmap) | 缺字显示空白 / 标签写错等**静默失败**(不依赖 `ref/`,先于像素比对运行、失败即中止防误刷参考) |
| 像素层 | 内容页黄金参考 66 张(64 页)+ 菜单 8 状态,与黄金参考逐字节比对 | 几何 / 样式 / 光标反显 / 整体外观 |
| 像素层 | 内容页黄金参考 74 张 + 菜单 8 状态,与黄金参考逐字节比对 | 几何 / 样式 / 光标反显 / 整体外观 |
| 池水位层 | `MENU_POOL_BASELINE=5368 B` / `POOL_AFTER_ROUNDTRIP=5448 B` | LVGL 内存池泄漏 / 结构性漂移(量的是 `used`,与 `LV_MEM_SIZE` 总大小无关) |
### 5.3 黄金参考与字体纪律
@@ -609,7 +609,6 @@ CI 就静默停摆(2026-10-11 实测 run 16~21 共 12 个 job 零日志)。
| `run_boot_test.py` / `verify_log.py` + `serial_tool` | 自动化闭环三件套(纯 Python 标准库 / 仓库内置) |
| `ftp_test.py` / `ftp_capture.py` / `ftp_up_dl_test.py` / `ftp_speed_test.py` | FTP PC 端测试工具 |
| `ch395f_socket_test.py` | CH395F PC 端 socket 回显 / 大文件传输工具 |
| `time_sync_server.py` | 对时推送 PC 端服务器 |
## 附录 B:模块测试文档
@@ -119,7 +119,7 @@
- **初始化回归**:`App/task/ui_task.c` 已有 `UI port init: OK` / `UI menu init: OK` / `UI net sink: started`
打印(见 `run_boot_test.py` 命令示例);将其加入 `--required` 即可经串口自动回归 UI 初始化。
- **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 66 张黄金参考 + 池水位门禁,
- **屏幕内容回归**:由 LVGL 模拟器像素回归覆盖(文本级断言 + 74 张黄金参考 + 池水位门禁,
见 `docs/UI设计/UI_自动化测试流程.md`);上机屏幕仍需人工目检(见 `测试开发标准流程.md` §1)。
## 扩展用法
@@ -2,7 +2,7 @@
> **定位**:面向使用者的操作手册——命令怎么敲、输出怎么看、常见场景怎么组合、出问题怎么排查。
> **关联文档**:设计决策与实现细节见 `串口助手设计说明.md`;/serial-boot-test 自动化闭环见 `自动化测试流程.md`;`serial-assistant` skill 有一页速查(用到自动加载,AGENTS.md 不再重复收录)。
> **版本**:v1.1(与 `.dsh/skills/serial-assistant/scripts/serial_tool.py` 当前实现一致,2026-09-15)
> **版本**:v1.2(与 `.dsh/skills/serial-assistant/scripts/serial_tool.py` 当前实现一致,2026-11-04;新增 daemon 死后串口自动重开与日志缺口标记——机理见 `docs/测试说明/测试工具/串口助手设计说明.md` §6.1,本文只讲行为)
## 1. 它是什么(60 秒版)
@@ -10,7 +10,7 @@
| 组成 | 是什么 | 你要知道的 |
|---|---|---|
| **常驻 daemon** | 后台 Python 进程,**串口唯一持有者**,把 RX/TX 持续写进日志文件 | 不用管它——任何命令发现它没在跑会**自动拉起** |
| **常驻 daemon** | 后台 Python 进程,**串口唯一持有者**,把 RX/TX 持续写进日志文件 | 不用管它——任何命令发现它没在跑会**自动拉起**;v1.2 起它死的时候口若还开着(且你没显式 close/stop),新实例还会**自动把口重开回来** |
| **薄 CLI** | `serial_tool.py <命令>`,每条命令都是带 5s 硬超时的短操作 | 你唯一直接打交道的界面 |
| **日志文件** | `.dsh/skills/serial-assistant/scripts/state/serial.log`,**全量历史就在这里** | 想回看任何时刻的数据 → 直接读这个文件 |
@@ -51,7 +51,7 @@
| `open` | 开串口(**幂等**:同口已开 = 直接成功;不带 `--dtr/--rts` = **保持现状**,显式给则现场应用并在 `note` 里回显) | 是(未运行自动拉起) |
| `close` | 关串口 | 是 |
| `send` | 发 TX 字节(进日志) | 是 |
| `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节) | 是 |
| `status` | 全状态一条 JSON(含 `state`、`rx_age_ms` = 距最后一个 RX 字节的毫秒数,`null` = 这个 daemon 还没收过字节;v1.2 起含 `port.desired` / `auto_reopen`) | 是 |
| `log` | 读日志文件(`--tail N` / `--grep P`) | **否**(直接读文件,daemon 死了也能用) |
| `clear` | 清空日志 + 代际 +1 | 是 |
| `wait-idle` | 等 RX 静默(/serial-boot-test 的收尾判据) | 是 |
@@ -101,7 +101,7 @@
... serial_tool.py close # → {"ok":true}
```
未开时返回 `{"ok":true,"note":"port not open"}`(不算错误)。daemon 继续运行。
未开时返回 `{"ok":true,"note":"port not open"}`(不算错误)。daemon 继续运行。**close 同时清除自动重开意图**(v1.2):此后 daemon 无论怎么死灰重生,都**不会**再自动重开这个口,直到你再次显式 `open`。
### 3.4 `send` — 发数据
@@ -127,11 +127,13 @@
{
"ok": true,
"daemon": {"pid": 16816, "version": "1.0.0", "uptime_s": 31},
"port": {"ready": true, "name": "COM1", "baud": 115200, "dtr": false, "rts": false,
"since": "2026-09-15T02:30:57.739+08:00"},
"port": {"ready": true, "desired": true, "name": "COM1", "baud": 115200, "dtr": false,
"rts": false, "since": "2026-09-15T02:30:57.739+08:00"},
"counters": {"rx_bytes": 1834, "tx_bytes": 22, "rx_lines": 57, "tx_lines": 3, "errors": 0},
"last_rx": {"ts": "…", "line": "UI menu init: OK", "age_ms": 320},
"log": {"path": "…\\tools\\serial_tool\\state\\serial.log", "size": 4523, "gen": 7},
"auto_reopen": {"enabled": true, "pending": false, "attempts": 0, "last_error": null,
"next_retry_in_ms": null},
"state": "active",
"error": null
}
@@ -139,8 +141,10 @@
| 字段 | 说明 |
|---|---|
| `state` | `active`(正常收数据)/ `stopped`(口未开)/ `error`(读错/拔线,`error` 字段有详情;重开 `open` 即恢复) |
| `port.ready` | 已开 && 非 error |
| `state` | `active`(正常收数据)/ `stopped`(口未开)/ `error`(读错/拔线,`error` 字段有详情;v1.2 起自动重连,无需手工 `open`) |
| `port.ready` | 已开 && 非 error(**实际**状态) |
| `port.desired` | 开口**意图**(v1.2):该不该开着口——显式 `open`/自动重开置位,`close`/`stop` 清除;`desired=true` 而 `ready=false` = 采集缺口进行中 |
| `auto_reopen` | 自动重开现场(v1.2):`enabled`(意图在不在)/ `pending`(正在退避等重试)/ `attempts`(本次缺口已试次数)/ `last_error`(最近一次失败原文)/ `next_retry_in_ms`(下次重试倒计时) |
| `counters` | 本 daemon 生命周期累计(daemon 重启清零) |
| `last_rx.age_ms` | 距最后一行 RX 的毫秒数——判断"还在刷/已静默" |
| `log.gen` | 当前日志代际(`clear` 会 +1) |
@@ -191,9 +195,10 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat
| `reason` | 含义 | `idle` |
|---|---|---|
| `idle` | 收到过数据,且已连续静默 ≥ `--settle-ms`(**正常收尾**) | true |
| `no_data` | **本次等待期间**一个字节都没收到(板子没输出/口不对/没烧进去) | false |
| `no_data` | **本次等待期间**一个字节都没收到(板子没输出/波特率不对/没烧进去)——**v1.2 起仅当口开着**才可能返回:口未开已改秒回 `ok:false`(见下) | false |
| `timeout` | `--max-ms` 到了仍在持续吐数据 | false |
- **v1.2 行为变化(issue #75)**:口未开时调用 → **立即返回** `{"ok":false,"error":"port not open; auto-reopen pending (attempts=… last_error=… next_retry_in_ms=…)"}`(exit 1,不占等待时间)。旧行为是干等满 `--max-ms` 再良性返回 `no_data`,把「采集器根本没在采」伪装成「板子静默」——现在 `no_data` 严格 = 口开着且板子真静默,可放心作为判定依据。
- **`--settle-ms` 默认 8000 不是随便定的**:本工程 boot 任务间有 3~4s 自然静默间隙,判据小于它会把开机日志截断。
- **首字节门(2026-10-10 加)**:首字节到达**之前**不启动静默计时——旧实现从进入即计时,板子第一行比 `--settle-ms` 来得晚就提前收尾("开机日志缺尾巴"的另一半成因)。`--first-timeout-ms`(0 = 同 `--max-ms`)到点仍零字节 → `no_data` 快速失败。
- **`rx_bytes` 是本次增量**(旧实现回的是 daemon 累计值,常驻 daemon 下动辄百万,无判读价值)。
@@ -210,7 +215,8 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat
- 日常**不需要** start(任何命令都会自动拉起)。
- **改了 `serial_tool.py` 之后**要 `restart`,否则运行中的还是旧代码。
- `stop` 后 COM 端口立即释放,可交给其他程序;daemon 死掉/被杀同理(下次命令自动拉起,但**串口不会自动重开**——需要时再 `open`)。
- `stop` 后 COM 端口立即释放,可交给其他程序。`stop` 与 `close` 都会**清除自动重开意图**——之后被拉起的 daemon 不会自动重开口(`auto_reopen.enabled=false`)。
- **v1.2**:daemon 崩溃/被强杀/拔线丢口 → **自动重开**(退避 1.5s→封顶 30s,现场看 `status.auto_reopen`);`restart` 时若重启前口开着,新 daemon 就绪后自动 `open` 回来(`stop` 语义本身不变;返回 JSON 里 `reopen` 键记录这次恢复)。
### 3.10 `selftest` — 无头自检
@@ -243,11 +249,15 @@ Select-String -Path '.dsh\skills\serial-assistant\scripts\state\serial.log' -Pat
[TX] 2026-09-15T02:31:23.986+08:00 hello_from_tool\r\n
[SYS] 2026-09-15T02:31:24.245+08:00 log cleared (gen=1)
[SYS] 2026-09-15T02:39:54.598+08:00 log truncated to 10MB (cap=20MB)
[SYS] 2026-11-04T09:00:00.000+08:00 daemon started v1.2.0-skill pid=4321 prev_pid=16816 auto-reopen pending COM3@115200
[SYS] 2026-11-04T09:00:00.000+08:00 GAP-START after daemon restart: previous daemon (pid=16816) died holding COM3; …
[SYS] 2026-11-04T09:00:01.520+08:00 GAP-END reopened COM3@115200 (gen=7) after 1.5s, 1 attempt(s); bytes lost inside the gap window are unrecoverable
```
| 规则 | 说明 |
|---|---|
| 方向标记 | `[RX]` 串口收到 / `[TX]` 你发出 / `[SYS]` 工具事件(open/close/error/cleared/truncated) |
| 方向标记 | `[RX]` 串口收到 / `[TX]` 你发出 / `[SYS]` 工具事件(open/close/error/cleared/truncated + v1.2 缺口标记) |
| **缺口标记(v1.2)** | `GAP-START`(丢口/崩溃恢复,缺口起)… `GAP-END`(重开成功,缺口止)之间的时间窗内**没有任何 [RX],字节不可找回**;`GAP-RETRY` 是退避期提示(首次 + 之后每分钟 ≤1 条)。有 `GAP-START` 无 `GAP-END` = 采集器还没恢复;口径全文见 `docs/测试说明/测试工具/串口助手设计说明.md` §6.1 |
| 时间戳 | 本地 ISO8601 含毫秒与偏移;= **该行首字节到达时刻** |
| **只含完整行** | 残行留 daemon 内存,200ms 无后续数据才冲刷成行 → 你**永远读不到半行** |
| 解码回退 | UTF-8 → GBK → 都不行则该文本域写 `HEX:ab12…`(纯二进制流时整行转 hex) |
@@ -332,9 +342,10 @@ Get-Content '.dsh\skills\serial-assistant\scripts\state\daemon.log' -Tail 30 #
| `port busy: COMx (held by another process)` | 别的程序占着口 | `Get-Process` 找占用者关掉;或 `… serial_tool.py close` 释放本工具占的口 |
| `daemon start timeout` / `daemon unreachable` | daemon 起不来(端口冲突/解释器问题) | 看 `state/daemon.log`;`… serial_tool.py restart`;确认 18765 未被占(改 `httpPort`) |
| `daemon did not answer within Ns (busy? raise --timeout)` | 命令耗时超过 `--timeout`——daemon 活着、只是还在等(典型:显式给 `wait-idle` 带了小 `--timeout`) | 调大 `--timeout`,或不显式给(`wait-idle` 按 `--max-ms` 自动推导)。2026-10-10 起这种情况**不再**顺手拉起第二个 daemon |
| `wait-idle` 返回 `no_data` | 全程无 RX | 核对 COM 号与波特率;板子供电;烧录是否成功;`status` 看 `port.ready` |
| `wait-idle` 返回 `no_data` | 全程无 RX(**口开着**才算数;v1.2 起口未开是秒回 `ok:false`,不再是 `no_data`) | 核对 COM 号与波特率;板子供电;烧录是否成功;`status` 看 `port.ready` |
| 日志里出现 `HEX:…` 行 | 该段字节不是 UTF-8/GBK 文本(二进制流/乱码) | 正常回退行为,按 hex 对照协议解析 |
| `state: error` + `error: read: …` | 读串口出错/拔线 | daemon 仍活着;重插后 `open` 即恢复(自动 1.5s 退避) |
| `state: error` + `error: read: …` | 读串口出错/拔线 | daemon 仍活着;**v1.2 起自动重连**(退避 1.5s→封顶 30s),插回线缆即自动恢复——进度看 `status.auto_reopen`,也可手工 `open` 立刻重试 |
| `GAP-START` 迟迟没有 `GAP-END` | 自动重开还没成功:口被别的程序占着 / 适配器还没回来 | 看 `status.auto_reopen.last_error` + `state/daemon.log`;腾出口后下一轮退避自动恢复;不想要了 `close` 清意图 |
| 改了 `serial_tool.py` 行为没变 | 运行中的 daemon 还是旧代码 | `… serial_tool.py restart` |
| 开机日志"缺尾巴"(如缺 `Network Init`) | ① `--settle-ms` 太小,在 boot 自然静默间隙就收尾;② 首字节晚于 `--settle-ms` 到达,静默计时提前触发 | 用默认 8000(`run_boot_test.py` 已内置);2026-10-10 起首字节到达前不启动静默计时,必要时调 `--first-timeout-ms` |
| `open` 报 `no port specified … [config: …]` | `.serialtest/config.json` 缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数(现按 `utf-8-sig` 读并**显式回显**错误,不再静默回退成"没配端口") | 按 `config_error` 文本修文件;`status` 与 `ports` 也回显 `config_error` |
@@ -343,7 +354,7 @@ Get-Content '.dsh\skills\serial-assistant\scripts\state\daemon.log' -Tail 30 #
## 8. 常见问题
- **daemon 会一直占着内存吗?** 一个 Python 进程约 20MB;不需要时 `stop` 即释放。常驻与否都不影响正确性。
- **daemon 被强杀(taskkill)会怎样?** 端口由系统自动释放;下次任何命令自动拉起新 daemon。**串口不会自动重开**,需要时再 `open`(这是设计:避免在设备异常时盲目重连)。
- **daemon 被强杀(taskkill)会怎样?** 端口由系统自动释放;下次任何命令自动拉起新 daemon;**v1.2 起串口还会自动重开**——条件是上一任死时口开着且没被显式 close/stop。之后口一直不存在或被别的程序占着,就按 1.5s→30s 倍增退避一直重试,直到成功或你显式 `close`(现场看 `status.auto_reopen`)。
- **能同时开两个串口吗?** v1 单口(一个 daemon 管一个口)。多口需求见 `docs/测试说明/测试工具/串口助手设计说明.md` §8 v2 候选。
- **能直接删 `serial.log` 吗?** 可以,但代际 `gen` 不会变、写句柄不变——建议用 `clear`(原子 + 代际 +1 + 标记行)。
- **为什么 TX 日志里是字面 `\r\n` 而不是真换行?** 日志按行存储;真换行会把一条 TX 劈成两行,转义后一条 TX 恒一行。
@@ -363,7 +374,7 @@ daemon 在 `127.0.0.1:<httpPort>`(默认 18765)提供 JSON API,与 CLI 一
| `POST /close` | — | 同 `close` |
| `POST /send` | `{"text":"hi","nl":true}` 或 `{"hex":"0D0A"}` | 同 `send` |
| `POST /clear` | — | 同 `clear` |
| `POST /wait-idle` | `{"settle_ms":8000,"max_ms":30000,"first_timeout_ms":12000}`(均可省) | **同步阻塞**至静默/首字节超时/整体超时才返回 |
| `POST /wait-idle` | `{"settle_ms":8000,"max_ms":30000,"first_timeout_ms":12000}`(均可省) | **同步阻塞**至静默/首字节超时/整体超时才返回;口未开 → **立即**返回 `ok:false`(v1.2 快速失败) |
| `POST /stop` | — | 关口后 daemon 退出(响应先于退出发出) |
响应码:`200` 成功 / `400` 领域错误(`ok:false`+`error`)/ `504` daemon 忙(命令 20s 未完成)/ `404` 未知端点。
@@ -1,7 +1,7 @@
# Python 串口助手(serial_tool)设计说明
> **版本**:v1.1(需求 + 设计 + 实施记录)
> **日期**:2026-09-15
> **版本**:v1.2(需求 + 设计 + 实施记录;版本记录见 §13)
> **日期**:2026-09-15(v1.0/v1.1)· 2026-11-04(v1.2:daemon 死后自动重开,Gitea issue #75)
> **状态**:**已实施**(步骤 1–6 完成;本机无硬件自检项全部通过;CH340 已接入 = COM3,「拔线容错」与「烧录+抓开机日志全流程」两条正路径的硬件前提已具备,后者已由 `/serial-boot-test` 台架持续跑通)
> **关联文档**:`串口助手使用指南.md`(命令用法/工作流/排障) · `自动化测试流程.md` · `serial-assistant` skill(命令速查/陷阱) · `.serialtest/config.json`(端口单一配置源,2026-10-10 由 `.opencode/serial_config.json` 迁入)
> **取代**:`serial-mcp-server_串口调试.md`(实施时删除,工具历史并入本文 §12)
@@ -84,7 +84,7 @@ flowchart LR
| `send` | 发 TX 字节(进日志);默认**精确字节**不加换行 | 位置参数文本(UTF-8)或 `--hex 0D0A`;`--nl` 追加 CRLF | `{"ok":true,"bytes":12}` |
| `status` | 全状态(见 §4.2) | — | 见 §4.2 |
| `log` | 读日志(**先过滤后截尾**,2026-10-10) | `--tail N`(默认 50;`0` = 全部)`--grep P` `-i` `--regex` | `{"ok":true,"gen":8,"lines":["…"],"scanned":N,"matched":M}` |
| `wait-idle` | 等 RX 静默(/serial-boot-test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}` |
| `wait-idle` | 等 RX 静默(/serial-boot-test 收尾判据) | `--settle-ms`(默认 8000)`--max-ms`(默认 30000)`--first-timeout-ms`(默认同 max) | `{"ok":true,"rx_bytes":本次增量,"idle":bool,"waited_ms":N,"reason":"idle\|no_data\|timeout"}`;口未开 → **秒回** `{"ok":false,"error":"port not open; auto-reopen …"}`(v1.2 快速失败,不再计满返回 `no_data`) |
| `clear` | 清空日志:原地截断 + 写 `[SYS] log cleared (gen=N+1)` | — | `{"ok":true,"gen":8}` |
| `start` / `stop` / `restart` | daemon 生命周期 | — | `{"ok":true,"pid":…}` / stop 后 `{"ok":true,"stopped":true}` |
| `selftest` | 无头自检(行拆分器/解码回退/截头算法 单测回环,无需硬件) | — | 0=PASS |
@@ -95,17 +95,19 @@ flowchart LR
{
"ok": true,
"daemon": {"pid": 1234, "version": "1.0.0", "uptime_s": 61},
"port": {"ready": true, "name": "COM6", "baud": 115200, "dtr": false, "rts": false,
"since": "2026-09-15T10:29:58.112+08:00"},
"port": {"ready": true, "desired": true, "name": "COM6", "baud": 115200, "dtr": false,
"rts": false, "since": "2026-09-15T10:29:58.112+08:00"},
"counters": {"rx_bytes": 1834, "tx_bytes": 12, "rx_lines": 57, "errors": 0},
"last_rx": {"ts": "2026-09-15T10:31:02.441+08:00", "line": "UI menu init: OK", "age_ms": 320},
"log": {"path": "D:\\…\\tools\\serial_tool\\state\\serial.log", "size": 4523, "gen": 7},
"auto_reopen": {"enabled": true, "pending": false, "attempts": 0, "last_error": null,
"next_retry_in_ms": null},
"state": "active",
"error": null
}
```
`state ∈ {stopped, active, error}`;`port.ready = 已开 && state≠error`。Agent 轮询等待只用本命令。
`state ∈ {stopped, active, error}`;`port.ready = 已开 && state≠error`(实际);`port.desired` = 开口意图(v1.2,该不该开着,见 §6.1);顶层 `auto_reopen` = 自动重开循环现场(v1.2)。Agent 轮询等待只用本命令。
### 4.3 CLI 退出码契约
@@ -129,7 +131,7 @@ flowchart LR
| 文件 | 用途 |
|---|---|
| `.dsh/skills/serial-assistant/scripts/state/serial.log` | RX/TX/SYS 全量日志(Agent 直接读) |
| `.dsh/skills/serial-assistant/scripts/state/meta.json`(运行时未创建) | daemon 元数据(pid / 版本 / 当前 gen) |
| `.dsh/skills/serial-assistant/scripts/state/meta.json`(运行时未创建) | daemon 元数据(pid / 版本 / 当前 gen / v1.2 起含开口意图现场 `port`——重启自动重开的依据,§6.1) |
| `.dsh/skills/serial-assistant/scripts/state/daemon.log` | daemon 自身 stderr(排障用) |
### 5.2 行格式
@@ -171,14 +173,35 @@ flowchart LR
| 拉起方式 | CLI detached 子进程(`CREATE_NO_WINDOW`),解释器 = `sys.executable`(保证同一 .venv) |
| `open` 语义 | 幂等(同端口已开 = 成功返回,**不重开**);端口不存在 → ≤1s 报错并附可用端口列表;被占用 → ≤1s 报错(Windows 共享冲突即时返回)并提示持有者 |
| DTR/RTS | 默认**不翻转**(`dtr=False, rts=False`,防 STM32 自动复位电路误复位目标板);`--dtr/--rts` 可显式置位 |
| 读线程容错 | 串口读异常/设备拔出 → 记 `[SYS] … serial error: …`,`state=error`(`status.error` 非空),**daemon 不退出**;重插后 `open` 重开恢复 |
| 重开退避 | 错误恢复路径重开端口前 ≥1.5s 退避(CH340 特性);正常流程不重开 |
| `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出 |
| 进程被强杀 | Windows 进程退出自动释放 COM 端口;下次命令自动拉起新实例(`open` 幂等路径) |
| 读线程容错 | 串口读异常/设备拔出 → 记 `[SYS] … serial error: …(GAP-START…)`,`state=error`(`status.error` 非空),**daemon 不退出**;v1.2 起自动进入重开重试循环(§6.1),插回/让位后自动恢复,也可手工 `open` |
| 重开退避 | 手工 `open` 与自动重开的首次尝试均距上次关口/daemon 启动 ≥1.5s(CH340 特性);自动重开失败按 1.5s 起步倍增、封顶 30s 持续重试(v1.2,§6.1) |
| `stop` | 关端口(flush 尾行)→ 写 `[SYS] port closed` → 退出;同时清除自动重开意图(v1.2) |
| 进程被强杀 | Windows 进程退出自动释放 COM 端口;下次命令自动拉起新实例(`open` 幂等路径);v1.2 起若上一任死时口开着(`meta.json` 的 `port` 现场),新实例**自动重开该口**(§6.1) |
| `wait-idle` 判据(2026-10-10 修) | `no_data` = **本次等待期间零字节**(旧实现比的是 daemon 累计 `rx_bytes` 是否为 0 → 常驻 daemon 下永不触发,板子全程静默也误报 `idle:true`,/serial-boot-test 的 no_data 拦截形同虚设);**首字节到达前不启动 settle 计时**(`first_timeout_ms` 兜底,默认 = `max_ms`);返回 `rx_bytes` = 本次增量 |
| CLI 超时与断连噪声(2026-10-10) | `wait-idle` 未显式带 `--timeout` 时按 `max_ms/1000 + 5` 自动推导(旧版固定 5s 会在 daemon 还在等时掐断连接);handler 侧 `ConnectionError`(客户端已断开)不再往 daemon.log 打 traceback(本机曾累积 16 次);`daemon.log` 超 1MB 在拉起新实例前原地保尾 256KB |
| 配置读取(2026-10-10) | 按 `utf-8-sig` 读(容忍 BOM,与 `run_boot_test.py` 同口径);文件缺失 / JSON 语法错 / `baudRate`·`httpPort` 非整数不再静默回退成"没配端口",错误文本进 `configError`,由 `status` / `ports` / `open` 显式回显 |
### 6.1 自动重开与日志缺口标记(v1.2,issue #75 R1–R4)
**动机**:daemon 是采集层,进程崩溃/被杀/读错丢口后,旧行为 = 「新实例起得来但口不开、serial.log 静默追加无边界」——读日志方无法区分「板子安静」与「采集器死」。v1.2 把「该不该开着口」升格为持久意图,并把采集缺口显式钉进日志。
- **意图现场(R1 载体)**:`meta.json` 的 `port` 字段 = 「上一任死时该不该开着口」。`_save_meta` 写 `_open_identity()`(实开口 > 待重开参数),**重开缺口期间不清空**(缺口正是最该留现场的时候);显式 `close` / `POST /stop` 写 `port:null` 清除意图。新 daemon `run()` **先读旧 meta**(记下旧 `pid` 供边界行引用,**再** `_save_meta` 覆写——旧实现覆写在前,现场即被抹成 null,这就是 R1 的根因)→ `plan_startup_reopen()` 判定:有现场 = 开着口死的 → `desired=True`。
- **重开在串口线程内执行**:「daemon 内串口对象唯一持有者」不变量(§3.1)不因自动重开而破坏——主线程只置标志,`serial_loop` 在 `ser is None 且 desired 且已到重试时刻` 时调 `_auto_reopen_attempt()`;首次尝试 = 线程出生 +1.5s(CH340 释放退避照旧适用于自动路径)。
- **失败自动重连(R2)**:`_port_error`(读错/丢适配器)不再只提示手工 `open`——快照现场后退避循环重试,直到成功或显式 close。退避:`REOPEN_SETTLE_S=1.5` 起步、`next_backoff()` 倍增、`REOPEN_BACKOFF_MAX_S=30` 封顶。每次失败的**错误原文进 daemon.log**(`dlog` = stderr),不刷 serial.log。
- **restart 特判**:`cmd_start(force=True)` 走 `POST /stop`,而 stop 会清现场——「重启」不该弄丢采集。CLI 在 stop **之前**用 `restart_reopen_plan(GET /status)` 抓开口现场,新 daemon 就绪后再 `POST /open` 开回来(open 命令本就经串口线程执行);`stop` 自身语义不变。
- **缺口标记(R3)**——serial.log 的 `[SYS]` 行共四种,构造函数 = serial_tool.py 内同名纯函数(selftest T12 锁格式),行式仍是 §5.2 的 `[SYS] <ISO8601> <文本>`:
| 时机 | 行文本(要点) |
|---|---|
| daemon 启动边界 | `daemon started v<版本> pid=<新> prev_pid=<旧\|none> auto-reopen pending <COMx@baud\|none>` |
| 缺口起(运行中丢口) | `serial error: <原文> (GAP-START: port released; bytes in this window are unrecoverable; auto-reopen retrying, backoff 1.5s doubling to 30s cap)` |
| 缺口起(崩溃恢复) | `GAP-START after daemon restart: previous daemon (pid=B) died holding COMx; gap began no later than now (exact start unknown); …`——崩溃时刻无从得知,只能声明**窗口起点下限** |
| 缺口止(重开成功) | `GAP-END reopened COMx@baud (gen=G) after Ss, N attempt(s); bytes lost inside the gap window are unrecoverable` |
**退避期刷屏口径(定死)**:`GAP-RETRY attempt N failed: <原文> (…)` 行仅**首次失败**(attempts=1)落 serial.log,此后**每 60s 最多一条**(`GAP_NOTE_INTERVAL_S`);逐次重试细节全在 daemon.log。**窗口内字节不可找回是硬事实**:GAP-START 与 GAP-END 之间到达 UART 的字节没有任何进程读过,任何标记/重试都救不回——标记行的价值是把「不可找回窗口」在时间轴上钉出来(读方见 `GAP-START` 无 `GAP-END` = 采集器未恢复,见 `no_data` = 口开着且板子真静默)。
- **状态可见(R4)**:`status.auto_reopen = {enabled(意图在不在), pending(正在退避等重试), attempts, last_error, next_retry_in_ms}`;`port.desired`(意图)与 `port.ready`(实际)并列,缺口期 `port.name/baud` 显示**待重开参数**(回退 `_open_identity`),消费方不用自己拼 meta。`wait-idle` 入口新增**无口门**:口未开 → 秒回 `ok:false` + auto-reopen 现场,**不再**计满返回良性 `no_data`。
- **兼容性**:JSON 只增字段;退出码形状不变(§4.3)。采集层消费者 `run_boot_test.py` 总是 `open→clear→wait-idle`:正常路径不受影响;它的 wait-idle 若撞上无口,`ok:false`(exit 1)经 `_tool_cmd` 归为**采集错误 exit 2**——把「采集器没在采」报成采集错误而非「板子静默」判读失败,正是 issue #75 要的归因。
## 7. 集成与迁移(/serial-boot-test 切换)
### 7.1 新 /serial-boot-test 流程(`run_boot_test.py` 采集步改写,退出码约定不变:0/1/2/3)
@@ -218,7 +241,7 @@ flowchart LR
### 9.1 无头自检(`selftest`,0=PASS)
行拆分器(CRLF/残行冲刷/纯二进制窗口)、解码回退链(UTF-8/GBK/HEX)、截头算法(合成大文件 → ftruncate 语义)——纯进程内,无需硬件。
行拆分器(CRLF/残行冲刷/纯二进制窗口)、解码回退链(UTF-8/GBK/HEX)、截头算法(合成大文件 → ftruncate 语义);v1.2 增补:重开意图判定(T10,含「显式 close 后不重开」「崩溃现场重开」)、退避倍增封顶(T11)、缺口标记行格式(T12)、wait-idle 无口门(T13)、`auto_reopen` 三态(T14)、restart 现场捕获(T15)——共 45 项,纯进程内,无需硬件。
### 9.2 端到端验收(逐项过)
@@ -264,3 +287,11 @@ flowchart LR
| 1 | Serial Studio MCP(GUI 依赖、导出文件定位问题) | 已清理(2026-09 前) |
| 2 | Adancurusul/serial-mcp-server(Rust,MCP+CLI) | **被本工具取代**:二进制绑定用户档案(本机缺失)+ 无历史缓冲(采集窗口仪式) |
| 3 | **serial_tool(本文,Python 仓库内)** | 当前方案 |
## 13. 版本记录
| 版本 | 日期 | 内容 |
|---|---|---|
| v1.0 | 2026-09-15 | 初版实施(§10 步骤 1–6) |
| v1.1 | 2026-10-10 | 修批(各行内「2026-10-10」标注):`log` 先过滤后截尾、wait-idle `no_data` 判据/增量/首字节门、CLI 超时推导与断连噪声、`SO_EXCLUSIVEADDRUSE` 真独占、残行 64KB 封顶与会话边界、截头按行边界、配置 `utf-8-sig` + `configError`;VERSION = `1.1.0-skill` |
| v1.2 | 2026-11-04 | daemon 死后自动重开 + 失败自动重连 + 日志缺口标记 + `status.auto_reopen`/`port.desired` + wait-idle 无口快速失败(机理 §6.1;Gitea issue #75 R1–R5);VERSION = `1.2.0-skill`,selftest 45 项 |
@@ -0,0 +1,866 @@
# 2026-11-04 交流补测轮串口日志摘录(源:serial-assistant serial.log,gen=328,daemon 打戳 +08:00)
# 板固件: fw build: Nov 4 2026 19:05:57 suite=NONE dstr=off(HEAD 87fa0276 构建,烧录回读一致)
# 完整原始日志本机备份: ci-artifacts/ac-20261104/serial_full_backup.log(本机产物,daemon 20MB 截头保险)
# 取证注意: 19:56:47 三条 [ERR][LFT_IO]/[FTP] 是 PC 侧 FTP 探测不存在文件 V5_UP.BIN 所致(操作自归因),非固件缺陷
# 通道静态位观察: 未接线的 TPAFE 输入浮空读数 ≈1743/1747 mV(撤线后 UA/IA 出现,非损伤、非零点漂移)
# # 削顶轮步2(UB 注 DC −1.00 V)锁存窗:19:31:42 唯一 [ERR] rail 行 + rail/edge 0x002 恒持 + 19:36:41 锁存态取段 WAVE11
[RX] 2026-11-04T19:30:06.563+08:00 [2026-11-04 19:30:05][ADC_TASK] frames=1259751 idle=0 skip=126 seq=1260006 frst_err=0 ring=1259625 adc1_rc=0 adc1_err=0 adc1_seq=1260005 adc1_gap=379 adc1_hw=0 tgap=276
[RX] 2026-11-04T19:30:06.584+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=1 (x0.1)
[RX] 2026-11-04T19:30:06.584+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:06.594+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:06.605+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:30:06.605+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:06.615+08:00 [2026-11-04 19:30:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:06.615+08:00 [2026-11-04 19:30:05][ADC_TASK] adc: UA=11429 UB=989 UC=1006 UN=-68 IA=-1469 IB=972 IC=1066 IN=1038 T=964
[RX] 2026-11-04T19:30:06.626+08:00 [2026-11-04 19:30:05][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-224 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:06.637+08:00 [2026-11-04 19:30:05][ADC_TASK] meas: seq=6300 v=1 ua_mv=0 un_mv=0 ia_mv=109 f=0 fv=0
[RX] 2026-11-04T19:30:06.647+08:00 [2026-11-04 19:30:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:16.562+08:00 [2026-11-04 19:30:15][ADC_TASK] frames=1269749 idle=0 skip=127 seq=1270006 frst_err=0 ring=1269622 adc1_rc=0 adc1_err=0 adc1_seq=1270005 adc1_gap=382 adc1_hw=0 tgap=278
[RX] 2026-11-04T19:30:16.583+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.583+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.594+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.604+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.604+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.615+08:00 [2026-11-04 19:30:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:16.615+08:00 [2026-11-04 19:30:15][ADC_TASK] adc: UA=11428 UB=1001 UC=991 UN=-41 IA=-41 IB=973 IC=1062 IN=1028 T=954
[RX] 2026-11-04T19:30:16.626+08:00 [2026-11-04 19:30:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:16.636+08:00 [2026-11-04 19:30:15][ADC_TASK] meas: seq=6350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:16.647+08:00 [2026-11-04 19:30:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:26.570+08:00 [2026-11-04 19:30:25][ADC_TASK] frames=1279747 idle=0 skip=128 seq=1280006 frst_err=0 ring=1279619 adc1_rc=0 adc1_err=0 adc1_seq=1280005 adc1_gap=385 adc1_hw=0 tgap=280
[RX] 2026-11-04T19:30:26.581+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.581+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.591+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.602+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.612+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.612+08:00 [2026-11-04 19:30:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:26.622+08:00 [2026-11-04 19:30:25][ADC_TASK] adc: UA=11429 UB=1019 UC=997 UN=-43 IA=-41 IB=975 IC=1079 IN=1045 T=954
[RX] 2026-11-04T19:30:26.622+08:00 [2026-11-04 19:30:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:26.643+08:00 [2026-11-04 19:30:25][ADC_TASK] meas: seq=6400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:26.643+08:00 [2026-11-04 19:30:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:36.564+08:00 [2026-11-04 19:30:35][ADC_TASK] frames=1289745 idle=0 skip=129 seq=1290006 frst_err=0 ring=1289616 adc1_rc=0 adc1_err=0 adc1_seq=1290005 adc1_gap=388 adc1_hw=0 tgap=282
[RX] 2026-11-04T19:30:36.574+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.585+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.595+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.595+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.606+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.616+08:00 [2026-11-04 19:30:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:36.616+08:00 [2026-11-04 19:30:35][ADC_TASK] adc: UA=11429 UB=1037 UC=1018 UN=-41 IA=-40 IB=983 IC=1082 IN=1057 T=966
[RX] 2026-11-04T19:30:36.627+08:00 [2026-11-04 19:30:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:36.637+08:00 [2026-11-04 19:30:35][ADC_TASK] meas: seq=6450 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:36.648+08:00 [2026-11-04 19:30:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:46.570+08:00 [2026-11-04 19:30:45][ADC_TASK] frames=1299743 idle=0 skip=130 seq=1300006 frst_err=0 ring=1299613 adc1_rc=0 adc1_err=0 adc1_seq=1300005 adc1_gap=391 adc1_hw=0 tgap=284
[RX] 2026-11-04T19:30:46.580+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.580+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.591+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.601+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.612+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.612+08:00 [2026-11-04 19:30:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:46.622+08:00 [2026-11-04 19:30:45][ADC_TASK] adc: UA=11428 UB=1029 UC=1003 UN=-43 IA=-41 IB=978 IC=1054 IN=1032 T=963
[RX] 2026-11-04T19:30:46.622+08:00 [2026-11-04 19:30:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:46.643+08:00 [2026-11-04 19:30:45][ADC_TASK] meas: seq=6500 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:46.643+08:00 [2026-11-04 19:30:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:56.566+08:00 [2026-11-04 19:30:55][ADC_TASK] frames=1309741 idle=0 skip=131 seq=1310006 frst_err=0 ring=1309610 adc1_rc=0 adc1_err=0 adc1_seq=1310005 adc1_gap=394 adc1_hw=0 tgap=286
[RX] 2026-11-04T19:30:56.576+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.586+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.596+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.596+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.607+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.618+08:00 [2026-11-04 19:30:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:56.618+08:00 [2026-11-04 19:30:55][ADC_TASK] adc: UA=11429 UB=1043 UC=1020 UN=-43 IA=-42 IB=993 IC=1053 IN=1026 T=971
[RX] 2026-11-04T19:30:56.628+08:00 [2026-11-04 19:30:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:56.639+08:00 [2026-11-04 19:30:55][ADC_TASK] meas: seq=6550 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:56.639+08:00 [2026-11-04 19:30:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:06.562+08:00 [2026-11-04 19:31:05][ADC_TASK] frames=1319739 idle=0 skip=132 seq=1320006 frst_err=0 ring=1319607 adc1_rc=0 adc1_err=0 adc1_seq=1320005 adc1_gap=397 adc1_hw=0 tgap=288
[RX] 2026-11-04T19:31:06.583+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.583+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.593+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.603+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.603+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.614+08:00 [2026-11-04 19:31:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:06.614+08:00 [2026-11-04 19:31:05][ADC_TASK] adc: UA=11429 UB=1043 UC=1042 UN=-41 IA=-40 IB=986 IC=1046 IN=1010 T=954
[RX] 2026-11-04T19:31:06.624+08:00 [2026-11-04 19:31:05][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:06.635+08:00 [2026-11-04 19:31:05][ADC_TASK] meas: seq=6600 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:06.645+08:00 [2026-11-04 19:31:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:16.561+08:00 [2026-11-04 19:31:15][ADC_TASK] frames=1329737 idle=0 skip=133 seq=1330006 frst_err=0 ring=1329604 adc1_rc=0 adc1_err=0 adc1_seq=1330005 adc1_gap=400 adc1_hw=0 tgap=290
[RX] 2026-11-04T19:31:16.582+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.582+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.593+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.603+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.603+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.613+08:00 [2026-11-04 19:31:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:16.613+08:00 [2026-11-04 19:31:15][ADC_TASK] adc: UA=11429 UB=1038 UC=1042 UN=-41 IA=-40 IB=962 IC=1047 IN=1003 T=935
[RX] 2026-11-04T19:31:16.624+08:00 [2026-11-04 19:31:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:16.634+08:00 [2026-11-04 19:31:15][ADC_TASK] meas: seq=6650 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:16.644+08:00 [2026-11-04 19:31:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:26.566+08:00 [2026-11-04 19:31:25][ADC_TASK] frames=1339735 idle=0 skip=134 seq=1340006 frst_err=0 ring=1339601 adc1_rc=0 adc1_err=0 adc1_seq=1340005 adc1_gap=403 adc1_hw=0 tgap=292
[RX] 2026-11-04T19:31:26.576+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.587+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.597+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.597+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.608+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.608+08:00 [2026-11-04 19:31:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:26.618+08:00 [2026-11-04 19:31:25][ADC_TASK] adc: UA=11428 UB=1024 UC=1049 UN=-42 IA=-41 IB=951 IC=1049 IN=1011 T=931
[RX] 2026-11-04T19:31:26.629+08:00 [2026-11-04 19:31:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:26.639+08:00 [2026-11-04 19:31:25][ADC_TASK] meas: seq=6700 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:26.639+08:00 [2026-11-04 19:31:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:36.566+08:00 [2026-11-04 19:31:35][ADC_TASK] frames=1349733 idle=0 skip=135 seq=1350006 frst_err=0 ring=1349598 adc1_rc=0 adc1_err=0 adc1_seq=1350005 adc1_gap=406 adc1_hw=0 tgap=294
[RX] 2026-11-04T19:31:36.577+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.587+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.587+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.598+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.608+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.608+08:00 [2026-11-04 19:31:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:36.618+08:00 [2026-11-04 19:31:35][ADC_TASK] adc: UA=11428 UB=1005 UC=1054 UN=-43 IA=-42 IB=947 IC=1074 IN=1024 T=934
[RX] 2026-11-04T19:31:36.629+08:00 [2026-11-04 19:31:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:36.639+08:00 [2026-11-04 19:31:35][ADC_TASK] meas: seq=6750 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:36.639+08:00 [2026-11-04 19:31:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:46.560+08:00 [2026-11-04 19:31:45][ADC_TASK] frames=1359731 idle=0 skip=136 seq=1360006 frst_err=0 ring=1359595 adc1_rc=0 adc1_err=0 adc1_seq=1360005 adc1_gap=409 adc1_hw=0 tgap=296
[RX] 2026-11-04T19:31:46.581+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.581+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.592+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.602+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.602+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.613+08:00 [2026-11-04 19:31:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:46.613+08:00 [2026-11-04 19:31:45][ADC_TASK] adc: UA=11428 UB=0 UC=1014 UN=-42 IA=11450 IB=939 IC=1050 IN=1030 T=924
[RX] 2026-11-04T19:31:46.623+08:00 [2026-11-04 19:31:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:31:46.634+08:00 [2026-11-04 19:31:45][ADC_TASK] meas: seq=6800 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:46.644+08:00 [2026-11-04 19:31:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:56.563+08:00 [2026-11-04 19:31:55][ADC_TASK] frames=1369729 idle=0 skip=137 seq=1370006 frst_err=0 ring=1369592 adc1_rc=0 adc1_err=0 adc1_seq=1370005 adc1_gap=412 adc1_hw=0 tgap=298
[RX] 2026-11-04T19:31:56.574+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.584+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.595+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.595+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.605+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.616+08:00 [2026-11-04 19:31:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:56.616+08:00 [2026-11-04 19:31:55][ADC_TASK] adc: UA=11429 UB=0 UC=1018 UN=-44 IA=11452 IB=943 IC=919 IN=991 T=934
[RX] 2026-11-04T19:31:56.626+08:00 [2026-11-04 19:31:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:31:56.637+08:00 [2026-11-04 19:31:55][ADC_TASK] meas: seq=6850 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:56.637+08:00 [2026-11-04 19:31:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:32:06.566+08:00 [2026-11-04 19:32:05][ADC_TASK] frames=1379727 idle=0 skip=138 seq=1380006 frst_err=0 ring=1379589 adc1_rc=0 adc1_err=0 adc1_seq=1380005 adc1_gap=415 adc1_hw=0 tgap=300
[RX] 2026-11-04T19:32:06.577+08:00 [2026-11-04 19:32:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:06.587+08:00 [2026-11-04 19:32:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:06.587+08:00 [2026-11-04 19:32:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:06.598+08:00 [2026-11-04 19:32:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:06.608+08:00 [2026-11-04 19:32:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:06.608+08:00 [2026-11-04 19:32:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:32:06.618+08:00 [2026-11-04 19:32:05][ADC_TASK] adc: UA=11428 UB=0 UC=996 UN=-43 IA=11452 IB=955 IC=836 IN=947 T=946
[RX] 2026-11-04T19:32:06.629+08:00 [2026-11-04 19:32:05][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:32:06.639+08:00 [2026-11-04 19:32:05][ADC_TASK] meas: seq=6900 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:32:06.639+08:00 [2026-11-04 19:32:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:32:16.560+08:00 [2026-11-04 19:32:15][ADC_TASK] frames=1389725 idle=0 skip=139 seq=1390006 frst_err=0 ring=1389586 adc1_rc=0 adc1_err=0 adc1_seq=1390005 adc1_gap=418 adc1_hw=0 tgap=302
[RX] 2026-11-04T19:32:16.581+08:00 [2026-11-04 19:32:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:16.581+08:00 [2026-11-04 19:32:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:16.591+08:00 [2026-11-04 19:32:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:16.591+08:00 [2026-11-04 19:32:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:16.601+08:00 [2026-11-04 19:32:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:16.612+08:00 [2026-11-04 19:32:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:32:16.612+08:00 [2026-11-04 19:32:15][ADC_TASK] adc: UA=11429 UB=0 UC=950 UN=-42 IA=11452 IB=966 IC=805 IN=898 T=945
[RX] 2026-11-04T19:32:16.622+08:00 [2026-11-04 19:32:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:32:16.633+08:00 [2026-11-04 19:32:15][ADC_TASK] meas: seq=6950 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:32:16.643+08:00 [2026-11-04 19:32:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:32:26.566+08:00 [2026-11-04 19:32:25][ADC_TASK] frames=1399723 idle=0 skip=140 seq=1400006 frst_err=0 ring=1399583 adc1_rc=0 adc1_err=0 adc1_seq=1400005 adc1_gap=421 adc1_hw=0 tgap=304
[RX] 2026-11-04T19:32:26.577+08:00 [2026-11-04 19:32:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:26.577+08:00 [2026-11-04 19:32:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:26.588+08:00 [2026-11-04 19:32:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:26.598+08:00 [2026-11-04 19:32:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:26.608+08:00 [2026-11-04 19:32:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:26.608+08:00 [2026-11-04 19:32:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:32:26.619+08:00 [2026-11-04 19:32:25][ADC_TASK] adc: UA=11428 UB=0 UC=966 UN=-61 IA=11452 IB=959 IC=794 IN=875 T=929
[RX] 2026-11-04T19:32:26.629+08:00 [2026-11-04 19:32:25][ADC_TASK] cal: ua_mv=1743 un_mv=-9 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:32:26.639+08:00 [2026-11-04 19:32:25][ADC_TASK] meas: seq=7000 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:32:26.639+08:00 [2026-11-04 19:32:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:32:36.567+08:00 [2026-11-04 19:32:35][ADC_TASK] frames=1409721 idle=0 skip=141 seq=1410006 frst_err=0 ring=1409580 adc1_rc=0 adc1_err=0 adc1_seq=1410005 adc1_gap=424 adc1_hw=0 tgap=306
[RX] 2026-11-04T19:32:36.577+08:00 [2026-11-04 19:32:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:36.577+08:00 [2026-11-04 19:32:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:36.587+08:00 [2026-11-04 19:32:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:36.598+08:00 [2026-11-04 19:32:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:36.609+08:00 [2026-11-04 19:32:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:36.609+08:00 [2026-11-04 19:32:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:32:36.619+08:00 [2026-11-04 19:32:35][ADC_TASK] adc: UA=11427 UB=0 UC=940 UN=-44 IA=11451 IB=932 IC=784 IN=849 T=906
[RX] 2026-11-04T19:32:36.619+08:00 [2026-11-04 19:32:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:32:36.640+08:00 [2026-11-04 19:32:35][ADC_TASK] meas: seq=7050 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:32:36.640+08:00 [2026-11-04 19:32:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:32:46.563+08:00 [2026-11-04 19:32:45][ADC_TASK] frames=1419719 idle=0 skip=142 seq=1420006 frst_err=0 ring=1419577 adc1_rc=0 adc1_err=0 adc1_seq=1420005 adc1_gap=427 adc1_hw=0 tgap=308
[RX] 2026-11-04T19:32:46.573+08:00 [2026-11-04 19:32:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:46.584+08:00 [2026-11-04 19:32:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:46.594+08:00 [2026-11-04 19:32:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:46.594+08:00 [2026-11-04 19:32:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:46.605+08:00 [2026-11-04 19:32:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:46.605+08:00 [2026-11-04 19:32:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:32:46.615+08:00 [2026-11-04 19:32:45][ADC_TASK] adc: UA=11427 UB=0 UC=912 UN=-42 IA=11451 IB=946 IC=772 IN=831 T=873
[RX] 2026-11-04T19:32:46.626+08:00 [2026-11-04 19:32:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:32:46.636+08:00 [2026-11-04 19:32:45][ADC_TASK] meas: seq=7100 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:32:46.636+08:00 [2026-11-04 19:32:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:32:56.567+08:00 [2026-11-04 19:32:55][ADC_TASK] frames=1429717 idle=0 skip=143 seq=1430006 frst_err=0 ring=1429574 adc1_rc=0 adc1_err=0 adc1_seq=1430005 adc1_gap=430 adc1_hw=0 tgap=310
[RX] 2026-11-04T19:32:56.577+08:00 [2026-11-04 19:32:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:56.577+08:00 [2026-11-04 19:32:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:56.588+08:00 [2026-11-04 19:32:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:56.598+08:00 [2026-11-04 19:32:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:56.609+08:00 [2026-11-04 19:32:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:32:56.609+08:00 [2026-11-04 19:32:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:32:56.619+08:00 [2026-11-04 19:32:55][ADC_TASK] adc: UA=11428 UB=0 UC=903 UN=-54 IA=11450 IB=948 IC=770 IN=826 T=895
[RX] 2026-11-04T19:32:56.619+08:00 [2026-11-04 19:32:55][ADC_TASK] cal: ua_mv=1743 un_mv=-8 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:32:56.630+08:00 [2026-11-04 19:32:55][ADC_TASK] meas: seq=7150 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:32:56.640+08:00 [2026-11-04 19:32:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:33:06.557+08:00 [2026-11-04 19:33:05][ADC_TASK] frames=1439715 idle=0 skip=144 seq=1440006 frst_err=0 ring=1439571 adc1_rc=0 adc1_err=0 adc1_seq=1440005 adc1_gap=433 adc1_hw=0 tgap=312
[RX] 2026-11-04T19:33:06.577+08:00 [2026-11-04 19:33:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:06.577+08:00 [2026-11-04 19:33:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:06.588+08:00 [2026-11-04 19:33:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:06.599+08:00 [2026-11-04 19:33:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:06.599+08:00 [2026-11-04 19:33:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:06.609+08:00 [2026-11-04 19:33:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:33:06.620+08:00 [2026-11-04 19:33:05][ADC_TASK] adc: UA=11427 UB=0 UC=900 UN=-54 IA=11451 IB=923 IC=772 IN=827 T=903
[RX] 2026-11-04T19:33:06.620+08:00 [2026-11-04 19:33:05][ADC_TASK] cal: ua_mv=1743 un_mv=-8 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:33:06.630+08:00 [2026-11-04 19:33:05][ADC_TASK] meas: seq=7200 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:33:06.641+08:00 [2026-11-04 19:33:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:33:16.557+08:00 [2026-11-04 19:33:15][ADC_TASK] frames=1449713 idle=0 skip=145 seq=1450006 frst_err=0 ring=1449568 adc1_rc=0 adc1_err=0 adc1_seq=1450005 adc1_gap=436 adc1_hw=0 tgap=314
[RX] 2026-11-04T19:33:16.577+08:00 [2026-11-04 19:33:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:16.577+08:00 [2026-11-04 19:33:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:16.587+08:00 [2026-11-04 19:33:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:16.598+08:00 [2026-11-04 19:33:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:16.608+08:00 [2026-11-04 19:33:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:16.608+08:00 [2026-11-04 19:33:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:33:16.619+08:00 [2026-11-04 19:33:15][ADC_TASK] adc: UA=11427 UB=0 UC=895 UN=-53 IA=11451 IB=914 IC=773 IN=827 T=907
[RX] 2026-11-04T19:33:16.619+08:00 [2026-11-04 19:33:15][ADC_TASK] cal: ua_mv=1743 un_mv=-8 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:33:16.629+08:00 [2026-11-04 19:33:15][ADC_TASK] meas: seq=7250 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:33:16.640+08:00 [2026-11-04 19:33:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:33:26.563+08:00 [2026-11-04 19:33:25][ADC_TASK] frames=1459711 idle=0 skip=146 seq=1460006 frst_err=0 ring=1459565 adc1_rc=0 adc1_err=0 adc1_seq=1460005 adc1_gap=439 adc1_hw=0 tgap=316
[RX] 2026-11-04T19:33:26.574+08:00 [2026-11-04 19:33:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:26.584+08:00 [2026-11-04 19:33:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:26.584+08:00 [2026-11-04 19:33:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:26.595+08:00 [2026-11-04 19:33:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:26.605+08:00 [2026-11-04 19:33:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:26.605+08:00 [2026-11-04 19:33:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:33:26.615+08:00 [2026-11-04 19:33:25][ADC_TASK] adc: UA=11429 UB=0 UC=895 UN=-49 IA=11451 IB=915 IC=775 IN=828 T=900
[RX] 2026-11-04T19:33:26.626+08:00 [2026-11-04 19:33:25][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:33:26.636+08:00 [2026-11-04 19:33:25][ADC_TASK] meas: seq=7300 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:33:26.636+08:00 [2026-11-04 19:33:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:33:36.558+08:00 [2026-11-04 19:33:35][ADC_TASK] frames=1469709 idle=0 skip=147 seq=1470006 frst_err=0 ring=1469562 adc1_rc=0 adc1_err=0 adc1_seq=1470005 adc1_gap=442 adc1_hw=0 tgap=318
[RX] 2026-11-04T19:33:36.579+08:00 [2026-11-04 19:33:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:36.579+08:00 [2026-11-04 19:33:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:36.589+08:00 [2026-11-04 19:33:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:36.599+08:00 [2026-11-04 19:33:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:36.599+08:00 [2026-11-04 19:33:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:36.610+08:00 [2026-11-04 19:33:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:33:36.610+08:00 [2026-11-04 19:33:35][ADC_TASK] adc: UA=11428 UB=0 UC=901 UN=-50 IA=11451 IB=952 IC=780 IN=833 T=881
[RX] 2026-11-04T19:33:36.621+08:00 [2026-11-04 19:33:35][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:33:36.631+08:00 [2026-11-04 19:33:35][ADC_TASK] meas: seq=7350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:33:36.642+08:00 [2026-11-04 19:33:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:33:46.566+08:00 [2026-11-04 19:33:45][ADC_TASK] frames=1479707 idle=0 skip=148 seq=1480006 frst_err=0 ring=1479559 adc1_rc=0 adc1_err=0 adc1_seq=1480005 adc1_gap=445 adc1_hw=0 tgap=320
[RX] 2026-11-04T19:33:46.576+08:00 [2026-11-04 19:33:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:46.576+08:00 [2026-11-04 19:33:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:46.586+08:00 [2026-11-04 19:33:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:46.596+08:00 [2026-11-04 19:33:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:46.607+08:00 [2026-11-04 19:33:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:46.607+08:00 [2026-11-04 19:33:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:33:46.617+08:00 [2026-11-04 19:33:45][ADC_TASK] adc: UA=11428 UB=0 UC=893 UN=-50 IA=11451 IB=947 IC=768 IN=826 T=869
[RX] 2026-11-04T19:33:46.617+08:00 [2026-11-04 19:33:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:33:46.628+08:00 [2026-11-04 19:33:45][ADC_TASK] meas: seq=7400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:33:46.639+08:00 [2026-11-04 19:33:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:33:56.556+08:00 [2026-11-04 19:33:55][ADC_TASK] frames=1489705 idle=0 skip=149 seq=1490006 frst_err=0 ring=1489556 adc1_rc=0 adc1_err=0 adc1_seq=1490005 adc1_gap=448 adc1_hw=0 tgap=322
[RX] 2026-11-04T19:33:56.577+08:00 [2026-11-04 19:33:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:56.577+08:00 [2026-11-04 19:33:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:56.588+08:00 [2026-11-04 19:33:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:56.598+08:00 [2026-11-04 19:33:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:56.598+08:00 [2026-11-04 19:33:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:33:56.609+08:00 [2026-11-04 19:33:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:33:56.609+08:00 [2026-11-04 19:33:55][ADC_TASK] adc: UA=11426 UB=0 UC=906 UN=-51 IA=11451 IB=962 IC=776 IN=840 T=895
[RX] 2026-11-04T19:33:56.619+08:00 [2026-11-04 19:33:55][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:33:56.630+08:00 [2026-11-04 19:33:55][ADC_TASK] meas: seq=7450 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:33:56.640+08:00 [2026-11-04 19:33:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:34:06.562+08:00 [2026-11-04 19:34:05][ADC_TASK] frames=1499703 idle=0 skip=150 seq=1500006 frst_err=0 ring=1499553 adc1_rc=0 adc1_err=0 adc1_seq=1500005 adc1_gap=451 adc1_hw=0 tgap=324
[RX] 2026-11-04T19:34:06.572+08:00 [2026-11-04 19:34:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:06.583+08:00 [2026-11-04 19:34:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:06.583+08:00 [2026-11-04 19:34:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:06.593+08:00 [2026-11-04 19:34:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:06.604+08:00 [2026-11-04 19:34:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:06.604+08:00 [2026-11-04 19:34:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:34:06.614+08:00 [2026-11-04 19:34:05][ADC_TASK] adc: UA=11428 UB=0 UC=908 UN=-38 IA=11451 IB=934 IC=673 IN=838 T=899
[RX] 2026-11-04T19:34:06.624+08:00 [2026-11-04 19:34:05][ADC_TASK] cal: ua_mv=1743 un_mv=-5 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:34:06.634+08:00 [2026-11-04 19:34:05][ADC_TASK] meas: seq=7500 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:34:06.634+08:00 [2026-11-04 19:34:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:34:16.555+08:00 [2026-11-04 19:34:15][ADC_TASK] frames=1509701 idle=0 skip=151 seq=1510006 frst_err=0 ring=1509550 adc1_rc=0 adc1_err=0 adc1_seq=1510005 adc1_gap=454 adc1_hw=0 tgap=326
[RX] 2026-11-04T19:34:16.575+08:00 [2026-11-04 19:34:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:16.575+08:00 [2026-11-04 19:34:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:16.586+08:00 [2026-11-04 19:34:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:16.596+08:00 [2026-11-04 19:34:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:16.606+08:00 [2026-11-04 19:34:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:16.606+08:00 [2026-11-04 19:34:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:34:16.617+08:00 [2026-11-04 19:34:15][ADC_TASK] adc: UA=11427 UB=0 UC=874 UN=-38 IA=11451 IB=938 IC=432 IN=735 T=898
[RX] 2026-11-04T19:34:16.617+08:00 [2026-11-04 19:34:15][ADC_TASK] cal: ua_mv=1743 un_mv=-5 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:34:16.637+08:00 [2026-11-04 19:34:15][ADC_TASK] meas: seq=7550 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:34:16.637+08:00 [2026-11-04 19:34:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:34:26.561+08:00 [2026-11-04 19:34:25][ADC_TASK] frames=1519699 idle=0 skip=152 seq=1520006 frst_err=0 ring=1519547 adc1_rc=0 adc1_err=0 adc1_seq=1520005 adc1_gap=457 adc1_hw=0 tgap=328
[RX] 2026-11-04T19:34:26.571+08:00 [2026-11-04 19:34:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:26.582+08:00 [2026-11-04 19:34:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:26.593+08:00 [2026-11-04 19:34:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:26.593+08:00 [2026-11-04 19:34:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:26.603+08:00 [2026-11-04 19:34:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:26.603+08:00 [2026-11-04 19:34:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:34:26.613+08:00 [2026-11-04 19:34:25][ADC_TASK] adc: UA=11429 UB=0 UC=811 UN=-35 IA=11451 IB=933 IC=361 IN=663 T=896
[RX] 2026-11-04T19:34:26.624+08:00 [2026-11-04 19:34:25][ADC_TASK] cal: ua_mv=1743 un_mv=-5 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:34:26.634+08:00 [2026-11-04 19:34:25][ADC_TASK] meas: seq=7600 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:34:26.634+08:00 [2026-11-04 19:34:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:34:36.556+08:00 [2026-11-04 19:34:35][ADC_TASK] frames=1529697 idle=0 skip=153 seq=1530006 frst_err=0 ring=1529544 adc1_rc=0 adc1_err=0 adc1_seq=1530005 adc1_gap=460 adc1_hw=0 tgap=330
[RX] 2026-11-04T19:34:36.577+08:00 [2026-11-04 19:34:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:36.577+08:00 [2026-11-04 19:34:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:36.587+08:00 [2026-11-04 19:34:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:36.598+08:00 [2026-11-04 19:34:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:36.598+08:00 [2026-11-04 19:34:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:36.608+08:00 [2026-11-04 19:34:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:34:36.608+08:00 [2026-11-04 19:34:35][ADC_TASK] adc: UA=11428 UB=0 UC=789 UN=-38 IA=11451 IB=933 IC=340 IN=631 T=851
[RX] 2026-11-04T19:34:36.619+08:00 [2026-11-04 19:34:35][ADC_TASK] cal: ua_mv=1743 un_mv=-5 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:34:36.629+08:00 [2026-11-04 19:34:35][ADC_TASK] meas: seq=7650 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:34:36.640+08:00 [2026-11-04 19:34:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:34:46.555+08:00 [2026-11-04 19:34:45][ADC_TASK] frames=1539695 idle=0 skip=154 seq=1540006 frst_err=0 ring=1539541 adc1_rc=0 adc1_err=0 adc1_seq=1540005 adc1_gap=463 adc1_hw=0 tgap=332
[RX] 2026-11-04T19:34:46.576+08:00 [2026-11-04 19:34:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:46.576+08:00 [2026-11-04 19:34:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:46.586+08:00 [2026-11-04 19:34:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:46.596+08:00 [2026-11-04 19:34:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:46.596+08:00 [2026-11-04 19:34:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:46.607+08:00 [2026-11-04 19:34:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:34:46.607+08:00 [2026-11-04 19:34:45][ADC_TASK] adc: UA=11429 UB=0 UC=781 UN=-40 IA=11450 IB=887 IC=367 IN=613 T=813
[RX] 2026-11-04T19:34:46.618+08:00 [2026-11-04 19:34:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:34:46.628+08:00 [2026-11-04 19:34:45][ADC_TASK] meas: seq=7700 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:34:46.639+08:00 [2026-11-04 19:34:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:34:56.556+08:00 [2026-11-04 19:34:55][ADC_TASK] frames=1549693 idle=0 skip=155 seq=1550006 frst_err=0 ring=1549538 adc1_rc=0 adc1_err=0 adc1_seq=1550005 adc1_gap=466 adc1_hw=0 tgap=334
[RX] 2026-11-04T19:34:56.578+08:00 [2026-11-04 19:34:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:56.578+08:00 [2026-11-04 19:34:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:56.588+08:00 [2026-11-04 19:34:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:56.588+08:00 [2026-11-04 19:34:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:56.599+08:00 [2026-11-04 19:34:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:34:56.609+08:00 [2026-11-04 19:34:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:34:56.609+08:00 [2026-11-04 19:34:55][ADC_TASK] adc: UA=11427 UB=0 UC=783 UN=-41 IA=11450 IB=884 IC=518 IN=660 T=818
[RX] 2026-11-04T19:34:56.619+08:00 [2026-11-04 19:34:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:34:56.630+08:00 [2026-11-04 19:34:55][ADC_TASK] meas: seq=7750 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:34:56.640+08:00 [2026-11-04 19:34:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:35:06.556+08:00 [2026-11-04 19:35:05][ADC_TASK] frames=1559691 idle=0 skip=156 seq=1560006 frst_err=0 ring=1559535 adc1_rc=0 adc1_err=0 adc1_seq=1560005 adc1_gap=469 adc1_hw=0 tgap=336
[RX] 2026-11-04T19:35:06.577+08:00 [2026-11-04 19:35:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:06.577+08:00 [2026-11-04 19:35:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:06.587+08:00 [2026-11-04 19:35:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:06.587+08:00 [2026-11-04 19:35:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:06.597+08:00 [2026-11-04 19:35:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:06.608+08:00 [2026-11-04 19:35:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:35:06.608+08:00 [2026-11-04 19:35:05][ADC_TASK] adc: UA=11428 UB=0 UC=801 UN=-38 IA=11451 IB=888 IC=571 IN=704 T=826
[RX] 2026-11-04T19:35:06.618+08:00 [2026-11-04 19:35:05][ADC_TASK] cal: ua_mv=1743 un_mv=-5 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:35:06.629+08:00 [2026-11-04 19:35:05][ADC_TASK] meas: seq=7800 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:35:06.639+08:00 [2026-11-04 19:35:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:35:16.559+08:00 [2026-11-04 19:35:15][ADC_TASK] frames=1569689 idle=0 skip=157 seq=1570006 frst_err=0 ring=1569532 adc1_rc=0 adc1_err=0 adc1_seq=1570005 adc1_gap=472 adc1_hw=0 tgap=338
[RX] 2026-11-04T19:35:16.570+08:00 [2026-11-04 19:35:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:16.581+08:00 [2026-11-04 19:35:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:16.591+08:00 [2026-11-04 19:35:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:16.591+08:00 [2026-11-04 19:35:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:16.602+08:00 [2026-11-04 19:35:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:16.602+08:00 [2026-11-04 19:35:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:35:16.612+08:00 [2026-11-04 19:35:15][ADC_TASK] adc: UA=11428 UB=0 UC=832 UN=-40 IA=11451 IB=891 IC=577 IN=729 T=832
[RX] 2026-11-04T19:35:16.623+08:00 [2026-11-04 19:35:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:35:16.633+08:00 [2026-11-04 19:35:15][ADC_TASK] meas: seq=7850 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:35:16.633+08:00 [2026-11-04 19:35:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:35:26.561+08:00 [2026-11-04 19:35:25][ADC_TASK] frames=1579687 idle=0 skip=158 seq=1580006 frst_err=0 ring=1579529 adc1_rc=0 adc1_err=0 adc1_seq=1580005 adc1_gap=475 adc1_hw=0 tgap=340
[RX] 2026-11-04T19:35:26.572+08:00 [2026-11-04 19:35:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:26.582+08:00 [2026-11-04 19:35:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:26.582+08:00 [2026-11-04 19:35:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:26.593+08:00 [2026-11-04 19:35:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:26.603+08:00 [2026-11-04 19:35:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:26.603+08:00 [2026-11-04 19:35:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:35:26.614+08:00 [2026-11-04 19:35:25][ADC_TASK] adc: UA=11429 UB=0 UC=842 UN=-41 IA=11451 IB=895 IC=576 IN=730 T=844
[RX] 2026-11-04T19:35:26.624+08:00 [2026-11-04 19:35:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:35:26.634+08:00 [2026-11-04 19:35:25][ADC_TASK] meas: seq=7900 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:35:26.634+08:00 [2026-11-04 19:35:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:35:36.562+08:00 [2026-11-04 19:35:35][ADC_TASK] frames=1589685 idle=0 skip=159 seq=1590006 frst_err=0 ring=1589526 adc1_rc=0 adc1_err=0 adc1_seq=1590005 adc1_gap=478 adc1_hw=0 tgap=342
[RX] 2026-11-04T19:35:36.573+08:00 [2026-11-04 19:35:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:36.573+08:00 [2026-11-04 19:35:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:36.583+08:00 [2026-11-04 19:35:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:36.593+08:00 [2026-11-04 19:35:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:36.604+08:00 [2026-11-04 19:35:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:36.604+08:00 [2026-11-04 19:35:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:35:36.614+08:00 [2026-11-04 19:35:35][ADC_TASK] adc: UA=11427 UB=0 UC=847 UN=-41 IA=11450 IB=912 IC=575 IN=731 T=861
[RX] 2026-11-04T19:35:36.614+08:00 [2026-11-04 19:35:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:35:36.635+08:00 [2026-11-04 19:35:35][ADC_TASK] meas: seq=7950 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:35:36.635+08:00 [2026-11-04 19:35:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:35:46.560+08:00 [2026-11-04 19:35:45][ADC_TASK] frames=1599683 idle=0 skip=160 seq=1600006 frst_err=0 ring=1599523 adc1_rc=0 adc1_err=0 adc1_seq=1600005 adc1_gap=481 adc1_hw=0 tgap=344
[RX] 2026-11-04T19:35:46.571+08:00 [2026-11-04 19:35:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:46.581+08:00 [2026-11-04 19:35:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:46.581+08:00 [2026-11-04 19:35:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:46.592+08:00 [2026-11-04 19:35:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:46.602+08:00 [2026-11-04 19:35:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:46.602+08:00 [2026-11-04 19:35:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:35:46.613+08:00 [2026-11-04 19:35:45][ADC_TASK] adc: UA=11428 UB=0 UC=853 UN=-41 IA=11451 IB=937 IC=589 IN=738 T=877
[RX] 2026-11-04T19:35:46.624+08:00 [2026-11-04 19:35:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:35:46.634+08:00 [2026-11-04 19:35:45][ADC_TASK] meas: seq=8000 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:35:46.634+08:00 [2026-11-04 19:35:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:35:56.555+08:00 [2026-11-04 19:35:55][ADC_TASK] frames=1609681 idle=0 skip=161 seq=1610006 frst_err=0 ring=1609520 adc1_rc=0 adc1_err=0 adc1_seq=1610005 adc1_gap=484 adc1_hw=0 tgap=346
[RX] 2026-11-04T19:35:56.575+08:00 [2026-11-04 19:35:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:56.575+08:00 [2026-11-04 19:35:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:56.585+08:00 [2026-11-04 19:35:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:56.596+08:00 [2026-11-04 19:35:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:56.596+08:00 [2026-11-04 19:35:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:35:56.606+08:00 [2026-11-04 19:35:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:35:56.606+08:00 [2026-11-04 19:35:55][ADC_TASK] adc: UA=11427 UB=0 UC=868 UN=-41 IA=11452 IB=953 IC=609 IN=758 T=892
[RX] 2026-11-04T19:35:56.617+08:00 [2026-11-04 19:35:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:35:56.627+08:00 [2026-11-04 19:35:55][ADC_TASK] meas: seq=8050 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:35:56.637+08:00 [2026-11-04 19:35:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:36:06.560+08:00 [2026-11-04 19:36:05][ADC_TASK] frames=1619679 idle=0 skip=162 seq=1620006 frst_err=0 ring=1619517 adc1_rc=0 adc1_err=0 adc1_seq=1620005 adc1_gap=487 adc1_hw=0 tgap=348
[RX] 2026-11-04T19:36:06.570+08:00 [2026-11-04 19:36:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:06.581+08:00 [2026-11-04 19:36:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:06.581+08:00 [2026-11-04 19:36:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:06.591+08:00 [2026-11-04 19:36:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:06.602+08:00 [2026-11-04 19:36:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:06.602+08:00 [2026-11-04 19:36:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:36:06.612+08:00 [2026-11-04 19:36:05][ADC_TASK] adc: UA=11429 UB=0 UC=875 UN=-41 IA=11451 IB=949 IC=604 IN=768 T=898
[RX] 2026-11-04T19:36:06.622+08:00 [2026-11-04 19:36:05][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:36:06.633+08:00 [2026-11-04 19:36:05][ADC_TASK] meas: seq=8100 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:36:06.633+08:00 [2026-11-04 19:36:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:36:16.553+08:00 [2026-11-04 19:36:15][ADC_TASK] frames=1629677 idle=0 skip=163 seq=1630006 frst_err=0 ring=1629514 adc1_rc=0 adc1_err=0 adc1_seq=1630005 adc1_gap=490 adc1_hw=0 tgap=350
[RX] 2026-11-04T19:36:16.574+08:00 [2026-11-04 19:36:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:16.574+08:00 [2026-11-04 19:36:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:16.585+08:00 [2026-11-04 19:36:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:16.595+08:00 [2026-11-04 19:36:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:16.595+08:00 [2026-11-04 19:36:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:16.606+08:00 [2026-11-04 19:36:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:36:16.606+08:00 [2026-11-04 19:36:15][ADC_TASK] adc: UA=11428 UB=0 UC=881 UN=-43 IA=11452 IB=945 IC=600 IN=764 T=898
[RX] 2026-11-04T19:36:16.617+08:00 [2026-11-04 19:36:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:36:16.627+08:00 [2026-11-04 19:36:15][ADC_TASK] meas: seq=8150 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:36:16.637+08:00 [2026-11-04 19:36:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:36:26.557+08:00 [2026-11-04 19:36:25][ADC_TASK] frames=1639675 idle=0 skip=164 seq=1640006 frst_err=0 ring=1639511 adc1_rc=0 adc1_err=0 adc1_seq=1640005 adc1_gap=493 adc1_hw=0 tgap=352
[RX] 2026-11-04T19:36:26.567+08:00 [2026-11-04 19:36:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:26.578+08:00 [2026-11-04 19:36:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:26.588+08:00 [2026-11-04 19:36:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:26.588+08:00 [2026-11-04 19:36:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:26.599+08:00 [2026-11-04 19:36:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:26.609+08:00 [2026-11-04 19:36:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:36:26.609+08:00 [2026-11-04 19:36:25][ADC_TASK] adc: UA=11428 UB=0 UC=878 UN=-42 IA=11452 IB=947 IC=591 IN=753 T=873
[RX] 2026-11-04T19:36:26.619+08:00 [2026-11-04 19:36:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:36:26.630+08:00 [2026-11-04 19:36:25][ADC_TASK] meas: seq=8200 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:36:26.630+08:00 [2026-11-04 19:36:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:36:36.554+08:00 [2026-11-04 19:36:35][ADC_TASK] frames=1649673 idle=0 skip=165 seq=1650006 frst_err=0 ring=1649508 adc1_rc=0 adc1_err=0 adc1_seq=1650005 adc1_gap=496 adc1_hw=0 tgap=354
[RX] 2026-11-04T19:36:36.575+08:00 [2026-11-04 19:36:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:36.575+08:00 [2026-11-04 19:36:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:36.585+08:00 [2026-11-04 19:36:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:36.585+08:00 [2026-11-04 19:36:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:36.596+08:00 [2026-11-04 19:36:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:36.606+08:00 [2026-11-04 19:36:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:36:36.606+08:00 [2026-11-04 19:36:35][ADC_TASK] adc: UA=11429 UB=0 UC=884 UN=-41 IA=11452 IB=945 IC=599 IN=759 T=866
[RX] 2026-11-04T19:36:36.617+08:00 [2026-11-04 19:36:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:36:36.627+08:00 [2026-11-04 19:36:35][ADC_TASK] meas: seq=8250 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:36:36.638+08:00 [2026-11-04 19:36:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:36:42.752+08:00 [2026-11-04 19:36:41] [WAVE][WARN] seg held 886 ms (idx=12)
[RX] 2026-11-04T19:36:46.556+08:00 [2026-11-04 19:36:45][ADC_TASK] frames=1659671 idle=0 skip=166 seq=1660006 frst_err=0 ring=1659505 adc1_rc=0 adc1_err=0 adc1_seq=1660005 adc1_gap=499 adc1_hw=0 tgap=356
[RX] 2026-11-04T19:36:46.566+08:00 [2026-11-04 19:36:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:46.577+08:00 [2026-11-04 19:36:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:46.587+08:00 [2026-11-04 19:36:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:46.587+08:00 [2026-11-04 19:36:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:46.597+08:00 [2026-11-04 19:36:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:46.608+08:00 [2026-11-04 19:36:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:36:46.608+08:00 [2026-11-04 19:36:45][ADC_TASK] adc: UA=11428 UB=0 UC=878 UN=-41 IA=11452 IB=930 IC=611 IN=760 T=855
[RX] 2026-11-04T19:36:46.618+08:00 [2026-11-04 19:36:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:36:46.628+08:00 [2026-11-04 19:36:45][ADC_TASK] meas: seq=8300 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:36:46.639+08:00 [2026-11-04 19:36:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:36:56.556+08:00 [2026-11-04 19:36:55][ADC_TASK] frames=1669669 idle=0 skip=167 seq=1670006 frst_err=0 ring=1669502 adc1_rc=0 adc1_err=0 adc1_seq=1670005 adc1_gap=502 adc1_hw=0 tgap=358
[RX] 2026-11-04T19:36:56.567+08:00 [2026-11-04 19:36:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:56.577+08:00 [2026-11-04 19:36:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:56.588+08:00 [2026-11-04 19:36:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:56.588+08:00 [2026-11-04 19:36:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:56.598+08:00 [2026-11-04 19:36:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:36:56.598+08:00 [2026-11-04 19:36:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:36:56.608+08:00 [2026-11-04 19:36:55][ADC_TASK] adc: UA=11428 UB=0 UC=863 UN=-42 IA=11451 IB=926 IC=614 IN=773 T=866
[RX] 2026-11-04T19:36:56.619+08:00 [2026-11-04 19:36:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:36:56.629+08:00 [2026-11-04 19:36:55][ADC_TASK] meas: seq=8350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:36:56.629+08:00 [2026-11-04 19:36:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:37:06.553+08:00 [2026-11-04 19:37:05][ADC_TASK] frames=1679667 idle=0 skip=168 seq=1680006 frst_err=0 ring=1679499 adc1_rc=0 adc1_err=0 adc1_seq=1680005 adc1_gap=505 adc1_hw=0 tgap=360
[RX] 2026-11-04T19:37:06.574+08:00 [2026-11-04 19:37:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:06.574+08:00 [2026-11-04 19:37:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:06.584+08:00 [2026-11-04 19:37:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:06.584+08:00 [2026-11-04 19:37:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:06.595+08:00 [2026-11-04 19:37:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:06.605+08:00 [2026-11-04 19:37:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:37:06.605+08:00 [2026-11-04 19:37:05][ADC_TASK] adc: UA=11428 UB=0 UC=855 UN=-42 IA=11451 IB=949 IC=596 IN=767 T=891
[RX] 2026-11-04T19:37:06.616+08:00 [2026-11-04 19:37:05][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:37:06.626+08:00 [2026-11-04 19:37:05][ADC_TASK] meas: seq=8400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:37:06.637+08:00 [2026-11-04 19:37:05][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:37:16.556+08:00 [2026-11-04 19:37:15][ADC_TASK] frames=1689665 idle=0 skip=169 seq=1690006 frst_err=0 ring=1689496 adc1_rc=0 adc1_err=0 adc1_seq=1690005 adc1_gap=508 adc1_hw=0 tgap=362
[RX] 2026-11-04T19:37:16.567+08:00 [2026-11-04 19:37:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:16.577+08:00 [2026-11-04 19:37:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:16.588+08:00 [2026-11-04 19:37:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:16.588+08:00 [2026-11-04 19:37:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:16.599+08:00 [2026-11-04 19:37:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:16.599+08:00 [2026-11-04 19:37:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:37:16.609+08:00 [2026-11-04 19:37:15][ADC_TASK] adc: UA=11429 UB=0 UC=866 UN=-43 IA=11452 IB=941 IC=583 IN=748 T=889
[RX] 2026-11-04T19:37:16.619+08:00 [2026-11-04 19:37:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:37:16.630+08:00 [2026-11-04 19:37:15][ADC_TASK] meas: seq=8450 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:37:16.630+08:00 [2026-11-04 19:37:15][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:37:26.554+08:00 [2026-11-04 19:37:25][ADC_TASK] frames=1699663 idle=0 skip=170 seq=1700006 frst_err=0 ring=1699493 adc1_rc=0 adc1_err=0 adc1_seq=1700005 adc1_gap=511 adc1_hw=0 tgap=364
[RX] 2026-11-04T19:37:26.564+08:00 [2026-11-04 19:37:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:26.575+08:00 [2026-11-04 19:37:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:26.586+08:00 [2026-11-04 19:37:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:26.586+08:00 [2026-11-04 19:37:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:26.596+08:00 [2026-11-04 19:37:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:26.607+08:00 [2026-11-04 19:37:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:37:26.607+08:00 [2026-11-04 19:37:25][ADC_TASK] adc: UA=11429 UB=0 UC=872 UN=-40 IA=11452 IB=936 IC=592 IN=746 T=886
[RX] 2026-11-04T19:37:26.617+08:00 [2026-11-04 19:37:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:37:26.628+08:00 [2026-11-04 19:37:25][ADC_TASK] meas: seq=8500 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:37:26.628+08:00 [2026-11-04 19:37:25][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:37:36.554+08:00 [2026-11-04 19:37:35][ADC_TASK] frames=1709661 idle=0 skip=171 seq=1710006 frst_err=0 ring=1709490 adc1_rc=0 adc1_err=0 adc1_seq=1710005 adc1_gap=514 adc1_hw=0 tgap=366
[RX] 2026-11-04T19:37:36.565+08:00 [2026-11-04 19:37:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:36.575+08:00 [2026-11-04 19:37:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:36.586+08:00 [2026-11-04 19:37:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:36.586+08:00 [2026-11-04 19:37:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:36.596+08:00 [2026-11-04 19:37:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:36.606+08:00 [2026-11-04 19:37:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:37:36.606+08:00 [2026-11-04 19:37:35][ADC_TASK] adc: UA=11429 UB=0 UC=872 UN=-42 IA=11452 IB=947 IC=595 IN=751 T=882
[RX] 2026-11-04T19:37:36.617+08:00 [2026-11-04 19:37:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:37:36.627+08:00 [2026-11-04 19:37:35][ADC_TASK] meas: seq=8550 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:37:36.638+08:00 [2026-11-04 19:37:35][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:37:46.551+08:00 [2026-11-04 19:37:45][ADC_TASK] frames=1719659 idle=0 skip=172 seq=1720006 frst_err=0 ring=1719487 adc1_rc=0 adc1_err=0 adc1_seq=1720005 adc1_gap=517 adc1_hw=0 tgap=368
[RX] 2026-11-04T19:37:46.572+08:00 [2026-11-04 19:37:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:46.572+08:00 [2026-11-04 19:37:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:46.583+08:00 [2026-11-04 19:37:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:46.583+08:00 [2026-11-04 19:37:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:46.593+08:00 [2026-11-04 19:37:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:46.604+08:00 [2026-11-04 19:37:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:37:46.604+08:00 [2026-11-04 19:37:45][ADC_TASK] adc: UA=11427 UB=0 UC=869 UN=-47 IA=11451 IB=943 IC=588 IN=748 T=870
[RX] 2026-11-04T19:37:46.614+08:00 [2026-11-04 19:37:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:37:46.625+08:00 [2026-11-04 19:37:45][ADC_TASK] meas: seq=8600 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:37:46.635+08:00 [2026-11-04 19:37:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:37:56.559+08:00 [2026-11-04 19:37:55][ADC_TASK] frames=1729657 idle=0 skip=173 seq=1730006 frst_err=0 ring=1729484 adc1_rc=0 adc1_err=0 adc1_seq=1730005 adc1_gap=520 adc1_hw=0 tgap=370
[RX] 2026-11-04T19:37:56.569+08:00 [2026-11-04 19:37:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:56.569+08:00 [2026-11-04 19:37:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:56.580+08:00 [2026-11-04 19:37:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:56.590+08:00 [2026-11-04 19:37:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:56.601+08:00 [2026-11-04 19:37:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:37:56.601+08:00 [2026-11-04 19:37:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:37:56.611+08:00 [2026-11-04 19:37:55][ADC_TASK] adc: UA=11428 UB=0 UC=879 UN=-48 IA=11452 IB=950 IC=624 IN=762 T=870
[RX] 2026-11-04T19:37:56.611+08:00 [2026-11-04 19:37:55][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:37:56.622+08:00 [2026-11-04 19:37:55][ADC_TASK] meas: seq=8650 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:37:56.632+08:00 [2026-11-04 19:37:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:38:06.551+08:00 [2026-11-04 19:38:05][ADC_TASK] frames=1739655 idle=0 skip=174 seq=1740006 frst_err=0 ring=1739481 adc1_rc=0 adc1_err=0 adc1_seq=1740005 adc1_gap=523 adc1_hw=0 tgap=372
[RX] 2026-11-04T19:38:06.571+08:00 [2026-11-04 19:38:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:06.571+08:00 [2026-11-04 19:38:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:06.581+08:00 [2026-11-04 19:38:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:38:06.592+08:00 [2026-11-04 19:38:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:06.592+08:00 [2026-11-04 19:38:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:06.602+08:00 [2026-11-04 19:38:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:38:06.602+08:00 [2026-11-04 19:38:05][ADC_TASK] adc: UA=11428 UB=0 UC=864 UN=-3515 IA=11453 IB=943 IC=722 IN=786 T=868
[RX] 2026-11-04T19:38:06.613+08:00 [2026-11-04 19:38:05][ADC_TASK] cal: ua_mv=1743 un_mv=-536 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:38:06.623+08:00 [2026-11-04 19:38:05][ADC_TASK] meas: seq=8700 v=1 ua_mv=0 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:38:06.634+08:00 [2026-11-04 19:38:05][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:38:16.550+08:00 [2026-11-04 19:38:15][ADC_TASK] frames=1749653 idle=0 skip=175 seq=1750006 frst_err=0 ring=1749478 adc1_rc=0 adc1_err=0 adc1_seq=1750005 adc1_gap=526 adc1_hw=0 tgap=374
[RX] 2026-11-04T19:38:16.571+08:00 [2026-11-04 19:38:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:16.571+08:00 [2026-11-04 19:38:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:16.582+08:00 [2026-11-04 19:38:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=9 Im=-1 rms=9 (x0.1)
[RX] 2026-11-04T19:38:16.592+08:00 [2026-11-04 19:38:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:16.592+08:00 [2026-11-04 19:38:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:16.602+08:00 [2026-11-04 19:38:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:38:16.602+08:00 [2026-11-04 19:38:15][ADC_TASK] adc: UA=11428 UB=0 UC=852 UN=-4359 IA=11451 IB=946 IC=676 IN=797 T=889
[RX] 2026-11-04T19:38:16.612+08:00 [2026-11-04 19:38:15][ADC_TASK] cal: ua_mv=1743 un_mv=-665 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:38:16.623+08:00 [2026-11-04 19:38:15][ADC_TASK] meas: seq=8750 v=1 ua_mv=0 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:38:16.633+08:00 [2026-11-04 19:38:15][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:38:26.557+08:00 [2026-11-04 19:38:25][ADC_TASK] frames=1759651 idle=0 skip=176 seq=1760006 frst_err=0 ring=1759475 adc1_rc=0 adc1_err=0 adc1_seq=1760005 adc1_gap=529 adc1_hw=0 tgap=377
[RX] 2026-11-04T19:38:26.567+08:00 [2026-11-04 19:38:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:26.578+08:00 [2026-11-04 19:38:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:26.578+08:00 [2026-11-04 19:38:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:26.588+08:00 [2026-11-04 19:38:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:26.598+08:00 [2026-11-04 19:38:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:26.598+08:00 [2026-11-04 19:38:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:38:26.609+08:00 [2026-11-04 19:38:25][ADC_TASK] adc: UA=11427 UB=0 UC=841 UN=-49 IA=11450 IB=937 IC=692 IN=784 T=889
[RX] 2026-11-04T19:38:26.619+08:00 [2026-11-04 19:38:25][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:38:26.630+08:00 [2026-11-04 19:38:25][ADC_TASK] meas: seq=8800 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:38:26.630+08:00 [2026-11-04 19:38:25][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:38:36.548+08:00 [2026-11-04 19:38:35][ADC_TASK] frames=1769649 idle=0 skip=177 seq=1770006 frst_err=0 ring=1769472 adc1_rc=0 adc1_err=0 adc1_seq=1770005 adc1_gap=532 adc1_hw=0 tgap=379
[RX] 2026-11-04T19:38:36.569+08:00 [2026-11-04 19:38:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:36.569+08:00 [2026-11-04 19:38:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:36.580+08:00 [2026-11-04 19:38:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:36.590+08:00 [2026-11-04 19:38:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:36.600+08:00 [2026-11-04 19:38:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:36.600+08:00 [2026-11-04 19:38:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:38:36.611+08:00 [2026-11-04 19:38:35][ADC_TASK] adc: UA=11428 UB=0 UC=864 UN=-51 IA=11452 IB=948 IC=742 IN=829 T=894
[RX] 2026-11-04T19:38:36.611+08:00 [2026-11-04 19:38:35][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:38:36.621+08:00 [2026-11-04 19:38:35][ADC_TASK] meas: seq=8850 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:38:36.631+08:00 [2026-11-04 19:38:35][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:38:46.551+08:00 [2026-11-04 19:38:45][ADC_TASK] frames=1779647 idle=0 skip=178 seq=1780006 frst_err=0 ring=1779469 adc1_rc=0 adc1_err=0 adc1_seq=1780005 adc1_gap=535 adc1_hw=0 tgap=381
[RX] 2026-11-04T19:38:46.571+08:00 [2026-11-04 19:38:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:46.571+08:00 [2026-11-04 19:38:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:46.582+08:00 [2026-11-04 19:38:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:46.582+08:00 [2026-11-04 19:38:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:46.592+08:00 [2026-11-04 19:38:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:46.603+08:00 [2026-11-04 19:38:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:38:46.603+08:00 [2026-11-04 19:38:45][ADC_TASK] adc: UA=11428 UB=0 UC=896 UN=-51 IA=11451 IB=940 IC=737 IN=846 T=893
[RX] 2026-11-04T19:38:46.613+08:00 [2026-11-04 19:38:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:38:46.623+08:00 [2026-11-04 19:38:45][ADC_TASK] meas: seq=8900 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:38:46.634+08:00 [2026-11-04 19:38:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:38:56.549+08:00 [2026-11-04 19:38:55][ADC_TASK] frames=1789645 idle=0 skip=179 seq=1790006 frst_err=0 ring=1789466 adc1_rc=0 adc1_err=0 adc1_seq=1790005 adc1_gap=538 adc1_hw=0 tgap=383
[RX] 2026-11-04T19:38:56.570+08:00 [2026-11-04 19:38:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:56.570+08:00 [2026-11-04 19:38:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:56.581+08:00 [2026-11-04 19:38:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:56.591+08:00 [2026-11-04 19:38:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:56.591+08:00 [2026-11-04 19:38:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:38:56.601+08:00 [2026-11-04 19:38:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:38:56.601+08:00 [2026-11-04 19:38:55][ADC_TASK] adc: UA=11428 UB=0 UC=909 UN=-49 IA=11451 IB=941 IC=734 IN=849 T=891
[RX] 2026-11-04T19:38:56.612+08:00 [2026-11-04 19:38:55][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:38:56.623+08:00 [2026-11-04 19:38:55][ADC_TASK] meas: seq=8950 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:38:56.633+08:00 [2026-11-04 19:38:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:39:06.553+08:00 [2026-11-04 19:39:05][ADC_TASK] frames=1799643 idle=0 skip=180 seq=1800006 frst_err=0 ring=1799463 adc1_rc=0 adc1_err=0 adc1_seq=1800005 adc1_gap=541 adc1_hw=0 tgap=385
[RX] 2026-11-04T19:39:06.563+08:00 [2026-11-04 19:39:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:06.573+08:00 [2026-11-04 19:39:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:06.584+08:00 [2026-11-04 19:39:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:06.584+08:00 [2026-11-04 19:39:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:06.594+08:00 [2026-11-04 19:39:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:06.605+08:00 [2026-11-04 19:39:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:39:06.605+08:00 [2026-11-04 19:39:05][ADC_TASK] adc: UA=11429 UB=0 UC=920 UN=-49 IA=11452 IB=950 IC=734 IN=853 T=890
[RX] 2026-11-04T19:39:06.615+08:00 [2026-11-04 19:39:05][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:39:06.626+08:00 [2026-11-04 19:39:05][ADC_TASK] meas: seq=9000 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:39:06.626+08:00 [2026-11-04 19:39:05][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:39:16.549+08:00 [2026-11-04 19:39:15][ADC_TASK] frames=1809641 idle=0 skip=181 seq=1810006 frst_err=0 ring=1809460 adc1_rc=0 adc1_err=0 adc1_seq=1810005 adc1_gap=544 adc1_hw=0 tgap=387
[RX] 2026-11-04T19:39:16.570+08:00 [2026-11-04 19:39:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:16.570+08:00 [2026-11-04 19:39:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:16.581+08:00 [2026-11-04 19:39:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:16.581+08:00 [2026-11-04 19:39:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:16.591+08:00 [2026-11-04 19:39:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:16.602+08:00 [2026-11-04 19:39:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:39:16.602+08:00 [2026-11-04 19:39:15][ADC_TASK] adc: UA=11429 UB=0 UC=911 UN=-51 IA=11453 IB=954 IC=730 IN=844 T=883
[RX] 2026-11-04T19:39:16.612+08:00 [2026-11-04 19:39:15][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:39:16.623+08:00 [2026-11-04 19:39:15][ADC_TASK] meas: seq=9050 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:39:16.633+08:00 [2026-11-04 19:39:15][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:39:26.557+08:00 [2026-11-04 19:39:25][ADC_TASK] frames=1819639 idle=0 skip=182 seq=1820006 frst_err=0 ring=1819457 adc1_rc=0 adc1_err=0 adc1_seq=1820005 adc1_gap=547 adc1_hw=0 tgap=389
[RX] 2026-11-04T19:39:26.567+08:00 [2026-11-04 19:39:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:26.567+08:00 [2026-11-04 19:39:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:26.577+08:00 [2026-11-04 19:39:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:26.588+08:00 [2026-11-04 19:39:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:26.598+08:00 [2026-11-04 19:39:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:26.598+08:00 [2026-11-04 19:39:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:39:26.609+08:00 [2026-11-04 19:39:25][ADC_TASK] adc: UA=11429 UB=0 UC=901 UN=-51 IA=11452 IB=945 IC=726 IN=837 T=872
[RX] 2026-11-04T19:39:26.609+08:00 [2026-11-04 19:39:25][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:39:26.620+08:00 [2026-11-04 19:39:25][ADC_TASK] meas: seq=9100 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:39:26.631+08:00 [2026-11-04 19:39:25][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:39:36.552+08:00 [2026-11-04 19:39:35][ADC_TASK] frames=1829637 idle=0 skip=183 seq=1830006 frst_err=0 ring=1829454 adc1_rc=0 adc1_err=0 adc1_seq=1830005 adc1_gap=550 adc1_hw=0 tgap=391
[RX] 2026-11-04T19:39:36.562+08:00 [2026-11-04 19:39:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:36.573+08:00 [2026-11-04 19:39:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:36.583+08:00 [2026-11-04 19:39:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:36.583+08:00 [2026-11-04 19:39:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:36.593+08:00 [2026-11-04 19:39:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:36.604+08:00 [2026-11-04 19:39:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:39:36.604+08:00 [2026-11-04 19:39:35][ADC_TASK] adc: UA=11429 UB=0 UC=907 UN=-49 IA=11453 IB=951 IC=734 IN=846 T=876
[RX] 2026-11-04T19:39:36.614+08:00 [2026-11-04 19:39:35][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:39:36.624+08:00 [2026-11-04 19:39:35][ADC_TASK] meas: seq=9150 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:39:36.635+08:00 [2026-11-04 19:39:35][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:39:46.549+08:00 [2026-11-04 19:39:45][ADC_TASK] frames=1839635 idle=0 skip=184 seq=1840006 frst_err=0 ring=1839451 adc1_rc=0 adc1_err=0 adc1_seq=1840005 adc1_gap=553 adc1_hw=0 tgap=393
[RX] 2026-11-04T19:39:46.570+08:00 [2026-11-04 19:39:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:46.570+08:00 [2026-11-04 19:39:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:46.581+08:00 [2026-11-04 19:39:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:46.581+08:00 [2026-11-04 19:39:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:46.591+08:00 [2026-11-04 19:39:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:46.602+08:00 [2026-11-04 19:39:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:39:46.602+08:00 [2026-11-04 19:39:45][ADC_TASK] adc: UA=11429 UB=0 UC=902 UN=-49 IA=11452 IB=940 IC=728 IN=839 T=868
[RX] 2026-11-04T19:39:46.612+08:00 [2026-11-04 19:39:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:39:46.622+08:00 [2026-11-04 19:39:45][ADC_TASK] meas: seq=9200 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:39:46.633+08:00 [2026-11-04 19:39:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:39:56.547+08:00 [2026-11-04 19:39:55][ADC_TASK] frames=1849633 idle=0 skip=185 seq=1850006 frst_err=0 ring=1849448 adc1_rc=0 adc1_err=0 adc1_seq=1850005 adc1_gap=556 adc1_hw=0 tgap=395
[RX] 2026-11-04T19:39:56.568+08:00 [2026-11-04 19:39:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:56.568+08:00 [2026-11-04 19:39:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:56.579+08:00 [2026-11-04 19:39:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:56.589+08:00 [2026-11-04 19:39:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:56.589+08:00 [2026-11-04 19:39:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:39:56.599+08:00 [2026-11-04 19:39:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:39:56.599+08:00 [2026-11-04 19:39:55][ADC_TASK] adc: UA=11429 UB=0 UC=905 UN=-52 IA=11451 IB=941 IC=730 IN=840 T=868
[RX] 2026-11-04T19:39:56.609+08:00 [2026-11-04 19:39:55][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:39:56.620+08:00 [2026-11-04 19:39:55][ADC_TASK] meas: seq=9250 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:39:56.631+08:00 [2026-11-04 19:39:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:40:06.549+08:00 [2026-11-04 19:40:05][ADC_TASK] frames=1859631 idle=0 skip=186 seq=1860006 frst_err=0 ring=1859445 adc1_rc=0 adc1_err=0 adc1_seq=1860005 adc1_gap=559 adc1_hw=0 tgap=397
[RX] 2026-11-04T19:40:06.560+08:00 [2026-11-04 19:40:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:06.571+08:00 [2026-11-04 19:40:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:06.581+08:00 [2026-11-04 19:40:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:06.581+08:00 [2026-11-04 19:40:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:06.591+08:00 [2026-11-04 19:40:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:06.602+08:00 [2026-11-04 19:40:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:40:06.602+08:00 [2026-11-04 19:40:05][ADC_TASK] adc: UA=11429 UB=0 UC=922 UN=-51 IA=11451 IB=959 IC=742 IN=856 T=888
[RX] 2026-11-04T19:40:06.612+08:00 [2026-11-04 19:40:05][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:40:06.623+08:00 [2026-11-04 19:40:05][ADC_TASK] meas: seq=9300 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:40:06.633+08:00 [2026-11-04 19:40:05][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:40:16.547+08:00 [2026-11-04 19:40:15][ADC_TASK] frames=1869629 idle=0 skip=187 seq=1870006 frst_err=0 ring=1869442 adc1_rc=0 adc1_err=0 adc1_seq=1870005 adc1_gap=562 adc1_hw=0 tgap=399
[RX] 2026-11-04T19:40:16.568+08:00 [2026-11-04 19:40:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:16.568+08:00 [2026-11-04 19:40:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:16.578+08:00 [2026-11-04 19:40:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:16.589+08:00 [2026-11-04 19:40:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:16.589+08:00 [2026-11-04 19:40:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:16.599+08:00 [2026-11-04 19:40:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:40:16.599+08:00 [2026-11-04 19:40:15][ADC_TASK] adc: UA=11428 UB=0 UC=909 UN=-50 IA=11451 IB=947 IC=730 IN=844 T=874
[RX] 2026-11-04T19:40:16.610+08:00 [2026-11-04 19:40:15][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:40:16.620+08:00 [2026-11-04 19:40:15][ADC_TASK] meas: seq=9350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:40:16.630+08:00 [2026-11-04 19:40:15][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:40:26.551+08:00 [2026-11-04 19:40:25][ADC_TASK] frames=1879627 idle=0 skip=188 seq=1880006 frst_err=0 ring=1879439 adc1_rc=0 adc1_err=0 adc1_seq=1880005 adc1_gap=565 adc1_hw=0 tgap=401
[RX] 2026-11-04T19:40:26.561+08:00 [2026-11-04 19:40:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:26.572+08:00 [2026-11-04 19:40:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:26.582+08:00 [2026-11-04 19:40:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:26.582+08:00 [2026-11-04 19:40:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:26.593+08:00 [2026-11-04 19:40:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:26.603+08:00 [2026-11-04 19:40:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:40:26.603+08:00 [2026-11-04 19:40:25][ADC_TASK] adc: UA=11429 UB=0 UC=916 UN=-48 IA=11453 IB=954 IC=735 IN=848 T=880
[RX] 2026-11-04T19:40:26.614+08:00 [2026-11-04 19:40:25][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:40:26.624+08:00 [2026-11-04 19:40:25][ADC_TASK] meas: seq=9400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:40:26.624+08:00 [2026-11-04 19:40:25][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:40:36.547+08:00 [2026-11-04 19:40:35][ADC_TASK] frames=1889625 idle=0 skip=189 seq=1890006 frst_err=0 ring=1889436 adc1_rc=0 adc1_err=0 adc1_seq=1890005 adc1_gap=568 adc1_hw=0 tgap=403
[RX] 2026-11-04T19:40:36.568+08:00 [2026-11-04 19:40:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:36.568+08:00 [2026-11-04 19:40:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:36.578+08:00 [2026-11-04 19:40:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:36.589+08:00 [2026-11-04 19:40:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:36.589+08:00 [2026-11-04 19:40:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:36.599+08:00 [2026-11-04 19:40:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:40:36.599+08:00 [2026-11-04 19:40:35][ADC_TASK] adc: UA=11428 UB=0 UC=917 UN=-50 IA=11452 IB=955 IC=733 IN=848 T=883
[RX] 2026-11-04T19:40:36.610+08:00 [2026-11-04 19:40:35][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:40:36.620+08:00 [2026-11-04 19:40:35][ADC_TASK] meas: seq=9450 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:40:36.630+08:00 [2026-11-04 19:40:35][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:40:46.547+08:00 [2026-11-04 19:40:45][ADC_TASK] frames=1899623 idle=0 skip=190 seq=1900006 frst_err=0 ring=1899433 adc1_rc=0 adc1_err=0 adc1_seq=1900005 adc1_gap=571 adc1_hw=0 tgap=405
[RX] 2026-11-04T19:40:46.567+08:00 [2026-11-04 19:40:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:46.567+08:00 [2026-11-04 19:40:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:46.577+08:00 [2026-11-04 19:40:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:46.588+08:00 [2026-11-04 19:40:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:46.588+08:00 [2026-11-04 19:40:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:46.599+08:00 [2026-11-04 19:40:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:40:46.599+08:00 [2026-11-04 19:40:45][ADC_TASK] adc: UA=11429 UB=0 UC=911 UN=-48 IA=11453 IB=955 IC=730 IN=846 T=883
[RX] 2026-11-04T19:40:46.609+08:00 [2026-11-04 19:40:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:40:46.620+08:00 [2026-11-04 19:40:45][ADC_TASK] meas: seq=9500 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:40:46.631+08:00 [2026-11-04 19:40:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:40:56.546+08:00 [2026-11-04 19:40:55][ADC_TASK] frames=1909621 idle=0 skip=191 seq=1910006 frst_err=0 ring=1909430 adc1_rc=0 adc1_err=0 adc1_seq=1910005 adc1_gap=574 adc1_hw=0 tgap=407
[RX] 2026-11-04T19:40:56.566+08:00 [2026-11-04 19:40:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:56.566+08:00 [2026-11-04 19:40:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:56.577+08:00 [2026-11-04 19:40:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:56.587+08:00 [2026-11-04 19:40:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:56.587+08:00 [2026-11-04 19:40:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:40:56.597+08:00 [2026-11-04 19:40:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:40:56.608+08:00 [2026-11-04 19:40:55][ADC_TASK] adc: UA=11427 UB=0 UC=915 UN=-50 IA=11453 IB=961 IC=736 IN=849 T=886
[RX] 2026-11-04T19:40:56.608+08:00 [2026-11-04 19:40:55][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:40:56.618+08:00 [2026-11-04 19:40:55][ADC_TASK] meas: seq=9550 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:40:56.628+08:00 [2026-11-04 19:40:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:41:06.546+08:00 [2026-11-04 19:41:05][ADC_TASK] frames=1919619 idle=0 skip=192 seq=1920006 frst_err=0 ring=1919427 adc1_rc=0 adc1_err=0 adc1_seq=1920005 adc1_gap=577 adc1_hw=0 tgap=409
[RX] 2026-11-04T19:41:06.567+08:00 [2026-11-04 19:41:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:06.567+08:00 [2026-11-04 19:41:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:06.578+08:00 [2026-11-04 19:41:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:06.578+08:00 [2026-11-04 19:41:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:06.588+08:00 [2026-11-04 19:41:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:06.599+08:00 [2026-11-04 19:41:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:41:06.599+08:00 [2026-11-04 19:41:05][ADC_TASK] adc: UA=11429 UB=0 UC=915 UN=-48 IA=11451 IB=960 IC=734 IN=848 T=883
[RX] 2026-11-04T19:41:06.609+08:00 [2026-11-04 19:41:05][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:41:06.619+08:00 [2026-11-04 19:41:05][ADC_TASK] meas: seq=9600 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:41:06.630+08:00 [2026-11-04 19:41:05][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:41:16.550+08:00 [2026-11-04 19:41:15][ADC_TASK] frames=1929617 idle=0 skip=193 seq=1930006 frst_err=0 ring=1929424 adc1_rc=0 adc1_err=0 adc1_seq=1930005 adc1_gap=580 adc1_hw=0 tgap=411
[RX] 2026-11-04T19:41:16.560+08:00 [2026-11-04 19:41:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:16.570+08:00 [2026-11-04 19:41:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:16.581+08:00 [2026-11-04 19:41:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:16.581+08:00 [2026-11-04 19:41:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:16.591+08:00 [2026-11-04 19:41:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:16.602+08:00 [2026-11-04 19:41:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:41:16.602+08:00 [2026-11-04 19:41:15][ADC_TASK] adc: UA=11428 UB=0 UC=904 UN=-49 IA=11451 IB=951 IC=727 IN=839 T=875
[RX] 2026-11-04T19:41:16.612+08:00 [2026-11-04 19:41:15][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:41:16.622+08:00 [2026-11-04 19:41:15][ADC_TASK] meas: seq=9650 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:41:16.632+08:00 [2026-11-04 19:41:15][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:41:26.548+08:00 [2026-11-04 19:41:25][ADC_TASK] frames=1939615 idle=0 skip=194 seq=1940006 frst_err=0 ring=1939421 adc1_rc=0 adc1_err=0 adc1_seq=1940005 adc1_gap=583 adc1_hw=0 tgap=413
[RX] 2026-11-04T19:41:26.558+08:00 [2026-11-04 19:41:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:26.568+08:00 [2026-11-04 19:41:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:26.579+08:00 [2026-11-04 19:41:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:26.579+08:00 [2026-11-04 19:41:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:26.589+08:00 [2026-11-04 19:41:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:26.600+08:00 [2026-11-04 19:41:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:41:26.600+08:00 [2026-11-04 19:41:25][ADC_TASK] adc: UA=11428 UB=0 UC=923 UN=-50 IA=11452 IB=960 IC=755 IN=864 T=885
[RX] 2026-11-04T19:41:26.610+08:00 [2026-11-04 19:41:25][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:41:26.621+08:00 [2026-11-04 19:41:25][ADC_TASK] meas: seq=9700 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:41:26.631+08:00 [2026-11-04 19:41:25][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:41:36.544+08:00 [2026-11-04 19:41:35][ADC_TASK] frames=1949613 idle=0 skip=195 seq=1950006 frst_err=0 ring=1949418 adc1_rc=0 adc1_err=0 adc1_seq=1950005 adc1_gap=586 adc1_hw=0 tgap=415
[RX] 2026-11-04T19:41:36.564+08:00 [2026-11-04 19:41:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:36.564+08:00 [2026-11-04 19:41:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:36.575+08:00 [2026-11-04 19:41:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:36.586+08:00 [2026-11-04 19:41:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:36.586+08:00 [2026-11-04 19:41:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:36.596+08:00 [2026-11-04 19:41:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:41:36.607+08:00 [2026-11-04 19:41:35][ADC_TASK] adc: UA=11429 UB=0 UC=928 UN=-49 IA=11452 IB=960 IC=738 IN=858 T=889
[RX] 2026-11-04T19:41:36.607+08:00 [2026-11-04 19:41:35][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:41:36.617+08:00 [2026-11-04 19:41:35][ADC_TASK] meas: seq=9750 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:41:36.627+08:00 [2026-11-04 19:41:35][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:41:46.545+08:00 [2026-11-04 19:41:45][ADC_TASK] frames=1959611 idle=0 skip=196 seq=1960006 frst_err=0 ring=1959415 adc1_rc=0 adc1_err=0 adc1_seq=1960005 adc1_gap=589 adc1_hw=0 tgap=417
[RX] 2026-11-04T19:41:46.565+08:00 [2026-11-04 19:41:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:46.565+08:00 [2026-11-04 19:41:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:46.576+08:00 [2026-11-04 19:41:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:46.587+08:00 [2026-11-04 19:41:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:46.587+08:00 [2026-11-04 19:41:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:46.597+08:00 [2026-11-04 19:41:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:41:46.597+08:00 [2026-11-04 19:41:45][ADC_TASK] adc: UA=11428 UB=0 UC=928 UN=-50 IA=11452 IB=957 IC=736 IN=854 T=898
[RX] 2026-11-04T19:41:46.607+08:00 [2026-11-04 19:41:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:41:46.618+08:00 [2026-11-04 19:41:45][ADC_TASK] meas: seq=9800 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:41:46.628+08:00 [2026-11-04 19:41:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:41:56.553+08:00 [2026-11-04 19:41:55][ADC_TASK] frames=1969609 idle=0 skip=197 seq=1970006 frst_err=0 ring=1969412 adc1_rc=0 adc1_err=0 adc1_seq=1970005 adc1_gap=592 adc1_hw=0 tgap=419
[RX] 2026-11-04T19:41:56.563+08:00 [2026-11-04 19:41:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:56.563+08:00 [2026-11-04 19:41:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:56.574+08:00 [2026-11-04 19:41:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:56.584+08:00 [2026-11-04 19:41:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:56.595+08:00 [2026-11-04 19:41:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:41:56.595+08:00 [2026-11-04 19:41:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:41:56.605+08:00 [2026-11-04 19:41:55][ADC_TASK] adc: UA=11428 UB=0 UC=916 UN=-50 IA=11453 IB=951 IC=734 IN=847 T=893
[RX] 2026-11-04T19:41:56.605+08:00 [2026-11-04 19:41:55][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:41:56.626+08:00 [2026-11-04 19:41:55][ADC_TASK] meas: seq=9850 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:41:56.626+08:00 [2026-11-04 19:41:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:42:06.543+08:00 [2026-11-04 19:42:05][ADC_TASK] frames=1979607 idle=0 skip=198 seq=1980006 frst_err=0 ring=1979409 adc1_rc=0 adc1_err=0 adc1_seq=1980005 adc1_gap=595 adc1_hw=0 tgap=421
[RX] 2026-11-04T19:42:06.564+08:00 [2026-11-04 19:42:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:06.564+08:00 [2026-11-04 19:42:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:06.574+08:00 [2026-11-04 19:42:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:06.585+08:00 [2026-11-04 19:42:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:06.585+08:00 [2026-11-04 19:42:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:06.595+08:00 [2026-11-04 19:42:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:42:06.606+08:00 [2026-11-04 19:42:05][ADC_TASK] adc: UA=11428 UB=0 UC=905 UN=-48 IA=11451 IB=949 IC=726 IN=839 T=881
[RX] 2026-11-04T19:42:06.606+08:00 [2026-11-04 19:42:05][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:42:06.617+08:00 [2026-11-04 19:42:05][ADC_TASK] meas: seq=9900 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:42:06.627+08:00 [2026-11-04 19:42:05][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:42:16.553+08:00 [2026-11-04 19:42:15][ADC_TASK] frames=1989605 idle=0 skip=199 seq=1990006 frst_err=0 ring=1989406 adc1_rc=0 adc1_err=0 adc1_seq=1990005 adc1_gap=598 adc1_hw=0 tgap=423
[RX] 2026-11-04T19:42:16.563+08:00 [2026-11-04 19:42:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:16.563+08:00 [2026-11-04 19:42:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:16.574+08:00 [2026-11-04 19:42:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:16.584+08:00 [2026-11-04 19:42:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:16.594+08:00 [2026-11-04 19:42:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:16.594+08:00 [2026-11-04 19:42:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:42:16.605+08:00 [2026-11-04 19:42:15][ADC_TASK] adc: UA=11429 UB=0 UC=899 UN=-50 IA=11452 IB=943 IC=726 IN=834 T=871
[RX] 2026-11-04T19:42:16.605+08:00 [2026-11-04 19:42:15][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:42:16.615+08:00 [2026-11-04 19:42:15][ADC_TASK] meas: seq=9950 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:42:16.625+08:00 [2026-11-04 19:42:15][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:42:26.547+08:00 [2026-11-04 19:42:25][ADC_TASK] frames=1999603 idle=0 skip=200 seq=2000006 frst_err=0 ring=1999403 adc1_rc=0 adc1_err=0 adc1_seq=2000005 adc1_gap=601 adc1_hw=0 tgap=425
[RX] 2026-11-04T19:42:26.557+08:00 [2026-11-04 19:42:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:26.568+08:00 [2026-11-04 19:42:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:26.578+08:00 [2026-11-04 19:42:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:26.578+08:00 [2026-11-04 19:42:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:26.589+08:00 [2026-11-04 19:42:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:26.599+08:00 [2026-11-04 19:42:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:42:26.599+08:00 [2026-11-04 19:42:25][ADC_TASK] adc: UA=11428 UB=0 UC=913 UN=-49 IA=11451 IB=954 IC=734 IN=847 T=878
[RX] 2026-11-04T19:42:26.610+08:00 [2026-11-04 19:42:25][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:42:26.621+08:00 [2026-11-04 19:42:25][ADC_TASK] meas: seq=10000 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:42:26.621+08:00 [2026-11-04 19:42:25][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:42:36.550+08:00 [2026-11-04 19:42:35][ADC_TASK] frames=2009601 idle=0 skip=201 seq=2010006 frst_err=0 ring=2009400 adc1_rc=0 adc1_err=0 adc1_seq=2010005 adc1_gap=604 adc1_hw=0 tgap=427
[RX] 2026-11-04T19:42:36.560+08:00 [2026-11-04 19:42:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:36.571+08:00 [2026-11-04 19:42:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:36.571+08:00 [2026-11-04 19:42:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:36.581+08:00 [2026-11-04 19:42:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:36.592+08:00 [2026-11-04 19:42:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:36.592+08:00 [2026-11-04 19:42:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:42:36.602+08:00 [2026-11-04 19:42:35][ADC_TASK] adc: UA=11429 UB=0 UC=911 UN=-51 IA=11450 IB=949 IC=732 IN=846 T=875
[RX] 2026-11-04T19:42:36.613+08:00 [2026-11-04 19:42:35][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:42:36.624+08:00 [2026-11-04 19:42:35][ADC_TASK] meas: seq=10050 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:42:36.624+08:00 [2026-11-04 19:42:35][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:42:46.546+08:00 [2026-11-04 19:42:45][ADC_TASK] frames=2019599 idle=0 skip=202 seq=2020006 frst_err=0 ring=2019397 adc1_rc=0 adc1_err=0 adc1_seq=2020005 adc1_gap=607 adc1_hw=0 tgap=429
[RX] 2026-11-04T19:42:46.556+08:00 [2026-11-04 19:42:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:46.567+08:00 [2026-11-04 19:42:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:46.577+08:00 [2026-11-04 19:42:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:46.577+08:00 [2026-11-04 19:42:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:46.587+08:00 [2026-11-04 19:42:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:46.598+08:00 [2026-11-04 19:42:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:42:46.598+08:00 [2026-11-04 19:42:45][ADC_TASK] adc: UA=11428 UB=0 UC=903 UN=-49 IA=11452 IB=939 IC=727 IN=839 T=868
[RX] 2026-11-04T19:42:46.608+08:00 [2026-11-04 19:42:45][ADC_TASK] cal: ua_mv=1743 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:42:46.619+08:00 [2026-11-04 19:42:45][ADC_TASK] meas: seq=10100 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:42:46.629+08:00 [2026-11-04 19:42:45][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
[RX] 2026-11-04T19:42:56.549+08:00 [2026-11-04 19:42:55][ADC_TASK] frames=2029597 idle=0 skip=203 seq=2030006 frst_err=0 ring=2029394 adc1_rc=0 adc1_err=0 adc1_seq=2030005 adc1_gap=610 adc1_hw=0 tgap=431
[RX] 2026-11-04T19:42:56.559+08:00 [2026-11-04 19:42:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:56.570+08:00 [2026-11-04 19:42:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:56.570+08:00 [2026-11-04 19:42:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:56.581+08:00 [2026-11-04 19:42:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:56.591+08:00 [2026-11-04 19:42:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:42:56.591+08:00 [2026-11-04 19:42:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:42:56.602+08:00 [2026-11-04 19:42:55][ADC_TASK] adc: UA=11430 UB=0 UC=908 UN=-49 IA=11452 IB=944 IC=732 IN=844 T=873
[RX] 2026-11-04T19:42:56.612+08:00 [2026-11-04 19:42:55][ADC_TASK] cal: ua_mv=1744 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:42:56.623+08:00 [2026-11-04 19:42:55][ADC_TASK] meas: seq=10150 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:42:56.623+08:00 [2026-11-04 19:42:55][ADC_TASK] wave: seg=3 armed=0 src=M pts=320 err=0 ovf=0 drop=14264
@@ -0,0 +1,469 @@
# 2026-11-04 交流补测轮串口日志摘录(源:serial-assistant serial.log,gen=328,daemon 打戳 +08:00)
# 板固件: fw build: Nov 4 2026 19:05:57 suite=NONE dstr=off(HEAD 87fa0276 构建,烧录回读一致)
# 完整原始日志本机备份: ci-artifacts/ac-20261104/serial_full_backup.log(本机产物,daemon 20MB 截头保险)
# 取证注意: 19:56:47 三条 [ERR][LFT_IO]/[FTP] 是 PC 侧 FTP 探测不存在文件 V5_UP.BIN 所致(操作自归因),非固件缺陷
# 通道静态位观察: 未接线的 TPAFE 输入浮空读数 ≈1743/1747 mV(撤线后 UA/IA 出现,非损伤、非零点漂移)
# # 谐波轮 IA 复测窗(IA 满幅 50Hz → Vpp0.453/50Hz 屏显 0.160 → 250Hz 屏显 0.153 心跳带外不可见)
[RX] 2026-11-04T19:25:06.576+08:00 [2026-11-04 19:25:05][ADC_TASK] frames=959811 idle=0 skip=96 seq=960006 frst_err=0 ring=959715 adc1_rc=0 adc1_err=0 adc1_seq=960005 adc1_gap=289 adc1_hw=0 tgap=216
[RX] 2026-11-04T19:25:06.587+08:00 [2026-11-04 19:25:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:06.597+08:00 [2026-11-04 19:25:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:06.597+08:00 [2026-11-04 19:25:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:06.608+08:00 [2026-11-04 19:25:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:06.618+08:00 [2026-11-04 19:25:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:06.618+08:00 [2026-11-04 19:25:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:25:06.629+08:00 [2026-11-04 19:25:05][ADC_TASK] adc: UA=-721 UB=976 UC=1011 UN=-94 IA=11453 IB=961 IC=1030 IN=1029 T=951
[RX] 2026-11-04T19:25:06.639+08:00 [2026-11-04 19:25:05][ADC_TASK] cal: ua_mv=-110 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:25:06.649+08:00 [2026-11-04 19:25:05][ADC_TASK] meas: seq=4800 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:25:06.649+08:00 [2026-11-04 19:25:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:25:16.577+08:00 [2026-11-04 19:25:15][ADC_TASK] frames=969809 idle=0 skip=97 seq=970006 frst_err=0 ring=969712 adc1_rc=0 adc1_err=0 adc1_seq=970005 adc1_gap=292 adc1_hw=0 tgap=218
[RX] 2026-11-04T19:25:16.587+08:00 [2026-11-04 19:25:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:16.598+08:00 [2026-11-04 19:25:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:16.598+08:00 [2026-11-04 19:25:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:16.609+08:00 [2026-11-04 19:25:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:16.609+08:00 [2026-11-04 19:25:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:16.619+08:00 [2026-11-04 19:25:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:25:16.629+08:00 [2026-11-04 19:25:15][ADC_TASK] adc: UA=-684 UB=975 UC=1028 UN=-94 IA=11452 IB=954 IC=1030 IN=1003 T=942
[RX] 2026-11-04T19:25:16.640+08:00 [2026-11-04 19:25:15][ADC_TASK] cal: ua_mv=-104 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:25:16.650+08:00 [2026-11-04 19:25:15][ADC_TASK] meas: seq=4850 v=1 ua_mv=80 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:25:16.650+08:00 [2026-11-04 19:25:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:25:26.578+08:00 [2026-11-04 19:25:25][ADC_TASK] frames=979807 idle=0 skip=98 seq=980006 frst_err=0 ring=979709 adc1_rc=0 adc1_err=0 adc1_seq=980005 adc1_gap=295 adc1_hw=0 tgap=220
[RX] 2026-11-04T19:25:26.592+08:00 [2026-11-04 19:25:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:26.592+08:00 [2026-11-04 19:25:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:26.603+08:00 [2026-11-04 19:25:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:26.603+08:00 [2026-11-04 19:25:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:26.613+08:00 [2026-11-04 19:25:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:26.624+08:00 [2026-11-04 19:25:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:25:26.624+08:00 [2026-11-04 19:25:25][ADC_TASK] adc: UA=11426 UB=950 UC=1006 UN=-94 IA=-646 IB=920 IC=1010 IN=989 T=909
[RX] 2026-11-04T19:25:26.634+08:00 [2026-11-04 19:25:25][ADC_TASK] cal: ua_mv=1743 un_mv=-14 ia_mv=-98 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:25:26.644+08:00 [2026-11-04 19:25:25][ADC_TASK] meas: seq=4900 v=1 ua_mv=0 un_mv=0 ia_mv=79 f=0 fv=0
[RX] 2026-11-04T19:25:26.655+08:00 [2026-11-04 19:25:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:25:36.577+08:00 [2026-11-04 19:25:35][ADC_TASK] frames=989805 idle=0 skip=99 seq=990006 frst_err=0 ring=989706 adc1_rc=0 adc1_err=0 adc1_seq=990005 adc1_gap=298 adc1_hw=0 tgap=222
[RX] 2026-11-04T19:25:36.588+08:00 [2026-11-04 19:25:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:36.588+08:00 [2026-11-04 19:25:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:36.598+08:00 [2026-11-04 19:25:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:36.609+08:00 [2026-11-04 19:25:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:36.609+08:00 [2026-11-04 19:25:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:36.619+08:00 [2026-11-04 19:25:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:25:36.630+08:00 [2026-11-04 19:25:35][ADC_TASK] adc: UA=11427 UB=944 UC=1019 UN=-95 IA=-609 IB=923 IC=1007 IN=1024 T=923
[RX] 2026-11-04T19:25:36.630+08:00 [2026-11-04 19:25:35][ADC_TASK] cal: ua_mv=1743 un_mv=-14 ia_mv=-92 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:25:36.640+08:00 [2026-11-04 19:25:35][ADC_TASK] meas: seq=4950 v=1 ua_mv=0 un_mv=0 ia_mv=79 f=0 fv=0
[RX] 2026-11-04T19:25:36.651+08:00 [2026-11-04 19:25:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:25:46.571+08:00 [2026-11-04 19:25:45][ADC_TASK] frames=999803 idle=0 skip=100 seq=1000006 frst_err=0 ring=999703 adc1_rc=0 adc1_err=0 adc1_seq=1000005 adc1_gap=301 adc1_hw=0 tgap=224
[RX] 2026-11-04T19:25:46.592+08:00 [2026-11-04 19:25:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:46.592+08:00 [2026-11-04 19:25:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:46.602+08:00 [2026-11-04 19:25:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:46.602+08:00 [2026-11-04 19:25:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:46.613+08:00 [2026-11-04 19:25:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:46.623+08:00 [2026-11-04 19:25:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:25:46.623+08:00 [2026-11-04 19:25:45][ADC_TASK] adc: UA=11429 UB=952 UC=1003 UN=-95 IA=-573 IB=942 IC=1011 IN=1041 T=944
[RX] 2026-11-04T19:25:46.633+08:00 [2026-11-04 19:25:45][ADC_TASK] cal: ua_mv=1743 un_mv=-14 ia_mv=-87 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:25:46.644+08:00 [2026-11-04 19:25:45][ADC_TASK] meas: seq=5000 v=1 ua_mv=0 un_mv=0 ia_mv=79 f=0 fv=0
[RX] 2026-11-04T19:25:46.655+08:00 [2026-11-04 19:25:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:25:56.576+08:00 [2026-11-04 19:25:55][ADC_TASK] frames=1009801 idle=0 skip=101 seq=1010006 frst_err=0 ring=1009700 adc1_rc=0 adc1_err=0 adc1_seq=1010005 adc1_gap=304 adc1_hw=0 tgap=226
[RX] 2026-11-04T19:25:56.587+08:00 [2026-11-04 19:25:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=-6 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:25:56.597+08:00 [2026-11-04 19:25:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:56.597+08:00 [2026-11-04 19:25:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:56.608+08:00 [2026-11-04 19:25:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:56.618+08:00 [2026-11-04 19:25:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:25:56.618+08:00 [2026-11-04 19:25:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:25:56.629+08:00 [2026-11-04 19:25:55][ADC_TASK] adc: UA=11428 UB=972 UC=1016 UN=-67 IA=-4402 IB=970 IC=1026 IN=1051 T=967
[RX] 2026-11-04T19:25:56.639+08:00 [2026-11-04 19:25:55][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-671 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:25:56.649+08:00 [2026-11-04 19:25:55][ADC_TASK] meas: seq=5050 v=1 ua_mv=0 un_mv=0 ia_mv=996 f=0 fv=0
[RX] 2026-11-04T19:25:56.649+08:00 [2026-11-04 19:25:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:26:06.572+08:00 [2026-11-04 19:26:05][ADC_TASK] frames=1019799 idle=0 skip=102 seq=1020006 frst_err=0 ring=1019697 adc1_rc=0 adc1_err=0 adc1_seq=1020005 adc1_gap=307 adc1_hw=0 tgap=228
[RX] 2026-11-04T19:26:06.582+08:00 [2026-11-04 19:26:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:06.593+08:00 [2026-11-04 19:26:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:06.604+08:00 [2026-11-04 19:26:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:06.604+08:00 [2026-11-04 19:26:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:06.614+08:00 [2026-11-04 19:26:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=-9 Im=1 rms=9 (x0.1)
[RX] 2026-11-04T19:26:06.624+08:00 [2026-11-04 19:26:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:26:06.624+08:00 [2026-11-04 19:26:05][ADC_TASK] adc: UA=11427 UB=993 UC=1015 UN=-73 IA=8606 IB=981 IC=1037 IN=1018 T=968
[RX] 2026-11-04T19:26:06.635+08:00 [2026-11-04 19:26:05][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=1313 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:26:06.645+08:00 [2026-11-04 19:26:05][ADC_TASK] meas: seq=5100 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:26:06.645+08:00 [2026-11-04 19:26:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:26:16.568+08:00 [2026-11-04 19:26:15][ADC_TASK] frames=1029797 idle=0 skip=103 seq=1030006 frst_err=0 ring=1029694 adc1_rc=0 adc1_err=0 adc1_seq=1030005 adc1_gap=310 adc1_hw=0 tgap=230
[RX] 2026-11-04T19:26:16.589+08:00 [2026-11-04 19:26:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:16.589+08:00 [2026-11-04 19:26:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:16.599+08:00 [2026-11-04 19:26:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:16.610+08:00 [2026-11-04 19:26:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:16.610+08:00 [2026-11-04 19:26:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=-7 Im=5 rms=9 (x0.1)
[RX] 2026-11-04T19:26:16.620+08:00 [2026-11-04 19:26:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:26:16.630+08:00 [2026-11-04 19:26:15][ADC_TASK] adc: UA=11427 UB=1017 UC=1043 UN=-74 IA=6925 IB=985 IC=1066 IN=1016 T=958
[RX] 2026-11-04T19:26:16.630+08:00 [2026-11-04 19:26:15][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=1056 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:26:16.641+08:00 [2026-11-04 19:26:15][ADC_TASK] meas: seq=5150 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:26:16.651+08:00 [2026-11-04 19:26:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:26:26.576+08:00 [2026-11-04 19:26:25][ADC_TASK] frames=1039795 idle=0 skip=104 seq=1040006 frst_err=0 ring=1039691 adc1_rc=0 adc1_err=0 adc1_seq=1040005 adc1_gap=313 adc1_hw=0 tgap=232
[RX] 2026-11-04T19:26:26.586+08:00 [2026-11-04 19:26:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:26.597+08:00 [2026-11-04 19:26:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:26.597+08:00 [2026-11-04 19:26:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:26.607+08:00 [2026-11-04 19:26:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:26.617+08:00 [2026-11-04 19:26:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=-3 Im=8 rms=9 (x0.1)
[RX] 2026-11-04T19:26:26.617+08:00 [2026-11-04 19:26:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:26:26.628+08:00 [2026-11-04 19:26:25][ADC_TASK] adc: UA=11428 UB=1024 UC=1016 UN=-70 IA=3574 IB=962 IC=1071 IN=1029 T=932
[RX] 2026-11-04T19:26:26.638+08:00 [2026-11-04 19:26:25][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=545 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:26:26.648+08:00 [2026-11-04 19:26:25][ADC_TASK] meas: seq=5200 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:26:26.648+08:00 [2026-11-04 19:26:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:26:36.575+08:00 [2026-11-04 19:26:35][ADC_TASK] frames=1049793 idle=0 skip=105 seq=1050006 frst_err=0 ring=1049688 adc1_rc=0 adc1_err=0 adc1_seq=1050005 adc1_gap=316 adc1_hw=0 tgap=234
[RX] 2026-11-04T19:26:36.585+08:00 [2026-11-04 19:26:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:36.596+08:00 [2026-11-04 19:26:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:36.596+08:00 [2026-11-04 19:26:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:36.607+08:00 [2026-11-04 19:26:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:36.617+08:00 [2026-11-04 19:26:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:26:36.617+08:00 [2026-11-04 19:26:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:26:36.628+08:00 [2026-11-04 19:26:35][ADC_TASK] adc: UA=11428 UB=1020 UC=1008 UN=-73 IA=-668 IB=959 IC=1072 IN=1039 T=948
[RX] 2026-11-04T19:26:36.638+08:00 [2026-11-04 19:26:35][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=-101 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:26:36.648+08:00 [2026-11-04 19:26:35][ADC_TASK] meas: seq=5250 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:26:36.648+08:00 [2026-11-04 19:26:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:26:46.567+08:00 [2026-11-04 19:26:45][ADC_TASK] frames=1059791 idle=0 skip=106 seq=1060006 frst_err=0 ring=1059685 adc1_rc=0 adc1_err=0 adc1_seq=1060005 adc1_gap=319 adc1_hw=0 tgap=236
[RX] 2026-11-04T19:26:46.588+08:00 [2026-11-04 19:26:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:46.588+08:00 [2026-11-04 19:26:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:46.598+08:00 [2026-11-04 19:26:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:46.608+08:00 [2026-11-04 19:26:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:46.608+08:00 [2026-11-04 19:26:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=5 Im=7 rms=9 (x0.1)
[RX] 2026-11-04T19:26:46.619+08:00 [2026-11-04 19:26:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:26:46.629+08:00 [2026-11-04 19:26:45][ADC_TASK] adc: UA=11428 UB=1016 UC=1009 UN=-71 IA=-4770 IB=972 IC=1091 IN=1051 T=963
[RX] 2026-11-04T19:26:46.629+08:00 [2026-11-04 19:26:45][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-727 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:26:46.639+08:00 [2026-11-04 19:26:45][ADC_TASK] meas: seq=5300 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:26:46.650+08:00 [2026-11-04 19:26:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:26:56.575+08:00 [2026-11-04 19:26:55][ADC_TASK] frames=1069789 idle=0 skip=107 seq=1070006 frst_err=0 ring=1069682 adc1_rc=0 adc1_err=0 adc1_seq=1070005 adc1_gap=322 adc1_hw=0 tgap=238
[RX] 2026-11-04T19:26:56.585+08:00 [2026-11-04 19:26:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:56.595+08:00 [2026-11-04 19:26:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:56.595+08:00 [2026-11-04 19:26:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:56.605+08:00 [2026-11-04 19:26:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:26:56.616+08:00 [2026-11-04 19:26:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=8 Im=4 rms=9 (x0.1)
[RX] 2026-11-04T19:26:56.616+08:00 [2026-11-04 19:26:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:26:56.626+08:00 [2026-11-04 19:26:55][ADC_TASK] adc: UA=11428 UB=1018 UC=1019 UN=-70 IA=-7743 IB=980 IC=1101 IN=1063 T=965
[RX] 2026-11-04T19:26:56.637+08:00 [2026-11-04 19:26:55][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-1181 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:26:56.647+08:00 [2026-11-04 19:26:55][ADC_TASK] meas: seq=5350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:26:56.647+08:00 [2026-11-04 19:26:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:27:06.575+08:00 [2026-11-04 19:27:05][ADC_TASK] frames=1079787 idle=0 skip=108 seq=1080006 frst_err=0 ring=1079679 adc1_rc=0 adc1_err=0 adc1_seq=1080005 adc1_gap=325 adc1_hw=0 tgap=240
[RX] 2026-11-04T19:27:06.585+08:00 [2026-11-04 19:27:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:06.585+08:00 [2026-11-04 19:27:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:06.596+08:00 [2026-11-04 19:27:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:06.607+08:00 [2026-11-04 19:27:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:06.617+08:00 [2026-11-04 19:27:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:27:06.617+08:00 [2026-11-04 19:27:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:27:06.628+08:00 [2026-11-04 19:27:05][ADC_TASK] adc: UA=11429 UB=1018 UC=1026 UN=-69 IA=-8862 IB=977 IC=1091 IN=1065 T=955
[RX] 2026-11-04T19:27:06.628+08:00 [2026-11-04 19:27:05][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-1352 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:27:06.639+08:00 [2026-11-04 19:27:05][ADC_TASK] meas: seq=5400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:27:06.649+08:00 [2026-11-04 19:27:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:27:16.573+08:00 [2026-11-04 19:27:15][ADC_TASK] frames=1089785 idle=0 skip=109 seq=1090006 frst_err=0 ring=1089676 adc1_rc=0 adc1_err=0 adc1_seq=1090005 adc1_gap=328 adc1_hw=0 tgap=242
[RX] 2026-11-04T19:27:16.583+08:00 [2026-11-04 19:27:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:16.593+08:00 [2026-11-04 19:27:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:16.593+08:00 [2026-11-04 19:27:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:16.604+08:00 [2026-11-04 19:27:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:16.614+08:00 [2026-11-04 19:27:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=1 Im=0 rms=1 (x0.1)
[RX] 2026-11-04T19:27:16.614+08:00 [2026-11-04 19:27:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:27:16.625+08:00 [2026-11-04 19:27:15][ADC_TASK] adc: UA=11427 UB=1033 UC=1040 UN=-72 IA=-1264 IB=972 IC=1079 IN=1065 T=939
[RX] 2026-11-04T19:27:16.635+08:00 [2026-11-04 19:27:15][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-192 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:27:16.646+08:00 [2026-11-04 19:27:15][ADC_TASK] meas: seq=5450 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:27:16.646+08:00 [2026-11-04 19:27:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:27:26.571+08:00 [2026-11-04 19:27:25][ADC_TASK] frames=1099783 idle=0 skip=110 seq=1100006 frst_err=0 ring=1099673 adc1_rc=0 adc1_err=0 adc1_seq=1100005 adc1_gap=331 adc1_hw=0 tgap=244
[RX] 2026-11-04T19:27:26.582+08:00 [2026-11-04 19:27:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:26.592+08:00 [2026-11-04 19:27:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:26.602+08:00 [2026-11-04 19:27:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:26.602+08:00 [2026-11-04 19:27:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:26.613+08:00 [2026-11-04 19:27:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:27:26.613+08:00 [2026-11-04 19:27:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:27:26.623+08:00 [2026-11-04 19:27:25][ADC_TASK] adc: UA=11429 UB=1028 UC=1023 UN=-69 IA=-876 IB=952 IC=1061 IN=1043 T=925
[RX] 2026-11-04T19:27:26.634+08:00 [2026-11-04 19:27:25][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-133 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:27:26.644+08:00 [2026-11-04 19:27:25][ADC_TASK] meas: seq=5500 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:27:26.644+08:00 [2026-11-04 19:27:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:27:36.568+08:00 [2026-11-04 19:27:35][ADC_TASK] frames=1109781 idle=0 skip=111 seq=1110006 frst_err=0 ring=1109670 adc1_rc=0 adc1_err=0 adc1_seq=1110005 adc1_gap=334 adc1_hw=0 tgap=246
[RX] 2026-11-04T19:27:36.589+08:00 [2026-11-04 19:27:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:36.589+08:00 [2026-11-04 19:27:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:36.599+08:00 [2026-11-04 19:27:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:36.599+08:00 [2026-11-04 19:27:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:36.609+08:00 [2026-11-04 19:27:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:27:36.620+08:00 [2026-11-04 19:27:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:27:36.620+08:00 [2026-11-04 19:27:35][ADC_TASK] adc: UA=11429 UB=998 UC=999 UN=-70 IA=-228 IB=942 IC=1066 IN=1029 T=937
[RX] 2026-11-04T19:27:36.631+08:00 [2026-11-04 19:27:35][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-34 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:27:36.641+08:00 [2026-11-04 19:27:35][ADC_TASK] meas: seq=5550 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:27:36.651+08:00 [2026-11-04 19:27:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:27:46.573+08:00 [2026-11-04 19:27:45][ADC_TASK] frames=1119779 idle=0 skip=112 seq=1120006 frst_err=0 ring=1119667 adc1_rc=0 adc1_err=0 adc1_seq=1120005 adc1_gap=337 adc1_hw=0 tgap=248
[RX] 2026-11-04T19:27:46.583+08:00 [2026-11-04 19:27:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:46.593+08:00 [2026-11-04 19:27:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:46.593+08:00 [2026-11-04 19:27:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:46.604+08:00 [2026-11-04 19:27:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:46.615+08:00 [2026-11-04 19:27:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:27:46.615+08:00 [2026-11-04 19:27:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:27:46.625+08:00 [2026-11-04 19:27:45][ADC_TASK] adc: UA=11429 UB=994 UC=1007 UN=-72 IA=458 IB=963 IC=1085 IN=1048 T=958
[RX] 2026-11-04T19:27:46.636+08:00 [2026-11-04 19:27:45][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=69 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:27:46.646+08:00 [2026-11-04 19:27:45][ADC_TASK] meas: seq=5600 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:27:46.646+08:00 [2026-11-04 19:27:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:27:56.570+08:00 [2026-11-04 19:27:55][ADC_TASK] frames=1129777 idle=0 skip=113 seq=1130006 frst_err=0 ring=1129664 adc1_rc=0 adc1_err=0 adc1_seq=1130005 adc1_gap=340 adc1_hw=0 tgap=250
[RX] 2026-11-04T19:27:56.580+08:00 [2026-11-04 19:27:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:56.591+08:00 [2026-11-04 19:27:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:56.601+08:00 [2026-11-04 19:27:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:56.601+08:00 [2026-11-04 19:27:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:27:56.612+08:00 [2026-11-04 19:27:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=-1 Im=0 rms=1 (x0.1)
[RX] 2026-11-04T19:27:56.622+08:00 [2026-11-04 19:27:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:27:56.622+08:00 [2026-11-04 19:27:55][ADC_TASK] adc: UA=11429 UB=995 UC=1009 UN=-71 IA=1015 IB=971 IC=1077 IN=1046 T=957
[RX] 2026-11-04T19:27:56.632+08:00 [2026-11-04 19:27:55][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=154 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:27:56.643+08:00 [2026-11-04 19:27:55][ADC_TASK] meas: seq=5650 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:27:56.643+08:00 [2026-11-04 19:27:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:28:06.574+08:00 [2026-11-04 19:28:05][ADC_TASK] frames=1139775 idle=0 skip=114 seq=1140006 frst_err=0 ring=1139661 adc1_rc=0 adc1_err=0 adc1_seq=1140005 adc1_gap=343 adc1_hw=0 tgap=252
[RX] 2026-11-04T19:28:06.584+08:00 [2026-11-04 19:28:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:06.584+08:00 [2026-11-04 19:28:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:06.595+08:00 [2026-11-04 19:28:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:06.606+08:00 [2026-11-04 19:28:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:06.616+08:00 [2026-11-04 19:28:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=-1 Im=0 rms=1 (x0.1)
[RX] 2026-11-04T19:28:06.616+08:00 [2026-11-04 19:28:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:28:06.626+08:00 [2026-11-04 19:28:05][ADC_TASK] adc: UA=11428 UB=1006 UC=1006 UN=-72 IA=1311 IB=973 IC=1075 IN=1042 T=959
[RX] 2026-11-04T19:28:06.626+08:00 [2026-11-04 19:28:05][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=200 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:28:06.637+08:00 [2026-11-04 19:28:05][ADC_TASK] meas: seq=5700 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:28:06.648+08:00 [2026-11-04 19:28:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:28:16.570+08:00 [2026-11-04 19:28:15][ADC_TASK] frames=1149773 idle=0 skip=115 seq=1150006 frst_err=0 ring=1149658 adc1_rc=0 adc1_err=0 adc1_seq=1150005 adc1_gap=346 adc1_hw=0 tgap=254
[RX] 2026-11-04T19:28:16.581+08:00 [2026-11-04 19:28:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:16.591+08:00 [2026-11-04 19:28:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:16.601+08:00 [2026-11-04 19:28:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:16.601+08:00 [2026-11-04 19:28:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:16.611+08:00 [2026-11-04 19:28:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=-1 Im=0 rms=1 (x0.1)
[RX] 2026-11-04T19:28:16.622+08:00 [2026-11-04 19:28:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:28:16.622+08:00 [2026-11-04 19:28:15][ADC_TASK] adc: UA=11429 UB=1035 UC=1028 UN=-74 IA=1275 IB=995 IC=1093 IN=1065 T=975
[RX] 2026-11-04T19:28:16.633+08:00 [2026-11-04 19:28:15][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=194 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:28:16.643+08:00 [2026-11-04 19:28:15][ADC_TASK] meas: seq=5750 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:28:16.643+08:00 [2026-11-04 19:28:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:28:26.566+08:00 [2026-11-04 19:28:25][ADC_TASK] frames=1159771 idle=0 skip=116 seq=1160006 frst_err=0 ring=1159655 adc1_rc=0 adc1_err=0 adc1_seq=1160005 adc1_gap=349 adc1_hw=0 tgap=256
[RX] 2026-11-04T19:28:26.587+08:00 [2026-11-04 19:28:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:26.587+08:00 [2026-11-04 19:28:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:26.598+08:00 [2026-11-04 19:28:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:26.598+08:00 [2026-11-04 19:28:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:26.608+08:00 [2026-11-04 19:28:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=-1 Im=1 rms=1 (x0.1)
[RX] 2026-11-04T19:28:26.619+08:00 [2026-11-04 19:28:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:28:26.619+08:00 [2026-11-04 19:28:25][ADC_TASK] adc: UA=11427 UB=1047 UC=1027 UN=-75 IA=915 IB=989 IC=1071 IN=1054 T=960
[RX] 2026-11-04T19:28:26.629+08:00 [2026-11-04 19:28:25][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=139 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:28:26.640+08:00 [2026-11-04 19:28:25][ADC_TASK] meas: seq=5800 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:28:26.650+08:00 [2026-11-04 19:28:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:28:36.566+08:00 [2026-11-04 19:28:35][ADC_TASK] frames=1169769 idle=0 skip=117 seq=1170006 frst_err=0 ring=1169652 adc1_rc=0 adc1_err=0 adc1_seq=1170005 adc1_gap=352 adc1_hw=0 tgap=258
[RX] 2026-11-04T19:28:36.587+08:00 [2026-11-04 19:28:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:28:36.587+08:00 [2026-11-04 19:28:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:36.597+08:00 [2026-11-04 19:28:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:36.597+08:00 [2026-11-04 19:28:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:36.608+08:00 [2026-11-04 19:28:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:36.618+08:00 [2026-11-04 19:28:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:28:36.618+08:00 [2026-11-04 19:28:35][ADC_TASK] adc: UA=11428 UB=1038 UC=1002 UN=-75 IA=-1555 IB=970 IC=1054 IN=1028 T=946
[RX] 2026-11-04T19:28:36.628+08:00 [2026-11-04 19:28:35][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=-237 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:28:36.639+08:00 [2026-11-04 19:28:35][ADC_TASK] meas: seq=5850 v=1 ua_mv=0 un_mv=0 ia_mv=159 f=0 fv=0
[RX] 2026-11-04T19:28:36.649+08:00 [2026-11-04 19:28:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:28:46.570+08:00 [2026-11-04 19:28:45][ADC_TASK] frames=1179767 idle=0 skip=118 seq=1180006 frst_err=0 ring=1179649 adc1_rc=0 adc1_err=0 adc1_seq=1180005 adc1_gap=355 adc1_hw=0 tgap=260
[RX] 2026-11-04T19:28:46.580+08:00 [2026-11-04 19:28:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:28:46.590+08:00 [2026-11-04 19:28:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:46.601+08:00 [2026-11-04 19:28:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:46.601+08:00 [2026-11-04 19:28:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:46.611+08:00 [2026-11-04 19:28:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:46.611+08:00 [2026-11-04 19:28:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:28:46.622+08:00 [2026-11-04 19:28:45][ADC_TASK] adc: UA=11428 UB=1035 UC=1002 UN=-72 IA=-1540 IB=973 IC=1052 IN=1022 T=955
[RX] 2026-11-04T19:28:46.632+08:00 [2026-11-04 19:28:45][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-234 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:28:46.643+08:00 [2026-11-04 19:28:45][ADC_TASK] meas: seq=5900 v=1 ua_mv=0 un_mv=0 ia_mv=160 f=0 fv=0
[RX] 2026-11-04T19:28:46.643+08:00 [2026-11-04 19:28:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:28:56.566+08:00 [2026-11-04 19:28:55][ADC_TASK] frames=1189765 idle=0 skip=119 seq=1190006 frst_err=0 ring=1189646 adc1_rc=0 adc1_err=0 adc1_seq=1190005 adc1_gap=358 adc1_hw=0 tgap=262
[RX] 2026-11-04T19:28:56.588+08:00 [2026-11-04 19:28:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:28:56.588+08:00 [2026-11-04 19:28:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:56.598+08:00 [2026-11-04 19:28:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:56.598+08:00 [2026-11-04 19:28:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:56.609+08:00 [2026-11-04 19:28:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:28:56.619+08:00 [2026-11-04 19:28:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:28:56.619+08:00 [2026-11-04 19:28:55][ADC_TASK] adc: UA=11427 UB=1042 UC=1032 UN=-74 IA=-1509 IB=989 IC=1058 IN=1026 T=971
[RX] 2026-11-04T19:28:56.629+08:00 [2026-11-04 19:28:55][ADC_TASK] cal: ua_mv=1743 un_mv=-11 ia_mv=-230 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:28:56.640+08:00 [2026-11-04 19:28:55][ADC_TASK] meas: seq=5950 v=1 ua_mv=0 un_mv=0 ia_mv=160 f=0 fv=0
[RX] 2026-11-04T19:28:56.650+08:00 [2026-11-04 19:28:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:29:06.565+08:00 [2026-11-04 19:29:05][ADC_TASK] frames=1199763 idle=0 skip=120 seq=1200006 frst_err=0 ring=1199643 adc1_rc=0 adc1_err=0 adc1_seq=1200005 adc1_gap=361 adc1_hw=0 tgap=264
[RX] 2026-11-04T19:29:06.585+08:00 [2026-11-04 19:29:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:29:06.585+08:00 [2026-11-04 19:29:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:06.596+08:00 [2026-11-04 19:29:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:06.596+08:00 [2026-11-04 19:29:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:06.607+08:00 [2026-11-04 19:29:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:06.617+08:00 [2026-11-04 19:29:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:29:06.617+08:00 [2026-11-04 19:29:05][ADC_TASK] adc: UA=11427 UB=1033 UC=1038 UN=-72 IA=-1464 IB=973 IC=1041 IN=1007 T=942
[RX] 2026-11-04T19:29:06.627+08:00 [2026-11-04 19:29:05][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-223 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:29:06.638+08:00 [2026-11-04 19:29:05][ADC_TASK] meas: seq=6000 v=1 ua_mv=0 un_mv=0 ia_mv=160 f=0 fv=0
[RX] 2026-11-04T19:29:06.648+08:00 [2026-11-04 19:29:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:29:16.570+08:00 [2026-11-04 19:29:15][ADC_TASK] frames=1209761 idle=0 skip=121 seq=1210006 frst_err=0 ring=1209640 adc1_rc=0 adc1_err=0 adc1_seq=1210005 adc1_gap=364 adc1_hw=0 tgap=266
[RX] 2026-11-04T19:29:16.580+08:00 [2026-11-04 19:29:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:29:16.591+08:00 [2026-11-04 19:29:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:16.591+08:00 [2026-11-04 19:29:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:16.601+08:00 [2026-11-04 19:29:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:16.612+08:00 [2026-11-04 19:29:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:16.612+08:00 [2026-11-04 19:29:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:29:16.622+08:00 [2026-11-04 19:29:15][ADC_TASK] adc: UA=11429 UB=1038 UC=1052 UN=-69 IA=-1406 IB=965 IC=1051 IN=1012 T=939
[RX] 2026-11-04T19:29:16.632+08:00 [2026-11-04 19:29:15][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-214 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:29:16.643+08:00 [2026-11-04 19:29:15][ADC_TASK] meas: seq=6050 v=1 ua_mv=0 un_mv=0 ia_mv=160 f=0 fv=0
[RX] 2026-11-04T19:29:16.643+08:00 [2026-11-04 19:29:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:29:26.564+08:00 [2026-11-04 19:29:25][ADC_TASK] frames=1219759 idle=0 skip=122 seq=1220006 frst_err=0 ring=1219637 adc1_rc=0 adc1_err=0 adc1_seq=1220005 adc1_gap=367 adc1_hw=0 tgap=268
[RX] 2026-11-04T19:29:26.584+08:00 [2026-11-04 19:29:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:29:26.584+08:00 [2026-11-04 19:29:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:26.595+08:00 [2026-11-04 19:29:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:26.606+08:00 [2026-11-04 19:29:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:26.606+08:00 [2026-11-04 19:29:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:26.616+08:00 [2026-11-04 19:29:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:29:26.616+08:00 [2026-11-04 19:29:25][ADC_TASK] adc: UA=11428 UB=1007 UC=1037 UN=-69 IA=-1333 IB=942 IC=1049 IN=1000 T=923
[RX] 2026-11-04T19:29:26.626+08:00 [2026-11-04 19:29:25][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-203 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:29:26.637+08:00 [2026-11-04 19:29:25][ADC_TASK] meas: seq=6100 v=1 ua_mv=0 un_mv=0 ia_mv=159 f=0 fv=0
[RX] 2026-11-04T19:29:26.647+08:00 [2026-11-04 19:29:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:29:36.564+08:00 [2026-11-04 19:29:35][ADC_TASK] frames=1229757 idle=0 skip=123 seq=1230006 frst_err=0 ring=1229634 adc1_rc=0 adc1_err=0 adc1_seq=1230005 adc1_gap=370 adc1_hw=0 tgap=270
[RX] 2026-11-04T19:29:36.585+08:00 [2026-11-04 19:29:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:36.585+08:00 [2026-11-04 19:29:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:36.596+08:00 [2026-11-04 19:29:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:36.596+08:00 [2026-11-04 19:29:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:36.606+08:00 [2026-11-04 19:29:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:36.617+08:00 [2026-11-04 19:29:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:29:36.617+08:00 [2026-11-04 19:29:35][ADC_TASK] adc: UA=11428 UB=1003 UC=1050 UN=-42 IA=-42 IB=948 IC=1078 IN=1034 T=932
[RX] 2026-11-04T19:29:36.627+08:00 [2026-11-04 19:29:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:29:36.638+08:00 [2026-11-04 19:29:35][ADC_TASK] meas: seq=6150 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:29:36.648+08:00 [2026-11-04 19:29:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:29:46.565+08:00 [2026-11-04 19:29:45][ADC_TASK] frames=1239755 idle=0 skip=124 seq=1240006 frst_err=0 ring=1239631 adc1_rc=0 adc1_err=0 adc1_seq=1240005 adc1_gap=373 adc1_hw=0 tgap=272
[RX] 2026-11-04T19:29:46.586+08:00 [2026-11-04 19:29:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:46.586+08:00 [2026-11-04 19:29:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:46.596+08:00 [2026-11-04 19:29:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:46.596+08:00 [2026-11-04 19:29:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:46.606+08:00 [2026-11-04 19:29:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:46.617+08:00 [2026-11-04 19:29:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:29:46.617+08:00 [2026-11-04 19:29:45][ADC_TASK] adc: UA=11428 UB=990 UC=1018 UN=-41 IA=-41 IB=945 IC=1083 IN=1045 T=934
[RX] 2026-11-04T19:29:46.628+08:00 [2026-11-04 19:29:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:29:46.638+08:00 [2026-11-04 19:29:45][ADC_TASK] meas: seq=6200 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:29:46.649+08:00 [2026-11-04 19:29:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:29:56.570+08:00 [2026-11-04 19:29:55][ADC_TASK] frames=1249753 idle=0 skip=125 seq=1250006 frst_err=0 ring=1249628 adc1_rc=0 adc1_err=0 adc1_seq=1250005 adc1_gap=376 adc1_hw=0 tgap=274
[RX] 2026-11-04T19:29:56.580+08:00 [2026-11-04 19:29:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:56.591+08:00 [2026-11-04 19:29:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:56.591+08:00 [2026-11-04 19:29:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:56.602+08:00 [2026-11-04 19:29:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:56.612+08:00 [2026-11-04 19:29:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:29:56.612+08:00 [2026-11-04 19:29:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:29:56.622+08:00 [2026-11-04 19:29:55][ADC_TASK] adc: UA=11430 UB=985 UC=1017 UN=-70 IA=-85 IB=959 IC=1079 IN=1054 T=958
[RX] 2026-11-04T19:29:56.633+08:00 [2026-11-04 19:29:55][ADC_TASK] cal: ua_mv=1744 un_mv=-10 ia_mv=-12 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:29:56.643+08:00 [2026-11-04 19:29:55][ADC_TASK] meas: seq=6250 v=1 ua_mv=0 un_mv=0 ia_mv=3 f=0 fv=0
[RX] 2026-11-04T19:29:56.643+08:00 [2026-11-04 19:29:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:06.563+08:00 [2026-11-04 19:30:05][ADC_TASK] frames=1259751 idle=0 skip=126 seq=1260006 frst_err=0 ring=1259625 adc1_rc=0 adc1_err=0 adc1_seq=1260005 adc1_gap=379 adc1_hw=0 tgap=276
[RX] 2026-11-04T19:30:06.584+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=1 (x0.1)
[RX] 2026-11-04T19:30:06.584+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:06.594+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:06.605+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=-1 rms=1 (x0.1)
[RX] 2026-11-04T19:30:06.605+08:00 [2026-11-04 19:30:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:06.615+08:00 [2026-11-04 19:30:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:06.615+08:00 [2026-11-04 19:30:05][ADC_TASK] adc: UA=11429 UB=989 UC=1006 UN=-68 IA=-1469 IB=972 IC=1066 IN=1038 T=964
[RX] 2026-11-04T19:30:06.626+08:00 [2026-11-04 19:30:05][ADC_TASK] cal: ua_mv=1743 un_mv=-10 ia_mv=-224 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:06.637+08:00 [2026-11-04 19:30:05][ADC_TASK] meas: seq=6300 v=1 ua_mv=0 un_mv=0 ia_mv=109 f=0 fv=0
[RX] 2026-11-04T19:30:06.647+08:00 [2026-11-04 19:30:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:16.562+08:00 [2026-11-04 19:30:15][ADC_TASK] frames=1269749 idle=0 skip=127 seq=1270006 frst_err=0 ring=1269622 adc1_rc=0 adc1_err=0 adc1_seq=1270005 adc1_gap=382 adc1_hw=0 tgap=278
[RX] 2026-11-04T19:30:16.583+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.583+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.594+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.604+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.604+08:00 [2026-11-04 19:30:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:16.615+08:00 [2026-11-04 19:30:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:16.615+08:00 [2026-11-04 19:30:15][ADC_TASK] adc: UA=11428 UB=1001 UC=991 UN=-41 IA=-41 IB=973 IC=1062 IN=1028 T=954
[RX] 2026-11-04T19:30:16.626+08:00 [2026-11-04 19:30:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:16.636+08:00 [2026-11-04 19:30:15][ADC_TASK] meas: seq=6350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:16.647+08:00 [2026-11-04 19:30:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:26.570+08:00 [2026-11-04 19:30:25][ADC_TASK] frames=1279747 idle=0 skip=128 seq=1280006 frst_err=0 ring=1279619 adc1_rc=0 adc1_err=0 adc1_seq=1280005 adc1_gap=385 adc1_hw=0 tgap=280
[RX] 2026-11-04T19:30:26.581+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.581+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.591+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.602+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.612+08:00 [2026-11-04 19:30:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:26.612+08:00 [2026-11-04 19:30:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:26.622+08:00 [2026-11-04 19:30:25][ADC_TASK] adc: UA=11429 UB=1019 UC=997 UN=-43 IA=-41 IB=975 IC=1079 IN=1045 T=954
[RX] 2026-11-04T19:30:26.622+08:00 [2026-11-04 19:30:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:26.643+08:00 [2026-11-04 19:30:25][ADC_TASK] meas: seq=6400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:26.643+08:00 [2026-11-04 19:30:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:36.564+08:00 [2026-11-04 19:30:35][ADC_TASK] frames=1289745 idle=0 skip=129 seq=1290006 frst_err=0 ring=1289616 adc1_rc=0 adc1_err=0 adc1_seq=1290005 adc1_gap=388 adc1_hw=0 tgap=282
[RX] 2026-11-04T19:30:36.574+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.585+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.595+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.595+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.606+08:00 [2026-11-04 19:30:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:36.616+08:00 [2026-11-04 19:30:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:36.616+08:00 [2026-11-04 19:30:35][ADC_TASK] adc: UA=11429 UB=1037 UC=1018 UN=-41 IA=-40 IB=983 IC=1082 IN=1057 T=966
[RX] 2026-11-04T19:30:36.627+08:00 [2026-11-04 19:30:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:36.637+08:00 [2026-11-04 19:30:35][ADC_TASK] meas: seq=6450 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:36.648+08:00 [2026-11-04 19:30:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:46.570+08:00 [2026-11-04 19:30:45][ADC_TASK] frames=1299743 idle=0 skip=130 seq=1300006 frst_err=0 ring=1299613 adc1_rc=0 adc1_err=0 adc1_seq=1300005 adc1_gap=391 adc1_hw=0 tgap=284
[RX] 2026-11-04T19:30:46.580+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.580+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.591+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.601+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.612+08:00 [2026-11-04 19:30:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:46.612+08:00 [2026-11-04 19:30:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:46.622+08:00 [2026-11-04 19:30:45][ADC_TASK] adc: UA=11428 UB=1029 UC=1003 UN=-43 IA=-41 IB=978 IC=1054 IN=1032 T=963
[RX] 2026-11-04T19:30:46.622+08:00 [2026-11-04 19:30:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:46.643+08:00 [2026-11-04 19:30:45][ADC_TASK] meas: seq=6500 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:46.643+08:00 [2026-11-04 19:30:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:30:56.566+08:00 [2026-11-04 19:30:55][ADC_TASK] frames=1309741 idle=0 skip=131 seq=1310006 frst_err=0 ring=1309610 adc1_rc=0 adc1_err=0 adc1_seq=1310005 adc1_gap=394 adc1_hw=0 tgap=286
[RX] 2026-11-04T19:30:56.576+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.586+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.596+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.596+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.607+08:00 [2026-11-04 19:30:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:30:56.618+08:00 [2026-11-04 19:30:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:30:56.618+08:00 [2026-11-04 19:30:55][ADC_TASK] adc: UA=11429 UB=1043 UC=1020 UN=-43 IA=-42 IB=993 IC=1053 IN=1026 T=971
[RX] 2026-11-04T19:30:56.628+08:00 [2026-11-04 19:30:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:30:56.639+08:00 [2026-11-04 19:30:55][ADC_TASK] meas: seq=6550 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:30:56.639+08:00 [2026-11-04 19:30:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:06.562+08:00 [2026-11-04 19:31:05][ADC_TASK] frames=1319739 idle=0 skip=132 seq=1320006 frst_err=0 ring=1319607 adc1_rc=0 adc1_err=0 adc1_seq=1320005 adc1_gap=397 adc1_hw=0 tgap=288
[RX] 2026-11-04T19:31:06.583+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.583+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.593+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.603+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.603+08:00 [2026-11-04 19:31:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:06.614+08:00 [2026-11-04 19:31:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:06.614+08:00 [2026-11-04 19:31:05][ADC_TASK] adc: UA=11429 UB=1043 UC=1042 UN=-41 IA=-40 IB=986 IC=1046 IN=1010 T=954
[RX] 2026-11-04T19:31:06.624+08:00 [2026-11-04 19:31:05][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:06.635+08:00 [2026-11-04 19:31:05][ADC_TASK] meas: seq=6600 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:06.645+08:00 [2026-11-04 19:31:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:16.561+08:00 [2026-11-04 19:31:15][ADC_TASK] frames=1329737 idle=0 skip=133 seq=1330006 frst_err=0 ring=1329604 adc1_rc=0 adc1_err=0 adc1_seq=1330005 adc1_gap=400 adc1_hw=0 tgap=290
[RX] 2026-11-04T19:31:16.582+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.582+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.593+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.603+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.603+08:00 [2026-11-04 19:31:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:16.613+08:00 [2026-11-04 19:31:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:16.613+08:00 [2026-11-04 19:31:15][ADC_TASK] adc: UA=11429 UB=1038 UC=1042 UN=-41 IA=-40 IB=962 IC=1047 IN=1003 T=935
[RX] 2026-11-04T19:31:16.624+08:00 [2026-11-04 19:31:15][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:16.634+08:00 [2026-11-04 19:31:15][ADC_TASK] meas: seq=6650 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:16.644+08:00 [2026-11-04 19:31:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:26.566+08:00 [2026-11-04 19:31:25][ADC_TASK] frames=1339735 idle=0 skip=134 seq=1340006 frst_err=0 ring=1339601 adc1_rc=0 adc1_err=0 adc1_seq=1340005 adc1_gap=403 adc1_hw=0 tgap=292
[RX] 2026-11-04T19:31:26.576+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.587+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.597+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.597+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.608+08:00 [2026-11-04 19:31:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:26.608+08:00 [2026-11-04 19:31:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:26.618+08:00 [2026-11-04 19:31:25][ADC_TASK] adc: UA=11428 UB=1024 UC=1049 UN=-42 IA=-41 IB=951 IC=1049 IN=1011 T=931
[RX] 2026-11-04T19:31:26.629+08:00 [2026-11-04 19:31:25][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:26.639+08:00 [2026-11-04 19:31:25][ADC_TASK] meas: seq=6700 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:26.639+08:00 [2026-11-04 19:31:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:36.566+08:00 [2026-11-04 19:31:35][ADC_TASK] frames=1349733 idle=0 skip=135 seq=1350006 frst_err=0 ring=1349598 adc1_rc=0 adc1_err=0 adc1_seq=1350005 adc1_gap=406 adc1_hw=0 tgap=294
[RX] 2026-11-04T19:31:36.577+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.587+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.587+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.598+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.608+08:00 [2026-11-04 19:31:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:36.608+08:00 [2026-11-04 19:31:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:36.618+08:00 [2026-11-04 19:31:35][ADC_TASK] adc: UA=11428 UB=1005 UC=1054 UN=-43 IA=-42 IB=947 IC=1074 IN=1024 T=934
[RX] 2026-11-04T19:31:36.629+08:00 [2026-11-04 19:31:35][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=-6 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:31:36.639+08:00 [2026-11-04 19:31:35][ADC_TASK] meas: seq=6750 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:36.639+08:00 [2026-11-04 19:31:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:46.560+08:00 [2026-11-04 19:31:45][ADC_TASK] frames=1359731 idle=0 skip=136 seq=1360006 frst_err=0 ring=1359595 adc1_rc=0 adc1_err=0 adc1_seq=1360005 adc1_gap=409 adc1_hw=0 tgap=296
[RX] 2026-11-04T19:31:46.581+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.581+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.592+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.602+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.602+08:00 [2026-11-04 19:31:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:46.613+08:00 [2026-11-04 19:31:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:46.613+08:00 [2026-11-04 19:31:45][ADC_TASK] adc: UA=11428 UB=0 UC=1014 UN=-42 IA=11450 IB=939 IC=1050 IN=1030 T=924
[RX] 2026-11-04T19:31:46.623+08:00 [2026-11-04 19:31:45][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:31:46.634+08:00 [2026-11-04 19:31:45][ADC_TASK] meas: seq=6800 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:46.644+08:00 [2026-11-04 19:31:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:31:56.563+08:00 [2026-11-04 19:31:55][ADC_TASK] frames=1369729 idle=0 skip=137 seq=1370006 frst_err=0 ring=1369592 adc1_rc=0 adc1_err=0 adc1_seq=1370005 adc1_gap=412 adc1_hw=0 tgap=298
[RX] 2026-11-04T19:31:56.574+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.584+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.595+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.595+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.605+08:00 [2026-11-04 19:31:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:31:56.616+08:00 [2026-11-04 19:31:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:31:56.616+08:00 [2026-11-04 19:31:55][ADC_TASK] adc: UA=11429 UB=0 UC=1018 UN=-44 IA=11452 IB=943 IC=919 IN=991 T=934
[RX] 2026-11-04T19:31:56.626+08:00 [2026-11-04 19:31:55][ADC_TASK] cal: ua_mv=1743 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x002 edge=0x002
[RX] 2026-11-04T19:31:56.637+08:00 [2026-11-04 19:31:55][ADC_TASK] meas: seq=6850 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:31:56.637+08:00 [2026-11-04 19:31:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
@@ -0,0 +1,17 @@
fc = 795.8 Hz (R19+R21 = 2000 Ω, C35 = 100 nF)
f/Hz |H|衰减% 相位° 实测偏差% 残差% 备注
50.0 -0.20 -3.6 -0.63 -0.43 C 行 纯基波(复测 0.160 → 0.0 %)
100.0 -0.78 -7.2 -1.25 -0.47 A 行 单注二次
250.0 -4.60 -17.4 -4.38 0.22 B 行 单注五次(11-02 初读 0.152 = −5.0 %)
产品含义外推(同一 1k+1k+100n 拓扑,按谐波次数):
5 次 = 250 Hz → 衰减 -4.6 %
7 次 = 350 Hz → 衰减 -8.5 %
9 次 = 450 Hz → 衰减 -13.0 %
11 次 = 550 Hz → 衰减 -17.7 %
13 次 = 650 Hz → 衰减 -22.6 %
15 次 = 750 Hz → 衰减 -27.2 %
21 次 = 1050 Hz → 衰减 -39.6 %
若 C 改小 10×(10 nF):fc = 7958 Hz,21 次(1050 Hz) 衰减仅 -0.9 %
@@ -0,0 +1,13 @@
# 2026-11-04 交流补测轮证据清单(md5 = 入库 LF 形态值,checkout 后直接可比对;
# BIN 为二进制不受行尾归一化影响)
# 本机全量备份(不入库): serial.log 与 ci-artifacts/ac-20261104/*
9f7d5f6b3243d62c5d75cfca0a8711e8 108603 B 20261104_ac_clip_rail_log.txt
8475adc49a5ff4693f2f3d2ae054c2fb 削顶锁存态观察段(触发 19:36:41,八路≈0、UB 恒钉零码、段头 clip_mask 0x00E6) 5250 B 20261104_ac_clip_sealed_wave11.BIN
8cdf7afd3e0e3c63240d6eeca55d2610 58923 B 20261104_ac_harm_ia_log.txt
ac5edc10399c9e8bd1d03762d7e645c2 92255 B 20261104_ac_phase_log.txt
e5dc076bc4c975eac92f93b2db8e0e81 相位轮 0°/90° 段原件(触发 19:15:32,fft 见 20261104_ac_wave09_scriptout.txt) 5250 B 20261104_ac_phase_wave09.BIN
b853cd0c34d3698184782a32cb34c2d2 无归属切换瞬态段(触发 19:22:19,UA 1408 mVpk 瞬态,不判任何行,留证溯源) 5250 B 20261104_ac_unattributed_wave10.BIN
599e8c5b79a590089f3bd47134c2e1be 6442 B 20261104_ac_wave09_scriptout.txt
783b45fe3fcf5034a0a84954c34a2d5d 6033 B 20261104_ac_wave10_11_scriptout.txt
236c34c4f33105d7c653f7939d3a2e3c 谐波 B 行归因证据:前端差分 RC 三点拟合模型输出(脚本 ia_rc_model.py,见登记簿 §3 与通道映射 §8.2) 818 B 20261104_ac_ia_rc_model.txt
@@ -0,0 +1,734 @@
# 2026-11-04 交流补测轮串口日志摘录(源:serial-assistant serial.log,gen=328,daemon 打戳 +08:00)
# 板固件: fw build: Nov 4 2026 19:05:57 suite=NONE dstr=off(HEAD 87fa0276 构建,烧录回读一致)
# 完整原始日志本机备份: ci-artifacts/ac-20261104/serial_full_backup.log(本机产物,daemon 20MB 截头保险)
# 取证注意: 19:56:47 三条 [ERR][LFT_IO]/[FTP] 是 PC 侧 FTP 探测不存在文件 V5_UP.BIN 所致(操作自归因),非固件缺陷
# 通道静态位观察: 未接线的 TPAFE 输入浮空读数 ≈1743/1747 mV(撤线后 UA/IA 出现,非损伤、非零点漂移)
# # 相位轮 0°/90° 复测窗(UA+UN 双路 Vpp 2.828 V / 50 Hz、相位差 90°);录波段 WAVE09 触发 @19:15:32
[RX] 2026-11-04T19:13:06.594+08:00 [2026-11-04 19:13:05][ADC_TASK] frames=239955 idle=0 skip=24 seq=240006 frst_err=0 ring=239931 adc1_rc=0 adc1_err=0 adc1_seq=240005 adc1_gap=73 adc1_hw=0 tgap=46
[RX] 2026-11-04T19:13:06.604+08:00 [2026-11-04 19:13:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:06.615+08:00 [2026-11-04 19:13:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:06.615+08:00 [2026-11-04 19:13:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:06.625+08:00 [2026-11-04 19:13:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:06.636+08:00 [2026-11-04 19:13:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:06.636+08:00 [2026-11-04 19:13:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:13:06.646+08:00 [2026-11-04 19:13:05][ADC_TASK] adc: UA=-46 UB=914 UC=1024 UN=-46 IA=11453 IB=907 IC=994 IN=1019 T=898
[RX] 2026-11-04T19:13:06.657+08:00 [2026-11-04 19:13:05][ADC_TASK] cal: ua_mv=-7 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:13:06.657+08:00 [2026-11-04 19:13:05][ADC_TASK] meas: seq=1200 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:13:06.668+08:00 [2026-11-04 19:13:05][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:13:16.590+08:00 [2026-11-04 19:13:15][ADC_TASK] frames=249953 idle=0 skip=25 seq=250006 frst_err=0 ring=249928 adc1_rc=0 adc1_err=0 adc1_seq=250005 adc1_gap=76 adc1_hw=0 tgap=48
[RX] 2026-11-04T19:13:16.610+08:00 [2026-11-04 19:13:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:16.610+08:00 [2026-11-04 19:13:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:16.621+08:00 [2026-11-04 19:13:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:16.621+08:00 [2026-11-04 19:13:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:16.631+08:00 [2026-11-04 19:13:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:16.642+08:00 [2026-11-04 19:13:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:13:16.642+08:00 [2026-11-04 19:13:15][ADC_TASK] adc: UA=-48 UB=914 UC=1014 UN=-45 IA=11451 IB=910 IC=1007 IN=1029 T=911
[RX] 2026-11-04T19:13:16.652+08:00 [2026-11-04 19:13:15][ADC_TASK] cal: ua_mv=-7 un_mv=-6 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:13:16.663+08:00 [2026-11-04 19:13:15][ADC_TASK] meas: seq=1250 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:13:16.673+08:00 [2026-11-04 19:13:15][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:13:26.589+08:00 [2026-11-04 19:13:25][ADC_TASK] frames=259951 idle=0 skip=26 seq=260006 frst_err=0 ring=259925 adc1_rc=0 adc1_err=0 adc1_seq=260005 adc1_gap=79 adc1_hw=0 tgap=50
[RX] 2026-11-04T19:13:26.610+08:00 [2026-11-04 19:13:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:26.610+08:00 [2026-11-04 19:13:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:26.620+08:00 [2026-11-04 19:13:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:26.620+08:00 [2026-11-04 19:13:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:26.631+08:00 [2026-11-04 19:13:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:26.641+08:00 [2026-11-04 19:13:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:13:26.641+08:00 [2026-11-04 19:13:25][ADC_TASK] adc: UA=-46 UB=921 UC=1004 UN=-47 IA=11452 IB=929 IC=1020 IN=1037 T=935
[RX] 2026-11-04T19:13:26.652+08:00 [2026-11-04 19:13:25][ADC_TASK] cal: ua_mv=-7 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:13:26.662+08:00 [2026-11-04 19:13:25][ADC_TASK] meas: seq=1300 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:13:26.673+08:00 [2026-11-04 19:13:25][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:13:36.594+08:00 [2026-11-04 19:13:35][ADC_TASK] frames=269949 idle=0 skip=27 seq=270006 frst_err=0 ring=269922 adc1_rc=0 adc1_err=0 adc1_seq=270005 adc1_gap=82 adc1_hw=0 tgap=52
[RX] 2026-11-04T19:13:36.605+08:00 [2026-11-04 19:13:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:36.615+08:00 [2026-11-04 19:13:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:36.615+08:00 [2026-11-04 19:13:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:36.626+08:00 [2026-11-04 19:13:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:36.626+08:00 [2026-11-04 19:13:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:36.636+08:00 [2026-11-04 19:13:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:13:36.647+08:00 [2026-11-04 19:13:35][ADC_TASK] adc: UA=-45 UB=941 UC=1008 UN=-47 IA=11452 IB=953 IC=1030 IN=1043 T=952
[RX] 2026-11-04T19:13:36.657+08:00 [2026-11-04 19:13:35][ADC_TASK] cal: ua_mv=-6 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:13:36.657+08:00 [2026-11-04 19:13:35][ADC_TASK] meas: seq=1350 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:13:36.668+08:00 [2026-11-04 19:13:35][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:13:46.588+08:00 [2026-11-04 19:13:45][ADC_TASK] frames=279947 idle=0 skip=28 seq=280006 frst_err=0 ring=279919 adc1_rc=0 adc1_err=0 adc1_seq=280005 adc1_gap=85 adc1_hw=0 tgap=54
[RX] 2026-11-04T19:13:46.609+08:00 [2026-11-04 19:13:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:46.609+08:00 [2026-11-04 19:13:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:46.620+08:00 [2026-11-04 19:13:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:46.630+08:00 [2026-11-04 19:13:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:46.630+08:00 [2026-11-04 19:13:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:46.641+08:00 [2026-11-04 19:13:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:13:46.641+08:00 [2026-11-04 19:13:45][ADC_TASK] adc: UA=-47 UB=977 UC=1021 UN=-46 IA=11452 IB=977 IC=1028 IN=1047 T=967
[RX] 2026-11-04T19:13:46.651+08:00 [2026-11-04 19:13:45][ADC_TASK] cal: ua_mv=-7 un_mv=-7 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:13:46.662+08:00 [2026-11-04 19:13:45][ADC_TASK] meas: seq=1400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:13:46.672+08:00 [2026-11-04 19:13:45][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:13:56.591+08:00 [2026-11-04 19:13:55][ADC_TASK] frames=289945 idle=0 skip=29 seq=290006 frst_err=0 ring=289916 adc1_rc=0 adc1_err=0 adc1_seq=290005 adc1_gap=88 adc1_hw=0 tgap=56
[RX] 2026-11-04T19:13:56.602+08:00 [2026-11-04 19:13:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:56.612+08:00 [2026-11-04 19:13:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=5 Im=8 rms=9 (x0.1)
[RX] 2026-11-04T19:13:56.612+08:00 [2026-11-04 19:13:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=8 Im=-5 rms=9 (x0.1)
[RX] 2026-11-04T19:13:56.623+08:00 [2026-11-04 19:13:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:56.634+08:00 [2026-11-04 19:13:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:13:56.634+08:00 [2026-11-04 19:13:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:13:56.644+08:00 [2026-11-04 19:13:55][ADC_TASK] adc: UA=5882 UB=986 UC=1030 UN=-7149 IA=11453 IB=968 IC=1024 IN=1045 T=954
[RX] 2026-11-04T19:13:56.654+08:00 [2026-11-04 19:13:55][ADC_TASK] cal: ua_mv=897 un_mv=-1090 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:13:56.665+08:00 [2026-11-04 19:13:55][ADC_TASK] meas: seq=1450 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:13:56.665+08:00 [2026-11-04 19:13:55][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:14:06.588+08:00 [2026-11-04 19:14:05][ADC_TASK] frames=299943 idle=0 skip=30 seq=300006 frst_err=0 ring=299913 adc1_rc=0 adc1_err=0 adc1_seq=300005 adc1_gap=91 adc1_hw=0 tgap=58
[RX] 2026-11-04T19:14:06.609+08:00 [2026-11-04 19:14:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:06.609+08:00 [2026-11-04 19:14:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=6 Im=7 rms=9 (x0.1)
[RX] 2026-11-04T19:14:06.620+08:00 [2026-11-04 19:14:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=7 Im=-6 rms=9 (x0.1)
[RX] 2026-11-04T19:14:06.620+08:00 [2026-11-04 19:14:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:06.631+08:00 [2026-11-04 19:14:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:06.641+08:00 [2026-11-04 19:14:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:14:06.641+08:00 [2026-11-04 19:14:05][ADC_TASK] adc: UA=5159 UB=968 UC=1012 UN=-7703 IA=11452 IB=943 IC=1018 IN=1017 T=933
[RX] 2026-11-04T19:14:06.651+08:00 [2026-11-04 19:14:05][ADC_TASK] cal: ua_mv=787 un_mv=-1175 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:14:06.662+08:00 [2026-11-04 19:14:05][ADC_TASK] meas: seq=1500 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:14:06.672+08:00 [2026-11-04 19:14:05][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:14:16.590+08:00 [2026-11-04 19:14:15][ADC_TASK] frames=309941 idle=0 skip=31 seq=310006 frst_err=0 ring=309910 adc1_rc=0 adc1_err=0 adc1_seq=310005 adc1_gap=94 adc1_hw=0 tgap=60
[RX] 2026-11-04T19:14:16.601+08:00 [2026-11-04 19:14:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:16.611+08:00 [2026-11-04 19:14:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=6 Im=7 rms=9 (x0.1)
[RX] 2026-11-04T19:14:16.611+08:00 [2026-11-04 19:14:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=7 Im=-6 rms=9 (x0.1)
[RX] 2026-11-04T19:14:16.622+08:00 [2026-11-04 19:14:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:16.632+08:00 [2026-11-04 19:14:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:16.643+08:00 [2026-11-04 19:14:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:14:16.643+08:00 [2026-11-04 19:14:15][ADC_TASK] adc: UA=4385 UB=970 UC=1022 UN=-8183 IA=11452 IB=942 IC=1028 IN=1015 T=932
[RX] 2026-11-04T19:14:16.653+08:00 [2026-11-04 19:14:15][ADC_TASK] cal: ua_mv=669 un_mv=-1248 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:14:16.663+08:00 [2026-11-04 19:14:15][ADC_TASK] meas: seq=1550 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:14:16.674+08:00 [2026-11-04 19:14:15][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:14:26.594+08:00 [2026-11-04 19:14:25][ADC_TASK] frames=319939 idle=0 skip=32 seq=320006 frst_err=0 ring=319907 adc1_rc=0 adc1_err=0 adc1_seq=320005 adc1_gap=97 adc1_hw=0 tgap=62
[RX] 2026-11-04T19:14:26.605+08:00 [2026-11-04 19:14:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:26.615+08:00 [2026-11-04 19:14:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=7 Im=6 rms=9 (x0.1)
[RX] 2026-11-04T19:14:26.615+08:00 [2026-11-04 19:14:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=6 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:14:26.626+08:00 [2026-11-04 19:14:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:26.626+08:00 [2026-11-04 19:14:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:26.636+08:00 [2026-11-04 19:14:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:14:26.647+08:00 [2026-11-04 19:14:25][ADC_TASK] adc: UA=3564 UB=950 UC=1012 UN=-8582 IA=11451 IB=923 IC=1013 IN=994 T=915
[RX] 2026-11-04T19:14:26.647+08:00 [2026-11-04 19:14:25][ADC_TASK] cal: ua_mv=543 un_mv=-1309 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:14:26.658+08:00 [2026-11-04 19:14:25][ADC_TASK] meas: seq=1600 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:14:26.668+08:00 [2026-11-04 19:14:25][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:14:36.586+08:00 [2026-11-04 19:14:35][ADC_TASK] frames=329937 idle=0 skip=33 seq=330006 frst_err=0 ring=329904 adc1_rc=0 adc1_err=0 adc1_seq=330005 adc1_gap=100 adc1_hw=0 tgap=64
[RX] 2026-11-04T19:14:36.606+08:00 [2026-11-04 19:14:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:36.606+08:00 [2026-11-04 19:14:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=8 Im=5 rms=9 (x0.1)
[RX] 2026-11-04T19:14:36.617+08:00 [2026-11-04 19:14:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=5 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:14:36.627+08:00 [2026-11-04 19:14:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:36.627+08:00 [2026-11-04 19:14:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:36.638+08:00 [2026-11-04 19:14:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:14:36.648+08:00 [2026-11-04 19:14:35][ADC_TASK] adc: UA=2709 UB=962 UC=1037 UN=-8905 IA=11452 IB=938 IC=1032 IN=1024 T=929
[RX] 2026-11-04T19:14:36.648+08:00 [2026-11-04 19:14:35][ADC_TASK] cal: ua_mv=413 un_mv=-1358 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:14:36.659+08:00 [2026-11-04 19:14:35][ADC_TASK] meas: seq=1650 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:14:36.669+08:00 [2026-11-04 19:14:35][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:14:46.594+08:00 [2026-11-04 19:14:45][ADC_TASK] frames=339935 idle=0 skip=34 seq=340006 frst_err=0 ring=339901 adc1_rc=0 adc1_err=0 adc1_seq=340005 adc1_gap=103 adc1_hw=0 tgap=66
[RX] 2026-11-04T19:14:46.604+08:00 [2026-11-04 19:14:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:46.615+08:00 [2026-11-04 19:14:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=8 Im=4 rms=9 (x0.1)
[RX] 2026-11-04T19:14:46.615+08:00 [2026-11-04 19:14:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=4 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:14:46.625+08:00 [2026-11-04 19:14:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:46.625+08:00 [2026-11-04 19:14:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:46.635+08:00 [2026-11-04 19:14:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:14:46.646+08:00 [2026-11-04 19:14:45][ADC_TASK] adc: UA=1822 UB=952 UC=1034 UN=-9133 IA=11452 IB=927 IC=1018 IN=1026 T=923
[RX] 2026-11-04T19:14:46.656+08:00 [2026-11-04 19:14:45][ADC_TASK] cal: ua_mv=278 un_mv=-1393 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:14:46.667+08:00 [2026-11-04 19:14:45][ADC_TASK] meas: seq=1700 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:14:46.667+08:00 [2026-11-04 19:14:45][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:14:56.590+08:00 [2026-11-04 19:14:55][ADC_TASK] frames=349933 idle=0 skip=35 seq=350006 frst_err=0 ring=349898 adc1_rc=0 adc1_err=0 adc1_seq=350005 adc1_gap=106 adc1_hw=0 tgap=68
[RX] 2026-11-04T19:14:56.601+08:00 [2026-11-04 19:14:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:56.611+08:00 [2026-11-04 19:14:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=4 rms=9 (x0.1)
[RX] 2026-11-04T19:14:56.622+08:00 [2026-11-04 19:14:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=4 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:14:56.622+08:00 [2026-11-04 19:14:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:56.632+08:00 [2026-11-04 19:14:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:14:56.642+08:00 [2026-11-04 19:14:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:14:56.642+08:00 [2026-11-04 19:14:55][ADC_TASK] adc: UA=917 UB=955 UC=1042 UN=-9276 IA=11454 IB=941 IC=1024 IN=1045 T=938
[RX] 2026-11-04T19:14:56.653+08:00 [2026-11-04 19:14:55][ADC_TASK] cal: ua_mv=139 un_mv=-1415 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:14:56.663+08:00 [2026-11-04 19:14:55][ADC_TASK] meas: seq=1750 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:14:56.663+08:00 [2026-11-04 19:14:55][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:15:06.585+08:00 [2026-11-04 19:15:05][ADC_TASK] frames=359931 idle=0 skip=36 seq=360006 frst_err=0 ring=359895 adc1_rc=0 adc1_err=0 adc1_seq=360005 adc1_gap=109 adc1_hw=0 tgap=70
[RX] 2026-11-04T19:15:06.606+08:00 [2026-11-04 19:15:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:06.606+08:00 [2026-11-04 19:15:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=3 rms=9 (x0.1)
[RX] 2026-11-04T19:15:06.617+08:00 [2026-11-04 19:15:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=3 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:15:06.627+08:00 [2026-11-04 19:15:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:06.627+08:00 [2026-11-04 19:15:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:06.638+08:00 [2026-11-04 19:15:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:15:06.638+08:00 [2026-11-04 19:15:05][ADC_TASK] adc: UA=-1 UB=946 UC=1031 UN=-9323 IA=11453 IB=936 IC=1015 IN=1041 T=935
[RX] 2026-11-04T19:15:06.648+08:00 [2026-11-04 19:15:05][ADC_TASK] cal: ua_mv=0 un_mv=-1422 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:15:06.659+08:00 [2026-11-04 19:15:05][ADC_TASK] meas: seq=1800 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:15:06.669+08:00 [2026-11-04 19:15:05][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:15:16.586+08:00 [2026-11-04 19:15:15][ADC_TASK] frames=369929 idle=0 skip=37 seq=370006 frst_err=0 ring=369892 adc1_rc=0 adc1_err=0 adc1_seq=370005 adc1_gap=112 adc1_hw=0 tgap=72
[RX] 2026-11-04T19:15:16.607+08:00 [2026-11-04 19:15:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:16.607+08:00 [2026-11-04 19:15:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=2 rms=9 (x0.1)
[RX] 2026-11-04T19:15:16.617+08:00 [2026-11-04 19:15:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=2 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:15:16.628+08:00 [2026-11-04 19:15:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:16.628+08:00 [2026-11-04 19:15:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:16.638+08:00 [2026-11-04 19:15:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:15:16.638+08:00 [2026-11-04 19:15:15][ADC_TASK] adc: UA=-916 UB=935 UC=1034 UN=-9283 IA=11453 IB=933 IC=1016 IN=1032 T=934
[RX] 2026-11-04T19:15:16.648+08:00 [2026-11-04 19:15:15][ADC_TASK] cal: ua_mv=-139 un_mv=-1416 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:15:16.659+08:00 [2026-11-04 19:15:15][ADC_TASK] meas: seq=1850 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:15:16.669+08:00 [2026-11-04 19:15:15][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:15:26.588+08:00 [2026-11-04 19:15:25][ADC_TASK] frames=379927 idle=0 skip=38 seq=380006 frst_err=0 ring=379889 adc1_rc=0 adc1_err=0 adc1_seq=380005 adc1_gap=115 adc1_hw=0 tgap=74
[RX] 2026-11-04T19:15:26.598+08:00 [2026-11-04 19:15:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:26.609+08:00 [2026-11-04 19:15:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=1 rms=9 (x0.1)
[RX] 2026-11-04T19:15:26.619+08:00 [2026-11-04 19:15:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=1 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:15:26.619+08:00 [2026-11-04 19:15:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:26.629+08:00 [2026-11-04 19:15:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:26.640+08:00 [2026-11-04 19:15:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:15:26.640+08:00 [2026-11-04 19:15:25][ADC_TASK] adc: UA=-1828 UB=932 UC=1007 UN=-9152 IA=11452 IB=933 IC=997 IN=1027 T=931
[RX] 2026-11-04T19:15:26.650+08:00 [2026-11-04 19:15:25][ADC_TASK] cal: ua_mv=-278 un_mv=-1396 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:15:26.661+08:00 [2026-11-04 19:15:25][ADC_TASK] meas: seq=1900 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:15:26.671+08:00 [2026-11-04 19:15:25][ADC_TASK] wave: seg=0 armed=0 src=- pts=320 err=0 ovf=0 drop=0
[RX] 2026-11-04T19:15:33.908+08:00 [2026-11-04 19:15:32] [WAVE][WARN] seg held 804 ms (idx=10)
[RX] 2026-11-04T19:15:36.587+08:00 [2026-11-04 19:15:35][ADC_TASK] frames=389925 idle=0 skip=39 seq=390006 frst_err=0 ring=389886 adc1_rc=0 adc1_err=0 adc1_seq=390005 adc1_gap=118 adc1_hw=0 tgap=76
[RX] 2026-11-04T19:15:36.598+08:00 [2026-11-04 19:15:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:36.608+08:00 [2026-11-04 19:15:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:15:36.619+08:00 [2026-11-04 19:15:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:15:36.619+08:00 [2026-11-04 19:15:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:36.629+08:00 [2026-11-04 19:15:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:36.639+08:00 [2026-11-04 19:15:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:15:36.639+08:00 [2026-11-04 19:15:35][ADC_TASK] adc: UA=-2711 UB=924 UC=998 UN=-8929 IA=11453 IB=923 IC=986 IN=1010 T=921
[RX] 2026-11-04T19:15:36.650+08:00 [2026-11-04 19:15:35][ADC_TASK] cal: ua_mv=-413 un_mv=-1362 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:15:36.660+08:00 [2026-11-04 19:15:35][ADC_TASK] meas: seq=1950 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:15:36.670+08:00 [2026-11-04 19:15:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:15:46.592+08:00 [2026-11-04 19:15:45][ADC_TASK] frames=399923 idle=0 skip=40 seq=400006 frst_err=0 ring=399883 adc1_rc=0 adc1_err=0 adc1_seq=400005 adc1_gap=121 adc1_hw=0 tgap=78
[RX] 2026-11-04T19:15:46.602+08:00 [2026-11-04 19:15:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:46.612+08:00 [2026-11-04 19:15:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:15:46.612+08:00 [2026-11-04 19:15:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:15:46.623+08:00 [2026-11-04 19:15:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:46.623+08:00 [2026-11-04 19:15:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:46.633+08:00 [2026-11-04 19:15:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:15:46.644+08:00 [2026-11-04 19:15:45][ADC_TASK] adc: UA=-3575 UB=932 UC=1000 UN=-8621 IA=11451 IB=930 IC=990 IN=1011 T=931
[RX] 2026-11-04T19:15:46.655+08:00 [2026-11-04 19:15:45][ADC_TASK] cal: ua_mv=-545 un_mv=-1315 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:15:46.665+08:00 [2026-11-04 19:15:45][ADC_TASK] meas: seq=2000 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:15:46.665+08:00 [2026-11-04 19:15:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:15:56.589+08:00 [2026-11-04 19:15:55][ADC_TASK] frames=409921 idle=0 skip=41 seq=410006 frst_err=0 ring=409880 adc1_rc=0 adc1_err=0 adc1_seq=410005 adc1_gap=124 adc1_hw=0 tgap=80
[RX] 2026-11-04T19:15:56.600+08:00 [2026-11-04 19:15:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:56.610+08:00 [2026-11-04 19:15:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=-1 rms=9 (x0.1)
[RX] 2026-11-04T19:15:56.620+08:00 [2026-11-04 19:15:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=-1 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:15:56.620+08:00 [2026-11-04 19:15:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:56.631+08:00 [2026-11-04 19:15:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:15:56.631+08:00 [2026-11-04 19:15:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:15:56.642+08:00 [2026-11-04 19:15:55][ADC_TASK] adc: UA=-4406 UB=936 UC=1000 UN=-8224 IA=11451 IB=930 IC=986 IN=1010 T=922
[RX] 2026-11-04T19:15:56.652+08:00 [2026-11-04 19:15:55][ADC_TASK] cal: ua_mv=-672 un_mv=-1254 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:15:56.663+08:00 [2026-11-04 19:15:55][ADC_TASK] meas: seq=2050 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:15:56.663+08:00 [2026-11-04 19:15:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:16:06.586+08:00 [2026-11-04 19:16:05][ADC_TASK] frames=419919 idle=0 skip=42 seq=420006 frst_err=0 ring=419877 adc1_rc=0 adc1_err=0 adc1_seq=420005 adc1_gap=127 adc1_hw=0 tgap=83
[RX] 2026-11-04T19:16:06.607+08:00 [2026-11-04 19:16:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:06.607+08:00 [2026-11-04 19:16:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=-2 rms=9 (x0.1)
[RX] 2026-11-04T19:16:06.617+08:00 [2026-11-04 19:16:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=-2 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:16:06.617+08:00 [2026-11-04 19:16:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:06.627+08:00 [2026-11-04 19:16:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:06.638+08:00 [2026-11-04 19:16:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:16:06.638+08:00 [2026-11-04 19:16:05][ADC_TASK] adc: UA=-5192 UB=934 UC=1028 UN=-7751 IA=11452 IB=926 IC=983 IN=1006 T=924
[RX] 2026-11-04T19:16:06.649+08:00 [2026-11-04 19:16:05][ADC_TASK] cal: ua_mv=-792 un_mv=-1182 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:16:06.659+08:00 [2026-11-04 19:16:05][ADC_TASK] meas: seq=2100 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:16:06.669+08:00 [2026-11-04 19:16:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:16:16.588+08:00 [2026-11-04 19:16:15][ADC_TASK] frames=429917 idle=0 skip=43 seq=430006 frst_err=0 ring=429874 adc1_rc=0 adc1_err=0 adc1_seq=430005 adc1_gap=130 adc1_hw=0 tgap=85
[RX] 2026-11-04T19:16:16.598+08:00 [2026-11-04 19:16:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:16.609+08:00 [2026-11-04 19:16:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=9 Im=-3 rms=9 (x0.1)
[RX] 2026-11-04T19:16:16.620+08:00 [2026-11-04 19:16:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=-3 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:16:16.620+08:00 [2026-11-04 19:16:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:16.630+08:00 [2026-11-04 19:16:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:16.641+08:00 [2026-11-04 19:16:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:16:16.641+08:00 [2026-11-04 19:16:15][ADC_TASK] adc: UA=-5929 UB=939 UC=1042 UN=-7203 IA=11453 IB=928 IC=990 IN=1011 T=917
[RX] 2026-11-04T19:16:16.651+08:00 [2026-11-04 19:16:15][ADC_TASK] cal: ua_mv=-904 un_mv=-1099 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:16:16.661+08:00 [2026-11-04 19:16:15][ADC_TASK] meas: seq=2150 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:16:16.661+08:00 [2026-11-04 19:16:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:16:26.589+08:00 [2026-11-04 19:16:25][ADC_TASK] frames=439915 idle=0 skip=44 seq=440006 frst_err=0 ring=439871 adc1_rc=0 adc1_err=0 adc1_seq=440005 adc1_gap=133 adc1_hw=0 tgap=88
[RX] 2026-11-04T19:16:26.599+08:00 [2026-11-04 19:16:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:26.610+08:00 [2026-11-04 19:16:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=8 Im=-4 rms=9 (x0.1)
[RX] 2026-11-04T19:16:26.620+08:00 [2026-11-04 19:16:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=-4 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:16:26.620+08:00 [2026-11-04 19:16:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:26.631+08:00 [2026-11-04 19:16:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:26.631+08:00 [2026-11-04 19:16:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:16:26.641+08:00 [2026-11-04 19:16:25][ADC_TASK] adc: UA=-6607 UB=941 UC=1046 UN=-6586 IA=11452 IB=923 IC=994 IN=1018 T=916
[RX] 2026-11-04T19:16:26.652+08:00 [2026-11-04 19:16:25][ADC_TASK] cal: ua_mv=-1008 un_mv=-1004 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:16:26.662+08:00 [2026-11-04 19:16:25][ADC_TASK] meas: seq=2200 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:16:26.673+08:00 [2026-11-04 19:16:25][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:16:36.584+08:00 [2026-11-04 19:16:35][ADC_TASK] frames=449913 idle=0 skip=45 seq=450006 frst_err=0 ring=449868 adc1_rc=0 adc1_err=0 adc1_seq=450005 adc1_gap=136 adc1_hw=0 tgap=91
[RX] 2026-11-04T19:16:36.605+08:00 [2026-11-04 19:16:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:36.605+08:00 [2026-11-04 19:16:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=8 Im=-5 rms=9 (x0.1)
[RX] 2026-11-04T19:16:36.615+08:00 [2026-11-04 19:16:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=-5 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:16:36.626+08:00 [2026-11-04 19:16:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:36.626+08:00 [2026-11-04 19:16:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:36.636+08:00 [2026-11-04 19:16:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:16:36.636+08:00 [2026-11-04 19:16:35][ADC_TASK] adc: UA=-7218 UB=955 UC=1030 UN=-5906 IA=11451 IB=937 IC=1015 IN=1041 T=929
[RX] 2026-11-04T19:16:36.647+08:00 [2026-11-04 19:16:35][ADC_TASK] cal: ua_mv=-1101 un_mv=-901 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:16:36.657+08:00 [2026-11-04 19:16:35][ADC_TASK] meas: seq=2250 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:16:36.668+08:00 [2026-11-04 19:16:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:16:46.590+08:00 [2026-11-04 19:16:45][ADC_TASK] frames=459911 idle=0 skip=46 seq=460006 frst_err=0 ring=459865 adc1_rc=0 adc1_err=0 adc1_seq=460005 adc1_gap=139 adc1_hw=0 tgap=94
[RX] 2026-11-04T19:16:46.600+08:00 [2026-11-04 19:16:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:46.610+08:00 [2026-11-04 19:16:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=7 Im=-6 rms=9 (x0.1)
[RX] 2026-11-04T19:16:46.610+08:00 [2026-11-04 19:16:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=-6 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:16:46.621+08:00 [2026-11-04 19:16:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:46.631+08:00 [2026-11-04 19:16:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:46.631+08:00 [2026-11-04 19:16:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:16:46.642+08:00 [2026-11-04 19:16:45][ADC_TASK] adc: UA=-7762 UB=928 UC=1032 UN=-5165 IA=11451 IB=916 IC=991 IN=1019 T=920
[RX] 2026-11-04T19:16:46.652+08:00 [2026-11-04 19:16:45][ADC_TASK] cal: ua_mv=-1184 un_mv=-788 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:16:46.663+08:00 [2026-11-04 19:16:45][ADC_TASK] meas: seq=2300 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:16:46.663+08:00 [2026-11-04 19:16:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:16:56.589+08:00 [2026-11-04 19:16:55][ADC_TASK] frames=469909 idle=0 skip=47 seq=470006 frst_err=0 ring=469862 adc1_rc=0 adc1_err=0 adc1_seq=470005 adc1_gap=142 adc1_hw=0 tgap=97
[RX] 2026-11-04T19:16:56.599+08:00 [2026-11-04 19:16:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:56.609+08:00 [2026-11-04 19:16:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=7 Im=-6 rms=9 (x0.1)
[RX] 2026-11-04T19:16:56.620+08:00 [2026-11-04 19:16:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=-6 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:16:56.620+08:00 [2026-11-04 19:16:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:56.630+08:00 [2026-11-04 19:16:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:16:56.630+08:00 [2026-11-04 19:16:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:16:56.641+08:00 [2026-11-04 19:16:55][ADC_TASK] adc: UA=-8233 UB=917 UC=1037 UN=-4376 IA=11452 IB=916 IC=989 IN=1013 T=912
[RX] 2026-11-04T19:16:56.652+08:00 [2026-11-04 19:16:55][ADC_TASK] cal: ua_mv=-1256 un_mv=-667 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:16:56.662+08:00 [2026-11-04 19:16:55][ADC_TASK] meas: seq=2350 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:16:56.662+08:00 [2026-11-04 19:16:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:17:06.584+08:00 [2026-11-04 19:17:05][ADC_TASK] frames=479907 idle=0 skip=48 seq=480006 frst_err=0 ring=479859 adc1_rc=0 adc1_err=0 adc1_seq=480005 adc1_gap=145 adc1_hw=0 tgap=100
[RX] 2026-11-04T19:17:06.605+08:00 [2026-11-04 19:17:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:06.605+08:00 [2026-11-04 19:17:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=6 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:17:06.615+08:00 [2026-11-04 19:17:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=-7 Im=-6 rms=9 (x0.1)
[RX] 2026-11-04T19:17:06.615+08:00 [2026-11-04 19:17:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:06.626+08:00 [2026-11-04 19:17:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:06.637+08:00 [2026-11-04 19:17:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:17:06.637+08:00 [2026-11-04 19:17:05][ADC_TASK] adc: UA=-8624 UB=938 UC=1043 UN=-3541 IA=11452 IB=932 IC=1012 IN=1039 T=925
[RX] 2026-11-04T19:17:06.647+08:00 [2026-11-04 19:17:05][ADC_TASK] cal: ua_mv=-1315 un_mv=-540 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:17:06.657+08:00 [2026-11-04 19:17:05][ADC_TASK] meas: seq=2400 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:17:06.668+08:00 [2026-11-04 19:17:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:17:16.589+08:00 [2026-11-04 19:17:15][ADC_TASK] frames=489905 idle=0 skip=49 seq=490006 frst_err=0 ring=489856 adc1_rc=0 adc1_err=0 adc1_seq=490005 adc1_gap=148 adc1_hw=0 tgap=102
[RX] 2026-11-04T19:17:16.599+08:00 [2026-11-04 19:17:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:16.610+08:00 [2026-11-04 19:17:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=5 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:17:16.610+08:00 [2026-11-04 19:17:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=-8 Im=-5 rms=9 (x0.1)
[RX] 2026-11-04T19:17:16.620+08:00 [2026-11-04 19:17:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:16.631+08:00 [2026-11-04 19:17:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:16.631+08:00 [2026-11-04 19:17:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:17:16.641+08:00 [2026-11-04 19:17:15][ADC_TASK] adc: UA=-8926 UB=924 UC=1011 UN=-2681 IA=11451 IB=912 IC=996 IN=1022 T=908
[RX] 2026-11-04T19:17:16.652+08:00 [2026-11-04 19:17:15][ADC_TASK] cal: ua_mv=-1361 un_mv=-409 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:17:16.662+08:00 [2026-11-04 19:17:15][ADC_TASK] meas: seq=2450 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:17:16.662+08:00 [2026-11-04 19:17:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:17:26.589+08:00 [2026-11-04 19:17:25][ADC_TASK] frames=499903 idle=0 skip=50 seq=500006 frst_err=0 ring=499853 adc1_rc=0 adc1_err=0 adc1_seq=500005 adc1_gap=151 adc1_hw=0 tgap=105
[RX] 2026-11-04T19:17:26.599+08:00 [2026-11-04 19:17:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:26.609+08:00 [2026-11-04 19:17:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=4 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:17:26.609+08:00 [2026-11-04 19:17:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=-8 Im=-4 rms=9 (x0.1)
[RX] 2026-11-04T19:17:26.619+08:00 [2026-11-04 19:17:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:26.630+08:00 [2026-11-04 19:17:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:26.630+08:00 [2026-11-04 19:17:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:17:26.641+08:00 [2026-11-04 19:17:25][ADC_TASK] adc: UA=-9140 UB=918 UC=1013 UN=-1797 IA=11451 IB=920 IC=1016 IN=1035 T=928
[RX] 2026-11-04T19:17:26.651+08:00 [2026-11-04 19:17:25][ADC_TASK] cal: ua_mv=-1394 un_mv=-274 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:17:26.661+08:00 [2026-11-04 19:17:25][ADC_TASK] meas: seq=2500 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:17:26.672+08:00 [2026-11-04 19:17:25][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:17:36.583+08:00 [2026-11-04 19:17:35][ADC_TASK] frames=509901 idle=0 skip=51 seq=510006 frst_err=0 ring=509850 adc1_rc=0 adc1_err=0 adc1_seq=510005 adc1_gap=154 adc1_hw=0 tgap=108
[RX] 2026-11-04T19:17:36.604+08:00 [2026-11-04 19:17:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:36.604+08:00 [2026-11-04 19:17:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=3 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:17:36.614+08:00 [2026-11-04 19:17:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=-3 rms=9 (x0.1)
[RX] 2026-11-04T19:17:36.614+08:00 [2026-11-04 19:17:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:36.625+08:00 [2026-11-04 19:17:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:36.635+08:00 [2026-11-04 19:17:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:17:36.635+08:00 [2026-11-04 19:17:35][ADC_TASK] adc: UA=-9268 UB=917 UC=991 UN=-895 IA=11452 IB=932 IC=1015 IN=1021 T=936
[RX] 2026-11-04T19:17:36.646+08:00 [2026-11-04 19:17:35][ADC_TASK] cal: ua_mv=-1414 un_mv=-136 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:17:36.656+08:00 [2026-11-04 19:17:35][ADC_TASK] meas: seq=2550 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:17:36.667+08:00 [2026-11-04 19:17:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:17:46.589+08:00 [2026-11-04 19:17:45][ADC_TASK] frames=519899 idle=0 skip=52 seq=520006 frst_err=0 ring=519847 adc1_rc=0 adc1_err=0 adc1_seq=520005 adc1_gap=157 adc1_hw=0 tgap=111
[RX] 2026-11-04T19:17:46.600+08:00 [2026-11-04 19:17:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:46.610+08:00 [2026-11-04 19:17:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=2 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:17:46.610+08:00 [2026-11-04 19:17:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=-2 rms=9 (x0.1)
[RX] 2026-11-04T19:17:46.620+08:00 [2026-11-04 19:17:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:46.631+08:00 [2026-11-04 19:17:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:46.631+08:00 [2026-11-04 19:17:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:17:46.641+08:00 [2026-11-04 19:17:45][ADC_TASK] adc: UA=-9301 UB=949 UC=1011 UN=23 IA=11452 IB=957 IC=1020 IN=1042 T=952
[RX] 2026-11-04T19:17:46.652+08:00 [2026-11-04 19:17:45][ADC_TASK] cal: ua_mv=-1419 un_mv=3 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:17:46.662+08:00 [2026-11-04 19:17:45][ADC_TASK] meas: seq=2600 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:17:46.662+08:00 [2026-11-04 19:17:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:17:56.584+08:00 [2026-11-04 19:17:55][ADC_TASK] frames=529897 idle=0 skip=53 seq=530006 frst_err=0 ring=529844 adc1_rc=0 adc1_err=0 adc1_seq=530005 adc1_gap=160 adc1_hw=0 tgap=113
[RX] 2026-11-04T19:17:56.595+08:00 [2026-11-04 19:17:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:56.605+08:00 [2026-11-04 19:17:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=2 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:17:56.616+08:00 [2026-11-04 19:17:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=-2 rms=9 (x0.1)
[RX] 2026-11-04T19:17:56.616+08:00 [2026-11-04 19:17:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:56.626+08:00 [2026-11-04 19:17:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:17:56.637+08:00 [2026-11-04 19:17:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:17:56.637+08:00 [2026-11-04 19:17:55][ADC_TASK] adc: UA=-9248 UB=957 UC=995 UN=942 IA=11453 IB=950 IC=996 IN=1016 T=937
[RX] 2026-11-04T19:17:56.647+08:00 [2026-11-04 19:17:55][ADC_TASK] cal: ua_mv=-1411 un_mv=143 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:17:56.658+08:00 [2026-11-04 19:17:55][ADC_TASK] meas: seq=2650 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:17:56.668+08:00 [2026-11-04 19:17:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:18:06.582+08:00 [2026-11-04 19:18:05][ADC_TASK] frames=539895 idle=0 skip=54 seq=540006 frst_err=0 ring=539841 adc1_rc=0 adc1_err=0 adc1_seq=540005 adc1_gap=163 adc1_hw=0 tgap=116
[RX] 2026-11-04T19:18:06.603+08:00 [2026-11-04 19:18:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:06.603+08:00 [2026-11-04 19:18:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=1 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:18:06.613+08:00 [2026-11-04 19:18:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=-1 rms=9 (x0.1)
[RX] 2026-11-04T19:18:06.623+08:00 [2026-11-04 19:18:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:06.623+08:00 [2026-11-04 19:18:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:06.634+08:00 [2026-11-04 19:18:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:18:06.634+08:00 [2026-11-04 19:18:05][ADC_TASK] adc: UA=-9102 UB=971 UC=993 UN=1847 IA=11451 IB=950 IC=1008 IN=1006 T=936
[RX] 2026-11-04T19:18:06.644+08:00 [2026-11-04 19:18:05][ADC_TASK] cal: ua_mv=-1388 un_mv=281 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:18:06.655+08:00 [2026-11-04 19:18:05][ADC_TASK] meas: seq=2700 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:18:06.665+08:00 [2026-11-04 19:18:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:18:16.589+08:00 [2026-11-04 19:18:15][ADC_TASK] frames=549893 idle=0 skip=55 seq=550006 frst_err=0 ring=549838 adc1_rc=0 adc1_err=0 adc1_seq=550005 adc1_gap=166 adc1_hw=0 tgap=118
[RX] 2026-11-04T19:18:16.599+08:00 [2026-11-04 19:18:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:16.609+08:00 [2026-11-04 19:18:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:18:16.609+08:00 [2026-11-04 19:18:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:18:16.620+08:00 [2026-11-04 19:18:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:16.620+08:00 [2026-11-04 19:18:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:16.630+08:00 [2026-11-04 19:18:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:18:16.641+08:00 [2026-11-04 19:18:15][ADC_TASK] adc: UA=-8863 UB=979 UC=1021 UN=2731 IA=11452 IB=953 IC=1030 IN=1027 T=945
[RX] 2026-11-04T19:18:16.651+08:00 [2026-11-04 19:18:15][ADC_TASK] cal: ua_mv=-1352 un_mv=416 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:18:16.661+08:00 [2026-11-04 19:18:15][ADC_TASK] meas: seq=2750 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:18:16.661+08:00 [2026-11-04 19:18:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:18:26.586+08:00 [2026-11-04 19:18:25][ADC_TASK] frames=559891 idle=0 skip=56 seq=560006 frst_err=0 ring=559835 adc1_rc=0 adc1_err=0 adc1_seq=560005 adc1_gap=169 adc1_hw=0 tgap=121
[RX] 2026-11-04T19:18:26.597+08:00 [2026-11-04 19:18:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:26.607+08:00 [2026-11-04 19:18:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:18:26.607+08:00 [2026-11-04 19:18:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:18:26.618+08:00 [2026-11-04 19:18:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:26.628+08:00 [2026-11-04 19:18:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:26.628+08:00 [2026-11-04 19:18:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:18:26.638+08:00 [2026-11-04 19:18:25][ADC_TASK] adc: UA=-8544 UB=975 UC=1032 UN=3589 IA=11451 IB=952 IC=1034 IN=1001 T=939
[RX] 2026-11-04T19:18:26.649+08:00 [2026-11-04 19:18:25][ADC_TASK] cal: ua_mv=-1303 un_mv=547 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:18:26.659+08:00 [2026-11-04 19:18:25][ADC_TASK] meas: seq=2800 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:18:26.659+08:00 [2026-11-04 19:18:25][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:18:36.583+08:00 [2026-11-04 19:18:35][ADC_TASK] frames=569889 idle=0 skip=57 seq=570006 frst_err=0 ring=569832 adc1_rc=0 adc1_err=0 adc1_seq=570005 adc1_gap=172 adc1_hw=0 tgap=124
[RX] 2026-11-04T19:18:36.593+08:00 [2026-11-04 19:18:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:36.604+08:00 [2026-11-04 19:18:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=-1 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:18:36.614+08:00 [2026-11-04 19:18:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=1 rms=9 (x0.1)
[RX] 2026-11-04T19:18:36.614+08:00 [2026-11-04 19:18:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:36.625+08:00 [2026-11-04 19:18:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:36.635+08:00 [2026-11-04 19:18:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:18:36.635+08:00 [2026-11-04 19:18:35][ADC_TASK] adc: UA=-8136 UB=976 UC=1029 UN=4414 IA=11451 IB=939 IC=1038 IN=994 T=919
[RX] 2026-11-04T19:18:36.646+08:00 [2026-11-04 19:18:35][ADC_TASK] cal: ua_mv=-1241 un_mv=673 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:18:36.656+08:00 [2026-11-04 19:18:35][ADC_TASK] meas: seq=2850 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:18:36.667+08:00 [2026-11-04 19:18:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:18:46.585+08:00 [2026-11-04 19:18:45][ADC_TASK] frames=579887 idle=0 skip=58 seq=580006 frst_err=0 ring=579829 adc1_rc=0 adc1_err=0 adc1_seq=580005 adc1_gap=175 adc1_hw=0 tgap=127
[RX] 2026-11-04T19:18:46.595+08:00 [2026-11-04 19:18:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:46.606+08:00 [2026-11-04 19:18:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=-2 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:18:46.616+08:00 [2026-11-04 19:18:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=2 rms=9 (x0.1)
[RX] 2026-11-04T19:18:46.616+08:00 [2026-11-04 19:18:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:46.627+08:00 [2026-11-04 19:18:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:46.627+08:00 [2026-11-04 19:18:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:18:46.637+08:00 [2026-11-04 19:18:45][ADC_TASK] adc: UA=-7649 UB=970 UC=1033 UN=5193 IA=11452 IB=927 IC=1046 IN=1001 T=913
[RX] 2026-11-04T19:18:46.648+08:00 [2026-11-04 19:18:45][ADC_TASK] cal: ua_mv=-1167 un_mv=792 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:18:46.658+08:00 [2026-11-04 19:18:45][ADC_TASK] meas: seq=2900 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:18:46.658+08:00 [2026-11-04 19:18:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:18:56.588+08:00 [2026-11-04 19:18:55][ADC_TASK] frames=589885 idle=0 skip=59 seq=590006 frst_err=0 ring=589826 adc1_rc=0 adc1_err=0 adc1_seq=590005 adc1_gap=178 adc1_hw=0 tgap=130
[RX] 2026-11-04T19:18:56.598+08:00 [2026-11-04 19:18:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:56.598+08:00 [2026-11-04 19:18:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=-3 Im=-9 rms=9 (x0.1)
[RX] 2026-11-04T19:18:56.609+08:00 [2026-11-04 19:18:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=-9 Im=3 rms=9 (x0.1)
[RX] 2026-11-04T19:18:56.619+08:00 [2026-11-04 19:18:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:56.629+08:00 [2026-11-04 19:18:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:18:56.629+08:00 [2026-11-04 19:18:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:18:56.640+08:00 [2026-11-04 19:18:55][ADC_TASK] adc: UA=-7087 UB=950 UC=1031 UN=5918 IA=11451 IB=916 IC=1035 IN=997 T=911
[RX] 2026-11-04T19:18:56.640+08:00 [2026-11-04 19:18:55][ADC_TASK] cal: ua_mv=-1081 un_mv=903 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:18:56.660+08:00 [2026-11-04 19:18:55][ADC_TASK] meas: seq=2950 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:18:56.660+08:00 [2026-11-04 19:18:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:19:06.583+08:00 [2026-11-04 19:19:05][ADC_TASK] frames=599883 idle=0 skip=60 seq=600006 frst_err=0 ring=599823 adc1_rc=0 adc1_err=0 adc1_seq=600005 adc1_gap=181 adc1_hw=0 tgap=133
[RX] 2026-11-04T19:19:06.594+08:00 [2026-11-04 19:19:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:06.604+08:00 [2026-11-04 19:19:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=-4 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:19:06.615+08:00 [2026-11-04 19:19:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=-8 Im=4 rms=9 (x0.1)
[RX] 2026-11-04T19:19:06.615+08:00 [2026-11-04 19:19:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:06.625+08:00 [2026-11-04 19:19:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:06.636+08:00 [2026-11-04 19:19:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:19:06.636+08:00 [2026-11-04 19:19:05][ADC_TASK] adc: UA=-6456 UB=930 UC=1034 UN=6589 IA=11451 IB=912 IC=1013 IN=1012 T=912
[RX] 2026-11-04T19:19:06.646+08:00 [2026-11-04 19:19:05][ADC_TASK] cal: ua_mv=-985 un_mv=1005 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:19:06.657+08:00 [2026-11-04 19:19:05][ADC_TASK] meas: seq=3000 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:19:06.657+08:00 [2026-11-04 19:19:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:19:16.584+08:00 [2026-11-04 19:19:15][ADC_TASK] frames=609881 idle=0 skip=61 seq=610006 frst_err=0 ring=609820 adc1_rc=0 adc1_err=0 adc1_seq=610005 adc1_gap=184 adc1_hw=0 tgap=135
[RX] 2026-11-04T19:19:16.594+08:00 [2026-11-04 19:19:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:16.605+08:00 [2026-11-04 19:19:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=-5 Im=-8 rms=9 (x0.1)
[RX] 2026-11-04T19:19:16.615+08:00 [2026-11-04 19:19:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=-8 Im=5 rms=9 (x0.1)
[RX] 2026-11-04T19:19:16.615+08:00 [2026-11-04 19:19:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:16.626+08:00 [2026-11-04 19:19:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:16.626+08:00 [2026-11-04 19:19:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:19:16.636+08:00 [2026-11-04 19:19:15][ADC_TASK] adc: UA=-5767 UB=916 UC=1017 UN=7187 IA=11451 IB=904 IC=989 IN=1020 T=903
[RX] 2026-11-04T19:19:16.646+08:00 [2026-11-04 19:19:15][ADC_TASK] cal: ua_mv=-879 un_mv=1096 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:19:16.657+08:00 [2026-11-04 19:19:15][ADC_TASK] meas: seq=3050 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:19:16.657+08:00 [2026-11-04 19:19:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:19:26.582+08:00 [2026-11-04 19:19:25][ADC_TASK] frames=619879 idle=0 skip=62 seq=620006 frst_err=0 ring=619817 adc1_rc=0 adc1_err=0 adc1_seq=620005 adc1_gap=187 adc1_hw=0 tgap=138
[RX] 2026-11-04T19:19:26.593+08:00 [2026-11-04 19:19:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:26.603+08:00 [2026-11-04 19:19:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=-6 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:19:26.614+08:00 [2026-11-04 19:19:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=-7 Im=6 rms=9 (x0.1)
[RX] 2026-11-04T19:19:26.614+08:00 [2026-11-04 19:19:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:26.624+08:00 [2026-11-04 19:19:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:26.635+08:00 [2026-11-04 19:19:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:19:26.635+08:00 [2026-11-04 19:19:25][ADC_TASK] adc: UA=-5016 UB=909 UC=1011 UN=7715 IA=11452 IB=909 IC=997 IN=1019 T=914
[RX] 2026-11-04T19:19:26.645+08:00 [2026-11-04 19:19:25][ADC_TASK] cal: ua_mv=-765 un_mv=1177 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:19:26.656+08:00 [2026-11-04 19:19:25][ADC_TASK] meas: seq=3100 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:19:26.656+08:00 [2026-11-04 19:19:25][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:19:36.581+08:00 [2026-11-04 19:19:35][ADC_TASK] frames=629877 idle=0 skip=63 seq=630006 frst_err=0 ring=629814 adc1_rc=0 adc1_err=0 adc1_seq=630005 adc1_gap=190 adc1_hw=0 tgap=141
[RX] 2026-11-04T19:19:36.602+08:00 [2026-11-04 19:19:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:36.602+08:00 [2026-11-04 19:19:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=-7 Im=-7 rms=9 (x0.1)
[RX] 2026-11-04T19:19:36.612+08:00 [2026-11-04 19:19:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=-7 Im=7 rms=9 (x0.1)
[RX] 2026-11-04T19:19:36.623+08:00 [2026-11-04 19:19:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:36.623+08:00 [2026-11-04 19:19:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:36.633+08:00 [2026-11-04 19:19:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:19:36.633+08:00 [2026-11-04 19:19:35][ADC_TASK] adc: UA=-4219 UB=926 UC=1007 UN=8168 IA=11452 IB=937 IC=1022 IN=1043 T=943
[RX] 2026-11-04T19:19:36.644+08:00 [2026-11-04 19:19:35][ADC_TASK] cal: ua_mv=-643 un_mv=1246 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:19:36.654+08:00 [2026-11-04 19:19:35][ADC_TASK] meas: seq=3150 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:19:36.665+08:00 [2026-11-04 19:19:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:19:46.585+08:00 [2026-11-04 19:19:45][ADC_TASK] frames=639875 idle=0 skip=64 seq=640006 frst_err=0 ring=639811 adc1_rc=0 adc1_err=0 adc1_seq=640005 adc1_gap=193 adc1_hw=0 tgap=144
[RX] 2026-11-04T19:19:46.596+08:00 [2026-11-04 19:19:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:46.606+08:00 [2026-11-04 19:19:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=-7 Im=-6 rms=9 (x0.1)
[RX] 2026-11-04T19:19:46.606+08:00 [2026-11-04 19:19:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=-6 Im=7 rms=9 (x0.1)
[RX] 2026-11-04T19:19:46.617+08:00 [2026-11-04 19:19:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:46.627+08:00 [2026-11-04 19:19:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:46.627+08:00 [2026-11-04 19:19:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:19:46.638+08:00 [2026-11-04 19:19:45][ADC_TASK] adc: UA=-3384 UB=933 UC=994 UN=8544 IA=11450 IB=944 IC=1007 IN=1036 T=941
[RX] 2026-11-04T19:19:46.648+08:00 [2026-11-04 19:19:45][ADC_TASK] cal: ua_mv=-516 un_mv=1303 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:19:46.658+08:00 [2026-11-04 19:19:45][ADC_TASK] meas: seq=3200 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:19:46.658+08:00 [2026-11-04 19:19:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:19:56.581+08:00 [2026-11-04 19:19:55][ADC_TASK] frames=649873 idle=0 skip=65 seq=650006 frst_err=0 ring=649808 adc1_rc=0 adc1_err=0 adc1_seq=650005 adc1_gap=196 adc1_hw=0 tgap=147
[RX] 2026-11-04T19:19:56.592+08:00 [2026-11-04 19:19:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:56.602+08:00 [2026-11-04 19:19:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=-8 Im=-5 rms=9 (x0.1)
[RX] 2026-11-04T19:19:56.613+08:00 [2026-11-04 19:19:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=-5 Im=8 rms=9 (x0.1)
[RX] 2026-11-04T19:19:56.613+08:00 [2026-11-04 19:19:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:56.623+08:00 [2026-11-04 19:19:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:19:56.634+08:00 [2026-11-04 19:19:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:19:56.634+08:00 [2026-11-04 19:19:55][ADC_TASK] adc: UA=-2511 UB=959 UC=999 UN=8831 IA=11452 IB=957 IC=1003 IN=1033 T=947
[RX] 2026-11-04T19:19:56.644+08:00 [2026-11-04 19:19:55][ADC_TASK] cal: ua_mv=-383 un_mv=1347 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:19:56.654+08:00 [2026-11-04 19:19:55][ADC_TASK] meas: seq=3250 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:19:56.665+08:00 [2026-11-04 19:19:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:20:06.586+08:00 [2026-11-04 19:20:05][ADC_TASK] frames=659871 idle=0 skip=66 seq=660006 frst_err=0 ring=659805 adc1_rc=0 adc1_err=0 adc1_seq=660005 adc1_gap=199 adc1_hw=0 tgap=150
[RX] 2026-11-04T19:20:06.597+08:00 [2026-11-04 19:20:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:06.597+08:00 [2026-11-04 19:20:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=-8 Im=-4 rms=9 (x0.1)
[RX] 2026-11-04T19:20:06.607+08:00 [2026-11-04 19:20:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=-4 Im=8 rms=9 (x0.1)
[RX] 2026-11-04T19:20:06.618+08:00 [2026-11-04 19:20:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:06.628+08:00 [2026-11-04 19:20:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:06.628+08:00 [2026-11-04 19:20:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:20:06.638+08:00 [2026-11-04 19:20:05][ADC_TASK] adc: UA=-1622 UB=970 UC=995 UN=9029 IA=11452 IB=950 IC=1007 IN=1030 T=939
[RX] 2026-11-04T19:20:06.638+08:00 [2026-11-04 19:20:05][ADC_TASK] cal: ua_mv=-247 un_mv=1377 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:20:06.659+08:00 [2026-11-04 19:20:05][ADC_TASK] meas: seq=3300 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:20:06.659+08:00 [2026-11-04 19:20:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:20:16.576+08:00 [2026-11-04 19:20:15][ADC_TASK] frames=669869 idle=0 skip=67 seq=670006 frst_err=0 ring=669802 adc1_rc=0 adc1_err=0 adc1_seq=670005 adc1_gap=202 adc1_hw=0 tgap=152
[RX] 2026-11-04T19:20:16.597+08:00 [2026-11-04 19:20:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:16.597+08:00 [2026-11-04 19:20:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=-3 rms=9 (x0.1)
[RX] 2026-11-04T19:20:16.608+08:00 [2026-11-04 19:20:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=-3 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:20:16.618+08:00 [2026-11-04 19:20:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:16.618+08:00 [2026-11-04 19:20:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:16.629+08:00 [2026-11-04 19:20:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:20:16.639+08:00 [2026-11-04 19:20:15][ADC_TASK] adc: UA=-710 UB=977 UC=999 UN=9138 IA=11452 IB=955 IC=1028 IN=1038 T=942
[RX] 2026-11-04T19:20:16.639+08:00 [2026-11-04 19:20:15][ADC_TASK] cal: ua_mv=-108 un_mv=1394 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:20:16.649+08:00 [2026-11-04 19:20:15][ADC_TASK] meas: seq=3350 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:20:16.660+08:00 [2026-11-04 19:20:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:20:26.581+08:00 [2026-11-04 19:20:25][ADC_TASK] frames=679867 idle=0 skip=68 seq=680006 frst_err=0 ring=679799 adc1_rc=0 adc1_err=0 adc1_seq=680005 adc1_gap=205 adc1_hw=0 tgap=155
[RX] 2026-11-04T19:20:26.592+08:00 [2026-11-04 19:20:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:26.603+08:00 [2026-11-04 19:20:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=-2 rms=9 (x0.1)
[RX] 2026-11-04T19:20:26.613+08:00 [2026-11-04 19:20:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=-2 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:20:26.613+08:00 [2026-11-04 19:20:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:26.624+08:00 [2026-11-04 19:20:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:26.624+08:00 [2026-11-04 19:20:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:20:26.634+08:00 [2026-11-04 19:20:25][ADC_TASK] adc: UA=203 UB=978 UC=1024 UN=9154 IA=11452 IB=953 IC=1038 IN=1012 T=943
[RX] 2026-11-04T19:20:26.645+08:00 [2026-11-04 19:20:25][ADC_TASK] cal: ua_mv=30 un_mv=1396 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:20:26.655+08:00 [2026-11-04 19:20:25][ADC_TASK] meas: seq=3400 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:20:26.655+08:00 [2026-11-04 19:20:25][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:20:36.578+08:00 [2026-11-04 19:20:35][ADC_TASK] frames=689865 idle=0 skip=69 seq=690006 frst_err=0 ring=689796 adc1_rc=0 adc1_err=0 adc1_seq=690005 adc1_gap=208 adc1_hw=0 tgap=158
[RX] 2026-11-04T19:20:36.599+08:00 [2026-11-04 19:20:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:36.599+08:00 [2026-11-04 19:20:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=-1 rms=9 (x0.1)
[RX] 2026-11-04T19:20:36.609+08:00 [2026-11-04 19:20:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=-1 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:20:36.609+08:00 [2026-11-04 19:20:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:36.620+08:00 [2026-11-04 19:20:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:36.630+08:00 [2026-11-04 19:20:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:20:36.630+08:00 [2026-11-04 19:20:35][ADC_TASK] adc: UA=1125 UB=978 UC=1032 UN=9086 IA=11452 IB=953 IC=1040 IN=999 T=938
[RX] 2026-11-04T19:20:36.641+08:00 [2026-11-04 19:20:35][ADC_TASK] cal: ua_mv=171 un_mv=1386 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:20:36.652+08:00 [2026-11-04 19:20:35][ADC_TASK] meas: seq=3450 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:20:36.662+08:00 [2026-11-04 19:20:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:20:46.578+08:00 [2026-11-04 19:20:45][ADC_TASK] frames=699863 idle=0 skip=70 seq=700006 frst_err=0 ring=699793 adc1_rc=0 adc1_err=0 adc1_seq=700005 adc1_gap=211 adc1_hw=0 tgap=161
[RX] 2026-11-04T19:20:46.599+08:00 [2026-11-04 19:20:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:46.599+08:00 [2026-11-04 19:20:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:20:46.609+08:00 [2026-11-04 19:20:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:20:46.609+08:00 [2026-11-04 19:20:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:46.620+08:00 [2026-11-04 19:20:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:46.630+08:00 [2026-11-04 19:20:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:20:46.630+08:00 [2026-11-04 19:20:45][ADC_TASK] adc: UA=2031 UB=974 UC=1028 UN=8919 IA=11452 IB=940 IC=1040 IN=995 T=923
[RX] 2026-11-04T19:20:46.641+08:00 [2026-11-04 19:20:45][ADC_TASK] cal: ua_mv=309 un_mv=1360 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:20:46.651+08:00 [2026-11-04 19:20:45][ADC_TASK] meas: seq=3500 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:20:46.661+08:00 [2026-11-04 19:20:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:20:56.583+08:00 [2026-11-04 19:20:55][ADC_TASK] frames=709861 idle=0 skip=71 seq=710006 frst_err=0 ring=709790 adc1_rc=0 adc1_err=0 adc1_seq=710005 adc1_gap=214 adc1_hw=0 tgap=164
[RX] 2026-11-04T19:20:56.593+08:00 [2026-11-04 19:20:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:56.603+08:00 [2026-11-04 19:20:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=0 rms=9 (x0.1)
[RX] 2026-11-04T19:20:56.614+08:00 [2026-11-04 19:20:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:20:56.614+08:00 [2026-11-04 19:20:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:56.624+08:00 [2026-11-04 19:20:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:20:56.624+08:00 [2026-11-04 19:20:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:20:56.635+08:00 [2026-11-04 19:20:55][ADC_TASK] adc: UA=2918 UB=967 UC=1027 UN=8667 IA=11451 IB=931 IC=1040 IN=995 T=922
[RX] 2026-11-04T19:20:56.645+08:00 [2026-11-04 19:20:55][ADC_TASK] cal: ua_mv=445 un_mv=1322 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:20:56.655+08:00 [2026-11-04 19:20:55][ADC_TASK] meas: seq=3550 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:20:56.655+08:00 [2026-11-04 19:20:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:21:06.585+08:00 [2026-11-04 19:21:05][ADC_TASK] frames=719859 idle=0 skip=72 seq=720006 frst_err=0 ring=719787 adc1_rc=0 adc1_err=0 adc1_seq=720005 adc1_gap=217 adc1_hw=0 tgap=167
[RX] 2026-11-04T19:21:06.595+08:00 [2026-11-04 19:21:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:06.595+08:00 [2026-11-04 19:21:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=1 rms=9 (x0.1)
[RX] 2026-11-04T19:21:06.605+08:00 [2026-11-04 19:21:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=1 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:21:06.616+08:00 [2026-11-04 19:21:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:06.616+08:00 [2026-11-04 19:21:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:06.626+08:00 [2026-11-04 19:21:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:21:06.636+08:00 [2026-11-04 19:21:05][ADC_TASK] adc: UA=3771 UB=956 UC=1028 UN=8327 IA=11451 IB=934 IC=1034 IN=997 T=933
[RX] 2026-11-04T19:21:06.636+08:00 [2026-11-04 19:21:05][ADC_TASK] cal: ua_mv=575 un_mv=1270 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:21:06.657+08:00 [2026-11-04 19:21:05][ADC_TASK] meas: seq=3600 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:21:06.657+08:00 [2026-11-04 19:21:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:21:16.580+08:00 [2026-11-04 19:21:15][ADC_TASK] frames=729857 idle=0 skip=73 seq=730006 frst_err=0 ring=729784 adc1_rc=0 adc1_err=0 adc1_seq=730005 adc1_gap=220 adc1_hw=0 tgap=169
[RX] 2026-11-04T19:21:16.591+08:00 [2026-11-04 19:21:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:16.601+08:00 [2026-11-04 19:21:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=-9 Im=2 rms=9 (x0.1)
[RX] 2026-11-04T19:21:16.611+08:00 [2026-11-04 19:21:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=2 Im=9 rms=9 (x0.1)
[RX] 2026-11-04T19:21:16.611+08:00 [2026-11-04 19:21:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:16.622+08:00 [2026-11-04 19:21:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:16.632+08:00 [2026-11-04 19:21:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:21:16.632+08:00 [2026-11-04 19:21:15][ADC_TASK] adc: UA=4586 UB=945 UC=1021 UN=7902 IA=11452 IB=933 IC=1015 IN=1008 T=927
[RX] 2026-11-04T19:21:16.643+08:00 [2026-11-04 19:21:15][ADC_TASK] cal: ua_mv=699 un_mv=1205 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:21:16.653+08:00 [2026-11-04 19:21:15][ADC_TASK] meas: seq=3650 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:21:16.653+08:00 [2026-11-04 19:21:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:21:26.579+08:00 [2026-11-04 19:21:25][ADC_TASK] frames=739855 idle=0 skip=74 seq=740006 frst_err=0 ring=739781 adc1_rc=0 adc1_err=0 adc1_seq=740005 adc1_gap=223 adc1_hw=0 tgap=172
[RX] 2026-11-04T19:21:26.590+08:00 [2026-11-04 19:21:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:26.601+08:00 [2026-11-04 19:21:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:26.611+08:00 [2026-11-04 19:21:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:26.611+08:00 [2026-11-04 19:21:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:26.621+08:00 [2026-11-04 19:21:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:26.632+08:00 [2026-11-04 19:21:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:21:26.632+08:00 [2026-11-04 19:21:25][ADC_TASK] adc: UA=9106 UB=953 UC=1048 UN=-104 IA=11451 IB=946 IC=1018 IN=1037 T=940
[RX] 2026-11-04T19:21:26.643+08:00 [2026-11-04 19:21:25][ADC_TASK] cal: ua_mv=1389 un_mv=-15 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:21:26.653+08:00 [2026-11-04 19:21:25][ADC_TASK] meas: seq=3700 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:21:26.653+08:00 [2026-11-04 19:21:25][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:21:36.581+08:00 [2026-11-04 19:21:35][ADC_TASK] frames=749853 idle=0 skip=75 seq=750006 frst_err=0 ring=749778 adc1_rc=0 adc1_err=0 adc1_seq=750005 adc1_gap=226 adc1_hw=0 tgap=174
[RX] 2026-11-04T19:21:36.591+08:00 [2026-11-04 19:21:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:36.602+08:00 [2026-11-04 19:21:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:36.612+08:00 [2026-11-04 19:21:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:36.612+08:00 [2026-11-04 19:21:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:36.623+08:00 [2026-11-04 19:21:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:36.623+08:00 [2026-11-04 19:21:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:21:36.633+08:00 [2026-11-04 19:21:35][ADC_TASK] adc: UA=-1490 UB=948 UC=1002 UN=-92 IA=11451 IB=934 IC=1004 IN=1032 T=926
[RX] 2026-11-04T19:21:36.644+08:00 [2026-11-04 19:21:35][ADC_TASK] cal: ua_mv=-227 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:21:36.654+08:00 [2026-11-04 19:21:35][ADC_TASK] meas: seq=3750 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:21:36.654+08:00 [2026-11-04 19:21:35][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:21:46.579+08:00 [2026-11-04 19:21:45][ADC_TASK] frames=759851 idle=0 skip=76 seq=760006 frst_err=0 ring=759775 adc1_rc=0 adc1_err=0 adc1_seq=760005 adc1_gap=229 adc1_hw=0 tgap=176
[RX] 2026-11-04T19:21:46.589+08:00 [2026-11-04 19:21:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:46.600+08:00 [2026-11-04 19:21:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:46.610+08:00 [2026-11-04 19:21:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:46.610+08:00 [2026-11-04 19:21:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:46.621+08:00 [2026-11-04 19:21:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:46.631+08:00 [2026-11-04 19:21:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:21:46.631+08:00 [2026-11-04 19:21:45][ADC_TASK] adc: UA=-1453 UB=956 UC=998 UN=-91 IA=11452 IB=941 IC=1011 IN=1033 T=942
[RX] 2026-11-04T19:21:46.642+08:00 [2026-11-04 19:21:45][ADC_TASK] cal: ua_mv=-221 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:21:46.652+08:00 [2026-11-04 19:21:45][ADC_TASK] meas: seq=3800 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:21:46.652+08:00 [2026-11-04 19:21:45][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:21:56.575+08:00 [2026-11-04 19:21:55][ADC_TASK] frames=769849 idle=0 skip=77 seq=770006 frst_err=0 ring=769772 adc1_rc=0 adc1_err=0 adc1_seq=770005 adc1_gap=232 adc1_hw=0 tgap=178
[RX] 2026-11-04T19:21:56.596+08:00 [2026-11-04 19:21:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:56.596+08:00 [2026-11-04 19:21:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:56.607+08:00 [2026-11-04 19:21:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:56.617+08:00 [2026-11-04 19:21:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:56.617+08:00 [2026-11-04 19:21:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:21:56.628+08:00 [2026-11-04 19:21:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:21:56.628+08:00 [2026-11-04 19:21:55][ADC_TASK] adc: UA=-1415 UB=977 UC=1005 UN=-88 IA=11452 IB=966 IC=1029 IN=1025 T=954
[RX] 2026-11-04T19:21:56.638+08:00 [2026-11-04 19:21:55][ADC_TASK] cal: ua_mv=-215 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:21:56.648+08:00 [2026-11-04 19:21:55][ADC_TASK] meas: seq=3850 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:21:56.659+08:00 [2026-11-04 19:21:55][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:22:06.582+08:00 [2026-11-04 19:22:05][ADC_TASK] frames=779847 idle=0 skip=78 seq=780006 frst_err=0 ring=779769 adc1_rc=0 adc1_err=0 adc1_seq=780005 adc1_gap=235 adc1_hw=0 tgap=180
[RX] 2026-11-04T19:22:06.593+08:00 [2026-11-04 19:22:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:06.603+08:00 [2026-11-04 19:22:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:06.603+08:00 [2026-11-04 19:22:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:06.613+08:00 [2026-11-04 19:22:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:06.613+08:00 [2026-11-04 19:22:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:06.623+08:00 [2026-11-04 19:22:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:22:06.634+08:00 [2026-11-04 19:22:05][ADC_TASK] adc: UA=-1379 UB=977 UC=1022 UN=-90 IA=11452 IB=953 IC=1027 IN=998 T=942
[RX] 2026-11-04T19:22:06.644+08:00 [2026-11-04 19:22:05][ADC_TASK] cal: ua_mv=-210 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:22:06.655+08:00 [2026-11-04 19:22:05][ADC_TASK] meas: seq=3900 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:22:06.655+08:00 [2026-11-04 19:22:05][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:22:16.583+08:00 [2026-11-04 19:22:15][ADC_TASK] frames=789845 idle=0 skip=79 seq=790006 frst_err=0 ring=789766 adc1_rc=0 adc1_err=0 adc1_seq=790005 adc1_gap=238 adc1_hw=0 tgap=182
[RX] 2026-11-04T19:22:16.593+08:00 [2026-11-04 19:22:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:16.593+08:00 [2026-11-04 19:22:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:16.604+08:00 [2026-11-04 19:22:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:16.614+08:00 [2026-11-04 19:22:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:16.614+08:00 [2026-11-04 19:22:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:16.625+08:00 [2026-11-04 19:22:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:22:16.635+08:00 [2026-11-04 19:22:15][ADC_TASK] adc: UA=-1343 UB=980 UC=1021 UN=-88 IA=11451 IB=940 IC=1034 IN=989 T=918
[RX] 2026-11-04T19:22:16.635+08:00 [2026-11-04 19:22:15][ADC_TASK] cal: ua_mv=-204 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:22:16.656+08:00 [2026-11-04 19:22:15][ADC_TASK] meas: seq=3950 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:22:16.656+08:00 [2026-11-04 19:22:15][ADC_TASK] wave: seg=1 armed=0 src=M pts=320 err=0 ovf=0 drop=6632
[RX] 2026-11-04T19:22:26.583+08:00 [2026-11-04 19:22:25][ADC_TASK] frames=799843 idle=0 skip=80 seq=800006 frst_err=0 ring=799763 adc1_rc=0 adc1_err=0 adc1_seq=800005 adc1_gap=241 adc1_hw=0 tgap=184
[RX] 2026-11-04T19:22:26.593+08:00 [2026-11-04 19:22:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:26.593+08:00 [2026-11-04 19:22:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:26.604+08:00 [2026-11-04 19:22:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:26.614+08:00 [2026-11-04 19:22:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:26.614+08:00 [2026-11-04 19:22:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:26.624+08:00 [2026-11-04 19:22:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:22:26.635+08:00 [2026-11-04 19:22:25][ADC_TASK] adc: UA=-1307 UB=979 UC=1035 UN=-90 IA=11453 IB=933 IC=1055 IN=1005 T=916
[RX] 2026-11-04T19:22:26.635+08:00 [2026-11-04 19:22:25][ADC_TASK] cal: ua_mv=-199 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:22:26.655+08:00 [2026-11-04 19:22:25][ADC_TASK] meas: seq=4000 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:22:26.655+08:00 [2026-11-04 19:22:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:22:36.581+08:00 [2026-11-04 19:22:35][ADC_TASK] frames=809841 idle=0 skip=81 seq=810006 frst_err=0 ring=809760 adc1_rc=0 adc1_err=0 adc1_seq=810005 adc1_gap=244 adc1_hw=0 tgap=186
[RX] 2026-11-04T19:22:36.591+08:00 [2026-11-04 19:22:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:36.602+08:00 [2026-11-04 19:22:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:36.602+08:00 [2026-11-04 19:22:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:36.612+08:00 [2026-11-04 19:22:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:36.612+08:00 [2026-11-04 19:22:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:36.623+08:00 [2026-11-04 19:22:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:22:36.633+08:00 [2026-11-04 19:22:35][ADC_TASK] adc: UA=-1271 UB=936 UC=1013 UN=-90 IA=11451 IB=908 IC=1019 IN=987 T=899
[RX] 2026-11-04T19:22:36.644+08:00 [2026-11-04 19:22:35][ADC_TASK] cal: ua_mv=-193 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:22:36.654+08:00 [2026-11-04 19:22:35][ADC_TASK] meas: seq=4050 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:22:36.654+08:00 [2026-11-04 19:22:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:22:46.579+08:00 [2026-11-04 19:22:45][ADC_TASK] frames=819839 idle=0 skip=82 seq=820006 frst_err=0 ring=819757 adc1_rc=0 adc1_err=0 adc1_seq=820005 adc1_gap=247 adc1_hw=0 tgap=188
[RX] 2026-11-04T19:22:46.589+08:00 [2026-11-04 19:22:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:46.600+08:00 [2026-11-04 19:22:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:46.610+08:00 [2026-11-04 19:22:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:46.610+08:00 [2026-11-04 19:22:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:46.621+08:00 [2026-11-04 19:22:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:46.621+08:00 [2026-11-04 19:22:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:22:46.631+08:00 [2026-11-04 19:22:45][ADC_TASK] adc: UA=-1234 UB=940 UC=1045 UN=-91 IA=11452 IB=925 IC=1021 IN=1026 T=919
[RX] 2026-11-04T19:22:46.642+08:00 [2026-11-04 19:22:45][ADC_TASK] cal: ua_mv=-188 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:22:46.652+08:00 [2026-11-04 19:22:45][ADC_TASK] meas: seq=4100 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:22:46.652+08:00 [2026-11-04 19:22:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:22:56.573+08:00 [2026-11-04 19:22:55][ADC_TASK] frames=829837 idle=0 skip=83 seq=830006 frst_err=0 ring=829754 adc1_rc=0 adc1_err=0 adc1_seq=830005 adc1_gap=250 adc1_hw=0 tgap=190
[RX] 2026-11-04T19:22:56.593+08:00 [2026-11-04 19:22:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:56.593+08:00 [2026-11-04 19:22:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:56.604+08:00 [2026-11-04 19:22:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:56.614+08:00 [2026-11-04 19:22:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:56.614+08:00 [2026-11-04 19:22:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:22:56.625+08:00 [2026-11-04 19:22:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:22:56.635+08:00 [2026-11-04 19:22:55][ADC_TASK] adc: UA=-1197 UB=919 UC=1032 UN=-90 IA=11451 IB=914 IC=1002 IN=1024 T=900
[RX] 2026-11-04T19:22:56.635+08:00 [2026-11-04 19:22:55][ADC_TASK] cal: ua_mv=-182 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:22:56.646+08:00 [2026-11-04 19:22:55][ADC_TASK] meas: seq=4150 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:22:56.656+08:00 [2026-11-04 19:22:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:23:06.580+08:00 [2026-11-04 19:23:05][ADC_TASK] frames=839835 idle=0 skip=84 seq=840006 frst_err=0 ring=839751 adc1_rc=0 adc1_err=0 adc1_seq=840005 adc1_gap=253 adc1_hw=0 tgap=192
[RX] 2026-11-04T19:23:06.590+08:00 [2026-11-04 19:23:05][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:06.600+08:00 [2026-11-04 19:23:05][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:06.600+08:00 [2026-11-04 19:23:05][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:06.611+08:00 [2026-11-04 19:23:05][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:06.621+08:00 [2026-11-04 19:23:05][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:06.621+08:00 [2026-11-04 19:23:05][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:23:06.632+08:00 [2026-11-04 19:23:05][ADC_TASK] adc: UA=-1162 UB=915 UC=1009 UN=-90 IA=11453 IB=914 IC=1019 IN=1035 T=908
[RX] 2026-11-04T19:23:06.642+08:00 [2026-11-04 19:23:05][ADC_TASK] cal: ua_mv=-177 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:23:06.653+08:00 [2026-11-04 19:23:05][ADC_TASK] meas: seq=4200 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:23:06.653+08:00 [2026-11-04 19:23:05][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:23:16.581+08:00 [2026-11-04 19:23:15][ADC_TASK] frames=849833 idle=0 skip=85 seq=850006 frst_err=0 ring=849748 adc1_rc=0 adc1_err=0 adc1_seq=850005 adc1_gap=256 adc1_hw=0 tgap=194
[RX] 2026-11-04T19:23:16.592+08:00 [2026-11-04 19:23:15][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:16.592+08:00 [2026-11-04 19:23:15][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:16.602+08:00 [2026-11-04 19:23:15][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:16.612+08:00 [2026-11-04 19:23:15][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:16.612+08:00 [2026-11-04 19:23:15][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:16.622+08:00 [2026-11-04 19:23:15][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:23:16.633+08:00 [2026-11-04 19:23:15][ADC_TASK] adc: UA=-1123 UB=912 UC=1015 UN=-94 IA=11452 IB=915 IC=1030 IN=1019 T=923
[RX] 2026-11-04T19:23:16.643+08:00 [2026-11-04 19:23:15][ADC_TASK] cal: ua_mv=-171 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:23:16.654+08:00 [2026-11-04 19:23:15][ADC_TASK] meas: seq=4250 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:23:16.654+08:00 [2026-11-04 19:23:15][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:23:26.574+08:00 [2026-11-04 19:23:25][ADC_TASK] frames=859831 idle=0 skip=86 seq=860006 frst_err=0 ring=859745 adc1_rc=0 adc1_err=0 adc1_seq=860005 adc1_gap=259 adc1_hw=0 tgap=196
[RX] 2026-11-04T19:23:26.595+08:00 [2026-11-04 19:23:25][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:26.595+08:00 [2026-11-04 19:23:25][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:26.605+08:00 [2026-11-04 19:23:25][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:26.605+08:00 [2026-11-04 19:23:25][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:26.615+08:00 [2026-11-04 19:23:25][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:26.626+08:00 [2026-11-04 19:23:25][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:23:26.626+08:00 [2026-11-04 19:23:25][ADC_TASK] adc: UA=-1084 UB=912 UC=1033 UN=-91 IA=11452 IB=929 IC=1035 IN=1003 T=931
[RX] 2026-11-04T19:23:26.637+08:00 [2026-11-04 19:23:25][ADC_TASK] cal: ua_mv=-165 un_mv=-13 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:23:26.647+08:00 [2026-11-04 19:23:25][ADC_TASK] meas: seq=4300 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:23:26.658+08:00 [2026-11-04 19:23:25][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:23:36.573+08:00 [2026-11-04 19:23:35][ADC_TASK] frames=869829 idle=0 skip=87 seq=870006 frst_err=0 ring=869742 adc1_rc=0 adc1_err=0 adc1_seq=870005 adc1_gap=262 adc1_hw=0 tgap=198
[RX] 2026-11-04T19:23:36.594+08:00 [2026-11-04 19:23:35][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:36.594+08:00 [2026-11-04 19:23:35][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:36.604+08:00 [2026-11-04 19:23:35][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:36.615+08:00 [2026-11-04 19:23:35][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:36.615+08:00 [2026-11-04 19:23:35][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:36.625+08:00 [2026-11-04 19:23:35][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:23:36.625+08:00 [2026-11-04 19:23:35][ADC_TASK] adc: UA=-1052 UB=911 UC=1018 UN=-94 IA=11452 IB=916 IC=1027 IN=986 T=910
[RX] 2026-11-04T19:23:36.636+08:00 [2026-11-04 19:23:35][ADC_TASK] cal: ua_mv=-160 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:23:36.646+08:00 [2026-11-04 19:23:35][ADC_TASK] meas: seq=4350 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:23:36.657+08:00 [2026-11-04 19:23:35][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:23:46.572+08:00 [2026-11-04 19:23:45][ADC_TASK] frames=879827 idle=0 skip=88 seq=880006 frst_err=0 ring=879739 adc1_rc=0 adc1_err=0 adc1_seq=880005 adc1_gap=265 adc1_hw=0 tgap=200
[RX] 2026-11-04T19:23:46.593+08:00 [2026-11-04 19:23:45][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:46.593+08:00 [2026-11-04 19:23:45][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:46.603+08:00 [2026-11-04 19:23:45][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:46.614+08:00 [2026-11-04 19:23:45][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:46.614+08:00 [2026-11-04 19:23:45][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:46.624+08:00 [2026-11-04 19:23:45][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:23:46.624+08:00 [2026-11-04 19:23:45][ADC_TASK] adc: UA=-1015 UB=922 UC=1027 UN=-92 IA=11452 IB=924 IC=1040 IN=996 T=930
[RX] 2026-11-04T19:23:46.635+08:00 [2026-11-04 19:23:45][ADC_TASK] cal: ua_mv=-154 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:23:46.645+08:00 [2026-11-04 19:23:45][ADC_TASK] meas: seq=4400 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:23:46.655+08:00 [2026-11-04 19:23:45][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
[RX] 2026-11-04T19:23:56.578+08:00 [2026-11-04 19:23:55][ADC_TASK] frames=889825 idle=0 skip=89 seq=890006 frst_err=0 ring=889736 adc1_rc=0 adc1_err=0 adc1_seq=890005 adc1_gap=268 adc1_hw=0 tgap=202
[RX] 2026-11-04T19:23:56.589+08:00 [2026-11-04 19:23:55][ADC_TASK] phasor(hb) C01-IA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:56.599+08:00 [2026-11-04 19:23:55][ADC_TASK] phasor(hb) C02-UA-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:56.599+08:00 [2026-11-04 19:23:55][ADC_TASK] phasor(hb) C03-UN-F1: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:56.609+08:00 [2026-11-04 19:23:55][ADC_TASK] phasor(hb) C04-IA-F2: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:56.620+08:00 [2026-11-04 19:23:55][ADC_TASK] phasor(hb) C05-IA-F5: Re=0 Im=0 rms=0 (x0.1)
[RX] 2026-11-04T19:23:56.620+08:00 [2026-11-04 19:23:55][ADC_TASK] freq(hb): f=0 valid=0 (x0.01)
[RX] 2026-11-04T19:23:56.630+08:00 [2026-11-04 19:23:55][ADC_TASK] adc: UA=-979 UB=916 UC=1012 UN=-93 IA=11451 IB=924 IC=1028 IN=983 T=924
[RX] 2026-11-04T19:23:56.640+08:00 [2026-11-04 19:23:55][ADC_TASK] cal: ua_mv=-149 un_mv=-14 ia_mv=1747 vl=0x00 cl=0xe6 rail=0x000 edge=0x000
[RX] 2026-11-04T19:23:56.651+08:00 [2026-11-04 19:23:55][ADC_TASK] meas: seq=4450 v=1 ua_mv=79 un_mv=0 ia_mv=0 f=0 fv=0
[RX] 2026-11-04T19:23:56.651+08:00 [2026-11-04 19:23:55][ADC_TASK] wave: seg=2 armed=0 src=M pts=320 err=0 ovf=0 drop=7128
@@ -0,0 +1,92 @@
# WAVE09(相位轮 0°/90°,2026-11-04 19:15:32 触发)wave_read.py 输出全套
## dump
FILE : WAVE09.BIN (5250 bytes)
magic : 57 52 01 ('WR' + version byte 1)
ver : 1
fs_hz : 1000
pts_per_cycle : 20
ch_count : 8
pts_per_ch : 320 (320 ms at 1000 Hz)
pre_pts : 160
trig_src : 1 (MANUAL)
trig_elm : 0
t0_ms : 386333 (ms since power-on)
wall : 1793819731 (epoch s)
eng : 0 (pin-domain)
flags_seq : 0x00 VALID bits: (none)
clip_mask : 0x00E6 [UB UC IB IC IN]
frame_seq : 386258
CRC16(hdr) : 0xB00D OK covers [0,126), low byte first
CRC16(data) : 0xF8AA OK covers 5120 bytes, low byte first
-- channel table (8 entries x 12 B) --
IDX SEM NAME SRC BITS AVAIL K_Q16 B_CODES LSB_uV FLAG
0 0 UA TPAFE5160 16 0x00 10000000 0 152.587891 .
1 1 UB STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
2 2 UC STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
3 3 UN TPAFE5160 16 0x00 10000000 0 152.587891 .
4 4 IA TPAFE5160 16 0x00 10000000 0 152.587891 .
5 5 IB STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
6 6 IC STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
7 7 IN STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
## fft --ch 0 --all
unit: mV eng=0 (pin-domain) [pin-domain = ADC pin voltage, NOT calibrated secondary]
CH NAME SRC CYCLE RMS-f[mV] ANG-f[deg] RMS-2x[mV] RMS-5x[mV] NOTE
(f = fundamental; 2x/5x = 2nd and 5th harmonic; angle reference = first sample of this window, not a phase lock to the signal generator)
0 UA TPAFE5160 0 995.94 -122.50 0.81 0.43
0 UA TPAFE5160 1 995.90 -122.50 0.98 0.40
0 UA TPAFE5160 2 995.91 -122.52 0.88 0.44
0 UA TPAFE5160 3 995.92 -122.52 0.87 0.48
0 UA TPAFE5160 4 995.83 -122.54 0.85 0.48
0 UA TPAFE5160 5 995.92 -122.54 0.88 0.40
0 UA TPAFE5160 6 995.91 -122.56 0.90 0.41
0 UA TPAFE5160 7 995.85 -122.57 0.88 0.36
0 UA TPAFE5160 8 995.88 -122.58 0.89 0.39
0 UA TPAFE5160 9 995.86 -122.60 0.87 0.35
0 UA TPAFE5160 10 995.88 -122.61 0.84 0.35
0 UA TPAFE5160 11 995.95 -122.62 0.88 0.36
0 UA TPAFE5160 12 995.91 -122.63 0.89 0.39
0 UA TPAFE5160 13 995.90 -122.64 0.87 0.41
0 UA TPAFE5160 14 995.89 -122.65 0.88 0.40
0 UA TPAFE5160 15 995.83 -122.66 0.82 0.41
0 UA TPAFE5160 ALL mean= 995.89 min= 995.83 max= 995.95 over 16 cycles
note: mV = (code + b_codes) * k_q16/65536/1000 (adc_cal_apply formula, unit labeled by header eng, never re-guessed); full-cycle window, coefficients = sin/cos(2*pi*k/N)*sqrt(2)/N as in App/algo/phasor.c; phase = atan2(im,re) deg
## fft --ch 3 --all
unit: mV eng=0 (pin-domain) [pin-domain = ADC pin voltage, NOT calibrated secondary]
CH NAME SRC CYCLE RMS-f[mV] ANG-f[deg] RMS-2x[mV] RMS-5x[mV] NOTE
(f = fundamental; 2x/5x = 2nd and 5th harmonic; angle reference = first sample of this window, not a phase lock to the signal generator)
3 UN TPAFE5160 0 996.92 +147.46 0.67 0.36
3 UN TPAFE5160 1 996.75 +147.45 0.72 0.33
3 UN TPAFE5160 2 996.78 +147.44 0.70 0.35
3 UN TPAFE5160 3 996.84 +147.44 0.74 0.31
3 UN TPAFE5160 4 996.85 +147.42 0.63 0.37
3 UN TPAFE5160 5 996.84 +147.41 0.69 0.33
3 UN TPAFE5160 6 996.84 +147.40 0.67 0.30
3 UN TPAFE5160 7 996.78 +147.39 0.71 0.30
3 UN TPAFE5160 8 996.81 +147.37 0.69 0.31
3 UN TPAFE5160 9 996.91 +147.36 0.65 0.32
3 UN TPAFE5160 10 996.86 +147.35 0.71 0.38
3 UN TPAFE5160 11 996.85 +147.34 0.66 0.39
3 UN TPAFE5160 12 996.80 +147.33 0.68 0.39
3 UN TPAFE5160 13 996.85 +147.31 0.65 0.37
3 UN TPAFE5160 14 996.86 +147.30 0.75 0.33
3 UN TPAFE5160 15 996.84 +147.29 0.72 0.34
3 UN TPAFE5160 ALL mean= 996.84 min= 996.75 max= 996.92 over 16 cycles
note: mV = (code + b_codes) * k_q16/65536/1000 (adc_cal_apply formula, unit labeled by header eng, never re-guessed); full-cycle window, coefficients = sin/cos(2*pi*k/N)*sqrt(2)/N as in App/algo/phasor.c; phase = atan2(im,re) deg
## shape
unit: mV eng=0(pin-domain) min-run for rail pin = 4 samples
CH NAME SRC CYCLES AMP[mVpk] POL SYM PIN_LO PIN_HI RAIL
0 UA TPAFE5160 16.00 1406.25 - 0.002 0 0 .
1 UB STM32-ADC1 23.50 14.90 + 1.614 0 0 .
2 UC STM32-ADC1 21.50 15.71 + 1.625 0 0 .
3 UN TPAFE5160 16.00 1408.16 + 0.001 0 0 .
4 IA TPAFE5160 52.00 0.31 + 1.582 0 0 .
5 IB STM32-ADC1 21.50 14.10 + 1.622 0 0 .
6 IC STM32-ADC1 24.50 15.31 + 1.603 0 0 .
7 IN STM32-ADC1 20.50 16.11 + 1.604 0 0 .
clip reconcile: measured=0x0000 [-] header=0x00E6 [UB UC IB IC IN] MISMATCH
yardstick: equality (default card check) -- measured rail pins must equal the sealed clip_mask
note: header clip_mask is the seal-time CLIPPED STAMP, source-derived in V0.1 (App/algo/adc_cal.c: s_rom_k_stm32 -> CLIPPED, unipolar no-bias), NOT measured from samples; measured is this segment's rail pins. Real V0.1 AC waves bottom-rail the 5 stamped channels, so equality is expected on-board; a non-railed measurement means synthetic fixture (e.g. column ramps) or wiring change -- investigate, do not auto-blame the seal
@@ -0,0 +1,84 @@
# WAVE11(削顶锁存态观察段,2026-11-04 19:36:41 触发)与 WAVE10(无归属切换瞬态段)wave_read.py 输出
## WAVE11 dump
FILE : WAVE11.BIN (5250 bytes)
magic : 57 52 01 ('WR' + version byte 1)
ver : 1
fs_hz : 1000
pts_per_cycle : 20
ch_count : 8
pts_per_ch : 320 (320 ms at 1000 Hz)
pre_pts : 160
trig_src : 1 (MANUAL)
trig_elm : 0
t0_ms : 1655148 (ms since power-on)
wall : 1793821000 (epoch s)
eng : 0 (pin-domain)
flags_seq : 0x00 VALID bits: (none)
clip_mask : 0x00E6 [UB UC IB IC IN]
frame_seq : 1654819
CRC16(hdr) : 0xC99C OK covers [0,126), low byte first
CRC16(data) : 0x5122 OK covers 5120 bytes, low byte first
-- channel table (8 entries x 12 B) --
IDX SEM NAME SRC BITS AVAIL K_Q16 B_CODES LSB_uV FLAG
0 0 UA TPAFE5160 16 0x00 10000000 0 152.587891 .
1 1 UB STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
2 2 UC STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
3 3 UN TPAFE5160 16 0x00 10000000 0 152.587891 .
4 4 IA TPAFE5160 16 0x00 10000000 0 152.587891 .
5 5 IB STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
6 6 IC STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
7 7 IN STM32-ADC1 12 0x02 52800000 0 805.664062 CLIPPED
## WAVE11 shape
unit: mV eng=0(pin-domain) min-run for rail pin = 4 samples
CH NAME SRC CYCLES AMP[mVpk] POL SYM PIN_LO PIN_HI RAIL
0 UA TPAFE5160 71.50 0.31 + 1.467 0 0 .
1 UB STM32-ADC1 0.00 0.00 0 1.000 320 0 YES
2 UC STM32-ADC1 48.00 10.88 + 1.406 0 0 .
3 UN TPAFE5160 57.50 0.38 - 1.497 0 0 .
4 IA TPAFE5160 63.00 0.31 + 1.369 0 0 .
5 IB STM32-ADC1 46.00 9.67 + 1.372 0 0 .
6 IC STM32-ADC1 52.00 6.85 + 1.415 0 0 .
7 IN STM32-ADC1 48.00 8.46 + 1.397 0 0 .
clip reconcile: measured=0x0002 [UB] header=0x00E6 [UB UC IB IC IN] MISMATCH
yardstick: equality (default card check) -- measured rail pins must equal the sealed clip_mask
note: header clip_mask is the seal-time CLIPPED STAMP, source-derived in V0.1 (App/algo/adc_cal.c: s_rom_k_stm32 -> CLIPPED, unipolar no-bias), NOT measured from samples; measured is this segment's rail pins. Real V0.1 AC waves bottom-rail the 5 stamped channels, so equality is expected on-board; a non-railed measurement means synthetic fixture (e.g. column ramps) or wiring change -- investigate, do not auto-blame the seal
## WAVE10 shape
unit: mV eng=0(pin-domain) min-run for rail pin = 4 samples
CH NAME SRC CYCLES AMP[mVpk] POL SYM PIN_LO PIN_HI RAIL
0 UA TPAFE5160 0.50 1408.39 + 1.978 0 0 .
1 UB STM32-ADC1 60.50 10.07 + 1.501 0 0 .
2 UC STM32-ADC1 52.50 10.47 + 1.510 0 0 .
3 UN TPAFE5160 0.50 6.03 - 2.029 0 0 .
4 IA TPAFE5160 55.00 0.31 + 1.491 0 0 .
5 IB STM32-ADC1 51.50 9.67 + 1.501 0 0 .
6 IC STM32-ADC1 59.50 10.07 + 1.501 0 0 .
7 IN STM32-ADC1 62.50 10.07 + 1.446 0 0 .
clip reconcile: measured=0x0000 [-] header=0x00E6 [UB UC IB IC IN] MISMATCH
yardstick: equality (default card check) -- measured rail pins must equal the sealed clip_mask
note: header clip_mask is the seal-time CLIPPED STAMP, source-derived in V0.1 (App/algo/adc_cal.c: s_rom_k_stm32 -> CLIPPED, unipolar no-bias), NOT measured from samples; measured is this segment's rail pins. Real V0.1 AC waves bottom-rail the 5 stamped channels, so equality is expected on-board; a non-railed measurement means synthetic fixture (e.g. column ramps) or wiring change -- investigate, do not auto-blame the seal
## WAVE10 fft ch0
unit: mV eng=0 (pin-domain) [pin-domain = ADC pin voltage, NOT calibrated secondary]
CH NAME SRC CYCLE RMS-f[mV] ANG-f[deg] RMS-2x[mV] RMS-5x[mV] NOTE
(f = fundamental; 2x/5x = 2nd and 5th harmonic; angle reference = first sample of this window, not a phase lock to the signal generator)
0 UA TPAFE5160 0 5.84 -139.52 2.44 0.99
0 UA TPAFE5160 1 15.51 -3.23 7.70 3.32
0 UA TPAFE5160 2 34.58 +4.50 17.43 7.66
0 UA TPAFE5160 3 51.19 +6.65 25.83 11.18
0 UA TPAFE5160 4 65.00 +7.38 32.69 14.36
0 UA TPAFE5160 5 74.50 +7.99 37.69 16.46
0 UA TPAFE5160 6 79.59 +8.70 40.07 17.62
0 UA TPAFE5160 7 79.39 +9.29 40.10 17.47
0 UA TPAFE5160 8 74.51 +9.87 37.56 16.41
0 UA TPAFE5160 9 64.92 +10.63 32.72 14.33
0 UA TPAFE5160 10 50.99 +11.42 25.81 11.22
0 UA TPAFE5160 11 34.17 +13.62 16.93 7.45
0 UA TPAFE5160 12 14.93 +20.84 7.48 3.33
0 UA TPAFE5160 13 5.76 +157.00 2.59 1.09
0 UA TPAFE5160 14 25.10 -177.39 12.59 5.53
0 UA TPAFE5160 15 43.38 -174.63 22.02 9.64
0 UA TPAFE5160 ALL mean= 44.96 min= 5.76 max= 79.59 over 16 cycles
note: mV = (code + b_codes) * k_q16/65536/1000 (adc_cal_apply formula, unit labeled by header eng, never re-guessed); full-cycle window, coefficients = sin/cos(2*pi*k/N)*sqrt(2)/N as in App/algo/phasor.c; phase = atan2(im,re) deg
@@ -35,7 +35,8 @@ flowchart LR
DEV -- "USB:下载器(ST-Link 等)" --> TGT
DEV -- "USB 串口 USART1<br/>COMx @115200 调试日志" --> TGT
TGT -- "以太网(CH395F)<br/>FTP:21 / 对时:8888 / 显示:7003" --> RMT
TGT -- "以太网(CH395F)<br/>FTP:21 / 远程显示:7003" --> RMT
TGT -. "对时:SNTP 客户端 UDP 123 出向(原 :8888 推送已退役)" .-> NTP["现场 NTP / 时间源"]
```
| 环境 | 角色 | 必需性 |
@@ -567,7 +568,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 服务 | 协议/端口 | 用途 | 核对方法 |
|---|---|---|---|
| 远程显示(RemoDispBus) | TCP **7003** | 远端看图(160×160 1bpp,3200 B/帧,客户端 LCDMEM 拉取)+ 远端导航按键 | DTU-RemoteLCD 或任意 TCP 客户端连 7003 收帧 |
| 墙钟对时 | TCP **8888** | PC 主动推送时间到设备 | 对时推送后核对设备时间 / 日志时间戳 |
| 墙钟对时(SNTP 客户端) | UDP **123**(出向;主/备服务器配 `[sntp]` 段,enable 默认关) | 设备作 SNTP 客户端从时间源取时写回 RTC(原 PC 推送 TCP :8888 已于 2026-11-04 退役删除) | 使能对时后核对设备时间 / 日志时间戳 |
| FTP | TCP **21** | 文件上传/下载(根目录 `/`,NAND 存储) | 任意 FTP 客户端列目录/读写 |
> 现场组网要求:设备与客户端**同网段**(默认 .100/24、网关 .1);跨网段部署先按 §4.1 改 IP 再上线。
@@ -575,7 +576,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
### 4.3 存储与运行时
- 文件系统:FatFS(FAT32)挂 NAND;**上电无卷自动格式化**(`f_mkfs`);格式化会清除卷上全部文件(含网口配置 `/config.ini`)
- RTC:SD2506 芯片(驱动 `Drivers/BSP/SD2506/`)提供墙钟基准与日志时间戳;首次使用建议先经 8888 对时
- RTC:SD2506 芯片(驱动 `Drivers/BSP/SD2506/`)提供墙钟基准与日志时间戳;首次使用建议配 `[sntp]` 段启用 SNTP 对时(或 UI 手工对时)
- 启动时序:`app_main_init`(存储 → 时钟 → RS485 → NAND → ADC → 启动转换)→ 就绪屏障(STORAGE / NET / FTP / TIMESYNC 事件)→ 六任务运行;详见 `docs/架构设计/项目启动顺序管理.md`、`docs/架构设计/项目架构总览.md`
### 4.4 现场验收清单
@@ -587,7 +588,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
| 5 | UI 功能 | 屏幕 / 远端操作 | 菜单 7 项导航、内容页、密码(查看 `000` / 修改 `123`)、网口设置保存后立即生效 |
| 6 | 对时 | PC 推送 8888 | 设备墙钟更新、日志时间戳正确 |
| 6 | 对时 | 配 `[sntp]` 段启用 SNTP(或 UI 手工对时;原 PC 推送 8888 已退役) | 设备墙钟更新、日志时间戳正确 |
| 7 | 掉电保持 | 写文件 / 改网口配置 → 重启 | 数据仍在(`/config.ini` 按新值生效) |
| 8 | LED 状态 | 目检 | 6 路 LED 随系统状态变化(低电平点亮) |
@@ -599,7 +600,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
|---|---|---|
| 操作系统 | 任意(Windows / Linux / macOS,有浏览器或 Python 即可) | 与设备同网段 |
| 远程显示客户端 | **DTU-RemoteLCD**(独立项目:Flask + WebSocket 参考实现)或任意符合 RemoDispBus 协议的 TCP 客户端 | 连设备 **TCP 7003**;协议见 `docs/UI设计/RemoDispBus协议与接收设计说明.md` |
| 对时工具 | 任意可 TCP 推送的工具/脚本 | 向设备 **8888** 推送 |
| 对时时间源 | 现场可达的 SNTP/NTP 服务器(配 `[sntp]` 段 server1/server2) | 设备作客户端 UDP/123 出向取时;原「向设备 8888 推送」的 TCP 工具随退役不再需要 |
| FTP 客户端 | 任意(资源管理器 / FileZilla 等) | 设备端 lftpd,端口 21 |
| 浏览器 | Chrome / Edge | 查看参考客户端 Web 页;开发期浏览文档 SVG 图 |
@@ -218,3 +218,40 @@ STM32(U17)为 LQFP144 脚号。
**两项残留口径**(不影响本轮 PASS,但别当成已验证):**串扰**未单独证(零点步骤时其余七路浮空、
本身在漂,小幅串扰看不出来);**节奏**只测了注入前后、没测注入进行中。
(澄清一句:本节标题里的"六项"= 上表六个**静态**判据项,与 §8.2 交流**动态六轮**不是同一套数。)
### 8.2 交流(正弦)动态验证实测记录(2026-11-02 台架首轮 + 2026-11-04 补测轮)
**执行手册与逐轮记录表**在 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8;证据归档
`docs/测试说明/测试数据/boot_log_data/20261104_ac_*`(含录波段原件与 `wave_read.py` 输出)。
两轮固件分别为 `fw build: Nov 2 2026 14:21:24` 与 `fw build: Nov 4 2026 19:05:57`(均产品构建、
未标定 `eng=0`)——**以下全部是引脚域电压口径,不是端子二次值**。发生器 VICTOR 2026H(设定一律
Vpp,端接高阻),万用表 FNIRST-DMT-99(判为真有效值档,判别依据见执行手册设备表)。
| 轮 | 实测结论 | 关键数 |
|---|---|---|
| 幅值 | **过**:三档幅值两点复现全落 ±2 % | −0.20 % / −0.20 % / +0.40 %(996 : 498 : 249 ≈ 4:2:1,无隐藏触底) |
| 波形 | **过**:非正弦也能读对,证数条是真有效值档 | 方波 −0.70 %、三角 −1.22 %;反推峰值两波形互合(1404 / 1410 mV vs 设定 1414 mV) |
| 相位 | **过(录波档 ≤2°)**:同段 `UA`(表序 0)vs `UN`(表序 3),同片 TPAFE 内比 | 0°/0° 段 Δφ = **0.07°**(WAVE06,11-02);0°/90° 段 Δφ = **90.05°**、偏差 0.05°、逐周波漂移 ≤0.01°(WAVE09,11-04,16 整周波全过);段与发生器无绝对相位锁,只比同段相对差 |
| 测频 | **如实报不可测成立**(现状判据就是不出数) | 两轮合计 561 行 `freq(hb)` 全部 `f=0 valid=0`(含满幅注入段);幅值门 `FREQ_AMP_MIN=50.0f`(`App/algo/freq.h:62`)在引脚域恒不过——标定态或改门限前测频轮不出数是**预期** |
| 削顶 | **直流档锁存成立、sticky 实证**;低频档与复位复核按裁定关闭 | UB 注 DC −1.00 V:`rail=0x002 edge=0x002`、唯一 `[ERR] rail: edge=0x2 sticky=0x2`、`adc: UB` 钉 0 码;撤注后恒持 ≥30 min(sticky 只有 `adc_health_rail_init()` 清,即复位)。50 Hz 同幅不锁存=预期(≈11 拍<20)。2.0 Hz 低频档与"复位清 sticky"复核**用户裁定 2026-11-04 不做**(判据链已被直流档闭合) |
| 谐波 | **选择性两轮成立**;幅值 A/C 过、B 已归因板端前端 RC 并关闭 | 单注 100 Hz → `Ia2` −1.25 %;纯基波 → `Ia` 0.0 %(复测)/ −0.6 %(初测)、两谐波桶=0;单注 250 Hz → `Ia5` **−4.4 %**(两轮复现 −4 ~ −5 %)——归因:差分滤波 C35 = 100 nF(用户确认)+ R19/R21 各 1 k(`docs/硬件平台/V0.1.tel`:49;拓扑 = 1 k 串联 + 1 k 接差分负端、不分压,`docs/产品需求/产品需求设计文档.md` FR-ADC-5)→ IA 差分传递 = 一阶低通 **fc ≈ 796 Hz**,该 −4.4 % 是前端在此频点的**真实衰减,板端读数忠实**;工频 50 Hz 仅 −0.2 %,不受影响(模型与数据见下段) |
**观察项(都判读过、非缺陷)**:TPAFE 输入浮空读数钉在 ≈1743/1747 mV 静态(撤线后 UA/IA 出现——
读 `meas:` 时先确认接线);未接线的 STM32 侧引脚码值随环境缓漂(UC/IC/IN/T),不消费即可;
录波触发后 `drop` 涨一截是 SEALED 窗口设计内(`App/algo/wave_ring.c:50`,涨完冻结,判失败只看
`err`/`ovf`);采集链健康两轮全程 Δ9998/10 s、`idle=0`、`adc1_err=0`(前置门 8 条全过)。
**IA 前端差分滤波 = 低通 fc ≈ 796 Hz(2026-11-04,谐波 B 行定量归因)**:工频测量够用、
谐波测量不够用——同拓扑外推 5 次 ≈ −4.6 %(出 ±2 % 判据)、7 次 −8.5 %、9 次 −13.0 %、11 次
−17.7 %、13 次 −22.6 %、15 次 −27.2 %、21 次(1050 Hz)−39.6 %;C35 若改 10 nF 则 fc ≈ 8 kHz,
21 次仅 −0.9 %。谐波功能产品化的硬件前置 = 板改小 C35 + 清点其余各路差分滤波电容容值(已登记
`docs/任务规划/登记簿.md` §3)。**为什么改板而不是软件逐阶修正**:修正系数依赖这颗电容的实际容值,
而电容有容差、随温度漂,每块板、每个温度都不一样,修正表跟不上;二次谐波制动这类"拿某次谐波幅值
与基波比值"的判据,比值被前端畸变后判定就不可信。换 10 nF 后测量带内是平的,板端读数直接可信,
不需要修正。证伪条件(将来有示波器再复核):该低通预测 250 Hz 差分分量额外滞后
≈17°;模型与三点拟合 `docs/测试说明/测试数据/boot_log_data/20261104_ac_ia_rc_model.txt`。
**边界**:本节不证端子二次值 / 精度等级 / 跨转换器相位(V0.1 硬件缺陷)/ THD——见
`docs/任务规划/数据可信.md` §4.6.5。
@@ -87,7 +87,7 @@
| 遥信 | 双位置遥信上**双点**,其余**单点**;点号 `0+` |
| 遥控 | **双点命令**,合闸选择 `82H`、执行 `02H`;点号 `6000H+` |
| **RS-485** | **对下 ModBus RTU 采集**(下行采集口,非上行口) |
| 对时 | 点表要求 **SNTP `192.168.1.121:5105`**;本项目**保留自定义 TCP 8888**,SNTP 本期不实现(产品定义中的对时方式) |
| 对时 | 点表要求 **SNTP `192.168.1.121:5105`**;本项目 **SNTP 客户端已落地**(2026-11-04,#73:`[sntp]` 段配服务器、**固定 UDP/123**),原自定义 TCP 8888 **已退役删除**(#78)——点表的**非标端口 5105 尚不满足**(客户端端口不可配),要按点表对接需给 `[sntp]` 增「服务器端口可配」小改 |
| 固有参数 | `8001H~800AH`:终端类型/操作系统/制造商/硬件版本/软件版本/校验码/规约类型/型号/ID/MAC |
| 运行参数 | `8020H~8058H`:死区(电流/交流电压/直流电压/功率/频率/功率因数)、PT/CT 变比、负荷越限/重载/过载报警、录波投退与定值、功率置负 |
| 定值 | `8820H~8882H`:过流 I/II/III 段、零序过流 I/II 段、重合闸、低压/低频、励磁涌流闭锁、小电流接地等 |
@@ -127,7 +127,7 @@
| # | 事项 | 处置 |
| --- | --- | --- |
| 产品定义中的后备电源与直流遥测裁剪 | `UBA` 后备电源电压、`V1/V2` 直流量三路遥测的硬件来源 | **本期不做**——V0.1 无对应硬件通道,不加子板/通道;点表此三项本期空缺 |
| 产品定义中的对时方式 | 对时 | **保留自定义 TCP 8888**;点表要求的 **SNTP 5105 本期不实现** |
| 产品定义中的对时方式 | 对时 | **SNTP 客户端已落地**(2026-11-04,#73);原自定义 TCP 8888 已退役删除(#78);点表要求的 **SNTP 5105 非标端口仍未实现**(客户端固定 UDP/123,端口不可配) |
| 产品定义中的本期保护范围 | 保护/控制范围 | **本期 = 骨架全做 + 有效通道子集闭环**(2026-10-10 用户确认),评估见 §1.6 |
> **产品定义 至此全部闭环**:上行 IEC 60870-5-104 / 持续采集 / 1000 Hz 已定(§1.5 表 1~4);产品定义中的后备电源与直流遥测裁剪 / 产品定义中的对时方式 本期不做或维持现状;产品定义中的本期保护范围 的范围裁剪见 §1.6。
+140 -34
View File
@@ -65,6 +65,7 @@
| `net_socket.c` | API 核心实现:消息队列处理与线程安全封装、内部 `_sock()` 版本;另含挂起 recv 登记路径 |
| `net_events.c`(**真机专属**) | **状态面收敛**(2026-10-14 从 `net_socket.c` 抽出,与 `net_listener.c` 同族):5 个 `handle_*_event`(`net_poll` 检测中断后的分派目标)+ `fire_event` + TRAP-NET-17 周期对账 + 监听重建**合一** reconciler(`net_reconcile_tick`,100ms,分级去抖+冷却,形态①②③④⑤/U11)+ `net_lost_connect_probe`;**只置事实与登记意图**,重建动作在 `net_listener_tick`;内部头 `net_events.h`,模拟器不编译 |
| `net_listener.c`(**真机专属**) | **监听生命周期**(E17 单一所有者收口):`net_listener_request`(**只登记意图**,5 个登记点)/ `net_listener_tick`(**唯一执行者**,门集 + D10 重试 30ms/2s)/ `net_listener_clear` / `net_listener_settle`(沉降/隔离 100ms/400ms);"该不该重建"的周期判据在 `net_reconcile_tick` 形态③④(单一 100ms tick);内部头,模拟器不编译 |
| `net_udp.c`(**真机专属**) | **UDP 数据面执行体**(2026-11-03 队列化改造新增):`net_udp_bind_exec` / `net_udp_set_peer_exec`(切芯片形态,不重开 Socket)/ `net_udp_sendto_exec`(**复用** `net_send_locked` 的 1KB 分块与挂起 send)/ `net_udp_recvfrom_exec` + `net_udp_tick`(挂起 recv 完成 / 预算超时)+ per-fd 出参槽(源地址写回调用方自己的内存,不占 `net_msg_t`)+ 截断排空;内部头 `net_udp.h`,模拟器不编译;用法与约束见 §6.0 / §6.3,调用结构见 `docs/网络/BSD_Socket_API_函数调用关系.md` §4.11 |
| `net_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现(仅 MCU 端编译) |
| `net_socket_sim.c`(`simulator/sim/`) | Winsock2 后端(模拟器端,与 `net_socket.c` 互斥编译),详见 §2.3 |
@@ -156,10 +157,10 @@ sequenceDiagram
| 规则 | 说明 |
|------|------|
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。**自愈无显式 API**(`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
| **`netTask` 外(任意应用任务)** | 公共 API:`net_send()` / `net_recv()` / `net_recv_to()` / `net_accept()` / `net_connect()` / `net_close()` / `net_listen()` / `net_drop_conn()`——请求入队列,netTask 串行执行,调用方阻塞等回执。UDP 侧同一口径:`net_bind()`(UDP) / `net_sendto()` / `net_recvfrom()` / `net_recvfrom_to()` / `net_udp_set_peer()`(2026-11-03 队列化改造,见 §6)。**自愈无显式 API**(`net_relisten()` / `net_resync_listen()` 已退役,静默回收/监听失位由对账⑤与 reconciler 形态③④自动认领,见 §3.6) |
| **`netTask` 内** | 内部 API `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` / `net_listen_locked()` |
| **`net_socket()` / `net_bind()`** | 直接调用,无需队列;实际在就绪屏障后的服务任务(timeSyncTask / ftpTask / uiTask)中创建监听 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **4 个非阻塞 tick**(D10 重监听重试 → 挂起 recv 完成 → 挂起 send 推进 → TRAP-NET-17 对账+监听重建合一 reconciler 100ms),再处理队列 |
| **`net_socket()` / `net_bind()`** | `net_socket()` 纯 RAM(分配控制块),任意任务直接调用;`net_bind()` 分两支——**TCP** 只记本地端口,仍是直接调用;**UDP** 要开芯片侧 Socket,走队列在 netTask 里执行(要发 5 条芯片命令,见 §6.0) |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环调用;`net_process_messages()` 每轮先跑 **5 个非阻塞 tick**(D10 重监听重试 → 挂起 recv 完成 → 挂起 send 推进 → **UDP 挂起 recv 完成/超时** → TRAP-NET-17 对账+监听重建合一 reconciler 100ms),再处理队列 |
### 3.4 内部 API 说明
@@ -440,8 +441,35 @@ void tcp_client(void)
## 6. UDP 编程
> **本项目不使用 UDP(2026-10-15 定案)**:UDP API 保留为 BSD socket 完整性,仅**单任务独占期**可用
> (`net_sendto` / `net_recvfrom` 直碰 SPI、不经队列,见 §2 三例外);本平台无 UDP 消费者。
> **UDP 已于 2026-11-03 队列化**(原"本项目不使用 UDP(2026-10-15 定案)"作废):
> `net_bind`(UDP) / `net_sendto` / `net_recvfrom` / `net_recvfrom_to` / `net_udp_set_peer` 与 TCP 同一口径
> ——请求投 `netMsgQueue`,芯片事务**全部在 `netTask` 里串行执行**,调用方阻塞等回执(结果搭 32 位任务
> 通知值回来)。任何任务都能安全调用,不再有"单任务独占期"这个前提。第一个真实消费者是 SNTP(请求-应答)。
### 6.0 先分清楚:芯片的 UDP 两种形态是在 **open Socket 那一刻**定死的
CH395F 用 `DesIP` 一个寄存器区分两种 UDP 形态(手册 `docs/芯片手册/CH395F.md` §9.2.4 讲的是**初始化步骤
②**),本层把它做成 socket 的**模式属性**(`net_types.h` `NET_UDP_MODE_SERVER` / `NET_UDP_MODE_CLIENT`),
但这个属性**只描述发送去向与芯片的对端过滤,不描述收包帧格式**——帧格式在开 Socket 时就已经定了:
| 开 Socket 时的 DesIP | 收包布局 | 谁能发给我 | 本层会不会产生这种 Socket |
|---|---|---|---|
| `0xFFFFFFFF`(服务器形态) | **8 字节信息头** + 载荷(头 [0-1] 长度、[2-3] 源端口、[4-7] 源 IP 线序) | 任意主机 | **会,且只有这一种**:`net_bind()` 恒此形态(BSD 的 bind 只定本地端口,不选对端) |
| 具体 IP(客户端形态) | 无信息头,整段就是载荷 | 只有该 IP | 不会:要它必须**在 `open_socket()` 之前**就把 DesIP 写成具体 IP,而 `bind` 先于 open 且语义上不该选对端 |
- **运行时把 DesIP 从全 F 改成具体对端,只改变发送去向,不会让信息头消失**——2026-11-04 08:16 真机
阶段 16 第一轮实测:切到 CLIENT 形态后 PC 发来的 48 B 应答,板端读到 **n=56、首字节 0x38**(= 56,
正是信息头的长度字段)。所以 `net_recvfrom` / `net_recvfrom_to` **恒剥 8 字节头、恒从头部取源地址**,
`udp_mode` 不参与剥头判定(把它加回剥头条件是登记过又被真机打回的做法,别再来一遍,见
`docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-22 的 2026-11-04 追加段)。
- `net_sendto()` 带目的地址、或 `net_udp_set_peer(fd, &peer)`:把 DesIP/DesPort 记到该对端,此后可用
`net_sendto(fd, buf, len, flags, NULL, 0)` 直接发它。**源地址与形态无关**,永远是包头里那个真正的发送方
——NAT 后的时间服务器就是"我发给 `119.28.220.214:123`、回来时源端口是 51999"那种,取记账值会答错。
- 从客户端形态回服务器形态:`net_udp_set_peer(fd, NULL)`,只改 DesIP/DesPort,**不重开 Socket**(不丢已
缓冲的应答,SNTP 这类请求-应答靠它);再次 `net_bind()` 则是重开(顺带丢掉芯片缓冲里的残留包),两者用途不同。
- ⚠ 运行时改窄 DesIP 到底会不会让芯片把其它主机的包过滤掉,**台架还没测出结论**(阶段 16 的 1604 只验到
"切回 SERVER 后换一个源端口的新包能收到")。要做**多源**服务(多个客户端同时发)就保持服务器形态、每次发送
都带显式 `dest_addr`——这条路径完全不碰这个未知项。
### 6.1 UDP Client
@@ -463,37 +491,45 @@ void udp_client(void)
return;
}
/* 2. 绑定本地端口(可选) */
/* 2. 绑定本地端口:UDP 侧**必做**(芯片侧 Socket 要在这里 open,
* 没 bind 就收发一律 NET_ERR_NOTCONN)。端口给 0 = 本层取动态端口 */
local_addr.sin_family = AF_INET;
local_addr.sin_port = net_htons(12345);
local_addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
net_bind(sock, (struct net_sockaddr *)&local_addr, sizeof(local_addr));
if (net_bind(sock, (struct net_sockaddr *)&local_addr, sizeof(local_addr)) != 0) {
printf("绑定失败\r\n");
net_close(sock);
return;
}
/* 3. 设置目标地址 */
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = net_htons(8888);
dest_addr.sin_addr.s_addr = net_inet_addr("192.168.31.200");
/* 4. 发送数据 */
/* 4. 发送数据:带目的地址的 sendto 顺手把 DesIP/DesPort 指向该地址,
* 本层记账转为**客户端形态**(下一次 dest_addr 传 NULL 就发往它,见 §6.0);
* 收包帧格式不受影响——Socket 是 bind 时在全F 形态下开的,恒带 8 字节信息头 */
const char *msg = "UDP Hello!";
net_sendto(sock, msg, strlen(msg), 0,
(struct net_sockaddr *)&dest_addr, sizeof(dest_addr));
printf("发送: %s\r\n", msg);
if (net_sendto(sock, msg, strlen(msg), 0,
(struct net_sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
printf("发送失败 %d\r\n", net_get_errno());
net_close(sock);
return;
}
/* 5. 接收数据 */
struct net_sockaddr_in src_addr;
int src_addr_len = sizeof(src_addr);
len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0,
(struct net_sockaddr *)&src_addr, &src_addr_len);
/* 5. 接收应答:受 IWDG 监管的任务用带预算的 net_recvfrom_to,
* 超时返回 -2(不置 errno),不会把喂狗窗口耗在等待上(§10.1) */
len = net_recvfrom_to(sock, buf, sizeof(buf) - 1, 2000U, NULL, NULL);
if (len > 0) {
buf[len] = '\0';
char ip_str[16];
printf("收到: %s (from %s:%d)\r\n", buf,
net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str),
net_ntohs(src_addr.sin_port));
/* 应答就来自刚记账的那个对端,所以没取源地址也要得对;真要取,它来自
* 信息头,与当前形态无关(§6.0) */
printf("收到: %s (from server)\r\n", buf);
} else if (len == -2) {
printf("应答超时\r\n");
}
net_close(sock);
@@ -531,15 +567,15 @@ void udp_server(void)
printf("UDP Server 启动,监听端口 8888\r\n");
/* 3. 循环接收数据 */
/* 3. 循环接收数据:一次调用 = 一个完整数据报(服务器模式自动剥 8 字节信息头) */
while (1)
{
net_poll();
struct net_sockaddr_in src_addr;
int src_addr_len = sizeof(src_addr);
int len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0,
/* 用带预算的收包当循环节拍:-2 = 本轮没人发包,不是错误;
* 换 net_recvfrom(...) 就是无限等,受 IWDG 监管的任务里别这么写(§10.1) */
int len = net_recvfrom_to(sock, buf, sizeof(buf) - 1, 500U,
(struct net_sockaddr *)&src_addr, &src_addr_len);
if (len > 0) {
@@ -549,14 +585,30 @@ void udp_server(void)
net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str),
net_ntohs(src_addr.sin_port));
/* 回显数据 */
net_sendto(sock, buf, len, 0,
/* 回显数据。⚠ 带目的地址的 sendto 会把本层记账切成**客户端形态**
* (DesIP = 这个客户端):此后不带地址的 sendto 都发往它。改窄 DesIP
* 会不会让芯片顺手把别的源过滤掉,台架没测出结论(§6.0),所以多客户端
* 服务别赌——回完就切回来: */
(void)net_sendto(sock, buf, len, 0,
(struct net_sockaddr *)&src_addr, src_addr_len);
(void)net_udp_set_peer(sock, NULL);
}
}
}
```
### 6.3 UDP 调用约束(2026-11-03 队列化之后)
| 约束 | 行为与理由 |
|---|---|
| 未 `net_bind()` 就 `sendto` / `recvfrom` / `set_peer` | 一律 `NET_ERR_NOTCONN`(-5):芯片侧 Socket 还没 open,此时写 DesIP 也会被随后的 open 复位 |
| 一次收包 = 一个完整数据报 | 缓冲装不下时交付 `len` 字节并把剩余部分**读空丢弃**(留半包在芯片缓冲里,下一次收包会从上一包的尾巴开始解析,源地址与载荷从此全部错位);短交付靠返回值判读 |
| 同一 fd 并发收包 | 第二个接收者拿 `NET_ERR_BUSY`(-11):数据报语义下无法把包归属到两个等待者,拒绝比交错投递安全(与 TCP 挂起 recv 同一守卫) |
| 阻塞预算 | 受 IWDG 监管的任务禁用无限等待的 `net_recvfrom`,改 `net_recvfrom_to(fd, buf, len, to_ms, ...)`:超时返回 **-2** 且不置 errno(与 `net_recv_to` 同口径);或先 `net_select()` / `net_can_read()` 门控 |
| 单包载荷 | ≤ 1472 字节(手册 §9.2.4);更长由本层按 1KB 分块、复用 TCP 的挂起 send 在 netTask 里推进,短写返回已发送字节数,调用方须检查返回值 |
| fd 额度 | 与 TCP 共用 `NET_MAX_SOCKETS`(8),一个 UDP fd 占一个名额 |
| 收包不占 netTask | 没数据时登记挂起(本 fd 一个槽)立即让出 netTask,完成条件由 `net_udp_tick` 每轮判定——与挂起 recv/send 同一套账,netTask 不会被一次 UDP 等待钉住 |
---
## 7. I/O 多路复用
@@ -738,10 +790,13 @@ void event_callback_example(void)
的 `s_addr` **同族、可直接相等比较**。历史背景(同一文件曾并存两套约定、`ntoa` 注释里的例子取了废弃族的值)
见 `docs/问题记录/CH395F_Trap_Records.md` TRAP-NET-21 的 2026-10-24 后续条目。
**UDP 直调限制**:`net_sendto` / `net_recvfrom` **不走消息队列**——它们在调用方上下文里直接 `net_poll()` + 读写芯片缓冲,
因此**仅限本层执行任务(NET 层 = netTask)调用**;别的任务调用回 `NET_ERR_NOT_IN_NETTASK`(-12)。该码**不是运行时故障,
重试永远无意义**:要么把调用挪进执行任务,要么改用走队列的 `net_send` / `net_recv`,没有第三条路。
「把这两个接口改成经 `netMsgQueue` 串行化」是已知后续项,未做的理由见 `Drivers/BSP/NET/net_socket.h` 同名函数头。
**UDP 也走队列(2026-11-03 改造)**:`net_bind`(UDP) / `net_sendto` / `net_recvfrom` / `net_recvfrom_to` /
`net_udp_set_peer` 与 TCP 同一口径——投 `netMsgQueue`、在 `netTask` 里碰芯片、结果搭 32 位任务通知值回来。
改造前有、现在作废的两件事:①"仅限本层执行任务(netTask)调用"——`NET_ERR_NOT_IN_NETTASK`(-12)自本
改造起**没有任何返回路径**,常量保留只为老代码与历史文档对得上号,新代码不要拿它当判据;②"UDP 只在单任务
独占期可用"。源地址回传不占 `net_msg_t`(该结构按值拷入队列,AGENTS 禁止加"回传用"字段),而是经
`Drivers/BSP/NET/net_udp.c` 的 per-fd 出参槽写回调用方自己的内存,代价是**同一 fd 不允许两个任务同时收包**
(第二个拿 `NET_ERR_BUSY`)。芯片形态(服务器 / 客户端)与剥头规则见 §6.0,调用约束见 §6.3。
### 9.1 端口号转换
@@ -835,7 +890,7 @@ void net_generation_inc(void);
| `net_get_generation` | 获取当前网络代次号(首次 `net_init` 成功前为 0,每次网络重建 +1) |
| `net_generation_inc` | 代次号加 1(netTask 重建成功、网络已可用后调用) |
**用途**:长驻 TCP 服务(FTP :21 / 对时 :8888 / 远程显示 :7003)在监听循环中检测代次号变化后主动重建监听 Socket,网口配置立即生效后可自动在新 IP 上恢复(重建会断开既有连接,属预期行为)。详见 `docs/子系统设计/网络配置说明.md` §4。
**用途**:长驻 TCP 服务(FTP :21 / 远程显示 :7003;对时 :8888 已退役,2026-11-04)与 UDP 出向客户端(SNTP,代次变化弃旧 socket 懒重建)在循环中检测代次号变化后主动重建,网口配置立即生效后可自动在新 IP 上恢复(重建会断开既有连接,属预期行为)。详见 `docs/子系统设计/网络配置说明.md` §4。
#### net_poll - 状态轮询
@@ -1007,20 +1062,71 @@ int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms);
调用方累计自身总预算:见到 -2 发起下一片,直到收齐 / EOF / 总预算耗尽。生产用途:lftpd 数据行读、timeSync 帧读(队头阻塞修复,完整例 §11.2);受 IWDG 监控任务的长等待必须走本接口(或 `net_select`)+ 每片喂心跳。
#### net_sendto - 发送 UDP 数据
#### net_sendto - 发送 UDP 数据报
```c
int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen);
```
#### net_recvfrom - 接收 UDP 数据
| 参数 | 说明 |
|------|------|
| `sockfd` | **必须已 `net_bind()`**(未 bind 回 -1 / `NET_ERR_NOTCONN`:芯片侧 Socket 还没 open) |
| `buf` / `len` | 一个数据报的载荷,单包 ≤ 1472 字节(手册 §9.2.4);更长由本层按 1KB 分块、复用 TCP 的挂起 send 在 netTask 里推进 |
| `flags` | 目前只消费 `NET_MSG_DONTWAIT`:芯片发送缓冲不可用时立即回 `NET_ERR_WOULDBLOCK`,不登记挂起 |
| `dest_addr` / `addrlen` | 非 NULL = 顺带把芯片 DesIP/DesPort 指向它 → 本层记账转**客户端形态**(此后 `NULL` 发送就发往它;收包帧格式不变,见 §6.0);NULL = 不改目的地址,沿用当前形态 |
| 返回值 | > 0 = 已发送字节数(**可能短写**,须检查);-1 = 失败(`net_get_errno()`) |
任意任务可调用:请求入 `netMsgQueue`,芯片事务在 `netTask` 串行执行(2026-11-03 起,旧"仅限 netTask 直调"形态作废)。
#### net_recvfrom - 接收 UDP 数据报(无限等待形态)
```c
int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen);
```
- 一次调用 = 一个**完整数据报**:自动剥掉芯片的 8 字节信息头并从中取源地址;本层 Socket 恒带这个头,与当前 `udp_mode` 无关(§6.0)。
- `buf` 装不下时交付 `len` 字节并把剩余部分**读空丢弃**——半包留在芯片缓冲里,下一次收包会从上一包的尾巴开始解析。
- `src_addr` / `addrlen` 可为 NULL;非 NULL 时 `*addrlen` 给 `sizeof(struct net_sockaddr_in)`,回填的 `s_addr` 是网络序整数(§9.0)。
- ⚠ **无数据即无限等待**:受 IWDG 监管的任务改 `net_recvfrom_to()`,或先 `net_select()` / `net_can_read()` 门控。
- 同一 fd 已有别的任务在收 → 本调用回 -1 / `NET_ERR_BUSY`(数据报无法归属两个等待者)。
#### net_recvfrom_to - 接收 UDP 数据报(显式预算形态)
```c
int net_recvfrom_to(int sockfd, void *buf, int len, uint32_t timeout_ms,
struct net_sockaddr *src_addr, int *addrlen);
```
| 参数 | 说明 |
|------|------|
| `timeout_ms` | 等待预算(ms,**必须 > 0**;0 = 无限等待,与本接口"有界"的存在理由相反,直接拒 `NET_ERR_INVAL`——无限形态请用 `net_recvfrom`) |
| 返回值 | > 0 = 本包交付字节数(小于芯片内包长 = 被截断,多余部分已丢弃);**-2 = 预算内没等到**(**不是 errno**,不得据此判错误);-1 = 真错误 |
与 `net_recv_to()` 同一口径,是受 IWDG 监控任务的合法 UDP 收包入口;等待期本 fd 登记一个挂起槽、netTask 立即让出(不占 CPU、不占 netTask)。
#### net_udp_set_peer - 切换 UDP 芯片形态(不重开 Socket)
```c
int net_udp_set_peer(int sockfd, const struct net_sockaddr_in *peer);
```
| 参数 | 说明 |
|------|------|
| `peer` | 非 NULL = 客户端形态,DesIP/DesPort 指向该对端(不带地址的发送就发往它;**收包仍带 8 字节信息头**,因为头是开 Socket 时定的,见 §6.0);NULL = 回服务器形态(DesIP = `0xFFFFFFFF`、DesPort = 0,任意主机可发) |
| 返回值 | 0 成功;-1 失败(未 bind → `NET_ERR_NOTCONN`) |
只写两个寄存器、**不重开 Socket**,因此不清芯片缓冲;要连残留包一起丢掉就再 `net_bind()` 一次(重开形态)。
#### net_udp_get_mode - 读当前 UDP 芯片形态
```c
int net_udp_get_mode(int sockfd);
```
返回 `NET_UDP_MODE_SERVER` / `NET_UDP_MODE_CLIENT`,参数错回 -1。给诊断、单测和上层判读"要不要切回服务器模式"用;纯 RAM 读,不入队。
#### net_close - 关闭 Socket
```c
@@ -5,9 +5,10 @@
>
> **范围**:MCU 端 `Drivers/BSP/NET/net_socket.c` + 其拆出的
> `Drivers/BSP/NET/net_listener.c`(**监听生命周期**)+
> `Drivers/BSP/NET/net_pending.c`(**挂起 recv / send 请求表**)。
> `Drivers/BSP/NET/net_pending.c`(**挂起 recv / send 请求表**)+
> `Drivers/BSP/NET/net_udp.c`(**UDP 数据面执行体与挂起 recv 槽**,2026-11-03 队列化改造新增,§4.11)。
> 模拟器端后端 `simulator/sim/net_socket_sim.c`(Winsock)是另一套实现,见 `docs/网络/BSD_Socket_API_使用指南.md` §2.3;`net_select.c` 不在此展开,见 `docs/网络/BSD_Socket_API_使用指南.md` §7 / §10.5。
> `net_listener.c` / `net_pending.c` 真机专属(模拟器不编译);设计依据分别见
> `net_listener.c` / `net_pending.c` / `net_udp.c` 真机专属(模拟器不编译);设计依据分别见
> `docs/网络/网络自愈架构设计.md` §11 与
> `docs/网络/Net层阻塞模型架构设计.md`。
>
@@ -25,11 +26,11 @@
| 执行域 | 运行位置 / 节奏 | 允许的操作 | 典型调用者 |
|---|---|---|---|
| **netTask 主循环** | 独立 FreeRTOS 任务,1ms/tick | **唯一**可碰芯片(SPI2)的地方;4 个非阻塞 tick(D10 重试 / 挂起 recv / 挂起 send / TRAP-NET-17 对账+监听重建合一 reconciler);队列分派;事件处理器;用户回调 | `net_poll` / `net_process_messages` 及其下游全部 `_locked` / `_tick` / `handle_*` |
| **netTask 主循环** | 独立 FreeRTOS 任务,1ms/tick | **唯一**可碰芯片(SPI2)的地方;5 个非阻塞 tick(D10 重试 / 挂起 recv / 挂起 send / **UDP 挂起 recv**(2026-11-03 队列化) / TRAP-NET-17 对账+监听重建合一 reconciler);队列分派;事件处理器;用户回调 | `net_poll` / `net_process_messages` 及其下游全部 `_locked` / `_tick` / `handle_*` |
| **应用任务侧** | ftpTask / uiTask / timeSyncTask / testTask 等 | 经队列的公共 API(入队 + `xTaskNotifyWait` 等回执);纯 RAM 直调 API;**禁止直碰芯片** | 各服务任务 |
| **直调快路径** | 同步调用,不经过队列 | `net_socket` / 查询族 / 回调注册(纯 RAM);**直碰 SPI 的三例外**:`net_bind`(UDP) / `net_sendto` / `net_recvfrom` | 初始化阶段、netTask 上下文、测试套件(硬件层测试模式下 netTask 自挂起,testTask 独占芯片) |
| **直调快路径** | 同步调用,不经过队列 | `net_socket` / 查询族 / 回调注册 / `net_bind`(TCP) 记账(纯 RAM);**UDP 侧不再是例外**:`net_bind`(UDP) / `net_sendto` / `net_recvfrom` / `net_recvfrom_to` / `net_udp_set_peer` 已于 2026-11-03 全部并入队列 | 初始化阶段、netTask 上下文、测试套件(硬件层测试模式下 netTask 自挂起,testTask 独占芯片) |
> ⚠ 直碰 SPI 的三例外**只能在"芯片访问者唯一"的上下文**调用:产品构建中应用任务一律走队列(TCP 全路径);三例外的实际消费者是测试套件(见 AGENTS.md"测试构建")。从其他任务直接调用会与 `net_poll` 的 SPI 事务竞争,破坏 CH395F 寄存器(TRAP-NET-14 实证:CONNECT 超时)。
> ⚠ 2026-11-03 起,**公开 API 里已经没有直碰 SPI 的口子**——原"三例外"(`net_bind`(UDP) / `net_sendto` / `net_recvfrom`)已全部并入队列,产品构建中应用任务的每一条 `net_*` 都是"入队 + 等回执"。仍需独占芯片的只剩**板级硬件层测试**:它绕过本层直接调 `ch395f_*`,前提是该测试模式下 netTask 自挂起、testTask 独占芯片(见 AGENTS.md"测试构建")。任何情况下从别的任务直接驱动 CH395F 都会与 `net_poll` 的 SPI 事务竞争、破坏芯片寄存器(TRAP-NET-14 实证:CONNECT 超时)。
---
@@ -38,9 +39,9 @@
```mermaid
flowchart TB
subgraph APP["应用任务(非 netTask)"]
PUB["经队列公共 API<br/>netListen · netAccept · netConnect · netSend<br/>netRecv · netRecvTo · netClose · netDropConn"]
PUB["经队列公共 API<br/>netListen · netAccept · netConnect · netSend<br/>netRecv · netRecvTo · netClose · netDropConn<br/>UDP:netBind · netSendto · netRecvFrom · netRecvFromTo · netUdpSetPeer"]
RAM["直调 · 纯 RAM<br/>netSocket · netSockState · netGetSock<br/>netGetErrno · netSetEventCb · netGetSockname<br/>字节序(netHtons / netInetAddr …)"]
SPI["直调 · 直碰 SPI(须芯片唯一上下文)<br/>netBind(UDP) · netSendto · netRecvFrom"]
SPI["板级硬件层测试 · 直调 BSP(须芯片访问者唯一)<br/>ch395f_*(该模式下 netTask 自挂起)"]
CB["用户事件回调<br/>(在 netTask 上下文执行!)"]
end
@@ -53,8 +54,9 @@ flowchart TB
RetryTick["① netListenerRetryTick<br/>D10 重监听重试:30ms / 2s 时限"]
PendTick["② netPendingTick<br/>挂起 recv:①数据 ②到点(仅显式预算) ③断连 ④tripwire 60s 只留痕"]
PendShimTick["③ netSendPendingTick<br/>挂起 send:①断连 ②推进(每轮≤1块) ③写完 ④无进展超时"]
ReconcileTick["④ netReconcileTick<br/>TRAP-NET-17 对账 + 监听重建合一:100ms / 分级去抖 / 冷却<br/>读驱动状态缓存(①②③④⑤ + U11)"]
DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT · DROP_CONN"]
ReconcileTick["⑤ netReconcileTick<br/>TRAP-NET-17 对账 + 监听重建合一:100ms / 分级去抖 / 冷却<br/>读驱动状态缓存(①②③④⑤ + U11)"]
UdpTick["④ netUdpTick<br/>UDP 挂起 recv:①数据到达(读一个完整数据报) ②预算到点 -2 ③会话已死"]
DISP["队列分派 switch<br/>SEND · RECV · CLOSE<br/>CONNECT · LISTEN · ACCEPT · DROP_CONN<br/>UDP_BIND · UDP_SENDTO · UDP_RECVFROM · UDP_PEER"]
subgraph LK["_locked / 内部分派目标(仅 netTask 上下文)"]
LL["netListenLocked<br/>初次监听(open 重试 3× + sock_reset 排渣)"]
LN["netConnectLocked<br/>open 重试 3×(sock_reset 排渣)+ tcpConnect"]
@@ -64,19 +66,30 @@ flowchart TB
LC["netCloseLocked<br/>结束挂起 + cmd_done 快路径 / sock_reset(GRACEFUL)"]
DC["netDropConnLocked<br/>拆会话保监听:sock_reset(QUARANTINE) + 登记重监听"]
RLS["netListenerTry → netRelistenLocked<br/>单次尝试 + TRAP-NET-18 排渣隔离"]
UB["netUdpBindExec<br/>开芯片侧 Socket(恒服务器模式,DesIP=FFH 在 open 前写)"]
US["netUdpSendtoExec<br/>带地址则先写 DesIP/DesPort(转客户端模式)"]
UR["netUdpRecvfromExec<br/>快路径 / DONTWAIT / 登记挂起 recv"]
UP["netUdpSetPeerExec<br/>只改 DesIP/DesPort,不重开 Socket"]
end
EV["事件处理器<br/>handleConnect · handleDisconnect<br/>handleRecv · handleSend · handleTimeout"]
ST["netListenerSettle<br/>只置沉降/隔离事实(100ms / 400ms)"]
FE["fireEvent → 用户回调"]
PR["s_net_pending<br/>(每 fd 至多一个挂起 recv)"]
SR["s_net_send<br/>(每 fd 至多一个挂起 send)"]
SU["s_net_udp<br/>(每 fd 至多一个接收槽:调用方出参指针 + 挂起 recv)"]
CH[("ch395f.* 驱动<br/>SPI2 串行事务")]
end
PUB -->|入队| MQ
MQ --> DISP
DISP --> LL & LN & LS & LR & LA & LC & DC & RLS
NPM --> RetryTick & PendTick & PendShimTick & ReconcileTick & NP
DISP --> LL & LN & LS & LR & LA & LC & DC & RLS & UB & US & UR & UP
NPM --> RetryTick & PendTick & PendShimTick & UdpTick & ReconcileTick & NP
US -->|共用同一条分块与挂起 send| LS
UR -->|登记 / 结算| SU
UdpTick -->|读出一个完整数据报| SU
UdpTick -->|xTaskNotify 完成或超时| XN
LC -->|结束 UDP 挂起 recv(settle_close)| SU
UB & US & UR & UP & UdpTick --> CH
RetryTick --> RLS
ReconcileTick -->|"① 已断"| EV
ReconcileTick -->|"② 补 accept"| EV
@@ -97,14 +110,14 @@ flowchart TB
XN -->|唤醒| PUB
NP --> CH
LL & LN & LS & LR & LA & LC & DC & RLS --> CH
SPI -->|直碰 SPI| CH
SPI -->|仅板级硬件层测试:netTask 自挂起时独占芯片| CH
CH -->|INT# 低电平| NP
```
**读图要点**:
1. 应用侧只有两种"等":① 入队后 `xTaskNotifyWait` 等回执(同步语义);② 挂起 recv / send 的等待(同一通道,只是"完成"由 `netPendingTick` / `netSendPendingTick` 判定,不由 netTask 立即处理)。
2. `net_process_messages()` 开头的 **4 个 tick 是网络层自愈主干**:没有任何应用消息时,重试 / 挂起 recv 完成 / 挂起 send 推进 / 状态对账 + 监听重建照常运转——"断连后即使没人发消息,监听器也能自己活过来"靠的就是它们。
1. 应用侧只有两种"等":① 入队后 `xTaskNotifyWait` 等回执(同步语义);② 挂起 recv / send 的等待(同一通道,只是"完成"由 `netPendingTick` / `netSendPendingTick` / `netUdpTick` 判定,不由 netTask 立即处理)。
2. `net_process_messages()` 开头的 **5 个 tick 是网络层自愈与等待结算主干**:没有任何应用消息时,重试 / 挂起 recv 完成 / 挂起 send 推进 / UDP 挂起 recv 完成与超时 / 状态对账 + 监听重建照常运转——"断连后即使没人发消息,监听器也能自己活过来"靠的就是它们。
3. 事件处理器**只由 `net_poll` 驱动**,例外是 `net_reconcile_tick`(分级去抖 + 1s 冷却)可再入 `handle_disconnect_event` / `handle_connect_event`——对账是事件未收到的自愈通道(TRAP-NET-17)。**注意职责边界(对账与重建合并后)**:对账与事件处理器**只置事实**(置 CLOSED / 登记沉降),**重建监听的动作统一由 `net_reconcile_tick` 形态③④ 做**(本层唯一重建入口)——"谁先看到 DISCONNECT 就谁重建"的竞争在结构上消失。
4. 回调 `event_cb` 在 **netTask 上下文**执行:回调内不得阻塞、不得发网络操作。
5. **模块归属**:`RetryTick`(D10 重试)/ `RLS` 的 `net_listener_request` / `ST`(settle)在 `net_listener.c`(真机专属、模拟器不编译);挂起 recv 完成检查(`net_pending_tick`)、挂起 send 推进(`net_send_pending_tick`)与两张挂起表(`PR` / `SR`)在 `net_pending.c`(真机专属);其余 tick(含合并后的 `ReconcileTick` reconciler)与 `_locked` 在 `net_socket.c`。
@@ -126,7 +139,7 @@ flowchart TB
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `net_poll()` | 事件泵:GPIO 轮询 INT#(低=有待处理)→ `GET_GLOB_INT_STATUS_ALL`(读=清)→ 按位图逐 Socket 读 SINT → 分派 5 类事件;**每轮末尾**对在用 Socket 全量刷新**驱动状态缓存** + 步进 `ch395f_sock_reset_poll` | `HAL_GPIO_ReadPin` → `ch395f_get_glob_int_status_all` → `ch395f_get_sock_int_status` ×n → `handle_connect/disconnect/recv/send/timeout_event` → `ch395f_get_sock_state` ×n(刷新缓存)+ `ch395f_sock_reset_poll` ×n | **RECV 中断是电平触发**:每次调用**只处理一批**(do-while(0)),处理完立即返回,否则主循环(echo/应用逻辑)饿死(TRAP-NET-08);SENBUF_FREE 只置 `send_ready` 不记事件;状态缓存**唯一刷新点**在此(reconciler 只读缓存,E2 实测单次回读 18.1µs);`sock_reset_poll` 步进使同步包装 600ms 放弃后的复位机不搁浅(IDLE 槽纯 RAM 判零 SPI) |
| `net_process_messages()` | 队列入口 + 自愈 tick 宿主:每轮**先**跑 4 个非阻塞 tick,再排空队列逐条分派,结果 `xTaskNotify` 回传 | `net_listener_tick` → `net_pending_tick` → `net_send_pending_tick` → `net_reconcile_tick`(对账+重建合一)→ `osMessageQueueGet`(0 超时) ×n → switch 分派 → `xTaskNotify` | ① tick 放在队列**之前**:断连后即使没有任何应用消息,监听器也能自主恢复、挂起 recv / send 也能完成;② RECV 若返回 `NET_PENDING_REGISTERED`(哨兵 0x70000000)则**不发即时回执**(`do_notify=0`),完成时才通知;③ 失败结果把 `s_net_errno`(具体负错误码)压入通知值——调用方恢复 errno 后对外统一 -1(BSD 语义);④ 通知值**整个 32 位**都是结果(不压位域标签,否则 32768 这类大字节数会溢出为负);⑤ **对账①置 CLOSED 后形态③④同轮即可重建**(合并后,原"pump 置于对账之后"的顺序依赖在单 tick 内自然成立) |
| `net_process_messages()` | 队列入口 + 自愈 tick 宿主:每轮**先**跑 5 个非阻塞 tick,再排空队列逐条分派,结果 `xTaskNotify` 回传 | `net_listener_tick` → `net_pending_tick` → `net_send_pending_tick` → `net_udp_tick`(UDP 挂起 recv 完成/超时)→ `net_reconcile_tick`(对账+重建合一)→ `osMessageQueueGet`(0 超时) ×n → switch 分派 → `xTaskNotify` | ① tick 放在队列**之前**:断连后即使没有任何应用消息,监听器也能自主恢复、挂起 recv / send 也能完成;② RECV 若返回 `NET_PENDING_REGISTERED`(哨兵 0x70000000)则**不发即时回执**(`do_notify=0`),完成时才通知;③ 失败结果把 `s_net_errno`(具体负错误码)压入通知值——调用方恢复 errno 后对外统一 -1(BSD 语义);④ 通知值**整个 32 位**都是结果(不压位域标签,否则 32768 这类大字节数会溢出为负);⑤ **对账①置 CLOSED 后形态③④同轮即可重建**(合并后,原"pump 置于对账之后"的顺序依赖在单 tick 内自然成立) |
### 4.3 经队列的公共 API 及其 netTask 侧实现
@@ -145,14 +158,17 @@ flowchart TB
| `net_drop_conn` | `NET_MSG_DROP_CONN` | `net_drop_conn_locked` | `net_pending_settle_close` + `net_sock_reset_sync(QUARANTINE)` +(auto_relisten)`net_listener_request` | 0(含幂等)/ -1 | **拆会话保监听**(队头阻塞收口):应用放弃会话(对时 30s 预算耗尽 / 协议超时)而单连接监听被挂起对端永久占住时的应用侧出口;EOF 路径**不得**调(`auto_relisten` 已接管);非 ESTABLISHED/CONNECTING 直接返 0 不动监听;本层收口 CLOSED 不清 fd(保留 local_port/auto_relisten),监听型 fd 登记重监听、失败交 D10 + reconciler 形态④ |
| ~~`net_relisten`~~ | ~~`NET_MSG_RELISTEN`~~ | **已删除**(消息类型同删) | — | — | 原"无条件芯片侧重建"与 pump/D10 完全重叠,且对健康监听器是 footgun(open→USE→close 自毁 ~1s);生产零调用。历史实现见 `net_socket.c` git 历史 |
| ~~`net_resync_listen`~~ | ~~`NET_MSG_RESYNC_LISTEN`~~ | **已删除**(语义并入对账⑤) | — | — | D14 静默回收(芯片自行回监听、不发 DISCONNECT)现由 `net_reconcile_tick` 判据⑤自动认领:本层 ESTABLISHED 且芯片 0x05/0x01 → 只修本层(state→LISTENING + 转登 `last_peer_port` + 清 `lost_conn_seen` + 取消挂起 recv),**不发芯片命令**(纪律原样保留,run8 实证 170s 拒绝);去抖/冷却沿用对账框架 |
| `net_bind`(**UDP 支**) | `NET_MSG_UDP_BIND`(端口按 `ntohs` 后的值传,0 = 由执行体取动态号) | `net_udp_bind_exec` | Socket 4~7 兜底 `set_send_buf` / `set_recv_buf` + `set_proto_type(UDP)` + `set_sour_port` + `set_des_ip(0xFFFFFFFF)` + `set_des_port(0)` + `open_socket` | 0 / -1 | **bind 恒服务器模式**(BSD 的 bind 只定本地端口,不选对端);DesIP/端口写在 open **之前**(open 复位寄存器,写晚了等于没写);动态端口游标在 netTask 内推进(旧形态在调用方上下文改静态量 = 跨任务竞争) |
| `net_sendto` | `NET_MSG_UDP_SENDTO`(目的地址按值拷进 `msg.addr`,`addrlen > 0` 才改 DesIP) | `net_udp_sendto_exec` → **共用** `net_send_locked` | `set_des_ip` / `set_des_port`(带地址时)+ `write_send_buf` 分块 ≤ 1KB | 实际字节数(**可能短写**)/ -1 | 带目的地址 = 切**客户端模式**并记账 `remote_*`;发送与 TCP 走同一条分块 + 挂起 send(`net_send_pending`)通路,不为 UDP 复制第二份 1KB 分块逻辑;未 bind → NOTCONN(芯片侧还没 open) |
| `net_recvfrom` / `net_recvfrom_to` | `NET_MSG_UDP_RECVFROM`(**出参指针不入队**:`net_udp_outparam_arm` 存进本 fd 槽,netTask 直接写回调用方内存) | `net_udp_recvfrom_exec`(即时读 / 登记挂起)+ `net_udp_tick`(完成 / 超时结算) | `net_poll` + `get_recv_len` + `read_recv_buf`(剥头 + 截断排空) | >0 / -1 / **-2(`_to` 预算内无数据,不占 errno)** | 一次调用 = 一个完整数据报;**恒剥 8 字节信息头、恒从头部取源地址**(本层 Socket 一律在 `DesIP = 0xFFFFFFFF` 的形态下开出,运行时改 DesIP 不改收包帧格式——真机实测见 TRAP-NET-22 的 2026-11-04 追加段;曾经写成"按 `udp_mode` 决定剥不剥",是真机第一轮跑翻的);无数据 + 阻塞 → 登记挂起立即让出 netTask;缓冲装不下时剩余部分**读空丢弃**;同 fd 第二个接收者 → BUSY |
| `net_udp_set_peer` | `NET_MSG_UDP_PEER`(`addrlen == 0` = 回服务器模式) | `net_udp_set_peer_exec` | `set_des_ip` + `set_des_port`(**不重开 Socket**) | 0 / -1 | 模式切换的轻量入口,给"上一轮当客户端问、这一轮要听所有人"的收包方;要连芯片缓冲残留一起丢掉就再 `net_bind()` 一次(重开形态) |
### 4.4 直调 API(不经队列)
| 函数 | 设计作用 | 主要调用 | 意图 / 注意 |
|---|---|---|---|
| `net_socket(domain,type)` | 分配空闲控制块(`alloc_socket` 线性扫描 `in_use=0`)→ CREATED | `alloc_socket` + `net_listener_clear` + `net_pending_clear` | 新 fd 不得继承上一次的待重试/挂起状态(复用描述符的经典坑);domain 仅 `NET_AF_INET` |
| `net_bind(sockfd,addr,addrlen)` | TCP:仅记录本地端口(port=0 → 动态端口 49152 起递增)→ BOUND,**无芯片命令**;UDP:完整开 Socket → UDP_OPEN | UDP 路径:`set_proto_type(UDP)` + `set_sour_port` + `set_des_ip(0xFFFFFFFF)` + `set_des_port(0)` + `open_socket`(Socket 4~7 兜底重设缓冲) | TCP 的 bind 只是"记账",真正的芯片动作在 listen/connect;UDP 直碰 SPI(§2 三例外之一);动态端口回绕 65535→49152 |
| `net_sendto` / `net_recvfrom` | UDP 收发(直碰 SPI,不经队列) | `set_des_ip` / `set_des_port` + `write_send_buf`(分块 1KB + `net_poll` 门控);`net_poll` + `get_recv_len` + `read_recv_buf` | 同 §2 上下文约束;recv 时 UDP Server 模式前 8 字节是固定头(源 IP + 端口 + 长度),先剥头填 `src_addr` 再读数据;recvfrom 阻塞语义与 net_recv 同构(blocking 属性 / DONTWAIT,无默认预算;轮询自旋实现,生产零调用) |
| `net_bind(sockfd,addr,addrlen)` | **TCP 支**:仅记录本地端口(port=0 → 动态端口 49152 起递增)→ BOUND,**无芯片命令** | 写 `s_net_socks[fd].local_port` / `state` | TCP 的 bind 只是"记账",真正的芯片动作在 listen/connect;**UDP 支自 2026-11-03 起入队**(要发 5 条芯片命令,见 §4.3 `net_bind`(UDP 支)行);动态端口回绕 65535→49152 |
| `net_setblocking(sockfd,on)` | 设置阻塞**模式属性**(直写控制块,不经队列) | 写 `s_net_socks[fd].blocking` | 对齐 BSD `O_NONBLOCK` / Python `setblocking`:"模式归使用者、超时归调用方";单字节原子写(先例 `net_set_event_cb`),最坏一次在途 recv 用旧模式;`net_socket()` 默认置 1(close 的 memset 清零后新 fd 必须显式重置) |
| `net_wait_result(kind,sockfd)` | 调用侧两段式等待:**零等待拾取** → 阻塞等回执 | `xTaskNotifyWait` ×2 | ① 零等待段兜住"入队→等待入口"间隙落地的通知(2026-09-22 台架实证丢通知);② **清除掩码必须全 1**(传 0 = 不清 → 上次结果残留被重读,run3~11 挂死/错位的真根因);③ 必须判 **pdTRUE** 而非 `v!=0`(合法结果 0 = EOF 会被误判"无通知"→ 永久挂死);④ 32 位整体作结果(32768 大字节数不截断);⑤ 本路径**禁止**加逐调用日志(曾占日志 98.6%、冲掉 [LAT] 证据行,run14 实证) |
| `net_sock_state(sockfd)` | 跨任务只读状态快照(单字节读,无锁) | 无 | 描述符无效/未占用 → -1;值可能滞后 netTask 最新状态——用于**轮询**(客户端等 ESTABLISHED),不替代 accept/connect |
@@ -257,8 +273,31 @@ flowchart TB
---
### 4.11 UDP 数据面(`net_udp.c`,2026-11-03 队列化改造)
> **模块归属**:`Drivers/BSP/NET/net_udp.c`(真机专属,模拟器不编译——模拟器端 UDP 由 `simulator/sim/net_socket_sim.c` 的 Winsock 形态承担)。
> **使用侧**契约(芯片两种模式、调用约束、超时口径)权威在 `docs/网络/BSD_Socket_API_使用指南.md` §6.0 / §6.3;
> 本节只讲调用结构与"为什么这么切"。
| 函数 | 上下文 | 设计作用 | 关键决策 |
|---|---|---|---|
| `net_udp_outparam_arm` / `net_udp_outparam_disarm` | 调用方任务(临界区只包"查占位"三步) | 登记"这次收包的源地址写回哪块调用方内存" | **源地址不搭 `net_msg_t`**:该结构按值拷入队列,AGENTS / TRAP-NET-10 明令禁止给它加"回传用"字段;出参指针进 per-fd 槽,netTask 直接写回调用方自己的内存,通知值只带结果码 |
| `net_udp_clear` | 两侧 | 清本 fd 槽(`net_socket` 分配新 fd、入队失败、执行体每条出口都要清) | 槽位泄漏 = 该 fd 从此永久 `BUSY`;出口必清(ns_s7_udp.c UDP-7d / UDP-9e 钉住) |
| `net_udp_bind_exec` | netTask | 服务器模式开芯片侧 Socket(5 条命令 + Socket 4~7 缓冲兜底) | DesIP/端口写在 open **之前**(手册 §9.2.4;open 复位寄存器);bind **恒服务器模式**——BSD 语义里 bind 不选对端;动态端口游标在 netTask 内推进(消掉跨任务写静态量) |
| `net_udp_set_peer_exec` | netTask | 只写 DesIP/DesPort 切形态;`peer == NULL` 回服务器模式 | 不重开 Socket,因此**不清**芯片缓冲;要连残留包一起丢就再 `net_bind()`(重开形态) |
| `net_udp_sendto_exec` | netTask | 记对端账 + **复用 `net_send_locked`** 发送 | 不为 UDP 复制第二份 1KB 分块 / 挂起 send 逻辑;"会话是否已死"由 `net_send_state_down()`(`net_pending.c`)按 `type` 分流——TCP 看 ESTABLISHED、UDP 看 UDP_OPEN,一条判据两侧共用(`net_send_locked` / `net_send_pending_tick` / `net_pending_tick` 三处原先各写一遍) |
| `net_udp_recvfrom_exec` | netTask | 三出口:数据已在 → 读一包;DONTWAIT / `blocking=0` → WOULDBLOCK;阻塞 → 登记挂起并立即让出 netTask | 自等守卫判 `caller == xTaskGetCurrentTaskHandle()`,**不是** `net_ctx_is_owner_task()`:执行体本身就跑在 netTask 里,用后者等于把每一条经队列投进来的正常请求都当自等拒掉(UDP-8a 当场抓到过这个错) |
| `net_udp_tick` | netTask 1ms | 挂起 recv 的完成 / 预算超时 / 会话已死三类结算 + `xTaskNotify` | 与挂起 recv、挂起 send 同一节奏;`readable` 只当"省一次 SPI"的门,电平型 RECV_OK 每轮重读可自愈(事件漏了也不永久瞎) |
| `net_udp_datagram_read` | netTask(private) | 一个完整数据报:按 `udp_mode` 决定剥不剥 8 字节信息头 → 截断排空 → 清 `readable` | `avail` 由调用方读一次**传进来**:剥头量、载荷量、排空量必须由同一个数字算出(读两次 = 决策与内容可能不同源);服务器模式不足 8 字节的残包不猜头,整包按载荷交付(TC-NETSOCK-440o 定案) |
| `net_udp_settle_close` / `net_udp_cancel_all` | netTask | close / 网络重建时结算等待者 | **任何结算都必须通知**:close 时有数据先交付、无数据回 CONNRESET;`net_init` 重建走 `net_udp_cancel_all`(UDP-8 / UDP-12 钉住"不许静默丢通知")。⚠ **没有** per-fd `net_udp_cancel`:唯一的 per-fd 取消点在 `net_events.c` 的 reconcile 形态⑤,而形态①⑤的判据门是 `state == ESTABLISHED`(TCP 专属,UDP 在 `net_events.c` 里被"其余状态不归本 tick 认领"挡掉),UDP 挂起收包的"会话已死"结算由 `net_udp_tick` 第①条按状态门当轮完成(UDP-19 钉住),不留没人调的 API |
改造前后的差别一句话:**改造前 UDP 在调用方上下文自旋碰 SPI**(既违反"netTask 唯一芯片访问者",也把
IWDG 预算吃在自旋上,且源地址无法回传);**改造后 UDP 与 TCP 同构**——入队、netTask 串行执行、挂起态
交 tick 结算、结果搭 32 位通知值回来。
## 5. 典型端到端路径
### 5.1 发送(队列路径)
```mermaid
@@ -409,15 +448,15 @@ sequenceDiagram
| `ch395f_set_mac_addr` | `net_init`(**须在 `ch395f_init` 之前**) | 写芯片 EEPROM;INIT_CH395 加载 MAC(`docs/芯片手册/CH395F.md` §9.2.1 步骤①) |
| `ch395f_set_ip_addr` / `set_gwip_addr` / `set_mask_addr` / `set_dhcp` | `net_init`(**均须在 `ch395f_init` 之前**) | 固定 IP 或 DHCP |
| `ch395f_init` / `ch395f_ping_enable` | `net_init` | 协议栈初始化(INIT_CH395)/ PING(须在 init 之后) |
| `ch395f_set_send_buf` / `ch395f_set_recv_buf` | `net_init`(8 Socket 布局);`net_bind`(UDP)(Socket 4~7 兜底) | 每 Socket 收 4 块 + 发 2 块 |
| `ch395f_set_send_buf` / `ch395f_set_recv_buf` | `net_init`(8 Socket 布局);`net_udp_bind_exec`(Socket 4~7 兜底,2026-11-03 起在 netTask 内执行) | 每 Socket 收 4 块 + 发 2 块 |
| `ch395f_get_glob_int_status_all` | **仅 `net_poll`**(唯一读者红线:全局寄存器唯一读者,见 `docs/网络/网络自愈架构设计.md` §4.10;`net_listen_locked` / `net_connect_locked` / `net_close_locked` / `net_relisten_locked` 的原全局读清已全部删除) | **读 = 清**(中断清除机制,无按位手段)——其他位置读清会焊死 net_poll 的按 Socket 门控 |
| `ch395f_get_sock_int_status` | `net_poll`(逐 Socket SINT);各 open 路径后清残留;事件处理器清 TIMEOUT | **读 = 清** |
| `ch395f_sock_events` | `net_connect_locked`(open 后清残留,唯一读者红线的护栏原语,见 `docs/网络/网络自愈架构设计.md` §4.10);`net_close_locked`(芯片已 CLOSED 快路径只清本 Socket 残留事件) | 只碰**本 Socket** SINT 的护栏原语 |
| `ch395f_open_socket` / `ch395f_close_socket` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`(含沉降期主动 close);`net_close_locked` 的 CLOSE 已改经 `ch395f_cmd_done` / `ch395f_sock_reset_*` | TRAP-NET-09:close 后需沉降才能重开 |
| `ch395f_open_socket` / `ch395f_close_socket` | `net_udp_bind_exec`(UDP 开 Socket)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`(含沉降期主动 close);`net_close_locked` 的 CLOSE 已改经 `ch395f_cmd_done` / `ch395f_sock_reset_*` | TRAP-NET-09:close 后需沉降才能重开 |
| `ch395f_tcp_listen` / `ch395f_tcp_connect` | `net_listen_locked` / `net_relisten_locked`;`net_connect_locked` | — |
| `ch395f_set_proto_type` / `set_sour_port` / `set_des_ip` / `set_des_port` | `net_bind`(UDP)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked`、`net_sendto` | open 之后写端口/IP(open 会复位寄存器);set_des_ip 期望**大端数组** |
| `ch395f_write_send_buf` | `net_send_locked` / `net_send_pending_tick`(挂起 send 推进)/ `net_sendto` | 单次 ≤1KB(硬件 FIFO 上限,TRAP-NET-16) |
| `ch395f_get_recv_len` / `ch395f_read_recv_buf` | `net_recv_locked`、`net_pending_tick`、`net_pending_settle_close`、`net_recvfrom`、`net_drain_chip_rx`、`handle_disconnect_event`(M3 可疑门 RX 非空判据) | `handle_recv_event` **故意不读**(防竞争,旧机理未证实) |
| `ch395f_set_proto_type` / `set_sour_port` / `set_des_ip` / `set_des_port` | `net_udp_bind_exec`、`net_udp_set_peer_exec`、`net_udp_sendto_exec`(2026-11-03 起 UDP 三条都只在 netTask 内跑)、`net_listen_locked`、`net_connect_locked`、`net_relisten_locked` | **两种顺序都存在,别当成一条**:TCP 各路径在 open **之后**写(open 会复位寄存器,TRAP-NET-14);UDP bind 按手册 §9.2.4 在 open **之前**写完 5 条——两侧都有钉子(TCP 见 TC-NETSOCK-410 组,UDP 见 ns_s7_udp.c UDP-1f 的命令顺序断言);set_des_ip 期望**线序数组**(`arr[0]` = 点分第一段) |
| `ch395f_write_send_buf` | `net_send_locked`(TCP 与 **UDP sendto 共用**,2026-11-03)/ `net_send_pending_tick`(挂起 send 推进,同样两侧共用) | 单次 ≤1KB(硬件 FIFO 上限,TRAP-NET-16) |
| `ch395f_get_recv_len` / `ch395f_read_recv_buf` | `net_recv_locked`、`net_pending_tick`、`net_pending_settle_close`、`net_udp_recvfrom_exec`、`net_udp_tick`、`net_udp_settle_close`(UDP 剥头与截断排空都在这三处)、`net_drain_chip_rx`、`handle_disconnect_event`(M3 可疑门 RX 非空判据) | `handle_recv_event` **故意不读**(防竞争,旧机理未证实);一次收包**只读一次长度**——剥头量/载荷量/排空量必须由同一个数字算出(`net_udp_datagram_read` 的 `avail` 参数) |
| `ch395f_get_socket_status` | `handle_disconnect_event`(两处甄别——事件路径**主动 SPI**,缓存必然滞后于刚到达的中断)、`net_relisten_locked`(沉降门)、`net_connect_locked`(诊断回读)。⚠ **周期对账不再直接调**——改读缓存(下行) | 输出 **2 字节**([0]=Socket 状态 [1]=TCP 状态),缓冲必须给 2 字节 |
| `ch395f_get_sock_state` / `ch395f_sock_state_cached` | 刷新**仅 `net_poll`** 每轮对在用 Socket 调 `get_sock_state`(唯一刷新点);`sock_state_cached` 读缓存 = `net_reconcile_tick`(形态①②③④⑤/U11 判据) | 芯片状态唯一事实源;E2 实测单次回读 18.1µs,缓存新鲜度 ≤1ms |
| `ch395f_sock_reset_begin` / `ch395f_sock_reset_poll` | `net_sock_reset_sync`(`net_listen_locked` / `net_connect_locked` open 重试、`net_close_locked`、`net_drop_conn_locked`);`net_poll` 每轮对在用 Socket **步进**(IDLE 槽纯 RAM 判零 SPI) | 驱动复位状态机:读空 RX → CLOSE → 排空事件(quiescent) → 等 byte0=00H;内置 LISTEN 态二段 CLOSE 补发(E2 补充发现);GRACEFUL 100ms 底 / QUARANTINE 400ms 底 |
@@ -613,7 +613,7 @@ net_pending_capture_owner() ← 捕获执行者
| 指标 | 值 | 来源 / 条件 |
|---|---|---|
| 队列响应性(挂起连接下) | **92.5 ms** | `test/host/e02_probe.py`:挂起对时客户端 + FTP banner 探测 |
| 队列响应性(挂起连接下) | **92.5 ms** | 台架实测(探针 `e02_probe.py` 已删除——2026-11-04 随 :8888 退役,正文见 git 历史):挂起对时客户端 + FTP banner 探测 |
| M3 静默档帧周期 | med 58.9 / p95 69.9 / max 72.1 ms | `docs/测试说明/专项验证/链路延迟测试说明.md` §6.3(零尖峰) |
| M3 压力档帧周期 | med 60.1 / p95 66.4 / max 69.3 ms | 同上(零尖峰) |
| netTask 栈峰值 | **1116 B / 3840 B(余 70%)**,FTP 传输期峰 **1164 B**;静态最坏链 ≈1.9 KB | `docs/内存管理/RAM使用分析与优化方案.md` §2.3 |
+13 -7
View File
@@ -32,7 +32,8 @@
> - 带连字符的 `E-<nn>` = **历史审计发现**编号(属主文档已于 2026-10-15 删除,
> 编号冻结于 `docs/编号规范.md` §4.3)——
> §5.2 末行引用它是为了记录该发现的**收口台架**,**不是**实验编号
> (证据:`test/host/e02_probe.py` 的文档串自述它是该审计发现的 timeSync 触发形态探针);
> (证据:`test/host/e02_probe.py`(已随 :8888 退役于 2026-11-04 删除,正文见 git 历史)
> 的文档串自述它是该审计发现的 timeSync 触发形态探针);
> - 该审计编号族已冻结、不再新增,引用一律改说人话。
## 1. 为什么是这个架构
@@ -111,7 +112,8 @@ fd 别名让这个边沿**必须有人接**,而"谁重建监听"在类型上
1. **不支持多监听**——`docs/问题记录/CH395F_Trap_Records.md` 附 FTP 改造清单第 4 条
「CH395F 多连接模式不支持多监听(类型:架构)」,处置即 `fun_para=0x00`。
本产品有 21/8888/7003 **三个常驻监听** + 会话期动态 PASV 监听。
本产品有 21/7003/2404 **三个常驻监听** + 会话期动态 PASV 监听(当时第三个是 :8888 对时,
2026-11-04 退役后由 104 :2404 在列——结论不依赖具体端口组合,见墙钟说明 §15)。
2. 池分配按**源端口匹配**(`docs/芯片手册/CH395F.md` §9.2.6 L1295),多监听器的池会互相串。
3. 实测:「多连接模式下最多接受 3 个客户端(Socket 1~3),Socket 4~7 的 CONNECT 中断从不触发」、
「多个 Socket 数据互相覆盖」——即 TRAP-NET-02/-11。
@@ -612,8 +614,9 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
> 「假设 → 方法 → 判据 → 它门禁哪个决策」四段式;本表只给**方法与结论**,原始记录(假设、
> 原始日志、统计)已归档,取用命令见 §12。
> ⚠ **这些编号是冻结标识**:`net_socket.c` / `net_events.c` / `net_listener.c` 注释、
> `test/host/e02_probe.py`、`test/host/e6_soak.py`、`docs/问题记录/CH395F_Trap_Records.md`
> 都直接引用它们,**重排或改名会断链**。
> `test/host/e6_soak.py`、`docs/问题记录/CH395F_Trap_Records.md`
> 都直接引用它们(原 `test/host/e02_probe.py` 已删除——2026-11-04 随 :8888 退役,正文见 git 历史),
> **重排或改名会断链**。
> ⚠ 末行 **不是实验编号**——它记录的是**历史审计发现**
> (带连字符的 `E-<nn>` 族,属主文档已于 2026-10-15 删除并冻结),列在此处只为记录该发现的**收口台架**。
> 区分规则:**带连字符 = 审计发现,无连字符 = 自愈实验**。
@@ -640,7 +643,7 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
| **E17** | **单一所有者收口**:`net_listener_try`(当场动作、**无状态门**,各调用点自带门)改为 `net_listener_request`(只登记意图)+ `net_listener_tick`(唯一执行者,门集含"芯片已在 LISTEN 即了结"),同板全量回归 | 全绿;**延后一轮(≤1 ms)执行不伤即时性**(恢复窗 p50 与当场动作持平);relisten 留痕减半 | §4.2 登记/执行分离;§9.2 门集;§11.1 判据 |
| **E18** | **负向注入打靶 · 三轮**(注入代码不入库):① 吞掉 DISCONNECT(在 i104 的 200 ms accept 轮询窗内 RST);③ 绕过本层簿记直接 close 芯片空闲监听;⑤ 开盲区吞掉芯片自发的中断 + 等芯片真 CLOSED 再 open+listen | **①③⑤ 全部命中并救回**;①⑤ 的前提会被更快的消费者路径消解 → 常态 0 命中;⑤ 还要先越过芯片 `FIN_WAIT_2`(E15 的 2MSL 长尾,实测 ≈1 s) | §4.1.4 / §4.1.5:**三条均不可降级为只留痕**;⑤ 的**取消挂起 recv/send** 不可省 |
| **E19** | **`force open` 反事实 A/B**(本层已收口 CLOSED + 芯片停 `FIN_WAIT_2` `0x05/0x06` + **对端挂住不关** = 队头阻塞场景;A = 现状放行,B = 只留痕不放行) | **放行与不放行的端口恢复时刻同一分布** → 动作在全部可产现场景内无可测收益;真正兜住"监听器永久失效"的是 **D10 预算 + 形态④ 重新登记**。两条实测坑:① 本层直接改 `state` **不会**进沉降窗(窗只由 `net_listener_settle` 开启);② `relisten retry budget exhausted` 在此被稳定复现 = 对端不配合拆链的签名,**非回归** | §4.3:800 ms 上限降级为**只留痕**(`keep waiting` 只留首末、隔离标志不清 → 继续排渣) |
| **队头阻塞审计**(非实验编号) | `test/host/e02_probe.py` 台架定案 `net_drop_conn` 收口后的时序 | 对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150 ms → 形态④ relisten,恢复全程 ~500 ms;对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close** | §4.6 |
| **队头阻塞审计**(非实验编号) | `net_drop_conn` 收口后的时序由台架定案(探针 `e02_probe.py` 已删除,2026-11-04 随 :8888 退役,正文见 git 历史) | 对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150 ms → 形态④ relisten,恢复全程 ~500 ms;对端内核存活但不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close** | §4.6 |
## 6. 红线(明确不做的事)
@@ -723,15 +726,18 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回)
| `net_listener_armed` | 唯一监听门控;**必须放行 ESTABLISHED**(§4.2) |
| `net_listener_tick`(唯一执行者) | 门集**只在这一处**:状态 ∈ {LISTENING, CLOSED} + 监听型 + **芯片已在 LISTEN 即视为达成、绝不 open**(U12 结构性防护)+ 挂起保险。登记侧 `net_listener_request` 因此**不需要**状态门——任何"当场动作 + 调用点各自补门"的写法都会把 U12 风险复制 N 份(对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭分支、**关掉正听着新 SYN 的 Socket**) |
### 9.3 三个服务的连接结束契约(现状)
### 9.3 长驻网络形态的连接结束契约(现状)
| 服务 | 连接结束信号 | 监听重建者 | 备注 |
|---|---|---|---|
| `lftpd` 控制 :21 | `read_line != 0`;阻塞挂起 recv 保证 CLOSED 时必被结束(FIN→0 / 异常→CONNRESET) | net 层;应用**永不** close 控制 fd | 契约一致,**正面样板** |
| `time_sync` :8888 | EOF / 预算耗尽 → `net_drop_conn` 拆会话保监听 | net 层(形态④) | 同 fd 即监听 fd,应用侧**不得** `net_close`(红线 8)→ 走 §4.6 |
| `ui_net_sink` :7003 | `net_recv` 返 0 或真错误;非阻塞收帧 | net 层 | 重建条件**不得**写成 `net_get_sock(g_listen)==NULL`(fd 不释放 → 恒假);靠 `session_dead` 电平 + 形态④ |
| 模拟器后端 | `recv` 0 恒可得、fd 分离 | 无需 | Winsock 原生;对上述全部失效模式**零覆盖**(D6:双模型分裂暂不消除) |
> **历史行(:8888,2026-11-04 退役删除)**:`time_sync` :8888 | EOF / 预算耗尽 → `net_drop_conn`
> 拆会话保监听 | net 层(形态④)| 同 fd 即监听 fd,应用侧**不得** `net_close`(红线 8)→ 走 §4.6。
> 退役后本表不再有应用侧 `net_drop_conn` 调用者(原全仓唯一调用者 `time_sync.c` 已随监听删除)。
## 10. 决策记录
| # | 决定 | 依据 |
+1 -1
View File
@@ -80,7 +80,7 @@
| [`cubemx-survival.md`](cubemx-survival.md) | `cubemx-survival` | CubeMX 重生成会不会把定制静默抹掉(人工目检审计兜底) | `.ioc`、`Src/`、`Inc/` | 已写 |
| [`test-suite-switch.md`](test-suite-switch.md) | `test-suite-switch` | 板级套件开关必须互斥;全注释 = 产品构建 | `test/board/test_config.h` | 已写 |
| [`font-charset.md`](font-charset.md) | `font-charset` | UI 用到的汉字是否都在子集字体里(缺字 = 白框) | `Drivers/BSP/UI/**`、字体脚本 | 已写 |
| [`ui-golden.md`](ui-golden.md) | `ui-golden` | UI 像素回归:菜单 8 状态 + 内容页 66 张黄金参考图比对 | UI 源码、模拟器 | 已写 |
| [`ui-golden.md`](ui-golden.md) | `ui-golden` | UI 像素回归:菜单 8 状态 + 内容页 74 张黄金参考图比对 | UI 源码、模拟器 | 已写 |
| [`bom-guard.md`](bom-guard.md) | `bom-guard` | 带 BOM 的文件被编辑工具剥掉 BOM(ARMCC 换解码 → 假语法错) | `Drivers/BSP/UI/**` 等名单内文件 | 已写 |
| [`setview-check.md`](setview-check.md) | `setview-check` | 定值查看行表与 `prot_set` 枚举序 / `g_prot_set_meta` 上下限是否同一份口径 | `tools/setview_check/**`、`ui_w_setview.*` | 已写 |
| (专篇待补,档位与限度正文在 [`../UI设计/UI_自动化测试流程.md`](../UI设计/UI_自动化测试流程.md) §7.3) | `uinav-selftest` | 板端 UI 导航器认屏快检(黄金参考帧分类;完整闭环档=合入前/上板前手工跑,快检不互替) | `scripts/board_ui_nav.py`、`simulator/tests/ref/*.bin` | 指针 |
+3 -3
View File
@@ -1,4 +1,4 @@
# `ui-golden` — UI 像素回归(菜单 8 状态 + 内容页 66 张黄金参考)
# `ui-golden` — UI 像素回归(菜单 8 状态 + 内容页 74 张黄金参考)
> 一句话:改 UI 源码时,把模拟器增量重编 + 两条 golden 回归串成一次提交门禁,**像素对不上就拦**。
> 它是包装器:真正的判据在被包装的两条脚本与参考图里。
@@ -11,7 +11,7 @@
## 1. 它在查什么
- 菜单侧:整棵菜单树的行渲染结果(8 个状态)与参考图逐像素比对;
- 内容页侧:66 张黄金参考(= 66 个截图态条目,同一内容页的多个状态各一张:分页页多拍的第 2 屏与确认链/录波的各态都单独计条目),逐张与参考图比对;
- 内容页侧:74 张黄金参考(= 74 个截图态条目,同一内容页的多个状态各一张:分页页多拍的第 2 屏与确认链/录波的各态都单独计条目),逐张与参考图比对;
- 参考图 `simulator/tests/ref/*.bin` **就是判据本身**。
## 2. 为什么要它(有牙的实测)
@@ -57,7 +57,7 @@
1. **参考图即判据**:刷新参考图 = 修改判据。评审时必须盯 `simulator/tests/ref/` 的 diff;
一旦"顺手更新",此后所有回归都只在和错的基线比。**门禁无法阻止这件事**,只能靠评审。
2. 只覆盖 8 个菜单状态 + 66 张内容页黄金参考:未列入的状态(密码错误态、超长文本、缺字渲染、
2. 只覆盖 8 个菜单状态 + 74 张内容页黄金参考:未列入的状态(密码错误态、超长文本、缺字渲染、
无 SD 卡 / 挂载失败的降级显示)**不会有任何回归**。
3. 跑的是**模拟器那份配置**,不是固件那份 —— 两份 `lv_conf.h` 漂移时,回归结论不能外推到固件
(所以 `lv-conf-sync` 是本门有效性的前提)。
+15
View File
@@ -33,6 +33,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
| TRAP-NET-19 | SET_MAC 顺序 | ✅ | §9.2.1:SET_MAC 必须在 `INIT_CH395` **之前**(INIT 从 EEPROM 加载) |
| TRAP-NET-20 | FTP 数据连接三连故障 | ✅ | 三重定案:状态回读补 accept + 幽灵甄别 / closed-with-data / 背靠背小段毒化 TX("丢中断"归因已被 E14 复核推翻) |
| TRAP-NET-21 | IP 显示段序与对端地址 | ✅ | `net_inet_ntoa` 段序 MSB 首;对端地址用 `net_getpeername`,勿用 `getsockname` |
| TRAP-NET-22 | UDP 形态与剥头不一致 | ✅ | DesIP 决定有无 8 字节信息头;旧 `sendto` 静默把芯片切成 Client 形态而 `recvfrom` 仍剥头 → 应答丢前 8 字节 |
---
@@ -204,6 +205,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
- **现象**:UDP 发送后立即接收,接收长度恒 0 或数据错误。
- **根因(定案)**:手册要求每次 `WRITE_SEND_BUF` 后必须等 `SINT_STAT_SENBUF_FREE`,否则下次写入被静默丢弃。
- **修复**:每次 `ch395f_write_send_buf()` 后轮询 `ch395f_get_sock_int_status()` 检查该标志。
- **现状(2026-11-03 UDP 队列化后)**:UDP 发送已改由 netTask 内的 `net_send_locked()` 执行(与 TCP 同一条分块 / 等 SENBUF_FREE 路径,见 `docs/网络/BSD_Socket_API_函数调用关系.md` §4.11),本条要求由该实现统一满足;调用方上下文不再直接碰 SPI,也不再自旋。
## TRAP-NET-14 · NET 层目的 IP 字节序写反 → CONNECT TIMEOUT
@@ -235,6 +237,17 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
- **教训**:显示层字节序 bug 不影响协议本身(FTP 功能全程正常),但会系统性误导排障——**日志里的地址也要当被测对象核对**。
- **后续(2026-10-24):同一文件里的`s_addr` 两套约定已统一**。修完段序之后,本条只解决了显示侧——当时 `net_inet_addr` 仍属**另一族**(小端累加后又 `NET_HTONL`,返回 `0xC0A80164`),而 `net_inet_ntoa` / `net_getsockname` 属 POSIX 网络序那族(`0x6401A8C0`),两者**不互逆**;按前者写的消费者(`net_connect_locked` 用 `>>24` 取首段)恰好自洽,所以一直没人撞上。现已统一到 **POSIX 网络序一族**(判据 `&s_addr[0] == 第一段`;小端上 192.168.1.100 = `0x6401A8C0`):改 `net_inet_addr` + 出向 IP 数组改按内存字节顺位拆装(**进与出同时换族 → 芯片侧数组净效果不变**)+ 模拟器 `simulator/sim/net_socket_sim.c` 逐字同步;`net_inet_ntoa` 函数体一字未动。**为什么它能长期不被发现**:本条"修复"行下方 `ntoa` 自己注释里的例子(`192.168.1.100 → 0xC0A80164`)**取的是废弃族的值**——注释替错误背书,比没有注释更糟。规范定义见 `docs/网络/BSD_Socket_API_使用指南.md` §9.0;往返不变量由 TC-NETSOCK-100g / 100h 钉住。
## TRAP-NET-22 · UDP 形态(DesIP)与剥头不一致 → 剥头判据最终由真机定案
- **状态**:✅(2026-11-03 随 UDP 队列化改造登记;**2026-11-04 真机定案,本条的根因半句被实测推翻并改正**)
- **登记时的推断(当时未实测,全仓无生产 UDP 调用者、也无台架用例)**:`net_bind(UDP)` 后 `net_sendto()` 带目的地址、再 `net_recvfrom()` 收应答,会**少收前 8 字节**(SNTP 的 48 B 应答只剩 40 B,魔数与时间戳整体左移),且 `src_addr` 是垃圾。
- **根因(2026-11-04 08:16 真机定案)**:CH395F 的 UDP 两种形态**是在 OPEN_SOCKET 那一刻由 DesIP 定死的**(手册 §9.2.4 说的是"初始化步骤②",读的时候漏了"初始化"这个限定语):开 Socket 时 DesIP = `0xFFFFFFFF` → **UDP Server**,此后**每一个**到达包都**前置 8 字节信息头**(`[0-1]` 长度、`[2-3]` 源端口、`[4-7]` 源 IP);开 Socket 时 DesIP = 具体 IP → **UDP Client**,才不带信息头。开完之后再改 DesIP:实测**只改发送去向,不改收包帧格式**。
- **实测证据(PR #74 阶段 16 第一轮)**:板端 bind `:6001`(服务器形态)→ 收满一个 20 B 包后 `net_sendto` 带显式目的地址(DesIP 被改成 PC 的 IP,`udp_mode` = CLIENT)→ PC 发一个 48 B 应答 → 板端 `net_recvfrom` 读到 **n=56、缓冲首字节 0x38**(= 56,正是信息头的长度字段)。头**没消失**。本层 Socket 一律由 `net_bind` 在全F 形态下开出,所以**恒有头、恒该剥**;旧实现"无条件剥 8 字节"在载荷层面恰好是对的,登记的那条"丢 8 字节"现象并不成立("垃圾源地址"同理——头一直在,从里头取就是对的)。
- **修复(定案)**:`net_udp_datagram_read` 的剥头条件是 `avail >= 8`,**与 `udp_mode` 无关**,并在函数注释里写明"不许改成按形态判"——这条判据是本轮从"看着更严谨"的写法被真机打回原形的,代码里留了反例说明。`udp_mode` / `net_udp_set_peer()` 仍保留,但只描述**芯片的对端过滤与发送去向**(`net_sendto` 带地址 = 切 CLIENT 并记账,`net_udp_set_peer(fd, NULL)` = 轻切回 SERVER,只写两个寄存器、**不重开 Socket** 因而不丢已缓冲的应答);`net_bind()` 恒置 SERVER(BSD 语义里 bind 不选对端);不足 8 字节的残包无从判头,整包交付、源地址退到对端记账。运行时把 DesIP 改窄是否真会让芯片按源过滤掉其它主机的包,台架**还没测出结论**(本轮 1604 只验到"切回 SERVER 后换源端口的新包能收到"),所以 `udp_mode` 的过滤语义只在注释里按手册口径叙述,不当已证事实用。
- **为什么长期没人发现**:全仓无生产 UDP 调用者(只有 `test/board` 的硬件层用例,直接用 `ch395f_*`、不经这层 API),`docs/网络/BSD_Socket_API_使用指南.md` §6 甚至写着"本项目不使用 UDP"。
- **教训**:① **读手册推断出来的"现象"必须写成假设,不能写成已观测现象**——本条原来把推断直接写进"现象(改前)",于是"修复"也跟着错:按形态决定剥不剥的判据看起来比"恒剥 8 字节"更严谨,真机第一轮就翻车,而且翻得隐蔽(长度对不上、首字节是个看着像长度的数);② **芯片的模式是"开 Socket 时定死的隐式全局状态"**,写寄存器的时机比写什么值更关键(TRAP-NET-14 的 DesIP 顺序是同一条族);③ 形态切换仍然是一个真实的隐式状态迁移(它改发送去向),"顺手写一个寄存器"之前要问"这一下改变了什么、谁负责改回来";④ "没有生产调用者"不等于"被测过",而**主机假件按我们的理解脚本化,永远测不出理解本身错了**——假件按"CLIENT 不剥头"喂裸载荷,667 条全绿也照样漏掉真芯片的行为(`ns_s7_udp.c` UDP-1~25 + TC-NETSOCK-440 / 555)。
- **关联**:`docs/网络/BSD_Socket_API_使用指南.md` §6.0(形态表 + 谁负责切换)/ §6.3;`docs/网络/BSD_Socket_API_函数调用关系.md` §4.11;`docs/测试说明/单元测试/主机单测覆盖率口径.md`「第 4 轮」;`docs/测试说明/板级测试/CH395F_Test_Guide.md` §12.6(阶段 16 与实测结果);TRAP-NET-14(信息头里源端口小端、源 IP 点分内存顺位);TRAP-NET-10(收包出参为何不能搭 `net_msg_t`)。
---
# 第五章 上层集成与主机侧工具
@@ -253,6 +266,8 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修
- **根因(定案)**:`osMessageQueue` 传 `net_msg_t` 是**值拷贝**——netTask 修改的是队列副本,调用方栈变量的 `msg.result` 从未被更新。
- **修复**:结果搭 `xTaskNotify(caller, (uint32_t)msg.result, eSetValueWithOverwrite)` 通知值回来,调用方 `xTaskNotifyWait` 取回。**勿给 `net_msg_t` 加"回传用"字段**(调用方只会读到栈垃圾)。
- **注意**:指针成员(`msg.buf`)不受值拷贝影响;内核级 `_locked` API 直接操作不受影响。
- **现状补充(2026-11-03)**:UDP 队列化时"要回传的不止结果码、还有源地址"这一类需求,按本条纪律**没有**往 `net_msg_t` 加字段,而是走 per-fd 出参槽(调用方 arm 自己的内存,netTask 就地写回,通知值只带结果码)——机制见 `docs/网络/BSD_Socket_API_函数调用关系.md` §4.11,坑见 TRAP-NET-22。
- **后续(2026-11-03)· UDP 的出参怎么回来**:`net_recvfrom` 的 `src_addr` / `addrlen` 同样**不进** `net_msg_t`——既不能加"回传用"字段,也不建议把调用方指针塞进 `msg.buf` / `msg.len` 这两个已有语义的槽(`net_accept` 那么做是历史例外,它没有别的出参要带)。新模块 `net_udp.c` 改用 **per-fd 出参槽**:调用方 `net_udp_outparam_arm()` 登记指针 → netTask 执行体经该指针就地写回调用方自己的内存 → 通知值只带结果码。两条硬约束:**互斥**(两个任务收同一 fd 时,前者的源地址会被写进后者的缓冲)与**每条出口都清**(槽泄漏 = 该 fd 从此永久 `BUSY`)。
## TRAP-NET-13 · DHCP 成功后 IP 寄存器提交滞后
+1 -1
View File
@@ -1247,7 +1247,7 @@ def selftest_board_frames(clf):
def selftest_refs(clf):
"""仓库黄金参考(66 张)逐帧分类自检:每张图的期望值 = 它的文件名。
"""仓库黄金参考(74 张)逐帧分类自检:每张图的期望值 = 它的文件名。
这一组是**语义正确的基线**(模拟器与板端同一份 UI 源码),用来把"分类器错了"与
"留档帧本来就是走歪的"分开——两类失败含义完全不同,混在一起就只能靠猜。
+2
View File
@@ -105,6 +105,7 @@ SIM_EXEMPT = [
("App/fs/vol_idle_sync.c", "MCU 存储属主后台空闲同步(FatFs disk_ioctl + 卷锁 + sys_events),主机侧纯逻辑单测覆盖于 test/host/vol_idle_sync/,模拟器无消费路径"),
("App/log/console_log.c", "MCU 控制台输出,模拟器无消费路径"),
("App/sync/time_sync.c", "对时任务(net+RTOS),MCU 专属"),
("App/sync/time_sntp.c", "SNTP 校时客户端(net+RTOS+sys_clock 依赖,2026-11-04 立项落地),MCU 专属;主机侧纯逻辑单测覆盖于 test/host/sntp"),
("App/task/adc_task.c", "固件 FreeRTOS 任务,模拟器不跑固件任务"),
("App/task/default_task.c", "固件 FreeRTOS 任务,模拟器不跑固件任务"),
("App/task/iec104_serv.c", "固件 FreeRTOS 任务(104 上行,B4),模拟器不跑固件任务"),
@@ -134,6 +135,7 @@ SIM_EXEMPT = [
("Drivers/BSP/NET/net_listener.c", "MCU 监听器生命周期(依赖 ch395f_* SPI 驱动与 net_socket.c 静态状态),模拟器无对应(Winsock 后端无孤儿态/重监听概念)"),
("Drivers/BSP/NET/net_events.c", "MCU 芯片事件处理 + 100ms 周期对账(依赖 ch395f_* SPI 驱动与驱动状态缓存),模拟器无对应(Winsock 后端无\"事件未收到/静默回收\"概念,无失同步可收敛)"),
("Drivers/BSP/NET/net_pending.c", "MCU 挂起 recv 模型(依赖 ch395f_* SPI 读出与 FreeRTOS 通知),模拟器另有 net_socket_sim.c 按外部语义模拟"),
("Drivers/BSP/NET/net_udp.c", "MCU UDP 数据面执行器与挂起 recv 槽(2026-11-03 UDP 队列化改造;依赖 ch395f_* SPI 读写、net_send_locked 与 FreeRTOS 通知),模拟器另有 net_socket_sim.c 按外部语义模拟"),
("Drivers/BSP/RS485/rs485.c", "MCU RS-485 驱动"),
("Drivers/BSP/SD2506/sd2506.c", "MCU RTC 驱动"),
("Drivers/BSP/ScreenLink/sb_proto.c", "屏板链路协议层纯逻辑(帧 A 解析 / 帧 B 组帧 / CRC-16),消费者 = MCU 侧 USART1 收发起发(批 1/2),模拟器无消费路径;主机侧纯逻辑单测覆盖于 test/host/sb_proto"),
+1
View File
@@ -85,6 +85,7 @@ def subsystem_of(obj):
"task_prof", "boot_stats", "vol_idle_sync",
"sys_", "cfg_", "dbg_", "console_log", "task_watchdog",
"sha256", "tpafe", "sd2506", "test_task", "time_sync",
"time_sntp",
"default_task")):
return "应用"
return "其它"

Some files were not shown because too many files have changed in this diff Show More