doc:jroboplc:modules:arctrm

This is an old revision of the document!


Модуль Arctrm

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

Сам модуль не реализует протокол обмена с оборудованием. Доступ к данным выполняется через Ref: каждый Sensor ссылается на тег вида <deviceName>:Pdv<num>.T<sensorIndex>, а ArctrmDevice контролирует состояние устройства через <deviceName>:SYSTEM.ErrorFlag. По коду видно, что конфигурация устройств берется из секции promauto.termo5; тип promauto.termo5 создается периферийным плагином как PaTermo5Module.

Описание плагина в ArctrmPlugin.getPluginDescription() - termo value archiver.

Модуль состоит из жизненного цикла JRoboPLC, объектной модели устройств и сервиса доступа к базе данных.

Класс Ответственность
ArctrmPlugin Регистрирует плагин с именем arctrm, создает ArctrmModule и вызывает загрузку конфигурации.
ArctrmModule Управляет загрузкой, подготовкой, инициализацией, циклом выполнения, записью архива, записью событий, connected-тегом, остановом и reload.
ArctrmDataService Инкапсулирует работу с БД: поиск модуля базы, проверку Firebird, загрузку и выполнение скрипта, синхронизацию PODVES, запись TEMPER и EVENTLOG, удаление старых архивных строк и учет текущего размера архива.
ArctrmDevice Описывает одно настроенное устройство, содержит список Podves, читает SYSTEM.ErrorFlag и пишет события изменения статуса устройства.
Podves Описывает один подвес устройства, содержит список Sensor, синхронизирует запись в PODVES, сохраняет одну строку архива со всеми своими датчиками.
Sensor Ссылается на температурный тег, читает целочисленное значение и заменяет отсутствующие или выходящие за диапазон значения специальными кодами.
Duration Разбирает строковый период и выравнивает дату-время на границу периода.
Constants Содержит коды событий и специальные значения датчиков.
Defaults Содержит значения конфигурации по умолчанию.
ArctrmException Проверяемое исключение для ошибок подготовки и инициализации Arctrm.

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

Иерархия объектов во время работы:

<code text> ArctrmModule └── ArctrmDevice

  └── Podves
      └── Sensor

<code>

ArctrmModule хранит все устройства в списке devices. Список создается в конструкторе и заполняется в loadModule() вызовом ArctrmDevice.load(…).

ArctrmDevice создается по одной записи из карты promauto.termo5. Ключ записи становится именем устройства и используется как имя модуля, из которого читаются теги. Устройство создает:

- список podvess; - ссылку Ref на тег SYSTEM.ErrorFlag; - состояние lastStatus, по которому определяется необходимость записи события.

Podves создается по записи внутри конфигурации устройства. Номер подвеса получается из ключа конфигурации через entry.getKey().substring(1) и Integer.parseInt(…). Например, ключ p3 дает номер 3. Имя подвеса формируется как <deviceName>.<podvesNum>, а значение конфигурации сохраняется как описание descr.

Sensor создается для каждого индекса от 0 до sensorCount - 1. Имя тега строится так:

<code java> String tagname = String.format(“Pdv%d.T%d”, num, i); <code>

Ссылка датчика указывает на <deviceName>:Pdv<num>.T<i>.

Связь с базой данных строится через PODVES.ID: после syncPodves(…) объект Podves сохраняет идентификатор в поле id, а строки TEMPER ссылаются на него через PODVES_ID.

ArctrmModule.loadModule(conf) читает параметры модуля, проверяет базовую корректность значений и создает тег connected.

Параметр Куда сохраняется Проверка
database svc.databaseModuleName Проверяется позже в ArctrmDataService.prepare(…).
schema svc.schema Специальной проверки в loadModule() нет.
size svc.maxRecordCount Должен быть больше 0.
period periodMs Разбирается через Duration.parseMillis(…); результат не должен быть 0.
sensorCount sensorCount Должен быть больше 0.
promauto.termo5 devices Передается в ArctrmDevice.load(…); ошибки конфигурации приводят к false.

Общие параметры enable, debug.logging, func.tags, tag.values, tag.flags и флаги модуля обрабатываются в AbstractModule.load(…). В самом Arctrm явно используется enable: при выключенном модуле getInfo() возвращает disabled, а унаследованный жизненный цикл пропускает подготовку и выполнение.

