1
1
import asyncio
2
2
from dataclasses import dataclass
3
- from typing import Any
3
+ from typing import Any , Coroutine , Type
4
4
5
5
from attr import Attribute
6
6
from fastcs .attributes import AttrR , AttrRW , AttrW
7
7
from fastcs .connections import HTTPConnection , IPConnectionSettings
8
8
from fastcs .controller import Controller
9
9
from fastcs .datatypes import Bool , Float , Int , String
10
- from fastcs .wrappers import command
10
+ from fastcs .wrappers import command , scan
11
11
12
12
13
13
@dataclass
@@ -22,19 +22,11 @@ class EigerHandler:
22
22
name : str
23
23
update_period : float = 0.2
24
24
25
- async def put (
26
- self ,
27
- controller : "EigerController" ,
28
- attr : AttrW ,
29
- value : Any ,
30
- ) -> None :
31
- await controller .connection .put (self .name , value )
32
-
33
- async def update (
34
- self ,
35
- controller : "EigerController" ,
36
- attr : AttrR ,
37
- ) -> None :
25
+ async def put (self , controller : "EigerController" , _ : AttrW , value : Any ) -> None :
26
+ parameters_to_update = await controller .connection .put (self .name , value )
27
+ await controller .queue_update (parameters_to_update )
28
+
29
+ async def update (self , controller : "EigerController" , attr : AttrR ) -> None :
38
30
try :
39
31
# TODO: Async sleep?
40
32
response = await controller .connection .get (self .name )
@@ -43,6 +35,21 @@ async def update(
43
35
print (f"update loop failed:{ e } " )
44
36
45
37
38
+ class EigerConfigHandler (EigerHandler ):
39
+ """Handler for config parameters that are polled once on startup."""
40
+
41
+ first_poll_complete : bool = False
42
+
43
+ async def update (self , controller : "EigerController" , attr : AttrR ) -> None :
44
+ # Only poll once on startup
45
+ if not self .first_poll_complete :
46
+ await super ().update (controller , attr )
47
+ self .first_poll_complete = True
48
+
49
+ async def config_update (self , controller : "EigerController" , attr : AttrR ) -> None :
50
+ await super ().update (controller , attr )
51
+
52
+
46
53
@dataclass
47
54
class LogicHandler :
48
55
"""
@@ -54,15 +61,16 @@ class LogicHandler:
54
61
55
62
name : str
56
63
57
- async def put (
58
- self ,
59
- controller : "EigerController" ,
60
- attr : AttrW ,
61
- value : Any ,
62
- ) -> None :
64
+ async def put (self , _ : "EigerController" , attr : AttrW , value : Any ) -> None :
63
65
await attr .set (value )
64
66
65
67
68
+ EIGER_HANDLERS : dict [str , Type [EigerHandler ]] = {
69
+ "status" : EigerHandler ,
70
+ "config" : EigerConfigHandler ,
71
+ }
72
+
73
+
66
74
class EigerController (Controller ):
67
75
"""
68
76
Controller Class for Eiger Detector
@@ -75,11 +83,16 @@ class EigerController(Controller):
75
83
Bool (),
76
84
handler = LogicHandler ("manual trigger" ),
77
85
)
86
+ stale_parameters = AttrR (Bool ())
78
87
79
88
def __init__ (self , settings : IPConnectionSettings ) -> None :
80
89
super ().__init__ ()
81
90
self ._ip_settings = settings
82
91
92
+ # Parameter update logic
93
+ self ._parameter_updates : set [str ] = set ()
94
+ self ._parameter_update_lock = asyncio .Lock ()
95
+
83
96
asyncio .run (self .initialise ())
84
97
85
98
async def connect (self ) -> None :
@@ -162,14 +175,14 @@ async def initialise(self) -> None:
162
175
case "r" :
163
176
attributes [name ] = AttrR (
164
177
datatype ,
165
- handler = EigerHandler (
178
+ handler = EIGER_HANDLERS [ mode ] (
166
179
f"{ subsystem } /api/1.8.0/{ mode } /{ parameter_name } "
167
180
),
168
181
)
169
182
case "rw" :
170
183
attributes [name ] = AttrRW (
171
184
datatype ,
172
- handler = EigerHandler (
185
+ handler = EIGER_HANDLERS [ mode ] (
173
186
f"{ subsystem } /api/1.8.0/{ mode } /{ parameter_name } "
174
187
),
175
188
)
@@ -250,3 +263,44 @@ async def start_acquisition(self):
250
263
for i in range (self .ntrigger ._value ):
251
264
print (f"Acquisition number: { i + 1 } " )
252
265
await self .trigger ()
266
+
267
+ async def queue_update (self , parameters : list [str ]):
268
+ """Add the given parameters to the list of parameters to update.
269
+
270
+ Args:
271
+ parameters: Parameters to be updated
272
+
273
+ """
274
+ async with self ._parameter_update_lock :
275
+ for parameter in parameters :
276
+ self ._parameter_updates .add (parameter )
277
+
278
+ await self .stale_parameters .set (True )
279
+
280
+ @scan (0.1 )
281
+ async def update (self ):
282
+ """Periodically update all stale parameters."""
283
+ if not self ._parameter_updates :
284
+ if self .stale_parameters .get ():
285
+ await self .stale_parameters .set (False )
286
+
287
+ return
288
+
289
+ # Take a copy of the current parameters and clear. Parameters may be repopulated
290
+ # during this call and need to be updated again immediately.
291
+ async with self ._parameter_update_lock :
292
+ parameters = self ._parameter_updates .copy ()
293
+ self ._parameter_updates .clear ()
294
+
295
+ # Release lock while fetching parameters - this may be slow
296
+ parameter_updates : list [Coroutine ] = []
297
+ for parameter in parameters :
298
+ match getattr (self , parameter ):
299
+ # TODO: mypy doesn't understand AttrR as a type for some reason:
300
+ # `error: Expected type in class pattern; found "Any" [misc]`
301
+ case AttrR (updater = EigerConfigHandler () as updater ) as attr : # type: ignore [misc]
302
+ parameter_updates .append (updater .config_update (self , attr ))
303
+ case _:
304
+ print (f"Failed to handle update for { parameter } " )
305
+
306
+ await asyncio .gather (* parameter_updates )
0 commit comments