CmpLog и Component Manager в Codesys. Быстрая отладка и диагностика технологического процесса.

Согласно мему у языка LD есть следующее преимущество.

А второй пункт, который часто упоминается теми, кто специализируется на LD — легкая и безболезненная диагностика. Из-за графического представления, при отладке самого технологического процесса, можно легко увидеть почему не сработал тот или иной механизм. Схема, как говорится, не соберется.

Состояние схемы при онлайн отладке

Такое представлении информации гораздо удобнее чем, в том же, ST.

Обычный вид ST кода в режиме онлайн

И я думаю те, кто думают, что ST код ужасен в отладке технологического процессе, не алгоритмов, а именно технологии, не так уж и заблуждаются, но что если предоставить информацию о работе различных участков кода в форме текста?

Логирование —  форма автоматической записи в хронологическом порядке операций в информационных технологиях, процесс записи информации о происходящих в рамках какого-либо процесса с некоторым объектом событиях. Именно логирование поможет понимать, что происходит с процессом и почему.

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

Лог сообщений

Навигация

Библиотека Component Manager

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

Для динамической работы с компонентами системы используется библиотека Component Manager.

Компонентом я буду называть организационную единицу нашей программы, которую необходимо зарегистрировать в системе с присвоением уникального ID. В этом контексте компонентом будет любой функциональный блок, информацию с которого мы хотим записывать в лог.

Из этой библиотеки нас интересует две функции — добавление компонента и удаление компонента.

CMAddComponent

Функция для добавления компонента имеет следующую сигнатуру

Сигнатура функции CMAddComponent

Возвращаемое значение: RTS_IEC_HANDLE

Входные параметры:

  • pszComponent: REFERENCE TO STRING — Имя компонента
  • udiCmpId: UDINT — Идентификатор компонента
  • udiVersion: UDINT — Версия компонента
  • pResult: POINTER TO RTS_IEC_RESULT — Результат выполнения функции

CMRemoveComponent

Функция, которая удаляет компонент из системы.

Сигнатуры функции CMRemoveComponent

Возвращаемое значение: RTS_IEC_RESULT

Входные параметры:

  • hComponent: RTS_IEC_HANDLE — ссылка на объект этого компонента в системе

CmpLog

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

LogAdd2

Функция, которая добавляет запись в лог.

Сигнатура функции LogAdd2

Возвращаемое значение: RTS_IEC_RESULT

Входные параметры:

  • hLogger: RTS_IEC_HANDLER — ссылка на логгер, но так как можно использовать стандартный, то есть возможность указать LogConstant.LOG_STD_LOGGER
  • udiCmpID: UDINT — Уникальный ID компонента
  • udiClassID: UDINT — Класс записи в логе.
  • udiErrorID: UDINT — уникальный идентификатор ошибки, которые представлены в библиотеки CmpErrors.Errors
  • udiInfoID: UDINT — Идентификатор информационного текста для включения текстов ошибок на нескольких языках
  • pszInfo: REFERENCE TO STRING — Сообщение, которое будет добавлено

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

Создание системы логирования

Для полноценного функционирования системы логирования требуется решить ряд задач:

  • Автоматизированное создание компонентов
  • Обертка для добавление логов различных классов

Автоматизированное создание компонентов

Данную задачу можно решить несколькими путями. Я рассматривал два варианта: 1)Заключается в создании отдельного объекта, который бы брал на себя данную задачу. Из минусов — надо было думать как правильно организовать данные, а в ходе работы не забывать добавлять новые элементы системы, чтобы он их регистрировал; 2)Написать один класс и унаследовать его.

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

Объект Component

Объект, который будет отвечать за регистрацию при запуске и удалении при остановке ППО.

Рассмотрим область объявления функционального блока

Область объявления для функционального блока Component

И вот тут начинается магия. Для начала стоит объяснить все прагма-выражения.

{attribute ‘reflection’} — используется для идентификации программных модулей, в которых некоторые переменные требуют специальной обработки и для этой цели помечены определенным атрибутом.

{attribute ‘instance-path’} — может быть применена к локальной переменной STRING и вызывает инициализацию этой локальной переменной STRING в последовательности с путем дерева устройств POU, к которому она принадлежит.

{attribute ‘noinit’} — применяется к переменным, которые не должны быть неявно инициализированы.

Есть обзорная статья про прагма-выражения

И весь этот карнавал для одной переменной str, которая будет хранить полный путь, до места инициализации функционального блока.

