Android 4.3: jak połączyć się z wieloma urządzeniami Bluetooth Low Energy

Moje pytanie brzmi: Czy Android 4.3 (klient) może mieć aktywne połączenia z wieloma urządzeniami BLE (serwerami)? Jeśli tak, to jak Mogę to osiągnąć?

Co zrobiłem do tej pory

Staram się ocenić, jaką przepustowość można osiągnąć za pomocą BLE i Android 4.3 BLE API. Ponadto staram się również dowiedzieć, ile urządzeń można podłączyć i aktywować w tym samym czasie. Używam Nexus 7( 2013), Android 4.4 jako master i Ti CC2540 Keyfob jako niewolników.

Napisałem prosty serwer oprogramowanie dla niewolników, które przesyła 10000 pakietów 20Byte poprzez powiadomienia BLE. Oparłem moją aplikację na Androidzie na Application Accelerator z Bluetooth SIG.

To działa dobrze dla jednego urządzenia i mogę osiągnąć około 56 kBits przepustowości ładunku w odstępie 7,5 ms. aby połączyć się z wieloma niewolnikami poszedłem za radą Nordyckiego pracownika, który napisał w Nordic Developer Zone:

Tak jest możliwość obsługi wielu niewolników za pomocą jednej aplikacji. Każdy slave musi być obsługiwany przez jedną instancję BluetoothGatt. Potrzebujesz również specyficznego BluetoothGattCallback dla każdego slave, z którym się łączysz.

Więc próbowałem i to częściowo działa. Mogę połączyć się z wieloma niewolnikami. Mogę również zarejestrować się do powiadomień na wielu niewolników. Problem zaczyna się, gdy zaczynam test. Otrzymuję na początku powiadomienia od wszystkich niewolników, ale po kilku odstępach czasu połączenia tylko powiadomienia z jednego urządzenia przychodzą koryto. Po około 10 sekundach Pozostałe niewolniki rozłączają się, ponieważ wydają się osiągać czas połączenia. Czasami od samego początku testu otrzymuję tylko powiadomienia od jednego slave ' a.

Próbowałem również uzyskać dostęp do atrybutu podczas operacji odczytu z takim samym wynikiem. Po kilku czytaniach pojawiły się tylko odpowiedzi z jednego urządzenia.

Zdaję sobie sprawę, że jest kilka podobnych pytań na tym forum: czy Android 4.3 obsługuje wiele urządzeń BLE powiązania?, czy natywna implementacja Androida BLE GATT ma charakter synchroniczny? lub Ble wielokrotne połączenie. Ale żadna z tych odpowiedzi nie wyjaśniła mi, czy jest to możliwe i jak to zrobić.

Byłbym bardzo wdzięczny za radę.

Author: Community, 2014-01-20

5 answers

Podejrzewam, że każdy dodawanie opóźnień pozwala systemowi BLE Na wykonanie czynności, o którą prosiłeś, zanim zgłosisz kolejną. System BLE Androida nie ma formy kolejkowania. If you do

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

Wtedy pierwsza operacja zapisu zostanie natychmiast nadpisana drugą. Tak, to naprawdę głupie i dokumentacja powinna prawdopodobnie o tym wspomnieć.

Jeśli wstawisz wait, to pierwsza operacja zostanie zakończona przed wykonaniem drugiej. To jest ogromny ale brzydki hack. Lepszym rozwiązaniem jest zaimplementowanie własnej kolejki (tak jak Google powinno mieć). Na szczęście Nordic wypuściło dla nas jedną.

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

Edit: przy okazji jest to uniwersalne zachowanie dla BLE API. WebBluetooth zachowuje się tak samo (ale Javascript czyni go łatwiejszym w użyciu), i wierzę, że BLE API iOS również zachowuje się to samo.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-22 18:54:25

Re: Problem z bluetooth-lowenergy na android : nadal używam opóźnień.

Koncepcja: po każdej większej akcji wywołującej BluetoothGattCallback (np. conenction, service discovery, write, read) potrzebna jest dealy. P. S. spójrz na przykład Google na BLE API level 19 przykład dla łączności , aby zrozumieć, jak powinny być wysyłane transmisje i uzyskać ogólne zrozumienie itp...

