Help us understand the problem. What is going on with this article?

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

More than 3 years have 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のおまけ付き)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした