'2014/05'에 해당되는 글 3건

  1. 2014.05.27 iOS 7 SDK : Core Bluetooth 실전 연습 5
  2. 2014.05.21 android Bluetooth LE programming(BLE) 193
  3. 2014.05.19 Core Bluetooth background execution mode 1
iOS2014. 5. 27. 16:27


http://code.tutsplus.com/tutorials/ios-7-sdk-core-bluetooth-practical-lesson--mobile-20741

Core Bluetooth framework은 iOS앱에서 Bluetooth low energy기술을 사용할 수 있게 해준다. 이 튜토리얼은 iOS5에서 iOS7으로 CB가 어떻게 진화하였는지 안내할것이다. CB central과 peripheral을 어떻게 설정하고 서로간에 통신을 하며 어떻게 CB로 코딩이 가능한지에 대해서 설명한다.

Introduction

CB 튜토리얼은 두개의 장으로 나누어지는데 첫번쨰 장에서는 CB에 대한 이론을 설명한다. 반면에 이 튜토리얼은 완전한 예제를 통한 실용적인 과정이 될것이다 관련된 전체 소스코드가 수록되어 있다.

1. 소스코드 다운로드

이 튜토리얼의 목적은 CB framework을 어떻게 사용하는지에 대해 가르치는 것이다. 샘플코드를 통해 쉽게 알수 있다. 코드를  다운로드 먼저 하라.

CB framework에 집중할것이기 때문에 당신이 XCode와 iOS에 대해 잘 알고 있다고 가정할것이다. 샘플코드는 다음을 포함한다.

  • 앱은 navigation controller, 3개의 view 그리고 상속한 controller들로 구성된다.
  • 초기 뷰컨트롤로인 ViewController는 2개의 버튼으로 구성된다.
  • CBCentrolManagerViewController는 custom iBeacon을 생성한다.
  • CBPeripheralViewController는 iBeacon과 상속한 정보를 수신한다.
  • SERVICES헤더파일 : 앱에서 사용하는 변수들 

기본적인 구현은 다 되어 있으며 CB process관련하여 코드를 추가하면 된다. 프로젝트를 열고 실행하면 된다.

SERVICES.h파일은 2개의 UUID가 있는데 uuidgen으로 생성한 값이다. 이 값은 재생성해서 변경해도 된다.

이 테스트는 iOS장비가 있어야 테스트가 가능하다.

앱을 실행하면 다음 화면이 나온다.



2.  Central Role 프로그래밍

이 튜토리얼에서  CBCentralManagerViewController class가 중앙에 있다. 첫번째 과정은 CBCentralManager와 CBPeripheral을 지원하기 위한 2개의 프로토콜을 추가하는것 이다. 이 프로토콜들의 선언은 메소드를 정의한다. interface는 다음과 같을 것이다.

1
@interface CBCentralManagerViewController : UIViewController < CBCentralManagerDelegate, CBPeripheralDelegate>

이제 반드시 3개의 속성을 정의해야 한다. CBCentralManager, CBperipheral그리고 NSMutableData가 그것이다. 두개는 명백한 정보이고 하나는 디바이스간에 주고받는 정보이다.

1
2
3
@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) CBPeripheral *discoveredPeripheral;
@property (strong, nonatomic) NSMutableData *data;

에러가 날거고 수정해야 한다. centralManager와 data 객체는 초기화해야 한다. centralManager는 sele delegate로 시작하고 viewDidLoad에서 다음과 같이 수행한다.

1
2
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
_data = [[NSMutableData alloc] init];

warning을 없에려면 다음 메소드를 추가한다.

- (void)centralManagerDidUpdateState:(CBCentralManager *)central

프로토콜메소드로이며 디바이스의 상태를 체크한다. 몇가지 상태가 정의되어 있다. 앱에서는 항상 상태를 확인해야 한다.

  • CBCentralManagerStateUnknown
  • CBCentralManagerStateResetting
  • CBCentralManagerStateUnsupported
  • CBCentralManagerStateUnauthorized
  • CBCentralManagerStatePoweredOff
  • CBCentralManagerStatePoweredOn

예를 들어, Bluetooth 4.0을 지원하지 않는 디바이스에서 이 앱을 실행하면 CBCentralManagerStateUnsupported 을 받게 될것이다.

CBCentralManagerStatePoweredOn 상태가 되면 디바이스를 스캔을 시작할 수 있다. scanForPeripheralsWithService메소드로 스캔한다. 첫번째 파라메터에 nil을 넣게 되면 CBCentralmanager는 모든 디바이스를 검색한다. UUID를 사용하게 되면 UUID에 해당하는 디바이스를 찾게 된다.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // You should test all scenarios
    if (central.state != CBCentralManagerStatePoweredOn) {
        return;
    }
     
    if (central.state == CBCentralManagerStatePoweredOn) {
        // Scan for devices
        [_centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
        NSLog(@"Scanning started");
    }
}

  이 때 앱은 다른  디바이스를 검색한다. 디바이스가 실제로 있음에도 불구하고 정보를 찾지 못할 수 있다. 이를 해결하기 위해서는 다음 메소드를 추가해야 한다.

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

이 메소든느 디바이스가 발견될때마다 호출된것이다. 하지만 TRANSFER_SERVICE_UUID로 게시중인 peripherals들에 대해서만 반응할것이다.

추가적으로 캐시시스템을 사용하여 추후에 참조하여 더 빠르게 통신할 수 있도록 디바이스정보를 저장할 수 있다. 다음코드를 보라

01
02
03
04
05
06
07
08
09
10
11
12
13
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
     
    NSLog(@"Discovered %@ at %@", peripheral.name, RSSI);
     
    if (_discoveredPeripheral != peripheral) {
        // Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it
        _discoveredPeripheral = peripheral;
         
        // And connect
        NSLog(@"Connecting to peripheral %@", peripheral);
        [_centralManager connectPeripheral:peripheral options:nil];
    }
}

연결이 실패할 수 있다. 이 경우 다음 메소드로 알수 있다.

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error

1
2
3
4
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"Failed to connect");
    [self cleanup];
}

사용자에게 경고할 수 있다. cleanup메소드는 아직 정의되지 않았다. 이제 정의해보자!. 

