Android 4.3: Cómo conectarse a múltiples dispositivos Bluetooth de baja energía


Mi pregunta es: ¿Puede Android 4.3 (cliente) tener conexiones activas con múltiples dispositivos BLE (servidores)? Si es así, ¿cómo puedo lograrlo?

Lo que hice hasta ahora

Intento evaluar qué rendimiento puede lograr usando BLE y Android 4.3 BLE API. Además, también intento averiguar cuántos dispositivos se pueden conectar y activar al mismo tiempo. Utilizo un Nexus 7 (2013), Android 4.4 como maestro y TI CC2540 Keyfob como esclavos.

Escribí un servidor simple software para los esclavos, que transmite 10000 paquetes de 20Byte a través de notificaciones BLE. Basé mi aplicación Android en el Acelerador de aplicaciones del Bluetooth SIG.

Funciona bien para un dispositivo y puedo lograr un rendimiento de carga útil de alrededor de 56 kBits en un Intervalo de conexión de 7.5 ms. Para conectar a varios esclavos seguí el consejo de un empleado nórdico que escribió en la Zona de Desarrollo Nórdico :

Sí es posible manejar varios esclavos con una sola aplicación. Tendría que manejar cada esclavo con una instancia BluetoothGatt. También necesitará BluetoothGattCallback específico para cada esclavo al que se conecte.

Así que lo intenté y en parte funciona. Puedo conectar con múltiples esclavos. También puedo registrarme para recibir notificaciones sobre varios esclavos. El problema comienza cuando empiezo la prueba. Recibo al principio las notificaciones de todos los esclavos, pero después de un par de intervalos de conexión solo las notificaciones de un dispositivo vienen comedero. Después de unos 10 segundos, los otros esclavos se desconectan, porque parecen alcanzar el tiempo de espera de conexión. A veces recibo desde el inicio de la prueba solo notificaciones de un esclavo.

También intenté acceder al atributo a través de una operación de lectura con el mismo resultado. Después de un par de lecturas solo las respuestas de un dispositivo llegaron a través.

Soy consciente de que hay algunas preguntas similares en este foro: ¿Android 4.3 soporta múltiples dispositivos BLE conexiones?, Tiene Android nativo BLE GATT implementación naturaleza síncrona? o Ble conexión múltiple. Pero ninguna de estas respuestas me dejó claro si es posible y cómo hacerlo.

Estaría muy agradecido por el consejo.

Author: Community, 2014-01-20

5 answers

Sospecho que todos los que agregan retrasos solo permiten que el sistema BLE complete la acción que ha solicitado antes de enviar otra. El sistema BLE de Android no tiene forma de hacer cola. Si lo hace

BluetoothGatt g;
g.writeDescriptor(a);
g.writeDescriptor(b);

Entonces la primera operación de escritura se sobrescribirá inmediatamente con la segunda. Sí, es realmente estúpido y la documentación probablemente debería mencionar esto.

Si inserta una espera, permite que la primera operación se complete antes de hacer la segunda. Eso es un enorme feo hack sin embargo. Una mejor solución es implementar tu propia cola (como debería tener Google). Afortunadamente Nordic ha lanzado uno para nosotros.

Https://github.com/NordicSemiconductor/puck-central-android/tree/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt

Editar: Por cierto, este es el comportamiento universal para las API BLE. WebBluetooth se comporta de la misma manera (pero Javascript hace que sea más fácil de usar), y creo que la API BLE de iOS también se comporta lo mismo.

 21
Author: Timmmm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-22 18:54:25

Volver a visitar el bluetooth-lowenergy problema en android: sigo usando los retrasos.

El concepto: después de cada acción importante que provoque el BluetoothGattCallback (por ejemplo, conexión, descubrimiento de servicios, escritura, lectura) se necesita un acuerdo. P.d. echa un vistazo a Google ejemplo en BLE API nivel 19 muestra de conectividad para entender cómo se deben enviar las transmisiones y obtener un entendimiento general, etc...

En primer lugar, scan (o scan) para dispositivos Bluetooth, rellene el connectionQueue con los dispositivos deseados y llame a initConnection () .

Eche un vistazo al siguiente ejemplo.

private Queue<BluetoothDevice> connectionQueue = new LinkedList<BluetoothDevice>();

public void initConnection(){
    if(connectionThread == null){
        connectionThread = new Thread(new Runnable() {
            @Override
            public void run() {
                connectionLoop();
                connectionThread.interrupt();
                connectionThread = null;
            }
        });

        connectionThread.start();
    }
}

private void connectionLoop(){
    while(!connectionQueue.isEmpty()){
        connectionQueue.poll().connectGatt(context, false, bleInterface.mGattCallback);
        try {
            Thread.sleep(250);
        } catch (InterruptedException e) {}
    }
}

Ahora si todo está bien, has hecho conexiones y BluetoothGattCallback.Se ha llamado onConnectionStateChange(BluetoothGatt gatt, int status, int newState).

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        switch(status){
            case BluetoothGatt.GATT_SUCCESS:
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    broadcastUpdate(BluetoothConstants.ACTION_GATT_CONNECTED, gatt);
                }else if(newState == BluetoothProfile.STATE_DISCONNECTED){
                    broadcastUpdate(BluetoothConstants.ACTION_GATT_DISCONNECTED, gatt);
                }
                break;
        }

    }
protected void broadcastUpdate(String action, BluetoothGatt gatt) {
    final Intent intent = new Intent(action);

    intent.putExtra(BluetoothConstants.EXTRA_MAC, gatt.getDevice().getAddress());

    sendBroadcast(intent);
}

