Android 4.3:如何连接到多个低功耗蓝牙设备
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21237093/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android 4.3: How to connect to multiple Bluetooth Low Energy devices
提问by Andreas Mueller
My Question is: Can Android 4.3 (client) have active connections with multiple BLE devices (servers)? If so, how can I achieve it?
我的问题是:Android 4.3(客户端)能否与多个 BLE 设备(服务器)建立活动连接?如果是这样,我该如何实现?
What I did so far
到目前为止我所做的
I try to evaluate what throughput you can achieve using BLE and Android 4.3 BLE API. In addition I also try to find out how many devices can be connected and active at the same time. I use a Nexus 7 (2013), Android 4.4 as master and TI CC2540 Keyfob as slaves.
我尝试评估使用 BLE 和 Android 4.3 BLE API 可以实现的吞吐量。此外,我还尝试找出可以同时连接和激活的设备数量。我使用 Nexus 7 (2013),Android 4.4 作为主设备,TI CC2540 Keyfob 作为从设备。
I wrote a simple server software for the slaves, which transmits 10000 20Byte packets through BLE notifications. I based my Android App on the Application Acceleratorfrom the Bluetooth SIG.
我为从机编写了一个简单的服务器软件,它通过 BLE 通知传输 10000 个 20Byte 数据包。我的 Android 应用程序基于蓝牙 SIG的应用程序加速器。
It works well for one device and I can achieve around 56 kBits payload throughput at a Connection Interval of 7.5 ms. To connect to multiple slaves I followed the advice of a Nordic Employee who wrote in the Nordic Developer Zone:
它适用于一台设备,我可以在 7.5 毫秒的连接间隔下实现大约 56 kBits 的有效负载吞吐量。为了连接到多个奴隶,我遵循了北欧开发人员区中写道的北欧员工的建议:
Yes it's possible to handle multiple slaves with a single app. You would need to handle each slave with one BluetoothGatt instance. You would also need specific BluetoothGattCallback for each slave you connect to.
是的,可以使用单个应用程序处理多个从站。您需要使用一个 BluetoothGatt 实例来处理每个从设备。您还需要为您连接的每个从站提供特定的 BluetoothGattCallback。
So I tried that and it partly works. I can connect to multiple slaves. I can also register for notifications on multiple slaves. The problem begins when I start the test. I receive at first notifications from all slaves, but after a couple Connection Intervals just the notifications from one device come trough. After about 10 seconds the other slaves disconnect, because they seem to reach the connection time-out. Sometimes I receive right from the start of the test just notifications from one slave.
所以我试过了,它部分有效。我可以连接到多个从站。我还可以注册多个奴隶的通知。当我开始测试时,问题就开始了。我首先收到来自所有从属设备的通知,但经过几次连接间隔后,只有来自一个设备的通知才通过。大约 10 秒后,其他从站断开连接,因为它们似乎已达到连接超时。有时我从测试一开始就收到来自一个奴隶的通知。
I also tried accessing the attribute over a read operation with the same result. After a couple of reads just the answers from one device came trough.
我还尝试通过读取操作访问属性,结果相同。读了几遍后,一个设备的答案就出来了。
I am aware that there are a few similar questions on this forum: Does Android 4.3 support multiple BLE device connections?, Has native Android BLE GATT implementation synchronous nature?or Ble multiple connection. But none of this answers made it clear for me, if it is possible and how to do it.
我知道这个论坛上有几个类似的问题:Android 4.3 是否支持多个 BLE 设备连接?,有原生Android BLE GATT实现同步性吗?或Ble 多重连接。但是这些答案都没有让我清楚,如果可能以及如何去做。
I would be very grateful for advice.
我将非常感谢您的建议。
采纳答案by Timmmm
I suspect everyone adding delays is just allowing the BLE system to complete the action you have asked before you submit another one. Android's BLE system has no form of queueing. If you do
我怀疑每个添加延迟的人只是让 BLE 系统在您提交另一个操作之前完成您要求的操作。Android 的 BLE 系统没有排队的形式。如果你这样做
BluetoothGatt g;
g.writeDescriptor(a);
g.writeDescriptor(b);
then the first write operation will immediately be overwritten with the second one. Yes it's really stupid and the documentation should probably actually mention this.
那么第一个写操作将立即被第二个写操作覆盖。是的,这真的很愚蠢,文档可能实际上应该提到这一点。
If you insert a wait, it allows the first operation to complete before doing the second. That is a huge ugly hack though. A better solution is to implement your own queue (like Google should have). Fortunately Nordic have released one for us.
如果您插入等待,它允许在执行第二个操作之前完成第一个操作。不过,这是一个巨大的丑陋黑客。更好的解决方案是实现您自己的队列(就像 Google 应该有的那样)。幸运的是,北欧已经为我们发布了一款。
Edit: By the way this is the universal behaviour for BLE APIs. WebBluetooth behaves the same way (but Javascript does make it easier to use), and I believe iOS's BLE API also behaves the same.
编辑:顺便说一下,这是 BLE API 的通用行为。WebBluetooth 的行为方式相同(但 Javascript 确实使它更易于使用),而且我相信 iOS 的 BLE API 的行为也相同。
回答by Rain
Re visting the bluetooth-lowenergyproblem on android: I am still using delays.
The concept: after every major action that provokes the BluetoothGattCallback(e.g. conenction, service discovery, write, read) a dealy is needed. P.S. have a look at Google example on BLE API level 19 sample for connectivityto understand how Broadcasts should be sent and get some general understanding etc...
概念:在引发BluetoothGattCallback 的每个主要操作(例如连接、服务发现、写入、读取)之后,都需要一个交易。PS 看看谷歌示例上的 BLE API 级别 19 示例以了解如何发送广播并获得一些一般性的理解等...
Firstly, scan(or scan) for BluetoothDevices, populate the connectionQueuewith desired devices and call initConnection().
首先,扫描(或扫描)蓝牙设备,使用所需设备填充connectionQueue并调用initConnection()。
Have a look on the following example.
看看下面的例子。
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) {}
}
}
Now if all is good, you have made connections and BluetoothGattCallback.onConnectionStateChange(BluetoothGatt gatt, int status, int newState)has been called.
现在,如果一切顺利,您已经建立了连接并且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(intent)might need to be done like this:
PS sendBroadcast(intent)可能需要这样做:
Context context = activity.getBaseContext();
context.sendBroadcast(intent);
Then the broadcast is received by BroadcastReceiver.onReceive(...)
然后广播被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
}
....
}
}
After doing whatever you want in the broadcast receiver, here is how I continue:
在广播接收器中做任何你想做的事情后,这是我继续的方式:
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){}
}
}
Again, after a successful service discovery, BluetoothGattCallback.onServicesDiscovered(...)is called. Again, I send an intent to the BroadcastReceiver (this time with different action String) and it is now that you can start reading, writing and enabling notifications/indications... 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.
同样,在成功发现服务后,BluetoothGattCallback.onServicesDiscovered(...)被调用。再次,我向 BroadcastReceiver 发送一个意图(这次使用不同的操作字符串),现在您可以开始读取、写入和启用通知/指示... 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 will have all your incoming data from the BLE sensor. A good practice is to send a broadcast with the data to your BroadcastReceiver and handle it over there.
BluetoothGattCallback 将拥有来自 BLE 传感器的所有传入数据。一个好的做法是将带有数据的广播发送到您的 BroadcastReceiver 并在那里处理它。
回答by Rain
I am developing an app with BLE features myself. The way I managed to connect to multiple devices and turn on notifications was to implement delays.
我自己正在开发具有 BLE 功能的应用程序。我设法连接到多个设备并打开通知的方式是实现延迟。
So I make a new thread (in order not to block UI thread) and in the new thread connect and turn on notifications.
所以我创建了一个新线程(为了不阻塞 UI 线程)并在新线程中连接并打开通知。
For example, after BluetoothDevice.connectGatt(); call Thread.sleep();
例如,在 BluetoothDevice.connectGatt(); 之后 调用 Thread.sleep();
And add the same delay for read/write and enable/dissable notifications.
并为读/写和启用/禁用通知添加相同的延迟。
EDIT
编辑
Use wait like this so that Android dindn't reaise ANR
像这样使用等待,这样 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;
}
回答by kodartcha
Rain is right in his answer, you need delays for pretty much everything when you work with BLE in Android. I developed several apps with it and it is really necessary. By using them you avoid a lot of crashes.
Rain 的回答是正确的,当您在 Android 中使用 BLE 时,几乎所有的事情都需要延迟。我用它开发了几个应用程序,这真的很有必要。通过使用它们,您可以避免很多崩溃。
In my case, I use delays after every read/write command. Doing so, you ensure you receive the response from the BLE device almost always. I do something like this: (of course everything is done in a separate thread to avoid to much work on the main thread)
就我而言,我在每个读/写命令后使用延迟。这样做,您可以确保您几乎总是收到来自 BLE 设备的响应。我做这样的事情:(当然,一切都在一个单独的线程中完成,以避免在主线程上做太多工作)
readCharacteristic(myChar);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
myChar.getValue();
or:
或者:
myChar.setValue(myByte);
writeCharacteristic(myChar);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
This is really useful when you read/write several characteristics in a row... As Android is enough fast to execute the commands almost instantly, if you don't use a delay between them you may get errors or incoherent values...
当您连续读/写几个特征时,这非常有用......由于Android足够快,几乎可以立即执行命令,如果您不使用它们之间的延迟,您可能会得到错误或不连贯的值......
Hope it helps even if it is not exactly the answer to your question.
希望它有所帮助,即使它不完全是您问题的答案。
回答by Ben Von Handorf
Unfortunately notifications in the current Android BLE stack are a bit buggy. There are some hardcoded limits and I've found some stability issues even with a single device. (I read at one point that you could only have 4 notifications... not sure if that's across all devices or per device. Trying to find the source for that info now.)
不幸的是,当前 Android BLE 堆栈中的通知有点问题。有一些硬编码限制,我发现即使使用单个设备也存在一些稳定性问题。(我曾读到你只能有 4 个通知......不确定这是跨所有设备还是每台设备。现在试图找到该信息的来源。)
I would try switching to a polling loop (say, poll the items in question 1/sec) and seeing if you find your stability increases. I would also consider switching to a different slave device (say a HRM or the TI SensorTag) to see if there is perhaps an issue with the slave-side code (unless you can test that against iOS or another platform and confirm it isn't part of the issue).
我会尝试切换到轮询循环(例如,以 1/秒的速度轮询有问题的项目),看看您是否发现稳定性有所提高。我还会考虑切换到不同的从设备(例如 HRM 或 TI SensorTag)以查看从端代码是否存在问题(除非您可以针对 iOS 或其他平台对其进行测试并确认它不是问题的一部分)。
Edit: Reference for notification limitation
编辑:通知限制参考