_images/icon.svg

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.

Indices and tables