Android BLE蓝牙oad升级实践之路

xiaoxiao2021-02-28  78

最近项目中用到了ble的蓝牙升级功能,看到网上基本找不到android的oad升级资源,只有一个demo源码包(文章最后会放置这个文件),网上基本都是OTA升级介绍,正好有空,来说说我的填坑之路,最近做了个实验发现可以大大提高蓝牙升级速度,遂做这次补充,补充在最后。

1.OAD升级原理 oad升级有2个文件,都是bin格式的文件,imagA和imagB,两个镜像文件,为了防止蓝牙升级出错,需要先查询当前蓝牙的镜像类型是哪个,如果是A镜像就用B文件去升级,如果是B镜像就用A去升级。

2.蓝牙镜像类型和版本查询 查询类型和版本需要使用相关的service和characteristic,下面是我的项目中使用到的,具体是那个还要看自己的情况 BT_OAD_SERVICE @“F000FFC0-0451-4000-B000-000000000000” //服务 BT_OAD_IMAGE_NOTIFY @“F000FFC1-0451-4000-B000-000000000000” //查询版本,发送升级通知 BT_OAD_IMAGE_BLOCK_REQUEST @“F000FFC2-0451-4000-B000-000000000000” //发送升级文件

Descriptor描述符UUID 00002901-0000-1000-8000-00805f9b34fb Descriptor描述符UUID 00002902-0000-1000-8000-00805f9b34fb

Note that: F000FFC1:这特征值用于发送版本查询动作和发送升级通知给蓝牙设备。F000FFC2:这个特征值用于发送升级文件 FFC1和FFC2都有这2个描述符,IOS不需要设置描述符信息就可以接受notify信息,但是android不设置的话我没能收到信息,所以为FFC1和FFC2设置notification的时候需要同时设置描述符的值,示例如下:

//设置notification mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); // 判断是否是蓝牙升级服务,这里有其他的服务,是项目其他需求,看格式就行, //自己是那个服务和characteristic就判断哪个, //UUID_QUERY_IMAGE就是FFC1,UUID_UPDATE_BT就是FFC2,这两个是我定义的字符串常量,是对应的characteristic的UUID if(UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())|| UUID_QUERY_IMAGE.equals(characteristic.getUuid()) || UUID_UPDATE_BT.equals(characteristic.getUuid())) { //00002902开头的描述符UUID是我项目用到的 BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); }

这样就设置好了notification,现在发送给蓝牙设备消息,就可以收到回执了,有消息返回会调用onCharacteristicChanged(luetoothGatt gatt,BluetoothGattCharacteristic characteristic), 在这里接受到回执,可以判断下是哪个characteristic的回执信息,有时候FFC1发消息,FFC2也会回执消息。

3,发送查询镜像版本信息 接下来我们需要发消息查询镜像版本和类型,示例如下

