Gửi dữ liệu đến BLESerial3 bằng Bluetooth LE Android

Về cơ bản kết nối đến các thiết bị đều giống nhau và tuỳ vào thiết bị mà có thêm vào bước xử lý đặc biệt khác.

Có 2 loại Bluetooth: Bluetooth Classic và BLE (Android 4.3 trở lên mới có BLE)

Muốn kết nối tới một thiết bị dùng Bluetooth LE thì ta cần một đối tượng của lớp BluetoothDevice. Để có đối tượng này được trả về khi bạn scan thiết bị xung quanh, việc còn lại của bạn là lọc, so sánh xem đã đúng module cần kết nối hay chưa.  Thông thường chúng ta lọc bằng địa chỉ MAC của thiết bị hoặc tên của thiết bị.

Sau khi tìm thấy module cần kết nối thì chúng ta cần xem thiết bị đó có những Service gì và có Service mình cần thì dùng hay không thông qua SERVICE_UUID. Cuối cùng ghi hay đọc lên các đặc tính của Service đó.

Tóm lại các bước ghi đó là:

  • Quét các thiết bị trong phạm vi và lọc device bằng địa chỉ MAC hoặc tên.
  • Kết nối bằng đối tượng của lớp BluetoothDevice.
  • Khám phá các dịch vụ và khám phá các đặc tính có trong những dịch vụ đó.
  • Ghi giá trị lên đặc tính.
  • Đọc giá trị từ đặc tính.

Nào bắt đầu thôi

Thống nhất khái niệm

  • Device/Client/Module tức là cái BLESerial3.
  • Điện thoaị là thiết bị chạy Android trên 4.3.
  • Ble là Bluetooth Low energy.
  • BC là Bluetooth classic.

Định nghĩa các UUID

Chú ý UUID chữ hoa và thường có khác nhau. Nêú để chữ in hoa bạn sẽ không thể kết nối đến được device. Dưới đây là thông tin SERVICE dùng để kết nối và CHARACTERISTIC_UUID để ghi. (Module này không cho phép đọc nhé!)

private static UUID SERVICE_UUID = UUID.fromString("feed0001-c497-4476-a7ed-727de7648ab1");
private static final UUID CHARACTERISTIC_UUID = UUID.fromString("feedaa02-c497-4476-a7ed-727de7648ab1");

Những UUID này lấy ở đâu? Khi nhà sản xuất họ làm ra phần cứng thì họ sẽ nạp cố định cho nó nên bạn là nhà phát triển bạn có thể yêu cầu họ cung cấp thông tin này cho mình.

Hình ảnh BLESerial3

BLESerial3 module

Cài đặt mã nguồn

Quét các thiết bị trong phạm vi và lọc device.

Ở đây cần tìm ra một thiết bị mình cần với điều kiện trùng SERVICE UUID. Sau khi tìm ra thì cần trả kết quả về thông qua đối tượng callback là mScanCallback. 

ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setReportDelay(1000).build();
ScanFilter scanFilter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(SERVICE_UUID)).build();
mScanner.startScan(Arrays.asList(scanFilter), settings, mScanCallback);

Thằng mScanCallback này sẽ được khởi tạo ở bước tiếp theo

Kết nối bằng địa chỉ MAC

private final ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.i(TAG, "onScanResult: " + result.getDevice().getAddress());
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            if (!results.isEmpty()) {
                ScanResult result = results.get(0);
                BluetoothDevice device = result.getDevice();
                String deviceAddress = device.getAddress();
                BluetoothDevice bleDevice = mBluetoothAdapter.getRemoteDevice(deviceAddress);
                mBluetoothGatt =bleDevice.connectGatt(MainActivity.this, false, mGattCallback);
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            stopLeScan();
            Log.i(TAG, "onScanFailed");

        }
    };

Ở đây ta tìm thấy được thiết bị ở bước trên thì lấy địa chỉ mac của nó, sau đó thực hiện kết nối tới GATT server trên device bằng hàm connectGatt. Sau khi gọi connectGatt thì kết quả dù được hay không nó đều tiếp tục thông báo cho mình biết thông qua một callback là mGattCallBack. Thằng mGattCallBack được khởi tạo ở bước tiếp theo.

Khám phá các dịch vụ và khám phá các đặc tính

Ở bước này ta cần khởi tạo callback để lấy kết quả từ việc kết nối tới device.

private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.i(TAG, "Changed-" + newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.i(TAG, "Mat ket noi");
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                BluetoothGattService service = gatt.getService(SERVICE_UUID);
                if (service != null) {
                    // do something
                }
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Log.d("onCharacteristicWrite", "Failed write, retrying");
            } else {
                Log.d("onCharacteristicWrite", "succ 11111");
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "Succesfully read characteristic: " + characteristic.getValue().toString());
            } else {
                Log.d(TAG, "Characteristic read not successful");
            }

        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            Log.i(TAG, "onCharacteristicChanged");
        }
    };

