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,116 @@
1
+ import logging
2
+ from logging.handlers import TimedRotatingFileHandler
3
+ from os import environ
4
+ from pathlib import Path
5
+
6
+ from dodal.log import (
7
+ ERROR_LOG_BUFFER_LINES,
8
+ CircularMemoryHandler,
9
+ DodalLogHandlers,
10
+ integrate_bluesky_and_ophyd_logging,
11
+ set_up_all_logging_handlers,
12
+ )
13
+ from dodal.log import LOGGER as dodal_logger
14
+
15
+ __logger_handlers: DodalLogHandlers | None = None
16
+
17
+
18
+ class ExperimentMetadataTagFilter(logging.Filter):
19
+ """When an instance of this custom filter is added to a logging handler, dc_group_id
20
+ and run_id will be tagged in that handlers' log messages."""
21
+
22
+ dc_group_id: str | None = None
23
+ run_uid: str | None = None
24
+
25
+ def filter(self, record):
26
+ if self.dc_group_id:
27
+ record.dc_group_id = self.dc_group_id
28
+ if self.run_uid:
29
+ record.run_uid = self.run_uid
30
+ return True
31
+
32
+
33
+ tag_filter = ExperimentMetadataTagFilter()
34
+
35
+
36
+ def set_dcgid_tag(dcgid):
37
+ """Set the datacollection group id as a tag on all subsequent log messages.
38
+ Setting to None will remove the tag."""
39
+ tag_filter.dc_group_id = dcgid
40
+
41
+
42
+ def set_uid_tag(uid):
43
+ """Set the unique id as a tag on all subsequent log messages.
44
+ Setting to None will remove the tag."""
45
+ tag_filter.run_uid = uid
46
+
47
+
48
+ def do_default_logging_setup(
49
+ file_name: str,
50
+ graylog_port: int,
51
+ dev_mode: bool = False,
52
+ integrate_all_logs: bool = True,
53
+ ):
54
+ """Configures dodal logger so that separate debug and info log files are created,
55
+ info logs are sent to Graylog, info logs are streamed to sys.sterr, and logs from ophyd
56
+ and bluesky and ophyd-async are optionally included."""
57
+
58
+ handlers = set_up_all_logging_handlers(
59
+ dodal_logger,
60
+ _get_logging_dir(),
61
+ file_name,
62
+ dev_mode,
63
+ ERROR_LOG_BUFFER_LINES,
64
+ graylog_port,
65
+ )
66
+
67
+ if integrate_all_logs:
68
+ integrate_bluesky_and_ophyd_logging(dodal_logger)
69
+
70
+ handlers["graylog_handler"].addFilter(tag_filter)
71
+
72
+ global __logger_handlers
73
+ __logger_handlers = handlers
74
+
75
+
76
+ def _get_debug_handler() -> CircularMemoryHandler:
77
+ assert (
78
+ __logger_handlers is not None
79
+ ), "You can only use this after running the default logging setup"
80
+ return __logger_handlers["debug_memory_handler"]
81
+
82
+
83
+ def flush_debug_handler() -> str:
84
+ """Writes the contents of the circular debug log buffer to disk and returns the written filename"""
85
+ handler = _get_debug_handler()
86
+ assert isinstance(
87
+ handler.target, TimedRotatingFileHandler
88
+ ), "Circular memory handler doesn't have an appropriate fileHandler target"
89
+ handler.flush()
90
+ return handler.target.baseFilename
91
+
92
+
93
+ def _get_logging_dir() -> Path:
94
+ """Get the path to write the mx_bluesky log files to.
95
+
96
+ Log location can be specified in the LOG_DIR environment variable, otherwise MX bluesky logs are written to 'dls_sw/ixx/logs/bluesky'.
97
+ This directory will be created if it is not found
98
+
99
+ Logs are written to ./tmp/logs/bluesky if BEAMLINE environment variable is not found
100
+
101
+ Returns:
102
+ logging_path (Path): Path to the log file for the file handler to write to.
103
+ """
104
+
105
+ logging_str = environ.get("LOG_DIR")
106
+ if logging_str:
107
+ logging_path = Path(logging_str)
108
+ else:
109
+ beamline = environ.get("BEAMLINE")
110
+ logging_path = (
111
+ Path(f"/dls_sw/{beamline}/logs/bluesky/")
112
+ if beamline
113
+ else Path("/tmp/logs/bluesky")
114
+ )
115
+ Path.mkdir(logging_path, exist_ok=True, parents=True)
116
+ return logging_path
@@ -8,8 +8,8 @@ from opentelemetry.sdk.trace import TracerProvider
8
8
  from opentelemetry.sdk.trace.export import BatchSpanProcessor
