iOSでのBluetooth通信入門 :CoreBluetooth プログラミングガイド 解説 : ペリフェラルが実行するタスク

bluetooth-ios_001

目次 >  ペリフェラルが実行するタスク

 Apple「CoreBluetoothプログラミングガイド」+独自解説


前章では、セントラル側の機器が実行する、主なBLEタスクについて説明しました。この章では、ペリフェラル側で実行する主なBLEタスクについて、Core Bluetoothフレームワークを使って実装する方法を説明します。ローカルデバイスにペリフェラルとしての役割を与えるアプリケーションを開発する際、以下に示すコード例が参考になるでしょう。具体的には次のようなタスクです。 ● ペリフェラルマネージャオブジェクトを起動する ● ローカルのペリフェラルが提供するサービスや特性を設定する ● サービスや特性を当該デバイスのローカルデータベースに登録する ● サービスをアドバタイズする ● 接続されたセントラルからの読み書き要求に応答する ● 特性値が変化したとき、あらかじめ申し込まれていたセントラルに通知する この章に示すコード例は簡略化してあり、そのまま実際のアプリケーションに組み込むことはできません。ペリフェラルとしての役割の実装に関するさらに進んだ話題(ヒント、技法、ベストプラクティス)を、章を改めて解説します。“Core Bluetoothのバックグラウンド処理(iOSアプリケーション用)” および“ローカルデバイスをペリフェラルとしてセットアップするためのベスト プラクティス” を参照してください。

ペリフェラルマネージャを起動する

ローカルデバイスにペリフェラルとしての役割を実装するためには、まず、ペリフェラルマネージャ(CBPeripheralManagerオブジェクトで表す)のインスタンスを生成、初期化しなければなりません。これは次のようにして、CBPeripheralManagerクラスのinitWithDelegate:queue:options:
メソッドで行います。

 myPeripheralManager =

[[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];

ここではselfを、ペリフェラルとしての役割に関係するイベントを受け取る、デリゲートオブジェク トとして設定しています。ディスパッチキューとしてnilを指定しているので、ペリフェラルマネー ジャはイベントのディスパッチ用に、メインキューを使います。

ペリフェラルマネージャは生成された後、デリゲートオブジェクトの peripheralManagerDidUpdateState:メソッドを呼び出します。このメソッドを、当該ペリフェラ ルでBLEが機能するように実装してください。実装方法について詳しくは、『CBPeripheralManagerDelegate Protocol Reference 』を参照してください。

サービスおよび特性をセットアップする

図 1-7 (14 ページ)に示したように、ローカル側ペリフェラルが提供するサービスや特性のデータ
ベースは、木のような構成になります。このような木構成になるよう、実際にサービスや特性をセットアップしなければなりません。しかしその前に、サービスや特性をどのように識別するか、理解しておきましょう。

サービスや特性はUUIDで識別する

ペリフェラルのサービスや特性は、Bluetoothに特有の128ビットUUIDで識別します。Core BluetoothフレームワークではUUIDを、CBUUIDオブジェクトで表します。あらゆるサービスや特性を網羅してはいませんが、Bluetooth分科会(SIG、Special Interest Group)はよく使われるUUIDを多数、使いやすいよう16ビットに短縮して定義、公開しています。たとえば心拍サービスの16ビットUUIDは180Dとなっています。これは同等の128ビットUUIDである「0000180D-0000-1000-8000-00805F9B34FB」を短縮したもので、Bluetooth Base UUID(Bluetooth 4.0仕様のVolume 3、Part F、Section 3.2.1)が基盤となっています。

CBUUIDクラスには、アプリケーション開発において、長い形式のUUIDを簡便に扱うためのファクトリメソッドがあります。たとえば、心拍サービスの128ビットUUIDを文字列としてコード中に記述する代わりに、次のように、UUIDWithStringを用い、定義済みの16ビットUUIDからCBUUIDオブジェクトを生成できます。

 CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @”180D”];

定義済みの16ビットUUIDからCBUUIDオブジェクトを生成する場合、Core Bluetoothは残りビットに Bluetooth Base UUIDを充塡します。

独自のサービスや特性を識別するUUIDを生成する

定義済みのBluetooth     UUIDでは識別できないサービスや特性があるかも知れません。その場合、独自 に128ビットUUIDを生成する必要があります。

