lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

iPad版Swift Playgroundsでsocket通信する

ネットワークにつながる組み込み機器とスマホを繋げるネタは数多ありますが、ほとんどがAndroid機器のようです。iOSバイスがそのような所謂"低レベル通信"のほとんどをユーザーに開放してないというのが理由のようです。
あとXCode使う=Mac買わなきゃ & お布施とかそういうやつ。

W5500(サーバー側)とiPad Pro(クライアント)だけでTCP/IPソケット通信できるといいなぁと探したらありました。

perl - How to implement a socket connection using a swift playground? - Stack Overflow

上記情報が古いバージョンのSwiftでいろいろ修正が必要でしたが、動作したのでメモがてら。
iPadのSwift Playgroundsアプリだけでプログラムできます。

これを現行のSwift 3.1用に修正しました。
下記例ではサーバーアドレス192.168.0.123:4567ポートと通信しています。(IP固定)

/* 
 Socket communication on Static IP
 Works on Swift 3.1 + playgrounds support
 */
import UIKit
import PlaygroundSupport

let addr = "192.168.0.123"
let port = 4567  //PORT Num

var buffer = [UInt8](repeating: 0, count: 255)

var inp : InputStream?
var out : OutputStream?
Stream.getStreamsToHost(withName: addr, port: port, inputStream: &inp, outputStream: &out)

if inp != nil && out != nil {
    let inputStream : InputStream = inp!
    let outputStream : OutputStream = out!
    inputStream.open()
    outputStream.open()
    
    if outputStream.streamError == nil && inputStream.streamError == nil {
        let queryString = "クライアントから送信したいデータ"
        let queryData = [UInt8](queryString.utf8) 
        while true {
            UnsafePointer<UInt8>(queryData)
            outputStream.write(queryData, maxLength: queryData.count)
            var readChars: Int = inputStream.read(&buffer, maxLength: buffer.count)
            if (readChars > 0) {
                let readString: String = NSString(data: NSData(bytes:buffer, length:readChars) as Data, encoding: String.Encoding.utf8.rawValue)! as String
                print(readString) //  サーバから受信したデータ
                usleep(300 * 1000) //300ms待つ(適当
                }
             
            } else {
                print ("server closed connection")
                inputStream.close()
                outputStream.close()
                break
            }
        }
    } else {
        print ("could not create socket")
    }
} else {
    print ("could not initialize stream")
}

通信中の切断に対する適切な対応とかタイムアウトとか全然考えてないので、そこら辺はご容赦。

受信したデータをprint()するだけの用途はあまりないと思うのでちょいと応用です。
趣味で作っている透過率測定器の結果をiPadに出力しました。この測定器はクライアントから「data?\r\n」という文字列を受信すると、ダイオードアレイのADCデータを「datais?t+1234567」という形式でクライアントへ送信します(欲しいデータは+1234567)。文字列から数値だけを抽出したいので、分割します。
先ほどの

print(readString) //  サーバから受信したデータ

を下記のコードに置き換えます。

//print(readString) //  サーバから受信したデータ

                if (readString.contains("datais?t")) {
                    let strSep = readString.components(separatedBy: "?t")
                    let rval = (strSep[1] as NSString).integerValue 
                    
                    usleep(300 * 1000) //300ms
                }
                

こちらのコードも変更

//let queryString = "クライアントから送信したいデータ"
let queryString = "data?\r\n"

Swift Playgroundsの便利機能で、変数の値変化をグラフの形でスコープ出来る(データ数上限あり)ので、簡単な確認なら気合い入れてグラフ描画コード書かなくても良い。

自分はWiznet W5500とWiFiルータ経由で通信をテストしました。
最近はやりのESP32とかXbee WifiとかWiFiが使えるデバイスで試した方がいれば、動作結果など是非お聞かせ願えれば。

参考:
Real-Time Communication with Streams Tutorial for iOS

iPad Pro 10.5用カバーでApple Pencil一緒に携帯できて頑丈なやつ。