Good day,

Not the first time I use the SerialPort.DataReceived event, but for the first time I thought about how to use it correctly and what is hidden under it. At the moment I use the following algorithm (in order to avoid inter-stream errors):

  1. Run the timer for a period of 10-50 msec.
    1.1. In the Tick event, the data from the static variable is assigned to the desired variables. This all happens only if the flag is set, that you can check this static variable. After that, the check permission flag is reset.

  2. Triggering a DataReceived event.
    2.1 If the static variable check flag is set, the command to retransmit data after 10 ms and exit the handler. Otherwise:

  3. Reading data from the port buffer to the end of the line (ReadLine) in a static variable.
  4. Setting the flag to check the static variable.

First question :
What are the algorithms for receiving data on the serial port to the main stream (for example, use Invoke, but I don’t really understand this method) and, of course, with explanations, if possible?

Second question :
What other ways are there to avoid data loss besides the way I use (the command to retransmit data after a certain time)

Third question :
DataReceived event, at which moment it is called? When coming the first byte? When joining EOF?

Thank you very much!

--- UPDATE ---

Inadvertently found the use of Invoke and for some reason immediately understood everything :) What other options are there?

https://stackoverflow.com/questions/11590945/how-to-display-the-data-read-in-datareceived-event-handler-of-serialport

public delegate void AddDataDelegate(String myString); public AddDataDelegate myDelegate; private void Form1_Load(object sender, EventArgs e) { //... this.myDelegate = new AddDataDelegate(AddDataMethod); } public void AddDataMethod(String myString) { textbox1.AppendText(myString); } private void mySerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { SerialPort sp = (SerialPort)sender; string s= sp.ReadExisting(); textbox1.Invoke(this.myDelegate, new Object[] {s}); } 

    1 answer 1

    From my practice, DataReceived is not the best method for working with a serial port. I would recommend using Stream and read data from it asynchronously.

    Suppose a serial port sends data of the following form:

    • 1 byte message type
    • 2 bytes string length ( n )
    • n byte string
    • checksum of all bytes

    Create a data structure:

     struct Message { public byte Type; public string Text; } 

    Now our code looks something like this:

     port = new SerialPort(...); port.Open(); try { while (!ct.IsCancellationRequested) { Message message = await ReadWholeMessage(port.BaseStream, ct); DispatchMessage(message); // у нас тут Rx, но вы можете // послать event или обработать на месте } } catch (OperationCanceledException) { /*нас остановили, выходим */ } catch (ObjectDisposedException) { /* тоже остановили */ } catch (IOException ex) { // а вот это проблема, логируем и бросаем дальше } catch (InvalidDataException ex) { // девайс вернул не то, логируем, нужна повторная инициализация } 

    The most interesting is ReadWholeMessage . For our case, it looks something like this:

     async Task<Message> ReadWholeMessage(Stream stream, CancellationToken ct) { var headerBytes = await StreamHelpers.ReadAsync(stream, 3, ct); var type = headerBytes[0]; var stringSize = BitConverter.ToUInt16(headerBytes, 1); var stringBytes = await StreamHelpers.ReadAsync(stream, stringSize, ct); var crc = await StreamHelpers.ReadAsync(stream, 1, ct)[0]; // тут проверка crc var text = Encoding.ASCII.GetString(stringBytes); var message = new Message() { Type = type, Text = text }; return message; } 

    Well, ReadAsync :

     public static class StreamHelpers { static public async Task<byte[]> ReadAsync(Stream s, int nBytes, CancellationToken ct) { var buf = new byte[nBytes]; var readpos = 0; while (readpos < nBytes) { var actuallyRead = await s.ReadAsync(buf, readpos, nBytes - readpos, ct); if (actuallyRead == 0) throw new EndOfStreamException(); readpos += actuallyRead; } return buf; } } 

    Please note that the logic for receiving a message is almost linear, you do not need to remember the intermediate state of an unread message.

    • one
      @Michael: ct is the CancellationToken , it is needed to stop waiting for new messages (for example, at the end of the program). - VladD Nov.
    • one
      @Michael: Code for C # 5 +, which has async / await. - VladD Nov.
    • one
      @Michael: DispatchMessage should be a function written by you. For me, it looks like a subject.OnNext(message); because I use Rx to deliver messages to the person who needs it. You may have something else there. The role of this function is to send a message to someone who needs it. - VladD
    • one
      @Michael: Yes, the function only works when the whole message is read to the end. In principle, you can immediately withdraw. - VladD
    • one
      Thank you so much! A very interesting solution, which I will definitely use in the programs for the new version of .Net! - Michael Vaysman