Android BLE никогда не получает уведомления от характеристик - PullRequest
0 голосов
/ 22 апреля 2020

Я уже давно борюсь, пытаясь заставить мое устройство BLE связаться с моим android приложением.

Прежде всего, вот мой полный код для обработки BLE:

BleCentral. java

import java.util.HashMap;
import java.util.function.Supplier;

import android.util.Log;
import android.content.Context;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothManager;

public class BleCentral
{
    private HashMap<String, BluetoothGatt> m_connectedDevices;

    public BleCentral()
    {
        m_connectedDevices = new HashMap<>();
    }

    private BluetoothAdapter m_GetAdapter(Context ctx)
    {
        final BluetoothManager bleMgr = (BluetoothManager)(ctx.getSystemService(Context.BLUETOOTH_SERVICE));
        BluetoothAdapter adapter = bleMgr.getAdapter();

        if (adapter == null || !adapter.isEnabled())
        {
            Log.e("BLE Central", "BLE either not available or not enabled. Please do something about it.");
            return null;
        }

        return adapter;
    }

    public BluetoothDevice GetDevice(Context ctx, String address)
    {
        return m_GetAdapter(ctx).getRemoteDevice(address);
    }

    public <T extends BlePeripheral>
    T Connect(Context ctx, String address, Supplier<T> supplier)
    {
        BluetoothDevice device = GetDevice(ctx, address);

        T result = supplier.get();
        m_connectedDevices.put(address, device.connectGatt(ctx, false, result, BluetoothDevice.TRANSPORT_LE));

        return result;
    }
}

BlePeripheral. java

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;

import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Build;
import android.os.Handler;
import android.util.Log;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattService;


