网上资料大多都是OAT升级和 DFU 升级的
非常感谢楼主写的博文,解决了我的问题,建议阅读原文
转载地址:http://blog.csdn.net/One_Month/article/details/72901646
最近项目中用到了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的时候需要同时设置描述符的值,示例如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这样就设置好了notification,现在发送给蓝牙设备消息,就可以收到回执了,有消息返回会调用onCharacteristicChanged(luetoothGatt gatt,
BluetoothGattCharacteristic characteristic),在这里接受到回执,可以判断下是哪个characteristic的回执信息,有时候FFC1发消息,FFC2也会回执消息。
3,发送查询镜像版本信息
接下来我们需要发消息查询镜像版本和类型,示例如下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
发送消息成功,蓝牙设备会回复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去升级,反之亦然。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
5.发送升级文件
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
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,做到兼容性和速度的平衡。
上面是原文,下面是我写的代码,一个方法
1,我这下面的代码是符合升级条件下,并且手环进入了OAD升级模式之后的操作
2, 从文件第四个字节开始复制8个字节发到OAD IMAGE NOTIFY的UUID(从原文中可以看懂,楼主是猜测通过发送这个,所以才不用发送结束标记的)
3,然后把文件从第0个字节到最后一个字节发到OAD IMAGE BLOCK_REQUEST的UUID(这个是oad升级需要用到的升级文件,我用的文件后缀为 .bin)
public static void oadUpdata113(){ BluetoothGattCharacteristic imageNotifyCharacter2 = imageNotifyCharacter; BluetoothGattCharacteristic imageBlockRequestCharacter2 = imageBlockRequestCharacter; BluetoothGatt mBluetoothGatt2 = mBluetoothGatt; sendFileAirUpgrade = true; //开始发送空中升级信息 int EACH_PACKAGE_SIZE = 16 ; //每次发送16个字节; InputStream is = null; int fileSize = 0; //文件的大小 byte [] fileByteArray = null; //把文件读取到数组中 try { //获取文件 is =MyApplication.context.getAssets().open(AirUpgradeActivity.nameFor113); fileSize = is.available(); //把文件读取到数组中 fileByteArray = new byte[fileSize]; is.read(fileByteArray,0,fileSize); } catch (IOException e){ e.printStackTrace(); } //判断有多少个包 int packnum = fileSize /EACH_PACKAGE_SIZE; int lastNum = fileSize % EACH_PACKAGE_SIZE; //检测是否有最后一个不满足16字节的包 int lastPack = packnum; //总包数 if(lastNum!=0){ lastPack++; } int FILE_HEADER_SIZE = 8; /**要发送的升级通知消息,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(fileByteArray, 4, prepareBuffer, 0, FILE_HEADER_SIZE); //首先配置信息发送到蓝牙设备 imageNotifyCharacter2.setValue(prepareBuffer); mBluetoothGatt2.writeCharacteristic(imageNotifyCharacter2); //然后发送文件 for(int i = 0; i<lastPack; i++){ /* *最终要发送的包还要加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[EACH_PACKAGE_SIZE+2]; //低位 temp[0] = (byte) (i & 0xff); //高位 temp[1] = (byte) (i >> 8 & 0xff); Log.d("doInBackground","正在发包" + i + "/" + new Gson().toJson(temp)); //复制数组 System.arraycopy(fileByteArray, i * EACH_PACKAGE_SIZE, temp, 2, EACH_PACKAGE_SIZE); Log.d("doInBackground","发送包内容" + new Gson().toJson(ByteUtil.bytetoShortHex(temp))); imageBlockRequestCharacter2.setValue(temp); mBluetoothGatt2.writeCharacteristic(imageBlockRequestCharacter2); //写入成功启动 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } }