Android: Conexión simultánea con múltiples dispositivos BLE + sensor Acelerómetro

Standard

 

Imagen tomada de: http://pocketnow.com/2013/01/17/bluetooth-low-energy

Imagen tomada de: http://pocketnow.com/2013/01/17/bluetooth-low-energy

¡Qué tal, gente! Después de un algún tiempo sin publicar, traigo para ustedes este ejemplo práctico y resumido (aunque usted no lo crea) de una implementación a la medida que fue realizada recientemente para un proyecto en el que me encuentro colaborando.

La situación—brevemente explicada— es esta: es necesario, mediante una app Android para móvil, conectarse como mínimo con 3 dispositivos BLE 4.0 vestibles de manera simultánea y leer los datos de sus diversos sensores en tiempo real. Los dispositivos en cuestión no cuentan con el sistema operativo Android instalado.

Para fines prácticos, en el presente ejemplo sólo se leen y muestran los datos del sensor acelerómetro de los dispositivos BLE 4.0 utilizados. Cabe destacar, además, que se consiguió conectar con hasta 4 dispositivos simultáneamente, esto ya que no se contaba con una cantidad mayor de los mismos. Por lo tanto, me aventuro a conjeturar que es probablemente viable el conectar con más de 4 dispositivos sin problema mediante esta aplicación.

Y bien, sin mayor preámbulo, pasemos a la descripción de las clases y layouts necesarios para construir nuestra aplicación de prueba. Pueden encontrar en GitHub el CÓDIGO COMPLETO DEL PROYECTO.

Construcción de la app

1. Creación del proyecto

Para comenzar, es necesario crear un nuevo proyecto llamado “MultiBLEConnectionApp” en Android Studio. En este caso particular usé como SDK mínimo la API 19, versión 4.4.4 o Kit-Kat.

2. Creación del layout (vista) y sus resources

Únicamente usaremos una vista, en la cual se mostrarán los dispositivos conectados (en caso de haberlos) y las opciones para buscar dispositivos o desconectarse de ellos una vez terminada la visualización. Con el código que se muestra más abajo se obtendrá algo como esto:

13767173_10208921957701684_4731155177859774533_o

activity_main.xml al tener dispositivos conectados

13723955_10208921958461703_3383974664583030604_o

activity_main.xml al no tener dispositivos conectados

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp"
    tools:context=".view.MainActivity">

    <Button
        android:id="@+id/main_button_scan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginRight="5dp"
        android:layout_toStartOf="@+id/main_button_disconnect"
        android:text="@string/action_scan"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button"
        android:textSize="16sp"
        android:textStyle="bold" />

    <Button
        android:id="@+id/main_button_disconnect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:enabled="false"
        android:text="@string/action_disconnect"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button"
        android:textSize="16sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/main_text_status"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignEnd="@+id/main_button_disconnect"
        android:layout_below="@+id/main_button_scan"
        android:layout_marginBottom="14dp"
        android:layout_marginTop="16dp"
        android:gravity="center_horizontal"
        android:text="@string/no_connected_devices"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@color/red" />

    <ListView
        android:id="@+id/main_list_devices"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_below="@+id/main_text_status" />

</RelativeLayout>

Es importante agregar el correspondiente archivo de strings.xml

<resources>
    <string name="app_name">MultiBLE Connection</string>

    <!-- Informacion/etiquetas -->    
    <string name="no_connected_devices">No connected devices</string>
    <string name="connected_device">Connected device</string>
    <string name="connected_devices">Connected devices</string>
    <string name="new_ble_device">New LE Device found:  </string>
    <string name="dialog_title_inr_scan">MultiBLE Scan</string>
    <string name="dialog_title_select_devices">Select devices</string>

    <!-- Acciones -->
    <string name="action_scan">SCAN</string>
    <string name="action_connect">Connect</string>
    <string name="action_disconnect">DISCONNECT</string>
    <string name="action_scanning_devices">Scanning for devices…</string>

    <!-- Errores -->
    <string name="error_no_ble_support">No LE support.</string>
    <string name="error_not_valid_device">Not Valid Device: </string>
    <string name="error_null_device">NULL Device</string>
