MacとiOSをBLEで連携 - Peripheral編
諸事情により、OpenCVはいったんお休みして、MacとiPhoneをBluetooth Low Energy(BLE)で連携してみることにしました。
やりたいこと
MacとiPhoneをBLEでつなぎ、iPhoneはボタンを押したときに、Macは一定時間(1秒)ごとに乱数を生成してそれぞれ送信しあう。
前提
- BLEを使用するために、MacではIOBluetooth.frameworkを、iOSではCoreBluetooth.frameworkを使用します。
- BLEで連携を行う場合、2台の端末はCentralとPeripheralに別れ、今回はiPhoneをCentralに、MacをPeripheralとしています。
Central : Peripheral端末の探索、書き込み・読み込みリクエストの送信などを行う。 Peripheral : Centralが探索可能にするためAdvertisingを行ったり、リクエストへの応答などを行う。
Peripheral
今回はMac側、Peripheralについて。
BLEのコントロール部分はPeripheralControllerで、UIなどその他の部分はAppDelegateで実行しています。
PeripheralController.h
#import < Foundation / Foundation.h > @interface PeripheralController : NSObject - (void) initPeripheralController; - (void) close; - (NSString *) getCentralValue; - (BOOL) updatePeripheralValue:(int) intSendData; @end
PeripheralController.m
#import < IOBluetooth / IOBluetooth.h > #import "PeripheralController.h" #define CHARACTERISTIC_UUID @"7F855F82-9378-4508-A3D2-CD989104AF22" #define SERVICE_UUID @"2B1DA6DE-9C29-4D6C-A930-B990EA2F12BB" @interface PeripheralController() < CBPeripheralManagerDelegate > @property (strong, nonatomic) CBPeripheralManager *cpmPeripheralManager; @property (strong, nonatomic) CBMutableCharacteristic *cmcCharacteristic; @property (strong, nonatomic) NSMutableData *mdtSendValue; @property (strong, nonatomic) NSString *strGotValue; @property (nonatomic) BOOL isSubscribed; @end @implementation PeripheralController - (void) initPeripheralController { // PeripheralManagerの初期化. Delegateにselfを設定し、起動時にBluetoothがOffならアラートを表示する. _cpmPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:@{CBPeripheralManagerOptionShowPowerAlertKey:@YES}]; _isSubscribed = NO; _strGotValue = @""; } - (void) close { // Advertisingをストップ. [self.cpmPeripheralManager stopAdvertising]; } - (NSString *) getCentralValue { // Centralの書き込みリクエストで受け取った値を返す. return _strGotValue; } // Bluetoothの状態が変わったら実行される. - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral { // BluetoothがOffならリターン. if (peripheral.state != CBPeripheralManagerStatePoweredOn) { return; } // Characteristicの初期化. ここで設定したIDをもとにCentralが検索する. // Centralからの読み出し、書き込みを可能にする. _cmcCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:CHARACTERISTIC_UUID] properties:(CBCharacteristicPropertyNotify|CBCharacteristicPropertyRead|CBCharacteristicPropertyWrite) value:nil permissions:(CBAttributePermissionsReadable|CBAttributePermissionsWriteable)]; // Serviceの初期化. ここで設定したIDをもとにCentralがServiceを検索する. CBMutableService *cmsService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:SERVICE_UUID] primary:YES]; // ServiceのCharacteristicを設定する. cmsService.characteristics = @[_cmcCharacteristic]; // PeripheralManagerにServiceを追加する. [_cpmPeripheralManager addService:cmsService]; // Advertisingの開始.Centralから探索可能にする. [_cpmPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @CBUUID UUIDWithString:SERVICE_UUID }]; } // Peripheralで設定した値を更新したら、Centralに通知がいくようにする(Centralからのリクエストで実行). - (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic { _isSubscribed = YES; } - (BOOL) updatePeripheralValue:(int) intSendData { if(_isSubscribed) { // Centralからの読み出しリクエストのための値を更新する. _mdtSendValue = (NSMutableData *)[[NSString stringWithFormat:@"%d", intSendData] dataUsingEncoding:NSUTF8StringEncoding]; if([_cpmPeripheralManager updateValue:_mdtSendValue forCharacteristic:_cmcCharacteristic onSubscribedCentrals:nil]) { return YES; } } return NO; } // Centralから書き込みリクエストを受けたら実行. - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests { for (CBATTRequest *rqs in requests) { if ([_cmcCharacteristic isEqual:rqs.characteristic]) { _strGotValue = [[NSString alloc] initWithData:rqs.value encoding:NSUTF8StringEncoding]; [_cpmPeripheralManager respondToRequest:rqs withResult:CBATTErrorSuccess]; } } } @end
UUID
連携をするときにCentral端末はPeripheral端末を見つけたあと、[Characteristic]と[Service]を探索します。そのときに必要になるのがUUIDです。
ただ数字を羅列すれば良いわけではないのですが、Terminalを開いて「uuidgen」とコマンドを入力すれば自動生成されます。便利です。
このIDは接続する2台の端末で一致している必要があります。
リクエスト
Central端末からリクエストがあった場合に応答します。今回は書き込み(write)と通知(Nortify)を使用しています。
書き込みリクエストがあった場合はdidReceiveWriteRequestsが、通知リクエストはdidSubscribeToCharacteristicが実行されます。
また、通知リクエストは値を更新すると(updatePeripheralValueで実行)、Central端末に自動で通知が届き、更新した値を渡すことができます。
なお、Central端末からリクエストできるようにするため、権限を設定しておく必要があります(peripheralManagerDidUpdateStateの、_cmcCharacteristicの初期化時に実行)。
課題
initPeripheralControllerでCBPeripheralManagerの初期化時に、Appを起動したときにアラートが表示されるようオプションを追加していますが、Bluetoothオフの状態で実行してもアラートは表示されません。
CoreBluetoothの場合、Appがバックグラウンドでも動作するよう、CBPeripheralManagerのオプションとして「CBPeripheralManagerOptionRestoreIdentifierKey」が設定できますが、IOBluetoothの場合は使用できないようです。バックグラウンドでの動作は不可能なのか、それとも別に何か設定方法があるのでしょうか。
UI部分は次回。
参考
BLE
- BTLE Central Peripheral Transfer
- CoreBluetoothの概要 - Reinforce-Lab.'s Blog
- Peripheralの実装 - エンジニアの備忘録
- Core Bluetooth プログラミングガイド - Apple Developer
- 【連載】Bluetooth LE (2) iOS デバイスで Bluetooth LE 機器を使う - フェンリル デベロッパーズブログ