이 메소드는 원격디바이스에 대한 모든 subscription정보를 취소하거나 연결을 끊어버린다. 서비스에 속한 특성들을 루프를 돌면서 바인드되어 있는것들을 제거한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
- (void)cleanup {
     
    // See if we are subscribed to a characteristic on the peripheral
    if (_discoveredPeripheral.services != nil) {
        for (CBService *service in _discoveredPeripheral.services) {
            if (service.characteristics != nil) {
                for (CBCharacteristic *characteristic in service.characteristics) {
                    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
                        if (characteristic.isNotifying) {
                            [_discoveredPeripheral setNotifyValue:NO forCharacteristic:characteristic];
                            return;
                        }
                    }
                }
            }
        }
    }
 
    [_centralManager cancelPeripheralConnection:_discoveredPeripheral];
}

성공적으로 연결이 되었다면 이젠 서비스와 특성(characteristic)을 알아내야 한다. 다음 메소드를 통해 가능하다.

 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral

연결이 완료되고 나서 스캔프로세스는 중단한다. 그리고나서 UUID(TRANSFER_SERVICE_UUID)에 맷칭되는 서비스를 검색한다.

01
02
03
04
05
06
07
08
09
10
11
12
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"Connected");
     
    [_centralManager stopScan];
    NSLog(@"Scanning stopped");
     
    [_data setLength:0];
     
    peripheral.delegate = self;
 
    [peripheral discoverServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
}

peripheral의 delegate를 통해 몇가지 콜백이 호출된다. 그 중 하나가 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error 인데 이 메소드는 서비스에 속한 특성들을 찾아준다. 에러가 났을 때는 하지말고 성공했을 때만 찾아야 한다. 

01
02
03
04
05
06
07
08
09
10
11
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    if (error) {
        [self cleanup];
        return;
    }
 
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]] forService:service];
    }
    // Discover other characteristics
}

모든게 정확하다면 특성이 발견될것이다. 다시 콜백이 호출될건데.. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 가 호출된다. 이렇게 발견이 되면 CBCentralManager가 peripheral로부터 받고자 하는 데이터를 받을 수 있도록 subscript할 수 있다.

에러시 처리가 되어야 한다. 믿을만하다면 특성을 바로 구독할 수 있지만 반드시 특성을 하나하나 확인해볼것을 권장한다. 그리고 확인이 되었을 때 만 구독하라. 이 과정까지 완료되면 데이터가 오는것을 기다리면된다.

01
02
03
04
05
06
07
08
09
10
11
12
- (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:TRANSFER_CHARACTERISTIC_UUID]]) {
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
    }
}

peripheral이 새로운 데이터를 보낼때마다 다음 콜백이 호출된다.

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

두번째 인자는 특성을 포함한다. 

초기에 특성값을 저장하려고 NSString을 생성할것이다. 그런 후 데이터 수신이 모두 수신되었는지 또는 더 전달이 될것인지 확인할 것이다. 새로운  데이터가 수신되자마자 textview에 갱신될것이다. 모든데이터가 완료되면 특성과 장치에 대한 연결을 해재할수 있다.

다시 말해 데이터가 수신되면 대기하든지 연결을 해재하든지 알아서 하면 된다. 이 콜백은 특성에 따라 추가적으로 데이터가 도달할지에 대해 알 수 있다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"Error");
        return;
    }
     
    NSString *stringFromData = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
     
    // Have we got everything we need?
    if ([stringFromData isEqualToString:@"EOM"]) {
     
        [_textview setText:[[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]];
         
        [peripheral setNotifyValue:NO forCharacteristic:characteristic];
         
        [_centralManager cancelPeripheralConnection:peripheral];
    }
     
    [_data appendData:characteristic.value];
}

추가적으로 CBCentral이 특성 변경에 대한 통지상태를 알수 있는 메소드가 있다. 

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error

특성통지가 중단되었는지 반드시 확인해야 하며 중단될경우 연결을 해재해야 한다.

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
     
    if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
        return;
    }
     
    if (characteristic.isNotifying) {
        NSLog(@"Notification began on %@", characteristic);
    } else {
        // Notification has stopped
        [_centralManager cancelPeripheralConnection:peripheral];
    }
}

디바이스들간에 연결해제가 발생하면 로컬카피로 저장해둔 peripheral을 clean up해야 한다. 다음 메소드를 사용해서..

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 

이 메소든느 단순하고 peripheral을 0으로 셋팅한다. 이어서 스켄을 다시 시작할 지 그냥 둘지 앱에서 정해서 하면 된다. 여기서는 다시 스캔하고 있다.

1
2
3
4
5
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    _discoveredPeripheral = nil;
 
    [_centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
}

최종적으로 하나의 단계가 더 있다. 뷰가 화면에서 가려질때마다 scan process를 중단해야 한다.

1
[_centralManager stopScan];

앱을 실행하자 하지만 peripherl앱이 있어야 데이터를 받을 수 있다. 다음 이미지는 최종 인터페이스를 보여준다.



3. Peripheral Role의 프로그래밍

이 튜터리얼에서는 CBPeripheralViewController에 집중한다. 먼저 CBPeripheralManagerDelegate와 UITextViewDelegate를 추가한다. 다음과 같은 인터페이스가 필요하다.

1
@interface CBPeripheralViewController : UIViewController < CBPeripheralManagerDelegate, UITextViewDelegate>

이어서 다음 4개의 속성을 추가한다. 처음 2개는 peripheral manager와 특성을 의미하고 3번째는 전송할 데이터, 그리고 마지막은 데이터 인덱스이다.

1
2
3
4
@property (strong, nonatomic) CBPeripheralManager *peripheralManager;
@property (strong, nonatomic) CBMutableCharacteristic *transferCharacteristic;
@property (strong, nonatomic) NSData *dataToSend;
@property (nonatomic, readwrite) NSInteger sendDataIndex;

먼저 _peripheralManager를 초기화하고 게시를 시작하기 위한 설정을 해야 한다. 서비스게시는 서비스UUID를 초기화하는것이다. viewDidLoad에서 다음과 같이 하면 된다.

