mx-bluesky 1.5.14__py3-none-any.whl → 1.5.16__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 (74) hide show
  1. mx_bluesky/Getting started.ipynb +1 -0
  2. mx_bluesky/_version.py +2 -2
  3. mx_bluesky/beamlines/i04/callbacks/murko_callback.py +18 -0
  4. mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +6 -7
  5. mx_bluesky/beamlines/i04/thawing_plan.py +1 -0
  6. mx_bluesky/beamlines/i24/jungfrau_commissioning/__init__.py +13 -0
  7. mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/__init__.py +0 -0
  8. mx_bluesky/beamlines/i24/jungfrau_commissioning/callbacks/metadata_writer.py +86 -0
  9. mx_bluesky/beamlines/i24/jungfrau_commissioning/composites.py +35 -0
  10. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/do_darks.py +18 -19
  11. mx_bluesky/beamlines/i24/jungfrau_commissioning/experiment_plans/rotation_scan_plan.py +292 -0
  12. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_external_acquisition.py +3 -8
  13. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/do_internal_acquisition.py +4 -5
  14. mx_bluesky/beamlines/i24/jungfrau_commissioning/plan_stubs/plan_utils.py +14 -18
  15. mx_bluesky/beamlines/i24/parameters/__init__.py +0 -0
  16. mx_bluesky/beamlines/i24/parameters/constants.py +9 -0
  17. mx_bluesky/beamlines/i24/serial/extruder/i24ssx_extruder_collect_py3v2.py +1 -1
  18. mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +2 -2
  19. mx_bluesky/beamlines/i24/serial/web_gui_plans/oav_plans.py +21 -0
  20. mx_bluesky/common/device_setup_plans/robot_load_unload.py +2 -24
  21. mx_bluesky/common/device_setup_plans/setup_zebra_and_shutter.py +5 -2
  22. mx_bluesky/common/experiment_plans/common_flyscan_xray_centre_plan.py +1 -1
  23. mx_bluesky/common/experiment_plans/inner_plans/read_hardware.py +2 -0
  24. mx_bluesky/common/experiment_plans/rotation/__init__.py +0 -0
  25. mx_bluesky/common/experiment_plans/rotation/rotation_utils.py +127 -0
  26. mx_bluesky/common/external_interaction/callbacks/common/ispyb_callback_base.py +14 -2
  27. mx_bluesky/common/external_interaction/callbacks/common/ispyb_mapping.py +1 -2
  28. mx_bluesky/common/external_interaction/ispyb/data_model.py +4 -1
  29. mx_bluesky/common/external_interaction/ispyb/exp_eye_store.py +3 -1
  30. mx_bluesky/common/parameters/components.py +17 -7
  31. mx_bluesky/common/parameters/constants.py +6 -0
  32. mx_bluesky/{hyperion → common}/parameters/rotation.py +10 -8
  33. mx_bluesky/common/preprocessors/preprocessors.py +98 -36
  34. mx_bluesky/hyperion/__main__.py +55 -22
  35. mx_bluesky/hyperion/baton_handler.py +24 -64
  36. mx_bluesky/hyperion/blueapi_config.yaml +17 -0
  37. mx_bluesky/hyperion/blueapi_dev_config.yaml +16 -0
  38. mx_bluesky/hyperion/blueapi_plans/__init__.py +96 -0
  39. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +8 -6
  40. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +1 -1
  41. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +3 -1
  42. mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +4 -5
  43. mx_bluesky/hyperion/experiment_plans/hyperion_grid_detect_then_xray_centre_plan.py +2 -2
  44. mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +3 -1
  45. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +17 -6
  46. mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +0 -3
  47. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +12 -126
  48. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +2 -2
  49. mx_bluesky/hyperion/experiment_plans/udc_default_state.py +8 -2
  50. mx_bluesky/hyperion/external_interaction/agamemnon.py +3 -8
  51. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +121 -47
  52. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +3 -1
  53. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +3 -1
  54. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +6 -3
  55. mx_bluesky/hyperion/external_interaction/callbacks/stomp/__init__.py +0 -0
  56. mx_bluesky/hyperion/external_interaction/callbacks/stomp/dispatcher.py +33 -0
  57. mx_bluesky/hyperion/in_process_runner.py +132 -0
  58. mx_bluesky/hyperion/parameters/cli.py +43 -4
  59. mx_bluesky/hyperion/parameters/components.py +13 -0
  60. mx_bluesky/hyperion/parameters/constants.py +2 -9
  61. mx_bluesky/hyperion/parameters/load_centre_collect.py +3 -1
  62. mx_bluesky/hyperion/plan_runner.py +45 -66
  63. mx_bluesky/hyperion/plan_runner_api.py +3 -4
  64. mx_bluesky/hyperion/supervisor/__init__.py +3 -0
  65. mx_bluesky/hyperion/supervisor/_supervisor.py +116 -0
  66. mx_bluesky/hyperion/supervisor/client_config.yaml +6 -0
  67. mx_bluesky/hyperion/supervisor/supervisor_config.yaml +10 -0
  68. mx_bluesky/hyperion/supervisor/supervisor_dev_config.yaml +9 -0
  69. {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/METADATA +3 -31
  70. {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/RECORD +74 -54
  71. {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/WHEEL +0 -0
  72. {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/entry_points.txt +0 -0
  73. {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/licenses/LICENSE +0 -0
  74. {mx_bluesky-1.5.14.dist-info → mx_bluesky-1.5.16.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,6 @@
1
+ from collections.abc import Callable
2
+
3
+ import bluesky.plan_stubs as bps
1
4
  from bluesky import preprocessors as bpp
2
5
  from bluesky.preprocessors import plan_mutator
3
6
  from bluesky.utils import Msg, MsgGenerator, make_decorator
@@ -12,15 +15,61 @@ from mx_bluesky.common.protocols.protocols import (
12
15
  )
13
16
 
14
17
 
15
- def transmission_and_xbpm_feedback_for_collection_wrapper(
18
+ def _create_insert_plans_mutator(
19
+ run_key_to_wrap: PlanNameConstants | None = None,
20
+ on_open: Callable[[Msg], MsgGenerator] | None = None,
21
+ on_close: Callable[[], MsgGenerator] | None = None,
22
+ ) -> Callable[[Msg], tuple[MsgGenerator | None, MsgGenerator | None]]:
23
+ """
24
+ Inserts plans to be executed when the run with the given name opens/closes.
25
+
26
+ Args:
27
+ run_key_to_wrap: The run name to insert before/after. If None (default) then
28
+ insert on the first open/close run regardless of its name.
29
+ on_open: The plan to perform just before the run opens
30
+ on_close: The plan to perform just after the run closes
31
+
32
+ """
33
+ _wrapped_run_name: None | str = None
34
+
35
+ def insert_plans(msg: Msg):
36
+ # Wrap the specified run, or, if none specified, wrap the first run encountered
37
+ nonlocal _wrapped_run_name
38
+
39
+ match msg.command:
40
+ case "open_run":
41
+ # If we specified a run key, did we encounter it
42
+ # If we didn't specify, then insert the plans and track the name of the run
43
+ if on_open is not None and (
44
+ not (run_key_to_wrap or _wrapped_run_name)
45
+ or run_key_to_wrap is msg.run
46
+ ):
47
+ _wrapped_run_name = msg.run if msg.run else "unnamed_run"
48
+ return on_open(msg), None
49
+ case "close_run":
50
+ # Check if the run tracked from above was closed
51
+ # An exception is raised in the RunEngine if two unnamed runs are opened
52
+ # at the same time, so we are safe from unpausing on the wrong run
53
+ if on_close is not None and (
54
+ (_wrapped_run_name == "unnamed_run" and not msg.run)
55
+ or (msg.run and _wrapped_run_name and _wrapped_run_name is msg.run)
56
+ ):
57
+ return None, on_close()
58
+
59
+ return None, None
60
+
61
+ return insert_plans
62
+
63
+
64
+ def pause_xbpm_feedback_during_collection_at_desired_transmission_wrapper(
16
65
  plan: MsgGenerator,
17
66
  devices: XBPMPauseDevices,
18
67
  desired_transmission_fraction: float,
19
68
  run_key_to_wrap: PlanNameConstants | None = None,
20
69
  ):
21
70
  """
22
- Sets the transmission for the data collection, ensuring the xbpm feedback is valid, then resets it immediately after
23
- the run has finished.
71
+ Sets the transmission for the data collection, ensuring the xbpm feedback is valid, then pauses
72
+ XBPM feedback. Resets transmission and unpauses XBPM immediately after the run has finished.
24
73
 
25
74
  This wrapper should be attached to the entry point of any beamline-specific plan that may disrupt the XBPM feedback,
26
75
  such as a data collection or an x-ray center grid scan.
@@ -38,15 +87,13 @@ def transmission_and_xbpm_feedback_for_collection_wrapper(
38
87
  Args:
39
88
  plan: The plan performing the data collection.
40
89
  devices (XBPMPauseDevices): Composite device including The XBPM device that is responsible for keeping
41
- the beam in position, and attenuator
90
+ the beam in position, and attenuator
42
91
  desired_transmission_fraction (float): The desired transmission for the collection
43
- run_key_to_wrap: (str | None): Pausing XBPM and setting transmission is inserted after the 'open_run' message is seen with
44
- the matching run key, and unpausing and resetting transmission is inserted after the corresponding 'close_run' message is
45
- seen. If not specified, instead wrap the first run encountered.
92
+ run_key_to_wrap: (str | None): Pausing XBPM and setting transmission is inserted before the 'open_run' message is seen with
93
+ the matching run key, and unpausing and resetting transmission is inserted after the corresponding 'close_run' message is
94
+ seen. If not specified, instead wrap the first run encountered.
46
95
  """
47
96
 
48
- _wrapped_run_name: None | str = None
49
-
50
97
  def head(msg: Msg):
51
98
  yield from check_and_pause_feedback(
52
99
  devices.xbpm_feedback,
@@ -62,36 +109,13 @@ def transmission_and_xbpm_feedback_for_collection_wrapper(
62
109
  devices.xbpm_feedback, devices.attenuator
63
110
  )
64
111
 
65
- def insert_plans(msg: Msg):
66
- # Wrap the specified run, or, if none specified, wrap the first run encountered
67
- nonlocal _wrapped_run_name
68
-
69
- match msg.command:
70
- case "open_run":
71
- # If we specified a run key, did we encounter it
72
- # If we didn't specify, then insert the plans and track the name of the run
73
- if (
74
- not (run_key_to_wrap or _wrapped_run_name)
75
- or run_key_to_wrap is msg.run
76
- ):
77
- _wrapped_run_name = msg.run if msg.run else "unnamed_run"
78
- return head(msg), None
79
- case "close_run":
80
- # Check if the run tracked from above was closed
81
- # An exception is raised in the RunEngine if two unnamed runs are opened
82
- # at the same time, so we are safe from unpausing on the wrong run
83
- if (_wrapped_run_name == "unnamed_run" and not msg.run) or (
84
- msg.run and _wrapped_run_name and _wrapped_run_name is msg.run
85
- ):
86
- return None, tail()
87
-
88
- return None, None
112
+ mutator = _create_insert_plans_mutator(run_key_to_wrap, head, tail)
89
113
 
90
114
  # Contingency wrapper can cause unpausing to occur on exception and again on close_run.
91
115
  # Not needed after https://github.com/bluesky/bluesky/issues/1891
92
116
  return (
93
117
  yield from bpp.contingency_wrapper(
94
- plan_mutator(plan, insert_plans),
118
+ plan_mutator(plan, mutator),
95
119
  except_plan=lambda _: unpause_xbpm_feedback_and_set_transmission_to_1(
96
120
  devices.xbpm_feedback,
97
121
  devices.attenuator,
@@ -100,6 +124,44 @@ def transmission_and_xbpm_feedback_for_collection_wrapper(
100
124
  )
101
125
 
102
126
 
103
- transmission_and_xbpm_feedback_for_collection_decorator = make_decorator(
104
- transmission_and_xbpm_feedback_for_collection_wrapper
127
+ def set_transmission_and_trigger_xbpm_feedback_before_collection_wrapper(
128
+ plan: MsgGenerator,
129
+ devices: XBPMPauseDevices,
130
+ desired_transmission_fraction: float,
131
+ run_key_to_wrap: PlanNameConstants | None = None,
132
+ ):
133
+ """
134
+ Sets the transmission and triggers xbpm feedback immediately before
135
+ doing the specified run. Doesn't revert transmission at the end of the plan.
136
+
137
+ Args:
138
+ plan: The plan performing the data collection.
139
+ devices (XBPMPauseDevices): Composite device including The XBPM device that is responsible for keeping
140
+ the beam in position, and the attenuator
141
+ desired_transmission_fraction (float): The desired transmission for the collection
142
+ run_key_to_wrap: (str | None): 'Set transmission' and 'trigger XBPM device' is inserted before the 'open_run' message with
143
+ the matching run key is seen. If not specified, instead wrap the first run encountered.
144
+ """
145
+
146
+ def head(msg: Msg):
147
+ yield from bps.mv(devices.attenuator, desired_transmission_fraction)
148
+ yield from bps.trigger(devices.xbpm_feedback)
149
+
150
+ # Allow 'open_run' message to pass through
151
+ yield msg
152
+
153
+ mutator = _create_insert_plans_mutator(run_key_to_wrap, head)
154
+
155
+ return (yield from plan_mutator(plan, mutator))
156
+
157
+
158
+ set_transmission_and_trigger_xbpm_feedback_before_collection_decorator = make_decorator(
159
+ set_transmission_and_trigger_xbpm_feedback_before_collection_wrapper
160
+ )
161
+
162
+
163
+ pause_xbpm_feedback_during_collection_at_desired_transmission_decorator = (
164
+ make_decorator(
165
+ pause_xbpm_feedback_during_collection_at_desired_transmission_wrapper
166
+ )
105
167
  )
@@ -2,9 +2,11 @@ import json
2
2
  import signal
3
3
  import threading
4
4
  from dataclasses import asdict
5
+ from pathlib import Path
5
6
  from sys import argv
6
7
  from traceback import format_exception
7
8
 
9
+ from blueapi.config import ApplicationConfig, ConfigLoader
8
10
  from blueapi.core import BlueskyContext
9
11
  from flask import Flask, request
10
12
  from flask_restful import Api, Resource
@@ -28,6 +30,7 @@ from mx_bluesky.hyperion.external_interaction.agamemnon import (
28
30
  compare_params,
29
31
  update_params_from_agamemnon,
30
32
  )
33
+ from mx_bluesky.hyperion.in_process_runner import InProcessRunner
31
34
  from mx_bluesky.hyperion.parameters.cli import (
32
35
  HyperionArgs,
33
36
  HyperionMode,
@@ -42,6 +45,7 @@ from mx_bluesky.hyperion.runner import (
42
45
  StatusAndMessage,
43
46
  make_error_status_and_message,
44
47
  )
48
+ from mx_bluesky.hyperion.supervisor import SupervisorRunner
45
49
  from mx_bluesky.hyperion.utils.context import setup_context
46
50
 
47
51
 
@@ -152,7 +156,11 @@ def create_app(runner: GDARunner, test_config=None) -> Flask:
152
156
  def initialise_globals(args: HyperionArgs):
153
157
  """Do all early main low-level application initialisation."""
154
158
  do_default_logging_setup(
155
- CONST.LOG_FILE_NAME, CONST.GRAYLOG_PORT, dev_mode=args.dev_mode
159
+ CONST.SUPERVISOR_LOG_FILE_NAME
160
+ if args.mode == HyperionMode.SUPERVISOR
161
+ else CONST.LOG_FILE_NAME,
162
+ CONST.GRAYLOG_PORT,
163
+ dev_mode=args.dev_mode,
156
164
  )
157
165
  LOGGER.info(f"Hyperion launched with args:{argv}")
158
166
  alerting.set_alerting_service(LoggingAlertService(CONST.GRAYLOG_STREAM_ID))
@@ -163,27 +171,52 @@ def main():
163
171
  args = parse_cli_args()
164
172
  initialise_globals(args)
165
173
  hyperion_port = HyperionConstants.HYPERION_PORT
166
- context = setup_context(dev_mode=args.dev_mode)
167
-
168
- if args.mode == HyperionMode.GDA:
169
- runner = GDARunner(context=context)
170
- app = create_app(runner)
171
- flask_thread = threading.Thread(
172
- target=lambda: app.run(
173
- host="0.0.0.0", port=hyperion_port, debug=True, use_reloader=False
174
- ),
175
- daemon=True,
176
- )
177
- flask_thread.start()
178
- LOGGER.info(
179
- f"Hyperion now listening on {hyperion_port} ({'IN DEV' if args.dev_mode else ''})"
180
- )
181
- runner.wait_on_queue()
182
- else:
183
- plan_runner = PlanRunner(context, args.dev_mode)
184
- create_server_for_udc(plan_runner)
185
- _register_sigterm_handler(plan_runner)
186
- run_forever(plan_runner)
174
+
175
+ match args.mode:
176
+ case HyperionMode.GDA:
177
+ context = setup_context(dev_mode=args.dev_mode)
178
+ runner = GDARunner(context=context)
179
+ app = create_app(runner)
180
+ flask_thread = threading.Thread(
181
+ target=lambda: app.run(
182
+ host="0.0.0.0", port=hyperion_port, debug=True, use_reloader=False
183
+ ),
184
+ daemon=True,
185
+ )
186
+ flask_thread.start()
187
+ LOGGER.info(
188
+ f"Hyperion now listening on {hyperion_port} ({'IN DEV' if args.dev_mode else ''})"
189
+ )
190
+ runner.wait_on_queue()
191
+ case HyperionMode.UDC:
192
+ context = setup_context(dev_mode=args.dev_mode)
193
+ plan_runner = InProcessRunner(context, args.dev_mode)
194
+ create_server_for_udc(plan_runner, HyperionConstants.HYPERION_PORT)
195
+ _register_sigterm_handler(plan_runner)
196
+ run_forever(plan_runner)
197
+ case HyperionMode.SUPERVISOR:
198
+ if not args.client_config:
199
+ raise RuntimeError(
200
+ "BlueAPI client configuration file must be specified in supervisor mode."
201
+ )
202
+ if not args.supervisor_config:
203
+ raise RuntimeError(
204
+ "BlueAPI supervisor configuration file must be specified in supervisor mode."
205
+ )
206
+
207
+ client_config = _load_config_from_yaml(Path(args.client_config))
208
+ supervisor_config = _load_config_from_yaml(Path(args.supervisor_config))
209
+ context = BlueskyContext(configuration=supervisor_config)
210
+ plan_runner = SupervisorRunner(context, client_config, args.dev_mode)
211
+ create_server_for_udc(plan_runner, HyperionConstants.SUPERVISOR_PORT)
212
+ _register_sigterm_handler(plan_runner)
213
+ run_forever(plan_runner)
214
+
215
+
216
+ def _load_config_from_yaml(config_path: Path):
217
+ loader = ConfigLoader(ApplicationConfig)
218
+ loader.use_values_from_yaml(config_path)
219
+ return loader.load()
187
220
 
188
221
 
189
222
  def _register_sigterm_handler(runner: PlanRunner):
@@ -1,45 +1,30 @@
1
1
  from collections.abc import Sequence
2
- from functools import partial
3
- from typing import Any
4
2
 
5
3
  from blueapi.core.context import BlueskyContext
6
4
  from bluesky import plan_stubs as bps
7
5
  from bluesky import preprocessors as bpp
8
6
  from bluesky.utils import MsgGenerator, RunEngineInterrupted
9
7
  from dodal.common.beamlines.commissioning_mode import set_commissioning_signal
10
- from dodal.devices.aperturescatterguard import ApertureScatterguard
11
8
  from dodal.devices.baton import Baton
12
- from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
13
- from dodal.devices.motors import XYZStage
14
- from dodal.devices.robot import BartRobot
15
- from dodal.devices.smargon import Smargon
16
9
 
17
- from mx_bluesky.common.device_setup_plans.robot_load_unload import robot_unload
18
10
  from mx_bluesky.common.external_interaction.alerting import (
19
11
  AlertService,
20
12
  get_alerting_service,
21
13
  )
22
- from mx_bluesky.common.parameters.components import MxBlueskyParameters
14
+ from mx_bluesky.common.parameters.components import (
15
+ MxBlueskyParameters,
16
+ get_param_version,
17
+ )
23
18
  from mx_bluesky.common.utils.context import (
24
- device_composite_from_context,
25
19
  find_device_in_context,
26
20
  )
27
21
  from mx_bluesky.common.utils.exceptions import BeamlineCheckFailureError
28
22
  from mx_bluesky.common.utils.log import LOGGER
29
- from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
30
- create_devices,
31
- load_centre_collect_full,
32
- )
33
- from mx_bluesky.hyperion.experiment_plans.udc_default_state import (
34
- UDCDefaultDevices,
35
- move_to_udc_default_state,
36
- )
37
23
  from mx_bluesky.hyperion.external_interaction.agamemnon import (
38
24
  create_parameters_from_agamemnon,
39
25
  )
40
26
  from mx_bluesky.hyperion.external_interaction.alerting.constants import Subjects
41
- from mx_bluesky.hyperion.parameters.components import Wait
42
- from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
27
+ from mx_bluesky.hyperion.parameters.components import UDCCleanup, UDCDefaultState
43
28
  from mx_bluesky.hyperion.plan_runner import PlanError, PlanRunner
44
29
  from mx_bluesky.hyperion.utils.context import (
45
30
  clear_all_device_caches,
@@ -102,7 +87,14 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
102
87
  """
103
88
  _raise_udc_start_alert(get_alerting_service())
104
89
  yield from bpp.contingency_wrapper(
105
- _move_to_udc_default_state(context),
90
+ runner.decode_and_execute(
91
+ None,
92
+ [
93
+ UDCDefaultState.model_validate(
94
+ {"parameter_model_version": get_param_version()}
95
+ )
96
+ ],
97
+ ),
106
98
  except_plan=trap_default_state_exception,
107
99
  auto_raise=False,
108
100
  )
@@ -115,7 +107,14 @@ def run_udc_when_requested(context: BlueskyContext, runner: PlanRunner):
115
107
  baton, runner, current_visit
116
108
  )
117
109
  if current_visit:
118
- yield from _clean_up_udc(runner.context, current_visit)
110
+ yield from runner.decode_and_execute(
111
+ current_visit,
112
+ [
113
+ UDCCleanup.model_validate(
114
+ {"parameter_model_version": get_param_version()}
115
+ )
116
+ ],
117
+ )
119
118
 
120
119
  def release_baton() -> MsgGenerator:
121
120
  # If hyperion has given up the baton itself we need to also release requested
@@ -177,23 +176,9 @@ def _fetch_and_process_agamemnon_instruction(
177
176
  ) -> MsgGenerator[str | None]:
178
177
  parameter_list: Sequence[MxBlueskyParameters] = create_parameters_from_agamemnon()
179
178
  if parameter_list:
180
- for parameters in parameter_list:
181
- LOGGER.info(
182
- f"Executing plan with parameters: {parameters.model_dump_json(indent=2)}"
183
- )
184
- match parameters:
185
- case LoadCentreCollect():
186
- current_visit = parameters.visit
187
- devices: Any = create_devices(runner.context)
188
- yield from runner.execute_plan(
189
- partial(load_centre_collect_full, devices, parameters)
190
- )
191
- case Wait():
192
- yield from runner.execute_plan(partial(_runner_sleep, parameters))
193
- case _:
194
- raise AssertionError(
195
- f"Unsupported instruction decoded from agamemnon {type(parameters)}"
196
- )
179
+ current_visit = yield from runner.decode_and_execute(
180
+ current_visit, parameter_list
181
+ )
197
182
  else:
198
183
  _raise_udc_completed_alert(get_alerting_service())
199
184
  # Release the baton for orderly exit from the instruction loop
@@ -224,20 +209,11 @@ def _raise_udc_completed_alert(alert_service: AlertService):
224
209
  )
225
210
 
226
211
 
227
- def _runner_sleep(parameters: Wait) -> MsgGenerator:
228
- yield from bps.sleep(parameters.duration_s)
229
-
230
-
231
212
  def _is_requesting_baton(baton: Baton) -> MsgGenerator:
232
213
  requested_user = yield from bps.rd(baton.requested_user)
233
214
  return requested_user == HYPERION_USER
234
215
 
235
216
 
236
- def _move_to_udc_default_state(context: BlueskyContext):
237
- udc_default_devices = device_composite_from_context(context, UDCDefaultDevices)
238
- yield from move_to_udc_default_state(udc_default_devices)
239
-
240
-
241
217
  def _get_baton(context: BlueskyContext) -> Baton:
242
218
  return find_device_in_context(context, "baton", Baton)
243
219
 
@@ -255,19 +231,3 @@ def _unrequest_baton(baton: Baton) -> MsgGenerator[str]:
255
231
  yield from bps.abs_set(baton.requested_user, NO_USER)
256
232
  return NO_USER
257
233
  return requested_user
258
-
259
-
260
- def _clean_up_udc(context: BlueskyContext, visit: str) -> MsgGenerator:
261
- cleanup_group = "cleanup"
262
- robot = find_device_in_context(context, "robot", BartRobot)
263
- smargon = find_device_in_context(context, "smargon", Smargon)
264
- aperture_scatterguard = find_device_in_context(
265
- context, "aperture_scatterguard", ApertureScatterguard
266
- )
267
- lower_gonio = find_device_in_context(context, "lower_gonio", XYZStage)
268
- detector_motion = find_device_in_context(context, "detector_motion", DetectorMotion)
269
- yield from bps.abs_set(
270
- detector_motion.shutter, ShutterState.CLOSED, group=cleanup_group
271
- )
272
- yield from robot_unload(robot, smargon, aperture_scatterguard, lower_gonio, visit)
273
- yield from bps.wait(cleanup_group)
@@ -0,0 +1,17 @@
1
+ env:
2
+ sources:
3
+ - kind: deviceManager
4
+ module: dodal.beamlines.i03
5
+ - kind: planFunctions
6
+ module: mx_bluesky.hyperion.blueapi_plans
7
+ events:
8
+ broadcast_status_events: false
9
+ api:
10
+ url: http://localhost:5005
11
+ stomp:
12
+ enabled: true
13
+ url: tcp://localhost:61613
14
+ logging:
15
+ graylog:
16
+ url: "tcp://graylog-log-target.diamond.ac.uk:12232"
17
+ enabled: true
@@ -0,0 +1,16 @@
1
+ env:
2
+ sources:
3
+ - kind: deviceManager
4
+ module: dodal.beamlines.i03
5
+ mock: true
6
+ - kind: planFunctions
7
+ module: mx_bluesky.hyperion.blueapi_plans
8
+ events:
9
+ broadcast_status_events: false
10
+ api:
11
+ url: http://localhost:5005
12
+ stomp:
13
+ enabled: true
14
+ url: tcp://localhost:61613
15
+ logging:
16
+ level: DEBUG
@@ -0,0 +1,96 @@
1
+ """
2
+ This module contains the bluesky plan entry points for use with hyperion-blueapi.
3
+ The json schema and documentation therein generated by the blueapi /plans endpoint
4
+ from this file constitutes the hyperion-blueapi interface to the hyperion supervisor
5
+ process.
6
+ """
7
+
8
+ from bluesky import plan_stubs as bps
9
+ from bluesky.utils import MsgGenerator
10
+ from dodal.common import inject
11
+ from dodal.devices.aperturescatterguard import ApertureScatterguard
12
+ from dodal.devices.detector.detector_motion import DetectorMotion, ShutterState
13
+ from dodal.devices.motors import XYZStage
14
+ from dodal.devices.robot import BartRobot
15
+ from dodal.devices.smargon import Smargon
16
+
17
+ from mx_bluesky.common.device_setup_plans.robot_load_unload import (
18
+ robot_unload as _robot_unload,
19
+ )
20
+ from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
21
+ LoadCentreCollectComposite,
22
+ )
23
+ from mx_bluesky.hyperion.experiment_plans.load_centre_collect_full_plan import (
24
+ load_centre_collect_full as _load_centre_collect_full,
25
+ )
26
+ from mx_bluesky.hyperion.experiment_plans.udc_default_state import (
27
+ UDCDefaultDevices,
28
+ )
29
+ from mx_bluesky.hyperion.experiment_plans.udc_default_state import (
30
+ move_to_udc_default_state as _move_to_udc_default_state,
31
+ )
32
+ from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
33
+
34
+ __all__ = [
35
+ "LoadCentreCollectComposite",
36
+ "LoadCentreCollect",
37
+ "UDCDefaultDevices",
38
+ "clean_up_udc",
39
+ "load_centre_collect",
40
+ "move_to_udc_default_state",
41
+ "robot_unload",
42
+ ]
43
+
44
+
45
+ def load_centre_collect(
46
+ parameters: LoadCentreCollect, composite: LoadCentreCollectComposite = inject()
47
+ ) -> MsgGenerator:
48
+ """
49
+ Attempt a complete data collection experiment, consisting of the following:
50
+ * Load the sample if necessary
51
+ * Move to the specified goniometer start angles
52
+ * Perform optical centring, then X-ray centring
53
+ * If X-ray centring finds one or more diffracting centres then for each centre
54
+ that satisfies the chosen selection function,
55
+ move to that centre and do a collection with the specified parameters.
56
+ """
57
+ yield from _load_centre_collect_full(composite, parameters)
58
+
59
+
60
+ def robot_unload(
61
+ visit: str,
62
+ robot: BartRobot = inject("robot"),
63
+ smargon: Smargon = inject("smargon"),
64
+ aperture_scatterguard: ApertureScatterguard = inject("aperture_scatterguard"),
65
+ lower_gonio: XYZStage = inject("lower_gonio"),
66
+ ) -> MsgGenerator:
67
+ """
68
+ Unload the currently mounted pin into the location that it was loaded from.
69
+ This is to be invoked as the final step upon successful completion of the UDC queue.
70
+ """
71
+ yield from _robot_unload(robot, smargon, aperture_scatterguard, lower_gonio, visit)
72
+
73
+
74
+ def clean_up_udc(
75
+ visit: str,
76
+ cleanup_group: str = "cleanup",
77
+ robot: BartRobot = inject("robot"),
78
+ smargon: Smargon = inject("smargon"),
79
+ aperture_scatterguard: ApertureScatterguard = inject("aperture_scatterguard"),
80
+ lower_gonio: XYZStage = inject("lower_gonio"),
81
+ detector_motion: DetectorMotion = inject("detector_motion"),
82
+ ) -> MsgGenerator:
83
+ yield from bps.abs_set(
84
+ detector_motion.shutter, ShutterState.CLOSED, group=cleanup_group
85
+ )
86
+ yield from _robot_unload(robot, smargon, aperture_scatterguard, lower_gonio, visit)
87
+ yield from bps.wait(cleanup_group)
88
+
89
+
90
+ def move_to_udc_default_state(
91
+ composite: UDCDefaultDevices = inject(),
92
+ ) -> MsgGenerator:
93
+ """
94
+ Move beamline hardware to known positions prior to UDC start.
95
+ """
96
+ yield from _move_to_udc_default_state(composite)
@@ -1,5 +1,3 @@
1
- import json
2
-
3
1
  import bluesky.plan_stubs as bps
4
2
  from dodal.devices.focusing_mirror import (
5
3
  FocusingMirrorWithStripes,
@@ -17,6 +15,9 @@ from mx_bluesky.common.utils.log import LOGGER
17
15
  from mx_bluesky.common.utils.utils import (
18
16
  energy_to_bragg_angle,
19
17
  )
18
+ from mx_bluesky.hyperion.external_interaction.config_server import (
19
+ get_hyperion_config_client,
20
+ )
20
21
 
21
22
  MIRROR_VOLTAGE_GROUP = "MIRROR_VOLTAGE_GROUP"
22
23
  DCM_GROUP = "DCM_GROUP"
@@ -27,11 +28,12 @@ def _apply_and_wait_for_voltages_to_settle(
27
28
  stripe: MirrorStripe,
28
29
  mirror_voltages: MirrorVoltages,
29
30
  ):
30
- with open(mirror_voltages.voltage_lookup_table_path) as lut_file:
31
- json_obj = json.load(lut_file)
32
-
31
+ config_server = get_hyperion_config_client()
32
+ config_dict = config_server.get_file_contents(
33
+ mirror_voltages.voltage_lookup_table_path, dict
34
+ )
33
35
  # sample mode is the only mode supported
34
- sample_data = json_obj["sample"]
36
+ sample_data = config_dict["sample"]
35
37
  if stripe == MirrorStripe.BARE:
36
38
  stripe_key = "bare"
37
39
  elif stripe == MirrorStripe.RHODIUM:
@@ -16,8 +16,8 @@ from ophyd_async.fastcs.panda import (
16
16
  )
17
17
 
18
18
  from mx_bluesky.common.device_setup_plans.setup_panda import load_panda_from_yaml
19
+ from mx_bluesky.common.parameters.constants import DeviceSettingsConstants
19
20
  from mx_bluesky.common.utils.log import LOGGER
20
- from mx_bluesky.hyperion.parameters.constants import DeviceSettingsConstants
21
21
 
22
22
  MM_TO_ENCODER_COUNTS = 200000
23
23
  GENERAL_TIMEOUT = 60
@@ -4,6 +4,9 @@ from collections.abc import Callable
4
4
  from typing import TypedDict
5
5
 
6
6
  import mx_bluesky.hyperion.experiment_plans.rotation_scan_plan as rotation_scan_plan
7
+ from mx_bluesky.common.parameters.rotation import (
8
+ RotationScan,
9
+ )
7
10
  from mx_bluesky.hyperion.experiment_plans import (
8
11
  hyperion_grid_detect_then_xray_centre_plan,
9
12
  load_centre_collect_full_plan,
@@ -15,7 +18,6 @@ from mx_bluesky.hyperion.parameters.gridscan import (
15
18
  PinTipCentreThenXrayCentre,
16
19
  )
17
20
  from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
18
- from mx_bluesky.hyperion.parameters.rotation import RotationScan
19
21
 
20
22
 
21
23
  def not_implemented():
@@ -49,11 +49,8 @@ def construct_hyperion_specific_features(
49
49
  signals_to_read_pre_flyscan = [
50
50
  xrc_composite.undulator.current_gap,
51
51
  xrc_composite.synchrotron.synchrotron_mode,
52
- xrc_composite.s4_slit_gaps.xgap,
53
- xrc_composite.s4_slit_gaps.ygap,
54
- xrc_composite.smargon.x,
55
- xrc_composite.smargon.y,
56
- xrc_composite.smargon.z,
52
+ xrc_composite.s4_slit_gaps,
53
+ xrc_composite.smargon,
57
54
  xrc_composite.dcm.energy_in_keV,
58
55
  ]
59
56
 
@@ -64,6 +61,8 @@ def construct_hyperion_specific_features(
64
61
  xrc_composite.dcm.energy_in_keV,
65
62
  xrc_composite.eiger.bit_depth,
66
63
  xrc_composite.beamsize,
64
+ xrc_composite.eiger.cam.roi_mode,
65
+ xrc_composite.eiger.ispyb_detector_id,
67
66
  ]
68
67
 
69
68
  setup_trigger_plan: Callable[..., MsgGenerator]
@@ -11,7 +11,7 @@ from mx_bluesky.common.experiment_plans.common_grid_detect_then_xray_centre_plan
11
11
  )
12
12
  from mx_bluesky.common.parameters.constants import OavConstants, PlanNameConstants
13
13
  from mx_bluesky.common.preprocessors.preprocessors import (
14
- transmission_and_xbpm_feedback_for_collection_decorator,
14
+ pause_xbpm_feedback_during_collection_at_desired_transmission_decorator,
15
15
  )
16
16
  from mx_bluesky.common.utils.context import device_composite_from_context
17
17
  from mx_bluesky.hyperion.experiment_plans.hyperion_flyscan_xray_centre_plan import (
@@ -45,7 +45,7 @@ def hyperion_grid_detect_then_xray_centre(
45
45
  """
46
46
 
47
47
  @verify_undulator_gap_before_run_decorator(composite)
48
- @transmission_and_xbpm_feedback_for_collection_decorator(
48
+ @pause_xbpm_feedback_during_collection_at_desired_transmission_decorator(
49
49
  composite, parameters.transmission_frac, PlanNameConstants.GRIDSCAN_OUTER
50
50
  )
51
51
  def plan_to_perform():