예제 #1
0
class MezfliprTests(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)

    def test_WHEN_ioc_is_started_THEN_ioc_is_not_disabled(self):
        self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED")

    @skip_if_recsim("Disconnection simulation not implemented in recsim")
    def test_GIVEN_device_is_connected_THEN_can_read_id(self):
        self.ca.assert_that_pv_is("ID", "Flipper Control")
        self.ca.assert_that_pv_alarm_is("ID", self.ca.Alarms.NONE)

    @parameterized.expand(["ANALYSER", "POLARISER"])
    def test_GIVEN_amplitude_is_set_THEN_amplitude_can_be_read_back(
            self, flipper):
        for val in [0., 0.12, 2.99]:  # Amplitude should be limited to 3A
            self.ca.assert_setting_setpoint_sets_readback(
                val, readback_pv="{}:AMPLITUDE".format(flipper))

    @parameterized.expand(["ANALYSER", "POLARISER"])
    def test_GIVEN_compensation_is_set_THEN_compensation_can_be_read_back(
            self, flipper):
        for val in [0., 0.12, 5000.5]:
            self.ca.assert_setting_setpoint_sets_readback(
                val, readback_pv="{}:COMPENSATION".format(flipper))

    @parameterized.expand(["ANALYSER", "POLARISER"])
    def test_GIVEN_dt_is_set_THEN_dt_can_be_read_back(self, flipper):
        for val in [0., -0.12, -5000.5]:  # DT only accepts negative values.
            self.ca.assert_setting_setpoint_sets_readback(
                val, readback_pv="{}:DT".format(flipper))

    @parameterized.expand(["ANALYSER", "POLARISER"])
    def test_GIVEN_constant_is_set_THEN_constant_can_be_read_back(
            self, flipper):
        for val in [0., 0.12, 5000.5]:
            self.ca.assert_setting_setpoint_sets_readback(
                val, readback_pv="{}:CONSTANT".format(flipper))

    @parameterized.expand(["ANALYSER", "POLARISER"])
    def test_GIVEN_constant_is_set_THEN_constant_can_be_read_back(
            self, flipper):
        for filename in [r"C:\a.txt", r"C:\b.txt"]:
            self.ca.assert_setting_setpoint_sets_readback(
                filename, readback_pv="{}:FILENAME".format(flipper))

    @parameterized.expand([
        "Analyser Off Pol. Off",
        "Analyser Off Pol. On",
        "Analyser On Pol. Off",
        "Analyser On Pol. On",
    ])
    def test_GIVEN_toggle_state_is_set_THEN_toggle_state_can_be_read_back(
            self, toggle_state):
        self.ca.set_pv_value("TOGGLE:SP", toggle_state)
        self.ca.assert_that_pv_is("TOGGLE", toggle_state)
        self.ca.assert_that_pv_alarm_is("TOGGLE", self.ca.Alarms.NONE)
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")
예제 #3
0
class CurrentTests(unittest.TestCase):
    # These current testing values are uncalibrated values from the DAQ lying between 0 and 10.
    current_values = [0, 1.33333, 5e1, 10e-3, 10]
    current_values_which_give_alarms = [10, 11]

    def setUp(self):
        self.ca = ChannelAccess(20, device_prefix=DEVICE_PREFIX)
        shared_setup(self.ca)
        self._simulate_current(0)

    def _simulate_current(self, current):
        curr_array = [current] * 1000
        self.ca.set_pv_value("DAQ:CURR:WV:SIM", curr_array)

    @parameterized.expand(parameterized_list(current_values))
    def test_that_GIVEN_current_value_THEN_calibrated_current_readback_changes(
            self, _, value):
        # GIVEN
        self._simulate_current(value)

        self.ca.assert_that_pv_is_number("CURR",
                                         value * DAQ_CURR_READ_SCALE_FACTOR,
                                         MARGIN_OF_ERROR)

    @parameterized.expand(parameterized_list(current_values_which_give_alarms))
    def test_that_WHEN_current_is_out_of_range_THEN_alarm_raised(
            self, _, value):
        # WHEN
        self._simulate_current(value)

        # THEN
        self.ca.assert_that_pv_alarm_is("CURR", ChannelAccess.Alarms.MAJOR)
