コード例 #1
0
class Sans2dVacuumSystemTests(unittest.TestCase):
    """
    Tests for the SANS2D vacuum system, based on a FINS PLC.
    """
    def setUp(self):
        self._ioc = IOCRegister.get_running("FINS_01")
        self.ca = ChannelAccess(device_prefix=ioc_prefix)

    def test_WHEN_ioc_is_run_THEN_heartbeat_record_exists(self):
        self.ca.assert_setting_setpoint_sets_readback(1, "HEARTBEAT",
                                                      "SIM:HEARTBEAT")

    @parameterized.expand([(1, "IN", ChannelAccess.Alarms.NONE),
                           (2, "OUT", ChannelAccess.Alarms.NONE),
                           (3, "UNKNOWN", ChannelAccess.Alarms.MAJOR),
                           (4, "ERROR", ChannelAccess.Alarms.MAJOR),
                           (5, "ERROR(IN)", ChannelAccess.Alarms.MAJOR),
                           (6, "ERROR(OUT)", ChannelAccess.Alarms.MAJOR)])
    def test_WHEN_data_changes_THEN_monitor_status_correct(
            self, raw_value, enum_string, alarm):
        self.ca.set_pv_value("SIM:ADDR:1001", raw_value)
        self.ca.assert_that_pv_is("MONITOR3:STATUS", enum_string)
        self.ca.assert_that_pv_alarm_is("MONITOR3:STATUS", alarm)

    @parameterized.expand([(7, "CLOSED", ChannelAccess.Alarms.NONE),
                           (8, "OPEN", ChannelAccess.Alarms.NONE),
                           (16, "ERROR", ChannelAccess.Alarms.MAJOR),
                           (24, "ERROR(OPEN)", ChannelAccess.Alarms.MAJOR)])
    def test_WHEN_data_changes_THEN_shutter_status_correct(
            self, raw_value, enum_string, alarm):
        self.ca.set_pv_value("SIM:ADDR:1001", raw_value)
        self.ca.assert_that_pv_is("SHUTTER:STATUS", enum_string)
        self.ca.assert_that_pv_alarm_is("SHUTTER:STATUS", alarm)

    @parameterized.expand([(127, "CLOSED", ChannelAccess.Alarms.NONE),
                           (128, "OPEN", ChannelAccess.Alarms.NONE),
                           (256, "ERROR", ChannelAccess.Alarms.MAJOR),
                           (384, "ERROR(OPEN)", ChannelAccess.Alarms.MAJOR)])
    def test_WHEN_data_changes_THEN_v8_status_correct(self, raw_value,
                                                      enum_string, alarm):
        self.ca.set_pv_value("SIM:ADDR:1001", raw_value)
        self.ca.assert_that_pv_is("V8:STATUS", enum_string)
        self.ca.assert_that_pv_alarm_is("V8:STATUS", alarm)

    def test_WHEN_common_alarm_low_THEN_common_alarm_bad_high_and_alarm(self):
        self.ca.set_pv_value("SIM:ADDR:1001", 0)
        self.ca.assert_that_pv_is("COMMON_ALARM:BAD", 1)
        self.ca.assert_that_pv_alarm_is("COMMON_ALARM:BAD",
                                        ChannelAccess.Alarms.MAJOR)

    def test_WHEN_common_alarm_high_THEN_common_alarm_bad_low_and_no_alarm(
            self):
        self.ca.set_pv_value("SIM:ADDR:1001", 32768)
        self.ca.assert_that_pv_is("COMMON_ALARM:BAD", 0)
        self.ca.assert_that_pv_alarm_is("COMMON_ALARM:BAD",
                                        ChannelAccess.Alarms.NONE)

    @parameterized.expand([(1, "DEFLATED", ChannelAccess.Alarms.NONE),
                           (2, "INFLATING", ChannelAccess.Alarms.NONE),
                           (4, "INFLATED", ChannelAccess.Alarms.NONE),
                           (8, "DEFLATING", ChannelAccess.Alarms.NONE)])
    def test_WHEN_data_changes_THEN_seal_status_correct(
            self, raw_value, enum_string, alarm):
        self.ca.set_pv_value("SIM:ADDR:1004", raw_value)
        self.ca.assert_that_pv_is("SEAL:STATUS", enum_string)
        self.ca.assert_that_pv_alarm_is("SEAL:STATUS", alarm)

    @parameterized.expand([(0, 0), (4000, 10000), (2000, 5000)])
    def test_WHEN_seal_supply_pressure_changes_THEN_correctly_converted(
            self, raw_value, expected_converted_val):
        self.ca.set_pv_value("SIM:SEAL:SUPPLY:PRESS:RAW", raw_value)
        self.ca.assert_that_pv_is("SEAL:SUPPLY:PRESS", expected_converted_val)

    def _set_sp_and_assert(self,
                           set_pv,
                           state,
                           expected_state=None,
                           int_state=None):
        if int_state is None:
            int_state = state
        if expected_state is None:
            expected_state = state
        self.ca.set_pv_value("{}:STATUS:SP".format(set_pv), int_state)
        self.ca.assert_that_pv_monitor_gets_values(
            "{}:{}:SP".format(set_pv, expected_state), [expected_state, "..."])

    def test_WHEN_opening_and_closing_shutter_THEN_propogates(self):
        self._set_sp_and_assert("SHUTTER", "OPEN")
        self._set_sp_and_assert("SHUTTER", "CLOSE")
        self._set_sp_and_assert("SHUTTER", "OPEN")

    def test_WHEN_opening_and_closing_shutter_with_numbers_THEN_propogates(
            self):
        self._set_sp_and_assert("SHUTTER", "OPEN", 1)
        self._set_sp_and_assert("SHUTTER", "CLOSE", 0)
        self._set_sp_and_assert("SHUTTER", "OPEN", 1)

    def test_WHEN_insert_and_extract_monitor_THEN_propogates(self):
        self._set_sp_and_assert("MONITOR3", "IN", "INSERT")
        self._set_sp_and_assert("MONITOR3", "OUT", "EXTRACT")
        self._set_sp_and_assert("MONITOR3", "IN", "INSERT")

    def test_WHEN_insert_and_extract_monitor_with_numbers_THEN_propogates(
            self):
        self._set_sp_and_assert("MONITOR3", "IN", "INSERT", 1)
        self._set_sp_and_assert("MONITOR3", "OUT", "EXTRACT", 0)
        self._set_sp_and_assert("MONITOR3", "IN", "INSERT", 1)

    def test_WHEN_start_and_stop_guide_THEN_propogates(self):
        self._set_sp_and_assert("GUIDE", "START")
        self._set_sp_and_assert("GUIDE", "STOP")
        self._set_sp_and_assert("GUIDE", "START")

    def test_WHEN_start_and_stop_guide_with_numbers_THEN_propogates(self):
        self._set_sp_and_assert("GUIDE", "START", 1)
        self._set_sp_and_assert("GUIDE", "STOP", 0)
        self._set_sp_and_assert("GUIDE", "START", 1)

    def test_WHEN_begin_run_in_auto_shutter_mode_THEN_shutter_opened(self):
        self.ca.set_pv_value("SHUTTER:STATUS:SP", "CLOSE", wait=True)
        self.ca.set_pv_value("SHUTTER:AUTO", 1, wait=True)

        self.ca.process_pv("SHUTTER:OPEN_IF_AUTO")

        self.ca.assert_that_pv_is("SHUTTER:STATUS:SP", "OPEN")

    def test_WHEN_begin_run_in_manual_shutter_mode_THEN_shutter_opened(self):
        self.ca.set_pv_value("SHUTTER:STATUS:SP", "CLOSE", wait=True)
        self.ca.set_pv_value("SHUTTER:AUTO", 0, wait=True)

        self.ca.process_pv("SHUTTER:OPEN_IF_AUTO")

        self.ca.assert_that_pv_is_not("SHUTTER:STATUS:SP", "OPEN", timeout=5)

    def test_WHEN_end_run_in_auto_shutter_mode_THEN_shutter_opened(self):
        self.ca.set_pv_value("SHUTTER:STATUS:SP", "OPEN", wait=True)
        self.ca.set_pv_value("SHUTTER:AUTO", 1, wait=True)

        self.ca.process_pv("SHUTTER:CLOSE_IF_AUTO")

        self.ca.assert_that_pv_is("SHUTTER:STATUS:SP", "CLOSE")

    def test_WHEN_end_run_in_manual_shutter_mode_THEN_shutter_opened(self):
        self.ca.set_pv_value("SHUTTER:STATUS:SP", "OPEN", wait=True)
        self.ca.set_pv_value("SHUTTER:AUTO", 0, wait=True)

        self.ca.process_pv("SHUTTER:CLOSE_IF_AUTO")

        self.ca.assert_that_pv_is_not("SHUTTER:STATUS:SP", "CLOSE", timeout=5)
