Skip to content

Documentation

K Lauer edited this page Sep 15, 2020 · 1 revision

Up-to-date version here: https://confluence.slac.stanford.edu/display/PCDS/Templated+IOC+Configurations

Templated IOC Configurations

There is currently some confusion over where EPICS IOCs should be in our svn repository.  For example, all of our unixCam IOCs are found in ioc/common/unixCam/current/iocBoot/ioc-*, but our IPIMB IOCs are found in ioc/XXX/ipimb/current/iocBoot/ioc-*, where XXX is a three-letter hutch identifier.   Although putting all of the IOCs into a single directory makes some maintenance tasks easier (there is only one executable built for all of them, while each hutch-specific directory has its own executable and set of module dependencies), having the IOCs grouped by hutch is often more convenient.  Furthermore, the st.cmd, archive template, and autosave template for each IOC are very similar to every other IOC of that type, with the exception of PV names.  However, as they are separate files, if any calls in the API change, all of these files need to be edited individually, which is a tedious and error-prone process.

This page describes how the code (executable, archive and autosave templates, and st.cmd files) can be kept separate from the data (PV names, trigger configurations, etc.)  This has been implemented for the unixCam IOCs and will shortly be done for the IPIMB IOCs as well.

General File Structure

All of the code for a given type of IOC will be placed in ioc/common/IOCTYPE/current.  This will generally look like the existing build directories, except that the iocBoot directory will no longer contain any actual IOCs.  Instead, it will contain two subdirectories:

  • ioc, which will contain a Makefile to build a generic envPaths file for this type of IOC.  Other global files can also be placed here if desired for inclusion by the st.cmd file.

  • templates, which will contain:

    • Makefile, ioc.sub-arch, ioc.sub-req, edm-ioc.cmd, and st.cmd - Templated versions of the Makefile, archive template, autosave template, edm launcher, and st.cmd file, respectively.  The Makefile in the child IOC directory will include rules that take these files and some configuration files and run a macro expander to create the actual IOC-specific versions of these files.

Most of the files in ioc/templates are specific to the particular type of IOC, but the Makefile will always be something close to:

# Makefile for ioc instance

TOP = ../..

-include ./IOC_APPL_TOP
ifneq (,$(IOC_APPL_TOP))
TOP = $(IOC_APPL_TOP)
endif

include $(TOP)/configure/CONFIG

ifndef ARCH
ARCH = $$IF(ARCH,$$ARCH,linux-x86_64)
endif
INSTALL_LOCATION = ../..
INSTALL_LOCATION_BIN = $(TOP)/bin
TARGETS = envPaths

# Make sure these archive files are fully expanded
USR_ARCHIVEFLAGS += -V

#----------------------------------------------------
# Create and install IOC archive file
#

ARCHIVE += $$IOCNAME.archive

#----------------------------------------------------
# Create and install IOC autosave file
#

REQ += $$IOCNAME.req

include $(TOP)/configure/RULES.ioc
-include $(TOP)/configure/RULES.archive
-include $(TOP)/configure/RULES.autosave

The actual IOCs will be located in ioc/XXX/IOCTYPE/current.  This directory contains a number of IOC configuration files named ioc-*.cfg, along with a very simple Makefile:

# SLAC PCDS Makefile for building templated IOC instances
IOC_CFG += $(wildcard *.cfg)
include /reg/g/pcds/controls/macro/RULES_EXPAND

This Makefile will create a build directory that has the familiar archive, autosave, and iocBoot directories.  Inside the iocBoot directory, there will be one subdirectory for each configuration file in the main child directory, named after the base name of the configuration file.  This will contain the generated st.cmd, edm-ioc.cmd, ioc.sub-arch, and ioc.sub-req files.

The intent is that the configuration file will contain all of the information about the hardware used by the IOC, how it is connected, how it is triggered, what the PV names should be, etc.  Should the API for a particular module change, the ioc/common/IOCTYPE directory will change, but changes in the configuration file will be limited to changing the definition of the RELEASE IOC.

The Format of the Configuration File

The format of the configuration file is intended to be simple, flexible, and easily extendable.  As an example, the configuration file for ioc-mec-ipimb01 is:

