目次 > セントラルが実行するタスク
目次
Apple「CoreBluetoothプログラミングガイド」+独自解説
セントラルが実行するタスク
BLE通信におけるセントラルとしての役割を実装したデバイスには、ほとんど常に必要となるさまざ まなタスクがあります。ペリフェラルを検出し、接続する、ペリフェラルが提供するデータを調査 し、やり取りするなどのタスクです。一方、ペリフェラルとして動作するデバイスにも多くのタスク が必要ですが、その内容は違います。サービスを公開、アドバタイズする、セントラルからの要求(読み書き、値の変化を通知するよう申し込み)に応答する、などのタスクです。
この章では、セントラル側で実行する主なタスクについて、Core Bluetoothフレームワークを使って実 装する方法を説明します。ローカルデバイスにセントラルとしての役割を与えるアプリケーションを 開発する際、以下に示すコード例が参考になるでしょう。具体的には次のようなタスクです。
- セントラルマネージャオブジェクトを起動する
- アドバタイズしているペリフェラルを検出、接続する
- 接続先のペリフェラルがどのようなデータを提供するか調査する
- ペリフェラルのサービスの特性値に対する読み書き要求を送信する
- 特性値が変化したときに通知するよう、ペリフェラルに申し込む
次の章では、ローカルデバイスにペリフェラルとしての役割を与えるアプリケーションの開発方法を 説明します。
この章に示すコード例は簡略化してあり、そのまま実際のアプリケーションに組み込むことはできま せん。セントラルとしての役割の実装に関するさらに進んだ話題(ヒント、技法、ベストプラクティ ス)を、章を改めて解説します。“Core Bluetoothのバックグラウンド処理(iOSアプリケーション 用)” および“リモートのペリフェラルとのやり取りに関するベストプラクティス” を参照してください。
セントラルマネージャを起動する
CBCentralManagerはローカルのセントラルを表すCore Bluetoothのオブジェクトなので、BLEトラン ザクションを実行するためには、そのインスタンスを生成、初期化しなければなりません。これは次のように、CBCentralManagerクラスのinitWithDelegate:queue:options:メソッドで行います。
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; |
ここではselfを、セントラルとしての役割に関係するイベントを受け取る、デリゲートオブジェクト として設定しています。ディスパッチキューとしてnilを指定しているので、セントラルマネージャ はイベントのディスパッチ用に、メインキューを使います。
セントラルマネージャは生成された後、デリゲートオブジェクトのcentralManagerDidUpdateState: メソッドを呼び出します。このメソッドを、当該セントラルでBLEが機能するように実装してくださ い。実装方法について詳しくは、『CBCentralManagerDelegate Protocol Reference 』を参照してくださ い。
アドバタイズしているペリフェラルを検出する
セントラル側でまず実行するタスクは、多くの場合、接続可能なペリフェラルの検出でしょう。“セ ントラルは、アドバタイズしているペリフェラルを検出し、接続する” で説明したように、アドバタイズはペリフェラルが自身の存在を他の機器に知らしめる重要な手段です。アドバタイ ズしているペリフェラルは、CBCentralManagerクラスのscanForPeripheralsWithServices:options:メソッドで検出できます。
[myCentralManager scanForPeripheralsWithServices:nil options:nil]; |
注意: 第1引数にnilを指定すれば、提供するサービスにかかわらず、検出したペリフェラ ルをすべて 返すようになります。実際のアプリケーションでは、サービスのUUID(汎用一 意識別子、Universally Unique IDentifier)を表す、CBUUIDオブジェクトの配列を指定すると よいでしょう。この場合、該当するサービスをアドバタイズしているペリフェラルのみが返 されるので、走査する範囲を絞り込めます。
scanForPeripheralsWithServices:options:メソッドでペリフェラルを検出すると、セントラル マネージャは見つかったペリフェラルそれぞれについて、デリゲートオブジェクトの centralManager:didDiscoverPeripheral:advertisementData:RSSI:メソッドを呼び出します。 このとき、ペリフェラルがCBPeripheralオブジェクトの形で渡されます。このデリゲートメソッド を次のように実装すれば、見つかったペリフェラルの名前を順に出力できます。
-(void)centralManager:(CBCentralManager *)central advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { NSLog(@”Discovered %@”, peripheral.name); … |
接続先のペリフェラルが見つかったら、省電力のため、他のペリフェラルの走査は停止してくださ い。
[myCentralManager stopScan]; NSLog(@”Scanning stopped”); |
検出したペリフェラルに接続する
必要なサービスをアドバタイズしているペリフェラルが見つかれば、次に接続を要求することになり ます。これはCBCentralManagerクラスのconnectPeripheral:options:メソッドで行います。接続先のペリフェラルを引数として、次のように呼び出すだけです。
[myCentralManager connectPeripheral:peripheral options:nil]; |
接続に成功した場合、セントラルマネージャはデリゲートオブジェクトの centralManager:didConnectPeripheral:メソッドを呼び出します。たとえば次のように実装すれ ば、接続が確立した旨のログが出力されるようになります。
ー(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@”Peripheral connected”); ・・・ |
ペリフェラルとのやり取りを始める前に、当該ペリフェラルのデリゲートを設定してください。イベ ントに応じ、コールバックとしてそのメソッドが呼び出されるようになります。
peripheral.delegate = self; |
接続したペリフェラルのサービスを検出する
ペリフェラルとの接続が確立したので、次にどのようなデータが提供されるか調査します。まず、 サービスの検出を行います。アドバタイズはデータ量に制限があるので、アドバタイズパケット中に はなかったサービスが見つかる可能性もあります。ペリフェラルが提供するサービスすべてのリスト は、CBPeripheralクラスのdiscoverServices:メソッドで取得できます。
[peripheral discoverServices:nil]; |
注意: 実際のアプリケーションでは、引数としてnilを指定し、提供するサービスをすべて 取得することは避けてください。不要なサービスが多数見つかる場合、電池と時間が無駄に なってしまいます。必要なサービスのUUIDを具体的に指定するとよいでしょう(“ペリフェ ラルのデータを的確に調査する” を参照)。
該当するサービスが見つかると、ペリフェラルマネージャ(接続先を表すCBPeripheralオブジェク ト)は、デリゲートオブジェクトのperipheral:didDiscoverServices:メソッドを呼び出します。 引数としてCBServiceオブジェクトの配列が渡されます。配列の各要素が、検出したサービスを表し ます。検出したサービスの配列にアクセスする、デリゲートメソッドのコード例を示します。
ー (void)peripheral:(CBPeripheral *)peripheraldidDiscoverServices:(NSError *)error {for (CBService *service in peripheral.services){ NSLog(@”Discovered service %@”, service);・・・}・・・ |
サービスの特性を検出する
必要なサービスが見つかったら、次にその特性をすべて検出します。次のように、該当するサービス を指定して、CBPeripheralクラスのdiscoverCharacteristics:forService:メソッドを実行するだけです。
NSLog(@”Discovering characteristics for service %@”, interestingService); [peripheral discoverCharacteristics:nil forService:interestingService]; |
注意: 実際のアプリケーションでは、引数としてnilを指定し、当該サービスの特性をすべ て 取得することは避けてください。不要な特性が多数見つかる場合、電池と時間が無駄に なってしまいます。必要な特性のUUIDを具体的に指定するとよいでしょう。
指定されたサービスの特性を検出すると、ペリフェラルマネージャは、デリゲートオブジェクトの peripheral:didDiscoverCharacteristicsForService:error:メソッドを呼び出します。引数と してCBCharacteristicオブジェクトの配列が渡されます。配列の各要素が、検出した特性を表しま す。検出した特性をログに出力する、デリゲートメソッドのコード例を示します。
ー (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@”Discovered characteristic %@”, characteristic); ・・・ } ・・・ |
特性の値を取得する
それぞれの特性には、サービスに関する「値」がひとつ対応しています。たとえば体温計サービスの「体温」特性には、摂氏で測った温度の値が対応している、といった具合です。特性の値は、直接読 み取るか、値が変化したときに通知するよう申し込むことにより取得できます。
特性の値を読み取る
該当するサービスの特性を指定して、CBPeripheralクラスのreadValueForCharacteristic:メソッ ドを実行することにより、その値を取得できます。
NSLog(@”Reading value for characteristic %@”, interestingCharacteristic); [peripheral readValueForCharacteristic:interestingCharacteristic]; |
値の読み取りが終了すると、ペリフェラルマネージャは、デリゲートオブジェクトのperipheral:didUpdateValueForCharacteristic:error:メソッドを呼び出します。読み取りに成 功した場合、特性のvalueプロパティとして値を取得できます。
ー (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSData *data = characteristic.value; // 必要に応じて値をパース ・・・ |
注意: 特性の値がすべて読み取り可能とは限りません。これは、特性プロパティの、 CBCharacteristicPropertyRead定数で表されるビットを参照することにより判断できま す(『CBCharacteristicClassReference 』を参照)。読み取り不可の値を読み取ろうとすると、 デリゲートオブジェクトのperipheral:didUpdateValueForCharacteristic:error:メ ソッドには、error引数を通してその旨のエラーコードが渡されます。
特性の値が変化したときに通知するよう申し込む
readValueForCharacteristic:メソッドで特性値を取得すれば充分なこともありますが、値が変化 しうる場合、この方法では非効率化も知れません。特性値の多くは、(たとえば心拍数のように)時 間とともに変化するので、その都度通知するよう申し込んでおく、という方法があります。すると、値が変化したとき、ペリフェラルから通知が届くようになります。
申し込みは、CBPeripheralクラスのsetNotifyValue:forCharacteristic:メソッドを、第1引数にYESを指定して呼び出すことにより行います。
[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic]; |
申し込み(またはその解除)を行うと、ペリフェラルマネージャは、デリゲートオブジェクトの peripheral:didUpdateNotificationStateForCharacteristic:error:メソッドを呼び出します。 申し込みに失敗したとき、その原因を出力するコード例を示します。
ー (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@”Error changing notification state: %@”, [error localizedDescription]); } ・・・ |
注意: 特性がすべて、この申し込みを受け付けるとは限りません。これは、特性プロパティ の、Characteristic Properties列挙子で定義されている所定のビットを参照することに より判断できます(詳しくは『CBCharacteristic Class Reference 』を参照)。
申し込みに成功すれば、値が変化する都度、ペリフェラルがアプリケーションに通知を送るようにな ります。するとペリフェラルマネージャは、デリゲートオブジェクトの peripheral:didUpdateValueForCharacteristic:error:メソッドを呼び出します。“特性の値を読 み取る” と同様にこのメソッドを実装すれば、新しい値を取得できます。
特性値を書き込む
用途によっては、特性値を書き込むことにも意味があるでしょう。たとえばデジタルサーモスタット とBLEでやり取りする場合、設定温度(何度に調整するか)の値を書き込むことができるかも知れません。特性値が書き込み可能である場合、値をNSDataのインスタンスとして表し、BPeripheralのwriteValue:forCharacteristic:type:メソッドを次のように実行します。
NSLog(@”Writing value for characteristic %@”, interestingCharacteristic); [peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic type:CBCharacteristicWriteWithResponse]; |
特性値を書き込む際には、その動作も指定できます。上の例では CBCharacteristicWriteWithResponseを指定しているので、正常に書き込めたかどうか、ペリフェ ラルがアプリケーションに通知するようになります。指定可能な動作については、『CBPeripheralClass Reference 』の、CBCharacteristicWriteType列挙子に関する項を参照してください。
CBCharacteristicWriteWithResponseを指定した場合、ペリフェラルマネージャは書き込み後、デ リゲートオブジェクトのperipheral:didWriteValueForCharacteristic:error:メソッドを呼び出 します。書き込みに失敗したとき、その原因を出力するコード例を示します。
ー (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@”Error writing characteristic value: %@”, [error localizedDescription]); } ・・・ |
注意: ここで指定できる動作に制限があるかも知れません。どのような動作が指定できるか は、Characteristic Properties列挙子で定義されている所定のビットを参照することに より判断できます(詳しくは『CBCharacteristic Class Reference 』を参照)。
※ このコンテンツはアップル社の提供する「CoreBluetoothプログラミングガイド」というPDFをwebの形式に変換したものをベースとしています。ページ中に含まれる図やテキストは「CoreBluetoothプログラミングガイド」からの引用が多く含まれます。PDFの形式ではなくウェブの形式で閲覧したい方への利便性を高めることや、「CoreBluetoothプログラミングガイド」だけではわかりにくいという箇所の捕捉を行うことが当コンテンツの主旨です。