예제 #4
0
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)
def set_axis_moving(axis):
    ca_motors = ChannelAccess(device_prefix="MOT")
    current_position = ca_motors.get_pv_value(axis)
    low_limit = ca_motors.get_pv_value(axis + ":MTR.LLM")
    high_limit = ca_motors.get_pv_value(axis + ":MTR.HLM")
    if current_position - low_limit < high_limit - current_position:
        ca_motors.set_pv_value(axis + ":SP", high_limit)
    else:
        ca_motors.set_pv_value(axis + ":SP", low_limit)
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)
예제 #7
0
class Moxa1210Tests(unittest.TestCase):
    """
    Tests for the Moxa ioLogik e1210. (16x Discrete inputs)
    """

    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc("moxa12xx", DEVICE_PREFIX)

        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)

        # Sends a backdoor command to the device to set a discrete input (DI) value

        self._lewis.backdoor_run_function_on_device("set_di", (0, [False]*16))

    def resetDICounter(self, channel):
        """
        Reset the counters for each DI (channel)

        Args:
            channel (int) : The DI (channel) counter to be reset

        We typically want to preserve our counter values for each channel even upon restart. For testing purposes
        this function will reset the counter values to 0. 
        """
        self.ca.set_pv_value("CH{:01d}:DI:CNT".format(channel), 0)

    @parameterized.expand([
        ("CH{:01d}".format(channel), channel) for channel in CHANNELS
    ])
    def test_WHEN_DI_input_is_switched_on_THEN_only_that_channel_readback_changes_to_state_just_set(self, _, channel):
        self._lewis.backdoor_run_function_on_device("set_di", (channel, (True,)))

        self.ca.assert_that_pv_is("CH{:d}:DI".format(channel), "High")

        # Test that all other channels are still off
        for test_channel in CHANNELS:
            if test_channel == channel:
                continue

            self.ca.assert_that_pv_is("CH{:1d}:DI".format(test_channel), "Low")

    @parameterized.expand([
        ("CH{:01d}:DI:CNT".format(channel), channel) for channel in CHANNELS
    ])
    def test_WHEN_di_input_is_triggered_a_number_of_times_THEN_di_counter_matches(self, channel_pv, channel):
        self.resetDICounter(channel)
        expected_count = 5

        for i in range(expected_count):
            # Toggle channel and ensure it's registered the trigger
            self._lewis.backdoor_run_function_on_device("set_di", (channel, (True,)))
            self._lewis.backdoor_run_function_on_device("set_di", (channel, (False,)))
            self.ca.assert_that_pv_is(channel_pv, i+1, timeout=5)

        self.ca.assert_that_pv_is(channel_pv, expected_count)
