Skip to main content

Support library for writing EPICS SoftIOCs in Python.

Project description

softioc

A python library supporting simple creation of soft EPICS iocs.

Features

  • generates customizable IOC status channels
  • handles timing of channel updates
  • handles all interaction with pcaspy
  • optional customizable alarm system
  • supports active servers that generate their own channel values.
  • supports passive servers that reflects values from external inputs.
  • can generate an INI file listing the channels of the IOC

Quick Start

This example IOC, also in examples/active.py, read an input dchannel and squares it, writing the result to an output channel.

It also reports the number of times the IOC process function has been run.

from ligo_softioc import SoftIOC                                                             
                                                                                        
                                                                                        
process_count = 0                                                                       
                                                                                        
def process(ioc: SoftIOC, gps_time_sec: int) -> None:                                   
    """                                                                                 
    Update the custom channels.                                                         
                                                                                        
    Auto-generated channels are auto-updated.                                           
    :param ioc:                                                                         
    :param gps_time_sec:                                                                
    :return:                                                                            
    """                                                                                 
    global process_count                                                                
    process_count += 1                                                                  
                                                                                        
    # Read the input - change this value with caput                                     
    in_val = ioc.getParam("INPUT")                                                      
                                                                                        
    # square INPUT and write it to SQUARED_OUTPUT                                       
    ioc.setParam("SQUARED_OUTPUT", in_val ** 2)                                         
                                                                                        
    # set to how many times process() has run                                           
    ioc.setParam("PROCESS_COUNT", process_count)                                        
                                                                                        
if __name__ == "__main__":                                                              
    # setup the ioc with a channel prefix.                                              
    # the ioc will by default run process() about 10 times per second                   
    ioc = SoftIOC(                                                                      
        prefix="X1:IOC-",                                                               
        process_func=process)                                                           
                                                                                        
    # add in some channels                                                              
                                                                                        
    channels = {                                                                        
        "INPUT": {'prec': 3},                                                           
        "SQUARED_OUTPUT": {'prec': 3},                                                  
    }                                                                                   
                                                                                        
    ioc.add_channels(channels)                                                          
                                                                                        
    ioc.add_channel("PROCESS_COUNT", {'type': 'int'})                                   
                                                                                        
    # call this when all channels are added.                                            
    # and before you try to set the value of any channel                                
    ioc.finalize_channels()                                                             
                                                                                        
    # setup the input varible so we know how it'll start                                
    ioc.setParam("INPUT", 0)                                                            
                                                                                        
    # start the ioc                                                                     
    ioc.start()                                                                         

Auto-generated channels

The following channels are autogenerated. The values are also set automatically.

These channels use the normal prefix, but the prefix can be lengthened with an optional parameter to SoftIOC.

The IOC declared with

SoftIOC(
    prefix="X1-SYS_EXAMPLE_",
    ioc_chan_prefix="IOC_"
)

will use the prefix "X1-SYS_EXAMPLE_IOC_" for the channels mentioned in this section. All other channels will use the prefix "X1-SYS_EXAMPLE_".

GPS : The current time in seconds from the GPS epoch (see below).

TIMESTAMP : The current time formatted as YYYY-MM-DD HH:mm:SS TZ

START_GPS
: The GPS second recorded when the IOC started.

START_TIMESTAMP : The start time formatted the same as TIMESTAMP

UPTIME_SEC : Seconds since the start of the IOC.

UPTIME_STR
: The uptime of the IOC as an easy to understand string, e.g. "3 weeks".

HOSTNAME : The name of the host the IOC is running on

PROCESS
: Some info about the process controller, e.g. "systemd"

ERROR_MESSAGE
: The latest error message generated by the IOC, or an empty string if there haven't been any errors.

ERROR_GPS : The time of the last error in GPS seconds.

ERROR_TIMESTAMP
: The time of the last error in TIMESTAMP format.

The GPS Epoch

GPS times count from Jan 6, 1980 UTC.

Passive IOC

A passive IOC, also known as a 'separate server' IOC serves custom channels but does not process them. Some external process is responsible for setting the channel value. This external process is sometimes called the 'server'. Some channels that reference it have 'SERVER' in the name.

An example can be found in examples/passive.py.

A passive IOC does not need a process function.

A passive IOC gets several more auto-generated channels. These channels can help monitor the external process.

Some of the channels should be set directly by the external process, and some are calculated by the SoftIOC. The server is responsible for setting these channels: SERVER_START_GPS, SERVER_GPS, LAST_PROCESS_GPS,

SERVER_START_GPS : The start time of the server in GPS seconds. Set by the server.

SERVER_GPS
: The current time of the server in GPS seconds. Set by the server.

SERVER_START_TIMESTAMP : The start time of the server as a string. Set automatically by SoftIOC.

SERVER_UPTIME_SEC : Uptime of the server. Set automatically by SoftIOC.

SERVER_UPTIME_STR : Server uptime as a reasonable string, e.g. '3 months'. Set automatically by SoftIOC.

LAST_PROCESS_GPS
: Timestamp of the last time the server set channel values. Set by the server.

LAST_PROCESS_TIMESTAMP : The last porcess time of the server as a string.

SINCE_LAST_PROCESS_SEC : Number of seconds since LAST_PROCESS_GPS was updated by the server. Automatically set by SoftIOC.

SINCE_LAST_PROCESS_STR : Time since LAST_PROCESS_GPS was updated by the server in a readable format. Automatically set by SoftIOC.

