Очевидная уязвимость IO.Ports.SerialPort в C# или возможная аппаратная уязвимость

Пожалуйста, простите меня, так как это будет довольно длинный пост. В настоящее время я использую класс SerialPort на C# для написания приложения для связи с устройством под названием Fluke 5500A. В прошлом у меня было много проблем, поскольку количество времени, которое требуется устройству для выдачи команды и возврата того, что оно выводит, в лучшем случае непредсказуемо. Вчера я задал вопрос здесь: System.Timers.Timer Usage Ответ на вопрос замечательный и большую часть времени, кажется, работает отлично. Например, мой класс, который я использую для подключения к SerialPort, теперь выглядит так:

public class SerialPortConnection
{
    private SerialPort serialPort;
    private string ping;
    double failOut;
    bool isReceiving;

    public SerialPortConnection(string comPort = "Com1", int baud = 9600, System.IO.Ports.Parity parity = System.IO.Ports.Parity.None, int dataBits = 8, System.IO.Ports.StopBits stopBits = System.IO.Ports.StopBits.One, string ping = "*IDN?", double failOut = 2)
    {
        this.ping = ping;
        this.failOut = failOut * 1000;

        try
        {
            serialPort = new SerialPort(comPort, baud, parity, dataBits, stopBits);
            serialPort.NewLine = ">";
            serialPort.ReadTimeout = 1000;
        }
        catch (Exception e)
        {
            serialPort = null;
        }
    }

    //Open Serial Connection. Returns False If Unable To Open.
    public bool OpenSerialConnection()
    {
        //Opens Initial Connection:
        try
        {
            serialPort.Open();
            serialPort.Write("REMOTE\r");
        }
        catch (Exception e)
        {
            return false;
        }

        serialPort.Write(ping + "\r");
        var testReceived = "";

        try
        {
            testReceived += serialPort.ReadLine();
            return true;
        }
        catch
        {
            return false;
        }
    }

    public string WriteSerialConnection(string SerialCommand)
    {
        serialPort.Write(String.Format(SerialCommand + "\r"));
        var received = "";

        try
        {
            received += serialPort.ReadLine();
            return received;
        }
        catch
        {
            received = "Error: No Data Received From Device";
            return received;
        }
    }

