[iOS] Bluetooth LEでデータのやりとり

こんにちは、最近プロジェクトが忙しくて映画を見れていないmanaです。

今回は、iOSでBluetooth LEを使ってデータのやりとりを行いたいと思います。

Bluetooth LEの説明は「AndroidでiBeaconを発見する」を参照してください。

実際にデータのやりとりする場合は、役割としてペリフェラル、セントラルにわかれます。

セントラル(中心機器)、ペリフェラル(周辺機器)

機器には「役割」があります。その役割に「セントラル」と「ペリフェラル」があります。
例えば、iPhoneをセントラル、iBeaconなどの周辺装置をペリフェラルとするとイメージしやすいです。
ペリフェラルはアドバタイジング(「宣伝」)パケットを常に発信しており、セントラルは一定範囲内に入ると、ペリフェラルからの通知を受け取ることができます。

通信

ペリフェラルからセントラルにアドバタイジングパケットで通知はできますが、その後、相互間のデータをやりとりする場合はクライアント(セントラル)からサーバ(ペリフェラル)に接続が必要になります。
データのやりとりはGATT(Generic Attribute Profile)というプロファイル上で行います。
GATTはサーバがもつデータをキャラクタリスティック(「特性」)単位で読み書きする仕組みです。
データ構造的には、1つのプロファイル(「サービス」と呼ぶ場合もある)の下に複数のキャラクタリスティックが設定される構造になります。

プログラムからできる通信のやりとりは以下の3つになります。

・write
クライアントからサーバに書き込みデータを渡す
(クライアントからサーバに書き込みデータを送った後、クライアントに返答させることもできます。)

「処理フロー」
クライアント⇒サーバ

・read
クライアントからサーバに読み込みデータを要求

「処理フロー」
クライアント⇒サーバ⇒クライアント

・notifty/indicate
サーバからクライアントに通知
(indicateはサーバからクライアントに通知後、クライアントに返答が必要)

「処理フロー」
サーバ⇒クライアント

では実際のプログラムを見ていきましょう。

iOSで接続する

– ペリフェラルの実装

1. CBPeripheralManagerDelegateを継承する

2. サービスを登録する

    CBPeripheralManager *manager;
    CBMutableCharacteristic *mychar = [[CBMutableCharacteristic alloc]
                                 initWithType:[CBUUID UUIDWithString:@"自分で定義したキャラクタリスティックのUUID"]
                                 properties:CBCharacteristicPropertyNotify
                                 value:nil
                                 permissions:0];    
    CBUUID *serviceUUID = [CBUUID UUIDWithString:@"自分で定義したサービスのUUID"];
    CBMutableService *service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
    service.characteristics = [NSArray arrayWithObjects: mychar, nil];
    [manager addService:self.confirmService];

ここでキャラクリスティックにpropertiesやpermissonsが設定できます。
この値によってwrite、read、notifyが可能なキャラクリスティックを設定します。
上記の例ではnotifyのみを設定しています。
writeを使う場合にはCBAttributePermissionsWriteable、readを使う場合にはCBAttributePermissionsReadableの設定が必要になります。

3. アドバタイジングを開始する

    CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] 
                              initWithProximityUUID:[CBUUID UUIDWithString:@"自分で定義したUUID"]
                              major:1
                              minor:1
                              identifier:@"jp.co.techfirm.demo"];
    NSDictionary *beaconPeripheralData = [beaconRegion peripheralDataWithMeasuredPower:nil];
    [manager startAdvertising:beaconPeripheralData];

– セントラルの実装

1. CBCentralManagerDelegate、CBPeripheralDelegateを継承する

2. CBCentralManagerを作成する

     CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

3. スキャンを開始する

     NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
     forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
     [centralManager scanForPeripheralsWithServices:nil options:options];   

4. デバイスが発見された場合、下記のデリゲートに返却される

     - (void)centralManager:(CBCentralManager *)central 
         didDiscoverPeripheral:(CBPeripheral *)peripheral 
         advertisementData:(NSDictionary *)advertisementData 
         RSSI:(NSNumber *)RSSI {}

5. ペリフェラルに接続する

     didDiscoverPeripheralで見つかったペリフェラルにアクセスする。
     [centralManager connectPeripheral:peripheral options:nil];

6. CBPeripheralDelegateで継承したデリゲートに接続の結果が返ってくる

     - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
          // 検索したいサービスを呼び出す
          [peripheral discoverServices:@[[CBUUID UUIDWithString:@"定義したサービス"]]];
     }

7. サービスの検索結果が返ってくる

     - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
          for (CBMutableService * service in peripheral.services) {
               // 検索したいキャラクリスティックを呼び出す
               [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:@"定義したキャラクタリスティック"]] 
                forService:service];
          }
     }

