Unity でシリアル通信


Unityでシリアル接続のRFIDリーダーを使用したアプリケーションを作成する必要があったので、シリアル機器と通信するクラスを作成してみました。

Unity でシリアル通信を行うには、まず PlayerSettings の API Compatibility Level を .NET2.0 Subset から .NET2.0 に変更します。
これでシリアル接続を行うクラス System.IO.Ports.SerialPort クラスが使えるようになります。

通信までの手順は簡単には下記の通りです。

1. 機器が接続されているシリアルポート名設定と機器に合わせた接続設定を行う。
2. シリアルポートを開いて機器と接続する。
3. 接続が確認されたら受信待機する。

また、送信については SerialPort.Write(“送信するコマンド”) で行います。

それぞれの処理は下記コードのコメントを参照してください。

サンプルコードは「シリアル接続の基本クラス」「機器に合わせて基本クラスを拡張したサブクラス」「動作確認用メインクラス」に分かれています。

▪️ シリアル接続の基本クラス

/**
* シリアル接続の基本クラス
* 受信処理の実装は使用する機器ごとに違うので
* このクラスを拡張して実装する
**/

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 クラスを拡張したサブクラス

/**
* 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);
		}
	}
}

▪️ 動作確認用

/**
* 動作確認用メインクラス
**/
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