iOS Core Bluetooth (Swift) を使用してみた
はじめに
今回はCore Boluetoothを使用してマイコンに文字列を送信してみました。
用語の意味を理解する事に苦労したので概要に意味をまとめています。
Core Bluetooth の概要
クライアント/サーバの考え方が基盤になっています。
ペリフェラル : サービスを提供 (サーバ)
セントラル : サービスを利用 (クライアント)
ペリフェラルは見つけてもらう為にアドバタイズパケットと呼ばれる自身の情報を発信しています。
セントラルはアドバタイズパケットを探して周囲のペリフェラルを見つけます。
接続に成功したペリフェラルと通信する際は、キャラクタリスティックというオブジェクトを使用します。
例えばペリフェラルが天気のサービスを提供するとすれば、気温や湿度というキャラクタリスティックがあります。
Core Bluetooth のコード
CoreBluetooth をインポートします。使用するクラスには CBCentralManagerDelegate, CBPeripheralDelegate が必要です。
centralManagerDidUpdateState メソッドが無いとビルドエラーになります。
import CoreBluetooth class bluetoothService: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch (central.state) { case .PoweredOff: // BLE PoweredOff case .PoweredOn: // BLE PoweredOn case .Resetting: // BLE Resetting case .Unauthorized: // BLE Unauthorized case .Unknown: // BLE Unknown case .Unsupported: // BLE Unsupported } } … }
周囲のペリフェラルを見つけるためにCBCentralManagerを使用します。
var centralManager: CBCentralManager? override init () { super.init() centralManager = CBCentralManager(delegate: self, queue: nil) }
scanForPeripheralsメソッドの引数withSerivicesにnilを渡すと全てのペリフェラルを探してくれます。nilの代わりにサービスのUUIDを渡すとUUIDが一致するペリフェラルを探します。
func scanStart() { if manager!.isScanning == false { // サービスのUUIDを指定しない centralManager!.scanForPeripherals(withServices: nil, options: nil) // サービスのUUIDを指定する // let service: CBUUID = CBUUID(string: "サービスのUUID") // centralManager!.scanForPeripherals(withServices: service, options: nil) } }
ペリフェラルが見つかると centralManager のデリゲートメソッドが呼び出されます。今回は見つけたペリフェラルは配列に保存して、後で目的のペリフェラルを探して接続するようにしました。
var peripherals: [CBPeripheral] = [] func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { peripherals.append(peripheral) }
配列から目的のペリフェラルをデバイス名で探して connectメソッドで接続しています。データを送信する際に CBPeripheral が必要になるので目的のペリフェラルはメンバ変数に保持します。
var cbPeripheral: CBPeripheral? = nil func connect() { for peripheral in peripherals { if peripheral.name != nil && peripheral.name == "デバイス名" { cbPeripheral = peripheral manager?.stopScan() break; } } if cbPeripheral != nil { manager!.connect(cbPeripheral!, options: nil) } }
第二引数が違う centralManager デリゲートメソッドが接続の可否によって呼び出されます。接続が成功した場合には、通信に使うサービスをdiscoverServicesメソッドで探します。
// 接続が成功すると呼ばれるデリゲートメソッド func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { cbPeripheral?.delegate = self // 指定のサービスを探す let services: [CBUUID] = [CBUUID(string: "サービスのUUID")] cbPeripheral!.discoverServices(services) // すべてのサービスを探す // cbPeripheral!.discoverServices(nil) } // 接続が失敗すると呼ばれるデリゲートメソッド func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { println("connection failed.") }
サービスが見つかるとperipheral デリゲートメソッドが呼び出されます。見つかったサービスの中から今度は discoverCharacteristics メソッドを使用してキャラクタリスティックを探します。
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { let serviceUUID: CBUUID = CBUUID(string: "サービスのUUID") for service in cbPeripheral!.services! { if(service.uuid == serviceUUID) { cbPeripheral?.discoverCharacteristics(nil, for: service) } } }
キャラクタリスティックが見つかると引数が違うperipheralデリゲートメソッドが呼び出されます。
今回使用したマイコンにはNotifyとWriteという2種類のキャラクタリスティックがあったためそれぞれ識別しています。
属性がWriteのキャラクタリスティックを使用して文字列データをマイコンに送信するため、そちらはメンバ変数に保持しています。
var writeCharacteristic: CBCharacteristic? = nil func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { for characreristic in service.characteristics!{ if characreristic.uuid.uuidString == "属性がNotifyのキャラクタリスティックのUUID" { //Notificationを受け取るハンドラ peripheral.setNotifyValue(true, for: i) } if characreristic.uuid.uuidString == "属性がWriteのキャラクタリスティックのUUID" { writeCharacteristic = characreristic } } }
蛇足ですが今回使用したマイコンが「ペリフェラルの接続成功」と「Notifyが有効になる」場合に接続が確立されるとのことで setNotifyValue メソッドを実行するとマイコンの接続中LEDが緑色に点灯してくれました。
属性がWriteのキャラクタリスティックを使用して文字列データの送信を行います。
writeValue メソッドを使用することで UTF8 に変更した文字列データをマイコンに送信しています。
func sendMessage(message: String) { var command = message + "\n" let data = command.data(using: String.Encoding.utf8, allowLossyConversion:true) cbPeripheral!.writeValue(data! , for: writeCharacteristic!, type: .withoutResponse) }
ペリフェラルが文字列データの受信に成功するとNotificationが返ってきます。
そのNotificationを検出すると引数が違う peripheral デリゲートメソッドが呼び出されます。
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { if let error = error { println("error \(error)") return } println("received Notification") }
Core Bluetooth を使用する中で受信を行う場合には以下のデリゲートメソッドを使用します。
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { let notify: CBUUID = CBUUID(string: "ペリフェラルから値が受信できるキャラクタリスティックのUUID") if characteristic.uuid.uuidString == notify.uuidString { let message = String(bytes: characteristic.value!, encoding: String.Encoding.ascii) println("received \(message)") } }
参考資料
iOSでのBluetooth通信入門 :CoreBluetooth プログラミングガイド 解説 : Core Bluetoothの概要 | IoT
Working with Core Bluetooth in iOS 11 | Swift Tutorial
Swift – Bluetooth Low Energy communication using Flow Controllers – Wojciech Kulik
iOS SwiftでBLEのサンプルを動かしてみる - Qiita
CoreBluetoothでNotificationを受け取る! – 野生のプログラマZ
Core Bluetooth with Swift (ObjCのおまけ付き) - Qiita
CoreBluetoothでiPhoneとMacをBLE接続してみた。 - Qiita
RaspberryPi3 を Bluetooth(Peripheral) にして iPhone と通信してみた - Qiita