Added tests for new EL734 driver

This commit is contained in:
Stefan Mathis
2025-11-03 16:31:18 +01:00
parent 7ab4486748
commit 9cab5b82d9
22 changed files with 293 additions and 88 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Ignore all .pyc files
*.pyc
__pycache__
# Ignore the EPICS environment variables (this file is recreated by ioc/startioc.py via config.yaml)
config.cmd

View File

@@ -1,7 +1,8 @@
pvprefix: DRVTESTS
versions:
turboPmac: "1.3.1"
masterMacs: "1.1"
turboPmac: "1.5"
masterMacs: "1.4"
el734: "mathis_s"
controllers:
turboPmac1:
ip: "172.28.101.24"
@@ -11,5 +12,10 @@ controllers:
masterMacs1:
ip: "172.28.101.66"
port: 1912
busypoll: 0.05
busypoll: 0.10
idlepoll: 1
el734_1:
ip: "sinqtest-ts"
port: 3002
busypoll: 0.5
idlepoll: 0.5

12
ioc/motors/el734_1.cmd Normal file
View File

@@ -0,0 +1,12 @@
epicsEnvSet("NAME","el734_1")
epicsEnvSet("ASYN_PORT","p$(NAME)")
drvAsynIPPortConfigure("$(ASYN_PORT)", "sinqtest-ts:3002")
el734Controller("$(NAME)","$(ASYN_PORT)",8,0.5,0.5,10);
el734Axis("$(NAME)",1);
epicsEnvSet("SINQDBPATH","$(el734_DB)/sinqMotor.db")
dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(el734_DB)/el734.db")
dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)")
# IOC shell does not execute the last line, hence an empty line at the end is needed

View File

@@ -0,0 +1,6 @@
file $(SINQDBPATH)
{
pattern
{ AXIS, M, EGU, DIR, MRES, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED }
{ 1, "ax1", mm, Pos, 0.001, 1, 1.0, 1 }
}

View File

@@ -1,5 +1,3 @@
# Configuration for the "modern" masterMacs-Axes
epicsEnvSet("NAME","masterMacs1")
epicsEnvSet("ASYN_PORT","p$(NAME)")
@@ -12,3 +10,4 @@ masterMacsAxis("$(NAME)",2);
epicsEnvSet("SINQDBPATH","$(masterMacs_DB)/sinqMotor.db")
dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)")
# IOC shell does not execute the last line, hence an empty line at the end is needed

View File

@@ -17,3 +17,4 @@ epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/sinqMotor.db")
dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/turboPmac.db")
dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)")
# IOC shell does not execute the last line, hence an empty line at the end is needed

View File

@@ -5,6 +5,7 @@
require turboPmac, $(TURBOPMAC_VERSION)
require masterMacs, $(MASTERMACS_VERSION)
require el734, $(EL734_VERSION)
################################################################################
# Motors
@@ -12,6 +13,7 @@ require masterMacs, $(MASTERMACS_VERSION)
# Initialize the motors itself
< motors/turboPmac1.cmd
< motors/masterMacs1.cmd
< motors/el734_1.cmd
# Create the test record which is used to detect if the IOC is running
dbLoadRecords("$(IOCDIR)/db/ready.db", "PVPREFIX=$(PVPREFIX)")

View File

@@ -30,6 +30,8 @@ def startioc():
f'epicsEnvSet("TURBOPMAC_VERSION", "{config["versions"]["turboPmac"]}")\n')
out.write(
f'epicsEnvSet("MASTERMACS_VERSION", "{config["versions"]["masterMacs"]}")\n')
out.write(
f'epicsEnvSet("EL734_VERSION", "{config["versions"]["el734"]}")\n')
out.write(
f'epicsEnvSet("TURBOPMAC1_IP", "{config["controllers"]["turboPmac1"]["ip"]}")\n')
@@ -49,6 +51,15 @@ def startioc():
out.write(
f'epicsEnvSet("MASTERMACS1_IDLEPOLL", "{config["controllers"]["masterMacs1"]["idlepoll"]}")\n')
out.write(
f'epicsEnvSet("EL734_1_IP", "{config["controllers"]["el734_1"]["ip"]}")\n')
out.write(
f'epicsEnvSet("EL734_1_PORT", "{config["controllers"]["el734_1"]["port"]}")\n')
out.write(
f'epicsEnvSet("EL734_1_BUSYPOLL", "{config["controllers"]["el734_1"]["busypoll"]}")\n')
out.write(
f'epicsEnvSet("EL734_1_IDLEPOLL", "{config["controllers"]["el734_1"]["idlepoll"]}")\n')
# Set environment variables
os.environ["EPICS_BASE"] = "/usr/local/epics/base-7.0.7"
os.environ["EPICS_HOST_ARCH"] = "RHEL8-x86_64"

View File

