【问题标题】:Android 4.3: How to connect to multiple Bluetooth Low Energy devicesAndroid 4.3:如何连接到多个低功耗蓝牙设备
【发布时间】:2014-02-09 19:19:42
【问题描述】:

我的问题是:Android 4.3(客户端)可以与多个 BLE 设备(服务器)建立活动连接吗?如果是这样,我该如何实现?

到目前为止我做了什么

我尝试评估使用 BLE 和 Android 4.3 BLE API 可以实现的吞吐量。此外,我还尝试找出可以同时连接和激活的设备数量。我使用 Nexus 7 (2013),Android 4.4 作为主机,TI CC2540 Keyfob 作为从机。

我为从机编写了一个简单的服务器软件,它通过 BLE 通知传输 10000 个 20Byte 数据包。我的 Android 应用基于蓝牙 SIG 的 Application Accelerator

它适用于一台设备,我可以在 7.5 毫秒的连接间隔下实现大约 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。但是这些答案都没有让我清楚,如果可能以及如何做到这一点。

非常感谢您的建议。

【问题讨论】:

  • 您一定需要连接吗?如果您担心隐藏数据和可靠或完全失败,也许您可​​以简单地将其放入广播数据包并扫描它们。
  • 感谢您的重播。出于可靠性原因,我必须使用连接模式
  • 我在整个网络上进行了搜索,以查找示例如何执行您在测试程序@Andreas Mueller 中所做的事情。你能帮我吗,告诉我如何使用修改后的“应用程序加速器”代码将通知从 Android 发送到 CC2540 芯片? (可能链接到您的项目?)

标签: android bluetooth-lowenergy


【解决方案1】:

我怀疑每个添加延迟的人只是允许 BLE 系统在您提交另一个之前完成您要求的操作。 Android 的 BLE 系统没有排队的形式。如果你这样做了

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

那么第一个写操作将立即被第二个写操作覆盖。是的,这真的很愚蠢,文档可能实际上应该提到这一点。

如果您插入一个等待,它允许第一个操作在执行第二个操作之前完成。不过,这是一个巨大的丑陋黑客。一个更好的解决方案是实现你自己的队列(就像谷歌应该有的那样)。幸运的是,Nordic 为我们发布了一款。

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 也有相同的行为方式。

【讨论】:

  • 所有Android智能手机是否同时支持多个gatt连接?请问您测试的手机和Android API版本。我想这也是手机中底层蓝牙芯片的特性。
  • 是的,所有手机都支持多个 gatt 连接。见this question
  • 你可以创建一个描述符队列,然后调用第一个 g.writeDescriptor(a);然后在 onDescriptorWrite 中继续使用剩余的描述符,直到全部写入。
【解决方案2】:

重新访问 上的 问题:我仍在使用延迟。

概念:在每个引发BluetoothGattCallback 的重大操作(例如连接、服务发现、写入、读取)之后,都需要一个交易。附言查看 BLE API level 19 sample for connectivity 上的 Google 示例,了解应该如何发送广播并获得一些一般性的了解等...

首先,scan(或scan)用于蓝牙设备,用所需设备填充 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);
}

附: 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 发送了一个意图(这次使用不同的操作字符串),现在您可以开始阅读、编写和启用通知/指示... 附注如果您使用多台设备,请确保在所有设备都报告已发现其服务后开始读取、写入等内容。

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 并在那里处理它。

【讨论】:

  • 感谢分享android示例的链接。有时,我们在世界各地移动,但忘记查看 android 示例..:)
  • 使用 Gatt 回调是处理 BLE 操作的唯一正确方法。看到这么多帖子谈论随机延迟,我感到非常惊讶。这些方法是异步的——谁知道它们可能需要多长时间。这就是 Gatt 回调的全部目的。是的,有效地实现该回调以使其与系统的其余部分很好地配合非常烦人,但这是唯一的方法。
  • 我正在尝试处理两个 ble 连接。我已经自己实现了大部分。但是,当我将每个设备设置为接收通知时,它似乎在我连接的最后一个设备上发生了两次。有没有人验证过以上答案有效?谢谢,我知道这是一篇旧帖子
【解决方案3】:

我自己正在开发一个具有 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;
    }

【讨论】:

  • 我更改了我的代码,以便它现在可以按顺序连接并注册每个从站上的通知。这解决了所有问题。非常感谢您的帮助。
  • 延迟是一种解决实际问题的技巧。看我的回答。
【解决方案4】:

Rain 的回答是正确的,当您在 Android 中使用 BLE 时,几乎所有事情都需要延迟。我用它开发了几个应用程序,这真的很有必要。通过使用它们,您可以避免很多崩溃。

就我而言,我在每个读/写命令后使用延迟。这样做,您可以确保您几乎总是收到来自 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 足够快,几乎可以立即执行命令,如果您不使用它们之间的延迟,您可能会收到错误或不连贯的值。 ..

希望它有所帮助,即使它不是您问题的确切答案。

【讨论】:

  • 例如,我需要 500 毫秒的延迟才能让一切工作 100%。
  • 这是一个非常糟糕的“解决方案”。回调用于传递响应。等待随机延迟确实很糟糕,因为您不知道操作是否真的完成(无线电干扰会增加不可预测的减速)。显然,它也没有尽快提供答案。
  • @Emil,感谢您在一个 6 岁的答案中的评论,这甚至不是被接受的答案!从那时起,BLE 框架中的许多事情都发生了变化……
  • 并非如此。这部分 API 现在看起来与 6 年前完全一样。
【解决方案5】:

不幸的是,当前 Android BLE 堆栈中的通知有点错误。有一些硬编码限制,即使使用单个设备,我也发现了一些稳定性问题。 (我曾经读到您只能有 4 条通知……不确定是针对所有设备还是每个设备。现在正在尝试查找该信息的来源。)

我会尝试切换到轮询循环(例如,每秒 1 次轮询有问题的项目),看看您是否发现稳定性有所提高。我还会考虑切换到不同的从设备(例如 HRM 或 TI SensorTag)以查看从端代码是否存在问题(除非您可以针对 iOS 或其他平台进行测试并确认它不是问题的一部分)。

编辑Reference for notification limitation

【讨论】:

  • 感谢您的回答。我认为slave端的代码还可以,因为测试是在iOS下运行的,最多有8个slave。我还尝试通过读取操作访问属性,结果相同。
  • 然后我会尝试快速更改轮询循环而不是通知,看看是否可以稳定连接。我正在尝试将等效的设置放在一起进行测试,但今天可能无法完成。
  • 谢谢。我真的很感激。
猜你喜欢
  • 1970-01-01
  • 2013-11-17
  • 1970-01-01
  • 1970-01-01
  • 2013-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多