new Thread(new Runnable() { @Override public void run() { //启动一个线程执行查询任务 byte[] data = new byte[1]; data[0] = 0; //发送0,发送成功后,间隔200ms左右发送1,query_cha是FFC1的characteristic。 query_cha.setValue(data); boolean success = false; success = mBluetoothLeService.writeCharacteristic(query_cha); Logger.d(success+""); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } //发送1 data[0] = 1; query_cha.setValue(data); success = mBluetoothLeService.writeCharacteristic(query_cha); } }).start();

发送消息成功,蓝牙设备会回复0000,但是这对于我们没太大用处,发送完0,1,回复我们01 00 00 7C 42 42 42 42类似这样的信息,一共8个字节(我们后面发的升级通知给蓝牙设备其实也就是发这种形式的信息),这些都是16进制表示。第一个字节01是镜像版本,7C*4其实就是镜像文件的真正大小,42是镜像类型,ASCII码代表的是字母"B",也就是说蓝牙设备现在是镜像B,我们升级的话就要发给他镜像A文件。我的项目这边,蓝牙设备发来的版本信息有个计算规则,如果是镜像A,版本号就除以2,比如版本为2,我这边就是除以2,算出版本为1,再和我这边定义版本号做对比,如果是镜像B就先-1再除以2,比如发来的版本号是1我这边算出是0。具体应该是根据嵌入式那边的来。

4.发送升级通知 获得了版本号和镜像类型,对比版本后如果可以升级,就先加载升级文件,查询到是镜像B就用A去升级,反之亦然。

private boolean loadFile(String filepath, boolean isAsset) { boolean fSuccess = false; // Load binary file try { // 判断是否是Asset文件夹下的文件,是的话打开文件 InputStream stream; if (isAsset) { stream = getAssets().open(filepath); } else { File f = new File(filepath); stream = new FileInputStream(f); } //获取文件大小,有时候available()无法得到文件大小,但是这里还可以获得, //不同的类里面可能对available()方法重写的不同,有的可能不是获取文件大小 dataSize = stream.available(); //根据文件大小创建数组,将内容存到数组中 mFileBuffer = new byte[dataSize]; stream.read(mFileBuffer, 0, dataSize); stream.close(); } catch (IOException e) { // Handle exceptions here return false; } //要发送的升级通知消息,FILE_HEADER_SIZE这里设置的是8,其实应该就发8个字节, //但是demo中又来了个+2+2,索性就按照他的来 byte[] prepareBuffer = new byte[FILE_HEADER_SIZE + 2 + 2]; //设置要发送的内容,从文件数组下标4将内容复制到prepareBuffer数组中01 00 00 7C 42 42 42 42 //一定包含这样类型的字节信息,其实就是通知蓝牙设备发送的镜像版本01,大小7C(嵌入式那边7C*4就是文件长度, //我猜就是通过这个我们最后才不用发送结束信号,设备那边就能自动判断是否发送结束,有兴趣的可以打印出来要发 //送的文件的前12个字节看看) System.arraycopy(mFileBuffer, 4, prepareBuffer, 0, FILE_HEADER_SIZE); //将信息发送到蓝牙设备 query.setValue(prepareBuffer); fSuccess = mBluetoothLeService.writeCharacteristic(query); return fSuccess; }

5.发送升级文件

//整个过程开启了一个异步任务类,用handler更新界面 protected Void doInBackground(Void... params) { int nowSize = 0; mHandler.sendEmptyMessage(0); boolean over = loadFile(finalPath, true); /**发送升级通知,判断是否发送成功,间隔一段时间开发发送文件*/ if (over) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendEmptyMessage(1); //发送文件 //用于发送升级文件的characteristic在升级过程中会每发送一个包就回复一次, //很耽误效率,所以最好先设置成不回复消息 update.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); //EACH_PACKAGE_SIZE代表每个数据包数据部分的大小, //这里按照demo中设定的一样16,计算一共需要发送多少个包*/ int blockNum = dataSize / EACH_PACKAGE_SIZE; //计算有没有不满16字节的数据包,有的话加上这个包 int lastNum = dataSize % EACH_PACKAGE_SIZE; //检测是否有最后一个不满足16字节的包 int lastBlockNum = blockNum; //总包数 if (lastNum != 0) { lastBlockNum = blockNum + 1; } Logger.d("分的包数" + lastBlockNum); //这里设置的升级锁,如果为真,如果有其他发送给蓝牙的消息一概丢弃,避免对升级造成影响 ComService.UPDATE_ROM_LOCK = true; for (int i = 0; i < lastBlockNum; i++) { //中途失败就取消异步任务,每次都判断下是否取消 if (isCancelled()) { return null; } //最终要发送的包还要加2个头字节,表示发送的包的索引,索引从0开始,索引从0开始,索引从0开始,重要的话说三遍, //这是折磨我很久的坑,但是不知道是不是都是需要从0开始,我之前从1开始一直不行,低位在前,高位在后, //比如第257个包,temp[0]就是1,temp[1]是1,都是16进制表示,还原到2进制就是0000 0001 0000 0001, //这就是头2个字节组合后代表的数字257*/ byte[] temp = new byte[2 + EACH_PACKAGE_SIZE]; //低位 temp[0] = (byte) (i & 0xff); //高位 temp[1] = (byte) (i >> 8 & 0xff); Logger.d("正在发包" + i + "/" + lastBlockNum); //每次偏移数据部分大小,将文件数组复制到发送的包中。 System.arraycopy(mFileBuffer, i * EACH_PACKAGE_SIZE, temp, 2, EACH_PACKAGE_SIZE); Logger.d("发送包内容" + ByteUtil.bytetoShortHex(temp)); update.setValue(temp); boolean b = mBluetoothLeService.writeCharacteristic(update); Logger.d("发送文件结果" + b); if (b) { nowSize = nowSize + EACH_PACKAGE_SIZE; Message message = Message.obtain(); message.what = 4; message.arg1 = (int) ((i + 1) / (float) lastBlockNum * 100); mHandler.sendMessage(message); //更新进度条 try { //这里需要注意发送的间隔时间 不能太短 不能太短 不能太短! demo中默认设置是20ms, //但是android实际测试完全不行,后来我改成50ms升级成功了,但是第二天再试又出错了, //又改成了60ms,升级有成功了,但是只是在小米试过,其他的不知道60ms会不会出问题, //还是ios强大,间隔20ms完全没问题!这里的mHandler都是用来更新界面的,比如刷新进度条 Thread.sleep(SEND_TIME_INTERNAL); } catch (InterruptedException e) { e.printStackTrace(); } } else { mHandler.sendEmptyMessage(2); cancel(true); break; } } } else { mHandler.sendEmptyMessage(2); //错误相关 cancel(true); }

6.更新成功识别和总结 完成了上面的步骤就可以等着蓝牙自己更新了,更新完成我这边蓝牙会自动断开一次,可以以此判断是否升级成功,当然更好的方法还是再次查询蓝牙版本和镜像类型,镜像类型从A变B或者从B变A就表示成功了。总结oad升级需要一下几步

① 找到对应服务和特征值,可能还需要找到描述符 ②为特征设置notification,为描述符设置开启notification信息。 ③发送查询版本和镜像信息 ④根据查到的版本和镜像类型决定发送给设备的文件, ⑤提取要发送的文件的几个字节数据,从索引4开始,可以先试试提取8个字节看看能行不,发送给设备 ⑥间隔200-300ms开始发送文件,利用FFC2发送,最好设置FFC2为无回执

7,官方demo源码包地址 http://download.csdn.net/detail/one_month/9863684

8,近期实验提高蓝牙升级速度 上次一个道友说通过接收到oncharacteristicWrite回调再发送下一个包太慢,当时也没太好的办法就推荐间隔固定时间发下一包,最近发现,可以吧发送升级文件的那个characteristic设置成没有回执,setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);前面的代码就是这么设置,但是发现还是会接收到设备的回执信息,但是再多一个操作就可以做到无回执,characteristic.setCharacteristicNotification(Characteristic cha,boolean enabled),设置为false,还有他所属的Descriptor设置成关闭notification的状态,就是和上面开始notification反着来,关闭notification,然后发现设置成每包发送间隔为20ms都能升级成功 直接提高2倍速度,有时候设置间隔10ms都能成功,大家可以试试10-30ms,做到兼容性和速度的平衡。

转载请注明原文地址: https://www.6miu.com/read-51529.html

最新回复(0)