ci-server-engineer/.claude/skills/data-access/tasks/opcua-client.md
cliu 39e58af2ff R1.05 V4: reduced CI-Eng-Sample load tested and removed on shared server
Classes, sub-items, Modbus loopback and OPC UA loopback pass on R1.05.
New examples/CI-Eng-Sample/shared/: snapshot, reduced load/remove,
test and DSS temp-file cleanup scripts for shared servers. Before/after
snapshot identical; ISS-091 (dss_*.tmp left in tls\lst); ISS-080 note.
Certificate files of the test await deletion by the user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:10:26 +11:00

194 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
task: OPC UA client to an OPC UA server by quick load (loopback to CI Server's own OPC UA server)
version: R1.05 (first written for R1.03)
source: IM 36K10L45-01EN 5th ed. (Feb. 2026) §10, §11; IM 36K10E20-01EN 4th ed. (Feb. 2026) §2; practiced on CI-Eng-Sample 2026-10-01 on R1.03 (station CIENG_UA; full remove/reload; point also configured in the Engineering Module by the user and compared)
status: verified on R1.03 (§1–§6). R1.05 (2026-10-08, read-only): line/station/subscription/point dataset definitions identical to R1.03, PKI folders and client setup files checked; loopback re-tested on R1.05 2026-10-08 with the reduced sample load (certificate exchange as in §4, done by the user; COMM_OK 1, SRV_STATE 0, LEVEL_COPY follows TANK1.LEVEL; removed afterwards), the new form fields were confirmed in the installed form definitions (point form `READ_NODE_INFO` / `REBROWSE`; method form node ID type, parent object node ID, manual arguments, no process-area field), the forms themselves not opened. §7 (methods) from docs.
---
# OPC UA client to an OPC UA server
Worked example: CI-Eng-Sample station `CIENG_UA`, CI Server's OPC UA client reading
`CI-Eng-Sample.TANK1.LEVEL` back through CI Server's **own** OPC UA server
(`examples/CI-Eng-Sample/12_`–`15_*.qli`, removal `76_`–`78_`). For a real server, replace the
endpoint, security settings and node IDs.
## 1. Server side: make the data visible
On the CI Server OPC UA server an item is reachable only when the item has **OPC visible** and
**Read** (Write to allow writing) and every parent section is OPC visible
(`12_opc_visible_df.qli`, loaded with `dssqld -m`):
```
@FIELDS
NAME,OPC_VISIBLE
@SECTION_DF
"CI-Eng-Sample",1
"CI-Eng-Sample.TANK1",1
@FIELDS
NAME,OPC_VISIBLE,OPC_READ,OPC_WRITE
@ITEM_DF
"CI-Eng-Sample.TANK1.LEVEL",1,1,0
```
Visible without Read: the item shows when browsing, but browsing into it fails
(`UncertainNotAllNodesAvailable`, client message `OPXC-E-BRW_NODE`; verified). By the manual,
Read is also what allows reading the value.
These flags also apply to the OPC DA and A&E servers.
## 2. Client configuration (quick load, in this order)
1. **Line** (`OPCUAC_LINE_DF`): loading it creates and starts the client process
`EQUIPMENT_MAN` (must start with `OPX`, ≤ 11 characters) — wait ~10 s before the station.
```
"CIENG_UA_LINE","CI-Eng-Sample OPC UA loopback line","OPXCIENG"
```
The process reads `tls\sup\<process>.sup` (here `opxcieng.sup`), else the template
`epauac_eqp.sup` (IM 36K10L45 5th ed. §10.11; the R1.03 manual named a non-existent
`opxuac.sup`, ISS-084). On the R1.03 test system `opxcieng.sup` appeared as an exact copy of
the template. R1.05 template changes (verified 2026-10-08, ISS-083): new
`OPXC_ENABLE_EXTENDED_FUNC` (NO; YES needed for OPC UA alarms), `OPXC_BASE_MODEL_CHANGE_EVENT`
(NO), `OPXC_DISABLE_SAV_FILE` (NO), `OPXC_ALARM_TIME_ATTRIBUTE`, hidden
`OPXC_CERT_EXP_PERIOD_WARN` (2 months), and `OPXC_SERVICE_CALL_TIME_OUT` 60 s (manual: 10 s).
A per-process file copied from R1.03 lacks these keywords, so their defaults apply.
2. **Station** (`OPCUAC_STATION_DF`), **subscription** (`OPCUAC_SUBSCRIPTION_DF`, name
`<station>:<subscription>`), **points** (`OPCUAC_POINT_DF`, name `<station>:<point>`) —
see `14_opcua_station_df.qli`:
- Station: `LINE`, `USER_ANONYMOUS` 1 (or `USER_NAME`/`USER_PASSWORD`),
`MSG_SECURITY_01_MODE` `SignAndEncrypt`, `MSG_SECURITY_01_POLICY` `Basic256Sha256`,
`SERVER_01_ENDPOINT` `opc.tcp://localhost:34493`, `STATUS_ITEM` (1 = connected; a Boolean
item worked on R1.03). `SERVER_01_NAME`/`_URI` may stay empty. The R1.05 OPC Interface
manual (IM 36K10E20 4th ed. §2.1.2) lists states 0–5 for this item (0 preparing,
1 connected, 2 comm error/reconnecting, 3 connect failed, 4 server shutdown received,
5 connection recreated) while IM 36K10L45 §10.9.2 still calls it Boolean; not re-tested on
R1.05 (ISS-080). An Integer item shows all states.
- Subscription: `REQUESTED_PUBLISH_INTERVAL` 1000.
- Point: `SUBSCRIPTION`, `EXTERNAL_RELATION` (`Input`, `Output`, `Input + Output`),
`OPC_NAMESPACE` (namespace **URI**, not index), `OPC_NODE_ID_TYPE` (`STRING`, `NUMERIC`,
`GUID`), `OPC_NODE_ID`.
3. **Items** (`ITEM_DF` with `STATION` and `POINT`), e.g. `CI-Eng-Sample.UA.LEVEL_COPY` →
`CIENG_UA`, `LEVEL`.
## 3. Node IDs
| Target | Namespace | Type | Node ID |
|---|---|---|---|
| Value of a CI Server item (by name — survives recreating the item) | `urn:Yokogawa:CI_SERVER_OPC_UA_SERVER` | STRING | `{"0":"Value","itnm":"<Section>.<Tag>"}` |
| Value of a CI Server item (by namespace id) | same | STRING | `{"0":"Value","nsid":"<n>"}` |
| Server state (any OPC UA server; good connection test, 0 = Running) | `http://opcfoundation.org/UA/` | NUMERIC | `2259` |
In a `.qli` file double the quotes inside the field:
`"{""0"":""Value"",""itnm"":""CI-Eng-Sample.TANK1.LEVEL""}"`.
Pitfalls found on the test system:
- Namespace `http://www.yokogawa.com/CI_Server/` (the one the manual shows) holds only the
**types**; with it the point is silently not monitored (ISS-032).
- In the Engineering Module's *Browse path*, **expand the item and pick its `Value` node**
(`Objects/<Section>/…/<Tag>/Value`, node ID `{"0":"Value","nsid":"<n>"}`); verified by the
user: *Last value* and *Data type* filled at once. Picking the item itself stores the
**object** (`{"nsid":"<n>"}`), which has no value (status 13, never monitored) (ISS-033).
For quick load prefer `{"0":"Value","itnm":"<item>"}`: it does not depend on `nsid`.
- `nsid` numbers change when the item is deleted and created again (e.g. sample reload).
A point that is not accepted shows `REVISED_SAMPLING_INTERVAL` 0 in `OPCUAC_POINT_DF` and the
log line `Can't find Point [<point>] in subscription` when changed; a monitored point gets the
revised interval (e.g. 1000).
**Browse the server from the command line** (station connected): export `OPCUAC_TAG_DF` with
a filter; start with an empty path, then `Objects`:
```
@SELECT
'OPC_STATION_NAME = "CIENG_UA" and OPC_TAG_PATH = "Objects"'
@OPCUAC_TAG_DF
```
Each record shows display name, namespace URI and node ID (inside `OPC_TAG_INFO`). Deeper
paths were not found yet on R1.03 (`Objects.CI-Eng-Sample` returned nothing).
R1.05: `OPCUAC_TAG_DF` (and `OPCUAC_TAG_CMPN_DF`) have two new Boolean fields, `REBROWSE`
(ask the server again) and `READ_NODE_INFO` (read node attributes while browsing; switching it
off is faster on slow links) (installed `opcuac_tag_df.dss`, verified 2026-10-08; not tried).
The point form has the matching *Read additional node information* check box, and F5 in the
expanded *Browse path* list rebrowses (IM 36K10E20 4th ed. §2.1.5).
**Data types (IM 36K10L45 5th ed. §10.3.4, from docs):** UInt32, Int64 and UInt64 map to Real
(no negatives for unsigned, no decimals), DateTime/UtcTime to String in the form
`YYYY-MM-DDThh:mm:ss.sssZ`, ExtensionObject only for EURange / EngineeringUnits, enumerations as
their Int32 value; one array element per point. A value that cannot be converted is logged and
the item gets status Bad.
**Model change events (IM 36K10L45 5th ed. §10.5, from docs):** the client reacts to the
server's `GeneralModelChangeEvent` (node or reference added: the point is recreated; deleted: the
item goes offline; external alarms are refreshed with ConditionRefresh). The coarse
`BaseModelChangeEvent` (recreate everything) is handled only with
`OPXC_BASE_MODEL_CHANGE_EVENT = YES`. Events within 10 s are handled once; the server must
support Read and Browse.
## 4. Certificate exchange (first connection; the user does it)
| Step | Where it shows | What the user does |
|---|---|---|
| 1. Client rejects the server | `OPXC-E-CER_NO_TRU … BadCertificateUntrusted` in `umhlog.log`; file `<thumbprint>.der` in `%TLS_ALLUSERS_PATH%\pki\opxuac\pkiclient\rejected` | Trust it: Engineering Module *OPC UA Client → Certificates → Rejected* → *Trusted*, or move the file to `…\pkiclient\trusted\certs` |
| 2. Server rejects the client | client log `BadSecurityChecksFailed`; file `CI_SERVER_OPCUA_CLIENT_<PROCESS> [<thumbprint>].der` in `…\pki\opxuas\pkiserver\rejected` (folder created now) | Move it to `…\pki\opxuas\pkiserver\trusted\certs` |
PowerShell: file names with `[ ]` need `-LiteralPath`, otherwise `Move-Item` silently does
nothing:
```powershell
Move-Item -LiteralPath "<rejected folder>\<file>.der" -Destination "<trusted folder>\"
```
The client retries every ~35 s (`OPXC_STATION_CON_TRY_INTERVAL` 30 s); then `OPXC-I-SRV_CONN`
and the status item goes to 1. Certificates and trust stay when the line is removed and loaded
again (verified: reload connected within 5 s without a new exchange). For an external server,
its certificate arrives the same way; the client certificate
(`…\pkiclient\own\certs\<host>_<PROCESS>.der`) must be trusted on that server.
From R1.05 the client warns in the Historical Alarm Overview and the UMH message log before its
own or the server's certificate expires (IM 36K10E20 4th ed. §2.2; lead time
`OPXC_CERT_EXP_PERIOD_WARN`, 2 months in the installed template). The own certificates made at
installation were valid for 5 years on the R1.05 test server (verified 2026-10-08).
## 5. Check
- `itmval -g -n CI-Eng-Sample.UA.COMM_OK` → 1; `UA.SRV_STATE` → 0 status 9;
`UA.LEVEL_COPY` follows `TANK1.LEVEL` within ~1 s (verified 55, 62.5, 71, 33, 44.4).
- Client log: `%TLS_LOG_PATH%\<PROCESS>.log` (`Successfully connected station`, errors with
status codes); server logs `OPXUAS.log`, `OpxUas_Sdk.log` (`CreateMonitoredItems`).
- Off-scan/on-scan: `OPCUAC_STATION_DF` `ON_SCAN` 0/1 with `dssqld -m` disconnects/reconnects.
## 6. Remove
Items (`76_`), then points, subscription, station (`77_`), then the line (`78_`, stops the
client process). Reset the OPC flags only if they were not there before.
## 7. Calling a method on an external OPC UA server (R1.04+, from docs)
Source: IM 36K10L45 5th ed. §10.6, App. 1.3–1.4; IM 36K10E20 4th ed. §2.1.6. Not practiced.
Installed on R1.05 (verified 2026-10-08): dataset `OPCUAC_METHOD_CALL2_DF`, loader
`opcuac_method_u0104`, AOG displays `AOG_UAC_Method_Demo`, `AOG_UAC_Method_Call`,
`AOG_UAC_MethodArg_Call(2)` with their symbols.
1. **Class** for the method object (sample Java class `UAC_MTD_JBASE`): attribute `MTD_KEY`
(holds the method name) and signals `MTD_TRG` (0 idle/done, 1 call, 2 failed), `MTD_RET`
(result code as text; its quality holds the numeric code), `MTD_IN_ARG_0…` and
`MTD_OUT_ARG_0…` (at most 10 each, same order and types as on the server). These names are
reserved in that class. Use a class with a unique name per method object (App. 1.4).
2. **Method** under the station (Engineering Module *Methods → Add method*, dataset
`OPCUAC_METHOD_CALL2_DF`, name `<station>:<method>`): `OPC_NAMESPACE`, `OPC_NODE_ID_TYPE`,
`OPC_NODE_ID`. If the server does not browse its method nodes: parent object node ID by hand
(`MANUAL_OBJ_OPC_NODE_ID`, `OBJ_OPC_NAMESPACE`, `OBJ_OPC_NODE_ID_TYPE`, `OBJ_OPC_NODE_ID`) and
argument types by hand (`MANUAL_ARGUMENT_CONFIGURATION`, `ARGUMENT_IN_n_TYPE` / `…OUT…`).
3. **Point**, one per method: *Input + Output*, namespace `CIX_UAC_MTD`, type STRING, node ID
`CIX_UAC_MTD`; connect it to the object's `MTD_TRG` signal (*Connected I/O point*). Set
`MTD_KEY` to the method name. The process area goes on `MTD_TRG` (the method form no longer
has one). `MTD_TRG` shows status UPD_OFFLINE.
4. **Symbol and display** for the class (see the AOG demo display), then trigger from there.
Upgrade from R1.03 (App. 1.3): R1.03 methods called through dataset fields
(`OPCUAC_METHOD_CALL_IN_DF` / `…_OUT_DF`, both gone in R1.05) stop working. Rebuild the class
with the `MTD_*` members (*Extended reset enabled*, then reset), rebuild symbols and displays,
and re-save (or copy and re-add) any method with String or Boolean input arguments.