Wait for Response from the Serial Port and Then Send Next Data

Wait for response from the Serial Port and then send next data

Wait in a loop for a full response after you write a first frame.

// Set read timeout to value recommended in the communication protocol specification 
// so serial port operations don't stuck.
_port.WriteTimeout = 200;
_port.ReadTimeout = 200;

public void OnClick()
{
// Write first frame.
_port.Write(...);
// Now wait for the full response.

// Expected response length. Look for the constant value from the device communication
// protocol specification or extract from the response header (first response bytes) if
// there is any specified in the protocol.
int count = ...;
var buffer = new byte[count];
var offset = 0;
while (count > 0)
{
var readCount = _port.Read(buffer, offset, count);
offset += readCount;
count -= readCount;
}
// Now buffer contains full response or TimeoutException instance is thrown by SerialPort.
// Check response status code and write other frames.
}

In order to not block UI thread you most probably still need to utilize synchronous API and Task.Run(). See C# await event and timeout in serial port communication discussion on StackOverflow.

For more information check Top 5 SerialPort Tips article by Kim Hamilton.

How to wait for next serial port data to be append with the collected byte?

First, don't use this.Invoke to call the whole method, just put UI update code in it, like your AppendTextBox method.

Second, do not WAIT in the DataReceived event. Because this event is triggered every time the serial port gets data. The while loop just blocks it.

Your code should be like this:

    List<byte> serialByte = new List<byte>();
int bufferLen = 0;

private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
if (!sp.IsOpen) return;

//other codes

int BytesToRead = sp.BytesToRead;
byte[] temp = new byte[BytesToRead];
sp.Read(temp, 0, BytesToRead);

serialByte.AddRange(temp);

//other codes

if (serialByte.Count >= bufferLen)
{
DataReceived_TranslateData(serialByte.GetRange(0, bufferLen).ToArray());
//clear the list if you need
}
}

C# Serial Port, wait for correct Response before allowing a new message

