Bluetooth LEとiBeaconを使った、すれ違い通信

22
Bluetooth LEiBeaconを使った、 すれ違い通信 2014.4.5 Bitz Co., Ltd. 村上幸雄

description

第65回 Cocoa勉強会 関東の発表資料。 Bluetooth LEとiBeaconを使った、すれ違い通信のサンプルコードを説明しています。 サンプルコードのURLは以下のとおり。 https://github.com/murakami/workbook/tree/master/ios/Wibree 【Cocoa勉強会】http://wp.cocoa-study.com/ 【Cocoa練習帳】http://www.bitz.co.jp/weblog/

Transcript of Bluetooth LEとiBeaconを使った、すれ違い通信

Bluetooth LEとiBeaconを使った、 すれ違い通信

2014.4.5 Bitz Co., Ltd. 村上幸雄

• 村上幸雄 • @m_yukio • ビッツ有限会社���http://www.bitz.co.jp/

今回のサンプルコードは以下のURL. https://github.com/murakami/workbook/tree/master/ios/Wibree

"   Bluetoothは、省電力な電波を使った無線通信で、最新の4.xでは対応機器は次の3つに分類されます。

分類 説明

Bluetooth Smart 4.0で追加されたBluetooth Low Energyのみ対応。

Bluetooth Smart Ready Bluetooth LEと従来のBluetoothの両方に対応。

Bluetooth 従来のBluetoothのみ対応。

"   iOSでBluetoothに対応する方法を整理してみます。

Bluetoothの種類 説明

従来のBLuetooth

MFi機器に対してExternal Accessory Frameworkで通信。

Game Kit

Bluetooth LE Core Bluetooth Framework

iOSでは、無関係の機器と自由に通信したいのならBluetooth LEという事になるかと思います。

すれ違い通信

識別子 識別子

識別子を交換

Bluetooth LE

Peripheral

Advertise

Central 発見

Service UUID

"  Peripheralが対応しているService UUIDをAdvertiseする。 "  Centralは探しているService UUIDがないか調査する。 "  Centralが見つけられたら、Peripheralに対して接続要求を出し、受けいれるとデータ通信が可能になる。

"  サンプルコードでは、識別子のCharacteristic UUIDを問い合わせて、識別子を受け取っている。

Service すれ違い通信

Characteristic 識別子

CBPeripheral

CBPeripheral

CBCharacteristic

SAMPLE CODE

Central Managerの用意 self.centralManager = [[CBCentralManager alloc] initWithDelegate:self ! queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];

Centralが使えるようになったので、Peripheralを探す - (void)centralManagerDidUpdateState:(CBCentralManager *)central !{ ! if (central.state != CBCentralManagerStatePoweredOn) { ! return; ! } ! [self scan]; ! !} !!- (void)scan !{ ! [self.centralManager scanForPeripheralsWithServices: ! @[[CBUUID UUIDWithString:WIBREE_SERVICE_UUID]] ! options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }]; !}

探すサービスUUID

Peripheralが見つかったので接続する - (void)centralManager:(CBCentralManager *)central ! didDiscoverPeripheral:(CBPeripheral *)peripheral ! advertisementData:(NSDictionary *)advertisementData ! RSSI:(NSNumber *)RSSI !{ ! if (self.discoveredPeripheral != peripheral) { ! self.discoveredPeripheral = peripheral; ! [self.centralManager connectPeripheral:peripheral options:nil]; ! } !}

サービスUUIDで検索

- (void)centralManager:(CBCentralManager *)central ! didConnectPeripheral:(CBPeripheral *)peripheral !{ ! [self.centralManager stopScan]; ! [self.data setLength:0]; ! peripheral.delegate = self; ! [peripheral discoverServices:@[[CBUUID UUIDWithString:WIBREE_SERVICE_UUID]]]; !} !

利用するサービスUUID

キャラクタリスティックスUUIDで検索 - (void)peripheral:(CBPeripheral *)peripheral ! didDiscoverServices:(NSError *)error !{ ! if (error) { ! [self cleanup]; ! return; ! } ! for (CBService *service in peripheral.services) { ! [peripheral discoverCharacteristics: ! @[[CBUUID UUIDWithString:WIBREE_CHARACTERISTIC_UUID]] ! forService:service]; ! } !} !!- (void)peripheral:(CBPeripheral *)peripheral !didDiscoverCharacteristicsForService:(CBService *)service ! error:(NSError *)error !{ ! if (error) { ! [self cleanup]; ! return; ! } ! for (CBCharacteristic *characteristic in service.characteristics) { ! if ([characteristic.UUID isEqual:[CBUUID UUIDWithString: ! WIBREE_CHARACTERISTIC_UUID]]) { ! [peripheral setNotifyValue:YES forCharacteristic:characteristic]; ! } ! } !}

