FEATURED · 精选文章

尾灯-应用层通信协议(BLE)

发布时间 / 2026/9/12 19:51:36
来源 / 创域科博编辑部
栏目 / 资讯中心
尾灯-应用层通信协议(BLE) 介绍这份刹车灯 TAG — BLE 协议是一份完整的蓝牙低功耗BLE应用层通信协议用于APP 与智能刹车灯设备之间的扫描发现、连接建立、命令交互与状态上报。第一部分BLE广播协议部分如何使用这个Manufacturer在scan_item中传入扫描结果ScanResult然后从中拿到厂商设定的数据。 //final manuData result.advertisementData.manufacturerData; 协议上有5个字节所以根据协议格式拿到自己想要的数据 前两个字节为CID后三个字节为MAC的后三个字节 BLE 广播里 ManufacturerData 的结构为 ┌────────────────┬─────────────────────────────┐ │ Company ID │ 厂商自定义数据 │ │ (2 字节, 小端) │ (N 字节) │ └────────────────┴─────────────────────────────┘ 参考以下的代码理解怎么使用manufacturerData // key Company ID从原始字节里解析出来的整数 //value 该厂商的私有数据字节 //manuData.forEach((key, value) { // if(key 1602 ) { //等于0x0642 CompanyID // _company key; // if(value.length 3){ // var mac4 value[0]; // var mac5 value[1]; // var mac6 value[2]; // _deviceMac 41:42:00:${_hex2(mac4)}:${_hex2(mac5)}:${_hex2(mac6)}; // // Log.d(⭐当前设备的MAC地址为$_deviceMac); // } // } // });第二部分:BLE连接后通讯协议部分这部分的协议规定了UUIDAPP通过UUID过滤得到相应的设备通过TX,RX和设备通信 //UUID static final Guid _serviceGuid Guid(FF20); static final Guid _txGuid Guid(FF21); //写 static final Guid _rxGuid Guid(FF22); //读 //过滤服务代码 if(service.uuid ! _serviceGuid) {//服务过滤,只处理指定服务UUID continue; } //过滤YX,RX代码 //遍历服务下的所有特征 for(var characteristic in service.characteristics) { //Log.d(characteristic.uuid.toString()); //根据 UUID 匹配特征 if(characteristic.uuid _txGuid) { _tx characteristic; //存储-发送特征值 } if(characteristic.uuid _rxGuid) { _rx characteristic; //存储-接收特征值 } }协议部分帧格式解析帧协议第一个字节和第二个字节都是固定的 第三个字节是命令ID用来区分是哪一条命令 第四个字节是用来区分是否是分包和是否需要回应 bit[0] 0,本包不是分包bit[0] 1,本包是分包 bit[1] 0本条命令不需要回应bit[1] 1,本条命令需要回应 第五个字节是序号seq 第六个字节第七个字节 是payload真正数据的长度并且是小端序显示 从第八个字节开始到倒数第二个字节都是数据段 最后一个字节是CRC校验 第一种情况不分包 payload **就是命令参数**命令表里写的字段。无参数则 N0。 第二种情况分包 各包 cmd、seq 相同 此时 payload **不是**命令参数本身 而是有一个格式payload[0]为分包序号payload[1]为分包的总数量后面的字节才是真正的数据 分包序号(1) | 总包数量(1) | chunk...(N-2) len 2 chunk长度。 设备按分包序号把各包 chunk 按序拼接得到完整命令参数后再按该 cmd 执行未收齐不执行。代码如何解析帧BleMessage.from(Listint data) { Log.d( BleMessage.from() 开始解析); // 保存原始数据 raw List.from(data); Log.d(原始数据长度: ${data.length}); if (data.length 8) { Log.d(❌ 数据长度不足: ${data.length}); return; } // 1.解析帧头 final magic data[0] 0xFF; // 0xFF的目的是为了,把有符号的字节值转成无符号的 0~255原始数据没有发生改变 final ver data[1] 0xFF; cmd data[2] 0xFF; final flags data[3] 0xFF; final seq data[4] 0xFF; //小端所以len本质上是data[6]拼上data[5]; // 8:左移八位然后或运算data[5],实现拼接效果 len (data[6] 8) | data[5]; if (magic ! 0xBF) { Log.d(❌ magic 不匹配: 0x${magic.toRadixString(16)}); return; } if (ver ! 0x01) { Log.d(❌ ver 不匹配: 0x${ver.toRadixString(16)}); return; } // 2.提取数据 payload payload []; if (len 0) { final payloadStart 7; // 帧头固定 7 字节payload 从下标 7 开始 if (data.length payloadStart len) { //判断数据长度是否正确 //偏移切包sublist(7, 7len)左闭右开从下标7开始包含一直包含到下标为7len的前一个字节 payload List.from(data.sublist(payloadStart, payloadStart len)); } else { Log.d(⚠️ payload 长度不足: 需要 $len 字节实际 ${data.length - payloadStart} 字节); payload List.from(data.sublist(payloadStart)); Log.d(⚠️ 实际提取: ${payload.length} 字节); } } else { Log.d( payload 为空); } // 3.检查 flags //0x02 0000 0010,(flags 0x02) 可以取出flag的bit[1]bit[1] 1需要回应bit[1] 0不需要回应 _response (flags 0x02) ! 0; //是否需要回应 final isFragment (flags 0x01) ! 0; //是否是分包 // 解析分包信息 if (isFragment payload.length 2) { offset payload[0] 0xFF; //分包的序号 size payload[1] 0xFF; //分包的总数量 payload.removeRange(0, 2);//删除前两个字节得到真正的数据 } else { size 0; offset 0; if (isFragment) { Log.d(⚠️ 分包标志设置但 payload 长度不足 2: ${payload.length}); } } }分片如何合并void contact(Listint data) { // 保存原始数据 raw.addAll(data); //检查数据长度 if (data.length 8) { Log.d(❌ contact 数据长度不足: ${data.length}); return; } //检查cmd final dataCmd data[2] 0xFF; if (dataCmd ! cmd) { Log.d(❌ cmd 不匹配: 数据cmd0x${dataCmd.toRadixString(16)}, 期望cmd0x${cmd.toRadixString(16)}); return; } //检查分包标志 final flags data[3] 0xFF; final isFragment (flags 0x01) ! 0; Log.d(分包标志: ${isFragment ? 是 : 否}); if (isFragment data.length 9) { final payloadStart 7; //包头大小 final dataLen (data[6] 8) | data[5]; //payload的大小 Log.d(数据长度: $dataLen 字节); if (dataLen 2) { offset data[payloadStart] 0xFF; //分包序号 size data[payloadStart 1] 0xFF;//分包的总数量 Log.d(分包信息: 分包序号为$offset, 分包的总数量为$size); if (dataLen 2) { final chunkStart payloadStart 2; //真实数据的第一个字节下标 final chunkEnd payloadStart dataLen;//真实数据的最后一个字节下标 //检查数据长度 if (data.length chunkEnd) { //拿到真正的数据 final chunk data.sublist(chunkStart, chunkEnd); //拼接数据 payload.addAll(chunk); Log.d(合并后 payload 长度: ${payload.length}); } else { Log.d(⚠️ chunk 数据不完整); } } else { Log.d( 无 chunk 数据 (dataLen 2)); } } else { Log.d(⚠️ dataLen 2无法解析分包信息); } } else { Log.d(⚠️ 不是分包或数据长度不足跳过合并); } }数据分包的逻辑1.rx监听拿到数据_valueState _rx?.onValueReceived.listen((event) { Log.d(rx: ${Log.listToHex(event)}); if(event.isNotEmpty){ _parse(event);//解析接收到的数据十进制数数组 Log.d(rx收到数据); } });2.解析数据的方法void _parse(Listint data) { Log.d( 收到原始数据 (${data.length} 字节): ${data.map((b) b.toRadixString(16).padLeft(2, 0)).join( )}); ///数据检查 // 数据完整性检查 if (data.length 8) { Log.d(❌ 数据长度不足: ${data.length}需要至少 8 字节); return; } // 检查 magic if (data[0] ! 0xBF) { Log.d(⚠️ magic 不匹配: 0x${data[0].toRadixString(16)}期望 0xBF); return; } // 检查 ver if (data[1] ! 0x01) { Log.d(⚠️ ver 不匹配: 0x${data[1].toRadixString(16)}期望 0x01); return; } ///解析为 BleMessage final msg BleMessage.from(data); Log.d( BleMessage 解析完成: cmd0x${msg.cmd.toRadixString(16)}, len${msg.len}, isAll${msg.isAll}, payload长度${msg.payload.length}); ///分片数据重组 //是完整的消息没有有正在拼接的信息 if (msg.isAll _msg null) { //_msg用来暂存“正在拼接种的分片消息” ///不分包的完整信息 Log.d(完整的消息直接解析); _dispatchMessage(msg); } else { ///分包逻辑 //正在拼接某条消息传入消息的cmd与正在等待拼接的cmd是一致的 if (_msg ! null _msg!.cmd msg.cmd) { ///除第一包的所有包 Log.d(合并分片); _msg!.contact(data); } else { ///第一包 Log.d(开始新的分片序列: cmd0x${msg.cmd.toRadixString(16)}); _msg msg; } //_msg!.contact(data);这里拼完最后一包进入if逻辑走消息分发的方法 if (_msg ! null _msg!.isAll) { Log.d(✅ 分片接收完成); _dispatchMessage(_msg!); _msg null; } else { Log.d(等待更多分片当前进度: ${_msg?.offset}/${_msg?.size}); } } }3.是否完成分包的判断bool get isAll { if(size 0){ //不分包 return true; }else{ //分包当前分包 1 分包的总数量 offset从0开始所以这里要1 final result size 0 offset 1 size; Log.d( isAll: $result (size$size, offset$offset)); return result; } }协议部分核心指令1.DEV_INFO_GET下行无 payload。 //APP给设备发送命令 上行 Notify同一 cmd。一条逻辑 payload可分包结构如下。 //设备上报信息协议解析前8个字节为 版本号传递的格式是ASCLL格式 第9个字节是 充电相关 bit[7] 1;表示在充电bit[7] 0;表示未在充电 bit[6]~bit[0]表示设备当前的电量 第10个字节表示 电量格数 第11个字节表示 智能启停 第12第13字节表示 智能启停的时间 第14个字节表示 刹车感应 第15个字节表示 刹车的时间 第16个字节表示 刹车的灵敏度 第17个字节表示 组队同频 第18个字节表示 当前被点亮的槽位 从第19个字节开始到最后一个字节表示 当前槽位上的动画 所以前18个字节为设备信息头
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