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

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

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()但我的数据没有使用该函数显示事件。 有没有人知道任何其他方法来解析和格式化数据到一个字符串?

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

通常的解决方案是维护接收数据的另一个“缓冲区”,包含您认为不完整的任何数据。 当数据通过端口进入并且您的事件触发时,它应首先获取缓冲区中的内容并将其附加到您已收到的内容中。 然后,您应该从这个数据缓冲区的开头开始,检查收到的数据,寻找对您有意义的primefaces“数据块”的已知模式; 例如,说你收到的第一件事是"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

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

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

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

假设没有终止字符,这样的东西可能会起作用。 棘手的部分是确定何时打印新线。

您可以尝试在每个ID:之前插入换行符ID:例如,将"ID:"替换为"\r\n\ID:" )。 当您收到StreetType:AveI时,这仍然会失败StreetType:AveI首先是StreetType:AveI ,然后是"D:23566 St" 。 为了解决这个问题,你可以在StreetType:之后查找任何I StreetType: ,但这并不像听起来那么容易 – 如果你看到345 Stre etTyetType:RdI 。 另外,如果I是有效字符( tType:DRIVE ID:23525 )怎么办?

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

 private static var previousStringPerPort = new Dictionary(); 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); } 

现在唯一的问题是,如果最后一条记录真的以IID结尾,它将永远不会打印出来。 刷新前一个字符串的定期超时可能会解决这个问题,但它会引入(很多)自己的更多问题。