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
+130
View File
@@ -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
+26
View File
@@ -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
BIN
View File
Binary file not shown.
+138
View File
@@ -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 {
}
}
+457
View File
@@ -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;
}
+51
View File
@@ -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.
}
BIN
View File
Binary file not shown.
+326
View File
@@ -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;
}
BIN
View File
Binary file not shown.
+367
View File
@@ -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;
}