Skip to content

See Frontends: overview for details about frontends, and Frontends: programming for details about writing frontends. This page contains the full API reference of the midas.frontend module.

midas.frontend

This module provides a way to write midas frontends in python.

It supports many of the features of the C frontend (including the concepts of "Equipment"), with a few limitations.

There are 2 main classes you need to create derived versions of: * EquipmentBase * FrontendBase

See full examples in examples/basic_frontend.py and examples/multi_frontend.py.

Limitations

  • Only supports periodic, polled, and manually-triggered equipment (no interrupt-triggered option).
  • Only supports writing events in MIDAS format, not FIXED format.
  • Multi-threaded support is untested and not guaranteed.

midas.frontend.EquipmentBase

You should define classes that inherit from this base class.

There are 3 main types of equipment:

  • Periodic equipment (midas.EQ_PERIODIC) - The frontend will call readout_func() periodically. readout_func() should return an event, or None if no event should be sent.
  • Polled equipment (midas.EQ_POLLED) - The frontend will call poll_func() often, which says whether an event is ready or not; if an event is ready, the frontend will then call readout_func().
  • Manually-triggered equipment (midas.EQ_MANUAL_TRIG) - The frontend will wait for a webpage or code to trigger an event (via RPC), and then call readout_func().

In your derived class you should implement the following functions:

  • __init__(), which calls this base class' __init__()
  • readout_func() that creates and returns a midas.event.Event
  • poll_func() if you are creating a polled equipment

See the documentation of those functions for more details, and the examples in the examples/ directory.

Attributes:

  • client (`midas.client.MidasClient`) –

    Client that you can use from within your equipment's functions to access the ODB etc.

  • name (str) –

    Name of this equipment.

  • frontend_name (str) –

    Name of the frontend/client that created this equipment.

  • settings (dict) –

    The content of /Equipment//Settings (or None if not settings exist). Updated automatically when there are changes in the ODB.

  • common (dict) –

    The content of /Equipment//Common. Updated automatically when there are changes in the ODB. Note that the python frontend does not use the Write cache size parameter at all, but it is still present in the ODB to match the structure of C++ frontends.

  • stats (dict) –

    Number of events this equipment has sent, etc.

  • last_get_event_time (float) –

    When we last tried to get an event from this equipment. From time.monotonic().

  • odb_var_write_period_secs (float) –

    How often we should copy an event into the ODB for this equipment.

  • last_odb_var_write_time (float) –

    When we last copied an event into the ODB from this equipment. From time.monotonic().

  • odb_common_dir (str) –

    /Equipment//Common

  • odb_stats_dir (str) –

    /Equipment//Statistics

  • odb_variables_dir (str) –

    /Equipment//Variable

  • odb_settings_dir (str) –

    /Equipment//Settings

  • is_manual_trig (bool) –

    If you're in readout_func(), whether this call was from a MANUAL_TRIG RPC call, or a regular periodic/polled trigger

  • _inited (bool) –

    Whether __init__() has been called

__init__(midas_client, equip_name, default_common, default_settings=None, overwrite_common=False)

You MUST call this function from your derived class' constructor.

It sets up the relevant entries for this equipment in the ODB, sets up hotlinks so we know when /Equipment//Common or /Equipment//Settings change, and sets the initial "status" of this equipment.

Parameters:

  • midas_client (MidasClient) –

    The object we can use for ODB access etc.

  • equip_name (str) –

    The name of this equipment. It should be unique. We do NOT do any automatic substitution of the frontend index etc.

  • default_common (InitialEquipmentCommon) –

    The initial settings we should use to populate /Equipment//Common the first time this equipment is run. After that, the ODB settings take precendence unless you also pass overwrite_common=True.

  • default_settings (dict, `collection.OrderedDict`, or None, default: None ) –

    Any ODB parameters we should create in /Equipment/<xxx>/Settings if that directory does not already exist. We do NOT update the values of any ODB keys that already exist. However, we will add/remove ODB keys if the structure you pass in does not match the existing ODB structure. This allows you to easily add/remove settings as your frontend development progresses.

  • overwrite_common (bool, default: False ) –

    If True, we'll overwrite /Equipment//Common with the values in the code, overwriting any changes that the user has made in the ODB since the frontend was last started. This is equivalent to setting BOOL equipment_common_overwrite = TRUE; in mfe C++ frontends.

