web-dev-qa-db-ja.com

CoreBluetoothを使用して長い特性値を読み取る

画像のデータを含む特性値があります。ペリフェラルでは、次のように値を設定します。

_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コールバックは、毎回異なるオフセットで複数回呼び出されると思います。ただし、呼び出されるのは1回だけです。

私が間違っていることを誰かが知っているのだろうかと思っていましたか?

15
James Andrews

特徴的な長さの512バイトの制限にぶつかっていると思います。これを回避するには、特性のサブスクリプションと更新の処理に移行する必要があります。

中央:

  1. -[CBPeripheral setNotifyValue:forCharacteristic]を呼び出して特性をサブスクライブします(通知値としてYESを使用)。

  2. -peripheral:didUpdateValueForCharacteristic:errorでは、すべての更新は追加するデータか、データの終わりを示すために周辺側で使用することを選択したものになります(これには空のNSDataを使用します)。 -peripheral:didUpdateValueForCharacteristic:errorコードを次のように更新します。

    • 値の読み取りを開始する場合は、着信バイトのシンクを初期化します(例:NSMutableData)。
    • 値を読み取っている最中の場合は、シンクに追加します。
    • EODマーカーが表示されている場合は、転送が完了したと見なします。通知値NOを指定して-[CBPeripheral setNotifyValue:forCharacteristic]を呼び出すことにより、この状態で特性のサブスクライブを解除することをお勧めします。
  3. -peripheral:didUpdateNotificationStateForCharacteristic:error:は、チャンクを読み取るシンクの初期化とその後の使用を管理するのに適した場所です。 characteristic.isNotifyingYESに更新された場合、新しいサブスクリプションがあります。 NOに更新された場合は、読み終えています。この時点で、NSKeyedUnarchiverを使用してデータをアーカイブ解除できます。

周辺機器:

  1. -[CBMutableCharacteristic initWithType:properties:value:permissions]で、propertiesの値にCBCharacteristicPropertyNotifyが含まれていることを確認します。

  2. -peripheralManager:central:didSubscribeToCharacteristic:ではなく、-peripheral:didReceiveReadRequest:result:を使用してデータのチャンク送信を開始します。

  3. データをチャンク化するときは、チャンクサイズがcentral.maximumUpdateValueLength以下であることを確認してください。 iOS7では、iPad3とiPhone5の間で、私は通常132バイトを見てきました。複数のセントラルに送信する場合は、最も一般的でない値を使用してください。

  4. -updateValue:forCharacteristic:onSubscribedCentralsの戻りコードを確認する必要があります。基になるキューがバックアップされた場合、これはNOを返し、続行する前に-peripheralManagerIsReadyToUpdateSubscribers:でのコールバックを待つ必要があります(これは、他の点ではスムーズなAPIのバリの1つだと思います) 。これをどのように処理するかに応じて、次の理由で自分を隅に追いやることができます。

  5. 周辺機器が操作に使用しているのと同じキューでチャンクを作成して送信し、正しいことを実行して-updateValue:forCharacteristic:onSubscribedCentrals:からの戻り値を確認している場合、自明ではないデッドロックに簡単に戻ることができます。 。 -updateValue:forCharacteristic:onSubscribedCentrals:を呼び出すたびにキューを生成するようにし、ペリフェラルのキューとは異なるキューでチャンクループを実行する必要があります(-updateValue:forCharacteristic:onSubscribedCentrals:は、その作業が適切な場所)。または、もっと凝ってしまうかもしれません。これに注意してください。

これが実際に動作することを確認するために、WWDC 2012 Advanced Core Bluetoothビデオには、このほとんどをカバーする例(VCardの共有)が含まれています。ただし、更新時に戻り値をチェックしないため、#4の落とし穴を完全に回避できます。

お役に立てば幸いです。

32

Cora Middletonが説明したアプローチを試しましたが、うまくいきませんでした。私が彼女のアプローチを正しく理解していれば、彼女は更新通知を通じてすべての部分的なデータを送信します。私にとっての問題は、これらの通知の値が頻繁に短い連続で変更される場合、各更新がセントラルによって読み取られるという保証がないことであるように思われました。

そのアプローチが機能しなかったので、私は次のことをしました:

  • 周辺機器の状態を追跡するために使用するいくつかの特性があります。この特性には一部のフラグのみが含まれ、1つ以上のフラグが変更された場合に通知を送信します。周辺機器でのユーザーによる対話によって状態が変化し、接続されたセントラルからのダウンロードをトリガーするためにユーザーが実行できる周辺機器でのアクションが1つあります。

  • セントラルからダウンロードされるデータは、ペリフェラルのスタックに追加されます。スタックの最後の項目はターミネータインジケータ(空のNSDataオブジェクト)です。

  • 前述の状態特性の通知を受信する中央レジスタ。フラグが設定されている場合、ダウンロードがトリガーされます。

  • 周辺側では、特定の特性の読み取り要求を受信するたびに、スタックから1つのアイテムを削除し、このアイテムを返します。

  • 中央側では、読み取り要求から返されるすべてのデータを追加します。空のデータ値が取得された場合は、返されたデータからオブジェクトを作成します(私の場合はJSON文字列です)。

  • 周辺機器側では、空のNSDataオブジェクトを返した後にダウンロードが終了したこともわかっているので、後で周辺機器の状態をもう一度変更できます。

0