public abstract class BlePeripheral
    extends BluetoothGattCallback
{
    private final String kCCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";

    private Handler       m_handler;
    private boolean       m_deviceReady;
    private BluetoothGatt m_bluetoothGatt;

    private Queue<CmdQueueItem> m_cmdQueue;
    private boolean             m_cmdQueueProcessing;

    // ------------------------------------------------------------------------
    // -- Own methods

    protected BlePeripheral()
    {
        m_handler       = new Handler();
        m_deviceReady   = false;
        m_bluetoothGatt = null;

        m_cmdQueue = new LinkedList<>();
        m_cmdQueueProcessing = false;
    }

    public boolean IsDeviceReady()
    { return m_deviceReady; }

    public void EnableNotifications(UUID service, UUID characteristic)
    {  EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), true); }

    public void DisableNotifications(UUID service, UUID characteristic)
    {  EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), false); }

    protected void WriteCharacteristic(UUID service, UUID characteristic, byte[] value, boolean requestResponse)
    { EnqueueWriteCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), value, requestResponse); }

    protected void ReadCharacteristic(UUID service, UUID characteristic)
    { EnqueueReadCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic)); }

    // ------------------------------------------------------------------------
    // -- BluetoothGattCallback overrides

    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState)
    {
        super.onConnectionStateChange(gatt, status, newState);

        final BluetoothDevice device = gatt.getDevice();

        switch (status)
        {
        case 133: /* GATT_ERROR */
            Log.e("BLE", "GATT_ERROR");
            gatt.close();

            try   { Thread.sleep(150); }
            catch (InterruptedException e) { e.printStackTrace(); }
            break;

        case 0: /* GATT_SUCCESS */
            switch (newState)
            {
            case BluetoothGatt.STATE_CONNECTED:
                Log.i("BLE", "Connected to " + device.getAddress() + " (" + device.getName() + ")");
                m_bluetoothGatt     = gatt;

                int delayWhenBonded = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) ? 2000 : 0;

                switch (device.getBondState())
                {
                case BluetoothDevice.BOND_NONE:
                    delayWhenBonded = 0;

                case BluetoothDevice.BOND_BONDED:
                    m_handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            boolean result = gatt.discoverServices();
                            if (!result)
                                Log.e("BLE", "discoverServices() failed to start");
                        }
                    }, delayWhenBonded);
                    break;

                case BluetoothDevice.BOND_BONDING:
                    Log.i("BLE", "Waiting for bonding to complete");
                    break;
                }
                break;

            case BluetoothGatt.STATE_DISCONNECTED:
                gatt.close();
                break;
            }

            break;
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status)
    {
        super.onServicesDiscovered(gatt, status);

        if (status == 129 /* GATT_INTERNAL_ERROR */)
        {
            Log.e("BLE", "Service discovery failed");
            gatt.disconnect();
            return;
        }

        final List<BluetoothGattService> services = gatt.getServices();
        Log.i("BLE", "Discovered " + services.size() + " services for " + gatt.getDevice().getAddress());

        m_deviceReady   = SetupDevice(gatt);

        if (!m_deviceReady)
            Log.e("BLE", "Peripheral does not comply to this device's requirements");
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic)
    {
        super.onCharacteristicChanged(gatt, characteristic);

        Log.i("BLE", "onCharacteristicChanged: " + characteristic.getUuid());
        final byte[] value = new byte[characteristic.getValue().length];
        System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);

        OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
    {
        super.onCharacteristicRead(gatt, characteristic, status);

        ProcessCmdQueue();

        Log.i("BLE", "onCharacteristicRead: " + characteristic.getUuid());
        final byte[] value = new byte[characteristic.getValue().length];
        System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);

        OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
    {
        super.onCharacteristicWrite(gatt, characteristic, status);

        ProcessCmdQueue();

        Log.i("BLE", "onCharacteristicWrite: " + characteristic.getUuid());
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status)
    {
        super.onDescriptorWrite(gatt, descriptor, status);

        ProcessCmdQueue();

        final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();

        if(status != 0 /* GATT_SUCCESS */)
        {
            Log.e("BLE", "WriteDescriptor failed for characteristic " + parentCharacteristic.getUuid());
            return;
        }

        if(descriptor.getUuid().equals(UUID.fromString(kCCC_DESCRIPTOR_UUID)))
        {
            if(status == 0 /* GATT_SUCCESS */)
            {
                byte[] value = descriptor.getValue();
                if (value != null)
                {
                    if (value[0] != 0)
                        Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now notifying");
                    else
                        Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now NOT notifying");
                }
            }
        }
    }

    // ------------------------------------------------------------------------
    // -- Command Queue implementation

    /* An enqueueable write operation - notification subscription or characteristic write */
    private class CmdQueueItem
    {
        BluetoothGattCharacteristic characteristic;
        byte[] dataToWrite; // Only used for characteristic write
        boolean writeWoRsp; // Only used for characteristic write
        boolean enabled;    // Only used for characteristic notification subscription
        public m_queueItemType type;
    }

    private enum m_queueItemType
    {
        SubscribeCharacteristic,
        ReadCharacteristic,
        WriteCharacteristic
    }

    /* queues enables/disables notification for characteristic */
    public void EnqueueSetNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
    {
        // Add to queue because shitty Android GATT stuff is only synchronous
        CmdQueueItem m_queueItem    = new CmdQueueItem();
        m_queueItem.characteristic = ch;
        m_queueItem.enabled        = enabled;
        m_queueItem.type           = m_queueItemType.SubscribeCharacteristic;
        EnqueueBleCommand(m_queueItem);
    }

    /* queues enables/disables notification for characteristic */
    public void EnqueueWriteCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean requestResponse)
    {
        // Add to queue because shitty Android GATT stuff is only synchronous
        CmdQueueItem m_queueItem    = new CmdQueueItem();
        m_queueItem.characteristic = ch;
        m_queueItem.dataToWrite    = dataToWrite;
        m_queueItem.writeWoRsp     = !requestResponse;
        m_queueItem.type           = m_queueItemType.WriteCharacteristic;
        EnqueueBleCommand(m_queueItem);
    }

    /* request to fetch newest value stored on the remote device for particular characteristic */
    public void EnqueueReadCharacteristic(BluetoothGattCharacteristic ch)
    {
        // Add to queue because shitty Android GATT stuff is only synchronous
        CmdQueueItem m_queueItem = new CmdQueueItem();
        m_queueItem.characteristic = ch;
        m_queueItem.type = m_queueItemType.ReadCharacteristic;
        EnqueueBleCommand(m_queueItem);
    }

    /**
     * Add a transaction item to transaction queue
     * @param m_queueItem
     */
    private void EnqueueBleCommand(CmdQueueItem m_queueItem)
    {
        m_cmdQueue.add(m_queueItem);

        // If there is no other transmission processing, go do this one!
        if (!m_cmdQueueProcessing)
            ProcessCmdQueue();
    }

    /**
     * Call when a transaction has been completed.
     * Will process next transaction if queued
     */
    private void ProcessCmdQueue()
    {
        if (m_cmdQueue.size() <= 0)
        {
            m_cmdQueueProcessing = false;
            return;
        }

        m_cmdQueueProcessing = true;
        CmdQueueItem m_queueItem = m_cmdQueue.remove();

        switch (m_queueItem.type)
        {
            case WriteCharacteristic:
                writeDataToCharacteristic(m_queueItem.characteristic, m_queueItem.dataToWrite, m_queueItem.writeWoRsp);
                break;

            case SubscribeCharacteristic:
                setNotificationForCharacteristic(m_queueItem.characteristic, m_queueItem.enabled);
                break;

            case ReadCharacteristic:
                requestCharacteristicValue(m_queueItem.characteristic);
                break;
        }
    }

    public void requestCharacteristicValue(BluetoothGattCharacteristic ch)
    {
        if (m_bluetoothGatt == null)
            return;

        m_bluetoothGatt.readCharacteristic(ch);
    }

    private void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean writeWoRsp)
    {
        if (m_bluetoothGatt == null || ch == null)
            return;

        ch.setValue(dataToWrite);
        ch.setWriteType(writeWoRsp ? BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE : BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        m_bluetoothGatt.writeCharacteristic(ch);
    }

    /* enables/disables notification for characteristic */
    private void setNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
    {
        if (m_bluetoothGatt == null || ch == null)
            return;

        ch.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        boolean success = m_bluetoothGatt.setCharacteristicNotification(ch, enabled);
        if(success)
        {
            // This is also sometimes required (e.g. for heart rate monitors) to enable notifications/indications
            // see: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
            BluetoothGattDescriptor descriptor = ch.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
            if(descriptor != null)
            {
                if (enabled)
                    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                else
                    descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);

                if (m_bluetoothGatt.writeDescriptor(descriptor))
                {
                    Log.i("BLE Peripheral", "SetNotification (Set + CCC) succeeded!");
                }
            }
            else
                Log.i("BLE Peripheral", "SetNotification (Set only) succeeded!");
        }
        else
            Log.e("BLE Peripheral", "SetNotification failed!");
    }

    // ------------------------------------------------------------------------
    // -- Abstract methods

    protected abstract boolean SetupDevice(BluetoothGatt gatt);
    protected abstract void    OnUpdate(UUID service, UUID characteristic, final byte[] value);
}

