質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.48%

iOS同士のBLE通信、connect方法

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,541

score 20

前提・実現したいこと

iOS同士のセントラル、ペリフェラルの接続(connect)

発生している問題

おそらくセントラルのスキャンが上手く機能していません。
ログ出しで確認しましたが、
centralManager.scanForPeripherals のメソッドには入っています。
しかしその先のcentralManager(_:didDiscover:_:_)のログが表示されず、ペリフェラルを一つも検知していません。
上記判断の根拠として、ログ出しの他に、
ペリフェラル側は peripheralManagerDidStartAdvertisingを成功させており、 
nordic社の nRF Connect でアドバタイズを飛ばしていることも確認できたからです。

該当のソースコード

// セントラル側
import Foundation
import CoreBluetooth


// セントラルマネージャーのデリゲートとペリフェラルマネージャーを持つ
class CentralService: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {

    private let Log = Logger(category: "Central" )!

    // オリジナルUUID。
    static let serviceUUID: CBUUID      = CBUUID(string: "566F2065-450F-4633-0123-456789ABCDEF")
    var serviceUUIDList: [CBUUID]       = [serviceUUID]
    static let charReadUUID: CBUUID     = CBUUID(string: "A1F86134-652D-1234-0123-456789ABCDEF")
    static let descriptorUUID: CBUUID   = CBUUID(string: "97A4D0DD-1556-4454-0123-456789ABCDEF")
    static let charNotifyUUID: CBUUID   = CBUUID(string: "A1F86134-652D-1234-FEDC-BA9876543210")

    var centralManager: CBCentralManager?               // センタラルマネージャーのインスタンス
    var peripherals: [CBPeripheral] = []                // CBPeripheral型の配列.見つけ出したペリフェラルを一時保存する
    var cbPeripheral: CBPeripheral? = nil               // ペリフェラルマネージャーのインスタンス
    var readCharacteristic: CBCharacteristic? = nil     // メッセージ読み取りのキャラクタリスティック
    var notifyCharacteristic: CBCharacteristic? = nil   // メッセージ読み取り可能通知のキャラクタリスティック
    var mssLen: Int = 0

    var VC :ViewController? = nil

    // 初期化
    override init () {
        super.init()
        Log.i("init")
//        serviceUUIDList = [CentralService.serviceUUID]
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }


    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch (central.state) {
        case .poweredOn:
            Log.i("BLE Power On")
            scanStart()

        case .poweredOff:
            Log.i("BLE Power off")
            break
        case .resetting:
            Log.i("BLE resetting")
            break
        case .unauthorized:
            Log.i("BLE unauthorized")
            break
        case .unknown:
            Log.i("BLE unknown")
            break
        case .unsupported:
            Log.i("BLE unsupported")
            break
        @unknown default:
            Log.i("BLE unknown Default")
            break
        }
    }

//    サービスUUID検索
    func scanStart() {
        Log.i("scan start")
        /* 重複フィルタリングの設定、
          trueなら同じPeripheralからのアドバタイズで毎回イベント発生させる
          ※このフィルタリングを利用するとバッテリーの消耗を抑えられる */
        let options = [CBCentralManagerScanOptionAllowDuplicatesKey : true]

        if centralManager!.isScanning == false {

//          探し出すサービスのUUIDを配列に入れて指定する.
            let UUIDArray :[CBUUID] = [CentralService.serviceUUID]

            Log.d("UUIDArray = \(UUIDArray)")

            /* scanForPeripherals(withServices
                @param
                    withServices: [CBUUID]? 検索するCBUUIDを集めたの配列。
                    options: nil            オプション
             */
            centralManager!.scanForPeripherals(withServices: UUIDArray, options: options)
        }
    }

/* ペリフェラル発見時に呼び出される
     @param
         central: CBCentralManager              セントラルマネージャー
         peripheral: CBPeripheral               発見したペリフェラル
         advertisementData: [String : Any]      発見したペリフェラルの広告
         rssi RSSI: NSNumber                    発見したペリフェラルのRSSI
*/
     func centralManager(_ central: CBCentralManager,
                         didDiscover peripheral: CBPeripheral,
                         advertisementData: [String : Any],
                         rssi RSSI: NSNumber) {
        Log.i("append Peripheral")
        /* 見つけ出したペリフェラルを配列に入れて保存する */
        peripherals.append(peripheral)
        connect()
    }

/*
     検索したペリフェラルとコネクト(ペアリング)する
*/
    func connect() {
        Log.d("connect")
        // 見つけ出したペリフェラル全てを調べる
        for peripheral in peripherals {
            // ペリフェラルの名前が一致したら
            if ((peripheral.name != nil) && (peripheral.name == "Test Device")) {
                cbPeripheral = peripheral
                centralManager?.stopScan()
                Log.d("scan stopped")
                break
            }
        }

        // 上のループでペリフェラルを見つけられていたらコネクトする
        if (cbPeripheral != nil) {
            centralManager!.connect(cbPeripheral!, options: nil)
        }
    }

/*****************
 以下長いので省略
******************/

セントラルのログ

[Central] [INF] init
[Central] [INF] BLE Power On
[Central] [INF] scan start
[Central] [DEB] isScanning == false in
[Central] [DEB] UUIDArray = [566F2065-450F-4633-0123-456789ABCDEF]

該当ソースコード

// ペリフェラル側
import Foundation
import CoreBluetooth

class PeripheralService: NSObject, CBPeripheralManagerDelegate {
    private let Log = Logger(category: "Peripheral" )!

