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'
).