コード例 #2
0
class LoqApertureTests(unittest.TestCase):
    """
    Tests for the LOQ Aperture
    """
    def setUp(self):
        self._ioc = IOCRegister.get_running("GALIL_01")
        self.ca = ChannelAccess(default_timeout=30)
        self.ca.assert_that_pv_exists(MOTOR, timeout=60)
        self.ca.assert_that_pv_exists(CLOSESTSHUTTER)
        self.ca.assert_that_pv_exists(CLOSEAPERTURE)

    # Closest positions defined in ticket 3623
    @parameterized.expand([
        ("Aperture_large", 0, 1),
        ("Stop_01", 1, 1),
        ("Aperture_medium", 2, 3),
        ("Stop_02", 3, 3),
        ("Aperture_small", 4, 3),
    ])
    def test_GIVEN_motor_on_an_aperture_position_WHEN_motor_set_to_closest_beamstop_THEN_motor_moves_to_closest_beamstop(
            self, start_position, start_index, closest_stop):
        # GIVEN
        self.ca.set_pv_value(POSITION_SP, start_index)
        self.ca.assert_that_pv_is_number(POSITION_INDEX,
                                         start_index,
                                         tolerance=TOLERANCE)
        self.ca.assert_that_pv_is_number(MOTOR,
                                         MOTION_SETPOINT[start_position],
                                         tolerance=TOLERANCE)

        # WHEN
        self.ca.process_pv(CLOSEAPERTURE)

        # THEN
        self.ca.assert_that_pv_is_number(CLOSESTSHUTTER, closest_stop)
        self.ca.assert_that_pv_is_number(POSITION_INDEX,
                                         closest_stop,
                                         timeout=5)
        self.ca.assert_that_pv_is_number(
            MOTOR,
            list(MOTION_SETPOINT.values())[closest_stop],
            tolerance=TOLERANCE)

    # Closest positions defined in ticket 3623
    @parameterized.expand([
        ("Aperture_large", 0, 1),
        ("Stop_01", 1, 1),
        ("Aperture_medium", 2, 3),
        ("Stop_02", 3, 3),
        ("Aperture_small", 4, 3),
    ])
    def test_GIVEN_motor_off_setpoint_WHEN_motor_set_to_closest_beamstop_THEN_motor_moves_to_closest_beamstop(
            self, _, start_index, closest_stop):
        # GIVEN
        # Move 25 per cent forwards and backwards off centre of setpoint
        for fraction_moved_off_setpoint in [0.25, -0.25]:
            initial_position = list(MOTION_SETPOINT.values())[start_index] + (
                fraction_moved_off_setpoint * SETPOINT_GAP)
            self.ca.set_pv_value(MOTOR, initial_position)
            self.ca.assert_that_pv_is_number(MOTOR,
                                             initial_position,
                                             tolerance=TOLERANCE)

            # This assertion ensures that this calc record has updated with the closest beam stop position
            self.ca.assert_that_pv_is_number(CLOSESTSHUTTER, closest_stop)

            # WHEN
            self.ca.process_pv(CLOSEAPERTURE)

            # THEN
            self.ca.assert_that_pv_is_number(CLOSESTSHUTTER, closest_stop)
            self.ca.assert_that_pv_is_number(POSITION_INDEX,
                                             closest_stop,
                                             timeout=5)
            self.ca.assert_that_pv_is_number(
                MOTOR,
                list(MOTION_SETPOINT.values())[closest_stop],
                tolerance=TOLERANCE)