prepareModule() вызывает svc.prepare(sensorCount). Сервис:

1. Ищет модуль базы данных по имени database. 2. Проверяет, что найденный модуль является FirebirdDatabaseModule. 3. Загружает ресурс dbscr/dbscr.arctrm.yml. 4. Формирует имена таблиц EVENTLOG, PODVES, TEMPER с учетом схемы. 5. Генерирует SQL вставки в TEMPER с колонками T0..T<n-1>.

После подготовки сервиса модуль вызывает prepare() у каждого устройства. Устройства подготавливают ссылки на SYSTEM.ErrorFlag, а подвесы - ссылки всех датчиков.

В конце подготовки выставляется needInit = true, а lastDt сбрасывается в null.

Инициализация выполняется в начале рабочего цикла при наличии подключения к базе и только если needInit == true.

init() выполняет следующие шаги:

1. svc.init(sensorCount) выполняет скрипт arctrm.init1, добавляет отсутствующие колонки датчиков в TEMPER, загружает recordCount и сбрасывает счетчики неподтвержденных операций. 2. Каждое устройство вызывает device.init(svc), а каждый подвес синхронизирует себя с таблицей PODVES. 3. lastDt загружается запросом последней строки TEMPER по убыванию ID. 4. В EVENTLOG пишется событие EVENT_ARCTRM_INIT с пустым сообщением. 5. Выполняется svc.commit(). 6. needInit сбрасывается в false.

Если в TEMPER нет строк, getLastDt() возвращает LocalDateTime.MIN.

Каждый вызов executeModule() начинается с проверки svc.isConnected(). Если база недоступна, модуль выключает тег connected, ставит needInit = true и возвращает false.

При доступной базе цикл такой:

1. Выполняется отложенная инициализация. 2. Тег connected устанавливается в true. 3. Берется серверное время базы через svc.now(). 4. Время округляется вниз до границы периода через Duration.floorToPeriod(…). 5. Все устройства, подвесы и датчики обновляют текущие значения. 6. Если рассчитанный dt отличается от lastDt, сохраняется архивный срез и выполняется очистка старых строк. 7. Для каждого устройства проверяется состояние связи и при изменении пишется событие в EVENTLOG. 8. Выполняется svc.commit(). 9. lastDt обновляется текущим выровненным временем.

При наступлении нового периода каждое устройство вызывает savePodves(…), каждый подвес собирает значения своих датчиков и передает их в svc.saveTemper(dt, id, values). В базу пишется одна строка TEMPER на один подвес.

События состояния устройства сохраняются отдельно через svc.saveEvent(…). Событие пишется только при изменении статуса относительно lastStatus.

closedownModule() при включенном модуле устанавливает connected в false. Закрытие соединения с базой в этом методе не выполняется.

reload() создает временный ArctrmModule, загружает новую конфигурацию, вызывает closedown() у текущего экземпляра, копирует унаследованные настройки, заменяет svc, devices, periodMs, sensorCount и снова вызывает prepare(). Явного переноса старых значений тегов в этом методе нет.

Схема создается скриптом src/main/resources/dbscr/dbscr.arctrm.yml, который загружается как ресурс и выполняется под именем arctrm.init1.

Назначение: хранение событий модуля и событий изменения состояния устройств.

<code sql> CREATE TABLE {schema}EVENTLOG (

  ID          INTEGER GENERATED BY DEFAULT AS IDENTITY CONSTRAINT {schema}PK_EVENTLOG PRIMARY KEY,
  DT          TIMESTAMP,
  EVENT_CODE  SMALLINT NOT NULL,
  MESSAGE     VARCHAR(128)

) <code>

Поле Назначение
ID Первичный ключ.
DT Время события; при записи берется db.getServerDatetime().
EVENT_CODE Код события из Constants.
MESSAGE Сообщение; для статуса устройства используется имя устройства.

Индекс: IX_EVENTLOG_DT по полю DT.

Коды событий, видимые из кода:

Константа Значение Где используется
—:
EVENT_ARCTRM_INIT 0 Записывается при инициализации Arctrm.
EVENT_CONNECTED 1 Записывается при переходе устройства в состояние связи.
EVENT_DISCONNECTED 2 Записывается, если SYSTEM.ErrorFlag == true.
EVENT_NOLINK 3 Записывается, если ссылка на SYSTEM.ErrorFlag не найдена.

