Runs: programming
This page covers initiating and handling run transitions from code.
For the concepts and day-to-day use, see Runs: overview.
For full descriptions of the programming interfaces (e.g. all the optional parameters that aren't mentioned here), see the API reference for C++ (cm_xxx) and Python (midas.client).
You can both initiate run transitions, and perform work whenever a transition happens.
Transition types
There are 5 transition types:
| Name | Code |
|---|---|
| Start | TR_START |
| Stop | TR_STOP |
| Pause | TR_PAUSE |
| Resume | TR_RESUME |
| Start aborted | TR_STARTABORT |
Note the extra start aborted transition. This is used when a start transition is attempted, but a client returns an error. Clients are told that the start was aborted, so they can do any cleanup that they would normally do at the end of a run (e.g. turning off hardware, stopping any threads that were spawned etc).
Sequence ordering
Each client defines the sequencer order in which it should be told about the transition. These are integer numbers, and clients with lower numbers are told about the transition first. Different orders can be specified for the different transition types (e.g. a program could be the first one to be told about a run starting, and the last one to be told about it stopping).
Clients are able to report an error, for example to prevent a run from starting. Subsequent clients would then not be told about the transition.
If multiple clients have the same sequence number, they handle the transition at the same time (in parallel).
Example transition sequence
For example, if you have 2 frontends with sequence 500, the mlogger with sequence 200, and another frontend with sequence 900, the flow would look like:
flowchart LR
L["mlogger (200)"] --> A["frontend A (500)"]
L --> B["frontend B (500)"]
A --> C["frontend C (900)"]
B --> C
Default transition sequence numbers
The default transition sequence numbers are shown in the table below. Note that the value for each frontend can be changed; 500 is just the default.
| Frontends | mlogger | manalyzer | |
|---|---|---|---|
| Start | 500 | 200 | 300 |
| Pause | 500 | 500 | N/A |
| Resume | 500 | 500 | N/A |
| Stop | 500 | 800 | 700 |
Initiating transitions from code
You can start/stop/pause/resume runs from code.
Async flag
You may specify whether the transition happens synchronously or asynchronously:
| C++ flag | Python param | Meaning | |
|---|---|---|---|
| Synchronous | async_flag=TR_SYNC |
async_flag=False |
Wait until the transition completes before returning |
| Asynchronous | async_flag=TR_ASYNC |
async_flag=True |
Return immediately, before the transition has completed |
In C++ you can also specify whether the transition happens in a separate thread or not, by setting the TR_MTHREAD bit in async_flag. You can use any of the 4 combinations of sync/async and multi-/single-threaded. Python programs always use the single-threaded approach.
Run number
You may manually specify a run number when starting a new run, but this is not needed in most cases.
Passing a run number of 0 means increment the current run number by 1.
INT run_number = 0;
INT async_flag = TR_ASYNC | TR_MTHREAD;
char error[256] = {};
INT status = cm_transition(TR_START, run_number, error, sizeof(error), async_flag, 0);
client.start_run() #(1)!
client.stop_run()
client.pause_run()
client.resume_run()
run_numandasync_flagare optional parameters, defaulting to 0 (auto-increment) and False (wait until transition completes before returning), respectively.
Handling transitions
Registering a callback function
Note that frontend frameworks have their own way of letting users define their transition callback functions.
INT my_callback(INT run_number, char *error) {
if (some_problem_detected) {
snprintf(error, 255, "Couldn't start run due to ....");
return FE_ERR_DRIVER;
}
printf("Run number %d is starting\n", run_number);
return CM_SUCCESS;
}
// Later, in main function...
INT status = cm_register_transition(TR_START, my_callback, 500);
def my_callback(client, run_number): #(1)!
if some_problem_detected:
return (midas.FE_ERR_DRIVER, "Couldn't start run due to ....")
print(f"Run number {run_number} is starting!")
return midas.SUCCESS
# Later, in main function...
client.register_transition_callback(midas.TR_START, 500, my_callback)
- If your code is in a class, your callback could be defined as
and the registration as
def my_callback(self, client, run_number)client.register_transition_callback(midas.TR_START, 500, self.my_callback)
You can use ODB keys to specify bash scripts that should be called during start, stop and start-abort transitions.
Setting the sequence order
Although you specify the sequence order when you register the callback, you can later change it. This is useful if you are using a frontend framework, as the framework uses a value of 500 by default.
INT status = cm_set_transition_sequence(TR_START, 750);
client.set_transition_sequence(midas.TR_START, 750)
Multiple transition callbacks from one program
A program can register multiple callback functions for the same transition.
Example use case
An example use case is a program that computes a timing sequence for a pulser, other programs then look at the timing that is going to be used, but the pulser hardware isn't actually started until the very end of the sequence.
flowchart LR
A["Pulser frontend:<br>pre-computation<br>(400)"] --> B["Some other frontend:<br>uses that info<br>(500)"]
B --> C["Pulser frontend:<br>starts the hardware<br>(600)"]
status = cm_register_transition(midas.TR_START, callback_precompute, 400)
status = cm_register_transition(midas.TR_START, callback_start_hw, 600)
client.register_transition_callback(midas.TR_START, 400, callback_precompute)
client.register_transition_callback(midas.TR_START, 600, callback_start_hw)
Deferred transitions
Deferred transitions allow a frontend to delay a run transition until it has done more work. Other clients do not know that a transition has been requested until the "deferring" frontend completes its work.
Example use cases for deferred transitions
- It may not be advisable to pause or stop a run until some hardware has turned off a particular valve.
- The start of the acquisition system should be postponed until the beam rate has been stable for a given period of time.
- While active, a particular acquisition system should not be interrupted until the "cycle" is completed.
The order of operations for a deferred transition is:
- A transition is requested
- The ODB flag
/Runinfo/Requested transitionis set, but other clients aren't told about the transition - A callback function in the "deferring" client is called repeatedly
- When this function returns true, the transition proceeds as normal
Support varies in different frameworks
- Fully supported
- mfe frontend framework
- Python frontend framework
- NOT supported
- TMFE frontend framework
- Partially supported
- Other programs - you must ensure to repeatedly call
cm_check_deferred_transition()orclient.check_deferred_transition()in your program's main loop.
- Other programs - you must ensure to repeatedly call
Avoid multiple deferred transitions
You should only have ONE program that registers a deferred transition. If you have multiple, the first one to finish its work will cause the transition to occur, even if the other program isn't ready - it's a race to see which one finishes first!
If you really need multiple programs to defer the transition, you could have one "real" deferrer, and have the other programs report their status via keys in the ODB. When the "real" deferrer sees that all the other programs have set their flags, it can then return true from its callback function.
Note that deferred transitions are skipped if using the "stop now" or "start now" commands in odbedit.
INT my_callback(INT transition, BOOL first) { //(1)!
if (finished_doing_some_work) {
return TRUE;
}
return FALSE;
}
// Later, in main function...
INT status = cm_register_deferred_transition(TR_STOP, my_callback);
// If you are NOT using the mfe frontend framework:
while (true) {
status = cm_yield(1000);
if (status == RPC_SHUTDOWN || status == SS_ABORT) {
break;
}
status = cm_check_deferred_transition();
}
- The
firstparameter says whether this is the first time your callback has been called for this transition.
def my_callback(client, transition_type):
if finished_doing_some_work:
return True
else:
return False
# Later, in main function...
client.register_deferred_transition_callback(midas.TR_STOP, my_callback)
# If you are NOT using the midas.frontend framework:
while True:
client.communicate(1000)
client.check_deferred_transition()