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 的坑还有很多,祝各位小伙伴们开发顺利!
原创作品,如需转载,请与作者联系,否则将追究法律责任。