Очистка EVENTLOG в коде не реализована.

Назначение: справочник настроенных подвесов.

<code sql> CREATE TABLE {schema}PODVES (

  ID          INTEGER GENERATED BY DEFAULT AS IDENTITY CONSTRAINT {schema}PK_PODVES PRIMARY KEY,
  NAME        VARCHAR(32) NOT NULL CONSTRAINT {schema}UQ_PODVES_NAME UNIQUE,
  DESCR       VARCHAR(64)

) <code>

Поле Назначение
ID Первичный ключ, сохраняется в Podves.id.
NAME Уникальное имя подвеса в формате <deviceName>.<podvesNum>.
DESCR Описание из конфигурации.

Синхронизация выполняется через Firebird-запрос:

<code sql> update or insert into <PODVES> (name, descr) values (?, ?) matching (name) returning id <code>

Удаление строк PODVES, исчезнувших из конфигурации, не реализовано.

Назначение: архив температурных срезов.

<code sql> CREATE TABLE {schema}TEMPER (

  ID          BIGINT GENERATED BY DEFAULT AS IDENTITY CONSTRAINT {schema}PK_TEMPER PRIMARY KEY,
  PODVES_ID   INTEGER CONSTRAINT {schema}FK_TEMPER_PODVES REFERENCES {schema}PODVES ON DELETE CASCADE ON UPDATE CASCADE,
  DT          TIMESTAMP

) <code>

Поле Назначение
ID Первичный ключ строки архива.
PODVES_ID Ссылка на PODVES.ID.
DT Выровненная метка времени архивного периода.
T0..T<n-1> Динамические целочисленные колонки значений датчиков.

Индекс: IX_TEMPER_DT по полю DT.

Колонки датчиков добавляются в ArctrmDataService.ensureSensorColumns(sensorCount):

<code sql> alter table <TEMPER> add T<i> integer <code>

Если колонка уже есть, она не создается повторно.

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

<code text> ArctrmModule.executeModule()

  1. > ArctrmDevice.update()
    1. > Podves.update()
      1. > Sensor.update()

<code>

Sensor.update() работает только с теговой ссылкой:

Условие Записываемое значение
—:
Тег не найден через ref.linkIfNotValid() VALUE_NOLINK = 3000
Значение меньше -1000 VALUE_BROKEN = 3010
Значение больше 2000 VALUE_SHORTAGE = 3011
Значение в диапазоне Исходное целое значение тега

Исходное значение, вышедшее за допустимый диапазон, отдельно не сохраняется.

Время архива берется от базы данных:

<code java> LocalDateTime dt = Duration.floorToPeriod(svc.now(), periodMs); <code>

svc.now() возвращает db.getServerDatetime(). JVM-время для архивной метки не используется.

Duration.floorToPeriod(…) считает миллисекунды от 1970-01-01T00:00 и округляет вниз до границы периода.

Строковый период разбирается в Duration.parseMillis(…).

Суффикс Единица
ms миллисекунды
s секунды
m минуты
h часы
d дни
нет суффикса миллисекунды

null, пустая строка, отрицательное значение, неверное число и переполнение приводят к IllegalArgumentException. Значение 0 дополнительно запрещено в ArctrmModule.loadModule().

Архивные строки пишутся только если рассчитанный dt отличается от lastDt. При записи:

- каждое устройство сохраняет все свои подвесы; - каждый подвес формирует список текущих значений датчиков; - ArctrmDataService.saveTemper(…) вставляет одну строку TEMPER.

Формат подготовленного SQL строится один раз при подготовке:

<code sql> insert into <TEMPER> (podves_id, dt, T0, T1, …) values (?, ?, ?, ?, …) <code>

При инициализации recordCount загружается запросом select count(*) from <TEMPER>. Каждая вставка увеличивает uncommitedCount.

Перед фиксацией нового архивного среза рассчитывается число удаляемых строк:

<code text> deleteCount = max(0, recordCount + uncommitedCount - maxRecordCount) <code>

Если deleteCount > 0, выполняется удаление старейших строк по ID:

<code sql> delete from <TEMPER> order by id rows <deleteCount> <code>

После успешного commit() счетчик обновляется:

