Android BLE蓝牙oad升级实践之路

最近项目中用到了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,做到兼容性和速度的平衡。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 43
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值