読者です 読者をやめる 読者になる 読者になる

こんにちは、@taku33です。 今回はiOSペリフェラルのデータ通信を中心に、実際の開発時に有益そうなお話をします。

設計

大量のデータをwirte/notifyする場合などは、 BluetoothSIGの仕様により一回の通信でやりとりできるデータのサイズは20バイほどが基本単位 になっています。
複数機種でのデータ依存性をなくす目的でなら、これに忠実に通信を設計(セントラル、ペリフェラルともに)した方が良いです。

元の大きなデータを20バイトほどに細切れにして、それを何回も送るというやり方です。

特定の相手を認識

近距離の複数の同種類のセントラル端末との通信を想定する場合、Writeリクエスト受信時に呼ばれるコールバック didReceiveWriteRequests requests: [CBATTRequest]
が呼ばれた際、writeリクエストしてきた相手(request.central)を保持しておきます。
そうすることで、その後の通信(notification、writeリクエストなど)はこの特定の相手とだけ通信するように判定できるようになります。

Notification

Notificationするためのメソッド updateValue:forCharacteristic:onSubscribedCentrals: は BLE 通信が混在している場合にNO を返すことがあります。
NOが呼ばれた場合は送信に失敗したことになります。
これは、iOSハードウェア自体が処理可能な量より多くのデータをアプリ上では生成できるため、らしいです。

実際にも大量のデータを送る場合などでNOが返ることがありますので、これを考慮した実装は必須かと思います。
対策としては再送可能になった時点で peripheralManagerIsReadyToUpdateSubscribers:
デリゲートで通知されるので、この通知を受け取ったら再送するようにしましょう。

また、Notificationがセントラル側からキャンセルされた場合に呼ばれるコールバック didUnsubscribeFromCharacteristic
が呼ばれた場合、Notification送信途中でもNotificationはできなくなるので注意してください。

通信データ構造

データを送る場合、ヘッダデータ + ペイロード で分けるとわかりやすいと思います。
ヘッダデータには通信サービス識別子、連番数などを定義するのが一般的かと思います。
ペイロード内での同類のデータを区切ったり全データの終了判定をするには、 番兵として0x00や0xffを使って判定することができます。 もちろんその場合は、余計なところで0x00や0xffを入れないことが前提となります。

逆にデータを受け取る場合は、防御的に入力値を判定するべきです。
例えば仕様で決めた範囲から受け取ったデータが外れるような場合はそのデータを弾くようにした方がいいです。 (サイズが小さすぎる、ヘッダデータの形式が異なる など)

また、実際の運用においては、通信するデータ構造の仕様や説明はドキュメントなどでも共有しておくと良いと思います。

おまけ swift スニペット

UInt8作成
let nullTerminal:UInt8 = UInt8.init(0)

UInt8からNSData作成
let data = NSData(bytes: [nullTerminal] as [UInt8], length: 1)

NSDataをUInt8型Arrayに変換
var arr = Array(UnsafeBufferPointer(start: UnsafePointer(data.bytes), count: data.length))

UInt8をそのまま文字列に変換
String(format:"%02x", nullTerminal)