After 4 days of almost no sleep I figure it out and want to post the solution to anybody who is trying to have some sort of a flow control in their serial communication. In the end I was using async methods. I think this is as simple as it can get for somebody who doesn't have a lot of C# experience. Here is the code for the form:

    namespace serialInterfaceTest
{

public partial class Form1 : Form
{
string serialDataIn;

public Form1()
{
InitializeComponent();
serialPort.PortName = "COM3";
serialPort.BaudRate = 9600;
serialPort.Open();
}

private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
serialDataIn = serialPort.ReadExisting();
this.Invoke(new EventHandler(saveData));
}

public void saveData(object sender, EventArgs e)
{
string trimmedMsg = serialDataIn;
richTextBox.Text += trimmedMsg;


}


private void richTextBox_TextChanged(object sender, EventArgs e)
{
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
}

private async void button_sendMsg_Click(object sender, EventArgs e)
{
button_sendMsg.Enabled = false;
await Task.Run(() => send(textBox_message.Text));
button_sendMsg.Enabled = true;

//WAIT FOR RESPONSE BEFORE ALLOWING THE USER TO SEND ANOTHER MESSAGE
}

private async void button_loopMsg_Click(object sender, EventArgs e)
{
button_loopMsg.Enabled = false;
for (int i = 0; i < 10; i++)
{
await Task.Run(() => send(textBox_message.Text));

//WAIT FOR RESPONSE BEFORE CONTINUING THE LOOP
}
button_loopMsg.Enabled = true;
}

private async Task send(String message)
{
serialDataIn = "";
serialPort.Write(message);

while (!serialDataIn.Contains("*"))
{

//PROCESS ANSWERS HERE
serialDataIn = serialPort.ReadExisting();
if (serialDataIn.Contains("*"))
{
this.Invoke(new EventHandler(saveData));
}

}

}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{

if (serialPort.IsOpen)
{
try
{
serialPort.Close();
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
}
}

}

I have the async method send data, and the two buttons are async as well. When I press them I'm just waiting for the task to complete before another input is allowed. I think this should be a good starting point for other projects as well. UI stays responsive, messages don't get queued up. The richTextBox on the UI get`s updated via Invoke so messages are displayed as soon as they arrive.

Here is the test code for an arduino:

#define println Serial.println

char serialIn;
String appendSerialData;

void setup()
{
Serial.begin(9600);
}

void loop()
{
appendSerialData = "";
while (Serial.available() > 0)
{
serialIn = Serial.read();
appendSerialData += serialIn;
}

/* asterisk functions as message identifier */
delay(1000);

if (appendSerialData != "") println(appendSerialData + " *");

for (int i = 0; i < 5; i ++)
{
println(i);
}
delay(100);

}

If there are any improvements I can make to this I`m happy to hear about it.

SerialPort wait for response

For your original question, to block the executing thread you can use a ManualResetEvent or AutoResetEvent which will get Set when your response has been obtained. There's a fairly good explanation on the page.

For threading, the rule of thumb is that if you're not extremely clear on what you're doing, don't do it.

Synchronous blocking when you have access to events seems like a waste. Considering that the data is a stream, this might end up being a hard to maintain abstraction.

There's a longer explanation of the above idea with an example over here.

You can also do this in async with TaskCompletionSource. Instead of set, you can call SetResult, and you await the .Task, but the idea is pretty much the same.

Waiting for a serial port response using timercallback

From the MSDN pages:

serialport.readline

will wait until it sees an end of line character on the serial port or until ReadTimeout has elapsed.

...

By default, the ReadLine method will block until a line is received. If this behavior is undesirable, set the ReadTimeout property to any non-zero value to force the ReadLine method to throw a TimeoutException if a line is not available on the port.

And for System.Threading.Timer:

Provides a mechanism for executing a method on a thread pool thread at specified intervals.

So Each timer callback may be on a separate thread and each readline() call pauses the current thread until it sees an end of line character. Your timer is firing every 500 milliseconds regardless of what else has occurred. So the first one calls readline() and is waiting there for a end of line character. It doesn't see one so it keeps waiting. 500ms later another thread does the exact same thing. 500ms later the same thing happens, etc. So you are stacking up all these readline() calls waiting for readline() to complete. Eventually you get tired of waiting and kill the program. Instead of readLine() you could do readExisting() to read the current buffer, but you'd have to find where the end of line character is yourself. Either that or you could get rid of the timer entirely and use the serialport events (such as DataReceived) to tell you when you've gotten some data.

C# Wait until data received and go to next iteration

Anyone can give me some idea?

You can use async/await in your code not to block your UI by writing an extension method like below. Usage would be:

async void SomeMethod()
{
SerialPort serialPort = .......

while (true)
{
serialPort.Write(.....);
var retval = await serialPort.ReadAsync();
}

}

The keyword here is using TaskCompletionSource class with your events...


static public class SerialPortExtensions
{
public static Task<byte[]> ReadAsync(this SerialPort serialPort)
{
var tcs = new TaskCompletionSource<byte[]>();
SerialDataReceivedEventHandler dataReceived = null;
dataReceived = (s, e) =>
{
serialPort.DataReceived -= dataReceived;
var buf = new byte[serialPort.BytesToRead];
serialPort.Read(buf, 0, buf.Length);
tcs.TrySetResult(buf);
};
serialPort.DataReceived += dataReceived;
return tcs.Task;
}
}

How to let the program wait to receive the serial port data?

Please read:

If you must use .NET System.IO.Ports.SerialPort.

That is a blog post from Ben Voigt written 2014 on sparxeng.com.

Excerpt:

To put it mildly, System.IO.Ports.SerialPort was designed by computer scientists operating far outside their area of core competence. They neither understood the characteristics of serial communication, nor common use cases, and it shows. Nor could it have been tested in any real world scenario prior to shipping, without finding flaws that litter both the documented interface and the undocumented behavior and make reliable communication using System.IO.Ports.SerialPort (henceforth IOPSP) a real nightmare.

and

The worst offending System.IO.Ports.SerialPort members, ones that not only should not be used but are signs of a deep code smell and the need to rearchitect all IOPSP usage:

  • The DataReceived event (100% redundant, also completely unreliable)
  • The BytesToRead property (completely unreliable)
  • The Read, ReadExisting, ReadLine methods (handle errors completely wrong, and are synchronous)
  • The PinChanged event (delivered out of order with
    respect to every interesting thing you might want to know about it)

and

And the one member that no one uses because MSDN gives no example, but is absolutely essential to your sanity:

  • The BaseStream property

Despite that blame from Ben Voigt, we are using the BytesToRead property and the ReadExisting() including Thread.Sleep(). We avoid DataReceived() and ReadLine(). That was our workaround, we didn't know the BaseStream property as well.

For new projects I would recommend to use the BaseStream property.

Waiting for Serial Port response between command calls

If what you need is:

  1. Send a command
  2. Wait for response
  3. Send next command

Then you can use a Semaphore

It works like the following codes.

Semaphore _sync = new Semaphore(1, 1);
SerialPort _port;
...

void Send(...){
_sync.WaitOne()
_port.Send(...);
}

void _port_DataReceived(...){
_sync.Release();
}

Take a try!



Related Topics



Leave a reply



Submit