예제 #8
0
class Lksh336Tests(unittest.TestCase):
    """
    Tests for the Lksh336 IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "Lksh336", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)

    def test_GIVEN_temp_set_WHEN_read_THEN_temp_is_as_expected(self):
        test_value = 10
        self.ca.set_pv_value("SIM:TEMP_A", test_value)
        self.ca.assert_that_pv_is("SIM:TEMP_A", test_value)
class Sans2dAperturesGuidesTests(unittest.TestCase):
    """
    Tests for the sans2d waveguides and apertures tank motor extensions.
    """

    def setUp(self):
        self.ca = ChannelAccess()

    @parameterized.expand(AXES_TO_STOP)
    def test_GIVEN_move_enabled_axis_moving_WHEN_stop_all_THEN_axis_stopped(self, axis):
        # Set interlock to enabled
        self.ca.set_pv_value("FINS_VAC:SIM:ADDR:1001", 64)
        self.ca.assert_that_pv_is("FINS_VAC:GALIL_INTERLOCK", "CAN MOVE")
        # Stop axis to prevent test instability (other tests may have left it moving)
        stop_axis_moving(axis)
        assert_axis_not_moving(axis)
        # Execute test
        for _ in range(3):
            set_axis_moving(axis)
            assert_axis_moving(axis)
            self.ca.set_pv_value("MOT:SANS2DAPWV:STOP_MOTORS:ALL", 1)
            assert_axis_not_moving(axis)

    @parameterized.expand(AXES_TO_STOP)
    def test_GIVEN_move_disabled_axis_moving_WHEN_stop_all_THEN_axis_not_stopped(self, axis):
        # Set interlock to disabled
        self.ca.set_pv_value("FINS_VAC:SIM:ADDR:1001", 0)
        self.ca.assert_that_pv_is("FINS_VAC:GALIL_INTERLOCK", "CANNOT MOVE")
        # Execute test
        for _ in range(3):
            set_axis_moving(axis)
            assert_axis_moving(axis)
            self.ca.set_pv_value("MOT:SANS2DAPWV:STOP_MOTORS:ALL", 1)
            assert_axis_moving(axis)
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 CP2800StatusTests(unittest.TestCase):
    def setUp(self):
        _, self._ioc = get_running_lewis_and_ioc(None, DEVICE_PREFIX)
        self.assertIsNotNone(self._ioc)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)

    def test_GIVEN_compressor_elapsed_minutes_THEN_alarm_correct(self):
        tests = [
            (-1, self.ca.Alarms.MAJOR),
            (0, self.ca.Alarms.NONE),
            (1, self.ca.Alarms.NONE),
            (500001, self.ca.Alarms.MINOR),
            (1000001, self.ca.Alarms.MAJOR),
        ]
        for test in tests:
            elapsed_time, expected_alarm = test
            self.ca.set_pv_value("SIM:ELAPSED", elapsed_time)
            self.ca.assert_that_pv_alarm_is("ELAPSED", expected_alarm, 10)

    def test_GIVEN_compressor_state_on_or_off_THEN_readback_correct(self):
        states = [(1, "On"), (0, "Off")]
        for state in states:
            send_val, expected_response = state
            self.ca.set_pv_value("SIM:POWER", send_val)
            self.ca.assert_that_pv_is("POWER", expected_response)

    def test_GIVEN_error_value_THEN_readback_correct(self):
        self.ca.set_pv_value("SIM:ERR", 1)
        self.ca.assert_that_pv_is("ERR", 1, timeout=10)

    def test_GIVEN_negative_error_value_THEN_alarm_correct(self):
        self.ca.set_pv_value("SIM:ERR", -1)
        self.ca.assert_that_pv_alarm_is("ERR",
                                        self.ca.Alarms.MAJOR,
                                        timeout=10)
예제 #12
0
class Mk2ChoprTests(unittest.TestCase):
    """
    Tests for the Mk2Chopr IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "Mk2Chopr", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)

    def test_GIVEN_frequency_set_WHEN_read_THEN_frequency_readback_is_as_expected(
            self):
        test_value = 10
        self.ca.set_pv_value("FREQ:SP", test_value)
        self.ca.assert_setting_setpoint_sets_readback(test_value, "FREQ",
                                                      "FREQ:SP", test_value)
예제 #13
0
class tcIocTests(unittest.TestCase):
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            EMULATOR_NAME, DEVICE_PREFIX)

        self.ca = ChannelAccess(device_prefix=None)

    def test_WHEN_var_one_and_two_written_to_THEN_output_is_sum(self):
        self.ca.set_pv_value("ITEST1", 10)
        self.ca.set_pv_value("ITEST2", 20)
        self.ca.assert_that_pv_is("ISUM", 30)

    @parameterized.expand(parameterized_list(["ENUM_VALUE_0", "ENUM_VALUE_1"]))
    def test_WHEN_enum_written_to_THEN_enum_readback_is_correct(self, _, enum):
        self.ca.set_pv_value("ETESTENUM", enum)
        self.ca.assert_that_pv_is("ETESTENUMRBV", enum)