Код для создания и подключиться к устройству

BleCentral central = new BleCentral();
m_customDevice     = central.Connect(this, deviceMacAddress, () -> new CustomDevice());

CustomDevice просто наследует класс BlePeripheral, реализуя SetupDevice (который проверяет наличие всех служб и характеристик) и OnUpdate (который получает новые данные и обрабатывает их).

Теперь меня беспокоят две вещи:

  • При подключении к устройству иногда работает сразу, а иногда нет. Если нет, мне нужно go подключиться к устройству через другое приложение, такое как Bluefruit Connect, затем снова запустить мое приложение, и оно подключится;

  • Когда оно подключится, оно проходит поиск службы и все, и в функции setNotificationForCharacteristi c все вызывается правильно (вызывается onDescriptorWrite и все), но я никогда не получаю никаких уведомлений.

Поскольку я тот, что стоит за кодом, работающим на моем BLE-периферии, я могу гарантировать, что характеристика c, от которой я пытаюсь получить данные, имеет тип NOTIFY (а не, например, INDICATE).

Если это может помочь в любом случае, единственная характеристика NOTIFY c - это посылает 56-байтовый массив, заполненный 14 числами с плавающей запятой, так часто, как это возможно. Ранние прототипы с Web Bluetooth или NativeScript (с плагином nativescript-bluetooth) показали мне, что это действительно работает, и в этих случаях я получаю результаты примерно каждые 90 миллисекунд.

Я думаю, что переписал этот код около 3 времена уже и я немного отчаялся, поэтому любая помощь в правильном направлении приветствуется. : D

Большое спасибо!

Редактировать : Просто для науки я попытался переключить характеристики c на ЧИТАТЬ на устройство, затем порождает поток, читающий его каждую секунду, вместо ожидания уведомлений. Хорошо, вызывается onCharacteristicRead, но переданный ему байтовый массив всегда имеет длину ноль ...

1 Ответ

0 голосов
/ 26 апреля 2020

Я обнаружил проблему - она ​​вообще не была связана с кодом Java, но была вызвана аппаратной ошибкой в ​​моем устройстве BLE, которая привела к тому, что все обновления значений были отменены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...