Kết nối với máy chủ GATT

Bước đầu tiên trong quá trình tương tác với thiết bị BLE là kết nối với thiết bị. Cụ thể hơn là kết nối với máy chủ GATT trên thiết bị. Để kết nối với máy chủ GATT trên thiết bị BLE, hãy sử dụng phương thức connectGatt(). Phương thức này nhận 3 tham số: một đối tượng Context, autoConnect (một boolean cho biết liệu có tự động kết nối với thiết bị BLE ngay khi có) và một tham chiếu đến BluetoothGattCallback:

Kotlin

var bluetoothGatt: BluetoothGatt? = null
...

bluetoothGatt = device.connectGatt(this, false, gattCallback)

Java

bluetoothGatt = device.connectGatt(this, false, gattCallback);

Thao tác này sẽ 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 mà sau đó bạn có thể sử dụng để tiến hành các hoạt động 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 để cung cấp kết quả cho ứng dụng, chẳng hạn như trạng thái kết nối, cũng như mọi hoạt động khác của ứng dụng GATT.

Thiết lập 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 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 hoạt động đầ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. Giao tiếp này sẽ tương tác với thiết bị BLE thông qua API BLE. Hoạt động giao tiếp được thực hiện bằng cách sử dụng dịch vụ ràng buộc, 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 triển khai Binder để cấp quyền truy cập vào dịch vụ của hoạt động.

Kotlin

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

Java

class BluetoothLeService extends Service {

    private Binder binder = new LocalBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    class LocalBinder extends Binder {
        public BluetoothLeService getService() {
            return BluetoothLeService.this;
        }
    }
}

Hoạt động có thể bắt đầu dịch vụ bằng cách sử dụng bindService(), truyền Intent để bắt đầu dịch vụ, 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 tuỳ chọn kết nối bổ sung.

Kotlin

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

Java

class DeviceControlActivity extends AppCompatActivity {

    private BluetoothLeService bluetoothService;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bluetoothService = ((LocalBinder) service).getService();
            if (bluetoothService != null) {
                // call functions on service to check connection and connect to devices
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bluetoothService = null;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.gatt_services_characteristics);

        Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
        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. Bạn cần kiểm tra để đảm bảo rằng bộ chuyển đổi có sẵn trên thiết bị. 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ực hiện thành công.

Kotlin

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
    }

    ...
}

Java

class BluetoothLeService extends Service {

    public static final String TAG = "BluetoothLeService";

    private BluetoothAdapter bluetoothAdapter;

    public boolean initialize() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }
        return true;
    }

    ...
}

Hoạt động 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() tuỳ thuộc vào ứng dụng của bạn. Bạn có thể hiển thị thông báo lỗi cho người dùng để cho biết rằng thiết bị hiện tại không hỗ trợ hoạt động Bluetooth hoặc tắt bất kỳ tính năng nào 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.

Kotlin

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

    ...
}

Java

class DeviceControlsActivity extends AppCompatActivity {

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bluetoothService = ((LocalBinder) service).getService();
            if (bluetoothService != null) {
                if (!bluetoothService.initialize()) {
                    Log.e(TAG, "Unable to initialize Bluetooth");
                    finish();
                }
                // perform device connection
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bluetoothService = null;
        }
    };

    ...
}

Kết nối với thiết bị

Sau khi khởi chạy thực thể BluetoothLeService, thực thể này có thể kết nối với thiết bị BLE. Hoạt động cần gửi địa chỉ thiết bị đến dịch vụ để 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ỉ đó, thì getRemoteDevice() sẽ gửi IllegalArgumentException.

Kotlin

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
    } ?: run {
        Log.w(TAG, "BluetoothAdapter not initialized")
        return false
    }
}

Java

public boolean connect(final String address) {
    if (bluetoothAdapter == null || address == null) {
        Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
        return false;
    }

    try {
        final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
    } catch (IllegalArgumentException exception) {
        Log.w(TAG, "Device not found with provided address.");
        return false;
    }
    // connect to the GATT server on the device
}

DeviceControlActivity gọi hàm connect() này sau khi khởi động dịch vụ. Hoạt động cần được truyền vào địa chỉ của thiết bị BLE. Trong ví dụ sau, địa chỉ thiết bị được chuyển đến hoạt động dưới dạng ý định bổ sung.

