【UniRx & Coroutine & async/await】Unityでシリアルポート読み取り
Unityでシリアル通信(受信)する方法です。UniRx版、コルーチン版、async/await版を記載します。
1. 準備
スクリプトからシリアルポートを操作できるようにするために、Unityの Project Settings
> Player
> Other Settings
の項目の Api Compatibility Level
を .NET 4.x
に設定します。
また、受信データ表示用にヒエラルキーにTextを追加してください。 Debug.Logで確認する場合は不要です。
2. スクリプト (UniRx版)
以下のスクリプトを適当なゲームオブジェクトにアタッチします。 ポート名は環境に合わせて変更してください。
using System.IO.Ports; using UnityEngine; using UniRx; using UnityEngine.UI; using System; public class MainScript : MonoBehaviour { private string portName = "COM4"; private int baurate = 9600; private SerialPort serial; private bool isLoop = true; [SerializeField] Text countText = default; void Start() { serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One); serial.Open(); Observable.Create<string>(ReadData) .SubscribeOn(Scheduler.ThreadPool) .ObserveOn(Scheduler.MainThread) .Subscribe(data => countText.text = data) .AddTo(this); } private IDisposable ReadData(IObserver<string> observer) { while (isLoop) { observer.OnNext(serial.ReadLine()); } return Disposable.Empty; } private void OnDestroy() { isLoop = false; serial.Close(); } }
内容としてはシリアルポートの読み取りをThreadPoolで、結果をTextに表示するために読み取ったデータをMainThreadで処理しています。 (GUIを操作する場合はMainThreadで行う必要があります。Debug.Logするだけであればスレッドの切り替えは不要です)
3. スクリプト (コルーチン版)
以下のスクリプトを適当なゲームオブジェクトにアタッチします。 ポート名は環境に合わせて変更してください。
using System.Collections; using UnityEngine; using System.IO.Ports; using UnityEngine.UI; public class MainScript : MonoBehaviour { private string portName = "COM4"; private int baurate = 9600; private SerialPort serial; private bool isLoop = true; [SerializeField] Text countText = default; void Start() { serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One); serial.Open(); StartCoroutine(ReadData()); } private IEnumerator ReadData() { while (isLoop) { countText.text = this.serial.ReadLine(); yield return null; } } private void OnDestroy() { isLoop = false; serial.Close(); } }
4. スクリプト (async/await版)
(2019.12.07 async/await版も追加しました)
using UnityEngine; using System.IO.Ports; using System.Threading.Tasks; public class MainScript : MonoBehaviour { private string portName = "COM4"; private int baurate = 9600; private SerialPort serial; private bool isLoop = true; [SerializeField] Text countText = default; async void Start() { serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One); serial.Open(); await ReadData(); } private async Task ReadData() { await Task.Run(() => { while (isLoop) { countText.text = this.serial.ReadLine(); } }); } private void OnDestroy() { isLoop = false; serial.Close(); } }
5. 動作確認準備
今回、動作確認用にArduino Unoを使用します。Arduinoに以下のコードを書き込みます。 ボーレートはUnity側で設定した値と同じにします。(例:9600bps)
byte a = 0; void setup() { Serial.begin(9600); } void loop() { Serial.println(a++); delay(500); }
6. 結果
正常に動作していれば、0.5秒間隔で数字がカウントアップしていきます。
7. Tips
今回、動作確認用にArduino Unoを使用しましたが、Mac OS環境でかつArduino Leonardo(ATmega32u4チップ)を使用する場合は、SerialPortクラスを以下のように設定することでUnityのフリーズを防ぐことができます。
serial = new SerialPort(portName, baurate, Parity.None, 8, StopBits.One); serial.Open(); serial.DtrEnable = true; serial.RtsEnable = true; serial.DiscardInBuffer(); serial.ReadTimeout = 5000;// msec
- DtrEnable / RtsEnableのどちらかをEnableにするだけでフリーズは回避できます。
- DiscardInBuffer()することで接続前のバッファをクリアしています。
- ReadTimeoutを設定しておくとSerialPortのトラブルでUnityのプログラム全体がフリーズするのを防ぐことができます。