我没有发现RX非常适合这种串行通信。一般来说,RX似乎更多的是单向数据流,而不是来回协议。对于这样的串行通信,我围绕使用WaitHandles
的串口编写了一个类,等待命令的响应。一般结构如下:
应用程序调用一个方法来启动异步操作以发送一系列命令。这将启动一个线程(我相信从线程池),轮流发送每个命令。一旦发送一个命令,操作就等待一个WaitHandle来获取响应(或者超时并重试或操作失败)。处理响应时,收到WaitHandle信号并发送下一个命令。
串行接收事件(在数据进入时在后台线程上运行)构建数据包。收到完整的数据包后,检查是否发送了命令。如果是这样,则发送新响应的线程并等待不同的WaitHandle来处理响应(这对于防止接收方甩掉响应数据很重要)。
编辑:增加了一个(稍大)示例显示两个核心发送和接收方法。
未显示Me.Receiver
属性,它属于ISerialReceiver
类型,负责构建数据包但不确定数据是否是正确的响应。同样未示出的是CheckResponse和ProcessIncoming,它们是由派生类覆盖的两种抽象方法,以确定响应是否是刚刚发送的命令,并分别处理“未经请求的”传入数据包。
''' <summary>This field is used by <see cref="SendCommand" /> to wait for a
''' response after sending data. It is set by <see cref="ReceiveData" />
''' when <see cref="ISerialReceiver.ProcessResponseByte">ProcessResponseByte</see>
''' on the <see cref="Receiver" /> returns true.</summary>
''' <remarks></remarks>
Private ReceiveResponse As New System.Threading.AutoResetEvent(False)
''' <summary>This field is used by <see cref="ReceiveData" /> to wait for
''' the response to be processed after setting <see cref="ReceiveResponse" />.
''' It is set by <see cref="SendCommand" /> when <see cref="CheckResponse" />
''' returns, regardless of the return value.</summary>
''' <remarks></remarks>
Private ProcessResponse As New System.Threading.ManualResetEvent(True)
''' <summary>
''' This field is used by <see cref="SendCommand" /> and <see cref="ReceiveData" />
''' to determine when an incoming packet is a response packet or if it is
''' one of a continuous stream of incoming packets.
''' </summary>
''' <remarks></remarks>
Private responseSolicited As Boolean
''' <summary>
''' Handles the DataReceived event of the wrapped SerialPort.
''' </summary>
''' <param name="sender">The wrapped SerialPort that raised the event.
''' This parameter is ignored.</param>
''' <param name="e">The event args containing data for the event</param>
''' <remarks>This function will process all bytes according to the
''' <see cref="Receiver" /> and allow <see cref="SendCommand" /> to
''' continue or will call <see cref="ProcessIncoming" /> when a complete
''' packet is received.</remarks>
Private Sub ReceiveData(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
If e.EventType <> SerialData.Chars Then Exit Sub
Dim input() As Byte
SyncLock _portLock
If Not _port.IsOpen OrElse _port.BytesToRead = 0 Then Exit Sub
input = New Byte(_port.BytesToRead - 1) {}
_port.Read(input, 0, input.Length)
End SyncLock
'process the received data
If input Is Nothing OrElse input.Length = 0 OrElse Me.Receiver Is Nothing Then Exit Sub
Dim responseCompleted As Boolean
For i As Integer = 0 To input.Length - 1
responseCompleted = Me.Receiver.ProcessResponseByte(input(i))
'process completed response
If responseCompleted Then
responseSolicited = False
System.Threading.WaitHandle.SignalAndWait(ReceiveResponse, ProcessResponse)
'the data is not a response to a command sent by the decoder
If Not responseSolicited Then
ProcessIncoming(Me.Receiver.GetResponseData())
End If
End If
Next
End Sub
''' <summary>
''' Sends a data command through the serial port.
''' </summary>
''' <param name="data">The data to be sent out the port</param>
''' <returns>The data received from the port or null if the operation
''' was cancelled.</returns>
''' <remarks>This function relies on the Receiver
''' <see cref="ISerialReceiver.GetResponseData">GetResponseData</see> and
''' the overriden <see cref="CheckResponse" /> to determine what response
''' was received and if it was the correct response for the command.
''' <seealso cref="CheckResponse" /></remarks>
''' <exception cref="TimeoutException">The operation timed out. The packet
''' was sent <see cref="MaxTries" /> times and no correct response was received.</exception>
''' <exception cref="ObjectDisposedException">The SerialTransceiver was disposed before
''' calling this method.</exception>
Private Function SendCommand(ByVal data() As Byte, ByVal ignoreCancelled As Boolean) As Byte()
CheckDisposed()
If data Is Nothing Then Return Nothing
'make a copy of the data to ensure that it does not change during sending
Dim sendData(data.Length - 1) As Byte
Array.Copy(data, sendData, data.Length)
Dim sendTries As Integer = 0
Dim responseReceived As Boolean
Dim responseData() As Byte = Nothing
ReceiveResponse.Reset()
ProcessResponse.Reset()
While sendTries < MaxTries AndAlso Not responseReceived AndAlso _
(ignoreCancelled OrElse Not Me.IsCancelled)
'send the command data
sendTries += 1
If Not Me.WriteData(sendData) Then Return Nothing
If Me.Receiver IsNot Nothing Then
'wait for Timeout milliseconds for a response. If no response is received
'then waitone will return false. If a response is received, the AutoResetEvent
'will be triggered by the SerialDataReceived function to return true.
If ReceiveResponse.WaitOne(Timeout, False) Then
Try
'get the data that was just received
responseData = Me.Receiver.GetResponseData()
'check to see if it is the correct response
responseReceived = CheckResponse(sendData, responseData)
If responseReceived Then responseSolicited = True
Finally
'allow the serial receive function to continue checking bytes
'regardless of if this function throws an error
ProcessResponse.Set()
End Try
End If
Else
'when there is no Receiver, assume that there is no response to
'data sent from the transceiver through this method.
responseReceived = True
End If
End While
If Not ignoreCancelled AndAlso Me.IsCancelled Then
'operation was cancelled, return nothing
Return Nothing
ElseIf Not responseReceived AndAlso sendTries >= MaxTries Then
'operation timed out, throw an exception
Throw New TimeoutException(My.Resources.SerialMaxTriesReached)
Else
'operation completed successfully, return the data
Return responseData
End If
End Function
我只是有点理解这一点,但我认为我会尝试实施它,看看它是否有效,如果是的话,将通过它来增加我的理解。但是,“receivedData.Subscribe(重放)”上存在编译错误;“线。编译器说:“不能从'System.Reactive.Subjects.ReplaySubject'转换为'System.IObserver >” –
我在上一行有一个错误,我解决了这个问题,现在编写代码。它似乎工作,虽然它只能工作一次。不止一次运行应用程序总是会挂起,我从未收到过设备的好回复。不知道那里发生了什么 - 我正在关闭并在方法的末尾放置SerialPort对象。但这可能是一个单独的问题。这似乎是一个很好的方法。谢谢! –
我有一个串行端口间谍应用程序运行,我认为这可能导致挂起。代码现在正在工作,尽管我在serialPort.Write(s)调用之后仍然使用Thread.Sleep()调用。似乎只需要一些时间来处理命令并发送回应,这是有道理的。如果我理解正确,DataReceived事件可能会在所有数据收到之前被多次触发,这可能是问题所在。 –