摄像头心率 App 里最难的 bug 不一定在数学里。很多问题藏在用户动作和算法时序之间。用户把手指抬起半秒,相机还在预热,结果出现又消失,界面写着测量中,但信号已经明显不在了。

解决办法不是再加一个 if,而是停止把测量当成一个按钮动作。它应该是一台状态机。
This diagram could not be rendered. Its source is still available below.
View diagram source
stateDiagram-v2
[*] --> initial
initial --> preparing: startDetection
preparing --> detectingFinger: 相机就绪
detectingFinger --> measuring: 覆盖良好并稳定
measuring --> calculating: 时长和质量达标
calculating --> completed: 接受结果
calculating --> failed: 拒绝结果
measuring --> detectingFinger: 超过宽限期仍丢失手指
failed --> initial: reset
completed --> initial: 停止或重新测量
准备中是一个真实状态
相机权限、会话配置、闪光灯预热、曝光稳定都不是瞬间完成的。如果界面直接进入测量,最开始几秒会被准备过程污染。MonoPump 保留 preparing 阶段,等相机链路就绪后才进入手指检测。
手指检测需要耐心
一帧好画面不代表手指放好了。MonoPump 会等待覆盖良好持续一段时间后才开始计时。这样可以保护测量窗口的开头,避免早期噪声在后面制造坏峰值。
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart TD
A[覆盖变好] --> B[启动稳定计时]
B --> C{延迟后仍然良好吗}
C -- 否 --> D[继续引导]
C -- 是 --> E[重置分析器并开始测量]
测量开始时重置分析器也是刻意的。找手指阶段的帧对 UI 有用,但不适合作为测量样本。
宽限期比立刻重置更像人
早期版本在手指移动时重置太快。技术上没错,体验上很烦。真实用户会调整握持,一个短宽限期可以让 App 容忍短暂坏片段,而不是假装整个测量都作废。
This diagram could not be rendered. Its source is still available below.
View diagram source
sequenceDiagram
participant F as 帧分析器
participant D as Detector
participant UI as UI
F-->>D: 覆盖变差
D->>D: 启动宽限计时器
D-->>UI: 保持测量上下文
F-->>D: 覆盖恢复
D->>D: 取消宽限计时器
D-->>UI: 继续测量
延长采集比快速失败更友好
如果计划时长结束,但置信度或质量还不够,MonoPump 可以在最大时长内继续采集。用户不用马上重来,App 也有硬上限,不会无限拖延。
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart LR
A[达到计划测量时长] --> B{质量和置信度达标吗}
B -- 是 --> C[完成]
B -- 否 --> D{还没超过最大时长吗}
D -- 是 --> E[延长采集]
D -- 否 --> F[明确失败原因]
测量状态机不只是内部代码整洁。它决定用户看到什么、什么时候震动、什么时候关闪光灯、什么时候保存结果、什么时候诚实地失败。不要让单帧直接控制用户体验。帧输入状态,状态驱动 App。