SMARTWATCH 学习手册有 C 基础 · 嵌入式从零开始
支线三 / Dart 与 Flutter App / 05

收到七个字节,怎样显示成 4000 毫伏

从主线已经见过的电量帧开始,在电脑上完成解析,再让解析结果更新页面。先把字节到含义这一步独立跑通。

接续上一篇:等待设备时,界面怎样继续响应

1 · 先拿一条知道答案的消息

十六进制消息 · 电压 4000 mV,百分比 60

AA 06 03 A0 0F 3C 62

AA 是帧头,06 表示电量消息,03 表示载荷有三个字节;A0 0F 按小端组成 0x0FA0,也就是 4000;3C 是百分比 60。前六字节之和为 0x19E,取负数再保留低八位得到 62;连同校验相加,低八位为零。主线已经解释帧边界和校验,本节用 Dart 表达相同规则。

Uint8List 表示每项为一个字节的连续数据,适合设备收发;普通 List 可以保存更广的整数值。import dart:typed_data 引入字节与二进制数值视图。

2 · 先检查长度和校验,再读字段

完整单帧电压解析函数 · 无效时返回 null

int? batteryMillivolts(Uint8List frame) {
  if (frame.length != 7 || frame[0] != 0xAA ||
      frame[1] != 0x06 || frame[2] != 3) return null;
  var sum = 0;
  for (var i = 0; i < frame.length - 1; i++) {
    sum = (sum + frame[i]) & 0xFF;
  }
  if (((-sum) & 0xFF) != frame.last) return null;
  return ByteData.sublistView(frame, 3, 5)
      .getUint16(0, Endian.little);
}

sublistView 让这两个载荷字节形成视图,getUint16 按小端读取无符号整数。先确认范围,才不会越界。视图可能共享原缓冲,长期保存数据前要考虑上游是否复用数组。完整下载示例还会翻转一个字节,确认坏校验被拒绝。

3 · 为什么一次通知不一定是一整条消息

蓝牙通知给出的是这一批收到的字节。协议帧可能拆成前后三批,也可能一批里有两条帧。前面的函数只接受已经完整的一帧,不能直接当成所有通知的处理器。

蓝牙包边界和应用消息边界不必相同。
蓝牙包边界和应用消息边界不必相同。 · 教学示意

仓库 SmartWatchBLE 的接收缓冲先寻找帧头、等待载荷长度足够、核对校验,再调用 ble_protocol.dart 中的字段解析函数。读代码时先画出这个顺序,不先把所有消息类型复制到练习程序。

4 · 让模拟帧也经过真实解析路径

把此前的模拟整数换成已知字节帧。每收到一帧,先解析成功才更新 millivolts。再分别注入错误校验、缺字节和断连事件。无效帧不应被转换成看起来正常的新测量;是否清除旧值则按页面的数据有效期约定执行。

心率、睡眠、血氧具有不同字段和有效性规则。当前 App 的实时血氧在 invalid、断连或超过五秒没有有效更新时清除;历史睡眠最低血氧不能拿来填补实时血氧。遇到这些业务数据时再解释,不能用一个“有数字即正常”的通用判断。

5 · 对照项目,保持一种协议含义

协议常量、字段和字节序对应 ble_protocol.dart;数据流装帧与消息分发对应 ble_service.dart。固定示例使用的 4000 毫伏消息也可以与主线 C 打包示例互相核对。

动手观察

运行下载示例,确认输出 4000 与 null。再把一条合法帧从每个可能位置拆开,沿装帧过程追踪什么时候才允许读字段;不要把缺失的尾字节补零。

资料与代码

配套文件

先复制到独立练习目录;用法见 支线示例说明。完整 Flutter 小应用见 tutorial/examples/flutter_watch/README.md。