</resources>

Y el archivo de colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="background">#ededed</color>
    <color name="red">#ee0000</color>
    <color name="green">#00aa00</color>

    <!-- Android defaults -->
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>

3. Creación de los delegates (interfaces) necesarios

Aquí comienza la mejor parte, no se me vayan a perder. Una vez que tenemos el layout que mostrará lo que está pasando en background, es necesario comenzar con la lógica que hace posible la magia.

Debido a que el código lleva comentarios en las líneas oportunas, no daré mucha explicación aquí, pero sí explico como preámbulo que fueron creados dos paquetes dentro del paquete principal (o main) del proyecto: uno de ellos lleva por nombre view y contiene únicamente a la clase de nuestra única Activity, y el otro es nombrado ble, el cual contiene a las clases que representan a los servicios necesarios para la conexión y recepción de datos. Dicho esto, les explico:

Como principio necesitamos definir los identificadores necesarios para reconocer los diferentes tipos de mensaje que los dispositivos nos envían. Para esto tenemos una interface que define las constantes a utilizar:

ble/IMultiBLEMessageType.java

package com.silvia_valdez.multibleconnectionapp.ble;

import java.util.UUID;

/**
 * Interface para Tipos de Mensaje.
 */
public interface IMultiBLEMessageType {
    // ID's del servicio acelerometro
    int ACCELEROMETER_SERVICE = 1;
    int ACCELEROMETER_MESSAGE = 10002;

    UUID ACCEL_SERVICE = UUID.fromString("f000aa10-0451-4000-b000-000000000000");
    UUID ACCEL_DATA_CHAR = UUID.fromString("f000aa11-0451-4000-b000-000000000000");
    UUID ACCEL_CONFIG_CHAR = UUID.fromString("f000aa12-0451-4000-b000-000000000000");

    UUID CONFIG_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    // Otros mensajes
    int PROGRESS = 201;
    int DISMISS = 202;
    int CLEAR = 301;
    int STOP_CAPTURE = 1;
    int RESUME_CAPTURE = 2;
}

Una vez que contamos con esto, continuamos definiendo a los delegates que informarán a nuestros servicios o vista de los sucesos que ocurren de manera asíncrona en background, como la conexión exitosa de los dispositivos BLE seleccionados o la recepción y lectura de valores del sensor acelerómetro de cada uno de ellos.

ble/IMultiBLEAccelDataReceiverDelegate.java

package com.silvia_valdez.multibleconnectionapp.ble;

import android.bluetooth.BluetoothGatt;

/**
 * Delegate para recepción de datos del acelerómetro.
 */
public interface IMultiBLEAccelDataReceiverDelegate {
    /**
     * Método para procesar cada dato recibido de los sensores
     * acelerómetro/giroscopio.
     *
     * @param gatt   el dispositivo que envía el mensaje.
     * @param accelX valor recibido para el eje x del acelerómetro.
     * @param accelY valor recibido para el eje y del acelerómetro.
     * @param accelZ valor recibido para el eje z del acelerómetro.
     * @param gyroX  valor recibido para el eje x del giroscopio.
     * @param gyroY  valor recibido para el eje y del giroscopio.
     * @param gyroZ  valor recibido para el eje z del giroscopio.
     */
    void updateAccelerometer(BluetoothGatt gatt, int accelX, int accelY, 
                             int accelZ, int gyroX, int gyroY, int gyroZ);
}

ble/IMultiBLEAccelServiceDelegate.java

package com.silvia_valdez.multibleconnectionapp.ble;

import android.bluetooth.BluetoothGatt;

import java.util.ArrayList;

/**
 * Delegate para el servicio de Acelerómetro/girocsopio.
 */