9
9
 
10
10
 
11
- def setup_tracing():
12
- resource = Resource(attributes={SERVICE_NAME: "Hyperion"})
11
+ def setup_tracing(service_name: str = "Hyperion"):
12
+ resource = Resource(attributes={SERVICE_NAME: service_name})
13
13
 
14
14
  traceProvider = TracerProvider(resource=resource)
15
15
  processor = BatchSpanProcessor(
@@ -14,6 +14,10 @@ from flask import Flask, request
14
14
  from flask_restful import Api, Resource
15
15
  from pydantic.dataclasses import dataclass
16
16
 
17
+ from mx_bluesky.common.parameters.components import MxBlueskyParameters
18
+ from mx_bluesky.common.parameters.constants import Actions, Status
19
+ from mx_bluesky.common.utils.log import do_default_logging_setup, flush_debug_handler
20
+ from mx_bluesky.common.utils.tracing import TRACER
17
21
  from mx_bluesky.hyperion.exceptions import WarningException
18
22
  from mx_bluesky.hyperion.experiment_plans.experiment_registry import (
19
23
  PLAN_REGISTRY,
@@ -36,13 +40,9 @@ from mx_bluesky.hyperion.external_interaction.callbacks.logging_callback import
36
40
  )
37
41
  from mx_bluesky.hyperion.log import (
38
42
  LOGGER,
39
- do_default_logging_setup,
40
- flush_debug_handler,
41
43
  )
42
44
  from mx_bluesky.hyperion.parameters.cli import parse_cli_args
43
- from mx_bluesky.hyperion.parameters.components import HyperionParameters
44
- from mx_bluesky.hyperion.parameters.constants import CONST, Actions, Status
45
- from mx_bluesky.hyperion.tracing import TRACER
45
+ from mx_bluesky.hyperion.parameters.constants import CONST
46
46
  from mx_bluesky.hyperion.utils.context import setup_context
47
47
 
48
48
  VERBOSE_EVENT_LOGGING: bool | None = None
@@ -53,7 +53,7 @@ class Command:
53
53
  action: Actions
54
54
  devices: Any | None = None
55
55
  experiment: Callable[[Any, Any], MsgGenerator] | None = None
56
- parameters: HyperionParameters | None = None
56
+ parameters: MxBlueskyParameters | None = None
57
57
  callbacks: CallbacksFactory | None = None
58
58
 
59
59
 
@@ -119,7 +119,7 @@ class BlueskyRunner:
119
119
  def start(
120
120
  self,
121
121
  experiment: Callable,
122
- parameters: HyperionParameters,
122
+ parameters: MxBlueskyParameters,
123
123
  plan_name: str,
124
124
  callbacks: CallbacksFactory | None,
125
125
  ) -> StatusAndMessage:
@@ -239,7 +239,7 @@ def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions)
239
239
  raise ValueError(f"Extra fields not allowed {parameters.model_extra}")
240
240
  except Exception as e:
241
241
  raise ValueError(
242
- f"Supplied parameters don't match the plan for this endpoint {request.data}"
242
+ f"Supplied parameters don't match the plan for this endpoint {request.data}, for plan {plan_name}"
243
243
  ) from e
244
244
  return plan, parameters, plan_name, callback_type
