Exemplo n.º 1
0
class NgpspsuRecsimOnlyVoltageAndCurrentTests(unittest.TestCase):
    def _set_voltage_setpoint(self, value):
        self._ioc.set_simulated_value("SIM:VOLT:SP", value)

    def _set_current_setpoint(self, value):
        self._ioc.set_simulated_value("SIM:CURR:SP", value)

    @parameterized.expand(
        parameterized_list([12.006768, 23, -5, -2.78, 3e-5, 0.00445676e4, 0]))
    @skip_if_devsim("These tests will fail in devsim as the device is not on.")
    def test_that_WHEN_setting_the_current_setpoint_THEN_it_is_set(
            self, _, value):
        # When
        self._set_current_setpoint(value)

        # Then:
        self.ca.assert_that_pv_is("CURR:SP:RBV", value)

    @parameterized.expand(
        parameterized_list([12.006768, 23, -5, -2.78, 3e-5, 0.00445676e4, 0]))
    @skip_if_devsim("These tests will fail in devsim as the device is not on.")
    def test_that_WHEN_setting_the_voltage_setpoint_THEN_it_is_set(
            self, _, value):
        # When
        self._set_voltage_setpoint(value)

        # Then:
        self.ca.assert_that_pv_is("VOLT:SP:RBV", value)
class OerconeTests(unittest.TestCase):
    """
    Tests for the Oercone IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(IOCS[0]["emulator"], DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)

    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(TEST_PRESSURE_VALUES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_pressure_is_set_via_backdoor_THEN_readback_updates(self, _, pressure):
        self._lewis.backdoor_set_on_device("pressure", pressure)
        self.ca.assert_that_pv_is_number("PRESSURE", pressure)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_measurement_units_is_set_via_backdoor_THEN_readback_updates(self):
        for units in Units:
            self._lewis.backdoor_run_function_on_device("backdoor_set_units", [units.value])
            expected_units = units.name
            self.ca.assert_that_pv_is("PRESSURE.EGU", expected_units)

    @parameterized.expand(parameterized_list(Units))
    def test_WHEN_units_setpoint_set_THEN_read_back_is_correct(self, _, units):
        self.ca.assert_setting_setpoint_sets_readback(units.name, "UNITS")
Exemplo n.º 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)
Exemplo n.º 4
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)
Exemplo n.º 5
0
class NgpspsuFaultTests(unittest.TestCase):
    @skip_if_recsim("Can't see faults from the status")
    def test_that_GIVEN_a_reset_device_THEN_the_fault_pv_is_not_in_alarm(self):
        # Given:
        _reset_device(self.ca)

        # Then:
        self.ca.assert_that_pv_is("STAT:FAULT", "No fault")
        self.ca.assert_that_pv_alarm_is("STAT:FAULT", self.ca.Alarms.NONE)

    @parameterized.expand(
        parameterized_list([
            "Fault condition", "Mains fault", "Earth leakage", "Earth fuse",
            "Regulation fault", "DCCT fault"
        ]))
    @skip_if_recsim("Can't see faults from the status")
    def test_that_GIVEN_a_device_experiencing_a_fault_THEN_the_fault_pv_is_in_alarm(
            self, _, fault_name):
        # Given:
        self._lewis.backdoor_run_function_on_device("fault", [fault_name])

        # Then:
        self.ca.assert_that_pv_is("STAT:FAULT", "Fault")
        self.ca.assert_that_pv_alarm_is("STAT:FAULT", self.ca.Alarms.MAJOR)

    @skip_if_recsim("Can't see faults from the status")
    def test_that_GIVEN_a_device_experiencing_two_faults_THEN_the_fault_pv_is_in_alarm(
            self):
        # Given:
        for fault_name in ["Fault condition", "Mains fault"]:
            self._lewis.backdoor_run_function_on_device("fault", [fault_name])

        # Then:
        self.ca.assert_that_pv_is("STAT:FAULT", "Fault")
        self.ca.assert_that_pv_alarm_is("STAT:FAULT", self.ca.Alarms.MAJOR)
Exemplo n.º 6
0
class JawsManagerTests(JawsManagerBase, unittest.TestCase):
    """
    Tests for the Jaws Manager.
    """
    def get_sample_pv(self):
        return "SAMPLE"

    def get_num_of_jaws(self):
        return 2

    @parameterized.expand(parameterized_list([
        (10, 10, [10, 10]),
        (20, 20, [20, 20]),
        (10, 0, [8, 5]),
        (20, 0, [16, 10]),
        (10, 5, [9, 7.5]),
        (20, 5, [17, 12.5]),
    ]))
    @unstable_test()
    def test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(self, _, mod_gap, sample_gap, expected):
        self.ca.set_pv_value(MOD_GAP.format("V"), mod_gap)
        self._test_WHEN_sample_gap_set_THEN_other_jaws_as_expected("V", sample_gap, expected)

    @parameterized.expand(["V", "H"])
    def test_WHEN_centre_is_changed_THEN_centres_of_all_jaws_follow_and_gaps_unchanged(self, direction):
        self._test_WHEN_centre_is_changed_THEN_centres_of_all_jaws_follow_and_gaps_unchanged(direction)

    @parameterized.expand(["V", "H"])
    def test_WHEN_sizes_at_moderator_and_sample_changed_THEN_centres_of_all_jaws_unchanged(self, direction):
        self._test_WHEN_sizes_at_moderator_and_sample_changed_THEN_centres_of_all_jaws_unchanged(direction)
Exemplo n.º 7
0
class NgpspsuCurrentTests(unittest.TestCase):
    def test_that_GIVEN_a_device_after_set_up_THEN_the_current_is_zero(self):
        # Then:
        self.ca.assert_that_pv_is("CURR", 0.0)

    @parameterized.expand(
        parameterized_list([12.006768, 23, -5, -2.78, 3e-5, 0.00445676e4, 0]))
    @skip_if_recsim("Can't test if the device is turned on")
    def test_that_GIVEN_device_which_is_on_WHEN_setting_the_current_setpoint_THEN_it_is_set(
            self, _, value):
        # Given:
        _start_device(self.ca)
        self.ca.assert_that_pv_is("STAT:POWER", "ON")

        # When\Then:
        self.ca.assert_setting_setpoint_sets_readback(value, "CURR:SP:RBV",
                                                      "CURR:SP")

    @skip_if_recsim("Can't test if the device is turned on")
    def test_that_GIVEN_a_device_with_a_current_WHEN_powered_off_THEN_the_current_is_zero(
            self):
        # Given:
        _start_device(self.ca)
        self.ca.assert_that_pv_is("STAT:POWER", "ON")
        self.ca.set_pv_value("CURR:SP", 4.5)
        self.ca.assert_that_pv_is("CURR", 4.5)

        # When:
        _stop_device(self.ca)

        # Then:
        self.ca.assert_that_pv_is("CURR", 0)
class MecfrfTests(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=30)

        self._lewis.backdoor_set_on_device("connected", True)
        self._lewis.backdoor_set_on_device("corrupted_messages", False)

    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_LENGTHS)))
    @skip_if_recsim("Uses Lewis backdoor")
    def test_WHEN_value_is_written_to_emulator_THEN_record_updates(
            self, _, sensor, length):
        self._lewis.backdoor_set_on_device("sensor{}".format(sensor),
                                           length * RAW_READING_SCALING)
        self.ca.assert_that_pv_is("SENSOR{}".format(sensor), length)
        self.ca.assert_that_pv_alarm_is("SENSOR{}".format(sensor),
                                        self.ca.Alarms.NONE)

    @skip_if_recsim("Uses Lewis backdoor")
    def test_WHEN_emulator_sends_corrupt_packets_THEN_records_go_into_alarm(
            self):
        with self.ca.assert_pv_processed("_RESET_CONNECTION"):
            self._lewis.backdoor_set_on_device("corrupted_messages", True)
            self.ca.assert_that_pv_is("_GETTING_INVALID_MESSAGES", 1)

        self._lewis.backdoor_set_on_device("corrupted_messages", False)
        self.ca.assert_that_pv_is("_GETTING_INVALID_MESSAGES", 0)

    @parameterized.expand(parameterized_list(SENSORS))
    @skip_if_recsim("Uses Lewis backdoor")
    def test_WHEN_emulator_disconnected_THEN_records_go_into_alarm(
            self, _, sensor):
        self.ca.assert_that_pv_is("_READINGS_OUTDATED", "No")
        self.ca.assert_that_pv_alarm_is("SENSOR{}".format(sensor),
                                        self.ca.Alarms.NONE)

        self._lewis.backdoor_set_on_device("connected", False)

        self.ca.assert_that_pv_is("_READINGS_OUTDATED", "Yes")
        self.ca.assert_that_pv_alarm_is("SENSOR{}".format(sensor),
                                        self.ca.Alarms.INVALID)
Exemplo n.º 9
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)
Exemplo n.º 10
0
class NimrodJawsManagerTests(JawsManagerBase, unittest.TestCase):
    """
    Tests for the Jaws Manager on Nimrod.
    """
    def get_num_of_jaws(self):
        return 6

    @parameterized.expand(
        parameterized_list([
            # Numbers taken from the VI
            (100, 10, [70.5, 63.1, 44.8, 29.4, 21.2, 10]),
            (70, 40, [60.2, 57.7, 51.6, 46.5, 43.7, 40]),
            (130, 5, [89, 78.8, 53.4, 31.9, 20.6, 5]),
        ]))
    @unstable_test(max_retries=10)
    def test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(
            self, _, mod_gap, sample_gap, expected):
        self.ca.set_pv_value(MOD_GAP.format("V"), mod_gap)
        self._test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(
            "V", sample_gap, expected)
Exemplo n.º 11
0
class GemJawsManagerTests(JawsManagerBase, unittest.TestCase):
    """
    Tests for the Jaws Manager on Gem.
    """
    def get_num_of_jaws(self):
        return 5

    @parameterized.expand(
        parameterized_list([
            # Numbers taken experimentally
            (30, 10, [22.6, 20.4, 17.9, 15.1, 11.9]),
            (130, 5, [83.6, 70.2, 54, 37, 16.9]),
            (100, 50, [81.4, 76.1, 69.6, 62.8, 54.7]),
        ]))
    @unittest.skip(
        "Fix as part of https://github.com/ISISComputingGroup/IBEX/issues/4841"
    )
    def test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(
            self, _, mod_gap, sample_gap, expected):
        self.ca.set_pv_value(MOD_GAP.format("V"), mod_gap)
        self._test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(
            "V", sample_gap, expected)
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 PolarisJawsManagerTests(JawsManagerBase, unittest.TestCase):
    """
    Tests for the Jaws Manager on Polaris.
    """
    def setUp(self):
        super(PolarisJawsManagerTests, self).setUp()
        with ManagerMode(self.ca):
            # Use a retry loop here in case the IOC has not connected to the manager mode PV yet
            for _ in range(10):
                try:
                    [
                        self.ca.set_pv_value(
                            TOP_LEVEL_JAW_5_GAP.format(direction), 0)
                        for direction in ["V", "H"]
                    ]
                    self.ca.set_pv_value(SET_JAW_5, 1)
                except WriteAccessException:
                    sleep(5)
                else:
                    break
            else:
                raise WriteAccessException(
                    "Unable to write to jaws 5 in setup after 10 attempts")

    def get_sample_pv(self):
        return "POLJAWSET"

    def get_num_of_jaws(self):
        return 4

    @parameterized.expand(
        parameterized_list([
            # Values gained experimentally
            ("V", 10, [52, 35.5, 26.3, 22.7], 14.6),
            ("H", 10, [53.7, 36.6, 27, 23.2], 14.8),
            ("V", 20, [55.9, 41.9, 34, 30.9], 24),
            ("H", 20, [57.6, 42.9, 34.6, 31.4], 24.1),
        ]))
    def test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(
            self, _, direction, sample_gap, expected, expected_5):
        self._test_WHEN_sample_gap_set_THEN_other_jaws_as_expected(
            direction, sample_gap, expected)
        self.ca.assert_that_pv_is_number(TOP_LEVEL_JAW_5_GAP.format(direction),
                                         expected_5, 0.1)

    @parameterized.expand(["V", "H"])
    def test_WHEN_centre_is_changed_THEN_centres_of_all_jaws_follow_and_gaps_unchanged(
            self, direction):
        expected_5_gap = self.ca.get_pv_value(
            TOP_LEVEL_JAW_5_GAP.format(direction))
        self._test_WHEN_centre_is_changed_THEN_centres_of_all_jaws_follow_and_gaps_unchanged(
            direction)
        self.ca.assert_that_pv_is_number(
            TOP_LEVEL_JAW_5_CENT.format(direction), 10, 0.1)
        self.ca.assert_that_pv_is_number(TOP_LEVEL_JAW_5_GAP.format(direction),
                                         expected_5_gap, 0.1)

    @parameterized.expand(["V", "H"])
    def test_GIVEN_not_in_manager_mode_WHEN_jawset_5_written_to_THEN_exception_raised(
            self, direction):
        self.assertRaises(WriteAccessException, self.ca.set_pv_value,
                          UNDERLYING_GAP_SP.format(5, direction), 10)

    @parameterized.expand(["V", "H"])
    def test_WHEN_jaw_5_readback_changed_THEN_underlying_jaw_5_not_changed(
            self, direction):
        underlying_jaw = UNDERLYING_GAP_SP.format(5, direction)
        self.ca.assert_that_pv_is_number(underlying_jaw, 0)
        self.ca.assert_that_pv_is_number(TOP_LEVEL_JAW_5_GAP.format(direction),
                                         0)

        self.ca.set_pv_value(SAMPLE_SP.format(direction), 10)
        self.ca.assert_that_pv_is_number(underlying_jaw,
                                         0)  # Underlying jaw not changed
        self.ca.assert_that_pv_is_not_number(
            TOP_LEVEL_JAW_5_GAP.format(direction), 0,
            5)  # Readback has changed

    @parameterized.expand(["V", "H"])
    @unstable_test(error_class=(AssertionError, WriteAccessException),
                   wait_between_runs=10)
    def test_WHEN_jaw_5_set_directly_THEN_underlying_jaw_5_not_changed(
            self, direction):
        underlying_jaw = UNDERLYING_GAP_SP.format(5, direction)
        with ManagerMode(self.ca):
            self.ca.set_pv_value(TOP_LEVEL_JAW_5_GAP.format(direction), 10)
            self.ca.assert_that_pv_is_number(underlying_jaw, 0)

    @parameterized.expand(["V", "H"])
    @unstable_test(error_class=(AssertionError, WriteAccessException),
                   wait_between_runs=10)
    def test_GIVEN_jaw_5_set_directly_WHEN_set_pv_called_THEN_underlying_jaw_5_changes(
            self, direction):
        underlying_jaw = UNDERLYING_GAP_SP.format(5, direction)
        with ManagerMode(self.ca):
            self.ca.set_pv_value(TOP_LEVEL_JAW_5_GAP.format(direction), 10)
            self.ca.set_pv_value(SET_JAW_5, 1)
            self.ca.assert_that_pv_is_number(underlying_jaw, 10)

    @parameterized.expand(["V", "H"])
    @unstable_test(error_class=(AssertionError, WriteAccessException),
                   wait_between_runs=10)
    def test_GIVEN_jaw_5_readback_changed_WHEN_set_pv_called_THEN_underlying_jaw_5_changes(
            self, direction):
        underlying_jaw = UNDERLYING_GAP_SP.format(5, direction)
        self.ca.set_pv_value(SAMPLE_SP.format(direction), 10)
        with ManagerMode(self.ca):
            self.ca.set_pv_value(SET_JAW_5, 1)
            self.ca.assert_that_pv_is_number(
                underlying_jaw,
                self.ca.get_pv_value(TOP_LEVEL_JAW_5_GAP.format(direction)))
class MercuryTests(unittest.TestCase):
    """
    Tests for the Mercury IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "mercuryitc", DEVICE_PREFIX)
        self._lewis.backdoor_set_on_device("connected", True)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX,
                                default_timeout=20)
        card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0])
        self.ca.assert_setting_setpoint_sets_readback(
            "OFF",
            readback_pv="{}:SPC".format(card_pv_prefix),
            expected_alarm=self.ca.Alarms.MAJOR)

    @parameterized.expand(
        parameterized_list(
            itertools.product(PID_PARAMS, PID_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_pid_params_set_via_backdoor_THEN_readback_updates(
            self, _, param, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [card, param.lower(), test_value])
        self.ca.assert_that_pv_is("{}:{}".format(card_pv_prefix, param),
                                  test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(PID_PARAMS, PID_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_pid_params_set_THEN_readback_updates(self, _, param,
                                                       test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            test_value,
            readback_pv="{}:{}".format(card_pv_prefix, param),
            set_point_pv="{}:{}:SP".format(card_pv_prefix, param))

    @parameterized.expand(
        parameterized_list(
            itertools.product(AUTOPID_MODES, TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_autopid_set_THEN_readback_updates(self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            test_value,
            readback_pv="{}:PID:AUTO".format(card_pv_prefix),
            set_point_pv="{}:PID:AUTO:SP".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(TEMPERATURE_TEST_VALUES, TEMP_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_actual_temp_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property", [card, "temperature", test_value])
        self.ca.assert_that_pv_is("{}:TEMP".format(card_pv_prefix), test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(TEMPERATURE_TEST_VALUES, PRESSURE_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_actual_pressure_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property", [card, "pressure", test_value])
        self.ca.assert_that_pv_is("{}:PRESSURE".format(card_pv_prefix),
                                  test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(RESISTANCE_TEST_VALUES, TEMP_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_resistance_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property", [card, "resistance", test_value])
        self.ca.assert_that_pv_is("{}:RESISTANCE".format(card_pv_prefix),
                                  test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(RESISTANCE_TEST_VALUES, PRESSURE_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_voltage_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property", [card, "voltage", test_value])
        self.ca.assert_that_pv_is("{}:VOLT".format(card_pv_prefix), test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(TEMPERATURE_TEST_VALUES, TEMP_CARDS)))
    def test_WHEN_sp_temp_is_set_THEN_pv_updates(self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            test_value,
            set_point_pv="{}:TEMP:SP".format(card_pv_prefix),
            readback_pv="{}:TEMP:SP:RBV".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(TEMPERATURE_TEST_VALUES, PRESSURE_CARDS)))
    def test_WHEN_sp_pressure_is_set_THEN_pv_updates(self, _, test_value,
                                                     card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            test_value,
            set_point_pv="{}:PRESSURE:SP".format(card_pv_prefix),
            readback_pv="{}:PRESSURE:SP:RBV".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(HEATER_MODES, TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_heater_mode_is_set_THEN_pv_updates(self, _, mode, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            mode,
            set_point_pv="{}:HEATER:MODE:SP".format(card_pv_prefix),
            readback_pv="{}:HEATER:MODE".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(GAS_FLOW_MODES, TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_gas_flow_mode_is_set_THEN_pv_updates(self, _, mode, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            mode,
            set_point_pv="{}:FLOW:STAT:SP".format(card_pv_prefix),
            readback_pv="{}:FLOW:STAT".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(GAS_FLOW_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_gas_flow_is_set_THEN_pv_updates(self, _, mode, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            mode,
            set_point_pv="{}:FLOW:SP".format(card_pv_prefix),
            readback_pv="{}:FLOW".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(HEATER_PERCENT_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_heater_percent_is_set_THEN_pv_updates(self, _, mode, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            mode,
            set_point_pv="{}:HEATER:SP".format(card_pv_prefix),
            readback_pv="{}:HEATER".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(HEATER_PERCENT_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_heater_voltage_limit_is_set_THEN_pv_updates(
            self, _, mode, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            mode,
            set_point_pv="{}:HEATER:VOLT_LIMIT:SP".format(card_pv_prefix),
            readback_pv="{}:HEATER:VOLT_LIMIT".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(HEATER_PERCENT_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_heater_power_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        heater_chan_name = self.ca.get_pv_value(
            "{}:HTRCHAN".format(card_pv_prefix))

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [heater_chan_name, "power", test_value])
        self.ca.assert_that_pv_is("{}:HEATER:POWER".format(card_pv_prefix),
                                  test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(HEATER_PERCENT_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_heater_curr_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        heater_chan_name = self.ca.get_pv_value(
            "{}:HTRCHAN".format(card_pv_prefix))

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [heater_chan_name, "current", test_value])
        self.ca.assert_that_pv_is("{}:HEATER:CURR".format(card_pv_prefix),
                                  test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(HEATER_PERCENT_TEST_VALUES,
                              TEMP_CARDS + PRESSURE_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_heater_voltage_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        heater_chan_name = self.ca.get_pv_value(
            "{}:HTRCHAN".format(card_pv_prefix))

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [heater_chan_name, "voltage", test_value])
        self.ca.assert_that_pv_is("{}:HEATER:VOLT".format(card_pv_prefix),
                                  test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(MOCK_NICKNAMES,
                              TEMP_CARDS + PRESSURE_CARDS + LEVEL_CARDS)))
    def test_WHEN_name_is_set_THEN_pv_updates(self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self.ca.assert_setting_setpoint_sets_readback(
            test_value,
            readback_pv="{}:NAME".format(card_pv_prefix),
            set_point_pv="{}:NAME:SP".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(GAS_LEVEL_TEST_VALUES, LEVEL_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_helium_level_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [card, "helium_level", test_value])
        self.ca.assert_that_pv_is_number("{}:HELIUM".format(card_pv_prefix),
                                         test_value,
                                         tolerance=0.01)

    @parameterized.expand(
        parameterized_list(
            itertools.product(GAS_LEVEL_TEST_VALUES, LEVEL_CARDS)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_nitrogen_level_is_set_via_backdoor_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)

        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [card, "nitrogen_level", test_value])
        self.ca.assert_that_pv_is_number("{}:NITROGEN".format(card_pv_prefix),
                                         test_value,
                                         tolerance=0.01)

    @parameterized.expand(
        parameterized_list(
            itertools.product(TEMP_CARDS + PRESSURE_CARDS, HEATER_CARDS)))
    def test_WHEN_heater_association_is_set_THEN_pv_updates(
            self, _, parent_card, associated_card):
        card_pv_prefix = get_card_pv_prefix(parent_card)

        with ManagerMode(ChannelAccess()):
            self.ca.assert_setting_setpoint_sets_readback(
                associated_card, "{}:HTRCHAN".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(
            itertools.product(TEMP_CARDS + PRESSURE_CARDS, AUX_CARDS)))
    def test_WHEN_aux_association_is_set_THEN_pv_updates(
            self, _, parent_card, associated_card):
        card_pv_prefix = get_card_pv_prefix(parent_card)
        with ManagerMode(ChannelAccess()):
            self.ca.assert_setting_setpoint_sets_readback(
                associated_card, "{}:AUXCHAN".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list(itertools.product(HELIUM_READ_RATES, LEVEL_CARDS)))
    def test_WHEN_he_read_rate_is_set_THEN_pv_updates(self, _, test_value,
                                                      card):
        card_pv_prefix = get_card_pv_prefix(card)
        self.ca.assert_setting_setpoint_sets_readback(
            test_value, "{}:HELIUM:READ_RATE".format(card_pv_prefix))

    @parameterized.expand(
        parameterized_list([
            ("CATALOG:PARSE.VALA", TEMP_CARDS),
            ("CATALOG:PARSE.VALB", PRESSURE_CARDS),
            ("CATALOG:PARSE.VALC", LEVEL_CARDS),
            ("CATALOG:PARSE.VALD", HEATER_CARDS),
            ("CATALOG:PARSE.VALE", AUX_CARDS),
        ]))
    @skip_if_recsim("Complex logic not tested in recsim")
    def test_WHEN_getting_catalog_it_contains_all_cards(self, _, pv, cards):
        for card in cards:
            self.ca.assert_that_pv_value_causes_func_to_return_true(
                pv, lambda val: card in val)

    @parameterized.expand(
        parameterized_list(
            itertools.product(MOCK_CALIB_FILES, TEMP_CARDS + PRESSURE_CARDS)))
    def test_WHEN_setting_calibration_file_THEN_pv_updates(
            self, _, test_value, card):
        card_pv_prefix = get_card_pv_prefix(card)
        with ManagerMode(ChannelAccess()):
            self.ca.assert_setting_setpoint_sets_readback(
                test_value, "{}:CALFILE".format(card_pv_prefix))

    @parameterized.expand(parameterized_list(["O", "R"]))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_resistance_suffix_is_changed_THEN_resistance_reads_correctly(
            self, _, resistance_suffix):
        self._lewis.backdoor_set_on_device("resistance_suffix",
                                           resistance_suffix)
        resistance_value = 3
        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [PRIMARY_TEMPERATURE_CHANNEL, "resistance", resistance_value])
        self.ca.assert_that_pv_is(
            "{}:RESISTANCE".format(
                get_card_pv_prefix(PRIMARY_TEMPERATURE_CHANNEL)),
            resistance_value)
        self.ca.assert_that_pv_alarm_is(
            "{}:RESISTANCE".format(
                get_card_pv_prefix(PRIMARY_TEMPERATURE_CHANNEL)),
            self.ca.Alarms.NONE)

    def test_WHEN_auto_flow_set_THEN_pv_updates_and_states_are_set(self):
        card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0])
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])

        self.ca.set_pv_value("{}:PID:AUTO:SP".format(card_pv_prefix), "OFF")
        self.ca.set_pv_value("{}:FLOW:STAT:SP".format(card_pv_prefix), "Auto")
        self.ca.set_pv_value("{}:HEATER:MODE:SP".format(card_pv_prefix),
                             "Manual")
        self.ca.set_pv_value("{}:FLOW:STAT:SP".format(pressure_card_pv_prefix),
                             "Manual")

        self.ca.assert_setting_setpoint_sets_readback(
            "ON",
            set_point_pv="{}:SPC:SP".format(card_pv_prefix),
            readback_pv="{}:SPC".format(card_pv_prefix))
        self.ca.assert_that_pv_is("{}:PID:AUTO".format(card_pv_prefix), "ON")
        self.ca.assert_that_pv_is("{}:FLOW:STAT".format(card_pv_prefix),
                                  "Manual")
        self.ca.assert_that_pv_is("{}:HEATER:MODE".format(card_pv_prefix),
                                  "Auto")
        self.ca.assert_that_pv_is(
            "{}:FLOW:STAT".format(pressure_card_pv_prefix), "Auto")
        self.ca.assert_that_pv_is("{}:SPC:SP".format(pressure_card_pv_prefix),
                                  "ON")

    def test_WHEN_auto_flow_set_off_THEN_pv_updates_and_states_are_not_set(
            self):
        card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0])
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])

        self.ca.set_pv_value("{}:PID:AUTO:SP".format(card_pv_prefix), "OFF")
        self.ca.set_pv_value("{}:FLOW:STAT:SP".format(card_pv_prefix), "Auto")
        self.ca.set_pv_value("{}:HEATER:MODE:SP".format(card_pv_prefix),
                             "Manual")
        self.ca.set_pv_value("{}:FLOW:STAT:SP".format(pressure_card_pv_prefix),
                             "Manual")

        self.ca.assert_setting_setpoint_sets_readback(
            "OFF",
            set_point_pv="{}:SPC:SP".format(card_pv_prefix),
            readback_pv="{}:SPC".format(card_pv_prefix),
            expected_alarm=self.ca.Alarms.MAJOR)
        self.ca.assert_that_pv_is("{}:PID:AUTO".format(card_pv_prefix), "OFF")
        self.ca.assert_that_pv_is("{}:FLOW:STAT".format(card_pv_prefix),
                                  "Auto")
        self.ca.assert_that_pv_is("{}:HEATER:MODE".format(card_pv_prefix),
                                  "Manual")
        self.ca.assert_that_pv_is(
            "{}:FLOW:STAT".format(pressure_card_pv_prefix), "Manual")
        self.ca.assert_that_pv_is("{}:SPC:SP".format(pressure_card_pv_prefix),
                                  "OFF")

    def set_temp_reading_and_sp(self, reading, set_point, spc_state="On"):
        """
        Set the temperature in lewis and the set point on the first card
        :param reading: The reading lewis will return
        :param set_point: The set point to set on the device
        :param spc_state: State to set SPC to (defaults to On)
        """
        card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0])
        self.ca.set_pv_value("{}:SPC:SP".format(card_pv_prefix), spc_state)
        self.ca.set_pv_value("{}:TEMP:SP".format(card_pv_prefix), set_point)
        self._lewis.backdoor_run_function_on_device(
            "backdoor_set_channel_property",
            [TEMP_CARDS[0], "temperature", reading])

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_auto_flow_set_on_and_temp_lower_than_2_deadbands_THEN_pressure_set_to_minimum_pressure(
            self):
        set_point = 10
        reading = 10 - SPC_TEMP_DEADBAND * 2.1
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])
        self.set_temp_reading_and_sp(reading, set_point)

        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP:RBV".format(pressure_card_pv_prefix),
            SPC_MIN_PRESSURE)

    @parameterized.expand([(10, ), (1, ), (300, ), (12, ), (20, )])
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_auto_flow_set_on_and_temp_low_but_within_1_to_2_deadbands_THEN_pressure_set_to_pressure_for_setpoint_temp_and_does_not_ramp(
            self, set_point):
        reading = set_point - SPC_TEMP_DEADBAND * 1.5
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])
        self.set_temp_reading_and_sp(reading, set_point)

        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP:RBV".format(pressure_card_pv_prefix),
            pressure_for(set_point))
        sleep(1.5)
        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP:RBV".format(pressure_card_pv_prefix),
            pressure_for(set_point))

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_auto_flow_set_on_and_temp_at_setpoint_THEN_pressure_set_to_pressure_for_setpoint_temp_and_does_ramp_down(
            self):
        set_point = 10
        reading = set_point
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])
        self.set_temp_reading_and_sp(reading, set_point)

        self.ca.assert_that_pv_is_number(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix),
            pressure_for(set_point) + SPC_OFFSET,
            tolerance=SPC_OFFSET / 4)  # should see number in ramp
        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix),
            pressure_for(set_point))  # final value

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_auto_flow_set_on_and_temp_above_setpoint_by_more_than_deadband_THEN_pressure_set_to_pressure_for_setpoint_temp_plus_gain_and_does_ramp(
            self):
        diff = SPC_TEMP_DEADBAND * 1.1
        set_point = 10
        reading = set_point + diff
        expected_pressure = pressure_for(set_point) + SPC_OFFSET + (
            abs(reading - set_point - SPC_TEMP_DEADBAND) * SPC_GAIN)**2
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])
        self.set_temp_reading_and_sp(reading, set_point)

        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix),
            expected_pressure)  # final value
        sleep(1.5)  # wait for possible ramp
        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix),
            expected_pressure)  # final value

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_auto_flow_set_on_and_pressure_would_be_high_THEN_pressure_set_to_maximum_pressure(
            self):
        diff = 1000
        set_point = 10
        reading = set_point + diff
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])

        self.set_temp_reading_and_sp(reading, set_point)

        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix),
            SPC_MAX_PRESSURE)  # final value

    def test_WHEN_auto_flow_set_off_THEN_pressure_is_not_updated(self):
        diff = 1000
        set_point = 10
        reading = set_point + diff

        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])
        expected_value = -10
        self.ca.set_pv_value("{}:PRESSURE:SP".format(pressure_card_pv_prefix),
                             expected_value)

        self.set_temp_reading_and_sp(reading, set_point, "OFF")

        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_value)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_auto_flow_on_but_error_in_temp_readback_THEN_pressure_is_not_updated(
            self):
        set_point = 10

        card_pv_prefix = get_card_pv_prefix(TEMP_CARDS[0])
        pressure_card_pv_prefix = get_card_pv_prefix(PRESSURE_CARDS[0])
        expected_value = -10
        self.ca.set_pv_value("{}:PRESSURE:SP".format(pressure_card_pv_prefix),
                             expected_value)
        self._lewis.backdoor_set_on_device("connected", False)
        self.ca.assert_that_pv_alarm_is(
            "{}:TEMP:SP:RBV".format(card_pv_prefix), self.ca.Alarms.INVALID)

        self.ca.set_pv_value("{}:SPC:SP".format(card_pv_prefix), "ON")
        self.ca.set_pv_value("{}:TEMP:SP".format(card_pv_prefix), set_point)

        self.ca.assert_that_pv_is(
            "{}:PRESSURE:SP".format(pressure_card_pv_prefix), expected_value)