利用するキャラクタリスティックスUUID

見つけたキャラクタリスティックスの値の通知を設定

識別子を受け取る

- (void)peripheral:(CBPeripheral *)peripheral !didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic ! error:(NSError *)error !{ ! NSString *stringFromData = [[NSString alloc] ! initWithData:characteristic.value encoding:NSUTF8StringEncoding]; ! if ([stringFromData isEqualToString:@"EOM"]) { ! NSString *uniqueIdentifier = [[NSString alloc] ! initWithData:self.data encoding:NSUTF8StringEncoding]; ! dispatch_async(dispatch_get_main_queue(), ^{ ! [self _notifyParserDidDiscoverUUID:uniqueIdentifier]; ! }); ! [peripheral setNotifyValue:NO forCharacteristic:characteristic]; ! [self.centralManager cancelPeripheralConnection:peripheral]; ! } ! [self.data appendData:characteristic.value]; !}

断片化された受信データを格納する

サンプルコードで決めた終端データを受け取る

値の通知の設定の結果を受け取る

- (void)peripheral:(CBPeripheral *)peripheral !didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic ! error:(NSError *)error !{ ! if (![characteristic.UUID isEqual:[CBUUID! UUIDWithString:WIBREE_CHARACTERISTIC_UUID]]) { ! return; ! } ! if (! characteristic.isNotifying) { ! [self.centralManager cancelPeripheralConnection:peripheral]; ! } !}

状態は、通知解除

Peripheral Managerの用意 self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self! queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; ![self.peripheralManager startAdvertising: ! @{ CBAdvertisementDataServiceUUIDsKey : ! @[[CBUUID UUIDWithString:WIBREE_SERVICE_UUID]] }];

Peripheralが利用可能 - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral !{ ! if (peripheral.state != CBPeripheralManagerStatePoweredOn) { ! return; ! } ! self.transferCharacteristic = [[CBMutableCharacteristic alloc] ! initWithType:[CBUUID UUIDWithString:WIBREE_CHARACTERISTIC_UUID] ! properties:CBCharacteristicPropertyNotify! value:nil! permissions:CBAttributePermissionsReadable]; ! CBMutableService *transferService = [[CBMutableService alloc] ! initWithType:[CBUUID UUIDWithString:WIBREE_SERVICE_UUID] ! primary:YES]; ! transferService.characteristics = @[self.transferCharacteristic]; ! [self.peripheralManager addService:transferService]; !}

見つけてもらうサービスUUID

利用可能なサービスUUIDとキャラクタリスティックスUUID

識別子の送信 - (void)peripheralManager:(CBPeripheralManager *)peripheral ! central:(CBCentral *)central !didSubscribeToCharacteristic:(CBCharacteristic *)characteristic !{ ! self.dataToSend = [[Document sharedDocument].uniqueIdentifier dataUsingEncoding:NSUTF8StringEncoding]; ! self.sendDataIndex = 0; ! [self sendData]; !} !!- (void)sendData !{ ! static BOOL sendingEOM = NO; ! if (sendingEOM) { ! BOOL didSend = [self.peripheralManager ! updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] ! forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil]; ! if (didSend) sendingEOM = NO; ! return; ! } ! if (self.sendDataIndex >= self.dataToSend.length) return; ! BOOL didSend = YES; ! while (didSend) { ! NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex; ! if (amountToSend > NOTIFY_MTU) amountToSend = NOTIFY_MTU; ! NSData *chunk = [NSData dataWithBytes:self.dataToSend.bytes+self.sendDataIndex length:amountToSend]; ! didSend = [self.peripheralManager updateValue:chunk ! forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil]; ! if (!didSend) return; ! NSString *stringFromData = [[NSString alloc] initWithData:chunk encoding:NSUTF8StringEncoding]; ! self.sendDataIndex += amountToSend; ! if (self.sendDataIndex >= self.dataToSend.length) { ! sendingEOM = YES; ! BOOL eomSent = [self.peripheralManager updateValue: ! [@"EOM" dataUsingEncoding:NSUTF8StringEncoding] ! forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil]; ! if (eomSent) sendingEOM = NO; ! return; ! } ! } !} !!- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral !{ ! [self sendData]; !} !

サンプルコードで決めた終端データを送る

"   バックグラウンドで動かすには

"   Info.plistのRequired background modesでApp communicates using CoreBluetoothを設定。

