Android 4.3: Как да се свържете с множество Bluetooth устройства с ниска енергия

Моят въпрос е: Може ли Android 4.3 (клиент) да има активни връзки с множество BLE устройства (сървъри)? Ако е така, как мога да го постигна?

Какво направих досега

Опитвам се да преценя каква пропускателна способност можете да постигнете, като използвате BLE и Android 4.3 BLE API. Освен това се опитвам да разбера колко устройства могат да бъдат свързани и активни едновременно. Използвам Nexus 7 (2013), Android 4.4 като главен и TI CC2540 Keyfob като подчинени.

Написах прост сървърен софтуер за робите, който предава 10 000 20-байтови пакета чрез BLE известия. Базирах приложението си за Android на Application Accelerator от Bluetooth SIG.

Работи добре за едно устройство и мога да постигна около 56 kBits полезен товар при интервал на връзка от 7,5 ms. За да се свържа с множество роби, последвах съвета на скандинавски служител, който писа в Скандинавска зона за разработчици:

Да, възможно е да управлявате множество роби с едно приложение. Ще трябва да управлявате всеки подчинен с един екземпляр на BluetoothGatt. Ще ви е необходим и специфичен BluetoothGattCallback за всеки подчинен, към който се свързвате.

Така че опитах това и отчасти работи. Мога да се свържа с множество роби. Мога също да се регистрирам за известия за множество роби. Проблемът започва, когато стартирам теста. Първоначално получавам известия от всички подчинени устройства, но след няколко интервала на свързване идват само известията от едно устройство. След около 10 секунди другите подчинени устройства прекъсват връзката, защото изглежда, че достигат времето за изчакване на връзката. Понякога получавам известия от самото начало на теста само от един роб.

Също така се опитах да осъществя достъп до атрибута през операция за четене със същия резултат. След няколко четения се появиха само отговорите от едно устройство.

Наясно съм, че има няколко подобни въпроса в този форум: Android 4.3 поддържа ли връзки с множество BLE устройства?, Има ли внедряването на Android BLE GATT синхронно по природа? или Множествена връзка на Ble. Но никой от тези отговори не ми даде яснота дали е възможно и как да го направя.

Ще съм много благодарен за съвет.


person Andreas Mueller    schedule 20.01.2014    source източник
comment
Трябва ли задължително да се свържете? Ако се притеснявате от укриването на данните и надеждността или пълния отказ, може би бихте могли просто да ги поставите в пакети за излъчване и да сканирате за тях.   -  person Chris Stratton    schedule 16.03.2014
comment
Благодаря ви за повторението. Трябва да използвам свързан режим от съображения за надеждност   -  person Andreas Mueller    schedule 17.03.2014
comment
Търсих из цялата мрежа, за да намеря примери как да направите това, което сте направили във вашата тестова програма @Andreas Mueller. Бихте ли били така любезни да ми помогнете и да ми покажете как да изпращам известия от Android до чипа CC2540 с модифициран код на ускорител на приложения? (може би връзка към вашия проект?)   -  person HenrikS    schedule 26.05.2014


Отговори (5)


Подозирам, че всеки, който добавя забавяния, просто позволява на системата BLE да завърши действието, което сте поискали, преди да изпратите друго. BLE системата на Android няма форма на опашка. Ако го направиш

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

тогава първата операция за запис веднага ще бъде презаписана с втората. Да, наистина е глупаво и документацията вероятно всъщност трябва да споменава това.

Ако вмъкнете изчакване, това позволява на първата операция да завърши, преди да извърши втората. Това обаче е огромен грозен хак. По-добро решение е да внедрите своя собствена опашка (както Google трябва да има). За щастие Nordic пуснаха един за нас.

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

Редактиране: Между другото това е универсалното поведение за BLE API. WebBluetooth се държи по същия начин (но Javascript го прави по-лесен за използване) и вярвам, че BLE API на iOS също се държи по същия начин.

person Timmmm    schedule 26.05.2015
comment
всички смартфони с Android поддържат ли няколко gatt връзки едновременно? Мога ли да знам телефона, който сте тествали това, и версията на Android API. Предполагам, че това е и характеристиката на основния Bluetooth чип в телефона. - person Raulp; 21.12.2016
comment
Да, всички телефони поддържат множество gatt връзки. Вижте този въпрос. - person Timmmm; 21.12.2016
comment
Можете да направите опашка за дескриптори и просто да извикате първия g.writeDescriptor(a); след това в onDescriptorWrite продължавате с останалите дескриптори, докато всички бъдат написани. - person Pedro Antonio; 04.02.2021

Повторно разглеждане на проблема с bluetooth-lowenergy на : Все още използвам забавяния.

Концепцията: след всяко основно действие, което провокира BluetoothGattCallback (напр. свързване, откриване на услуга, писане, прочетете) необходима е сделка. P.S. разгледайте примера на Google за BLE API ниво 19 пример за свързаност за да разберете как трябва да се изпращат излъчвания и да получите някакво общо разбиране и т.н...

Първо сканирайте (или сканиране) за BluetoothDevices, попълнете connectionQueue с желаните устройства и извикайте initConnection().

Обърнете внимание на следния пример.

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) {}
    }
}

Сега, ако всичко е наред, сте направили връзки и BluetoothGattCallback.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) може да се наложи да се направи по следния начин:

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

След това излъчването се получава от 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
        }
        ....
    }
}

След като правите каквото искате в излъчващия приемник, ето как продължавам:

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){}
    }
}