SERVER_RUNNING : A value that indicates if the server has run recnetly 0 - Invalid state. SINCE_LAST_PROCESS_SEC is negative. 1 - Running. SINCE_LAST_PROCESS_SEC is less than or equal to the maximum allowed, default 60 seconds. 2 - Stalled. SINCE_LAST_PROCESS_SEC is greater than the maximum allowed. Set automatically by SoftIOC.

Printing an INI file.

Add in all channels, call finalize_channels() on the SoftIOC, then call print_ini() on the SoftIOC.

Send to std output, one line per name in alphabetical order, each surrounded by square brackets:

  • All custom channels that aren't strings.
  • From the automatic channels: START_GPS, GPS, UPTIME_SEC,
  • If separate_server == True, SERVER_START_GPS, SERVER_GPS, SERVER_UPTIME_SEC, LAST_PROCESS_GPS, SINCE_LAST_PROCESS_SEC, SERVER_RUNNING,
  • If there are any alarm families, print the GPS_TIME channel on the alarm family.

Alarms

Alarms can be optionally added to the SoftIOC.

Create an alarm object, then add it to the soft IOC before calling finalize_channels().

from ligo_softioc import SoftIOC, Alarm

...

ioc = SoftIOC(...)

...

alarm = Alarm(
    name = 'chan_to_big`,
    text = 'the value of an example chan is greater than the allowed maximum`,
    predicate = lambda: ioc.getParam("X1:SYS-EXAMPLE_CHAN") > 100   
)
ioc.add(alarm)

...

ioc.finalize_channels()

Basic alarm channels

When an alarm is added to a SoftIOC, these additional channels are automatically created and updated.
Where a lowercase 'n' appears at the end of a channel name, n takes on the values from 1..10, so each listing represents 10 channels:

ALARM_GPS_TIME : GPS time of the last alarm

ALARM_MESSAGE : Message of the last alarm. This is the 'text' parameter of the alarm.

ALARM_TIMESTAMP : Time of the last alarm as a string.

ALARM_DUMP : An EPICS input. Set to 1 to send up to 100 currently-set alarms to the standard out of the IOC.

ALARM_DUMP_HISTORY : An EPICS input. Set to 1 to send a history of up to the last 100 alarms set to standar out of the IOC.

ALARM_HISTORY_GPS_TIME_n : The GPS time of the 'n'th alarm that has been set in order from the most recent alarm.

ALARM_HISTORY_TIMESTAMP_n : A formatted timestamp of the 'n'th alarm that has been set in order from the most recent alarm.

ALARM_HISTORY_MESSAGE_n : The alarm message of the 'n'th alarm that has been set in order from the most recent alarm.

ALARM_GPS_TIME_n : The GPS time of the 'n'th alarm that is currently set.

ALARM_TIMESTAMP_n : The formatted timestamp of the 'n'th alarm that is currently set.

ALARM_MESSAGE_n : The alarm message of the 'n'th alarm that is currently set.

The "ALARM" prefix of these channels can be changed using alarm families. See below.

Alarm Groups

Instead of adding alarms directly to an IOC, they can be added to an AlarmGroup object. Then the AlarmGroup object can be added to the IOC.

An alarm group is a set of alarms that produces only a single alarm at one time, even when multiple alarms are set.

The alarm group will always produce the most recent alarm set within the hour. After an hour has elapsed, The alarm group will cycle through older alarms and produce them one at a time, a new one each hour, eventually cycling back around the most recently set alarm.

If alarms are added to a SoftIOC directly, they are added to a default alarm group named "alarm".

from ligo_softioc import SoftIOC, Alarm, AlarmGroup

...

ioc = SoftIOC(...)

...

zoneB_bad_temp = Alarm(...)
zoneB_bad_humidity = Alarm(...)
zoneB_alarm_group = AlarmGroup('zoneB')
zoneB_alarm_group.add(zoneB_bad_temp)
zoneB_alarm_group.add(zoneB_bad_humidity)
ioc.add(zoneB_alarm_broup)

Alarm Families

Instead of adding alarm groups directly to an IOC, they can be added to an AlarmFamily.

An alarm family is a logical set of AlarmGroups. Alarm families create the alarm epics channels.

If Alarm or AlarmGroup objects are added directly to the IOC, then they will be put into tha default alarm family named 'alarm'. This is the reason for the 'ALARM_' prefix on the basic alarm channels described above.

from ligo_softioc import SoftIOC, Alarm, AlarmGroup, AlarmFamily

...

lvea_zoneB_group = AlarmGroup(...)
lvea_zoneC_group = AlarmGroup(...) 
cer_alarm_group = AlarmGroup(...)

...

lvea_alarm_family = AlarmFamily('lvea_alarm')
cer_alarm_family = AlarmFamily('cer_alarm')
lvea_alarm_family.add_alarm_group(lvea_zoneB_group)
lvea_alarm_family.add_alarm_group(lvea_zoneC_group)
cer_alarm_family.add_alarm_group(cer_alarm_group)

...

This example will create a set of alarm channels for each alarm family, base on its name.

There will be both LVEA_ALARM_GPS_TIME and CER_ALARM_GPS_TIME. Both LVEA_ALARM_MESSAGE and CER_ALARM_MESSAGE etc.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ligo_softioc-0.1.2.tar.gz (27.6 kB view hashes)

Uploaded Source

Built Distribution

ligo_softioc-0.1.2-py3-none-any.whl (27.8 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page