--- task: Write and load a Java PROCESS Service class version: R1.05 (first written for R1.03) source: IM 36K10P11-01EN 4th ed. (Feb. 2026) §1–§5, §10; IM 36K10L10-01EN §1; practiced with CI-Eng-Sample class CIENG_JFLOW 2026-09-30 status: verified on R1.03 (2026-09-30); re-tested on R1.05 2026-10-08 (CIENG_JFLOW compiled on load, sPerMin 2 for inflow 120, removed). Public API of fasttools.processfast compared with javap on 2026-10-09: identical to R1.03. systemMessage() output and the reset after a change were not re-tested on R1.05 --- # Java class Worked example: class `CIENG_JFLOW`, object `CI-Eng-Sample.TANK1.JFLOW` (`examples/CI-Eng-Sample/06_class_df.qli`, `07_object_df.qli`). Language reference: `knowledge/manuals/IM36K10P11-01EN.md`. Loading and removing: `load-class-object.md`. Choose Java when you need loops, arrays of signals, inheritance, libraries, or signals that may stay unattached; otherwise the Traditional language is simpler. ## 1. Class record Same as a Traditional class but `PF_LANGUAGE` = `"Java"` (cannot be changed later) and Java source in `METHOD`. The Java class name must equal the `CLASS_DF` name exactly. ``` @FIELDS NAME,DESCRIPTION,PF_LANGUAGE,TRIGGER_1,COMPILE,LISTING_WANTED,METHOD @CLASS_DF "CIENG_JFLOW","Inflow per minute (Java)","Java","",1,0,@" import fasttools.processfast.*; public class CIENG_JFLOW extends ProcessClass { Signal sInflow = defineDoubleSignal(Signal.Trigger.VALUE); Signal sPerMin = defineDoubleSignal(); public void create() { attachItemToSignal(sInflow, getSectionsPath() + ".INFLOW"); createItemForSignal(sPerMin, "CI-Eng-Sample.TEMPLATES.TPL_REAL", "DESCRIPTION", "Tank inflow (m3/min), class CIENG_JFLOW"); } public void signalEvent(Signal signal) { double mInflow = sInflow.getValueAsDouble(); sPerMin.setValue(mInflow / 60.0); systemMessage("CIENG_JFLOW " + getTag() + ": inflow " + mInflow); } } "@ ``` `OPCMAN` starts the Java compiler `OPCJCOM` on demand; allow ~15 s, then export the class and check `VALID_METHOD` = 1 (as in `load-class-object.md`). ## 2. Object ``` @FIELDS NAME,DESCRIPTION,CLASS @OBJECT_DF "CI-Eng-Sample.TANK1.JFLOW","Tank 1 inflow per minute (Java)","CIENG_JFLOW" ``` Prompts: `Attribute aX = defineStringAttributeForPrompt("…");` → answer in `ATTR_VALUE_n`; refuse bad answers with `cancel("text")` in `create()`. ## 3. Results on the test system - Signals: `sInflow` → `CI-Eng-Sample.TANK1.INFLOW`; `sPerMin` → created item **`CI-Eng-Sample.TANK1.JFLOW.sPerMin`** — automatic names keep the identifier's case, and `itmval` needs that exact case (`…SPERMIN` is not found). Give a `"NAME"` if you want upper-case item names. - `INFLOW` 120 → `sPerMin` 2; 90 → 1.5. - `systemMessage()` appears in `umhlog.log` from `OPCJEXE` as `UMH-I-INFO, (: )`. - The first object ran without resetting the new class, although the manual asks for a reset (ISS-029). After changing a Java class that has objects, compile **and reset** it (`change-reset-class.md`). - Deleting the object deleted `…JFLOW.sPerMin` and section `…TANK1.JFLOW`; `INFLOW` stayed. ## 4. Java on R1.05 (checked 2026-10-09) - Runtime: the JDK in `%TLS_ROOT_PATH%\tls\jre` is Eclipse Temurin **11.0.28** (R1.03: 11.0.18). Same Java 11 language level; write classes for Java 11. - API: the public classes and methods of `fasttools.processfast` (in `tls\exe\jopcsrv.jar`) are the same as on R1.03 (`javap -public`; the jar itself was rebuilt). - `createSection(path, description, blocked, opcVisible)` takes **four** arguments on both versions. The manual's examples pass a fifth (an empty process-area string) and would not compile as printed; R1.05 §10.2 now describes it correctly (ISS-154; not compile-tested). - The API has methods that P11 does not describe (ISS-159): `getHistory(...)` (item history as `ValueSample[]`, several overloads), `defineIntegerAttributeForPrompt` / `defineDoubleAttributeForPrompt` (P11 only names the String version), `sendAndReceiveMessage(...)` (BUS request/reply) and `getDssSession()` (release notes). Their behaviour is not documented here; test before relying on them. - Extra jars: the Java PROCESS processes (`OPCJCOM`, `OPCJACT`, `OPCJEXE`) put `tls\exe\*` and, on R1.05, also `tls\exe\lib\*` (bundled third-party jars) on the class path. Your own library jar still goes in `tls\exe` (P11 §9). - OPC UA methods (R1.04+): method objects are Java (or Traditional) objects with the attribute `MTD_KEY` and signals `MTD_TRG`, `MTD_RET`, `MTD_IN_ARG_n`, `MTD_OUT_ARG_n`. The installed sample classes are the starting point: `UAC_MTD_JBASE` (CI Server as OPC UA client; prompts for the method key `:` and for the input and output argument lists written as `/type/description`, type f, i, s or b, at most 10 each) and `UAS_MTD_JBASE` (method offered by the CI Server OPC UA server; your logic runs when `MTD_TRG` becomes 1 and must set the outputs, then `MTD_RET` if needed, then `MTD_TRG` 0 = 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`. - `create()`/`destroy()` run in a different process than the trigger methods: only attributes keep values between them; no timers or signal values in `create()`. - Never sleep or loop long in a trigger method: it blocks all objects of the same priority. - No anonymous listener classes (fail at object start with `NoClassDefFoundError`). - Dataset access: get the session with `getDssSession()` and never close it (release notes).