Copy of old lessons from afs webpage
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# Example EPICS Makefile
|
||||
|
||||
# If you don't modify this file it will create
|
||||
# a program with the name of the current directory
|
||||
# from all C and C++ source files found and link
|
||||
# it to the EPICS client libraries.
|
||||
|
||||
# Where is EPICS base?
|
||||
EPICS = /usr/local/epics/base
|
||||
|
||||
# Where to install the program)?
|
||||
BINDIR = .
|
||||
#BINDIR = bin/$(EPICS_HOST_ARCH)
|
||||
|
||||
# What is the name of the program?
|
||||
# Add one line for each program if the program name
|
||||
# is not equal to the directory name
|
||||
PROGRAM += testPV
|
||||
#PROGRAM += pvexample
|
||||
|
||||
# List all sources of the program if not simply
|
||||
# all *.c *.cc *.C *.cxx *.cpp files in this
|
||||
# directory should be used.
|
||||
# Add one line for each source file.
|
||||
# If you build more than one PROGRAM, list
|
||||
# the sources separately for each program like
|
||||
# SRCS_<program> += <filename>
|
||||
SRCS_testPV += epicsPV.c testPV.c
|
||||
SRCS_pvexample += epicsPV.cc pvexample.cc
|
||||
|
||||
# list all include directories
|
||||
INCDIRS += $(EPICS)/include/os/Linux
|
||||
INCDIRS += $(EPICS)/include
|
||||
|
||||
# list all library directories
|
||||
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
|
||||
|
||||
# list all libraries (ca and Com are EPICS)
|
||||
LIBS += ca Com
|
||||
|
||||
#optimize:
|
||||
CFLAGS += -O3
|
||||
#debug:
|
||||
CFLAGS += -g
|
||||
|
||||
# don't touch the code below this line unless you know what you're doing.
|
||||
CPPFLAGS += $(INCDIRS:%=-I %)
|
||||
|
||||
CFLAGS += -MMD
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(USR_CFLAGS)
|
||||
|
||||
LDFLAGS += $(LIBDIRS:%=-L %)
|
||||
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
|
||||
LDFLAGS += $(LIBS:%=-l %)
|
||||
|
||||
ifeq ($(words $(PROGRAM)),0)
|
||||
PROGRAM = $(notdir $(PWD))
|
||||
endif
|
||||
|
||||
SRCS += $(SRCS_$(PROGRAM))
|
||||
ifeq ($(words $(SRCS)),0)
|
||||
SRCS += $(wildcard *.c)
|
||||
SRCS += $(wildcard *.cc)
|
||||
SRCS += $(wildcard *.C)
|
||||
SRCS += $(wildcard *.cxx)
|
||||
SRCS += $(wildcard *.cpp)
|
||||
endif
|
||||
|
||||
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
|
||||
|
||||
ifndef EPICS_HOST_ARCH
|
||||
$(error EPICS_HOST_ARCH variable is missing on your system!)
|
||||
endif
|
||||
|
||||
.PHONY:
|
||||
.PHONY: build clean realclean
|
||||
build:
|
||||
|
||||
clean:
|
||||
rm -rf O.*
|
||||
|
||||
realclean: clean
|
||||
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
|
||||
|
||||
O.%:
|
||||
mkdir $@
|
||||
|
||||
$(BINDIR):
|
||||
mkdir -p $@
|
||||
|
||||
ifeq ($(words $(PROGRAM)),1)
|
||||
|
||||
build: $(BINDIR)/$(PROGRAM)
|
||||
|
||||
ifneq ($(BINDIR),.)
|
||||
$(PROGRAM): $(BINDIR)/$(PROGRAM)
|
||||
endif
|
||||
|
||||
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
|
||||
rm -f $@
|
||||
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
|
||||
$(CXX) -o $@ $(LDFLAGS) $^
|
||||
|
||||
else
|
||||
|
||||
build:
|
||||
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
|
||||
|
||||
$(PROGRAM):
|
||||
make PROGRAM=$@
|
||||
|
||||
endif
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.c
|
||||
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.cc
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.C
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
-include O.$(EPICS_HOST_ARCH)/*.d
|
||||
@@ -0,0 +1,411 @@
|
||||
/* caLesson6.c
|
||||
by Dirk Zimoch, 2007
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* include EPICS headers */
|
||||
#include <cadef.h>
|
||||
#define epicsAlarmGLOBAL
|
||||
#include <alarm.h>
|
||||
#include <epicsEvent.h>
|
||||
#include <epicsMutex.h>
|
||||
|
||||
/* Strings describing the connection status of a channel.
|
||||
See also enum channel_state in /usr/local/epics/base/include/cadef.h
|
||||
*/
|
||||
const char *channel_state_str[4] = {
|
||||
"not found",
|
||||
"connection lost",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
/* Define a generic process variable (PV).
|
||||
Each PV contains static information in info and
|
||||
current value in data.
|
||||
See /usr/local/epics/base/include/db_access.h for fields
|
||||
of dbr_* structures.
|
||||
*/
|
||||
|
||||
typedef struct epicsPV epicsPV;
|
||||
typedef struct monitorCallback monitorCallback;
|
||||
|
||||
struct monitorCallback {
|
||||
monitorCallback *next;
|
||||
void *userdata;
|
||||
void (*callbackfunction) (void *userdata, epicsPV* pv);
|
||||
};
|
||||
|
||||
struct epicsPV {
|
||||
chid channel;
|
||||
int datatype;
|
||||
epicsEventId monitorEvent;
|
||||
epicsMutexId accessMutex;
|
||||
monitorCallback* callbacks;
|
||||
union {
|
||||
struct dbr_sts_string string_data;
|
||||
struct {
|
||||
struct dbr_ctrl_long long_info;
|
||||
struct dbr_sts_long long_data;
|
||||
};
|
||||
struct {
|
||||
struct dbr_ctrl_double double_info;
|
||||
struct dbr_sts_double double_data;
|
||||
};
|
||||
struct {
|
||||
struct dbr_ctrl_enum enum_info;
|
||||
struct dbr_sts_enum enum_data;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
|
||||
|
||||
int connectPVsFileLine(const char* file, int line, const char* name, ...)
|
||||
{
|
||||
va_list va;
|
||||
epicsPV pv;
|
||||
|
||||
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
|
||||
va_start(va, name);
|
||||
while (name)
|
||||
{
|
||||
if (findPV(name))
|
||||
{
|
||||
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
|
||||
file, line, name);
|
||||
continue;
|
||||
}
|
||||
pv = malloc(sizeof(epicsPV));
|
||||
if (!pv)
|
||||
{
|
||||
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
|
||||
file, line, name);
|
||||
return ECA_ALLOCMEM;
|
||||
}
|
||||
ca_create_channel(name, connectCallback, pv,
|
||||
CA_PRIORITY_DEFAULT, &pv->channel);
|
||||
name = va_arg(va, char*)
|
||||
}
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Print NAME = VALUE for PV of any type.
|
||||
Precision and units are taken from info.
|
||||
*/
|
||||
void printPV(const epicsPV* pv)
|
||||
{
|
||||
int status, severity;
|
||||
|
||||
epicsMutexLock(pv->accessMutex);
|
||||
if (ca_state(pv->channel) != cs_conn)
|
||||
{
|
||||
printf("%s <%s>\n",
|
||||
ca_name(pv->channel),
|
||||
channel_state_str[ca_state(pv->channel)]);
|
||||
epicsMutexUnlock(pv->accessMutex);
|
||||
return;
|
||||
}
|
||||
switch (pv->datatype)
|
||||
{
|
||||
case DBF_STRING:
|
||||
printf("%s = %s",
|
||||
ca_name(pv->channel),
|
||||
pv->string_data.value);
|
||||
status = pv->string_data.status;
|
||||
severity = pv->string_data.severity;
|
||||
break;
|
||||
case DBF_LONG:
|
||||
printf("%s = %.ld %s",
|
||||
ca_name(pv->channel),
|
||||
pv->long_data.value, pv->long_info.units);
|
||||
status = pv->long_data.status;
|
||||
severity = pv->long_data.severity;
|
||||
break;
|
||||
case DBF_DOUBLE:
|
||||
printf("%s = %.*f %s",
|
||||
ca_name(pv->channel),
|
||||
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
|
||||
status = pv->double_data.status;
|
||||
severity = pv->double_data.severity;
|
||||
break;
|
||||
case DBF_ENUM:
|
||||
if (pv->enum_data.value < pv->enum_info.no_str)
|
||||
printf("%s = %s",
|
||||
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
|
||||
else
|
||||
printf("%s = %d",
|
||||
ca_name(pv->channel), pv->enum_data.value);
|
||||
status = pv->enum_data.status;
|
||||
severity = pv->enum_data.severity;
|
||||
break;
|
||||
default:
|
||||
printf ("%s <unsupported data type>\n",
|
||||
ca_name(pv->channel));
|
||||
epicsMutexUnlock(accessMutex);
|
||||
return;
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
if (severity != NO_ALARM)
|
||||
{
|
||||
printf(" <%s %s>",
|
||||
epicsAlarmSeverityStrings[severity],
|
||||
epicsAlarmConditionStrings[status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int caget(epicsPV* pv)
|
||||
{
|
||||
int dbr_type;
|
||||
|
||||
if (ca_state((pv)->channel) != cs_conn)
|
||||
{
|
||||
printf ("caget %s: %s\n", PV_name(pv),
|
||||
channel_state_str[PV_state(pv)]);
|
||||
return ECA_DISCONN;
|
||||
}
|
||||
/* Allocate memory only once but read dynamic data every time */
|
||||
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
|
||||
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
|
||||
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Generic monitor event handler.
|
||||
See /usr/local/epics/base/include/cadef.h for the definition of
|
||||
struct event_handler_args.
|
||||
This handler copies the new value into the PV and writes a message.
|
||||
We get the address of the PV in the 'usr' field of 'args'
|
||||
because we give that as the 4th argument to ca_add_event (see below).
|
||||
|
||||
In the multi threaded model, the monitor callback runs in a separate
|
||||
thread. That means, the monitor function may be called at any time,
|
||||
even while we are just accessing the PV from the main program. Thus,
|
||||
we must protect all accesses to a PV with a mutext. Here, we use
|
||||
just one global mutex. The next lesson will introduce a more
|
||||
sophisticated solution.
|
||||
*/
|
||||
|
||||
static void monitor(struct event_handler_args args)
|
||||
{
|
||||
epicsPV* pv = args.usr;
|
||||
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
SEVCHK(args.status, "monitor");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lock PV to be thread safe */
|
||||
epicsMutexLock(pv->accessMutex);
|
||||
/* Copy the value to the 'data' field of the PV.
|
||||
The current data, its type and the number of elements (for arrays) is
|
||||
stored in several fields of 'args'.
|
||||
*/
|
||||
switch (args.type)
|
||||
{
|
||||
case DBR_STS_DOUBLE:
|
||||
{
|
||||
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
break;
|
||||
}
|
||||
case DBR_STS_ENUM:
|
||||
{
|
||||
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
printf ("%s unsupported data type\n", ca_name(args.chid));
|
||||
}
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
|
||||
/* Inform other threads about monitor */
|
||||
epicsEventSignal(monitorEvent);
|
||||
}
|
||||
|
||||
int main(int argc, char** args)
|
||||
{
|
||||
char recordname[28];
|
||||
double search_timeout = 5.0; /* seconds */
|
||||
double put_timeout = 1.0; /* seconds */
|
||||
double get_timeout = 1.0; /* seconds */
|
||||
|
||||
int status;
|
||||
CA_SYNC_GID gid; /* required for blocking put (see below) */
|
||||
|
||||
epicsDoublePV setvalue;
|
||||
epicsDoublePV readvalue;
|
||||
epicsEnumPV doneflag;
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <prefix>\n"
|
||||
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
|
||||
args[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Step1: Initialize channel access and search for all channels. */
|
||||
|
||||
/* Start EPICS multi-threaded */
|
||||
ca_context_create(ca_enable_preemptive_callback);
|
||||
|
||||
sprintf(recordname, "%.19s:SET", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:READ", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:DONE", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Setup an event for monitors */
|
||||
monitorEvent = epicsEventCreate(epicsEventEmpty);
|
||||
|
||||
/* Setup a mutex semaphore to make PV access thread-safe */
|
||||
accessMutex = epicsMutexCreate();
|
||||
|
||||
/* Step 2: Get available infos and setup monitor for DONE flag*/
|
||||
|
||||
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
|
||||
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
|
||||
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
|
||||
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
|
||||
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
|
||||
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
|
||||
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
|
||||
/* Create the "synchronous group id" (gid) used later for put. */
|
||||
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Step 3: Enter main loop */
|
||||
while (1)
|
||||
{
|
||||
char userinput[40];
|
||||
double newvalue;
|
||||
|
||||
/* Get current setting */
|
||||
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
|
||||
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
|
||||
printDoublePV(&setvalue);
|
||||
|
||||
/* Ask for new setting */
|
||||
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
|
||||
{
|
||||
printf("Enter new value (range %.*f~%.*f): ",
|
||||
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
|
||||
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No limits known */
|
||||
printf("Enter new value: ");
|
||||
}
|
||||
fflush(stdout);
|
||||
fgets(userinput, sizeof(userinput), stdin);
|
||||
if (sscanf(userinput, "%lf", &newvalue) != 1)
|
||||
{
|
||||
printf("Invalid input \"%s\". Need a number.\n", userinput);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set new value and wait to complete.
|
||||
|
||||
This is a very important timing issue!
|
||||
The records are build in a way that the DONE record
|
||||
is set to "ACTIVE" before a put to the SET record completes.
|
||||
Insider info: They are linked via FLNK and PP output links.
|
||||
|
||||
Timing:
|
||||
|
||||
ca_put (0)-(1)---(2)----(3)
|
||||
:
|
||||
ca_sg_put (0)-------(2)----(3)
|
||||
:
|
||||
"DONE" =====*=====* *========
|
||||
| |
|
||||
"ACTIVE" *=======*
|
||||
|
||||
Some time after the put (0), the device becomes active.
|
||||
|
||||
A normal put may return before (1), while (2), or after (3)
|
||||
activity. A following reading of the DONE record cannot
|
||||
distinguish between (1) and (3).
|
||||
|
||||
However, a synchonous put will not return before (2).
|
||||
Thus, we can wait until the DONE record becomes "DONE".
|
||||
If it returns late after activity finished (3), the DONE record
|
||||
is already "DONE" and we don't need to wait. But we can never be
|
||||
in the situation that the DONE record is not yet "ACTIVE" (1).
|
||||
|
||||
To implement a synchronous put, we use the "synchronous group"
|
||||
mechanism. ca_sg_block does not return until all outstanding
|
||||
ca_sg_* requests with the same gid have completed (or timeout).
|
||||
|
||||
Note that we put a bare double here, no DBR_CTRL_*.
|
||||
*/
|
||||
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
|
||||
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
|
||||
|
||||
/* Wait until activity is done.
|
||||
This uses the monitor on the DONE record.
|
||||
Remember that monitors are handled in the background, but only
|
||||
while certain ca functions are active, e.g. ca_pend_event(),
|
||||
ca_pend_io(), or ca_sg_block().
|
||||
*/
|
||||
|
||||
/* First actively read current value of DONE record. */
|
||||
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
|
||||
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
|
||||
printEnumPV(&doneflag);
|
||||
|
||||
/* When not already done, wait for monitor events */
|
||||
while (!doneflag.data.value)
|
||||
{
|
||||
/* wait for the next monitor event */
|
||||
epicsEventWait(monitorEvent);
|
||||
/* This really does nothing until the mext monitor comes.
|
||||
Then, monitorEvent is triggered from the monitor callback
|
||||
function (see above).
|
||||
*/
|
||||
}
|
||||
printEnumPV(&doneflag);
|
||||
}
|
||||
|
||||
/* Step 4: clean up */
|
||||
ca_sg_delete(gid);
|
||||
end:
|
||||
ca_context_destroy();
|
||||
epicsMutexDestroy(accessMutex);
|
||||
epicsEventDestroy(monitorEvent);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
/* caLesson6.c
|
||||
by Dirk Zimoch, 2007
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* include EPICS headers */
|
||||
#include <cadef.h>
|
||||
#define epicsAlarmGLOBAL
|
||||
#include <alarm.h>
|
||||
#include <epicsEvent.h>
|
||||
#include <epicsMutex.h>
|
||||
|
||||
/* Strings describing the connection status of a channel.
|
||||
See also enum channel_state in /usr/local/epics/base/include/cadef.h
|
||||
*/
|
||||
const char *channel_state_str[4] = {
|
||||
"not found",
|
||||
"connection lost",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
/* Define a generic process variable (PV).
|
||||
Each PV contains static information in info and
|
||||
current value in data.
|
||||
See /usr/local/epics/base/include/db_access.h for fields
|
||||
of dbr_* structures.
|
||||
*/
|
||||
|
||||
typedef struct epicsPV epicsPV;
|
||||
typedef struct monitorCallback monitorCallback;
|
||||
|
||||
struct monitorCallback {
|
||||
monitorCallback *next;
|
||||
void *userdata;
|
||||
void (*callbackfunction) (void *userdata, epicsPV* pv);
|
||||
};
|
||||
|
||||
struct epicsPV {
|
||||
chid channel;
|
||||
int datatype;
|
||||
epicsEventId monitorEvent;
|
||||
epicsMutexId accessMutex;
|
||||
monitorCallback* callbacks;
|
||||
union {
|
||||
struct dbr_sts_string string_data;
|
||||
struct {
|
||||
struct dbr_ctrl_long long_info;
|
||||
struct dbr_sts_long long_data;
|
||||
};
|
||||
struct {
|
||||
struct dbr_ctrl_double double_info;
|
||||
struct dbr_sts_double double_data;
|
||||
};
|
||||
struct {
|
||||
struct dbr_ctrl_enum enum_info;
|
||||
struct dbr_sts_enum enum_data;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
|
||||
|
||||
int connectPVsFileLine(const char* file, int line, const char* name, ...)
|
||||
{
|
||||
va_list va;
|
||||
epicsPV pv;
|
||||
|
||||
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
|
||||
va_start(va, name);
|
||||
while (name)
|
||||
{
|
||||
if (findPV(name))
|
||||
{
|
||||
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
|
||||
file, line, name);
|
||||
continue;
|
||||
}
|
||||
pv = malloc(sizeof(epicsPV));
|
||||
if (!pv)
|
||||
{
|
||||
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
|
||||
file, line, name);
|
||||
return ECA_ALLOCMEM;
|
||||
}
|
||||
ca_create_channel(name, connectCallback, pv,
|
||||
CA_PRIORITY_DEFAULT, &pv->channel);
|
||||
name = va_arg(va, char*)
|
||||
}
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Print NAME = VALUE for PV of any type.
|
||||
Precision and units are taken from info.
|
||||
*/
|
||||
void printPV(const epicsPV* pv)
|
||||
{
|
||||
int status, severity;
|
||||
|
||||
epicsMutexLock(pv->accessMutex);
|
||||
if (ca_state(pv->channel) != cs_conn)
|
||||
{
|
||||
printf("%s <%s>\n",
|
||||
ca_name(pv->channel),
|
||||
channel_state_str[ca_state(pv->channel)]);
|
||||
epicsMutexUnlock(pv->accessMutex);
|
||||
return;
|
||||
}
|
||||
switch (pv->datatype)
|
||||
{
|
||||
case DBF_STRING:
|
||||
printf("%s = %s",
|
||||
ca_name(pv->channel),
|
||||
pv->string_data.value);
|
||||
status = pv->string_data.status;
|
||||
severity = pv->string_data.severity;
|
||||
break;
|
||||
case DBF_LONG:
|
||||
printf("%s = %.ld %s",
|
||||
ca_name(pv->channel),
|
||||
pv->long_data.value, pv->long_info.units);
|
||||
status = pv->long_data.status;
|
||||
severity = pv->long_data.severity;
|
||||
break;
|
||||
case DBF_DOUBLE:
|
||||
printf("%s = %.*f %s",
|
||||
ca_name(pv->channel),
|
||||
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
|
||||
status = pv->double_data.status;
|
||||
severity = pv->double_data.severity;
|
||||
break;
|
||||
case DBF_ENUM:
|
||||
if (pv->enum_data.value < pv->enum_info.no_str)
|
||||
printf("%s = %s",
|
||||
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
|
||||
else
|
||||
printf("%s = %d",
|
||||
ca_name(pv->channel), pv->enum_data.value);
|
||||
status = pv->enum_data.status;
|
||||
severity = pv->enum_data.severity;
|
||||
break;
|
||||
default:
|
||||
printf ("%s <unsupported data type>\n",
|
||||
ca_name(pv->channel));
|
||||
epicsMutexUnlock(accessMutex);
|
||||
return;
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
if (severity != NO_ALARM)
|
||||
{
|
||||
printf(" <%s %s>",
|
||||
epicsAlarmSeverityStrings[severity],
|
||||
epicsAlarmConditionStrings[status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int caget(epicsPV* pv)
|
||||
{
|
||||
int dbr_type;
|
||||
|
||||
if (ca_state((pv)->channel) != cs_conn)
|
||||
{
|
||||
printf ("caget %s: %s\n", PV_name(pv),
|
||||
channel_state_str[PV_state(pv)]);
|
||||
return ECA_DISCONN;
|
||||
}
|
||||
/* Allocate memory only once but read dynamic data every time */
|
||||
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
|
||||
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
|
||||
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Generic monitor event handler.
|
||||
See /usr/local/epics/base/include/cadef.h for the definition of
|
||||
struct event_handler_args.
|
||||
This handler copies the new value into the PV and writes a message.
|
||||
We get the address of the PV in the 'usr' field of 'args'
|
||||
because we give that as the 4th argument to ca_add_event (see below).
|
||||
|
||||
In the multi threaded model, the monitor callback runs in a separate
|
||||
thread. That means, the monitor function may be called at any time,
|
||||
even while we are just accessing the PV from the main program. Thus,
|
||||
we must protect all accesses to a PV with a mutext. Here, we use
|
||||
just one global mutex. The next lesson will introduce a more
|
||||
sophisticated solution.
|
||||
*/
|
||||
|
||||
static void monitor(struct event_handler_args args)
|
||||
{
|
||||
epicsPV* pv = args.usr;
|
||||
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
SEVCHK(args.status, "monitor");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lock PV to be thread safe */
|
||||
epicsMutexLock(pv->accessMutex);
|
||||
/* Copy the value to the 'data' field of the PV.
|
||||
The current data, its type and the number of elements (for arrays) is
|
||||
stored in several fields of 'args'.
|
||||
*/
|
||||
switch (args.type)
|
||||
{
|
||||
case DBR_STS_DOUBLE:
|
||||
{
|
||||
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
break;
|
||||
}
|
||||
case DBR_STS_ENUM:
|
||||
{
|
||||
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
printf ("%s unsupported data type\n", ca_name(args.chid));
|
||||
}
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
|
||||
/* Inform other threads about monitor */
|
||||
epicsEventSignal(monitorEvent);
|
||||
}
|
||||
|
||||
int main(int argc, char** args)
|
||||
{
|
||||
char recordname[28];
|
||||
double search_timeout = 5.0; /* seconds */
|
||||
double put_timeout = 1.0; /* seconds */
|
||||
double get_timeout = 1.0; /* seconds */
|
||||
|
||||
int status;
|
||||
CA_SYNC_GID gid; /* required for blocking put (see below) */
|
||||
|
||||
epicsDoublePV setvalue;
|
||||
epicsDoublePV readvalue;
|
||||
epicsEnumPV doneflag;
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <prefix>\n"
|
||||
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
|
||||
args[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Step1: Initialize channel access and search for all channels. */
|
||||
|
||||
/* Start EPICS multi-threaded */
|
||||
ca_context_create(ca_enable_preemptive_callback);
|
||||
|
||||
sprintf(recordname, "%.19s:SET", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:READ", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:DONE", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Setup an event for monitors */
|
||||
monitorEvent = epicsEventCreate(epicsEventEmpty);
|
||||
|
||||
/* Setup a mutex semaphore to make PV access thread-safe */
|
||||
accessMutex = epicsMutexCreate();
|
||||
|
||||
/* Step 2: Get available infos and setup monitor for DONE flag*/
|
||||
|
||||
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
|
||||
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
|
||||
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
|
||||
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
|
||||
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
|
||||
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
|
||||
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
|
||||
/* Create the "synchronous group id" (gid) used later for put. */
|
||||
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Step 3: Enter main loop */
|
||||
while (1)
|
||||
{
|
||||
char userinput[40];
|
||||
double newvalue;
|
||||
|
||||
/* Get current setting */
|
||||
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
|
||||
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
|
||||
printDoublePV(&setvalue);
|
||||
|
||||
/* Ask for new setting */
|
||||
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
|
||||
{
|
||||
printf("Enter new value (range %.*f~%.*f): ",
|
||||
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
|
||||
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No limits known */
|
||||
printf("Enter new value: ");
|
||||
}
|
||||
fflush(stdout);
|
||||
fgets(userinput, sizeof(userinput), stdin);
|
||||
if (sscanf(userinput, "%lf", &newvalue) != 1)
|
||||
{
|
||||
printf("Invalid input \"%s\". Need a number.\n", userinput);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set new value and wait to complete.
|
||||
|
||||
This is a very important timing issue!
|
||||
The records are build in a way that the DONE record
|
||||
is set to "ACTIVE" before a put to the SET record completes.
|
||||
Insider info: They are linked via FLNK and PP output links.
|
||||
|
||||
Timing:
|
||||
|
||||
ca_put (0)-(1)---(2)----(3)
|
||||
:
|
||||
ca_sg_put (0)-------(2)----(3)
|
||||
:
|
||||
"DONE" =====*=====* *========
|
||||
| |
|
||||
"ACTIVE" *=======*
|
||||
|
||||
Some time after the put (0), the device becomes active.
|
||||
|
||||
A normal put may return before (1), while (2), or after (3)
|
||||
activity. A following reading of the DONE record cannot
|
||||
distinguish between (1) and (3).
|
||||
|
||||
However, a synchonous put will not return before (2).
|
||||
Thus, we can wait until the DONE record becomes "DONE".
|
||||
If it returns late after activity finished (3), the DONE record
|
||||
is already "DONE" and we don't need to wait. But we can never be
|
||||
in the situation that the DONE record is not yet "ACTIVE" (1).
|
||||
|
||||
To implement a synchronous put, we use the "synchronous group"
|
||||
mechanism. ca_sg_block does not return until all outstanding
|
||||
ca_sg_* requests with the same gid have completed (or timeout).
|
||||
|
||||
Note that we put a bare double here, no DBR_CTRL_*.
|
||||
*/
|
||||
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
|
||||
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
|
||||
|
||||
/* Wait until activity is done.
|
||||
This uses the monitor on the DONE record.
|
||||
Remember that monitors are handled in the background, but only
|
||||
while certain ca functions are active, e.g. ca_pend_event(),
|
||||
ca_pend_io(), or ca_sg_block().
|
||||
*/
|
||||
|
||||
/* First actively read current value of DONE record. */
|
||||
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
|
||||
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
|
||||
printEnumPV(&doneflag);
|
||||
|
||||
/* When not already done, wait for monitor events */
|
||||
while (!doneflag.data.value)
|
||||
{
|
||||
/* wait for the next monitor event */
|
||||
epicsEventWait(monitorEvent);
|
||||
/* This really does nothing until the mext monitor comes.
|
||||
Then, monitorEvent is triggered from the monitor callback
|
||||
function (see above).
|
||||
*/
|
||||
}
|
||||
printEnumPV(&doneflag);
|
||||
}
|
||||
|
||||
/* Step 4: clean up */
|
||||
ca_sg_delete(gid);
|
||||
end:
|
||||
ca_context_destroy();
|
||||
epicsMutexDestroy(accessMutex);
|
||||
epicsEventDestroy(monitorEvent);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <stdexcept>
|
||||
class epicsExceptionOutOfMemory : public runtime_error
|
||||
{
|
||||
public epicsExceptionOutOfMemory(char* where)
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define epicsAlarmGLOBAL
|
||||
#include "epicsPV.h"
|
||||
|
||||
static int contextCreated = 0;
|
||||
static unsigned long pendingConnects = 0;
|
||||
static unsigned long pendingGets = 0;
|
||||
static unsigned long pendingPuts = 0;
|
||||
epicsTimeStamp PVinvalidTimestamp = {0,0};
|
||||
|
||||
const char* PVdataTypeStrings[9] = {
|
||||
"STRING",
|
||||
"SHORT",
|
||||
"FLOAT",
|
||||
"ENUM",
|
||||
"CHAR",
|
||||
"LONG",
|
||||
"DOUBLE",
|
||||
"NO_ACCESS",
|
||||
"UNDEFINED"
|
||||
};
|
||||
|
||||
const char* PVconnectionStateStrings [4] = {
|
||||
"never connected",
|
||||
"disconnected",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
epicsPV PVcreateWithTypeAndCallback(const char* channelName,caDatatype preferredDatatype,
|
||||
PVconnectionCallback cb, void* userarg)
|
||||
{
|
||||
epicsPV pv;
|
||||
|
||||
if (!contextCreated)
|
||||
{
|
||||
ca_context_create(ca_disable_preemptive_callback);
|
||||
atexit(ca_context_destroy);
|
||||
contextCreated = 1;
|
||||
}
|
||||
pv = malloc(sizeof (struct epicsPV_s));
|
||||
pv->channel = NULL;
|
||||
pv->data = NULL;
|
||||
pv->info = NULL;
|
||||
pv->units = "";
|
||||
pv->precision = 0;
|
||||
pv->usedDatatype = caTypeNative;
|
||||
pv->requestedDatatype = caTypeNative;
|
||||
pv->status = ECA_DISCONN;
|
||||
pv->connectionCallback = cb;
|
||||
pv->userarg = userarg;
|
||||
if (channelName) PVlinkWithType(pv, channelName, preferredDatatype);
|
||||
return pv;
|
||||
}
|
||||
|
||||
void PVdestroy(epicsPV pv)
|
||||
{
|
||||
if (!pv) return;
|
||||
PVunlink(pv);
|
||||
free(pv);
|
||||
}
|
||||
|
||||
static void connectCallback(struct connection_handler_args args)
|
||||
{
|
||||
epicsPV pv = ca_puser(args.chid);
|
||||
int up = (args.op == CA_OP_CONN_UP);
|
||||
|
||||
if (up)
|
||||
{
|
||||
if (pv->usedDatatype == caTypeNative)
|
||||
pendingConnects --;
|
||||
if (pv->requestedDatatype != caTypeNative)
|
||||
pv->usedDatatype = pv->requestedDatatype;
|
||||
else
|
||||
pv->usedDatatype = ca_field_type(pv->channel);
|
||||
|
||||
pv->data = realloc(pv->data, dbr_size_n(dbf_type_to_DBR_TIME(pv->usedDatatype),
|
||||
ca_element_count(pv->channel)));
|
||||
if (!pv->data)
|
||||
{
|
||||
pv->status = ECA_ALLOCMEM;
|
||||
return;
|
||||
}
|
||||
pv->units = "";
|
||||
pv->precision = 0;
|
||||
if (pv->usedDatatype != DBF_STRING)
|
||||
{
|
||||
pv->info = realloc(pv->info, dbr_size_n(dbf_type_to_DBR_CTRL(pv->usedDatatype),1));
|
||||
if (!pv->info)
|
||||
{
|
||||
pv->status = ECA_ALLOCMEM;
|
||||
return;
|
||||
}
|
||||
pv->status = ca_get(dbf_type_to_DBR_CTRL(pv->usedDatatype), pv->channel, pv->info);
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
pv->units = pv->info_CHAR->units;
|
||||
break;
|
||||
case DBF_SHORT:
|
||||
pv->units = pv->info_SHORT->units;
|
||||
break;
|
||||
case DBF_LONG:
|
||||
pv->units = pv->info_LONG->units;
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
pv->units = pv->info_FLOAT->units;
|
||||
pv->precision = pv->info_FLOAT->precision;
|
||||
break;
|
||||
case DBF_DOUBLE:
|
||||
pv->units = pv->info_DOUBLE->units;
|
||||
pv->precision = pv->info_DOUBLE->precision;
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
free(pv->info);
|
||||
pv->info = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_DISCONN;
|
||||
}
|
||||
if (pv->connectionCallback)
|
||||
{
|
||||
pv->connectionCallback(pv, up, pv->userarg);
|
||||
}
|
||||
}
|
||||
|
||||
int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!pv) return ECA_INTERNAL;
|
||||
pv->requestedDatatype = preferredDatatype;
|
||||
if (pv->channel) PVunlink(pv);
|
||||
if (!channelName) return ECA_NORMAL;
|
||||
status = ca_create_channel(channelName,
|
||||
connectCallback, pv,
|
||||
CA_PRIORITY_DEFAULT,
|
||||
&pv->channel);
|
||||
if (status == ECA_NORMAL)
|
||||
{
|
||||
pendingConnects ++;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void PVunlink(epicsPV pv)
|
||||
{
|
||||
if (pv && pv->channel)
|
||||
{
|
||||
if (pv->connectionCallback)
|
||||
{
|
||||
pv->connectionCallback(pv, 0, pv->userarg);
|
||||
}
|
||||
ca_clear_channel(pv->channel);
|
||||
pv->channel = NULL;
|
||||
free(pv->data);
|
||||
pv->data = NULL;
|
||||
free(pv->info);
|
||||
pv->info = NULL;
|
||||
pv->units = "";
|
||||
pv->precision = 0;
|
||||
pv->status = ECA_DISCONN;
|
||||
if (pv->usedDatatype != caTypeNative)
|
||||
{
|
||||
pendingConnects --;
|
||||
}
|
||||
pv->usedDatatype = caTypeNative;
|
||||
}
|
||||
}
|
||||
|
||||
double PVwaitForConnect(epicsPV pv, double timeoutSec)
|
||||
{
|
||||
ca_poll();
|
||||
double wait = 0.001;
|
||||
while (timeoutSec != 0.0 &&
|
||||
(pv ? ca_state(pv->channel) == cs_never_conn : pendingConnects != 0))
|
||||
{
|
||||
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
|
||||
ca_pend_event(wait);
|
||||
if (timeoutSec > 0.0) timeoutSec -= wait;
|
||||
wait *= 1.2;
|
||||
if (wait > 0.5) wait = 0.5;
|
||||
}
|
||||
return timeoutSec;
|
||||
}
|
||||
|
||||
double PVtoDoubleElement(epicsPV pv, unsigned long index)
|
||||
{
|
||||
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
|
||||
{
|
||||
return atof("NAN");
|
||||
}
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
return (&pv->CHAR->value)[index];
|
||||
case DBF_SHORT:
|
||||
return (&pv->SHORT->value)[index];
|
||||
case DBF_LONG:
|
||||
return (&pv->LONG->value)[index];
|
||||
case DBF_FLOAT:
|
||||
return (&pv->FLOAT->value)[index];
|
||||
case DBF_DOUBLE:
|
||||
return (&pv->DOUBLE->value)[index];
|
||||
case DBF_ENUM:
|
||||
return (&pv->ENUM->value)[index];
|
||||
case DBF_STRING:
|
||||
return atof((&pv->STRING->value)[index]);
|
||||
default:
|
||||
return atof("NAN");
|
||||
}
|
||||
}
|
||||
|
||||
long PVtoLongElement(epicsPV pv, unsigned long index)
|
||||
{
|
||||
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
return (&pv->CHAR->value)[index];
|
||||
case DBF_SHORT:
|
||||
return (&pv->SHORT->value)[index];
|
||||
case DBF_LONG:
|
||||
return (&pv->LONG->value)[index];
|
||||
case DBF_FLOAT:
|
||||
return (&pv->FLOAT->value)[index];
|
||||
case DBF_DOUBLE:
|
||||
return (&pv->DOUBLE->value)[index];
|
||||
case DBF_ENUM:
|
||||
return (&pv->ENUM->value)[index];
|
||||
case DBF_STRING:
|
||||
return atoi((&pv->STRING->value)[index]);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index)
|
||||
{
|
||||
double val;
|
||||
long ival;
|
||||
int prec;
|
||||
|
||||
if (!pv || !pv->data) return "<not connected>";
|
||||
if (index > ca_element_count(pv->channel)) return "<out of bounds>";
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
ival = (&pv->CHAR->value)[index];
|
||||
goto printint;
|
||||
case DBF_SHORT:
|
||||
ival = (&pv->SHORT->value)[index];
|
||||
goto printint;
|
||||
case DBF_LONG:
|
||||
ival = (&pv->LONG->value)[index];
|
||||
printint:
|
||||
if (flags & PV_WITHUNITS && pv->units[0])
|
||||
sprintf(pv->stringrep, "%ld %s", ival, pv->units);
|
||||
else
|
||||
sprintf(pv->stringrep, "%ld", ival);
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
val = (&pv->FLOAT->value)[index];
|
||||
goto printdouble;
|
||||
case DBF_DOUBLE:
|
||||
val = (&pv->DOUBLE->value)[index];
|
||||
printdouble:
|
||||
prec = pv->precision;
|
||||
if (prec > 17) prec = -17;
|
||||
if (prec < -17) prec = -17;
|
||||
if (flags & PV_WITHUNITS && pv->units[0])
|
||||
{
|
||||
if (prec >= 0)
|
||||
sprintf(pv->stringrep, "%.*f %s", prec, val, pv->units);
|
||||
else
|
||||
sprintf(pv->stringrep, "%.*g %s", -prec, val, pv->units);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prec >= 0)
|
||||
sprintf(pv->stringrep, "%.*f", prec, val);
|
||||
else
|
||||
sprintf(pv->stringrep, "%.*g", -prec, val);
|
||||
}
|
||||
break;
|
||||
case DBF_ENUM:
|
||||
ival = (&pv->ENUM->value)[index];
|
||||
if (ival < pv->info_ENUM->no_str)
|
||||
return pv->info_ENUM->strs[ival];
|
||||
sprintf(pv->stringrep, "%ld", ival);
|
||||
break;
|
||||
case DBF_STRING:
|
||||
return (&pv->STRING->value)[index];
|
||||
default:
|
||||
return "<not accessible>";
|
||||
}
|
||||
return pv->stringrep;
|
||||
}
|
||||
|
||||
static void getCallback(struct event_handler_args args)
|
||||
{
|
||||
epicsPV pv = args.usr;
|
||||
|
||||
if (pv->status == ECA_IOINPROGRESS)
|
||||
{
|
||||
pendingGets--;
|
||||
}
|
||||
pv->status = args.status;
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
fprintf(stderr, "getCallback %s: %s\n",
|
||||
ca_name(pv->channel), ca_message(args.status));
|
||||
return;
|
||||
}
|
||||
memcpy(pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
if (pv->STRING->severity > lastEpicsAlarmSev)
|
||||
{
|
||||
pv->STRING->severity = epicsSevNone;
|
||||
}
|
||||
if (pv->STRING->status > lastEpicsAlarmCond)
|
||||
{
|
||||
pv->STRING->status = epicsAlarmNone;
|
||||
}
|
||||
}
|
||||
|
||||
int cagetList(double timeoutSec, epicsPV pv1, ...)
|
||||
{
|
||||
va_list ap;
|
||||
epicsPV pv;
|
||||
unsigned long faults = 0;
|
||||
double wait = 0.001;
|
||||
double connectTimeout = 2.0;
|
||||
|
||||
/* Prepate get requests */
|
||||
va_start(ap, pv1);
|
||||
pv = pv1;
|
||||
while (pv)
|
||||
{
|
||||
if (!pv->channel)
|
||||
{
|
||||
/* not assigned */
|
||||
pv->status = ECA_BADCHID;
|
||||
faults ++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ca_state(pv->channel) == cs_never_conn)
|
||||
{
|
||||
/* try to connect for the first time */
|
||||
connectTimeout = PVwaitForConnect(pv, connectTimeout);
|
||||
}
|
||||
if (ca_state(pv->channel) == cs_conn)
|
||||
{
|
||||
pv->status = ca_array_get_callback(
|
||||
dbf_type_to_DBR_TIME(pv->usedDatatype),
|
||||
ca_element_count(pv->channel),
|
||||
pv->channel,
|
||||
getCallback, pv);
|
||||
if (pv->status != ECA_NORMAL)
|
||||
{
|
||||
/* io error */
|
||||
fprintf(stderr, "%s can't do get: %s\n",
|
||||
ca_name(pv->channel), ca_message(pv->status));
|
||||
faults ++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_IOINPROGRESS;
|
||||
pendingGets++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_DISCONN;
|
||||
faults++;
|
||||
}
|
||||
}
|
||||
pv = va_arg(ap, epicsPV);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* Wait for values */
|
||||
ca_poll();
|
||||
while (timeoutSec != 0.0 && pendingGets != 0)
|
||||
{
|
||||
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
|
||||
ca_pend_event(wait);
|
||||
if (timeoutSec > 0.0) timeoutSec -= wait;
|
||||
wait *= 1.2;
|
||||
if (wait > 0.5) wait = 0.5;
|
||||
}
|
||||
|
||||
/* Check results */
|
||||
va_start(ap, pv1);
|
||||
pv = pv1;
|
||||
while (pv)
|
||||
{
|
||||
if (pv->status == ECA_IOINPROGRESS)
|
||||
{
|
||||
pv->status = ECA_TIMEOUT;
|
||||
faults++;
|
||||
pendingGets--;
|
||||
}
|
||||
pv = va_arg(ap, epicsPV);
|
||||
}
|
||||
va_end(ap);
|
||||
return faults;
|
||||
}
|
||||
|
||||
static void putCallback(struct event_handler_args args)
|
||||
{
|
||||
epicsPV pv = args.usr;
|
||||
|
||||
printf("completing caput %s\n", ca_name(pv->channel));
|
||||
if (pv->status == ECA_IOINPROGRESS)
|
||||
{
|
||||
pendingPuts--;
|
||||
}
|
||||
pv->status = args.status;
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
fprintf(stderr, "putCallback %s: %s\n",
|
||||
ca_name(pv->channel), ca_message(args.status));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int caputDoubleList(double timeoutSec, int flags, epicsPV pv1, double value1, ...)
|
||||
{
|
||||
va_list ap;
|
||||
epicsPV pv;
|
||||
double value;
|
||||
unsigned long faults = 0;
|
||||
double wait = 0.001;
|
||||
double connectTimeout = 2.0;
|
||||
|
||||
/* Prepate put requests */
|
||||
va_start(ap, value1);
|
||||
pv = pv1;
|
||||
value = value1;
|
||||
while (pv)
|
||||
{
|
||||
if (!pv->channel)
|
||||
{
|
||||
/* not assigned */
|
||||
pv->status = ECA_BADCHID;
|
||||
faults ++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ca_state(pv->channel) == cs_never_conn)
|
||||
{
|
||||
/* try to connect for the first time */
|
||||
connectTimeout = PVwaitForConnect(pv, connectTimeout);
|
||||
}
|
||||
if (ca_state(pv->channel) == cs_conn)
|
||||
{
|
||||
pv->status = ca_put_callback(
|
||||
DBR_DOUBLE,
|
||||
pv->channel,
|
||||
&value,
|
||||
putCallback, pv);
|
||||
if (pv->status != ECA_NORMAL)
|
||||
{
|
||||
/* io error */
|
||||
fprintf(stderr, "%s can't do put: %s\n",
|
||||
ca_name(pv->channel), ca_message(pv->status));
|
||||
faults ++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_IOINPROGRESS;
|
||||
pendingPuts++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_DISCONN;
|
||||
faults++;
|
||||
}
|
||||
}
|
||||
pv = va_arg(ap, epicsPV);
|
||||
value = va_arg(ap, double);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
if (timeoutSec == 0) return faults;
|
||||
|
||||
/* Wait for acknowledge */
|
||||
ca_poll();
|
||||
while (timeoutSec != 0.0 && pendingPuts != 0)
|
||||
{
|
||||
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
|
||||
ca_pend_event(wait);
|
||||
if (timeoutSec > 0.0) timeoutSec -= wait;
|
||||
wait *= 1.2;
|
||||
if (wait > 0.5) wait = 0.5;
|
||||
}
|
||||
|
||||
/* Check results */
|
||||
if (!pendingPuts) return faults;
|
||||
va_start(ap, value1);
|
||||
pv = pv1;
|
||||
while (pv)
|
||||
{
|
||||
if (pv->status == ECA_IOINPROGRESS)
|
||||
{
|
||||
pv->status = ECA_TIMEOUT;
|
||||
faults++;
|
||||
pendingPuts--;
|
||||
}
|
||||
pv = va_arg(ap, epicsPV);
|
||||
va_arg(ap, double);
|
||||
}
|
||||
va_end(ap);
|
||||
return faults;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
epicsPV& epicsPV::put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec)
|
||||
{
|
||||
if (!channel)
|
||||
throw runtime_error("epicsPV::put: PV not linked to a channel");
|
||||
switch (connectionState())
|
||||
{
|
||||
case cs_conn:
|
||||
break;
|
||||
case cs_never_conn:
|
||||
timeoutSec = waitForConnect(timeoutSec, this);
|
||||
if (connectionState() == cs_conn) break;
|
||||
default:
|
||||
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" "+connectionStateStr());
|
||||
}
|
||||
if (!hasWriteAccess())
|
||||
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" not writable");
|
||||
cout << "putting " << name() << ", elements: " << elements << ", datatype: " << datatype << " ";
|
||||
switch (datatype)
|
||||
{
|
||||
case caTypeDouble:
|
||||
cout << *(const double*)value;
|
||||
break;
|
||||
case caTypeLong:
|
||||
cout << *(const long*)value;
|
||||
break;
|
||||
case caTypeString:
|
||||
cout << (const char*)value;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
cout << endl;
|
||||
SEVCHK(ca_array_put(datatype, elements, channel, value), "epicsPV::put");
|
||||
ca_pend_io(timeoutSec);
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,178 @@
|
||||
#ifndef epicsPV_h
|
||||
#define epicsPV_h
|
||||
|
||||
#include <cadef.h>
|
||||
#include <caerr.h>
|
||||
#include <alarm.h>
|
||||
|
||||
/* How epicsPVs work:
|
||||
* A PV is a container for a remote named value provided via a so
|
||||
* called EPICS "channel". It is "linked" to the channel in PVcreate()
|
||||
* or later with PVlink().
|
||||
* Because the original value is remote, the PV can only contain
|
||||
* a copy of the value.
|
||||
* With the caget() call, one or more PVs are updated from the remote value.
|
||||
* After that, the updated value can be read as long, double or string.
|
||||
* With the caput() call, the remote value is updated from the PV.
|
||||
*/
|
||||
|
||||
#ifndef PV_DEFAULT_GET_TIMEOUT_SEC
|
||||
#define PV_DEFAULT_GET_TIMEOUT_SEC 2.0
|
||||
#endif
|
||||
|
||||
#ifndef PV_DEFAULT_PUT_TIMEOUT_SEC
|
||||
#define PV_DEFAULT_PUT_TIMEOUT_SEC 10.0
|
||||
#endif
|
||||
|
||||
typedef struct epicsPV_s* epicsPV;
|
||||
|
||||
typedef void (*PVconnectionCallback) (epicsPV pv, int connectionUp, void* userarg);
|
||||
|
||||
/* Channel Access datatypes (plus caTypeNative as default value)
|
||||
* depending on the datatype of a PV, exporting the value
|
||||
* to double, long or const char* may give different results
|
||||
*/
|
||||
typedef enum {
|
||||
caTypeString,
|
||||
caTypeShort,
|
||||
caTypeFloat,
|
||||
caTypeEnum,
|
||||
caTypeChar,
|
||||
caTypeLong,
|
||||
caTypeDouble,
|
||||
caTypeNoAccess,
|
||||
caTypeNative
|
||||
} caDatatype;
|
||||
|
||||
/* You don't need to access anything in epicsPV_priv.h directly
|
||||
*/
|
||||
#include "epicsPV_priv.h"
|
||||
|
||||
/* create PV and optionally link it to an EPICS channel
|
||||
* normally, you call createPV with channelName but without
|
||||
* preferredDatatype to use the native datatype of the channel
|
||||
* you may read back datatype after connection
|
||||
* if channelName is NULL you must call PVlink later
|
||||
* a callback can be installed to track connection state changes
|
||||
* connectionUp is 1 when the PV connected, 0 when it disconnected
|
||||
* userarg is an arbitrary pointer that will be provided with the callback
|
||||
*/
|
||||
#define PVcreate(channelName) PVcreateWithTypeAndCallback(channelName, caTypeNative, NULL, NULL)
|
||||
#define PVcreateWithType(channelName, type) PVcreateWithTypeAndCallback(channelName, type, NULL, NULL)
|
||||
#define PVcreateWithCallback(channelName, cb, userarg) PVcreateWithTypeAndCallback(name, caTypeNative, cb, userarg)
|
||||
epicsPV PVcreateWithTypeAndCallback(const char* channelName, caDatatype preferredDatatype,
|
||||
PVconnectionCallback cb, void* userarg);
|
||||
|
||||
/* destroy PV and free all resources
|
||||
*/
|
||||
void PVdestroy(epicsPV pv);
|
||||
|
||||
/* explititely (re-)link PV to a (different) EPICS channel
|
||||
* an unlink PV from a channel
|
||||
* it is normally done implicitely by PVcreate and PVdestroy
|
||||
* note: linking does not yet send a search request to the
|
||||
* network until PVwaitForConnect is called (probably implicitely by caget, caput, camonitor)
|
||||
*/
|
||||
#define linkPV(pv, channelName)linkPVwithType(pv, channelName, caTypeNative)
|
||||
int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype);
|
||||
void PVunlink(epicsPV pv);
|
||||
|
||||
/* wait until one or all PVs are connected
|
||||
* timeoutSec = 0.0 just sends connect requests but does not wait
|
||||
* timeoutSec < 0.0 means wait forever
|
||||
* returns remaining seconds
|
||||
* calling this function is optional because it will be called
|
||||
* implicitely before the first get or put on an unconnected PV
|
||||
* using the get or put timeout
|
||||
*/
|
||||
double PVwaitForConnect(epicsPV pv, double timeoutSec);
|
||||
#define PVwaitForConnectAll(timeoutSec) PVwaitForConnect(NULL, timeoutSec)
|
||||
|
||||
/* return channel name (what you provided to PVlink or PVcreate)
|
||||
*/
|
||||
#define PVname(pv) ((pv)->channel?ca_name((pv)->channel):"")
|
||||
|
||||
/* return error status of PV
|
||||
* see $(EPICS_BASE)/include/caerr.h for error codes
|
||||
*/
|
||||
#define PVerrorStatus(pv) ((pv)->status)
|
||||
#define PVerrorStatusString(pv) (ca_message((pv)->status))
|
||||
|
||||
/* return connection state
|
||||
* see $(EPICS_BASE)/include/cadef.h for valid values
|
||||
*/
|
||||
#define PVconnectionState(pv) ((pv)->channel?ca_state((pv)->channel):cs_never_conn)
|
||||
#define PVconnectionStateString(pv) PVconnectionStateStrings[PVconnectionState(pv)]
|
||||
|
||||
/* The following functions return information which is available
|
||||
* as soon as the PV is connected.
|
||||
*/
|
||||
|
||||
/* return currently used data type
|
||||
*/
|
||||
#define PVdatatype(pv) ((pv)->usedDatatype)
|
||||
#define PVdatatypeString(pv) PVdataTypeStrings[PVdatatype(pv)]
|
||||
|
||||
/* return number of elements for array data
|
||||
*/
|
||||
#define PVnumberOfElements(pv) ((pv)->channel?ca_element_count((pv)->channel):0)
|
||||
|
||||
/* return units and precision (.EGU and .PREC fields)
|
||||
* if PV does not have units or precision it returns "" and/or 0
|
||||
*/
|
||||
#define PVunits(pv) ((pv)->units)
|
||||
#define PVprecision(pv) ((pv)->precision)
|
||||
|
||||
/* return access rights (might change at run-time)
|
||||
*/
|
||||
#define PVhasReadAccess(pv) ((pv)->channel?ca_read_access((pv)->channel):0)
|
||||
#define PVhasWriteAccess(pv) ((pv)->channel?ca_write_access((pv)->channel):0)
|
||||
|
||||
/* get value from remote server
|
||||
* do caget with as many PVs as possible in parallel to increase performance
|
||||
*/
|
||||
#define caget(pvs...) cagetList(PV_DEFAULT_GET_TIMEOUT_SEC, pvs, NULL)
|
||||
#define cagetWithTimeout(timeoutSec, pvs...) cagetList(timeoutSec, pvs, NULL)
|
||||
|
||||
/* The following functions return information which is available
|
||||
* after a successful get() or a monitor.
|
||||
*/
|
||||
|
||||
/* return alarm severity and status (.SEVR and .STAT fields)
|
||||
* see $(EPICS_BASE)/include/alarm.h for valid values
|
||||
* not conencted PV returns epicsSevInvalid/epicsAlarmNone
|
||||
*/
|
||||
#define PValarmSeverity(pv) ((pv)->STRING?(pv)->STRING->severity:epicsSevInvalid)
|
||||
#define PValarmSeverityString(pv) epicsAlarmSeverityStrings[PValarmSeverity(pv)]
|
||||
|
||||
#define PValarmStatus(pv) ((pv)->STRING?(pv)->STRING->status:epicsAlarmUDF)
|
||||
#define PValarmStatusString(pv) epicsAlarmConditionStrings[PValarmStatus(pv)]
|
||||
|
||||
/* return time of record processing (.TIME field) as (epicsTimeStamp*)
|
||||
* see $(EPICS_BASE)/include/epicsTime.h for epicsTimeStamp
|
||||
*/
|
||||
extern epicsTimeStamp PVinvalidTimestamp;
|
||||
#define PVtimestamp(pv) &((pv)->STRING?(pv)->STRING->stamp:PVinvalidTimestamp)
|
||||
|
||||
/* return value of pv as long double or string
|
||||
* for arrays use PVto*Element to access each element
|
||||
*/
|
||||
long PVtoLongElement(epicsPV pv, unsigned long index);
|
||||
#define PVtoLong(pv) PVtoLongElement(pv,0)
|
||||
double PVtoDoubleElement(epicsPV pv, unsigned long index);
|
||||
#define PVtoDouble(pv) PVtoDoubleElement(pv,0)
|
||||
|
||||
/* flags for toString() */
|
||||
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index);
|
||||
#define PVtoStringWithUnits(pv) PVtoStringElement(pv,PV_WITHUNITS,0)
|
||||
#define PVtoString(pv) PVtoStringElement(pv,0,0)
|
||||
|
||||
#define caputDouble(pv1, value1...) caputDoubleList(0.0, pv1, value1)
|
||||
#define caputWaitDouble(timeoutSec, pv1, val1, ...) caputDoubleList(timeoutSec, pv1, value1)
|
||||
int caputDoubleList(double timeoutSec, int flags, epicsPV pv1, double val1, ...);
|
||||
|
||||
#define caputLong(pv1, val1, ...)
|
||||
#define caputString(pv1, val1, ...)
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,560 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define epicsAlarmGLOBAL
|
||||
#include "epicsPV.h"
|
||||
|
||||
static int contextCreated = 0;
|
||||
static unsigned long pendingConnects = 0;
|
||||
static unsigned long pendingGets = 0;
|
||||
|
||||
const char* PVdataTypeStrings[9] = {
|
||||
"STRING",
|
||||
"SHORT",
|
||||
"FLOAT",
|
||||
"ENUM",
|
||||
"CHAR",
|
||||
"LONG",
|
||||
"DOUBLE",
|
||||
"NO_ACCESS",
|
||||
"UNDEFINED"
|
||||
};
|
||||
|
||||
const char* PVconnectionStateStrings [4] = {
|
||||
"never connected",
|
||||
"disconnected",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
struct epicsPV_s {
|
||||
chid channel;
|
||||
void* data;
|
||||
void* info;
|
||||
caDatatype usedDatatype;
|
||||
caDatatype requestedDatatype;
|
||||
int status;
|
||||
char stringrep[40];
|
||||
PVconnectionCallback connectionCallback;
|
||||
void* userarg;
|
||||
};
|
||||
|
||||
epicsPV PVcreateWithTypeAndConnectionCallback(const char* channelName,caDatatype preferredDatatype,
|
||||
PVconnectionCallback cb, void* userarg)
|
||||
{
|
||||
epicsPV pv;
|
||||
|
||||
if (!contextCreated)
|
||||
{
|
||||
ca_context_create(ca_disable_preemptive_callback);
|
||||
atexit(ca_context_destroy);
|
||||
contextCreated = 1;
|
||||
}
|
||||
pv = malloc(sizeof (struct epicsPV_s));
|
||||
pv->channel = NULL;
|
||||
pv->data = NULL;
|
||||
pv->info = NULL;
|
||||
pv->usedDatatype = caTypeNative;
|
||||
pv->requestedDatatype = caTypeNative;
|
||||
pv->status = ECA_DISCONN;
|
||||
pv->connectionCallback = cb;
|
||||
pv->userarg = userarg;
|
||||
if (channelName) PVlinkWithType(pv, channelName, preferredDatatype);
|
||||
return pv;
|
||||
}
|
||||
|
||||
void PVdestroy(epicsPV pv)
|
||||
{
|
||||
if (!pv) return;
|
||||
PVunlink(pv);
|
||||
free(pv);
|
||||
}
|
||||
|
||||
static void connectCallback(struct connection_handler_args args)
|
||||
{
|
||||
epicsPV pv = ca_puser(args.chid);
|
||||
int up = (args.op == CA_OP_CONN_UP);
|
||||
|
||||
if (up)
|
||||
{
|
||||
if (pv->usedDatatype == caTypeNative)
|
||||
pendingConnects --;
|
||||
if (pv->requestedDatatype != caTypeNative)
|
||||
pv->usedDatatype = pv->requestedDatatype;
|
||||
else
|
||||
pv->usedDatatype = ca_field_type(pv->channel);
|
||||
|
||||
pv->data = realloc(pv->data, dbr_size_n(dbf_type_to_DBR_TIME(pv->usedDatatype),
|
||||
ca_element_count(pv->channel)));
|
||||
if (!pv->data)
|
||||
{
|
||||
pv->status = ECA_ALLOCMEM;
|
||||
return;
|
||||
}
|
||||
if (pv->usedDatatype != DBF_STRING)
|
||||
{
|
||||
pv->info = realloc(pv->info, dbr_size_n(dbf_type_to_DBR_CTRL(pv->usedDatatype),1));
|
||||
if (!pv->info)
|
||||
{
|
||||
pv->status = ECA_ALLOCMEM;
|
||||
return;
|
||||
}
|
||||
pv->status = ca_get(dbf_type_to_DBR_CTRL(pv->usedDatatype), pv->channel, pv->info);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(pv->info);
|
||||
pv->info = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_DISCONN;
|
||||
}
|
||||
if (pv->connectionCallback)
|
||||
{
|
||||
pv->connectionCallback(pv, up, pv->userarg);
|
||||
}
|
||||
}
|
||||
|
||||
int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype)
|
||||
{
|
||||
int status;
|
||||
|
||||
if (!pv) return ECA_INTERNAL;
|
||||
pv->requestedDatatype = preferredDatatype;
|
||||
if (pv->channel) PVunlink(pv);
|
||||
if (!channelName) return ECA_NORMAL;
|
||||
status = ca_create_channel(channelName,
|
||||
connectCallback, pv,
|
||||
CA_PRIORITY_DEFAULT,
|
||||
&pv->channel);
|
||||
if (status == ECA_NORMAL)
|
||||
{
|
||||
pendingConnects ++;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void PVunlink(epicsPV pv)
|
||||
{
|
||||
if (pv && pv->channel)
|
||||
{
|
||||
if (pv->connectionCallback)
|
||||
{
|
||||
pv->connectionCallback(pv, 0, pv->userarg);
|
||||
}
|
||||
ca_clear_channel(pv->channel);
|
||||
pv->channel = NULL;
|
||||
free(pv->data);
|
||||
pv->data = NULL;
|
||||
free(pv->info);
|
||||
pv->info = NULL;
|
||||
pv->status = ECA_DISCONN;
|
||||
if (pv->usedDatatype != caTypeNative)
|
||||
{
|
||||
pendingConnects --;
|
||||
}
|
||||
pv->usedDatatype = caTypeNative;
|
||||
}
|
||||
}
|
||||
|
||||
double PVwaitForConnect(epicsPV pv, double timeoutSec)
|
||||
{
|
||||
ca_poll();
|
||||
double wait = 0.001;
|
||||
while (timeoutSec != 0.0 &&
|
||||
(pv ? ca_state(pv->channel) == cs_never_conn : pendingConnects != 0))
|
||||
{
|
||||
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
|
||||
ca_pend_event(wait);
|
||||
if (timeoutSec > 0.0) timeoutSec -= wait;
|
||||
wait *= 1.2;
|
||||
if (wait > 0.5) wait = 0.5;
|
||||
}
|
||||
return timeoutSec;
|
||||
}
|
||||
|
||||
const char* PVname(epicsPV pv)
|
||||
{
|
||||
return pv && pv->channel ? ca_name(pv->channel) : "";
|
||||
}
|
||||
|
||||
int PVerrorStatus(epicsPV pv)
|
||||
{
|
||||
return pv ? pv->status : ECA_BADCHID;
|
||||
}
|
||||
|
||||
enum channel_state PVconnectionState(epicsPV pv)
|
||||
{
|
||||
return pv && pv->channel ? ca_state(pv->channel) : cs_never_conn;
|
||||
}
|
||||
|
||||
caDatatype PVdatatype(epicsPV pv)
|
||||
{
|
||||
return pv ? pv->usedDatatype : caTypeNative;
|
||||
}
|
||||
|
||||
unsigned long PVelementCount(epicsPV pv)
|
||||
{
|
||||
return pv && pv->channel ? ca_element_count(pv->channel) : 0;
|
||||
}
|
||||
|
||||
const char* PVunits(epicsPV pv)
|
||||
{
|
||||
if (!pv->info) return "";
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
return ((struct dbr_ctrl_char*)(pv->info))->units;
|
||||
case DBF_SHORT:
|
||||
return ((struct dbr_ctrl_short*)(pv->info))->units;
|
||||
case DBF_LONG:
|
||||
return ((struct dbr_ctrl_long*)(pv->info))->units;
|
||||
case DBF_FLOAT:
|
||||
return ((struct dbr_ctrl_float*)(pv->info))->units;
|
||||
case DBF_DOUBLE:
|
||||
return ((struct dbr_ctrl_double*)(pv->info))->units;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
int PVprecision(epicsPV pv)
|
||||
{
|
||||
if (!pv->info) return 0;
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_FLOAT:
|
||||
return ((struct dbr_ctrl_float*)(pv->info))->precision;
|
||||
case DBF_DOUBLE:
|
||||
return ((struct dbr_ctrl_double*)(pv->info))->precision;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int PVhasReadAccess(epicsPV pv)
|
||||
{
|
||||
return pv && pv->channel ? ca_read_access(pv->channel) : 0;
|
||||
}
|
||||
|
||||
int PVhasWriteAccess(epicsPV pv)
|
||||
{
|
||||
return pv && pv->channel ? ca_write_access(pv->channel) : 0;
|
||||
}
|
||||
|
||||
epicsAlarmSeverity PValarmSeverity(epicsPV pv)
|
||||
{
|
||||
if (pv && pv->data)
|
||||
{
|
||||
epicsUInt16 sevr = ((struct dbr_time_string*)(pv->data))->severity;
|
||||
if (sevr <= lastEpicsAlarmSev) return sevr;
|
||||
}
|
||||
return epicsSevInvalid;
|
||||
}
|
||||
|
||||
epicsAlarmCondition PValarmStatus(epicsPV pv)
|
||||
{
|
||||
if (pv && pv->data)
|
||||
{
|
||||
epicsUInt16 stat = ((struct dbr_time_string*)(pv->data))->status;
|
||||
if (stat <= lastEpicsAlarmCond) return stat;
|
||||
}
|
||||
return epicsAlarmUDF;
|
||||
}
|
||||
|
||||
const epicsTimeStamp* PVtimestamp(epicsPV pv)
|
||||
{
|
||||
static const epicsTimeStamp zerotime = {0,0};
|
||||
if (pv && pv->data)
|
||||
{
|
||||
return &((struct dbr_time_string*)(pv->data))->stamp;
|
||||
}
|
||||
return &zerotime;
|
||||
}
|
||||
|
||||
double PVtoDoubleElement(epicsPV pv, unsigned long index)
|
||||
{
|
||||
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
|
||||
{
|
||||
return atof("NAN");
|
||||
}
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
return (&((struct dbr_time_char*)(pv->data))->value)[index];
|
||||
case DBF_SHORT:
|
||||
return (&((struct dbr_time_short*)(pv->data))->value)[index];
|
||||
case DBF_LONG:
|
||||
return (&((struct dbr_time_long*)(pv->data))->value)[index];
|
||||
case DBF_FLOAT:
|
||||
return (&((struct dbr_time_float*)(pv->data))->value)[index];
|
||||
case DBF_DOUBLE:
|
||||
return (&((struct dbr_time_double*)(pv->data))->value)[index];
|
||||
case DBF_ENUM:
|
||||
return (&((struct dbr_time_enum*)(pv->data))->value)[index];
|
||||
case DBF_STRING:
|
||||
return atof((&((struct dbr_time_string*)(pv->data))->value)[index]);
|
||||
default:
|
||||
return atof("NAN");
|
||||
}
|
||||
}
|
||||
|
||||
long PVtoLongElement(epicsPV pv, unsigned long index)
|
||||
{
|
||||
if (!pv || !pv->data || index >= ca_element_count(pv->channel))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
return (&((struct dbr_time_char*)(pv->data))->value)[index];
|
||||
case DBF_SHORT:
|
||||
return (&((struct dbr_time_short*)(pv->data))->value)[index];
|
||||
case DBF_LONG:
|
||||
return (&((struct dbr_time_long*)(pv->data))->value)[index];
|
||||
case DBF_FLOAT:
|
||||
return (&((struct dbr_time_float*)(pv->data))->value)[index];
|
||||
case DBF_DOUBLE:
|
||||
return (&((struct dbr_time_double*)(pv->data))->value)[index];
|
||||
case DBF_ENUM:
|
||||
return (&((struct dbr_time_enum*)(pv->data))->value)[index];
|
||||
case DBF_STRING:
|
||||
return atoi((&((struct dbr_time_string*)(pv->data))->value)[index]);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index)
|
||||
{
|
||||
int prec;
|
||||
double val;
|
||||
long ival;
|
||||
struct dbr_ctrl_enum* enuminfo;
|
||||
const char* units;
|
||||
|
||||
if (!pv || !pv->data) return "<not connected>";
|
||||
if (index > ca_element_count(pv->channel)) return "<out of bounds>";
|
||||
switch (pv->usedDatatype)
|
||||
{
|
||||
case DBF_CHAR:
|
||||
ival = (&((struct dbr_time_char*)(pv->data))->value)[index];
|
||||
units = ((struct dbr_ctrl_char*)(pv->info))->units;
|
||||
goto printint;
|
||||
case DBF_SHORT:
|
||||
ival = (&((struct dbr_time_short*)(pv->data))->value)[index];
|
||||
units = ((struct dbr_ctrl_short*)(pv->info))->units;
|
||||
goto printint;
|
||||
case DBF_LONG:
|
||||
ival = (&((struct dbr_time_long*)(pv->data))->value)[index];
|
||||
units = ((struct dbr_ctrl_long*)(pv->info))->units;
|
||||
printint:
|
||||
if (flags & PV_WITHUNITS && units[0])
|
||||
sprintf(pv->stringrep, "%ld %s", ival, units);
|
||||
else
|
||||
sprintf(pv->stringrep, "%ld", ival);
|
||||
break;
|
||||
case DBF_FLOAT:
|
||||
val = (&((struct dbr_time_float*)(pv->data))->value)[index];
|
||||
prec = ((struct dbr_ctrl_float*)(pv->info))->precision;
|
||||
units = ((struct dbr_ctrl_float*)(pv->info))->units;
|
||||
goto printdouble;
|
||||
case DBF_DOUBLE:
|
||||
val = (&((struct dbr_time_double*)(pv->data))->value)[index];
|
||||
prec = ((struct dbr_ctrl_double*)(pv->info))->precision;
|
||||
units = ((struct dbr_ctrl_double*)(pv->info))->units;
|
||||
printdouble:
|
||||
if (prec > 17) prec = -17;
|
||||
if (prec < -17) prec = -17;
|
||||
if (flags & PV_WITHUNITS && units[0])
|
||||
{
|
||||
if (prec >= 0)
|
||||
sprintf(pv->stringrep, "%.*f %s", prec, val, units);
|
||||
else
|
||||
sprintf(pv->stringrep, "%.*g %s", -prec, val, units);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prec >= 0)
|
||||
sprintf(pv->stringrep, "%.*f", prec, val);
|
||||
else
|
||||
sprintf(pv->stringrep, "%.*g", -prec, val);
|
||||
}
|
||||
break;
|
||||
case DBF_STRING:
|
||||
return (&((struct dbr_time_string*)(pv->data))->value)[index];
|
||||
case DBF_ENUM:
|
||||
ival = (&((struct dbr_time_enum*)(pv->data))->value)[index];
|
||||
enuminfo = (struct dbr_ctrl_enum*)(pv->info);
|
||||
if (ival < enuminfo->no_str)
|
||||
return enuminfo->strs[ival];
|
||||
sprintf(pv->stringrep, "%ld", ival);
|
||||
break;
|
||||
default:
|
||||
return "<not accessible>";
|
||||
}
|
||||
return pv->stringrep;
|
||||
}
|
||||
|
||||
static void getCallback(struct event_handler_args args)
|
||||
{
|
||||
epicsPV pv = args.usr;
|
||||
|
||||
printf("updating %s %ld elements\n", ca_name(pv->channel), args.count);
|
||||
if (pv->status == ECA_IOINPROGRESS)
|
||||
{
|
||||
pendingGets--;
|
||||
}
|
||||
pv->status = args.status;
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
fprintf(stderr, "getCallback %s: %s\n",
|
||||
ca_name(pv->channel), ca_message(args.status));
|
||||
return;
|
||||
}
|
||||
if (args.chid != pv->channel)
|
||||
{
|
||||
fprintf(stderr, "INTERNAL ERROR in updateValueCallback %s: got unexpected chid\n",
|
||||
ca_name(pv->channel));
|
||||
return;
|
||||
}
|
||||
if (args.type != dbf_type_to_DBR_TIME(pv->usedDatatype))
|
||||
{
|
||||
fprintf(stderr, "INTERNAL ERROR in updateValueCallback %s: got unexpected type\n",
|
||||
ca_name(pv->channel));
|
||||
return;
|
||||
}
|
||||
memcpy(pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
}
|
||||
|
||||
int cagetList(epicsPV pv1, ...)
|
||||
{
|
||||
va_list ap;
|
||||
epicsPV pv;
|
||||
unsigned long faults = 0;
|
||||
double timeoutSec = 2.0;
|
||||
double wait = 0.001;
|
||||
|
||||
/* Prepate get requests */
|
||||
va_start(ap, pv1);
|
||||
pv = pv1;
|
||||
while (pv)
|
||||
{
|
||||
if (!pv->channel)
|
||||
{
|
||||
/* not assigned */
|
||||
pv->status = ECA_BADCHID;
|
||||
faults ++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ca_state(pv->channel) == cs_never_conn)
|
||||
{
|
||||
/* try to connect for the first time */
|
||||
timeoutSec = PVwaitForConnect(pv, timeoutSec);
|
||||
}
|
||||
if (ca_state(pv->channel) == cs_conn)
|
||||
{
|
||||
pv->status = ca_array_get_callback(
|
||||
dbf_type_to_DBR_TIME(pv->usedDatatype),
|
||||
ca_element_count(pv->channel),
|
||||
pv->channel,
|
||||
getCallback, pv);
|
||||
if (pv->status != ECA_NORMAL)
|
||||
{
|
||||
/* io error */
|
||||
fprintf(stderr, "%s can't do get: %s\n",
|
||||
ca_name(pv->channel), ca_message(pv->status));
|
||||
faults ++;
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_IOINPROGRESS;
|
||||
pendingGets++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pv->status = ECA_DISCONN;
|
||||
faults++;
|
||||
}
|
||||
}
|
||||
pv = va_arg(ap, epicsPV);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* Wait for values */
|
||||
timeoutSec = 2.0;
|
||||
ca_poll();
|
||||
while (timeoutSec != 0.0 && pendingGets != 0)
|
||||
{
|
||||
if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec;
|
||||
printf("waiting for %g seconds\n", wait);
|
||||
ca_pend_event(wait);
|
||||
if (timeoutSec > 0.0) timeoutSec -= wait;
|
||||
wait *= 1.2;
|
||||
if (wait > 0.5) wait = 0.5;
|
||||
}
|
||||
|
||||
/* Check results */
|
||||
va_start(ap, pv1);
|
||||
pv = pv1;
|
||||
while (pv)
|
||||
{
|
||||
if (pv->status == ECA_IOINPROGRESS)
|
||||
{
|
||||
pv->status = ECA_TIMEOUT;
|
||||
faults++;
|
||||
pendingGets--;
|
||||
}
|
||||
pv = va_arg(ap, epicsPV);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
printf ("pendingGets = %ld, faults = %ld\n",
|
||||
pendingGets, faults);
|
||||
return faults;
|
||||
}
|
||||
|
||||
/*
|
||||
epicsPV& epicsPV::put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec)
|
||||
{
|
||||
if (!channel)
|
||||
throw runtime_error("epicsPV::put: PV not linked to a channel");
|
||||
switch (connectionState())
|
||||
{
|
||||
case cs_conn:
|
||||
break;
|
||||
case cs_never_conn:
|
||||
timeoutSec = waitForConnect(timeoutSec, this);
|
||||
if (connectionState() == cs_conn) break;
|
||||
default:
|
||||
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" "+connectionStateStr());
|
||||
}
|
||||
if (!hasWriteAccess())
|
||||
throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" not writable");
|
||||
cout << "putting " << name() << ", elements: " << elements << ", datatype: " << datatype << " ";
|
||||
switch (datatype)
|
||||
{
|
||||
case caTypeDouble:
|
||||
cout << *(const double*)value;
|
||||
break;
|
||||
case caTypeLong:
|
||||
cout << *(const long*)value;
|
||||
break;
|
||||
case caTypeString:
|
||||
cout << (const char*)value;
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
cout << endl;
|
||||
SEVCHK(ca_array_put(datatype, elements, channel, value), "epicsPV::put");
|
||||
ca_pend_io(timeoutSec);
|
||||
return *this;
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,38 @@
|
||||
struct epicsPV_s {
|
||||
chid channel;
|
||||
union {
|
||||
void* data;
|
||||
struct dbr_time_char* CHAR;
|
||||
struct dbr_time_short* SHORT;
|
||||
struct dbr_time_long* LONG;
|
||||
struct dbr_time_float* FLOAT;
|
||||
struct dbr_time_double* DOUBLE;
|
||||
struct dbr_time_enum* ENUM;
|
||||
struct dbr_time_string* STRING;
|
||||
};
|
||||
union {
|
||||
void* info;
|
||||
struct dbr_ctrl_char* info_CHAR;
|
||||
struct dbr_ctrl_short* info_SHORT;
|
||||
struct dbr_ctrl_long* info_LONG;
|
||||
struct dbr_ctrl_float* info_FLOAT;
|
||||
struct dbr_ctrl_double* info_DOUBLE;
|
||||
struct dbr_ctrl_enum* info_ENUM;
|
||||
};
|
||||
const char* units;
|
||||
int precision;
|
||||
caDatatype usedDatatype;
|
||||
caDatatype requestedDatatype;
|
||||
int status;
|
||||
char stringrep[40];
|
||||
PVconnectionCallback connectionCallback;
|
||||
void* userarg;
|
||||
};
|
||||
|
||||
/* don't use cagetList directly, use caget */
|
||||
int cagetList(double timeoutSec, epicsPV pv1, ...);
|
||||
|
||||
extern const char* PVdataTypeStrings[9];
|
||||
extern const char* PVconnectionStateStrings [4];
|
||||
|
||||
#define PV_WITHUNITS 1
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
#include "epicsPV.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main (int argc, const char** argv)
|
||||
{
|
||||
try {
|
||||
epicsPV pv(argv[1]);
|
||||
pv.get();
|
||||
cout << pv.name() << " = " << pv <<
|
||||
" SEVR=" << pv.alarmSeverity() <<
|
||||
" STAT=" << pv.alarmStatus() << endl;
|
||||
if (argc == 3)
|
||||
{
|
||||
pv.put(argv[2]);
|
||||
pv.get();
|
||||
cout << pv.name() << " = " << pv <<
|
||||
" SEVR=" << pv.alarmSeverity() <<
|
||||
" STAT=" << pv.alarmStatus() << endl;
|
||||
}
|
||||
}
|
||||
catch (exception& e)
|
||||
{
|
||||
cout << endl << "Error: " << e.what() << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
#include "epicsPV.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void printPV(epicsPV pv)
|
||||
{
|
||||
char timebuf[32];
|
||||
|
||||
epicsTimeToStrftime (timebuf, sizeof(timebuf),
|
||||
"%d.%m.%Y %H:%M:%S.%03f", PVtimestamp(pv));
|
||||
printf("%s %s = %s %s %s [%s]\n",
|
||||
timebuf,
|
||||
PVname(pv), PVtoStringWithUnits(pv),
|
||||
PValarmSeverityString(pv),
|
||||
PValarmStatusString(pv),
|
||||
PVerrorStatusString(pv));
|
||||
}
|
||||
|
||||
int main (int argc, const char** argv)
|
||||
{
|
||||
epicsPV current = PVcreate("ARIDI-PCT:CURRENT");
|
||||
epicsPV gap = PVcreate("X10SA-ID-GAP:SET");
|
||||
caget(current, gap);
|
||||
printPV(current);
|
||||
printPV(gap);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user