class KepcoTests(object): """ Tests for the KEPCO. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc("kepco", DEVICE_PREFIX) self.ca = ChannelAccess(default_timeout=30, device_prefix=DEVICE_PREFIX) self._lewis.backdoor_run_function_on_device("reset") self.ca.assert_that_pv_exists("VOLTAGE", timeout=30) reset_calibration_file(self.ca, "default_calib.dat") def _write_voltage(self, expected_voltage): self._lewis.backdoor_set_on_device("voltage", expected_voltage) self._ioc.set_simulated_value("SIM:VOLTAGE", expected_voltage) def _write_current(self, expected_current): self._lewis.backdoor_set_on_device("current", expected_current) self._ioc.set_simulated_value("SIM:CURRENT", expected_current) def _set_IDN(self, expected_idn_no_firmware, expected_firmware): self._lewis.backdoor_set_on_device("idn_no_firmware", expected_idn_no_firmware) self._lewis.backdoor_set_on_device("firmware", expected_firmware) expected_idn = "{}{}".format(expected_idn_no_firmware, str(expected_firmware))[:39] # EPICS limited to 40 chars self._ioc.set_simulated_value("SIM:IDN", expected_idn) self._ioc.set_simulated_value("SIM:FIRMWARE", str(expected_firmware)) # Both firmware and IDN are passive so must be updated self.ca.process_pv("FIRMWARE") self.ca.process_pv("IDN") return expected_idn def _set_output_mode(self, expected_output_mode): self._lewis.backdoor_set_on_device("output_mode", expected_output_mode) self._ioc.set_simulated_value("SIM:OUTPUTMODE", expected_output_mode) def _set_output_status(self, expected_output_status): self._lewis.backdoor_set_on_device("output_status", expected_output_status) def test_GIVEN_voltage_set_WHEN_read_THEN_voltage_is_as_expected(self): expected_voltage = 1.2 self._write_voltage(expected_voltage) self.ca.assert_that_pv_is("VOLTAGE", expected_voltage) def test_GIVEN_current_set_WHEN_read_THEN_current_is_as_expected(self): expected_current = 1.5 self._write_current(expected_current) self.ca.assert_that_pv_is("CURRENT", expected_current) def test_GIVEN_setpoint_voltage_set_WHEN_read_THEN_setpoint_voltage_is_as_expected(self): # Get current Voltage current_voltage = self.ca.get_pv_value("VOLTAGE") # Set new Voltage via SP self.ca.set_pv_value("VOLTAGE:SP", current_voltage + 5) # Check SP RBV matches new current self.ca.assert_that_pv_is("VOLTAGE:SP:RBV", current_voltage + 5) @parameterized.expand(parameterized_list([-5.1, 7.8])) def test_GIVEN_setpoint_current_set_WHEN_read_THEN_setpoint_current_is_as_expected(self, _, expected_current): self.ca.set_pv_value("CURRENT:SP", expected_current) # Check SP RBV matches new current self.ca.assert_that_pv_is("CURRENT:SP:RBV", expected_current) def test_GIVEN_output_mode_set_WHEN_read_THEN_output_mode_is_as_expected(self): expected_output_mode_flag = UnitFlags.CURRENT expected_output_mode_str = OutputMode.CURRENT self._set_output_mode(expected_output_mode_flag) # Check OUTPUT MODE matches new OUTPUT MODE self.ca.assert_that_pv_is("OUTPUTMODE", expected_output_mode_str) def test_GIVEN_output_status_set_WHEN_read_THEN_output_STATUS_is_as_expected(self): expected_output_status_flag = UnitFlags.ON expected_output_status_str = Status.ON self.ca.set_pv_value("OUTPUTSTATUS:SP", expected_output_status_flag) self.ca.assert_that_pv_is("OUTPUTSTATUS:SP:RBV", expected_output_status_str) @parameterized.expand(parameterized_list(IDN_LIST)) def test_GIVEN_idn_set_WHEN_read_THEN_idn_is_as_expected(self, _, idn_no_firmware, firmware): expected_idn = self._set_IDN(idn_no_firmware, firmware) self.ca.process_pv("IDN") self.ca.assert_that_pv_is("IDN", expected_idn) @skip_if_recsim("In rec sim you can not diconnect the device") def test_GIVEN_diconnected_WHEN_read_THEN_alarms_on_readbacks(self): self._lewis.backdoor_set_on_device("connected", False) self.ca.assert_that_pv_alarm_is("OUTPUTMODE", self.ca.Alarms.INVALID) self.ca.assert_that_pv_alarm_is("CURRENT", self.ca.Alarms.INVALID) self.ca.assert_that_pv_alarm_is("VOLTAGE", self.ca.Alarms.INVALID) def _test_ramp_to_target(self, start_current, target_current, ramp_rate, step_number, wait_between_changes): self._write_current(start_current) self.ca.set_pv_value("CURRENT:SP", start_current) self.ca.assert_that_pv_is("CURRENT:SP:RBV", start_current) self.ca.set_pv_value("RAMP:RATE:SP", ramp_rate) self.ca.set_pv_value("RAMP:STEPS:SP", step_number) self.ca.set_pv_value("RAMPON:SP", "ON") self.ca.set_pv_value("CURRENT:SP", target_current, sleep_after_set=0.0) if start_current < target_current: self.ca.assert_that_pv_value_is_increasing("CURRENT:SP:RBV", wait=wait_between_changes) else: self.ca.assert_that_pv_value_is_decreasing("CURRENT:SP:RBV", wait=wait_between_changes) self.ca.assert_that_pv_is("RAMPING", "YES") # Device stops ramping when it gets to target self.ca.assert_that_pv_is("CURRENT:SP:RBV", target_current, timeout=40) self._write_current(target_current) self.ca.assert_that_pv_is("RAMPING", "NO") self.ca.assert_that_pv_value_is_unchanged("CURRENT:SP:RBV", wait=wait_between_changes) self.ca.set_pv_value("RAMPON:SP", "OFF") def test_GIVEN_rampon_WHEN_target_set_THEN_current_ramps_to_target(self): self._test_ramp_to_target(1, 2, 2, 20, 7) def test_GIVEN_rampon_WHEN_target_set_with_different_step_rate_THEN_current_ramps_to_target_more_finely(self): self._test_ramp_to_target(4, 3, 2, 60, 2) @parameterized.expand(parameterized_list(IDN_LIST)) def test_GIVEN_idn_set_AND_firmware_set_THEN_firmware_pv_correct(self, _, idn_no_firmware, firmware): self._set_IDN(idn_no_firmware, firmware) self.ca.process_pv("FIRMWARE") self.ca.assert_that_pv_is("FIRMWARE", firmware) @parameterized.expand(parameterized_list([ ("default_calib.dat", 100, 100), ("field_double_amps.dat", 100, 50), ])) @skip_if_recsim("Calibration lookup does not work in recsim") def test_GIVEN_calibration_WHEN_field_set_THEN_current_as_expected(self, _, calibration_file, field, expected_current): with use_calibration_file(self.ca, calibration_file, "default_calib.dat"): self.ca.set_pv_value("FIELD:SP", field) self.ca.assert_that_pv_is("FIELD:SP:RBV", field) self.ca.assert_that_pv_is("CURRENT:SP", expected_current) self.ca.assert_that_pv_is("CURRENT:SP:RBV", expected_current) @parameterized.expand(parameterized_list([ ("default_calib.dat", 100, 100), ("field_double_amps.dat", 100, 200), ])) @skip_if_recsim("Calibration lookup does not work in recsim") def test_GIVEN_calibration_WHEN_current_set_THEN_field_as_expected(self, _, calibration_file, current, expected_field): with use_calibration_file(self.ca, calibration_file, "default_calib.dat"): self._write_current(current) self.ca.assert_that_pv_is("CURRENT", current) self.ca.assert_that_pv_is("FIELD", expected_field) @skip_if_recsim("Lewis not available in recsim") def test_WHEN_sending_setpoint_THEN_only_one_setpoint_sent(self): self._lewis.backdoor_set_and_assert_set("current_set_count", 0) self.ca.set_pv_value("CURRENT:SP", 100) self._lewis.assert_that_emulator_value_is("current_set_count", 1) # Wait a short time and make sure count is not being incremented again later. time.sleep(5) self._lewis.assert_that_emulator_value_is("current_set_count", 1)
class InstronStressRigTests(unittest.TestCase): """ Tests for the Instron IOC. """ def _change_channel(self, name): # Setpoint is zero-indexed self.ca.set_pv_value("CHANNEL:SP", CHANNELS[name] - 1) self.ca.assert_that_pv_is("CHANNEL.RVAL", CHANNELS[name]) self.ca.assert_that_pv_alarm_is("CHANNEL", self.ca.Alarms.NONE) self.ca.assert_that_pv_is("CHANNEL:GUI", name) self.ca.assert_that_pv_alarm_is("CHANNEL:GUI", self.ca.Alarms.NONE) def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "instron_stress_rig", DEVICE_PREFIX) self.ca = ChannelAccess(15, device_prefix=DEVICE_PREFIX) self.ca.assert_that_pv_exists("CHANNEL", timeout=30) # Can't use lewis backdoor commands in recsim # All of the below commands apply to devsim only. if not IOCRegister.uses_rec_sim: # Reinitialize the emulator state self._lewis.backdoor_command(["device", "reset"]) self._lewis.backdoor_set_on_device("status", 7680) for index, chan_type2 in enumerate((3, 2, 4)): self._lewis.backdoor_command([ "device", "set_channel_param", str(index + 1), "channel_type", str(chan_type2) ]) self.ca.assert_that_pv_is("CHANNEL:SP.ZRST", "Position") self._change_channel("Position") # Ensure the rig is stopped self._lewis.backdoor_command(["device", "movement_type", "0"]) self.ca.assert_that_pv_is("GOING", "NO") # Ensure stress area and strain length are sensible values (i.e. not zero) self._lewis.backdoor_command( ["device", "set_channel_param", "2", "area", "10"]) self._lewis.backdoor_command( ["device", "set_channel_param", "3", "length", "10"]) self.ca.assert_that_pv_is_number("STRESS:AREA", 10, tolerance=0.001) self.ca.assert_that_pv_is_number("STRAIN:LENGTH", 10, tolerance=0.001) # Ensure that all the scales are sensible values (i.e. not zero) for index, channel in enumerate(POS_STRESS_STRAIN, 1): self._lewis.backdoor_command( ["device", "set_channel_param", str(index), "scale", "10"]) self.ca.assert_that_pv_is_number(channel + ":SCALE", 10, tolerance=0.001) # Always set the waveform generator to run for lots of cycles so it only stops if we want it to self.ca.set_pv_value(quart_prefixed("CYCLE:SP"), LOTS_OF_CYCLES) @skip_if_recsim("In rec sim we can not set the code easily") def test_WHEN_the_rig_has_no_error_THEN_the_status_is_ok(self): self._lewis.backdoor_set_on_device("status", 7680) self.ca.assert_that_pv_is("STAT:DISP", "System OK") self.ca.assert_that_pv_alarm_is("STAT:DISP", ChannelAccess.Alarms.NONE) @skip_if_recsim("In rec sim we can not set the code easily") def test_WHEN_the_rig_has_other_no_error_THEN_the_status_is_ok(self): self._lewis.backdoor_set_on_device("status", 0) self.ca.assert_that_pv_is("STAT:DISP", "System OK") self.ca.assert_that_pv_alarm_is("STAT:DISP", ChannelAccess.Alarms.NONE) @skip_if_recsim("In rec sim we can not set the code easily") def test_WHEN_the_rig_has_error_THEN_the_status_is_emergency_stop_pushed( self): code_and_errors = [([0, 1, 1, 0], "Emergency stop pushed"), ([0, 0, 0, 1], "Travel limit exceeded"), ([0, 0, 1, 0], "Power amplifier too hot"), ([0, 1, 1, 0], "Emergency stop pushed"), ([0, 1, 0, 1], "Invalid status from rig"), ([0, 1, 0, 0], "Invalid status from rig"), ([0, 1, 1, 0], "Emergency stop pushed"), ([0, 1, 1, 1], "Oil too hot"), ([1, 0, 0, 0], "Oil level too low"), ([1, 0, 0, 1], "Motor too hot"), ([1, 0, 1, 0], "Oil pressure too high"), ([1, 0, 1, 1], "Oil pressure too low"), ([1, 1, 0, 0], "Manifold/pump blocked"), ([1, 1, 0, 1], "Oil level going too low"), ([1, 1, 1, 0], "Manifold low pressure")] for code, error in code_and_errors: code_val = code[0] * 2**12 code_val += code[1] * 2**11 code_val += code[2] * 2**10 code_val += code[3] * 2**9 self._lewis.backdoor_set_on_device("status", code_val) self.ca.assert_that_pv_is("STAT:DISP", error, msg="code set {0} = {code_val}".format( code, code_val=code_val)) self.ca.assert_that_pv_alarm_is("STAT:DISP", ChannelAccess.Alarms.MAJOR) def test_WHEN_the_rig_is_initialized_THEN_it_is_not_going(self): self.ca.assert_that_pv_is("GOING", "NO") def test_WHEN_the_rig_is_initialized_THEN_it_is_not_panic_stopping(self): self.ca.assert_that_pv_is("PANIC:SP", "READY") def test_WHEN_the_rig_is_initialized_THEN_it_is_not_stopping(self): self.ca.assert_that_pv_is("STOP:SP", "READY") def test_that_the_rig_is_not_normally_in_control_mode(self): self.ca.assert_that_pv_is("STOP:SP", "READY") def test_WHEN_init_sequence_run_THEN_waveform_ramp_is_set_the_status_is_ok( self): for chan in POS_STRESS_STRAIN: self.ca.set_pv_value("{0}:RAMP:WFTYP:SP".format(chan), 0) self.ca.set_pv_value("INIT", 1) for chan in POS_STRESS_STRAIN: self.ca.assert_that_pv_is("{0}:RAMP:WFTYP".format(chan), RAMP_WAVEFORM_TYPES[3]) def _switch_to_position_channel_and_change_setpoint(self): # It has to be big or the set point will be reached before the test completes _big_set_point = 999999999999 # Select position as control channel self._change_channel("Position") # Change the setpoint so that movement can be started self.ca.set_pv_value("POS:SP", _big_set_point) self.ca.assert_that_pv_is_number("POS:SP", _big_set_point, tolerance=1) self.ca.assert_that_pv_is_number("POS:SP:RBV", _big_set_point, tolerance=1) @skip_if_recsim("Dynamic behaviour not captured in RECSIM") def test_WHEN_going_and_then_stopping_THEN_going_pv_reflects_the_expected_state( self): self.ca.assert_that_pv_is("GOING", "NO") self._switch_to_position_channel_and_change_setpoint() self.ca.set_pv_value("MOVE:GO:SP", 1) self.ca.assert_that_pv_is("GOING", "YES") self.ca.set_pv_value("STOP:SP", 1) self.ca.assert_that_pv_is("GOING", "NO") self.ca.set_pv_value("STOP:SP", 0) @skip_if_recsim("Dynamic behaviour not captured in RECSIM") def test_WHEN_going_and_then_panic_stopping_THEN_going_pv_reflects_the_expected_state( self): self.ca.assert_that_pv_is("GOING", "NO") self._switch_to_position_channel_and_change_setpoint() self.ca.set_pv_value("MOVE:GO:SP", 1) self.ca.assert_that_pv_is("GOING", "YES") self.ca.set_pv_value("PANIC:SP", 1) self.ca.assert_that_pv_is("GOING", "NO") self.ca.set_pv_value("PANIC:SP", 0) @skip_if_recsim("In rec sim this test fails") def test_WHEN_arbitrary_command_Q22_is_sent_THEN_the_response_is_a_status_code( self): self.ca.set_pv_value("ARBITRARY:SP", "Q22") # Assert that the response to Q22 is a status code self.ca.assert_that_pv_is_within_range("ARBITRARY", min_value=0, max_value=65535) @skip_if_recsim("In rec sim this test fails") def test_WHEN_arbitrary_command_Q300_is_sent_THEN_the_response_is_a_number_between_1_and_3( self): self.ca.set_pv_value("ARBITRARY:SP", "Q300") # Assert that the response to Q300 is between 1 and 3 self.ca.assert_that_pv_is_within_range("ARBITRARY", min_value=1, max_value=3) @skip_if_recsim("In rec sim this test fails") def test_WHEN_arbitrary_command_C4_is_sent_THEN_Q4_gives_back_the_value_that_was_just_set( self): def _set_and_check(value): # Put the record into a non-alarm state. This is needed so that we can wait until the record is in alarm # later, when we do a command which (expectedly) puts the record into a timeout alarm. self.ca.set_pv_value("ARBITRARY:SP", "Q4,1") self.ca.assert_that_pv_alarm_is("ARBITRARY", self.ca.Alarms.NONE) self.ca.set_pv_value("ARBITRARY:SP", "C4,1," + str(value)) self.ca.assert_that_pv_is("ARBITRARY:SP", "C4,1," + str(value)) # No response from arbitrary command causes record to be TIMEOUT INVALID - this is expected. self.ca.assert_that_pv_alarm_is("ARBITRARY", self.ca.Alarms.INVALID) self.ca.set_pv_value("ARBITRARY:SP", "Q4,1") self.ca.assert_that_pv_is_number("ARBITRARY", value, tolerance=0.001, timeout=60) for v in [0, 1, 0]: _set_and_check(v) def test_WHEN_control_channel_is_requested_THEN_an_allowed_value_is_returned( self): self.ca.assert_that_pv_is_one_of("CHANNEL", CHANNELS.keys()) @skip_if_recsim("In rec sim this test fails") def test_WHEN_control_channel_setpoint_is_requested_THEN_it_is_one_of_the_allowed_values( self): self.ca.assert_that_pv_is_one_of("CHANNEL:SP", CHANNELS.keys()) @skip_if_recsim("In rec sim this test fails") def test_WHEN_the_control_channel_is_set_THEN_the_readback_contains_the_value_that_was_just_set( self): for channel in CHANNELS.keys(): # change channel function contains the relevant assertions. self._change_channel(channel) def test_WHEN_the_step_time_for_various_channels_is_set_as_an_integer_THEN_the_readback_contains_the_value_that_was_just_set( self): for chan, val in [("POS", 123), ("STRESS", 456), ("STRAIN", 789)]: pv_name = chan + ":STEP:TIME" self.ca.assert_setting_setpoint_sets_readback(val, pv_name) def test_WHEN_the_step_time_for_various_channels_is_set_as_a_float_THEN_the_readback_contains_the_value_that_was_just_set( self): for chan, val in [("POS", 111.111), ("STRESS", 222.222), ("STRAIN", 333.333)]: pv_name = chan + ":STEP:TIME" self.ca.assert_setting_setpoint_sets_readback(val, pv_name) def test_WHEN_the_ramp_waveform_for_a_channel_is_set_THEN_the_readback_contains_the_value_that_was_just_set( self): pv_name = "{0}:RAMP:WFTYP" for chan in POS_STRESS_STRAIN: for set_value, return_value in enumerate(RAMP_WAVEFORM_TYPES): self.ca.assert_setting_setpoint_sets_readback( set_value, pv_name.format(chan), expected_value=return_value) def test_WHEN_the_ramp_amplitude_for_a_channel_is_set_as_an_integer_THEN_the_readback_contains_the_value_that_was_just_set( self): for chan in POS_STRESS_STRAIN: for val in [0, 10, 1000, 1000000]: pv_name = chan + ":RAW:SP" pv_name_rbv = pv_name + ":RBV" self.ca.assert_setting_setpoint_sets_readback( val, readback_pv=pv_name_rbv, set_point_pv=pv_name) def test_WHEN_the_ramp_amplitude_for_a_channel_is_set_as_a_float_THEN_the_readback_contains_the_value_that_was_just_set( self): for chan in POS_STRESS_STRAIN: for val in [1.0, 5.5, 1.000001, 9.999999, 10000.1]: pv_name = chan + ":RAW:SP" pv_name_rbv = pv_name + ":RBV" self.ca.assert_setting_setpoint_sets_readback( val, readback_pv=pv_name_rbv, set_point_pv=pv_name) @skip_if_recsim("In rec sim this test fails") def test_WHEN_the_setpoint_for_a_channel_is_set_THEN_the_readback_contains_the_value_that_was_just_set( self): def _set_and_check(chan, value): self.ca.set_pv_value(chan + ":SP", value) self.ca.assert_that_pv_is_number(chan + ":SP", value, tolerance=0.001) self.ca.assert_that_pv_is_number(chan + ":SP:RBV", value, tolerance=0.05, timeout=30) for chan in POS_STRESS_STRAIN: for i in [1.0, 123.456, 555.555, 1000]: _set_and_check(chan, i) def test_WHEN_channel_tolerance_is_set_THEN_it_changes_limits_on_SP_RBV( self): for chan in POS_STRESS_STRAIN: sp_val = 1 self.ca.set_pv_value(chan + ":SP", sp_val) for val in [0.1, 1.0, 2.5]: pv_name = chan + ":TOLERANCE" pv_name_high = chan + ":SP:RBV.HIGH" pv_name_low = chan + ":SP:RBV.LOW" self.ca.assert_setting_setpoint_sets_readback( val, readback_pv=pv_name_high, set_point_pv=pv_name, expected_value=val + sp_val, expected_alarm=None) self.ca.assert_setting_setpoint_sets_readback( val, readback_pv=pv_name_low, set_point_pv=pv_name, expected_value=sp_val - val, expected_alarm=None) @skip_if_recsim("Alarms not properly emulated in recsim") def test_GIVEN_a_big_tolerance_WHEN_the_setpoint_is_set_THEN_the_setpoint_has_no_alarms( self): def _set_and_check(chan, value): self.ca.set_pv_value(chan + ":SP", value) self.ca.set_pv_value(chan + ":TOLERANCE", 9999) self.ca.assert_that_pv_alarm_is(chan + ":SP:RBV", ChannelAccess.Alarms.NONE) for chan in POS_STRESS_STRAIN: for i in [0.123, 567]: _set_and_check(chan, i) @skip_if_recsim("Alarms not properly emulated in recsim") def test_GIVEN_a_tolerance_of_minus_one_WHEN_the_setpoint_is_set_THEN_the_setpoint_readback_has_alarms( self): def _set_and_check(chan, value): self.ca.set_pv_value(chan + ":SP", value) self.ca.set_pv_value(chan + ":TOLERANCE", -1) self.ca.assert_that_pv_alarm_is(chan + ":SP:RBV", ChannelAccess.Alarms.MINOR) for chan in POS_STRESS_STRAIN: for i in [0.234, 789]: _set_and_check(chan, i) @skip_if_recsim("In rec sim this test fails") def test_WHEN_ioc_gets_a_raw_position_reading_from_the_device_THEN_it_is_converted_correctly( self): for chan_scale in [0.1, 10.0]: self._lewis.backdoor_command( ["device", "set_channel_param", "1", "scale", str(chan_scale)]) self.ca.assert_that_pv_is("POS:SCALE", chan_scale) for raw_value in [0, 123]: self._lewis.backdoor_command([ "device", "set_channel_param", "1", "value", str(raw_value) ]) self.ca.assert_that_pv_is_number("POS:RAW", raw_value, tolerance=0.01) self.ca.assert_that_pv_is_number("POS", raw_value * chan_scale * 1000, tolerance=(0.01 * chan_scale * 1000)) @skip_if_recsim("In rec sim this test fails") def test_WHEN_ioc_gets_a_raw_stress_reading_from_the_device_THEN_it_is_converted_correctly( self): for chan_area in [0.1, 10.0]: self._lewis.backdoor_command( ["device", "set_channel_param", "2", "area", str(chan_area)]) self.ca.assert_that_pv_is("STRESS:AREA", chan_area) for chan_scale in [0.1, 10.0]: self._lewis.backdoor_command([ "device", "set_channel_param", "2", "scale", str(chan_scale) ]) self.ca.assert_that_pv_is("STRESS:SCALE", chan_scale) for raw_value in [0, 123]: self._lewis.backdoor_command([ "device", "set_channel_param", "2", "value", str(raw_value) ]) self.ca.assert_that_pv_is("STRESS:RAW", raw_value) self.ca.assert_that_pv_is( "STRESS", raw_value * chan_scale * (1.0 / chan_area)) @skip_if_recsim("In rec sim this test fails") def test_WHEN_strain_length_updates_on_device_THEN_pv_updates(self): for value in [1, 123]: self._lewis.backdoor_command( ["device", "set_channel_param", "3", "length", str(value)]) self.ca.assert_that_pv_is("STRAIN:LENGTH", value) @skip_if_recsim("In rec sim this test fails") def test_WHEN_ioc_gets_a_raw_strain_reading_from_the_device_THEN_it_is_converted_correctly( self): for chan_scale in [0.1, 10.0]: self._lewis.backdoor_command( ["device", "set_channel_param", "3", "scale", str(chan_scale)]) for chan_length in [0.1, 10.0]: self._lewis.backdoor_command([ "device", "set_channel_param", "3", "length", str(chan_length) ]) for raw_value in [0, 0.001]: self._lewis.backdoor_command([ "device", "set_channel_param", "3", "value", str(raw_value) ]) self.ca.assert_that_pv_is("STRAIN:SCALE", chan_scale) self.ca.assert_that_pv_is("STRAIN:LENGTH", chan_length) self.ca.assert_that_pv_is("STRAIN:RAW", raw_value) self.ca.assert_that_pv_is( "STRAIN", (raw_value * chan_scale * 100000 * (1 / chan_length))) def test_WHEN_the_area_setpoint_is_set_THEN_the_area_readback_updates( self): def _set_and_check(value): self.ca.set_pv_value("STRESS:AREA:SP", value) self.ca.assert_that_pv_is_number("STRESS:AREA", value, tolerance=0.01) self.ca.assert_that_pv_alarm_is("STRESS:AREA", ChannelAccess.Alarms.NONE) for val in [0.234, 789]: _set_and_check(val) def test_WHEN_the_area_setpoint_is_set_THEN_the_diameter_readback_updates( self): def _set_and_check(value): self.ca.set_pv_value("STRESS:AREA:SP", value) self.ca.assert_that_pv_is_number("STRESS:DIAMETER", (2 * math.sqrt(value / math.pi)), tolerance=0.01) self.ca.assert_that_pv_alarm_is("STRESS:DIAMETER", ChannelAccess.Alarms.NONE) for val in [0.234, 789]: _set_and_check(val) def test_WHEN_the_diameter_setpoint_is_set_THEN_the_diameter_readback_updates( self): def _set_and_check(value): self.ca.set_pv_value("STRESS:DIAMETER:SP", value) self.ca.assert_that_pv_is_number("STRESS:DIAMETER", value, tolerance=0.0005) self.ca.assert_that_pv_alarm_is("STRESS:DIAMETER", ChannelAccess.Alarms.NONE) for val in [0.234, 789]: _set_and_check(val) def test_WHEN_the_diameter_setpoint_is_set_THEN_the_area_readback_updates( self): def _set_and_check(value): self.ca.set_pv_value("STRESS:DIAMETER:SP", value) self.ca.assert_that_pv_is_number("STRESS:AREA", ((value / 2.0)**2 * math.pi), tolerance=0.0005) self.ca.assert_that_pv_alarm_is("STRESS:AREA", ChannelAccess.Alarms.NONE) for val in [0.234, 789]: _set_and_check(val) @skip_if_recsim("In rec sim this test fails") def test_WHEN_a_position_setpoint_is_set_THEN_it_is_converted_correctly( self): for scale in [2.34, 456.78]: self._lewis.backdoor_command( ["device", "set_channel_param", "1", "scale", str(scale)]) self.ca.assert_that_pv_is("POS:SCALE", scale) for val in [1.23, 123.45]: self.ca.set_pv_value("POS:SP", val) self.ca.assert_that_pv_is_number("POS:RAW:SP", val * (1.0 / 1000.0) * (1 / scale), tolerance=0.0000000001) self.ca.assert_that_pv_alarm_is("POS:RAW:SP", ChannelAccess.Alarms.NONE) @skip_if_recsim("In rec sim this test fails") def test_WHEN_a_stress_setpoint_is_set_THEN_it_is_converted_correctly( self): for area in [789, 543.21]: self._lewis.backdoor_command( ["device", "set_channel_param", "2", "area", str(area)]) self.ca.assert_that_pv_is("STRESS:AREA", area) for chan_scale in [2.34, 456.78]: self._lewis.backdoor_command([ "device", "set_channel_param", "2", "scale", str(chan_scale) ]) self.ca.assert_that_pv_is("STRESS:SCALE", chan_scale) for val in [1.23, 123.45]: self.ca.set_pv_value("STRESS:SP", val) self.ca.assert_that_pv_is_number("STRESS:RAW:SP", val * (1 / chan_scale) * area, tolerance=0.0000000001) self.ca.assert_that_pv_alarm_is("STRESS:RAW:SP", ChannelAccess.Alarms.NONE) @skip_if_recsim("In rec sim this test fails") def test_WHEN_a_strain_setpoint_is_set_THEN_it_is_converted_correctly( self): for length in [789, 543.21]: self._lewis.backdoor_command( ["device", "set_channel_param", "3", "length", str(length)]) self.ca.assert_that_pv_is("STRAIN:LENGTH", length) for chan_scale in [2.34, 456.78]: self._lewis.backdoor_command([ "device", "set_channel_param", "3", "scale", str(chan_scale) ]) self.ca.assert_that_pv_is("STRAIN:SCALE", chan_scale) for val in [1.23, 123.45]: self.ca.set_pv_value("STRAIN:SP", val) self.ca.assert_that_pv_is_number("STRAIN:RAW:SP", val * (1 / chan_scale) * length * (1.0 / 100000.0), tolerance=0.0000000001) self.ca.assert_that_pv_alarm_is("STRAIN:RAW:SP", ChannelAccess.Alarms.NONE) @skip_if_recsim("In rec sim this test fails") def test_WHEN_the_channel_type_updates_on_the_device_THEN_the_pv_updates( self): for chan_name, chan_num in [("POS", 1), ("STRESS", 2), ("STRAIN", 3)]: for value_1, value_2, return_value_1, return_value_2 in [ (0, 1, "Standard transducer", "Unrecognized"), (1, 10, "User transducer", "Ext. waveform generator") ]: self._lewis.backdoor_command([ "device", "set_channel_param", str(chan_num), "transducer_type", str(value_1) ]) self._lewis.backdoor_command([ "device", "set_channel_param", str(chan_num), "channel_type", str(value_2) ]) self.ca.assert_that_pv_is("" + chan_name + ":TYPE:STANDARD", return_value_1) self.ca.assert_that_pv_is("" + chan_name + ":TYPE", return_value_2) def test_WHEN_waveform_type_abs_set_on_axes_THEN_all_axes_are_set(self): def _set_and_check(set_value, return_value): self.ca.set_pv_value("AXES:RAMP:WFTYP:SP", set_value) for chan in POS_STRESS_STRAIN: self.ca.assert_that_pv_is("{0}:RAMP:WFTYP".format(chan), return_value) self.ca.assert_that_pv_alarm_is("{0}:RAMP:WFTYP".format(chan), ChannelAccess.Alarms.NONE) for set_value, return_value in enumerate(RAMP_WAVEFORM_TYPES): _set_and_check(set_value, return_value) @skip_if_recsim("In rec sim this test fails") def test_WHEN_channel_fails_check_THEN_channel_mbbi_record_is_invalid_and_has_tag_disabled( self): for index, (chan_name, type, index_as_name, channel_as_name) in enumerate( zip(POS_STRESS_STRAIN, (1, 1, 1), ("ZR", "ON", "TW"), ("Position", "Stress", "Strain"))): self._lewis.backdoor_command([ "device", "set_channel_param", str(index + 1), "channel_type", str(type) ]) self.ca.assert_that_pv_is("" + chan_name + ":TYPE:CHECK", "FAIL") self.ca.assert_that_pv_is("CHANNEL:SP.{}ST".format(index_as_name), "{0} - disabled".format(channel_as_name)) self.ca.set_pv_value("CHANNEL:SP", index) self.ca.assert_that_pv_alarm_is("CHANNEL:SP", ChannelAccess.Alarms.INVALID) @skip_if_recsim("In rec sim this test fails") def test_WHEN_channel_succeeds_check_THEN_channel_mbbi_record_is_invalid_and_has_tag_disabled( self): self.ca.set_pv_value("CHANNEL:SP", 3) for index, (chan_name, type, index_as_name, channel_as_name) in enumerate( zip(POS_STRESS_STRAIN, (3, 2, 4), ("ZR", "ON", "TW"), ("Position", "Stress", "Strain"))): self._lewis.backdoor_command([ "device", "set_channel_param", str(index + 1), "channel_type", str(type) ]) self.ca.assert_that_pv_is("" + chan_name + ":TYPE:CHECK", "PASS", timeout=30) self.ca.assert_that_pv_is("CHANNEL:SP.{}ST".format(index_as_name), channel_as_name) self.ca.assert_that_pv_is("CHANNEL:SP.{}SV".format(index_as_name), ChannelAccess.Alarms.NONE) self.ca.set_pv_value("CHANNEL:SP", index) self.ca.assert_that_pv_alarm_is("CHANNEL:SP", ChannelAccess.Alarms.NONE) @skip_if_recsim("In rec sim we can not disconnect the device from the IOC") def test_WHEN_the_rig_is_not_connected_THEN_the_status_has_alarm(self): self._lewis.backdoor_set_on_device("status", None) self.ca.assert_that_pv_alarm_is("STAT:DISP", ChannelAccess.Alarms.INVALID) # Waveform tests def check_running_state(self, status, running, continuing, timeout=None): self.ca.assert_that_pv_is(wave_prefixed("STATUS"), status, timeout) self.ca.assert_that_pv_is(wave_prefixed("RUNNING"), "Running" if running else "Not running", timeout) self.ca.assert_that_pv_is( wave_prefixed("CONTINUING"), "Continuing" if continuing else "Not continuing", timeout) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_stopped_WHEN_it_is_started_THEN_it_is_running( self): self.check_running_state(status="Stopped", running=False, continuing=False) self.ca.set_pv_value(wave_prefixed("START"), 1) self.check_running_state(status="Running", running=True, continuing=True) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_stopped_WHEN_it_is_aborted_THEN_it_is_stopped( self): self.check_running_state(status="Stopped", running=False, continuing=False) self.ca.set_pv_value(wave_prefixed("ABORT"), 1) self.check_running_state(status="Stopped", running=False, continuing=False) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_stopped_WHEN_it_is_stopped_THEN_it_is_stopped( self): self.check_running_state(status="Stopped", running=False, continuing=False) self.ca.set_pv_value(wave_prefixed("STOP"), 1) self.check_running_state(status="Stopped", running=False, continuing=False) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_running_WHEN_it_is_started_THEN_it_is_running( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_RUNNING]) self.check_running_state(status="Running", running=True, continuing=True) self.ca.set_pv_value(wave_prefixed("START"), 1) self.check_running_state(status="Running", running=True, continuing=True) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_running_WHEN_it_is_aborted_THEN_it_is_aborted( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_RUNNING]) self.check_running_state(status="Running", running=True, continuing=True) self.ca.set_pv_value(wave_prefixed("ABORT"), 1) self.check_running_state(status="Aborted", running=False, continuing=False) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_running_WHEN_it_is_stopped_THEN_it_is_finishing_and_then_stops_within_5_seconds( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_RUNNING]) self.check_running_state(status="Running", running=True, continuing=True) self.ca.set_pv_value(wave_prefixed("STOP"), 1) self.check_running_state(status="Finishing", running=True, continuing=False) self.check_running_state(status="Stopped", running=False, continuing=False, timeout=5) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_aborted_WHEN_it_is_started_THEN_it_is_running_and_keeps_running( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_ABORTED]) self.check_running_state(status="Aborted", running=False, continuing=False) self.ca.set_pv_value(wave_prefixed("START"), 1) self.check_running_state(status="Running", running=True, continuing=True) # We need to make sure it can keep running for a few scans. The IOC could feasibly stop the generator shortly # after it is started time.sleep(5) self.check_running_state(status="Running", running=True, continuing=True) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_aborted_WHEN_it_is_aborted_THEN_it_is_aborted( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_ABORTED]) self.check_running_state(status="Aborted", running=False, continuing=False) self.ca.set_pv_value(wave_prefixed("ABORT"), 1) self.check_running_state(status="Aborted", running=False, continuing=False) @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_aborted_WHEN_it_is_stopped_THEN_it_is_aborted( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_ABORTED]) self.check_running_state(status="Aborted", running=False, continuing=False) self.ca.set_pv_value(wave_prefixed("STOP"), 1) self.check_running_state(status="Aborted", running=False, continuing=False) def test_WHEN_waveform_type_is_set_THEN_the_device_reports_it_has_changed( self): for index, wave_type in enumerate([ "Sine", "Triangle", "Square", "Haversine", "Havetriangle", "Haversquare", "Sensor", "Aux", "Sawtooth" ]): self.ca.assert_setting_setpoint_sets_readback( index, wave_prefixed("TYPE"), expected_value=wave_type) @skip_if_recsim("Recsim record does not handle multiple channels ") def test_GIVEN_multiple_channels_WHEN_waveform_frequency_is_set_THEN_the_device_is_updated_to_that_value( self): expected_values = [123.456, 789.012, 345.678] assert len(expected_values) == NUMBER_OF_CHANNELS # Do this as two separate loops so that we can verify that all 3 channel values are stored independently for device_channel in range(NUMBER_OF_CHANNELS): self.ca.set_pv_value("CHANNEL:SP.VAL", device_channel) self.ca.set_pv_value(wave_prefixed("FREQ:SP"), expected_values[device_channel]) for device_channel in range(NUMBER_OF_CHANNELS): self.ca.set_pv_value("CHANNEL:SP.VAL", device_channel) self.ca.assert_that_pv_is(wave_prefixed("FREQ"), expected_values[device_channel]) @unstable_test() @skip_if_recsim("Conversion factors initialized to 0") def test_GIVEN_multiple_channels_WHEN_waveform_amplitude_is_set_THEN_the_device_is_updated_to_that_value_with_channel_conversion_factor_applied( self): input_values = [123.4, 567.8, 91.2] conversion_factors = [ float(self.ca.get_pv_value("POS:SCALE")) * 1000, float(self.ca.get_pv_value("STRESS:SCALE")) / float(self.ca.get_pv_value("STRESS:AREA")), float(self.ca.get_pv_value("STRAIN:SCALE")) * 100000 * float(self.ca.get_pv_value("STRAIN:LENGTH")) ] for i in range(len(conversion_factors)): self.assertNotEqual(0, conversion_factors[i], "Factor {} was zero".format(i)) expected_values = [ input_values[i] / conversion_factors[i] for i in range(NUMBER_OF_CHANNELS) ] assert len(expected_values) == len(conversion_factors) == len( input_values) == NUMBER_OF_CHANNELS # Do this as two separate loops so that we can verify that all 3 channel values are stored independently for device_channel in range(NUMBER_OF_CHANNELS): self.ca.set_pv_value("CHANNEL:SP.VAL", device_channel) self.ca.set_pv_value(wave_prefixed("AMP:SP"), input_values[device_channel]) self.ca.assert_that_pv_is(wave_prefixed("AMP"), expected_values[device_channel]) self.ca.assert_that_pv_is(wave_prefixed("AMP:SP:RBV"), input_values[device_channel]) for device_channel in range(NUMBER_OF_CHANNELS): self.ca.set_pv_value("CHANNEL:SP.VAL", device_channel) self.ca.assert_that_pv_is(wave_prefixed("AMP"), expected_values[device_channel]) self.ca.assert_that_pv_is(wave_prefixed("AMP:SP:RBV"), input_values[device_channel]) @skip_if_recsim("RECSIM does not capture dynamic behaviour") def test_WHEN_the_quarter_counter_is_off_THEN_the_number_of_counts_is_and_remains_zero( self): self.ca.set_pv_value(quart_prefixed("OFF"), 1) self.ca.assert_that_pv_is("QUART", 0) self.ca.assert_that_pv_is("QUART", 0, timeout=5) @skip_if_recsim("Status more complicated than RECSIM can handle") def test_WHEN_the_quarter_counter_is_armed_THEN_the_status_is_armed(self): self.ca.set_pv_value(quart_prefixed("ARM"), 1) self.ca.assert_that_pv_is(quart_prefixed("STATUS"), "Armed") @skip_if_recsim("Counting part of dynamic device behaviour") def test_WHEN_the_waveform_generator_is_started_THEN_the_quarter_counter_starts_counting_and_keeps_increasing( self): self.ca.set_pv_value(wave_prefixed("START"), 1) self.ca.assert_that_pv_value_is_increasing("QUART", 5) @skip_if_recsim("Status more complicated than RECSIM can handle") def test_WHEN_the_quarter_counter_is_armed_THEN_the_number_of_quarts_never_exceeds_the_requested_maximum( self): cycles = 5 self.ca.set_pv_value(quart_prefixed("CYCLE:SP"), cycles) self.ca.assert_that_pv_is(quart_prefixed("SP"), cycles * 4) self.ca.set_pv_value(wave_prefixed("START"), 1) while self.ca.get_pv_value(quart_prefixed("STATUS")) == "Armed": self.assertLessEqual(float(self.ca.get_pv_value("QUART") / 4.0), cycles) self.ca.assert_that_pv_is(quart_prefixed("STATUS"), "Tripped") def test_GIVEN_the_waveform_generator_is_stopped_WHEN_instructed_to_hold_THEN_status_is_stopped( self): self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Stopped") self.ca.set_pv_value(wave_prefixed("HOLD"), 1) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Stopped") @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_running_WHEN_instructed_to_hold_THEN_status_is_holding( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_RUNNING]) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Running") self.ca.set_pv_value(wave_prefixed("HOLD"), 1) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Holding") @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_holding_WHEN_instructed_to_hold_THEN_status_is_holding( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_HOLDING]) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Holding") self.ca.set_pv_value(wave_prefixed("HOLD"), 1) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Holding") @skip_if_recsim("No backdoor in LewisNone") def test_GIVEN_the_waveform_generator_is_finishing_WHEN_instructed_to_hold_THEN_status_is_finishing( self): self._lewis.backdoor_command( ["device", "set_waveform_state", WAVEFORM_FINISHING]) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Finishing") self.ca.set_pv_value(wave_prefixed("HOLD"), 1) self.ca.assert_that_pv_is(wave_prefixed("STATUS"), "Finishing") def verify_channel_abs(self, expected_value): self.ca.assert_that_pv_is("POS:RAMP:WFTYP", expected_value) self.ca.assert_that_pv_is("STRAIN:RAMP:WFTYP", expected_value) self.ca.assert_that_pv_is("STRESS:RAMP:WFTYP", expected_value) def test_WHEN_the_waveform_generator_is_started_THEN_every_axis_is_set_to_ramp( self): self.ca.set_pv_value(wave_prefixed("START"), 1) self.verify_channel_abs(RAMP_WAVEFORM_TYPES[0]) def test_WHEN_the_waveform_generator_is_stopped_THEN_every_axis_is_set_to_absolute_ramp( self): self.ca.set_pv_value(wave_prefixed("STOP"), 1) self.verify_channel_abs(RAMP_WAVEFORM_TYPES[3]) @skip_if_recsim("Different statuses don't interact in RECSIM") def test_WHEN_the_waveform_generator_is_started_THEN_the_quarter_counter_is_armed( self): self.ca.set_pv_value(wave_prefixed("START"), 1) self.ca.assert_that_pv_is(quart_prefixed("STATUS"), "Armed") def test_WHEN_the_max_cyles_is_set_THEN_the_readback_matches_setpoint( self): value = 7 self.ca.assert_setting_setpoint_sets_readback( value=value, set_point_pv=quart_prefixed("CYCLE:SP"), readback_pv=quart_prefixed("CYCLE:SP:RBV"))
class Jsco4180Tests(unittest.TestCase): """ Tests for the Jsco4180 IOC. """ def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc(DEVICE_NAME, DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=30) for pv in required_pvs: self.ca.assert_that_pv_exists(pv, timeout=30) self._lewis.backdoor_run_function_on_device("reset") @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_running_THEN_retry_run_and_updates_component(self): expected_value_A = 30 expected_value_B = 15 expected_value_C = 55 self.ca.set_pv_value("COMP:A:SP", expected_value_A) self.ca.set_pv_value("COMP:B:SP", expected_value_B) self.ca.set_pv_value("COMP:C:SP", expected_value_C) self.ca.set_pv_value("START:SP", 1) sleep(10) # Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components. self._lewis.backdoor_set_on_device("component_A", 25) self._lewis.backdoor_set_on_device("component_B", 10) self._lewis.backdoor_set_on_device("component_C", 14) self.ca.assert_that_pv_is("COMP:A", expected_value_A, timeout=30) self.ca.assert_that_pv_is("COMP:B", expected_value_B, timeout=30) self.ca.assert_that_pv_is("COMP:C", expected_value_C, timeout=30) # there was a previous problem where if setpoint and readback differed a sleep and resend was started, # but the old state machine did not look to see if a new sp was issued while it was asleep and so then # resent the old out of date SP @unstable_test(max_retries=2, wait_between_runs=60) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_send_new_sp_THEN_state_machine_aborts_resend(self): value = 50 self.ca.set_pv_value("COMP:A:SP", value) self.ca.set_pv_value("COMP:B:SP", value) self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("STATUS", "Pumping", timeout=5) self.ca.assert_that_pv_is("COMP:A", value, timeout=30) self.ca.assert_that_pv_is("COMP:B", value, timeout=30) # Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components after a delay initial_delay = self.ca.get_pv_value("ERROR:DELAY") # delay before state machine reset delay = 30 # Increase delay to avoid race conditions self.ca.set_pv_value("ERROR:DELAY", delay) try: with self.ca.assert_pv_not_processed("RESET:SP"): self._lewis.backdoor_set_on_device("component_A", value - 5) self.ca.assert_that_pv_is("COMP:A", value - 5, timeout=5) sleep(delay / 2.0) # however if we change setpoint, the loop should start again self._lewis.backdoor_set_on_device("component_A", value - 5) self.ca.set_pv_value("COMP:A:SP", value - 10) self.ca.set_pv_value("COMP:B:SP", value + 10) # reset should not have happened yet self.ca.assert_that_pv_is("COMP:A", value - 5, timeout=delay / 2.0) self.ca.assert_that_pv_value_is_unchanged("COMP:A", wait=delay / 2.0) # Reset should now happen within a further timeout/2 seconds (but give it longer to avoid races) with self.ca.assert_pv_processed("RESET:SP"): self.ca.assert_that_pv_is("COMP:A", value - 10, timeout=delay * 2) finally: # Put error delay back to it's initial value self.ca.set_pv_value("ERROR:DELAY", initial_delay) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_running_continuous_THEN_retry_run_and_updates_component_in_correct_mode( self): value = 50 expected_value = "Pumping" self.ca.set_pv_value("COMP:A:SP", value) self.ca.set_pv_value("COMP:B:SP", value) self.ca.set_pv_value("START:SP", 1) # Give the device some time running in a good state sleep(10) # Sabotage! - Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components. self._lewis.backdoor_set_on_device("component_A", 33) self.ca.assert_that_pv_is("STATUS", expected_value, timeout=30) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_wrong_component_on_device_WHEN_running_timed_THEN_retry_run_and_updates_component_in_correct_mode( self): value = 50 expected_value = "Pumping" self.ca.set_pv_value("COMP:A:SP", value) self.ca.set_pv_value("COMP:B:SP", value) self.ca.set_pv_value("TIME:RUN:SP", 100) self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1) # Give the device some time running in a good state sleep(10) # Sabotage! - Setting an incorrect component on the device will result in the state machine attempting # to rerun the pump and reset components. self._lewis.backdoor_set_on_device("component_A", 33) self.ca.assert_that_pv_is("STATUS", expected_value, timeout=30) @skip_if_recsim("Flowrate device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_flowrate_THEN_flowrate_setpoint_is_correct(self): error_delay = float(self.ca.get_pv_value("ERROR:DELAY")) sleep(2 * error_delay) # To make sure we're not in the middle of the error-checking state machine expected_value = 1.000 self.ca.set_pv_value("FLOWRATE:SP", expected_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_value) self.ca.set_pv_value("TIME:RUN:SP", 100) self.ca.set_pv_value("START:SP", "Start") self.ca.assert_that_pv_is("FLOWRATE", expected_value) @skip_if_recsim("LeWIS backdoor not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_flowrate_and_pump_volume_THEN_ioc_uses_rbv_for_calculation_of_remaining_time(self): expected_sp_value = 1.000 expected_rbv_value = 2.000 pump_for_volume = 2 expected_time_value = (pump_for_volume / expected_rbv_value) * 60 error_delay = float(self.ca.get_pv_value("ERROR:DELAY")) sleep(2 * error_delay) # To make sure we're not in the middle of the error-checking state machine # 1. set invalid flowrate setpoint (FLOWRATE:SP) self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value) # 2. set valid hardware flowrate (FLOWRATE:SP:RBV) via backdoor command self._lewis.backdoor_set_on_device("flowrate_rbv", expected_rbv_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_rbv_value) # 3. set volume setpoint and start pump self.ca.set_pv_value("TIME:VOL:SP", pump_for_volume) self.ca.set_pv_value("START:SP", "Start") # 4. check calculated time is based on flowrate setpoint readback (:SP:RBV rather than :SP) self.ca.assert_that_pv_is("TIME:VOL:CALCRUN", expected_time_value) @skip_if_recsim("LeWIS backdoor not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_flowrate_and_pump_time_THEN_ioc_uses_rbv_for_calculation_of_remaining_volume(self): expected_sp_value = 1.000 expected_rbv_value = 2.000 pump_for_time = 120 expected_volume_value = (pump_for_time * expected_rbv_value) / 60 error_delay = float(self.ca.get_pv_value("ERROR:DELAY")) sleep(2 * error_delay) # To make sure we're not in the middle of the error-checking state machine # 1. set invalid flowrate setpoint (FLOWRATE:SP) self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value) # 2. set valid hardware flowrate (FLOWRATE:SP:RBV) via backdoor command self._lewis.backdoor_set_on_device("flowrate_rbv", expected_rbv_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_rbv_value) # 3. set time setpoint and start pump self.ca.set_pv_value("TIME:RUN:SP", pump_for_time) self.ca.set_pv_value("START:SP", "Start") # 4. check calculated volume is based on flowrate setpoint readback (:SP:RBV rather than :SP) self.ca.assert_that_pv_is("TIME:RUN:CALCVOL", expected_volume_value) # test to check that the IOC updates the flowrate RBV quickly enough # for the remaining volume calculation to be valid. simulates operation of a script. def test_GIVEN_an_ioc_WHEN_set_flowrate_and_immediately_set_pump_to_start_THEN_ioc_updates_rbv_for_calculation_of_remaining_volume( self): expected_sp_value = 2.000 script_sp_value = 3.000 pump_for_time = 120 # 1. initialize flowrate self.ca.set_pv_value("FLOWRATE:SP", expected_sp_value) self.ca.assert_that_pv_is("FLOWRATE:SP:RBV", expected_sp_value, timeout=5) # 2. set new flowrate and immediately set pump to run, to simulate script self.ca.set_pv_value("FLOWRATE:SP", script_sp_value) self.ca.set_pv_value("TIME:RUN:SP", pump_for_time) self.ca.set_pv_value("START:SP", "Start") # 3. calculate remaining volume expected_volume_value = (pump_for_time * self.ca.get_pv_value("FLOWRATE:SP:RBV")) / 60 # 4. check ioc calculation is as expected self.ca.assert_that_pv_is("TIME:RUN:CALCVOL", expected_volume_value) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_maximum_pressure_limit_THEN_maximum_pressure_limit_is_correct(self): expected_value = 200 self.ca.assert_setting_setpoint_sets_readback(expected_value, "PRESSURE:MAX") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_set_minimum_pressure_limit_THEN_minimum_pressure_limit_is_correct(self): expected_value = 100 self.ca.set_pv_value("PRESSURE:MIN:SP", expected_value) self.ca.assert_setting_setpoint_sets_readback(expected_value, "PRESSURE:MIN") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_continuous_pump_set_THEN_pump_on(self): self.ca.set_pv_value("START:SP", 1) self.ca.assert_that_pv_is("STATUS", "Pumping") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_an_ioc_WHEN_timed_pump_set_THEN_timed_pump_on(self): # Set a run time for a timed run self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1) self.ca.assert_that_pv_is("STATUS", "Pumping") @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_an_ioc_WHEN_get_current_pressure_THEN_current_pressure_returned(self): expected_value = 300 self._lewis.backdoor_set_on_device("pressure", expected_value) self.ca.assert_that_pv_is("PRESSURE", expected_value) @parameterized.expand([ ("component_{}".format(suffix), suffix) for suffix in ["A", "B", "C", "D"] ]) @skip_if_recsim("Reliant on setUP lewis backdoor call") def test_GIVEN_an_ioc_WHEN_get_component_THEN_correct_component_returned(self, component, suffix): expected_value = 10.0 self._lewis.backdoor_set_on_device(component, expected_value) self.ca.assert_that_pv_is("COMP:{}".format(suffix), expected_value) @parameterized.expand([ ("COMP:{}".format(suffix), suffix) for suffix in ["A", "B", "C"] ]) @skip_if_recsim("Reliant on setUP lewis backdoor call") def test_GIVEN_an_ioc_WHEN_set_component_THEN_correct_component_set(self, component, suffix): expected_value = 100.0 self.ca.set_pv_value("COMP:{}:SP".format(suffix), expected_value) if component == "COMP:A": self.ca.set_pv_value("COMP:B:SP", 0) self.ca.set_pv_value("COMP:C:SP", 0) elif component == "COMP:B": self.ca.set_pv_value("COMP:A:SP", 0) self.ca.set_pv_value("COMP:C:SP", 0) elif component == "COMP:C": self.ca.set_pv_value("COMP:A:SP", 0) self.ca.set_pv_value("COMP:B:SP", 0) self.ca.set_pv_value("PUMP_FOR_TIME:SP", "Start") self.ca.assert_that_pv_is(component, expected_value) def test_GIVEN_ioc_initial_state_WHEN_get_error_THEN_error_returned(self): expected_value = "No error" self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_ioc_in_hardware_error_state_WHEN_get_error_THEN_hardware_error_returned(self): expected_value = "Hardware error" self._lewis.backdoor_set_on_device("error", ERROR_STATE_HARDWARE_FAULT) self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_ioc_in_error_state_WHEN_reset_error_THEN_error_reset(self): expected_value = "No error" self._lewis.backdoor_set_on_device("error", ERROR_STATE_NO_ERROR) self.ca.set_pv_value("ERROR:SP", "Reset") self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_ioc_in_error_state_WHEN_reset_error_THEN_error_reset(self): expected_value = "No error" self._lewis.backdoor_set_on_device("error", ERROR_STATE_HARDWARE_FAULT) self.ca.assert_that_pv_is("ERROR", expected_value) @skip_if_recsim("Unable to use lewis backdoor in RECSIM") def test_GIVEN_device_not_connected_WHEN_get_error_THEN_alarm(self): self._lewis.backdoor_set_on_device('connected', False) self.ca.assert_that_pv_alarm_is('ERROR:SP', ChannelAccess.Alarms.INVALID) @skip_if_recsim("Reliant on setUP lewis backdoor call") def test_GIVEN_timed_pump_WHEN_get_program_runtime_THEN_program_runtime_increments(self): self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.set_pv_value("PUMP_FOR_TIME:SP", 1) self.ca.assert_that_pv_value_is_increasing("TIME", wait=2) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_timed_pump_WHEN_set_constant_pump_THEN_state_updated_to_constant_pump(self): # Set a run time for a timed run self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.process_pv("PUMP_FOR_TIME:SP") expected_value = "Pumping" self.ca.assert_that_pv_is("STATUS", expected_value) self.ca.process_pv("START:SP") expected_value = "Pumping" self.ca.assert_that_pv_is("STATUS", expected_value) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_constant_pump_WHEN_set_timed_pump_THEN_state_updated_to_timed_pump(self): expected_value = "Pumping" self.ca.process_pv("START:SP") self.ca.assert_that_pv_is("STATUS", expected_value) # Set a run time for a timed run self.ca.set_pv_value("TIME:RUN:SP", 10000) self.ca.process_pv("PUMP_FOR_TIME:SP") self.ca.assert_that_pv_is("STATUS", expected_value) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_input_incorrect_WHEN_set_flowrate_THEN_trouble_message_returned(self): self._lewis.backdoor_set_on_device("input_correct", False) self.ca.set_pv_value("FLOWRATE:SP", 0.010) self.ca.assert_that_pv_is("ERROR:STR", "[Error:stack underflow]") @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_command_seq_that_would_crash_pump_WHEN_command_seq_called_THEN_pump_crashes(self): self.ca.set_pv_value("_TEST_CRASH.PROC", 1) self.ca.assert_that_pv_alarm_is("COMP:A", ChannelAccess.Alarms.INVALID, timeout=30) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_pump_running_WHEN_set_file_number_command_called_THEN_program_is_busy_error(self): expected_value = "[Program is Busy]" self.ca.set_pv_value("START:SP", 1) self.ca.set_pv_value("FILE:SP", 0) self.ca.assert_that_pv_is("ERROR:STR", expected_value) @parameterized.expand([("low_set_time", 100, 1, 1), ("high_set_time", 1000, 10, 1), ("non_standard_set_time", 456, 5, 1)]) @unstable_test(max_retries=5) @skip_if_recsim("Lewis device logic not supported in RECSIM") def test_GIVEN_pump_for_volume_WHEN_pumping_THEN_device_is_pumping_set_volume(self, _, time, volume, flowrate): # Set a target pump time a target pump volume. When we start a pump set volume run, then the remaining # time should be related to the target volume, and not the target time (that would be used for a pump for time). set_time = time set_volume = volume set_flowrate = flowrate expected_time = set_volume * set_flowrate * 60 # flow rate units = mL/min, so convert to seconds self.ca.set_pv_value("TIME:RUN:SP", set_time) self.ca.set_pv_value("TIME:VOL:SP", set_volume) self.ca.set_pv_value("FLOWRATE:SP", set_flowrate) self.ca.process_pv("PUMP_SET_VOLUME:SP") self.ca.assert_that_pv_is_within_range("TIME:REMAINING", min_value=expected_time - 20, max_value=expected_time + 20)
class GemorcTests(unittest.TestCase): """ Tests for the Gemorc IOC. """ def reset_emulator(self): self._lewis.backdoor_set_on_device("reset", True) sleep( 1 ) # Wait for reset to finish so we don't jump the gun. No external indicator from emulator def reset_ioc(self): self.ca.set_pv_value("RESET", 1) # INIT:ONCE is a property held exclusively in the IOC calc_pv = "INIT:ONCE:CALC.CALC" original_calc = self.ca.get_pv_value(calc_pv) self.ca.set_pv_value(calc_pv, "0") self.ca.assert_that_pv_is("INIT:ONCE", "No") self.ca.set_pv_value(calc_pv, original_calc) def setUp(self): self._lewis, self._ioc = get_running_lewis_and_ioc( "gemorc", DEVICE_PREFIX) self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX, default_timeout=DEFAULT_TIMEOUT) self.ca.assert_that_pv_exists("ID", timeout=30) self.reset_ioc() if not IOCRegister.uses_rec_sim: self.reset_emulator() self.check_init_state(False, False, True, False) self.ca.assert_that_pv_is_number("CYCLES", 0) def check_init_state(self, initialising, initialised, initialisation_required, oscillating): def bi_to_bool(val): return val == "Yes" # Do all states at once. match = False total_time = 0.0 max_wait = DEFAULT_TIMEOUT interval = 1.0 actual_initialising = None actual_initialised = None actual_initialisation_required = None actual_oscillating = None while not match and total_time < max_wait: actual_initialising = bi_to_bool( self.ca.get_pv_value("INIT:PROGRESS")) actual_initialised = bi_to_bool(self.ca.get_pv_value("INIT:DONE")) actual_initialisation_required = bi_to_bool( self.ca.get_pv_value("INIT:REQUIRED")) actual_oscillating = bi_to_bool(self.ca.get_pv_value("STAT:OSC")) match = all([ initialising == actual_initialising, initialised == actual_initialised, initialisation_required == actual_initialisation_required, oscillating == actual_oscillating ]) total_time += interval sleep(interval) try: self.assertTrue(match) except AssertionError: message_format = "State did not match the required state (initialising, initialised, initialisation " \ "required, oscillating)\nExpected: ({}, {}, {}, {})\nActual: ({}, {}, {}, {})" self.fail( message_format.format(initialising, initialised, initialisation_required, oscillating, actual_initialising, actual_initialised, actual_initialisation_required, actual_oscillating)) def initialise(self): self.ca.set_pv_value("INIT", 1) self.ca.assert_that_pv_is("INIT:DONE", "Yes", timeout=10) def start_oscillating(self): self.initialise() self.ca.set_pv_value("START", 1) def wait_for_re_initialisation_required(self, interval=10): self.ca.set_pv_value("INIT:OPT", interval) self.start_oscillating() while self.ca.get_pv_value("CYCLES") < interval: sleep(1) @staticmethod def backlash(speed, acceleration): return int(0.5 * speed**2 / float(acceleration)) @staticmethod def utility(width, backlash): return width / float(width + backlash) * 100.0 @staticmethod def period(width, backlash, speed): return 2.0 * (width + backlash) / float(speed) @staticmethod def frequency(width, backlash, speed): return 1.0 / GemorcTests.period(width, backlash, speed) def set_and_confirm_state(self, width=None, speed=None, acceleration=None, offset=None): pv_value_pairs = [("WIDTH", width), ("SPEED", speed), ("ACC", acceleration), ("OFFSET", offset)] filtered_pv_values = [(pv, value) for pv, value in pv_value_pairs if value is not None] for pv, value in filtered_pv_values: self.ca.set_pv_value("{}:SP".format(pv), value) # Do all sets then all confirms to reduce wait time for pv, value in filtered_pv_values: self.ca.assert_that_pv_is_number(pv, value) def test_WHEN_width_setpoint_set_THEN_local_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_WIDTH + 1, "WIDTH:SP:RBV", "WIDTH:SP") def test_WHEN_width_setpoint_set_THEN_remote_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_WIDTH + 1, "WIDTH") def test_WHEN_speed_setpoint_set_THEN_local_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_SPEED + 1, "SPEED:SP:RBV", "SPEED:SP") def test_WHEN_speed_setpoint_set_THEN_remote_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_SPEED + 1, "SPEED") def test_WHEN_acceleration_setpoint_set_THEN_local_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_ACCELERATION + 1, "ACC:SP:RBV", "ACC:SP") def test_WHEN_acceleration_setpoint_set_THEN_remote_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_ACCELERATION + 1, "ACC") def test_WHEN_offset_setpoint_set_THEN_local_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_OFFSET + 1, "OFFSET:SP:RBV", "OFFSET:SP") def test_WHEN_offset_setpoint_set_THEN_remote_readback_matches(self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_OFFSET + 1, "OFFSET") def test_WHEN_offset_setpoint_set_to_negative_value_THEN_remote_readback_matches( self): self.ca.assert_setting_setpoint_sets_readback(-DEFAULT_OFFSET, "OFFSET") def test_WHEN_device_first_started_THEN_initialisation_required(self): self.check_init_state(initialising=False, initialised=False, initialisation_required=True, oscillating=False) @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_starting_state_WHEN_initialisation_requested_THEN_initialising_becomes_true( self): self.ca.set_pv_value("INIT", 1) self.check_init_state(initialising=True, initialised=False, initialisation_required=False, oscillating=False) @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_starting_state_WHEN_initialisation_requested_THEN_becomes_initialised_when_no_longer_in_progress( self): self.ca.set_pv_value("INIT", 1) total_wait = 0 max_wait = DEFAULT_TIMEOUT interval = 1 initialisation_complete = self.ca.get_pv_value("INIT:DONE") while self.ca.get_pv_value( "INIT:PROGRESS") == "Yes" and total_wait < max_wait: # Always check value from before we confirmed initialisation was in progress to avoid race conditions self.assertNotEqual(initialisation_complete, 1) sleep(interval) total_wait += interval initialisation_complete = self.ca.get_pv_value("INIT:DONE") self.check_init_state(initialising=False, initialised=True, initialisation_required=False, oscillating=False) @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_initialised_WHEN_oscillation_requested_THEN_reports_oscillating( self): self.start_oscillating() self.ca.assert_that_pv_is("STAT:OSC", "Yes") @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_initialised_WHEN_oscillation_requested_THEN_complete_cycles_increases( self): self.start_oscillating() self.ca.assert_that_pv_value_is_increasing("CYCLES", DEFAULT_TIMEOUT) @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_oscillating_WHEN_oscillation_stopped_THEN_reports_not_oscillating( self): self.start_oscillating() self.ca.set_pv_value("STOP", 1) self.ca.assert_that_pv_is("STAT:OSC", "No") @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_initialised_WHEN_oscillation_requested_THEN_complete_cycles_does_not_change( self): self.start_oscillating() self.ca.set_pv_value("STOP", 1) self.ca.assert_that_pv_value_is_unchanged("CYCLES", DEFAULT_TIMEOUT) @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_oscillating_WHEN_initialisation_requested_THEN_initialises( self): self.start_oscillating() self.ca.set_pv_value("INIT", 1) self.check_init_state(initialising=True, initialised=False, initialisation_required=False, oscillating=False) @skip_if_recsim("Device reset requires Lewis backdoor") def test_GIVEN_oscillating_and_initialisation_requested_WHEN_initialisation_complete_THEN_resumes_oscillation( self): self.start_oscillating() self.initialise() self.check_init_state(initialising=False, initialised=True, initialisation_required=False, oscillating=True) def test_WHEN_settings_reset_requested_THEN_settings_return_to_default_values( self): settings = ( ("WIDTH", DEFAULT_WIDTH), ("ACC", DEFAULT_ACCELERATION), ("SPEED", DEFAULT_SPEED), ("OFFSET", DEFAULT_OFFSET), ("INIT:AUTO", DEFAULT_AUTO_INITIALISE), ("INIT:OPT", DEFAULT_OPT_INITIALISE), ) for pv, default in settings: self.ca.set_pv_value("{}:SP".format(pv), default + 1) # I prefer the two lines here self.ca.assert_that_pv_is_not_number(pv, default) self.ca.set_pv_value("RESET", 1) for pv, default in settings: self.ca.assert_that_pv_is_number(pv, default) @skip_if_recsim("ID is emulator specific") def test_WHEN_device_is_running_THEN_it_gets_PnP_identity_from_emulator( self): self.ca.assert_that_pv_is( "ID", "0002 0001 ISIS Gem Oscillating Rotary Collimator (IBEX EMULATOR)", timeout=20) # On a very slow scan def test_GIVEN_standard_test_cases_WHEN_backlash_calculated_locally_THEN_result_is_in_range_supported_by_device( self): for _, speed, acceleration in SETTINGS_TEST_CASES: self.assertTrue(0 <= self.backlash(speed, acceleration) <= 999) @skip_if_recsim("Depends on emulator value") def test_WHEN_emulator_running_THEN_backlash_has_value_derived_from_speed_and_acceleration( self): for width, speed, acceleration in SETTINGS_TEST_CASES: self.set_and_confirm_state(speed=speed, acceleration=acceleration) self.ca.assert_that_pv_is_number( "BACKLASH", self.backlash(speed, acceleration)) def test_GIVEN_non_zero_speed_WHEN_width_and_speed_set_THEN_utility_time_corresponds_to_formula_in_test( self): for width, speed, acceleration in SETTINGS_TEST_CASES: self.set_and_confirm_state(width, speed, acceleration) backlash = self.ca.get_pv_value("BACKLASH") self.ca.assert_that_pv_is_number("UTILITY", self.utility(width, backlash), tolerance=DEFAULT_TOLERANCE) def test_WHEN_emulator_running_THEN_period_has_value_as_derived_from_speed_width_and_backlash( self): for width, speed, acceleration in SETTINGS_TEST_CASES: self.set_and_confirm_state(width, speed, acceleration) backlash = self.ca.get_pv_value("BACKLASH") self.ca.assert_that_pv_is_number("PERIOD", self.period( width, backlash, speed), tolerance=DEFAULT_TOLERANCE) def test_WHEN_emulator_running_THEN_frequency_has_value_as_derived_from_speed_width_and_backlash( self): for width, speed, acceleration in SETTINGS_TEST_CASES: self.set_and_confirm_state(width, speed, acceleration) backlash = self.ca.get_pv_value("BACKLASH") self.ca.assert_that_pv_is_number("FREQ", self.frequency( width, backlash, speed), tolerance=DEFAULT_TOLERANCE) @skip_if_recsim("This behaviour not implemented in recsim") def test_GIVEN_non_zero_offset_WHEN_re_zeroed_to_datum_THEN_offset_is_zero( self): self.ca.assert_setting_setpoint_sets_readback(DEFAULT_OFFSET + 1, "OFFSET", "OFFSET:SP") self.ca.assert_that_pv_is_not_number("OFFSET", 0) self.ca.set_pv_value("ZERO", 1) self.ca.assert_that_pv_is_number("OFFSET", 0) def test_WHEN_auto_initialisation_interval_set_THEN_readback_matches_set_value( self): self.ca.assert_setting_setpoint_sets_readback( DEFAULT_AUTO_INITIALISE + 1, "INIT:AUTO") def test_WHEN_opt_initialisation_interval_set_THEN_readback_matches_set_value( self): self.ca.assert_setting_setpoint_sets_readback( DEFAULT_OPT_INITIALISE + 1, "INIT:OPT") @skip_if_recsim("Cycle counting not performed in Recsim") def test_GIVEN_oscillating_WHEN_number_of_cycles_exceeds_optional_init_interval_THEN_initialisation_required( self): self.wait_for_re_initialisation_required() self.check_init_state(False, True, True, True) @skip_if_recsim("Cycle counting not performed in Recsim") def test_GIVEN_initialisation_required_after_oscillating_WHEN_reinitialised_THEN_re_initialisation_not_required( self): self.wait_for_re_initialisation_required() self.ca.set_pv_value("INIT:OPT", DEFAULT_OPT_INITIALISE) self.initialise() self.check_init_state(False, True, False, True) @skip_if_recsim("Initialisation logic not performed in Recsim") def test_WHEN_device_initialised_THEN_initialised_once(self): self.initialise() self.ca.assert_that_pv_is("INIT:ONCE", "Yes") @skip_if_recsim("Initialisation logic not performed in Recsim") def test_WHEN_oscillating_THEN_initialised_once(self): self.start_oscillating() self.ca.assert_that_pv_is("INIT:ONCE", "Yes") @skip_if_recsim("Initialisation logic not performed in Recsim") def test_WHEN_oscillating_and_initialisation_required_THEN_initialised_once( self): self.wait_for_re_initialisation_required() self.ca.assert_that_pv_is("INIT:ONCE", "Yes") @skip_if_recsim("Initialisation logic not performed in Recsim") def test_WHEN_reinitialising_THEN_initialised_once(self): self.wait_for_re_initialisation_required() self.ca.set_pv_value("INIT", 1) self.ca.assert_that_pv_is("INIT:ONCE", "Yes") @skip_if_recsim("Initialisation logic not performed in Recsim") def test_WHEN_reinitialised_THEN_initialised_once(self): self.wait_for_re_initialisation_required() self.initialise() self.ca.assert_that_pv_is("INIT:ONCE", "Yes") @skip_if_recsim("Initialisation logic not performed in Recsim") def test_GIVEN_oscillating_WHEN_stopped_and_immediately_initialised_THEN_number_of_cycles_goes_to_zero( self): self.start_oscillating() self.ca.set_pv_value("STOP", 1) self.ca.set_pv_value("INIT", 1) self.ca.assert_that_pv_is_number("CYCLES", 0) @skip_if_recsim("Initialisation logic not performed in Recsim") def test_WHEN_oscillating_THEN_auto_reinitialisation_triggers_after_counter_reaches_auto_trigger_value( self): initialisation_interval = 100 initial_status_string = "Sequence not run since IOC startup" self.ca.set_pv_value("INIT:AUTO", initialisation_interval) self.start_oscillating() while self.ca.get_pv_value("CYCLES") < initialisation_interval: self.ca.assert_that_pv_is("INIT:PROGRESS", "No") self.ca.assert_that_pv_is("INIT:STAT", initial_status_string) sleep(1) self.ca.assert_that_pv_is_not("INIT:STAT", initial_status_string) self.ca.assert_that_pv_is( "STAT:OSC", "No", timeout=10) # Initialisation seq has a 5s wait at the start