Skip to content

feat(bs1): 修复挡位显示、放开灯光控制、新增关机停转与开机恢复 - #50

Open
Bolingboluo wants to merge 4 commits into
TIANLI0:mainfrom
Bolingboluo:pr/bs1-features
Open

Bolingboluo wants to merge 4 commits into
TIANLI0:mainfrom
Bolingboluo:pr/bs1-features

Conversation

@Bolingboluo

Copy link
Copy Markdown
Contributor

改动说明

四件事,全部在 BS1(固件 0.0.6.2,USB HID 连接)上验证:

  1. 修复挡位显示 —— HID 通道下 0xEF 首字节是 ASCII 挡位码,原代码按 BLE 位域解析,界面恒显示「未知」
  2. 放开 BS1 的灯光控制 —— 实测 BS1 的 6 颗灯可被 0x47 关键帧驱动,与当前「BS1 没有灯光硬件」的判断不同,供参考
  3. 修复托盘状态回调的锁顺序反转 —— 灯效下发偶发超时的根因(托盘刷新是所有型号共用的路径)
  4. 新增关机停转与关灯 —— 设备不会因为主机停止发命令而停转,关机后风扇会一直转到拔电

关于第 4 点:「关机时停转关灯」在多数场景下符合直觉,本次按默认行为实现,界面上没有增加开关。

它与现有的「休眠时关闭输出」属于两个不同场景——那个选项的文案与语义都限定在睡眠,所以没有复用。

是否需要为关机场景提供独立选项,交由维护者判断。


改动类型

  • 问题修复(fix)
  • 新功能(feat)
  • 设备协议 / 新设备适配
  • 重构 / 性能优化(refactor / perf)
  • 文档 / 官网(docs / website)
  • 构建 / CI / 打包(build / ci)

实现方式

四个提交,各自独立:

fix(device): BS1 走 USB HID 时正确解析当前挡位
feat(light): 放开 BS1 的灯光控制并修复下发稳定性
fix(core): 修复托盘状态回调的锁顺序反转
feat(core): 关机时停转关灯,开机自动恢复

1. 挡位显示

0xEF 首字节在两条传输上编码不同:

传输 编码
BLE 位域(高 4 位最大挡位,低 4 位当前挡位)
USB HID ASCII 挡位码,0x30 + 2×(挡位-1)

原 parseBS1Notification 统一按位域解析,HID 下 0x30/0x32/0x34 的低 4 位都是 0x0,因此恒为「未知」。

实测数据:

挡位 payload[0]
静音 0x30
标准 0x32
强劲 0x34
超频 0x36(公式延伸,未实测——设超频需 PD 供电,否则设备静默降级)

改动:解析层按传输类型分支,新增 FanData.SelectedGear(1..4,0 未知)供下游统一使用;
新增 bs1_gear_test.go 穷举 0x00–0xFF,证明 BLE 路径的解码结果与原实现完全一致。

2. 灯光控制

当前代码里 NO_LIGHT_STRIP_MODELS = {BS1, BS2, BS3},注释是「BS1、BS2、BS3 没有灯光硬件」。

我们在 BS1 上实测:6 颗灯(每侧 3 颗)可以被 0x47 关键帧程序驱动,颜色、动画、亮度均有效。

验证项 结果
186 字节布局(6 头 + 6 灯 × 10 关键帧 × RGB) 19 帧,190 字节
下发序列 0x46(01) → 0x41 → 19×0x47 → 0x43(01) 22 条全部 ACK
静态单色 / 多色 左右各三颗,顺序一致
旋转 / 流光 / 呼吸 均能正常播放
亮度 0~100 0 = 完全灭灯,可恢复

改动范围:只给 BS1 开例外,BS2/BS3/Pro 的判定完全不变。

// 原:{hasLightStrip && (
// 现:{(hasLightStrip || isBs1) && (

另外 BS1 会过滤掉「智能温控灯效」选项——那条路靠 cmd 68 选固件原生预设,BS1 固件里没有。

关于 BS2/BS3:从代码看这两个型号同样被归为「没有 RGB 灯带」。我们手头没有这两个型号,
无法确认是否同理可驱动,因此这部分判断保持原样,没有改动。

如果后续有机会,这两个型号是否具备同样的能力,或许也值得确认一下。

放开功能之外,还修了三个稳定性问题(否则功能不可用):

  • 自死锁:IsBS1() 内部取 m.mutex.RLock,而灯效路径已持有写锁;Go 的 RWMutex 不可重入,请求永不返回(表现为 IPC 12 秒超时)。改为直接读 m.deviceType
  • 句柄位置:BS1 的句柄在 bleManager 里,m.device 恒为 nil,原来的判空对 BS1 必然失败
  • 0x43 是慢命令:它触发固件擦写 flash,ACK 常超过默认 900ms,界面报「设置灯带失败」而灯其实已生效。单独放宽到 2500ms 并重发

