Skip to content

Commit e59b3de

Browse files
committed
Disable config polling
Make config parameters only update once on startup Update put to handle response and trigger the returned parameters to be updated. Add update loop to request specific parameters that need updating
1 parent a5b2021 commit e59b3de

File tree

1 file changed

+77
-23
lines changed

1 file changed

+77
-23
lines changed

src/eiger_fastcs/eiger_controller.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import asyncio
22
from dataclasses import dataclass
3-
from typing import Any
3+
from typing import Any, Coroutine, Type
44

55
from attr import Attribute
66
from fastcs.attributes import AttrR, AttrRW, AttrW
77
from fastcs.connections import HTTPConnection, IPConnectionSettings
88
from fastcs.controller import Controller
99
from fastcs.datatypes import Bool, Float, Int, String
10-
from fastcs.wrappers import command
10+
from fastcs.wrappers import command, scan
1111

1212

1313
@dataclass
@@ -22,19 +22,11 @@ class EigerHandler:
2222
name: str
2323
update_period: float = 0.2
2424

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:
3830
try:
3931
# TODO: Async sleep?
4032
response = await controller.connection.get(self.name)
@@ -43,6 +35,21 @@ async def update(
4335
print(f"update loop failed:{e}")
4436

4537

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+
4653
@dataclass
4754
class LogicHandler:
4855
"""
@@ -54,15 +61,16 @@ class LogicHandler:
5461

5562
name: str
5663

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:
6365
await attr.set(value)
6466

6567

68+
EIGER_HANDLERS: dict[str, Type[EigerHandler]] = {
69+
"status": EigerHandler,
70+
"config": EigerConfigHandler,
71+
}
72+
73+
6674
class EigerController(Controller):
6775
"""
6876
Controller Class for Eiger Detector
@@ -75,11 +83,16 @@ class EigerController(Controller):
7583
Bool(),
7684
handler=LogicHandler("manual trigger"),
7785
)
86+
stale_parameters = AttrR(Bool())
7887

7988
def __init__(self, settings: IPConnectionSettings) -> None:
8089
super().__init__()
8190
self._ip_settings = settings
8291

92+
# Parameter update logic
93+
self._parameter_updates: set[str] = set()
94+
self._parameter_update_lock = asyncio.Lock()
95+
8396
asyncio.run(self.initialise())
8497

8598
async def connect(self) -> None:
@@ -162,14 +175,14 @@ async def initialise(self) -> None:
162175
case "r":
163176
attributes[name] = AttrR(
164177
datatype,
165-
handler=EigerHandler(
178+
handler=EIGER_HANDLERS[mode](
166179
f"{subsystem}/api/1.8.0/{mode}/{parameter_name}"
167180
),
168181
)
169182
case "rw":
170183
attributes[name] = AttrRW(
171184
datatype,
172-
handler=EigerHandler(
185+
handler=EIGER_HANDLERS[mode](
173186
f"{subsystem}/api/1.8.0/{mode}/{parameter_name}"
174187
),
175188
)
@@ -250,3 +263,44 @@ async def start_acquisition(self):
250263
for i in range(self.ntrigger._value):
251264
print(f"Acquisition number: {i+1}")
252265
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

Comments
 (0)