<code text> recordCount = recordCount + uncommitedCount - deleteCount <code>

rollback() сбрасывает uncommitedCount и deleteCount, но не пересчитывает recordCount из базы.

Параметры Arctrm, читаемые из loadModule():

Параметр Значение по умолчанию Назначение Проверка
database db Имя модуля базы данных. В prepare() модуль должен существовать и быть FirebirdDatabaseModule.
schema AT Схема/префикс объектов БД. Отдельной проверки нет.
size 1_000_000 Максимальное количество строк в TEMPER. Должно быть больше 0.
period 1h Период архивирования. Должен корректно разобраться и быть больше 0.
sensorCount 6 Количество датчиков на каждый подвес. Должно быть больше 0.
promauto.termo5 нет в Defaults Карта устройств и подвесов. Ошибки разбора приводят к отказу загрузки.
enable true в AbstractModule Общий флаг включения модуля. Обрабатывается базовым классом.

<code yaml> plugin.arctrm:

module.arctrm:
  database:   db
  period:     5s
  size:       30 #604800
  schema:        AT
  sensorCount:   6
  promauto.termo5:
    mytrm1:
      p0: 231 силос, корпус 2 (гос.резерв)
      p5: 232 силос, корпус 2.
    mytrm2:
      p1: 401 силос, корпус 4.

<code>

В этом примере plugin.arctrm и module.arctrm соответствуют схеме загрузки конфигурации: ConfigurationYaml.getModuleConf(…) ищет plugin.<pluginName> и внутри него module.<moduleName>. Значения database, period, size, schema и sensorCount читаются в ArctrmModule.loadModule().

Секция promauto.termo5 передается в ArctrmDevice.load(…). Ключи mytrm1 и mytrm2 становятся именами модулей-источников тегов. Ключи p0, p5 и p1 дают номера подвесов 0, 5 и 1, потому что код берет часть ключа после первого символа и разбирает ее как число. При sensorCount: 6 для каждого подвеса создаются ссылки на теги Pdv<num>.T0Pdv<num>.T5.

Комментарий #604800 в строке size является YAML-комментарием и не участвует в значении параметра. Активное значение в примере - 30.

Пример выше предоставлен как документационный пример и соответствует фактическому разбору конфигурации в коде. Готовый Arctrm-конфиг в просмотренных файлах репозитория не найден.

getInfo() возвращает краткую строку состояния.

Если модуль выключен:

<code text> disabled <code>

Для включенного модуля формат такой:

<code text> <NOT CONNECTED! >devices=<n> podves=<n> sensors=<n>< nolinkSensors=<n» cursize=<n> last=<time|never> <code>

Поле Значение
NOT CONNECTED! Добавляется через ANSI.redBold(…), если tagConnected.getBool() == false.
devices Количество объектов в devices.
podves Сумма device.podvess.size() по всем устройствам.
sensors Сумма podves.sensors.size() по всем подвесам.
nolinkSensors Показывается только если есть датчики со значением VALUE_NOLINK; фрагмент подсвечивается ANSI.redBold(…).
cursize Текущий кэшированный размер TEMPER, возвращаемый svc.getRecordCount().
last never, если lastDt == null или LocalDateTime.MIN; иначе дата в формате yyyy-MM-dd HH:mm:ss.

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

На этапе подготовки ArctrmDataService.prepare(…) выбрасывает ArctrmException в случаях:

- модуль базы данных не найден; - найденный модуль не является FirebirdDatabaseModule; - ресурс dbscr/dbscr.arctrm.yml не загружен.

prepareModule() перехватывает ArctrmException, пишет ошибку через env.printError(…) и возвращает false.

На этапе выполнения отсутствие подключения определяется через svc.isConnected(). В этом случае модуль:

1. выключает connected; 2. ставит needInit = true; 3. возвращает false.

Состояние устройства определяется по SYSTEM.ErrorFlag:

Условие Код события
Ссылка на SYSTEM.ErrorFlag не найдена EVENT_NOLINK
Ссылка есть, ErrorFlag == true EVENT_DISCONNECTED
Ссылка есть, ErrorFlag == false EVENT_CONNECTED

Событие пишется только при изменении состояния относительно lastStatus. Низкоуровневые ошибки обмена с оборудованием Arctrm не анализирует; они видны только через теги внешних модулей.

