Upgraed DDC

This commit is contained in:
gac-x05la
2025-04-16 12:15:27 +02:00
parent 84bc0d692f
commit 5f5bf291a1
2 changed files with 165 additions and 143 deletions

View File

@@ -38,72 +38,72 @@ femto_mean_curr:
readOnly: true readOnly: true
softwareTrigger: false softwareTrigger: false
# es1_roty: es1_roty:
# readoutPriority: monitored readoutPriority: monitored
# description: 'Test rotation stage' description: 'Test rotation stage'
# deviceClass: ophyd.EpicsMotor deviceClass: ophyd.EpicsMotor
# deviceConfig: deviceConfig:
# prefix: X02DA-ES1-SMP1:ROTY prefix: X02DA-ES1-SMP1:ROTY
# deviceTags: deviceTags:
# - es1-sam - es1-sam
# onFailure: buffer onFailure: buffer
# enabled: true enabled: true
# readOnly: false readOnly: false
# softwareTrigger: false softwareTrigger: false
# es1_ismc: es1_ismc:
# description: 'Automation1 iSMC interface' description: 'Automation1 iSMC interface'
# deviceClass: tomcat_bec.devices.aa1Controller deviceClass: tomcat_bec.devices.aa1Controller
# deviceConfig: deviceConfig:
# prefix: 'X02DA-ES1-SMP1:CTRL:' prefix: 'X02DA-ES1-SMP1:CTRL:'
# deviceTags: deviceTags:
# - es1 - es1
# enabled: true enabled: true
# onFailure: buffer onFailure: buffer
# readOnly: false readOnly: false
# readoutPriority: monitored readoutPriority: monitored
# softwareTrigger: false softwareTrigger: false
# es1_tasks: es1_tasks:
# description: 'Automation1 task management interface' description: 'Automation1 task management interface'
# deviceClass: tomcat_bec.devices.aa1Tasks deviceClass: tomcat_bec.devices.aa1Tasks
# deviceConfig: deviceConfig:
# prefix: 'X02DA-ES1-SMP1:TASK:' prefix: 'X02DA-ES1-SMP1:TASK:'
# deviceTags: deviceTags:
# - es1 - es1
# enabled: false enabled: false
# onFailure: buffer onFailure: buffer
# readOnly: false readOnly: false
# readoutPriority: monitored readoutPriority: monitored
# softwareTrigger: false softwareTrigger: false
# es1_psod: es1_psod:
# description: 'AA1 PSO output interface (trigger)' description: 'AA1 PSO output interface (trigger)'
# deviceClass: tomcat_bec.devices.aa1AxisPsoDistance deviceClass: tomcat_bec.devices.aa1AxisPsoDistance
# deviceConfig: deviceConfig:
# prefix: 'X02DA-ES1-SMP1:ROTY:PSO:' prefix: 'X02DA-ES1-SMP1:ROTY:PSO:'
# deviceTags: deviceTags:
# - es1 - es1
# enabled: true enabled: true
# onFailure: buffer onFailure: buffer
# readOnly: false readOnly: false
# readoutPriority: monitored readoutPriority: monitored
# softwareTrigger: true softwareTrigger: true
# es1_ddaq: es1_ddaq:
# description: 'Automation1 position recording interface' description: 'Automation1 position recording interface'
# deviceClass: tomcat_bec.devices.aa1AxisDriveDataCollection deviceClass: tomcat_bec.devices.aa1AxisDriveDataCollection
# deviceConfig: deviceConfig:
# prefix: 'X02DA-ES1-SMP1:ROTY:DDC:' prefix: 'X02DA-ES1-SMP1:ROTY:DDC:'
# deviceTags: deviceTags:
# - es1 - es1
# enabled: true enabled: true
# onFailure: buffer onFailure: buffer
# readOnly: false readOnly: false
# readoutPriority: monitored readoutPriority: monitored
# softwareTrigger: false softwareTrigger: false
#camera: #camera:
@@ -119,25 +119,25 @@ femto_mean_curr:
# readoutPriority: monitored # readoutPriority: monitored
# softwareTrigger: true # softwareTrigger: true
gfcam: # gfcam:
description: GigaFrost camera client # description: GigaFrost camera client
deviceClass: tomcat_bec.devices.GigaFrostCamera # deviceClass: tomcat_bec.devices.GigaFrostCamera
deviceConfig: # deviceConfig:
prefix: 'X02DA-CAM-GF2:' # prefix: 'X02DA-CAM-GF2:'
backend_url: 'http://sls-daq-001:8080' # backend_url: 'http://sls-daq-001:8080'
auto_soft_enable: true # auto_soft_enable: true
std_daq_live: 'tcp://129.129.95.111:20000' # std_daq_live: 'tcp://129.129.95.111:20000'
std_daq_ws: 'ws://129.129.95.111:8080' # std_daq_ws: 'ws://129.129.95.111:8080'
std_daq_rest: 'http://129.129.95.111:5000' # std_daq_rest: 'http://129.129.95.111:5000'
deviceTags: # deviceTags:
- camera # - camera
- trigger # - trigger
- gfcam # - gfcam
enabled: true # enabled: true
onFailure: buffer # onFailure: buffer
readOnly: false # readOnly: false
readoutPriority: monitored # readoutPriority: monitored
softwareTrigger: true # softwareTrigger: true
# gfdaq: # gfdaq:
# description: GigaFrost stdDAQ client # description: GigaFrost stdDAQ client

