diff --git a/.codecov.yml b/.codecov.yml index 214fccccbe..a5d2c1645f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -8,7 +8,7 @@ coverage: threshold: 1% patch: default: - target: auto + target: 90 threshold: 1% github_checks: annotations: false diff --git a/src/ophyd_async/core/detector.py b/src/ophyd_async/core/detector.py index ee168f8830..53f10568df 100644 --- a/src/ophyd_async/core/detector.py +++ b/src/ophyd_async/core/detector.py @@ -332,7 +332,7 @@ async def collect_asset_docs( # Collect stream datum documents for all indices written. # The index is optional, and provided for fly scans, however this needs to be # retrieved for step scans. - if not index: + if index is None: index = await self.writer.get_indices_written() async for doc in self.writer.collect_stream_docs(index): yield doc diff --git a/src/ophyd_async/core/flyer.py b/src/ophyd_async/core/flyer.py index d2df2d5777..f43f1a880f 100644 --- a/src/ophyd_async/core/flyer.py +++ b/src/ophyd_async/core/flyer.py @@ -39,7 +39,7 @@ class HardwareTriggeredFlyable( def __init__( self, trigger_logic: TriggerLogic[T], - configuration_signals: Sequence[SignalR], + configuration_signals: Sequence[SignalR] = (), name: str = "", ): self._trigger_logic = trigger_logic diff --git a/src/ophyd_async/epics/areadetector/writers/hdf_writer.py b/src/ophyd_async/epics/areadetector/writers/hdf_writer.py index b358ed347c..9c82877b7f 100644 --- a/src/ophyd_async/epics/areadetector/writers/hdf_writer.py +++ b/src/ophyd_async/epics/areadetector/writers/hdf_writer.py @@ -43,12 +43,13 @@ def __init__( async def open(self, multiplier: int = 1) -> Dict[str, DataKey]: self._file = None info = self._directory_provider() + file_path = str(info.root / info.resource_dir) await asyncio.gather( self.hdf.num_extra_dims.set(0), self.hdf.lazy_open.set(True), self.hdf.swmr_mode.set(True), # See https://github.com/bluesky/ophyd-async/issues/122 - self.hdf.file_path.set(str(info.root / info.resource_dir)), + self.hdf.file_path.set(file_path), self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"), self.hdf.file_template.set("%s/%s.h5"), self.hdf.file_write_mode.set(FileWriteMode.stream), @@ -59,7 +60,7 @@ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]: assert ( await self.hdf.file_path_exists.get_value() - ), f"File path {self.hdf.file_path.get_value()} for hdf plugin does not exist" + ), f"File path {file_path} for hdf plugin does not exist" # Overwrite num_capture to go forever await self.hdf.num_capture.set(0) diff --git a/src/ophyd_async/panda/writers/_hdf_writer.py b/src/ophyd_async/panda/writers/_hdf_writer.py index 6b6bc6a814..60f4a67bf8 100644 --- a/src/ophyd_async/panda/writers/_hdf_writer.py +++ b/src/ophyd_async/panda/writers/_hdf_writer.py @@ -79,10 +79,10 @@ async def get_signals_marked_for_capture( capture_signals.keys(), capture_signals.values(), signal_values ): signal_path = signal_path.replace("_capture", "") - if (signal_value.value in iter(Capture)) and (signal_value.value != Capture.No): + if (signal_value in iter(Capture)) and (signal_value != Capture.No): signals_to_capture[signal_path] = CaptureSignalWrapper( signal_object, - signal_value.value, + signal_value, ) return signals_to_capture @@ -126,7 +126,7 @@ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]: str(info.root / info.resource_dir) ), self.panda_device.data.hdf_file_name.set( - f"{info.prefix}{self.panda_device.name}{info.suffix}", + f"{info.prefix}{self.panda_device.name}{info.suffix}.h5", ), self.panda_device.data.num_capture.set(0), ) @@ -149,7 +149,7 @@ async def open(self, multiplier: int = 1) -> Dict[str, DataKey]: else split_path[-2] ) - for suffix in str(capture_signal.capture_type).split(" "): + for suffix in capture_signal.capture_type.split(" "): self._datasets.append( _HDFDataset( name, diff --git a/src/ophyd_async/planstubs/__init__.py b/src/ophyd_async/plan_stubs/__init__.py similarity index 62% rename from src/ophyd_async/planstubs/__init__.py rename to src/ophyd_async/plan_stubs/__init__.py index d97ec112e2..c2146d9c2a 100644 --- a/src/ophyd_async/planstubs/__init__.py +++ b/src/ophyd_async/plan_stubs/__init__.py @@ -1,9 +1,11 @@ from .ensure_connected import ensure_connected -from .prepare_trigger_and_dets import ( +from .fly import ( prepare_static_seq_table_flyer_and_detectors_with_same_trigger, + time_resolved_fly_and_collect_with_static_seq_table, ) __all__ = [ + "time_resolved_fly_and_collect_with_static_seq_table", "prepare_static_seq_table_flyer_and_detectors_with_same_trigger", "ensure_connected", ] diff --git a/src/ophyd_async/planstubs/ensure_connected.py b/src/ophyd_async/plan_stubs/ensure_connected.py similarity index 100% rename from src/ophyd_async/planstubs/ensure_connected.py rename to src/ophyd_async/plan_stubs/ensure_connected.py diff --git a/src/ophyd_async/plan_stubs/fly.py b/src/ophyd_async/plan_stubs/fly.py new file mode 100644 index 0000000000..a4ad880aed --- /dev/null +++ b/src/ophyd_async/plan_stubs/fly.py @@ -0,0 +1,130 @@ +from typing import List + +import bluesky.plan_stubs as bps +from bluesky.utils import short_uid + +from ophyd_async.core.detector import DetectorTrigger, StandardDetector, TriggerInfo +from ophyd_async.core.flyer import HardwareTriggeredFlyable +from ophyd_async.core.utils import in_micros +from ophyd_async.panda._table import SeqTable, SeqTableRow, seq_table_from_rows +from ophyd_async.panda._trigger import SeqTableInfo + + +def time_resolved_fly_and_collect_with_static_seq_table( + stream_name: str, + detectors: List[StandardDetector], + flyer: HardwareTriggeredFlyable[SeqTableInfo], + number_of_frames: int, + exposure: int, + shutter_time: float, + repeats: int = 1, + period: float = 0.0, +): + """Run a scan wth a flyer and multiple detectors. + + The standard basic flow for a flyscan: + + - Set up the flyer with a static sequence table and detectors with a trigger + - Declare the stream and kickoff the scan + - Collect while completing + + This needs to be used in a plan that instantates detectors and a flyer, + stages/unstages the devices, and opens and closes the run. + + """ + # Set up scan and prepare trigger + deadtime = max(det.controller.get_deadtime(exposure) for det in detectors) + yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger( + flyer, + detectors, + number_of_frames=number_of_frames, + exposure=exposure, + deadtime=deadtime, + shutter_time=shutter_time, + repeats=repeats, + period=period, + ) + yield from bps.declare_stream(*detectors, name=stream_name, collect=True) + yield from bps.kickoff(flyer, wait=True) + for detector in detectors: + yield from bps.kickoff(detector) + + # collect_while_completing + group = short_uid(label="complete") + + yield from bps.complete(flyer, wait=False, group=group) + for detector in detectors: + yield from bps.complete(detector, wait=False, group=group) + + done = False + while not done: + try: + yield from bps.wait(group=group, timeout=0.5) + except TimeoutError: + pass + else: + done = True + yield from bps.collect( + *detectors, + return_payload=False, + name=stream_name, + ) + yield from bps.wait(group=group) + + +def prepare_static_seq_table_flyer_and_detectors_with_same_trigger( + flyer: HardwareTriggeredFlyable[SeqTableInfo], + detectors: List[StandardDetector], + number_of_frames: int, + exposure: float, + deadtime: float, + shutter_time: float, + repeats: int = 1, + period: float = 0.0, +): + """Prepare a hardware triggered flyable and one or more detectors. + + Prepare a hardware triggered flyable and one or more detectors with the + same trigger. This method constructs TriggerInfo and a static sequence + table from required parameters. The table is required to prepare the flyer, + and the TriggerInfo is required to prepare the detector(s). + + This prepares all supplied detectors with the same trigger. + + """ + trigger_info = TriggerInfo( + num=number_of_frames * repeats, + trigger=DetectorTrigger.constant_gate, + deadtime=deadtime, + livetime=exposure, + ) + + trigger_time = number_of_frames * (exposure + deadtime) + pre_delay = max(period - 2 * shutter_time - trigger_time, 0) + + table: SeqTable = seq_table_from_rows( + # Wait for pre-delay then open shutter + SeqTableRow( + time1=in_micros(pre_delay), + time2=in_micros(shutter_time), + outa2=True, + ), + # Keeping shutter open, do N triggers + SeqTableRow( + repeats=number_of_frames, + time1=in_micros(exposure), + outa1=True, + outb1=True, + time2=in_micros(deadtime), + outa2=True, + ), + # Add the shutter close + SeqTableRow(time2=in_micros(shutter_time)), + ) + + table_info = SeqTableInfo(table, repeats) + + for det in detectors: + yield from bps.prepare(det, trigger_info, wait=False, group="prep") + yield from bps.prepare(flyer, table_info, wait=False, group="prep") + yield from bps.wait(group="prep") diff --git a/src/ophyd_async/planstubs/prepare_trigger_and_dets.py b/src/ophyd_async/planstubs/prepare_trigger_and_dets.py deleted file mode 100644 index 541c61faac..0000000000 --- a/src/ophyd_async/planstubs/prepare_trigger_and_dets.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import List - -import bluesky.plan_stubs as bps - -from ophyd_async.core.detector import DetectorTrigger, StandardDetector, TriggerInfo -from ophyd_async.core.flyer import HardwareTriggeredFlyable -from ophyd_async.core.utils import in_micros -from ophyd_async.panda._table import SeqTable, SeqTableRow, seq_table_from_rows -from ophyd_async.panda._trigger import SeqTableInfo - - -def prepare_static_seq_table_flyer_and_detectors_with_same_trigger( - flyer: HardwareTriggeredFlyable[SeqTableInfo], - detectors: List[StandardDetector], - num: int, - width: float, - deadtime: float, - shutter_time: float, - repeats: int = 1, - period: float = 0.0, -): - trigger_info = TriggerInfo( - num=num * repeats, - trigger=DetectorTrigger.constant_gate, - deadtime=deadtime, - livetime=width, - ) - - trigger_time = num * (width + deadtime) - pre_delay = max(period - 2 * shutter_time - trigger_time, 0) - - table: SeqTable = seq_table_from_rows( - # Wait for pre-delay then open shutter - SeqTableRow( - time1=in_micros(pre_delay), - time2=in_micros(shutter_time), - outa2=True, - ), - # Keeping shutter open, do N triggers - SeqTableRow( - repeats=num, - time1=in_micros(width), - outa1=True, - outb1=True, - time2=in_micros(deadtime), - outa2=True, - ), - # Add the shutter close - SeqTableRow(time2=in_micros(shutter_time)), - ) - - table_info = SeqTableInfo(table, repeats) - - for det in detectors: - yield from bps.prepare(det, trigger_info, wait=False, group="prep") - yield from bps.prepare(flyer, table_info, wait=False, group="prep") - yield from bps.wait(group="prep") diff --git a/tests/core/test_device.py b/tests/core/test_device.py index 4b09aaf764..964c949f4f 100644 --- a/tests/core/test_device.py +++ b/tests/core/test_device.py @@ -15,9 +15,7 @@ ) from ophyd_async.core.soft_signal_backend import SoftSignalBackend from ophyd_async.epics.motion import motor -from ophyd_async.planstubs.ensure_connected import ( - ensure_connected, -) +from ophyd_async.plan_stubs.ensure_connected import ensure_connected from ophyd_async.sim.demo.sim_motor import SimMotor diff --git a/tests/core/test_flyer.py b/tests/core/test_flyer.py index d5edfb6b86..655f3b3dbc 100644 --- a/tests/core/test_flyer.py +++ b/tests/core/test_flyer.py @@ -109,7 +109,7 @@ async def close(self) -> None: @pytest.fixture -async def detector_list(RE: RunEngine) -> tuple[StandardDetector, StandardDetector]: +async def detectors(RE: RunEngine) -> tuple[StandardDetector, StandardDetector]: writers = [DummyWriter("testa", (1, 1)), DummyWriter("testb", (1, 1))] await writers[0].dummy_signal.connect(mock=True) await writers[1].dummy_signal.connect(mock=True) @@ -137,7 +137,7 @@ async def dummy_arm_2(self=None, trigger=None, num=0, exposure=None): async def test_hardware_triggered_flyable( - RE: RunEngine, detector_list: tuple[StandardDetector] + RE: RunEngine, detectors: tuple[StandardDetector] ): names = [] docs = [] @@ -155,7 +155,7 @@ def append_and_print(name, doc): ) def flying_plan(): - yield from bps.stage_all(*detector_list, flyer) + yield from bps.stage_all(*detectors, flyer) assert flyer._trigger_logic.state == TriggerState.stopping # move the flyer to the correct place, before fly scanning. @@ -163,7 +163,7 @@ def flying_plan(): yield from bps.prepare(flyer, 1, wait=True) # prepare detectors second. - for detector in detector_list: + for detector in detectors: yield from bps.prepare( detector, trigger_info, @@ -171,23 +171,23 @@ def flying_plan(): ) assert flyer._trigger_logic.state == TriggerState.preparing - for detector in detector_list: + for detector in detectors: detector.controller.disarm.assert_called_once # type: ignore yield from bps.open_run() - yield from bps.declare_stream(*detector_list, name="main_stream", collect=True) + yield from bps.declare_stream(*detectors, name="main_stream", collect=True) yield from bps.kickoff(flyer) - for detector in detector_list: + for detector in detectors: yield from bps.kickoff(detector) yield from bps.complete(flyer, wait=False, group="complete") - for detector in detector_list: + for detector in detectors: yield from bps.complete(detector, wait=False, group="complete") assert flyer._trigger_logic.state == TriggerState.null # Manually incremenet the index as if a frame was taken - for detector in detector_list: + for detector in detectors: detector.writer.index += 1 done = False @@ -199,15 +199,15 @@ def flying_plan(): else: done = True yield from bps.collect( - *detector_list, + *detectors, return_payload=False, name="main_stream", ) yield from bps.wait(group="complete") yield from bps.close_run() - yield from bps.unstage_all(flyer, *detector_list) - for detector in detector_list: + yield from bps.unstage_all(flyer, *detectors) + for detector in detectors: assert detector.controller.disarm.called # type: ignore assert trigger_logic.state == TriggerState.stopping diff --git a/tests/epics/demo/test_demo_ad_sim_detector.py b/tests/epics/demo/test_demo_ad_sim_detector.py index ac143fd024..2398a04df9 100644 --- a/tests/epics/demo/test_demo_ad_sim_detector.py +++ b/tests/epics/demo/test_demo_ad_sim_detector.py @@ -1,10 +1,12 @@ """Integration tests for a StandardDetector using a HDFWriter and ADSimController.""" import time +from collections import defaultdict from pathlib import Path from typing import List, cast import bluesky.plan_stubs as bps +import bluesky.preprocessors as bpp import pytest from bluesky import RunEngine from bluesky.utils import new_uid @@ -17,6 +19,7 @@ callback_on_mock_put, set_mock_value, ) +from ophyd_async.core.signal import assert_emitted from ophyd_async.epics.areadetector.controllers import ADSimController from ophyd_async.epics.areadetector.drivers import ADBase from ophyd_async.epics.areadetector.utils import FileWriteMode, ImageMode @@ -102,11 +105,36 @@ async def two_detectors(tmp_path: Path): set_mock_value(controller.driver.image_mode, ImageMode.continuous) set_mock_value(writer.hdf.num_capture, 1000) set_mock_value(writer.hdf.num_captured, 0) + set_mock_value(writer.hdf.file_path_exists, True) set_mock_value(controller.driver.array_size_x, 1024 + i) set_mock_value(controller.driver.array_size_y, 768 + i) yield deta, detb +async def test_two_detectors_fly_different_rate( + two_detectors: List[DemoADSimDetector], RE: RunEngine +): + docs = defaultdict(list) + + @bpp.stage_decorator(two_detectors) + @bpp.run_decorator() + def fly_plan(): + yield from bps.declare_stream(*two_detectors, name="primary") + # Make one produce some frames and collect + set_mock_value(two_detectors[0].hdf.num_captured, 15) + yield from bps.collect(*two_detectors) + # It shouldn't make anything as the other one is lagging + assert "stream_datum" not in docs + # Make the other one produce some frames + set_mock_value(two_detectors[1].hdf.num_captured, 15) + yield from bps.collect(*two_detectors) + + RE(fly_plan(), lambda name, doc: docs[name].append(doc)) + assert_emitted( + docs, start=1, descriptor=1, stream_resource=2, stream_datum=2, stop=1 + ) + + async def test_two_detectors_step( two_detectors: List[StandardDetector], RE: RunEngine, diff --git a/tests/panda/test_hdf_panda.py b/tests/panda/test_hdf_panda.py index 626e272bd3..019380dcbe 100644 --- a/tests/panda/test_hdf_panda.py +++ b/tests/panda/test_hdf_panda.py @@ -13,7 +13,7 @@ from ophyd_async.panda import HDFPanda from ophyd_async.panda._trigger import StaticSeqTableTriggerLogic from ophyd_async.panda.writers._hdf_writer import Capture -from ophyd_async.planstubs.prepare_trigger_and_dets import ( +from ophyd_async.plan_stubs import ( prepare_static_seq_table_flyer_and_detectors_with_same_trigger, ) @@ -80,12 +80,11 @@ def flying_plan(): yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger( flyer, [mock_hdf_panda], - num=1, - width=exposure, + number_of_frames=1, + exposure=exposure, deadtime=mock_hdf_panda.controller.get_deadtime(1), shutter_time=shutter_time, ) - # mock_hdf_panda.controller.disarm.assert_called_once # type: ignore yield from bps.open_run() yield from bps.declare_stream(mock_hdf_panda, name="main_stream", collect=True) @@ -120,7 +119,6 @@ def flying_plan(): yield from bps.unstage_all(flyer, mock_hdf_panda) yield from bps.wait_for([lambda: mock_hdf_panda.controller.disarm()]) - # assert mock_hdf_panda.controller.disarm.called # type: ignore # fly scan RE(flying_plan()) diff --git a/tests/panda/test_writer.py b/tests/panda/test_writer.py index 0492cc6f0e..e056648ef4 100644 --- a/tests/panda/test_writer.py +++ b/tests/panda/test_writer.py @@ -68,7 +68,7 @@ async def mock_panda(panda_t): @pytest.fixture async def mock_writer(tmp_path, mock_panda) -> PandaHDFWriter: dir_prov = StaticDirectoryProvider( - directory_path=str(tmp_path), filename_prefix="", filename_suffix="/data.h5" + directory_path=str(tmp_path), filename_prefix="", filename_suffix="/data" ) async with DeviceCollector(mock=True): writer = PandaHDFWriter( @@ -205,4 +205,4 @@ async def get_numeric_signal(_): "ophyd_async.panda.writers._hdf_writer.get_signals_marked_for_capture", get_numeric_signal, ): - assert "test-panda-block-1-Capture.Value" in await mock_writer.open() + assert "test-panda-block-1-Value" in await mock_writer.open() diff --git a/tests/test_flyer_with_panda.py b/tests/plan_stubs/test_fly.py similarity index 65% rename from tests/test_flyer_with_panda.py rename to tests/plan_stubs/test_fly.py index ef386471c9..e7fd789291 100644 --- a/tests/test_flyer_with_panda.py +++ b/tests/plan_stubs/test_fly.py @@ -16,15 +16,21 @@ observe_value, set_mock_value, ) +from ophyd_async.core.async_status import AsyncStatus, WatchableAsyncStatus from ophyd_async.core.detector import StandardDetector from ophyd_async.core.device import DeviceCollector +from ophyd_async.core.flyer import TriggerLogic +from ophyd_async.core.signal import SignalR +from ophyd_async.core.utils import WatcherUpdate from ophyd_async.epics.pvi.pvi import fill_pvi_entries from ophyd_async.epics.signal.signal import epics_signal_rw from ophyd_async.panda import CommonPandaBlocks from ophyd_async.panda._trigger import StaticSeqTableTriggerLogic -from ophyd_async.planstubs import ( +from ophyd_async.plan_stubs import ( prepare_static_seq_table_flyer_and_detectors_with_same_trigger, + time_resolved_fly_and_collect_with_static_seq_table, ) +from ophyd_async.protocols import AsyncReadable class DummyWriter(DetectorWriter): @@ -86,9 +92,44 @@ async def collect_stream_docs( async def close(self) -> None: self._file = None + def increment_index(self) -> None: + self.index += 1 + + +class MockDetector(StandardDetector): + def __init__( + self, + controller: DetectorControl, + writer: DetectorWriter, + config_sigs: Sequence[AsyncReadable] = [], + name: str = "", + writer_timeout: float = 1, + ) -> None: + super().__init__(controller, writer, config_sigs, name, writer_timeout) + + @WatchableAsyncStatus.wrap + async def complete(self): + assert self._arm_status, "Prepare not run" + assert self._trigger_info + self.writer.increment_index() + async for index in self.writer.observe_indices_written( + self._frame_writing_timeout + ): + yield WatcherUpdate( + name=self.name, + current=index, + initial=self._initial_frame, + target=self._trigger_info.num, + unit="", + precision=0, + time_elapsed=time.monotonic() - self._fly_start, + ) + if index >= self._trigger_info.num: + break + @pytest.fixture -async def detector_list(RE: RunEngine) -> tuple[StandardDetector, StandardDetector]: +async def detectors(RE: RunEngine) -> tuple[MockDetector, MockDetector]: writers = [DummyWriter("testa", (1, 1)), DummyWriter("testb", (1, 1))] await writers[0].dummy_signal.connect(mock=True) await writers[1].dummy_signal.connect(mock=True) @@ -99,13 +140,13 @@ async def dummy_arm_1(self=None, trigger=None, num=0, exposure=None): async def dummy_arm_2(self=None, trigger=None, num=0, exposure=None): return writers[1].dummy_signal.set(1) - detector_1: StandardDetector = StandardDetector( + detector_1 = MockDetector( Mock(spec=DetectorControl, get_deadtime=lambda num: num, arm=dummy_arm_1), writers[0], name="detector_1", writer_timeout=3, ) - detector_2: StandardDetector = StandardDetector( + detector_2 = MockDetector( Mock(spec=DetectorControl, get_deadtime=lambda num: num, arm=dummy_arm_2), writers[1], name="detector_2", @@ -134,9 +175,37 @@ async def connect(self, mock: bool = False, timeout: float = DEFAULT_TIMEOUT): yield mock_panda +@pytest.fixture +async def flyer(panda): + class MockFlyer(HardwareTriggeredFlyable): + def __init__( + self, + trigger_logic: TriggerLogic, + configuration_signals: Sequence[SignalR] = ..., + name: str = "", + ): + super().__init__(trigger_logic, configuration_signals, name) + + @AsyncStatus.wrap + async def kickoff(self) -> None: + set_mock_value(self.trigger_logic.seq.active, 1) + await super().kickoff() + + @AsyncStatus.wrap + async def complete(self) -> None: + set_mock_value(self.trigger_logic.seq.active, 0) + await self._trigger_logic.complete() + + # Make flyer + trigger_logic = StaticSeqTableTriggerLogic(panda.seq[1]) + flyer = MockFlyer(trigger_logic, [], name="flyer") + + return flyer + + async def test_hardware_triggered_flyable_with_static_seq_table_logic( RE: RunEngine, - detector_list: tuple[StandardDetector], + detectors: tuple[StandardDetector], panda, ): """Run a dummy scan using a flyer with a prepare plan stub. @@ -150,6 +219,7 @@ async def test_hardware_triggered_flyable_with_static_seq_table_logic( """ names = [] docs = [] + detector_list = list(detectors) def append_and_print(name, doc): names.append(name) @@ -157,9 +227,10 @@ def append_and_print(name, doc): RE.subscribe(append_and_print) - shutter_time = 0.004 + number_of_frames = 1 exposure = 1 deadtime = max(det.controller.get_deadtime(1) for det in detector_list) + shutter_time = 0.004 trigger_logic = StaticSeqTableTriggerLogic(panda.seq[1]) flyer = HardwareTriggeredFlyable(trigger_logic, [], name="flyer") @@ -170,8 +241,8 @@ def flying_plan(): yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger( flyer, detector_list, - num=1, - width=exposure, + number_of_frames=number_of_frames, + exposure=exposure, deadtime=deadtime, shutter_time=shutter_time, ) @@ -194,7 +265,7 @@ def flying_plan(): # Manually incremenet the index as if a frame was taken for detector in detector_list: - detector.writer.index += 1 + detector.writer.increment_index() set_mock_value(flyer.trigger_logic.seq.active, 0) @@ -230,3 +301,51 @@ def flying_plan(): "stream_datum", "stop", ] + + +async def test_time_resolved_fly_and_collect_with_static_seq_table( + RE: RunEngine, + detectors: tuple[StandardDetector], + flyer, +): + names = [] + docs = [] + detector_list = list(detectors) + + def append_and_print(name, doc): + names.append(name) + docs.append(doc) + + RE.subscribe(append_and_print) + + # Trigger parameters + number_of_frames = 1 + exposure = 1 + shutter_time = 0.004 + + def fly(): + yield from bps.stage_all(*detector_list, flyer) + yield from bps.open_run() + yield from time_resolved_fly_and_collect_with_static_seq_table( + stream_name="stream1", + detectors=detector_list, + flyer=flyer, + number_of_frames=number_of_frames, + exposure=exposure, + shutter_time=shutter_time, + ) + yield from bps.close_run() + yield from bps.unstage_all(flyer, *detector_list) + + # fly scan + RE(fly()) + + assert names == [ + "start", + "descriptor", + "stream_resource", + "stream_datum", + "stream_resource", + "stream_datum", + "stop", + ]