dls-dodal 1.66.0__py3-none-any.whl → 1.67.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 (48) hide show
  1. {dls_dodal-1.66.0.dist-info → dls_dodal-1.67.0.dist-info}/METADATA +1 -1
  2. {dls_dodal-1.66.0.dist-info → dls_dodal-1.67.0.dist-info}/RECORD +47 -37
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/i03.py +92 -208
  5. dodal/beamlines/i04.py +22 -1
  6. dodal/beamlines/i05.py +1 -1
  7. dodal/beamlines/i06.py +1 -1
  8. dodal/beamlines/i09_1.py +26 -2
  9. dodal/beamlines/i09_2.py +57 -2
  10. dodal/beamlines/i10_optics.py +44 -25
  11. dodal/beamlines/i17.py +7 -3
  12. dodal/beamlines/i19_1.py +26 -14
  13. dodal/beamlines/i19_2.py +49 -38
  14. dodal/beamlines/i21.py +2 -2
  15. dodal/beamlines/i22.py +16 -1
  16. dodal/beamlines/training_rig.py +0 -16
  17. dodal/cli.py +26 -12
  18. dodal/common/coordination.py +3 -2
  19. dodal/device_manager.py +604 -0
  20. dodal/devices/cryostream.py +28 -57
  21. dodal/devices/eiger.py +26 -18
  22. dodal/devices/i04/max_pixel.py +38 -0
  23. dodal/devices/i09_1_shared/__init__.py +8 -1
  24. dodal/devices/i09_1_shared/hard_energy.py +112 -0
  25. dodal/devices/i09_2_shared/__init__.py +0 -0
  26. dodal/devices/i09_2_shared/i09_apple2.py +86 -0
  27. dodal/devices/i10/i10_apple2.py +23 -21
  28. dodal/devices/i17/i17_apple2.py +32 -20
  29. dodal/devices/i19/access_controlled/attenuator_motor_squad.py +61 -0
  30. dodal/devices/i19/access_controlled/blueapi_device.py +9 -1
  31. dodal/devices/i19/access_controlled/shutter.py +2 -4
  32. dodal/devices/insertion_device/__init__.py +0 -0
  33. dodal/devices/{apple2_undulator.py → insertion_device/apple2_undulator.py} +36 -28
  34. dodal/devices/insertion_device/energy_motor_lookup.py +88 -0
  35. dodal/devices/insertion_device/lookup_table_models.py +287 -0
  36. dodal/devices/motors.py +14 -0
  37. dodal/devices/robot.py +16 -11
  38. dodal/plans/__init__.py +1 -1
  39. dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -4
  40. dodal/testing/fixtures/devices/__init__.py +0 -0
  41. dodal/testing/fixtures/devices/apple2.py +78 -0
  42. dodal/utils.py +6 -3
  43. dodal/devices/util/lookup_tables_apple2.py +0 -390
  44. {dls_dodal-1.66.0.dist-info → dls_dodal-1.67.0.dist-info}/WHEEL +0 -0
  45. {dls_dodal-1.66.0.dist-info → dls_dodal-1.67.0.dist-info}/entry_points.txt +0 -0
  46. {dls_dodal-1.66.0.dist-info → dls_dodal-1.67.0.dist-info}/licenses/LICENSE +0 -0
  47. {dls_dodal-1.66.0.dist-info → dls_dodal-1.67.0.dist-info}/top_level.txt +0 -0
  48. /dodal/plans/{scanspec.py → spec_path.py} +0 -0
dodal/devices/robot.py CHANGED
@@ -71,6 +71,7 @@ class BartRobot(StandardReadable, Movable[SampleLocation | None]):
71
71
  LOAD_TIMEOUT = 60
72
72
 
73
73
  # Error codes that we do special things on
74
+ NO_ERROR = 0
74
75
  NO_PIN_ERROR_CODE = 25
