如何開發Android Kotlin BLE通訊 - QCA402X BLE應用

隨著物聯網時代的來臨,越來越多的智能硬體開始盛行,例如:智慧型手機、智慧型手環/錶、心率檢測器、及各式各樣智能裝置。藍牙低功耗(Bluetooth Low Energy, 或稱Bluetooth LE、BLE),Android 4.3 以上開始引入BLE,App可以利用BLE API來藍牙掃描、連線、送收資料等操作,以下使用官方提供的程式做調整與整理,並透過QCA402X開發模塊做傳輸驗證。

 

添加權限

進行BLE APP的開發,需要在AndroidManifest.xml文件中添加權限:

Android 6.0(API Level 23) 及以上,需要添加開啟位置權限,如果應用未允許位置權限,無法搜尋到BLE裝置



若以上權限無法使用,須在程式碼中加入權限確認:

 

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_COARSE_LOCATION)
    }
}

 

搜尋BLE裝置

Android 5.0 API以下版本使用 startLeScan 開始搜尋和 stopLeScan 停止搜尋的函式,將 Callback Function 傳入給系統,搜尋結果就會傳送至 mLeScanCallback內進行處理,以下的掃描方式為:

 

private var mBluetoothManager: BluetoothManager? = null
private var mBluetoothAdapter: BluetoothAdapter? = null
mBluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
mBluetoothAdapter = mBluetoothManager!!.adapter
 
mBluetoothAdapter!!.startLeScan(mLeScanCallback) // 開始搜尋
mBluetoothAdapter!!.stopLeScan(mLeScanCallback) // 停止搜尋

 

把LeScanCallback回調搜尋到的設備資料、訊號強度及設備廣播封包的內容,將搜尋到的資料呈現在ListActivity 中

 

private val mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord ->
    runOnUiThread {
        val name = device.name
        if(name != null){
            mLeDeviceListAdapter!!.addDevice(device) // 取得 BLE 裝置資料
            mLeDeviceListAdapter!!.notifyDataSetChanged()
        }
    }
}

 

Android 5.0 API以上含5.0版本使用 startScan開始搜尋和 stopScan停止搜尋的函式,將 Callback Function 傳入給系統,搜尋結果傳送至 mScanCallback內進行處理,以下的掃描方式為:

 

private var mBluetoothLeScanner: BluetoothLeScanner? = null
mBluetoothLeScanner = mBluetoothManager!!.adapter.bluetoothLeScanner

mBluetoothLeScanner!!.startScan(mScanCallback) // 開始搜尋
mBluetoothLeScanner!!.stopScan(mScanCallback) // 停止搜尋

 

將ScanCallback回調搜尋到的設備資料,其資料格式為ScanResult,使用result取得設備資料、訊號強度、設備廣播封包的內容呈現在ListActivity 中

 

private val mScanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult?) {
        super.onScanResult(callbackType, result)
        val deviceName = result?.device?.name
        if (deviceName != null && deviceName.isNotEmpty()){
            mLeDeviceListAdapter!!.addDevice(result.device!!)
            mLeDeviceListAdapter!!.notifyDataSetChanged()
        }
        override fun onBatchScanResults(results: MutableList?) {
            super.onBatchScanResults(results)
            Log.d(TAG,"onBatchScanResults:${results.toString()}")
        }
        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            Log.d(TAG, "onScanFailed: $errorCode")
        }
    }
}

 

由於BLE掃描的操作比較耗電,不能一直搜尋裝置,需設定一段時間之後停止BLE掃描:

 

mHandler!!.postDelayed({
    mScanning = false
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        mBluetoothLeScanner!!.stopScan(mScanCallback) // 5.0以下,停止搜尋
    else{
        mBluetoothLeScanner!!.stopScan(mScanCallback) // 5.0以上,停止搜尋
    }

}, SCAN_PERIOD)  // 設定延遲時間(ms)

 

連線BLE裝置

