iOS
BLE
Swift
Swift3.0

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

More than 1 year has passed since last update.

はじめに

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

基礎知識

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

用語 説明
セントラル データを提供される側(クライアント側のイメージ)
ペリフェラル データを提供する側(サーバー側のイメージ)
■Core Bluetoothプログラミングガイドから引用

スクリーンショット 2017-05-24 18.53.52.png

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

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

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

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

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

サービスとサービス特性の関係性は、下図のとおりです。

スクリーンショット 2017-05-24 18.55.09.png

主な登場人物

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

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

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

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

②ペリフェラルを検出する。

電源がONになるのを待って、スキャンする。

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

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

    func centralManagerDidUpdateState(_ central: CBCentralManager) {

        switch central.state {

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

        default:
            break
        }
    }

③ペリフェラルと接続する。

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

    central.connect(peripheral, options: nil)

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

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

④ペリフェラルのサービスを検出する。

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

   peripheral.discoverServices([serviceUUID])

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

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

⑤サービス特性を検出する。

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

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

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

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

⑥サービス特性の値を取得する。

サービス特性が発見されると、CBPeripheralDelegateの下記のメソッドが呼ばれる。

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

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

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

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

サンプル

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

セントラル側は、本アプリです。
ペリフェラル側は、Light BlueのHeart Rateを利用しています。

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のおまけ付き)