75
76
  LIGHT_CURTAIN_TRIPPED = 40
76
77
 
@@ -115,21 +116,25 @@ class BartRobot(StandardReadable, Movable[SampleLocation | None]):
115
116
  )
116
117
  super().__init__(name=name)
117
118
 
118
- async def pin_mounted_or_no_pin_found(self):
119
- """This co-routine will finish when either a pin is detected or the robot gives
120
- an error saying no pin was found (whichever happens first). In the case where no
121
- pin was found a RobotLoadError error is raised.
119
+ async def pin_state_or_error(self, expected_state=PinMounted.PIN_MOUNTED):
120
+ """This co-routine will finish when either the pin sensor reaches the specified
121
+ state or the robot gives an error (whichever happens first). In the case where
122
+ there is an error a RobotLoadError error is raised.
122
123
  """
123
124
 
124
- async def raise_if_no_pin():
125
- await wait_for_value(self.prog_error.code, self.NO_PIN_ERROR_CODE, None)
126
- raise RobotLoadError(self.NO_PIN_ERROR_CODE, "Pin was not detected")
125
+ async def raise_if_error():
126
+ await wait_for_value(
127
+ self.prog_error.code, lambda value: value != self.NO_ERROR, None
128
+ )
129
+ error_code = await self.prog_error.code.get_value()
130
+ error_msg = await self.prog_error.str.get_value()
131
+ raise RobotLoadError(error_code, error_msg)
127
132
 
128
133
  async def wfv():
129
- await wait_for_value(self.gonio_pin_sensor, PinMounted.PIN_MOUNTED, None)
134
+ await wait_for_value(self.gonio_pin_sensor, expected_state, None)
130
135
 
131
136
  tasks = [
132
- (Task(raise_if_no_pin())),
137
+ (Task(raise_if_error())),
133
138
  (Task(wfv())),
134
139
  ]
135
140
  try:
@@ -168,10 +173,10 @@ class BartRobot(StandardReadable, Movable[SampleLocation | None]):
168
173
  await self.load.trigger()
169
174
  if await self.gonio_pin_sensor.get_value() == PinMounted.PIN_MOUNTED:
170
175
  LOGGER.info(WAIT_FOR_OLD_PIN_MSG)
171
- await wait_for_value(self.gonio_pin_sensor, PinMounted.NO_PIN_MOUNTED, None)
176
+ await self.pin_state_or_error(PinMounted.NO_PIN_MOUNTED)
172
177
  LOGGER.info(WAIT_FOR_NEW_PIN_MSG)
173
178
 
174
- await self.pin_mounted_or_no_pin_found()
179
+ await self.pin_state_or_error()
175
180
 
176
181
  @AsyncStatus.wrap
177
182
  async def set(self, value: SampleLocation | None):
dodal/plans/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from .scanspec import spec_scan
1
+ from .spec_path import spec_scan
2
2
  from .wrapped import count
3
3
 
4
4
  __all__ = ["count", "spec_scan"]
@@ -12,7 +12,7 @@ from ophyd_async.core import (
12
12
  )
13
13
  from ophyd_async.fastcs.eiger import EigerDetector
14
14
 
15
- from dodal.beamlines.i03 import fastcs_eiger, set_path_provider
15
+ from dodal.beamlines.i03 import fastcs_eiger
16
16
  from dodal.devices.detector import DetectorParams
17
17
  from dodal.log import LOGGER, do_default_logging_setup
18
18
 
@@ -163,9 +163,7 @@ if __name__ == "__main__":
163
163
  PurePath("/dls/i03/data/2025/cm40607-2/test_new_eiger/"),
164
164
  )
165
165
 
166
- set_path_provider(path_provider)
167
-
168
- eiger = fastcs_eiger(connect_immediately=True)
166
+ eiger = fastcs_eiger.build(connect_immediately=True, path_provider=path_provider)
169
167
  run_engine(
170
168
  configure_arm_trigger_and_disarm_detector(
171
169
  eiger=eiger,
File without changes
@@ -0,0 +1,78 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ import pytest
4
+ from daq_config_server.client import ConfigServer
5
+ from ophyd_async.core import (
6
+ init_devices,
7
+ set_mock_value,
8
+ )
9
+
10
+ from dodal.devices.insertion_device.apple2_undulator import (
11
+ EnabledDisabledUpper,
12
+ UndulatorGap,
13
+ UndulatorGateStatus,
14
+ UndulatorJawPhase,
15
+ UndulatorPhaseAxes,
16
+ )
17
+
18
+
19
+ @pytest.fixture
20
+ def mock_config_client() -> ConfigServer:
21
+ mock_config_client = ConfigServer()
22
+
23
+ mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"])
24
+
25
+ def my_side_effect(file_path, reset_cached_result) -> str:
26
+ assert reset_cached_result is True
27
+ with open(file_path) as f:
28
+ return f.read()
29
+
30
+ mock_config_client.get_file_contents.side_effect = my_side_effect
31
+ return mock_config_client
32
+
33
+
34
+ @pytest.fixture
35
+ async def mock_id_gap(prefix: str = "BLXX-EA-DET-007:") -> UndulatorGap:
36
+ async with init_devices(mock=True):
37
+ mock_id_gap = UndulatorGap(prefix, "mock_id_gap")
38
+ assert mock_id_gap.name == "mock_id_gap"
39
+ set_mock_value(mock_id_gap.gate, UndulatorGateStatus.CLOSE)
40
+ set_mock_value(mock_id_gap.velocity, 1)
41
+ set_mock_value(mock_id_gap.user_readback, 1)
42
+ set_mock_value(mock_id_gap.user_setpoint, "1")
43
+ set_mock_value(mock_id_gap.status, EnabledDisabledUpper.ENABLED)
44
+ return mock_id_gap
45
+
46
+
47
+ @pytest.fixture
48
+ async def mock_phase_axes(prefix: str = "BLXX-EA-DET-007:") -> UndulatorPhaseAxes:
49
+ async with init_devices(mock=True):
50
+ mock_phase_axes = UndulatorPhaseAxes(
51
+ prefix=prefix,
52
+ top_outer="RPQ1",
53
+ top_inner="RPQ2",
54
+ btm_outer="RPQ3",
55
+ btm_inner="RPQ4",
56
+ )
57
+ assert mock_phase_axes.name == "mock_phase_axes"
58
+ set_mock_value(mock_phase_axes.gate, UndulatorGateStatus.CLOSE)
59
+ set_mock_value(mock_phase_axes.top_outer.velocity, 2)
60
+ set_mock_value(mock_phase_axes.top_inner.velocity, 2)
61
+ set_mock_value(mock_phase_axes.btm_outer.velocity, 2)
62
+ set_mock_value(mock_phase_axes.btm_inner.velocity, 2)
63
+ set_mock_value(mock_phase_axes.status, EnabledDisabledUpper.ENABLED)
64
+ return mock_phase_axes
65
+
66
+
67
+ @pytest.fixture
68
+ async def mock_jaw_phase(prefix: str = "BLXX-EA-DET-007:") -> UndulatorJawPhase:
69
+ async with init_devices(mock=True):
70
+ mock_jaw_phase = UndulatorJawPhase(
71
+ prefix=prefix, move_pv="RPQ1", jaw_phase="JAW"
72
+ )
73
+ set_mock_value(mock_jaw_phase.gate, UndulatorGateStatus.CLOSE)
74
+ set_mock_value(mock_jaw_phase.jaw_phase.velocity, 2)
75
+ set_mock_value(mock_jaw_phase.jaw_phase.user_readback, 0)
76
+ set_mock_value(mock_jaw_phase.jaw_phase.user_setpoint_readback, 0)
77
+ set_mock_value(mock_jaw_phase.status, EnabledDisabledUpper.ENABLED)
78
+ return mock_jaw_phase
dodal/utils.py CHANGED
@@ -11,7 +11,7 @@ from functools import update_wrapper, wraps
11
11
  from importlib import import_module
12
12
  from inspect import signature
13
13
  from os import environ
14
- from types import ModuleType
14
+ from types import FunctionType, ModuleType
15
15
  from typing import (
16
16
  Any,
17
17
  Generic,
@@ -240,7 +240,8 @@ def make_device(
240
240
  def make_all_devices(
241
241
  module: str | ModuleType | None = None, include_skipped: bool = False, **kwargs
242
242
  ) -> tuple[dict[str, AnyDevice], dict[str, Exception]]:
243
- """Makes all devices in the given beamline module.
243
+ """Makes all devices in the given beamline module, for those modules using device factories
244
+ as opposed to the DeviceManager.
244
245
 
245
246
  In cases of device interdependencies it ensures a device is created before any which
246
247
  depend on it.
@@ -413,7 +414,9 @@ def is_v2_device_factory(func: Callable) -> TypeGuard[V2DeviceFactory]:
413
414
 
414
415
 
415
416
  def is_any_device_factory(func: Callable) -> TypeGuard[AnyDeviceFactory]:
416
- return is_v1_device_factory(func) or is_v2_device_factory(func)
417
+ return isinstance(func, FunctionType | DeviceInitializationController) and (
418
+ is_v1_device_factory(func) or is_v2_device_factory(func)
419
+ )
417
420
 
418
421
 
419
422
  def is_v2_device_type(obj: type[Any]) -> bool:
@@ -1,390 +0,0 @@
1
- """Apple2 lookup table utilities and CSV converter.
2
-
3
- This module provides helpers to read, validate and convert Apple2 insertion-device
4
- lookup tables (energy -> gap/phase polynomials) from CSV sources into an
5
- in-memory dictionary format used by the Apple2 controllers.
6
-
7
- Data format produced
8
- The lookup-table dictionary created by convert_csv_to_lookup() follows this
9
- structure:
10
-
11
- {
12
- "POL_MODE": {
13
- "energies": {
14
- "<min_energy>": {
15
- "low": <float>,
16
- "high": <float>,
17
- "poly": <numpy.poly1d>
18
- },
19
- ...
20
- },
21
- "limit": {
22
- "minimum": <float>,
23
- "maximum": <float>
24
- }
25
- },
26
- }
27
-
28
- """
29
-
30
- import csv
31
- import io
32
- from collections.abc import Generator
33
- from pathlib import Path
34
-
35
- import numpy as np
36
- from daq_config_server.client import ConfigServer
37
- from pydantic import (
38
- BaseModel,
39
- ConfigDict,
40
- Field,
41
- RootModel,
42
- field_serializer,
43
- field_validator,
44
- )
45
-
46
- from dodal.devices.apple2_undulator import Pol
47
- from dodal.log import LOGGER
48
-
49
- DEFAULT_GAP_FILE = "IDEnergy2GapCalibrations.csv"
50
- DEFAULT_PHASE_FILE = "IDEnergy2PhaseCalibrations.csv"
51
-
52
-
53
- DEFAULT_POLY_DEG = [
54
- "7th-order",
55
- "6th-order",
56
- "5th-order",
57
- "4th-order",
58
- "3rd-order",
59
- "2nd-order",
60
- "1st-order",
61
- "b",
62
- ]
63
-
64
- MODE_NAME_CONVERT = {"CR": "pc", "CL": "nc"}
65
-
66
-
67
- class LookupTableConfig(BaseModel):
68
- source: tuple[str, str] | None = None
69
- mode: str = "Mode"
70
- min_energy: str = "MinEnergy"
71
- max_energy: str = "MaxEnergy"
72
- poly_deg: list[str] = Field(default_factory=lambda: DEFAULT_POLY_DEG)
73
- mode_name_convert: dict[str, str] = Field(default_factory=lambda: MODE_NAME_CONVERT)
74
-
75
-
76
- class EnergyMinMax(BaseModel):
77
- minimum: float
78
- maximum: float
79
-
80
-
81
- class EnergyCoverageEntry(BaseModel):
82
- model_config = ConfigDict(arbitrary_types_allowed=True) # So np.poly1d can be used.
83
- low: float
84
- high: float
85
- poly: np.poly1d
86
-
87
- @field_validator("poly", mode="before")
88
- @classmethod
89
- def validate_and_convert_poly(cls, value):
90
- """If reading from serialized data, it will be using a list. Convert to np.poly1d"""
91
- if isinstance(value, list):
92
- return np.poly1d(value)
93
- return value
94
-
95
- @field_serializer("poly", mode="plain")
96
- def serialize_poly(self, value: np.poly1d) -> list:
97
- """Allow np.poly1d to work when serializing."""
98
- return value.coefficients.tolist()
99
-
100
-
101
- class EnergyCoverage(RootModel[dict[float, EnergyCoverageEntry]]):
102
- pass
103
-
104
-
105
- class LookupTableEntries(BaseModel):
106
- energies: EnergyCoverage
107
- limit: EnergyMinMax
108
-
109
-
110
- class LookupTable(RootModel[dict[Pol, LookupTableEntries]]):
111
- # Allow to auto specify a dict if one not provided
112
- def __init__(self, root: dict[Pol, LookupTableEntries] | None = None):
113
- super().__init__(root=root or {})
114
-
115
-
116
- class GapPhaseLookupTables(BaseModel):
117
- gap: LookupTable = Field(default_factory=lambda: LookupTable())
118
- phase: LookupTable = Field(default_factory=lambda: LookupTable())
119
-
120
-
121
- def convert_csv_to_lookup(
122
- file_contents: str,
123
- lut_config: LookupTableConfig,
124
- skip_line_start_with: str = "#",
125
- ) -> LookupTable:
126
- """
127
- Convert CSV content into the Apple2 lookup-table dictionary.
128
-
129
- Parameters:
130
- -----------
131
- file_contents:
132
- The CSV file contents as string.
133
- lut_config:
134
- The configuration that how to process the file_contents into a LookupTable.
135
- skip_line_start_with
136
- Lines beginning with this prefix are skipped (default "#").
137
-
138
- Returns:
139
- -----------
140
- LookupTable
141
- """
142
-
143
- def process_row(row: dict, lut: LookupTable):
144
- """Process a single row from the CSV file and update the lookup table."""
145
- mode_value = str(row[lut_config.mode]).lower()
146
- if mode_value in lut_config.mode_name_convert:
147
- mode_value = lut_config.mode_name_convert[f"{mode_value}"]
148
- mode_value = Pol(mode_value)
149
-
150
- # Create polynomial object for energy-to-gap/phase conversion
151
- coefficients = [float(row[coef]) for coef in lut_config.poly_deg]
152
- if mode_value not in lut.root:
153
- lut.root[mode_value] = generate_lookup_table_entry(
154
- min_energy=float(row[lut_config.min_energy]),
155
- max_energy=float(row[lut_config.max_energy]),
156
- poly1d_param=coefficients,
157
- )
158
-
159
- else:
160
- lut.root[mode_value].energies.root[float(row[lut_config.min_energy])] = (
161
- EnergyCoverageEntry(
162
- low=float(row[lut_config.min_energy]),
163
- high=float(row[lut_config.max_energy]),
164
- poly=np.poly1d(coefficients),
165
- )
166
- )
167
-
168
- # Update energy limits
169
- lut.root[mode_value].limit.minimum = min(
170
- lut.root[mode_value].limit.minimum,
171
- float(row[lut_config.min_energy]),
172
- )
173
- lut.root[mode_value].limit.maximum = max(
174
- lut.root[mode_value].limit.maximum,
175
- float(row[lut_config.max_energy]),
176
- )
177
- return lut
178
-
179
- reader = csv.DictReader(read_file_and_skip(file_contents, skip_line_start_with))
180
- lut = LookupTable()
181
-
182
- for row in reader:
183
- # If there are multiple source only convert requested.
184
- if lut_config.source is not None:
185
- if row[lut_config.source[0]] == lut_config.source[1]:
186
- process_row(row=row, lut=lut)
187
- else:
188
- process_row(row=row, lut=lut)
189
-
190
- # Check if our LookupTable is empty after processing, raise error if it is.
191
- if not lut.root:
192
- raise RuntimeError(
193
- "LookupTable content is empty, failed to convert the file contents to "
194
- "a LookupTable!"
195
- )
196
- return lut
197
-
198
-
199
- def read_file_and_skip(file: str, skip_line_start_with: str = "#") -> Generator[str]:
200
- """Yield non-comment lines from the CSV content string."""
201
- for line in io.StringIO(file):
202
- if line.startswith(skip_line_start_with):
203
- continue
204
- else:
205
- yield line
206
-
207
-
208
- def get_poly(
209
- energy: float,
210
- pol: Pol,
211
- lookup_table: LookupTable,
212
- ) -> np.poly1d:
213
- """
214
- Return the numpy.poly1d polynomial applicable for the given energy and polarisation.
215
-
216
- Parameters:
217
- -----------
218
- energy:
219
- Energy value in the same units used to create the lookup table (eV).
220
- pol:
221
- Polarisation mode (Pol enum).
222
- lookup_table:
223
- The converted lookup table dictionary for either 'gap' or 'phase'.
224
- """
225
- if (
226
- energy < lookup_table.root[pol].limit.minimum
227
- or energy > lookup_table.root[pol].limit.maximum
228
- ):
229
- raise ValueError(
230
- "Demanding energy must lie between"
231
- + f" {lookup_table.root[pol].limit.minimum}"
232
- + f" and {lookup_table.root[pol].limit.maximum} eV!"
233
- )
234
- else:
235
- for energy_range in lookup_table.root[pol].energies.root.values():
236
- if energy >= energy_range.low and energy < energy_range.high:
237
- return energy_range.poly
238
-
239
- raise ValueError(
240
- "Cannot find polynomial coefficients for your requested energy."
241
- + " There might be gap in the calibration lookup table."
242
- )
243
-
244
-
245
- def generate_lookup_table_entry(
246
- min_energy: float, max_energy: float, poly1d_param: list[float]
247
- ) -> LookupTableEntries:
248
- return LookupTableEntries(
249
- energies=EnergyCoverage(
250
- {
251
- min_energy: EnergyCoverageEntry(
252
- low=min_energy,
253
- high=max_energy,
254
- poly=np.poly1d(poly1d_param),
255
- )
256
- }
257
- ),
258
- limit=EnergyMinMax(
259
- minimum=float(min_energy),
260
- maximum=float(max_energy),
261
- ),
262
- )
263
-
264
-
265
- def generate_lookup_table(
266
- pol: Pol, min_energy: float, max_energy: float, poly1d_param: list[float]
267
- ) -> LookupTable:
268
- return LookupTable(
269
- {pol: generate_lookup_table_entry(min_energy, max_energy, poly1d_param)}
270
- )
271
-
272
-
273
- def make_phase_tables(
274
- pols: list[Pol],
275
- min_energies: list[float],
276
- max_energies: list[float],
277
- poly1d_params: list[list[float]],
278
- ) -> LookupTable:
279
- """Generate a dictionary containing multiple lookuptable entries
280
- for provided polarisations."""
281
- lookuptable_phase = LookupTable()
282
- for i in range(len(pols)):
283
- lookuptable_phase.root[pols[i]] = generate_lookup_table_entry(
284
- min_energy=min_energies[i],
285
- max_energy=max_energies[i],
286
- poly1d_param=poly1d_params[i],
287
- )
288
-
289
- return lookuptable_phase
290
-
291
-
292
- class EnergyMotorLookup:
293
- """
294
- Handles lookup tables for Apple2 ID, converting energy and polarisation to gap
295
- and phase. Fetches and parses lookup tables from a config server, supports dynamic
296
- updates, and validates input. If custom logic is required for lookup tables, sub
297
- classes should override the _update_gap_lut and _update_phase_lut methods.
298
-
299
- After update_lookuptable() has populated the 'gap' and 'phase' tables,
300
- `get_motor_from_energy()` can be used to compute (gap, phase) for a requested
301
- (energy, pol) pair.
302
- """
303
-
304
- def __init__(
305
- self,
306
- config_client: ConfigServer,
307
- lut_config: LookupTableConfig,
308
- gap_path: Path,
309
- phase_path: Path,
310
- ):
311
- """Initialise the EnergyMotorLookup class with lookup table headers provided.
312
-
313
- Parameters:
314
- -----------
315
- config_client:
316
- The config server client to fetch the look up table data.
317
- lut_config:
318
- Configuration that defines how to process file contents into a LookupTable
319
- gap_path:
320
- File path to the gap lookup table.
321
- phase_path:
322
- File path to the phase lookup table.
323
- """
324
- self.lookup_tables = GapPhaseLookupTables()
325
- self.config_client = config_client
326
- self.lut_config = lut_config
327
- self.gap_path = gap_path
328
- self.phase_path = phase_path
329
- self._available_pol = []
330
-
331
- @property
332
- def available_pol(self) -> list[Pol]:
333
- return self._available_pol
334
-
335
- @available_pol.setter
336
- def available_pol(self, value: list[Pol]) -> None:
337
- self._available_pol = value
338
-
339
- def _update_gap_lut(self) -> None:
340
- file_contents = self.config_client.get_file_contents(
341
- self.gap_path, reset_cached_result=True
342
- )
343
- self.lookup_tables.gap = convert_csv_to_lookup(
344
- file_contents, lut_config=self.lut_config
345
- )
346
- self.available_pol = list(self.lookup_tables.gap.root.keys())
347
-
348
- def _update_phase_lut(self) -> None:
349
- file_contents = self.config_client.get_file_contents(
350
- self.phase_path, reset_cached_result=True
351
- )
352
- self.lookup_tables.phase = convert_csv_to_lookup(
353
- file_contents, lut_config=self.lut_config
354
- )
355
-
356
- def update_lookuptables(self):
357
- """
358
- Update lookup tables from files and validate their format.
359
- """
360
- LOGGER.info("Updating lookup table from file for gap.")
361
- self._update_gap_lut()
362
- LOGGER.info("Updating lookup table from file for phase.")
363
- self._update_phase_lut()
364
-
365
- def get_motor_from_energy(self, energy: float, pol: Pol) -> tuple[float, float]:
366
- """
367
- Convert energy and polarisation to gap and phase motor positions.
368
-
369
- Parameters:
370
- -----------
371
- energy : float
372
- Desired energy in eV.
373
- pol : Pol
374
- Polarisation mode.
375
-
376
- Returns:
377
- ----------
378
- tuple[float, float]
379
- (gap, phase) motor positions.
380
- """
381
- if self.available_pol == []:
382
- self.update_lookuptables()
383
-
384
- gap_poly = get_poly(lookup_table=self.lookup_tables.gap, energy=energy, pol=pol)
385
- phase_poly = get_poly(
386
- lookup_table=self.lookup_tables.phase,
387
- energy=energy,
388
- pol=pol,
389
- )
390
- return gap_poly(energy), phase_poly(energy)
File without changes