arctrm для разработчика
1. Назначение
ArctrmModule - модуль архивации температурных значений. Он читает значения датчиков из тегов уже настроенных модулей оборудования, сохраняет периодические срезы в базу Firebird и пишет события изменения состояния связи с устройствами и потери актуальности данных подвесов.
Сам модуль не реализует протокол обмена с оборудованием. Доступ к данным выполняется через Ref: каждый Sensor ссылается на тег вида <deviceName>:Pdv<num>.T<sensorIndex>, Podves проверяет актуальность данных по <deviceName>:Pdv<num>.Time, а ArctrmDevice контролирует состояние устройства через <deviceName>:SYSTEM.ErrorFlag. По коду видно, что конфигурация устройств берется из секции promauto.termo5; тип promauto.termo5 создается периферийным плагином как PaTermo5Module.
Описание плагина в ArctrmPlugin.getPluginDescription() - termo value archiver.
2. Архитектура
Модуль состоит из жизненного цикла JRoboPLC, объектной модели устройств и сервиса доступа к базе данных.
| Класс | Ответственность |
|---|---|
ArctrmPlugin | Регистрирует плагин с именем arctrm, создает ArctrmModule и вызывает загрузку конфигурации. |
ArctrmModule | Управляет загрузкой, подготовкой, инициализацией, циклом выполнения, записью архива, записью событий, connected-тегом, остановом и reload. |
ArctrmDataService | Инкапсулирует работу с БД: поиск модуля базы, проверку Firebird, загрузку и выполнение скрипта, синхронизацию PODVES, запись TEMPER и EVENTLOG, удаление старых архивных строк и учет текущего размера архива. |
ArctrmDevice | Описывает одно настроенное устройство, содержит список Podves, читает SYSTEM.ErrorFlag и пишет события изменения статуса устройства. |
Podves | Описывает один подвес устройства, проверяет актуальность данных по тегу Pdv<num>.Time, содержит список Sensor, синхронизирует запись в PODVES, сохраняет строку архива с актуальными данными и формирует событие потери актуальности. |
Sensor | Ссылается на температурный тег, читает целочисленное значение и заменяет отсутствующие или выходящие за диапазон значения специальными кодами. |
Duration | Разбирает строковый период и выравнивает дату-время на границу периода. |
Constants | Содержит коды событий, специальные значения датчиков и предел актуальности данных подвеса. |
Defaults | Содержит значения конфигурации по умолчанию. |
Params | Пустой класс; в текущей реализации не используется. |
ArctrmException | Проверяемое исключение для ошибок конфигурации, подготовки и инициализации Arctrm. |
Прямое подключение к базе выполняет только ArctrmDataService. Остальная часть модуля работает с объектами предметной модели и теговыми ссылками.
3. Структура объектов
Иерархия объектов во время работы:
ArctrmModule
└── ArctrmDevice
└── Podves
└── Sensor
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. Кроме списка датчиков подвес создает ссылку refTime на тег <deviceName>:Pdv<num>.Time и хранит признаки dataValid и needSaveEvent.
Sensor создается для каждого индекса от 0 до sensorCount - 1. Имя тега строится так:
String tagname = String.format("Pdv%d.T%d", num, i);
Ссылка датчика указывает на <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). Сервис:
- Ищет модуль базы данных по имени
database. - Проверяет, что найденный модуль является
FirebirdDatabaseModule. - Загружает ресурс
dbscr/dbscr.arctrm.yml. - Формирует имена таблиц
EVENTLOG,PODVES,TEMPERс учетом схемы. - Генерирует SQL вставки в
TEMPERс колонкамиT0..T<n-1>.
После подготовки сервиса модуль вызывает prepare() у каждого устройства. Устройства подготавливают ссылки на SYSTEM.ErrorFlag, а подвесы - ссылки всех датчиков и Pdv<num>.Time.
В конце подготовки выставляется needInit = true, а lastDt сбрасывается в null.
Инициализация
Инициализация выполняется в начале рабочего цикла при наличии подключения к базе и только если needInit == true.
init() выполняет следующие шаги:
svc.init(sensorCount)выполняет скриптarctrm.init1, добавляет отсутствующие колонки датчиков вTEMPER, загружаетrecordCountи сбрасывает счетчики неподтвержденных операций.- Каждое устройство вызывает
device.init(svc), а каждый подвес синхронизирует себя с таблицейPODVES. lastDtзагружается запросом последней строкиTEMPERпо убываниюID.- В
EVENTLOGпишется событиеEVENT_ARCTRM_INITс пустым сообщением. - Выполняется
svc.commit(). needInitсбрасывается вfalse.
Если в TEMPER нет строк, getLastDt() возвращает LocalDateTime.MIN.
Цикл выполнения
Каждый вызов executeModule() начинается с проверки svc.isConnected(). Если база недоступна, модуль выключает тег connected, ставит needInit = true и возвращает false.
При доступной базе цикл такой:
- Выполняется отложенная инициализация.
- Тег
connectedустанавливается вtrue. - Берется серверное время базы через
svc.now(). - Время округляется вниз до границы периода через
Duration.floorToPeriod(…). - Все устройства и подвесы обновляют состояние; датчики опрашиваются только у подвесов с актуальным значением
Pdv<num>.Time. - Если рассчитанный
dtотличается отlastDt, сохраняется архивный срез и выполняется очистка старых строк. - Для каждого устройства проверяется состояние связи и при изменении пишется событие в
EVENTLOG. - Выполняется
svc.commit(). lastDtобновляется текущим выровненным временем.
Сохранение данных
При наступлении нового периода каждое устройство вызывает savePodves(…). Подвес с актуальными данными собирает значения своих датчиков и передает их в svc.saveTemper(dt, id, values); в базу пишется одна строка TEMPER. Если ссылка на Pdv<num>.Time не найдена или значение находится вне диапазона 0..600, строка для этого подвеса не создается.
События состояния устройства сохраняются отдельно через svc.saveEvent(…). Событие пишется только при изменении статуса относительно lastStatus. Событие EVENT_PODVES_UPDATE_STALE формируется при первой потере актуальности данных подвеса и сохраняется вызовом Podves.save(…) при наступлении следующего архивного периода.
Останов и reload
closedownModule() при включенном модуле устанавливает connected в false. Закрытие соединения с базой в этом методе не выполняется.
reload() создает временный ArctrmModule, загружает новую конфигурацию, вызывает closedown() у текущего экземпляра, копирует унаследованные настройки, заменяет svc, devices, periodMs, sensorCount и снова вызывает prepare(). Явного переноса старых значений тегов в этом методе нет.
5. Структура базы данных
Схема создается скриптом src/main/resources/dbscr/dbscr.arctrm.yml, который загружается как ресурс и выполняется под именем arctrm.init1.
Таблица ''EVENTLOG''
Назначение: хранение событий модуля, изменения состояния устройств и потери актуальности данных подвесов.
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) )
| Поле | Назначение |
|---|---|
ID | Первичный ключ. |
DT | Время события; при записи берется db.getServerDatetime(). |
EVENT_CODE | Код события из Constants. |
MESSAGE | Сообщение; для статуса устройства используется имя устройства, для потери актуальности - имя подвеса, для инициализации - пустая строка. |
Индекс: IX_EVENTLOG_DT по полю DT.
Коды событий, видимые из кода:
| Константа | Значение | Где используется |
|---|---|---|
EVENT_ARCTRM_INIT | 100 | Записывается при инициализации Arctrm. |
EVENT_DEVICE_CONNECTED | 99 | Записывается при переходе устройства в состояние связи. |
EVENT_DEVICE_DISCONNECTED | 1 | Записывается, если SYSTEM.ErrorFlag == true. |
EVENT_DEVICE_NOLINK | 0 | Записывается, если ссылка на SYSTEM.ErrorFlag не найдена и предыдущий статус имел другой код. |
EVENT_PODVES_UPDATE_STALE | 3 | Записывается при первой потере актуальности данных подвеса; в MESSAGE передается имя <deviceName>.<podvesNum>. |
Очистка EVENTLOG в коде не реализована.
Таблица ''PODVES''
Назначение: справочник настроенных подвесов.
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) )
| Поле | Назначение |
|---|---|
ID | Первичный ключ, сохраняется в Podves.id. |
NAME | Уникальное имя подвеса в формате <deviceName>.<podvesNum>. |
DESCR | Описание из конфигурации. |
Синхронизация выполняется через Firebird-запрос:
UPDATE OR INSERT INTO <PODVES> (name, descr) VALUES (?, ?) matching (name) returning id
Удаление строк PODVES, исчезнувших из конфигурации, не реализовано.
Таблица ''TEMPER''
Назначение: архив температурных срезов.
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 )
| Поле | Назначение |
|---|---|
ID | Первичный ключ строки архива. |
PODVES_ID | Ссылка на PODVES.ID. |
DT | Выровненная метка времени архивного периода. |
T0..T<n-1> | Динамические целочисленные колонки значений датчиков. |
Индекс: IX_TEMPER_DT по полю DT.
Колонки датчиков добавляются в ArctrmDataService.ensureSensorColumns(sensorCount):
ALTER TABLE <TEMPER> ADD T<i> INTEGER
Если колонка уже есть, она не создается повторно.
6. Алгоритм архивирования
Опрос датчиков
На каждом цикле выполнения вызывается цепочка:
ArctrmModule.executeModule()
-> ArctrmDevice.update()
-> Podves.update()
-> Podves.checkTimeValid()
-> Sensor.update() при актуальных данных
Сначала Podves.update() проверяет ссылку Pdv<num>.Time. Данные считаются актуальными, если ссылка существует, а значение тега находится в диапазоне от 0 до PODVES_UPDATE_TIME_LIMIT = 600 включительно. При неактуальных данных Sensor.update() не вызывается и значения датчиков остаются прежними.
Для подвеса с актуальными данными Sensor.update() работает с теговой ссылкой:
| Условие | Записываемое значение |
|---|---|
Тег не найден через ref.linkIfNotValid() | VALUE_NOLINK = 3013 |
Значение меньше -1000 | VALUE_SHORTAGE = 3010 |
Значение больше 2000 | VALUE_BROKEN = 3011 |
| Значение в диапазоне | Исходное целое значение тега |
Исходное значение, вышедшее за допустимый диапазон, отдельно не сохраняется.
Формирование времени
Время архива берется от базы данных:
LocalDateTime dt = Duration.floorToPeriod(svc.now(), periodMs);
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. При записи:
- каждое устройство вызывает
save(…)для всех своих подвесов; - каждый подвес с актуальными данными формирует список текущих значений датчиков;
ArctrmDataService.saveTemper(…)вставляет одну строкуTEMPERдля каждого подвеса с актуальными данными;- подвес с неактуальными данными не создает строку
TEMPER, но при установленномneedSaveEventзаписывает событиеEVENT_PODVES_UPDATE_STALE.
Формат подготовленного SQL строится один раз при подготовке:
INSERT INTO <TEMPER> (podves_id, dt, T0, T1, ...) VALUES (?, ?, ?, ?, ...)
Циклическое обслуживание архива
При инициализации recordCount загружается запросом select count(*) from <TEMPER>. Каждая вставка увеличивает uncommitedCount.
Перед фиксацией нового архивного среза рассчитывается число удаляемых строк:
deleteCount = max(0, recordCount + uncommitedCount - maxRecordCount)
Если deleteCount > 0, выполняется удаление старейших строк по ID:
DELETE FROM <TEMPER> ORDER BY id ROWS <deleteCount>
После успешного commit() счетчик обновляется:
recordCount = recordCount + uncommitedCount - deleteCount
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 | Общий флаг включения модуля. | Обрабатывается базовым классом. |
Пример конфигурации
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.
В этом примере 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, а также ссылка на Pdv<num>.Time.
Комментарий #604800 в строке size является YAML-комментарием и не участвует в значении параметра. Активное значение в примере - 30.
Пример выше предоставлен как документационный пример и соответствует фактическому разбору конфигурации в коде. Готовый Arctrm-конфиг в просмотренных файлах репозитория не найден.
8. Диагностика
getInfo() возвращает краткую строку состояния.
Если модуль выключен:
disabled
Для включенного модуля формат такой:
<NOT CONNECTED! >devices=<n> podves=<n> sensors=<n>< nolinkSensors=<n>> cursize=<n> last=<time|never>
| Поле | Значение |
|---|---|
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. Обработка ошибок
Ошибки базы данных
На этапе подготовки ArctrmDataService.prepare(…) выбрасывает ArctrmException в случаях:
- модуль базы данных не найден;
- найденный модуль не является
FirebirdDatabaseModule; - ресурс
dbscr/dbscr.arctrm.ymlне загружен.
prepareModule() перехватывает ArctrmException, пишет ошибку через env.printError(…) и возвращает false.
На этапе выполнения отсутствие подключения определяется через svc.isConnected(). В этом случае модуль:
- выключает
connected; - ставит
needInit = true; - возвращает
false.
Ошибки связи с устройствами
Состояние устройства определяется по SYSTEM.ErrorFlag:
| Условие | Код события |
|---|---|
Ссылка на SYSTEM.ErrorFlag не найдена | EVENT_DEVICE_NOLINK = 0 |
Ссылка есть, ErrorFlag == true | EVENT_DEVICE_DISCONNECTED = 1 |
Ссылка есть, ErrorFlag == false | EVENT_DEVICE_CONNECTED = 99 |
Событие пишется только при изменении состояния относительно lastStatus. Поле lastStatus изначально равно 0, поэтому начальное состояние EVENT_DEVICE_NOLINK не записывается, а начальные состояния EVENT_DEVICE_DISCONNECTED и EVENT_DEVICE_CONNECTED записываются. Низкоуровневые ошибки обмена с оборудованием Arctrm не анализирует; они видны только через теги внешних модулей.
Неактуальные данные подвеса
Ссылка на Pdv<num>.Time считается неактуальной, если она не найдена либо содержит значение меньше 0 или больше 600. При переходе dataValid из null или true в false устанавливается needSaveEvent. До восстановления актуальности датчики не опрашиваются, а строка TEMPER для подвеса не записывается.
При наступлении следующего архивного периода Podves.save(…) записывает EVENT_PODVES_UPDATE_STALE = 3 с именем подвеса в MESSAGE и сбрасывает needSaveEvent. Повторное событие возможно только после восстановления актуальности и ее новой потери; отдельное событие восстановления не предусмотрено.
Некорректные значения датчиков
Значения ниже -1000 заменяются на VALUE_SHORTAGE = 3010, значения выше 2000 - на VALUE_BROKEN = 3011. Отдельные события и отдельное логирование для таких значений не реализованы.
Если тег датчика не найден, значение становится VALUE_NOLINK = 3013. Такие датчики дополнительно подсчитываются в getInfo().
Неожиданные исключения
Все исключения внутри основной части executeModule() перехватываются общим catch (Exception e). Модуль:
- пишет ошибку через
env.printError(…); - вызывает
svc.rollback(); - выключает
connected; - устанавливает
needInit = true; - возвращает
true.
10. Производительность
Реализация использует несколько простых оптимизаций.
| Механизм | Эффект |
|---|---|
Генерация sqlTemperInsert один раз в prepare() | Не строит SQL вставки на каждом архивном цикле. |
openPreparedStatement(…) в saveTemper() | Подготовленный PreparedStatement переиспользуется до commit() или rollback(). |
| Запись только при смене периода | Обычные циклы выполнения без нового периода не пишут строки TEMPER. |
Кэш recordCount | Не выполняет count(*) после каждой вставки. |
Очистка только при превышении size | Удаление старых строк запускается только при необходимости. |
Однократная загрузка списка колонок TEMPER при init | Позволяет добавить только отсутствующие T*-колонки. |
JDBC batch-вставки не используются: при наступлении периода выполняется одна вставка на каждый подвес с актуальными данными.
11. Ограничения
| Ограничение | Основание в реализации |
|---|---|
| Поддерживается только 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. Возможные улучшения
- Добавить тесты для
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;Params.java;src/main/resources/dbscr/dbscr.arctrm.yml;DatabaseProtoServiceImpl.java;AbstractModule.java;Ref.java;PeripherialPlugin.java;PaTermo5Module.java.
Места, где код не дает полной информации:
- Пример конфигурации предоставлен как документационный; штатный Arctrm-конфиг в просмотренных файлах репозитория не найден.
- Бизнес-смысл событий известен только по именам констант.
- Низкоуровневое поведение оборудования находится вне Arctrm; модуль видит только теги датчиков,
Pdv<num>.TimeиSYSTEM.ErrorFlag. - Требования к эксплуатационной очистке
EVENTLOG, удалению старыхPODVESи реакции на внешние изменения базы в коде не определены.