Here's the problem I'm trying to solve...
I'm writing a program that controls a piece of test equipment. The program polls the instrument via serial interface every second, and gets a string of test data in return.
Connection speed is 9600 baud, and the instrument only sends about 100 characters at a time, so performance is not an issue.
One of the requirements is a "cycle" mode, which is should work as follows:
The user sets up three timer intervals - Pre-Test time, Test time, and Inter-test time - and clicks a Start button on the UI.
The program commands the instrument to start a test cycle, and displays data received by the instrument until the Pre-Test time has expired.
The program then displays AND records the data until the Test time has expired.
The program then displays data until the Inter-test time has expired.
Steps 2 - 4 are repeated until the user clicks a Stop button on the UI.
In the past, I've written simple state machines for this kind of problem. But for this project, (and with the help of SO Rx experts), I've been able to use Rx to parse the serial data, and am very happy with the resulting code.
My intuition tells me that this problem might also be nicely handled by Rx, but it's not at all clear how to proceed.
So, is my intuition correct, or would you suggest a different approach?
If Rx is a good fit, could someone provide some quick and dirty sample code that demonstrates a solution?
Edit...as per Enigmativity:
Here's the code that reads the serial data. In a nutshell, it converts a stream of serial data into individual packet strings e.g. "A0", "C00004", "H0501100", etc. These are passed to the ProcessPacket method, which interprets the packets as instrument status, pressures, etc.
/// <summary>
/// Create a Reactive Extensions subscription to the selected serial port
/// </summary>
private IDisposable SetupSerialDataSubscription()
{
// Create Observable stream of chars from serial port's DataRecieved event
var serialData = Observable
.FromEventPattern<SerialDataReceivedEventArgs>(_serialPort, "DataReceived")
// Collapse groups of chars into stream of individual chars
.SelectMany(_ =>
{
int bytesToRead = _serialPort.BytesToRead;
byte[] bytes = new byte[bytesToRead];
int nbrBytesRead = _serialPort.Read(bytes, 0, bytesToRead);
// Empty array if no bytes were actually read
if (nbrBytesRead == 0)
return new char[0];
var chars = Encoding.ASCII.GetChars(bytes);
return chars;
})
// Strip out null chars, which can cause trouble elsewhere (e.g. Console.WriteLine)
.SkipWhile(c => c == '\0');
// Emits packets, one string per packet, e.g. "A0", "C00004", "H0501100", etc
var packets = serialData
.Scan(string.Empty, (prev, cur) => char.IsLetter(cur) ? cur.ToString() : prev + cur)
.Where(IsCompletePacket);
// Process each packet, on the UI thread
var packetsSubscription = packets
.ObserveOn(this)
.Subscribe(packet => ProcessPacket(packet) );
return packetsSubscription;
}