コード例 #3
0
class Jsco4180Tests(unittest.TestCase):
    """
    Tests for the Jsco4180 IOC.
    """

    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(DEVICE_NAME, DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=30)
        for pv in required_pvs:
            self.ca.assert_that_pv_exists(pv, timeout=30)
        self._lewis.backdoor_run_function_on_device("reset")

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_wrong_component_on_device_WHEN_running_THEN_retry_run_and_updates_component(self):
        expected_value_A = 30
        expected_value_B = 15
        expected_value_C = 55

        self.ca.set_pv_value("COMP:A:SP", expected_value_A)
        self.ca.set_pv_value("COMP:B:SP", expected_value_B)
        self.ca.set_pv_value("COMP:C:SP", expected_value_C)

        self.ca.set_pv_value("START:SP", 1)

        sleep(10)
        # Setting an incorrect component on the device will result in the state machine attempting
        # to rerun the pump and reset components.
        self._lewis.backdoor_set_on_device("component_A", 25)
        self._lewis.backdoor_set_on_device("component_B", 10)
        self._lewis.backdoor_set_on_device("component_C", 14)

        self.ca.assert_that_pv_is("COMP:A", expected_value_A, timeout=30)
        self.ca.assert_that_pv_is("COMP:B", expected_value_B, timeout=30)
        self.ca.assert_that_pv_is("COMP:C", expected_value_C, timeout=30)

    # there was a previous problem where if setpoint and readback differed a sleep and resend was started,
    # but the old state machine did not look to see if a new sp was issued while it was asleep and so then
    # resent the old out of date SP
    @unstable_test(max_retries=2, wait_between_runs=60)
    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_wrong_component_on_device_WHEN_send_new_sp_THEN_state_machine_aborts_resend(self):
        value = 50
        self.ca.set_pv_value("COMP:A:SP", value)
        self.ca.set_pv_value("COMP:B:SP", value)
        self.ca.set_pv_value("START:SP", 1)
        self.ca.assert_that_pv_is("STATUS", "Pumping", timeout=5)
        self.ca.assert_that_pv_is("COMP:A", value, timeout=30)
        self.ca.assert_that_pv_is("COMP:B", value, timeout=30)

        # Setting an incorrect component on the device will result in the state machine attempting
        # to rerun the pump and reset components after a delay
        initial_delay = self.ca.get_pv_value("ERROR:DELAY")  # delay before state machine reset
        delay = 30  # Increase delay to avoid race conditions
        self.ca.set_pv_value("ERROR:DELAY", delay)
        try:
            with self.ca.assert_pv_not_processed("RESET:SP"):
                self._lewis.backdoor_set_on_device("component_A", value - 5)
                self.ca.assert_that_pv_is("COMP:A", value - 5, timeout=5)
                sleep(delay / 2.0)

                # however if we change setpoint, the loop should start again
                self._lewis.backdoor_set_on_device("component_A", value - 5)
                self.ca.set_pv_value("COMP:A:SP", value - 10)
                self.ca.set_pv_value("COMP:B:SP", value + 10)
                # reset should not have happened yet
                self.ca.assert_that_pv_is("COMP:A", value - 5, timeout=delay / 2.0)
                self.ca.assert_that_pv_value_is_unchanged("COMP:A", wait=delay / 2.0)

            # Reset should now happen within a further timeout/2 seconds (but give it longer to avoid races)
            with self.ca.assert_pv_processed("RESET:SP"):
                self.ca.assert_that_pv_is("COMP:A", value - 10, timeout=delay * 2)
        finally:
            # Put error delay back to it's initial value
            self.ca.set_pv_value("ERROR:DELAY", initial_delay)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_wrong_component_on_device_WHEN_running_continuous_THEN_retry_run_and_updates_component_in_correct_mode(
            self):
        value = 50
        expected_value = "Pumping"
        self.ca.set_pv_value("COMP:A:SP", value)
        self.ca.set_pv_value("COMP:B:SP", value)

        self.ca.set_pv_value("START:SP", 1)

        # Give the device some time running in a good state
        sleep(10)
        # Sabotage! - Setting an incorrect component on the device will result in the state machine attempting
        # to rerun the pump and reset components.
        self._lewis.backdoor_set_on_device("component_A", 33)

        self.ca.assert_that_pv_is("STATUS", expected_value, timeout=30)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_wrong_component_on_device_WHEN_running_timed_THEN_retry_run_and_updates_component_in_correct_mode(
            self):
        value = 50
        expected_value = "Pumping"
        self.ca.set_pv_value("COMP:A:SP", value)
        self.ca.set_pv_value("COMP:B:SP", value)
        self.ca.set_pv_value("TIME:RUN:SP", 100)
        self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1)

        # Give the device some time running in a good state
        sleep(10)
        # Sabotage! - Setting an incorrect component on the device will result in the state machine attempting
        # to rerun the pump and reset components.
        self._lewis.backdoor_set_on_device("component_A", 33)

        self.ca.assert_that_pv_is("STATUS", expected_value, timeout=30)

    @skip_if_recsim("Flowrate device logic not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_set_flowrate_THEN_flowrate_setpoint_is_correct(self):

        error_delay = float(self.ca.get_pv_value("ERROR:DELAY"))
        sleep(2 * error_delay)  # To make sure we're not in the middle of the error-checking state machine

        expected_value = 1.000
        self.ca.set_pv_value("FLOWRATE:SP", expected_value)

        self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_value)

        self.ca.set_pv_value("TIME:RUN:SP", 100)
        self.ca.set_pv_value("START:SP", "Start")

        self.ca.assert_that_pv_is("FLOWRATE", expected_value)

    @skip_if_recsim("LeWIS backdoor not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_set_flowrate_and_pump_volume_THEN_ioc_uses_rbv_for_calculation_of_remaining_time(self):
        expected_sp_value = 1.000
        expected_rbv_value = 2.000
        pump_for_volume = 2
        expected_time_value = (pump_for_volume / expected_rbv_value) * 60

        error_delay = float(self.ca.get_pv_value("ERROR:DELAY"))
        sleep(2 * error_delay)  # To make sure we're not in the middle of the error-checking state machine

        # 1. set invalid flowrate setpoint (FLOWRATE:SP)
        self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value)
        self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value)

        # 2. set valid hardware flowrate (FLOWRATE:SP:RBV) via backdoor command
        self._lewis.backdoor_set_on_device("flowrate_rbv", expected_rbv_value)
        self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_rbv_value)

        # 3. set volume setpoint and start pump
        self.ca.set_pv_value("TIME:VOL:SP", pump_for_volume)
        self.ca.set_pv_value("START:SP", "Start")

        # 4. check calculated time is based on flowrate setpoint readback (:SP:RBV rather than :SP)
        self.ca.assert_that_pv_is("TIME:VOL:CALCRUN", expected_time_value)

    @skip_if_recsim("LeWIS backdoor not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_set_flowrate_and_pump_time_THEN_ioc_uses_rbv_for_calculation_of_remaining_volume(self):
        expected_sp_value = 1.000
        expected_rbv_value = 2.000
        pump_for_time = 120
        expected_volume_value = (pump_for_time * expected_rbv_value) / 60

        error_delay = float(self.ca.get_pv_value("ERROR:DELAY"))
        sleep(2 * error_delay)  # To make sure we're not in the middle of the error-checking state machine

        # 1. set invalid flowrate setpoint (FLOWRATE:SP)
        self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value)
        self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value)

        # 2. set valid hardware flowrate (FLOWRATE:SP:RBV) via backdoor command
        self._lewis.backdoor_set_on_device("flowrate_rbv", expected_rbv_value)
        self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_rbv_value)

        # 3. set time setpoint and start pump
        self.ca.set_pv_value("TIME:RUN:SP", pump_for_time)
        self.ca.set_pv_value("START:SP", "Start")

        # 4. check calculated volume is based on flowrate setpoint readback (:SP:RBV rather than :SP)
        self.ca.assert_that_pv_is("TIME:RUN:CALCVOL", expected_volume_value)

    # test to check that the IOC updates the flowrate RBV quickly enough
    # for the remaining volume calculation to be valid.  simulates operation of a script.
    def test_GIVEN_an_ioc_WHEN_set_flowrate_and_immediately_set_pump_to_start_THEN_ioc_updates_rbv_for_calculation_of_remaining_volume(
            self):
        expected_sp_value = 2.000
        script_sp_value = 3.000
        pump_for_time = 120

        # 1. initialize flowrate
        self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value)
        self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value, timeout=5)

        # 2. set new flowrate and immediately set pump to run, to simulate script
        self.ca.set_pv_value("FLOWRATE:SP", script_sp_value)
        self.ca.set_pv_value("TIME:RUN:SP", pump_for_time)
        self.ca.set_pv_value("START:SP", "Start")

        # 3. calculate remaining volume
        expected_volume_value = (pump_for_time * self.ca.get_pv_value("FLOWRATE:SP:RBV")) / 60

        # 4. check ioc calculation is as expected
        self.ca.assert_that_pv_is("TIME:RUN:CALCVOL", expected_volume_value)

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_set_maximum_pressure_limit_THEN_maximum_pressure_limit_is_correct(self):
        expected_value = 200
        self.ca.assert_setting_setpoint_sets_readback(expected_value, "PRESSURE:MAX")

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_set_minimum_pressure_limit_THEN_minimum_pressure_limit_is_correct(self):
        expected_value = 100
        self.ca.set_pv_value("PRESSURE:MIN:SP", expected_value)
        self.ca.assert_setting_setpoint_sets_readback(expected_value, "PRESSURE:MIN")

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_continuous_pump_set_THEN_pump_on(self):
        self.ca.set_pv_value("START:SP", 1)

        self.ca.assert_that_pv_is("STATUS", "Pumping")

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_an_ioc_WHEN_timed_pump_set_THEN_timed_pump_on(self):
        # Set a run time for a timed run
        self.ca.set_pv_value("TIME:RUN:SP", 10000)
        self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1)

        self.ca.assert_that_pv_is("STATUS", "Pumping")

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_an_ioc_WHEN_get_current_pressure_THEN_current_pressure_returned(self):
        expected_value = 300
        self._lewis.backdoor_set_on_device("pressure", expected_value)

        self.ca.assert_that_pv_is("PRESSURE", expected_value)

    @parameterized.expand([
        ("component_{}".format(suffix), suffix) for suffix in ["A", "B", "C", "D"]
    ])
    @skip_if_recsim("Reliant on setUP lewis backdoor call")
    def test_GIVEN_an_ioc_WHEN_get_component_THEN_correct_component_returned(self, component, suffix):
        expected_value = 10.0
        self._lewis.backdoor_set_on_device(component, expected_value)

        self.ca.assert_that_pv_is("COMP:{}".format(suffix), expected_value)

    @parameterized.expand([
        ("COMP:{}".format(suffix), suffix) for suffix in ["A", "B", "C"]
    ])
    @skip_if_recsim("Reliant on setUP lewis backdoor call")
    def test_GIVEN_an_ioc_WHEN_set_component_THEN_correct_component_set(self, component, suffix):
        expected_value = 100.0
        self.ca.set_pv_value("COMP:{}:SP".format(suffix), expected_value)
        if component == "COMP:A":
            self.ca.set_pv_value("COMP:B:SP", 0)
            self.ca.set_pv_value("COMP:C:SP", 0)
        elif component == "COMP:B":
            self.ca.set_pv_value("COMP:A:SP", 0)
            self.ca.set_pv_value("COMP:C:SP", 0)
        elif component == "COMP:C":
            self.ca.set_pv_value("COMP:A:SP", 0)
            self.ca.set_pv_value("COMP:B:SP", 0)
        self.ca.set_pv_value("PUMP_FOR_TIME:SP", "Start")

        self.ca.assert_that_pv_is(component, expected_value)

    def test_GIVEN_ioc_initial_state_WHEN_get_error_THEN_error_returned(self):
        expected_value = "No error"

        self.ca.assert_that_pv_is("ERROR", expected_value)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_ioc_in_hardware_error_state_WHEN_get_error_THEN_hardware_error_returned(self):
        expected_value = "Hardware error"
        self._lewis.backdoor_set_on_device("error", ERROR_STATE_HARDWARE_FAULT)

        self.ca.assert_that_pv_is("ERROR", expected_value)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_ioc_in_error_state_WHEN_reset_error_THEN_error_reset(self):
        expected_value = "No error"
        self._lewis.backdoor_set_on_device("error", ERROR_STATE_NO_ERROR)
        self.ca.set_pv_value("ERROR:SP", "Reset")

        self.ca.assert_that_pv_is("ERROR", expected_value)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_ioc_in_error_state_WHEN_reset_error_THEN_error_reset(self):
        expected_value = "No error"
        self._lewis.backdoor_set_on_device("error", ERROR_STATE_HARDWARE_FAULT)

        self.ca.assert_that_pv_is("ERROR", expected_value)

    @skip_if_recsim("Unable to use lewis backdoor in RECSIM")
    def test_GIVEN_device_not_connected_WHEN_get_error_THEN_alarm(self):
        self._lewis.backdoor_set_on_device('connected', False)

        self.ca.assert_that_pv_alarm_is('ERROR:SP', ChannelAccess.Alarms.INVALID)

    @skip_if_recsim("Reliant on setUP lewis backdoor call")
    def test_GIVEN_timed_pump_WHEN_get_program_runtime_THEN_program_runtime_increments(self):
        self.ca.set_pv_value("TIME:RUN:SP", 10000)
        self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1)

        self.ca.assert_that_pv_value_is_increasing("TIME", wait=2)

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_timed_pump_WHEN_set_constant_pump_THEN_state_updated_to_constant_pump(self):
        # Set a run time for a timed run
        self.ca.set_pv_value("TIME:RUN:SP", 10000)
        self.ca.process_pv("PUMP_FOR_TIME:SP")
        expected_value = "Pumping"
        self.ca.assert_that_pv_is("STATUS", expected_value)

        self.ca.process_pv("START:SP")
        expected_value = "Pumping"
        self.ca.assert_that_pv_is("STATUS", expected_value)

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_constant_pump_WHEN_set_timed_pump_THEN_state_updated_to_timed_pump(self):
        expected_value = "Pumping"

        self.ca.process_pv("START:SP")
        self.ca.assert_that_pv_is("STATUS", expected_value)

        # Set a run time for a timed run
        self.ca.set_pv_value("TIME:RUN:SP", 10000)
        self.ca.process_pv("PUMP_FOR_TIME:SP")
        self.ca.assert_that_pv_is("STATUS", expected_value)

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_input_incorrect_WHEN_set_flowrate_THEN_trouble_message_returned(self):
        self._lewis.backdoor_set_on_device("input_correct", False)
        self.ca.set_pv_value("FLOWRATE:SP", 0.010)

        self.ca.assert_that_pv_is("ERROR:STR", "[Error:stack underflow]")

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_command_seq_that_would_crash_pump_WHEN_command_seq_called_THEN_pump_crashes(self):
        self.ca.set_pv_value("_TEST_CRASH.PROC", 1)

        self.ca.assert_that_pv_alarm_is("COMP:A", ChannelAccess.Alarms.INVALID, timeout=30)

    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_pump_running_WHEN_set_file_number_command_called_THEN_program_is_busy_error(self):
        expected_value = "[Program is Busy]"
        self.ca.set_pv_value("START:SP", 1)
        self.ca.set_pv_value("FILE:SP", 0)

        self.ca.assert_that_pv_is("ERROR:STR", expected_value)

    @parameterized.expand([("low_set_time", 100, 1, 1),
                           ("high_set_time", 1000, 10, 1),
                           ("non_standard_set_time", 456, 5, 1)])
    @unstable_test(max_retries=5)
    @skip_if_recsim("Lewis device logic not supported in RECSIM")
    def test_GIVEN_pump_for_volume_WHEN_pumping_THEN_device_is_pumping_set_volume(self, _, time, volume, flowrate):
        # Set a target pump time a target pump volume. When we start a pump set volume run, then the remaining
        # time should be related to the target volume, and not the target time (that would be used for a pump for time).
        set_time = time
        set_volume = volume
        set_flowrate = flowrate
        expected_time = set_volume * set_flowrate * 60  # flow rate units = mL/min, so convert to seconds

        self.ca.set_pv_value("TIME:RUN:SP", set_time)
        self.ca.set_pv_value("TIME:VOL:SP", set_volume)
        self.ca.set_pv_value("FLOWRATE:SP", set_flowrate)

        self.ca.process_pv("PUMP_SET_VOLUME:SP")

        self.ca.assert_that_pv_is_within_range("TIME:REMAINING", min_value=expected_time - 20,
                                               max_value=expected_time + 20)
