g+
g+ Communities
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  <20122013  2014  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014 
<== Date ==> <== Thread ==>

Subject: Re: Creating INLINK/OUTLINK in record support
From: Benjamin Franksen <benjamin.franksen@helmholtz-berlin.de>
To: <tech-talk@aps.anl.gov>
Date: Sat, 25 Aug 2012 01:32:28 +0200
tl;dr: I propose some new features for SNL to improve mass pv assign, feedback
appreciated

Am Freitag, 24. August 2012, 00:47:22 schrieb Zhang, Dehong:
> I am trying to build an IOC/record to watch and protect a large system,
> where I need to access 100+ PVs.  Since those PVs have very similiar
> names, I was hoping to hard-code the common part of the names, then use
> something like the dbNameToAddr function to create an array of links to
> use in for loops.

Others suggested you use SNL, which prompts me to ask what features you or
others could imagine that would make this kind of "mass PV assign"
applications easier to code.

The state of the art in SNL is this: you do it either statically, e.g.

double x[100];

assign x to {
        "PV0",
        "PV1",
        "PV2",
        ...etc etc...
};

or dynamically, at runtime, e.g.

double x[100];

assign x to {};

entry {
        int i;
        for (i=0; i < 100; i++) {
                char name[50];
                snprintf(name, 50, "PV%d", i);
                pvAssign(x[i], name);
        }
}

The first solution involves lots of repetition and I do not recommend it for
more than a hand full of PVs. The second solution suffers mainly from (a) the
poor string handling capabilities of C and (b) a less than optimal integration
of pvAssign with SNL.

This is the point where I hijack the thread to discuss possible improvements.

As to (a):

In the above example I would say the (syntactic) overhead of sprintf-ing into
a buffer is acceptable, but if the correspondence between name and array
position is more complex then it gets a bit heavy. It gets still heavier if
you want to use program arguments, since the automatic macro (i.e. program
argument) replacement works only for static assign clauses.

I have been wondering how to improve on this. One idea is to automatically
resolve macros for the PV name argument of pvAssign, just like with the static
assign clause. Another idea is to improve pvAssign so that it treats the
resulting PV name as a stream. In other words, we allow printf-like variable
number of arguments to pvAssign. In the above code snippet we could say

        for (i=0; i < 100; i++) {
                pvAssign(x[i], "PV%d", i);
        }

Combined with the macro replacement, you could even say

        for (i=0; i < 100; i++) {
                pvAssign(x[i], "{DEVICE}:channel%d", i);
        }

Alternatively, it might be better to let the user specify the buffer size:

(1)             pvAssign(x[i], 50, "PV%d", i);

Automatically expanding macros would not be necessary as you could write

                pvAssign(x[i], "%s:channel%d", macValueGet("DEVICE"), i);

One might ask oneself why such a function cannot be written by the user. Well,
it can:

%{
pvStat seq_pvAssignV(SS_ID ssId, CH_ID chId, unsigned size, const char *fmt,
...)
{
        char buf[size];
        va_list args;
        va_start(args, fmt);
        vsnprintf(buf, size, fmt, args);
        va_end(args);
        return seq_pvAssign(ssId, chId, buf);
}
}%

(This assumes your C compiler supports C99, for non-C99 compilers you have to
use a non-portable call to alloca() or use a malloc()/free() pair.)

Unfortunately a look at the call-site of our self-written pvAssignV reveals
that it is not really a built-in function:

entry {
        int i;
        for (i = 0; i < 100; i++) {
                seq_pvAssignV(ssId, pvIndex(a[i]), 10, "PV%d", i);
        }
}

This makes me think that it would be nice if the user could extend SNL with
other pv functions, so we could use the simpler notation (1) even for our
self-written pvAssignV. This would be possible with no changes to the SNL
compiler simply by using the macro preprocessor, i.e.

        #define pvAssignV(v,sz,fmt,...) seq_pvAssignV(ssId, pvIndex(v), sz,
__VA_ARGS__)

except that, again, the "..." and __VA_ARGS__ have been standardised only in
C99. Ask me what I think about Microsoft and their deliberate refusal to
support C99.

Actually, if only we could assume a C99 capable compiler, there would be no
need to treat built-in functions in any special way by the SNL compiler.

