doc:jroboplc:modules:arctrm

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
doc:jroboplc:modules:arctrm [2026/07/22 20:56] – created denisdoc:jroboplc:modules:arctrm [2026/08/01 11:25] (current) – [События] denis
Line 1: Line 1:
-Arctrm Module =+====== arctrm ======
  
-== 1Purpose ==+Модуль ''arctrm'' предназначен для периодического архивирования температурных значений в базе данных Firebird. На текущий момент значения считываются только из тегов модулей ''promauto.termo5''. В дальнейшем возможно расширение поддержки других модулей термометрии.
  
-The `arctrm` plugin provides the `ArctrmModule`, a temperature value archiver for JRoboPLC. Its responsibility is to read temperature values from configured termo device tags, write periodic snapshots into a Firebird database, and record device connection status changes in an event log.+Модуль сохраняет:
  
-The module does not implement the hardware protocol itself. It reads values through `Ref` objects that point to tags exposed by other modules. In the inspected codeconfiguration is read from the `promauto.termo5` section, and the referenced hardware type `promauto.termo5` is created by the peripheral plugin as `PaTermo5Module`.+  * температурные срезы по подвескам с актуальными данными; 
 +  * сведения о подвесках; 
 +  * события запуска архиваизменения состояния связи с устройствами и потери актуальности данных подвесок.
  
-The plugin description returned by `ArctrmPlugin.getPluginDescription()` is `termo value archiver`.+Сам модуль не выполняет обмен с оборудованиемДанные и состояние связи он получает из тегов других модулей.
  
-== 2. High-level Architecture ==+[[doc:jroboplc:modules:arctrm_dev]]
  
-The module is split into a small runtime layer, an object hierarchy for configured devices, and a database service.+===== Конфигурация =====
  
-`ArctrmPlugin` registers the plugin name `arctrm` and creates `ArctrmModule` instances. It delegates module loading to `ArctrmModule.load(conf)`.+Пример настройки:
  
-`ArctrmModule` owns the module lifecycleIt loads configuration, creates the `connected` tag, prepares the database service and device references, initializes database structures, runs each execution cycle, writes archive records, writes status events, and handles reload/closedown.+<code yaml> 
 +plugin.arctrm: 
 +  module.arctrm: 
 +    database: db 
 +    schema: AT 
 +    period: 5s 
 +    size: 1000000 
 +    sensorCount: 6
  
-`ArctrmDataService` extends `DatabaseProtoServiceImpl` and contains all direct database interactionIt resolves the configured database moduleverifies that it is a `FirebirdDatabaseModule`loads and executes the Arctrm database script, builds schema-qualified table names, creates missing sensor columns, synchronizes podves metadata, writes temperature rows and event rows, deletes old archive rows, and tracks the current archive record count.+    promauto.termo5: 
 +      mytrm1: 
 +        p0: 231 силоскорпус 2 
 +        p5: 232 силоскорпус 2
  
-`ArctrmDevice` represents one configured termo device. It owns a list of `Podves` objects and a reference to the device tag `SYSTEM.ErrorFlag`. It delegates value collection to its podves list and writes an event when its connection status changes.+      mytrm2: 
 +        p1: 401 силос, корпус 4 
 +</code>
  
-`Podves` represents one podves channel group under a device. It owns a list of `Sensor` objects, synchronizes itself with the `PODVES` table, and writes one `TEMPER` archive row for all its sensor values.+==== Параметры ====
  
-`Sensor` represents one temperature tag referenceIt links to a tag such as `Pdv0.T0`reads the integer valueand normalizes missing or out-of-range values to module constants.+^ Параметр ^ По умолчанию ^ Описание ^ 
 +| ''database'' | ''db'' | Имя модуля базы данныхМодуль должен использовать Firebird
 +| ''schema'' | ''AT'' | Префикс таблиц базы данных. | 
 +| ''period'' | ''1h'' | Период сохранения архива. Поддерживаются суффиксы ''ms''''s''''m'', ''h'', ''d''. | 
 +| ''size'' | ''1000000'' | Максимальное количество строк в таблице ''TEMPER''. | 
 +| ''sensorCount'' | ''6'' | Количество датчиков в каждой подвеске. | 
 +| ''promauto.termo5'' | — | Список модулей-источников и настроенных подвесок|
  