예제 #14
0
class GamryTests(unittest.TestCase):
    """
    Tests for the Gamry.
    """

    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc("gamry", DEVICE_PREFIX)
        self.assertIsNotNone(self._lewis)
        self.assertIsNotNone(self._ioc)

        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)

    def test_GIVEN_start_charging_WHEN_charge_completed_THEN_charging_finished_received(self):
        self.ca.assert_that_pv_is("CHARGE:STAT", "Idle")
        self.ca.set_pv_value("CHARGE:SP", 1)
        self.ca.assert_that_pv_is("CHARGE:STAT", "Charging", timeout=2)
        self.ca.assert_that_pv_is("CHARGE:STAT", "Idle", timeout=10)
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 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)
예제 #17
0
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 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)
예제 #19
0
def _update_fields_continuously(psu_amps_at_measured_zero):
    """
    This method is run in a background process for some tests which require the measured fields to "respond" to the
    power supply setpoints in a semi-realistic way.

    It makes new channel access objects so that we don't share locks with the existing ones, as that can cause issues
    with the update rate of this loop. In general this loop needs to be about as fast (or ideally faster) than the
    loop speed of the IOC, so that whenever the state machine picks up new magnetometer readings they reflect the
    latest power supply setpoints.
    """
    psu_ca = {
        "X": ChannelAccess(device_prefix=X_KEPCO_DEVICE_PREFIX),
        "Y": ChannelAccess(device_prefix=Y_KEPCO_DEVICE_PREFIX),
        "Z": ChannelAccess(device_prefix=Z_KEPCO_DEVICE_PREFIX),
    }

    controller_ca = ChannelAccess(device_prefix=ZF_DEVICE_PREFIX)
    magnetometer_ca = ChannelAccess(device_prefix=MAGNETOMETER_DEVICE_PREFIX)

    amps_per_mg = {
        "X": controller_ca.get_pv_value("P:X"),
        "Y": controller_ca.get_pv_value("P:Y"),
        "Z": controller_ca.get_pv_value("P:Z"),
    }

    while True:
        outputs = {
            axis: psu_ca[axis].get_pv_value("CURRENT:SP:RBV")
            for axis in FIELD_AXES
        }

        measured = {
            axis: (outputs[axis] - psu_amps_at_measured_zero[axis]) *
            (1.0 / amps_per_mg[axis])
            for axis in FIELD_AXES
        }

        for axis in FIELD_AXES:
            magnetometer_ca.set_pv_value("SIM:DAQ:{}".format(axis),
                                         measured[axis],
                                         sleep_after_set=0)
