"""
This script interfaces with the Thermostreamer MPI_TA5000.
:Date: |today|
:Author: Semi-ATE <info@Semi-ATE.org>
todo:
MPI_TA5K send no acknowledge,
check if this module is running for other Thermostreamer
"""
import time
from enum import Enum
from datetime import datetime
from pylab_ml.common.spinner import spinner
from pylab_ml.base_instrument import logger
from pylab_ml.collate_instrument import Interface
from pylab_ml.thermostreamer.base_thermostreamer import Base_Thermostreamer
from pylab_ml.attributes import create_attributes
[docs]
class MPI_TA5K(create_attributes, Base_Thermostreamer):
"""
Interface to the Thermostreamer MPI_TA5000.
:Date: |today|
:Author: Semi-ATE <info@Semi-ATE.org>
.. image:: ../static/mpi_ta5000.jpg
"""
# create functions or proberty and wrap it to the inst.funcname:
# if state != necessary state -> switch to the necessary state
# proberty/function name -> inst.funcname (get,set) , range, call functions
# range : None, Enum or range value
# call functions: see help in attributes
_properties = {
'airtemp': (('TMPA?', None), None, None),
'compressor': (('COOL?', 'COOL '), 'Compressor', {'sac': '_compressor(value)'}),
'dutsensortype': (('DSNS?', 'DSNS '), [0, 4], None),
'dutmode': (('DUTM?', 'DUTM '), [0, 2], None),
'duttemp': (('TMPD?', None), None, None),
'flow': (('FLOW?', 'FLOW '), 'Flow', {'sa': '_wait4flow(value)'}),
'flowrate': (('FLWM?', 'FLWM '), [5, 18], None),
'head': (('HEAD?', 'HEAD '), 'Head', {'sac': '_head(value)'}),
'headlock': (('HDLK?', 'HDLK '), 'Headlock', None),
'llim': (('LLIM?', 'LLIM '), [-150.0, 25.0], None),
'ramp': (('RAMP?', 'RAMP '), [0.0, 99.9], None),
'setn': (('SETN?', 'SETN '), [0-17], None),
'setupfile': (('SFIL?', 'SFIL '), None, None),
'soak': (('SOAK?', 'SOAK '), [0, 9999], None),
'state': (('TECR?', None), None, {'gac': '_trstate(value)'}),
'Temp': (('SETP?', 'SETP '), None, None),
'ulim': (('ULIM?', 'ULIM '), [25.0, 225.0], None),
'window': (('WNDW?', 'WNDW '), [0.1, 9.9], None),
'what': (('WHAT?', None), None, None),
}
_states = {1: ' at temperature (soak time has elapsed)',
2: ' not at temperature',
4: ' end of test (test time has elapsed',
8: ' end of one cycle',
16: ' end of all cycle',
32: ' stopped cycling ("stop on fail" signal was received',
128: ' datalogging on',
}
interchoices = [Interface.usbserial, Interface.gpib]
[docs]
class Flow(Enum):
"""Enum for Flow control."""
off = 0
"""value for flow off"""
on = 1
"""value for flow on"""
[docs]
class Head(Enum):
"""Enum for Head control."""
up = 0
"""value for head up"""
down = 1
"""value for head down"""
[docs]
class Headlock(Enum):
"""Enum for Headlock control."""
off = 0.0
on = 1.0
[docs]
class Compressor(Enum):
"""Enum for Compressor control."""
off = 0
on = 1
[docs]
def __init__(self, addr=None, interface=None, backend=None, identify=True, instName=None):
"""
Initialise the Thermostreamer.
Parameters
----------
addr (int):
Interface address
interface (dev_interface.Instrument):
GPIB, USBSerial
backend (str):
Backend for the interface, e.g. 'pyvisa', 'pyvisa-py', 'python-gpib', 'pyserial-asyncio'
identify (bool):
Query IDN after initialization
instName (string):
Instance Name from parent.
Raises
------
TimeoutError:
Timeout after set temp.
InvalidInstrumentConnection:
Something is wrong with the connection.
Examples:
>>> # Initialization
>>> thermo = Thermo(addr=1) # GPIB address
More detailed examples:
Common for MPI_TA5k.py: :download:`examples/thermostreamer/mpi_ta5k <../../../examples/thermostreamer/mpi_ta5k.py>`
"""
create_attributes.__init__(self)
kwargs = {"addr": addr, "interface": interface, "backend": backend, "identify": identify, "instName": instName}
Base_Thermostreamer.__init__(self, **kwargs)
logger.debug("Class {}".format(self.__class__.__name__))
self.setdefault()
[docs]
def setup_inst(self):
"""Set for instrument settings."""
self.createattributes(self._properties)
super().setup_inst()
self.__is_initialized = True
[docs]
def setdefault(self):
"""Set for default values."""
logger.measure("{} set default values".format(self.instName))
# self.head=0 # head sould be hold in it' old position
self.flow = self.Flow.off
self.llim = -85 # lower temperature limit
self.ulim = 180 # upper temperature limit
self.window = 1.0 # temp window: 0.1 .. 9.9
self.soak = 20 # tsoak time: 0 .. 9999s
self.flowrate = 10 # flow rate: 4 .. 18
self.dut_mode = 0 # 0: off (air control)
self._setpoint = None
self.dutsensortype = 0 # 0: no dut sensor
self.get_state()
# ----------------------------------------------------------
# doc strings for _properties, propertie themselve will create form dictionary _properties
@property
def airtemp(self):
"""Current air temperture."""
@property
def compressor(self):
"""Set/get compressor on/off.
| 'off'(=0, Compressor.off).
| 'on'(=1, Compressor.on)
"""
@property
def dutsensortype(self):
"""Set/get dut sensortype.
| 0: no dut sensor
| 1: type T thermocouple
| 2: type K thermocouple
| 3: RTD
"""
@property
def dutmode(self):
"""Set/get dut mode.
| 0: off (air control)
| 1: on (dut control)
| 2: TC Meter mode
"""
@property
def duttemp(self):
"""Get current dut temperture."""
@property
def flow(self):
"""Set/get flow.
| 'off'(=0, Flow.off).
| 'on'(=1, FLow.on).
"""
@property
def flowrate(self):
"""Set/get air flow rate."""
@property
def head(self):
"""Set/get head.
| 'up'(=0, Head.up)
| 'down'(=1, Head.down)
"""
@property
def headlock(self):
"""Set/get head lock.
| 'off' (=0, Headlock.off) -> unlock
| 'on' (=1, Headlock.on) -> lock
"""
@property
def llim(self):
"""Set/get lower limit."""
@property
def ramp(self):
"""Set/get ramp rate for the currently selected setpoint."""
@property
def setn(self):
"""Set/get setpoint.
| set: select a setpoint to the current setpoint
| get: read the current setpoint number
"""
@property
def setupfile(self):
"""Set/get filename for setup file.
| get the filename,
| set = load the test setup file with the filename
"""
@property
def soak(self):
"""Set/get soak time for a given setpoint."""
@property
def state(self):
"""Get temperature status register."""
@property
def Temp(self):
"""Get/set the currently selected setpoint temperatur."""
@property
def ulim(self):
"""Set/get upper limit."""
@property
def window(self):
"""set/get setpoint temperature window."""
# end doc strings for _properties
# ----------------------------------------------------------
def _compressor(self, value):
"""Set compressor on/off."""
self.flow = self.Flow.off
def _head(self, value):
"""Set head up/down."""
if self.headlock == self.Headlock.on:
logger.error("{}.Head is locked! Can not move Head".format(self.instName))
return self.ATTR_ERROR
return None
def _wait4flow(self, value):
"""Set flow on/off and wait until flow is set."""
# logging.disable(logging.MEASURE)
# if value==self.flow.on and self.head==self.Head.up: self.head=self.Head.down
if value == self.Flow.on and float(self.inst.query('HDLK?')) == 0 and self.inst.query('HEAD?') == '0':
logger.error("{}.Head is up and not locked! Can not set Flow.on".format(self.instName))
return self.ATTR_ERROR
start = time.time()
with spinner():
while int(self.inst.query('FLOW?')) != value.value:
time.sleep(0.5)
if time.time()-start > 20:
logger.error("{}.Flow couln't set flow := {}".format(self.instName, value))
return self.ATTR_ERROR
def _trstate(self, value):
for bit in range(0, 7):
mask = value & (1 << bit)
if mask > 0 and mask in self._states:
print(self._states[mask])
@property
def id(self):
"""Query IDN."""
value = self.inst.query('*IDN?')
return value.replace('\r', '').replace('\n', '')
[docs]
def get_id(self):
"""
Get identification from Thermostreamer.
Returns
-------
str
Identification string from Thermostreamer.
"""
self.inst.write('*IDN?')
return self.inst.read()
@property
def temp(self):
"""
Set/get DUT sensor or air temperature.
- get temperature
- or set the currently selected setpoint temperatur and wait until temperture is reached.
Parameters
----------
value : float or tuple
| if float: set temperature and use default timeout of 600s
| if tuple: (temperature, timeout) -> set temperature and wait until temperture is reached or timeout is elapsed
Returns
-------
float :
* in Air mode: get main air temperature
* in Dut mode: get DUT sensor temperatur
* in TC Meter mode: siehe TEMP? in Manual.
Example:
>>> thermo.temp = -10, 100 # -> set temperatur=-10 and timeout to 100s (default 600s)
>>> thermo.temp = 80 # -> set temperatur=80 and timeout is default = 600s
>>> print(thermo.temp) # get temperature
"""
return float(self.inst.query('TEMP?'))
@temp.setter
def temp(self, value, timeout=600):
if isinstance(value, tuple):
timeout = value[1]
value = value[0]
newvalue = self._temp_set(value, timeout)
logger.measure(f"{self.instName}.temp == {newvalue}")
self._setpoint = float(value)
def _temp_set(self, temp=None, timeout=600):
"""
Get/Set the setpoint temperature.
Parameters
----------
temp (float, optional):
If None, get the current setpoint temperature.
If float, set the setpoint temperature to this value and wait until the temperature is reached or timeout is elapsed.
Default is None.
timeout (int, optional):
Timeout in seconds to wait for the temperature to reach the setpoint. Default is 600 seconds.
Returns
-------
temp (float):
The current setpoint temperature after setting it (if temp is not None) or the current setpoint temperature (if temp is None).
"""
if temp is None:
return float(self.inst.query('SETP?'))
# elif self._setpoint==None or float(temp) != self._setpoint:
if -99.9 <= temp <= 225.0:
self.inst.write(f'SETP {temp}')
else:
fmtstr = f'temp value {temp} must be in [-99.9, 225.0]'
logger.error(fmtstr)
raise ValueError(fmtstr)
# if int(self.inst.query('HEAD?'))==self.Head.up.value:
# fmtstr = "{}.temp: Thermo Stream can set Temperature, if head is 'Up'".format(self.instName)
# logger.error(fmtstr)
# raise ValueError(fmtstr)
if int(self.inst.query('FLOW?')) == self.Flow.off.value:
self.flow = self.Flow.on
logger.measure("{}.temp := {}".format(self.instName, temp))
# wait until steady state of desired temperature
start = datetime.now()
with spinner():
while 1:
lifesign = False
self.inst.write('TECR?') # read temperature event condition register
if self.inst.read() == '1':
break
if ((datetime.now() - start).seconds) % 30 == 0:
print('\r {} not at temperature or soak time not elapsed {}°C != {}°C, timeout in {}s \r'
.format(self.instName, float(self.inst.query('TEMP?')), temp, timeout-(datetime.now() - start).seconds), end='')
lifesign = True
if timeout > 0 and (datetime.now() - start).seconds > timeout:
fmtstr = 'timeout: T_set = {}, T_meas = {}'
raise TimeoutError(fmtstr.format(temp, self.temp))
time.sleep(2)
if lifesign:
print(' ')
self._setpoint = float(temp)
return self.temp
[docs]
def get_state(self):
"""Get actual state and configuration."""
lines = []
lines.append('Setup File: {}'.format(self.setupfile))
lines.append('lower temp limit: {}'.format(self.llim))
lines.append('upper temp limit: {}'.format(self.ulim))
lines.append('temp window: {}'.format(self.window))
lines.append('soak time: {}s'.format(self.soak))
lines.append('flow rate: {}'.format(self.flowrate))
lines.append('flow: {}'.format(self.flow))
lines.append('head: {}'.format(self.head))
lines.append('temp: {}'.format(self.temp))
lines.append('temp_set: {}'.format(self._temp_set()))
_dutmode = {
0: 'off (air control)',
1: 'on (dut control)',
2: 'TC meter mode',
}
val = self.dut_mode
lines.append('dut mode: {}: {}'.format(val, _dutmode[val]))
lines.append('dut sensor type: {}'.format(self.dutsensortype))
for i in lines:
logger.info(i)
return lines
if __name__ == '__main__':
from pylab_ml.base_instrument import logsetup
logsetup()
thermo = MPI_TA5K(addr=1, instName='thermo')
# thermo.setupfile='GPIB-Config'
thermo.get_state()
# thermo.headlock # for Thermostreamer at 3d-Coil
thermo.flow = 1
thermo.head = 'up'
thermo.head = 'down'
thermo.flow = 'on'
thermo.flow
thermo.flow = 1
thermo.flow = thermo.Flow.on
thermo.flow = "sjflskjf"
thermo.state
thermo.temp = 20, 100
thermo.state
thermo.ulim
thermo.ulim = 450
thermo.llim
thermo.window
thermo.soak
thermo.ramp
thermo.flow
thermo.dutsensortype
thermo.dutmode
print('Temperatur= {}°C'.format(thermo.temp))
thermo.temp = 30, 100
thermo.temp = 20
thermo.flow = 'off'
thermo.close()