Creating a new beamline#

A beamline is a collection of devices that can be used together to run experiments, they may be read-only or capable of being set. They include motors in the experiment hutch, optical components in the optics hutch, the synchrotron “machine” and more.

Beamline Modules#

Each beamline should have its own file in the dodal.beamlines folder, in which the particular devices for the beamline are instantiated. The file should be named after the colloquial name for the beamline. For example:

  • i03.py

  • i20_1.py

  • vmxi.py

Beamline modules (in dodal.beamlines) are code-as-configuration. They define the set of devices and common device settings needed for a particular beamline or group of similar beamlines (e.g. a beamline and its digital twin). Some of our tooling depends on the convention of only beamline modules going in this package. Common utilities should go somewhere else e.g. dodal.utils or dodal.beamlines.common.

The following example creates a fictitious beamline w41, with a simulated twin s41. w41 needs to monitor the status of the Synchrotron and has an AdAravisDetector. s41 has a simulated clone of the AdAravisDetector, but not of the Synchrotron machine.

from ophyd_async.epics.adaravis import AravisDetector

from dodal.common.beamlines.beamline_utils import (
    device_factory,
    get_path_provider,
    set_path_provider,
)
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
from dodal.common.beamlines.device_helpers import CAM_SUFFIX, HDF5_SUFFIX
from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitPathProvider
from dodal.devices.synchrotron import Synchrotron
from dodal.log import set_beamline as set_log_beamline
from dodal.utils import BeamlinePrefix

BL = get_beamline_name("s41")  # Default used when not on a live beamline
PREFIX = BeamlinePrefix(BL)
set_log_beamline(BL)  # Configure logging and util functions
set_utils_beamline(BL)

# Currently we must hard-code the visit, determining the visit is WIP.
set_path_provider(
    StaticVisitPathProvider(
        BL,
        # Root directory for all detectors
        Path("/dls/w41/data/YYYY/cm12345-1"),
        # Uses an existing GDA server to ensure filename uniqueness
        client=RemoteDirectoryServiceClient("http://s41-control:8088/api"),
        # Else if no GDA server use a LocalDirectoryServiceClient(),
    )
)

"""
Define device factory functions below this point.
A device factory function is any function that has a return type which conforms
to one or more Bluesky Protocols.
"""

"""
A valid factory function which:
- may be instantiated automatically, selectively on live beamline
    - caches and re-uses the result for subsequent calls
- automatically names the device
- may be skipped when make_all_devices is called on this module
- must be explicitly connected (may be automated by tools)
    - when connected may connect to a simulated backend
    - may be connected concurrently (when automated by tools)
""""
@device_factory(skip = BL == "s41")
def synchrotron() -> Synchrotron:
    return Synchrotron()


@device_factory()
def d11() -> AravisDetector:
    return AravisDetector(
        f"{PREFIX.beamline_prefix}-DI-DCAM-01:",
        path_provider=get_path_provider(),
        drv_suffix=CAM_SUFFIX,
        fileio_suffix=HDF5_SUFFIX,
    )

w41 should also be added to the list of ALL_BEAMLINES in tests/beamlines/test_device_instantiation. This test checks that the function returns a type that conforms to Bluesky protocols, that it always returns the same instance of the device and that the arguments passed into the Device class constructor are valid.