0

背景资料:

我已经为 OSX 实现了一个蓝牙 LE 外设,它公开了两个特性(使用 CoreBluetooth)。一个是可读的,一个是可写的(都带有指示)。我在 iOS 上实现了一个蓝牙 LE Central,它将读取可读特性并写入可写特性。我已对其进行设置,以便每次读取特征值时都会更新该值(以类似于此示例的方式)。我通过这种设置获得的传输速率非常慢(以大约 340字节/秒的测量持续速度达到顶峰)。这个速度是实际数据,而不是包括数据包详细信息、ACK 等在内的衡量标准。

问题:

这种持续的速度太慢了。我考虑了两种解决方案:

  1. CoreBluetooth 中有一些我错过的参数可以帮助我提高速度。
  2. 我需要使用 IOBluetooth 类而不是 CoreBluetooth 来实现自定义蓝牙 LE 服务。

我相信,我已经用尽了选项 1。我没有看到任何其他可以调整的参数。我被限制为每条消息发送 20 个字节。其他任何事情,我都会在 iOS 设备上收到关于未知错误、不太可能的错误或值为“不长”的神秘错误。由于演示项目还指示 20 字节 MTU,我会接受这可能是不可能的。

所以我只剩下选项 2。我正在尝试以某种方式修改 OSX 上蓝牙 LE 的连接参数,希望能够提高传输速度(通过将最小和最大连接间隔分别设置为 20 毫秒和 40 毫秒 - 如以及每个连接间隔发送多个 BT 数据包)。看起来在 IOBluetooth 上提供我自己的 SDP 服务是在 OSX 上实现这一目标的唯一方法。问题在于如何做到这一点的文档可以忽略不计甚至不存在。

告诉我如何实现我自己的服务(尽管使用不推荐使用的 API),但是,它没有解释注册 SDP 服务所需的参数。所以我想知道:

  1. 我在哪里可以找到这本词典所需的参数?
  2. 如何定义这些参数以提供蓝牙 LE 服务?
  3. 有没有其他方法可以通过另一个框架在 OSX 上提供蓝牙 LE 外设(Python 库?虚拟机中的 Linux 可以访问蓝牙堆栈?我想完全避免这种情况。)
4

2 回答 2

6

我决定我最好的做法是尝试在 VM 中使用 Linux,因为有更多可用的文档,并且访问源代码有望保证我能找到解决方案。对于同样面临此问题的任何人,以下是如何在 OS X 上发出连接参数更新请求(有点)。

步骤1

安装 Linux 虚拟机。我将 Virtual Box 与 Linux Mint 15(64 位 Cinnamon)一起使用。

第2步

允许在您的 VM 中使用 OS X 蓝牙设备。尝试将蓝牙 USB 控制器转发到您的 VM 将给出错误消息。为此,您需要停止使用控制器的所有内容。在我的机器上,这包括从命令行发出以下命令:

sudo launchctl unload /System/Library/LaunchDaemons/com.apple.blued.plist

这将杀死 OS X 蓝牙守护进程。尝试从活动监视器中杀死 blued 只会导致它自动重新启动。

sudo kextunload -b com.apple.iokit.BroadcomBluetoothHostControllerUSBTransport

在我的 MacBook 上,我有一个 Broadcom 控制器,这是 OS X 用于它的内核模块。不要担心发出这些命令。要撤消更改,您可以关闭并重新启动机器(注意,在某些情况下,当使用 BT 控制器并进入错误状态时,我实际上必须让机器关闭大约 10 秒钟,然后重新启动以清除易失性存储器)。

如果运行这两个命令后仍然无法挂载蓝牙控制器,您可以运行kextstat | grep Bluetooth并查看其他与蓝牙相关的内核模块,然后尝试卸载它们。我有一些不需要卸载的名为 IOBluetoothFamily 和 IOBluetoothSerialManager。

第 3 步

启动您的 VM 并获取您的 Linux BT 堆栈。我从这里查看了 bluez Git repo 。我特别抓住了 5.14 发布标签,git checkout tags/5.14只是为了确保它至少是一个带标签的版本并且不太可能被破坏。5.14 是撰写此答案时的最新标签。

第4步

构建蓝兹。这是使用引导程序完成的,然后是配置,然后是 make 和 make install。我--prefix=/opt/bluez在配置上使用了标志来防止覆盖安装蓝牙堆栈。--enable-maintainer-mode此外,出于下一步中所述的原因,我使用了配置标志。您可能还需要使用--disable-systemd它来配置它。Bluez 有很多工具和实用程序,可用于各种用途。为了使用内置的蓝牙守护进程,您需要使用 . 停止系统守护进程sudo service bluetooth stop。然后,您可以使用sudo /opt/bluez/libexec/bluetooth/bluetoothd -n -d(这在非守护程序模式下启动并带有调试输出)启动构建的。