@@ -36,7 +36,7 @@ for arg in sys.argv[1:]:
extra_args.append(arg)
# Find all axes paths (pa) within all controller paths (pc)
folders =[]
folders = []
for pc in Path("tests").glob("*"):
if pc.is_dir() and "__pycache__" not in pc.parts:
for pa in Path(pc).glob("*"):
@@ -51,9 +51,12 @@ if not path_args:
enabled_folders = folders
else:
for folder in folders:
if any(Path(arg).resolve().as_posix().startswith(Path(folder).resolve().as_posix()) for arg in path_args):
if any(Path(folder).resolve().as_posix().startswith(Path(arg).resolve().as_posix()) for arg in path_args):
enabled_folders.append(folder)
if not enabled_folders:
print(f"No tests were found within the specified path {path_args}")
# Check if the IOC is running and try to start it, if it isn't
try:
check_ioc_running()
@@ -77,14 +80,7 @@ except IocNotRunning:
# Run each enabled folder's relevant tests in parallel
processes = []
for folder in enabled_folders:
folder_path_args = (
[arg for arg in path_args if Path(arg).resolve().as_posix().startswith(Path(folder).resolve().as_posix())]
if path_args else [folder.as_posix()]
)
command = ["pytest"] + folder_path_args + extra_args
print(f"Running: {' '.join(command)}")
command = ["pytest"] + [str(folder)] + extra_args
proc = subprocess.Popen(command)
processes.append(proc)

View File

@@ -285,3 +285,7 @@ class TurboPMAC(SinqMotor):
class MasterMACS(SinqMotor):
pass
class EL734(SinqMotor):
pass

View File

