This commit is contained in:
mohacsi_i 2024-03-12 10:56:30 +01:00
parent 7baf9ec118
commit 4b5a270071
2 changed files with 828 additions and 570 deletions

View File

@ -8,38 +8,72 @@ import time
try:
from .AerotechAutomation1Enums import *
from .AerotechAutomation1Enums import (DataCollectionMode, DataCollectionFrequency,
AxisDataSignal, PsoWindowInput, DriveDataCaptureInput, DriveDataCaptureTrigger,
TaskDataSignal, SystemDataSignal, TomcatSequencerState)
from .AerotechAutomation1Enums import (
DataCollectionMode,
DataCollectionFrequency,
AxisDataSignal,
PsoWindowInput,
DriveDataCaptureInput,
DriveDataCaptureTrigger,
TaskDataSignal,
SystemDataSignal,
TomcatSequencerState,
)
except:
from AerotechAutomation1Enums import *
from AerotechAutomation1Enums import (DataCollectionMode, DataCollectionFrequency,
AxisDataSignal, PsoWindowInput, DriveDataCaptureInput, DriveDataCaptureTrigger,
TaskDataSignal, SystemDataSignal, TomcatSequencerState)
from AerotechAutomation1Enums import (
DataCollectionMode,
DataCollectionFrequency,
AxisDataSignal,
PsoWindowInput,
DriveDataCaptureInput,
DriveDataCaptureTrigger,
TaskDataSignal,
SystemDataSignal,
TomcatSequencerState,
)
from typing import Union
from collections import OrderedDict
class EpicsMotorX(EpicsMotor):
""" Special motor class that provides flyer interface and progress bar.
"""
SUB_PROGRESS = "progress"
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
self._startPosition = None
self._targetPosition = None
self.subscribe(self._progress_update, run=False)
def configure(self, d: dict):
if "target" in d:
self._targetPosition = d['target']
del d['target']
self._targetPosition = d["target"]
del d["target"]
if "position" in d:
self._targetPosition = d['position']
del d['position']
self._targetPosition = d["position"]
del d["position"]
return super().configure(d)
def kickoff(self):
@ -53,19 +87,27 @@ class EpicsMotorX(EpicsMotor):
def _progress_update(self, value, **kwargs) -> None:
"""Progress update on the scan"""
if (self._startPosition is None) or (self._targetPosition is None) or (not self.moving):
self._run_subs( sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1, )
self._run_subs(
sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1,
)
return
progress = np.abs( (value-self._startPosition)/(self._targetPosition-self._startPosition) )
progress = np.abs(
(value - self._startPosition) / (self._targetPosition - self._startPosition)
)
max_value = 100
self._run_subs(
sub_type=self.SUB_PROGRESS,
value=int(100*progress), max_value=max_value, done=int(np.isclose(max_value, progress, 1e-3)), )
value=int(100 * progress),
max_value=max_value,
done=int(np.isclose(max_value, progress, 1e-3)),
)
class EpicsPassiveRO(EpicsSignalRO):
""" Small helper class to read PVs that need to be processed first.
"""
def __init__(self, read_pv, *, string=False, name=None, **kwargs):
super().__init__(read_pv, string=string, name=name, **kwargs)
self._proc = EpicsSignal(read_pv + ".PROC", kind=Kind.omitted, put_complete=True)
@ -83,10 +125,9 @@ class EpicsPassiveRO(EpicsSignalRO):
return super().value
class aa1Controller(Device):
"""Ophyd proxy class for the Aerotech Automation 1's core controller functionality"""
controllername = Component(EpicsSignalRO, "NAME", kind=Kind.config)
serialnumber = Component(EpicsSignalRO, "SN", kind=Kind.config)
apiversion = Component(EpicsSignalRO, "API_VERSION", kind=Kind.config)
@ -94,8 +135,28 @@ class aa1Controller(Device):
taskcount = Component(EpicsSignalRO, "TASKCOUNT", kind=Kind.config)
fastpoll = Component(EpicsSignalRO, "POLLTIME", auto_monitor=True, kind=Kind.hinted)
slowpoll = Component(EpicsSignalRO, "DRVPOLLTIME", auto_monitor=True, kind=Kind.hinted)
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
class aa1Tasks(Device):
""" Task management API
@ -127,6 +188,7 @@ class aa1Tasks(Device):
'''
"""
SUB_PROGRESS = "progress"
_failure = Component(EpicsSignalRO, "FAILURE", auto_monitor=True, kind=Kind.hinted)
errStatus = Component(EpicsSignalRO, "ERRW", auto_monitor=True, kind=Kind.hinted)
@ -140,11 +202,31 @@ class aa1Tasks(Device):
fileName = Component(EpicsSignal, "FILENAME", string=True, kind=Kind.omitted, put_complete=True)
_fileRead = Component(EpicsPassiveRO, "FILEREAD", string=True, kind=Kind.omitted)
_fileWrite = Component(EpicsSignal, "FILEWRITE", string=True, kind=Kind.omitted, put_complete=True)
_fileWrite = Component(
EpicsSignal, "FILEWRITE", string=True, kind=Kind.omitted, put_complete=True
)
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
""" __init__ MUST have a full argument list"""
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
self._currentTask = None
self._textToExecute = None
self._isConfigured = False
@ -154,7 +236,9 @@ class aa1Tasks(Device):
def _progress_update(self, value, **kwargs) -> None:
"""Progress update on the scan"""
value = self.progress()
self._run_subs( sub_type=self.SUB_PROGRESS, value=value, max_value=1, done=1, )
self._run_subs(
sub_type=self.SUB_PROGRESS, value=value, max_value=1, done=1,
)
def _progress(self) -> None:
"""Progress update on the scan"""
@ -185,7 +269,7 @@ class aa1Tasks(Device):
def runScript(self, filename: str, taskIndex: int == 2, filetext=None, settle_time=0.5) -> None:
""" Run a script file that either exists, or is newly created and compiled"""
self.configure({'text': filetext, 'filename': filename, 'taskIndex': taskIndex})
self.configure({"text": filetext, "filename": filename, "taskIndex": taskIndex})
print("Runscript configured")
self.trigger().wait()
print("Runscript waited")
@ -194,7 +278,7 @@ class aa1Tasks(Device):
""" Run a short text command on the Automation1 controller"""
print(f"Executing program on task: {taskIndex}")
self.configure({'text': text, 'taskIndex': taskIndex, 'mode': mode})
self.configure({"text": text, "taskIndex": taskIndex, "mode": mode})
self.kickoff().wait()
if mode in [0, "None", None]:
@ -207,12 +291,12 @@ class aa1Tasks(Device):
""" Configuration interface for flying
"""
# Unrolling the configuration dict
text = str(d['text']) if 'text' in d else None
filename = str(d['filename']) if 'filename' in d else None
taskIndex = int(d['taskIndex']) if 'taskIndex' in d else 4
settle_time = float(d['settle_time']) if 'settle_time' in d else None
mode = d['mode'] if 'mode' in d else None
self._isStepConfig = d['stepper'] if 'stepper' in d else False
text = str(d["text"]) if "text" in d else None
filename = str(d["filename"]) if "filename" in d else None
taskIndex = int(d["taskIndex"]) if "taskIndex" in d else 4
settle_time = float(d["settle_time"]) if "settle_time" in d else None
mode = d["mode"] if "mode" in d else None
self._isStepConfig = d["stepper"] if "stepper" in d else False
# Validation
if taskIndex < 1 or taskIndex > 31:
@ -302,6 +386,7 @@ class aa1Tasks(Device):
print("Called aa1Task.complete()")
timestamp_ = 0
taskIdx = int(self.taskIndex.get())
def notRunning2(*args, old_value, value, timestamp, **kwargs):
nonlocal timestamp_
result = False if value[taskIdx] in ["Running", 4] else True
@ -315,7 +400,14 @@ class aa1Tasks(Device):
def describe_collect(self) -> OrderedDict:
dd = OrderedDict()
dd['success'] = {'source': "internal", 'dtype': 'integer', 'shape': [], 'units': '', 'lower_ctrl_limit': 0, 'upper_ctrl_limit': 0}
dd["success"] = {
"source": "internal",
"dtype": "integer",
"shape": [],
"units": "",
"lower_ctrl_limit": 0,
"upper_ctrl_limit": 0,
}
return {self.name: dd}
def collect(self) -> OrderedDict:
@ -331,6 +423,7 @@ class aa1TaskState(Device):
This is the task state monitoring interface for Automation1 tasks. It
does not launch execution, but can wait for the execution to complete.
"""
index = Component(EpicsSignalRO, "INDEX", kind=Kind.config)
status = Component(EpicsSignalRO, "STATUS", auto_monitor=True, kind=Kind.hinted)
errorCode = Component(EpicsSignalRO, "ERROR", auto_monitor=True, kind=Kind.hinted)
@ -341,6 +434,7 @@ class aa1TaskState(Device):
print("Called aa1TaskState.complete()")
# Define wait until the busy flag goes down (excluding initial update)
timestamp_ = 0
def notRunning(*args, old_value, value, timestamp, **kwargs):
nonlocal timestamp_
result = False if (timestamp_ == 0) else (value not in ["Running", 4])
@ -359,7 +453,14 @@ class aa1TaskState(Device):
def describe_collect(self) -> OrderedDict:
dd = OrderedDict()
dd['success'] = {'source': "internal", 'dtype': 'integer', 'shape': [], 'units': '', 'lower_ctrl_limit': 0, 'upper_ctrl_limit': 0}
dd["success"] = {
"source": "internal",
"dtype": "integer",
"shape": [],
"units": "",
"lower_ctrl_limit": 0,
"upper_ctrl_limit": 0,
}
return dd
def collect(self) -> OrderedDict:
@ -381,6 +482,7 @@ class aa1DataAcquisition(Device):
3. Start your data collection
4. Read back the recorded data with "readback"
"""
# Status monitoring
status = Component(EpicsSignalRO, "RUNNING", auto_monitor=True, kind=Kind.hinted)
points_max = Component(EpicsSignal, "MAXPOINTS", kind=Kind.config, put_complete=True)
@ -486,7 +588,6 @@ class aa1DataAcquisition(Device):
return data
class aa1GlobalVariables(Device):
""" Global variables
@ -505,6 +606,7 @@ class aa1GlobalVariables(Device):
ret_arr = var.readFloat(1000, 1024)
"""
# Status monitoring
num_real = Component(EpicsSignalRO, "NUM-REAL_RBV", kind=Kind.config)
num_int = Component(EpicsSignalRO, "NUM-INT_RBV", kind=Kind.config)
@ -559,7 +661,6 @@ class aa1GlobalVariables(Device):
else:
raise RuntimeError("Unsupported integer value type: {type(value)}")
def readFloat(self, address: int, size: int = None) -> float:
""" Read a 64-bit double global variable """
if address > self.num_real.get():
@ -613,7 +714,6 @@ class aa1GlobalVariables(Device):
raise RuntimeError("Unsupported string value type: {type(value)}")
class aa1GlobalVariableBindings(Device):
""" Polled global variables
@ -621,30 +721,124 @@ class aa1GlobalVariableBindings(Device):
on the Automation1 controller. These variables are continuously polled
and are thus a convenient way to interface scripts with the outside word.
"""
int0 = Component(EpicsSignalRO, "INT0_RBV", auto_monitor=True, name="int0", kind=Kind.hinted)
int1 = Component(EpicsSignalRO, "INT1_RBV", auto_monitor=True, name="int1", kind=Kind.hinted)
int2 = Component(EpicsSignalRO, "INT2_RBV", auto_monitor=True, name="int2", kind=Kind.hinted)
int3 = Component(EpicsSignalRO, "INT3_RBV", auto_monitor=True, name="int3", kind=Kind.hinted)
int8 = Component(EpicsSignal, "INT8_RBV", put_complete=True, write_pv="INT8", auto_monitor=True, name="int8", kind=Kind.hinted)
int9 = Component(EpicsSignal, "INT9_RBV", put_complete=True, write_pv="INT9", auto_monitor=True, name="int9", kind=Kind.hinted)
int10 = Component(EpicsSignal, "INT10_RBV", put_complete=True, write_pv="INT10", auto_monitor=True, name="int10", kind=Kind.hinted)
int11 = Component(EpicsSignal, "INT11_RBV", put_complete=True, write_pv="INT11", auto_monitor=True, name="int11", kind=Kind.hinted)
int8 = Component(
EpicsSignal,
"INT8_RBV",
put_complete=True,
write_pv="INT8",
auto_monitor=True,
name="int8",
kind=Kind.hinted,
)
int9 = Component(
EpicsSignal,
"INT9_RBV",
put_complete=True,
write_pv="INT9",
auto_monitor=True,
name="int9",
kind=Kind.hinted,
)
int10 = Component(
EpicsSignal,
"INT10_RBV",
put_complete=True,
write_pv="INT10",
auto_monitor=True,
name="int10",
kind=Kind.hinted,
)
int11 = Component(
EpicsSignal,
"INT11_RBV",
put_complete=True,
write_pv="INT11",
auto_monitor=True,
name="int11",
kind=Kind.hinted,
)
float0 = Component(EpicsSignalRO, "REAL0_RBV", auto_monitor=True, name="float0", kind=Kind.hinted)
float1 = Component(EpicsSignalRO, "REAL1_RBV", auto_monitor=True, name="float1", kind=Kind.hinted)
float2 = Component(EpicsSignalRO, "REAL2_RBV", auto_monitor=True, name="float2", kind=Kind.hinted)
float3 = Component(EpicsSignalRO, "REAL3_RBV", auto_monitor=True, name="float3", kind=Kind.hinted)
float16 = Component(EpicsSignal, "REAL16_RBV", write_pv="REAL16", put_complete=True, auto_monitor=True, name="float16", kind=Kind.hinted)
float17 = Component(EpicsSignal, "REAL17_RBV", write_pv="REAL17", put_complete=True, auto_monitor=True, name="float17", kind=Kind.hinted)
float18 = Component(EpicsSignal, "REAL18_RBV", write_pv="REAL18", put_complete=True, auto_monitor=True, name="float18", kind=Kind.hinted)
float19 = Component(EpicsSignal, "REAL19_RBV", write_pv="REAL19", put_complete=True, auto_monitor=True, name="float19", kind=Kind.hinted)
float0 = Component(
EpicsSignalRO, "REAL0_RBV", auto_monitor=True, name="float0", kind=Kind.hinted
)
float1 = Component(
EpicsSignalRO, "REAL1_RBV", auto_monitor=True, name="float1", kind=Kind.hinted
)
float2 = Component(
EpicsSignalRO, "REAL2_RBV", auto_monitor=True, name="float2", kind=Kind.hinted
)
float3 = Component(
EpicsSignalRO, "REAL3_RBV", auto_monitor=True, name="float3", kind=Kind.hinted
)
float16 = Component(
EpicsSignal,
"REAL16_RBV",
write_pv="REAL16",
put_complete=True,
auto_monitor=True,
name="float16",
kind=Kind.hinted,
)
float17 = Component(
EpicsSignal,
"REAL17_RBV",
write_pv="REAL17",
put_complete=True,
auto_monitor=True,
name="float17",
kind=Kind.hinted,
)
float18 = Component(
EpicsSignal,
"REAL18_RBV",
write_pv="REAL18",
put_complete=True,
auto_monitor=True,
name="float18",
kind=Kind.hinted,
)
float19 = Component(
EpicsSignal,
"REAL19_RBV",
write_pv="REAL19",
put_complete=True,
auto_monitor=True,
name="float19",
kind=Kind.hinted,
)
# BEC LiveTable crashes on non-numeric values
str0 = Component(EpicsSignalRO, "STR0_RBV", auto_monitor=True, string=True, name="str0", kind=Kind.config)
str1 = Component(EpicsSignalRO, "STR1_RBV", auto_monitor=True, string=True, name="str1", kind=Kind.config)
str4 = Component(EpicsSignal, "STR4_RBV", put_complete=True, string=True, auto_monitor=True, write_pv="STR4", name="str4", kind=Kind.config)
str5 = Component(EpicsSignal, "STR5_RBV", put_complete=True, string=True, auto_monitor=True, write_pv="STR5", name="str5", kind=Kind.config)
str0 = Component(
EpicsSignalRO, "STR0_RBV", auto_monitor=True, string=True, name="str0", kind=Kind.config
)
str1 = Component(
EpicsSignalRO, "STR1_RBV", auto_monitor=True, string=True, name="str1", kind=Kind.config
)
str4 = Component(
EpicsSignal,
"STR4_RBV",
put_complete=True,
string=True,
auto_monitor=True,
write_pv="STR4",
name="str4",
kind=Kind.config,
)
str5 = Component(
EpicsSignal,
"STR5_RBV",
put_complete=True,
string=True,
auto_monitor=True,
write_pv="STR5",
name="str5",
kind=Kind.config,
)
class aa1AxisIo(Device):
@ -655,6 +849,7 @@ class aa1AxisIo(Device):
should be done in AeroScript. Only one pin can be writen directly but
several can be polled!
"""
polllvl = Component(EpicsSignal, "POLLLVL", put_complete=True, kind=Kind.config)
ai0 = Component(EpicsSignalRO, "AI0-RBV", auto_monitor=True, kind=Kind.hinted)
ai1 = Component(EpicsSignalRO, "AI1-RBV", auto_monitor=True, kind=Kind.hinted)
@ -692,7 +887,6 @@ class aa1AxisIo(Device):
self.do.set(value, settle_time=settle_time).wait()
class aa1AxisPsoBase(Device):
""" Position Sensitive Output - Base class
@ -708,6 +902,7 @@ class aa1AxisPsoBase(Device):
Specific operation modes should be implemented in child classes.
"""
# ########################################################################
# General module status
status = Component(EpicsSignalRO, "STATUS", auto_monitor=True, kind=Kind.hinted)
@ -722,7 +917,9 @@ class aa1AxisPsoBase(Device):
dstCounterEna = Component(EpicsSignal, "DIST:COUNTER", put_complete=True, kind=Kind.omitted)
dstCounterVal = Component(EpicsSignalRO, "DIST:CTR0_RBV", auto_monitor=True, kind=Kind.hinted)
dstArrayIdx = Component(EpicsSignalRO, "DIST:IDX_RBV", auto_monitor=True, kind=Kind.hinted)
dstArrayDepleted = Component(EpicsSignalRO, "DIST:ARRAY-DEPLETED-RBV", auto_monitor=True, kind=Kind.hinted)
dstArrayDepleted = Component(
EpicsSignalRO, "DIST:ARRAY-DEPLETED-RBV", auto_monitor=True, kind=Kind.hinted
)
dstDirection = Component(EpicsSignal, "DIST:EVENTDIR", put_complete=True, kind=Kind.omitted)
dstDistance = Component(EpicsSignal, "DIST:DISTANCE", put_complete=True, kind=Kind.hinted)
@ -767,9 +964,6 @@ class aa1AxisPsoBase(Device):
self.waveMode.set(orig_waveMode).wait()
class aa1AxisPsoDistance(aa1AxisPsoBase):
""" Position Sensitive Output - Distance mode
@ -802,11 +996,30 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
pso.configure(d={'distance': 1.8, 'wmode': "pulsed", 'n_pulse': 5})
pso.kickoff().wait()
"""
SUB_PROGRESS = "progress"
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
""" __init__ MUST have a full argument list"""
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
self._Vdistance = 3.141592
self.subscribe(self._progress_update, "progress", run=False)
@ -814,7 +1027,9 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
"""Progress update on the scan"""
if self.dstArrayDepleted.value:
print("PSO array depleted")
self._run_subs( sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1, )
self._run_subs(
sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1,
)
return
progress = 1
@ -822,7 +1037,10 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
print(f"PSO array proggress: {progress}")
self._run_subs(
sub_type=self.SUB_PROGRESS,
value=int(progress), max_value=max_value, done=int(np.isclose(max_value, progress, 1e-3)), )
value=int(progress),
max_value=max_value,
done=int(np.isclose(max_value, progress, 1e-3)),
)
# ########################################################################
# PSO high level interface
@ -833,12 +1051,11 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
:param distance: The trigger distance or the array of distances between subsequent points.
:param wmode: Waveform mode configuration, usually pulsed/toggled.
"""
distance = d['distance']
wmode = str(d['wmode'])
t_pulse = float(d['t_pulse']) if 't_pulse' in d else 100
w_pulse = float(d['w_pulse']) if 'w_pulse' in d else 200
n_pulse = float(d['n_pulse']) if 'n_pulse' in d else 1
distance = d["distance"]
wmode = str(d["wmode"])
t_pulse = float(d["t_pulse"]) if "t_pulse" in d else 100
w_pulse = float(d["w_pulse"]) if "w_pulse" in d else 200
n_pulse = float(d["n_pulse"]) if "n_pulse" in d else 1
# Validate input parameters
if wmode not in ["pulse", "pulsed", "toggle", "toggled"]:
@ -908,11 +1125,14 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
self.dstEventsEna.set("Off").wait()
self.dstCounterEna.set("Off").wait()
return super().unstage()
# ########################################################################
# Bluesky flyer interface
def kickoff(self) -> DeviceStatus:
# Rearm the configured array
if hasattr(self, "_distanceValue") and isinstance(self._distanceValue, (np.ndarray, list, tuple)):
if hasattr(self, "_distanceValue") and isinstance(
self._distanceValue, (np.ndarray, list, tuple)
):
self.dstArrayRearm.set(1).wait()
# Start monitoring the counters
self.dstEventsEna.set("On").wait()
@ -925,9 +1145,12 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
def complete(self) -> DeviceStatus:
""" Bluesky flyer interface"""
# Array mode waits until the buffer is empty
if hasattr(self, "_distanceValue") and isinstance(self._distanceValue, (np.ndarray, list, tuple)):
if hasattr(self, "_distanceValue") and isinstance(
self._distanceValue, (np.ndarray, list, tuple)
):
# Define wait until the busy flag goes down (excluding initial update)
timestamp_ = 0
def notRunning(*args, old_value, value, timestamp, **kwargs):
nonlocal timestamp_
result = False if (timestamp_ == 0) else bool(int(value) & 0x1000)
@ -948,7 +1171,14 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
def describe_collect(self) -> OrderedDict:
ret = OrderedDict()
ret['index'] = {'source': "internal", 'dtype': 'integer', 'shape': [], 'units': '', 'lower_ctrl_limit': 0, 'upper_ctrl_limit': 0}
ret["index"] = {
"source": "internal",
"dtype": "integer",
"shape": [],
"units": "",
"lower_ctrl_limit": 0,
"upper_ctrl_limit": 0,
}
return {self.name: ret}
def collect(self) -> OrderedDict:
@ -958,8 +1188,6 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
yield ret
class aa1AxisPsoWindow(aa1AxisPsoBase):
""" Position Sensitive Output - Window mode
@ -979,9 +1207,28 @@ class aa1AxisPsoWindow(aa1AxisPsoBase):
For a more detailed description of additional signals and masking plase
refer to Automation1's online manual.
"""
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
""" __init__ MUST have a full argument list"""
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
self._mode = "output"
self._eventMode = "Enter"
@ -994,12 +1241,12 @@ class aa1AxisPsoWindow(aa1AxisPsoBase):
:param distance: The trigger distance or the array of distances between subsequent points.
:param wmode: Waveform mode configuration, usually output/pulsed/toggled.
"""
bounds = d['distance']
wmode = str(d['wmode'])
emode = str(d['emode'])
t_pulse = float(d['t_pulse']) if 't_pulse' in d else None
w_pulse = float(d['w_pulse']) if 'w_pulse' in d else None
n_pulse = float(d['n_pulse']) if 'n_pulse' in d else None
bounds = d["distance"]
wmode = str(d["wmode"])
emode = str(d["emode"])
t_pulse = float(d["t_pulse"]) if "t_pulse" in d else None
w_pulse = float(d["w_pulse"]) if "w_pulse" in d else None
n_pulse = float(d["n_pulse"]) if "n_pulse" in d else None
# Validate input parameters
if wmode not in ["pulse", "pulsed", "toggle", "toggled", "output", "flag"]:
@ -1080,11 +1327,6 @@ class aa1AxisPsoWindow(aa1AxisPsoBase):
return super().unstage()
class aa1AxisDriveDataCollection(Device):
""" Axis data collection
@ -1125,29 +1367,50 @@ class aa1AxisDriveDataCollection(Device):
SUB_PROGRESS = "progress"
def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs)
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
self.subscribe(self._progress_update, "progress", run=False)
def _progress_update(self, value, **kwargs) -> None:
"""Progress update on the scan"""
if self.state.value not in (2, "Acquiring"):
self._run_subs( sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1, )
self._run_subs(
sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1,
)
return
progress = 1
max_value = 1
self._run_subs(
sub_type=self.SUB_PROGRESS,
value=int(progress), max_value=max_value, done=int(np.isclose(max_value, progress, 1e-3)), )
value=int(progress),
max_value=max_value,
done=int(np.isclose(max_value, progress, 1e-3)),
)
def configure(self, d: dict = {}) -> tuple:
npoints = int(d['npoints'])
trigger = int(d['trigger']) if 'trigger' in d else DriveDataCaptureTrigger.PsoOutput
source0 = int(d['source0']) if 'source0' in d else DriveDataCaptureInput.PrimaryFeedback
source1 = int(d['source1']) if 'source1' in d else DriveDataCaptureInput.PositionCommand
npoints = int(d["npoints"])
trigger = int(d["trigger"]) if "trigger" in d else DriveDataCaptureTrigger.PsoOutput
source0 = int(d["source0"]) if "source0" in d else DriveDataCaptureInput.PrimaryFeedback
source1 = int(d["source1"]) if "source1" in d else DriveDataCaptureInput.PositionCommand
old = self.read_configuration()
@ -1194,6 +1457,7 @@ class aa1AxisDriveDataCollection(Device):
# Define wait until the busy flag goes down (excluding initial update)
timestamp_ = 0
def negEdge(*args, old_value, value, timestamp, **kwargs):
nonlocal timestamp_
result = False if (timestamp_ == 0) else (old_value == 1 and value == 0)
@ -1212,11 +1476,24 @@ class aa1AxisDriveDataCollection(Device):
status.wait()
return status
def describe_collect(self) -> OrderedDict:
ret = OrderedDict()
ret['buffer0'] = {'source': "internal", 'dtype': 'array', 'shape': [], 'units': '', 'lower_ctrl_limit': 0, 'upper_ctrl_limit': 0}
ret['buffer1'] = {'source': "internal", 'dtype': 'array', 'shape': [], 'units': '', 'lower_ctrl_limit': 0, 'upper_ctrl_limit': 0}
ret["buffer0"] = {
"source": "internal",
"dtype": "array",
"shape": [],
"units": "",
"lower_ctrl_limit": 0,
"upper_ctrl_limit": 0,
}
ret["buffer1"] = {
"source": "internal",
"dtype": "array",
"shape": [],
"units": "",
"lower_ctrl_limit": 0,
"upper_ctrl_limit": 0,
}
return {self.name: ret}
def collect(self) -> OrderedDict:
@ -1232,9 +1509,6 @@ class aa1AxisDriveDataCollection(Device):
yield ret
# Automatically start simulation if directly invoked
if __name__ == "__main__":
AA1_IOC_NAME = "X02DA-ES1-SMP1"
@ -1250,6 +1524,3 @@ if __name__ == "__main__":
globb.describe()
mot = EpicsMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="x")
mot.wait_for_connection()