1
2
3
4
5
6
7
- (void)viewDidLoad {
    [super viewDidLoad];
 
    _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
     
    [_peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
}

warning을 없애려면 CBCentramManager처럼 다음 메소드를 정의해야 한다.  - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral 상태가 CBPeripheralManagerStatePoweredOn이면 서비스와 특성을 빌드하고 정의해야 한다.

각 서비스와 특성은 Unique UUID이어야 한다. 

특성의 값들이 어떻게 사용되어질지를 정의하는 속성들이 있다.

  • CBCharacteristicPropertyBroadcast
  • CBCharacteristicPropertyRead
  • CBCharacteristicPropertyWriteWithoutResponse
  • CBCharacteristicPropertyWrite
  • CBCharacteristicPropertyWrite
  • CBCharacteristicPropertyNotify
  • CBCharacteristicPropertyIndicate
  • CBCharacteristicPropertyAuthenticatedSignedWrites
  • CBCharacteristicPropertyExtendedProperties
  • CBCharacteristicPropertyNotifyEncryptionRequired
  • CBCharacteristicPropertyIndicateEncryptionRequired

이 속석들에 대해 더 자세히 알고 싶으면 다음 링크를 참고해라.

 CBCharacteristic Class Reference.

i nit의 마지막 인자는 read, write 그리고 속성에 대한 안호화권한이다.

  • CBAttributePermissionsReadable
  • CBAttributePermissionsWriteable
  • CBAttributePermissionsReadEncryptionRequired
  • CBAttributePermissionsWriteEncryptionRequired

특성을 정의 완료하면  CBMutableService를 사용하여 서비스를 정의할 시간이다. 서비스는 TRANSFER_CHARACTERISTIC_UUID로 정의되어야 한다. 서비스에 특성을 추가하고 peripheral에 추가한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
        return;
    }
     
    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
         
        CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID] primary:YES];
         
        transferService.characteristics = @[_transferCharacteristic];
         
        [_peripheralManager addService:transferService];
    }
}

자 인자 서비스와 특성도 정의가 되었다. 인자 디바이스가 연결되었을 때 그리고 그에 반응하는것이 남았다.

- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic 메소드는 지원하는 특성에 맞는 누군가 연결이 되었을 때 호출된는데 이 때 데이터를 전송할 수 있다.

앱은 textview에서 데이터가 유효하면 전송한다. 사용자가 값을 수정하면 앱은 구독중인 central에 바로 전송한다. sendData메소드는 custom메소드이다.

1
2
3
4
5
6
7
8
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
     
    _dataToSend = [_textView.text dataUsingEncoding:NSUTF8StringEncoding];
 
    _sendDataIndex = 0;
 
    [self sendData];
}

sendData는 데이터를 전송하는 메소드이다. 다음과 같은 몇가지 동작을 수행할 것이다.

  • Send data
  • Send the end of communication flag
  • Test if the app sent the data
  • Check if all data was sent
  • React to all of the previous topics

