Android DFU升级

作者: 进击的包籽 | 来源:发表于2019-04-15 14:39 被阅读12次
  • 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模式,再把升级包发送过去,接受回调完成升级即可。

DFU UUID.jpg

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!!)
}

相关文章

网友评论

    本文标题:Android DFU升级

    本文链接:https://www.haomeiwen.com/subject/azzywqtx.html