Skip to content

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 Presenter in this case acts as a middle-man between the GUI and the device, directly calling Bluesky methods and bypassing the RunEngine;
  • 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.

views:
  storage:
    plugin_name: redsun
    plugin_id: storage