Copy of old lessons from afs webpage

This commit is contained in:
2026-02-06 10:10:27 +01:00
commit 80df91d0df
43 changed files with 6945 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# Lessons on Channel Access Client Programming
# - **OLD and maybe Obsolete** -
Written by Dirk Zimoch dirk.zimoch@psi.ch
This lessons should qualify a C-programmer to make efficient use
of the Channel Access client libraries and to write his/her own
EPICS client applications in C.
The webpage for this project can be found on
https://epics_training.pages.psi.ch/epics_training_webpages/tutorials/
+130
View File
@@ -0,0 +1,130 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM +=
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS +=
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+171
View File
@@ -0,0 +1,171 @@
/* caLesson1.c
by Dirk Zimoch, 2007
This is a very simple channel access client program.
It uses the EPICS R3.13 channel access functions
but can as well run with EPICS 3.14.
*/
#include <stdio.h>
/* include EPICS headers */
#include <cadef.h>
/*
#define WITH_NOT_EXISTING_CHANNEL
*/
/* Strings describing the connection status of a channel */
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
int main ()
{
chid beamcurrentID;
double beamcurrent;
chid gapID;
double gap;
#ifdef WITH_NOT_EXISTING_CHANNEL
chid doesnotexistID;
double doesnotexist;
#endif
int status;
/* Step1: initialize channel access and search for all channels. */
ca_task_initialize();
/* Assign channel names to channel IDs. */
ca_search ("ARIDI-PCT:CURRENT", &beamcurrentID);
#ifdef WITH_NOT_EXISTING_CHANNEL
ca_search ("doesnotexist", &doesnotexistID);
#endif
ca_search ("X10SA-ID-GAP:READ", &gapID);
/* Nothing has been sent to the network so far! */
/* Send all requests in parallel, wait for maximal 5.0 seconds. */
printf ("searching ...\n");
status = ca_pend_io(5.0);
/* This ca_pend_io() is a very expensive action in terms of network
bandwidth because UDP broadcasts are sent to all IOCs in the subnet.
For every broadcast, each IOC has to check if it owns one of the
requested channels. If no IOC replies, the boradcast request
is repeated up to 100 times.
Do not search for obsolete channels! If channels have been
removed, also remove them from your clients to reduce unnecessary
broadcast traffic. Check the spelling of channel names if
channels don't connect.
One broadcast package can contain many channel requests. This
is much more efficient than sending only one request at a time.
Thus, always try to connect all channels at once, using only
one ca_pend_io() after all ca_search() calls. This also speeds up
your program: waiting 10 seconds for 1000 channels in parallel
is much shorter than even waiting only 1 second for 1000 sequential
channel searches. ca_pend_io() returns early when all channels are
found.
*/
/* Check for errors */
switch (status)
{
case ECA_NORMAL:
printf ("all channels found\n");
break;
case ECA_TIMEOUT:
printf ("some channels not found yet\n");
break;
default:
printf ("unexpected error while searching: %s\n",
ca_message(status));
}
/* If not all channels can be found now, the IOC is probably down.
Searching continues in the background and channels connect
automatically when the IOC comes up.
Try to uncomment the #define WITH_NOT_EXISTING_CHANNEL above to
see what happens if a channel cannot be found.
Normally, ca_search() should not be called any more after startup.
There may be exceptions, when channels are added dynamically to a
running program. But this is not the normal case.
Connected channels may disconnect and reconnect later automatically
when an IOC reboots. Always keep this in mind when doing any
network traffic. Any long-lived program, such as GUIs or servers,
MUST be written in a way to survive disconnected channels and
they MUST react in a reasonable manner.
It depends on the application and is generally is your problem what
"reasonable" means.
*/
/* Step 2: do channel access data transfer. */
ca_get(DBR_DOUBLE, beamcurrentID, &beamcurrent);
#ifdef WITH_NOT_EXISTING_CHANNEL
ca_get(DBR_DOUBLE, doesnotexistID, &doesnotexist);
#endif
ca_get(DBR_DOUBLE, gapID, &gap);
/* Nothing has been sent to the network so far! */
/* Send all request in parallel, wait for maximal 1.0 second. */
printf ("reading ...\n");
status = ca_pend_io(1.0);
/* As before, it increases network performance to do as many ca_get()
calls as possible with one ca_pend_io(). In opposite to searching,
data transfer is done via TCP. Thus, it affects only the client
and the IOC and all network components in between. It does not
affect all IOCs on the same network as searching does! But still,
many requests can be sent in the same message if they go to the
same IOC -- which is often the case. Luckily, you don't have to
care about this. Just always try to read as many channels as
possible in parallel.
*/
switch (status)
{
case ECA_NORMAL:
printf ("all values received\n");
break;
case ECA_TIMEOUT:
printf ("some values not received\n");
break;
default:
printf ("unexpected error while reading: %s\n",
ca_message(status));
}
/* Print values of all channels but inform the user
if a channel is connected or not. The value of a
not connected channel is not valid, of course.
Never take such a value for serious!
Always when the result of ca_pend_io() after ca_get()
is not ECA_NORMAL, you MUST check ca_state() of all
involved channels before trusting any value.
*/
printf ("Beam current (%s): %g\n",
channel_state_str[ca_state(beamcurrentID)],
beamcurrent);
#ifdef WITH_NOT_EXISTING_CHANNEL
printf ("Does not exist (%s): %g\n",
channel_state_str[ca_state(doesnotexistID)],
doesnotexist);
#endif
printf ("Gap (%s): %g\n",
channel_state_str[ca_state(gapID)],
gap);
/* Last step: free all channel access resources */
ca_task_exit();
return 0;
}
+130
View File
@@ -0,0 +1,130 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM +=
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS +=
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+119
View File
@@ -0,0 +1,119 @@
/* caLesson2.c
by Dirk Zimoch, 2007
In this lesson, we get some more information out of EPICS
and store the data a bit more structured.
For this purpose, we define a "PV" structure and some macros
to work on this structure. You should be familiar with macro
programming.
*/
#include <stdio.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
/* Strings describing the connection status of a channel */
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a "process variable" (PV) */
typedef struct {
chid chid;
int status;
struct dbr_ctrl_double ctrl;
} epicsDoublePV;
/* print (some of) the contents of our PV */
void printPV(const epicsDoublePV* pv)
{
if (ca_state(pv->chid) == cs_conn)
{
/* EPICS can give you a lot more infos than only the
naked value. The most important one is the severity
because if it is INVALID_ALARM, the IOC says:
"Do not trust this value, it's something wrong with it."
See /usr/local/epics/base/include/alarm.h.
Precision and units are useful for displaying or prining
the value. Limits can be used to scale a GUI display
and to know the accepted range of set values.
See /usr/local/epics/base/include/db_access.h for
all existing DBR_* types and dbr_* structures.
*/
printf("%s = %.*f %s %s range:[%.*f,%.*f] setrange:[%.*f,%.*f]\n",
ca_name(pv->chid),
pv->ctrl.precision, pv->ctrl.value, pv->ctrl.units,
epicsAlarmSeverityStrings[pv->ctrl.severity],
pv->ctrl.precision, pv->ctrl.lower_disp_limit,
pv->ctrl.precision, pv->ctrl.upper_disp_limit,
pv->ctrl.precision, pv->ctrl.lower_ctrl_limit,
pv->ctrl.precision, pv->ctrl.upper_ctrl_limit);
}
else
{
printf("%s: <%s>\n",
ca_name(pv->chid), channel_state_str[ca_state(pv->chid)]);
}
}
/* Define a macro around ca_get to work with the above structure.
It first checks that the channel is currently connected and then
does the actual ca_get. Finally, the status is stored in the PV and
SEVCHK is called to print an error message if the status indicates failure.
See /usr/local/epics/base/include/cadef.h for SEVCHK.
The DBR_* type must match the dbr_* structure where the data is stored.
*/
#define caget(pv) SEVCHK(\
(pv).status = (ca_state((pv).chid) != cs_conn ? ECA_DISCONN : \
ca_get(DBR_CTRL_DOUBLE, (pv).chid, &(pv).ctrl)), ca_name((pv).chid))
int main()
{
epicsDoublePV beamcurrent, gapread, gapset;
double search_timeout = 5.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
/* Step1: initialize channel access and search for all channels. */
ca_task_initialize();
ca_search("ARIDI-PCT:CURRENT", &beamcurrent.chid);
ca_search("X10SA-ID-GAP:READ", &gapread.chid);
ca_search("X10SA-ID-GAP:SET", &gapset.chid);
/* Send all collected searches and wait in parallel
until they have connected (or until time runs out).
*/
status = ca_pend_io(search_timeout);
/* Use channel access error reporting facility */
SEVCHK(status, "ca_search");
/* You may also try what happens if you misspell
one of the above channel names.
*/
/* Step 2: get value plus other important infos */
caget(beamcurrent);
caget(gapread);
caget(gapset);
/* Send all collected requests and wait until all have returned. */
status = ca_pend_io(get_timeout);
SEVCHK(status, "ca_get");
/* Step 3: use the data */
printPV(&beamcurrent);
printPV(&gapread);
printPV(&gapset);
/* Last step: free all channel access resources */
ca_task_exit();
return 0;
}
+130
View File
@@ -0,0 +1,130 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM +=
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS +=
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+338
View File
@@ -0,0 +1,338 @@
/* caLesson3.c
by Dirk Zimoch, 2007
Meanwhile, we know how to read double values from EPICS.
Now we will learn about other data types. Some include
more information than others.
And we will read in a loop.
*/
#include <stdio.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
/* Strings describing the connection status of a channel */
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a "process variable" (PV)
Note that it now contains two different buffers, info and data.
See /usr/local/epics/base/include/db_access.h for
different dbr_* structures.
We use dbr_ctrl_* to store full information about a PV. This
information normally does not change, it is "static". Thus,
we need to read it only once.
We use dbr_sts_* structures to store the "dynamic" data. This
normally changes frequently. Thus, we read it repeatedly.
We define different types of PVs for different data types.
When reading info and data (via cainfo and caget) we must specify
what data type we have. We use the DBF_* macros for that.
At the moment, only support DBF_DOUBLE, DBF_LONG, DBF_STRING, DBF_ENUM.
Note: The info structure is nice for displaying and formatting a value
but it is not necessary. You can perfectly live without it and use
only dbr_sts_* structures in your program.
But always read at least dbr_sts_* (or longer structures), because it
contains the very important severity information.
If severity is INVALID_ALARM (=3) (see /usr/local/epics/base/include/alarm.h)
the value is really not valid! One reason can be that the driver on the
IOC cannot read the value from the hardware.
Don't ignore that or your application may read rubbish and fail unexpectedly.
*/
typedef struct {
chid channel;
int status;
struct dbr_ctrl_double info;
struct dbr_sts_double data;
} epicsDoublePV;
/* There is more than just double values, e.g. long integers */
typedef struct {
chid channel;
int status;
struct dbr_ctrl_long info;
struct dbr_sts_long data;
} epicsLongPV;
/* Enums are something special: they have a table of strings attached */
typedef struct {
chid channel;
int status;
struct dbr_ctrl_enum info;
struct dbr_sts_enum data;
} epicsEnumPV;
/* Strings don't have a dbr_ctrl_ structure. They are too primitive */
typedef struct {
chid channel;
int status;
struct dbr_sts_string data;
} epicsStringPV;
/* Print the contents of a PV
We have have to print different information for different
types of PVs. Note how static information (e.g. 'units') is taken from info
and dynamic information ('value' and 'severity') is taken from from data.
Also note that the data type of a PV is not necessarily the same as the
native type of the channel. EPICS converts the types automatically.
In a later lesson, we will learn how to select the correct dbr_* structure
automatically from the native channel type. This is useful for interfaces
to scripting languages.
*/
void printDoublePV(const epicsDoublePV* pv)
{
if (ca_state(pv->channel) != cs_conn)
{
printf("%s: <%s>\n",
ca_name(pv->channel), channel_state_str[ca_state(pv->channel)]);
return;
}
/* Print channel name, native channel type,
value, units, severity, range and setrange */
printf("%s (%s as DOUBLE) = %#.*f %s %s range:[%#.*f ... %#.*f] setrange:[%#.*f ... %#.*f]\n",
/* Get name and native type from channel ID */
ca_name(pv->channel), dbf_type_to_text(ca_field_type(pv->channel)),
/* Get static info 'precision' and 'units', and dynamic data 'value' */
pv->info.precision, pv->data.value, pv->info.units,
/* Get dynamic data 'severity' */
epicsAlarmSeverityStrings[pv->data.severity],
/* Get more static infos */
pv->info.precision, pv->info.lower_disp_limit,
pv->info.precision, pv->info.upper_disp_limit,
pv->info.precision, pv->info.lower_ctrl_limit,
pv->info.precision, pv->info.upper_ctrl_limit);
}
void printLongPV(const epicsLongPV* pv)
{
if (ca_state(pv->channel) != cs_conn)
{
printf("%s: <%s>\n",
ca_name(pv->channel), channel_state_str[ca_state(pv->channel)]);
return;
}
/* Print channel name, native channel type,
value, units, severity, range and setrange
Note: In EPICS, LONG means 32 bit, INT and SHORT both mean 16 bit.
*/
printf("%s (%s as LONG) = %i %s %s range:[%i ... %i] setrange:[%i ... %i]\n",
/* This is similar to the above case but uses long everywhere */
ca_name(pv->channel), dbf_type_to_text(ca_field_type(pv->channel)),
pv->data.value, pv->info.units,
epicsAlarmSeverityStrings[pv->data.severity],
pv->info.lower_disp_limit,
pv->info.upper_disp_limit,
pv->info.lower_ctrl_limit,
pv->info.upper_ctrl_limit);
}
void printEnumPV(const epicsEnumPV* pv)
{
int i;
if (ca_state(pv->channel) != cs_conn)
{
printf("%s: <%s>\n",
ca_name(pv->channel), channel_state_str[ca_state(pv->channel)]);
return;
}
/* Print channel name, native channel type,
value as number and as string, severity, and all defined strings.
Note that enums don't have units and instead of a range,
they have a list of strings - one for each state (max 16).
*/
printf("%s (%s as ENUM) = %i = \"%s\" %s %i strings:",
/* Get name and native type from channel ID */
ca_name(pv->channel), dbf_type_to_text(ca_field_type(pv->channel)),
/* Get dynamic data 'value' and convert it to a string using the
static information 'strs'. Never forget to check if value
is within bounds or unexpected "segmentation fault" crashes
may happen.
*/
pv->data.value,
pv->data.value < pv->info.no_str ? pv->info.strs[pv->data.value] : "",
/* Get dynamic data 'severity' */
epicsAlarmSeverityStrings[pv->data.severity],
/* Get all defined stings for this channel (from static information) */
pv->info.no_str);
for (i = 0; i < pv->info.no_str; i++)
{
printf("%s\"%s\"", i>0 ? "," : "", pv->info.strs[i]);
}
printf ("\n");
}
void printStringPV(const epicsStringPV* pv)
{
if (ca_state(pv->channel) != cs_conn)
{
printf("%s: <%s>\n",
ca_name(pv->channel), channel_state_str[ca_state(pv->channel)]);
return;
}
/* Print channel name, native channel type,
value and severity */
printf("%s (%s as STRING) = \"%s\" %s\n",
ca_name(pv->channel), dbf_type_to_text(ca_field_type(pv->channel)),
pv->data.value,
epicsAlarmSeverityStrings[pv->data.severity]);
}
/* Connect a PV to a channel */
#define casearch(name, pv) SEVCHK(\
(pv).status = (ca_search((name), &(pv).channel)), name)
/* Wrapper macros to read 'info' and 'data' fields of a PV
Call cainfo once during initialization to get all available infos.
Later, call caget to get only the dynamic information (value and severity).
Both macros need an additiional DBF_* argument to know the data type.
The dbf_type_to_DBR_* macros from db_access.h are used to translate
DBF_* to the full data structure (either DBR_CTRL_* or DBR_STS_*).
Both macros do nothing when the channel is disconnected.
*/
/* get full information about a PV */
#define cainfo(pv, type) SEVCHK(\
(pv).status = (ca_state((pv).channel) != cs_conn ? ECA_DISCONN : \
ca_get(dbf_type_to_DBR_CTRL(type), (pv).channel, &(pv).info)), ca_name((pv).channel))
/* get only usually changing information about a PV */
#define caget(pv, type) SEVCHK(\
(pv).status = (ca_state((pv).channel) != cs_conn ? ECA_DISCONN : \
ca_get(dbf_type_to_DBR_STS(type), (pv).channel, &(pv).data)), ca_name((pv).channel))
int main()
{
epicsDoublePV gapreadD, gapdoneD;
epicsLongPV gapreadL, gapdoneL;
epicsStringPV gapreadS, gapdoneS;
epicsEnumPV gapreadE, gapdoneE;
double search_timeout = 5.0; /* seconds */
double get_timeout = 1.0; /* seconds */
double loop_period = 5.0; /* seconds */
int num_turns = 6;
int i;
/* Step1: initialize channel access and search for all channels. */
ca_task_initialize();
/* Let's have a look how EPICS conencts to different types of PVs.
We try here to connect an analogue value and a discrete value
to double, long, string and enum PVs. We'll see what happens.
*/
casearch("X10SA-ID-GAP:READ", gapreadD);
casearch("X10SA-ID-GAP:DONE", gapdoneD);
casearch("X10SA-ID-GAP:READ", gapreadL);
casearch("X10SA-ID-GAP:DONE", gapdoneL);
casearch("X10SA-ID-GAP:READ", gapreadS);
casearch("X10SA-ID-GAP:DONE", gapdoneS);
casearch("X10SA-ID-GAP:READ", gapreadE);
casearch("X10SA-ID-GAP:DONE", gapdoneE);
SEVCHK(ca_pend_io(search_timeout), "casearch");
/* Step 2: get available infos */
/* We get all the static information now and the dynamic
information in a loop. That gives us all the infos we
need for nice formatting etc., but saves network bandwidth
in the loop.
Take care that the DBF_* matches the PV type.
Skip the string PVs here because they don't have info.
*/
cainfo(gapreadD, DBF_DOUBLE);
cainfo(gapdoneD, DBF_DOUBLE);
cainfo(gapreadL, DBF_LONG);
cainfo(gapdoneL, DBF_LONG);
cainfo(gapreadE, DBF_ENUM);
cainfo(gapdoneE, DBF_ENUM);
/* The above cainfo calls only fill the 'info' structure of the PV.
Let's as well fill the 'data' structure. Of course we could
as well do this in the loop, but I want to print the values
once before entering the loop.
*/
caget(gapreadD, DBF_DOUBLE);
caget(gapdoneD, DBF_DOUBLE);
caget(gapreadL, DBF_LONG);
caget(gapdoneL, DBF_LONG);
caget(gapreadS, DBF_STRING);
caget(gapdoneS, DBF_STRING);
caget(gapreadE, DBF_ENUM);
caget(gapdoneE, DBF_ENUM);
/* Send all above requests in parallel and wait for reply */
SEVCHK(ca_pend_io(get_timeout), "cainfo");
printf("Init\n");
printDoublePV(&gapreadD);
printDoublePV(&gapdoneD);
printLongPV(&gapreadL);
printLongPV(&gapdoneL);
printStringPV(&gapreadS);
printStringPV(&gapdoneS);
printEnumPV(&gapreadE);
printEnumPV(&gapdoneE);
/* Step 3: enter the main loop */
for (i = 1; i <= num_turns; i++)
{
/* Wait some time while doing channel access in the background.
This allows to handle disconnection/connection and other
"background activity" while we wait. We will learn more
about this in future lessons when we use callbacks and
monitors.
*/
printf("Waiting for %g seconds\n", loop_period);
ca_pend_event(loop_period);
/* Get all current values and print them */
printf("Turn %d/%d\n", i, num_turns);
caget(gapreadD, DBR_DOUBLE);
caget(gapdoneD, DBR_DOUBLE);
caget(gapreadL, DBR_LONG);
caget(gapdoneL, DBR_LONG);
caget(gapreadS, DBR_STRING);
caget(gapdoneS, DBR_STRING);
caget(gapreadE, DBR_ENUM);
caget(gapdoneE, DBR_ENUM);
SEVCHK(ca_pend_io(get_timeout), "caget");
printDoublePV(&gapreadD);
printDoublePV(&gapdoneD);
printLongPV(&gapreadL);
printLongPV(&gapdoneL);
printStringPV(&gapreadS);
printStringPV(&gapdoneS);
printEnumPV(&gapreadE);
printEnumPV(&gapdoneE);
}
/* Last step: free all channel access resources */
printf("Done\n");
ca_task_exit();
return 0;
}
+130
View File
@@ -0,0 +1,130 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM +=
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS +=
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+13
View File
@@ -0,0 +1,13 @@
The two files caLesson4a.c and caLesson4b.c do exactly
the same.
The difference is that caLesson4a.c uses EPICS 3.13-style
functions which are deprecated in EPICS 3.14.
caLesson4a.c compiles with 3.13 and 3.14,
caLesson4b.c only compiles with 3.14.
To see what has changed try
diff caLesson4a.c caLesson4b.c
or
tkdiff caLesson4a.c caLesson4b.c
+118
View File
@@ -0,0 +1,118 @@
/* caLesson4.c
by Dirk Zimoch, 2007
In this lesson we will learn to use monitors to read channels
whenever they change instead of polling them.
Whenever you need to know about changes quickly, use monitors
instead of high rate polling. It unnecessarily wastes network
bandwidth to ask for a value 10 times per second when it only
changes about once per minute. With any poll rate, you will
always have a delay and you might still miss short peaks. With
monitors you won't. And it only produces network traffic when
something "interesting" happens.
For analog (i.e. DOUBLE) values, it is defined in the record
how much change is required to be "interesting". For other types,
e.g. ENUM, every change is "interesting".
To reduce the level of confusion, we leave away PV and macros for
now and use the CA fundtions directly.
This file uses EPICS 3.13 functions.
*/
#include <stdio.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
/* This is a user-defined callback function.
Whenever a channel has a new value, this function is called.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
We don't use the fields 'count' and 'usr' here. The field 'count'
if for arrays (that comes later) and 'usr' is an arbitrary pointer
which you can pass to the monitor installation function (see below).
*/
static void monitor(struct event_handler_args args)
{
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Let's have a look at the type of the data.
It should be one of the types that we have requested.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
const struct dbr_sts_double* data = args.dbr;
printf ("%s = %#g %s\n",
ca_name(args.chid), data->value,
epicsAlarmSeverityStrings[data->severity]);
break;
}
case DBR_STS_ENUM:
{
const struct dbr_sts_enum* data = args.dbr;
printf ("%s = %i %s\n",
ca_name(args.chid),
data->value,
epicsAlarmSeverityStrings[data->severity]);
break;
}
default:
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
int main()
{
char* gapName="X10SA-ID-GAP:READ";
char* doneName="X10SA-ID-GAP:DONE";
chid gapChannel, doneChannel;
double search_timeout = 5.0; /* seconds */
/* Step1: initialize channel access and search for all channels. */
ca_task_initialize();
ca_search(gapName, &gapChannel);
ca_search(doneName, &doneChannel);
SEVCHK(ca_pend_io(search_timeout), "ca_search");
/* Step 2: setup the monitors */
/* Create two monitors with different data types.
Connect them to the same callback function.
The 4th argument will be passed to the 'usr' element
in the handler arguments. We don't need it here.
*/
ca_add_event(DBR_STS_DOUBLE, gapChannel, monitor, NULL, NULL);
ca_add_event(DBR_STS_ENUM, doneChannel, monitor, NULL, NULL);
/* In EPICS 3.13, too many different things are called
"event". I guess, this is the reason why this function
has been renamed in 3.14 to ca_create_subscription.
I would have preferred ca_create_monitor, however.
*/
SEVCHK(ca_flush_io(), "ca_add_event");
/* We have used ca_flush_io() here because there is nothing
to wait for. We just send out the request.
Note: ca_pend_io(timeout) works like ca_flush_io() plus
additional waiting for outstanding replies.
*/
/* Step 3: wait forever and do Channel Access in the background */
ca_pend_event(0.0);
/* We should never reach this point! */
printf("Done\n");
ca_task_exit();
return 0;
}
+124
View File
@@ -0,0 +1,124 @@
/* caLesson4.c
by Dirk Zimoch, 2007
In this lesson we will learn to use monitors to read channels
whenever they change instead of polling them.
Whenever you need to know about changes quickly, use monitors
instead of high rate polling. It unnecessarily wastes network
bandwidth to ask for a value 10 times per second when it only
changes about once per minute. With any poll rate, you will
always have a delay and you might still miss short peaks. With
monitors you won't. And it only produces network traffic when
something "interesting" happens.
For analog (i.e. DOUBLE) values, it is defined in the record
how much change is required to be "interesting". For other types,
e.g. ENUM, every change is "interesting".
To reduce the level of confusion, we leave away PV and macros for
now and use the CA fundtions directly.
This file uses EPICS 3.14 functions.
*/
#include <stdio.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
/* This is a user-defined callback function.
Whenever a channel has a new value, this function is called.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
We don't use the fields 'count' and 'usr' here. The field 'count'
if for arrays (that comes later) and 'usr' is an arbitrary pointer
which you can pass to the monitor installation function (see below).
*/
static void monitor(struct event_handler_args args)
{
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Let's have a look at the type of the data.
It should be one of the types that we have requested.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
const struct dbr_sts_double* data = args.dbr;
printf ("%s = %#g %s\n",
ca_name(args.chid), data->value,
epicsAlarmSeverityStrings[data->severity]);
break;
}
case DBR_STS_ENUM:
{
const struct dbr_sts_enum* data = args.dbr;
printf ("%s = %i %s\n",
ca_name(args.chid),
data->value,
epicsAlarmSeverityStrings[data->severity]);
break;
}
default:
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
int main()
{
char* gapName="X10SA-ID-GAP:READ";
char* doneName="X10SA-ID-GAP:DONE";
chid gapChannel, doneChannel;
double search_timeout = 5.0; /* seconds */
/* Step1: initialize channel access and search for all channels. */
ca_context_create(ca_disable_preemptive_callback);
/* ca_create_channel has more parameters than the old ca_search
but we don't need them here.
*/
ca_create_channel(gapName, NULL, NULL, CA_PRIORITY_DEFAULT, &gapChannel);
ca_create_channel(doneName, NULL, NULL, CA_PRIORITY_DEFAULT, &doneChannel);
SEVCHK(ca_pend_io(search_timeout), "ca_search");
/* Step 2: setup the monitors */
/* Create two monitors with different data types.
Connect them to the same callback function.
The 6th argument will be passed to the 'usr' element
in the handler arguments. We don't need it here.
*/
ca_create_subscription(DBR_STS_DOUBLE, 1, gapChannel,
DBE_VALUE|DBE_ALARM, monitor, NULL, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneChannel,
DBE_VALUE|DBE_ALARM, monitor, NULL, NULL);
/* In 3.13 we have actually used a macro with default
values for some arguments. Here, we have to specify:
* we want scalars, not arrays (count=1)
* we are interested in value and alarm changes
*/
SEVCHK(ca_flush_io(), "ca_add_event");
/* We have used ca_flush_io() here because there is nothing
to wait for. We just send out the request.
Note: ca_pend_io(timeout) works like ca_flush_io() plus
additional waiting for outstanding replies.
*/
/* Step 3: wait forever and do Channel Access in the background */
ca_pend_event(0.0);
/* We should never reach this point! */
printf("Done\n");
ca_context_destroy();
return 0;
}
+130
View File
@@ -0,0 +1,130 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM +=
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS +=
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $^ $(LDFLAGS)
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+26
View File
@@ -0,0 +1,26 @@
The two files caLesson5a.c and caLesson5b.c do the same.
The difference is that caLesson5a.c uses a EPICS 3.13-compatible
single-threaded model, whereas caLesson5a.c uses a multi-threaded
model.
caLesson5a.c compiles with 3.13 and 3.14,
caLesson5b.c only compiles with 3.14.
To see the differences try
diff caLesson5a.c caLesson5b.c
or
tkdiff caLesson5a.c caLesson5b.c
The caLesson5.db file provides a soft IOC to be used in this example.
Start it with (choose a unique string for prefix):
xterm -e iocsh caLesson5.db P=prefix &
An medm panel is also provided:
medm -x -macro P=prefix caLesson5.adl &
The programs caLesson5a and caLesson5b also need the prefix:
caLesson5a prefix
caLesson5b prefix
BIN
View File
Binary file not shown.
+138
View File
@@ -0,0 +1,138 @@
file {
name="/afs/psi.ch/project/epics/www/training/caClientLessons/caLesson5/caLesson5.adl"
version=030004
}
display {
object {
x=64
y=607
width=400
height=236
}
clr=14
bclr=4
cmap=""
gridSpacing=5
gridOn=0
snapToGrid=0
}
"color map" {
ncolors=65
colors {
ffffff,
ececec,
dadada,
c8c8c8,
bbbbbb,
aeaeae,
9e9e9e,
919191,
858585,
787878,
696969,
5a5a5a,
464646,
2d2d2d,
000000,
00d800,
1ebb00,
339900,
2d7f00,
216c00,
fd0000,
de1309,
be190b,
a01207,
820400,
5893ff,
597ee1,
4b6ec7,
3a5eab,
27548d,
fbf34a,
f9da3c,
eeb62b,
e19015,
cd6100,
ffb0ff,
d67fe2,
ae4ebc,
8b1a96,
610a75,
a4aaff,
8793e2,
6a73c1,
4d52a4,
343386,
c7bb6d,
b79d5c,
a47e3c,
7d5627,
58340f,
99ffff,
73dfff,
4ea5f9,
2a63e4,
0a00b8,
ebf1b5,
d4db9d,
bbc187,
a6a462,
8b8239,
73ff6b,
52da3b,
3cb420,
289315,
1a7309,
}
}
meter {
object {
x=13
y=83
width=198
height=117
}
monitor {
chan="$(P):READ"
clr=14
bclr=4
}
label="channel"
limits {
}
}
valuator {
object {
x=10
y=9
width=372
height=70
}
control {
chan="$(P):SET"
clr=14
bclr=4
}
label="channel"
dPrecision=1.000000
limits {
}
}
"text update" {
object {
x=221
y=101
width=138
height=65
}
monitor {
chan="$(P):DONE"
clr=14
bclr=4
}
clrmod="alarm"
limits {
}
}
+457
View File
@@ -0,0 +1,457 @@
/* caLesson5.c
by Dirk Zimoch, 2007
*/
#include <stdio.h>
#include <stdlib.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
/* Strings describing the connection status of a channel */
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a "process variable" (PV)
This time, the PV should be generic. There should be only one
type of PV for all possible data types. That means we need to
find out the data type before actually allocating memory for
the data.
Especially interfaces to scipting languages, such as TCL or IDL
have to use generic PVs, because at the time the interface is
compiled we cannot know to what type of channel the user will
connect later.
Again, we use dbr_ctrl_* structures for "static" data and
dbr_sts_* structures for "dynamic" data.
At compile-time, we don't know the exact data type of 'info' and
'data'. Thus, we have to use void* and cast to the correct type.
*/
typedef struct {
chid channel;
int status;
union {
struct dbr_ctrl_short SHORT;
struct dbr_ctrl_float FLOAT;
struct dbr_ctrl_enum ENUM;
struct dbr_ctrl_char CHAR;
struct dbr_ctrl_long LONG;
struct dbr_ctrl_double DOUBLE;
} *info;
union {
struct dbr_sts_string STRING;
struct dbr_sts_short SHORT;
struct dbr_sts_float FLOAT;
struct dbr_sts_enum ENUM;
struct dbr_sts_char CHAR;
struct dbr_sts_long LONG;
struct dbr_sts_double DOUBLE;
} *data;
} epicsPV;
/* Define some macros to get infos out of a generic PV
*/
#define PV_name(pv) (ca_name((pv)->channel))
#define PV_state(pv) (ca_state((pv)->channel))
#define PV_has_info(pv) ((pv)->info != NULL)
#define PV_has_data(pv) ((pv)->data != NULL)
#define PV_type(pv) (ca_field_type((pv)->channel))
#define PV_status(pv) ((pv)->data->SHORT.status)
#define PV_severity(pv) ((pv)->data->SHORT.severity)
#define PV_value(pv,type) ((pv)->data->type.value)
#define PV_info(pv,type) ((pv)->info->type)
/* Get the units out of the PV.
When we don't know return an empty string.
*/
const char* PV_units(epicsPV* pv)
{
if (!pv || !pv->info) return "";
switch (PV_type(pv))
{
case DBF_SHORT:
return pv->info->SHORT.units;
case DBF_FLOAT:
return pv->info->FLOAT.units;
case DBF_CHAR:
return pv->info->CHAR.units;
case DBF_LONG:
return pv->info->LONG.units;
case DBF_DOUBLE:
return pv->info->DOUBLE.units;
default:
return "";
}
}
/* Get the precision out of the PV.
When we don't know return something reasonable. */
short PV_precision(epicsPV* pv)
{
if (!pv) return 0;
switch (PV_type(pv))
{
case DBF_FLOAT:
if (!pv->info) return 4;
return ((struct dbr_ctrl_float*)pv->info)->precision;
case DBF_DOUBLE:
if (!pv->info) return 6;
return ((struct dbr_ctrl_double*)pv->info)->precision;
default:
return 0;
}
}
int cainfo(epicsPV* pv)
{
if (PV_state(pv) != cs_conn)
{
printf ("cainfo %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Read static info only once. */
if (!pv->info && PV_type(pv) != DBF_STRING)
{
int dbr_type;
dbr_type = dbf_type_to_DBR_CTRL(PV_type(pv));
if (!pv->info)
{
printf ("cainfo %s: allocating memory for static data\n", PV_name(pv));
pv->info = malloc(dbr_size[dbr_type]);
if (!pv->info) return ECA_ALLOCMEM;
}
printf ("cainfo %s: requesting static data\n", PV_name(pv));
return ca_get(dbr_type, pv->channel, pv->info);
}
return ECA_NORMAL;
}
int caget(epicsPV* pv)
{
int dbr_type;
if (PV_state(pv) != cs_conn)
{
printf ("caget %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Allocate memory only once but read dynamic data every time */
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
if (!pv->data)
{
printf ("caget %s: allocating memory for dynamic data\n", PV_name(pv));
pv->data = malloc(dbr_size[dbr_type]);
if (!pv->data) return ECA_ALLOCMEM;
}
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
return ca_get(dbr_type, pv->channel, pv->data);
}
int camonitor(epicsPV* pv, void (*monitor)(epicsPV* pv))
{
return ECA_NORMAL;
}
/* Print the contents of a generic PV.
*/
void printPV(epicsPV* pv)
{
const char* name;
const char* status_str;
const char* severity_str;
const char* units;
short precision;
name = PV_name(pv);
if (!pv)
{
printf("<NULL PV>\n");
}
if (PV_state(pv) != cs_conn)
{
/* Channel is not connected */
printf("%s: <%s>\n",
name, channel_state_str[PV_state(pv)]);
return;
}
if (!pv->data)
{
printf("%s: <caget never called>\n", name);
return;
}
status_str = epicsAlarmConditionStrings[PV_status(pv)];
severity_str = epicsAlarmSeverityStrings[PV_severity(pv)];
units = PV_units(pv);
precision = PV_precision(pv);
/* Handle different data types.
See /usr/local/epics/base/include/db_access.h for DBF_* types.
For each possible type, we have to cast 'info' and 'data'
to the correct structures before we can access the data.
*/
switch (PV_type(pv))
{
case DBF_STRING:
{
/* Print channel name, native channel type,
value and severity */
printf("%s (STRING) = \"%s\" %s:%s\n",
name,
PV_value(pv,STRING),
severity_str, status_str);
break;
}
case DBF_SHORT:
{
printf("%s (SHORT) = %hi %s %s:%s",
name,
PV_value(pv,SHORT), units,
severity_str, status_str);
if (PV_has_info(pv)) printf(" range:[%hi ... %hi] setrange:[%hi ... %hi]\n",
PV_info(pv,SHORT).lower_disp_limit,
PV_info(pv,SHORT).upper_disp_limit,
PV_info(pv,SHORT).lower_ctrl_limit,
PV_info(pv,SHORT).upper_ctrl_limit);
else printf("\n");
break;
}
case DBF_FLOAT:
{
printf("%s (FLOAT) = %#.*f %s %s:%s",
name,
precision, PV_value(pv,FLOAT), units,
severity_str, status_str);
if (PV_has_info(pv)) printf(" range:[%#.*f ... %#.*f] setrange:[%#.*f ... %#.*f]\n",
precision, PV_info(pv,FLOAT).lower_disp_limit,
precision, PV_info(pv,FLOAT).upper_disp_limit,
precision, PV_info(pv,FLOAT).lower_ctrl_limit,
precision, PV_info(pv,FLOAT).upper_ctrl_limit);
else printf("\n");
break;
}
case DBF_ENUM:
{
int i;
if (PV_has_info(pv))
{
printf("%s (ENUM) = %i = \"%s\" %s:%s %i strings:",
name,
PV_value(pv,ENUM),
PV_value(pv,ENUM) < PV_info(pv,ENUM).no_str ?
PV_info(pv,ENUM).strs[PV_value(pv,ENUM)] : "",
severity_str, status_str,
PV_info(pv,ENUM).no_str);
for (i = 0; i < PV_info(pv,ENUM).no_str; i++)
{
printf("%s\"%s\"", i>0 ? "," : "", PV_info(pv,ENUM).strs[i]);
}
printf ("\n");
}
else printf("%s (ENUM) = %i %s:%s\n",
name,
data->value,
severity_str, status_str);
break;
}
case DBF_CHAR:
{
struct dbr_ctrl_char* info = pv->info;
struct dbr_sts_char* data = pv->data;
/* Print channel name, native channel type,
value, units, severity, and ranges */
printf("%s (CHAR) = %i %s %s:%s",
name,
data->value, units,
severity_str, status_str);
if (PV_has_info(pv)) printf(" range:[%i ... %i] setrange:[%i ... %i]\n",
info->lower_disp_limit,
info->upper_disp_limit,
info->lower_ctrl_limit,
info->upper_ctrl_limit);
else printf("\n");
break;
}
case DBF_LONG:
{
struct dbr_ctrl_long* info = pv->info;
struct dbr_sts_long* data = pv->data;
/* Print channel name, native channel type,
value, units, severity, and ranges */
printf("%s (LONG) = %i %s %s:%s",
name,
data->value, units,
severity_str, status_str);
if (info) printf(" range:[%i ... %i] setrange:[%i ... %i]\n",
info->lower_disp_limit,
info->upper_disp_limit,
info->lower_ctrl_limit,
info->upper_ctrl_limit);
else printf("\n");
break;
}
case DBF_DOUBLE:
{
struct dbr_ctrl_double* info = pv->info;
struct dbr_sts_double* data = pv->data;
/* Print channel name, native channel type,
value, units, severity, and ranges */
printf("%s (DOUBLE) = %#.*f %s %s:%s",
name,
precision, data->value, units,
severity_str, status_str);
if (info) printf(" range:[%#.*f ... %#.*f] setrange:[%#.*f ... %#.*f]\n",
precision, info->lower_disp_limit,
precision, info->upper_disp_limit,
precision, info->lower_ctrl_limit,
precision, info->upper_ctrl_limit);
else printf("\n");
break;
}
}
}
epicsPV* newPV(const char* name)
{
int status;
epicsPV* pv = malloc(sizeof(epicsPV));
pv->info = NULL;
pv->data = NULL;
printf("searching for channel %s\n", name);
status = ca_search(name, &pv->channel);
/* status = ca_search(name, &pv->channel); */
SEVCHK(status, "ca_search_and_connect");
if (status != ECA_NORMAL)
{
free(pv);
return NULL;
}
return pv;
}
int main()
{
epicsPV *gapread, *gapdone;
double search_timeout = 5.0; /* seconds */
double get_timeout = 1.0; /* seconds */
double loop_period = 5.0; /* seconds */
int num_turns = 3;
int i;
/* Step1: initialize channel access and search for all channels. */
ca_task_initialize();
/* Let's have a look how EPICS conencts to different types of PVs.
We try here to connect an analogue value and a discrete value
to double, long, string and enum PVs. We'll see what happens.
*/
gapread = newPV("X10SA-ID-GAP:READ");
gapdone = newPV("X10SA-ID-GAP:DONE");
/* Send all the search requests but don't wait for connection. */
printf("sending search request\n");
ca_flush_io();
printf("doing other stuff ...\n");
/* Now, connection is done in the background.
We can use the time to initialize the rest of the program,
read files, setup the GUI, etc ... Whatever can be done
before the channels have connected.
*/
printf("finishing search request ...\n");
SEVCHK(ca_pend_io(search_timeout), "search");
printf("searching done\n");
/* Step2: get all static infos */
/*
SEVCHK(cainfo(gapread),"cainfo gapread");
SEVCHK(cainfo(gapdone),"cainfo gapdone");
SEVCHK(ca_pend_io(get_timeout), "cainfo");
*/
/* Step3: reading dynamic data periodically */
printf("\nEntering loop without static infos\n");
for (i=1; i <= num_turns; i++)
{
printf("\nTurn %d/%d\n", i, num_turns);
SEVCHK(caget(gapread), "caget gapread");
SEVCHK(caget(gapdone), "caget gapdone");
SEVCHK(ca_pend_io(get_timeout), "caget");
printPV(gapread);
printPV(gapdone);
ca_pend_event(loop_period);
}
printf("\nFinished loop\n");
SEVCHK(cainfo(gapread),"cainfo gapread");
SEVCHK(cainfo(gapdone),"cainfo gapdone");
SEVCHK(ca_pend_io(get_timeout), "cainfo");
printf("\nEntering loop with static infos\n");
for (i=1; i <= num_turns; i++)
{
printf("\nTurn %d/%d\n", i, num_turns);
SEVCHK(caget(gapread), "caget gapread");
SEVCHK(caget(gapdone), "caget gapdone");
SEVCHK(ca_pend_io(get_timeout), "caget");
printPV(gapread);
printPV(gapdone);
ca_pend_event(loop_period);
}
printf("\nFinished loop\n");
camonitor(gapread, printPV);
camonitor(gapdone, printPV);
cainfo(gapread);
caget(gapread);
cainfo(gapdone);
caget(gapdone);
ca_pend_io(get_timeout);
printPV(gapread);
printPV(gapdone);
printf("doing channel access forever ...\n");
ca_pend_event(0.0);
/* Last step: free all channel access resources */
printf("Done\n");
ca_task_exit();
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
record (ao, "$(P):SET")
{
field (PREC, "2")
field (OUT, "$(P):RUN") # Set new value
field (FLNK, "$(P):ACTIVATE") # Set $(P):DONE to ACTIVE
field (DRVL, "5")
field (DRVH, "40")
field (LOPR, "5")
field (HOPR, "40")
field (VAL, "10")
}
record (ao, "$(P):RUN") # A record that changes slowly.
{
field (VAL, "10")
field (OROC, "0.1") # Value changes only by 0.1
field (SCAN, ".1 second") # each 0.1 seconds.
field (PREC, "2")
}
record (ai, "$(P):READ")
{
field (INP, "$(P):RUN.OVAL CP") # Get value whenever it changes.
field (PREC, "2")
field (LOPR, "0")
field (HOPR, "40")
}
record (bo, "$(P):DONE")
{
field (ZNAM, "ACTIVE")
field (ONAM, "DONE")
field (PINI, "YES") # After reboot initialize
field (VAL, "1") # to state 1 (DONE)
}
record (seq, "$(P):ACTIVATE")
{
field (DOL1, "0") # Write 0 (ACTIVE)
field (LNK1, "$(P):DONE PP") # to $(P):DONE
field (FLNK, "$(P):CHECKDONE") # Check if already done
}
record (calcout, "$(P):CHECKDONE")
{
field (INPA, "$(P):READ CP")
field (INPB, "$(P):SET CP")
field (CALC, "A=B") # When READ and SET
field (OOPT, "When Non-zero") # become equal then write 1
field (OUT, "$(P):DONE PP") # to $(P):DONE.
}
BIN
View File
Binary file not shown.
+326
View File
@@ -0,0 +1,326 @@
/* caLesson5a.c
by Dirk Zimoch, 2007
This lesson introduces writing to channels and waiting for channels.
We will write a value to a (simulated) device which takes a while and
wait until the device is done. The device provides a DONE record.
We will use a monitor to get informed when the device is done without
polling it over the network.
A configuration file for a soft IOC is provided in this directory.
Before trying this program, start the soft IOC with:
xterm -e ioch caLesson5.db P=prefix &
where prefix should be something unique to you (e.g. your initials).
Use this program with the same prefix:
caLesson5a prefix
This is a single-threaded EPICS 3.13. compatible version of the example.
For the EPICS 3.14. multi-threaded version see caLesson5b.c.
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define process variable (PV) structures.
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct {
chid channel;
struct dbr_ctrl_double info;
struct dbr_sts_double data;
} epicsDoublePV;
typedef struct {
chid channel;
struct dbr_ctrl_enum info;
struct dbr_sts_enum data;
} epicsEnumPV;
/* Print NAME = VALUE for double.
Precision and units are taken from info.
*/
void printDoublePV(const epicsDoublePV* pv)
{
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
return;
}
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->info.precision, pv->data.value, pv->info.units);
if (pv->data.severity != NO_ALARM)
{
printf(" <%s %s>\n",
epicsAlarmSeverityStrings[pv->data.severity],
epicsAlarmConditionStrings[pv->data.status]);
}
else
{
printf("\n");
}
}
/* Print NAME = VALUE for enum.
VALUE is printed as string if possible, otherwise as number.
*/
void printEnumPV(const epicsEnumPV* pv)
{
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
return;
}
if (pv->data.value < pv->info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->data.value]);
else
{
printf("%s = %d",
ca_name(pv->channel), pv->data.value);
}
if (pv->data.severity != NO_ALARM)
{
printf(" <%s %s>\n",
epicsAlarmSeverityStrings[pv->data.severity],
epicsAlarmConditionStrings[pv->data.status]);
}
else
{
printf("\n");
}
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
*/
static void monitor(struct event_handler_args args)
{
printf("Monitor: ");
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
epicsDoublePV* pv = args.usr;
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
printDoublePV(pv);
break;
}
case DBR_STS_ENUM:
{
epicsEnumPV* pv = args.usr;
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
printEnumPV(pv);
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
ca_task_initialize();
sprintf(recordname, "%.19s:SET", args[1]);
ca_search(recordname, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_search(recordname, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_search(recordname, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_add_event(DBR_STS_DOUBLE, readvalue.channel, monitor, &readvalue, NULL);
ca_add_event(DBR_STS_ENUM, doneflag.channel, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor events */
ca_pend_event(0.01);
/* Unfortunately, there is no function "wait until next event".
Thus, we do "wait for 0.01 seconds and process events" in
a loop.
Note that we don't poll the IOC for "DONE"!.
This only generates network traffic when something happens
with a maximum latency of 0.01 seconds.
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_task_exit();
return 0;
}
BIN
View File
Binary file not shown.
+367
View File
@@ -0,0 +1,367 @@
/* caLesson5b.c
by Dirk Zimoch, 2007
This lesson introduces writing to channels and waiting for channels.
We will write a value to a (simulated) device which takes a while and
wait until the device is done. The device provides a DONE record.
We will use a monitor to get informed when the device is done without
polling it over the network.
A configuration file for a soft IOC is provided in this directory.
Before trying this program, start the soft IOC with:
xterm -e ioch caLesson5.db P=prefix &
where prefix should be something unique to you (e.g. your initials).
Use this program with the same prefix:
caLesson5b prefix
This is a multi-threaded EPICS 3.14. version of the example.
For the EPICS 3.13. compatible single-threaded version see caLesson5a.c.
You should be familiar with multi-threading to understand this.
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
#include <epicsEvent.h>
#include <epicsMutex.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define process variable (PV) structures.
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct {
chid channel;
struct dbr_ctrl_double info;
struct dbr_sts_double data;
} epicsDoublePV;
typedef struct {
chid channel;
struct dbr_ctrl_enum info;
struct dbr_sts_enum data;
} epicsEnumPV;
/* Being multi threaded, we must protect PV access by mutex semaphores.
We will use events to get notification of monitors
*/
epicsEventId monitorEvent = NULL;
epicsMutexId accessMutex = NULL;
/* Print NAME = VALUE for double.
Precision and units are taken from info.
*/
void printDoublePV(const epicsDoublePV* pv)
{
epicsMutexLock(accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(accessMutex);
return;
}
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->info.precision, pv->data.value, pv->info.units);
if (pv->data.severity != NO_ALARM)
{
printf(" <%s %s>\n",
epicsAlarmSeverityStrings[pv->data.severity],
epicsAlarmConditionStrings[pv->data.status]);
}
else
{
printf("\n");
}
epicsMutexUnlock(accessMutex);
}
/* Print NAME = VALUE for enum.
VALUE is printed as string if possible, otherwise as number.
*/
void printEnumPV(const epicsEnumPV* pv)
{
epicsMutexLock(accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(accessMutex);
return;
}
if (pv->data.value < pv->info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->data.value]);
else
{
printf("%s = %d",
ca_name(pv->channel), pv->data.value);
}
if (pv->data.severity != NO_ALARM)
{
printf(" <%s %s>\n",
epicsAlarmSeverityStrings[pv->data.severity],
epicsAlarmConditionStrings[pv->data.status]);
}
else
{
printf("\n");
}
epicsMutexUnlock(accessMutex);
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
In the multi threaded model, the monitor callback runs in a separate
thread. That means, the monitor function may be called at any time,
even while we are just accessing the PV from the main program. Thus,
we must protect all accesses to a PV with a mutext. Here, we use
just one global mutex. The next lesson will introduce a more
sophisticated solution.
*/
static void monitor(struct event_handler_args args)
{
printf("Monitor: ");
epicsMutexLock(accessMutex);
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
epicsMutexUnlock(accessMutex);
return;
}
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
epicsDoublePV* pv = args.usr;
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
printDoublePV(pv);
break;
}
case DBR_STS_ENUM:
{
epicsEnumPV* pv = args.usr;
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
printEnumPV(pv);
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
epicsMutexUnlock(accessMutex);
/* Inform other threads about monitor */
epicsEventSignal(monitorEvent);
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
/* Start EPICS multi-threaded */
ca_context_create(ca_enable_preemptive_callback);
/* ca_create_channel has more parameters than the old ca_search
but we don't need them here.
*/
sprintf(recordname, "%.19s:SET", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Setup an event for monitors */
monitorEvent = epicsEventCreate(epicsEventEmpty);
/* Setup a mutex semaphore to make PV access thread-safe */
accessMutex = epicsMutexCreate();
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor event */
epicsEventWait(monitorEvent);
/* This really does nothing until the mext monitor comes.
Then, monitorEvent is triggered from the monitor callback
function (see above).
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_context_destroy();
epicsMutexDestroy(accessMutex);
epicsEventDestroy(monitorEvent);
return 0;
}
+132
View File
@@ -0,0 +1,132 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM += testPV
PROGRAM += pvexample
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS_testPV += epicsPV.cc testPV.cc
SRCS_pvexample += epicsPV.cc pvexample.cc
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $(LDFLAGS) $^
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+411
View File
@@ -0,0 +1,411 @@
/* caLesson6.c
by Dirk Zimoch, 2007
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
#include <epicsEvent.h>
#include <epicsMutex.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a generic process variable (PV).
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct epicsPV epicsPV;
typedef struct monitorCallback monitorCallback;
struct monitorCallback {
monitorCallback *next;
void *userdata;
void (*callbackfunction) (void *userdata, epicsPV* pv);
};
struct epicsPV {
chid channel;
int datatype;
epicsEventId monitorEvent;
epicsMutexId accessMutex;
monitorCallback* callbacks;
union {
struct dbr_sts_string string_data;
struct {
struct dbr_ctrl_long long_info;
struct dbr_sts_long long_data;
};
struct {
struct dbr_ctrl_double double_info;
struct dbr_sts_double double_data;
};
struct {
struct dbr_ctrl_enum enum_info;
struct dbr_sts_enum enum_data;
};
};
};
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
int connectPVsFileLine(const char* file, int line, const char* name, ...)
{
va_list va;
epicsPV pv;
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
va_start(va, name);
while (name)
{
if (findPV(name))
{
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
file, line, name);
continue;
}
pv = malloc(sizeof(epicsPV));
if (!pv)
{
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
file, line, name);
return ECA_ALLOCMEM;
}
ca_create_channel(name, connectCallback, pv,
CA_PRIORITY_DEFAULT, &pv->channel);
name = va_arg(va, char*)
}
va_end(va);
}
/* Print NAME = VALUE for PV of any type.
Precision and units are taken from info.
*/
void printPV(const epicsPV* pv)
{
int status, severity;
epicsMutexLock(pv->accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(pv->accessMutex);
return;
}
switch (pv->datatype)
{
case DBF_STRING:
printf("%s = %s",
ca_name(pv->channel),
pv->string_data.value);
status = pv->string_data.status;
severity = pv->string_data.severity;
break;
case DBF_LONG:
printf("%s = %.ld %s",
ca_name(pv->channel),
pv->long_data.value, pv->long_info.units);
status = pv->long_data.status;
severity = pv->long_data.severity;
break;
case DBF_DOUBLE:
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
status = pv->double_data.status;
severity = pv->double_data.severity;
break;
case DBF_ENUM:
if (pv->enum_data.value < pv->enum_info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
else
printf("%s = %d",
ca_name(pv->channel), pv->enum_data.value);
status = pv->enum_data.status;
severity = pv->enum_data.severity;
break;
default:
printf ("%s <unsupported data type>\n",
ca_name(pv->channel));
epicsMutexUnlock(accessMutex);
return;
}
epicsMutexUnlock(accessMutex);
if (severity != NO_ALARM)
{
printf(" <%s %s>",
epicsAlarmSeverityStrings[severity],
epicsAlarmConditionStrings[status]);
}
else
{
printf("\n");
}
}
int caget(epicsPV* pv)
{
int dbr_type;
if (ca_state((pv)->channel) != cs_conn)
{
printf ("caget %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Allocate memory only once but read dynamic data every time */
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
In the multi threaded model, the monitor callback runs in a separate
thread. That means, the monitor function may be called at any time,
even while we are just accessing the PV from the main program. Thus,
we must protect all accesses to a PV with a mutext. Here, we use
just one global mutex. The next lesson will introduce a more
sophisticated solution.
*/
static void monitor(struct event_handler_args args)
{
epicsPV* pv = args.usr;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Lock PV to be thread safe */
epicsMutexLock(pv->accessMutex);
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
case DBR_STS_ENUM:
{
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
epicsMutexUnlock(accessMutex);
/* Inform other threads about monitor */
epicsEventSignal(monitorEvent);
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
/* Start EPICS multi-threaded */
ca_context_create(ca_enable_preemptive_callback);
sprintf(recordname, "%.19s:SET", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Setup an event for monitors */
monitorEvent = epicsEventCreate(epicsEventEmpty);
/* Setup a mutex semaphore to make PV access thread-safe */
accessMutex = epicsMutexCreate();
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor event */
epicsEventWait(monitorEvent);
/* This really does nothing until the mext monitor comes.
Then, monitorEvent is triggered from the monitor callback
function (see above).
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_context_destroy();
epicsMutexDestroy(accessMutex);
epicsEventDestroy(monitorEvent);
return 0;
}
+411
View File
@@ -0,0 +1,411 @@
/* caLesson6.c
by Dirk Zimoch, 2007
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
#include <epicsEvent.h>
#include <epicsMutex.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a generic process variable (PV).
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct epicsPV epicsPV;
typedef struct monitorCallback monitorCallback;
struct monitorCallback {
monitorCallback *next;
void *userdata;
void (*callbackfunction) (void *userdata, epicsPV* pv);
};
struct epicsPV {
chid channel;
int datatype;
epicsEventId monitorEvent;
epicsMutexId accessMutex;
monitorCallback* callbacks;
union {
struct dbr_sts_string string_data;
struct {
struct dbr_ctrl_long long_info;
struct dbr_sts_long long_data;
};
struct {
struct dbr_ctrl_double double_info;
struct dbr_sts_double double_data;
};
struct {
struct dbr_ctrl_enum enum_info;
struct dbr_sts_enum enum_data;
};
};
};
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
int connectPVsFileLine(const char* file, int line, const char* name, ...)
{
va_list va;
epicsPV pv;
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
va_start(va, name);
while (name)
{
if (findPV(name))
{
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
file, line, name);
continue;
}
pv = malloc(sizeof(epicsPV));
if (!pv)
{
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
file, line, name);
return ECA_ALLOCMEM;
}
ca_create_channel(name, connectCallback, pv,
CA_PRIORITY_DEFAULT, &pv->channel);
name = va_arg(va, char*)
}
va_end(va);
}
/* Print NAME = VALUE for PV of any type.
Precision and units are taken from info.
*/
void printPV(const epicsPV* pv)
{
int status, severity;
epicsMutexLock(pv->accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(pv->accessMutex);
return;
}
switch (pv->datatype)
{
case DBF_STRING:
printf("%s = %s",
ca_name(pv->channel),
pv->string_data.value);
status = pv->string_data.status;
severity = pv->string_data.severity;
break;
case DBF_LONG:
printf("%s = %.ld %s",
ca_name(pv->channel),
pv->long_data.value, pv->long_info.units);
status = pv->long_data.status;
severity = pv->long_data.severity;
break;
case DBF_DOUBLE:
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
status = pv->double_data.status;
severity = pv->double_data.severity;
break;
case DBF_ENUM:
if (pv->enum_data.value < pv->enum_info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
else
printf("%s = %d",
ca_name(pv->channel), pv->enum_data.value);
status = pv->enum_data.status;
severity = pv->enum_data.severity;
break;
default:
printf ("%s <unsupported data type>\n",
ca_name(pv->channel));
epicsMutexUnlock(accessMutex);
return;
}
epicsMutexUnlock(accessMutex);
if (severity != NO_ALARM)
{
printf(" <%s %s>",
epicsAlarmSeverityStrings[severity],
epicsAlarmConditionStrings[status]);
}
else
{
printf("\n");
}
}
int caget(epicsPV* pv)
{
int dbr_type;
if (ca_state((pv)->channel) != cs_conn)
{
printf ("caget %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Allocate memory only once but read dynamic data every time */
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
In the multi threaded model, the monitor callback runs in a separate
thread. That means, the monitor function may be called at any time,
even while we are just accessing the PV from the main program. Thus,
we must protect all accesses to a PV with a mutext. Here, we use
just one global mutex. The next lesson will introduce a more
sophisticated solution.
*/
static void monitor(struct event_handler_args args)
{
epicsPV* pv = args.usr;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Lock PV to be thread safe */
epicsMutexLock(pv->accessMutex);
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
case DBR_STS_ENUM:
{
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
epicsMutexUnlock(accessMutex);
/* Inform other threads about monitor */
epicsEventSignal(monitorEvent);
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
/* Start EPICS multi-threaded */
ca_context_create(ca_enable_preemptive_callback);
sprintf(recordname, "%.19s:SET", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Setup an event for monitors */
monitorEvent = epicsEventCreate(epicsEventEmpty);
/* Setup a mutex semaphore to make PV access thread-safe */
accessMutex = epicsMutexCreate();
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor event */
epicsEventWait(monitorEvent);
/* This really does nothing until the mext monitor comes.
Then, monitorEvent is triggered from the monitor callback
function (see above).
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_context_destroy();
epicsMutexDestroy(accessMutex);
epicsEventDestroy(monitorEvent);
return 0;
}
@@ -0,0 +1,5 @@
#include <stdexcept>
class epicsExceptionOutOfMemory : public runtime_error
{
public epicsExceptionOutOfMemory(char* where)
}
+420
View File
@@ -0,0 +1,420 @@
#define epicsAlarmGLOBAL
#include "epicsPV.h"
using namespace std;
class caContext
{
public:
caContext(ca_preemptive_callback_select select=ca_disable_preemptive_callback)
{
ca_context_create(select);
}
~caContext()
{
ca_context_destroy();
}
};
static caContext globalContext;
unsigned long epicsPV::nconnecting = 0;
const char* epicsPV::dataTypeStrings[9] = {
"STRING",
"SHORT",
"FLOAT",
"ENUM",
"CHAR",
"LONG",
"DOUBLE",
"NO_ACCESS",
"UNDEFINED"
};
const char* epicsPV::connectionStateStrings [4] = {
"never connected",
"disconnected",
"connected",
"closed"
};
epicsPV::epicsPV(const char* channelName, caDatatype preferredDatatype)
{
channel = NULL;
data = NULL;
info = NULL;
usedDatatype = caTypeNative;
requestedDatatype = caTypeNative;
if (channelName) link(channelName, preferredDatatype);
}
epicsPV::~epicsPV()
{
unlink();
}
void epicsPV::link(const char* channelName, caDatatype preferredDatatype)
{
int status;
requestedDatatype = preferredDatatype;
if (channel) unlink();
if (!channelName) return;
status = ca_create_channel(channelName,
epicsPV::connectCallback, this,
CA_PRIORITY_DEFAULT,
&channel);
switch (status)
{
case ECA_NORMAL:
nconnecting ++;
return;
case ECA_BADTYPE:
throw invalid_argument("epicsPV::link: bad datatype");
case ECA_STRTOBIG:
throw invalid_argument("epicsPV::link: channel name too long");
case ECA_ALLOCMEM:
throw epicsExceptionOutOfMemory();
default:
throw runtime_error("epicsPV::link: unknown error");
}
}
void epicsPV::unlink()
{
if (channel)
{
connectionDownEvent();
ca_clear_channel(channel);
channel = NULL;
free(data);
data = NULL;
free(info);
info = NULL;
if (usedDatatype != caTypeNative)
{
nconnecting --;
}
usedDatatype = caTypeNative;
}
}
double epicsPV::waitForConnect(double timeoutSec, epicsPV* pv)
{
ca_poll();
double wait = 0.001;
while ((timeoutSec != 0.0) &&
(pv ? pv->connectionState() == cs_never_conn : nconnecting != 0))
{
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
ca_pend_event(wait);
if (timeoutSec > 0.0) timeoutSec -= wait;
wait *= 1.2;
if (wait > 0.5) wait = 0.5;
}
return timeoutSec;
}
void epicsPV::connectCallback(connection_handler_args args)
{
epicsPV* pv = static_cast<epicsPV*>(ca_puser(args.chid));
if (args.op == CA_OP_CONN_UP)
pv->connectionUp();
else
pv->connectionDown();
}
void epicsPV::connectionUp()
{
if (usedDatatype == caTypeNative)
nconnecting --;
if (requestedDatatype != caTypeNative)
usedDatatype = requestedDatatype;
else
usedDatatype = static_cast<caDatatype>(ca_field_type(channel));
data = realloc(data, dbr_size_n(dbf_type_to_DBR_TIME(usedDatatype),ca_element_count(channel)));
if (!data) throw epicsExceptionOutOfMemory();
if (usedDatatype != DBF_STRING)
{
info = realloc(info, dbr_size_n(dbf_type_to_DBR_CTRL(usedDatatype),1));
if (!info) throw epicsExceptionOutOfMemory();
SEVCHK(ca_get(dbf_type_to_DBR_CTRL(usedDatatype), channel, info),
"epicsPV::connectionUp");
}
else
{
free(info);
info = NULL;
}
connectionUpEvent();
}
void epicsPV::connectionDown()
{
connectionDownEvent();
}
void epicsPV::connectionUpEvent()
{
// User can overwrite this function
}
void epicsPV::connectionDownEvent()
{
// User can overwrite this function
}
const char* epicsPV::units() const
{
if (!info) return "";
switch (usedDatatype)
{
case DBF_CHAR:
return static_cast<dbr_ctrl_char*>(info)->units;
case DBF_SHORT:
return static_cast<dbr_ctrl_short*>(info)->units;
case DBF_LONG:
return static_cast<dbr_ctrl_long*>(info)->units;
case DBF_FLOAT:
return static_cast<dbr_ctrl_float*>(info)->units;
case DBF_DOUBLE:
return static_cast<dbr_ctrl_double*>(info)->units;
default:
return "";
}
}
int epicsPV::precision() const
{
if (!info) return 0;
switch (usedDatatype)
{
case DBF_FLOAT:
return static_cast<dbr_ctrl_float*>(info)->precision;
case DBF_DOUBLE:
return static_cast<dbr_ctrl_double*>(info)->precision;
default:
return 0;
}
}
double epicsPV::toDouble (unsigned long index) const
{
if (!data) throw epicsExceptionNoData();
if (index >= elements()) throw epicsExceptionOutOfBounds();
switch (usedDatatype)
{
case DBF_CHAR:
return (&static_cast<dbr_time_char*>(data)->value)[index];
case DBF_SHORT:
return (&static_cast<dbr_time_short*>(data)->value)[index];
case DBF_LONG:
return (&static_cast<dbr_time_long*>(data)->value)[index];
case DBF_FLOAT:
return (&static_cast<dbr_time_float*>(data)->value)[index];
case DBF_DOUBLE:
return (&static_cast<dbr_time_double*>(data)->value)[index];
case DBF_ENUM:
return (&static_cast<dbr_time_enum*>(data)->value)[index];
default:
throw epicsExceptionInvalidConversion();
}
}
long epicsPV::toLong (unsigned long index) const
{
if (!data) throw epicsExceptionNoData();
if (index > elements()) throw epicsExceptionOutOfBounds();
switch (usedDatatype)
{
case DBF_CHAR:
return (&static_cast<dbr_time_char*>(data)->value)[index];
case DBF_SHORT:
return (&static_cast<dbr_time_short*>(data)->value)[index];
case DBF_LONG:
return (&static_cast<dbr_time_long*>(data)->value)[index];
case DBF_FLOAT:
return static_cast<long>((&static_cast<dbr_time_float*>(data)->value)[index]);
case DBF_DOUBLE:
return static_cast<long>((&static_cast<dbr_time_double*>(data)->value)[index]);
case DBF_ENUM:
return (&static_cast<dbr_time_enum*>(data)->value)[index];
default:
throw epicsExceptionInvalidConversion();
}
}
const char* epicsPV::toStr(int flags, unsigned long index)
{
int prec;
double val;
long ival;
dbr_ctrl_enum* enuminfo;
const char* units;
if (!data) return "<not connected>";
if (index > elements()) throw epicsExceptionOutOfBounds();
switch (usedDatatype)
{
case DBF_CHAR:
ival = (&static_cast<dbr_time_char*>(data)->value)[index];
units = static_cast<dbr_ctrl_char*>(info)->units;
goto printint;
case DBF_SHORT:
ival = (&static_cast<dbr_time_short*>(data)->value)[index];
units = static_cast<dbr_ctrl_short*>(info)->units;
goto printint;
case DBF_LONG:
ival = (&static_cast<dbr_time_long*>(data)->value)[index];
units = static_cast<dbr_ctrl_long*>(info)->units;
printint:
if (flags & withUnits && units[0])
sprintf(stringrep, "%ld %s", ival, units);
else
sprintf(stringrep, "%ld", ival);
break;
case DBF_FLOAT:
val = (&static_cast<dbr_time_float*>(data)->value)[index];
prec = static_cast<dbr_ctrl_float*>(info)->precision;
units = static_cast<dbr_ctrl_float*>(info)->units;
goto printdouble;
case DBF_DOUBLE:
val = (&static_cast<dbr_time_double*>(data)->value)[index];
prec = static_cast<dbr_ctrl_double*>(info)->precision;
units = static_cast<dbr_ctrl_double*>(info)->units;
printdouble:
if (prec > 17) prec = -17;
if (prec < -17) prec = -17;
if (flags & withUnits && units[0])
{
if (prec >= 0)
sprintf(stringrep, "%.*f %s", prec, val, units);
else
sprintf(stringrep, "%.*g %s", -prec, val, units);
}
else
{
if (prec >= 0)
sprintf(stringrep, "%.*f", prec, val);
else
sprintf(stringrep, "%.*g", -prec, val);
}
break;
case DBF_STRING:
return (&static_cast<dbr_time_string*>(data)->value)[index];
case DBF_ENUM:
ival = (&static_cast<dbr_time_enum*>(data)->value)[index];
enuminfo = static_cast<dbr_ctrl_enum*>(info);
if (ival < enuminfo->no_str)
return enuminfo->strs[ival];
sprintf(stringrep, "%ld", ival);
break;
default:
return "<not accessible>";
}
return stringrep;
}
int epicsPV::getPVs(double timeoutSec, epicsPV* pv1, ...)
{
va_list ap;
epicsPV* pv;
int faults = 0;
int status;
va_start(ap, pv1);
pv = pv1;
while (pv)
{
if (!pv->channel)
{
// not assigned
pv->status = ECA_BADCHID;
faults ++;
}
else
{
if (pv->connectionState() == cs_never_conn)
{
// try to connect for the first time
timeoutSec = waitForConnect(timeoutSec, pv);
}
SEVCHK(pv->status = ca_array_get(dbf_type_to_DBR_TIME(pv->usedDatatype),
pv->elements(), pv->channel, pv->data),
"caget");
if (pv->status)
{
// io error
faults ++;
}
}
pv = va_arg(ap, epicsPV*);
}
va_end(ap);
SEVCHK(status = ca_pend_io(timeoutSec), "caget");
if (status)
{
// io timeout
faults ++;
}
return faults;
}
epicsPV& epicsPV::get(double timeoutSec)
{
if (!channel)
throw runtime_error("epicsPV::get: PV not linked to a channel");
switch (connectionState())
{
case cs_conn:
break;
case cs_never_conn:
timeoutSec = waitForConnect(timeoutSec, this);
if (connectionState() == cs_conn) break;
default:
throw runtime_error(string("epicsPV::get: ")+ca_name(channel)+" "+connectionStateStr());
}
SEVCHK(ca_array_get(dbf_type_to_DBR_TIME(usedDatatype),
ca_element_count(channel), channel, data),
"epicsPV::get");
ca_pend_io(timeoutSec);
return *this;
}
epicsPV& epicsPV::put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec)
{
if (!channel)
throw runtime_error("epicsPV::put: PV not linked to a channel");
switch (connectionState())
{
case cs_conn:
break;
case cs_never_conn:
timeoutSec = waitForConnect(timeoutSec, this);
if (connectionState() == cs_conn) break;
default:
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" "+connectionStateStr());
}
if (!hasWriteAccess())
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" not writable");
cout << "putting " << name() << ", elements: " << elements << ", datatype: " << datatype << " ";
switch (datatype)
{
case caTypeDouble:
cout << *(const double*)value;
break;
case caTypeLong:
cout << *(const long*)value;
break;
case caTypeString:
cout << (const char*)value;
break;
default:;
}
cout << endl;
SEVCHK(ca_array_put(datatype, elements, channel, value), "epicsPV::put");
ca_pend_io(timeoutSec);
return *this;
}
+215
View File
@@ -0,0 +1,215 @@
#ifndef epicsPV_h
#define epicsPV_h
#include <cadef.h>
#include <alarm.h>
#include <iostream>
#include <stdexcept>
// How epicsPVs work:
// A PV is a container for a remote named value provided via a so
// called EPICS "channel". It is "linked" to the channel in the
// constructor or later via the link() call.
// Because the original value is remote, the PV can only contain
// a copy of the original value.
// With the get() call, the PV is updated from the remote value.
// With the put() call, the remote value is updated from the PV.
// Channel Access datatypes (plus caTypeNative as default value)
// depending on the datatype of a PV, exporting the value
// to double, long or const char* may give different results
typedef enum {
caTypeString,
caTypeShort,
caTypeFloat,
caTypeEnum,
caTypeChar,
caTypeLong,
caTypeDouble,
caTypeNoAccess,
caTypeNative
} caDatatype;
class epicsExceptionOutOfMemory {};
class epicsExceptionNoData {};
class epicsExceptionOutOfBounds {};
class epicsExceptionInvalidConversion {};
class epicsPV
{
public:
// create PV and optionally link it to an EPICS channel
// normally, you call the constructor with channelName but without
// preferredDatatype to use the native datatype of the channel
// you may read back datatype later
epicsPV(const char* channelName=NULL, caDatatype preferredDatatype=caTypeNative);
// destroy PV and free all resources
virtual ~epicsPV();
// explititely (re-)link PV to a (different) EPICS channel
// it is normally called by the constructor and destructor.
// note: linking does not yet send a search request to the
// network until waitForConnect is called (probably implicitely)
void link(const char* channelName, caDatatype preferredDatatype=caTypeNative);
void unlink();
// wait until all (or one) PVs are connected
// timeoutSec = 0.0 just sends connect requests but does not wait
// timeoutSec < 0.0 means wait forever
// returns remaining seconds
// calling this function is optional because it will be called
// implicitely before the first get or put on an unconnected PV
static double waitForConnect(double timeoutSec=5.0, epicsPV* pv=NULL);
// these functions are called when the connection state changes
// the default implementation does nothing
// overwrite them if you want
// connectionUpEvent is called after all data is initialized
// connectionDownEvent is called before any data is deleted
virtual void connectionUpEvent();
virtual void connectionDownEvent();
// get and put data
epicsPV& get(double timeoutSec=2.0);
static int getPVs(double timeoutSec, epicsPV* pv1, ...);
epicsPV& put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec=0.0);
epicsPV& put(long value, double timeoutSec=0.0)
{ return put(&value, 1, caTypeLong, timeoutSec); }
epicsPV& put(double value, double timeoutSec=0.0)
{ return put(&value, 1, caTypeDouble, timeoutSec); }
epicsPV& put(const char* value, double timeoutSec=0.0)
{ return put(value, 1, caTypeString, timeoutSec); }
// read back channel name (what you provided to link or constuctor)
const char* name() const
{ return channel ? ca_name(channel) : ""; }
// find out connection state
// see $(EPICS_BASE)/include/cadef.h for valid values
channel_state connectionState() const
{ return channel ? ca_state(channel) : cs_never_conn; }
const char* connectionStateStr() const
{ return connectionStateStrings[connectionState()]; }
static const char* connectionStateStr(channel_state state)
{ return connectionStateStrings[state]; }
// The following methods return information which is available
// as soon as the PV is connected.
// return currently used data type
caDatatype datatype() const
{ return usedDatatype; }
const char* datatypeStr() const
{ return dataTypeStrings[datatype()+1]; }
// return number of elements (for array data)
unsigned long elements() const
{ return channel ? 0 : ca_element_count(channel); }
// return units and precision (.EGU and .PREC fields)
const char* units() const;
int precision() const;
// return access rights (might change at run-time)
bool hasReadAccess() const
{ return channel ? ca_read_access(channel) : false; }
bool hasWriteAccess() const
{ return channel ? ca_write_access(channel) : false; }
// The following methods return information which is available
// after a successful get().
// return alarm severity and status (.SEVR and .STAT fields)
// see $(EPICS_BASE)/include/alarm.h for valid values
// not conencted PV returns epicsSevInvalid/epicsAlarmNone
epicsAlarmSeverity alarmSeverity() const
{ if (data)
{ epicsUInt16 sevr = static_cast<dbr_time_string*>(data)->severity;
if (sevr <= lastEpicsAlarmSev)
return static_cast<epicsAlarmSeverity>(sevr);
}
return epicsSevInvalid;
}
const char* alarmSeverityStr() const
{ return epicsAlarmSeverityStrings[alarmSeverity()]; }
static const char* alarmSeverityStr(epicsAlarmSeverity severity)
{ return epicsAlarmSeverityStrings[severity]; }
epicsAlarmCondition alarmStatus() const
{ if (data)
{ epicsUInt16 stat = static_cast<dbr_time_string*>(data)->status;
if (stat <= lastEpicsAlarmCond)
return static_cast<epicsAlarmCondition>(stat);
}
return epicsAlarmNone;
}
const char* alarmStatusStr() const
{ return epicsAlarmConditionStrings[alarmStatus()]; }
static const char* alarmStatusStr(epicsAlarmCondition status)
{ return epicsAlarmConditionStrings[status]; }
// return time of record processing
// see $(EPICS_BASE)/include/epicsTime.h for class epicsTime
epicsTime timestamp() const
{ if (data)
{ return epicsTime(static_cast<dbr_time_string*>(data)->stamp);
}
return epicsTime();
}
// return value of pv
long toLong(unsigned long index = 0) const;
double toDouble(unsigned long index = 0) const;
enum { withUnits = 1 }; // flags for toStr()
const char* toStr(int flags = 0, unsigned long index = 0);
// conversion operators for convenience
// We try not to be ambigous and define ops for all types.
operator double() const
{ return toDouble(); }
double operator [] (unsigned long index) const
{ return toDouble(index); }
static const char* dataTypeStrings[9];
static const char* connectionStateStrings[4];
private:
chid channel;
void* data;
void* info;
caDatatype usedDatatype;
caDatatype requestedDatatype;
int status;
static unsigned long nconnecting;
char stringrep[40];
static void connectCallback(connection_handler_args);
void connectionUp();
void connectionDown();
};
inline std::ostream& operator << (std::ostream& o, epicsPV& pv)
{
return o << pv.toStr(epicsPV::withUnits);
}
inline std::ostream& operator << (std::ostream& o, epicsAlarmSeverity sevr)
{
return o << epicsAlarmSeverityStrings[sevr];
}
inline std::ostream& operator << (std::ostream& o, epicsAlarmCondition stat)
{
return o << epicsAlarmConditionStrings[stat];
}
inline std::ostream& operator << (std::ostream& o, caDatatype type)
{
return o << epicsPV::dataTypeStrings[type];
}
#define caget(pvs...) epicsPV::getPVs(2.0, pvs, NULL)
#endif
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
#include "epicsPV.h"
using namespace std;
int main (int argc, const char** argv)
{
try {
epicsPV pv(argv[1]);
pv.get();
cout << pv.name() << " = " << pv <<
" SEVR=" << pv.alarmSeverity() <<
" STAT=" << pv.alarmStatus() << endl;
if (argc == 3)
{
pv.put(argv[2]);
pv.get();
cout << pv.name() << " = " << pv <<
" SEVR=" << pv.alarmSeverity() <<
" STAT=" << pv.alarmStatus() << endl;
}
}
catch (exception& e)
{
cout << endl << "Error: " << e.what() << endl;
}
return 0;
}
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
#include "epicsPV.h"
#include <stdexcept>
using namespace std;
class myPV : public epicsPV
{
public:
myPV(const char* channelName=NULL, caDatatype preferredDatatype=caTypeNative)
: epicsPV(channelName, preferredDatatype) {}
void connectionUpEvent();
void connectionDownEvent();
};
void myPV::connectionUpEvent()
{
cout << name() << " connected, datatype = " << datatype() << endl;
}
void myPV::connectionDownEvent()
{
cout << name() << " disconnected" << endl;
}
int main (int argc, const char** argv)
{
try {
myPV pv(argv[1]);
cout << "before get: " << pv.name() << " = " << pv <<
" " << pv.alarmSeverity() <<
" " << pv.alarmStatus() << endl;
caget(pv);
cout << "after get: " << pv.name() << " = " << pv <<
" " << pv.alarmSeverity() <<
" " << pv.alarmStatus() << endl;
double val = pv;
cout << pv.name() << " = " << val << endl;
unsigned int ival = pv;
cout << pv.name() << " = " << ival << endl;
if (argc == 3)
{
pv.put(argv[2]);
}
}
catch (exception& e)
{
cout << endl << e.what() << endl;
}
return 0;
}
+132
View File
@@ -0,0 +1,132 @@
# Example EPICS Makefile
# If you don't modify this file it will create
# a program with the name of the current directory
# from all C and C++ source files found and link
# it to the EPICS client libraries.
# Where is EPICS base?
EPICS = /usr/local/epics/base
# Where to install the program)?
BINDIR = .
#BINDIR = bin/$(EPICS_HOST_ARCH)
# What is the name of the program?
# Add one line for each program if the program name
# is not equal to the directory name
PROGRAM += testPV
#PROGRAM += pvexample
# List all sources of the program if not simply
# all *.c *.cc *.C *.cxx *.cpp files in this
# directory should be used.
# Add one line for each source file.
# If you build more than one PROGRAM, list
# the sources separately for each program like
# SRCS_<program> += <filename>
SRCS_testPV += epicsPV.c testPV.c
SRCS_pvexample += epicsPV.cc pvexample.cc
# list all include directories
INCDIRS += $(EPICS)/include/os/Linux
INCDIRS += $(EPICS)/include
# list all library directories
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
# list all libraries (ca and Com are EPICS)
LIBS += ca Com
#optimize:
CFLAGS += -O3
#debug:
CFLAGS += -g
# don't touch the code below this line unless you know what you're doing.
CPPFLAGS += $(INCDIRS:%=-I %)
CFLAGS += -MMD
CFLAGS += -Wall
CFLAGS += $(USR_CFLAGS)
LDFLAGS += $(LIBDIRS:%=-L %)
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
LDFLAGS += $(LIBS:%=-l %)
ifeq ($(words $(PROGRAM)),0)
PROGRAM = $(notdir $(PWD))
endif
SRCS += $(SRCS_$(PROGRAM))
ifeq ($(words $(SRCS)),0)
SRCS += $(wildcard *.c)
SRCS += $(wildcard *.cc)
SRCS += $(wildcard *.C)
SRCS += $(wildcard *.cxx)
SRCS += $(wildcard *.cpp)
endif
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
ifndef EPICS_HOST_ARCH
$(error EPICS_HOST_ARCH variable is missing on your system!)
endif
.PHONY:
.PHONY: build clean realclean
build:
clean:
rm -rf O.*
realclean: clean
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
O.%:
mkdir $@
$(BINDIR):
mkdir -p $@
ifeq ($(words $(PROGRAM)),1)
build: $(BINDIR)/$(PROGRAM)
ifneq ($(BINDIR),.)
$(PROGRAM): $(BINDIR)/$(PROGRAM)
endif
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
rm -f $@
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
$(CXX) -o $@ $(LDFLAGS) $^
else
build:
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
$(PROGRAM):
make PROGRAM=$@
endif
O.$(EPICS_HOST_ARCH)/%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cc
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.C
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
-include O.$(EPICS_HOST_ARCH)/*.d
+411
View File
@@ -0,0 +1,411 @@
/* caLesson6.c
by Dirk Zimoch, 2007
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
#include <epicsEvent.h>
#include <epicsMutex.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a generic process variable (PV).
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct epicsPV epicsPV;
typedef struct monitorCallback monitorCallback;
struct monitorCallback {
monitorCallback *next;
void *userdata;
void (*callbackfunction) (void *userdata, epicsPV* pv);
};
struct epicsPV {
chid channel;
int datatype;
epicsEventId monitorEvent;
epicsMutexId accessMutex;
monitorCallback* callbacks;
union {
struct dbr_sts_string string_data;
struct {
struct dbr_ctrl_long long_info;
struct dbr_sts_long long_data;
};
struct {
struct dbr_ctrl_double double_info;
struct dbr_sts_double double_data;
};
struct {
struct dbr_ctrl_enum enum_info;
struct dbr_sts_enum enum_data;
};
};
};
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
int connectPVsFileLine(const char* file, int line, const char* name, ...)
{
va_list va;
epicsPV pv;
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
va_start(va, name);
while (name)
{
if (findPV(name))
{
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
file, line, name);
continue;
}
pv = malloc(sizeof(epicsPV));
if (!pv)
{
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
file, line, name);
return ECA_ALLOCMEM;
}
ca_create_channel(name, connectCallback, pv,
CA_PRIORITY_DEFAULT, &pv->channel);
name = va_arg(va, char*)
}
va_end(va);
}
/* Print NAME = VALUE for PV of any type.
Precision and units are taken from info.
*/
void printPV(const epicsPV* pv)
{
int status, severity;
epicsMutexLock(pv->accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(pv->accessMutex);
return;
}
switch (pv->datatype)
{
case DBF_STRING:
printf("%s = %s",
ca_name(pv->channel),
pv->string_data.value);
status = pv->string_data.status;
severity = pv->string_data.severity;
break;
case DBF_LONG:
printf("%s = %.ld %s",
ca_name(pv->channel),
pv->long_data.value, pv->long_info.units);
status = pv->long_data.status;
severity = pv->long_data.severity;
break;
case DBF_DOUBLE:
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
status = pv->double_data.status;
severity = pv->double_data.severity;
break;
case DBF_ENUM:
if (pv->enum_data.value < pv->enum_info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
else
printf("%s = %d",
ca_name(pv->channel), pv->enum_data.value);
status = pv->enum_data.status;
severity = pv->enum_data.severity;
break;
default:
printf ("%s <unsupported data type>\n",
ca_name(pv->channel));
epicsMutexUnlock(accessMutex);
return;
}
epicsMutexUnlock(accessMutex);
if (severity != NO_ALARM)
{
printf(" <%s %s>",
epicsAlarmSeverityStrings[severity],
epicsAlarmConditionStrings[status]);
}
else
{
printf("\n");
}
}
int caget(epicsPV* pv)
{
int dbr_type;
if (ca_state((pv)->channel) != cs_conn)
{
printf ("caget %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Allocate memory only once but read dynamic data every time */
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
In the multi threaded model, the monitor callback runs in a separate
thread. That means, the monitor function may be called at any time,
even while we are just accessing the PV from the main program. Thus,
we must protect all accesses to a PV with a mutext. Here, we use
just one global mutex. The next lesson will introduce a more
sophisticated solution.
*/
static void monitor(struct event_handler_args args)
{
epicsPV* pv = args.usr;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Lock PV to be thread safe */
epicsMutexLock(pv->accessMutex);
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
case DBR_STS_ENUM:
{
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
epicsMutexUnlock(accessMutex);
/* Inform other threads about monitor */
epicsEventSignal(monitorEvent);
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
/* Start EPICS multi-threaded */
ca_context_create(ca_enable_preemptive_callback);
sprintf(recordname, "%.19s:SET", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Setup an event for monitors */
monitorEvent = epicsEventCreate(epicsEventEmpty);
/* Setup a mutex semaphore to make PV access thread-safe */
accessMutex = epicsMutexCreate();
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor event */
epicsEventWait(monitorEvent);
/* This really does nothing until the mext monitor comes.
Then, monitorEvent is triggered from the monitor callback
function (see above).
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_context_destroy();
epicsMutexDestroy(accessMutex);
epicsEventDestroy(monitorEvent);
return 0;
}
+411
View File
@@ -0,0 +1,411 @@
/* caLesson6.c
by Dirk Zimoch, 2007
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
#include <epicsEvent.h>
#include <epicsMutex.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a generic process variable (PV).
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct epicsPV epicsPV;
typedef struct monitorCallback monitorCallback;
struct monitorCallback {
monitorCallback *next;
void *userdata;
void (*callbackfunction) (void *userdata, epicsPV* pv);
};
struct epicsPV {
chid channel;
int datatype;
epicsEventId monitorEvent;
epicsMutexId accessMutex;
monitorCallback* callbacks;
union {
struct dbr_sts_string string_data;
struct {
struct dbr_ctrl_long long_info;
struct dbr_sts_long long_data;
};
struct {
struct dbr_ctrl_double double_info;
struct dbr_sts_double double_data;
};
struct {
struct dbr_ctrl_enum enum_info;
struct dbr_sts_enum enum_data;
};
};
};
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
int connectPVsFileLine(const char* file, int line, const char* name, ...)
{
va_list va;
epicsPV pv;
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
va_start(va, name);
while (name)
{
if (findPV(name))
{
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
file, line, name);
continue;
}
pv = malloc(sizeof(epicsPV));
if (!pv)
{
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
file, line, name);
return ECA_ALLOCMEM;
}
ca_create_channel(name, connectCallback, pv,
CA_PRIORITY_DEFAULT, &pv->channel);
name = va_arg(va, char*)
}
va_end(va);
}
/* Print NAME = VALUE for PV of any type.
Precision and units are taken from info.
*/
void printPV(const epicsPV* pv)
{
int status, severity;
epicsMutexLock(pv->accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(pv->accessMutex);
return;
}
switch (pv->datatype)
{
case DBF_STRING:
printf("%s = %s",
ca_name(pv->channel),
pv->string_data.value);
status = pv->string_data.status;
severity = pv->string_data.severity;
break;
case DBF_LONG:
printf("%s = %.ld %s",
ca_name(pv->channel),
pv->long_data.value, pv->long_info.units);
status = pv->long_data.status;
severity = pv->long_data.severity;
break;
case DBF_DOUBLE:
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
status = pv->double_data.status;
severity = pv->double_data.severity;
break;
case DBF_ENUM:
if (pv->enum_data.value < pv->enum_info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
else
printf("%s = %d",
ca_name(pv->channel), pv->enum_data.value);
status = pv->enum_data.status;
severity = pv->enum_data.severity;
break;
default:
printf ("%s <unsupported data type>\n",
ca_name(pv->channel));
epicsMutexUnlock(accessMutex);
return;
}
epicsMutexUnlock(accessMutex);
if (severity != NO_ALARM)
{
printf(" <%s %s>",
epicsAlarmSeverityStrings[severity],
epicsAlarmConditionStrings[status]);
}
else
{
printf("\n");
}
}
int caget(epicsPV* pv)
{
int dbr_type;
if (ca_state((pv)->channel) != cs_conn)
{
printf ("caget %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Allocate memory only once but read dynamic data every time */
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
In the multi threaded model, the monitor callback runs in a separate
thread. That means, the monitor function may be called at any time,
even while we are just accessing the PV from the main program. Thus,
we must protect all accesses to a PV with a mutext. Here, we use
just one global mutex. The next lesson will introduce a more
sophisticated solution.
*/
static void monitor(struct event_handler_args args)
{
epicsPV* pv = args.usr;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Lock PV to be thread safe */
epicsMutexLock(pv->accessMutex);
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
case DBR_STS_ENUM:
{
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
epicsMutexUnlock(accessMutex);
/* Inform other threads about monitor */
epicsEventSignal(monitorEvent);
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
/* Start EPICS multi-threaded */
ca_context_create(ca_enable_preemptive_callback);
sprintf(recordname, "%.19s:SET", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Setup an event for monitors */
monitorEvent = epicsEventCreate(epicsEventEmpty);
/* Setup a mutex semaphore to make PV access thread-safe */
accessMutex = epicsMutexCreate();
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor event */
epicsEventWait(monitorEvent);
/* This really does nothing until the mext monitor comes.
Then, monitorEvent is triggered from the monitor callback
function (see above).
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_context_destroy();
epicsMutexDestroy(accessMutex);
epicsEventDestroy(monitorEvent);
return 0;
}
@@ -0,0 +1,5 @@
#include <stdexcept>
class epicsExceptionOutOfMemory : public runtime_error
{
public epicsExceptionOutOfMemory(char* where)
}
+572
View File
@@ -0,0 +1,572 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define epicsAlarmGLOBAL
#include "epicsPV.h"
static int contextCreated = 0;
static unsigned long pendingConnects = 0;
static unsigned long pendingGets = 0;
static unsigned long pendingPuts = 0;
epicsTimeStamp PVinvalidTimestamp = {0,0};
const char* PVdataTypeStrings[9] = {
"STRING",
"SHORT",
"FLOAT",
"ENUM",
"CHAR",
"LONG",
"DOUBLE",
"NO_ACCESS",
"UNDEFINED"
};
const char* PVconnectionStateStrings [4] = {
"never connected",
"disconnected",
"connected",
"closed"
};
epicsPV PVcreateWithTypeAndCallback(const char* channelName,caDatatype preferredDatatype,
PVconnectionCallback cb, void* userarg)
{
epicsPV pv;
if (!contextCreated)
{
ca_context_create(ca_disable_preemptive_callback);
atexit(ca_context_destroy);
contextCreated = 1;
}
pv = malloc(sizeof (struct epicsPV_s));
pv->channel = NULL;
pv->data = NULL;
pv->info = NULL;
pv->units = "";
pv->precision = 0;
pv->usedDatatype = caTypeNative;
pv->requestedDatatype = caTypeNative;
pv->status = ECA_DISCONN;
pv->connectionCallback = cb;
pv->userarg = userarg;
if (channelName) PVlinkWithType(pv, channelName, preferredDatatype);
return pv;
}
void PVdestroy(epicsPV pv)
{
if (!pv) return;
PVunlink(pv);
free(pv);
}
static void connectCallback(struct connection_handler_args args)
{
epicsPV pv = ca_puser(args.chid);
int up = (args.op == CA_OP_CONN_UP);
if (up)
{
if (pv->usedDatatype == caTypeNative)
pendingConnects --;
if (pv->requestedDatatype != caTypeNative)
pv->usedDatatype = pv->requestedDatatype;
else
pv->usedDatatype = ca_field_type(pv->channel);
pv->data = realloc(pv->data, dbr_size_n(dbf_type_to_DBR_TIME(pv->usedDatatype),
ca_element_count(pv->channel)));
if (!pv->data)
{
pv->status = ECA_ALLOCMEM;
return;
}
pv->units = "";
pv->precision = 0;
if (pv->usedDatatype != DBF_STRING)
{
pv->info = realloc(pv->info, dbr_size_n(dbf_type_to_DBR_CTRL(pv->usedDatatype),1));
if (!pv->info)
{
pv->status = ECA_ALLOCMEM;
return;
}
pv->status = ca_get(dbf_type_to_DBR_CTRL(pv->usedDatatype), pv->channel, pv->info);
switch (pv->usedDatatype)
{
case DBF_CHAR:
pv->units = pv->info_CHAR->units;
break;
case DBF_SHORT:
pv->units = pv->info_SHORT->units;
break;
case DBF_LONG:
pv->units = pv->info_LONG->units;
break;
case DBF_FLOAT:
pv->units = pv->info_FLOAT->units;
pv->precision = pv->info_FLOAT->precision;
break;
case DBF_DOUBLE:
pv->units = pv->info_DOUBLE->units;
pv->precision = pv->info_DOUBLE->precision;
break;
default:
;
}
}
else
{
free(pv->info);
pv->info = NULL;
}
}
else
{
pv->status = ECA_DISCONN;
}
if (pv->connectionCallback)
{
pv->connectionCallback(pv, up, pv->userarg);
}
}
int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype)
{
int status;
if (!pv) return ECA_INTERNAL;
pv->requestedDatatype = preferredDatatype;
if (pv->channel) PVunlink(pv);
if (!channelName) return ECA_NORMAL;
status = ca_create_channel(channelName,
connectCallback, pv,
CA_PRIORITY_DEFAULT,
&pv->channel);
if (status == ECA_NORMAL)
{
pendingConnects ++;
}
return status;
}
void PVunlink(epicsPV pv)
{
if (pv && pv->channel)
{
if (pv->connectionCallback)
{
pv->connectionCallback(pv, 0, pv->userarg);
}
ca_clear_channel(pv->channel);
pv->channel = NULL;
free(pv->data);
pv->data = NULL;
free(pv->info);
pv->info = NULL;
pv->units = "";
pv->precision = 0;
pv->status = ECA_DISCONN;
if (pv->usedDatatype != caTypeNative)
{
pendingConnects --;
}
pv->usedDatatype = caTypeNative;
}
}
double PVwaitForConnect(epicsPV pv, double timeoutSec)
{
ca_poll();
double wait = 0.001;
while (timeoutSec != 0.0 &&
(pv ? ca_state(pv->channel) == cs_never_conn : pendingConnects != 0))
{
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
ca_pend_event(wait);
if (timeoutSec > 0.0) timeoutSec -= wait;
wait *= 1.2;
if (wait > 0.5) wait = 0.5;
}
return timeoutSec;
}
double PVtoDoubleElement(epicsPV pv, unsigned long index)
{
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
{
return atof("NAN");
}
switch (pv->usedDatatype)
{
case DBF_CHAR:
return (&pv->CHAR->value)[index];
case DBF_SHORT:
return (&pv->SHORT->value)[index];
case DBF_LONG:
return (&pv->LONG->value)[index];
case DBF_FLOAT:
return (&pv->FLOAT->value)[index];
case DBF_DOUBLE:
return (&pv->DOUBLE->value)[index];
case DBF_ENUM:
return (&pv->ENUM->value)[index];
case DBF_STRING:
return atof((&pv->STRING->value)[index]);
default:
return atof("NAN");
}
}
long PVtoLongElement(epicsPV pv, unsigned long index)
{
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
{
return 0;
}
switch (pv->usedDatatype)
{
case DBF_CHAR:
return (&pv->CHAR->value)[index];
case DBF_SHORT:
return (&pv->SHORT->value)[index];
case DBF_LONG:
return (&pv->LONG->value)[index];
case DBF_FLOAT:
return (&pv->FLOAT->value)[index];
case DBF_DOUBLE:
return (&pv->DOUBLE->value)[index];
case DBF_ENUM:
return (&pv->ENUM->value)[index];
case DBF_STRING:
return atoi((&pv->STRING->value)[index]);
default:
return 0;
}
}
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index)
{
double val;
long ival;
int prec;
if (!pv || !pv->data) return "<not connected>";
if (index > ca_element_count(pv->channel)) return "<out of bounds>";
switch (pv->usedDatatype)
{
case DBF_CHAR:
ival = (&pv->CHAR->value)[index];
goto printint;
case DBF_SHORT:
ival = (&pv->SHORT->value)[index];
goto printint;
case DBF_LONG:
ival = (&pv->LONG->value)[index];
printint:
if (flags & PV_WITHUNITS && pv->units[0])
sprintf(pv->stringrep, "%ld %s", ival, pv->units);
else
sprintf(pv->stringrep, "%ld", ival);
break;
case DBF_FLOAT:
val = (&pv->FLOAT->value)[index];
goto printdouble;
case DBF_DOUBLE:
val = (&pv->DOUBLE->value)[index];
printdouble:
prec = pv->precision;
if (prec > 17) prec = -17;
if (prec < -17) prec = -17;
if (flags & PV_WITHUNITS && pv->units[0])
{
if (prec >= 0)
sprintf(pv->stringrep, "%.*f %s", prec, val, pv->units);
else
sprintf(pv->stringrep, "%.*g %s", -prec, val, pv->units);
}
else
{
if (prec >= 0)
sprintf(pv->stringrep, "%.*f", prec, val);
else
sprintf(pv->stringrep, "%.*g", -prec, val);
}
break;
case DBF_ENUM:
ival = (&pv->ENUM->value)[index];
if (ival < pv->info_ENUM->no_str)
return pv->info_ENUM->strs[ival];
sprintf(pv->stringrep, "%ld", ival);
break;
case DBF_STRING:
return (&pv->STRING->value)[index];
default:
return "<not accessible>";
}
return pv->stringrep;
}
static void getCallback(struct event_handler_args args)
{
epicsPV pv = args.usr;
if (pv->status == ECA_IOINPROGRESS)
{
pendingGets--;
}
pv->status = args.status;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
fprintf(stderr, "getCallback %s: %s\n",
ca_name(pv->channel), ca_message(args.status));
return;
}
memcpy(pv->data, args.dbr, dbr_size_n(args.type, args.count));
if (pv->STRING->severity > lastEpicsAlarmSev)
{
pv->STRING->severity = epicsSevNone;
}
if (pv->STRING->status > lastEpicsAlarmCond)
{
pv->STRING->status = epicsAlarmNone;
}
}
int cagetList(double timeoutSec, epicsPV pv1, ...)
{
va_list ap;
epicsPV pv;
unsigned long faults = 0;
double wait = 0.001;
double connectTimeout = 2.0;
/* Prepate get requests */
va_start(ap, pv1);
pv = pv1;
while (pv)
{
if (!pv->channel)
{
/* not assigned */
pv->status = ECA_BADCHID;
faults ++;
}
else
{
if (ca_state(pv->channel) == cs_never_conn)
{
/* try to connect for the first time */
connectTimeout = PVwaitForConnect(pv, connectTimeout);
}
if (ca_state(pv->channel) == cs_conn)
{
pv->status = ca_array_get_callback(
dbf_type_to_DBR_TIME(pv->usedDatatype),
ca_element_count(pv->channel),
pv->channel,
getCallback, pv);
if (pv->status != ECA_NORMAL)
{
/* io error */
fprintf(stderr, "%s can't do get: %s\n",
ca_name(pv->channel), ca_message(pv->status));
faults ++;
}
else
{
pv->status = ECA_IOINPROGRESS;
pendingGets++;
}
}
else
{
pv->status = ECA_DISCONN;
faults++;
}
}
pv = va_arg(ap, epicsPV);
}
va_end(ap);
/* Wait for values */
ca_poll();
while (timeoutSec != 0.0 && pendingGets != 0)
{
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
ca_pend_event(wait);
if (timeoutSec > 0.0) timeoutSec -= wait;
wait *= 1.2;
if (wait > 0.5) wait = 0.5;
}
/* Check results */
va_start(ap, pv1);
pv = pv1;
while (pv)
{
if (pv->status == ECA_IOINPROGRESS)
{
pv->status = ECA_TIMEOUT;
faults++;
pendingGets--;
}
pv = va_arg(ap, epicsPV);
}
va_end(ap);
return faults;
}
static void putCallback(struct event_handler_args args)
{
epicsPV pv = args.usr;
printf("completing caput %s\n", ca_name(pv->channel));
if (pv->status == ECA_IOINPROGRESS)
{
pendingPuts--;
}
pv->status = args.status;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
fprintf(stderr, "putCallback %s: %s\n",
ca_name(pv->channel), ca_message(args.status));
return;
}
}
int caputDoubleList(double timeoutSec, int flags, epicsPV pv1, double value1, ...)
{
va_list ap;
epicsPV pv;
double value;
unsigned long faults = 0;
double wait = 0.001;
double connectTimeout = 2.0;
/* Prepate put requests */
va_start(ap, value1);
pv = pv1;
value = value1;
while (pv)
{
if (!pv->channel)
{
/* not assigned */
pv->status = ECA_BADCHID;
faults ++;
}
else
{
if (ca_state(pv->channel) == cs_never_conn)
{
/* try to connect for the first time */
connectTimeout = PVwaitForConnect(pv, connectTimeout);
}
if (ca_state(pv->channel) == cs_conn)
{
pv->status = ca_put_callback(
DBR_DOUBLE,
pv->channel,
&value,
putCallback, pv);
if (pv->status != ECA_NORMAL)
{
/* io error */
fprintf(stderr, "%s can't do put: %s\n",
ca_name(pv->channel), ca_message(pv->status));
faults ++;
}
else
{
pv->status = ECA_IOINPROGRESS;
pendingPuts++;
}
}
else
{
pv->status = ECA_DISCONN;
faults++;
}
}
pv = va_arg(ap, epicsPV);
value = va_arg(ap, double);
}
va_end(ap);
if (timeoutSec == 0) return faults;
/* Wait for acknowledge */
ca_poll();
while (timeoutSec != 0.0 && pendingPuts != 0)
{
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
ca_pend_event(wait);
if (timeoutSec > 0.0) timeoutSec -= wait;
wait *= 1.2;
if (wait > 0.5) wait = 0.5;
}
/* Check results */
if (!pendingPuts) return faults;
va_start(ap, value1);
pv = pv1;
while (pv)
{
if (pv->status == ECA_IOINPROGRESS)
{
pv->status = ECA_TIMEOUT;
faults++;
pendingPuts--;
}
pv = va_arg(ap, epicsPV);
va_arg(ap, double);
}
va_end(ap);
return faults;
}
/*
epicsPV& epicsPV::put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec)
{
if (!channel)
throw runtime_error("epicsPV::put: PV not linked to a channel");
switch (connectionState())
{
case cs_conn:
break;
case cs_never_conn:
timeoutSec = waitForConnect(timeoutSec, this);
if (connectionState() == cs_conn) break;
default:
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" "+connectionStateStr());
}
if (!hasWriteAccess())
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" not writable");
cout << "putting " << name() << ", elements: " << elements << ", datatype: " << datatype << " ";
switch (datatype)
{
case caTypeDouble:
cout << *(const double*)value;
break;
case caTypeLong:
cout << *(const long*)value;
break;
case caTypeString:
cout << (const char*)value;
break;
default:;
}
cout << endl;
SEVCHK(ca_array_put(datatype, elements, channel, value), "epicsPV::put");
ca_pend_io(timeoutSec);
return *this;
}
*/
+178
View File
@@ -0,0 +1,178 @@
#ifndef epicsPV_h
#define epicsPV_h
#include <cadef.h>
#include <caerr.h>
#include <alarm.h>
/* How epicsPVs work:
* A PV is a container for a remote named value provided via a so
* called EPICS "channel". It is "linked" to the channel in PVcreate()
* or later with PVlink().
* Because the original value is remote, the PV can only contain
* a copy of the value.
* With the caget() call, one or more PVs are updated from the remote value.
* After that, the updated value can be read as long, double or string.
* With the caput() call, the remote value is updated from the PV.
*/
#ifndef PV_DEFAULT_GET_TIMEOUT_SEC
#define PV_DEFAULT_GET_TIMEOUT_SEC 2.0
#endif
#ifndef PV_DEFAULT_PUT_TIMEOUT_SEC
#define PV_DEFAULT_PUT_TIMEOUT_SEC 10.0
#endif
typedef struct epicsPV_s* epicsPV;
typedef void (*PVconnectionCallback) (epicsPV pv, int connectionUp, void* userarg);
/* Channel Access datatypes (plus caTypeNative as default value)
* depending on the datatype of a PV, exporting the value
* to double, long or const char* may give different results
*/
typedef enum {
caTypeString,
caTypeShort,
caTypeFloat,
caTypeEnum,
caTypeChar,
caTypeLong,
caTypeDouble,
caTypeNoAccess,
caTypeNative
} caDatatype;
/* You don't need to access anything in epicsPV_priv.h directly
*/
#include "epicsPV_priv.h"
/* create PV and optionally link it to an EPICS channel
* normally, you call createPV with channelName but without
* preferredDatatype to use the native datatype of the channel
* you may read back datatype after connection
* if channelName is NULL you must call PVlink later
* a callback can be installed to track connection state changes
* connectionUp is 1 when the PV connected, 0 when it disconnected
* userarg is an arbitrary pointer that will be provided with the callback
*/
#define PVcreate(channelName) PVcreateWithTypeAndCallback(channelName, caTypeNative, NULL, NULL)
#define PVcreateWithType(channelName, type) PVcreateWithTypeAndCallback(channelName, type, NULL, NULL)
#define PVcreateWithCallback(channelName, cb, userarg) PVcreateWithTypeAndCallback(name, caTypeNative, cb, userarg)
epicsPV PVcreateWithTypeAndCallback(const char* channelName, caDatatype preferredDatatype,
PVconnectionCallback cb, void* userarg);
/* destroy PV and free all resources
*/
void PVdestroy(epicsPV pv);
/* explititely (re-)link PV to a (different) EPICS channel
* an unlink PV from a channel
* it is normally done implicitely by PVcreate and PVdestroy
* note: linking does not yet send a search request to the
* network until PVwaitForConnect is called (probably implicitely by caget, caput, camonitor)
*/
#define linkPV(pv, channelName)linkPVwithType(pv, channelName, caTypeNative)
int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype);
void PVunlink(epicsPV pv);
/* wait until one or all PVs are connected
* timeoutSec = 0.0 just sends connect requests but does not wait
* timeoutSec < 0.0 means wait forever
* returns remaining seconds
* calling this function is optional because it will be called
* implicitely before the first get or put on an unconnected PV
* using the get or put timeout
*/
double PVwaitForConnect(epicsPV pv, double timeoutSec);
#define PVwaitForConnectAll(timeoutSec) PVwaitForConnect(NULL, timeoutSec)
/* return channel name (what you provided to PVlink or PVcreate)
*/
#define PVname(pv) ((pv)->channel?ca_name((pv)->channel):"")
/* return error status of PV
* see $(EPICS_BASE)/include/caerr.h for error codes
*/
#define PVerrorStatus(pv) ((pv)->status)
#define PVerrorStatusString(pv) (ca_message((pv)->status))
/* return connection state
* see $(EPICS_BASE)/include/cadef.h for valid values
*/
#define PVconnectionState(pv) ((pv)->channel?ca_state((pv)->channel):cs_never_conn)
#define PVconnectionStateString(pv) PVconnectionStateStrings[PVconnectionState(pv)]
/* The following functions return information which is available
* as soon as the PV is connected.
*/
/* return currently used data type
*/
#define PVdatatype(pv) ((pv)->usedDatatype)
#define PVdatatypeString(pv) PVdataTypeStrings[PVdatatype(pv)]
/* return number of elements for array data
*/
#define PVnumberOfElements(pv) ((pv)->channel?ca_element_count((pv)->channel):0)
/* return units and precision (.EGU and .PREC fields)
* if PV does not have units or precision it returns "" and/or 0
*/
#define PVunits(pv) ((pv)->units)
#define PVprecision(pv) ((pv)->precision)
/* return access rights (might change at run-time)
*/
#define PVhasReadAccess(pv) ((pv)->channel?ca_read_access((pv)->channel):0)
#define PVhasWriteAccess(pv) ((pv)->channel?ca_write_access((pv)->channel):0)
/* get value from remote server
* do caget with as many PVs as possible in parallel to increase performance
*/
#define caget(pvs...) cagetList(PV_DEFAULT_GET_TIMEOUT_SEC, pvs, NULL)
#define cagetWithTimeout(timeoutSec, pvs...) cagetList(timeoutSec, pvs, NULL)
/* The following functions return information which is available
* after a successful get() or a monitor.
*/
/* return alarm severity and status (.SEVR and .STAT fields)
* see $(EPICS_BASE)/include/alarm.h for valid values
* not conencted PV returns epicsSevInvalid/epicsAlarmNone
*/
#define PValarmSeverity(pv) ((pv)->STRING?(pv)->STRING->severity:epicsSevInvalid)
#define PValarmSeverityString(pv) epicsAlarmSeverityStrings[PValarmSeverity(pv)]
#define PValarmStatus(pv) ((pv)->STRING?(pv)->STRING->status:epicsAlarmUDF)
#define PValarmStatusString(pv) epicsAlarmConditionStrings[PValarmStatus(pv)]
/* return time of record processing (.TIME field) as (epicsTimeStamp*)
* see $(EPICS_BASE)/include/epicsTime.h for epicsTimeStamp
*/
extern epicsTimeStamp PVinvalidTimestamp;
#define PVtimestamp(pv) &((pv)->STRING?(pv)->STRING->stamp:PVinvalidTimestamp)
/* return value of pv as long double or string
* for arrays use PVto*Element to access each element
*/
long PVtoLongElement(epicsPV pv, unsigned long index);
#define PVtoLong(pv) PVtoLongElement(pv,0)
double PVtoDoubleElement(epicsPV pv, unsigned long index);
#define PVtoDouble(pv) PVtoDoubleElement(pv,0)
/* flags for toString() */
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index);
#define PVtoStringWithUnits(pv) PVtoStringElement(pv,PV_WITHUNITS,0)
#define PVtoString(pv) PVtoStringElement(pv,0,0)
#define caputDouble(pv1, value1...) caputDoubleList(0.0, pv1, value1)
#define caputWaitDouble(timeoutSec, pv1, val1, ...) caputDoubleList(timeoutSec, pv1, value1)
int caputDoubleList(double timeoutSec, int flags, epicsPV pv1, double val1, ...);
#define caputLong(pv1, val1, ...)
#define caputString(pv1, val1, ...)
#endif
+560
View File
@@ -0,0 +1,560 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define epicsAlarmGLOBAL
#include "epicsPV.h"
static int contextCreated = 0;
static unsigned long pendingConnects = 0;
static unsigned long pendingGets = 0;
const char* PVdataTypeStrings[9] = {
"STRING",
"SHORT",
"FLOAT",
"ENUM",
"CHAR",
"LONG",
"DOUBLE",
"NO_ACCESS",
"UNDEFINED"
};
const char* PVconnectionStateStrings [4] = {
"never connected",
"disconnected",
"connected",
"closed"
};
struct epicsPV_s {
chid channel;
void* data;
void* info;
caDatatype usedDatatype;
caDatatype requestedDatatype;
int status;
char stringrep[40];
PVconnectionCallback connectionCallback;
void* userarg;
};
epicsPV PVcreateWithTypeAndConnectionCallback(const char* channelName,caDatatype preferredDatatype,
PVconnectionCallback cb, void* userarg)
{
epicsPV pv;
if (!contextCreated)
{
ca_context_create(ca_disable_preemptive_callback);
atexit(ca_context_destroy);
contextCreated = 1;
}
pv = malloc(sizeof (struct epicsPV_s));
pv->channel = NULL;
pv->data = NULL;
pv->info = NULL;
pv->usedDatatype = caTypeNative;
pv->requestedDatatype = caTypeNative;
pv->status = ECA_DISCONN;
pv->connectionCallback = cb;
pv->userarg = userarg;
if (channelName) PVlinkWithType(pv, channelName, preferredDatatype);
return pv;
}
void PVdestroy(epicsPV pv)
{
if (!pv) return;
PVunlink(pv);
free(pv);
}
static void connectCallback(struct connection_handler_args args)
{
epicsPV pv = ca_puser(args.chid);
int up = (args.op == CA_OP_CONN_UP);
if (up)
{
if (pv->usedDatatype == caTypeNative)
pendingConnects --;
if (pv->requestedDatatype != caTypeNative)
pv->usedDatatype = pv->requestedDatatype;
else
pv->usedDatatype = ca_field_type(pv->channel);
pv->data = realloc(pv->data, dbr_size_n(dbf_type_to_DBR_TIME(pv->usedDatatype),
ca_element_count(pv->channel)));
if (!pv->data)
{
pv->status = ECA_ALLOCMEM;
return;
}
if (pv->usedDatatype != DBF_STRING)
{
pv->info = realloc(pv->info, dbr_size_n(dbf_type_to_DBR_CTRL(pv->usedDatatype),1));
if (!pv->info)
{
pv->status = ECA_ALLOCMEM;
return;
}
pv->status = ca_get(dbf_type_to_DBR_CTRL(pv->usedDatatype), pv->channel, pv->info);
}
else
{
free(pv->info);
pv->info = NULL;
}
}
else
{
pv->status = ECA_DISCONN;
}
if (pv->connectionCallback)
{
pv->connectionCallback(pv, up, pv->userarg);
}
}
int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype)
{
int status;
if (!pv) return ECA_INTERNAL;
pv->requestedDatatype = preferredDatatype;
if (pv->channel) PVunlink(pv);
if (!channelName) return ECA_NORMAL;
status = ca_create_channel(channelName,
connectCallback, pv,
CA_PRIORITY_DEFAULT,
&pv->channel);
if (status == ECA_NORMAL)
{
pendingConnects ++;
}
return status;
}
void PVunlink(epicsPV pv)
{
if (pv && pv->channel)
{
if (pv->connectionCallback)
{
pv->connectionCallback(pv, 0, pv->userarg);
}
ca_clear_channel(pv->channel);
pv->channel = NULL;
free(pv->data);
pv->data = NULL;
free(pv->info);
pv->info = NULL;
pv->status = ECA_DISCONN;
if (pv->usedDatatype != caTypeNative)
{
pendingConnects --;
}
pv->usedDatatype = caTypeNative;
}
}
double PVwaitForConnect(epicsPV pv, double timeoutSec)
{
ca_poll();
double wait = 0.001;
while (timeoutSec != 0.0 &&
(pv ? ca_state(pv->channel) == cs_never_conn : pendingConnects != 0))
{
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
ca_pend_event(wait);
if (timeoutSec > 0.0) timeoutSec -= wait;
wait *= 1.2;
if (wait > 0.5) wait = 0.5;
}
return timeoutSec;
}
const char* PVname(epicsPV pv)
{
return pv && pv->channel ? ca_name(pv->channel) : "";
}
int PVerrorStatus(epicsPV pv)
{
return pv ? pv->status : ECA_BADCHID;
}
enum channel_state PVconnectionState(epicsPV pv)
{
return pv && pv->channel ? ca_state(pv->channel) : cs_never_conn;
}
caDatatype PVdatatype(epicsPV pv)
{
return pv ? pv->usedDatatype : caTypeNative;
}
unsigned long PVelementCount(epicsPV pv)
{
return pv && pv->channel ? ca_element_count(pv->channel) : 0;
}
const char* PVunits(epicsPV pv)
{
if (!pv->info) return "";
switch (pv->usedDatatype)
{
case DBF_CHAR:
return ((struct dbr_ctrl_char*)(pv->info))->units;
case DBF_SHORT:
return ((struct dbr_ctrl_short*)(pv->info))->units;
case DBF_LONG:
return ((struct dbr_ctrl_long*)(pv->info))->units;
case DBF_FLOAT:
return ((struct dbr_ctrl_float*)(pv->info))->units;
case DBF_DOUBLE:
return ((struct dbr_ctrl_double*)(pv->info))->units;
default:
return "";
}
}
int PVprecision(epicsPV pv)
{
if (!pv->info) return 0;
switch (pv->usedDatatype)
{
case DBF_FLOAT:
return ((struct dbr_ctrl_float*)(pv->info))->precision;
case DBF_DOUBLE:
return ((struct dbr_ctrl_double*)(pv->info))->precision;
default:
return 0;
}
}
int PVhasReadAccess(epicsPV pv)
{
return pv && pv->channel ? ca_read_access(pv->channel) : 0;
}
int PVhasWriteAccess(epicsPV pv)
{
return pv && pv->channel ? ca_write_access(pv->channel) : 0;
}
epicsAlarmSeverity PValarmSeverity(epicsPV pv)
{
if (pv && pv->data)
{
epicsUInt16 sevr = ((struct dbr_time_string*)(pv->data))->severity;
if (sevr <= lastEpicsAlarmSev) return sevr;
}
return epicsSevInvalid;
}
epicsAlarmCondition PValarmStatus(epicsPV pv)
{
if (pv && pv->data)
{
epicsUInt16 stat = ((struct dbr_time_string*)(pv->data))->status;
if (stat <= lastEpicsAlarmCond) return stat;
}
return epicsAlarmUDF;
}
const epicsTimeStamp* PVtimestamp(epicsPV pv)
{
static const epicsTimeStamp zerotime = {0,0};
if (pv && pv->data)
{
return &((struct dbr_time_string*)(pv->data))->stamp;
}
return &zerotime;
}
double PVtoDoubleElement(epicsPV pv, unsigned long index)
{
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
{
return atof("NAN");
}
switch (pv->usedDatatype)
{
case DBF_CHAR:
return (&((struct dbr_time_char*)(pv->data))->value)[index];
case DBF_SHORT:
return (&((struct dbr_time_short*)(pv->data))->value)[index];
case DBF_LONG:
return (&((struct dbr_time_long*)(pv->data))->value)[index];
case DBF_FLOAT:
return (&((struct dbr_time_float*)(pv->data))->value)[index];
case DBF_DOUBLE:
return (&((struct dbr_time_double*)(pv->data))->value)[index];
case DBF_ENUM:
return (&((struct dbr_time_enum*)(pv->data))->value)[index];
case DBF_STRING:
return atof((&((struct dbr_time_string*)(pv->data))->value)[index]);
default:
return atof("NAN");
}
}
long PVtoLongElement(epicsPV pv, unsigned long index)
{
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
{
return 0;
}
switch (pv->usedDatatype)
{
case DBF_CHAR:
return (&((struct dbr_time_char*)(pv->data))->value)[index];
case DBF_SHORT:
return (&((struct dbr_time_short*)(pv->data))->value)[index];
case DBF_LONG:
return (&((struct dbr_time_long*)(pv->data))->value)[index];
case DBF_FLOAT:
return (&((struct dbr_time_float*)(pv->data))->value)[index];
case DBF_DOUBLE:
return (&((struct dbr_time_double*)(pv->data))->value)[index];
case DBF_ENUM:
return (&((struct dbr_time_enum*)(pv->data))->value)[index];
case DBF_STRING:
return atoi((&((struct dbr_time_string*)(pv->data))->value)[index]);
default:
return 0;
}
}
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index)
{
int prec;
double val;
long ival;
struct dbr_ctrl_enum* enuminfo;
const char* units;
if (!pv || !pv->data) return "<not connected>";
if (index > ca_element_count(pv->channel)) return "<out of bounds>";
switch (pv->usedDatatype)
{
case DBF_CHAR:
ival = (&((struct dbr_time_char*)(pv->data))->value)[index];
units = ((struct dbr_ctrl_char*)(pv->info))->units;
goto printint;
case DBF_SHORT:
ival = (&((struct dbr_time_short*)(pv->data))->value)[index];
units = ((struct dbr_ctrl_short*)(pv->info))->units;
goto printint;
case DBF_LONG:
ival = (&((struct dbr_time_long*)(pv->data))->value)[index];
units = ((struct dbr_ctrl_long*)(pv->info))->units;
printint:
if (flags & PV_WITHUNITS && units[0])
sprintf(pv->stringrep, "%ld %s", ival, units);
else
sprintf(pv->stringrep, "%ld", ival);
break;
case DBF_FLOAT:
val = (&((struct dbr_time_float*)(pv->data))->value)[index];
prec = ((struct dbr_ctrl_float*)(pv->info))->precision;
units = ((struct dbr_ctrl_float*)(pv->info))->units;
goto printdouble;
case DBF_DOUBLE:
val = (&((struct dbr_time_double*)(pv->data))->value)[index];
prec = ((struct dbr_ctrl_double*)(pv->info))->precision;
units = ((struct dbr_ctrl_double*)(pv->info))->units;
printdouble:
if (prec > 17) prec = -17;
if (prec < -17) prec = -17;
if (flags & PV_WITHUNITS && units[0])
{
if (prec >= 0)
sprintf(pv->stringrep, "%.*f %s", prec, val, units);
else
sprintf(pv->stringrep, "%.*g %s", -prec, val, units);
}
else
{
if (prec >= 0)
sprintf(pv->stringrep, "%.*f", prec, val);
else
sprintf(pv->stringrep, "%.*g", -prec, val);
}
break;
case DBF_STRING:
return (&((struct dbr_time_string*)(pv->data))->value)[index];
case DBF_ENUM:
ival = (&((struct dbr_time_enum*)(pv->data))->value)[index];
enuminfo = (struct dbr_ctrl_enum*)(pv->info);
if (ival < enuminfo->no_str)
return enuminfo->strs[ival];
sprintf(pv->stringrep, "%ld", ival);
break;
default:
return "<not accessible>";
}
return pv->stringrep;
}
static void getCallback(struct event_handler_args args)
{
epicsPV pv = args.usr;
printf("updating %s %ld elements\n", ca_name(pv->channel), args.count);
if (pv->status == ECA_IOINPROGRESS)
{
pendingGets--;
}
pv->status = args.status;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
fprintf(stderr, "getCallback %s: %s\n",
ca_name(pv->channel), ca_message(args.status));
return;
}
if (args.chid != pv->channel)
{
fprintf(stderr, "INTERNAL ERROR in updateValueCallback %s: got unexpected chid\n",
ca_name(pv->channel));
return;
}
if (args.type != dbf_type_to_DBR_TIME(pv->usedDatatype))
{
fprintf(stderr, "INTERNAL ERROR in updateValueCallback %s: got unexpected type\n",
ca_name(pv->channel));
return;
}
memcpy(pv->data, args.dbr, dbr_size_n(args.type, args.count));
}
int cagetList(epicsPV pv1, ...)
{
va_list ap;
epicsPV pv;
unsigned long faults = 0;
double timeoutSec = 2.0;
double wait = 0.001;
/* Prepate get requests */
va_start(ap, pv1);
pv = pv1;
while (pv)
{
if (!pv->channel)
{
/* not assigned */
pv->status = ECA_BADCHID;
faults ++;
}
else
{
if (ca_state(pv->channel) == cs_never_conn)
{
/* try to connect for the first time */
timeoutSec = PVwaitForConnect(pv, timeoutSec);
}
if (ca_state(pv->channel) == cs_conn)
{
pv->status = ca_array_get_callback(
dbf_type_to_DBR_TIME(pv->usedDatatype),
ca_element_count(pv->channel),
pv->channel,
getCallback, pv);
if (pv->status != ECA_NORMAL)
{
/* io error */
fprintf(stderr, "%s can't do get: %s\n",
ca_name(pv->channel), ca_message(pv->status));
faults ++;
}
else
{
pv->status = ECA_IOINPROGRESS;
pendingGets++;
}
}
else
{
pv->status = ECA_DISCONN;
faults++;
}
}
pv = va_arg(ap, epicsPV);
}
va_end(ap);
/* Wait for values */
timeoutSec = 2.0;
ca_poll();
while (timeoutSec != 0.0 && pendingGets != 0)
{
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
printf("waiting for %g seconds\n", wait);
ca_pend_event(wait);
if (timeoutSec > 0.0) timeoutSec -= wait;
wait *= 1.2;
if (wait > 0.5) wait = 0.5;
}
/* Check results */
va_start(ap, pv1);
pv = pv1;
while (pv)
{
if (pv->status == ECA_IOINPROGRESS)
{
pv->status = ECA_TIMEOUT;
faults++;
pendingGets--;
}
pv = va_arg(ap, epicsPV);
}
va_end(ap);
printf ("pendingGets = %ld, faults = %ld\n",
pendingGets, faults);
return faults;
}
/*
epicsPV& epicsPV::put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec)
{
if (!channel)
throw runtime_error("epicsPV::put: PV not linked to a channel");
switch (connectionState())
{
case cs_conn:
break;
case cs_never_conn:
timeoutSec = waitForConnect(timeoutSec, this);
if (connectionState() == cs_conn) break;
default:
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" "+connectionStateStr());
}
if (!hasWriteAccess())
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" not writable");
cout << "putting " << name() << ", elements: " << elements << ", datatype: " << datatype << " ";
switch (datatype)
{
case caTypeDouble:
cout << *(const double*)value;
break;
case caTypeLong:
cout << *(const long*)value;
break;
case caTypeString:
cout << (const char*)value;
break;
default:;
}
cout << endl;
SEVCHK(ca_array_put(datatype, elements, channel, value), "epicsPV::put");
ca_pend_io(timeoutSec);
return *this;
}
*/
+38
View File
@@ -0,0 +1,38 @@
struct epicsPV_s {
chid channel;
union {
void* data;
struct dbr_time_char* CHAR;
struct dbr_time_short* SHORT;
struct dbr_time_long* LONG;
struct dbr_time_float* FLOAT;
struct dbr_time_double* DOUBLE;
struct dbr_time_enum* ENUM;
struct dbr_time_string* STRING;
};
union {
void* info;
struct dbr_ctrl_char* info_CHAR;
struct dbr_ctrl_short* info_SHORT;
struct dbr_ctrl_long* info_LONG;
struct dbr_ctrl_float* info_FLOAT;
struct dbr_ctrl_double* info_DOUBLE;
struct dbr_ctrl_enum* info_ENUM;
};
const char* units;
int precision;
caDatatype usedDatatype;
caDatatype requestedDatatype;
int status;
char stringrep[40];
PVconnectionCallback connectionCallback;
void* userarg;
};
/* don't use cagetList directly, use caget */
int cagetList(double timeoutSec, epicsPV pv1, ...);
extern const char* PVdataTypeStrings[9];
extern const char* PVconnectionStateStrings [4];
#define PV_WITHUNITS 1
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
#include "epicsPV.h"
using namespace std;
int main (int argc, const char** argv)
{
try {
epicsPV pv(argv[1]);
pv.get();
cout << pv.name() << " = " << pv <<
" SEVR=" << pv.alarmSeverity() <<
" STAT=" << pv.alarmStatus() << endl;
if (argc == 3)
{
pv.put(argv[2]);
pv.get();
cout << pv.name() << " = " << pv <<
" SEVR=" << pv.alarmSeverity() <<
" STAT=" << pv.alarmStatus() << endl;
}
}
catch (exception& e)
{
cout << endl << "Error: " << e.what() << endl;
}
return 0;
}
BIN
View File
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
#include "epicsPV.h"
#include <stdio.h>
void printPV(epicsPV pv)
{
char timebuf[32];
epicsTimeToStrftime (timebuf, sizeof(timebuf),
"%d.%m.%Y %H:%M:%S.%03f", PVtimestamp(pv));
printf("%s %s = %s %s %s [%s]\n",
timebuf,
PVname(pv), PVtoStringWithUnits(pv),
PValarmSeverityString(pv),
PValarmStatusString(pv),
PVerrorStatusString(pv));
}
int main (int argc, const char** argv)
{
epicsPV current = PVcreate("ARIDI-PCT:CURRENT");
epicsPV gap = PVcreate("X10SA-ID-GAP:SET");
caget(current, gap);
printPV(current);
printPV(gap);
return 0;
}