Copy of old lessons from afs webpage

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