某高専生の某高専生による高専生のための...

技術メモ / 車 / 音楽 / 雑記 / etc...

Swift4.2でBluetooth通信の簡単な実装

みなさまご無沙汰です。かれこれ前回のブログから2ヶ月近く経ってました...。

その間に、シリコンバレーに起業するための研修などいってたのですが、その記事はいつか書きます...。

今回はあるプロジェクトでiOSのBluetooth周りを担当することになったので、その知見をまとめておきます。

通信関係は、バージョンが上がるたびに変わったりするので、過去の遺産になるのも早そうだけど....😇

環境情報

  • Xcode 10.1 (10B61)
  • Swift4.2

実装

とりあえず Bluetooth を使うには CoreBluetooth が必要になるので、importします

import CoreBluetooth

そのほかに継承するべきクラスとして、CBCentralManagerDelegate, CBPeripheralDelegate があるので、継承します。

ここで、すべてのファイルに全ての implementメソッドを書いてもいいのですが、チームでソース管理をしたりすると、何かと面倒なので、extension として分割することにしました。

ディレクトリ構成としては、以下の通りにしておきました。

ProjectDir/
┠ ViewController.swift
┠ BluetoothController.swift
┗ BluetoothExt/
 ┠ CBCentralManagerDelegate.swift
 ┗ CBPeripheralDelegate.swift

また、Bluetooth 関連のクラスは複数のクラスから呼び出されることが想定されます。その時に毎回インスタンスが生成されると、ちょっとややこしいのでBluetooth関連のクラスはシングルトンにして、生成は一回のみ、あとは初回生成されたインスタンスを使うことにします。

・ViewController.swift

import UIKit

class ViewController: UIViewController
{
    /**
     * Bluetooth制御クラスインスタンス
     **/
    var bluetoothController = BluetoothController.shared()
}

・BluetoothController.swift

import UIKit
import CoreBluetooth

class BluetoothController: UIViewController
{
    // シングルトンクラスを生成
    private static let sharedInstance : BluetoothController =
    {
        return BluetoothController()
    }()
    
    // Sharedインスタンスを返す
    class func shared() -> BluetoothController
    {
        return self.sharedInstance
    }
    
    /**
     * Bluetooth
     **/
    var centralManager       : CBCentralManager!  // Bluetooth_CentralManager
    var targetPeripheral     : CBPeripheral!      // 接続先のBluetooth情報を保持
    var targetService        : CBService!         // 対象BLE端末の接続サービスを保持
    var targetCharacteristic : CBCharacteristic!  // 対象のBLE端末のCharacteristicを保持
}

・ CBCentralManagerDelegate.swift

import UIKit
import CoreBluetooth

/**
 * Bluetooth制御に関するデリゲートをViewControllerに拡張
 */
extension BluetoothController : CBCentralManagerDelegate
{
    /**
     * CentralManagerのステートが更新された時に呼び出されるコールバック関数
     **/
    public func centralManagerDidUpdateState(_ central: CBCentralManager)
    {
        // セントラルの状態を取得
        switch central.state
        {
            case .poweredOff:   // Bluetooth is OFF.
                break
            case .poweredOn:    // Bluetooth is ON.
                // Peripheralが使用できるサービスを指定
                // Peripheral端末の検索を開始
                break
            case .resetting:    // Bluetooth is Resetting. Connection was momentarily lost.
                break
            case .unauthorized: // Not Authorized.
                break
            case .unsupported:  // Bluetooth is not Support.
                break
            case .unknown:      // Somethings Error.
                break
            default:
                break
        }
    }
    
    /**
     * PheripheralのScanが成功した時
     **/
    public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
    {
        // BLE端末の検索を止める
        self.centralManager.stopScan()
        
        // 接続先のBLE端末としてデータ保持
        self.targetPeripheral = peripheral
        
        // 接続開始
        self.centralManager.connect(self.targetPeripheral, options: nil)
    }
    
    /**
     * Pheripheralに接続した時
     **/
    public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
    {
        // 検索に関するメソッドデリゲートを自分自身にバインド
        self.targetPeripheral.delegate = self
        // 対象のBLE端末の提供しているサービスを検索
        self.targetPeripheral.discoverServices(nil)
    }
    
    /**
     * Pheripheralと接続が切れた時
     **/
    public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
    {
        print(#function)
        if let _ = error
        {
            return
        }
    }
    
    /**
     * Pheripheralへの接続が失敗した時
     **/
    public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
    {
        print(#function)
        if let _ = error
        {
            return
        }
    }
}

・ CBPeripheralDelegate.swift

import UIKit
import CoreBluetooth

/**
 * 接続先のBLE端末間通信制御デリゲートをViewControllerに拡張
 **/
extension BluetoothController : CBPeripheralDelegate
{
    /**
     * BLE端末が提供してるサービスの検索が終わった
     **/
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
    {
        print(#function)
        if let _ = error
        {
            return
        }
        
        // 取得したサービスを一覧で表示
        for service in peripheral.services!
        {
            self.targetService = service
            
            // BLE端末検索に関するメソッドを自分自身にバインド
            self.targetPeripheral.delegate = self
            // 対象BLE端末のCharacteristicの検索
            self.targetPeripheral.discoverCharacteristics(nil, for: self.targetService)
        }
    }
    
    /**
     * 対象BLE端末のCharacteristicの検索
     **/
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
    {
        print(#function)
        if let _ = error
        {
            return
        }
        
        for characteristic in service.characteristics!
        {
            // poproのCharacteristicCBUUIDと対象のCBUUIDが同じか
        }
        
        /***
          peripheralの情報(GATT情報)はiOS端末がキャッシュする
          新規情報を得るためにはSwiftから操作することはできない
          iOS端末のBluetoothスイッチをOn/Offするしか今の所方法はない
         ***/
    }
    
    
    /**
     * Characteristic読み込み時のコールバック関数
     **/
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
    {
        print(#function)
        if let _ = error
        {
            return
        }
    }
    
    /**
     * BLE端末へのCharacteristic書き込みが完了した時に呼び出されるコールバック関数
     **/
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?)
    {
        print(#function)
        if let _ = error
        {
            return
        }
    }
}

基本的にはこれだけでBluetooth通信を使えるようになりますが、自分で接続しようとしているBLE端末のUUIDなどを指定して検索しないと、特定の端末に接続することはできないので要注意です。