Experimental Physics and Industrial Control System
Hi!
I'm developing an IOC using asyn, and it controls an acquisition system,
where the resulting data is exported in PVs (AAI records). The number of
samples is controlled by another PV, and is propagated into the AAI
record from the doCallbacks*Array() functions, and can be found in the
value of the NORD field of the record. This is useful because clients
like pyepics's PV.get(), camonitor, pvget and pvmonitor all read only
NORD elements from the PV.
I'd like to perform some calculations on top of the acquisition data
(for now, things like applying a gain and offset on all elements), and
it seems more adequate to do so in a database, using records, instead of
adding parameters to my asyn driver so it can do the calculations itself
and expose another array: future developments get easier, things are
more flexible, and I have less code in my driver.
The alternatives I took a look at were aSubRecord [1] and aCalcoutRecord
[2]. Neither of them claim to do anything re. NORD of the input
parameters; given that both of them support arbitrary computations with
either of those, it makes sense that there couldn't be a default
behavior for this functionality. But it definitely feels inefficient to
run a loop for NELM (can be something like 1_000_000) elements in an
array when we have only NORD (perhaps 1_000?) valid elements.
Another IOC in my org uses aSubRecord to perform the calculation on NELM
elements, and then uses subArrayRecord [3] to expose only NORD elements
from the intermediary array, but it means adding a seemingly unnecessary
intermediary record (and I don't know if there are memory usage
implications for that).
Using aSubRecord, I came up with the following solution:
record(aSub, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentConv"){
field(SNAM, "asub_goff")
field(INPA, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentRawData CP")
field(NOA, "$(NELM)")
field(FTA, "SHORT")
field(INPB, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentRawData.NORD")
field(INPC, "1")
field(INPD, "$(S)$(RTM_CHAN)CurrOffset-SP")
field(INPE, "$(S)$(RTM_CHAN)CurrGain-SP")
field(OUTA, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentData PP")
field(FTVA, "FLOAT")
field(NOVA, "$(NELM)")
}
where asub_goff is shown at the end of this email. The highlight from it
is that it copies the value from INPB into prec->neva, which, though it
is documented as read-only in [1] (maybe that's only about DB/channel
access?), does write into the NORD field of the record in OUTA.
However, it feels a bit dirty to do things like this; supporting DB
links in the NEA field, so we could pass CurrentRawData.NORD to it
instead, and then use prec->nea in our subroutine, seems much better. I
believe the same could go for fields like NOA and NOVA, where
CurrentRawData.NELM and CurrentData.NELM, respectively, could be used
(or it could even get that information from the INPA link? though I have
no idea if that would be possible).
On top of that, the FT* behavior is a bit confusing/inconsistent. It
seems the record does convert things for scalar values, so, for example,
FTD is DOUBLE by default (per the documentation) and the value read from
it (using (epicsFloat64 *)) is correct, despite the CurrOffset-SP.VAL
field being of type LONG. But for the array inputs, my code didn't work
correctly until I specified FTA and FTVA, because the pointers in
prec->a and prec->vala were always to the underlying arrays of
epicsInt16 and epicsFloat32, respectively, despite prec->fta and
prec->ftva specifying them as DOUBLE. Is this a matter of documentation,
or would it be possible to improve the ergonomics (by automatically
setting FT* fields?), or at least have some layer print a warning about
the wrong types being used?
Am I missing some other obvious way of implementing this? Are there
better ways?
Thanks in advance,
Érico
[1] https://epics.anl.gov/base/R7-0/7-docs/subArrayRecord.html
[2] https://epics-modules.github.io/calc/aCalcoutRecord.html
[3] https://epics.anl.gov/base/R7-0/7-docs/subArrayRecord.html
asub_goff implementation file:
template <class T>
static inline void apply_fn(void *v, epicsInt16 ft, T fn)
{
if (ft == menuFtypeSHORT) fn((epicsInt16 *)v);
else if (ft == menuFtypeLONG) fn((epicsInt32 *)v);
else if (ft == menuFtypeINT64) fn((epicsInt64 *)v);
else if (ft == menuFtypeFLOAT) fn((epicsFloat32 *)v);
else if (ft == menuFtypeDOUBLE) fn((epicsFloat64 *)v);
else throw std::runtime_error("unimplemented type");
}
template <class T>
static inline void get_scalar (T &out, void *v, epicsInt16 ft)
{
apply_fn(v, ft, [&out](auto rv){ out = *rv; });
}
/** This array subroutine is used to apply gain and offset to an array,
* given the following formula:
*
* out = (in * pre_gain + offset) * post_gain
*
* This function expects as input:
* - array in A
* - array size in B
* - pre_gain in C
* - offset in D
* - post_gain in E
*
* And will write its output to:
* - array in OUTA
*/
static long asub_goff(aSubRecord *prec)
{
/* sanity checking: these input elements were provided */
if (prec->neb != 1 || prec->nec != 1 || prec->ned != 1 || prec->nee
!= 1)
return 1;
/* sanity checking: the arrays are big enough */
printf("nova %d noa %d\n", prec->nova, prec->noa);
if (prec->nova < prec->noa)
return 2;
epicsUInt32 elements;
get_scalar(elements, prec->b, prec->ftb);
double pre_gain, offset, post_gain;
get_scalar(pre_gain, prec->c, prec->ftc);
get_scalar(offset, prec->d, prec->ftd);
get_scalar(post_gain, prec->e, prec->fte);
/* write NORD field in output */
prec->neva = elements;
auto apply_goff = [pre_gain, offset, post_gain, elements](auto out,
auto in) {
for (epicsUInt32 i = 0; i < elements; i++)
out[i] = (in[i] * pre_gain + offset) * post_gain;
};
/* run apply_goff regardless of the types of INPA and OUTA,
* without having to convert anything */
apply_fn(prec->a, prec->fta,
[&prec, apply_goff](auto in) {
apply_fn(prec->vala, prec->ftva, [in, apply_goff](auto out)
{ apply_goff(out, in); });
});
return 0;
}
epicsRegisterFunction(asub_goff);
Aviso Legal: Esta mensagem e seus anexos podem conter informações confidenciais e/ou de uso restrito. Observe atentamente seu conteúdo e considere eventual consulta ao remetente antes de copiá-la, divulgá-la ou distribuí-la. Se você recebeu esta mensagem por engano, por favor avise o remetente e apague-a imediatamente.
Disclaimer: This email and its attachments may contain confidential and/or privileged information. Observe its content carefully and consider possible querying to the sender before copying, disclosing or distributing it. If you have received this email by mistake, please notify the sender and delete it immediately.
- Replies:
- Re: Standard/clean way to copy NORD from one waveform to another (and aSub limitations) Jure Varlec via Tech-talk
- Navigate by Date:
- Prev:
Re: Does anyone have documented PV Access run time ENV variables? Kim, Kuktae via Tech-talk
- Next:
Re: Epics Archiver Online Status Question Tynan Ford via Tech-talk
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
<2023>
2024
- Navigate by Thread:
- Prev:
Re: EPICS support for the Omega Platinum Series PID Controllers Michael Davidsaver via Tech-talk
- Next:
Re: Standard/clean way to copy NORD from one waveform to another (and aSub limitations) Jure Varlec via Tech-talk
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
<2023>
2024