SPIKEプライムのハブに自作macOSアプリからコマンドを送る
macOS SDKにはIOBluetoothというフレームワークがあって、それを使うとSPP(Serial Port Profile)対応のBluetoothデバイスと双方向通信をすることができます。IOBluetoothフレームワークを使って、前回と同じことをするmacOSアプリ作ってみます。
SPPでコマンドを送信するまでの手順
1.Bluetoothデバイスを探す
2.Bluetoothデバイスに接続する
3.SPPを提供しているサービスを探す
4.サービスからRFCOMMのチャンネルIDを取得する
5.チャンネルIDを指定してRFCOMMチャンネルを開く
6.RFCOMMチャンネルにバイト列を送信する
手順1には複数の方法があるのですが、ここではSPIKEアプリと同じようにシステム提供のダイアログを使ってみます。IOBluetoothUIフレームワークのIOBluetoothDeviceSelectorControllerがそれに該当します。また、SPIKEプライムのハブではRFCOMMチャンネルのチャンネルIDは 1 で固定のようなので、今回は手順3と手順4は飛ばします。
ちなみに、LEGO MINDSTORMS EV3も同じようにチャンネルIDが 1 固定で、ブラウザ上で動くScratch 3.0とEV3の間を仲介するアプリであるScratch Linkでも、同様に決め打ちで接続しています。現状Scratch 3.0が対応しているBluetooth ClassicデバイスはEV3だけですが、今後変更が必要かもしれませんね。
さらに、macOSで手順5を実行すると手順2も自動的に行われるので、コードはかなり短くなります。これもScratch Linkを真似しました。
Swift 5の最小コード
var connectedChannel: IOBluetoothRFCOMMChannel?
func openDeviceSelectorController() {
// 1.Bluetoothデバイスを探す
guard let deviceSelector = IOBluetoothDeviceSelectorController.deviceSelector() else { return }
if deviceSelector.runModal() != Int32(kIOBluetoothUISuccess) {
print("Canceled")
return
}
if let device = deviceSelector.getResults()?.first as? IOBluetoothDevice {
// 5.チャンネルIDを指定してRFCOMMチャンネルを開く
device.openRFCOMMChannelSync(&connectedChannel, withChannelID: 1, delegate: nil)
}
}
func sendCommand(_ command: String) {
guard let connectedChannel = connectedChannel else { return }
guard var data = command.data(using: .utf8) else { return }
data.append(0x0d) // 末尾に \r を追加
data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in
if let bytes = buffer.baseAddress {
// 6.RFCOMMチャンネルにバイト列を送信する
connectedChannel.writeSync(bytes, length: UInt16(buffer.count))
}
}
}
例えば、ストリーミングモードにするコマンドを送りたい場合は、openDeviceSelectorController() でハブを選択したあとに、sendCommand() で以下のようにJSON文字列を送ります。
sendCommand(#"{"m":"program_modechange","p":{"mode":"play"}}"#)
補足
自作のmacOSアプリでBluetooth通信をするには、プロジェクトのSign & CapabilitiesでBluetoothを有効にしておく必要があります。
また、SPIKEアプリのように接続ダイアログで表示されるデバイスを限定したい場合は、IOBluetoothDeviceSelectorControllerの addAllowedUUID() や setSearchAttributes() を使います。
サンプルプロジェクト
以下のプロジェクト内のSendCommand-macOSが、上記のコードとほぼ同じです。任意のコマンドを送れる機能を追加してあります。よかったら試してみてください。