From 5f5bf291a154cab484e50995c4bfc4f39a277163 Mon Sep 17 00:00:00 2001 From: gac-x05la Date: Wed, 16 Apr 2025 12:15:27 +0200 Subject: [PATCH] Upgraed DDC --- .../device_configs/microxas_test_bed.yaml | 158 +++++++++--------- .../aerotech/AerotechDriveDataCollection.py | 150 ++++++++++------- 2 files changed, 165 insertions(+), 143 deletions(-) diff --git a/tomcat_bec/device_configs/microxas_test_bed.yaml b/tomcat_bec/device_configs/microxas_test_bed.yaml index d16dfd2..5b37ad7 100644 --- a/tomcat_bec/device_configs/microxas_test_bed.yaml +++ b/tomcat_bec/device_configs/microxas_test_bed.yaml @@ -38,72 +38,72 @@ femto_mean_curr: readOnly: true softwareTrigger: false -# es1_roty: -# readoutPriority: monitored -# description: 'Test rotation stage' -# deviceClass: ophyd.EpicsMotor -# deviceConfig: -# prefix: X02DA-ES1-SMP1:ROTY -# deviceTags: -# - es1-sam -# onFailure: buffer -# enabled: true -# readOnly: false -# softwareTrigger: false +es1_roty: + readoutPriority: monitored + description: 'Test rotation stage' + deviceClass: ophyd.EpicsMotor + deviceConfig: + prefix: X02DA-ES1-SMP1:ROTY + deviceTags: + - es1-sam + onFailure: buffer + enabled: true + readOnly: false + softwareTrigger: false -# es1_ismc: -# description: 'Automation1 iSMC interface' -# deviceClass: tomcat_bec.devices.aa1Controller -# deviceConfig: -# prefix: 'X02DA-ES1-SMP1:CTRL:' -# deviceTags: -# - es1 -# enabled: true -# onFailure: buffer -# readOnly: false -# readoutPriority: monitored -# softwareTrigger: false +es1_ismc: + description: 'Automation1 iSMC interface' + deviceClass: tomcat_bec.devices.aa1Controller + deviceConfig: + prefix: 'X02DA-ES1-SMP1:CTRL:' + deviceTags: + - es1 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: false -# es1_tasks: -# description: 'Automation1 task management interface' -# deviceClass: tomcat_bec.devices.aa1Tasks -# deviceConfig: -# prefix: 'X02DA-ES1-SMP1:TASK:' -# deviceTags: -# - es1 -# enabled: false -# onFailure: buffer -# readOnly: false -# readoutPriority: monitored -# softwareTrigger: false +es1_tasks: + description: 'Automation1 task management interface' + deviceClass: tomcat_bec.devices.aa1Tasks + deviceConfig: + prefix: 'X02DA-ES1-SMP1:TASK:' + deviceTags: + - es1 + enabled: false + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: false -# es1_psod: -# description: 'AA1 PSO output interface (trigger)' -# deviceClass: tomcat_bec.devices.aa1AxisPsoDistance -# deviceConfig: -# prefix: 'X02DA-ES1-SMP1:ROTY:PSO:' -# deviceTags: -# - es1 -# enabled: true -# onFailure: buffer -# readOnly: false -# readoutPriority: monitored -# softwareTrigger: true +es1_psod: + description: 'AA1 PSO output interface (trigger)' + deviceClass: tomcat_bec.devices.aa1AxisPsoDistance + deviceConfig: + prefix: 'X02DA-ES1-SMP1:ROTY:PSO:' + deviceTags: + - es1 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: true -# es1_ddaq: -# description: 'Automation1 position recording interface' -# deviceClass: tomcat_bec.devices.aa1AxisDriveDataCollection -# deviceConfig: -# prefix: 'X02DA-ES1-SMP1:ROTY:DDC:' -# deviceTags: -# - es1 -# enabled: true -# onFailure: buffer -# readOnly: false -# readoutPriority: monitored -# softwareTrigger: false +es1_ddaq: + description: 'Automation1 position recording interface' + deviceClass: tomcat_bec.devices.aa1AxisDriveDataCollection + deviceConfig: + prefix: 'X02DA-ES1-SMP1:ROTY:DDC:' + deviceTags: + - es1 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: false #camera: @@ -119,25 +119,25 @@ femto_mean_curr: # readoutPriority: monitored # softwareTrigger: true -gfcam: - description: GigaFrost camera client - deviceClass: tomcat_bec.devices.GigaFrostCamera - deviceConfig: - prefix: 'X02DA-CAM-GF2:' - backend_url: 'http://sls-daq-001:8080' - auto_soft_enable: true - std_daq_live: 'tcp://129.129.95.111:20000' - std_daq_ws: 'ws://129.129.95.111:8080' - std_daq_rest: 'http://129.129.95.111:5000' - deviceTags: - - camera - - trigger - - gfcam - enabled: true - onFailure: buffer - readOnly: false - readoutPriority: monitored - softwareTrigger: true +# gfcam: +# description: GigaFrost camera client +# deviceClass: tomcat_bec.devices.GigaFrostCamera +# deviceConfig: +# prefix: 'X02DA-CAM-GF2:' +# backend_url: 'http://sls-daq-001:8080' +# auto_soft_enable: true +# std_daq_live: 'tcp://129.129.95.111:20000' +# std_daq_ws: 'ws://129.129.95.111:8080' +# std_daq_rest: 'http://129.129.95.111:5000' +# deviceTags: +# - camera +# - trigger +# - gfcam +# enabled: true +# onFailure: buffer +# readOnly: false +# readoutPriority: monitored +# softwareTrigger: true # gfdaq: # description: GigaFrost stdDAQ client diff --git a/tomcat_bec/devices/aerotech/AerotechDriveDataCollection.py b/tomcat_bec/devices/aerotech/AerotechDriveDataCollection.py index b81ec3f..55cf2a9 100644 --- a/tomcat_bec/devices/aerotech/AerotechDriveDataCollection.py +++ b/tomcat_bec/devices/aerotech/AerotechDriveDataCollection.py @@ -8,68 +8,17 @@ drive data collection (DDC) interface. import time from collections import OrderedDict -from ophyd import Component, EpicsSignal, EpicsSignalRO, Kind +from ophyd import Device, Component, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import SubscriptionStatus -from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase as PSIDeviceBase -from ophyd_devices.interfaces.base_classes.psi_detector_base import ( - CustomDetectorMixin as CustomDeviceMixin, -) +from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase + from bec_lib import bec_logger logger = bec_logger.logger -class AerotechDriveDataCollectionMixin(CustomDeviceMixin): - """Mixin class for self-configuration and staging - - NOTE: scripted scans start drive data collection internally - """ - # parent : aa1Tasks - def on_stage(self) -> None: - """Configuration and staging""" - - # Fish out configuration from scaninfo (does not need to be full configuration) - d = {} - if "kwargs" in self.parent.scaninfo.scan_msg.info: - scanargs = self.parent.scaninfo.scan_msg.info["kwargs"] - # NOTE: Scans don't have to fully configure the device - if "ddc_trigger" in scanargs: - d["ddc_trigger"] = scanargs["ddc_trigger"] - if "ddc_num_points" in scanargs: - d["num_points_total"] = scanargs["ddc_num_points"] - else: - # Try to figure out number of points - num_points = 1 - points_valid = False - if "steps" in scanargs and scanargs['steps'] is not None: - num_points *= scanargs["steps"] - points_valid = True - elif "exp_burst" in scanargs and scanargs['exp_burst'] is not None: - num_points *= scanargs["exp_burst"] - points_valid = True - elif "repeats" in scanargs and scanargs['repeats'] is not None: - num_points *= scanargs["repeats"] - points_valid = True - if points_valid: - d["num_points_total"] = num_points - - # Perform bluesky-style configuration - if len(d) > 0: - logger.warning(f"[{self.parent.name}] Configuring with:\n{d}") - self.parent.configure(d=d) - - # Stage the data collection if not in internally launced mode - # NOTE: Scripted scans start acquiring from the scrits - if self.parent.scaninfo.scan_type not in ("script", "scripted"): - self.parent.bluestage() - - def on_unstage(self): - """Standard bluesky unstage""" - self.parent._switch.set("Stop", settle_time=0.2).wait() - - -class aa1AxisDriveDataCollection(PSIDeviceBase): +class aa1AxisDriveDataCollection(PSIDeviceBase, Device): """Axis data collection This class provides convenience wrappers around the Aerotech API's axis @@ -88,9 +37,10 @@ class aa1AxisDriveDataCollection(PSIDeviceBase): ... ret = yield from ddc.collect() + NOTE: scripted scans start drive data collection internally NOTE: Expected behavior is that the device is disabled when not in use, - i.e. there's avtive enable/disable management. + i.e. there's active enable/disable management. """ # ######################################################################## @@ -111,8 +61,31 @@ class aa1AxisDriveDataCollection(PSIDeviceBase): _buffer0 = Component(EpicsSignalRO, "BUFFER0", auto_monitor=True, kind=Kind.normal) _buffer1 = Component(EpicsSignalRO, "BUFFER1", auto_monitor=True, kind=Kind.normal) - custom_prepare_cls = AerotechDriveDataCollectionMixin - USER_ACCESS = ["configure", "reset"] + USER_ACCESS = ["configure", "reset", "arm", "disarm"] + + def __init__( + self, + prefix="", + *, + name, + kind=None, + read_attrs=None, + configuration_attrs=None, + parent=None, + scan_info=None, + **kwargs, + ): + # super() will call the mixin class + super().__init__( + prefix=prefix, + name=name, + kind=kind, + read_attrs=read_attrs, + configuration_attrs=configuration_attrs, + parent=parent, + scan_info=scan_info, + **kwargs, + ) def configure(self, d: dict = None) -> tuple: """Configure data capture @@ -128,21 +101,68 @@ class aa1AxisDriveDataCollection(PSIDeviceBase): if "num_points_total" in d: self.npoints.set(d["num_points_total"]).wait() if "ddc_trigger" in d: - self._trigger.set(d['ddc_trigger']).wait() + self._trigger.set(d["ddc_trigger"]).wait() if "ddc_source0" in d: - self._input0.set(d['ddc_source0']).wait() + self._input0.set(d["ddc_source0"]).wait() if "ddc_source1" in d: - self._input1.set(d['ddc_source1']).wait() + self._input1.set(d["ddc_source1"]).wait() # Reset incremental readback self._switch.set("ResetRB", settle_time=0.1).wait() new = self.read_configuration() return (old, new) - def bluestage(self) -> None: + def on_stage(self) -> None: + """Configuration and staging""" + # Fish out configuration from scaninfo (does not need to be full configuration) + d = {} + if "kwargs" in self.scaninfo.scan_msg.info: + scanargs = self.scaninfo.scan_msg.info["kwargs"] + # NOTE: Scans don't have to fully configure the device + if "ddc_trigger" in scanargs: + d["ddc_trigger"] = scanargs["ddc_trigger"] + if "ddc_num_points" in scanargs: + d["num_points_total"] = scanargs["ddc_num_points"] + else: + # Try to figure out number of points + num_points = 1 + points_valid = False + if "steps" in scanargs and scanargs["steps"] is not None: + num_points *= scanargs["steps"] + points_valid = True + elif "exp_burst" in scanargs and scanargs["exp_burst"] is not None: + num_points *= scanargs["exp_burst"] + points_valid = True + elif "repeats" in scanargs and scanargs["repeats"] is not None: + num_points *= scanargs["repeats"] + points_valid = True + if points_valid: + d["num_points_total"] = num_points + + # Perform bluesky-style configuration + if len(d) > 0: + logger.warning(f"[{self.name}] Configuring with:\n{d}") + self.configure(d=d) + + # Stage the data collection if not in internally launced mode + # NOTE: Scripted scans start acquiring from the scrits + if self.scaninfo.scan_type not in ("script", "scripted"): + self.arm() + # Reset readback + self.reset() + + def on_unstage(self): + """Standard bluesky unstage""" + self.disarm() + + def arm(self) -> None: """Bluesky-style stage""" self._switch.set("Start", settle_time=0.2).wait() + def disarm(self): + """Standard bluesky unstage""" + self._switch.set("Stop", settle_time=0.2).wait() + def reset(self): """Reset incremental readback""" self._switch.set("ResetRB", settle_time=0.1).wait() @@ -164,20 +184,22 @@ class aa1AxisDriveDataCollection(PSIDeviceBase): timestamp_ = timestamp return result + status = None if index == 0: status = SubscriptionStatus(self._readstatus0, neg_edge, settle_time=0.5) self._readback0.set(1).wait() elif index == 1: status = SubscriptionStatus(self._readstatus1, neg_edge, settle_time=0.5) self._readback1.set(1).wait() + else: + raise RuntimeError(f"Unsupported drive data collection channel: {index}") # Start asynchronous readback status.wait() return status def describe_collect(self) -> OrderedDict: - """Describes collected array format according to JSONschema - """ + """Describes collected array format according to JSONschema""" ret = OrderedDict() ret["buffer0"] = { "source": "internal",