web-dev-qa-db-ja.com

シリアルポート通信をバッファに読み込み、完全なメッセージを解析する方法

次のコードを使用して、COMポートから値を読み取ります。

Private port As New SerialPort("COM13", 9600, Parity.None, 8, StopBits.One)

Private Sub port_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
    Debug.Print(port.ReadExisting())
End Sub

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    AddHandler port.DataReceived, New SerialDataReceivedEventHandler(AddressOf port_DataReceived)
    port.Open()
End Sub

これは問題なく機能しますが、すべてのデータを取得するわけではなく、結果として1つではなく2つの文字列になります。

例として、comポートが「HELLO2YOU」という単語を送信していた場合、次のようになります。

HEL
LO2YOU

または

HELLO2
YOU

バッファをそこに配置して、表示する前にすべてのデータが読み取られていることを確認するにはどうすればよいですか?

ありがとう!

6
StealthRT

シリアルポート通信をストリーミングデータと考える必要があります。データを受信するときはいつでも、それが完全なメッセージ、部分的なメッセージのみ、または複数のメッセージである可能性があることを期待する必要があります。それはすべて、データが入ってくる速度と、アプリケーションがキューから読み取ることができる速度に依存します。したがって、バッファが必要だと考えるのは正しいことです。ただし、まだ気付いていないかもしれませんが、各メッセージが開始および終了するシリアルポートを厳密に介して知る方法はありません。これは、送信者と受信者の間で合意されたプロトコルを介して処理する必要があります。たとえば、多くの人は、標準のテキスト開始(STX)文字とテキスト終了(ETX)文字を使用して、各メッセージ送信の開始と終了を示します。そうすれば、データを受信したときに、完全なメッセージを受信したことを知ることができます。

たとえば、STXおよびETX文字を使用した場合、次のようなクラスを作成できます。

Public Class DataBuffer
    Private ReadOnly _startOfText As String = ASCII.GetChars(New Byte() {2})
    Private ReadOnly _endOfText As String = ASCII.GetChars(New Byte() {4})

    Public Event MessageReceived(ByVal message As String)
    Public Event DataIgnored(ByVal text As String)

    Private _buffer As StringBuilder = New StringBuilder

    Public Sub AppendText(ByVal text As String)
        _buffer.Append(text)
        While processBuffer(_buffer)
        End While
    End Sub

    Private Function processBuffer(ByVal buffer As StringBuilder) As Boolean
        Dim foundSomethingToProcess As Boolean = False
        Dim current As String = buffer.ToString()
        Dim stxPosition As Integer = current.IndexOf(_startOfText)
        Dim etxPosition As Integer = current.IndexOf(_endOfText)
        If (stxPosition >= 0) And (etxPosition >= 0) And (etxPosition > stxPosition) Then
            Dim messageText As String = current.Substring(0, etxPosition + 1)
            buffer.Remove(0, messageText.Length)
            If stxPosition > 0 Then
                RaiseEvent DataIgnored(messageText.Substring(0, stxPosition))
                messageText = messageText.Substring(stxPosition)
            End If
            RaiseEvent MessageReceived(messageText)
            foundSomethingToProcess = True
        ElseIf (stxPosition = -1) And (current.Length <> 0) Then
            buffer.Remove(0, current.Length)
            RaiseEvent DataIgnored(current)
            foundSomethingToProcess = True
        End If
        Return foundSomethingToProcess
    End Function


    Public Sub Flush()
        If _buffer.Length <> 0 Then
            RaiseEvent DataIgnored(_buffer.ToString())
        End If
    End Sub
End Class

また、通信プロトコルでは、送信者と受信者の間の送信中にメッセージが破損したかどうかを判断できるチェックサムバイトが一般的であることにも言及する必要があります。

9
Steven Doggart

これはごく普通のことで、シリアルポートは非​​常に遅いデバイスです。 9600のようなボーレートがあり、マシンがあまり動かなくなっていない場合、ReadExisting()を使用すると、ポートから1バイトまたは2バイトしか取得できません。 Debug.Print()はラインターミネータを出力するので、受信したものはすべて分割されて表示されます。

これを修正する最も簡単な方法は、代わりにReadLine()を使用することです。そのためには、デバイスが行の終わりに、SerialPort.NewLineプロパティ値と一致する特殊文字を送信する必要があります。これは非常に一般的ですが、改行は定型です。

そうでない場合は、他の種類のバッファリングスキームが必要になります。

3
Hans Passant