commit 80df91d0df38d148db375f0cf8152869d94fd637 Author: Elke Zimoch Date: Fri Feb 6 10:10:27 2026 +0100 Copy of old lessons from afs webpage diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..e6f3864 --- /dev/null +++ b/Readme.md @@ -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/ + diff --git a/caLesson1/Makefile b/caLesson1/Makefile new file mode 100644 index 0000000..71ec109 --- /dev/null +++ b/caLesson1/Makefile @@ -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_ += +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 diff --git a/caLesson1/caLesson1.c b/caLesson1/caLesson1.c new file mode 100644 index 0000000..ebe71af --- /dev/null +++ b/caLesson1/caLesson1.c @@ -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 + +/* include EPICS headers */ +#include + +/* +#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; +} diff --git a/caLesson2/Makefile b/caLesson2/Makefile new file mode 100644 index 0000000..71ec109 --- /dev/null +++ b/caLesson2/Makefile @@ -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_ += +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 diff --git a/caLesson2/caLesson2.c b/caLesson2/caLesson2.c new file mode 100644 index 0000000..eed6777 --- /dev/null +++ b/caLesson2/caLesson2.c @@ -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 + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include + +/* 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; +} diff --git a/caLesson3/Makefile b/caLesson3/Makefile new file mode 100644 index 0000000..71ec109 --- /dev/null +++ b/caLesson3/Makefile @@ -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_ += +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 diff --git a/caLesson3/caLesson3.c b/caLesson3/caLesson3.c new file mode 100644 index 0000000..0e62ef7 --- /dev/null +++ b/caLesson3/caLesson3.c @@ -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 + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include + +/* 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; +} diff --git a/caLesson4/Makefile b/caLesson4/Makefile new file mode 100644 index 0000000..71ec109 --- /dev/null +++ b/caLesson4/Makefile @@ -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_ += +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 diff --git a/caLesson4/README b/caLesson4/README new file mode 100644 index 0000000..551664e --- /dev/null +++ b/caLesson4/README @@ -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 diff --git a/caLesson4/caLesson4a.c b/caLesson4/caLesson4a.c new file mode 100644 index 0000000..5190743 --- /dev/null +++ b/caLesson4/caLesson4a.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 + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include + +/* 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; +} diff --git a/caLesson4/caLesson4b.c b/caLesson4/caLesson4b.c new file mode 100644 index 0000000..42cc166 --- /dev/null +++ b/caLesson4/caLesson4b.c @@ -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 + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include + +/* 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; +} diff --git a/caLesson5/Makefile b/caLesson5/Makefile new file mode 100644 index 0000000..71ec109 --- /dev/null +++ b/caLesson5/Makefile @@ -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_ += +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 diff --git a/caLesson5/README b/caLesson5/README new file mode 100644 index 0000000..8a4ef0f --- /dev/null +++ b/caLesson5/README @@ -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 + diff --git a/caLesson5/caLesson5 b/caLesson5/caLesson5 new file mode 100755 index 0000000..c6c31e9 Binary files /dev/null and b/caLesson5/caLesson5 differ diff --git a/caLesson5/caLesson5.adl b/caLesson5/caLesson5.adl new file mode 100644 index 0000000..d1e6839 --- /dev/null +++ b/caLesson5/caLesson5.adl @@ -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 { + } +} diff --git a/caLesson5/caLesson5.c b/caLesson5/caLesson5.c new file mode 100644 index 0000000..80a7675 --- /dev/null +++ b/caLesson5/caLesson5.c @@ -0,0 +1,457 @@ +/* caLesson5.c + by Dirk Zimoch, 2007 + +*/ + +#include +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include + +/* 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("\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: \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; +} diff --git a/caLesson5/caLesson5.db b/caLesson5/caLesson5.db new file mode 100644 index 0000000..ddc892d --- /dev/null +++ b/caLesson5/caLesson5.db @@ -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. +} diff --git a/caLesson5/caLesson5a b/caLesson5/caLesson5a new file mode 100755 index 0000000..1e30de6 Binary files /dev/null and b/caLesson5/caLesson5a differ diff --git a/caLesson5/caLesson5a.c b/caLesson5/caLesson5a.c new file mode 100644 index 0000000..fc8d50e --- /dev/null +++ b/caLesson5/caLesson5a.c @@ -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 +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include + +/* 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 \n" + "Where 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; +} diff --git a/caLesson5/caLesson5b b/caLesson5/caLesson5b new file mode 100755 index 0000000..909bf04 Binary files /dev/null and b/caLesson5/caLesson5b differ diff --git a/caLesson5/caLesson5b.c b/caLesson5/caLesson5b.c new file mode 100644 index 0000000..62a5f57 --- /dev/null +++ b/caLesson5/caLesson5b.c @@ -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 +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include +#include +#include + +/* 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 \n" + "Where 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; +} diff --git a/caLesson6/caClientWrapperC++/Makefile b/caLesson6/caClientWrapperC++/Makefile new file mode 100644 index 0000000..43a2975 --- /dev/null +++ b/caLesson6/caClientWrapperC++/Makefile @@ -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_ += +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 diff --git a/caLesson6/caClientWrapperC++/caLesson6.c b/caLesson6/caClientWrapperC++/caLesson6.c new file mode 100644 index 0000000..71d8964 --- /dev/null +++ b/caLesson6/caClientWrapperC++/caLesson6.c @@ -0,0 +1,411 @@ +/* caLesson6.c + by Dirk Zimoch, 2007 + + + + + +*/ + +#include +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include +#include +#include + +/* 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 \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 \n" + "Where 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; +} diff --git a/caLesson6/caClientWrapperC++/caLesson6b.cc b/caLesson6/caClientWrapperC++/caLesson6b.cc new file mode 100644 index 0000000..71d8964 --- /dev/null +++ b/caLesson6/caClientWrapperC++/caLesson6b.cc @@ -0,0 +1,411 @@ +/* caLesson6.c + by Dirk Zimoch, 2007 + + + + + +*/ + +#include +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include +#include +#include + +/* 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 \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 \n" + "Where 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; +} diff --git a/caLesson6/caClientWrapperC++/epicsExceptions.h b/caLesson6/caClientWrapperC++/epicsExceptions.h new file mode 100644 index 0000000..c0520fa --- /dev/null +++ b/caLesson6/caClientWrapperC++/epicsExceptions.h @@ -0,0 +1,5 @@ +#include +class epicsExceptionOutOfMemory : public runtime_error +{ + public epicsExceptionOutOfMemory(char* where) +} diff --git a/caLesson6/caClientWrapperC++/epicsPV.cc b/caLesson6/caClientWrapperC++/epicsPV.cc new file mode 100644 index 0000000..68b0512 --- /dev/null +++ b/caLesson6/caClientWrapperC++/epicsPV.cc @@ -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(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(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(info)->units; + case DBF_SHORT: + return static_cast(info)->units; + case DBF_LONG: + return static_cast(info)->units; + case DBF_FLOAT: + return static_cast(info)->units; + case DBF_DOUBLE: + return static_cast(info)->units; + default: + return ""; + } +} + +int epicsPV::precision() const +{ + if (!info) return 0; + switch (usedDatatype) + { + case DBF_FLOAT: + return static_cast(info)->precision; + case DBF_DOUBLE: + return static_cast(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(data)->value)[index]; + case DBF_SHORT: + return (&static_cast(data)->value)[index]; + case DBF_LONG: + return (&static_cast(data)->value)[index]; + case DBF_FLOAT: + return (&static_cast(data)->value)[index]; + case DBF_DOUBLE: + return (&static_cast(data)->value)[index]; + case DBF_ENUM: + return (&static_cast(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(data)->value)[index]; + case DBF_SHORT: + return (&static_cast(data)->value)[index]; + case DBF_LONG: + return (&static_cast(data)->value)[index]; + case DBF_FLOAT: + return static_cast((&static_cast(data)->value)[index]); + case DBF_DOUBLE: + return static_cast((&static_cast(data)->value)[index]); + case DBF_ENUM: + return (&static_cast(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 ""; + if (index > elements()) throw epicsExceptionOutOfBounds(); + switch (usedDatatype) + { + case DBF_CHAR: + ival = (&static_cast(data)->value)[index]; + units = static_cast(info)->units; + goto printint; + case DBF_SHORT: + ival = (&static_cast(data)->value)[index]; + units = static_cast(info)->units; + goto printint; + case DBF_LONG: + ival = (&static_cast(data)->value)[index]; + units = static_cast(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(data)->value)[index]; + prec = static_cast(info)->precision; + units = static_cast(info)->units; + goto printdouble; + case DBF_DOUBLE: + val = (&static_cast(data)->value)[index]; + prec = static_cast(info)->precision; + units = static_cast(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(data)->value)[index]; + case DBF_ENUM: + ival = (&static_cast(data)->value)[index]; + enuminfo = static_cast(info); + if (ival < enuminfo->no_str) + return enuminfo->strs[ival]; + sprintf(stringrep, "%ld", ival); + break; + default: + return ""; + } + 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; +} + diff --git a/caLesson6/caClientWrapperC++/epicsPV.h b/caLesson6/caClientWrapperC++/epicsPV.h new file mode 100644 index 0000000..87f34fa --- /dev/null +++ b/caLesson6/caClientWrapperC++/epicsPV.h @@ -0,0 +1,215 @@ +#ifndef epicsPV_h +#define epicsPV_h + +#include +#include +#include +#include + +// 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(data)->severity; + if (sevr <= lastEpicsAlarmSev) + return static_cast(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(data)->status; + if (stat <= lastEpicsAlarmCond) + return static_cast(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(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 diff --git a/caLesson6/caClientWrapperC++/pvexample b/caLesson6/caClientWrapperC++/pvexample new file mode 100755 index 0000000..6a1cd5b Binary files /dev/null and b/caLesson6/caClientWrapperC++/pvexample differ diff --git a/caLesson6/caClientWrapperC++/pvexample.cc b/caLesson6/caClientWrapperC++/pvexample.cc new file mode 100644 index 0000000..dcb2fd7 --- /dev/null +++ b/caLesson6/caClientWrapperC++/pvexample.cc @@ -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; +} diff --git a/caLesson6/caClientWrapperC++/testPV b/caLesson6/caClientWrapperC++/testPV new file mode 100755 index 0000000..023eae7 Binary files /dev/null and b/caLesson6/caClientWrapperC++/testPV differ diff --git a/caLesson6/caClientWrapperC++/testPV.cc b/caLesson6/caClientWrapperC++/testPV.cc new file mode 100644 index 0000000..73faba5 --- /dev/null +++ b/caLesson6/caClientWrapperC++/testPV.cc @@ -0,0 +1,54 @@ +#include "epicsPV.h" +#include + +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; +} diff --git a/caLesson6/caClientWrapperC/Makefile b/caLesson6/caClientWrapperC/Makefile new file mode 100644 index 0000000..51e1ee1 --- /dev/null +++ b/caLesson6/caClientWrapperC/Makefile @@ -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_ += +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 diff --git a/caLesson6/caClientWrapperC/caLesson6.c b/caLesson6/caClientWrapperC/caLesson6.c new file mode 100644 index 0000000..71d8964 --- /dev/null +++ b/caLesson6/caClientWrapperC/caLesson6.c @@ -0,0 +1,411 @@ +/* caLesson6.c + by Dirk Zimoch, 2007 + + + + + +*/ + +#include +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include +#include +#include + +/* 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 \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 \n" + "Where 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; +} diff --git a/caLesson6/caClientWrapperC/caLesson6b.cc b/caLesson6/caClientWrapperC/caLesson6b.cc new file mode 100644 index 0000000..71d8964 --- /dev/null +++ b/caLesson6/caClientWrapperC/caLesson6b.cc @@ -0,0 +1,411 @@ +/* caLesson6.c + by Dirk Zimoch, 2007 + + + + + +*/ + +#include +#include + +/* include EPICS headers */ +#include +#define epicsAlarmGLOBAL +#include +#include +#include + +/* 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 \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 \n" + "Where 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; +} diff --git a/caLesson6/caClientWrapperC/epicsExceptions.h b/caLesson6/caClientWrapperC/epicsExceptions.h new file mode 100644 index 0000000..c0520fa --- /dev/null +++ b/caLesson6/caClientWrapperC/epicsExceptions.h @@ -0,0 +1,5 @@ +#include +class epicsExceptionOutOfMemory : public runtime_error +{ + public epicsExceptionOutOfMemory(char* where) +} diff --git a/caLesson6/caClientWrapperC/epicsPV.c b/caLesson6/caClientWrapperC/epicsPV.c new file mode 100644 index 0000000..6fa85de --- /dev/null +++ b/caLesson6/caClientWrapperC/epicsPV.c @@ -0,0 +1,572 @@ +#include +#include +#include + +#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 ""; + if (index > ca_element_count(pv->channel)) return ""; + 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 ""; + } + 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; +} +*/ diff --git a/caLesson6/caClientWrapperC/epicsPV.h b/caLesson6/caClientWrapperC/epicsPV.h new file mode 100644 index 0000000..ef6100d --- /dev/null +++ b/caLesson6/caClientWrapperC/epicsPV.h @@ -0,0 +1,178 @@ +#ifndef epicsPV_h +#define epicsPV_h + +#include +#include +#include + +/* 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 diff --git a/caLesson6/caClientWrapperC/epicsPV2.c b/caLesson6/caClientWrapperC/epicsPV2.c new file mode 100644 index 0000000..2b7e2a9 --- /dev/null +++ b/caLesson6/caClientWrapperC/epicsPV2.c @@ -0,0 +1,560 @@ +#include +#include +#include + +#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 ""; + if (index > ca_element_count(pv->channel)) return ""; + 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 ""; + } + 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; +} +*/ diff --git a/caLesson6/caClientWrapperC/epicsPV_priv.h b/caLesson6/caClientWrapperC/epicsPV_priv.h new file mode 100644 index 0000000..b7fb692 --- /dev/null +++ b/caLesson6/caClientWrapperC/epicsPV_priv.h @@ -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 diff --git a/caLesson6/caClientWrapperC/pvexample b/caLesson6/caClientWrapperC/pvexample new file mode 100755 index 0000000..6a1cd5b Binary files /dev/null and b/caLesson6/caClientWrapperC/pvexample differ diff --git a/caLesson6/caClientWrapperC/pvexample.cc b/caLesson6/caClientWrapperC/pvexample.cc new file mode 100644 index 0000000..dcb2fd7 --- /dev/null +++ b/caLesson6/caClientWrapperC/pvexample.cc @@ -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; +} diff --git a/caLesson6/caClientWrapperC/testPV b/caLesson6/caClientWrapperC/testPV new file mode 100755 index 0000000..4cb8751 Binary files /dev/null and b/caLesson6/caClientWrapperC/testPV differ diff --git a/caLesson6/caClientWrapperC/testPV.c b/caLesson6/caClientWrapperC/testPV.c new file mode 100644 index 0000000..d0ecad7 --- /dev/null +++ b/caLesson6/caClientWrapperC/testPV.c @@ -0,0 +1,26 @@ +#include "epicsPV.h" +#include + +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; +}