RELEASE=/reg/g/pcds/package/epics/3.14/ioc/common/ipimb/R2.0.7
ARCH=linux-x86
ENGINEER=Michael Browne (mcbrowne)
LOCATION=MEC:R64B:39
IOC_PV=IOC:MEC:IPIMB01
EVR0: EVR(NAME=MEC:TC1:EVR:01,TYPE=PMC)
IPIMB(NAME=MEC:TC1:IMB:01,PORT=/dev/ttyPS7,BLDID=22,EVR0,TRIG=0)
IPIMB(NAME=MEC:USR:IMB:01,PORT=/dev/ttyPS6,BLDID=21,EVR0,TRIG=1)
IPIMB(NAME=MEC:USR:IMB:02,PORT=/dev/ttyPS5,BLDID=20,EVR0,TRIG=2)
TRIGGER(NAME=MEC:TC1:IMB:01,EVR0,TRIG=0)
TRIGGER(NAME=MEC:USR:IMB:01,EVR0,TRIG=1)
TRIGGER(NAME=MEC:USR:IMB:02,EVR0,TRIG=2)
TRIGGER(NAME=,EVR0,TRIG=3)

The first five lines are just variable defines.  The first line defines the release location of the parent IOC, and the second declares that it is a 32-bit IOC.  (By default, IOCs are 64-bit.).  After that, the file contains a number of instantiations with parameters.  First, an EVR named EVR0 is instantiated, followed by three IPIMBs and four TRIGGERS.  Most of the parameters are simple variable defines ("NAME=MEC:TC1:EVR:01", etc.), but it is also possible to tell an instantiation to use a particular version of some other instantiation.  In this case, the parameter "EVR0" passed into each IPIMB says that the trigger for this IPIMB comes from specified EVR.  If two EVRs were present, another name could be used to select a trigger from the second EVR.

The format of the configuration file is, unfortunately, rather strict.  White space is generally to be avoided, other than in the middle of variable values and between lines.  This could change in the future.

The templated edm-ioc.cmd, ioc.sub-arch, ioc.sub-req, and st.cmd files will use the instantiations and definitions in the configuration file to produce the actual files needed to run the IOC.  should additional defines or instantiations be included that these files do not use, no warning or error will be given.  The resulting output should always be examined for reasonableness, as typos in the configuration file could result in empty files being generated!

"New Style" Configuration Files

An alternate syntax for configuration files also exists which has the advantage that instantiations can be spread out over several lines.  This syntax also relaxes the need for '=' and totally removes most commas.  In this new syntax, the configuration file is assumed to have assignments at the top and then one or more instantiations introduced by one of:

INSTANCE instance_type
INSTANCE instance_type instance_name

The following lines contain the parameters as described above, except that '=' is optional, commas are unnecessary, and newlines are permitted.  All parameters up to the next INSTANCE belong to the current instance.  So, for example, the configuration file in the previous section can be rewritten as:

RELEASE /reg/g/pcds/package/epics/3.14/ioc/common/ipimb/R2.0.7
ARCH linux-x86
ENGINEER Michael Browne (mcbrowne)
LOCATION MEC:R64B:39
IOC_PV IOC:MEC:IPIMB01

INSTANCE EVR EVR0
    NAME MEC:TC1:EVR:01 TYPE PMC
INSTANCE IPIMB
    NAME MEC:TC1:IMB:01 PORT /dev/ttyPS7
    BLDID=22
    EVR0 TRIG=0

INSTANCE IPIMB
    NAME MEC:USR:IMB:01 PORT /dev/ttyPS6
    BLDID=21
    EVR0 TRIG=1

INSTANCE IPIMB
    NAME MEC:USR:IMB:02 PORT /dev/ttyPS5
    BLDID=20
    EVR0 TRIG=2

INSTANCE TRIGGER
    NAME=MEC:TC1:IMB:01 EVR0 TRIG=0

INSTANCE TRIGGER
    NAME=MEC:USR:IMB:01 EVR0 TRIG=1

INSTANCE TRIGGER
    NAME=MEC:USR:IMB:02 EVR0 TRIG=2

