- DFU,全称是Device Firmware Upgrade
做蓝牙设备开发的,几乎都要遇到固件升级,我目前项目用的是Nordic的方案,所以就以此为基础,记录一下。
升级流程:或者设备的版本 -> 去服务器查看是否有更新包 -> 下载更新包 -> 校验升级 -> 指定DFU模式的名字 -> 进入DFU -> 传升级包 -> 完成后会自动重启
Nordic Github主页
Nordic 官网
如果你家的蓝牙芯片用的是Nordic方案,建议你下载他们的调试工具,叫
nRF Connect,去各个平台找,都有,可以帮你更好的理解。
蓝牙设备升级使用的UUID服务跟平时通信用的不一样,可以用工具查看
DFU Service 的UUID:
0000fe59-0000-1000-8000-00805f9b34fb
特征值UUID:
8ec90003-f315-4F60-9FB8-838830daea50
先开启 indicate ,如果对设备有重命名的要求,可以先把名字发给设备,没这个需求的就不用管,DFU模式默认名字是 DfuTarg 然后再发送进入DFU的指令,设备就会重启,自动到DFU模式,再把升级包发送过去,接受回调完成升级即可。
- Android 端 Android-DFU-Library
1.build.gradle加入远程依赖
implementation 'no.nordicsemi.android:dfu:1.9.0'
//如果你是用Jetpack
implementation 'no.nordicsemi.android:dfu:1.8.1'
2.创建一个NotificationActivity
class NotificationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isTaskRoot) {
// Start the app before finishing
//DeviceManageActivity是升级的activity
val intent = Intent(this, DeviceManageActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtras(getIntent().extras!!) // copy all extras
startActivity(intent)
}
finish()
}
}
3.创建一个DfuService,继承与DfuBaseService
class DfuService : DfuBaseService() {
override fun getNotificationTarget(): Class<out Activity> {
return NotificationActivity::class.java
}
override fun onBind(intent: Intent?): IBinder? {
return super.onBind(intent)
}
}
4.我写了一个fragmentDialog,将升级内容都放在这,使用了FastBle,大家也可以用自己写的蓝牙库,或者Nordic也有。
FastBle
中文文档
Android BLE开发详解和FastBle源码解析
object Constant {
//进入duf,uuid
val dfuUuid = "8ec90003-f315-4F60-9FB8-838830daea50"
val dfuServiceUuid = "0000fe59-0000-1000-8000-00805f9b34fb"
//进入DFU模式
val dfuEnterBootLoader: Byte = 0x01
//设置DFU模式的名字
val dfuSetName: Byte = 0x02
//dfu模式的名字
val dfuName = "BaoDfuTarg"
}
/**
* 固件升级dialog
*/
class FirmwareUpdateDialogFragment : BaseDialogFragment() {
/**
* 升级监听、回调
*/
private val dfuProgressListener = object : DfuProgressListener {
override fun onProgressChanged(deviceAddress: String?, percent: Int, speed: Float, avgSpeed: Float, currentPart: Int, partsTotal: Int) {
//升级进度
setProgress(percent)
LogUtils.d("DFU onProgressChanged")
}
override fun onDeviceDisconnecting(deviceAddress: String?) {
LogUtils.d("DFU onDeviceDisconnecting")
}
override fun onDeviceDisconnected(deviceAddress: String?) {
LogUtils.d("DFU onDeviceDisconnected")
}
override fun onDeviceConnected(deviceAddress: String?) {
LogUtils.d("DFU onDeviceConnected")
}
override fun onDfuProcessStarting(deviceAddress: String?) {
LogUtils.d("DFU onDfuProcessStarting")
}
override fun onDfuAborted(deviceAddress: String?) {
//升级流产
LogUtils.d("DFU onDfuAborted")
dfuError()
}
override fun onEnablingDfuMode(deviceAddress: String?) {
LogUtils.d("DFU onEnablingDfuMode")
}
override fun onDfuCompleted(deviceAddress: String?) {
//升级完成
LogUtils.d("DFU onDfuCompleted")
onDfuResultListener?.let {
it.onSuccess()
}
dfuFinish()
}
override fun onFirmwareValidating(deviceAddress: String?) {
LogUtils.d("DFU onFirmwareValidating")
}
override fun onDfuProcessStarted(deviceAddress: String?) {
LogUtils.d("DFU onDfuProcessStarted")
}
override fun onError(deviceAddress: String?, error: Int, errorType: Int, message: String?) {
//失败
LogUtils.d("DFU onError")
dfuError()
}
override fun onDeviceConnecting(deviceAddress: String?) {
LogUtils.d("DFU onDeviceConnecting")
}
}
private var onDfuResultListener: OnDfuResultListener? = null
private lateinit var normalDialogFragment: NormalDialogFragment
private lateinit var progressBar: ProgressBar
override fun layoutResource() = R.layout.firmware_update_dialog
private lateinit var mac: String
companion object {
fun newInstance(mac: String) = FirmwareUpdateDialogFragment().apply {
arguments = Bundle().apply {
//用mac地址指定你要升级的设备
putString("mac", mac)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.HeightDialogStyle)
arguments?.let {
mac = it.getString("mac")
if (mac.isNullOrEmpty()) {
dismissDialog()
}
}
context?.let {
DfuServiceListenerHelper.registerProgressListener(it, dfuProgressListener)
}
}
override fun onDestroy() {
super.onDestroy()
context?.let {
DfuServiceListenerHelper.unregisterProgressListener(it, dfuProgressListener)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
normalDialogFragment = NormalDialogFragment
.newInstance()
.setMessage("升级完成")
.setNegativeVisible(false)
.setPositive(getString(R.string.i_know))
.setOnClickListener(object : NormalDialogFragment.OnClickListener {
override fun onPositive(positive: String) {
}
override fun onNegative(negative: String) {
}
})
var btnUpdate = root.findViewById<TextView>(R.id.btnUpdate)
btnUpdate.setOnClickListener {
root.findViewById<View>(R.id.btnClose).visibility = View.INVISIBLE
btnUpdate.isEnabled = false
btnUpdate.setText(R.string.updating)
connectDevice()
}
root.findViewById<View>(R.id.btnClose).setOnClickListener {
dismissDialog()
}
progressBar = root.findViewById(R.id.progressBar)
}
fun setProgress(progress: Int) {
progressBar.progress = progress
}
/**
* 升级完成
*/
fun dfuFinish() {
normalDialogFragment.showDialog(fragmentManager!!)
dismissDialog()
}
/**
* 升级失败
*/
private fun dfuError() {
onDfuResultListener?.let {
it.onError()
}
dismissDialog()
}
/**
* 将DFU的蓝牙模块跟普通的分离
* 重新连接回设备
*/
private fun connectDevice() {
Thread.sleep(500)
var bleScanRuleConfig = BleScanRuleConfig.Builder()
.setDeviceMac(mac)
.build()
BleManager.getInstance().initScanRule(bleScanRuleConfig)
BleManager.getInstance().scanAndConnect(object : BleScanAndConnectCallback() {
override fun onStartConnect() {
}
override fun onScanStarted(success: Boolean) {
}
override fun onDisConnected(isActiveDisConnected: Boolean, device: BleDevice?, gatt: BluetoothGatt?, status: Int) {
}
override fun onConnectSuccess(bleDevice: BleDevice?, gatt: BluetoothGatt?, status: Int) {
indicate(bleDevice!!)
}
override fun onScanFinished(scanResult: BleDevice?) {
scanResult
}
override fun onConnectFail(bleDevice: BleDevice?, exception: BleException?) {
}
override fun onScanning(bleDevice: BleDevice?) {
}
})
}
/**
* dfu indicate
*/
private fun indicate(bleDevice: BleDevice) {
BleManager.getInstance().indicate(bleDevice
, Constant.dfuServiceUuid
, Constant.dfuUuid
, object : BleIndicateCallback() {
override fun onCharacteristicChanged(data: ByteArray?) {
}
override fun onIndicateSuccess() {
writeDfuName(bleDevice)
}
override fun onIndicateFailure(exception: BleException?) {
}
})
}
/**
* 进入dfu前,是可以对设备命名的,设备重启后,会以这个名字广播出来
* 指定dfu名字,02+名字长度+名字
*/
private fun writeDfuName(bleDevice: BleDevice) {
BleManager.getInstance().write(bleDevice
, Constant.dfuServiceUuid
, Constant.dfuUuid
, byteArrayOf(Constant.dfuSetName, Constant.dfuName.length.toByte()) + Constant.dfuName.toByteArray()
, object : BleWriteCallback() {
override fun onWriteSuccess(current: Int, total: Int, justWrite: ByteArray?) {
//进入dfu模式指令
writeDfu(bleDevice)
}
override fun onWriteFailure(exception: BleException?) {
//写入失败
dfuError()
}
})
}
/**
* 发送指令进入DFU
*/
private fun writeDfu(bleDevice: BleDevice) {
BleManager.getInstance().write(bleDevice
, Constant.dfuServiceUuid
, Constant.dfuUuid
, byteArrayOf(Constant.dfuEnterBootLoader)
, object : BleWriteCallback() {
override fun onWriteSuccess(current: Int, total: Int, justWrite: ByteArray?) {
//通知设备进入DFU模式
scanDfu()
}
override fun onWriteFailure(exception: BleException?) {
//写入失败
dfuError()
}
})
}
/**
* 扫描DFU模式的机子
*/
private fun scanDfu() {
BleManager.getInstance().disconnectAllDevice()
//设置搜索对应名字的设备
var bleScanRuleConfig = BleScanRuleConfig.Builder()
.setDeviceName(true, Constant.dfuName)
.build()
BleManager.getInstance().initScanRule(bleScanRuleConfig)
//开始搜索
BleManager.getInstance().scanAndConnect(object : BleScanAndConnectCallback() {
override fun onStartConnect() {
}
override fun onScanStarted(success: Boolean) {
}
override fun onDisConnected(isActiveDisConnected: Boolean, device: BleDevice?, gatt: BluetoothGatt?, status: Int) {
}
override fun onConnectSuccess(bleDevice: BleDevice?, gatt: BluetoothGatt?, status: Int) {
if (bleDevice?.name == Constant.dfuName) {
Thread.sleep(500)
//DFU升级包路径
val zip = Environment.getExternalStorageDirectory().absolutePath + File.separator + Environment.DIRECTORY_DOWNLOADS + File.separator + "test.zip"
var starter = DfuServiceInitiator(bleDevice.mac)
.setDeviceName(bleDevice.name)
.setKeepBond(false)
.setDisableNotification(true)
.setPacketsReceiptNotificationsEnabled(false)
.setPacketsReceiptNotificationsValue(10)
.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)
.setZip(zip)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Android O,通知栏问题
DfuServiceInitiator.createDfuNotificationChannel(context!!)
}
context?.let {
starter.start(it, DfuService::class.java)
}
}
}
override fun onScanFinished(scanResult: BleDevice?) {
//搜索不到,失败
if (scanResult == null) {
dfuError()
}
}
override fun onConnectFail(bleDevice: BleDevice?, exception: BleException?) {
//连接失败
dfuError()
}
override fun onScanning(bleDevice: BleDevice?) {
}
})
}
/**
* 升级结果
*/
interface OnDfuResultListener {
fun onSuccess()
fun onError()
}
fun setOnDfuResultListener(listener: OnDfuResultListener) {
this.onDfuResultListener = listener
}
}
使用
//固件升级?
dataBinding.clFirmwareUpdate.setOnClickListener {
val firmwareDialog = FirmwareUpdateDialogFragment.newInstance(viewModel.mg03.mac)
firmwareDialog.setOnDfuResultListener(object : FirmwareUpdateDialogFragment.OnDfuResultListener {
override fun onSuccess() {
//升级完成,重新连接
connectDevice()
}
override fun onError() {
//升级失败
connectDevice()
ToastUtils.shortToast(R.string.update_error)
}
})
firmwareDialog.showDialog(fragmentManager!!)
}