C#で作ってみた シリアル通信(COMポート)で垂れ流しデータをキャッチする

USBやBluetoothを経由して測量機やバーコードリーダー、2次元コードリーダーなどといった機器とPCを連携したい! でもキーボードとデータ入力が混在するのはイヤだ!

で、どうするんだ?と、無知な私がネットでいろいろ調べて実装方法を検討してみました。そして作ったお試しソース・確認方法を共有していきます。

1.そもそもどういう仕組み?

USB経由でもBluetooth経由でも、「HID」「COM」という言葉を目にします。

「HID」はヒューマンインターフェースデバイスの略。簡単にいうと人が操作して入力する装置のこと。キーボード、マウスのような装置のことです。

「COM」はCOMポート。RS-232C規格というシリアル通信ポートの別称。COM1、COM2・・・という感じです。

USB・Bluetoothは接続方法なだけで、接続した装置と各アプリを連携するのにはこの「HID」や「COM」という言葉に着目することが、重要でした。

ちなみに、USB・BluetoothをPCに接続しただけなのに、装置をピッとしたらテキストエディタにキーボードでないところからデータが入力された。…ってのが「HID」っていう規格と思ったらいいです。この規格は、わかりやすい仕組みですが、どの装置から入力されたか判断するのが困難です。

反対に、装置でピッとしても、アプリ側がシリアル通信ポートを監視して、データを受信できるようにしないと何も起きないってのが「COM」っていう規格だと思ったらいいです。

今回やりたいのは「COM」の規格を使うのが便利です。なお、今回取り上げた装置たちのほとんどはシリアル通信ポートへ一方的にデータを垂れ流せる仕組みを持っています。

2.送られてくるデータはどんなもの?

メーカーによって若干の仕様が異なりますが、基本的な垂れ流しデータは、テキスト開始(STX)・テキスト終了(ETX)の制御文字とデータ内容で構成されます。

STX (16進数: 0x02) データ内容(任意のバイト配列) ETX (16進数: 0x03)

今回はCOMポートを監視して上記のデータが装置から送られてきたら画面に表示する仕組みを試しに実装してみます。各マシンで使えるCOMポートはデバイスマネージャーで確認できます。

3.お試しソース

今回、WPF・C#を使って作ってみました。私の場合はCOM4ポートです。

 画面イメージ

 

 

装置から送られてくるデータがJSON形式だったらなぁ…なんて思いながら書いたソースです。送られてくるデータはお任せします。装置によっては電文仕様が定まっているものもあります。

ソースの中に、DataReceivedイベントを記載していますが、このイベントは「送られてくるデータの途中であっても受信したら呼び出される」ものです。テキストボックスのTextChangedイベントみたいな感じです。今回したいことはデータの開始・終了位置までのデータを取込むことです。ここを実現化できるよう作っています。

ちなみに参考にさせて頂いた色々なサイトでは、通信することへの説明がメインとなっていたので、「ReadExisting」や「ReadLine」で読込んだデータをそのまま使うといったものが多かったです。

参考にさせて頂いたサイト: C#でシリアル通信する方法をメモ WPF | hyperT'sブログ (hyperts.net)