public interface IMultiBLEAccelServiceDelegate {
    /**
     * Método para actualizar la vista con los dispositivos conectados.
     *
     * @param gatts ArrayList que contiene a los dispositivos conectados.
     */
    void updateConnectedDevices(ArrayList<BluetoothGatt> gatts);

    /**
     * Método para procesar cada dato recibido de los sensores
     * acelerómetro/giroscopio.
     *
     * @param gatt   el dispositivo que envía el mensaje.
     * @param accelX valor recibido para el eje x del acelerómetro.
     * @param accelY valor recibido para el eje y del acelerómetro.
     * @param accelZ valor recibido para el eje z del acelerómetro.
     * @param gyroX  valor recibido para el eje x del giroscopio.
     * @param gyroY  valor recibido para el eje y del giroscopio.
     * @param gyroZ  valor recibido para el eje z del giroscopio.
     */
    void updateAccelerometerValues(BluetoothGatt gatt, int accelX, int accelY, int accelZ, int gyroX, int gyroY, int gyroZ); }

4. Creación de servicios

Y bien, teniendo lo anterior, procedemos a crear los servicios que se encargan realmente de todo el trabajo. La manera en que se definió la presente aplicación consta de tres elementos básicos que, trabajando en conjunto, se encargan de realizar la conexión con los dispositivos BLE y recibir e interpretar los mensajes que estos envían como respuesta. El primer componente al que me referiré es la clase MultiBLEService, que, como su nombre lo dice, brinda a nuestro Activity los servicios que este requiere. Pueden verla a detalle a continuación:
ble/MultiBLEService.java

package com.silvia_valdez.multibleconnectionapp.ble;

import android.app.Activity;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.os.Message;
import android.util.Log;
import android.util.SparseArray;

import com.silvia_valdez.multibleconnectionapp.R;

import java.util.ArrayList;

/**
 * Implementación del servicio BLE, 
 * con BLE Scan y funcionalidad de Data Receiver
 */