DeviceScanActivity內的 ListActivity列出搜尋到的藍牙位址,當使用者點選後,透過 Intent 方式將Mac Address傳送至 DeviceControlActivity。於onCreate內執行 BluetoothLeService 的背景服務,並在服務內執行初始化與連線。如下的程式碼:

 

val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
bindService(gattServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE)

 

連線呼叫 connectGatt 函式,第二個參數設定是否開啟系統自動連線,設定為True時,在連線的時候系統會依照排成進行連線,非即時性速度較慢,第三個參數為 BluetoothGattCallback function (mGattCallback) 做資料回調,程式碼如下:

 

val device = mBluetoothAdapter!!.getRemoteDevice(address)
mBluetoothGatt = device.connectGatt(this, false, mGattCallback)

 

BluetoothGattCallback常使用的回調,像是連線狀態改變、搜尋到GATT 的服務列表、資料變動、屬性....等等方法,這些回調收到資料後,要於UI中做顯示與資料處理,於收到資料後sendBroadcast發送資訊到DeviceControlActivity做接收,在DeviceControlActivity需先註冊監聽廣播,如下,註冊廣播後於mGattUpdateReceiver Function內處理接收到的廣播

 

val intentFilter = IntentFilter()
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED) // Gatt連線
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED) // Gatt斷線
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED) // 發現Gatt Service
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE) // 收到資料
registerReceiver( mGattUpdateReceiver, intentFilter )

 

發現Gatt服務

從BluetoothLeService內的connectGatt 第三個參數BluetoothGattCallback收到回調函式mGattCallback,mGattCallback內部幾個常用的function,分別為:onConnectionStateChange()、onServicesDiscovered()、onCharacteristicRead()、onCharacteristicChanged()等,其中onConnectionStateChange內可以判斷裝置是否連線。

如果連接上裝置後執行mBluetoothGatt!!.discoverServices(),BLE的Profile架構如左圖,當系統發現相關的Services後,通過onServicesDiscovered做回調,此時可以在DeviceControlActivity中的監聽內顯示Services列表,如右圖所示。



收發資料

韌體工程師在開發BLE設備時,會設置不同的Characteristic並設置其讀、寫、通知等屬性,只要具備這些屬性,才可以對Characteristic進行相應的讀寫等操作。讀數據和接收數據不一樣,讀數據需要主動發送指令,通過BluetoothGatt.readCharacteristic(characteristic),然後在回調方法裡接收數據,系統的BLE框架會回調onCharacteristicRead();而接收數據是BLE設備主動發送數據,則回調為onCharacteristicChanged(),只需在這兩個方法裡提取數據即可。

 

於下方兩張圖,左圖點選List內的“Send Characteristic”選項之後跳出Alert,在Alert內的EditText中輸入要傳送的Hex值,按下發送透過BluetoothGatt.writeCharacteristic(characteristic) 將資料發送出去。右圖,當資料發送出去,BLE裝置收到資料後回傳原資料,Gatt回調中的onCharacteristicChanged()會收到相關資料,收到資料後透過sendBroadcast發送到監聽廣播的DeviceControlActivity,收到廣播資料,於UI中的Data欄位顯示收到的資料,其中亂碼的部分為轉換成String的方式顯示,實際為收到的Hex資料為主。




小結

以上是Android Kotlin BLE設備接入的基本流程和讀寫接收等操作,內含Android 5.0以下及5.0以上在搜尋裝置的指令與操作的介紹,目前官方指定語言Kotlin與之前Java語法應用上些許差異,但是大致流程都差不多,希望這篇文章能對大家有所幫助。

附上GitHub程式碼供大家參考: https://github.com/s801210/Kotlin_BluetoothLeGatt

 

★博文內容均由個人提供,與平台無關,如有違法或侵權,請與網站管理員聯繫。

★文明上網,請理性發言。內容一周內被舉報5次,發文人進小黑屋喔~

評論

AQ

AQ

2022年3月9日
推推 很詳細又清楚