Значения ниже -1000 заменяются на VALUE_BROKEN, значения выше 2000 - на VALUE_SHORTAGE. Отдельные события и отдельное логирование для таких значений не реализованы.

Если тег датчика не найден, значение становится VALUE_NOLINK. Такие датчики дополнительно подсчитываются в getInfo().

Все исключения внутри основной части executeModule() перехватываются общим catch (Exception e). Модуль:

1. пишет ошибку через env.printError(…); 2. вызывает svc.rollback(); 3. выключает connected; 4. устанавливает needInit = true; 5. возвращает true.

Реализация использует несколько простых оптимизаций.

Механизм Эффект
Генерация sqlTemperInsert один раз в prepare() Не строит SQL вставки на каждом архивном цикле.
openPreparedStatement(…) в saveTemper() Подготовленный PreparedStatement переиспользуется до commit() или rollback().
Запись только при смене периода Обычные циклы выполнения без нового периода не пишут строки TEMPER.
Кэш recordCount Не выполняет count(*) после каждой вставки.
Очистка только при превышении size Удаление старых строк запускается только при необходимости.
Однократная загрузка списка колонок TEMPER при init Позволяет добавить только отсутствующие T*-колонки.

JDBC batch-вставки не используются: при наступлении периода выполняется одна вставка на каждый подвес.

Ограничение Основание в реализации
Поддерживается только Firebird. ArctrmDataService.prepare(…) требует FirebirdDatabaseModule.
sensorCount общий для всего модуля. Один параметр используется для всех Podves.
Удаление отсутствующих в конфигурации подвесов не выполняется. Есть update or insert, но нет удаления из PODVES.
EVENTLOG не очищается. В коде нет метода очистки этой таблицы.
Очистка TEMPER идет по ID, а не по DT. SQL: delete … order by id rows ….
Последняя дата архива ищется по максимальному ID, не по максимальному DT. SQL: select first 1 dt … order by id desc.
recordCount может устареть при внешних изменениях TEMPER. После init счетчик обновляется только внутренними commit/rollback.
Значение Sensor.value до первого опроса равно Java-значению по умолчанию 0. Поле int value не инициализируется явно.
Нет отдельного признака качества значения в TEMPER. Спецсостояния записываются числами в T*.
Штатный Arctrm-конфиг в просмотренных файлах не найден. Пример в этом документе является документационным и проверен по коду загрузки.
Назначение кодов событий описано только именами констант. Дополнительной документации в коде нет.

- Добавить тесты для Duration, Sensor.update(), getInfo(), смены статусов устройств и расчета recordCount. - Использовать batch-вставки для TEMPER, если число подвесов большое. - Добавить явные поля качества/статуса вместо кодирования состояний специальными числами в T*. - Реализовать политику очистки EVENTLOG. - Добавить синхронизацию удаления PODVES, если удаленные из конфигурации подвесы должны исчезать из справочника. - Загружать последнюю архивную метку по max(DT), если важна именно временная непрерывность, а не порядок вставки. - Периодически сверять recordCount с реальным количеством строк при возможных внешних изменениях базы. - Расширить getInfo(): показывать имя БД, период, лимит архива, последнюю ошибку или время последней успешной записи.

Документ сверялся с текущей реализацией:

- ArctrmModule.java; - ArctrmDataService.java; - ArctrmDevice.java; - Podves.java; - Sensor.java; - Constants.java; - Defaults.java; - Duration.java; - ArctrmPlugin.java; - ArctrmException.java; - Context.java; - src/main/resources/dbscr/dbscr.arctrm.yml; - DatabaseProtoServiceImpl.java; - AbstractModule.java; - Ref.java; - PeripherialPlugin.java; - PaTermo5Module.java.

Места, где код не дает полной информации:

- Пример конфигурации предоставлен как документационный; штатный Arctrm-конфиг в просмотренных файлах репозитория не найден. - Бизнес-смысл событий известен только по именам констант. - Низкоуровневое поведение оборудования находится вне Arctrm; модуль видит только теги и SYSTEM.ErrorFlag. - Требования к эксплуатационной очистке EVENTLOG, удалению старых PODVES и реакции на внешние изменения базы в коде не определены.

  • doc/jroboplc/modules/arctrm.1784744120.txt.gz
  • Last modified: 2026/07/22 21:15
  • by denis