Android 4.3: как подключиться к нескольким Bluetooth-устройствам с низким энергопотреблением
Мой вопрос: может ли Android 4.3 (клиент) иметь активные соединения с несколькими устройствами BLE (серверами)? Если так, как я могу достичь этого?
Что я сделал до сих пор
Я пытаюсь оценить, какую пропускную способность вы можете достичь, используя BLE и Android 4.3 BLE API. Кроме того, я также пытаюсь выяснить, сколько устройств может быть подключено и активно одновременно. Я использую Nexus 7 (2013), Android 4.4 в качестве главного и TI CC2540 Keyfob в качестве подчиненных.
Я написал простое серверное программное обеспечение для подчиненных, которое передает 10000 20-байтовых пакетов через уведомления BLE. Я основал свое приложение для Android на ускорителе приложений из Bluetooth SIG.
Он хорошо работает для одного устройства, и я могу достичь пропускной способности около 56 кбит / с при интервале подключения 7,5 мс. Чтобы подключиться к нескольким рабам, я последовал совету Северного Сотрудника, который написал в Зоне разработчиков Северных стран:
Да, с одним приложением можно работать с несколькими рабами. Вам нужно будет обрабатывать каждого ведомого с одним экземпляром BluetoothGatt. Вам также понадобится специальный BluetoothGattCallback для каждого ведомого, к которому вы подключаетесь.
Я попробовал это, и это отчасти работает. Я могу подключиться к нескольким рабам. Я также могу зарегистрироваться для уведомлений на нескольких рабов. Проблема начинается, когда я запускаю тест. Сначала я получаю уведомления от всех ведомых, но через пару интервалов подключения приходят только уведомления от одного устройства. Примерно через 10 секунд другие подчиненные устройства отключаются, так как кажется, что они достигли тайм-аута соединения. Иногда с самого начала теста я получаю только уведомления от одного ведомого.
Я также попытался получить доступ к атрибуту через операцию чтения с тем же результатом. После пары чтений только ответы с одного устройства пришли через.
Мне известно, что на этом форуме есть несколько похожих вопросов: Поддерживает ли Android 4.3 несколько подключений устройств BLE?, Имеет ли родная реализация Android BLE GATT синхронную природу? или Ble несколько соединений. Но ни один из этих ответов не дал мне понять, возможно ли это и как это сделать.
Буду очень признателен за совет.
5 ответов
Я подозреваю, что все, кто добавляет задержки, просто позволяют системе BLE выполнить запрошенное вами действие, прежде чем вы отправите еще одно. Система Android BLE не имеет формы очередей. Если вы делаете
BluetoothGatt g;
g.writeDescriptor(a);
g.writeDescriptor(b);
тогда первая операция записи будет немедленно перезаписана второй. Да, это действительно глупо, и в документации, вероятно, следует упомянуть об этом.
Если вы вставите ожидание, это позволит завершить первую операцию перед выполнением второй. Это огромный уродливый взлом. Лучшее решение - создать собственную очередь (как должно быть в Google). К счастью, Nordic выпустили один для нас.
Изменить: Кстати, это универсальное поведение для API BLE. WebBluetooth ведет себя так же (но Javascript делает его проще в использовании), и я считаю, что BLE API iOS также ведет себя так же.
Относительно проблемы /questions/tagged/bluetooth-lowenergy на Android: я все еще использую задержки.
Концепция: после каждого серьезного действия, которое вызывает BluetoothGattCallback (например, соединение, обнаружение службы, запись, чтение), требуется деили. PS посмотрите на пример Google на примере BLE API level 19, чтобы узнать, как следует отправлять широковещательные рассылки, и получить общее представление и т. Д.
Во-первых, отсканируйте (или отсканируйте) устройства BluetoothDee, введите в 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);
}
PS sendBroadcast(намерение) может потребоваться сделать так:
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 (на этот раз с другим действием String), и теперь вы можете начать чтение, запись и включение уведомлений / указаний... PS Если вы работаете с несколькими устройствами, убедитесь, что вы запускаете чтение, запись и т. д. после того, как все устройства сообщили, что их службы были обнаружены.
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 и обработка их там.
Я разрабатываю приложение с функциями BLE самостоятельно. Мне удалось подключиться к нескольким устройствам и включить уведомления, чтобы реализовать задержки.
Поэтому я создаю новый поток (чтобы не блокировать поток пользовательского интерфейса) и в новом потоке подключаюсь и включаю уведомления.
Например, после 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;
}
Дождь прав в своем ответе, вам нужны задержки почти для всего, когда вы работаете с BLE в Android. Я разработал несколько приложений с ним, и это действительно необходимо. Используя их, вы избежите много сбоев.
В моем случае я использую задержки после каждой команды чтения / записи. Таким образом, вы гарантируете, что получите ответ от устройства BLE почти всегда. Я делаю что-то вроде этого: (конечно все делается в отдельном потоке, чтобы избежать большой работы в основном потоке)
readCharacteristic(myChar);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
myChar.getValue();
или же:
myChar.setValue(myByte);
writeCharacteristic(myChar);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Это действительно полезно, когда вы читаете / записываете несколько характеристик подряд... Поскольку Android достаточно быстр для выполнения команд почти мгновенно, если вы не используете задержку между ними, вы можете получить ошибки или несогласованные значения...
Надеюсь, это поможет, даже если это не совсем ответ на ваш вопрос.
К сожалению, уведомления в текущем стеке Android BLE немного глючат. Существуют некоторые жестко заданные ограничения, и я обнаружил некоторые проблемы со стабильностью даже на одном устройстве. (В какой-то момент я прочитал, что вы можете иметь только 4 уведомления... не уверен, что это на всех устройствах или для каждого устройства. Пытаюсь сейчас найти источник этой информации.)
Я попытался бы переключиться на цикл опроса (скажем, опрашивать элементы в вопросе 1/ сек) и посмотреть, если вы обнаружите, что ваша стабильность увеличивается. Я также хотел бы рассмотреть вопрос о переключении на другое ведомое устройство (например, HRM или TI SensorTag), чтобы выяснить, возможно ли проблема с кодом ведомой стороны (если только вы не можете проверить это на iOS или другой платформе и убедиться, что это не так. часть вопроса).
Редактировать: ссылка для ограничения уведомлений