侧边栏壁纸
博主头像
M的小站

记录问题、证据与可复现的解决过程

  • 累计撰写 15 篇文章
  • 累计创建 36 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

蓝牙上位机的状态机设计:连接只是第一步

蓝牙上位机的状态机设计:连接只是第一步

第一版上位机只有“扫描”和“连接”两个按钮。设备少、网络好时一切正常;一旦用户连续点击、系统蓝牙关闭或固件升级中途断开,界面就出现按钮仍可点击、进度条不动、后台线程继续运行的组合故障。问题不在某个回调,而在应用从未定义自己处于什么状态。

用状态描述事实

我把生命周期拆成:IdleScanningConnectingHandshakingReadyUpdatingRecoveringError。所有外部事件先进入单一事件队列,再由状态机决定动作。界面只渲染当前状态,不直接改变连接对象。

Idle -> Scanning -> Connecting -> Handshaking -> Ready
                         |             |           |
                         +----------> Error <------+
                                         |
                                      Recovering

“已连接”不是 Ready。物理链路建立后还要读取设备标识、协议版本、能力位和时钟,只有握手成功才开放业务操作。这个区分解决了许多刚连接就发送命令导致的竞态。

每个异步动作都有身份

扫描和连接会返回操作编号。回调到达时先核对编号与当前状态,旧操作的迟到结果直接丢弃。取消操作不是简单隐藏对话框,而是发出取消令牌、等待资源释放,然后进入目标状态。

超时也作为事件处理。连接超时只负责终止这一次尝试,不把任何异常都转换成“设备离线”。错误对象包含阶段、底层码、是否可重试和面向用户的下一步,因此日志与界面不再各说各话。

重连不是无限循环

Recovering 使用有上限的指数退避:一秒、两秒、四秒,达到次数后停在可解释的 Error。用户主动断开会设置意图标志,不触发自动重连。固件升级期间断线则保存进度和设备身份,重新握手后先查询设备端状态,再决定续传或重新开始。

列表里的设备也有生命周期。扫描结果按稳定身份合并,RSSI 做平滑显示,长时间未出现的设备标记为离线而不是瞬间消失,避免用户追着跳动的列表点击。

界面派生规则

  • Idle:允许开始扫描,隐藏设备操作
  • Scanning:允许停止,禁用重复扫描
  • Connecting:显示目标和取消入口
  • Handshaking:展示“正在确认设备信息”,不伪装成可用
  • Ready:根据能力位显示功能,而非按型号硬编码
  • Updating:锁定冲突操作,保留日志和安全退出说明
  • Error:给出原因、建议和可执行的重试动作

回归清单

  • [x] 在每个状态关闭系统蓝牙并观察收敛结果
  • [x] 连续点击扫描、连接、取消不会产生两个会话
  • [x] 迟到回调不能覆盖新的连接
  • [x] 应用退出时所有任务和句柄均被释放
  • [x] 升级断线后根据设备真实状态恢复
  • [x] 日志能从一次会话编号串起完整时间线

状态机最大的收益不是代码“更优雅”,而是产品行为可以被列举和测试。用户看到的每个按钮都对应一个明确事实,后台每个任务都有开始、结束和取消路径。连接只是链路事件;让设备在复杂环境中始终给出一致反馈,才是上位机真正的工作。

0

评论区