コード例 #4
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)
コード例 #5
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)
コード例 #6
0
class Lksh218Tests(unittest.TestCase):
    """
    Tests for the Lksh218 IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "Lksh218", DEVICE_PREFIX)
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX)
        self._lewis.backdoor_set_on_device("connected", True)

    def tearDown(self):
        self._lewis.backdoor_set_on_device("connected", True)

    def _set_temperature(self, number, temperature):
        pv = "SIM:TEMP{}".format(number)
        self._lewis.backdoor_run_function_on_device("set_temp",
                                                    [number, temperature])
        self._ioc.set_simulated_value(pv, temperature)

    def _set_sensor(self, number, value):
        pv = "SIM:SENSOR{}".format(number)
        self._lewis.backdoor_run_function_on_device("set_sensor",
                                                    [number, value])
        self._ioc.set_simulated_value(pv, value)

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

    def test_that_GIVEN_temp_float_WHEN_temp_pvs_are_read_THEN_temp_is_as_expected(
            self):
        expected_value = 10.586

        for index in range(1, 9):
            pv = "TEMP{}".format(index)
            self._set_temperature(index, expected_value)
            self.ca.assert_that_pv_is(pv, expected_value)

    def test_that_GIVEN_sensor_float_WHEN_sensor_pvs_are_read_THEN_sensor_is_as_expected(
            self):
        expected_value = 11.386

        for index in range(1, 9):
            pv = "SENSOR{}".format(index)
            self._set_sensor(index, expected_value)
            self.ca.assert_that_pv_is(pv, expected_value)

    def test_that_WHEN_reading_all_temps_pv_THEN_all_temp_pv_are_as_expected(
            self):
        expected_string = "10.4869"
        self._lewis.backdoor_set_on_device("temp_all", expected_string)
        self._ioc.set_simulated_value("SIM:TEMPALL", expected_string)

        self.ca.process_pv("TEMPALL")
        self.ca.assert_that_pv_is("TEMPALL", expected_string)

    def test_that_WHEN_reading_sensor_all_pv_THEN_sensor_all_pv_returns_as_expected(
            self):
        expected_string = "12.129"
        self._lewis.backdoor_set_on_device("sensor_all", expected_string)
        self._ioc.set_simulated_value("SIM:SENSORALL", expected_string)

        self.ca.process_pv("SENSORALL")
        self.ca.assert_that_pv_is("SENSORALL", expected_string)

    @skip_if_recsim("Recsim is unable to simulate a disconnected device.")
    def test_that_WHEN_the_emulator_is_disconnected_THEN_an_alarm_is_raised_on_TEMP_and_SENSOR(
            self):
        self._lewis.backdoor_set_on_device("connected", False)

        for i in range(1, 9):
            self.ca.assert_that_pv_alarm_is("TEMP{}".format(i),
                                            ChannelAccess.Alarms.INVALID)
            self.ca.assert_that_pv_alarm_is("SENSOR{}".format(i),
                                            ChannelAccess.Alarms.INVALID)

    @skip_if_recsim("Recsim is unable to simulate a disconnected device.")
    def test_that_WHEN_the_emulator_is_disconnected_THEN_an_alarm_is_raised_on_SENSORALL(
            self):
        self._lewis.backdoor_set_on_device("connected", False)

        self.ca.process_pv("SENSORALL")
        self.ca.assert_that_pv_alarm_is("SENSORALL",
                                        ChannelAccess.Alarms.INVALID)

    @unstable_test()
    @skip_if_recsim("Recsim is unable to simulate a disconnected device.")
    def test_that_WHEN_the_emulator_is_disconnected_THEN_an_alarm_is_raised_on_TEMPALL(
            self):
        self._lewis.backdoor_set_on_device("connected", False)

        self.ca.process_pv("TEMPALL")
        self.ca.assert_that_pv_alarm_is("TEMPALL",
                                        ChannelAccess.Alarms.INVALID)
コード例 #7
0
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)
コード例 #8
0
class RkndioVersionTests(unittest.TestCase):
    """
    Tests for the Rkndio IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            "rkndio", DEVICE_PREFIX)
        self.ca = ChannelAccess(20, device_prefix=DEVICE_PREFIX)
        self._reset_device()

    def _reset_device(self):
        self._connect_emulator()
        self.ca.assert_that_pv_exists("IDN")

        self._lewis.backdoor_run_function_on_device("reset_error")

        self.ca.assert_that_pv_is("STATUS", "No error")
        self.ca.assert_that_pv_is("ERROR", "No error")

    def _connect_emulator(self):
        self._lewis.backdoor_run_function_on_device("connect")

    def _disconnect_emulator(self):
        self._lewis.backdoor_run_function_on_device("disconnect")

    def test_that_we_can_receive_the_correct_IDN(self):
        # When:
        self.ca.process_pv("IDN")

        # Then:
        self.ca.assert_that_pv_is("IDN", "RIKENFE Prototype v2.0")

    @skip_if_recsim("Recsim is unable to simulate a disconnected device")
    def test_that_GIVEN_a_disconnected_emulator_WHEN_getting_pressure_THEN_INVALID_alarm_shows(
            self):
        # Given:
        self._disconnect_emulator()

        # When:
        self.ca.process_pv("IDN")

        # Then:
        self.ca.assert_that_pv_alarm_is("IDN", self.ca.Alarms.INVALID)

    def test_that_we_can_get_the_status_of_the_device(self):
        # Given
        status_message = "A Status"
        self._lewis.backdoor_set_on_device("status", status_message)

        # When/Then:
        self.ca.assert_that_pv_is("STATUS", status_message)

    def test_that_we_can_get_the_error_status_of_the_device(self):
        # Given:
        error_message = "The pin is not readable"
        self._lewis.backdoor_set_on_device("error", error_message)

        # When/Then:
        self.ca.assert_that_pv_is("ERROR", error_message)

    @parameterized.expand([("Pin_{}".format(i), i) for i in range(2, 8)])
    def test_that_we_can_read_a_digital_input(self, _, pin):
        # Given
        pv = "PIN:{}".format(pin)
        self._lewis.backdoor_run_function_on_device(
            "set_input_state_via_the_backdoor", [pin, "FALSE"])
        self.ca.assert_that_pv_is(pv, "FALSE")

        self._lewis.backdoor_run_function_on_device(
            "set_input_state_via_the_backdoor", [pin, "TRUE"])

        # When:
        self.ca.process_pv(pv)

        # Then:
        self.ca.assert_that_pv_is(pv, "TRUE")

    @parameterized.expand([("Pin_{}".format(i), i) for i in range(8, 14)])
    def test_that_we_can_write_to_a_digital_output(self, _, pin):
        # Given
        pv = "PIN:{}".format(pin)
        self.ca.set_pv_value(pv, "FALSE")
        reset_check = self._lewis.backdoor_run_function_on_device(
            "get_output_state_via_the_backdoor", [pin])
        self.assertEqual(reset_check, "FALSE")

        # When:
        self.ca.set_pv_value(pv, "TRUE")

        # Then:
        result = self._lewis.backdoor_run_function_on_device(
            "get_output_state_via_the_backdoor", [pin])
        self.assertEqual(result, "TRUE")