예제 #20
0
class FlipprpsTests(unittest.TestCase):
    """
    Tests for the Flipprps IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "flipprps", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)
        self._lewis.backdoor_set_on_device("connected", True)

    @skip_if_recsim("Lewis backdoor commands not available in RecSim")
    def test_SET_polarity(self):
        self.ca.set_pv_value("POLARITY", "Down")
        self._lewis.assert_that_emulator_value_is("polarity", 0)
        self.ca.set_pv_value("POLARITY", "Up")
        self._lewis.assert_that_emulator_value_is("polarity", 1)

    def test_GET_id(self):
        self.ca.assert_that_pv_is("ID", "Flipper")

    @skip_if_recsim("Lewis backdoor commands not available in RecSim")
    def test_GIVEN_device_not_connected_THEN_id_is_in_alarm(self):
        self._lewis.backdoor_set_on_device("connected", False)
        self.ca.assert_that_pv_alarm_is("ID", self.ca.Alarms.INVALID, 20)

    @skip_if_recsim("Lewis backdoor commands not available in RecSim")
    def test_GIVEN_device_not_connected_THEN_polarity_raises_timeout_alarm_after_set(
            self):
        self._lewis.backdoor_set_on_device("connected", False)
        self.ca.set_pv_value("POLARITY", "Up")
        self.ca.assert_that_pv_alarm_is("POLARITY", self.ca.Alarms.INVALID)
예제 #21
0
class PowerStatusTests(unittest.TestCase):
    def setUp(self):
        self.ca = ChannelAccess(20, device_prefix=DEVICE_PREFIX)
        shared_setup(self.ca)

    def test_that_GIVEN_psu_off_WHEN_voltage_setpoint_changed_higher_than_threshold_THEN_psu_status_changes_on(
            self):
        # GIVEN
        # asserted in setUp
        # WHEN
        self.ca.set_pv_value("VOLT:SP", 100)

        # THEN
        self.ca.assert_that_pv_is("POWER:STAT", "ON")

    def test_that_GIVEN_psu_on_WHEN_voltage_setpoint_changed_lower_than_threshold_THEN_psu_status_changes_off(
            self):
        # GIVEN
        self.ca.set_pv_value("VOLT:SP", 10)
        self.ca.assert_that_pv_is("VOLT", 10)
        self.ca.assert_that_pv_is("POWER:STAT", "ON")

        # WHEN
        self.ca.set_pv_value("VOLT:SP", 0)

        # THEN
        self.ca.assert_that_pv_is("POWER:STAT", "OFF")
class Sans2dVacuumTankTest(unittest.TestCase):
    """
    Tests for the SANS2D vacuum tank, based on a FINS PLC.
    """
    def setUp(self):
        self._ioc = IOCRegister.get_running("FINS_01")
        self.ca = ChannelAccess(device_prefix=ioc_prefix)

    @parameterized.expand(parameterized_list([-5, 0, 3, 5, 7, 9, 16]))
    def test_WHEN_set_tank_status_to_unknown_value_THEN_error_status(
            self, _, status_rval):
        self.ca.set_pv_value("SIM:TANK:STATUS", status_rval)
        self.ca.assert_that_pv_is("TANK:STATUS", "ERROR: STATUS UNKNOWN")
        self.ca.assert_that_pv_alarm_is("TANK:STATUS", "MAJOR")

    @parameterized.expand([(1, "ATMOSPHERE"), (2, "VAC DOWN"),
                           (4, "AT VACUUM"), (8, "VENTING")])
    def test_WHEN_set_tank_status_to_known_value_THEN_no_error(
            self, status_rval, status_val):
        self.ca.set_pv_value("SIM:TANK:STATUS", status_rval)
        self.ca.assert_that_pv_is("TANK:STATUS", status_val)
        self.ca.assert_that_pv_alarm_is("TANK:STATUS", "NO_ALARM")
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)
예제 #24
0
class Pt2025Tests(unittest.TestCase):
    """
    Tests for the Pt2025 IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "pt2025", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)
        self._lewis.backdoor_run_function_on_device("reset_values")

    @parameterized.expand(DATA_LOCKED)
    def test_when_locked_THEN_all_other_Pvs_change_appropriately(self, data):
        self._lewis.backdoor_set_on_device("data", data)
        self.ca.assert_that_pv_is("FIELD:RAW", data)
        self.ca.assert_that_pv_is("LOCKED", "LOCKED")
        self.ca.assert_that_pv_is("FIELD_TESLA", float(data[1:-1]))
        self.ca.assert_that_pv_is("FIELD_GAUSS", float(data[1:-1]) * 10000)

    @parameterized.expand(DATA_UNLOCKED)
    def test_when_not_locked_THEN_all_other_Pvs_do_not_change(self, data):
        self._lewis.backdoor_set_on_device("data", data)
        self.ca.assert_that_pv_is("FIELD:RAW", data)
        self.ca.assert_that_pv_is("LOCKED", "UNLOCKED")
        self.ca.assert_that_pv_is("CALC_FIELD", -1.0)
        self.ca.assert_that_pv_is("FIELD_GAUSS", -1.0)

    @parameterized.expand(DATA_LOCKED)
    def test_when_data_read_THEN_Activity_PV_is_turned_ON_OFF(self, data):
        # set to a opposite value for PV and check later if it has changed
        self.ca.set_pv_value("ACTIVITY_ON", 0)
        self.ca.set_pv_value("ACTIVITY_OFF", 1)

        self._lewis.backdoor_set_on_device("data", data)

        self.ca.assert_that_pv_is("ACTIVITY", 1)
        self.ca.assert_that_pv_is("ACTIVITY_ON", 1)
        self.ca.assert_that_pv_is("ACTIVITY_OFF", 0)
        self.ca.assert_that_pv_is("ACTIVITY", 0, timeout=3)
