Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

867 devices in blueapi context #871

Merged
merged 17 commits into from
Oct 12, 2023
Merged
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
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package_dir =
install_requires =
bluesky
pyepics
blueapi
blueapi @ git+https://github.com/DiamondLightSource/blueapi@main
flask-restful
zocalo
ispyb
Expand All @@ -36,7 +36,7 @@ install_requires =
xarray
doct
databroker
dodal @ git+https://github.com/DiamondLightSource/python-dodal.git@af63f5cf6742e094faac298396e2c16b422bcc8e
dls-dodal
pydantic<2.0 # See https://github.com/DiamondLightSource/hyperion/issues/774


Expand Down
51 changes: 30 additions & 21 deletions src/hyperion/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
from dataclasses import asdict
from queue import Queue
from traceback import format_exception
from typing import Callable, Optional, Tuple
from typing import Any, Callable, Optional, Tuple

from blueapi.core import BlueskyContext
from blueapi.core import BlueskyContext, MsgGenerator
from bluesky.run_engine import RunEngine
from flask import Flask, request
from flask_restful import Api, Resource
from pydantic.dataclasses import dataclass

import hyperion.experiment_plans as hyperion_plans
import hyperion.log
from hyperion.exceptions import WarningException
from hyperion.experiment_plans.experiment_registry import PLAN_REGISTRY, PlanNotFound
Expand All @@ -25,14 +24,16 @@
from hyperion.parameters.constants import Actions, Status
from hyperion.parameters.internal_parameters import InternalParameters
from hyperion.tracing import TRACER
from hyperion.utils.context import setup_context

VERBOSE_EVENT_LOGGING: Optional[bool] = None


@dataclass
class Command:
action: Actions
experiment: Optional[Callable] = None
devices: Optional[Any] = None
experiment: Optional[Callable[[Any, Any], MsgGenerator]] = None
parameters: Optional[InternalParameters] = None


Expand Down Expand Up @@ -62,25 +63,31 @@ class BlueskyRunner:
aperture_change_callback = ApertureChangeCallback()
RE: RunEngine
skip_startup_connection: bool
context: BlueskyContext

def __init__(self, RE: RunEngine, skip_startup_connection=False) -> None:
def __init__(
self, RE: RunEngine, context: BlueskyContext, skip_startup_connection=False
) -> None:
self.RE = RE
self.skip_startup_connection = skip_startup_connection
self.context = context
if VERBOSE_EVENT_LOGGING:
RE.subscribe(VerbosePlanExecutionLoggingCallback())
RE.subscribe(self.aperture_change_callback)

if not self.skip_startup_connection:
for plan_name in PLAN_REGISTRY:
PLAN_REGISTRY[plan_name]["setup"]()
PLAN_REGISTRY[plan_name]["setup"](context)

def start(
self, experiment: Callable, parameters: InternalParameters, plan_name: str
self,
experiment: Callable,
parameters: InternalParameters,
plan_name: str,
) -> StatusAndMessage:
hyperion.log.LOGGER.info(f"Started with parameters: {parameters}")

if self.skip_startup_connection:
PLAN_REGISTRY[plan_name]["setup"]()
devices: Any = PLAN_REGISTRY[plan_name]["setup"](self.context)

