2
votes

Faced a problem. I need to poll the device by serial port using the Modbus protocol.

But polling the device takes a long time - about 2 seconds.

True, my program still additionally constantly polls devices in a separate thread, but I wanted it to be faster.

Maybe someone can help me how to optimize my code. I would be very grateful.

public override RcResult ExecuteModBus(RcModBusDevice device, byte[] request, out byte[] answer)
    {
        answer = null;
        var result = new RcResult();
        OnLogMessage?.Invoke(this, "ExecuteModBus LOCK?");
        lock (communication)
        {
            OnLogMessage?.Invoke(this, "ExecuteModBus LOCK!");
            var dt = DateTime.Now;
            if (device != null)
            {
                serialPort.WriteTimeout = device.SendTimeout;
                serialPort.ReadTimeout = device.ReceiveTimeout;
            }
            serialPort.DiscardOutBuffer();
            serialPort.DiscardInBuffer();

            OnLogMessage?.Invoke(this, "REQUEST->:" + Utils.BytesToHex(request));
            try
            {
                serialPort.Write(request, 0, request.Length);
            }
            catch(Exception ex)
            {
                result.ErrorCode = 1;
                result.ErrorText = ex.Message;
                return result;
            }
            var tmp = new byte[0x2000];
            int dataLength = 0;
            try
            {
                for (int i = 0; i < tmp.Length; i++)
                    tmp[dataLength++] = (byte)serialPort.ReadByte();
            }
            catch (Exception ex)
            {
            }

            var crc = Utils.GetCRC(tmp, 0, dataLength - 2);
            if (crc[0] != tmp[dataLength - 2] || crc[1] != tmp[dataLength - 1])
            {
                result.ErrorCode = 1;
                result.ErrorText = "Bad CRC";
            }
            answer = new byte[dataLength];
            Array.Copy(tmp, 0, answer, 0, dataLength);
            OnLogMessage?.Invoke(this, "ANSWER<-:" + Utils.BytesToHex(answer));
            if (device != null)
                SaveToLog(DbID, device.DbID, dt, Utils.BytesToHex(request), DateTime.Now, Utils.BytesToHex(answer));
            if (dataLength == 0)
                result.ErrorCode = 1;
        }
        OnLogMessage?.Invoke(this, "ExecuteModBus UNLOCK");
        return result;
    }
1
It looks like you are always attempting to receive 0x2000 bytes and relying on the serial timeout to detect the end? If so you can speed this up by calculating the expected packet size and only reading that much data (e.g.). You did not mention the baud rate or amount of data being read so its difficult to say if 2s is, in fact, a long time.Brits
@Brits I tried to change the size of the package to a smaller number of bytes, but that did not help. Baud rate = 19200.This is what my program displays: Request: 07.04.2020 12:51.48.038 01 03 04 00 00 03 04 FB Answer: 07.04.2020 12:51.49.949 (1,911 seconds) 01 03 06 51 47 07 12 20 84 20 29 00Aleksey Malashenkov
"to a smaller number of bytes"; what did you set this to? For that to work the buffer len needs to be equal to the number of bytes expected; that means the for (int i = 0; i < tmp.Length; i++) loop will exit as soon as the data is received. Try setting the buffer size to 11 (3 byte header; the 3rd byte indicating the number of bytes following - 6 in this case + 2 bytes for the CRC) - this will only work for the specific example query in your comment!. Note that this is a guess; at 19200, with an 11 byte payload, I would expect a faster response but some devices are quite slow.Brits

1 Answers

2
votes

I found the answer in another forum. The error was a read timeout for the port.

var tmp = new byte[0x2000];
int dataLength = 0;
try
{
   while(true)
   {
      byte receiveByte= (byte)serialPort.ReadByte();

      // We received one byte. Check buffer size
      if (dataLength >= tmp.Length) 
      {
         // Buffer to small
         result.ErrorCode = 1; // Appropriate ErrorCode
         result.ErrorText = "Buffer Overrun";
         return result;
      }

      // Add it to receive buffer
      tmp[dataLength++] = receiveByte;

      // Set character Timeout after we received the first byte of the answer
      if (dataLength == 1)
      {
         // Set new character timeout according to Modbus spec.
         // At least 3.5 characters silence but here we take about 
         // 5 times more chars to be on the safe side
         //
         //                   10Bit/Byte     mS      ca. 5x3.5
         serialPort.ReadTimeout= (10    *   1000   *   18)      /   serialPort.Baudrate; 
      }

      // Don't eat CPU too aggressive (has to be proofed whether it really helps)
      Thread.Sleep(0);
   }
}
catch (TimeoutException ex)
{
   // Expected exception. No more character received in the specified
   // character timeout period
}
catch (Exception ex)
{
   // Other than (TimeoutException is an error condition
   result.ErrorCode = 1; // Appropriate ErrorCode
   result.ErrorText = ex.Message;
   return result;
}