detailed_settings_changed_func(path, idx, new_value)

You MAY implement this function in your derived class if you want to be told when a variable in /Equipment//Settings has changed.

If you don't care about what changed (just that any setting has changed), implement settings_changed_func() instead,

We will automatically update self.settings before calling this function, but you may want to implement this function to validate any settings the user has set, for example.

It may return anything (we don't check it).

Parameters:

  • path (str) –

    The ODB path that changed

  • idx (int) –

    If the entry is an array, the array element that changed

  • new_value (int/float/str etc) –

    The new ODB value (the single array element if it's an array that changed)

poll_func()

If you are creating a polled equipment (midas.EQ_POLLED), you MUST implement this function in your derived class.

  • It should return True if you want readout_func() to be called (which will then construct and return the actual event).
  • It should return False if no event should be read out.

readout_func()

You MUST implement this function in your derived class if you ever want to send events.

  • It should return None if you don't want to send an event at this time.
  • It should return a midas.event.Event if you do want to send an event; the frontend will convert it to the binary format midas expects automatically.

If you're pushing the midas python code to its limits, you may return a list of midas.event.Event. For example, if you're using a polled frontend and have a backlog of 5 events to send, it will be more efficient to send all 5 in one call to readout_func() rather than waiting for poll_func()/readout_func() to be called 5 times each.

The framework will automatically set the event's event_id, serial_number and timestamp if you do not set them in your readout_func() code. However you must manually set the trigger_mask if you want to use a value that isn't 0.

Example

def readout_func(self):
    ev = midas.event.Event()
    ev.create_bank("MYBK", midas.TID_DOUBLE, [1.234, 3.14])

    # You must manually set the trigger mask if you don't want
    # to use the default value of 0!
    ev.header.trigger_mask = self.common["Trigger mask"]

    return ev

set_status(status_text, status_color='greenLight')

You can call this function to set the "status" of this equipment. The status is shown on the main midas status page.

Parameters:

  • status_text (str) –

    This should be a short string, or else the formatting of the main status page may get a little wacky. Absolute max length is 255 chars; longer strings will be truncated.

  • status_color (str, default: 'greenLight' ) –

    There are 4 pre-defined colors (greenLight, yellowLight, yellowGreenLight, redLight) or you can set any CSS background color name / hex value. Absolute max length is 31 chars; longer strings will be truncated.

settings_changed_func()

You MAY implement this function in your derived class if you want to be told when a variable in /Equipment//Settings has changed.

If you want more fine-grained information about WHAT changed (not just that some setting changed), implement detailed_settings_changed_func() instead.

  • settings_changed_func() => High-level "something changed"
  • detailed_settings_changed_func() => Low-level "this specific thing changed"

We will automatically update self.settings before calling this function, but you may want to implement this function to validate any settings the user has set, for example.

It may return anything (we don't check it).

midas.frontend.FrontendBase

A frontend can control multiple equipment. You should define a class that inherits from this base class.

Configuration of the frontend/equipment should be done in your class' __init__(). It should:

  • Call this base class' init
  • Create and add any equipment.

You may implement the begin_of_run() functions etc in your derived class, and they will be called at the appropriate transition.

See the frontend examples in the examples/ directory for more.

Attributes:

  • client (`midas.client.MidasClient`) –

    The object we can use for ODB access etc.

  • equipment (dict of {str) –

    EquipmentBase}): Keyed by equipment name.

  • run_state (int) –

    The current run state (midas.STATE_RUNNING etc).

  • buffers (dict of {str) –

    int}): Buffer name to buffer handle.

  • last_stats_update (float) –

    The last time we updated the equipment statistics in the ODB (from time.monotonic())

  • stats_update_period_secs (float) –

    How often we should update equipment statistics in the ODB.

  • _inited (bool) –

    Whether this base clas init has been called.

__enter__()

Context manager (with style).

__exit__(type, value, traceback)

Context manager (with style).

__init__(frontend_name)

You MUST call this function from your derived class' constructor.

It creates a midas client that you can use for ODB access, and registers functions that will be called on run transitions.

If you need to edit your frontend name based on the frontend index (supplied by the -i command line flag) you should call parse_args() BEFORE calling this base class init(); the index will be available from frontend_index. See examples/multi_frontend.py for an example.

Parameters:

  • frontend_name (str) –

    The name of this client/frontend. It should be unique within your experiment. We do NOT do any automatic substitution based on the frontend index.

add_equipment(equip)

You MUST call this function to add equipment to your frontend before you call run(). You can call it multiple times to add several equipment if you want.

Parameters:

  • equip (something that inherits from `EquipmentBase`) –

begin_of_run(run_number)

You MAY implement this function in your derived class. It will be called at the start of each run, and you can use it to prevent the run from starting if needed.

Parameters:

  • run_number (int) –

    The new run number.

It should return:

  • None or 1 - Transition was successful
  • int that isn't 1 - Transition failed
  • 2-tuple of (int, str) - Transition failed and the reason why

end_of_run(run_number)

You MAY implement this function in your derived class.

See begin_of_run documentation.

frontend_exit()

You MAY implement this function in your dervived class.

To GUARANTEE that this function gets called, you should use a context manager style for the frontend class (with syntax).

Example

# This style will work in most cases, but is not guaranteed to call
# `frontend_exit()` when the program exits (depends on your specific 
# python version/implementation and python code):
my_fe = MyFrontend("my_fe")
my_fe.run()

# This style is guaranteed to call `frontend_exit()`:
with MyFrontend("my_fe") as my_fe:
    my_fe.run()

pause_run(run_number)

You MAY implement this function in your derived class.

See begin_of_run documentation.

resume_run(run_number)

You MAY implement this function in your derived class.

See begin_of_run documentation.

run()

You MUST call this function to run your frontend.

It is the main routine that talks to midas and gets events from your equipment as needed.

It will run forever until the frontend is killed (either through a signal or via the midas Programs webpage).

set_all_equipment_status(status_text, status_color='greenLight')

Like set_equipment_status(), but apply to same status to all equipment controlled by this frontend.

set_equipment_status(equipment_name, status_text, status_color='greenLight')

You can call this function to set the "status" of an equipment. The status is shown on the main midas status page.

Parameters:

  • equipment_name (str) –

    The name of an equipment you added.

  • status_text (str) –

    This should be a short string, or else the formatting of the main status page may get a little wacky.

  • status_color (str, default: 'greenLight' ) –

    There are 4 pre-defined colors (greenLight, yellowLight, yellowGreenLight, redLight) or you can set any CSS background color name / hex value (max length 32 chars).

midas.frontend.InitialEquipmentCommon

Equipment settings are stored in the ODB at /Equipment//Common. But the first time we run a frontend/equipment we need to populate the ODB. This class/object defines the parameters the develop should set.

At a minimum, the developer must update equip_type and event_id.

Attributes:

  • equip_type (int) –

    Whether this is a periodic or polled equipment. One of midas.EQ_xxx (or a bitmask from multiple such flags).

  • event_id (int) –

    The "Event ID" events from this equipment will be flagged with by default. Each equipment in your experiment should have a different event ID.

  • buffer_name (str) –

    Name of the buffer events from this equipment will be sent to. The framework will create the buffer for you if needed. Empty buffer name means do not send events to a buffer.

  • trigger_mask (int) –

    The "Trigger Mask" events from this equipment will be flagged with by default.

  • period_ms (int) –

    How often the readout function will be called for periodic equipment.

  • read_when (int) –

    When to call the readout function of this equipment. One of midas.RO_xx (or a bitmask from multiple such flags).

  • log_history (bool) –

    Whether to periodically copy events produced by this equipment into the ODB (in /Equipment//Variables) so they get logged in the history database.

midas.frontend.parse_args()

Parse command-line arguments. Specifically we support:

  • -i for the frontend index
  • -h for the midas hostname (defaults to what's set by env vars)
  • -e for the midas expt name (defaults to what's set by env vars)
  • -d to enable debug logging
  • -D to become a daemon
  • -O to become a daemon but retain stdout

If you want to support more arguments for your frontend, you can work on the global parser object and call add_argument() etc.

Example

parser = midas.frontend.parser
parser.add_argument("-x", type=int, help="Some custom argument")

args = midas.frontend.parse_args()

print("X argument was %s" % args.x)

# Presumably you will use the argument for something in your frontend class.
my_fe = SomeFrontendClass(args.x)
my_fe.run()

Returns:

  • Namespace –

    The result of calling parser.parse_args()