128ビットUUIDは、コマンドラインユーティリティ「uuidgen」で簡単に生成できます。まず、「ター ミナル(Terminal)」のウインドウを開いてください。次に、UUIDで識別するべきサービスや特性ごと に、「uuidgen」コマンドを実行して、次のように、128ビットの値を表すハイフン区切りのASCII文 字列を得ます。

$ uuidgen

71DA3FD1-7E10-41C1-B16F-4430B506CDE7

このUUIDを引数としてUUIDWithStringメソッドを実行すれば、CBUUIDオブジェクトを生成できま す。

 CBUUID *myCustomServiceUUID =

[CBUUID UUIDWithString:@”71DA3FD1-7E10-41C1-B16F-4430B506CDE7″];

サービスおよび特性の木を構築する

サービスや特性のUUID(CBUUIDオブジェクトで表す)が得られたので、可変のサービスや特性を表 すオブジェクトを生成し、上述のような木構成にしていきます。たとえば次のように、特性のUUIDを 引数としてCBMutableCharacteristicのinitWithType:properties:value:permissions:メソッドを実行することにより、可変の特性を生成できます。

 myCharacteristic =

[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID

properties:CBCharacteristicPropertyRead

value:myValue permissions:CBAttributePermissionsReadable];

次に、生成した可変の特性に対して、プロパティ、値、操作権限を設定します。プロパティや操作権 限は、特性の値が読み取り/書き込み可能か、値の変化を通知するよう申し込むことができるか、な どを表します。この例では、セントラルが接続して値を読み取れるよう設定しています。可変の特性 に対して設定可能なプロパティや操作権限について詳しくは、『CBMutableCharacteristicClassReference 』 を参照してください。

注意: 特性の値を指定すると、この値はキャッシュの対象になり、読み取り可能であるよう
にプロパティや操作権限が設定されます。書き込みも可能にしたい、あるいは時間の経過に
伴い変化するようにしたい場合は、値としてnilを指定しなければなりません。こうしてお
けば、セントラルから読み取り/書き込み要求を受け取ったペリフェラルマネージャが、値
を動的に変更できるようになります。

可変の特性を生成したので、次にこれに関連づける可変のサービスを生成しましょう。これは
CBMutableServiceクラスのinitWithType:primary:メソッドで行います。

 myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];

この例では第2引数にYESを渡しているので、主サービスであることになります。主サービス(primaryservice)とは、デバイスの主たる機能のことです。他のサービスがこれを組み込む(参照する)ことも可能です。一方、副サービス(secondary service)とは、もっぱら他のサービスに組み込まれる(参照される)サービスのことです。たとえば、心拍モニタの主サービスは、心拍センサーから得た心拍データを公開することです。一方、センサーの電池の状態を知らせるのは副サービスです。

生成したサービスに特性を対応づけます。次のように、特性を表す配列を指定して行います。

 myService.characteristics = @[myCharacteristic];

サービスや特性を登録する

サービスと特性の木を構築できたので、次にこれを、デバイスのサービス/特性データベースに登録
します。これもCore Bluetoothフレームワークを使えば簡単に実行できます。次のように、
CBPeripheralManagerクラスのaddService:メソッドを実行してください。

 [myPeripheralManager addService:myService];

このようにしてサービスを公開すると、ペリフェラルマネージャは、デリゲートオブジェクトの
peripheralManager:didAddService:error:メソッドを呼び出します。エラーがあってサービスを
公開できない場合、その原因を調べて対処するように実装してください。

 – (void)peripheralManager:(CBPeripheralManager *)peripheral

didAddService:(CBService *)service

error:(NSError *)error {

if (error) {

NSLog(@”Error publishing service: %@”, [error localizedDescription]);

}

注意: サービスやこれに関連する特性をペリフェラルのデータベースに登録すると、当該
サービスはキャッシュされ、変更はできなくなります。

サービスをアドバタイズする

サービスや特性をデータベースに登録すれば、セントラルに向けてアドバタイズできるようになります。サービスのアドバタイズは次のように、CBPeripheralManagerクラスのstartAdvertising:メ
ソッドで行います。その際、アドバタイズデータの辞書(NSDictionaryのインスタンス)を引数と
して渡します。

 [myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :

@[myFirstService.UUID, mySecondService.UUID] }];