Kotlin

// 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
            bluetooth.connect(deviceAddress)
        }
    }

    override fun onServiceDisconnected(componentName: ComponentName) {
        bluetoothService = null
    }
}

Java

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        bluetoothService = ((LocalBinder) service).getService();
        if (bluetoothService != null) {
            if (!bluetoothService.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth");
                finish();
            }
            // perform device connection
            bluetoothService.connect(deviceAddress);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        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 cần có BluetoothGattCallback để nhận thông báo về trạng thái kết nối, khả năng khám phá dịch vụ, lượt đọc đặc điểm và thông báo về đặ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. Xem bài viết Chuyển dữ liệu BLE để biết cách 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 nên có thể sử dụng lệnh gọi lại này với BluetoothDevice sau khi dịch vụ kết nối với dịch vụ đó.

Kotlin

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

Java

private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        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() sẽ được sử dụng. Để sử dụng tính năng này, bạn cần có đối tượng Context, cờ boolean autoConnectBluetoothGattCallback. 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.

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 đến.

Kotlin

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

Java

class BluetoothLeService extends Service {

...

    private BluetoothGatt bluetoothGatt;

    ...

    public boolean connect(final String address) {
        if (bluetoothAdapter == null || address == null) {
            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }
        try {
            final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
            // connect to the GATT server on the device
            bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
            return true;
        } catch (IllegalArgumentException exception) {
            Log.w(TAG, "Device not found with provided address.  Unable to connect.");
            return false;
        }
    }
}

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 khỏi máy chủ GATT, máy chủ cần thông báo cho hoạt động của trạng thái mới. Có một vài cách để thực hiện việc này. Ví dụ sau đây sử dụng thông báo truyền tin để gửi thông tin từ dịch vụ đến hoạt động.

Dịch vụ khai báo một hàm để truyền trạng thái mới. Hàm này lấy một chuỗi hành động được truyền đến đối tượng Intent trước khi được truyền lên hệ thống.

Kotlin

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

Java

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

Sau khi tạo xong 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. 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ụ biểu thị các hành động Intent.

Kotlin

class BluetoothLeService : Service() {

    private var connectionState = STATE_DISCONNECTED

    private val bluetoothGattCallback: 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

    }
}

Java


class BluetoothLeService extends Service {

    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTED = 2;

    private int connectionState;
    ...

    private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            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);
            }
        }
    };

    …
}

Nghe thông tin cập nhật về hoạt động

Sau khi dịch vụ truyền phát thông tin cập nhật kết nối, hoạt động cần triển khai BroadcastReceiver. Đăng ký receiver 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 của dịch vụ, hoạt động 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.

Kotlin

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())
        if (bluetoothService != null) {
            val result = bluetoothService!!.connect(deviceAddress)
            Log.d(DeviceControlsActivity.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)
        }
    }
}

Java

class DeviceControlsActivity extends AppCompatActivity {

...

    private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
                connected = true;
                updateConnectionState(R.string.connected);
            } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
                connected = false;
                updateConnectionState(R.string.disconnected);
            }
        }
    };

    @Override
    protected void onResume() {
        super.onResume();

        registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());
        if (bluetoothService != null) {
            final boolean result = bluetoothService.connect(deviceAddress);
            Log.d(TAG, "Connect request result=" + result);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(gattUpdateReceiver);
    }

    private static IntentFilter makeGattUpdateIntentFilter() {
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
        intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
        return intentFilter;
    }
}

Trong phần Chuyển dữ liệu BLE, BroadcastReceiver cũng được dùng để giao tiếp quá trình khám phá dịch vụ cũng như dữ liệu đặc điểm 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 việc sử dụng kết nối đó. Để 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ụ giữ tham chiếu đến BluetoothGatt. Khi hoạt động huỷ liên kết với dịch vụ, kết nối sẽ bị đóng để tránh làm tiêu hao pin thiết bị.

Kotlin

class BluetoothLeService : Service() {

...

    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}

Java

class BluetoothLeService extends Service {

...

      @Override
      public boolean onUnbind(Intent intent) {
          close();
          return super.onUnbind(intent);
      }

      private void close() {
          if (bluetoothGatt == null) {
              Return;
          }
          bluetoothGatt.close();
          bluetoothGatt = null;
      }
}