【iOS/SwiftUI】Bluetooth Low Energy(BLE)で取得したデータを表示する

SwiftUIに戻る

目次

概要

Bluetoothでペリフェラルと接続ができたらどのようなサービスだったか、そしてそのデータの内容を取得する。
その時にキーワードとなるのは以下の用語だ。

名前役割
サービスキャラクタリスティックを機能単位で一括りにしたラベル。
要はデータの名前のようなものだろう。
キャラクタリスティックペリフェラル機器がセントラル機器に公開して共有するデータ構造。
データの内容のようなものだろう。

以上を踏まえて実装していこう。

ソースコード

import CoreBluetooth
import Foundation

class BluetoothManager: NSObject {
    private var centralManager: CBCentralManager
    private var characteristic: CBCharacteristic
    
    override init() {
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
    }
}

extension BluetoothManager: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unknown:
            print("Bluetooth is unknown.")
        case .resetting:
            print("Bluetooth is resetting.")
        case .unsupported:
            print("Bluetooth is unsupported.")
        case .unauthorized:
            print("Bluetooth is unauthorized.")
        case .poweredOff:
            print("Bluetooth is powered off.")
        case .poweredOn:
            print("Bluetooth is powered on.")
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print("Peripheralデバイスを発見: \(peripheral.name) RSSI: \(RSSI)")
        centralManager.connect(peripheral, options: nil)
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("Peripheralデバイスに接続しました: \(peripheral.name)")
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) {
        print("Peripheralデバイスへの接続に失敗しました: \(error?.localizedDescription ?? "不明なエラー")")
    }
}

extension BluetoothManager: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {
        if let error = error {
            print("サービスの探索中にエラーが発生しました: \(error.localizedDescription)")
        }
        
        guard let services = peripheral.services else {
            return
        }
        
        for service in services {
            print("service UUID: \(service.uuid)")
            if service.uuid == CBUUID(string: "FFF1") {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
        if let error = error {
            print("Characteristicsの探索中にエラーが発生しました: \(error.localizedDescription)")
        }
        
        guard let characteristics = service.characteristics else {
            return
        }
        
        for characteristic in characteristics {
            print("Characteristics UUID: \(characteristic.uuid)")
        }
        
        if characteristic.properties.contains(.read) {
            // Characteristicの読み取り処理
            peripheral.readValue(for: characteristic)
        }
        if characteristic.properties.contains(.notify) {
            // 通知の受け取りを有効化
            peripheral.setNotifyValue(true, for: characteristic)
        }
        if characteristic.properties.contains(.write) {
            // PeripheralとCharacteristicのプロパティに値を格納
            self.peripheral = peripheral
            self.characteristic = characteristic
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: (any Error)?) {
        if let error = error {
            print("Characteristicsの探索中にエラーが発生しました: \(error.localizedDescription)")
        }
        
        guard let data = characteristic.value else {
            print("characteristicの値が存在しません。")
        }
        
        if let stringValue = String(data: data, encoding: .utf8) {
            print("読み取った値: \(stringValue)")
        } else {
            print("dataをUTF-8に変換できません。")
        }
    }
}

詳細

CBPeripheralDelegateクラスのメソッドを使用する。

まず、サービス特性が発見された時に以下のメソッドが呼ばれる。
今回の実装は探索したすべてのサービス特性のキャラクタリスティックを表示している。

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: (any Error)?) {
    if let error = error {
        print("サービスの探索中にエラーが発生しました: \(error.localizedDescription)")
    }
        
    guard let services = peripheral.services else {
        return
    }
        
    for service in services {
        print("service UUID: \(service.uuid)")
        if service.uuid == CBUUID(string: "FFF1") {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
}

今回はif文を設けているが、これは特定のものからの情報のみ受け取りたい場合のために実装している。
イベント会場などでBLEの機能を使う場合、想定外の情報を取得しないようにするためだ。

そして、取得したデータの内容(キャラクタリスティック)を見ていこう。

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
    if let error = error {
        print("Characteristicsの探索中にエラーが発生しました: \(error.localizedDescription)")
    }
        
    guard let characteristics = service.characteristics else {
        return
    }
        
    for characteristic in characteristics {
        print("Characteristics UUID: \(characteristic.uuid)")
    }
        
    if characteristic.properties.contains(.read) {
        // Characteristicの読み取り処理
        peripheral.readValue(for: characteristic)
    }
    if characteristic.properties.contains(.notify) {
        // 通知の受け取りを有効化
        peripheral.setNotifyValue(true, for: characteristic)
    }
    if characteristic.properties.contains(.write) {
        // PeripheralとCharacteristicのプロパティに値を格納
        self.peripheral = peripheral
        self.characteristic = characteristic
    }
}

最初のほうの動きは基本的に共通。
取得できなかった場合の分岐処理や、どの端末から受け取ったかの判別のための処理だ。

大事なのはそれ以降。properties.containsメソッドの部分。
どのようなプロパティのデータを受け取ったかで細かい仕様が変わる。
それにまずは分岐させている。
今回は例題なのでただ分けているだけだが、今後データの修正などを行う時に必要になる知識だろう。

本項ではデータの読み取り処理を行っ値流。
readValue(for:)メソッドを呼び出しているが、このメソッドが正常終了することで以下のdidUpdateNotificationStateForメソッドが呼ばれる。

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: (any Error)?) {
    if let error = error {
        print("Characteristicsの探索中にエラーが発生しました: \(error.localizedDescription)")
    }
        
    guard let data = characteristic.value else {
        print("characteristicの値が存在しません。")
    }
        
    if let stringValue = String(data: data, encoding: .utf8) {
        print("読み取った値: \(stringValue)")
    } else {
        print("dataをUTF-8に変換できません。")
    }
}

このstringValueが受け取ったデータの文字列だ。
今回は文字列だが、変換のやり方によってはJSON形式やカスタム形式のデータも可能ではないかと思われる。

可能なら実装してデモ結果などを見せたい限りだ…

参考ページ

SwiftUIに戻る