この例では、辞書のキーはCBAdvertisementDataServiceUUIDsKeyのみであり、その値は(アドバ
タイズするサービスのUUIDを表す)CBUUIDオブジェクトの配列(NSArrayのインスタンス)です。
アドバタイズデータの辞書に指定しうるキーについては、『CBCentralManagerDelegate Protocol
Reference 』の「Advertisement Data Retrieval Keys」に記載されている定数を参照してくださ
い。もっとも、ペリフェラルマネージャオブジェクトに対して指定できるのは、
CBAdvertisementDataLocalNameKeyとCBAdvertisementDataServiceUUIDsKeyの2つだけです。
ローカルのペリフェラルにあるデータのアドバタイズを始めると、ペリフェラルマネージャは、デリゲートオブジェクトのperipheralManagerDidStartAdvertising:error:メソッドを呼び出します。
エラーがあってサービスをアドバタイズできない場合、その原因を調べて対処するように実装してください。

 – (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral

error:(NSError *)error {

if (error) {

NSLog(@”Error advertising: %@”, [error localizedDescription]);

}

注意: データのアドバタイズは「ベストエフォート型」で行います。狭い空間内で、複数の
アプリケーションが同時にアドバタイズすることもありうるからです。詳しくは
『CBPeripheralManager Class Reference 』の、startAdvertising:メソッドに関する項を参照
してください。
アドバタイズの処理は、アプリケーションがバックグラウンド状態か否かによっても変わり
ます。詳しくは次の章、“Core Bluetoothのバックグラウンド処理(iOSアプリケーション
用)” (32 ページ)を参照してください。

データのアドバタイズを始めると、リモートのセントラルが検出し、接続を試みるようになります。

セントラルからの読み書き要求に応答する

リモートのセントラルと接続すると、読み取りや書き込みの要求が届くでしょう。これに適切に応答しなければなりません。以下の例はその手順を表しています。
接続したセントラルから、ある特性値の読み取り要求が届くと、ペリフェラルマネージャは、デリ
ゲートオブジェクトのperipheralManager:didReceiveReadRequest:メソッドを呼び出します。デ
リゲートメソッドにはこの要求が、CBATTRequestオブジェクトの形で渡されます。このオブジェクトには、要求を満たすために使える、さまざまなプロパティがあります。
たとえば、ある特性値の単純な読み取り要求の場合、このCBATTRequestオブジェクトのプロパティを使って、デバイスのデータベースに登録されている特性が、読み取り要求の指定と合致しているかどうか確認できます。実装は、たとえば次のようになるでしょう。

 – (void)peripheralManager:(CBPeripheralManager *)peripheral

didReceiveReadRequest:(CBATTRequest *)request {

if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {

if (request.offset > myCharacteristic.value.length) {

[myPeripheralManager respondToRequest:request

withResult:CBATTErrorInvalidOffset];

return;

}

指定されたオフセットが適切であれば、このオブジェクトのvalueプロパティ(当初の値はnil)に、
オフセットも考慮しながら、該当する特性の値を設定します。

 request.value = [myCharacteristic.value

subdataWithRange:NSMakeRange(request.offset,

myCharacteristic.value.length – request.offset)];

その後、リモートのセントラルに対して、要求が満たされた旨応答します。これは、
CBPeripheralManagerクラスのrespondToRequest:withResult:メソッドで、(valueプロパティを
更新した)requestを返すことにより行います。

 [myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];

respondToRequest:withResult:メソッドは、peripheralManager:didReceiveReadRequest:デリ
ゲートメソッドが呼び出される都度、1回ずつ実行します。

注意: 特性のUUIDが合致しなかった、あるいは何らかの理由で読み取りができなかった場
合、要求を満たすことはできません。そのままrespondToRequest:withResult:メソッド
で、原因を表す結果コードを返してください。指定できる結果コードの一覧が、『Core
Bluetooth Constants Reference 』の、CBATTError Constants列挙子の項にあります。

書き込み要求に対する応答も、特に難しいことはありません。接続したセントラルから、ある特性値の書き込み要求が届くと、ペリフェラルマネージャは、デリゲートオブジェクトの
peripheralManager:didReceiveWriteRequests:メソッドを呼び出します。デリゲートメソッドに
はこの要求が、1つ以上のCBATTRequestオブジェクトの配列として渡されます。それぞれが1件の書き込みを表します。書き込み要求に応じられることを確認した後、実際に書き込みを行います。

 myCharacteristic.value = request.value;

この例では省略していますが、特性値を書き込む際には、要求(request)のoffsetプロパティも考慮
してください。
読み取り要求に対する応答と同様、respondToRequest:withResult:メソッドは、
peripheralManager:didReceiveWriteRequests:デリゲートメソッドが呼び出される都度、1回ず
つ実行します。なお、このrespondToRequest:withResult:デリゲートメソッドには配列の形で複
数のCBATTRequestオブジェクトが渡されますが、peripheralManager:didReceiveWriteRequests:
メソッドの第1引数に指定するのは単一のオブジェクトです。次のように、request配列の先頭要素を渡してください。

 [myPeripheralManager respondToRequest:[requests objectAtIndex:0]

withResult:CBATTErrorSuccess];

注意: 複数件の要求がある場合、一括して扱います。いずれかの要求に応じることができな
ければ、ほかの要求にも応じるべきではありません。そのまま
respondToRequest:withResult:メソッドで、原因を表す結果コードを返してください。

特性値が変化したとき、あらかじめ申し込まれているセントラルに通知する

セントラルは、特性を指定して、値が変化したら通知するよう申し込むことができます(“特性の値
が変化したときに通知するよう申し込む” (20 ページ)を参照)。この場合、該当する特性値が変化したら通知を送らなければなりません。その方法を以下に示します。
セントラルが特性値の変化を通知するよう申し込むと、ペリフェラルマネージャは、デリゲートオブジェクトのperipheralManager:central:didSubscribeToCharacteristic:メソッドを呼び出します。

 – (void)peripheralManager:(CBPeripheralManager *)peripheral

central:(CBCentral *)central

didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {

NSLog(@”Central subscribed to characteristic %@”, characteristic);

これ以降、値が変化したらセントラルに通知することになります。

次に、新しい特性値を取得し、セントラルに通知します。これはCBPeripheralManagerクラスの
updateValue:forCharacteristic:onSubscribedCentrals:メソッドで行います。

 NSData *updatedValue = // 新しい特性値を取得

BOOL didSendValue = [myPeripheralManager updateValue:updatedValue

forCharacteristic:characteristic onSubscribedCentrals:nil];

このとき、最後の引数で、送信先のセントラルを指定できます。例のようにnilを指定した場合は、
申し込みを受け付けたセントラルすべてが対象になります(申し込みしていない機器は対象外)。
updateValue:forCharacteristic:onSubscribedCentrals:メソッドは、新しい特性値の送信に成功したかどうかを表すブール値を返します。送信処理のため内部的に使っているキューがいっぱいになった場合、戻り値はNOになります。その後、キューに余裕が生じて送信できるようになると、ペリフェラルマネージャは、デリゲートオブジェクトのperipheralManagerIsReadyToUpdateSubscribers:メソッドを呼び出します。再びupdateValue:forCharacteristic:onSubscribedCentrals:メソッドを使って、値を再送するように実装するとよいでしょう。

注意: 新しい特性値は、1パケット内に収容し、通知としてセントラルに送信します。新し
い特性値の全体を、(updateValue:forCharacteristic:onSubscribedCentrals:メソッドを1回だけ呼び出して、)1件の通知で返すのです。
しかし特性値の長さによっては、通知パケットに収容できず、全体を送信できないかも知れません。その場合は、セントラル側がCBPeripheralクラスのreadValueForCharacteristic:メソッドを呼び出して、値全体を取得するように実装してください。

 


※ このコンテンツはアップル社の提供する「CoreBluetoothプログラミングガイド」というPDFをwebの形式に変換したものをベースとしています。ページ中に含まれる図やテキストは「CoreBluetoothプログラミングガイド」からの引用が多く含まれます。PDFの形式ではなくウェブの形式で閲覧したい方への利便性を高めることや、「CoreBluetoothプログラミングガイド」だけではわかりにくいという箇所の捕捉を行うことが当コンテンツの主旨です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です