|
|
|
|
@@ -16,7 +16,7 @@ import time
|
|
|
|
|
import os
|
|
|
|
|
import jinja2
|
|
|
|
|
import numpy as np
|
|
|
|
|
from ophyd import Component, EpicsMotor, EpicsSignal
|
|
|
|
|
from ophyd import Component, EpicsMotor, EpicsSignal, EpicsSignalRO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from tomcat_bec.devices.aerotech import (
|
|
|
|
|
@@ -27,21 +27,22 @@ from tomcat_bec.devices.aerotech import (
|
|
|
|
|
aa1Tasks,
|
|
|
|
|
aa1AxisDriveDataCollection,
|
|
|
|
|
aa1AxisPsoDistance,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from tomcat_bec.devices.aerotech.AerotechAutomation1Enums import (
|
|
|
|
|
DriveDataCaptureInput,
|
|
|
|
|
DriveDataCaptureTrigger,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
os.environ["EPICS_CA_ADDR_LIST"] = "129.129.106.255"
|
|
|
|
|
|
|
|
|
|
os.environ["EPICS_CA_ADDR_LIST"] = "129.129.106.255 129.129.99.255"
|
|
|
|
|
AA1_IOC_NAME = "X02DA-ES1-SMP1"
|
|
|
|
|
AA1_AXIS_NAME = "ROTY"
|
|
|
|
|
MOTOR_TOL = 1.0
|
|
|
|
|
AA1_IOC_AXIS = "ROTY"
|
|
|
|
|
AA1_DRV_AXIS = "RY"
|
|
|
|
|
AXIS_VELOCITY = 42
|
|
|
|
|
AXIS_TRAVEL = 80
|
|
|
|
|
AXIS_TOL = 1.0
|
|
|
|
|
|
|
|
|
|
class AerotechMotor(EpicsMotor):
|
|
|
|
|
""" EpicsMotor extension with additional PVs"""
|
|
|
|
|
motor_enable_rbv = Component(EpicsSignalRO, "-EnaAct", kind="omitted", auto_monitor=True)
|
|
|
|
|
motor_enable = Component(EpicsSignal, ".CNEN", kind="omitted", auto_monitor=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -56,7 +57,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
# HIL testing only if there's hardware
|
|
|
|
|
dev = aa1Controller(AA1_IOC_NAME + ":CTRL:", name="aa1")
|
|
|
|
|
dev.wait_for_connection()
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="mot")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_IOC_AXIS, name="mot")
|
|
|
|
|
mot.wait_for_connection()
|
|
|
|
|
mot.motor_enable.set(1, settle_time=0.1).wait()
|
|
|
|
|
cls._isConnected = True
|
|
|
|
|
@@ -83,7 +84,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
print("TASK: Testing the 'run' command (run text with intermediary file)")
|
|
|
|
|
# Send another empty program
|
|
|
|
|
filename = "unittesting2.ascript"
|
|
|
|
|
filename = "test_program.ascript"
|
|
|
|
|
text = "program\nvar $positions[4] as real = [2, 4, 8, 16]\nend"
|
|
|
|
|
dev.configure({'script_file': filename, 'script_task': 3, 'script_text': text})
|
|
|
|
|
dev.arm()
|
|
|
|
|
@@ -120,21 +121,21 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
# # Attempt to run a short motion command using the Execute interface
|
|
|
|
|
print("TASK: Testing a short motion command")
|
|
|
|
|
text = """
|
|
|
|
|
text = f"""
|
|
|
|
|
var $fDist as real = 90
|
|
|
|
|
var $fVelo as real = 200
|
|
|
|
|
var $axis as axis = ROTY
|
|
|
|
|
var $fVelo as real = 60
|
|
|
|
|
var $axis as axis = {AA1_DRV_AXIS}
|
|
|
|
|
Enable($axis)
|
|
|
|
|
MoveLinear($axis, $fDist, $fVelo)
|
|
|
|
|
"""
|
|
|
|
|
dev.configure({'script_task': 3, 'script_text': text})
|
|
|
|
|
dev.configure({'script_task': 3, 'script_text': text, 'script_file': "test_shortmove.ascript"})
|
|
|
|
|
dev.arm()
|
|
|
|
|
t_start = time.time()
|
|
|
|
|
dev.kickoff()
|
|
|
|
|
dev.complete().wait()
|
|
|
|
|
t_end = time.time()
|
|
|
|
|
t_elapsed = t_end - t_start
|
|
|
|
|
self.assertTrue( 0 < t_elapsed < 4, f"Motion finished outside of window in: {t_elapsed}")
|
|
|
|
|
self.assertTrue( 1 < t_elapsed < 4, f"Motion finished outside of window in: {t_elapsed}")
|
|
|
|
|
dev.unstage()
|
|
|
|
|
|
|
|
|
|
print("TASK: Done testing TASK module!")
|
|
|
|
|
@@ -268,7 +269,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
$iglobal[1]={val_i}
|
|
|
|
|
$rglobal[1]={val_f}
|
|
|
|
|
"""
|
|
|
|
|
tsk.configure({'script_text': text, 'script_task': 4})
|
|
|
|
|
tsk.configure({'script_text': text, 'script_task': 4, 'script_file': "test_global.ascript"})
|
|
|
|
|
tsk.arm()
|
|
|
|
|
tsk.launch()
|
|
|
|
|
tsk.complete().wait()
|
|
|
|
|
@@ -285,19 +286,30 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
def test_05_axis_motorrecord(self):
|
|
|
|
|
"""Tests the basic move functionality of the MotorRecord"""
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="x")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_IOC_AXIS, name="x")
|
|
|
|
|
mot.wait_for_connection()
|
|
|
|
|
mot.motor_enable.set(1, settle_time=0.1).wait()
|
|
|
|
|
mot.velocity.set(200).wait()
|
|
|
|
|
mot.velocity.set(AXIS_VELOCITY).wait()
|
|
|
|
|
print("\nMR: Checking the standard EPICS motor record")
|
|
|
|
|
|
|
|
|
|
# Test enabling/disabling the axis
|
|
|
|
|
mot.motor_enable.set(1, settle_time=0.1).wait()
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
self.assertTrue(mot.motor_enable_rbv.value, "Motor should be enabled")
|
|
|
|
|
mot.motor_enable.set(0, settle_time=0.1).wait()
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
self.assertFalse(mot.motor_enable_rbv.value, "Motor should be disabled")
|
|
|
|
|
mot.motor_enable.set(1, settle_time=0.1).wait()
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
self.assertTrue(mot.motor_enable_rbv.value, "Motor should be enabled")
|
|
|
|
|
|
|
|
|
|
# Four test with absolute moves
|
|
|
|
|
def move_rel(dist):
|
|
|
|
|
target = mot.position + dist
|
|
|
|
|
print(f"Absolute move to {target}, distance: {dist}")
|
|
|
|
|
mot.move(target, wait=True)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(target - mot.position) < MOTOR_TOL,
|
|
|
|
|
np.abs(target - mot.position) < AXIS_TOL,
|
|
|
|
|
f"Final position {mot.position} is too far from target {target}",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@@ -311,7 +323,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
def test_06_axis_io(self):
|
|
|
|
|
"""Test axis IO bindings"""
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
dev = aa1AxisIo(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":IO:", name="io")
|
|
|
|
|
dev = aa1AxisIo(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":IO:", name="io")
|
|
|
|
|
dev.wait_for_connection()
|
|
|
|
|
|
|
|
|
|
# Writing random values to analog/digital outputs
|
|
|
|
|
@@ -338,9 +350,9 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
configuration, state monitoring and readback.
|
|
|
|
|
"""
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
dev = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
dev = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":DDC:", name="ddc")
|
|
|
|
|
event = EpicsSignal(
|
|
|
|
|
AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":PSO:EVENT:SINGLE", put_complete=True
|
|
|
|
|
AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":PSO:EVENT:SINGLE", put_complete=True
|
|
|
|
|
)
|
|
|
|
|
dev.wait_for_connection()
|
|
|
|
|
event.wait_for_connection()
|
|
|
|
|
@@ -464,9 +476,9 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
the PSO module and the polled feedback.
|
|
|
|
|
"""
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name=AA1_AXIS_NAME.lower())
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_IOC_AXIS, name=AA1_IOC_AXIS.lower())
|
|
|
|
|
mot.wait_for_connection()
|
|
|
|
|
pso.wait_for_connection()
|
|
|
|
|
ddc.wait_for_connection()
|
|
|
|
|
@@ -479,7 +491,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
# Configure steps and move to middle of range
|
|
|
|
|
pso.configure({"pso_distance": 5, "pso_wavemode": "toggle"})
|
|
|
|
|
pso.arm()
|
|
|
|
|
mot.velocity.set(200).wait()
|
|
|
|
|
mot.velocity.set(AXIS_VELOCITY).wait()
|
|
|
|
|
mot.move(mot.position + 2.5, wait=True)
|
|
|
|
|
|
|
|
|
|
ddc.arm()
|
|
|
|
|
@@ -527,7 +539,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
pso.disarm()
|
|
|
|
|
ddc.disarm()
|
|
|
|
|
|
|
|
|
|
print("AX:PSO: Checking basic 'pulsed' event generation with DDC (3/5)")
|
|
|
|
|
print("AX:PSO: Checking basic 'pulsed' event generation with DDC (3/3)")
|
|
|
|
|
# Configure steps and move to middle of range
|
|
|
|
|
pso.configure({"pso_distance": 1.8, "pso_wavemode": "pulsed", "pso_n_pulse": 3})
|
|
|
|
|
pso.arm()
|
|
|
|
|
@@ -565,9 +577,9 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
and compares them to the expected values.
|
|
|
|
|
"""
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="x")
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_IOC_AXIS, name="x")
|
|
|
|
|
mot.wait_for_connection()
|
|
|
|
|
pso.wait_for_connection()
|
|
|
|
|
ddc.wait_for_connection()
|
|
|
|
|
@@ -578,7 +590,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
"\ntest_09_PsoIntegration01: Integration test for vector PSO triggering (advanced)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print("AX:PSO: Testing standard 'enable' style PSO triggering with DDC (1/3)")
|
|
|
|
|
print("AX:PSO: Testing standard 'enable' style PSO triggering with DDC (1/2)")
|
|
|
|
|
# This is one line of the Tomcat sequence scan!
|
|
|
|
|
acc_dist = 0.5 * mot.velocity.value * mot.acceleration.value
|
|
|
|
|
vec_dist = [acc_dist, 18, 0.01, 18, 0.01, 18, 0.01, 18, 0.01, 18, 0.01, 18]
|
|
|
|
|
@@ -615,7 +627,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
pso.unstage()
|
|
|
|
|
ddc.unstage()
|
|
|
|
|
|
|
|
|
|
print("AX:PSO: Checking 'individual' style trigger signal with DDC (2/5)")
|
|
|
|
|
print("AX:PSO: Checking 'individual' style trigger signal with DDC (2/2)")
|
|
|
|
|
# This is practically golden ratio tomography!
|
|
|
|
|
acc_dist = 0.5 * mot.velocity.value * mot.acceleration.value
|
|
|
|
|
vec_dist = [acc_dist]
|
|
|
|
|
@@ -654,114 +666,16 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
pso.unstage()
|
|
|
|
|
ddc.unstage()
|
|
|
|
|
|
|
|
|
|
# def test_10_AxisPsoWindow1(self):
|
|
|
|
|
# """ Test basic PSO window mode functionality
|
|
|
|
|
|
|
|
|
|
# This module tests basic PSO distance functionality, like events and
|
|
|
|
|
# waveform generation. The goal is to ensure the basic operation of
|
|
|
|
|
# the PSO module and the polled feedback.
|
|
|
|
|
# """
|
|
|
|
|
# # Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
# pso = aa1AxisPsoWindow(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":PSO:", name="pso1")
|
|
|
|
|
# ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
# mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="x")
|
|
|
|
|
# mot.wait_for_connection()
|
|
|
|
|
# pso.wait_for_connection()
|
|
|
|
|
# ddc.wait_for_connection()
|
|
|
|
|
# pso.unstage()
|
|
|
|
|
# ddc.unstage()
|
|
|
|
|
# mot.unstage()
|
|
|
|
|
# print("\nAX:PSO: Checking axis PSO in window mode (basic)")
|
|
|
|
|
|
|
|
|
|
# print("AX:PSO: Directly checking basic window output (1/3)")
|
|
|
|
|
# # Configure steps and move to middle of range
|
|
|
|
|
# pso.configure({'bounds': (5, 13), 'pso_wavemode': "output"})
|
|
|
|
|
# time.sleep(1)
|
|
|
|
|
# # Move half a step
|
|
|
|
|
# mot.move(mot.position + 0.1)
|
|
|
|
|
# dist = 0.1
|
|
|
|
|
|
|
|
|
|
# # Start moving in steps, while checking output
|
|
|
|
|
# for ii in range(42):
|
|
|
|
|
# mot.move(mot.position + 0.2)
|
|
|
|
|
# dist += 0.2
|
|
|
|
|
# time.sleep(1)
|
|
|
|
|
# if 5 < dist < 13:
|
|
|
|
|
# self.assertTrue(pso.output.value == 1,
|
|
|
|
|
# f"Expected HIGH PSO output inside the window at distance {dist}")
|
|
|
|
|
# else:
|
|
|
|
|
# self.assertTrue(pso.output.value == 0,
|
|
|
|
|
# f"Expected LOW PSO output outside the window at distance {dist}")
|
|
|
|
|
|
|
|
|
|
# print("AX:PSO: The next tests fail due to encoder jitter!")
|
|
|
|
|
# pso.unstage()
|
|
|
|
|
# return
|
|
|
|
|
|
|
|
|
|
# print("AX:PSO: Checking basic window event generation with DDC (2/3)")
|
|
|
|
|
# # Move to acceleration position
|
|
|
|
|
# acc_dist = 0.5 * mot.velocity.value * mot.acceleration.value
|
|
|
|
|
# print(f"Acceleration distance: {acc_dist}")
|
|
|
|
|
# start_position = mot.position
|
|
|
|
|
# mot.move(start_position - acc_dist)
|
|
|
|
|
# # Configure PSO in window mode
|
|
|
|
|
# cfg = {'bounds': (acc_dist, 30 + acc_dist), 'pso_wavemode': "pulsed", "emode": 'Both'}
|
|
|
|
|
# dev.configure(cfg)
|
|
|
|
|
# # Configure the data capture (must be performed in start position)
|
|
|
|
|
# cfg = {'ntotal': 5*10, 'trigger': DriveDataCaptureTrigger.PsoOutput}
|
|
|
|
|
# ddc.configure(cfg)
|
|
|
|
|
# ddc.arm()
|
|
|
|
|
# npoints_rbv = ddc.nsamples_rbv.value
|
|
|
|
|
# self.assertEqual(npoints_rbv, 0,
|
|
|
|
|
# f"DDC.stage should reset the number of DDC points but got: {npoints_rbv}")
|
|
|
|
|
# # Repeated line scan
|
|
|
|
|
# for ii in range(10):
|
|
|
|
|
# dev.arm()
|
|
|
|
|
# mot.move(start_position + 30 + acc_dist)
|
|
|
|
|
# dev.unstage()
|
|
|
|
|
# mot.move(start_position - acc_dist)
|
|
|
|
|
# time.sleep(0.5)
|
|
|
|
|
# npoints_rbv = ddc.nsamples_rbv.value
|
|
|
|
|
# self.assertTrue(np.abs(npoints_rbv - 50) < 1,
|
|
|
|
|
# f"Expected to record 10 points but got {npoints_rbv}")
|
|
|
|
|
|
|
|
|
|
# print("AX:PSO: Checking basic 'toggled' event generation with DDC (3/3)")
|
|
|
|
|
# print("Test removed as there's some heavy jitter on the PSO output flag")
|
|
|
|
|
# # Move to acceleration position
|
|
|
|
|
# acc_dist = 0.5 * mot.velocity.value * mot.acceleration.value
|
|
|
|
|
# print(f"Acceleration distance: {acc_dist}")
|
|
|
|
|
# mot.move(mot.position - acc_dist)
|
|
|
|
|
# # Configure PSO in window mode
|
|
|
|
|
# cfg = {'bounds': (acc_dist, 30 + acc_dist + 1), 'pso_wavemode': "output"}
|
|
|
|
|
# dev.configure(cfg)
|
|
|
|
|
# dev.arm()
|
|
|
|
|
# # Configure the data capture
|
|
|
|
|
# cfg = {'ntotal': 10, 'trigger': DriveDataCaptureTrigger.PsoOutput}
|
|
|
|
|
# ddc.configure(cfg)
|
|
|
|
|
# ddc.arm()
|
|
|
|
|
# npoints_rbv = ddc.nsamples_rbv.value
|
|
|
|
|
# self.assertEqual(npoints_rbv, 0,
|
|
|
|
|
# f"DDC.stage should reset the number of DDC points but got: {npoints_rbv}")
|
|
|
|
|
# # Snake scan
|
|
|
|
|
# for ii in range(10):
|
|
|
|
|
# if ii % 2 == 0:
|
|
|
|
|
# mot.move(mot.position + 30 + 2 * acc_dist + 0.1)
|
|
|
|
|
# else:
|
|
|
|
|
# mot.move(mot.position - 30 - 2 * acc_dist - 0.1)
|
|
|
|
|
# time.sleep(0.5)
|
|
|
|
|
# npoints_rbv = ddc.nsamples_rbv.value
|
|
|
|
|
# self.assertTrue(np.abs(npoints_rbv - 10) < 1,
|
|
|
|
|
# f"Expected to record 10 points but got {npoints_rbv}")
|
|
|
|
|
|
|
|
|
|
def test_12_StepScan(self):
|
|
|
|
|
def test_10_StepScan(self):
|
|
|
|
|
"""Test a simple blusky style step scan (via BEC)
|
|
|
|
|
|
|
|
|
|
This module tests manual PSO trigger functionality. The goal is to
|
|
|
|
|
ensure the basic operation of the PSO module and the polled feedback.
|
|
|
|
|
"""
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="x")
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_IOC_AXIS, name="x")
|
|
|
|
|
mot.wait_for_connection()
|
|
|
|
|
pso.wait_for_connection()
|
|
|
|
|
ddc.wait_for_connection()
|
|
|
|
|
@@ -769,7 +683,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
ddc.disarm()
|
|
|
|
|
mot.unstage()
|
|
|
|
|
t_start = time.time()
|
|
|
|
|
print("\ntest_12_StepScan: Simple step scan (via Ophyd)")
|
|
|
|
|
print("\ntest_10_StepScan: Simple step scan (via Ophyd)")
|
|
|
|
|
|
|
|
|
|
pos_start = 112.0
|
|
|
|
|
scan_range = 180
|
|
|
|
|
@@ -787,7 +701,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
f"Expected to move to scan start position {pos_start}, now at {mot.position}",
|
|
|
|
|
)
|
|
|
|
|
# PSO setup (very large pulse distance)
|
|
|
|
|
pso.configure(d={"pso_n_pulse": 2, "pso_distance": 99999})
|
|
|
|
|
pso.configure(d={"pso_n_pulse": 2, "pso_distance": 9999})
|
|
|
|
|
# DDC setup (trigger at each point plus some extra)
|
|
|
|
|
cfg = {"num_points_total": 1000 * (scan_steps + 1), "ddc_trigger": DriveDataCaptureTrigger.PsoOutput}
|
|
|
|
|
ddc.configure(cfg)
|
|
|
|
|
@@ -800,7 +714,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
for ii in range(len(positions)):
|
|
|
|
|
mot.move(positions[ii])
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(mot.position - positions[ii]) < MOTOR_TOL,
|
|
|
|
|
np.abs(mot.position - positions[ii]) < AXIS_TOL,
|
|
|
|
|
f"Expected to reach {positions[ii]}, now is at {mot.position} on step {ii}",
|
|
|
|
|
)
|
|
|
|
|
pso.trigger()
|
|
|
|
|
@@ -824,7 +738,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
f"Expected to record {len(positions)} DDC points, got: {npoints_rbv}",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_13_TomcatSequenceScan(self):
|
|
|
|
|
def test_11_TomcatSequenceScan(self):
|
|
|
|
|
"""Test the zig-zag sequence scan from Tomcat (via BEC)
|
|
|
|
|
|
|
|
|
|
This module tests basic PSO distance functionality, like events and
|
|
|
|
|
@@ -832,9 +746,9 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
the PSO module and the polled feedback.
|
|
|
|
|
"""
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_AXIS_NAME, name="x")
|
|
|
|
|
pso = aa1AxisPsoDistance(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":PSO:", name="pso1")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":DDC:", name="ddc")
|
|
|
|
|
mot = AerotechMotor(AA1_IOC_NAME + ":" + AA1_IOC_AXIS, name="x")
|
|
|
|
|
mot.wait_for_connection()
|
|
|
|
|
pso.wait_for_connection()
|
|
|
|
|
ddc.wait_for_connection()
|
|
|
|
|
@@ -842,7 +756,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
ddc.disarm()
|
|
|
|
|
mot.unstage()
|
|
|
|
|
t_start = time.time()
|
|
|
|
|
print("\ntest_13_TomcatSequenceScan: TOMCAT Sequeence scan (via Ophyd)")
|
|
|
|
|
print("\ntest_11_TomcatSequenceScan: TOMCAT Sequeence scan (via Ophyd)")
|
|
|
|
|
|
|
|
|
|
scan_start = 42.0
|
|
|
|
|
scan_range = 180
|
|
|
|
|
@@ -850,14 +764,14 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
scan_type = "PosNeg"
|
|
|
|
|
|
|
|
|
|
# Dynamic properties
|
|
|
|
|
scan_vel = 90
|
|
|
|
|
scan_vel = AXIS_VELOCITY
|
|
|
|
|
scan_acc = 500
|
|
|
|
|
dist_safe = 10.0
|
|
|
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
print("\tConfigure")
|
|
|
|
|
# Scan range
|
|
|
|
|
dist_acc = 0.5 * scan_vel * scan_vel / scan_acc + dist_safe
|
|
|
|
|
dist_acc = 0.5 * AXIS_VELOCITY * AXIS_VELOCITY / scan_acc + dist_safe
|
|
|
|
|
if scan_type in ["PosNeg", "Pos"]:
|
|
|
|
|
pos_start = scan_start - dist_acc
|
|
|
|
|
pos_end = scan_start + scan_range + dist_acc
|
|
|
|
|
@@ -868,8 +782,8 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
raise RuntimeError(f"Unexpected ScanType: {scan_type}")
|
|
|
|
|
|
|
|
|
|
# Motor setup
|
|
|
|
|
mot.velocity.set(scan_vel).wait()
|
|
|
|
|
mot.acceleration.set(scan_vel / scan_acc).wait()
|
|
|
|
|
mot.velocity.set(AXIS_VELOCITY).wait()
|
|
|
|
|
mot.acceleration.set(AXIS_VELOCITY / scan_acc).wait()
|
|
|
|
|
mot.move(pos_start)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(mot.position - pos_start) < 1.0,
|
|
|
|
|
@@ -897,12 +811,12 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
if scan_type in ["Pos", "Neg"]:
|
|
|
|
|
mot.move(pos_end)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(mot.position - pos_end) < MOTOR_TOL,
|
|
|
|
|
np.abs(mot.position - pos_end) < AXIS_TOL,
|
|
|
|
|
f"Expected to reach {pos_end}, now is at {mot.position} on iteration {ii}",
|
|
|
|
|
)
|
|
|
|
|
mot.move(pos_start)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(mot.position - pos_start) < MOTOR_TOL,
|
|
|
|
|
np.abs(mot.position - pos_start) < AXIS_TOL,
|
|
|
|
|
f"Expected to rewind to {pos_start}, now at {mot.position} on iteration {ii}",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@@ -910,13 +824,13 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
if ii % 2 == 0:
|
|
|
|
|
mot.move(pos_end, wait=True)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(mot.position - pos_end) < MOTOR_TOL,
|
|
|
|
|
np.abs(mot.position - pos_end) < AXIS_TOL,
|
|
|
|
|
f"Expected to reach {pos_end}, now at {mot.position} on iteration {ii}",
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
mot.move(pos_start, wait=True)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
np.abs(mot.position - pos_start) < MOTOR_TOL,
|
|
|
|
|
np.abs(mot.position - pos_start) < AXIS_TOL,
|
|
|
|
|
f"Expected to reach {pos_start}, now at {mot.position} on iteration {ii}",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@@ -946,7 +860,7 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
f"Expected to record {num_repeat} DDC points, but got: {npoints_rbv}",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_14_JinjaTomcatSequenceScan(self):
|
|
|
|
|
def test_12_JinjaTomcatSequenceScan(self):
|
|
|
|
|
""" Run a test sequence scan with template substitution"""
|
|
|
|
|
# Load the test file
|
|
|
|
|
path = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
|
@@ -955,17 +869,18 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
templatetext = f.read()
|
|
|
|
|
|
|
|
|
|
scanconfig = {
|
|
|
|
|
"rotaxis": "RY",
|
|
|
|
|
"startpos": 42,
|
|
|
|
|
"scanrange": 180,
|
|
|
|
|
"nrepeat": 13,
|
|
|
|
|
"scandir": "NEGPOS",
|
|
|
|
|
"npoints": 1300,
|
|
|
|
|
"scanvel": 150,
|
|
|
|
|
"scanvel": AXIS_VELOCITY,
|
|
|
|
|
"scanacc": 500,
|
|
|
|
|
"accdist": 0.5*150*150/500,
|
|
|
|
|
"accdist": 0.5*AXIS_VELOCITY*AXIS_VELOCITY/500,
|
|
|
|
|
"psotrigger": "PsoEvent",
|
|
|
|
|
"psoBoundsPos": [0.5*150*150/500, 180],
|
|
|
|
|
"psoBoundsNeg": [0.5*150*150/500, 180]
|
|
|
|
|
"psoBoundsPos": [0.5*AXIS_VELOCITY*AXIS_VELOCITY/500, 180],
|
|
|
|
|
"psoBoundsNeg": [0.5*AXIS_VELOCITY*AXIS_VELOCITY/500, 180]
|
|
|
|
|
}
|
|
|
|
|
tm = jinja2.Template(templatetext)
|
|
|
|
|
text = tm.render(scan=scanconfig)
|
|
|
|
|
@@ -974,10 +889,10 @@ class AerotechAutomation1Test(unittest.TestCase):
|
|
|
|
|
|
|
|
|
|
# Throws if IOC is not available or PVs are incorrect
|
|
|
|
|
tsk = aa1Tasks(AA1_IOC_NAME + ":TASK:", name="tsk")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_AXIS_NAME + ":DDC:", name="ddc")
|
|
|
|
|
ddc = aa1AxisDriveDataCollection(AA1_IOC_NAME + ":" + AA1_IOC_AXIS + ":DDC:", name="ddc")
|
|
|
|
|
tsk.wait_for_connection()
|
|
|
|
|
ddc.wait_for_connection()
|
|
|
|
|
print("\nTOMCAT Sequeence scan (via Jinjad AeroScript)")
|
|
|
|
|
print("\ntest_12_JinjaTomcatSequenceScan: TOMCAT Sequeence scan (via Jinjad AeroScript)")
|
|
|
|
|
|
|
|
|
|
# Copy file to controller and run it
|
|
|
|
|
t_start = time.time()
|
|
|
|
|
|