Skip to content

Commit 789df0a

Browse files
Add open signal to FastShutter (#1987)
* Add open signal to FastShutter * Adjust space * Fix lint * Add missing test * Fix tests * Improve code coverage * Add hinted signal
1 parent a039d73 commit 789df0a

File tree

7 files changed

+151
-70
lines changed

7 files changed

+151
-70
lines changed

src/dodal/beamlines/i09.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from dodal.devices.common_dcm import DoubleCrystalMonochromatorWithDSpacing
99
from dodal.devices.electron_analyser.base import DualEnergySource
1010
from dodal.devices.electron_analyser.vgscienta import VGScientaDetector
11-
from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter
11+
from dodal.devices.fast_shutter import DualFastShutter, FastShutter
1212
from dodal.devices.hutch_shutter import EXP_SHUTTER_2_INFIX, HutchShutter
1313
from dodal.devices.motors import XYZAzimuthPolarStage
1414
from dodal.devices.pgm import PlaneGratingMonochromator
@@ -74,17 +74,17 @@ def dual_energy_source(
7474

7575

7676
@devices.factory()
77-
def fsi1() -> GenericFastShutter[InOut]:
78-
return GenericFastShutter[InOut](
77+
def fsi1() -> FastShutter[InOut]:
78+
return FastShutter[InOut](
7979
f"{I_PREFIX.beamline_prefix}-EA-FSHTR-01:CTRL",
8080
open_state=InOut.OUT,
8181
close_state=InOut.IN,
8282
)
8383

8484

8585
@devices.factory()
86-
def fsj1() -> GenericFastShutter[InOut]:
87-
return GenericFastShutter[InOut](
86+
def fsj1() -> FastShutter[InOut]:
87+
return FastShutter[InOut](
8888
f"{J_PREFIX.beamline_prefix}-EA-FSHTR-01:CTRL",
8989
open_state=InOut.OUT,
9090
close_state=InOut.IN,
@@ -93,7 +93,7 @@ def fsj1() -> GenericFastShutter[InOut]:
9393

9494
@devices.factory()
9595
def dual_fast_shutter(
96-
fsi1: GenericFastShutter, fsj1: GenericFastShutter, source_selector: SourceSelector
96+
fsi1: FastShutter, fsj1: FastShutter, source_selector: SourceSelector
9797
) -> DualFastShutter[InOut]:
9898
return DualFastShutter[InOut](fsi1, fsj1, source_selector.selected_source)
9999

src/dodal/devices/electron_analyser/base/base_controller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
TAbstractBaseRegion,
1414
)
1515
from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource
16-
from dodal.devices.fast_shutter import FastShutter
16+
from dodal.devices.fast_shutter import GenericFastShutter
1717
from dodal.devices.selectable_source import SourceSelector
1818

1919

@@ -40,7 +40,7 @@ def __init__(
4040
self,
4141
driver: TAbstractAnalyserDriverIO,
4242
energy_source: AbstractEnergySource,
43-
shutter: FastShutter | None = None,
43+
shutter: GenericFastShutter | None = None,
4444
source_selector: SourceSelector | None = None,
4545
deadtime: float = 0,
4646
image_mode: ADImageMode = ADImageMode.SINGLE,

src/dodal/devices/electron_analyser/specs/specs_detector.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource
99
from dodal.devices.electron_analyser.specs.specs_driver_io import SpecsAnalyserDriverIO
1010
from dodal.devices.electron_analyser.specs.specs_region import SpecsRegion
11-
from dodal.devices.fast_shutter import FastShutter
11+
from dodal.devices.fast_shutter import GenericFastShutter
1212
from dodal.devices.selectable_source import SourceSelector
1313

1414

@@ -25,7 +25,7 @@ def __init__(
2525
lens_mode_type: type[TLensMode],
2626
psu_mode_type: type[TPsuMode],
2727
energy_source: AbstractEnergySource,
28-
shutter: FastShutter | None = None,
28+
shutter: GenericFastShutter | None = None,
2929
source_selector: SourceSelector | None = None,
3030
name: str = "",
3131
):

src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
TPassEnergyEnum,
1414
VGScientaRegion,
1515
)
16-
from dodal.devices.fast_shutter import FastShutter
16+
from dodal.devices.fast_shutter import GenericFastShutter
1717
from dodal.devices.selectable_source import SourceSelector
1818

1919

@@ -31,7 +31,7 @@ def __init__(
3131
psu_mode_type: type[TPsuMode],
3232
pass_energy_type: type[TPassEnergyEnum],
3333
energy_source: AbstractEnergySource,
34-
shutter: FastShutter | None = None,
34+
shutter: GenericFastShutter | None = None,
3535
source_selector: SourceSelector | None = None,
3636
name: str = "",
3737
):

src/dodal/devices/fast_shutter.py

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Generic, Protocol, TypeVar
1+
from abc import abstractmethod
2+
from typing import Generic, TypeVar
23

34
from bluesky.protocols import Movable
45
from ophyd_async.core import (
@@ -18,7 +19,7 @@
1819
EnumTypesT = TypeVar("EnumTypesT", bound=EnumTypes)
1920

2021

21-
class FastShutter(Movable[EnumTypesT], Protocol, Generic[EnumTypesT]):
22+
class GenericFastShutter(StandardReadable, Movable[EnumTypesT], Generic[EnumTypesT]):
2223
"""Enum device specialised for a fast shutter with configured open_state and
2324
close_state so it is generic enough to be used with any device or plan without
2425
knowing the specific enum to use.
@@ -28,24 +29,60 @@ class FastShutter(Movable[EnumTypesT], Protocol, Generic[EnumTypesT]):
2829
await shutter.set(shutter.open_state)
2930
await shutter.set(shutter.close_state)
3031
32+
await shutter.open.set(True)
33+
await shutter.open.set(False)
34+
3135
OR::
3236
3337
run_engine(bps.mv(shutter, shutter.open_state))
3438
run_engine(bps.mv(shutter, shutter.close_state))
39+
40+
run_engine(bps.mv(shutter.open, True))
41+
run_engine(bps.mv(shutter.open, False))
3542
"""
3643

37-
open_state: EnumTypesT
38-
close_state: EnumTypesT
39-
shutter_state: SignalRW[EnumTypesT]
44+
def __init__(
45+
self,
46+
open_state: EnumTypesT,
47+
close_state: EnumTypesT,
48+
name: str = "",
49+
):
50+
self.open_state = open_state
51+
self.close_state = close_state
52+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
53+
self.shutter_state = self._create_shutter_state()
54+
55+
self.open = derived_signal_rw(
56+
self._read_open, self._set_open, shutter_state=self.shutter_state
57+
)
58+
super().__init__(name)
59+
60+
@abstractmethod
61+
def _create_shutter_state(self) -> SignalRW[EnumTypesT]:
62+
"""Method to implement on how to create the shutter state."""
63+
64+
async def _set_open(self, open: bool) -> None:
65+
await self.set(self.open_state if open else self.close_state)
66+
67+
def _read_open(self, shutter_state: EnumTypesT) -> bool:
68+
if shutter_state == self.open_state:
69+
return True
70+
elif shutter_state == self.close_state:
71+
return False
72+
else:
73+
raise ValueError(
74+
f'{self.name} shutter_state is at position "{shutter_state}". Cannot '
75+
"determine if shutter is open or closed as it doesn't match the "
76+
f'configured open_state "{self.open_state}" or close_state '
77+
f'"{self.close_state}".'
78+
)
4079

4180
@AsyncStatus.wrap
4281
async def set(self, state: EnumTypesT):
4382
await self.shutter_state.set(state)
4483

4584

46-
class GenericFastShutter(
47-
StandardReadable, FastShutter[EnumTypesT], Generic[EnumTypesT]
48-
):
85+
class FastShutter(GenericFastShutter[EnumTypesT], Generic[EnumTypesT]):
4986
"""Implementation of fast shutter that connects to an epics pv. This pv is an enum that
5087
controls the open and close state of the shutter.
5188
@@ -65,14 +102,14 @@ def __init__(
65102
close_state: EnumTypesT,
66103
name: str = "",
67104
):
68-
self.open_state = open_state
69-
self.close_state = close_state
70-
with self.add_children_as_readables():
71-
self.shutter_state = epics_signal_rw(type(self.open_state), pv)
72-
super().__init__(name)
105+
self._pv = pv
106+
super().__init__(open_state, close_state, name)
73107

108+
def _create_shutter_state(self):
109+
return epics_signal_rw(type(self.open_state), self._pv)
74110

75-
class DualFastShutter(StandardReadable, FastShutter[EnumTypesT], Generic[EnumTypesT]):
111+
112+
class DualFastShutter(GenericFastShutter[EnumTypesT], Generic[EnumTypesT]):
76113
"""A fast shutter device that handles the positions of two other fast shutters. The
77114
"active" shutter is the one that corrosponds to the selected_shutter signal. For
78115
example, active shutter is shutter1 if selected_source is at SelectedSource.SOURCE1
@@ -97,22 +134,11 @@ def __init__(
97134
):
98135
self._validate_shutter_states(shutter1.open_state, shutter2.open_state)
99136
self._validate_shutter_states(shutter1.close_state, shutter2.close_state)
100-
self.open_state = shutter1.open_state
101-
self.close_state = shutter1.close_state
102137

103138
self._shutter1_ref = Reference(shutter1)
104139
self._shutter2_ref = Reference(shutter2)
105140
self._selected_shutter_ref = Reference(selected_source)
106141

107-
with self.add_children_as_readables():
108-
self.shutter_state = derived_signal_rw(
109-
self._read_shutter_state,
110-
self._set_shutter_state,
111-
selected_shutter=selected_source,
112-
shutter1=shutter1.shutter_state,
113-
shutter2=shutter2.shutter_state,
114-
)
115-
116142
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
117143
self.shutter1_device_name, _ = soft_signal_r_and_setter(
118144
str, initial_value=shutter1.name
@@ -123,7 +149,16 @@ def __init__(
123149

124150
self.add_readables([shutter1, shutter2, selected_source])
125151

126-
super().__init__(name)
152+
super().__init__(shutter1.open_state, shutter1.close_state, name)
153+
154+
def _create_shutter_state(self) -> SignalRW[EnumTypesT]:
155+
return derived_signal_rw(
156+
self._read_shutter_state,
157+
self._set_shutter_state,
158+
selected_shutter=self._selected_shutter_ref(),
159+
shutter1=self._shutter1_ref().shutter_state,
160+
shutter2=self._shutter2_ref().shutter_state,
161+
)
127162

128163
def _validate_shutter_states(self, state1: EnumTypesT, state2: EnumTypesT) -> None:
129164
if state1 is not state2:

tests/devices/electron_analyser/conftest.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
)
1919
from dodal.devices.electron_analyser.specs import SpecsDetector
2020
from dodal.devices.electron_analyser.vgscienta import VGScientaDetector
21-
from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter
21+
from dodal.devices.fast_shutter import DualFastShutter, FastShutter
2222
from dodal.devices.pgm import PlaneGratingMonochromator
2323
from dodal.devices.selectable_source import SourceSelector
2424

@@ -62,9 +62,9 @@ async def dual_energy_source(source_selector: SourceSelector) -> DualEnergySourc
6262

6363

6464
@pytest.fixture
65-
def shutter1() -> GenericFastShutter[InOut]:
65+
def shutter1() -> FastShutter[InOut]:
6666
with init_devices(mock=True):
67-
shutter1 = GenericFastShutter[InOut](
67+
shutter1 = FastShutter[InOut](
6868
pv="TEST:",
6969
open_state=InOut.OUT,
7070
close_state=InOut.IN,
@@ -73,9 +73,9 @@ def shutter1() -> GenericFastShutter[InOut]:
7373

7474

7575
@pytest.fixture
76-
def shutter2() -> GenericFastShutter[InOut]:
76+
def shutter2() -> FastShutter[InOut]:
7777
with init_devices(mock=True):
78-
shutter2 = GenericFastShutter[InOut](
78+
shutter2 = FastShutter[InOut](
7979
pv="TEST:",
8080
open_state=InOut.OUT,
8181
close_state=InOut.IN,
@@ -85,8 +85,8 @@ def shutter2() -> GenericFastShutter[InOut]:
8585

8686
@pytest.fixture
8787
def dual_fast_shutter(
88-
shutter1: GenericFastShutter[InOut],
89-
shutter2: GenericFastShutter[InOut],
88+
shutter1: FastShutter[InOut],
89+
shutter2: FastShutter[InOut],
9090
source_selector: SourceSelector,
9191
) -> DualFastShutter[InOut]:
9292
with init_devices(mock=True):
@@ -101,7 +101,7 @@ def dual_fast_shutter(
101101
@pytest.fixture
102102
async def b07b_specs150(
103103
single_energy_source: EnergySource,
104-
shutter1: GenericFastShutter,
104+
shutter1: FastShutter,
105105
) -> SpecsDetector[b07.LensMode, b07_shared.PsuMode]:
106106
with init_devices(mock=True):
107107
b07b_specs150 = SpecsDetector[b07.LensMode, b07_shared.PsuMode](

0 commit comments

Comments
 (0)