Trong đối tượng callback này ta kiểm tra nếu kết nối thành công thì sẽ khám phá service của nó thông qua hàm discoverServices(); còn không sẽ báo ra không kết nối được. Nếu kết khám phá ra service thì nó sẽ tự động gọi hàm onServicesDiscovered.

Ngoài ra trong đối tượng này ta còn lắng nghe các sự kiện khác như:

onCharacteristicWrite: Hàm này sẽ kích hoạt khi ghi xong một giá trị vào device (được chạy ngay sau khi ta gọi hàm writeCharacteristic)
onCharacteristicRead: Hàm này sẽ kích hoạt khi đọc xong một giá trị từ device (được chạy ngay sau khi ta gọi hàm readCharacteristic).
onCharacteristicChanged: Hàm này sẽ kích hoạt khi một dđặc tính bị thay đổi trên device (ví dụ như nhịp tim thay đổi trên device thì trên app của bạn cũng tự động lấy được giá trị đó).

Ghi giá trị lên đặc tính

Coi như mọi thứ đã thành công và đây là bước ghi một giá trị lên device của bạn.

Bởi vì theo quy định BLE chỉ được gửi tối đa 20 bytes một lần, vậy nên nếu bạn phải gửi gói nào nặng hơn 20 bytes thì cần phải chia ra làm nhiều gói mỗi gói nhỏ hơn hoặc bằng 20 bytes. Mỗi gói gửi cách nhau tầm 150 mili giây. Mình sẽ chia sẻ đoạn code đấy cho bạn luôn  lovedog

public void writeCustomCharacteristic() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        BluetoothGattService mCustomService = mBluetoothGatt.getService(SERVICE_UUID);
        if (mCustomService == null) {
            Log.w(TAG, "Custom BLE Service not found");
            return;
        }
        BluetoothGattCharacteristic mWriteCharacteristic = mCustomService.getCharacteristic(CHARACTERISTIC_UUID);
        if (mWriteCharacteristic != null) {
            isWritted = false;
            int i;
            for (i = 0; i < splitStringArr.size(); i++) {
                mWriteCharacteristic.setValue(splitStringArr.get(i));
                boolean codeCheck = mBluetoothGatt.writeCharacteristic(mWriteCharacteristic);
                if (!codeCheck) {
                    Log.w(TAG, "ghi fail lan" + i);
                } else {
                    Log.w(TAG, "ghi thanh cong lan" + i);
                }
                try {
                    Thread.sleep(150);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            splitStringArr.clear();
            isWritted = true;
        } else {
            Log.w(TAG, "characteristic bi null");
        }
    }

Đọc giá trị từ đặc tính

Do module này nó không cho phép đọc nên mình sẽ không làm thực tế, nhưng để dùng cho các dự án khác thì mình nghĩ đoạn code này bạn có thể tham khảo.

public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            LogUtils.logDebug(TAG + "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.readCharacteristic(characteristic);
    }

Dừng việc quét thiết bị

Sau khi tìm thấy thì dừng việc quét lại nhé không thì hao mòn pin điện thoại đấy.

private void stopLeScan() {
        if (mScanning) {
            mScanning = false;
            mScanner.stopScan(mScanCallback);
        }
    }

Trên đây là tổng quan việc kết nối và gửi dữ liệu thông qua bluetooth năng lượng thấp, mong rằng với tài liệu ít ỏi này sẽ giúp ích được cho công việc của bạn. Nếu bạn muốn biết sâu hơn thì có lẽ nên nghiên cứu thêm trên mạng hoặc mua sách. Bạn có bất kỳ đóng góp nào hãy bình luận để chúng ta cùng trao đổi.

Xin cám ơn.

Tặng cho bạn tất cả các bài viết mình đã đọc trong khi làm việc với ble

– Quyển sách rất hay nhưng mất phí http://www.althosbooks.com/intobleb.html
– Các giao thức và thuộc tính https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3478807/
– Phân biệt các profile high level https://www.philips.co.in/c-f/XC000008687
– Ví dụ hay về cả server và client: http://nilhcem.com/android-things/bluetooth-low-energy
Scan với điều kiện: https://blog.creatorslab.jp/2017/05/13/android-ble-service-uuid/
Tham khảo thêm ví dụ: http://mslgt.hatenablog.com/entry/2015/05/17/212257

Các bài viết không xem thì tiếc:

Chia sẻ là sexy

dotrinh

Một lập trình viên dũng cảm, hòa đồng, luôn sống tích cực và anh ấy quay tay khỏe và khéo trong môn bi lắc :) Xem thêm thông tin tại trang -> Giới thiệu

Thảo luận

This site uses Akismet to reduce spam. Learn how your comment data is processed.