モケラ

Tech Sheets

mokelab

CoreBluetoothを使ってBLE Central側を実装する

最終更新日:2022-05-11

CoreBluetoothを使って、Peripheralの、特定のサービスの特定のCharacteristicの値を読み取るには、次の6ステップで実装します。実装済みのサンプルはこちら

  • CBCentralManagerを作る
  • サービスのUUIDを指定してPeripheralを検索する
  • Peripheralに接続する
  • PeripheralのサービスをUUID指定で検索する
  • サービスのCharacteristicをUUID指定で検索する
  • Characteristicから値を読み取る

順にみていきましょう。

CBCentralManagerを作る

まずはviewDidLoad() のあたりでCBCentralManagerオブジェクトを作ります。今回はdelegateにself(=ViewController)を指定したので、ViewControllerにCBCentralManagerDelegateプロトコルをつけます。また、サービスとCharacteristicのUUIDを表すオブジェクトも作っておきます。

class CentralViewController : UIViewController, CBCentralManagerDelegate {
    var serviceUUID : CBUUID!
    var characteristicUUID : CBUUID!

    var manager : CBCentralManager!
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.       
        // UUIDを表すオブジェクト生成。
        self.serviceUUID = CBUUID(string: Constants.SERVICE_UUID)
        self.characteristicUUID = CBUUID(string: Constants.CHARACTERISTIC_UUID)

        self.manager = CBCentralManager(delegate : self, queue : nil)
        // その他処理
    }
}

オブジェクトを作ると、早速CBCentralManagerDelegateのcentralManagerDidUpdateState() が呼ばれます。端末のBluetoothがOFFの場合は次の図のようにダイアログが出ます。

<img src="./bluetooth_off.webp"/>

BluetoothがONの時のみ、後続の処理を行うようcentralManagerDidUpdateState() を実装します。

func centralManagerDidUpdateState(central: CBCentralManager!) {
    if central.state == CBCentralManagerState.PoweredOn {
        self.addMessage("BLE Power On")
        // ONの時だけ開始ボタンが押せるようにする
        self.startButton?.enabled = true
    }
}

サービスのUUIDを指定してPeripheralを検索する

BluetoothがONになっているのを確認したら、次はPeripheralの検索です。検索はCBCentralManagerのscanForPeripheralsWithServices() を使います。第1引数には検索対象とするサービスのUUIDを配列で指定します。nilを指定すると見つかるもの全部を返すので、デバッグ時はnilを指定して、ちゃんとPeripheralが見つかるか確認することもできます。第2引数は検索オプションですが、詳しくは別の回で紹介します。

// 開始ボタンをタップしたら検索を始めるようにする
@IBAction func startClicked(sender: AnyObject) {
    self.startButton?.enabled = false
    self.stopButton?.enabled = true
    self.manager.scanForPeripheralsWithServices([self.serviceUUID], options: nil)
    self.addMessage("Start scan")
}

見つかると、CBCentralManagerDelegateのcentralManager:didDiscoverPeripheral:advertisementData:RSSI() が呼ばれます。引数に見つかったperipheralが渡されますが、後続の処理実行中にGCされてしまうことがあるので強参照で捕まえておきます。

func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!,
    advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {
        self.addMessage("Peripheral discovered")
            
        self.peripheral = peripheral
        self.peripheral.delegate = self
}

また、delegateも設定するので、対象クラス(ここではViewController)にCBPeripheralDelegateをつけておきます。

class CentralViewController : UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
    // 中身は省略
}

Peripheralに接続する

Peripheralが見つかったら、次は接続します。接続するにはCBCentralManagerのconnectPeripheral を使います。advertisementのデータだけ使うのであれば接続は必要ありません。また、省電力のため他のPeripheralのスキャンは止めておきます。止めるにはCBCentralManagerのstopScan() を使います(出典:Appleのドキュメント)。

self.manager.connectPeripheral(peripheral, options: nil)
self.manager.stopScan()

接続が完了すると、CBCentralManagerDelegateのcentralManager:didConnectPeripheral が呼ばれます。

func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) {
    self.addMessage("Connected to peripheral")
}

PeripheralのサービスをUUID指定で検索する

Peripheralに接続できたら、次はPeripheralが提供しているサービスをUUID指定で検索します。検索するにはCBPeripheralのdiscoverServices() を使います。引数には検索したいサービスのUUIDを配列で渡します。Peripheralの検索と同様、デバッグ時はnilを渡すことですべてのサービスを検索することができます。

self.peripheral.discoverServices([self.serviceUUID])

サービスが見つかると、CBPeripheralDelegateのperipheral:didDiscoverServices:error が呼ばれます。エラー時は第3引数に何か値が入っているのでそこで確認します。

func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) {
    if (error != nil) {
        self.addMessage("Failed to discover services " + error!.localizedDescription)
        return
    }    
}

サービスのCharacteristicをUUID指定で検索する

Peripheralのサービスが見つかったら、次はサービスに紐づくCharacteristicを検索します。まずは見つかったサービスの中から、目的のサービスを次のようにして見つけます。見つかったらCBPeripheralのdiscoverCharacteristics:forService:service() で検索を開始します。第1引数には検索対象となるCharacteristicのUUIDを配列で指定します。nilを指定した場合の挙動はこれまでと同様、すべてを検索対象とします。

let services : NSArray = peripheral.services
self.addMessage("Service discovered count=\(services.count) services=\(services)")
for service in services as! [CBService] {
    if service.UUID.isEqual(self.serviceUUID) {
        self.peripheral.discoverCharacteristics(nil, forService:service)
     }
 }

Characteristicが見つかると、CBPeripheralDelegateのperipheral:didDiscoverCharacteristicsForService:error() が呼ばれます。

func peripheral(peripheral: CBPeripheral!, didDiscoverCharacteristicsForService service: CBService!, error: NSError!) {
    if error != nil {
        self.addMessage("Failed to discover characteristics " + error!.localizedDescription)
        return
    }
    // 見つかった時の処理
}

Characteristicから値を読み取る

Characteristicまで見つけたら、ようやくBLEデバイス側の値を読み取ることができます。値を読み取るにはCBPeripheralのreadValueForCharacteristic() を使います。

self.peripheral.readValueForCharacteristic(c)

値を最後まで読み取ると、CBPeripheralDelegateのperipheral:didUpdateValueForCharacteristic:error() が呼ばれます。読み取った値はCBCharacteristicのvalue プロパティにNSDataの形で入っています。

func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {
    if error != nil {
        self.addMessage("Failed to read value " + error!.localizedDescription)
        return
    }
    var data = NSString(data: characteristic.value, encoding: NSUTF8StringEncoding)
    self.addMessage("value=\(data!)")
}

一覧に戻る