☰
ArkTS Uint8Array:BLE温湿度定长帧大端解析与校验失败隔离【鸿蒙心迹】
2026/10/14 15:44:41 网站建设 项目流程

Demo:ByteThermo| 页面:SensorFramePage| 任务:BT-2605
声明:本文定义的七字节格式属于自建教学协议,图片、日志和数据均为演示;未连接真实 BLE 外设,也未通过真机、SDK 编译或无线链路认证测试。

一、先看错误帧,再决定如何解码

离线设备教学里有一类问题很隐蔽:屏幕偶尔出现不可能的温度,排查时大家先怀疑传感器,最终却发现是手机端把两个字节的高低位读反了。更危险的是收到长度不足的载荷仍然访问后续下标,读出零值或未定义结果后继续更新界面,看起来像采集成功,实际只是数据链路失去了边界。

我用ByteThermo做一个窄范围演示:不实现蓝牙扫描、连接或 GATT 通知,只设计收到一段字节以后如何裁判它是否可信。任务号BT-2605,展示页SensorFramePage,帧长度固定七字节。合法样例写成A5 01 00 EA 02 37 7B,其中十六进制的00 EA是大端有符号温度,换算成摄氏二十三点四度,02 37是大端无符号湿度,换算成百分之五十六点七。最后一字节7B是按教学约定对前六字节做异或校验的结果。

这套格式是本文业务层自行约定的模拟协议,不代表 HarmonyOS BLE 官方帧格式,也不代表任何真实硬件的标准消息。华为官方 BLE Codelab 讲的是设备角色、权限、扫描与 GATT 等传输能力;应用到底怎样解释收到的Uint8Array,必须以真实外设协议书为准。把这两层分开,文章里的代码才不会被读者误拿去当通用驱动。

二、协议边界写在代码前面

约定的结构非常简单:第零字节是帧头A5,第一字节是版本01,第二和第三字节表示有符号十六位温度、单位十分之一摄氏度,第四和第五字节是湿度、单位千分之一比例换成十分之一百分数,第六字节是对前六字节逐位异或得到的校验字节。完整长度只能等于七,多字节整数按大端排列;否则后面的判断没有意义。

如果上游交给页面的是ArrayBuffer,需要在调用解析器前统一转换成Uint8Array;如果上游只给出分片,也必须先完成可靠的组帧。这里故意不做分片重组:一来避免混淆传输责任和协议责任,二来保证案例能用固定输入复现。使用异或只是为了把字节级校验讲透,它的检错能力很弱,不应承担安全认证、防重放或防篡改职责。

先实现两个无外部依赖的函数。它们既不知道蓝牙连接,也不写系统日志;一旦出现数组之外的数据,调用者必须先完成类型和长度检查。温度按二补码解释,若原始十六位值高于或等于0x8000,减去0x10000才得到负数,这一步可以防止负温度被误读成数千度。

function xorFirstSix(bytes: Uint8Array): number { let crc: number = 0 for (let i: number = 0; i < 6; i++) { crc = crc ^ bytes[i] } return crc & 0xFF } function signedTenths(hi: number, lo: number): number { const value: number = (hi << 8) | lo return value >= 0x8000 ? value - 0x10000 : value } function hexByte(value: number): string { return '0x' + value.toString(16).toUpperCase().padStart(2, '0') }

上述函数只应接收已经满足七字节结构的数组;这里的xorFirstSix无意兼容变长帧。产品级实现可改成按照协议约定的长度字段与正式 CRC 算法计算,但需要另行核对多项式、初始值、位序和末位异或。函数本身不持有资源,不存在退出页面要释放的句柄;不要把“纯函数无释放”误写成 BLE 连接无需关闭。

三、拒绝有问题的载荷,比渲染速度更重要

解析过程的先后顺序需要刻意设计:先检查长度,再核对帧头与版本,再验证校验字节,最后解释温湿度并检查物理合理范围。如果收到A5 01 00 EA 02 37 7A,只差最后一个字节,应该明确返回CRC_BAD,不能先把二十三点四度显示出来再说“可能无效”。同样,虽然这不是安全加密,但至少让 UI 层不至于把简单噪声当成合格测量。

