EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

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  <20232024  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  <20232024 
<== Date ==> <== Thread ==>

Subject: Standard/clean way to copy NORD from one waveform to another (and aSub limitations)
From: Érico Nogueira Rolim via Tech-talk <tech-talk at aps.anl.gov>
To: "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>
Date: Wed, 3 May 2023 21:16:15 +0000
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  <20232024 
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  <20232024 
ANJ, 04 May 2023 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·