-`Duration` parses period strings and aligns timestamps to a configured period boundary.+Значения ''size'', ''period'' и ''sensorCount'' должны быть больше нуля.
  
-`Constants` defines event codes and special sensor values.+==== Настройка устройств ====
  
-`Defaults` defines default configuration values.+Ключ первого уровня в секции ''promauto.termo5'' является именем модуля-источника тегов. Ключ подвески состоит из произвольного первого символа и числового номера. Например, из ключа ''p5'' модуль получает номер подвеса ''5''.
  
-`ArctrmException` is a checked exception used for module-specific preparation and initialization failures.+Для каждой подвески создаются ссылки на теги:
  
-`Context` currently contains no fields or methods and is not used by the implementation. +<code text> 
- +<deviceName>:Pdv<podvesNum>.T0 
-== 3Object Model == +<deviceName>:Pdv<podvesNum>.T1 
- +... 
-The runtime object hierarchy is: +<deviceName>:Pdv<podvesNum>.T<sensorCount-1>
- +
-<code+
-ArctrmModule +
-+-- ArctrmDevice +
-    +-- Podves +
-        +-- Sensor+
 </code> </code>
  
-`ArctrmModule` owns all configured devices in the `devices` list. It creates this list during construction and fills it in `loadModule()` by calling `ArctrmDevice.load(...)`.+Актуальность данных подвески определяется по тегу:
  
-`ArctrmDevice` is created from one entry in the `promauto.termo5` configuration map. The entry key becomes the device name. The device creates one `Ref` to `<deviceName>:SYSTEM.ErrorFlag` and one `Podves` object for each configured podves entry. +<code text
- +<deviceName>:Pdv<podvesNum>.Time
-`Podves` names are built as `<deviceName>.<podvesNum>`The numeric `podvesNum` is parsed from the podves configuration key by taking `substring(1)` and parsing it as an integer. For example, a key such as `p3` becomes podves number `3`. The configuration value becomes the podves description. +
- +
-`Sensor` instances are created inside each podves. For each sensor index from `0` to `sensorCount - 1`, `Podves` creates a tag name `Pdv<num>.T<index>` and the sensor links to `<deviceName>:<tagName>`. +
- +
-The database model mirrors part of this object model: +
- +
-<code> +
-PODVES +
-+-- TEMPER+
 </code> </code>
  
