mx-bluesky 1.5.15__py3-none-any.whl → 1.6.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. mx_bluesky/Getting started.ipynb +1 -0
  2. mx_bluesky/_version.py +2 -2
  3. mx_bluesky/beamlines/i02_1/parameters/gridscan.py +1 -1
  4. mx_bluesky/beamlines/i04/__init__.py +4 -0
  5. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +18 -0
  6. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +5 -4
  7. mx_bluesky/beamlines/i04/oav_centering_plans/oav_imaging.py +224 -10
  8. mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +5 -2
  9. mx_bluesky/beamlines/i04/thawing_plan.py +3 -2
  10. mx_bluesky/beamlines/i24/jungfrau_commissioning/__init__.py +13 -0
  11. mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/__init__.py +0 -0
  12. mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/metadata_writer.py +86 -0
  13. mx_bluesky/beamlines/i24/jungfrau_commissioning/composites.py +35 -0
  14. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +19 -20
  15. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/rotation_scan_plan.py +292 -0
  16. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_external_acquisition.py +4 -9
  17. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_internal_acquisition.py +4 -5
  18. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/plan_utils.py +15 -19
  19. mx_bluesky/beamlines/i24/parameters/__init__.py +0 -0
  20. mx_bluesky/beamlines/i24/parameters/constants.py +9 -0
  21. mx_bluesky/beamlines/i24/serial/dcid.py +3 -3
  22. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +7 -7
  23. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_collect_py3v1.py +7 -7
  24. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_chip_manager_py3v1.py +3 -3
  25. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +3 -3
  26. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +5 -5
  27. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +7 -7
  28. mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +1 -1
  29. mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -24
  30. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +5 -2
  31. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +2 -2
  32. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +1 -0
  33. mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +1 -1
  34. mx_bluesky/common/experiment_plans/pin_tip_centring_plan.py +2 -2
  35. mx_bluesky/common/experiment_plans/rotation/__init__.py +0 -0
  36. mx_bluesky/common/experiment_plans/rotation/rotation_utils.py +127 -0
  37. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +13 -2
  38. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +0 -2
  39. mx_bluesky/common/external_interaction/ispyb/data_model.py +1 -1
  40. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +1 -1
  41. mx_bluesky/common/parameters/components.py +17 -7
  42. mx_bluesky/common/parameters/constants.py +6 -0
  43. mx_bluesky/{hyperion → common}/parameters/rotation.py +10 -8
  44. mx_bluesky/common/preprocessors/preprocessors.py +98 -36
  45. mx_bluesky/hyperion/__main__.py +55 -22
  46. mx_bluesky/hyperion/baton_handler.py +24 -64
  47. mx_bluesky/hyperion/blueapi_config.yaml +17 -0
  48. mx_bluesky/hyperion/blueapi_dev_config.yaml +16 -0
  49. mx_bluesky/hyperion/blueapi_plans/__init__.py +96 -0
  50. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +9 -7
  51. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -1
  52. mx_bluesky/hyperion/device_setup_plans/utils.py +1 -1
  53. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -1
  54. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +1 -0
  55. mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +2 -2
  56. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +3 -1
  57. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +17 -6
  58. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +2 -5
  59. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +3 -3
  60. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +14 -128
  61. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +4 -4
  62. mx_bluesky/hyperion/experiment_plans/udc_default_state.py +19 -6
  63. mx_bluesky/hyperion/external_interaction/agamemnon.py +3 -8
  64. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +121 -47
  65. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
  66. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +3 -1
  67. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +6 -3
  68. mx_bluesky/hyperion/external_interaction/callbacks/stomp/__init__.py +0 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/stomp/dispatcher.py +33 -0
  70. mx_bluesky/hyperion/in_process_runner.py +132 -0
  71. mx_bluesky/hyperion/parameters/cli.py +43 -4
  72. mx_bluesky/hyperion/parameters/components.py +13 -0
  73. mx_bluesky/hyperion/parameters/constants.py +2 -9
  74. mx_bluesky/hyperion/parameters/device_composites.py +1 -1
  75. mx_bluesky/hyperion/parameters/load_centre_collect.py +3 -1
  76. mx_bluesky/hyperion/plan_runner.py +45 -66
  77. mx_bluesky/hyperion/plan_runner_api.py +3 -4
  78. mx_bluesky/hyperion/supervisor/__init__.py +3 -0
  79. mx_bluesky/hyperion/supervisor/_supervisor.py +116 -0
  80. mx_bluesky/hyperion/supervisor/client_config.yaml +6 -0
  81. mx_bluesky/hyperion/supervisor/supervisor_config.yaml +10 -0
  82. mx_bluesky/hyperion/supervisor/supervisor_dev_config.yaml +9 -0
  83. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/METADATA +3 -31
  84. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/RECORD +88 -68
  85. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/WHEEL +1 -1
  86. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/entry_points.txt +0 -0
  87. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/licenses/LICENSE +0 -0
  88. {mx_bluesky-1.5.15.dist-info → mx_bluesky-1.6.3.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ from mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan
11
11
  )
