diff --git a/.claude/skills/alarm-management/tasks/item-alarming.md b/.claude/skills/alarm-management/tasks/item-alarming.md index 651b46b..cd5b9a3 100644 --- a/.claude/skills/alarm-management/tasks/item-alarming.md +++ b/.claude/skills/alarm-management/tasks/item-alarming.md @@ -139,3 +139,8 @@ No alarm line from `ALDPRT` appeared in `umhlog.log` (ISS-035). test item of a non-shared R1.05 system (expected results as in the table above). - Read `ALARM_CHRONO` only with a filter on item name **and** a time range: big history reads load the alarm storage process that all clients share (ISS-136). + +- 2026-10-12 (user project on the R1.05 test server, 2026-10-09 to 10-12): LOW/HIGH limit alarms on Real items loaded with the item (`LOW_LOW_LIMIT,LOW_LIMIT, + HIGH_LIMIT,HIGH_HIGH_LIMIT` = `-100,-5,5,100`, `ALARMING` 1, `ACKN_TYPE` `"MANUAL"`): a process class + writing the value raised status 8 (LOW) / 10 (HIGH) and one `ALARM_CURRENT` row each (priority 2 + from `STATUS_DF`), cleared on return to normal. diff --git a/.claude/skills/authorization/tasks/users-groups.md b/.claude/skills/authorization/tasks/users-groups.md index 562f36f..f1bcbbd 100644 --- a/.claude/skills/authorization/tasks/users-groups.md +++ b/.claude/skills/authorization/tasks/users-groups.md @@ -128,3 +128,11 @@ and `AUTH_GROUP_DF` keep the same relation, but the DSS processes are arranged d (ISS-117), so treat ISS-073 as still valid until it is re-tested on a separate system (ISS-152). The same applies to the password behaviour of ISS-072 (`USER_DF.PASSWORD` unchanged): keep giving `PASSWORD ""` or setting the password in the Engineering Module. + +## R1.05 (2026-10-12, user project on the R1.05 test server, 2026-10-09 to 10-12) + +A user with `PASSWORD ""`, factory group `OPERATOR`, `LAYOUT` and `INITIAL_DISPLAY` loaded with 0 +errors and logged on to the web client without a password, landing on the display in the layout +(no `?l=` or `?d=` needed). `OPERATOR` includes `ITEM_MODIFY_VALUE` and `ITEM_MODIFY_STRING_VALUE`, +so display buttons that write items work for it. Removing the user (`dssqld -r`) after the user +had exited the web session gave 0 errors. diff --git a/.claude/skills/class-object-engineering/tasks/change-reset-class.md b/.claude/skills/class-object-engineering/tasks/change-reset-class.md index 3eb55e7..0e6e710 100644 --- a/.claude/skills/class-object-engineering/tasks/change-reset-class.md +++ b/.claude/skills/class-object-engineering/tasks/change-reset-class.md @@ -41,7 +41,10 @@ for a reset report (empty = none; this prompt is not in the manual): ``` printf '2\n8\n\n0\n\n-1\n-1\n' | timeout 30 opcdbg 0 ``` -(Bash. Interactively, type the same answers.) Keep the time-out: `opcdbg` repeats its last +(Bash. Interactively, type the same answers.) In Windows `cmd`, put the answers in a file +(`2`, `8`, ``, `0`, empty line, `-1`, `-1`, one per line, CRLF) and run +`opcdbg 0 < reset_class.txt` (verified on R1.05 2026-10-12). A `dssqld -m` with `NAME,TRIGGER_1,COMPILE,METHOD` +also changes the class's trigger group (verified on R1.05). Keep the time-out: `opcdbg` repeats its last prompt forever when the input runs out. Afterwards `RESET_REQUIRED` = 0 and the object used the new code at once (verified). @@ -63,8 +66,17 @@ and a reset report; click **Reset** (verified by the user 2026-10-01: 1 of 1 obj ## 3. Changing prompts or signals -Only a change in the method bodies was practiced. When the class **prolog** changes (new +Only a change in the method bodies was practiced on R1.03. When the class **prolog** changes (new signals, prompts, attributes, created items), check `EXTENDED_RESET_REQUIRED` in `CLASS_DF` after compiling; what an extended reset does was not tested. The safe way is to recreate the objects (remove, load) — note that removing an object deletes the items it created, with their history links. + +**R1.05, Java (verified 2026-10-12, user project on the R1.05 test server, 2026-10-09 to 10-12): new signals are refused while objects exist.** `dssqld -m` of +the class with extra `define…Signal` fields gives only `DSI-E-OPCCOMERR, Compilation failed with 1 +error(s)` / `DSSP-E-UPDREC` in the `.qlo`; the reason is in the newest `%TLS_ROOT_PATH%\tls\lst\opc_t*.lst`: +`**** ERROR: Attribute or signal definitions cannot be changed while objects are derived from the +class`. The old class keeps running. Procedure that worked: remove the object (`@OBJECT_DF`, `-r`), +`dssqld -m` the class, wait for the compile, insert the object again. **The object's attributes are +lost** with it; the tested class rebuilt its data on the first run of the new object from items it +also writes (its log message confirmed the attributes were empty). diff --git a/.claude/skills/class-object-engineering/tasks/java-class.md b/.claude/skills/class-object-engineering/tasks/java-class.md index ddebeaf..0a3b4b2 100644 --- a/.claude/skills/class-object-engineering/tasks/java-class.md +++ b/.claude/skills/class-object-engineering/tasks/java-class.md @@ -104,6 +104,30 @@ refuse bad answers with `cancel("text")` in `create()`. good or 2 = bad). Use one class with its own name per method object. Procedure: skill `data-access`, `opcua-client.md` §7, and the L45 summary (§10.6, §11.4). Not practiced. +## 4a. Large classes on R1.05 (verified 2026-10-12, user project on the R1.05 test server, 2026-10-09 to 10-12) + +A Java class with about 1,600 signals ran on R1.05 every second without problems: + +- **Signal arrays**: `Signal[] t = defineDoubleSignalArray(400);` (also `defineIntegerSignalArray`, + `defineStringSignalArray`), attached in a loop in `create()`: + `for (int k = 0; k < 400; k++) attachItemToSignal(t[k], "
.TEMP." + name(k));`. + Signals without a trigger, plus `TRIGGER_1` = a trigger group and `triggerGroupEvent(TriggerGroup tg)`, + give one run per second for all inputs. +- **Write only on change**: the class compared with `getValueAsDouble()` / `getValueAsString()` + before `setValue()` (and also wrote when `getStatusNumber() == Signal.Status.NOT_INIT`). +- **String signals**: `setValue(String)` on a String item works. +- **Attribute arrays** (`defineDoubleAttributeArray(Attribute.Scope.OBJECT, n)`) keep their values + over a class reset; Java fields do not (initialise them in the first trigger run, not in + `create()`). Attributes are **lost when the object is deleted** (verified: after remove and + insert the new object found them empty and logged that it rebuilt the data from items; see + `change-reset-class.md` §3). +- **Compile check before loading** (no CI Server change): + `"%TLS_ROOT_PATH%\tls\jre\bin\javac.exe" -Xlint:all -cp "%TLS_ROOT_PATH%\tls\exe\jopcsrv.jar" -d cls .java`. + The exact API: `javap -cp %TLS_ROOT_PATH%\tls\exe\jopcsrv.jar fasttools.processfast.ProcessClass` + (also `Signal`, `Attribute$Scope`, `Signal$Trigger`, `Signal$Status` constants, e.g. `NOT_INIT` 13). +- **Keep quick load lines short**: put long generated arrays (tables in the class source) on several + lines; the class loaded with lines up to 233 characters. + ## 5. Pitfalls (manual) - Definitions (`define…()`) only at class level, without `public`/`private`. diff --git a/.claude/skills/equipment/tasks/modbus-station.md b/.claude/skills/equipment/tasks/modbus-station.md index da1794f..f7ff72f 100644 --- a/.claude/skills/equipment/tasks/modbus-station.md +++ b/.claude/skills/equipment/tasks/modbus-station.md @@ -122,3 +122,18 @@ files deleted), reloaded from the sample files and re-tested: all 0 errors. With `eqpciengslv_start.cmd`/`_stop.cmd` copied to `tls\com` and CI Server restarted, the mirror and `EQPCIENG` started under the CI Service, `COMM_OK` = 1, and the saved setpoint (2468) was re-sent to the freshly zeroed slave memory on connect (output method *On change and connect*). + +## R1.05: a 400-register device (2026-10-12, user project on the R1.05 test server, 2026-10-09 to 10-12) + +- Own line and equipment manager (`EQUIPMENT_MAN` e.g. `EQP`) next to another project's + line: loaded, removed and loaded again with 0 errors; removing the line stopped only its manager. +- 400 holding registers as points `RO:1`–`RO:400`, `CONV_TYPE` `Linear` with `HAS_SIGN` 1, + `ELEC_LOW,ELEC_HIGH` = `-32768,32767` and `PHYS_LOW,PHYS_HIGH` = `-327.68,327.67` (raw ÷ 100): + all 400 items read the expected signed values (test pattern from a simulator, quality 0, + status 9). +- Communication loss: status item 0 (status 4), inputs keep their last value with the `off` option; + values and status item recover on the next scan after the device answers again. +- Test without the device: `tools/ModbusSim.java` (Java, runs with CI Server's own JDK; holding + registers with read and write, scenarios through a mode file). A PowerShell script that opens a + TCP listener was blocked by Windows antivirus (AMSI) as malicious content on the engineering PC + (Windows 10). diff --git a/.claude/skills/graphics/tasks/create-display.md b/.claude/skills/graphics/tasks/create-display.md index 48740c9..7831af4 100644 --- a/.claude/skills/graphics/tasks/create-display.md +++ b/.claude/skills/graphics/tasks/create-display.md @@ -71,6 +71,21 @@ Module, the Operator Interface and the HTML display, if these rules are kept (sa | Server-side command | `` with `` and `true` runs on the CI Server node as the service account `CIX_PROCESS`, also from the web; a user in the factory group `OPERATOR` may use it (tested 2026-10-06, sample live-data *Start*) | | Alarm overview | `autoFit` true in a tab that is hidden while loading squeezes all columns to ~0 px on the web: set `autoFit` false and give `columnWidth`; `column…Visible` for `AlarmAttribute` is an array property (a plain value fails the HTML export with `ClassCastException`); the empty part of the list stays **white** on both clients (no property; web: `jquery.dataTables.css` forces it); web rows are 20 px at a 14 px font (2026-10-02/06) | +## R1.05 web client: findings from a generated display (verified 2026-10-12, user project on the R1.05 test server, 2026-10-09 to 10-12) + +A generated display with about 1,900 components (6 MB XML, 1 MB HTML) on the R1.05 web client: + +| Topic | Finding | +|---|---| +| Text bound to a String item | `` with `text.value` ← `ItemValue` works. `keepOriginalSize` false stretches it to the box (as on R1.03, unreadable for changing text); `true` draws it centred at natural size, good for centred values. For **left-aligned** changing text use a ``: `textField.value` ← `ItemValue`, `false`, ``, justification `TextJustificationLeft` (or `Right`). `false` is ignored: set `backgroundColor` to the colour behind it, or connect `textField.backgroundColor` through a threshold with output type `ColorSet` when the background changes. | +| Fixed text that must align | A `` is centred on its box at its natural width; on one viewer's client long lines started left of their box. A `` with a fixed `` (no connection) is left-aligned (checked in the test browser). | +| Hidden at start | `false` plus a `*.visible` connection: rectangles, texts and numbers appear when the condition becomes true; **ellipses, textFields and numberFields stay hidden**. Leave those visible at start. Without a start value every conditional component is drawn until the first values arrive. | +| Two conditions | Two connections to the same `*.visible` property do not combine (numbers stayed visible after one of their two conditions became false). Encode the condition in one item (e.g. a state item the calculation writes). | +| Display actions on open | Actions placed directly in `coreObjectDefinition` (before the first layer, as in the factory `AOG_ItemFaceplate`) with `` run when the display opens **and** on *Reload*; e.g. `actionSetItemAttribute` to reset a selection item. | +| Blinking | `rectangle.blinking` ← item through a threshold (output Boolean) plus a `blinkingPaint` blinks (the component toggles visibility at the display's `blinkDelay`, 500 ms). | +| Also verified | `numberField` with `asDateTime` on `ItemValue` (epoch seconds); `text.fillPaint`, `rectangle.strokePaint`, `ellipse.strokePaint` through thresholds; `actionSetItemAttribute` with event `Clicked b1` on a rectangle and on a number (a number drawn on a clickable shape needs its own action, it catches the click). | +| Size and first load | Once the first values took about a minute on a display this size; later loads took 3–10 s. | + ## R1.05: component changes since R1.03 (from docs, IM 36K10C50-01EN 5th ed.) | Component / sheet | Change (version) | diff --git a/.claude/skills/graphics/tasks/layouts.md b/.claude/skills/graphics/tasks/layouts.md index 9c899bd..a067677 100644 --- a/.claude/skills/graphics/tasks/layouts.md +++ b/.claude/skills/graphics/tasks/layouts.md @@ -193,12 +193,14 @@ browser and in the web client code. "n/a" = the browser window is the main layou | `toolbar` (check) | hides/shows **all** toolbars; `toolbar false` behaves as a toggle too | no effect | | `statusline` (check) | hides/shows (a piece of the date field stays painted) | no effect | | `print …` | preview / setup dialogs open | disabled | -| `login` / `logout` / `exit` | — / — / closes | login disabled; logout, exit work | +| `login` / `logout` / `exit` | — / — / closes | login disabled; **`logout` disabled on R1.05** (greyed; see note); `exit` closes the window and ends the session | | `shell client ` | runs on the client (Notepad opened) | sent to the server; `shell client ACTION ` runs a layout command | | `lookfeel ` | — | no effect | | `playback record` (check) / `view` | — | not supported (view disabled) | | `help` | **error** *Unrecognized keyword* | — | +**`logout` is a secondary logout (R1.05, verified 2026-10-12).** The factory layout labels it *Secondary logout* (pair of `login`). The R1.05 web client always disables it (`LayoutWindow.js`: `setEnabled(false)` for `logout`; the command calls `secondaryLogout()`), so a toolbar or menu item with `logout` is greyed out. To let a user leave on the web, use **`exit`**: it closes the layout window and, for the last window, ends the session; the browser then closes the tab (reopening the URL shows the login page). Label the item *Exit* rather than *Log out*. Tested on the web client; the desktop Operator Interface was not tried. The sample layout `CIENG_Operator` still has a *Log out* (`logout`) menu item. + "—" = not checked. ### 6.5 Layouts opened from a display, translations, debug key diff --git a/.claude/skills/item-engineering/tasks/string-items.md b/.claude/skills/item-engineering/tasks/string-items.md index e7aa7a1..67c3c9b 100644 --- a/.claude/skills/item-engineering/tasks/string-items.md +++ b/.claude/skills/item-engineering/tasks/string-items.md @@ -37,3 +37,8 @@ After a write, check the status: 12 means the text was cut. The representation (type) and length cannot be changed on an existing item: remove and load it again. + +**Mixed item files (R1.05, 2026-10-12).** `STRING_LENGTH` is checked on every record of an `@FIELDS` +block: `0` (or empty) on a Real/Integer item is refused with `DSS-E-VAL_RANGE ... (Field: +STRING_LENGTH)`. Put String items in their own `@FIELDS` block with `STRING_LENGTH`, and the other +items in a block without it. diff --git a/.claude/skills/system-management/SKILL.md b/.claude/skills/system-management/SKILL.md index 1654527..fdaf0d8 100644 --- a/.claude/skills/system-management/SKILL.md +++ b/.claude/skills/system-management/SKILL.md @@ -40,6 +40,7 @@ Rules for every task: | Task | File | Status | |---|---|---| +| Package a project for another CI Server (quick load, display, layout, user, README, pre-check) and test the install | `tasks/package-project.md` | verified on R1.05 with a user project 2026-10-12 (not on CI-Eng-Sample) | | Export dataset contents (whole or filtered) / dump the data dictionary | `tasks/export-dataset.md` | verified (R1.03; re-tested on R1.05 2026-10-08) | | Load or remove records with quick load files (`.qli`), e.g. sections and items | `tasks/load-remove-records.md` | verified (R1.03; load/remove used on R1.05 in the CI-Eng-Sample test 2026-10-08) | | Read / write item values and read limits | `tasks/read-write-item.md` | verified (R1.03; `-g`/`-p` used on R1.05 in the CI-Eng-Sample test, read re-checked 2026-10-08) | diff --git a/.claude/skills/system-management/tasks/export-dataset.md b/.claude/skills/system-management/tasks/export-dataset.md index 2980dfe..14183fe 100644 --- a/.claude/skills/system-management/tasks/export-dataset.md +++ b/.claude/skills/system-management/tasks/export-dataset.md @@ -85,6 +85,11 @@ dssqld -e .qli .qli [-c] -l .lst REST API keys out on purpose. `-d API_KEYS_DF` still exports it; treat that file as a secret, or create new keys on the target system instead (skill `data-access`). +- **Range on `NAME`** (R1.05, 2026-10-12): `'NAME > "
." and NAME < ""'` (`` = the section + name with its last character raised by one, e.g. `TANK1.` → `TANK2`) selects all items below a + section on `ITEM_DF` (tested with 1,751 items); a `NAME = "A*"` wildcard does not work. + The same works on `ALARM_CURRENT` with `ITEM_NAME`, to list the current alarms of one section. + ## Clean up: `dss_*.tmp` files (R1.05) Exporting `OBJECT_DF` leaves 0-byte `dss__.tmp` files in `%TLS_LST_PATH%` (`tls\lst`), diff --git a/.claude/skills/system-management/tasks/package-project.md b/.claude/skills/system-management/tasks/package-project.md new file mode 100644 index 0000000..c951da6 --- /dev/null +++ b/.claude/skills/system-management/tasks/package-project.md @@ -0,0 +1,38 @@ +--- +task: Package a project for another CI Server (quick load files, display, layout, user) and check the install +version: R1.05 +source: IM 36K10J10-01EN §5.6 (moving engineering data); IM 36K10L50-01EN §3; practiced with a user project on the R1.05 test server 2026-10-12 (remove and reload from the package, twice) +status: verified on R1.05 with a user project (not on CI-Eng-Sample) +--- + +# Package a project for another CI Server + +Goal: a folder (zip) a junior engineer can install from, step by step, on another server. + +## 1. What goes in + +| Part | Notes | +|---|---| +| Quick load files `01_…` to `nn_…` in load order + `load.cmd` | sections, status items, line, station/points, items, trigger, class, object, users. **No node names** (leave `EQUIPMENT_NODE`, `FRONT_END_NODE` out) and no `NSID` / `ID_NUMBER`: the target assigns them. Insert (`-i`) only. | +| Removal files in reverse order + `remove.cmd` | so the install can be undone | +| Displays + a deploy script | copy to `DEPLOY\displays`, `jwuiutl -d -v -i -l … ` (must say *No problems found*), copy again (validation re-saves), `jwuiutl -e HTML -i -f DEPLOY `. Displays without `` bind by name, so new item numbers on the target do not matter. | +| Layout block + script | only the project's layout(s) between ``, inserted before the first ``; the script makes a timestamped backup and checks the XML (sample: `examples/CI-Eng-Sample/layouts/layout.ps1`). | +| README | numbered steps with the exact command, the expected output and what to do if it differs | +| Pre-check script (read-only) | CI Server version (`%TLS_ALLUSERS_PATH%\lst\tls_versions.lst`), `dssqld` answers, longest file path ≤ 79 (ISS-020), `tls\jre\bin\javac.exe` present (Java classes compile on load), device reachable (TCP connect), each name free (filtered export per dataset, e.g. `'NAME = ""'` on `MODBUS_LINE_DF`, `'EQUIPMENT_MAN = ""'`), layout name not used outside the project block | +| Post-install check | `itmval -g` of the station status item, one value to compare with the device's own software, a value the calculation updates (read twice), learn/initial states | + +## 2. Script rules (found by testing the README in one Command Prompt) + +- **`pushd "%~dp0"` … `popd`**, not `cd /d "%~dp0"`: the engineer runs the scripts one after the other + in the same prompt; a script that leaves the prompt in its own folder breaks the next command. +- `load.cmd`: delete the old `0*.qlo` first and end with `findstr /c:"_DF " 0*.qlo` (one result + line per data set; the last column is *Errors*). Otherwise old results (e.g. of an undo) are listed too. +- Wait after the line (`powershell -NoProfile -Command "Start-Sleep -Seconds 15"`, the equipment + manager starts) and after a class (about 20 s, compile) before the next file. + +## 3. Test the package + +On a test server: run the README's undo, then every step exactly as written, in one Command Prompt, +from a fresh copy of the zip; point the device address at a simulator (`tools/ModbusSim.java`) if +the device is not reachable. Compare the server against the package afterwards (filtered `ITEM_DF` +export by name range, class source from a `CLASS_DF` export, display file hash). diff --git a/knowledge/issues.md b/knowledge/issues.md index 3a14d4e..e82b431 100644 --- a/knowledge/issues.md +++ b/knowledge/issues.md @@ -179,3 +179,10 @@ development system (see "Test system" in `progress.md`); they do not exist on ot | ISS-179 | 2026-10-09 | PYTOOLS (login method) | pyTOOLS supports only *Manual CI Server* (J10 §2.9.1); the installed R1.05 *Python Interface* lines are Manual CI Server. Behaviour with another method or with the lines missing after an upgrade (ISS-149) is not documented. | open | From docs plus the installed file. | P40 summary (done); `.claude/skills/authorization/tasks/login-methods.md` (done) | | ISS-180 | 2026-10-09 | PYTOOLS (BUS name routing) | `dss.sup` routes only `PYDSS*` clients to DSSSDAPY; a script that sets its own BUS name with `busSetName` probably falls back to the general DSSSDA00–03 routing. Not in P40. | open | Untested (no Python). | `.claude/skills/pytools/tasks/event-notification.md`, P40 summary (done) | | ISS-181 | 2026-10-09 | PYTOOLS (event queue) | IM 36K10P40 §3.10 says the event queue size is set in the DSS setup file without naming the keyword: it is `EVENT_Q_SLOTS` in `dss.sup` (default 100, no explicit line). | open | From docs and the `dss.sup` help. | P40 summary, `.claude/skills/pytools/tasks/event-notification.md` (done) | +| ISS-182 | 2026-10-12 | GRAPHICS (layouts, web) | Layout command `logout` is a *secondary* logout and is always disabled on the R1.05 web client (`LayoutWindow.js`); the R1.03 table said it works on the web. | closed | Use `exit` (closes the window; the last window ends the session, the browser closes the tab). Verified on the R1.05 web client 2026-10-12. The sample layout `CIENG_Operator` still has a `logout` item: change it after re-testing on the sample (ISS-188). | `graphics/tasks/layouts.md` §6.4, `examples/CI-Eng-Sample/layouts/CIENG_Operator.xml` | +| ISS-183 | 2026-10-12 | GRAPHICS (web, visibility) | Ellipses, textFields and numberFields that start with `false` stay hidden on the R1.05 web client when their visibility connection becomes true (rectangles, texts, numbers work). | closed | Leave those components visible at start (workaround). Verified 2026-10-12. | `graphics/tasks/create-display.md` §R1.05 web client | +| ISS-184 | 2026-10-12 | GRAPHICS (web, String items) | A `` bound to a String item is stretched to its box with `keepOriginalSize` false and centred with true; `textField` `opaque false` is ignored (white background). | closed | Left-aligned text: borderless read-only `textField` with the background colour set or connected. Verified 2026-10-12. | `graphics/tasks/create-display.md` | +| ISS-185 | 2026-10-12 | PROCESS (Java, signals) | Changing the signal or attribute definitions of a Java class while an object exists fails with only `DSI-E-OPCCOMERR` in the `.qlo`; the reason is in `tls\lst\opc_t*.lst`. Recreating the object loses its attributes. | closed | Remove object, modify class, insert object; rebuild attribute data from items. Verified on R1.05 2026-10-12. | `class-object-engineering/tasks/change-reset-class.md` §3, `java-class.md` §4a | +| ISS-186 | 2026-10-12 | TOOLS (Windows security) | A PowerShell script that opens a TCP listener (Modbus test server) is blocked by Windows antivirus/AMSI: *This script contains malicious content and has been blocked* (engineering PC, Windows 10). | closed | Use the Java test server `tools/ModbusSim.java` (CI Server JDK). Seen 2026-10-12. | `tools/README.md`, `equipment/tasks/modbus-station.md` | +| ISS-187 | 2026-10-12 | ITEM (quick load) | `STRING_LENGTH` 0 on a Real item in a mixed `@FIELDS` block is refused (`DSS-E-VAL_RANGE`). | closed | Separate `@FIELDS` blocks for String and other items. Verified on R1.05 2026-10-12. | `item-engineering/tasks/string-items.md` | +| ISS-188 | 2026-10-12 | SAMPLE (R1.05) | The R1.05 findings of 2026-10-12 were verified with a user project, not with CI-Eng-Sample (displays, layouts and users of the sample are not loaded on the shared R1.05 server). | open | Re-test on CI-Eng-Sample when the full sample can be loaded on an R1.05 system, including replacing the sample layout `logout` item with `exit`. | `knowledge/progress.md` R1.05 table | diff --git a/knowledge/progress.md b/knowledge/progress.md index 48c6e91..b7a635e 100644 --- a/knowledge/progress.md +++ b/knowledge/progress.md @@ -66,6 +66,16 @@ screenshot. | `reporting` | none listed (E10 one sentence removed, ISS-166) | re-checked (read-only) | 2026-10-09: report definitions, setup and `.rpt` files identical; hooks (ISS-121); 28 system reports, 6 factory destinations; interactive RQL re-run, new `ITEM_VAL` fields (ISS-126, 167); `API_KEYS_DF` not queried (ISS-168). Generate, print, item trigger, `rptstp`, SPOOL re-tested on R1.05 (V12, 2026-10-09). **Not tested:** Excel Add-in (no Office), report component, hardcopy printers. | | `examples/CI-Eng-Sample` | — | re-tested | Reduced load (no history group, user or layouts) loaded, tested and removed on the shared R1.05 server 2026-10-08 (V4); scripts in `shared\`. Full load not tested on R1.05. | +### Findings from a user project (2026-10-12) + +A Modbus TCP device with 400 registers, a large Java process class, a generated display with a +pop-up, a minimal layout, a password-less user and an install package were built and tested on the +R1.05 test server (own objects only). Added to the skills: `layouts.md` (`logout` vs `exit`), +`create-display.md` (R1.05 web client table), `java-class.md` §4a, `change-reset-class.md` (new +signals, `opcdbg` in cmd), `string-items.md`, `users-groups.md`, `modbus-station.md`, +`export-dataset.md` (name range), `item-alarming.md`, new task `system-management/tasks/package-project.md`, +`tools/ModbusSim.java`. Issues ISS-182 to ISS-188. Not re-tested on CI-Eng-Sample (ISS-188). + ## Manuals, in working order | Phase | Manual | Title | Status | diff --git a/tools/ModbusSim.java b/tools/ModbusSim.java new file mode 100644 index 0000000..3bde213 --- /dev/null +++ b/tools/ModbusSim.java @@ -0,0 +1,142 @@ +/* + * ModbusSim: a small Modbus TCP server (slave) for testing a CI Server Modbus station without the + * real device. Java 11 single-file program: runs with the JDK that ships with CI Server, nothing + * to install (a PowerShell TCP listener script is blocked by Windows antivirus/AMSI as + * "malicious content", see knowledge/issues.md). + * + * "%TLS_ROOT_PATH%\tls\jre\bin\java.exe" ModbusSim.java [port] [registers] [folder] + * defaults: 15020, 1000, the current folder. Listens on 127.0.0.1 only. + * + * One table of holding registers (FC03 read, FC06 / FC16 write); FC04 reads the same table as input + * registers (like CI Server's own slave mirror). FC07 answers 0, FC08 echoes, others exception 01. + * Register 1 = protocol address 0 (CI Server point RO:1 / RI:1 = 40001 / 30001). + * + * /mode.txt is read every 2 s and can be changed while it runs: + * pattern raw(k) = ((k * 157) mod 30000) - 5000 (fixed, signed test values; the default) + * zero all registers 0 + * csv values from /values.csv: one integer per line, line k = register k + * hold keep the current values (use with writes from CI Server) + * stop close and exit + * Messages go to the console and /sim.log. + * To keep it running after an SSH session ends, start it detached, e.g. with PowerShell + * Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine='...'} + * Never run it on a production system or on a port a real device uses. + */ +import java.io.*; +import java.net.*; +import java.nio.file.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class ModbusSim { + static int n; + static int[] reg; + static volatile String mode = ""; + static Path folder; + + public static void main(String[] args) throws Exception { + int port = args.length > 0 ? Integer.parseInt(args[0]) : 15020; + n = args.length > 1 ? Integer.parseInt(args[1]) : 1000; + folder = Paths.get(args.length > 2 ? args[2] : ".").toAbsolutePath(); + reg = new int[n + 1]; + Path modeFile = folder.resolve("mode.txt"); + if (!Files.exists(modeFile)) Files.writeString(modeFile, "pattern"); + ServerSocket server = new ServerSocket(port, 10, InetAddress.getLoopbackAddress()); + log("listening on 127.0.0.1:" + port + ", " + n + " registers, pid " + ProcessHandle.current().pid()); + + Thread control = new Thread(() -> { + while (true) { + try { + String m = Files.readAllLines(modeFile).stream().findFirst().orElse("").trim().toLowerCase(); + if (m.equals("stop")) { log("stop requested"); System.exit(0); } + if (!m.equals(mode)) { mode = m; log("mode " + m); apply(m); } + Thread.sleep(2000); + } catch (Exception e) { log("control: " + e); } + } + }); + control.setDaemon(true); + control.start(); + while (true) { + Socket c = server.accept(); + new Thread(() -> serve(c)).start(); + } + } + + static synchronized void apply(String m) throws IOException { + if (m.equals("pattern")) for (int k = 1; k <= n; k++) reg[k] = ((k * 157) % 30000) - 5000; + else if (m.equals("zero")) java.util.Arrays.fill(reg, 0); + else if (m.equals("csv")) { + java.util.List lines = Files.readAllLines(folder.resolve("values.csv")); + for (int k = 1; k <= n && k <= lines.size(); k++) reg[k] = Integer.parseInt(lines.get(k - 1).trim()); + } + } + + static void serve(Socket c) { + String who = String.valueOf(c.getRemoteSocketAddress()); + log("connect " + who); + try (c; DataInputStream in = new DataInputStream(new BufferedInputStream(c.getInputStream())); + OutputStream out = c.getOutputStream()) { + while (true) { + byte[] h = new byte[7]; + in.readFully(h); + int len = ((h[4] & 0xFF) << 8) | (h[5] & 0xFF); + if (len < 2 || len > 256) throw new IOException("bad length " + len); + byte[] pdu = new byte[len - 1]; + in.readFully(pdu); + byte[] r = handle(pdu); + byte[] resp = new byte[7 + r.length]; + System.arraycopy(h, 0, resp, 0, 4); + resp[4] = (byte) ((r.length + 1) >> 8); resp[5] = (byte) (r.length + 1); resp[6] = h[6]; + System.arraycopy(r, 0, resp, 7, r.length); + out.write(resp); + out.flush(); + } + } catch (EOFException e) { + log("disconnect " + who); + } catch (Exception e) { + log("disconnect " + who + " (" + e + ")"); + } + } + + static int u16(byte[] b, int i) { return ((b[i] & 0xFF) << 8) | (b[i + 1] & 0xFF); } + + static synchronized byte[] handle(byte[] pdu) { + int fc = pdu[0] & 0xFF; + if (fc == 3 || fc == 4) { + int start = u16(pdu, 1), qty = u16(pdu, 3); + if (qty < 1 || qty > 125) return new byte[]{(byte) (fc | 0x80), 3}; + if (start + qty > n) return new byte[]{(byte) (fc | 0x80), 2}; + byte[] r = new byte[2 + 2 * qty]; + r[0] = (byte) fc; r[1] = (byte) (2 * qty); + for (int i = 0; i < qty; i++) { + int v = reg[start + i + 1] & 0xFFFF; + r[2 + 2 * i] = (byte) (v >> 8); r[3 + 2 * i] = (byte) v; + } + return r; + } + if (fc == 6) { + int a = u16(pdu, 1); + if (a >= n) return new byte[]{(byte) 0x86, 2}; + reg[a + 1] = (short) u16(pdu, 3); + return pdu.clone(); + } + if (fc == 16) { + int start = u16(pdu, 1), qty = u16(pdu, 3); + if (qty < 1 || qty > 123 || start + qty > n) return new byte[]{(byte) 0x90, 2}; + for (int i = 0; i < qty; i++) reg[start + i + 1] = (short) u16(pdu, 6 + 2 * i); + return new byte[]{16, pdu[1], pdu[2], pdu[3], pdu[4]}; + } + if (fc == 7) return new byte[]{7, 0}; + if (fc == 8) return pdu.clone(); + return new byte[]{(byte) (fc | 0x80), 1}; + } + + static synchronized void log(String s) { + String line = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " " + s; + System.out.println(line); + try { + Files.writeString(folder.resolve("sim.log"), line + System.lineSeparator(), + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException ignored) { } + } +} diff --git a/tools/README.md b/tools/README.md index c2c289d..1caf450 100644 --- a/tools/README.md +++ b/tools/README.md @@ -6,4 +6,5 @@ location; take it from an argument or environment variable, with | Script | Purpose | |---|---| +| `ModbusSim.java` | Modbus TCP test server (holding registers, read and write, scenarios through `mode.txt`) for testing a Modbus station without the device; runs with CI Server's JDK (`java ModbusSim.java [port] [registers] [folder]`). Verified on R1.05 2026-10-12 | | `setup_check.ps1` | Read-only check of a machine against the CI Server R1.03 setup and security requirements (skill `installation-security`) |