Creating an IOC

Introduction

Once the module has been installed (see Installation Tutorial) we can create a simple EPICS Input/Output Controller (IOC).

An EPICS IOC created with the help of pythonSoftIOC and softioc is referred to as a “Python soft IOC”. The code below illustrates a simple IOC with two Process Variables (PVs):

# Import the basic framework components.
from softioc import softioc, builder
import cothread

# Set the record prefix
builder.SetDeviceName("MY-DEVICE-PREFIX")

# Create some records
ai = builder.aIn('AI', initial_value=5)
ao = builder.aOut('AO', initial_value=12.45, always_update=True,
                  on_update=lambda v: ai.set(v))

# Boilerplate get the IOC started
builder.LoadDatabase()
softioc.iocInit()

# Start processes required to be run after iocInit
def update():
    while True:
        ai.set(ai.get() + 1)
        cothread.Sleep(1)

cothread.Spawn(update)

# Finally leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())

Each section is explained in detail below:

from softioc import softioc, builder
import cothread

The softioc library is part of pythonSoftIOC. The two submodules softioc.softioc and softioc.builder provide the basic functionality for Python soft IOCs and are the ones that are normally used.

Using the cothread Library is one of the two possible libraries the IOC can use for asynchronous operations.

Note

Using the cothread Library doesn’t work on Windows or on a Mac M1. You can use asyncio instead by following Use asyncio in an IOC

ai = builder.aIn('AI', initial_value=5)
ao = builder.aOut('AO', initial_value=12.45, always_update=True,
                  on_update=lambda v: ai.set(v))

PVs are normally created dynamically using softioc.builder. All PV creation must be done before initialising the IOC. We define a lambda function for on_update on ao such that whenever we set ao, ai will be set to the same value. The always_update flag ensures that the on_update function is always triggered, which is not the default behaviour if the updated value is the same as the current value.

builder.LoadDatabase()
softioc.iocInit()

Initializing the IOC is simply a matter of calling two functions: LoadDatabase() and iocInit(), which must be called in this order. After calling LoadDatabase() it is no longer possible to create PVs.

def update():
    while True:
        ai.set(ai.get() + 1)
        cothread.Sleep(1)

cothread.Spawn(update)

We define a long-running operation that will increment the value of ai once per second. This is run in the background by Using the cothread Library.

softioc.interactive_ioc(globals())

Finally the application must refrain from exiting until the IOC is no longer needed. The interactive_ioc() runs a Python interpreter shell with a number of useful EPICS functions in scope, and passing globals() through can allow interactive interaction with the internals of the IOC while it’s running. The alternative is to call something like cothread.WaitForQuit() or some other Using the cothread Library blocking action.

Note

The following section should only be used for debugging purposes, and not for production testing, as the functions used do not necessarily reflect what a real user would see when querying an IOC over the network. See Read data from an IOC for proper network access examples.

In this interpreter there is immediate access to methods defined in the softioc.softioc module. For example the dbgf() function can be run to observe the increasing value of AI:

>>> dbgf("MY-DEVICE-PREFIX:AI")
DBF_DOUBLE:         36
>>> dbgf("MY-DEVICE-PREFIX:AI")
DBF_DOUBLE:         37

And the dbpf() method allows data to be set and to observe the functionality of the lambda passed to on_update . We set the value on AO and read the value on AI (exact values will vary based on time taken):

>>> dbgf("MY-DEVICE-PREFIX:AI")
DBF_DOUBLE:         15
>>> dbpf("MY-DEVICE-PREFIX:AO","999")
DBF_DOUBLE:         999
>>> dbgf("MY-DEVICE-PREFIX:AI")
DBF_DOUBLE:         1010

Creating PVs

See the documentation of softioc.builder for details, but an overview is provided here.

PVs are created internally and dynamically using functionality provided by epicsdbbuilder, which in this context simply provides mechanisms for creating .db files, but softioc.builder also binds each created PV to a special Python device – this allows PV processing to be hooked into Python support.

PV creation must be done in two stages: first the device name must be set by calling SetDeviceName(). After this PVs can be created by calling any of the following PV creation functions:

These functions create, respectively, Python device bound records of the following types:

ai, ao, bi, bo, longin, longout, mbbi, mbbo, stringin, stringout, waveform

Occasionally it may be desirable to create a soft record without Python device support, particularly if any other record type is required. This can be done using the corresponding record creation functions provided as methods of softioc.builder.records. For example, if a calc record is required then this can be created by calling softioc.builder.records.calc. See an example in Use soft records in an IOC.

For all records created by these methods both get() and set() methods are available for reading and writing the current value of the record. For IN records calling set() will trigger a record update (all IN records are by default created with SCAN='I/O Intr').