CoreBluetoothでBLEセンサーの情報を取得する

iOSのCore Bluetooth APIを使ってBLE温湿度センサーから情報を読み取るメモです。BLEセンサーはSANWA SUPPLYの[MM-BLEBC7](https://www.sanwa.co.jp/product/syohin.asp?code=MM-BLEBC7)を使用しました。

# Core Bluetoothとは
Core BluetoothはAppleの公式フレームワークで、iOSにランタイムが内臓されているので外付けのライブラリーは不要です。importするだけで使うことができます。

```swift:swift
import CoreBluetooth
```
# Core Bluetoothの使用方法
Core Bluetooth APIはBEL機器をペリフェラルとして定義していて、ペリフェラルの中に複数のサービス、サービスの中に複数のキャラクタリスティックとう具合に階層化されています。そのため、BLE機器からデータを取得するためには、それぞれの階層を順番に辿って、目的のデータを見つける必要があります。

## 手順例
1. ペリフェラルの検出
1. ペリフェラルへ接続
1. サービスのディスカバー
1. キャラクタリスティックの取得
1. データ取得

なのですが、MM-BLEBC7ではすべてのサービス、すべてのキャラクタリスティックを探しても、温度や湿度のデータが見つかりませんでした。はたと、ペリフェラルの検出時のアドバイス情報をダンプしたところ、Dictionaryの「kCBAdvDataServiceData」の中に入っていました。今回の場合、温湿度データの取得は上の手順の１だけで良いことになります。

```
["kCBAdvDataServiceData": {FFE1 = {length = 13, bytes = 0xa1010e183d4d19１xxxxxxxxxxxx};}]
```
先頭の0xa101は固定で、その後に電池残量、温度、湿度を示します。
xxxxxxxxxxxxのところはMACアドレスが入っています。(リトルエンディアン)

# サンプルコード

```swift:swift
import UIKit
import CoreBluetooth

class ViewController: UIViewController {
    var centralManager: CBCentralManager!
    var peripheral: CBPeripheral!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // セントラルマネージャの生成
        self.centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
    }
}

extension ViewController: CBCentralManagerDelegate {
    // Bluetoothの状態
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case CBManagerState.poweredOn:
            print("poweredOn")

            // スキャン開始
            self.centralManager.scanForPeripherals(withServices: nil, options: nil)
        
        case CBManagerState.poweredOff:
            print("poweredOff")
        
        default:
            break
        }
    }

    // ペリフェラルの検出
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        if (peripheral.name == nil || peripheral.name == "nRF5x") {
            print("advertisementData:", advertisementData)
            if let dic = advertisementData["kCBAdvDataServiceData"] as? Dictionary<CBUUID, Data> {
                if let data = dic[CBUUID(string:"FFE1")] {
                    print("data:", data.map({ String(format:"%02x ", $0) }).joined())
                    
                    if (data.count == 13) {
                        let frameType = data[0] // 0xA1固定
                        let productMode = data[1] // 0x01固定
                        let batteryLevel = data[2] // 電池残量(100%)
                        let temperature = Float(data[3]) + Float(data[4]) / 256 // 温度：固定小数点(1バイト目:整数部、2バイト目:少数部)
                        let humidity = Float(data[5]) + Float(data[6]) / 256 // 湿度：固定小数点(1バイト目:整数部、2バイト目:少数部)
                        let macAddress = data[7...12].reversed()// MACアドレス(リトルエンディアン)

                        print("frameType:", String(format: "%02x", frameType))
                        print("productMode:", String(format: "%02x", productMode))
                        print("batteryLevel:", batteryLevel)
                        print("temperature:", temperature)
                        print("humidity:", humidity)
                        print("macAddress:", macAddress.map({ String(format:"%02x ", $0) }).joined())
                        
                        //スキャン停止
                        self.centralManager.stopScan()
                    }
                }
            }
        }
    }
}
```


#参考
[[翻訳] 初めてのCore Bluetooth](https://www.ditto.live/blog/posts/jp-getting-started-with-core-bluetooth)