如何正确地将接收到的串口数据解析成行?

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

直接,将以默认颜色在行中列出它们。但是,如果您打算拆分它们以分别处理每一行(例如验证),那么就可以了。