diff --git a/examples/CI-Eng-Sample/.gitignore b/examples/CI-Eng-Sample/.gitignore index 3f93a26..7742c18 100644 --- a/examples/CI-Eng-Sample/.gitignore +++ b/examples/CI-Eng-Sample/.gitignore @@ -1 +1,2 @@ *.qlo +17_printer_df.qli diff --git a/examples/CI-Eng-Sample/17_printer_df.ps1 b/examples/CI-Eng-Sample/17_printer_df.ps1 new file mode 100644 index 0000000..af26ea5 --- /dev/null +++ b/examples/CI-Eng-Sample/17_printer_df.ps1 @@ -0,0 +1,16 @@ +# Build 17_printer_df.qli from 17_printer_df.tpl for this system (CI-Eng-Sample, CI Server R1.03.00). +# The printer device needs the CI Server node name (":FILE") and a literal output path, +# so they are filled in here: node name from LOCAL_NODE_DF, path from %TLS_LST_PATH%. +# Called by load.cmd; needs CI Server running. +$ErrorActionPreference = 'Stop' +Set-Location -LiteralPath $PSScriptRoot +$null = & dssqld -e 17_node.qlo -d LOCAL_NODE_DF -c +$node = $null +foreach ($row in (Import-Csv 17_node.qlo)) { if ($row.NAME -eq 'THIS_NODE') { $node = $row.NODE_NAME } } +Remove-Item 17_node.qlo +if (-not $node) { throw 'Node name not found in LOCAL_NODE_DF' } +$lst = $env:TLS_LST_PATH +if (-not $lst) { throw 'TLS_LST_PATH is not set' } +$text = (Get-Content -Raw 17_printer_df.tpl).Replace('', $node).Replace('', $lst) +Set-Content -Encoding ascii -NoNewline -Path 17_printer_df.qli -Value $text +Write-Output "17_printer_df.qli: node $node, path $lst" diff --git a/examples/CI-Eng-Sample/17_printer_df.tpl b/examples/CI-Eng-Sample/17_printer_df.tpl new file mode 100644 index 0000000..c8b64fd --- /dev/null +++ b/examples/CI-Eng-Sample/17_printer_df.tpl @@ -0,0 +1,23 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! CI-Eng-Sample report output device and destination, CI Server R1.03.00. +! TEMPLATE: load.cmd replaces with the CI Server node name (LOCAL_NODE_DF NODE_NAME) and +! with %TLS_LST_PATH%, writes 17_printer_df.qli and loads that. The output type must be +! ":FILE" (PRINTER_TYPE_DF) and the path must be a literal path (no %variables%). +! Every printed report overwrites \cieng_report.txt. +@FIELDS +NAME,DESCRIPTION,OUTPUT_TYPE,PARAMETERS,SUSPEND + +@PRINTER_DEV_DF +"CIENG_FILE","CI-Eng-Sample report file",":FILE","PATH,"""",FILE,""cieng_report.txt"",OVERWRITE,""Yes""",0 + +! Destination used by the sample reports; visible to operators (PRINTER_OPER_DEST_DF). +@FIELDS +NAME,DESCRIPTION,PRIMARY_DEVICE,POLICY,ALLOW_OPERATOR,PRIORITY + +@PRINTER_DEST_DF +"CIENG_FILE","CI-Eng-Sample report file","CIENG_FILE","None",1,0 diff --git a/examples/CI-Eng-Sample/18_report_df.qli b/examples/CI-Eng-Sample/18_report_df.qli new file mode 100644 index 0000000..0e85714 --- /dev/null +++ b/examples/CI-Eng-Sample/18_report_df.qli @@ -0,0 +1,82 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! CI-Eng-Sample reports (REPORT_DF), CI Server R1.03.00. Load after 17_printer_df (destination). +! Each report gets a storage group of type Report with the same name (lifetime = LIFE_TIME, +! rollover = GENERATION_INTERVAL, so a schedule must be at least 10 minutes). +! Report names: at most 16 characters. +! +! CIENG_TANK1 : values of all TANK1 items plus last-hour statistics of one item (parameter +! @p0, picked from a list of TANK1 items); daily at 06:00 local time and ad hoc. +! The activation time lies in the past; CI Server moves it to the next 06:00. +! CORRECT_DAYLIGHT 1 keeps 06:00 across daylight-saving changes (with 0 the +! interval runs in UTC and 06:00 in January became 05:00 in October). +! CIENG_INFLOW_HI: generated 5 s after TANK1.INFLOW rises above 400 (only when the condition +! becomes true, not again while it stays above). +! CIENG_HTML : HTML output (CONTAINS_HTML is set automatically), cursor loop, logbook text. +@FIELDS +NAME,DESCRIPTION,PRIORITY,ADHOC_ENABLED,LOGBOOK_ENABLED,LIFE_TIME,ACTIVATION_TIME,GENERATION_INTERVAL,CORRECT_DAYLIGHT,DEST_NAME,ADHOC_DEST,PARAM_PROMPT_1,QUERY + +@REPORT_DF +"CIENG_TANK1","CI-Eng-Sample tank 1 values",0,1,1,"1 Weeks","01-01-2026 06:00:00","1 Days",1,"CIENG_FILE","CIENG_FILE","[ITEM:NAME like ""CI-Eng-Sample.TANK1.*""]Item for statistics:",\ +@" +/* CI-Eng-Sample: current values of tank 1, then last-hour statistics of one item */ +column v as '9999.9' heading 'Value'; +select name, item_value v, status from item_val where name like 'CI-Eng-Sample.TANK1.%'; +select ''; +if @p0 = '' then + set @item 'CI-Eng-Sample.TANK1.LEVEL'; +else + set @item @p0; +endif; +select 'Last hour: ' || @item; +column n heading 'Samples'; +column mi as '9999.9' heading 'Min'; +column ma as '9999.9' heading 'Max'; +column av as '9999.9' heading 'Average'; +select count(item_value) n, min(item_value) mi, max(item_value) ma, avg(item_value) av + from item_history where item_name = @item and sample_time > @date - 3600; +"@ + +@FIELDS +NAME,DESCRIPTION,PRIORITY,ADHOC_ENABLED,LIFE_TIME,TRIGGER_ITEM,TRIGGER_CON_1,TRIGGER_VAL_1,DELAY,DEST_NAME,ADHOC_DEST,QUERY + +@REPORT_DF +"CIENG_INFLOW_HI","CI-Eng-Sample tank 1 inflow above 400",0,1,"1 Weeks","CI-Eng-Sample.TANK1.INFLOW","Greater Than",400,"5 Seconds","CIENG_FILE","CIENG_FILE",\ +@" +column t as 'DD-MMM-YYYY hh:mm:ss'; +select 'Tank 1 inflow high: ' || char(gpv_value('CI-Eng-Sample.TANK1.INFLOW', 0), '%.1f') || ' m3/h at', @date t; +"@ + +@FIELDS +NAME,DESCRIPTION,PRIORITY,ADHOC_ENABLED,LOGBOOK_ENABLED,LIFE_TIME,ADHOC_DEST,LOGBOOK,QUERY + +@REPORT_DF +"CIENG_HTML","CI-Eng-Sample tank 1 as HTML",0,1,1,"1 Weeks","CIENG_FILE",@" +Sample logbook text +"@,\ +@" +/* HTML output: no header/footer, no page breaks */ +header ''; +footer ''; +set @page_size 0; +set @width 512; +select '

