Parsing/formatting data from serial port - C#

前端 未结 3 806
别那么骄傲
别那么骄傲 2020-12-01 15:25

I\'ve developed a small program that listens to a serial port. My program is receiving data. The problem is, its not displaying it in the desired format (one string). The da

相关标签:
3条回答
  • 2020-12-01 15:53

    Assuming there is no termination character, something like this may work. The tricky part is figuring out when to print a new line.

    You may try inserting a newline before every ID: (e.g., replace "ID:" with "\r\n\ID:"). This will still sometimes fail when you receive StreetType:AveI first and then "D:23566 St" next. To fix this, you could just look for any I after StreetType:, but that's not as easy as it sounds either -- what if you see 345 Stre, etTy, pe:RdI. Also, what if I is a valid character (tType:DRI,VE ID:23525)?

    I think that the following code should correctly handle these cases. Note that I switched from Console.WriteLine to Console.Write and manually add the new line when needed:

    private static var previousStringPerPort = new Dictionary<SerialPort,string>();
    private static void Port_DataReceived(object sender, 
                                          SerialDataReceivedEventArgs e)
    {
        SerialPort spL = (SerialPort) sender;
        int bufSize = 20;
        Byte[] dataBuffer = new Byte[bufSize];
        Console.WriteLine("Data Received at"+DateTime.Now);
        Console.WriteLine(spL.Read(dataBuffer, 0, bufSize));
        if (!previousStringPerPort.ContainsKey(spL))
            previousStringPerPort[spL] = "";
        string s = previousStringPerPort[spL] + 
                   System.Text.ASCIIEncoding.ASCII.GetString(dataBuffer);
        s = s.Replace("ID:",Environment.NewLine + "ID:");
        if (s.EndsWith("I"))
        {
            previousStringPerPort[spL] = "I";
            s = s.Remove(s.Length-1);
        }
        else if (s.EndsWith("ID"))
        {
            previousStringPerPort[spL] = "ID";
            s = s.Remove(s.Length - 2);
        }
        Console.Write(s);
    }
    

    Now the only problem remaining is that if the last record really does end in I or ID, it will never be printed. A periodic timeout to flush the previous string could fix this, but it introduces (many) more problems of its own.

    0 讨论(0)
  • 2020-12-01 16:14

    The problem is, as you may have guessed, that the event DataReceived is raised as soon as data has been received over the serial port. There may not be a complete record there; the SerialPort object has no clue what you consider to be "enough" data to be significant, or workable.

    The usual solution is to maintain another "buffer" of received data, containing any data you have recognized as incomplete. When data comes in over the port and your event fires, it should first take what's in the buffer and append it to what you have already received. Then, you should start at the beginning of this data buffer and inspect the received data, looking for known patterns of atomic "chunks" of data that have meaning to you; for instance, say the first thing you receive is "ID: 12". You take this, put it in the buffer, then scan the buffer looking for a pattern defined by a regex "ID: \d*? ". Because the trailing space is not present in your buffer, your pattern fails to find anything of meaning, and so you now know you haven't received a full message.

    Then, on the next raising of the DataReceived event, you pull "453 Sta" out of the serial buffer. You append it to what you already have and get "ID:12453 Sta", and when you apply the regex, you get the match "ID: 12345 ". You pass this into a method for further processing (display to the console, maybe), and remove the same string from the front of the buffer, leaving "Sta". Scanning again you don't find anything else of interest, so you leave what you have, and the cycle repeats aws data continues to come in. Obviously, you'll be testing more patterns than just the ID pattern; you may search for an entire "string" you expect to receive, such as "ID: \d*? State: \w{2} ". You may even keep the data in your buffer until you have both strings for a record: "ID:\d*? State:\w{2} Zip:\d{5} StreetType:\w*? ".

    Either way, you will need to identify whether the data being received is either reliably "fixed-length" (meaning each string of a particular type will always have the same number of bytes or characters), or reliably "delimited" (meaning there will be some character or character combination that always separates significant elements of data). If neither of these apply, it may be very difficult to parse the data into single-field chunks.

    Here's a sample based on what you have already:

    private static StringBuilder receiveBuffer = new StringBuilder();
    
    private static void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
    
        SerialPort spL = (SerialPort) sender;
        int bufSize = 20;
        Byte[] dataBuffer = new Byte[bufSize];
        Console.WriteLine("Data Received at"+DateTime.Now);
        Console.WriteLine(spL.Read(dataBuffer, 0, bufSize));
        string s = System.Text.ASCIIEncoding.ASCII.GetString(dataBuffer);
        //here's the difference; append what you have to the buffer, then check it front-to-back
        //for known patterns indicating fields
        receiveBuffer.Append(s);
    
        var regex = new Regex(@"(ID:\d*? State:\w{2} Zip:\d{5} StreetType:\w*? )");
        Match match;
        do{
           match = regex.Match(receiveBuffer.ToString());
           if(match.Success)
           {
              //"Process" the significant chunk of data
              Console.WriteLine(match.Captures[0].Value);
              //remove what we've processed from the StringBuilder.
              receiveBuffer.Remove(match.Captures[0].Index, match.Captures[0].Length);
           }
        } while (match.Success);
    }
    
    0 讨论(0)
  • See tip #1

    http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx

    When using SerialPort.Read(buffer, offset, count), where count is the number of bytes you want to read, check the return value, which tells you the number of bytes actually read. Developers sometimes assume that count bytes/chars will be returned when Read finishes.Here’s what Read actually does. If there are bytes available on the serial port, Read returns up to count bytes but will not block for the remaining bytes. If there are no bytes available on the serial port, Read will block until at least one byte is available on the port, up until the ReadTimeout milliseconds have elapsed, at which time a TimeoutException will be thrown. To fix this in your code, check number of bytes actually read and use that value when processing the returned data.

    Basically, you are not guaranteed to get count bytes. You will get what's available to be read, up to count bytes - not more than count, but possibly less.

    0 讨论(0)
提交回复
热议问题