mx-bluesky 1.4.1a0__py3-none-any.whl → 1.4.2__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.
- mx_bluesky/_version.py +2 -2
- mx_bluesky/beamlines/i04/redis_to_murko_forwarder.py +178 -0
- mx_bluesky/beamlines/i24/serial/__init__.py +0 -6
- mx_bluesky/beamlines/i24/serial/dcid.py +125 -151
- mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +66 -36
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +2 -46
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +74 -120
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +58 -66
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +1 -19
- mx_bluesky/beamlines/i24/serial/parameters/__init__.py +9 -1
- mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -0
- mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +75 -16
- mx_bluesky/beamlines/i24/serial/parameters/utils.py +19 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
- mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +32 -8
- mx_bluesky/beamlines/i24/serial/write_nexus.py +66 -67
- mx_bluesky/common/parameters/components.py +3 -3
- mx_bluesky/common/parameters/constants.py +5 -0
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +21 -31
- mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +6 -6
- mx_bluesky/hyperion/device_setup_plans/smargon.py +3 -3
- mx_bluesky/hyperion/exceptions.py +13 -1
- mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +16 -10
- mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +0 -8
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +58 -34
- mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +8 -2
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +3 -3
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +30 -26
- mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +26 -7
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +0 -7
- mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +8 -7
- mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +4 -0
- mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
- mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +18 -10
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
- mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +10 -7
- mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
- mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
- mx_bluesky/hyperion/utils/validation.py +1 -1
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.2.dist-info}/METADATA +2 -2
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.2.dist-info}/RECORD +49 -46
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.2.dist-info}/LICENSE +0 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.2.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.2.dist-info}/entry_points.txt +0 -0
- {mx_bluesky-1.4.1a0.dist-info → mx_bluesky-1.4.2.dist-info}/top_level.txt +0 -0
|
@@ -10,7 +10,7 @@ import bluesky.preprocessors as bpp
|
|
|
10
10
|
import pydantic
|
|
11
11
|
from blueapi.core import BlueskyContext
|
|
12
12
|
from bluesky.utils import Msg
|
|
13
|
-
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
13
|
+
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
|
|
14
14
|
from dodal.devices.attenuator import Attenuator
|
|
15
15
|
from dodal.devices.dcm import DCM
|
|
16
16
|
from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages
|
|
@@ -94,7 +94,11 @@ def take_robot_snapshots(oav: OAV, webcam: Webcam, directory: Path):
|
|
|
94
94
|
def prepare_for_robot_load(
|
|
95
95
|
aperture_scatterguard: ApertureScatterguard, smargon: Smargon
|
|
96
96
|
):
|
|
97
|
-
yield from bps.
|
|
97
|
+
yield from bps.abs_set(
|
|
98
|
+
aperture_scatterguard,
|
|
99
|
+
ApertureValue.ROBOT_LOAD,
|
|
100
|
+
group="prepare_robot_load",
|
|
101
|
+
)
|
|
98
102
|
|
|
99
103
|
yield from bps.mv(smargon.stub_offsets, StubPosition.RESET_TO_ROBOT_LOAD) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
|
|
100
104
|
|
|
@@ -124,11 +128,7 @@ def do_robot_load(
|
|
|
124
128
|
group="robot_load",
|
|
125
129
|
)
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
yield from set_energy_plan(
|
|
129
|
-
demand_energy_ev / 1000,
|
|
130
|
-
cast(SetEnergyComposite, composite),
|
|
131
|
-
)
|
|
131
|
+
yield from set_energy_plan(demand_energy_ev, cast(SetEnergyComposite, composite))
|
|
132
132
|
|
|
133
133
|
yield from bps.wait("robot_load")
|
|
134
134
|
|
|
@@ -214,24 +214,28 @@ def robot_load_and_change_energy_plan(
|
|
|
214
214
|
yield from prepare_for_robot_load(
|
|
215
215
|
composite.aperture_scatterguard, composite.smargon
|
|
216
216
|
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
|
|
231
|
-
|
|
217
|
+
|
|
218
|
+
yield from bpp.set_run_key_wrapper(
|
|
219
|
+
bpp.run_wrapper(
|
|
220
|
+
robot_load_and_snapshots(
|
|
221
|
+
composite,
|
|
222
|
+
sample_location,
|
|
223
|
+
params.snapshot_directory,
|
|
224
|
+
params.thawing_time,
|
|
225
|
+
params.demand_energy_ev,
|
|
226
|
+
),
|
|
227
|
+
md={
|
|
228
|
+
"subplan_name": CONST.PLAN.ROBOT_LOAD,
|
|
229
|
+
"metadata": {
|
|
230
|
+
"visit": params.visit,
|
|
231
|
+
"sample_id": params.sample_id,
|
|
232
|
+
"sample_puck": sample_location.puck,
|
|
233
|
+
"sample_pin": sample_location.pin,
|
|
234
|
+
},
|
|
235
|
+
"activate_callbacks": [
|
|
236
|
+
"RobotLoadISPyBCallback",
|
|
237
|
+
],
|
|
232
238
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
],
|
|
236
|
-
},
|
|
239
|
+
),
|
|
240
|
+
CONST.PLAN.ROBOT_LOAD_AND_SNAPSHOTS,
|
|
237
241
|
)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from math import isclose
|
|
3
4
|
from typing import cast
|
|
4
5
|
|
|
5
6
|
import bluesky.preprocessors as bpp
|
|
6
7
|
import pydantic
|
|
7
8
|
from blueapi.core import BlueskyContext
|
|
9
|
+
from bluesky import plan_stubs as bps
|
|
8
10
|
from bluesky.utils import MsgGenerator
|
|
9
11
|
from dodal.devices.aperturescatterguard import ApertureScatterguard
|
|
10
12
|
from dodal.devices.attenuator import Attenuator
|
|
@@ -56,6 +58,10 @@ from mx_bluesky.hyperion.experiment_plans.robot_load_and_change_energy import (
|
|
|
56
58
|
pin_already_loaded,
|
|
57
59
|
robot_load_and_change_energy_plan,
|
|
58
60
|
)
|
|
61
|
+
from mx_bluesky.hyperion.experiment_plans.set_energy_plan import (
|
|
62
|
+
SetEnergyComposite,
|
|
63
|
+
set_energy_plan,
|
|
64
|
+
)
|
|
59
65
|
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
60
66
|
|
|
61
67
|
|
|
@@ -170,20 +176,33 @@ def robot_load_then_xray_centre(
|
|
|
170
176
|
yield from pin_already_loaded(composite.robot, sample_location)
|
|
171
177
|
)
|
|
172
178
|
|
|
173
|
-
|
|
179
|
+
current_chi = yield from bps.rd(composite.smargon.chi)
|
|
180
|
+
LOGGER.info(f"Read back current smargon chi of {current_chi} degrees.")
|
|
181
|
+
doing_chi_change = parameters.chi_start_deg is not None and not isclose(
|
|
182
|
+
current_chi, parameters.chi_start_deg, abs_tol=0.001
|
|
183
|
+
)
|
|
174
184
|
|
|
175
185
|
if doing_sample_load:
|
|
186
|
+
LOGGER.info("Pin not loaded, loading and centring")
|
|
176
187
|
plan = _robot_load_then_flyscan_plan(
|
|
177
188
|
composite,
|
|
178
189
|
parameters,
|
|
179
190
|
)
|
|
180
|
-
LOGGER.info("Pin not loaded, loading and centring")
|
|
181
|
-
elif doing_chi_change:
|
|
182
|
-
plan = _flyscan_plan_from_robot_load_params(composite, parameters)
|
|
183
|
-
LOGGER.info("Pin already loaded but chi changed so centring")
|
|
184
191
|
else:
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
# Robot load normally sets the energy so we should do this explicitly if no load is
|
|
193
|
+
# being done
|
|
194
|
+
demand_energy_ev = parameters.demand_energy_ev
|
|
195
|
+
LOGGER.info(f"Setting the energy to {demand_energy_ev}eV")
|
|
196
|
+
yield from set_energy_plan(
|
|
197
|
+
demand_energy_ev, cast(SetEnergyComposite, composite)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if doing_chi_change:
|
|
201
|
+
plan = _flyscan_plan_from_robot_load_params(composite, parameters)
|
|
202
|
+
LOGGER.info("Pin already loaded but chi changed so centring")
|
|
203
|
+
else:
|
|
204
|
+
LOGGER.info("Pin already loaded and chi not changed so doing nothing")
|
|
205
|
+
return
|
|
187
206
|
|
|
188
207
|
detector_params = yield from fill_in_energy_if_not_supplied(
|
|
189
208
|
composite.dcm, parameters.detector_params
|
|
@@ -331,13 +331,6 @@ def _move_and_rotation(
|
|
|
331
331
|
yield from setup_beamline_for_OAV(
|
|
332
332
|
composite.smargon, composite.backlight, composite.aperture_scatterguard
|
|
333
333
|
)
|
|
334
|
-
yield from bps.wait(group=CONST.WAIT.READY_FOR_OAV)
|
|
335
|
-
if params.selected_aperture:
|
|
336
|
-
yield from bps.abs_set(
|
|
337
|
-
composite.aperture_scatterguard.aperture_outside_beam,
|
|
338
|
-
params.selected_aperture,
|
|
339
|
-
group=CONST.WAIT.ROTATION_READY_FOR_DC,
|
|
340
|
-
)
|
|
341
334
|
yield from oav_snapshot_plan(composite, params, oav_params)
|
|
342
335
|
yield from rotation_scan_plan(
|
|
343
336
|
composite,
|
|
@@ -48,12 +48,13 @@ def _set_energy_plan(
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def set_energy_plan(
|
|
51
|
-
|
|
51
|
+
energy_ev: float | None,
|
|
52
52
|
composite: SetEnergyComposite,
|
|
53
53
|
):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
if energy_ev:
|
|
55
|
+
yield from transmission_and_xbpm_feedback_for_collection_wrapper(
|
|
56
|
+
_set_energy_plan(energy_ev / 1000, composite),
|
|
57
|
+
composite.xbpm_feedback,
|
|
58
|
+
composite.attenuator,
|
|
59
|
+
DESIRED_TRANSMISSION_FRACTION,
|
|
60
|
+
)
|
|
@@ -20,6 +20,9 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback
|
|
|
20
20
|
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
|
|
21
21
|
RotationNexusFileCallback,
|
|
22
22
|
)
|
|
23
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
|
|
24
|
+
SampleHandlingCallback,
|
|
25
|
+
)
|
|
23
26
|
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
24
27
|
GridscanISPyBCallback,
|
|
25
28
|
)
|
|
@@ -49,6 +52,7 @@ def setup_callbacks():
|
|
|
49
52
|
RotationISPyBCallback(emit=zocalo),
|
|
50
53
|
LogUidTaggingCallback(),
|
|
51
54
|
RobotLoadISPyBCallback(),
|
|
55
|
+
SampleHandlingCallback(),
|
|
52
56
|
]
|
|
53
57
|
|
|
54
58
|
|
|
@@ -11,6 +11,9 @@ from mx_bluesky.hyperion.external_interaction.callbacks.rotation.ispyb_callback
|
|
|
11
11
|
from mx_bluesky.hyperion.external_interaction.callbacks.rotation.nexus_callback import (
|
|
12
12
|
RotationNexusFileCallback,
|
|
13
13
|
)
|
|
14
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
|
|
15
|
+
SampleHandlingCallback,
|
|
16
|
+
)
|
|
14
17
|
from mx_bluesky.hyperion.external_interaction.callbacks.xray_centre.ispyb_callback import (
|
|
15
18
|
GridscanISPyBCallback,
|
|
16
19
|
)
|
|
@@ -53,6 +56,7 @@ def create_load_centre_collect_callbacks() -> (
|
|
|
53
56
|
RobotLoadISPyBCallback,
|
|
54
57
|
RotationNexusFileCallback,
|
|
55
58
|
RotationISPyBCallback,
|
|
59
|
+
SampleHandlingCallback,
|
|
56
60
|
]
|
|
57
61
|
):
|
|
58
62
|
return (
|
|
@@ -61,4 +65,5 @@ def create_load_centre_collect_callbacks() -> (
|
|
|
61
65
|
RobotLoadISPyBCallback(),
|
|
62
66
|
RotationNexusFileCallback(),
|
|
63
67
|
RotationISPyBCallback(emit=ZocaloCallback()),
|
|
68
|
+
SampleHandlingCallback(),
|
|
64
69
|
)
|
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from event_model.documents import EventDescriptor
|
|
6
|
-
|
|
7
5
|
from mx_bluesky.hyperion.external_interaction.callbacks.common.ispyb_mapping import (
|
|
8
6
|
get_proposal_and_session_from_visit_string,
|
|
9
7
|
)
|
|
@@ -11,6 +9,7 @@ from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback i
|
|
|
11
9
|
PlanReactiveCallback,
|
|
12
10
|
)
|
|
13
11
|
from mx_bluesky.hyperion.external_interaction.ispyb.exp_eye_store import (
|
|
12
|
+
BLSampleStatus,
|
|
14
13
|
ExpeyeInteraction,
|
|
15
14
|
RobotActionID,
|
|
16
15
|
)
|
|
@@ -25,6 +24,7 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
25
24
|
def __init__(self) -> None:
|
|
26
25
|
ISPYB_LOGGER.debug("Initialising ISPyB Robot Load Callback")
|
|
27
26
|
super().__init__(log=ISPYB_LOGGER)
|
|
27
|
+
self._metadata: dict | None = None
|
|
28
28
|
self.run_uid: str | None = None
|
|
29
29
|
self.descriptors: dict[str, EventDescriptor] = {}
|
|
30
30
|
self.action_id: RobotActionID | None = None
|
|
@@ -35,16 +35,17 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
35
35
|
if doc.get("subplan_name") == CONST.PLAN.ROBOT_LOAD:
|
|
36
36
|
ISPYB_LOGGER.debug(f"ISPyB robot load callback received: {doc}")
|
|
37
37
|
self.run_uid = doc.get("uid")
|
|
38
|
-
|
|
38
|
+
self._metadata = doc.get("metadata")
|
|
39
|
+
assert isinstance(self._metadata, dict)
|
|
39
40
|
proposal, session = get_proposal_and_session_from_visit_string(
|
|
40
|
-
|
|
41
|
+
self._metadata["visit"]
|
|
41
42
|
)
|
|
42
43
|
self.action_id = self.expeye.start_load(
|
|
43
44
|
proposal,
|
|
44
45
|
session,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
self._metadata["sample_id"],
|
|
47
|
+
self._metadata["sample_puck"],
|
|
48
|
+
self._metadata["sample_pin"],
|
|
48
49
|
)
|
|
49
50
|
return super().activity_gated_start(doc)
|
|
50
51
|
|
|
@@ -77,10 +78,17 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
|
|
|
77
78
|
assert (
|
|
78
79
|
self.action_id is not None
|
|
79
80
|
), "ISPyB Robot load callback stop called unexpectedly"
|
|
80
|
-
exit_status = (
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
exit_status = doc.get("exit_status")
|
|
82
|
+
assert exit_status, "Exit status not available in stop document!"
|
|
83
|
+
assert self._metadata, "Metadata not received before stop document."
|
|
83
84
|
reason = doc.get("reason") or "OK"
|
|
85
|
+
|
|
84
86
|
self.expeye.end_load(self.action_id, exit_status, reason)
|
|
87
|
+
self.expeye.update_sample_status(
|
|
88
|
+
self._metadata["sample_id"],
|
|
89
|
+
BLSampleStatus.LOADED
|
|
90
|
+
if exit_status == "success"
|
|
91
|
+
else BLSampleStatus.ERROR_BEAMLINE,
|
|
92
|
+
)
|
|
85
93
|
self.action_id = None
|
|
86
94
|
return super().activity_gated_stop(doc)
|
|
File without changes
|
mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
from functools import partial
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import bluesky.plan_stubs as bps
|
|
7
|
+
from bluesky.preprocessors import contingency_wrapper
|
|
8
|
+
from bluesky.utils import Msg, make_decorator
|
|
9
|
+
from event_model import Event, EventDescriptor, RunStart
|
|
10
|
+
|
|
11
|
+
from mx_bluesky.hyperion.exceptions import CrystalNotFoundException, SampleException
|
|
12
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.common.abstract_event import (
|
|
13
|
+
AbstractEvent,
|
|
14
|
+
)
|
|
15
|
+
from mx_bluesky.hyperion.external_interaction.callbacks.plan_reactive_callback import (
|
|
16
|
+
PlanReactiveCallback,
|
|
17
|
+
)
|
|
18
|
+
from mx_bluesky.hyperion.external_interaction.ispyb.exp_eye_store import (
|
|
19
|
+
BLSampleStatus,
|
|
20
|
+
ExpeyeInteraction,
|
|
21
|
+
)
|
|
22
|
+
from mx_bluesky.hyperion.log import ISPYB_LOGGER
|
|
23
|
+
from mx_bluesky.hyperion.parameters.constants import CONST
|
|
24
|
+
|
|
25
|
+
# TODO remove this event-raising shenanigans once
|
|
26
|
+
# https://github.com/bluesky/bluesky/issues/1829 is addressed
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass(frozen=True)
|
|
30
|
+
class _ExceptionEvent(AbstractEvent):
|
|
31
|
+
exception_type: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _exception_interceptor(exception: Exception) -> Generator[Msg, Any, Any]:
|
|
35
|
+
yield from bps.create(CONST.DESCRIPTORS.SAMPLE_HANDLING_EXCEPTION)
|
|
36
|
+
yield from bps.read(_ExceptionEvent(type(exception).__name__))
|
|
37
|
+
yield from bps.save()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
sample_handling_callback_decorator = make_decorator(
|
|
41
|
+
partial(contingency_wrapper, except_plan=_exception_interceptor)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SampleHandlingCallback(PlanReactiveCallback):
|
|
46
|
+
"""Intercepts exceptions from experiment plans and updates the ISPyB BLSampleStatus
|
|
47
|
+
field according to the type of exception raised."""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
super().__init__(log=ISPYB_LOGGER)
|
|
51
|
+
self._sample_id: int | None = None
|
|
52
|
+
self._descriptor: str | None = None
|
|
53
|
+
|
|
54
|
+
def activity_gated_start(self, doc: RunStart):
|
|
55
|
+
if not self._sample_id:
|
|
56
|
+
sample_id = doc.get("metadata", {}).get("sample_id")
|
|
57
|
+
self.log.info(f"Recording sample ID at run start {sample_id}")
|
|
58
|
+
self._sample_id = sample_id
|
|
59
|
+
|
|
60
|
+
def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
|
|
61
|
+
if doc.get("name") == CONST.DESCRIPTORS.SAMPLE_HANDLING_EXCEPTION:
|
|
62
|
+
self._descriptor = doc["uid"]
|
|
63
|
+
return super().activity_gated_descriptor(doc)
|
|
64
|
+
|
|
65
|
+
def activity_gated_event(self, doc: Event) -> Event | None:
|
|
66
|
+
if doc["descriptor"] == self._descriptor:
|
|
67
|
+
exception_type = doc["data"]["exception_type"]
|
|
68
|
+
self.log.info(
|
|
69
|
+
f"Sample handling callback intercepted exception of type {exception_type}"
|
|
70
|
+
)
|
|
71
|
+
self._record_exception(exception_type)
|
|
72
|
+
return doc
|
|
73
|
+
|
|
74
|
+
def _record_exception(self, exception_type: str):
|
|
75
|
+
expeye = ExpeyeInteraction()
|
|
76
|
+
assert self._sample_id, "Unable to record exception due to no sample ID"
|
|
77
|
+
sample_status = self._decode_sample_status(exception_type)
|
|
78
|
+
expeye.update_sample_status(self._sample_id, sample_status)
|
|
79
|
+
|
|
80
|
+
def _decode_sample_status(self, exception_type: str) -> BLSampleStatus:
|
|
81
|
+
match exception_type:
|
|
82
|
+
case SampleException.__name__ | CrystalNotFoundException.__name__:
|
|
83
|
+
return BLSampleStatus.ERROR_SAMPLE
|
|
84
|
+
return BLSampleStatus.ERROR_BEAMLINE
|
|
@@ -53,13 +53,16 @@ if TYPE_CHECKING:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def ispyb_activation_wrapper(plan_generator: MsgGenerator, parameters):
|
|
56
|
-
return bpp.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
return bpp.set_run_key_wrapper(
|
|
57
|
+
bpp.run_wrapper(
|
|
58
|
+
plan_generator,
|
|
59
|
+
md={
|
|
60
|
+
"activate_callbacks": ["GridscanISPyBCallback"],
|
|
61
|
+
"subplan_name": CONST.PLAN.GRID_DETECT_AND_DO_GRIDSCAN,
|
|
62
|
+
"hyperion_parameters": parameters.model_dump_json(),
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
CONST.PLAN.ISPYB_ACTIVATION,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
from mx_bluesky.hyperion.exceptions import WarningException
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
class ISPyBDepositionNotMade(Exception):
|
|
5
2
|
"""Raised when the ISPyB or Zocalo callbacks can't access ISPyB deposition numbers."""
|
|
6
3
|
|
|
7
4
|
pass
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class NoCentreFoundException(WarningException):
|
|
11
|
-
"""Error for if zocalo is unable to find the centre during a gridscan."""
|
|
12
|
-
|
|
13
|
-
pass
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import configparser
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import StrEnum
|
|
2
4
|
|
|
3
5
|
from requests import patch, post
|
|
4
6
|
from requests.auth import AuthBase
|
|
@@ -29,20 +31,44 @@ def _get_base_url_and_token() -> tuple[str, str]:
|
|
|
29
31
|
return expeye_config["url"], expeye_config["token"]
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _send_and_get_response(auth, url, data, send_func) -> dict:
|
|
35
|
+
response = send_func(url, auth=auth, json=data)
|
|
36
|
+
if not response.ok:
|
|
37
|
+
raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {response}")
|
|
38
|
+
return response.json()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class BLSample:
|
|
43
|
+
container_id: int
|
|
44
|
+
bl_sample_id: int
|
|
45
|
+
bl_sample_status: str | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BLSampleStatus(StrEnum):
|
|
49
|
+
# The sample has been loaded
|
|
50
|
+
LOADED = "LOADED"
|
|
51
|
+
# Problem with the sample e.g. pin too long/short
|
|
52
|
+
ERROR_SAMPLE = "ERROR - sample"
|
|
53
|
+
# Any other general error
|
|
54
|
+
ERROR_BEAMLINE = "ERROR - beamline"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
assert all(
|
|
58
|
+
len(value) <= 20 for value in BLSampleStatus
|
|
59
|
+
), "Column size limit of 20 for BLSampleStatus"
|
|
60
|
+
|
|
61
|
+
|
|
32
62
|
class ExpeyeInteraction:
|
|
63
|
+
"""Exposes functionality from the Expeye core API"""
|
|
64
|
+
|
|
33
65
|
CREATE_ROBOT_ACTION = "/proposals/{proposal}/sessions/{visit_number}/robot-actions"
|
|
34
66
|
UPDATE_ROBOT_ACTION = "/robot-actions/{action_id}"
|
|
35
67
|
|
|
36
68
|
def __init__(self) -> None:
|
|
37
69
|
url, token = _get_base_url_and_token()
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
|
|
41
|
-
def _send_and_get_response(self, url, data, send_func) -> dict:
|
|
42
|
-
response = send_func(url, auth=self.auth, json=data)
|
|
43
|
-
if not response.ok:
|
|
44
|
-
raise ISPyBDepositionNotMade(f"Could not write {data} to {url}: {response}")
|
|
45
|
-
return response.json()
|
|
70
|
+
self._base_url = url
|
|
71
|
+
self._auth = BearerAuth(token)
|
|
46
72
|
|
|
47
73
|
def start_load(
|
|
48
74
|
self,
|
|
@@ -66,7 +92,7 @@ class ExpeyeInteraction:
|
|
|
66
92
|
Returns:
|
|
67
93
|
RobotActionID: The id of the robot load action that is created
|
|
68
94
|
"""
|
|
69
|
-
url = self.
|
|
95
|
+
url = self._base_url + self.CREATE_ROBOT_ACTION.format(
|
|
70
96
|
proposal=proposal_reference, visit_number=visit_number
|
|
71
97
|
)
|
|
72
98
|
|
|
@@ -77,7 +103,7 @@ class ExpeyeInteraction:
|
|
|
77
103
|
"containerLocation": container_location,
|
|
78
104
|
"dewarLocation": dewar_location,
|
|
79
105
|
}
|
|
80
|
-
response = self.
|
|
106
|
+
response = _send_and_get_response(self._auth, url, data, post)
|
|
81
107
|
return response["robotActionId"]
|
|
82
108
|
|
|
83
109
|
def update_barcode_and_snapshots(
|
|
@@ -95,14 +121,14 @@ class ExpeyeInteraction:
|
|
|
95
121
|
snapshot_before_path (str): Path to the snapshot before robot load
|
|
96
122
|
snapshot_after_path (str): Path to the snapshot after robot load
|
|
97
123
|
"""
|
|
98
|
-
url = self.
|
|
124
|
+
url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
|
|
99
125
|
|
|
100
126
|
data = {
|
|
101
127
|
"sampleBarcode": barcode,
|
|
102
128
|
"xtalSnapshotBefore": snapshot_before_path,
|
|
103
129
|
"xtalSnapshotAfter": snapshot_after_path,
|
|
104
130
|
}
|
|
105
|
-
self.
|
|
131
|
+
_send_and_get_response(self._auth, url, data, patch)
|
|
106
132
|
|
|
107
133
|
def end_load(self, action_id: RobotActionID, status: str, reason: str):
|
|
108
134
|
"""Finish an existing robot action, providing final information about how it went
|
|
@@ -113,13 +139,37 @@ class ExpeyeInteraction:
|
|
|
113
139
|
otherwise error
|
|
114
140
|
reason (str): If the status is in error than the reason for that error
|
|
115
141
|
"""
|
|
116
|
-
url = self.
|
|
142
|
+
url = self._base_url + self.UPDATE_ROBOT_ACTION.format(action_id=action_id)
|
|
117
143
|
|
|
118
144
|
run_status = "SUCCESS" if status == "success" else "ERROR"
|
|
119
145
|
|
|
120
146
|
data = {
|
|
121
147
|
"endTimestamp": get_current_time_string(),
|
|
122
148
|
"status": run_status,
|
|
123
|
-
"message": reason,
|
|
149
|
+
"message": reason[:255] if reason else "",
|
|
124
150
|
}
|
|
125
|
-
self.
|
|
151
|
+
_send_and_get_response(self._auth, url, data, patch)
|
|
152
|
+
|
|
153
|
+
def update_sample_status(
|
|
154
|
+
self, bl_sample_id: int, bl_sample_status: BLSampleStatus
|
|
155
|
+
) -> BLSample:
|
|
156
|
+
"""Update the blSampleStatus of a sample.
|
|
157
|
+
Args:
|
|
158
|
+
bl_sample_id: The sample ID
|
|
159
|
+
bl_sample_status: The sample status
|
|
160
|
+
status_message: An optional message
|
|
161
|
+
Returns:
|
|
162
|
+
The updated sample
|
|
163
|
+
"""
|
|
164
|
+
data = {"blSampleStatus": (str(bl_sample_status))}
|
|
165
|
+
response = _send_and_get_response(
|
|
166
|
+
self._auth, self._base_url + f"/samples/{bl_sample_id}", data, patch
|
|
167
|
+
)
|
|
168
|
+
return self._sample_from_json(response)
|
|
169
|
+
|
|
170
|
+
def _sample_from_json(self, response) -> BLSample:
|
|
171
|
+
return BLSample(
|
|
172
|
+
bl_sample_id=response["blSampleId"],
|
|
173
|
+
bl_sample_status=response["blSampleStatus"],
|
|
174
|
+
container_id=response["containerId"],
|
|
175
|
+
)
|
|
@@ -9,7 +9,7 @@ import bluesky.preprocessors as bpp
|
|
|
9
9
|
from bluesky.run_engine import RunEngine
|
|
10
10
|
from dodal.beamlines import i03
|
|
11
11
|
from dodal.devices.oav.oav_parameters import OAVConfig
|
|
12
|
-
from ophyd_async.
|
|
12
|
+
from ophyd_async.testing import set_mock_value
|
|
13
13
|
|
|
14
14
|
from mx_bluesky.hyperion.device_setup_plans.read_hardware_for_setup import (
|
|
15
15
|
read_hardware_during_collection,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mx-bluesky
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: Bluesky tools for MX Beamlines at DLS
|
|
5
5
|
Author-email: Dominic Oram <dominic.oram@diamond.ac.uk>
|
|
6
6
|
License: Apache License
|
|
@@ -239,7 +239,7 @@ Requires-Dist: daq-config-server>=0.1.1
|
|
|
239
239
|
Requires-Dist: ophyd==1.9.0
|
|
240
240
|
Requires-Dist: ophyd-async>=0.8a5
|
|
241
241
|
Requires-Dist: bluesky>=1.13.0a4
|
|
242
|
-
Requires-Dist: dls-dodal==1.36.
|
|
242
|
+
Requires-Dist: dls-dodal==1.36.3
|
|
243
243
|
Provides-Extra: dev
|
|
244
244
|
Requires-Dist: black; extra == "dev"
|
|
245
245
|
Requires-Dist: build; extra == "dev"
|