| Both sides previous revision Previous revision Next revision | Previous revision |
| doc:jroboplc:modules:arctrm [2026/07/22 21:16] – [3. Структура объектов] denis | doc:jroboplc:modules:arctrm [2026/08/01 11:25] (current) – [События] denis |
|---|
| ====== Модуль Arctrm ====== | ====== arctrm ====== |
| |
| ===== 1. Назначение ===== | Модуль ''arctrm'' предназначен для периодического архивирования температурных значений в базе данных Firebird. На текущий момент значения считываются только из тегов модулей ''promauto.termo5''. В дальнейшем возможно расширение поддержки других модулей термометрии. |
| |
| ''ArctrmModule'' - модуль архивации температурных значений. Он читает значения датчиков из тегов уже настроенных модулей оборудования, сохраняет периодические срезы в базу Firebird и пишет события изменения состояния связи с устройствами. | Модуль сохраняет: |
| |
| Сам модуль не реализует протокол обмена с оборудованием. Доступ к данным выполняется через ''Ref'': каждый ''Sensor'' ссылается на тег вида ''<deviceName>:Pdv<num>.T<sensorIndex>'', а ''ArctrmDevice'' контролирует состояние устройства через ''<deviceName>:SYSTEM.ErrorFlag''. По коду видно, что конфигурация устройств берется из секции ''promauto.termo5''; тип ''promauto.termo5'' создается периферийным плагином как ''PaTermo5Module''. | * температурные срезы по подвескам с актуальными данными; |
| | * сведения о подвесках; |
| | * события запуска архива, изменения состояния связи с устройствами и потери актуальности данных подвесок. |
| |
| Описание плагина в ''ArctrmPlugin.getPluginDescription()'' - ''termo value archiver''. | Сам модуль не выполняет обмен с оборудованием. Данные и состояние связи он получает из тегов других модулей. |
| |
| ===== 2. Архитектура ===== | [[doc:jroboplc:modules:arctrm_dev]] |
| |
| Модуль состоит из жизненного цикла 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''. Остальная часть модуля работает с объектами предметной модели и теговыми ссылками. | |
| | |
| ===== 3. Структура объектов ===== | |
| | |
| Иерархия объектов во время работы: | |
| | |
| <code> | |
| 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''. | |
| | |
| ===== 4. Принцип работы ===== | |
| | |
| ==== Загрузка конфигурации ==== | |
| | |
| ''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''. | |
| | |
| ==== Останов и reload ==== | |
| | |
| ''closedownModule()'' при включенном модуле устанавливает ''connected'' в ''false''. Закрытие соединения с базой в этом методе не выполняется. | |
| | |
| ''reload()'' создает временный ''ArctrmModule'', загружает новую конфигурацию, вызывает ''closedown()'' у текущего экземпляра, копирует унаследованные настройки, заменяет ''svc'', ''devices'', ''periodMs'', ''sensorCount'' и снова вызывает ''prepare()''. Явного переноса старых значений тегов в этом методе нет. | |
| | |
| ===== 5. Структура базы данных ===== | |
| | |
| Схема создается скриптом ''src/main/resources/dbscr/dbscr.arctrm.yml'', который загружается как ресурс и выполняется под именем ''arctrm.init1''. | |
| | |
| ==== Таблица ''EVENTLOG'' ==== | |
| | |
| Назначение: хранение событий модуля и событий изменения состояния устройств. | |
| | |
| <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'' в коде не реализована. | |
| | |
| ==== Таблица ''PODVES'' ==== | |
| | |
| Назначение: справочник настроенных подвесов. | |
| | |
| <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'', исчезнувших из конфигурации, не реализовано. | |
| | |
| ==== Таблица ''TEMPER'' ==== | |
| | |
| Назначение: архив температурных срезов. | |
| | |
| <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> | |
| | |
| Если колонка уже есть, она не создается повторно. | |
| | |
| ===== 6. Алгоритм архивирования ===== | |
| | |
| ==== Опрос датчиков ==== | |
| | |
| На каждом цикле выполнения вызывается цепочка: | |
| | |
| <code text> | |
| ArctrmModule.executeModule() | |
| -> ArctrmDevice.update() | |
| -> Podves.update() | |
| -> 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'' из базы. | |
| | |
| ===== 7. Конфигурация ===== | |
| | |
| Параметры 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> | <code yaml> |
| plugin.arctrm: | plugin.arctrm: |
| module.arctrm: | module.arctrm: |
| database: db | database: db |
| period: 5s | schema: AT |
| size: 30 #604800 | period: 5s |
| schema: AT | size: 1000000 |
| sensorCount: 6 | sensorCount: 6 |
| |
| promauto.termo5: | promauto.termo5: |
| mytrm1: | mytrm1: |
| p0: 231 силос, корпус 2 (гос.резерв) | p0: 231 силос, корпус 2 |
| p5: 232 силос, корпус 2. | p5: 232 силос, корпус 2 |
| |
| mytrm2: | mytrm2: |
| p1: 401 силос, корпус 4. | p1: 401 силос, корпус 4 |
| <code> | </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>.T0'' ... ''Pdv<num>.T5''. | ^ Параметр ^ По умолчанию ^ Описание ^ |
| | | ''database'' | ''db'' | Имя модуля базы данных. Модуль должен использовать Firebird. | |
| | | ''schema'' | ''AT'' | Префикс таблиц базы данных. | |
| | | ''period'' | ''1h'' | Период сохранения архива. Поддерживаются суффиксы ''ms'', ''s'', ''m'', ''h'', ''d''. | |
| | | ''size'' | ''1000000'' | Максимальное количество строк в таблице ''TEMPER''. | |
| | | ''sensorCount'' | ''6'' | Количество датчиков в каждой подвеске. | |
| | | ''promauto.termo5'' | — | Список модулей-источников и настроенных подвесок. | |
| |
| Комментарий ''#604800'' в строке ''size'' является YAML-комментарием и не участвует в значении параметра. Активное значение в примере - ''30''. | Значения ''size'', ''period'' и ''sensorCount'' должны быть больше нуля. |
| |
| Пример выше предоставлен как документационный пример и соответствует фактическому разбору конфигурации в коде. Готовый Arctrm-конфиг в просмотренных файлах репозитория не найден. | ==== Настройка устройств ==== |
| |
| ===== 8. Диагностика ===== | Ключ первого уровня в секции ''promauto.termo5'' является именем модуля-источника тегов. Ключ подвески состоит из произвольного первого символа и числового номера. Например, из ключа ''p5'' модуль получает номер подвеса ''5''. |
| |
| ''getInfo()'' возвращает краткую строку состояния. | Для каждой подвески создаются ссылки на теги: |
| | |
| Если модуль выключен: | |
| |
| <code text> | <code text> |
| disabled | <deviceName>:Pdv<podvesNum>.T0 |
| <code> | <deviceName>:Pdv<podvesNum>.T1 |
| | ... |
| | <deviceName>:Pdv<podvesNum>.T<sensorCount-1> |
| | </code> |
| |
| Для включенного модуля формат такой: | Актуальность данных подвески определяется по тегу: |
| |
| <code text> | <code text> |
| <NOT CONNECTED! >devices=<n> podves=<n> sensors=<n>< nolinkSensors=<n>> cursize=<n> last=<time|never> | <deviceName>:Pdv<podvesNum>.Time |
| <code> | </code> |
| |
| | Поле | Значение | | Данные считаются актуальными, если ссылка на тег существует, а его значение находится в диапазоне от ''0'' до ''600'' включительно. |
| | --- | --- | | |
| | ''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()'' не выводятся. | Состояние связи с устройством определяется по тегу: |
| |
| ===== 9. Обработка ошибок ===== | <code text> |
| | <deviceName>:SYSTEM.ErrorFlag |
| | </code> |
| |
| ==== Ошибки базы данных ==== | ===== Принцип работы ===== |
| |
| На этапе подготовки ''ArctrmDataService.prepare(...)'' выбрасывает ''ArctrmException'' в случаях: | После подключения к базе модуль автоматически: |
| |
| - модуль базы данных не найден; | - создаёт необходимые таблицы и индексы; |
| - найденный модуль не является ''FirebirdDatabaseModule''; | - добавляет отсутствующие колонки ''T0...Tn'' в таблицу ''TEMPER''; |
| - ресурс ''dbscr/dbscr.arctrm.yml'' не загружен. | - синхронизирует справочник подвесок; |
| | - загружает дату последней архивной записи; |
| | - записывает событие запуска архива. |
| |
| ''prepareModule()'' перехватывает ''ArctrmException'', пишет ошибку через ''env.printError(...)'' и возвращает ''false''. | На каждом рабочем цикле модуль сначала проверяет ''Pdv<podvesNum>.Time'' каждой подвески. Значения датчиков считываются только для подвесок с актуальными данными. Время архивного среза берётся с сервера базы данных и округляется вниз до границы периода ''period''. |
| |
| На этапе выполнения отсутствие подключения определяется через ''svc.isConnected()''. В этом случае модуль: | Если наступил новый период, в ''TEMPER'' записывается по одной строке для каждой подвески с актуальными данными. Для подвески с отсутствующим тегом ''Time'' или значением вне диапазона ''0...600'' строка не создаётся. При превышении лимита ''size'' удаляются строки с наименьшими значениями ''ID''. |
| |
| 1. выключает ''connected''; | ===== База данных ===== |
| 2. ставит ''needInit = true''; | |
| 3. возвращает ''false''. | |
| |
| ==== Ошибки связи с устройствами ==== | ^ Таблица ^ Назначение ^ |
| | | ''PODVES'' | Справочник подвесок: имя и описание. | |
| | | ''TEMPER'' | Архив температурных срезов. Содержит ''PODVES_ID'', ''DT'' и динамические колонки ''T0...Tn''. | |
| | | ''EVENTLOG'' | Журнал запуска архива, изменения состояния связи с устройствами и потери актуальности данных подвесок. | |
| |
| Состояние устройства определяется по ''SYSTEM.ErrorFlag'': | Основные связи: |
| |
| | Условие | Код события | | <code text> |
| | --- | --- | | PODVES.ID <- TEMPER.PODVES_ID |
| | Ссылка на ''SYSTEM.ErrorFlag'' не найдена | ''EVENT_NOLINK'' | | </code> |
| | Ссылка есть, ''ErrorFlag == true'' | ''EVENT_DISCONNECTED'' | | |
| | Ссылка есть, ''ErrorFlag == false'' | ''EVENT_CONNECTED'' | | |
| |
| Событие пишется только при изменении состояния относительно ''lastStatus''. Низкоуровневые ошибки обмена с оборудованием Arctrm не анализирует; они видны только через теги внешних модулей. | Одна строка ''TEMPER'' содержит значения всех датчиков одной подвески за один архивный период. |
| |
| ==== Некорректные значения датчиков ==== | ===== Значения датчиков ===== |
| |
| Значения ниже ''-1000'' заменяются на ''VALUE_BROKEN'', значения выше ''2000'' - на ''VALUE_SHORTAGE''. Отдельные события и отдельное логирование для таких значений не реализованы. | Обычные значения записываются без изменения. Ошибочные состояния сохраняются в тех же колонках ''T0...Tn'' как специальные числа. |
| |
| Если тег датчика не найден, значение становится ''VALUE_NOLINK''. Такие датчики дополнительно подсчитываются в ''getInfo()''. | ^ Значение ^ Состояние ^ |
| | | ''3010'' | Значение ниже допустимого диапазона — короткое замыкание. | |
| | | ''3011'' | Значение выше допустимого диапазона — обрыв. | |
| | | ''3013'' | Тег датчика не найден — ''NOLINK''. | |
| |
| ==== Неожиданные исключения ==== | Допустимый диапазон обычного значения: от ''-1000'' до ''2000'' включительно. |
| |
| Все исключения внутри основной части ''executeModule()'' перехватываются общим ''catch (Exception e)''. Модуль: | ===== События ===== |
| |
| 1. пишет ошибку через ''env.printError(...)''; | ^ Код ^ Событие ^ |
| 2. вызывает ''svc.rollback()''; | | ''0'' | Ссылка на ''SYSTEM.ErrorFlag'' не найдена. | |
| 3. выключает ''connected''; | | ''1'' | Связь с устройством потеряна: ''SYSTEM.ErrorFlag = true''. | |
| 4. устанавливает ''needInit = true''; | | ''3'' | Данные подвески неактуальны: ссылка на ''Pdv<podvesNum>.Time'' не найдена или значение находится вне диапазона ''0...600''. | |
| 5. возвращает ''true''. | | ''99'' | Связь с устройством установлена. | |
| | | ''100'' | Инициализация архивации. | |
| |
| ===== 10. Производительность ===== | Для событий состояния устройства в поле ''MESSAGE'' записывается имя устройства, для события неактуальных данных — имя подвески в формате ''<deviceName>.<podvesNum>'', для события инициализации — пустая строка. |
| |
| Реализация использует несколько простых оптимизаций. | Событие состояния устройства записывается только при изменении его состояния. Событие неактуальных данных формируется при первой потере актуальности, записывается при наступлении следующего архивного периода и не повторяется до восстановления актуальности и её следующей потери. |
| |
| | Механизм | Эффект | | ===== Теги модуля ===== |
| | --- | --- | | |
| | Генерация ''sqlTemperInsert'' один раз в ''prepare()'' | Не строит SQL вставки на каждом архивном цикле. | | |
| | ''openPreparedStatement(...)'' в ''saveTemper()'' | Подготовленный ''PreparedStatement'' переиспользуется до ''commit()'' или ''rollback()''. | | |
| | Запись только при смене периода | Обычные циклы выполнения без нового периода не пишут строки ''TEMPER''. | | |
| | Кэш ''recordCount'' | Не выполняет ''count(*)'' после каждой вставки. | | |
| | Очистка только при превышении ''size'' | Удаление старых строк запускается только при необходимости. | | |
| | Однократная загрузка списка колонок ''TEMPER'' при init | Позволяет добавить только отсутствующие ''T*''-колонки. | | |
| |
| JDBC batch-вставки не используются: при наступлении периода выполняется одна вставка на каждый подвес. | ^ Тег ^ Тип ^ Описание ^ |
| | | ''connected'' | boolean | Установлен, когда модуль подключён к базе и рабочий цикл выполняется без ошибки. | |
| |
| ===== 11. Ограничения ===== | ===== Диагностика ===== |
| |
| | Ограничение | Основание в реализации | | Метод ''getInfo()'' возвращает краткую строку состояния, например: |
| | --- | --- | | |
| | Поддерживается только 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-конфиг в просмотренных файлах не найден. | Пример в этом документе является документационным и проверен по коду загрузки. | | |
| | Назначение кодов событий описано только именами констант. | Дополнительной документации в коде нет. | | |
| |
| ===== 12. Возможные улучшения ===== | <code text> |
| | devices=2 podves=3 sensors=18 cursize=30 last=2026-07-23 00:16:50 |
| - Добавить тесты для ''Duration'', ''Sensor.update()'', ''getInfo()'', смены статусов устройств и расчета ''recordCount''. | </code> |
| - Использовать batch-вставки для ''TEMPER'', если число подвесов большое. | |
| - Добавить явные поля качества/статуса вместо кодирования состояний специальными числами в ''T*''. | |
| - Реализовать политику очистки ''EVENTLOG''. | |
| - Добавить синхронизацию удаления ''PODVES'', если удаленные из конфигурации подвесы должны исчезать из справочника. | |
| - Загружать последнюю архивную метку по ''max(DT)'', если важна именно временная непрерывность, а не порядок вставки. | |
| - Периодически сверять ''recordCount'' с реальным количеством строк при возможных внешних изменениях базы. | |
| - Расширить ''getInfo()'': показывать имя БД, период, лимит архива, последнюю ошибку или время последней успешной записи. | |
| | |
| ===== Проверка по исходникам ===== | |
| |
| Документ сверялся с текущей реализацией: | ^ Поле ^ Описание ^ |
| | | ''devices'' | Количество настроенных устройств. | |
| | | ''podves'' | Общее количество подвесок. | |
| | | ''sensors'' | Общее количество датчиков. | |
| | | ''nolinkSensors'' | Количество датчиков без ссылки; выводится только при ненулевом значении. | |
| | | ''cursize'' | Текущий размер таблицы ''TEMPER'', учтённый модулем. | |
| | | ''last'' | Дата и время последнего архивного периода или ''never''. | |
| |
| - ''ArctrmModule.java''; | При отсутствии подключения в начало строки добавляется ''NOT CONNECTED!''. Если модуль выключен, возвращается ''disabled''. |
| - ''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-конфиг в просмотренных файлах репозитория не найден. | * Поддерживается только Firebird. |
| - Бизнес-смысл событий известен только по именам констант. | * Значение ''sensorCount'' является общим для всех подвесок. |
| - Низкоуровневое поведение оборудования находится вне Arctrm; модуль видит только теги и ''SYSTEM.ErrorFlag''. | * Таблица ''EVENTLOG'' автоматически не очищается. |
| - Требования к эксплуатационной очистке ''EVENTLOG'', удалению старых ''PODVES'' и реакции на внешние изменения базы в коде не определены. | * Удаление архивных строк выполняется по ''ID'', а не по дате ''DT''. |