mx-bluesky 1.2.0__py3-none-any.whl → 1.4.1__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 (105) 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/redis_to_murko_forwarder.py +178 -0
  6. mx_bluesky/beamlines/i04/thawing_plan.py +49 -11
  7. mx_bluesky/beamlines/i24/serial/__init__.py +3 -0
  8. mx_bluesky/beamlines/i24/serial/dcid.py +143 -171
  9. mx_bluesky/beamlines/i24/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +1 -1
  10. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +121 -110
  11. mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +3 -6
  12. mx_bluesky/beamlines/i24/serial/fixed_target/ft_utils.py +0 -1
  13. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +164 -169
  14. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +149 -225
  15. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -216
  16. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +18 -17
  17. mx_bluesky/beamlines/i24/serial/log.py +58 -49
  18. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +4 -0
  19. mx_bluesky/beamlines/i24/serial/parameters/constants.py +6 -1
  20. mx_bluesky/beamlines/i24/serial/parameters/experiment_parameters.py +42 -15
  21. mx_bluesky/beamlines/i24/serial/parameters/fixed_target/cs/cs_maker.json +3 -3
  22. mx_bluesky/beamlines/i24/serial/run_extruder.sh +30 -5
  23. mx_bluesky/beamlines/i24/serial/run_fixed_target.sh +30 -5
  24. mx_bluesky/beamlines/i24/serial/run_serial.py +24 -8
  25. mx_bluesky/beamlines/i24/serial/setup_beamline/ca.py +0 -2
  26. mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +2 -0
  27. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_beamline.py +104 -82
  28. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_detector.py +9 -20
  29. mx_bluesky/beamlines/i24/serial/setup_beamline/setup_zebra_plans.py +26 -28
  30. mx_bluesky/beamlines/i24/serial/write_nexus.py +74 -72
  31. mx_bluesky/common/__init__.py +0 -0
  32. mx_bluesky/common/device_setup_plans/read_hardware_for_setup.py +14 -0
  33. mx_bluesky/common/external_interaction/config_server.py +46 -0
  34. mx_bluesky/common/parameters/components.py +258 -0
  35. mx_bluesky/common/parameters/constants.py +143 -0
  36. mx_bluesky/common/parameters/gridscan.py +94 -0
  37. mx_bluesky/common/parameters/robot_load.py +16 -0
  38. mx_bluesky/common/plans/__init__.py +1 -0
  39. mx_bluesky/common/plans/do_fgs.py +121 -0
  40. mx_bluesky/common/utils/log.py +118 -0
  41. mx_bluesky/{hyperion → common/utils}/tracing.py +2 -2
  42. mx_bluesky/hyperion/__main__.py +13 -10
  43. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +47 -52
  44. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +6 -12
  45. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +6 -12
  46. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +5 -6
  47. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +49 -18
  48. mx_bluesky/hyperion/device_setup_plans/smargon.py +9 -9
  49. mx_bluesky/hyperion/device_setup_plans/utils.py +2 -2
  50. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +4 -4
  51. mx_bluesky/hyperion/exceptions.py +13 -1
  52. mx_bluesky/hyperion/experiment_plans/__init__.py +4 -0
  53. mx_bluesky/hyperion/experiment_plans/change_aperture_then_move_plan.py +83 -0
  54. mx_bluesky/hyperion/experiment_plans/common/xrc_result.py +47 -0
  55. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +9 -9
  56. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +147 -169
  57. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +48 -22
  58. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +75 -9
  59. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +21 -20
  60. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +9 -6
  61. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +2 -2
  62. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +40 -21
  63. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +22 -22
  64. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +43 -39
  65. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +69 -18
  66. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +17 -7
  67. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +13 -13
  68. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +0 -4
  69. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +5 -2
  70. mx_bluesky/hyperion/external_interaction/callbacks/common/abstract_event.py +66 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +5 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +1 -1
  73. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +30 -25
  74. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +29 -12
  75. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +1 -1
  76. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +19 -11
  77. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +7 -4
  78. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +5 -3
  79. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/__init__.py +0 -0
  80. mx_bluesky/hyperion/external_interaction/callbacks/sample_handling/sample_handling_callback.py +84 -0
  81. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +38 -27
  82. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +5 -4
  83. mx_bluesky/hyperion/external_interaction/config_server.py +11 -28
  84. mx_bluesky/hyperion/external_interaction/exceptions.py +0 -9
  85. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +65 -15
  86. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +1 -1
  87. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +2 -2
  88. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +1 -1
  89. mx_bluesky/hyperion/log.py +0 -84
  90. mx_bluesky/hyperion/parameters/components.py +4 -251
  91. mx_bluesky/hyperion/parameters/constants.py +22 -119
  92. mx_bluesky/hyperion/parameters/gridscan.py +35 -74
  93. mx_bluesky/hyperion/parameters/load_centre_collect.py +16 -11
  94. mx_bluesky/hyperion/parameters/rotation.py +23 -10
  95. mx_bluesky/hyperion/utils/utils.py +17 -0
  96. mx_bluesky/hyperion/utils/validation.py +5 -6
  97. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/METADATA +36 -33
  98. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/RECORD +102 -89
  99. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/WHEEL +1 -1
  100. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +0 -161
  101. mx_bluesky/example.py +0 -19
  102. mx_bluesky/hyperion/parameters/robot_load.py +0 -16
  103. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/LICENSE +0 -0
  104. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/entry_points.txt +0 -0
  105. {mx_bluesky-1.2.0.dist-info → mx_bluesky-1.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,118 @@
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 = logging.getLogger("mx-bluesky")
16
+
17
+ __logger_handlers: DodalLogHandlers | None = None
18
+
19
+
20
+ class ExperimentMetadataTagFilter(logging.Filter):
21
+ """When an instance of this custom filter is added to a logging handler, dc_group_id
22
+ and run_id will be tagged in that handlers' log messages."""
23
+
24
+ dc_group_id: str | None = None
25
+ run_uid: str | None = None
26
+
27
+ def filter(self, record):
28
+ if self.dc_group_id:
29
+ record.dc_group_id = self.dc_group_id
30
+ if self.run_uid:
31
+ record.run_uid = self.run_uid
32
+ return True
33
+
34
+
35
+ tag_filter = ExperimentMetadataTagFilter()
36
+
37
+
38
+ def set_dcgid_tag(dcgid):
39
+ """Set the datacollection group id as a tag on all subsequent log messages.
40
+ Setting to None will remove the tag."""
41
+ tag_filter.dc_group_id = dcgid
42
+
43
+
44
+ def set_uid_tag(uid):
45
+ """Set the unique id as a tag on all subsequent log messages.
46
+ Setting to None will remove the tag."""
47
+ tag_filter.run_uid = uid
48
+
49
+
50
+ def do_default_logging_setup(
51
+ file_name: str,
52
+ graylog_port: int,
53
+ dev_mode: bool = False,
54
+ integrate_all_logs: bool = True,
55
+ ):
56
+ """Configures dodal logger so that separate debug and info log files are created,
57
+ info logs are sent to Graylog, info logs are streamed to sys.sterr, and logs from ophyd
58
+ and bluesky and ophyd-async are optionally included."""
59
+
60
+ handlers = set_up_all_logging_handlers(
61
+ dodal_logger,
62
+ _get_logging_dir(),
63
+ file_name,
64
+ dev_mode,
65
+ ERROR_LOG_BUFFER_LINES,
66
+ graylog_port,
67
+ )
68
+
69
+ if integrate_all_logs:
70
+ integrate_bluesky_and_ophyd_logging(dodal_logger)
71
+
72
+ handlers["graylog_handler"].addFilter(tag_filter)
73
+
74
+ global __logger_handlers
75
+ __logger_handlers = handlers
76
+
77
+
78
+ def _get_debug_handler() -> CircularMemoryHandler:
79
+ assert (
80
+ __logger_handlers is not None
81
+ ), "You can only use this after running the default logging setup"
82
+ return __logger_handlers["debug_memory_handler"]
83
+
84
+
85
+ def flush_debug_handler() -> str:
86
+ """Writes the contents of the circular debug log buffer to disk and returns the written filename"""
87
+ handler = _get_debug_handler()
88
+ assert isinstance(
89
+ handler.target, TimedRotatingFileHandler
90
+ ), "Circular memory handler doesn't have an appropriate fileHandler target"
91
+ handler.flush()
92
+ return handler.target.baseFilename
93
+
94
+
95
+ def _get_logging_dir() -> Path:
96
+ """Get the path to write the mx_bluesky log files to.
97
+
98
+ Log location can be specified in the LOG_DIR environment variable, otherwise MX bluesky logs are written to 'dls_sw/ixx/logs/bluesky'.
99
+ This directory will be created if it is not found
100
+
101
+ Logs are written to ./tmp/logs/bluesky if BEAMLINE environment variable is not found
102
+
103
+ Returns:
104
+ logging_path (Path): Path to the log file for the file handler to write to.
105
+ """
106
+
107
+ logging_str = environ.get("LOG_DIR")
108
+ if logging_str:
109
+ logging_path = Path(logging_str)
110
+ else:
111
+ beamline = environ.get("BEAMLINE")
112
+ logging_path = (
113
+ Path(f"/dls_sw/{beamline}/logs/bluesky/")
114
+ if beamline
115
+ else Path("/tmp/logs/bluesky")
116
+ )
117
+ Path.mkdir(logging_path, exist_ok=True, parents=True)
118
+ 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(
@@ -7,13 +7,18 @@ from queue import Queue
7
7
  from traceback import format_exception
8
8
  from typing import Any
9
9
 
10
- from blueapi.core import BlueskyContext, MsgGenerator
10
+ from blueapi.core import BlueskyContext
11
11
  from bluesky.callbacks.zmq import Publisher
12
12
  from bluesky.run_engine import RunEngine
13
+ from bluesky.utils import MsgGenerator
13
14
  from flask import Flask, request
14
15
  from flask_restful import Api, Resource
15
16
  from pydantic.dataclasses import dataclass
16
17
 
18
+ from mx_bluesky.common.parameters.components import MxBlueskyParameters
19
+ from mx_bluesky.common.parameters.constants import Actions, Status
20
+ from mx_bluesky.common.utils.log import do_default_logging_setup, flush_debug_handler
21
+ from mx_bluesky.common.utils.tracing import TRACER
17
22
  from mx_bluesky.hyperion.exceptions import WarningException
18
23
  from mx_bluesky.hyperion.experiment_plans.experiment_registry import (
19
24
  PLAN_REGISTRY,
@@ -36,13 +41,9 @@ from mx_bluesky.hyperion.external_interaction.callbacks.logging_callback import
36
41
  )
37
42
  from mx_bluesky.hyperion.log import (
38
43
  LOGGER,
39
- do_default_logging_setup,
40
- flush_debug_handler,
41
44
  )
42
45
  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
46
+ from mx_bluesky.hyperion.parameters.constants import CONST
46
47
  from mx_bluesky.hyperion.utils.context import setup_context
47
48
 
48
49
  VERBOSE_EVENT_LOGGING: bool | None = None
@@ -53,7 +54,7 @@ class Command:
53
54
  action: Actions
54
55
  devices: Any | None = None
55
56
  experiment: Callable[[Any, Any], MsgGenerator] | None = None
56
- parameters: HyperionParameters | None = None
57
+ parameters: MxBlueskyParameters | None = None
57
58
  callbacks: CallbacksFactory | None = None
58
59
 
59
60
 
@@ -119,7 +120,7 @@ class BlueskyRunner:
119
120
  def start(
120
121
  self,
121
122
  experiment: Callable,
122
- parameters: HyperionParameters,
123
+ parameters: MxBlueskyParameters,
123
124
  plan_name: str,
124
125
  callbacks: CallbacksFactory | None,
125
126
  ) -> StatusAndMessage:
@@ -239,7 +240,7 @@ def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions)
239
240
  raise ValueError(f"Extra fields not allowed {parameters.model_extra}")
240
241
  except Exception as e:
241
242
  raise ValueError(
242
- f"Supplied parameters don't match the plan for this endpoint {request.data}"
243
+ f"Supplied parameters don't match the plan for this endpoint {request.data}, for plan {plan_name}"
243
244
  ) from e
244
245
  return plan, parameters, plan_name, callback_type
245
246
 
@@ -345,7 +346,9 @@ def create_app(
345
346
  def create_targets():
346
347
  hyperion_port = 5005
347
348
  args = parse_cli_args()
348
- do_default_logging_setup(dev_mode=args.dev_mode)
349
+ do_default_logging_setup(
350
+ CONST.LOG_FILE_NAME, CONST.GRAYLOG_PORT, dev_mode=args.dev_mode
351
+ )
349
352
  if not args.use_external_callbacks:
350
353
  setup_callback_logging(args.dev_mode)
351
354
  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,79 +13,96 @@ 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"
22
+ YAW_LAT_TIMEOUT_S = 30
19
23
 
20
24
 
21
25
  def _apply_and_wait_for_voltages_to_settle(
22
26
  stripe: MirrorStripe,
23
- mirror: FocusingMirrorWithStripes,
24
- mirror_voltages: VFMMirrorVoltages,
27
+ mirror_voltages: MirrorVoltages,
25
28
  ):
26
29
  with open(mirror_voltages.voltage_lookup_table_path) as lut_file:
27
30
  json_obj = json.load(lut_file)
28
31
 
29
32
  # sample mode is the only mode supported
30
33
  sample_data = json_obj["sample"]
31
- mirror_key = mirror.name.lower()
32
34
  if stripe == MirrorStripe.BARE:
33
35
  stripe_key = "bare"
34
36
  elif stripe == MirrorStripe.RHODIUM:
35
37
  stripe_key = "rh"
36
38
  elif stripe == MirrorStripe.PLATINUM:
37
39
  stripe_key = "pt"
38
- else:
39
- raise ValueError(f"Unsupported stripe '{stripe}'")
40
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
- )
41
+ for mirror_key, channels in {
42
+ "hfm": mirror_voltages.horizontal_voltages,
43
+ "vfm": mirror_voltages.vertical_voltages,
44
+ }.items():
45
+ required_voltages = sample_data[stripe_key][mirror_key]
51
46
 
52
- yield from bps.wait(group=MIRROR_VOLTAGE_GROUP)
47
+ for voltage_channel, required_voltage in zip(
48
+ channels.values(), required_voltages, strict=True
49
+ ):
50
+ LOGGER.info(
51
+ f"Applying and waiting for voltage {voltage_channel.name} = {required_voltage}"
52
+ )
53
+ yield from bps.abs_set(
54
+ voltage_channel, required_voltage, group=MIRROR_VOLTAGE_GROUP, wait=True
55
+ )
53
56
 
54
57
 
55
58
  def adjust_mirror_stripe(
56
- energy_kev, mirror: FocusingMirrorWithStripes, mirror_voltages: VFMMirrorVoltages
59
+ energy_kev, mirror: FocusingMirrorWithStripes, mirror_voltages: MirrorVoltages
57
60
  ):
58
61
  """Feedback should be OFF prior to entry, in order to prevent
59
62
  feedback from making unnecessary corrections while beam is being adjusted."""
60
- stripe = mirror.energy_to_stripe(energy_kev)
63
+ mirror_config = mirror.energy_to_stripe(energy_kev)
61
64
 
62
65
  LOGGER.info(
63
- f"Adjusting mirror stripe for {energy_kev}keV selecting {stripe} stripe"
66
+ f"Adjusting mirror stripe for {energy_kev}keV selecting {mirror_config['stripe']} stripe"
64
67
  )
65
- yield from bps.abs_set(mirror.stripe, stripe, wait=True)
68
+ yield from bps.abs_set(mirror.stripe, mirror_config["stripe"], wait=True)
66
69
  yield from bps.trigger(mirror.apply_stripe)
67
70
 
71
+ # yaw, lat cannot be done simultaneously
72
+ LOGGER.info(f"Adjusting {mirror.name} lat to {mirror_config['lat_mm']}")
73
+ yield from bps.abs_set(
74
+ mirror.x_mm, mirror_config["lat_mm"], wait=True, timeout=YAW_LAT_TIMEOUT_S
75
+ )
76
+
77
+ LOGGER.info(f"Adjusting {mirror.name} yaw to {mirror_config['yaw_mrad']}")
78
+ yield from bps.abs_set(
79
+ mirror.yaw_mrad, mirror_config["yaw_mrad"], wait=True, timeout=YAW_LAT_TIMEOUT_S
80
+ )
81
+
68
82
  LOGGER.info("Adjusting mirror voltages...")
69
- yield from _apply_and_wait_for_voltages_to_settle(stripe, mirror, mirror_voltages)
83
+ yield from _apply_and_wait_for_voltages_to_settle(
84
+ mirror_config["stripe"], mirror_voltages
85
+ )
70
86
 
71
87
 
72
88
  def adjust_dcm_pitch_roll_vfm_from_lut(
73
89
  undulator_dcm: UndulatorDCM,
74
90
  vfm: FocusingMirrorWithStripes,
75
- vfm_mirror_voltages: VFMMirrorVoltages,
91
+ mirror_voltages: MirrorVoltages,
76
92
  energy_kev,
77
93
  ):
78
94
  """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.
95
+ Lookups are performed against the Bragg angle which is computed directly from the target energy
96
+ rather than waiting for the EPICS controls PV to reach it.
81
97
  Feedback should be OFF prior to entry, in order to prevent
82
98
  feedback from making unnecessary corrections while beam is being adjusted."""
83
99
 
84
- # DCM Pitch
100
+ # Adjust DCM Pitch
85
101
  dcm = undulator_dcm.dcm
86
102
  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")
103
+ d_spacing_a: float = yield from bps.rd(undulator_dcm.dcm.crystal_metadata_d_spacing)
104
+ bragg_deg = energy_to_bragg_angle(energy_kev, d_spacing_a)
105
+ LOGGER.info(f"Target Bragg angle = {bragg_deg} degrees")
89
106
  dcm_pitch_adjuster = lookup_table_adjuster(
90
107
  linear_interpolation_lut(undulator_dcm.pitch_energy_table_path),
91
108
  dcm.pitch_in_mrad,
@@ -104,31 +121,9 @@ def adjust_dcm_pitch_roll_vfm_from_lut(
104
121
  yield from dcm_roll_adjuster(DCM_GROUP)
105
122
  LOGGER.info("Waiting for DCM roll adjust to complete...")
106
123
 
107
- # DCM Perp pitch
108
- offset_mm = undulator_dcm.dcm_fixed_offset_mm
109
- LOGGER.info(f"Adjusting DCM offset to {offset_mm} mm")
110
- yield from bps.abs_set(dcm.offset_in_mm, offset_mm, group=DCM_GROUP)
111
-
112
124
  #
113
- # Adjust mirrors
125
+ # Adjust vfm mirror stripe and mirror voltages
114
126
  #
115
127
 
116
- # No need to change HFM
117
-
118
- # Assumption is focus mode is already set to "sample"
119
- # not sure how we check this
120
-
121
128
  # VFM Stripe selection
122
- yield from adjust_mirror_stripe(energy_kev, vfm, vfm_mirror_voltages)
123
- yield from bps.wait(DCM_GROUP)
124
-
125
- # VFM Adjust - for I03 this table always returns the same value
126
- vfm_lut = vfm.bragg_to_lat_lookup_table_path
127
- assert vfm_lut is not None
128
- vfm_x_adjuster = lookup_table_adjuster(
129
- linear_interpolation_lut(vfm_lut),
130
- vfm.x_mm,
131
- bragg_deg,
132
- )
133
- LOGGER.info("Waiting for VFM Lat (Horizontal Translation) to complete...")
134
- yield from vfm_x_adjuster()
129
+ yield from adjust_mirror_stripe(energy_kev, vfm, mirror_voltages)
@@ -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
  )
@@ -4,7 +4,7 @@ from importlib import resources
4
4
  from pathlib import Path
5
5
 
6
6
  import bluesky.plan_stubs as bps
7
- from blueapi.core import MsgGenerator
7
+ from bluesky.utils import MsgGenerator
8
8
  from dodal.common.beamlines.beamline_utils import get_path_provider
9
9
  from dodal.devices.fast_grid_scan import PandAGridScanParams
10
10
  from ophyd_async.core import load_device
@@ -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):
@@ -62,12 +63,12 @@ def _get_seq_table(
62
63
  An instance of SeqTable describing the panda sequencer table
63
64
  """
64
65
 
65
- start_of_grid_x_counts = int(parameters.x_start * MM_TO_ENCODER_COUNTS)
66
+ start_of_grid_x_counts = int(parameters.x_start_mm * MM_TO_ENCODER_COUNTS)
66
67
 
67
68
  # x_start is the first trigger point, so we need to travel to x_steps-1 for the final trigger point
68
69
  end_of_grid_x_counts = int(
69
70
  start_of_grid_x_counts
70
- + (parameters.x_step_size * (parameters.x_steps - 1) * MM_TO_ENCODER_COUNTS)
71
+ + (parameters.x_step_size_mm * (parameters.x_steps - 1) * MM_TO_ENCODER_COUNTS)
71
72
  )
72
73
 
73
74
  exposure_distance_x_counts = int(exposure_distance_mm * MM_TO_ENCODER_COUNTS)
@@ -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
@@ -141,7 +140,7 @@ def setup_panda_for_flyscan(
141
140
  """
142
141
  assert parameters.x_steps > 0
143
142
  assert time_between_x_steps_ms * 1000 >= exposure_time_s
144
- assert sample_velocity_mm_per_s * exposure_time_s < parameters.x_step_size
143
+ assert sample_velocity_mm_per_s * exposure_time_s < parameters.x_step_size_mm
145
144
 
146
145
  yield from bps.stage(panda, group="panda-config")
147
146
 
@@ -3,16 +3,18 @@ from functools import wraps
3
3
 
4
4
  import bluesky.plan_stubs as bps
5
5
  import bluesky.preprocessors as bpp
6
- from blueapi.core import MsgGenerator
6
+ from bluesky.utils 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
 
@@ -2,24 +2,24 @@ import numpy as np
2
2
  from bluesky import plan_stubs as bps
3
3
  from dodal.devices.smargon import Smargon
4
4
 
5
- from mx_bluesky.hyperion.exceptions import WarningException
5
+ from mx_bluesky.hyperion.exceptions import SampleException
6
6
 
7
7
 
8
8
  def move_smargon_warn_on_out_of_range(
9
9
  smargon: Smargon, position: np.ndarray | list[float] | tuple[float, float, float]
10
10
  ):
11
- """Throws a WarningException if the specified position is out of range for the
11
+ """Throws a SampleException if the specified position is out of range for the
12
12
  smargon. Otherwise moves to that position."""
13
13
  limits = yield from smargon.get_xyz_limits()
14
14
  if not limits.position_valid(position):
15
- raise WarningException(
15
+ raise SampleException(
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
  )