Skip to content
Keenan Lang edited this page Apr 13, 2017 · 5 revisions

About

What Are These IOC Shell Files I'm Seeing?

IOC shell files are lines of startup code pertaining to a single device's configuration. They allow you to quickly add or drop support for specific devices in an IOC with a single line and not have to worry that they missed a command somewhere or didn't get the right value on something. As well, since the IOC shell scripts are managed by the developers of a device's support, you can worry less about whether the startup code you are seeing are out of date or customized, these scripts are exactly how the creator wanted you to use their code.

Why Do They Have A .iocsh File Extension Instead Of .cmd?

These scripts are targeted to be run by the IOC shell rather than being cross-compatible with the vxWorks interpreter. Many of the scripts could potentially be run under the vxWorks interpreter, but a lot will end up having issues, usually pertaining to long line lengths.

How Do I Use These Scripts?

The scripts are designed to be used with the iocshLoad function that was introduced in EPICS Base 3.15. This function is a modification of the command iocsh which ran a file through the IOC shell interpreter. iocshLoad does the same thing, however it takes an additional parameter which is a set of macro definitions.

While the script is running, these macros are treated as if they are environment variables with the given values, and when the script terminates, the definitions disappear from scope. The IOC shell scripts are written to use these temporary environment variables as parameters defining how to configure a device's support. All that a user needs to do to take advantage of these scripts is to call iocshLoad with the name of a script and some parameters:

iocshLoad("$(MODULE)/iocsh/script.iocsh", "PARAM1=Hello, PARAM2=World")

This means that something like setting up the autosave module to properly run autosaveBuild across the IOC, which would normally take about 20 lines of code can be managed with just:

iocshLoad("$(AUTOSAVE)/iocsh/autosave_settings.iocsh", "PREFIX=xxx:, SAVE_PATH=$(TOP)/iocBoot/$(IOC)"
iocshLoad("$(AUTOSAVE)/iocsh/autosaveBuild.iocsh", "PREFIX=xxx:"

What If I Only Have Base 3.14 (or earlier)?

You still have the capability of using most of these scripts. Some of the scripts will use either iocshLoad or iocshRun which means that they won't work under versions before 3.15, however any of the other ones can be run by using epicsEnvSet to set permanent environment variables rather than temporary ones. So, instead of a line like this:

iocshLoad("script.iocsh", "PARAM1=Hello, PARAM2=World")

You'd end up with the following:

epicsEnvSet("PARAM1", "Hello")
epicsEnvSet("PARAM2", "World")
iocsh("script.iocsh")

But, because the environment variables are permanent, you should be careful and set all parameters that a script uses. You don't want to accidentally have a value from a previous call to the script bleed over because you forgot to set the variable to a new value.


Best Practices

Location

For the modules in synApps, we will be making the effort to put all relevant IOC shell scripts for that module in a top-level iocsh/ directory. It is recommended to do the same with any distributed support, however, scripts can be located and run from anywhere by changing where the iocshLoad command is pointed to. In order to assist in being able to be run from anywhere, it is recommended to use the macros defined by envPaths to point to any databases or other scripts that are necessary to include in your device support so that filepaths don't break.

Comments

Scripts should generally contain details at the top of the script about what environment variables need to be set in order for the script to function correctly. However, these comments are not extremely useful during runtime, so as of EPICS Base 3.16, any comment that start with #- wont show up in the IOC output during startup. Try to preface your comments about the use of a script with #- so you aren't cluttering the shell output with unnecessary lines.

Default Values

Many devices have different configuration settings that can be changed in certain instances, but for most uses would be set to some default value. It is a good idea to allow the users of a script to be able to set that value, if needed, but they shouldn't have to define the default value every time they want to load a script. The macro system provides the ability to do this with default values. By adding an equals sign after the macro's name you can specify a default value if the macro isn't defined.

$(MACRO=default)

Just remember that the default must be set on any instance of the macro. If you find yourself using a specific macro enough times that specifying the default each time is making your script unwieldy, it may be a good idea to set an environment variable:

epicsEnvSet("MACRO_VAL", "$(MACRO=default)")

However be careful to use an environment variable name that is less than likely to be being used by the user themselves. You don't want to accidentally change an environment variable that they are using.

Common Parameter Names

Every device is going to have a slate of different parameters that need to be set for it to work. However, take a moment when writing any scripts to see if any of your parameters are similar to other scripts and try to use the same names to describe those parameters. Some common ones:

PREFIX - IOC Prefix, usually $(P) in databases
INSTANCE - Device instance prefix, usually $(R), $(Q), or $(N) in databases
PORT - Asyn communication port name
M_XYZ - The name of a motor, XYZ would either be an axis, a rotation, or a number depending on how the device works.


Tips and Tricks

Converting from Strings to Numbers

Occasionally, you will want or need to do some sort of conversion from a user-friendly value to one that the machine understands. Usually, this will be converting from a YES/NO value to a number.

The IOC shell wasn't designed to handle branching statements, nor was it designed to do advanced calculations, so this isn't as straightforward of a process as we would like. Instead, you will have to slightly abuse the environment variable and macro system to do the conversion. If YES or NO was contained in the parameter PARAM, a quick approach would look like so:

epicsEnvSet("VAL_YES", 1)  
epicsEnvSet("VAL_NO", 0)  
$(VAL_$(PARAM))

Where $(VAL_$(PARAM)) would then evaluate to either 1 or 0 depending on the value of PARAM.

However, this approach ends up cluttering the user's environment with two environment variables that really only needed to be used for this single conversion. By using iocshRun instead, you can run the line of conversion code with temporary environment variables that are immediately discarded after the line is run. Like so:

iocshRun("$(VAL_$(PARAM))", "VAL_YES=1, VAL_NO=0")

Finally, it may be most user friendly to accept multiple different capitalizations and then choose a default for any other answers. Which would lead us to this:

iocshRun("$(VAL_$(PARAM)=0)", "VAL_YES=1, VAL_Yes=1, VAL_Y=1, VAL_yes=1")

Device Code That Should Only Run Once

Certain devices have an initialization command that needs to be run that specifies how many of a certain driver will be ever be created during startup. These commands should only ever be run once, have to be run before the configuration of a certain driver, and are specifically tied to a certain device. Most generation 2 motor drivers work exactly like this, where there is an initialization call that sets up the maximum number of controllers of a certain type, and then individual configuration calls for each controller.

It would be nice for the user of a script not to have to remember to call a device's initialization function before running said device's script, however you can't just put the call into your script normally, because then it would be run a second time if the user runs the script to configure a second device. The macro system luckily does allow for a way to make sure that a certain line of code is only ever run a single time by defining a macro to have the comment character as it's value:

$(MYDEVICE_INIT_COMPLETE="") function_to_run_once()

function_to_run_every_time()

epicsEnvSet("MYDEVICE_INIT_COMPLETE", "#")

The first time the IOC shell reaches these lines, the macro $(MYDEVICE_INIT_COMPLETE) isn't defined yet and defaults to nothing, allowing the function to run. However, during any subsequent runs of those lines $(MYDEVICE_INIT_COMPLETE) gets replaced with the comment character # and the IOC shell ignores it, meaning that only function_to_run_every_time is run. Be sure to use some device-specific macro name instead of MYDEVICE.