Unity でシリアル通信
Unityでシリアル接続のRFIDリーダーを使用したアプリケーションを作成する必要があったので、シリアル機器と通信するクラスを作成してみました。
Unity でシリアル通信を行うには、まず PlayerSettings の API Compatibility Level を .NET2.0 Subset から .NET2.0 に変更します。
これでシリアル接続を行うクラス System.IO.Ports.SerialPort クラスが使えるようになります。
通信までの手順は簡単には下記の通りです。
1. 機器が接続されているシリアルポート名設定と機器に合わせた接続設定を行う。
2. シリアルポートを開いて機器と接続する。
3. 接続が確認されたら受信待機する。
また、送信については SerialPort.Write(“送信するコマンド”) で行います。
それぞれの処理は下記コードのコメントを参照してください。
サンプルコードは「シリアル接続の基本クラス」「機器に合わせて基本クラスを拡張したサブクラス」「動作確認用メインクラス」に分かれています。
シリアル接続の基本クラス
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 | /** * シリアル接続の基本クラス * 受信処理の実装は使用する機器ごとに違うので * このクラスを拡張して実装する **/ using System.Collections; using System.Collections.Generic; using System.Threading; using System.IO.Ports; using UnityEngine; public class SerialConnector : MonoBehaviour { protected SerialPort _serialPort; // ボート名などはエディタから設定できる public string portName = "COM3" ; public int baudRate = 38400; public int threadSleepTime = 100; // シリアル設定用パラメータ protected Parity _parity; protected int _databits; protected StopBits _stopbits; protected int _readTimeout; protected int _writeTimeout; // スレッド protected Thread _thread; protected bool _isRunning; // 終了処理 protected void OnDestroy() { close(); } // シリアルポートを開いて接続 protected bool open() { try { // ポート名, ボーレート, パリティチェック, データビット長, ストップビット長, // 読み取り時タイムアウト, 書き込み時タイムアウトを設定してポートを開く _serialPort = new SerialPort(portName, baudRate, _parity, _databits, _stopbits); _serialPort.ReadTimeout = _readTimeout; _serialPort.WriteTimeout = _writeTimeout; _serialPort.Open(); } catch (System.Exception e) { Debug.LogWarning (e.Message); } return _serialPort.IsOpen; } // スレッドを停止してシリアルポートを閉じる protected void close() { _isRunning = false ; if (_thread != null && _thread.IsAlive) { _thread.Abort(); } if (_serialPort != null && _serialPort.IsOpen) { _serialPort.Close(); _serialPort.Dispose(); } } // スレッドラン protected void run() { while (_isRunning && _serialPort != null && _serialPort.IsOpen) { // 受信バッファをクリア try { _serialPort.DiscardInBuffer (); } catch (System.Exception e) { Debug.LogWarning (e.Message); } // Thread.Sleep (threadSleepTime); // 読み取り serialRead (); } } // シリアルポート読み取り処理(シリアル受信処理) public virtual void serialRead() { // 実装はサブクラスでオーバーライドする } } |
SerialConnector クラスを拡張したサブクラス
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | /** * SerialConnector クラスを拡張したサブクラス * このクラスで機器に適合した設定や受信処理を実装 *(今回は大信機器製のRFIDリーダーライター HF-06) **/ using System.Collections; using System.Collections.Generic; using System.Threading; using System.IO.Ports; using UnityEngine; public class RFIDHandler : SerialConnector { // delegate で EventDispatcher を作成 public delegate void Dispatcher( string msg); private Dispatcher _dispatcher; // Dispatcher をインスタンス化 // Dispatcher のコールバック関数 public void addEventListener(Dispatcher dispatcher) { _dispatcher += dispatcher; } public string message = "0" ; protected bool _isMessageReaded; void Start(){ // シリアルポート設定 _parity = Parity.None; _databits = 8; _stopbits = StopBits.One; _readTimeout = 500; _writeTimeout = 1000; // オープンしたら読み取りスレッドを開始 if (open ()) { // _isRunning = true ; _thread = new Thread(run); _thread.Start(); // _serialPort.Write( "1" ); _serialPort.Write( "2XS\r" ); } } void Update () { if (_isMessageReaded) { _serialPort.Write( "2XS\r" ); _isMessageReaded = false ; } } // Read(RFIDリーダー HF-06 のシリアル受信処理) public override void serialRead() { message = "0" ; try { string data = _serialPort.ReadTo( "\r" ); // タグを認識したら19文字のデータが来て待ち受け終了するので // 機器に次の待ち受け開始コマンド送信する if (data.Length == 19) { _isMessageReaded = true ; } _dispatcher (data); } catch (System.Exception e) { //Debug.LogWarning(e.Message); } } } |
動作確認用
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * 動作確認用メインクラス **/ using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class MainController : MonoBehaviour { protected RFIDHandler serial; void Start () { // RFIDHandler がこのスクリプトと同じGameObjectにアタッチされているものとする serial = gameObject.GetComponent<RFIDHandler> (); // 受信イベントのリスナー serial.addEventListener (onMessage); } // 受信イベントハンドラ void onMessage( string msg) { Debug.Log(msg); } } |
というわけで、たったこれだけのコードで Unity からシリアル通信を行うことができるようになりました。
しかもこれ、Unity なので Mac でも動作します。
$ ls /dev/tty.usb* で接続されている機器のシリアルポートを調べて設定すればオッケーです。
このクラスを利用すれば Arduino などとも通信できるはずなので試してみようかと思ったのですが、肝心の Arduino が行方不明なので… またそのうち試してみます。
【2017.04.30 追記】
シリアルポートの受信バッファが蓄積されるとメモリに負荷がかかるかもしれないということを想定して、シリアル接続の基本クラスに受信バッファを削除する処理を追加しました。
今回参考にさせていただいた記事 http://tips.hecomi.com/entry/2014/07/28/023525