首页 文章

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

提问于
浏览
34

My Question is: Can Android 4.3 (client) have active connections with multiple BLE devices (servers)? If so, how can I achieve it?

What I did so far

我尝试使用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

    我怀疑每个人添加延迟只是让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行为也一样 .

  • 7



    上重新回答bluetooth-lowenergy问题:我仍在使用延迟 .

    这个概念:在每一个引发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) {}
        }
    }
    

    现在,如果一切都很好,你已经 Build 了连接,并且已经调用了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发送一个意图(这次使用不同的动作字符串),现在您可以开始阅读,编写和启用通知/指示...... P.S. If you are working with multiple devices, make sure that you start the reading, writing etc... stuff after all devices have reported that their services have been discovered.

    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

    我正在开发一个具有BLE功能的应用程序 . 我设法连接到多个设备并打开通知的方式是实现延迟 .

    所以我创建一个新线程(为了不阻止UI线程)并在新线程中连接并打开通知 .

    例如,在BluetoothDevice.connectGatt()之后;调用Thread.sleep();

    并为读/写和启用/不可用通知添加相同的延迟 .

    EDIT

    使用这样的等待,以便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;
        }
    
  • 21

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

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

  • 13

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

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

    EditReference for notification limitation

相关问题