返回值采用固定结构,失败时温度与湿度都是null,而不是零。零度和零湿度本身可能是合法业务值,拿零当错误哨兵会让错误与观测互相污染。正常结果的状态写成VALID,校验失败写成CRC_BAD,长度错误写成LENGTH_BAD。另外保留HEAD_BAD、VERSION_BAD和RANGE_BAD,让后续日志能直接指向决策分支。

下面是完整的核心解析函数。温度容许区间暂定负四十至八十五摄氏度,湿度百分比在零到一百之间;这也是教学规则,并不是 HarmonyOS 平台替硬件规定的传感器量程。如果设备资料给出不同范围,必须在自己的协议适配层修改。

type FrameStatus = 'VALID' | 'LENGTH_BAD' | 'HEAD_BAD' | 'VERSION_BAD' | 'CRC_BAD' | 'RANGE_BAD' interface FrameResult { status: FrameStatus tempC: number | null humidityPct: number | null check: string } function rejectFrame(status: FrameStatus, check: string): FrameResult { return { status: status, tempC: null, humidityPct: null, check: check } } function parseFrame(bytes: Uint8Array): FrameResult { if (bytes.length !== 7) return rejectFrame('LENGTH_BAD', '--') if (bytes[0] !== 0xA5) return rejectFrame('HEAD_BAD', '--') if (bytes[1] !== 0x01) return rejectFrame('VERSION_BAD', '--') const expected: number = xorFirstSix(bytes) if (bytes[6] !== expected) { return rejectFrame('CRC_BAD', hexByte(expected)) } const temperature: number = signedTenths(bytes[2], bytes[3]) const humidity: number = (bytes[4] << 8) | bytes[5] if (temperature < -400 || temperature > 850 || humidity > 1000) { return rejectFrame('RANGE_BAD', hexByte(expected)) } return { status: 'VALID', tempC: temperature / 10, humidityPct: humidity / 10, check: hexByte(expected) } }

这里不会抛出“校验失败异常”,因为无效帧是通信链路可预期的常见输入,应该变成可统计的结果而不是把页面流程打断。真正的程序错误,例如调用方传入非Uint8Array或读取错误的数据缓冲区,则要在更外层处理。若未来格式需要签名、设备身份或时序号,校验通过不意味着来源可信,仍须做应用层认证与回放保护。

四、页面不应记住上一次有效值冒充本次结果

协议适配的另一个坑发生在状态更新时:上一帧是合法的二十三点四度,下一帧校验失败,如果页面仅修改状态标签,旧温湿度仍留在屏幕上,读者很容易把它们当成失败帧的测量结果。这次约定失败结果直接把显示值变成短横线,顶部状态显示失败代码,避免“红色报错旁边还有正常值”的含混界面。

页面只有两个演示按钮:一个验证合法样例,一个模拟末字节从7B被改为7A。它们不会调用系统蓝牙 API,也不会申请扫描权限。打开页面时主动加载合法样例,最终演示图统一展示VALID、23.4 ℃、56.7 %、XOR=0x7B。如果按失败按钮,状态才改变为CRC_BAD,两项测量值清空。

为避免用 UI 去推导协议内部判断,把接收动作收束在一个inspect()方法。这里只使用公开 ArkUI 组件和 ArkTS 基础字节类型;在接入真实 GATT 特征值通知时,应由生命周期所有者注册和取消监听,页面只接收解析后的业务对象。

