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

【Swift5】Bluetoothクラス実装の備忘録

はじめに

iOSでBluetooth接続処理を実装したときの備忘録です。

環境

  • Xcode10.3
  • Swift5

概要

import CoreBluetooth

端末検索〜接続、操作までCoreBluetoothで全て実装することができました。

主役となるManagerクラス

  • CBCentralManager・・・アドバタイズされた周辺機器のスキャン、検出、接続や、検出または接続されたリモート周辺機器の管理に使用されます。

  • CBPeripheralManager・・・ローカル周辺機器のGeneric Attribute Profile(GATT)データベース内で公開されたサービスを管理し、これらのサービスをアドバタイズするために使用されます。

スクリーンショット 2019-09-02 8.17.39.png

スクリーンショット 2019-09-03 9.09.19.png

なお、Bluetoothの操作は、主に以下のデリゲートメソッドを使用して行います。

実装フロー

①セントラルマネージャーを起動する。
②ペリフェラルを検出する。
③ペリフェラルと接続する。
④ペリフェラルのサービスを検出する。
⑤サービス特性を検出する。
⑥サービス特性の値を取得する。
引用:iOS SwiftでBLEのサンプルを動かしてみる

スクリーンショット 2019-10-01 1.27.38.png

BluetoothServiceクラスの実装

今回は、Constに使用するサービスのUUIDを定義し、
スキャン時には、タイムアウトエラー実装用に、タイマーを設定するようにしました。
なお、対象のペリフェラルの検出には、ペリフェラルの名前を指定するようにしています。

Const.swift
struct Const {

    struct Bluetooth {
        /// サービスのUUID
        struct Service {
            static let kUUID: String = "cd000000-1234-1234-1234-hogehogehoge"
        }

        /// サービスのキャラクタリスティックのUUID
        struct Characteristic {
            static let kUUID01 = "cd100001-1234-1234-1234-hogehogehoge"
            static let kUUID02 = "cd100002-1234-1234-1234-hogehogehoge"
        }

        static let kPeripheralName = "Hoge Bluetooth"
    }

}
BluetoothService.swift
final class BluetoothService: NSObject {
    /// 接続先の機器
    private var connectPeripheral: CBPeripheral? = nil
    /// 対象のキャラクタリスティック
    private var writeCharacteristic: CBCharacteristic? = nil

    override init() {
        self.centralManager = CBCentralManager()
        self.peripheralManager = CBPeripheralManager()
    }

    // MARK: - Public Methods

    /// Bluetooth接続のセットアップ
    func setupBluetoothService() {
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
        self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    }

    /// スキャン開始
    func startBluetoothScan() {
        print("スキャン開始")
        // タイマーを設定する
        self.scanTimer = Timer.scheduledTimer(timeInterval: TimeInterval(10),
                                              target: self,
                                              selector: #selector(self.timeOutScanning),
                                              userInfo: nil,
                                              repeats: false)
        // 機器を検出
        if self.centralManager.isScanning == false {
            self.centralManager.scanForPeripherals(withServices: nil, options: nil)
        }
    }

    /// スキャン停止
    func stopBluetoothScan() {
        self.centralManager.stopScan()
        // Timerを削除
        self.scanTimer?.invalidate()
        self.scanTimer = nil
    }

    /// 機器に接続
    func connectPeripheral() {
        guard let connectPeripheral = self.connectPeripheral else {
            // 失敗処理
            return
        }
        self.centralManager.connect(connectPeripheral, options: nil)
    }
}

CBCentralManagerDelegate

BluetoothService.swift
// MARK: CBCentralManagerDelegate

extension BluetoothService: CBCentralManagerDelegate {

    /// Bluetoothのステータスを取得する(CBCentralManagerの状態が変わる度に呼び出される)
    ///
    /// - Parameter central: CBCentralManager
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOff:
            print("Bluetooth PoweredOff")
            break
        case .poweredOn:
            print("Bluetooth poweredOn")
            break
        case .resetting:
            print("Bluetooth resetting")
            break
        case .unauthorized:
            print("Bluetooth unauthorized")
            break
        case .unknown:
            print("Bluetooth unknown")
            break
        case .unsupported:
            print("Bluetooth unsupported")
            break
        }
    }

    /// スキャン結果取得
    ///
    /// - Parameters:
    ///   - central: CBCentralManager
    ///   - peripheral: CBPeripheral
    ///   - advertisementData: アドバタイズしたデータを含む辞書型
    ///   - RSSI: 周辺機器の現在の受信信号強度インジケータ(RSSI)(デシベル単位)
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        // 対象機器のみ保持する
        if let peripheralName = peripheral.name,
            peripheralName.contains(Const.Bluetooth.kPeripheralName) {
            // 対象機器のみ保持する
            self.connectPeripheral = peripheral
            // 機器に接続
            print("機器に接続:\(String(describing: peripheral.name))")
            self.centralManager.connect(peripheral, options: nil)
        }
    }

    /// 接続成功時
    ///
    /// - Parameters:
    ///   - central: CBCentralManager
    ///   - peripheral: CBPeripheral
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("接続成功")
        self.connectPeripheral = peripheral
        self.connectPeripheral?.delegate = self
        // 指定のサービスを探索
        if let peripheral = self.connectPeripheral {
            peripheral.discoverServices([CBUUID(string: Const.Bluetooth.Service.kUUID)])
        }
        // スキャン停止処理
        self.stopBluetoothScan()
    }

    /// 接続失敗時
    ///
    /// - Parameters:
    ///   - central: CBCentralManager
    ///   - peripheral: CBPeripheral
    ///   - error: Error
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("接続失敗:\(String(describing: error))")
    }

    /// 接続切断時
    ///
    /// - Parameters:
    ///   - central: CBCentralManager
    ///   - peripheral: CBPeripheral
    ///   - error: Error
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("接続切断:\(String(describing: error))")
    }

}

