android Ble开发的那些事(四)—— OTA升级

android Ble开发的那些事(一)
android Ble开发的那些事(二)
android Ble开发的那些事(三)--Ble数据分包处理
android Ble开发的那些事(四)—— OTA升级
前面几篇文章终于把ble的基本相关操作以及数据处理的大概思想写完了,终于开始写 ble 的 OTA 升级(over-the-air,又称空中升级、DFU 升级等)了!其实,整个流程也很简单,因为 Nordic Semiconductor 这家公司已经帮我们提供好了 DFU 升级的库了,只需要学会怎么用就好了,感兴趣的同学也可以去看看源码,最后附上传送门:https://github.com/NordicSemiconductor/Android-DFU-Library

其实 DFU 的升级还是要移动端和硬件共同完成,不同的产品会有不同的协议商定,但从根本上看都是一样的,换汤不换药,就是先让 ble 设备进入 DFU 模式,然后把固件升级包发送给固件进行升级。

DFU 的升级流程

接下来就分享下我项目中一个升级方案吧,可以先看下下面这张图,主要也就是这么几步来完成的(需要说明的是升级包的格式,不同的项目也许也会不一样,这里跟大家分享的是 .zip 包的,常见的还有 bin 文件、hex 文件等)。


DFU升级流程.png

1.连接检测更新

首先得连接上设备,获取到相应的版本来比对最新版本,确定是否需要升级。

private void scanAndConnectDfu( BluetoothDevice device) {
        BluetoothLeService.liteBluetooth.connect(device, false, new LiteBleGattCallback() {//搜20s
            @Override
            public void onConnectSuccess(BluetoothGatt gatt, int status) {
                gatt.discoverServices();
            }
            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                BluetoothUtil.printServices(gatt);
                //连接成功后读取所需数据
            }
            @Override
            public void onConnectFailure(BleException exception) {
                //连接失败的相关处理
            }
        });
    }

2.记录相关数据

确定需要升级后,把所需的相关数据先记录下来(为了最后的强制自检通过而做的准备),这个每个公司都有自己需求,所以这里只做参考。

private void readDataFromCharacteristic() {
    LiteBleConnector connector = liteBluetooth.newBleConnector();
    connector.withUUIDString("00002345-0000-1000-8000-00805f9b34fb", "000012345-0000-1000-8000-00805f9b34fb", null)
            .readCharacteristic(new BleCharactCallback() {
                @Override
                public void onSuccess(BluetoothGattCharacteristic characteristic) {
                    //获取数据
                    //发送指令进入dfu模式
                }

                @Override
                public void onFailure(BleException exception) {
                    BleLog.i(TAG, "Read failure: " + exception);
                    bleExceptionHandler.handleException(exception);
                }
            });
}

3.使 Ble 设备进入 DFU 模式

当所需数据都记录下来后就可以开始升级了,升级的第一步就是要先让设备进入 DFU 模式,这个呢也是需要和硬件那边沟通的,我们是直接写入一个指令就可以进入 DFU 模式了,之前的蓝牙连接也会自动断开,名字也会变成 “DfuTarg”,mac地址也会变。

4.发送升级包,DFU 升级

确保设备在 DFU 模式下之后,我们就可以用文章一开始介绍的库进行升级了,代码演示的话在后面会简单贴出。升级的话还需要一个固件升级包,这个可以放在你的文件里或者在服务器上下载,你开心就好。

4.1 搜索 DFU 模式下的该设备,并记录该设备目前状态的 mac 地址

在调用那个第三方库的时候,需要传入一个mac 地址,而这个地址是 DFU 模式下的设备的 mac 地址,之所以强调是 DFU 模式下,是因为进入 DFU 模式后,设备的 mac 地址会变化,升级成功后变回之前的 mac 地址。

liteBluetooth.startLeScan(new PeriodScanCallback(5000) {
                        @Override
                        public void onScanTimeout() {

                        }
                        @Override
                        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                            if (device.getName() == null)
                                return;
                            if (device.getName().equals("DfuTarg")){
                                BleLog.i(TAG, "device: " + device.getName() + "  mac: " + device.getAddress() + "  rssi: "
                                        + rssi + "  scanRecord: " + DeviceBytes.byte2hex(scanRecord));
                                SPUtil.put(mContext, BaseConfig.Key.DFU_ADDRESS,device.getAddress());
                                //调用第三方库开始 DFU 升级
                                sendBroadcast(new Intent(BluetoothLeService.ACTION_DFU_UPDATA));
                            }
                        }
                    });
4.2 简单使用 Android-DFU-Library

这里只做简单使用的介绍,并没有添加异常处理,异常情况还需自己考虑。

4.2.1 在工程中加入 Android-DFU-Library 这个库
compile 'no.nordicsemi.android:dfu:0.6.3'
4.2.2 创建一个 DfuService 并注册
public class DfuService extends DfuBaseService {

    @Override
    protected Class<? extends Activity> getNotificationTarget() {
      /*
       * As a target activity the NotificationActivity is returned, not the MainActivity. This is because the notification must create a new task:
       *
       * intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       *
       * when user press it. Using NotificationActivity we can check whether the new activity is a root activity (that means no other activity was open before)
       * or that there is other activity already open. In the later case the notificationActivity will just be closed. System will restore the previous activity.
       * However if the application has been closed during upload and user click the notification a NotificationActivity will be launched as a root activity.
       * It will create and start the main activity and terminate itself.
       *
       * This method may be used to restore the target activity in case the application was closed or is open. It may also be used to recreate an activity
       * history (see NotificationActivity).
       */
        return null;
    }
}
<service
    android:name="com.test.bluetooth.DfuService"
    android:exported="true"
    android:label="@string/dfu_service_title" >