using System;
using System.Windows;
using System.IO.Ports;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        //STX
        private const char STX = '\x02';
        //ETX
        private const char ETX = '\x03';

        //シリアルポートクラス
        private SerialPort SeriPort = null;
        //受信メッセージ
        private string ReceiveMsg = null;

        public MainWindow()
        {
            InitializeComponent();
        }

        /// ポート監視開始ボタン
        private void ButtonClicked(object sender, RoutedEventArgs e)
        {
            try
            {
                this.btnStart.IsEnabled = false;

                //シリアルポートの初期化(接続設定)
                SeriPort = new SerialPort()
                {
                    BaudRate = 9600, //変調回数(1秒あたり)
                    DataBits = 8, // 1変調あたりのbit数
                    StopBits = StopBits.One,
                    Parity = Parity.None,
                    Handshake = Handshake.None,
                    DtrEnable = false,
                    RtsEnable = false,
                    PortName = "COM4",
                    ReadTimeout = 10000,
                    WriteTimeout = 10000,
                };
                //データ受信時に呼び出すイベント
                SeriPort.DataReceived += SerialPort_DataReceived;
                //ポートの監視を開始する
                SeriPort.Open();

            } catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
                this.btnStart.IsEnabled = true;
            }
        }

        /// シリアルポート・データ受信イベント
        /// ※データを受信する都度呼ばれるので、電文が全て送られてくるとは限らないので注意
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort seriPort = (SerialPort)sender;
            //受信データ長を取得
            int readByte = seriPort.BytesToRead;

            if (readByte > 0)
            {
                byte[] buff = new byte[readByte];
                //シリアルポートから受信データをバッファへ読込む
                seriPort.Read(buff, 0, readByte);

                //初期化
                bool isStx = false;
                bool isEtx = false;
                int posStart = 0;
                int posEnd = readByte - 1;

                for (int i = 0; i < buff.Length; i++)
                {
                    if (!isStx && buff[i] == STX)
                    {
                        //STXがあればその次の位置を開始インデックスにする
                        if (isEtx) isEtx = false;
                        posStart = i + 1;
                        isStx = true;
                    }
                    if (!isEtx && buff[i] == ETX)
                    {
                        //ETXがあればその前の位置を終了インデックスにする
                        posEnd = i - 1;
                        isEtx = true;
                    }
                }
                //STXがあれば溜めていた受信メッセージをクリアする
                if (isStx) ReceiveMsg = string.Empty;
                //ETXがなければ終了インデックスを末尾にする
                if (!isEtx) posEnd = readByte - 1;

                if (posStart < readByte || posEnd >= 0)
                {
                    //開始・終了インデックスが範囲内の場合
                    byte[] tmpBuff = new byte[posEnd - posStart + 1];
                    //開始から終了インデックスの範囲のデータを文字列変換用のバッファにコピーする
                    Array.Copy(buff, posStart, tmpBuff, 0, tmpBuff.Length);
                    //ASCII文字列としてエンコードして溜めていた受信メッセージに連結する(送信する内容に合わせて下さい)
                    ReceiveMsg += System.Text.Encoding.ASCII.GetString(tmpBuff);
                }

                if (isEtx)
                {
                    //ETXを受け取った場合に合成した受信メッセージを画面の項目へ設定する
                    this.Dispatcher.Invoke((Action)(() => { this.txtResult.Text = ReceiveMsg; }));
                }
                //イベントが発生する都度、受信したメッセージを画面の項目へ設定する
                this.Dispatcher.Invoke((Action)(() => { this.txtReceive.Text = System.Text.Encoding.ASCII.GetString(buff); }));
            }
        }

        /// ウィンドウが閉じられたとき
        private void Window_Closed(object sender, EventArgs e)
        {
            try
            {
                if (SeriPort != null && SeriPort.IsOpen)
                {
                    //ポートが開いている場合はクローズする
                    SeriPort.Close();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

4.確認方法

装置を直接つなげてテストなんてのも当然したいのですが、開発環境には接続できなかったり、テストパターンが作れないなんてこともあり、色々困ることだらけです。

ということで、2つの問題を解決させました。

その1.監視する仮想のCOMポートが欲しい

仮想ポートドライバの「com0com」(フリーソフト)というものがあります。送受信いずれものポートを作るので、今回の場合は、COM4を監視、COM5にテストデータを送信とすることができました。

参考:仮想シリアル(COM) ポートドライバ「com0com」によるシリアル通信 - Qiita

その2.仮想のCOMポートにテストデータを送信したい

ググったらすぐ出てきました。シリアル通信をさせる人たちには有名すぎるソフトのようです。

シリアル通信テスター「Serister」なるフリーソフトがVectorを通じて配布されています。

赤枠のところにテストデータを入力して送信ボタンをポチっとするとCOMポートにその内容が送信できます。↓ではCOM5に送信するので上記「com0com」を通じてCOM4で受信できるようになりました。

ということで以上です。

今回作ったものは、STXで始まり、ETXが送られるまで受信する。それ以外のデータは破棄する。といったものです。改行コード(CR,CR+LF)といった場合もあるようなので、まずは装置の仕様をしっかり確認して作るようにしていかないといけませんね。

タイトルとURLをコピーしました