Bước đầu tiên trong việc tương tác với một thiết bị BLE là kết nối với thiết bị đó. Cụ thể hơn, kết nối với máy chủ GATT trên thiết bị. Để kết nối với một máy chủ GATT trên thiết bị BLE, hãy dùng phương thức connectGatt(). Phương thức này có 3 tham số: một đối tượng Context, autoConnect (một giá trị boolean cho biết có tự động kết nối với thiết bị BLE ngay khi thiết bị này có sẵn hay không) và một tham chiếu đến BluetoothGattCallback:
var bluetoothGatt: BluetoothGatt? = null // ... bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
Thao tác này kết nối với máy chủ GATT do thiết bị BLE lưu trữ và trả về một thực thể BluetoothGatt. Sau đó, bạn có thể dùng thực thể này để thực hiện các thao tác của ứng dụng GATT. Phương thức gọi (ứng dụng Android) là ứng dụng GATT. BluetoothGattCallback được dùng để gửi kết quả đến ứng dụng, chẳng hạn như trạng thái kết nối, cũng như mọi thao tác tiếp theo của ứng dụng GATT.
Thiết lập một dịch vụ ràng buộc
Trong ví dụ sau, ứng dụng BLE cung cấp một hoạt động (DeviceControlActivity) để kết nối với các thiết bị Bluetooth, hiển thị dữ liệu thiết bị, đồng thời hiển thị các dịch vụ và đặc điểm GATT mà thiết bị hỗ trợ. Dựa trên thông tin đầu vào của người dùng, hoạt động này giao tiếp với một Service có tên là BluetoothLeService, tương tác với thiết bị BLE thông qua BLE API. Quá trình giao tiếp được thực hiện bằng cách sử dụng một dịch vụ liên kết cho phép hoạt động kết nối với BluetoothLeService và gọi các hàm để kết nối với thiết bị. BluetoothLeService cần một quy trình triển khai Binder cung cấp quyền truy cập vào dịch vụ cho hoạt động.
class BluetoothLeService : Service() { private val binder = LocalBinder() override fun onBind(intent: Intent): IBinder? { return binder } inner class LocalBinder : Binder() { fun getService(): BluetoothLeService { return this@BluetoothLeService } } }
Hoạt động này có thể khởi động dịch vụ bằng cách sử dụng bindService(), truyền vào một Intent để khởi động dịch vụ, một phương thức triển khai ServiceConnection để theo dõi các sự kiện kết nối và ngắt kết nối, cũng như một cờ để chỉ định các lựa chọn kết nối bổ sung.
class DeviceControlActivity : AppCompatActivity() { private var bluetoothService: BluetoothLeService? = null // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> // call functions on service to check connection and connect to devices } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.gatt_services_characteristics) val gattServiceIntent = Intent(this, BluetoothLeService::class.java) bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE) } }
Thiết lập BluetoothAdapter
Sau khi được liên kết, dịch vụ cần truy cập vào BluetoothAdapter. Thao tác này sẽ kiểm tra xem thiết bị có bộ chuyển đổi hay không. Hãy đọc bài viết Thiết lập Bluetooth để biết thêm thông tin về BluetoothAdapter. Ví dụ sau đây gói mã thiết lập này trong một hàm initialize() trả về giá trị Boolean cho biết thành công.
private const val TAG = "BluetoothLeService" class BluetoothLeService : Service() { private var bluetoothAdapter: BluetoothAdapter? = null fun initialize(): Boolean { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter.") return false } return true } // ... }
Hoạt động này gọi hàm này trong quá trình triển khai ServiceConnection.
Việc xử lý giá trị trả về sai từ hàm initialize() phụ thuộc vào ứng dụng của bạn. Bạn có thể cho người dùng thấy thông báo lỗi cho biết thiết bị hiện tại không hỗ trợ hoạt động Bluetooth hoặc vô hiệu hoá mọi tính năng yêu cầu Bluetooth hoạt động. Trong ví dụ sau, finish() được gọi trên hoạt động để đưa người dùng quay lại màn hình trước.
class DeviceControlActivity : AppCompatActivity() { // Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } } // ... }
Kết nối với thiết bị
Sau khi được khởi tạo, phiên bản BluetoothLeService có thể kết nối với thiết bị BLE. Hoạt động này cần gửi địa chỉ thiết bị đến dịch vụ để có thể bắt đầu kết nối. Trước tiên, dịch vụ sẽ gọi getRemoteDevice() trên BluetoothAdapter để truy cập vào thiết bị. Nếu bộ chuyển đổi không tìm thấy thiết bị có địa chỉ đó, getRemoteDevice() sẽ gửi IllegalArgumentException.
fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address.") return false } // connect to the GATT server on the device return true } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
DeviceControlActivity sẽ gọi hàm connect() này sau khi dịch vụ được khởi chạy. Hoạt động này cần truyền địa chỉ của thiết bị BLE. Trong ví dụ sau, địa chỉ thiết bị được truyền đến hoạt động dưới dạng một phần bổ sung của ý định.
// Code to manage Service lifecycle. private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected( componentName: ComponentName, service: IBinder ) { bluetoothService = (service as LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth") finish() } // perform device connection deviceAddress?.let { bluetooth.connect(it) } } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
Khai báo lệnh gọi lại GATT
Sau khi hoạt động cho dịch vụ biết thiết bị cần kết nối và dịch vụ kết nối với thiết bị, dịch vụ cần kết nối với máy chủ GATT trên thiết bị BLE. Kết nối này yêu cầu BluetoothGattCallback để nhận thông báo về trạng thái kết nối, phát hiện dịch vụ, đọc đặc điểm và thông báo đặc điểm.
Chủ đề này tập trung vào các thông báo về trạng thái kết nối. Hãy xem phần Truyền dữ liệu BLE để biết cách thực hiện việc khám phá dịch vụ, đọc đặc điểm và yêu cầu thông báo về đặc điểm.
Hàm onConnectionStateChange() được kích hoạt khi kết nối với máy chủ GATT của thiết bị thay đổi.
Trong ví dụ sau, lệnh gọi lại được xác định trong lớp Service để có thể dùng với BluetoothDevice sau khi dịch vụ kết nối với lớp đó.
private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } }
Kết nối với dịch vụ GATT
Sau khi BluetoothGattCallback được khai báo, dịch vụ có thể sử dụng đối tượng BluetoothDevice từ hàm connect() để kết nối với dịch vụ GATT trên thiết bị.
Hàm connectGatt() được dùng. Thao tác này yêu cầu một đối tượng Context, một cờ boolean autoConnect và BluetoothGattCallback. Trong ví dụ này, ứng dụng đang kết nối trực tiếp với thiết bị BLE, vì vậy, false được truyền cho autoConnect.
Một thuộc tính BluetoothGatt cũng được thêm vào. Điều này cho phép dịch vụ đóng kết nối khi không còn cần thiết.
class BluetoothLeService : Service() { // ... private var bluetoothGatt: BluetoothGatt? = null // ... fun connect(address: String): Boolean { bluetoothAdapter?.let { adapter -> try { val device = adapter.getRemoteDevice(address) // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback) return true } catch (exception: IllegalArgumentException) { Log.w(TAG, "Device not found with provided address. Unable to connect.") return false } } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } } }
Thông tin cập nhật về chương trình phát sóng
Khi kết nối hoặc ngắt kết nối với máy chủ GATT, máy chủ cần thông báo cho hoạt động về trạng thái mới. Có một số cách để thực hiện việc này. Ví dụ sau đây sử dụng broadcast để gửi thông tin từ dịch vụ đến hoạt động.
Dịch vụ này khai báo một hàm để truyền trạng thái mới. Hàm này nhận một chuỗi hành động được truyền đến một đối tượng Intent trước khi được phát đến hệ thống.
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Sau khi có hàm truyền tin, hàm này sẽ được dùng trong BluetoothGattCallback để gửi thông tin về trạng thái kết nối với máy chủ GATT. Các hằng số và trạng thái kết nối hiện tại của dịch vụ được khai báo trong dịch vụ đại diện cho các thao tác Intent.
class BluetoothLeService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server connectionState = STATE_CONNECTED broadcastUpdate(ACTION_GATT_CONNECTED) } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server connectionState = STATE_DISCONNECTED broadcastUpdate(ACTION_GATT_DISCONNECTED) } } } // ... companion object { const val ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED" const val ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED" private const val STATE_DISCONNECTED = 0 private const val STATE_CONNECTED = 2 } }
Lắng nghe thông tin cập nhật về hoạt động
Sau khi dịch vụ truyền tin cập nhật về kết nối, hoạt động cần triển khai BroadcastReceiver.
Đăng ký bộ nhận này khi thiết lập hoạt động và huỷ đăng ký khi hoạt động rời khỏi màn hình. Bằng cách theo dõi các sự kiện từ dịch vụ, hoạt động này có thể cập nhật giao diện người dùng dựa trên trạng thái kết nối hiện tại với thiết bị BLE.
class DeviceControlActivity : AppCompatActivity() { private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { BluetoothLeService.ACTION_GATT_CONNECTED -> { connected = true updateConnectionState(R.string.connected) } BluetoothLeService.ACTION_GATT_DISCONNECTED -> { connected = false updateConnectionState(R.string.disconnected) } } } } override fun onResume() { super.onResume() registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()) bluetoothService?.let { service -> deviceAddress?.let { address -> val result = service.connect(address) Log.d(TAG, "Connect request result=$result") } } } override fun onPause() { super.onPause() unregisterReceiver(gattUpdateReceiver) } private fun makeGattUpdateIntentFilter(): IntentFilter { return IntentFilter().apply { addAction(BluetoothLeService.ACTION_GATT_CONNECTED) addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED) } } }
Trong Chuyển dữ liệu BLE, BroadcastReceiver cũng được dùng để truyền đạt thông tin về việc phát hiện dịch vụ cũng như dữ liệu đặc trưng từ thiết bị.
Đóng kết nối GATT
Một bước quan trọng khi xử lý các kết nối Bluetooth là đóng kết nối khi bạn hoàn tất. Để thực hiện việc này, hãy gọi hàm close() trên đối tượng BluetoothGatt. Trong ví dụ sau, dịch vụ này giữ tham chiếu đến BluetoothGatt. Khi hoạt động huỷ liên kết khỏi dịch vụ, kết nối sẽ đóng để tránh làm hao pin thiết bị.
class BluetoothLeService : Service() { // ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }