Reputation: 4199
I receive data from a serial port and I show those data in a multiline text box. The data have a starting header (>),an identifier ("T" or "I" or "N") and a closing header (<). So a complete message is something like >T123< or >N456< or >I086<. The data shown in txtOutput appear in the correct shape >T123< per each line even if in the serial output windows they appear in this way:
>T22
0<\r\n
>T22
2<\r\n
>T22
2<\r\n
>T223<\r\n
>
T225<\r\n
>
T228<\r\n
....
I need to filter those data for some calculation in a PID and I have created a class to strip out the headers and the identifier. The class returns the data already sorted out in different variables (a, b, c) ready to be used for other calculations. This is the method that receives the data from the serial and append the data in the txtOutput, which is fine. The method then sends the data to class "strp.Distri(invia, out a, out b, out c)".
private void SetText(string text)
{
if (this.txtOutput.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.BeginInvoke(d, new object[] { text });
}
else
{
txtOutput.AppendText(text);
string a = "", b = "", c = "";
string invia = text.ToString();
Stripper strp = new Stripper();
strp.Distri(invia, out a, out b, out c);
textBox7.Text = a; //current
textBox2.Text = b; //temperature
textBox6.Text = c; //giri
This is the class that I am using to filter and strip out the unnecessary characters.
class Stripper
{
public void Distri (string inComing, out string param1, out string param2, out string param3)
{
string current="";
string temperature="";
string numRPM="";
string f = inComing;
char firstChar = f[0];
char lastChar =f [f.Length - 1];
bool test1 =(firstChar.Equals('>'));
bool test2 =(lastChar.Equals('<'));
int messLenght = f.Length;
if (test1 == true && test2 == true && messLenght <=6)
{
f = f.Replace("<", "");
f = f.Replace(">", "");
if (f[0] == 'I')
{
string _current = f;
_current = _current.Replace("I", "");
current = _current;
}
else if (f[0] == 'T')
{
string _temperature = f;
_temperature = _temperature.Replace("T", "");
temperature = _temperature;
}
else if (f[0] == 'N')
{
string _numRPM = f;
_numRPM = _numRPM.Replace("N", "");
numRPM = _numRPM;
}
else
{}
}
else
{}
param1 = current;
param2 = temperature;
param3 = numRPM;
}
}
This is my serial port and the delegate associated:
delegate void SetTextCallback(string text);
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
try
{
SetText(serialPort1.ReadExisting());
}
catch (Exception ex)
{
SetText(ex.ToString());
}
}
My problem is that nothing is returned from a, b and c because the messages after "test1" and "test2" are obviously not true because they are received in fragments. How can I solve this problem? Is there a way to re-assemble those messages correctly before I send them to the class Stripper?
Upvotes: 0
Views: 1707
Reputation: 418
As you receive the data append it to a string. Keep checking the CRLF (\r\n) and if found then you can process the line. But keep in mind there might be characters after you encounter the end because of fragments received from serial port.
Sometime long before, I needed similar functionality. So, the code was similar to below. The receive handler can be like this (receivedText is a global string, receivedData is the current data received)
receivedText = String.Concat(receivedText, receivedData);
/* do nothing - already copied to buffer */
if ((receivedText.Contains("\r\n") == false))
{
return;
}
/* get the lines from the received text */
string[] lines = receivedText.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
/* process your lines here */
/* keep the remaining portion of the fragment after the end of new line */
if(receivedText.EndsWith("\r\n"))
{
receivedText = "";
}
else
{
receivedText = receivedText.Substring(receivedText.LastIndexOf('\n') + 1);
}
Upvotes: 1
Reputation: 941455
SetText(serialPort1.ReadExisting());
That's where it went wrong. Serial ports are no different from other ways to communicate between devices, like TCP. It just implements a simple stream, it doesn't try to assign any meaning to the data. It isn't partial to where the data starts and ends.
So when you call ReadExisting(), you just get whatever happens to be received by the port. Which can be any number of characters, usually a few since serial ports are not very fast. And the slower your program operates, the more characters you get.
So, just like TCP, if you need to be aware of the start and end of a stream of data then you need a protocol. Like TCP has protocols like http, ftp, etc. Extra bytes added to the stream data that help you figure out what a packet of data looks like. There are not a lot of common protocols in use in serial port land, most everybody just makes their own. Except one, commonly used in modems. A special character or string that indicates the end of data. Very often a line feed character. The SerialPort.NewLine property lets you set that, you take advantage of it by using ReadLine() instead of ReadExisting(). The transmitter has to co-operate though, it actually needs to transmits those characters.
Which it already appears to do, your snippet is showing \r\n. Standard characters in Windows to indicate the end of a line. So just set the NewLine property to "\r\n" and use ReadLine() instead ReadExisting() and you're good to go. Avoiding calling the port's Close() method however, that's likely to deadlock if you close your program while the device is sending.
Upvotes: 1
Reputation: 902
For example the input string is ">T223<\r\n", your firstchar will be '>' and lastchar will be '<'. test1 will pass but test2 will always fail. The if condition
if (test1 == true && test2 == true && messLenght <=6)
will never be fulfilled. Replace or remove unwanted chars from your string.
Upvotes: 0