16

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

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

我的理解是,当请求这个值时,didReceiveReadRequest会调用回调:

-(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];
}

进而调用didUpdateValueForCharacteristic回调:

- (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回调被多次调用,每次都有不同的偏移量。但是它只被调用一次。

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

4

2 回答 2

35

我猜你正在碰到特征长度的 512 字节限制。您需要转到特征订阅和更新处理来解决这个问题:

在中央:

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

  2. -peripheral:didUpdateValueForCharacteristic:error中,每个更新要么是要附加的数据,要么是您选择在外围设备端使用的东西来指示数据结束(我NSData为此使用了一个空)。更新您的-peripheral:didUpdateValueForCharacteristic:error代码,以便:

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

在外围设备上:

  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:将确保其工作在正确的位置完成)。或者你可以变得更漂亮;请注意这一点。

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

希望有帮助。

于 2013-12-02T05:37:56.670 回答
1

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

所以因为这种方法不起作用,我做了以下事情:

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

  • 要从中央下载的数据被添加到外围设备的堆栈中。堆栈上的最后一项是终止指示符(一个空NSData对象)

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

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

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

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

于 2020-03-06T11:47:55.333 回答