    var myPeripheralManager: CBPeripheralManager?
    let advertisementData = [CBAdvertisementDataLocalNameKey: "Test Device"]    // アドバタイズに乗せるデータ
    static let serviceUUID: CBUUID          = CBUUID(string: "566F2065-450F-4633-0123-456789ABCDEF")
    static let characteristicUUID: CBUUID   = CBUUID(string: "A1F86134-652D-1234-0123-456789ABCDEF")
    static let descriptorUUID: CBUUID       = CBUUID(string: "97A4D0DD-1556-4454-0123-456789ABCDEF")
    static let charNotifyUUID: CBUUID       = CBUUID(string: "A1F86134-652D-1234-FEDC-BA9876543210")

    // サービスの設定、UUIDと
    let service = CBMutableService(type: PeripheralService.serviceUUID, primary: true)

    // プロパティの設定、このキャラクタリスティックに対して何ができるかを設定している
    let readProperties: CBCharacteristicProperties = [.read]

    // 認可の設定、キャラクタリスティックの読み取り、書き込み、暗号化の認可
    let permissions: CBAttributePermissions = [.readable]

    // キャラクタリスティックが属するサービスと、プロパティおよびその認可を設定
    var characteristic: CBMutableCharacteristic? = nil

    var requestList: [CBATTRequest] = []

    // 初期化
    override init () {
        super.init()
        Log.i("init")
        myPeripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
    }


    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        switch peripheral.state {
        case .poweredOn:
            Log.i("bluetooth Success")
            characteristic = CBMutableCharacteristic(type: PeripheralService.characteristicUUID, properties: readProperties, value: nil, permissions: permissions)
            setService_Charactaristic()

        case .unknown:
            Log.i("bluetooth error")
        case .resetting:
            Log.i("bluetooth error")
        case .unsupported:
            Log.i("bluetooth error")
        case .unauthorized:
            Log.i("bluetooth error")
        case .poweredOff:
            Log.i("bluetooth error")
        @unknown default:
            Log.i("bluetooth error")
        }
    }


    func setService_Charactaristic(){
        Log.i("set Service and Characteristic")
        //      キャラクタリスティックが属するサービスと、プロパティおよびその認可を設定
            characteristic = CBMutableCharacteristic(type: PeripheralService.characteristicUUID, properties: readProperties,
            value: nil, permissions: permissions)

            // 作成したサービスのキャラクタリスティックに、作成したキャラクタリスティックを設定
            // 設定は配列で複数個所持できる
            service.characteristics = [characteristic!]

            // ペリフェラルへ上記サービスを設定
            myPeripheralManager?.add(service)

            advStart()
        }

        // アドバタイズ開始
        func advStart(){
            Log.i("Start Advetising")
            myPeripheralManager!.startAdvertising(advertisementData)
    }


/* peripheralManager(_: didAddService:_)
     addServiceメソッドを呼び出してローカル周辺機器のGATTデータベースにサービスを公開すると、
     Core Bluetoothがこのメソッドを呼び出します

     peripheralManager:(CBPeripheralManager *)peripheral //サービスを追加したペリフェラル
     didAddService:(CBService *)service  //  GATTデータベースへ追加されたサービス
     error:(NSError *)error;     //失敗したエラー理由 失敗しなかった場合nilが入る
*/
    func peripheralManager(peripheral: CBPeripheralManager, didAddService service: CBService, error: NSError?) {

        if error != nil {
            Log.i("サービス追加失敗! error: \(error!)")
            return
        } else {
            myPeripheralManager!.stopAdvertising()
            Log.i("Stop Advertising")
        }

    }


/* peripheralManagerDidStartAdvertising
     アドバタイズが正常に開始できたかを確認
     @param
        peripheral: CBPeripheralManager    アドバタイズを始めるペリフェラル
        error: NSError?                    失敗理由。失敗しなかった(成功した)場合はnil
*/
    func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {

        if (error != nil) {
            Log.i("Advertise Failed  error: \(error!)")
            return
        }
        else {
            Log.i("Advertise Succeeded!")
        }

    }

/*****************
 以下長いので省略
******************/

ペリフェラルのログ

[Peripheral] [INF] init
[Peripheral] [INF] bluetooth Success
[Peripheral] [INF] set Service and Characteristic
[Peripheral] [INF] Start Advetising
[Peripheral] [INF] Advertise Succeeded!

その他

動作させている室内には、このペリフェラル以外にもBluetoothの電波が多数飛んでいます。
もし、不特定多数の電波が多すぎることが原因なら、
そういった環境下でもコネクトが行える方法があればご教授いただけると幸いです。

使っているツールのバージョンなど補足情報

Swift 5.0
Xcode 11.0
iOS 12.3
MacOS Mojave 10.14.6

都合上、アップデートができません。
ご助力いただけますと幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

0

解決しました。
Peripheral側のアドバタイズデータにCBAdvertisementDataServiceUUIDsKeyを乗せていなかったのが原因です。

// PeripheralService

/* 修正前 */
let advertisementData = [CBAdvertisementDataLocalNameKey: "Test Device"]

/* 修正後 */
let advertisementData: Dictionary<String, Any> = [CBAdvertisementDataLocalNameKey: "TEST", CBAdvertisementDataServiceUUIDsKey: [serviceUUID] ]


advertisementData: Dictionary<String, Any> =....
上記の太字部分を明記しないとエラーが出てビルドできませんでした。
CBAdvertisementDataLocalNameKeyを配列([ ]を付ける)にしたり、
CBAdvertisementDataServiceUUIDsKeyを配列にしない([ ]を付けない)と、
ビルドは通っても正常に動作しませんでした。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.48%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る