第 5 步

通过 bluez 运行您的 LE 服务。您可以查看bluez/plugins/gatt-example.c如何执行此操作。我直接修改了这一点,删除了不必要的代码,并使用电池服务代码作为我自己的服务和特性的模板。您需要重新编译 bluez 以将此代码添加到蓝牙守护程序。需要注意的一件事(导致我一两天的麻烦)是 iOS 缓存了 GATT 服务列表,并且在每个连接上都不会读取/刷新。如果您添加服务或特性或更改 UUID,您需要在 iOS 设备上禁用蓝牙,然后重新启用它。这在 Apples 文档中没有记录,并且没有编程方式来做到这一点。

第 6 步

不幸的是,这就是事情变得棘手的地方。Bluez 不支持使用其任何实用程序发出连接参数更新请求。我不得不自己写。我目前正在查看他们是否希望我的代码包含在 bluez 堆栈中。我目前无法发布代码,因为我需要先看看 bluez 开发人员是否对代码感兴趣,然后获得我工作场所的批准才能提供代码。但是,我目前可以解释我为启用支持所做的工作。

第 7 步

蓝牙标准做好准备。任何 4.0 或更高版本都将包含您需要的详细信息。阅读以下部分。

  • 见卷。2,E 部分,4.1 主机到控制器 HCI 流程。
  • 见卷。2,E 部分,5.4.2 用于 HCI ACL 数据包格式。
  • 见卷。3,A部分,4为信令包格式。
  • 见卷。3,A 部分,4.20 用于连接参数更新请求格式。

您基本上需要编写代码来格式化数据包,然后将它们写入 hci 设备。HCI ACL 数据包标头将包含 4 个字节。接下来是 4 个字节,用于信令命令的长度和通道 ID。然后是您的信号有效负载,在我的情况下为 12 个字节(用于连接参数更新请求)。

然后,您可以将它们写入类似于hci_send_cmdin的设备bluez/lib/hci.c。我将每个数据包标头作为它自己的结构进行处理,并将它们分别作为iovecs 写入设备。我将新函数放在 hci.c 文件中,并在 .c 文件中使用函数原型公开它bluez/lib/hci_lib.h。然后我进行了修改bluez/tools/hcitool.c以允许我从命令行调用此方法。就我而言,我这样做是为了使该命令与 lecup 命令几乎相同,因为它需要相同的参数(不能使用 lecup,因为它应该在主端调用,而不是从属端)。

重新编译所有这些,然后,瞧,我可以在 hcitool 上使用我的新命令将参数发送到蓝牙控制器。发送我的命令后,它会按预期与 iOS 设备重新协商。

注释

这个过程不适合胆小的人。希望将此或其他设置连接参数的方法添加到 bluez 以简化此过程。理想情况下,Apple 将允许在某个时候通过 CoreBluetooth 或 IOBluetooth 来实现这一点(这可能是可能的,但没有文档记录/很难做到,我放弃了 Apple 库)。我已经走过了兔子洞,了解了更多关于蓝牙规范的知识,然后我认为我必须简单地更改 MacBook 和 iPhone 之间的连接参数。希望这会在某些时候对某人有所帮助(即使是我回头检查我是如何做到的)。

我知道我在此省略了很多细节,以使其保持简短(即在 bluez 工具上的用法)。如果有不清楚的地方,请发表评论。

于 2014-02-10T13:36:46.393 回答
2

如果您使用 CoreBluetooth 实现外围设备,您可以通过调用-[CBPeripheralManager setDesiredConnectionLatency:forCentral:]Low、Medium 或 High(其中低延迟意味着更高的带宽)来请求一些自定义的连接参数。文档没有具体说明这是什么意思,所以我们必须自己测试一下。

在 OSX 外围设备上,当您将所需的延迟设置为低时,间隔仍然是 22.5 毫秒,这与 7.5 毫秒的最小值相差甚远。

  • 在 OSX Yosemite 10.10.4 上,这就是 CBPeripheralManagerConnectionLatency 值的含义:
    • :最小间隔:18 (22.5ms),最大间隔:18 (22.5ms),从属延迟:4 个事件,超时:200 (2s)。
    • :最小间隔:32 (40ms),最大间隔:32 (40ms),从属延迟:6 个事件,超时:200 (2s)
    • :最小间隔:160 (200ms),最大间隔:160 (200ms),从属延迟:2 个事件,超时:300 (3s)

