1. Creating a StreamDevice IOC

In this tutorial, we’ll create an EPICS IOC with EPNix that communicates with a power supply using the StreamDevice support module.

1.1. Prerequisites

Make sure that you have all prerequisites installed by following the Prerequisites section.

1.2. Running the power supply simulator

EPNix provides a power supply simulator for testing your IOC.

To run it:

nix run 'github:epics-extensions/epnix#psu-simulator'

For the rest of the tutorial, leave it running in a separate terminal.

1.3. Creating your top

Use these commands to create an EPNix top:

# Initialise an EPNix top
nix flake new -t 'github:epics-extensions/epnix' my-top
cd my-top

# Enter the EPNix development shell,
# which has EPICS base installed.
nix develop

# Create your app and IOC boot folder
makeBaseApp.pl -t ioc example
makeBaseApp.pl -i -t ioc -p example -a linux-x86_64 Example

# Create a Git repository, and make sure all files are tracked
git init
git add .

After that, you can already check that your top builds with:

nix build -L

This nix build command compiles your IOC and all its dependencies. This eliminates the need for the usual EPICS environment.

If packages are found in the official Nix cache server, Nix downloads them from there instead of compiling them.

This command creates a ./result symbolic link in your current directory containing the build output.

Tip

If you want an explanation of the files generated by the template, read Template files.

1.4. Adding StreamDevice to the EPNix environment

You can add dependencies to the EPNix environment in the ioc.nix file.

To add StreamDevice, update yours as follows:

ioc.nix
   # EPICS support modules can only be in propagatedBuildInputs
   # --
   propagatedBuildInputs = [
-    #epnix.support.StreamDevice
+    epnix.support.StreamDevice
   ];

Then, exit your EPNix development shell by running exit, re-enter it with nix develop and run epicsConfigurePhase.

By changing your package’s dependencies, you have changed build environment: before the change, the build environment didn’t have StreamDevice installed. With this change, StreamDevice is available to build your EPICS top.

Besides being available in the build environment, the EPICS build system must know where to find StreamDevice. EPICS uses the configure/RELEASE and configure/RELEASE.local files to locate EPICS-specific dependencies, often called “EPICS support modules”.

During the “configure” phase, EPNix automatically generates the configure/RELEASE.local file, which is why you need to run epicsConfigurePhase.

Now your development shell has StreamDevice available, and the EPICS build system can find StreamDevice by reading the configure/RELEASE.local file.

Tip

As a rule, each time you edit the ioc.nix file, exit and re-enter your development shell (exit then nix develop), and run epicsConfigurePhase.

1.5. Adding StreamDevice to your EPICS app

To add StreamDevice to your app, make the following changes:

Edit the exampleApp/src/Makefile so your App knows the record types of StreamDevice and its dependencies. Also update it so that it links to the StreamDevice library and its dependencies during compilation.

exampleApp/src/Makefile
# ...

# Include dbd files from all support applications:
example_DBD += calc.dbd
example_DBD += asyn.dbd
example_DBD += stream.dbd
example_DBD += drvAsynIPPort.dbd

# Add all the support libraries needed by this IOC
example_LIBS += calc
example_LIBS += asyn
example_LIBS += stream

# ...

Create the exampleApp/Db/example.proto file which defines the protocol. This file tells StreamDevice what to send to the power supply and what to expect in return.

exampleApp/Db/example.proto
Terminator = LF;

getVoltage {
    out ":volt?"; in "%f";
}

setVoltage {
    out ":volt %f";
    @init { getVoltage; }
}

Create the exampleApp/Db/example.db file. This file specifies the name, type, and properties of the Process Variables (PVs) that EPICS exposes over the network. It also defines how they relate to the functions in the protocol file.

exampleApp/Db/example.db
record(ai, "${PREFIX}VOLT-RB") {
    field(DTYP, "stream")
    field(INP, "@example.proto getVoltage ${PORT}")
}

record(ao, "${PREFIX}VOLT") {
    field(DTYP, "stream")
    field(OUT, "@example.proto setVoltage ${PORT}")
    field(FLNK, "${PREFIX}VOLT-RB")
}

Edit exampleApp/Db/Makefile so that the EPICS build system installs example.proto and example.db:

exampleApp/Db/Makefile
# ...

#----------------------------------------------------
# Create and install (or just install) into <top>/db
# databases, templates, substitutions like this
DB += example.db
DB += example.proto

# ...

Edit your st.cmd file so it knows where to load the protocol file and how to connect to the remote power supply.

iocBoot/iocExample/st.cmd
#!../../bin/linux-x86_64/example

< envPaths

## Register all support components
dbLoadDatabase("${TOP}/dbd/example.dbd")
example_registerRecordDeviceDriver(pdbbase)

# Where to find the protocol files
epicsEnvSet("STREAM_PROTOCOL_PATH", "${TOP}/db")
# The TCP/IP address of the power supply
drvAsynIPPortConfigure("PS1", "localhost:9999")

## Load record instances
dbLoadRecords("${TOP}/db/example.db", "PREFIX=, PORT=PS1")

iocInit()

Run chmod +x iocBoot/iocExample/st.cmd so you can run your command file as-is.

You can test that your top builds by running:

nix build -L

You will see that your IOC does not build. This is because Git isn’t tracking the newly added files, so Nix ignores them too.

Run git add . so Git and Nix track all files, then try nix build -L again.

If everything goes well, you can inspect your compiled top under ./result.

You can observe that the EPICS build system:

  • installs the example app in bin/linux-x86_64 and links to the correct libraries

  • installs example.proto and example.db under db/

  • generates example.dbd and installs it under dbd/

1.6. Running your IOC

To run your IOC, build it first with nix build -L, then change directory into the ./result/iocBoot/iocExample folder. Run:

./st.cmd

You should see the IOC starting and connecting to localhost:9999.

Tip

./result is a symbolic link, so if you make any changes to your IOC and re-run nix build, a terminal already in ./result/iocBoot/iocExample would still point to the old version.

To run the new version, either open a new window and cd into the new ./result/, or in the old location, run:

user@machine .../result/iocBoot/iocExample $ cd .

For quickly re-running an IOC, you can use this command:

user@machine .../result/iocBoot/iocExample $ cd . ; ./st.cmd

1.7. Recompiling with make

Using nix build to compile your IOC each time might feel slow because Nix recompiles your IOC from scratch each time.

If you prefer a more traditional edit/compile/run workflow, enter the development shell with nix develop and use make from here.

Be sure to exit and re-enter the development shell each time you edit Nix files, and re-run epicsConfigurePhase.

1.8. Next steps

The power supply simulator supports more commands. To view them, stop your IOC and open a direct connection to the simulator:

nc localhost 9999
# or
telnet localhost 9999

You can install the nc command from the netcat package, or you can install the telnet command from the telnet package,

Either command opens a prompt where you can type help and press Enter to view the available commands.

Try editing the protocol file and the database file to add those features to your IOC.

For more information about writing the StreamDevice protocol, see the Protocol Files documentation.

You might also want to read Setting up the flake registry.

1.9. Pitfalls

Although EPNix tries to closely follow standard EPICS development, some differences might cause confusion. You can find more information in the Frequently Asked Questions.