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: Re: Using waveform to read array in StreamDevice
From: Zimoch Dirk via Tech-talk <tech-talk at aps.anl.gov>
To: "279762081 at qq.com" <279762081 at qq.com>, "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>
Date: Thu, 20 Apr 2023 10:26:40 +0000
Hello Kevin,

If you want to read the full multi-line array into one waveform with one 'in'
command, you must make sure that the line end is not seen as the terminator.
Otherwise the 'in' command ends. (Actually, the driver first reads the line up
to the InTerminator and only then gives the result to the 'in' command. Thus the
'in' command never gets the chance to read more than one line.)

Thus, we first need to set InTerminator = ""; in our waveform read protocol.
That of course leads to the question how the end of the array is found. The
default without InTerminator would be ReadTimeout. Thus, don't make it wait too
long. The other possibility would be MaxInput if your input has a constant
length. Unfortunately, that seems not to be the case, because the timer column
is 7 or 8 chars wide. (maybe more after 99999999 ?)

Reading everything into one waveform now has the problem that some separators
are comma and some are newline. But that can be solved with a wildcard:
Separator = "\?";
The "\?" wildcard will match any char, comma or CR. Since the "%d" format skips
over all whitespace, it will take ignore the LF the same as it ignores the space
after the comma.
Only at the end, there will be a single LF left that we need to take care of.
To fit values like 94999442, the array data type needs to be LONG (or FLOAT or
DOUBLE).

Thus the record and protocol look like this:
record(waveform, "$(D):Counters")
{
    field(DTYP, "stream")
    field(INP, "@xxx.proto counters_in $(PORT)")
    field(NELM, "200")
    field(FTVL, "LONG")
    field(SCAN, "5 second")
}

Terminator = CR LF;
counters_in {
    InTerminator="";
    ReadTimeout=100; 1/10 second
    Separator = "\?";
    
    out "GSDAL?";
    in "%d\n";  # The \n is for the last LF after the array
}

For (2), the situation is easier, because it is only one array.
We can use Separator="\r\n" and do not need to care about left-over bytes:

record(waveform, "$(D):Array0")
{
    field(DTYP, "stream")
    field(INP, "@Kevin.proto array_in terminal")
    field(NELM, "100")
    field(FTVL, "SHORT")
    field(SCAN, "5 second")
}

array_in {
    InTerminator = "";
    ReadTimeout = 100; # 1/10 second
    Separator = "\r\n";
    
    out "GSCRD?00000000100";
    in "%d";
}

To split the table of problem (1) into columns, some tricks may help to avoid
writing your own aSubRecord code.
With an I/O Intr record, we can read every line separately and redirect the 9
values into 9 separate (scalar) records. From these, we can reconstruct arrays
using compress records:

counters_trigger {
    out "GSCRD?00000000100";
}

counterline_in {
    in "%d, "
       "%(\$1:Counter2)d, "
       "%(\$1:Counter3)d, "
       "%(\$1:Counter4)d, "
       "%(\$1:Counter5)d, "
       "%(\$1:Counter6)d, "
       "%(\$1:Counter7)d, "
       "%(\$1:Counter8)d, "
       "%(\$1:Timer)d";
}

record(bo, "$(D):Trigger")
#This record just triggers the readout
{
    field(DTYP, "stream")
    field(OUT, "@xxx.proto counters_trigger $(PORT)")
}

record(ai, "$(D):Counter1")
#This record reads one line and distributes it among
#the other records, keeping the first value for itself
#This record gets one value from one input line
{
    field(DTYP, "stream")
    field(INP, "@xxx.proto counterline_in($(D)) $(PORT)")
    field(SCAN, "I/O Intr")
    field(FLNK, "$(D):Counter1Array")
}

record(compress, "$(D):Counter1Array")
# Build the array
{
    field(INP, "$(D):Counter1")
    field(ALG, "Circular Buffer")
    field(NSAM, "$(N)")
    field(N,    "1")
}

# Now the passive receivers triggered by redirection

record(ai, "$(D):Counter2")
#This record gets a value from CounterTimer
{
    field(FLNK, "$(D):Counter2Array")
}

record(compress, "$(D):Counter2Array")
{
    field(INP, "$(D):Counter2")
    field(ALG, "Circular Buffer")
    field(NSAM, "$(N)")
    field(N,    "1")
}