public class MultiBLEService implements BluetoothAdapter.LeScanCallback,
        IMultiBLEAccelDataReceiverDelegate {

    private static final String TAG = MultiBLEService.class.getSimpleName();

    private Context mContext;
    private Activity mActivity;
    private IMultiBLEAccelServiceDelegate mDelegate;

    // Componentes BLE
    private BluetoothAdapter mBluetoothAdapter;
    private MultiBLECallback mMultiBleCallback;
    private MultiBLEHandler mMultiBleHandler;

    private ArrayList<BluetoothGatt> mConnectedGatts;
    private ArrayList<BluetoothDevice> mSelectedDevices;
    private SparseArray<BluetoothDevice> mBluetoothDevices;


    // MultiBLEService constructor.
    public MultiBLEService(Context context) {
        this.mContext = context;
        this.mActivity = (Activity) context;
        this.mDelegate = (IMultiBLEAccelServiceDelegate) context;
        this.mConnectedGatts = new ArrayList<>();
    }

    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        if (device != null) {
            if (device.getName() != null) {
                Log.e(TAG, mContext.getString(R.string.new_ble_device)
                        + device.getName() + " @ " + rssi);
                getBluetoothDevices().put(device.hashCode(), device);
            } else {
                Log.e(TAG, mContext.getString(R.string.error_not_valid_device)
                + device.getName());
            }
        } else {
            Log.e(TAG, mContext.getString(R.string.error_null_device));
        }
    }

    // Método para conectar con una lista de dispositivos.
    public void connectToDevices(ArrayList<BluetoothDevice> devices) {
        mSelectedDevices = devices;
        for (BluetoothDevice device : devices) connectToDevice(device);
        mDelegate.updateConnectedDevices(mConnectedGatts);
    }

    // Método para conectar con un dispositivo en específico.
    public void connectToDevice(BluetoothDevice device) {
        if (null != device) {
            Log.e(TAG, String.format("Connecting to %s %s...",
                    device.getName(), device.getAddress()));

            mConnectedGatts.add(device.connectGatt(mContext, 
                                false, mMultiBleCallback));
            mMultiBleHandler.sendMessage(Message.obtain(null, 
                                         IMultiBLEMessageType.PROGRESS,
                    String.format("Connecting to %s %s...",
                            device.getName(), device.getAddress())));
        }
    }

    // Desconectar de todos los dispositivos.
    public void disconnectFromDevices() {
        if (!mConnectedGatts.isEmpty()) {
            for (BluetoothGatt gatt : mConnectedGatts) gatt.disconnect();
            mConnectedGatts.clear();
        }
    }

    // Inicializar la conexión bluetooth.
    public void setupBluetoothConnection() {
        ProgressDialog messageNotifier = new ProgressDialog(mContext);
        mBluetoothDevices = new SparseArray<>();
        mMultiBleHandler = new MultiBLEHandler(messageNotifier, this);
        mMultiBleCallback = new MultiBLECallback(mMultiBleHandler);
        mBluetoothAdapter = ((BluetoothManager)
                mContext.getSystemService(Context.BLUETOOTH_SERVICE))
                    .getAdapter();
    }

    // Comenzar el escaneo llamando a un callback al final.
    public void startScan(Runnable callback) {
        Log.e(TAG, mContext.getString(R.string.action_scanning_devices));
        getBluetoothDevices().clear();
        getBluetoothAdapter().startLeScan(this);
        mActivity.setProgressBarIndeterminateVisibility(true);
        mMultiBleHandler.postDelayed(callback, 3000L);
    }

    // Detener escaneo.
    public void stopScan() {
        getBluetoothAdapter().stopLeScan(this);
        mActivity.setProgressBarIndeterminateVisibility(false);
    }

    public BluetoothAdapter getBluetoothAdapter() {
        return mBluetoothAdapter;
    }

    public SparseArray<BluetoothDevice> getBluetoothDevices() {
        return mBluetoothDevices;
    }

    public ArrayList<BluetoothDevice> getSelectedDevices() {
        return mSelectedDevices;
    }
    
    // Actualizar los valores recibidos del acelerómetro.
    @Override
    public void updateAccelerometer(BluetoothGatt gatt, 
                                    int accelX, int accelY, int accelZ,
                                    int gyroX, int gyroY, int gyroZ) {
        if (mDelegate != null) {
            mDelegate.updateAccelerometerValues(gatt, 
                                                accelX, accelY, accelZ, 
                                                gyroX, gyroY, gyroZ);
        }
    }
}

En esta clase se realiza la mayor parte de la  comunicación con los dispositivos y habilitación de sensores, se vale de callbacks para notificar en la vista sobre los sucesos que ocurren en background.

ble/MultiBLECallback.java

package com.silvia_valdez.multibleconnectionapp.ble;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothProfile;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.SparseArray;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Implementación personalizada de Bluetooth GATT Callbacks
 * para conexión con múltiples dispositivos.
 */
public class MultiBLECallback extends BluetoothGattCallback {

    private static final String TAG = MultiBLECallback.class.getSimpleName();

    // Seguimiento de la máquina de estados
    private Handler mHandler;
    private int mBleServiceId;
    private List<Integer> mBleSensors;
    private SparseArray<Integer> mCurrentSensors;


