Skip to content

Commit a6e46fb

Browse files
Use ADOdin with FastCS-Eiger (#863)
* Add fastcs eiger but use old odin * Use ADOdin with fastcs-eiger * Reorder signal type matching * Get value from initial_values response * Add test for order of signal sets and fix odin nodes --------- Co-authored-by: Dominic Oram <[email protected]>
1 parent c3a48e2 commit a6e46fb

File tree

4 files changed

+99
-32
lines changed

4 files changed

+99
-32
lines changed

src/ophyd_async/epics/eiger/_odin_io.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
from event_model import DataKey # type: ignore
66

77
from ophyd_async.core import (
8+
DEFAULT_TIMEOUT,
89
DetectorWriter,
910
Device,
1011
DeviceVector,
1112
PathProvider,
1213
StrictEnum,
1314
observe_value,
15+
set_and_wait_for_other_value,
1416
set_and_wait_for_value,
17+
wait_for_value,
1518
)
1619
from ophyd_async.epics.core import (
1720
epics_signal_r,
@@ -21,44 +24,53 @@
2124

2225

2326
class Writing(StrictEnum):
24-
ON = "ON"
25-
OFF = "OFF"
27+
CAPTURE = "Capture"
28+
DONE = "Done"
2629

2730

2831
class OdinNode(Device):
2932
def __init__(self, prefix: str, name: str = "") -> None:
30-
self.writing = epics_signal_r(Writing, f"{prefix}HDF:Writing")
31-
self.connected = epics_signal_r(bool, f"{prefix}Connected")
33+
self.writing = epics_signal_r(str, f"{prefix}Writing_RBV")
34+
self.frames_dropped = epics_signal_r(int, f"{prefix}FramesDropped_RBV")
35+
self.frames_time_out = epics_signal_r(int, f"{prefix}FramesTimedOut_RBV")
36+
self.error_status = epics_signal_r(str, f"{prefix}FPErrorState_RBV")
37+
self.fp_initialised = epics_signal_r(int, f"{prefix}FPProcessConnected_RBV")
38+
self.fr_initialised = epics_signal_r(int, f"{prefix}FRProcessConnected_RBV")
39+
self.num_captured = epics_signal_r(int, f"{prefix}NumCaptured_RBV")
40+
self.clear_errors = epics_signal_rw(int, f"{prefix}FPClearErrors")
41+
self.error_message = epics_signal_rw(str, f"{prefix}FPErrorMessage_RBV")
3242

3343
super().__init__(name)
3444

3545

3646
class Odin(Device):
3747
def __init__(self, prefix: str, name: str = "") -> None:
38-
self.nodes = DeviceVector({i: OdinNode(f"{prefix}FP{i}:") for i in range(4)})
39-
40-
self.capture = epics_signal_rw(
41-
Writing, f"{prefix}Writing", f"{prefix}ConfigHdfWrite"
48+
self.nodes = DeviceVector(
49+
{i: OdinNode(f"{prefix[:-1]}{i + 1}:") for i in range(4)}
4250
)
43-
self.num_captured = epics_signal_r(int, f"{prefix}FramesWritten")
44-
self.num_to_capture = epics_signal_rw_rbv(int, f"{prefix}ConfigHdfFrames")
4551

46-
self.start_timeout = epics_signal_rw_rbv(int, f"{prefix}TimeoutTimerPeriod")
52+
self.capture = epics_signal_rw(Writing, f"{prefix}Capture")
53+
self.capture_rbv = epics_signal_r(str, prefix + "Capture_RBV")
54+
self.num_captured = epics_signal_r(int, f"{prefix}NumCapture_RBV")
55+
self.num_to_capture = epics_signal_rw_rbv(int, f"{prefix}NumCapture")
4756

48-
self.image_height = epics_signal_rw_rbv(int, f"{prefix}DatasetDataDims0")
49-
self.image_width = epics_signal_rw_rbv(int, f"{prefix}DatasetDataDims1")
57+
self.start_timeout = epics_signal_rw(str, f"{prefix}StartTimeout")
58+
self.timeout_active_rbv = epics_signal_r(str, f"{prefix}TimeoutActive_RBV")
5059

51-
self.num_row_chunks = epics_signal_rw_rbv(int, f"{prefix}DatasetDataChunks1")
52-
self.num_col_chunks = epics_signal_rw_rbv(int, f"{prefix}DatasetDataChunks2")
60+
self.image_height = epics_signal_rw_rbv(int, f"{prefix}ImageHeight")
61+
self.image_width = epics_signal_rw_rbv(int, f"{prefix}ImageWidth")
5362

54-
self.file_path = epics_signal_rw_rbv(str, f"{prefix}ConfigHdfFilePath")
55-
self.file_name = epics_signal_rw_rbv(str, f"{prefix}ConfigHdfFilePrefix")
63+
self.num_row_chunks = epics_signal_rw_rbv(int, f"{prefix}NumRowChunks")
64+
self.num_col_chunks = epics_signal_rw_rbv(int, f"{prefix}NumColChunks")
5665

57-
self.acquisition_id = epics_signal_rw_rbv(
58-
str, f"{prefix}ConfigHdfAcquisitionId"
59-
)
66+
self.file_path = epics_signal_rw_rbv(str, f"{prefix}FilePath")
67+
self.file_name = epics_signal_rw_rbv(str, f"{prefix}FileName")
68+
69+
self.num_frames_chunks = epics_signal_rw(int, prefix + "NumFramesChunks")
70+
self.meta_active = epics_signal_r(str, prefix + "META:AcquisitionActive_RBV")
71+
self.meta_writing = epics_signal_r(str, prefix + "META:Writing_RBV")
6072

61-
self.data_type = epics_signal_rw_rbv(str, f"{prefix}DatasetDataDatatype")
73+
self.data_type = epics_signal_rw_rbv(str, f"{prefix}DataType")
6274

6375
super().__init__(name)
6476

@@ -81,12 +93,23 @@ async def open(self, name: str, exposures_per_event: int = 1) -> dict[str, DataK
8193
self._drv.file_path.set(str(info.directory_path)),
8294
self._drv.file_name.set(info.filename),
8395
self._drv.data_type.set(
84-
"uint16"
96+
"UInt16"
8597
), # TODO: Get from eiger https://github.com/bluesky/ophyd-async/issues/529
8698
self._drv.num_to_capture.set(0),
8799
)
88100

89-
await self._drv.capture.set(Writing.ON)
101+
await wait_for_value(self._drv.meta_active, "Active", timeout=DEFAULT_TIMEOUT)
102+
103+
await set_and_wait_for_other_value(
104+
self._drv.capture,
105+
Writing.CAPTURE,
106+
self._drv.capture_rbv,
107+
"Capturing",
108+
set_timeout=None,
109+
wait_for_set_completion=False,
110+
) # TODO: Investigate why we do not get a put callback when setting capture pv https://github.com/bluesky/ophyd-async/issues/866
111+
112+
await wait_for_value(self._drv.meta_writing, "Writing", timeout=DEFAULT_TIMEOUT)
90113

91114
return await self._describe()
92115

@@ -122,4 +145,4 @@ def collect_stream_docs(
122145
raise NotImplementedError()
123146

124147
async def close(self) -> None:
125-
await set_and_wait_for_value(self._drv.capture, Writing.OFF)
148+
await set_and_wait_for_value(self._drv.capture, Writing.DONE)

src/ophyd_async/fastcs/eiger/_eiger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ def __init__(
2222
prefix: str,
2323
path_provider: PathProvider,
2424
drv_suffix="-EA-EIGER-01:",
25-
hdf_suffix="-EA-ODIN-01:",
25+
hdf_suffix="-EA-EIGER-01:OD:",
2626
name="",
2727
):
2828
self.drv = EigerDriverIO(prefix + drv_suffix)
29-
self.odin = Odin(prefix + hdf_suffix + "FP:")
29+
self.odin = Odin(prefix + hdf_suffix)
3030

3131
super().__init__(
3232
EigerController(self.drv),

tests/epics/eiger/test_odin_io.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from pathlib import Path
2-
from unittest.mock import ANY, MagicMock
2+
from unittest.mock import ANY, AsyncMock, MagicMock, call, patch
33

44
import pytest
55

6-
from ophyd_async.core import init_devices
6+
from ophyd_async.core import DEFAULT_TIMEOUT, init_devices
77
from ophyd_async.epics.eiger import Odin, OdinWriter, Writing
88
from ophyd_async.testing import get_mock_put, set_mock_value
99

@@ -17,6 +17,11 @@ def odin_driver_and_writer(RE) -> OdinDriverAndWriter:
1717
with init_devices(mock=True):
1818
driver = Odin("")
1919
writer = OdinWriter(MagicMock(), driver)
20+
21+
# Set meta and capturing pvs high
22+
set_mock_value(driver.meta_active, "Active")
23+
set_mock_value(driver.capture_rbv, "Capturing")
24+
set_mock_value(driver.meta_writing, "Writing")
2025
return driver, writer
2126

2227

@@ -41,10 +46,10 @@ async def test_when_open_called_then_all_expected_signals_set(
4146
driver, writer = odin_driver_and_writer
4247
await writer.open(ODIN_DETECTOR_NAME)
4348

44-
get_mock_put(driver.data_type).assert_called_once_with("uint16", wait=ANY)
49+
get_mock_put(driver.data_type).assert_called_once_with("UInt16", wait=ANY)
4550
get_mock_put(driver.num_to_capture).assert_called_once_with(0, wait=ANY)
4651

47-
get_mock_put(driver.capture).assert_called_once_with(Writing.ON, wait=ANY)
52+
get_mock_put(driver.capture).assert_called_once_with(Writing.CAPTURE, wait=ANY)
4853

4954

5055
async def test_given_data_shape_set_when_open_called_then_describe_has_correct_shape(
@@ -62,4 +67,40 @@ async def test_when_closed_then_data_capture_turned_off(
6267
):
6368
driver, writer = odin_driver_and_writer
6469
await writer.close()
65-
get_mock_put(driver.capture).assert_called_once_with(Writing.OFF, wait=ANY)
70+
get_mock_put(driver.capture).assert_called_once_with(Writing.DONE, wait=ANY)
71+
72+
73+
@pytest.mark.asyncio
74+
@patch("ophyd_async.epics.eiger._odin_io.wait_for_value")
75+
@patch("ophyd_async.epics.eiger._odin_io.set_and_wait_for_other_value")
76+
async def test_wait_for_active_before_capture_then_wait_for_writing(
77+
mock_set_and_wait_for_other_value,
78+
mock_wait_for_value,
79+
odin_driver_and_writer,
80+
):
81+
driver, writer = odin_driver_and_writer
82+
83+
mock_manager = AsyncMock()
84+
mock_manager.attach_mock(mock_wait_for_value, "mock_wait_for_value")
85+
mock_manager.attach_mock(
86+
mock_set_and_wait_for_other_value, "mock_set_and_wait_for_other_value"
87+
)
88+
89+
await writer.open(ODIN_DETECTOR_NAME)
90+
91+
expected_calls = [
92+
call.mock_wait_for_value(driver.meta_active, "Active", timeout=DEFAULT_TIMEOUT),
93+
call.mock_set_and_wait_for_other_value(
94+
driver.capture,
95+
Writing.CAPTURE,
96+
driver.capture_rbv,
97+
"Capturing",
98+
set_timeout=None,
99+
wait_for_set_completion=False,
100+
),
101+
call.mock_wait_for_value(
102+
driver.meta_writing, "Writing", timeout=DEFAULT_TIMEOUT
103+
),
104+
]
105+
106+
assert mock_manager.mock_calls == expected_calls

tests/fastcs/eiger/test_eiger_detector.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
from ophyd_async.core import DetectorTrigger, init_devices
66
from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo
7-
from ophyd_async.testing import get_mock_put
7+
from ophyd_async.testing import get_mock_put, set_mock_value
88

99

1010
@pytest.fixture
1111
def detector(RE):
1212
with init_devices(mock=True):
1313
detector = EigerDetector("BL03I", MagicMock())
14+
set_mock_value(detector.odin.meta_active, "Active")
15+
set_mock_value(detector.odin.capture_rbv, "Capturing")
16+
set_mock_value(detector.odin.meta_writing, "Writing")
1417
return detector
1518

1619

0 commit comments

Comments
 (0)