SInce we cannot, we need to tell the compiler that certain functions are to be
treated specially, so the compiler can insert the appropriate ssId and pvIndex
calls. The obvious place is inside the program, and a simple solution might be
to just declare a (escaped C) function in SNL as a special pvFunction:

        pvFunction pvAssignV;

where it is understood that the first two arguments must be of type SS_ID and
CH_ID. A more flexible, but also more complex and a bit more verbose, way to do
this would require to specify which arguments should be handled specially;
specifically, which is the hidden ssId argument, and at which positions does
the function expect a channel rather than a value. We could, for instance,
mirror the (thanks to M$ unavailable) varargs macro and say

        macro pvAssignV(v, ...) = seq_pvAssignV(ssId, pvIndex(v), ...);

As to (b):

A problem with dynamic PV assignment is that the option -c (which is turned on
by default) does not take calls to pvAssign into account. This option takes
care that all (statically) assigned channels have connected (and for those
that are monitored, an initial monitor event has been received) before state
sets are started. You can fake this, at least for the connect test, by doing

ss test {
        state init {
                when (pvConnectCount() == pvAssignCount()) {
                } state ...
        }
        ...
}

but this has a number of disadvantages:

* There is no check for initial monitor events, so you cannot know whether teh
values of monitored variables are actually valid. You can use event flags and
sync them to the variables in question, but this further complicates your
program.
* In case some of the underlying PVs cannot be connected (maybe you misspelled
the names, IOCs are down, network hangs, whatever) there is no feedback: your
program just hangs and nothing happens. Again, you can code around this:

ss test {
        state init {
                when (pvConnectCount() == pvAssignCount()) {
                } state ...
                when(delay(2.0)) {
                        printf("still waiting for some PVs to connect...\n");
                } state init
        }
        ...
}

but again here, too, this further complicates your program which hasn't done
anything useful yet. It gets worse with more than one state set per program:
either you repeat this stuff in each state set, or you create yet another state
set only for checking/reporting connection status and use an event flag to
communicate readiness to the other state sets.

Instead I am thinking about adding yet another built-in function

        boolean pvConnected(); /* no arguments */

with the idea that you call this inside a when clause. It would return true
only if all PVs visible at the point of call (according to the static scoping
rules) have connected and received an initial monitor event (if they are
monitored), just like with the +c option, but taking preceeding calls to
pvAssign into account. It would also regularly print the usual message
reporting how many channels have connected etc.

Alternatively, and even easier to use, I could add a _state set_ option +c,
that does the same. For instance

entry {
        for (...)
                pvAssign(...)
}

ss test {
        option +c;
        state init {
                /* can assume here that above pvAssign have connected */
                ...
        }
}

This is a bit less flexible, in that this works for pvAssign calls inside the
global entry{} block, but not for pvAssign calls inside a state set.

Ok, enough for one message. Thanks to anyone who has actually read until this
point. I would very much appreciate any kind of feedback.
--
Ben Franksen
()  ascii ribbon campaign - against html e-mail
/\  www.asciiribbon.org   - against proprietary attachments

________________________________

Helmholtz-Zentrum Berlin für Materialien und Energie GmbH

Mitglied der Hermann von Helmholtz-Gemeinschaft Deutscher Forschungszentren e.V.

Aufsichtsrat: Vorsitzender Prof. Dr. Dr. h.c. mult. Joachim Treusch, stv. Vorsitzende Dr. Beatrix Vierkorn-Rudolph
Geschäftsführung: Prof. Dr. Anke Rita Kaysser-Pyzalla, Thomas Frederking

Sitz Berlin, AG Charlottenburg, 89 HRB 5583

Postadresse:
Hahn-Meitner-Platz 1
D-14109 Berlin

http://www.helmholtz-berlin.de


Replies:
Re: Creating INLINK/OUTLINK in record support Tim Mooney
References:
Creating INLINK/OUTLINK in record support Zhang, Dehong
Re: Creating INLINK/OUTLINK in record support Andrew Johnson
RE: Creating INLINK/OUTLINK in record support Zhang, Dehong

Navigate by Date:
Prev: Re: EDM X Error on Linux John William Sinclair
Next: Re: EDM X Error on Linux Jason Abernathy
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014 
Navigate by Thread:
Prev: Re: Creating INLINK/OUTLINK in record support Bruce Hill
Next: Re: Creating INLINK/OUTLINK in record support Tim Mooney
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014 
ANJ, 18 Nov 2013 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· EPICSv4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·