From 80df91d0df38d148db375f0cf8152869d94fd637 Mon Sep 17 00:00:00 2001 From: Elke Zimoch Date: Fri, 6 Feb 2026 10:10:27 +0100 Subject: [PATCH] Copy of old lessons from afs webpage --- Readme.md | 12 + caLesson1/Makefile | 130 ++++ caLesson1/caLesson1.c | 171 ++++++ caLesson2/Makefile | 130 ++++ caLesson2/caLesson2.c | 119 ++++ caLesson3/Makefile | 130 ++++ caLesson3/caLesson3.c | 338 +++++++++++ caLesson4/Makefile | 130 ++++ caLesson4/README | 13 + caLesson4/caLesson4a.c | 118 ++++ caLesson4/caLesson4b.c | 124 ++++ caLesson5/Makefile | 130 ++++ caLesson5/README | 26 + caLesson5/caLesson5 | Bin 0 -> 25504 bytes caLesson5/caLesson5.adl | 138 +++++ caLesson5/caLesson5.c | 457 ++++++++++++++ caLesson5/caLesson5.db | 51 ++ caLesson5/caLesson5a | Bin 0 -> 20860 bytes caLesson5/caLesson5a.c | 326 ++++++++++ caLesson5/caLesson5b | Bin 0 -> 21806 bytes caLesson5/caLesson5b.c | 367 +++++++++++ caLesson6/caClientWrapperC++/Makefile | 132 ++++ caLesson6/caClientWrapperC++/caLesson6.c | 411 +++++++++++++ caLesson6/caClientWrapperC++/caLesson6b.cc | 411 +++++++++++++ .../caClientWrapperC++/epicsExceptions.h | 5 + caLesson6/caClientWrapperC++/epicsPV.cc | 420 +++++++++++++ caLesson6/caClientWrapperC++/epicsPV.h | 215 +++++++ caLesson6/caClientWrapperC++/pvexample | Bin 0 -> 106274 bytes caLesson6/caClientWrapperC++/pvexample.cc | 27 + caLesson6/caClientWrapperC++/testPV | Bin 0 -> 110598 bytes caLesson6/caClientWrapperC++/testPV.cc | 54 ++ caLesson6/caClientWrapperC/Makefile | 132 ++++ caLesson6/caClientWrapperC/caLesson6.c | 411 +++++++++++++ caLesson6/caClientWrapperC/caLesson6b.cc | 411 +++++++++++++ caLesson6/caClientWrapperC/epicsExceptions.h | 5 + caLesson6/caClientWrapperC/epicsPV.c | 572 ++++++++++++++++++ caLesson6/caClientWrapperC/epicsPV.h | 178 ++++++ caLesson6/caClientWrapperC/epicsPV2.c | 560 +++++++++++++++++ caLesson6/caClientWrapperC/epicsPV_priv.h | 38 ++ caLesson6/caClientWrapperC/pvexample | Bin 0 -> 106274 bytes caLesson6/caClientWrapperC/pvexample.cc | 27 + caLesson6/caClientWrapperC/testPV | Bin 0 -> 31543 bytes caLesson6/caClientWrapperC/testPV.c | 26 + 43 files changed, 6945 insertions(+) create mode 100644 Readme.md create mode 100644 caLesson1/Makefile create mode 100644 caLesson1/caLesson1.c create mode 100644 caLesson2/Makefile create mode 100644 caLesson2/caLesson2.c create mode 100644 caLesson3/Makefile create mode 100644 caLesson3/caLesson3.c create mode 100644 caLesson4/Makefile create mode 100644 caLesson4/README create mode 100644 caLesson4/caLesson4a.c create mode 100644 caLesson4/caLesson4b.c create mode 100644 caLesson5/Makefile create mode 100644 caLesson5/README create mode 100755 caLesson5/caLesson5 create mode 100644 caLesson5/caLesson5.adl create mode 100644 caLesson5/caLesson5.c create mode 100644 caLesson5/caLesson5.db create mode 100755 caLesson5/caLesson5a create mode 100644 caLesson5/caLesson5a.c create mode 100755 caLesson5/caLesson5b create mode 100644 caLesson5/caLesson5b.c create mode 100644 caLesson6/caClientWrapperC++/Makefile create mode 100644 caLesson6/caClientWrapperC++/caLesson6.c create mode 100644 caLesson6/caClientWrapperC++/caLesson6b.cc create mode 100644 caLesson6/caClientWrapperC++/epicsExceptions.h create mode 100644 caLesson6/caClientWrapperC++/epicsPV.cc create mode 100644 caLesson6/caClientWrapperC++/epicsPV.h create mode 100755 caLesson6/caClientWrapperC++/pvexample create mode 100644 caLesson6/caClientWrapperC++/pvexample.cc create mode 100755 caLesson6/caClientWrapperC++/testPV create mode 100644 caLesson6/caClientWrapperC++/testPV.cc create mode 100644 caLesson6/caClientWrapperC/Makefile create mode 100644 caLesson6/caClientWrapperC/caLesson6.c create mode 100644 caLesson6/caClientWrapperC/caLesson6b.cc create mode 100644 caLesson6/caClientWrapperC/epicsExceptions.h create mode 100644 caLesson6/caClientWrapperC/epicsPV.c create mode 100644 caLesson6/caClientWrapperC/epicsPV.h create mode 100644 caLesson6/caClientWrapperC/epicsPV2.c create mode 100644 caLesson6/caClientWrapperC/epicsPV_priv.h create mode 100755 caLesson6/caClientWrapperC/pvexample create mode 100644 caLesson6/caClientWrapperC/pvexample.cc create mode 100755 caLesson6/caClientWrapperC/testPV create mode 100644 caLesson6/caClientWrapperC/testPV.c 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 0000000000000000000000000000000000000000..c6c31e9f211d3b2bd626440896e6645047b8e920 GIT binary patch literal 25504 zcmch9d3;pW-S@q7C(Hl?2_XWq=wK4r2SPv=@qv&4i3Ea42#Cu#B$G@q$%M>=2#P=x zs%eVit8M9HTea2Jy|z_AS|t`YtXq|87qwPVCqfmKB4XRT-|t!O++peaKL0%T^Eq?Q zZ$0PtJKLRe&OMtJlrA())6kF0una;+Z*Y63A@06JQl=Sx!()s!vJv+IinM8)!GrKF z(hO+`ZUhS<3re!KxV;R60W*+B-3;P^tU>u4$RkkR$dGh_dlWPTugfrM5STCNNE$ch zB0T2yG8_lYKsxha7>i8CB5wviUyW27cE^bWLXXdmU|GwIS5{ap$NkeMk4e@&<}a&hkgi|2-J5r!U%-35Jn+nA?U~U zlSUw>4Sbj{AK??3G<=8$sx+hiD$VkF5C$NyZukt5kue-`x=NoMv7GcT@q>)ddiNtf z_)WL>`-oZog9wS8ZdMQILyS(uM?pUqG3lQn907ePV$x3_cB5StK!Efu2w8x?Mof4O zLJr`26nrHdFb(i}#H7=Azkz;4fcqKMz!!o3fTDX4R{|a{=!UTxaSh-~1;-G_0XHi6 zNyHt1w<-90kf%KWQ8%CUGT+-ke@D?*0KOaWECsXfn(xr%8DP8#_)*Z=I#}KxAUq9t zf`WgE_yFK_3f_KFe)QXIDu4RB3-p8Fe_ZkZ1aQ%=Wck(r-j4j)CI+GArz6&nABhqT zASP^}T~~+Dpp9VBrArHzloT4l`lkB05v)aI1Q(T-%`YeomMvUZv7jrY78?5F)k9d_2Gt^ zV0?XZ*l2EzS4ToEB*#Ost3~GZp@#YmQX(8`sg49gO*O&lXj4>HSiGis!UXpG=>%z7-~y)Vs;h%BLV1L$CBC_(zA0Wy<6=f*sG%WR zZ3Iio;DDO?reJFzAAs0lSp z4ma0V$0n~1#lm7xswmB$bj{2Qj76m-^9zGh15;3`Xax4dp6CLGv3!^&Mni;61v6d( z62~Dv8TIuTB3#A`iv1wkVjm1j>=S7iIM|odFqqtl*n=$YW{k#nFJlZu4>HC;^9W<~ zqDL8H0Nl+OgVApoW02}*jGUfki~;L;#u$WNWQ;-bRmSK*2N+{O{3BxwkZ&=@!1XR; zbiU=CAG9C#CN4p76A$v|T8z5;_Zhv@cYvce_Xqf;UA?(1afHd9cYE>Vvg8rcdR`SU zEr`$!J>3GPB@qtcJ&y>O7DfDo?-VdCi;N|_UBI+3!pi8`EMQt1nMt@=z_d6rkMLCj zrsa`h!pj6q4@8y`o+n^>BC?Y36amvCk*f&%1x(LGXi!gvfa#$~Ghsu(^i<>;!bkoM zL@qrR*-ZFdzXSi4&PQJAYfd! zWnU~{7|XiaS-!2^aaZrFU03{b|31M3|8Kr%9>=8Cad#y!S9fJ>eJwt;^H_Vg`zM5Z zU$fDzhn`I%tGRz)=P_A#cWp<(pmWG>W`)Fn_QSpik-e|BKb?V0z_+5RqW$pLt}DE~ z`H5ek_?^$LM5+!cu(Tg{#m7JhnB@ZF>z%gs8cbSMkw4!FbGm`t7Cw5}mBIb{x)%Gk z?oaPnj;37OHK?}z#E|q|C(eFt^qXDoC;Skp?ON}uZSQrT{aX62S4RK7bHR}=_cjBB z&IO0N3Q^AVtskJ-Ker&U_b#_rW;zA>cgS+2ckBVR{dk}BjvYwkbqkKp=Mp}fr%(C~ zQC3jS)^7Nn^wuN2t%p&t_NUyafrBja0AYu}X8{=DU&+$jn&_(VrSIL~UQre4CEJJp z>h0~S@U}nghsV!>E)a*UNZ)($@JrJ7w)m>j_pTaP5%K=S?LGA1a|^tj%#|a0MnKf5 z7P4OWZ*9g+hIIeD9y;jcq@iMe?8KO$574W!R}Km5R(neu>49 zZGU=RPdhB>y4JfD<@BX@Y(WW7v@-W4GVj{1i{0tFexJTK=$`fGjlpg1@@*r(v8^cB zXBn{NRgq?@GN3B&7>ulK-{dyZZ<>mdZwvO>zV%pn#qJp>==J9=i_Bty-}q;5??S3) zZR{I9>*eeI(&GmAZdTTzKg;ND+vn-Kge&KP*wcw}Zd+%-29y?MwSAMmb5&YJRiujg z*Fis>-J9O=5Niky-?<{QJbhjn>6?ptWNY#80{&IO5{W3WU(-a|*=zpBVj>Cmmf ziasv-t`B2P;)WB#XKOMNFCleh@VR+jw>Y+-|^Tcp)*oPdhMh=%=c2W+99XZ_3!sr}GNbHsv zJBMAEpOC|Sh(r#g=lz9E3j@@@DRJzp-rmG5KtJcsIqSzjd;kQ}6PW%J(koYCgwC3` z#z>5CC{OR0E_FtV$@qvAdY*0lZl)I;p^?aF*%}MAA6Y@J6I9nbBhiAGWjO9i@0be5 zp)kN+F0l5%2h6vCS#{~MNDYObkV1R9q4&$*QhR#GA21zLI6rZ&L#1V7`=+S81bj1q zxpI|Kx#lz~e~J+XEdVwjnG2i8NEYGH^p1K#^sPutgey8n<|J|uCmpovIGWaiBQ$N} z5i9*B&SG?Q3(LO^L>k|USQ|eJuNBhxpujqfCqIqfLg8*Hlw$l_XnE53^BgKa#G8x6 z`c6`rpP21b*?1b2-x9*M@#7_nGJcuV968KN-uva5D)}r3(avq)&~{#mo+|BZ6IiF6 z_W)y8@*ky8ik<(&yPC9UwnOE38=I4&l9gNzj9tlFPowfB3=ob=o-bLHo!bQwmAnX+ zu#)G}BD)u~9{UfKd@qPJemi1qd^09$Y5WF(bsA58yORH+ghX#x>dw{{nH=^Vc^%i3-AK?%UekI$iP=fbUZD`f1EI zPN7}5U2p_aomn{vw^X~eyR#6}JKtQTkWUHKP-w1X>WyKrSz}fuZa&)E+cU`KJpzz3 z%(i%+e;?}TJk}NSZatR1^)WRo<|keOgDiV`#~n8IaT}{G`WY~1vZ%tl^)+o#6ntzr zH?nX)rqHf$ON)FtiK$X8E%K&!@ZC;YG+PPnrck3~>W$%rlombl1uQac-g7{f7CmzO zPzU8_B>G7ay8>n;jzj27#a-dk#hsDp1|N$nD&PwWIf~m37`{Pq7oh^?Vt$s{btxfH z0Z&RMzFN#+aR&nK$*_5&0FxE4y{k?5{}+5g5k)VP;wXB0$L%(DmW`GEpOF{?%oz=` z{XZk|>E{mrpGP5wLB9l183g}Nw6oizgoOY9E}44UywaeH0QdZhZwR8mKLVKae`GH@ zaO6Aa9+A87vn&ym(mfRrzGA-@;Wrt8IaM zfYUYE(}t2np0I6uOxRW}we)<1xmmsA%}6v!q-`7CCxAJVZK7=o+h!zkz-PD36%=w* zD&W0h7_gsa-leEScbl_(6%OurYd+DFEh7t`Vb*s`kF%vqzaZ z6mq!c#ZR?s3cae@ALT?Us{IuZDJhz|OBTQ$YRW;Bu4%Ug9tKXgcTw%zBahkU|4EpC zwal=`10mGA=;PY{k2v#)N(oCY+x{7e3LB$)c>yqIvVR48xoR4H!I!k3LQeZX(e^LW z_J{0JyaHm;A_xZULa`gFE{;rL;`}`ix4x;j31JUmDX|e!zpI;85 zG``ao_yusO{o;PxHreN+GQXaIkg@yx28pz7n~{hBb0*uIeSQk~SUqAss-}>mV!t@7 zZL|A4ry|+Bj)6!?(dWm>0@!_iJ&4k_Rkpw%fJ?RQ`?hTf(dTcK`Sm=EaTT_8e$jI! zQjxVb+oyu9L9tB)vGdEGKVrN&g{N5Y{1im2Idv`IvXrN59^Zxf@8PaV=c`<|s_iPq z8qrr=xHxBE+fcWpREqwg4 z&R4J8zYq2Z8{>V&ILrKGDgP}r@ntlQoL!y6?2p06GKiPC zKTycg8pBepc$rhf?3MO=XGBSgVfJR6W2P+AQna;j4GRpX#d@ z1;**C6TpbJkM@|D1bwIYN_yfwsmtz{`H5G7ImHv|MZJRWi5E!89V!RWFMmfNhbJzR zYQ>AX@I+j^m*yu{D=E<@zXQM?Y!V6Kg-dNOECN;c#zP%km)(tShUNBT+gX!=)|2=I zlBr8CSg>7HAr^NMdC(%(bt)1Ik;LkiT5*XFUmPC{Q>qe6fzRt^J3=ek-g)HE(Nj9> z?Z^TyKlGwlgO+Pd2^5Bl)z4!0HSyj@7!YnP#XI@7F8+>t;i(g@ykoQ8h^2SFxxM$c zUlF-**1ubhqD_mnVc1h;FL-|gI;7yXb|L=MQ(w8Z?%%eyx3zy_HuFKzI*%zmou4m7 zJ$!!X1NQIoyEfzBQTy@UUo(#T+7G$0UTy)=dUWMw`}dJY7j*=(DNpQ#F`}1>y;<(h zRMQkZT(X^tunK|u!ebHUAS^_>)F zDq9W@2$_?z6Sh=`uzTrm!lq}7AA5rh;TqVsGH*&n!K9L+Ns9`W&8FLonAk?GZ>oc% zz!(W(E42XyJBhcbZ0Q2ij1(ICgR=x8q~%@K{H9{$F5a$St0OfnBuq?1Zy zW1+X-f+lRxvvTNFKX#w>8E0WDZ4^Q_!Z-vcjcL+f!g7Be;u#2Y5DE|$B3z8H451R?QiKpfJwg*g4B;At zjR;#2IuUL`_%6ba5q^&FIKqB}*AY0E>cEX(rq9O@hGcnjx z5NA=ifm6H%`}Bwpw`2bvvF{shvHf=d{8-@^`~5s`#D{OTx4}19C^c?G;5@@~N|>6A zY0%5`NW?tPG7N!daOfn=pV5DA#Xc=!s5S6MHQEgr$^X#<`Cba>m}!0Hi0O}!_X&i4 zI0rKvVLZZYgo_bYA*?~zfbcDZ|3MTLd4{bO--Yn(qnFg1|x zpE_kq{*>ucruxU0V=O8T#r>r`?i;~S|1BB%U?g0}wxLg+-Fc(i zdyYOl2JBxEB>e_7opES};yBE*Dua7;;;hS4XhX}YFz#aFs*kTX-d8Y&rq-B|hL#0t zE6yRs48H_0u2jVEzhf^J~fdK;KE=xe4c zfu85~xE=t~^t##+xje4-CGsI6`?yvEa-~@~Eo8XUTsH#prn%l>H8^@k31&;It2%e=tzvnQD zB7(8OLJ$mr3WkvMW7P2+mdDO3zTLDp19^+>=YN(EN^IzRK{T(xxEYm(|pd zqoU`bKD2Klg3t4-{zc6CQE9O}oauQ?8tm?W8Ff7_YtUThMT)(ZBKCHQ*gJVz?0SgR3$gd4*!wAB2esHb@K_&=kYdu(4<(dl^mzMgvuoaC z`20>dT{OtEje+k0y9`;KWBtPPh&ueGsWS73O8k`qU7p3P;YUptjzd!`s=|6qQGFPs z0Pa-aK+j6(wRR~m!_z_8-3rX~93%B{1!j2`Qg)95hj?D0OMk7vVV(iZ=r;-+?zxAJ z=?MjnGzR__s{aXzY1UK9g5N5lAuZUaK$qttDt=mlZjW%^GYae@?R!?)=au$7XUcYz zA?C?jtq-$z&6^vVUqB13v+KJHim_ZKe60 zRLy_2HH*rBH?{KrX3B=HD*rtN4v@}yUx5Rqa}FvnT{`Cj1!hR+e5k-d(m5X~FjG1w zp}@hi%6b%-B`x|`foB;5A3;-j2S!h^K2ZjJnrhIIRD(WCHRy9w_9bP|7YfXf1|3yk zmNe)e3LGX4I;Oyp(x5LD=$8f^S744b=qm+|l@^>(;6!8Kb{vjbh#t1m`nR&6H`M~e zrFy??&87m~o;~bSE(NOAY`J7_Rjt|WlB0ua&1ni8ChhB^K)DJ}DU zG=@HzkAcUNIRn4m%&S1{oB1*NK)=i^NcGSB3_QNfSHM3YlgID}X3hXldS(vN8JQEo zIVf``%95G+KKjbw%)$80%KRoUXBqAwl%}=3j&cp&g>v0%np3kzLX|m@<82nd-7qJ; z$0YlT8Q^G@)erj3$?Vu!7n5NMJ7U%zisi8h8)pr`f4zz3g;_DMo71j0Q?Ar{ z0jE)^bq1$KsdXl&JE?URr!c8?HYXxu$Wmy{Fz59DoSBYeQ{!CCQQsUdUtiq)cYxEJ zN={d{xBpmjURWt0AFAwX*qG*~rS7XJHircrT91FSW)3UjdDAtld7WWCN7UIfV7Iw{ zJP2p^r`i`pvJZ9sP9!<1m@kSX$7b`TVia@u)6h9$3lxm(!jCch0O9)x|4P7rCH#Al z{Fup4L^2bh-r(JWo*J#ngATNjFKpHRLn=w9=eSzilz;I7!?rh-q%Nv@|zcMw**V zEzQlQk>+MwOLMbrq`BGH(%ftuqxqLF3={b8XcZQn&E@CC0Gbuq8yQt)--yU{OLO+E zB5oM@BgEPFk+veI13rkNG+#nE4J+58_Oe-f<}xGa$Dq5j3W z%qCUTY|g7d(|V#VCtB2UZUSgN+0!Y$BD+9p$u&`Yu5o0S0pYr(Ci^l;I6nsjy||wJ zsVHvt*Nn=u`8|VKWsKpv`!a;=p+v06&X%Ia*zx$cYwn=;uM>@jhwQVk=x#0_HJM#_ zqe)+n8buD3ErK9mk9wHRf0In#%HFYArr(uS#vV5g4f7lFPsuIKId7w^%(D^s zyn-;|A^_PXEJa23az<6-XF_UZ2x)w8H6UH`lQ(bM>G`=JrTBAm!G8_l3Fot9-xv9! z3UZeNza98d0)M~2PmCgu2cYy*D&7JhyGxV+>!4e~f3MB|K$8D!@Xo*K6#gHAzuV6D zK|9}Pf&Zhe=P{}0d&uQp=r~M0_W?kLpMvp-&A2nkcpOY~Z%#6LGBC!YYNUm`?3`u* z?+0F5_=HqI3%5h~02QQKSO@-@Hvf}J{&&GUX8S4po4~)!&UdfQH|Hn7)1Yk-d68l? zXnX{m*^eTH)@S5A1s)cK^ntgWq&nvVrGb=XqzVn2Q#4qRrUue)I$6W{!$2AOJ(!80 za~(5u2M`DvVN9X|^JzAWh$-5&Ddj z{5M6;lZZ!%*v4G4CE`&coc=4dy5PP9VQGNzxy88UAQIq(@Bz9;f=Ez;&V%b0!w z+-Ys)l7pWjO)o=v%ycEg@0sv|Yi9Jx#IH4Q#K7EvgE9ZOatv!=|Nj4lq=lqcCf!4T zo!$S!{v~vNy3t=G`V2PB!Q>_dud)LqQ(DR3nRv(Wk_{d>-=I)GtpTsLiVr%-=fk(+ zgUK>rG&mLFc$X~+g(#$aSK2MuwM8-hCX%(1|Zgz(^@#F7_4Rx&HRy$Xsd z><}u|!b1Z9=K7?GV|;!{4oida+TydPIm{p4AIx~YOEdBijhrT;QHle$4<-Y6c~PnZ;-#^9BzxGtGJM!+BKbLL!?gnaCMsGhnh|w#(7R&S!!`hB2l;>W+!A z4*kegs~nds2=$HcZ&Ja8WLb%wILefTFpNn~HDi3Tm;rc#buX-$tN>B}Q_ez3#-Jwh z(nK9kRf<8J>thY&ClSnPS~6ovpPr=8^^wB}qMNyleMp(+*8}_ES6n1up8osQ`ljlJ z)|#;Vy7hFx^-+)QccWidp!jl@%pn>Pww{KZ~30t{X^>; z*MI8wPO?szt~}G~xPF&+h4nSlyS^NS+@v4W4Iz zXnnd-XxJvGp9$(sifU{4dgC+JC8MBYILv_fY8eUWTfwY+d1+Z+gFQVg|Y9nNIi$SHUS9n@tDH1J-(1Bn7_0)nS^}fVEbe_r9d6 z2dq|C{prwyT%G?}-yRbgz)h}Vv-XRtijUUrFQ5I=W^3SmE3CDftiIlDTdcmfZ>~XV z?W7I6ueJK`s+u|beb=}_R_1bR?JjHJT5FwGnSG>k_5|z7hSP2;G7Y z+bfSI77RrDn?E3JVSCy)-8WbZ0vVSFe9HqFu3rWGuGIl|O~8L$V4iDnz`q8_tAIQj zz#Ab3bD_rIUy|MU+i;SeC18FrV3Z-?WMY8RfQz+8kw0 z%JD_f`j2wUgPS6qHQ*%&p><(# zXF+n`b$+O(qII=nBNp50SF2M=!FYXR80S(P)Z+SLTs=|S5UPt!O4`Pw#jX6ZIf{Z3TL*O>Lyo* z3LENi=Z2i2V}R9MZSVS1dt<2O>Trw0ErmFIB(%Yc`LdR)<1GzAQL8C@YE#}o4He3h zYfGc?`r7plrgEP7;vuJCGtLu4Yeb`|iMFoBH6FbGqeb2|;_$w>iG+l3Q){E*iN#|< zDI^;mz=mj3U9wRN=OmS!a$Z1O?_uyLml3RPZNZf#4Xuq$j&kx)BMvqs&oWep^pORj zwo<%(=td{%Puy36l8DAzUQ$sQY=*6Zd%0|YnnRuvlcZEdYz(Y)%}JXTz~f;szqJ{T z3yKPm?N@jO=k-)2iPIC6s-sekV6AP4hT;YfShe!95?Cj)kzntqxUR-P$bM zbIJuU@o&loie1i?rD%I;^25Q9~W_25yqE5)1 zrr~L$k`0hUElMkgxwcRXd%hmAbdJ2HCfFFo>%2a13FCR!%~Vk;%ok!-Kquj7FNgQ! zF&|dFJpB`F)l$)|;OrwCZE9)6=^|m$LN-o}EXk^69f_uak|euw zYJHYOt7DO5yA=H&E!)`*3PTOmhByU`8BCsE5{*BZ5r?CKY95m`#BDyOaWTeSBiMvX ziEvJO>7vt|GHQwnrD8s~V47|z?D8`Wc%=I3WFf`SU|0N^ESry!UCeZ-FFDq;!tA~* z#)PEfE|}(UTtz*mAmqYR!%_k_Hlax3o+Mt6BxXOy=#g}Vo}*-Ub-J;rJ{H3GYy{WE z8K+FXoR~suFfp-s9G2xsBP;y0v#jVn+UH70P9>k??S-dbcnakhf_`{7;8FzQxPAF^ zq@P02&j2jqWFoA?YZ^h4Yu2)v|&H2sXjiq=GgTd*=hntmPvj{8A_@Os(J zS4{Q1gX5+jB4BCC;OW5x(jOz(Paw9w5lzDgp#RA@qEi@*!!6B507w7Bfr9xZlj5N| zBM=Fp>jrR$7=kj!7#OH)Y7L;5ilc*0AP8DZxPkmK(cBO>0(hsvFK-Jo0^$a=Kuc8g z;XoMIg0=7>vw(!vVPzvwjT474U}dzXnt4VcjvKr{#M>KQjzA8tZmkQ3#L01T3$VF$ zHD?4H=J{n!3$0#_6V@8yMPxdyiy>6NB^s}E+YbDHC3wdm8$3IOxR((CR=?~=cZO_W zP~n14+@A=7-{wLMs&pP=G*rRI^Tx&Cdm6B&GiI29fJP$tcn;Zz0=x@e$|4dxY|wKN z&@lxc&n+JY!1K!b(aA+X;O}shcJRD$Hx$i5TIWkVLnQ*wC^OA-%DfB1kF+yifZ|&l z?41f3o^u|_UISR`V~hrEG$3d`o{OFaz8e)E?bCd%NVBXUi*wn(L4qeA^`rGR1EKj2 zK;HrI`L&Utk&oe82j5YgWj>0^xz3?b>)Ybs$J|U~-`@Z61-SMtY-rIRE8Pwxf@g0ci6Y`0BJ9FioojPmk zqn|0ue96Zzs5YN3@c_l6^Zhv>&37aCZaf3uP6r?F726Izt%C}6`qvIVo`=5;d~8Gd zAs<6G0?Wzr@%D@a_%26UKcw+_27%?%5Bazms2`nt83>&w%|8}_3H4~&+kkbNqCmVB>{onKlzh%YL3f_ik4lc%FN#y6K4#w<+)O(z*U ziet=NVHj7TFP#TG#~zlC;}H9}Q{Rp&QQx|ef~NH$dCJ3al}Jl*ar~69%$eh)gk=sK z4<#(U!*Nf-GDH16g(Z599LFRb1;@kjO2Q%%-)l&iR><*3!g4C+I3r<>Sn`fv3Ck&5 z-(_lvn_}hoAnEc|f#ZOL<*Nq!yM*bv>$n5kz1*8Vr~_0M9! z+J7ci_ErMseT#@7pCQI1aoM7PhZqsWa~%0!5BM4f?05-9U-+!|TcCG1%JV(I`Wq^i z=Wf8x^86C8vpi1%F2($UNZ5OT3H4C_yGV0em@VJL-L+pL=7z9_2cn~KKUl-LfVmy4 zVSWq6-Czy#ZeDH%Yj_=C?geZ3yMVbBtl?h(=1#DNUjodHU=4o=nESvQHqnLk`e-KV zkN2N)E0-;Q5d1wFu>Nj|clUC4*Lhd+Ou(FPsgHN{&IPR3nW=9vVBMcGT%Yw`4w&1% zT7MH@_6J0eA7WgK#8LETwnX->n*bjH9mAxce-H3M)gA`9GK{+c^RCY#!S6F31?M^Ne=+70n9m;@CAV5fEyKD1o$XmfI@yT;10ms6ukth3QBHkdokYj-UPFW9nrWlDB`!Wi#wWqSU}0rhd9bvkq7t`H z2ZKc^(1PON!t#P83xe|(EGk(l7%ncfxtnokGXAGEH-zKingG`-YofurhUjWs2QB{9 z1w*aZ;Cg2KQw!F#Ha4!;xh+^)l*~*6G9wK!8=X#S6@M9pR(rJ^Hk@vm2wM}ULu2*m zG-!JfGIdRBiQK$6jlh|92=oHr85C;rnZ)&a^J#MYPx}&b?d>!|dg)4j(kB?iGV95U zOZFA!Sa%iof@66O|Gk5?&A~`p%J+sralJYIj`IR=u7j!7EwKj4)mkn+Ic^Py!jo?& z7fVyTNF2>277b!`tcI&u@>=ieSgX819fHYQ#?@Wt^X4V*>IR+5p5h+#n3EuHVV8G@ zJMqaK1B(8KJIg^9tIdcV*R3~%NY~~ zI6R!P@4+P$RTS}UXl;t9yn3G7B+S^MBU$yQ?7avzSTKgU$(!Gki^_D?f4)mz-@Ja> w8}E5HdrA@6W}sbuqMBl>avoM$#deUq?Okt71RaK-{ucdIqjgQxIq%m0zt%gE?f?J) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1e30de6e69cc955f0a3862fe8ced011b3aed21ff GIT binary patch literal 20860 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..909bf0491efde689083d9d7cc14bf24183a0a5e9 GIT binary patch literal 21806 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 +#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 0000000000000000000000000000000000000000..6a1cd5bc6abed853a2fbbef52a250b67cf3fe151 GIT binary patch literal 106274 zcmd4430#!b8b5yCnRl2OW?)7|BNZG)6ckZ#$puXWR1|dt#VtbwbT9;Camh3bDTy%4 z7A@O1@4e-ZJOG4TZXW*GPBb7f4}E_XP80j-uwCg|G&?VI?J=1=XuU^ zp0m9(?|5BC)@VghgzwKFjDpaUcdC{FC^xOtXaj^xSVWwN72QM#@T3h`1s){X0Bk1U zR3sy5fCpR;I#N5pOr%j7ll)^_r0$tQFtNRGYV?tSM;=uzOx+=HHyTqf=>`-_$02>B zTA0oPW+I(Bn8rfMSmZDE@1+n9+{Wn0?G#D1kAO+vCDp=&{ZRzQ7_LXpTz-wsD|An|Vu zQb(k8q*x^Say(Kz68|pK3Ym}4>m5+GM;eBdiZm7}@b6Lp6OlS2B_YxF(MUaz_?L(j zBWpsKQ4ZDXEV7U?beR3(Ur!_qi5Nlvf8De~T!Qj)qzOn}bsURc0d-#$L-zm4AHwB= zKaO4NKaNcoq)s})hB8X8+fgze_~(*2VpK0#gzNPzm9TFCz0P?N?T%f~EC`$ly9NE4<Y+Vw;8O3)9$-@f=h3;0R!H-Jw5U(jwRV8#*Q*CC&OK7YOr`Wv9{(fcz7 z<#E8vz(f8Tq?3SO)9GA4H|_MrN0``$_UAz7I!OLYpx=%?^je+22L5%zpRu6PpKk!} z40?{vAKt1zT$gS+)-pe>peKO;d7XbQ$`rug>2Oyra38n$;~~h;0)3rM9|}9J0K7oA zr!(j!fUnW%|A7710^YB~Ucmd%Kd>R*!o@Q9>r?ob`vm9jr=Z^g{sB6_Z*2o8Tu5(2 z&nfVuknz6|f;NGldlmhYh4xukAMBFeR%GhqL74H)dFcCdv4TCfARn8p$V;CwF?B*_ zn#e0IEv^)K^H7MqFF^2)qCicKq9NVx_1c?D&ql|@S{^9ss~@+*rZrlcspJg>l$Us_sJ zA`y}lh*?xrT9{X~xTv&}bja0OE~=_1Dkr9>q-Y_C5MBkA!nyPEq>jSkg#gO)DvGa> zn)1ub^Oxn#FVdtG&o9j{$y-uf=|TTWit_TI1ILY`(lXg-aehfvk-zD@;-Zqmyvk*Z zq#$WevFuN2{=xv+i>jpTCFRAH5Li%9R8b*&q<6ZosG_pGY#BXLQCV2fyEjiZ14Z7< zoXV1l%$&-kB)FkGud+P9xUwR%AR{9ixc+nVD~b#9%FwhZe_>|9l)Q|bfqCxn1!y-h zr>vwRqeRv!hZHR>C|XomTvnQ%oUvHqDh6P179}O+<-jB53yaa?3=B`fQVc|XNl9K+ zY0=U}MFo{bgS8j04~ePD_Xb+ z)|VG8LXel0=g%+F^j8ipF005x925=6#Dq-K90++s^71M@QZMei<1_ z{R)dD4cH9#V!DVO5A=mUI$L$i?~9B1sUKrT+B+AkO{p&uB*v#N&p!d6HK%d_BBms> zfZG4_w8Dbc4-;z;Rx~!nO41Vf{>c#>JnY)|R(%_=#FJks!<*4};do1TR1C^r#3dvH zL*;W&uxOv#u~2Z~OdP}|V}3zFUIn2fkvFw;NipU_c2NZ;DTxId+rpxS1&fvmtP~s{ z-D4PvrC7!*7L^y5R?ZW7nUfG+g>WL)PxK9|-dsqQtM$VC;!-iMyr@Xz6&2=J=7TyH zQ&c6mh!rmlhwb^(!qnon6$LK{)zp>n5@iEX?e*3 zwWP%U8rJOwN3uwpK0S3*W?m9+ciKJ9C~tR0+;6#Aac5(RjUIJX$C5{*lg1iv)LLDG z6+Z?HI)~QA1PI$A{uGp&Bt_PVp&lddxa1`lrLs<%BJKl3TUwFFks>NH4kHD>&FpVL3sFIXJ(C;DE?`i3uIyeZxc@I5(JJ5$_8o^n~{U z6D;O=-h{&g&($UzOnKfj;quCJiD`^>-Z0@3x&h}VGwk2Q5^j5tB`kiFrCEs0EYYJU zS>nL4m1VdP+gRe#-M|uukew`X5!lTV2g`jdap5??5*OPySmL60h$Rk$@3F*X@(4>@ ztdFzAh4NFDxEOxH(j|nKWmlXhS$2cnEMtT?&9Xb5Ww4CJd6Fd$l<bi3@|1B@VI?Ec*x%$ube=OP07Wx>zELyR*baG>#<>YzZuJ$?V4x2eSbzhv3}L z5*Pjymbg%jWQj|BI?IuGmcVip&TlN!5N|BgF|I5#5C<&B;=IXnybx1szpOrK@#bTQ zy*rsTqZ0@Bispgq(8K1~JCLK7qB)i$=V7(+(`Nj|as)l3H6D^MN7BQvY}_qjj;M!M z?Z&MV=E!y{W~6u)5f&0=#CVnxKJ^C>v5XwgD#D*i zm=WYzL->${8A+aXgm+7r5#`xHc&mgNS)K<8KPX{Fm}fKLbrNQzdA1T>C1FOKr-ASy z2{ZCMy9v*cFeA`&fUsM_j6}~N!XqWji1ZvG+)u)cOwXr;T@q%5dc1_45@w`&P7xLo zX2g2V5XUX4kzWaYTg`MMmn z=J0+a&8qVI;iNsMxqSjq)0-<)Z$7#+V}F{oxw*pPy$#_pbAOsNzPZBgy&f=}QoY=1 z*`Fr+QW?@5b_8zQpCfdaziP?#HbZmuK3DbGevZ{m*o0WQ^-F}o*sAbm6Fpg9 zbb8j6c?b749DI?%U0=1ierkhbHJ?<~?mw5b`*|+eKW_Quo%-t!fTQ}PVcr6f?e>t} zYr(l@R$gsL{fZ5_)4biFtZv0-j9C4o(=V{yDaXrs=W33voW{Vnc-#Ei+`L~b!t}12 zu&I9X>Ds;ZOVzqjmY2O<&}jAUDp!5#xt9<};*`>__CT%K?cvip505@M-k;EV@&b{Y z?Y$IU=4AP}ZnBCwUA-qPd$RWq*;QxL9SyXzF$e0a&(3kI{tDu%&z3uCIs()16=>+? z>;tlwyD!qqFTvsOCE2~*aEh5Zqt=*HKl@0IHw((^W*=ZL$DiH>ne65Ab2ZH?=V-m` zeU`mEbb`G+uJv+JU@zgUsvhX*F0G@3(b16o#*vs5xgI`z^zg)(<5kgJ_X063+uI8@ zcK1l#)v~*S1`=8XEOo3mk5 zZN}zl-csnQ%aCI@)<1^RIfi=gCj8RUJADj;dWWt}KG&di&Ct>{cVO4z;URRbe!}L- z+1@Mw?A`l;z1y#MxvqgmH;zW{;M_V}PV;2DTsnH7Rp3<4MT5M6sXST}@aI#i@eNG6 z+P!rd2kOV3en}t3MdxbHth7Q?j&~{@5fEMjb&Eb9RcZ2WI3w+Gdmlih>C~v&y;ZNF zmD_tO;M%=SFG>;J;41d)5TN=AM{4)(&p5(ip6p!%O4HMrE0_V3{Mwg5`*k-2D&37< zHr0-*S}TPoQ&}~XIcjET4sm;CoL63gjq9jm@X-pAgr09{ZQH;$3cJ?DhttRWNCK>nCiOT)WqM zJ9IT30FT@k{cCiUN$!ehSl2G6Ia?dgT=42EGsf5Ay^&$2t;~S!-o=_P|9m`~{;utb zSRoTU8}Gh)_=yaQr_DEgu0JvRRNZW+_qo%}%_mH2c3}$aa~}P!_RNWlQzsff`E=D6 z8LQ5XbgX^{6Q<^vWA$?=PaKh(iKB*x&ZONnyN`ZXbuIZn`{dITN6+lbs9JFJAOTiq zoj6i`(s?4H`-#~{ypy2i=yy6R6oKJH#*wQ2#~aaL_Nn7x>tMhM(+1>z36<6pM~@zC zEP?w@96_Z(^W%x5O^1*kIdP=vffGmYUk3VtlMEeBRfSLm4G`Zr3oX{wwdZnPbJ$Un zEhVAd8*JC~s@yxcb?~%+eFNk5lWbf4M@VG^(H1x66@=9notN+}`WDn{)0_~*P2G-IsM(!2(^11uvFdVg#B!{0pz7Ea#=C9( z)#r!&=(v?1nbZ#$SD!xUtYiBsUUuCa_e2@5&YIn|X^_0cFJU!50>D~NVGpC~6SnO9 zkl!4)a-8Z1+zer-9NX7W*klIHXPS#B%c+o*X;Hl{js@u&CAt4DAk`S@XG3gRUPQ1{yELLj2_>XS$oEFD-?SSSfV^( z$lP9s=Gdk~wPzX-_|1(Ak>UJD*Vw&J!yf%0({xm$^F~yAMtW&!eqg7r%hA1)3zHq& z8`vrDR*0V|nP=3GZC)DZ*fmi0VD$MRd#>wPua?)R3%GL~pCvf{`C7d?r15diKdGzf zE4_Sbojz1Aqn{f%a{HW(Bgm&6t{P)d2Md;uydM}SEz3t4T4?jE`ea1iR7+iGwiY=D z_u}l!$l+PpSJq9i)J?Y69Bz0D&Z+EBm+GuO8?vNrbN8ADF#)hv)Q?iv@%$+nntrYy zWobHv_F6kkyu+0a*~n07MaxPxY4^dsj$NbFrn7oX%X21Az*QLF-53mQ;;w!K)ui1+ zUUgL8AdymWS18^I#qjrd%RI-wri44PKT*3^?^AtouXTFCDPO1g49D%g22S7@Sw2ZG z4{v%GcB6hY{TX}Hey#4{+S#X%Ev!PMEZ|T*>=L2=g@XSOWUSBT4#U%&evIbrE#h5 z1>xNz-Q{eI^ZVtHpH=#W4%7TnS7oWoWgN_QR)sgjAl&qB!k%2*LmaDzWA-B=_Bw9L z;G&Z2to|*ep+6Xxyx-jY&eQNw-9$KOip^0s+Fo5`S8yj>wZbkcO{)frs!QD2o^(pR zvU84ioHkyyN1E;ddMwaQU)S63t-I1vpZ>bNsfL1fO_>qi^c-wDntqQR5*@qTYV~eQ z^>1xfns&hF&DFnsze2A;nbb5Knkom^99|lYh0cw1qI36b&m^DPccnXAO*iS(Up|ub zY1IF5?HRNS$E1|FJmPR7iO_ip3Gbj?+hJGTM3^%}9rBT*J{FeLch0UKa8G^u)gg`h zG4a7wH)W%}@n@}mBK@edahN^=!kZ}L#o73*e~C!>HgNdd-Ykp}VpZN%uwP(NF(pM(CY6!_D=0+(mY_$t@jgZfe!Bsz`V}H^(~~A|TYawPEgh_#wXL z-!kdn{+b<4Z#7&Cm{-Lk^|RMFR&T%=p>~h&s#yD>c3E5w9__M`(qJg(qP}ZAOFN(obk9#N^;EnC%@vF<3@MtGcsqg#bAK zzkMSIpl(6~b#$&#u}oL!W?`y7B_qCuq_jNFtH?{id@H#GQ4`{9kzKV9#17mIA^}(be8|R{7ApTo)objJb ze87G7eI0KAeqEr-1Wje*W~?|#yBimyAo)33gl7R^IyqXy=VW_-M~|uDH9XB}U>G)5 zqPA{wa}@*qD2`Ak?sHT73QOG>ZLsS!#B)=Ct};W*-Fza$$@hm>6LupI4FbDF%fo{m z@O$e=B+dS)$0HK5d%wg-IJjc3ID+Q~)4a{Ft*+_-%xg8gD`#rM_1)Jk!*#KT2Y5sh ze7(*Md_-~x*2;_Z28`8Dn(0l)0^MJ#58!PLG*v#}|BFko8{C1d`{}LaC3rndl|H%$ zmB35z&4B%v;3(aSLtveTOW_K@O)o$vR*#9NcfsL&ezFqJPp-QqP}M8&RMUDis-JLx zm&wPd4VUTtK68Cdm$K>8fuVFWo4zZXHn8b}z@`nDC#{+W-0~;+n&zTivfl2si(b^X z1zLNFY=@ijSK7Yl_SSrbUE&BJJ{(vh{ZvjAmpAaeGm#!(`V#)9+G&cWU5;c(LCVAPjS1&d_)&!&hT(4cYE0% zSXkX`a;$y|vyqisSEEwXT)1<1P$T!wXQyt#(cQ>ou4Xv}5N?_7GPu$!_nSe{(;qzmLp0^)GTkpXNP5HIe~7Xtk#~*6!q9C+%*vemd6H zl9aKgRTJN1%ey}JudDS}Xnnx5x4+&ACkHOARWMd=OSBfN>z#mMA$G!m<-@6ue_bH* zcYQqb&@gbB`nQfxpgD_0uJ3{9FimpfzcBuCG3Qg#9y*erP&RPD8$(f(@nX~DU_#!x z$MLT{jV0Wwd~u^4(fB>q3y)|Q#?4@SPoO1kvf;D8IHD#0_eZoNpHb2UN3O2f?Ee3WBie7D_+!)evB>`rn8!cW z1C#Ryk#Y{O7MKC2t0m|QkwHhaxf=3_L}CLrrR25>VG<; zJxOx|)}UMT*7A(@{R!PiXHW?|qkRR~e@0t|4lz1>XSAb$|C=*fSEvd+qba(a|Kg1H z6*?YxMthZp`i^&heMb9-uPM)HYxQ>j#Tl(aw!<;We@6T4IL<@CXSBD7;wA?^{~7Ih zA}>6nJq&d48EqXIFFd0yBf9k&t&qqI&u9~f<{7OqpVnY;jBW#A_{@;Sxl@F1H(-AH^G?Hwnd6K#T}yZ~?m_aF4N zgY0a`ymF@JalEDE_PPP!Bl`tP?W@zgIr8!t>CW{I(RltYc@hBBzFe2_pp;n|Hm`1x zVP5qgM#uIK7Mph9hQW`0ng`rD2j~CE-an4Y^Z(Pb=??$`n%d{p^+U_{Y-vb3tTkID zo9zeZj4S1R!0mnQh#a))6&7P9KiHY(ZIHdxKG;dxjfab93$yv3f=7nayszVPkLrD$ z{ccFZ(~Y_epBr9+k*(a23x$3+WCF;MZrD4`n}w@l^}a}VjyD--++{R3!~zKD3*0ar zZulK;c=sZ1_yt{M#lZ<2M#HvL=<#^A+c$h=EoJRaO(K2&}8#GS?Mv7(2RcyS} ziz{HV6pl)~uhLI5$-5MY#!^jZ?cUlCn(pEn=*=e|^)*1BHqRN`2OAG)Fm(Io_BZEb z=d`)4J&X2Yl=ua)+wr9p@0A~*L+dwSy{NBa9{<|QzZsIw4EdnKQTyS#<~yDxa`cct z%1uZEg~^;b7n*}V7?so=+`wP$oWwLea*Ib8K) z)0dEull{8EQA{#SSWKGJN zglw9~nUV@Z+N2z$2@}MW%n2EjrcM!Kr)6bM$ebe5QnS)TPFm_jk(D`dyvUg}dWsm8 znx2<4b(ENzK3b$_=J0JJk&}tW)RdQ+mX?u|qbc+OIa9JTCyo(0V<%-#5u>vvrA`qU z6Q@oPX=78fVFgI(lcrJ(JEFCpIx#(Cbmqj2bWw`ei^^SiJE>HA9j36jq6NTPAN;1g z{Jy(8cj!>OCUn(MS5@g%rDaP>UD~@Ut%!5;3tjvIy?kXzrzI8?w8+q<<87U(i$qFk zS!Ijz5gM$$KU6%oq-cbQEh%Jj#a4*e#9s6Ca=s5Ha=s|Q3nP^+8d?KGT>|eXRTh`d zcZmd-{?1edunB%k0r>mDcbNhK&|EZ=FE{1Gs1~B;4*d--7vG3-RhE^x@T$yw29RF} z3>xZk=SnGTQ&b2(SH8>l?pGi$wErjG{7SxlQb`%wOFJ&SBE{bEoi0~tRY{3!Q5oKN zz&m2{@7&*hI6Psl`||$s@2AT(?5CHndq}mcLAo2sSNFk@599qiq>)HjNCN2!BoC7M z2;MD4Nd({;GW;5%3tb)}PW7{7pw*8#+a_nVvfWnvX8T4(IXZj>WYZbDgz(vCHiU#7hl z<&!8+qkIqLRxIIsF*y=@UjoX0I1RZ`E<(8!gF&JxPCmJs;{tgx%<8pQ<~)AYMkOEF5Y<3;~)OO0Vvm?^c|n4jpB z^mL&2fd{We1oIe1Ch~ivE+p>d&b$Ab=N9l>0iIHnt#w_Y>)Hz18-Kym9dv#fIc0

1o~xjGLY&mI9!2b4iP>D9&@@N`GprJ(Ukqa#|&8C9*`XdIX5T#%^t1MNZZ z^?N|IgfTV%`~9!mI5*L<5++6~i59~o$nFdl#@k5DWxP%j+yuyi3H~`)2YR;V z*ZZ^>i~}Hd6ZqCVj^E$F^Kf;|t1aV`sg2L{L?^nx0Nu}0wGqPmC202^+KH#|n;W5M zckTX*wKGgjjDR?36e{YBCIrl5M$`A9;#;HXZW|4J!X_RxnNC{8R+H&tt9Zp^`pm9;WHLQx zSAH~^Zm}yjhnT*wDUXD()t(U3wovh22xa~lf=K+_OeLqxrW&hw+d?tNEfn*E#q@?% zdB6%||7kTnZ&hBgnqISt_pM~VC)D(YU3ov$^q^f?XEXg`Qx4cnZ`+iQY^G0a;#-^P zN1M38Zn_D-mtd#9f7nf1?P8DJwBIg1w409G#dmhoPjc?dw6VRi+H5)%p=>gn zzHcXn)bF4kA|AwZ6jW{ znf}&J9J84oZ6{9IO!e)Q8oTMUw#t2WQ$t(jIlF0HTjgy#Tfy*8onloOW!@hK2d)jL z#hb!SPdmi#;k0j!gZAC$Fr9TMuRCDJF^B0(hw_8NbjBgBcar^8r|E24<@z?JH`*#Y z+n63{tDJ6Q`n`>EV_Q>QTd}dN>HfB2OIy>kZN)2Xsqe$KrsHkJcWq5SwH4LvOl#VS zd)l#Y@3%8;ZLh44Fs*K{ycc0Q7NL9PxWQbE z;{e{%c?ao3q!UQ3c}V{f=^La|NIxU}hSZE?d=cvzQW#QOq>f14ka{8|ASEFULK=ZI z3TYhDB&1nLK0O}P%aE#&mLsh~szJIL={BT$ksd>O4(T6Adysr>`CcdYbMDtuuqR!C zM4}THS@Q7P`TUwbf7fCY-cBHrzsW)xvnteCUa6O(^wNbAzNLR@_^I@ON48T>|<08{GRm*W>+9l$+P!eNvPS)v5*Ci&y~wuaC<2Sn=AX z{KN7re^-Os5I-}CK*`^m;2QegI@Q8@I!e4|D%K(KcVO5D+`!BB-HLHRNg4PCT3HXk zsQyoXXy45Jh3hsO67~u3{T>hD+tJ5|k)A=?gLD|_bEF@U4DfYZq;5#bNTZOlk>(>JJQ2Q&miqVI*jx=(vL_6{7y+*q;5#bNTZOlk>(>JJQ2Q&miqVI*jx= z(vL_6JltrD)D0;aX%td6(p;o+q#KZKM|v2^*Eha+J=7J4pT>%J;qB_g{;uSH{rdMC z*e}@?myNsD*!)UY7JpAmqxKmvBpzRh`~Sbqz~uj3&;QMB=r^9T7~lE{M&1#&cf~xw zJdFj`_q#s6^N?gk#`BFqxbE<^<2k1jgI)jo7L!erOP-oZ0k>!0t#Ql6kY@ssD}Ag( z+yU+V*!dU5md*^|AB={?3n&CpD0#)W?M3h`C*toLZ7!zA%T6@$d^U zzGkC?sA*-Tg~j}V93Ocd$l4FZT;!)Z8g!!ndCjLf;;){q{OIGk_+uG$y{7P@BJ?LZ z9dw@m{9I_T#+l={utZ1v=cem*&c8BbhmYstefi8szSz?#{ZSKt%GBYwFVz}!t9!;^5Fb0e#v*FfrG+TvJZ8#4ySx}~Sd z5hH92a_!iS`ii8H(_Tr=z6O$*Q!M1-T0qU+nDYh+ydM=Q(k#;9a zX)b?Z<{APRJr?F0k&Ey+)~PZW?+C)-Y(5rz6Or0~i&l2g;oZJyVpNGXsxy&W9|ysx z7O_e{Mo<}@rnhja_oJIenPdFPMucGyd>s=uAOWzU_y57tPO#%?M0M# zjnalkwevx)?9<1wumz1^ds`OfNaP~?9O)DkjCTa#+9~SdU^B|xseXdGl?cPV zJ>Z1PUVt1!M>DE}IW&f0SRTTM<)f;`p+l+~(HYio?U5tYQ-t0EI1L|nSi&x`{jGx# zVUfR49HMU;R)nxiHAuA=f=~K?dOB`!1eCDbS(rB<7va~kPC>zVM-YzY(*UE)5gn*o zi7>EbSVvYvqY<=QyWr7oSTEbxw4pbr2`%~&T?k8fiXDqdu%7|5v`7gTf{mcLI}39! z8MapFy0Y_qeWXF$S8BC+7FGaM1($uT68AO=&zYEAjph?J~MdBm1gutg^i%@ zA1usIBNyR!vQ9z4ct;S9W()+QQRYr{JL*;no8}Dr&y>qZ z;!kIg3-V)FnB$R)@G#cdG8pd&!cp%|sh7D^?TbcMRzvSYtxZ4bP4ComB>bMKN50Ws zM#fO?bPhm?Nr&92{u8-Xt8!5Zea9}u z4Hyke*o00pIv>~EE{vUIFg&4ws?pA?t~9D{q!4Tdk}+hV~8cg6POZV^RLDDFwXr_N025R*LW~Jl1O6-d`GIo4Vc?hiNR%{m#JHD@jhS+&ncshQdgKDfBhIRZ<*)EwwV#gQ& z9#x(MXpDK>Xdi>d(hK>}PBrLl6$|rn8|7@k1iZ5cNL5qb%h*LWZ;w(&N}opm43=vT+GMZz|Lkzl~(?0X=k zohuOqYy*3uSeRpwi*PgRloO111mS4sW(YFM+^NQ+kyS$sK($BDnaE>eaR9Kr5%*IM z$c_6N59yV-%^hJy+%c@nM$xkZOq=mN3Dx#NTyI>ZMcmHeU=lqyfG{5+sM>qRdEsXf z_YXvz64!eWTkmWqTgN+vm=v?%i>>qE8`l zkyvreab3V^iJOi?f;CRX>Ju9G0o2&y2ID4Vk4r)8u(;{4EIjUUEMbl~%P90X?kluv z6E_g`wsCfJr(N7I7a}e0ax72nMZAi8X|sY)nd2==?8lHAZ&fD242IUvT>*wGobnii zSbSql#5;7*5<3*j@)+u@-`t=wSC)e55AgBXuw?_G?%if2j^8#O{m%*h!fIcNt>(#XgV#FiOdj z;DFdK*o7{7j|Rs+#}3BmU`lL%tUB?r$|2dok+COni#cjKDX< zOphIpB|JV=H{*)fB?Qxy9#YgC`*GZ*WU$|hIW!1J1U37km{S0V@YAGo%!2WbARPX^ zi>*-JcCM0-Hlau0v`YpcetJ!?%}0#%nm9>XDtgy~qV{qpOiBfIGQ%O^b~NmjEt?bo zUg*MiHc>*|cPz}Ok&DP{kgw;G7lghv5XEVZ5bc%I3%m%SOP)bVam^^v&V)&!m~F^K z~d)|VnS20QRYtdH`J{}I8~=aGk2O= z%c0v46KR_XO$pcCEuqe~%>w|f+AblNZ4VZdgw>jc?zTCY_zC*+q&V9g+M+*CO0eZ1 z1{3t>N&RegO3|Mg4X_PGNG05|OjVF^rln88C|?H>*35jno_)u-&&6F+0oGsL*;J)ueVU}b0PhIGu_p`&qdE(@^#K_MwA!d@^<4VxU5HH>ze1v`k@w+vaXM+SMNVO8+Y3w+ zAC^p;$;4q~wb#V@dl0d7lpGq`$2eyc|I-crOyh z!f>9hn%x!c5dX;gjPr?TX(&UVy)!cdqm1Qf_%ETz14Jl&dk^-xhy;&qGHqHwBN_XP z;nRexXcl9CG0KDzw}dQOge}w}%x=4c6R}hqC8zyO@W@dbhEdGDh(vf(RL|4XFKyQ-USo zDckiNjxD+9HM^IHZYKPb6M@~6Q)Vl<@%+oa4yEkE9U$)~poG22!u&3B5x$vqiVem) zf^hIDo6?NLZ_x_Y4F!xRU&x*6QRG%u`#z2pd;(4h3B3~&?gFBBU!wcX1UjL2l1-wL ziK5vww0D0Bg`t-W01sDSihwOovM@h`TttpXexJ6W2cRzvL_z-fM2H*kGlXBV3Joah zMG8^E_OdV^L@pxlM!rFl6@b1p5XCN0j8W!JwE<16L^#!tC7QWYeVb@jlP01I@lS&~w_>ui73Q3Fvuk&9K;$&`J)_Duiqi0jy~(%vT{7 z;hC&cY%tytgrl*?V2@GdInF8TPW5UuwG!R)Uex}HbC4Q;AZ`yX6C!Rk_>{Or_{9$MKNy#Ai}R=orVYF@olRB9F{D$F*Adk=n$jI9{1UYY%xGdGIQ}Z z6QyMzu(lCRwCp%Qw0jMO^=)M8rK6CUj4E3gRWce?o>KbIJ*}i(AjV6^vk5f9ssXlTHuo>|Zs=Ws@iD-!!PZO&+k%X;7T)M))TF4{du*u&q)6HEYlS z?UXAN_QnvCqMeWp3j1Y@*`~za%hR^1{3PK9#r_;BGQ!3q@O~wrgz-qMm`(Cv`zGu3 zNHE?Jgu~?j7JM8Wc?iS;zXO{S7I>na>Z9^%A0mV+o}V^^PQcX3NAH#B!O5KQ(Zgz>k68R;hROHz1tw(!?gSdf zko+nXL=XQOw-XJcCJ)20C3?j9I3LE6{5Jd$efds5#+ke|6_}CN^zreyk~?A5h)x}Z zy+@NXGI!QDL_p+Y4)L%WGB_RulEZwBo7+^%;c$f z9Huu*j%N3!(xNQ6C?1$;v;BRuB;SV@vJLSbJ){3{AI2rDFQA*D_hO{?flo1)Mla*2cSe8BHyuKB<$|tg&%*{N{kfK` z=gSluC7QXm%H*TQ2xY`M(=1%oeG_?{DT8`R2#IVxdZ6AlaRP`C0LjqJSWno^4T!hbM zT`~vZ9YHv%iUy-m=1z4r>Q*8`zd|-Q8Y0?H8At?{H^u%EDpKd8a6dVq^CS!NkH|&% z2dv9hL3l?Hjyh|>Xq35AJ&U@Ph|rmsR;zkIMEl%qe*IO;qPiAI?_)eNsy%K@x;p0X3*b$@5o$4;stwb0~LU0Q2%Dpvy zDwffraI^`@RR)j23Z)E*!=e>ZVK|31<|$@t;8o_rf4V;QA8_{e{ z7_!;Ge=k&%q1l@cZgV1$(#U--~^-0o$6ErUfwWkQ5j80SohSRori zl;t!fjGy`|BKlp@zC+lk_u%D;AeyU*wAWmq-T#*KkklhwMy_-QjK2EtA+*u;i|8)I zcf(MrkGI2awo&TQ+C+~e?O}wt`uC^cAI6H$?tJix=y_zg8^Nz`vBSp!jb&77p_y_M zxqip2Gu@~RNWlWgUTB{V$xwGaN(MWuQlEco7^=E&Bysp25~DEYCcH!g`-7;_ACuar z1DfvLmL6n*xLnze)1T>0BJ?&y!j`^huD&&p!NLXEwjD_IFq?24%m5?@ z^)(prEp1oAev)1I6}bqXO*&gjIv?r(uj>8 zgI^~OCsE#|#HcuO;NQ^}>bGstoMY6|JcGm&B>s5Dh!ZhHv=HZ#_%>og{dsCeD`L0h zBtDDSP=6ha$V4ltRHWTc!ZB!4aq~?LZly2YAx@22pUmW!nMk*D|C20Tl+&29K9(qiqnVjV@9SrYQQjWi!Xk%Oop=kQgkIr`5N8g z8e&w2y-4O5gs1v|PoRkUz=uSL>q_K|iTd78c-2Qp!$^xLK39}tOz#E~T5J?96&a&Lk;tgWajJ)4Z{oY5FZF%g~1PptgEZx}aNtjwPZhTwX zF_IR#K+0C!C^8wzx)WqyQ_lN2rYe6RI0HFosYvO<T0Z-CedL~ld^b?mThq0jckVI{cr|`=P%5^xxsLPeJSRR5}@}FVTh}S7S%&l5> zC-DueAL?qw3bDE9yss-MsU-3iqu!(}$7&Wtyplw2=IYJLzEQ!%;n$Ox6^al)r8Ltx zeSrMqEQa%QILV@}Q&R20EFxtmsV`9adS!RVR?jBcm@Sliz46{ew zsC1Egini{gII)>3|K@77N#VTLHdft*$t21R?;#~+j1W9==#J2)ZORls$79N8V|{y~ zwve)4VssTb^6^FJCzZ{J!3#u;=5{U_wkdD;chvw1DGg-c_e<0lmEBlo0s~VE?vMN& zyOnOZ3c}ZXGCt90BV_ z%GG|}T*H{8VBWQGi|)SlJ~nKBc?i`&kz^Ao{D@mKE8pP8GzjDf2%IBidl`eQ9#=La z*nOif`xgBxsXs9C&Lsd7C`0R7EVpGcsQ*^xA+lO?1a>XU&-PmoF+(q^O8RjcuNx4nA!yO{DbpbCel! zM_$OGogh-Ckxj*Ls7no(!K{nwGixrd_wkzI4KHE!|8w3zzrW;brCx8i3@g{gTLn1( z8-Ihl42Lll{=9)m=>)H7=HFvzkEP|}yaB^7#^2z6!#8Qc4P+$BOzZDbKkp+3C$3n5 z!drL)EPvGB;0eQTSe^g8fk=7Z$7`xJjQCUD0Lw$LrD|Q>Zs^x4*jlXF$eGJbq+I52 zu+#7zj+=jOjdsXO@%S6OY{+XBdo3FLHIcH>-(bICG0qbg*U)kp9kq)Mcq=zOxeB`! z?ZlDQ-%0I&l+r{lK1nofH0;2F60kLEpODA~%*1dj7H}mr47?>uV>Igec1cr2N)J*; zBqF-QjO(%GaIE|%fHV^SkqKgZV;^fP;v5oBVVO`Pjcc(X@)G55c?*f3Vlg!R&5)K6 zY@k0J^!4*uAFtZQ7>&I*urmxZo$Vt(Ta58mC78`GQlu!DcADnxhK<2T2mXt*K521e z!!IP@yDi2K;FUj7I>pCkdd-kFGMLS;R5x(Bk5^4HKGF40cty&?J~q?GhF5w7vjrG< zkZfD<#7P}&ygm4UB^MiA>CZkk(+`HP|AT>@2l+%^ZX6F+1oqkA8Ih9aXG=52g$J_* z7`T9J=b&C4W30j450n-#Y5r8(pBZU|y+Sx8Wy$tL4UPu_6Yx;wa+Qhn-bgX}a0?a&$09 zi(HM@?dQGExF+$hcs2EDOW2^w2p4>7*oH3a>$Z#^naR6v+9PCp5o4qtH%xB-Cv0YZ z1|!Buai;pqVCqGsnQL9_mzCjwPd6|$MwvU+&!NStAy{>)e0XG(xl?T>k%$OxQ*5k% zS1z8r(A9%rTf{KKzz?wy^O4xtD0C`fxPc}BHrQvPmApKMVz(HDbssnMu`J9Jk&Ezh z()(b~2*wY)$%l`~z+-CJLJWJzj~wemnrBc<%1fjS842B!4X?oj?Ga=l6^WE%B+kJd zA!3T50TYMDDPcPCEQv9fgE-A_ODEk%U13T`oFg>iOv7G8I(^`?pCN-Y0Xf#Ef^?sr zz)@;2;%u!MDZ8h0d?sbk$a2FE*c>jDVZ>m{cNroU8}=#}k#V_lCA3E@GrWUXpwE2{ z6!CnxaJZu$2vr~j1_Gm520{TfEQFQ+w%{L>rvQ9#z`}eIxd>lQx*iAt_+jgOco_(i zB8L6lk7OWd?7vV<%6?LYjD)F^4Od`0)UMSA2AD|sfcD8GPFP#*Xd$1>=;a6Ahd-SE|B35gb1wg z^tsQ0qQhCNJxYgbfydV;21AgQJAhGT?vx0ptcPv~?Rpm?1|P9-Lks?hr3!-;^byNf zmkPlz@8Dxd{lT}hw~xjD;9GyvJqt5gf6^^N9{|t;lTwY`mi$Cte0KT9{fK~Cjyk?% zaVO?V9@Ts-v!AkKIZ+R-BbOsu3+BCo2nZC|awd_y%KS zwi1Ib-mjzP!^FrOWeI}dc?~5Z(okWV02?z=TG=rx4UM1)8p)bNnEG|NfH0Noa0Ov% z*5PWxRI9`532#BhL{-~RT1m7%f-H-l+)=R_`KB_~g{c#HOyN$3b;&s7DLdegl{467 z^-pHg!@w_>s>sijui*zgN~Z|LJPWy)*pqdPLFB*T)j-OmVLpl%i<@kM50^qaL%~Uf z?_^S`5|##=6pq?Us5qrxdM-iBja^`3)IfJvtk9C9XFag@_9C{zeLp6F#D;!_Aj(E} zN}}$Jp9S8tiNHq2eKaKMA3-cldx-P!?=G z$-?|2auKdVq#|Vo;fL`{a}pml+}A=38$jeQFd%A#52-(TAD%_hgGP{2l`n0mn`wcV ztb9HQxO628k*$b9mFQR0GgHvZ^U{{=!lD8Z_3Y6oA7T){0vfe#3&iOho}#59>bZ|G zuQbG@LQv z)e&JB$f%dE!-AnB{yqkXJw=eDBibYbvG<=XhCP=8#Qq;~Q7~ZM;)CdR)N)`Vrn-(8 z0dY|WcVOD;2>wG*QLo7_w#p`%r{J2X808KWd`%e3iUpy#5pFZe94ADKhLs3wJ~IYr zXiqeLAFF^nwr>u@J9fwfM71u!CE~||eg}N4C|i^viFo*>Jfm!Z_#7%VC?bLtbO)2v zO5XV6$liwk6s4~z{vd#f7DT&}Y>M3iH=yKOBbcUkK3|kU`$`OcMw5UN_Bji)7r6+3 zo^>`3#yf&=_yNr{vJ5aaVv+k!NMxh50JvB773-k~s+P2*Od74U9&aJJPK2~c&2h4};IB76tyk~s+P2*ObnUo|w!+^HT%-AY90K!jTj$IlvT z4`b*pbm7~yLxms#s)n&Jry>{Ov8+qxAiN_8M^&FmRm`0#e=EkyYA6q$FJbLcjAr{% zilV9$m`x-=)vGMbZy*=p>sXh}LHMY@`S8Y2A7Liuo#?E*2+7899jPzHP@jbcMwvU+ z!>C({2<<%K?1CMn}zv( z(&L}{nkkw_3?gUlsUd{kGhozr^;ilQRYtdN1}pLvK{;(A&tjmw0YV7tMB<+@nPAjnNSfAyWb@0lEu@fJa zTDL8Oczh?w*y%Umt#A^8zP}uo9Eg1%e9d;9&cQ)ET=g2YA_JrWu0@9qY4_KIwBNySnkts~ z$ZV5z{XtbxTj^9-z=6I*T56t{Ein#DUN_Wbmbas`m9&DZq6#Id+g^ zN_BTXhlsw7_-AoyFwTD+JKqgZrkI(VX`GnxH0irAB*q6uDySNqh5-Fy#yg}h#bRoF z^s$kWZVaT4{)u${guij2vdye}MDIA&h?Mr@!EqYPu5qMtFYYLGb$|&o#pu!G;7`pN zZ%_t0gJtkrZt}Hs=_qNrm{sCyV0FhCgZYm^y3TdGpDTL_o-fL=3b0J)YVdPSIoB8*J~a1aY> z*V9kSS7LRVnXU1iagwqT?^aI?wlEH{Ut}I>b&T2tlRyg+xtuh<;ASl~BWz^|`^>zX zv{{^)pzh5q?_T7)&~8rJWkibT@5!(g)7AL;3w`y;rMXRKXg?7QJut6~zn+I_no-)SFbUFht|W53 zgE?q@@+0VH#QAN=CXJ6XtXpnHs0Yxr#@!Z>;cKp1OG53)6*FI*7u~9fUx2tbuDU;w zcrEnB6$&>E<30Qhb3an_;o?h^W@7dJJL)*SB9GvDyB28qtkNPPIEnl)-}B{SV3awY ze}ZV$5UkGb2Z1ZbzzqJ=1TP~gQ?IOppOi9cQgW_r;7LmYhTH6UWM2xd8l*YtWD)sp z6x_QFbKi$&2q^`-aP&8Z%5B=pJwAUG&|G#kCPGTV&sYSFy=ZDdPB^P_nY7aPh5TvK zsYv|S5W?}R#>&*9JT`I{6|=}Ls$?-)6n==3jQl4NX8r|Z{uwZFfe)}?ZeN>zxD|!n z-9k_n|Jj7sG&!FERC4l2DFmh%X-*;lbca8O$^}0_m?-40p;82Yg?Z=(X>)*x_kcax zBBJn4otwYc{LBU1>A++n`4ql5RS0fC0SYz$4QcV;5c+Q(H%sGCm?7rB4;7k#`9+ga z-47vi-eGne+5FQapF>T|&n3A-E;1Lh-pJ;UAZ|RtY0L_U>q*>os0q)UJBfXW*;CA( zCsxd#NRGFNI6{PH&bJcpnWIi?fyDep+2D8@s5vi4eAskUFF`i{RthL4Sb+=z*0X*e zNqKD4%F6$Ty*Gi5qqz2myQfEJMv`sW@*;#SWMqSlZCRUT$qTlzg)O{bEO}!ak7q{G z*pf!lj4baq7%&jbo;<>ysEDFHDM1BZSYX&FZD63C-0@Z{^PLV}g%J2z1SOwo3I< z4ZVLRC;VM{?NNp^dTk|Pswwx-n?a8o2+QcTeS{sh*X|_75i8rdVZ~X%TZc!-OH7fw zo4!mrkWsB&gk^Noc*4@UX(}PAZ42qm?xqz4t8O}v-mGp)5t^%;uFIX_7YROM%ORNG z1R{^&DmlrJ84rb$M&6s0lkkS&eG6bC$kT;E?mdR{A)@%^iAuzfX$1KiM|dXuJhHB0 z7&0RkG&BE?KC83y|7Z@tMv$l9Rd&4ybC=Y&>3XPE*!5)5o4$Umlk%&aUh)x9?TxA`yn-;_mGQJFpD??$58&lnGoEOp zYZwGEkL~CE-qTuR&2<3t!~B6UtENGu&iFCHTmlv?fk#$ZVJNH@Uxt7=gve(84NNk0 z1N}^y4)5Mg_}le(utXhcFQdjviZS@Yp33TSLZV;y(dAw+<{<>x+CJgE>%3NaWp_^is0{VebND z7B?}%Pr%0)dAwP4{RTiXpr*6%hezM*3P3#4dT1?I=@?cW5j&=#30Rxr`I5S zZ!zMBnc~1$MC|;T#6w+1z=iz>&mg^BQ9Q=tXPu$)X4qz+?l}BLkm-X=9_6R;sS5J? zti;WtG~SA!*%~`YXHD#0IxBl07N@W6YZ?J-08mc^u%21VY!#yH znuXvBf^uqweSbvCQ_m0lifVj&6zlL!#Y4tS#+zs$&0s11r)Br=|RSk)8 z^1?ENx}b@!>3a)F*fAdhz2OGkF|D4Cxd9k#5ity4WVVPMqvLXS%r}q&JH|rlV_1gn zm>(d59a98g8MAgfrUJowf^v7vjpyPsv=}F0pmY<>(e^?n2GfIH$J=*~AJd4W7Ma^xcEnvb*{bv6YdkyM7HYyXyjg4iM~g zmxg3@*A3^X?)oXvhu(-J>H1sWV#Kqv82>V}OxKiT6zRC!owWfuu(O!@Rs5XJ8bky; z>o)+t$E@AX`UpX(P43QG0$IV%nuiK6Wu?b=E;7;VS%Ipa*V35?O`!JpwR= ze*&NZf<-+K5%d!TpLak;^c_0~UO4!?lCsRhY&F8;H-NED$Mg{yS%IO|LGw+#cME(p zM6}-mZO@7m*tuZ}P%$PN;LzBpggZl=^ne`Q3O|eDorQ1XPC{5MK19%Wb#Kg1wAS9C zbj}H}(D6X1!cS*=D_}Gw`66>@BpRfRsGk+t%8GPuIEZ+ZelHvvj#TMlNim|o1CWLD zBE%|Oa)^R2KoA8UptCB(%!?3FfuGKa7pc%R<;kgI2w>i|&A9Y%XL}RoW9v$pgpFt` zVp%lrRh*0arcmr(q}Zf^zut00b6p+nfrmBVU5dP$3V3V{1ZQ|3qG$m3RKkef3Lw)E zcfG>jilFc60NM`^E#hejegq&B@zSfXU4TyTYj|jMRS8sgMU9QHBO@XTnKXoyY2uLh85i2InrFF=s^`{B|I>A{awR)b1E{0h=^gb=;qT0U;Z!|2#V@3+Of zo!;m8c!lB44Mqh10z9BZ90Z7aj@DBuV}@-7rShvp4|4Z0$j!&__YkJd*;56h>-PY1 zC((LZff=?lnDCBdz&*tvw-mcZqfBlES}!YHhHc!5$FV*^ZVCpuCm3CgFv-D-FTa6H zEzy~?n00Gx5uG)mZ@_sGk-x=HwbTgn`WxDDJvCHB`jrsrLPUFsbS1-zNVn2^2qHya zLnQ0N%Qb(9i!)J|5Q3;noQ|r?(};W#KV27IyF;_Es*AEk{;~j#|B!60s`VkOmAZ2w zpu^16{m{a3Gidua$HV=ISp8eVU553y3*c?9Z36V~zo3_2V6#5vXJFgKjQ@`-0PQ#z z1**b3JuNY!F9FUL@XAkKp&r#ak%$)pah--JiW&;VpZ>n zbk6RrQK30>&g@-GXQgz*ACdEi%w1?{gg3+tgWEyAO(N{(n{2@5-Yqo-c77;k&e{_0 zHLL|Dnq+LD@Hth$gSXB^!xoxs`k+`C7m2^45g950)^R4hgCZW*OI)X2Xr}^31PK25 zHSpMSws=1-B}J=$gP`x~&e)&DEk3LF6EqYN3?8oT-O0K*RFFfQ{}enTu97}s;wq0 zeJ1btkXdjBcZ{TdVyX5WM{l-W=R|^K8t-VJH_NWGjL=+movz#&?j-n#tv5PuC7w*b z9UC{pdjQ?H;j?&jv~5ZYu8XGy&*?-kqZ|Lgyi_xnGdhD-GYHG*#&W_A+l|e{IATM# z{U@uuh;?+W+A|o;sKhaZWpvn`EF!JL?juCCgJ6dD*4LJO(qUQA}6{HST3@ zY1DX_5Jioj(wklR*9cbB_!GTZ)W~N+xu|gxBeFA`N$|f$4NRS0VRk-q$}P;!BxVLh zP9Q9UBHv{eX%zV}A&Mez(wp6YzYwe_Vlm4siX2O5E{ZfUB0Iwtf{#cMqoa#>!Up!# zxYM2*d+tGudB;4M>>jds+S#}14QL<;Fk;h~2Jus+7<_I%{NENN+YtCOpzwurHKU2O z5Z2)&Ow}e+7~!{oz;h5D70igf3r(F9GH(axP2ZS|-$4+XcI-pAX6x39Zdh88zPFo| zgB2*;2Ynj;Q@(@?y6?c8N?{zMFg9EJX7##}r8-h1b>F;%8}(zP^`u+VH?>Et$C>GN z{Icy@KSw>dA}2;Olc%$VtjKj-?iKl;kpoxcOOSdg%OHDT3D_4z;ariQ31FC6yDRbz z1X-Wl=96B^G~d-r`tC*!G-K@t>H(rVgIbJO4mhLETxmOT=`1Z~5hsqUEk-OtE@J`C zO1tk#l+AVal>mR5QNq)GuL8{0-T=^T_&KdDK|gQ-emj6)C%&@)*O1Hwc<6pK^S^*9 z-R6k)5NSs66U;?u1~wr{tg9c`)AtLMLz*zr%fv{hi383-6DKa6CVfvJOB(kW{{pjg z=t9VIgeba%NDyL3!9ozQaNkgHC!78zV*WyGr|BA!v+3^x^&_IEH{Ai-P49~$dk#Y~ zemR++5eFSSVeTeL741Nc|zu}i< z41QlnkYixn=H|bQBOQ^87*1RcVmNUQF$nnpKbIIrUva;phH(()Rs3vfECiU;7y?i! z!45SvB$FC_XCVo@J?~CGK0k~k((Qf2A#7|3NUO)~U_VbZ?AWIw#(h-@>AoDtLIr__qbNY*rFX89yuSZZXa_`pwzmIXky&?A1LkK=ekkeNhlG#^A zc;{}MopRk7pUdp<0(w(8S9Kz!juMb zAHC^a*>N*r8S9LT36p*~)N=7F#5kgk(ea%Dpt;M%*O@A}hV>0%W>oD$!qU5?V-jKP zj!wG^I_d~f?K_>`Y=vkw!O|%mF?zE!tQ~~r(y(sKo#9;sAF=gD$Nvyd#$3nM+u(JV zi~F{w1@{l81wXX~!HjM!BTO}OC%qZ8iV~L5jmrr;Y&WKeam0qGB~y}hbmq>ED1#Z5 zm_b-Zhy9#Iq;=REgs7JMjo$1IJBInF4jWBxR)SI2t?IY;TYWpNYbG3a5BeL7Rj^KZ(?H!+H z((L8pF7mQVjUEOws4RC+QWP0OZ#G3vAXrhPmfkFiEFm-( zMLKh5xRu}|QpD)Ek$6g&-A?btn6HeEWxL>Y<=V=fX~DJ~2xbVf*O^!~v6;~sbecw3 zh9DbF*kJ|PTw)xt5lWEFVI5ueoyK5BCH}$eGCJ$?gr#-XR|rupd7R$t&U%Jm)mgu! zH>@ux z1gp0Hg5IpQ|B29CZ6D5BWoI~^;D4#@9ZSKho}s&$IprR@vx%8Oks`t}C~`NmNTbMu zgeZ#qgx>51yiBm7$XoPgQH0-v$w`q)Mr3DLNAM9TVsvx^&$nO}P6KuE-O3H0XS%!a z=-luWo^aLP$ZfU(D10@be3`>*quG8xfM(Ch*nj&my}oz?zHh@9xp)+Ta%{fXzY>rj z5yCIv!FOi7xkNv?6y_gj%(U{nbiY`7K0=U|9zWDW?lu*RTO1oJ?#S3AxUlI=+G7}0 zlIJ4ja{QD8>RSsi>$@7DM+kOW7kbPFcM_~xy0sJN8&G_249J_L~s@L5P(lHYj+j(69ieG+^Z;i$^XIbzE_aNrR$JCa6iB=Yss%8GONv1 zlM|OdEM&>=#Bq3twTO`SStWPL|2|6RqUj@m4U$&IMc*V)oC~Mn0F5QsSvYA(=E6yg zLKQ%j4oYA06Fi-{2z4Bz(3`va-bFcVJQFP@h9eVYf$xCRB|(TycjD5U-uEK1q)~|R z3z((Td_qnkL^WTGLd1}QrQHUMLY3e{HhmRh&Ly_fbPdVb^cYYtBYJw%2_7WaZhGHu zkv&(>e+4k_?i{PhI{Opc$z0M$KFq(Vk6YV8NI!zpK4w^V|=``tk4q0;KGk!a>bm&6JAR(TSPYfy88TsSDn{4{05c3&g zJ5AS+oK3$Ts5cWmz3C2EkNhOEr#YBx|0wYtbFc%+GzWi#BqY+$fc^@8S#yJq$Ygf1 zjuV$oCXoqFoI@f)e!*I~tTP+~nXL0`fd9ZK#X5@sCYk;Ukje4wkV!*wk|__U^IwDg*>WEy#aN=?h!-;cVf*opTNG3J<;z%-n1N4z^_yb?V&o&2FKqI3$_(gz5-HV2E!G=T8 z&Kr7tPa`2k95G&F!i>Q|5vSvFD>A=94is{4AoXwYbIidXBZ5NiEdc+b>edLmlNmo7a>dW^Y+&>s2BUI74Y?p zll~HO@CF1o6Xf)jhGh1Yv7`7h!?>*!9pTI02KB&*P6dF*>iI+eDVG3ftudrt->|Kn~U?a>oX836frKHPiv!3@8itRUEt^ro?TI>i%?}3j; zTZ zl5Zxq4?hV}SAZ{!{!pmj$h(0rjdaXL{08%KJc{t>+%TEmdOqe!cnzNT>sUoRXLZ%7 zR%J%V0Kp{v&W>(+v)7&-w-dgZIS%r%508%P2$Q!gJ2%`w=zsEYHy()m86&?-z>o0Q z_7WbQ8$MvzcL0x}c%;4HX>`2q$xgkyDeu8-FGV7=v;dk=|03k^qQKZ}eapAj$1Mi=$~$}Tg)zXL8Wj!{e{ zZv)9IVz?%TQG-8V_YN@yS-k~x90*p=pz1hy2X3V2k2JpHGe>N{$BGXx_i$z-==Gr=bL6ZJ-+kJk>c^6 zZ=NKc!1?AZ@f4qLE*DS5`Q`@kAe$sTckeYXqI>%J=6}%jqds@i1D9VeVZHuGBKDfE z+VDTya6dSZ^&L$Y^_@f)^_@kx;sWze<8dtA{M88v?K9t3PM}gk$0?^uISc4qX!4FB z)A^v`KGQ!@a7QWUTa3L>KEvxp>NrJ^wkqd~boQFxp#%4Mx@f^~>H7DYAJRol3+KT_ zO~=u#*k{h5yVqPo=a{|bIpXd!@1eWTd_p-tQ_dfh^AF_=pD!pAl(Ss{f1yJBI31FA zDQA~*u29ac%DG!Pk1FRU%6VNm?Xv;TGF1fpj80>gk?day6_uLHI7>=l;x*zRHvF=_a<#M86t{MeF$0 zGUSlY9EM$NPHZ86zxs23;9~h65pJ1%k^avK{AYmgH*cS57$=Rn)PT&|Zw?!RKq&*p zev_YCJLxzE3ig{<9D~4E25R@4+shCb$G}G|@xcCcJYYZbsrxZ6HFu%tq8j)%861Fn!2AG> zR**bv83=s2`Q>5_F+XLlI1R5w5^t%6uZ2mR$)nNVtW$+o&kzL+2<5B08;QQTnnhK^l2Z52-deM#pS`3WypEla5MB7KSZ+X#1 z1Fg`+VR{?wJfhu6wAZ|7%_*q`h9ie3!m?I7+e8=kjQ!dK0JdH#mlGfPs}#p>xSdY_kAFF`Tinh`F;bQ z^yT?Vf=Hj|`wQX%`Tk#{uaCR(0j4cQ1nTn1P(ZG_l@L{zg3U;u#K=yK8dvxsM*XPc z3Bfzwg-vMXs1x=}sJ`$!lM$-CMna8+|AlTHH9>{u6n>SVlWvx{`9%xin%xzc4l%61 z!T@fpq?7+mJdL4e)6XK);lt8kP<3|z?|l4*{{X3qj1kXHN0K~T*e|Hp#BXNmt|qS5Pbz-SvaiB&3?=R;dh0I`aWD|`_BS2R|rh1P!u zv@06NZ1DxNqA|deHobzLU?SALnNI#vJdL5#>1Rgi@L_2%*5VnInkUZ^|CvY>B)~t9 zg;j8?PHku$1#44wQUJdII*LevrzmJS~61+f9$K+BV7 ziGKtV1_|(&L2BkV?n6GlNvOKtKV}8uI#9Mh@1)W21sP(NPH6y6=|d%~!kAwI zX)OT$yt7NM!dzjLc8n$xuRQW$83v@%b*PiS^aYHMywWR+Fm6gq7of1xyXFL(%TyiV_0e6RB%Y?Z;)zu=?wTslomlEmz2(~ z!0<1fw-6*Sj{O#D{0V4vU0J{^dj+|c1x-asql{OLmkl+ypjKdsGTt>_Hp~tN%2wmF zP+74ZEGoMVn3t?BK{U-Zx%WVh2mh-jaF94vsD3jpJpb>|jM1uN*HM zV+SXc-32x+E471@%6RE`*|BzTYS}}T2p%VMt*^4WjJJuEjWb6UwURSoX5Wxj@ zHO?s;WN@MRlEiK(x&~b$L+?g3;*S7=LD?RB#TNl%=$(vbpQXcxrNJQe?uK45bfZbp zT0n(kOg;E#?DE16@W|K|b|;UWhZz6ZmE+D>0O*-yhjG^+;n-D7GV)kFx+`!P4lauO zBAxs@@iazN<9RJ4eL8rw7Xd<#TO z2aooGz|O44M4*envoJcDbDlg){C6WVNI;4Iy8`C3#Q!A0^!lsOqvcCpKse9;GM?qb z@?pL}cKP(=&xZWZ&jTOl7xFQhj}!P16h4q{6fg^j`8pOvbIMbNFJj&-ANU?Pi*v{t z#FTHLOh$VP189BuR;Hw!;%&+0+tqL?Dm;x9*r7tjh3nDu@?B2>=Bpf8_$LhM^4*hA zwy(0Z@GO*6zGu0F#unazZYIu-I4zRb||sz!mrJd{^{lL{3T z{&EgNx2aHZ;i(XT<)0_>kXBEghR_$-zogZxY}c3WLSDYgilUcwOP)W`F!<6VYRtYf z^I5hr^nLnSxperjG#Fb#hRE{|$1}JS3G@7{b)H}OA=?c9v+Tg7bpmd}vz-0K;iPzr zn1+AE$%t67PLR$c61$Pj5+uXFp8bBtR*Bz@=P{S!VaGK=N|dj=PU3FBv-~MMoV1F0 z*@KFCbFi`D|CppY_XWw~WoBW33X+AJP*|U>%jW}aBrp5RLBH}?GrR?)?G0$x$nws@ zGg+ritR#7V3*=|{W>$;5zhyK+UB>{Mye}NqtvIZxaLi)F^{8GdF4_ki;kOw}@Uk-l z<`Bl2`S;^#484(l7MTtomIjml0b+@NxIDRb3er2G1=0dvSc zI{BC4X$(D&erB5vAC?A#d}C5pXj$Y691Km3ldOqPLW|OpNOs-S2gZ4 zAo{rJ1dlHUcsmhD4}BO8#Q8+j$+BCl<9^7r6rj9QN8^(rq9c(fP9<}sT*d6xKlkR(Vz ziT^qQ^I77*h+uk8Sb-5%_LTyzsz-viipsuGSh^UXdkJAnH#Z=3pPKspg}2Q|=$lhf z8+Oodk&SfFUSxF~XH&>DnN-K)X$<{@@ys?IJ}eE!%3ck`JU{ig67iS#PeKMkg2$W* z-`BwY{<80tK87WaS=;cQxgk$wopnjDH3}0nYVFX-O_IjCwiVL?f^o9zJEL?;BF8j?wi7PF9 zgSq@x#f>fe9oO~0BQ+r%3#U;z`n?J*D+;0{q0nR`X+d&Og8GY@e+B}^(DC%M;_2{V zX)uT^rpf06WFkTG{A_Xgc08scC{g(o-uVQn$K~{GK@#=220^6T3nzFH9zI@B;LpqR zGew@ChW!T3_O)+8m^};9Bs;JyUSFrtFRj8s+ zH0+O5XhNYV*nd}{Nre|lsSj?$2QCfj9I-fNao zZkPGZgA#tgEZPi@aM)5X-q#o~hfpOp^B=_17`l&sQY0NdEDZ)B`32yyb3_S*fd3XI zCkS|gOZ;bnzIpO2@jnVkkbq*U=@6JYNuK{QjMri+*ABD}iPfq=Fj{X08!P*;HVe+aXe`l&#$}$6bhdD5EjG=vUkEn z1btj{PRjT63LhyTVeI1l~1*tHC z*Q1#QCnDEUq&Eu^6MtO=KyneDAA?n3GA{TLVy{Oor3Kd)`oRSSw=f1{xo87x(_Mk> z0vLdZ2Il@Lo&1;ZG=^@apGh;|pUQ+ACw_sT@`cF#^D|L~osV(igM{w}FBLrWRq&*S zpJcv(h=M0Z`dt`!oDIp7XNmt;@COO-cR?Tz8xI;9$8NBP7 z9$1qlOG0qwkY~)vD4sWC0Av(jf^ztiK|TyM{sb{6vu*`{!{Zq+puf1WfcuDY$JFqh z6M1jRh`I{k5yLbm-i!K6=%U#gwL-~g)fCay>MP|l~y<}xZ*tK7S0Q<6iQ~{yx>X~Ua%T`C>LCn z+*`>Du1e&Bt9D>e-tdB}61m_?L41|G;HpF}xKa>*B`>%tkqfRABv8o53fejAwd%qf{^PUfvvCF)iyKU7+>1#h(~QMX!+ zv_ZVpszlvtHNgh)R;vQ*amLNKoc8>()#YO_Hdkc1`bR;z>p8O0x> zO8nK3;ot_@njkA$%qpm;U=ZG7mT!lTWtfHA;mHiMWINo%FpIUrEeuET;EzRZf*WLH za6Nq0pT>g)`%9iSCw~PjS)y*S(t~5nT|kgKr2;c*$DazG08x!Gj{r!lmV@sej6yl%OM2Y9EHq%i6_3FMtp0p2Mkz#&q$QNNc_w_%D5=sTs> zA$wzn+$j~%cS=o6psSl(!e{V0&=I60!-wcQUSSB%EsWGQUSSB%EtH!L}ftk zl(GT5Qz{^LO4&(xr&K`hl(I2+r&K`hl(GSnAjJZ5r<4ugol*h0Q_2SLPN{(0DP;q` z09Fskol-Wy*93svDP;qAr&K`hl(GRU*cQ1{$_DUGses%mWdnGpR6y>OvH`qPDj;`C z*#O=t6_7in8iZmKd8brB?v%0tyi+P5cS_j+-YFH3JEat08t;?}s5_;&`5<>nmH3|q zzvaoZ#6J~z2MGw0d4puog0lgWJEg`kx$rS~gIZ$x+ zWR~1fV26r~*|&4b|10Jl>lne9^Ni!4^8@j6c{BG0pq;``Tm_h5?hHKq3ua-x)>VZP z=kdVCybe6(o`{%vS2B1#gJT%{7K7ixWB$Cq;7NB#Ii3qj@s!|n3Gzq={^p*}L~9Y4 z7v`gj;Q5|1E=wNE;{4Pn6q`n_q77;BJnNTX-_#cpZ#iZe;lxgF_Yr7q?*{|NIah z#zbBcIg_q*;Jp%Xw_unZoVMcY7nq^@t$fFF)vs zaQT1v@4Vo@Ai%$6b5MkZ`dx$UKaHP^4r9fc0N;e*LHw3-sF*8_Wt{+$qE7?(BjR%C z7-zfy@9V^qV@)fODK8A>(Wdd=!qIm8G6&SMe-HsxXh!4`{IVM6L}WHhM`Se20Xq#F zRiPU8S!90`>%d`etT-9qFCcg~eon&{0LX?t2jC0Dm4>ZY1Mn*d{+_sY<5pf}z|MIi zI0nb@l0dMJpspcc$&~=GquIgFsZORW=MmIZe*oK?n+fGf1aq~Y=h8+tJdRmuqI)il z1IRR6)wwj8rmVl?Fi6`-j7ZaLJCTmBt&xtu?Q}YUwsYtdxAoAeXxmF?e%m#4_O9kg z#&NXO_lxUW{R46Rt6vs3u=-tbi&tCFhfSkm^=NVDw@s#dciU__r>|a4$8WZ6pli1E z)A6-kDA2agDBv9m_$>u|icX;I6*|Rj@6xGQGZG7&wRv3OtSy!26nUN^&okv2<#Qd^ zM(%mCwRcI>L-Kr65V(HvAWUL#ZIe7>e70T4r`h%yI=;3$==j^dMJLeq6rJL>SLjr% z{VSceAe2_~n6`1^tnH+;c85H#kmu**d9OU5l;?}`oGs7uIeD(7j%uzIhFJS`@joWd zXXW{ZJl~h+G0+9gwPWNtNuEveJYAk=%d<+*b$&$c}3 z2Z6RzgpJzHQO;rlE7o?>SxfE1ES^^BIe!r_*2+XtywD4^jJ2~dM$Pp+8df@n%dE(H zTAoWw={MGgp&6APM}NV3>Mx~Z>91YSV_~J^=s#;cPlc6M&_8lLH#SPg(?4nb?U?aP zPoRI!`nN0KucZI9^*j+)$`3diXRrSpYEwE<{AckXS6-u!4>EFR&?yXr+i zP4g_SUXHy&ww=whcy{a9D^=iZ`vBHC-^56qh}A!qqru^D&*90ZH7F7Q!yIlFAG6A| zh8{O`;?R;p4BpYWyKq+F?7|jsuQkjl6c0=#*3iPjQ-F{MZ$NpE8D*Nl%Fi>193V0# zsS_nsP=Yw7VO}uK4RNw<1YL@%5Ie8P8oGb}(3X<=qB%t{EHImTc!ugo1s_&r5>o|= zi|rZ>m;6x?mH!AkO+cZQNE$mZ5)Cb34M&Yf3DZ%V(Iu!2%N)aGmQ#gN6;yc~TLrQx zZy9PIP()$vu`Qu#)L2l9{Kl#(5`(#p(+$voaxXxdOtj;x3^t5N4y+;rlX&C3uv#hM z1TP4Q6|^!fLnfVI_d*^DfrdNLu6SMxZvBSY;zV%4N&d8s!g|RXIu>o3?CC%xJ=x|9 zB*op7V~|FZ9%g+_4a1m5mLN(MX^jKr#B|x9nwjc^HJa5hit6n=gb?$<53E96HWW3f zcZuzg4O%jr#J*_AN`wyOBBVOFoY+N$A zg0psuof><~TjFIRQoJPx3E6$8D-L2#OEKUA^eiK6v0?%AQD zJTqGF1)?&`3YQnCn6oeq8fSaj;|5J-m6$rGz_q_%|~ zJ}YHsCBuKS9V3A*RWzj-i|=NTVvST$(z8c{$XqQ_$pk@4YA{i6yWLykT6ZW0C42_@!ZL zq*2=8rXoRLru!LfPr=Oi5*Ue7_djq%XF)m45L5h9;nA_2GI}AE9UU60IbRRGq6ej8VF;Nubg0 z^gbqi3M|!A;ISSVfL^5*AiSq$&*Qu^;#lvDILlI>G_nZ3zQ_vp3GUe zp32<@5W2_~x1xzRvwlj_pT=^Pq+hIC$Wgh3D6XV$(USgjEe)4yal6bbZkM~W$_iqt z3FeGK2nEW*l}Z4gsp&&`xhhjWI`eLe%q^I1wRprVD_*ec8hSCIYT3BXjskVpyX{~H zo#h6V3(H*HlF! zXuN@{#3LcZ#(O#xZW<&7ETm7Sx}RIDF3{UNqI)IQJ?u@gqZ(O5eYVWThH74Gj=UP<2;?~Zwc+j?V61k99*0js2J zAZLCBgfE$js!U=2R1Iq%j3TAo4qcaQh^n2=6X^7&wG&y#wnk#TDH2lA*~wc)#1lP? zi*5{sBav9rt})Vu`$=C^34MVyM1oI>R5bzT#S`#?ek56{6CD<{$<%?+G&Cwa(3Mgs zy((T;7R^R_RU?J4)d_%3s`obzw6oYq0()&+QlVsTED{fQ#kYr3s5MJM(OvOw@;%U0 zCrB+21+t?NQbFVgKGfbpG6N6*4J{5u5 zCd6N5Hu>@~mi-n=;H%`;_h92H(-6LQE|7_U0D~7sTW`bs<+E0<_M299AAL=J%W8Yp z+S>HAHO4p3yw=)2<%)j({lnU}*b_S48o$R1t+vKTtnd_TeBYf{^k30`*J=IJV%DmI zM<4f^C*9hE-)fjM>&_*CrGb=H7(t6}%G(3t{B1{{{}}6Ti&S~uXaWUp*umK6{edRy zy&Zuj#6D$_DBs_CBmDOZ=J$6tp?#0;Zd%)lCQV*^BjP_}M&SSb?g(=IUxO*6z2Dr! z?4Cr{0qdqci}xY`T3SP<0Qr;MQ-J*6X5cmCd?#|wvwn)4mEUhQ_$HbErYcr0M!7ec z@1W2(q=p~6HT;Iu@M+ZW80%s`EB?_ARof|Pwf#_P`?%>l(ac@lz-qwn+S7*|GD(Su z`JkoWx_2k({gh3`2>iJyI&tx5rM{%-Z=}9|bL)Gb)c1!uDT+GAK(fb7UuCXJTGnkl z0;_Lw*a?N~GoO{NjG!y?tlyeXph_8CVa4vZjsrWy6dU;JnYFI=6o(O*dU3AQ2dv{> zV`9tt%8tO4*^|GIVy`y?_*yhrNSC`OS4x(B8SkoAdS|{wWHr~JU{ReJ-V6LyFxNTPS zE?+&In6GK{1HRhAJFR}-MWz|(wo1WpS6opwd)@4H(FXz(&S*}Y6TRZ8X(wI0w`~?m z_&f-2S+C+0L|}w9^qpPSmcYRWAGEf=^PrVFV2$!EH18C=PYK@t60gDXg>K7f3$GXsmQ#wKg&$5wF9?B?0lNeADZYpq^vRUEL^ z-G9DSIAySZ_DwgvzQigF9JB_f1lBKk{{6kP9&^OPOm^Af7u1TwT5#9{J~@w2CYx;enLV`Q+-VZR%u|*1G`DccXqLv zxB3Emd`pXOMqj<{JK{iGZM|v+rdZc)Z?gWFH^n-*eUJ4*Uc`EF+Z5~eW9V{lT_A(& zACNB;1rg8S%D3*{!_o1I5xCL%I>-k>OtCRcjl(@QD!_|`2aVP6GD*-{6SxunlOyyp zi%U$5v!L|>MvdQU>H|+#?|~1pC|9A(ZC&7ib+MUSxcRMzO-=)fPY)Nete^N6TQBT# zMd7s&hQU~9w12Z)1A3w=b_-1_20XE zQTF|tyEs~|_Z<}f{ixSfd4U7S@Ng3XYM`ZhwS^IdCOU|eA8>_9mh_na?4K08b&AH_(SlD_P^3)m*(uUZ%dc${l6|HpWWq)QA$25 zQt~SzC7<2(e@9Bb0)qU|Ly%jAAdk5Od0Pl_O-_Q`D+Iap|Cf?!Qj#JB+Mu4cIXmP~ zFCk4FSx@0|2>n%|JuCXm!R`N?5KkB0KI;oyP+<-D&J3&xUo{T+#$dVFhxM?}_$=1K z{~m*ptjLdK0`gd(4rfl%_A1v(McdHP+K9YnEp5m+q`|0nOKfYnySFQ5n~c(|JKAak zQn`i=Wje5JGb$#Bp+igxhc|QpCBx7m#*8#$lwx0F`uq&i)mK$1}zg)_X>ib@`qqU_j-tKC@>rd{eB4_h7a2D54(T z)3jvCRq(Ec208AB*7l}(OJ-eV&6;8@4va%?AMHM94GnOS-gF~^5A6w15%}K!^NcUr z<6C3;)(wIAWik{-)0+B@3_7%_7{5>OhvO$Z14VxSB{0Rw5i`JPzlpUI;@uWk(=P+L#|I{JkE0BhNbFTLX zw!qcpBK^85|1xG_OeF&T?gh?+U?t3Jn!os4ch4VMfcl&+hd{z<9J@{% zs{*@GrE;?>;5&nPY8vt`Y@9iB@L6Go`Nw;&!MRSHhVy#sTyWlE-IxQPw^g6$fmguK zKVIGZ4qRPVX^*ds)pOd+E0xE1Xwu9!`<4q1PWH9d7Mx?gz<;Z{f=U9T3^uj{IcLB>rW<1)YWY_(m!e9IoY8QJ*0)m0-wQL8=Q zW(9}t{ER2tsnaY$A8PvY>zp`P=EPOl<zmWLu; ziJlmfgj*ubn~Mk>*o>Sw;K$tEQ19!P~#vF1&im}tdJ zNwRWH%}f~mdtwppD6H;n-V*Cckt%NOjbvw{KUFc%lZ-v z*R$p}0v)50eDpR=2rF(P`ar=`^Qw?p;Xn>7_~Q(|e->dg3Y7{^}68%h{5fL;opCwJ-q+Z$)OU+$_Sz)IcOPM|>5m_sOTE)hE8S zxtpkLbOak>iakKQY7EPPZqX_m5l_W035ua9N5KZsm@`z#%5mLUk#HN7)@6yF4(7yu z%jg0CmWTRdT`_KBvQKmmHz=)fMyLgfvUFrT$_j#t5vf*9U36oAD3a>$3dI2T@~x&< zQ*S>u>ErN>fz%o8%VXV%{%xM*ttp2iX10gpU04uEfvqXKPL18zh75Pa zNFER}8tO*(r^5Z)>^5ewVSOagyRCU;L}XSd)a_xt^hVa(xw*N!>`~K@#zqhv%{7hE z8evGyUSzKJ1lUNT+Ay+NMRy2*uSD799&ArC6r~uFE_U!`Kr3;_5#N~nasqRouxF-=x#v)E%gi;9r z*@m-LfKf;|?!zkYZ?F3JW@wUpfYuL1##_SZzGYff1K{WZ=|>z~&Kq?;QQo8tM*jb;O!*q0KkUx3%!VClbq;p6uC1igg9arcF?yk_M`Txm(3G#Cwvl{?yUQ z7!gb^S9G^PQ-OpSj7MXkEvfLvE;hUyk^)(7i6KUm!P>)-a!I_S6Nv;7E*b?{h|zAS zfrLu7wLog3nowOZF<``D#!?e~hBW0MNp6n!4ssTOqyP){W0Fi0&+QQq4Lr|^DuFE! zkq6!Zhe;JVAfJJrfn+QS#(})7W+!)t`!}cQbZLX$(aPq`H3o6^=(I#XlnE-E%)up{ zFmaQsVE0p#lpb-XO2wEQP_AatjA87`(V7{yOj?M8PaAY*RfdtjMt-skP|a1TKNbg?=4`DkR!CWwJeGD^+}{ zO9_gWbPX1qj`?;WWNc5YYegaz#|4*qO)__`ksJ*7=HjU3amcf@kzG@rjKsk!@pfCm z$tX5`jzqDtvdE>;oC=*g3VMZ7k@irHt)oN|U0t@a*GSHwey)U*5_fJ-)>OB5CBmup zc&sZb?y$78`v?mXOr;_Q zpvtw!pfyLibmAO=_UQ>i4$_5EUM>^^~ zGu+Mx>W?K`~oFlF6Xq@ zo8UZ~*11iPXM?>7Os-^~?$Blts*X+$No~(|G7nM|IW0oRbj5mbhLKrnqSegE;ACVg z<4a5v!h{$ON$_BdOz0|z&|Omw_e&D;M5Ci$Qq*V*hpg9wtD2LnF1uphvt2ZJ~!N z1h~r0NW+d$;vKDMO<@{P?C+4crbm+vN}!C$WOxgBMoouDsIoM$7d9L(Ju@_UC=fYU zsamFMGf_G-KnNtF5MJrfaMxgXTN1`E)LF^|4|v3sSNbdS6>I~W)MSPAWQ)>aR9$Ps zDcZ8qm+FRE-JizULZSWB!@o5I9hs{^BZOI$ViUY&gzPOU>bDUv6s=h>4N#tjlm>JN zDyg+R>%wVerkLuK&Cs?0s;R}5W;NB2d@Hc-Y>h>lAt6G&_5u;!WN*BO#lpK8v#3qE zjj6<7MAd@TMV+cbwN27|cM5IrEPp)8dm~3YHEzX;Twzdp(bf~|%_YRe7RD(+24D?? z=u2orF!6-K16x~=ycx@uwoq$b$Tm5$liR~xFsH~YNtr+oM<&cH)J9PfbWagJ@PfEh z#s+%zT7ZV90UXp1w^;S=UUZs) zHS@|fNrSd1Q37mxhzF}R4x1f&GF98KF&>S>I7Qol#}FP$g*)`HEJPr;$29Mu<+6AW2d0>pRrPG$4%53Rx-w>1q4Yka zF3Fjcs&O@XoDJY2A`U4IKf5T`EG{WF==XK zWJ{y4AYK*#2SOLyMxAD3A|V=zX487S?y{NU!K(sgbi_>CT7|YkeMAP~uHH^%(9_vC zi!Lyl%=4UmpclhRw>?Uy^!%8SI&{8g~W*b5a{3%R`2`k<8W z*s!Ey?b)j>%?@HO+OhpF%TX|Z+6mzfQd05mAe1o9F81yXa|T*tB-|V3J2<3S=AM<; zgrWL9n@cKk57ns(p2a5|!fN@Jy`QBj$=D;*P-{-m_Jk}V z+Gw5>lYm-hIfE*r(q0QTH~O4$(#$QMo)#q%53w^5j`R&cPK){>T;N*mY<+-Jw{W6X zHL*lEghr~S@eHFn$48o4;O-nXx|EWk3zD)96eiF{4zrsom~hVxOZk80TqJ+Nx27vLxe6n%w^YBv@w z$CL*xCzpnUVyfE{{exIcr^$=7Zm>6wXl9f5_B^V7vv^6>SnufEpOYvcz%MwT4*Uh=xAs!Bzn@ zVd@-FwbpCpMgwW4i6M|2mXbW{6c~Ik7K7zf|bvk=NFJxBi3Jt5DHfx>n9LG1; z@tu-BW6Qy>P%2w-vLjB(oVPWc0(m*Kft1IH+Bu!WEkhb{B!;yB zm~uI0Q&(;o%1~nT$NC1K_@+;wsFcmn=&el#H%vA2BO4aS%F?_Hmc|s$%p;vKu8P{@ zm;e$O{{8LLXGGl=`xG|9-6dQ*)N8ehI!d`!&&ki0bD)@FW}J!uooI(~HUu;5U0fTX zq0MOKKQ#uSN1tp@$p4bO9;iJ7y&SFP=JqyO8_u*h;uHhohy*z>d#Vtf8FL+Mp8AZ1GB3;o zr~^hL%1$z_Ec-nxT06h2BLa0uM`cJ}t+iQ01We|_q$&LnkM4#BCDav9WmK4KBeNK* z3lpuX!_iD*V(Wkg-H(Nq82YG9a1X%Vu-2v5TGO#fvPNhHXqnkL4B)2HPt+< z8prgs1+$YY9F==(L=nW{Au@x@|K>0evz-=1#F_YRyNq7#!l#HX1#^ zy<#byDWFqodxLH4^qxU*R_jM8A9}lF?T>bOL~&$`yFCTE9eQ=GNZ2C#@5+3&0vq&O zAicx@FG6e+HcORG=}Jenh~?QeRd=d=A|7?Kv6SecclwkqTF9w>|N2 z$o4vuXhm139h$9UOFfd($yJ0SyJeaqO>^jZ1Vd8Oad*5>P#mUkeWpr`I-EoW%eI=a zX|8r$pKkQGN4L9ue55r6>q`-|q7^%(nEWI-BbQJU_SX}j2)4MKZ&G7RxZBx>Jfx6O zyF{(f6qPMgskd{uO+_iH$D2Ct#6JpL!Vs@vytz z0oe;T=ID-`?4x8*@w^(QYUK=lh>z8=+svuh)rS?cd&IEp-e?=6-n~cNl_uHS1+y|Z z+dXk31s&C*QAQaSeUTVwFsQ}^$GV)|;MTs;f2B8O)LoFic(tn4$Au58(~iEA4%KB)7y9KsM2Vpns|6hkwM+1uz4RlqsTVlXp1>qIdW9oiB$6&3EEiorQm7RKsar^kQdDYPSE6{`rER+E{acvq}lE#5Jq zrR@f)&2jW2l6NKs?AgN^W$I7|btD=L*&I>CZ1Nw%z?UJ8G7n4$H>hD_$o@3=UavLP z-m9F^vRi`MXNb%ewaF?vg^ognv^pYNMvxvf9xxBI*t6p+iyS~5<*XJ2^_;HuXjrnL zEVZCXVgbL^gL5KL&uJRWU%CQD57)K3Tf}6C=5%8-pN9^(1HlbKk`C>J(I$c;b)hbt zgZfuGUluFm1#~z>C(ptnFMeNUjuP84o1n?sHiv=jQJpcnqbqqY1bft;UyR+}#tF$Z zbsAF%CBSgrP02NTcN)#YIUKPWyZdKM>h^sj&k)LK^;JtV4NqhNY2@O(yJvp$Kr6g?O|NU#1eWjv^&9q?#fx++oNo9NTOTn>4sNDF-6#b4JeonmWOVYIx?zM#v^Hn9x?kX@t?O$vSkYnol!m zuGX2}qhWG3A0r<_&(jgBF}Pj9CZ-3(tITt4g;Nxgp>$_~vQ50I0gH2CRJGG0z} z9i4esX6nuu^j!O$3$?^;AK>IkNSM4fltE=m(XH?f#Oj?Dvg_eX<71&E1*s5Zu&8VHMs^?gv)G;Yx754x-D987U zJnScO09~S19rE&#+Drg%*9#|bF6!?T4G>IVn@yC+gQB)PyotxITn?_ul$|w7s>{-F zA0Qci?sZ_mkGDS8zqU+0T_wiFaNC(^V%^uuk;$sqN-g``v>TR|7Qsk~@H z`EI{fv)k@`HVcMxS+(($z(V`wA6t1J9F(wibS?X)*s{-&Co|o5VA30`W;EJGp;5@uZbg#A?Q!c_Fs=!hvB=nD zq_%{(KSdXZ-|>cutyihMomw?{78^X$SXU#>bKW_$k8@$*&^*qO6M^!NGeKkmj=wlw z%FMDQ%eo`?V1X4GuMowtYlQCL=M;G~RlQr6;&P&+JB9O6QcG=r6d{5na`aY?Ct=EI zPlS4tv4JRcE?db=83|01FlRGoDCk=dfzMjveAvcK_ThB&rG3EW=&YJB^r(N~CwHK9 zxo>wKkz%^9WX#c+E!!hpn}bE&EF)X$X*yGrxuXg;>cdy|B%hoi^hZecvE{&`dcReX z?4NEPh7*W}buQ>4S)?yWxe=-@M=X)&l+-kwsc&%pNSiUGy=t*1HV<{0Ra@~zW#DQ+ zt#LTD{nK@~)gXIu%Bm_E+DrwC6R8~1mh)^Je3(Nx^0MEvNb4byKj0ov1#3YZW72s^ zVl6AQjSc2sd1cAYx;}k*)e>!P)anR?3D{O`hjC~!h1O(hJ?lw5BhvI>OFx&0IJ<3c zuBkVQa`YIt-R0<JE zO>*RMX#1=b4PE6hXR^3@v`^`=@tDQbb~b6B@fL!o8@+F}kTuYR>;ty;N*-_h=KRit z?jq=9IE871wm@7}39(nMQCmJDsF1r$y;vWocP=k*(mrRd=$tMt=KiJmo^(eB+Wf5v zuq}P5p&Cp@i?6iH5fGRQ>!s>#S~BX^Im)A&?xkYchx#0BR@-^Mh6GeCNt>YMB|zJX zq}7GA&k;CBjlpW}VK(NJb9Yz0!{Mny?(TG$64CBAJCtaEZbj}inpCFM5Pj41x<;6N2RaVY!zRP?yHJ;sR z5r2nRFi_0Elz{1x^O-2p&i5b;_fQmv7#4!IM#qqQUNPrELZ^$A=}NIi_PZ^3dBt9U zdpQYwt=25+Wduwt?qPiFUGYO5GLTi*+MR7Dv0v{+$HUMhOF!AufubR+E1KjmisQ*B zOfSeqRAWwEisxZaRPXPBCMj4v93i7VL4b4CYQGZNFxG!K{*L-Pt40Q()56%niH@_unVNQJi93Si#zTZ(+p4fA$`PdaO*L{IVUSGg4c82LCa zUot+2q{#1xIlNsS1NW54PTRO7b{KMh|7&tiO5>`eT& z;*(eUZwLG@;r9l9?|I-xRdQQ5z5)O@)vw%6?UU7AuTh01L#%4SsV&p^@ z8AVDRLjDo(k9;@^F9-2pMwP-xzG2)DVpQ>Uq$Hrq*@XAysyY%VWMdLg>_p1<2dbj6 zjRPHcYbo4=@74fYg1rM9)y|I{miLx4>c)*YNvSb$D4HcpDygOpa00U7cHWxsM{;p> zC@&D<#TZ8Y`TjsX0=T5c`C}aaD)Hlo8Hu+9cuNpb0l)s^x7YZGJ2rVohTwGquM>DH zkx0kWf_xamgl9@ys(Jb>?;y^9E@Wtqg`x6bC_hbY0j;}U=14zSuTe3MT95to09 z@x!MT6Rh8fSK>Q+!wlmV_?Zv?29WwpZ&`}mdk$o9)3(t zyxw2<1HDTXzac1px_-|fr1ACwZ!hqeUjJ#lp8}xqE&<*pz|(QW)&5_2@Gb}5<-q$H z5$r$W@%JizEQj^$dFXBoJ++Vf`_yu9B1vy9{20DP8Fkr5uB5k4U!T$vG`ARO}}yFYwJk?^%f z@*98pgf-OkGL4bO$p~wDnZ_ui5nQjnN`gmOwrOjIu@;=fHgrSKD)3 znE1MCC&QldcOzVlZyUqGxXG^1Wq|Lr`-AoQ4}>pt!lR5YBK$L(A6TDz5cbyRzY*SQ z^B41f4q^Sr_Wlxq4?OjI3*iqv;lClQzs=0_Q_v9oU0Q}IkMuWe8J>->{&p0{$W!A0MMOHlV*avW5fyScnk)9bw{EAx!?~$Qh6RI}>3&zJ`N-XCeG$ zMLWzJi02^8 zsg&^>5Z2$dX8zj{rhbbk#1|NQ5MBoS>umg65H3aiDM`tncOksa(;q)X_#EIzZ2Vs# z+=cK|JN$ntJD=A$i!hF_7Y`bX6b~LMhEfo;#B8jJP$bP}Q@5mP$fhMNr9-;ew9!pc zvPs%VK>vU!f(21~5kW*tPl`fI!6I0s7r}#IEfp^!5$wTRK|kMlXZD?U-gnz{4`IKV z-_OiEGds^S-+727>Ralol|+mt~FUT7)*zvv$zCieM zF8l)cB3PW~YTs92eN?_yo4>$U2>;sk&+{yiy~ukfVUqV@u;=QQJl+7`ApShz%5M#P z6a22jpMw4RsQ6!iHwpjTg*M?LX|^mkh=0w=V*q>;yha@5Hw4!24W;SjKL^2^g#YN`8?fKc zY%e`lYJ%?){)|h17Cc0MdHwYPxQF$RZKlot5;&~iHSpV%H${5saq#P|{bjI!+d%OTf&H5V!Uo(T{+llS zXW(`44-Q`ihw}Rg{C%(<+y>ufyd?Va$IZHynvdztu>Z$EexeMYrO zzr9og`}Y!*{sdSbg-aj3<*@$Ot3M5lb$I=7vdqCC&XtX&<@WN*>}($f2~(LoG+{<- z^-05#xf#x&>T70XV&B248LIBDjoAnXDxKJ8`rv7S%(2zv>T;iM@!+YNFkc&c=p`Su zQgU`Svob#qi%?Qpb!<2-%!8yLk5r8Rp5|&#)NHQKN~UL8#0SRdw7aAhF>fE+!?ElZ zPu=H4V&Rw>9Su!zhH6VpZoj2>`t0SE%=v4O!k)Tv3hk}Z<-i3+$JdnBI5+JNzM*^Z zEcAnem8mICH%+)znt{?Xb{ zWqjP=s#-F`N5}RZtX1TZA|6O%-34q%SYoy)b|8TVBO3M1%DIF1fO~ldEVkO!R$P<& z-tMd`+4#sreX(w;QR>+<=Cnvkx8lMVc_VbS)kaLtB$Mz9Xln65TbSdRcxigg+T?@2 z>q0N+h179Yh^Qd2COEUwa&tA?)WGpAa~na!)hd$}%w^a!nfKYTioejwhH11{Y;}=U zXYhCTzhV$2&B6*K=@vUb1zB}_c)?C4PVncJU2DuhU%BrE+hd{WB->VVY^F6HwVx_U zXEfD-z=mXPS(S1%icO@>49pVSTcd*V==j0QpdFV#dBk^>t!kl#I=z2fZx82cp!z}= z4pWG|C?oy{Bcq~>0}w%I$lh;C6QxrNvXk{8X?<#P8XhQUxldG%2pDBa#U>V*+4h{} zt@vCHRt4gtx_CA_l9&i$O4hq1(|)G~^xWRcE&5iChoe7V!{okaflF-^HK~oQ8g6ED3oaPt zhM$0SCCDrb(Yz5cLgb_oO&TVHu26>ABKqX2(R2e2WooqF6B0M zOz=on$}nx)*kKJvDqIQ8L3yveBaP$)v$$bHbcn< z^Tt0EBL|cqGln{|ElW^nHLdk`ESSq=nJmK424pfC$Y+!`E>RIuF1rL)=YJ+-`IkbU{wuw%uJx=ezd{yN#+#oApP3AY!w&WoDQS zd&l~h+6{#6v8BQ)7x3#HSf#zExO3cGs_4xY-SyOVA$1AyoQun$w?QV?yShoI_0{^x&r_Kl}a%x9I@l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4cb8751bcdc13bc39adb4ee022e044938a976e23 GIT binary patch literal 31543 zcmd6Q3wTt;+5b6v&Te+eZgznH0Rk>0(a0qP2?zm|m;@3VASNN;1=o;l*kEoZyIiy) zHbjYQDpakuqNS~$#adfxRj8K|uYj#BMXR7jvDG$dyr5Ew^^*VZH#29m0sOwt=l?wa z=YNvfncvJi@4W9jw{zyqIp_Mqk_Ec1Yt~`Y42|f(&89O4{_R1TlB4;x1T9mWsGXrX z5XH2dji7-$f-s#O&V)1IlEEbT2GdD59GH%2H)8Kw%I_XXT(=nZ8&}E{KOq5OM2d1;qE`Bdu19-||<)2nxw{lv2 z&D8q3#>l#?)~2j;WL^|4WM8;=smMz?=qPsv+}Ut$xU=BK!%^o{IP0KYGT|&fLlI6^ ziKF3X8oThZ_QSx`T3m-9bA!zb0`Q z;uGW2TnLYX6LqKJN5-Wym=cF4#DzU7oDdgh1%HJ4%kD6p|Cy|5EOR{YeBc6viK!Rs z9-cN-`?$Xqeks!LhR^gn;jRQug`&iNM*e1C+KBi(VE?10b1}k0wUOG*@Ed_?7vi%> zAD5mDyb<{`6u*)1w*b!rJ^9@Lw;lLCg<1DbV0cI$qP>XxJAogBPx{?(UBGNf#Jz~` zLV2`3%fA%(0Maj3=_SBNfEyI{03QY3q3}cSHT2nY75+8SeZV`>7|8!WaDHIwOS}>; z8@OKKNzf-11@w6KeF`q zXwM>kBhuM7n4Wf0d2H|79~r36p|mgPf2-(wm=64*!n2URFHYVr#P3A9pEmcbKGM2GvTbmk3g{n;1g>Y1FEag`L#g7C5!VG73XV#y2iS&7N~`%1s0Yp zxiGIJuw=o4^1_NhMc#!ag#j&a(V9S6a8+GvIM|Y3U)9Bm9>Fza9uc1Q`f-Qs+N|j z^?_AEWUH#LUs+Xs6=NHMt*uq7f($lRH3(l4Hb({%R<{JfHBcR@YHSSFlfJsCQSg^3 z@i8^Q)^JPHdJ$D0tZIqR)f{OJwm`U+Kx^GKapgfU5xb_UJ`#+j)z$^;YXaf*%|R{L zTvy#%QP&WxXetl4)P@nFRa(O})srW4w4F&v6J^a}XjOG}pp|GkQMHT@z&I`8rW(`< zY~5H@4?ByP`hl2+U_*8DdaVIgYO2<1n_ECzt3iE%TNOsR!Il;+P`m^TYU&yT(5nXA zSQcvlwboEo*QnLD1cQjKUWXchx!4kdHC5p%#I0;?m6V8TtkI@LT3e=}$g28jLf2_4 zt6GC%PAM;$F?HSSS=z#q;tTTw=VVRKnh^_Uv(ceZYiOt{Bm!bK)kO+Z7=~?HonoGXKIO>N#Mo%Zz|1kxj!}VQ zp&dCnM%j_^7wD@A;JcGP@;*WzoozRLOa@QV$JoDkYvTFfAHdPMj?;EgQO$*e@_G9HK(KK7ncJ zP&2V6FfAThM||`vL}bwNp^d~x1ZE9{HWMEZm^BgFLfj=VYb10R@lJtRGokIo+XZF~ zg?12c5tua<+DW`oVAfb@H*vGTthrDZ@s$Fz21EOZOM$0KCv#8R z`|c1U`wz4|=R+Z&TUJ?)*ysvp`y0{g+|h_HoDj`7+q)v($1{FxIy-~MF1w=RKw$r~ zpz5E|vjG|+@0=GNpNZ79N22-3Z6_R$7ZSYOpWglmX1wUPOz&eAN#3@9=wW9x-@S3R z78zAq7ODhM+q3@ip8b+Z$LoFD5q&A5`~K1Cek^*e*;(-?cVCnY_EcP*)b~`h+&oSts4)6mBDqVrk94QtkNzMrTP1| zsMLQS66tj;_N|WN$d}aiezJ(WnsF}zDVjCJk3+8Lx18Qx6>F2cd#*O;eB$lQf*hSW zWu2)HcUD~D=zSGMK4Bq7{g@UgGA0Ab6=}T1ikLTmu6r47_4?vQ4-agWO z`1o^$-u9P8%pqnH_(|dwhrqi(@@9YJP|t5axA-Cxmz57t#<8XI&9}*)HJPtJQ<_NFv9whz%?X|S$0%!sS zXosI6vuwkYWMtb3Ti6Y*T}bG?la1tkcX{aO579kll%lEM2C_>pDfR9d!6^t0z4M|k z=j`jODE-3w{Vwlg`-)vJmi65G9~R(mJE3>vr-pMxp^ASg4LYOeO+n!<4P6V1_0Qz= z)w2$93n5qU1QZJqsC>Vj#_075L1Zm=7akB5-MwO8PljOQlP%})haJbe3-_sN7j?cL zTC~66-Ne)I!~2Dz-79u?uh`KuSLST*>Mq>fUAQB%fjO<#5UGzzS)ZmEB2kX0fZB~4n9N55bs`A#&7X zs~g>ap#G=Q&(jPg{voOXu zuE%Wy)tf=+1$!Mht--+~5~1oSEhc_=M0BPQ<+#=3dZ!4>Mn{O~gWgT4z|j@S?1p~t zrX(T{)2x1&939{Gf*S+QhaDomw+}USk<|k`{@nLA>gQ&3xN5<;cZT$2F}=S7G19kS zo?;(-;@`18coQ@dO|$2I89AsAih=rlXL+dWZj1^`kCgS#Sgo&i^xO>NoZQD=koi>~ zTPG0u*p`7lmW(*j$LtU%*2k!2@26j~vs-;^pXy^>Jx?DOlE(M3t^vDxH=VYR?dV={ zSI=uQC;Qlr?!vp+$GT$m|22JVN6*E`#qnE^T77JX)yKL{Tjk%Nk9DC(^md9q<`I2t z|7rVJbi`~JrQ^N!gO%mch||02Mp;X%OM83}FuKSWU7E~6^mxa!dy#~RX+AG%;W?$} z-h^pj?|h#`$&B4QKSg?CcL@JD2!fr zqT|qISKz#)-%fe^J_P-0t>+q0>^0No^MNStQDP;$OJ8sC?nhb1^z zKL9^AU*ha$Ur*wv!no|qoT%8BkN!`6`A|sGZ>?zT9j-7#V|(wpt7Q7ly^2XR)RR!BiFFY?$Zszl7L`oby8K zU*xI$+Lv$^e?Xqa*NW5lvbW9jw&%>g7r+wiaH8HBei8k$vhNp&dApz`sqaB`p5FHp z1iZU0Z%HbD!%XknkMrO+3Vtom=y^ii_Z=lC&NHkoVTqbJy-SHE<_*X;EGFB#(emW! zU2XqytbMaT{f_r39m;vEV5GP2_Xt79ArK3)L9oi6$EBiuJH>TG0j?wNN9ZsFIegvm z+USboyWF)MD~=s*IQGVhqlX)gM+=YkZ4!b*m)^CoEI<^O9lcjEEYIad)}_ZEL)N7O zS^EaAp;DqazmH(PyXq(|0uREdZC^ROw?ET(xcu@|3>F)TbGOhme=^vg(+<( z5PlNjj)lprjdOeaki5O?(o5v|zju#^XP%vxd>LJqJm+=qP2)h@nR8KR!K@SBT^qR| zQrf@P>=`2U#aOb?^lsXR1`Yc5gL@as0c@cdz`UC{b`P+`{707eBeFB+X0SZ!-L;u4 zFYaIK?AZk!paEGr!|O}?&t0v1_t?dG@RH=t<}c@b+-!VNn!O8Cvms#LiwN(=MAk1(C}sPAlovzFy$i)?jZ*vW*&=C< zfU@KGfU3fJYnQ|S0I@j`U z`V}i|X=+D#YP2vF7ZQ=WXkl`DSG3jKumn?tch@b$C~%Q?*X_fkG|zOJ&a%?}NJ?eT z`6w9I>nOLjZJo{7;Hup_AB#tbs%@J+VuQVwwI1tNn9IGpHo3^I?{|<9!k6}3`ys5* zQJ9P#myKXYp>J=Y`vHs?(Gk2@=~(Incgdo6&j(63ajmidH6qqBWOoLGkC1-JrKO># zIN*E-+>q5esk|s&Np-2z5Bf`G*^eM7%RU$@8@W-um|_c&yv3Ra`o&pk-!EZHaeYvN z^%f3vtDT`7oQm{h;@nT+1;8E8_KXx~h{-tZC=bCR^H*yfFZ5KQ{Rrdv+q)hw!Z}6Q z=qxqMLd%e6wZHSaeam{XP%I{{)SjgXv_0o8#n6Cjl}*FY6}@|GPamLmR`S6di`)<9jpiPb5Sk{Yp*@4{|O$qy^o89&M1G+e(~x5*rp$!^J=TNtUjn+10}+;+I0 zADGU!;S#U_V}&iecu63yB(H3dwy1dVk}_>k-bMH=UR;@1Qe2>w73LLa%gTx?3bmr* zqGJ3kEYeDrlq|u|GOfHK4+;58%Hb9*(khA<6)ss?p%pDFDPB}uq2=e5md5g7@ z;>F+6%9kvt&@Rj?2$V0qP+MBCKr1LN=bbgJycn5TQXnrszp%Vq7HEO;in8Lx3$^m1 zC1n-bf|4b96ZOUZ_c$oxwe7ZffiUR+q9HR5(#iy!yE8iUng z-1(`gYaIa9^-a7RG_SEKJV0{3Hm@lX_BYk~SK_{1O)D_dtE#JU53X)yeeirOqrQgD zpV6viWKF77{wfPIa+Q|R>aVV9JS*(4Y4YQ)SuRqC42s09I~lnc1_DLCy59#v8DHJh z&|Du3*EOyJCr}Fx@J}Jc{g4MfKDcOQ=>=#)Jm$|3$EOM(x!{|$zr3>Ig5s&A`4zeO zOUud%7guP@r)QVvO)V~%x-hRa7dJ$;jGC;BhOCTB{24{L8H;i=%Cj=EXVg+V{GZ1k z`F~kP>k2mc|M9pBJ?nHt={U)m3O5684&2E!OwWT`09OKcFBitIe z4RCF6o8da)z6bY1xO?I5hkFoi7u?C^d>wucw7-Y@Gu&%%Z^FF;_jkAt;QkG#W5yi{ zmjE{&4wD=1+ex=v`HhHR+;z&oN%_3D$GstQaoWMDfO`&bLr>g$=jC?$?Kj*k4_Bg=D zgwu57CM>VQr;M1_n{EL{@EaY--_Hz|&#<+}gW*}wZ2?>*TrJ!hxEtZV4|hM@V{k9P zy$bhtxZ`jREL)6(I}>ge+yb~txLUY1a5utzAMSp*$KYOoyALN1EKggQpP%c`#CD&v z{WG%8$(rFmCp&va_RQ>a{F!CJ8h=q$*k8guLNaw~&YZLVpX8Z&&NnaT|K~ik8*kOJ z-N~I+n)YqXXa^-`r)?X^Lv;9gS~u4`(vl0QRoX(cx(PzNhE5w?MTQWQary$n|3 zIFVS_h@BgCHQb@4%>sEwApQc zhUc`~W+4q*4X#EB?pvpT!sJ4zd&>%DmAvgH7y4|x)22Hdrro>;p#N@A|;MeFx~YwR7^}!Fw@0NREeV%oZTG?DATDu5D8~8v9RgOkzD;<6g3DbGGjE;}<8s$- zs-LevBv@Uk+0Ani8p7H#|AmliGW)cwK8Od}hn!T`O_%r^_fT?sL>fR$cUt13Qh(F^ z5n2CMR;Uv^Dg>u{!P-N~Sw_+g_#yvAi10iWA3H-czW{bMa`T6I5z0xJl?z&v8xIrC zW&1X-gV7RZ(`M!F?#GW~S{j4@ z0FfhmALVa=EgaKdX7DcjIC9=%&WYm^u(2y> za|u2UT`_Wn!NQTCQr#}W*QsEV=KUEM7eWr7!>t&51~W$g6+$S+-WX%S$)`VHiKs#Q z@4?VMN3!CC9w`zvDdNb%oJPes2@%mjB_}E3X#F+;Az}!OI{p@)AYfd&WUjnrkG)Ik|O5lj|j#-%`+B^V~smO^GuJ&UeOarYu-;0 zTLfLlJASQ-e|T{5zljxZg0tfhodToPxk4DS9FMA0w<`zL>3B@RB-dZqzIQ3;bB$s1 z-L2p-*GBMg{8qtaSAcmRS8$~3RTlJwf+;SO1wE zC91actb#Vzf7n5vQ_z&n{dol)E}`EGNi3d@nNq($DmX>z_a_CjHD4Bl-UGKocf70=d}XkLe~BqLhYG$LQ!q-a zzoxS#*wP%5^I_3hjI{d=&D#(1HE<&x-HP*DgE_w)<1CDGWN_ubqxyrY{J$#bk;eI3 ztn-Ms2sWYV=P9M`+%s(Nxw!>1@Y` zN}-PiD|B?QLLU!S=#yAy60P}D1$|PXV+tnA+Wwb>13Z_bh{;i;2Ds)1@bg9s1 z3T8?T{-fX&&HH0q8{Um^cDds#r9uB-4K$lI#){UgD`>jJ0A^EAwPwR6d#h^ArcI3w zvNhWkOp*FI6!c5|5){mo`Z*QMmioCAoGtYmqTqa~U!sCVQa`tXrJBi0T6@7Sprze> z7{5a^&~4I_=+b3+j^Hh`}5@pN%H&@`-c>+HZF;A49SMqxrI=}j|t!DW^XY^@^sgh=6189 z%u$lB54$IUQmzn`$!^|~Hb+a8A|x=!NXk_AW~PnhnTjpV@8-sOGnF#f(u&*{k;^!# zafxQ~=9)3i08_i^!f)~!_|ZquMkB~>1Sz$VEXOp`M%@e5v{7GxhdydO`iyNQ0pF7_}bZgi&?Sz^R$<(jrsII^{)jGTo#9f!=1C+b=go_~495K2LQD%}5C7(RKOc))YD85n#?`5GwD;d0xq71v7!Ft9HU&-L@ z%$6KN5WG3=+D*=yc9R38-Q-+pH#x@GP0l)Fc$KUF1FZBfbWz!3o7ZUi=d^n2UqGyX zPTQwGiXZ*+BK(fqf^51+A9o#^hMuC2$EBX8f63F+Gq%Ih`d3pxb;j!`PVZ-4%|9^- zd%#f+FXN72eOX^RVIzwzpKvq1WfSh8S2=Mzyw^c9aR&nFAWYlIgv-*NpjVdm0=-f# z{fFSE?}pC$Qe9pZWZXtnp;L-8o7YTdm@1r=S=9{AI#1Ixsn{|ReKr_r`f{E6oIMT3 z&@T~L&n{*Br6N3O@-I-#2>~7zRuBw#GpPCJ zc+uaGjcSkCB2=FVXxn<_ggoK<&PrQMwv}m@&?`@?p|>opS)?vc`?l~)r&8zPx0=pl za6+;Pl+3oZD2>ADm&0IOjL>(`5Kj&=%M(J_3{jg;2zyBrJt1t7v7WUoX{5QH8Zg)I zq`7#)r|*#_5~k~lGu_iNUYPC~5kA#){^eMPp)FEjgAKO{JCp$0wl1HrT=>2THNsDx z&6NG5CQ`3V`w`)?vB4M7j-!q-d!8_hO^JC^2^AtqT zr?Anc39W3YeQd51*y>YfqvPolIJS(fUa9F5IYy;&fYK+jH;!l9NoPe*n*1m5nS7W( z@8W05NBEgE*~N8DG?CAN^d}I~ZP$H{strN z(PiV>#+;hJ2?;deMyOYCyD;BsmQ*<*La%hfM(N!wytF&$l~1L{?<2<$VPB zq-AkAN}tO0H6o`IMk-o-Rw2#>|Z~P_8gg25(5%=88V1`ENo#n(8Q2*n=C9Xcya9cKL++ z=~YgpCcJaW#0eA^dQ-Z5;DvCf3P1f3@NDB?ny=eZi%~WDh0o$tNsI&qG{N`?rk27P z)YShLh$#g3Lvs5$1h^d#q(KZw;(oW0(A3?l@VJQ@j3IQ%81(;u^6`WjFS;}F=7#{?D5NpZ6X3fcqYh*?Ti>p ziitAfK1N)^Qqp9^4n{14_?~nb@e4-W3E4dvGGZqpJgcynO(#|j&eVqFL9Us%;m46| zy9V*PK4clZnP)O;l&v!^>LZlqIAq%{qbjKo*VQ?a3vPxsYXo{p()9l#h27xB5f9ha zV-zE_S+9_K?OsX2-6kF`ug55y+N@C!Sx@Sf6j%NR_nLmnN*SZLZeq-4HXSz?yHF1I zyX35fIJ%R~&~9*3ZO%W~XkF}?c7uCxbACnYAu@uSesXfyr4wZYcT;P7cq*Z>qD|d~ z658;*6I;szARJh8f0-pYti|?0EAXcIjThhTa`Bz(qjtIYUW=ejvUQkjT@JbC&Y+#E zsg7Cs401KqaV}4a?OfY(%)W?0uBJM2IZWBPn(CM!G+|UCS5p&G{>Y4JS|V3d6Glsf^L!E|jA3^B ze#m5>#s_%pTurrS|AaxVrrM`J#2{Bw?K#{;WanzCede*HcI)o$n> z!Nt@S8;@XPSJ37Wd>p!B1~#XuY;3VO|>U0P9p|$8abHLs2C?9Vv0&tB97J@1Y?MJAvEZK^V!EK#^VPwK4UOr ze~d8~BkU7mjJcX>pNI+392 zya!HVHPt>%ampUdY5HJJ=fpS(Q_Qe9NfC4O?+V5~&G|1djy3KC&6$Wc6T@E7rUQqfurG9%=oApWk_9>Vw_1mvtiq!8#1yiMd zzgN&N_4|W@=~BO!6wH+R{ZYXwQolbbn5`vo4~AS#wZE(sd}XkLe~Br`)l~bdF$KAr zYJW{1b!Z=3;SN=PyKd8$8tAZYBoWCiU zB#rZ~f?jEy_Z0L=1{aZo5ROp0)=~AK36wH(w z{71nln)_PRpjb_{f2B0&AFP39QxsT+^rD12n$Qoe7 zF_J;9Tp9_f3_d`sI43g5l}p2w$>0x|ZAcb_T)8w7a}b0YoR1wQ&psR`r%;Eu$&MNl zVb1#w^9M*Bu?6&|F=8ZFE{!B13Rf--FB3-cB+c-BpFyr%8bh}+cppU>_7euVa%l|T z!5~*IjpSb<2;Lmy942Qdhsi<7VRBw_nC#CElOz0)g>DoN5wH`(^9o3?6GPUW=21H_ zwt^KWCbbhoLZ_z#^@5!k61qJvf(LeDNa*vZofr}(d(=)02~#|3Cx(Qn9<>ugLcd4t z#E>xEqjq9QnCVeFF(jPgv36oW4eZ45SUWKU%<-t57*eCzo+wnoP7Dd>c|Ib{(8EH( z`5v_s<01hIJZdKfxBc1D7Iug!sQ;d6GMt|xyRaxAy{3BieaZ4CHGtmi6U0*xkyxdE(D!?^%M472xKP8Jr`3% zfllzKK)l0z1nt22GXyh`AkIw~y9g0ZZXJrn&X9}e`orvU!-au(a~MRHjbF8YdUtuLq;=z>QlDFrLfnSZ~$N zV3HmDop1w5#?~FMp`YvA`kiEL*KHHHY-p0}#9fSXFjaGM%Tr8&GqkiH1C!|?Fyy6| zzBHXB>gB4;<=PlteLF`zj99zj#3Kg@yt)~~t8ZtDjCCV+46nYOV`YSo5xn|#rpkz9 zM)2y}IZj5TFoIX#PQQ#u6%pu5&IvNY&j?<9J15GBbVl&%+nFXKG8w_EZ)duUn8FBN zeLFK`L^dLvSK|tkjYHI=p=I!T*)_WgvAXX3ZhYbhErXZJt}PoxVg|2xZ9n7%iLPg; zwF=ZAfLf~%K+}inKF2Wp8cAc5GLnX4fyS1O5h*e8d;|>yoib>SL~L&2+{EJHFs4^a z6afbVxP&{C_8PA1!A1IZ)6@GKCFu#2Kcn!uKHqcV{@6@{dX zii;gRAQGBM7E&&7ORdjx7Z3M?9F=MnhLvuBm`%nFQTn3I=fn}60T3hdljro|L-k?$ ze26%~u1z0qLjdy&WASdT92JuuSGwIQJwp`;o8pFz5=DTsQcwe#s$3CswklVENr~V+ znWbUrokeKc)N#5L1dG{mWhI{Frk>LhAt>&qkBiBRMHiA;<{;5Iu?R@Wh#3RAUf^ck zOSw<7D-tamqG##y|rQ&2pfrvntUvEly~j1ErveQnZ0K zoNl$@i~%B9x!PA4G;P{!F$R5}Gz5c>juED8zCnR1)hL=LF zoK`nu_N=Kb!J1H2SiU53n%EvbZBPaUrmd_Cx2lc2rIlILC|kpe#=s^kq(Ks~!xsr5 zCMXcVrrI?!rn;&oSc@Qr)tZ$7`HYE#f|i?mTjMdd?MjF;s#;bd4E_0HvnB*l9LlwV zd>b3{Vvv@+LU=b?jm%xeEyps5k=Dk$zsBdf{^Xe7WF$HtFxF0XUQ+azPjhEi?l!J?$4J~|oWIL(?K0NR znKz|olX1=D+<9~7-fK*_+W$-Ecw5F$TZZoy=eF-2$+e{?=bkd83w7ZS1*I&PF zw=v8)Zlm#zc6~7N^P2Cr{4$^9Q-j8LufN*(o3_ii_xg{F7q#oZlR59fNx749jiHkt z8blYk`n%2vn;L_LYck%%dQW56<%^v8QJd8?>6=z*VEcU_G^()CkDfK%Snce*|-$A`fkI*je{R)79q_F>2o--oTvZ}s6AmVGz|t!{}3 zt6L&y^=S+YR-Z=D>eJ}-?6*9fHiqfDkM5XKd2ou+vUJuw=U&@N&)jpJdkvrM65Y7U zR;mBO`8V6L5w^2oAMrd8pTEJ|1iy`qL?$n#)5#16M}ZpPyd-D4?Lv=zL7V+X^Clyd z<+J@J%a4g#P{|Dwxsc!_I@`rrKH~~K%Nfk_S7iAXXSwlf<>BUnm8Z#bBl4`#<1(NO zOCH`)e-2obQ{=H1w3#;=iy=)-mVafIFGk1P-#@VEAa`DyaRsPWWSJ$P8sNtp-!H|{ zT}XaSpsdgGSA&wzsPN$3?VI6S`hnl2WMrlD7Wa@iI~BAUsB@5u`oW-B7^|fn&3Kc81#rxj1I40ZHb?GOIzxi_=L}*s&!hR z39n0Ss;zCs+f?Hw=NL(_rY_PTbYD=62U`V6abtM;ED?u6?W#ant8ZExY{6?|TbuDV z(+0e;v|2n2RM?2Dk}0$=7Z3SJQZc+J+5n3O6PagmQ(iS6Z)FYOiK>=hvrwNeZ-wE( zuZ~;E#G7jac(ZIxJqpI@M=M^pdUXUNWw53Ik0Nn&5(2J;p1}a$iz=&>@1f-|$j6^h zt>V?Ri}@Pc_=lBZPhqjj8>(8a3M%tK@fqhxjY=y6t;{4U&w45={s}I&km_(ty=)q% zvu$xxb!$jyS0f$_gAK$pP7)2MEoxmr8UR%BvcjqZ4_3vRs8G19xI915Tvan&7A_v{ zI_2>#j_!?kwd|_KNHtnC>I6@$MezP%yimA00u`F-BMpt3d~tAqZNE9(qM^~`E8{hV z>uT}h-q@2~gKOn9O{%DFRVc1*z)`a#m4Ysn?TZE!)h?Q^tTx#{WG9l(U-L!AXeSGU z5IB~jp0!IP8xc$vX>LYC5%nQk=&29w#VV|}zNsp#wFaw8t6IZ_%}v#z_-CR|SCb`8 zYf*KoNzdv2Ms#fU&bYSS@piC+(~G z9J*`|;)%+E=PhCG#$r5QkNOu?8LR&IQAf2K^annU->Tu+TYvul>fu{H;ED$p!x8il z7?YPW0rm;25ob#w#XAB18~sJSi#AGgLq;)@OK0?kdWvYPN{W!w|}!hEb%NTyzU zAp6i@$%KLn*kRB<@E9mYV7|wjdWpU(`;usM5NV)A4Q@q?&^=;}n}ups;ifu{*sXF% zWLuh6RognPxwS5+G*hy^fcIQT6I-^eI4FzUV^El zsj)SNm$g)JB*@qJ!0LdQR)xtM@%3mKOM}6jy2Vp zN+aklELSv;H5vczlqvs#)&3(E?I0<|qX!N`&r z`KlV~sG;KpqoCt^BZ@#bAI?grPj@aHR_6pA-zTvd0&Ia}UXTkKUN2k##|u%) z$M;X9fPn9vunyAlSO|w}izX)Vy%D!S*fNBzav4uo0mpYuFwFN&906T6!tvz-YSnPK zMX(@>`($QTTM|S}4n>c_~Qf@i54vXInadiBaM$H*mT(yYcTXZ)mI@)C; z=r)4RPQ$B%y7IUcj^dDRGw$qco;bh*Q89kJRwf-;Y{za#ev9WA!q(wefgi!AO-Luc z#gm5j?<36m;6eFV_6|6UAK#F4OS+6lv^Xq1eg$OF-443jzlQFyI6A&heH-X39%N{R zpNOO5drh{3j@P%=K{~oFILb*q_zjfQ41C`JVe7E$_dFsfpLLL~J}$xv{t*OLn6#VE z)U^2wsKZL@2DZXX+6TJ!xCkrw!AW$BGSLS?YaJHdXD88ZTC8b&XvsP(I)1mo3N!5; zbha$3(txeB;V04ga95feAg#k9AvBb2T?5`5DRxs|Oe@Hw`VD>+W<&469 zCb1l-*q3EA0x{b!~MaXoYb~ys#V-fIm*ee7-1#7EJXZai8SHxjiLrmp(^>IJf z9=9m_Qy$)&x89FMdF}&_m*-*NczJ#YJYTgJmiG#Sq01Mv^%tr$({3~ES8DQbx1M|TE3%?7@ z=K?H@#cGY)=Oy-ON$A4dJ#XRhz}!498>E#T^|MroFhK!@{%|_j6d7_TqL93)5cQ&0%5Mi<>ztOnYU< zVcLsZIjnTri#s_iybAJL@3_)!J}nGvt&tCdej9+ZG5%QJUb`E3K5zk)=IC%AFrSm) zNHP?641Nb}y{C)cV%rN`3i`Vh{a=8sHF5I)J21y9$}<%0>jU5`L7xhr{0$7b&A@3Y zKOdUrt5-3dfqtaMU52-){^YZL+|S3g`Gkcf&k|t1Qx@z5{pG-X>8h1K2yCtOQy%WU z=C@*`er|0O@J5txed}#2@D|{CN}dOSw*%j&@FT!{<|AE{=hI#Qw#IXQ`|S_FJ3;>- z>Vf*b1XQR} z6zMxudLgiebe^}f{8C`+-U;dJfqh7~z7^L2><6a3NWTp@8<^+I#18?-+rJB#pV=uA z>2B=+ur(j>TXJs#TlWZ9-aEka!H?q%`F#Z(&yUZ5lp_5ZWv_|A);$;I#~zZ{eCCJ! z_(rHJK|e?FD*|o?=I%i9zZBS-FNa~iyb^dL($7-#Exz)$vEZ}%~i-9-A^|$4~UEr6a%DW1f=lOt;*Qc!m zw(dcZ-)3Oz@FTDdIG+E*z}CG%(m!_+J=Rp}>*GFyr&YIvTf>psS}Y_40{In7$^s?D zY7Gxn7V}%E}iq!$ZRV=-T`@bS|(!v?; z;G8ZR>uINn=924aq8G_~Jf}$!Ym}#nk6*+6Z<4<*yR~S1nyj%E@zbQq`z5DIuol8j zx}kRR5^wyig7_7yxEG!dS^-t}GzONTtsC*E{?;aSpCfM3^OR2o&Yy36e-F(E-(2LX zrL2I{E~Ul3|43Ra>8kHDo^nSqP}>{`ttH3Pe4$Z&eK5XgE(ylps}vuWRC&2_{I$1} z2HhJV7x4kclkPIA4;WUrM&t)85!7*?hg4ruL}c~JpPaP5arxif-#g_qknyFHsruNZ zwIq(H!C&DF#Mc9CA?~?ghedUOwv?X$jr-!GSmiwBYmxDV21KT~ip8x7et|NcCUy&e zMpqUPORKfvmyR)Dz=Ef&d7t+4 Rtb + +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; +}

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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..023eae7b15aefd00c4f26cc9a7228eea3f6f1cc9 GIT binary patch literal 110598 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%0D{>jtydJ;+a9)T}%(P#qY?0lJb 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6a1cd5bc6abed853a2fbbef52a250b67cf3fe151 GIT binary patch literal 106274 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