3. 托盘锁顺序(影响所有型号)

现象:应用灯效时约每 2~3 次报一次「等待命令 0x47 响应超时」,灯其实已经变了。

根因(协程栈证实):托盘状态回调在持有 a.mutex 读锁时调用 GetCurrentFanData(),
后者需要设备层 m.mutex;而灯效下发会持有 m.mutex 长达数秒。形成四向环:

灯效下发   持【设备锁】→ 等 ACK(只有设备读循环能交付)
托盘刷新   持【Core 锁】→ 等【设备锁】
控温循环           → 等【Core 锁】写
设备读循环         → 等【Core 锁】读(被写请求挡住)

修复:托盘回调改为先取设备数据(短暂持有设备锁),再拿 Core 锁,与灯效路径顺序一致。

这不是 BS1 特有的——托盘刷新是所有型号共用路径,BS2 Pro/BS3 Pro 的灯效上传同样
持有设备锁约 2 秒,理论上会踩同一个坑(本仓库只有 BS1,未实测)。

同时补了两处诊断,避免同类问题再次「日志里一个字都没有」:IPC 慢请求看门狗
(handler 超过 5 秒未返回就记错误日志)、灯效下发记录模式与耗时。

4. 关机停转与开机恢复

问题:设备不会因为主机停止发命令而停转。实测关掉 THRM 后连续监控 48 秒,
设备保持 2800 RPM,5 分钟后仍在转。

实现:

  • session_end_windows.go 自建不可见顶层窗口接收 WM_QUERYENDSESSION
    (Core 是 -H=windowsgui 无控制台进程,收不到 CTRL_SHUTDOWN_EVENT;
    message-only 窗口收不到广播,必须用顶层窗口)
  • 关机:下发目标 0 转速 + 关闭灯光输出(+ BS2/BS3 的挡位灯,它们的 0x48
    是独立于灯带的另一套灯)
  • 先置 shutdownInProgress 再写 0,否则控温循环几秒内会把曲线目标写回去
  • 开机:BS1 只重开灯光输出(1 条命令,程序仍在设备 flash 中,不重传 19 帧、
    不多写一次闪存);BS2/BS3 沿用原有的完整重放
  • 取消关机:恢复灯光输出

为什么 BS2/BS3 不需要额外适配:两条命令(SetCustomFanSpeed(0) / SetRGBOff)
本来就是全型号通用的;开机侧 BS2/BS3 走的是仓库原有的完整重放路径。


验证

  • 测试设备:Flydigi BS1 / 固件 0.0.6.2 / USB HID 连接

  • 测试系统:Windows 11 23H2(22631)

  • go test ./... 通过

  • cd frontend && bun run build 通过

  • 在真实设备上验证过

项目 结果
挡位显示(静音/标准/强劲) 通过
灯光:6 种模式 + 亮度 23 次应用,0 失败
灯珠映射(逐颗三色) 左 3(LED0-2)/ 右 3(LED3-5),顺序一致
模拟开关机 5 轮 5/5,每轮 84~87 秒
真机关机 风扇停、灯灭;开机后自动亮灯起转

关键实测数据:0x21 目标 0 后,2200→0 约 20 秒;2600→0 约 55~60 秒。
USB 一直供电,所以固件会在断电后继续滑行到 0。


截图

1. BS1 的「灯光效果」区块

此前该区块被 NO_LIGHT_STRIP_MODELS 隐藏,现在 BS1 可以看到并应用灯效。

image

2. 挡位显示修复

修复前显示「未知」,修复后正确显示 静音 / 标准 / 强劲。

image image

检查清单

  • 改动范围清晰,没有混入无关修改
  • 提交信息符合 feat: ... / fix: ... 格式
  • 新增的界面文案已补充多语言(本 PR 未新增文案,复用已有 key)
  • 没有提交抓包、诊断包等可能包含个人信息的文件

补充说明:

  • 四个提交各自独立、可单独 review;同属「BS1 支持」这一主题,因此放在一个 PR

已知限制

  • 本仓库只有 BS1 一台设备,BS2/BS3 的行为是代码推理,未实测
  • 灯光 UI 的预览是连续导光条,而 BS1 是 6 颗离散灯珠,预览与实物不完全一致(仅近似)
  • 0x36(超频挡位)未实测,需 PD 供电
  • BLE 通道下的上述功能未测试(本次全部通过 USB HID 连接)

0xEF 首字节在两条传输上编码不同:BLE 是位域(高 4 位最大挡位、低 4 位当前挡位),
USB HID 是 ASCII 挡位码 0x30 + 2×(挡位-1)。原代码统一按位域解析,HID 下
0x30/0x32/0x34 的低 4 位都是 0x0,界面因此恒显示「未知」。

