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

问题描述

我正在创建一个程序,该程序与不断发送数据的串行设备通信。我每100毫秒(使用计时器)从设备读取一次数据。我使用port.ReadExisting()从设备接收所有当前可用的数据,然后尝试将其分成几行,因为我需要检查一些接收到的数据,而最好的方法是检查行。设备发送不以"\r\n"'\n'结尾的数据时,会发生问题。

在理想情况下,port.ReadExisting()返回:"sampletext\r\nsomesampletext\nsampletext\r\n

但是,如果末尾没有CR或LF字符,则会出现问题:

第一次port.ReadExisting()返回此内容"text\nsamp"

第二次port.ReadExisting()返回此:letext\r\ntext\r\n"

最终结果应如下所示:

text
sampletext
text

但是我得到的是这样的:

text
samp
letext
text

我的代码

这是timer,每100毫秒运行一次:

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可以解决\nEnvironment.NewLine的问题。我不确定AppendText的作用,但是如果您使用它来存储值,那么您会做得过多。您需要先将所有数据存储在StringBuilder中,然后处理它们,或处理每个数据并将它们存储在托管类型(例如Array)中,以分别定义每行。仅在表示层中使用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()中的数据,尽管GUI是不必要的。由于数据已经分隔成几行。所以,如果你这样做

TextBox_System_Log.Text = _strDataBuilder.ToString();

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