Wait for response from the Serial Port and then send next data

I am reading data in bytes from .bin file and split the whole byte data into 16-16 bytes frames, so I want to 16 bytes frame one by one and wait until the first frame finished its cycle.

Callback method of SerialPort class:

private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{

    // Read data from serial port:
    byte[] buffer = new byte[serialPort.BytesToRead];
    serialPort.Read(buffer, 0, buffer.Length);
    StringBuilder sb = new StringBuilder();
    List<string> response = new List<string>();
    for (int i = 0; i < buffer.Length; i++)
    {
        string currentByte = string.Format("{0:X2}", buffer[i]);
        response.Add(currentByte);

        sb.AppendFormat("{0:X2}", buffer[i]);
    }

    string responesCode = response[1].ToString();
    if (responesCode == "44")
    {
        // Wait until the first response is not received
        foreach (var packet in packetList.Skip(1))
        {
            // This method which sending the the data
            this.ReadByteDataFromFile(packet);
        }
    }
}

FdBrowseFile_Click button click:

private void FdBrowseFile_Click(object sender, RoutedEventArgs e)
{
    Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
    Nullable<bool> result = dlg.ShowDialog();
    if (result == true)
    {
        byte[] fileBytes = File.ReadAllBytes(filename);

        foreach (byte[] copySlice in fileBytes.Slices(16))
        {
            var splitedByteArray = copySlice;
            if (splitedByteArray.Length != 16)
            {
                byte[] padd = new byte[16];
                var startAt = 0;
                Array.Copy(splitedByteArray, 0, padd, startAt, splitedByteArray.Length);
                packetList.Add(padd);
            }
            else
            {
                packetList.Add(splitedByteArray);
            }
        }
        ReadByteDataFromFile(packetList[0]);
    }
}

ReadByteDataFromFile method:

public void ReadByteDataFromFile(byte[] packet) {
 try {
  byte[] mBuffer = new byte[24];
  byte[] payload = new byte[16];
  int i = 0;
  foreach(var bytes in packet) {
   payload[i++] = bytes;
  }
  CheckSumHelper checkSumHelper = new CheckSumHelper();
  var ckSum = checkSumHelper.GetCheckSum(payload);
  mBuffer[0] = 0x02;
  mBuffer[1] = 0x10;
  mBuffer[2] = CheckSumHelper.GetBytesFromDecimal(packet[0]);
  mBuffer[3] = CheckSumHelper.GetBytesFromDecimal(packet[1]);
  mBuffer[4] = CheckSumHelper.GetBytesFromDecimal(packet[2]);
  mBuffer[5] = CheckSumHelper.GetBytesFromDecimal(packet[3]);
  mBuffer[6] = CheckSumHelper.GetBytesFromDecimal(packet[4]);
  mBuffer[7] = CheckSumHelper.GetBytesFromDecimal(packet[5]);
  mBuffer[8] = CheckSumHelper.GetBytesFromDecimal(packet[6]);
  mBuffer[9] = CheckSumHelper.GetBytesFromDecimal(packet[7]);
  mBuffer[10] = CheckSumHelper.GetBytesFromDecimal(packet[8]);
  mBuffer[11] = CheckSumHelper.GetBytesFromDecimal(packet[9]);
  mBuffer[12] = CheckSumHelper.GetBytesFromDecimal(packet[10]);
  mBuffer[13] = CheckSumHelper.GetBytesFromDecimal(packet[11]);
  mBuffer[14] = CheckSumHelper.GetBytesFromDecimal(packet[12]);
  mBuffer[15] = CheckSumHelper.GetBytesFromDecimal(packet[13]);
  mBuffer[16] = CheckSumHelper.GetBytesFromDecimal(packet[14]);
  mBuffer[17] = CheckSumHelper.GetBytesFromDecimal(packet[15]);
  mBuffer[18] = 0x17;
  mBuffer[19] = 0x00;
  mBuffer[20] = 0x00;
  mBuffer[21] = 0x00;
  mBuffer[22] = Convert.ToByte(int.Parse(ckSum, System.Globalization.NumberStyles.HexNumber));
  mBuffer[23] = 0x03;
  serialPort.Write(mBuffer, 0, mBuffer.Length);
 } catch (Exception ex) {
  ExceptionHandler exceptionHandler = new ExceptionHandler();
  exceptionHandler.HandleException(ex);
 }
}

How I can add a delay for ReadByteDataFromFile method?


Solution 1:

What you need is a way to block the execution of some code, until something else has happened, or - how to get things running on two threads synchronously. .NET has quite a few classes in the System.Threading namespace for synchronization. We'll use AutoResetEvent here.

Think of AutoResetEvent as a turnstile.

turnstile

You can't move forward if the the person on the other side stops. When you move forward, you call Wait - and it blocks you from moving, until someone calls Set on it.

Now if we apply that to our problem: We need to stop sending data until we get an acceptable response. So call Wait when sending data, and let the response handling code call Set to let it move forward.

Here's an example which simulates a modem. You send some AT commands, it responds, but the responses always end with \r\n.

var port = new SerialPort("COM2");
port.Open();

var mre = new AutoResetEvent(false);
var buffer = new StringBuilder();

port.DataReceived += (s, e) =>
{
    buffer.Append(port.ReadExisting());
    if (buffer.ToString().IndexOf("\r\n") >= 0)
    {
        Console.WriteLine("Got response: {0}", buffer);

        mre.Set(); //allow loop to continue
        buffer.Clear();
    }
};


var commandsToSend = new string[] { "AT", "AT", "AT+CSQ" };
var responseTimeout = TimeSpan.FromSeconds(10);

foreach (var command in commandsToSend)
{
    try
    {
        Console.WriteLine("Write '{0}' to {1}", command, port.PortName);
        port.WriteLine(command);

        Console.WriteLine("Waiting for response...");

        //this is where we block
        if (!mre.WaitOne(responseTimeout))
        {
            Console.WriteLine("Did not receive response");
            //do something
        }
    }
    catch (TimeoutException)
    {
        Console.WriteLine("Write took longer than expected");
    }
    catch
    {
        Console.WriteLine("Failed to write to port");
    }
}

Console.ReadLine();

Sample output when tested through virtual serial port: (I just reply with OK<CR><LF>)

Write 'AT' to COM2
Waiting for response...
Got response: OK

Write 'AT' to COM2
Waiting for response...
Got response: OK

Write 'AT+CSQ' to COM2
Waiting for response...
Did not receive response

Solution 2:

Wait in a loop for a full response after you write a first frame.

// Set read timeout to value recommended in the communication protocol specification 
// so serial port operations don't stuck.
_port.WriteTimeout = 200;
_port.ReadTimeout = 200;

public void OnClick()
{
    // Write first frame.
    _port.Write(...);
    // Now wait for the full response.

    // Expected response length. Look for the constant value from the device communication 
    // protocol specification or extract from the response header (first response bytes) if  
    // there is any specified in the protocol.
    int count = ...; 
    var buffer = new byte[count];
    var offset = 0;
    while (count > 0)
    {
        var readCount = _port.Read(buffer, offset, count);                 
        offset += readCount;
        count -= readCount;
    }
    // Now buffer contains full response or TimeoutException instance is thrown by SerialPort.
    // Check response status code and write other frames.
}

In order to not block UI thread you most probably still need to utilize synchronous API and Task.Run(). See C# await event and timeout in serial port communication discussion on StackOverflow.

For more information check Top 5 SerialPort Tips article by Kim Hamilton.