    // MultiBLECallback constructor.
    public MultiBLECallback(Handler handler) {
        mHandler = handler;
        mCurrentSensors = new SparseArray<>();

        /*
         * Arreglo que contiene los sensores a leer por dispositivo.
         * En nuestro caso, solo acelerómetro.
         */
        mBleSensors = new ArrayList<>();
        mBleSensors.add(IMultiBLEMessageType.ACCELEROMETER_SERVICE);
        // El ID del primer sensor en el arreglo.
        mBleServiceId = mBleSensors.get(0);
    }

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, 
                                        int status, 
                                        int newState) {
        Log.e(TAG, String.format("Connection State Change: %d -> %s",
                status, connectionState(newState)));
        if (status == BluetoothGatt.GATT_SUCCESS 
            && newState == BluetoothProfile.STATE_CONNECTED) {
                /*
                 * Una vez que se conectó exitosamente, debemos descubrir
                 * todos los servicios en el dispositivo antes de poder leer
                 * y escribir sus características.
                 */
            gatt.discoverServices();
            mHandler.sendMessage(Message.obtain(null,
                    IMultiBLEMessageType.PROGRESS,
                    "Discovering Services..."));
        } else if (status == BluetoothGatt.GATT_SUCCESS
                && newState == BluetoothProfile.STATE_DISCONNECTED) {
                /*
                 * Si en cualquier punto nos desconectamos, se envía un
                 * mensaje para limpiar los valores de la interfaz de usuario.
                 */
            mHandler.sendEmptyMessage(IMultiBLEMessageType.CLEAR);
        } else if (status != BluetoothGatt.GATT_SUCCESS) {
                /*
                 * Si hay un fallo en cualquier etapa, simplemente desconectamos.
                 */
            gatt.disconnect();
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        Log.d(TAG, "Services Discovered: " + status);
        mHandler.sendMessage(Message.obtain(null, 
                             IMultiBLEMessageType.PROGRESS, 
                             "Enabling Sensors..."));
            /*
             * Una vez descubiertos los servicios, vamos a resetear 
             * nuestra máquina de estados y comenzar trabajando en los
             * sensores que necesitamos habilitar.
             */
        bleServiceReset();
        enableNextSensor(gatt);
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic 
                                         characteristic,
                                     int status) {
        // Nada aquí.
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt,
                                      BluetoothGattCharacteristic 
                                          characteristic,
                                      int status) {
        // Después de habilitar, leemos el valor inicial.
        setNotifyNextSensor(gatt);
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic 
                                            characteristic) {
        /*
         * Después de que las notificaciones están habilitadas, todas
         * las actualizaciones del dispositivo en cambios de características
         * serán publicadas aquí. Las enviamos al thread de la interfaz
         * de usuario para actualizarla.
         */
        BluetoothGattDto bluetoothGattDto = new BluetoothGattDto(gatt, 
            characteristic);

        if (isAccelerometerChar(characteristic.getUuid())) {
            mHandler.sendMessage(Message.obtain(null,
                    IMultiBLEMessageType.ACCELEROMETER_MESSAGE, 
                    bluetoothGattDto));
        }
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt,
                                  BluetoothGattDescriptor descriptor,
                                  int status) {
        /*
         * Una vez que las notificaciones están habilitadas, nos movemos
         * al siguiente sensor para habilitarlo.
         */
        bleNextService(gatt);
        enableNextSensor(gatt);
    }

    @Override
    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
        Log.d(TAG, "Remote RSSI: " + rssi);
    }

    /*
     * Enviar un comando para habilitar cada sensor escribiendo una 
     * característica de configuración. Esto es específico para el SensorTag,
     * para mantener la energía baja deshabilitando los sensores no usados.
     */
    private void enableNextSensor(BluetoothGatt gatt) {
        BluetoothGattCharacteristic characteristic;
        switch (mBleServiceId) {
            case IMultiBLEMessageType.ACCELEROMETER_SERVICE:
                Log.e(TAG, "Enabling accelerometer service...");
                characteristic = gatt.getService(
                        IMultiBLEMessageType.ACCEL_SERVICE)
                        .getCharacteristic(
                        IMultiBLEMessageType.ACCEL_CONFIG_CHAR);
                characteristic.setValue(new byte[]{0x01});
                break;

            default:
                mHandler.sendEmptyMessage(IMultiBLEMessageType.DISMISS);
                Log.e(TAG, String.format("All Sensors Enabled for %s!", 
                    gatt.getDevice()));
                return;
        }
        gatt.writeCharacteristic(characteristic);
    }

    /*
     * Habilitar notificaciones de cambios en las características para cada
     * sensor escribiendo la bandera ENABLE_NOTIFICATION_VALUE al descriptor
     * de dicha característica.
     */
    private void setNotifyNextSensor(BluetoothGatt gatt) {
        BluetoothGattCharacteristic characteristic;
        switch (mBleServiceId) {
            case IMultiBLEMessageType.ACCELEROMETER_SERVICE:
                Log.e(TAG, "Set notify accelerometer sensor.");
                characteristic = gatt.getService(
                        IMultiBLEMessageType.ACCEL_SERVICE)
                        .getCharacteristic(
                            IMultiBLEMessageType.ACCEL_DATA_CHAR);
                break;

            default:
                mHandler.sendEmptyMessage(IMultiBLEMessageType.DISMISS);
                Log.e(TAG, String.format("All Sensors Notified for %s!", 
                    gatt.getDevice()));
                return;
        }

        // Habilitar notificaciones locales
        gatt.setCharacteristicNotification(characteristic, true);

        // Habilitar notificaciones remotas
        BluetoothGattDescriptor descriptor =
                characteristic.getDescriptor(
                IMultiBLEMessageType.CONFIG_DESCRIPTOR);
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        gatt.writeDescriptor(descriptor);
    }

    private String connectionState(int status) {
        switch (status) {
            case BluetoothProfile.STATE_CONNECTED:
                return "Connected";
            case BluetoothProfile.STATE_DISCONNECTED:
                return "Disconnected";
            case BluetoothProfile.STATE_CONNECTING:
                return "Connecting...";
            case BluetoothProfile.STATE_DISCONNECTING:
                return "Disconnecting...";
            default:
                return String.valueOf(status);
        }
    }

    private void bleServiceReset() {
        // Limpiar la lista de sensores leídos y comenzar con el primero
        mCurrentSensors.clear();
        mBleServiceId = mBleSensors.get(0);
    }

    /*
     * Para cada dispositivo conectado, guardar la lista de sus sensores
     */
    private void bleNextService(BluetoothGatt gatt) {
        /* 
         * Contador que indica la posición del sensor en nuestra lista
         * de sensores por leer. 
         */
        Integer currentSensor;
        // El hash code del dispositivo actual.
        int gattCode = gatt.hashCode();

        if ((currentSensor = mCurrentSensors.get(gattCode)) != null) {
            /*
             * Si el dispositivo está ya en la lista, incrementa el contador
             * de sus sensores leídos.
             */
            mCurrentSensors.put(gattCode, ++currentSensor);
        } else {
            // Si el dispositivo no está en la lista, inicializa sus valores.
            currentSensor = 0;
            mCurrentSensors.put(gattCode, currentSensor);
        }

        /*
         * Cuando todos los sensores de un dispositivo fueron leídos, 
         * asigna un valor inalcanzable a la variable mBleServiceId.
         */
        if (currentSensor < mBleSensors.size()) {
            mBleServiceId = mBleSensors.get(mCurrentSensors.get(gattCode));
        } else {
            mBleServiceId = 100;
        }
    }

    private boolean isAccelerometerChar(UUID UuidChar) {
        return UuidChar.toString().equals(
            IMultiBLEMessageType.ACCEL_DATA_CHAR.toString());
    }
}