8. キャラクリスティックの検索結果が返ってくる

     - (void)peripheral:(CBPeripheral *)peripheral 
           didDiscoverCharacteristicsForService:(CBService *)mservice error:(NSError *)error;{}

データのやりとり[write]

writeはセントラルが契機にデータのやりとりがはじまります。ペリフェラルはセントラルから書込みデータを受け取ります。

プログラムでは以下の処理フローでデータの書き込みを行っています。
・セントラル⇒ペリフェラル

– セントラルの実装(セントラルからペリフェラルにデータを書込)

1. CBPeripheralDelegateのデリゲートのdidDiscoverCharacteristicsForServiceで書きたいサービスを保存しておきます。

     CBMutableCharacteristic * mychar;
     for (CBMutableService * service in mservice) {
        for (CBMutableCharacteristic * cbchar in service.characteristics) {
           mychar = cbchar;
        }
     }

2. 送るデータを作成する

    NSString * sendMessage = @"sendData";
    NSData *data = [sendMessage dataUsingEncoding:NSUTF8StringEncoding];

3. データをペリフェラルに送る

    [peripheral writeValue:data forCharacteristic:mychar type:CBCharacteristicWriteWithoutResponse];

– ペリフェラルの実装(セントラルから書込を受けた時)

1. セントラルから通知を受ける

      - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{}

2. 書込みデータを取得する

     for (CBATTRequest *request in requests) {
         NSString *res = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
     }

データのやりとり[read]

readはセントラルが契機にデータのやりとりがはじまります。ペリフェラルは通知を受けてセントラルにデータを返却します。

プログラムでは以下の処理フローでデータの読込を行っています。
・セントラル⇒ペリフェラル⇒セントラル

– セントラルの実装(ペリフェラルに読込の要求)

1. CBPeripheralDelegateのデリゲートのdidDiscoverCharacteristicsForServiceで読みたいサービスを保存しておきます。

    CBMutableCharacteristic * mychar;
     for (CBMutableService * service in mservice) {
        for (CBMutableCharacteristic * cbchar in service.characteristics) {
           mychar = cbchar;
        }
     }

2. データをペリフェラルから読み込み

    [peripheral readValueForCharacteristic:mychar]

– ペリフェラルの実装(セントラルから読込要求を受ける)

1. セントラルから通知を受ける

     - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{}

2. データを設定する

     NSString * value = @"data";
     NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
     request.value = data;

3. セントラルにレスポンスを返却する

     [manager respondToRequest:request withResult:CBATTErrorSuccess];

– セントラルの実装(ペリフェラルからデータを受け取る)

1. ペリフェラルからデータが返答される

     - (void)peripheral:(CBPeripheral *)peripheral 
     didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{}

2. 送られてきたデータの取得

     NSData *data = characteristic.value;
     NSString *responseData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

データのやりとり[notification]

notificationはペリフェラルが契機にデータのやりとりがはじまります。セントラルはペリフェラルからデータを受け取ります。
プログラムでは以下の処理フローでデータの通知を行っています。
・ペリフェラル⇒セントラル

– セントラルの実装(ペリフェラルから通知を受けられるようにする)

1. CBPeripheralDelegateのデリゲートのdidDiscoverCharacteristicsForServiceで通知のサービスを保存しておきます。

     CBMutableCharacteristic * mychar;
     for (CBMutableService * service in mservice) {
        for (CBMutableCharacteristic * cbchar in service.characteristics) {
           mychar = cbchar;
        }
     }

2. 通知を設定します。

     [peripheralValue setNotifyValue:YES forCharacteristic:mychar];

– ペリフェラルの実装(セントラルに通知)

1. データを取得する

     NSString * value = @"data";
     NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];

2. セントラルに通知する

     [manager updateValue:data forCharacteristic:mychar onSubscribedCentrals:nil];

– セントラルの実装(ペリフェラルからデータを通知)

1. ペリフェラルから通知される

     - (void)peripheral:(CBPeripheral *)peripheral 
     didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{}

2. データの取得

     NSData *data = characteristic.value;
     NSString *responseData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

これでiOSでBluetooth LEのデータのやりとりができます。
これからウェアラブル機器連携でANCSを使う機会などがあると思いますが、ベースはBluetooth LEなのでしっかりと基礎を覚えておきましょう。

mana

mana の紹介

好きな映画は「影武者」で、好きな映画監督は「岩井俊二」です。 岩井作品では、「PiCNiC 」「スワロウテイル 」「花とアリス」が好きです。 (。・_・。)ノ
カテゴリー: iOS タグ: , , , , パーマリンク

コメントを残す

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