Demuter Ответов: 2

C# - опрос устройства по последовательному порту


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.


Что я уже пробовал:

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 00


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;
    }

2 Ответов

Рейтинг:
17

0x01AA

После всех обсуждений я бы попробовал что-то вроде приведенного ниже в разделе кода вашего приемника, чтобы ускорить связь.
Я надеюсь, что это поможет и что я не сделал слишком много ошибок.

// The following expects that serialPort.ReadTimeout is setup
// for "message timeout" which is usually much bigger than character 
// timeout after the first byte has been  received
//
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;
}


Maciej Los

5ed!

0x01AA

Спасибо Мацей

Demuter

Хороший день. Я хотел бы поблагодарить вас за вашу помощь с моей прошлой проблемой, если есть возможность, не могли бы вы взглянуть еще на один из моих вопросов. Он говорит на подобную тему ... Я не могу понять, в чем проблема ...

Рейтинг:
1

OriginalGriff

Начните с того, чтобы выяснить, где он проводит свое время: поперчите его Секундомер Класса (Система.Диагностика) | Microsoft Docs[^] экземпляры и протоколировать различные части операции в файл в конце. Затем сосредоточьтесь на самой длинной части и добавьте больше секундомеров, чтобы попытаться сузить область, где он тратит свое время.

Когда у вас есть это, вы можете начать смотреть на ускорение, но пока у вас нет реальных цифр, это все догадки. Когда вы знаете, вы можете сосредоточиться на "почему это медленно?" - но не до тех пор, пока у вас нет данных и ссылки, чтобы проверить свои улучшения.


Demuter

Этот код работает дольше всего. Я попытался уменьшить размер буфера, но время не изменилось ...

var tmp = new byte[0x2000];
                //var tmp = new byte[0x11];
                int dataLength = 0;
                try
                {
                    for (int i = 0; i < tmp.Length; i++)
                        tmp[dataLength++] = (byte)serialPort.ReadByte();
                }
                catch (Exception ex)
                {
                }

0x01AA

В чем ценность для это соответственно, последовательный порт.ReadTimeout?

Кроме того, я бы предложил регистрировать время для первого полученного байта. Это покажет вам, как долго другая часть должна отвечать на ваш запрос.

OriginalGriff

разумный Совет. :большой палец вверх:

0x01AA

Спасибо :счастливо:

Demuter

Значение для serialPort.ReadTimeout = 1000.
Я сделал вывод, как вы и предлагали - первый ответ за 0,033 секунды.

0x01AA

Итак, я бы сказал, что вы тратите/теряете 1000 мс, пока не остановите свой цикл ;)

Что я помню из Modbus, так это то, что конец передачи-это когда нет символа, полученного для "по крайней мере 3½ символьных времен молчания"

Demuter

Может быть, это поможет, при настройке модема, с помощью которого я пингую устройство, у меня есть: тайм-аут бездействия=30 секунд...

0x01AA

Но я говорю от serialPort.ReadTimeout Установите его, например, на 10, это означает тайм-аут 10 мс (около 20 символов молчат при 19'200), после чего ваш цикл останавливается.
[Edit] забыл упомянуть, что эти 10 мс должны быть установлены после получения первого символа.

Кстати. Я бы поймал TimeoutException явно потому, что это ожидаемое исключение. Все остальные исключения должны обрабатываться особым образом, поскольку они являются условием ошибки.

Demuter

Это работает! Большое спасибо.
Но не могли бы вы сказать мне, как обойти новую ошибку?.. =))

Теперь, когда я читаю несколько регистров в цикле, вероятно, у программы нет времени, чтобы получить ответ, и данные не поступают.
Возможно ли решить только увеличение времени? Или есть еще варианты?
Еще раз спасибо!

Я не видел вашего кода. Сейчас я попробую, а потом напишу, как идут дела.

0x01AA

:большой палец вверх:
Мы всегда рады вам. С нетерпением жду результатов вашего теста.

Demuter

Ваш код работает, но первый улов(TimeoutException ex) иногда выдает ошибку: время ожидания операции истекло.
И тогда возникает проблема, как в моем комментарии выше ...

0x01AA

Убедитесь, что в начале ReadTimeout большой, что-то вроде 1000 мс, как вы делали это раньше.

OriginalGriff

Так... это другой конец.
Ваш код сидит там, ожидая поступления данных - вы не можете ускорить это, это в значительной степени полностью зависит от устройства на другом конце связи.
Подумайте об этом: предполагая, что вы общаетесь по каналу связи 9600 бод, это в лучшем случае ~1000 символов в секунду, поэтому ваши 17 символов займут менее двух сотен секунд, чтобы быть физически принятыми и переданными в ваше приложение. Если этот код принимает лучшую часть в 100 раз больше, то задержка происходит с поставщиком данных, а не с получателем.

Вы не можете кодировать вокруг этого!

Garth J Lancaster

Я повторяю вывод OriginalGriff - я много работал с Modbus, и последнее конкретное устройство (например) было небольшой микропроцессорной платой, работающей по протоколу Modbus, но контролирующей множество датчиков-у него могли быть моменты, когда он просто не мог ответить на запрос показаний регистра

Demuter

Я бы не беспокоился, если бы не было нормальной скорости на tcp. Скорость отклика по tcp составляет около 0,15 сек. Поэтому я думаю, что это где-то проблема в моем коде...
Может быть, где-то еще в коде ...