Переменные функционального блока:

  • str:STRING — путь инициализации ФБ
  • Component: RTS_IEC_HANDLE — экземпляр компонента
  • CreateComponentResult: RTS_IEC_RESULT — результат функции создания компонента
  • ComponentID: UDINT — Идентификатор компонента

Объект содержит два метода: Init и FB_Exit

Метод Init

Данный метод требуется для регистрации компонента. Предлагаю посмотреть на его сигнатуру и логику метода

Метод Init

Возвращаемое значение: RTS_IEC_RESULT — результат выполнения метода

Входные параметры:

  • ID: UDINT — Идентификатор компонента

На строках 1-4 вызывается функция создания компонента. В строке 5 копируется ID в экземпляр функционального блока.

На строках 6-9 происходит обработка ошибки дублирования компонента

Строка 10 возвращает результат работы метода.

Метод FB_Exit

Данный метод является стандартным для любого функционального блока в Codesys.

//FB_Exit должен быть объявлен явно. Если есть реализация, то
//метод вызывается перед тем, как контроллер удаляет код экземпляра функицонального блока
//(неявный вызов). Возвращаемое значение не оценивается.

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

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

Обертка для добавление логов различных классов

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

UPD. Лирическое отступление. Потому что нельзя быть наследников более одного функционального блока. Что не относится к реализации интерфейса.

UPD2. Можно для удобства просто инкапсулировать данный класс

Объект LoggerController

Посмотрим на область определения

Все что хранит данный ФБ — Результат выполнения функций

А вот методов у него 4 штуки

Метод _addLog

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

Сигнатура метода и его логика

Возвращаемое значение: STATUS — псевдоним для типа WORD. Нужен мне чтобы понимать что происходит. Метод завершен успешно или в работе, а может и ошибкой завершился.

Входные параметры:

  • sMessage: STRING — сообщение, которое будет добавлено в лог
  • udiComponent: UDINT — идентификатор компонента
  • udiClass: UDINT — класс сообщения
  • udiErrorID: UDINT — идентификатор ошибки

В строчках 1-6 происходит вызов функции из библиотеки, а 7-11 строчки отвечают за обработку результата

Метод Error

В этом и следующих методах конкретизируется класс сообщения — для удобства

Возвращаем результат выполнения метода. В теле метода вызываем приватный метод _addLog и прокидываем необходимые переменные. udiClass(3 строчка) — указан жестко.

Метод Info

Метод для добавления информации

Метод Warning

Данный метод добавляет предупреждения

Создание компонента

Следующим шагом стоит собрать весь этот конструктор воедино.

Для теста просто соберем какой-нибудь объект, который должен наследовать класс Component и внутрь стоит прокинуть LoggerController

Область объявления функционального блока

Далее нам требуется метод, который бы инициализировал все, что нам потребуется.

Метод UnitInit

Входные параметры:

  • udiID: UDINT — уникальный идентификатор, который надо задать ручками
  • ptrLogger: POINTER TO LoggerController — указатель на экземпляр класса LoggerController

В теле метода вызывается метод Init класс Controller. А указатель просто копируется.

Далее просто пишете необходимую логику и самые тонкие места обкладываете логированием.

Пример

Для примера будет просто инкрементировать переменную

Как только переменная станет строго больше 5, то в лог будет добавлена запись «Action1._i overflow» , что символизирует какой-либо сбой почему далее технологический процесс не может продолжаться. Ну и в процессе просто будут показаны промежуточные значения

Для начала стоит инициализировать все классы.

В области определении программы

Строчка 21 — экземпляр нашего компонента

Строчка 22 — создаем экземпляр класса для LoggerController

Строчка 23 — Результат выполнения метода

В строчках 1-5 содержится инициализация нашего компонента. udiID мы придумываем сами для наших компонентов. Желательно брать числа после 1000, чтоб не попасть на зарезервированные.

И после инициализации вызываем метод и вот что у нас в журнале событий.

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

И благодаря правильному описанию можно прочесть, что какой-то компонент, который находится в Device->Application->PLC_PRG и носит имя U1 в методе Action1 для переменной _i выдал переполнение. Если уж очень хочется, то можно добавить и строчку кода.

Кстати, прошу заметить, что цикл 20 мс, как и в настройках программы.

ИТОГ

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

Спасибо за внимание! Исходники в ТГ канале.

Почта для сотрудничества: info@engcore.ru

Телеграм канал с новостями : https://t.me/wtfcontrolsengineer

Место для ваших вопросов и общения: https://t.me/wtfplc_topics

Ответить