INSTANCE TRIGGER
    NAME="" EVR0 TRIG=3

Note that the two types of instances cannot be mixed, and that empty strings must be explicitly quoted (as in the fourth TRIGGER in the example).

The Macro Processor

The macro processor is a simple python program installed as /reg/g/pcds/controls/macro/expand.  The format of the command line is one of:

expand [ -c CONFIGURATION ] INPUT_FILENAME OUTPUT_FILENAME [ ADDITIONAL_DEFINES ]
expand [ -c CONFIGURATION ] VARIABLE_NAME

If no CONFIGURATION is specified, the default is config.  The ADDITIONAL_DEFINES at the end of the line are treated as additional configuration information to be added to the beginning of the config file.  (The Makefile uses this to pass in definitions for TOP (the hutch IOC directory) and IOCTOP (the common IOC directory).)  In the first command, the macro processor reads the configuration file and then expands the input file using these definitions to produce the output file.  Instantiations in the configuration file produce additional variable definitions.  For example, the first IPIMB instantiation in the file given above will produce definitions for IPIMBNAME0, IPIMBPORT0, IPIMBBLDID0, and IPIMBTRIG0, the second will produce definitions for IPIMBNAME1, IPIMBPORT1, IPIMBBLDID1, and IPIMBTRIG1, and so on.  The second form of command line reads in the configuration, and prints the value of the given variable to the standard output.  (This is used within a Makefile to retrieve values.)

Most of what is in the input file is ordinary text, which is passed directly into the output file.  However, $$ is an escape sequence that indicates that the macro processor needs to do some work.  The various escapes are:

  • $$VARNAME or $$(VARNAME) is a simple variable substitution.  If VARNAME is undefined, no error is generated and no output is produced.

  • $$DIRNAME is the name (not the path!) of the current working directory.  As the macro processor is usually run in one of the iocBoot/ioc-* directories, this is the current IOC name.

  • $$TRANSLATE(VARNAME,"STR1","STR2") evaluates the variable name VARNAME, and then substitutes the characters in STR1 for the corresponding characters in STR2.  Both strings may contain character ranges separated by a "-".  A sequence of "-" at either the beginning or end of of the string is understood to be literally "-" and not a range.  For example, the IPIMB driver wants names separated with "-", while PV names are separated with ":".  So when the box name passed to the driver is $$TRANSLATE(NAME,":","-"), where NAME is the PV name for this IPIMB. "MEC:TC1:IMB:01" will be translated to "MEC-TC1-IMB-01".  Similarly, $$TRANSLATE(NAME,"A-Z","a-z") will translate "MEC:TC1:IMB:01" to "mec:tc1:imb:01".

  • $$IF(VARNAME)if-body$$ELSE(VARNAME)else-body$$ENDIF(VARNAME) is a simple conditional.  If VARNAME has a value that is not the empty string, the if-body is processed, otherwise the else-body is processed.  The $$ELSE and else-body may be omitted.

  • $$IF(VARNAME,VALUE)if-body$$ELSE(VARNAME)else-body$$ENDIF(VARNAME) is a simple conditional.  If VARNAME is identical to VALUE, the if-body is processed, otherwise the else-body is processed.  The $$ELSE and else-body may be omitted.

  • $$IF(VARNAME*,if-body***,else-body)** is an abbreviated form of the conditional that is useful for short expressions where neither body contains a comma or right parenthesis.

  • $$INCLUDE(FILENAME) tries to evaluate FILENAME as a variable and if this succeeds, the value is treated as a file name to be opened and processed.  If the evaluation fails, the string FILENAME itself is treated as a file name to be opened and processed.

  • $$COUNT(INSTNAME) gives the number of instantiations of INSTNAME, formatted as an integer.

  • $$CALC{EXPR} or $$CALC{EXPR,FORMAT} is used to do arithmetic calculations.  EXPR is macro-expanded, and the result is treated as a numeric expression.  Note the "{}" not "()"!!  (This is to simplify the parsing of expressions that include parentheses.) Any names in the resulting expansion are evaluated as macro processor definitions, with undefined variables or variables that cannot be evaluated as a number evaluating to zero.  The result is formatted using the specified FORMAT string, which defaults to "%d".  (Note that the FORMAT should not be surrounded by double-quotes unless double-quotes are desired in the output!)

  • $$LOOP(INSTNAME)loop-body$$ENDLOOP(INSTNAME) iterates over instances of a particular type.  The loop-body is processed once for each instance, with additional variable definitions for the parameters of the particular instance and INDEX defined to be the number of the instance.   So in the case of the first IPIMB instance in the sample config file, additional variables NAME, PORT, BLDID, and TRIG will be defined.  In addition, the parameter EVR0 will create additional variables EVRNAME and EVRTYPE which refer to the values given by the first EVR instantiation.

  • $$NAME(INST,VARNAME) is used to get correct names when you want to pass two named instances as parameters to an instantiation.  For instance, in the IPIMB config file example above, the IPIMB and TRIGGER instantiations take a single named instance: EVR0.  But suppose for some reason, you had another instantiation that needs to specify two EVRs.  In this case, which EVR the variable EVRTYPE refers to isn't clear.  So instead, the instantiation names will be passed in as parameters: EXAMPLE_WITH_TWO(E1=EVR0,E2=EVR1).  Then inside a $$LOOP for this instance, $$NAME(E1,TYPE) refers to the TYPE of EVR0, and $$NAME(E2,TYPE) would refer to the TYPE of EVR1.