コード例 #9
0
class AstriumTests(unittest.TestCase):
    """
    Tests for the Astrium Chopper.
    """
    def setUp(self):
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX,
                                default_timeout=30)

    @parameterized.expand(parameterized_list(VALID_FREQUENCIES))
    def test_that_WHEN_setting_the_frequency_setpoint_THEN_it_is_set(
            self, _, value):
        self.ca.set_pv_value("CH1:FREQ:SP", value)
        self.ca.assert_that_pv_is("CH1:FREQ", value)

    @parameterized.expand(parameterized_list(VALID_PHASE_DELAYS))
    def test_that_WHEN_setting_the_phase_setpoint_THEN_it_is_set(
            self, _, value):
        self.ca.set_pv_value("CH1:PHASE:SP", value)
        self.ca.assert_that_pv_is("CH1:PHASE", value)

    @parameterized.expand(parameterized_list(VALID_PHASE_DELAYS))
    @skip_if_recsim("Behaviour of phase readback not implemented in recsim")
    def test_that_WHEN_setting_the_phase_setpoint_and_then_speed_THEN_phases_to_the_correct_place(
            self, _, value):
        """
        This test simulates the bug in https://github.com/ISISComputingGroup/IBEX/issues/4123
        """

        # Arrange - set initial speed and phase
        old_speed = 10
        self.ca.set_pv_value("CH1:FREQ:SP", old_speed)
        self.ca.assert_that_pv_is_number("CH1:FREQ",
                                         old_speed)  # Wait for it to get there
        self.ca.set_pv_value("CH1:PHASE:SP", value)
        self.ca.assert_that_pv_is_number("CH1:PHASE", value)
        self.ca.assert_that_pv_is_number("CH1:PHASE:SP:RBV", value)

        # Act - set frequency
        new_speed = 20
        self.ca.set_pv_value("CH1:FREQ:SP", new_speed)
        self.ca.assert_that_pv_is_number("CH1:FREQ",
                                         new_speed)  # Wait for it to get there

        # Assert - both the actual phase and the setpoint readback should be correct after setting speed.
        self.ca.assert_that_pv_is_number("CH1:PHASE", value)
        self.ca.assert_that_pv_value_is_unchanged("CH1:PHASE", wait=10)
        self.ca.assert_that_pv_is_number("CH1:PHASE:SP:RBV", value)
        self.ca.assert_that_pv_value_is_unchanged("CH1:PHASE:SP:RBV", wait=10)

    def test_WHEN_frequency_set_to_180_THEN_actual_setpoint_not_updated(self):
        sent_frequency = 180
        self.ca.set_pv_value("CH1:FREQ:SP", sent_frequency)
        self.ca.assert_that_pv_is_not("CH1:FREQ:SP_ACTUAL", sent_frequency)
        self.ca.assert_that_pv_is_not("CH1:FREQ", sent_frequency)

    @skip_if_recsim("No state changes in recsim")
    def test_WHEN_brake_called_THEN_state_is_BRAKE(self):
        self.ca.set_pv_value("CH1:BRAKE", 1)
        self.ca.assert_that_pv_is("CH1:STATE", "BRAKE")

    @skip_if_recsim("No state changes in recsim")
    def test_WHEN_speed_set_THEN_state_is_POSITION(self):
        self.ca.set_pv_value("CH1:FREQ:SP", 10)
        self.ca.assert_that_pv_is("CH1:STATE", "POSITION")

    @skip_if_devsim("No backdoor to state in devsim")
    def test_WHEN_one_channel_state_not_ESTOP_THEN_calibration_disabled(self):
        self.ca.set_pv_value("CH1:SIM:STATE", "NOT_ESTOP")
        self.ca.set_pv_value("CH2:SIM:STATE", "E_STOP")
        # Need to process to update disabled status.
        # We don't want the db to process when disabled changes as this will write to the device.
        self.ca.process_pv("CALIB")
        self.ca.assert_that_pv_is("CALIB.STAT",
                                  self.ca.Alarms.DISABLE,
                                  timeout=1)

    @skip_if_devsim("No backdoor to state in devsim")
    def test_WHEN_both_channels_state_ESTOP_THEN_calibration_enabled(self):
        self.ca.set_pv_value("CH1:SIM:STATE", "E_STOP")
        self.ca.set_pv_value("CH2:SIM:STATE", "E_STOP")
        # Need to process to update disabled status.
        # We don't want the db to process when disabled changes as this will write to the device.
        self.ca.process_pv("CALIB")
        self.ca.assert_that_pv_is_not("CALIB.STAT",
                                      self.ca.Alarms.DISABLE,
                                      timeout=1)
