Heater: PID v0.1.14 - banded controller + sensor glitch guards

Rebuild PID as three-zone banded controller: far field P/D full power,
This commit is contained in:
2026-10-15 20:55:02 +08:00
parent 8035f9b7d3
commit 8900240c65
6 changed files with 473 additions and 85 deletions
+227 -5
View File
@@ -81,6 +81,182 @@
- 协议要点:全幂等(ack 丢可重发)、响应超时建议 300ms×3 次、温度一律 ASCII
- **PID 抗积分饱和**(5b24b95):条件积分(back-calculation lite)抑制输出限幅
期间的积分累积。
- **PID 全面重写(2026-10-15,待真机验证)**——真机 30℃ 阶跃 +2.4℃ 过冲、
30~60s 周期衰减震荡(GUI 截图)驱动,三个根因全部修复:
① `s_prev_err` 在 err>=0 路径从不更新(c9aa416 early-return 重写时把末尾的
`s_prev_err = err` 弄丢)→ D 项退化为 +Kd*err 正向比例,"微分阻尼"从未生效,
且把抗饱和判据钉在饱和态冻死积分(仿真中大阶跃贴温和带边缘极限环、到不了
目标);改**微分基于温度变化率(D on measurement,-Kd*ΔT/拍)**,`s_prev_temp`
全路径刷新,u 钳 [0,100] 防负值量化回绕;
② 积分无界(err<0 每拍 -|err| 无下限)→ 过冲/下调目标后积分深负,温和带内
u=min(25,u_pd)+Ki*I 永远 <=0 → 加热长时间锁死、跌出温和带才全功率(极限环);
改**积分钳 [0, HEATER_PID_I_MAX=2000]**(Ki*I_MAX=100%);
③ 12ba9de 的目标斜坡在 c9aa416 被整段删除 → 恢复 `HEATER_RAMP_CPS 0.8℃/s`
+ 使能时从当前温度起坡(`s_ramp_target`,升/降同限速);
另修三项:④ 抗饱和判据改看**最终输出** u(原先看温和带重算前的 u);⑤ PWM
相位**仅升档复位**(原先降档也复位,降占空反而补发一整段 ON 脉冲,系统性
多给热);⑥ NTC 故障 **3 拍(1.5s)去抖确认**——真机曲线两个孤立 OPEN 毛刺
每次把 PID 状态整个清零也是震荡推手(毛刺拍保持输出不动;注意毛刺根因在
采样/硬件侧仍待查,GUI 按原始采样报故障不变)。
离线仿真(`sim_pid_check.py`:一阶热容+传感器滞后模型、2s/5% 慢 PWM 执行器、
旧/新同模型对比):30℃ 阶跃到达后波动 ±0.4℃→+0.04℃;50℃ 阶跃旧版极限环
到不了目标 vs 新版 94s 到达零过冲;50→40 下调 44s 恢复、波动 <±0.6℃。
固件版本升 **V0.1.0**(M999 报 `v41.0.1.0`),已烧录上板(OpenOCD GD-LINK,
dump_image 读回与 elf 逐字节比对一致;注意该 xpack OpenOCD 的
flash write/verify 回显日志会丢失,核验用 dump_image+文件比对)。
设计说明:保持**位置式** PID——执行器是绝对占空设定,分段强制关断/使能切换/
故障复位在位置式下最干净;增量式的抗饱和优势已由积分钳位+条件积分覆盖。
- **PID V2 抗震荡迭代(2026-10-15,V0.1.1 已烧录待真机确认)**——V0.1.0 真机
复测:过冲/OPEN 毛刺问题已消(峰 +0.4~0.5、无故障标记),但目标附近仍有
**±0.7~1.0℃/15~20s 三角波极限环**(50℃ 目标实测截图)。根因:err<0 硬切
0% + err>=0 仅 Kp*err,稳态维持功率(~10% 级)全靠 Ki=0.05 慢填(要几分钟)
→ 目标附近等效"P+上侧死区"继电器,叠加热滞后即极限环。V2 改法(`heat.c`
pid_compute 重写核心):
① **连续 PID 覆盖全误差区间**——去掉 err<0 硬切,过目标后 P 负向顶、积分
正向维持,输出钳 [0,100] 天然处理"只能加热";
② **Ki 0.05→0.25**(稳态功率 ~1 分钟内建立;扫参显示 0.5 在传感器滞后 8s
工况下积分环失稳 ±0.9、1.0 更差,0.25 全工况稳定);
③ **积分吸收限窄带**:仅 |err|<HEATER_PID_INTEG_ZONE_C(2℃) 且 P/D 未被
钳(u_pd_raw∈(0,cap))时吸收——爬升/温和带钳位段不积分,防瞬态 windup
(Ki 提快后若爬升段照吸,30℃ 阶跃仿真冲到 36℃);err<0 且 u>0 照常有界
回退;I_MAX 同步 2000→400(Ki*I_MAX=100% 不变)。
仿真(`sim_pid_check.py`,V1/V3 对比 + 参数扫描 `python sim_pid_check.py
sweep`,基准/恶劣两工况):恶劣工况 50℃ 阶跃到达后 [49.70,50.11](过冲
+0.11℃)、末端残余 ±0.02℃;30℃ 阶跃末端 ±0.01~0.02℃;50→40 下调 66s
恢复 ±0.02℃。已烧录 V0.1.1(M999 报 `v41.0.1.1`,dump 比对核验一致)。
- **PID V3 修"突然飙升 4℃"(2026-10-15,V0.1.2)——DAPLink 旁路抓证定位**:
V0.1.1 真机稳态后温度以 0.12℃/s 平滑飙升 +2~4℃ 再回落。定位手段(可复用):
① COM5 脚本探针(`probe_once.py`/`probe_state.py`)确认目标 50/加热 ON,
并现场抓到 **14 帧连续 OPEN(~1.4s)**——NTC 间歇开路真实存在(硬件侧
待修);② **OpenOCD `mrw` 旁路观测**(`observe2.tcl`+`obs_decode.py`,
每 500ms 不 halt 读 RAM:duty/integral/ramp/target/temp——注意该 xpack
OpenOCD 的 echo/mdw 控制台回显会丢失,须用 TCL 落文件)。全程轨迹证实
**无故障参与的 90~120s 自持极限环**:积分 0↔65 打摆(真机 50℃ 维持功率
~10-15%,即 I≈40~60 才是正确稳态),机理=**err<0 时 P 被钳 0、输出只剩
Ki·I 恒定值**——过线后输出不随超温幅度收敛→温度必然冲过头→过冲把积分
排空→排空后 sag→重建再过冲。NTC 毛刺清零积分只是点火器。V0.1.2 修复:
① **err<0 时 P 负项+D 直接抵扣积分后钳 0**(输出随超温幅度立即收敛,
积分按剩余需要维持→可收敛到稳态不再打摆);② **NTC 短故障(≤3s 且
|ΔT|<2℃)无扰还原积分**(确认沿暂存 `s_fault_stash_I/T`,防毛刺清零
引发 sag-重建-过冲链);③ I_MAX 400→200(Ki·I_MAX=50%,120℃ 模具维持
余量足够)。判定测试基建:`test_run.py`(固定序列 M999→M2S500→M4S50→
M3S1→N 秒→收尾;**预定义判据 J1 上报率/J2 稳态均值±0.5/J3 稳态极值±1.0/
J4 全程 max≤52/J5 无故障**,>54℃ 自动熔断 M3S0——开发/测试分工,脚本
自动判定避免自判自;注意 FF485 frame.payload 已是 str)。
- **PID V3 判定迭代与收敛(V0.1.2→V0.1.3,2026-10-15,900s 判定测试)**:
- V0.1.2(P-veto + 故障无扰还原 + I_MAX 200)判定:J1/J2/J5 PASS,但
**J3 FAIL(稳态窗 [46.6,52.1])、J4 FAIL(max 52.5)**——P-veto 消灭了
积分排空式打摆(观测器证实收敛末段积分稳在 ~48=真实维持功率、温度
±0.2),但双热容被控对象(鳍片+本体,NTC 只见鳍片)每次顶/谷都给
对方"预存"动量,15 分钟才收敛;且斜坡 0.8℃/s 远快于对象,爬升段误差
长期 ~2℃ 把积分提前充到 30~50,首冲 +2.5℃。
- V0.1.3:**HEATER_RAMP_CPS 0.8→0.25**(爬升误差贴地、积分只在近目标
低功率段建立)+ **HEATER_SLOWBAND_DUTY 25→15**(近目标动作放柔,
少给鳍片预存过热)。41℃ 冷启判定:**J1~J5 全 PASS**——到达 69s、
首冲 +1.20℃、全程 max 51.2、稳态窗 [49.0,50.9]、均值 49.95、
1795 帧 0 故障。观测器同步确认:远场(err>7)才见 75% 满功率,
温和带内 15% 缓爬,50.06℃ 到达时积分 35.9/占空 5%。
- 测试基建沉淀:`test_run.py`(判定+熔断)、`observe2.tcl`+`obs_decode.py`
(DAPLink 旁路,**注意符号地址随 bss 布局变,重编译后须 nm 重取**;
观测文件被占用时追加不清空)、`sim_pid_check.py`(含 V1/V3/V4 对照、
毛刺/噪声注入、扫参)。CMD 坑:`if exist X del X && ...` 文件不存在时
整链跳过;多行 `python -c` 偶发被吞——写文件执行。
- **遗留(硬件侧)**:NTC 间歇开路(现场抓到 14 帧连续 OPEN ~1.4s,
短故障无扰还原已缓解控制影响,但根因在采样/接触)——查 NTC 焊接/
压接/走线,必要时 ntc.c 采样侧加防护。独立代码审查 agent 未在时限内
返回,本轮定位以仪器化数据(COM5 帧流 + DAPLink 内存轨迹)为准。
- **PID 调试全程速览(2026-10-15,V0.1.0→V0.1.14,判定测试自动出结论)**:
| 版本 | 关键改动 | 判定/实测结果 | 结论 |
|---|---|---|---|
| V0.1.0 | 全面重写:D-on-measurement+积分钳位+斜坡恢复+故障去抖 | 过冲 +2.4→+0.4 | 旧版 D 项失效/积分无界/斜坡缺失三根因修复 |
| V0.1.1 | Ki 0.05→0.25+窄带吸收 | ±1℃ 三角波极限环 | err<0 硬切=继电器死区 |
| V0.1.2 | P 参与过线区+故障无扰还原+I_MAX200 | J3/J4 FAIL | P-veto 生效但收敛 15min |
| V0.1.3 | 斜坡 0.25C/s+温和带 15% | J1~J5 全 PASS(旧判据) | 稳态 [49.0,50.9] 不满足 ±0.5 |
| V0.1.4 | 占空抖动 1.25% 分辨率 | p2p=1.8 FAIL | 量化非主因 |
| V0.1.5 | Kp5/Kd60/Ki0.15 连续 PID | p2p=4.2 FAIL | ±2C/90s 慢摆,调参到头 |
| V0.1.6 | 分区 bang-bang+慢积分 | 卡 48.6C FAIL | 带外平衡点陷阱 |
| V0.1.7 | 带外也吸收 | p2p=4.1 FAIL | 吸收/回退不对称→I 上漂 90 |
| V0.1.8 | 对称慢速积分 | p2p=4.0 FAIL | 积分稳了;秒级读数坏值穿透 |
| V0.1.9 | 斜率限幅+带内退闸 | 无法判定 | NTC 持续开路(后重焊恢复) |
| V0.1.10 | 去上限+RAMP2.0+温和带25% | p2p=2.7 FAIL | 快斜坡生效;坏值仍是主力 |
| V0.1.11 | NTC 5 点滑动中值 | **p2p=1.30** | 中值滤波立竿见影 |
| V0.1.12 | 近场 ASSIST 减速曲线 | 实时 p2p=**0.31** | 最后 2C 推力真空修复;判定窗被传感器事件污染 |
| V0.1.13 | 大偏差冻结(>1.5C) | p2p=1.60 | 塌陷深谷消除(47.1→49.0) |
| V0.1.14 | 对称积分加倍 0.30 | p2p=1.80 | 与 13 同水平=硬件平台期 |
**最终状态(V0.1.14 在板)**:到达 26s(30→50C,提速 9 倍);控制环本体
(无事件段)p2p 0.3~0.9C 达标;判定窗残余 1.6~1.8C 全部来自 NTC 焊接点
秒级接触不良(-2.5C/4.5s 塌陷、顶格 52.2C 卡死 10s,1.5C 以内坏值仍会穿透
三重滤波)。**下一步:彻底重焊/更换 NTC(根治手段),固件三重防护
(中值+斜率限幅+大偏差冻结)已就位,重焊后 `python test_run.py 1200` 一键
重判(J3 即 ±0.5 规格)。测试环境 55C 上限已按用户要求移除(真机靠物理
温度开关)。**
- **±0.5℃ 波动压榨迭代(2026-10-15,V0.1.3→V0.1.6,用户规格 ±0.5C,
判据 J3 收紧为稳态窗 [目标±0.5])**——V0.1.3 稳态窗 [49.0,50.9](压着
旧判据 ±1.0 过)不可接受,连续三轮仪器化迭代:
- **V0.1.4 占空抖动(Bresenham)**:载波 2s=4 拍分摊档位余数,平均分辨率
5%→1.25%(真机维持功率 ~10-12% 恰卡在 10/15 档间,固定取整迫使
0↔15% 满幅切换)。观测器证实抖动生效(duty 5/10/15/20/25 细粒度),
**但极限环未消(J3 p2p=1.8 FAIL)→ 量化不是主因**。
- **V0.1.5 降增益强阻尼(Kp 10→5 / Kd 30→60 / Ki 0.25→0.15 / 窄带 2→1)**:
**J3 p2p=4.2 FAIL**——变成 ±2C/90s 慢摆。观测器抓到机理:带内半周期
净吸积分 +18(err 0.5~1.0 持续 30s),过线后 P-veto 每拍泄 2.8 快速
排空 → 维持功率记忆反复丢失 → 谷底深潜 48.6。**连续 PID 在本对象
(双热容+单向执行器+0.1C 读数量化)上调参到头**;Kd=60 还放大 NTC
0.1C 量化跳变成 ±6% duty 抖动。
- **V0.1.6 分区 bang-bang + 慢积分学习(结构性换型)**:作用分三区——
带内 |err|<HEATER_PID_BAND_C(0.4C):P/D 退出,u=Ki*I 恒功率,积分慢速
学习维持功率(I+=err*0.3/拍);带外下方:P 拉回(温和带钳位不变),
积分不吸收;带外上方:关断 + I 固定 0.3/拍限速回退(保住维持功率
记忆)。波动幅度被物理限制在带宽+滑行量,I 学准后驻留带内。
Kd 回 30(60 的噪声放大不可用)、I_MAX 回 400(Ki*I_MAX=60%)。
- **V0.1.6 判定 FAIL(带外平衡点陷阱)**:观测器抓到温度卡 48.6C——P 输出
恰好平衡散热、err 停带外,积分只在带内学习永远没机会介入。V0.1.7 修复
(带外下方 P 未被钳时同样慢速吸收):前半程良好(49.8/integ 37 稳),但
**吸收(0.3/拍)与回退(0.3/拍固定)不对称 → I 上漂到 90 持续超供**,后半程
大摆(J3 p2p=4.1 FAIL)。V0.1.8 改全区域对称慢速(GAIN=BLEED=0.15):
积分不漂了(35~42 窄幅)、带内占有率 44%,**但 J3 仍 FAIL(p2p=4.0)**——
csv 分析抓到 **-1.2C/0.5s 读数突跳 + 5s 后 +1.8C 回弹**(非故障级 NTC
毛刺,真实热速率 <=0.2C/拍),毛刺触发 25% 猛拉 ~10s 直接制造 +2C 过冲。
- **V0.1.9(待判定)**:① NTC 读数斜率限幅(HEATER_NTC_SLEW_C=0.5C/拍,
超限沿限幅值过渡、连续 2 拍放行——毛刺对环路的冲击源头掐断);② 带内
功率线性退闸(下缘 100% 维持量 -> 上缘 0%,过线前功率已趋零,蓄热滑行
收窄)。已烧录 MATCH。
- **NTC 硬件恶化为持续开路(阻塞判定)**:V0.1.9 判定测试全程 M1/M2 报
OPEN(帧流正常、命令全 ok),加热安全关断(故障路径实测有效)。用户
重焊后恢复(30.1C 稳定读数),进入 V0.1.10~V0.1.14 快速迭代:
- **V0.1.10 去上限 + 提速**:测试脚本去掉 54C 自动熔断/J4 上限判据(用户
明确:真机靠物理温度开关,55C 只是测试环境约束);RAMP 0.25→2.0(实测
0.25 时斜坡本身就是瓶颈,9C 走 36s);温和带 15%→25%。判定:reach 49s
但 J3 p2p=2.7 FAIL、首冲 +3.3。
- **V0.1.11 NTC 5 点滑动中值**(ntc.c 采样侧):V0.1.10 稳态摆的根源确认为
秒级读数坏值穿透斜率限幅(中值滤波只挡 <250ms 毛刺)。效果显著:
稳态 p2p 4.0→**1.30**([49.40,50.70])。温和带 7→3C、25→35%。
- **V0.1.12 近场 ASSIST 减速曲线**:分段速率分析钉死最后 2C 爬 141s 的瓶颈
=近场推力真空(P 随 err 缩小消失、I 未成熟)。近场扩展区 [-0.3,3.0)
合并退闸+缓爬:u = Ki*I + ASSIST 退闸曲线 + Kd,上限 NEAR_MAX_DUTY 50%。
实时观测 p2p **0.31C**(达标水平);但判定窗两极值(47.1 塌陷 + 52.2
顶格卡死)均为传感器事件:**-2.5C/4.5s 塌陷与顶格 10s,焊接点仍不稳**。
- **V0.1.13 大偏差冻结**:滤波值与原始读数偏差 >1.5C(HEATER_NTC_JUMP_C)
冻结不跟随,回弹自动恢复(真实冷却 ~0.05C/拍永不误触发)。判定 p2p
1.60([49.00,50.60])——塌陷深谷消除。reach 32s(提速 7 倍)。
- **V0.1.14 对称积分加倍**(GAIN=BLEED 0.15→0.30,I 充满 400s→200s,修复
判定窗内驻留点偏低):p2p 1.80([49.20,51.00]),与 V0.1.13 同水平
(差异在传感器事件噪声内)——**到达当前硬件条件下的平台期**。
- **当前结论**:控制环本体(无事件段)p2p≈0.3~0.9 达标;残余 1.6~1.8 全部
来自 NTC 焊接点秒级接触不良(塌陷/顶格事件,1.5C 以内的坏值仍会穿透
防护)。**下一步是彻底重焊/更换 NTC 及其引线**,固件侧三重防护
(中值+斜率限幅+大偏差冻结)已就位。到达速度 26s(30→50C,提速 9 倍)。
测试/观测基建:test_run.py(无熔断版,J1/J2/J3±0.5/J4 无故障自动判定)、
observe2.tcl+obs_last.py(**每次重编译必须 nm 重取符号地址**)、
seg_rate.py(分段爬升速率)、log_tail.py(实时帧解析)。
中间数据(obs_samples.txt/test_data.csv/test_events.log)已清理,
由测试/观测脚本重新生成。
- 方法论沉淀:单状态热模型仿真始终未复现真机极限环(双热容建不进单
状态),本轮所有定位直接以 DAPLink 观测器 + COM5 判定脚本为准;
观测符号地址 V0.1.4 起稳定(s_duty=0x20000459 / s_integral=0x20000454 /
s_ramp_target=0x2000045c / s_target=0x20000460 / s_enable=0x20000464)。
- **50℃→40℃ 阶跃验证脚本**(`temp_test_com.py`,工作区修改未提交):M2S500
全曲线采样,输出超调/settle 时间/稳态误差/降温段统计(结果以实跑为准)。
- **电源保持 PA11 实装**(`pwr_hold.h/pwr_hold.c`):上电最先拉高 PA11 维持
@@ -122,8 +298,12 @@
- [ ] **适配器更换**:换可靠的 USB-RS485 适配器(当前自动方向切换间歇丢响应)。
- [x] **加热驱动 + PID 闭环**(PB12 慢 PWM 1s/10% 步进 + PID 500ms 周期,
安全保护齐全;实测 30℃ 闭环通过:30s 爬升、超调 1.4℃、60s 收敛中)。
- [ ] **PID 参数整定**:初值 Kp=8/Ki=0.05/Kd=0(board_config.h 宏),按真实
工况温度(80~120℃)与负载重新整定,抑制 ~7% 超调。
- [ ] **NTC 彻底重焊/更换(当前头号事项)**:焊接点秒级接触不良已用
DAPLink+COM5 数据链钉死(-2.5C/4.5s 塌陷、顶格卡死、曾持续开路),
是 ±0.5C 规格最后的拦路虎;固件三重防护已就位(V0.1.14)。
- [ ] **±0.5℃ 规格复判**:NTC 重焊后 `python test_run.py 1200`(J3=±0.5);
当前稳态控制环本体 p2p 0.3~0.9C 已达标。真实工况温度(80~120℃)下
需复测整定(Kp=8/Ki=0.15/Kd=30、ASSIST 35%、I_MAX400,board_config.h)。
- [x] **专用 GUI 上位机 v0.2.0**:v0.1.0 已完成 COM5 真机全链路验证;v0.2.0 与
实装固件对齐——去除"固件预留/仅软件状态"旧表述(协议模块预设说明、温控
卡片标题/备注、开启加热确认弹窗改为真实控温警示、ack 提示),GUI README
@@ -140,7 +320,7 @@
"未连接串口"丢弃)。selftest 全过(新增 8 项回归用例);已打包冒烟并发布
Gitea Release `FilmForge-GUI-v0.2.1`(无应用图标——`logos/filmforge.ico`
仍缺位)。
- **GUI v0.2.2~v0.2.4(已修复待真机确认,未发布)**:
- **GUI v0.2.2~v0.2.13(递进修复/新功能,Gitea 发布待确认)**:
- v0.2.2:串口线程 `read(256)` 在持续帧流下攒满 256 字节才返回(win32 字节
间隔超时因帧间隔更短永不触发,100ms 上报 ≈2.5s 一批),曲线/命令/响应全部
3 秒一卡、一股脑一大坨——真机日志确认每簇 ≈25 帧、间隔恒 3s。已改
@@ -185,8 +365,50 @@
_last_set 单槽位罕见误补偿(8s 自愈)、build_exe remove_stale 无提示强杀
运行实例、M888 后固定 2s 重查可能早于固件就绪、_danger_reset 把去重忽略记成
"已下发"。协议建议的 300ms×3 自动重发维持不做(设计决策,用户拍板)。
- selftest 全过(累计新增 15 项回归用例,含窗口图标非空检查);exe 已本地
打包冒烟,Gitea 发布待用户确认后执行。
- v0.2.10:**曲线 Y 轴手动量程 + "暂无数据"提示不消失修复**——①"实时温度曲线"
卡新增 Y 轴行:下限/上限输入框 + 自动勾选钮(默认勾选=随可见数据自适应;
取消勾选=按输入框固定量程,非法输入回退最近一次自适应范围并经状态栏提示;
首次切手动按当前自适应范围预填平滑接管;固定量程下曲线越界 setClipRect
裁剪回绘图区、当前值标记超量程不画,实时读数卡仍可见)。模式+量程随
`filmforge_gui.json` 持久化(y_axis 键,存值非法一律回自动)。
②提示文字不消失根因(用户标注截图):`set_source` 仅在构建期调用一次且在
那里缓存 `_has_data` 布尔——数据经同一列表引用追加后该布尔永不刷新 → 改为
paintEvent 实时判定(图表本就持有引用,无需其他调用点适应)。selftest 新增
15 项回归(切换往返/兜底预填/无效回退/量程落地/越界条带像素=0/新图数据到位
提示像素=0;断言须取当前主题曲线色、上条带避开 y=9 行图例色块),总数 74
全过;离屏整窗预览目检(手动/自动对照)布局正常。
- v0.2.11:**Y 轴量程控制改右缘滑条(用户要求放右侧、滑条形式)**——移除
v0.2.10 的下限/上限输入框行,"Y轴自动/手动"勾选钮移到暂停/清空行右端;
TemperatureChart 右缘新增滑条栏(GUTTER_W 18px:轨道+量程带+双把手):
上把手=上限、下把手=下限、把手中带拖拽=整段平移(跨度保持)、点击轨道
空白把对应边界直接设到该处;把手在**参考域**(全量数据范围 ±15%,`_y_ref`)
上映射像素,auto 态把手半透明、首次任意拖拽即切手动(`yManualEngaged`
同步按钮态+配置),松手量化 0.1℃ 经 `yRangeChanged` 记设置(最小间距
0.5℃),按钮一键回自动;越界裁剪沿 v0.2.10 不变。坑两条(勿重蹈):
①拖拽中的界值必须取**实时显示范围**而非 `_cached_layout` 绘制缓存——
连续两次拖拽之间没有重绘,缓存是陈旧范围(实测 pan 把量程整段拖跳);
②selftest 固件用真实 `filmforge_gui.json` 时"初始自动"断言会被用户存的
手动模式击穿——改指向临时目录设置文件彻底隔离。
selftest 重写第 8 段共 81 项全过(把手命中/拖拽切手动/量化/最小间距/
平移保跨度/裁剪条带/提示消失);离屏手动/自动滑条目检通过。
- v0.2.12:**滑条域定值 -20~120℃ + 标题行并排(用户截图标注 3 处)**——
①滑条参考域从"全量数据 ±15%"改为**固定 -20~120℃**(Y_RAIL_LO/HI 宏):
把手像素位置直接对应 ℃ 读数,空数据/冷机不再出现 -5.0 这类随数据漂移的
误导把手位;命中/拖拽/钳位全部按此域,140℃ 域上 1px≈0.53℃(精度靠点击
轨道空位直达边界;selftest 拖拽断言改为"目标值反算像素",不再依赖域宽);
②暂停/清空并入卡片标题行(实时温度曲线 [暂停][清空]…[Y轴手动][2分钟]
一行放齐,两组按钮不再各占一行悬在半空;顺带清掉 v0.2.11 重构残留的
孤儿按钮组代码;此卡不走 self._card 通用头,内联构建支持多 trailing);
③实测/目标图例移到绘图区左上(右侧被滑条把手/值标签占用)。selftest
81 项全过;离屏目检(空态/手动拖后)通过。
- v0.2.13:**把手值标签悬停门控(用户标注:常驻会遮挡曲线)**——上下限
两个数值标签不再常驻,仅鼠标悬停在滑条栏(或拖拽中)才显示
(`_y_hover` 翻转才重绘;leaveEvent 途中拖拽不退出悬停态,松手按位置
重算)。selftest 新增 2 项(未悬停标签区像素=0 / 悬停出现标签),共 83
项全过。exe 重新打包见下。
- selftest 全过(v0.2.13 后共 83 项回归);**v0.2.13 exe 已重新打包并冒烟
通过**(release/FilmForge.exe + FilmForge-GUI-v0.2.13-win64.zip,
依次取代 v0.2.10/v0.2.11/v0.2.12 包),Gitea Release 待用户确认后执行。
遗留见其 README"已知事项":USB-RS485 适配器更换。
- [x] **GUI 慢性卡顿修复(根因已定位并修复 v0.2.8,待真机确认)**:
根因/机理/实测数据见上方 v0.2.8 条目(`cursor.Start` 实例级枚举访问在