隨著物聯網時代的來臨,越來越多的智能硬體開始盛行,例如:智慧型手機、智慧型手環/錶、心率檢測器、及各式各樣智能裝置。藍牙低功耗(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
評論
AQ
2022年3月9日