1- from typing import Generic , Protocol , TypeVar
1+ from abc import abstractmethod
2+ from typing import Generic , TypeVar
23
34from bluesky .protocols import Movable
45from ophyd_async .core import (
1819EnumTypesT = 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 :
0 commit comments