SPIKEプライムのハブに自作iOSアプリからコマンドを送る

前回の記事では、macOS SDKのIOBluetoothフレームワークを使ってハブにBluetooth ClassicのSPPでコマンドを送りました。しかし、iOS SDKにはIOBluetoothフレームワークがなく、macOSのように自由にSPPを使うことができません。

iOSアプリとBluetoothデバイスの間でSPPを行うには、以下の条件を満たしている必要があります。この条件を満たすと、IOBluetoothではなくMFiデバイスを扱うためのExternalAccessoryフレームワークを使って、バイト列による通信を行うことができます。

1.BluetoothデバイスがAppleからMFi認定を受けている
2.iOSアプリがそのデバイスの通信プロトコルをサポートしている

当然のことながら、iOS版のSPIKEアプリはこの条件を満たしています。条件1は、箱にMFi認定ロゴがあるので満たしています。

また、App Storeから配布されている SPIKE.app に同梱されている Info.plist には UISupportedExternalAccessoryProtocols として "com.lego.les" の記載があります。これはSPIKEプライムのハブが提供している "com.lego.les" と名付けられた通信プロトコルを、アプリがサポートしていることを意味しており、これが条件2になります。

条件1はAppleによる認定が必要なので、個人が勝手にiOSアプリと通信可能なBluetooth SPPデバイスを作ることはできません。一方、条件2は自作アプリでも Info.plist に記述するだけで満たすことができます。これを使って自作iOSアプリからコマンドを送ってみます。

MFiデバイスにコマンドを送信するまでの手順

1.MFi認定を受けているBluetoothデバイスを接続する
2.該当の通信プロトコルをサポートしている接続済みデバイスを探す
3.通信プロトコルを指定してセッションを開く
4.セッションの出力ストリームにバイト列を送信する

手順1には、SPIKEアプリのようにシステム提供のダイアログを使う方法と、iOSの設定アプリから接続する方法があります。ただ、前者は最新のSDKだと不安定のようで、特にiOS 13からのUISceneベースのアプリだとダイアログが表示できなかったりします…。なので、下記のように設定>Bluetoothからハブを選択するのが安心感があってよいでしょう。

画像1

手順2から4はBluetooth SPPのことをよく知らなくても、Streamベースのプログラミングをしたことがあれば問題なく理解できるでしょう。

Swift 5の最小コード

let protocolString = "com.lego.les"
var connectedSession: EASession?

func openSessionWithConnectedAccessory() {
    // 2.該当の通信プロトコルをサポートしている接続済みデバイスを探す
    guard let accessory = EAAccessoryManager.shared().connectedAccessories.first(where: { $0.protocolStrings.contains(protocolString) } ) else { return }

    // 3.通信プロトコルを指定してセッションを開く
    guard let session = EASession(accessory: accessory, forProtocol: protocolString) else { return }

    session.outputStream?.schedule(in: .current, forMode: .default)
    session.outputStream?.open()

    self.connectedSession = session
}

func sendCommand(_ command: String) {
    guard let outputStream = connectedSession?.outputStream else { return }
    guard var data = command.data(using: .utf8) else { return }

    data.append(0x0d) // 末尾に \r を追加

    data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
        if let bytes = buffer.bindMemory(to: UInt8.self).baseAddress {
            // 4.セッションの出力ストリームにバイト列を送信する
            outputStream.write(bytes, maxLength: buffer.count)
        }
    }
}

Info.plist に UISupportedExternalAccessoryProtocols として "com.lego.les" を追加するのを忘れないでください。Raw表示にしていない場合はSupported external accessory protocolsと表示されます。

画像2

サンプルプロジェクト

以下のプロジェクト内のSendCommand-iOSが、上記のコードとほぼ同じです。任意のコマンドを送れる機能を追加してあります。よかったら試してみてください。


この記事が気に入ったらサポートをしてみませんか?