mx-bluesky 1.5.2__py3-none-any.whl → 1.5.4__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.
- mx_bluesky/_version.py +16 -3
- mx_bluesky/beamlines/aithre_lasershaping/__init__.py +2 -0
- mx_bluesky/beamlines/aithre_lasershaping/beamline_safe.py +17 -0
- mx_bluesky/beamlines/i04/__init__.py +7 -3
- mx_bluesky/beamlines/i04/experiment_plans/i04_grid_detect_then_xray_centre_plan.py +3 -8
- mx_bluesky/beamlines/i04/thawing_plan.py +3 -3
- mx_bluesky/beamlines/i24/serial/blueapi_config.yaml +2 -2
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/CustomChip_py3v1.edl +10 -10
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DetStage.edl +1 -1
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/DiamondChipI24-py3v1.edl +68 -68
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/ME14E-GeneralPurpose.edl +120 -120
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/MappingLite-oxford_py3v1.edl +135 -135
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/PMAC_Command.edl +2 -2
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/Shutter_Control.edl +3 -3
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/nudgechip.edl +24 -24
- mx_bluesky/beamlines/i24/serial/fixed_target/FT-gui-edm/pumpprobe-py3v1.edl +12 -12
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_Chip_Manager_py3v1.py +13 -12
- mx_bluesky/beamlines/i24/serial/fixed_target/i24ssx_moveonclick.py +10 -10
- mx_bluesky/beamlines/i24/serial/parameters/utils.py +1 -1
- mx_bluesky/beamlines/i24/serial/set_visit_directory.sh +1 -1
- mx_bluesky/beamlines/i24/serial/setup_beamline/pv.py +142 -135
- mx_bluesky/common/device_setup_plans/manipulate_sample.py +2 -2
- mx_bluesky/common/experiment_plans/common_grid_detect_then_xray_centre_plan.py +2 -2
- mx_bluesky/common/experiment_plans/inner_plans/udc_default_state.py +65 -0
- mx_bluesky/common/experiment_plans/oav_grid_detection_plan.py +12 -2
- mx_bluesky/common/experiment_plans/oav_snapshot_plan.py +2 -2
- mx_bluesky/common/external_interaction/alerting/__init__.py +13 -0
- mx_bluesky/common/external_interaction/alerting/_service.py +82 -0
- mx_bluesky/common/external_interaction/alerting/log_based_service.py +57 -0
- mx_bluesky/common/external_interaction/callbacks/common/grid_detection_callback.py +35 -17
- mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py +31 -6
- mx_bluesky/common/external_interaction/config_server.py +151 -54
- mx_bluesky/common/parameters/constants.py +27 -8
- mx_bluesky/common/parameters/gridscan.py +1 -1
- mx_bluesky/hyperion/__main__.py +50 -178
- mx_bluesky/hyperion/baton_handler.py +130 -69
- mx_bluesky/hyperion/device_setup_plans/dcm_pitch_roll_mirror_adjuster.py +29 -24
- mx_bluesky/hyperion/experiment_plans/hyperion_flyscan_xray_centre_plan.py +4 -1
- mx_bluesky/hyperion/experiment_plans/load_centre_collect_full_plan.py +17 -5
- mx_bluesky/hyperion/experiment_plans/pin_tip_centring_plan.py +1 -1
- mx_bluesky/hyperion/experiment_plans/robot_load_and_change_energy.py +6 -2
- mx_bluesky/hyperion/experiment_plans/rotation_scan_plan.py +2 -3
- mx_bluesky/hyperion/external_interaction/agamemnon.py +128 -73
- mx_bluesky/hyperion/external_interaction/alerting/__init__.py +0 -0
- mx_bluesky/hyperion/external_interaction/alerting/constants.py +12 -0
- mx_bluesky/hyperion/external_interaction/callbacks/__main__.py +9 -0
- mx_bluesky/hyperion/external_interaction/callbacks/alert_on_container_change.py +54 -0
- mx_bluesky/hyperion/external_interaction/callbacks/rotation/nexus_callback.py +2 -2
- mx_bluesky/hyperion/external_interaction/config_server.py +12 -31
- mx_bluesky/hyperion/parameters/cli.py +15 -3
- mx_bluesky/hyperion/parameters/components.py +7 -5
- mx_bluesky/hyperion/parameters/constants.py +20 -4
- mx_bluesky/hyperion/parameters/gridscan.py +22 -14
- mx_bluesky/hyperion/parameters/load_centre_collect.py +1 -14
- mx_bluesky/hyperion/parameters/robot_load.py +1 -4
- mx_bluesky/hyperion/parameters/rotation.py +1 -2
- mx_bluesky/hyperion/plan_runner.py +78 -0
- mx_bluesky/hyperion/runner.py +189 -0
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.4.dist-info}/METADATA +4 -3
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.4.dist-info}/RECORD +64 -55
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.4.dist-info}/entry_points.txt +0 -2
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.4.dist-info}/WHEEL +0 -0
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.4.dist-info}/licenses/LICENSE +0 -0
- {mx_bluesky-1.5.2.dist-info → mx_bluesky-1.5.4.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TypedDict
|
|
1
|
+
from typing import Generic, TypedDict, TypeVar
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from bluesky.callbacks import CallbackBase
|
|
@@ -7,6 +7,8 @@ from event_model.documents import Event
|
|
|
7
7
|
|
|
8
8
|
from mx_bluesky.common.utils.log import LOGGER
|
|
9
9
|
|
|
10
|
+
T = TypeVar("T", int, float)
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class GridParamUpdate(TypedDict):
|
|
12
14
|
"""
|
|
@@ -40,14 +42,22 @@ class GridParamUpdate(TypedDict):
|
|
|
40
42
|
z_step_size_um: float
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
class XYZParams(TypedDict, Generic[T]):
|
|
46
|
+
x: T
|
|
47
|
+
y: T
|
|
48
|
+
z: T
|
|
49
|
+
|
|
50
|
+
|
|
43
51
|
class GridDetectionCallback(CallbackBase):
|
|
52
|
+
OMEGA_TOLERANCE = 1
|
|
53
|
+
|
|
44
54
|
def __init__(
|
|
45
55
|
self,
|
|
46
56
|
*args,
|
|
47
57
|
) -> None:
|
|
48
58
|
super().__init__(*args)
|
|
49
|
-
self.
|
|
50
|
-
self.box_numbers:
|
|
59
|
+
self.start_positions_um: XYZParams[float] = XYZParams(x=0, y=0, z=0)
|
|
60
|
+
self.box_numbers: XYZParams[int] = XYZParams(x=0, y=0, z=0)
|
|
51
61
|
|
|
52
62
|
def event(self, doc: Event):
|
|
53
63
|
data = doc.get("data")
|
|
@@ -82,13 +92,21 @@ class GridDetectionCallback(CallbackBase):
|
|
|
82
92
|
)
|
|
83
93
|
LOGGER.info(f"Calculated start position {position_grid_start_mm}")
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
self.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
# If data is taken at omega=~0 then it gives us x-y info, at omega=~-90 it is x-z
|
|
96
|
+
if abs(smargon_omega) < self.OMEGA_TOLERANCE:
|
|
97
|
+
self.start_positions_um["x"] = position_grid_start_mm[0] * 1000
|
|
98
|
+
self.start_positions_um["y"] = position_grid_start_mm[1] * 1000
|
|
99
|
+
self.box_numbers["x"] = data["oav-grid_snapshot-num_boxes_x"]
|
|
100
|
+
self.box_numbers["y"] = data["oav-grid_snapshot-num_boxes_y"]
|
|
101
|
+
elif abs(smargon_omega + 90) < self.OMEGA_TOLERANCE:
|
|
102
|
+
self.start_positions_um["x"] = position_grid_start_mm[0] * 1000
|
|
103
|
+
self.start_positions_um["z"] = position_grid_start_mm[2] * 1000
|
|
104
|
+
self.box_numbers["x"] = data["oav-grid_snapshot-num_boxes_x"]
|
|
105
|
+
self.box_numbers["z"] = data["oav-grid_snapshot-num_boxes_y"]
|
|
106
|
+
else:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Grid detection only works at omegas of 0 or -90, omega of {smargon_omega} given."
|
|
90
109
|
)
|
|
91
|
-
)
|
|
92
110
|
|
|
93
111
|
self.x_step_size_um = box_width_px * microns_per_pixel_x
|
|
94
112
|
self.y_step_size_um = box_width_px * microns_per_pixel_y
|
|
@@ -97,14 +115,14 @@ class GridDetectionCallback(CallbackBase):
|
|
|
97
115
|
|
|
98
116
|
def get_grid_parameters(self) -> GridParamUpdate:
|
|
99
117
|
return {
|
|
100
|
-
"x_start_um": self.
|
|
101
|
-
"y_start_um": self.
|
|
102
|
-
"y2_start_um": self.
|
|
103
|
-
"z_start_um": self.
|
|
104
|
-
"z2_start_um": self.
|
|
105
|
-
"x_steps": self.box_numbers[
|
|
106
|
-
"y_steps": self.box_numbers[
|
|
107
|
-
"z_steps": self.box_numbers[
|
|
118
|
+
"x_start_um": self.start_positions_um["x"],
|
|
119
|
+
"y_start_um": self.start_positions_um["y"],
|
|
120
|
+
"y2_start_um": self.start_positions_um["y"],
|
|
121
|
+
"z_start_um": self.start_positions_um["z"],
|
|
122
|
+
"z2_start_um": self.start_positions_um["z"],
|
|
123
|
+
"x_steps": self.box_numbers["x"],
|
|
124
|
+
"y_steps": self.box_numbers["y"],
|
|
125
|
+
"z_steps": self.box_numbers["z"],
|
|
108
126
|
"x_step_size_um": self.x_step_size_um,
|
|
109
127
|
"y_step_size_um": self.y_step_size_um,
|
|
110
128
|
"z_step_size_um": self.z_step_size_um,
|
mx_bluesky/common/external_interaction/callbacks/sample_handling/sample_handling_callback.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
from dodal.utils import get_beamline_name
|
|
1
2
|
from event_model import RunStart, RunStop
|
|
2
3
|
|
|
4
|
+
from mx_bluesky.common.external_interaction.alerting import (
|
|
5
|
+
Metadata,
|
|
6
|
+
get_alerting_service,
|
|
7
|
+
)
|
|
3
8
|
from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
|
|
4
9
|
PlanReactiveCallback,
|
|
5
10
|
)
|
|
@@ -12,36 +17,43 @@ from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
|
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
class SampleHandlingCallback(PlanReactiveCallback):
|
|
15
|
-
"""Intercepts exceptions from experiment plans and
|
|
16
|
-
field according to the type of exception raised.
|
|
20
|
+
"""Intercepts exceptions from experiment plans and:
|
|
21
|
+
* Updates the ISPyB BLSampleStatus field according to the type of exception raised.
|
|
22
|
+
* Triggers an alert with details of the error."""
|
|
17
23
|
|
|
18
24
|
def __init__(self, record_loaded_on_success=False):
|
|
19
25
|
super().__init__(log=ISPYB_ZOCALO_CALLBACK_LOGGER)
|
|
20
26
|
self._sample_id: int | None = None
|
|
27
|
+
self._visit: str | None = None
|
|
21
28
|
self._descriptor: str | None = None
|
|
22
29
|
self._run_id: str | None = None
|
|
30
|
+
self._container: int | None = None
|
|
23
31
|
|
|
24
32
|
# Record 'sample loaded' if document successfully stops
|
|
25
33
|
self.record_loaded_on_success = record_loaded_on_success
|
|
26
34
|
|
|
27
35
|
def activity_gated_start(self, doc: RunStart):
|
|
28
36
|
if not self._sample_id and self.active:
|
|
29
|
-
|
|
37
|
+
metadata = doc.get("metadata", {})
|
|
38
|
+
sample_id = metadata.get("sample_id")
|
|
30
39
|
self.log.info(f"Recording sample ID at run start {sample_id}")
|
|
31
40
|
self._sample_id = sample_id
|
|
41
|
+
self._visit = metadata.get("visit")
|
|
32
42
|
self._run_id = self.activity_uid
|
|
43
|
+
self._container = metadata.get("container")
|
|
33
44
|
|
|
34
45
|
def activity_gated_stop(self, doc: RunStop) -> RunStop:
|
|
35
46
|
if self._run_id == doc.get("run_start"):
|
|
36
47
|
expeye = ExpeyeInteraction()
|
|
37
48
|
if doc["exit_status"] != "success":
|
|
49
|
+
reason = doc.get("reason", "")
|
|
38
50
|
exception_type, message = SampleException.type_and_message_from_reason(
|
|
39
|
-
|
|
51
|
+
reason
|
|
40
52
|
)
|
|
41
53
|
self.log.info(
|
|
42
54
|
f"Sample handling callback intercepted exception of type {exception_type}: {message}"
|
|
43
55
|
)
|
|
44
|
-
self._record_exception(exception_type, expeye)
|
|
56
|
+
self._record_exception(exception_type, expeye, reason)
|
|
45
57
|
|
|
46
58
|
elif self.record_loaded_on_success:
|
|
47
59
|
self._record_loaded(expeye)
|
|
@@ -51,10 +63,23 @@ class SampleHandlingCallback(PlanReactiveCallback):
|
|
|
51
63
|
|
|
52
64
|
return doc
|
|
53
65
|
|
|
54
|
-
def _record_exception(
|
|
66
|
+
def _record_exception(
|
|
67
|
+
self, exception_type: str, expeye: ExpeyeInteraction, reason: str
|
|
68
|
+
):
|
|
55
69
|
assert self._sample_id, "Unable to record exception due to no sample ID"
|
|
56
70
|
sample_status = self._decode_sample_status(exception_type)
|
|
57
71
|
expeye.update_sample_status(self._sample_id, sample_status)
|
|
72
|
+
if sample_status == BLSampleStatus.ERROR_BEAMLINE:
|
|
73
|
+
beamline = get_beamline_name("")
|
|
74
|
+
get_alerting_service().raise_alert(
|
|
75
|
+
f"UDC encountered an error on {beamline}",
|
|
76
|
+
f"Hyperion encountered the following beamline error: {reason}",
|
|
77
|
+
{
|
|
78
|
+
Metadata.SAMPLE_ID: str(self._sample_id),
|
|
79
|
+
Metadata.VISIT: self._visit or "",
|
|
80
|
+
Metadata.CONTAINER: str(self._container),
|
|
81
|
+
},
|
|
82
|
+
)
|
|
58
83
|
|
|
59
84
|
def _decode_sample_status(self, exception_type: str) -> BLSampleStatus:
|
|
60
85
|
match exception_type:
|
|
@@ -1,57 +1,154 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import fields
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Generic, TypeVar
|
|
3
6
|
|
|
4
7
|
from daq_config_server.client import ConfigServer
|
|
5
|
-
from pydantic import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
8
|
+
from pydantic import TypeAdapter
|
|
9
|
+
|
|
10
|
+
from mx_bluesky.common.parameters.constants import (
|
|
11
|
+
GDA_DOMAIN_PROPERTIES_PATH,
|
|
12
|
+
FeatureSetting,
|
|
13
|
+
FeatureSettingources,
|
|
14
|
+
OavConstants,
|
|
15
|
+
)
|
|
16
|
+
from mx_bluesky.common.utils.log import LOGGER
|
|
17
|
+
|
|
18
|
+
FEATURE_FLAG_CACHE_LENGTH_S = 60 * 5
|
|
19
|
+
# Used by the config server when refreshing its cache
|
|
20
|
+
_JSON_CONFIG_PATHS = [OavConstants.OAV_CONFIG_JSON]
|
|
21
|
+
|
|
22
|
+
T = TypeVar("T", bound=FeatureSetting)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MXConfigClient(ConfigServer, Generic[T]):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
feature_sources: type[FeatureSettingources],
|
|
29
|
+
feature_dc: type[T],
|
|
30
|
+
url: str = "https://daq-config.diamond.ac.uk",
|
|
31
|
+
):
|
|
32
|
+
"""MX implementation of the config server client. Makes requests to the config server to retrieve config while falling back to
|
|
33
|
+
the filesystem in the case that the request failed.
|
|
34
|
+
|
|
35
|
+
See mx_bluesky/hyperion/external_interaction/config_server.py for example implementation.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
feature_sources: A StrEnum containing available features, where the string is the name of that feature toggle in a beamline's GDA
|
|
39
|
+
domain.properties.
|
|
40
|
+
|
|
41
|
+
feature_dc: A dataclass containing available features along with their default flags. This dataclass must contain the same keys
|
|
42
|
+
as the feature_sources parameter. These defaults are used when a server request fails.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
self.feature_sources = feature_sources
|
|
46
|
+
self.feature_dc: type[T] = feature_dc
|
|
47
|
+
self._cached_features: T | None = None
|
|
48
|
+
self._cached_json_config: dict[str, dict[str, Any]] = {}
|
|
49
|
+
self._time_of_last_feature_get: float = 0
|
|
50
|
+
self._verify_feature_parameters()
|
|
51
|
+
super().__init__(url)
|
|
52
|
+
|
|
53
|
+
def get_feature_flags(self) -> T:
|
|
54
|
+
"""Get feature flags by making a request to the config server. If the request fails, use the hardcoded defaults. Store results in a cache
|
|
55
|
+
which should be updated at the start of a plan using self.refresh_cache()
|
|
56
|
+
"""
|
|
57
|
+
return self._get_feature_flags()
|
|
58
|
+
|
|
59
|
+
def get_json_config(self, path_to_json: Path | str) -> dict[str, Any]:
|
|
60
|
+
"""Get the OAV config in the form of a python dictionary. Store results in a cache
|
|
61
|
+
which should be updated at the start of a plan using self.refresh_cache().
|
|
62
|
+
|
|
63
|
+
Note that this method replicates the functionality of the config server, but provides
|
|
64
|
+
a fallback incase of server. It will be removed once the config server has been run reliably
|
|
65
|
+
for several months.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
path_to_json: Absolute file path to json file
|
|
69
|
+
"""
|
|
70
|
+
return self._get_json_config(path_to_json)
|
|
71
|
+
|
|
72
|
+
def refresh_cache(self):
|
|
73
|
+
"""Refresh the client's cache. Use at the beginning of a plan"""
|
|
74
|
+
self._get_feature_flags(reset_cached_result=True)
|
|
75
|
+
for path in _JSON_CONFIG_PATHS:
|
|
76
|
+
self._get_json_config(path, reset_cached_result=True)
|
|
77
|
+
|
|
78
|
+
def _verify_feature_parameters(self):
|
|
79
|
+
sources_keys = {feature.name for feature in self.feature_sources}
|
|
80
|
+
feature_dc_keys = {key.name for key in fields(self.feature_dc)}
|
|
81
|
+
assert sources_keys == feature_dc_keys, (
|
|
82
|
+
f"MXConfig server feature_sources names do not match feature_dc keys: {sources_keys} != {feature_dc_keys}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _get_feature_flags(self, reset_cached_result=False) -> T:
|
|
86
|
+
"""
|
|
87
|
+
Args:
|
|
88
|
+
reset_cached_result (bool): Force refresh the cache for this request
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
if reset_cached_result:
|
|
92
|
+
self._cached_features = None
|
|
93
|
+
if not self._cached_features:
|
|
94
|
+
self._cached_features = None
|
|
95
|
+
# Construct self.feature_dc by reading the domain.properties file
|
|
96
|
+
all_features = list(self.feature_sources)
|
|
97
|
+
feature_dict = {}
|
|
98
|
+
domain_properties = self.get_file_contents(
|
|
99
|
+
GDA_DOMAIN_PROPERTIES_PATH, reset_cached_result=reset_cached_result
|
|
100
|
+
).splitlines()
|
|
101
|
+
for line in domain_properties:
|
|
102
|
+
line = line.strip()
|
|
103
|
+
if not line or line.startswith("#"):
|
|
104
|
+
continue
|
|
105
|
+
line = line.split("#", 1)[0].strip() # Remove inline comments
|
|
106
|
+
if "=" in line:
|
|
107
|
+
key, value = map(str.strip, line.split("=", 1))
|
|
108
|
+
for feature in all_features:
|
|
109
|
+
assert isinstance(feature, Enum)
|
|
110
|
+
if key == feature.value:
|
|
111
|
+
feature_dict[feature.name] = value
|
|
112
|
+
self._check_missing_fields(
|
|
113
|
+
{f.name for f in fields(self.feature_dc)}, set(feature_dict.keys())
|
|
114
|
+
)
|
|
115
|
+
self._cached_features = self.feature_dc(**feature_dict)
|
|
116
|
+
return self._cached_features
|
|
117
|
+
except Exception as e:
|
|
118
|
+
LOGGER.warning(
|
|
119
|
+
f"Failed to get feature flags from config server: {e} \nUsing defaults..."
|
|
120
|
+
)
|
|
121
|
+
return self.feature_dc()
|
|
122
|
+
|
|
123
|
+
def _check_missing_fields(self, expected: set, actual: set):
|
|
124
|
+
missing = expected - actual
|
|
125
|
+
if missing:
|
|
126
|
+
LOGGER.warning(
|
|
127
|
+
f"Missing features from domain.properties: {missing}.\n Using defaults for missing features"
|
|
56
128
|
)
|
|
57
|
-
|
|
129
|
+
|
|
130
|
+
def _get_json_config(
|
|
131
|
+
self, path_to_json: Path | str, reset_cached_result=False
|
|
132
|
+
) -> dict[str, Any]:
|
|
133
|
+
"""
|
|
134
|
+
Args:
|
|
135
|
+
reset_cached_result (bool): Force refresh the cache for this request
|
|
136
|
+
"""
|
|
137
|
+
if reset_cached_result:
|
|
138
|
+
self._cached_json_config = {}
|
|
139
|
+
str_to_json = str(path_to_json)
|
|
140
|
+
if str_to_json not in self._cached_json_config:
|
|
141
|
+
try:
|
|
142
|
+
self._cached_json_config[str_to_json] = self.get_file_contents(
|
|
143
|
+
path_to_json, dict, reset_cached_result=reset_cached_result
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
LOGGER.warning(
|
|
148
|
+
f"Failed to get json config from config server: {e} \nReading the file directly..."
|
|
149
|
+
)
|
|
150
|
+
with open(path_to_json) as f:
|
|
151
|
+
self._cached_json_config[str_to_json] = TypeAdapter(
|
|
152
|
+
dict[str, Any]
|
|
153
|
+
).validate_python(json.loads(f.read()))
|
|
154
|
+
return self._cached_json_config[str_to_json]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from enum import Enum
|
|
2
|
+
from enum import Enum, StrEnum
|
|
3
3
|
|
|
4
4
|
from dodal.devices.aperturescatterguard import ApertureValue
|
|
5
5
|
from dodal.devices.detector import EIGER2_X_16M_SIZE
|
|
@@ -13,10 +13,17 @@ BEAMLINE = get_beamline_name("test")
|
|
|
13
13
|
TEST_MODE = BEAMLINE == "test"
|
|
14
14
|
ZEBRA_STATUS_TIMEOUT = 30
|
|
15
15
|
|
|
16
|
+
GDA_DOMAIN_PROPERTIES_PATH = (
|
|
17
|
+
"tests/test_data/test_domain_properties"
|
|
18
|
+
if TEST_MODE
|
|
19
|
+
else (f"/dls_sw/{BEAMLINE}/software/daq_configuration/domain/domain.properties")
|
|
20
|
+
)
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
@dataclass(frozen=True)
|
|
18
24
|
class DocDescriptorNames:
|
|
19
25
|
# Robot load/unload event descriptor
|
|
26
|
+
ROBOT_PRE_LOAD = "robot_update_pre_load"
|
|
20
27
|
ROBOT_UPDATE = "robot_update"
|
|
21
28
|
# For callbacks to use
|
|
22
29
|
OAV_ROTATION_SNAPSHOT_TRIGGERED = "rotation_snapshot_triggered"
|
|
@@ -28,15 +35,18 @@ class DocDescriptorNames:
|
|
|
28
35
|
FLYSCAN_RESULTS = "flyscan_results_obtained"
|
|
29
36
|
|
|
30
37
|
|
|
38
|
+
def _get_oav_config_json_path():
|
|
39
|
+
if TEST_MODE:
|
|
40
|
+
return "tests/test_data/test_OAVCentring.json"
|
|
41
|
+
elif BEAMLINE == "i03":
|
|
42
|
+
return f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring_hyperion.json"
|
|
43
|
+
else:
|
|
44
|
+
return f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring.json"
|
|
45
|
+
|
|
46
|
+
|
|
31
47
|
@dataclass(frozen=True)
|
|
32
48
|
class OavConstants:
|
|
33
|
-
OAV_CONFIG_JSON = (
|
|
34
|
-
"tests/test_data/test_OAVCentring.json"
|
|
35
|
-
if TEST_MODE
|
|
36
|
-
else (
|
|
37
|
-
f"/dls_sw/{BEAMLINE}/software/daq_configuration/json/OAVCentring_hyperion.json"
|
|
38
|
-
)
|
|
39
|
-
)
|
|
49
|
+
OAV_CONFIG_JSON = _get_oav_config_json_path()
|
|
40
50
|
|
|
41
51
|
|
|
42
52
|
@dataclass(frozen=True)
|
|
@@ -150,3 +160,12 @@ class Status(Enum):
|
|
|
150
160
|
BUSY = "Busy"
|
|
151
161
|
ABORTING = "Aborting"
|
|
152
162
|
IDLE = "Idle"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class FeatureSetting: ... # List of features and their default values. Subclasses must also be a pydantic dataclass
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class FeatureSettingources(
|
|
170
|
+
StrEnum
|
|
171
|
+
): ... # List of features and the name of that property in domain.properties
|
|
@@ -102,7 +102,7 @@ class SpecifiedThreeDGridScan(
|
|
|
102
102
|
"""Parameters representing a so-called 3D grid scan, which consists of doing a
|
|
103
103
|
gridscan in X and Y, followed by one in X and Z."""
|
|
104
104
|
|
|
105
|
-
grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1)
|
|
105
|
+
grid1_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_1)
|
|
106
106
|
grid2_omega_deg: float = Field(default=GridscanParamConstants.OMEGA_2)
|
|
107
107
|
x_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|
|
108
108
|
y_step_size_um: float = Field(default=GridscanParamConstants.BOX_WIDTH_UM)
|