mx-bluesky 1.1.0__py3-none-any.whl → 1.4.0__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 (81) hide show
  1. mx_bluesky/__init__.py +8 -3
  2. mx_bluesky/__main__.py +12 -7
  3. mx_bluesky/_version.py +2 -2
  4. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +14 -4
  5. mx_bluesky/beamlines/i04/thawing_plan.py +48 -10
  6. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  7. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +68 -90
  8. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
  9. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +104 -126
  10. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +139 -162
  11. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +25 -36
  12. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +24 -34
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +14 -11
  14. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  15. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  16. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  17. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +31 -7
  18. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  19. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  20. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +1 -1
  21. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +8 -18
  22. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +2 -2
  23. mx_bluesky/common/__init__.py +0 -0
  24. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  25. mx_bluesky/common/parameters/components.py +221 -0
  26. mx_bluesky/common/parameters/constants.py +133 -0
  27. mx_bluesky/common/plans/__init__.py +1 -0
  28. mx_bluesky/common/plans/do_fgs.py +121 -0
  29. mx_bluesky/common/utils/log.py +116 -0
  30. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  31. mx_bluesky/hyperion/__main__.py +11 -9
  32. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +31 -26
  33. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  34. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  35. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -2
  36. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +48 -17
  37. mx_bluesky/hyperion/device_setup_plans/smargon.py +6 -6
  38. mx_bluesky/hyperion/device_setup_plans/utils.py +13 -2
  39. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  40. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -0
  41. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +59 -108
  42. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +7 -5
  43. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +46 -0
  44. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +19 -18
  45. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -5
  46. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +4 -4
  47. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +17 -17
  48. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +241 -0
  49. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +24 -181
  50. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +6 -4
  51. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +3 -11
  52. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -2
  53. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +18 -0
  54. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -9
  55. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +18 -13
  56. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +32 -15
  57. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  58. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +3 -5
  59. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +4 -3
  60. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +23 -18
  61. mx_bluesky/hyperion/external_interaction/config_server.py +22 -10
  62. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  63. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +0 -2
  64. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  65. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  66. mx_bluesky/hyperion/log.py +0 -84
  67. mx_bluesky/hyperion/parameters/components.py +1 -242
  68. mx_bluesky/hyperion/parameters/constants.py +22 -118
  69. mx_bluesky/hyperion/parameters/gridscan.py +20 -11
  70. mx_bluesky/hyperion/parameters/load_centre_collect.py +50 -0
  71. mx_bluesky/hyperion/parameters/robot_load.py +16 -0
  72. mx_bluesky/hyperion/parameters/rotation.py +9 -5
  73. mx_bluesky/hyperion/utils/utils.py +17 -0
  74. mx_bluesky/hyperion/utils/validation.py +5 -6
  75. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/METADATA +4 -2
  76. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/RECORD +80 -70
  77. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/WHEEL +1 -1
  78. mx_bluesky/example.py +0 -19
  79. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/LICENSE +0 -0
  80. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/entry_points.txt +0 -0
  81. {mx_bluesky-1.1.0.dist-info → mx_bluesky-1.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,241 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ from collections.abc import Generator
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import cast
8
+
9
+ import bluesky.plan_stubs as bps
10
+ import bluesky.preprocessors as bpp
11
+ from blueapi.core import BlueskyContext
12
+ from bluesky.utils import Msg
13
+ from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
14
+ from dodal.devices.attenuator import Attenuator
15
+ from dodal.devices.dcm import DCM
16
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
17
+ from dodal.devices.motors import XYZPositioner
18
+ from dodal.devices.oav.oav_detector import OAV
19
+ from dodal.devices.robot import BartRobot, SampleLocation
20
+ from dodal.devices.smargon import Smargon, StubPosition
21
+ from dodal.devices.thawer import Thawer
22
+ from dodal.devices.undulator_dcm import UndulatorDCM
23
+ from dodal.devices.webcam import Webcam
24
+ from dodal.devices.xbpm_feedback import XBPMFeedback
25
+ from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
26
+
27
+ from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
28
+ SetEnergyComposite,
29
+ set_energy_plan,
30
+ )
31
+ from mx_bluesky.hyperion.log import LOGGER
32
+ from mx_bluesky.hyperion.parameters.constants import CONST
33
+ from mx_bluesky.hyperion.parameters.robot_load import RobotLoadAndEnergyChange
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class RobotLoadAndEnergyChangeComposite:
38
+ # SetEnergyComposite fields
39
+ vfm: FocusingMirrorWithStripes
40
+ mirror_voltages: MirrorVoltages
41
+ dcm: DCM
42
+ undulator_dcm: UndulatorDCM
43
+ xbpm_feedback: XBPMFeedback
44
+ attenuator: Attenuator
45
+
46
+ # RobotLoad fields
47
+ robot: BartRobot
48
+ webcam: Webcam
49
+ lower_gonio: XYZPositioner
50
+ thawer: Thawer
51
+ oav: OAV
52
+ smargon: Smargon
53
+ aperture_scatterguard: ApertureScatterguard
54
+
55
+
56
+ def create_devices(context: BlueskyContext) -> RobotLoadAndEnergyChangeComposite:
57
+ from mx_bluesky.hyperion.utils.context import device_composite_from_context
58
+
59
+ return device_composite_from_context(context, RobotLoadAndEnergyChangeComposite)
60
+
61
+
62
+ def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
63
+ """Waits for the smargon disabled flag to go low. The robot hardware is responsible
64
+ for setting this to low when it is safe to move. It does this through a physical
65
+ connection between the robot and the smargon.
66
+ """
67
+ LOGGER.info("Waiting for smargon enabled")
68
+ SLEEP_PER_CHECK = 0.1
69
+ times_to_check = int(timeout / SLEEP_PER_CHECK)
70
+ for _ in range(times_to_check):
71
+ smargon_disabled = yield from bps.rd(smargon.disabled)
72
+ if not smargon_disabled:
73
+ LOGGER.info("Smargon now enabled")
74
+ return
75
+ yield from bps.sleep(SLEEP_PER_CHECK)
76
+ raise TimeoutError(
77
+ "Timed out waiting for smargon to become enabled after robot load"
78
+ )
79
+
80
+
81
+ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
82
+ time_now = datetime.now()
83
+ snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
84
+ for device in [oav.snapshot, webcam]:
85
+ yield from bps.abs_set(
86
+ device.filename, snapshot_format.format(device=device.name)
87
+ )
88
+ yield from bps.abs_set(device.directory, str(directory))
89
+ # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
90
+ yield from bps.trigger(device, group="snapshots")
91
+ yield from bps.wait("snapshots")
92
+
93
+
94
+ def prepare_for_robot_load(
95
+ aperture_scatterguard: ApertureScatterguard, smargon: Smargon
96
+ ):
97
+ yield from bps.abs_set(
98
+ aperture_scatterguard,
99
+ ApertureValue.ROBOT_LOAD,
100
+ group="prepare_robot_load",
101
+ )
102
+
103
+ yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
104
+
105
+ # fmt: off
106
+ yield from bps.mv(
107
+ smargon.x, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
108
+ smargon.y, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
109
+ smargon.z, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
110
+ smargon.omega, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
111
+ smargon.chi, 0, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
112
+ smargon.phi, 0 # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
113
+ )
114
+ # fmt: on
115
+
116
+ yield from bps.wait("prepare_robot_load")
117
+
118
+
119
+ def do_robot_load(
120
+ composite: RobotLoadAndEnergyChangeComposite,
121
+ sample_location: SampleLocation,
122
+ demand_energy_ev: float | None,
123
+ thawing_time: float,
124
+ ):
125
+ yield from bps.abs_set(
126
+ composite.robot,
127
+ sample_location,
128
+ group="robot_load",
129
+ )
130
+
131
+ if demand_energy_ev:
132
+ yield from set_energy_plan(
133
+ demand_energy_ev / 1000,
134
+ cast(SetEnergyComposite, composite),
135
+ )
136
+
137
+ yield from bps.wait("robot_load")
138
+
139
+ yield from bps.abs_set(
140
+ composite.thawer.thaw_for_time_s, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
141
+ thawing_time,
142
+ group="thawing_finished",
143
+ )
144
+ yield from wait_for_smargon_not_disabled(composite.smargon)
145
+
146
+
147
+ def raise_exception_if_moved_out_of_cryojet(exception):
148
+ yield from bps.null()
149
+ if isinstance(exception, MoveTooLarge):
150
+ raise Exception(
151
+ f"Moving {exception.axis} back to {exception.position} after \
152
+ robot load would move it out of the cryojet. The max safe \
153
+ distance is {exception.maximum_move}"
154
+ )
155
+
156
+
157
+ def pin_already_loaded(
158
+ robot: BartRobot, sample_location: SampleLocation
159
+ ) -> Generator[Msg, None, bool]:
160
+ current_puck = yield from bps.rd(robot.current_puck)
161
+ current_pin = yield from bps.rd(robot.current_pin)
162
+ return (
163
+ int(current_puck) == sample_location.puck
164
+ and int(current_pin) == sample_location.pin
165
+ )
166
+
167
+
168
+ def robot_load_and_snapshots(
169
+ composite: RobotLoadAndEnergyChangeComposite,
170
+ location: SampleLocation,
171
+ snapshot_directory: Path,
172
+ thawing_time: float,
173
+ demand_energy_ev: float | None,
174
+ ):
175
+ robot_load_plan = do_robot_load(
176
+ composite,
177
+ location,
178
+ demand_energy_ev,
179
+ thawing_time,
180
+ )
181
+
182
+ # The lower gonio must be in the correct position for the robot load and we
183
+ # want to put it back afterwards. Note we don't wait the robot is interlocked
184
+ # to the lower gonio and the move is quicker than the robot takes to get to the
185
+ # load position.
186
+ yield from bpp.contingency_wrapper(
187
+ home_and_reset_wrapper(
188
+ robot_load_plan,
189
+ composite.lower_gonio,
190
+ BartRobot.LOAD_TOLERANCE_MM,
191
+ CONST.HARDWARE.CRYOJET_MARGIN_MM,
192
+ "lower_gonio",
193
+ wait_for_all=False,
194
+ ),
195
+ except_plan=raise_exception_if_moved_out_of_cryojet,
196
+ )
197
+
198
+ yield from take_robot_snapshots(composite.oav, composite.webcam, snapshot_directory)
199
+
200
+ yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
201
+ yield from bps.read(composite.robot.barcode)
202
+ yield from bps.read(composite.oav.snapshot) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
203
+ yield from bps.read(composite.webcam)
204
+ yield from bps.save()
205
+
206
+ yield from bps.wait("reset-lower_gonio")
207
+
208
+
209
+ def robot_load_and_change_energy_plan(
210
+ composite: RobotLoadAndEnergyChangeComposite,
211
+ params: RobotLoadAndEnergyChange,
212
+ ):
213
+ assert params.sample_puck is not None
214
+ assert params.sample_pin is not None
215
+
216
+ sample_location = SampleLocation(params.sample_puck, params.sample_pin)
217
+
218
+ yield from prepare_for_robot_load(
219
+ composite.aperture_scatterguard, composite.smargon
220
+ )
221
+ yield from bpp.run_wrapper(
222
+ robot_load_and_snapshots(
223
+ composite,
224
+ sample_location,
225
+ params.snapshot_directory,
226
+ params.thawing_time,
227
+ params.demand_energy_ev,
228
+ ),
229
+ md={
230
+ "subplan_name": CONST.PLAN.ROBOT_LOAD,
231
+ "metadata": {
232
+ "visit": params.visit,
233
+ "sample_id": params.sample_id,
234
+ "sample_puck": sample_location.puck,
235
+ "sample_pin": sample_location.pin,
236
+ },
237
+ "activate_callbacks": [
238
+ "RobotLoadISPyBCallback",
239
+ ],
240
+ },
241
+ )
@@ -1,16 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- from collections.abc import Generator
5
- from datetime import datetime
6
- from pathlib import Path
7
4
  from typing import cast