Отново, след успешно откриване на услуга, BluetoothGattCallback.onServicesDiscovered (...) се извиква. Отново изпращам намерение до BroadcastReceiver (този път с различен низ за действие) и сега можете да започнете да четете, пишете и активирате известия/индикации... P.S. Ако работите с множество устройства, уверете се, че сте започнали нещата за четене, писане и т.н., след като всички устройства са съобщили, че услугите им са били открити.

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 ще има всичките ви входящи данни от BLE сензора. Добра практика е да изпратите излъчване с данните до вашия BroadcastReceiver и да го обработвате там.

person Rain    schedule 10.12.2014
comment
Благодаря за споделянето на връзката към примера за Android. Понякога се движим по света, но забравяме да разгледаме примерите за Android..:) - person Rahul Rastogi; 16.02.2015
comment
Използването на обратното извикване на Gatt е ЕДИНСТВЕНИЯТ правилен начин за обработка на BLE операции. Много съм изненадан да видя толкова много публикации, които говорят за произволни закъснения. Тези методи са асинхронни - кой знае колко време може да отнеме. Това е цялата цел на обратното извикване на Gatt. Да, много е досадно да се приложи ефективно това обратно извикване, така че да работи добре с останалата част от вашата система, но това е единственият начин. - person SuperDeclarative; 29.05.2015
comment
Опитвам се да се справя с две ble връзки. Вече съм приложил повечето от това сам. Въпреки това, когато настроя всяко устройство да получава известия, това изглежда се случва два пъти на последното устройство, което свързвам. Някой проверил ли е този отговор по-горе работи? Благодаря, знам, че това е стара публикация - person luckyging3r; 21.03.2018

Аз самият разработвам приложение с BLE функции. Начинът, по който успях да се свържа с множество устройства и да включа известията, беше да внедря забавяния.

Затова правя нова нишка (за да не блокирам UI нишка) и в новата нишка се свързвам и включвам известия.

Например след BluetoothDevice.connectGatt(); извикване на Thread.sleep();

И добавете същото забавяне за четене/запис и активиране/деактивиране на известия.

РЕДАКТИРАНЕ

Използвайте изчакване по този начин, така че Android да не генерира отново 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;
    }
person Rain    schedule 16.03.2014
comment
Промених кода си, така че сега да се свързва и да се регистрира за известия на всеки подчинен последователно. И това оправи всички проблеми. Така че много ви благодаря за помощта. - person Andreas Mueller; 17.03.2014
comment
Закъсненията са хак, който заобикаля истинския проблем. Вижте моя отговор. - person Timmmm; 26.05.2015

Rain е прав в отговора си, имате нужда от забавяния за почти всичко, когато работите с BLE в Android. Разработих няколко приложения с него и наистина е необходимо. Използвайки ги, вие избягвате много сривове.

В моя случай използвам закъснения след всяка команда за четене/запис. По този начин гарантирате, че получавате отговора от BLE устройството почти винаги. Правя нещо подобно: (разбира се, всичко се прави в отделна нишка, за да се избегне много работа върху основната нишка)

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

or:

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

Това е наистина полезно, когато четете/пишете няколко характеристики подред... Тъй като Android е достатъчно бърз, за ​​да изпълни командите почти моментално, ако не използвате забавяне между тях, може да получите грешки или непоследователни стойности...

Надявам се да помогне, дори и да не е точно отговорът на въпроса ви.

person kodartcha    schedule 13.08.2014
comment
Например имах нужда от 500ms забавяне, за да направя всичко да работи на 100%. - person Rain; 13.08.2014
comment
Това е много лошо решение. Обратните повиквания са там, за да доставят отговорите. Изчакването на произволно забавяне вместо това е наистина лошо, тъй като не знаете дали операцията наистина е завършена (радиосмущенията могат да добавят непредсказуеми забавяния). Очевидно също така не дава отговор възможно най-скоро. - person Emil; 05.12.2020
comment
@Emil, благодаря за коментара ти в 6-годишен отговор, който дори не е приетият! Много неща се промениха оттогава в рамката на BLE... - person kodartcha; 06.12.2020
comment
Не точно. Тази част от API изглежда точно както днес, както преди 6 години. - person Emil; 06.12.2020

За съжаление известията в текущия Android BLE стек са малко бъги. Има някои твърдо кодирани ограничения и открих някои проблеми със стабилността дори с едно устройство. (В един момент прочетох, че можете да имате само 4 известия... не съм сигурен дали това е на всички устройства или на устройство. Сега се опитвам да намеря източника на тази информация.)

Бих опитал да превключа към цикъл на анкетиране (да речем, анкетиране на въпросните елементи 1/сек) и да видя дали установявате, че стабилността ви се увеличава. Също така бих обмислил преминаване към друго подчинено устройство (да речем HRM или TI SensorTag), за да видя дали може би има проблем с подчинения код (освен ако не можете да го тествате срещу iOS или друга платформа и да потвърдите, че не е част от проблема).

Редактиране: Справка за ограничение за уведомяване

person Ben Von Handorf    schedule 20.01.2014
comment
Благодаря Ви за отговора. Мисля, че кодът от страна на подчинения е ок, защото тестът се изпълнява под iOS с до 8 подчинени устройства. Също така се опитах да осъществя достъп до атрибута през операция за четене със същия резултат. - person Andreas Mueller; 20.01.2014
comment
След това бих опитал бърза промяна на цикъл на избиране вместо известия и ще видя дали това изобщо стабилизира връзката. Опитвам се да събера еквивалентна настройка за тестване, но това може да не стане днес. - person Ben Von Handorf; 20.01.2014
comment
Благодаря. Наистина бих го оценил. - person Andreas Mueller; 21.01.2014