245
245
 
@@ -345,7 +345,9 @@ def create_app(
345
345
  def create_targets():
346
346
  hyperion_port = 5005
347
347
  args = parse_cli_args()
348
- do_default_logging_setup(dev_mode=args.dev_mode)
348
+ do_default_logging_setup(
349
+ CONST.LOG_FILE_NAME, CONST.GRAYLOG_PORT, dev_mode=args.dev_mode
350
+ )
349
351
  if not args.use_external_callbacks:
350
352
  setup_callback_logging(args.dev_mode)
351
353
  app, runner = create_app(
@@ -4,7 +4,7 @@ import bluesky.plan_stubs as bps
4
4
  from dodal.devices.focusing_mirror import (
5
5
  FocusingMirrorWithStripes,
6
6
  MirrorStripe,
7
- VFMMirrorVoltages,
7
+ MirrorVoltages,
8
8
  )
9
9
  from dodal.devices.undulator_dcm import UndulatorDCM
10
10
  from dodal.devices.util.adjuster_plans import lookup_table_adjuster
@@ -13,6 +13,9 @@ from dodal.devices.util.lookup_tables import (
13
13
  )
14
14
 
15
15
  from mx_bluesky.hyperion.log import LOGGER
16
+ from mx_bluesky.hyperion.utils.utils import (
17
+ energy_to_bragg_angle,
18
+ )
16
19
 
17
20
  MIRROR_VOLTAGE_GROUP = "MIRROR_VOLTAGE_GROUP"
18
21
  DCM_GROUP = "DCM_GROUP"
@@ -20,40 +23,41 @@ DCM_GROUP = "DCM_GROUP"
20
23
 
21
24
  def _apply_and_wait_for_voltages_to_settle(
22
25
  stripe: MirrorStripe,
23
- mirror: FocusingMirrorWithStripes,
24
- mirror_voltages: VFMMirrorVoltages,
26
+ mirror_voltages: MirrorVoltages,
25
27
  ):
26
28
  with open(mirror_voltages.voltage_lookup_table_path) as lut_file:
27
29
  json_obj = json.load(lut_file)
28
30
 
29
31
  # sample mode is the only mode supported
30
32
  sample_data = json_obj["sample"]
31
- mirror_key = mirror.name.lower()
32
33
  if stripe == MirrorStripe.BARE:
33
34
  stripe_key = "bare"
34
35
  elif stripe == MirrorStripe.RHODIUM:
35
36
  stripe_key = "rh"
36
37
  elif stripe == MirrorStripe.PLATINUM:
37
38
  stripe_key = "pt"
38
- else:
39
- raise ValueError(f"Unsupported stripe '{stripe}'")
40
-
41
- required_voltages = sample_data[stripe_key][mirror_key]
42
- for voltage_channel, required_voltage in zip(
43
- mirror_voltages.voltage_channels.values(), required_voltages, strict=False
44
- ):
45
- LOGGER.debug(
46
- f"Applying and waiting for voltage {voltage_channel.name} = {required_voltage}"
47
- )
48
- yield from bps.abs_set(
49
- voltage_channel, required_voltage, group=MIRROR_VOLTAGE_GROUP
50
- )
39
+
40
+ for mirror_key, channels in {
41
+ "hfm": mirror_voltages.horizontal_voltages,
42
+ "vfm": mirror_voltages.vertical_voltages,
43
+ }.items():
44
+ required_voltages = sample_data[stripe_key][mirror_key]
45
+
46
+ for voltage_channel, required_voltage in zip(
47
+ channels.values(), required_voltages, strict=True
48
+ ):
49
+ LOGGER.debug(
50
+ f"Applying and waiting for voltage {voltage_channel.name} = {required_voltage}"
51
+ )
52
+ yield from bps.abs_set(
53
+ voltage_channel, required_voltage, group=MIRROR_VOLTAGE_GROUP
54
+ )
51
55
 
52
56
  yield from bps.wait(group=MIRROR_VOLTAGE_GROUP)
53
57
 
54
58
 
55
59
  def adjust_mirror_stripe(
56
- energy_kev, mirror: FocusingMirrorWithStripes, mirror_voltages: VFMMirrorVoltages
60
+ energy_kev, mirror: FocusingMirrorWithStripes, mirror_voltages: MirrorVoltages
57
61
  ):
58
62
  """Feedback should be OFF prior to entry, in order to prevent
59
63
  feedback from making unnecessary corrections while beam is being adjusted."""
@@ -66,26 +70,27 @@ def adjust_mirror_stripe(
66
70
  yield from bps.trigger(mirror.apply_stripe)
67
71
 
68
72
  LOGGER.info("Adjusting mirror voltages...")
69
- yield from _apply_and_wait_for_voltages_to_settle(stripe, mirror, mirror_voltages)
73
+ yield from _apply_and_wait_for_voltages_to_settle(stripe, mirror_voltages)
70
74
 
71
75
 
72
76
  def adjust_dcm_pitch_roll_vfm_from_lut(
73
77
  undulator_dcm: UndulatorDCM,
74
78
  vfm: FocusingMirrorWithStripes,
75
- vfm_mirror_voltages: VFMMirrorVoltages,
79
+ mirror_voltages: MirrorVoltages,
76
80
  energy_kev,
77
81
  ):
78
82
  """Beamline energy-change post-adjustments : Adjust DCM and VFM directly from lookup tables.
79
- Lookups are performed against the Bragg angle which will have been automatically set by EPICS as a side-effect of the
80
- energy change prior to calling this function.
83
+ Lookups are performed against the Bragg angle which is computed directly from the target energy
84
+ rather than waiting for the EPICS controls PV to reach it.
81
85
  Feedback should be OFF prior to entry, in order to prevent
82
86
  feedback from making unnecessary corrections while beam is being adjusted."""
83
87
 
84
- # DCM Pitch
88
+ # Adjust DCM Pitch
85
89
  dcm = undulator_dcm.dcm
86
90
  LOGGER.info(f"Adjusting DCM and VFM for {energy_kev} keV")
87
- bragg_deg = yield from bps.rd(dcm.bragg_in_degrees.user_readback)
88
- LOGGER.info(f"Read Bragg angle = {bragg_deg} degrees")
91
+ d_spacing_a: float = yield from bps.rd(undulator_dcm.dcm.crystal_metadata_d_spacing)
92
+ bragg_deg = energy_to_bragg_angle(energy_kev, d_spacing_a)
93
+ LOGGER.info(f"Target Bragg angle = {bragg_deg} degrees")
89
94
  dcm_pitch_adjuster = lookup_table_adjuster(
90
95
  linear_interpolation_lut(undulator_dcm.pitch_energy_table_path),
91
96
  dcm.pitch_in_mrad,
@@ -119,7 +124,7 @@ def adjust_dcm_pitch_roll_vfm_from_lut(
119
124
  # not sure how we check this
120
125
 
121
126
  # VFM Stripe selection
122
- yield from adjust_mirror_stripe(energy_kev, vfm, vfm_mirror_voltages)
127
+ yield from adjust_mirror_stripe(energy_kev, vfm, mirror_voltages)
123
128
  yield from bps.wait(DCM_GROUP)
124
129
 
125
130
  # VFM Adjust - for I03 this table always returns the same value
@@ -6,7 +6,6 @@ from dodal.devices.attenuator import Attenuator
6
6
  from dodal.devices.dcm import DCM
7
7
  from dodal.devices.eiger import EigerDetector
8
8
  from dodal.devices.flux import Flux
9
- from dodal.devices.robot import BartRobot
10
9
  from dodal.devices.s4_slit_gaps import S4SlitGaps
11
10
  from dodal.devices.smargon import Smargon
12
11
  from dodal.devices.synchrotron import Synchrotron
@@ -20,7 +19,7 @@ def read_hardware_pre_collection(
20
19
  undulator: Undulator,
21
20
  synchrotron: Synchrotron,
22
21
  s4_slit_gaps: S4SlitGaps,
23
- robot: BartRobot,
22
+ dcm: DCM,
24
23
  smargon: Smargon,
25
24
  ):
26
25
  LOGGER.info("Reading status of beamline for callbacks, pre collection.")
@@ -29,11 +28,12 @@ def read_hardware_pre_collection(
29
28
  ) # gives name to event *descriptor* document
30
29
  yield from bps.read(undulator.current_gap)
31
30
  yield from bps.read(synchrotron.synchrotron_mode)
32
- yield from bps.read(s4_slit_gaps.xgap)
33
- yield from bps.read(s4_slit_gaps.ygap)
31
+ yield from bps.read(s4_slit_gaps.xgap) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
32
+ yield from bps.read(s4_slit_gaps.ygap) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
34
33
  yield from bps.read(smargon.x)
35
34
  yield from bps.read(smargon.y)
36
35
  yield from bps.read(smargon.z)
36
+ yield from bps.read(dcm.energy_in_kev)
37
37
  yield from bps.save()
38
38
 
39
39
 
@@ -48,13 +48,7 @@ def read_hardware_during_collection(
48
48
  yield from bps.create(name=CONST.DESCRIPTORS.HARDWARE_READ_DURING)
49
49
  yield from bps.read(aperture_scatterguard)
50
50
  yield from bps.read(attenuator.actual_transmission)
51
- yield from bps.read(flux.flux_reading)
51
+ yield from bps.read(flux.flux_reading) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
52
52
  yield from bps.read(dcm.energy_in_kev)
53
- yield from bps.read(detector.bit_depth)
54
- yield from bps.save()
55
-
56
-
57
- def read_hardware_for_zocalo(detector: EigerDetector):
58
- yield from bps.create(name=CONST.DESCRIPTORS.ZOCALO_HW_READ)
59
- yield from bps.read(detector.odin.file_writer.id)
53
+ yield from bps.read(detector.bit_depth) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
60
54
  yield from bps.save()
@@ -1,11 +1,10 @@
1
1
  from functools import partial
2
2
 
3
3
  import bluesky.plan_stubs as bps
4
+ from dodal.devices.areadetector.plugins.CAM import ColorMode
4
5
  from dodal.devices.oav.oav_detector import OAV
5
- from dodal.devices.oav.oav_errors import OAVError_ZoomLevelNotFound
6
6
  from dodal.devices.oav.oav_parameters import OAVParameters
7
7
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
8
- from dodal.devices.oav.utils import ColorMode
9
8
 
10
9
  from mx_bluesky.hyperion.parameters.constants import CONST
11
10
 
@@ -56,19 +55,14 @@ def setup_pin_tip_detection_params(
56
55
 
57
56
 
58
57
  def setup_general_oav_params(oav: OAV, parameters: OAVParameters):
59
- yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
60
- yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
61
- yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
62
- yield from set_using_group(oav.cam.gain, parameters.gain)
58
+ yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
59
+ yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
60
+ yield from set_using_group(oav.cam.acquire_time, parameters.exposure) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
61
+ yield from set_using_group(oav.cam.gain, parameters.gain) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
63
62
 
64
63
  zoom_level_str = f"{float(parameters.zoom)}x"
65
- if zoom_level_str not in oav.zoom_controller.allowed_zoom_levels:
66
- raise OAVError_ZoomLevelNotFound(
67
- f"Found {zoom_level_str} as a zoom level but expected one of {oav.zoom_controller.allowed_zoom_levels}"
68
- )
69
-
70
64
  yield from bps.abs_set(
71
- oav.zoom_controller,
65
+ oav.zoom_controller, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
72
66
  zoom_level_str,
73
67
  wait=True,
74
68
  )
@@ -20,6 +20,7 @@ from mx_bluesky.hyperion.log import LOGGER
20
20
  MM_TO_ENCODER_COUNTS = 200000
21
21
  GENERAL_TIMEOUT = 60
22
22
  TICKS_PER_MS = 1000 # Panda sequencer prescaler will be set to us
23
+ PULSE_WIDTH_US = 50
23
24
 
24
25
 
25
26
  class Enabled(Enum):
@@ -76,8 +77,6 @@ def _get_seq_table(
76
77
 
77
78
  delay_between_pulses = time_between_steps_ms * TICKS_PER_MS
78
79
 
79
- PULSE_WIDTH_US = 1
80
-
81
80
  assert delay_between_pulses > PULSE_WIDTH_US
82
81
 
83
82
  # BITA_1 trigger wired from TTLIN1, this is the trigger input
@@ -6,13 +6,15 @@ import bluesky.preprocessors as bpp
6
6
  from blueapi.core import MsgGenerator
7
7
  from dodal.devices.zebra import (
8
8
  AUTO_SHUTTER_GATE,
9
- AUTO_SHUTTER_INPUT,
9
+ AUTO_SHUTTER_INPUT_1,
10
+ AUTO_SHUTTER_INPUT_2,
10
11
  DISCONNECT,
11
12
  IN1_TTL,
12
13
  IN3_TTL,
13
14
  IN4_TTL,
14
15
  PC_GATE,
15
16
  PC_PULSE,
17
+ SOFT_IN1,
16
18
  TTL_DETECTOR,
17
19
  TTL_PANDA,
18
20
  TTL_XSPRESS3,
@@ -55,7 +57,7 @@ def bluesky_retry(func: Callable):
55
57
 
56
58
 
57
59
  def arm_zebra(zebra: Zebra):
58
- yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True)
60
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.ARM, wait=True) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
59
61
 
60
62
 
61
63
  def tidy_up_zebra_after_rotation_scan(
@@ -64,7 +66,7 @@ def tidy_up_zebra_after_rotation_scan(
64
66
  group="tidy_up_zebra_after_rotation",
65
67
  wait=True,
66
68
  ):
67
- yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group)
69
+ yield from bps.abs_set(zebra.pc.arm, ArmDemand.DISARM, group=group) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
68
70
  yield from bps.abs_set(
69
71
  zebra_shutter.control_mode, ZebraShutterControl.MANUAL, group=group
70
72
  )
@@ -73,15 +75,45 @@ def tidy_up_zebra_after_rotation_scan(
73
75
 
74
76
 
75
77
  def set_shutter_auto_input(zebra: Zebra, input: int, group="set_shutter_trigger"):
76
- """Set the input that the shutter uses when set to auto.
78
+ """Set the signal that controls the shutter. We use the second input to the
79
+ Zebra's AND2 gate for this input. ZebraShutter control mode must be in auto for this input to take control
77
80
 
78
81
  For more details see the ZebraShutter device."""
79
82
  auto_shutter_control = zebra.logic_gates.and_gates[AUTO_SHUTTER_GATE]
80
83
  yield from bps.abs_set(
81
- auto_shutter_control.sources[AUTO_SHUTTER_INPUT], input, group
84
+ auto_shutter_control.sources[AUTO_SHUTTER_INPUT_2], input, group
82
85
  )
83
86
 
84
87
 
88
+ def configure_zebra_and_shutter_for_auto_shutter(
89
+ zebra: Zebra, zebra_shutter: ZebraShutter, input: int, group="use_automatic_shutter"
90
+ ):
91
+ """Set the shutter to auto mode, and configure the zebra to trigger the shutter on
92
+ an input source. For the input, use one of the source constants in zebra.py
93
+
94
+ When the shutter is in auto/manual, logic in EPICS sets the Zebra's
95
+ SOFT_IN1 to low/high respectively. The Zebra's AND2 gate should be used to control the shutter while in auto mode.
96
+ 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.
97
+ """
98
+ # See https://github.com/DiamondLightSource/dodal/issues/813 for better typing here.
99
+
100
+ # Set shutter to auto mode
101
+ yield from bps.abs_set(
102
+ zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
103
+ )
104
+
105
+ # Set first input of AND2 gate to SOFT_IN1, which is high when shutter is in auto mode
106
+ # Note the Zebra should ALWAYS be setup this way. See https://github.com/DiamondLightSource/mx-bluesky/issues/551
107
+ yield from bps.abs_set(
108
+ zebra.logic_gates.and_gates[AUTO_SHUTTER_GATE].sources[AUTO_SHUTTER_INPUT_1],
109
+ SOFT_IN1,
110
+ group=group,
111
+ )
112
+
113
+ # Set the second input of AND2 gate to the requested zebra input source
114
+ yield from set_shutter_auto_input(zebra, input, group=group)
115
+
116
+
85
117
  @bluesky_retry
86
118
  def setup_zebra_for_rotation(
87
119
  zebra: Zebra,
@@ -137,11 +169,10 @@ def setup_zebra_for_rotation(
137
169
  yield from bps.abs_set(zebra.pc.pulse_start, abs(shutter_opening_s), group=group)
138
170
  # Set gate position to be angle of interest
139
171
  yield from bps.abs_set(zebra.pc.gate_trigger, axis.value, group=group)
140
- # Trigger the shutter with the gate
141
- yield from bps.abs_set(
142
- zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
172
+ # Set shutter to automatic and to trigger via PC_GATE
173
+ yield from configure_zebra_and_shutter_for_auto_shutter(
174
+ zebra, zebra_shutter, PC_GATE, group=group
143
175
  )
144
- yield from set_shutter_auto_input(zebra, PC_GATE, group=group)
145
176
  # Trigger the detector with a pulse
146
177
  yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], PC_PULSE, group=group)
147
178
  # Don't use the fluorescence detector
@@ -159,11 +190,12 @@ def setup_zebra_for_gridscan(
159
190
  group="setup_zebra_for_gridscan",
160
191
  wait=True,
161
192
  ):
162
- yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN3_TTL, group=group)
163
- yield from bps.abs_set(
164
- zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
193
+ # Set shutter to automatic and to trigger via motion controller GPIO signal (IN4_TTL)
194
+ yield from configure_zebra_and_shutter_for_auto_shutter(
195
+ zebra, zebra_shutter, IN4_TTL, group=group
165
196
  )
166
- yield from set_shutter_auto_input(zebra, IN4_TTL, group=group)
197
+
198
+ yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN3_TTL, group=group)
167
199
  yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
168
200
  yield from bps.abs_set(zebra.output.pulse_1.input, DISCONNECT, group=group)
169
201
 
@@ -198,11 +230,10 @@ def setup_zebra_for_panda_flyscan(
198
230
  # Forwards eiger trigger signal from panda
199
231
  yield from bps.abs_set(zebra.output.out_pvs[TTL_DETECTOR], IN1_TTL, group=group)
200
232
 
201
- # Forwards signal from PPMAC to fast shutter. High while panda PLC is running
202
- yield from bps.abs_set(
203
- zebra_shutter.control_mode, ZebraShutterControl.AUTO, group=group
233
+ # Set shutter to automatic and to trigger via motion controller GPIO signal (IN4_TTL)
234
+ yield from configure_zebra_and_shutter_for_auto_shutter(
235
+ zebra, zebra_shutter, IN4_TTL, group=group
204
236
  )
205
- yield from set_shutter_auto_input(zebra, IN4_TTL, group=group)
206
237
 
207
238
  yield from bps.abs_set(zebra.output.out_pvs[TTL_XSPRESS3], DISCONNECT, group=group)
208
239
 
@@ -16,10 +16,10 @@ def move_smargon_warn_on_out_of_range(
16
16
  "Pin tip centring failed - pin too long/short/bent and out of range"
17
17
  )
18
18
  yield from bps.mv(
19
- smargon.x,
20
- position[0],
21
- smargon.y,
22
- position[1],
23
- smargon.z,
24
- position[2],
19
+ smargon.x, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
20
+ position[0], # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
21
+ smargon.y, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
22
+ position[1], # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
23
+ smargon.z, # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
24
+ position[2], # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
25
25
  )
@@ -3,6 +3,10 @@ from collections.abc import Generator
3
3
  from bluesky import plan_stubs as bps
4
4
  from bluesky import preprocessors as bpp
5
5
  from bluesky.utils import Msg
6
+ from dodal.devices.dcm import DCM
7
+ from dodal.devices.detector import (
8
+ DetectorParams,
9
+ )
6
10
  from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
7
11
  from dodal.devices.eiger import EigerDetector
8
12
 
@@ -12,6 +16,13 @@ from mx_bluesky.hyperion.device_setup_plans.position_detector import (
12
16
  )
13
17
 
14
18
 
19
+ def fill_in_energy_if_not_supplied(dcm: DCM, detector_params: DetectorParams):
20
+ if not detector_params.expected_energy_ev:
21
+ actual_energy_ev = 1000 * (yield from bps.rd(dcm.energy_in_kev))
22
+ detector_params.expected_energy_ev = actual_energy_ev
23
+ return detector_params
24
+
25
+
15
26
  def start_preparing_data_collection_then_do_plan(
16
27
  eiger: EigerDetector,
17
28
  detector_motion: DetectorMotion,
@@ -30,7 +41,7 @@ def start_preparing_data_collection_then_do_plan(
30
41
  """
31
42
 
32
43
  def wrapped_plan():
33
- yield from bps.abs_set(eiger.do_arm, 1, group=group)
44
+ yield from bps.abs_set(eiger.do_arm, 1, group=group) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
34
45
  if detector_distance_mm:
35
46
  yield from set_detector_z_position(
36
47
  detector_motion, detector_distance_mm, group
@@ -40,5 +51,5 @@ def start_preparing_data_collection_then_do_plan(
40
51
 
41
52
  yield from bpp.contingency_wrapper(
42
53
  wrapped_plan(),
43
- except_plan=lambda e: (yield from bps.stop(eiger)),
54
+ except_plan=lambda e: (yield from bps.stop(eiger)), # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
44
55
  )
@@ -23,14 +23,14 @@ def _check_and_pause_feedback(
23
23
  turning XBPM feedback off.
24
24
 
25
25
  """
26
- yield from bps.mv(attenuator, 1.0)
26
+ yield from bps.mv(attenuator, 1.0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
27
27
  LOGGER.info("Waiting for XBPM feedback to be stable")
28
28
  yield from bps.trigger(xbpm_feedback, wait=True)
29
29
  LOGGER.info(
30
30
  f"XPBM feedback in position, pausing and setting transmission to {desired_transmission_fraction}"
31
31
  )
32
- yield from bps.mv(xbpm_feedback.pause_feedback, Pause.PAUSE)
33
- yield from bps.mv(attenuator, desired_transmission_fraction)
32
+ yield from bps.mv(xbpm_feedback.pause_feedback, Pause.PAUSE) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
33
+ yield from bps.mv(attenuator, desired_transmission_fraction) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
34
34
 
35
35
 
36
36
  def _unpause_xbpm_feedback_and_set_transmission_to_1(
@@ -44,7 +44,7 @@ def _unpause_xbpm_feedback_and_set_transmission_to_1(
44
44
  the beam in position
45
45
  attenuator (Attenuator): The attenuator used to set transmission
46
46
  """
47
- yield from bps.mv(xbpm_feedback.pause_feedback, Pause.RUN, attenuator, 1.0)
47
+ yield from bps.mv(xbpm_feedback.pause_feedback, Pause.RUN, attenuator, 1.0) # type: ignore # See: https://github.com/bluesky/bluesky/issues/1809
48
48
 
49
49
 
50
50
  def transmission_and_xbpm_feedback_for_collection_wrapper(