4.2.3 创建一个 DfuProgressListener 并在对应的 Activity 中注册和反注册

DfuProgressListener 中会有很多的回调函数,在对应的回调函数中可以进行相应的操作,其中以下几个回调函数比较重要:

  • onProgressChanged():在这个回调中,我们可以根据所给的升级进度参数更新界面的进度百分比
  • onDfuCompleted():升级完成,停止 DFU 服务,重新连接设备并强制自检
  • onError():升级失败
  • onDfuAborted():由于意外原因,升级流产,升级失败
private final DfuProgressListener mDfuProgressListener = new DfuProgressListener() {
        @Override
        public void onDeviceConnecting(String deviceAddress) {
            Log.i("dfu", "onDeviceConnecting");
        }

        @Override
        public void onDeviceConnected(String deviceAddress) {
            Log.i("dfu", "onDeviceConnected");
        }

        @Override
        public void onDfuProcessStarting(String deviceAddress) {
            Log.i("dfu", "onDfuProcessStarting");
        }

        @Override
        public void onDfuProcessStarted(String deviceAddress) {
            Log.i("dfu", "onDfuProcessStarted");
        }

        @Override
        public void onEnablingDfuMode(String deviceAddress) {
            Log.i("dfu", "onEnablingDfuMode");
        }

        @Override
        public void onProgressChanged(String deviceAddress, int percent, float speed, float avgSpeed, int currentPart, int partsTotal) {
            Log.i("dfu", "onProgressChanged");
            Log.i("dfu", "onProgressChanged" + percent);
            dfuDialogFragment.setProgress(percent);
        }

        @Override
        public void onFirmwareValidating(String deviceAddress) {
            Log.i("dfu", "onFirmwareValidating");
        }

        @Override
        public void onDeviceDisconnecting(String deviceAddress) {

            Log.i("dfu", "onDeviceDisconnecting");
        }

        @Override
        public void onDeviceDisconnected(String deviceAddress) {
            Log.i("dfu", "onDeviceDisconnected");

        }

        @Override
        public void onDfuCompleted(String deviceAddress) {
            Log.i("dfu", "onDfuCompleted");
            stopDfu();
            dfuDialogFragment.getProgressBar().setIndeterminate(true);
            //升级成功,重新连接设备
        }

        @Override
        public void onDfuAborted(String deviceAddress) {
            Log.i("dfu", "onDfuAborted");
            //升级流产,失败
        }

        @Override
        public void onError(String deviceAddress, int error, int errorType, String message) {
            Log.i("dfu", "onError");
            stopDfu();
            dfuDialogFragment.dismiss();
            Toast.makeText(mContext, "升级失败,请重新点击升级。", Toast.LENGTH_SHORT).show();
        }
    };

在 onResume 注册 mDfuProgressListener:

    @Override
    protected void onResume() {
        DfuServiceListenerHelper.registerProgressListener(this, mDfuProgressListener);
        super.onResume();
    }

在 onPause 反注册 mDfuProgressListener:

    @Override
    protected void onPause() {
        DfuServiceListenerHelper.unregisterProgressListener(this, mDfuProgressListener);
        super.onPause();
    }
4.2.4 开启 DfuService 进行升级
new DfuServiceInitiator(mac_address)
        .setDisableNotification(true)
        .setZip(R.raw.testFile)
        .start((getBaseContext()), DfuService.class);

5.重新连接,自检

DFU 升级完成后,会有个完成的回调,我们就在这个回调里重新连接设备了。我们的设备在升级后是需要自检的,需要对空,然而这样的事怎么可能交给用户操作呢,用户就是上帝哇!所以呢,这也是为什么我们要在升级前保存自检所需的相关数据的原因了。当设备连接成功后,我们就把数据写进去,最后写入一个强制自检通过的指令就大功告成了!

其实很想总结下 android Ble 开发中的那些坑,然后有心无力啊,因为好多我自己都还没解决掉,比如说:

  • 连接过程中遇到133这个状态就怎么都连不上了,有时候得重新开关蓝牙或者等一段时间再去连,网上看到的解决方案是,每次断开连接后都 close ,尝试了下,好像还是会偶现。
  • 有款魅族的机型,当 Ble 设备进入升级模式后,每次在给 Ble 设备写升级包的过程中必挂!
  • 有些安卓手机不能反复给 Ble 设备升级(反复升级,是开发中 Ble 设备升级后,重新降级再次升级),因为连接设备后获取的服务列表没有被更新,一直是设备升级后的服务列表,重新开机就好了。这好像是因为缓存的原因,但是没有找到调用清理蓝牙缓存的方法,网上查到的资料好像是说,这个方法在内部的,想使用得需要用反射的方式才能调用。
  • 还有好多好多。。。。

PS:最后提示大家,能不升级就别升级吧!android 的 Ble 升级就像场赌博,很不稳定,加上各种机型。当也有可能是我对异常情况没有做处理的原因吧,android Ble 的坑还有很多,祝各位小伙伴们开发顺利!

原创作品,如需转载,请与作者联系,否则将追究法律责任。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容