"   Info.plistのRequired background modesでApp shares data using CoreBluetoothを設定。

"   iBeaconを試してみる。

"   CoreBluetoothでビーコンを実装。

"   CoreLocationでビーコンを検出。

"   iBeaconはアドバタイズのパケットにiBeaconの情報を付与したもの。

"   ビーコンUUIDというのを定義するので、これをサンプルのサービスUUIDとした。

"   ペリフェラルにメジャー番号とマイナー番号を与えられるので、サンプルではこれを識別子とした。

/* CLLocationManagerを生成 */!self.locationManager = [[CLLocationManager alloc] init]; !self.locationManager.delegate = self; !if (! self.locationManager) { ! /* CLLocationManagerの初期化失敗 */! self.state = kBeaconCentralStateError; ! self.error = [self _errorWithCode:kBeaconCentralResponseParserGenericError! localizedDescription:@"CLLocationManagerの初期化に失敗しました。"]; ! return; !} !!/* ビーコン領域を生成 */!NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACON_SERVICE_UUID]; !self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid !                identifier:@"demo.Wibree.BeaconCentralResponseParser"]; !if (! self.beaconRegion) { ! /* ビーコン領域の初期化失敗 */! self.state = kBeaconCentralStateError; ! self.error = [self _errorWithCode:kBeaconCentralResponseParserGenericError! localizedDescription:@"ビーコン領域の初期化に失敗しました。"]; ! self.locationManager = nil; ! return; !} !!/* ビーコン領域の出入りを監視 */![self.locationManager startMonitoringForRegion:self.beaconRegion]; !!/* 距離を監視 */![self.locationManager startRangingBeaconsInRegion:self.beaconRegion]; !

iBeaconを探す

探すビーコンのUUID

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region !{ ! DBGMSG(@"%s", __func__); ! if ([self.delegate respondsToSelector:@selector(beaconCentralResponseParser:didEnterRegion:)]) { ! [self.delegate beaconCentralResponseParser:self didEnterRegion:region]; ! } !} !!- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region !{ ! DBGMSG(@"%s", __func__); ! if ([self.delegate respondsToSelector:@selector(beaconCentralResponseParser:didExitRegion:)]) { ! [self.delegate beaconCentralResponseParser:self didExitRegion:region]; ! } !} !!- (void)locationManager:(CLLocationManager *)manager ! didRangeBeacons:(NSArray *)beacons ! inRegion:(CLBeaconRegion *)region !{ ! DBGMSG(@"%s", __func__); ! if ([self.delegate respondsToSelector:@selector(beaconCentralResponseParser:didRangeBeacons:inRegion:)]) { ! [self.delegate beaconCentralResponseParser:self didRangeBeacons:beacons inRegion:region]; ! } !} !!- (void)locationManager:(CLLocationManager *)manager ! monitoringDidFailForRegion:(CLRegion *)region ! withError:(NSError *)error !{ ! DBGMSG(@"%s region:%@", __func__, region); ! DBGMSG(@"%s error:%@", __func__, error); !} !

検出 領域に入る

領域から外れる

距離を監視

/* CBPeripheralManagerを生成 */!self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self! queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; !if (! self.peripheralManager) { ! /* CBPeripheralManagerの初期化失敗 */! self.state = kBeaconPeripheralStateError; ! self.error = [self _errorWithCode:kBeaconPeripheralResponseParserGenericError! localizedDescription:@"CBPeripheralManagerの初期化に失敗しました。"]; ! return; !} !!/* ビーコン領域を生成 */!NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BEACON_SERVICE_UUID]; !self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid ! major:12345 ! minor:67890 ! identifier:@"demo.Wibree.BeaconCentralResponseParser"]; !if (! self.beaconRegion) { ! /* ビーコン領域の初期化失敗 */! self.state = kBeaconPeripheralStateError; ! self.error = [self _errorWithCode:kBeaconPeripheralResponseParserGenericError! localizedDescription:@"ビーコン領域の初期化に失敗しました。"]; ! self.peripheralManager = nil; ! return; !} !!/* 告知開始 */!NSDictionary *dictionary = [self.beaconRegion peripheralDataWithMeasuredPower:nil]; ![self.peripheralManager startAdvertising:dictionary];

見つけてほしいUUIDを告知

見つけてほしいビーコンのUUID

メジャー番号とマイナー番号

"   バックグラウンドで動かすには

"   Info.plistのRequired background modesでlocationを設定。

"   課題など

"   Bluetooth LEとiBeaconのバックグラウンド設定が衝突し、動作しなくなる。

"   iBeaconのペリフェラルはバックグラウンドでは動作しない。