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