-Each `Podves` stores its database identifier in `Podves.id` after `ArctrmDataService.syncPodves(...)` returns the `PODVES.ID` value. Temperature archive rows in `TEMPER` refer to this identifier through `TEMPER.PODVES_ID`. +Данные считаются актуальнымиесли ссылка на тег существуета его значение находится в диапазоне от ''0'' до ''600'' включительно.
- +
-== 4. Execution Flow == +
- +
-=== Loading === +
- +
-`ArctrmModule.loadModule(conf)` reads module-specific configuration: +
- +
-- `database` into `svc.databaseModuleName`; +
-- `schema` into `svc.schema`; +
-- `size` into `svc.maxRecordCount`; +
-- `period`parsed by `Duration.parseMillis(...)` into `periodMs`; +
-- `sensorCount` into `sensorCount`; +
-- `promauto.termo5`passed to `ArctrmDevice.load(...)`. +
- +
-`size` must be greater than zero. `period` must parse successfully and must not result in `0`. `sensorCount` must be greater than zero. Any exception while parsing the period or loading devices is logged through `env.printError(...)`, and loading returns `false`. +
- +
-After successful configuration loading, the module creates a boolean tag named `connected` with initial value `false`. +
- +
-The inherited `AbstractModule.load(...)` also reads common module settings such as `enable`, `debug.logging`, flags, function tags, initial tag values, and tag flags. The Arctrm code directly relies on `enable` in lifecycle handling and in `getInfo()`. +
- +
-=== Preparation === +
- +
-`prepareModule()` calls `svc.prepare(sensorCount)`. The service resolves the configured database module, requires it to be a Firebird database module, loads `dbscr/dbscr.arctrm.yml`, builds table names for `EVENTLOG`, `PODVES`, and `TEMPER`, and builds the parameterized insert SQL for temperature rows. +
- +
-After the service is prepared, each `ArctrmDevice` prepares its podves and its `SYSTEM.ErrorFlag` reference. Each `Podves` prepares all of its sensor references. `prepareModule()` then sets `needInit = true` and resets `lastDt` to `null`. +
- +
-If `svc.prepare(...)` throws `ArctrmException`, the module logs the message and preparation returns `false`. +
- +
-=== Initialization === +
- +
-`executeModule()` calls `init()` at the beginning of a connected execution cycle. `init()` returns immediately if `needInit` is `false`. +
- +
-When initialization is required, `init()`: +
- +
-1. Executes the database initialization script through `svc.init(sensorCount)`. +
-2. Synchronizes all podves through `device.init(svc)`. +
-3. Loads the last saved timestamp from `TEMPER` into `lastDt` through `svc.getLastDt()`. +
-4. Writes an initialization event with `Constants.EVENT_ARCTRM_INIT` and an empty message. +
-5. Commits the database transaction. +
-6. Sets `needInit = false`. +
- +
-If no row exists in `TEMPER`, `svc.getLastDt()` returns `LocalDateTime.MIN`. +
- +
-=== Execute Cycle === +
- +
-Each `executeModule()` cycle starts by checking `svc.isConnected()`. If the database is not connected, the module sets the `connected` tag off, sets `needInit = true`, and returns `false`. +
- +
-When the database is connected, the module: +
- +
-1. Runs `init()` if needed. +
-2. Sets the `connected` tag on. +
-3. Gets the database server datetime from `svc.now()`. +
-4. Floors that timestamp to the configured period with `Duration.floorToPeriod(...)`. +
-5. Updates every configured device, podves, and sensor. +
-6. If the floored timestamp differs from `lastDt`, writes one temperature row per podves and runs archive cleanup. +
-7. Writes device status events for devices whose status changed. +
-8. Commits the transaction. +
-9. Assigns the floored timestamp to `lastDt`. +
- +
-All device and sensor communication in this flow is tag based. `Sensor.update()` calls `ref.linkIfNotValid()` and `ref.getInt()`; it does not communicate with serial ports or hardware protocols directly. +
- +
-=== Exceptions During Execution === +
- +
-Any exception thrown inside the connected execution block is caught by `executeModule()`. The module logs the exception, rolls back the data service, turns the `connected` tag off, sets `needInit = true`, and then returns `true`. +
- +
-=== Closedown === +
- +
-`closedownModule()` returns immediately when `enable` is `false`. Otherwise it sets the `connected` tag off and returns `true`. It does not close the database connection directly. +
- +
-=== Reload === +
- +
-`reload()` creates a temporary `ArctrmModule`, loads it, calls `closedown()` on the current instance, copies inherited settings from the temporary module, replaces `svc`, `devices`, `periodMs`, and `sensorCount`, then calls `prepare()`. +
- +
-The implementation does not copy old runtime values from the previous `connected` tag. It also does not explicitly move tags between tag tables during reload. +
- +
-== 5. Database Schema == +
- +
-The schema is defined in `src/main/resources/dbscr/dbscr.arctrm.yml` and executed as script `arctrm.init1` with the configured `schema` parameter. +
- +
-=== EVENTLOG === +
- +
-Purpose: stores module and device status events. +
- +
-Fields: +
- +
-- `ID INTEGER GENERATED BY DEFAULT AS IDENTITY`: primary key. +
-- `DT TIMESTAMP`: event timestamp. +
-- `EVENT_CODE SMALLINT NOT NULL`: event code from `Constants`. +
-- `MESSAGE VARCHAR(128)`: event message. +
- +
-Indexes: +
- +
-- `IX_EVENTLOG_DT` on `DT`. +
- +
-Writes: +
- +
-- `ArctrmModule.init()` writes `EVENT_ARCTRM_INIT` with an empty message. +
-- `ArctrmDevice.saveStatus()` writes `EVENT_CONNECTED`, `EVENT_DISCONNECTED`, or `EVENT_NOLINK` with the device name as the message when device status changes. +
- +
-The code does not implement retention cleanup for `EVENTLOG`. +
- +
-=== PODVES === +
- +
-Purpose: stores the configured podves names and descriptions. +
- +
-Fields: +
- +
-- `ID INTEGER GENERATED BY DEFAULT AS IDENTITY`: primary key. +
-- `NAME VARCHAR(32) NOT NULL`: unique podves name, built as `<deviceName>.<podvesNum>`. +
-- `DESCR VARCHAR(64)`: description from configuration. +
- +
-Constraints: +
- +
-- Unique constraint on `NAME`. +
- +
-Writes: +
- +
-- `ArctrmDataService.syncPodves(...)` uses Firebird `update or insert ... matching (name) returning id` to insert or update the row and return `ID`. +
- +
-The code does not delete `PODVES` rows that disappear from configuration. +
- +
-=== TEMPER === +
- +
-Purpose: stores periodic temperature snapshots. +
- +
-Fields from the database script: +
- +
-- `ID BIGINT GENERATED BY DEFAULT AS IDENTITY`: primary key. +
-- `PODVES_ID INTEGER`: foreign key to `PODVES`. +
-- `DT TIMESTAMP`: archive timestamp. +
- +
-Dynamic fields: +
- +
-- `T0`, `T1`, ..., `T<sensorCount - 1>` are added by `ArctrmDataService.ensureSensorColumns(sensorCount)` if they do not already exist. +
-- Each dynamic field is created as `INTEGER`. +
- +
-Relationships: +
- +
-- `TEMPER.PODVES_ID` references `PODVES.ID` with `ON DELETE CASCADE ON UPDATE CASCADE`. +
- +
-Indexes: +
- +
-- `IX_TEMPER_DT` on `DT`. +
- +
-Writes: +
- +
-- `ArctrmDataService.saveTemper(...)` inserts one row per podves with `PODVES_ID`, `DT`, and all sensor values. +
- +
-Cleanup: +
- +
-- `ArctrmDataService.deleteOldRecords()` deletes oldest rows by `ID` when the cached record count plus pending inserts exceeds `maxRecordCount`. +
- +
-== 6. Archive Algorithm == +
- +
-=== Sensor Collection === +
- +
-Each execution cycle calls `device.update()`, which calls `podves.update()`, which calls `sensor.update()` for every sensor. +
- +
-`Sensor.update()` behavior: +
- +
-- If the referenced tag cannot be linked, `value` becomes `Constants.VALUE_NOLINK` (`3000`). +
-- If the referenced integer value is below `Sensor.VALUE_LIMIT_LOW` (`-1000`), `value` becomes `Constants.VALUE_BROKEN` (`3010`). +
-- If the referenced integer value is above `Sensor.VALUE_LIMIT_HIGH` (`2000`), `value` becomes `Constants.VALUE_SHORTAGE` (`3011`). +
-- Otherwise, the referenced integer value is stored unchanged. +
- +
-The implementation does not preserve the raw sensor value when it is outside the allowed range. +
- +
-=== Timestamp Generation === +
- +
-The module uses the database server time, not JVM local time, for archive timestamps. `ArctrmDataService.now()` returns `db.getServerDatetime()`. +
- +
-The timestamp is aligned by `Duration.floorToPeriod(dateTime, periodMs)`. The alignment epoch is `1970-01-01T00:00`, and the method floors milliseconds from that epoch using `Math.floorDiv`. +
- +
-Supported period suffixes are: +
- +
-- `ms`: milliseconds; +
-- `s`: seconds; +
-- `m`: minutes; +
-- `h`: hours; +
-- `d`: days; +
-- no suffix: milliseconds. +
- +
-Negative periods, empty periods, null periods, invalid numeric values, and arithmetic overflow are rejected by `Duration.parseMillis(...)`. `ArctrmModule.loadModule(...)` additionally rejects a parsed period of `0`. +
- +
-=== Write Timing === +
- +
-Temperature rows are written only when the current floored timestamp differs from `lastDt`. +
- +
-During initialization, `lastDt` is loaded as the `DT` from the latest `TEMPER` row by descending `ID`. If there are no rows, `lastDt` is `LocalDateTime.MIN`. After a successful execution commit, `lastDt` is set to the current floored timestamp. +
- +
-When a write is due, each device writes all of its podves. Each podves writes exactly one row containing all current sensor values. +
- +
-=== Archive Maintenance === +
- +
-`ArctrmDataService.init(...)` initializes `recordCount` with `select count(*) from <TEMPER>`.+
  
