From 034c302483c531e642d85f1fc3c160c5b77b7022 Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Mon, 6 May 2019 10:25:19 -0400 Subject: [PATCH 01/13] initial version of iocs/eps_motor_ioc_sim.py --- .travis.yml | 1 + nslsii/iocs/epics_motor_ioc_sim.py | 148 +++++++++++++++++++ nslsii/iocs/tests/test_epicsmotor_ioc.py | 64 ++++++++ nslsii/iocs/tests/test_epstwostate_ioc.py | 6 + nslsii/tests/temperature_controllers_test.py | 9 ++ 5 files changed, 228 insertions(+) create mode 100644 nslsii/iocs/epics_motor_ioc_sim.py create mode 100644 nslsii/iocs/tests/test_epicsmotor_ioc.py diff --git a/.travis.yml b/.travis.yml index 6f4c2e21..402ff29a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ env: - secure: "FhNkkbod0Wc/zUf9cTvwziAYHcjfte2POf+hoVSmC+v/RcYKCNCo+mGGMhF9F4KyC2nzvulfzow7YXoswZqav4+TEEu+mpuPaGlf9aqp8V61eij8MVTwonzQEYmHAy3KatwXxyvvhQpfj3gOuDVolfOg2MtNZi6QERES4E1sjOn714fx2HkVxqH2Y8/PF/FzzGeJaRlVaVci0EdIJ5Ss5c5SjO6JGgxj4hzhTPHjTaLjdLHlVhuB9Yatl80zbhGriljLcDQTHmoSODwBpAh5YLDUZq6B9vomaNB9Hb3e0D5gItjOdj53v6AsHU8LkncZMvsgJgh2sZZqMO6nkpHcYPwJgbPbKd3RtVlk6Kg/tvKQk0rMcxl5fFFeD2i9POnANg/xJsKN6yAEY3kaRwQtajQmlcicSa/wdwv9NhUTtBmA/mnyzxHbQXrB0bEc2P2QVu7U8en6dWaOAqc1VCMrWIhp2ADNWb7JZhYj70TgmExIU3UH8qlMb6dyx50SJUE9waJj3fiiZVkjh+E568ZRSMvL9n+bLlFt4uDT4AysSby6cj+zjfNViKFstTAqjyd5VJEvCoUu73vNzWEiWFtEvKKVL1P3pbLN/G3aSSJMa5fc1o+2lRUwdwNNOOdH6iKBDZGNpE8nGDlTP2b2dhFyEt8nICKJhbgU208jhyyH8Vk=" script: + - export OPHYD_CONTROL_LAYER=caproto - coverage run -m pytest # Run the tests and check for test coverage. - coverage report -m # Generate test coverage report. - codecov # Upload the report to codecov. diff --git a/nslsii/iocs/epics_motor_ioc_sim.py b/nslsii/iocs/epics_motor_ioc_sim.py new file mode 100644 index 00000000..03d4eaba --- /dev/null +++ b/nslsii/iocs/epics_motor_ioc_sim.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +from caproto.server import pvproperty, PVGroup +from caproto.server import ioc_arg_parser, run +from caproto import ChannelType + + +class EpicsMotorIOC(PVGroup): + """ + Simulates ophyd.EpicsMotor. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + _dir_states = ['neg', 'pos'] + _false_true_states = ['False', 'True'] + + # position + + _upper_alarm_limit = 100.0 + _lower_alarm_limit = -100.0 + + _upper_warning_limit = 90.0 + _lower_warning_limit = -90.0 + + _upper_ctrl_limit = 110.0 + _lower_ctrl_limit = -110.0 + + _egu = 'mm' + + _precision = 3 + + user_readback = pvproperty(value=0.0, read_only=True, + dtype=ChannelType.DOUBLE, + upper_alarm_limit=_upper_alarm_limit, + lower_alarm_limit=_lower_alarm_limit, + upper_warning_limit=_upper_warning_limit, + lower_warning_limit=_lower_warning_limit, + upper_ctrl_limit=_upper_ctrl_limit, + lower_ctrl_limit=_lower_ctrl_limit, + units=_egu, + precision=_precision, + name='.RBV') + user_setpoint = pvproperty(value=0.0, + dtype=ChannelType.DOUBLE, + upper_alarm_limit=_upper_alarm_limit, + lower_alarm_limit=_lower_alarm_limit, + upper_warning_limit=_upper_warning_limit, + lower_warning_limit=_lower_warning_limit, + upper_ctrl_limit=_upper_ctrl_limit, + lower_ctrl_limit=_lower_ctrl_limit, + units=_egu, + precision=_precision, + name='.VAL') + + # calibration dial <--> user + + user_offset = pvproperty(value=0.0, read_only=True, + dtype=ChannelType.DOUBLE, + name='.OFF') + + user_offset_dir = pvproperty(value=_dir_states[1], + enum_strings=_dir_states, + dtype=ChannelType.ENUM, + name='.DIR') + + offset_freeze_switch = pvproperty(value=_false_true_states[0], + enum_strings=_false_true_states, + dtype=ChannelType.ENUM, + name='.FOFF') + set_use_switch = pvproperty(value=_false_true_states[0], + enum_strings=_false_true_states, + dtype=ChannelType.ENUM, + name='.SET') + + # configuration + + _velocity = 1. + _acceleration = 3. + + velocity = pvproperty(value=_velocity, + dtype=ChannelType.DOUBLE, + name='.VELO') + acceleration = pvproperty(value=_acceleration, + dtype=ChannelType.DOUBLE, + name='.ACCL') + motor_egu = pvproperty(value=_egu, + dtype=ChannelType.STRING, + name='.EGU') + + # motor status + + motor_is_moving = pvproperty(value='False', read_only=True, + enum_strings=_false_true_states, + dtype=ChannelType.ENUM, + name='.MOVN') + motor_done_move = pvproperty(value='False', read_only=False, + enum_strings=_false_true_states, + dtype=ChannelType.ENUM, + name='.DMOV') + + high_limit_switch = pvproperty(value=0, read_only=True, + dtype=ChannelType.INT, + name='.HLS') + low_limit_switch = pvproperty(value=0, read_only=True, + dtype=ChannelType.INT, + name='.LLS') + + direction_of_travel = pvproperty(value=_dir_states[1], + enum_strings=_dir_states, + dtype=ChannelType.ENUM, + name='.TDIR') + + # commands + + _cmd_states = ['False', 'True'] + + motor_stop = pvproperty(value=_cmd_states[0], + enum_strings=_cmd_states, + dtype=ChannelType.ENUM, + name='.STOP') + home_forward = pvproperty(value=_cmd_states[0], + enum_strings=_cmd_states, + dtype=ChannelType.ENUM, + name='.HOMF') + home_reverse = pvproperty(value=_cmd_states[0], + enum_strings=_cmd_states, + dtype=ChannelType.ENUM, + name='.HOMR') + + # Methods + + @user_setpoint.putter + async def user_setpoint(self, instance, value): + await self.motor_done_move.write(value='False') + await self.user_readback.write(value=value) + await self.motor_done_move.write(value='True') + return value + + +if __name__ == '__main__': + + ioc_options, run_options = ioc_arg_parser( + default_prefix='mtr:', + desc='EpicsMotor IOC.') + + ioc = EpicsMotorIOC(**ioc_options) + run(ioc.pvdb, **run_options) diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py new file mode 100644 index 00000000..356fb392 --- /dev/null +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -0,0 +1,64 @@ +import os +import subprocess +import sys +import time + +from ophyd.epics_motor import EpicsMotor + + +def test_epicsmotor_ioc(): + + stdout = subprocess.PIPE + stdin = None + + ''' + ioc_process = subprocess.Popen([sys.executable, '-m', + 'caproto.tests.example_runner', + 'nslsii.iocs.epics_motor_ioc_sim'], + stdout=stdout, stdin=stdin, + env=os.environ) + ''' + ioc_process = subprocess.Popen([sys.executable, '-m', + 'nslsii.iocs.epics_motor_ioc_sim'], + stdout=stdout, stdin=stdin, + env=os.environ) + + print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') + + time.sleep(5) + + # Wrap the rest in a try-except to ensure the ioc is killed before exiting + try: + + mtr = EpicsMotor(prefix='mtr:', name='mtr') + + time.sleep(5) + + # 1. check the ioc-device connection and initial values + + assert mtr.egu == 'mm' + + assert mtr.low_limit == -110.0 + assert mtr.high_limit == 110.0 + + # 2. set_current_position + + mtr.set_current_position(50) + setpoint_val = mtr.user_setpoint.get() + readback_val = mtr.user_readback.get() + assert setpoint_val == 50 + assert readback_val == 50 + + # 3. move + + mtr.move(80, timeout=2) + setpoint_val = mtr.user_setpoint.get() + readback_val = mtr.user_readback.get() + assert setpoint_val == 80 + assert readback_val == 80 + + finally: + # Ensure that for any exception the ioc sub-process is terminated + # before raising. + # pass + ioc_process.terminate() diff --git a/nslsii/iocs/tests/test_epstwostate_ioc.py b/nslsii/iocs/tests/test_epstwostate_ioc.py index a30dabfb..cd3c421d 100644 --- a/nslsii/iocs/tests/test_epstwostate_ioc.py +++ b/nslsii/iocs/tests/test_epstwostate_ioc.py @@ -43,11 +43,17 @@ def test_epstwostate_ioc(): stdout = subprocess.PIPE stdin = None + ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'nslsii.iocs.eps_two_state_ioc_sim'], stdout=stdout, stdin=stdin, env=os.environ) + ''' + ioc_process = subprocess.Popen([sys.executable, '-m', + 'nslsii.iocs.eps_two_state_ioc_sim'], + stdout=stdout, stdin=stdin, + env=os.environ) print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') diff --git a/nslsii/tests/temperature_controllers_test.py b/nslsii/tests/temperature_controllers_test.py index 33b7978b..c2370416 100644 --- a/nslsii/tests/temperature_controllers_test.py +++ b/nslsii/tests/temperature_controllers_test.py @@ -5,6 +5,7 @@ import subprocess import os import sys +import time import pytest @@ -26,14 +27,22 @@ def test_Eurotherm(RE): stdin = None # Start up an IOC based on the thermo_sim device in caproto.ioc_examples + ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'caproto.ioc_examples.thermo_sim'], stdout=stdout, stdin=stdin, env=os.environ) + ''' + ioc_process = subprocess.Popen([sys.executable, '-m', + 'caproto.ioc_examples.thermo_sim'], + stdout=stdout, stdin=stdin, + env=os.environ) print(f'caproto.ioc_examples.thermo_sim is now running') + time.sleep(5) + # Wrap the rest in a try-except to ensure the ioc is killed before exiting try: euro = Eurotherm('thermo:', name='euro') From 54fd5be053dfc0e60d5b559bf6f397bade55543b Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Tue, 7 May 2019 14:07:45 -0400 Subject: [PATCH 02/13] added timeout tests and Tom's suggestion --- nslsii/iocs/epics_motor_ioc_sim.py | 57 +++++++++++++++++++----- nslsii/iocs/tests/test_epicsmotor_ioc.py | 53 +++++++++++++++++----- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/nslsii/iocs/epics_motor_ioc_sim.py b/nslsii/iocs/epics_motor_ioc_sim.py index 03d4eaba..b49ff8c7 100644 --- a/nslsii/iocs/epics_motor_ioc_sim.py +++ b/nslsii/iocs/epics_motor_ioc_sim.py @@ -2,6 +2,25 @@ from caproto.server import pvproperty, PVGroup from caproto.server import ioc_arg_parser, run from caproto import ChannelType +import contextvars +import functools +import math + +internal_process = contextvars.ContextVar('internal_process', + default=False) + +def no_reentry(func): + @functools.wraps(func) + async def inner(*args, **kwargs): + if internal_process.get(): + return + try: + internal_process.set(True) + return (await func(*args, **kwargs)) + finally: + internal_process.set(False) + + return inner class EpicsMotorIOC(PVGroup): @@ -17,14 +36,14 @@ def __init__(self, **kwargs): # position - _upper_alarm_limit = 100.0 - _lower_alarm_limit = -100.0 + _upper_alarm_limit = 10.0 + _lower_alarm_limit = -10.0 - _upper_warning_limit = 90.0 - _lower_warning_limit = -90.0 + _upper_warning_limit = 9.0 + _lower_warning_limit = -9.0 - _upper_ctrl_limit = 110.0 - _lower_ctrl_limit = -110.0 + _upper_ctrl_limit = 11.0 + _lower_ctrl_limit = -11.0 _egu = 'mm' @@ -78,13 +97,13 @@ def __init__(self, **kwargs): _velocity = 1. _acceleration = 3. - velocity = pvproperty(value=_velocity, + velocity = pvproperty(value=_velocity, read_only=True, dtype=ChannelType.DOUBLE, name='.VELO') - acceleration = pvproperty(value=_acceleration, + acceleration = pvproperty(value=_acceleration, read_only=True, dtype=ChannelType.DOUBLE, name='.ACCL') - motor_egu = pvproperty(value=_egu, + motor_egu = pvproperty(value=_egu, read_only=True, dtype=ChannelType.STRING, name='.EGU') @@ -130,11 +149,29 @@ def __init__(self, **kwargs): # Methods + @user_setpoint.startup + async def user_setpoint(self, instance, async_lib): + instance.ev = async_lib.library.Event() + instance.async_lib = async_lib + @user_setpoint.putter + @no_reentry async def user_setpoint(self, instance, value): + disp = (value - instance.value) + step_size = 0.1 + dwell = step_size/self._velocity + N = max(1, int(disp / step_size)) + await self.motor_done_move.write(value='False') - await self.user_readback.write(value=value) + + for j in range(N): + new_value = instance.value + step_size + await instance.write(new_value) + await instance.async_lib.library.sleep(dwell) + await self.user_readback.write(value=new_value) + await self.motor_done_move.write(value='True') + return value diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index 356fb392..3470affc 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -1,9 +1,11 @@ import os +import pytest import subprocess import sys import time from ophyd.epics_motor import EpicsMotor +from ophyd.status import MoveStatus def test_epicsmotor_ioc(): @@ -38,27 +40,58 @@ def test_epicsmotor_ioc(): assert mtr.egu == 'mm' - assert mtr.low_limit == -110.0 - assert mtr.high_limit == 110.0 + velocity_val = mtr.velocity.get() + assert velocity_val == 1 + + assert mtr.low_limit == -11.0 + assert mtr.high_limit == 11.0 # 2. set_current_position - mtr.set_current_position(50) + target_val = 5 + + mtr.set_current_position(target_val) setpoint_val = mtr.user_setpoint.get() readback_val = mtr.user_readback.get() - assert setpoint_val == 50 - assert readback_val == 50 + assert round(setpoint_val, 3) == target_val + assert round(readback_val, 3) == target_val + + # 3. move (timeout > moving time) + + target_val = 7 + mvtime = (target_val - readback_val)/velocity_val + + move_status = MoveStatus(mtr, target_val) - # 3. move + try: + move_status = mtr.move(target_val, timeout=mvtime+1) + except: + pass - mtr.move(80, timeout=2) + assert move_status.success == True + + time.sleep(mvtime) + setpoint_val = mtr.user_setpoint.get() readback_val = mtr.user_readback.get() - assert setpoint_val == 80 - assert readback_val == 80 + assert round(setpoint_val, 3) == target_val + assert round(readback_val, 3) == target_val + + # 4. move (timeout < moving time) + + target_val = 9 + mvtime = (target_val - readback_val)/velocity_val + + move_status = MoveStatus(mtr, target_val) + + try: + move_status = mtr.move(target_val, timeout=mvtime-1) + except: + pass + + assert move_status.success == False finally: # Ensure that for any exception the ioc sub-process is terminated # before raising. - # pass ioc_process.terminate() From a02614eec3a2e6bd3bbfdf81d308ae0290e931eb Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Tue, 7 May 2019 14:35:02 -0400 Subject: [PATCH 03/13] added time delay --- nslsii/iocs/tests/test_epicsmotor_ioc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index 3470affc..b71a0b42 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -27,14 +27,14 @@ def test_epicsmotor_ioc(): print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') - time.sleep(5) + time.sleep(7) # Wrap the rest in a try-except to ensure the ioc is killed before exiting try: mtr = EpicsMotor(prefix='mtr:', name='mtr') - time.sleep(5) + time.sleep(7) # 1. check the ioc-device connection and initial values From 6a9feb20ae4af7718bf7e5453e85b9ea65fb969f Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Tue, 7 May 2019 19:39:01 -0400 Subject: [PATCH 04/13] updated user_setpoint() --- nslsii/iocs/epics_motor_ioc_sim.py | 40 +++++------------------- nslsii/iocs/tests/test_epicsmotor_ioc.py | 27 +++++++++------- 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/nslsii/iocs/epics_motor_ioc_sim.py b/nslsii/iocs/epics_motor_ioc_sim.py index b49ff8c7..27a4f9b0 100644 --- a/nslsii/iocs/epics_motor_ioc_sim.py +++ b/nslsii/iocs/epics_motor_ioc_sim.py @@ -2,25 +2,7 @@ from caproto.server import pvproperty, PVGroup from caproto.server import ioc_arg_parser, run from caproto import ChannelType -import contextvars -import functools -import math - -internal_process = contextvars.ContextVar('internal_process', - default=False) - -def no_reentry(func): - @functools.wraps(func) - async def inner(*args, **kwargs): - if internal_process.get(): - return - try: - internal_process.set(True) - return (await func(*args, **kwargs)) - finally: - internal_process.set(False) - - return inner +import time class EpicsMotorIOC(PVGroup): @@ -34,6 +16,8 @@ def __init__(self, **kwargs): _dir_states = ['neg', 'pos'] _false_true_states = ['False', 'True'] + _step_size = 0.1 + # position _upper_alarm_limit = 10.0 @@ -149,25 +133,17 @@ def __init__(self, **kwargs): # Methods - @user_setpoint.startup - async def user_setpoint(self, instance, async_lib): - instance.ev = async_lib.library.Event() - instance.async_lib = async_lib - @user_setpoint.putter - @no_reentry async def user_setpoint(self, instance, value): - disp = (value - instance.value) - step_size = 0.1 - dwell = step_size/self._velocity - N = max(1, int(disp / step_size)) + p0 = instance.value + dwell = self._step_size/self._velocity + N = max(1, int((value - p0) / self._step_size)) await self.motor_done_move.write(value='False') for j in range(N): - new_value = instance.value + step_size - await instance.write(new_value) - await instance.async_lib.library.sleep(dwell) + new_value = p0 + self._step_size*(j+1) + time.sleep(dwell) await self.user_readback.write(value=new_value) await self.motor_done_move.write(value='True') diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index b71a0b42..2304d3ab 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -1,5 +1,4 @@ import os -import pytest import subprocess import sys import time @@ -27,14 +26,14 @@ def test_epicsmotor_ioc(): print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') - time.sleep(7) + time.sleep(5) # Wrap the rest in a try-except to ensure the ioc is killed before exiting try: mtr = EpicsMotor(prefix='mtr:', name='mtr') - time.sleep(7) + time.sleep(5) # 1. check the ioc-device connection and initial values @@ -50,7 +49,13 @@ def test_epicsmotor_ioc(): target_val = 5 + readback_val = mtr.user_readback.get() + mvtime = (target_val - readback_val)/velocity_val + mtr.set_current_position(target_val) + + time.sleep(mvtime) + setpoint_val = mtr.user_setpoint.get() readback_val = mtr.user_readback.get() assert round(setpoint_val, 3) == target_val @@ -63,15 +68,15 @@ def test_epicsmotor_ioc(): move_status = MoveStatus(mtr, target_val) - try: + try: move_status = mtr.move(target_val, timeout=mvtime+1) - except: + except RuntimeError: pass - assert move_status.success == True - + assert move_status.success is True + time.sleep(mvtime) - + setpoint_val = mtr.user_setpoint.get() readback_val = mtr.user_readback.get() assert round(setpoint_val, 3) == target_val @@ -84,12 +89,12 @@ def test_epicsmotor_ioc(): move_status = MoveStatus(mtr, target_val) - try: + try: move_status = mtr.move(target_val, timeout=mvtime-1) - except: + except RuntimeError: pass - assert move_status.success == False + assert move_status.success is False finally: # Ensure that for any exception the ioc sub-process is terminated From 7cb6875cd691082543e7e12c141c880fa7903d53 Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Tue, 7 May 2019 20:03:55 -0400 Subject: [PATCH 05/13] moved temperature_controllers_test.py into iocs/tests --- nslsii/{ => iocs}/tests/temperature_controllers_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nslsii/{ => iocs}/tests/temperature_controllers_test.py (100%) diff --git a/nslsii/tests/temperature_controllers_test.py b/nslsii/iocs/tests/temperature_controllers_test.py similarity index 100% rename from nslsii/tests/temperature_controllers_test.py rename to nslsii/iocs/tests/temperature_controllers_test.py From 155ebfd9db313b92d1d86d52333502c72c90ad6d Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Wed, 8 May 2019 10:06:35 -0400 Subject: [PATCH 06/13] added caproto.tests.example_runner --- nslsii/iocs/tests/temperature_controllers_test.py | 2 +- nslsii/iocs/tests/test_epicsmotor_ioc.py | 2 +- nslsii/iocs/tests/test_epstwostate_ioc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nslsii/iocs/tests/temperature_controllers_test.py b/nslsii/iocs/tests/temperature_controllers_test.py index c2370416..744842b1 100644 --- a/nslsii/iocs/tests/temperature_controllers_test.py +++ b/nslsii/iocs/tests/temperature_controllers_test.py @@ -27,7 +27,6 @@ def test_Eurotherm(RE): stdin = None # Start up an IOC based on the thermo_sim device in caproto.ioc_examples - ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'caproto.ioc_examples.thermo_sim'], @@ -38,6 +37,7 @@ def test_Eurotherm(RE): 'caproto.ioc_examples.thermo_sim'], stdout=stdout, stdin=stdin, env=os.environ) + ''' print(f'caproto.ioc_examples.thermo_sim is now running') diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index 2304d3ab..5466abd7 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -12,7 +12,6 @@ def test_epicsmotor_ioc(): stdout = subprocess.PIPE stdin = None - ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'nslsii.iocs.epics_motor_ioc_sim'], @@ -23,6 +22,7 @@ def test_epicsmotor_ioc(): 'nslsii.iocs.epics_motor_ioc_sim'], stdout=stdout, stdin=stdin, env=os.environ) + ''' print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') diff --git a/nslsii/iocs/tests/test_epstwostate_ioc.py b/nslsii/iocs/tests/test_epstwostate_ioc.py index cd3c421d..fee7c2df 100644 --- a/nslsii/iocs/tests/test_epstwostate_ioc.py +++ b/nslsii/iocs/tests/test_epstwostate_ioc.py @@ -43,7 +43,6 @@ def test_epstwostate_ioc(): stdout = subprocess.PIPE stdin = None - ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'nslsii.iocs.eps_two_state_ioc_sim'], @@ -54,6 +53,7 @@ def test_epstwostate_ioc(): 'nslsii.iocs.eps_two_state_ioc_sim'], stdout=stdout, stdin=stdin, env=os.environ) + ''' print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') From 68890d0d4a2cb846e890b54b4587ca3b4d28ed52 Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Wed, 8 May 2019 10:36:03 -0400 Subject: [PATCH 07/13] removed caproto.tests.example_runner --- nslsii/iocs/tests/temperature_controllers_test.py | 2 +- nslsii/iocs/tests/test_epicsmotor_ioc.py | 3 ++- nslsii/iocs/tests/test_epstwostate_ioc.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nslsii/iocs/tests/temperature_controllers_test.py b/nslsii/iocs/tests/temperature_controllers_test.py index 744842b1..c2370416 100644 --- a/nslsii/iocs/tests/temperature_controllers_test.py +++ b/nslsii/iocs/tests/temperature_controllers_test.py @@ -27,6 +27,7 @@ def test_Eurotherm(RE): stdin = None # Start up an IOC based on the thermo_sim device in caproto.ioc_examples + ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'caproto.ioc_examples.thermo_sim'], @@ -37,7 +38,6 @@ def test_Eurotherm(RE): 'caproto.ioc_examples.thermo_sim'], stdout=stdout, stdin=stdin, env=os.environ) - ''' print(f'caproto.ioc_examples.thermo_sim is now running') diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index 5466abd7..c3500fbf 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -12,17 +12,18 @@ def test_epicsmotor_ioc(): stdout = subprocess.PIPE stdin = None + ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'nslsii.iocs.epics_motor_ioc_sim'], stdout=stdout, stdin=stdin, env=os.environ) ''' + ioc_process = subprocess.Popen([sys.executable, '-m', 'nslsii.iocs.epics_motor_ioc_sim'], stdout=stdout, stdin=stdin, env=os.environ) - ''' print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') diff --git a/nslsii/iocs/tests/test_epstwostate_ioc.py b/nslsii/iocs/tests/test_epstwostate_ioc.py index fee7c2df..cd3c421d 100644 --- a/nslsii/iocs/tests/test_epstwostate_ioc.py +++ b/nslsii/iocs/tests/test_epstwostate_ioc.py @@ -43,6 +43,7 @@ def test_epstwostate_ioc(): stdout = subprocess.PIPE stdin = None + ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.tests.example_runner', 'nslsii.iocs.eps_two_state_ioc_sim'], @@ -53,7 +54,6 @@ def test_epstwostate_ioc(): 'nslsii.iocs.eps_two_state_ioc_sim'], stdout=stdout, stdin=stdin, env=os.environ) - ''' print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') From 6abcac082369fbcaaf219c77407d5c962b49b130 Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Wed, 8 May 2019 12:50:40 -0400 Subject: [PATCH 08/13] disable temperature_controllers_test by renaming --- ...rature_controllers_test.py => temperature_controllers_test.p} | 0 nslsii/iocs/tests/test_epicsmotor_ioc.py | 1 - 2 files changed, 1 deletion(-) rename nslsii/iocs/tests/{temperature_controllers_test.py => temperature_controllers_test.p} (100%) diff --git a/nslsii/iocs/tests/temperature_controllers_test.py b/nslsii/iocs/tests/temperature_controllers_test.p similarity index 100% rename from nslsii/iocs/tests/temperature_controllers_test.py rename to nslsii/iocs/tests/temperature_controllers_test.p diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index c3500fbf..2304d3ab 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -19,7 +19,6 @@ def test_epicsmotor_ioc(): stdout=stdout, stdin=stdin, env=os.environ) ''' - ioc_process = subprocess.Popen([sys.executable, '-m', 'nslsii.iocs.epics_motor_ioc_sim'], stdout=stdout, stdin=stdin, From a4f889818080d66093ecc61f1153f6153eb2470c Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Wed, 8 May 2019 13:59:38 -0400 Subject: [PATCH 09/13] removed commented code --- nslsii/iocs/tests/test_epicsmotor_ioc.py | 7 ------- nslsii/iocs/tests/test_epstwostate_ioc.py | 7 ------- 2 files changed, 14 deletions(-) diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/iocs/tests/test_epicsmotor_ioc.py index 2304d3ab..f14556cf 100644 --- a/nslsii/iocs/tests/test_epicsmotor_ioc.py +++ b/nslsii/iocs/tests/test_epicsmotor_ioc.py @@ -12,13 +12,6 @@ def test_epicsmotor_ioc(): stdout = subprocess.PIPE stdin = None - ''' - ioc_process = subprocess.Popen([sys.executable, '-m', - 'caproto.tests.example_runner', - 'nslsii.iocs.epics_motor_ioc_sim'], - stdout=stdout, stdin=stdin, - env=os.environ) - ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'nslsii.iocs.epics_motor_ioc_sim'], stdout=stdout, stdin=stdin, diff --git a/nslsii/iocs/tests/test_epstwostate_ioc.py b/nslsii/iocs/tests/test_epstwostate_ioc.py index cd3c421d..80346b96 100644 --- a/nslsii/iocs/tests/test_epstwostate_ioc.py +++ b/nslsii/iocs/tests/test_epstwostate_ioc.py @@ -43,13 +43,6 @@ def test_epstwostate_ioc(): stdout = subprocess.PIPE stdin = None - ''' - ioc_process = subprocess.Popen([sys.executable, '-m', - 'caproto.tests.example_runner', - 'nslsii.iocs.eps_two_state_ioc_sim'], - stdout=stdout, stdin=stdin, - env=os.environ) - ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'nslsii.iocs.eps_two_state_ioc_sim'], stdout=stdout, stdin=stdin, From a802103a4bfacfea732298c9f5d8f707239620c1 Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Fri, 10 May 2019 10:27:15 -0400 Subject: [PATCH 10/13] moved tests into nslsii/tests and enable temperature_controllers_test --- .../temperature_controllers_test.py} | 0 nslsii/{iocs => }/tests/test_epicsmotor_ioc.py | 0 nslsii/{iocs => }/tests/test_epstwostate_ioc.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename nslsii/{iocs/tests/temperature_controllers_test.p => tests/temperature_controllers_test.py} (100%) rename nslsii/{iocs => }/tests/test_epicsmotor_ioc.py (100%) rename nslsii/{iocs => }/tests/test_epstwostate_ioc.py (100%) diff --git a/nslsii/iocs/tests/temperature_controllers_test.p b/nslsii/tests/temperature_controllers_test.py similarity index 100% rename from nslsii/iocs/tests/temperature_controllers_test.p rename to nslsii/tests/temperature_controllers_test.py diff --git a/nslsii/iocs/tests/test_epicsmotor_ioc.py b/nslsii/tests/test_epicsmotor_ioc.py similarity index 100% rename from nslsii/iocs/tests/test_epicsmotor_ioc.py rename to nslsii/tests/test_epicsmotor_ioc.py diff --git a/nslsii/iocs/tests/test_epstwostate_ioc.py b/nslsii/tests/test_epstwostate_ioc.py similarity index 100% rename from nslsii/iocs/tests/test_epstwostate_ioc.py rename to nslsii/tests/test_epstwostate_ioc.py From 27bdb2e61cf49769d67274b6406e6c33d48f170f Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Mon, 13 May 2019 19:10:04 -0400 Subject: [PATCH 11/13] removed synchronous sleeps, splitted tests --- nslsii/iocs/epics_motor_ioc_sim.py | 8 ++- nslsii/tests/temperature_controllers_test.py | 7 -- nslsii/tests/test_epicsmotor_ioc.py | 70 ++++++++++++-------- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/nslsii/iocs/epics_motor_ioc_sim.py b/nslsii/iocs/epics_motor_ioc_sim.py index 27a4f9b0..1be07b3a 100644 --- a/nslsii/iocs/epics_motor_ioc_sim.py +++ b/nslsii/iocs/epics_motor_ioc_sim.py @@ -2,7 +2,6 @@ from caproto.server import pvproperty, PVGroup from caproto.server import ioc_arg_parser, run from caproto import ChannelType -import time class EpicsMotorIOC(PVGroup): @@ -133,6 +132,11 @@ def __init__(self, **kwargs): # Methods + @user_setpoint.startup + async def user_setpoint(self, instance, async_lib): + instance.ev = async_lib.library.Event() + instance.async_lib = async_lib + @user_setpoint.putter async def user_setpoint(self, instance, value): p0 = instance.value @@ -143,7 +147,7 @@ async def user_setpoint(self, instance, value): for j in range(N): new_value = p0 + self._step_size*(j+1) - time.sleep(dwell) + await instance.async_lib.library.sleep(dwell) await self.user_readback.write(value=new_value) await self.motor_done_move.write(value='True') diff --git a/nslsii/tests/temperature_controllers_test.py b/nslsii/tests/temperature_controllers_test.py index c2370416..916cb97f 100644 --- a/nslsii/tests/temperature_controllers_test.py +++ b/nslsii/tests/temperature_controllers_test.py @@ -27,13 +27,6 @@ def test_Eurotherm(RE): stdin = None # Start up an IOC based on the thermo_sim device in caproto.ioc_examples - ''' - ioc_process = subprocess.Popen([sys.executable, '-m', - 'caproto.tests.example_runner', - 'caproto.ioc_examples.thermo_sim'], - stdout=stdout, stdin=stdin, - env=os.environ) - ''' ioc_process = subprocess.Popen([sys.executable, '-m', 'caproto.ioc_examples.thermo_sim'], stdout=stdout, stdin=stdin, diff --git a/nslsii/tests/test_epicsmotor_ioc.py b/nslsii/tests/test_epicsmotor_ioc.py index f14556cf..775e3740 100644 --- a/nslsii/tests/test_epicsmotor_ioc.py +++ b/nslsii/tests/test_epicsmotor_ioc.py @@ -1,4 +1,5 @@ import os +import pytest import subprocess import sys import time @@ -7,7 +8,10 @@ from ophyd.status import MoveStatus -def test_epicsmotor_ioc(): +@pytest.fixture(scope='class') +def ioc_sim(request): + + # setup code stdout = subprocess.PIPE stdin = None @@ -21,48 +25,59 @@ def test_epicsmotor_ioc(): time.sleep(5) - # Wrap the rest in a try-except to ensure the ioc is killed before exiting - try: + mtr = EpicsMotor(prefix='mtr:', name='mtr') + + time.sleep(5) + + request.cls.mtr = mtr + + yield - mtr = EpicsMotor(prefix='mtr:', name='mtr') + # teardown code - time.sleep(5) + ioc_process.terminate() - # 1. check the ioc-device connection and initial values - assert mtr.egu == 'mm' +@pytest.mark.usefixtures('ioc_sim') +class TestIOC: - velocity_val = mtr.velocity.get() + def test_initial_values(self): + + assert self.mtr.egu == 'mm' + + velocity_val = self.mtr.velocity.get() assert velocity_val == 1 - assert mtr.low_limit == -11.0 - assert mtr.high_limit == 11.0 + assert self.mtr.low_limit == -11.0 + assert self.mtr.high_limit == 11.0 - # 2. set_current_position + def test_set_current_position(self): target_val = 5 - - readback_val = mtr.user_readback.get() + readback_val = self.mtr.user_readback.get() + velocity_val = self.mtr.velocity.get() mvtime = (target_val - readback_val)/velocity_val - mtr.set_current_position(target_val) + self.mtr.set_current_position(target_val) time.sleep(mvtime) - setpoint_val = mtr.user_setpoint.get() - readback_val = mtr.user_readback.get() + setpoint_val = self.mtr.user_setpoint.get() + readback_val = self.mtr.user_readback.get() assert round(setpoint_val, 3) == target_val assert round(readback_val, 3) == target_val - # 3. move (timeout > moving time) + def test_move_with_timeout_gt_moving_time(self): target_val = 7 + readback_val = self.mtr.user_readback.get() + velocity_val = self.mtr.velocity.get() mvtime = (target_val - readback_val)/velocity_val - move_status = MoveStatus(mtr, target_val) + move_status = MoveStatus(self.mtr, target_val) try: - move_status = mtr.move(target_val, timeout=mvtime+1) + move_status = self.mtr.move(target_val, timeout=mvtime+1) except RuntimeError: pass @@ -70,26 +85,23 @@ def test_epicsmotor_ioc(): time.sleep(mvtime) - setpoint_val = mtr.user_setpoint.get() - readback_val = mtr.user_readback.get() + setpoint_val = self.mtr.user_setpoint.get() + readback_val = self.mtr.user_readback.get() assert round(setpoint_val, 3) == target_val assert round(readback_val, 3) == target_val - # 4. move (timeout < moving time) + def test_move_with_timeout_lt_moving_time(self): target_val = 9 + readback_val = self.mtr.user_readback.get() + velocity_val = self.mtr.velocity.get() mvtime = (target_val - readback_val)/velocity_val - move_status = MoveStatus(mtr, target_val) + move_status = MoveStatus(self.mtr, target_val) try: - move_status = mtr.move(target_val, timeout=mvtime-1) + move_status = self.mtr.move(target_val, timeout=mvtime-1) except RuntimeError: pass assert move_status.success is False - - finally: - # Ensure that for any exception the ioc sub-process is terminated - # before raising. - ioc_process.terminate() From 16705098f02abbbec4504a2c5c767a497f64634f Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Mon, 13 May 2019 19:58:52 -0400 Subject: [PATCH 12/13] added pytest.raises --- nslsii/tests/test_epicsmotor_ioc.py | 30 +++++++---------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/nslsii/tests/test_epicsmotor_ioc.py b/nslsii/tests/test_epicsmotor_ioc.py index 775e3740..e68c4d3c 100644 --- a/nslsii/tests/test_epicsmotor_ioc.py +++ b/nslsii/tests/test_epicsmotor_ioc.py @@ -5,7 +5,6 @@ import time from ophyd.epics_motor import EpicsMotor -from ophyd.status import MoveStatus @pytest.fixture(scope='class') @@ -41,6 +40,10 @@ def ioc_sim(request): @pytest.mark.usefixtures('ioc_sim') class TestIOC: + def test_zero_division(self): + with pytest.raises(ZeroDivisionError): + 1 / 0 + def test_initial_values(self): assert self.mtr.egu == 'mm' @@ -74,22 +77,9 @@ def test_move_with_timeout_gt_moving_time(self): velocity_val = self.mtr.velocity.get() mvtime = (target_val - readback_val)/velocity_val - move_status = MoveStatus(self.mtr, target_val) - - try: - move_status = self.mtr.move(target_val, timeout=mvtime+1) - except RuntimeError: - pass - + move_status = self.mtr.move(target_val, timeout=mvtime+1) assert move_status.success is True - time.sleep(mvtime) - - setpoint_val = self.mtr.user_setpoint.get() - readback_val = self.mtr.user_readback.get() - assert round(setpoint_val, 3) == target_val - assert round(readback_val, 3) == target_val - def test_move_with_timeout_lt_moving_time(self): target_val = 9 @@ -97,11 +87,5 @@ def test_move_with_timeout_lt_moving_time(self): velocity_val = self.mtr.velocity.get() mvtime = (target_val - readback_val)/velocity_val - move_status = MoveStatus(self.mtr, target_val) - - try: - move_status = self.mtr.move(target_val, timeout=mvtime-1) - except RuntimeError: - pass - - assert move_status.success is False + with pytest.raises(RuntimeError): + self.mtr.move(target_val, timeout=mvtime-1) From b35b93644fd8e7517c7f8479144e90a904c8cb07 Mon Sep 17 00:00:00 2001 From: nmalitsky Date: Tue, 14 May 2019 09:51:16 -0400 Subject: [PATCH 13/13] added putter_lock --- nslsii/iocs/epics_motor_ioc_sim.py | 12 ++++++++++++ nslsii/tests/test_epicsmotor_ioc.py | 6 +----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/nslsii/iocs/epics_motor_ioc_sim.py b/nslsii/iocs/epics_motor_ioc_sim.py index 1be07b3a..65498b8c 100644 --- a/nslsii/iocs/epics_motor_ioc_sim.py +++ b/nslsii/iocs/epics_motor_ioc_sim.py @@ -3,6 +3,8 @@ from caproto.server import ioc_arg_parser, run from caproto import ChannelType +from threading import Lock + class EpicsMotorIOC(PVGroup): """ @@ -55,6 +57,8 @@ def __init__(self, **kwargs): precision=_precision, name='.VAL') + putter_lock = Lock() + # calibration dial <--> user user_offset = pvproperty(value=0.0, read_only=True, @@ -139,6 +143,12 @@ async def user_setpoint(self, instance, async_lib): @user_setpoint.putter async def user_setpoint(self, instance, value): + + if self.putter_lock.locked() is True: + return instance.value + else: + self.putter_lock.acquire() + p0 = instance.value dwell = self._step_size/self._velocity N = max(1, int((value - p0) / self._step_size)) @@ -152,6 +162,8 @@ async def user_setpoint(self, instance, value): await self.motor_done_move.write(value='True') + self.putter_lock.release() + return value diff --git a/nslsii/tests/test_epicsmotor_ioc.py b/nslsii/tests/test_epicsmotor_ioc.py index e68c4d3c..6e62da9d 100644 --- a/nslsii/tests/test_epicsmotor_ioc.py +++ b/nslsii/tests/test_epicsmotor_ioc.py @@ -20,7 +20,7 @@ def ioc_sim(request): stdout=stdout, stdin=stdin, env=os.environ) - print(f'nslsii.iocs.epc_two_state_ioc_sim is now running') + print(f'nslsii.iocs.epics_motor_ioc_sim is now running') time.sleep(5) @@ -40,10 +40,6 @@ def ioc_sim(request): @pytest.mark.usefixtures('ioc_sim') class TestIOC: - def test_zero_division(self): - with pytest.raises(ZeroDivisionError): - 1 / 0 - def test_initial_values(self): assert self.mtr.egu == 'mm'