Copy of old lessons from afs webpage
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# Example EPICS Makefile
|
||||
|
||||
# If you don't modify this file it will create
|
||||
# a program with the name of the current directory
|
||||
# from all C and C++ source files found and link
|
||||
# it to the EPICS client libraries.
|
||||
|
||||
# Where is EPICS base?
|
||||
EPICS = /usr/local/epics/base
|
||||
|
||||
# Where to install the program)?
|
||||
BINDIR = .
|
||||
#BINDIR = bin/$(EPICS_HOST_ARCH)
|
||||
|
||||
# What is the name of the program?
|
||||
# Add one line for each program if the program name
|
||||
# is not equal to the directory name
|
||||
PROGRAM +=
|
||||
|
||||
# List all sources of the program if not simply
|
||||
# all *.c *.cc *.C *.cxx *.cpp files in this
|
||||
# directory should be used.
|
||||
# Add one line for each source file.
|
||||
# If you build more than one PROGRAM, list
|
||||
# the sources separately for each program like
|
||||
# SRCS_<program> += <filename>
|
||||
SRCS +=
|
||||
|
||||
# list all include directories
|
||||
INCDIRS += $(EPICS)/include/os/Linux
|
||||
INCDIRS += $(EPICS)/include
|
||||
|
||||
# list all library directories
|
||||
LIBDIRS += $(EPICS)/lib/$(EPICS_HOST_ARCH)
|
||||
|
||||
# list all libraries (ca and Com are EPICS)
|
||||
LIBS += ca Com
|
||||
|
||||
#optimize:
|
||||
CFLAGS += -O3
|
||||
#debug:
|
||||
CFLAGS += -g
|
||||
|
||||
# don't touch the code below this line unless you know what you're doing.
|
||||
CPPFLAGS += $(INCDIRS:%=-I %)
|
||||
|
||||
CFLAGS += -MMD
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(USR_CFLAGS)
|
||||
|
||||
LDFLAGS += $(LIBDIRS:%=-L %)
|
||||
LDFLAGS += $(LIBDIRS:%=-Wl,-rpath,%)
|
||||
LDFLAGS += $(LIBS:%=-l %)
|
||||
|
||||
ifeq ($(words $(PROGRAM)),0)
|
||||
PROGRAM = $(notdir $(PWD))
|
||||
endif
|
||||
|
||||
SRCS += $(SRCS_$(PROGRAM))
|
||||
ifeq ($(words $(SRCS)),0)
|
||||
SRCS += $(wildcard *.c)
|
||||
SRCS += $(wildcard *.cc)
|
||||
SRCS += $(wildcard *.C)
|
||||
SRCS += $(wildcard *.cxx)
|
||||
SRCS += $(wildcard *.cpp)
|
||||
endif
|
||||
|
||||
OBJS = $(addprefix O.$(EPICS_HOST_ARCH)/,$(addsuffix .o,$(basename $(SRCS))))
|
||||
|
||||
ifndef EPICS_HOST_ARCH
|
||||
$(error EPICS_HOST_ARCH variable is missing on your system!)
|
||||
endif
|
||||
|
||||
.PHONY:
|
||||
.PHONY: build clean realclean
|
||||
build:
|
||||
|
||||
clean:
|
||||
rm -rf O.*
|
||||
|
||||
realclean: clean
|
||||
rm -f $(foreach prog,$(PROGRAM),$(BINDIR)/$(prog))
|
||||
|
||||
O.%:
|
||||
mkdir $@
|
||||
|
||||
$(BINDIR):
|
||||
mkdir -p $@
|
||||
|
||||
ifeq ($(words $(PROGRAM)),1)
|
||||
|
||||
build: $(BINDIR)/$(PROGRAM)
|
||||
|
||||
ifneq ($(BINDIR),.)
|
||||
$(PROGRAM): $(BINDIR)/$(PROGRAM)
|
||||
endif
|
||||
|
||||
$(BINDIR)/$(PROGRAM): $(BINDIR) O.$(EPICS_HOST_ARCH) O.$(EPICS_HOST_ARCH)/$(PROGRAM)
|
||||
rm -f $@
|
||||
cp O.$(EPICS_HOST_ARCH)/$(@F) $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/$(PROGRAM): $(OBJS)
|
||||
$(CXX) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
else
|
||||
|
||||
build:
|
||||
for prog in $(PROGRAM); do make PROGRAM=$$prog; done
|
||||
|
||||
$(PROGRAM):
|
||||
make PROGRAM=$@
|
||||
|
||||
endif
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.c
|
||||
$(CC) -c $(CPPFLAGS) $(CFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.cc
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.C
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.cxx
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
O.$(EPICS_HOST_ARCH)/%.o: %.cpp
|
||||
$(CXX) -c $(CPPFLAGS) $(CFLAGS) $(CXXFLAGS) $< -o $@
|
||||
|
||||
-include O.$(EPICS_HOST_ARCH)/*.d
|
||||
@@ -0,0 +1,26 @@
|
||||
The two files caLesson5a.c and caLesson5b.c do the same.
|
||||
|
||||
The difference is that caLesson5a.c uses a EPICS 3.13-compatible
|
||||
single-threaded model, whereas caLesson5a.c uses a multi-threaded
|
||||
model.
|
||||
|
||||
caLesson5a.c compiles with 3.13 and 3.14,
|
||||
caLesson5b.c only compiles with 3.14.
|
||||
|
||||
To see the differences try
|
||||
diff caLesson5a.c caLesson5b.c
|
||||
or
|
||||
tkdiff caLesson5a.c caLesson5b.c
|
||||
|
||||
|
||||
The caLesson5.db file provides a soft IOC to be used in this example.
|
||||
Start it with (choose a unique string for prefix):
|
||||
xterm -e iocsh caLesson5.db P=prefix &
|
||||
|
||||
An medm panel is also provided:
|
||||
medm -x -macro P=prefix caLesson5.adl &
|
||||
|
||||
The programs caLesson5a and caLesson5b also need the prefix:
|
||||
caLesson5a prefix
|
||||
caLesson5b prefix
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,138 @@
|
||||
|
||||
file {
|
||||
name="/afs/psi.ch/project/epics/www/training/caClientLessons/caLesson5/caLesson5.adl"
|
||||
version=030004
|
||||
}
|
||||
display {
|
||||
object {
|
||||
x=64
|
||||
y=607
|
||||
width=400
|
||||
height=236
|
||||
}
|
||||
clr=14
|
||||
bclr=4
|
||||
cmap=""
|
||||
gridSpacing=5
|
||||
gridOn=0
|
||||
snapToGrid=0
|
||||
}
|
||||
"color map" {
|
||||
ncolors=65
|
||||
colors {
|
||||
ffffff,
|
||||
ececec,
|
||||
dadada,
|
||||
c8c8c8,
|
||||
bbbbbb,
|
||||
aeaeae,
|
||||
9e9e9e,
|
||||
919191,
|
||||
858585,
|
||||
787878,
|
||||
696969,
|
||||
5a5a5a,
|
||||
464646,
|
||||
2d2d2d,
|
||||
000000,
|
||||
00d800,
|
||||
1ebb00,
|
||||
339900,
|
||||
2d7f00,
|
||||
216c00,
|
||||
fd0000,
|
||||
de1309,
|
||||
be190b,
|
||||
a01207,
|
||||
820400,
|
||||
5893ff,
|
||||
597ee1,
|
||||
4b6ec7,
|
||||
3a5eab,
|
||||
27548d,
|
||||
fbf34a,
|
||||
f9da3c,
|
||||
eeb62b,
|
||||
e19015,
|
||||
cd6100,
|
||||
ffb0ff,
|
||||
d67fe2,
|
||||
ae4ebc,
|
||||
8b1a96,
|
||||
610a75,
|
||||
a4aaff,
|
||||
8793e2,
|
||||
6a73c1,
|
||||
4d52a4,
|
||||
343386,
|
||||
c7bb6d,
|
||||
b79d5c,
|
||||
a47e3c,
|
||||
7d5627,
|
||||
58340f,
|
||||
99ffff,
|
||||
73dfff,
|
||||
4ea5f9,
|
||||
2a63e4,
|
||||
0a00b8,
|
||||
ebf1b5,
|
||||
d4db9d,
|
||||
bbc187,
|
||||
a6a462,
|
||||
8b8239,
|
||||
73ff6b,
|
||||
52da3b,
|
||||
3cb420,
|
||||
289315,
|
||||
1a7309,
|
||||
}
|
||||
}
|
||||
meter {
|
||||
object {
|
||||
x=13
|
||||
y=83
|
||||
width=198
|
||||
height=117
|
||||
}
|
||||
monitor {
|
||||
chan="$(P):READ"
|
||||
clr=14
|
||||
bclr=4
|
||||
}
|
||||
label="channel"
|
||||
limits {
|
||||
}
|
||||
}
|
||||
valuator {
|
||||
object {
|
||||
x=10
|
||||
y=9
|
||||
width=372
|
||||
height=70
|
||||
}
|
||||
control {
|
||||
chan="$(P):SET"
|
||||
clr=14
|
||||
bclr=4
|
||||
}
|
||||
label="channel"
|
||||
dPrecision=1.000000
|
||||
limits {
|
||||
}
|
||||
}
|
||||
"text update" {
|
||||
object {
|
||||
x=221
|
||||
y=101
|
||||
width=138
|
||||
height=65
|
||||
}
|
||||
monitor {
|
||||
chan="$(P):DONE"
|
||||
clr=14
|
||||
bclr=4
|
||||
}
|
||||
clrmod="alarm"
|
||||
limits {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
/* caLesson5.c
|
||||
by Dirk Zimoch, 2007
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* include EPICS headers */
|
||||
#include <cadef.h>
|
||||
#define epicsAlarmGLOBAL
|
||||
#include <alarm.h>
|
||||
|
||||
/* Strings describing the connection status of a channel */
|
||||
const char *channel_state_str[4] = {
|
||||
"not found",
|
||||
"connection lost",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
/* Define a "process variable" (PV)
|
||||
This time, the PV should be generic. There should be only one
|
||||
type of PV for all possible data types. That means we need to
|
||||
find out the data type before actually allocating memory for
|
||||
the data.
|
||||
|
||||
Especially interfaces to scipting languages, such as TCL or IDL
|
||||
have to use generic PVs, because at the time the interface is
|
||||
compiled we cannot know to what type of channel the user will
|
||||
connect later.
|
||||
|
||||
Again, we use dbr_ctrl_* structures for "static" data and
|
||||
dbr_sts_* structures for "dynamic" data.
|
||||
|
||||
At compile-time, we don't know the exact data type of 'info' and
|
||||
'data'. Thus, we have to use void* and cast to the correct type.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
chid channel;
|
||||
int status;
|
||||
union {
|
||||
struct dbr_ctrl_short SHORT;
|
||||
struct dbr_ctrl_float FLOAT;
|
||||
struct dbr_ctrl_enum ENUM;
|
||||
struct dbr_ctrl_char CHAR;
|
||||
struct dbr_ctrl_long LONG;
|
||||
struct dbr_ctrl_double DOUBLE;
|
||||
} *info;
|
||||
union {
|
||||
struct dbr_sts_string STRING;
|
||||
struct dbr_sts_short SHORT;
|
||||
struct dbr_sts_float FLOAT;
|
||||
struct dbr_sts_enum ENUM;
|
||||
struct dbr_sts_char CHAR;
|
||||
struct dbr_sts_long LONG;
|
||||
struct dbr_sts_double DOUBLE;
|
||||
} *data;
|
||||
} epicsPV;
|
||||
|
||||
/* Define some macros to get infos out of a generic PV
|
||||
*/
|
||||
|
||||
#define PV_name(pv) (ca_name((pv)->channel))
|
||||
|
||||
#define PV_state(pv) (ca_state((pv)->channel))
|
||||
|
||||
#define PV_has_info(pv) ((pv)->info != NULL)
|
||||
|
||||
#define PV_has_data(pv) ((pv)->data != NULL)
|
||||
|
||||
#define PV_type(pv) (ca_field_type((pv)->channel))
|
||||
|
||||
#define PV_status(pv) ((pv)->data->SHORT.status)
|
||||
|
||||
#define PV_severity(pv) ((pv)->data->SHORT.severity)
|
||||
|
||||
#define PV_value(pv,type) ((pv)->data->type.value)
|
||||
|
||||
#define PV_info(pv,type) ((pv)->info->type)
|
||||
|
||||
|
||||
/* Get the units out of the PV.
|
||||
When we don't know return an empty string.
|
||||
*/
|
||||
const char* PV_units(epicsPV* pv)
|
||||
{
|
||||
if (!pv || !pv->info) return "";
|
||||
switch (PV_type(pv))
|
||||
{
|
||||
case DBF_SHORT:
|
||||
return pv->info->SHORT.units;
|
||||
case DBF_FLOAT:
|
||||
return pv->info->FLOAT.units;
|
||||
case DBF_CHAR:
|
||||
return pv->info->CHAR.units;
|
||||
case DBF_LONG:
|
||||
return pv->info->LONG.units;
|
||||
case DBF_DOUBLE:
|
||||
return pv->info->DOUBLE.units;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the precision out of the PV.
|
||||
When we don't know return something reasonable. */
|
||||
short PV_precision(epicsPV* pv)
|
||||
{
|
||||
if (!pv) return 0;
|
||||
switch (PV_type(pv))
|
||||
{
|
||||
case DBF_FLOAT:
|
||||
if (!pv->info) return 4;
|
||||
return ((struct dbr_ctrl_float*)pv->info)->precision;
|
||||
case DBF_DOUBLE:
|
||||
if (!pv->info) return 6;
|
||||
return ((struct dbr_ctrl_double*)pv->info)->precision;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int cainfo(epicsPV* pv)
|
||||
{
|
||||
if (PV_state(pv) != cs_conn)
|
||||
{
|
||||
printf ("cainfo %s: %s\n", PV_name(pv),
|
||||
channel_state_str[PV_state(pv)]);
|
||||
return ECA_DISCONN;
|
||||
}
|
||||
/* Read static info only once. */
|
||||
if (!pv->info && PV_type(pv) != DBF_STRING)
|
||||
{
|
||||
int dbr_type;
|
||||
|
||||
dbr_type = dbf_type_to_DBR_CTRL(PV_type(pv));
|
||||
if (!pv->info)
|
||||
{
|
||||
printf ("cainfo %s: allocating memory for static data\n", PV_name(pv));
|
||||
pv->info = malloc(dbr_size[dbr_type]);
|
||||
if (!pv->info) return ECA_ALLOCMEM;
|
||||
}
|
||||
printf ("cainfo %s: requesting static data\n", PV_name(pv));
|
||||
return ca_get(dbr_type, pv->channel, pv->info);
|
||||
}
|
||||
return ECA_NORMAL;
|
||||
}
|
||||
|
||||
int caget(epicsPV* pv)
|
||||
{
|
||||
int dbr_type;
|
||||
|
||||
if (PV_state(pv) != cs_conn)
|
||||
{
|
||||
printf ("caget %s: %s\n", PV_name(pv),
|
||||
channel_state_str[PV_state(pv)]);
|
||||
return ECA_DISCONN;
|
||||
}
|
||||
/* Allocate memory only once but read dynamic data every time */
|
||||
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
|
||||
if (!pv->data)
|
||||
{
|
||||
printf ("caget %s: allocating memory for dynamic data\n", PV_name(pv));
|
||||
pv->data = malloc(dbr_size[dbr_type]);
|
||||
if (!pv->data) return ECA_ALLOCMEM;
|
||||
}
|
||||
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
|
||||
return ca_get(dbr_type, pv->channel, pv->data);
|
||||
}
|
||||
|
||||
int camonitor(epicsPV* pv, void (*monitor)(epicsPV* pv))
|
||||
{
|
||||
return ECA_NORMAL;
|
||||
}
|
||||
|
||||
/* Print the contents of a generic PV.
|
||||
*/
|
||||
void printPV(epicsPV* pv)
|
||||
{
|
||||
const char* name;
|
||||
const char* status_str;
|
||||
const char* severity_str;
|
||||
const char* units;
|
||||
short precision;
|
||||
|
||||
name = PV_name(pv);
|
||||
|
||||
if (!pv)
|
||||
{
|
||||
printf("<NULL PV>\n");
|
||||
}
|
||||
if (PV_state(pv) != cs_conn)
|
||||
{
|
||||
/* Channel is not connected */
|
||||
printf("%s: <%s>\n",
|
||||
name, channel_state_str[PV_state(pv)]);
|
||||
return;
|
||||
}
|
||||
if (!pv->data)
|
||||
{
|
||||
printf("%s: <caget never called>\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
status_str = epicsAlarmConditionStrings[PV_status(pv)];
|
||||
severity_str = epicsAlarmSeverityStrings[PV_severity(pv)];
|
||||
units = PV_units(pv);
|
||||
precision = PV_precision(pv);
|
||||
|
||||
/* Handle different data types.
|
||||
See /usr/local/epics/base/include/db_access.h for DBF_* types.
|
||||
For each possible type, we have to cast 'info' and 'data'
|
||||
to the correct structures before we can access the data.
|
||||
*/
|
||||
switch (PV_type(pv))
|
||||
{
|
||||
case DBF_STRING:
|
||||
{
|
||||
/* Print channel name, native channel type,
|
||||
value and severity */
|
||||
printf("%s (STRING) = \"%s\" %s:%s\n",
|
||||
name,
|
||||
PV_value(pv,STRING),
|
||||
severity_str, status_str);
|
||||
break;
|
||||
}
|
||||
case DBF_SHORT:
|
||||
{
|
||||
printf("%s (SHORT) = %hi %s %s:%s",
|
||||
name,
|
||||
PV_value(pv,SHORT), units,
|
||||
severity_str, status_str);
|
||||
if (PV_has_info(pv)) printf(" range:[%hi ... %hi] setrange:[%hi ... %hi]\n",
|
||||
PV_info(pv,SHORT).lower_disp_limit,
|
||||
PV_info(pv,SHORT).upper_disp_limit,
|
||||
PV_info(pv,SHORT).lower_ctrl_limit,
|
||||
PV_info(pv,SHORT).upper_ctrl_limit);
|
||||
else printf("\n");
|
||||
break;
|
||||
}
|
||||
case DBF_FLOAT:
|
||||
{
|
||||
printf("%s (FLOAT) = %#.*f %s %s:%s",
|
||||
name,
|
||||
precision, PV_value(pv,FLOAT), units,
|
||||
severity_str, status_str);
|
||||
if (PV_has_info(pv)) printf(" range:[%#.*f ... %#.*f] setrange:[%#.*f ... %#.*f]\n",
|
||||
precision, PV_info(pv,FLOAT).lower_disp_limit,
|
||||
precision, PV_info(pv,FLOAT).upper_disp_limit,
|
||||
precision, PV_info(pv,FLOAT).lower_ctrl_limit,
|
||||
precision, PV_info(pv,FLOAT).upper_ctrl_limit);
|
||||
else printf("\n");
|
||||
break;
|
||||
}
|
||||
case DBF_ENUM:
|
||||
{
|
||||
int i;
|
||||
|
||||
if (PV_has_info(pv))
|
||||
{
|
||||
printf("%s (ENUM) = %i = \"%s\" %s:%s %i strings:",
|
||||
name,
|
||||
PV_value(pv,ENUM),
|
||||
PV_value(pv,ENUM) < PV_info(pv,ENUM).no_str ?
|
||||
PV_info(pv,ENUM).strs[PV_value(pv,ENUM)] : "",
|
||||
severity_str, status_str,
|
||||
PV_info(pv,ENUM).no_str);
|
||||
for (i = 0; i < PV_info(pv,ENUM).no_str; i++)
|
||||
{
|
||||
printf("%s\"%s\"", i>0 ? "," : "", PV_info(pv,ENUM).strs[i]);
|
||||
}
|
||||
printf ("\n");
|
||||
}
|
||||
else printf("%s (ENUM) = %i %s:%s\n",
|
||||
name,
|
||||
data->value,
|
||||
severity_str, status_str);
|
||||
break;
|
||||
}
|
||||
case DBF_CHAR:
|
||||
{
|
||||
struct dbr_ctrl_char* info = pv->info;
|
||||
struct dbr_sts_char* data = pv->data;
|
||||
/* Print channel name, native channel type,
|
||||
value, units, severity, and ranges */
|
||||
printf("%s (CHAR) = %i %s %s:%s",
|
||||
name,
|
||||
data->value, units,
|
||||
severity_str, status_str);
|
||||
if (PV_has_info(pv)) printf(" range:[%i ... %i] setrange:[%i ... %i]\n",
|
||||
info->lower_disp_limit,
|
||||
info->upper_disp_limit,
|
||||
info->lower_ctrl_limit,
|
||||
info->upper_ctrl_limit);
|
||||
else printf("\n");
|
||||
break;
|
||||
}
|
||||
case DBF_LONG:
|
||||
{
|
||||
struct dbr_ctrl_long* info = pv->info;
|
||||
struct dbr_sts_long* data = pv->data;
|
||||
/* Print channel name, native channel type,
|
||||
value, units, severity, and ranges */
|
||||
printf("%s (LONG) = %i %s %s:%s",
|
||||
name,
|
||||
data->value, units,
|
||||
severity_str, status_str);
|
||||
if (info) printf(" range:[%i ... %i] setrange:[%i ... %i]\n",
|
||||
info->lower_disp_limit,
|
||||
info->upper_disp_limit,
|
||||
info->lower_ctrl_limit,
|
||||
info->upper_ctrl_limit);
|
||||
else printf("\n");
|
||||
break;
|
||||
}
|
||||
case DBF_DOUBLE:
|
||||
{
|
||||
struct dbr_ctrl_double* info = pv->info;
|
||||
struct dbr_sts_double* data = pv->data;
|
||||
/* Print channel name, native channel type,
|
||||
value, units, severity, and ranges */
|
||||
printf("%s (DOUBLE) = %#.*f %s %s:%s",
|
||||
name,
|
||||
precision, data->value, units,
|
||||
severity_str, status_str);
|
||||
if (info) printf(" range:[%#.*f ... %#.*f] setrange:[%#.*f ... %#.*f]\n",
|
||||
precision, info->lower_disp_limit,
|
||||
precision, info->upper_disp_limit,
|
||||
precision, info->lower_ctrl_limit,
|
||||
precision, info->upper_ctrl_limit);
|
||||
else printf("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
epicsPV* newPV(const char* name)
|
||||
{
|
||||
int status;
|
||||
|
||||
epicsPV* pv = malloc(sizeof(epicsPV));
|
||||
pv->info = NULL;
|
||||
pv->data = NULL;
|
||||
printf("searching for channel %s\n", name);
|
||||
status = ca_search(name, &pv->channel);
|
||||
/* status = ca_search(name, &pv->channel); */
|
||||
SEVCHK(status, "ca_search_and_connect");
|
||||
if (status != ECA_NORMAL)
|
||||
{
|
||||
free(pv);
|
||||
return NULL;
|
||||
}
|
||||
return pv;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
epicsPV *gapread, *gapdone;
|
||||
double search_timeout = 5.0; /* seconds */
|
||||
double get_timeout = 1.0; /* seconds */
|
||||
double loop_period = 5.0; /* seconds */
|
||||
int num_turns = 3;
|
||||
int i;
|
||||
|
||||
/* Step1: initialize channel access and search for all channels. */
|
||||
ca_task_initialize();
|
||||
|
||||
/* Let's have a look how EPICS conencts to different types of PVs.
|
||||
We try here to connect an analogue value and a discrete value
|
||||
to double, long, string and enum PVs. We'll see what happens.
|
||||
*/
|
||||
gapread = newPV("X10SA-ID-GAP:READ");
|
||||
gapdone = newPV("X10SA-ID-GAP:DONE");
|
||||
|
||||
/* Send all the search requests but don't wait for connection. */
|
||||
printf("sending search request\n");
|
||||
ca_flush_io();
|
||||
printf("doing other stuff ...\n");
|
||||
|
||||
/* Now, connection is done in the background.
|
||||
We can use the time to initialize the rest of the program,
|
||||
read files, setup the GUI, etc ... Whatever can be done
|
||||
before the channels have connected.
|
||||
*/
|
||||
printf("finishing search request ...\n");
|
||||
SEVCHK(ca_pend_io(search_timeout), "search");
|
||||
printf("searching done\n");
|
||||
|
||||
/* Step2: get all static infos */
|
||||
|
||||
/*
|
||||
SEVCHK(cainfo(gapread),"cainfo gapread");
|
||||
SEVCHK(cainfo(gapdone),"cainfo gapdone");
|
||||
SEVCHK(ca_pend_io(get_timeout), "cainfo");
|
||||
*/
|
||||
|
||||
/* Step3: reading dynamic data periodically */
|
||||
|
||||
printf("\nEntering loop without static infos\n");
|
||||
for (i=1; i <= num_turns; i++)
|
||||
{
|
||||
printf("\nTurn %d/%d\n", i, num_turns);
|
||||
SEVCHK(caget(gapread), "caget gapread");
|
||||
SEVCHK(caget(gapdone), "caget gapdone");
|
||||
SEVCHK(ca_pend_io(get_timeout), "caget");
|
||||
|
||||
printPV(gapread);
|
||||
printPV(gapdone);
|
||||
|
||||
ca_pend_event(loop_period);
|
||||
}
|
||||
printf("\nFinished loop\n");
|
||||
|
||||
SEVCHK(cainfo(gapread),"cainfo gapread");
|
||||
SEVCHK(cainfo(gapdone),"cainfo gapdone");
|
||||
SEVCHK(ca_pend_io(get_timeout), "cainfo");
|
||||
|
||||
printf("\nEntering loop with static infos\n");
|
||||
for (i=1; i <= num_turns; i++)
|
||||
{
|
||||
printf("\nTurn %d/%d\n", i, num_turns);
|
||||
SEVCHK(caget(gapread), "caget gapread");
|
||||
SEVCHK(caget(gapdone), "caget gapdone");
|
||||
SEVCHK(ca_pend_io(get_timeout), "caget");
|
||||
|
||||
printPV(gapread);
|
||||
printPV(gapdone);
|
||||
|
||||
ca_pend_event(loop_period);
|
||||
}
|
||||
printf("\nFinished loop\n");
|
||||
|
||||
camonitor(gapread, printPV);
|
||||
camonitor(gapdone, printPV);
|
||||
|
||||
cainfo(gapread);
|
||||
caget(gapread);
|
||||
cainfo(gapdone);
|
||||
caget(gapdone);
|
||||
ca_pend_io(get_timeout);
|
||||
printPV(gapread);
|
||||
printPV(gapdone);
|
||||
|
||||
|
||||
printf("doing channel access forever ...\n");
|
||||
ca_pend_event(0.0);
|
||||
|
||||
|
||||
|
||||
/* Last step: free all channel access resources */
|
||||
printf("Done\n");
|
||||
ca_task_exit();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
record (ao, "$(P):SET")
|
||||
{
|
||||
field (PREC, "2")
|
||||
field (OUT, "$(P):RUN") # Set new value
|
||||
field (FLNK, "$(P):ACTIVATE") # Set $(P):DONE to ACTIVE
|
||||
field (DRVL, "5")
|
||||
field (DRVH, "40")
|
||||
field (LOPR, "5")
|
||||
field (HOPR, "40")
|
||||
field (VAL, "10")
|
||||
}
|
||||
|
||||
record (ao, "$(P):RUN") # A record that changes slowly.
|
||||
{
|
||||
field (VAL, "10")
|
||||
field (OROC, "0.1") # Value changes only by 0.1
|
||||
field (SCAN, ".1 second") # each 0.1 seconds.
|
||||
field (PREC, "2")
|
||||
}
|
||||
|
||||
record (ai, "$(P):READ")
|
||||
{
|
||||
field (INP, "$(P):RUN.OVAL CP") # Get value whenever it changes.
|
||||
field (PREC, "2")
|
||||
field (LOPR, "0")
|
||||
field (HOPR, "40")
|
||||
}
|
||||
|
||||
record (bo, "$(P):DONE")
|
||||
{
|
||||
field (ZNAM, "ACTIVE")
|
||||
field (ONAM, "DONE")
|
||||
field (PINI, "YES") # After reboot initialize
|
||||
field (VAL, "1") # to state 1 (DONE)
|
||||
}
|
||||
|
||||
record (seq, "$(P):ACTIVATE")
|
||||
{
|
||||
field (DOL1, "0") # Write 0 (ACTIVE)
|
||||
field (LNK1, "$(P):DONE PP") # to $(P):DONE
|
||||
field (FLNK, "$(P):CHECKDONE") # Check if already done
|
||||
}
|
||||
|
||||
record (calcout, "$(P):CHECKDONE")
|
||||
{
|
||||
field (INPA, "$(P):READ CP")
|
||||
field (INPB, "$(P):SET CP")
|
||||
field (CALC, "A=B") # When READ and SET
|
||||
field (OOPT, "When Non-zero") # become equal then write 1
|
||||
field (OUT, "$(P):DONE PP") # to $(P):DONE.
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,326 @@
|
||||
/* caLesson5a.c
|
||||
by Dirk Zimoch, 2007
|
||||
|
||||
This lesson introduces writing to channels and waiting for channels.
|
||||
We will write a value to a (simulated) device which takes a while and
|
||||
wait until the device is done. The device provides a DONE record.
|
||||
We will use a monitor to get informed when the device is done without
|
||||
polling it over the network.
|
||||
|
||||
A configuration file for a soft IOC is provided in this directory.
|
||||
Before trying this program, start the soft IOC with:
|
||||
|
||||
xterm -e ioch caLesson5.db P=prefix &
|
||||
|
||||
where prefix should be something unique to you (e.g. your initials).
|
||||
|
||||
Use this program with the same prefix:
|
||||
|
||||
caLesson5a prefix
|
||||
|
||||
|
||||
This is a single-threaded EPICS 3.13. compatible version of the example.
|
||||
For the EPICS 3.14. multi-threaded version see caLesson5b.c.
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* include EPICS headers */
|
||||
#include <cadef.h>
|
||||
#define epicsAlarmGLOBAL
|
||||
#include <alarm.h>
|
||||
|
||||
/* Strings describing the connection status of a channel.
|
||||
See also enum channel_state in /usr/local/epics/base/include/cadef.h
|
||||
*/
|
||||
const char *channel_state_str[4] = {
|
||||
"not found",
|
||||
"connection lost",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
/* Define process variable (PV) structures.
|
||||
Each PV contains static information in info and
|
||||
current value in data.
|
||||
See /usr/local/epics/base/include/db_access.h for fields
|
||||
of dbr_* structures.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
chid channel;
|
||||
struct dbr_ctrl_double info;
|
||||
struct dbr_sts_double data;
|
||||
} epicsDoublePV;
|
||||
|
||||
typedef struct {
|
||||
chid channel;
|
||||
struct dbr_ctrl_enum info;
|
||||
struct dbr_sts_enum data;
|
||||
} epicsEnumPV;
|
||||
|
||||
|
||||
/* Print NAME = VALUE for double.
|
||||
Precision and units are taken from info.
|
||||
*/
|
||||
void printDoublePV(const epicsDoublePV* pv)
|
||||
{
|
||||
if (ca_state(pv->channel) != cs_conn)
|
||||
{
|
||||
printf("%s <%s>\n",
|
||||
ca_name(pv->channel),
|
||||
channel_state_str[ca_state(pv->channel)]);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s = %.*f %s",
|
||||
ca_name(pv->channel),
|
||||
pv->info.precision, pv->data.value, pv->info.units);
|
||||
|
||||
if (pv->data.severity != NO_ALARM)
|
||||
{
|
||||
printf(" <%s %s>\n",
|
||||
epicsAlarmSeverityStrings[pv->data.severity],
|
||||
epicsAlarmConditionStrings[pv->data.status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Print NAME = VALUE for enum.
|
||||
VALUE is printed as string if possible, otherwise as number.
|
||||
*/
|
||||
void printEnumPV(const epicsEnumPV* pv)
|
||||
{
|
||||
if (ca_state(pv->channel) != cs_conn)
|
||||
{
|
||||
printf("%s <%s>\n",
|
||||
ca_name(pv->channel),
|
||||
channel_state_str[ca_state(pv->channel)]);
|
||||
return;
|
||||
}
|
||||
if (pv->data.value < pv->info.no_str)
|
||||
printf("%s = %s",
|
||||
ca_name(pv->channel), pv->info.strs[pv->data.value]);
|
||||
else
|
||||
{
|
||||
printf("%s = %d",
|
||||
ca_name(pv->channel), pv->data.value);
|
||||
}
|
||||
|
||||
if (pv->data.severity != NO_ALARM)
|
||||
{
|
||||
printf(" <%s %s>\n",
|
||||
epicsAlarmSeverityStrings[pv->data.severity],
|
||||
epicsAlarmConditionStrings[pv->data.status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Generic monitor event handler.
|
||||
See /usr/local/epics/base/include/cadef.h for the definition of
|
||||
struct event_handler_args.
|
||||
This handler copies the new value into the PV and writes a message.
|
||||
We get the address of the PV in the 'usr' field of 'args'
|
||||
because we give that as the 4th argument to ca_add_event (see below).
|
||||
*/
|
||||
|
||||
static void monitor(struct event_handler_args args)
|
||||
{
|
||||
printf("Monitor: ");
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
SEVCHK(args.status, "monitor");
|
||||
return;
|
||||
}
|
||||
/* Copy the value to the 'data' field of the PV.
|
||||
The current data, its type and the number of elements (for arrays) is
|
||||
stored in several fields of 'args'.
|
||||
*/
|
||||
switch (args.type)
|
||||
{
|
||||
case DBR_STS_DOUBLE:
|
||||
{
|
||||
epicsDoublePV* pv = args.usr;
|
||||
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
printDoublePV(pv);
|
||||
break;
|
||||
}
|
||||
case DBR_STS_ENUM:
|
||||
{
|
||||
epicsEnumPV* pv = args.usr;
|
||||
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
printEnumPV(pv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
printf ("%s unsupported data type\n", ca_name(args.chid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** args)
|
||||
{
|
||||
char recordname[28];
|
||||
double search_timeout = 5.0; /* seconds */
|
||||
double put_timeout = 1.0; /* seconds */
|
||||
double get_timeout = 1.0; /* seconds */
|
||||
|
||||
int status;
|
||||
CA_SYNC_GID gid; /* required for blocking put (see below) */
|
||||
|
||||
epicsDoublePV setvalue;
|
||||
epicsDoublePV readvalue;
|
||||
epicsEnumPV doneflag;
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <prefix>\n"
|
||||
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
|
||||
args[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Step1: Initialize channel access and search for all channels. */
|
||||
ca_task_initialize();
|
||||
|
||||
sprintf(recordname, "%.19s:SET", args[1]);
|
||||
ca_search(recordname, &setvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:READ", args[1]);
|
||||
ca_search(recordname, &readvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:DONE", args[1]);
|
||||
ca_search(recordname, &doneflag.channel);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Step 2: Get available infos and setup monitor for DONE flag*/
|
||||
|
||||
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
|
||||
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
|
||||
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
|
||||
ca_add_event(DBR_STS_DOUBLE, readvalue.channel, monitor, &readvalue, NULL);
|
||||
ca_add_event(DBR_STS_ENUM, doneflag.channel, monitor, &doneflag, NULL);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
|
||||
/* Create the "synchronous group id" (gid) used later for put. */
|
||||
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Step 3: Enter main loop */
|
||||
while (1)
|
||||
{
|
||||
char userinput[40];
|
||||
double newvalue;
|
||||
|
||||
/* Get current setting */
|
||||
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
|
||||
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
|
||||
printDoublePV(&setvalue);
|
||||
|
||||
/* Ask for new setting */
|
||||
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
|
||||
{
|
||||
printf("Enter new value (range %.*f~%.*f): ",
|
||||
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
|
||||
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No limits known */
|
||||
printf("Enter new value: ");
|
||||
}
|
||||
fflush(stdout);
|
||||
fgets(userinput, sizeof(userinput), stdin);
|
||||
if (sscanf(userinput, "%lf", &newvalue) != 1)
|
||||
{
|
||||
printf("Invalid input \"%s\". Need a number.\n", userinput);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set new value and wait to complete.
|
||||
|
||||
This is a very important timing issue!
|
||||
The records are build in a way that the DONE record
|
||||
is set to "ACTIVE" before a put to the SET record completes.
|
||||
Insider info: They are linked via FLNK and PP output links.
|
||||
|
||||
Timing:
|
||||
|
||||
ca_put (0)-(1)---(2)----(3)
|
||||
:
|
||||
ca_sg_put (0)-------(2)----(3)
|
||||
:
|
||||
"DONE" =====*=====* *========
|
||||
| |
|
||||
"ACTIVE" *=======*
|
||||
|
||||
Some time after the put (0), the device becomes active.
|
||||
|
||||
A normal put may return before (1), while (2), or after (3)
|
||||
activity. A following reading of the DONE record cannot
|
||||
distinguish between (1) and (3).
|
||||
|
||||
However, a synchonous put will not return before (2).
|
||||
Thus, we can wait until the DONE record becomes "DONE".
|
||||
If it returns late after activity finished (3), the DONE record
|
||||
is already "DONE" and we don't need to wait. But we can never be
|
||||
in the situation that the DONE record is not yet "ACTIVE" (1).
|
||||
|
||||
To implement a synchronous put, we use the "synchronous group"
|
||||
mechanism. ca_sg_block does not return until all outstanding
|
||||
ca_sg_* requests with the same gid have completed (or timeout).
|
||||
|
||||
Note that we put a bare double here, no DBR_CTRL_*.
|
||||
*/
|
||||
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
|
||||
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
|
||||
|
||||
/* Wait until activity is done.
|
||||
This uses the monitor on the DONE record.
|
||||
Remember that monitors are handled in the background, but only
|
||||
while certain ca functions are active, e.g. ca_pend_event(),
|
||||
ca_pend_io(), or ca_sg_block().
|
||||
*/
|
||||
|
||||
/* First actively read current value of DONE record. */
|
||||
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
|
||||
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
|
||||
printEnumPV(&doneflag);
|
||||
|
||||
/* When not already done, wait for monitor events */
|
||||
while (!doneflag.data.value)
|
||||
{
|
||||
/* wait for the next monitor events */
|
||||
ca_pend_event(0.01);
|
||||
/* Unfortunately, there is no function "wait until next event".
|
||||
Thus, we do "wait for 0.01 seconds and process events" in
|
||||
a loop.
|
||||
Note that we don't poll the IOC for "DONE"!.
|
||||
This only generates network traffic when something happens
|
||||
with a maximum latency of 0.01 seconds.
|
||||
*/
|
||||
}
|
||||
printEnumPV(&doneflag);
|
||||
}
|
||||
|
||||
/* Step 4: clean up */
|
||||
ca_sg_delete(gid);
|
||||
end:
|
||||
ca_task_exit();
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,367 @@
|
||||
/* caLesson5b.c
|
||||
by Dirk Zimoch, 2007
|
||||
|
||||
This lesson introduces writing to channels and waiting for channels.
|
||||
We will write a value to a (simulated) device which takes a while and
|
||||
wait until the device is done. The device provides a DONE record.
|
||||
We will use a monitor to get informed when the device is done without
|
||||
polling it over the network.
|
||||
|
||||
A configuration file for a soft IOC is provided in this directory.
|
||||
Before trying this program, start the soft IOC with:
|
||||
|
||||
xterm -e ioch caLesson5.db P=prefix &
|
||||
|
||||
where prefix should be something unique to you (e.g. your initials).
|
||||
|
||||
Use this program with the same prefix:
|
||||
|
||||
caLesson5b prefix
|
||||
|
||||
This is a multi-threaded EPICS 3.14. version of the example.
|
||||
For the EPICS 3.13. compatible single-threaded version see caLesson5a.c.
|
||||
|
||||
You should be familiar with multi-threading to understand this.
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* include EPICS headers */
|
||||
#include <cadef.h>
|
||||
#define epicsAlarmGLOBAL
|
||||
#include <alarm.h>
|
||||
#include <epicsEvent.h>
|
||||
#include <epicsMutex.h>
|
||||
|
||||
/* Strings describing the connection status of a channel.
|
||||
See also enum channel_state in /usr/local/epics/base/include/cadef.h
|
||||
*/
|
||||
const char *channel_state_str[4] = {
|
||||
"not found",
|
||||
"connection lost",
|
||||
"connected",
|
||||
"closed"
|
||||
};
|
||||
|
||||
/* Define process variable (PV) structures.
|
||||
Each PV contains static information in info and
|
||||
current value in data.
|
||||
See /usr/local/epics/base/include/db_access.h for fields
|
||||
of dbr_* structures.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
chid channel;
|
||||
struct dbr_ctrl_double info;
|
||||
struct dbr_sts_double data;
|
||||
} epicsDoublePV;
|
||||
|
||||
typedef struct {
|
||||
chid channel;
|
||||
struct dbr_ctrl_enum info;
|
||||
struct dbr_sts_enum data;
|
||||
} epicsEnumPV;
|
||||
|
||||
|
||||
/* Being multi threaded, we must protect PV access by mutex semaphores.
|
||||
We will use events to get notification of monitors
|
||||
*/
|
||||
|
||||
epicsEventId monitorEvent = NULL;
|
||||
epicsMutexId accessMutex = NULL;
|
||||
|
||||
/* Print NAME = VALUE for double.
|
||||
Precision and units are taken from info.
|
||||
*/
|
||||
void printDoublePV(const epicsDoublePV* pv)
|
||||
{
|
||||
epicsMutexLock(accessMutex);
|
||||
if (ca_state(pv->channel) != cs_conn)
|
||||
{
|
||||
printf("%s <%s>\n",
|
||||
ca_name(pv->channel),
|
||||
channel_state_str[ca_state(pv->channel)]);
|
||||
epicsMutexUnlock(accessMutex);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("%s = %.*f %s",
|
||||
ca_name(pv->channel),
|
||||
pv->info.precision, pv->data.value, pv->info.units);
|
||||
|
||||
if (pv->data.severity != NO_ALARM)
|
||||
{
|
||||
printf(" <%s %s>\n",
|
||||
epicsAlarmSeverityStrings[pv->data.severity],
|
||||
epicsAlarmConditionStrings[pv->data.status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
}
|
||||
|
||||
/* Print NAME = VALUE for enum.
|
||||
VALUE is printed as string if possible, otherwise as number.
|
||||
*/
|
||||
void printEnumPV(const epicsEnumPV* pv)
|
||||
{
|
||||
epicsMutexLock(accessMutex);
|
||||
if (ca_state(pv->channel) != cs_conn)
|
||||
{
|
||||
printf("%s <%s>\n",
|
||||
ca_name(pv->channel),
|
||||
channel_state_str[ca_state(pv->channel)]);
|
||||
epicsMutexUnlock(accessMutex);
|
||||
return;
|
||||
}
|
||||
if (pv->data.value < pv->info.no_str)
|
||||
printf("%s = %s",
|
||||
ca_name(pv->channel), pv->info.strs[pv->data.value]);
|
||||
else
|
||||
{
|
||||
printf("%s = %d",
|
||||
ca_name(pv->channel), pv->data.value);
|
||||
}
|
||||
|
||||
if (pv->data.severity != NO_ALARM)
|
||||
{
|
||||
printf(" <%s %s>\n",
|
||||
epicsAlarmSeverityStrings[pv->data.severity],
|
||||
epicsAlarmConditionStrings[pv->data.status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
}
|
||||
|
||||
/* Generic monitor event handler.
|
||||
See /usr/local/epics/base/include/cadef.h for the definition of
|
||||
struct event_handler_args.
|
||||
This handler copies the new value into the PV and writes a message.
|
||||
We get the address of the PV in the 'usr' field of 'args'
|
||||
because we give that as the 4th argument to ca_add_event (see below).
|
||||
|
||||
In the multi threaded model, the monitor callback runs in a separate
|
||||
thread. That means, the monitor function may be called at any time,
|
||||
even while we are just accessing the PV from the main program. Thus,
|
||||
we must protect all accesses to a PV with a mutext. Here, we use
|
||||
just one global mutex. The next lesson will introduce a more
|
||||
sophisticated solution.
|
||||
*/
|
||||
|
||||
static void monitor(struct event_handler_args args)
|
||||
{
|
||||
printf("Monitor: ");
|
||||
epicsMutexLock(accessMutex);
|
||||
if (args.status != ECA_NORMAL)
|
||||
{
|
||||
/* Something went wrong. */
|
||||
SEVCHK(args.status, "monitor");
|
||||
epicsMutexUnlock(accessMutex);
|
||||
return;
|
||||
}
|
||||
/* Copy the value to the 'data' field of the PV.
|
||||
The current data, its type and the number of elements (for arrays) is
|
||||
stored in several fields of 'args'.
|
||||
*/
|
||||
switch (args.type)
|
||||
{
|
||||
case DBR_STS_DOUBLE:
|
||||
{
|
||||
epicsDoublePV* pv = args.usr;
|
||||
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
printDoublePV(pv);
|
||||
break;
|
||||
}
|
||||
case DBR_STS_ENUM:
|
||||
{
|
||||
epicsEnumPV* pv = args.usr;
|
||||
memcpy(&pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
||||
printEnumPV(pv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
printf ("%s unsupported data type\n", ca_name(args.chid));
|
||||
}
|
||||
}
|
||||
epicsMutexUnlock(accessMutex);
|
||||
|
||||
/* Inform other threads about monitor */
|
||||
epicsEventSignal(monitorEvent);
|
||||
}
|
||||
|
||||
int main(int argc, char** args)
|
||||
{
|
||||
char recordname[28];
|
||||
double search_timeout = 5.0; /* seconds */
|
||||
double put_timeout = 1.0; /* seconds */
|
||||
double get_timeout = 1.0; /* seconds */
|
||||
|
||||
int status;
|
||||
CA_SYNC_GID gid; /* required for blocking put (see below) */
|
||||
|
||||
epicsDoublePV setvalue;
|
||||
epicsDoublePV readvalue;
|
||||
epicsEnumPV doneflag;
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <prefix>\n"
|
||||
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
|
||||
args[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Step1: Initialize channel access and search for all channels. */
|
||||
|
||||
/* Start EPICS multi-threaded */
|
||||
ca_context_create(ca_enable_preemptive_callback);
|
||||
|
||||
/* ca_create_channel has more parameters than the old ca_search
|
||||
but we don't need them here.
|
||||
*/
|
||||
sprintf(recordname, "%.19s:SET", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:READ", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
|
||||
|
||||
sprintf(recordname, "%.19s:DONE", args[1]);
|
||||
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Setup an event for monitors */
|
||||
monitorEvent = epicsEventCreate(epicsEventEmpty);
|
||||
|
||||
/* Setup a mutex semaphore to make PV access thread-safe */
|
||||
accessMutex = epicsMutexCreate();
|
||||
|
||||
/* Step 2: Get available infos and setup monitor for DONE flag*/
|
||||
|
||||
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
|
||||
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
|
||||
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
|
||||
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
|
||||
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
|
||||
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
|
||||
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
|
||||
|
||||
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
|
||||
/* Create the "synchronous group id" (gid) used later for put. */
|
||||
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
|
||||
if (status != ECA_NORMAL) goto end;
|
||||
|
||||
/* Step 3: Enter main loop */
|
||||
while (1)
|
||||
{
|
||||
char userinput[40];
|
||||
double newvalue;
|
||||
|
||||
/* Get current setting */
|
||||
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
|
||||
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
|
||||
printDoublePV(&setvalue);
|
||||
|
||||
/* Ask for new setting */
|
||||
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
|
||||
{
|
||||
printf("Enter new value (range %.*f~%.*f): ",
|
||||
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
|
||||
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No limits known */
|
||||
printf("Enter new value: ");
|
||||
}
|
||||
fflush(stdout);
|
||||
fgets(userinput, sizeof(userinput), stdin);
|
||||
if (sscanf(userinput, "%lf", &newvalue) != 1)
|
||||
{
|
||||
printf("Invalid input \"%s\". Need a number.\n", userinput);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set new value and wait to complete.
|
||||
|
||||
This is a very important timing issue!
|
||||
The records are build in a way that the DONE record
|
||||
is set to "ACTIVE" before a put to the SET record completes.
|
||||
Insider info: They are linked via FLNK and PP output links.
|
||||
|
||||
Timing:
|
||||
|
||||
ca_put (0)-(1)---(2)----(3)
|
||||
:
|
||||
ca_sg_put (0)-------(2)----(3)
|
||||
:
|
||||
"DONE" =====*=====* *========
|
||||
| |
|
||||
"ACTIVE" *=======*
|
||||
|
||||
Some time after the put (0), the device becomes active.
|
||||
|
||||
A normal put may return before (1), while (2), or after (3)
|
||||
activity. A following reading of the DONE record cannot
|
||||
distinguish between (1) and (3).
|
||||
|
||||
However, a synchonous put will not return before (2).
|
||||
Thus, we can wait until the DONE record becomes "DONE".
|
||||
If it returns late after activity finished (3), the DONE record
|
||||
is already "DONE" and we don't need to wait. But we can never be
|
||||
in the situation that the DONE record is not yet "ACTIVE" (1).
|
||||
|
||||
To implement a synchronous put, we use the "synchronous group"
|
||||
mechanism. ca_sg_block does not return until all outstanding
|
||||
ca_sg_* requests with the same gid have completed (or timeout).
|
||||
|
||||
Note that we put a bare double here, no DBR_CTRL_*.
|
||||
*/
|
||||
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
|
||||
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
|
||||
|
||||
/* Wait until activity is done.
|
||||
This uses the monitor on the DONE record.
|
||||
Remember that monitors are handled in the background, but only
|
||||
while certain ca functions are active, e.g. ca_pend_event(),
|
||||
ca_pend_io(), or ca_sg_block().
|
||||
*/
|
||||
|
||||
/* First actively read current value of DONE record. */
|
||||
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
|
||||
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
|
||||
printEnumPV(&doneflag);
|
||||
|
||||
/* When not already done, wait for monitor events */
|
||||
while (!doneflag.data.value)
|
||||
{
|
||||
/* wait for the next monitor event */
|
||||
epicsEventWait(monitorEvent);
|
||||
/* This really does nothing until the mext monitor comes.
|
||||
Then, monitorEvent is triggered from the monitor callback
|
||||
function (see above).
|
||||
*/
|
||||
}
|
||||
printEnumPV(&doneflag);
|
||||
}
|
||||
|
||||
/* Step 4: clean up */
|
||||
ca_sg_delete(gid);
|
||||
end:
|
||||
ca_context_destroy();
|
||||
epicsMutexDestroy(accessMutex);
|
||||
epicsEventDestroy(monitorEvent);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user