Repository navigation
feat(device): BS1 支持 USB HID 通道 - #47
Merged
Merged
Conversation
BS1 插 USB 线时官方 App 走的是 HID(32 字节报文、Report ID 0x01、校验和挪到 末字节),而 THRM 一直只走 BLE。本次把 HID 作为 BLEManager 的第二种传输接入, BS1 的命令集、应答等待、0xEF 状态解析、调试记录全部原样复用,只有收发这一层不同。 - 新增 bs1_hid.go:BS1 HID 枚举(优先按产品名,退回 VID/PID)、报文封装、 读循环。读循环独占句柄并在退出时自行关闭,避免 hidapi use-after-free。 - ble.go:WriteCommand 按 hidTransport 分派(测试注入的 writeFrame 仍优先); Disconnect 优先处理 HID 传输;新增 ConnectHID(只试 HID,不做 BLE 扫描)。 - device.go:connect() 在 BS2/BS3 系列 HID 之后、shouldSkipBLEFallback 之前 尝试 BS1 HID。必须排在跳过判断之前,否则上一次连的是 BS2 时插着的 BS1 永远连不上。 未在真机验证:开发机没有 BS1 设备。报文布局依据官方 HIDClient.Send 的 IsBS1Serial 分支(曾在固件 0.0.6.02 上验证),并有单元测试覆盖封装逻辑。
用真机抓包的字节序列喂 handleHIDFrame,验证: - 0xEF 状态帧解析出正确的转速与模式(2100 RPM / 模式 0x02) - 命令应答派发给等待者(sendBLECommandAndWait 换传输后仍成立) - 校验和错误的帧被丢弃,不污染状态也不误派发 - 非协议杂散数据不会让读循环崩掉 不改任何生产代码。
handleDisconnect 现在同时服务两条传输(HID 与 BLE),但日志硬编码了 「BS1 BLE 连接已断开」。USB HID 掉线时也会打这句,日志与实际传输不符, 排查时容易误判。 改为在清空 hidTransport 之前先记下当时的传输类型,再按类型输出对应措辞。
Core 靠「HID 接口到达通知」发现设备插入,同时健康检查在通知可用时会放慢 周期性重连。原注册只覆盖 BS2/BS3 系列(VID 0x37D7),BS1 用的是 VID 0x04A0 / PID 0x2501,不在名单里。结果是 BS1 插回电脑时: 1) 到达通知因 VID 不匹配被丢弃; 2) 健康检查又认为通知已就绪,不再快速轮询; 3) 两头都不动,只能由用户手动点“连接”。 实测确认:拔掉再插回后 60 秒内日志没有任何重连尝试,手动点击才连上。 改动: - powernotify:注册接口改为接收 []HIDDeviceID(VID/PID 对),不再假定单一 VID - coreapp:同时登记 BS2/BS3 系列与 BS1 - device:导出 BS1HIDVendorID / BS1HIDProductID;enumerateBS1HID 改为可注入变量 - 测试:TestReconnectWithoutDeviceEnumeratesOnceAndQuietsRepeats 一并置空 BS1 枚举,避免测试结果取决于本机是否接着 BS1
BS1 走 USB 时,系统睡眠/休眠唤醒后会出现一种假连接:设备仍留在系统 枚举列表里,风扇照转,但 HID 通道完全不再响应——新开句柄、复用旧句柄、 连 0x05 握手都无应答,只有重新枚举 USB(拔插线或重启)才能恢复。 已用保持句柄跨睡眠的独立脚本实测确认(帧计数在唤醒后 2 分 19 秒零增长, 查询 7 次全部超时)。 原先的读循环把读超时/空读一律当成暂时没数据,于是 Core 一直显示 「已连接」而转速空白,自动重连还会反复重演「连上但没有任何数据」。 改动: - 读循环加静默看门狗:超过 bs1HIDIdleTimeout(20s) 一帧未收即判定掉线。 实测 BS1 无论风扇转不转都约每 0.5 秒推一次 0xEF,20 秒静默必然是异常。 - 连接阶段加首帧探测:句柄打开后 bs1HIDProbeTimeout(2s) 内收不到任何帧 就判定设备装死,直接关闭并返回失败——实测唤醒后 2 秒即识破,界面 不会先显示成已连接再断开。 - 探测失败时跳过 BLE 兜底:实测设备活着且插着 USB 时扫描 15 秒扫不到任何 BS1 广播,再扫只是白等 10 秒。失败检测因此从 ~12 秒缩短到 ~2 秒。 该判断每次尝试都重新求值(ConnectHID 入口重置),避免上一次的结论残留 导致设备改接充电头后蓝牙永远连不上。 - 把该失败与设备没插区分开(Manager.HIDUnresponsive)。用户手动点 「连接」失败时通过 coremsg 发系统通知,明确要求拔插 USB-C 线或重启。 不做自动提醒:自动重连发生在刚唤醒、桌面还没出来的时候,实测通知会 落在锁屏阶段,用户看不见;等用户主动来点,反馈才落在眼前。 - 通知文案进 coremsg 目录(中/英/日)。 注意:这是设备固件的限制,软件侧无法恢复连接本身,本次改动只负责让状态 如实反映、并明确告诉用户该怎么做。
|
@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. |
- go-hid 的 Close 释放原生句柄后不置空,读循环因掉线自行关闭句柄时若恰有一次 调速写入会写到已释放的句柄上;传输内加 ioMu 串行化 Write 与关闭 - 读循环异常退出改走 handleHIDTransportLost,只有退出的仍是当前传输时才断开, 避免 Close 超时后晚退出的旧循环把已重连的新连接标成断开 - 断开复位收拢到 resetConnectionLocked,stopChan 改在锁内关闭 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
BLEManager内部加一层 HID 传输,BS1 的命令集/应答等待/0xEF解析全部复用,上层零改动IsBS1Serial分支实现(32 字节 / Report ID0x01/ 校验和挪到末字节),真机逐条对比蓝牙一致0x27无响应、挡位转速预设偏大等),均未在本 PR 修改背景
THRM 目前对 BS1 只走蓝牙。但 BS1 插 USB-C 线时同时提供数据通道——官方 App
(
HIDClient.Send的IsBS1Serial分支)就是有线优先,只有没插线时才走 BLE。THRM 少了这条路,导致:
这个 PR 把 USB HID 通道补上。
改动思路
不新建设备类型,而是在
BLEManager内部加一层传输实现:deviceType仍然是DeviceTypeBLE——它表示"走 BS1 协议栈",不是"走蓝牙"。所以 GUI、智能控温、曲线、热键等上层代码零改动。
报文格式差异
0x37D7/0x1001..10040x04A0/0x25010x020x010x03依据:官方 App 的
IsBS1Serial分支;已在真机(固件0.0.6.2)逐条验证。文件清单(11 个,+795 / -17)
新增
internal/device/bs1_hid.gointernal/device/bs1_hid_test.gointernal/powernotify/hid.goHIDDeviceID类型修改
internal/device/ble.goWriteCommand按传输分流;新增ConnectHID;断开处理区分传输internal/device/device.goconnect()在 BS2/BS3 之后、BLE 兜底之前尝试 BS1 HIDinternal/coreapp/lifecycle.gointernal/coreapp/system_device.gointernal/coremsg/catalog.gointernal/powernotify/hid_windows.go/hid_linux.gointernal/device/hid_enumerate_test.go实现中值得说明的三点
1. BS1 的分支位置
connect()里的顺序必须是:BS2/BS3 → BS1 HID → BLE 兜底。BS1 那一段必须排在
shouldSkipBLEFallback之前——否则上一次连的是 BS2 时,该函数会直接跳过整个 BLE 分支,插着的 BS1 就永远连不上。
2. HID 到达通知要单独登记 BS1 的 VID
Core 靠「HID 接口到达通知」发现设备插入,同时健康检查在通知可用时会放慢周期性
重连。原注册只覆盖 BS2/BS3(VID
0x37D7),BS1 是0x04A0,不在名单里。后果(实测):BS1 插回电脑后,到达通知因 VID 不匹配被丢弃;健康检查又认为通知
已就绪而不再快速轮询 → 两头都不动,60 秒内日志零重连尝试,只能手动点连接。
所以把
RegisterHIDInterfaceArrivalNotifications的入参从(vendorID uint16, productIDs []uint16)改成([]HIDDeviceID),两组 VID 一起登记。3. "假连接"检测
BS1 走 USB 时有一种状态:设备仍在设备管理器里、风扇照转,但 HID 通道完全不再响应。
(详见下文「已知限制」)
原来的读循环把"读超时/空读"一律当成"暂时没数据",于是 Core 一直显示「已连接」而
转速空白。加了两道防线:
都约每 0.5 秒推一次
0xEF,20 秒静默必然是异常避免界面先显示成"已连接"再断开,也避免自动重连反复重演
探测失败时还会跳过 BLE 兜底扫描(依据见「已知限制」第 2 条),失败检测从
约 12 秒缩短到约 2 秒。这个判断每次尝试都重新求值,避免上一次的结论残留导致
设备改接充电头后蓝牙连不上。
验证情况
真机:Flydigi BS1,固件
0.0.6.2,Windows 11。0xEF)解析那 1 次失败的具体原因(不是代码问题):在蓝牙 GATT 服务发现进行中把线插回了
电脑,设备从蓝牙侧消失 →
发现 GATT 服务失败: could not retrieve device services。5 秒后自动重试走 HID 成功。
已知限制
1. 睡眠/休眠唤醒后 USB 通道会失效(设备固件行为)
系统从 S3 唤醒后,BS1 仍留在设备管理器里、风扇照转,但 HID 通道完全不再响应:
0x05握手:无应答0x22查询 7 次全部超时)只有重新枚举 USB 能恢复——拔插线、或重启系统,两种方式都实测过。
用"保持句柄跨睡眠"的独立脚本确认过,与句柄新旧无关。
蓝牙通道在睡眠唤醒后是否也会失效,我们没有测过——本次所有睡眠测试设备都接在
USB 上。
本 PR 的处理:2 秒探测识破 + 界面显示离线 + 用户手动点「连接」失败时发系统通知,
告知拔插线或重启。
没有做自动提醒,原因是实测:自动重连发生在刚唤醒、桌面还没出来的时候,
通知会落在锁屏阶段,用户看不见(要事后去通知中心翻)。试过用"距上次键鼠输入"
判断用户是否在场,但唤醒本身就需要按键,这个信号失效。所以改成等用户主动点连接
——那时的反馈一定落在眼前。
2. 插着 USB 时设备不广播蓝牙
实测:设备活着且插着 USB 时扫描 15 秒,扫到 15 个设备但没有 BS1。
所以 HID 探测失败后无需再试 BLE,直接在
connect()里跳过。顺带发现的既有问题(都不是本 PR 引入,均未修改)
以下是接 BS1 的过程中撞见的、与传输层无关的现象。每条都标了证据来源,
供判断是否需要单独处理。
实测确认
①
0x27(QueryGearRPMTable)完全无响应——USB 与 BLE 都一样QueryDeviceSettings对两条通道用同一份basicDeviceSettingsQueryCommands(
0x27/0x25/0x45),其中0x27每次都超时。本次测试期间的日志统计:两条通道都不响应,不是 HID 特有的问题。每次读设备信息白等一个
deviceResponseTimeout(response.go:11,900ms)。(协议报告里没有
0x27,所以我们的验证工具直接拒发;是 THRM 自己在发。)② 挡位转速预设与设备实际不符
后两个偏大。超频那一条的实测方式:PD 供电下点超频,设备自己报的目标是 3000
(模式 4);非 PD 下点超频,设备降级到目标 2700(模式 3)。
③
0x05会清除实时目标转速实测:Core 运行中目标 1320,另一个进程发
0x05后目标变 0、风扇停转。THRM 把它命名为
ClearInitializationLatch是准确的。对测试的启示:Core 在跑时不要用其他工具连同一设备,否则会把转速打掉。
④
0x45在设备失效状态下也会超时正常状态下
0x45工作良好;睡眠唤醒后设备"装死"期间会超时(本次 2 次)。属于失效状态的连带表现,不是独立问题。
⑤ HID 下挡位显示「未知」
0xEF的payload[0]两条通道编码不同:现有
parseBS1GearSettings按位域解,所以 HID 下挡位名显示"未知"。只影响挡位名称,转速/模式/控制全部正常。
挡位 4 因供电不足会被设备降级回"强劲",没取到它的
payload[0]样本,所以这条公式只覆盖挡位 1–3。
⑥ BS1 的固件版本被硬编码成
unsupportedinternal/device/query.go:248在 BS1 路径里写死FirmwareReadStatus: "unsupported"。但实测
0x01在 USB HID 下正常响应,返回0.0.6.2。BLE 下
0x01是否响应,我们没有测过(测的时候设备插着线,无法切 BLE)。所以这条只能说明:接入 HID 后,USB 场景下 BS1 的固件版本是可读的。
⑦ 超频在非 PD 供电下会被设备静默降级
实测两次对比:
界面此时仍显示"超频"选中——挡位选中态取自
config.manualGear(
FanCurve.tsx:1886),不读设备实际挡位。这里只陈述这个现象。代码确认
⑧ 实时转速下发只在 Debug 级留痕
两条路径的日志级别不一致:挡位是 Info,实时转速是 Debug。默认日志里看不到
实时转速的下发记录,排查"转速不对"时需要开
--debug。⑨ 界面没有实时帧列表
后端有
GetDebugFrames,前端从未调用(grep -rn "GetDebugFrames" frontend/src/无结果)。调试面板只有「调试模式 / 刷新调试信息 / PawnIO / 命令发送框」。
⑩ 没有「停止」模式
原版客户端
FanSpeedMode有STOP,THRM 的类型里没有对应项(
grep "STOP" internal/types/types.go无结果)。原版切手动挡位前会先置
STOP再发0x08;THRM 直接发0x08,实测功能正常。设备行为备忘(实测,方便以后排查)
但进了实时模式(
0x23)后,实测 1500 和 2500 都是 3 格全亮。具体含义未知,只记录这个现象。
0xEF的WorkMode。设备重新上电 → 系统重新枚举 → 自动重连",不是无缝切换。
兼容性
hidTransport == nil时完全走原路径)测试
internal/appmeta的TestFirstExistingPath_Hit在 Windows 上失败(期望/tmp),该用例在
main上同样失败,与本次改动无关。顺带修了一个既有测试的隐患:
TestReconnectWithoutDeviceEnumeratesOnceAndQuietsRepeats原先只 mock 了enumerateVendorHID(BS2/BS3),本 PR 让它开始枚举 BS1 → 在接着设备的机器上会"意外连上"而失败。已一并置空
enumerateBS1HID。不在本 PR 范围(供参考,不需要在这个 PR 里解决)
以下几件事和传输层无关,本 PR 没有触碰,列出来只是让它们有记录:
payload[0]样本)thrmFwVersion < 1挡住(该门槛是给 BS2/BS3 定制固件设的)附:本 PR 对既有行为的影响评估
WriteCommand改为按传输分流BLEManager)writeFrame优先级不变,逻辑等价;仅错误文案由"BLE 设备未连接"改为"BS1 设备未连接")connect()插入 BS1 HID 分支hid.Enumerate约 11ms,健康检查 30 秒一次)HIDUnresponsive()恒为 false)Disconnect增加 HID 分支hidTransport为 nil 时走原路径)handleDisconnect区分传输ConnectDevice失败时发通知HIDUnresponsive()恒为 false,提前返回)净新增的运行时开销:非 BS1 用户在设备未插时,每次连接尝试多约 11ms 的 HID 枚举。