opcua
opcua copied to clipboard
Update bidirectional output records without fully processing them
Reported by @dirk-zimoch:
When the device sends value updates, output records are fully processed, even if they are set to SCAN="Passive". Generally, "Passive" records are not expected to process on their own - so this behaviour violates the "rule of least surprise".
Also, other records connected through FLNK are being processed, no matter if the output record was written to or updated.
It would be better to just update output records: update the time stamp, check alarms, send monitors. FLNK processing should be limited to being done when the output record processes regularly.
This hit me today again when implementing a writable structure.
I have an opcuaItem record connected to ns=2;s=Demo.Static.Scalar.Structure
of the demo UASDK demo server. I have set its DEFACTN=write
. Two ao records are connected to the fields of the structure: element=Low
and element=High
. All other link options are default. The second record has a FLNK
to the opcuaItem, so that when LOW is set, it simply stores the value, but when HIGH is set, the opcuaItem record fetches both values writes the structure to the server.
But the opcuaItem is monitoring the structure and when getting updated in turn updates of the two ao records. But this triggers the FLNK
and thus the Item record. Rinse and repeat.
This is the output with TPRO=1
in all three records:
OPC UA Client Device Support 0.10.0-dev (v0.9.5-40-g79ec509-dirty); using Unified Automation C++ Client SDK v1.7.0-449
iocRun: All initialization complete
OPC UA: Autoconnecting sessions
cbLow: dbProcess of 'Demo:Low'
Demo:Low: (client time 2024-06-14 10:00:09.159959699) connectionLoss --- remaining queue 0/3
cbLow: dbProcess of 'Demo:High'
Demo:High: (client time 2024-06-14 10:00:09.159959699) connectionLoss --- remaining queue 0/3
cbLow: dbProcess of 'Demo:Structure'
cbLow: dbProcess of 'Demo:Structure'
OPC UA session Demo: connected as 'Anonymous' (sec-mode: None; sec-policy: None)
OPC UA session Demo: WARNING - this session uses *** NO SECURITY ***
cbLow: dbProcess of 'Demo:Low'
Demo:Low: (server time 2024-06-14 10:00:09.187297300) read readComplete (Good) (OpcUa_Null) as epicsFloat64 --- remaining queue 0/3
cbLow: dbProcess of 'Demo:High'
Demo:High: (server time 2024-06-14 10:00:09.187297300) read readComplete (Good) (OpcUa_Null) as epicsFloat64 --- remaining queue 0/3
cbLow: dbProcess of 'Demo:Structure'
cbLow: Re-process Demo:Structure
Demo:Low : incoming data () out-of-bounds
Demo:High : incoming data () out-of-bounds
cbLow: dbProcess of 'Demo:Low'
Demo:Low: (client time 2024-06-14 10:00:09.188616260) writeComplete --- remaining queue 0/3
cbLow: dbProcess of 'Demo:High'
Demo:High: (client time 2024-06-14 10:00:09.188616260) writeComplete --- remaining queue 0/3
cbLow: dbProcess of 'Demo:Structure'
cbLow: Re-process Demo:Structure
cbLow: dbProcess of 'Demo:Low'
Demo:Low: (client time 2024-06-14 10:00:09.190475498) writeComplete --- remaining queue 0/3
cbLow: dbProcess of 'Demo:High'
Demo:High: (client time 2024-06-14 10:00:09.190475498) writeComplete --- remaining queue 0/3
cbLow: dbProcess of 'Demo:Structure'
cbLow: Re-process Demo:Structure
[...]
Updating records from the hardware must not trigger their FLNK
(but should trigger monitors on the updated records). Thus, it cannot be done by actually processing the record, because EPICS has not way to process the record without triggering the FLNK
. Instead, the device support needs to implement its own update()
function for each supported record type, which mimics most of the functionality of the record's process()
function. For example see https://github.com/paulscherrerinstitute/regdev/blob/68d4e13fe9e53f0db496e2e97e9db6516b11c8e2/regDevSup.c#L803.
It would be nice to have this update functionality already build into output records, but alas ...
As implemented currently, this severely limits the user friendlyness of output structures and basically make DEFACTN
pointless, because one cannot FLINK
to the Item record to trigger the structure write automatically but instead has to process the Item record separately from setting the fields. In that case one can directly trigger the WRITE
field and does not need DEFACTN
.
I see and agree.
FLNK
ing to "automatically" trigger the structure write is looping (as you describe) but also blocking many FLNK
s that you might need for other things.
Because of that, the itemRecord's WOC
field/functionality implements that mechanism in the device support layer, without looping or using explicit FLNK
s. Would that work better for your use case?
Wouldn't that write on any field change? In my case once when LOW is set and the again when HIGH is set?
I am currently investigating disabling the FLNK
temporarily inside processCallback()
.
Yes, it would.
In our use case that was the required behavior:
Either "Loading a Snapshot", where WOC
is disabled, then many elements are set, then WOC
is enabled (which writes once).
Or "Run Mode", where changes (by the operator) are rare and single-value, so any change is triggering the structure write.
DEFACTN
is needed to decide what to do if the itemRecord is FLNK
'd to or CA writes to the PROC
field (= remote FLNK
). Useful or not, it can happen and the record needs to do something.
I had considered another option for the data elements, so that setting 'woc=n' in an element would not trigger writing the structure in WOC mode, but that was deemed unnecessary by my users.
Doable with not too much effort.
I will try WOC
.
For the moment, I have disabled FLNK
inside processCallback()
by sabotaging and later restoring its lset
pointer. That seems to do the trick.
FYI: My "hack" in processCallback looks like this:
dbScanLock(prec);
ProcessReason oldreason = pvt->reason;
pvt->reason = reason;
struct lset *lset = prec->flnk.lset;
if (reason != writeComplete && prec->scan != menuScanI_O_Intr)
prec->flnk.lset = NULL;
if (prec->pact)
reProcess(prec);
else
dbProcess(prec);
pvt->reason = oldreason;
prec->flnk.lset = lset;
dbScanUnlock(prec);