|
Hi André,
There is a test program in asyn called testOutputReadback. It tests the device support for the asyn:READBACK feature.
It did not test what you are trying to do, which is to modify the value of the record that has info:READBACK while the record is processing due to a write.
I just modified testOutputReadback to test this capability. I added the writeFloat64 method, and have it clip the value written to a maximum of 100.
***********************************************
(sphinx) [epics@corvette src]$ git diff testOutputReadback.cpp
diff --git a/testOutputReadbackApp/src/testOutputReadback.cpp b/testOutputReadbackApp/src/testOutputReadback.cpp
index 15877423..185d4209 100644
--- a/testOutputReadbackApp/src/testOutputReadback.cpp
+++ b/testOutputReadbackApp/src/testOutputReadback.cpp
@@ -18,6 +18,7 @@
// static const char *driverName="testOutputReadback";
#define UINT32_DIGITAL_MASK 0xFFFFFFFF
+#define MAX_FLOAT64_OUTPUT_VALUE 100.0
/** Constructor for the testOutputReadback class.
* Calls constructor for the asynPortDriver base class.
@@ -76,6 +77,15 @@ asynStatus testOutputReadback::readFloat64(asynUser *pasynUser, epicsFloat64 *va
return asynPortDriver::readFloat64(pasynUser, value);
}
+asynStatus testOutputReadback::writeFloat64(asynUser *pasynUser, epicsFloat64 value)
+{
+ if (value > MAX_FLOAT64_OUTPUT_VALUE) {
+ value = MAX_FLOAT64_OUTPUT_VALUE;
+ }
+ asynPrint(pasynUserSelf, ASYN_TRACE_FLOW, "testOutputReadback::writeFloat64 value=%f\n", value);
+ return asynPortDriver::writeFloat64(pasynUser, value);
+}
+
asynStatus testOutputReadback::readUInt32Digital(asynUser *pasynUser, epicsUInt32 *value, epicsUInt32 mask)
{
if (initialReadStatus_)
*************************************************
I have pushed this version to the master branch.
For each interface, including asynFloat64 there are 3 records using the same asyn parameter called FLOAT64_VALUE.
-
Output record without asyn:READBACK. For the asynFloat64 interface this record is $(P)AoFloat64.
-
Output record with asyn:READBACK. For the asynFloat64 interface this record is $(P)AoFloat64RB.
-
Input record with SCAN=I/O Intr. For the asynFloat64 interface this record is $(P)AiFloat64.
I have reproduced the behavior you describe.
-
If I write 105 to AoFloat64 then AoFloat64RB has the value of 100, i.e. it is clipped.
-
If I write 105 directly AoFloat64RB then is has the value 105, i.e. it is not clipped.
-
AiFloat64 has the value 100 in both cases, i.e. it is clipped.
The asyn:READBACK feature was not really intended to be used when the value is changing because the output record with asyn:READBACK was directly written and processed. Rather, it
was intended to handle the case where some other record or some action in the driver causes that parameter to change.
It is possible that the device support could be enhanced to handle the case you describe, but I think it would be fairly complex, because the record is currently processing when the
callback occurs.
In principle you could make it work now by having your driver delay the callback with the clipped value until after the output record has finished processing.
However, I think your solution of modifying the DRVH (and possibly HOPR) fields of the output record based on the AbsMaxPressure is better. It then relies on the record to enforce
the limit, and provides the opportunity to display that limit in the OPI screen.
Mark
From: Tech-talk <tech-talk-bounces at aps.anl.gov> on behalf of André Favoto via Tech-talk <tech-talk at aps.anl.gov>
Sent: Monday, June 22, 2026 7:19 AM
To: Jörn Dreyer <j.dreyer at hzdr.de>; Tech Talk <tech-talk at aps.anl.gov>
Subject: Re: ASYN parameter value vs VAL field relationship
Thanks for the reply,
The problem you have is that you assume that an output record will be updated when you change the value inside the driver code.
From asyn docs, I understand that `info(asyn:READBACK, "1")` was added exactly for this purpose:
By default output records do not update when a driver does interrupt callbacks. However, if the following info tag is added for a record in the database file then callbacks will be enabled and the output record will be updated whenever the driver does a
callback for that value.
info(asyn:READBACK, "1")
I would expect that after I modify the parameter and run "callParamCallbacks", the PV should be updated with the correct value and always stay in sync, even if that means seeing a momentary "glitch" in the VAL field.
I have successfully used that for systems that can be controlled from more than one place, say, a local HMI or the IOC.
The -SP record is always updated with the current hardware value gotten via readXXX, without driving the output.
Maybe the infotag only works if the parameter is updated via the readXXX overrides?
The other alternative I see is adding an intermediary calcout that changes DRVH
field whenever the absolute maximum is changed, like:
record(ai, "$(P)$(R)#$(PUMP)AbsMaxPressure") {
field(DESC, "Max pressure for selected syringe")
field(DTYP, "asynFloat64")
field(EGU, "bar")
field(PREC, "2")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SYR_MAX_PRESSURE")
field(FLNK, "$(P)$(R)#$(PUMP)ClipMaxPressure")
info(asyn:READBACK, "1")
}
record(calcout, "$(P)$(R)#$(PUMP)ClipMaxPressure") {
field(DESC, "Clip max pressure to AbsMaxPressure")
field(INPA, "$(P)$(R)#$(PUMP)AbsMaxPressure")
field(CALC, "A")
field(OUT, "$(P)$(R)$(PUMP)MaxPressure.DRVH PP")
}
From: Jörn Dreyer <j.dreyer at hzdr.de>
Sent: Monday, June 22, 2026 13:49
To: Tech Talk <tech-talk at aps.anl.gov>; André Favoto <andrefavotto at outlook.com>
Cc: André Favoto <andrefavotto at outlook.com>
Subject: Re: ASYN parameter value vs VAL field relationship
Hi Andre,
The problem you have is that you assume that an output record will be updated when you change the value inside the driver code.
To achive what you want, you need to define an input record that reads back the value from the driver:
record(ai, "$(P)$(R)$(PUMP)MaxPressure_RBV") {
field(DESC, "set max pressure for process")
field(DTYP, "asynFloat64")
field(EGU, "bar")
field(PREC, "2")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROC_MAX_PRESSURE")
}
And display this in the GUI. You can not use the forward link of the input record to set the correct value on the output record because this would lead to a infinite loop,
where the records call each other recursively.
If you set in the output record the HIGH and LOW limits for the alarm to the maximum and minimum values of the hardware, you could use those to give the user feedback
that the the value might be clipped by the code.
Regards
Jörn
Am Montag, 22. Juni 2026, 13:36:42 Mitteleuropäische Sommerzeit schrieb André Favoto via Tech-talk:
> Sorry, I accidentally sent the message incomplete...
>
> I am trying to control the upper limit for a given PV via asyn (same as DRVH does, but since the value is dynamic, I'd rather handle it in the driver instead of DB links).
> I have the following record:
>
> record(ao, "$(P)$(R)$(PUMP)MaxPressure") {
> field(DESC, "set max pressure for process")
> field(DTYP, "asynFloat64")
> field(EGU, "bar")
> field(PREC, "2")
> field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROC_MAX_PRESSURE")
>
> info(asyn:READBACK, "1")
> }
> And the asyn code that sets the value is
>
> else if (function == procMaxPressure_) {
> double maxPressure;
> status = getDoubleParam(addr, syrMaxPressure_, &maxPressure);
> // do not allow process pressure to be set above syringe max pressure.
> if (value > maxPressure) {
> asynPrint(pasynUser, ASYN_TRACE_ERROR, "Pressure above syringe limit, clipping to %f\n", maxPressure);
> value = maxPressure;
> }
> if (value < 0) {
> value = 0;
> }
> }
> Later on, inside writeFloat64, I do:
>
> if (status == asynSuccess && apiErrCode == ERR_NOERR) {
> setDoubleParam(addr, function, value);
> callParamCallbacks(addr);
> asynPrint(pasynUser, ASYN_TRACEIO_DRIVER, "%s:%s: port=%s, param=%s, value=%f, status=%d\n", driverName,
> functionName, this->portName, paramName, value, (int)status);
> } else {
> (...)
>
> What I would expect is that the record only assumes the value that I clipped, i.e., 0 <= value <= maxPressure
>
> However, what I see is that the asyn parameter PROC_MAX_PRESSURE is indeed clipped, but the PV value assumes whatever value I put. Assuming maxPressure=517, asynReport gives:
>
> Parameter 10 type=asynFloat64, name=PROC_MAX_PRESSURE, value=517, status=0
>
> But I successfully wrote "600" to "$(P)$(R)$(PUMP)MaxPressure":
>
> > dbgf B02-CSLab:SE-Pumps:SP1MaxPressure
> DBF_DOUBLE: 600
> I understand the asyn driver and the database are on different layers, but I would expect is that the PROC_MAX_PRESSURE param and VAL field of the record stayed in sync with the setup above.
> Maybe I misinterpreted how that is supposed to behave?
>
> I have seen the same behavior in other modules, so I'd like to check if someone has any ideas on how to handle this properly.
>
> Thanks, and sorry for the previous noise 🙂
>
> Cheers,
> André Favoto
>
>
> ________________________________
> From: Tech-talk <tech-talk-bounces at aps.anl.gov> on behalf of André Favoto via Tech-talk <tech-talk at aps.anl.gov>
> Sent: Monday, June 22, 2026 13:28
> To: Tech Talk <tech-talk at aps.anl.gov>
> Subject: ASYN parameter value vs VAL field relationship
>
> Hi folks,
> I am trying to control the upper limit for a given PV via asyn (same as DRVH does, but since the value is dynamic, I'd rather handle it in the driver instead of DB links).
> I have the following record:
>
> record(ao, "$(P)$(R)$(PUMP)MaxPressure") {
> field(DESC, "set max pressure for process")
> field(DTYP, "asynFloat64")
> field(EGU, "bar")
> field(PREC, "2")
> field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROC_MAX_PRESSURE")
>
> info(asyn:READBACK, "1")
> }
>
>
|