mx-bluesky 1.5.0__py3-none-any.whl → 1.5.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.
Files changed (56) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/__init__.py +4 -1
  3. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +56 -1
  4. mx_bluesky/beamlines/i04/experiment_plans/__init__.py +0 -0
  5. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +259 -0
  6. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +8 -8
  7. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +8 -6
  8. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +4 -4
  9. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +2 -2
  10. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +5 -5
  11. mx_bluesky/beamlines/i24/serial/web_gui_plans/general_plans.py +3 -3
  12. mx_bluesky/common/device_setup_plans/robot_load_unload.py +123 -0
  13. mx_bluesky/common/experiment_plans/change_aperture_then_move_plan.py +5 -1
  14. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +27 -3
  15. mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +1 -0
  16. mx_bluesky/common/experiment_plans/inner_plans/do_fgs.py +3 -1
  17. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +26 -24
  18. mx_bluesky/common/external_interaction/ispyb/ispyb_store.py +11 -7
  19. mx_bluesky/common/external_interaction/nexus/write_nexus.py +2 -2
  20. mx_bluesky/common/parameters/__init__.py +0 -0
  21. mx_bluesky/common/parameters/components.py +7 -2
  22. mx_bluesky/common/parameters/constants.py +5 -3
  23. mx_bluesky/common/parameters/device_composites.py +1 -1
  24. mx_bluesky/common/parameters/gridscan.py +1 -0
  25. mx_bluesky/common/xrc_result.py +25 -2
  26. mx_bluesky/hyperion/__main__.py +1 -1
  27. mx_bluesky/hyperion/baton_handler.py +36 -4
  28. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +4 -93
  29. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +19 -31
  30. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +26 -8
  31. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +21 -75
  32. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +2 -2
  33. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +14 -9
  34. mx_bluesky/hyperion/external_interaction/agamemnon.py +4 -4
  35. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +1 -1
  36. mx_bluesky/hyperion/external_interaction/callbacks/{robot_load → robot_actions}/ispyb_callback.py +28 -19
  37. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +1 -1
  38. mx_bluesky/hyperion/external_interaction/callbacks/snapshot_callback.py +3 -0
  39. mx_bluesky/hyperion/external_interaction/config_server.py +0 -11
  40. mx_bluesky/hyperion/parameters/constants.py +2 -7
  41. mx_bluesky/hyperion/parameters/gridscan.py +2 -6
  42. mx_bluesky/hyperion/parameters/load_centre_collect.py +15 -0
  43. mx_bluesky/hyperion/parameters/rotation.py +7 -3
  44. mx_bluesky/hyperion/utils/context.py +19 -5
  45. mx_bluesky/phase1_zebra/__init__.py +1 -0
  46. mx_bluesky/phase1_zebra/device_setup_plans/__init__.py +0 -0
  47. mx_bluesky/phase1_zebra/device_setup_plans/setup_zebra.py +112 -0
  48. {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/METADATA +5 -4
  49. {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/RECORD +55 -49
  50. mx_bluesky/hyperion/utils/validation.py +0 -196
  51. /mx_bluesky/common/experiment_plans/{read_hardware.py → inner_plans/read_hardware.py} +0 -0
  52. /mx_bluesky/common/experiment_plans/{write_sample_status.py → inner_plans/write_sample_status.py} +0 -0
  53. {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/WHEEL +0 -0
  54. {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/entry_points.txt +0 -0
  55. {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/licenses/LICENSE +0 -0
  56. {mx_bluesky-1.5.0.dist-info → mx_bluesky-1.5.2.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from mx_bluesky.common.external_interaction.ispyb.exp_eye_store import (
12
12
  BLSampleStatus,
13
13
  ExpeyeInteraction,
14
14
  RobotActionID,
15
+ create_update_data_from_event_doc,
15
16
  )
16
17
  from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
17
18
  from mx_bluesky.hyperion.parameters.constants import CONST
@@ -20,11 +21,21 @@ if TYPE_CHECKING:
20
21
  from event_model.documents import Event, EventDescriptor, RunStart, RunStop
21
22
 
22
23
 
24
+ robot_update_mapping = {
25
+ "robot-barcode": "sampleBarcode",
26
+ "robot-current_pin": "containerLocation",
27
+ "robot-current_puck": "dewarLocation",
28
+ # I03 uses webcam/oav snapshots in place of before/after snapshots
29
+ "webcam-last_saved_path": "xtalSnapshotBefore",
30
+ "oav-snapshot-last_saved_path": "xtalSnapshotAfter",
31
+ }
32
+
33
+
23
34
  class RobotLoadISPyBCallback(PlanReactiveCallback):
24
35
  def __init__(self) -> None:
25
36
  ISPYB_ZOCALO_CALLBACK_LOGGER.debug("Initialising ISPyB Robot Load Callback")
26
37
  super().__init__(log=ISPYB_ZOCALO_CALLBACK_LOGGER)
27
- self._metadata: dict | None = None
38
+ self._sample_id: int | None = None
28
39
 
29
40
  self.run_uid: str | None = None
30
41
  self.descriptors: dict[str, EventDescriptor] = {}
@@ -35,22 +46,24 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
35
46
  ISPYB_ZOCALO_CALLBACK_LOGGER.debug(
36
47
  "ISPyB robot load callback received start document."
37
48
  )
38
- if doc.get("subplan_name") == CONST.PLAN.ROBOT_LOAD:
49
+ subplan = doc.get("subplan_name")
50
+ if subplan == CONST.PLAN.ROBOT_LOAD or subplan == CONST.PLAN.ROBOT_UNLOAD:
39
51
  ISPYB_ZOCALO_CALLBACK_LOGGER.debug(
40
52
  f"ISPyB robot load callback received: {doc}"
41
53
  )
42
54
  self.run_uid = doc.get("uid")
43
- self._metadata = doc.get("metadata")
44
- assert isinstance(self._metadata, dict)
55
+ metadata = doc.get("metadata")
56
+ assert isinstance(metadata, dict)
57
+ self._sample_id = metadata["sample_id"]
58
+ assert isinstance(self._sample_id, int)
45
59
  proposal, session = get_proposal_and_session_from_visit_string(
46
- self._metadata["visit"]
60
+ metadata["visit"]
47
61
  )
48
- self.action_id = self.expeye.start_load(
62
+ self.action_id = self.expeye.start_robot_action(
63
+ "LOAD" if subplan == CONST.PLAN.ROBOT_LOAD else "UNLOAD",
49
64
  proposal,
50
65
  session,
51
- self._metadata["sample_id"],
52
- self._metadata["sample_puck"],
53
- self._metadata["sample_pin"],
66
+ self._sample_id,
54
67
  )
55
68
  return super().activity_gated_start(doc)
56
69
 
@@ -62,18 +75,14 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
62
75
  event_descriptor = self.descriptors.get(doc["descriptor"])
63
76
  if (
64
77
  event_descriptor
65
- and event_descriptor.get("name") == CONST.DESCRIPTORS.ROBOT_LOAD
78
+ and event_descriptor.get("name") == CONST.DESCRIPTORS.ROBOT_UPDATE
66
79
  ):
67
80
  assert self.action_id is not None, (
68
81
  "ISPyB Robot load callback event called unexpectedly"
69
82
  )
70
- barcode = doc["data"]["robot-barcode"]
71
- oav_snapshot = doc["data"]["oav-snapshot-last_saved_path"]
72
- webcam_snapshot = doc["data"]["webcam-last_saved_path"]
73
83
  # I03 uses webcam/oav snapshots in place of before/after snapshots
74
- self.expeye.update_barcode_and_snapshots(
75
- self.action_id, barcode, webcam_snapshot, oav_snapshot
76
- )
84
+ update_data = create_update_data_from_event_doc(robot_update_mapping, doc)
85
+ self.expeye.update_robot_action(self.action_id, update_data)
77
86
 
78
87
  return super().activity_gated_event(doc)
79
88
 
@@ -87,12 +96,12 @@ class RobotLoadISPyBCallback(PlanReactiveCallback):
87
96
  )
88
97
  exit_status = doc.get("exit_status")
89
98
  assert exit_status, "Exit status not available in stop document!"
90
- assert self._metadata, "Metadata not received before stop document."
99
+ assert self._sample_id is not None, "Stop called before start"
91
100
  reason = doc.get("reason") or "OK"
92
101
 
93
- self.expeye.end_load(self.action_id, exit_status, reason)
102
+ self.expeye.end_robot_action(self.action_id, exit_status, reason)
94
103
  self.expeye.update_sample_status(
95
- self._metadata["sample_id"],
104
+ self._sample_id,
96
105
  BLSampleStatus.LOADED
97
106
  if exit_status == "success"
98
107
  else BLSampleStatus.ERROR_BEAMLINE,
@@ -54,6 +54,7 @@ class RotationISPyBCallback(BaseISPyBCallback):
54
54
  super().__init__(emit=emit)
55
55
  self.last_sample_id: int | None = None
56
56
  self.ispyb_ids: IspybIds = IspybIds()
57
+ self.ispyb = StoreInIspyb(self.ispyb_config)
57
58
 
58
59
  def activity_gated_start(self, doc: RunStart):
59
60
  if doc.get("subplan_name") == CONST.PLAN.ROTATION_OUTER:
@@ -82,7 +83,6 @@ class RotationISPyBCallback(BaseISPyBCallback):
82
83
  f"Collection is {self.params.ispyb_experiment_type} - storing sampleID to bundle images"
83
84
  )
84
85
  self.last_sample_id = self.params.sample_id
85
- self.ispyb = StoreInIspyb(self.ispyb_config)
86
86
  ISPYB_ZOCALO_CALLBACK_LOGGER.info("Beginning ispyb deposition")
87
87
  data_collection_group_info = populate_data_collection_group(self.params)
88
88
  data_collection_info = populate_data_collection_info_for_rotation(
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import os
2
3
  import re
3
4
  from collections.abc import Iterator
4
5
  from datetime import datetime
@@ -172,6 +173,8 @@ class BeamDrawingCallback(PlanReactiveCallback):
172
173
  f"Generating snapshot at {current_sample_pos_mm} from base snapshot {snapshot_info}"
173
174
  )
174
175
  output_snapshot_directory = data["oav-snapshot-directory"]
176
+ if not os.path.exists(output_snapshot_directory):
177
+ os.mkdir(output_snapshot_directory)
175
178
  base_file_stem = Path(snapshot_info.snapshot_path).stem
176
179
  output_snapshot_filename = _snapshot_filename(base_file_stem)
177
180
  output_snapshot_path = (
@@ -1,7 +1,6 @@
1
1
  from functools import cache
2
2
 
3
3
  from daq_config_server.client import ConfigServer
4
- from pydantic import model_validator
5
4
 
6
5
  from mx_bluesky.common.external_interaction.config_server import FeatureFlags
7
6
  from mx_bluesky.common.utils.log import LOGGER
@@ -14,8 +13,6 @@ class HyperionFeatureFlags(FeatureFlags):
14
13
 
15
14
  Attributes:
16
15
  use_panda_for_gridscan: If True then the PandA is used for gridscans, otherwise the zebra is used
17
- compare_cpu_and_gpu_zocalo: If True then GPU result processing is enabled
18
- alongside CPU and the results are compared. The CPU result is still take.n
19
16
  use_gpu_results: If True then GPU result processing is enabled
20
17
  and the GPU result is taken.
21
18
  set_stub_offsets: If True then set the stub offsets after moving to the crystal (ignored for
@@ -31,15 +28,7 @@ class HyperionFeatureFlags(FeatureFlags):
31
28
  def get_config_server() -> ConfigServer:
32
29
  return ConfigServer(CONST.CONFIG_SERVER_URL, LOGGER)
33
30
 
34
- @model_validator(mode="after")
35
- def use_gpu_and_compare_cannot_both_be_true(self):
36
- assert not (self.use_gpu_results and self.compare_cpu_and_gpu_zocalo), (
37
- "Cannot both use GPU results and compare them to CPU"
38
- )
39
- return self
40
-
41
31
  use_panda_for_gridscan: bool = CONST.I03.USE_PANDA_FOR_GRIDSCAN
42
- compare_cpu_and_gpu_zocalo: bool = CONST.I03.COMPARE_CPU_AND_GPU_ZOCALO
43
32
  use_gpu_results: bool = CONST.I03.USE_GPU_RESULTS
44
33
  set_stub_offsets: bool = CONST.I03.SET_STUB_OFFSETS
45
34
  omega_flip: bool = CONST.I03.OMEGA_FLIP
@@ -29,17 +29,12 @@ class I03Constants:
29
29
  OMEGA_FLIP = True
30
30
  ALTERNATE_ROTATION_DIRECTION = True
31
31
 
32
- # Turns on GPU processing for zocalo and logs a comparison between GPU and CPU-
33
- # processed results.
34
- COMPARE_CPU_AND_GPU_ZOCALO = False
35
-
36
32
  # Turns on GPU processing for zocalo and uses the results that come back
37
- USE_GPU_RESULTS = False
33
+ USE_GPU_RESULTS = True
38
34
 
39
35
 
40
36
  @dataclass(frozen=True)
41
37
  class HyperionConstants:
42
- DESCRIPTORS = DocDescriptorNames()
43
38
  ZOCALO_ENV = EnvironmentConstants.ZOCALO_ENV
44
39
  HARDWARE = HardwareConstants()
45
40
  I03 = I03Constants()
@@ -53,7 +48,7 @@ class HyperionConstants:
53
48
  if TEST_MODE
54
49
  else "https://daq-config.diamond.ac.uk/api"
55
50
  )
56
- GRAYLOG_PORT = 12232
51
+ GRAYLOG_PORT = 12232 # Hyperion stream
57
52
  PARAMETER_SCHEMA_DIRECTORY = "src/hyperion/parameters/schemas/"
58
53
  LOG_FILE_NAME = "hyperion.log"
59
54
  DEVICE_SETTINGS_CONSTANTS = DeviceSettingsConstants()
@@ -20,9 +20,7 @@ class GridCommonWithHyperionDetectorParams(GridCommon, WithHyperionUDCFeatures):
20
20
  @property
21
21
  def detector_params(self):
22
22
  params = super().detector_params
23
- params.enable_dev_shm = (
24
- self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
25
- )
23
+ params.enable_dev_shm = self.features.use_gpu_results
26
24
  return params
27
25
 
28
26
 
@@ -35,9 +33,7 @@ class HyperionSpecifiedThreeDGridScan(WithHyperionUDCFeatures, SpecifiedThreeDGr
35
33
  @property
36
34
  def detector_params(self):
37
35
  params = super().detector_params
38
- params.enable_dev_shm = (
39
- self.features.compare_cpu_and_gpu_zocalo or self.features.use_gpu_results
40
- )
36
+ params.enable_dev_shm = self.features.use_gpu_results
41
37
  return params
42
38
 
43
39
  # Relative to common grid scan, stub offsets are defined by config server
@@ -50,6 +50,12 @@ class LoadCentreCollect(
50
50
  f"Unexpected fields found in LoadCentreCollect {disallowed_keys}"
51
51
  )
52
52
 
53
+ assert "features" not in values["robot_load_then_centre"], (
54
+ "Features flags must be specified at top-level in LoadCentreCollect"
55
+ )
56
+ assert "features" not in values["multi_rotation_scan"], (
57
+ "Features flags must be specified at top-level in LoadCentreCollect"
58
+ )
53
59
  keys_from_outer_load_centre_collect = (
54
60
  MxBlueskyParameters.model_fields.keys()
55
61
  | WithSample.model_fields.keys()
@@ -70,6 +76,9 @@ class LoadCentreCollect(
70
76
  f"Unexpected keys in multi_rotation_scan: {', '.join(duplicated_multi_rotation_scan_keys)}"
71
77
  )
72
78
 
79
+ for rotation in values["multi_rotation_scan"]["rotation_scans"]:
80
+ rotation["sample_id"] = values["sample_id"]
81
+
73
82
  new_robot_load_then_centre_params = construct_from_values(
74
83
  values, values["robot_load_then_centre"], RobotLoadThenCentre
75
84
  )
@@ -80,6 +89,12 @@ class LoadCentreCollect(
80
89
  values["robot_load_then_centre"] = new_robot_load_then_centre_params
81
90
  return values
82
91
 
92
+ @model_validator(mode="after")
93
+ def _ensure_features_are_internally_consistent(self) -> Self:
94
+ self.robot_load_then_centre.features = self.features
95
+ self.multi_rotation_scan.features = self.features
96
+ return self
97
+
83
98
  @model_validator(mode="after")
84
99
  def _check_rotation_start_xyz_is_not_specified(self) -> Self:
85
100
  for scan in self.multi_rotation_scan.single_rotation_scans:
@@ -18,12 +18,14 @@ from scanspec.core import Path as ScanPath
18
18
  from scanspec.specs import Line
19
19
 
20
20
  from mx_bluesky.common.parameters.components import (
21
+ DiffractionExperiment,
21
22
  DiffractionExperimentWithSample,
22
23
  IspybExperimentType,
23
24
  OptionalGonioAngleStarts,
24
25
  OptionalXyzStarts,
25
26
  RotationAxis,
26
27
  SplitScan,
28
+ WithSample,
27
29
  WithScan,
28
30
  )
29
31
  from mx_bluesky.hyperion.parameters.components import WithHyperionUDCFeatures
@@ -33,7 +35,7 @@ from mx_bluesky.hyperion.parameters.constants import (
33
35
  )
34
36
 
35
37
 
36
- class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
38
+ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts, WithSample):
37
39
  """
38
40
  Describes a rotation scan about the specified axis.
39
41
 
@@ -54,7 +56,7 @@ class RotationScanPerSweep(OptionalGonioAngleStarts, OptionalXyzStarts):
54
56
  nexus_vds_start_img: int = Field(default=0, ge=0)
55
57
 
56
58
 
57
- class RotationExperiment(DiffractionExperimentWithSample, WithHyperionUDCFeatures):
59
+ class RotationExperiment(DiffractionExperiment, WithHyperionUDCFeatures):
58
60
  shutter_opening_time_s: float = Field(default=CONST.I03.SHUTTER_TIME_S)
59
61
  rotation_increment_deg: float = Field(default=0.1, gt=0)
60
62
  ispyb_experiment_type: IspybExperimentType = Field(
@@ -105,7 +107,9 @@ class RotationExperiment(DiffractionExperimentWithSample, WithHyperionUDCFeature
105
107
  return aperture_position
106
108
 
107
109
 
108
- class SingleRotationScan(WithScan, RotationScanPerSweep, RotationExperiment):
110
+ class SingleRotationScan(
111
+ WithScan, RotationScanPerSweep, RotationExperiment, DiffractionExperimentWithSample
112
+ ):
109
113
  @property
110
114
  def detector_params(self):
111
115
  return self._detector_params(self.omega_start_deg)
@@ -1,5 +1,6 @@
1
1
  from blueapi.core import BlueskyContext
2
- from dodal.utils import get_beamline_based_on_environment_variable
2
+ from dodal.common.beamlines.beamline_utils import clear_devices
3
+ from dodal.utils import collect_factories, get_beamline_based_on_environment_variable
3
4
 
4
5
  import mx_bluesky.hyperion.experiment_plans as hyperion_plans
5
6
  from mx_bluesky.common.utils.log import LOGGER
@@ -9,11 +10,24 @@ def setup_context(dev_mode: bool = False) -> BlueskyContext:
9
10
  context = BlueskyContext()
10
11
  context.with_plan_module(hyperion_plans)
11
12
 
12
- context.with_dodal_module(
13
- get_beamline_based_on_environment_variable(),
14
- mock=dev_mode,
15
- )
13
+ setup_devices(context, dev_mode)
16
14
 
17
15
  LOGGER.info(f"Plans found in context: {context.plan_functions.keys()}")
18
16
 
19
17
  return context
18
+
19
+
20
+ def clear_all_device_caches(context: BlueskyContext):
21
+ context.unregister_all_devices()
22
+ clear_devices()
23
+
24
+ for f in collect_factories(get_beamline_based_on_environment_variable()).values():
25
+ if hasattr(f, "cache_clear"):
26
+ f.cache_clear() # type: ignore
27
+
28
+
29
+ def setup_devices(context: BlueskyContext, dev_mode: bool):
30
+ context.with_dodal_module(
31
+ get_beamline_based_on_environment_variable(),
32
+ mock=dev_mode,
33
+ )
@@ -0,0 +1 @@
1
+ """The zebra on i03 and i04 (phase1 beamlines) are configured in a specific way which are compatible to the plans in this module"""
File without changes
@@ -0,0 +1,112 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from bluesky.utils import MsgGenerator
5
+ from dodal.devices.zebra.zebra import (
6
+ Zebra,
7
+ )
8
+ from dodal.devices.zebra.zebra_controlled_shutter import (
9
+ ZebraShutter,
10
+ ZebraShutterControl,
11
+ )
12
+
13
+ from mx_bluesky.common.parameters.constants import ZEBRA_STATUS_TIMEOUT
14
+ from mx_bluesky.common.utils.log import LOGGER
15
+
16
+
17
+ @runtime_checkable
18
+ class GridscanSetupDevices(Protocol):
19
+ zebra: Zebra
20
+ sample_shutter: ZebraShutter
21
+
22
+
23
+ def setup_zebra_for_gridscan(
24
+ composite: GridscanSetupDevices, # XRC gridscan's generic trigger setup expects a composite rather than individual devices
25
+ group="setup_zebra_for_gridscan",
26
+ wait=True,
27
+ ) -> MsgGenerator:
28
+ zebra = composite.zebra
29
+ # Set shutter to automatic and to trigger via motion controller GPIO signal (IN4_TTL)
30
+ yield from configure_zebra_and_shutter_for_auto_shutter(
31
+ zebra, composite.sample_shutter, zebra.mapping.sources.IN4_TTL, group=group
32
+ )
33
+
34
+ yield from bps.abs_set(
35
+ zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
36
+ zebra.mapping.sources.IN3_TTL,
37
+ group=group,
38
+ )
39
+ yield from bps.abs_set(
40
+ zebra.output.out_pvs[zebra.mapping.outputs.TTL_XSPRESS3],
41
+ zebra.mapping.sources.DISCONNECT,
42
+ group=group,
43
+ )
44
+ yield from bps.abs_set(
45
+ zebra.output.pulse_1.input, zebra.mapping.sources.DISCONNECT, group=group
46
+ )
47
+
48
+ if wait:
49
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
50
+
51
+
52
+ def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
53
+ """Set the signal that controls the shutter. We use the second input to the
54
+ Zebra's AND2 gate for this input. ZebraShutter control mode must be in auto for this input to take control
55
+
56
+ For more details see the ZebraShutter device."""
57
+ auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
58
+ auto_shutter_control = zebra.logic_gates.and_gates[auto_gate]
59
+ yield from bps.abs_set(auto_shutter_control.sources[2], input, group)
60
+
61
+
62
+ def configure_zebra_and_shutter_for_auto_shutter(
63
+ zebra: Zebra, zebra_shutter: ZebraShutter, input: int, group="use_automatic_shutter"
64
+ ):
65
+ """Set the shutter to auto mode, and configure the zebra to trigger the shutter on
66
+ an input source. For the input, use one of the source constants in zebra.py
67
+
68
+ When the shutter is in auto/manual, logic in EPICS sets the Zebra's
69
+ SOFT_IN1 to low/high respectively. The Zebra's AND2 gate should be used to control the shutter while in auto mode.
70
+ To do this, we need (AND2 = SOFT_IN1 AND input), where input is the zebra signal we want to control the shutter when in auto mode.
71
+ """
72
+ # See https://github.com/DiamondLightSource/dodal/issues/813 for better typing here.
73
+
74
+ # Set shutter to auto mode
75
+ yield from bps.abs_set(
76
+ zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
77
+ )
78
+
79
+ auto_gate = zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER
80
+
81
+ # Set first input of AND2 gate to SOFT_IN1, which is high when shutter is in auto mode
82
+ # Note the Zebra should ALWAYS be setup this way. See https://github.com/DiamondLightSource/mx-bluesky/issues/551
83
+ yield from bps.abs_set(
84
+ zebra.logic_gates.and_gates[auto_gate].sources[1],
85
+ zebra.mapping.sources.SOFT_IN1,
86
+ group=group,
87
+ )
88
+
89
+ # Set the second input of AND2 gate to the requested zebra input source
90
+ yield from set_shutter_auto_input(zebra, input, group=group)
91
+
92
+
93
+ def tidy_up_zebra_after_gridscan(
94
+ zebra: Zebra,
95
+ zebra_shutter: ZebraShutter,
96
+ group="tidy_up_zebra_after_gridscan",
97
+ wait=True,
98
+ ) -> MsgGenerator:
99
+ LOGGER.info("Tidying up Zebra")
100
+
101
+ yield from bps.abs_set(
102
+ zebra.output.out_pvs[zebra.mapping.outputs.TTL_DETECTOR],
103
+ zebra.mapping.sources.PC_PULSE,
104
+ group=group,
105
+ )
106
+ yield from bps.abs_set(
107
+ zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
108
+ )
109
+ yield from set_shutter_auto_input(zebra, zebra.mapping.sources.PC_GATE, group=group)
110
+
111
+ if wait:
112
+ yield from bps.wait(group, timeout=ZEBRA_STATUS_TIMEOUT)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mx-bluesky
3
- Version: 1.5.0
3
+ Version: 1.5.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
@@ -221,7 +221,7 @@ Requires-Dist: ispyb
221
221
  Requires-Dist: jupyterlab
222
222
  Requires-Dist: matplotlib
223
223
  Requires-Dist: nexgen>=0.11.0
224
- Requires-Dist: numpy
224
+ Requires-Dist: numpy==2.2.6
225
225
  Requires-Dist: opencv-python
226
226
  Requires-Dist: opentelemetry-distro
227
227
  Requires-Dist: opentelemetry-exporter-otlp
@@ -233,13 +233,14 @@ Requires-Dist: requests
233
233
  Requires-Dist: scanspec
234
234
  Requires-Dist: scipy
235
235
  Requires-Dist: semver
236
+ Requires-Dist: deepdiff
236
237
  Requires-Dist: matplotlib
237
- Requires-Dist: blueapi>=0.11.1
238
+ Requires-Dist: blueapi>=0.15.0
238
239
  Requires-Dist: daq-config-server==0.1.1
239
240
  Requires-Dist: ophyd>=1.10.5
240
241
  Requires-Dist: ophyd-async>=0.10.0a2
241
242
  Requires-Dist: bluesky>=1.13.1
242
- Requires-Dist: dls-dodal==1.49.0
243
+ Requires-Dist: dls-dodal==1.52.0
243
244
  Provides-Extra: dev
244
245
  Requires-Dist: black; extra == "dev"
245
246
  Requires-Dist: build; extra == "dev"