Overview¶
DevIOC is a package which enables python based EPICS IOC Soft Device support all within python. It allows you to define the IOC database model in a manner similar to Django Database models, and to use the model to develop dynamic, IOC servers.
To use the full capabilities, it is is highly recommended to use a GObject compatible main loop, such as the one provided by PyGObject or even better, the GObject compatible Twisted reactor.
This library has been used to support very complex network IOC devices with non-trivial communication protocols. It works!
Getting Started¶
Before you can use DevIOC, you’ll need to install it and its dependencies. We recommend installing it inside a virtual environment using the following commands on the shell
$ python -m venv devioc
$ source devioc/bin/activate
(devioc) $ pip3 install devioc
It is a pure python module, although it requires PyGObject, Numpy and Twisted.
DevIOC Also requires a fully functional EPICS Base installation. If you haven’t so yet, please build and install EPICS Base. It has been tested with EPICS Base versions 3.14 to 3.16.
Once it is installed, you can run the tests to make sure your system is properly _setup, and all required dependencies are available.
(devioc) $ python -m unittest -v devioc.tests.test_ioc
Write your first IOC¶
Your IOC application can be structured at will although we recommend the following directory template
myioc
├── bin
│ ├── app.server # Command to run IOC Application
│ └── opi.client # Command to run Operator Display Application
├── opi # Directory containing Operator Display screens
└── myioc # Python package for your IOC Application and all other supporting modules
├── __init__.py
└── ioc.py # IOC module containing your IOC application
A program is included to create a default IOC project. This can be run within the devioc virtual environment as follows
(devioc) $ devioc-startproject myioc
This will create a directory structure similar to the one listed above, except the opi.client file which you will have to create yourself based on your preferred EPICS display manager. The project created as such is a fully functional example application that you can modify as needed.
Creating the IOC Model¶
If you are familar with the Django Framework, the IOC model should feel natural. All IOC models should inherit from devioc.models.Model and declare database records using the record types defined in devioc.models.
For example:
from devioc import models
class MyIOC(models.Model):
enum = models.Enum('enum', choices=['ZERO', 'ONE', 'TWO'], default=0, desc='Enum Test')
toggle = models.Toggle('toggle', zname='ON', oname='OFF', desc='Toggle Test')
sstring = models.String('sstring', max_length=20, desc='Short String Test')
lstring = models.String('lstring', max_length=512, desc='Long String Test')
intval = models.Integer('intval', max_val=1000, min_val=-1000, default=0, desc='Int Test')
floatval = models.Float(
'floatval', max_val=1e6, min_val=1e-6, default=0.0,
prec=5, desc='Float Test'
)
floatout = models.Float('floatout', desc='Test Float Output')
intarray = models.Array('intarray', type=int, length=16, desc='Int Array Test')
floatarray = models.Array('floatarray', type=float, length=16, desc='Float Array Test')
calc = models.Calc(
'calc', calc='A+B',
inpa='$(device):intval CP NMS',
inpb='$(device):floatval CP NMS',
desc='Calc Test'
)
Once the model is defined, it can then be instanciated within the application. For example:
ioc = MyIOC('MYIOC-001')
Note
In the example above, the floatarray field uses a macro substitution variable $(device) within its specification. By default the first argument passed to the model is used as the device name and will be substituted for $(device) references.
Additional macro substitution variables can be used and provided to the model during initialization as key-value pairs using the macros keyword argument of the model. In practice, the best way to pass these external variables is to make them command-line arguments of the application, and then forward them to the model through the application as keyword arguments.
This will create an IOC database with Process variable fields MYIOC-001:enum, MYIOC-001:toggle, … etc, where the process variable name is generated based on the model name, and the field name. Once instanciated, the IOC is ready to be used and alive on the Channel Access network. However, for more responsive applications, it is recommended to to create an IOC Application as well.
-
class
devioc.models.
Model
(device_name, callbacks=None, command='softIoc', macros=None)¶ IOC Database Model
- Parameters
device_name – Root Name of device this will be available as the $(device) macro within the model
callbacks – Callback handler which provides callback methods for handling events and commands
command – The softIoc command to execute. By default this is ‘softIoc’ from EPICS base.
macros – additional macros to be used in the database as a dictionary
Process Variable records will be named <device_name>:<record_name>.
If Callback Handler is not provided, it is assumed that all callbacks are defined within the model itself. The expected callback methods must follow the signature:
def do_<record_name>(self, pv, value, ioc): ...
which accepts the active record (pv), the changed value (value) and the ioc instance (ioc). If the Model is also the callbacks provider, self, and ioc are identical, otherwise ioc is a reference to the database model on which the record resides.
-
shutdown
()¶ Shutdown the ioc application
See also
Record Types for detailed documentation about database records.
Creating the IOC Application¶
The IOC Application manages the IOC database and should provide the logic for the application. This can include connecting to a device through a serial interface, over the network, handling commands from the model, processing and generating new values for the database fields, etc. The sky is the limit.
For example, let us create an application which uses the model above and responds to changes to the MYIOC-001:toggle field.
class MyIOCApp(object):
def __init__(self, device_name):
self.ioc = MyIOC(device_name, callbacks=self)
def do_toggle(self, pv, value, ioc):
"""
I am called whenever the `toggle` record's value changes
"""
if value == 1:
# Command activated, value will return to 0 after some time
print('Toggle Changed Value', value)
ioc.enum.put((ioc.enum.get() + 1) % 3, wait=True) # cycle through values
def do_enum(self, pv, value, ioc):
print('New Enum Value', value)
def shutdown(self):
# needed for proper IOC shutdown
self.ioc.shutdown()
This application is initialized with the IOC device name.
Running the IOC Application¶
The script bin/app.server is responsible for running the IOC Application. An example script is generated by the devioc-startproject command. It can be modified to suit your needs. For example:
#!/usr/bin/env python
import os
import logging
import sys
import argparse
# Twisted boiler-plate code.
from twisted.internet import gireactor
gireactor.install()
from twisted.internet import reactor
# add the project to the python path and inport it
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from devioc import log
from myioc import ioc
# Setup command line arguments
parser = argparse.ArgumentParser(description='Run IOC Application')
parser.add_argument('--verbose', action='store_true', help='Verbose Logging')
parser.add_argument('--device', type=str, help='Device Name', required=True)
if __name__== '__main__':
args = parser.parse_args()
if args.verbose:
log.log_to_console(logging.DEBUG)
else:
log.log_to_console(logging.INFO)
# initialize App
app = ioc.MyIOCApp(args.device)
# make sure app is properly shutdown
reactor.addSystemEventTrigger('before', 'shutdown', app.shutdown)
# run main-loop
reactor.run()
This example uses the Twisted framework. It is highly recommended to use it too.
The above script is executed to start the application within the devioc virtual environment as follows:
(devioc) $ myioc/bin/app.server --device MYIOC-001
Record Types¶
Records are defined within the devioc.models module. The following record types are currently supported:
-
class
devioc.models.
Record
(name, desc=None, **kwargs)¶ Base class for all record types. Do not use directly.
- Parameters
name – Record name (str)
- Keyword Arguments
desc – Description (str). Sets the DESC field
* – additional keyword arguments
-
class
devioc.models.
Enum
(name, choices=None, out='', default=0, **kwargs)¶ Enum record type
- Parameters
name – Record name (str)
- Keyword Arguments
choices – list/tuple of strings corresponding to the choice names, values will be 0-index integers.
default – default value of the record, 0 by default. Sets the VAL field
* – Extra keyword arguments
-
class
devioc.models.
BinaryInput
(name, default=0, inp='', shift=0, **kwargs)¶ Binary record type for converting between integers and bits
- Parameters
name – Record name (str)
- Keyword Arguments
inp – Input link. Sets the INP field
shift – shift value by this number of bits to the right. Sets the SHFT field
default – default value of the record, 0 by default. Sets the VAL field
* – Extra keyword arguments
-
class
devioc.models.
BinaryOutput
(name, default=0, out='', shift=0, **kwargs)¶ Binary record type for converting between integers and bits
- Parameters
name – Record name (str)
- Keyword Arguments
out – Output link specification. Sets the OUT field
shift – shift value by this number of bits to the right. Sets the SHFT field
default – default value of the record, 0 by default. Sets the VAL field
* – Extra keyword arguments
-
class
devioc.models.
Toggle
(name, high=0.25, zname=None, oname=None, **kwargs)¶ Toggle field corresponding to a binary out record.
- Parameters
name – Record name (str)
- Keyword Arguments
high – Duration to keep high before returning to zero. Sets the HIGH field.
zname – string value when zero. Sets the ZNAM field
oname – string value when high. Sets the ONAM field
* – Extra keyword arguments
-
class
devioc.models.
Integer
(name, max_val=0, min_val=0, default=0, units='', **kwargs)¶ Integer Record.
- Parameters
name – Record Name.
- Keyword Arguments
max_val – Maximum value permitted (float), default (no limit). Sets the DRVH and HOPR fields
min_val – Minimum value permitted (float), default (no limit). Sets the DRVL and LOPR fields
default – default value, default (0.0). Sets the VAL field
units – engineering units (str), default empty string. Sets the EGU field
* – Extra keyword arguments
-
class
devioc.models.
Float
(name, max_val=0, min_val=0, default=0.0, prec=4, units='', **kwargs)¶ Float Record.
- Parameters
name – Record Name.
- Keyword Arguments
max_val – Maximum value permitted (float), default (no limit). Sets the DRVH and HOPR fields
min_val – Minimum value permitted (float), default (no limit). Sets the DRVL and LOPR fields
default – default value, default (0.0). Sets the VAL field
prec – number of decimal places, default (4). Sets the PREC field
units – engineering units (str), default empty string. Sets the EGU field
* – Extra keyword arguments
-
class
devioc.models.
String
(name, max_length=20, default=' ', **kwargs)¶ String record. Uses standard string record, or character array depending on length
- Parameters
name – Record name (str)
- Keyword Arguments
max_length – maximum number of characters expected. Char Array records will be used for fields bigger than 40 characters, in which case the NELM and FTVL field will be set.
default – default value, empty string by default
* – Extra keyword arguments
-
class
devioc.models.
Array
(name, type=<class 'int'>, length=None, **kwargs)¶ Array Record.
- Parameters
name – Record Name
type – Element type (str or python type), supported types are [‘STRING’, ‘SHORT’, ‘FLOAT’, int, str, float]
length – Number of elements in the array
kwargs – Extra kwargs
-
class
devioc.models.
Calc
(name, scan=0, prec=4, **kwargs)¶ Calc Record
- Parameters
name – Record name
- Keyword Arguments
scan – scan parameter, default (0 ie passive). Sets the SCAN field
prec – number of decimal places, default (4). Sets the PREC field
calc – Calculation. Sets CALC field
inpa – Input A specification. Sets the INPA field
* – Extra keyword arguments. Any additional database fields required should be specified as lower-case kwargs.
-
class
devioc.models.
CalcOut
(name, out='', oopt=0, dopt=0, **kwargs)¶ CalcOutput Record
- Parameters
name – Record name
- Keyword Arguments
out – OUT Output specification
oopt – OOPT Output Execute field
dopt – DOPT Output Data field
* – Extra keyword arguments, supports Calc kwargs also.