#... repeat previous two records for all counters ...

record(ai, "$(D):Counter8")
{
    field(FLNK, "$(D):Counter8Array")
}

record(compress, "$(D):Counter8Array")
{
    field(INP, "$(D):Counter8")
    field(ALG, "Circular Buffer")
    field(NSAM, "$(N)")
    field(N,    "1")
}

# ... and for the timer

record(ai, "$(D):Timer")
{
    field(FLNK, "$(D):TimerArray")
}

record(compress, "$(D):TimerArray")
{
    field(INP, "$(D):Timer")
    field(ALG, "Circular Buffer")
    field(NSAM, "$(N)")
    field(N,    "1")
}

Note how the passive records are NOT "stream" records.
They get their values via redirection from the
counterline_in protocol.

The compress records will process once for each
line read from the table, adding value by value.
You may have to count processing to wait for a full
array.

The last thing we need to do is to reset the circular
buffers each time before we trigger a new read:

record(seq, "$(D):CountersReset")
{
    field(SCAN, "5 second")
    field(LNK1, "$(D):Counter1Array.RES")
    field(LNK2, "$(D):Counter2Array.RES")
    field(LNK3, "$(D):Counter3Array.RES")
    field(LNK4, "$(D):Counter4Array.RES")
    field(LNK5, "$(D):Counter5Array.RES")
    field(LNK6, "$(D):Counter6Array.RES")
    field(LNK7, "$(D):Counter7Array.RES")
    field(LNK8, "$(D):Counter8Array.RES")
    field(LNKA, "$(D):CounterTimerArray.RES")
    field(FLNK, "$(D):Trigger")
}

Two problems with this approach: When monitoring the arrays, you will see them
filling up gradually. You may want to count the numbers of processing to know
when they are "full".

Also if any other input matches the counterline_in protocol at least up to one
redirection, this will redirect the value.

As the protocol starts with "%d, %(...)d", any input line consisting of at least
2 integers from any protocol will trigger the redirect and pollute Counter2!

Your Array0 is not a problem, because the input lines consist of only 1 integer
and that does not match. But others tables will be a problem. In particular, do
not use this method and reading the whole table into one array as shown first at
the same time or you will get each value twice.

Dirk


On Wed, 2023-04-19 at 19:53 +0800, 若兰 via Tech-talk wrote:
> Hello everyone,
>     I have a Counter which can store data  to a buffer. 
>     (1)  With command "GSDAL?", an array can be read out from the buffer, which looks like this:
>       
>        fig.1
>     
>               
>     The first 8 colums are Counter data and the last coloum is Timer data.  Every line end with CR+RF. 
>     (2) With another command “GSCRD?00000000100”, the buffer can get the following array. Every line end with CR+RF.
>       
>     fig.2
>      
> 
> Question 1. How to read the  2-d array (fig 1) into EPICS? Can I use a waveform to do it? Does anybody has an example for it?
>    
> Question2. I used a waveform to read the second array (in fig.2). 
>     record(waveform, "$(D):Array0")
>    {
>       field(DTYP, "stream")
>       field(INP, "@xxx.proto  array_in  $(PORT)")
>       field(NELM, "100")
>       field(FTVL, "SHORT")
>       field(SCAN, "5 second")
>    }
> 
> in the xxx.proto:
>   array_in {
>   seperator=CR;  #or CR LF,  no different result
>   out  "GSCRD?00000000100";
>   in "%5d";
> }
>     
> and the result shows:
>  xxx:Array0 200  0  0  0   0  0  ....   0
> Surely the data is not read correctly. 
> 
> I wonder what the problem is.  
> Can I use waveform to read it?
> Is there any problem with waveform? For example, the seperator?
> If not use waveform, how can I read the array out from device buffer?
> 
> Thanks very much for any advice.
> 
> Best reguards
> 
> Kevin
> 279762081 at qq.com
> 
> 
> 

References:
Using waveform to read array in StreamDevice =?gb18030?b?yPTAvA==?= via Tech-talk

Navigate by Date:
Prev: Re: Using waveform to read array in StreamDevice Maren Purves via Tech-talk
Next: Re: Using waveform to read array in StreamDevice Zimoch Dirk 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: Using waveform to read array in StreamDevice Maren Purves via Tech-talk
Next: Search all PV available CAOUEN Loic 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, 20 Apr 2023 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·