从串口解析/格式化数据 - C#

时间:2012-02-29 19:14:26

标签: c# serial-port

我开发了一个监听串口的小程序。我的程序正在接收数据。问题是,它没有以所需的格式显示它(一个字符串)。我的程序接收的数据有两个字符串,例如:

ID:34242 State:NY

邮编:12345 StreetType:Ave

它由块显示,一些数据传递到下一行:

 ID:34242
State:N
Y Zip:12
345 Street
Type:Ave

我使用了SerialDataReceive事件处理程序来接收我的数据,它看起来像这样:

 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);
        Console.WriteLine(s);



    }

如您所见,我将字节检索到缓冲区,创建一个字节数组来保存数据,并使用ASCII编码将字节转换为字符串。我尝试使用ReadLine(),但我的数据没有使用该函数显示事件。有没有人知道任何其他方法来解析和格式化数据到一个字符串?

3 个答案:

答案 0 :(得分:4)

问题是,正如您可能已经猜到的那样,一旦通过串行端口接收到数据,就会引发事件DataReceived。那里可能没有完整的记录; SerialPort对象不知道您认为“足够”的数据是重要的还是可行的。

通常的解决方案是维护接收数据的另一个“缓冲区”,其中包含您认为不完整的任何数据。当数据通过端口进入并且您的事件触发时,它应首先获取缓冲区中的内容并将其附加到您已收到的内容中。然后,您应该从这个数据缓冲区的开头开始,检查收到的数据,寻找对您有意义的原子“数据块”的已知模式;例如,说你收到的第一件事是"ID: 12"。你拿这个,把它放在缓冲区中,然后扫描缓冲区,寻找由正则表达式"ID: \d*? "定义的模式。因为缓冲区中不存在尾随空格,所以您的模式无法找到任何意义,因此您现在知道您没有收到完整的消息。

然后,在下次引发DataReceived事件时,将"453 Sta"拉出串行缓冲区。您将其附加到已有的内容并获得"ID:12453 Sta",并且当您应用正则表达式时,您将获得匹配“ID:12345”。您将此传递给方法以进行进一步处理(可能显示到控制台),并从缓冲区前面删除相同的字符串,留下“Sta”。再次扫描你没有找到任何有趣的东西,所以你留下你拥有的东西,循环重复aws数据继续进来。显然,你将测试更多的模式,而不仅仅是ID模式;您可以搜索您希望收到的整个“字符串”,例如"ID: \d*? State: \w{2} "。您甚至可以将数据保留在缓冲区中,直到您有两个记录字符串:"ID:\d*? State:\w{2} Zip:\d{5} StreetType:\w*? "

无论哪种方式,您都需要确定所接收的数据是否是可靠的“固定长度”(意味着特定类型的每个字符串将始终具有相同数量的字节或字符),或者可靠地“分隔”(意味着将存在一些始终将重要数据元素分开的字符或字符组合。如果这些都不适用,则可能很难将数据解析为单字段块。

以下是基于您已经拥有的内容的示例:

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);
}

答案 1 :(得分:2)

见提示#1

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

  

使用SerialPort.Read(缓冲区,偏移量,计数)时,count为   要读取的字节数,检查返回值,告诉   你实际读取的字节数。开发人员有时会假设   当读完时,将返回计数字节/字符。这就是什么   实际上读了。如果串口上有可用字节,   读取返回计数字节但不会阻止剩余的   字节。如果串行端口上没有可用字节,则Read将   阻塞直到端口上至少有一个字节可用,直到   ReadTimeout毫秒已经过去,此时a   将抛出TimeoutException。要在代码中修复此问题,请检查   实际读取的字节数,并在处理时使用该值   返回数据。

基本上,您无法保证获得计数字节。您将获得可读取的内容,最多可计算字节数 - 不超过计数,但可能更少。

答案 2 :(得分:0)

假设没有终止字符,这样的事情可能有效。棘手的部分是确定何时打印新行。

您可以尝试在每ID:之前插入换行符(例如,将"ID:"替换为"\r\n\ID:")。如果您先收到StreetType:AveI,然后再收到"D:23566 St",则有时仍会失败。要解决此问题,您可以在I之后查找任何StreetType:,但这并不像听起来那么容易 - 如果看到345 StreetTy,{ {1}}。另外,如果pe:RdI是有效字符(ItType:DRI),该怎么办?

我认为以下代码应该正确处理这些情况。请注意,我从VE ID:23525切换到Console.WriteLine并在需要时手动添加新行:

Console.Write

现在唯一的问题是,如果最后一条记录确实以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); } I结束,则永远不会打印出来。刷新前一个字符串的定期超时可能会解决这个问题,但它会引入(很多)自己的更多问题。