P.d. sendBroadcast (intent) puede que tenga que hacerse así:

Context context = activity.getBaseContext();
context.sendBroadcast(intent);

Entonces la emisión es recibida por BroadcastReceiver.onReceive(...)

public BroadcastReceiver myUpdateReceiver = new BroadcastReceiver(){

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if(BluetoothConstants.ACTION_GATT_CONNECTED.equals(action)){
            //Connection made, here you can make a decision: do you want to initiate service discovery.
            // P.S. If you are working with multiple devices, 
            // make sure that you start the service discovery 
            // after all desired connections are made
        }
        ....
    }
}

Después de hacer lo que quieras en el receptor de transmisión, así es como continúo:

private Queue<BluetoothGatt> serviceDiscoveryQueue = new LinkedList<BluetoothGatt>();

private void initServiceDiscovery(){
    if(serviceDiscoveryThread == null){
        serviceDiscoveryThread = new Thread(new Runnable() {
            @Override
            public void run() {
                serviceDiscovery();

                serviceDiscoveryThread.interrupt();
                serviceDiscoveryThread = null;
            }
        });

        serviceDiscoveryThread.start();
    }
}

private void serviceDiscovery(){
    while(!serviceDiscoveryQueue.isEmpty()){
        serviceDiscoveryQueue.poll().discoverServices();
        try {
            Thread.sleep(250);
        } catch (InterruptedException e){}
    }
}

De nuevo, después de un descubrimiento de servicio exitoso, BluetoothGattCallback.ervservicesdiscovered(...) se llama. Una vez más, envío una intent al BroadcastReceiver (esta vez con una cadena de acción diferente) y es ahora que puede comenzar a leer, escribir y habilitar notificaciones/indicaciones... P.d. Si está trabajando con varios dispositivos, asegúrese de iniciar la lectura, escritura, etc... cosas después de que todos los dispositivos han informado de que sus servicios han sido descubiertos.

private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();

private void startThread(){

    if(initialisationThread == null){
        initialisationThread = new Thread(new Runnable() {
            @Override
            public void run() {
                loopQueues();

                initialisationThread.interrupt();
                initialisationThread = null;
            }
        });

        initialisationThread.start();
    }

}

private void loopQueues() {

    while(!characteristicReadQueue.isEmpty()){
        readCharacteristic(characteristicReadQueue.poll());
        try {
            Thread.sleep(BluetoothConstants.DELAY);
        } catch (InterruptedException e) {}
    }
    // A loop for starting indications and all other stuff goes here!
}

BluetoothGattCallback tendrá todos los datos entrantes del sensor BLE. Una buena práctica es enviar una transmisión con los datos a su BroadcastReceiver y manejarla allí.

 13
Author: Rain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-31 13:04:14

Estoy desarrollando una aplicación con características BLE yo mismo. La forma en que me las arreglé para conectarme a múltiples dispositivos y activar las notificaciones fue implementar retrasos.

Así que hago un nuevo hilo (para no bloquear el hilo de la interfaz de usuario) y en el nuevo hilo conectar y activar las notificaciones.

Por ejemplo, después de BluetoothDevice.connectGatt (); llama al hilo.sleep ();

Y agregue el mismo retraso para las notificaciones de lectura/escritura y habilitar/disable.

EDITAR

Use wait como esto para que Android dindn't reaise ANR

public static boolean waitIdle() {
        int i = 300;
        i /= 10;
        while (--i > 0) {
            if (true)
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

        }

        return i > 0;
    }
 7
Author: Rain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-19 13:52:43

Rain tiene razón en su respuesta, necesita retrasos para casi todo cuando se trabaja con BLE en Android. He desarrollado varias aplicaciones con él y es realmente necesario. Al usarlos se evitan muchos accidentes.

En mi caso, uso retrasos después de cada comando de lectura/escritura. Al hacerlo, se asegura de recibir la respuesta del dispositivo BLE casi siempre. Hago algo como esto: (por supuesto todo se hace en un hilo separado para evitar mucho trabajo en el principal thread)

 readCharacteristic(myChar);
 try {
    Thread.sleep(100);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }
 myChar.getValue();

O:

 myChar.setValue(myByte);
 writeCharacteristic(myChar);
 try {
    Thread.sleep(100);
 } catch (InterruptedException e) {
    e.printStackTrace();
 }

Esto es realmente útil cuando lee/escribe varias características seguidas... Como Android es lo suficientemente rápido como para ejecutar los comandos casi al instante, si no utiliza un retraso entre ellos puede obtener errores o valores incoherentes...

Espero que ayude incluso si no es exactamente la respuesta a su pregunta.

 2
Author: margabro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-13 08:51:03

Desafortunadamente, las notificaciones en la pila actual de Android BLE tienen un poco de errores. Hay algunos límites codificados y he encontrado algunos problemas de estabilidad incluso con un solo dispositivo. (Leí en un momento que solo podía tener 4 notificaciones... no estoy seguro de si es en todos los dispositivos o por dispositivo. Tratando de encontrar la fuente de esa información ahora.)

Intentaría cambiar a un bucle de sondeo (por ejemplo, sondear los elementos en la pregunta 1/seg) y ver si encuentra que su estabilidad aumenta. Lo haría también considere cambiar a un dispositivo esclavo diferente (por ejemplo, un HRM o el TI SensorTag) para ver si tal vez hay un problema con el código del lado esclavo (a menos que pueda probar eso con iOS u otra plataforma y confirmar que no es parte del problema).

Editar: Referencia para la limitación de la notificación

 0
Author: Ben Von Handorf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-20 15:15:54