@Entry @Component struct SensorFramePage { @State status: string = 'VALID' @State temperatureText: string = '23.4 ℃' @State humidityText: string = '56.7 %' @State logLine: string = 'BT-2605 | VALID | T=23.4 | RH=56.7 | XOR=0x7B' private inspect(bytes: Uint8Array): void { const item: FrameResult = parseFrame(bytes) this.status = item.status this.temperatureText = item.tempC === null ? '--' : `${item.tempC.toFixed(1)} ℃` this.humidityText = item.humidityPct === null ? '--' : `${item.humidityPct.toFixed(1)} %` this.logLine = item.status === 'VALID' ? `BT-2605 | VALID | T=${item.tempC} | RH=${item.humidityPct} | XOR=${item.check}` : `BT-2605 | ${item.status} | expected=${item.check}` } aboutToAppear(): void { this.inspect(new Uint8Array([0xA5, 0x01, 0x00, 0xEA, 0x02, 0x37, 0x7B])) } build() { Column({ space: 16 }) { Text('温湿度帧审校').fontSize(26).fontWeight(FontWeight.Bold) Text('任务 BT-2605') Text(this.status) Text(this.temperatureText) Text(this.humidityText) Button('验证样例帧').onClick(() => { this.inspect(new Uint8Array([0xA5, 0x01, 0x00, 0xEA, 0x02, 0x37, 0x7B])) }) Button('模拟校验失败').onClick(() => { this.inspect(new Uint8Array([0xA5, 0x01, 0x00, 0xEA, 0x02, 0x37, 0x7A])) }) Text(this.logLine).fontSize(12) }.padding(20).width('100%') } }

从资源角度看,这段演示只有瞬时创建的字节数组与普通组件,没有真正的 BluetoothGattClient,也不存在需要假装释放的设备连接。真实页面需明确连接、通知订阅和页面退场之间的所有权:如果蓝牙会话由应用级服务管理,页面消失时只注销自己的观察者;如果页面自己持有会话,则要在销毁或取消时关闭相关资源。两种设计不能混写,更不能从本例“无需释放”推出实际 BLE 开发也无需释放。

五、用失败矩阵,而不是单张成功截图验收

校验案例至少应包含合法七字节帧、只有六字节的截断帧、错误帧头、错误版本、异或字节不匹配以及物理量超范围。合法输入返回VALID,温度二十三点四、湿度五十六点七、校验值0x7B。把末字节改为7A则必须返回CRC_BAD,数值是null,UI 使用短横线代替;截断帧只能得到LENGTH_BAD,不能越界读取,更不能猜测缺失字节为零。

测试负温度也值得单独做。假设温度原始字节是FF CC,二补码应解释为负五十二个十分之一摄氏度,即负五点二度,而不能读成六千多度。这个分支与实际传感器是否能测到零下五点二无关,它验证的是大端有符号解析是否正确。校验逻辑用 XOR 演示,至少可以发现单字节翻转;但两个字节同时发生某些变化时可能相互抵消,所以它永远不能替代更强的 CRC 或加密鉴别。

最终的纯屏幕演示保持合法帧状态:页面SensorFramePage,任务BT-2605,输入字节A5 01 00 EA 02 37 7B,状态VALID,温湿度为23.4 ℃、56.7 %,HiLog 示例字符串为BT-2605 | VALID | T=23.4 | RH=56.7 | XOR=0x7B。顶部时间十七点二十四,电量百分之八十六。这些标注只代表本文设计的演示输出,不是实际采集设备值。

六、真正需要带走的边界

这个案例最终给自己立了三条规矩:字节解释必须先于页面渲染完成;拒绝状态不应携带貌似有效的测量值;传输框架和业务协议不能互相冒充。Uint8Array解决的是如何按字节访问和组合数据,并没有自动保证来源可信。页面即使做得再漂亮,也不应掩盖数据已经被拒绝的事实。若未来要接真实设备,应先确认完整的 GATT 特征值、MTU、分包方式、端序、长度、校验多项式和断链后的状态更新策略,再动手扩展适配器。

官方核对:HarmonyOS ArkTS 简介、Huawei BLE Codelab、官方 ArrayBuffer 类型转换示例。本文的七字节结构、XOR 校验、温湿度量程均为教学协议,不是华为定义的 BLE 协议接口。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询