From af0f9861c060681dada00ffc66b0b7e36cbb8702 Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Tue, 14 May 2024 18:46:21 -0400 Subject: [PATCH 1/7] Dev updates for the Zebra IOC --- scripts/run-caproto-ioc.sh | 5 +- scripts/run-caproto-zebra-ioc.sh | 7 ++ src/srx_caproto_iocs/base.py | 7 +- src/srx_caproto_iocs/zebra/caproto_ioc.py | 119 +++++++++++++++++++++- 4 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 scripts/run-caproto-zebra-ioc.sh diff --git a/scripts/run-caproto-ioc.sh b/scripts/run-caproto-ioc.sh index 9cfaefe..808b8dd 100644 --- a/scripts/run-caproto-ioc.sh +++ b/scripts/run-caproto-ioc.sh @@ -3,7 +3,8 @@ set -vxeuo pipefail CAPROTO_IOC="${1:-srx_caproto_iocs.base}" - +DEFAULT_PREFIX="BASE:{{Dev:Save1}}:" +CAPROTO_IOC_PREFIX="${2:-${DEFAULT_PREFIX}}" # shellcheck source=/dev/null if [ -f "/etc/profile.d/epics.sh" ]; then . /etc/profile.d/epics.sh @@ -12,4 +13,4 @@ fi export EPICS_CAS_AUTO_BEACON_ADDR_LIST="no" export EPICS_CAS_BEACON_ADDR_LIST="${EPICS_CA_ADDR_LIST:-127.0.0.255}" -python -m "${CAPROTO_IOC}" --prefix="BASE:{{Dev:Save1}}:" --list-pvs +python -m "${CAPROTO_IOC}" --prefix="${CAPROTO_IOC_PREFIX}" --list-pvs diff --git a/scripts/run-caproto-zebra-ioc.sh b/scripts/run-caproto-zebra-ioc.sh new file mode 100644 index 0000000..1d4118c --- /dev/null +++ b/scripts/run-caproto-zebra-ioc.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -vxeuo pipefail + +SCRIPT_DIR="$(dirname "$0")" + +bash "${SCRIPT_DIR}/run-caproto-ioc.sh" srx_caproto_iocs.zebra.caproto_ioc "XF:05IDD-ES:1{{Dev:Zebra2}}:" diff --git a/src/srx_caproto_iocs/base.py b/src/srx_caproto_iocs/base.py index 570794d..6c3152b 100644 --- a/src/srx_caproto_iocs/base.py +++ b/src/srx_caproto_iocs/base.py @@ -104,8 +104,7 @@ async def queue(self, instance, async_lib): ) thread.start() - @stage.putter - async def stage(self, instance, value): + async def _stage(self, instance, value): """The stage method to perform preparation of a dataset to save the data.""" if ( instance.value in [True, StageStates.STAGED.value] @@ -150,6 +149,10 @@ async def stage(self, instance, value): return False + @stage.putter + async def stage(self, *args, **kwargs): + return await self._stage(*args, **kwargs) + def _get_current_dataset(self, frame): """The method to return a desired dataset. diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index f2ae288..5970327 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -1,20 +1,137 @@ from __future__ import annotations import textwrap +from pprint import pformat +from caproto.asyncio.client import Context from caproto.server import run, template_arg_parser from ..base import CaprotoSaveIOC, check_args +from ..utils import now + +# def export_nano_zebra_data(zebra, filepath, fastaxis): +# j = 0 +# while zebra.pc.data_in_progress.get() == 1: +# print("Waiting for zebra...") +# ttime.sleep(0.1) +# j += 1 +# if j > 10: +# print("THE ZEBRA IS BEHAVING BADLY CARRYING ON") +# break + +# time_d = zebra.pc.data.time.get() +# enc1_d = zebra.pc.data.enc1.get() +# enc2_d = zebra.pc.data.enc2.get() +# enc3_d = zebra.pc.data.enc3.get() + +# px = zebra.pc.pulse_step.get() +# if fastaxis == 'NANOHOR': +# # Add half pixelsize to correct encoder +# enc1_d = enc1_d + (px / 2) +# elif fastaxis == 'NANOVER': +# # Add half pixelsize to correct encoder +# enc2_d = enc2_d + (px / 2) +# elif fastaxis == 'NANOZ': +# # Add half pixelsize to correct encoder +# enc3_d = enc3_d + (px / 2) + +# size = (len(time_d),) +# with h5py.File(filepath, "w") as f: +# dset0 = f.create_dataset("zebra_time", size, dtype="f") +# dset0[...] = np.array(time_d) +# dset1 = f.create_dataset("enc1", size, dtype="f") +# dset1[...] = np.array(enc1_d) +# dset2 = f.create_dataset("enc2", size, dtype="f") +# dset2[...] = np.array(enc2_d) +# dset3 = f.create_dataset("enc3", size, dtype="f") +# dset3[...] = np.array(enc3_d) + + +# class ZebraPositionCaptureData(Device): +# """ +# Data arrays for the Zebra position capture function and their metadata. +# """ +# # Data arrays +# ... +# enc1 = Cpt(EpicsSignal, "PC_ENC1") # XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC1 +# enc2 = Cpt(EpicsSignal, "PC_ENC2") # XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC2 +# enc3 = Cpt(EpicsSignal, "PC_ENC3") # XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC3 +# time = Cpt(EpicsSignal, "PC_TIME") # XF:05IDD-ES:1{Dev:Zebra2}:PC_TIME +# ... + +# class ZebraPositionCapture(Device): +# """ +# Signals for the position capture function of the Zebra +# """ + +# # Configuration settings and status PVs +# ... +# pulse_step = Cpt(EpicsSignalWithRBV, "PC_PULSE_STEP") # XF:05IDD-ES:1{Dev:Zebra2}:PC_PULSE_STEP +# ... +# data_in_progress = Cpt(EpicsSignalRO, "ARRAY_ACQ") # XF:05IDD-ES:1{Dev:Zebra2}:ARRAY_ACQ +# ... +# data = Cpt(ZebraPositionCaptureData, "") + +# nanoZebra = SRXZebra( +# "XF:05IDD-ES:1{Dev:Zebra2}:", name="nanoZebra", +# read_attrs=["pc.data.enc1", "pc.data.enc2", "pc.data.enc3", "pc.data.time"], +# ) class ZebraSaveIOC(CaprotoSaveIOC): """Zebra caproto save IOC.""" + # enc1 = Cpt(EpicsSignal, "PC_ENC1") + # enc2 = Cpt(EpicsSignal, "PC_ENC2") + # enc3 = Cpt(EpicsSignal, "PC_ENC3") + # enc4 = Cpt(EpicsSignal, "PC_ENC4") + + # data_in_progress = pvproperty() # + + # time_d = pvproperty() + # enc1_d = pvproperty() + # enc2_d = pvproperty() + # enc3_d = pvproperty() + # pulse_step = pvproperty() + + def __init__(self, *args, external_pvs=None, **kwargs): + """Init method. + + external_pvs : dict + a dictionary of external PVs with keys as human-readable names. + """ + super().__init__(*args, **kwargs) + self._external_pvs = external_pvs + + async def _stage(self, *args, **kwargs): + await super()._stage(*args, **kwargs) + client_context = Context() + res = {} + if self._external_pvs is not None: + pvobjects = await client_context.get_pvs(*self._external_pvs.values()) + for i, (name, pv) in enumerate(self._external_pvs.items()): # noqa: B007 + pvobject = pvobjects[i] + # print(f"{now()}: {pvobject = }") + ret = await pvobject.read() + # print(f"{now()}: {val.data}") + res[name] = {"data": ret.data, "shape": ret.data.shape} + print(f"{now()}:\n{pformat(res)}") + return True + if __name__ == "__main__": parser, split_args = template_arg_parser( default_prefix="", desc=textwrap.dedent(ZebraSaveIOC.__doc__) ) ioc_options, run_options = check_args(parser, split_args) - ioc = ZebraSaveIOC(**ioc_options) # TODO: pass libca IOC PVs of interest + + external_pvs = { + "pulse_step": "XF:05IDD-ES:1{Dev:Zebra2}:PC_PULSE_STEP", + "data_in_progress": "XF:05IDD-ES:1{Dev:Zebra2}:ARRAY_ACQ", + "enc1": "XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC1", + "enc2": "XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC2", + "enc3": "XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC3", + "time": "XF:05IDD-ES:1{Dev:Zebra2}:PC_TIME", + } + + ioc = ZebraSaveIOC(external_pvs=external_pvs, **ioc_options) run(ioc.pvdb, **run_options) From 0afd370e6316d2858706f5f2ffaf3ad98128262e Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Tue, 14 May 2024 19:47:50 -0400 Subject: [PATCH 2/7] WIP: can save `enc1` 1-D data --- src/srx_caproto_iocs/base.py | 8 +++--- src/srx_caproto_iocs/utils.py | 32 ++++++++++++++++++++--- src/srx_caproto_iocs/zebra/caproto_ioc.py | 29 ++++++++++---------- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/srx_caproto_iocs/base.py b/src/srx_caproto_iocs/base.py index 6c3152b..725f4ab 100644 --- a/src/srx_caproto_iocs/base.py +++ b/src/srx_caproto_iocs/base.py @@ -16,7 +16,7 @@ from ophyd import Device, EpicsSignal, EpicsSignalRO from ophyd.status import SubscriptionStatus -from .utils import now, save_hdf5 +from .utils import now, save_hdf5_1d class AcqStatuses(Enum): @@ -153,7 +153,7 @@ async def _stage(self, instance, value): async def stage(self, *args, **kwargs): return await self._stage(*args, **kwargs) - def _get_current_dataset(self, frame): + async def _get_current_dataset(self, frame): """The method to return a desired dataset. See https://scikit-image.org/docs/stable/auto_examples/data/plot_3d.html @@ -187,7 +187,7 @@ async def acquire(self, instance, value): # Delegate saving the resulting data to a blocking callback in a thread. payload = { "filename": self.full_file_path.value, - "data": self._get_current_dataset(frame=self.frame_num.value), + "data": await self._get_current_dataset(frame=self.frame_num.value), "uid": str(uuid.uuid4()), "timestamp": ttime.time(), "frame_number": self.frame_num.value, @@ -213,7 +213,7 @@ def saver(request_queue, response_queue): data = received["data"] frame_number = received["frame_number"] try: - save_hdf5(fname=filename, data=data, mode="a") + save_hdf5_1d(fname=filename, data=data, mode="x", group_path="enc1") print( f"{now()}: saved {frame_number=} {data.shape} data into:\n {filename}" ) diff --git a/src/srx_caproto_iocs/utils.py b/src/srx_caproto_iocs/utils.py index 750b480..fd17f4c 100644 --- a/src/srx_caproto_iocs/utils.py +++ b/src/srx_caproto_iocs/utils.py @@ -15,7 +15,33 @@ def now(as_object=False): return _now.isoformat() -def save_hdf5( +def save_hdf5_1d( + fname, + data, + group_path="data", + dtype="float32", + mode="x", +): + """The function to export the 1-D data to an HDF5 file. + + Check https://docs.h5py.org/en/stable/high/file.html#opening-creating-files for modes: + + r Readonly, file must exist (default) + r+ Read/write, file must exist + w Create file, truncate if exists + w- or x Create file, fail if exists + a Read/write if exists, create otherwise + """ + with h5py.File(fname, mode, libver="latest") as h5file_desc: + dataset = h5file_desc.create_dataset( + group_path, + data=data, + dtype=dtype, + ) + dataset.flush() + + +def save_hdf5_nd( fname, data, group_name="/entry", @@ -23,7 +49,7 @@ def save_hdf5( dtype="float32", mode="x", ): - """The function to export the data to an HDF5 file. + """The function to export the N-D data to an HDF5 file (N>1). Check https://docs.h5py.org/en/stable/high/file.html#opening-creating-files for modes: @@ -54,5 +80,5 @@ def save_hdf5( h5file_desc.swmr_mode = True dataset.resize((frame_num + 1, *frame_shape)) - dataset[frame_num, :, :] = data + dataset[frame_num, ...] = data dataset.flush() diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index 5970327..69b0403 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -1,7 +1,6 @@ from __future__ import annotations import textwrap -from pprint import pformat from caproto.asyncio.client import Context from caproto.server import run, template_arg_parser @@ -102,20 +101,22 @@ def __init__(self, *args, external_pvs=None, **kwargs): super().__init__(*args, **kwargs) self._external_pvs = external_pvs - async def _stage(self, *args, **kwargs): - await super()._stage(*args, **kwargs) + # async def _stage(self, *args, **kwargs): + # ret = await super()._stage(*args, **kwargs) + # return ret + + async def _get_current_dataset(self, frame, external_pv="enc1"): client_context = Context() - res = {} - if self._external_pvs is not None: - pvobjects = await client_context.get_pvs(*self._external_pvs.values()) - for i, (name, pv) in enumerate(self._external_pvs.items()): # noqa: B007 - pvobject = pvobjects[i] - # print(f"{now()}: {pvobject = }") - ret = await pvobject.read() - # print(f"{now()}: {val.data}") - res[name] = {"data": ret.data, "shape": ret.data.shape} - print(f"{now()}:\n{pformat(res)}") - return True + (pvobject,) = await client_context.get_pvs(self._external_pvs[external_pv]) + print(f"{pvobject = }") + # pvobject = pvobjects[0] + ret = await pvobject.read() + + dataset = ret.data + + print(f"{now()}:\n{dataset} {dataset.shape}") + + return dataset if __name__ == "__main__": From 6524861cb14e98cac913af1baa817a081d260749 Mon Sep 17 00:00:00 2001 From: Maksim Rakitin Date: Thu, 16 May 2024 13:29:49 -0400 Subject: [PATCH 3/7] Vendor acquire/saver methods from the base IOC in the Zebra one --- src/srx_caproto_iocs/base.py | 2 +- src/srx_caproto_iocs/zebra/caproto_ioc.py | 82 +++++++++++++++++++++-- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/srx_caproto_iocs/base.py b/src/srx_caproto_iocs/base.py index 725f4ab..1bf4658 100644 --- a/src/srx_caproto_iocs/base.py +++ b/src/srx_caproto_iocs/base.py @@ -213,7 +213,7 @@ def saver(request_queue, response_queue): data = received["data"] frame_number = received["frame_number"] try: - save_hdf5_1d(fname=filename, data=data, mode="x", group_path="enc1") + save_hdf5_nd(fname=filename, data=data, mode="x", group_path="enc1") print( f"{now()}: saved {frame_number=} {data.shape} data into:\n {filename}" ) diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index 69b0403..153a9b8 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -118,6 +118,73 @@ async def _get_current_dataset(self, frame, external_pv="enc1"): return dataset + @acquire.putter + @no_reentry + async def acquire(self, instance, value): + """The acquire method to perform an individual acquisition of a data point.""" + if ( + value != AcqStatuses.ACQUIRING.value + # or self.stage.value not in [True, StageStates.STAGED.value] + ): + return False + + if ( + instance.value in [True, AcqStatuses.ACQUIRING.value] + and value == AcqStatuses.ACQUIRING.value + ): + print( + f"The device is already acquiring. Please wait until the '{AcqStatuses.IDLE.value}' status." + ) + return True + + await self.acquire.write(AcqStatuses.ACQUIRING.value) + + # Delegate saving the resulting data to a blocking callback in a thread. + payload = { + "filename": self.full_file_path.value, + "data": await self._get_current_dataset(frame=self.frame_num.value), + "uid": str(uuid.uuid4()), + "timestamp": ttime.time(), + "frame_number": self.frame_num.value, + } + + await self._request_queue.async_put(payload) + response = await self._response_queue.async_get() + + if response["success"]: + # Increment the counter only on a successful saving of the file. + await self.frame_num.write(self.frame_num.value + 1) + + # await self.acquire.write(AcqStatuses.IDLE.value) + + return False + + @staticmethod + def saver(request_queue, response_queue): + """The saver callback for threading-based queueing.""" + while True: + received = request_queue.get() + filename = received["filename"] + data = received["data"] + frame_number = received["frame_number"] + try: + save_hdf5_1d(fname=filename, data=data, mode="x", group_path="enc1") + print( + f"{now()}: saved {frame_number=} {data.shape} data into:\n {filename}" + ) + + success = True + error_message = "" + except Exception as exc: # pylint: disable=broad-exception-caught + success = False + error_message = exc + print( + f"Cannot save file {filename!r} due to the following exception:\n{exc}" + ) + + response = {"success": success, "error_message": error_message} + response_queue.put(response) + if __name__ == "__main__": parser, split_args = template_arg_parser( @@ -125,13 +192,16 @@ async def _get_current_dataset(self, frame, external_pv="enc1"): ) ioc_options, run_options = check_args(parser, split_args) + external_pv_prefix = ioc_options["prefix"].replace("{{", "{").replace("}}", "}") # "XF:05IDD-ES:1{Dev:Zebra2}:" + external_pvs = { - "pulse_step": "XF:05IDD-ES:1{Dev:Zebra2}:PC_PULSE_STEP", - "data_in_progress": "XF:05IDD-ES:1{Dev:Zebra2}:ARRAY_ACQ", - "enc1": "XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC1", - "enc2": "XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC2", - "enc3": "XF:05IDD-ES:1{Dev:Zebra2}:PC_ENC3", - "time": "XF:05IDD-ES:1{Dev:Zebra2}:PC_TIME", + "pulse_step": external_pv_prefix + "PC_PULSE_STEP", + "data_in_progress": external_pv_prefix + "ARRAY_ACQ", + "enc1": external_pv_prefix + "PC_ENC1", + "enc2": external_pv_prefix + "PC_ENC2", + "enc3": external_pv_prefix + "PC_ENC3", + "enc4": external_pv_prefix + "PC_ENC4", + "time": external_pv_prefix + "PC_TIME", } ioc = ZebraSaveIOC(external_pvs=external_pvs, **ioc_options) From adb96a3ef5fb2ef89a69a8adb8a8b4a99953ea41 Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Fri, 17 May 2024 14:44:12 -0400 Subject: [PATCH 4/7] Prototyped IOC to accept the a dict of arrays to save - the ophyd/bluesky side should do the logic and call the saver when done --- src/srx_caproto_iocs/base.py | 2 +- src/srx_caproto_iocs/utils.py | 16 ++-- src/srx_caproto_iocs/zebra/caproto_ioc.py | 110 ++++++++++------------ 3 files changed, 58 insertions(+), 70 deletions(-) diff --git a/src/srx_caproto_iocs/base.py b/src/srx_caproto_iocs/base.py index 1bf4658..412d33c 100644 --- a/src/srx_caproto_iocs/base.py +++ b/src/srx_caproto_iocs/base.py @@ -16,7 +16,7 @@ from ophyd import Device, EpicsSignal, EpicsSignalRO from ophyd.status import SubscriptionStatus -from .utils import now, save_hdf5_1d +from .utils import now, save_hdf5_nd class AcqStatuses(Enum): diff --git a/src/srx_caproto_iocs/utils.py b/src/srx_caproto_iocs/utils.py index fd17f4c..737fe3d 100644 --- a/src/srx_caproto_iocs/utils.py +++ b/src/srx_caproto_iocs/utils.py @@ -15,10 +15,9 @@ def now(as_object=False): return _now.isoformat() -def save_hdf5_1d( +def save_hdf5_zebra( fname, data, - group_path="data", dtype="float32", mode="x", ): @@ -33,12 +32,13 @@ def save_hdf5_1d( a Read/write if exists, create otherwise """ with h5py.File(fname, mode, libver="latest") as h5file_desc: - dataset = h5file_desc.create_dataset( - group_path, - data=data, - dtype=dtype, - ) - dataset.flush() + for pvname, value in data.items(): + dataset = h5file_desc.create_dataset( + pvname, + data=value, + dtype=dtype, + ) + dataset.flush() def save_hdf5_nd( diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index 153a9b8..405fd8f 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -2,11 +2,10 @@ import textwrap -from caproto.asyncio.client import Context -from caproto.server import run, template_arg_parser +from caproto.server import pvproperty, run, template_arg_parser from ..base import CaprotoSaveIOC, check_args -from ..utils import now +from ..utils import now, save_hdf5_zebra # def export_nano_zebra_data(zebra, filepath, fastaxis): # j = 0 @@ -76,10 +75,36 @@ # read_attrs=["pc.data.enc1", "pc.data.enc2", "pc.data.enc3", "pc.data.time"], # ) +DEFAULT_MAX_LENGTH = 100_000 + class ZebraSaveIOC(CaprotoSaveIOC): """Zebra caproto save IOC.""" + enc1 = pvproperty( + value=0, + doc="enc1 data", + max_length=DEFAULT_MAX_LENGTH, + ) + + enc2 = pvproperty( + value=0, + doc="enc2 data", + max_length=DEFAULT_MAX_LENGTH, + ) + + enc3 = pvproperty( + value=0, + doc="enc3 data", + max_length=DEFAULT_MAX_LENGTH, + ) + + zebra_time = pvproperty( + value=0, + doc="zebra time", + max_length=DEFAULT_MAX_LENGTH, + ) + # enc1 = Cpt(EpicsSignal, "PC_ENC1") # enc2 = Cpt(EpicsSignal, "PC_ENC2") # enc3 = Cpt(EpicsSignal, "PC_ENC3") @@ -87,9 +112,8 @@ class ZebraSaveIOC(CaprotoSaveIOC): # data_in_progress = pvproperty() # + # time_d = pvproperty() - # enc1_d = pvproperty() - # enc2_d = pvproperty() - # enc3_d = pvproperty() + # enc1_d = pvproperty()TypeError: ZebraSaveIOC._get_current_dataset() got an unexpected keyword argument 'frame' + # pulse_step = pvproperty() def __init__(self, *args, external_pvs=None, **kwargs): @@ -105,60 +129,23 @@ def __init__(self, *args, external_pvs=None, **kwargs): # ret = await super()._stage(*args, **kwargs) # return ret - async def _get_current_dataset(self, frame, external_pv="enc1"): - client_context = Context() - (pvobject,) = await client_context.get_pvs(self._external_pvs[external_pv]) - print(f"{pvobject = }") - # pvobject = pvobjects[0] - ret = await pvobject.read() + async def _get_current_dataset( + self, *args, **kwargs + ): # , frame, external_pv="enc1"): + # client_context = Context() + # (pvobject,) = await client_context.get_pvs(self._external_pvs[external_pv]) + # print(f"{pvobject = }") + # # pvobject = pvobjects[0] + # ret = await pvobject.read() - dataset = ret.data + dataset = {} + for pvname in ["enc1", "enc2", "enc3", "zebra_time"]: + dataset[pvname] = getattr(self, pvname).value - print(f"{now()}:\n{dataset} {dataset.shape}") + print(f"{now()}:\n{dataset}") return dataset - @acquire.putter - @no_reentry - async def acquire(self, instance, value): - """The acquire method to perform an individual acquisition of a data point.""" - if ( - value != AcqStatuses.ACQUIRING.value - # or self.stage.value not in [True, StageStates.STAGED.value] - ): - return False - - if ( - instance.value in [True, AcqStatuses.ACQUIRING.value] - and value == AcqStatuses.ACQUIRING.value - ): - print( - f"The device is already acquiring. Please wait until the '{AcqStatuses.IDLE.value}' status." - ) - return True - - await self.acquire.write(AcqStatuses.ACQUIRING.value) - - # Delegate saving the resulting data to a blocking callback in a thread. - payload = { - "filename": self.full_file_path.value, - "data": await self._get_current_dataset(frame=self.frame_num.value), - "uid": str(uuid.uuid4()), - "timestamp": ttime.time(), - "frame_number": self.frame_num.value, - } - - await self._request_queue.async_put(payload) - response = await self._response_queue.async_get() - - if response["success"]: - # Increment the counter only on a successful saving of the file. - await self.frame_num.write(self.frame_num.value + 1) - - # await self.acquire.write(AcqStatuses.IDLE.value) - - return False - @staticmethod def saver(request_queue, response_queue): """The saver callback for threading-based queueing.""" @@ -166,12 +153,11 @@ def saver(request_queue, response_queue): received = request_queue.get() filename = received["filename"] data = received["data"] - frame_number = received["frame_number"] + # 'frame_number' is not used for this exporter. + frame_number = received["frame_number"] # noqa: F841 try: - save_hdf5_1d(fname=filename, data=data, mode="x", group_path="enc1") - print( - f"{now()}: saved {frame_number=} {data.shape} data into:\n {filename}" - ) + save_hdf5_zebra(fname=filename, data=data, mode="x") + print(f"{now()}: saved data into:\n {filename}") success = True error_message = "" @@ -192,7 +178,9 @@ def saver(request_queue, response_queue): ) ioc_options, run_options = check_args(parser, split_args) - external_pv_prefix = ioc_options["prefix"].replace("{{", "{").replace("}}", "}") # "XF:05IDD-ES:1{Dev:Zebra2}:" + external_pv_prefix = ( + ioc_options["prefix"].replace("{{", "{").replace("}}", "}") + ) # "XF:05IDD-ES:1{Dev:Zebra2}:" external_pvs = { "pulse_step": external_pv_prefix + "PC_PULSE_STEP", From d7ea796793c47deaafd90e68c3152977a3d7444a Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Fri, 17 May 2024 15:01:36 -0400 Subject: [PATCH 5/7] Add support for SIS scaler exporting --- src/srx_caproto_iocs/zebra/caproto_ioc.py | 47 ++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index 405fd8f..152abc2 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -1,7 +1,9 @@ from __future__ import annotations import textwrap +from enum import Enum +from caproto import ChannelType from caproto.server import pvproperty, run, template_arg_parser from ..base import CaprotoSaveIOC, check_args @@ -78,9 +80,23 @@ DEFAULT_MAX_LENGTH = 100_000 +class DevTypes(Enum): + """Enum class for devices.""" + + ZEBRA = "zebra" + SCALER = "scaler" + + class ZebraSaveIOC(CaprotoSaveIOC): """Zebra caproto save IOC.""" + dev_type = pvproperty( + value=DevTypes.ZEBRA, + enum_strings=[x.value for x in DevTypes], + dtype=ChannelType.ENUM, + doc="Pick device type", + ) + enc1 = pvproperty( value=0, doc="enc1 data", @@ -105,6 +121,30 @@ class ZebraSaveIOC(CaprotoSaveIOC): max_length=DEFAULT_MAX_LENGTH, ) + i0 = pvproperty( + value=0, + doc="i0 data", + max_length=DEFAULT_MAX_LENGTH, + ) + + im = pvproperty( + value=0, + doc="im data", + max_length=DEFAULT_MAX_LENGTH, + ) + + it = pvproperty( + value=0, + doc="it data", + max_length=DEFAULT_MAX_LENGTH, + ) + + sis_time = pvproperty( + value=0, + doc="sis time", + max_length=DEFAULT_MAX_LENGTH, + ) + # enc1 = Cpt(EpicsSignal, "PC_ENC1") # enc2 = Cpt(EpicsSignal, "PC_ENC2") # enc3 = Cpt(EpicsSignal, "PC_ENC3") @@ -138,8 +178,13 @@ async def _get_current_dataset( # # pvobject = pvobjects[0] # ret = await pvobject.read() + if self.dev_type == DevTypes.ZEBRA: + pvnames = ["enc1", "enc2", "enc3", "zebra_time"] + else: + pvnames = ["i0", "im", "it", "sis_time"] + dataset = {} - for pvname in ["enc1", "enc2", "enc3", "zebra_time"]: + for pvname in pvnames: dataset[pvname] = getattr(self, pvname).value print(f"{now()}:\n{dataset}") From afdb4888d88e773636ee9c499747ecd4eda2dca4 Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Fri, 17 May 2024 15:09:48 -0400 Subject: [PATCH 6/7] Fix enum default value for the dev_type --- src/srx_caproto_iocs/zebra/caproto_ioc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index 152abc2..04cb2db 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -91,7 +91,7 @@ class ZebraSaveIOC(CaprotoSaveIOC): """Zebra caproto save IOC.""" dev_type = pvproperty( - value=DevTypes.ZEBRA, + value=DevTypes.ZEBRA.value, enum_strings=[x.value for x in DevTypes], dtype=ChannelType.ENUM, doc="Pick device type", From 246d8857aefd1e70009f9f772f31d99db6101a9b Mon Sep 17 00:00:00 2001 From: Max Rakitin Date: Fri, 17 May 2024 15:14:27 -0400 Subject: [PATCH 7/7] Clean up irrelevant code --- src/srx_caproto_iocs/zebra/caproto_ioc.py | 60 +++++++++-------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/srx_caproto_iocs/zebra/caproto_ioc.py b/src/srx_caproto_iocs/zebra/caproto_ioc.py index 04cb2db..1f39217 100644 --- a/src/srx_caproto_iocs/zebra/caproto_ioc.py +++ b/src/srx_caproto_iocs/zebra/caproto_ioc.py @@ -145,29 +145,14 @@ class ZebraSaveIOC(CaprotoSaveIOC): max_length=DEFAULT_MAX_LENGTH, ) - # enc1 = Cpt(EpicsSignal, "PC_ENC1") - # enc2 = Cpt(EpicsSignal, "PC_ENC2") - # enc3 = Cpt(EpicsSignal, "PC_ENC3") - # enc4 = Cpt(EpicsSignal, "PC_ENC4") + # def __init__(self, *args, external_pvs=None, **kwargs): + # """Init method. - # data_in_progress = pvproperty() # + - # time_d = pvproperty() - # enc1_d = pvproperty()TypeError: ZebraSaveIOC._get_current_dataset() got an unexpected keyword argument 'frame' - - # pulse_step = pvproperty() - - def __init__(self, *args, external_pvs=None, **kwargs): - """Init method. - - external_pvs : dict - a dictionary of external PVs with keys as human-readable names. - """ - super().__init__(*args, **kwargs) - self._external_pvs = external_pvs - - # async def _stage(self, *args, **kwargs): - # ret = await super()._stage(*args, **kwargs) - # return ret + # external_pvs : dict + # a dictionary of external PVs with keys as human-readable names. + # """ + # super().__init__(*args, **kwargs) + # self._external_pvs = external_pvs async def _get_current_dataset( self, *args, **kwargs @@ -223,19 +208,20 @@ def saver(request_queue, response_queue): ) ioc_options, run_options = check_args(parser, split_args) - external_pv_prefix = ( - ioc_options["prefix"].replace("{{", "{").replace("}}", "}") - ) # "XF:05IDD-ES:1{Dev:Zebra2}:" - - external_pvs = { - "pulse_step": external_pv_prefix + "PC_PULSE_STEP", - "data_in_progress": external_pv_prefix + "ARRAY_ACQ", - "enc1": external_pv_prefix + "PC_ENC1", - "enc2": external_pv_prefix + "PC_ENC2", - "enc3": external_pv_prefix + "PC_ENC3", - "enc4": external_pv_prefix + "PC_ENC4", - "time": external_pv_prefix + "PC_TIME", - } - - ioc = ZebraSaveIOC(external_pvs=external_pvs, **ioc_options) + # external_pv_prefix = ( + # ioc_options["prefix"].replace("{{", "{").replace("}}", "}") + # ) # "XF:05IDD-ES:1{Dev:Zebra2}:" + + # external_pvs = { + # "pulse_step": external_pv_prefix + "PC_PULSE_STEP", + # "data_in_progress": external_pv_prefix + "ARRAY_ACQ", + # "enc1": external_pv_prefix + "PC_ENC1", + # "enc2": external_pv_prefix + "PC_ENC2", + # "enc3": external_pv_prefix + "PC_ENC3", + # "enc4": external_pv_prefix + "PC_ENC4", + # "time": external_pv_prefix + "PC_TIME", + # } + + # ioc = ZebraSaveIOC(external_pvs=external_pvs, **ioc_options) + ioc = ZebraSaveIOC(**ioc_options) run(ioc.pvdb, **run_options)