8
5
 
9
- import bluesky.plan_stubs as bps
10
- import bluesky.preprocessors as bpp
11
6
  from blueapi.core import BlueskyContext, MsgGenerator
12
- from bluesky.utils import Msg
13
- from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
7
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
14
8
  from dodal.devices.attenuator import Attenuator
15
9
  from dodal.devices.backlight import Backlight
16
10
  from dodal.devices.dcm import DCM
@@ -18,13 +12,13 @@ from dodal.devices.detector.detector_motion import DetectorMotion
18
12
  from dodal.devices.eiger import EigerDetector
19
13
  from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan
20
14
  from dodal.devices.flux import Flux
21
- from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
15
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
22
16
  from dodal.devices.motors import XYZPositioner
23
17
  from dodal.devices.oav.oav_detector import OAV
24
18
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
25
19
  from dodal.devices.robot import BartRobot, SampleLocation
26
20
  from dodal.devices.s4_slit_gaps import S4SlitGaps
27
- from dodal.devices.smargon import Smargon, StubPosition
21
+ from dodal.devices.smargon import Smargon
28
22
  from dodal.devices.synchrotron import Synchrotron
29
23
  from dodal.devices.thawer import Thawer
30
24
  from dodal.devices.undulator import Undulator
@@ -34,10 +28,12 @@ from dodal.devices.xbpm_feedback import XBPMFeedback
34
28
  from dodal.devices.zebra import Zebra
