# CI-Eng-Sample A small, invented sample project that anyone with this repo can load into a CI Server **R1.03** test system. Skills use its names in their examples, and every skill task is re-tested against it so that no other project is needed. Built and tested on CI Server R1.03.00 (unlicensed test system), 2026-09-30; operator HMI, demo user and live-data feed added 2026-10-06. ## Load and remove Test systems only: loading writes to the CI Server database. CI Server must be running. From an elevated prompt, call the scripts by full path (a bare `load.cmd` may not be found from the current folder): ``` cmd /c "\examples\CI-Eng-Sample\load.cmd" cmd /c "\examples\CI-Eng-Sample\remove.cmd" ``` - Each step prints a summary and writes a `.qlo` listing; check that `Errors` is 0. - **Restart CI Server after loading** (stop, then start the CI Service): a new storage group stores no history until CI Server restarts. - `load.cmd` on a system that already has the project reports `Exists` and changes nothing. - Before `remove.cmd`, log out every `CIENG` session (Operator Interface and web): on 2026-10-06 an open session kept its user's authorization group in use (`DSSP-E-NODEL` … `USER_VAL`), and a retry in that state stopped the `DSSSDA03` process (`DUR-F-BAD_SIZE`) until CI Server was restarted. - `remove.cmd` takes about 1½ minutes: a storage group can only be deleted after its stop time has passed, so the script sets a stop time one minute ahead and waits. Removing the group also deletes its history. ## Current contents | Name | Kind | Details | |---|---|---| | `CI-Eng-Sample` | Section | Top level | | `CI-Eng-Sample.TANK1`, `.PUMP1`, `.SYSTEM` | Sections | Tank, pump, system monitoring | | `CI-Eng-Sample.TANK1.LEVEL` | Item, Real | %, format `999.9`, limits LL 10 / L 20 / H 80 / HH 90, scale 0–100, stored in `CIENG_EVENT`; alarming on (ack type `CIENG_ACK`, area `CIENG_TANKS`; `16_item_alarm_df.qli`) | | `CI-Eng-Sample.TANK1.INFLOW` | Item, Real | m3/h, format `9999.9`, scale 0–500, stored in `CIENG_EVENT` | | `CI-Eng-Sample.PUMP1.RUNNING` | Item, Boolean | | | `CI-Eng-Sample.PUMP1.MODE` | Item, Integer | 0 manual, 1 auto | | `CI-Eng-Sample.SYSTEM.CPU_LOAD` | Item, Real | Target for `intcpumon` | | `CI-Eng-Sample.SYSTEM.DISK_C` | Item, Real | Target for `intdskmon` (reports **used** %) | | `CI-Eng-Sample.SYSTEM.PING_LOCALHOST` | Item, Integer | Target for `inttcpmon` (1 = reachable) | | `CI-Eng-Sample.SYSTEM.PROC_ALM` | Item, Integer | Target for `intmon` (1 = `alm` process running) | | `CI-Eng-Sample.PUMP1.RUNNING.RUN_SECONDS` | Sub-item, Integrator | Running time in seconds (adds value × integration granule, `itm_int_secs` in `itm.sup`, default 1 s) | | `CI-Eng-Sample.TANK1.LEVEL.MAX` | Sub-item, Min-Max | Highest level seen | | `CI-Eng-Sample.DEMO` | Section | Tutorial examples | | `CI-Eng-Sample.DEMO.COUNTER` | Item, Integer | Counts 1…100, one step per second, written by `DEMO.INCR1` | | `CI-Eng-Sample.DEMO.FEED_ON` | Item, Boolean | 1 while the live-data feed runs in display mode (`feed\cieng_feed.ps1 -UntilFlag`); the display button *Stop* writes 0 | | `CIENG_1S` | Trigger group | Every second | | `CIENG_INCR` | Class (traditional) | Increments `DEMO.COUNTER`, wraps at 100; compiled on load | | `CI-Eng-Sample.DEMO.INCR1` | Object | Instance of `CIENG_INCR` | | `CI-Eng-Sample.TEMPLATES` | Section | Template items for classes | | `CI-Eng-Sample.TEMPLATES.TPL_REAL` | Item, Real | Template for items that objects create (format `9999.9`, scale 0–1000) | | `CIENG_TANK` | Class (traditional) | Prompt *Tank capacity (m3)*; signal `LEVEL` = `.LEVEL` (trigger on value); creates `.VOLUME` from `TPL_REAL`; volume = level × capacity / 100; logs when the level crosses 80; refuses capacity ≤ 0 | | `CI-Eng-Sample.TANK1.CALC` | Object | `CIENG_TANK`, capacity 200; creates section `TANK1.CALC` and item `TANK1.CALC.VOLUME` | | `CIENG_JFLOW` | Class (Java) | Attaches `.INFLOW`; creates `.sPerMin` = inflow / 60; logs each change | | `CI-Eng-Sample.TANK1.JFLOW` | Object | `CIENG_JFLOW`; creates item `TANK1.JFLOW.sPerMin` (mixed case, as the Java identifier) | | `CIENG_EVENT` | Item storage group | Event/Item, lifetime 1 week, rollover 1 day | | `CIENG_SCAN10S` | Item storage group | Scan/Time every 10 s with quality, priority 10 (source for aggregation), lifetime 1 day, rollover 1 hour; `TANK1.LEVEL`, `TANK1.INFLOW` | | `CIENG_AGG` | Item storage group | Aggregation 30 min + hourly, lifetime 4 weeks, rollover 1 week; `TANK1.LEVEL` max/min/average (read with `INTERPOLATION = "30Minutes"` / `"Hour"`, `ATTRIBUTE = "Maximum"` …) | | `CI-Eng-Sample.PLC1` | Section | Modbus loopback station | | `CI-Eng-Sample.PLC1.COMM_OK` | Item, Boolean | Status item of station `CIENG_PLC` (1 = communicating, status 22 when off-scan) | | `CI-Eng-Sample.PLC1.SETPOINT` / `.READBACK` | Items, Integer | Written to holding register 1 (`RO:1`) / read from input register 1 (`RI:1`), same memory | | `CI-Eng-Sample.PLC1.CMD` / `.FEEDBACK` | Items, Boolean | Written to coil 1 (`DO:1`) / read from input bit 1 (`DI:1`), same memory | | `CIENG_MB_LINE` | Modbus line (TCP/IP) | Equipment manager `EQPCIENG` (created and started by loading the line) | | `CIENG_PLC` | Modbus station | Open Modbus/TCP to `localhost:5020`, unit 1, output method *On change and connect* | | `CIENG_SCAN1S` | Modbus scan type | 1000 ms | | `CI-Eng-Sample.UA` | Section | OPC UA loopback station | | `CI-Eng-Sample.UA.COMM_OK` | Item, Boolean | Status item of station `CIENG_UA` (1 = connected) | | `CI-Eng-Sample.UA.LEVEL_COPY` | Item, Real | `TANK1.LEVEL` read back through CI Server's own OPC UA server | | `CI-Eng-Sample.UA.SRV_STATE` | Item, Integer | OPC UA server state node (ns 0, i=2259), 0 = Running | | `CIENG_UA_LINE` | OPC UA client line | Client process `OPXCIENG` (created and started by loading the line) | | `CIENG_UA` | OPC UA station | `opc.tcp://localhost:34493`, Basic256Sha256 / SignAndEncrypt, anonymous; subscription `CIENG_SUB` (1 s); points `LEVEL`, `SRV_STATE` | | OPC visible | Flags | Sections `CI-Eng-Sample`, `TANK1` and item `TANK1.LEVEL` are OPC visible, item read-only (`12_opc_visible_df.qli`) | | `CIENG_ACK` | Acknowledgement type | Every alarm needs an acknowledgement; return to normal automatic only if the alarm was acknowledged (same matrix as `MANUAL`) | | `CIENG_TANKS` | Area of interest | Label for tank items (`TANK1.LEVEL` `AOI_1`) | | `CIENG_TANK1` | Alarm selection area | `SECTION = "CI-Eng-Sample.TANK1"`, compiled on load | | `CIENG_VIEWER` | Authorization group | Read-only: `ITEM_READ`, `SECTION_READ`, `ALARM_CURRENT_READ`, `ALARM_CHRONO_READ` | | `CIENG` | User | Demo user, **no password**, factory group `OPERATOR`; logs on to layout `CIENG_Operator` with display `CIENG_Overview` (desktop and web). The only user the sample creates; `ADMIN` is not changed. | | `CIENG_FILE` | Printer device + destination | FILE output to `%TLS_LST_PATH%\cieng_report.txt` (overwritten by each printed report); destination visible to operators | | `CIENG_TANK1` | Report | Values of the `TANK1` items + last-hour statistics of one item (ad-hoc parameter chosen in an item browser, default `LEVEL`); daily 06:00 local time and ad hoc; output `CIENG_FILE`; lifetime 1 week | | `CIENG_INFLOW_HI` | Report | Generated 5 s after `TANK1.INFLOW` rises above 400; output `CIENG_FILE` | | `CIENG_HTML` | Report | Ad hoc; `TANK1` values as an HTML table (cursor loop) plus the logbook text | | `CIENG_AUDIT` | Report | Ad hoc; audit trail events on `CI-Eng-Sample.*` of the last hour | | `CIENG_ITEMS` | Audit selection group | Expression `sbid >= "CI-Eng-Sample." and sbid < "CI-Eng-Sample/"`, compiled on load | Items created by objects (`TANK1.CALC.VOLUME`, `TANK1.JFLOW.sPerMin`) and their sections are deleted with the objects; they are not in `02_item_df.qli`. All items except `PLC1.*` are internal (no station). The `PLC1` station talks to CI Server's own Modbus slave mirror (see below), so no PLC is needed either. New items start with status 13 (`NOT INIT`) until a value is written. ## Files | File | Purpose | |---|---| | `01_section_df.qli` | Sections (`SECTION_DF`); parents before children | | `02_item_df.qli` | Items (`ITEM_DF`) | | `02b_sub_item_df.qli` | Sub-items (`SUB_ITEM_DF`) | | `03_his_group_df.qli` | Item storage groups (`HIS_GROUP_DF`): event, scan, aggregation | | `04_item_his_df.qli` | Items in the storage group (`ITEM_HIS_DF`, name `:`) | | `05_trigger_df.qli`, `06_class_df.qli`, `07_object_df.qli` | Trigger group; classes (source in the `METHOD` memo, `@"` … `"@`, `COMPILE` 1; Traditional and Java); objects (`ATTR_VALUE_1` = prompt answer) | | `08_auth_group_df.qli` | Authorization group (`AUTH_GROUP_DF`) | | `08b_user_df.qli` | Demo user `CIENG` (`USER_DF`); `PASSWORD ""` given explicitly - without the field the user has no password set and every logon fails (`DSS-E-UNK_USER`) | | `09_modbus_line_df.qli`, `10_modbus_station_df.qli`, `11_item_eqp_df.qli` | Modbus line; station, scan type, points; items connected to points | | `12_opc_visible_df.qli` | OPC visible / read flags (loaded with `dssqld -m`) | | `13_opcua_line_df.qli`, `14_opcua_station_df.qli`, `15_item_opcua_df.qli` | OPC UA client line; station, subscription, points; connected items | | `01b_alarm_df.qli` | Alarm definitions: acknowledgement type, area of interest, alarm selection area (`ALARM_ACK_DF`, `ALARM_AOI_DF`, `ALARM_ASA_DF`) | | `16_item_alarm_df.qli` | Switch on alarming for `TANK1.LEVEL` (loaded with `dssqld -m`) | | `17_printer_df.tpl`, `17_printer_df.ps1` | Report file device and destination; the script fills in this system's node name and `%TLS_LST_PATH%` and writes `17_printer_df.qli` (not in the repo) | | `18_report_df.qli` | Reports (`REPORT_DF`, RQL in the `QUERY` memo) | | `19_audit_df.qli` | Audit selection group (`AUDIT_SELGRP_DF`) and audit report | | `audit/adtlogci.sup` | Setup file for an extra audit event logger `ADTLOGCI` filtered on `CIENG_ITEMS` (copied to `tls\sup` by an administrator, not by `load.cmd`) | | `83a_stop_report_df.qli` | Clears the schedule of `CIENG_TANK1` before removal (a scheduled report cannot be deleted) | | `equipment/eqpciengslv_start.cmd`, `_stop.cmd` | Start/stop the Modbus slave mirror (copy to `tls\com`) | | `76_`…`93_remove_*.qli` | Names to delete, in reverse order (audit `79`, reports and printer `83`/`84` first, alarm definitions last, `93`) | | `load.cmd`, `remove.cmd` | Run the steps in the right order | | `19b_audit_item_df.qli` | *Audit trailing wanted* for `PUMP1.MODE` (loaded with `dssqld -m`) | | `displays/CIENG_*.xml` | The operator HMI: 8 displays in HTML mode (desktop and web), see *Displays and layout* | | `displays/generator/` | `build.py` + `cixml.py` write the `CIENG_*.xml` files (Python 3 + Pillow); `README.md` there describes the tool | | `layouts/CIENG_Operator.xml`, `layouts/layout.ps1` | Layout block (`CIENG_Operator`, `CIENG_Popup`) and the script that adds/removes it in `Layouts.xml` | | `displays/add_trend_spares.ps1` | Pads every trend in a display file to 50 visible pens (web workaround, ISS-041); safe to run again. Kept as a tool; the sample trend is not padded | | `displays/deploy_displays.cmd`, `remove_displays.cmd` | Copy the displays into the operator interface and export their HTML versions / delete them | | `feed/cieng_feed.ps1`, `cieng_feed_start.cmd`, `install_feed.ps1` | Live-data feed (test values for the tank, pump and system items), its start wrapper for the display button, and the installer into `C:\Users\Public\CI-Eng-Sample\feed` | | `monitoring/*.sup.txt` | Setup-file lines for the monitoring processes (not applied by `load.cmd`) | ## Modbus slave mirror (for station CIENG_PLC) `CIENG_PLC` reads and writes CI Server's own Modbus slave: `eqpmdcmem` (slave memory) and `eqpmdcslvtcp` (Modbus TCP on port 5020). Without them the station reports bad communication (`COMM_OK` = 0) but everything loads. To start them with CI Server, an administrator copies `equipment/eqpciengslv_start.cmd` and `eqpciengslv_stop.cmd` to `%TLS_ROOT_PATH%\tls\com`; for a quick test they can also be started by hand from a normal prompt: ``` start eqpmdcmem -n EQPMDCMEM start eqpmdcslvtcp -n EQPMDCSLVTCP -p 5020 ``` Remove the two files from `tls\com` when the sample is removed. See skill `equipment`, task `modbus-station.md`. ## OPC UA loopback (station CIENG_UA) `CIENG_UA` is CI Server's OPC UA client connected to CI Server's own OPC UA server, so no external server is needed. The **first** connection on a system needs a one-time certificate exchange, done by the user (skill `data-access`, task `opcua-client.md` §4): 1. After loading, the client logs `OPXC-E-CER_NO_TRU`; trust the server certificate in `%TLS_ALLUSERS_PATH%\pki\opxuac\pkiclient\rejected` (Engineering Module, or move it to `…\pkiclient\trusted\certs`). 2. The server then rejects the client; move `CI_SERVER_OPCUA_CLIENT_OPXCIENG […].der` from `%TLS_ALLUSERS_PATH%\pki\opxuas\pkiserver\rejected` to `…\pkiserver\trusted\certs` (PowerShell: `Move-Item -LiteralPath`, the name contains brackets). Then `UA.COMM_OK` = 1 and `UA.LEVEL_COPY` follows `TANK1.LEVEL`. The trust is kept when the sample is removed and loaded again. ## Alarms (TANK1.LEVEL) `TANK1.LEVEL` raises HIGH above 80 (Alarm 2, priority 2) and HIGH HIGH above 90 (Alarm 3, priority 1). To try it on a test system: `itmval -p -n CI-Eng-Sample.TANK1.LEVEL -v 85`, export `ALARM_CURRENT` (filter on `ITEM_NAME`), acknowledge with `dssqld -m` on `ALARM_CURRENT` (`NAME`, `ACK` 1), write the level back below 80. See skill `alarm-management`, task `item-alarming.md`. ## Displays and layout (optional) A dark dashboard-style operator HMI with its own layout (no AOG displays). All displays are in HTML mode, so the same files run in the Operator Interface (desktop) and in the browser (web). No admin rights needed. After `load.cmd`: 1. `displays\deploy_displays.cmd` copies the 8 displays into the operator interface and creates their HTML versions. 2. `powershell -ExecutionPolicy Bypass -File layouts\layout.ps1` adds the layouts `CIENG_Operator` and `CIENG_Popup` to `Layouts.xml` (first run saves `Layouts-before-CIENG.xml`). **Restart the Operator Interface** afterwards: it reads the layouts only at start. 3. `powershell -ExecutionPolicy Bypass -File feed\install_feed.ps1` installs the live-data feed (see *Live data*). 4. Log in as **`CIENG`** (no password): Operator Interface, or the web at `http://localhost:8080/mobileoperator/`. Other users: `?l=CIENG_Operator` (web) or `jwuipoi.cmd -layout CIENG_Operator` (desktop). | Display | Where | Shows | |---|---|---| | `CIENG_Header` | top frame | KPIs (level coloured by its limits, inflow, volume, pump, PLC / OPC UA communication), live data *Start* / *Stop* with lamp, *Tank 1* faceplate button, last update | | `CIENG_Overview` | tab *Overview* | Tank 1 mimic (inflow pipe, level fill coloured by limits, limit marks, pump, outlet pipes), volume / flow / maximum / counter / run time, three ring gauges, micro trend; click the tank for the faceplate | | `CIENG_Control` | tab *Process control* | Slider, spinner and input field on `PLC1.SETPOINT` with Modbus readback; toggle and *Set ON / OFF* on `PLC1.CMD` with `FEEDBACK` lamp; pump mode combo box and *Manual* / *Auto* buttons (audited), run check box; level test slider and buttons LL / L / Normal / H / HH | | `CIENG_Trends` | tab *Trends* | Trend of level and inflow (10 min, 2 pens) and micro trends for level, inflow and CPU | | `CIENG_Alarms` | tab *Alarms* | Current alarms and alarm history of `CIENG_TANK1` | | `CIENG_System` | tab *System* | CPU and disk gauges, communication and process lamps with status, OPC UA loopback, report component | | `CIENG_AlarmBanner` | bottom frame | Tank 1 alarm lamp, *Alarm list* button, 4-row alarm banner | | `CIENG_TankFaceplate` | layout `CIENG_Popup` | Level, status, limits, maximum, last update, micro trend, *Trends* / *Alarms* / *Close* | Screen sizes: designed for 1920 x 1080. The desktop opens the tabs with `stretchDisplay`, so they fill any monitor (slightly stretched where the ratio differs); maximise the window, it opens over the taskbar. The web client does not zoom displays: use full screen (F11) and the browser zoom (estimates, not tested: about 100 % at 1920 x 1080, 80 % at 1536 x 864, 70 % at 1366 x 768). Known limits (R1.03.00): the trend draws no line in the browser (a web trend needs 50 pens, ISS-041; the 2-pen trend keeps the desktop hairline box readable); the empty part of the alarm lists stays white; a tab opened by clicking the tab strip itself keeps its zoom until it is opened once from the toolbar, menu or F1–F6. Remove: `layouts\layout.ps1 -Remove` (restores the file exactly), `displays\remove_displays.cmd` and `feed\install_feed.ps1 -Remove`. Close the displays in the Edit Module first. See skill `graphics`. ## Live data The sample items are internal: after a CI Server start they stay `NOT INIT`, so displays, trends and alarms show nothing until values are written. `feed\cieng_feed.ps1` writes them every 5 s: | Option | Effect | |---|---| | (none) | Runs `-Seconds` (default 3600): `TANK1.LEVEL` 50 ± 25 % (inside the limits), `TANK1.INFLOW` 250 ± 150 m3/h; `PUMP1.RUNNING` follows the inflow in auto mode | | `-Alarms` | `TANK1.LEVEL` 50 ± 33 %: crosses L 20 and H 80, so alarms appear (manual acknowledgement) | | `-System` | Also writes `SYSTEM.CPU_LOAD` / `DISK_C` / `PING_LOCALHOST` / `PROC_ALM` with this PC's values; skipped while the monitoring processes run (they write the same items) | | `-UntilFlag` | Runs while `DEMO.FEED_ON` = 1 (display buttons *Start* / *Stop*) | The header button *Start* runs `C:\Users\Public\CI-Eng-Sample\feed\cieng_feed_start.cmd` on the server (as the CI Server service account `CIX_PROCESS`; the factory group `OPERATOR` is enough), which starts the feed with `-UntilFlag -Alarms -System`. Only one copy runs at a time. Log: `%TLS_LST_PATH%\cieng_feed.log`. Stop the feed before manual tests with `itmval` (it overwrites the values within 5 s). After a CI Server restart the *Live data* lamp can stay green although no feed runs: click *Start*. ## Monitoring processes (optional) `monitoring/` holds the lines to add to the setup files in `%TLS_ROOT_PATH%\tls\sup\` so that `intcpumon`, `intdskmon`, `inttcpmon`, `intmon` and `intspawn` write into the `SYSTEM` items. These change the system's own setup files: back the files up first and restore them after testing. While they run, the live-data feed leaves the `SYSTEM` items alone (`-System` is skipped). See the `system-management` skill, task `monitoring-processes.md`. ## Reports No admin rights needed. `load.cmd` creates the device, destination and three reports; each report gets its own storage group of type *Report*. Try them (skill `reporting`, task `define-reports.md`): - Ad hoc: `REPORT_DF` `NAME,PARAM_VALUE_1,GENERATE` = `"CIENG_TANK1","CI-Eng-Sample.TANK1.INFLOW",1` with `dssqld -m`, or in the Engineering Module *Configuration > Reporting > Reports* › right-click › *Generate*. The output lands in `REPORT_VAL`; runs from the Engineering Module also print to `%TLS_LST_PATH%\cieng_report.txt` (runs from `dssqld` do not print, ISS-045). - Trigger: `itmval -p -n CI-Eng-Sample.TANK1.INFLOW -v 450` → `CIENG_INFLOW_HI` 5 s later; write a value below 400 before the next test. - `CIENG_TANK1` also runs every day at 06:00 (local time). `remove.cmd` clears the schedule, removes the reports (with their generated reports and logbook files), the destination and device, and deletes `cieng_report.txt`. ## Audit trail No admin rights needed for `19_audit_df.qli`. Every change made by `load.cmd`, `dssqld` or the Engineering Module is in the audit trail (skill `audit-trail`): - Generate `CIENG_AUDIT` (ad hoc, like the other reports) to list the last hour of changes on `CI-Eng-Sample.*`: time, dataset, field, action, source, user, old and new value. - Operator writes: with the displays deployed, click *Manual* / *Auto* on `CIENG_Control`; each click is stored as `ITEM.VALUE.MODIFY.OPER-INTERFACE` because `PUMP1.MODE` has *Audit trailing wanted* (`19b_audit_item_df.qli`). - Export: `dssqld -e audit.qlo -d AUDIT_TRAIL` (whole trail) or a qli with `@SELECT` (keep `NAME` in `@FIELDS`). - Event logger (optional, admin): copy `audit\adtlogci.sup` to `%TLS_ROOT_PATH%\tls\sup\`, then run `adtlog -n adtlogci` in a console; it prints each new CI-Eng-Sample event on one line (Ctrl+C to stop). Delete the file from `tls\sup` when the sample is removed. Audit records themselves cannot be deleted; `remove.cmd` removes only the report and the selection group. ## Planned additions | Phase | Adds | |---|---| | 10 | A symbol (the faceplate popup `CIENG_TankFaceplate` exists since 2026-10-06) | ## Rules - Test systems only; invented names only, no real plant or customer data. - Keep the load order: sections → template items → classes (compiled) → objects → other items → storage groups → items in storage groups (IM 36K10J10 §5.6). Keep `remove.cmd` in reverse order. - Every file notes the CI Server version it was built and tested on.