Po pierwsze, scan (lub scan) W poszukiwaniu urządzeń BluetoothDevices, należy wypełnić connectionQueue żądanymi urządzeniami i wywołać initConnection () .

Spójrz na poniższy przykład.

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) {}
    }
}
/ Align = "left" / wywołano 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. S. sendBroadcast (intent) Może Trzeba zrobić tak:

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

Wtedy transmisja jest odbierana przez 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
        }
        ....
    }
}

Po zrobieniu co chcesz w odbiorniku transmisji, oto jak kontynuuję:

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){}
    }
}
Ponownie, po udanym odkryciu usługi, BluetoothGattCallback.onServicesDiscovered(...) jest nazywany. Ponownie wysyłam intencję do BroadcastReceiver (tym razem z innym ciągiem akcji) i teraz możesz zacząć czytać, pisać i włączać powiadomienia/wskazania... P. S. jeśli pracujesz z wieloma urządzeniami, upewnij się, że zaczynasz czytanie, pisanie itp... rzeczy po tym, jak wszystkie urządzenia zgłosiły, że ich usługi zostały odkryte.
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 będzie miał wszystkie przychodzące dane z czujnika BLE. Dobrą praktyką jest wysyłanie transmisji z danymi do BroadcastReceiver i obsługiwanie jej tam.
 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-31 13:04:14

Sam opracowuję aplikację z funkcjami BLE. Sposób, w jaki udało mi się połączyć z wieloma urządzeniami i włączyć powiadomienia, polegał na wdrożeniu opóźnień.

Więc robię nowy wątek (aby nie blokować wątku UI) i w nowym wątku połączyć i włączyć powiadomienia.

Na przykład po BluetoothDevice.connectGatt (); wywołanie wątku.sleep ();

I dodać to samo opóźnienie dla odczytu/zapisu i włączenia/wyłączenia powiadomień.

EDIT

Użyj wait tak, że 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/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-19 13:52:43

Rain ma rację w jego odpowiedzi, trzeba Opóźnienia dla prawie wszystkiego, gdy pracujesz z BLE w Androidzie. Opracowałem z nim kilka aplikacji i jest to naprawdę konieczne. Korzystając z nich unikasz wielu awarii.

W moim przypadku używam opóźnień po każdym poleceniu odczytu/zapisu. W ten sposób niemal zawsze otrzymujesz odpowiedź z urządzenia BLE. Robię coś takiego: (oczywiście wszystko jest robione w osobnym wątku, aby uniknąć dużo pracy na głównej thread)

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

Lub:

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

Jest to bardzo przydatne, gdy czytasz / zapisujesz kilka cech pod rząd... Ponieważ Android jest wystarczająco szybki, aby wykonać polecenia niemal natychmiast, jeśli nie użyjesz opóźnienia między nimi, możesz uzyskać błędy lub niespójne wartości...

Mam nadzieję, że to pomoże, nawet jeśli nie jest to dokładnie odpowiedź na twoje pytanie.

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-13 08:51:03

Niestety powiadomienia w bieżącym stosie Androida BLE są nieco błędne. Istnieją pewne twarde limity i znalazłem pewne problemy ze stabilnością nawet w przypadku pojedynczego urządzenia. (Czytałem w pewnym momencie, że można mieć tylko 4 powiadomienia... nie jestem pewien, czy dotyczy to wszystkich urządzeń, czy każdego urządzenia. Próbuję znaleźć źródło tych informacji.)

Spróbowałbym przełączyć się na pętlę ankietową (powiedzmy, przepytaj przedmioty w pytaniu 1 / sek) i sprawdzić, czy Twoja stabilność wzrasta. Ja bym rozważ również przejście na inne urządzenie slave (powiedzmy HRM lub TI SensorTag), aby sprawdzić, czy nie występuje problem z kodem po stronie slave (chyba że możesz przetestować go na iOS lub innej platformie i potwierdzić, że nie jest częścią problemu).

Edytuj: odniesienie do ograniczenia zgłoszenia

 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/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-20 15:15:54