Skip to content
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
161 changes: 161 additions & 0 deletions nslsii/iocs/epics_motor_ioc_sim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
from caproto.server import pvproperty, PVGroup
from caproto.server import ioc_arg_parser, run
from caproto import ChannelType
import time


class EpicsMotorIOC(PVGroup):
"""
Simulates ophyd.EpicsMotor.
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)

_dir_states = ['neg', 'pos']
_false_true_states = ['False', 'True']

_step_size = 0.1

# position

_upper_alarm_limit = 10.0
_lower_alarm_limit = -10.0

_upper_warning_limit = 9.0
_lower_warning_limit = -9.0

_upper_ctrl_limit = 11.0
_lower_ctrl_limit = -11.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, read_only=True,
dtype=ChannelType.DOUBLE,
name='.VELO')
acceleration = pvproperty(value=_acceleration, read_only=True,
dtype=ChannelType.DOUBLE,
name='.ACCL')
motor_egu = pvproperty(value=_egu, read_only=True,
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):
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 = 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')

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)
9 changes: 9 additions & 0 deletions nslsii/tests/temperature_controllers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import subprocess
import os
import sys
import time
import pytest


Expand All @@ -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')
Expand Down
95 changes: 95 additions & 0 deletions nslsii/tests/test_epicsmotor_ioc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import os
import subprocess
import sys
import time

from ophyd.epics_motor import EpicsMotor
from ophyd.status import MoveStatus


def test_epicsmotor_ioc():

stdout = subprocess.PIPE
stdin = None

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'

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

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
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)

try:
move_status = mtr.move(target_val, timeout=mvtime+1)
except RuntimeError:
pass

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
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 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()
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ def test_epstwostate_ioc():
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)
Expand Down