Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update device factory docs #855

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 57 additions & 35 deletions docs/how-to/create-beamline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ 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.
They include motors and detectors 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 ``doodal.beamlines`` folder, in which the particular devices for the
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``
Expand All @@ -20,62 +20,84 @@ of our tooling depends on the convention of *only* beamline modules going in thi
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.
``w41`` needs to monitor the status of the Synchrotron and drives an Aravis AreaDetector through a PandA.
``s41`` has a simulated clone of the AravisDetector, but does not have access to read the Synchrotron machine, and cannot simulate the hardware behaviours of a PandA.

.. code-block:: python
from ophyd_async.epics.adaravis import AravisDetector
from ophyd_async.fastcs.panda import HDFPanda

from dodal.common.beamlines.beamline_utils import device_instantiation
from dodal.common.beamlines.beamline_utils import (
device_factory,
device_instantiation,
get_path_provider,
)
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
from dodal.devices.areadetector.adaravis import AdAravisDetector
from dodal.devices.synchrotron import Synchrotron
from dodal.log import set_beamline as set_log_beamline
from dodal.utils import get_beamline_name, skip_device
from dodal.utils import BeamlinePrefix, get_beamline_name, skip_device

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


"""
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 device factory function is any function that has a return type which conforms to one
or more Bluesky Protocols.
"""


"""
A valid factory function which is:
- instantiated only on the live beamline
- a maximum of once
- can optionally be faked with ophyd simulated axes
- can optionally be connected concurrently by not waiting for connect to complete
- if constructor took a prefix, could optionally exclude the BLIXX prefix
""""
@skip_device(lambda: BL == "s41") # Conditionally do not instantiate this device
def synchrotron(
# Only compatible with ophyd_async devices
@device_factory(mock=BL == "s41") # When connect is called default to a mock backend
def synchrotron() -> Synchrotron:
"""
A valid factory function which:
- always returns a singleton instance of the device
- optionally (re)connects when called
- may be reconnected concurrently with other devices
- optionally when first connecting may connect to mocked signal backends
- optionally may be named or renamed when called, setting the name after connecting
and propagating the name to all child devices
- optionally may be skipped when make_all_devices(this_beamline) called
"""
return Synchrotron()


@skip_device(
BL == "s41"
) # skip this device when calling make_all_devices(this_beamline)
def panda1(
wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
) -> Synchrotron:
"""Calls the Synchrotron class's constructor with name="synchrotron", prefix=""
If this is called when already instantiated, it will return the existing object.
) -> HDFPanda:
"""
A valid factory function which:
- always returns a singleton instance of the device
- optionally connects concurrently when multiple devices created simultaneously
- optionally when connecting may connect to mocked signal backends
- constructor must take prefix which may optionally exclude the BLIXX prefix
- constructor must take name which is set when the device is constructed
- optionally may be skipped when make_all_devices(this_beamline) called
"""
return device_instantiation(
Synchrotron,
"synchrotron",
"",
HDFPanda,
"panda1",
"-EA-PANDA-01:",
wait_for_connection,
fake_with_ophyd_sim,
bl_prefix=False,
)

def d11(name: str = "D11") -> AdAravisDetector:

def d11(name: str = "D11") -> AravisDetector:
"""
Also a valid Device factory function, but as multiple calls would instantiate
multiple copies of a device, discouraged.
Also a valid Device factory function, but as multiple calls would instantiate and
connect multiple copies of a device, discouraged.
Incompatible with calls to make_all_devices(this_beamline)
"""
return AdAravisDetector(name=name, prefix=f"{BL}-DI-DCAM-01:")

``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.
return AravisDetector(
name=name,
prefix=f"{PREFIX.beamline_prefix}-DI-DCAM-01:",
path_provider=get_path_provider(),
)
2 changes: 1 addition & 1 deletion docs/reference/device-standards.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Dodal is written with the philosophy that Ophyd devices should be assumed to be
should think about where to place them in the following order:

#. A device that could be used at any facility, e.g. a generic ``EpicsMotor`` or a commercial product with a
standard IOC, should go in https://github.com/bluesky/ophyd-epics-devices
standard IOC, should go in https://github.com/bluesky/ophyd-async/ or https://github.com/bluesky/ophyd-epics-devices
#. A device that may be on any beamline should go in the top level of the ``devices`` folder. If it is a quite
complex device (e.g. multiple files) it should have a folder of its own e.g. ``oav``
#. A device that is very specific to a particular beamline should go in the ``devices/ixx`` folder
Expand Down