Presenters¶
Presenters represent the execution logic of your system.
Where Devices are "workers" (as they instruct your device to perform a certain task), Presenters can be "orchestrators", in the sense that they define the sequence of actions that workers must perform through Bluesky plans.
We highlight "can be" because Presenters are not limited to that:
- they can consume Bluesky documents for on-the-fly processing, intermediate storage or redirection to a GUI (i.e. computing the FFT of an image and sending it to the GUI for display);
- they can provide manual control for device task execution and/or configuration;
- in comparison to plans (which represents an experimental procedure), one may wish to - for example - manually move a motor stage from the GUI, or change the exposure time of a camera; the
Presenterin this case acts as a middle-man between the GUI and the device, directly calling Bluesky methods and bypassing theRunEngine; - they can act as communication points with external applications to trigger actions via a custom communication protocol (or wait for possible commands incoming by said applications).
Presenters are meant to communicate between each other via the VirtualContainer, which takes care of redirecting information (commands and/or documents) to the appropriate destination (whether it is another Presenter or a View).
The presenter contract¶
A presenter is recognized structurally through the
PPresenter protocol: any class whose
instances expose a name (str) and a devices
(Mapping[str, Device]) satisfies it. The protocol declares both members
as read-only properties, so implementers are free to use plain instance
attributes, class attributes, or properties - and devices may be any
Mapping subtype, such as a plain dict.
Compliance is a dual gate. At class level, the constructor's
positional shape is verified: its leading positional parameters must be
exactly (name, devices) - the container instantiates presenters as
cls(name, devices, **config_kwargs) and has no control over the keyword
arguments. At build time, the constructed instance is checked against the
protocol, raising a TypeError naming the missing members - attributes
assigned in __init__ are invisible before instantiation
(see ADR 0003).
The Presenter ABC is an optional
convenience base providing the conventional constructor shape:
- a
name, used as the unique identifier of the presenter; - a
Mapping[str, Device]of the allocated devices in the session; - additional keyword arguments, parsed from the session configuration file.
The ABC deliberately does not inherit the protocol - it satisfies it structurally, like every other implementer.
Access to the virtual container is opt-in via the
IsProvider and
IsInjectable protocols; synchronous
teardown via HasShutdown.
Consuming documents¶
Presenters that process acquisition data subscribe to the RunEngine's
document stream (directly, or through the callback registry on the
VirtualContainer). Document callbacks
run synchronously on the engine's event loop thread - they can never
await. To persist derived results (e.g. a median image computed from Event
documents), a callback uses the storage layer's synchronous face:
register a StreamSpec derived from the
descriptor document, then put_nowait on a
FrameSink. The dual-context design is
documented in Session storage and
ADR 0002.
Built-in presenters¶
Reusable presenters ship in redsun.presenter.builtins and are available
both for declarative containers and from configuration files via the
redsun plugin (see component system).
StoragePresenter¶
StoragePresenter is the
application-level control point for storage paths. It owns the
SessionPathProvider (created with
the session name from the configuration), exposes it on the virtual
container under PATH_PROVIDER. Views observe
the provider through its signals (base directory and plan name).
Plan lifecycle reaches it through two slots the application connects:
def wire(self) -> None:
self.connect(self.acquisition.sig_pre_launch_notify, self.storage.set_plan)
self.connect(self.acquisition.sig_plan_done, self.storage.reset_plan)
set_plan makes burst filenames adopt the name of the upcoming run;
reset_plan returns it to unknown, so bursts arriving after a run are not
attributed to it. Which signals mean "a plan started" is the application's
knowledge, not the presenter's, so nothing is connected until it says so.
presenters:
storage:
plugin_name: redsun
plugin_id: storage
base_dir: "~/my-data" # optional; defaults to ~/redsun-storage
Its Qt counterpart ships alongside it:
StorageView shows the base
directory and lets the user change it. It resolves the same key, so an
application that declares the view without the presenter still builds, showing
a read-only placeholder.