C# Windows form - parsing string received from ser

2020-05-02 11:11发布

问题:

I'm developing a Windows Form application using C#, in which I receive data from the serial port and for now I have the following code (this is just the relevant code for my problem):

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)     
{
      ReceivedData = serialPort1.ReadExisting();
      this.Invoke(new EventHandler(interp_string));       
}

private void interp_string(object sender, EventArgs e)      
{
      textReceive.Text += ReceivedData + "\n";
}

But now I need to parse the received data into small string. The ReceivedData variable is a combination of multiple strings with the following format: "value time \n" where value goes from 0 to 1024 and time is in seconds (and is always increasing) and has 4 decimal places. I need to split the ReceivedData variable into individual values and it's corresponding time to plot it in a chart. Taking in account that using ReadExisting, it may happen that one string will be read only partially and the rest will only be read in the next time that the DataReceived event is triggered, but I don't mind if I lose one point of data, is not crucial.

I already tried to use ReadLine instead of ReadExisting and I managed to split each string and plot the data but, given the large amount of data the app is receiving, one string per 1 ms, the app can't keep up and even though it has passed 10 seconds the app is still printing data from the 2nd second, and I press a button to stop receiving data the app keeps printing values, for a long time, that I assume are the ones stored in the receiving buffer. And changing to ReadExisting was the only method I found to read and print everything in real time.

回答1:

You can always turn to regex in String types instead of spliting every line all alone. Regex .NET , Matches method

Lets suppose you have this intercepted data:

1250 154873210
1250 15487556574
1250 15487444
1250 154871111
1250 154875454524545444
1250 154873210
1250 15487556574
1250 15487444
1250 154871111
1250 154875454524545444
1250 154873210
1250 15487556574
1250 15487444
1250 154871111
1250 154875454524545444
1250 154873210
1250 15487556574
1250 15487444
1250 154871111
89877 154875454524545444
001 154873210
877 15487556574
15647 15487444
540 154871111
12 154875454524545444

With regular expression it would be easy to get paired Value/Time as a result (as mentioned in the link above).

So your regular would be :

(?'Value'\d+)\s*(?'Time'\d+\.\d+)

Demo

C# Example :

        string ReceivedData = "1221 1111.1111\n1221 1111.1111";
        MatchCollection matches = Regex.Matches(ReceivedData, @"(?'Value'\d+)\s*(?'Time'\d+\.\d+)");
        Console.WriteLine("Matches found : "+matches.Count);
        foreach(Match Pairs in matches)
        {

            Console.WriteLine(String.Format("Match : Value -> {0} , Time -> {1}", Pairs.Groups["Value"], Pairs.Groups["Time"]));
        }


回答2:

The DataReceived event may fire in the middle of a string and you might not receive the whole message yet. You'll need to know what you're looking for, generally a Line Feed (LF) or a Carriage Return (CR). I use StringBuilder to build my strings, below is an example where LF means I got the whole message. I append characters to my string until I know I have the whole message. I clear the buffer quickly, because your next string can be coming in while you are evaluating. For this simple example, I'm just calling a function to evaluate my string, but you may want to use a delegate here.

StringBuilder sb = new StringBuilder();
char LF = (char)10;

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string Data = serialPort1.ReadExisting();

    foreach (char c in Data)
    {
        if (c == LF)
        {
            sb.Append(c);

            CurrentLine = sb.ToString();
            sb.Clear();

            //do something with your response 'CurrentLine'
            Eval_String(CurrentLine);
        }
        else
        {
            sb.Append(c);
        }
    }
}

Once you get the whole message you can evaluate it for what you need. I'm not sure what your data looks like but in my example, my message comes back comma delimited, so I can split my string into a string array using a comma as my delimiter and get each value from the message.

public void Eval_String(string s)
{
    string[] eachParam;
    eachParam = s.Split(',');

    if (eachParam.Length == 5)
    {
        //do something
        Console.WriteLine(eachParam[2]);
    }
}

Edit: Here is an example on how to update your gui with a queue.

StringBuilder sb = new StringBuilder();
char LF = (char)10;
Queue<string> q = new Queue<string>();

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string Data = serialPort1.ReadExisting();

    foreach (char c in Data)
    {
        if (c == LF)
        {
            sb.Append(c);

            CurrentLine = sb.ToString();
            sb.Clear();

            q.Enqueue(currentLine);
        }
        else
        {
            sb.Append(c);
        }
    }
}

private void backgroundWorkerQ_DoWork(object sender, DoWorkEventArgs e)
{
    while (true)
    {
        if (backgroundWorkerQ.CancellationPending)
            break;

        if (q.Count > 0)
        {
            richTextBoxTerminal.AppendText(q.Dequeue());
        }

        Thread.Sleep(10);
    }
}

If you've never worked with background worker, you'll just have to create a new backgroundworker object, set supportsCancellation to true then add a event for it's DoWork event. Then when you start up your form you can tell it to .RunWorkerAsync().

if (!backgroundWorkerQ.IsBusy)
{
    backgroundWorkerQ.RunWorkerAsync();
}

See: https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2 Also note that backgroundworker is in it's own thread and you will most likely need to BeginInvoke it to update your textbox.