コード例 #10
0
class IpsTests(unittest.TestCase):
    """
    Tests for the Ips IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(EMULATOR_NAME, DEVICE_PREFIX)
        # Some changes happen on the order of HEATER_WAIT_TIME seconds. Use a significantly longer timeout
        # to capture a few heater wait times plus some time for PVs to update.
        self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=HEATER_WAIT_TIME*10)

        # Wait for some critical pvs to be connected.
        for pv in ["MAGNET:FIELD:PERSISTENT", "FIELD", "FIELD:SP:RBV", "HEATER:STATUS"]:
            self.ca.assert_that_pv_exists(pv)

        # Ensure in the correct mode
        self.ca.set_pv_value("CONTROL:SP", "Remote & Unlocked")
        self.ca.set_pv_value("ACTIVITY:SP", "To Setpoint")

        # Don't run reset as the sudden change of state confuses the IOC's state machine. No matter what the initial
        # state of the device the SNL should be able to deal with it.
        # self._lewis.backdoor_run_function_on_device("reset")

        self.ca.set_pv_value("HEATER:WAITTIME", HEATER_WAIT_TIME)

        self.ca.set_pv_value("FIELD:RATE:SP", 10)
        # self.ca.assert_that_pv_is_number("FIELD:RATE:SP", 10)

        self.ca.process_pv("FIELD:SP")

        # Wait for statemachine to reach "at field" state before every test.
        self.ca.assert_that_pv_is("STATEMACHINE", "At field")

    def tearDown(self):
        # Wait for statemachine to reach "at field" state after every test.
        self.ca.assert_that_pv_is("STATEMACHINE", "At field")

        self.assertEqual(self._lewis.backdoor_get_from_device("quenched"), False)

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

    def _assert_field_is(self, field, check_stable=False):
        self.ca.assert_that_pv_is_number("FIELD", field, tolerance=TOLERANCE)
        self.ca.assert_that_pv_is_number("FIELD:USER", field, tolerance=TOLERANCE)
        if check_stable:
            self.ca.assert_that_pv_value_is_unchanged("FIELD", wait=30)
            self.ca.assert_that_pv_is_number("FIELD", field, tolerance=TOLERANCE, timeout=10)
            self.ca.assert_that_pv_is_number("FIELD:USER", field, tolerance=TOLERANCE, timeout=10)

    def _assert_heater_is(self, heater_state):
        self.ca.assert_that_pv_is("HEATER:STATUS:SP", "On" if heater_state else "Off")
        if heater_state:
            self.ca.assert_that_pv_is("HEATER:STATUS", "On",)
        else:
            self.ca.assert_that_pv_is_one_of("HEATER:STATUS", HEATER_OFF_STATES)

    def _set_and_check_persistent_mode(self, mode):
        self.ca.assert_setting_setpoint_sets_readback("YES" if mode else "NO", "PERSISTENT")

    @parameterized.expand(val for val in parameterized_list(TEST_VALUES))
    def test_GIVEN_persistent_mode_enabled_WHEN_magnet_told_to_go_to_field_setpoint_THEN_goes_to_that_setpoint_and_psu_ramps_to_zero(self, _, val):

        self._set_and_check_persistent_mode(True)

        # Field in the magnet already from persistent mode.
        persistent_field = float(self.ca.get_pv_value("MAGNET:FIELD:PERSISTENT"))

        # Set the new field. This will cause all of the following events based on the state machine.
        self.ca.set_pv_value("FIELD:SP", val)

        # PSU should be ramped to match the persistent field inside the magnet
        self._assert_field_is(persistent_field)
        self.ca.assert_that_pv_is("ACTIVITY", "To Setpoint")

        # Then it is safe to turn on the heater
        self._assert_heater_is(True)

        # Assert that value gets passed to device by SNL. SNL waits 30s for the heater to cool down/warm up
        # after being set.
        self._assert_field_is(val)

        # Now that the correct current is in the magnet, the SNL should turn the heater off
        self._assert_heater_is(False)

        # Now that the heater is off, can ramp down the PSU to zero (SNL waits some time for heater to be off before
        # ramping PSU to zero)
        self.ca.assert_that_pv_is_number("FIELD", 0, tolerance=TOLERANCE)  # PSU field
        self.ca.assert_that_pv_is_number("MAGNET:FIELD:PERSISTENT", val, tolerance=TOLERANCE)  # Persistent field
        self.ca.assert_that_pv_is_number("FIELD:USER", val, tolerance=TOLERANCE)  # User field should be tracking persistent field here
        self.ca.assert_that_pv_is("ACTIVITY", "To Zero")

        # ...And the magnet should now be in the right state!
        self.ca.assert_that_pv_is("STATEMACHINE", "At field")
        self.ca.assert_that_pv_is_number("MAGNET:FIELD:PERSISTENT", val, tolerance=TOLERANCE)

        # "User" field should take the value put in the setpoint, even when the actual field provided by the supply
        # drops to zero
        self.ca.assert_that_pv_is_number("FIELD", 0, tolerance=TOLERANCE)  # PSU field
        self.ca.assert_that_pv_is_number("MAGNET:FIELD:PERSISTENT", val, tolerance=TOLERANCE)  # Persistent field
        self.ca.assert_that_pv_is_number("FIELD:USER", val, tolerance=TOLERANCE)  # User field should be tracking persistent field here

    @parameterized.expand(val for val in parameterized_list(TEST_VALUES))
    def test_GIVEN_non_persistent_mode_WHEN_magnet_told_to_go_to_field_setpoint_THEN_goes_to_that_setpoint_and_psu_does_not_ramp_to_zero(self, _, val):

        self._set_and_check_persistent_mode(False)

        # Field in the magnet already from persistent mode.
        persistent_field = float(self.ca.get_pv_value("MAGNET:FIELD:PERSISTENT"))

        # Set the new field. This will cause all of the following events based on the state machine.
        self.ca.set_pv_value("FIELD:SP", val)

        # PSU should be ramped to match the persistent field inside the magnet (if there was one)
        self._assert_field_is(persistent_field)

        # Then it is safe to turn on the heater (the heater is explicitly switched on and we wait for it even if it
        # was already on out of an abundance of caution).
        self._assert_heater_is(True)

        # Assert that value gets passed to device by SNL. SNL waits 30s for the heater to cool down/warm up
        # after being set.
        self._assert_field_is(val)

        # ...And the magnet should now be in the right state!
        self.ca.assert_that_pv_is_number("MAGNET:FIELD:PERSISTENT", val, tolerance=TOLERANCE)

        # And the PSU should remain stable providing the required current/field
        self.ca.assert_that_pv_is("STATEMACHINE", "At field")
        self._assert_field_is(val, check_stable=True)

    @contextmanager
    def _backdoor_magnet_quench(self, reason="Test framework quench"):
        self._lewis.backdoor_run_function_on_device("quench", [reason])
        try:
            yield
        finally:
            # Get back out of the quenched state. This is because the tearDown method checks that magnet has not
            # quenched.
            self._lewis.backdoor_run_function_on_device("unquench")
            # Wait for IOC to notice quench state has gone away
            self.ca.assert_that_pv_alarm_is("STS:SYSTEM:FAULT", self.ca.Alarms.NONE)

    @parameterized.expand(field for field in parameterized_list(TEST_VALUES))
    def test_GIVEN_magnet_quenches_while_at_field_THEN_ioc_displays_this_quench_in_statuses(self, _, field):

        self._set_and_check_persistent_mode(False)
        self.ca.set_pv_value("FIELD:SP", field)
        self._assert_field_is(field)
        self.ca.assert_that_pv_is("STATEMACHINE", "At field")

        with self._backdoor_magnet_quench():
            self.ca.assert_that_pv_is("STS:SYSTEM:FAULT", "Quenched")
            self.ca.assert_that_pv_alarm_is("STS:SYSTEM:FAULT", self.ca.Alarms.MAJOR)
            self.ca.assert_that_pv_is("CONTROL", "Auto-Run-Down")
            self.ca.assert_that_pv_alarm_is("CONTROL", self.ca.Alarms.MAJOR)

            # The trip field should be the field at the point when the magnet quenched.
            self.ca.assert_that_pv_is_number("FIELD:TRIP", field, tolerance=TOLERANCE)

            # Field should be set to zero by emulator (mirroring what the field ought to do in the real device)
            self.ca.assert_that_pv_is_number("FIELD", 0, tolerance=TOLERANCE)
            self.ca.assert_that_pv_is_number("FIELD:USER", 0, tolerance=TOLERANCE)
            self.ca.assert_that_pv_is_number("MAGNET:FIELD:PERSISTENT", 0, tolerance=TOLERANCE)

    @parameterized.expand(val for val in parameterized_list(TEST_VALUES))
    def test_WHEN_inductance_set_via_backdoor_THEN_value_in_ioc_updates(self, _, val):
        self._lewis.backdoor_set_on_device("inductance", val)
        self.ca.assert_that_pv_is_number("MAGNET:INDUCTANCE", val, tolerance=TOLERANCE)

    @parameterized.expand(val for val in parameterized_list(TEST_VALUES))
    def test_WHEN_measured_current_set_via_backdoor_THEN_value_in_ioc_updates(self, _, val):
        self._lewis.backdoor_set_on_device("measured_current", val)
        self.ca.assert_that_pv_is_number("MAGNET:CURR:MEAS", val, tolerance=TOLERANCE)

    @parameterized.expand(val for val in parameterized_list(TEST_SWEEP_RATES))
    def test_WHEN_sweep_rate_set_THEN_sweep_rate_on_ioc_updates(self, _, val):
        self.ca.set_pv_value("FIELD:RATE:SP", val)
        self.ca.assert_that_pv_is_number("FIELD:RATE:SP", val, tolerance=TOLERANCE)
        self.ca.assert_that_pv_is_number("FIELD:RATE", val, tolerance=TOLERANCE)
        self.ca.assert_that_pv_alarm_is("FIELD:RATE", self.ca.Alarms.NONE)

    @parameterized.expand(activity_state for activity_state in parameterized_list(ACTIVITY_STATES))
    @unstable_test()
    def test_WHEN_activity_set_via_backdoor_to_clamped_THEN_alarm_major_ELSE_no_alarm(self, _, activity_state):
        self.ca.set_pv_value("ACTIVITY", activity_state)
        if activity_state == "Clamped":
            self.ca.assert_that_pv_alarm_is("ACTIVITY", "MAJOR")
        else:
            self.ca.assert_that_pv_alarm_is("ACTIVITY", "NO_ALARM")

    @parameterized.expand(control_command for control_command in parameterized_list(CONTROL_COMMANDS_WITH_VALUES))
    def test_WHEN_control_command_value_set_THEN_remote_unlocked_set(self, _, control_pv, set_value):
        self.ca.set_pv_value("CONTROL", "Local & Locked")
        self.ca.set_pv_value(control_pv, set_value)
        self.ca.assert_that_pv_is("CONTROL", "Remote & Unlocked")

    @parameterized.expand(control_pv for control_pv in parameterized_list(CONTROL_COMMANDS_WITHOUT_VALUES))
    def test_WHEN_control_command_processed_THEN_remote_unlocked_set(self, _, control_pv):
        self.ca.set_pv_value("CONTROL", "Local & Locked")
        self.ca.process_pv(control_pv)
        self.ca.assert_that_pv_is("CONTROL", "Remote & Unlocked")
コード例 #11
0
class HeliumRecoveryPLCTests(unittest.TestCase):
    """
    Tests for the FINS helium gas recovery PLC IOC.
    """
    def setUp(self):
        self._lewis, self._ioc = get_running_lewis_and_ioc(
            IOCS[0]["emulator"], DEVICE_PREFIX)
        self.ca = ChannelAccess(default_timeout=20, device_prefix=IOC_PREFIX)

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

    # The heartbeat and coldbox turbine speeds are tested separately despite storing 16 bit integers because it does
    # not have a calc record that divides the value by 10. The heartbeat is not tested with negative numbers because it
    # does not support them and does not need to. Because of that, it has no associated _RAW PV, and has a 0.5 second
    # scan rate regardless of the global scan rate, so it does not need to be manually processed by the test.
    @parameterized.expand(
        parameterized_list(
            zip(INT16_NO_CALC_PV_NAMES, INT16_NO_CALC_TEST_VALUES)))
    @skip_if_recsim("lewis backdoor not supported in recsim")
    def test_WHEN_int16_no_calc_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (pv_name, test_value))

        if pv_name != "HEARTBEAT":
            self.ca.process_pv("{}:_RAW".format(pv_name))

        self.ca.assert_that_pv_after_processing_is(pv_name, test_value)

    @parameterized.expand(
        parameterized_list(
            zip(INT16_NO_CALC_PV_NAMES, INT16_NO_CALC_TEST_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_int16_no_calc_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self.ca.set_pv_value("SIM:{}".format(pv_name), test_value)

        if pv_name != "HEARTBEAT":
            self.ca.process_pv("{}:_RAW".format(pv_name))

        self.ca.assert_that_pv_after_processing_is(pv_name, test_value)

    @parameterized.expand(
        parameterized_list(
            zip(INT16_NO_CALC_PV_NAMES, INT16_NO_CALC_TEST_VALUES)))
    @skip_if_recsim("lewis backdoor not supported in recsim")
    def test_WHEN_int16_no_calc_set_negative_value_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        if pv_name == "HEARTBEAT":
            self.skipTest(
                "HEARTBEAT does not have support for negative values")

        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (pv_name, -test_value))
        self.ca.process_pv("{}:_RAW".format(pv_name))
        self.ca.assert_that_pv_after_processing_is(pv_name, -test_value)

    @parameterized.expand(
        parameterized_list(
            zip(INT16_NO_CALC_PV_NAMES, INT16_NO_CALC_TEST_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_int16_no_calc_set_negative_value_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        if pv_name == "HEARTBEAT":
            self.skipTest(
                "HEARTBEAT does not have support for negative values")

        self.ca.set_pv_value("SIM:{}".format(pv_name), -test_value)
        self.ca.process_pv("{}:_RAW".format(pv_name))
        self.ca.assert_that_pv_after_processing_is(pv_name, -test_value)

    @parameterized.expand(
        parameterized_list(zip(INT16_PV_NAMES, INT16_TEST_VALUES)))
    @skip_if_recsim("lewis backdoor not supported in recsim")
    def test_WHEN_int16_value_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (pv_name, test_value))
        self.ca.process_pv("{}:_RAW".format(pv_name))
        self.ca.assert_that_pv_is(pv_name, test_value / 10)

    @parameterized.expand(
        parameterized_list(zip(INT16_PV_NAMES, INT16_TEST_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_int16_value_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self.ca.set_pv_value("SIM:{}".format(pv_name), test_value)
        self.ca.process_pv("{}:_RAW".format(pv_name))
        self.ca.assert_that_pv_is(pv_name, test_value / 10)

    @parameterized.expand(
        parameterized_list(zip(INT16_PV_NAMES, INT16_TEST_VALUES)))
    @skip_if_recsim("lewis backdoor not supported in recsim")
    def test_WHEN_int16_value_set_negative_value_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (pv_name, -test_value))
        self.ca.process_pv("{}:_RAW".format(pv_name))
        self.ca.assert_that_pv_is(pv_name, -test_value / 10)

    @parameterized.expand(
        parameterized_list(zip(INT16_PV_NAMES, INT16_TEST_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_int16_value_set_negative_value_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self.ca.set_pv_value("SIM:{}".format(pv_name), -test_value)
        self.ca.process_pv("{}:_RAW".format(pv_name))
        self.ca.assert_that_pv_is(pv_name, -test_value / 10)

    @parameterized.expand(
        parameterized_list(zip(DWORD_PV_NAMES, DWORD_TEST_VALUES)))
    @skip_if_recsim("lewis backdoor not supported in recsim")
    def test_WHEN_int32_value_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (pv_name, test_value))
        self.ca.assert_that_pv_after_processing_is(pv_name, test_value)

    @parameterized.expand(
        parameterized_list(zip(DWORD_PV_NAMES, DWORD_TEST_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_int32_value_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self.ca.set_pv_value("SIM:{}".format(pv_name), test_value)
        self.ca.assert_that_pv_after_processing_is(pv_name, test_value)

    @parameterized.expand(
        parameterized_list(zip(ANALOGUE_IN_PV_NAMES, ANALOGUE_TEST_VALUES)))
    @skip_if_recsim("lewis backdoor not supported in recsim")
    def test_WHEN_analogue_value_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (pv_name, test_value))
        self.ca.assert_that_pv_after_processing_is_number(
            pv_name, test_value, 0.001)

    @parameterized.expand(
        parameterized_list(zip(ANALOGUE_IN_PV_NAMES, ANALOGUE_TEST_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_analogue_value_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        self.ca.set_pv_value("SIM:{}".format(pv_name), test_value)
        self.ca.assert_that_pv_after_processing_is_number(
            pv_name, test_value, 0.001)

    @parameterized.expand(parameterized_list(AUTO_MANUAL_PV_NAMES))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_auto_manual_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name):
        self.ca.assert_that_pv_after_processing_is(pv_name, "MANUAL")

        self._lewis.backdoor_run_function_on_device("set_memory", (pv_name, 2))
        self.ca.assert_that_pv_after_processing_is(pv_name, "AUTOMATIC")

        self._lewis.backdoor_run_function_on_device("set_memory", (pv_name, 1))
        self.ca.assert_that_pv_after_processing_is(pv_name, "MANUAL")

    @parameterized.expand(parameterized_list(AUTO_MANUAL_PV_NAMES))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_auto_manual_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name):
        self.ca.assert_that_pv_after_processing_is(pv_name, "MANUAL")

        self.ca.set_pv_value("SIM:{}".format(pv_name), 1)
        self.ca.assert_that_pv_after_processing_is(pv_name, "AUTOMATIC")

        self.ca.set_pv_value("SIM:{}".format(pv_name), 0)
        self.ca.assert_that_pv_after_processing_is(pv_name, "MANUAL")

    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_liquid_nitrogen_status_set_backdoor_THEN_ioc_read_correctly(
            self):
        self.ca.assert_that_pv_after_processing_is("LIQUID_NITROGEN:STATUS",
                                                   "Not selected")

        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("LIQUID_NITROGEN:STATUS", 2))
        self.ca.assert_that_pv_after_processing_is("LIQUID_NITROGEN:STATUS",
                                                   "Selected")

        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("LIQUID_NITROGEN:STATUS", 1))
        self.ca.assert_that_pv_after_processing_is("LIQUID_NITROGEN:STATUS",
                                                   "Not selected")

    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_liquid_nitrogen_status_set_sim_pv_THEN_ioc_read_correctly(
            self):
        self.ca.assert_that_pv_after_processing_is("LIQUID_NITROGEN:STATUS",
                                                   "Not selected")

        self.ca.set_pv_value("SIM:LIQUID_NITROGEN:STATUS", 1)
        self.ca.assert_that_pv_after_processing_is("LIQUID_NITROGEN:STATUS",
                                                   "Selected")

        self.ca.set_pv_value("SIM:LIQUID_NITROGEN:STATUS", 0)
        self.ca.assert_that_pv_after_processing_is("LIQUID_NITROGEN:STATUS",
                                                   "Not selected")

    @parameterized.expand(parameterized_list(CONTROL_VALVE_POSITION_VALUES))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_CNTRL_VALVE_120_position_set_backdoor_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = CONTROL_VALVE_POSITION_VALUES.index(test_value) + 1
        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("CNTRL_VALVE_120:POSITION", index_test_value))
        self.ca.assert_that_pv_after_processing_is("CNTRL_VALVE_120:POSITION",
                                                   test_value)

    @parameterized.expand(parameterized_list(CONTROL_VALVE_POSITION_VALUES))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_CNTRL_VALVE_120_position_set_sim_pv_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = CONTROL_VALVE_POSITION_VALUES.index(test_value)
        self.ca.set_pv_value("SIM:CNTRL_VALVE_120:POSITION", index_test_value)
        self.ca.assert_that_pv_after_processing_is("CNTRL_VALVE_120:POSITION",
                                                   test_value)

    @parameterized.expand(parameterized_list(CONTROL_VALVE_POSITION_VALUES))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_CNTRL_VALVE_121_position_set_backdoor_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = CONTROL_VALVE_POSITION_VALUES.index(test_value) + 1
        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("CNTRL_VALVE_121:POSITION", index_test_value))
        self.ca.assert_that_pv_after_processing_is("CNTRL_VALVE_121:POSITION",
                                                   test_value)

    @parameterized.expand(parameterized_list(CONTROL_VALVE_POSITION_VALUES))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_CNTRL_VALVE_121_position_set_sim_pv_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = CONTROL_VALVE_POSITION_VALUES.index(test_value)
        self.ca.set_pv_value("SIM:CNTRL_VALVE_121:POSITION", index_test_value)
        self.ca.assert_that_pv_after_processing_is("CNTRL_VALVE_121:POSITION",
                                                   test_value)

    @parameterized.expand(parameterized_list(PURIFIER_STATUS_VALUES))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_purifier_status_set_backdoor_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = PURIFIER_STATUS_VALUES.index(test_value) + 1
        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("PURIFIER:STATUS", index_test_value))
        self.ca.assert_that_pv_after_processing_is("PURIFIER:STATUS",
                                                   test_value)

    @parameterized.expand(parameterized_list(PURIFIER_STATUS_VALUES))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_purifier_status_set_sim_pv_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = PURIFIER_STATUS_VALUES.index(test_value)
        self.ca.set_pv_value("SIM:PURIFIER:STATUS", index_test_value)
        self.ca.assert_that_pv_after_processing_is("PURIFIER:STATUS",
                                                   test_value)

    @parameterized.expand(parameterized_list(COMPRESSOR_STATUS_VALUES))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_compressor_status_set_backdoor_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = COMPRESSOR_STATUS_VALUES.index(test_value) + 1
        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("CMPRSSR:STATUS", index_test_value))
        self.ca.assert_that_pv_after_processing_is("CMPRSSR:STATUS",
                                                   test_value)

    @parameterized.expand(parameterized_list(COMPRESSOR_STATUS_VALUES))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_compressor_status_set_sim_pv_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = COMPRESSOR_STATUS_VALUES.index(test_value)
        self.ca.set_pv_value("SIM:CMPRSSR:STATUS", index_test_value)
        self.ca.assert_that_pv_after_processing_is("CMPRSSR:STATUS",
                                                   test_value)

    @parameterized.expand(parameterized_list(COLDBOX_STATUS_VALUES))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_coldbox_status_set_backdoor_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = COLDBOX_STATUS_VALUES.index(test_value) + 1
        self._lewis.backdoor_run_function_on_device(
            "set_memory", ("COLDBOX:STATUS", index_test_value))
        self.ca.assert_that_pv_after_processing_is("COLDBOX:STATUS",
                                                   test_value)

    @parameterized.expand(parameterized_list(COLDBOX_STATUS_VALUES))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_coldbox_status_set_sim_pv_THEN_ioc_read_correctly(
            self, _, test_value):
        index_test_value = COLDBOX_STATUS_VALUES.index(test_value)
        self.ca.set_pv_value("SIM:COLDBOX:STATUS", index_test_value)
        self.ca.assert_that_pv_after_processing_is("COLDBOX:STATUS",
                                                   test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(VALVE_STATUS_PVS, VALVE_STATUS_VALUES)))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_valve_status_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        index_test_value = VALVE_STATUS_VALUES.index(test_value) + 1
        self._lewis.backdoor_run_function_on_device(
            "set_memory", (pv_name, index_test_value))
        self.ca.assert_that_pv_after_processing_is(pv_name, test_value)

    @parameterized.expand(
        parameterized_list(
            itertools.product(VALVE_STATUS_PVS, VALVE_STATUS_VALUES)))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_valve_status_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name, test_value):
        index_test_value = VALVE_STATUS_VALUES.index(test_value)
        self.ca.set_pv_value("SIM:{}".format(pv_name), index_test_value)
        self.ca.assert_that_pv_after_processing_is(pv_name, test_value)

    # Liquefier alarms are tested separately because in the memory map they are unsigned integers. The C driver does
    # not support unsigned 16 bit integers directly, but the value is put into a longin record, which should display
    # the unsigned 16 bit integer correctly. There are two mbbiDirect records that read from the two memory locations
    # that store alarms in the form of 16 bit integers. These tests then check that the bi records that read from the
    # mbbiDirect recors work properly.

    @parameterized.expand(parameterized_list(LIQUEFIER_ALARMS))
    @skip_if_recsim("lewis backdoor not available in recsim")
    def test_WHEN_liquefier_alarm_set_backdoor_THEN_ioc_read_correctly(
            self, _, pv_name):
        alarm_index = LIQUEFIER_ALARMS.index(pv_name)
        mbbi_direct_pv = HeliumRecoveryPLCTests._get_liquefier_hardware_pv(
            alarm_index)
        test_value = HeliumRecoveryPLCTests._get_alarm_test_value(
            mbbi_direct_pv, alarm_index)

        raw_pv = "{}:_RAW".format(mbbi_direct_pv)
        full_pv_name = "{}:ALARM".format(pv_name)

        self.ca.process_pv(raw_pv)
        self.ca.assert_that_pv_is(full_pv_name, "OK")

        self._lewis.backdoor_run_function_on_device(
            "set_memory", (mbbi_direct_pv, test_value))
        self.ca.process_pv(raw_pv)
        self.ca.assert_that_pv_is(full_pv_name, "IN ALARM")

        self._lewis.backdoor_run_function_on_device("set_memory",
                                                    (mbbi_direct_pv, 0))
        self.ca.process_pv(raw_pv)
        self.ca.assert_that_pv_is(full_pv_name, "OK")

    @parameterized.expand(parameterized_list(LIQUEFIER_ALARMS))
    @skip_if_devsim("sim pvs not available in devsim")
    def test_WHEN_liquefier_alarm_set_sim_pv_THEN_ioc_read_correctly(
            self, _, pv_name):
        alarm_index = LIQUEFIER_ALARMS.index(pv_name)
        mbbi_direct_pv = HeliumRecoveryPLCTests._get_liquefier_hardware_pv(
            alarm_index)
        test_value = HeliumRecoveryPLCTests._get_alarm_test_value(
            mbbi_direct_pv, alarm_index)

        raw_pv = "{}:_RAW".format(mbbi_direct_pv)
        full_pv_name = "{}:ALARM".format(pv_name)

        self.ca.process_pv(raw_pv)
        self.ca.assert_that_pv_is(full_pv_name, "OK")

        self.ca.set_pv_value("SIM:{}".format(mbbi_direct_pv), test_value)
        self.ca.process_pv(raw_pv)
        self.ca.assert_that_pv_is(full_pv_name, "IN ALARM")

        self.ca.set_pv_value("SIM:{}".format(mbbi_direct_pv), 0)
        self.ca.process_pv(raw_pv)
        self.ca.assert_that_pv_is(full_pv_name, "OK")

    @staticmethod
    def _get_liquefier_hardware_pv(alarm_index):
        """
        The 24 alarm bi records get their value from one of two mbbi records. The first 15 get their value from the
        first, and the rest from the second one.

        Args:
            alarm_index (int): Index of the pv name in the list of alarm PVs.

        Returns (string): The name of the mbbiDirect record from where the alarm bi record gets its value.
        """
        if alarm_index < 15:
            mbbi_direct_pv = "LIQUEFIER:_ALARM1"
        else:
            mbbi_direct_pv = "LIQUEFIER:_ALARM2"

        return mbbi_direct_pv

    @staticmethod
    def _get_alarm_test_value(mbbi_direct_pv, alarm_index):
        """
        The alarm bi records get their valuer from 16 bit number in mbbiDirect records, and we need to compute the
        correct value for the mbbiDirect such that the right alarm bi record is 1 and not 0.

        Args:
            mbbi_direct_pv (string): The name of the mbbiDirect record from where the alarm bi records gets their value.
            alarm_index (int): Index of the pv name in the list of alarm PVs

        Returns:
            int: The corect test value for the mbbi record, such that the bi record being tested will have a value of
                1, or be in alarm.
        """
        if mbbi_direct_pv == "LIQUEFIER:_ALARM1":
            # We add 1 to the index because the first bit int LIQUEFIER:_ALARM1 is not used
            return 2**(alarm_index + 1)
        elif mbbi_direct_pv == "LIQUEFIER:_ALARM2":
            # We subtract 15 because the bi record for ALARM2 are after the 15 bi records for ALARM1 in the liquefier
            # alarms list.
            return 2**(alarm_index - 15)