- parseBS1Notification 增加编码参数,HID 路径传 ASCII、BLE 路径传位域
- 新增 FanData.SelectedGear(1..4,0 未知),下游统一使用,不必各自判断传输
- 新增 bs1_gear_test.go:穷举 0x00-0xFF,证明 BLE 路径的解码结果与原实现一致

实测(固件 0.0.6.2):静音 0x30 / 标准 0x32 / 强劲 0x34;超频 0x36 是公式延伸,
未实测(设超频需 PD 供电,否则设备静默降级回强劲)。
BS1 的 6 颗灯(左 3 右 3)可被 0x47 关键帧程序驱动,真机验证有效:
静态单色/多色、旋转/流光/呼吸、亮度 0-100 全部可用。

放开功能本身只需去掉三处 IsBS1 拦截,真正的工作量在三个稳定性问题:

1. 自死锁:IsBS1() 内部取 m.mutex.RLock,而灯效路径已持有写锁,Go 的 RWMutex
   不可重入,请求永不返回(表现为 IPC 12 秒超时)。改为直接读 m.deviceType。
2. BS1 的句柄不在 m.device(由 bleManager 持有),原来的 m.device == nil 判断对
   BS1 必然失败,改为放行 BS1 并走 bleManager 的发送路径。
3. 0x43 提交命令会触发固件擦写 flash,ACK 常超过默认 900ms,界面因此报「设置灯带
   失败」而灯其实已经生效。单独放宽到 2500ms 并重发。

另:灯效帧偶发丢 ACK 时重发(lightFrameRetries=2)。

界面:BS1 显示「灯光效果」区块((hasLightStrip || isBs1)),BS2/BS3/Pro 的判定
不变;BS1 过滤掉「智能温控灯效」选项——那条路靠 cmd 68 选固件原生预设,BS1
固件里没有。

实测:6 种模式 + 亮度共 23 次应用,0 失败。
现象:应用灯效时约每 2~3 次报一次「等待命令 0x47 响应超时」,灯其实已经变了。

根因(协程栈证实):托盘状态回调在持有 a.mutex 读锁时调用 GetCurrentFanData(),
后者需要设备层 m.mutex;而灯效下发会持有 m.mutex 长达数秒(19 帧逐条往返)。
形成四向环:

  灯效下发   持【设备锁】→ 等 ACK(只有设备读循环能交付)
  托盘刷新   持【Core 锁】→ 等【设备锁】
  控温循环           → 等【Core 锁】写
  设备读循环         → 等【Core 锁】读(被写请求挡住)

读循环读不到 ACK,灯效命令只能全部等到超时。

修复:托盘回调改为先取设备数据(短暂持有设备锁),再拿 Core 锁,与灯效路径的
加锁顺序一致。

这不是 BS1 特有的——托盘刷新是所有型号共用的代码路径,BS2 Pro/BS3 Pro 的灯效
上传同样持有设备锁约 2 秒,理论上会踩同一个坑(本仓库只有 BS1,未实测)。

同时补上两处诊断,避免同类问题再次「日志里一个字都没有」:
- IPC 慢请求看门狗:handler 每超过 5 秒未返回就记一条错误日志(客户端 12 秒超时)
- 灯效下发记录模式、亮度、耗时与失败原因

修复后 23 次应用 0 失败。
@vercel

vercel Bot commented Nov 9, 2026

Copy link
Copy Markdown

@Bolingboluo is attempting to deploy a commit to the Tianli's projects Team on Vercel.

A member of the Team first needs to authorize it.

设备不会因为主机停止发命令而停转(实测:关掉 THRM 后连续监控 48 秒,设备保持
2800 RPM,5 分钟后仍在转)。关机后风扇会一直转到拔电为止。

实现:
- session_end_windows.go 自建不可见顶层窗口接收 WM_QUERYENDSESSION。Core 是
  -H=windowsgui 无控制台进程,收不到 CTRL_SHUTDOWN_EVENT;message-only 窗口
  收不到广播,所以必须用顶层窗口。
- 关机:下发目标 0 转速 + 关闭灯光输出(+ BS2/BS3 的挡位灯,它们的 0x48 是独立
  于灯带的另一套灯)。先置 shutdownInProgress 再写 0,否则控温循环几秒内会把
  曲线目标写回去。
- 开机:BS1 只重开灯光输出(1 条命令,程序仍在设备 flash 中,不重传 19 帧、
  不多写一次闪存);BS2/BS3 沿用原有的完整重放。
- 取消关机:恢复灯光输出。

实测:
- 0x21 目标 0:2200→0 约 20 秒;2600→0 约 55~60 秒
- 从 0 唤醒:直接发目标即可,0↔正常往返十余次未出现「不可恢复」
- 模拟开关机 5 轮:5/5 通过,每轮 84~87 秒
- 真机关机:风扇停、灯灭;开机后自动亮灯起转

该功能对所有型号生效,且不受「休眠时关闭输出」开关控制(那个开关的语义是
「系统睡眠时」,与关机是不同场景)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant