iOS SwiftでBLEのサンプルを動かしてみる

# はじめに 
BLEとの接続について触れてみたいと思い、
「iOS × BLE Core Bluetoothプログラミング」のサンプル(Heart Rate）を
テキストに沿って実装してみました。 

## 基礎知識
Core BluetoothフレームワークというBLEを実装したデバイスとの通信をするクラス郡が用意されている。
(iOS5以上サポート）

| 用語 | 説明 |
|---|---|
|セントラル| データを提供される側（クライアント側のイメージ）|
|ペリフェラル | データを提供する側（サーバー側のイメージ）|

##### ■Core Bluetoothプログラミングガイドから引用

<img width="600" alt="スクリーンショット 2017-05-24 18.53.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/71694/ef233735-2541-a334-d79f-0138fbb912bb.png">

今回は、セントラル側だけの話をします。

ここからは、セントラルがペリフェラルからデータを定期的に取得する流れを
下記に説明します。

### ペリフェラルからデータを取得までのフロー

①セントラルマネージャーを起動する。
②ペリフェラルを検出する。
③ペリフェラルと接続する。
④ペリフェラルのサービスを検出する。
⑤サービス特性を検出する。
⑥サービス特性の値を取得する。

##### ■Core Bluetoothプログラミングガイドから引用
サービスとサービス特性の関係性は、下図のとおりです。

<img width="546" alt="スクリーンショット 2017-05-24 18.55.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/71694/6839aadd-7826-3d4a-da86-125f7a0392ee.png">

### 主な登場人物

| 用語 | 説明 |
|---|---|
|CBCentralManager| セントラル側のクラス|
|CBPeripheral | ペリフェラル側のクラス|
|CBService|サービスのクラス|
|CBCharacteristic| サービスの特性|
|CBCentralManagerDelegate |電源のON/OFFの検出、ペリフェラルの検出/接続|
|CBPeripheralDelegate |サービスの検出、サービス特性の取得|


### データを取得までのフローとソースコードの関係性

#### ①セントラルマネージャーを起動する。

```swift:
    centralManager = CBCentralManager(delegate: self, queue: nil)
```

#### ②ペリフェラルを検出する。
電源がONになるのを待って、スキャンする。

```swift:
    let services: [CBUUID] = [serviceUUID]
    centralManager?.scanForPeripherals(withServices: services,
                                           options: nil)
```

ちなみに、電源のONは、
CBCentralManagerDelegateのcentralManagerDidUpdateStateメソッドで通知される。

```swift:
    func centralManagerDidUpdateState(_ central: CBCentralManager) {

        switch central.state {

        //電源ONのとき
        case CBManagerState.poweredOn:
            break

        default:
            break
        }
    }
```

#### ③ペリフェラルと接続する。
ペリフェラルを検出したら、検出したペリフェラルに接続する。

```swift:
    central.connect(peripheral, options: nil)
```

ちなみに、ペリフェラルの検出は、CBCentralManagerDelegateの下記のメソッドで通知される。

```swift:
    func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String : Any],
                        rssi RSSI: NSNumber) {
    }
```

#### ④ペリフェラルのサービスを検出する。
接続されたら、接続したペリフェラルのサービスを検出する。

```swift:
   peripheral.discoverServices([serviceUUID])
```

ちなみに、接続されると、CBCentralManagerDelegateの下記のメソッドで通知される。

```swift:
    func centralManager(_ central: CBCentralManager,
                        didConnect peripheral: CBPeripheral) {
    }
```

#### ⑤サービス特性を検出する。
サービスを検出したら、サービスの特性を検出する。

```swift:
    peripheral.discoverCharacteristics([charcteristicUUID],
                                       for: (peripheral.services?.first)!)
```

ちなみに、サービスが検出されると、CBPeripheralDelegateの下記のメソッドが呼ばれる。

```swift:
    func peripheral(_ peripheral: CBPeripheral,
                    didDiscoverServices error: Error?) {
    }
```

#### ⑥サービス特性の値を取得する。
サービス特性が発見されると、CBPeripheralDelegateの下記のメソッドが呼ばれる。

```swift:
    func peripheral(_ peripheral: CBPeripheral,
                    didDiscoverCharacteristicsFor service: CBService,
                    error: Error?) {
    }
```

データが更新されると、CBPeripheralDelegateの下記のメソッドが呼ばれる

```swift:
    func peripheral(_ peripheral: CBPeripheral,
                    didUpdateValueFor characteristic: CBCharacteristic,
                    error: Error?) {

        // characteristic.valueから値を取得する(Date型）
    }
```

### サンプル
心拍数モニターアプリを写経し、Swift3に変換してみました。

セントラル側は、本アプリです。
ペリフェラル側は、[Light Blue](https://itunes.apple.com/jp/app/lightblue/id639944780?mt=12)のHeart Rateを利用しています。

```swift:ViewController.swift
import UIKit
import CoreBluetooth

//Central : 本アプリ
//Peripheral : Light Blue
final class ViewController: UIViewController {

    //GATTサービス(Heart Rate) https://www.bluetooth.com/ja-jp/specifications/gatt/services
    let kServiveUUIDHeartRate = "0x180D"

    //Attribute Types (UUIDs)
    let kCharacteristcUUIDHeartRateMeasurement = "0x2A37"

    var centralManager: CBCentralManager!
    var peripheral: CBPeripheral!
    var serviceUUID : CBUUID!
    var charcteristicUUID: CBUUID!

    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
    }

    /// セントラルマネージャー、UUIDの初期化
    private func setup() {
        centralManager = CBCentralManager(delegate: self, queue: nil)
        serviceUUID = CBUUID(string: kServiveUUIDHeartRate)
        charcteristicUUID = CBUUID(string: kCharacteristcUUIDHeartRateMeasurement)
    }
}

//MARK : - CBCentralManagerDelegate
extension ViewController: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(_ central: CBCentralManager) {

        switch central.state {

        //電源ONを待って、スキャンする
        case CBManagerState.poweredOn:
            let services: [CBUUID] = [serviceUUID]
            centralManager?.scanForPeripherals(withServices: services,
                                               options: nil)
        default:
            break
        }
    }

    /// ペリフェラルを発見すると呼ばれる
    func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String : Any],
                        rssi RSSI: NSNumber) {

        self.peripheral = peripheral
        centralManager?.stopScan()

        //接続開始
        central.connect(peripheral, options: nil)
    }

    /// 接続されると呼ばれる
    func centralManager(_ central: CBCentralManager,
                        didConnect peripheral: CBPeripheral) {

        peripheral.delegate = self
        peripheral.discoverServices([serviceUUID])
    }
}

//MARK : - CBPeripheralDelegate
extension ViewController: CBPeripheralDelegate {

    /// サービス発見時に呼ばれる
    func peripheral(_ peripheral: CBPeripheral,
                    didDiscoverServices error: Error?) {

        if error != nil {
            print(error.debugDescription)
            return
        }

        //キャリアクタリスティク探索開始
        peripheral.discoverCharacteristics([charcteristicUUID],
                                           for: (peripheral.services?.first)!)
    }

    /// キャリアクタリスティク発見時に呼ばれる
    func peripheral(_ peripheral: CBPeripheral,
                    didDiscoverCharacteristicsFor service: CBService,
                    error: Error?) {

        if error != nil {
            print(error.debugDescription)
            return
        }

        peripheral.setNotifyValue(true,
                                  for: (service.characteristics?.first)!)
    }

    /// データ更新時に呼ばれる
    func peripheral(_ peripheral: CBPeripheral,
                    didUpdateValueFor characteristic: CBCharacteristic,
                    error: Error?) {

        if error != nil {
            print(error.debugDescription)
            return
        }

        updateWithData(data: characteristic.value!)
    }

    private func updateWithData(data : Data) {
        print(#function)

        let reportData = data.withUnsafeBytes {
            [UInt8](UnsafeBufferPointer(start: $0, count: data.count))
        }

        /// Format Bitが0 or 1
        if (reportData.first != nil) && 0x01 == 0 {
            print("BPM: \(reportData.last!)")
        } else {
            print("BPM : \(CFSwapInt16LittleToHost(UInt16(reportData.last!)))")
        }
    }
}
```

# まとめ
今回は、BLEとの接続の感触をみるために、セントラルのみを触ってみました。
HTTP/HTTPSと違い、用語等知らない点が多いため、少しずつ学習していきたいと思います。

#### 参考
[Core Bluetooth with Swift （ObjCのおまけ付き）](http://qiita.com/shu223/items/78614325ce25bf7f4379)