如何正确地将接收到的串口数据解析成行?
How to correctly parse received serial data into lines?
我正在创建一个与不断发送数据的串行设备通信的程序。我每 100 毫秒从设备读取一次数据(使用计时器)。我使用 port.ReadExisting()
从设备接收所有当前可用的数据,然后尝试将其拆分成行,因为我需要检查一些接收到的数据,最好的方法是检查行。当设备发送不以 "\r\n"
或 '\n'
.
结尾的数据时会出现问题
完美的情况下port.ReadExisting()
returns: "sampletext\r\nsomesampletext\nsampletext\r\n
但当结尾没有CR或LF字符时出现问题:
第一次port.ReadExisting()
returns这次:"text\nsamp"
第二次port.ReadExisting()
returns这个:letext\r\ntext\r\n"
最终结果应如下所示:
text
sampletext
text
但我得到的是这样的:
text
samp
letext
text
我的代码:
这是每 100 毫秒运行一次的 timer
:
private void CommandTimer_Tick(object sender, EventArgs e)
{
BackgroundWorker seriaDataWorker = new BackgroundWorker();
seriaDataWorker.DoWork += (obj, p) => PrintSerialData();
seriaDataWorker.RunWorkerAsync();
}
BackgroundWorker
由计时器调用:
private void PrintSerialData()
{
try
{
if (RandomReboot)
{
RebootWatch.Start();
}
if (COMport.IsOpen)
{
if (COMport.BytesToRead != 0)
{
SerialPrint(COMport.ReadExisting());
}
}
}
catch (System.IO.IOException SerialException)
{
return;
}
}
将接收到的数据解析成行的函数:
private void SerialPrint(string data)
{
using (var buffer = new StringReader(data))
{
string line = "";
while((line = buffer.ReadLine()) != null)
{
if (CheckForAnsw)
{
ReceivedCommandData = line;
if (ReceivedCommandData.Contains(AnswExpected))
{
ReceivedAnsw = true;
ReceivedLine = ReceivedCommandData;
ReceivedCommandData = "";
}
}
this.Invoke(new MethodInvoker(delegate
{
AppendText(TextBox_System_Log, Color.Black, line + "\r\n");
}
));
}
}
}
我知道问题是 buffer.ReadLine()
将不以 CR 或 LF 字符结尾的字符串的其余部分视为单独的一行,但我不知道如何解决它。
我过去曾尝试使用 port.ReadLine()
,但它速度较慢,并且在串行端口断开连接等时给我带来了问题。
我认为 StringReader
没有简单的方法来处理这个问题。相反,您可以自己拆分字符串:
private static string _buffer = string.Empty;
private static void SerialPrint(string data)
{
// Append the new data to the leftover of the previous operation
data = _buffer + data;
int index = data.IndexOf('\n');
int start = 0;
while (index != -1)
{
var command = data.Substring(start, index - start);
ProcessCommand(command.TrimEnd('\r'));
start = index + 1;
index = data.IndexOf('\n', start);
}
// Store the leftover in the buffer
if (!data.EndsWith("\n"))
{
_buffer = data.Substring(start);
}
else
{
_buffer = string.Empty;
}
}
private static void ProcessCommand(string command)
{
Console.WriteLine(command);
}
您可以使用 AnonymousPipes 来传输和缓冲传入的数据,并将它们作为行读取以将它们输出到某个地方。
这是一个创建服务器和客户端管道流的小示例,然后在一个任务中将数据写入服务器(数据中有一些换行符)并在每行的不同任务中读取数据并将它们输出到控制台。
public class Program
{
public static async Task Main()
{
(var writer, var reader) = CreatePipe();
using (writer)
using (reader)
{
var writerTask = Task.Run(async () =>
{
writer.AutoFlush = true;
writer.Write("?");
for (int i = 0; i < 100; i++)
{
if (i % 10 == 9)
{
await writer.WriteAsync("!");
await writer.WriteAsync(Environment.NewLine);
await writer.WriteAsync("?");
}
else
{
await writer.WriteAsync((i % 10).ToString());
}
await Task.Delay(100);
}
writer.Close();
});
var readerTask = Task.Run(async () =>
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
Console.WriteLine(line);
}
});
await Task.WhenAll(writerTask, readerTask);
}
}
public static (StreamWriter, StreamReader) CreatePipe()
{
var server = new AnonymousPipeServerStream(PipeDirection.Out);
var client = new AnonymousPipeClientStream(server.GetClientHandleAsString());
return
(
new StreamWriter(server, Encoding.UTF8),
new StreamReader(client, Encoding.UTF8)
);
}
}
尝试根据您的用例调整此代码并在遇到困难时发表评论。
\r\n
和 \n
的问题可以通过 Environment.NewLine
解决。我不确定 AppendText
的作用,但是如果您使用它来存储值,那么您就过度了。您需要的是首先将所有数据存储在 StringBuilder
中然后处理它们,或者处理每个数据并将它们存储在托管类型(例如数组)中,以分别定义每一行。仅在表示层使用 string
(如果您有一些 GUI 希望用户看到结果)。
所以,我建议将这些行存储在 StringBuilder
中,像这样:
private readonly StringBuilder _strDataBuilder = new StringBuilder();
private void PrintSerialData()
{
try
{
if (RandomReboot)
{
RebootWatch.Start();
}
if(COMport.IsOpen && COMport.BytesToRead != 0)
{
var data = COMport.ReadExisting();
if(!string.IsNullOrEmpty(data)) {
_strDataBuilder.Append(data);
}
}
}
catch (System.IO.IOException SerialException)
{
return;
}
}
private void SerialPrint()
{
var data = _strDataBuilder.ToString();
if(string.IsNullOrEmpty(data)) { return; }
var lines = data.Split(Environment.NewLine);
if(lines.Length == 0) { return; }
for(int x = 0; x < lines.Length; x++)
{
var line = lines[x];
if (CheckForAnsw)
{
ReceivedCommandData = line;
if (ReceivedCommandData.Contains(AnswExpected))
{
ReceivedAnsw = true;
ReceivedLine = ReceivedCommandData;
ReceivedCommandData = "";
}
}
this.Invoke(new MethodInvoker(delegate
{
AppendText(TextBox_System_Log, Color.Black, line + Environment.NewLine);
}
));
}
}
当您想添加更多处理步骤或重用结果时,首先存储它们将使事情更具可维护性和可修复性。
尽管 SerialPrint()
如果您只是 re-print GUI
中的数据,那么 SerialPrint()
是不必要的。由于数据已经分行。所以,如果你这样做
TextBox_System_Log.Text = _strDataBuilder.ToString();
直接,将以默认颜色在行中列出它们。但是,如果您打算拆分它们以分别处理每一行(例如验证),那么就可以了。
我正在创建一个与不断发送数据的串行设备通信的程序。我每 100 毫秒从设备读取一次数据(使用计时器)。我使用 port.ReadExisting()
从设备接收所有当前可用的数据,然后尝试将其拆分成行,因为我需要检查一些接收到的数据,最好的方法是检查行。当设备发送不以 "\r\n"
或 '\n'
.
完美的情况下port.ReadExisting()
returns: "sampletext\r\nsomesampletext\nsampletext\r\n
但当结尾没有CR或LF字符时出现问题:
第一次port.ReadExisting()
returns这次:"text\nsamp"
第二次port.ReadExisting()
returns这个:letext\r\ntext\r\n"
最终结果应如下所示:
text
sampletext
text
但我得到的是这样的:
text
samp
letext
text
我的代码:
这是每 100 毫秒运行一次的 timer
:
private void CommandTimer_Tick(object sender, EventArgs e)
{
BackgroundWorker seriaDataWorker = new BackgroundWorker();
seriaDataWorker.DoWork += (obj, p) => PrintSerialData();
seriaDataWorker.RunWorkerAsync();
}
BackgroundWorker
由计时器调用:
private void PrintSerialData()
{
try
{
if (RandomReboot)
{
RebootWatch.Start();
}
if (COMport.IsOpen)
{
if (COMport.BytesToRead != 0)
{
SerialPrint(COMport.ReadExisting());
}
}
}
catch (System.IO.IOException SerialException)
{
return;
}
}
将接收到的数据解析成行的函数:
private void SerialPrint(string data)
{
using (var buffer = new StringReader(data))
{
string line = "";
while((line = buffer.ReadLine()) != null)
{
if (CheckForAnsw)
{
ReceivedCommandData = line;
if (ReceivedCommandData.Contains(AnswExpected))
{
ReceivedAnsw = true;
ReceivedLine = ReceivedCommandData;
ReceivedCommandData = "";
}
}
this.Invoke(new MethodInvoker(delegate
{
AppendText(TextBox_System_Log, Color.Black, line + "\r\n");
}
));
}
}
}
我知道问题是 buffer.ReadLine()
将不以 CR 或 LF 字符结尾的字符串的其余部分视为单独的一行,但我不知道如何解决它。
我过去曾尝试使用 port.ReadLine()
,但它速度较慢,并且在串行端口断开连接等时给我带来了问题。
我认为 StringReader
没有简单的方法来处理这个问题。相反,您可以自己拆分字符串:
private static string _buffer = string.Empty;
private static void SerialPrint(string data)
{
// Append the new data to the leftover of the previous operation
data = _buffer + data;
int index = data.IndexOf('\n');
int start = 0;
while (index != -1)
{
var command = data.Substring(start, index - start);
ProcessCommand(command.TrimEnd('\r'));
start = index + 1;
index = data.IndexOf('\n', start);
}
// Store the leftover in the buffer
if (!data.EndsWith("\n"))
{
_buffer = data.Substring(start);
}
else
{
_buffer = string.Empty;
}
}
private static void ProcessCommand(string command)
{
Console.WriteLine(command);
}
您可以使用 AnonymousPipes 来传输和缓冲传入的数据,并将它们作为行读取以将它们输出到某个地方。
这是一个创建服务器和客户端管道流的小示例,然后在一个任务中将数据写入服务器(数据中有一些换行符)并在每行的不同任务中读取数据并将它们输出到控制台。
public class Program
{
public static async Task Main()
{
(var writer, var reader) = CreatePipe();
using (writer)
using (reader)
{
var writerTask = Task.Run(async () =>
{
writer.AutoFlush = true;
writer.Write("?");
for (int i = 0; i < 100; i++)
{
if (i % 10 == 9)
{
await writer.WriteAsync("!");
await writer.WriteAsync(Environment.NewLine);
await writer.WriteAsync("?");
}
else
{
await writer.WriteAsync((i % 10).ToString());
}
await Task.Delay(100);
}
writer.Close();
});
var readerTask = Task.Run(async () =>
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
Console.WriteLine(line);
}
});
await Task.WhenAll(writerTask, readerTask);
}
}
public static (StreamWriter, StreamReader) CreatePipe()
{
var server = new AnonymousPipeServerStream(PipeDirection.Out);
var client = new AnonymousPipeClientStream(server.GetClientHandleAsString());
return
(
new StreamWriter(server, Encoding.UTF8),
new StreamReader(client, Encoding.UTF8)
);
}
}
尝试根据您的用例调整此代码并在遇到困难时发表评论。
\r\n
和 \n
的问题可以通过 Environment.NewLine
解决。我不确定 AppendText
的作用,但是如果您使用它来存储值,那么您就过度了。您需要的是首先将所有数据存储在 StringBuilder
中然后处理它们,或者处理每个数据并将它们存储在托管类型(例如数组)中,以分别定义每一行。仅在表示层使用 string
(如果您有一些 GUI 希望用户看到结果)。
所以,我建议将这些行存储在 StringBuilder
中,像这样:
private readonly StringBuilder _strDataBuilder = new StringBuilder();
private void PrintSerialData()
{
try
{
if (RandomReboot)
{
RebootWatch.Start();
}
if(COMport.IsOpen && COMport.BytesToRead != 0)
{
var data = COMport.ReadExisting();
if(!string.IsNullOrEmpty(data)) {
_strDataBuilder.Append(data);
}
}
}
catch (System.IO.IOException SerialException)
{
return;
}
}
private void SerialPrint()
{
var data = _strDataBuilder.ToString();
if(string.IsNullOrEmpty(data)) { return; }
var lines = data.Split(Environment.NewLine);
if(lines.Length == 0) { return; }
for(int x = 0; x < lines.Length; x++)
{
var line = lines[x];
if (CheckForAnsw)
{
ReceivedCommandData = line;
if (ReceivedCommandData.Contains(AnswExpected))
{
ReceivedAnsw = true;
ReceivedLine = ReceivedCommandData;
ReceivedCommandData = "";
}
}
this.Invoke(new MethodInvoker(delegate
{
AppendText(TextBox_System_Log, Color.Black, line + Environment.NewLine);
}
));
}
}
当您想添加更多处理步骤或重用结果时,首先存储它们将使事情更具可维护性和可修复性。
尽管 SerialPrint()
如果您只是 re-print GUI
中的数据,那么 SerialPrint()
是不必要的。由于数据已经分行。所以,如果你这样做
TextBox_System_Log.Text = _strDataBuilder.ToString();
直接,将以默认颜色在行中列出它们。但是,如果您打算拆分它们以分别处理每一行(例如验证),那么就可以了。