【问题标题】:How to correctly parse received serial data into lines?如何正确地将接收到的串行数据解析成行?
【发布时间】:2020-10-08 10:51:35
【问题描述】:

我正在创建一个与不断发送数据的串行设备通信的程序。我每 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(),但它速度较慢,并且在串行端口断开连接等时会给我带来问题。

【问题讨论】:

  • 您是否尝试将数据附加到TextBox_System_Log 而不是自己解析?如果是,出了什么问题?
  • @Ackdari 如果我只是将它附加到TextBox_System_Log 没有问题,但正如我所说,这个应用程序需要检查接收到的数据并将 ir 与给定的答案或其他数据进行比较,这就是我需要拆分它的原因成行。
  • 我经常看到这个问题,我已经回答了很多。这里有一篇很好的文章:stackoverflow.com/a/59203139/2009197

标签: c# .net string serial-port


【解决方案1】:

我认为使用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);
}

【讨论】:

  • 我要试试你的代码,如果能解决问题,我会通知你
【解决方案2】:

您可以使用 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)
        );
    }
}

尝试使此代码适应您的用例,如果有困难,请发表评论。

【讨论】:

    【解决方案3】:

    您的\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() 是不必要的,但如果您只是重新打印GUI 中的数据。由于数据已经分行。所以,如果你这样做了

    TextBox_System_Log.Text = _strDataBuilder.ToString();
    

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

    【讨论】:

    • 这不是吐出没有完全传输的行吗?
    • @Ackdari 实际上我错过了这部分,它应该先存储,让我修复它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-22
    • 2012-05-15
    相关资源
    最近更新 更多