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

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

MacとiOSをBLEで連携 - Peripheral編

諸事情により、OpenCVはいったんお休みして、MaciPhoneBluetooth Low Energy(BLE)で連携してみることにしました。

やりたいこと

MaciPhoneを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

参考書籍

上を目指すプログラマーのためのiPhoneアプリ開発テクニック iOS 7編