La clase MultiBLEHandler es el intermediario que se encarga del intercambio de mensajes entre las anteriormente descritas MultiBLEService y MultiBLECallback, podemos verla a continuación:

ble/MultiBLEHandler.java

package com.silvia_valdez.multibleconnectionapp.ble;

import android.app.ProgressDialog;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * Implementación personalizada de Handler para procesar Mensajes y Runnables
 * de los BLECallbacks para múltiples dispositivos.
 */
public class MultiBLEHandler extends Handler {

    private static final String TAG = MultiBLEHandler.class.getSimpleName();

    private IMultiBLEAccelDataReceiverDelegate mDelegate;
    private ProgressDialog mProgressDialog;
    private boolean mStopCapture;

    // MultiBLEHandler constructor.
    public MultiBLEHandler(ProgressDialog dialog, 
                           IMultiBLEAccelDataReceiverDelegate delegate) {
        /*
         * Delegate de la clase IMultiBLEAccelDataReceiver 
         * para actualizar la vista.
         */
        this.mDelegate = delegate;

        this.mProgressDialog = dialog;
        this.mStopCapture = false;
    }

    @Override
    public void handleMessage(Message msg) {
        BluetoothGattDto bluetoothGattDto;
        BluetoothGatt gatt = null;
        BluetoothGattCharacteristic characteristic = null;
        int subject = msg.what;

        if (subject == IMultiBLEMessageType.STOP_CAPTURE && !mStopCapture) {
            mStopCapture = true;
        } else if (subject == IMultiBLEMessageType.RESUME_CAPTURE) {
            mStopCapture = false;
        }

        if (!mStopCapture) {
            if (msg.obj instanceof BluetoothGattDto) {
                /* DTO personalizado para guardar los datos del dispositivo
                 * y la característica leída.
                 */
                bluetoothGattDto = (BluetoothGattDto) msg.obj;
                gatt = bluetoothGattDto.getBluetoothGatt();
                characteristic = bluetoothGattDto.getBluetoothCharacteristic();
            }

            switch (msg.what) {
                case IMultiBLEMessageType.PROGRESS:
                    mProgressDialog.setMessage((String) msg.obj);
                    if (!mProgressDialog.isShowing()) {
                        mProgressDialog.show();
                    }
                    break;

                case IMultiBLEMessageType.ACCELEROMETER_MESSAGE:
                    if (characteristic == null 
                        || characteristic.getValue() == null) {
                        Log.w(TAG, "Error obtaining accelerometer value");
                        return;
                    }
                    updateAccelerometerValue16(gatt, characteristic);
                    break;

                case IMultiBLEMessageType.DISMISS:
                    mProgressDialog.hide();
                    break;
            }
        }
    }