An example of $$LOOP will make things clearer.  From ioc/common/ipimb/iocBoot/templates/st.cmd:

$$LOOP(IPIMB)

dbLoadRecords( "db/ipimb.db",
"RECNAME=$$NAME,BOX=$$TRANSLATE(NAME,":","-"),TRIGGER=$$EVRNAME:CTRL.DG$$(TRIG)E")

$$ENDLOOP(IPIMB)

When this is processed with the sample config, the resulting output is:

dbLoadRecords( "db/ipimb.db",
"RECNAME=MEC:TC1:IMB:01,BOX=MEC-TC1-IMB-01,TRIGGER=MEC:TC1:EVR:01:CTRL.DG0E")

dbLoadRecords( "db/ipimb.db",
"RECNAME=MEC:USR:IMB:01,BOX=MEC-USR-IMB-01,TRIGGER=MEC:TC1:EVR:01:CTRL.DG1E")

dbLoadRecords( "db/ipimb.db",
"RECNAME=MEC:USR:IMB:02,BOX=MEC-USR-IMB-02,TRIGGER=MEC:TC1:EVR:01:CTRL.DG2E")

Some General Notes on Writing Templated "st.cmd" Files

In general, most st.cmd files will begin:

#!$$IOCTOP/bin/linux-x86_64/executable-name

epicsEnvSet("IOCNAME", "$$DIRNAME")

epicsEnvSet("ENGINEER", "$$ENGINEER" )

epicsEnvSet("LOCATION", "$$LOCATION" )

...

< $(IOCTOP)/iocBoot/ioc/envPaths

< envPaths

cd( "$(IOCTOP)" )

Then if the created st.cmd file is set executable, it will automatically pick up the correct executable from the ioc/common/IOCTYPE directory.  The generic envPaths is read to get the defines for the particular module versions, and then the local envPaths is read to get the information for the particular IOC.  The IOC then changes to the ioc/common/IOCTYPE directory, as this is where the database and other files live.

Files that are specific to the IOC must be referred to by a full path beginning with $$TOP.  In particular, the autosave file path must be specified as:

set_requestfile_path( "$(TOP)/autosave" )

In some cases, configuration file readability can be improved by defining additional EPICS environment variables in the templated st.cmd file and allowing an instance parameter to select between environment variables.  For example, there are two types of EVRs commonly in use: the SLAC EVR, and the PMC EVR from MRF.  These require different database files as well as different parameters to ErConfigure.  Rather than have two instance parameters, one for the database file and one for the ErConfigure parameter, it is preferable to use a single TYPE parameter, that can be either SLAC or PMC.  Then, in the st.cmd template:

