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

Preview:

DESCRIPTION

第1回 MOSA Tech Meetingの発表資料。 GitHub https://github.com/murakami/workbook/tree/master/ios/Wibree Cocoa練習帳 http://www.bitz.co.jp/weblog/

Citation preview

BLUETOOTH LEを使った、 すれ違い通信

2014.2.25 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 Smart4.0で追加されたBluetooth Low Energyのみ対応。

Bluetooth Smart ReadyBluetooth 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を問い合わせて、識別子を受け取っている。

SAMPLE CODE

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

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で検索- (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]; } } }

識別子を受け取る- (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

識別子の送信- (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を設定。

Core Bluetooth使用時の課題

数分単位の周期でしたバックグラウンドで動かないし、動いても数秒。

なので、実際のすれ違い通信は難しい。

iBeaconを試してみる。

CoreBluetoothでビーコンを実装。

CoreLocationでビーコンを検出。

常に検出できるみたい。

/* 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

iBeacon使用時の課題

個体の識別子はmajorとminorの番号。

ただし、Sample Codeでは取得できていない。

Recommended