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 文件等)。


android Ble开发的那些事(四)—— OTA升级_第1张图片
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 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;
    }
}

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 的坑还有很多,祝各位小伙伴们开发顺利!

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

你可能感兴趣的:(android Ble开发的那些事(四)—— OTA升级)