diff --git a/exopy_hqc_legacy/instruments/drivers/driver_tools.py b/exopy_hqc_legacy/instruments/drivers/driver_tools.py index 4e8e38e..7885c85 100644 --- a/exopy_hqc_legacy/instruments/drivers/driver_tools.py +++ b/exopy_hqc_legacy/instruments/drivers/driver_tools.py @@ -50,6 +50,12 @@ class InstrIOError(InstrError): pass +class InstrTimeoutError(InstrError): + """Generic error raised when an instrument does not behave as expected + """ + pass + + class instrument_property(property): """Property allowing to cache the result of a get operation and return it on the next get. The cache can be cleared. @@ -157,11 +163,15 @@ class InstrJob(object): cancel : Callable, optional Function to cancel the task. + timeout : Callable, optional + Function called when the task timeouts + """ - def __init__(self, condition_callable, expected_waiting_time, cancel): + def __init__(self, condition_callable, expected_waiting_time, cancel, timeout): self.condition_callable = condition_callable self.expected_waiting_time = expected_waiting_time self.cancel = cancel + self.timeout = timeout self._start_time = time.time() def wait_for_completion(self, break_condition_callable=None, timeout=15, @@ -185,6 +195,10 @@ def wait_for_completion(self, break_condition_callable=None, timeout=15, result : bool Boolean indicating if the wait succeeded of was interrupted. + Raises + ------ + InstrTimeoutError: + Raised if the operation timeout. """ while True: remaining_time = (self.expected_waiting_time - @@ -205,7 +219,13 @@ def wait_for_completion(self, break_condition_callable=None, timeout=15, if self.condition_callable(): return True if remaining_time < 0 or break_condition_callable(): - return False + if remaining_time < 0: + if self.timeout: + self.timeout() + raise InstrTimeoutError() + else: + return False + def cancel(self, *args, **kwargs): """Cancel the long running job. diff --git a/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_cs4.py b/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_cs4.py index 8846e05..2c839ce 100644 --- a/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_cs4.py +++ b/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_cs4.py @@ -32,7 +32,7 @@ class CS4(VisaInstrument): #: Typical fluctuations at the output of the instrument. #: We use a class variable since we expect this to be identical for all #: instruments. - OUTPUT_FLUCTUATIONS = 2e-4 + OUTPUT_FLUCTUATIONS = 5e-4 caching_permissions = {'heater_state': True, 'target_field': True, @@ -116,20 +116,35 @@ def sweep_to_field(self, value, rate=None): # Start ramping. self.target_field = value + # Added a pause and then sweep up due to a buggy behavior of the source + sleep(1) + self.activity = 'Hold' + sleep(1) self.activity = 'To set point' # Create job. span = abs(self.read_output_field() - value) wait = 60 * span / rate - job = InstrJob(self.is_target_reached, wait, cancel=self.stop_sweep) + job = InstrJob(self.is_target_reached, wait, cancel=self.stop_sweep, + timeout=self.stop_sweep_safe) return job + @secure_communication() def stop_sweep(self): """Stop the field sweep at the current value. """ self.activity = 'Hold' + @secure_communication() + def stop_sweep_safe(self): + """Stop the field sweep at the current value, and turn of the switch heater. + + """ + self.activity = 'Hold' + self.heater_state = 'Off' + sleep(self.post_switch_wait) + def check_connection(self): pass diff --git a/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_g4.py b/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_g4.py index 64dd337..4ddbc95 100644 --- a/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_g4.py +++ b/exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_g4.py @@ -27,7 +27,8 @@ class C4G(CS4): """Driver for the superconducting magnet power supply Cryomagnetics 4G. - + The fields are read and given in kG (can't change it in G4) + The display only is in T. """ def open_connection(self, **para): @@ -40,7 +41,7 @@ def open_connection(self, **para): self.read_termination = '\n' # Need to write the lower limit in kG for source 4G #(LLIM needs to be lower than any ULIM) - self.write('LLIM -70') + self.write('LLIM -70;') @secure_communication() def read_output_field(self): @@ -62,7 +63,6 @@ def target_field(self): """Field that the source will try to reach. """ - # in T return float(self.ask('ULIM?').strip(' kG')) / 10 @target_field.setter @@ -76,6 +76,16 @@ def target_field(self, target): # can't reuse CS4 class because a semi-colon is needed self.write('ULIM {};'.format(target * 10)) + @instrument_property + @secure_communication() + def field_sweep_rate(self): + """Rate at which to ramp the field (T/min). + + """ + # converted from A/s to T/min + rate = float(self.ask('RATE? 0')) + return rate * (60 * self.field_current_ratio) + @field_sweep_rate.setter @secure_communication() def field_sweep_rate(self, rate): diff --git a/exopy_hqc_legacy/manifest.enaml b/exopy_hqc_legacy/manifest.enaml index 3f1c488..893db69 100644 --- a/exopy_hqc_legacy/manifest.enaml +++ b/exopy_hqc_legacy/manifest.enaml @@ -359,7 +359,7 @@ enamldef HqcLegacyManifest(PluginManifest): task = 'meas_mag_field_task:MeasMagFieldTask' view = 'views.meas_mag_field_view:MeasMagFieldView' instruments = ['exopy_hqc_legacy.Legacy.IPS12010', - 'exopy_hqc_legacy.Legacy.CS40', + 'exopy_hqc_legacy.Legacy.CS4', 'exopy_hqc_legacy.Legacy.C4G'] metadata = {'loopable': True} Task: diff --git a/exopy_hqc_legacy/tasks/tasks/instr/apply_mag_field_task.py b/exopy_hqc_legacy/tasks/tasks/instr/apply_mag_field_task.py index 49c5093..d7c86b2 100644 --- a/exopy_hqc_legacy/tasks/tasks/instr/apply_mag_field_task.py +++ b/exopy_hqc_legacy/tasks/tasks/instr/apply_mag_field_task.py @@ -16,6 +16,7 @@ from atom.api import (Unicode, Float, Bool, set_default) from exopy.tasks.api import InstrumentTask, validators +from exopy_hqc_legacy.instruments.drivers.driver_tools import InstrTimeoutError class ApplyMagFieldTask(InstrumentTask): @@ -59,8 +60,9 @@ def perform(self, target_value=None): driver = self.driver normal_end = True - if (abs(driver.read_persistent_field() - target_value) > - driver.output_fluctuations): + # Only if driver.heater_state == 'Off' otherwise we wait for + # post_switch_wait no matter what + if driver.heater_state == 'Off': job = driver.sweep_to_persistent_field() if job.wait_for_completion(self.check_for_interruption, timeout=60, refresh_time=1): @@ -69,31 +71,41 @@ def perform(self, target_value=None): else: return False + if (abs(driver.read_persistent_field() - target_value) > + driver.output_fluctuations): # set the magnetic field job = driver.sweep_to_field(target_value, self.rate) - normal_end = job.wait_for_completion(self.check_for_interruption, - timeout=60, - refresh_time=10) - # Always close the switch heater when the ramp was interrupted. + try: + normal_end = job.wait_for_completion(self.check_for_interruption, + timeout=60, + refresh_time=5) + except InstrTimeoutError: + # job.timeout() has been called, which stops the sweep and turn off + # the switch heater + self.write_in_database('field', driver.read_persistent_field()) + # fail the measurement + self.root.should_stop.set() + raise ValueError(cleandoc('''Field source did not set the field to + {}'''.format(target_value))) + if not normal_end: + # this happens when a stop signal has been send to the measurement job.cancel() - driver.heater_state = 'Off' - sleep(self.post_switch_wait) - self.write_in_database('field', driver.read_persistent_field()) - return False + return - # turn off heater + # turn off heater if required if self.auto_stop_heater: driver.heater_state = 'Off' sleep(self.post_switch_wait) - job = driver.sweep_to_field(0) + # sweep down to zero at the fast sweep rate + job = driver.sweep_to_field(0, driver.fast_sweep_rate) job.wait_for_completion(self.check_for_interruption, timeout=60, refresh_time=1) self.write_in_database('field', target_value) - + class ApplyMagFieldAndDropTask(InstrumentTask): """Use a supraconducting magnet to apply a magnetic field. Parallel task. diff --git a/tests/tasks/tasks/instr/instr_helper.py b/tests/tasks/tasks/instr/instr_helper.py index 9a9fb5a..38bad14 100644 --- a/tests/tasks/tasks/instr/instr_helper.py +++ b/tests/tasks/tasks/instr/instr_helper.py @@ -25,7 +25,8 @@ class DummyJob(InstrJob): """ def __init__(self): - super(DummyJob, self).__init__(lambda: True, 0.1, lambda: None) + super(DummyJob, self).__init__(lambda: True, 0.1, lambda: None, + lambda: None) class HelperMeta(type): diff --git a/tests/tasks/tasks/instr/test_apply_mag_field_task.py b/tests/tasks/tasks/instr/test_apply_mag_field_task.py index 790519e..4a016eb 100644 --- a/tests/tasks/tasks/instr/test_apply_mag_field_task.py +++ b/tests/tasks/tasks/instr/test_apply_mag_field_task.py @@ -43,7 +43,9 @@ def setup(self): {'Test1': {'connections': {'C': {'owner': [], 'output_fluctuations': 1e-6, - 'heater_state': []}}, + 'heater_state': ['On', 'Off'], + 'fast_sweep_rate': '1.', + 'field_sweep_rate': '1.'}}, 'settings': {'S': {'sweep_to_field': [DummyJob(), DummyJob(), DummyJob()], 'sweep_to_persistent_field': [DummyJob()],