35
29
  from dodal.devices.zebra_controlled_shutter import ZebraShutter
36
30
  from dodal.devices.zocalo import ZocaloResults
37
- from dodal.plans.motor_util_plans import MoveTooLarge, home_and_reset_wrapper
31
+ from dodal.log import LOGGER
38
32
  from ophyd_async.fastcs.panda import HDFPanda
39
33
 
34
+ from mx_bluesky.common.parameters.constants import OavConstants
40
35
  from mx_bluesky.hyperion.device_setup_plans.utils import (
36
+ fill_in_energy_if_not_supplied,
41
37
  start_preparing_data_collection_then_do_plan,
42
38
  )
43
39
  from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan import (
@@ -46,12 +42,11 @@ from mx_bluesky.hyperion.experiment_plans.grid_detect_then_xray_centre_plan impo
46
42
  from mx_bluesky.hyperion.experiment_plans.pin_centre_then_xray_centre_plan import (
47
43
  pin_centre_then_xray_centre_plan,
48
44
  )
49
- from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
50
- SetEnergyComposite,
51
- read_energy,
52
- set_energy_plan,
45
+ from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
46
+ RobotLoadAndEnergyChangeComposite,
47
+ pin_already_loaded,
48
+ robot_load_and_change_energy_plan,
53
49
  )
