Why use pythonSoftIOC?
EPICS IOCs are flexible and modular, why do we need to wrap it in Python? This page attempts to answer that question and list a few good use-cases for it.
Calculating PVs from other values
Some use cases require PVs to be calculated from multiple sources. This is
possible in EPICS records with calc
or aSub
records, but pythonSoftIOC
allows you to write this as:
import numpy as np
from cothread.catools import camonitor
from softioc import builder, softioc
# The PVs we want to average and initial values
PVs = [f"DEVICE{i}:CURRENT" for i in range(100)]
values = np.empty(len(PVs)) * np.nan
# The PV we want to serve, initially undefined
avg = builder.aOut("AVERAGE:CURRENT")
# Start the IOC
builder.LoadDatabase()
softioc.iocInit()
# Make a monitor on the PVs to keep the value up to date
def update_avg(value: float, index: int):
values[index] = value
mean = np.mean(values)
# If all PVs have returned a value, set the mean
if not np.isnan(mean)
avg.set(mean)
camonitor(PVs, update_avg)
# Leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())
Note
If using asyncio
then you would use aioca.camonitor
instead of
cothread.catools.camonitor
:
from aioca import camonitor
You also need to do the camonitor
from inside the dispatcher’s
event loop:
dispatcher.loop.call_soon_threadsafe(camonitor, PVs, update_avg)
Dynamically created PVs
Other use cases will require PVs to be created based on the features of a device. As EPICS database records are statically created at IOC boot, you can generate the database with a script, but pythonSoftIOC allows you to write this as:
from my_device_lib import Device
import cothread
from softioc import builder, softioc
# Connect to the device
device = Device("hostname")
device.connect()
# Make a record for each parameter it has
records = {}
for param in device.get_params():
records[param] = builder.aIn(param.name, param.value)
# Start the IOC
builder.LoadDatabase()
softioc.iocInit()
# Poll the device for changes and update the records
def update_params():
while True:
for param in device.poll_changed_params():
records[param].set(param.value)
cothread.Spawn(update_params)
# Leave the IOC running with an interactive shell.
softioc.interactive_ioc(globals())
Existing Python Support
It may be that you have specific device support written in Python that you wish to expose as PVs. This could be either in the form of a device support library or using a Python library to calculate PV values as above.