从串口解析/格式化数据 – 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
使用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
etTy
, etTy
, pe:RdI
。 另外,如果I
是有效字符( tType:DRI
, VE 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); }
现在唯一的问题是,如果最后一条记录真的以I
或ID
结尾,它将永远不会打印出来。 刷新前一个字符串的定期超时可能会解决这个问题,但它会引入(很多)自己的更多问题。