epicsEnvSet("EVRID_PMC", "1")
epicsEnvSet("EVRID_SLAC", "15")
epicsEnvSet("EVRDB_PMC", "db/evr-ipimb.db")
epicsEnvSet("EVRDB_SLAC", "db/evrSLAC-ipimb.db")
...
$$LOOP(EVR)
dbLoadRecords( $(EVRDB_$$TYPE), "IOC=$(IOCBASE),EVR=$$(NAME)")
$$ENDLOOP(EVR)
...
$$LOOP(EVR)
ErConfigure( 0, 0, 0, 0, $(EVRID_$$TYPE) )
$$ENDLOOP(EVR)

It is also possible to allow a limited amount of customization of st.cmd files.  For example, the common unixCam does this by defining an instantiation type EXTRA, which has as parameters the NAME of a file to be included, and time indicator parameters INITTIME, LOADTIME, and FINISHTIME to indicate that the file should be included at initialization time, at record loading time, or at the end of the file, respectively.  Then, the template files includes fragments such as:

$$LOOP(EXTRA)
$$IF(FINISHTIME)
$$INCLUDE(NAME)
$$ENDIF(FINISHTIME)
$$ENDLOOP(EXTRA)

Then, when a template includes an instances such as:

EXTRA(NAME=load.cmd,LOADTIME=1)
EXTRA(NAME=finish.cmd,FINISHTIME=1)

the contents of the file finish.cmd will be inserted at this point in the file.  (load.cmd will be included earlier, at record loading time.)  If no EXTRA instances are defined, then no additional files are included.

Macro Expansion Within Configuration Files

The macro processor actually makes two passes over the configuration file.  On the first pass, the variable definitions are found, and on the second pass, the variable definitions and instantiations are found.  The intent of this is that the parameter list for an instantiation may contain a macro that expands to a list of parameters, as in:

TYPEA="A=3,B=4"
TYPEB="A=7,B=9"
DEVICE(NAME=FOO,$$TYPEA)
DEVICE(NAME=BAR,$$TYPEB)
DEVICE(NAME=BAZ,$$TYPEA,B=7)

The TYPEA and TYPEB macros are expanded on the second pass, resulting in three instantiations:

DEVICE(NAME=FOO,A=3,B=4)
DEVICE(NAME=BAR,A=7,B=9)
DEVICE(NAME=BAZ,A=3,B=4,B=7)

Since later parameters overwrite earlier ones, the last instantiation is equivalent to "DEVICE(NAME=BAZ,A=3,B=7)".    This becomes useful for IOCs such as motors, which have large numbers of parameters, but only a few typical default settings for them.  A file to be $$INCLUDEd could define several default settings, and the motors that use these settings could be easily declared, possibly overwriting one or more parameters to handle small differences.

Naming Convention

In the interest of keeping things consistent and simple across different IOC templates, please adhere to the following naming convention for macro fields, and add any new fields as you define them in your own templates.  Note that it may be necessary in some cases to have more than one field of the same type in a macro, in this case, preserve the name but add a prefix, for example, if you need two digi ports, call them "ABC_PORT" and "XYZ_PORT" where "ABC" and "XYZ" are useful short descriptors, but it is still obvious this is a PORT field.

Fields:

Field Name Value Format or Example Purpose
NAME MEC:USR:IMB:01 PV Base Name for Record
PORT  (1) digi-xcs-09:2107 Remote Digi Port Address
PORT  (2) /dev/ttyPS5 Local Serial Port Device Path
TYPE Varies; Enumeration For Enumerated Types (eg: EVR/TYPE := {PMC, SLAC} )
AI CXI:DS1:AIN:01:ai0:in2 Analog Input PV

Macros:

Macro Name Link to Deployment Guide Purpose
EVR EVR Device Record
IPIMB IPIMB Deployment Guide IPIMB Diode Readout Box Record
TRIGGER EVR Trigger Channel Record
IMS (Various) New IMS Motor IOC Deployment / Migration IMS Motor & Composite-Device Records
CHILLER Thermocube Deployment Guide Thermocube Chiller Device Records
AGILENT Agilent E3632A Power Supply Deployment Guide Agilent E3632A Power Supply Records