这是我用来在 OSX 上运行 CBPeripheralManager 的代码。我使用BLE Explorer将 Android 设备用作中央设备,并将蓝牙流量转储到 Btsnoop 文件

// clang main.m -framework Foundation -framework IOBluetooth
#import <Foundation/Foundation.h>
#import <IOBluetooth/IOBluetooth.h>

@interface MyPeripheralManagerDelegate: NSObject<CBPeripheralManagerDelegate>
@property (nonatomic, assign) CBPeripheralManager* peripheralManager;
@property (nonatomic) CBPeripheralManagerConnectionLatency nextLatency;
@end
@implementation MyPeripheralManagerDelegate
+ (NSString*)stringFromCBPeripheralManagerState:(CBPeripheralManagerState)state {
    switch (state) {
        case CBPeripheralManagerStatePoweredOff: return @"PoweredOff";
        case CBPeripheralManagerStatePoweredOn: return @"PoweredOn";
        case CBPeripheralManagerStateResetting: return @"Resetting";
        case CBPeripheralManagerStateUnauthorized: return @"Unauthorized";
        case CBPeripheralManagerStateUnknown: return @"Unknown";
        case CBPeripheralManagerStateUnsupported: return @"Unsupported";
    }
}
+ (CBUUID*)LatencyCharacteristicUuid {
    return [CBUUID UUIDWithString:@"B81672D5-396B-4803-82C2-029D34319015"];
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    NSLog(@"CBPeripheralManager entered state %@", [MyPeripheralManagerDelegate stringFromCBPeripheralManagerState:peripheral.state]);
    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        NSDictionary* dict = @{CBAdvertisementDataLocalNameKey: @"ConnLatencyTest"};
        // Generated with uuidgen
        CBUUID *serviceUuid = [CBUUID UUIDWithString:@"7AE48DEE-2597-4B4D-904E-A3E8C7735738"];
        CBMutableService* service = [[CBMutableService alloc] initWithType:serviceUuid primary:TRUE];
        // value:nil makes it a dynamic-valued characteristic
        CBMutableCharacteristic* latencyCharacteristic = [[CBMutableCharacteristic alloc] initWithType:MyPeripheralManagerDelegate.LatencyCharacteristicUuid properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
        service.characteristics = @[latencyCharacteristic];
        [self.peripheralManager addService:service];
        [self.peripheralManager startAdvertising:dict];
        NSLog(@"startAdvertising. isAdvertising: %d", self.peripheralManager.isAdvertising);
    }
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
                                       error:(NSError *)error {

    if (error) {
        NSLog(@"Error advertising: %@", [error localizedDescription]);
    }
    NSLog(@"peripheralManagerDidStartAdvertising %d", self.peripheralManager.isAdvertising);

}
+ (CBPeripheralManagerConnectionLatency) nextLatencyAfter:(CBPeripheralManagerConnectionLatency)latency {
    switch (latency) {
        case CBPeripheralManagerConnectionLatencyLow: return CBPeripheralManagerConnectionLatencyMedium;
        case CBPeripheralManagerConnectionLatencyMedium: return CBPeripheralManagerConnectionLatencyHigh;
        case CBPeripheralManagerConnectionLatencyHigh: return CBPeripheralManagerConnectionLatencyLow;
    }
}
+ (NSString*)describeLatency:(CBPeripheralManagerConnectionLatency)latency {
    switch (latency) {
        case CBPeripheralManagerConnectionLatencyLow: return @"Low";
        case CBPeripheralManagerConnectionLatencyMedium: return @"Medium";
        case CBPeripheralManagerConnectionLatencyHigh: return @"High";
    }
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
    if ([request.characteristic.UUID isEqualTo:MyPeripheralManagerDelegate.LatencyCharacteristicUuid]) {
        [self.peripheralManager setDesiredConnectionLatency:self.nextLatency forCentral:request.central];
        NSString* description = [MyPeripheralManagerDelegate describeLatency: self.nextLatency];
        request.value = [description dataUsingEncoding:NSUTF8StringEncoding];
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
        NSLog(@"didReceiveReadRequest:latencyCharacteristic. Responding with %@", description);
        self.nextLatency = [MyPeripheralManagerDelegate nextLatencyAfter:self.nextLatency];
    } else {
        NSLog(@"didReceiveReadRequest: (unknown) %@", request);
    }
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyPeripheralManagerDelegate *peripheralManagerDelegate = [[MyPeripheralManagerDelegate alloc] init];
        CBPeripheralManager* peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:peripheralManagerDelegate queue:nil];
        peripheralManagerDelegate.peripheralManager = peripheralManager;
        [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}
于 2015-08-21T15:25:17.623 回答