LoginSignup
33
29

More than 5 years have passed since last update.

CoreBluetoothでiPhoneとMacをBLE接続してみた。

Posted at

基本的には「Core Bluetoothプログラミングガイド」というドキュメントを確認しながら実装していけば特にハマるポイントはないと思います。
BLE通信はクライアント/サーバー型になっています。
「iPhoneのセンサー情報をMacへ送信する」ことを試してみたかったので
Macを「セントラル」、iPhoneを「ペリフェラル」として実装してみました。

セントラルが実行するタスク

ますはクライアントとなるセントラルの機能を持ったMacでは必要なタスクは5つです。

1.セントラルマネージャオブジェクトを起動する

セントラルとしての処理を行うためにCBCentralManagerのインスタンスを作成します。

central.swift
var myCentralManager: CBCentralManager!
myCentralManager = CBCentralManager(delegate: self, queue: nil)

2.アドバタイズしているペリフェラルを検出、接続する

接続先のペリフェラルを探すためにCBCentralManagerクラスの scanForPeripheralsWithServices:options:メソッドを実行します。
withServicesをnilにしていますが、実際には接続すべきサービスのUUIDを指定したほうが良いです。

central.swift
myCentralManager.scanForPeripherals(withServices:nil, options: nil)

スキャンを実行するとその結果が下記のデリゲートメソッドに返ってきます。

central.swift
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
 /*
目的のペリフェラルが見つかったらconnectPeripheralでで接続します。
 myCentralManager.connect(peripheral, options: nil)
*/
    }

接続成功の可否は下記のデリゲートメソッドで確認できます。

central.swift
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
  print("接続成功")
/*
ペリフェラルとのやり取りを始める前にペリフェラルのデリゲートを設定します。
そうすることでイベントに必要なメソッドが呼び出せるようになります。
peripheral.delegate = self
*/
    }

3.接続先のペリフェラルがどのようなデータを提供するか調査する

次にペリフェラル側のサービスを検出する必要があります。

Core Bluetoothプログラミングガイドから引用
ペリフェラルは、1つ以上のサービス(あるいは接続信号強度に関する有用な情報)を提供します。 サービス(service)とは、デバイス(またはその一部)の目的を達成するためのデータと、これに関 連する動作をまとめて呼ぶ用語です。たとえば心拍モニタには、心拍センサーから取得した心拍デー タを提供する、というサービスがあります。

サービスを検索するためには、discoverServicesメソッドを使用します。
ここではnilを使用していますが電池消費や時間を無駄にしないため
必要なサービスのUUIDを具体的に指定することをおすすめします。

central.swift
peripheral.discoverServices(nil)

指定されたサービスの特性を検出すると、ペリフェラルマネージャは、デリゲートオブジェクトの peripheral:didDiscoverCharacteristicsForService:error:メソッドを呼び出します。

central.swift
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {

        if error != nil {
            print(error!)
        }

               /*
         discoverServicesメソッドでnilを指定した場合は検出されたペリフェラルが
         配列で渡されます。
     */ 
        for service in peripheral.services! {
            print(service.uuid)
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

4.ペリフェラルのサービスの特性値に対する読み書き要求を送信する

単純な読み取りであればCBPeripheralクラスのreadValueメソッドを実行するだけです。
読み取りに成功すれば下記のデリゲートメソッドに結果が返ってきます

central.swift
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
/*
読み取ったデータは peripheral.value で取得できます
*/
}

5.特性値が変化したときに通知するよう、ペリフェラルに申し込む

セントラル側で何かしらのアクション(ボタンタップなど)をした時のみデータを読み取りたい時はreadValueで問題ないですが、今回の目的のようにセンサーデータを取得したい場合はCBPeripheralクラスのsetNotifyValueをtrueとしておけばセンサーの値が変化するとその都度情報が通知されるようになります。

central.swift
peripheral.setNotifyValue(ture,for:"ペリフェラル")

ペリフェラルで実行するタスク

情報の提供元(サーバー)となるiPhoneで実行する必要があるタスクは6つです。

1.ペリフェラルマネージャオブジェクトを起動する

ペリフェラルマネージャーのインスタンスを作成します。

peripheral.swift
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)

2.ローカルのペリフェラルが提供するサービスや特性を設定する

セントラルへ提供するサービスとその特性を設定します。

Core Bluetoothプログラミングガイドから引用
特 性(characteristic)とは、ペリフェラルのサービスに関する詳細です。心拍サービスは、心拍センサーが体のどの位置にあるかを表す特性、心拍測定データを送信する特性などから成ります。

ペリフェラルのサービスや特性は、Bluetoothに特有の128ビットUUIDで識別します
Bluetooth分科会というところではよく使われるUUIDを多数、使いやすいよう16ビットに短縮して定義、公開しているのでそれをそれを使う、またはターミナルで「uuidgen」を実行すれば値作成してくれるのでそれを使うこともできます。