전체코드를 아래에 나타내었다. 

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
- (void)sendData {
 
    static BOOL sendingEOM = NO;
     
    // end of message?
    if (sendingEOM) {
        BOOL didSend = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
         
        if (didSend) {
            // It did, so mark it as sent
            sendingEOM = NO;
        }
        // didn't send, so we'll exit and wait for peripheralManagerIsReadyToUpdateSubscribers to call sendData again
        return;
    }
     
    // We're sending data
    // Is there any left to send?
    if (self.sendDataIndex >= self.dataToSend.length) {
        // No data left.  Do nothing
        return;
    }
     
    // There's data left, so send until the callback fails, or we're done.
    BOOL didSend = YES;
     
    while (didSend) {
        // Work out how big it should be
        NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex;
         
        // Can't be longer than 20 bytes
        if (amountToSend > NOTIFY_MTU) amountToSend = NOTIFY_MTU;
         
        // Copy out the data we want
        NSData *chunk = [NSData dataWithBytes:self.dataToSend.bytes+self.sendDataIndex length:amountToSend];
         
        didSend = [self.peripheralManager updateValue:chunk forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
         
        // If it didn't work, drop out and wait for the callback
        if (!didSend) {
            return;
        }
         
        NSString *stringFromData = [[NSString alloc] initWithData:chunk encoding:NSUTF8StringEncoding];
        NSLog(@"Sent: %@", stringFromData);
         
        // It did send, so update our index
        self.sendDataIndex += amountToSend;
         
        // Was it the last one?
        if (self.sendDataIndex >= self.dataToSend.length) {
             
            // Set this so if the send fails, we'll send it next time
            sendingEOM = YES;
 
            BOOL eomSent = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
             
            if (eomSent) {
                // It sent, we're all done
                sendingEOM = NO;
                NSLog(@"Sent: EOM");
            }
             
            return;
        }
    }
}

 마지막으로 PeripheralManager가 다음 chunk를 전송할 수 있는 준비가 될때 콜백을 구현해주어야 한다. 이 콜백은 전송한 데이터가 모두 완료되었음을 의미하며 - (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral 이다. 

1
2
3
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral {
    [self sendData];
}

인자 앱을 빌드하고 실행해보아라 다음과 같은 화면이 나올것이다.



인자 BTLE프로그래밍에 대한 기본적이 이해를 코드로서 완료하였다.

이 정보를 알아서 잘 사용하길 바란다.


소스코드 다운로드 링크 

CBTutorial-Initial.zip








Posted by 삼스
Android/App개발2014. 5. 21. 21:57


Bluetooth Low Energy

4.3부터 BLE central role을 지원하기 시작했다. 그리고 디바이스 검색과 서비스 검색 그리고 속성들의 읽기/쓰기를 지원한다. BLE를 통해 저전력으로 설계된 다양한 디바이스와통신이 가능해졌다.

Key Terms and Concepts
  • Generic Attribute Profile (GATT) - BLE link상에서 송수신 가능한 일반적인 사양을 말하며 모든 LE 애플리케이션프로파일은 GATT에 기반한다.
    • 블루투스 SIG는 많은 프로파일을 정의하였는데 하나의 프로파일은 앱에서 어떻게 동작할지에 대한 사양을 의미한다. 하나의 장치는 여러 프로파일을 구현할 수 있다. 
  • Attribute Protocol (ATT) - GATT는 ATT의 최상위 구현체이며 GATT/ATT로 참조되기도 한다. ATT는 BLE장치에서 동작하도록 최적화 되어 있다. 개개의 속성(Attribute)은 UUID를 가지며 128비트로 구성된다. ATT에 의해 부여된 속성은 특성과 서비스를 결정한다.
  • Characteristic - 하나의 특성(characteristic)은 하나의 값과 n개의 디스크립터를 포함하는데 디스크립터는 특성 값을 기술한다. 하나의 특성은 클래스와 유사한 타입으로 생각하면 된다. 
  • Descriptor - 디스크립터는 특성의 값을 기술한다.
  • Service - 하나의 서비스는 특성드의 집합니다. 예를 들어 "Heart Rate Monitor"라고 불리는 서비스를 가지고 있다면 그 서비스는 "heart rate measurement"같은 특성을 포함한다. GATT-based profile의 리스트와 서비스를 확인하려면 bluetooth.org를 방문하라.
Roles and Responsibilities
이 문서에서 설명하는 역할과 책임은 Android장치가  BLE장치와 연동하는데 적용되는것들이다.
  • Central vs. peripheral - BLE 연결에 적용된다. central 역할은 scan, 게시검색(looking for advertisement), 그리고 peripheral역할은 게시를 만든다.
  • GATT server vs. GATT client - 디바이스가 연결된 이 후 서로 어떻게 대화하는지에 대해 정의한다. 
차이점을 이해하려면 안드로이드폰하나와 움직임을 감지하는 BLE장치를 가지고 있다고 가정 해보자. 폰은 central역할을 한다. BLE장치는 peripheral역할을 한다. 

폰과 BLE장치가 한번 연결이 되면 두 장치는 서로  GATT metadata를 주고 받는다. 주고받는 데이터에 따라 하나이상의 서버 액션이 있을 수 있다. 예를 들어 BLE장치가 폰에 센서정보를 전달하려고 할 수 있다. 이 때 장치는 서버로 동작한다. 장치가 폰으로부터 업데이트를 받고자 할수 있다 이 때는 폰이 서버로 동작한다.

이 문서에서 제공하는 샘플은 안드로이드 디바이스로 동작하는 앱으로 GATT client이다. 앱은 GATT server로부터 데이터를 가져오는데 그것은 Heart Rate Profile을 지원하는 BLE heart rate monitor이다.
반면에 앱을 GATT 서버역할을 하도록 구현할 수 도 있다. BluetoothGattServer를 참조하라.

BLE permissions
블루투스통신(연결요청, 연결수락, 데이터 전송)을 하려면 BLUETOOTH 퍼미션을 추가해야 함다.
블루투스장치를 검색하고 설정을 조작하려면 BLUETOOTH_ADMIN 퍼미션을 추가해야 한다.
BLUETOOTH_ADMIN은 BLUETOOTH퍼미션과 함께 정의되어야 한다.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
블루투스 BLE만 사용할거라면 아래와 같이 manifest에 포함시켜야 한다.

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
BLE를 지원하지 않는 앱으로 만들고자 한다면 여전히 동일한  feature를 추가해야 하며 다만 required="false"로 하면 된다. 런타임에 BLE활성화 여부를 PackageManager.hasSystemFeature로 알아낼 수 있다.

// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
   
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish
();
}

Setting Up BLE

BLE로 통신하기 전에 디바이스가 BLE를 지원하는지 확이해야 한다. 지원한다면 활성화되어야 한다. 
만일  BLE를 지원하지 않는다면 BLE feature들을 비활성화해야 한다. 지원한다면 사용자에게 BT를 앱을 떠나지 않고 활성화하도록 유도해야 한다. 이 과정은 BluetoothAdapter를 사용하여 2단계로 가능하다.

1. BluetoothAdapter얻기

BluetoothAdapter는 블루투스관련 일부 또는 모든 블루투스 동작들을 필요로 한다. BluetoothAdapter는 디바이스자체의 BluetoothAdapter를 나타낸다.  전체시스템을 위한 하나의 어댑터가 있고 앱은 이 객체를 통해서 상호작용을 한다. 다음 코드조각은 어댑터를 얻는 방법을 보여준다.

    1. // Initializes Bluetooth adapter.
      final BluetoothManager bluetoothManager =
             
      (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
      mBluetoothAdapter
      = bluetoothManager.getAdapter();
    2. Bluetooth 활성화.
    다음에 블루투스를 활성화해야 한다. isEnabled()로 활성화여부를 확인 가능하다. false면 비활성화이다. 다음은 그 샘플이다.
    1. private BluetoothAdapter mBluetoothAdapter;
      ...
      // Ensures Bluetooth is available on the device and it is enabled. If not,
      // displays a dialog requesting user permission to enable Bluetooth.
      if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
         
      Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
          startActivityForResult
      (enableBtIntent, REQUEST_ENABLE_BT);
      }

    BLE 장치 찾기
    BLE장치를 찾으려면 startLeScan() 메소드를 호출한다. LeScanCallack이 파라메터로 호출된다. 이 메소드를 구현해야 하고 스캔결과를 받을 수 있다. 배터리 소모가 심심하기 때문에 다음의 가이드라인을 잘 지켜야 한다.
    • 원하는 디바이스를 찾으면 바로 스캔을 중단해야 한다.
    • 하나의 루프에서 스캔하지 말고 타임아웃을 적용해라. 디바이스가 범위안에서 벗어났을 수도 있고 그럴경우 배터리 는 골로 간다.
    다음 코드는 스캔을 시작하고 중단하는 예이다.
    /**
     * Activity for scanning and displaying available BLE devices.
     */

    public class DeviceScanActivity extends ListActivity {

       
    private BluetoothAdapter mBluetoothAdapter;
       
    private boolean mScanning;
       
    private Handler mHandler;

       
    // Stops scanning after 10 seconds.
       
    private static final long SCAN_PERIOD = 10000;
       
    ...
       
    private void scanLeDevice(final boolean enable) {
           
    if (enable) {
               
    // Stops scanning after a pre-defined scan period.
                mHandler
    .postDelayed(new Runnable() {
                   
    @Override
                   
    public void run() {
                        mScanning
    = false;
                        mBluetoothAdapter
    .stopLeScan(mLeScanCallback);
                   
    }
               
    }, SCAN_PERIOD);

                mScanning
    = true;
                mBluetoothAdapter
    .startLeScan(mLeScanCallback);
           
    } else {
                mScanning
    = false;
                mBluetoothAdapter
    .stopLeScan(mLeScanCallback);
           
    }
           
    ...
       
    }
    ...
    }

    특정타입의 페리퍼렁만 스캔하고자 한다면 startLeScan(UUID[], BluetoothAdapter.LeScanCallback)을 사용할 수 있다. UUID[]에는 앱에서 지원하고자 하는  GATT 서비스목록이 들어간다.
    다음코드 예를 보아라.

    private LeDeviceListAdapter mLeDeviceListAdapter;
    ...
    // Device scan callback.
    private BluetoothAdapter.LeScanCallback mLeScanCallback =
           
    new BluetoothAdapter.LeScanCallback() {
       
    @Override
       
    public void onLeScan(final BluetoothDevice device, int rssi,
               
    byte[] scanRecord) {
            runOnUiThread
    (new Runnable() {
               
    @Override
               
    public void run() {
                   mLeDeviceListAdapter
    .addDevice(device);
                   mLeDeviceListAdapter
    .notifyDataSetChanged();
               
    }
           
    });
       
    }
    };
    BLE또는 Class BT장치를 검색할 수 는 있지만 두가지를 동시에 검색은 할 수 없다.

    GATT 서버에 연결하기
    첫번째 연동을 위한 과정은 BLE디바이스에 연결하는것인데 좀더 자세히 말하면 디바이스의 GATT서버에 연결하는 것이다. connectGatt()메소드로 하면 된다. 이 메소드는 3개의 파라메터가 있는데 context, autoConnect, 그리고 BluetoothGattCallback이 있다. autoConnect는 검색되었을 때 자동으로 연결할지에 대한 파라메터다.

    mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

    이 코드는 BLE device를 GATT 서버 호스트로 연결한다. 그리고 BluetoothGatt 인스턴스를 반환한다. 이 인스턴스로 GATT client를 운영한다.
    BluetoothGattCallback은 클라이언트에 연결상태나 client 운영에 대한 결과를 전달한다.

    이 예제에서 BLE 앱은 하나의 액티비티가 연결하고 데이터를 표시하고 GATT 서비스와 특성들을 표시한다. 사용자 입력에 기반하여 BluetoothLeService로 불리는 서비스와 통신을 수행하도 Android BLE API를 통해 장치와 상호연동을 한다.


    // A service that interacts with the BLE device via the Android BLE API.
    public class BluetoothLeService extends Service {
       
    private final static String TAG = BluetoothLeService.class.getSimpleName();

       
    private BluetoothManager mBluetoothManager;
       
    private BluetoothAdapter mBluetoothAdapter;
       
    private String mBluetoothDeviceAddress;
       
    private BluetoothGatt mBluetoothGatt;
       
    private int mConnectionState = STATE_DISCONNECTED;

       
    private static final int STATE_DISCONNECTED = 0;
       
    private static final int STATE_CONNECTING = 1;
       
    private static final int STATE_CONNECTED = 2;

       
    public final static String ACTION_GATT_CONNECTED =
               
    "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
       
    public final static String ACTION_GATT_DISCONNECTED =
               
    "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
       
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
               
    "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
       
    public final static String ACTION_DATA_AVAILABLE =
               
    "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
       
    public final static String EXTRA_DATA =
               
    "com.example.bluetooth.le.EXTRA_DATA";

       
    public final static UUID UUID_HEART_RATE_MEASUREMENT =
                UUID
    .fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

       
    // Various callback methods defined by the BLE API.
       
    private final BluetoothGattCallback mGattCallback =
               
    new BluetoothGattCallback() {
           
    @Override
           
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
                   
    int newState) {
               
    String intentAction;
               
    if (newState == BluetoothProfile.STATE_CONNECTED) {
                    intentAction
    = ACTION_GATT_CONNECTED;
                    mConnectionState
    = STATE_CONNECTED;
                    broadcastUpdate
    (intentAction);
                   
    Log.i(TAG, "Connected to GATT server.");
                   
    Log.i(TAG, "Attempting to start service discovery:" +
                            mBluetoothGatt
    .discoverServices());

               
    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    intentAction
    = ACTION_GATT_DISCONNECTED;
                    mConnectionState
    = STATE_DISCONNECTED;
                   
    Log.i(TAG, "Disconnected from GATT server.");
                    broadcastUpdate
    (intentAction);
               
    }
           
    }

           
    @Override
           
    // New services discovered
           
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
               
    if (status == BluetoothGatt.GATT_SUCCESS) {
                    broadcastUpdate
    (ACTION_GATT_SERVICES_DISCOVERED);
               
    } else {
                   
    Log.w(TAG, "onServicesDiscovered received: " + status);
               
    }
           
    }

           
    @Override
           
    // Result of a characteristic read operation
           
    public void onCharacteristicRead(BluetoothGatt gatt,
                   
    BluetoothGattCharacteristic characteristic,
                   
    int status) {
               
    if (status == BluetoothGatt.GATT_SUCCESS) {
                    broadcastUpdate
    (ACTION_DATA_AVAILABLE, characteristic);
               
    }
           
    }
         
    ...
       
    };
    ...
    }
    콜백이 호출되면 broadcastUpdate()를 호출한다. 여기서 데이터 파싱은 Bluetooth Heart Rate Measurement profile 사양에 맞춰서 수행하고 있다.
    private void broadcastUpdate(final String action) {
       
    final Intent intent = new Intent(action);
        sendBroadcast
    (intent);
    }

    private void broadcastUpdate(final String action,
                                 
    final BluetoothGattCharacteristic characteristic) {
       
    final Intent intent = new Intent(action);

       
    // This is special handling for the Heart Rate Measurement profile. Data
       
    // parsing is carried out as per profile specifications.
       
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
           
    int flag = characteristic.getProperties();
           
    int format = -1;
           
    if ((flag & 0x01) != 0) {
                format
    = BluetoothGattCharacteristic.FORMAT_UINT16;
               
    Log.d(TAG, "Heart rate format UINT16.");
           
    } else {
                format
    = BluetoothGattCharacteristic.FORMAT_UINT8;
               
    Log.d(TAG, "Heart rate format UINT8.");
           
    }
           
    final int heartRate = characteristic.getIntValue(format, 1);
           
    Log.d(TAG, String.format("Received heart rate: %d", heartRate));
            intent
    .putExtra(EXTRA_DATA, String.valueOf(heartRate));
       
    } else {
           
    // For all other profiles, writes the data formatted in HEX.
           
    final byte[] data = characteristic.getValue();
           
    if (data != null && data.length > 0) {
               
    final StringBuilder stringBuilder = new StringBuilder(data.length);
               
    for(byte byteChar : data)
                    stringBuilder
    .append(String.format("%02X ", byteChar));
                intent
    .putExtra(EXTRA_DATA, new String(data) + "\n" +
                        stringBuilder
    .toString());
           
    }
       
    }
        sendBroadcast
    (intent);
    }
    이 이벤드들은 B roadcastReceiver에 의해 처리된다.
    // Handles various events fired by the Service.
    // ACTION_GATT_CONNECTED: connected to a GATT server.
    // ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
    // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
    // ACTION_DATA_AVAILABLE: received data from the device. This can be a
    // result of read or notification operations.
    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
       
    @Override
       
    public void onReceive(Context context, Intent intent) {
           
    final String action = intent.getAction();
           
    if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
                mConnected
    = true;
                updateConnectionState
    (R.string.connected);
                invalidateOptionsMenu
    ();
           
    } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
                mConnected
    = false;
                updateConnectionState
    (R.string.disconnected);
                invalidateOptionsMenu
    ();
                clearUI
    ();
           
    } else if (BluetoothLeService.
                    ACTION_GATT_SERVICES_DISCOVERED
    .equals(action)) {
               
    // Show all the supported services and characteristics on the
               
    // user interface.
                displayGattServices
    (mBluetoothLeService.getSupportedGattServices());
           
    } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
                displayData
    (intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
           
    }
       
    }
    };

    BLE 속성 읽기
    앱이 GATT 서버에 연결하고 서비스를 찾게 되면 속성을  읽고/쓸수 있게 된다. 다음 코드는 서비스와 특성들을 나열하여 표시해준다.
    public class DeviceControlActivity extends Activity {
       
    ...
       
    // Demonstrates how to iterate through the supported GATT
       
    // Services/Characteristics.
       
    // In this sample, we populate the data structure that is bound to the
       
    // ExpandableListView on the UI.
       
    private void displayGattServices(List<BluetoothGattService> gattServices) {
           
    if (gattServices == null) return;
           
    String uuid = null;
           
    String unknownServiceString = getResources().
                    getString
    (R.string.unknown_service);
           
    String unknownCharaString = getResources().
                    getString
    (R.string.unknown_characteristic);
           
    ArrayList<HashMap<String, String>> gattServiceData =
                   
    new ArrayList<HashMap<String, String>>();
           
    ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                   
    = new ArrayList<ArrayList<HashMap<String, String>>>();
            mGattCharacteristics
    =
                   
    new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

           
    // Loops through available GATT Services.
           
    for (BluetoothGattService gattService : gattServices) {
               
    HashMap<String, String> currentServiceData =
                       
    new HashMap<String, String>();
                uuid
    = gattService.getUuid().toString();
                currentServiceData
    .put(
                        LIST_NAME
    , SampleGattAttributes.
                                lookup
    (uuid, unknownServiceString));
                currentServiceData
    .put(LIST_UUID, uuid);
                gattServiceData
    .add(currentServiceData);

               
    ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                       
    new ArrayList<HashMap<String, String>>();
               
    List<BluetoothGattCharacteristic> gattCharacteristics =
                        gattService
    .getCharacteristics();
               
    ArrayList<BluetoothGattCharacteristic> charas =
                       
    new ArrayList<BluetoothGattCharacteristic>();
               
    // Loops through available Characteristics.
               
    for (BluetoothGattCharacteristic gattCharacteristic :
                        gattCharacteristics
    ) {
                    charas
    .add(gattCharacteristic);
                   
    HashMap<String, String> currentCharaData =
                           
    new HashMap<String, String>();
                    uuid
    = gattCharacteristic.getUuid().toString();
                    currentCharaData
    .put(
                            LIST_NAME
    , SampleGattAttributes.lookup(uuid,
                                    unknownCharaString
    ));
                    currentCharaData
    .put(LIST_UUID, uuid);
                    gattCharacteristicGroupData
    .add(currentCharaData);
               
    }
                mGattCharacteristics
    .add(charas);
                gattCharacteristicData
    .add(gattCharacteristicGroupData);
             
    }
       
    ...
       
    }
    ...
    }

    GATT 통지 수신
    디바이스의 특성이 변경에 대한 통지를 앱이 알수 있다. 다음 코드는 setCharacteristicNotification()메소드로 어떻게 통지를 설정하는지 보여준다.
    private BluetoothGatt mBluetoothGatt;
    BluetoothGattCharacteristic characteristic;
    boolean enabled;
    ...
    mBluetoothGatt
    .setCharacteristicNotification(characteristic, enabled);
    ...
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
            UUID
    .fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
    descriptor
    .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt
    .writeDescriptor(descriptor);
    하나의 특성에 대해 통지가 활성화되면 onCharacteristicChanged()가 장치에서 해당 특성정보가 변경이 되면 호출된다.
    @Override
    // Characteristic notification
    public void onCharacteristicChanged(BluetoothGatt gatt,
           
    BluetoothGattCharacteristic characteristic) {
        broadcastUpdate
    (ACTION_DATA_AVAILABLE, characteristic);
    }

    클라이언트 앱의 종료
    BLE 장치를 사용하는 앱이 종료되면 반드시 close()를 호출하여 시스템이 관련 리소스를 반환하도록 해야 한다.
    public void close() {
       
    if (mBluetoothGatt == null) {
           
    return;
       
    }
        mBluetoothGatt
    .close();
        mBluetoothGatt
    = null;
    }


    Posted by 삼스
    iOS2014. 5. 19. 19:09




    코어블루투스 백그라운드 실행모드

    - info.plist에 임의값을 추가하여 속성을 부여 가능

     UIBackgroundModes에 bluetooth-central또는  bluetooth-peripheral를 지정하면 됨.

    - 2가지 모드지원

      1. central role

          bluetooth와 low energy peripheral로 통신한다.

      2. peripheral role

          데이터를 공유한다.


     Bluetooth-central 백그라운드 모드

     - 앱이 백그라운드 모드이어도 장치를 찾고(discover) 연결(connect)할 수 있으며 데이터를 탐색하고 연동할 수 있다.

     - CBCentralManagerDelegate와 CBPeripheralDelegate 델리게이트메소드로 앱을 깨울 수 있으며 중요한 central role 이벤트를 처리할수 있게 해준다(연결이나 연결해재, 속성값이 변경되었을때, 그리고 central manager의 상태가 변경되었을 때 등).

     - 백그라운드에서 블루투스동작을 할 때와 포그라운드에서 동작할때가 서러 다르다는것을 인지해야 한다. 특히 장치의 검색과정이 그렇다.

      -> CBCentralManagerScanOptionAllowDuplicatesKey 스캔옵션키는 무시된다. 그리고 여러개의 광고장치의 발견은 하나의 이벤트로 모아진다.

      -> 모든 앱들이 백그라운드에서 장치를 스캐닝한다면 스캐닝 인터벌은 늘어날것이고 그로 인해 장치를 찾는데 오래걸릴것이다.


    bluetooth-peripheral 백그라운드모드

    info.plist에 UIBackgroundModes키에 bluetooth-peripheral를 셋팅하면 되며 시스템이 등록된 이벤트들이 발생하면 앱을 깨워줄것이다.

    추가적으로 연결된 central들로부터의 요청을 처리할 수 있도록 하기 위해 bluetooth framework은 백그라운드 동안 앱의 상태를 공시(advertise)한다. 즉 앱이 포그라운드 상태인지 상태를 체크해야 한다.

     - CBAdvertisementDataLocalNameKey 키는 무시된다. 그리고 페리퍼렁의 로칼네임은 공시되지 않는다.

     - 모든 서비스 UUID들(CBAdvertisementDataServiceUUIDsKey 키에 값으로 저장된)는 "overflow"영역에 위치하며 오로지 하나의 iOS장치(명시적으로 검색하고 있는)에 발견될 수 있다.


    어떤 백그라운드 실행모드를 사용할것인가?

     백그라운드모드 한가지나 두가지를 모두 동시에도 사용이 가능하지만 백그라운드로 수행하는 작업은 정확하게 처리하고 정리되어야 한다. 왜냐 하면 배터리사용량때문에 문제가 발생할 수 있기 때문이다.

    앱은 블루투스관련 이벤트를 가능한 빨리 처리하고 리턴되어야 하며 다시 대기상태가 될 수 있다. 배터리는 그렇게 절약하는 것이다.

    모든 블루투스 백그라운드실행모드를 지원하는 앱은 다음의 단계를 따라야 한다.

    • 앱은 세션기반이어야 하며 사용자에게 블루투스 동작의 시작과 정지를 결정할 수 있는 인터페이스를 제공해야 한다.

    • 꺠어나면 앱은 10초안에 관련 테스크를 모두 마쳐야 한다.안그러면 시스템에 막대한 로드(병목)를 줄수 있으며 앱이 죽을수도 있다.
    • 쓸데없이 깨우는 짓을 하지 않아야 한다. 꼭 필요한 경우에만 앱을 깨울 수 있도록 해야 함.

    백그라운드에서 아주 오래걸리는 동작의 수행하기


    Some apps may need to use the Core Bluetooth framework to perform long-term actions in the background. As an example, imagine you are developing a home security app for an iOS device that communicates with a door lock (equipped with Bluetooth low energy technology). The app and the lock interact to automatically lock the door when the user leaves home and unlock the door when the user returns—all while the app is in the background. When the user leaves home, the iOS device may eventually become out of range of the lock, causing the connection to the lock to be lost. At this point, the app can simply call theconnectPeripheral:options: method of the CBCentralManagerclass, and because connection requests do not time out, the iOS device will reconnect when the user returns home.

    Now imagine that the user is away from home for a few days. If the app is terminated by the system while the user is away, the app will not be able to reconnect to the lock when the user returns home, and the user may not be able to unlock the door. For apps like these, it is critical to be able to continue using Core Bluetooth to perform long-term actions, such as monitoring active and pending connections.

    State Preservation and Restoration

    Because state preservation and restoration is built in to Core Bluetooth, your app can opt in to this feature to ask the system to preserve the state of your app’s central and peripheral managers and to continue performing certain Bluetooth-related tasks on their behalf, even when your app is no longer running. When one of these tasks completes, the system relaunches your app into the background and gives your app the opportunity to restore its state and to handle the event appropriately. In the case of the home security app described above, the system would monitor the connection request, and re-relaunch the app to handle thecentralManager:didConnectPeripheral: delegate callback when the user returned home and the connection request completed.

    Core Bluetooth supports state preservation and restoration for apps that implement the central role, peripheral role, or both. When your app implements the central role and adds support for state preservation and restoration, the system saves the state of your central manager object when the system is about to terminate your app to free up memory (if your app has multiple central managers, you can choose which ones you want the system to keep track of). In particular, for a given CBCentralManager object, the system keeps track of:

    • The services the central manager was scanning for (and any scan options specified when the scan started)

    • The peripherals the central manager was trying to connect to or had already connected to

    • The characteristics the central manager was subscribed to

    Apps that implement the peripheral role can likewise take advantage of state preservation and restoration. For CBPeripheralManagerobjects, the system keeps track of:

    • The data the peripheral manager was advertising

    • The services and characteristics the peripheral manager published to the device’s database

    • The centrals that were subscribed to your characteristics’ values

    When your app is relaunched into the background by the system (because a peripheral your app was scanning for is discovered, for instance), you can reinstantiate your app’s central and peripheral managers and restore their state. The following section describes in detail how to take advantage of state preservation and restoration in your app.

    Adding Support for State Preservation and Restoration

    State preservation and restoration in Core Bluetooth is an opt-in feature and requires help from your app to work. You can add support for this feature in your app by following this process:

    1. (Required) Opt in to state preservation and restoration when you allocate and initialize a central or peripheral manager object. This step is described in “Opt In to State Preservation and Restoration.”

    2. (Required) Reinstantiate any central or peripheral manager objects after your app is relaunched by the system. This step is described in “Reinstantiate Your Central and Peripheral Managers.”

    3. (Required) Implement the appropriate restoration delegate method. This step is described in “Implement the Appropriate Restoration Delegate Method.”

    4. (Optional) Update your central and peripheral managers’ initialization process. This step is described in “Update Your Initialization Process.”

    Opt In to State Preservation and Restoration

    To opt in to the state preservation and restoration feature, simply provide a unique restoration identifier when you allocate and initialize a central or peripheral manager. A restoration identifier is a string that identifies the central or peripheral manager to Core Bluetooth and to your app. The value of the string is significant only to your code, but the presence of this string tells Core Bluetooth that it needs to preserve the state of the tagged object. Core Bluetooth preserves the state of only those objects that have a restoration identifier.

    For example, to opt in to state preservation and restoration in an app that uses only one instance of a CBCentralManager object to implement the central role, specify theCBCentralManagerOptionRestoreIdentifierKey initialization option and provide a restoration identifier for the central manager when you allocate and initialize it.

        myCentralManager =
            [[CBCentralManager alloc] initWithDelegate:self queue:nil
             options:@{ CBCentralManagerOptionRestoreIdentifierKey:
             @"myCentralManagerIdentifier" }];

    Although the above example does not demonstrate this, you opt in to state preservation and restoration in an app that uses peripheral manager objects in an analogous way: Specify theCBPeripheralManagerOptionRestoreIdentifierKeyinitialization option, and provide a restoration identifier when you allocate and initialize each peripheral manager object.

    Reinstantiate Your Central and Peripheral Managers

    When your app is relaunched into the background by the system, the first thing you need to do is reinstantiate the appropriate central and peripheral managers with the same restoration identifiers as they had when they were first created. If your app uses only one central or peripheral manager, and that manager exists for the lifetime of your app, there is nothing more you need to do for this step.

    If your app uses more than one central or peripheral manager or if it uses a manager that isn’t around for the lifetime of your app, your app needs to know which managers to reinstantiate when it is relaunched by the system. You can access a list of all the restoration identifiers for the manager objects the system was preserving for your app when it was terminated, by using the appropriate launch option keys (UIApplicationLaunchOptionsBluetoothCentralsKey orUIApplicationLaunchOptionsBluetoothPeripheralsKey) when implementing your app delegate’sapplication:didFinishLaunchingWithOptions: method.

    For example, when your app is relaunched by system, you can retrieve all the restoration identifiers for the central manager objects the system was preserving for your app, like this:

    - (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     
        NSArray *centralManagerIdentifiers =
            launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
        ...

    After you have the list of restoration identifiers, simply loop through it and reinstantiate the appropriate central manager objects.

    Implement the Appropriate Restoration Delegate Method

    After you have reinstantiated the appropriate central and peripheral managers in your app, restore them by synchronizing their state with the state of the Bluetooth system. To bring your app up to speed with what the system has been doing on its behalf (while it was not running), you must implement the appropriate restoration delegate method. For central managers, implement thecentralManager:willRestoreState: delegate method; for peripheral managers, implement theperipheralManager:willRestoreState: delegate method.

    In both of the above delegate methods, the last parameter is a dictionary that contains information about the managers that were preserved at the time the app was terminated. For a list of the available dictionary keys, see the Central Manager State Restoration Options constants in CBCentralManagerDelegate Protocol Reference and thePeripheral_Manager_State_Restoration_Options constants inCBPeripheralManagerDelegate Protocol Reference.

    To restore the state of a CBCentralManager object, use the keys to the dictionary that is provided in thecentralManager:willRestoreState: delegate method. As an example, if your central manager object had any active or pending connections at the time your app was terminated, the system continued to monitor them on your app’s behalf. As the following shows, you can use theCBCentralManagerRestoredStatePeripheralsKey dictionary key to get of a list of all the peripherals (represented byCBPeripheral objects) the central manager was connected to or was trying to connect to:

    - (void)centralManager:(CBCentralManager *)central
          willRestoreState:(NSDictionary *)state {
     
        NSArray *peripherals =
            state[CBCentralManagerRestoredStatePeripheralsKey];
        ...

    What you do with the list of restored peripherals in the above example depends on the use case. For instance, if your app keeps a list of the peripherals the central manager discovers, you may want to add the restored peripherals to that list to keep references to them. As described in “Connecting to a Peripheral Device After You’ve Discovered It,” be sure to set a peripheral’s delegate to ensure that it receives the appropriate callbacks.

    You can restore the state of a CBPeripheralManager object in a similar way by using the keys to the dictionary that is provided in theperipheralManager:willRestoreState: delegate method.

    Update Your Initialization Process

    After you have implemented the previous three required steps, you may want to take a look at updating your central and peripheral managers’ initialization process. Although this is an optional step, it can be important in ensuring that things run smoothly in your app. As an example, your app may have been terminated while it was in the middle of exploring the data of a connected peripheral. When your app is restored with this peripheral, it won’t know how far it made it the discovery process at the time it was terminated. You’ll want to make sure you’re starting from where you left off in the discovery process.

    For example, when initializing your app in thecentralManagerDidUpdateState: delegate method, you can find out if you successfully discovered a particular service of a restored peripheral (before your app was terminated), like this:

        NSUInteger serviceUUIDIndex =
            [peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,
            NSUInteger index, BOOL *stop) {
                return [obj.UUID isEqual:myServiceUUIDString];
            }];
     
        if (serviceUUIDIndex == NSNotFound) {
            [peripheral discoverServices:@[myServiceUUIDString]];
            ...

    As the above example shows, if the system terminated your app before it finished discovering the service, begin the exploring the restored peripheral’s data at that point by calling thediscoverServices:. If your app discovered the service successfully, you can then check to see whether the appropriate characteristics were discovered (and whether you already subscribed to them). By updating your initialization process in this manner, you’ll ensure that you’re calling the right methods at the right time.


    Posted by 삼스