From: Davis, Mark
Sent: Friday, May 28, 2010 10:45 AM
To: Vuppala, Vasu
Cc: Priller, John
Subject: RE: First portion of CA/PV
interface to ModBus register definitions
Vasu,
I have added
the functions to support event processing now. With these additional
functions you should have everything you need to interface with the register
definitions without any application-specific code required.
New functions:
int pvCheckEvents(long pid, uint checkEvents);
void pvCheckAllEvents(time_t *dateTime, uint *ms);
And a change to
one of the previous functions:
int pvGetVal(long pid, uint toType, uint toSize, void *toVal, uint
event);
The new
functions work together as follows:
pvCheckEvents() returns a bit-mask of the events that the last call
to pvCheckAllEvents() detected and adds to the events to be checked for the PV
the next time pvCheckAllEvents() is called.
pvCheckAllEvents() updates the bitmask of events that happened for
each PV (based solely on the calls to pvCheckEvents() since the last time
pvCheckAllEvents() was called)
These functions
should be used as follows:
Each time you get a new subscription, call pvCheckEvents() with the PID for the
channel and the checkEvents argument = to the Monitor Mask supplied with the
subscription.
On
a periodic basis (once each time through the main loop of the epics_cad task,
which should occur about 5 to 10 times per second), call pvCheckAllEvents() once,
then call pvCheckEvents() for each subscription on each connection.
The functions
work as follows:
A
checkEvents and eventsOccured bit-mask is kept for each PV. A call to
pvCheckEvents() adds the bits in the checkEvents argument to the ones already
set for the PV. Each call to pvCheckAllEvents() clears the eventsOccured
mask, updates it based on the checkEvents mask for the PV, then clears the PV’s
checkEvents mask.
Note how this
keeps things very simple: Having pvCheckAllEvents() clear the checkEvents
mask for each PV and pvCheckEvents() reassert the bits, we insure that the
checkEvents bits for each PV represent only the active subscriptions each time
pvCheckAllEvents() is called. No need for the additional memory and
complexity of trying to keep a counter for each event for each PV, or (even
worse) the structures that would be needed to support a callback approach.
The addedl
parameter in the pvGetVal() call allows you to get the current value for the PV
(event = CUR_VALUE) or the value the PV had when the specified event was
detected by the last call to pvCheckAllEvents() (e.g. event = DBE_VALUE).
This model
continues to assume that there is a single process managing all the
connections. I still think this is the best model. Basically the
loop looks like this:
-
Check
for search requests and process each one received
-
Check
for a new Virtual Circuit request (i.e. TCP connection via a call to accept())
-
Call
poll() with a timeout on each of the existing TCP connections
-
For
each connection that we received some data on, process at least one command if
possible
-
Call
pvCheckAllEvents() (keep the returned date and time for use when sending
messages that include a timestamp with the data)
-
For
each subscription on each circuit, call pvCheckEvents() and send a message (as
needed) for each event detected (use write_() so you can specify a timeout)
To avoid or at
least minimize the risk of delays caused by a connection that is having a
problems, I suggest a combination of the following:
-
ioctl(fd,
FIONREAD, …), to determine how many bytes can be read without blocking
-
Changing
an fd to non-blocking mode before doing a read()
-
Use
of read_() and write_(), which provide a timeout on a blocking read or write
-
If
an error occurs on a circuit, then close the connection for that circuit.
The read_() and
write_() functions, while not standard Linux/Unix functions, could easily be
implemented on those platforms.
While there is
always the possibility that a connection with a problem will cause a delay with
processing of the other connections, this approach still seems to be the best
in terms of avoiding the need for synchronizing multiple tasks or using large
buffers to deal with incomplete commands (which we don’t’ have the resources
for). And given the fact that we will rarely HAVE more than one
active connection, I think even that risk is not significant.
Also, keep in
mind that IOC’s have to deal with hundreds or even thousands of connections,
and I don’t believe they create one thread or task per connection. So
they must make similar compromises.
The test_pv
command has been updated so that if you don’t specify a new value for the PV it
will run a loop that checks for value change events on the specified PV.
This provides a basic example of how to use all the PV functions.
The reg command
has a new –d option for setting the deadband value for each PV. I still
have to add code to make changes persistent (i.e. make sure they are written to
a file on flash, restored on bootup, and automatically backed up to and
restored if needed from the server – all things I already do with the devdata
file, which I may simply add these values to).
The test_poll
command provides an example of how poll can be used.
NOTE: The
only event supported at this time is the value-change event (DBE_VALUE).
Adding other events would be fairly simple, but would increase memory usage and
processing time, so I think we should leave these up to the record code running
on the IOC (which is what everyone but the IOC will be using to communicate
with the device). I designed everything so it COULD easily be expanded if
necessary or desired when we port this to another platform with more resources.
Mark
From: Davis, Mark
Sent: Wednesday, May 26, 2010 10:57 AM
To: Vuppala, Vasu
Subject: First portion of CA/PV interface to ModBus register definitions
Vasu,
I have the following
functions defined and working so far:
long
pvGetPID(char *pvName);
int
pvGetInfo(long pid, PV_Info *pvInfo);
int pvGetVal(long
pid, uint toType, uint toSize, void *toVal);
int
pvSetVal(long pid, uint fromType, uint fromSize, void *fromVal);
These are defined in
deamons/modbus.c. There is a test command for them that also serves as a
basic example of their use in usr/test_pv.c
These provide basic
functionality as follows:
-
pvGetPID()
returns a PID value given the name of a value (minus the hostname portion), or
-1 if there is no such value.
-
pvGetInfo() fills
in a structure which currently contains the data type, the size, and a
bit-mapped flags value. The types are defined in include/sys/modbus.h
(DT_S16, DT_U16, DT_S32, DT_U32, DT_F32, DT_STR). The size is in
bytes. And the flags currently contains bits for read and write access
(PV_RD, PV_WR). Returns 0 if successful, < 0 if an error.
-
pvGetVal() gets
the current value of a PV in the requested format. Returns 0 if
successful, < 0 if an error.
-
pvSetVal()
attempts to write a new value to a PV. Returns 0 if successful, < 0 if
an error.
For the data types you will
need to map back and forth between the CA values and the PV_Info type values
(and of course add on the time of day, etc, for the CA types that represent
structures with more than just the PV’s value).
The functions are not quite
complete yet: For example they currently don’t attempt to check the range
of values or check for an error returned by the function that actually updates
a register value. But they appear to work otherwise, and I think the
interface provides all that is needed for just reading and writing so you can
expand your tests to work on real values. The next step will be to add
functions to support the event processing, and then go back and add more error
checking to the get and set functions.
One more note on your
testing: The processing of the analog inputs and the control logic to
operate a supply (multiplied by 8 if all 8 supplies are on) takes a
considerable chunk of CPU time. The code that updates an analog input’s
values is skipped if no change occurs, and when nothing is connected to the
inputs (as is the case with the box you are testing on), they will drift to the
bottom end of the range and not change. Coupled with the fact that the
supplies are not logically “on”, the box you are testing on has considerably
more idle time than one that has active inputs and all the supplies turned
on. Comparing the one you are testing on to the other one in my office it
looks like about a 15% difference in CPU usage.
Mark
__________ Information from ESET NOD32 Antivirus, version of virus signature
database 5147 (20100526) __________
The message was checked by ESET NOD32 Antivirus.
http://www.eset.com
__________ Information from ESET NOD32 Antivirus, version of virus signature
database 5153 (20100528) __________
The message was checked by ESET NOD32 Antivirus.
http://www.eset.com