peripheral.swift
myCustomServiceUUID = CBUUID(string: "FCC6AD8D-A8EF-475F-B0FD-D106C00882FC")
myCharacteristicUUID = CBUUID(string: "B5D06B73-0B67-4861-B336-3386C2FF8A7B")

UUIDを設定した後はそれぞれのオブジェクトを作成して設定を行なっていきます。

peripheral.swift
myCharacteristic = CBMutableCharacteristic.init(type: myCharacteristicUUID, properties: .notify, value: nil, permissions: .readable)

propertiesやpermissionsは、特性の値が読み取り/書き込み可能か、値の変化を通知するよう申し込むことができるか、などを表します。
以下のように配列にまとめて設定することも可能です。

peripheral.swift
properties = [.notify, .read, .write]
permissions = [.readable, .writeable]

Core Bluetoothプログラミングガイドから引用
注意: 特性の値を指定すると、この値はキャッシュの対象になり、読み取り可能であるよう にプロパティや操作権限が設定されます。書き込みも可能にしたい、あるいは時間の経過に 伴い変化するようにしたい場合は、値としてnilを指定しなければなりません。こうしてお けば、セントラルから読み取り/書き込み要求を受け取ったペリフェラルマネージャが、値 を動的に変更できるようになります。

次にサービスを作成します。
primaryをtrueにすること主サービスになります。

peripheral.swift
service = CBMutableService(type: serviceUUID, primary: true)

最後に作成したサービスに特性を登録します。

peripheral.swift
service.characteristics = myCharacteristic

3.サービスや特性を当該デバイスのローカルデータベースに登録する

サービスと特性の設定が終わったらペリフェラルマネージャーにサービスを登録します。

peripheral.swift
peripheralManager.add(service)

ペリフェラルマネージャーにサービスを登録するとデリゲートオブジェクトにその結果が返ってきます。
エラーがあってサービスを登録できない場合、その原因を調べて対処するように実装してください

peripheral.swift
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
        if error != nil {
            print("Service Add Failed... \(error!)")
            return
        }
        print("Service Add Sucsess!")
    }

4.サービスをアドバタイズする

サービスの登録が完了したらセントラルへ向けてアドバタイズを行います。
これを行うことでセントラルから接続できるようになります。
アドバタイズ時の指定できるのはCBAdvertisementDataLocalNameKeyとCBAdvertisementDataServiceUUIDsKeyの2つだけです。

peripheral.swift
let advertisementData = [CBAdvertisementDataServiceUUIDsKey: [service.uuid],CBAdvertisementDataLocalNameKey: "testDevice"] as [String : Any]
peripheralManager.startAdvertising(advertisementData)

startAdvertisingメソッドが実行されると結果がデリゲートオブジェクトに返ってきます。
エラーが出る場合には内容を確認して対処してください。

peripheral.swift
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
        if error != nil {
            print("Failed... error: \(error!)")
            return
        }
        print("Succeeded!")
    }

5.接続されたセントラルからの読み書き要求に応答する

アドバタイズを行いセントラルから接続されるとデータ読み取りリクエストが届くはずです。
その際にはペリフェラルマネージャーのdidReceiveReadRequestを使用します。

peripheral.swift
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {

/*例
request.value = myCharacteristic.value
peripheralManager.respond(to: request, withResult: CBATTError.success)
*/
    }

6.特性値が変化したとき、あらかじめ申し込まれていたセントラルに通知する

今回は「iPhoneのセンサー情報をMacへ送信する」ことが目的なので
特性値が変化したら通知を送らなければなりません。
その場合は、デリゲートオブ ジェクトのdidSubscribeToCharacteristicを使用します。

peripheral.swift
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {

    }

これ以降、値が変化したらセントラルに通知することになります。

新しい特性値を取得しセントラルに通知するには、updateValue:forCharacteristic:onSubscribedCentrals:メソッドで行います。
成功の可否は戻り値のbool値で確認できます。

peripheral.swift
var updateValue: Data = //新しい値を設定
var sendValue: Bool = peripheralManager.updateValue(updatedValue, for: characteristic, onSubscribedCentrals: nil)

onSubscribedCentralsで送信先のセントラルを設定することもできます。

おわりに

だいぶざっくりとした説明になってしまいましたが、
最初に書いた通りCore Bluetoothプログラミングガイドの通りに実装していけばOKです。
他にもこのドキュメントには「Core Bluetoothのバックグラウンド処理(iOSアプリケーション用)」や「リモートのペリフェラルとのやり取りに関するベストプラクティス」なども記載してあるので一通り学ぶことができると思います。

読みづらい箇所や誤っている箇所があればご指摘いただけると幸いです。

33
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
29