12
12
  from mx_bluesky.common.parameters.constants import OavConstants, PlanNameConstants
13
13
  from mx_bluesky.common.preprocessors.preprocessors import (
14
- transmission_and_xbpm_feedback_for_collection_decorator,
14
+ pause_xbpm_feedback_during_collection_at_desired_transmission_decorator,
15
15
  )
16
16
  from mx_bluesky.common.utils.context import device_composite_from_context
17
17
  from mx_bluesky.hyperion.experiment_plans.hyperion_flyscan_xray_centre_plan import (
@@ -45,7 +45,7 @@ def hyperion_grid_detect_then_xray_centre(
45
45
  """
46
46
 
47
47
  @verify_undulator_gap_before_run_decorator(composite)
48
- @transmission_and_xbpm_feedback_for_collection_decorator(
48
+ @pause_xbpm_feedback_during_collection_at_desired_transmission_decorator(
49
49
  composite, parameters.transmission_frac, PlanNameConstants.GRIDSCAN_OUTER
50
50
  )
51
51
  def plan_to_perform():
@@ -13,6 +13,9 @@ from dodal.devices.oav.oav_parameters import OAVParameters
13
13
 
14
14
  import mx_bluesky.common.xrc_result as flyscan_result
15
15
  from mx_bluesky.common.parameters.components import WithSnapshot
16
+ from mx_bluesky.common.parameters.rotation import (
17
+ RotationScanPerSweep,
18
+ )
16
19
  from mx_bluesky.common.utils.context import device_composite_from_context
17
20
  from mx_bluesky.common.utils.exceptions import CrystalNotFoundError
18
21
  from mx_bluesky.common.utils.log import LOGGER
@@ -31,7 +34,6 @@ from mx_bluesky.hyperion.external_interaction.config_server import (
31
34
  )
32
35
  from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
33
36
  from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
34
- from mx_bluesky.hyperion.parameters.rotation import RotationScanPerSweep
35
37
 
36
38
 
37
39
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
@@ -28,7 +28,10 @@ from mx_bluesky.common.experiment_plans.pin_tip_centring_plan import (
28
28
  from mx_bluesky.common.external_interaction.callbacks.xray_centre.ispyb_callback import (
29
29
  ispyb_activation_wrapper,
30
30
  )
31
- from mx_bluesky.common.parameters.constants import OavConstants
31
+ from mx_bluesky.common.parameters.constants import OavConstants, PlanNameConstants
32
+ from mx_bluesky.common.preprocessors.preprocessors import (
33
+ pause_xbpm_feedback_during_collection_at_desired_transmission_decorator,
34
+ )
32
35
  from mx_bluesky.common.utils.context import device_composite_from_context
33
36
  from mx_bluesky.common.utils.log import LOGGER
34
37
  from mx_bluesky.common.xrc_result import XRayCentreEventHandler
@@ -103,13 +106,21 @@ def pin_centre_then_flyscan_plan(
103
106
  grid_detect_params = create_parameters_for_grid_detection(parameters)
104
107
  oav_params = OAVParameters("xrayCentring", oav_config_file)
105
108
 
106
- yield from detect_grid_and_do_gridscan(
109
+ @pause_xbpm_feedback_during_collection_at_desired_transmission_decorator(
107
110
  composite,
108
- grid_detect_params,
109
- oav_params,
110
- HyperionSpecifiedThreeDGridScan,
111
- construct_hyperion_specific_features,
111
+ parameters.transmission_frac,
112
+ PlanNameConstants.GRIDSCAN_OUTER,
112
113
  )
114
+ def _grid_detect_plan():
115
+ yield from detect_grid_and_do_gridscan(
116
+ composite,
117
+ grid_detect_params,
118
+ oav_params,
119
+ HyperionSpecifiedThreeDGridScan,
120
+ construct_hyperion_specific_features,
121
+ )
122
+
123
+ yield from _grid_detect_plan()
113
124
 
114
125
  yield from ispyb_activation_wrapper(_pin_centre_then_flyscan_plan(), parameters)
115
126
 
@@ -13,9 +13,9 @@ from bluesky.utils import Msg
13
13
  from dodal.devices.aperturescatterguard import ApertureScatterguard
14
14
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
15
15
  from dodal.devices.backlight import Backlight, InOut
16
+ from dodal.devices.beamlines.i03.dcm import DCM
17
+ from dodal.devices.beamlines.i03.undulator_dcm import UndulatorDCM
16
18
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
17
- from dodal.devices.i03.dcm import DCM
18
- from dodal.devices.i03.undulator_dcm import UndulatorDCM
19
19
  from dodal.devices.motors import XYZStage
20
20
  from dodal.devices.oav.oav_detector import OAV
21
21
  from dodal.devices.robot import BartRobot, SampleLocation
@@ -27,7 +27,6 @@ from dodal.devices.xbpm_feedback import XBPMFeedback
27
27
  from mx_bluesky.common.device_setup_plans.robot_load_unload import (
28
28
  do_plan_while_lower_gonio_at_home,
29
29
  prepare_for_robot_load,
30
- wait_for_smargon_not_disabled,
31
30
  )
32
31
  from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
33
32
  SetEnergyComposite,
@@ -95,8 +94,6 @@ def do_robot_load(
95
94
 
96
95
  yield from bps.wait("robot_load")
97
96
 
98
- yield from wait_for_smargon_not_disabled(composite.smargon)
99
-
100
97
  yield from bps.mv(composite.thawer, OnOff.ON)
101
98
 
102
99
 
@@ -10,15 +10,15 @@ from bluesky.utils import MsgGenerator
10
10
  from dodal.devices.aperturescatterguard import ApertureScatterguard
11
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
12
  from dodal.devices.backlight import Backlight
13
+ from dodal.devices.beamlines.i03 import Beamstop
14
+ from dodal.devices.beamlines.i03.dcm import DCM
15
+ from dodal.devices.beamlines.i03.undulator_dcm import UndulatorDCM
13
16
  from dodal.devices.beamsize.beamsize import BeamsizeBase
14
17
  from dodal.devices.detector.detector_motion import DetectorMotion
15
18
  from dodal.devices.eiger import EigerDetector
16
19
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScanThreeD
17
20
  from dodal.devices.flux import Flux
18
21
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
19
- from dodal.devices.i03 import Beamstop
20
- from dodal.devices.i03.dcm import DCM
21
- from dodal.devices.i03.undulator_dcm import UndulatorDCM
22
22
  from dodal.devices.motors import XYZStage
23
23
  from dodal.devices.oav.oav_detector import OAV
24
24
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
@@ -1,7 +1,3 @@
1
- from __future__ import annotations
2
-
3
- import dataclasses
4
-
5
1
  import bluesky.plan_stubs as bps
6
2
  import bluesky.preprocessors as bpp
7
3
  import pydantic
@@ -10,12 +6,12 @@ from bluesky.utils import MsgGenerator
10
6
  from dodal.devices.aperturescatterguard import ApertureScatterguard
11
7
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
8
  from dodal.devices.backlight import Backlight
9
+ from dodal.devices.beamlines.i03 import Beamstop
10
+ from dodal.devices.beamlines.i03.dcm import DCM
13
11
  from dodal.devices.beamsize.beamsize import BeamsizeBase
14
12
  from dodal.devices.detector.detector_motion import DetectorMotion
15
13
  from dodal.devices.eiger import EigerDetector
16
14
  from dodal.devices.flux import Flux
17
- from dodal.devices.i03 import Beamstop
18
- from dodal.devices.i03.dcm import DCM
19
15
  from dodal.devices.oav.oav_detector import OAV
20
16
  from dodal.devices.oav.oav_parameters import OAVParameters
21
17
  from dodal.devices.robot import BartRobot
@@ -25,7 +21,7 @@ from dodal.devices.synchrotron import Synchrotron
25
21
  from dodal.devices.thawer import Thawer
26
22
  from dodal.devices.undulator import UndulatorInKeV
27
23
  from dodal.devices.xbpm_feedback import XBPMFeedback
28
- from dodal.devices.zebra.zebra import RotationDirection, Zebra
24
+ from dodal.devices.zebra.zebra import Zebra
29
25
  from dodal.devices.zebra.zebra_controlled_shutter import ZebraShutter
30
26
  from dodal.plan_stubs.check_topup import check_topup_and_wait_if_necessary
31
27
  from dodal.plans.preprocessors.verify_undulator_gap import (
@@ -53,20 +49,24 @@ from mx_bluesky.common.experiment_plans.oav_snapshot_plan import (
53
49
  oav_snapshot_plan,
54
50
  setup_beamline_for_oav,
55
51
  )
52
+ from mx_bluesky.common.experiment_plans.rotation.rotation_utils import (
53
+ RotationMotionProfile,
54
+ calculate_motion_profile,
55
+ )
56
56
  from mx_bluesky.common.parameters.components import WithSnapshot
57
+ from mx_bluesky.common.parameters.rotation import (
58
+ RotationScan,
59
+ SingleRotationScan,
60
+ )
57
61
  from mx_bluesky.common.preprocessors.preprocessors import (
58
- transmission_and_xbpm_feedback_for_collection_decorator,
62
+ pause_xbpm_feedback_during_collection_at_desired_transmission_decorator,
59
63
  )
60
64
  from mx_bluesky.common.utils.context import device_composite_from_context
61
65
  from mx_bluesky.common.utils.log import LOGGER
62
66
  from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
63
67
  arm_zebra,
64
68
  )
65
- from mx_bluesky.hyperion.parameters.constants import CONST, I03Constants
66
- from mx_bluesky.hyperion.parameters.rotation import (
67
- RotationScan,
68
- SingleRotationScan,
69
- )
69
+ from mx_bluesky.hyperion.parameters.constants import CONST
70
70
 
71
71
 
72
72
  @pydantic.dataclasses.dataclass(config={"arbitrary_types_allowed": True})
@@ -100,120 +100,6 @@ def create_devices(context: BlueskyContext) -> RotationScanComposite:
100
100
  return device_composite_from_context(context, RotationScanComposite)
101
101
 
102
102
 
103
- DEFAULT_DIRECTION = RotationDirection.NEGATIVE
104
- DEFAULT_MAX_VELOCITY = 120
105
- # Use a slightly larger time to acceleration than EPICS as it's better to be cautious
106
- ACCELERATION_MARGIN = 1.5
107
-
108
-
109
- @dataclasses.dataclass
110
- class RotationMotionProfile:
111
- start_scan_deg: float
112
- start_motion_deg: float
113
- scan_width_deg: float
114
- shutter_time_s: float
115
- direction: RotationDirection
116
- speed_for_rotation_deg_s: float
117
- acceleration_offset_deg: float
118
- shutter_opening_deg: float
119
- total_exposure_s: float
120
- distance_to_move_deg: float
121
- max_velocity_deg_s: float
122
-
123
-
124
- def calculate_motion_profile(
125
- params: SingleRotationScan,
126
- motor_time_to_speed_s: float,
127
- max_velocity_deg_s: float,
128
- ) -> RotationMotionProfile:
129
- """Calculates the various numbers needed for motions in the rotation scan.
130
- Rotates through "scan width" plus twice an "offset" to take into account
131
- acceleration at the start and deceleration at the end, plus the number of extra
132
- degrees of rotation needed to make sure the fast shutter has fully opened before the
133
- detector trigger is sent.
134
- See https://github.com/DiamondLightSource/hyperion/wiki/rotation-scan-geometry
135
- for a simple pictorial explanation."""
136
-
137
- assert params.rotation_increment_deg > 0
138
-
139
- direction = params.rotation_direction
140
- start_scan_deg = params.omega_start_deg
141
-
142
- if I03Constants.OMEGA_FLIP:
143
- # If omega_flip is True then the motor omega axis is inverted with respect to the
144
- # hyperion coordinate system.
145
- start_scan_deg = -start_scan_deg
146
- direction = (
147
- direction.POSITIVE
148
- if direction == direction.NEGATIVE
149
- else direction.NEGATIVE
150
- )
151
-
152
- num_images = params.num_images
153
- shutter_time_s = params.shutter_opening_time_s
154
- image_width_deg = params.rotation_increment_deg
155
- exposure_time_s = params.exposure_time_s
156
- motor_time_to_speed_s *= ACCELERATION_MARGIN
157
-
158
- LOGGER.info("Calculating rotation scan motion profile:")
159
- LOGGER.info(
160
- f"{num_images=}, {shutter_time_s=}, {image_width_deg=}, {exposure_time_s=}, {direction=}"
161
- )
162
-
163
- scan_width_deg = num_images * params.rotation_increment_deg
164
- LOGGER.info(f"{scan_width_deg=} = {num_images=} * {params.rotation_increment_deg=}")
165
-
166
- speed_for_rotation_deg_s = image_width_deg / exposure_time_s
167
- LOGGER.info("speed_for_rotation_deg_s = image_width_deg / exposure_time_s")
168
- LOGGER.info(
169
- f"{speed_for_rotation_deg_s=} = {image_width_deg=} / {exposure_time_s=}"
170
- )
171
-
172
- acceleration_offset_deg = motor_time_to_speed_s * speed_for_rotation_deg_s
173
- LOGGER.info(
174
- f"{acceleration_offset_deg=} = {motor_time_to_speed_s=} * {speed_for_rotation_deg_s=}"
175
- )
176
-
177
- start_motion_deg = start_scan_deg - (acceleration_offset_deg * direction.multiplier)
178
- LOGGER.info(
179
- f"{start_motion_deg=} = {start_scan_deg=} - ({acceleration_offset_deg=} * {direction.multiplier=})"
180
- )
181
-
182
- shutter_opening_deg = speed_for_rotation_deg_s * shutter_time_s
183
- LOGGER.info(
184
- f"{shutter_opening_deg=} = {speed_for_rotation_deg_s=} * {shutter_time_s=}"
185
- )
186
-
187
- shutter_opening_deg = speed_for_rotation_deg_s * shutter_time_s
188
- LOGGER.info(
189
- f"{shutter_opening_deg=} = {speed_for_rotation_deg_s=} * {shutter_time_s=}"
190
- )
191
-
192
- total_exposure_s = num_images * exposure_time_s
193
- LOGGER.info(f"{total_exposure_s=} = {num_images=} * {exposure_time_s=}")
194
-
195
- distance_to_move_deg = (
196
- scan_width_deg + shutter_opening_deg + acceleration_offset_deg * 2
197
- ) * direction.multiplier
198
- LOGGER.info(
199
- f"{distance_to_move_deg=} = ({scan_width_deg=} + {shutter_opening_deg=} + {acceleration_offset_deg=} * 2) * {direction=})"
200
- )
201
-
202
- return RotationMotionProfile(
203
- start_scan_deg=start_scan_deg,
204
- start_motion_deg=start_motion_deg,
205
- scan_width_deg=scan_width_deg,
206
- shutter_time_s=shutter_time_s,
207
- direction=direction,
208
- speed_for_rotation_deg_s=speed_for_rotation_deg_s,
209
- acceleration_offset_deg=acceleration_offset_deg,
210
- shutter_opening_deg=shutter_opening_deg,
211
- total_exposure_s=total_exposure_s,
212
- distance_to_move_deg=distance_to_move_deg,
213
- max_velocity_deg_s=max_velocity_deg_s,
214
- )
215
-
216
-
217
103
  def rotation_scan_plan(
218
104
  composite: RotationScanComposite,
219
105
  params: SingleRotationScan,
@@ -399,7 +285,7 @@ def rotation_scan_internal(
399
285
  eiger: EigerDetector = composite.eiger
400
286
  eiger.set_detector_parameters(parameters.detector_params)
401
287
 
402
- @transmission_and_xbpm_feedback_for_collection_decorator(
288
+ @pause_xbpm_feedback_during_collection_at_desired_transmission_decorator(
403
289
  composite,
404
290
  parameters.transmission_frac,
405
291
  )
@@ -9,15 +9,15 @@ import bluesky.preprocessors as bpp
9
9
  import pydantic
10
10
  from bluesky import plan_stubs as bps
11
11
  from dodal.devices.attenuator.attenuator import BinaryFilterAttenuator
12
+ from dodal.devices.beamlines.i03.dcm import DCM
13
+ from dodal.devices.beamlines.i03.undulator_dcm import UndulatorDCM
12
14
  from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
13
- from dodal.devices.i03.dcm import DCM
14
- from dodal.devices.i03.undulator_dcm import UndulatorDCM
15
15
  from dodal.devices.undulator import UndulatorInKeV
16
16
  from dodal.devices.xbpm_feedback import XBPMFeedback
17
17
 
18
18
  from mx_bluesky.common.parameters.constants import PlanNameConstants
19
19
  from mx_bluesky.common.preprocessors.preprocessors import (
20
- transmission_and_xbpm_feedback_for_collection_wrapper,
20
+ pause_xbpm_feedback_during_collection_at_desired_transmission_wrapper,
21
21
  )
22
22
  from mx_bluesky.hyperion.device_setup_plans import dcm_pitch_roll_mirror_adjuster
23
23
 
@@ -74,7 +74,7 @@ def set_energy_plan(
74
74
  )
75
75
 
76
76
  if energy_ev:
77
- yield from transmission_and_xbpm_feedback_for_collection_wrapper(
77
+ yield from pause_xbpm_feedback_during_collection_at_desired_transmission_wrapper(
78
78
  _set_energy_plan(energy_ev / 1000, composite),
79
79
  composite_for_wrapper,
80
80
  DESIRED_TRANSMISSION_FRACTION,
@@ -63,12 +63,7 @@ def move_to_udc_default_state(devices: UDCDefaultDevices):
63
63
  """Moves beamline to known positions prior to UDC start"""
64
64
  yield from _verify_correct_cryostream_selected(devices.cryostream_gantry)
65
65
 
66
- cryostream_temp = yield from bps.rd(devices.cryostream.temp)
67
- cryostream_pressure = yield from bps.rd(devices.cryostream.back_pressure)
68
- if cryostream_temp > CONST.HARDWARE.MAX_CRYO_TEMP_K:
69
- raise CryoStreamError("Cryostream temperature is too high, not starting UDC")
70
- if cryostream_pressure > CONST.HARDWARE.MAX_CRYO_PRESSURE_BAR:
71
- raise CryoStreamError("Cryostream back pressure is too high, not starting UDC")
66
+ yield from _check_cryostream(devices)
72
67
 
73
68
  yield from _verify_no_sample_present(devices.robot)
74
69
 
@@ -150,6 +145,24 @@ def move_to_udc_default_state(devices: UDCDefaultDevices):
150
145
  yield from bps.wait(_GROUP_POST_BEAMSTOP_CHECK, timeout=10)
151
146
 
152
147
 
148
+ def _check_cryostream(devices: UDCDefaultDevices):
149
+ commissioning_mode = yield from bps.rd(devices.baton.commissioning)
150
+ cryo_mode = yield from bps.rd(devices.robot.cryomode_rbv)
151
+ if commissioning_mode and cryo_mode == BartRobot.CRYO_MODE_WARM:
152
+ LOGGER.warning("Ignoring cryostream status in commissioning mode")
153
+ else:
154
+ cryostream_temp = yield from bps.rd(devices.cryostream.temp)
155
+ cryostream_pressure = yield from bps.rd(devices.cryostream.back_pressure)
156
+ if cryostream_temp > CONST.HARDWARE.MAX_CRYO_TEMP_K:
157
+ raise CryoStreamError(
158
+ "Cryostream temperature is too high, not starting UDC"
159
+ )
160
+ if cryostream_pressure > CONST.HARDWARE.MAX_CRYO_PRESSURE_BAR:
161
+ raise CryoStreamError(
162
+ "Cryostream back pressure is too high, not starting UDC"
163
+ )
164
+
165
+
153
166
  def _verify_correct_cryostream_selected(
154
167
  cryostream_gantry: CryoStreamGantry,
155
168
  ) -> MsgGenerator:
@@ -12,12 +12,11 @@ import requests
12
12
  from deepdiff.diff import DeepDiff
13
13
  from dodal.utils import get_beamline_name
14
14
  from jsonschema import ValidationError
15
- from pydantic_extra_types.semantic_version import SemanticVersion
16
15
 
17
16
  from mx_bluesky.common.parameters.components import (
18
- PARAMETER_VERSION,
19
17
  MxBlueskyParameters,
20
18
  WithVisit,
19
+ get_param_version,
21
20
  )
22
21
  from mx_bluesky.common.parameters.constants import (
23
22
  GridscanParamConstants,
@@ -88,7 +87,7 @@ def create_parameters_from_agamemnon() -> Sequence[MxBlueskyParameters]:
88
87
  Wait.model_validate(
89
88
  {
90
89
  "duration_s": data,
91
- "parameter_model_version": _get_param_version(),
90
+ "parameter_model_version": get_param_version(),
92
91
  }
93
92
  )
94
93
  ]
@@ -228,10 +227,6 @@ def _get_withenergy_parameters_from_agamemnon(parameters: dict) -> dict[str, Any
228
227
  return {"demand_energy_ev": None}
229
228
 
230
229
 
231
- def _get_param_version() -> SemanticVersion:
232
- return SemanticVersion.validate_from_str(str(PARAMETER_VERSION))
233
-
234
-
235
230
  def _populate_parameters_from_agamemnon(
236
231
  agamemnon_params,
237
232
  ) -> Sequence[LoadCentreCollect]:
@@ -250,7 +245,7 @@ def _populate_parameters_from_agamemnon(
250
245
  return [
251
246
  LoadCentreCollect.model_validate(
252
247
  {
253
- "parameter_model_version": _get_param_version(),
248
+ "parameter_model_version": get_param_version(),
254
249
  "visit": visit,
255
250
  "detector_distance_mm": detector_distance,
256
251
  "sample_id": agamemnon_params["sample"]["id"],
@@ -1,12 +1,17 @@
1
1
  import logging
2
- from collections.abc import Callable, Sequence
2
+ from abc import abstractmethod
3
+ from collections.abc import Callable
4
+ from contextlib import AbstractContextManager
3
5
  from threading import Thread
4
6
  from time import sleep # noqa
5
7
  from urllib import request
6
8
  from urllib.error import URLError
7
9
 
10
+ from blueapi.config import ApplicationConfig, ConfigLoader
8
11
  from bluesky.callbacks import CallbackBase
9
12
  from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
13
+ from bluesky_stomp.messaging import StompClient
14
+ from bluesky_stomp.models import Broker
10
15
  from dodal.log import LOGGER as DODAL_LOGGER
11
16
  from dodal.log import set_up_all_logging_handlers
12
17
 
@@ -52,8 +57,11 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback
52
57
  from mx_bluesky.hyperion.external_interaction.callbacks.snapshot_callback import (
53
58
  BeamDrawingCallback,
54
59
  )
55
- from mx_bluesky.hyperion.parameters.cli import parse_callback_dev_mode_arg
56
- from mx_bluesky.hyperion.parameters.constants import CONST, HyperionConstants
60
+ from mx_bluesky.hyperion.external_interaction.callbacks.stomp.dispatcher import (
61
+ StompDispatcher,
62
+ )
63
+ from mx_bluesky.hyperion.parameters.cli import CallbackArgs, parse_callback_args
64
+ from mx_bluesky.hyperion.parameters.constants import CONST
57
65
  from mx_bluesky.hyperion.parameters.gridscan import (
58
66
  GridCommonWithHyperionDetectorParams,
59
67
  HyperionSpecifiedThreeDGridScan,
@@ -143,29 +151,72 @@ def log_debug(msg, *args, **kwargs):
143
151
  NEXUS_LOGGER.debug(msg, *args, **kwargs)
144
152
 
145
153
 
146
- def wait_for_threads_forever(threads: Sequence[Thread]):
147
- alive = [t.is_alive() for t in threads]
148
- try:
149
- log_debug("Trying to wait forever on callback and dispatcher threads")
150
- while all(alive):
151
- sleep(LIVENESS_POLL_SECONDS)
152
- alive = [t.is_alive() for t in threads]
153
- except KeyboardInterrupt:
154
- log_info("Main thread received interrupt - exiting.")
155
- else:
156
- log_info("Proxy or dispatcher thread ended - exiting.")
157
-
158
-
159
154
  class HyperionCallbackRunner:
160
155
  """Runs Nexus, ISPyB and Zocalo callbacks in their own process."""
161
156
 
162
- def __init__(self, dev_mode) -> None:
163
- setup_logging(dev_mode)
157
+ def __init__(self, callback_args: CallbackArgs) -> None:
158
+ setup_logging(callback_args.dev_mode)
164
159
  log_info("Hyperion callback process started.")
165
160
  set_alerting_service(LoggingAlertService(CONST.GRAYLOG_STREAM_ID))
166
161
 
167
162
  self.callbacks = setup_callbacks()
168
163
 
164
+ self.watchdog_thread = Thread(
165
+ target=run_watchdog,
166
+ daemon=True,
167
+ name="Watchdog",
168
+ args=[callback_args.watchdog_port],
169
+ )
170
+
171
+ self._dispatcher_cm: DispatcherContextMgr
172
+ if callback_args.stomp_config:
173
+ self._dispatcher_cm = StompDispatcherContextMgr(
174
+ callback_args, self.callbacks
175
+ )
176
+ else:
177
+ self._dispatcher_cm = RemoteDispatcherContextMgr(self.callbacks)
178
+
179
+ def start(self):
180
+ log_info(f"Launching threads, with callbacks: {self.callbacks}")
181
+ self.watchdog_thread.start()
182
+ with self._dispatcher_cm:
183
+ ping_watchdog_while_alive(self._dispatcher_cm, self.watchdog_thread)
184
+
185
+
186
+ def run_watchdog(watchdog_port: int):
187
+ log_info("Hyperion watchdog keepalive running")
188
+ while True:
189
+ try:
190
+ with request.urlopen(
191
+ f"http://localhost:{watchdog_port}/callbackPing",
192
+ timeout=PING_TIMEOUT_S,
193
+ ) as response:
194
+ if response.status != 200:
195
+ log_debug(
196
+ f"Unable to ping Hyperion liveness endpoint, status {response.status}"
197
+ )
198
+ except URLError as e:
199
+ log_debug("Unable to ping Hyperion liveness endpoint", exc_info=e)
200
+ sleep(HYPERION_PING_INTERVAL_S)
201
+
202
+
203
+ def main(dev_mode=False) -> None:
204
+ callback_args = parse_callback_args()
205
+ callback_args.dev_mode = dev_mode or callback_args.dev_mode
206
+ print(f"In dev mode: {dev_mode}")
207
+ runner = HyperionCallbackRunner(callback_args)
208
+ runner.start()
209
+
210
+
211
+ class DispatcherContextMgr(AbstractContextManager):
212
+ @abstractmethod
213
+ def is_alive(self) -> bool: ...
214
+
215
+
216
+ class RemoteDispatcherContextMgr(DispatcherContextMgr):
217
+ def __init__(self, callbacks: list[CallbackBase]):
218
+ super().__init__()
219
+
169
220
  self.proxy = Proxy(*CONST.CALLBACK_0MQ_PROXY_PORTS)
170
221
  self.proxy_thread = Thread(
171
222
  target=self.proxy.start, daemon=True, name="0MQ Proxy"
@@ -182,47 +233,70 @@ class HyperionCallbackRunner:
182
233
 
183
234
  self.dispatcher_thread = Thread(
184
235
  target=start_dispatcher,
185
- args=[self.callbacks],
236
+ args=[callbacks],
186
237
  daemon=True,
187
238
  name="0MQ Dispatcher",
188
239
  )
189
-
190
- self.watchdog_thread = Thread(target=run_watchdog, daemon=True, name="Watchdog")
191
240
  log_info("Created 0MQ proxy and local RemoteDispatcher.")
192
241
 
193
- def start(self):
194
- log_info(f"Launching threads, with callbacks: {self.callbacks}")
242
+ def __enter__(self):
243
+ log_info("Proxy and dispatcher thread launched.")
195
244
  self.proxy_thread.start()
196
245
  self.dispatcher_thread.start()
197
- self.watchdog_thread.start()
198
- log_info("Proxy and dispatcher thread launched.")
199
- wait_for_threads_forever(
200
- [self.proxy_thread, self.dispatcher_thread, self.watchdog_thread]
246
+ return self
247
+
248
+ def __exit__(self, exc_type, exc_value, traceback, /):
249
+ self.dispatcher.stop()
250
+ # proxy has no way to stop
251
+
252
+ def is_alive(self):
253
+ return self.proxy_thread.is_alive() and self.dispatcher_thread.is_alive()
254
+
255
+
256
+ class StompDispatcherContextMgr(DispatcherContextMgr):
257
+ def __init__(self, args: CallbackArgs, callbacks: list[CallbackBase]):
258
+ super().__init__()
259
+ loader = ConfigLoader(ApplicationConfig)
260
+ loader.use_values_from_yaml(args.stomp_config)
261
+ config = loader.load()
262
+ log_info(
263
+ f"Stomp client configured on {config.stomp.url.host}:{config.stomp.url.port}"
264
+ )
265
+ self._stomp_client = StompClient.for_broker(
266
+ broker=Broker(
267
+ host=config.stomp.url.host,
268
+ port=config.stomp.url.port,
269
+ auth=config.stomp.auth,
270
+ )
201
271
  )
272
+ self._dispatcher = StompDispatcher(self._stomp_client)
273
+ for cb in callbacks:
274
+ self._dispatcher.subscribe(cb)
202
275
 
276
+ def is_alive(self) -> bool:
277
+ return self._stomp_client.is_connected()
203
278
 
204
- def run_watchdog():
205
- log_info("Hyperion watchdog keepalive running")
206
- while True:
207
- try:
208
- with request.urlopen(
209
- f"http://localhost:{HyperionConstants.HYPERION_PORT}/callbackPing",
210
- timeout=PING_TIMEOUT_S,
211
- ) as response:
212
- if response.status != 200:
213
- log_debug(
214
- f"Unable to ping Hyperion liveness endpoint, status {response.status}"
215
- )
216
- except URLError as e:
217
- log_debug("Unable to ping Hyperion liveness endpoint", exc_info=e)
218
- sleep(HYPERION_PING_INTERVAL_S)
279
+ def __enter__(self):
280
+ self._dispatcher.__enter__()
281
+ return self
219
282
 
283
+ def __exit__(self, exc_type, exc_value, traceback, /):
284
+ self._dispatcher.__exit__(exc_type, exc_value, traceback)
220
285
 
221
- def main(dev_mode=False) -> None:
222
- dev_mode = dev_mode or parse_callback_dev_mode_arg()
223
- print(f"In dev mode: {dev_mode}")
224
- runner = HyperionCallbackRunner(dev_mode)
225
- runner.start()
286
+
287
+ def ping_watchdog_while_alive(
288
+ dispatcher_cm: DispatcherContextMgr, watchdog_thread: Thread
289
+ ):
290
+ alive = watchdog_thread.is_alive() and dispatcher_cm.is_alive()
291
+ try:
292
+ log_debug("Trying to wait forever on callback and dispatcher threads")
293
+ while alive:
294
+ sleep(LIVENESS_POLL_SECONDS)
295
+ alive = watchdog_thread.is_alive() and dispatcher_cm.is_alive()
296
+ except KeyboardInterrupt:
297
+ log_info("Main thread received interrupt - exiting.")
298
+ else:
299
+ log_info("Proxy or dispatcher thread ended - exiting.")
226
300
 
227
301
 
228
302
  if __name__ == "__main__":
@@ -25,13 +25,15 @@ from mx_bluesky.common.external_interaction.ispyb.ispyb_store import (
25
25
  StoreInIspyb,
26
26
  )
27
27
  from mx_bluesky.common.parameters.components import IspybExperimentType
28
+ from mx_bluesky.common.parameters.rotation import (
29
+ SingleRotationScan,
30
+ )
28
31
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER, set_dcgid_tag
29
32
  from mx_bluesky.common.utils.utils import number_of_frames_from_scan_spec
30
33
  from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_mapping import (
31
34
  populate_data_collection_info_for_rotation,
32
35
  )
33
36
  from mx_bluesky.hyperion.parameters.constants import CONST
34
- from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
35
37
 
36
38
  if TYPE_CHECKING:
37
39
  from event_model.documents import Event, RunStart, RunStop
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from mx_bluesky.common.external_interaction.ispyb.data_model import DataCollectionInfo
4
- from mx_bluesky.hyperion.parameters.rotation import SingleRotationScan
4
+ from mx_bluesky.common.parameters.rotation import (
5
+ SingleRotationScan,
6
+ )
5
7
 
6
8
 
7
9
  def populate_data_collection_info_for_rotation(params: SingleRotationScan):