@@ -6,9 +6,11 @@ def home(motor, forward):
encoder = motor.get_pv('encoder_type', as_string=True)
if encoder == 'absolute':
is_absolute = True
can_home = False
elif encoder == 'incremental':
is_absolute = False
can_home = True
elif encoder == 'none':
can_home = False
else:
raise ValueError(f'Unknown encoder type {encoder}')
@@ -21,16 +23,7 @@ def home(motor, forward):
motor.wait_for_start()
if is_absolute:
# EPICS initially assumes the motor is moving and sets it into moving state.
# However, after the first poll it should realize that the motor is in fact not moving.
time.sleep(2 * motor.busypoll)
# Motor should not move at all
assert motor.get_pv('moving') == 0
assert motor.get_pv('donemoving') == 1
assert not motor.has_error()
else:
if can_home:
# The controller might take a bit of time to actually start the movement,
# therefore we wait a bit.
time.sleep(2 * motor.busypoll)
@@ -48,6 +41,15 @@ def home(motor, forward):
assert motor.get_pv('donemoving') == 1
assert not motor.has_error()
assert motor.is_homed()
else:
# EPICS initially assumes the motor is moving and sets it into moving state.
# However, after the first poll it should realize that the motor is in fact not moving.
time.sleep(3 * motor.busypoll)
# Motor should not move at all
assert motor.get_pv('moving') == 0
assert motor.get_pv('donemoving') == 1
assert not motor.has_error()
def start_home_while_moving(motor, target, forward):
"""

View File

@@ -1,3 +1,5 @@
import time
def reset(motor, target):
"""
Reset the motor for the next test. This means the following things:
@@ -7,9 +9,18 @@ def reset(motor, target):
4) Moving to zero
"""
# Wait a bit before starting each test so the IOC has some time
# to recover from any previous failed tests.
time.sleep(0.2)
if target is None:
target = motor.default_position
motor.put_pv('stop', 1)
motor.wait_for_done()
motor.put_pv('reseterrorpv', 1)
motor.put_pv('enable', 1)
motor.wait_disabled()
motor.enable_and_wait()
motor.move_and_wait(target)
assert motor.at_target(target)
assert not motor.has_error()

View File

@@ -21,7 +21,7 @@ def check_ioc_running():
return
except TimeoutError:
# IOC startup failed in the given time -> Raise an error
path = Path.cwd() / 'startioc'
path = Path.cwd() / 'ioc' / 'startioc'
raise IocNotRunning(f'Start the test IOC first by running {path}')
@pytest.fixture(autouse=True)

View File

View File

View File

@@ -0,0 +1,19 @@
# This module defines fixtures which are shared for all tests of motor "lin1".
import time
import pytest
from setup.classes import EL734
# Prepare the motor at the start of the test by resetting and homing it.
@pytest.fixture(scope="session", autouse=True)
def reset_and_home():
mot = EL734('el734_1', 'ax1')
mot.put_pv('stop', 1)
mot.put_pv('reseterrorpv', 1)
mot.put_pv('homeforward', 1)
time.sleep(1)
mot.wait_for_done()
@pytest.fixture(autouse=True)
def motor():
return EL734('el734_1', 'ax1')

View File

@@ -0,0 +1,123 @@
# Run a selection of common tests
import pytest
from setup.move import *
from setup.home import *
from setup.sinqMotor.limits import *
from setup.sinqMotor.speed import *
def test_move_to_low_limit_switch(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
move_to_low_limit_switch(motor)
def test_move_to_high_limit_switch(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
move_to_high_limit_switch(motor)
def test_move_while_move_same_direction(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
move_while_move(motor, -30, -10, 2)
def test_move_while_move_opposite_direction(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
move_while_move(motor, 10, -10, 2)
def test_stop(motor):
stop(motor, -60)
def test_stop_then_move(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
stop_then_move(motor, 10)
@pytest.mark.stresstest
def test_stop_then_move_stress(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
for _ in range(50):
stop_then_move(motor, 5)
stop_then_move(motor, 0)
def test_speed_fields_valid(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
speed_fields_valid(motor)
def test_set_minspeed_maxspeed_meanspeed(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
set_minspeed_maxspeed_meanspeed(motor)
def test_set_invalid_speed_above_min_below_max(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
set_invalid_speed_above_min_below_max(motor)
def test_set_speed_and_move(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
set_speed_and_move(motor, motor.get_pv('maxspeed'), 5)
set_speed_and_move(motor, 0.5*motor.get_pv('maxspeed'), 0)
@pytest.mark.stresstest
def test_set_speed_and_move_stress(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
for _ in range(50):
set_speed_and_move(motor, motor.get_pv('maxspeed'), 5)
set_speed_and_move(motor, 0.5*motor.get_pv('maxspeed'), 0)
def test_targets_outside_limits(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
targets_outside_limits(motor)
def test_home(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
home(motor, True)
def test_start_home_while_moving(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
start_home_while_moving(motor, 15, True)
def test_start_home_while_moving(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
start_home_while_moving(motor, 15, True)
def test_move_then_home(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
move_then_home(motor, 15, True)
def test_home_then_move(motor):
motor.put_pv('stop', 1)
motor.wait_for_done()
home_then_move(motor, 15, True)
# def test_reread_limits_from_hw(motor):
# reread_limits_from_hw(motor)

View File

@@ -1,9 +0,0 @@
# This module defines fixtures which are shared for all tests of motor "lin1".
import pytest
from setup.classes import MasterMACS
@pytest.fixture(autouse=True)
def motor():
return MasterMACS('masterMacs1', 'ax1')

View File

@@ -1,50 +0,0 @@
# Run a selection of common tests
from setup.move import *
from setup.sinqMotor.limits import *
from setup.home import *
from setup.sinqMotor.masterMacs.reset import reset
def test_reset(motor):
reset(motor, 4)
reset(motor, 2)
def test_move_to_low_limit_switch(motor):
reset(motor, 1)
move_to_low_limit_switch(motor)
def test_move_to_high_limit_switch(motor):
reset(motor, 1)
move_to_high_limit_switch(motor)
def test_move_while_move(motor):
reset(motor, 1)
move_while_move(motor, -60, -20)
def test_stop(motor):
reset(motor, 1)
stop(motor, 18)
def test_home(motor):
reset(motor, 1)
home(motor, True)
def test_start_home_while_moving(motor):
reset(motor, 1)
start_home_while_moving(motor, 10, True)
def test_start_home_while_moving(motor):
reset(motor, 1)
start_home_while_moving(motor, 10, True)
# def test_reread_limits_from_hw(motor):
# reread_limits_from_hw(motor)

View File

View File

@@ -0,0 +1,21 @@
# This module defines fixtures which are shared for all tests of motor "ax1".
import time
import pytest
from setup.classes import MasterMACS
# Prepare the motor at the start of the test by resetting and homing it.
@pytest.fixture(scope="session", autouse=True)
def reset_and_home():
mot = MasterMACS('masterMacs1', 'ax1')
mot.put_pv('stop', 1)
mot.put_pv('reseterrorpv', 1)
mot.wait_disabled()
mot.enable_and_wait()
mot.put_pv('homeforward', 1)
time.sleep(1)
mot.wait_for_done()
@pytest.fixture(autouse=True)
def motor():
return MasterMACS('masterMacs1', 'ax1')

View File

@@ -0,0 +1,50 @@
# Run a selection of common tests
from setup.move import *
from setup.sinqMotor.limits import *
from setup.home import *
from setup.sinqMotor.masterMacs.reset import reset
def test_reset(motor):
reset(motor, 4)
reset(motor, 2)
# def test_move_to_low_limit_switch(motor):
# reset(motor, 1)
# move_to_low_limit_switch(motor)
# def test_move_to_high_limit_switch(motor):
# reset(motor, 1)
# move_to_high_limit_switch(motor)
# def test_move_while_move(motor):
# reset(motor, 1)
# move_while_move(motor, -60, -20)
# def test_stop(motor):
# reset(motor, 1)
# stop(motor, 18)
# def test_home(motor):
# reset(motor, 1)
# home(motor, True)
# def test_start_home_while_moving(motor):
# reset(motor, 1)
# start_home_while_moving(motor, 10, True)
# def test_start_home_while_moving(motor):
# reset(motor, 1)
# start_home_while_moving(motor, 10, True)
# def test_reread_limits_from_hw(motor):
# reread_limits_from_hw(motor)