예제 #25
0
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 DanfysikBase(object):
    """
    Tests for danfysik.
    """
    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=15)

        self._lewis.backdoor_run_function_on_device("reinitialise")
        self._lewis.backdoor_set_on_device("comms_initialized", True)

        # Used for daisy chained Danfysiks, default is a single Danfysik so we don't need an id
        self.id_prefixes = [""]

        self.current_readback_factor = 1

        self.set_autoonoff(False)

    def set_autoonoff(self, state):
        """
        Sets the status of the AUTOONOFF pv.

        Args:
            state (bool): True to enable AUTOONOFF, false otherwise
        """
        state_desc = "Enabled" if state else "Disabled"

        if self.ca.get_pv_value("AUTOONOFF") != state_desc:
            old_autoonoff_disp = int(self.ca.get_pv_value("AUTOONOFF.DISP"))
            self.ca.set_pv_value("AUTOONOFF.DISP",
                                 0,
                                 wait=True,
                                 sleep_after_set=0)
            self.ca.set_pv_value("AUTOONOFF", state, sleep_after_set=0)
            self.ca.assert_that_pv_is("AUTOONOFF", state_desc)
            self.ca.set_pv_value("AUTOONOFF.DISP",
                                 old_autoonoff_disp,
                                 sleep_after_set=0)