if (
self.current_status.status == Status.BUSY.value
Expand All @@ -89,7 +96,9 @@ def start(
return StatusAndMessage(Status.FAILED, "Bluesky already running")
else:
self.current_status = StatusAndMessage(Status.BUSY)
self.command_queue.put(Command(Actions.START, experiment, parameters))
self.command_queue.put(
Command(Actions.START, devices, experiment, parameters)
)
return StatusAndMessage(Status.SUCCESS)

def stopping_thread(self):
Expand Down Expand Up @@ -123,9 +132,11 @@ def wait_on_queue(self):
if command.action == Actions.SHUTDOWN:
return
elif command.action == Actions.START:
if command.experiment is None:
raise ValueError("No experiment provided for START")
try:
with TRACER.start_span("do_run"):
self.RE(command.experiment(command.parameters))
self.RE(command.experiment(command.devices, command.parameters))

self.current_status = StatusAndMessage(
Status.IDLE,
Expand Down Expand Up @@ -195,13 +206,6 @@ def put(self, plan_name: str, action: Actions):
return asdict(status_and_message) # type: ignore


def setup_context() -> BlueskyContext:
context = BlueskyContext()
context.with_plan_module(hyperion_plans)
hyperion.log.LOGGER.info(f"Found plans: {context.plan_functions}")
return context


class StopOrStatus(Resource):
def __init__(self, runner: BlueskyRunner) -> None:
super().__init__()
Expand All @@ -222,10 +226,15 @@ def get(self, **kwargs):


def create_app(
test_config=None, RE: RunEngine = RunEngine({}), skip_startup_connection=False
test_config=None,
RE: RunEngine = RunEngine({}),
skip_startup_connection: bool = False,
) -> Tuple[Flask, BlueskyRunner]:
runner = BlueskyRunner(RE, skip_startup_connection=skip_startup_connection)
context = setup_context()
context = setup_context(
wait_for_connection=not skip_startup_connection,
)

runner = BlueskyRunner(RE, context=context)
app = Flask(__name__)
if test_config:
app.config.update(test_config)
Expand Down
2 changes: 1 addition & 1 deletion src/hyperion/device_setup_plans/setup_oav.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def get_move_required_so_that_beam_is_at_pixel(
return calculate_x_y_z_of_pixel(current_motor_xyz, current_angle, pixel, oav_params)


def wait_for_tip_to_be_found(mxsc: MXSC):
def wait_for_tip_to_be_found(mxsc: MXSC) -> Generator[Msg, None, Tuple[int, int]]:
pin_tip = mxsc.pin_tip
yield from bps.trigger(pin_tip, wait=True)
found_tip = yield from bps.rd(pin_tip)
Expand Down
2 changes: 1 addition & 1 deletion src/hyperion/experiment_plans/experiment_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ def do_nothing():
},
"stepped_grid_scan": {
"setup": stepped_grid_scan_plan.create_devices,
"run": stepped_grid_scan_plan.get_plan,
"internal_param_type": SteppedGridScanInternalParameters,
"experiment_param_type": SteppedGridScanParams,
"callback_collection_type": NullPlanCallbackCollection,
},
}
EXPERIMENT_NAMES = list(PLAN_REGISTRY.keys())
Expand Down
138 changes: 59 additions & 79 deletions src/hyperion/experiment_plans/flyscan_xray_centre_plan.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
from __future__ import annotations

import argparse
import dataclasses
from typing import TYPE_CHECKING, Any

import bluesky.plan_stubs as bps
import bluesky.preprocessors as bpp
import numpy as np
from blueapi.core import MsgGenerator
from bluesky import RunEngine
from blueapi.core import BlueskyContext, MsgGenerator
from bluesky.run_engine import RunEngine
from bluesky.utils import ProgressBarManager
from dodal.beamlines import i03
from dodal.beamlines.i03 import (
ApertureScatterguard,
Attenuator,
Backlight,
EigerDetector,
FastGridScan,
Flux,
S4SlitGaps,
Smargon,
Synchrotron,
Undulator,
XBPMFeedback,
Zebra,
)
from dodal.devices.aperturescatterguard import AperturePositions
from dodal.devices.eiger import DetectorParams
from dodal.devices.aperturescatterguard import ApertureScatterguard
from dodal.devices.attenuator import Attenuator
from dodal.devices.backlight import Backlight
from dodal.devices.eiger import EigerDetector
from dodal.devices.fast_grid_scan import FastGridScan
from dodal.devices.fast_grid_scan import set_fast_grid_scan_params as set_flyscan_params
from dodal.devices.flux import Flux
from dodal.devices.s4_slit_gaps import S4SlitGaps
from dodal.devices.smargon import Smargon
from dodal.devices.synchrotron import Synchrotron
from dodal.devices.undulator import Undulator
from dodal.devices.xbpm_feedback import XBPMFeedback
from dodal.devices.zebra import Zebra

import hyperion.log
from hyperion.device_setup_plans.manipulate_sample import move_x_y_z
Expand All @@ -43,64 +39,51 @@
XrayCentreCallbackCollection,
)
from hyperion.parameters import external_parameters
from hyperion.parameters.beamline_parameters import (
get_beamline_parameters,
get_beamline_prefixes,
)
from hyperion.parameters.constants import SIM_BEAMLINE
from hyperion.tracing import TRACER
from hyperion.utils.aperturescatterguard import (
load_default_aperture_scatterguard_positions_if_unset,
)
from hyperion.utils.context import device_composite_from_context, setup_context

if TYPE_CHECKING:
from hyperion.parameters.plan_specific.gridscan_internal_params import (
GridscanInternalParameters,
)


class GridscanComposite:
"""A container for all the Devices required for a fast gridscan."""

def __init__(
self,
aperture_positions: AperturePositions = None,
detector_params: DetectorParams = None,
fake: bool = False,
):
self.aperture_scatterguard: ApertureScatterguard = i03.aperture_scatterguard(
fake_with_ophyd_sim=fake, aperture_positions=aperture_positions
@dataclasses.dataclass
class FlyScanXRayCentreComposite:
"""All devices which are directly or indirectly required by this plan"""

aperture_scatterguard: ApertureScatterguard
attenuator: Attenuator
backlight: Backlight
eiger: EigerDetector
fast_grid_scan: FastGridScan
flux: Flux
s4_slit_gaps: S4SlitGaps
smargon: Smargon
undulator: Undulator
synchrotron: Synchrotron
xbpm_feedback: XBPMFeedback
zebra: Zebra

@property
def sample_motors(self) -> Smargon:
"""Convenience alias with a more user-friendly name"""
return self.smargon

def __post_init__(self):
"""Ensure that aperture positions are loaded whenever this class is created."""
load_default_aperture_scatterguard_positions_if_unset(
self.aperture_scatterguard
)
self.backlight: Backlight = i03.backlight(fake_with_ophyd_sim=fake)
self.eiger: EigerDetector = i03.eiger(
fake_with_ophyd_sim=fake, params=detector_params
)
self.fast_grid_scan: FastGridScan = i03.fast_grid_scan(fake_with_ophyd_sim=fake)
self.flux: Flux = i03.flux(fake_with_ophyd_sim=fake)
self.s4_slit_gaps: S4SlitGaps = i03.s4_slit_gaps(fake_with_ophyd_sim=fake)
self.sample_motors: Smargon = i03.smargon(fake_with_ophyd_sim=fake)
self.undulator: Synchrotron = i03.undulator(fake_with_ophyd_sim=fake)
self.synchrotron: Undulator = i03.synchrotron(fake_with_ophyd_sim=fake)
self.zebra: Zebra = i03.zebra(fake_with_ophyd_sim=fake)
self.attenuator: Attenuator = i03.attenuator(fake_with_ophyd_sim=fake)
self.xbpm_feedback: XBPMFeedback = i03.xbpm_feedback(fake_with_ophyd_sim=fake)


flyscan_xray_centre_composite: GridscanComposite | None = None


def create_devices():
def create_devices(context: BlueskyContext) -> FlyScanXRayCentreComposite:
Tom-Willemsen marked this conversation as resolved.
Show resolved Hide resolved
"""Creates the devices required for the plan and connect to them"""
global flyscan_xray_centre_composite
prefixes = get_beamline_prefixes()
hyperion.log.LOGGER.info(
f"Creating devices for {prefixes.beamline_prefix} and {prefixes.insertion_prefix}"
)
aperture_positions = AperturePositions.from_gda_beamline_params(
get_beamline_parameters()
)
hyperion.log.LOGGER.info("Connecting to EPICS devices...")
flyscan_xray_centre_composite = GridscanComposite(
aperture_positions=aperture_positions
)
hyperion.log.LOGGER.info("Connected.")
return device_composite_from_context(context, FlyScanXRayCentreComposite)


def set_aperture_for_bbox_size(
Expand Down Expand Up @@ -145,15 +128,15 @@ def wait_for_gridscan_valid(fgs_motors: FastGridScan, timeout=0.5):
raise WarningException("Scan invalid - pin too long/short/bent and out of range")


def tidy_up_plans(fgs_composite: GridscanComposite):
def tidy_up_plans(fgs_composite: FlyScanXRayCentreComposite):
hyperion.log.LOGGER.info("Tidying up Zebra")
yield from set_zebra_shutter_to_manual(fgs_composite.zebra)


@bpp.set_run_key_decorator("run_gridscan")
@bpp.run_decorator(md={"subplan_name": "run_gridscan"})
def run_gridscan(
fgs_composite: GridscanComposite,
fgs_composite: FlyScanXRayCentreComposite,
parameters: GridscanInternalParameters,
md={
"plan_name": "run_gridscan",
Expand Down Expand Up @@ -207,7 +190,7 @@ def do_fgs():
@bpp.set_run_key_decorator("run_gridscan_and_move")
@bpp.run_decorator(md={"subplan_name": "run_gridscan_and_move"})
def run_gridscan_and_move(
fgs_composite: GridscanComposite,
fgs_composite: FlyScanXRayCentreComposite,
parameters: GridscanInternalParameters,
subscriptions: XrayCentreCallbackCollection,
):
Expand Down Expand Up @@ -247,6 +230,7 @@ def run_gridscan_and_move(


def flyscan_xray_centre(
composite: FlyScanXRayCentreComposite,
parameters: Any,
) -> MsgGenerator:
"""Create the plan to run the grid scan based on provided parameters.
Expand All @@ -260,10 +244,7 @@ def flyscan_xray_centre(
Returns:
Generator: The plan for the gridscan
"""
assert flyscan_xray_centre_composite is not None
flyscan_xray_centre_composite.eiger.set_detector_parameters(
parameters.hyperion_params.detector_params
)
composite.eiger.set_detector_parameters(parameters.hyperion_params.detector_params)

subscriptions = XrayCentreCallbackCollection.from_params(parameters)

Expand All @@ -277,18 +258,16 @@ def flyscan_xray_centre(
"hyperion_internal_parameters": parameters.json(),
}
)
@bpp.finalize_decorator(lambda: tidy_up_plans(flyscan_xray_centre_composite))
@bpp.finalize_decorator(lambda: tidy_up_plans(composite))
@transmission_and_xbpm_feedback_for_collection_decorator(
flyscan_xray_centre_composite.xbpm_feedback,
flyscan_xray_centre_composite.attenuator,
composite.xbpm_feedback,
composite.attenuator,
parameters.hyperion_params.ispyb_params.transmission_fraction,
)
def run_gridscan_and_move_and_tidy(fgs_composite, params, comms):
yield from run_gridscan_and_move(fgs_composite, params, comms)

return run_gridscan_and_move_and_tidy(
flyscan_xray_centre_composite, parameters, subscriptions
)
return run_gridscan_and_move_and_tidy(composite, parameters, subscriptions)


if __name__ == "__main__":
Expand All @@ -309,6 +288,7 @@ def run_gridscan_and_move_and_tidy(fgs_composite, params, comms):
parameters = GridscanInternalParameters(**external_parameters.from_file())
subscriptions = XrayCentreCallbackCollection.from_params(parameters)

create_devices()
context = setup_context(wait_for_connection=True)
composite = create_devices(context)

RE(flyscan_xray_centre(parameters))
RE(flyscan_xray_centre(composite, parameters))
Loading