class ATS525(ATSBase): """Represent the TemptronicATS525 instruments. """ temperature_limit_air_low = Instrument.control( "LLIM?", "LLIM %g", """Control lower air temperature limit. :type: float Valid range between -60 to 25 (°C). Setpoints below current value cause “out of range” error in TS. """, validator=truncated_range, values=[-60, 25]) system_current = Instrument.measurement( "AMPS?", """Operating current. """, ) def __init__(self, adapter, **kwargs): super().__init__(adapter, name="Temptronic ATS-525 Thermostream", **kwargs)
class GenericInstrument(FakeInstrument): # Use truncated_range as this easily lets us test for the range boundaries fake_ctrl = Instrument.control( "", "%d", "docs", validator=truncated_range, values=(1, 10), dynamic=True, ) fake_setting = Instrument.setting( "%d", "docs", validator=truncated_range, values=(1, 10), dynamic=True, ) fake_measurement = Instrument.measurement( "", "docs", values={ 'X': 1, 'Y': 2, 'Z': 3 }, map_values=True, dynamic=True, )
class ATS545(ATSBase): """Represents the TemptronicATS545 instrument. Coding example .. code-block:: python ts = ATS545('ASRL3::INSTR') # replace adapter address ts.configure() # basic configuration (defaults to T-DUT) ts.start() # starts flow (head position not changed) ts.set_temperature(25) # sets temperature to 25 degC ts.wait_for_settling() # blocks script execution and polls for settling ts.shutdown(head=False) # disables thermostream, keeps head down """ temperature_limit_air_low = Instrument.control( "LLIM?", "LLIM %g", """Control lower air temperature limit. :type: float Valid range between -80 to 25 (°C). Setpoints below current value cause “out of range” error in TS. """, validator=truncated_range, values=[-80, 25]) mode = Instrument.measurement( "WHAT?", """Returns an integer indicating what the system is doing at the time the query is processed. 10 = on Operator screen (manual mode) 0 = on Cycle screen (program mode) 63 = initial state after power-up """, values={ 'manual': 10, # 5 in ATSbase 'program': 0, # 6 in ATSbase 'initial': 63 }, # after power up, reading is 63 map_values=True) def next_setpoint(self): """not implemented in ATS545 set ``self.set_point_number`` instead """ raise NotImplementedError def __init__(self, adapter, **kwargs): super().__init__(adapter, name="Temptronic ATS-545 Thermostream", **kwargs)
class Fake(FakeInstrument): x = Instrument.control( "", "%d", "", validator=strict_discrete_set, values=range(10), )
class Fake(FakeInstrument): x = Instrument.control( "", "%d", "", validator=strict_discrete_set, values={5: 1, 10: 2, 20: 3}, map_values=True, )
class Fake(FakeInstrument): x = Instrument.setting( "OUT %d", "", set_process=lambda v: int(bool(v)), dynamic=dynamic, )
class Fake(FakeInstrument): x = Instrument.control( "", "JUNK%d", "", preprocess_reply=lambda v: v.replace('JUNK', ''), cast=int )
class Fake(FakeInstrument): x = Instrument.control( "", "%d", "", validator=strict_discrete_set, values=[4, 5, 6, 7], map_values=True, )
class Fake(FakeInstrument): x = Instrument.control( "", "%d", "", validator=strict_discrete_set, values={'X': 1, 'Y': 2, 'Z': 3}, map_values=True, )
class Fake(FakeInstrument): x = Instrument.control( "", "JUNK%d", "", validator=strict_range, values=[0, 10], get_process=lambda v: int(v.replace('JUNK', '')), )
class Fake(FakeInstrument): x = Instrument.control( "", "%d,%d", "", dynamic=dynamic, )
class Fake(FakeInstrument): def __init__(self): super().__init__(preprocess_reply=lambda v: v.replace('JUNK', '')) x = Instrument.control( "", "JUNK%d", "", cast=int )
class Fake(FakeInstrument): x = Instrument.control( "", "%d", "", validator=strict_range, values=[5e-3, 120e-3], get_process=lambda v: v * 1e-3, set_process=lambda v: v * 1e3, )
class Fake(FakeInstrument): x = Instrument.measurement( "", "", values={ 'X': 1, 'Y': 2, 'Z': 3 }, map_values=True, )
class Fake(FakeInstrument): x = Instrument.control( "", "%d,%d", "", )
class Fake(Instrument): x = Instrument.control( "", "%d", doc )
class Fake(Instrument): x = Instrument.control("", "%d", doc, dynamic=dynamic)
class ATSBase(Instrument): """The base class for Temptronic ATSXXX instruments. """ remote_mode = Instrument.setting( "%s", """``True`` disables TS GUI but displays a “Return to local" switch.""", validator=strict_discrete_set, values={True: "%RM", False: r"%GL"}, map_values=True ) maximum_test_time = Instrument.control( "TTIM?", "TTIM %g", """Control maximum allowed test time (s). :type: float This prevents TS from staying at a single temperature forever. Valid range: 0 to 9999 """, validator=truncated_range, values=[0, 9999] ) dut_mode = Instrument.control( "DUTM?", "DUTM %g", """ ``On`` enables DUT mode, ``OFF`` enables air mode :type: string """, validator=strict_discrete_set, values={'ON': 1, 'OFF': 0}, map_values=True ) dut_type = Instrument.control( "DSNS?", "DSNS %g", """Control DUT sensor type. :type: string Possible values are: ====== ====== String Meaning ====== ====== '' no DUT 'T' T-DUT 'K' K-DUT ====== ====== Warning: If in DUT mode without DUT being connected, TS flags DUT error """, validator=strict_discrete_set, values={None: 0, 'T': 1, 'K': 2}, map_values=True ) dut_constant = Instrument.control( "DUTC?", "DUTC %g", """Control thermal constant (default 100) of DUT. :type: float Lower values indicate lower thermal mass, higher values indicate higher thermal mass respectively. """, validator=truncated_range, values=[20, 500] ) head = Instrument.control( "HEAD?", "HEAD %s", """Control TS head position. :type: string ``down``: transfer head to lower position ``up``: transfer head to elevated position """, validator=strict_discrete_set, values={'up': 0, 'down': 1}, map_values=True ) enable_air_flow = Instrument.setting( "FLOW %g", """Set TS air flow. ``True`` enables air flow, ``False`` disables it :type: bool """, validator=strict_discrete_set, map_values=True, values={True: 1, False: 0} ) temperature_limit_air_low = Instrument.control( "LLIM?", "LLIM %g", """Control lower air temperature limit. :type: float Valid range between -99 to 25 (°C). Setpoints below current value cause “out of range” error in TS. """, validator=truncated_range, values=[-99, 25] ) temperature_limit_air_high = Instrument.control( "ULIM?", "ULIM %g", """upper air temperature limit. :type: float Valid range between 25 to 255 (°C). Setpoints above current value cause “out of range” error in TS. """, validator=truncated_range, values=[25, 225] ) temperature_limit_air_dut = Instrument.control( "ADMD?", "ADMD %g", """Air to DUT temperature limit. :type: float Allowed difference between nozzle air and DUT temperature during settling. Valid range between 10 to 300 °C in 1 degree increments. """, validator=truncated_range, values=[10, 300] ) temperature_setpoint = Instrument.control( "SETP?", "SETP %g", """Set or get selected setpoint's temperature. :type: float Valid range is -99.9 to 225.0 (°C) or as indicated by :attr:`~.temperature_limit_air_high` and :attr:`~.temperature_limit_air_low`. Use convenience function :meth:`~ATSBase.set_temperature` to prevent unexpected behavior. """, validator=truncated_range, values=[-99.9, 225] ) temperature_setpoint_window = Instrument.control( "WNDW?", "WNDW %g", """Setpoint's temperature window. :type: float Valid range is between 0.1 to 9.9 (°C). Temperature status register flags ``at temperature`` in case soak time elapsed while temperature stays in between bounds given by this value around the current setpoint. """, validator=truncated_range, values=[0.1, 9.9] ) temperature_soak_time = Instrument.control( "SOAK?", "SOAK %g", """ Set the soak time for the currently selected setpoint. :type: float Valid range is between 0 to 9999 (s). Lower values shorten cycle times. Higher values increase cycle times, but may reduce settling errors. See :attr:`~.temperature_setpoint_window` for further information. """, validator=truncated_range, values=[0.0, 9999] ) temperature = Instrument.measurement( "TEMP?", """Read current temperature with 0.1 °C resolution. :type: float Temperature readings origin depends on :attr:`dut_mode` setting. Reading higher than 400 (°C) indicates invalidity. """ ) temperature_condition_status_code = Instrument.measurement( "TECR?", """Temperature condition status register. :type: :class:`.TemperatureStatusCode` """, validator=truncated_range, values=[0, 255], get_process=lambda v: TemperatureStatusCode(v), ) set_point_number = Instrument.control( "SETN?", "SETN %g", """Select a setpoint to be the current setpoint. :type: int Valid range is 0 to 17 when on the Cycle screen or or 0 to 2 in case of operator screen (0=hot, 1=ambient, 2=cold). """, validator=truncated_range, values=[0, 17] ) local_lockout = Instrument.setting( "%s", """``True`` disables TS GUI, ``False`` enables it. """, validator=strict_discrete_set, values={True: r"%LL", False: r"%GL"}, map_values=True ) auxiliary_condition_code = Instrument.measurement( "AUXC?", """Read out auxiliary condition status register. :type: int Relevant flags are: ====== ====== Bit Meaning ====== ====== 10 None 9 Ramp mode 8 Mode: 0 programming, 1 manual 7 None 6 TS status: 0 start-up, 1 ready 5 Flow: 0 off, 1 on 4 Sense mode: 0 air, 1 DUT 3 Compressor: 0 on, 1 off (heating possible) 2 Head: 0 lower, upper 1 None 0 None ====== ====== Refere to chapter 4 in the manual """, validator=truncated_range, values=[0, 1023] ) copy_active_setup_file = Instrument.setting( "CFIL %g", """Copy active setup file (0) to setup n (1 - 12). :type: int """, validator=strict_range, values=[1, 12] ) compressor_enable = Instrument.setting( "COOL %g", """ ``True`` enables compressors, ``False`` disables it. :type: Boolean """, validator=strict_discrete_set, map_values=True, values={True: 1, False: 0} ) total_cycle_count = Instrument.control( "CYCC?", "CYCC %g", """Set or read current cycle count (1 - 9999). :type: int Sending 0 will stop cycling """, validator=truncated_range, values=[0, 9999] ) cycling_enable = Instrument.setting( "CYCL %g", """CYCL Start/stop cycling. :type: bool cycling_enable = True (start cycling) cycling_enable = False (stop cycling) """, validator=strict_discrete_set, map_values=True, values={True: 1, False: 0} ) current_cycle_count = Instrument.measurement( "CYCL?", """Read the number of cycles to do :type: int """, ) error_code = Instrument.measurement( "EROR?", # it is indeed EROR """Read the device-specific error register (16 bits). :type: :class:`.ErrorCode` """, validator=truncated_range, values=[0, int(2**16-1)], get_process=lambda v: ErrorCode(v), ) nozzle_air_flow_rate = Instrument.measurement( "FLWR?", """Read main nozzle air flow rate in scfm. """ ) main_air_flow_rate = Instrument.measurement( "FLRL?", """Read main nozzle air flow rate in liters/sec. """ ) learn_mode = Instrument.control( "LRNM?", "LRNM %g", """Control DUT automatic tuning (learning). :type: bool ``False``: off ``True``: automatic tuning on """, validator=strict_discrete_set, map_values=True, values={True: 1, False: 0} ) ramp_rate = Instrument.control( "RAMP?", "RAMP %g", """Control ramp rate (K / min). :type: float allowed values: nn.n: 0 to 99.9 in 0.1 K per minute steps. nnnn: 100 to 9999 in 1 K per minute steps. """, validator=strict_discrete_set, values={i/10 for i in range(1000)} | {i for i in range(100, 10000)} ) dynamic_temperature_setpoint = Instrument.measurement( "SETD?", """Read the dynamic temperature setpoint. :type: float """ ) load_setup_file = Instrument.setting( "SFIL %g", """loads setup file SFIL. Valid range is between 1 to 12. :type: int """, validator=strict_range, values=[1, 12] ) temperature_event_status = Instrument.measurement( "TESR?", """ temperature event status register. :type: :class:`.TemperatureStatusCode` Hint: Reading will clear register content. """, validator=strict_range, values=[0, 255] ) air_temperature = Instrument.measurement( "TMPA?", """Read air temperature in 0.1 °C increments. :type: float """ ) dut_temperature = Instrument.measurement( "TMPD?", """Read DUT temperature, in 0.1 °C increments. :type: float """ ) mode = Instrument.measurement( "WHAT?", """Returns an integer indicating what the system is doing at the time the query is processed. 5: on Operator screen (manual mode) 6: on Cycle screen (program mode) """, values={'manual': 5, 'program': 6, }, map_values=True ) def __init__(self, adapter, **kwargs): super().__init__(adapter, query_delay=0.05, **kwargs) def reset(self): """Reset (force) the System to the Operator screen. :returns: self """ self.write("RSTO") return self def enter_cycle(self): """Enter Cycle by sending ``RMPC 1``. :returns: self """ self.write("RMPC 1") return self def enter_ramp(self): """Enter Ramp by sending ``RMPS 0``. :returns: self """ self.write("RMPS 0") return self def clear(self): """Clear device-specific errors. See :attr:`~.error_code` for further information. """ self.write("CLER") return self def next_setpoint(self): """Step to the next setpoint during temperature cycling. """ self.write("NEXT") def configure(self, temp_window=1, dut_type='T', soak_time=30, dut_constant=100, temp_limit_air_low=-60, temp_limit_air_high=220, temp_limit_air_dut=50, maximum_test_time=1000 ): """Convenience method for most relevant configuration properties. :param dut_type: string: indicating which DUT type to use :param soak_time: float: elapsed time in soak_window before settling is indicated :param soak_window: float: Soak window size or temperature settlings bounds (K) :param dut_constant: float: time constant of DUT, higher values indicate higher thermal mass :param temp_limit_air_low: float: minimum flow temperature limit (°C) :param temp_limit_air_high: float: maximum flow temperature limit (°C) :param temp_limit_air_dut: float: allowed temperature difference (K) between DUT and Flow :param maximum_test_time: float: maximum test time (seconds) for a single temperature point (safety) :returns: self """ self.temperature_setpoint_window = temp_window self.temperature_limit_air_low = temp_limit_air_low self.temperature_limit_air_high = temp_limit_air_high self.dut_type = dut_type self.maximum_test_time = maximum_test_time if dut_type is None: self.dut_mode = 'OFF' else: self.dut_constant = dut_constant self.dut_mode = 'ON' self.temperature_limit_air_dut = temp_limit_air_dut self.temperature_soak_time = soak_time # logging: wd = self.temperature_setpoint_window airflwlimlow = self.temperature_limit_air_low airflwlimhigh = self.temperature_limit_air_high dut = self.dut_type tst_time = self.maximum_test_time airdutlim = self.temperature_limit_air_dut sktime = self.temperature_soak_time message = ( "Configuring TS finished, reading back:\n" f"DUT type: {dut}\n" f"Temperature Window: {wd} K\n" f"Maximum test time: {tst_time} s\n" f"Air flow temperature limit low: {airflwlimlow:.1f} K\n" f"Air flow temperature limit high: {airflwlimhigh:.1f} K\n" f"Air to DUT temperature limit: {airdutlim} degC\n" f"Soak time {sktime} s\n" ) log.info(message) return self def set_temperature(self, set_temp): """sweep to a specified setpoint. :param set_temp: target temperature for DUT (float) :returns: self """ if self.mode == 'manual': message = f"new set point temperature: {set_temp:.1f} Deg" log.info(message) if set_temp <= 20: self.set_point_number = 2 # cold elif set_temp < 30: self.set_point_number = 1 # ambient elif set_temp >= 30: self.set_point_number = 0 # hot else: raise ValueError(f"Temperature {set_temp} is impossible to set!") self.temperature_setpoint = set_temp # fixed typo in attr name return self def wait_for_settling(self, time_limit=300): """block script execution until TS is settled. :param time_limit: set the maximum blocking time within TS has to settle (float). :returns: self Script execution is blocked until either TS has settled or time_limit has been exceeded (float). """ time.sleep(1) t = 0 t_start = time.time() while(not self.at_temperature()): # assert at temperature time.sleep(1) t = time.time() - t_start tstatus = self.temperature_condition_status_code message = ("temp_set= %4.1f deg, " "temp= %4.1f deg, " "time= %.2f s, " "status= %s" ) log.info(message, self.temperature_setpoint, self.temperature, t, tstatus) if t > time_limit: log.info('no settling achieved') break log.info('finished this temperature point') return self def shutdown(self, head=False): """Turn down TS (flow and remote operation). :param head: Lift head if ``True`` :returns: self """ self.enable_air_flow = 0 self.remote_mode = False if head: self.head = 'up' super().shutdown() return self def start(self, enable_air_flow=True): """start TS in remote mode. :param enable_air_flow: flow starts if ``True`` :returns: self """ self.remote_mode = 1 self.enable_air_flow = enable_air_flow # enable TS return self def error_status(self): """Returns error status code (maybe used for logging). :returns: :class:`.ErrorCode` """ code = self.error_code if not code == 0: log.warning('%s', code) return code def cycling_stopped(self): """:returns: ``True`` if cycling has stopped. """ return self.temperature_condition_status_code == TemperatureStatusCode.CYCLING_STOPPED def end_of_all_cycles(self): """:returns: ``True`` if cycling has stopped. """ return self.temperature_condition_status_code == TemperatureStatusCode.END_OF_ALL_CYCLES def end_of_one_cycle(self): """:returns: ``True`` if TS is at end of one cycle. """ return self.temperature_condition_status_code == TemperatureStatusCode.END_OF_ONE_CYCLE def end_of_test(self): """:returns: ``True`` if TS is at end of test. """ return self.temperature_condition_status_code == TemperatureStatusCode.END_OF_TEST def not_at_temperature(self): """:returns: ``True`` if not at temperature. """ return self.temperature_condition_status_code == TemperatureStatusCode.NOT_AT_TEMPERATURE def at_temperature(self): """:returns: ``True`` if at temperature. """ return self.temperature_condition_status_code == TemperatureStatusCode.AT_TEMPERATURE