예제 #27
0
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)
예제 #28
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 Lakeshore340Tests(unittest.TestCase):
    """
    Tests for the lakeshore 340 IOC.
    """
    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=15)

    def test_WHEN_device_is_started_THEN_it_is_not_disabled(self):
        self.ca.assert_that_pv_is("DISABLE", "COMMS ENABLED")

    @parameterized.expand(
        parameterized_list(itertools.product(SENSORS, TEST_TEMPERATURES)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_temperature_set_via_backdoor_THEN_it_can_be_read_back(
            self, _, sensor, value):
        self._lewis.backdoor_set_on_device("temp_{}".format(sensor.lower()),
                                           value)
        self.ca.assert_that_pv_is_number("{}:TEMP".format(sensor.upper()),
                                         value)

    @parameterized.expand(
        parameterized_list(itertools.product(SENSORS, TEST_TEMPERATURES)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_measurement_set_via_backdoor_THEN_it_can_be_read_back(
            self, _, sensor, value):
        self._lewis.backdoor_set_on_device(
            "measurement_{}".format(sensor.lower()), value)
        self.ca.assert_that_pv_is_number("{}:RDG".format(sensor.upper()),
                                         value)

    @parameterized.expand(parameterized_list(TEST_TEMPERATURES))
    def test_WHEN_tset_is_changed_THEN_readback_updates(self, _, val):
        self.ca.assert_setting_setpoint_sets_readback(
            val, readback_pv="A:TEMP:SP:RBV", set_point_pv="A:TEMP:SP")

    @parameterized.expand(
        parameterized_list(itertools.product(PID_SETTINGS, PID_TEST_VALUES)))
    def test_WHEN_pid_settings_changed_THEN_can_be_read_back(
            self, _, setting, value):
        if setting == "D":
            value = int(
                value)  # Derivative is only allowed to take integer values.

        self.ca.assert_setting_setpoint_sets_readback(value, setting)

    @parameterized.expand(parameterized_list(PID_MODES))
    def test_WHEN_pid_settings_changed_THEN_can_be_read_back(self, _, mode):
        self.ca.assert_setting_setpoint_sets_readback(mode, "PIDMODE")

    @parameterized.expand(parameterized_list(LOOP_STATES))
    def test_WHEN_loop_turned_on_or_off_THEN_can_be_read_back(
            self, _, loopstate):
        self.ca.assert_setting_setpoint_sets_readback(loopstate, "LOOP")

    @parameterized.expand(parameterized_list(TEST_TEMPERATURES))
    def test_WHEN_max_temperature_set_THEN_can_be_read_back(self, _, temp):
        self.ca.assert_setting_setpoint_sets_readback(temp, "TEMP:MAX")

    @parameterized.expand(parameterized_list(HEATER_PERCENTAGES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_heater_power_set_via_backdoor_THEN_can_be_read_back(
            self, _, output):
        self._lewis.backdoor_set_on_device("heater_output", output)
        self.ca.assert_that_pv_is_number("OUTPUT", output)

    @parameterized.expand(parameterized_list(RANGES))
    def test_WHEN_heater_range_set_THEN_can_be_read_back(self, _, range):
        self.ca.assert_setting_setpoint_sets_readback(range, "RANGE")

    @parameterized.expand(parameterized_list(EXCITATIONS))
    def test_WHEN_excitation_a_set_THEN_can_be_read_back(self, _, excitation):
        self.ca.assert_setting_setpoint_sets_readback(excitation,
                                                      "EXCITATIONA")

    @parameterized.expand(parameterized_list(EXCITATIONS))
    @skip_if_recsim
    def test_WHEN_excitation_set_by_backdoor_THEN_can_be_read_back(
            self, _, excitation):
        self._lewis.backdoor_set_on_device("excitationa",
                                           EXCITATIONS.index(excitation))
        self.ca.assert_that_pv_is("EXCITATIONA", excitation)

    def test_WHEN_use_valid_file_THEN_threshold_file_is_none(self):
        self.ca.assert_that_pv_is_path(THRESHOLD_FILE_PV,
                                       THRESHOLD_FILES_DIR + THRESHOLDS_FILE)
        self.ca.assert_that_pv_is(THRESHOLDS_ERROR_PV, "No Error")
        self.ca.assert_that_pv_is_path("THRESHOLDS:USE", "YES")

    def test_WHEN_do_not_use_file_THEN_threshold_file_is_not_set(self):
        with self._ioc.start_with_macros(
            {"USE_EXCITATION_THRESHOLD_FILE": "NO"},
                pv_to_wait_for=THRESHOLD_FILE_PV):
            self.ca.assert_that_pv_is_path("THRESHOLDS:USE", "NO")
            self.ca.assert_that_pv_is(THRESHOLDS_ERROR_PV, "No Error")

    def test_WHEN_initialise_with_incorrect_macro_THEN_pv_is_in_alarm(self):
        filename = "DoesNotExist.txt"
        with self._ioc.start_with_macros(
            {
                "EXCITATION_THRESHOLD_FILE": filename,
                "USE_EXCITATION_THRESHOLD_FILE": "YES"
            },
                pv_to_wait_for=THRESHOLD_FILE_PV):
            self.ca.assert_that_pv_is(THRESHOLDS_ERROR_PV, "File Not Found")
            self.ca.assert_that_pv_is_path("THRESHOLDS:USE", "YES")

    def test_WHEN_initialise_with_invalid_file_THEN_pv_is_in_alarm(self):
        filename = "InvalidLines.txt"
        with self._ioc.start_with_macros(
            {
                "EXCITATION_THRESHOLD_FILE": filename,
                "USE_EXCITATION_THRESHOLD_FILE": "YES"
            },
                pv_to_wait_for=THRESHOLD_FILE_PV):
            self.ca.assert_that_pv_is(THRESHOLDS_ERROR_PV,
                                      "Invalid Lines In File")
            self.ca.assert_that_pv_is_path("THRESHOLDS:USE", "YES")

    def reset_thresholds_values(self, thresholds_excitations, thresholds_temp,
                                excitationa, error, delay_change, temp):
        self.ca.assert_setting_setpoint_sets_readback(excitationa,
                                                      EXCITATIONA_PV)
        self.ca.set_pv_value(THRESHOLD_TEMP_PV, thresholds_temp)
        self.ca.set_pv_value(THRESHOLD_EXCITATIONS_PV, thresholds_excitations)
        self.ca.set_pv_value(THRESHOLDS_DELAY_CHANGE_PV, delay_change)
        self.ca.set_pv_value(THRESHOLDS_ERROR_PV, error)
        self._lewis.backdoor_set_on_device("temp_a", temp)

    def assert_threshold_values(self, thresholds_excitations, thresholds_temp,
                                excitationa, error, error_severity,
                                delay_change):
        self.ca.assert_that_pv_is(THRESHOLD_EXCITATIONS_PV,
                                  thresholds_excitations)
        self.ca.assert_that_pv_is(THRESHOLD_TEMP_PV, thresholds_temp)
        self.ca.assert_that_pv_is(THRESHOLDS_DELAY_CHANGE_PV, delay_change)
        self.ca.assert_that_pv_is(THRESHOLDS_ERROR_PV, error)
        self.ca.assert_that_pv_alarm_is(THRESHOLDS_ERROR_PV, error_severity)
        self.ca.assert_that_pv_is(EXCITATIONA_PV, excitationa)

    @parameterized.expand(parameterized_list(TEMP_SP_EXCITATIONS))
    @skip_if_recsim
    def test_WHEN_set_temp_sp_THEN_thresholds_recalculated(
            self, _, temp_sp_excitations_map):
        new_temp_sp = temp_sp_excitations_map["TEMP:SP"]
        expected_thresholds_temp = temp_sp_excitations_map["THRESHOLDS:TEMP"]
        expected_thresholds_excitation = temp_sp_excitations_map[
            "THRESHOLDS:EXCITATION"]
        # Reset pv values to test
        self.reset_thresholds_values("Off", 0, "Off", "No Error", "NO",
                                     new_temp_sp - 10)
        # Set setpoint
        self.ca.assert_setting_setpoint_sets_readback(
            new_temp_sp, readback_pv="A:TEMP:SP:RBV", set_point_pv="A:TEMP:SP")
        # Confirm change is delayed but threshold temp is set
        self.assert_threshold_values(expected_thresholds_excitation,
                                     expected_thresholds_temp, "Off",
                                     "No Error", "NO_ALARM", "YES")
        # Make temperature equal setpoint
        self._lewis.backdoor_set_on_device("temp_a", new_temp_sp)
        # Confirm Excitations is set correctly
        self.assert_threshold_values(expected_thresholds_excitation,
                                     expected_thresholds_temp,
                                     expected_thresholds_excitation,
                                     "No Error", "NO_ALARM", "NO")

    @parameterized.expand(
        parameterized_list([("None.txt", "NO_ALARM", "No Error"),
                            ("DoesNotExist.txt", "MINOR", "File Not Found"),
                            ("InvalidLines.txt", "MINOR",
                             "Invalid Lines In File")]))
    @skip_if_recsim
    def test_GIVEN_not_using_excitations_OR_invalid_file_WHEN_set_temp_sp_THEN_thresholds_not_recalculated(
            self, _, filename, expected_error_severity, expected_error):
        with self._ioc.start_with_macros(
            {"EXCITATION_THRESHOLD_FILE": filename},
                pv_to_wait_for=THRESHOLD_FILE_PV):
            for temp_sp, temp, excitation in [(5.2, 3.1, "30 nA"),
                                              (16.4, 18.2, "100 nA"),
                                              (20.9, 0, "Off"),
                                              (400.2, 20.3, "1 mV")]:
                # Reset pv values to test
                self.reset_thresholds_values(excitation, temp, excitation,
                                             "No Error", "NO", temp_sp - 10)
                # Set temp
                self.ca.assert_setting_setpoint_sets_readback(
                    temp_sp,
                    readback_pv="A:TEMP:SP:RBV",
                    set_point_pv="A:TEMP:SP")
                self._lewis.backdoor_set_on_device("temp_a", temp_sp)
                # Assert nothing has changed
                self.assert_threshold_values(excitation, temp, excitation,
                                             expected_error,
                                             expected_error_severity, "NO")
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)