54
- from mx_bluesky.hyperion.log import LOGGER
55
50
  from mx_bluesky.hyperion.parameters.constants import CONST
56
51
  from mx_bluesky.hyperion.parameters.gridscan import RobotLoadThenCentre
57
52
 
@@ -84,7 +79,7 @@ class RobotLoadThenCentreComposite:
84
79
 
85
80
  # SetEnergyComposite fields
86
81
  vfm: FocusingMirrorWithStripes
87
- vfm_mirror_voltages: VFMMirrorVoltages
82
+ mirror_voltages: MirrorVoltages
88
83
  dcm: DCM
89
84
  undulator_dcm: UndulatorDCM
90
85
 
@@ -100,147 +95,10 @@ def create_devices(context: BlueskyContext) -> RobotLoadThenCentreComposite:
100
95
  return device_composite_from_context(context, RobotLoadThenCentreComposite)
101
96
 
102
97
 
103
- def wait_for_smargon_not_disabled(smargon: Smargon, timeout=60):
104
- """Waits for the smargon disabled flag to go low. The robot hardware is responsible
105
- for setting this to low when it is safe to move. It does this through a physical
106
- connection between the robot and the smargon.
107
- """
108
- LOGGER.info("Waiting for smargon enabled")
109
- SLEEP_PER_CHECK = 0.1
110
- times_to_check = int(timeout / SLEEP_PER_CHECK)
111
- for _ in range(times_to_check):
112
- smargon_disabled = yield from bps.rd(smargon.disabled)
113
- if not smargon_disabled:
114
- LOGGER.info("Smargon now enabled")
115
- return
116
- yield from bps.sleep(SLEEP_PER_CHECK)
117
- raise TimeoutError(
118
- "Timed out waiting for smargon to become enabled after robot load"
119
- )
120
-
121
-
122
- def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
123
- time_now = datetime.now()
124
- snapshot_format = f"{time_now.strftime('%H%M%S')}_{{device}}_after_load"
125
- for device in [oav.snapshot, webcam]:
126
- yield from bps.abs_set(
127
- device.filename, snapshot_format.format(device=device.name)
128
- )
129
- yield from bps.abs_set(device.directory, str(directory))
130
- # Note: should be able to use `wait=True` after https://github.com/bluesky/bluesky/issues/1795
131
- yield from bps.trigger(device, group="snapshots")
132
- yield from bps.wait("snapshots")
133
-
134
-
135
- def prepare_for_robot_load(composite: RobotLoadThenCentreComposite):
136
- yield from bps.abs_set(
137
- composite.aperture_scatterguard,
138
- ApertureValue.ROBOT_LOAD,
139
- group="prepare_robot_load",
140
- )
141
-
142
- yield from bps.mv(composite.smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD)
143
-
144
- # fmt: off
145
- yield from bps.mv(composite.smargon.x, 0,
146
- composite.smargon.y, 0,
147
- composite.smargon.z, 0,
148
- composite.smargon.omega, 0,
149
- composite.smargon.chi, 0,
150
- composite.smargon.phi, 0)
151
- # fmt: on
152
-
153
- yield from bps.wait("prepare_robot_load")
154
-
155
-
156
- def do_robot_load(
157
- composite: RobotLoadThenCentreComposite,
158
- sample_location: SampleLocation,
159
- demand_energy_ev: float | None,
160
- thawing_time: float,
161
- ):
162
- yield from bps.abs_set(
163
- composite.robot,
164
- sample_location,
165
- group="robot_load",
166
- )
167
-
168
- if demand_energy_ev:
169
- yield from set_energy_plan(
170
- demand_energy_ev / 1000,
171
- cast(SetEnergyComposite, composite),
172
- )
173
-
174
- yield from bps.wait("robot_load")
175
-
176
- yield from bps.abs_set(
177
- composite.thawer.thaw_for_time_s, thawing_time, group="thawing_finished"
178
- )
179
- yield from wait_for_smargon_not_disabled(composite.smargon)
180
-
181
-
182
- def raise_exception_if_moved_out_of_cryojet(exception):
183
- yield from bps.null()
184
- if isinstance(exception, MoveTooLarge):
185
- raise Exception(
186
- f"Moving {exception.axis} back to {exception.position} after \
187
- robot load would move it out of the cryojet. The max safe \
188
- distance is {exception.maximum_move}"
189
- )
190
-
191
-
192
- def _pin_already_loaded(
193
- robot: BartRobot, pin_to_load: int, puck_to_load: int
194
- ) -> Generator[Msg, None, bool]:
195
- current_puck = yield from bps.rd(robot.current_puck)
196
- current_pin = yield from bps.rd(robot.current_pin)
197
- return int(current_puck) == puck_to_load and int(current_pin) == pin_to_load
198
-
199
-
200
- def robot_load_and_snapshots(
201
- composite: RobotLoadThenCentreComposite,
202
- params: RobotLoadThenCentre,
203
- location: SampleLocation,
204
- ):
205
- robot_load_plan = do_robot_load(
206
- composite,
207
- location,
208
- params.demand_energy_ev,
209
- params.thawing_time,
210
- )
211
-
212
- # The lower gonio must be in the correct position for the robot load and we
213
- # want to put it back afterwards. Note we don't wait the robot is interlocked
214
- # to the lower gonio and the move is quicker than the robot takes to get to the
215
- # load position.
216
- yield from bpp.contingency_wrapper(
217
- home_and_reset_wrapper(
218
- robot_load_plan,
219
- composite.lower_gonio,
220
- BartRobot.LOAD_TOLERANCE_MM,
221
- CONST.HARDWARE.CRYOJET_MARGIN_MM,
222
- "lower_gonio",
223
- wait_for_all=False,
224
- ),
225
- except_plan=raise_exception_if_moved_out_of_cryojet,
226
- )
227
-
228
- yield from take_robot_snapshots(
229
- composite.oav, composite.webcam, params.snapshot_directory
230
- )
231
-
232
- yield from bps.create(name=CONST.DESCRIPTORS.ROBOT_LOAD)
233
- yield from bps.read(composite.robot.barcode)
234
- yield from bps.read(composite.oav.snapshot)
235
- yield from bps.read(composite.webcam)
236
- yield from bps.save()
237
-
238
- yield from bps.wait("reset-lower_gonio")
239
-
240
-
241
98
  def centring_plan_from_robot_load_params(
242
99
  composite: RobotLoadThenCentreComposite,
243
100
  params: RobotLoadThenCentre,
101
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
244
102
  ):