View File

@ -383,9 +383,6 @@ class TaskDataSignal:
CommandQueueExecutedCount = 424
class SystemDataSignal:
VirtualBinaryInput = 46
VirtualBinaryOutput = 47
@ -442,7 +439,6 @@ class SystemDataSignal:
ModbusServerEnabled = 439
class DataCollectionFrequency:
Undefined = 0
Fixed1kHz = 1
@ -482,11 +478,12 @@ class PsoWindowInput:
XC4eSyncPortA = 137
XC4eSyncPortB = 138
XC4eDrivePulseStream = 139
XL5ePrimaryFeedback = 145,
XL5eAuxiliaryFeedback = 146,
XL5eSyncPortA = 147,
XL5eSyncPortB = 148,
XL5eDrivePulseStream = 149,
XL5ePrimaryFeedback = (145,)
XL5eAuxiliaryFeedback = (146,)
XL5eSyncPortA = (147,)
XL5eSyncPortB = (148,)
XL5eDrivePulseStream = (149,)
# @brief Specifies the PSO output pin settings for each drive.
class XC4ePsoOutputPin:
@ -494,6 +491,7 @@ class XC4ePsoOutputPin:
AuxiliaryMarkerDifferential = 112
AuxiliaryMarkerSingleEnded = 113
class XC4PsoOutputPin:
DedicatedOutput = 108
AuxiliaryMarkerDifferential = 109
@ -854,61 +852,50 @@ class DriveDataCaptureTrigger:
AuxiliaryMarkerFallingEdge = 7
class PsoOutputPin:
GL4None = 100,
GL4LaserOutput0 = 101,
XL4sNone = 102,
XL4sLaserOutput0 = 103,
XR3None = 104,
XR3PsoOutput1 = 105,
XR3PsoOutput2 = 106,
XR3PsoOutput3 = 107,
XC4DedicatedOutput = 108,
XC4AuxiliaryMarkerDifferential = 109,
XC4AuxiliaryMarkerSingleEnded = 110,
XC4eDedicatedOutput = 111,
XC4eAuxiliaryMarkerDifferential = 112,
XC4eAuxiliaryMarkerSingleEnded = 113,
XC6eDedicatedOutput = 114,
XC6eAuxiliaryMarkerDifferential = 115,
XC6eAuxiliaryMarkerSingleEnded = 116,
XL5eDedicatedOutput = 117,
XL5eAuxiliaryMarkerDifferential = 118,
XL5eAuxiliaryMarkerSingleEnded = 119,
XC2DedicatedOutput = 120,
XC2eDedicatedOutput = 121,
XL2eDedicatedOutput = 122,
XI4DedicatedOutput = 123,
iXC4DedicatedOutput = 124,
iXC4AuxiliaryMarkerDifferential = 125,
iXC4AuxiliaryMarkerSingleEnded = 126,
iXC4eDedicatedOutput = 127,
iXC4eAuxiliaryMarkerDifferential = 128,
iXC4eAuxiliaryMarkerSingleEnded = 129,
iXC6eDedicatedOutput = 130,
iXC6eAuxiliaryMarkerDifferential = 131,
iXC6eAuxiliaryMarkerSingleEnded = 132,
iXL5eDedicatedOutput = 133,
iXL5eAuxiliaryMarkerDifferential = 134,
iXL5eAuxiliaryMarkerSingleEnded = 135,
iXR3None = 136,
iXR3PsoOutput1 = 137,
iXR3PsoOutput2 = 138,
iXR3PsoOutput3 = 139,
GI4None = 140,
GI4LaserOutput0 = 141,
iXC2DedicatedOutput = 142,
iXC2eDedicatedOutput = 143,
iXL2eDedicatedOutput = 144,
iXI4DedicatedOutput = 145,
GL4None = (100,)
GL4LaserOutput0 = (101,)
XL4sNone = (102,)
XL4sLaserOutput0 = (103,)
XR3None = (104,)
XR3PsoOutput1 = (105,)
XR3PsoOutput2 = (106,)
XR3PsoOutput3 = (107,)
XC4DedicatedOutput = (108,)
XC4AuxiliaryMarkerDifferential = (109,)
XC4AuxiliaryMarkerSingleEnded = (110,)
XC4eDedicatedOutput = (111,)
XC4eAuxiliaryMarkerDifferential = (112,)
XC4eAuxiliaryMarkerSingleEnded = (113,)
XC6eDedicatedOutput = (114,)
XC6eAuxiliaryMarkerDifferential = (115,)
XC6eAuxiliaryMarkerSingleEnded = (116,)
XL5eDedicatedOutput = (117,)
XL5eAuxiliaryMarkerDifferential = (118,)
XL5eAuxiliaryMarkerSingleEnded = (119,)
XC2DedicatedOutput = (120,)
XC2eDedicatedOutput = (121,)
XL2eDedicatedOutput = (122,)
XI4DedicatedOutput = (123,)
iXC4DedicatedOutput = (124,)
iXC4AuxiliaryMarkerDifferential = (125,)
iXC4AuxiliaryMarkerSingleEnded = (126,)
iXC4eDedicatedOutput = (127,)
iXC4eAuxiliaryMarkerDifferential = (128,)
iXC4eAuxiliaryMarkerSingleEnded = (129,)
iXC6eDedicatedOutput = (130,)
iXC6eAuxiliaryMarkerDifferential = (131,)
iXC6eAuxiliaryMarkerSingleEnded = (132,)
iXL5eDedicatedOutput = (133,)
iXL5eAuxiliaryMarkerDifferential = (134,)
iXL5eAuxiliaryMarkerSingleEnded = (135,)
iXR3None = (136,)
iXR3PsoOutput1 = (137,)
iXR3PsoOutput2 = (138,)
iXR3PsoOutput3 = (139,)
GI4None = (140,)
GI4LaserOutput0 = (141,)
iXC2DedicatedOutput = (142,)
iXC2eDedicatedOutput = (143,)
iXL2eDedicatedOutput = (144,)
iXI4DedicatedOutput = (145,)