    public bool CloseSerialConnection()
    {
        try
        {
            serialPort.Write("LOCAL\r");
            serialPort.Close();
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }
}

Как видите, когда я открываю соединение с Com1, я проверяю соединение, записывая команду *IDN? в SerialPort. Возврат этой команды выглядит так:

FLUKE,5500A,8030005,2.61+1.3+2.0+*
66>

В классе я установил ">" в качестве свойства NewLine, так что SerialPort.ReadLine() не завершится, пока не найдет этот токен. У меня ни разу не было случая, чтобы сам класс выдавал исключение, но во время отладки я заметил, что иногда testReceived не может правильно поймать возвращаемые данные, несмотря на то, что исключения не выдаются, и код продолжает выполняться правильно, и вместо этого received будет поймать возвращаемую строку:

FLUKE,5500A,8030005,2.61+1.3+2.0+*
66>

всякий раз, когда я передаю свою первую команду через SerialPort.Write(); Важно знать, что команды могут выполняться без полного возврата этих данных. Меня беспокоит то, что начальный ReadLine(), кажется, иногда пропускает это, не улавливая весь возврат. Я думаю, что в устройстве, с которым я общаюсь, есть неотъемлемая ошибка, вызывающая это, но я бы предпочел быть полностью уверенным, прежде чем продолжать.

Мой порядок команд выглядит так:

Сначала я отправляю команду при запуске:

REMOTE

Это отключает взаимодействие с ручным интерфейсом устройства и позволяет мне отправлять команды через последовательный порт.

Затем я выдаю *IDN?, в данном случае, чтобы проверить, что устройство подключено:

*IDN?

Если ничего не возвращается, приложение настроено на отображение ошибки в окне сообщения, а затем FailFast. Если все пойдет хорошо, команду можно отправить так:

STBY
OUT 30MV,60HZ
OPER

Единственная команда, представленная здесь вручную, это OUT 30,MV,60HZ. STBY и OPER устанавливаются в app.config, поскольку они только добавляют ненужный шаг к использованию приложения. Команда STBY переводит машину в режим ожидания из соображений безопасности. Команда OPER переводит его в рабочий режим, и устройство начинает работать с заданными параметрами.

Затем приложение ожидает, пока технический специалист введет результат в текстовое поле и отправит его. Содержание этих результатов не имеет особого значения, но при нажатии кнопки результата машина снова переходит в режим ожидания:

STBY

Наконец, при закрытии приложения отправляются еще две команды:

*RST
LOCAL

Сначала *RST сбрасывает машину, чтобы убедиться, что она находится в том же состоянии, что и при включении (т.е. она не работает и параметры не установлены). Затем LOCAL повторно включает ручной интерфейс для взаимодействия с пользователем и отключает доступ через последовательный порт до тех пор, пока REMOTE не будет выдано еще раз.

Как видите, команда выдается после *IDN? и перед первой отправляемой вручную командой (в данном случае мы предполагаем, что это команда OUT 30MV,60HZ). Проблема в том, что иногда я получаю вывод *IDN всякий раз, когда я проверяю вывод OUT 30MV,60HZ, но я не вижу никаких проблем в моем коде или процедуре, которую я использую для работы машины. Есть ли причина, по которой это может происходить?

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

РЕДАКТИРОВАТЬ:

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

public string SubmitCommand()
    {
        if (_command_Input != "No further commands to input.")
        {
            string received;
            serialPort.WriteSerialConnection("STBY");
            received = serialPort.WriteSerialConnection(_command_Input);
            serialPort.WriteSerialConnection("OPER");

            //Controls Enabled:
            _input_IsEnabled = false;
            _user_Input_IsEnabled = true;
            _results_Input_IsEnabled = false;
            RaisePropertyChanged("Input_IsEnabled");
            RaisePropertyChanged("User_Input_IsEnabled");
            RaisePropertyChanged("Results_Input_IsEnabled");

            return received;
        }
        else
            return "";
    }

полученный затем обрабатывается следующим образом:

public bool SetOutput()
    {
        string inter1 = SubmitCommand();

        try
        {

            string[] lines = inter1.Split(Environment.NewLine.ToCharArray()).ToArray();
            _results_Changed = lines[2];
            RaisePropertyChanged("Results_Changed");
        }
        catch
        {
            _results_Changed = inter1;
            RaisePropertyChanged("Results_Changed");
        }
        return true;
    }

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


person DanteTheEgregore    schedule 16.07.2013    source источник
comment
Как вы можете быть уверены, что дефект не существует на устройстве?   -  person Brad Rem    schedule 16.07.2013
comment
@BradRem Вот почему я спрашиваю. Я не уверен, является ли это недостатком устройства, но это гораздо сложнее (если не невозможно) устранить, чем программную ошибку. Я хотел бы убедиться, что это не ошибка где-то в моем коде или в классе SerialPort, прежде чем я продолжу попытки устранения неполадок.   -  person DanteTheEgregore    schedule 16.07.2013
comment
По сравнению с Win32 Communications API, который он использует, IO.Ports.SerialPort ужасно сломан. У меня самого были проблемы, и я видел отчеты других о различных проблемах, которые исчезли после перехода на прямое использование Win32.   -  person Ben Voigt    schedule 16.07.2013
comment
@BenVoigt Значит, есть большая вероятность, что это прямая ошибка в IO.Ports.SerialPort, а не аппаратная ошибка?   -  person DanteTheEgregore    schedule 16.07.2013
comment
Кстати, ваш код совсем не надежный. Когда ReadLine истечет время ожидания, не похоже, что у вас есть какая-либо логика восстановления для синхронизации.   -  person Ben Voigt    schedule 16.07.2013
comment
@BradRem В настоящее время я также вынужден предположить, что это не аппаратный недостаток, поскольку это все еще не объясняет, почему приложение продолжает выполняться, как если бы ">" было найдено ReadLine(), а возвращаемая строка не была правильно перехвачена ReadLine().   -  person DanteTheEgregore    schedule 16.07.2013
comment
@Zach: Дело не столько в том, что IO.Ports.SerialPort глючит, сколько в плохом дизайне. Это заставляет вас использовать глупый подход, вызывая несущественные API-интерфейсы конфигурации во время открытия, используя потоки пула потоков для получения событий, а затем синхронизируя данные между потоками, не допуская межсимвольных тайм-аутов. Что очень затрудняет правильное использование.   -  person Ben Voigt    schedule 16.07.2013
comment
Кстати, как вы думаете, что делает catch (Exception e) { throw e; }? Если вы не знакомы с обработкой исключений, этот блок совершенно бесполезен.   -  person Ben Voigt    schedule 16.07.2013
comment
@BenVoigt Я понимаю, что это бесполезно. Это остаток кода, который я использовал раньше. Я не удалял его, так как был сосредоточен на попытке решить проблему, с которой столкнулся.   -  person DanteTheEgregore    schedule 16.07.2013
comment
@BenVoigt Первоначально я писал исключения для консоли, потому что у меня было множество других проблем. Это просто то, что нужно удалить.   -  person DanteTheEgregore    schedule 16.07.2013
comment
@ Зак Смит, я думаю, ты неправильно истолковываешь то, что говорит Бен. Когда вы вызываете ReadLine() и он, по-видимому, пропускает вывод, который, по вашему мнению, должен был быть там, вполне вероятно, что он никогда не встречал ваш символ NewLine, и все эти данные все еще находятся в буфере. Если ваша NewLine, наконец, вступит в поток, при следующем вызове ReadLine() вы получите вывод ранее выпущенных команд, и теперь вы не синхронизированы. Либо проверьте, что ReadLine() возвращает что-то полезное, либо задержитесь, пока не убедитесь, что буфер заполнен.   -  person B L    schedule 16.07.2013
comment
@glace: Он говорит, что никогда не получает TimeoutException. Хотя мне интересно, что происходит, если в буфере приема есть > или просто передается с устройства, когда его программа запускается.   -  person Ben Voigt    schedule 16.07.2013
comment
Моя ошибка, я пропустил строку, где ReadTimeout был установлен не на 0.   -  person B L    schedule 16.07.2013
comment
@glace ReadLine() всегда должен проверять ">" или ждать, пока не истечет время чтения. Если время чтения истекло, метод возвращает False и FailFast включен. Я бы не столкнулся с этой ошибкой, если бы она не встречала ">".   -  person DanteTheEgregore    schedule 16.07.2013
comment
Вы пробовали использовать терминальную программу и просто набирать команды, чтобы посмотреть, что произойдет?   -  person dbasnett    schedule 16.07.2013
comment
@dbasnett Я пробовал запускать команды в терминале, и все работает нормально. Возврат выглядит точно так же, как я написал в своем вопросе. 66> — это приглашение, показывающее, что запись в буфер завершена и устройство готово принять другую команду.   -  person DanteTheEgregore    schedule 16.07.2013
comment
Вы получаете командную строку после каждой команды? Какую версию .Net вы используете? Мой опыт работы с SerialPort до версии 3.5 был плохим. С тех пор у меня не было проблем, насколько я могу вспомнить, особенно начиная с 4.0.   -  person dbasnett    schedule 16.07.2013
comment
@dbasnett ">" получается после каждой команды, которую я ввожу. Насколько мне известно, это постоянно (могут быть довольно неясные команды, которые я не использую, но они не относятся к делу). В настоящее время я использую WPF и .Net 4.0.   -  person DanteTheEgregore    schedule 16.07.2013
comment
Я начну сбрасывать всю возможную информацию из класса в лог-файл. Я добавлю это к вопросу, поскольку это может дать некоторые разъяснения. Спасибо всем за ваше время.   -  person DanteTheEgregore    schedule 16.07.2013
comment
При работе с серийным оборудованием нет гарантии, что вы всегда получите нужный ответ, каждое магнитное поле может изменить и повредить связь. при этом - если проблема не в потоке кода - она, вероятно, связана с синхронизацией между вызовами. Чтобы действительно ответить на вопрос, вам нужно описать, когда он не работает - иногда это больше относится к проблеме, связанной с оборудованием.   -  person G.Y    schedule 16.07.2013


Ответы (1)


Вы усложнили диагностику: ответ, который вам не нравится, выглядит в точности как тот, который вам нравится.

В общем, вам нужно убедиться, что ваша программа синхронизирована с устройством. Возможный режим сбоя — это когда у драйвера все еще есть непрочитанные данные в приемном буфере из предыдущего соединения. Устаревшие данные также могут существовать в буфере передачи устройства. Когда вы начнете резервное копирование, вы прочитаете эти устаревшие данные и решите, что это был ответ на вашу команду. Это не так. Теперь вы будете постоянно рассинхронизированы, всегда читая устаревшие данные, которые были ответом на предыдущую команду.

Также довольно странно, что это работает без рукопожатия, устройство обычно делает обращает на это внимание.

Чтобы избежать несчастных случаев, инициализируйте свою программу следующим образом:

  • Вызовите метод Open(), чтобы открыть порт
  • Установите для свойств RtsEnable и DtrEnable значение true, чтобы устройство всегда видело хороший сигнал, позволяющий ему передавать данные
  • Спящий режим примерно на 100 мс, чтобы позволить устройству отправить любые данные, которые оно все еще буферизировало из предыдущего соединения, но не смогло отправить из-за того, что рукопожатие было отключено.
  • Вызовите DiscardInBuffer(), чтобы отбросить все устаревшие байты ответа.

Теперь у вас есть разумная гарантия того, что вы будете синхронизированы.

person Hans Passant    schedule 16.07.2013
comment
Подключение к устройству не требует рукопожатия. Я не знаю причин этого, но я знаю, что устройство довольно старое, и в руководстве программиста, прилагаемом к устройству, не упоминается подтверждение связи для RS-232. Я заметил, что он требует установления связи на IEEE-488, но он работает по-другому, и его использование не вариант, поскольку компания, в которой я работаю, предпочла бы не использовать его. - person DanteTheEgregore; 16.07.2013
comment
Однако я мало знаю о том, как это должно работать, поскольку я никогда раньше не работал с SerialPorts. Я попробую то, что вы упомянули. - person DanteTheEgregore; 16.07.2013