245
103
  yield from pin_centre_then_xray_centre_plan(
246
104
  cast(GridDetectThenXRayCentreComposite, composite),
@@ -251,26 +109,14 @@ def centring_plan_from_robot_load_params(
251
109
  def robot_load_then_centre_plan(
252
110
  composite: RobotLoadThenCentreComposite,
253
111
  params: RobotLoadThenCentre,
254
- sample_location: SampleLocation,
112
+ oav_config_file: str = OavConstants.OAV_CONFIG_JSON,
255
113
  ):
256
- yield from prepare_for_robot_load(composite)
257
- yield from bpp.run_wrapper(
258
- robot_load_and_snapshots(composite, params, sample_location),
259
- md={
260
- "subplan_name": CONST.PLAN.ROBOT_LOAD,
261
- "metadata": {
262
- "visit_path": str(params.visit_directory),
263
- "sample_id": params.sample_id,
264
- "sample_puck": params.sample_puck,
265
- "sample_pin": params.sample_pin,
266
- },
267
- "activate_callbacks": [
268
- "RobotLoadISPyBCallback",
269
- ],
270
- },
114
+ yield from robot_load_and_change_energy_plan(
115
+ cast(RobotLoadAndEnergyChangeComposite, composite),
116
+ params.robot_load_params(),
271
117
  )
272
118
 
273
- yield from centring_plan_from_robot_load_params(composite, params)
119
+ yield from centring_plan_from_robot_load_params(composite, params, oav_config_file)
274
120
 
275
121
 
276
122
  def robot_load_then_centre(
@@ -283,10 +129,10 @@ def robot_load_then_centre(
283
129
  assert parameters.sample_puck is not None
284
130
  assert parameters.sample_pin is not None
285
131
 
132
+ sample_location = SampleLocation(parameters.sample_puck, parameters.sample_pin)
133
+
286
134
  doing_sample_load = not (
287
- yield from _pin_already_loaded(
288
- composite.robot, parameters.sample_pin, parameters.sample_puck
289
- )
135
+ yield from pin_already_loaded(composite.robot, sample_location)
290
136
  )
291
137
 
292
138
  doing_chi_change = parameters.chi_start_deg is not None
@@ -295,7 +141,6 @@ def robot_load_then_centre(
295
141
  plan = robot_load_then_centre_plan(
296
142
  composite,
297
143
  parameters,
298
- SampleLocation(parameters.sample_puck, parameters.sample_pin),
299
144
  )
300
145
  LOGGER.info("Pin not loaded, loading and centring")
301
146
  elif doing_chi_change:
@@ -305,12 +150,10 @@ def robot_load_then_centre(
305
150
  LOGGER.info("Pin already loaded and chi not changed so doing nothing")
306
151
  return
307
152
 
308
- detector_params = parameters.detector_params
309
- if not detector_params.expected_energy_ev:
310
- actual_energy_ev = 1000 * (
311
- yield from read_energy(cast(SetEnergyComposite, composite))
312
- )
313
- detector_params.expected_energy_ev = actual_energy_ev
153
+ detector_params = yield from fill_in_energy_if_not_supplied(
154
+ composite.dcm, parameters.detector_params
155
+ )
156
+
314
157
  eiger.set_detector_parameters(detector_params)
315
158
 
316
159
  yield from start_preparing_data_collection_then_do_plan(
@@ -24,6 +24,9 @@ from dodal.devices.zebra import RotationDirection, Zebra
24
24
  from dodal.devices.zebra_controlled_shutter import ZebraShutter
25
25
  from dodal.plans.check_topup import check_topup_and_wait_if_necessary
26
26
 
27
+ from mx_bluesky.common.device_setup_plans.read_hardware_for_setup import (
28
+ read_hardware_for_zocalo,
29
+ )
27
30
  from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
28
31
  cleanup_sample_environment,
29
32
  move_phi_chi_omega,
@@ -32,7 +35,6 @@ from mx_bluesky.hyperion.device_setup_plans.manipulate_sample import (
32
35
  )
33
36
  from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
34
37
  read_hardware_during_collection,
35
- read_hardware_for_zocalo,
36
38
  read_hardware_pre_collection,
37
39
  )
38
40
  from mx_bluesky.hyperion.device_setup_plans.setup_zebra import (
@@ -252,7 +254,7 @@ def rotation_scan_plan(
252
254
  composite.undulator,
253
255
  composite.synchrotron,
254
256
  composite.s4_slit_gaps,
255
- composite.robot,
257
+ composite.dcm,
256
258
  composite.smargon,
257
259
  )
258
260
 
@@ -348,7 +350,7 @@ def rotation_scan(
348
350
  md={
349
351
  "subplan_name": CONST.PLAN.ROTATION_OUTER,
350
352
  CONST.TRIGGER.ZOCALO: CONST.PLAN.ROTATION_MAIN,
351
- "zocalo_environment": parameters.zocalo_environment,
353
+ "zocalo_environment": CONST.ZOCALO_ENV,
352
354
  "hyperion_parameters": parameters.model_dump_json(),
353
355
  "activate_callbacks": [
354
356
  "RotationISPyBCallback",
@@ -379,7 +381,7 @@ def rotation_scan(
379
381
  rotation_with_cleanup_and_stage(params),
380
382
  group=CONST.WAIT.ROTATION_READY_FOR_DC,
381
383
  )
382
- yield from bps.unstage(eiger)
384
+ yield from bps.unstage(eiger) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
383
385
 
384
386
  yield from rotation_scan_plan_with_stage_and_cleanup(parameters)
385
387
 
@@ -6,14 +6,11 @@
6
6
  """
7
7
 
8
8
  import dataclasses
9
- from collections.abc import Generator
10
- from typing import Any
11
9
 
12
10
  from bluesky import plan_stubs as bps
13
- from bluesky.utils import Msg
14
11
  from dodal.devices.attenuator import Attenuator
15
12
  from dodal.devices.dcm import DCM
16
- from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, VFMMirrorVoltages
13
+ from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
17
14
  from dodal.devices.undulator_dcm import UndulatorDCM
18
15
  from dodal.devices.xbpm_feedback import XBPMFeedback
19
16
 
@@ -30,7 +27,7 @@ UNDULATOR_GROUP = "UNDULATOR_GROUP"
30
27
  @dataclasses.dataclass
31
28
  class SetEnergyComposite:
32
29
  vfm: FocusingMirrorWithStripes
33
- vfm_mirror_voltages: VFMMirrorVoltages
30
+ mirror_voltages: MirrorVoltages
34
31
  dcm: DCM
35
32
  undulator_dcm: UndulatorDCM
36
33
  xbpm_feedback: XBPMFeedback
@@ -45,17 +42,12 @@ def _set_energy_plan(
45
42
  yield from dcm_pitch_roll_mirror_adjuster.adjust_dcm_pitch_roll_vfm_from_lut(
46
43
  composite.undulator_dcm,
47
44
  composite.vfm,
48
- composite.vfm_mirror_voltages,
45
+ composite.mirror_voltages,
49
46
  energy_kev,
50
47
  )
51
48
  yield from bps.wait(group=UNDULATOR_GROUP)
52
49
 
53
50
 
54
- def read_energy(composite: SetEnergyComposite) -> Generator[Msg, Any, float]:
55
- """Obtain the energy in kev"""
56
- return (yield from bps.rd(composite.dcm.energy_in_kev)) # type: ignore
57
-
58
-
59
51
  def set_energy_plan(
60
52
  energy_kev,
61
53
  composite: SetEnergyComposite,
@@ -7,6 +7,7 @@ from bluesky.callbacks.zmq import Proxy, RemoteDispatcher
7
7
  from dodal.log import LOGGER as dodal_logger
8
8
  from dodal.log import set_up_all_logging_handlers
9
9
 
10
+ from mx_bluesky.common.utils.log import _get_logging_dir, tag_filter
10
11
  from mx_bluesky.hyperion.external_interaction.callbacks.log_uid_tag_callback import (
11
12
  LogUidTaggingCallback,
12
13
  )
@@ -31,8 +32,6 @@ from mx_bluesky.hyperion.external_interaction.callbacks.zocalo_callback import (
31
32
  from mx_bluesky.hyperion.log import (
32
33
  ISPYB_LOGGER,
33
34
  NEXUS_LOGGER,
34
- _get_logging_dir,
35
- tag_filter,
36
35
  )
37
36
  from mx_bluesky.hyperion.parameters.cli import parse_callback_dev_mode_arg
38
37
  from mx_bluesky.hyperion.parameters.constants import CONST
@@ -44,3 +44,21 @@ def create_rotation_callbacks() -> (
44
44
  tuple[RotationNexusFileCallback, RotationISPyBCallback]
45
45
  ):
46
46
  return (RotationNexusFileCallback(), RotationISPyBCallback(emit=ZocaloCallback()))
47
+
48
+
49
+ def create_load_centre_collect_callbacks() -> (
50
+ tuple[
51
+ GridscanNexusFileCallback,
52
+ GridscanISPyBCallback,
53
+ RobotLoadISPyBCallback,
54
+ RotationNexusFileCallback,
55
+ RotationISPyBCallback,
56
+ ]
57
+ ):
58
+ return (
59
+ GridscanNexusFileCallback(),
60
+ GridscanISPyBCallback(emit=ZocaloCallback()),
61
+ RobotLoadISPyBCallback(),
62
+ RotationNexusFileCallback(),
63
+ RotationISPyBCallback(emit=ZocaloCallback()),
64
+ )
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
-
3
+ from mx_bluesky.common.parameters.components import DiffractionExperimentWithSample
5
4
  from mx_bluesky.hyperion.external_interaction.ispyb.data_model import (
6
5
  DataCollectionGroupInfo,
7
6
  DataCollectionInfo,
@@ -11,10 +10,8 @@ from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_store import (
11
10
  I03_EIGER_DETECTOR,
12
11
  )
13
12
  from mx_bluesky.hyperion.external_interaction.ispyb.ispyb_utils import (
14
- VISIT_PATH_REGEX,
15
13
  get_current_time_string,
16
14
  )
17
- from mx_bluesky.hyperion.parameters.components import DiffractionExperimentWithSample
18
15
 
19
16
 
20
17
  def populate_data_collection_group(params: DiffractionExperimentWithSample):
@@ -63,8 +60,3 @@ def get_proposal_and_session_from_visit_string(visit_string: str) -> tuple[str,
63
60
  visit_parts = visit_string.split("-")
64
61
  assert len(visit_parts) == 2, f"Unexpected visit string {visit_string}"
65
62
  return visit_parts[0], int(visit_parts[1])
66
-
67
-
68
- def get_visit_string_from_path(path: str | None) -> str | None:
69
- match = re.search(VISIT_PATH_REGEX, path) if path else None
70
- return str(match.group(1)) if match else None