class Mk3chopperTests(unittest.TestCase): # Remote access modes LOCAL = "LOCAL" REMOTE = "REMOTE" COMP_MODE_PV = "COMP:MODE" def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) # Comp mode is on a slow 10s read. Needs a longer timeout than default self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=30) self.ca.assert_that_pv_exists(self.COMP_MODE_PV) def test_WHEN_ioc_is_in_remote_mode_THEN_it_has_no_alarm(self): # In RECSIM, switch to remote explicitly. DEVSIM can only have remote mode so no switch needed if IOCRegister.uses_rec_sim: # Bug in CA channel. Reports invalid alarm severity if you set enum directly self.ca.set_pv_value("SIM:{}.VAL".format(self.COMP_MODE_PV), 1) self.ca.assert_that_pv_is(self.COMP_MODE_PV, self.REMOTE) self.ca.assert_that_pv_alarm_is(self.COMP_MODE_PV, ChannelAccess.Alarms.NONE) @skip_if_devsim("Can't switch to local mode in DEVSIM") def test_WHEN_ioc_is_in_local_mode_THEN_it_has_a_major_alarm(self): # Bug in CA channel. Reports invalid alarm severity if you set enum directly self.ca.set_pv_value("SIM:{}.VAL".format(self.COMP_MODE_PV), 0) self.ca.assert_that_pv_is(self.COMP_MODE_PV, self.LOCAL) self.ca.assert_that_pv_alarm_is(self.COMP_MODE_PV, ChannelAccess.Alarms.MAJOR)
class InhibitrTests(unittest.TestCase): """ Tests for the Inhibitr IOC. """ def setUp(self): self._ioc = IOCRegister.get_running(IOC_PREFIX) self.ca = ChannelAccess(default_timeout=20) self.values = ["SIMPLE:VALUE1:SP", "SIMPLE:VALUE2:SP"] for pv in self.values: self.ca.assert_that_pv_exists(pv) self.reset_values_to_zero() def reset_values_to_zero(self): for val in self.values: self.ca.set_pv_value(val, 0) self.ca.assert_that_pv_is(val, 0) for val in self.values: self.ca.assert_that_pv_is(val + ".DISP", "0") def test_GIVEN_both_inputs_are_zero_WHEN_setting_either_input_THEN_this_is_allowed( self): for val in self.values: self.ca.assert_that_pv_is("{}.DISP".format(val), "0") @unstable_test() def test_GIVEN_one_input_is_one_WHEN_setting_other_value_to_one_THEN_this_is_not_allowed( self): self.ca.set_pv_value("SIMPLE:VALUE1:SP", 1) self.ca.assert_that_pv_is("SIMPLE:VALUE1:SP", 1) # When value1 is set to non-zero, the disallowed value of value2 should be 1 # i.e 'Not allowed to set this value to non-zero' self.ca.assert_that_pv_is("SIMPLE:VALUE2:SP.DISP", "1")
class SamposTests(unittest.TestCase): """ Tests for the sampos IOC. """ test_values = [0, 10] axes = ['X', 'Y', 'Z', 'W', 'S'] def setUp(self): self._ioc = IOCRegister.get_running("SAMPOS") self.ca = ChannelAccess(20, device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("DISABLE", timeout=30) def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") def test_WHEN_values_are_set_THEN_readbacks_update(self): for axis in self.axes: for value in self.test_values: self.ca.assert_setting_setpoint_sets_readback( value, readback_pv="{}".format(axis), set_point_pv="{}:SP".format(axis)) def test_WHEN_values_are_set_THEN_setpoint_readbacks_update(self): for axis in self.axes: for value in self.test_values: self.ca.assert_setting_setpoint_sets_readback( value, readback_pv="{}:SP:RBV".format(axis), set_point_pv="{}:SP".format(axis))
def setUp(self): self._ioc = IOCRegister.get_running("GALIL_01") ca_mot = ChannelAccess() ca_mot.assert_that_pv_exists("MOT:MTR0103", timeout=30) ca_mot.assert_setting_setpoint_sets_readback(DEFAULT_MOTOR_RESOLUTION, set_point_pv="MOT:MTR0103.MRES", readback_pv="MOT:MTR0103.MRES", ) self.ca = ChannelAccess(device_prefix=PREFIX) self.ca.assert_that_pv_exists("VEL:SP", timeout=30)
class FinsTests(unittest.TestCase): """ Tests for the Fins IOC. """ def setUp(self): self._ioc = IOCRegister.get_running("FINS_01") self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) def test_GIVEN_fins_THEN_has_flow_pv(self): self.ca.assert_that_pv_exists("BENCH:FLOW1")
class FmrTests(unittest.TestCase): """ Tests for the fmr IOC. """ def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.ca = ChannelAccess(20, device_prefix=DEVICE_PREFIX) def test_WHEN_ioc_is_started_THEN_PV_exists(self): self.ca.assert_that_pv_exists("FMR:ACTIVITY", timeout=30)
class Wm323Tests(unittest.TestCase): """ Tests for the wm323 IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "wm323", DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=20) self.ca.assert_that_pv_exists("DISABLE") def test_WHEN_ioc_is_started_THEN_it_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") @skip_if_recsim("Requires emulator logic so not supported in RECSIM") def test_WHEN_ioc_is_started_THEN_pump_type_pv_correct(self): self.ca.assert_that_pv_is("TYPE", "323Du") @parameterized.expand([('On low limit', SPEED_LOW_LIMIT), ('Intermediate value', 42), ('On high limit', SPEED_HIGH_LIMIT)]) def test_WHEN_speed_setpoint_is_sent_THEN_readback_updates(self, _, value): self.ca.assert_setting_setpoint_sets_readback(value, 'SPEED') @parameterized.expand([('Low limit', SPEED_LOW_LIMIT, SPEED_LOW_LIMIT - 1), ('High limit', SPEED_HIGH_LIMIT, SPEED_HIGH_LIMIT + 1)]) def test_WHEN_speed_setpoint_is_set_outside_max_limits_THEN_setpoint_within( self, _, limit, value): self.ca.set_pv_value("SPEED:SP", value) self.ca.assert_that_pv_is("SPEED:SP", limit) def test_WHEN_direction_setpoint_is_sent_THEN_readback_updates(self): for mode in ["Clockwise", "Anti-Clockwise"]: self.ca.assert_setting_setpoint_sets_readback(mode, "DIRECTION") def test_WHEN_status_setpoint_is_sent_THEN_readback_updates(self): for mode in ["Running", "Stopped"]: self.ca.assert_setting_setpoint_sets_readback(mode, "STATUS") @skip_if_recsim("Requires emulator logic so not supported in RECSIM") def test_GIVEN_pump_off_WHEN_set_pump_on_THEN_pump_turned_on(self): self.ca.set_pv_value("RUN:SP", "Run") self.ca.assert_that_pv_is("STATUS", "Running") @skip_if_recsim("Requires emulator logic so not supported in RECSIM") def test_GIVEN_pump_on_WHEN_set_pump_off_THEN_pump_paused(self): self.ca.set_pv_value("RUN:SP", "Run") self.ca.assert_that_pv_is("STATUS", "Running") self.ca.set_pv_value("STOP:SP", "Stop") self.ca.assert_that_pv_is("STATUS", "Stopped")
class Ag53220ATests(unittest.TestCase): """ Tests for the Ag53220A IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "Ag53220A", DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("DISABLE", timeout=30) def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED")
class FermiChopperLifterTests(unittest.TestCase): """ Tests for the fermi chopper lift. There isn't any logic to test in this IOC so this is really just a test that the DB records get loaded. """ def setUp(self): self._ioc = IOCRegister.get_running("GALIL_01") self.ca = ChannelAccess(default_timeout=30) self.ca.assert_that_pv_exists("MOT:CHOPLIFT:STATUS", timeout=60) def test_WHEN_ioc_is_run_THEN_status_record_exists(self): # Simulated galil user variables are initialized to zero which maps to "Unknown". self.ca.assert_that_pv_is("MOT:CHOPLIFT:STATUS", "Unknown")
class ChannelTests(unittest.TestCase): def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "keithley_2700", DEVICE_PREFIX) self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("IDN") self.ca.set_pv_value("BUFF:CLEAR:SP", "") self.ca.assert_that_pv_is("BUFF:AUTOCLEAR", "ON") if not IOCRegister.uses_rec_sim: self._lewis.backdoor_set_on_device("simulate_readings", False) @unstable_test() @skip_if_recsim("Cannot use lewis backdoor in recsim") def test_GIVEN_empty_buffer_WHEN_reading_inserted_THEN_channel_PVs_get_correct_values( self): _reset_drift_channels(self) reading = "+1386.05,+4000,101" expected_values = { 'read': 1386.05, 'time': 4000, 'temp101': 47.424, 'drift': 0, } # GIVEN self.ca.set_pv_value("BUFF:CLEAR:SP", "") # WHEN _insert_reading(self, [reading]) # THEN self.ca.assert_that_pv_is_number("CHNL:101:READ", expected_values['read'], tolerance=READ_TOLERANCE) self.ca.assert_that_pv_is_number("CHNL:101:TIME", expected_values['time'], tolerance=TIME_TOLERANCE) self.ca.assert_that_pv_is_number("CHNL:101:TEMP", expected_values['temp101'], tolerance=TEMP_TOLERANCE) self.ca.assert_that_pv_is_number("CHNL:101:DRIFT", expected_values['drift'], tolerance=DRIFT_TOLERANCE) @skip_if_recsim("Alarm invalid in recsim") def test_GIVEN_temperature_out_of_bounds_THEN_alarm_is_major(self): #GIVEN reading = "+939,+10,101" _insert_reading(self, [reading]) #THEN self.ca.assert_that_pv_alarm_is("CHNL:101:TEMP:CHECK", self.ca.Alarms.MAJOR)
class GemJawsTests(unittest.TestCase): """ Tests for the gem beamscraper jaws """ def setUp(self): self._ioc = IOCRegister.get_running("gem_jaws") self.ca = ChannelAccess(default_timeout=30) [self.ca.assert_that_pv_exists(mot) for mot in all_motors] def _test_readback(self, underlying_motor, calibrated_axis, to_read_func, x): self.ca.set_pv_value(underlying_motor, x, wait=True) self.ca.assert_that_pv_is_number(underlying_motor + ".DMOV", 1) # Wait for axis to finish moving self.ca.assert_that_pv_is_number(calibrated_axis + ".RBV", to_read_func(x), TOLERANCE) def _test_set_point(self, underlying_motor, calibrated_axis, to_write_func, x): self.ca.set_pv_value(calibrated_axis, x, wait=True) self.ca.assert_that_pv_is_number(underlying_motor + ".DMOV", 1) # Wait for axis to finish moving self.ca.assert_that_pv_is_number(underlying_motor + ".VAL", to_write_func(x), TOLERANCE) def test_WHEN_underlying_quadratic_motor_set_to_a_position_THEN_calibrated_axis_as_expected(self): motors = {MOTOR_E: UNDERLYING_MTR_EAST, MOTOR_W: UNDERLYING_MTR_WEST} for mot, underlying in motors.items(): for position in TEST_POSITIONS: self._test_readback(underlying, mot, calc_expected_quad_read, position) def test_WHEN_calibrated_quadratic_motor_set_to_a_position_THEN_underlying_motor_as_expected(self): motors = {MOTOR_E: UNDERLYING_MTR_EAST, MOTOR_W: UNDERLYING_MTR_WEST} for mot, underlying in motors.items(): for position in TEST_POSITIONS: self._test_set_point(underlying, mot, calc_expected_quad_write, position) def test_WHEN_underlying_linear_motor_set_to_a_position_THEN_calibrated_axis_as_expected(self): motors = {MOTOR_N: UNDERLYING_MTR_NORTH, MOTOR_S: UNDERLYING_MTR_SOUTH} for mot, underlying in motors.items(): for position in TEST_POSITIONS: self._test_readback(underlying, mot, calc_expected_linear_read, position) def test_WHEN_calibrated_linear_motor_set_to_a_position_THEN_underlying_motor_as_expected(self): motors = {MOTOR_N: UNDERLYING_MTR_NORTH, MOTOR_S: UNDERLYING_MTR_SOUTH} for mot, underlying in motors.items(): for position in TEST_POSITIONS: self._test_set_point(underlying, mot, calc_expected_linear_write, position) def test_GIVEN_quad_calibrated_motor_limits_set_THEN_underlying_motor_limits_set(self): for lim in [".LLM", ".HLM"]: underlying_limit = self.ca.get_pv_value(UNDERLYING_MTR_WEST + lim) self.ca.assert_that_pv_is_number(MOTOR_W + lim, calc_expected_quad_read(underlying_limit), TOLERANCE) def test_GIVEN_linear_calibrated_motor_limits_set_THEN_underlying_motor_limits_set(self): for lim in [".LLM", ".HLM"]: underlying_limit = self.ca.get_pv_value(UNDERLYING_MTR_NORTH + lim) self.ca.assert_that_pv_is_number(MOTOR_N + lim, calc_expected_linear_read(underlying_limit), TOLERANCE)
class GalilmulTests(unittest.TestCase): """ Tests for loading multiple motor controllers into a single IOC """ def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.assertIsNotNone(self._ioc) self.ca = ChannelAccess(device_prefix=None, default_timeout=20) def test_GIVEN_ioc_started_THEN_pvs_for_all_motors_exist(self): for controller in ("01", "02"): for motor in ["{:02d}".format(mtr) for mtr in range(1, 8 + 1)]: self.ca.assert_that_pv_exists("MOT:MTR{}{}".format( controller, motor)) def test_GIVEN_ioc_started_THEN_axes_for_all_motors_exist(self): for controller in (1, 2): for motor in range(1, 8 + 1): self.ca.assert_that_pv_exists("GALILMUL_01:{}:AXIS{}".format( controller, motor)) def test_GIVEN_axis_moved_THEN_other_axes_do_not_move(self): # This is to check that axes are independent, i.e. they're not accidentally using the same underlying driver # Set all motors to zero for controller in ("01", "02"): for motor in ["{:02d}".format(mtr) for mtr in range(1, 8 + 1)]: self.ca.set_pv_value("MOT:MTR{}{}".format(controller, motor), 0) self.ca.assert_that_pv_is( "MOT:MTR{}{}".format(controller, motor), 0) # Move motor 0101 self.ca.set_pv_value("MOT:MTR0101", 20) self.ca.assert_that_pv_is("MOT:MTR0101", 20) # Check all other motors are still at zero for controller in ("01", "02"): for motor in ["{:02d}".format(mtr) for mtr in range(1, 8 + 1)]: if controller == "01" and motor == "01": continue self.ca.assert_that_pv_is( "MOT:MTR{}{}".format(controller, motor), 0)
class DriftTests(unittest.TestCase): # Tuple format (reading, temperature, expected_drift) drift_test_data = ( ('+1386.05,+4000,101', 47.424, 0.), ('+1387.25,+4360,101', 47.243, -0.000666667), ('+1388.51,+4720,101', 47.053, -0.00135333), ('+1389.79,+5080,101', 46.860, -0.00202627), ('+1391.07,+5440,101', 46.667, -0.00268574), ('+1392.35,+5800,101', 46.474, -0.00333203), ('+1393.71,+6160,101', 46.269, -0.00399872), ('+1395.01,+6520,101', 46.072, -0.00461874), ('+1396.38,+6880,101', 45.866, -0.0052597), ('+1397.70,+7240,101', 45.667, -0.00585451), ) def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "keithley_2700", DEVICE_PREFIX) self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("IDN") self.ca.set_pv_value("BUFF:CLEAR:SP", "") if not IOCRegister.uses_rec_sim: self._lewis.backdoor_set_on_device("simulate_readings", False) @skip_if_recsim("Cannot use lewis backdoor in recsim") def test_GIVEN_empty_buffer_WHEN_values_added_THEN_temp_AND_drift_correct( self, test_data=drift_test_data): _reset_drift_channels(self) readings = [ r[0] for r in test_data ] # extract reading strings from test data to insert to buffer self.ca.set_pv_value("BUFF:SIZE:SP", 1000) # GIVEN in setup # WHEN for i in range(0, len(test_data)): _insert_reading(self, [readings[i]]) # THEN self.ca.assert_that_pv_is_number("CHNL:101:DRIFT", test_data[i][2], tolerance=DRIFT_TOLERANCE) self.ca.assert_that_pv_is_number("CHNL:101:TEMP", test_data[i][1], tolerance=TEMP_TOLERANCE)
class Ag3631aTests(unittest.TestCase): """ Tests for the AG3631A. """ def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.ca = ChannelAccess(20, DEVICE_PREFIX) self.ca.assert_that_pv_exists("DISABLE", timeout=30) def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") def test_GIVEN_voltage_set_WHEN_read_THEN_voltage_is_as_set(self): set_voltage = 123.456 self.ca.set_pv_value("VOLT:SP", set_voltage) self.ca.assert_that_pv_is("VOLT:SP", set_voltage) self.ca.assert_that_pv_is("VOLT", set_voltage)
class CAENv895Tests(unittest.TestCase): """ Tests for CAENv895 """ def setUp(self): self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) self.ca_np = ChannelAccess( default_timeout=30) # no device name prefix in PV self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.ca.assert_that_pv_exists("CR0:BOARD_ID") def test_GIVEN_ioc_WHEN_startup_complete_THEN_defaults_loaded(self): self.ca.assert_that_pv_is("CR0:CRATE", 0) self.ca.assert_that_pv_is("CR0:BOARD_ID", 0) # check values from configmenu config "ioctestdefaults" file loaded in pre_ioc_launch_hook() self.ca.assert_that_pv_is("CR0:C0:CH3:THOLD:SP", 75) self.ca.assert_that_pv_is("CR0:C1:CH0:ENABLE:SP", "YES") self.ca_np.assert_that_pv_is( "AS:{}:vmeconfigMenu:currName".format(DEVICE_PREFIX), "ioctestdefaults") self.ca_np.assert_that_pv_is( "AS:{}:vmeconfigMenu:status".format(DEVICE_PREFIX), "Success") self.ca_np.assert_that_pv_is( "AS:{}:vmeconfigMenu:busy".format(DEVICE_PREFIX), "Done") def test_GIVEN_output_widths_WHEN_output_widths_set_THEN_width_read_back( self): # GIVEN width_0_to_7 = 10 width_8_to_15 = 13 # WHEN self.ca.set_pv_value("CR0:C0:OUT:WIDTH:0_TO_7:SP", width_0_to_7) self.ca.set_pv_value("CR0:C0:OUT:WIDTH:8_TO_15:SP", width_8_to_15) # THEN self.ca.assert_that_pv_is("SIM:CR0:C0:OUT:WIDTH:0_TO_7", width_0_to_7) self.ca.assert_that_pv_is("SIM:CR0:C0:OUT:WIDTH:8_TO_15", width_8_to_15)
class EgxcolimTests(unittest.TestCase): """ Tests for the egxcolim IOC. """ directions = ["NORTH", "SOUTH"] axes = ["X"] def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.ca = ChannelAccess(20, device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("DISABLE", timeout=30) def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") def test_WHEN_setpoint_is_set_THEN_readback_updates(self): for direction in self.directions: for axis in self.axes: self.ca.assert_setting_setpoint_sets_readback( 123, "{direction}:{axis}".format(direction=direction, axis=axis))
class Knrk6Tests(unittest.TestCase): """ Tests for the Knrk6 IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( DEVICE_NAME, DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("POSITION", timeout=30) self._lewis.backdoor_run_function_on_device("reset") def test_GIVEN_home_position_THEN_home_position_returned(self): expected_value = 1 self.ca.set_pv_value("POSITION:SP", expected_value) self.ca.assert_that_pv_is("POSITION", expected_value, timeout=5) def test_GIVEN_set_position_THEN_moved_to_correct_position(self): expected_value = 6 self.ca.set_pv_value("POSITION:SP", expected_value) self.ca.assert_that_pv_is("POSITION", expected_value, timeout=5) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_device_not_connected_WHEN_get_error_THEN_alarm(self): self._lewis.backdoor_set_on_device('connected', False) self.ca.assert_that_pv_alarm_is('POSITION', ChannelAccess.Alarms.INVALID, timeout=5) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_an_input_error_WHEN_open_file_THEN_error_str_returned(self): self._lewis.backdoor_set_on_device("input_correct", False) expected_value = "Position change slow" self.ca.set_pv_value("POSITION:SP", 5) self.ca.assert_that_pv_is("ERROR", expected_value, timeout=5)
class VerticalJawsTests(unittest.TestCase): def set_motor_speeds(self): self.ca.set_pv_value(UNDERLYING_MTR_NORTH + ".VMAX", 20) self.ca.set_pv_value(UNDERLYING_MTR_NORTH + ".VELO", 20) self.ca.set_pv_value(UNDERLYING_MTR_SOUTH + ".VMAX", 20) self.ca.set_pv_value(UNDERLYING_MTR_SOUTH + ".VELO", 20) """ Tests for vertical jaws """ def setUp(self): self._ioc = IOCRegister.get_running("vertical_jaws") self.ca = ChannelAccess(default_timeout=30) self.set_motor_speeds() [self.ca.assert_that_pv_exists(mot) for mot in all_motors] @parameterized.expand(parameterized_list(TEST_POSITIONS)) def test_WHEN_south_jaw_setpoint_changed_THEN_south_jaw_moves( self, _, value): self.ca.assert_setting_setpoint_sets_readback(value, MOTOR_S, MOTOR_S_SP) @parameterized.expand(parameterized_list(TEST_POSITIONS)) def test_WHEN_north_jaw_setpoint_changed_THEN_north_jaw_moves( self, _, value): self.ca.assert_setting_setpoint_sets_readback(value, MOTOR_N, MOTOR_N_SP) def test_GIVEN_jaws_closed_at_centre_WHEN_gap_opened_THEN_north_and_south_jaws_move( self): # GIVEN self.ca.assert_that_pv_is("MOT:JAWS1:VGAP", 0) self.ca.assert_that_pv_is("MOT:JAWS1:VCENT", 0) # WHEN self.ca.set_pv_value("MOT:JAWS1:VGAP:SP", 10) # THEN self.ca.assert_that_pv_is(MOTOR_S, -5) self.ca.assert_that_pv_is(MOTOR_N, 5) def test_GIVEN_jaws_open_WHEN_jaws_closed_THEN_jaws_close(self): # GIVEN self.ca.set_pv_value("MOT:JAWS1:VGAP:SP", 10) # WHEN self.ca.set_pv_value("MOT:JAWS1:VGAP:SP", 0) # THEN self.ca.assert_that_pv_is("MOT:JAWS1:VGAP", 0)
class JawsManagerBase(object): """ Base classes for all jaws manager tests. """ def setUp(self): self._ioc = IOCRegister.get_running("GALIL_01") self.ca = ChannelAccess() self.ca.assert_that_pv_exists("MOT:MTR0101", timeout=30) for jaw in range(1, self.get_num_of_jaws() + 1): self.ca.assert_that_pv_exists(UNDERLYING_GAP_SP.format(jaw, "V"), timeout=30) self.ca.assert_that_pv_exists(UNDERLYING_GAP_SP.format(jaw, "H"), timeout=30) self.ca.assert_that_pv_exists(self.get_sample_pv() + ":{}GAP:SP".format("V"), timeout=30) def get_sample_pv(self): return "JAWMAN:SAMPLE" @abc.abstractmethod def get_num_of_jaws(self): pass def _test_WHEN_centre_is_changed_THEN_centres_of_all_jaws_follow_and_gaps_unchanged( self, direction): expected_gaps = [ self.ca.get_pv_value(UNDERLYING_GAP_SP.format(jaw, direction)) for jaw in range(1, self.get_num_of_jaws() + 1) ] self.ca.set_pv_value( self.get_sample_pv() + ":{}CENT:SP".format(direction), 10) for jaw in range(1, self.get_num_of_jaws() + 1): self.ca.assert_that_pv_is_number( UNDERLYING_CENT_SP.format(jaw, direction), 10, 0.1) self.ca.assert_that_pv_is_number( UNDERLYING_GAP_SP.format(jaw, direction), expected_gaps[jaw - 1], 0.1) def _test_WHEN_sizes_at_moderator_and_sample_changed_THEN_centres_of_all_jaws_unchanged( self, direction): # Set up jaws initially to have "custom" centre. centre = 12.34 for jaw in range(1, self.get_num_of_jaws() + 1): # Set to centre * jaw so that each jaw is given a different centre self.ca.set_pv_value(UNDERLYING_CENT_SP.format(jaw, direction), centre * jaw) # Now change size at sample + moderator self.ca.set_pv_value( "{}:{}GAP:SP".format(self.get_sample_pv(), direction), 11.111) self.ca.set_pv_value(MOD_GAP.format(direction), 22.222) # Assert that centres are unchanged for jaw in range(1, self.get_num_of_jaws() + 1): self.ca.assert_that_pv_is_number( UNDERLYING_CENT_SP.format(jaw, direction), centre * jaw, 0.001) def _test_WHEN_sample_gap_set_THEN_other_jaws_as_expected( self, direction, sample_gap, expected): self.ca.set_pv_value( self.get_sample_pv() + ":{}GAP:SP".format(direction), sample_gap) for i, exp in enumerate(expected): self.ca.assert_that_pv_is_number(UNDERLYING_GAP_SP.format( i + 1, direction), exp, 0.1, timeout=1)
class CryoSMSTests(unittest.TestCase): def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( EMULATOR_NAME, DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=10) if IOCRegister.uses_rec_sim: self.ca.assert_that_pv_exists("DISABLE", timeout=30) else: self.ca.assert_that_pv_is("INIT", "Startup complete", timeout=60) self._lewis.backdoor_set_on_device("mid_target", 0) self._lewis.backdoor_set_on_device("output", 0) self.ca.set_pv_value("MID:SP", 0) self.ca.set_pv_value("START:SP", 1) self.ca.set_pv_value("PAUSE:SP", 1) self._lewis.backdoor_set_on_device("output", 0) self.ca.set_pv_value("ABORT:SP", 1) self.ca.set_pv_value("PAUSE:SP", 0) self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON TARGET") self.ca.assert_that_pv_is("OUTPUT:RAW", 0) @skip_if_recsim("Cannot properly simulate device startup in recsim") def test_GIVEN_certain_macros_WHEN_IOC_loads_THEN_correct_values_initialised( self): expectedValues = { "OUTPUT:SP": 0, "OUTPUT": 0, "OUTPUT:COIL": 0, "OUTPUT:PERSIST": 0, "OUTPUT:VOLT": 0, "RAMP:RATE": 1.12, "READY": 1, "RAMP:RAMPING": 0, "TARGET:TIME": 0, "STAT": "", "HEATER:STAT": "OFF", "START:SP.DISP": "0", "PAUSE:SP.DISP": "0", "ABORT:SP.DISP": "0", "OUTPUT:SP.DISP": "0", "MAGNET:MODE.DISP": "1", "RAMP:LEADS.DISP": "1", } failedPVs = [] for PV in expectedValues: try: self.ca.assert_that_pv_is(PV, expectedValues[PV], timeout=5) except Exception as e: failedPVs.append(e.message) if failedPVs: self.fail("The following PVs generated errors:\n{}".format( "\n".join(failedPVs))) def test_GIVEN_outputmode_sp_correct_WHEN_outputmode_sp_written_to_THEN_outputmode_changes( self): # For all other tests, alongside normal operation, communication should be in amps self.ca.assert_setting_setpoint_sets_readback("TESLA", "OUTPUTMODE", "OUTPUTMODE:SP", timeout=10) self.ca.assert_setting_setpoint_sets_readback("AMPS", "OUTPUTMODE", "OUTPUTMODE:SP", timeout=10) @parameterized.expand(parameterized_list(TEST_RAMPS)) @skip_if_recsim("C++ driver can not correctly initialise in recsim") def test_GIVEN_psu_at_field_strength_A_WHEN_told_to_ramp_to_B_THEN_correct_rates_used( self, _, ramp_data): startPoint, endPoint = ramp_data[0] ramp_rates = ramp_data[1] # When setting output, convert from Gauss to Amps by dividing by 10000 and T_TO_A, also ensure sign handled # correctly sign = 1 if startPoint >= 0 else -1 self._lewis.backdoor_run_function_on_device("switch_direction", [sign]) self._lewis.backdoor_set_on_device("output", abs(startPoint) / (0.037 * 10000)) self.ca.set_pv_value("MID:SP", endPoint) self.ca.set_pv_value("START:SP", 1) for rate in ramp_rates: self.ca.assert_that_pv_is("RAMP:RATE", rate, timeout=20) self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON TARGET", timeout=25) self.ca.assert_that_pv_is_within_range("OUTPUT", endPoint - 0.01, endPoint + 0.01) @skip_if_recsim("C++ driver can not correctly initialise in recsim") def test_GIVEN_IOC_not_ramping_WHEN_ramp_started_THEN_simulated_ramp_performed( self): self.ca.set_pv_value("MID:SP", 10000) self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("RAMP:STAT", "RAMPING", msg="Ramping failed to start") self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON TARGET", timeout=10) @skip_if_recsim("C++ driver can not correctly initialise in recsim") def test_GIVEN_IOC_ramping_WHEN_paused_and_unpaused_THEN_ramp_is_paused_resumed_and_completes( self): # GIVEN ramping self.ca.set_pv_value("MID:SP", 10000) self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("RAMP:STAT", "RAMPING") # Pauses when pause set to true self.ca.set_pv_value("PAUSE:SP", 1) self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON PAUSE", msg="Ramping failed to pause") self.ca.assert_that_pv_is_not( "RAMP:STAT", "HOLDING ON TARGET", timeout=5, msg="Ramp completed even though it should have paused") # Resumes when pause set to false, completes ramp self.ca.set_pv_value("PAUSE:SP", 0) self.ca.assert_that_pv_is("RAMP:STAT", "RAMPING", msg="Ramping failed to resume") self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON TARGET", timeout=10, msg="Ramping failed to complete") @skip_if_recsim("C++ driver can not correctly initialise in recsim") def test_GIVEN_IOC_ramping_WHEN_aborted_THEN_ramp_aborted(self): # Given Ramping self.ca.set_pv_value("MID:SP", 10000) self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("RAMP:STAT", "RAMPING") # Aborts when abort set to true, then hits ready again self.ca.set_pv_value("ABORT:SP", 1) self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON TARGET", timeout=10) @skip_if_recsim("C++ driver can not correctly initialise in recsim") def test_GIVEN_IOC_paused_WHEN_aborted_THEN_ramp_aborted(self): # GIVEN paused self.ca.set_pv_value("MID:SP", 10000) self.ca.set_pv_value("START:SP", 1) self.ca.set_pv_value("PAUSE:SP", 1) rampTarget = self.ca.get_pv_value("MID") self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON PAUSE", msg="Ramping failed to pause") # Aborts when abort set to true, then hits ready again self.ca.set_pv_value("ABORT:SP", 1) self.ca.assert_that_pv_is("RAMP:STAT", "HOLDING ON TARGET", timeout=10) self.ca.assert_that_pv_is_not("MID", rampTarget) @skip_if_recsim( "Test is to tell whether data from emulator is correctly received") def test_GIVEN_output_nonzero_WHEN_units_changed_THEN_output_raw_adjusts( self): # Check that it is currently working correctly in Amps self._lewis.backdoor_set_on_device("is_paused", True) self._lewis.backdoor_set_on_device("output", 1 / 0.037) # 1T (0.037 = T_TO_A) self.ca.assert_that_pv_is_number("OUTPUT:RAW", 1 / 0.037, 0.001) self.ca.assert_that_pv_is_number("OUTPUT", 10000, 1) # OUTPUT should remain in Gauss # Set outputmode to tesla self.ca.set_pv_value("OUTPUTMODE:SP", "TESLA") self.ca.assert_that_pv_is_number("OUTPUT:RAW", 1, 0.001) self.ca.assert_that_pv_is_number("OUTPUT", 10000, 1) # Confirm functionality returns to normal when going back to Amps self.ca.set_pv_value("OUTPUTMODE:SP", "AMPS") self.ca.assert_that_pv_is_number("OUTPUT:RAW", 1 / 0.037, 0.001) self.ca.assert_that_pv_is_number("OUTPUT", 10000, 1)
class LoqApertureTests(unittest.TestCase): """ Tests for the LOQ Aperture """ def setUp(self): self._ioc = IOCRegister.get_running("GALIL_01") self.ca = ChannelAccess(default_timeout=30) self.ca.assert_that_pv_exists(MOTOR, timeout=60) self.ca.assert_that_pv_exists(CLOSESTSHUTTER) self.ca.assert_that_pv_exists(CLOSEAPERTURE) # Closest positions defined in ticket 3623 @parameterized.expand([ ("Aperture_large", 0, 1), ("Stop_01", 1, 1), ("Aperture_medium", 2, 3), ("Stop_02", 3, 3), ("Aperture_small", 4, 3), ]) def test_GIVEN_motor_on_an_aperture_position_WHEN_motor_set_to_closest_beamstop_THEN_motor_moves_to_closest_beamstop( self, start_position, start_index, closest_stop): # GIVEN self.ca.set_pv_value(POSITION_SP, start_index) self.ca.assert_that_pv_is_number(POSITION_INDEX, start_index, tolerance=TOLERANCE) self.ca.assert_that_pv_is_number(MOTOR, MOTION_SETPOINT[start_position], tolerance=TOLERANCE) # WHEN self.ca.process_pv(CLOSEAPERTURE) # THEN self.ca.assert_that_pv_is_number(CLOSESTSHUTTER, closest_stop) self.ca.assert_that_pv_is_number(POSITION_INDEX, closest_stop, timeout=5) self.ca.assert_that_pv_is_number( MOTOR, list(MOTION_SETPOINT.values())[closest_stop], tolerance=TOLERANCE) # Closest positions defined in ticket 3623 @parameterized.expand([ ("Aperture_large", 0, 1), ("Stop_01", 1, 1), ("Aperture_medium", 2, 3), ("Stop_02", 3, 3), ("Aperture_small", 4, 3), ]) def test_GIVEN_motor_off_setpoint_WHEN_motor_set_to_closest_beamstop_THEN_motor_moves_to_closest_beamstop( self, _, start_index, closest_stop): # GIVEN # Move 25 per cent forwards and backwards off centre of setpoint for fraction_moved_off_setpoint in [0.25, -0.25]: initial_position = list(MOTION_SETPOINT.values())[start_index] + ( fraction_moved_off_setpoint * SETPOINT_GAP) self.ca.set_pv_value(MOTOR, initial_position) self.ca.assert_that_pv_is_number(MOTOR, initial_position, tolerance=TOLERANCE) # This assertion ensures that this calc record has updated with the closest beam stop position self.ca.assert_that_pv_is_number(CLOSESTSHUTTER, closest_stop) # WHEN self.ca.process_pv(CLOSEAPERTURE) # THEN self.ca.assert_that_pv_is_number(CLOSESTSHUTTER, closest_stop) self.ca.assert_that_pv_is_number(POSITION_INDEX, closest_stop, timeout=5) self.ca.assert_that_pv_is_number( MOTOR, list(MOTION_SETPOINT.values())[closest_stop], tolerance=TOLERANCE)
class ReflTests(unittest.TestCase): """ Tests for reflectometry server """ def setUp(self): self._ioc = IOCRegister.get_running("refl") self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) self.ca_galil = ChannelAccess(default_timeout=30, device_prefix="MOT") self.ca_cs = ChannelAccess(default_timeout=30, device_prefix="CS") self.ca_no_prefix = ChannelAccess() self.ca_cs.set_pv_value("MOT:STOP:ALL", 1) self.ca_cs.assert_that_pv_is("MOT:MOVING", 0, timeout=60) self.ca.set_pv_value("BL:MODE:SP", "NR") self.ca.set_pv_value("PARAM:S1:SP", 0) self.ca.set_pv_value("PARAM:S3:SP", 0) self.ca.set_pv_value("PARAM:SMANGLE:SP", 0) self.ca.set_pv_value("PARAM:SMOFFSET:SP", 0) self.ca.set_pv_value("PARAM:SMINBEAM:SP", "OUT") self.ca.set_pv_value("PARAM:THETA:SP", 0) self.ca.set_pv_value("PARAM:DET_POS:SP", 0) self.ca.set_pv_value("PARAM:DET_ANG:SP", 0) self.ca.set_pv_value("PARAM:DET_LONG:SP", 0) self.ca.set_pv_value("PARAM:S3INBEAM:SP", "IN") self.ca.set_pv_value("PARAM:CHOICE:SP", "MTR0205") self.ca_galil.set_pv_value("MTR0207", 0) self.ca.set_pv_value("PARAM:NOTINMODE:SP", 0) self.ca.set_pv_value("BL:MODE:SP", "NR") self.ca.set_pv_value("BL:MOVE", 1) self.ca_galil.assert_that_pv_is("MTR0105", 0.0) self.ca_cs.assert_that_pv_is("MOT:MOVING", 0, timeout=60) def set_up_velocity_tests(self, velocity): self.ca_galil.set_pv_value("MTR0102.VELO", velocity) self.ca_galil.set_pv_value("MTR0104.VELO", velocity) self.ca_galil.set_pv_value( "MTR0105.VELO", FAST_VELOCITY) # Remove angle as a speed limiting factor def _check_param_pvs(self, param_name, expected_value): self.ca.assert_that_pv_is_number("PARAM:%s" % param_name, expected_value, 0.01) self.ca.assert_that_pv_is_number("PARAM:%s:SP" % param_name, expected_value, 0.01) self.ca.assert_that_pv_is_number("PARAM:%s:SP:RBV" % param_name, expected_value, 0.01) @contextmanager def _assert_pv_monitors(self, param_name, expected_value): with self.ca.assert_that_pv_monitor_is_number("PARAM:%s" % param_name, expected_value, 0.01), \ self.ca.assert_that_pv_monitor_is_number("PARAM:%s:SP" % param_name, expected_value, 0.01), \ self.ca.assert_that_pv_monitor_is_number("PARAM:%s:SP:RBV" % param_name, expected_value, 0.01): yield def test_GIVEN_loaded_WHEN_read_status_THEN_status_ok(self): self.ca.assert_that_pv_is("STAT", "OKAY") def test_GIVEN_slit_with_beam_along_z_axis_WHEN_set_value_THEN_read_back_MTR_and_setpoints_moves_to_given_value( self): expected_value = 3.0 self.ca.set_pv_value("PARAM:S1:SP_NO_ACTION", expected_value) self.ca.assert_that_pv_is("PARAM:S1:SP_NO_ACTION", expected_value) self.ca.set_pv_value("BL:MOVE", 1) self.ca.assert_that_pv_is("PARAM:S1:SP:RBV", expected_value) self.ca_galil.assert_that_pv_is("MTR0101", expected_value) self.ca_galil.assert_that_pv_is("MTR0101.RBV", expected_value) self.ca.assert_that_pv_is("PARAM:S1", expected_value) def test_GIVEN_slit_with_beam_along_z_axis_WHEN_set_value_THEN_monitors_updated( self): expected_value = 3.0 self.ca.set_pv_value("PARAM:S1:SP_NO_ACTION", expected_value) self.ca.set_pv_value("BL:MOVE", 1) self.ca.assert_that_pv_monitor_is("PARAM:S1", expected_value) def test_GIVEN_theta_with_detector_and_slits3_WHEN_set_theta_THEN_values_are_all_correct_rbvs_updated_via_monitors_and_are_available_via_gets( self): theta_angle = 2 self.ca.set_pv_value("PARAM:THETA:SP", theta_angle) expected_s3_value = SPACING * tan(radians(theta_angle * 2.0)) with self._assert_pv_monitors("S1", 0.0), \ self._assert_pv_monitors("S3", 0.0), \ self._assert_pv_monitors("THETA", theta_angle), \ self._assert_pv_monitors("DET_POS", 0.0), \ self._assert_pv_monitors("DET_ANG", 0.0): self.ca.set_pv_value("BL:MOVE", 1) # s1 not moved self._check_param_pvs("S1", 0.0) self.ca_galil.assert_that_pv_is_number("MTR0101", 0.0, 0.01) # s3 moved in line self._check_param_pvs("S3", 0.0) self.ca_galil.assert_that_pv_is_number("MTR0102", expected_s3_value, 0.01) # theta set self._check_param_pvs("THETA", theta_angle) # detector moved in line self._check_param_pvs("DET_POS", 0.0) expected_det_value = 2 * SPACING * tan(radians(theta_angle * 2.0)) self.ca_galil.assert_that_pv_is_number("MTR0104", expected_det_value, 0.01) # detector angle faces beam self._check_param_pvs("DET_ANG", 0.0) expected_det_angle = 2.0 * theta_angle self.ca_galil.assert_that_pv_is_number("MTR0105", expected_det_angle, 0.01) def test_GIVEN_s3_in_beam_WHEN_disable_THEN_monitor_updates_and_motor_moves_to_out_of_beam_position( self): expected_value = "OUT" with self.ca.assert_that_pv_monitor_is("PARAM:S3INBEAM", expected_value): self.ca.set_pv_value("PARAM:S3INBEAM:SP_NO_ACTION", expected_value) self.ca.set_pv_value("BL:MOVE", 1) self.ca_galil.assert_that_pv_is("MTR0102", OUT_POSITION_HIGH) def test_GIVEN_mode_is_NR_WHEN_change_mode_THEN_monitor_updates_to_new_mode( self): expected_value = "POLARISED" with self.ca.assert_that_pv_monitor_is("BL:MODE", expected_value), \ self.ca.assert_that_pv_monitor_is("BL:MODE.VAL", expected_value): self.ca.set_pv_value("BL:MODE:SP", expected_value) @unstable_test() def test_GIVEN_new_parameter_setpoint_WHEN_triggering_move_THEN_SP_is_only_set_on_motor_when_difference_above_motor_resolution( self): target_mres = 0.001 pos_above_res = 0.01 pos_below_res = pos_above_res + 0.0001 self.ca_galil.set_pv_value("MTR0101.MRES", target_mres) with self.ca_galil.assert_that_pv_monitor_is_number("MTR0101.VAL", pos_above_res), \ self.ca_galil.assert_that_pv_monitor_is_number("MTR0101.RBV", pos_above_res): self.ca.set_pv_value("PARAM:S1:SP", pos_above_res) with self.ca_galil.assert_that_pv_monitor_is_number("MTR0101.VAL", pos_above_res), \ self.ca_galil.assert_that_pv_monitor_is_number("MTR0101.RBV", pos_above_res): self.ca.set_pv_value("PARAM:S1:SP", pos_below_res) def test_GIVEN_motor_velocity_altered_by_move_WHEN_move_completed_THEN_velocity_reverted_to_original_value( self): expected = INITIAL_VELOCITY self.set_up_velocity_tests(expected) self.ca.set_pv_value("PARAM:THETA:SP", 5) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 1, timeout=10) self.ca_galil.assert_that_pv_is("MTR0102.VELO", expected) self.ca_galil.assert_that_pv_is("MTR0104.DMOV", 1, timeout=10) self.ca_galil.assert_that_pv_is("MTR0104.VELO", expected) def test_GIVEN_motor_velocity_altered_by_move_WHEN_moving_THEN_velocity_altered( self): # Given a known initial velocity, confirm that on a move the velocity has changed for the axes self.set_up_velocity_tests(INITIAL_VELOCITY) self.ca.set_pv_value("PARAM:THETA:SP", 15) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 0, timeout=10) self.ca_galil.assert_that_pv_is_not("MTR0102.VELO", INITIAL_VELOCITY) self.ca_galil.assert_that_pv_is("MTR0104.DMOV", 0, timeout=10) self.ca_galil.assert_that_pv_is_not("MTR0104.VELO", INITIAL_VELOCITY) def test_GIVEN_motor_velocity_altered_by_move_WHEN_move_interrupted_THEN_velocity_reverted_to_original_value( self): expected = INITIAL_VELOCITY final_position = SPACING self.set_up_velocity_tests(expected) # move and wait for completion self.ca.set_pv_value("PARAM:THETA:SP", 22.5) self.ca_galil.set_pv_value("MTR0102.STOP", 1) self.ca_galil.set_pv_value("MTR0104.STOP", 1) self.ca_galil.set_pv_value("MTR0105.STOP", 1) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 1, timeout=2) self.ca_galil.assert_that_pv_is_not_number("MTR0102.RBV", final_position, tolerance=0.1) self.ca_galil.assert_that_pv_is("MTR0102.VELO", expected) self.ca_galil.assert_that_pv_is("MTR0104.DMOV", 1, timeout=2) self.ca_galil.assert_that_pv_is_not_number("MTR0104.RBV", 2 * final_position, tolerance=0.1) self.ca_galil.assert_that_pv_is("MTR0104.VELO", expected) def test_GIVEN_move_was_issued_while_different_move_already_in_progress_WHEN_move_completed_THEN_velocity_reverted_to_value_before_first_move( self): expected = INITIAL_VELOCITY self.set_up_velocity_tests(expected) self.ca_galil.set_pv_value("MTR0102", -4) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 0, timeout=1) self.ca.set_pv_value("PARAM:THETA:SP", 22.5) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 1, timeout=30) self.ca_galil.assert_that_pv_is("MTR0102.VELO", expected) def test_GIVEN_move_in_progress_WHEN_modifying_motor_velocity_THEN_velocity_reverted_to_value_before_modified_velocity( self): # The by-design behaviour (but maybe not expected by the user) is that if a velocity is sent during a move # then we ignore this and restore the cached value we had for the currently issued move. initial = INITIAL_VELOCITY altered = INITIAL_VELOCITY + 5 expected = INITIAL_VELOCITY self.set_up_velocity_tests(initial) self.ca.set_pv_value("PARAM:THETA:SP", 22.5) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 0, timeout=1) self.ca_galil.set_pv_value("MTR0102.VELO", altered) self.ca_galil.assert_that_pv_is("MTR0102.DMOV", 1, timeout=30) self.ca_galil.assert_that_pv_is("MTR0102.VELO", expected) def test_GIVEN_mode_is_NR_WHEN_change_mode_THEN_monitor_updates_to_new_mode_and_PVs_inmode_are_labeled_as_such( self): expected_mode_value = "TESTING" PARAM_PREFIX = "PARAM:" IN_MODE_SUFFIX = ":IN_MODE" expected_in_mode_value = "YES" expected_out_of_mode_value = "NO" with self.ca.assert_that_pv_monitor_is("BL:MODE", expected_mode_value), \ self.ca.assert_that_pv_monitor_is("BL:MODE.VAL", expected_mode_value): self.ca.set_pv_value("BL:MODE:SP", expected_mode_value) test_in_mode_param_names = ["S1", "S3", "THETA", "DET_POS", "S3INBEAM"] test_out_of_mode_params = ["DET_ANG", "THETA_AUTO"] for param in test_in_mode_param_names: self.ca.assert_that_pv_monitor_is( "{}{}{}".format(PARAM_PREFIX, param, IN_MODE_SUFFIX), expected_in_mode_value) for param in test_out_of_mode_params: self.ca.assert_that_pv_monitor_is( "{}{}{}".format(PARAM_PREFIX, param, IN_MODE_SUFFIX), expected_out_of_mode_value) def test_GIVEN_jaws_set_to_value_WHEN_change_sp_at_low_level_THEN_jaws_sp_rbv_does_not_change( self): expected_gap_in_refl = 0.2 expected_change_to_gap = 1.0 self.ca.set_pv_value("PARAM:S1HG:SP", expected_gap_in_refl) self.ca.assert_that_pv_is_number("PARAM:S1HG", expected_gap_in_refl, timeout=15, tolerance=MOTOR_TOLERANCE) self.ca_galil.set_pv_value("JAWS1:HGAP:SP", expected_change_to_gap) self.ca_galil.assert_that_pv_is_number("JAWS1:HGAP", expected_change_to_gap, timeout=15, tolerance=MOTOR_TOLERANCE) self.ca.assert_that_pv_is("PARAM:S1HG", expected_change_to_gap) self.ca.assert_that_pv_is("PARAM:S1HG:SP:RBV", expected_gap_in_refl) @parameterized.expand([("slits", "S1", 30.00), ("multi_component", "THETA", 20.00), ("angle", "DET_ANG", -80.0), ("displacement", "DET_POS", 20.0), ("binary", "S3INBEAM", 0)]) def test_GIVEN_new_parameter_sp_WHEN_parameter_rbv_changing_THEN_parameter_changing_pv_correct( self, _, param, value): expected_value = "YES" value = value self.ca.set_pv_value("PARAM:{}:SP".format(param), value) self.ca.assert_that_pv_is("PARAM:{}:CHANGING".format(param), expected_value) @parameterized.expand([("slits", "S1", 500.00), ("multi_component", "THETA", -500.00), ("angle", "DET_ANG", -800.0), ("displacement", "DET_POS", 500.0), ("binary", "S3INBEAM", 0)]) def test_GIVEN_new_parameter_sp_WHEN_parameter_rbv_not_changing_THEN_parameter_changing_pv_correct( self, _, param, value): expected_value = "NO" value = value self.ca.set_pv_value("PARAM:{}:SP".format(param), value) self.ca_cs.set_pv_value("MOT:STOP:ALL", 1) self.ca.assert_that_pv_is("PARAM:{}:CHANGING".format(param), expected_value) @parameterized.expand([("slits", "S1", 500.00), ("multi_component", "THETA", 500.00), ("angle", "DET_ANG", -800.0), ("displacement", "DET_POS", 500.0), ("binary", "S3INBEAM", "OUT")]) def test_GIVEN_new_parameter_sp_WHEN_parameter_rbv_outside_of_sp_target_tolerance_THEN_parameter_at_rbv_pv_correct( self, _, param, value): expected_value = "NO" value = value self.ca.set_pv_value("PARAM:{}:SP".format(param), value) self.ca_cs.set_pv_value("MOT:STOP:ALL", 1) self.ca.assert_that_pv_is("PARAM:{}:RBV:AT_SP".format(param), expected_value) @parameterized.expand([("slits", "S1", 0.00), ("multi_component", "THETA", 0.00), ("angle", "DET_ANG", 0.0), ("displacement", "DET_POS", 0.0), ("binary", "S3INBEAM", 1)]) def test_GIVEN_new_parameter_sp_WHEN_parameter_rbv_within_sp_target_tolerance_THEN_parameter_at_rbv_pv_correct( self, _, param, value): expected_value = "YES" value = value self.ca.set_pv_value("PARAM:{}:SP".format(param), value) self.ca_cs.set_pv_value("MOT:STOP:ALL", 1) self.ca.assert_that_pv_is("PARAM:{}:RBV:AT_SP".format(param), expected_value) def test_GIVEN_a_low_level_beamline_change_WHEN_values_changed_THEN_high_level_parameters_updated( self): self.ca_galil.set_pv_value("MTR0102", -400) self.ca.assert_that_pv_value_is_changing("PARAM:S3", wait=2) self.ca.assert_that_pv_is("PARAM:S3:RBV:AT_SP", "NO") def test_GIVEN_engineering_correction_WHEN_move_THEN_move_includes_engineering_correction( self): theta = 2 self.ca.set_pv_value("PARAM:THETA:SP", theta) self.ca.set_pv_value("PARAM:S5:SP", 0) self.ca.assert_that_pv_is( "COR:MOT:MTR0206:DESC", "Interpolated from file s4_correction.dat on MOT:MTR0206 for s5") self.ca.assert_that_pv_is("COR:MOT:MTR0206", theta / 10.0) # s4 correction is a 1/10 of theta # soon after movement starts and before movement stops the velocity should be the same distance_from_sample_to_s4 = (3.5 - 2.0) * 2 expected_position = distance_from_sample_to_s4 * tan(radians( theta * 2)) + theta / 10.0 self.ca_galil.assert_that_pv_is_number("MTR0206.RBV", expected_position, tolerance=MOTOR_TOLERANCE, timeout=30) self.ca.assert_that_pv_is_number("PARAM:S5", 0, tolerance=MOTOR_TOLERANCE, timeout=10) def test_GIVEN_param_not_in_mode_and_sp_changed_WHEN_performing_beamline_move_THEN_sp_is_applied( self): expected = 1.0 self.ca.set_pv_value("PARAM:NOTINMODE:SP_NO_ACTION", expected) self.ca.set_pv_value("BL:MOVE", 1, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP:RBV", expected) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", expected) def test_GIVEN_param_not_in_mode_and_sp_changed_WHEN_performing_individual_move_THEN_sp_is_applied( self): expected = 1.0 self.ca.set_pv_value("PARAM:NOTINMODE:SP_NO_ACTION", expected) self.ca.set_pv_value("PARAM:NOTINMODE:ACTION", 1, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP:RBV", expected) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", expected) def test_GIVEN_param_not_in_mode_and_sp_changed_WHEN_performing_individual_move_on_other_param_THEN_no_value_applied( self): param_sp = 0.0 motor_pos = 1.0 self.ca.set_pv_value("PARAM:NOTINMODE:SP", param_sp) self.ca_galil.set_pv_value("MTR0205", motor_pos, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", motor_pos) self.ca.set_pv_value("PARAM:THETA:SP", 0.2, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP", param_sp) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP:RBV", param_sp) self.ca_galil.assert_that_pv_is_number("MTR0205", motor_pos) def test_GIVEN_param_not_in_mode_and_sp_unchanged_WHEN_performing_beamline_move_THEN_no_value_applied( self): param_sp = 0.0 motor_pos = 1.0 self.ca_galil.set_pv_value("MTR0205", motor_pos, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", motor_pos) self.ca.set_pv_value("BL:MOVE", 1, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP", param_sp) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP:RBV", param_sp) self.ca_galil.assert_that_pv_is_number("MTR0205", motor_pos) def test_GIVEN_param_not_in_mode_and_sp_unchanged_WHEN_performing_individual_move_THEN_sp_is_applied( self): param_sp = 0.0 motor_pos = 1.0 self.ca_galil.set_pv_value("MTR0205", motor_pos, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", motor_pos) self.ca.set_pv_value("PARAM:NOTINMODE:ACTION", 1, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP", param_sp) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP:RBV", param_sp) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", param_sp) def test_GIVEN_param_not_in_mode_and_sp_unchanged_WHEN_performing_individual_move_on_other_param_THEN_no_value_applied( self): param_sp = 0.0 motor_pos = 1.0 self.ca_galil.set_pv_value("MTR0205", motor_pos, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE", motor_pos) self.ca.set_pv_value("PARAM:THETA:SP", 0.2, wait=True) self.ca_galil.assert_that_pv_is("MTR0205.DMOV", 1, timeout=10) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP", param_sp) self.ca.assert_that_pv_is_number("PARAM:NOTINMODE:SP:RBV", param_sp) self.ca_galil.assert_that_pv_is_number("MTR0205", motor_pos) def test_GIVEN_non_synchronised_axis_WHEN_move_which_should_change_velocity_THEN_velocity_not_changed( self): self.ca_galil.set_pv_value("MTR0103.VELO", MEDIUM_VELOCITY) self.ca.set_pv_value("PARAM:THETA:SP", 22.5) # soon after movement starts and before movement stops the velocity should be the same self.ca_galil.assert_that_pv_is("MTR0103.DMOV", 0, timeout=10) self.ca_galil.assert_that_pv_is("MTR0103.VELO", MEDIUM_VELOCITY, timeout=0.5) self.ca_galil.assert_that_pv_is("MTR0103.DMOV", 0, timeout=10) # when the movement finishes it should still be the same self.ca_galil.assert_that_pv_is("MTR0103.DMOV", 1, timeout=10) self.ca_galil.assert_that_pv_is("MTR0103.VELO", MEDIUM_VELOCITY) def test_GIVEN_motor_axis_is_angle_WHEN_motor_alarm_status_is_updated_THEN_alarms_propagate_to_correct_parameters_on_component( self): expected_severity_code = "MINOR" expected_status_code = "HIGH" no_alarm_code = "NO_ALARM" # Setting High Limit = Low limit produces alarm on 0105 (detector angle) self.ca_galil.set_pv_value("MTR0105.HLM", SOFT_LIMIT_LO) # detector angle should be in alarm self.ca.assert_that_pv_is("PARAM:DET_ANG.STAT", expected_status_code) self.ca.assert_that_pv_is("PARAM:DET_ANG.SEVR", expected_severity_code) # detector offset is independent and should not be in alarm self.ca.assert_that_pv_is("PARAM:DET_POS.STAT", no_alarm_code) self.ca.assert_that_pv_is("PARAM:DET_POS.SEVR", no_alarm_code) # Setting High Limit back clears alarm self.ca_galil.set_pv_value("MTR0105.HLM", SOFT_LIMIT_HI) self.ca.assert_that_pv_is("PARAM:DET_ANG.STAT", no_alarm_code) self.ca.assert_that_pv_is("PARAM:DET_ANG.SEVR", no_alarm_code) def test_GIVEN_motor_axis_is_displacement_WHEN_motor_alarm_status_is_updated_THEN_alarms_propagate_to_correct_parameters_on_component( self): expected_severity_code = "MINOR" expected_status_code = "HIGH" no_alarm_code = "NO_ALARM" # Setting High Limit = Low limit produces alarm on 0104 (detector height) self.ca_galil.set_pv_value("MTR0104.HLM", SOFT_LIMIT_LO) # detector offset should be in alarm self.ca.assert_that_pv_is("PARAM:DET_POS.STAT", expected_status_code) self.ca.assert_that_pv_is("PARAM:DET_POS.SEVR", expected_severity_code) # theta is derived from detector offset and should be in alarm self.ca.assert_that_pv_is("PARAM:THETA.STAT", expected_status_code) self.ca.assert_that_pv_is("PARAM:THETA.SEVR", expected_severity_code) # detector angle is independent and should not be in alarm self.ca.assert_that_pv_is("PARAM:DET_ANG.STAT", no_alarm_code) self.ca.assert_that_pv_is("PARAM:DET_ANG.SEVR", no_alarm_code) # Setting High Limit back clears alarm self.ca_galil.set_pv_value("MTR0104.HLM", SOFT_LIMIT_HI) self.ca.assert_that_pv_is("PARAM:DET_POS.STAT", no_alarm_code) self.ca.assert_that_pv_is("PARAM:DET_POS.SEVR", no_alarm_code) self.ca.assert_that_pv_is("PARAM:THETA.STAT", no_alarm_code) self.ca.assert_that_pv_is("PARAM:THETA.SEVR", no_alarm_code) @parameterized.expand([("Variable", "DET_POS", "MTR0104"), ("Frozen", "DET_POS", "MTR0104"), ("Frozen", "DET_ANG", "MTR0105")]) def test_GIVEN_motors_not_at_zero_WHEN_define_motor_position_to_THEN_motor_position_is_changed_without_move( self, initial_foff, param_name, motor_name): offset = 10 new_position = 2 self.ca.set_pv_value("PARAM:{}:SP".format(param_name), offset) self.ca_galil.set_pv_value("MTR0104.FOFF", initial_foff) self.ca_galil.set_pv_value("MTR0104.OFF", 0) self.ca.assert_that_pv_is_number("PARAM:{}".format(param_name), offset, tolerance=MOTOR_TOLERANCE, timeout=30) self.ca_galil.assert_that_pv_is("MTR0104.DMOV", 1, timeout=30) with ManagerMode(self.ca_no_prefix): self.ca.set_pv_value( "PARAM:{}:DEFINE_POSITION_AS".format(param_name), new_position) # soon after change there should be no movement, ie a move is triggered but the motor itself does not move so it # is very quick self.ca_galil.assert_that_pv_is("{}.DMOV".format(motor_name), 1, timeout=1) self.ca_galil.assert_that_pv_is("{}.RBV".format(motor_name), new_position) self.ca_galil.assert_that_pv_is("{}.VAL".format(motor_name), new_position) self.ca_galil.assert_that_pv_is("{}.SET".format(motor_name), "Use") self.ca_galil.assert_that_pv_is("{}.FOFF".format(motor_name), initial_foff) self.ca_galil.assert_that_pv_is_number("{}.OFF".format(motor_name), 0.0, tolerance=MOTOR_TOLERANCE) self.ca.assert_that_pv_is("PARAM:{}".format(param_name), new_position) self.ca.assert_that_pv_is("PARAM:{}:SP".format(param_name), new_position) self.ca.assert_that_pv_is("PARAM:{}:SP_NO_ACTION".format(param_name), new_position) self.ca.assert_that_pv_is("PARAM:{}:CHANGED".format(param_name), "NO") self.ca.assert_that_pv_is("PARAM:THETA", 0) self.ca.assert_that_pv_is("PARAM:THETA:SP", 0) self.ca.assert_that_pv_is("PARAM:THETA:SP:RBV", 0) def test_GIVEN_jaws_not_at_zero_WHEN_define_motor_position_for_jaw_gaps_THEN_jaws_position_are_changed_without_move( self): param_name = "S1VG" jaw_motors = ["MTR0201", "MTR0202"] initial_gap = 1.0 initial_centre = 2.0 new_gap = 4.0 expected_pos = { "MTR0201": new_gap / 2.0 - initial_centre, "MTR0202": new_gap / 2.0 + initial_centre } self.ca.assert_setting_setpoint_sets_readback(initial_gap, "PARAM:S1VG", expected_alarm=None, timeout=30) self.ca.assert_setting_setpoint_sets_readback(initial_centre, "PARAM:S1VC", expected_alarm=None, timeout=30) for motor_name in jaw_motors: self.ca_galil.set_pv_value("{}.FOFF".format(motor_name), "Frozen") self.ca_galil.set_pv_value("{}.OFF".format(motor_name), 0) for motor_name in jaw_motors: self.ca_galil.assert_that_pv_is("{}.DMOV".format(motor_name), 1, timeout=30) with ManagerMode(self.ca_no_prefix): self.ca.set_pv_value( "PARAM:{}:DEFINE_POSITION_AS".format(param_name), new_gap) # soon after change there should be no movement, ie a move is triggered but the motor itself does not move so it # is very quick for motor_name in jaw_motors: self.ca_galil.assert_that_pv_is("{}.DMOV".format(motor_name), 1, timeout=1) for motor_name in jaw_motors: # jaws are open to half the gap self.ca_galil.assert_that_pv_is("{}.RBV".format(motor_name), expected_pos[motor_name]) self.ca_galil.assert_that_pv_is("{}.VAL".format(motor_name), expected_pos[motor_name]) self.ca_galil.assert_that_pv_is("{}.SET".format(motor_name), "Use") self.ca_galil.assert_that_pv_is("{}.FOFF".format(motor_name), "Frozen") self.ca_galil.assert_that_pv_is_number("{}.OFF".format(motor_name), 0.0, tolerance=MOTOR_TOLERANCE) self.ca.assert_that_pv_is("PARAM:{}".format(param_name), new_gap) self.ca.assert_that_pv_is("PARAM:{}:SP".format(param_name), new_gap) self.ca.assert_that_pv_is("PARAM:{}:SP_NO_ACTION".format(param_name), new_gap) self.ca.assert_that_pv_is("PARAM:{}:CHANGED".format(param_name), "NO") def test_GIVEN_jaws_not_at_zero_WHEN_define_motor_position_for_jaw_centres_THEN_jaws_position_are_changed_without_move( self): param_name = "S1HC" jaw_motors = ["MTR0203", "MTR0204"] initial_gap = 1.0 initial_centre = 2.0 new_centre = 4.0 expected_pos = { "MTR0203": initial_gap / 2.0 + new_centre, "MTR0204": initial_gap / 2.0 - new_centre } self.ca.assert_setting_setpoint_sets_readback(initial_gap, "PARAM:S1HG", expected_alarm=None, timeout=30) self.ca.assert_setting_setpoint_sets_readback(initial_centre, "PARAM:S1HC", expected_alarm=None, timeout=30) for motor_name in jaw_motors: self.ca_galil.set_pv_value("{}.FOFF".format(motor_name), "Frozen") self.ca_galil.set_pv_value("{}.OFF".format(motor_name), 0) for motor_name in jaw_motors: self.ca_galil.assert_that_pv_is("{}.DMOV".format(motor_name), 1, timeout=30) with ManagerMode(self.ca_no_prefix): self.ca.set_pv_value( "PARAM:{}:DEFINE_POSITION_AS".format(param_name), new_centre) # soon after change there should be no movement, ie a move is triggered but the motor itself does not move so it # is very quick for motor_name in jaw_motors: self.ca_galil.assert_that_pv_is("{}.DMOV".format(motor_name), 1, timeout=1) for motor_name in jaw_motors: # jaws are open to half the gap self.ca_galil.assert_that_pv_is("{}.RBV".format(motor_name), expected_pos[motor_name]) self.ca_galil.assert_that_pv_is("{}.VAL".format(motor_name), expected_pos[motor_name]) self.ca_galil.assert_that_pv_is("{}.SET".format(motor_name), "Use") self.ca_galil.assert_that_pv_is("{}.FOFF".format(motor_name), "Frozen") self.ca_galil.assert_that_pv_is_number("{}.OFF".format(motor_name), 0.0, tolerance=MOTOR_TOLERANCE) self.ca.assert_that_pv_is("PARAM:{}".format(param_name), new_centre) self.ca.assert_that_pv_is("PARAM:{}:SP".format(param_name), new_centre) self.ca.assert_that_pv_is("PARAM:{}:SP_NO_ACTION".format(param_name), new_centre) self.ca.assert_that_pv_is("PARAM:{}:CHANGED".format(param_name), "NO") def test_GIVEN_theta_THEN_define_position_as_does_not_exist(self): param_name = "THETA" self.ca.assert_that_pv_exists("PARAM:{}".format(param_name)) self.ca.assert_that_pv_does_not_exist( "PARAM:{}:DEFINE_POSITION_AS".format(param_name)) def test_GIVEN_motors_at_zero_WHEN_define_motor_position_to_and_back_multiple_times_THEN_motor_position_is_changed_without_move( self): param_name = "DET_POS" motor_name = "MTR0104" initial_foff = "Frozen" self.ca.set_pv_value("PARAM:{}:SP".format(param_name), 0) self.ca_galil.set_pv_value("MTR0104.FOFF", initial_foff) self.ca_galil.set_pv_value("MTR0104.OFF", 0) self.ca.assert_that_pv_is_number("PARAM:{}".format(param_name), 0, tolerance=MOTOR_TOLERANCE, timeout=30) self.ca_galil.assert_that_pv_is("MTR0104.DMOV", 1, timeout=30) for i in range(20): new_position = i - 5 with ManagerMode(self.ca_no_prefix): self.ca.set_pv_value( "PARAM:{}:DEFINE_POSITION_AS".format(param_name), new_position) # soon after change there should be no movement, ie a move is triggered but the motor itself does not move so it # is very quick self.ca_galil.assert_that_pv_is("{}.DMOV".format(motor_name), 1, timeout=1) self.ca_galil.assert_that_pv_is("{}.RBV".format(motor_name), new_position) self.ca_galil.assert_that_pv_is("{}.VAL".format(motor_name), new_position) self.ca_galil.assert_that_pv_is("{}.SET".format(motor_name), "Use") self.ca_galil.assert_that_pv_is("{}.FOFF".format(motor_name), initial_foff) self.ca_galil.assert_that_pv_is_number("{}.OFF".format(motor_name), 0.0, tolerance=MOTOR_TOLERANCE) self.ca.assert_that_pv_is("PARAM:{}".format(param_name), new_position) self.ca.assert_that_pv_is("PARAM:{}:SP".format(param_name), new_position) self.ca.assert_that_pv_is( "PARAM:{}:SP_NO_ACTION".format(param_name), new_position) self.ca.assert_that_pv_is("PARAM:{}:CHANGED".format(param_name), "NO") def test_GIVEN_parameter_not_in_manager_mode_WHEN_define_position_THEN_position_is_not_defined( self): new_position = 10 param_pv = "PARAM:{}:DEFINE_POSITION_AS".format("DET_POS") self.assertRaises(IOError, self.ca.set_pv_value, param_pv, new_position) self.ca.assert_that_pv_is_not(param_pv, new_position) def test_GIVEN_value_parameter_WHEN_read_THEN_value_returned(self): param_pv = "CONST:TEN" self.ca.assert_that_pv_is(param_pv, 10) self.ca.assert_that_pv_is("{}.DESC".format(param_pv), "The value 10") def test_GIVEN_bool_parameter_WHEN_read_THEN_value_returned(self): param_pv = "CONST:YES" self.ca.assert_that_pv_is(param_pv, "YES") def test_GIVEN_string_constant_parameter_WHEN_read_THEN_value_returned( self): param_pv = "CONST:STRING" self.ca.assert_that_pv_is(param_pv, "Test String") def test_GIVEN_PNR_mode_with_SM_angle_WHEN_move_in_disable_mode_and_into_PNR_THEN_beamline_is_updated_on_mode_change_and_value_of_pd_offsets_correct( self): self.ca.set_pv_value("BL:MODE:SP", "POLARISED") self.ca.set_pv_value("PARAM:SMANGLE:SP_NO_ACTION", 0.2) self.ca.set_pv_value("BL:MOVE", 1) self.ca.assert_that_pv_is_number("PARAM:SMANGLE", 0.2, tolerance=1e-2) self.ca.set_pv_value("BL:MODE:SP", "DISABLED") self.ca.set_pv_value("PARAM:SMANGLE:SP", 0) self.ca.assert_that_pv_is_number("PARAM:SMANGLE", 0.0, tolerance=1e-2) self.ca.assert_that_pv_is_number("PARAM:S3", 0.0, tolerance=1e-2) self.ca.assert_that_pv_is_number("PARAM:DET_POS", 0.0, tolerance=1e-2) self.ca.set_pv_value("BL:MODE:SP", "POLARISED") # In polarised mode the sm angle will now make everything appear to be in the wrong place. # This test will also check that on changing modes the beamline is updated self.ca.assert_that_pv_is_not_number("PARAM:S3", 0.0, tolerance=1e-2) self.ca.assert_that_pv_is_not_number("PARAM:DET_POS", 0.0, tolerance=1e-2) @parameterized.expand([(0, OUT_POSITION_HIGH), (22.5, OUT_POSITION_LOW)]) def test_GIVEN_component_with_multiple_parked_positions_WHEN_moving_out_of_beam_THEN_driver_moves_to_correct_out_of_beam_position_based_on_beam_interception( self, theta_sp, expected_out_of_beam_position): self.ca.assert_that_pv_is("PARAM:S3INBEAM", "IN") self.ca.set_pv_value("PARAM:THETA:SP_NO_ACTION", theta_sp, wait=True) self.ca.set_pv_value("PARAM:S3INBEAM:SP_NO_ACTION", "OUT", wait=True) self.ca.set_pv_value("BL:MOVE", 1, wait=True) self.ca_galil.assert_that_pv_is_number("MTR0102.VAL", expected_out_of_beam_position, timeout=5) def test_GIVEN_component_with_multiple_out_of_beam_positions_is_out_of_beam_WHEN_beam_intercept_moves_above_threshold_THEN_driver_moves_to_correct_out_of_beam_position( self): self.ca.assert_that_pv_is("PARAM:S3INBEAM", "IN") self.ca.set_pv_value("PARAM:S3INBEAM:SP", "OUT", wait=True) self.ca.assert_that_pv_is("PARAM:S3INBEAM:CHANGING", "NO", timeout=30) self.ca_galil.assert_that_pv_is_number("MTR0102.RBV", OUT_POSITION_HIGH, timeout=20) self.ca.set_pv_value("PARAM:THETA:SP", 22.5, wait=True) self.ca_galil.assert_that_pv_is_number("MTR0102.VAL", OUT_POSITION_LOW, timeout=5) @parameterized.expand([(0, OUT_POSITION_HIGH, "OUT"), (0, OUT_POSITION_LOW, "IN"), (22.5, OUT_POSITION_HIGH, "IN"), (22.5, OUT_POSITION_LOW, "OUT")]) def test_GIVEN_component_with_multiple_parked_positions_WHEN_moving_axis_to_sp_THEN_inbeam_param_reports_correct_rbv( self, theta_sp, axis_sp, expected_inbeam_status): self.ca.assert_that_pv_is("PARAM:S3INBEAM", "IN") self.ca.set_pv_value("PARAM:THETA:SP", theta_sp, wait=True) self.ca.assert_that_pv_is("PARAM:THETA:CHANGING", "NO", timeout=30) self.ca_galil.set_pv_value("MTR0102.VAL", axis_sp, wait=True) self.ca.assert_that_pv_is("PARAM:S3INBEAM:CHANGING", "NO", timeout=30) self.ca.assert_that_pv_is("PARAM:S3INBEAM", expected_inbeam_status) def test_GIVEN_driver_with_param_value_dependent_axis_WHEN_set_value_THEN_correct_axis_drives_to_position_and_read_back_is_correct( self): expected_offset1 = 0.3 expected_offset2 = 0.2 for expected_offset, choice, mot0205, mot0207 in [ (expected_offset1, "MTR0207", 0, expected_offset1), (expected_offset2, "MTR0205", expected_offset2, expected_offset1) ]: self.ca.assert_setting_setpoint_sets_readback( choice, "PARAM:CHOICE") self.ca.set_pv_value("PARAM:NOTINMODE:SP", expected_offset) self.ca.assert_that_pv_is("PARAM:NOTINMODE", expected_offset, timeout=20) self.ca_galil.assert_that_pv_is_number("MTR0205.RBV", mot0205, timeout=20) self.ca_galil.assert_that_pv_is_number("MTR0207.RBV", mot0207, timeout=20) def test_GIVEN_theta_WHEN_detector_long_axis_changes_THEN_detector_tracks( self): theta_angle = 2 long_axis_addition = 1 self.ca.set_pv_value("PARAM:THETA:SP", theta_angle) self.ca.set_pv_value("BL:MOVE", 1) # theta set self._check_param_pvs("THETA", theta_angle) self.ca.set_pv_value("PARAM:DET_LONG:SP", long_axis_addition) self.ca.set_pv_value("BL:MOVE", 1) expected_det_value = (2 * SPACING + long_axis_addition) * tan( radians(theta_angle * 2.0)) self._check_param_pvs("DET_LONG", long_axis_addition) self._check_param_pvs("DET_POS", 0.0) self.ca_galil.assert_that_pv_is_number("MTR0104", expected_det_value, 0.01)
class KepcoTests(object): """ Tests for the KEPCO. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc("kepco", DEVICE_PREFIX) self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) self._lewis.backdoor_run_function_on_device("reset") self.ca.assert_that_pv_exists("VOLTAGE", timeout=30) reset_calibration_file(self.ca, "default_calib.dat") def _write_voltage(self, expected_voltage): self._lewis.backdoor_set_on_device("voltage", expected_voltage) self._ioc.set_simulated_value("SIM:VOLTAGE", expected_voltage) def _write_current(self, expected_current): self._lewis.backdoor_set_on_device("current", expected_current) self._ioc.set_simulated_value("SIM:CURRENT", expected_current) def _set_IDN(self, expected_idn_no_firmware, expected_firmware): self._lewis.backdoor_set_on_device("idn_no_firmware", expected_idn_no_firmware) self._lewis.backdoor_set_on_device("firmware", expected_firmware) expected_idn = "{}{}".format(expected_idn_no_firmware, str(expected_firmware))[:39] # EPICS limited to 40 chars self._ioc.set_simulated_value("SIM:IDN", expected_idn) self._ioc.set_simulated_value("SIM:FIRMWARE", str(expected_firmware)) # Both firmware and IDN are passive so must be updated self.ca.process_pv("FIRMWARE") self.ca.process_pv("IDN") return expected_idn def _set_output_mode(self, expected_output_mode): self._lewis.backdoor_set_on_device("output_mode", expected_output_mode) self._ioc.set_simulated_value("SIM:OUTPUTMODE", expected_output_mode) def _set_output_status(self, expected_output_status): self._lewis.backdoor_set_on_device("output_status", expected_output_status) def test_GIVEN_voltage_set_WHEN_read_THEN_voltage_is_as_expected(self): expected_voltage = 1.2 self._write_voltage(expected_voltage) self.ca.assert_that_pv_is("VOLTAGE", expected_voltage) def test_GIVEN_current_set_WHEN_read_THEN_current_is_as_expected(self): expected_current = 1.5 self._write_current(expected_current) self.ca.assert_that_pv_is("CURRENT", expected_current) def test_GIVEN_setpoint_voltage_set_WHEN_read_THEN_setpoint_voltage_is_as_expected(self): # Get current Voltage current_voltage = self.ca.get_pv_value("VOLTAGE") # Set new Voltage via SP self.ca.set_pv_value("VOLTAGE:SP", current_voltage + 5) # Check SP RBV matches new current self.ca.assert_that_pv_is("VOLTAGE:SP:RBV", current_voltage + 5) @parameterized.expand(parameterized_list([-5.1, 7.8])) def test_GIVEN_setpoint_current_set_WHEN_read_THEN_setpoint_current_is_as_expected(self, _, expected_current): self.ca.set_pv_value("CURRENT:SP", expected_current) # Check SP RBV matches new current self.ca.assert_that_pv_is("CURRENT:SP:RBV", expected_current) def test_GIVEN_output_mode_set_WHEN_read_THEN_output_mode_is_as_expected(self): expected_output_mode_flag = UnitFlags.CURRENT expected_output_mode_str = OutputMode.CURRENT self._set_output_mode(expected_output_mode_flag) # Check OUTPUT MODE matches new OUTPUT MODE self.ca.assert_that_pv_is("OUTPUTMODE", expected_output_mode_str) def test_GIVEN_output_status_set_WHEN_read_THEN_output_STATUS_is_as_expected(self): expected_output_status_flag = UnitFlags.ON expected_output_status_str = Status.ON self.ca.set_pv_value("OUTPUTSTATUS:SP", expected_output_status_flag) self.ca.assert_that_pv_is("OUTPUTSTATUS:SP:RBV", expected_output_status_str) @parameterized.expand(parameterized_list(IDN_LIST)) def test_GIVEN_idn_set_WHEN_read_THEN_idn_is_as_expected(self, _, idn_no_firmware, firmware): expected_idn = self._set_IDN(idn_no_firmware, firmware) self.ca.process_pv("IDN") self.ca.assert_that_pv_is("IDN", expected_idn) @skip_if_recsim("In rec sim you can not diconnect the device") def test_GIVEN_diconnected_WHEN_read_THEN_alarms_on_readbacks(self): self._lewis.backdoor_set_on_device("connected", False) self.ca.assert_that_pv_alarm_is("OUTPUTMODE", self.ca.Alarms.INVALID) self.ca.assert_that_pv_alarm_is("CURRENT", self.ca.Alarms.INVALID) self.ca.assert_that_pv_alarm_is("VOLTAGE", self.ca.Alarms.INVALID) def _test_ramp_to_target(self, start_current, target_current, ramp_rate, step_number, wait_between_changes): self._write_current(start_current) self.ca.set_pv_value("CURRENT:SP", start_current) self.ca.assert_that_pv_is("CURRENT:SP:RBV", start_current) self.ca.set_pv_value("RAMP:RATE:SP", ramp_rate) self.ca.set_pv_value("RAMP:STEPS:SP", step_number) self.ca.set_pv_value("RAMPON:SP", "ON") self.ca.set_pv_value("CURRENT:SP", target_current, sleep_after_set=0.0) if start_current < target_current: self.ca.assert_that_pv_value_is_increasing("CURRENT:SP:RBV", wait=wait_between_changes) else: self.ca.assert_that_pv_value_is_decreasing("CURRENT:SP:RBV", wait=wait_between_changes) self.ca.assert_that_pv_is("RAMPING", "YES") # Device stops ramping when it gets to target self.ca.assert_that_pv_is("CURRENT:SP:RBV", target_current, timeout=40) self._write_current(target_current) self.ca.assert_that_pv_is("RAMPING", "NO") self.ca.assert_that_pv_value_is_unchanged("CURRENT:SP:RBV", wait=wait_between_changes) self.ca.set_pv_value("RAMPON:SP", "OFF") def test_GIVEN_rampon_WHEN_target_set_THEN_current_ramps_to_target(self): self._test_ramp_to_target(1, 2, 2, 20, 7) def test_GIVEN_rampon_WHEN_target_set_with_different_step_rate_THEN_current_ramps_to_target_more_finely(self): self._test_ramp_to_target(4, 3, 2, 60, 2) @parameterized.expand(parameterized_list(IDN_LIST)) def test_GIVEN_idn_set_AND_firmware_set_THEN_firmware_pv_correct(self, _, idn_no_firmware, firmware): self._set_IDN(idn_no_firmware, firmware) self.ca.process_pv("FIRMWARE") self.ca.assert_that_pv_is("FIRMWARE", firmware) @parameterized.expand(parameterized_list([ ("default_calib.dat", 100, 100), ("field_double_amps.dat", 100, 50), ])) @skip_if_recsim("Calibration lookup does not work in recsim") def test_GIVEN_calibration_WHEN_field_set_THEN_current_as_expected(self, _, calibration_file, field, expected_current): with use_calibration_file(self.ca, calibration_file, "default_calib.dat"): self.ca.set_pv_value("FIELD:SP", field) self.ca.assert_that_pv_is("FIELD:SP:RBV", field) self.ca.assert_that_pv_is("CURRENT:SP", expected_current) self.ca.assert_that_pv_is("CURRENT:SP:RBV", expected_current) @parameterized.expand(parameterized_list([ ("default_calib.dat", 100, 100), ("field_double_amps.dat", 100, 200), ])) @skip_if_recsim("Calibration lookup does not work in recsim") def test_GIVEN_calibration_WHEN_current_set_THEN_field_as_expected(self, _, calibration_file, current, expected_field): with use_calibration_file(self.ca, calibration_file, "default_calib.dat"): self._write_current(current) self.ca.assert_that_pv_is("CURRENT", current) self.ca.assert_that_pv_is("FIELD", expected_field) @skip_if_recsim("Lewis not available in recsim") def test_WHEN_sending_setpoint_THEN_only_one_setpoint_sent(self): self._lewis.backdoor_set_and_assert_set("current_set_count", 0) self.ca.set_pv_value("CURRENT:SP", 100) self._lewis.assert_that_emulator_value_is("current_set_count", 1) # Wait a short time and make sure count is not being incremented again later. time.sleep(5) self._lewis.assert_that_emulator_value_is("current_set_count", 1)
class CybamanTests(unittest.TestCase): """ Tests for the cybaman IOC. """ AXES = ["A", "B", "C"] test_positions = [-200, -1.23, 0, 180.0] def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( EMULATOR_DEVICE, DEVICE_PREFIX) self.ca = ChannelAccess(default_timeout=20, device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("INITIALIZE", timeout=30) self._lewis.backdoor_set_on_device('connected', True) # Check that all the relevant PVs are up. for axis in self.AXES: self.ca.assert_that_pv_exists(axis) self.ca.assert_that_pv_exists("{}:SP".format(axis)) # Initialize the device, do this in setup to avoid doing it in every test self.ca.set_pv_value("INITIALIZE", 1) self.ca.assert_that_pv_is("INITIALIZED", "TRUE") def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") @skip_if_recsim("Uses lewis backdoor command") def test_WHEN_position_setpoints_are_set_via_backdoor_THEN_positions_move_towards_setpoints( self): for axis in self.AXES: for pos in self.test_positions: self._lewis.backdoor_set_on_device( "{}_setpoint".format(axis.lower()), pos) self.ca.assert_that_pv_is_number("{}".format(axis), pos, tolerance=0.01) @skip_if_recsim("Uses lewis backdoor command") def test_GIVEN_home_position_is_set_WHEN_home_pv_is_set_THEN_position_moves_towards_home( self): for axis in self.AXES: for pos in self.test_positions: self._lewis.backdoor_set_on_device( "home_position_axis_{}".format(axis.lower()), pos) self.ca.set_pv_value("{}:HOME".format(axis), 1) self.ca.assert_that_pv_is_number("{}".format(axis), pos, tolerance=0.01) @skip_if_recsim("Uses lewis backdoor command") def test_GIVEN_a_device_in_some_other_state_WHEN_reset_command_is_sent_THEN_device_is_reset_to_original_state( self): modifier = 12.34 # Reset cybaman self.ca.set_pv_value("RESET", 1) self.ca.assert_that_pv_is("INITIALIZED", "FALSE") self.ca.set_pv_value("INITIALIZE", 1) self.ca.assert_that_pv_is("INITIALIZED", "TRUE") self.ca.assert_that_pv_value_is_unchanged("INITIALIZED", 10) original = {} for axis in self.AXES: original[axis] = float( self.ca.get_pv_value("{}".format(axis.upper()))) # Set both value and setpoint to avoid the device moving back towards the setpoint self._lewis.backdoor_set_on_device( "{}_setpoint".format(axis.lower()), original[axis] + modifier) self._lewis.backdoor_set_on_device("{}".format(axis.lower()), original[axis] + modifier) self.ca.assert_that_pv_is_number("{}".format(axis.upper()), original[axis] + modifier, tolerance=0.001) # Reset cybaman self.ca.set_pv_value("RESET", 1) # Check that a, b and c values are now at original values for axis in self.AXES: self.ca.assert_that_pv_is_number("{}".format(axis.upper()), original[axis], tolerance=0.001) def test_GIVEN_a_device_in_initialized_state_WHEN_setpoints_are_sent_THEN_device_goes_to_setpoint( self): for axis in self.AXES: for pos in self.test_positions: self.ca.set_pv_value("{}:SP".format(axis.upper()), pos) self.ca.assert_that_pv_is_number("{}".format(axis.upper()), pos) @skip_if_recsim("Uses lewis backdoor command") def test_GIVEN_a_device_with_a_setpoint_less_than_minus_150_WHEN_homed_THEN_setpoint_is_set_to_minus_150_before_home( self): for axis in self.AXES: # Ensure home position is known self._lewis.backdoor_set_on_device( "home_position_axis_{}".format(axis.lower()), 100) # Ensure setpoint and readback are less than -150 self.ca.set_pv_value("{}:SP".format(axis.upper()), -155) self.ca.assert_that_pv_is_number("{}".format(axis.upper()), -155, tolerance=0.01) # Tell axis to home self.ca.set_pv_value("{}:HOME".format(axis.upper()), 1) # Ensure that setpoint is updated to -150 before home self.ca.assert_that_pv_is_number("{}:SP".format(axis.upper()), -150, tolerance=0.01) # Let device actually reach home position self.ca.assert_that_pv_is_number("{}".format(axis.upper()), 100) @skip_if_recsim("Uses lewis backdoor command") def test_GIVEN_a_device_with_a_setpoint_more_than_minus_150_WHEN_homed_THEN_setpoint_is_not_set_before_home( self): for axis in self.AXES: # Ensure home position is known self._lewis.backdoor_set_on_device( "home_position_axis_{}".format(axis.lower()), 100) # Ensure setpoint and readback are more than -150 self.ca.set_pv_value("{}:SP".format(axis.upper()), -145) self.ca.assert_that_pv_is_number("{}".format(axis.upper()), -145, tolerance=0.01) # Tell axis to home self.ca.set_pv_value("{}:HOME".format(axis.upper()), 1) # Ensure that setpoint has not been updated self.ca.assert_that_pv_is_number("{}:SP".format(axis.upper()), -145, tolerance=0.01) # Let device actually reach home position self.ca.assert_that_pv_is_number("{}".format(axis.upper()), 100) def test_GIVEN_a_device_at_a_specific_position_WHEN_setpoint_is_updated_THEN_tm_val_is_calculated_correctly( self): test_cases = ( # No change in setpoint, TM val should be 4000 { "old_pos": (-1, -2, -3), "axis_to_change": "A", "new_setpoint": -1, "expected_tm_val": 4000 }, # Test case provided from flowchart specification { "old_pos": (0, 0, 0), "axis_to_change": "A", "new_setpoint": 30, "expected_tm_val": 6000 }, # Test case provided from flowchart specification { "old_pos": (11, -5, 102), "axis_to_change": "C", "new_setpoint": 50, "expected_tm_val": 10000 }, # Very small change, TM val should be 4000 { "old_pos": (10, 20, 30), "axis_to_change": "B", "new_setpoint": 21, "expected_tm_val": 4000 }, ) for case in test_cases: # Ensure original position is what it's meant to be for axis, setpoint in zip(self.AXES, case["old_pos"]): self.ca.set_pv_value("{}:SP".format(axis.upper()), setpoint) self.ca.assert_that_pv_is_number("{}".format(axis.upper()), setpoint, tolerance=0.01) # Change the relevant axis to a new setpoint self.ca.set_pv_value( "{}:SP".format(case["axis_to_change"].upper()), case["new_setpoint"]) # Assert that the TM val calculation record contains the correct value # Tolerance is 1001 because rounding errors would get multiplied by 1000 self.ca.assert_that_pv_is_number("{}:_CALC_TM_AND_SET".format( case["axis_to_change"].upper()), case["expected_tm_val"], tolerance=1001)
class OscillatingCollimatorTests(unittest.TestCase): """ Tests for the LET Oscillating collimator. The CA.Client.Exceptions these tests generate are expected because of a workaround we had to make in the DB file to prevent a hang in the case of using asynFloat64 for the SP types. Issue described in ticket #2736 """ def setUp(self): self._ioc = IOCRegister.get_running("GALIL_01") ca_mot = ChannelAccess() ca_mot.assert_that_pv_exists("MOT:MTR0103", timeout=30) ca_mot.assert_setting_setpoint_sets_readback(DEFAULT_MOTOR_RESOLUTION, set_point_pv="MOT:MTR0103.MRES", readback_pv="MOT:MTR0103.MRES", ) self.ca = ChannelAccess(device_prefix=PREFIX) self.ca.assert_that_pv_exists("VEL:SP", timeout=30) def _custom_name_func(testcase_func, param_num, param): return "{}_ang_{}_freq_{}_rad_{}".format( testcase_func.__name__, *param.args[0] ) @parameterized.expand( # [(angle, frequency, radius), (expected distance, expected velocity) # Values confirmed via LabView VI [[(2.0, 0.5, 10.0), (281, 283)], [(1.0, 0.5, 10.0), (140, 140)], [(0.5, 0.5, 10.0), (70, 70)], [(2.0, 0.1, 10.0), (279, 56)], [(1.0, 0.1, 10.0), (140, 28)], [(0.5, 0.1, 10.0), (70, 14)], [(2.0, 0.5, 50.0), (1442, 1487)], [(1.0, 0.5, 50.0), (709, 719)], [(0.5, 0.5, 50.0), (352, 354)], [(2.0, 0.1, 50.0), (1398, 280)], [(1.0, 0.1, 50.0), (699, 140)], [(0.5, 0.1, 50.0), (349, 70)]], testcase_func_name=_custom_name_func ) def test_GIVEN_angle_frequency_and_radius_WHEN_set_THEN_distance_and_velocity_match_LabView_generated_values(self, settings, expected_values): # Arrange tolerance = 0.5 # Act # in normal operations the radius is not dynamic so set it first so it is considered in future calcs self.ca.set_pv_value(RADIUS, settings[2]) self.ca.set_pv_value(ANGLE, settings[0]) self.ca.set_pv_value(FREQUENCY, settings[1]) # Assert self.ca.assert_that_pv_is_number("DIST:SP", expected_values[0], tolerance) self.ca.assert_that_pv_is_number("VEL:SP", expected_values[1], tolerance) def test_WHEN_angle_set_negative_THEN_angle_is_zero(self): self.ca.set_pv_value(ANGLE, -1.0) self.ca.assert_that_pv_is_number(ANGLE, 0.0) def test_WHEN_angle_set_greater_than_two_THEN_angle_is_two(self): self.ca.set_pv_value(ANGLE, 5.0) self.ca.assert_that_pv_is_number(ANGLE, 2.0) def test_WHEN_frequency_set_negative_THEN_angle_is_zero(self): self.ca.set_pv_value(FREQUENCY, -1.0) self.ca.assert_that_pv_is_number(FREQUENCY, 0.0) def test_WHEN_angle_set_greater_than_half_THEN_angle_is_half(self): self.ca.set_pv_value(FREQUENCY, 1.0) self.ca.assert_that_pv_is_number(FREQUENCY, 0.5) def test_WHEN_frq_set_greater_than_two_THEN_angle_is_two(self): self.ca.set_pv_value(ANGLE, 5.0) self.ca.assert_that_pv_is_number(ANGLE, 2.0) def test_WHEN_input_values_cause_discriminant_to_be_negative_THEN_discriminant_pv_is_one(self): # Act # in normal operations the radius is not dynamic so set it first so it is considered in future calcs self.ca.set_pv_value(RADIUS, 1000.0) self.ca.set_pv_value(ANGLE, 2.0) self.ca.set_pv_value(FREQUENCY, 0.5) # Assert self.ca.assert_that_pv_is_number(DISCRIMINANT, 1.0) def test_WHEN_input_values_cause_discriminant_to_be_positive_THEN_discriminant_pv_is_zero(self): # Act # in normal operations the radius is not dynamic so set it first so it is considered in future calcs self.ca.set_pv_value(RADIUS, 1.0) self.ca.set_pv_value(ANGLE, 2.0) self.ca.set_pv_value(FREQUENCY, 0.5) # Assert self.ca.assert_that_pv_is_number(DISCRIMINANT, 0.0) def test_WHEN_collimator_running_THEN_thread_is_not_on_reserved_thread(self): # Threads 0 and 1 are reserved for homing under IBEX self.ca.assert_that_pv_is_not("THREAD", "0") self.ca.assert_that_pv_is_not("THREAD", "1")
class Itc503Tests(unittest.TestCase): """ Tests for the Itc503 IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "itc503", DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=20) self.ca.assert_that_pv_exists("DISABLE") self._make_device_scan_faster() def _make_device_scan_faster(self): """ Purely so that the tests run faster, the real IOC scans excruciatingly slowly. """ # Skip setting the PVs if the scan rate is already fast self.ca.assert_that_pv_exists("FAN1") self.ca.assert_that_pv_exists("FAN2") if self.ca.get_pv_value("FAN1.SCAN") != ".1 second": for i in range(1, 8 + 1): # Ensure all DLY links are 0 in both FAN records self.ca.set_pv_value("FAN1.DLY{}".format(i), 0) self.ca.set_pv_value("FAN2.DLY{}".format(i), 0) # Set the scan rate to .1 second (setting string does not work, have to use numeric value) self.ca.set_pv_value("FAN1.SCAN", 9) def test_WHEN_ioc_is_started_THEN_it_is_not_disabled(self): self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED") @parameterized.expand( (pv, val) for pv, val in itertools.product(["P", "I", "D"], TEST_VALUES)) def test_WHEN_setting_pid_settings_THEN_can_be_read_back(self, pv, val): self.ca.set_pv_value("{}:SP".format(pv), val) self.ca.assert_that_pv_is_number( pv, val, tolerance=0.1) # Only comes back to 1dp @parameterized.expand(val for val in parameterized_list(TEST_VALUES)) def test_WHEN_setting_flows_THEN_can_be_read_back(self, _, val): self.ca.set_pv_value("GASFLOW:SP", val) self.ca.assert_that_pv_is_number( "GASFLOW", val, tolerance=0.1) # Only comes back to 1dp @parameterized.expand(mode for mode in parameterized_list(MODES)) def test_WHEN_setting_gas_flow_control_mode_THEN_can_be_read_back( self, _, mode): self.ca.assert_setting_setpoint_sets_readback(mode, "MODE:GAS") @parameterized.expand(mode for mode in parameterized_list(MODES)) def test_WHEN_setting_heater_flow_control_mode_THEN_can_be_read_back( self, _, mode): self.ca.assert_setting_setpoint_sets_readback(mode, "MODE:HTR") @parameterized.expand(val for val in parameterized_list(TEST_VALUES)) def test_WHEN_temperature_is_set_THEN_temperature_and_setpoint_readbacks_update_to_new_value( self, _, val): self.ca.set_pv_value("TEMP:SP", val) self.ca.assert_that_pv_is_number("TEMP:SP:RBV", val, tolerance=0.1) self.ca.assert_that_pv_is_number("TEMP:1", val, tolerance=0.1) self.ca.assert_that_pv_is_number("TEMP:2", val, tolerance=0.1) self.ca.assert_that_pv_is_number("TEMP:3", val, tolerance=0.1) @parameterized.expand(chan for chan in parameterized_list(CHANNELS)) @skip_if_recsim( "Comes back via record redirection which recsim can't handle easily") def test_WHEN_control_channel_is_set_THEN_control_channel_can_be_read_back( self, _, chan): self.ca.assert_setting_setpoint_sets_readback(chan, "CTRLCHANNEL") @parameterized.expand(mode for mode in CTRL_MODE_ALARMS) @skip_if_recsim( "Comes back via record redirection which recsim can't handle easily") def test_WHEN_setting_control_mode_THEN_can_be_read_back(self, mode): self.ca.assert_setting_setpoint_sets_readback( mode, "CTRL", expected_alarm=CTRL_MODE_ALARMS[mode]) @skip_if_recsim("Backdoor does not exist in recsim") def test_WHEN_sweeping_mode_is_set_via_backdoor_THEN_it_updates_in_the_ioc( self): self._lewis.backdoor_set_on_device("sweeping", False) self.ca.assert_that_pv_is("SWEEPING", "Not Sweeping") self._lewis.backdoor_set_on_device("sweeping", True) self.ca.assert_that_pv_is("SWEEPING", "Sweeping") @parameterized.expand(state for state in ("ON", "OFF")) @skip_if_recsim( "Comes back via record redirection which recsim can't handle easily") def test_WHEN_setting_autopid_THEN_readback_reflects_setting_just_sent( self, state): self.ca.assert_setting_setpoint_sets_readback(state, "AUTOPID") @parameterized.expand(val for val in parameterized_list(TEST_VALUES)) @skip_if_recsim("Backdoor does not exist in recsim") def test_WHEN_heater_voltage_is_set_THEN_heater_voltage_updates( self, _, val): self.ca.set_pv_value("HEATERP:SP", val) self.ca.assert_that_pv_is_number("HEATERP", val, tolerance=0.1) # Emulator responds with heater p == heater v. Test that heater p is also reading. self.ca.assert_that_pv_is_number("HEATERV", val, tolerance=0.1) @parameterized.expand( control_command for control_command in parameterized_list(ALL_CONTROL_COMMANDS)) @skip_if_recsim( "Comes back via record redirection which recsim can't handle easily") def test_WHEN_control_command_sent_THEN_remote_unlocked_set( self, _, control_pv, set_value): self.ca.set_pv_value("CTRL", "Locked") self.ca.set_pv_value("{}:SP".format(control_pv), set_value) self.ca.assert_that_pv_is("CTRL", "Local and remote") self.ca.set_pv_value("CTRL", "Locked") @skip_if_recsim( "Comes back via record redirection which recsim can't handle easily") def test_WHEN_sweeping_reported_by_hardware_THEN_correct_sweep_state_reported( self): """ The hardware can report the control channel with and without a leading zero (depending on the hardware). Ensure we catch all cases. """ for report_sweep_state_with_leading_zero in [True, False]: for sweeping in [True, False]: self._lewis.backdoor_set_on_device( "report_sweep_state_with_leading_zero", report_sweep_state_with_leading_zero) self._lewis.backdoor_set_on_device("sweeping", sweeping) self.ca.assert_that_pv_is( "SWEEPING", "Sweeping" if sweeping else "Not Sweeping")
class Jsco4180Tests(unittest.TestCase): """ Tests for the Jsco4180 IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc(DEVICE_NAME, DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=30) for pv in required_pvs: self.ca.assert_that_pv_exists(pv, timeout=30) self._lewis.backdoor_run_function_on_device("reset") @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_running_THEN_retry_run_and_updates_component(self): expected_value_A = 30 expected_value_B = 15 expected_value_C = 55 self.ca.set_pv_value("COMP:A:SP", expected_value_A) self.ca.set_pv_value("COMP:B:SP", expected_value_B) self.ca.set_pv_value("COMP:C:SP", expected_value_C) self.ca.set_pv_value("START:SP", 1) sleep(10) # Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components. self._lewis.backdoor_set_on_device("component_A", 25) self._lewis.backdoor_set_on_device("component_B", 10) self._lewis.backdoor_set_on_device("component_C", 14) self.ca.assert_that_pv_is("COMP:A", expected_value_A, timeout=30) self.ca.assert_that_pv_is("COMP:B", expected_value_B, timeout=30) self.ca.assert_that_pv_is("COMP:C", expected_value_C, timeout=30) # there was a previous problem where if setpoint and readback differed a sleep and resend was started, # but the old state machine did not look to see if a new sp was issued while it was asleep and so then # resent the old out of date SP @unstable_test(max_retries=2, wait_between_runs=60) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_send_new_sp_THEN_state_machine_aborts_resend(self): value = 50 self.ca.set_pv_value("COMP:A:SP", value) self.ca.set_pv_value("COMP:B:SP", value) self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("STATUS", "Pumping", timeout=5) self.ca.assert_that_pv_is("COMP:A", value, timeout=30) self.ca.assert_that_pv_is("COMP:B", value, timeout=30) # Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components after a delay initial_delay = self.ca.get_pv_value("ERROR:DELAY") # delay before state machine reset delay = 30 # Increase delay to avoid race conditions self.ca.set_pv_value("ERROR:DELAY", delay) try: with self.ca.assert_pv_not_processed("RESET:SP"): self._lewis.backdoor_set_on_device("component_A", value - 5) self.ca.assert_that_pv_is("COMP:A", value - 5, timeout=5) sleep(delay / 2.0) # however if we change setpoint, the loop should start again self._lewis.backdoor_set_on_device("component_A", value - 5) self.ca.set_pv_value("COMP:A:SP", value - 10) self.ca.set_pv_value("COMP:B:SP", value + 10) # reset should not have happened yet self.ca.assert_that_pv_is("COMP:A", value - 5, timeout=delay / 2.0) self.ca.assert_that_pv_value_is_unchanged("COMP:A", wait=delay / 2.0) # Reset should now happen within a further timeout/2 seconds (but give it longer to avoid races) with self.ca.assert_pv_processed("RESET:SP"): self.ca.assert_that_pv_is("COMP:A", value - 10, timeout=delay * 2) finally: # Put error delay back to it's initial value self.ca.set_pv_value("ERROR:DELAY", initial_delay) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_running_continuous_THEN_retry_run_and_updates_component_in_correct_mode( self): value = 50 expected_value = "Pumping" self.ca.set_pv_value("COMP:A:SP", value) self.ca.set_pv_value("COMP:B:SP", value) self.ca.set_pv_value("START:SP", 1) # Give the device some time running in a good state sleep(10) # Sabotage! - Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components. self._lewis.backdoor_set_on_device("component_A", 33) self.ca.assert_that_pv_is("STATUS", expected_value, timeout=30) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_running_timed_THEN_retry_run_and_updates_component_in_correct_mode( self): value = 50 expected_value = "Pumping" self.ca.set_pv_value("COMP:A:SP", value) self.ca.set_pv_value("COMP:B:SP", value) self.ca.set_pv_value("TIME:RUN:SP", 100) self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1) # Give the device some time running in a good state sleep(10) # Sabotage! - Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components. self._lewis.backdoor_set_on_device("component_A", 33) self.ca.assert_that_pv_is("STATUS", expected_value, timeout=30) @skip_if_recsim("Flowrate device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_flowrate_THEN_flowrate_setpoint_is_correct(self): error_delay = float(self.ca.get_pv_value("ERROR:DELAY")) sleep(2 * error_delay) # To make sure we're not in the middle of the error-checking state machine expected_value = 1.000 self.ca.set_pv_value("FLOWRATE:SP", expected_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_value) self.ca.set_pv_value("TIME:RUN:SP", 100) self.ca.set_pv_value("START:SP", "Start") self.ca.assert_that_pv_is("FLOWRATE", expected_value) @skip_if_recsim("LeWIS backdoor not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_flowrate_and_pump_volume_THEN_ioc_uses_rbv_for_calculation_of_remaining_time(self): expected_sp_value = 1.000 expected_rbv_value = 2.000 pump_for_volume = 2 expected_time_value = (pump_for_volume / expected_rbv_value) * 60 error_delay = float(self.ca.get_pv_value("ERROR:DELAY")) sleep(2 * error_delay) # To make sure we're not in the middle of the error-checking state machine # 1. set invalid flowrate setpoint (FLOWRATE:SP) self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value) # 2. set valid hardware flowrate (FLOWRATE:SP:RBV) via backdoor command self._lewis.backdoor_set_on_device("flowrate_rbv", expected_rbv_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_rbv_value) # 3. set volume setpoint and start pump self.ca.set_pv_value("TIME:VOL:SP", pump_for_volume) self.ca.set_pv_value("START:SP", "Start") # 4. check calculated time is based on flowrate setpoint readback (:SP:RBV rather than :SP) self.ca.assert_that_pv_is("TIME:VOL:CALCRUN", expected_time_value) @skip_if_recsim("LeWIS backdoor not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_flowrate_and_pump_time_THEN_ioc_uses_rbv_for_calculation_of_remaining_volume(self): expected_sp_value = 1.000 expected_rbv_value = 2.000 pump_for_time = 120 expected_volume_value = (pump_for_time * expected_rbv_value) / 60 error_delay = float(self.ca.get_pv_value("ERROR:DELAY")) sleep(2 * error_delay) # To make sure we're not in the middle of the error-checking state machine # 1. set invalid flowrate setpoint (FLOWRATE:SP) self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value) # 2. set valid hardware flowrate (FLOWRATE:SP:RBV) via backdoor command self._lewis.backdoor_set_on_device("flowrate_rbv", expected_rbv_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_rbv_value) # 3. set time setpoint and start pump self.ca.set_pv_value("TIME:RUN:SP", pump_for_time) self.ca.set_pv_value("START:SP", "Start") # 4. check calculated volume is based on flowrate setpoint readback (:SP:RBV rather than :SP) self.ca.assert_that_pv_is("TIME:RUN:CALCVOL", expected_volume_value) # test to check that the IOC updates the flowrate RBV quickly enough # for the remaining volume calculation to be valid. simulates operation of a script. def test_GIVEN_an_ioc_WHEN_set_flowrate_and_immediately_set_pump_to_start_THEN_ioc_updates_rbv_for_calculation_of_remaining_volume( self): expected_sp_value = 2.000 script_sp_value = 3.000 pump_for_time = 120 # 1. initialize flowrate self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value, timeout=5) # 2. set new flowrate and immediately set pump to run, to simulate script self.ca.set_pv_value("FLOWRATE:SP", script_sp_value) self.ca.set_pv_value("TIME:RUN:SP", pump_for_time) self.ca.set_pv_value("START:SP", "Start") # 3. calculate remaining volume expected_volume_value = (pump_for_time * self.ca.get_pv_value("FLOWRATE:SP:RBV")) / 60 # 4. check ioc calculation is as expected self.ca.assert_that_pv_is("TIME:RUN:CALCVOL", expected_volume_value) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_maximum_pressure_limit_THEN_maximum_pressure_limit_is_correct(self): expected_value = 200 self.ca.assert_setting_setpoint_sets_readback(expected_value, "PRESSURE:MAX") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_minimum_pressure_limit_THEN_minimum_pressure_limit_is_correct(self): expected_value = 100 self.ca.set_pv_value("PRESSURE:MIN:SP", expected_value) self.ca.assert_setting_setpoint_sets_readback(expected_value, "PRESSURE:MIN") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_continuous_pump_set_THEN_pump_on(self): self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("STATUS", "Pumping") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_timed_pump_set_THEN_timed_pump_on(self): # Set a run time for a timed run self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1) self.ca.assert_that_pv_is("STATUS", "Pumping") @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_an_ioc_WHEN_get_current_pressure_THEN_current_pressure_returned(self): expected_value = 300 self._lewis.backdoor_set_on_device("pressure", expected_value) self.ca.assert_that_pv_is("PRESSURE", expected_value) @parameterized.expand([ ("component_{}".format(suffix), suffix) for suffix in ["A", "B", "C", "D"] ]) @skip_if_recsim("Reliant on setUP lewis backdoor call") def test_GIVEN_an_ioc_WHEN_get_component_THEN_correct_component_returned(self, component, suffix): expected_value = 10.0 self._lewis.backdoor_set_on_device(component, expected_value) self.ca.assert_that_pv_is("COMP:{}".format(suffix), expected_value) @parameterized.expand([ ("COMP:{}".format(suffix), suffix) for suffix in ["A", "B", "C"] ]) @skip_if_recsim("Reliant on setUP lewis backdoor call") def test_GIVEN_an_ioc_WHEN_set_component_THEN_correct_component_set(self, component, suffix): expected_value = 100.0 self.ca.set_pv_value("COMP:{}:SP".format(suffix), expected_value) if component == "COMP:A": self.ca.set_pv_value("COMP:B:SP", 0) self.ca.set_pv_value("COMP:C:SP", 0) elif component == "COMP:B": self.ca.set_pv_value("COMP:A:SP", 0) self.ca.set_pv_value("COMP:C:SP", 0) elif component == "COMP:C": self.ca.set_pv_value("COMP:A:SP", 0) self.ca.set_pv_value("COMP:B:SP", 0) self.ca.set_pv_value("PUMP_FOR_TIME:SP", "Start") self.ca.assert_that_pv_is(component, expected_value) def test_GIVEN_ioc_initial_state_WHEN_get_error_THEN_error_returned(self): expected_value = "No error" self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_ioc_in_hardware_error_state_WHEN_get_error_THEN_hardware_error_returned(self): expected_value = "Hardware error" self._lewis.backdoor_set_on_device("error", ERROR_STATE_HARDWARE_FAULT) self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_ioc_in_error_state_WHEN_reset_error_THEN_error_reset(self): expected_value = "No error" self._lewis.backdoor_set_on_device("error", ERROR_STATE_NO_ERROR) self.ca.set_pv_value("ERROR:SP", "Reset") self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_ioc_in_error_state_WHEN_reset_error_THEN_error_reset(self): expected_value = "No error" self._lewis.backdoor_set_on_device("error", ERROR_STATE_HARDWARE_FAULT) self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_device_not_connected_WHEN_get_error_THEN_alarm(self): self._lewis.backdoor_set_on_device('connected', False) self.ca.assert_that_pv_alarm_is('ERROR:SP', ChannelAccess.Alarms.INVALID) @skip_if_recsim("Reliant on setUP lewis backdoor call") def test_GIVEN_timed_pump_WHEN_get_program_runtime_THEN_program_runtime_increments(self): self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1) self.ca.assert_that_pv_value_is_increasing("TIME", wait=2) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_timed_pump_WHEN_set_constant_pump_THEN_state_updated_to_constant_pump(self): # Set a run time for a timed run self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.process_pv("PUMP_FOR_TIME:SP") expected_value = "Pumping" self.ca.assert_that_pv_is("STATUS", expected_value) self.ca.process_pv("START:SP") expected_value = "Pumping" self.ca.assert_that_pv_is("STATUS", expected_value) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_constant_pump_WHEN_set_timed_pump_THEN_state_updated_to_timed_pump(self): expected_value = "Pumping" self.ca.process_pv("START:SP") self.ca.assert_that_pv_is("STATUS", expected_value) # Set a run time for a timed run self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.process_pv("PUMP_FOR_TIME:SP") self.ca.assert_that_pv_is("STATUS", expected_value) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_input_incorrect_WHEN_set_flowrate_THEN_trouble_message_returned(self): self._lewis.backdoor_set_on_device("input_correct", False) self.ca.set_pv_value("FLOWRATE:SP", 0.010) self.ca.assert_that_pv_is("ERROR:STR", "[Error:stack underflow]") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_command_seq_that_would_crash_pump_WHEN_command_seq_called_THEN_pump_crashes(self): self.ca.set_pv_value("_TEST_CRASH.PROC", 1) self.ca.assert_that_pv_alarm_is("COMP:A", ChannelAccess.Alarms.INVALID, timeout=30) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_pump_running_WHEN_set_file_number_command_called_THEN_program_is_busy_error(self): expected_value = "[Program is Busy]" self.ca.set_pv_value("START:SP", 1) self.ca.set_pv_value("FILE:SP", 0) self.ca.assert_that_pv_is("ERROR:STR", expected_value) @parameterized.expand([("low_set_time", 100, 1, 1), ("high_set_time", 1000, 10, 1), ("non_standard_set_time", 456, 5, 1)]) @unstable_test(max_retries=5) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_pump_for_volume_WHEN_pumping_THEN_device_is_pumping_set_volume(self, _, time, volume, flowrate): # Set a target pump time a target pump volume. When we start a pump set volume run, then the remaining # time should be related to the target volume, and not the target time (that would be used for a pump for time). set_time = time set_volume = volume set_flowrate = flowrate expected_time = set_volume * set_flowrate * 60 # flow rate units = mL/min, so convert to seconds self.ca.set_pv_value("TIME:RUN:SP", set_time) self.ca.set_pv_value("TIME:VOL:SP", set_volume) self.ca.set_pv_value("FLOWRATE:SP", set_flowrate) self.ca.process_pv("PUMP_SET_VOLUME:SP") self.ca.assert_that_pv_is_within_range("TIME:REMAINING", min_value=expected_time - 20, max_value=expected_time + 20)
class ZeroFieldMagFieldTests(unittest.TestCase): def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("DISABLE", timeout=30) self.write_offset(0) self.ca.set_pv_value("RANGE", 1.0, sleep_after_set=0.0) self.write_simulated_field_values(ZERO_FIELD) self.write_simulated_alarm_level(self.ca.Alarms.NONE) self.ca.process_pv("TAKEDATA") def write_offset(self, offset): """ Writes offset values for all three IOC components Args: offset: float, the offset to be written to the IOC Returns: None """ for axis in AXES.keys(): self.ca.set_pv_value("OFFSET:{}".format(axis), offset, sleep_after_set=0.0) def write_sensor_matrix(self, sensor_matrix): """ Writes the provided sensor matrix to the relevant PVs Args: sensor_matrix: 3x3 numpy ndarray containing the values to use as the fixed sensor matrix. Returns: None """ assert sensor_matrix.shape == (SENSOR_MATRIX_SIZE, SENSOR_MATRIX_SIZE) for i in range(SENSOR_MATRIX_SIZE): for j in range(SENSOR_MATRIX_SIZE): self.ca.set_pv_value(SENSOR_MATRIX_PVS.format(row=i+1, column=j+1), sensor_matrix[i, j], sleep_after_set=0.0) def apply_offset_to_field(self, simulated_field, offset): """ Applies offset to the simulated measured field Args: simulated_field: dict with 'X', 'Y' and 'Z' keys. Values are the corresponding simulated field values offset: float, The offset to subtract from the input data. Applies same offset to all fields Returns: offset_applied_field: dict with 'X', 'Y', 'Z' keys. Values are offset-subtracted simulated_field values """ offset_applied_field = {} for axis in AXES.keys(): offset_applied_field[axis] = simulated_field[axis] - offset return offset_applied_field def write_simulated_field_values(self, simulated_field): """ Writes the given simulated field values to the IOC. Also asserts that the value has been taken up by the '_RAW' PV. We need to do this because the '_RAW' PVs are on SCAN = .1 second in RECSIM, so some time is taken between writing the SIM field and it being available in the '_RAW' PV. Args: simulated_field: dict with 'X', 'Y' and 'Z' keys. Values are the corresponding simulated field values Returns: None """ for component in AXES.keys(): self.ca.set_pv_value("SIM:DAQ:{}".format(component), simulated_field[component], sleep_after_set=0.0) self.ca.assert_that_pv_is_number("DAQ:{}:_RAW".format(component), simulated_field[component]) def apply_offset_and_matrix_multiplication(self, simulated_field, offset, sensor_matrix): """ Applies trasformation between raw or 'measured' field to 'corrected' field. Subtracts the offset from the input (raw) data, then matrix multiplies by the sensor matrix. Args: simulated_field: dict with keys matching AXES (X, Y and Z). Values are the simulated field values offset: float, The Offset to subtract from the input data. Applies same offset to all fields sensor_matrix: 3x3 numpy ndarray containing the values to use as the fixed sensor matrix. Returns: corrected_field_vals: 3-element array containing corrected X, Y and Z field values """ offset_input_field = self.apply_offset_to_field(simulated_field, offset) corrected_field_vals = np.matmul(sensor_matrix, np.array([offset_input_field["X"], offset_input_field["Y"], offset_input_field["Z"]])) return corrected_field_vals def get_overload_range_value(self): """ Returns the maximum value an input field can have before the magnetometer is overloaded """ return self.ca.get_pv_value("RANGE") * 4.5 def write_simulated_alarm_level(self, level): """ Writes to the SIML field of the RAW data pvs. This sets the severity level of the three pvs to level. Waits for the SEVR fields of the RAW data pvs to update before returning. Args: level: Class attribute of ChannelAccess.Alarms (e.g. ca.Alarms.NONE). The severity level to set to the PV """ for axis in AXES.keys(): self.ca.set_pv_value("DAQ:{}:_RAW.SIMS".format(axis), level, sleep_after_set=0.0) # Wait for the raw PVs to process for axis in AXES.keys(): self.ca.assert_that_pv_alarm_is("DAQ:{}:_RAW".format(axis), level) @parameterized.expand(parameterized_list(itertools.product(AXES.keys(), FIELD_STRENGTHS))) def test_GIVEN_field_offset_THEN_field_strength_read_back_with_offset_applied(self, _, hw_axis, field_strength): # GIVEN self.write_offset(OFFSET) field = {"X": 0, "Y": 0, "Z": 0} field[hw_axis] = field_strength self.write_simulated_field_values(field) self.ca.set_pv_value("SIM:DAQ:{}".format(hw_axis), field_strength, sleep_after_set=0.0) # WHEN self.ca.process_pv("TAKEDATA") # THEN self.ca.assert_that_pv_is_number("APPLYOFFSET:{}".format(hw_axis), field_strength-OFFSET) def test_GIVEN_offset_corrected_field_WHEN_sensor_matrix_is_identity_THEN_input_field_returned_by_matrix_multiplier(self): offset_corrected_field = {"X": 1.1, "Y": 2.2, "Z": 3.3} # GIVEN self.write_simulated_field_values(offset_corrected_field) # WHEN self.write_sensor_matrix(np.identity(3)) self.ca.process_pv("TAKEDATA") # THEN for hw_axis in AXES.keys(): expected_value = offset_corrected_field[hw_axis] self.ca.assert_that_pv_is_number("CORRECTEDFIELD:{}".format(hw_axis), expected_value, tolerance=0.1*abs(expected_value)) self.ca.assert_that_pv_alarm_is("CORRECTEDFIELD:{}".format(hw_axis), self.ca.Alarms.NONE) @parameterized.expand(parameterized_list(['X', 'Y', 'Z'])) def test_GIVEN_sensor_matrix_with_only_one_nonzero_row_THEN_corrected_field_has_component_in_correct_dimension(self, _, hw_axis): input_field = {"X": 1.1, "Y": 2.2, "Z": 3.3} self.write_simulated_field_values(input_field) # GIVEN sensor_matrix = np.zeros((3, 3)) # Set one row to one if hw_axis == "X": sensor_matrix[0, :] = 1 elif hw_axis == "Y": sensor_matrix[1, :] = 1 elif hw_axis == "Z": sensor_matrix[2, :] = 1 # WHEN self.write_sensor_matrix(sensor_matrix) self.ca.process_pv("TAKEDATA") # THEN for component in AXES.keys(): if component == hw_axis: expected_value = sum(input_field.values()) else: expected_value = 0 self.ca.assert_that_pv_is_number("CORRECTEDFIELD:{}".format(component), expected_value) def test_GIVEN_test_input_field_strengths_WHEN_corrections_applied_THEN_corrected_fields_agree_with_labview(self): # GIVEN input_field = {"X": 11.1, "Y": 22.2, "Z": 33.3} input_offsets = {"X": -8.19e-1, "Y": 3.45e-1, "Z": -6.7e-1} sensor_matrix = np.array([-1.17e-1, 7.36e-2, -2e-1, -3.41e-1, -2.15e-1, -3e-1, -2.3e-1, -4e-2, 1e-1]).reshape(3, 3) self.write_simulated_field_values(input_field) self.write_sensor_matrix(sensor_matrix) for axis in input_offsets.keys(): self.ca.set_pv_value("OFFSET:{}".format(axis), input_offsets[axis], sleep_after_set=0.0) # WHEN self.ca.process_pv("TAKEDATA") # THEN labview_result = {"X": -6.58, "Y": -18.9542, "Z": -0.21857} for component in AXES.keys(): self.ca.assert_that_pv_is_number("CORRECTEDFIELD:{}".format(component), labview_result[component], tolerance=1e-4) def test_GIVEN_measured_data_WHEN_corrections_applied_THEN_field_magnitude_read_back(self): # GIVEN input_field = {"X": 2.2, "Y": 3.3, "Z": 4.4} sensor_matrix = np.array([-1.17e-1, 7.36e-2, -2e-1, -3.41e-1, -2.15e-1, -3e-1, -2.3e-1, -4e-2, 1e-1]).reshape(3, 3) self.write_simulated_field_values(input_field) self.write_offset(OFFSET) self.write_sensor_matrix(sensor_matrix) # WHEN self.ca.process_pv("TAKEDATA") # THEN expected_field_vals = self.apply_offset_and_matrix_multiplication(input_field, OFFSET, sensor_matrix) expected_magnitude = np.linalg.norm(expected_field_vals) self.ca.assert_that_pv_is_number("FIELDSTRENGTH", expected_magnitude, tolerance=0.1*expected_magnitude, timeout=30) def test_WHEN_takedata_alias_processed_THEN_all_magnetometer_axes_read_and_processed(self): # GIVEN test_field = {"X": 1.1, "Y": 2.2, "Z": 3.3} self.write_simulated_field_values(test_field) for component in AXES.keys(): self.ca.assert_that_pv_is_not_number("DAQ:{}".format(component), test_field[component]) # WHEN self.ca.process_pv("TAKEDATA") # THEN for component in AXES.keys(): self.ca.assert_that_pv_is_number("DAQ:{}".format(component), test_field[component], tolerance=0.1*test_field[component]) @parameterized.expand(parameterized_list(FIELD_STRENGTHS)) def test_GIVEN_magnetometer_scaling_factor_WHEN_data_read_THEN_inputs_scaled_by_factor(self, _, factor): # GIVEN self.ca.set_pv_value("RANGE", factor, sleep_after_set=0.0) test_field = {"X": 1.1, "Y": 2.2, "Z": 3.3} self.write_simulated_field_values(test_field) self.ca.process_pv("TAKEDATA") # THEN for component in AXES.keys(): self.ca.assert_that_pv_is_number("MEASURED:{}".format(component), test_field[component]*factor) @parameterized.expand(parameterized_list(AXES.keys())) def test_GIVEN_measured_field_too_high_THEN_overload_pv_reads_true_and_is_in_alarm(self, _, axis): # GIVEN test_field = { "X": 1.1, "Y": 1.1, "Z": 1.1 } test_field[axis] = self.ca.get_pv_value("RANGE") * 4.5 + 1.0 # WHEN self.write_simulated_field_values(test_field) self.ca.process_pv("TAKEDATA") # THEN self.ca.assert_that_pv_is("OVERLOAD", "OVERLOADED") self.ca.assert_that_pv_alarm_is("OVERLOAD", self.ca.Alarms.MAJOR) def test_GIVEN_measured_field_in_range_THEN_overload_pv_reads_false_and_not_in_alarm(self): # GIVEN test_value = self.get_overload_range_value() - 1.0 test_field = { "X": test_value, "Y": test_value, "Z": test_value } # WHEN self.write_simulated_field_values(test_field) self.ca.process_pv("TAKEDATA") # THEN self.ca.assert_that_pv_is("OVERLOAD", "NORMAL") self.ca.assert_that_pv_alarm_is("OVERLOAD", self.ca.Alarms.NONE) def test_GIVEN_field_overloaded_THEN_output_PVs_in_major_alarm(self): # GIVEN overload_value = self.get_overload_range_value() + 1.0 test_field = { "X": overload_value, "Y": overload_value, "Z": overload_value } self.write_simulated_field_values(test_field) self.ca.process_pv("TAKEDATA") # THEN self.ca.assert_that_pv_alarm_is("FIELDSTRENGTH", self.ca.Alarms.MAJOR) for axis in AXES.keys(): self.ca.assert_that_pv_alarm_is("CORRECTEDFIELD:{}".format(axis), self.ca.Alarms.MAJOR) @parameterized.expand(parameterized_list(itertools.product([ChannelAccess.Alarms.INVALID, ChannelAccess.Alarms.MAJOR, ChannelAccess.Alarms.MAJOR], PVS_WHICH_USE_DAQ_DATA))) def test_GIVEN_raw_daq_pvs_in_alarm_WHEN_PVs_processed_THEN_alarm_copied_to_downstream_pvs(self, _, alarm, pv): # GIVEN self.ca.assert_that_pv_alarm_is("{}.SEVR".format(pv), self.ca.Alarms.NONE) self.write_simulated_alarm_level(alarm) self.ca.process_pv("TAKEDATA") # THEN self.ca.assert_that_pv_alarm_is("{}.SEVR".format(pv), alarm) @parameterized.expand(parameterized_list(AXES.keys())) def test_GIVEN_smoothing_samples_WHEN_setting_field_THEN_average_field_is_given(self, _, axis): number_samples = 10 field_number = 100 pv = "DAQ:{}".format(axis) with self._ioc.start_with_macros({"NUM_SAMPLES": number_samples}, pv_to_wait_for=pv): field = {"X": 0, "Y": 0, "Z": 0} self.write_simulated_field_values(field) for i in range(1, number_samples+1): self.ca.process_pv("TAKEDATA") self.ca.process_pv("TAKEDATA") # make sure the field is 0 self.ca.assert_that_pv_is_number(pv, 0) # Change the field number field[axis] = field_number self.write_simulated_field_values(field) # every sample check the average has been processed correctly for i in range(1, number_samples+1): self.ca.process_pv("TAKEDATA") # assert that after every TAKEDATA the average has gone up by the field_number divided by the sample # number self.ca.assert_that_pv_is_number(pv, (field_number // number_samples) * i) # Check the final value stays the same self.ca.process_pv("TAKEDATA") self.ca.assert_that_pv_is_number(pv, field_number)
class SimpleTests(unittest.TestCase): """ Tests for the stability checking logic """ def setUp(self): self._ioc = IOCRegister.get_running(DEVICE_PREFIX) self.assertIsNotNone(self._ioc) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=3) self.ca.assert_that_pv_exists("VAL") self.ca.assert_that_pv_exists("VAL:SP") self.ca.assert_that_pv_is_number("STAB:IS_STABLE.E", TOLERANCE) # Need to do this to ensure buffer is properly up before starting any tests self.ca.assert_that_pv_exists("STAB:_VAL_BUFF") while int(self.ca.get_pv_value("STAB:_VAL_BUFF.NUSE")) < NUMBER_OF_SAMPLES: self.ca.process_pv("VAL") self.ca.set_pv_value("VAL.SIMS", 0) def test_GIVEN_pv_not_changing_and_WHEN_pv_exactly_equal_to_sp_THEN_stable(self): test_value = 100 self.ca.set_pv_value("VAL:SP", test_value) for _ in range(NUMBER_OF_SAMPLES): self.ca.set_pv_value("VAL", test_value) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", True) @parameterized.expand(parameterized_list([operator.add, operator.sub])) def test_GIVEN_pv_not_changing_and_WHEN_pv_outside_tolerance_of_sp_THEN_stable(self, _, op): test_value = 200 self.ca.set_pv_value("VAL:SP", test_value) for _ in range(NUMBER_OF_SAMPLES): self.ca.set_pv_value("VAL", op(test_value, 1.1 * TOLERANCE)) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", False) @parameterized.expand(parameterized_list([operator.add, operator.sub])) def test_GIVEN_pv_not_changing_and_WHEN_pv_inside_tolerance_of_sp_THEN_stable(self, _, op): test_value = 300 self.ca.set_pv_value("VAL:SP", test_value) for _ in range(NUMBER_OF_SAMPLES): self.ca.set_pv_value("VAL", op(test_value, 0.9 * TOLERANCE)) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", True) def test_GIVEN_one_out_of_range_value_at_end_of_buffer_THEN_unstable(self): stable_value = 400 self.ca.set_pv_value("VAL:SP", stable_value) for _ in range(NUMBER_OF_SAMPLES - 1): self.ca.set_pv_value("VAL", stable_value) self.ca.set_pv_value("VAL", stable_value + 1.1 * TOLERANCE) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", False) def test_GIVEN_one_out_of_range_value_at_beginning_of_buffer_THEN_unstable(self): stable_value = 500 self.ca.set_pv_value("VAL:SP", stable_value) self.ca.set_pv_value("VAL", stable_value + 1.1 * TOLERANCE) for _ in range(NUMBER_OF_SAMPLES - 1): self.ca.set_pv_value("VAL", stable_value) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", False) def test_GIVEN_one_alarmed_value_at_end_of_buffer_THEN_unstable(self): stable_value = 400 self.ca.set_pv_value("VAL:SP", stable_value) for _ in range(NUMBER_OF_SAMPLES - 1): self.ca.set_pv_value("VAL", stable_value) self.ca.set_pv_value("VAL.SIMS", 3) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", False) def test_GIVEN_one_alarmed_value_at_beginning_of_buffer_THEN_unstable(self): stable_value = 500 self.ca.set_pv_value("VAL:SP", stable_value) self.ca.set_pv_value("VAL", stable_value) self.ca.set_pv_value("VAL.SIMS", 3) self.ca.set_pv_value("VAL.SIMS", 0) for _ in range(NUMBER_OF_SAMPLES - 2): # -2 because setting SEVR back to zero will cause a record to process. self.ca.set_pv_value("VAL", stable_value) self.ca.assert_that_pv_is("STAB:HAS_RECENT_ALARM", False) self.ca.assert_that_pv_is("STAB:IS_STABLE", False) # Adding one more valid reading at the end of the buffer should cause the invalid value at the beginning # to be forgotten, meaning it should then be considered stable self.ca.set_pv_value("VAL", stable_value) self.ca.assert_that_pv_is("STAB:IS_STABLE", True)
class RknpsTests(DanfysikCommon, unittest.TestCase): """ Tests for the RIKEN Multidrop Danfysik Power Supplies. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc("rknps", PREFIX) self.ca = ChannelAccess(device_prefix=PREFIX, default_timeout=60) self._lewis.backdoor_set_on_device("connected", True) self.current_readback_factor = 1000 self.id_prefixes = [ID + ":" for ID in IDS] for id_prefix in self.id_prefixes: self.ca.assert_that_pv_exists("{}ADDRESS".format(id_prefix), timeout=30) def disconnect_device(self): """RIKEN PSU emulator disconnects slightly differently""" self._lewis.backdoor_set_on_device("connected", False) def set_voltage(self, voltage): self._lewis.backdoor_set_on_device("set_all_volt_values", voltage) def _activate_interlocks(self): """ Activate both interlocks in the emulator. """ self._lewis.backdoor_set_on_device("set_all_interlocks", True) def _deactivate_interlocks(self): """ Deactivate both interlocks in the emulator. """ self._lewis.backdoor_set_on_device("set_all_interlocks", False) @skip_if_recsim("Interlock statuses depend on emulator") def test_WHEN_interlocks_are_active_THEN_ilk_is_Interlocked(self): self._activate_interlocks() for IDN in IDS: self.ca.assert_that_pv_is("{0}:ILK".format(IDN), HAS_TRIPPED[True]) @skip_if_recsim("Interlock statuses depend on emulator") def test_WHEN_interlocks_are_inactive_THEN_ilk_is_not_Interlocked(self): self._deactivate_interlocks() for IDN in IDS: self.ca.assert_that_pv_is("{0}:ILK".format(IDN), HAS_TRIPPED[False]) @skip_if_recsim( "In rec sim this test fails as the changes are not propagated to all appropriate PVs" ) def test_GIVEN_a_positive_value_and_emulator_in_use_WHEN_current_is_set_THEN_values_are_as_expected( self): expected_value = 480 for IDN in IDS: self.ca.set_pv_value("{0}:CURR:SP".format(IDN), expected_value) self.ca.assert_that_pv_is("{0}:CURR".format(IDN), expected_value) self.ca.assert_that_pv_is("{0}:RA".format(IDN), expected_value) self.ca.assert_that_pv_is("{0}:POL".format(IDN), "+") @skip_if_recsim( "In rec sim this test fails as the changes are not propagated to all appropriate PVs" ) def test_GIVEN_a_negative_value_and_emulator_in_use_WHEN_current_is_set_THEN_values_are_as_expected( self): expected_value = -123 for IDN in IDS: self.ca.set_pv_value("{0}:CURR:SP".format(IDN), expected_value) self.ca.assert_that_pv_is("{0}:CURR".format(IDN), expected_value) self.ca.assert_that_pv_is("{0}:RA".format(IDN), abs(expected_value)) self.ca.assert_that_pv_is("{0}:POL".format(IDN), "-") @skip_if_devsim("In dev sim this test fails as the emulator " "handles the difference in values between write and read") def test_GIVEN_a_negative_value_and_emulator_not_in_use_WHEN_current_is_set_THEN_values_are_as_expected( self): set_value = -123 return_value = set_value * 1000 for IDN in IDS: self.ca.set_pv_value("{0}:CURR:SP".format(IDN), set_value) self.ca.assert_that_pv_is("{0}:CURR".format(IDN), return_value) self.ca.assert_that_pv_is("{0}:RA".format(IDN), return_value) @skip_if_recsim("Power updates through protocol redirection") def test_GIVEN_rb3_status_changes_THEN_rb3_banner_pv_updates_correctly( self): if "RB3" not in IDS: self.fail("Didn't find RB3 for test.") for powered_on in (True, False): self.ca.set_pv_value("RB3:POWER:SP", powered_on) self.ca.assert_that_pv_is( "RB3:BANNER", "on; beam to ports 1,2" if powered_on else "off; ports 1,2 safe") @skip_if_recsim("Power updates through protocol redirection") def test_GIVEN_rb4_status_changes_THEN_rb4_banner_pv_updates_correctly( self): if "RB4" not in IDS: self.fail("Didn't find RB4 for test.") for powered_on in (True, False): self.ca.set_pv_value("RB4:POWER:SP", powered_on) self.ca.assert_that_pv_is( "RB4:BANNER", "on; beam to ports 3,4" if powered_on else "off; ports 3,4 safe") @parameterized.expand(INTERLOCKS) @skip_if_recsim("Test requires emulator to change interlock state") def test_GIVEN_interlock_status_WHEN_read_all_status_THEN_status_is_as_expected( self, interlock): for boolean_value, expected_value in HAS_TRIPPED.items(): for IDN, ADDR in zip(IDS, PSU_ADDRESSES): # GIVEN self._lewis.backdoor_run_function_on_device( "set_{0}".format(interlock), (boolean_value, ADDR)) # THEN self.ca.assert_that_pv_is("{0}:ILK:{1}".format(IDN, interlock), expected_value) self.ca.assert_that_pv_alarm_is( "{0}:ILK:{1}".format(IDN, interlock), self.ca.Alarms.NONE) @parameterized.expand(INTERLOCKS) @skip_if_recsim("Test requires emulator") def test_GIVEN_individual_interlock_read_WHEN_device_not_connected_THEN_interlock_PV_in_alarm( self, interlock): # WHEN self._lewis.backdoor_set_on_device("connected", False) # THEN for IDN, ADDR in zip(IDS, PSU_ADDRESSES): self.ca.assert_that_pv_alarm_is( "{0}:ILK:{1}".format(IDN, interlock), self.ca.Alarms.INVALID) @parameterized.expand( parameterized_list([ ("FAULT STATE", 0, 0), ("BEND 1", 1, 0), ("BEND 2", 0, 1), ("SEPTUM", 1, 1), ])) @skip_if_devsim("DAQ does not exist in devsim") def test_GIVEN_mock_DAQ_inputs_THEN_RB2_mode_is_correct( self, _, state, val1, val2): self.ca.set_pv_value("DAQ:R04:DATA:SIM", val1) self.ca.set_pv_value("DAQ:R05:DATA:SIM", val2) self.ca.assert_that_pv_is("RB2:MODE", state) @parameterized.expand( parameterized_list([ ("FAULT (LOW)", 0, 0), ("PORT 3 (RQ18-20)", 1, 0), ("PORT 4 (RQ21-23)", 0, 1), ("FAULT (HIGH)", 1, 1), ])) @skip_if_devsim("DAQ does not exist in devsim") def test_GIVEN_mock_DAQ_inputs_THEN_PORT3_4_mode_is_correct( self, _, state, val1, val2): self.ca.set_pv_value("DAQ:R02:DATA:SIM", val1) self.ca.set_pv_value("DAQ:R03:DATA:SIM", val2) self.ca.assert_that_pv_is("PORT3_4:MODE", state) @skip_if_devsim("DAQ does not exist in devsim") def test_GIVEN_fault_condition_THEN_RB2_alarms_correct(self): self.ca.set_pv_value("DAQ:R04:DATA:SIM", 0) self.ca.set_pv_value("DAQ:R05:DATA:SIM", 0) self.ca.assert_that_pv_alarm_is("RB2:MODE", ChannelAccess.Alarms.MAJOR) @skip_if_devsim("DAQ does not exist in devsim") def test_GIVEN_high_fault_condition_THEN_PORT3_4_alarms_correct(self): self.ca.set_pv_value("DAQ:R02:DATA:SIM", 1) self.ca.set_pv_value("DAQ:R03:DATA:SIM", 1) self.ca.assert_that_pv_alarm_is("PORT3_4:MODE", ChannelAccess.Alarms.MAJOR) @skip_if_devsim("DAQ does not exist in devsim") def test_GIVEN_low_fault_condition_THEN_PORT3_4_alarms_correct(self): self.ca.set_pv_value("DAQ:R02:DATA:SIM", 0) self.ca.set_pv_value("DAQ:R03:DATA:SIM", 0) self.ca.assert_that_pv_alarm_is("PORT3_4:MODE", ChannelAccess.Alarms.MAJOR)