-Every temperature insert increments `uncommitedCount`. Before commit, `deleteOldRecords()` computes:+Состояние связи с устройством определяется по тегу:
  
-<code> +<code text
-deleteCount = max(0, recordCount + uncommitedCount - maxRecordCount)+<deviceName>:SYSTEM.ErrorFlag
 </code> </code>
  
-If `deleteCount` is greater than zero, the service executes:+===== Принцип работы =====
  
-<code> +После подключения к базе модуль автоматически:
-delete from <TEMPER> order by id rows <deleteCount> +
-</code>+
  
-After commit, the cached count is updated as:+  - создаёт необходимые таблицы и индексы; 
 +  - добавляет отсутствующие колонки ''T0...Tn'' в таблицу ''TEMPER''; 
 +  - синхронизирует справочник подвесок; 
 +  - загружает дату последней архивной записи; 
 +  - записывает событие запуска архива.
  
-<code> +На каждом рабочем цикле модуль сначала проверяет ''Pdv<podvesNum>.Time'' каждой подвески. Значения датчиков считываются только для подвесок с актуальными данными. Время архивного среза берётся с сервера базы данных и округляется вниз до границы периода ''period''.
-recordCount = recordCount + uncommitedCount - deleteCount +
-</code>+
  
-Then `uncommitedCount` and `deleteCount` are reset to zero. Rollback also resets `uncommitedCount` and `deleteCount`but it does not recompute `recordCount`.+Если наступил новый периодв ''TEMPER'' записывается по одной строке для каждой подвески с актуальными данными. Для подвески с отсутствующим тегом ''Time'' или значением вне диапазона ''0...600'' строка не создаётся. При превышении лимита ''size'' удаляются строки с наименьшими значениями ''ID''.
  
