使用CoreBluetooth读取长特征值

时间:2013-10-09 19:02:55

标签: ios objective-c bluetooth core-bluetooth

我有一个包含图像数据的特征值。在外围设备中,我设置了如下值:

_photoUUID = [CBUUID UUIDWithString:bPhotoCharacteristicUUID];
_photoCharacteristic = [[CBMutableCharacteristic alloc] initWithType:_photoUUID
                                                          properties:CBCharacteristicPropertyRead
                                                               value:Nil
                                                         permissions:CBAttributePermissionsReadable];

我的理解是,当请求此值时,将调用didRecieveRequest回调:

-(void) peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {

    if ([request.characteristic.UUID isEqual:_photoUUID]) {
        if (request.offset > request.characteristic.value.length) {
            [_peripheralManager respondToRequest:request withResult:CBATTErrorInvalidOffset];
            return;
        }
        else {
            // Get the photos
            if (request.offset == 0) {
                _photoData = [NSKeyedArchiver archivedDataWithRootObject:_myProfile.photosImmutable];
            }

            request.value = [_photoData subdataWithRange:NSMakeRange(request.offset, request.characteristic.value.length - request.offset)];
            [_peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
        }
    }
}

这几乎来自Apple的文档。在didDiscoverCharacteristic回调的中央一侧,我有以下代码:

if ([characteristic.UUID isEqual:_photoUUID]) {
    _photoCharacteristic = characteristic;
    [peripheral readValueForCharacteristic:characteristic];
}

反过来调用didUpdateValueCorCharacteristic回调:

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    NSLog(@"updated value for characteristic");

    if ([characteristic.UUID isEqual:_photoUUID]) {
        NSArray * photos = [NSKeyedUnarchiver unarchiveObjectWithData:characteristic.value];
    }
}

所有回调都被调用,但是当我尝试重新构造数组时,它已被破坏,因为并非所有数据都被正确传输。我希望didRecieveReadRequest回调可以多次调用,每次都有不同的偏移量。然而它只被召唤一次。

我想知道是否有人知道我做错了什么?

2 个答案:

答案 0 :(得分:31)

我猜你正在突破特征长度的512字节限制。您需要转移订阅特征和处理更新以解决此问题:

在中央:

  1. 通过调用-[CBPeripheral setNotifyValue:forCharacteristic]订阅此特征(YES为通知值)。

  2. -peripheral:didUpdateValueForCharacteristic:error中,每次更新都将是要追加的数据,或者您选择在外围设备上使用以指示数据结束的内容(我使用空NSData这个)。更新您的-peripheral:didUpdateValueForCharacteristic:error代码,以便:

    • 如果您开始读取值,请为传入字节初始化接收器(例如NSMutableData)。
    • 如果您正在阅读某个值,则会附加到接收器。
    • 如果您看到EOD标记,则认为传输已完成。您可以通过调用-[CBPeripheral setNotifyValue:forCharacteristic]通知值为NO来取消订阅此状态下的特征。
  3. -peripheral:didUpdateNotificationStateForCharacteristic:error:是管理初始化以及稍后使用读取块的接收器的好地方。如果characteristic.isNotifying更新为YES,则您有新订阅;如果它已更新为NO,那么您已完成阅读。此时,您可以使用NSKeyedUnarchiver取消归档数据。

  4. 在外设上:

    1. -[CBMutableCharacteristic initWithType:properties:value:permissions]中,确保properties值包含CBCharacteristicPropertyNotify

    2. 使用-peripheralManager:central:didSubscribeToCharacteristic:启动数据的分块发送,而不是-peripheral:didReceiveReadRequest:result:

    3. 在分块数据时,请确保您的块大小不大于central.maximumUpdateValueLength。在iOS7上,在iPad 3和iPhone 5之间,我通常看到132个字节。如果您要发送到多个中心,请使用最不常见的值。

    4. 您需要查看-updateValue:forCharacteristic:onSubscribedCentrals的返回代码;如果底层队列备份,这将返回NO,并且你必须等待-peripheralManagerIsReadyToUpdateSubscribers:上的回调才能继续(我认为这是其他平滑API中的一个毛刺)。根据你如何处理这个问题,你可以把自己画成一个角落,因为:

    5. 如果你正在构建和发送你的块在外围设备用于其操作的同一队列上,并且做正确的事情并检查来自-updateValue:forCharacteristic:onSubscribedCentrals:的返回值,那么很容易支持你自己陷入非明显的僵局。您要么确保在每次调用-updateValue:forCharacteristic:onSubscribedCentrals:后都产生队列,要在与外围队列不同的队列上执行组块循环(-updateValue:forCharacteristic:onSubscribedCentrals:将确保其工作在正确的地点)。或者你可以变得更加漂亮;请注意这一点。

    6. 为了看到这一点,WWDC 2012高级核心蓝牙视频包含一个涵盖大部分内容的示例(共享VCards)。但是,它不会检查更新的返回值,因此它们完全避免了#4中的陷阱。

      希望有所帮助。

答案 1 :(得分:0)

我尝试了Cora Middleton所描述的方法,但无法使其起作用。如果我正确理解她的方法,她将通过更新通知发送所有部分数据。对我来说,问题似乎在于,如果这些通知中的值经常在短时间内连续更改,则不能保证每次更新都会被中央读取。

因此,由于该方法无效,因此我执行了以下操作:

  • 我使用一些特性来跟踪外围设备的状态。此特征将仅包含一些标志,并且如果一个或多个标志发生更改,则将发出通知。用户在外围设备上的交互会更改状态,并且外围设备上有一项操作,用户可以执行该操作来触发从连接的中心进行下载。

  • 将从中央下载的数据添加到外围设备的堆栈中。堆栈上的最后一项是终止符指示符(一个空的class C(object): myProp = None def __init__(self): assert self.myProp, 'you should set class property "name"' class D(C): def __init__(self): C.__init__(self) class E(C): myProp = 'e' def __init__(self): C.__init__(self) print(D.myProp) print(E.myProp) 对象)

  • 中央寄存器,用于接收上述状态特征的通知。如果设置了某些标志,则会触发下载。

  • 在外围设备上,每当我收到对某个特定特征的读取请求时,都会从堆栈中删除1个项目并返回该项目。

  • 在中央,我添加了读取请求返回的所有数据。如果检索到空数据值,则根据返回的数据创建一个对象(在我的情况下为JSON字符串)。

  • 在外围设备端,我还知道在返回空的NSData对象之后下载已完成,因此之后我可以再次更改外围设备的状态。