Copy of old lessons from afs webpage
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
@@ -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
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user