CBPeripheralDelegate

BluetoothService.swift
// MARK: CBPeripheralDelegate

extension WearBluetoothService: CBPeripheralDelegate {

    /// キャラクタリスティック探索時(機器接続直後に呼ばれる)
    ///
    /// - Parameters:
    ///   - peripheral: CBPeripheral
    ///   - error: Error
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard error == nil else {
            // スキャン停止処理
            self.stopScan()
            // 失敗処理
            return
        }

        if let peripheralServices = peripheral.services {
            for service in peripheralServices where service.uuid == CBUUID(string: Const.Bluetooth.Service.kUUID) {
                print("キャラクタリスティック探索")
                // キャラクタリスティック探索開始
                let characteristicUUIDArray: [CBUUID] = [CBUUID(string: Const.Bluetooth.Characteristic.kUUID01),
                                                         CBUUID(string: Const.Bluetooth.Characteristic.kUUID02)]
                peripheral.discoverCharacteristics(characteristicUUIDArray, for: service)
            }
        }
    }
}

CBPeripheralManagerDelegate

BluetoothService.swift
// MARK: CBPeripheralManagerDelegate

extension WearBluetoothService: CBPeripheralManagerDelegate {

    /// 端末のBluetooth設定を取得(WearBluetoothServiceの使用開始時、端末のBluetooth設定変更時に呼ばれる)
    ///
    /// - Parameter peripheral: CBPeripheralManager
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        if peripheral.state == .poweredOn {
            // サービスを登録
            let service = CBMutableService(type: CBUUID(string: Const.Bluetooth.Service.kUUID), primary: true)
            self.peripheralManager.add(service)
        }
    }

    /// キャラクタリスティック発見時(機器接続直後に呼ばれる)
    ///
    /// - Parameters:
    ///   - peripheral: CBPeripheral
    ///   - service: CBService
    ///   - error: Error
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard error == nil else {
            // スキャン停止処理
            self.stopScan()
            print("キャラクタリスティック発見時:\(String(describing: error))")
            // エラー処理
            return
        }
        guard let serviceCharacteristics = service.characteristics else {
            // スキャン停止処理
            self.stopScan()
            // エラー処理
            return
        }
        // キャラクタリスティック別の処理
        for characreristic in serviceCharacteristics {
            if characreristic.uuid == CBUUID(string: Const.Bluetooth.Characteristic.kUUID01) {
                // データ書き込み用のキャラクタリスティックを保持
                self.writeCharacteristic = characreristic
                print("Write 01")
            }
            if characreristic.uuid == CBUUID(string: Const.Bluetooth.Characteristic.kUUID02) {
                isNotDiscoverCharacteristic02 = false
                peripheral.setNotifyValue(true, for: characreristic)
                print("Notify 02")
            }
        }
    }

    /// キャラクタリスティックにデータ書き込み時(コマンド送信時に呼ばれる)
    ///
    /// - Parameters:
    ///   - peripheral: CBPeripheral
    ///   - characteristic: CBCharacteristic
    ///   - error: Error
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        guard error == nil else {
            print("キャラクタリスティックデータ書き込み時エラー:\(String(describing: error))")
            // 失敗処理
            return
        }
        // 読み込み開始
        peripheral.readValue(for: characteristic)
    }

    /// キャラクタリスティック値取得・変更時(コマンド送信後、受信時に呼ばれる)
    ///
    /// - Parameters:
    ///   - peripheral: CBPeripheral
    ///   - characteristic: CBCharacteristic
    ///   - error: Error
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard error == nil else {
            print("キャラクタリスティック値取得・変更時エラー:\(String(describing: error))")
            // 失敗処理
            return
        }
        guard let data = characteristic.value else {
            // 失敗処理
            return
        }
        // データが渡ってくる
        print(data)
    }
}

まとめ

Dalegateメソッドの呼ばれ順(実際処理のフロー)は、docになるべく記載するようにしました。
CoreBluetoothを扱うに当たって、Dataの変換周りに関しても勉強になったので、近々まとめられたらと思います。

参考

Why do not you register as a user and use Qiita more conveniently?
  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
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