mx-bluesky 0.3.1__py3-none-any.whl → 1.1.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 (138) hide show
  1. mx_bluesky/_version.py +2 -2
  2. mx_bluesky/beamlines/i04/__init__.py +3 -0
  3. mx_bluesky/{i04 → beamlines/i04}/thawing_plan.py +5 -4
  4. mx_bluesky/{i24 → beamlines/i24}/serial/blueapi_config.yaml +1 -1
  5. mx_bluesky/{i24 → beamlines/i24}/serial/dcid.py +2 -2
  6. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DetStage.edl +3 -3
  7. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/DiamondExtruder-I24-py3v1.edl +7 -7
  8. mx_bluesky/{i24 → beamlines/i24}/serial/extruder/i24ssx_Extruder_Collect_py3v2.py +12 -9
  9. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +3 -3
  10. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DetStage.edl +3 -3
  11. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +245 -200
  12. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +4 -4
  13. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +8 -8
  14. mx_bluesky/beamlines/i24/serial/fixed_target/__init__.py +0 -0
  15. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Collect_py3v1.py +80 -70
  16. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +20 -21
  17. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_Mapping_py3v1.py +5 -5
  18. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_Chip_StartUp_py3v1.py +7 -4
  19. mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/i24ssx_moveonclick.py +59 -39
  20. mx_bluesky/{i24 → beamlines/i24}/serial/log.py +1 -9
  21. mx_bluesky/beamlines/i24/serial/parameters/__init__.py +15 -0
  22. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/constants.py +1 -1
  23. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/experiment_parameters.py +4 -25
  24. mx_bluesky/{i24 → beamlines/i24}/serial/parameters/utils.py +5 -3
  25. mx_bluesky/{i24 → beamlines/i24}/serial/run_serial.py +1 -1
  26. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv_abstract.py +1 -1
  27. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_beamline.py +2 -2
  28. mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_detector.py +5 -5
  29. mx_bluesky/{i24 → beamlines/i24}/serial/write_nexus.py +6 -3
  30. mx_bluesky/hyperion/__init__.py +1 -0
  31. mx_bluesky/hyperion/__main__.py +374 -0
  32. mx_bluesky/hyperion/device_setup_plans/__init__.py +0 -0
  33. mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +134 -0
  34. mx_bluesky/hyperion/device_setup_plans/manipulate_sample.py +110 -0
  35. mx_bluesky/hyperion/device_setup_plans/position_detector.py +16 -0
  36. mx_bluesky/hyperion/device_setup_plans/read_hardware_for_setup.py +60 -0
  37. mx_bluesky/hyperion/device_setup_plans/setup_oav.py +87 -0
  38. mx_bluesky/hyperion/device_setup_plans/setup_panda.py +210 -0
  39. mx_bluesky/hyperion/device_setup_plans/setup_zebra.py +214 -0
  40. mx_bluesky/hyperion/device_setup_plans/smargon.py +25 -0
  41. mx_bluesky/hyperion/device_setup_plans/utils.py +44 -0
  42. mx_bluesky/hyperion/device_setup_plans/xbpm_feedback.py +93 -0
  43. mx_bluesky/hyperion/exceptions.py +47 -0
  44. mx_bluesky/hyperion/experiment_plans/__init__.py +30 -0
  45. mx_bluesky/hyperion/experiment_plans/experiment_registry.py +84 -0
  46. mx_bluesky/hyperion/experiment_plans/flyscan_xray_centre_plan.py +528 -0
  47. mx_bluesky/hyperion/experiment_plans/grid_detect_then_xray_centre_plan.py +209 -0
  48. mx_bluesky/hyperion/experiment_plans/oav_grid_detection_plan.py +173 -0
  49. mx_bluesky/hyperion/experiment_plans/oav_snapshot_plan.py +81 -0
  50. mx_bluesky/hyperion/experiment_plans/optimise_attenuation_plan.py +463 -0
  51. mx_bluesky/hyperion/experiment_plans/pin_centre_then_xray_centre_plan.py +119 -0
  52. mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +164 -0
  53. mx_bluesky/hyperion/experiment_plans/robot_load_then_centre_plan.py +322 -0
  54. mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +436 -0
  55. mx_bluesky/hyperion/experiment_plans/set_energy_plan.py +68 -0
  56. mx_bluesky/hyperion/external_interaction/__init__.py +9 -0
  57. mx_bluesky/hyperion/external_interaction/callbacks/__init__.py +10 -0
  58. mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +148 -0
  59. mx_bluesky/hyperion/external_interaction/callbacks/aperture_change_callback.py +22 -0
  60. mx_bluesky/hyperion/external_interaction/callbacks/common/__init__.py +0 -0
  61. mx_bluesky/hyperion/external_interaction/callbacks/common/callback_util.py +46 -0
  62. mx_bluesky/hyperion/external_interaction/callbacks/common/ispyb_mapping.py +70 -0
  63. mx_bluesky/hyperion/external_interaction/callbacks/grid_detection_callback.py +88 -0
  64. mx_bluesky/hyperion/external_interaction/callbacks/ispyb_callback_base.py +203 -0
  65. mx_bluesky/hyperion/external_interaction/callbacks/log_uid_tag_callback.py +20 -0
  66. mx_bluesky/hyperion/external_interaction/callbacks/logging_callback.py +29 -0
  67. mx_bluesky/hyperion/external_interaction/callbacks/plan_reactive_callback.py +101 -0
  68. mx_bluesky/hyperion/external_interaction/callbacks/robot_load/ispyb_callback.py +88 -0
  69. mx_bluesky/hyperion/external_interaction/callbacks/rotation/__init__.py +0 -0
  70. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_callback.py +174 -0
  71. mx_bluesky/hyperion/external_interaction/callbacks/rotation/ispyb_mapping.py +17 -0
  72. mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +102 -0
  73. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/__init__.py +0 -0
  74. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_callback.py +269 -0
  75. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/ispyb_mapping.py +53 -0
  76. mx_bluesky/hyperion/external_interaction/callbacks/xray_centre/nexus_callback.py +95 -0
  77. mx_bluesky/hyperion/external_interaction/callbacks/zocalo_callback.py +92 -0
  78. mx_bluesky/hyperion/external_interaction/config_server.py +35 -0
  79. mx_bluesky/hyperion/external_interaction/exceptions.py +13 -0
  80. mx_bluesky/hyperion/external_interaction/ispyb/__init__.py +0 -0
  81. mx_bluesky/hyperion/external_interaction/ispyb/data_model.py +95 -0
  82. mx_bluesky/hyperion/external_interaction/ispyb/exp_eye_store.py +125 -0
  83. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_store.py +276 -0
  84. mx_bluesky/hyperion/external_interaction/ispyb/ispyb_utils.py +29 -0
  85. mx_bluesky/hyperion/external_interaction/nexus/__init__.py +0 -0
  86. mx_bluesky/hyperion/external_interaction/nexus/nexus_utils.py +148 -0
  87. mx_bluesky/hyperion/external_interaction/nexus/write_nexus.py +114 -0
  88. mx_bluesky/hyperion/log.py +99 -0
  89. mx_bluesky/hyperion/parameters/__init__.py +2 -0
  90. mx_bluesky/hyperion/parameters/cli.py +68 -0
  91. mx_bluesky/{parameters → hyperion/parameters}/components.py +77 -24
  92. mx_bluesky/hyperion/parameters/constants.py +158 -0
  93. mx_bluesky/hyperion/parameters/gridscan.py +216 -0
  94. mx_bluesky/hyperion/parameters/rotation.py +160 -0
  95. mx_bluesky/hyperion/resources/panda/panda-gridscan.yaml +964 -0
  96. mx_bluesky/hyperion/tracing.py +28 -0
  97. mx_bluesky/hyperion/utils/context.py +84 -0
  98. mx_bluesky/hyperion/utils/utils.py +25 -0
  99. mx_bluesky/hyperion/utils/validation.py +196 -0
  100. mx_bluesky/jupyter_example.ipynb +3 -2
  101. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/METADATA +26 -11
  102. mx_bluesky-1.1.0.dist-info/RECORD +136 -0
  103. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/WHEEL +1 -1
  104. mx_bluesky-1.1.0.dist-info/entry_points.txt +8 -0
  105. mx_bluesky/i04/__init__.py +0 -3
  106. mx_bluesky/i24/serial/parameters/__init__.py +0 -15
  107. mx_bluesky/parameters/__init__.py +0 -31
  108. mx_bluesky-0.3.1.dist-info/RECORD +0 -67
  109. mx_bluesky-0.3.1.dist-info/entry_points.txt +0 -4
  110. /mx_bluesky/{i24 → beamlines}/__init__.py +0 -0
  111. /mx_bluesky/{i04 → beamlines/i04}/callbacks/murko_callback.py +0 -0
  112. /mx_bluesky/{i24/serial/extruder → beamlines/i24}/__init__.py +0 -0
  113. /mx_bluesky/{i24 → beamlines/i24}/serial/__init__.py +0 -0
  114. /mx_bluesky/{i24 → beamlines/i24}/serial/extruder/EX-gui-edm/microdrop_alignment.edl +0 -0
  115. /mx_bluesky/{i24/serial/fixed_target → beamlines/i24/serial/extruder}/__init__.py +0 -0
  116. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +0 -0
  117. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +0 -0
  118. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +0 -0
  119. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/microdrop_alignment.edl +0 -0
  120. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/nudgechip.edl +0 -0
  121. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short1-laser.png +0 -0
  122. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/FT-gui-edm/short2-laser.png +0 -0
  123. /mx_bluesky/{i24 → beamlines/i24}/serial/fixed_target/ft_utils.py +0 -0
  124. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/cs_maker.json +0 -0
  125. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/cs/motor_direction.txt +0 -0
  126. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/minichip-oxford.pvar +0 -0
  127. /mx_bluesky/{i24 → beamlines/i24}/serial/parameters/fixed_target/pvar_files/oxford.pvar +0 -0
  128. /mx_bluesky/{i24 → beamlines/i24}/serial/run_extruder.sh +0 -0
  129. /mx_bluesky/{i24 → beamlines/i24}/serial/run_fixed_target.sh +0 -0
  130. /mx_bluesky/{i24 → beamlines/i24}/serial/run_ssx.sh +0 -0
  131. /mx_bluesky/{i24 → beamlines/i24}/serial/set_visit_directory.sh +0 -0
  132. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/__init__.py +0 -0
  133. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/ca.py +0 -0
  134. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/pv.py +0 -0
  135. /mx_bluesky/{i24 → beamlines/i24}/serial/setup_beamline/setup_zebra_plans.py +0 -0
  136. /mx_bluesky/{i24 → beamlines/i24}/serial/start_blueapi.sh +0 -0
  137. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/LICENSE +0 -0
  138. {mx_bluesky-0.3.1.dist-info → mx_bluesky-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,15 @@
1
+ from mx_bluesky.beamlines.i24.serial.parameters.constants import SSXType
2
+ from mx_bluesky.beamlines.i24.serial.parameters.experiment_parameters import (
3
+ ChipDescription,
4
+ ExtruderParameters,
5
+ FixedTargetParameters,
6
+ )
7
+ from mx_bluesky.beamlines.i24.serial.parameters.utils import get_chip_format
8
+
9
+ __all__ = [
10
+ "SSXType",
11
+ "ExtruderParameters",
12
+ "ChipDescription",
13
+ "FixedTargetParameters",
14
+ "get_chip_format",
15
+ ]
@@ -2,7 +2,7 @@ from enum import Enum
2
2
  from os import environ
3
3
  from pathlib import Path
4
4
 
5
- from mx_bluesky.i24.serial.log import _read_visit_directory_from_file
5
+ from mx_bluesky.beamlines.i24.serial.log import _read_visit_directory_from_file
6
6
 
7
7
 
8
8
  class SSXType(Enum):
@@ -2,9 +2,9 @@ import json
2
2
  from pathlib import Path
3
3
  from typing import Literal
4
4
 
5
- from pydantic import BaseModel, ConfigDict, validator
5
+ from pydantic import BaseModel, field_validator
6
6
 
7
- from mx_bluesky.i24.serial.fixed_target.ft_utils import (
7
+ from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import (
8
8
  ChipType,
9
9
  MappingType,
10
10
  PumpProbeSetting,
@@ -21,7 +21,8 @@ class SerialExperiment(BaseModel):
21
21
  detector_distance_mm: float
22
22
  detector_name: Literal["eiger", "pilatus"]
23
23
 
24
- @validator("visit", pre=True)
24
+ @field_validator("visit", mode="before")
25
+ @classmethod
25
26
  def _parse_visit(cls, visit: str | Path):
26
27
  if isinstance(visit, str):
27
28
  return Path(visit)
@@ -56,8 +57,6 @@ class ExtruderParameters(SerialExperiment, LaserExperiment):
56
57
  class ChipDescription(BaseModel):
57
58
  """Parameters defining the chip in use for FT collection."""
58
59
 
59
- model_config = ConfigDict(use_enum_values=True)
60
-
61
60
  chip_type: ChipType
62
61
  x_num_steps: int
63
62
  y_num_steps: int
@@ -68,13 +67,6 @@ class ChipDescription(BaseModel):
68
67
  b2b_horz: float
69
68
  b2b_vert: float
70
69
 
71
- @validator("chip_type", pre=True)
72
- def _parse_chip(cls, chip_type: str | int):
73
- if isinstance(chip_type, str):
74
- return ChipType[chip_type]
75
- else:
76
- return ChipType(chip_type)
77
-
78
70
  @property
79
71
  def chip_format(self) -> list[int]:
80
72
  return [self.x_blocks, self.y_blocks, self.x_num_steps, self.y_num_steps]
@@ -97,8 +89,6 @@ class ChipDescription(BaseModel):
97
89
  class FixedTargetParameters(SerialExperiment, LaserExperiment):
98
90
  """Fixed target parameter model."""
99
91
 
100
- model_config = ConfigDict(use_enum_values=True)
101
-
102
92
  num_exposures: int
103
93
  chip: ChipDescription
104
94
  map_type: MappingType
@@ -106,17 +96,6 @@ class FixedTargetParameters(SerialExperiment, LaserExperiment):
106
96
  checker_pattern: bool = False
107
97
  total_num_images: int = 0 # Calculated in the code for now
108
98
 
109
- @validator("map_type", pre=True)
110
- def _parse_map(cls, map_type: str | int):
111
- if isinstance(map_type, str):
112
- return MappingType[map_type]
113
- else:
114
- return MappingType(map_type)
115
-
116
- @validator("pump_repeat", pre=True)
117
- def _parse_pump(cls, pump_repeat: int):
118
- return PumpProbeSetting(pump_repeat)
119
-
120
99
  @classmethod
121
100
  def from_file(cls, filename: str | Path):
122
101
  with open(filename) as fh:
@@ -1,8 +1,10 @@
1
1
  from typing import Any
2
2
 
3
- from mx_bluesky.i24.serial.fixed_target.ft_utils import ChipType
4
- from mx_bluesky.i24.serial.parameters.experiment_parameters import ChipDescription
5
- from mx_bluesky.i24.serial.setup_beamline import caget, pv
3
+ from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType
4
+ from mx_bluesky.beamlines.i24.serial.parameters.experiment_parameters import (
5
+ ChipDescription,
6
+ )
7
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import caget, pv
6
8
 
7
9
 
8
10
  def get_chip_format(chip_type: ChipType) -> ChipDescription:
@@ -11,7 +11,7 @@ def get_location(default: str = "dev") -> str:
11
11
 
12
12
 
13
13
  def get_edm_path() -> Path:
14
- return Path(__file__).parents[4] / "edm_serial"
14
+ return Path(__file__).parents[5] / "edm_serial"
15
15
 
16
16
 
17
17
  def _get_file_path() -> Path:
@@ -5,7 +5,7 @@ Takes the PV tables from I24's setup_beamline and wraps a slightly more
5
5
  abstract wrapper around them.
6
6
  """
7
7
 
8
- from mx_bluesky.i24.serial.setup_beamline import pv
8
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import pv
9
9
 
10
10
 
11
11
  class Pilatus:
@@ -7,8 +7,8 @@ from dodal.devices.i24.beamstop import Beamstop, BeamstopPositions
7
7
  from dodal.devices.i24.dual_backlight import BacklightPositions, DualBacklight
8
8
  from dodal.devices.i24.i24_detector_motion import DetectorMotion
9
9
 
10
- from mx_bluesky.i24.serial.setup_beamline import pv
11
- from mx_bluesky.i24.serial.setup_beamline.ca import caget, caput
10
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import pv
11
+ from mx_bluesky.beamlines.i24.serial.setup_beamline.ca import caget, caput
12
12
 
13
13
  logger = logging.getLogger("I24ssx.sup")
14
14
 
@@ -13,11 +13,11 @@ from bluesky.utils import Msg
13
13
  from dodal.common import inject
14
14
  from dodal.devices.i24.i24_detector_motion import DetectorMotion
15
15
 
16
- from mx_bluesky.i24.serial import log
17
- from mx_bluesky.i24.serial.parameters import SSXType
18
- from mx_bluesky.i24.serial.setup_beamline import pv
19
- from mx_bluesky.i24.serial.setup_beamline.ca import caget
20
- from mx_bluesky.i24.serial.setup_beamline.pv_abstract import (
16
+ from mx_bluesky.beamlines.i24.serial import log
17
+ from mx_bluesky.beamlines.i24.serial.parameters import SSXType
18
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import pv
19
+ from mx_bluesky.beamlines.i24.serial.setup_beamline.ca import caget
20
+ from mx_bluesky.beamlines.i24.serial.setup_beamline.pv_abstract import (
21
21
  Detector,
22
22
  Eiger,
23
23
  Pilatus,
@@ -8,9 +8,12 @@ from typing import Literal
8
8
 
9
9
  import requests
10
10
 
11
- from mx_bluesky.i24.serial.fixed_target.ft_utils import ChipType, MappingType
12
- from mx_bluesky.i24.serial.parameters import ExtruderParameters, FixedTargetParameters
13
- from mx_bluesky.i24.serial.setup_beamline import Eiger, caget, cagetstring
11
+ from mx_bluesky.beamlines.i24.serial.fixed_target.ft_utils import ChipType, MappingType
12
+ from mx_bluesky.beamlines.i24.serial.parameters import (
13
+ ExtruderParameters,
14
+ FixedTargetParameters,
15
+ )
16
+ from mx_bluesky.beamlines.i24.serial.setup_beamline import Eiger, caget, cagetstring
14
17
 
15
18
  logger = logging.getLogger("I24ssx.nexus_writer")
16
19
 
@@ -0,0 +1 @@
1
+ # placeholder file to start layout
@@ -0,0 +1,374 @@
1
+ import atexit
2
+ import json
3
+ import threading
4
+ from collections.abc import Callable
5
+ from dataclasses import asdict
6
+ from queue import Queue
7
+ from traceback import format_exception
8
+ from typing import Any
9
+
10
+ from blueapi.core import BlueskyContext, MsgGenerator
11
+ from bluesky.callbacks.zmq import Publisher
12
+ from bluesky.run_engine import RunEngine
13
+ from flask import Flask, request
14
+ from flask_restful import Api, Resource
15
+ from pydantic.dataclasses import dataclass
16
+
17
+ from mx_bluesky.hyperion.exceptions import WarningException
18
+ from mx_bluesky.hyperion.experiment_plans.experiment_registry import (
19
+ PLAN_REGISTRY,
20
+ PlanNotFound,
21
+ )
22
+ from mx_bluesky.hyperion.external_interaction.callbacks.__main__ import (
23
+ setup_logging as setup_callback_logging,
24
+ )
25
+ from mx_bluesky.hyperion.external_interaction.callbacks.aperture_change_callback import (
26
+ ApertureChangeCallback,
27
+ )
28
+ from mx_bluesky.hyperion.external_interaction.callbacks.common.callback_util import (
29
+ CallbacksFactory,
30
+ )
31
+ from mx_bluesky.hyperion.external_interaction.callbacks.log_uid_tag_callback import (
32
+ LogUidTaggingCallback,
33
+ )
34
+ from mx_bluesky.hyperion.external_interaction.callbacks.logging_callback import (
35
+ VerbosePlanExecutionLoggingCallback,
36
+ )
37
+ from mx_bluesky.hyperion.log import (
38
+ LOGGER,
39
+ do_default_logging_setup,
40
+ flush_debug_handler,
41
+ )
42
+ 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.utils.context import setup_context
47
+
48
+ VERBOSE_EVENT_LOGGING: bool | None = None
49
+
50
+
51
+ @dataclass
52
+ class Command:
53
+ action: Actions
54
+ devices: Any | None = None
55
+ experiment: Callable[[Any, Any], MsgGenerator] | None = None
56
+ parameters: HyperionParameters | None = None
57
+ callbacks: CallbacksFactory | None = None
58
+
59
+
60
+ @dataclass
61
+ class StatusAndMessage:
62
+ status: str
63
+ message: str = ""
64
+
65
+ def __init__(self, status: Status, message: str = "") -> None:
66
+ self.status = status.value
67
+ self.message = message
68
+
69
+
70
+ @dataclass
71
+ class ErrorStatusAndMessage(StatusAndMessage):
72
+ exception_type: str = ""
73
+
74
+
75
+ def make_error_status_and_message(exception: Exception):
76
+ return ErrorStatusAndMessage(
77
+ status=Status.FAILED.value,
78
+ message=repr(exception),
79
+ exception_type=type(exception).__name__,
80
+ )
81
+
82
+
83
+ class BlueskyRunner:
84
+ def __init__(
85
+ self,
86
+ RE: RunEngine,
87
+ context: BlueskyContext,
88
+ skip_startup_connection=False,
89
+ use_external_callbacks: bool = False,
90
+ ) -> None:
91
+ self.command_queue: Queue[Command] = Queue()
92
+ self.current_status: StatusAndMessage = StatusAndMessage(Status.IDLE)
93
+ self.last_run_aborted: bool = False
94
+ self.aperture_change_callback = ApertureChangeCallback()
95
+ self.logging_uid_tag_callback = LogUidTaggingCallback()
96
+ self.context: BlueskyContext
97
+
98
+ self.RE = RE
99
+ self.context = context
100
+ self.subscribed_per_plan_callbacks: list[int] = []
101
+ RE.subscribe(self.aperture_change_callback)
102
+ RE.subscribe(self.logging_uid_tag_callback)
103
+
104
+ self.use_external_callbacks = use_external_callbacks
105
+ if self.use_external_callbacks:
106
+ LOGGER.info("Connecting to external callback ZMQ proxy...")
107
+ self.publisher = Publisher(f"localhost:{CONST.CALLBACK_0MQ_PROXY_PORTS[0]}")
108
+ RE.subscribe(self.publisher)
109
+
110
+ if VERBOSE_EVENT_LOGGING:
111
+ RE.subscribe(VerbosePlanExecutionLoggingCallback())
112
+
113
+ self.skip_startup_connection = skip_startup_connection
114
+ if not self.skip_startup_connection:
115
+ LOGGER.info("Initialising dodal devices...")
116
+ for plan_name in PLAN_REGISTRY:
117
+ PLAN_REGISTRY[plan_name]["setup"](context)
118
+
119
+ def start(
120
+ self,
121
+ experiment: Callable,
122
+ parameters: HyperionParameters,
123
+ plan_name: str,
124
+ callbacks: CallbacksFactory | None,
125
+ ) -> StatusAndMessage:
126
+ LOGGER.info(f"Started with parameters: {parameters.model_dump_json(indent=2)}")
127
+
128
+ devices: Any = PLAN_REGISTRY[plan_name]["setup"](self.context)
129
+
130
+ if (
131
+ self.current_status.status == Status.BUSY.value
132
+ or self.current_status.status == Status.ABORTING.value
133
+ ):
134
+ return StatusAndMessage(Status.FAILED, "Bluesky already running")
135
+ else:
136
+ self.current_status = StatusAndMessage(Status.BUSY)
137
+ self.command_queue.put(
138
+ Command(
139
+ action=Actions.START,
140
+ devices=devices,
141
+ experiment=experiment,
142
+ parameters=parameters,
143
+ callbacks=callbacks,
144
+ )
145
+ )
146
+ return StatusAndMessage(Status.SUCCESS)
147
+
148
+ def stopping_thread(self):
149
+ try:
150
+ self.RE.abort()
151
+ self.current_status = StatusAndMessage(Status.IDLE)
152
+ except Exception as e:
153
+ self.current_status = make_error_status_and_message(e)
154
+
155
+ def stop(self) -> StatusAndMessage:
156
+ if self.current_status.status == Status.IDLE.value:
157
+ return StatusAndMessage(Status.FAILED, "Bluesky not running")
158
+ elif self.current_status.status == Status.ABORTING.value:
159
+ return StatusAndMessage(Status.FAILED, "Bluesky already stopping")
160
+ else:
161
+ self.current_status = StatusAndMessage(Status.ABORTING)
162
+ stopping_thread = threading.Thread(target=self.stopping_thread)
163
+ stopping_thread.start()
164
+ self.last_run_aborted = True
165
+ return StatusAndMessage(Status.ABORTING)
166
+
167
+ def shutdown(self):
168
+ """Stops the run engine and the loop waiting for messages."""
169
+ print("Shutting down: Stopping the run engine gracefully")
170
+ self.stop()
171
+ self.command_queue.put(Command(action=Actions.SHUTDOWN))
172
+
173
+ def wait_on_queue(self):
174
+ while True:
175
+ command = self.command_queue.get()
176
+ if command.action == Actions.SHUTDOWN:
177
+ return
178
+ elif command.action == Actions.START:
179
+ if command.experiment is None:
180
+ raise ValueError("No experiment provided for START")
181
+ try:
182
+ if (
183
+ not self.use_external_callbacks
184
+ and command.callbacks
185
+ and (cbs := command.callbacks())
186
+ ):
187
+ LOGGER.info(
188
+ f"Using callbacks for this plan: {not self.use_external_callbacks} - {cbs}"
189
+ )
190
+ self.subscribed_per_plan_callbacks += [
191
+ self.RE.subscribe(cb) for cb in cbs
192
+ ]
193
+ with TRACER.start_span("do_run"):
194
+ self.RE(command.experiment(command.devices, command.parameters))
195
+
196
+ self.current_status = StatusAndMessage(
197
+ Status.IDLE,
198
+ self.aperture_change_callback.last_selected_aperture,
199
+ )
200
+
201
+ self.last_run_aborted = False
202
+ except WarningException as exception:
203
+ LOGGER.warning("Warning Exception", exc_info=True)
204
+ self.current_status = make_error_status_and_message(exception)
205
+ except Exception as exception:
206
+ LOGGER.error("Exception on running plan", exc_info=True)
207
+
208
+ if self.last_run_aborted:
209
+ # Aborting will cause an exception here that we want to swallow
210
+ self.last_run_aborted = False
211
+ else:
212
+ self.current_status = make_error_status_and_message(exception)
213
+ finally:
214
+ [
215
+ self.RE.unsubscribe(cb)
216
+ for cb in self.subscribed_per_plan_callbacks
217
+ ]
218
+
219
+
220
+ def compose_start_args(context: BlueskyContext, plan_name: str, action: Actions):
221
+ experiment_registry_entry = PLAN_REGISTRY.get(plan_name)
222
+ if experiment_registry_entry is None:
223
+ raise PlanNotFound(f"Experiment plan '{plan_name}' not found in registry.")
224
+
225
+ experiment_internal_param_type = experiment_registry_entry.get("param_type")
226
+ callback_type = experiment_registry_entry.get("callback_collection_type")
227
+ plan = context.plan_functions.get(plan_name)
228
+ if experiment_internal_param_type is None:
229
+ raise PlanNotFound(
230
+ f"Corresponding internal param type for '{plan_name}' not found in registry."
231
+ )
232
+ if plan is None:
233
+ raise PlanNotFound(
234
+ f"Experiment plan '{plan_name}' not found in context. Context has {context.plan_functions.keys()}"
235
+ )
236
+ try:
237
+ parameters = experiment_internal_param_type(**json.loads(request.data))
238
+ if parameters.model_extra:
239
+ raise ValueError(f"Extra fields not allowed {parameters.model_extra}")
240
+ except Exception as e:
241
+ raise ValueError(
242
+ f"Supplied parameters don't match the plan for this endpoint {request.data}"
243
+ ) from e
244
+ return plan, parameters, plan_name, callback_type
245
+
246
+
247
+ class RunExperiment(Resource):
248
+ def __init__(self, runner: BlueskyRunner, context: BlueskyContext) -> None:
249
+ super().__init__()
250
+ self.runner = runner
251
+ self.context = context
252
+
253
+ def put(self, plan_name: str, action: Actions):
254
+ status_and_message = StatusAndMessage(Status.FAILED, f"{action} not understood")
255
+ if action == Actions.START.value:
256
+ try:
257
+ plan, params, plan_name, callback_type = compose_start_args(
258
+ self.context, plan_name, action
259
+ )
260
+ status_and_message = self.runner.start(
261
+ plan, params, plan_name, callback_type
262
+ )
263
+ except Exception as e:
264
+ status_and_message = make_error_status_and_message(e)
265
+ LOGGER.error(format_exception(e))
266
+
267
+ elif action == Actions.STOP.value:
268
+ status_and_message = self.runner.stop()
269
+ # no idea why mypy gives an attribute error here but nowhere else for this
270
+ # exact same situation...
271
+ return asdict(status_and_message) # type: ignore
272
+
273
+
274
+ class StopOrStatus(Resource):
275
+ def __init__(self, runner: BlueskyRunner) -> None:
276
+ super().__init__()
277
+ self.runner: BlueskyRunner = runner
278
+
279
+ def put(self, action):
280
+ status_and_message = StatusAndMessage(Status.FAILED, f"{action} not understood")
281
+ if action == Actions.STOP.value:
282
+ status_and_message = self.runner.stop()
283
+ return asdict(status_and_message)
284
+
285
+ def get(self, **kwargs):
286
+ action = kwargs.get("action")
287
+ status_and_message = StatusAndMessage(Status.FAILED, f"{action} not understood")
288
+ if action == Actions.STATUS.value:
289
+ LOGGER.debug(
290
+ f"Runner received status request - state of the runner object is: {self.runner.__dict__} - state of the RE is: {self.runner.RE.__dict__}"
291
+ )
292
+ status_and_message = self.runner.current_status
293
+ return asdict(status_and_message)
294
+
295
+
296
+ class FlushLogs(Resource):
297
+ def put(self, **kwargs):
298
+ try:
299
+ status_and_message = StatusAndMessage(
300
+ Status.SUCCESS, f"Flushed debug log to {flush_debug_handler()}"
301
+ )
302
+ except Exception as e:
303
+ status_and_message = StatusAndMessage(
304
+ Status.FAILED, f"Failed to flush debug log: {e}"
305
+ )
306
+ return asdict(status_and_message)
307
+
308
+
309
+ def create_app(
310
+ test_config=None,
311
+ RE: RunEngine = RunEngine({}),
312
+ skip_startup_connection: bool = False,
313
+ use_external_callbacks: bool = False,
314
+ ) -> tuple[Flask, BlueskyRunner]:
315
+ context = setup_context(
316
+ wait_for_connection=not skip_startup_connection,
317
+ )
318
+ runner = BlueskyRunner(
319
+ RE,
320
+ context=context,
321
+ use_external_callbacks=use_external_callbacks,
322
+ skip_startup_connection=skip_startup_connection,
323
+ )
324
+ app = Flask(__name__)
325
+ if test_config:
326
+ app.config.update(test_config)
327
+ api = Api(app)
328
+ api.add_resource(
329
+ RunExperiment,
330
+ "/<string:plan_name>/<string:action>",
331
+ resource_class_args=[runner, context],
332
+ )
333
+ api.add_resource(
334
+ FlushLogs,
335
+ "/flush_debug_log",
336
+ )
337
+ api.add_resource(
338
+ StopOrStatus,
339
+ "/<string:action>",
340
+ resource_class_args=[runner],
341
+ )
342
+ return app, runner
343
+
344
+
345
+ def create_targets():
346
+ hyperion_port = 5005
347
+ args = parse_cli_args()
348
+ do_default_logging_setup(dev_mode=args.dev_mode)
349
+ if not args.use_external_callbacks:
350
+ setup_callback_logging(args.dev_mode)
351
+ app, runner = create_app(
352
+ skip_startup_connection=args.skip_startup_connection,
353
+ use_external_callbacks=args.use_external_callbacks,
354
+ )
355
+ return app, runner, hyperion_port, args.dev_mode
356
+
357
+
358
+ def main():
359
+ app, runner, port, dev_mode = create_targets()
360
+ atexit.register(runner.shutdown)
361
+ flask_thread = threading.Thread(
362
+ target=lambda: app.run(
363
+ host="0.0.0.0", port=port, debug=True, use_reloader=False
364
+ ),
365
+ daemon=True,
366
+ )
367
+ flask_thread.start()
368
+ LOGGER.info(f"Hyperion now listening on {port} ({'IN DEV' if dev_mode else ''})")
369
+ runner.wait_on_queue()
370
+ flask_thread.join()
371
+
372
+
373
+ if __name__ == "__main__":
374
+ main()
File without changes
@@ -0,0 +1,134 @@
1
+ import json
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from dodal.devices.focusing_mirror import (
5
+ FocusingMirrorWithStripes,
6
+ MirrorStripe,
7
+ VFMMirrorVoltages,
8
+ )
9
+ from dodal.devices.undulator_dcm import UndulatorDCM
10
+ from dodal.devices.util.adjuster_plans import lookup_table_adjuster
11
+ from dodal.devices.util.lookup_tables import (
12
+ linear_interpolation_lut,
13
+ )
14
+
15
+ from mx_bluesky.hyperion.log import LOGGER
16
+
17
+ MIRROR_VOLTAGE_GROUP = "MIRROR_VOLTAGE_GROUP"
18
+ DCM_GROUP = "DCM_GROUP"
19
+
20
+
21
+ def _apply_and_wait_for_voltages_to_settle(
22
+ stripe: MirrorStripe,
23
+ mirror: FocusingMirrorWithStripes,
24
+ mirror_voltages: VFMMirrorVoltages,
25
+ ):
26
+ with open(mirror_voltages.voltage_lookup_table_path) as lut_file:
27
+ json_obj = json.load(lut_file)
28
+
29
+ # sample mode is the only mode supported
30
+ sample_data = json_obj["sample"]
31
+ mirror_key = mirror.name.lower()
32
+ if stripe == MirrorStripe.BARE:
33
+ stripe_key = "bare"
34
+ elif stripe == MirrorStripe.RHODIUM:
35
+ stripe_key = "rh"
36
+ elif stripe == MirrorStripe.PLATINUM:
37
+ 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
+ )
51
+
52
+ yield from bps.wait(group=MIRROR_VOLTAGE_GROUP)
53
+
54
+
55
+ def adjust_mirror_stripe(
56
+ energy_kev, mirror: FocusingMirrorWithStripes, mirror_voltages: VFMMirrorVoltages
57
+ ):
58
+ """Feedback should be OFF prior to entry, in order to prevent
59
+ feedback from making unnecessary corrections while beam is being adjusted."""
60
+ stripe = mirror.energy_to_stripe(energy_kev)
61
+
62
+ LOGGER.info(
63
+ f"Adjusting mirror stripe for {energy_kev}keV selecting {stripe} stripe"
64
+ )
65
+ yield from bps.abs_set(mirror.stripe, stripe, wait=True)
66
+ yield from bps.trigger(mirror.apply_stripe)
67
+
68
+ LOGGER.info("Adjusting mirror voltages...")
69
+ yield from _apply_and_wait_for_voltages_to_settle(stripe, mirror, mirror_voltages)
70
+
71
+
72
+ def adjust_dcm_pitch_roll_vfm_from_lut(
73
+ undulator_dcm: UndulatorDCM,
74
+ vfm: FocusingMirrorWithStripes,
75
+ vfm_mirror_voltages: VFMMirrorVoltages,
76
+ energy_kev,
77
+ ):
78
+ """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.
81
+ Feedback should be OFF prior to entry, in order to prevent
82
+ feedback from making unnecessary corrections while beam is being adjusted."""
83
+
84
+ # DCM Pitch
85
+ dcm = undulator_dcm.dcm
86
+ 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")
89
+ dcm_pitch_adjuster = lookup_table_adjuster(
90
+ linear_interpolation_lut(undulator_dcm.pitch_energy_table_path),
91
+ dcm.pitch_in_mrad,
92
+ bragg_deg,
93
+ )
94
+ yield from dcm_pitch_adjuster(DCM_GROUP)
95
+ # It's possible we can remove these waits but we need to check
96
+ LOGGER.info("Waiting for DCM pitch adjust to complete...")
97
+
98
+ # DCM Roll
99
+ dcm_roll_adjuster = lookup_table_adjuster(
100
+ linear_interpolation_lut(undulator_dcm.roll_energy_table_path),
101
+ dcm.roll_in_mrad,
102
+ bragg_deg,
103
+ )
104
+ yield from dcm_roll_adjuster(DCM_GROUP)
105
+ LOGGER.info("Waiting for DCM roll adjust to complete...")
106
+
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
+ #
113
+ # Adjust mirrors
114
+ #
115
+
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
+ # 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()