    /**
     * Actualizar el valor del acelerómetro en base 16, si está implementado.
     *
     * @param characteristic la característica leída.
     */
    private void updateAccelerometerValue16(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic 
                                            characteristic) {
        if (mDelegate != null) {
            int[] accelerometer = getAccelData16(characteristic);
            mDelegate.updateAccelerometer(gatt, accelerometer[0], 
                    accelerometer[1], accelerometer[2], accelerometer[3], 
                    accelerometer[4], accelerometer[5]);
        }
    }

    public int[] getAccelData16(BluetoothGattCharacteristic characteristic) {
        int[] result = new int[6];
        byte[] value = characteristic.getValue();

        // Tres primeros valores de los datos del acelerómetro.
        result[0] = getIntFromByteArray(value, 0);
        result[1] = getIntFromByteArray(value, 2);
        result[2] = getIntFromByteArray(value, 4);
        // Tres primeros valores de los datos del giroscopio.
        result[3] = getIntFromByteArray(value, 6);
        result[4] = getIntFromByteArray(value, 8);
        result[5] = getIntFromByteArray(value, 10);

        return result;
    }

    private int getIntFromByteArray(byte[] byteArray, int offset) {
        int result, high, low;

        high = byteArray[offset];
        low = byteArray[offset + 1];
        result = ((high & 0x000000FF) << 8) | (low & 0x000000FF);
        result = (result > 32767) ? result - 65536 : result;

        return result;
    }

}

Y bien, por mi parte hoy ha sido todo, espero se tomen el tiempo de analizar a detalle el código presentado en este post y que sea de ayuda para algún Android Developer interesado en la tecnología BLE. En caso de haber posibles mejoras me encuentro por supuesto interesada en leerlas.

¡Hasta luego!

Leave a Reply

Your email address will not be published. Required fields are marked *