Android 4.3:如何连接多个蓝牙低功耗设备

时间:2014-01-20 14:55:48

标签: android bluetooth-lowenergy

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

到目前为止我做了什么

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

我为奴隶编写了一个简单的服务器软件,通过BLE通知传输10000个20Byte数据包。我将我的Android应用程序基于Bluetooth SIG的Application Accelerator

它适用于一个设备,我可以在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。但是,如果有可能以及如何做到这一点,这些答案都没有让我清楚。

我非常感谢你的建议。

5 个答案:

答案 0 :(得分:21)

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

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确实使其更易于使用),我相信iOS的BLE API行为也相同。

答案 1 :(得分:13)

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

这个概念:在引发BluetoothGattCallback的每一个主要行动(例如,连接,服务发现,写作,阅读)之后,需要一个dealy。附:看一下BLE API level 19 sample for connectivity上的Google示例,了解广播应该如何发送并获得一些一般性的理解......等等。

首先,对于BluetoothDevices,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);
}

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

答案 2 :(得分:7)

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

答案 3 :(得分:2)

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几乎可以快速执行命令,如果您不在它们之间使用延迟,则可能会出现错误或不连贯值...

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

答案 4 :(得分:0)

不幸的是,当前Android BLE堆栈中的通知有点儿麻烦。有一些硬编码限制,即使使用单个设备,我也发现了一些稳定性问题。 (我一度读到你只能有4个通知......不确定这是在所有设备上还是在每台设备上。现在尝试找到该信息的来源。)

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

修改Reference for notification limitation