dls-dodal 1.59.1__py3-none-any.whl → 1.61.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.59.1.dist-info → dls_dodal-1.61.0.dist-info}/METADATA +2 -3
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/RECORD +46 -25
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/i15.py +242 -0
- dodal/beamlines/i15_1.py +156 -0
- dodal/beamlines/i19_1.py +3 -1
- dodal/beamlines/i19_2.py +1 -1
- dodal/devices/apple2_undulator.py +85 -52
- dodal/devices/areadetector/__init__.py +0 -0
- dodal/devices/areadetector/plugins/__init__.py +0 -0
- dodal/devices/attenuator/__init__.py +0 -0
- dodal/devices/electron_analyser/abstract/__init__.py +2 -2
- dodal/devices/electron_analyser/abstract/base_detector.py +13 -26
- dodal/devices/electron_analyser/abstract/base_driver_io.py +5 -4
- dodal/devices/electron_analyser/abstract/base_region.py +28 -13
- dodal/devices/electron_analyser/detector.py +19 -31
- dodal/devices/electron_analyser/specs/driver_io.py +0 -1
- dodal/devices/electron_analyser/vgscienta/driver_io.py +0 -1
- dodal/devices/fast_grid_scan.py +14 -11
- dodal/devices/i04/murko_results.py +24 -12
- dodal/devices/i10/i10_apple2.py +15 -15
- dodal/devices/i10/rasor/__init__.py +0 -0
- dodal/devices/i11/__init__.py +0 -0
- dodal/devices/i15/__init__.py +0 -0
- dodal/devices/i15/dcm.py +77 -0
- dodal/devices/i15/focussing_mirror.py +55 -0
- dodal/devices/i15/jack.py +31 -0
- dodal/devices/i15/laue.py +14 -0
- dodal/devices/i15/motors.py +27 -0
- dodal/devices/i15/multilayer_mirror.py +21 -0
- dodal/devices/i15/rail.py +13 -0
- dodal/devices/i18/__init__.py +0 -0
- dodal/devices/i22/__init__.py +0 -0
- dodal/devices/i24/commissioning_jungfrau.py +9 -1
- dodal/devices/motors.py +52 -1
- dodal/devices/mx_phase1/__init__.py +0 -0
- dodal/devices/oav/snapshots/__init__.py +0 -0
- dodal/devices/slits.py +18 -0
- dodal/devices/v2f.py +7 -7
- dodal/devices/xspress3/__init__.py +0 -0
- dodal/parameters/__init__.py +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i19_1.py
CHANGED
|
@@ -58,6 +58,8 @@ def oav() -> OAVBeamCentreFile:
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
# NOTE EH1 uses the Zebra 2 box. While a Zebra 1 box exists and is connected
|
|
62
|
+
# on the beamline, it is currently not in use
|
|
61
63
|
@device_factory()
|
|
62
64
|
def zebra() -> Zebra:
|
|
63
65
|
"""Get the i19-1 zebra device, instantiate it if it hasn't already been.
|
|
@@ -65,7 +67,7 @@ def zebra() -> Zebra:
|
|
|
65
67
|
"""
|
|
66
68
|
return Zebra(
|
|
67
69
|
mapping=I19_1_ZEBRA_MAPPING,
|
|
68
|
-
prefix=f"{PREFIX.beamline_prefix}-EA-ZEBRA-
|
|
70
|
+
prefix=f"{PREFIX.beamline_prefix}-EA-ZEBRA-02:",
|
|
69
71
|
)
|
|
70
72
|
|
|
71
73
|
|
dodal/beamlines/i19_2.py
CHANGED
|
@@ -301,9 +301,68 @@ class UndulatorJawPhase(SafeUndulatorMover[float]):
|
|
|
301
301
|
)
|
|
302
302
|
|
|
303
303
|
|
|
304
|
+
class Apple2Motors(StandardReadable, Movable):
|
|
305
|
+
"""
|
|
306
|
+
Device representing the combined motor controls for an Apple2 undulator.
|
|
307
|
+
|
|
308
|
+
Attributes
|
|
309
|
+
----------
|
|
310
|
+
gap : UndulatorGap
|
|
311
|
+
The undulator gap motor device.
|
|
312
|
+
phase : UndulatorPhaseAxes
|
|
313
|
+
The undulator phase axes device, consisting of four phase motors.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
def __init__(self, id_gap: UndulatorGap, id_phase: UndulatorPhaseAxes, name=""):
|
|
317
|
+
"""
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
|
|
321
|
+
id_gap: UndulatorGap
|
|
322
|
+
An UndulatorGap device.
|
|
323
|
+
id_phase: UndulatorPhaseAxes
|
|
324
|
+
An UndulatorPhaseAxes device.
|
|
325
|
+
name: str
|
|
326
|
+
Name of the device.
|
|
327
|
+
"""
|
|
328
|
+
with self.add_children_as_readables():
|
|
329
|
+
self.gap = id_gap
|
|
330
|
+
self.phase = id_phase
|
|
331
|
+
super().__init__(name=name)
|
|
332
|
+
|
|
333
|
+
@AsyncStatus.wrap
|
|
334
|
+
async def set(self, id_motor_values: Apple2Val) -> None:
|
|
335
|
+
"""
|
|
336
|
+
Check ID is in a movable state and set all the demand value before moving them
|
|
337
|
+
all at the same time. This should be modified by the beamline specific ID
|
|
338
|
+
class, if the ID motors has to move in a specific order.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
# Only need to check gap as the phase motors share both fault and gate with gap.
|
|
342
|
+
await self.gap.raise_if_cannot_move()
|
|
343
|
+
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),
|
|
349
|
+
)
|
|
350
|
+
timeout = np.max(
|
|
351
|
+
await asyncio.gather(self.gap.get_timeout(), self.phase.get_timeout())
|
|
352
|
+
)
|
|
353
|
+
LOGGER.info(
|
|
354
|
+
f"Moving f{self.name} apple2 motors to {id_motor_values}, timeout = {timeout}"
|
|
355
|
+
)
|
|
356
|
+
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),
|
|
359
|
+
)
|
|
360
|
+
await wait_for_value(self.gap.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
361
|
+
|
|
362
|
+
|
|
304
363
|
class EnergyMotorConvertor(Protocol):
|
|
305
364
|
def __call__(self, energy: float, pol: Pol) -> tuple[float, float]:
|
|
306
|
-
"""Protocol to provide energy to motor position
|
|
365
|
+
"""Protocol to provide energy to motor position conversion"""
|
|
307
366
|
...
|
|
308
367
|
|
|
309
368
|
|
|
@@ -318,15 +377,13 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
318
377
|
The class is designed to manage the undulator's gap, phase motors, and polarisation settings, while
|
|
319
378
|
abstracting hardware interactions and providing a high-level interface for beamline operations.
|
|
320
379
|
|
|
321
|
-
The class is abstract and requires beamline-specific implementations for
|
|
380
|
+
The class is abstract and requires beamline-specific implementations for _set motor
|
|
322
381
|
positions based on energy and polarisation.
|
|
323
382
|
|
|
324
383
|
Attributes
|
|
325
384
|
----------
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
phase : UndulatorPhaseAxes
|
|
329
|
-
The phase control device, consisting of four phase motors.
|
|
385
|
+
apple2_motors : Apple2Motors
|
|
386
|
+
A collection of gap and phase motor devices.
|
|
330
387
|
energy : SignalR
|
|
331
388
|
A soft signal for the current energy readback.
|
|
332
389
|
polarisation_setpoint : SignalR
|
|
@@ -340,7 +397,7 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
340
397
|
|
|
341
398
|
Abstract Methods
|
|
342
399
|
----------------
|
|
343
|
-
|
|
400
|
+
_set(value: float) -> None
|
|
344
401
|
Abstract method to set motor positions for a given energy and polarisation.
|
|
345
402
|
|
|
346
403
|
Methods
|
|
@@ -363,8 +420,7 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
363
420
|
|
|
364
421
|
def __init__(
|
|
365
422
|
self,
|
|
366
|
-
|
|
367
|
-
id_phase: UndulatorPhaseAxes,
|
|
423
|
+
apple2_motors: Apple2Motors,
|
|
368
424
|
energy_motor_convertor: EnergyMotorConvertor,
|
|
369
425
|
name: str = "",
|
|
370
426
|
) -> None:
|
|
@@ -378,8 +434,7 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
378
434
|
name: Name of the device.
|
|
379
435
|
"""
|
|
380
436
|
|
|
381
|
-
self.
|
|
382
|
-
self.phase = id_phase
|
|
437
|
+
self.motors = apple2_motors
|
|
383
438
|
self.energy_to_motor = energy_motor_convertor
|
|
384
439
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
385
440
|
# Store the set energy for readback.
|
|
@@ -398,11 +453,11 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
398
453
|
raw_to_derived=self._read_pol,
|
|
399
454
|
set_derived=self._set_pol,
|
|
400
455
|
pol=self.polarisation_setpoint,
|
|
401
|
-
top_outer=self.phase.top_outer.user_readback,
|
|
402
|
-
top_inner=self.phase.top_inner.user_readback,
|
|
403
|
-
btm_inner=self.phase.btm_inner.user_readback,
|
|
404
|
-
btm_outer=self.phase.btm_outer.user_readback,
|
|
405
|
-
gap=
|
|
456
|
+
top_outer=self.motors.phase.top_outer.user_readback,
|
|
457
|
+
top_inner=self.motors.phase.top_inner.user_readback,
|
|
458
|
+
btm_inner=self.motors.phase.btm_inner.user_readback,
|
|
459
|
+
btm_outer=self.motors.phase.btm_outer.user_readback,
|
|
460
|
+
gap=self.motors.gap.user_readback,
|
|
406
461
|
)
|
|
407
462
|
super().__init__(name)
|
|
408
463
|
|
|
@@ -420,24 +475,32 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
420
475
|
self._set_pol_setpoint(value)
|
|
421
476
|
await self.set(await self.energy.get_value())
|
|
422
477
|
|
|
423
|
-
@abc.abstractmethod
|
|
424
478
|
@AsyncStatus.wrap
|
|
425
479
|
async def set(self, value: float) -> None:
|
|
426
480
|
"""
|
|
427
481
|
Set should be in energy units, this will set the energy of the ID by setting the
|
|
428
482
|
gap and phase motors to the correct position for the given energy
|
|
429
483
|
and polarisation.
|
|
430
|
-
|
|
431
|
-
motor positions will be different for each beamline depending on the
|
|
432
|
-
undulator design and the lookup table used.
|
|
433
|
-
_set can be used to set the motor positions for the given energy and
|
|
434
|
-
polarisation provided that all motors can be moved at the same time.
|
|
484
|
+
|
|
435
485
|
|
|
436
486
|
Examples
|
|
437
487
|
--------
|
|
438
488
|
RE( id.set(888.0)) # This will set the ID to 888 eV
|
|
439
489
|
RE(scan([detector], id,600,700,100)) # This will scan the ID from 600 to 700 eV in 100 steps.
|
|
440
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.")
|
|
494
|
+
|
|
495
|
+
@abc.abstractmethod
|
|
496
|
+
async def _set(self, value: float) -> None:
|
|
497
|
+
"""
|
|
498
|
+
This method should be implemented by the beamline specific ID class as the
|
|
499
|
+
motor positions will be different for each beamline depending on the
|
|
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.
|
|
503
|
+
"""
|
|
441
504
|
|
|
442
505
|
def _read_pol(
|
|
443
506
|
self,
|
|
@@ -468,36 +531,6 @@ class Apple2(abc.ABC, StandardReadable, Movable):
|
|
|
468
531
|
|
|
469
532
|
return read_pol
|
|
470
533
|
|
|
471
|
-
async def _set(self, value: Apple2Val, energy: float) -> None:
|
|
472
|
-
"""
|
|
473
|
-
Check ID is in a movable state and set all the demand value before moving them
|
|
474
|
-
all at the same time. This should be modified by the beamline specific ID class
|
|
475
|
-
, if the ID motors has to move in a specific order.
|
|
476
|
-
"""
|
|
477
|
-
|
|
478
|
-
# Only need to check gap as the phase motors share both fault and gate with gap.
|
|
479
|
-
await self.gap.raise_if_cannot_move()
|
|
480
|
-
await asyncio.gather(
|
|
481
|
-
self.phase.top_outer.user_setpoint.set(value=value.top_outer),
|
|
482
|
-
self.phase.top_inner.user_setpoint.set(value=value.top_inner),
|
|
483
|
-
self.phase.btm_inner.user_setpoint.set(value=value.btm_inner),
|
|
484
|
-
self.phase.btm_outer.user_setpoint.set(value=value.btm_outer),
|
|
485
|
-
self.gap.user_setpoint.set(value=value.gap),
|
|
486
|
-
)
|
|
487
|
-
timeout = np.max(
|
|
488
|
-
await asyncio.gather(self.gap.get_timeout(), self.phase.get_timeout())
|
|
489
|
-
)
|
|
490
|
-
LOGGER.info(
|
|
491
|
-
f"Moving f{self.name} energy and polorisation to {energy}, {await self.polarisation.get_value()}"
|
|
492
|
-
+ f"with motor position {value}, timeout = {timeout}"
|
|
493
|
-
)
|
|
494
|
-
await asyncio.gather(
|
|
495
|
-
self.gap.set_move.set(value=1, wait=False, timeout=timeout),
|
|
496
|
-
self.phase.set_move.set(value=1, wait=False, timeout=timeout),
|
|
497
|
-
)
|
|
498
|
-
await wait_for_value(self.gap.gate, UndulatorGateStatus.CLOSE, timeout=timeout)
|
|
499
|
-
self._set_energy_rbv(energy) # Update energy after move for readback.
|
|
500
|
-
|
|
501
534
|
def determine_phase_from_hardware(
|
|
502
535
|
self,
|
|
503
536
|
top_outer: float,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .base_detector import (
|
|
2
|
-
|
|
2
|
+
BaseElectronAnalyserDetector,
|
|
3
3
|
)
|
|
4
4
|
from .base_driver_io import AbstractAnalyserDriverIO, TAbstractAnalyserDriverIO
|
|
5
5
|
from .base_region import (
|
|
@@ -19,7 +19,7 @@ __all__ = [
|
|
|
19
19
|
"TAcquisitionMode",
|
|
20
20
|
"TLensMode",
|
|
21
21
|
"AbstractAnalyserDriverIO",
|
|
22
|
-
"
|
|
22
|
+
"BaseElectronAnalyserDetector",
|
|
23
23
|
"AbstractAnalyserDriverIO",
|
|
24
24
|
"TAbstractAnalyserDriverIO",
|
|
25
25
|
]
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
1
|
from typing import Generic
|
|
3
2
|
|
|
4
3
|
from bluesky.protocols import Reading, Triggerable
|
|
@@ -9,14 +8,14 @@ from ophyd_async.core import (
|
|
|
9
8
|
AsyncStatus,
|
|
10
9
|
Device,
|
|
11
10
|
)
|
|
11
|
+
from ophyd_async.epics.adcore import ADBaseController
|
|
12
12
|
|
|
13
|
-
from dodal.devices.controllers import ConstantDeadTimeController
|
|
14
13
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
15
14
|
TAbstractAnalyserDriverIO,
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
class
|
|
18
|
+
class BaseElectronAnalyserDetector(
|
|
20
19
|
Device,
|
|
21
20
|
Triggerable,
|
|
22
21
|
AsyncReadable,
|
|
@@ -34,43 +33,31 @@ class AbstractElectronAnalyserDetector(
|
|
|
34
33
|
|
|
35
34
|
def __init__(
|
|
36
35
|
self,
|
|
37
|
-
|
|
36
|
+
controller: ADBaseController[TAbstractAnalyserDriverIO],
|
|
38
37
|
name: str = "",
|
|
39
38
|
):
|
|
40
|
-
self.
|
|
39
|
+
self._controller = controller
|
|
41
40
|
super().__init__(name)
|
|
42
41
|
|
|
43
42
|
@AsyncStatus.wrap
|
|
44
43
|
async def trigger(self) -> None:
|
|
45
|
-
await self.
|
|
46
|
-
await self.
|
|
44
|
+
await self._controller.arm()
|
|
45
|
+
await self._controller.wait_for_idle()
|
|
47
46
|
|
|
48
47
|
async def read(self) -> dict[str, Reading]:
|
|
49
|
-
return await self.driver.read()
|
|
48
|
+
return await self._controller.driver.read()
|
|
50
49
|
|
|
51
50
|
async def describe(self) -> dict[str, DataKey]:
|
|
52
|
-
data = await self.driver.describe()
|
|
51
|
+
data = await self._controller.driver.describe()
|
|
53
52
|
# Correct the shape for image
|
|
54
|
-
prefix = self.driver.name + "-"
|
|
55
|
-
energy_size = len(await self.driver.energy_axis.get_value())
|
|
56
|
-
angle_size = len(await self.driver.angle_axis.get_value())
|
|
53
|
+
prefix = self._controller.driver.name + "-"
|
|
54
|
+
energy_size = len(await self._controller.driver.energy_axis.get_value())
|
|
55
|
+
angle_size = len(await self._controller.driver.angle_axis.get_value())
|
|
57
56
|
data[prefix + "image"]["shape"] = [angle_size, energy_size]
|
|
58
57
|
return data
|
|
59
58
|
|
|
60
59
|
async def read_configuration(self) -> dict[str, Reading]:
|
|
61
|
-
return await self.driver.read_configuration()
|
|
60
|
+
return await self._controller.driver.read_configuration()
|
|
62
61
|
|
|
63
62
|
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
64
|
-
return await self.driver.describe_configuration()
|
|
65
|
-
|
|
66
|
-
@property
|
|
67
|
-
@abstractmethod
|
|
68
|
-
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
69
|
-
"""
|
|
70
|
-
Define common property for all implementations to access the driver. Some
|
|
71
|
-
implementations will store this as a reference so it doesn't have conflicting
|
|
72
|
-
parents.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
instance of the driver.
|
|
76
|
-
"""
|
|
63
|
+
return await self._controller.driver.describe_configuration()
|
|
@@ -153,11 +153,12 @@ class AbstractAnalyserDriverIO(
|
|
|
153
153
|
self.energy_source.selected_source.set(region.excitation_energy_source)
|
|
154
154
|
excitation_energy = await self.energy_source.energy.get_value()
|
|
155
155
|
|
|
156
|
-
#
|
|
157
|
-
ke_region = region.
|
|
158
|
-
ke_region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
|
|
159
|
-
|
|
156
|
+
# Switch to kinetic energy as epics doesn't support BINDING.
|
|
157
|
+
ke_region = region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
|
|
160
158
|
await self._set_region(ke_region)
|
|
159
|
+
# Set the true energy mode from original region so binding_energy_axis can be
|
|
160
|
+
# calculated correctly.
|
|
161
|
+
await self.energy_mode.set(region.energy_mode)
|
|
161
162
|
|
|
162
163
|
@abstractmethod
|
|
163
164
|
async def _set_region(self, ke_region: TAbstractBaseRegion):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from abc import ABC
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
-
from typing import Generic, TypeVar
|
|
4
|
+
from typing import Generic, Self, TypeVar
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field, model_validator
|
|
7
7
|
|
|
@@ -88,28 +88,43 @@ class AbstractBaseRegion(
|
|
|
88
88
|
return self.energy_mode == EnergyMode.KINETIC
|
|
89
89
|
|
|
90
90
|
def switch_energy_mode(
|
|
91
|
-
self, energy_mode: EnergyMode, excitation_energy: float
|
|
92
|
-
) ->
|
|
91
|
+
self, energy_mode: EnergyMode, excitation_energy: float, copy: bool = True
|
|
92
|
+
) -> Self:
|
|
93
93
|
"""
|
|
94
|
-
Switch region to new energy mode: Kinetic or Binding.
|
|
95
|
-
|
|
94
|
+
Switch region with to a new energy mode with a new energy mode: Kinetic or Binding.
|
|
95
|
+
It caculates new values for low_energy, centre_energy, high_energy, via the
|
|
96
|
+
excitation enerrgy. It doesn't calculate anything if the region is already of
|
|
97
|
+
the same energy mode.
|
|
96
98
|
|
|
97
99
|
Parameters:
|
|
98
|
-
energy_mode:
|
|
99
|
-
excitation_energy:
|
|
100
|
-
|
|
100
|
+
energy_mode: Mode you want to switch the region to.
|
|
101
|
+
excitation_energy: Energy conversion for low_energy, centre_energy, and
|
|
102
|
+
high_energy for new energy mode.
|
|
103
|
+
copy: Defaults to True. If true, create a copy of this region for the new
|
|
104
|
+
energy_mode and return it. If False, alter this region for the
|
|
105
|
+
energy_mode and return it self.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Region with selected energy mode and new calculated energy values.
|
|
101
109
|
"""
|
|
110
|
+
switched_r = self.model_copy() if copy else self
|
|
102
111
|
conv = (
|
|
103
112
|
to_binding_energy
|
|
104
113
|
if energy_mode == EnergyMode.BINDING
|
|
105
114
|
else to_kinetic_energy
|
|
106
115
|
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self.centre_energy, self.energy_mode, excitation_energy
|
|
116
|
+
switched_r.low_energy = conv(
|
|
117
|
+
switched_r.low_energy, switched_r.energy_mode, excitation_energy
|
|
110
118
|
)
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
switched_r.centre_energy = conv(
|
|
120
|
+
switched_r.centre_energy, switched_r.energy_mode, excitation_energy
|
|
121
|
+
)
|
|
122
|
+
switched_r.high_energy = conv(
|
|
123
|
+
switched_r.high_energy, switched_r.energy_mode, excitation_energy
|
|
124
|
+
)
|
|
125
|
+
switched_r.energy_mode = energy_mode
|
|
126
|
+
|
|
127
|
+
return switched_r
|
|
113
128
|
|
|
114
129
|
@model_validator(mode="before")
|
|
115
130
|
@classmethod
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from typing import Generic, TypeVar
|
|
2
2
|
|
|
3
3
|
from bluesky.protocols import Stageable
|
|
4
|
-
from ophyd_async.core import
|
|
5
|
-
|
|
6
|
-
Reference,
|
|
7
|
-
)
|
|
4
|
+
from ophyd_async.core import AsyncStatus
|
|
5
|
+
from ophyd_async.epics.adcore import ADBaseController
|
|
8
6
|
|
|
9
7
|
from dodal.common.data_util import load_json_file_to_class
|
|
8
|
+
from dodal.devices.controllers import ConstantDeadTimeController
|
|
10
9
|
from dodal.devices.electron_analyser.abstract.base_detector import (
|
|
11
|
-
|
|
10
|
+
BaseElectronAnalyserDetector,
|
|
12
11
|
)
|
|
13
12
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
14
13
|
TAbstractAnalyserDriverIO,
|
|
@@ -20,35 +19,27 @@ from dodal.devices.electron_analyser.abstract.base_region import (
|
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class ElectronAnalyserRegionDetector(
|
|
23
|
-
|
|
22
|
+
BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
24
23
|
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
25
24
|
):
|
|
26
25
|
"""
|
|
27
26
|
Extends electron analyser detector to configure specific region settings before data
|
|
28
|
-
|
|
29
|
-
is designed to only exist inside a plan.
|
|
27
|
+
acquisition. It is designed to only exist inside a plan.
|
|
30
28
|
"""
|
|
31
29
|
|
|
32
30
|
def __init__(
|
|
33
31
|
self,
|
|
34
|
-
|
|
32
|
+
controller: ADBaseController[TAbstractAnalyserDriverIO],
|
|
35
33
|
region: TAbstractBaseRegion,
|
|
36
34
|
name: str = "",
|
|
37
35
|
):
|
|
38
|
-
self._driver_ref = Reference(driver)
|
|
39
36
|
self.region = region
|
|
40
|
-
super().__init__(
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
44
|
-
# Store as a reference, this implementation will be given a driver so needs to
|
|
45
|
-
# make sure we don't get conflicting parents.
|
|
46
|
-
return self._driver_ref()
|
|
37
|
+
super().__init__(controller, name)
|
|
47
38
|
|
|
48
39
|
@AsyncStatus.wrap
|
|
49
40
|
async def trigger(self) -> None:
|
|
50
41
|
# Configure region parameters on the driver first before data collection.
|
|
51
|
-
await self.driver.set(self.region)
|
|
42
|
+
await self._controller.driver.set(self.region)
|
|
52
43
|
await super().trigger()
|
|
53
44
|
|
|
54
45
|
|
|
@@ -59,7 +50,7 @@ TElectronAnalyserRegionDetector = TypeVar(
|
|
|
59
50
|
|
|
60
51
|
|
|
61
52
|
class ElectronAnalyserDetector(
|
|
62
|
-
|
|
53
|
+
BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
63
54
|
Stageable,
|
|
64
55
|
Generic[
|
|
65
56
|
TAbstractAnalyserDriverIO,
|
|
@@ -79,16 +70,11 @@ class ElectronAnalyserDetector(
|
|
|
79
70
|
driver: TAbstractAnalyserDriverIO,
|
|
80
71
|
name: str = "",
|
|
81
72
|
):
|
|
82
|
-
#
|
|
83
|
-
self.
|
|
73
|
+
# Save driver as direct child so participates with connect()
|
|
74
|
+
self.driver = driver
|
|
84
75
|
self._sequence_class = sequence_class
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@property
|
|
88
|
-
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
89
|
-
# This implementation creates the driver and wants this to be the parent so it
|
|
90
|
-
# can be used with connect() method.
|
|
91
|
-
return self._driver
|
|
76
|
+
controller = ConstantDeadTimeController[TAbstractAnalyserDriverIO](driver, 0)
|
|
77
|
+
super().__init__(controller, name)
|
|
92
78
|
|
|
93
79
|
@AsyncStatus.wrap
|
|
94
80
|
async def stage(self) -> None:
|
|
@@ -103,13 +89,13 @@ class ElectronAnalyserDetector(
|
|
|
103
89
|
Raises:
|
|
104
90
|
Any exceptions raised by the driver's stage or controller's disarm methods.
|
|
105
91
|
"""
|
|
106
|
-
await self.
|
|
92
|
+
await self._controller.disarm()
|
|
107
93
|
await self.driver.stage()
|
|
108
94
|
|
|
109
95
|
@AsyncStatus.wrap
|
|
110
96
|
async def unstage(self) -> None:
|
|
111
97
|
"""Disarm the detector."""
|
|
112
|
-
await self.
|
|
98
|
+
await self._controller.disarm()
|
|
113
99
|
await self.driver.unstage()
|
|
114
100
|
|
|
115
101
|
def load_sequence(self, filename: str) -> TAbstractBaseSequence:
|
|
@@ -144,7 +130,9 @@ class ElectronAnalyserDetector(
|
|
|
144
130
|
seq = self.load_sequence(filename)
|
|
145
131
|
regions = seq.get_enabled_regions() if enabled_only else seq.regions
|
|
146
132
|
return [
|
|
147
|
-
ElectronAnalyserRegionDetector(
|
|
133
|
+
ElectronAnalyserRegionDetector(
|
|
134
|
+
self._controller, r, self.name + "_" + r.name
|
|
135
|
+
)
|
|
148
136
|
for r in regions
|
|
149
137
|
]
|
|
150
138
|
|
|
@@ -66,7 +66,6 @@ class SpecsAnalyserDriverIO(
|
|
|
66
66
|
async def _set_region(self, ke_region: SpecsRegion[TLensMode, TPsuMode]):
|
|
67
67
|
await asyncio.gather(
|
|
68
68
|
self.region_name.set(ke_region.name),
|
|
69
|
-
self.energy_mode.set(ke_region.energy_mode),
|
|
70
69
|
self.low_energy.set(ke_region.low_energy),
|
|
71
70
|
self.high_energy.set(ke_region.high_energy),
|
|
72
71
|
self.slices.set(ke_region.slices),
|
|
@@ -74,7 +74,6 @@ class VGScientaAnalyserDriverIO(
|
|
|
74
74
|
async def _set_region(self, ke_region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
|
|
75
75
|
await asyncio.gather(
|
|
76
76
|
self.region_name.set(ke_region.name),
|
|
77
|
-
self.energy_mode.set(ke_region.energy_mode),
|
|
78
77
|
self.low_energy.set(ke_region.low_energy),
|
|
79
78
|
self.centre_energy.set(ke_region.centre_energy),
|
|
80
79
|
self.high_energy.set(ke_region.high_energy),
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -296,15 +296,15 @@ class FastGridScanThreeD(FastGridScanCommon[ParamType]):
|
|
|
296
296
|
Subclasses must implement _create_position_counter.
|
|
297
297
|
"""
|
|
298
298
|
|
|
299
|
-
def __init__(self, prefix: str, name: str = "") -> None:
|
|
300
|
-
full_prefix = prefix +
|
|
299
|
+
def __init__(self, prefix: str, infix: str, name: str = "") -> None:
|
|
300
|
+
full_prefix = prefix + infix
|
|
301
301
|
|
|
302
302
|
# Number of vertical steps during the second grid scan, after the rotation in omega
|
|
303
|
-
self.z_steps = epics_signal_rw_rbv(int, f"{
|
|
304
|
-
self.z_step_size = epics_signal_rw_rbv(float, f"{
|
|
305
|
-
self.z2_start = epics_signal_rw_rbv(float, f"{
|
|
306
|
-
self.y2_start = epics_signal_rw_rbv(float, f"{
|
|
307
|
-
|
|
303
|
+
self.z_steps = epics_signal_rw_rbv(int, f"{full_prefix}Z_NUM_STEPS")
|
|
304
|
+
self.z_step_size = epics_signal_rw_rbv(float, f"{full_prefix}Z_STEP_SIZE")
|
|
305
|
+
self.z2_start = epics_signal_rw_rbv(float, f"{full_prefix}Z2_START")
|
|
306
|
+
self.y2_start = epics_signal_rw_rbv(float, f"{full_prefix}Y2_START")
|
|
307
|
+
# panda does not have x counter
|
|
308
308
|
self.y_counter = epics_signal_r(int, f"{full_prefix}Y_COUNTER")
|
|
309
309
|
|
|
310
310
|
super().__init__(full_prefix, prefix, name)
|
|
@@ -343,10 +343,12 @@ class ZebraFastGridScanThreeD(FastGridScanThreeD[ZebraGridScanParamsThreeD]):
|
|
|
343
343
|
"""
|
|
344
344
|
|
|
345
345
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
346
|
-
|
|
346
|
+
infix = "FGS:"
|
|
347
|
+
full_prefix = prefix + infix
|
|
347
348
|
# Time taken to travel between X steps
|
|
348
349
|
self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}DWELL_TIME")
|
|
349
|
-
|
|
350
|
+
self.x_counter = epics_signal_r(int, f"{full_prefix}X_COUNTER")
|
|
351
|
+
super().__init__(prefix, infix, name)
|
|
350
352
|
self.movable_params["dwell_time_ms"] = self.dwell_time_ms
|
|
351
353
|
|
|
352
354
|
def _create_position_counter(self, prefix: str):
|
|
@@ -363,7 +365,8 @@ class PandAFastGridScan(FastGridScanThreeD[PandAGridScanParams]):
|
|
|
363
365
|
"""
|
|
364
366
|
|
|
365
367
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
366
|
-
|
|
368
|
+
infix = "PGS:"
|
|
369
|
+
full_prefix = prefix + infix
|
|
367
370
|
self.time_between_x_steps_ms = (
|
|
368
371
|
epics_signal_rw_rbv( # Used by motion controller to set goniometer velocity
|
|
369
372
|
float, f"{full_prefix}TIME_BETWEEN_X_STEPS"
|
|
@@ -375,7 +378,7 @@ class PandAFastGridScan(FastGridScanThreeD[PandAGridScanParams]):
|
|
|
375
378
|
self.run_up_distance_mm = epics_signal_rw_rbv(
|
|
376
379
|
float, f"{full_prefix}RUNUP_DISTANCE"
|
|
377
380
|
)
|
|
378
|
-
super().__init__(prefix, name)
|
|
381
|
+
super().__init__(prefix, infix, name)
|
|
379
382
|
|
|
380
383
|
self.movable_params["run_up_distance_mm"] = self.run_up_distance_mm
|
|
381
384
|
|