ちょっとハマった
などなど、先人の残した情報が豊富なチャレンジなのですが、VisualStudio+C#で
FormApplicationを作ったことがあんまりなかったせいか、それでもうまく行かなくてハマったので書き残します。
こんなかんじのFormを適当に作ります。
フォームデザイナの下にserialPortオブジェクトが鎮座ましましてますが、こんなかんじでVSのフォームデザイナはUIオブジェクト以外のオブジェクトをフォームに埋め込むことができます。
これをどうやってやるか、というと、
この様にツールボックスのコンポーネントの中にSerialPortオブジェクトがあるのでこれをドラッグ&ドロップすればOKというわけです。
SerialPortオブジェクトのプロパティでポート名やボーレートを設定できます。
私のアプリではCOMポート名とボーレートをUIから選べるようにしてるんですが、テストのためだけならここで決め打ちしてしまってもよいでしょう。
そして実際にデータ受信するのに重要な操作が、このイベントハンドラの設定です。雷(?)アイコンのタブでイベントハンドラを設定できるので、ここで“DataReceived”に受信処理を担当するメンバメソッドを指定します。まだ作ってないなら空欄をダブルクリックすると“(シリアルポートオブジェクト名)_DataReceived”というメソッドを勝手に作ってくれるので楽できます。
private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try {
string data = serialPort.ReadExisting();
if(!string.IsNullOrEmpty(data)) {
Invoke((MethodInvoker)(() => rcvTextBox.AppendText(data)));
}
}
catch(Exception ex) {
MessageBox.Show(ex.Message);
}
}
connectボタンにもハンドラを設定して、ここでシリアルポートのオープン処理を仕込みます。
private void connectButton_Click(object sender, EventArgs e)
{
if(serialPort.IsOpen) {
serialPort.Close();
connectButton.Text = "connect";
portNames.Enabled = true;
baudRates.Enabled = true;
}
else {
serialPort.PortName = portNames.SelectedItem.ToString();
BuadRateItem baud = (BuadRateItem)baudRates.SelectedItem;
serialPort.BaudRate = baud.BaudRate;
try {
serialPort.Open();
connectButton.Text = "disconnet";
portNames.Enabled = false;
baudRates.Enabled = false;
}
catch(Exception ex) {
MessageBox.Show(ex.Message);
}
}
}
ま、ここまでは冒頭に挙げた先人の知恵と全く一緒です。足りない情報はそこから拾ってください。
なんか繋がらない!
Arduino IDEのシリアルモニタではちゃんとデータが来てるのを確認してるんでArduino側のプログラムに不備はないと思うんですが、先人の知恵に従った実装では待てど暮らせど*_DataRecievedが呼ばれる気配がありません。一体何が違うのか、といろいろオロオロした結果、RtsEnableをTrueに設定すれば良いことがわかりました。
この点に触れている記事が見当たらなかったため右往左往したのですが、(自分が使ってる)Arduino Microだとこうする必要があって、Unoならいらない、とかなんですかね?
こんどはX押してウィンドウを閉じようとするとハングする!
無事データも受信できてめでたしめでたし、シリアル通信のデータをダンプするだけじゃなく見栄え良く表示する実装に移ろうか、と思ったらウィンドウを閉じてアプリを落とそうとするとそのまま固まってしまうことに気づきました。ちゃんと切断してから閉じれば即終了するのですが、これはちょっと不便です。
ああー、ちゃんとウィンドウが閉じるときにシリアルをCloseしてやればいいのかな、とやってみたのですが症状変わらず。
connectButton_Click内でCloseする分にはちゃんと働くのに、なぜ?となるわけですが、解決方法はGoogle先生が見つけてくれました。
フォームのデストラクト時SerialPortは自動でクローズされて解体されるわけですが、RtsEnableをTrueにしてオープンしているSerialPortオブジェクトがある場合、そのままだとデッドロックするようです。FormClosingイベントハンドラ内で明示的にCloseする場合でも一緒。
なので、
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if(serialPort.IsOpen && serialPort.RtsEnable) {
e.Cancel = true;
var tryCloseThread = new Thread(() => {
try {
serialPort.Close();
}
catch(Exception ex) {
MessageBox.Show(ex.Message);
}
this.Invoke((MethodInvoker)(() => this.Close()));
});
tryCloseThread.Start();
}
}
こんなかんじでRtsEnableがTrueの場合はウィンドウのCloseイベントを一旦キャンセルして別スレッドでSerialPortをCloseしてから改めてウィンドウを閉じるとうまくいくようです。