dls-dodal 1.62.0__py3-none-any.whl → 1.63.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.63.0.dist-info}/METADATA +1 -1
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.63.0.dist-info}/RECORD +76 -71
- dls_dodal-1.63.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/i09.py +10 -3
- dodal/beamlines/i09_1.py +9 -3
- dodal/beamlines/i10.py +7 -69
- dodal/beamlines/i10_1.py +35 -0
- dodal/beamlines/i10_optics.py +205 -0
- dodal/beamlines/i15_1.py +5 -5
- dodal/beamlines/i17.py +50 -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/i24.py +3 -3
- dodal/cli.py +4 -4
- dodal/common/visit.py +4 -4
- dodal/devices/aperturescatterguard.py +6 -4
- dodal/devices/apple2_undulator.py +211 -114
- dodal/devices/attenuator/filter_selections.py +6 -6
- dodal/devices/common_dcm.py +62 -15
- 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/i04/murko_results.py +35 -14
- dodal/devices/i09/__init__.py +1 -2
- 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 +6 -45
- 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 +2 -2
- 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/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 +2 -2
- 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.63.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.63.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.63.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/devices/i10/i10_apple2.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import csv
|
|
3
2
|
import io
|
|
4
3
|
from dataclasses import dataclass
|
|
@@ -10,20 +9,18 @@ from bluesky.protocols import Movable
|
|
|
10
9
|
from daq_config_server.client import ConfigServer
|
|
11
10
|
from ophyd_async.core import (
|
|
12
11
|
AsyncStatus,
|
|
13
|
-
Device,
|
|
14
12
|
Reference,
|
|
15
13
|
StandardReadable,
|
|
16
14
|
StandardReadableFormat,
|
|
17
|
-
|
|
15
|
+
derived_signal_rw,
|
|
18
16
|
soft_signal_rw,
|
|
19
17
|
)
|
|
20
18
|
from pydantic import BaseModel, ConfigDict, RootModel
|
|
21
19
|
|
|
22
20
|
from dodal.devices.apple2_undulator import (
|
|
23
21
|
Apple2,
|
|
24
|
-
|
|
22
|
+
Apple2Controller,
|
|
25
23
|
Apple2Val,
|
|
26
|
-
EnergyMotorConvertor,
|
|
27
24
|
Pol,
|
|
28
25
|
UndulatorGap,
|
|
29
26
|
UndulatorJawPhase,
|
|
@@ -31,8 +28,6 @@ from dodal.devices.apple2_undulator import (
|
|
|
31
28
|
)
|
|
32
29
|
from dodal.log import LOGGER
|
|
33
30
|
|
|
34
|
-
from ..pgm import PGM
|
|
35
|
-
|
|
36
31
|
ROW_PHASE_MOTOR_TOLERANCE = 0.004
|
|
37
32
|
MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
|
|
38
33
|
MAXIMUM_GAP_MOTOR_POSITION = 100
|
|
@@ -107,7 +102,7 @@ class I10EnergyMotorLookup:
|
|
|
107
102
|
|
|
108
103
|
def __init__(
|
|
109
104
|
self,
|
|
110
|
-
|
|
105
|
+
lookuptable_dir: str,
|
|
111
106
|
source: tuple[str, str],
|
|
112
107
|
config_client: ConfigServer,
|
|
113
108
|
mode: str = "Mode",
|
|
@@ -141,8 +136,8 @@ class I10EnergyMotorLookup:
|
|
|
141
136
|
"Gap": {},
|
|
142
137
|
"Phase": {},
|
|
143
138
|
}
|
|
144
|
-
energy_gap_table_path = Path(
|
|
145
|
-
energy_phase_table_path = Path(
|
|
139
|
+
energy_gap_table_path = Path(lookuptable_dir, gap_file_name)
|
|
140
|
+
energy_phase_table_path = Path(lookuptable_dir, phase_file_name)
|
|
146
141
|
self.lookup_table_config = LookupTableConfig(
|
|
147
142
|
path=LookupPath(Gap=energy_gap_table_path, Phase=energy_phase_table_path),
|
|
148
143
|
source=source,
|
|
@@ -322,82 +317,123 @@ class I10EnergyMotorLookup:
|
|
|
322
317
|
|
|
323
318
|
|
|
324
319
|
class I10Apple2(Apple2):
|
|
325
|
-
|
|
326
|
-
|
|
320
|
+
def __init__(
|
|
321
|
+
self,
|
|
322
|
+
id_gap: UndulatorGap,
|
|
323
|
+
id_phase: UndulatorPhaseAxes,
|
|
324
|
+
id_jaw_phase: UndulatorJawPhase,
|
|
325
|
+
name: str = "",
|
|
326
|
+
) -> None:
|
|
327
|
+
"""
|
|
328
|
+
I10Apple2 device is an apple2 with extra jaw phase motor.
|
|
327
329
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
330
332
|
|
|
331
|
-
|
|
333
|
+
id_gap : UndulatorJawPhase
|
|
334
|
+
The gap motor of the undulator.
|
|
335
|
+
id_phase : UndulatorJawPhase
|
|
336
|
+
The phase motors of the undulator.
|
|
337
|
+
id_jaw_phase : UndulatorJawPhase
|
|
338
|
+
The jaw phase motor of the undulator.
|
|
339
|
+
name : str, optional
|
|
340
|
+
The name of the device, by default "".
|
|
341
|
+
"""
|
|
342
|
+
with self.add_children_as_readables():
|
|
343
|
+
self.jaw_phase = id_jaw_phase
|
|
344
|
+
super().__init__(id_gap=id_gap, id_phase=id_phase, name=name)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class I10Apple2Controller(Apple2Controller[I10Apple2]):
|
|
348
|
+
"""
|
|
349
|
+
I10Apple2Controller is a extension of Apple2Controller which provide linear
|
|
350
|
+
arbitrary angle control.
|
|
332
351
|
"""
|
|
333
352
|
|
|
334
353
|
def __init__(
|
|
335
354
|
self,
|
|
336
|
-
|
|
337
|
-
|
|
355
|
+
apple2: I10Apple2,
|
|
356
|
+
lookuptable_dir: str,
|
|
357
|
+
source: tuple[str, str],
|
|
358
|
+
config_client: ConfigServer,
|
|
359
|
+
jaw_phase_limit: float = 12.0,
|
|
360
|
+
jaw_phase_poly_param: list[float] = DEFAULT_JAW_PHASE_POLY_PARAMS,
|
|
361
|
+
angle_threshold_deg=30.0,
|
|
338
362
|
name: str = "",
|
|
339
363
|
) -> None:
|
|
340
364
|
"""
|
|
341
|
-
|
|
365
|
+
|
|
366
|
+
parameters
|
|
342
367
|
----------
|
|
343
|
-
|
|
368
|
+
apple2 : I10Apple2
|
|
369
|
+
An I10Apple2 device.
|
|
370
|
+
lookuptable_dir : str
|
|
344
371
|
The path to look up table.
|
|
345
|
-
source:
|
|
346
|
-
The column name and the name of the source in look up table. e.g. ("source", "idu")
|
|
347
|
-
|
|
348
|
-
The
|
|
349
|
-
|
|
350
|
-
The
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
The
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
Name:
|
|
358
|
-
Name of the device
|
|
372
|
+
source : tuple[str, str]
|
|
373
|
+
The column name and the name of the source in look up table. e.g. ( "source", "idu")
|
|
374
|
+
config_client : ConfigServer
|
|
375
|
+
The config server client to fetch the look up table.
|
|
376
|
+
jaw_phase_limit : float, optional
|
|
377
|
+
The maximum allowed jaw_phase movement., by default 12.0
|
|
378
|
+
jaw_phase_poly_param : list[float], optional
|
|
379
|
+
polynomial parameters highest power first., by default DEFAULT_JAW_PHASE_POLY_PARAMS
|
|
380
|
+
angle_threshold_deg : float, optional
|
|
381
|
+
The angle threshold to switch between 0-180 and 180-360 range., by default 30.0
|
|
382
|
+
name : str, optional
|
|
383
|
+
New device name.
|
|
359
384
|
"""
|
|
360
385
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
),
|
|
372
|
-
),
|
|
373
|
-
energy_motor_convertor=energy_motor_convertor,
|
|
374
|
-
name=name,
|
|
375
|
-
)
|
|
376
|
-
self.id_jaw_phase = UndulatorJawPhase(
|
|
377
|
-
prefix=prefix,
|
|
378
|
-
move_pv="RPQ1",
|
|
379
|
-
)
|
|
386
|
+
self.lookup_table_client = I10EnergyMotorLookup(
|
|
387
|
+
lookuptable_dir=lookuptable_dir,
|
|
388
|
+
source=source,
|
|
389
|
+
config_client=config_client,
|
|
390
|
+
)
|
|
391
|
+
super().__init__(
|
|
392
|
+
apple2=apple2,
|
|
393
|
+
energy_to_motor_converter=self.lookup_table_client.get_motor_from_energy,
|
|
394
|
+
name=name,
|
|
395
|
+
)
|
|
380
396
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
"""
|
|
397
|
+
self.jaw_phase_from_angle = np.poly1d(jaw_phase_poly_param)
|
|
398
|
+
self.angle_threshold_deg = angle_threshold_deg
|
|
399
|
+
self.jaw_phase_limit = jaw_phase_limit
|
|
400
|
+
self._linear_arbitrary_angle = soft_signal_rw(float, initial_value=None)
|
|
386
401
|
|
|
387
|
-
|
|
402
|
+
self.linear_arbitrary_angle = derived_signal_rw(
|
|
403
|
+
raw_to_derived=self._read_linear_arbitrary_angle,
|
|
404
|
+
set_derived=self._set_linear_arbitrary_angle,
|
|
405
|
+
pol_angle=self._linear_arbitrary_angle,
|
|
406
|
+
pol=self.polarisation,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def _read_linear_arbitrary_angle(self, pol_angle: float, pol: Pol) -> float:
|
|
410
|
+
self._raise_if_not_la(pol)
|
|
411
|
+
return pol_angle
|
|
388
412
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
413
|
+
async def _set_linear_arbitrary_angle(self, pol_angle: float) -> None:
|
|
414
|
+
pol = await self.polarisation.get_value()
|
|
415
|
+
self._raise_if_not_la(pol)
|
|
416
|
+
# Moving to real angle which is 210 to 30.
|
|
417
|
+
alpha_real = (
|
|
418
|
+
pol_angle
|
|
419
|
+
if pol_angle > self.angle_threshold_deg
|
|
420
|
+
else pol_angle + ALPHA_OFFSET
|
|
421
|
+
)
|
|
422
|
+
jaw_phase = self.jaw_phase_from_angle(alpha_real)
|
|
423
|
+
if abs(jaw_phase) > self.jaw_phase_limit:
|
|
424
|
+
raise RuntimeError(
|
|
425
|
+
f"jaw_phase position for angle ({pol_angle}) is outside permitted range"
|
|
426
|
+
f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]"
|
|
393
427
|
)
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
428
|
+
await self.apple2().jaw_phase.set(jaw_phase)
|
|
429
|
+
await self._linear_arbitrary_angle.set(pol_angle)
|
|
430
|
+
|
|
431
|
+
async def _set_motors_from_energy(self, value: float) -> None:
|
|
432
|
+
"""
|
|
433
|
+
Set the undulator motors for a given energy and polarisation.
|
|
434
|
+
"""
|
|
399
435
|
|
|
400
|
-
|
|
436
|
+
pol = await self._check_and_get_pol_setpoint()
|
|
401
437
|
gap, phase = self.energy_to_motor(energy=value, pol=pol)
|
|
402
438
|
phase3 = phase * (-1 if pol == Pol.LA else 1)
|
|
403
439
|
id_set_val = Apple2Val(
|
|
@@ -409,185 +445,45 @@ class I10Apple2(Apple2):
|
|
|
409
445
|
)
|
|
410
446
|
|
|
411
447
|
LOGGER.info(f"Setting polarisation to {pol}, with values: {id_set_val}")
|
|
412
|
-
await self.
|
|
448
|
+
await self.apple2().set(id_motor_values=id_set_val)
|
|
413
449
|
if pol != Pol.LA:
|
|
414
|
-
await self.
|
|
415
|
-
await self.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
class EnergySetter(StandardReadable, Movable[float]):
|
|
419
|
-
"""
|
|
420
|
-
Compound device to set both ID and PGM energy at the same time.
|
|
421
|
-
|
|
422
|
-
"""
|
|
423
|
-
|
|
424
|
-
def __init__(self, id: I10Apple2, pgm: PGM, name: str = "") -> None:
|
|
425
|
-
"""
|
|
426
|
-
Parameters
|
|
427
|
-
----------
|
|
428
|
-
id:
|
|
429
|
-
An Apple2 device.
|
|
430
|
-
pgm:
|
|
431
|
-
A PGM/mono device.
|
|
432
|
-
name:
|
|
433
|
-
New device name.
|
|
434
|
-
"""
|
|
435
|
-
super().__init__(name=name)
|
|
436
|
-
self.id = id
|
|
437
|
-
self.pgm_ref = Reference(pgm)
|
|
438
|
-
|
|
439
|
-
self.add_readables(
|
|
440
|
-
[self.id.energy, self.pgm_ref().energy.user_readback],
|
|
441
|
-
StandardReadableFormat.HINTED_SIGNAL,
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
445
|
-
self.energy_offset = soft_signal_rw(float, initial_value=0)
|
|
446
|
-
|
|
447
|
-
@AsyncStatus.wrap
|
|
448
|
-
async def set(self, value: float) -> None:
|
|
449
|
-
LOGGER.info(f"Moving f{self.name} energy to {value}.")
|
|
450
|
-
await asyncio.gather(
|
|
451
|
-
self.id.set(value=value + await self.energy_offset.get_value()),
|
|
452
|
-
self.pgm_ref().energy.set(value),
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
class I10Apple2Pol(StandardReadable, Movable[Pol]):
|
|
457
|
-
"""
|
|
458
|
-
Compound device to set polorisation of ID.
|
|
459
|
-
"""
|
|
460
|
-
|
|
461
|
-
def __init__(self, id: I10Apple2, name: str = "") -> None:
|
|
462
|
-
"""
|
|
463
|
-
Parameters
|
|
464
|
-
----------
|
|
465
|
-
id:
|
|
466
|
-
An I10Apple2 device.
|
|
467
|
-
name:
|
|
468
|
-
New device name.
|
|
469
|
-
"""
|
|
470
|
-
super().__init__(name=name)
|
|
471
|
-
self.id_ref = Reference(id)
|
|
472
|
-
self.add_readables([self.id_ref().polarisation])
|
|
450
|
+
await self.apple2().jaw_phase.set(0)
|
|
451
|
+
await self.apple2().jaw_phase.set_move.set(1)
|
|
473
452
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
453
|
+
def _raise_if_not_la(self, pol: Pol) -> None:
|
|
454
|
+
if pol != Pol.LA:
|
|
455
|
+
raise RuntimeError(
|
|
456
|
+
"Angle control is not available in polarisation"
|
|
457
|
+
+ f" {pol} with {self.name}"
|
|
458
|
+
)
|
|
479
459
|
|
|
480
460
|
|
|
481
461
|
class LinearArbitraryAngle(StandardReadable, Movable[SupportsFloat]):
|
|
482
462
|
"""
|
|
483
|
-
Device to set
|
|
484
|
-
is the direction of the magnetic field which can be change by varying the jaw_phase
|
|
485
|
-
in (linear arbitrary (la) mode,
|
|
486
|
-
The angle of 0 is equivalent to linear horizontal "lh" (sigma) and
|
|
487
|
-
90 is linear vertical "lv" (pi).
|
|
488
|
-
This device require a jaw_phase to angle conversion which is done via a polynomial.
|
|
463
|
+
Device to set the polarisation angle of the Apple2 undulator in Linear Arbitrary (LA) mode.
|
|
489
464
|
"""
|
|
490
465
|
|
|
491
466
|
def __init__(
|
|
492
467
|
self,
|
|
493
|
-
|
|
468
|
+
id_controller: I10Apple2Controller,
|
|
494
469
|
name: str = "",
|
|
495
|
-
jaw_phase_limit: float = 12.0,
|
|
496
|
-
jaw_phase_poly_param: list[float] = DEFAULT_JAW_PHASE_POLY_PARAMS,
|
|
497
|
-
angle_threshold_deg=30.0,
|
|
498
470
|
) -> None:
|
|
499
471
|
"""
|
|
500
472
|
Parameters
|
|
501
473
|
----------
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
name: str
|
|
474
|
+
id_controller : I10Apple2Controller
|
|
475
|
+
The I10Apple2Controller which control the ID.
|
|
476
|
+
name : str, optional
|
|
505
477
|
New device name.
|
|
506
|
-
jaw_phase_limit: float
|
|
507
|
-
The maximum allowed jaw_phase movement.
|
|
508
|
-
jaw_phase_poly_param: list
|
|
509
|
-
polynomial parameters highest power first.
|
|
510
478
|
"""
|
|
511
479
|
super().__init__(name=name)
|
|
512
|
-
self.
|
|
513
|
-
self.jaw_phase_from_angle = np.poly1d(jaw_phase_poly_param)
|
|
514
|
-
self.angle_threshold_deg = angle_threshold_deg
|
|
515
|
-
self.jaw_phase_limit = jaw_phase_limit
|
|
516
|
-
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
517
|
-
self.angle, self._angle_set = soft_signal_r_and_setter(
|
|
518
|
-
float, initial_value=None
|
|
519
|
-
)
|
|
480
|
+
self.linear_arbitrary_angle = Reference(id_controller.linear_arbitrary_angle)
|
|
520
481
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
pol = await self.id_ref().polarisation.get_value()
|
|
525
|
-
if pol != Pol.LA:
|
|
526
|
-
raise RuntimeError(
|
|
527
|
-
f"Angle control is not available in polarisation {pol} with {self.id_ref().name}"
|
|
528
|
-
)
|
|
529
|
-
# Moving to real angle which is 210 to 30.
|
|
530
|
-
alpha_real = value if value > self.angle_threshold_deg else value + ALPHA_OFFSET
|
|
531
|
-
jaw_phase = self.jaw_phase_from_angle(alpha_real)
|
|
532
|
-
if abs(jaw_phase) > self.jaw_phase_limit:
|
|
533
|
-
raise RuntimeError(
|
|
534
|
-
f"jaw_phase position for angle ({value}) is outside permitted range"
|
|
535
|
-
f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]"
|
|
536
|
-
)
|
|
537
|
-
await self.id_ref().id_jaw_phase.set(jaw_phase)
|
|
538
|
-
self._angle_set(value)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
class I10Id(Device):
|
|
542
|
-
def __init__(
|
|
543
|
-
self,
|
|
544
|
-
pgm: PGM,
|
|
545
|
-
prefix: str,
|
|
546
|
-
look_up_table_dir: str,
|
|
547
|
-
source: tuple[str, str],
|
|
548
|
-
config_client: ConfigServer,
|
|
549
|
-
jaw_phase_limit=12.0,
|
|
550
|
-
jaw_phase_poly_param=DEFAULT_JAW_PHASE_POLY_PARAMS,
|
|
551
|
-
angle_threshold_deg=30.0,
|
|
552
|
-
name: str = "",
|
|
553
|
-
) -> None:
|
|
554
|
-
"""I10Id is a compound device that combines the I10-specific Apple2 undulator,
|
|
555
|
-
energy setter, and polarization control.
|
|
556
|
-
This class provides a high-level interface for controlling the undulator's
|
|
557
|
-
energy, polarization, and linear arbitrary angle.
|
|
558
|
-
|
|
559
|
-
Attributes
|
|
560
|
-
----------
|
|
561
|
-
id : I10Apple2
|
|
562
|
-
The I10-specific Apple2 undulator device.
|
|
563
|
-
energy_setter : EnergySetter
|
|
564
|
-
A device for synchronizing the undulator and monochromator energy.
|
|
565
|
-
pol : I10Apple2Pol
|
|
566
|
-
A device for controlling the polarization of the undulator.
|
|
567
|
-
linear_arbitrary_angle : LinearArbitraryAngle
|
|
568
|
-
A device for controlling the linear arbitrary polarization angle.
|
|
569
|
-
"""
|
|
570
|
-
self.lookup_table_client = I10EnergyMotorLookup(
|
|
571
|
-
look_up_table_dir=look_up_table_dir,
|
|
572
|
-
source=source,
|
|
573
|
-
config_client=config_client,
|
|
574
|
-
)
|
|
575
|
-
self.energy = EnergySetter(
|
|
576
|
-
id=I10Apple2(
|
|
577
|
-
prefix=prefix,
|
|
578
|
-
energy_motor_convertor=self.lookup_table_client.get_motor_from_energy,
|
|
579
|
-
name="id_energy",
|
|
580
|
-
),
|
|
581
|
-
pgm=pgm,
|
|
582
|
-
name="energy",
|
|
583
|
-
)
|
|
584
|
-
self.pol = I10Apple2Pol(id=self.energy.id, name="pol")
|
|
585
|
-
self.laa = LinearArbitraryAngle(
|
|
586
|
-
id=self.energy.id,
|
|
587
|
-
name="laa",
|
|
588
|
-
jaw_phase_limit=jaw_phase_limit,
|
|
589
|
-
jaw_phase_poly_param=jaw_phase_poly_param,
|
|
590
|
-
angle_threshold_deg=angle_threshold_deg,
|
|
482
|
+
self.add_readables(
|
|
483
|
+
[self.linear_arbitrary_angle()],
|
|
484
|
+
StandardReadableFormat.HINTED_SIGNAL,
|
|
591
485
|
)
|
|
592
486
|
|
|
593
|
-
|
|
487
|
+
@AsyncStatus.wrap
|
|
488
|
+
async def set(self, angle: float) -> None:
|
|
489
|
+
await self.linear_arbitrary_angle().set(angle)
|
dodal/devices/i10/slits.py
CHANGED
|
@@ -77,9 +77,7 @@ class I10PrimarySlits(Slits):
|
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
class
|
|
81
|
-
"""Collection of all the i10 slits before end station."""
|
|
82
|
-
|
|
80
|
+
class I10SharedSlits(Device):
|
|
83
81
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
84
82
|
self.s1 = I10PrimarySlits(
|
|
85
83
|
prefix=prefix + "01:",
|
|
@@ -90,6 +88,13 @@ class I10Slits(Device):
|
|
|
90
88
|
self.s3 = I10SlitsBlades(
|
|
91
89
|
prefix=prefix + "03:",
|
|
92
90
|
)
|
|
91
|
+
super().__init__(name=name)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class I10Slits(Device):
|
|
95
|
+
"""Collection of all the i10 slits before end station."""
|
|
96
|
+
|
|
97
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
93
98
|
self.s4 = MinimalSlits(
|
|
94
99
|
prefix=prefix + "04:",
|
|
95
100
|
x_gap="XSIZE",
|
|
@@ -104,9 +109,39 @@ class I10Slits(Device):
|
|
|
104
109
|
super().__init__(name=name)
|
|
105
110
|
|
|
106
111
|
|
|
112
|
+
class I10JSlits(Device):
|
|
113
|
+
"""Collection of all the i10-1 slits before end station."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
116
|
+
self.s7 = MinimalSlits(
|
|
117
|
+
prefix=prefix + "01:",
|
|
118
|
+
x_gap="XSIZE",
|
|
119
|
+
y_gap="YSIZE",
|
|
120
|
+
)
|
|
121
|
+
self.s8 = I10SlitsBlades(
|
|
122
|
+
prefix=prefix + "02:",
|
|
123
|
+
)
|
|
124
|
+
self.s9 = I10SlitsBlades(
|
|
125
|
+
prefix=prefix + "03:",
|
|
126
|
+
)
|
|
127
|
+
super().__init__(name=name)
|
|
128
|
+
|
|
129
|
+
|
|
107
130
|
class I10SlitsDrainCurrent(Device):
|
|
108
131
|
"""Collection of all the drain current from i10 slits."""
|
|
109
132
|
|
|
133
|
+
def __init__(
|
|
134
|
+
self, prefix: str, name: str = "", connector: DeviceConnector | None = None
|
|
135
|
+
) -> None:
|
|
136
|
+
self.s4 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-02:")
|
|
137
|
+
self.s5 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-03:")
|
|
138
|
+
self.s6 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-04:")
|
|
139
|
+
super().__init__(name, connector)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class I10SharedSlitsDrainCurrent(Device):
|
|
143
|
+
"""Collection of all the drain current from i10 and i10-1 slits."""
|
|
144
|
+
|
|
110
145
|
def __init__(
|
|
111
146
|
self, prefix: str, name: str = "", connector: DeviceConnector | None = None
|
|
112
147
|
) -> None:
|
|
@@ -118,7 +153,4 @@ class I10SlitsDrainCurrent(Device):
|
|
|
118
153
|
suffix_bot_blade="YMINUS:I",
|
|
119
154
|
)
|
|
120
155
|
self.s3 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-01:")
|
|
121
|
-
self.s4 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-02:")
|
|
122
|
-
self.s5 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-03:")
|
|
123
|
-
self.s6 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-04:")
|
|
124
156
|
super().__init__(name, connector)
|
dodal/devices/i15/dcm.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
from typing import Generic, TypeVar
|
|
2
|
-
|
|
3
|
-
from ophyd_async.core import StandardReadable
|
|
4
1
|
from ophyd_async.epics.motor import Motor
|
|
5
2
|
|
|
6
3
|
from dodal.devices.common_dcm import (
|
|
4
|
+
DoubleCrystalMonochromatorBase,
|
|
7
5
|
StationaryCrystal,
|
|
8
6
|
)
|
|
9
7
|
|
|
@@ -24,50 +22,13 @@ class ThetaRollYZCrystal(ThetaYCrystal):
|
|
|
24
22
|
super().__init__(prefix)
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
Xtal_2 = TypeVar("Xtal_2", bound=StationaryCrystal)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class BaseDCMforI15(StandardReadable, Generic[Xtal_1, Xtal_2]):
|
|
32
|
-
"""
|
|
33
|
-
Device for double crystal monochromators (DCM), which only allow energy of the beam to be selected.
|
|
34
|
-
|
|
35
|
-
Features common across all DCM's should include virtual motors to set energy/wavelength and contain two crystals,
|
|
36
|
-
each of which can be movable. Some DCM's contain crystals with roll motors, and some contain crystals with roll and pitch motors.
|
|
37
|
-
This device only accounts for combinations of energy plus two crystals.
|
|
38
|
-
|
|
39
|
-
This device is designed to be a drop in replacement for BaseDCM for i15, which doesn't require WAVELENGTH, BRAGG and OFFSET to
|
|
40
|
-
be available. Once the i15 DCM supports all of the PVs required by BaseDCM, the i15 DCM device can switch to inheriting from
|
|
41
|
-
BaseDCM and this class can be removed.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
def __init__(
|
|
45
|
-
self, prefix: str, xtal_1: type[Xtal_1], xtal_2: type[Xtal_2], name: str = ""
|
|
46
|
-
) -> None:
|
|
47
|
-
with self.add_children_as_readables():
|
|
48
|
-
# Virtual motor PV's which set the physical motors so that the DCM produces requested
|
|
49
|
-
# wavelength/energy
|
|
50
|
-
self.energy_in_kev = Motor(prefix + "ENERGY")
|
|
51
|
-
self._make_crystals(prefix, xtal_1, xtal_2)
|
|
52
|
-
|
|
53
|
-
super().__init__(name)
|
|
54
|
-
|
|
55
|
-
# Prefix convention is different depending on whether there are one or two controllable crystals
|
|
56
|
-
def _make_crystals(self, prefix: str, xtal_1: type[Xtal_1], xtal_2: type[Xtal_2]):
|
|
57
|
-
if StationaryCrystal not in [xtal_1, xtal_2]:
|
|
58
|
-
self.xtal_1 = xtal_1(f"{prefix}XTAL1:")
|
|
59
|
-
self.xtal_2 = xtal_2(f"{prefix}XTAL2:")
|
|
60
|
-
else:
|
|
61
|
-
self.xtal_1 = xtal_1(prefix)
|
|
62
|
-
self.xtal_2 = xtal_2(prefix)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class DCM(BaseDCMforI15[ThetaRollYZCrystal, ThetaYCrystal]):
|
|
25
|
+
class DCM(DoubleCrystalMonochromatorBase[ThetaRollYZCrystal, ThetaYCrystal]):
|
|
66
26
|
"""
|
|
67
|
-
A double crystal
|
|
27
|
+
A double crystal monochromator device, used to select the beam energy.
|
|
68
28
|
|
|
69
|
-
Once the i15 DCM supports all of the PVs required by
|
|
70
|
-
|
|
29
|
+
Once the i15 DCM supports all of the PVs required by DoubleCrystalMonochromator or
|
|
30
|
+
DoubleCrystalMonochromatorWithDSpacing this class can be changed to inherit from it,
|
|
31
|
+
see https://jira.diamond.ac.uk/browse/I15-1053 for more info.
|
|
71
32
|
"""
|
|
72
33
|
|
|
73
34
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from dodal.devices.apple2_undulator import (
|
|
2
|
+
Apple2,
|
|
3
|
+
Apple2Controller,
|
|
4
|
+
Apple2Val,
|
|
5
|
+
EnergyMotorConvertor,
|
|
6
|
+
)
|
|
7
|
+
from dodal.log import LOGGER
|
|
8
|
+
|
|
9
|
+
ROW_PHASE_MOTOR_TOLERANCE = 0.004
|
|
10
|
+
MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
|
|
11
|
+
MAXIMUM_GAP_MOTOR_POSITION = 100
|
|
12
|
+
DEFAULT_JAW_PHASE_POLY_PARAMS = [1.0 / 7.5, -120.0 / 7.5]
|
|
13
|
+
ALPHA_OFFSET = 180
|
|
14
|
+
MAXIMUM_MOVE_TIME = 550 # There is no useful movements take longer than this.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class I17Apple2Controller(Apple2Controller[Apple2]):
|
|
18
|
+
"""
|
|
19
|
+
I10Apple2Controller is a extension of Apple2Controller which provide linear
|
|
20
|
+
arbitrary angle control.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
apple2: Apple2,
|
|
26
|
+
energy_to_motor_converter: EnergyMotorConvertor,
|
|
27
|
+
name: str = "",
|
|
28
|
+
) -> None:
|
|
29
|
+
super().__init__(
|
|
30
|
+
apple2=apple2,
|
|
31
|
+
energy_to_motor_converter=energy_to_motor_converter,
|
|
32
|
+
name=name,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def _set_motors_from_energy(self, value: float) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Set the undulator motors for a given energy and polarisation.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
pol = await self._check_and_get_pol_setpoint()
|
|
41
|
+
gap, phase = self.energy_to_motor(energy=value, pol=pol)
|
|
42
|
+
id_set_val = Apple2Val(
|
|
43
|
+
top_outer=f"{phase:.6f}",
|
|
44
|
+
top_inner="0.0",
|
|
45
|
+
btm_inner=f"{phase:.6f}",
|
|
46
|
+
btm_outer="0.0",
|
|
47
|
+
gap=f"{gap:.6f}",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
LOGGER.info(f"Setting polarisation to {pol}, with values: {id_set_val}")
|
|
51
|
+
await self.apple2().set(id_motor_values=id_set_val)
|
|
File without changes
|
|
@@ -2,8 +2,11 @@ from ophyd_async.core import AsyncStatus, StandardReadableFormat
|
|
|
2
2
|
from ophyd_async.epics.core import epics_signal_r
|
|
3
3
|
|
|
4
4
|
from dodal.devices.hutch_shutter import ShutterDemand, ShutterState
|
|
5
|
-
from dodal.devices.i19.blueapi_device import
|
|
6
|
-
|
|
5
|
+
from dodal.devices.i19.access_controlled.blueapi_device import (
|
|
6
|
+
HutchState,
|
|
7
|
+
OpticsBlueAPIDevice,
|
|
8
|
+
)
|
|
9
|
+
from dodal.devices.i19.access_controlled.hutch_access import ACCESS_DEVICE_NAME
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class AccessControlledShutter(OpticsBlueAPIDevice):
|
|
@@ -39,7 +42,7 @@ class AccessControlledShutter(OpticsBlueAPIDevice):
|
|
|
39
42
|
|
|
40
43
|
@AsyncStatus.wrap
|
|
41
44
|
async def set(self, value: ShutterDemand):
|
|
42
|
-
|
|
45
|
+
request_params = {
|
|
43
46
|
"name": "operate_shutter_plan",
|
|
44
47
|
"params": {
|
|
45
48
|
"experiment_hutch": self.hutch_request.value,
|
|
@@ -48,4 +51,4 @@ class AccessControlledShutter(OpticsBlueAPIDevice):
|
|
|
48
51
|
},
|
|
49
52
|
"instrument_session": self.instrument_session,
|
|
50
53
|
}
|
|
51
|
-
await super().set(
|
|
54
|
+
await super().set(request_params)
|