Чтение с последовательного порта C # — получение сообщений в кодировке COBS разной длины

#c# #serial-port

#c# #последовательный порт

Вопрос:

итак, я новичок в программировании на C #. Я запрограммировал приложение C # Forms в Visual Studio для связи с устройством / инициализации устройства через последовательный порт. Обмен данными между устройствами осуществляется в кодировке COBS, поэтому нет 0x00 байт, кроме как в конце каждого сообщения. Отправленные и полученные сообщения имеют разную длину.

На данный момент моя проблема заключается в том, что сообщения, которые я получаю, не являются полными или начинаются с середины сообщения, поэтому я не могу инициировать отправленные сообщения с определенным значением в полученных сообщениях. Вы можете определить конец сообщения с полученным 0x00 (0x00 означает конец сообщения в данных в кодировке COBS)

Итак, что мне нужно, так это что-то для обработки полного сообщения и помещения его в массив байтов для анализа, т. е. байт [11] для определенного значения.

Вот что я сделал до сих пор:

     private bool   b_portopen = false;
    private byte[] b_rcv_buffer = new byte[256];

    private void button1_Click(object sender, EventArgs e) {

            //InitTimer();

            if (b_portopen == false)
            {
                serialPort1.PortName = comboBox1.SelectedItem.ToString();
                serialPort1.DataReceived  = new SerialDataReceivedEventHandler(DataReceivedHandler);
                serialPort1.Open();
                b_portopen = true;
                button1.Text = "Close";
                button2.Enabled = true;
                Console.WriteLine("Serial Port Opened");
            }
            else if (b_portopen == true)
            {
                serialPort1.Close();
                b_portopen = false;
                button1.Text = "Open";
                button2.Enabled = false;
                Console.WriteLine("Serial Port Closed");
            }
        }
private async void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            serialPort1.Read(b_rcv_buffer, 0, 256);
            //serialPort1.Read(b_rcv_buffer1, 11, 2);
            richTextBox1_receive.Invoke(new Action(() =>
            {
                richTextBox1_receive.AppendText(BitConverter.ToString(b_rcv_buffer)   "n");
                richTextBox1_receive.ScrollToCaret();
            }));
            switch (b_rcv_buffer[10])
            {
                case b_state_startup:
                    do something

                case b_state_be_start_conf:
                    do something

                case b_state_keepalive_conf:
                    do something

                case b_state_unprepare_conf:
                    do something

                case b_state_prepare_conf:
                    do something


            }


        }
  

Комментарии:

1. Возможно, измените ваш DataReceivedHandler, чтобы он просто буферизовал входящие данные (добавляя к текущему буферу) и имел отдельный поток, обрабатывающий этот буфер, проверяющий наличие завершенных команд и удаляющий связанные данные из буфера. Возможно, используйте что-то вроде потокобезопасного класса, такого как ConcurrentQueue, для буфера, чтобы входящие данные можно было безопасно добавлять во время обработки очереди.

2. Спасибо за ответ. Как вы думаете, каким был бы наилучший способ добавления полученных данных? Если я использую массив байтов для заполнения входящими данными, в какой-то момент он будет заполнен, и я также не смогу вырезать из него полные сообщения. Итак, мне нужно использовать какой-то динамический массив, в котором я могу хранить простые байтовые значения и вырезать их с самого начала, если есть полное сообщение. Существует ли какой-либо тип данных, подобный этому?

3. Я предложил ConcurrentQueue<T> в качестве возможности — использовать Enqueue для постановки в очередь входящих данных побайтно и использовать метод Contains extension для поиска байта 0x00. Обратите внимание, что могут быть классы получше для использования — это была моя первая мысль, поскольку это потокобезопасно.

4. @PaulF: ConcurrentQueue не обеспечивает адекватной синхронизации между потоками для этой цели. (Он синхронизирует каждый байт отдельно, что означает, что вы можете получать несколько входящих сообщений, смешанных вместе в очереди)

Ответ №1:

Итак, я нашел решение с использованием ConcurrentQueue:

     ConcurrentQueue<byte> b_rcv_buffer = new ConcurrentQueue<byte>();

    private Timer timer2;

    public void InitTimer()
            {
                timer2 = new System.Windows.Forms.Timer();
                timer2.Tick  = new EventHandler(timer2_Tick);
                timer2.Interval = 1; // in miliseconds
                timer2.Start();
            }

    private async void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            int bytes = serialPort1.BytesToRead;
            byte[] buf = new byte[bytes];
            serialPort1.Read(buf, 0, serialPort1.BytesToRead);

            for(int i = 0; i < buf.Length; i  )
            {
                b_rcv_buffer.Enqueue(buf[i]); //Enqueue every received Byte in Concurrentqueue
            }
        }

private async void timer2_Tick(object sender, EventArgs e)
        {
            if (b_rcv_buffer.Contains<byte>(0x00))
            {
                byte[] array = b_rcv_buffer.ToArray();

                richTextBox1_receive.Invoke(new Action(() =>
                {
                    richTextBox1_receive.AppendText(BitConverter.ToString(array)   "n");
                    //richTextBox1_receive.ScrollToCaret();
                }));            

                byte ignored;

                while (b_rcv_buffer.TryDequeue(out ignored));
        }
  

Комментарии:

1. Это неверно — если во время выполнения первого Enqueue цикла получено больше данных, DataReceivedHandler он будет выполняться в нескольких потоках одновременно и буферы данных будут чередоваться. DataReceived Событие не вызывает ничего, кроме проблем. Просто создайте один async цикл, который вызывает await port.BaseStream.ReadAsync(...); , обрабатывает данные, повторяет. Таким образом, каждый буфер полностью обрабатывается в полученном порядке. В качестве бонуса это экономит потоки, позволяет избежать других условий гонки, может взаимодействовать с пользовательским интерфейсом без InvokeRequired , не требует ConcurrentQueue и в целом более эффективен.