See Sequencer: overview for details about the midas sequencer. This page contains the full API reference of the midas.sequencer module, which is used to both run PySequencer, and write the sequencer scripts.
Note that many of the SequenceClient functions are inherited from the MidasClient class, and their documentation is repeated on this page.
midas.sequencer
midas.sequencer.BreakpointState
Used internally by the sequence thread to track stepping through the script line-by-line when in debug mode.
Attributes:
-
stepping(bool) –whether we're currently stepping through line-by-line
-
prev_step_over_line_num(int / None) –the last line number we stepped-over, used to ensure the user doesn't have to click multiple times for the same line (e.g. if the line is a list comprehension, we step over it in one go, rather than each iteration individually)
midas.sequencer.SequenceClient
Bases: MidasClient
This class contains all the midas-related functions that you can use as part of
your sequencer script. Unlike a regular midas client, your script does NOT need
to periodically call communicate, even if it's going to be running for a
really long time.
Your script has access to a global variable named seq, which is an instance of
this class. The seq object is also passed to the three main functions you can
define in your script (define_params(), sequence() and at_exit()).
As well as the functions defined here, you have access to all of the
functions in midas.client.MidasClient, such as odb_set() and odb_get().
An example sequencer script
# You can import arbitrary python modules
import time
# You can define/call arbitrary functions
def subfn(a):
b = a*6
return a^b
# You MAY define this special function, which will get run when we
# first load the script. It should call `register_param()` for any
# "parameters" that you want the user to be able to set when they are
# starting the sequence.
def define_params(seq):
seq.register_param("num_loops", "Number of loops", 2)
seq.register_param("do_sleep", "Do extra sleep", True)
# You MUST define this special function, which will get run when the user
# starts the sequence. Progress through the script is displayed on a webpage.
def sequence(seq):
# You can call arbitrary functions that are defined either in your script
# or in external modules. We only track progress through functions in the
# main script, not those in other modules. i.e. we'll show the line-by-line
# progress through `subfn()`, but will only show that we're in `time.sleep()`,
# not whatever the internal logic of `time.sleep()` is.
subfn()
time.sleep(3.5)
# You can access the value of "parameters" you defined
num_loops = seq.get_param("num_loops")
# If you use `seq.range()` instead of the regular `range()`, the webpage will
# show a progress bar of how far through the loop we are. Nested loops are
# supported up to 10 levels deep (you can have deeper nesting, we just won't show
# the progress on the webpage).
for i in seq.range(num_loops):
print(i)
# If you use `seq.wait_seconds()` instead of `time.sleep()`, we'll track the
# progress of the sleep on the webpage using a progress bar.
seq.wait_seconds(1.5)
# You can use most functions from `midas.client.MidasClient`
rn = seq.odb_get("/Run info/Run number")
if seq.get_param("do_sleep"):
print("Doing extra sleep")
seq.wait_seconds(3)
# You can start and stop other programs, and wait until they're running.
seq.start_client("Logger", True)
seq.wait_clients_running(["Logger"])
# You can start and stop runs
seq.start_run()
# You can send messages that will be displayed on the webpage
seq.sequencer_msg("We started a run!")
seq.wait_seconds(10)
seq.stop_run()
print("fin")
# You MAY define this special function that will get run when the main
# `sequence()` function finishes (even if the main function raised an exception).
def at_exit(seq):
if seq.odb_get("/Runinfo/State") != midas.STATE_STOPPED:
seq.stop_run()
range
Use this in a for loop to track progress on the Sequencer webpage.
Argument usage matches the standard python range() function:
seq.range(4)-> [0, 1, 2, 3]seq.range(2, 6)-> [2, 3, 4, 5]seq.range(20, 50, 10)-> [20, 30, 40]
Example
for i in seq.range(5):
seq.odb_set("/path/to/something", i)
seq.wait_seconds(2.5)
Each time we iterate over the loop, a progress bar on the sequencer webpage will be updated.
brpc_client_call(connection, cmd, args, as_event=False, use_numpy=False, max_len=1000000)
Call the BRPC (aka "binary RPC") function exposed by another client.
For BRPC, the other client returns arbitrary binary data (possibly encoded as
a midas event). Use jrpc_client_call if the other client will be returning
a string.
Parameters:
-
connection(int) –The connection handle returned to you when connecting.
-
cmd(str) –The command to execute. The other client's BRPC handler must know what to do with this.
-
args(str) –Any other arguments to pass to the other client. Specify as a single string (probably separating arguments with spaces).
-
as_event(bool, default:False) –Whether you expect the data from the other client to be arbitrary binary data, or encoded as a midas event. If True, we'll automatically decode the event for you.
-
use_numpy(bool, default:False) –Is
as_eventis True, whether to use numpy arrays or regular python tuples for bank data in the receivedEvent. -
max_len(int, default:1000000) –The maximum length you expect the returned data to be (in bytes). This is an imposition of the underlying C++ code, not the python side.
Null bytes
Note that if your cmd or args parameter contains an embedded
null byte, the argument will be truncated at that point.
Raises:
-
MidasError–if the other client returns a non-SUCCESS status
Returns:
-
ctypes.Array of ctypes.char_c–the data populated by the other client if
as_eventis False. -
Event–the data populated by the other client if
as_eventis True.
check_deferred_transition()
If we're currently in a deferred transition, call the user's callback function to see if the work has been completed. If so, proceed with the actual transition.
If you are writing a program that registers a deferred transition, but is NOT using the frontend framework, you must call this function repeatedly in your main loop. In the frontend framework it is called for you automatically.
client_exists(client_name, exact_match=True)
Check whether a client/program is running.
Note that this function does not consider the client that is performing the request (i.e. if program "mytest" asks if "mytest" is running, this function will return False!).
Parameters:
-
client_name(str) –name of the other client
-
exact_match(bool, default:True) –If False, we'll look for all clients that start with this name. E.g. "myfe" would match "myfe_1", "myfetest" etc. If True, we require an exact match of the name.
Returns:
-
bool–Whether this client is running
clients_exist(client_names, return_as_dict=False, exact_match=True)
Check whether multiple clients/programs are running.
Parameters:
-
client_names(list of str) –names of the clients to check.
-
return_as_dict(bool, default:False) –whether to return a single boolean or a dict with the per-client status.
-
exact_match(bool, default:True) –If False, we'll look for all clients that start with the name. E.g. "myfe" would match "myfe_1", "myfetest" etc. If True, we require an exact match of the name.
Returns:
-
dict of {str: bool}–If return_as_dict is True: whether each client is running or not.
-
boolean–If return_as_dict is False: whether all the clients/ programs are running or not. If any client/program is not running, we'll return False.
communicate(*args, **kwargs)
You cannot call this function from a sequencer script.
connect_to_other_client(other_client_name)
Connect to a different midas client, so that you can later call RPC functions exposed by that client.
Parameters:
-
other_client_name(str) –name of the other client (e.g. as shown in the Programs webpage)
Returns:
-
int–handle that you should pass to
disconnect_from_other_client,jrpc_client_callandbrpc_client_call.
create_alarm_class(class_name, execute_command='', execute_interval_secs=0, stop_run=False)
Alarm classes define what to do when an alarm is triggered. All alarms belong to a single class. You can create as many classes as you like. The alarm classes "Alarm" and "Warning" come pre-defined by midas.
Parameters:
-
class_name(str) –The name of this alarm class. An entry will be created in the ODB at
/Alarms/Classes/<class_name>if it doesn't already exist. If the entry does exist, it will be overwritten with the values you provide here. -
execute_command(str, default:'') –System command to run when an alarm with this class is triggered (e.g. "python /path/to/some/script.py" or "~/path/to/script.sh")
-
execute_interval_secs(int, default:0) –How often to run the above command, in seconds. NOTE - if this is 0, THE COMMAND WILL NOT BE RUN AT ALL! If you just want the command to be run once, when the alarm is first raised, set a very high value like 1e9 (but < 2^31 as the value is a signed 32-bit int in the ODB).
-
stop_run(bool, default:False) –Whether to stop the run when an alarm of this class is triggered.
create_evaluated_alarm(alarm_name, odb_condition, message=None, alarm_class='Alarm', activate_immediately=True)
Create an alarm that will trigger if a condition in the ODB is met.
To deactivate this alarm, set /Alarms/Alarms/<alarm_name>/Active to False in the ODB.
To completely delete this alarm, just delete /Alarms/Alarms/<alarm_name> from the ODB.
Parameters:
-
alarm_name(str) –The name of this alarm. An entry will be created in the ODB at
/Alarms/Alarms/<alarm_name>if it doesn't already exist. Max 31 characters, and no "/" characters allowed. -
odb_condition(str) –What to check for. Examples are "/Path/to/float < 0.3" and "/Path/to/boolean == y". Max 256 characters.
-
message(str, default:None) –The message to be displayed to users. Max 80 characters. If not set, the message will be the same as
odb_condition. -
alarm_class(str, default:'Alarm') –The "class" of this alarm (i.e. what to do when this alarm is triggered). See
create_alarm_classfor more about alarm classes. -
activate_immediately(bool, default:True) –Whether to activate this alarm immediately or not. (This doesn't mean it will actually trigger, just that midas will start evaluating whether to trigger - "active" and "triggered" have different meanings).
Raises:
-
ValueError–If invalid alarm or class name provided
deregister_disconnect_callback()
De-register a function that was previously passed to
register_disconnect_callback.
deregister_event_request(buffer_handle, request_id)
Cancel an event request that had previously been opened with
register_event_request.
Parameters:
-
buffer_handle(int) –The return value from a previous call to
open_event_buffer. -
request_id(int) –The return value from a previous call to
register_event_request.
deregister_message_callback()
Unregister a callback that was registered with register_message_callback.
deregister_transition_callback(transition)
Deregister functions previously registered with
register_transition_callback, so they are no longer called.
Parameters:
-
transition(int) –One of
midas.TR_START,midas.TR_STOP,midas.TR_PAUSE,midas.TR_RESUME,midas.TR_STARTABORT.
disable_tracing()
Disable tracing/tracking of the current sequence state. You might want to do this if you have a "hot" loop that gets executed many times.
Tracing each iteration of such a loop may slow down your program by a factor 2 or more. Note that this also applies to "hidden" loops like list comprehensions.
Only use this functionality if you've identified a hot loop that is affecting performance of your script.
Example
# The webpage will just show the state at `do_abc()` and `do_xyz()`, skipping all
# updates that we're in `do_something_quick()`. May speed up the program a lot.
do_abc()
seq.disable_tracing()
for i in range(1000000):
do_something_quick()
seq.enable_tracing()
do_xyz()
disconnect()
Nicely disconnect from midas. If this function isn't called at the end of a program, midas will complain in the message log and it might take a while for our disappearance to be noted.
This function will be called automatically from the destructor of this class, HOWEVER some implementations of python do not guarantee that del will be called at system exit.
To be sure of a trouble-free disconnection from midas, you should either:
- Call this function at the end of your program.
- Wrap your usage in a
withstatement:with midas.client.MidasClient('name') as c: .......
disconnect_from_other_client(connection, shutdown_other_client=False)
enable_tracing()
Opposite of disable_tracing.
exec(command)
get_all_required_program_names()
Get all the clients/programs listed in /Programs in the ODB that have the "Required" key set to True.
Returns:
-
list of str–names that can be passed to
start_client,stop_client,connect_to_other_clientetc.
get_experiment_dir()
get_message_facilities()
Get a list of "facilties" that send messages to the message log.
By default the "midas" facility is used when sending messages, but
others can be used. The facility name can be used when calling
get_recent_messages.
Returns:
-
list of string–
get_param(name)
Get the value a user set for a given parameter.
Parameters:
-
name(str) –Name of a parameter previously created by
register_param
Returns:
-
int/float/bool/str etc–whatever is in the ODB
get_recent_messages(min_messages=1, before=None, facility='midas')
Get a list of messages that were sent to the midas message log before a certain time.
The number of messages returned is based on:
- Search until we find
min_messages - If there are more messages with the same UNIX timestamp (to 1s resolution), include those messages as well.
- If log files are being split by date, and we've had to look back through several files without finding anything, give up.
Parameters:
-
min_messages(int, default:1) –Minimum number of messages to try to retrieve.
-
before((datetime, number or None), default:None) –Start searching for messages before the specified time. None means get the most recent. A number is interpreted as a UNIX timestamp.
-
facility(str, default:'midas') –The "facility" to search in. By default most messages are written to the "midas" facility.
Returns:
-
list of string–messages, with the most recent one first
get_triggered_alarms()
Get all the alarms that have been triggered but not yet reset.
Returns:
-
dict of {str: str}–key is the alarm name, value is the alarm message.
Raises:
-
KeyError–if alarms system is not correctly configured in ODB.
hist_create_plot(group_name, panel_name, variables, labels=None)
Helper function for adding a plot to the midas history display. It requires you to know how midas refers to different variables within the history system, so is an expert-level function (or create one plot manually, see the pattern of variable names used in the ODB for that plot, then infer what the names would be for future plots).
Parameters:
-
group_name(str) –The group this plot belongs to (used to organise plots on the main "History" webpage).
-
panel_name(str) –The name of this plot (called a panel name as you can view all the plots in a group on a single page with multiple panels).
-
variables(list of str) –The variables to show, as referred to by the internal midas history system. Combines both the "event name" and "tag name" into a single string. E.g. "Lakeshore:L336[0]" or "MyEvent:Custom tag name".
-
labels(list of str, default:None) –Human readable name for each variable (optional).
Raises:
-
KeyError–if a plot already exists with this name
-
ValueError–if there is a problem with the other arguments
-
TypeError–if there is a problem with the other arguments
hist_get_data(start_time, end_time, interval_secs, event_name, tag_name, index=None, timestamps_as_datetime=False)
Read data from the history system.
Parameters:
-
start_time(int / float / datetime) –Start of the time period to retrieve data for. If a number, it's interpreted as a UNIX timestamp.
-
end_time(int / float / datetime) –End of the time period to retrieve data for. If a number, it's interpreted as a UNIX timestamp.
-
interval_secs(int / float) –Return at most one data point per N seconds.
-
event_name(str) –Name of the midas history "event" to query. Use
hist_get_eventsto find valid event names. -
tag_name(str) –Name of the midas history "tag" to query. Use
hist_get_tagsto find valid tag names for a given event. -
index(int / None, default:None) –If the tag represents an array, you can ask to get data for a specific element in that array. If None, you'll get data for all the elements in the array. The length of the array is returned as part of
hist_get_tags. -
timestamps_as_datetime(bool, default:False) –Whether to convert timestamps in the return value to
datetime.datetimeobjects, or leave them as integer UNIX timestamps (the default).
Integer timestamps
Note that the midas history system works at 1-second resolution. If you pass start/end/interval times that are fractional, they will be rounded down to the nearest integer. Intervals must be at least 1 second long.
Returns:
-
list of dicts–One dict per event/tag/index combination. The dict has the following keys:
{ "event_name": str, "tag_name": str, "index": int, "num_entries": int, "status": int, "timestamps": list of int/datetime.datetime, "values": list of float }- "index" is the element index if a tag represents an array of data.
- "num_entries" is how many timestamp/value pairs were found.
- "status" is a midas status code for this event/tag/index combination. 1 indicates SUCCESS. 702 indicates data we couldn't find any data. 707 indicates either the variable does not exist at all, or it didn't exist during the time period you asked for (the different backend drivers treat this slightly differently). Any other status codes will result in an exception being thrown.
- "timestamps" is the timestamp of each data point found.
- "values" is the value of each data point found.
The
timestampsandvaluesarrays will be the same length.
Raises:
-
ValueError–If there's a problem with the time/index arguments.
-
MidasError–If the C++ side had a problem reading data.
hist_get_events()
Get a list of valid history "events". Use the event name when calling
hist_get_tags or hist_get_data.
Returns:
-
list of string–
hist_get_recent_data(num_hours, interval_secs, event_name, tag_name, index=None, timestamps_as_datetime=False)
Convenience wrapper around hist_get_data for getting the most recent
N hours of data from the history system.
Parameters:
For all other parameters, see hist_get_data
Returns:
-
list of dicts–see
hist_get_data
hist_get_tags(event_name)
Get a list of valid history "tags" for a given history "event".
Find valid event names using hist_get_events. Use event name
and tag name when calling hist_get_data.
Returns:
-
list of dicts–like
[{"name": string, "type": int, "n_data": int}]where- "name" is the tag name that can be passed to
- "type" is the midas type (e.g. TID_DOUBLE = 10),
- "n_data" is the number of entries if it's an array.
jrpc_client_call(connection, cmd, args, max_len=1024)
Call the JRPC (aka "javascript RPC") function exposed by another client.
For JRPC, the other client returns a string of data. Use brpc_client_call
if the other client will be returning arbitrary binary data.
Parameters:
-
connection(int) –The connection handle returned to you when connecting.
-
cmd(str) –The command to execute. The other client's JRPC handler must know what to do with this.
-
args(str) –Any other arguments to pass to the other client. Specify as a single string (probably separating arguments with spaces, or encoding your arguments as JSON).
-
max_len(int, default:1024) –The maximum length you expect the returned string to be. This is an imposition of the underlying C++ code, not the python side.
Null bytes
Note that if your cmd or args parameter contains an embedded
null byte, the argument will be truncated at that point.
Returns:
-
str–the string populated by the other client. Note that an exception will be raised if the other client returns a non-SUCESSS status.
keep_modules_when_reloading_script(module_names)
Some python modules don't like being imported multiple times. This can be a problem for sequencer scripts, as we generally want to re-import modules when starting a script, so that any changes a user has made to their own libraries get picked up automatically (e.g. if the user has a collection of helper functions or analysis tools).
Known problematic modules are ones that use Rust code for their implementation, and PyO3 to interface with python. You may see a message like "ImportError: PyO3 modules compiled for CPython 3.8 or older may only be initialized once per interpreter process".
sshtunnel module
A concrete example is use of the sshtunnel package, which uses paramiko,
which uses cryptography. And the cryptography Rust bindings are the issue.
To fix this, we can use this line in our sequencer script (either in
define_params() or sequence()):
seq.keep_modules_when_reloading_script("cryptography.hazmat.bindings._rust")
Using a regex
If you have a large number of modules/submodules to keep, you can pass a compiled regular expression. Any modules that match the regex will be kept. You can also pass an array of regexes/strings to this function, e.g:
# Minor annoyance in documetation - your code should only have
# a SINGLE backslash, not two.
modules = []
modules.append(re.compile(r"numpy\.random.*"))
modules.append("bisect")
seq.keep_modules_when_reloading_script(modules)
# This would match "numpy.random", "numpy.random.xyz", "bisect".
# This would not match "xyz.numpy.random", "_bisect", "bisect.xyz".
Parameters:
-
module_names(str, `re.Pattern`, or list of such) –modules names to keep
manual_trig_client_call(connection, event_id)
msg(message, is_error=False, facility='midas', from_logging_record=None)
Send a message into the midas message log.
These messages are stored in a text file, and are visible on the the "messages" webpage.
Parameters:
-
message(str) –The actual message.
-
is_error(bool, default:False) –Whether this message is informational or an error message. Error messages are highlighted in red on the message page.
-
facility(str, default:'midas') –The log file to write to. Vast majority of messages are written to the "midas" facility.
-
from_logging_record(LogRecord, default:None) –If this is coming from a log handler, we can grab the filename/line number of the caller from that record, rather than via stack inspection. Users should almost certainly leave this as None.
odb_chmod(path, mode, recurse=True)
Set permissions on an ODB key, and optionally all sub-keys if path
is a directory.
The default permissions of new ODB keys have read/write/delete enabled.
Example
To allow read/write, but not delete:
client.odb_chmod("/Some/path", midas.MODE_READ|midas.MODE_WRITE)
which is equivalent to
client.odb_chmod("/Some/path", 3)
Parameters:
odb_delete(path, follow_links=False)
Delete the given location in the ODB (and all of its children if the specified path points to a directory).
Parameters:
odb_exists(path)
odb_get(path, recurse_dir=True, include_key_metadata=False, just_key_list=False)
Get the content of an ODB entry, with automatic conversion to appropriate python types. You can request either a single ODB key or an entire directory (in which case a dict will be returned).
Dictionary ordering
Midas directories have an associated order to them
(you can rearrange the order of entries in a midas directory by calling
db_reorder_key in the C library). Until python3.7, basic python dicts
did not have a well-defined order. Therefore we return a
collections.OrderedDict rather than a basic dict, so the order in
which you iterate over the keys will match the order in which you
see them on the midas status page, for example.
ODB links
odb_get will always try to follow any symlinks in the ODB.
- If the target of the link exists, the result will be transparent (e.g. the key metadata will show the info of the target, not the link itself).
- If you try to read a broken link, a
midas.MidasErrorexception will be raised (with codemidas.status_codes["DB_INVALID_LINK"]). - If you try to read an ODB directory that contains a broken link, an exception will NOT be raised, and the returned dict will contain the details of the link, rather than the target.
Parameters:
-
path(str) –e.g. "/Runinfo". You may specify a single array index if desired (e.g. "/Path/To/My/Array[1]").
-
recurse_dir(bool, default:True) –If
pathpoints to a directory, whether to recruse down and get all entries beneath that directory, or just the entries immediately below the specified path.recurse_dirbeing False acts more like anlsof the directory, while it being True acts more like a full dump of the content. -
include_key_metadata(bool, default:False) –Whether to include extra information in the return value containing metadata about each ODB key. Examples metadata include when the ODB key was last written, the data type (e.g. TID_INT) etc. The metadata is stored in extra dict entries named as "
/key" (e.g. if the actual ODB content is in "State", the metadata is in "State/key"). -
just_key_list(bool, default:False) –Just "ls" an ODB directory - don't recurse, don't return values - just list the key names.
Returns:
-
dict–If
pathpoints to a directory and/orinclude_key_metadatais True. Dicts are keyed by the ODB key name. Ifrecurse_diris False and one of the entries inpathis also a directory, it's value will be an empty dict. Ifinclude_key_metadatais True, the keys at the top level will be the "directory_name" and "directory_name/key" -
int/float/bool etc, or a list of them–If
pathpoints to a single ODB key andinclude_key_metadatais False. -
list of strings–If
just_key_listis True, andpathpoints to a directory.
Raises:
-
KeyError–if
pathdoesn't exist in the ODB -
MidasError–for other midas-related issues
-
ValueError–if
just_key_listis True andpathisn't a directory.
odb_get_link_destination(link_path)
odb_last_update_time(path)
odb_link(link_path, destination_path)
Create or edit a link within the ODB.
To remove the link call odb_delete with the follow_links flag set
to False.
Parameters:
-
link_path(str) –The ODB path for the link name
-
destination_path(str) –Where in the ODB the link points to
Raises:
-
ValueError–if link_path already exists, but isn't already a link
odb_rename(current_path, new_name)
Rename an existing ODB entry. This function does not allow you to change which directory the entry is in, just the name of it.
Parameters:
odb_set(path, contents, create_if_needed=True, remove_unspecified_keys=True, resize_arrays=True, lengthen_strings=True, explicit_new_midas_type=None, update_structure_only=False)
Set the value of an ODB key, or an entire directory. You may pass in normal python values, lists and dicts and they will be converted to appropriate midas ODB key types (e.g. int becomes midas.TID_INT, bool becomes midas.TID_BOOL).
Sensible defaults have been chosen for converting python types to the C
types used internally in the midas ODB. However if you want more control
over the ODB type, you may use the types defined in the ctypes library.
For example, regular python integers become a midas.TID_INT, but you can
use a ctypes.c_uint32 to get a midas.TID_DWORD.
If you pass a list/tuple of values, they will be converted to an ODB
array. The data type of the first element in the list/tuple is used to
determine the ODB type. If you pass [1, 2, 3.14] it will be stored in
the ODB as [1, 2, 3]. Conversely, passing [3.14, 2, 1] will give
[3.14, 2.0, 1.0].
If you are setting the content of a directory and care about the order
in which the entries appear in that directory, contents should be a
collections.OrderedDict rather than a basic python dict. See the note
in odb_get for more about dictionary ordering.
Integer overflow
If you pass a value that is too large for the ODB type, the value will either overflow or be set to 0, depending on how the ctypes library handles it.
E.g. midas.TID_INT has a range of -231 to 231-1. Passing 233 will result in 0. Passing (231)+5 will result in a large negative number (overflow).
If you want to store 2**33 in the ODB, make sure to pass it as a
ctypes.c_int64 or ctypes.c_uint64, not as a regular python int!
Passing None
If you pass a contents of None, the behaviour depends on whether we
know the ODB type (either because the key already exists, or because
you passed the explicit_new_midas_type parameter).
- If we know the type and it's a string, None will be converted to "".
- If we know the type and it's numeric, None will be converted to 0.
- If we don't know the type, None will cause a ValueError to be raised.
Parameters:
-
path(str) –The ODB entry to set. You may specify a single array index if desired (e.g. "/Path/To/My/Array[1]").
-
contents(int/float/string/tuple/list/dict etc) –The new value to set
-
create_if_needed(bool, default:True) –Automatically create the ODB entry (and parent directories) if needed.
-
remove_unspecified_keys(bool, default:True) –If
pathpoints to a directory andcontentsis a dict, remove any ODB keys withinpaththat aren't present incontents. This means that the ODB will exactly match the dict you passed in. You may want to set this to False if you want to only update a few entries within a directory, and want to do so with only a single call toodb_set(). -
resize_arrays(bool, default:True) –Automatically resize any ODB arrays to match the length of lists present in
contents. Arrays will be both lengthened and shortened as needed. -
lengthen_strings(bool, default:True) –Automatically increase the storage size of a TID_STRING entry in the ODB if it is not long enough to store the value specified. We will include enough space for a final null byte. If this is False, and your string is longer than the current capacity, it will be silently truncated.
-
explicit_new_midas_type(one of midas.TID_xxx, default:None) –If you're setting the value of a single ODB entry, you can explicitly specify the type to use when creating the ODB entry (if needed).
-
update_structure_only(bool, default:False) –If you want to add/remove entries in an ODB directory, but not change the value of any entries that already exist. Only makes sense if contents is a dict /
collections.OrderedDict. Think of it like db_check_record from the C library.
Raises:
-
KeyError–if
create_if_neededis False and the ODB entry does not already exist. -
TypeError–if there is a problem converting
contentsto the C type we must pass to the midas library (e.g. the ODB entry is a TID_STRING but you passed in a float). -
ValueError–if there is a non-type-related problem with
contents(e.g. ifresize_arraysis False and you provided a list that doesn't match the size of the existing ODB array). -
MidasError–if there is some other midas issue, including if you try to write to a symlink where the target of the link no longer exists (use
odb_linkto fix the link first).
odb_stop_watching(path)
odb_watch(*args, **kwargs)
You cannot call this function from a sequencer script.
open_event_buffer(buffer_name, buf_size=None, max_event_size=None)
Open a buffer that can be used to send or receive midas events.
Parameters:
-
buffer_name(str) –The name of the buffer to open. "SYSTEM" is the main midas buffer, but other clients can read/write to/from their own buffers if desired.
-
buf_size(int, default:None) –The size of the buffer to open, in bytes. If not specified, defaults to 10 * max_event_size. Maximum value is 2^31 - 1 bytes (2.14 GB)
-
max_event_size(int, default:None) –The size of the largest event we expect to receive/send. Defaults to the value of the ODB setting "/Experiment/MAX_EVENT_SIZE".
Returns:
-
int–the "buffer handle" that must be passed to other buffer/event- related functions.
pause_run(async_flag=False)
Pause a run.
Parameters:
-
async_flag(bool, default:False) –If True, this function will return as soon as the transition has started; if False, it will wait until the transition has completed (or failed).
Raises:
-
TransitionFailedError–if async_flag is False and the transition fails.
receive_event(buffer_handle, async_flag=True, use_numpy=False)
Receive an event from a buffer. You must previously have called
open_event_buffer to open the buffer and register_event_request
to define which events you want to be told about.
See examples/event_receiver.py for a full example.
Parameters:
-
buffer_handle(int) –The return value from a previous call to
open_event_buffer. -
async_flag(bool, default:True) –If True, we'll return None if there is not currently an event in the buffer. If False, we'll wait until an event is ready and then return.
-
use_numpy(bool, default:False) –Whether to use numpy arrays or regular python tuples for bank data in the received
Event.
Returns:
-
Event–
register_brpc_callback(*args, **kwargs)
You cannot call this function from a sequencer script.
register_deferred_transition_callback(transition, callback)
Register a function that can defer a run transition. No client will receive the actual transition until your callback function says that it is okay.
Note that there is no way to de-register a deferred transition callback, though you can change which function gets called by registering a new one.
Parameters:
-
transition(int) –One of
midas.TR_START,midas.TR_STOP,midas.TR_PAUSE,midas.TR_RESUME,midas.TR_STARTABORT. -
callback(function) –See below. This will be called frequently by midas during the deferral period, and tells midas whether the transition can proceed or not yet.
Callback function details
- Arguments it should accept:
- client (midas.client.MidasClient)
- transition_type (int): The transition that has been requested (e.g.
midas.TR_STOP)
- Value it should return:
- bool - True if the transition can proceed; False if the transition should wait.
register_disconnect_callback(*args, **kwargs)
You cannot call this function from a sequencer script.
register_event_request(buffer_handle, event_id=-1, trigger_mask=-1, sampling_type=midas.GET_ALL)
Register to be told about events matching certain criteria in a given buffer.
To actually get the events, you must later call receive_event.
See examples/event_receiver.py for a full example.
Parameters:
-
buffer_handle(int) –The return value from a previous call to
open_event_buffer. -
event_id(int, default:-1) –Limit to only events where the event ID matches this. -1 means no filtering.
-
trigger_mask(int, default:-1) –Limit to only events where the trigger mask matches this. -1 means no filtering.
-
sampling_type(int, default:GET_ALL) –One of :
midas.GET_ALL(get all events)midas.GET_NONBLOCKING(get as many as possible without blocking producer)midas.GET_RECENT(like non-blocking, but only get events < 1s old)
Returns:
-
int–the "request ID", which can later be used to cancel this event request.
register_jrpc_callback(*args, **kwargs)
You cannot call this function from a sequencer script.
register_message_callback(*args, **kwargs)
You cannot call this function from a sequencer script.
register_param(name, comment, default_value, options=[])
warning
Only call this from your define_params() function!
Do not call get_param() in define_params(), as the parameters
do not get fully setup until the user has been asked for their
values when starting a sequence.
This will register a parameter that the user will be asked to set when they start the sequence.
Parameters:
-
name(str) –parameter name, must be < 32 chars long, be valid UTF-8, not contain []/ chars.
-
comment(str) –an explanation of what the parameter is for
-
default_value(int/float/bool/str etc) –default value for when the parameter is first created. Also used for determining the correct type to use in the ODB.
-
options(list of str, default:[]) –If you want the user to choose from a set of options in a drop-down menu (rather than being able to type arbitrary values), you can set those here. Probably only works correctly if
default_valueis a string!
register_transition_callback(*args, **kwargs)
You cannot call this function from a sequencer script.
reset_alarm(alarm_name)
Reset an alarm that is currently triggered.
Note that if it as an evaluated alarm, midas may re-trigger the alarm again very quickly if the ODB condition is still met.
Parameters:
-
alarm_name(str) –The name of the alarm to reset. If empty (or None), will reset all alarms.
reset_all_alarms()
Reset all alarms that are currently triggered.
resume_run(async_flag=False)
Resume a paused run.
Parameters:
-
async_flag(bool, default:False) –If True, this function will return as soon as the transition has started; if False, it will wait until the transition has completed (or failed).
Raises:
-
TransitionFailedError–if async_flag is False and the transition fails.
send_event(buffer_handle, event)
Send an event into a midas buffer.
Parameters:
-
buffer_handle(int) –The return value from a previous call to
open_event_buffer. -
event(Event) –The event to write.
!!! example:
import midas.client
import midas.event
client = midas.client.MidasClient("MyClientName")
buffer_handle = client.open_event_buffer("SYSTEM")
event = midas.event.Event()
event.header.event_id = 123
event.header.serial_number = 1
event.create_bank("MYBK", midas.TID_WORD, [1,2,3,4])
client.send_event(buffer_handle, event)
sequencer_msg(text, wait=False)
Send a message to display on the sequencer webpage, and optionally wait until the user has acknowledged it before continuing.
Note this is different to the midas.client.MidasClient.msg function that just
writes a message to the midas message log!
Parameters:
set_py_logger(debug, to_midas_msg_log)
Configure python logging - i.e. what happens when logger.info("xyz") etc.
are called.
Note that this can also be set using the command-line flags
--verbose and --log-to-midas.
Parameters:
-
debug(bool) –if True,
logger.debug()statements will be handled; if False they will be ignored (and only info/warning/error messages will be handled); if None, no changes will be made. -
to_midas_msg_log(bool) –if True, statements will be copied to the midas message log; if False, they will just be printed to screen; if None, no changes will be made.
Summary of what happens for different output:
* `print()` - always printed to screen; never written to midas log.
* `logger.info()` / `logger.error()` - always printed to screen;
written to midas log if `to_midas_msg_log` is True.
* `logger.debug()` - printed to screen if `debug` is True;
written to midas log if both `debug` and `to_midas_msg_log` are True.
set_py_logger_debug(debug)
Same as set_py_logger, but only affecting the debug argument.
set_py_logger_to_midas(to_midas_msg_log)
Same as set_py_logger, but only affecting the to_midas_msg_log
argument.
set_run_description(desc)
Set the ODB key "/Experiment/Run Parameters/Run Description" to the given value.
Parameters:
-
desc(str) –the description to set
set_transition_sequence(transition, sequence)
sleep(value)
Alias for wait_seconds as it feels so much more natural to write
seq.sleep() instead of seq.wait_seconds()! But wait_seconds()
matches the rest of the wait_xxx() functions, so let the user decide
which they prefer!
start_all_required_programs()
Start all the programs listed in /Programs in the ODB that have the "Required"
key set to True and that are not already running.
start_client(client_name, check_if_already_exists_before_starting=False)
Start a client/program that has been defined in /Programs in the ODB.
Must have the ODB key /Programs/<client_name>/Start command set, as this is the
command we'll execute to try to start the client.
You can later use client_exists to check whether the client started
successfully (e.g. wait a few seconds and then call client_exists).
Parameters:
-
client_name(str) –The name of a client/program defined in /Programs in the ODB.
-
check_if_already_exists_before_starting(bool, default:False) –If True, we won't do anything if midas thinks the client/program is already running.
Returns:
-
int / None–the PID of the spawned process. Most users won't care about this. Returns None if
check_if_already_exists_before_startingis True and the client is already running.
Raises:
-
KeyError–if the client/program isn't found in /Programs.
-
MidasError–if
fork()failed or similar.
start_run(run_num=0, async_flag=False)
Start a new run.
Parameters:
-
run_num(int, default:0) –The new run number. If 0, the run number will be automatically incremented.
-
async_flag(bool, default:False) –If True, this function will return as soon as the transition has started; if False, it will wait until the transition has completed (or failed).
Raises:
-
TransitionFailedError–if async_flag is False and the transition fails.
stop_client(client_name, exact_match=True)
Stop a client/program that is running and connected to midas.
Note that the underlying C++ code does not distinguish between: * a client that doesn't exist versus a client that timed-out and had to be forcefully stopped with SIGKILL * a client that was shut down gracefully versus a client we couldn't connect to and had to be forcefully stopped with SIGKILL.
Therefore we don't even bother returning a value from this python wrapper. You can just assume that we tried our best to stop the client, and that it no longer has access to the ODB etc.
Parameters:
-
client_name(str) –The name of a midas client that is connected. You can pass the special value "all" to shutdown all clients.
-
exact_match(bool, default:True) –If False, we'll look for all clients that start with this name. E.g. "myfe" would match "myfe_1", "myfetest" etc. If True, we require an exact match of the name.
Raises:
-
KeyError–if
/System/Clientsdoesn't exist in the ODB (but you have very big problems if that's the case!)
stop_run(async_flag=False)
Stop a run.
Parameters:
-
async_flag(bool, default:False) –If True, this function will return as soon as the transition has started; if False, it will wait until the transition has completed (or failed).
Raises:
-
TransitionFailedError–if async_flag is False and the transition fails.
trigger_internal_alarm(alarm_name, message, default_alarm_class='Alarm')
Trigger an "internal" alarm (i.e. an alarm raised directly by code, rather than due to an ODB condition failing or a program not running).
Example
client.trigger_internal_alarm("Example", "Oh no, something went wrong!")
client.get_triggered_alarms()
# Returns {'Example': 'Oh no, something went wrong!'}
client.reset_alarm("Example")
client.get_triggered_alarms()
# Returns {}
Parameters:
-
alarm_name(str) –The name of this alarm. An entry will be created in the ODB at
/Alarms/Alarms/<alarm_name>if it doesn't already exist. Max 31 characters, and no "/" characters allowed. -
message(str) –The message to be displayed to users. Max 80 characters.
-
default_alarm_class(str, default:'Alarm') –The "class" of this alarm. (i.e. what to do when this alarm is triggered). See
create_alarm_classfor more about alarm classes. If an alarm already exists calledalarm_name, the class of that alarm will not be changed.
Raises:
-
ValueError–If invalid alarm or class name provided
wait_clients_running(client_names, timeout_secs=10)
Wait until a certain set of clients are running before continuing the sequencer script.
Useful if you need to start/stop programs as part of your sequencer script (using
start_client(), start_all_required_programs() etc.). You should try to start them, then
call this function to make sure they're running before continuing the script.
If the clients do not start within the specified timeout, execution of the sequencer script will stop.
Parameters:
-
client_names(str or list of str) –client/program names to check
-
timeout_secs(float / int, default:10) –how long to wait before giving up and concluding that the client/program isn't running or failed to start successfully when you previously called
start_client()etc. Raises amidas.WaitTimeoutExpiredexception if the timeout fails.
Note that a timeout is REQUIRED in this function, unlike the other wait_xxx functions where it is optional (allowing them to wait indefinitely).
Example
import midas
# Note we had to import midas, so we can specify midas.WaitTimeoutExpired
# in our exception handling!
def sequence(seq):
try:
seq.wait_clients_running(["Logger", "my_fe"])
except midas.WaitTimeoutExpired:
seq.sequencer_msg("Wait timeout expired...")
wait_events(target, timeout_secs=None)
Wait until the ODB value "/Equipment/Trigger/Statistics/Events sent" exceeds a certain value before continuing the sequencer script.
Note that this is implemented to match the functionality of the MSL-based sequencer, but it is unlikley to be useful for most experiments (unless you have an equipment named "Trigger"!).
Parameters:
wait_func(func, check_period_secs=0.1, timeout_secs=None)
Wait until an arbitrary condition is met.
The function you specify should return either:
- a boolean - True if the sequencer should continue, or False if we should wait and try again.
- a 4-tuple of (boolean, string, number, number) for (wait_is_complete, text,
current_value, target_value).
wait_is_completehas the same meaning as the "single boolean" return value.textis a short string to show the user what we're waiting for.current_valueandtarget_valueare used to draw the progress bar. These values only really make sense if you're counting "up" to something. Ifcurrent_valueis larger thantarget_value, the progress bar will show 100%. You might want to manipulate your values so they show absolute differences.
If you don't want to write a separate function, you can specify a lambda as the function to call. See examples below for the syntax.
Parameters:
-
func(function/lambda) –code to run
-
check_period_secs(float, default:0.1) –how often to check the condition
-
timeout_secs(int / float / None, default:None) –Raise a
midas.WaitTimeoutExpiredexception if the condition hasn't been met within the specified period. None means wait indefinitely.
Example using a lambda (anonymous one-line function)
def sequence(seq):
seq.wait_func(lambda : seq.odb_get("/path/aaa") > 1.5 and seq.odb_get("/path/bbb") < 3.11)
Example using a custom function (which must return a boolean or 4-tuple)
import random
def custom_func():
# This version returns a 4-tuple
target = 7
val = random.randint(0, 10)
print(f"random val was {val}")
message = "RandomRoll"
complete = (val == target)
return (complete, message, val, target)
def sequence(seq):
# DO NOT WRITE ANY PARENTHESES - just state the name of the function
# that we should call to check if the condition is met or not.
seq.wait_func(custom_func)
Example using a custom function, with a timeout
import random
import midas
# Note we had to import midas, so we can specify midas.WaitTimeoutExpired
# in our exception handling!
def custom_func():
# This version just returns a boolean
target = 7
val = random.randint(0, 10)
print(f"random val was {val}")
return (val == target)
def sequence(seq):
# DO NOT WRITE ANY PARENTHESES - just state the name of the function
# that we should call to check if the condition is met or not.
try:
seq.wait_func(custom_func, timeout_secs=0.5)
except midas.WaitTimeoutExpired:
seq.sequencer_msg("Wait timeout expired...")
wait_odb(path, op, target, between_upper_target=None, stable_for_n_secs=None, timeout_secs=None)
Wait until a certain condition is met in the ODB.
E.g. wait_odb("/Path/to/something", ">", 6.5) will wait until the ODB value
"/Path/to/something" exceeds 6.5 before continuing the sequencer script.
Parameters:
-
path(str) –ODB path
-
op(str) –one of "<", ">", "<=", ">=", "==", "!=", "between"
-
target(int/float/str etc) –the value to compare to
-
between_upper_target(int / float / None, default:None) –if
opis "between", we'll check if the value is >=targetand <=between_upper_target. Ignored ifopis not "between" -
stable_for_n_secs(int / float / None, default:None) –require that the condition is met for at least N seconds (i.e. ensuring that the condition is stable, not just transient). If None, we'll return as soon as the condition is met for the first time.
-
timeout_secs(int / float / None, default:None) –Raise a
midas.WaitTimeoutExpiredexception if the condition hasn't been met within the specified period. None means wait indefinitely.
Example with basic comparison
def sequence(seq):
seq.wait_odb("/Runinfo/Run number", ">", 12345)
Example with a range and a timeout
import midas
# Note we had to import midas, so we can specify midas.WaitTimeoutExpired
# in our exception handling!
def sequence(seq):
try:
seq.wait_odb("/Runinfo/Run number", "between", 12345, 56789, timeout_secs=5)
seq.sequencer_msg("Wait was successful!")
except midas.WaitTimeoutExpired:
seq.sequencer_msg("Wait timeout expired...")
midas.sequencer.check_sequence_syntax(code, is_auto=True)
Validate Python sequence source without executing it or changing ODB.