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

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

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

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

Навигация
- Библиотека ComponentManager
- Функция CMAddComponent
- Функция CMRemoveComponent
- Библиотека CmpLog
- Функция LogAdd2
- Создание системы логирования
- Итог
Библиотека Component Manager
Для начала адекватной работы с логированием, чтобы было понимание, а какой фб нашей системы пишет в лог, стоит зарегистрировать каждый весомы фб как компонент системы.
Для динамической работы с компонентами системы используется библиотека Component Manager.
Компонентом я буду называть организационную единицу нашей программы, которую необходимо зарегистрировать в системе с присвоением уникального ID. В этом контексте компонентом будет любой функциональный блок, информацию с которого мы хотим записывать в лог.
Из этой библиотеки нас интересует две функции — добавление компонента и удаление компонента.
CMAddComponent
Функция для добавления компонента имеет следующую сигнатуру

Возвращаемое значение: RTS_IEC_HANDLE
Входные параметры:
- pszComponent: REFERENCE TO STRING — Имя компонента
- udiCmpId: UDINT — Идентификатор компонента
- udiVersion: UDINT — Версия компонента
- pResult: POINTER TO RTS_IEC_RESULT — Результат выполнения функции
CMRemoveComponent
Функция, которая удаляет компонент из системы.

Возвращаемое значение: RTS_IEC_RESULT
Входные параметры:
- hComponent: RTS_IEC_HANDLE — ссылка на объект этого компонента в системе
CmpLog
Вторая библиотека, которая и осуществляет сам процесс логирования. Имеет в арсенале все необходимые функции для создания своего собственного лога, но мы пока обойдемся стандартным, так что интересна одна функция
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
Объект, который будет отвечать за регистрацию при запуске и удалении при остановке ППО.
Рассмотрим область объявления функционального блока

И вот тут начинается магия. Для начала стоит объяснить все прагма-выражения.
{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
Данный метод требуется для регистрации компонента. Предлагаю посмотреть на его сигнатуру и логику метода

Возвращаемое значение: 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