-== 7. Runtime Information ==+===== База данных =====
  
-`ArctrmModule.getInfo()` returns runtime summary text.+^ Таблица ^ Назначение ^ 
 +| ''PODVES'' | Справочник подвесок: имя и описание
 +| ''TEMPER'' | Архив температурных срезовСодержит ''PODVES_ID'', ''DT'' и динамические колонки ''T0...Tn''. | 
 +| ''EVENTLOG'' | Журнал запуска архива, изменения состояния связи с устройствами и потери актуальности данных подвесок. |
  
-If the module is disabled, it returns:+Основные связи:
  
-<code> +<code text
-disabled+PODVES.ID <- TEMPER.PODVES_ID
 </code> </code>
  
-For enabled modules, the format is:+Одна строка ''TEMPER'' содержит значения всех датчиков одной подвески за один архивный период.
  
-<code> +===== Значения датчиков =====
-<optional NOT CONNECTED prefix>devices=<n> podves=<n> sensors=<n><optional nolinkSensors> cursize=<n> last=<timestamp-or-never> +
-</code>+
  
-Fields:+Обычные значения записываются без изменения. Ошибочные состояния сохраняются в тех же колонках ''T0...Tn'' как специальные числа.
  
-- `NOT CONNECTED!`: prepended with `ANSI.redBold(...)` when `tagConnected.getBool()` is `false`. +^ Значение ^ Состояние ^ 
-- `devices`: current size of the `devices` list+| ''3010'' | Значение ниже допустимого диапазона — короткое замыкание| 
-- `podves`: sum of `device.podvess.size()` across all devices+| ''3011'' | Значение выше допустимого диапазона — обрыв| 
-- `sensors`: sum of `podves.sensors.size()` across all podves. +| ''3013'' | Тег датчика не найден — ''NOLINK''|
-- `nolinkSensors`: shown only when at least one sensor has `Sensor.getValue() == Constants.VALUE_NOLINK`; the whole ` nolinkSensors=<n>` fragment is wrapped in `ANSI.redBold(...)`. +
-- `cursize`: current cached `TEMPER` record count from `svc.getRecordCount()`. +
-- `last`: `never` when `lastDt` is `null` or `LocalDateTime.MIN`; otherwise `lastDt` formatted as `yyyy-MM-dd HH:mm:ss`.+
  
