2014-01-20 113 views
31

我的問題是:Android 4.3(客戶端)可以與多個BLE設備(服務器)進行主動連接嗎?如果是這樣,我該如何實現它?Android 4.3:如何連接到多個藍牙低功耗設備

我做了什麼至今

我試着評估哪些吞吐量,可以使用BLE和Android 4.3 BLE API實現。此外,我還嘗試瞭解可以同時連接和激活多少個設備。我使用Nexus 7(2013),Android 4.4作爲主設備,TI CC2540 Keyfob作爲從設備。

我爲奴隸寫了一個簡單的服務器軟件,它通過BLE通知傳輸10000個20Byte數據包。我基於Bluetooth SIG的Application Accelerator上安裝了我的Android應用程序。

它適用於一個設備,我可以在7.5 ms的連接間隔內實現大約56 kBits的有效負載吞吐量。爲了連接到多個奴隸,我遵循了寫在Nordic Developer Zone北歐員工的建議:

是的,它可以處理多個奴隸與一個應用程序。您需要使用一個BluetoothGatt實例處理每個從屬設備。您還需要爲每個連接的從機配備特定的BluetoothGattCallback。

所以我試過了,它部分工作。我可以連接到多個從站。我也可以在多個從站上註冊通知。當我開始測試時,問題就開始了。我首先收到來自所有奴隸的通知,但是在幾次連接間隔後,只有來自一臺設備的通知纔會出現低谷。大約10秒後,其他從站斷開,因爲它們似乎達到連接超時。有時我從測試開始就收到來自一個奴隸的通知。

我也嘗試通過讀取操作訪問屬性的結果相同。在幾次讀取之後,只有一臺設備的答案出現了低谷。

我知道這個論壇上有幾個類似的問題:Does Android 4.3 support multiple BLE device connections?Has native Android BLE GATT implementation synchronous nature?Ble multiple connection。但是,沒有一個答案對我來說很清楚,如果可能的話以及如何去做。

我會非常感謝您的建議。

+0

您是否需要連接?如果您擔心隱藏數據和可靠或全部失敗,也許您可​​以簡單地將它放在廣播數據包中並進行掃描。 –

+0

謝謝您的重播。我必須使用連接模式的可靠性原因 –

+0

我有搜索遍佈網絡找到示例如何做你在你做的測試程序@Andreas Mueller。您能否如此善待我,並告訴我如何通過修改的「應用程序加速器」代碼將Android通知發送至CC2540芯片? (鏈接到您的項目可能?) – HenrikS

回答

18

我懷疑每個人都添加延遲只是讓BLE系統完成您在提交另一個之前所要求的操作。 Android的BLE系統沒有任何排隊形式。如果你這樣做

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

然後第一個寫操作將立即被第二個寫操作覆蓋。是的,這真的很愚蠢,文件應該可以提到這一點。

如果插入等待,它可以在完成第一個操作之前完成第一個操作。雖然這是一個巨大的醜陋的黑客攻擊。更好的解決方案是實現你自己的隊列(就像谷歌應該有的)。幸運的是,北歐已經爲我們發佈了一款。

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

編輯:順便說一下這是BLE的API的普遍行爲。 WebBluetooth的行爲方式相同(但Javascript確實使它更易於使用),我相信iOS的BLE API也具有相同的行爲。

+0

是否所有Android智能手機同時支持多個gatt連接?我可以知道您測試的手機和Android API版本。我想這也是手機中底層藍牙芯片的特性。 – Raulp

+0

是的所有電話都支持多個gatt連接。看看[這個問題](https://stackoverflow.com/questions/34400182/android-limit-of-simultaneous-ble-connections)。 – Timmmm

0

不幸的是,當前Android BLE堆棧中的通知有點兒bug。有一些硬編碼限制,即使使用單個設備,我也發現了一些穩定性問題。 (我讀過一點,你只能有4個通知...不知道是否跨越所有設備或每個設備。試圖找到該信息的來源現在。)

我會嘗試切換到輪詢循環(比如,輪詢問題1 /秒),看看你是否發現你的穩定性增加。我還會考慮切換到不同的從設備(比如HRM或TI SensorTag),以查看從設備端代碼是否存在問題(除非您可以在iOS或其他平臺上測試該設備並確認它不是問題的一部分)。

編輯Reference for notification limitation

+0

謝謝您的回答。我認爲從機端的代碼是可以的,因爲它在iOS下運行的測試多達8個從機。我也嘗試通過讀取操作訪問屬性,獲得相同的結果。 –

+0

然後,我會嘗試快速更改爲輪詢循環而不是通知,並查看是否穩定連接。我試圖獲得一個相同的測試設置,但這可能不會在今天完成。 –

+0

謝謝。我真的很感激。 –

7

我開發一個應用程序與BLE功能自己。我設法連接到多個設備並打開通知的方式是實施延遲。

因此,我做了一個新線程(爲了不阻止UI線程)並在新線程中連接並打開通知。

例如,BluetoothDevice.connectGatt();調用Thread.sleep();

併爲讀/寫和啓用/解除通知添加相同的延遲。

編輯

使用等這樣的,這樣的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; 
    } 
+0

我改變了我的代碼,以便它現在連接並註冊每個從站的通知。並解決了所有問題。非常感謝你的幫助。 –

+0

不客氣;) – Rain

+1

延遲是一種黑客,解決真正的問題。看到我的答案。 – Timmmm

2

雨是正確的,他的回答,你當你在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足夠快速地執行命令幾乎立即,如果你不使用它們之間的延遲,你可能會得到錯誤或不連貫的值...

希望它有助於即使這不完全是你的問題的答案。

+0

例如,我需要500毫秒的延遲才能使所有工作都100%。 – Rain

11

重新登錄問題:我仍在使用延遲。

概念:在每一個引發BluetoothGattCallback(例如連接,服務發現,寫入,讀取)的主要動作之後,都需要一個dealy。附:看看上BLE API level 19 sample for connectivity谷歌的例子來理解廣播應該如何被髮送,並得到一些大致的瞭解等等......

首先,scan(或scan)爲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(意向)可能需要這樣做:

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並在那裏處理。

+0

分享android示例鏈接的問題。有時,我們在世界各地移動,但忘了看看android例子.. :) –

+5

使用GATT回調是處理BLE操作的唯一正確方法。看到這麼多的帖子在談論隨機延遲,我感到非常驚訝。這些方法是異步的 - 誰知道他們可能需要多長時間。這是GAT回調的全部目的。是的,它非常討厭有效地實現該回調,以便與系統的其他部分良好地配合,但是它是唯一的出路。 – MattC