CI-Eng-Sample tank 1

'; +set @n ''; +set @v 0.; +declare @c is select name, item_value from item_val where name like 'CI-Eng-Sample.TANK1.%'; +open @c; +loop + fetch @c into @n, @v; + if @rqlcount = 0 then + exit; + endif; + select ''; +endloop; +close @c; +declare; +select '
' || @n || '' || char(@v, '%.1f') || '

Logbook: ' || logbook_text('CIENG_HTML') || '

'; +"@ diff --git a/examples/CI-Eng-Sample/83_remove_report_df.qli b/examples/CI-Eng-Sample/83_remove_report_df.qli new file mode 100644 index 0000000..3027d7a --- /dev/null +++ b/examples/CI-Eng-Sample/83_remove_report_df.qli @@ -0,0 +1,15 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! Remove CI-Eng-Sample reports. Also deletes each report's storage group (its generated +! reports) and logbook file (tls\dat\.lbk). +@FIELDS +NAME + +@REPORT_DF +"CIENG_TANK1" +"CIENG_INFLOW_HI" +"CIENG_HTML" diff --git a/examples/CI-Eng-Sample/83a_stop_report_df.qli b/examples/CI-Eng-Sample/83a_stop_report_df.qli new file mode 100644 index 0000000..e406d5c --- /dev/null +++ b/examples/CI-Eng-Sample/83a_stop_report_df.qli @@ -0,0 +1,13 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! Stop the schedule of CIENG_TANK1 (load with dssqld -m before 83_remove_report_df): +! a report with an active schedule cannot be deleted (RPT-E-ACTIVE). +@FIELDS +NAME,ACTIVATION_TIME,GENERATION_INTERVAL + +@REPORT_DF +"CIENG_TANK1","","" diff --git a/examples/CI-Eng-Sample/84_remove_printer_df.qli b/examples/CI-Eng-Sample/84_remove_printer_df.qli new file mode 100644 index 0000000..a38b730 --- /dev/null +++ b/examples/CI-Eng-Sample/84_remove_printer_df.qli @@ -0,0 +1,19 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! Remove the CI-Eng-Sample report destination, then its device (after 83_remove_report_df). +! The output file %TLS_LST_PATH%\cieng_report.txt is not deleted (remove.cmd deletes it). +@FIELDS +NAME + +@PRINTER_DEST_DF +"CIENG_FILE" + +@FIELDS +NAME + +@PRINTER_DEV_DF +"CIENG_FILE" diff --git a/examples/CI-Eng-Sample/README.md b/examples/CI-Eng-Sample/README.md index 146dc5e..496b55a 100644 --- a/examples/CI-Eng-Sample/README.md +++ b/examples/CI-Eng-Sample/README.md @@ -73,6 +73,10 @@ cmd /c "\examples\CI-Eng-Sample\remove.cmd" | `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`. No users are created by the sample. | +| `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, pick list of `TANK1` items, 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 | 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`. @@ -96,8 +100,11 @@ New items start with status 13 (`NOT INIT`) until a value is written. | `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) | +| `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 (alarm definitions last, `93`) | +| `76_`…`93_remove_*.qli` | Names to delete, in reverse order (reports and printer `83`/`84` first, alarm definitions last, `93`) | | `load.cmd`, `remove.cmd` | Run the steps in the right order | | `displays/CIENG_Tank.xml` | Display (HTML mode): title, number field on `TANK1.LEVEL`, alarm overview, trend of `TANK1.LEVEL`, button *Operator layout* | | `displays/CIENG_Menu.xml` | Navigation display (200 x 700): *Tank 1*, *Current Alarm* (main frame), *Tank 1 popup* (layout `popup`) | @@ -171,12 +178,26 @@ Remove: `layouts\layout.ps1 -Remove` (restores the file exactly) and These change the system's own setup files: back the files up first and restore them after testing. 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 `%TLS_LST_PATH%\cieng_report.txt` and in `REPORT_VAL`. +- 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`. + ## Planned additions | Phase | Adds | |---|---| | 10 | A symbol and a faceplate popup (displays and the operator layout exist) | -| 11 | A report | ## Rules diff --git a/examples/CI-Eng-Sample/load.cmd b/examples/CI-Eng-Sample/load.cmd index c3d7666..c8f2dac 100644 --- a/examples/CI-Eng-Sample/load.cmd +++ b/examples/CI-Eng-Sample/load.cmd @@ -37,3 +37,8 @@ dssqld -i 14_opcua_station_df.qli -l 14_opcua_station_df.qlo dssqld -i 15_item_opcua_df.qli -l 15_item_opcua_df.qlo rem Alarming: switch on alarms for TANK1.LEVEL (modify existing record) dssqld -m 16_item_alarm_df.qli -l 16_item_alarm_df.qlo +rem Reports: file output device and destination (17_printer_df.qli is built for this node +rem from 17_printer_df.tpl), then the report definitions. +powershell -NoProfile -ExecutionPolicy Bypass -File 17_printer_df.ps1 +dssqld -i 17_printer_df.qli -l 17_printer_df.qlo +dssqld -i 18_report_df.qli -l 18_report_df.qlo diff --git a/examples/CI-Eng-Sample/remove.cmd b/examples/CI-Eng-Sample/remove.cmd index bab8255..9ccce96 100644 --- a/examples/CI-Eng-Sample/remove.cmd +++ b/examples/CI-Eng-Sample/remove.cmd @@ -1,10 +1,17 @@ @echo off rem Remove CI-Eng-Sample from a CI Server R1.03 TEST system (also deletes its history). rem Run in an elevated prompt while CI Server is running; call this script by full path. -rem Order: object, class, trigger group, storage links, stop + delete storage groups, items, sections (children first). +rem Order: reports, printer, OPC UA, Modbus, auth group, object, class, trigger group, storage links, stop + delete storage groups, items, sections (children first). rem A storage group with an active unit cannot be deleted (HIS-E-GROUPHASUNT), so the rem group first gets a stop time one minute ahead; the script waits until it has passed. cd /d "%~dp0" +rem Reports first (they refer to TANK1.INFLOW and the destination), then destination and device. +rem A scheduled report cannot be deleted, so its schedule is cleared first. +dssqld -m 83a_stop_report_df.qli -l 83a_stop_report_df.qlo +dssqld -r 83_remove_report_df.qli -l 83_remove_report_df.qlo +dssqld -r 84_remove_printer_df.qli -l 84_remove_printer_df.qlo +if exist "%TLS_LST_PATH%\cieng_report.txt" del "%TLS_LST_PATH%\cieng_report.txt" +if exist 17_printer_df.qli del 17_printer_df.qli dssqld -r 76_remove_item_opcua_df.qli -l 76_remove_item_opcua_df.qlo dssqld -r 77_remove_opcua_station_df.qli -l 77_remove_opcua_station_df.qlo dssqld -r 78_remove_opcua_line_df.qli -l 78_remove_opcua_line_df.qlo