Exemplo n.º 15
0
class VoltageTests(unittest.TestCase):
    voltage_values = [0, 10.1111111, 10e1, 20e-2, 200]
    voltage_values_which_give_alarms = [
        -50, MIN_SEPARATOR_VOLT, MAX_SEPARATOR_VOLT, 250
    ]

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

    def test_that_GIVEN_sim_val_0_and_data_0_WHEN_voltage_set_point_changed_THEN_data_changed(
            self):
        # GIVEN
        self.ca.set_pv_value("DAQ:VOLT:SIM", 0)
        self.ca.assert_that_pv_is("DAQ:VOLT:SP", 0)

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

        # THEN
        self.ca.assert_that_pv_is("DAQ:VOLT:SP",
                                  20. * DAQ_VOLT_WRITE_SCALE_FACTOR)

    @parameterized.expand(parameterized_list(voltage_values))
    def test_that_WHEN_set_THEN_the_voltage_changes(self, _, value):
        # WHEN
        self.ca.set_pv_value("VOLT:SP", value)

        # THEN
        self.ca.assert_that_pv_is_number("VOLT", value, MARGIN_OF_ERROR)

    @parameterized.expand(parameterized_list(voltage_values_which_give_alarms))
    def test_that_WHEN_voltage_out_of_range_THEN_alarm_raised(self, _, value):
        # WHEN

        self.ca.set_pv_value("VOLT:SP", value)

        # THEN
        self.ca.assert_that_pv_alarm_is("VOLT", ChannelAccess.Alarms.MAJOR)

    def test_that_GIVEN_voltage_in_range_WHEN_setpoint_is_above_range_THEN_setpoint_is_set_to_max_value(
            self):
        # GIVEN
        self.ca.set_pv_value("VOLT:SP", 30)
        self.ca.assert_that_pv_is("VOLT", 30)

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

        # THEN
        self.ca.assert_that_pv_is("VOLT:SP", MAX_SEPARATOR_VOLT)
        self.ca.assert_that_pv_is("VOLT", MAX_SEPARATOR_VOLT)

    def test_that_GIVEN_voltage_in_range_WHEN_setpoint_is_below_range_THEN_setpoint_is_set_to_min_value(
            self):
        # GIVEN
        self.ca.set_pv_value("VOLT:SP", 30)
        self.ca.assert_that_pv_is("VOLT", 30)

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

        # THEN
        self.ca.assert_that_pv_is("VOLT", MIN_SEPARATOR_VOLT)
        self.ca.assert_that_pv_is("VOLT:SP", MIN_SEPARATOR_VOLT)

    def test_GIVEN_data_to_be_filtered_WHEN_filtering_applied_THEN_returned_data_has_correct_shape(
            self):
        # stride_length = 1
        # GIVEN
        self.ca.set_pv_value("DAQ:VOLT:WV:SIM", DAQ_DATA)

        # THEN
        returned_data_shape = self.ca.get_pv_value("FILTERED:VOLT.NORD")

        # THEN
        self.assertEqual(returned_data_shape, len(DAQ_DATA) - STRIDE_LENGTH)

    def test_GIVEN_unfiltered_data_WHEN_filtering_applied_THEN_corrected_data_is_returned(
            self):
        # GIVEN
        # Writing directly to DAQ:VOLT, need to remove the scaling factor
        self.ca.set_pv_value("DAQ:VOLT:WV:SIM",
                             DAQ_DATA * DAQ_VOLT_WRITE_SCALE_FACTOR)

        # THEN
        returned_data_shape = int(self.ca.get_pv_value("FILTERED:VOLT.NORD"))
        returned_data = self.ca.get_pv_value(
            "FILTERED:VOLT")[:returned_data_shape]

        filtered_data = apply_average_filter(DAQ_DATA)

        self.assertEqual(len(returned_data), len(filtered_data))

        for filtered_value, reference_value in zip(returned_data,
                                                   filtered_data):
            self.assertAlmostEqual(filtered_value, reference_value, places=3)
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")
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
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)
Exemplo n.º 19
0
class IndfurnTests(unittest.TestCase):
    """
    Tests for the Indfurn 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=30)

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

    @skip_if_recsim("Recsim does not emulate version command")
    def test_that_version_pv_exists(self):
        self.ca.assert_that_pv_is("VERSION", "EMULATED FURNACE")

    @parameterized.expand(parameterized_list(TEST_TEMPERATURES))
    def test_GIVEN_a_setpoint_WHEN_ask_for_the_setpoint_readback_THEN_get_the_value_just_set(
            self, _, temp, alarm):
        self.ca.assert_setting_setpoint_sets_readback(
            temp,
            set_point_pv="TEMP:SP",
            readback_pv="TEMP:SP:RBV",
            expected_alarm=alarm)

    @parameterized.expand(parameterized_list(TEST_TEMPERATURES))
    def test_GIVEN_a_setpoint_WHEN_ask_for_the_current_temperature_THEN_get_the_value_just_set(
            self, _, temp, alarm):
        self.ca.assert_setting_setpoint_sets_readback(temp,
                                                      set_point_pv="TEMP:SP",
                                                      readback_pv="TEMP",
                                                      expected_alarm=alarm)

    @parameterized.expand(parameterized_list(TEST_TEMPERATURES))
    def test_GIVEN_a_setpoint_WHEN_ask_for_the_sample_temperature_THEN_get_the_value_just_set(
            self, _, temp, alarm):
        self.ca.assert_setting_setpoint_sets_readback(
            temp,
            set_point_pv="TEMP:SP",
            readback_pv="SAMPLE:TEMP",
            expected_alarm=alarm)

    @parameterized.expand(parameterized_list(TEST_DIAGNOSTIC_TEMPERATURES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_pipe_temperature_set_via_backdoor_when_read_pipe_temperature_THEN_get_value_just_set(
            self, _, temp, alarm):
        self._lewis.backdoor_set_on_device("pipe_temperature", temp)
        self.ca.assert_that_pv_is_number("PIPE:TEMP", temp, tolerance=0.1)
        self.ca.assert_that_pv_alarm_is("PIPE:TEMP", alarm)

    @parameterized.expand(parameterized_list(TEST_DIAGNOSTIC_TEMPERATURES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_capacitor_temperature_set_via_backdoor_when_read_capacitor_temperature_THEN_get_value_just_set(
            self, _, temp, alarm):
        self._lewis.backdoor_set_on_device("capacitor_bank_temperature", temp)
        self.ca.assert_that_pv_is_number("CAPACITOR:TEMP", temp, tolerance=0.1)
        self.ca.assert_that_pv_alarm_is("CAPACITOR:TEMP", alarm)

    @parameterized.expand(parameterized_list(TEST_DIAGNOSTIC_TEMPERATURES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_fet_temperature_set_via_backdoor_when_read_fet_temperature_THEN_get_value_just_set(
            self, _, temp, alarm):
        self._lewis.backdoor_set_on_device("fet_temperature", temp)
        self.ca.assert_that_pv_is_number("FET:TEMP", temp, tolerance=0.1)
        self.ca.assert_that_pv_alarm_is("FET:TEMP", alarm)

    @parameterized.expand(parameterized_list(TEST_PID_VALUES))
    def test_GIVEN_p_parameter_changed_WHEN_read_p_THEN_value_can_be_read_back(
            self, _, val):
        self.ca.assert_setting_setpoint_sets_readback(val, "P")

    @parameterized.expand(parameterized_list(TEST_PID_VALUES))
    def test_GIVEN_i_parameter_changed_WHEN_read_i_THEN_value_can_be_read_back(
            self, _, val):
        self.ca.assert_setting_setpoint_sets_readback(val, "I")

    @parameterized.expand(parameterized_list(TEST_PID_VALUES))
    def test_GIVEN_d_parameter_changed_WHEN_read_d_THEN_value_can_be_read_back(
            self, _, val):
        self.ca.assert_setting_setpoint_sets_readback(val, "D")

    @parameterized.expand(parameterized_list(TEST_SAMPLE_TIMES))
    def test_GIVEN_sample_time_changed_WHEN_read_sample_time_THEN_value_can_be_read_back(
            self, _, sample_time):
        self.ca.assert_setting_setpoint_sets_readback(sample_time,
                                                      "SAMPLETIME")

    def test_GIVEN_pid_direction_is_set_THEN_it_can_be_read_back(self):
        for mode in ["Heating", "Cooling",
                     "Heating"]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(
                mode, "PID:DIRECTION")

    def test_GIVEN_pid_run_status_is_set_THEN_it_can_be_read_back(self):
        for mode in ["Stopped", "Running",
                     "Stopped"]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(mode, "PID:RUNNING")

    @parameterized.expand(parameterized_list(TEST_PID_LIMITS))
    def test_GIVEN_pid_lower_limit_is_set_THEN_it_can_be_read_back(
            self, _, pid_limit):
        self.ca.assert_setting_setpoint_sets_readback(pid_limit,
                                                      "PID:LIMIT:LOWER")

    @parameterized.expand(parameterized_list(TEST_PID_LIMITS))
    def test_GIVEN_pid_upper_limit_is_set_THEN_it_can_be_read_back(
            self, _, pid_limit):
        self.ca.assert_setting_setpoint_sets_readback(pid_limit,
                                                      "PID:LIMIT:UPPER")

    @parameterized.expand(parameterized_list(TEST_PSU_VOLTAGES))
    def test_GIVEN_psu_voltage_is_set_THEN_it_can_be_read_back(
            self, _, psu_volt):
        self.ca.assert_setting_setpoint_sets_readback(psu_volt, "PSU:VOLT")

    @parameterized.expand(parameterized_list(TEST_PSU_CURRENTS))
    def test_GIVEN_psu_current_is_set_THEN_it_can_be_read_back(
            self, _, psu_curr):
        self.ca.assert_setting_setpoint_sets_readback(psu_curr, "PSU:CURR")

    @parameterized.expand(parameterized_list(TEST_OUTPUTS))
    def test_GIVEN_output_is_set_THEN_it_can_be_read_back(self, _, output):
        self.ca.assert_setting_setpoint_sets_readback(output, "OUTPUT")

    def test_GIVEN_pid_mode_is_set_THEN_it_can_be_read_back(self):
        for mode in ["Automatic", "Manual",
                     "Automatic"]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(mode, "PID:MODE")

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_power_supply_mode_is_set_to_either_local_or_remote_THEN_it_sets_successfully_in_emulator(
            self):
        for remote in [False, True, False]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(
                "Remote" if remote else "Local",
                "PSU:CONTROLMODE",
                expected_alarm=self.ca.Alarms.NONE
                if remote else self.ca.Alarms.MAJOR)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_power_supply_output_is_set_to_either_on_or_off_THEN_it_sets_successfully_in_emulator(
            self):
        for output in [False, True, False]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(
                "On" if output else "Off", "PSU:POWER")

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_sample_area_led_is_set_to_either_on_or_off_THEN_it_sets_successfully_in_emulator(
            self):
        for led_on in [False, True, False]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(
                "On" if led_on else "Off", "LED")

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_GIVEN_power_supply_hf_is_set_to_either_on_or_off_THEN_it_sets_successfully_in_emulator(
            self):
        for hf_on in [False, True, False]:  # Check both transitions
            self.ca.assert_setting_setpoint_sets_readback(
                "On" if hf_on else "Off", "PSU:HF")

    @skip_if_recsim("Can't use lewis backdoor in recsim")
    def test_GIVEN_psu_goes_over_temperature_THEN_alarm_comes_on_AND_can_reset_via_pv(
            self):
        self._lewis.backdoor_set_on_device("psu_overtemp", True)
        self.ca.assert_that_pv_is("ALARM:PSUTEMP", "ALARM")
        self.ca.assert_that_pv_alarm_is("ALARM:PSUTEMP", self.ca.Alarms.MAJOR)
        self.ca.set_pv_value("ALARM:CLEAR", 1)
        self.ca.assert_that_pv_is("ALARM:PSUTEMP", "OK")
        self.ca.assert_that_pv_alarm_is("ALARM:PSUTEMP", self.ca.Alarms.NONE)

    @skip_if_recsim("Can't use lewis backdoor in recsim")
    def test_GIVEN_psu_goes_over_voltage_THEN_alarm_comes_on_AND_can_reset_via_pv(
            self):
        self._lewis.backdoor_set_on_device("psu_overvolt", True)
        self.ca.assert_that_pv_is("ALARM:PSUVOLT", "ALARM")
        self.ca.assert_that_pv_alarm_is("ALARM:PSUVOLT", self.ca.Alarms.MAJOR)
        self.ca.set_pv_value("ALARM:CLEAR", 1)
        self.ca.assert_that_pv_is("ALARM:PSUVOLT", "OK")
        self.ca.assert_that_pv_alarm_is("ALARM:PSUVOLT", self.ca.Alarms.NONE)

    @skip_if_recsim("Can't use lewis backdoor in recsim")
    def test_GIVEN_cooling_water_flow_turns_off_THEN_this_is_visible_from_ioc_and_causes_alarm(
            self):

        self._lewis.backdoor_set_on_device("cooling_water_flow", 0)
        self.ca.assert_that_pv_is("COOLINGWATER:FLOW", 0)
        self.ca.assert_that_pv_is("COOLINGWATER:STATUS", "ALARM")
        self.ca.assert_that_pv_alarm_is("COOLINGWATER:STATUS",
                                        self.ca.Alarms.MAJOR)

        self._lewis.backdoor_set_on_device("cooling_water_flow", 500)
        self.ca.assert_that_pv_is("COOLINGWATER:FLOW", 500)
        self.ca.assert_that_pv_is("COOLINGWATER:STATUS", "OK")
        self.ca.assert_that_pv_alarm_is("COOLINGWATER:STATUS",
                                        self.ca.Alarms.NONE)

    @skip_if_recsim("Recsim can't handle arbitrary commands")
    def test_GIVEN_an_arbitrary_command_THEN_get_a_response(self):
        self.ca.set_pv_value("ARBITRARY:SP", "?ver")
        self.ca.assert_that_pv_is(
            "ARBITRARY", "<EMULATED FURNACE\r\n<EMULATED FURNACE\r\n")

    @parameterized.expand(parameterized_list(SAMPLE_HOLDER_MATERIALS))
    def test_GIVEN_sample_holder_material_is_set_THEN_sample_holder_material_can_be_read_back(
            self, _, material):
        self.ca.assert_setting_setpoint_sets_readback(material, "SAMPLEHOLDER")

    @skip_if_recsim("Can't use lewis backdoor in recsim")
    def test_GIVEN_thermocouple_1_fault_on_device_THEN_read_successfully(self):
        self._lewis.backdoor_set_on_device("thermocouple_1_fault", 0)
        for fault_pv in FAULTS:
            self.ca.assert_that_pv_is("TC1FAULTS:{}".format(fault_pv), "OK")

        for fault_pv, fault_number in FAULTS.items():
            self._lewis.backdoor_set_on_device("thermocouple_1_fault",
                                               fault_number)
            self.ca.assert_that_pv_is("TC1FAULTS:{}".format(fault_pv), "FAULT")

    @skip_if_recsim("Can't use lewis backdoor in recsim")
    def test_GIVEN_thermocouple_2_fault_on_device_THEN_read_successfully(self):
        self._lewis.backdoor_set_on_device("thermocouple_2_fault", 0)
        for fault_pv in FAULTS:
            self.ca.assert_that_pv_is("TC2FAULTS:{}".format(fault_pv), "OK")

        for fault_pv, fault_number in FAULTS.items():
            self._lewis.backdoor_set_on_device("thermocouple_2_fault",
                                               fault_number)
            self.ca.assert_that_pv_is("TC2FAULTS:{}".format(fault_pv), "FAULT")
Exemplo n.º 20
0
class TcIocTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.emulator, cls._ioc = get_running_lewis_and_ioc(EMULATOR_NAME, DEVICE_PREFIX)

        cls.ca = ChannelAccess(device_prefix=None)

        for i in range(1, 3):
            cls.ca.set_pv_value("AXES_{}:BENABLE".format(i), 1)
            cls.ca.set_pv_value("AXES_{}:FOVERRIDE".format(i), 100)

    def setUp(self):
        for i in range(1, 3):
            self.ca.set_pv_value("FWLIMIT_{}".format(i), 1)
            self.ca.set_pv_value("BWLIMIT_{}".format(i), 1)

        self.ca.set_pv_value(MOTOR_2_SP, 0)

        self.ca.set_pv_value(MOTOR_VELO, 1)
        self.ca.set_pv_value(MOTOR_SP, 0)
        self.ca.set_pv_value(MOTOR_SP + ".UEIP", 1)
        self.ca.assert_that_pv_is(MOTOR_DONE, 1, timeout=10)

    def check_moving(self, expected_moving, moving_state=DISCRETE_MOTION_STATE):
        self.ca.assert_that_pv_is(MOTOR_MOVING, int(expected_moving), timeout=1)
        self.ca.assert_that_pv_is(MOTOR_DONE, int(not expected_moving), timeout=1)
        self.ca.assert_that_pv_is_number(GET_STATE, moving_state if expected_moving else IDLE_STATE)

    @parameterized.expand(
        parameterized_list([3.5, 6, -10])
    )
    def test_WHEN_moving_to_position_THEN_status_is_moving_and_gets_to_position(self, _, target):
        self.check_moving(False)
        self.ca.set_pv_value(MOTOR_SP, target)
        self.check_moving(True)
        self.ca.assert_that_pv_is(MOTOR_RBV, target, timeout=20)
        self.check_moving(False)

    def test_WHEN_moving_forward_THEN_motor_record_in_positive_direction(self):
        self.ca.set_pv_value(MOTOR_SP, 2)
        self.ca.assert_that_pv_is(MOTOR_DIR, 1)

    def test_WHEN_moving_backwards_THEN_motor_record_in_backwards_direction(self):
        self.ca.set_pv_value(MOTOR_SP, -2)
        self.ca.assert_that_pv_is(MOTOR_DIR, 0)

    @unittest.skipIf(True, 'Stop is not implemented for absolute moves in the PLC code')
    def test_WHEN_moving_THEN_can_stop_motion(self):
        self.ca.set_pv_value(MOTOR_SP, 100)
        self.ca.set_pv_value(MOTOR_STOP, 1)
        self.check_moving(False)
        self.ca.assert_that_pv_is_not_number(MOTOR_RBV, 100, 10)

    @parameterized.expand(
        parameterized_list([(MOTOR_JOGR, 0), (MOTOR_JOGF, 1)])
    )
    def test_WHEN_jogging_THEN_can_stop_motion(self, _, pv, direction):
        self.ca.set_pv_value(pv, 1)
        self.ca.assert_that_pv_is(MOTOR_DIR, direction)
        self.check_moving(True, CONTINUOUS_MOTION_STATE)
        self.ca.set_pv_value(MOTOR_STOP, 1)
        self.check_moving(False, CONTINUOUS_MOTION_STATE)

    @parameterized.expand(
        parameterized_list([3.5, 6, -10])
    )
    def test_WHEN_motor_2_moved_THEN_motor_2_gets_to_position_and_motor_1_not_moved(self, _, target):
        self.ca.set_pv_value(MOTOR_2_SP, target)
        self.ca.assert_that_pv_is(MOTOR_MOVING, 0)
        self.ca.assert_that_pv_is_number(MOTOR_RBV, 0)
        self.ca.assert_that_pv_is(MOTOR_2_RBV, target, timeout=20)

    @parameterized.expand(
        parameterized_list([(".HLS", "FWLIMIT_1"), (".LLS", "BWLIMIT_1")])
    )
    def test_WHEN_limits_hit_THEN_motor_reports_limits(self, _, motor_pv_suffix, pv_to_set):
        self.ca.set_pv_value(pv_to_set, 0)
        self.ca.assert_that_pv_is(MOTOR_SP + motor_pv_suffix, 1)

    @parameterized.expand(
        parameterized_list([3.5, 6, -10])
    )
    def test_WHEN_not_using_encoder_THEN_move_reaches_set_point(self, _, target):
        self.ca.set_pv_value(MOTOR_SP + ".UEIP", 0)
        self.ca.set_pv_value(MOTOR_SP, target)
        self.check_moving(True)
        self.ca.assert_that_pv_is(MOTOR_RBV, target, timeout=20)

    def send_command(self, number):
        self.ca.set_pv_value("AXES_1:ECOMMAND", number)
        self.ca.set_pv_value("AXES_1:BEXECUTE", 1)

    def perform_dummy_home_routine(self):
        """
        This will perform a home routine via the backdoor, ideally this
        will be done in the PLC when a home command is sent but not currently simulated
        """
        self.send_command(43)  # Start homing
        sleep(1)  # Let the motor move a bit
        self.ca.set_pv_value("AXES_1:MCSIGNALREF-LEVEL", 1)  # Simulate home signal
        self.send_command(40)  # Home finished

    def get_homed_bit(self):
        return int(self.ca.get_pv_value(MOTOR_SP + ".MSTA")) & (1 << 14) != 0

    def test_WHEN_homing_sent_THEN_ends_in_homed_state(self):
        self.assertFalse(self.get_homed_bit())
        self.perform_dummy_home_routine()
        self.ca.assert_that_pv_is(MOTOR_DONE, 1, timeout=10)
        self.assertTrue(self.get_homed_bit())
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 DMA4500MTests(unittest.TestCase):
    """
    Tests for the DMA4500M density meter
    """

    PVS_ENABLED_OUTSIDE_MEASUREMENT = ["TEMPERATURE:SP", "START"]
    PVS_DISABLED_DURING_MEASUREMENT = ["TEMPERATURE:SP", "START", "AUTOMEASURE"]

    def _reset_ioc(self):
        self.ca.set_pv_value("MEASUREMENT", "ready")
        self.ca.set_pv_value("TEMPERATURE", 0)
        self.ca.set_pv_value("TEMPERATURE:SP", 0)
        self.ca.set_pv_value("DENSITY", 0)
        self.ca.set_pv_value("CONDITION", "0.00000")
        self.ca.set_pv_value("AUTOMEASURE:ENABLED", 0)
        self.ca.set_pv_value("AUTOMEASURE:FREQ:SP", 0)

    def _assert_pvs_disabled(self, pvs, disabled):
        for pv in pvs:
            self.ca.process_pv(pv)
            if disabled:
                self.ca.assert_that_pv_is("{0}.STAT".format(pv), "DISABLE")
            else:
                self.ca.assert_that_pv_is_not("{0}.STAT".format(pv), "DISABLE")

    def _start_instant_measurement(self):
        self._start_measurement(0)

    def _start_measurement(self, measurement_time=10):
        measurement_time = measurement_time
        self._lewis.backdoor_set_on_device("measurement_time", measurement_time * LEWIS_SPEED)
        self.ca.set_pv_value("START", 1)

    def _enable_automeasure(self, interval):
        self.ca.set_pv_value("AUTOMEASURE:FREQ:SP", interval)
        self.ca.set_pv_value("AUTOMEASURE:ENABLED", 1)

    def _disable_automeasure(self):
        self.ca.set_pv_value("AUTOMEASURE:ENABLED", 0)

    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("reset")
        self._reset_ioc()

    def test_WHEN_temperature_set_THEN_setpoint_readback_updates(self):
        self.ca.set_pv_value("TEMPERATURE:SP", 12.34)
        self.ca.assert_that_pv_is("TEMPERATURE:SP:RBV", 12.34)

    def test_WHEN_status_is_done_and_temperature_set_THEN_status_is_ready(self):
        self.ca.set_pv_value("MEASUREMENT", "done")
        self.ca.set_pv_value("TEMPERATURE:SP", 12.34)
        self.ca.assert_that_pv_is("MEASUREMENT", "ready")

    def test_WHEN_temperature_set_THEN_it_updates_after_measurement(self):
        self.ca.set_pv_value("TEMPERATURE:SP", 12.34)
        self._start_instant_measurement()
        self.ca.assert_that_pv_is("TEMPERATURE", 12.34)

    def test_WHEN_density_set_via_backdoor_THEN_it_updates_after_measurement(self):
        self._lewis.backdoor_set_on_device("density", 98.76)
        self._start_instant_measurement()
        self.ca.assert_that_pv_is("DENSITY", 98.76)

    def test_WHEN_condition_set_via_backdoor_THEN_it_updates_after_measurement(self):
        self._lewis.backdoor_set_on_device("condition", "valid")
        self._start_instant_measurement()
        self.ca.assert_that_pv_is("CONDITION", "valid")

    def test_WHEN_measurement_starts_THEN_status_updates(self):
        self._start_measurement(measurement_time=10)
        self.ca.assert_that_pv_is("MEASUREMENT", "measuring", timeout=SCAN_FREQUENCY)
        sleep(10)
        self.ca.assert_that_pv_is("MEASUREMENT", "done", timeout=SCAN_FREQUENCY)

    def test_WHEN_status_is_ready_THEN_correct_records_enabled(self):
        self._assert_pvs_disabled(self.PVS_ENABLED_OUTSIDE_MEASUREMENT, False)

    def test_WHEN_status_is_measuring_THEN_correct_records_disabled(self):
        self._start_measurement(measurement_time=10)
        self.ca.assert_that_pv_is("MEASUREMENT", "measuring", timeout=SCAN_FREQUENCY)
        self._assert_pvs_disabled(self.PVS_DISABLED_DURING_MEASUREMENT, True)

    def test_WHEN_status_is_done_THEN_correct_records_enabled(self):
        self._start_instant_measurement()
        self.ca.assert_that_pv_is("MEASUREMENT", "done", timeout=SCAN_FREQUENCY)
        self._assert_pvs_disabled(self.PVS_ENABLED_OUTSIDE_MEASUREMENT, False)

    @parameterized.expand(parameterized_list([2, 5, 10, 20]))
    def test_WHEN_automeasure_frequency_set_THEN_measurement_repeats(self, _, automeasure_interval):
        measurement_time = 5
        self._lewis.backdoor_set_on_device("measurement_time", measurement_time * LEWIS_SPEED)
        self._enable_automeasure(automeasure_interval)
        self.ca.assert_that_pv_is("MEASUREMENT", "measuring", timeout=2*automeasure_interval)
        self.ca.assert_that_pv_is("MEASUREMENT", "done", timeout=2*measurement_time)
        self.ca.assert_that_pv_is("MEASUREMENT", "measuring", timeout=2*automeasure_interval)

    @parameterized.expand(parameterized_list([2, 5, 10, 20]))
    def test_WHEN_automeasure_frequency_set_then_unset_THEN_measurement_stops(self, _, automeasure_interval):
        measurement_time = 5
        self._lewis.backdoor_set_on_device("measurement_time", measurement_time * LEWIS_SPEED)
        self._enable_automeasure(automeasure_interval)
        self.ca.assert_that_pv_is("MEASUREMENT", "measuring", timeout=2*automeasure_interval)
        self._disable_automeasure()
        self.ca.assert_that_pv_is("MEASUREMENT", "done", timeout=2*measurement_time)
        self.ca.assert_that_pv_is("MEASUREMENT", "done", timeout=2*automeasure_interval)

    def test_GIVEN_device_not_connected_WHEN_get_status_THEN_alarm(self):
        self._lewis.backdoor_set_on_device('connected', False)
        self.ca.assert_that_pv_alarm_is('MEASUREMENT', ChannelAccess.Alarms.INVALID)
        self.ca.assert_that_pv_alarm_is('TEMPERATURE:SP:RBV', ChannelAccess.Alarms.INVALID)
        self.ca.assert_that_pv_alarm_is('TEMPERATURE', ChannelAccess.Alarms.INVALID)
        self.ca.assert_that_pv_alarm_is('DENSITY', ChannelAccess.Alarms.INVALID)
        self.ca.assert_that_pv_alarm_is('CONDITION', ChannelAccess.Alarms.INVALID)
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)
Exemplo n.º 24
0
class IceFridgeTests(unittest.TestCase):
    """
    Tests for the IceFrdge IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(IOCS[0]["emulator"], DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=25)

        if not IOCRegister.uses_rec_sim:
            self._lewis.backdoor_run_function_on_device("reset")
            self._lewis.backdoor_set_on_device("connected", True)

    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(VTI_TEMP_SUFFIXES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_VTI_temp_set_backdoor_THEN_ioc_read_correctly(self, _, temp_num):
        self._lewis.backdoor_run_function_on_device("set_cryo_temp", (temp_num, 3.6))
        self.ca.assert_that_pv_is_number("VTI:TEMP{}".format(temp_num), 3.6, 0.001)

    @parameterized.expand(parameterized_list(itertools.product(VTI_LOOPS, VTI_LOOP_TEST_INPUTS)))
    def test_WHEN_vti_loop_setpoint_THEN_readback_identical(self, _, loop_num, temp):
        self.ca.assert_setting_setpoint_sets_readback(temp, "VTI:LOOP{}:TSET".format(loop_num),
                                                      "VTI:LOOP{}:TSET:SP".format(loop_num))

    @parameterized.expand(parameterized_list(itertools.product(VTI_LOOPS, VTI_LOOP_TEST_INPUTS)))
    def test_WHEN_vti_loop_proportional_THEN_readback_identical(self, _, loop_num, temp):
        self.ca.assert_setting_setpoint_sets_readback(temp, "VTI:LOOP{}:P".format(loop_num),
                                                      "VTI:LOOP{}:P:SP".format(loop_num))

    @parameterized.expand(parameterized_list(itertools.product(VTI_LOOPS, VTI_LOOP_TEST_INPUTS)))
    def test_WHEN_vti_loop_integral_THEN_readback_identical(self, _, loop_num, temp):
        self.ca.assert_setting_setpoint_sets_readback(temp, "VTI:LOOP{}:I".format(loop_num),
                                                      "VTI:LOOP{}:I:SP".format(loop_num))

    @parameterized.expand(parameterized_list(itertools.product(VTI_LOOPS, VTI_LOOP_TEST_INPUTS)))
    def test_WHEN_vti_loop_derivative_THEN_readback_identical(self, _, loop_num, temp):
        self.ca.assert_setting_setpoint_sets_readback(temp, "VTI:LOOP{}:D".format(loop_num),
                                                      "VTI:LOOP{}:D:SP".format(loop_num))

    @parameterized.expand(parameterized_list(itertools.product(VTI_LOOPS, VTI_LOOP_TEST_INPUTS)))
    def test_WHEN_vti_loop_ramp_rate_THEN_readback_identical(self, _, loop_num, temp):
        self.ca.assert_setting_setpoint_sets_readback(temp, "VTI:LOOP{}:RAMPRATE".format(loop_num),
                                                      "VTI:LOOP{}:RAMPRATE:SP".format(loop_num))

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_Lakeshore_MC_Cernox_set_backdoor_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("lakeshore_mc_cernox", 0.5)
        self.ca.assert_that_pv_is_number("LS:MC:CERNOX", 0.5, 0.001)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_Lakeshore_MC_RuO_set_backdoor_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("lakeshore_mc_ruo", 0.6)
        self.ca.assert_that_pv_is_number("LS:MC:RUO", 0.6, 0.001)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_Lakeshore_still_temp_set_backdoor_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("lakeshore_still_temp", 0.7)
        self.ca.assert_that_pv_is_number("LS:STILL:TEMP", 0.7, 0.001)

    def test_WHEN_Lakeshore_MC_setpoint_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback(0.8, "LS:MC:TEMP", "LS:MC:TEMP:SP")

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_Lakeshore_MC_setpoint_is_zero_THEN_scan_correct(self):
        self.ca.set_pv_value("LS:MC:TEMP:SP", 0)
        self._lewis.assert_that_emulator_value_is("lakeshore_scan", 1, 15)
        self._lewis.assert_that_emulator_value_is("lakeshore_cmode", 4, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_Lakeshore_MC_setpoint_is_larger_than_zero_THEN_scan_correct(self):
        self.ca.set_pv_value("LS:MC:TEMP:SP", 4)
        self._lewis.assert_that_emulator_value_is("lakeshore_scan", 0, 15)
        self._lewis.assert_that_emulator_value_is("lakeshore_cmode", 1, 15)

    def test_WHEN_Lakeshore_MC_setpoint_negative_THEN_readback_zero(self):
        self.ca.set_pv_value("LS:MC:TEMP:SP", -1)

        self.ca.assert_that_pv_is("LS:MC:TEMP", 0)

    def test_WHEN_Lakeshore_MC_setpoint_over_limit_THEN_readback_at_limit(self):
        self.ca.set_pv_value("LS:MC:TEMP:SP", 301)

        self.ca.assert_that_pv_is("LS:MC:TEMP", 300)

    def test_WHEN_Lakeshore_MC_proportional_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback(0.9, "LS:MC:P", "LS:MC:P:SP")

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_Lakeshore_MC_integral_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback(11, "LS:MC:I", "LS:MC:I:SP")

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_Lakeshore_MC_derivative_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback(12, "LS:MC:D", "LS:MC:D:SP")

    @parameterized.expand(parameterized_list(LS_MC_HTR_RANGE_VALUES))
    def test_WHEN_Lakeshore_MC_heater_range_THEN_readback_identical(self, _, heater_range):
        self.ca.assert_setting_setpoint_sets_readback(heater_range, "LS:MC:HTR:RANGE", "LS:MC:HTR:RANGE:SP")

    @parameterized.expand(parameterized_list(LS_MC_HTR_RANGE_INVALID_VALUES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_lakeshore_MC_heater_range_invalid_setpoint_THEN_pv_in_alarm(self, _, invalid_range):
        self.ca.assert_that_pv_alarm_is("LS:MC:HTR:RANGE", self.ca.Alarms.NONE, timeout=15)

        self._lewis.backdoor_set_on_device("lakeshore_mc_heater_range", invalid_range)
        self.ca.assert_that_pv_alarm_is("LS:MC:HTR:RANGE", self.ca.Alarms.INVALID, timeout=15)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_Lakeshore_MC_heater_percentage_set_backdoor_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("lakeshore_mc_heater_percentage", 50)
        self.ca.assert_that_pv_is_number("LS:MC:HTR:PERCENT", 50, 0.001)

    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_Lakeshore_MC_still_output_set_backdoor_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("lakeshore_still_output", 1.3)
        self.ca.assert_that_pv_is_number("LS:STILL", 1.3, 0.001)

    @parameterized.expand(parameterized_list(itertools.product(LS_VOLTAGE_CHANNELS, LS_VOLTAGE_RANGE_VALUES)))
    def test_WHEN_Lakeshore_voltage_range_THEN_readback_identical(self, _, voltage_channel, voltage_value):
        self.ca.assert_setting_setpoint_sets_readback(voltage_value, "LS:VLTG:RANGE:CH{}".format(voltage_channel),
                                                      "LS:VLTG:RANGE:SP")

    @parameterized.expand(parameterized_list(itertools.product(LS_VOLTAGE_CHANNELS, LS_VOLTAGE_RANGE_INVALID_VALUES)))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_Lakeshore_voltage_range_invalid_setpoint_THEN_pv_in_alarm(self, _, voltage_channel, invalid_range):
        self.ca.assert_that_pv_alarm_is("LS:VLTG:RANGE:CH{}".format(voltage_channel), self.ca.Alarms.NONE,
                                        timeout=15)

        self._lewis.backdoor_set_on_device("lakeshore_exc_voltage_range_ch{}".format(voltage_channel), invalid_range)
        self.ca.assert_that_pv_alarm_is("LS:VLTG:RANGE:CH{}".format(voltage_channel), self.ca.Alarms.INVALID,
                                        timeout=15)

    @parameterized.expand(parameterized_list(MIMIC_PRESSURE_SUFFIXES))
    @skip_if_recsim("Lewis backdoor not available in recsim")
    def test_WHEN_pressure_set_backdoor_THEN_ioc_read_correctly(self, _, pressure_num):
        self._lewis.backdoor_run_function_on_device("set_pressure", (pressure_num, 1.4))
        self.ca.assert_that_pv_is_number("PRESSURE{}".format(pressure_num), 1.4, 0.001)

    @parameterized.expand(parameterized_list(MIMIC_VALVE_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_valve_status_open_THEN_readback_identical(self, _, valve_num):
        self.ca.assert_setting_setpoint_sets_readback("OPEN", "VALVE{}".format(valve_num),
                                                      "VALVE{}:SP".format(valve_num))

    @parameterized.expand(parameterized_list(MIMIC_VALVE_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_valve_status_closed_THEN_readback_identical(self, _, valve_num):
        self.ca.assert_setting_setpoint_sets_readback("OPEN", "VALVE{}".format(valve_num),
                                                      "VALVE{}:SP".format(valve_num))

        self.ca.assert_setting_setpoint_sets_readback("CLOSED", "VALVE{}".format(valve_num),
                                                      "VALVE{}:SP".format(valve_num))

    @parameterized.expand(parameterized_list(MIMIC_PROPORTIONAL_VALVES_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_proportional_valve_THEN_readback_identical(self, _, proportional_valve_num):
        self.ca.assert_setting_setpoint_sets_readback(1.5, "PROPORTIONAL_VALVE{}".format(proportional_valve_num),
                                                      "PROPORTIONAL_VALVE{}:SP".format(proportional_valve_num))

    @parameterized.expand(parameterized_list(itertools.product(MIMIC_PROPORTIONAL_VALVES_NUMBERS, [0.001, 2])))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_proportional_valve_not_0_THEN_calc_is_one(self, _, proportional_valve_num, test_value):
        self.ca.set_pv_value("PROPORTIONAL_VALVE{}:SP".format(proportional_valve_num), test_value)

        self.ca.assert_that_pv_is("PROPORTIONAL_VALVE{}:_CALC".format(proportional_valve_num), 1)

    @parameterized.expand(parameterized_list(MIMIC_PROPORTIONAL_VALVES_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_proportional_valve_0_THEN_calc_is_zero(self, _, proportional_valve_num):
        self.ca.set_pv_value("PROPORTIONAL_VALVE{}:SP".format(proportional_valve_num), 1)
        self.ca.assert_that_pv_is("PROPORTIONAL_VALVE{}:_CALC".format(proportional_valve_num), 1)

        self.ca.set_pv_value("PROPORTIONAL_VALVE{}:SP".format(proportional_valve_num), 0)
        self.ca.assert_that_pv_is("PROPORTIONAL_VALVE{}:_CALC".format(proportional_valve_num), 0)

    @parameterized.expand(parameterized_list(MIMIC_PROPORTIONAL_VALVES_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_proportional_valve_sp_negative_THEN_readback_zero(self, _, proportional_valve_num):
        self.ca.set_pv_value("PROPORTIONAL_VALVE{}:SP".format(proportional_valve_num), -1)

        self.ca.assert_that_pv_is("PROPORTIONAL_VALVE{}".format(proportional_valve_num), 0)

    @parameterized.expand(parameterized_list(MIMIC_PROPORTIONAL_VALVES_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_proportional_valve_sp_over_limit_THEN_readback_at_limit(self, _, proportional_valve_num):
        self.ca.set_pv_value("PROPORTIONAL_VALVE{}:SP".format(proportional_valve_num), 101)

        self.ca.assert_that_pv_is("PROPORTIONAL_VALVE{}".format(proportional_valve_num), 100)

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_needle_valve_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback(1.6, "NEEDLE_VALVE", "NEEDLE_VALVE:SP")

    @parameterized.expand(parameterized_list([0.001, 2]))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_needle_valve_not_0_THEN_calc_is_one(self, _, test_value):
        self.ca.set_pv_value("NEEDLE_VALVE:SP", test_value)
        self.ca.assert_that_pv_is("NEEDLE_VALVE:_CALC", 1)

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_needle_valve_0_THEN_calc_is_zero(self):
        self.ca.set_pv_value("NEEDLE_VALVE:SP", 0)
        self.ca.assert_that_pv_is("NEEDLE_VALVE:_CALC", 0)

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_needle_valve_sp_negative_THEN_readback_zero(self):
        self.ca.set_pv_value("NEEDLE_VALVE:SP", -1)

        self.ca.assert_that_pv_is("NEEDLE_VALVE", 0)

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_needle_valve_sp_over_limit_THEN_readback_at_limit(self):
        self.ca.set_pv_value("NEEDLE_VALVE:SP", 101)

        self.ca.assert_that_pv_is("NEEDLE_VALVE", 100)

    @parameterized.expand(parameterized_list(MIMIC_SOLENOID_VALVES_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_solenoid_valve_open_THEN_readback_identical(self, _, solenoid_valve_num):
        self.ca.assert_setting_setpoint_sets_readback("OPEN", "SOLENOID_VALVE{}".format(solenoid_valve_num),
                                                      "SOLENOID_VALVE{}:SP".format(solenoid_valve_num))

    @parameterized.expand(parameterized_list(MIMIC_SOLENOID_VALVES_NUMBERS))
    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_solenoid_valve_close_THEN_readback_identical(self, _, solenoid_valve_num):
        self.ca.assert_setting_setpoint_sets_readback("OPEN", "SOLENOID_VALVE{}".format(solenoid_valve_num),
                                                      "SOLENOID_VALVE{}:SP".format(solenoid_valve_num))

        self.ca.assert_setting_setpoint_sets_readback("CLOSED", "SOLENOID_VALVE{}".format(solenoid_valve_num),
                                                      "SOLENOID_VALVE{}:SP".format(solenoid_valve_num))

    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_1K_stage_temp_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("temp_1K_stage", 1.7)
        self.ca.assert_that_pv_is_number("1K:TEMP", 1.7, 0.001)

    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_MC_temperature_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("mixing_chamber_temp", 1.8)
        self.ca.assert_that_pv_is_number("MC:TEMP", 1.8, 0.001)

    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_MC_resistance_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("mixing_chamber_resistance", 1.9)
        self.ca.assert_that_pv_is_number("MC:_RESISTANCE", 1.9, 0.001)

    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_MC_resistance_calc_THEN_calculation_correct(self):
        self._lewis.backdoor_set_on_device("mixing_chamber_resistance", 1918)
        self.ca.assert_that_pv_is_number("MC:RESISTANCE:CALC", 1.918, 0.001)

    def test_WHEN_mimic_mode_manual_THEN_buttons_disabled(self):
        self.ca.set_pv_value("MIMIC:MODE:SP", "MANUAL")

        self.ca.assert_that_pv_is("MIMIC:START:SP.DISP", '1')
        self.ca.assert_that_pv_is("MIMIC:SKIP:SP.DISP", '1')
        self.ca.assert_that_pv_is("MIMIC:STOP:SP.DISP", '1')

    def test_WHEN_mimic_mode_automatic_THEN_buttons_disabled(self):
        self.ca.set_pv_value("MIMIC:MODE:SP", "AUTOMATIC")

        self.ca.assert_that_pv_is("MIMIC:START:SP.DISP", '1')
        self.ca.assert_that_pv_is("MIMIC:SKIP:SP.DISP", '1')
        self.ca.assert_that_pv_is("MIMIC:STOP:SP.DISP", '1')

    def test_WHEN_mimic_mode_semi_automatic_THEN_buttons_enabled(self):
        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")

        self.ca.assert_that_pv_is("MIMIC:START:SP.DISP", '0')
        self.ca.assert_that_pv_is("MIMIC:SKIP:SP.DISP", '0')
        self.ca.assert_that_pv_is("MIMIC:STOP:SP.DISP", '0')

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_skip_THEN_skipped(self):
        self._lewis.assert_that_emulator_value_is("skipped", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:SKIP:SP", "SKIP")

        self._lewis.assert_that_emulator_value_is("skipped", True, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_stop_THEN_stopped(self):
        self._lewis.assert_that_emulator_value_is("stopped", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:STOP:SP", "STOP")

        self._lewis.assert_that_emulator_value_is("stopped", True, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_sequence_condense_THEN_condense(self):
        self._lewis.assert_that_emulator_value_is("condense", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        self.ca.set_pv_value("MIMIC:SEQUENCE:SP", "Condense")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:START:SP", "START")

        self._lewis.assert_that_emulator_value_is("condense", True, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_sequence_circulate_THEN_circulate(self):
        self._lewis.assert_that_emulator_value_is("circulate", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        self.ca.set_pv_value("MIMIC:SEQUENCE:SP", "Circulate")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:START:SP", "START")

        self._lewis.assert_that_emulator_value_is("circulate", True, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_sequence_condense_and_circulate_THEN_condense_and_circulate(self):
        self._lewis.assert_that_emulator_value_is("condense", False, 15)
        self._lewis.assert_that_emulator_value_is("circulate", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        self.ca.set_pv_value("MIMIC:SEQUENCE:SP", "Condense & Circulate")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:START:SP", "START")

        self._lewis.assert_that_emulator_value_is("condense", True, 15)
        self._lewis.assert_that_emulator_value_is("circulate", True, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_sequence_temp_control_THEN_readback_identical(self):
        self._lewis.assert_that_emulator_value_is("temp_control", 0, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        self.ca.set_pv_value("MIMIC:SEQUENCE:SP", "Temperature Control")
        self.ca.set_pv_value("MIMIC:SEQUENCE:TEMP:SP", 2.3)
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:START:SP", "START")

        self.ca.assert_that_pv_is("MIMIC:SEQUENCE:TEMP", 2.3)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_sequence_make_safe_THEN_make_safe(self):
        self._lewis.assert_that_emulator_value_is("make_safe", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        self.ca.set_pv_value("MIMIC:SEQUENCE:SP", "Make Safe")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:START:SP", "START")

        self._lewis.assert_that_emulator_value_is("make_safe", True, 15)

    @skip_if_recsim("Lewis assertion not working in recsim")
    def test_WHEN_mimic_sequence_warm_up_THEN_warm_up(self):
        self._lewis.assert_that_emulator_value_is("warm_up", False, 15)

        self.ca.set_pv_value("MIMIC:MODE:SP", "SEMI AUTOMATIC")
        self.ca.set_pv_value("MIMIC:SEQUENCE:SP", "Warm Up")
        # does not matter what value the pv is set to, only that it processes
        self.ca.set_pv_value("MIMIC:START:SP", "START")

        self._lewis.assert_that_emulator_value_is("warm_up", True, 15)

    @skip_if_recsim("Lewis backdoor not working in recsim")
    def test_WHEN_mimic_info_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("mimic_info", "RBMK reactors do not explode!")
        self.ca.assert_that_pv_is("MIMIC:INFO", "RBMK reactors do not explode!")

    @skip_if_recsim("Lewis backdoor not working in recsim")
    def test_WHEN_state_THEN_ioc_read_correctly(self):
        self._lewis.backdoor_set_on_device("state", "It\\'s disgraceful, really!")
        self.ca.assert_that_pv_is("STATE", "It's disgraceful, really!")

    def test_WHEN_nv_mode_setpoint_manual_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("MANUAL", "NVMODE", "NVMODE:SP")

    def test_WHEN_nv_mode_setpoint_auto_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("MANUAL", "NVMODE", "NVMODE:SP")

        self.ca.assert_setting_setpoint_sets_readback("AUTO", "NVMODE", "NVMODE:SP")

    def test_WHEN_1K_pump_off_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("ON", "1K:PUMP", "1K:PUMP:SP")

        self.ca.assert_setting_setpoint_sets_readback("OFF", "1K:PUMP", "1K:PUMP:SP")

    def test_WHEN_1K_pump_on_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("OFF", "1K:PUMP", "1K:PUMP:SP")

        self.ca.assert_setting_setpoint_sets_readback("ON", "1K:PUMP", "1K:PUMP:SP")

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_He3_pump_off_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("OFF", "HE3:PUMP", "HE3:PUMP:SP")

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_He3_pump_on_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("OFF", "HE3:PUMP", "HE3:PUMP:SP")

        self.ca.assert_setting_setpoint_sets_readback("ON", "HE3:PUMP", "HE3:PUMP:SP")

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_roots_pump_off_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("OFF", "ROOTS", "ROOTS:SP")

    @skip_if_recsim("pv updated when other pv processes, has no scan field")
    def test_WHEN_roots_pump_on_THEN_readback_identical(self):
        self.ca.assert_setting_setpoint_sets_readback("OFF", "ROOTS", "ROOTS:SP")

        self.ca.assert_setting_setpoint_sets_readback("ON",  "ROOTS", "ROOTS:SP")

    @skip_if_recsim("testing lack of connection to device makes no sense in recsim")
    def test_WHEN_ioc_disconnected_THEN_all_pvs_in_alarm(self):
        for pv in TEST_ALARM_STATUS_PVS:
            self.ca.assert_that_pv_alarm_is(pv, self.ca.Alarms.NONE)

        self._lewis.backdoor_set_on_device("connected", False)

        for pv in TEST_ALARM_STATUS_PVS:
            self.ca.assert_that_pv_alarm_is(pv, self.ca.Alarms.INVALID)
Exemplo n.º 25
0
class Sans2dVacCollisionAvoidanceTests(unittest.TestCase):
    """
    Tests for the sans2d vacuum tank motor extensions.
    """
    def setUp(self):
        self.ca = ChannelAccess(device_prefix="MOT", default_timeout=30)
        with ManagerMode(ChannelAccess()):
            self._disable_collision_avoidance()

            for axis in BAFFLES_AND_DETECTORS_Z_AXES:
                current_position = self.ca.get_pv_value("{}".format(axis))

                new_position = self._get_axis_default_position(
                    "{}".format(axis))

                self.ca.set_pv_value("{}:MTR.VMAX".format(axis),
                                     TEST_SPEED,
                                     sleep_after_set=0)
                self.ca.set_pv_value("{}:MTR.VELO".format(axis),
                                     TEST_SPEED,
                                     sleep_after_set=0)
                self.ca.set_pv_value("{}:MTR.ACCL".format(axis),
                                     TEST_ACCELERATION,
                                     sleep_after_set=0)

                if current_position != new_position:
                    self.ca.set_pv_value("{}:SP".format(axis),
                                         new_position,
                                         sleep_after_set=0)

                timeout = self._get_timeout_for_moving_to_position(
                    axis, new_position)
                self.ca.assert_that_pv_is("{}".format(axis),
                                          new_position,
                                          timeout=timeout)

            # re-enable collision avoidance
            self._enable_collision_avoidance()

    def _disable_collision_avoidance(self):
        self._set_collision_avoidance_state(1, "DISABLED")

    def _enable_collision_avoidance(self):
        self._set_collision_avoidance_state(0, "ENABLED")

    def _set_collision_avoidance_state(self, write_value, read_value):

        # Do nothing if manager mode is already in correct state
        if ChannelAccess().get_pv_value(ManagerMode.MANAGER_MODE_PV) != "Yes":
            cm = ManagerMode(ChannelAccess())
        else:
            cm = nullcontext()

        with cm:
            err = None
            for _ in range(20):
                try:
                    self.ca.set_pv_value("SANS2DVAC:COLLISION_AVOIDANCE",
                                         write_value,
                                         sleep_after_set=0)
                    break
                except WriteAccessException as e:
                    err = e
                    sleep(1)
            else:
                raise err
            self.ca.assert_that_pv_is("SANS2DVAC:COLLISION_AVOIDANCE",
                                      read_value)

    @contextlib.contextmanager
    def _assert_last_stop_time_updated(self, timeout=5):
        initial_stop_time = self.ca.get_pv_value("SANS2DVAC:_LAST_STOP_TIME")
        try:
            yield
        except Exception as e:
            raise e
        else:
            self.ca.assert_that_pv_is_not("SANS2DVAC:_LAST_STOP_TIME",
                                          initial_stop_time,
                                          timeout=timeout)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_motor_interval_above_minor_warning_threshold_THEN_interval_is_correct_and_not_in_alarm(
            self, _, axis_pair):
        # disable collision avoidance so it does not interfere with checking the intervals and their alarm status
        self._disable_collision_avoidance()

        rear_axis_position = self.ca.get_pv_value(axis_pair.rear_axis)
        front_axis_position = rear_axis_position - 50 - MINOR_ALARM_INTERVAL_THRESHOLD
        expected_interval = rear_axis_position - front_axis_position

        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_position,
                             sleep_after_set=0)

        timeout = self._get_timeout_for_moving_to_position(
            axis_pair.front_axis, front_axis_position)
        self.ca.assert_that_pv_is_number("SANS2DVAC:{}:INTERVAL".format(
            axis_pair.name),
                                         expected_interval,
                                         timeout=timeout,
                                         tolerance=0.1)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.NONE)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_setpoint_interval_above_minor_warning_threshold_THEN_interval_is_correct_and_not_in_alarm(
            self, _, axis_pair):
        # disable collision avoidance so it does not interfere with checking the intervals and their alarm status
        self._disable_collision_avoidance()

        rear_axis_position = 1000
        front_axis_position = rear_axis_position - 50 - MINOR_ALARM_INTERVAL_THRESHOLD
        expected_interval = rear_axis_position - front_axis_position

        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_position,
                             sleep_after_set=0)
        self.ca.set_pv_value(axis_pair.rear_axis_sp,
                             rear_axis_position,
                             sleep_after_set=0)
        self._assert_axis_position_reached(axis_pair.front_axis,
                                           front_axis_position)
        self._assert_axis_position_reached(axis_pair.rear_axis,
                                           rear_axis_position)

        self.ca.assert_that_pv_is_number("SANS2DVAC:{}:INTERVAL".format(
            axis_pair.name),
                                         expected_interval,
                                         timeout=5,
                                         tolerance=0.1)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.NONE)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_motor_interval_under_minor_warning_threshold_THEN_interval_is_correct_and_in_minor_alarm(
            self, _, axis_pair):
        # disable collision avoidance so it does not interfere with checking the intervals and their alarm status
        self._disable_collision_avoidance()

        rear_position = self.ca.get_pv_value(axis_pair.rear_axis)
        front_new_position = rear_position - MINOR_ALARM_INTERVAL_THRESHOLD + 1
        expected_interval = rear_position - front_new_position

        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_new_position,
                             sleep_after_set=0)

        timeout = self._get_timeout_for_moving_to_position(
            axis_pair.front_axis, front_new_position)
        self.ca.assert_that_pv_is_number("SANS2DVAC:{}:INTERVAL".format(
            axis_pair.name),
                                         expected_interval,
                                         timeout=timeout,
                                         tolerance=0.1)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.MINOR)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_setpoint_interval_under_minor_warning_threshold_THEN_interval_is_correct_and_in_minor_alarm(
            self, _, axis_pair):
        # disable collision avoidance so it does not interfere with checking the intervals and their alarm status
        self._disable_collision_avoidance()

        rear_axis_position = 1000
        front_axis_position = rear_axis_position - MINOR_ALARM_INTERVAL_THRESHOLD + 1
        expected_interval = rear_axis_position - front_axis_position

        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_position,
                             sleep_after_set=0)
        self.ca.set_pv_value(axis_pair.rear_axis_sp,
                             rear_axis_position,
                             sleep_after_set=0)
        self._assert_axis_position_reached(axis_pair.front_axis,
                                           front_axis_position)
        self._assert_axis_position_reached(axis_pair.rear_axis,
                                           rear_axis_position)

        self.ca.assert_that_pv_is_number("SANS2DVAC:{}:INTERVAL".format(
            axis_pair.name),
                                         expected_interval,
                                         timeout=5,
                                         tolerance=0.1)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.MINOR)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_motor_interval_under_major_warning_threshold_THEN_interval_is_correct_and_in_major_alarm(
            self, _, axis_pair):
        # disable collision avoidance so it does not interfere with checking the intervals and their alarm status
        self._disable_collision_avoidance()

        rear_axis_position = self.ca.get_pv_value(axis_pair.rear_axis)
        front_axis_position = rear_axis_position - MAJOR_ALARM_INTERVAL_THRESHOLD + 1
        expected_interval = rear_axis_position - front_axis_position

        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_position,
                             sleep_after_set=0)

        timeout = self._get_timeout_for_moving_to_position(
            axis_pair.front_axis, front_axis_position)
        self.ca.assert_that_pv_is_number("SANS2DVAC:{}:INTERVAL".format(
            axis_pair.name),
                                         expected_interval,
                                         timeout=timeout,
                                         tolerance=0.1)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.MAJOR)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_setpoint_interval_under_major_warning_threshold_THEN_interval_is_correct_and_in_major_alarm(
            self, _, axis_pair):
        # disable collision avoidance so it does not interfere with checking the intervals and their alarm status
        self._disable_collision_avoidance()

        rear_axis_position = 1000
        front_axis_position = rear_axis_position - MAJOR_ALARM_INTERVAL_THRESHOLD + 1
        expected_interval = rear_axis_position - front_axis_position

        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_position,
                             sleep_after_set=0)
        self.ca.set_pv_value(axis_pair.rear_axis_sp,
                             rear_axis_position,
                             sleep_after_set=0)

        self._assert_axis_position_reached(axis_pair.front_axis,
                                           front_axis_position)
        self._assert_axis_position_reached(axis_pair.rear_axis,
                                           rear_axis_position)

        self.ca.assert_that_pv_is_number("SANS2DVAC:{}:INTERVAL".format(
            axis_pair.name),
                                         expected_interval,
                                         timeout=5,
                                         tolerance=0.1)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.MAJOR)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_front_axis_moves_towards_rear_axis_WHEN_setpoint_interval_greater_than_threshold_THEN_motor_not_stopped(
            self, _, axis_pair):
        front_axis_new_position = (self.ca.get_pv_value(axis_pair.rear_axis) -
                                   axis_pair.minimum_interval) - 50
        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_new_position,
                             sleep_after_set=0)

        self._assert_axis_position_reached(axis_pair.front_axis,
                                           front_axis_new_position)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_front_axis_moves_towards_rear_axis_WHEN_setpoint_interval_smaller_than_threshold_THEN_motor_stops(
            self, _, axis_pair):

        with self._assert_last_stop_time_updated():
            front_axis_new_position = (self.ca.get_pv_value(
                axis_pair.rear_axis) - axis_pair.minimum_interval) + 50
            self.ca.set_pv_value(axis_pair.front_axis_sp,
                                 front_axis_new_position,
                                 sleep_after_set=0)

            self.ca.assert_that_pv_is("{}:MTR.MOVN".format(
                axis_pair.front_axis),
                                      1,
                                      timeout=1)
            self.ca.assert_that_pv_is("{}:MTR.TDIR".format(
                axis_pair.front_axis),
                                      1,
                                      timeout=1)

            timeout = self._get_timeout_for_moving_to_position(
                axis_pair.front_axis, front_axis_new_position)
            assert_axis_not_moving(axis_pair.front_axis, timeout=timeout)
            self.ca.assert_that_pv_is_not(axis_pair.front_axis,
                                          front_axis_new_position,
                                          timeout=timeout)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_front_axis_within_threhsold_distance_to_rear_axis_WHEN_set_to_move_away_THEN_motor_not_stopped(
            self, _, axis_pair):
        front_axis_new_position = (self.ca.get_pv_value(axis_pair.rear_axis) -
                                   axis_pair.minimum_interval) + 50
        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_new_position,
                             sleep_after_set=0)

        self.ca.assert_that_pv_is("{}:MTR.MOVN".format(axis_pair.front_axis),
                                  1,
                                  timeout=1)
        self.ca.assert_that_pv_is("{}:MTR.TDIR".format(axis_pair.front_axis),
                                  1,
                                  timeout=1)

        timeout = self._get_timeout_for_moving_to_position(
            axis_pair.front_axis, front_axis_new_position)
        assert_axis_not_moving(axis_pair.front_axis, timeout=timeout)
        self.ca.assert_that_pv_is_not(axis_pair.front_axis,
                                      front_axis_new_position,
                                      timeout=timeout)

        front_axis_new_position = self.ca.get_pv_value(
            axis_pair.front_axis) - 200
        self.ca.set_pv_value(axis_pair.front_axis_sp,
                             front_axis_new_position,
                             sleep_after_set=0)

        assert_axis_moving(axis_pair.front_axis, timeout=1)
        self.ca.assert_that_pv_is("{}:MTR.TDIR".format(axis_pair.front_axis),
                                  0,
                                  timeout=1)
        self._assert_axis_position_reached(axis_pair.front_axis,
                                           front_axis_new_position)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_rear_axis_moves_towards_front_axis_WHEN_setpoint_interval_greater_than_threshold_THEN_motor_not_stopped(
            self, _, axis_pair):
        rear_axis_position = (self.ca.get_pv_value(axis_pair.front_axis) +
                              axis_pair.minimum_interval) + 50
        self.ca.set_pv_value(axis_pair.rear_axis_sp,
                             rear_axis_position,
                             sleep_after_set=0)

        self._assert_axis_position_reached(axis_pair.rear_axis,
                                           rear_axis_position)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_rear_axis_moves_towards_front_axis_WHEN_setpoint_interval_smaller_than_threshold_THEN_motor_stops(
            self, _, axis_pair):
        with self._assert_last_stop_time_updated():
            rear_axis_new_position = (self.ca.get_pv_value(
                axis_pair.front_axis) + axis_pair.minimum_interval) - 50
            self.ca.set_pv_value(axis_pair.rear_axis_sp,
                                 rear_axis_new_position,
                                 sleep_after_set=0)

            self.ca.assert_that_pv_is("{}:MTR.MOVN".format(
                axis_pair.rear_axis),
                                      1,
                                      timeout=1)
            self.ca.assert_that_pv_is("{}:MTR.TDIR".format(
                axis_pair.rear_axis),
                                      0,
                                      timeout=1)

            timeout = self._get_timeout_for_moving_to_position(
                axis_pair.rear_axis, rear_axis_new_position)
            assert_axis_not_moving(axis_pair.rear_axis, timeout=timeout)
            self.ca.assert_that_pv_is_not(axis_pair.rear_axis,
                                          rear_axis_new_position,
                                          timeout=timeout)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_rear_axis_within_threhsold_distance_to_front_axis_WHEN_set_to_move_away_THEN_motor_not_stopped(
            self, _, axis_pair):
        rear_axis_new_position = (self.ca.get_pv_value(axis_pair.front_axis) +
                                  axis_pair.minimum_interval) - 50
        self.ca.set_pv_value(axis_pair.rear_axis_sp,
                             rear_axis_new_position,
                             sleep_after_set=0)

        self.ca.assert_that_pv_is("{}:MTR.MOVN".format(axis_pair.rear_axis),
                                  1,
                                  timeout=1)
        self.ca.assert_that_pv_is("{}:MTR.TDIR".format(axis_pair.rear_axis),
                                  0,
                                  timeout=1)

        timeout = self._get_timeout_for_moving_to_position(
            axis_pair.rear_axis, rear_axis_new_position)
        assert_axis_not_moving(axis_pair.rear_axis, timeout=timeout)
        self.ca.assert_that_pv_is_not(axis_pair.rear_axis,
                                      rear_axis_new_position,
                                      timeout=timeout)

        rear_axis_new_position = self.ca.get_pv_value(
            axis_pair.rear_axis) + 200
        self.ca.set_pv_value(axis_pair.rear_axis_sp,
                             rear_axis_new_position,
                             sleep_after_set=0)

        assert_axis_moving(axis_pair.rear_axis, timeout=1)
        self.ca.assert_that_pv_is("{}:MTR.TDIR".format(axis_pair.rear_axis),
                                  1,
                                  timeout=1)
        self._assert_axis_position_reached(axis_pair.rear_axis,
                                           rear_axis_new_position)

    def _invalid_alarm_on_motor(self, motor, invalid):
        """
        Puts a motor into invalid alarm. The simulated motor record doesn't respect SIMS so instead use a readback
        link pointing at an invalid PV and tell the motor to use the readback link. This causes an invalid alarm as
        desired.
        """
        if invalid:
            self.ca.set_pv_value("{}:MTR.RDBL".format(motor),
                                 "fake_input_link_doesnt_connect",
                                 sleep_after_set=0)

        self.ca.set_pv_value("{}:MTR.URIP".format(motor),
                             "Yes" if invalid else "No",
                             sleep_after_set=0)

        self.ca.assert_that_pv_alarm_is(
            motor, self.ca.Alarms.INVALID if invalid else self.ca.Alarms.NONE)

    @parameterized.expand(parameterized_list(AXIS_PAIRS))
    def test_GIVEN_axis_has_comms_error_THEN_axes_are_stopped(
            self, _, axis_pair):
        with self._assert_last_stop_time_updated(timeout=10):
            self._invalid_alarm_on_motor(axis_pair.front_axis, True)
            self.ca.assert_that_pv_alarm_is(
                "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
                self.ca.Alarms.INVALID)
        self._invalid_alarm_on_motor(axis_pair.front_axis, False)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.NONE)

        with self._assert_last_stop_time_updated(timeout=10):
            self._invalid_alarm_on_motor(axis_pair.rear_axis, True)
            self.ca.assert_that_pv_alarm_is(
                "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
                self.ca.Alarms.INVALID)
        self._invalid_alarm_on_motor(axis_pair.rear_axis, False)
        self.ca.assert_that_pv_alarm_is(
            "SANS2DVAC:{}:INTERVAL".format(axis_pair.name),
            self.ca.Alarms.NONE)

    def _get_axis_default_position(self, axis):
        if axis == "FRONTDETZ":
            new_position = 1000
        elif axis == "FRONTBAFFLEZ":
            new_position = 3000
        elif axis == "REARBAFFLEZ":
            new_position = 5000
        elif axis == "REARDETZ":
            new_position = 7000
        else:
            raise ValueError("invalid axis!")

        return new_position

    def _get_timeout_for_moving_to_position(self, moving_axis, new_position):
        distance_to_travel = abs(new_position -
                                 self.ca.get_pv_value(moving_axis))

        time_to_accelerate_and_decelerate = 2 * TEST_ACCELERATION

        # between 0 and full speed, the average speed is half the full speed, same for when decelerating.
        # Therefore, the distance traveled when accelerating and decelerating is
        # 2 * (full_speed/2 * acceleration_time), so full_speed / acceleration_time
        time_at_full_speed = (distance_to_travel -
                              TEST_SPEED * TEST_ACCELERATION) / TEST_SPEED

        total_time = ceil(time_to_accelerate_and_decelerate +
                          time_at_full_speed)

        return total_time + 10  # +10 as a small tolerance to avoid instability

    def _assert_axis_position_reached(self, axis, position):
        timeout = self._get_timeout_for_moving_to_position(axis, position)
        self.ca.assert_that_pv_is_number(axis,
                                         position,
                                         tolerance=0.1,
                                         timeout=timeout)
Exemplo n.º 26
0
class Lakeshore372Tests(unittest.TestCase):
    """
    Tests for the lakeshore 372 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")

    def _assert_readback_alarm_states(self, alarm):
        for readback_pv in [
                "TEMP", "TEMP:SP:RBV", "P", "I", "D", "HEATER:POWER",
                "RESISTANCE", "HEATER:RANGE"
        ]:
            self.ca.assert_that_pv_alarm_is(readback_pv, alarm)

    @contextlib.contextmanager
    def _simulate_disconnected_device(self):
        self._lewis.backdoor_set_on_device("connected", False)
        try:
            yield
        finally:
            self._lewis.backdoor_set_on_device("connected", True)

    @parameterized.expand(parameterized_list(TEST_TEMPERATURES))
    def test_WHEN_temp_setpoint_is_set_THEN_actual_temperature_updates(
            self, _, temperature):
        self.ca.assert_setting_setpoint_sets_readback(temperature,
                                                      set_point_pv="TEMP:SP",
                                                      readback_pv="TEMP")

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

    @parameterized.expand(parameterized_list(HEATER_RANGES))
    def test_WHEN_heater_range_is_set_THEN_heater_range_readback_updates(
            self, _, rng):
        self.ca.assert_setting_setpoint_sets_readback(
            rng, set_point_pv="HEATER:RANGE:SP", readback_pv="HEATER:RANGE")

    @parameterized.expand(parameterized_list(TEST_HEATER_POWER_PERCENTAGES))
    @skip_if_recsim("Uses lewis backdoor")
    def test_WHEN_heater_power_is_set_via_backdoor_THEN_heater_power_pv_updates(
            self, _, pwr):
        self._lewis.backdoor_set_on_device("heater_power", pwr)
        self.ca.assert_that_pv_is_number("HEATER:POWER", pwr, tolerance=0.001)

    @parameterized.expand(parameterized_list(TEST_SENSOR_RESISTANCES))
    @skip_if_recsim("Uses lewis backdoor")
    def test_WHEN_sensor_resistance_is_set_via_backdoor_THEN_resistance_pv_updates(
            self, _, res):
        self._lewis.backdoor_set_on_device("sensor_resistance", res)
        self.ca.assert_that_pv_is_number("RESISTANCE", res, tolerance=0.000001)

    @parameterized.expand(parameterized_list(TEST_PID_PARAMS))
    def test_WHEN_pid_parameters_are_set_THEN_readbacks_update(
            self, _, p, i, d):
        # Simulate a script by writing PIDs all in one go without waiting for update first.
        self.ca.set_pv_value("P:SP", p)
        self.ca.set_pv_value("I:SP", i)
        self.ca.set_pv_value("D:SP", d)
        self.ca.assert_that_pv_is("P", p)
        self.ca.assert_that_pv_is("I", i)
        self.ca.assert_that_pv_is("D", d)

    @skip_if_recsim("Recsim does not support simulated disconnection")
    def test_WHEN_device_does_not_respond_THEN_pvs_go_into_invalid_alarm(self):
        self._assert_readback_alarm_states(self.ca.Alarms.NONE)
        with self._simulate_disconnected_device():
            self._assert_readback_alarm_states(self.ca.Alarms.INVALID)
        # Assert alarms clear on reconnection
        self._assert_readback_alarm_states(self.ca.Alarms.NONE)

    @skip_if_recsim("Complex logic not testable in recsim")
    def test_WHEN_temperature_setpoint_is_sent_THEN_control_mode_changed_to_5(
            self):
        # 5 is the control mode for closed loop PID control, which should always be sent along with a temperature set.
        self._lewis.backdoor_set_on_device("control_mode", 0)
        self._lewis.assert_that_emulator_value_is("control_mode", 0, cast=int)
        self.ca.set_pv_value("TEMP:SP", 0)
        self._lewis.assert_that_emulator_value_is("control_mode", 5, cast=int)
Exemplo n.º 27
0
class ZeroFieldTests(unittest.TestCase):
    """
    Tests for the muon zero field controller IOC.
    """
    def _set_simulated_measured_fields(self,
                                       fields,
                                       overload=False,
                                       wait_for_update=True):
        """
        Args:
            fields (dict[AnyStr, float]): A dictionary with the same keys as FIELD_AXES and values corresponding to the
              required fields
            overload (bool): whether to simulate the magnetometer being overloaded
            wait_for_update (bool): whether to wait for the statemachine to pick up the new readings
        """
        for axis in FIELD_AXES:
            self.magnetometer_ca.set_pv_value("SIM:DAQ:{}".format(axis),
                                              fields[axis],
                                              sleep_after_set=0)

        # Just overwrite the calculation to return a constant as we are not interested in testing the
        # overload logic in the magnetometer in these tests (that logic is tested separately).
        self.magnetometer_ca.set_pv_value("OVERLOAD:_CALC.CALC",
                                          "1" if overload else "0",
                                          sleep_after_set=0)

        if wait_for_update:
            for axis in FIELD_AXES:
                self.zfcntrl_ca.assert_that_pv_is("FIELD:{}".format(axis),
                                                  fields[axis])
                self.zfcntrl_ca.assert_that_pv_is("FIELD:{}:MEAS".format(axis),
                                                  fields[axis])

    def _set_user_setpoints(self, fields):
        """
        Args:
            fields (dict[AnyStr, float]): A dictionary with the same keys as FIELD_AXES and values corresponding to the
              required fields 
        """
        for axis in FIELD_AXES:
            self.zfcntrl_ca.set_pv_value("FIELD:{}:SP".format(axis),
                                         fields[axis],
                                         sleep_after_set=0)

    def _set_simulated_power_supply_currents(self,
                                             currents,
                                             wait_for_update=True):
        """
        Args:
            currents (dict[AnyStr, float]): A dictionary with the same keys as FIELD_AXES and values corresponding to
              the required currents
            wait_for_update (bool): whether to wait for the readback and setpoint readbacks to update
        """
        for axis in FIELD_AXES:
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR:SP".format(axis),
                                         currents[axis],
                                         sleep_after_set=0)

        if wait_for_update:
            for axis in FIELD_AXES:
                self.zfcntrl_ca.assert_that_pv_is(
                    "OUTPUT:{}:CURR".format(axis), currents[axis])
                self.zfcntrl_ca.assert_that_pv_is(
                    "OUTPUT:{}:CURR:SP:RBV".format(axis), currents[axis])

    def _set_simulated_power_supply_voltages(self,
                                             voltages,
                                             wait_for_update=True):
        """
        Args:
            voltages (dict[AnyStr, float]): A dictionary with the same keys as FIELD_AXES and values corresponding to
              the required voltages
            wait_for_update (bool): whether to wait for the readback and setpoint readbacks to update
        """
        for axis in FIELD_AXES:
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:VOLT:SP".format(axis),
                                         voltages[axis],
                                         sleep_after_set=0)

        if wait_for_update:
            for axis in FIELD_AXES:
                self.zfcntrl_ca.assert_that_pv_is(
                    "OUTPUT:{}:VOLT".format(axis), voltages[axis])
                self.zfcntrl_ca.assert_that_pv_is(
                    "OUTPUT:{}:VOLT:SP:RBV".format(axis), voltages[axis])

    def _assert_at_setpoint(self, status):
        """
        Args:
            status (string): value of AT_SETPOINT PV (either Yes, No or N/A)
        """
        self.zfcntrl_ca.assert_that_pv_is("AT_SETPOINT", status)

    def _assert_status(self, status):
        """
        Args:
            status (Tuple[str, str]): the controller status and error to assert.
        """
        name, expected_alarm = status

        # Special case - this alarm should be suppressed in manual mode. This is because, in manual mode, the
        # scientists will intentionally apply large fields (which overload the magnetometer), but they do not want
        # alarms for this case as it is a "normal" mode of operation.
        if name == Statuses.MAGNETOMETER_OVERLOAD[
                0] and self.zfcntrl_ca.get_pv_value(
                    "AUTOFEEDBACK") == "Manual":
            expected_alarm = self.zfcntrl_ca.Alarms.NONE

        self.zfcntrl_ca.assert_that_pv_is("STATUS", name)
        self.zfcntrl_ca.assert_that_pv_alarm_is("STATUS", expected_alarm)

    def _set_autofeedback(self, autofeedback):
        self.zfcntrl_ca.set_pv_value(
            "AUTOFEEDBACK", "Auto-feedback" if autofeedback else "Manual")

    def _set_scaling_factors(self, px, py, pz, fiddle):
        """
        Args:
            px (float): Amps per mG for the X axis.
            py (float): Amps per mG for the Y axis.
            pz (float): Amps per mG for the Z axis.
            fiddle (float): The feedback (sometimes called "fiddle") factor.
        """
        self.zfcntrl_ca.set_pv_value("P:X", px, sleep_after_set=0)
        self.zfcntrl_ca.set_pv_value("P:Y", py, sleep_after_set=0)
        self.zfcntrl_ca.set_pv_value("P:Z", pz, sleep_after_set=0)
        self.zfcntrl_ca.set_pv_value("P:FEEDBACK", fiddle, sleep_after_set=0)

    def _set_output_limits(self, lower_limits, upper_limits):
        """
        Args:
            lower_limits (dict[AnyStr, float]): A dictionary with the same keys as FIELD_AXES and values corresponding to
              the required output lower limits
            upper_limits (dict[AnyStr, float]): A dictionary with the same keys as FIELD_AXES and values corresponding to
              the required output upper limits
        """
        for axis in FIELD_AXES:
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR:SP.DRVL".format(axis),
                                         lower_limits[axis],
                                         sleep_after_set=0)
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR:SP.LOLO".format(axis),
                                         lower_limits[axis],
                                         sleep_after_set=0)
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR:SP.DRVH".format(axis),
                                         upper_limits[axis],
                                         sleep_after_set=0)
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR:SP.HIHI".format(axis),
                                         upper_limits[axis],
                                         sleep_after_set=0)

            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR.LOLO".format(axis),
                                         lower_limits[axis],
                                         sleep_after_set=0)
            self.zfcntrl_ca.set_pv_value("OUTPUT:{}:CURR.HIHI".format(axis),
                                         upper_limits[axis],
                                         sleep_after_set=0)

            self.zfcntrl_ca.set_pv_value(
                "OUTPUT:{}:CURR:SP:RBV.LOLO".format(axis),
                lower_limits[axis],
                sleep_after_set=0)
            self.zfcntrl_ca.set_pv_value(
                "OUTPUT:{}:CURR:SP:RBV.HIHI".format(axis),
                upper_limits[axis],
                sleep_after_set=0)

    @contextlib.contextmanager
    def _simulate_disconnected_magnetometer(self):
        """
        While this context manager is active, the magnetometer IOC will fail to take any new readings or process any PVs
        """
        self.magnetometer_ca.set_pv_value("DISABLE", 1, sleep_after_set=0)
        try:
            yield
        finally:
            self.magnetometer_ca.set_pv_value("DISABLE", 0, sleep_after_set=0)

    @contextlib.contextmanager
    def _simulate_invalid_magnetometer_readings(self):
        """
        While this context manager is active, any new readings from the magnetometer will be marked as INVALID
        """
        for axis in FIELD_AXES:
            self.magnetometer_ca.set_pv_value(
                "DAQ:{}:_RAW.SIMS".format(axis),
                self.magnetometer_ca.Alarms.INVALID,
                sleep_after_set=0)

        # Wait for RAW PVs to process
        for axis in FIELD_AXES:
            self.magnetometer_ca.assert_that_pv_alarm_is(
                "DAQ:{}:_RAW.SEVR".format(axis),
                self.magnetometer_ca.Alarms.INVALID)
        try:
            yield
        finally:
            for axis in FIELD_AXES:
                self.magnetometer_ca.set_pv_value(
                    "DAQ:{}:_RAW.SIMS".format(axis),
                    self.magnetometer_ca.Alarms.NONE,
                    sleep_after_set=0)
            # Wait for RAW PVs to process
            for axis in FIELD_AXES:
                self.magnetometer_ca.assert_that_pv_alarm_is(
                    "DAQ:{}:_RAW.SEVR".format(axis),
                    self.magnetometer_ca.Alarms.NONE)

    @contextlib.contextmanager
    def _simulate_invalid_power_supply(self):
        """
        While this context manager is active, the readback values from all power supplies will be marked as INVALID
        (this simulates the device not being plugged in, for example)
        """
        pvs_to_make_invalid = ("CURRENT", "_CURRENT:SP:RBV", "OUTPUTMODE",
                               "OUTPUTSTATUS", "VOLTAGE", "VOLTAGE:SP:RBV")

        for ca, pv in itertools.product(
            (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca),
                pvs_to_make_invalid):
            # 3 is the Enum value for an invalid alarm
            ca.set_pv_value("{}.SIMS".format(pv), 3, sleep_after_set=0)

        # Use a separate loop to avoid needing to wait for a 1-second scan 6 times.
        for ca, pv in itertools.product(
            (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca),
                pvs_to_make_invalid):
            ca.assert_that_pv_alarm_is(pv, ca.Alarms.INVALID)

        try:
            yield
        finally:
            for ca, pv in itertools.product(
                (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca),
                    pvs_to_make_invalid):
                ca.set_pv_value("{}.SIMS".format(pv), 0, sleep_after_set=0)

            # Use a separate loop to avoid needing to wait for a 1-second scan 6 times.
            for ca, pv in itertools.product(
                (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca),
                    pvs_to_make_invalid):
                ca.assert_that_pv_alarm_is(pv, ca.Alarms.NONE)

    @contextlib.contextmanager
    def _simulate_failing_power_supply_writes(self):
        """
        While this context manager is active, any writes to the power supply PVs will be ignored. This simulates the
        device being in local mode, for example. Note that this does not mark readbacks as invalid (for that, use
        _simulate_invalid_power_supply instead).
        """
        pvs = [
            "CURRENT:SP.DISP", "VOLTAGE:SP.DISP", "OUTPUTMODE:SP.DISP",
            "OUTPUTSTATUS:SP.DISP"
        ]

        for ca, pv in itertools.product(
            (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca), pvs):
            ca.set_pv_value(pv, 1, sleep_after_set=0)
        try:
            yield
        finally:
            for ca, pv in itertools.product(
                (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca), pvs):
                ca.set_pv_value(pv, 0, sleep_after_set=0)

    @contextlib.contextmanager
    def _simulate_measured_fields_changing_with_outputs(
            self, psu_amps_at_measured_zero):
        """
        Calculates and sets somewhat realistic simulated measured fields based on the current values of power supplies.

        Args:
            psu_amps_at_measured_zero: Dictionary containing the Amps of the power supplies when the measured field
              corresponds to zero. i.e. if the system is told to go to zero field, these are the power supply readings
              it will require to get there.
        """
        # Always start at zero current
        self._set_simulated_power_supply_currents({"X": 0, "Y": 0, "Z": 0})

        thread = multiprocessing.Process(target=_update_fields_continuously,
                                         args=(psu_amps_at_measured_zero, ))
        thread.start()
        try:
            yield
        finally:
            thread.terminate()

    def _wait_for_all_iocs_up(self):
        """
        Waits for the "primary" pv(s) from each ioc to be available
        """
        for ca in (self.x_psu_ca, self.y_psu_ca, self.z_psu_ca):
            ca.assert_that_pv_exists("CURRENT")
            ca.assert_that_pv_exists("CURRENT:SP")
            ca.assert_that_pv_exists("CURRENT:SP:RBV")

        for axis in FIELD_AXES:
            self.zfcntrl_ca.assert_that_pv_exists("FIELD:{}".format(axis))
            self.magnetometer_ca.assert_that_pv_exists(
                "CORRECTEDFIELD:{}".format(axis))

    def setUp(self):
        _, self._ioc = get_running_lewis_and_ioc(None, ZF_DEVICE_PREFIX)

        timeout = 20
        self.zfcntrl_ca = ChannelAccess(device_prefix=ZF_DEVICE_PREFIX,
                                        default_timeout=timeout)
        self.magnetometer_ca = ChannelAccess(
            device_prefix=MAGNETOMETER_DEVICE_PREFIX, default_timeout=timeout)
        self.x_psu_ca = ChannelAccess(default_timeout=timeout,
                                      device_prefix=X_KEPCO_DEVICE_PREFIX)
        self.y_psu_ca = ChannelAccess(default_timeout=timeout,
                                      device_prefix=Y_KEPCO_DEVICE_PREFIX)
        self.z_psu_ca = ChannelAccess(default_timeout=timeout,
                                      device_prefix=Z_KEPCO_DEVICE_PREFIX)

        self._wait_for_all_iocs_up()

        self.zfcntrl_ca.set_pv_value("TOLERANCE",
                                     STABILITY_TOLERANCE,
                                     sleep_after_set=0)
        self.zfcntrl_ca.set_pv_value("STATEMACHINE:LOOP_DELAY",
                                     LOOP_DELAY_MS,
                                     sleep_after_set=0)
        self._set_autofeedback(False)

        # Set the magnetometer calibration to the 3x3 identity matrix
        for x, y in itertools.product(range(1, 3 + 1), range(1, 3 + 1)):
            self.magnetometer_ca.set_pv_value("SENSORMATRIX:{}{}".format(x, y),
                                              1 if x == y else 0,
                                              sleep_after_set=0)

        self._set_simulated_measured_fields(ZERO_FIELD, overload=False)
        self._set_user_setpoints(ZERO_FIELD)
        self._set_simulated_power_supply_currents(ZERO_FIELD,
                                                  wait_for_update=True)
        self._set_scaling_factors(1, 1, 1, 1)
        self._set_output_limits(
            lower_limits={
                "X": DEFAULT_LOW_OUTPUT_LIMIT,
                "Y": DEFAULT_LOW_OUTPUT_LIMIT,
                "Z": DEFAULT_LOW_OUTPUT_LIMIT
            },
            upper_limits={
                "X": DEFAULT_HIGH_OUTPUT_LIMIT,
                "Y": DEFAULT_HIGH_OUTPUT_LIMIT,
                "Z": DEFAULT_HIGH_OUTPUT_LIMIT
            },
        )

        self._assert_at_setpoint(AtSetpointStatuses.NA)
        self._assert_status(Statuses.NO_ERROR)

    def test_WHEN_ioc_is_started_THEN_it_is_not_disabled(self):
        self.zfcntrl_ca.assert_that_pv_is("DISABLE", "COMMS ENABLED")

    @parameterized.expand(parameterized_list(FIELD_AXES))
    def test_WHEN_manual_mode_and_any_readback_value_is_not_equal_to_setpoint_THEN_at_setpoint_field_is_na(
            self, _, axis_to_vary):
        fields = {"X": 10, "Y": 20, "Z": 30}
        self._set_simulated_measured_fields(fields, overload=False)
        self._set_user_setpoints(fields)

        # Set one of the parameters to a completely different value
        self.zfcntrl_ca.set_pv_value("FIELD:{}:SP".format(axis_to_vary),
                                     100,
                                     sleep_after_set=0)

        self._assert_at_setpoint(AtSetpointStatuses.NA)
        self._assert_status(Statuses.NO_ERROR)

    def test_GIVEN_manual_mode_and_magnetometer_not_overloaded_WHEN_readback_values_are_equal_to_setpoints_THEN_at_setpoint_field_is_na(
            self):
        fields = {"X": 55, "Y": 66, "Z": 77}
        self._set_simulated_measured_fields(fields, overload=False)
        self._set_user_setpoints(fields)

        self._assert_at_setpoint(AtSetpointStatuses.NA)
        self._assert_status(Statuses.NO_ERROR)

    def test_GIVEN_manual_mode_and_within_tolerance_WHEN_magnetometer_is_overloaded_THEN_status_overloaded_and_setpoint_field_is_na(
            self):
        fields = {"X": 55, "Y": 66, "Z": 77}
        self._set_simulated_measured_fields(fields, overload=True)
        self._set_user_setpoints(fields)

        self._assert_at_setpoint(AtSetpointStatuses.NA)
        self._assert_status(Statuses.MAGNETOMETER_OVERLOAD)

    def test_GIVEN_manual_mode_and_just_outside_tolerance_WHEN_magnetometer_is_overloaded_THEN_status_overloaded_and_setpoint_field_is_na(
            self):
        fields = {"X": 55, "Y": 66, "Z": 77}
        self._set_simulated_measured_fields(fields, overload=True)
        self._set_user_setpoints({
            k: v + 1.01 * STABILITY_TOLERANCE
            for k, v in six.iteritems(fields)
        })

        self._assert_at_setpoint(AtSetpointStatuses.NA)
        self._assert_status(Statuses.MAGNETOMETER_OVERLOAD)

    def test_GIVEN_manual_mode_and_just_within_tolerance_WHEN_magnetometer_is_overloaded_THEN_status_overloaded_and_setpoint_field_is_na(
            self):
        fields = {"X": 55, "Y": 66, "Z": 77}
        self._set_simulated_measured_fields(fields, overload=True)
        self._set_user_setpoints({
            k: v + 0.99 * STABILITY_TOLERANCE
            for k, v in six.iteritems(fields)
        })

        self._assert_at_setpoint(AtSetpointStatuses.NA)
        self._assert_status(Statuses.MAGNETOMETER_OVERLOAD)

    def test_WHEN_magnetometer_ioc_does_not_respond_THEN_status_is_magnetometer_read_error(
            self):
        fields = {"X": 1, "Y": 2, "Z": 3}
        self._set_simulated_measured_fields(fields, overload=False)
        self._set_user_setpoints(fields)

        with self._simulate_disconnected_magnetometer():
            self._assert_status(Statuses.MAGNETOMETER_READ_ERROR)
            for axis in FIELD_AXES:
                self.zfcntrl_ca.assert_that_pv_alarm_is(
                    "FIELD:{}".format(axis), self.zfcntrl_ca.Alarms.INVALID)
                self.zfcntrl_ca.assert_that_pv_alarm_is(
                    "FIELD:{}:MEAS".format(axis),
                    self.zfcntrl_ca.Alarms.INVALID)

        # Now simulate recovery and assert error gets cleared correctly
        self._assert_status(Statuses.NO_ERROR)
        for axis in FIELD_AXES:
            self.zfcntrl_ca.assert_that_pv_alarm_is(
                "FIELD:{}".format(axis), self.zfcntrl_ca.Alarms.NONE)
            self.zfcntrl_ca.assert_that_pv_alarm_is(
                "FIELD:{}:MEAS".format(axis), self.zfcntrl_ca.Alarms.NONE)

    def test_WHEN_magnetometer_ioc_readings_are_invalid_THEN_status_is_magnetometer_invalid(
            self):
        fields = {"X": 1, "Y": 2, "Z": 3}
        self._set_simulated_measured_fields(fields, overload=False)
        self._set_user_setpoints(fields)

        with self._simulate_invalid_magnetometer_readings():
            self._assert_status(Statuses.MAGNETOMETER_DATA_INVALID)
            for axis in FIELD_AXES:
                self.zfcntrl_ca.assert_that_pv_alarm_is(
                    "FIELD:{}".format(axis), self.zfcntrl_ca.Alarms.INVALID)
                self.zfcntrl_ca.assert_that_pv_alarm_is(
                    "FIELD:{}:MEAS".format(axis),
                    self.zfcntrl_ca.Alarms.INVALID)

        # Now simulate recovery and assert error gets cleared correctly
        self._assert_status(Statuses.NO_ERROR)
        for axis in FIELD_AXES:
            self.zfcntrl_ca.assert_that_pv_alarm_is(
                "FIELD:{}".format(axis), self.zfcntrl_ca.Alarms.NONE)
            self.zfcntrl_ca.assert_that_pv_alarm_is(
                "FIELD:{}:MEAS".format(axis), self.zfcntrl_ca.Alarms.NONE)

    def test_WHEN_power_supplies_are_invalid_THEN_status_is_power_supplies_invalid(
            self):
        fields = {"X": 1, "Y": 2, "Z": 3}
        self._set_simulated_measured_fields(fields, overload=False)
        self._set_user_setpoints(fields)
        self._set_autofeedback(True)

        with self._simulate_invalid_power_supply():
            self._assert_at_setpoint(
                AtSetpointStatuses.TRUE
            )  # Invalid power supplies do not mark the field as "not at setpoint"
            self._assert_status(Statuses.PSU_INVALID)

        # Now simulate recovery and assert error gets cleared correctly
        self._assert_at_setpoint(AtSetpointStatuses.TRUE)
        self._assert_status(Statuses.NO_ERROR)

    def test_WHEN_power_supplies_writes_fail_THEN_status_is_power_supply_writes_failed(
            self):
        fields = {"X": 1, "Y": 2, "Z": 3}
        self._set_simulated_measured_fields(fields, overload=False)

        # For this test we need changing fields so that we can detect that the writes failed
        self._set_user_setpoints({
            k: v + 10 * STABILITY_TOLERANCE
            for k, v in six.iteritems(fields)
        })
        # ... and we also need large limits so that we see that the writes failed as opposed to a limits error
        self._set_output_limits(lower_limits={k: -999999
                                              for k in FIELD_AXES},
                                upper_limits={k: 999999
                                              for k in FIELD_AXES})
        self._set_autofeedback(True)

        with self._simulate_failing_power_supply_writes():
            self._assert_status(Statuses.PSU_WRITE_FAILED)

        # Now simulate recovery and assert error gets cleared correctly
        self._assert_status(Statuses.NO_ERROR)

    def test_GIVEN_measured_field_and_setpoints_are_identical_THEN_setpoints_remain_unchanged(
            self):
        fields = {"X": 5, "Y": 10, "Z": -5}
        outputs = {"X": -1, "Y": -2, "Z": -3}

        self._set_simulated_measured_fields(fields, overload=False)
        self._set_user_setpoints(fields)
        self._set_simulated_power_supply_currents(outputs,
                                                  wait_for_update=True)

        self._set_autofeedback(True)

        for axis in FIELD_AXES:
            self.zfcntrl_ca.assert_that_pv_is_number(
                "OUTPUT:{}:CURR".format(axis), outputs[axis], tolerance=0.0001)
            self.zfcntrl_ca.assert_that_pv_value_is_unchanged(
                "OUTPUT:{}:CURR".format(axis), wait=5)

    @parameterized.expand(
        parameterized_list([
            # If measured field is smaller than the setpoint, we want to adjust the output upwards to compensate
            (operator.sub, operator.gt, 1),
            # If measured field is larger than the setpoint, we want to adjust the output downwards to compensate
            (operator.add, operator.lt, 1),
            # If measured field is smaller than the setpoint, and A/mg is negative, we want to adjust the output downwards
            # to compensate
            (operator.sub, operator.lt, -1),
            # If measured field is larger than the setpoint, and A/mg is negative, we want to adjust the output upwards
            # to compensate
            (operator.add, operator.gt, -1),
            # If measured field is smaller than the setpoint, and A/mg is zero, then power supply output should remain
            # unchanged
            (operator.sub, operator.eq, 0),
            # If measured field is larger than the setpoint, and A/mg is zero, then power supply output should remain
            # unchanged
            (operator.add, operator.eq, 0),
        ]))
    def test_GIVEN_autofeedback_WHEN_measured_field_different_from_setpoints_THEN_power_supply_outputs_move_in_correct_direction(
            self, _, measured_field_modifier, output_comparator,
            scaling_factor):

        fields = {"X": 5, "Y": 0, "Z": -5}

        adjustment_amount = 10 * STABILITY_TOLERANCE  # To ensure that it is not considered stable to start with
        measured_fields = {
            k: measured_field_modifier(v, adjustment_amount)
            for k, v in six.iteritems(fields)
        }

        self._set_scaling_factors(scaling_factor,
                                  scaling_factor,
                                  scaling_factor,
                                  fiddle=1)
        self._set_simulated_measured_fields(measured_fields, overload=False)
        self._set_user_setpoints(fields)
        self._set_simulated_power_supply_currents({
            "X": 0,
            "Y": 0,
            "Z": 0
        },
                                                  wait_for_update=True)
        self._set_output_limits(lower_limits={k: -999999
                                              for k in FIELD_AXES},
                                upper_limits={k: 999999
                                              for k in FIELD_AXES})

        self._assert_status(Statuses.NO_ERROR)

        self._set_autofeedback(True)
        self._assert_at_setpoint(AtSetpointStatuses.FALSE)

        for axis in FIELD_AXES:
            self.zfcntrl_ca.assert_that_pv_value_over_time_satisfies_comparator(
                "OUTPUT:{}:CURR".format(axis),
                wait=5,
                comparator=output_comparator)

        # In this happy-path case, we shouldn't be hitting any long timeouts, so loop times should remain fairly quick
        self.zfcntrl_ca.assert_that_pv_is_within_range(
            "STATEMACHINE:LOOP_TIME", min_value=0, max_value=2 * LOOP_DELAY_MS)

    def test_GIVEN_output_limits_too_small_for_required_field_THEN_status_error_and_alarm(
            self):
        self._set_output_limits(
            lower_limits={
                "X": -0.1,
                "Y": -0.1,
                "Z": -0.1
            },
            upper_limits={
                "X": 0.1,
                "Y": 0.1,
                "Z": 0.1
            },
        )

        # The measured field is smaller than the setpoint, i.e. the output needs to go up to the limits
        self._set_simulated_measured_fields({"X": -1, "Y": -1, "Z": -1})
        self._set_user_setpoints(ZERO_FIELD)
        self._set_simulated_power_supply_currents(ZERO_FIELD)

        self._set_autofeedback(True)

        self._assert_status(Statuses.PSU_ON_LIMITS)
        for axis in FIELD_AXES:
            # Value should be on one of the limits
            self.zfcntrl_ca.assert_that_pv_is_one_of(
                "OUTPUT:{}:CURR:SP".format(axis), [-0.1, 0.1])
            # ...and in alarm
            self.zfcntrl_ca.assert_that_pv_alarm_is(
                "OUTPUT:{}:CURR:SP".format(axis), self.zfcntrl_ca.Alarms.MAJOR)

    def test_GIVEN_limits_wrong_way_around_THEN_appropriate_error_raised(self):
        # Set upper limits < lower limits
        self._set_output_limits(
            lower_limits={
                "X": 0.1,
                "Y": 0.1,
                "Z": 0.1
            },
            upper_limits={
                "X": -0.1,
                "Y": -0.1,
                "Z": -0.1
            },
        )
        self._set_autofeedback(True)
        self._assert_status(Statuses.INVALID_PSU_LIMITS)

    @parameterized.expand(
        parameterized_list([
            {
                "X": 45.678,
                "Y": 0.123,
                "Z": 12.345
            },
            {
                "X": 0,
                "Y": 0,
                "Z": 0
            },
            {
                "X": -45.678,
                "Y": -0.123,
                "Z": -12.345
            },
        ]))
    def test_GIVEN_measured_values_updating_realistically_WHEN_in_auto_mode_THEN_converges_to_correct_answer(
            self, _, psu_amps_at_zero_field):
        self._set_output_limits(lower_limits={k: -100
                                              for k in FIELD_AXES},
                                upper_limits={k: 100
                                              for k in FIELD_AXES})
        self._set_user_setpoints({"X": 0, "Y": 0, "Z": 0})
        self._set_simulated_power_supply_currents({"X": 0, "Y": 0, "Z": 0})

        # Set fiddle small to get a relatively slow response, which should theoretically be stable
        self._set_scaling_factors(0.001, 0.001, 0.001, fiddle=0.05)

        with self._simulate_measured_fields_changing_with_outputs(
                psu_amps_at_measured_zero=psu_amps_at_zero_field):
            self._set_autofeedback(True)
            for axis in FIELD_AXES:
                self.zfcntrl_ca.assert_that_pv_is_number(
                    "OUTPUT:{}:CURR:SP:RBV".format(axis),
                    psu_amps_at_zero_field[axis],
                    tolerance=STABILITY_TOLERANCE * 0.001,
                    timeout=60)
                self.zfcntrl_ca.assert_that_pv_is_number(
                    "FIELD:{}".format(axis),
                    0.0,
                    tolerance=STABILITY_TOLERANCE)

            self._assert_at_setpoint(AtSetpointStatuses.TRUE)
            self.zfcntrl_ca.assert_that_pv_value_is_unchanged("AT_SETPOINT",
                                                              wait=20)
            self._assert_status(Statuses.NO_ERROR)

    @parameterized.expand(parameterized_list(FIELD_AXES))
    def test_GIVEN_output_is_off_WHEN_autofeedback_switched_on_THEN_psu_is_switched_back_on(
            self, _, axis):
        self.zfcntrl_ca.assert_setting_setpoint_sets_readback(
            "Off", "OUTPUT:{}:STATUS".format(axis))
        self._set_autofeedback(True)
        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:{}:STATUS".format(axis),
                                          "On")

    @parameterized.expand(parameterized_list(FIELD_AXES))
    def test_GIVEN_output_mode_is_voltage_WHEN_autofeedback_switched_on_THEN_psu_is_switched_to_current_mode(
            self, _, axis):
        self.zfcntrl_ca.assert_setting_setpoint_sets_readback(
            "Voltage",
            "OUTPUT:{}:MODE".format(axis),
            expected_alarm=self.zfcntrl_ca.Alarms.MAJOR)
        self._set_autofeedback(True)
        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:{}:MODE".format(axis),
                                          "Current")

    @parameterized.expand(parameterized_list(FIELD_AXES))
    def test_GIVEN_output_is_off_and_cannot_write_to_psu_WHEN_autofeedback_switched_on_THEN_get_psu_write_error(
            self, _, axis):
        self.zfcntrl_ca.assert_setting_setpoint_sets_readback(
            "Off", "OUTPUT:{}:STATUS".format(axis))
        with self._simulate_failing_power_supply_writes():
            self._set_autofeedback(True)
            self._assert_status(Statuses.PSU_WRITE_FAILED)

        # Check it can recover when writes work again
        self._assert_status(Statuses.NO_ERROR)
        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:{}:STATUS".format(axis),
                                          "On")

    @parameterized.expand(parameterized_list(FIELD_AXES))
    def test_GIVEN_output_mode_is_voltage_and_cannot_write_to_psu_WHEN_autofeedback_switched_on_THEN_get_psu_write_error(
            self, _, axis):
        self.zfcntrl_ca.assert_setting_setpoint_sets_readback(
            "Voltage",
            "OUTPUT:{}:MODE".format(axis),
            expected_alarm=self.zfcntrl_ca.Alarms.MAJOR)

        with self._simulate_failing_power_supply_writes():
            self._set_autofeedback(True)
            self._assert_status(Statuses.PSU_WRITE_FAILED)

        # Check it can recover when writes work again
        self._assert_status(Statuses.NO_ERROR)
        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:{}:MODE".format(axis),
                                          "Current")

    @parameterized.expand(
        parameterized_list([
            (True, True),
            (False, True),
            (True, False),
            (False, False),
        ]))
    def test_GIVEN_magnetometer_overloaded_THEN_error_suppressed_if_in_manual_mode(
            self, _, autofeedback, overloaded):
        self._set_autofeedback(autofeedback)
        self._set_simulated_measured_fields(ZERO_FIELD,
                                            overload=overloaded,
                                            wait_for_update=True)
        self._assert_status(Statuses.MAGNETOMETER_OVERLOAD
                            if overloaded else Statuses.NO_ERROR)
        self.zfcntrl_ca.assert_that_pv_alarm_is(
            "STATUS", self.zfcntrl_ca.Alarms.MAJOR
            if overloaded and autofeedback else self.zfcntrl_ca.Alarms.NONE)

    def test_GIVEN_power_supply_voltage_limit_is_set_incorrectly_WHEN_going_into_auto_mode_THEN_correct_limits_applied(
            self):
        self._set_simulated_power_supply_voltages({"X": 0, "Y": 0, "Z": 0})

        self._set_autofeedback(True)

        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:X:VOLT:SP:RBV",
                                          X_KEPCO_VOLTAGE_LIMIT)
        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:Y:VOLT:SP:RBV",
                                          Y_KEPCO_VOLTAGE_LIMIT)
        self.zfcntrl_ca.assert_that_pv_is("OUTPUT:Z:VOLT:SP:RBV",
                                          Z_KEPCO_VOLTAGE_LIMIT)
Exemplo n.º 28
0
class Lakeshore460Tests(unittest.TestCase):
    """
    Tests for the Lakeshore460.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "lakeshore460", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix="LKSH460_01",
                                default_timeout=30,
                                default_wait_time=0.0)
        self.ca.assert_that_pv_exists("IDN")

    @parameterized.expand([("tesla", UnitFlags.TESLA, UnitStrings.TESLA),
                           ("gauss", UnitFlags.GAUSS, UnitStrings.GAUSS)])
    def test_GIVEN_unit_set_to_value_WHEN_read_THEN_unit_is_value(
            self, _, unit_flag, unit_string):
        self.ca.set_pv_value("CHANNEL", "X")
        self.ca.assert_setting_setpoint_sets_readback(
            unit_flag, "UNIT", expected_value=unit_string)

    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_magnetic_field_reading_set_WHEN_read_THEN_magnetic_field_reading_is_set_value(
            self):
        for chan in channels:
            set_field_reading = 1.2356
            self._lewis.backdoor_command([
                "device", "set_channel_param", chan, "field_reading",
                str(set_field_reading)
            ])
            self.ca.assert_that_pv_is("{}:FIELD:RAW".format(chan),
                                      set_field_reading)

    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_prms_set_peak_WHEN_read_THEN_prms_is_peak(self):
        for chan in channels:
            set_prms = UnitFlags.PEAK
            expected_prms = UnitStrings.PEAK
            self.ca.assert_setting_setpoint_sets_readback(
                set_prms,
                "{}:PRMS".format(chan),
                expected_value=expected_prms,
                timeout=15)

    def test_GIVEN_source_set_WHEN_read_THEN_source_is_set_value(self):
        for key in vectors:
            set_value = key
            expected_value = vectors[key]
            self.ca.assert_setting_setpoint_sets_readback(
                set_value, "SOURCE", expected_value=expected_value)

    @parameterized.expand([("DC", UnitFlags.DC, UnitStrings.DC),
                           ("AC", UnitFlags.AC, UnitStrings.AC)])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_output_mode_set_to_value_WHEN_read_THEN_output_mode_is_value(
            self, _, unit_flag, unit_string):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                unit_flag, "{}:MODE".format(chan), expected_value=unit_string)

    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_prms_set_rms_WHEN_read_THEN_prms_is_rms(self):
        for chan in channels:
            set_prms = UnitFlags.RMS
            expected_prms = UnitStrings.RMS
            self.ca.assert_setting_setpoint_sets_readback(
                set_prms,
                "{}:PRMS".format(chan),
                expected_value=expected_prms,
                timeout=15)

    @parameterized.expand([("ON", UnitFlags.ON, UnitStrings.ON),
                           ("OFF", UnitFlags.OFF, UnitStrings.OFF)])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_display_filter_set_to_val_WHEN_read_THEN_display_filter_is_set_value(
            self, _, unit_flag, unit_string):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                unit_flag,
                "{}:FILTER".format(chan),
                expected_value=unit_string)

    @parameterized.expand([("ON", UnitFlags.ON, UnitStrings.ON),
                           ("OFF", UnitFlags.OFF, UnitStrings.OFF)])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_rel_mode_status_set_to_val_WHEN_read_THEN_rel_mode_status_is_set_value(
            self, _, unit_flag, unit_string):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                unit_flag,
                "{}:RELMODE".format(chan),
                expected_value=unit_string)

    @parameterized.expand(parameterized_list([10.4, 20, 3]))
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_rel_mode_setpoint_set_to_val_WHEN_read_THEN_rel_mode_setpoint_is_set_value(
            self, _, value):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                value, "{}:RELMODESET".format(chan))

    @parameterized.expand([("ON", UnitFlags.ON, UnitStrings.ON),
                           ("OFF", UnitFlags.OFF, UnitStrings.OFF)])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_auto_mode_status_set_to_value_WHEN_read_THEN_auto_mode_status_is_set_value(
            self, _, unit_flag, unit_string):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                unit_flag, "{}:AUTO".format(chan), expected_value=unit_string)

    @parameterized.expand([("ON", UnitFlags.ON, UnitStrings.ON),
                           ("OFF", UnitFlags.OFF, UnitStrings.OFF)])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_max_hold_status_set_to_value_WHEN_read_THEN_max_hold_status_is_set_value(
            self, _, unit_flag, unit_string):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                unit_flag,
                "{}:MAXHOLD".format(chan),
                expected_value=unit_string)

    @parameterized.expand([
        ("ON", UnitFlags.CHANNEL_ON, UnitStrings.CHANNEL_ON),
        ("OFF", UnitFlags.CHANNEL_OFF, UnitStrings.CHANNEL_OFF)
    ])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_channel_status_set_on_WHEN_read_THEN_channel_status_is_set_value(
            self, _, unit_flag, unit_string):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                unit_flag,
                "{}:STATUS".format(chan),
                expected_value=unit_string)

    @parameterized.expand([("11_alarm_major", 11, "MAJOR"),
                           ("4_no_alarm", 4, "NO_ALARM")])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_filter_windows_set_WHEN_read_THEN_alarm_is_as_expected(
            self, _, filter_windows, exp_alarm):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                filter_windows,
                "{}:FWIN".format(chan),
                expected_alarm=exp_alarm)

    @parameterized.expand([("65_alarm_major", 65, "MAJOR"),
                           ("10_no_alarm", 10, "NO_ALARM")])
    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_filter_points_set_WHEN_read_THEN_alarm_is_major(
            self, _, filter_points, exp_alarm):
        for chan in channels:
            self.ca.assert_setting_setpoint_sets_readback(
                filter_points,
                "{}:FNUM".format(chan),
                expected_alarm=exp_alarm)

    @skip_if_recsim("In rec sim this test fails")
    def test_GIVEN_range_set_WHEN_read_THEN_range_is_set_value(self):
        for chan in channels:
            for key in ranges:
                set_range = key
                expected_range = ranges[key]
                self.ca.assert_setting_setpoint_sets_readback(
                    set_range,
                    "{}:RANGE".format(chan),
                    expected_value=expected_range)
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 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)