-The current implementation does not include the database module name in `getInfo()`.+Допустимый диапазон обычного значения: от ''-1000'' до ''2000'' включительно.
  
-== 8. Configuration ==+===== События =====
  
-The module reads the following Arctrm-specific parameters in `ArctrmModule.loadModule(...)`.+^ Код ^ Событие ^ 
 +| ''0'' | Ссылка на ''SYSTEM.ErrorFlag'' не найдена
 +| ''1'' | Связь с устройством потеряна: ''SYSTEM.ErrorFlag = true''
 +| ''3'' | Данные подвески неактуальны: ссылка на ''Pdv<podvesNum>.Time'' не найдена или значение находится вне диапазона ''0...600''. | 
 +| ''99'' | Связь с устройством установлена. | 
 +| ''100'' | Инициализация архивации. |
  
-=== database ===+Для событий состояния устройства в поле ''MESSAGE'' записывается имя устройства, для события неактуальных данных — имя подвески в формате ''<deviceName>.<podvesNum>'', для события инициализации — пустая строка.
  
-Database module name.+Событие состояния устройства записывается только при изменении его состояния. Событие неактуальных данных формируется при первой потере актуальности, записывается при наступлении следующего архивного периода и не повторяется до восстановления актуальности и её следующей потери.
  
-Default: `db`.+===== Теги модуля =====
  
-Stored in `svc.databaseModuleName`. During preparation it must resolve to a module implementing `Database`and it must be an instance of `FirebirdDatabaseModule`.+^ Тег ^ Тип ^ Описание ^ 
 +| ''connected'' | boolean | Установленкогда модуль подключён к базе и рабочий цикл выполняется без ошибки|
  
-=== schema ===+===== Диагностика =====
  
-Database schema prefix used by the database script and by table-name generation.+Метод ''getInfo()'' возвращает краткую строку состояния, например:
  
-Default: `AT`. +<code text
- +devices=2 podves=sensors=18 cursize=30 last=2026-07-23 00:16:50
-Stored in `svc.schema` and passed to the database script as `schema=<value>`. +
- +
-=== size === +
- +
-Maximum number of records retained in `TEMPER`. +
- +
-Default: `1_000_000`. +
- +
-The value must be greater than zero. It is stored in `svc.maxRecordCount`. +
- +
-=== period === +
- +
-Archive period. +
- +
-Default: `1h`. +
- +
-The value is parsed by `Duration.parseMillis(...)`. Supported suffixes are `ms`, `s`, `m`, `h`, and `d`; values without suffix are interpreted as milliseconds. The parsed value must be greater than zero. +
- +
-=== sensorCount === +
- +
-Number of temperature sensors created for each podves. +
- +
-Default: `6`. +
- +
-The value must be greater than zero. It controls how many `Sensor` objects are created for every podves and how many dynamic `T*` columns are required in `TEMPER`. +
- +
-=== promauto.termo5 === +
- +
-Device configuration map consumed by `ArctrmDevice.load(...)`. +
- +
-Expected shape from implementation: +
- +
-<code> +
-promauto.termo5: +
-  <deviceModuleName>: +
-    p0: <description> +
-    p1: <description>+
 </code> </code>
  
