dls-dodal 1.62.0__py3-none-any.whl → 1.64.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.
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/RECORD +89 -76
- dls_dodal-1.64.0.dist-info/entry_points.txt +3 -0
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/adsim.py +5 -3
- dodal/beamlines/b21.py +3 -1
- dodal/beamlines/i02_2.py +32 -0
- dodal/beamlines/i03.py +9 -0
- dodal/beamlines/i07.py +21 -0
- dodal/beamlines/i09.py +11 -4
- dodal/beamlines/i09_1.py +10 -4
- dodal/beamlines/i09_2.py +30 -0
- dodal/beamlines/i10.py +7 -69
- dodal/beamlines/i10_1.py +35 -0
- dodal/beamlines/i10_optics.py +231 -0
- dodal/beamlines/i15_1.py +5 -5
- dodal/beamlines/i17.py +60 -1
- dodal/beamlines/i18.py +15 -9
- dodal/beamlines/i19_1.py +3 -3
- dodal/beamlines/i19_2.py +2 -2
- dodal/beamlines/i19_optics.py +4 -1
- dodal/beamlines/i21.py +31 -1
- dodal/beamlines/i24.py +3 -3
- dodal/cli.py +7 -7
- dodal/common/visit.py +4 -4
- dodal/devices/aperturescatterguard.py +6 -4
- dodal/devices/apple2_undulator.py +225 -126
- dodal/devices/attenuator/filter_selections.py +6 -6
- dodal/devices/b07_1/ccmc.py +1 -1
- dodal/devices/common_dcm.py +63 -16
- dodal/devices/current_amplifiers/femto.py +4 -4
- dodal/devices/current_amplifiers/sr570.py +3 -3
- dodal/devices/fast_grid_scan.py +4 -4
- dodal/devices/fast_shutter.py +19 -7
- dodal/devices/i02_2/__init__.py +0 -0
- dodal/devices/i03/dcm.py +4 -2
- dodal/devices/i03/undulator_dcm.py +1 -1
- dodal/devices/i04/murko_results.py +35 -14
- dodal/devices/i07/__init__.py +0 -0
- dodal/devices/i07/dcm.py +33 -0
- dodal/devices/i09/__init__.py +1 -2
- dodal/devices/i09_1_shared/__init__.py +3 -0
- dodal/devices/i09_1_shared/hard_undulator_functions.py +111 -0
- dodal/devices/i10/__init__.py +29 -0
- dodal/devices/i10/diagnostics.py +37 -5
- dodal/devices/i10/i10_apple2.py +125 -229
- dodal/devices/i10/slits.py +38 -6
- dodal/devices/i15/dcm.py +7 -46
- dodal/devices/i17/__init__.py +0 -0
- dodal/devices/i17/i17_apple2.py +51 -0
- dodal/devices/i19/access_controlled/__init__.py +0 -0
- dodal/devices/i19/{shutter.py → access_controlled/shutter.py} +7 -4
- dodal/devices/i22/dcm.py +3 -3
- dodal/devices/i24/dcm.py +2 -2
- dodal/devices/oav/oav_detector.py +1 -1
- dodal/devices/oav/oav_parameters.py +4 -4
- dodal/devices/oav/oav_to_redis_forwarder.py +4 -4
- dodal/devices/oav/pin_image_recognition/__init__.py +3 -3
- dodal/devices/oav/pin_image_recognition/utils.py +1 -1
- dodal/devices/oav/snapshots/snapshot.py +1 -1
- dodal/devices/oav/snapshots/snapshot_image_processing.py +12 -12
- dodal/devices/oav/snapshots/snapshot_with_grid.py +1 -1
- dodal/devices/oav/utils.py +2 -2
- dodal/devices/pgm.py +3 -3
- dodal/devices/robot.py +5 -5
- dodal/devices/tetramm.py +9 -5
- dodal/devices/thawer.py +0 -4
- dodal/devices/util/lookup_tables.py +8 -2
- dodal/devices/v2f.py +2 -2
- dodal/devices/zebra/zebra_constants_mapping.py +2 -2
- dodal/devices/zocalo/__init__.py +4 -4
- dodal/devices/zocalo/zocalo_results.py +4 -4
- dodal/log.py +9 -9
- dodal/plan_stubs/motor_utils.py +4 -4
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -2
- dodal/plans/save_panda.py +7 -7
- dodal/plans/verify_undulator_gap.py +4 -4
- dodal/testing/fixtures/__init__.py +0 -0
- dodal/testing/fixtures/run_engine.py +46 -0
- dodal/testing/fixtures/utils.py +57 -0
- dls_dodal-1.62.0.dist-info/entry_points.txt +0 -3
- dodal/beamlines/i10-1.py +0 -25
- dodal/devices/i09/dcm.py +0 -26
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/areadetector/plugins/{CAM.py → cam.py} +0 -0
- /dodal/devices/areadetector/plugins/{MJPG.py → mjpg.py} +0 -0
- /dodal/devices/i18/{KBMirror.py → kb_mirror.py} +0 -0
- /dodal/devices/i19/{blueapi_device.py → access_controlled/blueapi_device.py} +0 -0
- /dodal/devices/i19/{hutch_access.py → access_controlled/hutch_access.py} +0 -0
dodal/cli.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
from bluesky.run_engine import RunEngine
|
|
7
|
-
from ophyd_async.core import
|
|
7
|
+
from ophyd_async.core import NotConnectedError, StaticPathProvider, UUIDFilenameProvider
|
|
8
8
|
from ophyd_async.plan_stubs import ensure_connected
|
|
9
9
|
|
|
10
10
|
from dodal.beamlines import all_beamline_names, module_name_for_beamline
|
|
@@ -58,7 +58,7 @@ def connect(beamline: str, all: bool, sim_backend: bool) -> None:
|
|
|
58
58
|
|
|
59
59
|
# We need to make a RunEngine to allow ophyd-async devices to connect.
|
|
60
60
|
# See https://blueskyproject.io/ophyd-async/main/explanations/event-loop-choice.html
|
|
61
|
-
|
|
61
|
+
run_engine = RunEngine(call_returns_result=True)
|
|
62
62
|
|
|
63
63
|
print(f"Attempting connection to {beamline} (using {full_module_path})")
|
|
64
64
|
|
|
@@ -71,7 +71,7 @@ def connect(beamline: str, all: bool, sim_backend: bool) -> None:
|
|
|
71
71
|
fake_with_ophyd_sim=sim_backend,
|
|
72
72
|
wait_for_connection=False,
|
|
73
73
|
)
|
|
74
|
-
devices, connect_exceptions = _connect_devices(
|
|
74
|
+
devices, connect_exceptions = _connect_devices(run_engine, devices, sim_backend)
|
|
75
75
|
|
|
76
76
|
# Inform user of successful connections
|
|
77
77
|
_report_successful_devices(devices, sim_backend)
|
|
@@ -79,7 +79,7 @@ def connect(beamline: str, all: bool, sim_backend: bool) -> None:
|
|
|
79
79
|
# If exceptions have occurred, this will print details of the relevant PVs
|
|
80
80
|
exceptions = {**instance_exceptions, **connect_exceptions}
|
|
81
81
|
if len(exceptions) > 0:
|
|
82
|
-
raise
|
|
82
|
+
raise NotConnectedError(exceptions)
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def _report_successful_devices(
|
|
@@ -96,7 +96,7 @@ def _report_successful_devices(
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _connect_devices(
|
|
99
|
-
|
|
99
|
+
run_engine: RunEngine,
|
|
100
100
|
devices: Mapping[str, AnyDevice],
|
|
101
101
|
sim_backend: bool,
|
|
102
102
|
) -> tuple[Mapping[str, AnyDevice], Mapping[str, Exception]]:
|
|
@@ -112,8 +112,8 @@ def _connect_devices(
|
|
|
112
112
|
|
|
113
113
|
# Connect ophyd-async devices
|
|
114
114
|
try:
|
|
115
|
-
|
|
116
|
-
except
|
|
115
|
+
run_engine(ensure_connected(*ophyd_async_devices.values(), mock=sim_backend))
|
|
116
|
+
except NotConnectedError as ex:
|
|
117
117
|
exceptions = {**exceptions, **ex.sub_errors}
|
|
118
118
|
|
|
119
119
|
# Only return the subset of devices that haven't raised an exception
|
dodal/common/visit.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Literal
|
|
|
4
4
|
|
|
5
5
|
from aiohttp import ClientSession
|
|
6
6
|
from ophyd_async.core import FilenameProvider, PathInfo
|
|
7
|
-
from pydantic import BaseModel
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
9
|
from dodal.common.types import UpdatingPathProvider
|
|
10
10
|
from dodal.log import LOGGER
|
|
@@ -20,7 +20,7 @@ class DataCollectionIdentifier(BaseModel):
|
|
|
20
20
|
Should be always incrementing, unique per-visit, co-ordinated with any other scan engines.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
collection_number: int = Field(alias="collectionNumber")
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class DirectoryServiceClient(ABC):
|
|
@@ -46,7 +46,7 @@ class DiamondFilenameProvider(FilenameProvider):
|
|
|
46
46
|
def __call__(self, device_name: str | None = None):
|
|
47
47
|
assert device_name, "Diamond filename requires device_name to be passed"
|
|
48
48
|
assert self.collectionId is not None
|
|
49
|
-
return f"{self._beamline}-{self.collectionId.
|
|
49
|
+
return f"{self._beamline}-{self.collectionId.collection_number}-{device_name}"
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class RemoteDirectoryServiceClient(DirectoryServiceClient):
|
|
@@ -143,7 +143,7 @@ class StaticVisitPathProvider(UpdatingPathProvider):
|
|
|
143
143
|
|
|
144
144
|
async def data_session(self) -> str:
|
|
145
145
|
collection = await self._client.get_current_collection()
|
|
146
|
-
return f"{self._beamline}-{collection.
|
|
146
|
+
return f"{self._beamline}-{collection.collection_number}"
|
|
147
147
|
|
|
148
148
|
def __call__(self, device_name: str | None = None) -> PathInfo:
|
|
149
149
|
assert device_name, "Must call PathProvider with device_name"
|
|
@@ -18,7 +18,7 @@ from dodal.devices.aperture import Aperture
|
|
|
18
18
|
from dodal.devices.motors import XYStage
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class InvalidApertureMoveError(Exception):
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
|
|
@@ -242,7 +242,7 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
242
242
|
diff_on_z = abs(current_ap_z - expected_z_position)
|
|
243
243
|
aperture_z_tolerance = self._tolerances.aperture_z
|
|
244
244
|
if diff_on_z > aperture_z_tolerance:
|
|
245
|
-
raise
|
|
245
|
+
raise InvalidApertureMoveError(
|
|
246
246
|
f"Current aperture z ({current_ap_z}), outside of tolerance ({aperture_z_tolerance}) from target ({expected_z_position})."
|
|
247
247
|
)
|
|
248
248
|
|
|
@@ -256,7 +256,7 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
256
256
|
for axis in all_axes:
|
|
257
257
|
axis_stationary = await axis.motor_done_move.get_value()
|
|
258
258
|
if not axis_stationary:
|
|
259
|
-
raise
|
|
259
|
+
raise InvalidApertureMoveError(
|
|
260
260
|
f"{axis.name} is still moving. Wait for it to finish before"
|
|
261
261
|
"triggering another move."
|
|
262
262
|
)
|
|
@@ -294,7 +294,9 @@ class ApertureScatterguard(StandardReadable, Preparable):
|
|
|
294
294
|
):
|
|
295
295
|
return ApertureValue.OUT_OF_BEAM
|
|
296
296
|
|
|
297
|
-
raise
|
|
297
|
+
raise InvalidApertureMoveError(
|
|
298
|
+
"Current aperture/scatterguard state unrecognised"
|
|
299
|
+
)
|
|
298
300
|
|
|
299
301
|
async def _safe_move_whilst_in_beam(self, position: AperturePosition):
|
|
300
302
|
"""
|
|
@@ -5,19 +5,23 @@ from math import isclose
|
|
|
5
5
|
from typing import Generic, Protocol, TypeVar
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
-
from bluesky.protocols import Movable
|
|
8
|
+
from bluesky.protocols import Locatable, Location, Movable
|
|
9
9
|
from ophyd_async.core import (
|
|
10
10
|
AsyncStatus,
|
|
11
|
+
Reference,
|
|
11
12
|
SignalR,
|
|
13
|
+
SignalRW,
|
|
12
14
|
SignalW,
|
|
13
15
|
StandardReadable,
|
|
14
16
|
StandardReadableFormat,
|
|
15
17
|
StrictEnum,
|
|
16
18
|
derived_signal_rw,
|
|
17
19
|
soft_signal_r_and_setter,
|
|
20
|
+
soft_signal_rw,
|
|
18
21
|
wait_for_value,
|
|
19
22
|
)
|
|
20
23
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_w
|
|
24
|
+
from ophyd_async.epics.motor import Motor
|
|
21
25
|
|
|
22
26
|
from dodal.log import LOGGER
|
|
23
27
|
|
|
@@ -40,12 +44,8 @@ class Apple2PhasesVal:
|
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
@dataclass
|
|
43
|
-
class Apple2Val:
|
|
47
|
+
class Apple2Val(Apple2PhasesVal):
|
|
44
48
|
gap: str
|
|
45
|
-
top_outer: str
|
|
46
|
-
top_inner: str
|
|
47
|
-
btm_inner: str
|
|
48
|
-
btm_outer: str
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class Pol(StrictEnum):
|
|
@@ -185,24 +185,24 @@ class UndulatorPhaseMotor(StandardReadable):
|
|
|
185
185
|
name : str
|
|
186
186
|
Name of the Id phase device
|
|
187
187
|
"""
|
|
188
|
-
|
|
189
|
-
self.user_setpoint = epics_signal_w(str,
|
|
190
|
-
self.user_setpoint_readback = epics_signal_r(float,
|
|
191
|
-
|
|
188
|
+
full_pv = f"{prefix}BL{infix}"
|
|
189
|
+
self.user_setpoint = epics_signal_w(str, full_pv + "SET")
|
|
190
|
+
self.user_setpoint_readback = epics_signal_r(float, full_pv + "DMD")
|
|
191
|
+
full_pv = full_pv + "MTR"
|
|
192
192
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
193
|
-
self.user_readback = epics_signal_r(float,
|
|
193
|
+
self.user_readback = epics_signal_r(float, full_pv + ".RBV")
|
|
194
194
|
|
|
195
195
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
196
|
-
self.motor_egu = epics_signal_r(str,
|
|
197
|
-
self.velocity = epics_signal_rw(float,
|
|
198
|
-
|
|
199
|
-
self.max_velocity = epics_signal_r(float,
|
|
200
|
-
self.acceleration_time = epics_signal_rw(float,
|
|
201
|
-
self.precision = epics_signal_r(int,
|
|
202
|
-
self.deadband = epics_signal_r(float,
|
|
203
|
-
self.motor_done_move = epics_signal_r(int,
|
|
204
|
-
self.low_limit_travel = epics_signal_rw(float,
|
|
205
|
-
self.high_limit_travel = epics_signal_rw(float,
|
|
196
|
+
self.motor_egu = epics_signal_r(str, full_pv + ".EGU")
|
|
197
|
+
self.velocity = epics_signal_rw(float, full_pv + ".VELO")
|
|
198
|
+
|
|
199
|
+
self.max_velocity = epics_signal_r(float, full_pv + ".VMAX")
|
|
200
|
+
self.acceleration_time = epics_signal_rw(float, full_pv + ".ACCL")
|
|
201
|
+
self.precision = epics_signal_r(int, full_pv + ".PREC")
|
|
202
|
+
self.deadband = epics_signal_r(float, full_pv + ".RDBD")
|
|
203
|
+
self.motor_done_move = epics_signal_r(int, full_pv + ".DMOV")
|
|
204
|
+
self.low_limit_travel = epics_signal_rw(float, full_pv + ".LLM")
|
|
205
|
+
self.high_limit_travel = epics_signal_rw(float, full_pv + ".HLM")
|
|
206
206
|
super().__init__(name=name)
|
|
207
207
|
|
|
208
208
|
|
|
@@ -301,7 +301,7 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
|
301
301
|
)
|
|
302
302
|
|
|
303
303
|
|
|
304
|
-
class
|
|
304
|
+
class Apple2(StandardReadable, Movable):
|
|
305
305
|
"""
|
|
306
306
|
Device representing the combined motor controls for an Apple2 undulator.
|
|
307
307
|
|
|
@@ -326,38 +326,39 @@ class Apple2Motors(StandardReadable, Movable):
|
|
|
326
326
|
Name of the device.
|
|
327
327
|
"""
|
|
328
328
|
with self.add_children_as_readables():
|
|
329
|
-
self.gap = id_gap
|
|
330
|
-
self.phase = id_phase
|
|
329
|
+
self.gap = Reference(id_gap)
|
|
330
|
+
self.phase = Reference(id_phase)
|
|
331
331
|
super().__init__(name=name)
|
|
332
332
|
|
|
333
333
|
@AsyncStatus.wrap
|
|
334
334
|
async def set(self, id_motor_values: Apple2Val) -> None:
|
|
335
335
|
"""
|
|
336
336
|
Check ID is in a movable state and set all the demand value before moving them
|
|
337
|
-
all at the same time.
|
|
338
|
-
class, if the ID motors has to move in a specific order.
|
|
337
|
+
all at the same time.
|
|
339
338
|
"""
|
|
340
339
|
|
|
341
340
|
# Only need to check gap as the phase motors share both fault and gate with gap.
|
|
342
|
-
await self.gap.raise_if_cannot_move()
|
|
341
|
+
await self.gap().raise_if_cannot_move()
|
|
343
342
|
await asyncio.gather(
|
|
344
|
-
self.phase.top_outer.user_setpoint.set(value=id_motor_values.top_outer),
|
|
345
|
-
self.phase.top_inner.user_setpoint.set(value=id_motor_values.top_inner),
|
|
346
|
-
self.phase.btm_inner.user_setpoint.set(value=id_motor_values.btm_inner),
|
|
347
|
-
self.phase.btm_outer.user_setpoint.set(value=id_motor_values.btm_outer),
|
|
348
|
-
self.gap.user_setpoint.set(value=id_motor_values.gap),
|
|
343
|
+
self.phase().top_outer.user_setpoint.set(value=id_motor_values.top_outer),
|
|
344
|
+
self.phase().top_inner.user_setpoint.set(value=id_motor_values.top_inner),
|
|
345
|
+
self.phase().btm_inner.user_setpoint.set(value=id_motor_values.btm_inner),
|
|
346
|
+
self.phase().btm_outer.user_setpoint.set(value=id_motor_values.btm_outer),
|
|
347
|
+
self.gap().user_setpoint.set(value=id_motor_values.gap),
|
|
349
348
|
)
|
|
350
349
|
timeout = np.max(
|
|
351
|
-
await asyncio.gather(self.gap.get_timeout(), self.phase.get_timeout())
|
|
350
|
+
await asyncio.gather(self.gap().get_timeout(), self.phase().get_timeout())
|
|
352
351
|
)
|
|
353
352
|
LOGGER.info(
|
|
354
353
|
f"Moving f{self.name} apple2 motors to {id_motor_values}, timeout = {timeout}"
|
|
355
354
|
)
|
|
356
355
|
await asyncio.gather(
|
|
357
|
-
self.gap.set_move.set(value=1, wait=False, timeout=timeout),
|
|
358
|
-
self.phase.set_move.set(value=1, wait=False, timeout=timeout),
|
|
356
|
+
self.gap().set_move.set(value=1, wait=False, timeout=timeout),
|
|
357
|
+
self.phase().set_move.set(value=1, wait=False, timeout=timeout),
|
|
358
|
+
)
|
|
359
|
+
await wait_for_value(
|
|
360
|
+
self.gap().gate, UndulatorGateStatus.CLOSE, timeout=timeout
|
|
359
361
|
)
|
|
360
|
-
await wait_for_value(self.gap.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
361
362
|
|
|
362
363
|
|
|
363
364
|
class EnergyMotorConvertor(Protocol):
|
|
@@ -366,80 +367,76 @@ class EnergyMotorConvertor(Protocol):
|
|
|
366
367
|
...
|
|
367
368
|
|
|
368
369
|
|
|
369
|
-
|
|
370
|
-
"""
|
|
371
|
-
Apple2 Undulator Device
|
|
370
|
+
Apple2Type = TypeVar("Apple2Type", bound="Apple2")
|
|
372
371
|
|
|
373
|
-
The `Apple2` class represents an Apple 2 insertion device (undulator) used in synchrotron beamlines.
|
|
374
|
-
This device provides additional degrees of freedom compared to standard undulators, allowing independent
|
|
375
|
-
movement of magnet banks to produce X-rays with various polarisations and energies.
|
|
376
372
|
|
|
377
|
-
|
|
378
|
-
|
|
373
|
+
class Apple2Controller(abc.ABC, StandardReadable, Generic[Apple2Type]):
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
Abstract base class for controlling an Apple2 undulator device.
|
|
379
377
|
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
This class manages the undulator's gap and phase motors, and provides an interface
|
|
379
|
+
for controlling polarisation and energy settings. It exposes derived signals for
|
|
380
|
+
energy and polarisation, and handles conversion between energy/polarisation and
|
|
381
|
+
motor positions via a user-supplied conversion callable.
|
|
382
382
|
|
|
383
383
|
Attributes
|
|
384
384
|
----------
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
energy :
|
|
388
|
-
|
|
385
|
+
apple2 : Reference[Apple2Type]
|
|
386
|
+
Reference to the Apple2 device containing gap and phase motors.
|
|
387
|
+
energy : derived_signal_rw
|
|
388
|
+
Derived signal for moving and reading back energy.
|
|
389
389
|
polarisation_setpoint : SignalR
|
|
390
|
-
|
|
391
|
-
polarisation :
|
|
392
|
-
|
|
393
|
-
lookup_tables : dict
|
|
394
|
-
A dictionary storing lookup tables for gap and phase motor positions, used for energy and polarisation conversion.
|
|
390
|
+
Soft signal for the polarisation setpoint.
|
|
391
|
+
polarisation : derived_signal_rw
|
|
392
|
+
Hardware-backed signal for polarisation readback and control.
|
|
395
393
|
energy_to_motor : EnergyMotorConvertor
|
|
396
|
-
|
|
394
|
+
Callable that converts energy and polarisation to motor positions.
|
|
397
395
|
|
|
398
396
|
Abstract Methods
|
|
399
397
|
----------------
|
|
400
|
-
|
|
398
|
+
_set_motors_from_energy(value: float) -> None
|
|
401
399
|
Abstract method to set motor positions for a given energy and polarisation.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
-------
|
|
405
|
-
determine_phase_from_hardware(...) -> tuple[Pol, float]
|
|
406
|
-
Determines the polarisation and phase value based on motor positions.
|
|
400
|
+
energy_to_motor : EnergyMotorConvertor
|
|
401
|
+
A callable that converts energy and polarisation to motor positions.
|
|
407
402
|
|
|
408
403
|
Notes
|
|
409
404
|
-----
|
|
410
|
-
-
|
|
411
|
-
-
|
|
405
|
+
- Subclasses must implement `_set_motors_from_energy` for beamline-specific logic.
|
|
406
|
+
- LH3 polarisation is indistinguishable from LH in hardware; special handling is provided.
|
|
407
|
+
- Supports multiple polarisation modes, including linear horizontal (LH), linear vertical (LV),
|
|
412
408
|
positive circular (PC), negative circular (NC), and linear arbitrary (LA).
|
|
413
409
|
|
|
414
|
-
For more detail see
|
|
415
|
-
`UML </_images/apple2_design.png>`__ for detail.
|
|
416
|
-
|
|
417
|
-
.. figure:: /explanations/umls/apple2_design.png
|
|
418
|
-
|
|
419
410
|
"""
|
|
420
411
|
|
|
421
412
|
def __init__(
|
|
422
413
|
self,
|
|
423
|
-
|
|
424
|
-
|
|
414
|
+
apple2: Apple2Type,
|
|
415
|
+
energy_to_motor_converter: EnergyMotorConvertor,
|
|
425
416
|
name: str = "",
|
|
426
417
|
) -> None:
|
|
427
418
|
"""
|
|
428
419
|
|
|
429
420
|
Parameters
|
|
430
421
|
----------
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
422
|
+
apple2: Apple2
|
|
423
|
+
An Apple2 device.
|
|
424
|
+
name: str
|
|
425
|
+
Name of the device.
|
|
435
426
|
"""
|
|
427
|
+
self.energy_to_motor = energy_to_motor_converter
|
|
428
|
+
self.apple2 = Reference(apple2)
|
|
436
429
|
|
|
437
|
-
|
|
438
|
-
self.
|
|
430
|
+
# Store the set energy for readback.
|
|
431
|
+
self._energy, self._energy_set = soft_signal_r_and_setter(
|
|
432
|
+
float, initial_value=None, units="eV"
|
|
433
|
+
)
|
|
439
434
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
435
|
+
self.energy = derived_signal_rw(
|
|
436
|
+
raw_to_derived=self._read_energy,
|
|
437
|
+
set_derived=self._set_energy,
|
|
438
|
+
energy=self._energy,
|
|
439
|
+
derived_units="eV",
|
|
443
440
|
)
|
|
444
441
|
|
|
445
442
|
# Store the polarisation for setpoint. And provide readback for LH3.
|
|
@@ -447,61 +444,65 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
447
444
|
self.polarisation_setpoint, self._polarisation_setpoint_set = (
|
|
448
445
|
soft_signal_r_and_setter(Pol)
|
|
449
446
|
)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
447
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
448
|
+
# Hardware backed read/write for polarisation.
|
|
449
|
+
self.polarisation = derived_signal_rw(
|
|
450
|
+
raw_to_derived=self._read_pol,
|
|
451
|
+
set_derived=self._set_pol,
|
|
452
|
+
pol=self.polarisation_setpoint,
|
|
453
|
+
top_outer=self.apple2().phase().top_outer.user_readback,
|
|
454
|
+
top_inner=self.apple2().phase().top_inner.user_readback,
|
|
455
|
+
btm_inner=self.apple2().phase().btm_inner.user_readback,
|
|
456
|
+
btm_outer=self.apple2().phase().btm_outer.user_readback,
|
|
457
|
+
gap=self.apple2().gap().user_readback,
|
|
458
|
+
)
|
|
462
459
|
super().__init__(name)
|
|
463
460
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
async def _set_pol(
|
|
471
|
-
self,
|
|
472
|
-
value: Pol,
|
|
473
|
-
) -> None:
|
|
474
|
-
# This changes the pol setpoint and then changes polarisation via set energy.
|
|
475
|
-
self._set_pol_setpoint(value)
|
|
476
|
-
await self.set(await self.energy.get_value())
|
|
477
|
-
|
|
478
|
-
@AsyncStatus.wrap
|
|
479
|
-
async def set(self, value: float) -> None:
|
|
461
|
+
@abc.abstractmethod
|
|
462
|
+
async def _set_motors_from_energy(self, value: float) -> None:
|
|
463
|
+
"""
|
|
464
|
+
This method should be implemented by the beamline specific ID class as the
|
|
465
|
+
motor positions will be different for each beamline depending on the
|
|
466
|
+
undulator design and the lookup table used.
|
|
480
467
|
"""
|
|
481
|
-
Set should be in energy units, this will set the energy of the ID by setting the
|
|
482
|
-
gap and phase motors to the correct position for the given energy
|
|
483
|
-
and polarisation.
|
|
484
468
|
|
|
469
|
+
async def _set_energy(self, energy: float) -> None:
|
|
470
|
+
await self._set_motors_from_energy(energy)
|
|
471
|
+
self._energy_set(energy)
|
|
485
472
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
RE(scan([detector], id,600,700,100)) # This will scan the ID from 600 to 700 eV in 100 steps.
|
|
490
|
-
"""
|
|
491
|
-
await self._set(value)
|
|
492
|
-
self._set_energy_rbv(value) # Update energy after move for readback.
|
|
493
|
-
LOGGER.info(f"Energy set to {value} eV successfully.")
|
|
473
|
+
def _read_energy(self, energy: float) -> float:
|
|
474
|
+
"""Readback for energy is just the set value."""
|
|
475
|
+
return energy
|
|
494
476
|
|
|
495
|
-
|
|
496
|
-
async def _set(self, value: float) -> None:
|
|
477
|
+
async def _check_and_get_pol_setpoint(self) -> Pol:
|
|
497
478
|
"""
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
undulator design and the lookup table used. The set method can be
|
|
501
|
-
used to set the motor positions for the given energy and polarisation
|
|
502
|
-
provided that all motors can be moved at the same time.
|
|
479
|
+
Check the polarisation setpoint and if it is NONE try to read it from
|
|
480
|
+
hardware.
|
|
503
481
|
"""
|
|
504
482
|
|
|
483
|
+
pol = await self.polarisation_setpoint.get_value()
|
|
484
|
+
|
|
485
|
+
if pol == Pol.NONE:
|
|
486
|
+
LOGGER.warning(
|
|
487
|
+
"Found no setpoint for polarisation. Attempting to"
|
|
488
|
+
" determine polarisation from hardware..."
|
|
489
|
+
)
|
|
490
|
+
pol = await self.polarisation.get_value()
|
|
491
|
+
if pol == Pol.NONE:
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f"Polarisation cannot be determined from hardware for {self.name}"
|
|
494
|
+
)
|
|
495
|
+
self._polarisation_setpoint_set(pol)
|
|
496
|
+
return pol
|
|
497
|
+
|
|
498
|
+
async def _set_pol(
|
|
499
|
+
self,
|
|
500
|
+
value: Pol,
|
|
501
|
+
) -> None:
|
|
502
|
+
# This changes the pol setpoint and then changes polarisation via set energy.
|
|
503
|
+
self._polarisation_setpoint_set(value)
|
|
504
|
+
await self.energy.set(await self.energy.get_value())
|
|
505
|
+
|
|
505
506
|
def _read_pol(
|
|
506
507
|
self,
|
|
507
508
|
pol: Pol,
|
|
@@ -605,3 +606,101 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
605
606
|
|
|
606
607
|
LOGGER.warning("Unable to determine polarisation. Defaulting to NONE.")
|
|
607
608
|
return Pol.NONE, 0.0
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class InsertionDeviceEnergyBase(abc.ABC, StandardReadable, Movable):
|
|
612
|
+
"""Base class for ID energy movable device."""
|
|
613
|
+
|
|
614
|
+
def __init__(self, name: str = "") -> None:
|
|
615
|
+
self.energy: Reference[SignalRW[float]]
|
|
616
|
+
super().__init__(name=name)
|
|
617
|
+
|
|
618
|
+
@abc.abstractmethod
|
|
619
|
+
@AsyncStatus.wrap
|
|
620
|
+
async def set(self, energy: float) -> None: ...
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class BeamEnergy(StandardReadable, Movable[float]):
|
|
624
|
+
"""
|
|
625
|
+
Compound device to set both ID and energy motor at the same time with an option to add an offset.
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
def __init__(
|
|
629
|
+
self, id_energy: InsertionDeviceEnergyBase, mono: Motor, name: str = ""
|
|
630
|
+
) -> None:
|
|
631
|
+
"""
|
|
632
|
+
Parameters
|
|
633
|
+
----------
|
|
634
|
+
|
|
635
|
+
id_energy: InsertionDeviceEnergy
|
|
636
|
+
An InsertionDeviceEnergy device.
|
|
637
|
+
mono: Motor
|
|
638
|
+
A Motor(energy) device.
|
|
639
|
+
name:
|
|
640
|
+
New device name.
|
|
641
|
+
"""
|
|
642
|
+
super().__init__(name=name)
|
|
643
|
+
self._id_energy = Reference(id_energy)
|
|
644
|
+
self._mono_energy = Reference(mono)
|
|
645
|
+
|
|
646
|
+
self.add_readables(
|
|
647
|
+
[
|
|
648
|
+
self._id_energy().energy(),
|
|
649
|
+
self._mono_energy().user_readback,
|
|
650
|
+
],
|
|
651
|
+
StandardReadableFormat.HINTED_SIGNAL,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
655
|
+
self.id_energy_offset = soft_signal_rw(float, initial_value=0)
|
|
656
|
+
|
|
657
|
+
@AsyncStatus.wrap
|
|
658
|
+
async def set(self, energy: float) -> None:
|
|
659
|
+
LOGGER.info(f"Moving f{self.name} energy to {energy}.")
|
|
660
|
+
await asyncio.gather(
|
|
661
|
+
self._id_energy().set(
|
|
662
|
+
energy=energy + await self.id_energy_offset.get_value()
|
|
663
|
+
),
|
|
664
|
+
self._mono_energy().set(energy),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class InsertionDeviceEnergy(InsertionDeviceEnergyBase):
|
|
669
|
+
"""Apple2 ID energy movable device."""
|
|
670
|
+
|
|
671
|
+
def __init__(self, id_controller: Apple2Controller, name: str = "") -> None:
|
|
672
|
+
self.energy = Reference(id_controller.energy)
|
|
673
|
+
super().__init__(name=name)
|
|
674
|
+
|
|
675
|
+
self.add_readables(
|
|
676
|
+
[
|
|
677
|
+
self.energy(),
|
|
678
|
+
],
|
|
679
|
+
StandardReadableFormat.HINTED_SIGNAL,
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
@AsyncStatus.wrap
|
|
683
|
+
async def set(self, energy: float) -> None:
|
|
684
|
+
await self.energy().set(energy)
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
class InsertionDevicePolarisation(StandardReadable, Locatable[Pol]):
|
|
688
|
+
"""Apple2 ID polarisation movable device."""
|
|
689
|
+
|
|
690
|
+
def __init__(self, id_controller: Apple2Controller, name: str = "") -> None:
|
|
691
|
+
self.polarisation = Reference(id_controller.polarisation)
|
|
692
|
+
self.polarisation_setpoint = Reference(id_controller.polarisation_setpoint)
|
|
693
|
+
super().__init__(name=name)
|
|
694
|
+
|
|
695
|
+
self.add_readables([self.polarisation()], StandardReadableFormat.HINTED_SIGNAL)
|
|
696
|
+
|
|
697
|
+
@AsyncStatus.wrap
|
|
698
|
+
async def set(self, pol: Pol) -> None:
|
|
699
|
+
await self.polarisation().set(pol)
|
|
700
|
+
|
|
701
|
+
async def locate(self) -> Location[Pol]:
|
|
702
|
+
"""Return the current polarisation"""
|
|
703
|
+
setpoint, readback = await asyncio.gather(
|
|
704
|
+
self.polarisation_setpoint().get_value(), self.polarisation().get_value()
|
|
705
|
+
)
|
|
706
|
+
return Location(setpoint=setpoint, readback=readback)
|
|
@@ -20,7 +20,7 @@ class P99FilterSelections(SubsetEnum):
|
|
|
20
20
|
USER = "User"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
class I02_1FilterOneSelections(SubsetEnum):
|
|
23
|
+
class I02_1FilterOneSelections(SubsetEnum): # noqa: N801
|
|
24
24
|
EMPTY = "Empty"
|
|
25
25
|
AL8 = "Al8"
|
|
26
26
|
AL15 = "Al15"
|
|
@@ -33,7 +33,7 @@ class I02_1FilterOneSelections(SubsetEnum):
|
|
|
33
33
|
TWO_TIMES_TI500 = "2xTi500"
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class I02_1FilterTwoSelections(SubsetEnum):
|
|
36
|
+
class I02_1FilterTwoSelections(SubsetEnum): # noqa: N801
|
|
37
37
|
EMPTY = "Empty"
|
|
38
38
|
AL50 = "Al50"
|
|
39
39
|
AL100 = "Al100"
|
|
@@ -46,7 +46,7 @@ class I02_1FilterTwoSelections(SubsetEnum):
|
|
|
46
46
|
TWO_TIMES_TI500 = "2xTi500"
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
class I02_1FilterThreeSelections(SubsetEnum):
|
|
49
|
+
class I02_1FilterThreeSelections(SubsetEnum): # noqa: N801
|
|
50
50
|
EMPTY = "Empty"
|
|
51
51
|
AL15 = "Al15"
|
|
52
52
|
AL25 = "Al25"
|
|
@@ -59,7 +59,7 @@ class I02_1FilterThreeSelections(SubsetEnum):
|
|
|
59
59
|
TI200 = "Ti200"
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
class I02_1FilterFourSelections(SubsetEnum):
|
|
62
|
+
class I02_1FilterFourSelections(SubsetEnum): # noqa: N801
|
|
63
63
|
EMPTY = "Empty"
|
|
64
64
|
AL15 = "Al15"
|
|
65
65
|
AL25 = "Al25"
|
|
@@ -72,7 +72,7 @@ class I02_1FilterFourSelections(SubsetEnum):
|
|
|
72
72
|
TI500 = "Ti500"
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
class
|
|
75
|
+
class I24FilterOneSelections(SubsetEnum):
|
|
76
76
|
EMPTY = "Empty"
|
|
77
77
|
AL12_5 = "Al12.5"
|
|
78
78
|
AL25 = "Al25"
|
|
@@ -85,7 +85,7 @@ class I24_FilterOneSelections(SubsetEnum):
|
|
|
85
85
|
TI500 = "Ti500"
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
class
|
|
88
|
+
class I24FilterTwoSelections(SubsetEnum):
|
|
89
89
|
EMPTY = "Empty"
|
|
90
90
|
AL100 = "Al100"
|
|
91
91
|
AL200 = "Al200"
|
dodal/devices/b07_1/ccmc.py
CHANGED