Android,BLE,OAD,空中升级

网上资料大多都是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的时候需要同时设置描述符的值,示例如下:

//设置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);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这样就设置好了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();
 
 
  • 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去升级,反之亦然。

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;
    }
 
 
  • 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.发送升级文件

//整个过程开启了一个异步任务类,用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);
            }
 
 
  • 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();
        }


    }
}



评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值