Я уже писал про то, как занимался АСУ ТП, где вскользь затронул тему эмуляции. Но на днях попалась статья про эмуляцию — и я решил поделиться своим опытом.


Мы преимущественно занимались автоматизацией предприятий, связанных с дозированием материалов: производство бетона, асфальта и прочих в основном сыпучих веществ.

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

Мы не использовали никакие SCADA-системы. Всё писали самостоятельно. Это сильно упрощало систему, убирало лишние зависимости, удешевляло разработку и оставляло нам полный контроль над всеми процессами.

Скриншот запущенного приложения одного из предприятий по производству бетона. Работу всех этих устройств, их взаимодействие, правильность отображения всех индикаторов, отклик на действия пользователя, отказ оборудования, нестандартные ситуации (не закрылся затвор щебня) нужно тщательно оттестировать.
Скриншот запущенного приложения одного из предприятий по производству бетона. Работу всех этих устройств, их взаимодействие, правильность отображения всех индикаторов, отклик на действия пользователя, отказ оборудования, нестандартные ситуации (не закрылся затвор щебня) нужно тщательно оттестировать.
А это оно же на предприятии
А это оно же на предприятии

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

Поэтому для этого один из сотрудников когда-то смастерил пусконаладочный пульт (или просто пульт). Мы собирали по возможности все (или хотя бы пару) ящиков с контроллерами вместе, подключали их к компьютеру с одной стороны и к пульту — с другой. На пульте было несколько десятков переключателей, имитирующих работу датчиков. Весовой датчик (тензодатчик) подключали один и приходилось растягивать его руками. Так что больше 30 кг обычно в таком режиме не дозировали — попробуй растянуть руками сплошной кусок металла.

Те самые пульт и тензодатчик
Те самые пульт и тензодатчик

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

Приложение за приложением я постепенно пришёл к единому ядру, на которое нанизывал разные интерфейсы и прописывал всё то, что нельзя было вытащить в ядро. И однажды я пришёл к пониманию, что наши процессы уже можно тестировать иначе, программно.

Что мне нужно?

  • Имитировать работу Modbus RTU (команды должны оставаться неизменными, ответы должны соответствовать спецификации).

  • Имитировать работу модулей ввода-вывода и срабатывание (или нет) датчиков.

  • Имитировать работу весовых контроллеров (их настройку, изменение веса, смену состояния и пр.).

  • Повторить всё это для всех используемых версий устройств.

  • Всё должно быть подписано.

  • Иметь возможность сохранять и загружать эмулятор под конкретную программу (конкретный завод).

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

Так выглядит главное окно эмулятора
Так выглядит главное окно эмулятора

Приложения для нашей системы я писал на Delphi. Эмулятор, соответственно, тоже. На старте пользователь выбирает завод из ранее сохранённых или создаёт новый. Для нового просто выбираются нужные устройства из списка поддерживаемых, назначается сетевой адрес в RS-485 и название ("Цемент", "Вода" и т.д.). У модулей ввода-вывода также подписываются все входы и выходы. Так как нам надо было тестировать все возможные ситуации, к прочим кнопкам на форме я добавил кнопку выключения устройства для имитации обрыва связи. Вес у контроллеров менялся ползунком, так что больше не было ограничений по весу и можно было имитировать застревание материала.

На изображении слева весовой контроллер. Сверху индикатор работы, кнопка выключения, название. Ниже можно задать вручную параметры, поменять состояние устройства, изменить вес. Галочка переключает режим на автоматический. На изображении справа модуль ввода-вывода с ручным переключением входов и выходов Вверху также можно задать название и выключить устройство из сети.
На изображении слева весовой контроллер. Сверху индикатор работы, кнопка выключения, название. Ниже можно задать вручную параметры, поменять состояние устройства, изменить вес. Галочка переключает режим на автоматический. На изображении справа модуль ввода-вывода с ручным переключением входов и выходов Вверху также можно задать название и выключить устройство из сети.

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

  {$IF Emulate=True}
Emulator.Send(EmulData, FWaitingTime);
  {$ELSE}
ComPort.Write(OutDIOM, 11, @InDIOM, 8);
ComPort.Wait(FWaitingTime);
  {$IFEND}

// подобных мест было немного и все они были в одном модуле, отвечающим за связь

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

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

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

Сам эмулятор оказался настолько простой, что с ним разберётся любой, кто сможет прочитать базовые вещи из спецификации протокола Modbus RTU, а также документацию по используемым контроллерам. Добавление нового устройства в список поддерживаемых — вопрос всего нескольких часов.

Скриншоты сделаны с запущенного приложения, созданного более 10 лет назад под Windows XP (или 7). Хоть сейчас можно запустить приложение для предприятия в режиме тестирования и сэмулировать его работу.


Ещё пару слов о том, как была организована связь с устройствами в программе. За пределами программы всё стандартно: преобразователь USB <-> RS-485 и открытие COM-порта. А вот в программе у нас был реализован паттерн "команда" (хотя тогда я ещё ничего не знал о паттернах), параллельно существовало несколько очередей команд.

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

Индикатор работы очередей. Название устройства загорается красным, если происходит потеря связи с устройством.
Индикатор работы очередей. Название устройства загорается красным, если происходит потеря связи с устройством.

Количество очередей у разных программ было разное — это было нужно для локализации потенциальной проблемы. Но в целом было примерно следующее разделение:

  • чтение состояния контроллеров,

  • чтение состояний модулей ввода-вывода,

  • отправка команд устройствам.

За работу всех очередей отвечал непрерывно работающий поток. Очереди на чтение постоянно пополнялись, как только количество оставшихся команд доходило до порогового значения. У МДВВ читались входы (с датчиков) и выходы (для контроля и сравнения с ожиданием). У контроллеров читались текущие состояния. Непрерывно, даже если не дозировались, т.к. нам всегда нужно видеть, что на весах.

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

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

Вроде всё прекрасно, но приходилось биться за миллисекунды, особенно с большим количеством устройств. На одном из предприятий только МДВВ было 10 штук, а это порядка 20 мс на каждый запрос. 20 команд на все устройства "съедают" 400 мс времени. Добавляем к этому опрос контроллеров и отправку команд — и получаем ответ реакции на действия ПО примерно раз в секунду, что очень много. Это приходилось учитывать и в самой программе при отображении ошибок, чтобы не показывать лишнего. Всё возможное со стороны программы было сделано, а ПО устройств мы оптимизировать не в силах.

Всё работало почти идентично, независимо от того, работаем ли мы с портом или с эмулятором.


Надеюсь, вам понравилось небольшое погружение в процесс создания систем управления.