我开发了一个监听串口的小程序。我的程序正在接收数据。问题是,它没有以所需的格式显示它(一个字符串)。我的程序接收的数据有两个字符串,例如:
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(),但我的数据没有使用该函数显示事件。有没有人知道任何其他方法来解析和格式化数据到一个字符串?
答案 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
使用SerialPort.Read(缓冲区,偏移量,计数)时,count为 要读取的字节数,检查返回值,告诉 你实际读取的字节数。开发人员有时会假设 当读完时,将返回计数字节/字符。这就是什么 实际上读了。如果串口上有可用字节, 读取返回计数字节但不会阻止剩余的 字节。如果串行端口上没有可用字节,则Read将 阻塞直到端口上至少有一个字节可用,直到 ReadTimeout毫秒已经过去,此时a 将抛出TimeoutException。要在代码中修复此问题,请检查 实际读取的字节数,并在处理时使用该值 返回数据。
基本上,您无法保证获得计数字节。您将获得可读取的内容,最多可计算字节数 - 不超过计数,但可能更少。
答案 2 :(得分:0)
假设没有终止字符,这样的事情可能有效。棘手的部分是确定何时打印新行。
您可以尝试在每ID:
之前插入换行符(例如,将"ID:"
替换为"\r\n\ID:"
)。如果您先收到StreetType:AveI
,然后再收到"D:23566 St"
,则有时仍会失败。要解决此问题,您可以在I
之后查找任何StreetType:
,但这并不像听起来那么容易 - 如果看到345 Stre
,etTy
,{ {1}}。另外,如果pe:RdI
是有效字符(I
,tType: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
结束,则永远不会打印出来。刷新前一个字符串的定期超时可能会解决这个问题,但它会引入(很多)自己的更多问题。