Правильно ли я использую Android BLE API? - PullRequest
0 голосов
/ 12 июля 2020

Я пытаюсь прочитать характеристику c с устройства BLE, и просто для этого я получил этот огромный конечный автомат. И это даже не обработка тайм-аутов или нескольких запросов на чтение в очереди, не говоря уже об уведомлениях. Могу ли я покончить с этим и позволить Android справиться с этим?

package com.example.android.app;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.util.Log;

import java.util.UUID;

/**
 * Manages the state of the bluetooth connection
 *
 * The Android BLE API is completely asynchronous and with different state changes
 * reported by different callbacks. In order to do the necessary calls in the correct order you have
 * to manage the current connection state yourself.
 */
public class BleStateMachine extends BluetoothGattCallback {
    public enum ErrorType {
        GATT_FAILURE,
        ONCONNECTIONSTATECHANGE_WITHOUT_GATT_SUCCESS,
        UNEXPECTED_CONNECTION_EVENT,
        UNEXPECTED_DISCOVERY_EVENT
    }

    public static abstract class ReadCallback {
        void onReadCompleted(ErrorType error, int data) {};
        void onReadCompleted(ErrorType error, String data) {};
    }

    public static class InvalidStateException extends Exception {}

    private enum State {
        CONNECTING,
        DISCOVERY,
        READING,
        IDLE,
        FAILURE
    }

    private static class PendingRead {
        private UUID characeristicUuid;
        private ReadCallback callback;
    }

    // From the constructor
    private String remoteMac;
    private UUID serviceUuid;
    private BluetoothAdapter bluetoothAdapter;
    private Activity activity;

    // State
    private State currentState;
    private PendingRead readInProgress = null;

    // Internal onbjects
    private BluetoothGatt gatt;

    /**
     * @param activity The result callback will run on this activity's UI thread. Also needed for
     *                 connectGatt, the reason is undocumented.
     * @param bluetoothAdapter The BluetoothAdapter instance to use
     * @param remoteMac The MAC address of the device to connect to
     */
    public BleStateMachine(Activity activity, BluetoothAdapter bluetoothAdapter, String remoteMac, UUID serviceUuid) {
        this.activity = activity;
        this.bluetoothAdapter = bluetoothAdapter;
        this.remoteMac = remoteMac;
        this.serviceUuid = serviceUuid;

        currentState = State.CONNECTING;
        openConnection();
    }

    public void tryRead(UUID characteristicUuid, ReadCallback cb) {
        try {
            read(characteristicUuid, cb);
        } catch (InvalidStateException ignored) {}
    }

    /**
     * Read a characteristic
     *
     * @param cb A callback that is called when the reading is done, regardless of whether it was
     *           successful. (The Android BLE API is asynchronous, so this is as well.)
     */
    public void read(UUID characteristicUuid, ReadCallback cb) throws InvalidStateException {
        if (readInProgress != null) {
            throw new InvalidStateException();
        }
        PendingRead pr = new PendingRead();
        pr.characeristicUuid = characteristicUuid;
        pr.callback = cb;
        this.readInProgress = pr;

        if (currentState == State.FAILURE) {
            // Current strategy after a failure: Try to start over with a fresh connection. (We
            // currently don't differentiate between different points of failure nor do we pick up
            // at the step where it failed.)
            currentState = State.CONNECTING;
            reviveConnection();
        } else if (currentState == State.IDLE) {
            currentState = State.READING;
            readCharacteristic();
        } else {
            // From all other states we should eventually get to the point where the read is started
            nop();
        }
    }

    public void close() {
        if (readInProgress != null)
            readInProgress = null;
        if (gatt != null) {
            gatt.disconnect();
            gatt.close();
        }
    }

    private void nop() {}

    private void reviveConnection() {
        gatt.disconnect();
        gatt.close();
        openConnection();
    }

    private void openConnection() {
        BluetoothDevice dev = bluetoothAdapter.getRemoteDevice(remoteMac);
        // Here is the missing documentation for the autoConnect flag:
        // https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble/40187086#40187086
        gatt = dev.connectGatt(activity, false, this);
    }

    private void readCharacteristic() {
        BluetoothGattService service = gatt.getService(serviceUuid);
        BluetoothGattCharacteristic charac = service.getCharacteristic(readInProgress.characeristicUuid);
        gatt.readCharacteristic(charac);
    }

    private void report(final ErrorType error, final int intData, final String stringData) {
        final ReadCallback cb = readInProgress.callback; // Not sure if we have to save this before the Runnable runs on the UI thread?
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                cb.onReadCompleted(error, intData);
                cb.onReadCompleted(error, stringData);
            }
        });
        readInProgress = null;
    }

    private void reportSuccess(int intData, String stringData) {
        report(null, intData, stringData);
    }

    private void reportFailure(ErrorType error)
    {
        if (readInProgress != null) {
            report(error, 0, null);
        }
    }

    //region BluetoothGattCallback implementation
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        Log.d("GATT", "onConnectionStateChange " + status + " " + newState);

        if (status != BluetoothGatt.GATT_SUCCESS) {
            currentState = State.FAILURE;
            reportFailure(ErrorType.ONCONNECTIONSTATECHANGE_WITHOUT_GATT_SUCCESS);
            return;
        }

        if (newState == BluetoothProfile.STATE_CONNECTED) {
            if (currentState == State.CONNECTING) {
                currentState = State.DISCOVERY;
                gatt.discoverServices();
            } else {
                currentState = State.FAILURE;
                reportFailure(ErrorType.UNEXPECTED_CONNECTION_EVENT);
            }
        } else {
            // We were disconnected, reconnect
            currentState = State.CONNECTING;
            gatt.connect();
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt ignored, int status) {
        Log.d("GATT", "onServicesDiscovered " + status);

        if (currentState == State.DISCOVERY) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (readInProgress != null) {
                    currentState = State.READING;
                    readCharacteristic();
                } else {
                    currentState = State.IDLE;
                }
            } else {
                currentState = State.FAILURE;
                reportFailure(ErrorType.GATT_FAILURE);
            }
        } else {
            currentState = State.FAILURE;
            reportFailure(ErrorType.UNEXPECTED_DISCOVERY_EVENT);
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt ignored, BluetoothGattCharacteristic characteristic, int status) {
        Log.d("GATT", "onCharacteristicRead " + status);

        if (status == BluetoothGatt.GATT_SUCCESS) {
            currentState = State.IDLE;
                reportSuccess(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, 0), characteristic.getStringValue(0));
        } else {
            // Only this read failed, otherwise this connection should still be ready to handle
            // transactions, so we set it to IDLE instead of FAILURE.
            currentState = State.IDLE;
            reportFailure(ErrorType.GATT_FAILURE);
        }
    }
    //endregion
}

1 Ответ

0 голосов
/ 15 июля 2020

Что ж, для того, чтобы прочитать характеристику BLE c на устройстве, вам необходимо:

  1. Установить sh соединение с BluetoothGatt экземпляром устройства.

  2. Полное обнаружение службы с использованием discoverServices() на этом экземпляре

  3. Позвоните readCharacteristic() на этом экземпляре.

Это то, что делает ваш образец.

Если вы уже знаете UUID службы и характеристики c, которые хотите прочитать, вы можете прочитать chara c следующим образом:

BluetoothGattService service = gatt.getService(serviceUUID);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(characteristicUUID);

Вот другие примеры кода при использовании readCharacteristic()

https://www.programcreek.com/java-api-examples/?class=android.bluetooth.BluetoothGatt&method=readCharacteristic

...