-The device map key is used as the referenced module name. Each podves key must have at least two characters and must contain a numeric suffix after the first character, because the code parses `entry.getKey().substring(1)` as an integer. The code does not require the first character to be `p`; it only ignores the first character and parses the rest. +^ Поле ^ Описание ^ 
- +| ''devices'' | Количество настроенных устройств| 
-The repository does not contain an Arctrm-specific sample configuration, so the exact production configuration shape beyond this parsing behavior is unknown+| ''podves'' | Общее количество подвесок| 
- +| ''sensors'' | Общее количество датчиков| 
-=== enable === +| ''nolinkSensors'' | Количество датчиков без ссылкивыводится только при ненулевом значении| 
- +| ''cursize'' | Текущий размер таблицы ''TEMPER''учтённый модулем| 
-This is inherited from `AbstractModule`, not parsed in `ArctrmModule.loadModule(...)`+| ''last'' | Дата и время последнего архивного периода или ''never''|
- +
-Default: `true`+
- +
-When `enable` is false, inherited lifecycle methods skip preparation, execution, and closedown, and `getInfo()` returns `disabled`. +
- +
-== 9. Error Handling == +
- +
-=== Database Connection Failures === +
- +
-During preparation: +
- +
-- missing configured database module causes `ArctrmException("Database module is not found: ...")`; +
-- non-Firebird database causes `ArctrmException("Database type must be Firebird: ...")`; +
-- database script load failure causes `ArctrmException("Failed to load database script")`. +
- +
-`prepareModule()` catches `ArctrmException`, logs the message, and returns `false`. +
- +
-During execution: +
- +
-- if `svc.isConnected()` is false, the module turns the `connected` tag off, sets `needInit = true`, and returns `false`; +
-- `getInfo()` shows `NOT CONNECTED!` when the module's `connected` tag is false. +
- +
-=== Communication Failures === +
- +
-Arctrm detects device communication state through the referenced `SYSTEM.ErrorFlag` tag: +
- +
-- if the reference cannot be linked, device status is `EVENT_NOLINK`; +
-- if the tag is linked and `refErrorFlag.getBool()` is true, status is `EVENT_DISCONNECTED`; +
-- otherwise status is `EVENT_CONNECTED`. +
- +
-`ArctrmDevice.saveStatus(...)` writes an event only when this status differs from `lastStatus`. +
- +
-The module does not communicate with hardware directly and does not inspect serial-port errors. Those details belong to the referenced peripheral modules. +
- +
-=== Invalid Sensor Values === +
- +
-`Sensor.update()` treats values below `-1000` as `VALUE_BROKEN` (`3010`) and values above `2000` as `VALUE_SHORTAGE` (`3011`). These constants are archived in the same `TEMPER.T*` fields as normal sensor values. +
- +
-The implementation does not log invalid sensor values and does not write separate events for them. +
- +
-=== NOLINK Sensors === +
- +
-If a sensor reference cannot be linked, `Sensor.update()` stores `VALUE_NOLINK` (`3000`). `getInfo()` counts sensors whose current value equals this constant and displays `nolinkSensors=<n>` only when the count is greater than zero. +
- +
-=== Unexpected Exceptions === +
- +
-Unexpected exceptions during the connected execution block are caught by `executeModule()`. The module logs the exception, rolls back through `svc.rollback()`, turns the `connected` tag off, and marks `needInit = true` so the next connected cycle reinitializes. +
- +
-== 10. Performance Considerations == +
- +
-The implementation contains several simple optimizations: +
- +
-- The temperature insert SQL is generated once in `ArctrmDataService.prepare(...)` based on `sensorCount`. +
-- `saveTemper(...)` uses a prepared statement through `openPreparedStatement(...)`the inherited service reuses it until commit or rollback closes it+
-- Archive rows are written only when the period-aligned timestamp changes, not on every module execution cycle. +
-- `recordCount` is cached after initialization and updated on commit instead of running `count(*)` after every insert. +
-- Old records are deleted only when the cached count plus pending inserts exceeds `maxRecordCount`. +
-- Existing `TEMPER` column names are loaded once during initialization before missing sensor columns are added. +
- +
-The implementation does not use JDBC batch inserts. It executes one insert per podves when an archive write is due. +
- +
-== 11. Known Limitations == +
- +
-- Only Firebird databases are supported; `ArctrmDataService.prepare(...)` rejects any database module that is not a `FirebirdDatabaseModule`. +
-- `sensorCount` is global for the whole module. Different podves cannot have different sensor counts. +
-- `PODVES` rows are inserted or updatedbut rows removed from configuration are not deleted by Arctrm+
-- `EVENTLOG` has no cleanup logic. +
-- `TEMPER` cleanup is based on ascending `ID`, not on `DT`. +
-- The last saved timestamp is loaded from the row with the highest `ID`, not from the maximum `DT`. +
-- The cached `recordCount` can become stale if another process modifies `TEMPER` after initialization. +
-- `Sensor.value` defaults to Java's default integer value `0` until the first `Sensor.update()` call. +
-- `ArctrmDevice.lastStatus` defaults to `0`, which is also `EVENT_ARCTRM_INIT`; therefore the first device status event is written unless the current status also equals `0`. Current device statuses are `1`, `2`, or `3`. +
-- The module creates a `connected` tag with `tagtable.createBool(...)`; behavior when reloading and recreating this tag is governed by the tag table implementation, which is outside this module. +
-- No Arctrm-specific automated tests or sample Arctrm module configuration were found in the inspected source tree. +
-- The business meaning of event codes is limited to the constant names in `Constants`; no additional event-code documentation is present in the implementation. +
- +
-== 12. Possible Improvements == +
- +
-- Add unit tests for `Duration`, `Sensor.update()`, `getInfo()`, device status transitions, and archive cleanup count updates. +
-- Add an Arctrm sample configuration that shows the expected `promauto.termo5` structure. +
-- Use JDBC batch inserts for archive writes when many podves are configured. +
-- Add explicit quality/status columns or a separate status table instead of encoding sensor states as special integer values in `T*` fields. +
-- Add retention or cleanup for `EVENTLOG`. +
-- Add synchronization cleanup for `PODVES` rows no longer present in configuration, if that matches operational requirements. +
-- Load the last archive timestamp by maximum `DT` if archive continuity should be timestamp-based rather than insertion-order based. +
-- Periodically reconcile cached `recordCount` with the database if external writers or manual maintenance are expected. +
-- Expose more runtime diagnostics, such as database module name, period, maximum archive size, last exception text, or event write counts. +
- +
-== Source Coverage Review == +
- +
-The documentation above was checked against the current implementation of:+
  
-- `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` +
-- directly referenced behavior in `DatabaseProtoServiceImpl`, `Database`, `Ref`and peripheral termo tag creation.+
  
-Sections with intentionally stated unknowns or limited evidence:+===== Ограничения =====
  
-- Configuration: no Arctrm-specific sample configuration was found, so only the structure implied by `loadModule()` and `ArctrmDevice.load()` is documented+  * Поддерживается только Firebird. 
-- Purpose: the code describes the plugin as `termo value archiver`; any broader product or operator-facing purpose is not documented in source+  * Значение ''sensorCount'' является общим для всех подвесок
-- Database Schema: field business semantics beyond namestypes, constraints, and write paths are not documented in source. +  * Таблица ''EVENTLOG'' автоматически не очищается
-- Error Handling: hardware/protocol-level failures are not implemented by Arctrm and are only visible through referenced tags. +  * Удаление архивных строк выполняется по ''ID''а не по дате ''DT''.
-- Known Limitations: operational impact is inferred only from implementation behavior; deployment-specific requirements are unknown.+
  • doc/jroboplc/modules/arctrm.1784742991.txt.gz
  • Last modified: 2026/07/22 20:56
  • by denis