redsun.engine¶
Run engine¶
RunEngine
¶
RunEngine(
md: dict[str, Any] | None = None,
*,
loop: AbstractEventLoop | None = None,
preprocessors: list[Preprocessor] | None = None,
md_validator: MDValidator | None = None,
md_normalizer: MDNormalizer | None = None,
scan_id_source: MDScanIDSource = default_scan_id_source,
call_returns_result: bool = True,
)
Bases: RunEngine
Runs plans and emits documents without blocking the calling thread.
Wraps bluesky.run_engine.RunEngine: __call__ runs the plan on a
separate thread and returns a concurrent.futures.Future of its result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
md
|
dict[str, Any] | None
|
Metadata store, a |
None
|
loop
|
AbstractEventLoop | None
|
Event loop plans run on. Defaults to the shared background loop. |
None
|
preprocessors
|
list[Preprocessor] | None
|
Generator functions modifying a plan's messages, such as the
|
None
|
md_validator
|
MDValidator | None
|
Raises to prevent a run whose metadata it finds invalid; its return value is ignored. |
None
|
md_normalizer
|
MDNormalizer | None
|
Like |
None
|
scan_id_source
|
MDScanIDSource
|
Function, possibly async, returning the next |
default_scan_id_source
|
call_returns_result
|
bool
|
What the future |
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
md |
The metadata store described above. |
|
record_interruptions |
|
|
state |
One of |
|
suspenders |
Read-only collection of |
|
preprocessors |
The list of preprocessors described above. |
|
msg_hook |
|
|
state_hook |
|
|
waiting_hook |
|
|
progress_hook |
Callable[[list[PlanProgress] | None], None] | None
|
|
ignore_callback_exceptions |
|
|
loop |
The event loop plans run on, such as one from
|
|
max_depth |
Maximum stack depth, preventing calls to the RunEngine from inside a
function, which breaks introspection. |
|
pause_msg |
Message printed when a run is interrupted, with instructions for
changing the RunEngine's state. Empty here; |
|
commands |
The list of commands a |
sig_locks_changed
class-attribute
instance-attribute
¶
The names of the locked devices, whenever that set changes.
sig_state_changed
class-attribute
instance-attribute
¶
The engine's new and old state on every change, named as state names them.
sig_progress
class-attribute
instance-attribute
¶
Every progress scope of the running plan, as a tuple of PlanProgress,
parents before children, whenever one is declared, updated or finished;
an empty tuple when none is left.
The scopes are the engine's own: the engine's thread replaces their
update after the emission, so a slot reads it once for each emission and
keeps a copy of what it needs later.
__call__
¶
__call__(
plan: Iterable[Msg],
subs: Subscribers | None = None,
/,
**metadata_kw: Any,
) -> Future[RunEngineResult | tuple[str, ...]]
Execute a plan.
Keyword arguments are metadata recorded with every run the plan creates. The plan and optional subscriptions are positional.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plan
|
Iterable[Msg]
|
A generator yielding |
required |
subs
|
Subscribers | None
|
Callbacks subscribed for this run only, given as:
|
None
|
resume
¶
Resume the paused plan on a separate thread.
Returns a future of the resumed plan's result. Pausing completes the
future __call__ returned, so this is a new one.
stop
¶
Stop the plan and mark it successful, on a thread of its own.
A paused plan runs its cleanup there, not on the caller's thread.
abort
¶
Stop the plan and mark it aborted, on a thread of its own.
halt
¶
Stop the plan with no cleanup, on a thread of its own.
Deferrals
¶
Changes to apply before the next message of the running plan.
Every change runs on the engine's loop. One asked for while a plan runs waits for the message under way to complete, then runs before the next message is sent, or before the plan returns if that message was its last; the plan is neither suspended nor rewound. One asked for while no plan runs is applied at once. A change that raises is logged, and the ones after it still run.
PlanProgress
¶
Bases: Generic[T]
A progress scope a plan declared, passing each update to its watchers.
Watchers receive every field of a WatcherUpdate as keywords, those that
are None included, and the values themselves, not copies. name is
always the name of the scope.
watch
¶
Pass every update to watcher, starting with the last one, if any.
register_bound_command
¶
Register command in engine, under the command's __name__.
Unlike RunEngine.register_command, binds the command to engine: it is
called with the engine first, then the Msg.
RunEngineResult is the class of bluesky,
RunEngineResult.
Actions¶
Decorators and types for continuous, interactive plans.
A continuous plan loops until stopped, and may be paused and resumed and take actions the user triggers while it runs.
SRLatch: a set-reset latch synchronising a plan with outside signals.continuous: marks a plan as continuous, recording whether it is pausable.PlanAction: what a plan declares of an action: its name, its description and the labels of its button.ActionManager: the actions a running plan offers, asked for from outside and reported as they change state.
PlanAction
dataclass
¶
An action a user triggers while a continuous plan runs.
A declaration only. A plan names it as the default of a parameter, from
which its button is made, and waits for it through an ActionManager.
Subclass it to add fields; the subclass is a frozen dataclass too.
description
class-attribute
instance-attribute
¶
Short description of the action, usable as a tooltip.
toggle_states
class-attribute
instance-attribute
¶
Labels of a button that stays pressed until it is released.
The first is shown while it is released and the second while it is
pressed. None for a button that is clicked.
ActionManager
¶
The actions a running plan offers, and the state each is in.
Whoever owns the plans owns one. A plan waits on it with wait, a user
asks through request, and sig_changed reports every change of state.
Only the plan changes a state, so the changes are reported in the order
they happen.
sig_changed
class-attribute
instance-attribute
¶
Emitted with the name of an action and its new ActionState.
request
¶
Ask for the action name, or with on=False ask a running one to end.
Safe from any thread. Asking for an action no plan offers, or asking one that is not running to end, changes nothing and raises nothing: it is logged as a warning.
wait
¶
Offer actions, wait until one is asked for, and return its name.
Every call offers latches of its own, so a request left from an
earlier wait cannot start an action of this one. The action returned
runs until done. The others go back to idle, as all of them do when
the plan is stopped while it waits. A checkpoint is yielded every
poll_interval seconds, so it cannot be used between create and
save.
Raises:
| Type | Description |
|---|---|
ValueError
|
If no action is given. |
wait_released
¶
Wait until the running action is asked to end.
Raises:
| Type | Description |
|---|---|
ValueError
|
If action is not running. |
SRLatch
¶
A set-reset latch a coroutine waits on, settable from any thread.
A coroutine waits for either the set or the reset state, on whatever loop it runs. A new latch is reset.
changed_at
property
¶
When the latch last changed state, as time.monotonic reads it.
0.0 for a latch that never changed.
set
¶
Set the latch, waking every coroutine in wait_for_set.
Does nothing if already set.
reset
¶
Reset the latch, waking every coroutine in wait_for_reset.
Does nothing if already reset.
wait_for_set
async
¶
Wait until the latch is set; return at once if it already is.
wait_for_reset
async
¶
Wait until the latch is reset; return at once if it already is.
Continuous
dataclass
¶
What continuous records on the plan it marks.
pausable
class-attribute
instance-attribute
¶
Whether the run engine can pause and resume the plan.
continuous
¶
Mark a plan as continuous: it loops until it is stopped.
A continuous plan gets a toggle to start and stop it, and with pausable a button to pause and resume it. Usable with or without arguments:
@continuous
def my_plan() -> MsgGenerator[None]: ...
@continuous(pausable=True)
def my_plan(detectors: Sequence[DetectorProtocol]) -> MsgGenerator[None]: ...
The signature is untouched; what was asked is stored on the function as
__continuous__, a Continuous.
Waiting on a latch¶
ActionManager makes one SRLatch for each action it offers, and waits on it. A
latch is set and reset from any thread, and a coroutine waits for either
state on whatever loop it runs:
latch = SRLatch()
# in a coroutine:
await latch.wait_for_set() # blocks until set()
await latch.wait_for_reset() # blocks until reset()
latch.changed_at is when the latch last changed state, as time.monotonic
reads it, and 0.0 for a latch that never changed.
For a wait_for_actions message, the RunEngine runs one wait_for_set (or
wait_for_reset) task per latch, for poll_interval seconds at most. Of
several latches in the wanted state, it returns the one that reached that
state first. Of those that reached it together, it returns the first in the
map. If no latch is in the wanted state when the interval ends, the stub
yields its next checkpoint and sends the message again.
Plan stubs¶
redsun.engine.plan_stubs holds steps to use inside larger plans, beside those
of bluesky.plan_stubs.
Descriptor stubs¶
import redsun.engine.plan_stubs as rps
# gather descriptors from Readable / Collectable devices inside a plan
descriptor = yield from rps.describe(readable)
descriptors = yield from rps.describe_collect(collectable)
Lock stubs¶
A plan locks the devices it must not have disturbed, and views disable their
controls while those devices are locked. The run engine keeps the locks and
announces them on RunEngine.sig_locks_changed, which a view connects to.
import redsun.engine.plan_stubs as rps
# lock the stage and camera while the inner plan runs, however it ends
yield from rps.lock_wrapper(scan(stage, camera), stage, camera)
Action flow-control stubs¶
wait_for_actions waits on a map of names to latches. ActionManager.wait and
ActionManager.wait_released use it, so a plan written against an ActionManager does
not call it.
import redsun.engine.plan_stubs as rps
from redsun.engine.actions import SRLatch
latches = {"snap": SRLatch()}
# wait until a latch in the map is set, however long that takes,
# with a checkpoint every `poll_interval` seconds
name, latch = yield from rps.wait_for_actions(latches, poll_interval=0.016)
The stub does not time out. It waits as long as it takes for a latch to be
set, or reset with wait_for="reset", and returns at once if one already is.
While it waits it yields a checkpoint every poll_interval seconds, 1/60 s by
default. A checkpoint is where the plan can be paused, so the stub cannot sit
between create and save. An empty map raises ValueError.
Progress stubs¶
A plan opens a progress scope, moves it and finishes it. The run engine
announces every open scope on RunEngine.sig_progress, which a view connects
to. How to show a plan's progress shows
the whole path.
import redsun.engine.plan_stubs as rps
yield from rps.declare_progress("series")
yield from rps.update_progress("series", current=3, initial=0, target=10, unit="frames")
yield from rps.update_progress("series", done=True)
yield from rps.monitor_progress("frames", status) # follow a device status
Plan stubs adding action flow control to bluesky.plan_stubs.
wait_for_actions waits on user actions; lock, unlock and lock_wrapper
lock devices against the user. Every stub is a generator yielding Msg
objects, used inside larger plans with yield from.
wait_for_actions
¶
wait_for_actions(
events: Mapping[str, SRLatch],
poll_interval: float = SIXTY_FPS,
wait_for: Literal["set", "reset"] = "set",
) -> MsgGenerator[tuple[str, SRLatch]]
Wait until one of the given latches is in the wanted state, and return it.
Returns the name and the latch as soon as a latch is set, or reset with
wait_for="reset", whether it was already or changed meanwhile. Of several
in the wanted state, the one that reached it first is returned, and of
those that reached it together, the first in events. It waits as long as that
takes. A checkpoint is yielded every poll_interval seconds, where the
plan can be paused, so it cannot be used between create and save.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
events
|
Mapping[str, SRLatch]
|
Mapping of action names to their |
required |
poll_interval
|
float
|
Seconds between two checkpoints, 1/60 s by default. |
SIXTY_FPS
|
wait_for
|
Literal['set', 'reset']
|
Whether to wait for a latch to be set or reset. |
'set'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If events is empty. |
describe
¶
Return what obj.describe() returns, from inside a plan.
describe_collect
¶
describe_collect(
obj: Collectable,
) -> MsgGenerator[
dict[str, Descriptor] | dict[str, dict[str, Descriptor]]
]
Return what obj.describe_collect() returns, from inside a plan.
lock
¶
Lock devices, so views disable their controls, and return the lock's token.
Prefer lock_wrapper, which unlocks however the plan ends.
lock_wrapper
¶
declare_progress
¶
Open a progress scope named name, nested under parent when given.
Returns the scope. A view shows it while the plan runs, and the engine finishes it when the plan ends.
update_progress
¶
update_progress(
name: str,
*,
current: Any = None,
initial: Any = None,
target: Any = None,
unit: str = "unit",
precision: int | None = None,
fraction: float | None = None,
time_elapsed: float | None = None,
time_remaining: float | None = None,
done: bool = False,
) -> MsgGenerator[None]
Report how far the scope name has got, or finish it with done.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current
|
Any
|
Where the scope is, started and ends, in unit; target left out when the end is not known. |
None
|
initial
|
Any
|
Where the scope is, started and ends, in unit; target left out when the end is not known. |
None
|
target
|
Any
|
Where the scope is, started and ends, in unit; target left out when the end is not known. |
None
|
fraction
|
float | None
|
How far the scope has got, from 0 to 1, in place of the three above. |
None
|
precision
|
int | None
|
Decimals to show the numbers with. |
None
|
monitor_progress
¶
monitor_progress(
name: str, status: Status, *, parent: str | None = None
) -> MsgGenerator[PlanProgress]
Open the progress scope name and keep it in step with status.
A status reporting updates, such as a detector's complete or a motor's
set in ophyd-async, fills the scope; any other shows it without an
end. The scope finishes when the status is done, whether it succeeds or
not; the plan still waits on the status itself, and does not update or
finish the scope with update_progress.
Returns the scope, which reports values of the type the status reports.