View File

@@ -8,68 +8,17 @@ drive data collection (DDC) interface.
import time import time
from collections import OrderedDict 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.status import SubscriptionStatus
from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase as PSIDeviceBase from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
CustomDetectorMixin as CustomDeviceMixin,
)
from bec_lib import bec_logger from bec_lib import bec_logger
logger = bec_logger.logger logger = bec_logger.logger
class AerotechDriveDataCollectionMixin(CustomDeviceMixin): class aa1AxisDriveDataCollection(PSIDeviceBase, Device):
"""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):
"""Axis data collection """Axis data collection
This class provides convenience wrappers around the Aerotech API's axis This class provides convenience wrappers around the Aerotech API's axis
@@ -88,9 +37,10 @@ class aa1AxisDriveDataCollection(PSIDeviceBase):
... ...
ret = yield from ddc.collect() 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, 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) _buffer0 = Component(EpicsSignalRO, "BUFFER0", auto_monitor=True, kind=Kind.normal)
_buffer1 = Component(EpicsSignalRO, "BUFFER1", auto_monitor=True, kind=Kind.normal) _buffer1 = Component(EpicsSignalRO, "BUFFER1", auto_monitor=True, kind=Kind.normal)
custom_prepare_cls = AerotechDriveDataCollectionMixin USER_ACCESS = ["configure", "reset", "arm", "disarm"]
USER_ACCESS = ["configure", "reset"]
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: def configure(self, d: dict = None) -> tuple:
"""Configure data capture """Configure data capture
@@ -128,21 +101,68 @@ class aa1AxisDriveDataCollection(PSIDeviceBase):
if "num_points_total" in d: if "num_points_total" in d:
self.npoints.set(d["num_points_total"]).wait() self.npoints.set(d["num_points_total"]).wait()
if "ddc_trigger" in d: if "ddc_trigger" in d:
self._trigger.set(d['ddc_trigger']).wait() self._trigger.set(d["ddc_trigger"]).wait()
if "ddc_source0" in d: if "ddc_source0" in d:
self._input0.set(d['ddc_source0']).wait() self._input0.set(d["ddc_source0"]).wait()
if "ddc_source1" in d: if "ddc_source1" in d:
self._input1.set(d['ddc_source1']).wait() self._input1.set(d["ddc_source1"]).wait()
# Reset incremental readback # Reset incremental readback
self._switch.set("ResetRB", settle_time=0.1).wait() self._switch.set("ResetRB", settle_time=0.1).wait()
new = self.read_configuration() new = self.read_configuration()
return (old, new) 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""" """Bluesky-style stage"""
self._switch.set("Start", settle_time=0.2).wait() 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): def reset(self):
"""Reset incremental readback""" """Reset incremental readback"""
self._switch.set("ResetRB", settle_time=0.1).wait() self._switch.set("ResetRB", settle_time=0.1).wait()
@@ -164,20 +184,22 @@ class aa1AxisDriveDataCollection(PSIDeviceBase):
timestamp_ = timestamp timestamp_ = timestamp
return result return result
status = None
if index == 0: if index == 0:
status = SubscriptionStatus(self._readstatus0, neg_edge, settle_time=0.5) status = SubscriptionStatus(self._readstatus0, neg_edge, settle_time=0.5)
self._readback0.set(1).wait() self._readback0.set(1).wait()
elif index == 1: elif index == 1:
status = SubscriptionStatus(self._readstatus1, neg_edge, settle_time=0.5) status = SubscriptionStatus(self._readstatus1, neg_edge, settle_time=0.5)
self._readback1.set(1).wait() self._readback1.set(1).wait()
else:
raise RuntimeError(f"Unsupported drive data collection channel: {index}")
# Start asynchronous readback # Start asynchronous readback
status.wait() status.wait()
return status return status
def describe_collect(self) -> OrderedDict: def describe_collect(self) -> OrderedDict:
"""Describes collected array format according to JSONschema """Describes collected array format according to JSONschema"""
"""
ret = OrderedDict() ret = OrderedDict()
ret["buffer0"] = { ret["buffer0"] = {
"source": "internal", "source": "internal",