# OxfordInstruments_ILM200.py class, to perform the communication between the Wrapper and the device
# Copyright (c) 2017 QuTech (Delft)
# Code is available under the available under the `MIT open-source license <https://opensource.org/licenses/MIT>`__
#
# Pieter Eendebak <pieter.eendebak@tno.nl>, 2017
# Takafumi Fujita <t.fujita@tudelft.nl>, 2016
# Guenevere Prawiroatmodjo <guen@vvtp.tudelft.nl>, 2009
# Pieter de Groot <pieterdegroot@gmail.com>, 2009
from time import sleep
import pyvisa
import pyvisa.constants
import logging
from qcodes.instrument import VisaInstrument
[docs]
class OxfordInstruments_ILM200(VisaInstrument):
"""
This is the qcodes driver for the Oxford Instruments ILM 200 Helium Level Meter.
Usage:
Initialize with
<name> = instruments.create('name', 'OxfordInstruments_ILM200', address='<Instrument address>')
<Instrument address> = ASRL4::INSTR
Note: Since the ISOBUS allows for several instruments to be managed in parallel, the command
which is sent to the device starts with '@n', where n is the ISOBUS instrument number.
"""
[docs]
def __init__(self, name, address, number=1, **kwargs):
"""
Initializes the Oxford Instruments ILM 200 Helium Level Meter.
Args:
name (str): name of the instrument
address (str): instrument address
number (int): ISOBUS instrument number (number=1 is specific to the ILM in F008)
Returns:
None
"""
logging.debug(__name__ + ' : Initializing instrument')
super().__init__(name, address, **kwargs)
self.visa_handle.set_visa_attribute(pyvisa.constants.VI_ATTR_ASRL_STOP_BITS,
pyvisa.constants.VI_ASRL_STOP_TWO)
self._address = address
self._number = number
self._values = {}
self.add_parameter('level',
label='level',
get_cmd=self._do_get_level,
unit='%')
self.add_parameter('status',
get_cmd=self._do_get_status)
self.add_parameter('rate',
get_cmd=self._do_get_rate,
set_cmd=self._do_set_rate)
# a dummy command to avoid the initial error
try:
self.get_idn()
sleep(70e-3) # wait for the device to be able to respond
self._read() # to flush the buffer
except Exception as ex:
logging.debug(ex)
def _execute(self, message):
"""
Write a command to the device and read answer. This function writes to
the buffer by adding the device number at the front, instead of 'ask'.
Args:
message (str) : write command for the device
Returns:
None
"""
logging.info(
__name__ + ' : Send the following command to the device: %s' % message)
self.visa_handle.write('@%s%s' % (self._number, message))
sleep(70e-3) # wait for the device to be able to respond
result = self._read()
if result.find('?') >= 0:
print("Error: Command %s not recognized" % message)
else:
return result
def _read(self):
"""
Reads the total bytes in the buffer and outputs as a string.
Args:
None
Returns:
message (str)
"""
# because protocol has no termination chars the read reads the number
# of bytes in the buffer
bytes_in_buffer = self.visa_handle.bytes_in_buffer
# a workaround for a timeout error in the pyvsia read_raw() function
with(self.visa_handle.ignore_warning(pyvisa.constants.VI_SUCCESS_MAX_CNT)):
mes = self.visa_handle.visalib.read(
self.visa_handle.session, bytes_in_buffer)
# cannot be done on same line for some reason
mes = str(mes[0].decode())
return mes
[docs]
def get_idn(self):
"""
Overrides the function of Instrument since ILM does not support `*IDN?`
This string is supposed to be a
comma-separated list of vendor, model, serial, and firmware, but
semicolon and colon are also common separators so we accept them here
as well.
Returns:
A dict containing vendor, model, serial, and firmware.
"""
try:
idstr = '' # in case self.ask fails
idstr = self._get_version().split()
# form is supposed to be comma-separated, but we've seen
# other separators occasionally
idparts = [idstr[3] + ' ' + idstr[4], idstr[0], idstr[5],
idstr[1] + ' ' + idstr[2]]
# in case parts at the end are missing, fill in None
if len(idparts) < 4:
idparts += [None] * (4 - len(idparts))
except Exception as ex:
logging.warning('Error getting or interpreting *IDN?: ' + repr(idstr))
logging.debug(ex)
idparts = [None, None, None, None]
return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts))
[docs]
def get_all(self):
"""
Reads all implemented parameters from the instrument,
and updates the wrapper.
"""
logging.info(__name__ + ' : reading all settings from instrument')
self.level.get()
self.status.get()
self.rate.get()
[docs]
def close(self):
"""
Safely close connection
"""
logging.info(__name__ + ' : Closing ILM200 connection')
self.local()
super().close()
# Functions: Monitor commands
def _get_version(self):
"""
Identify the device
Args:
None
Returns:
identification (str): should be 'ILM200 Version 1.08 (c) OXFORD 1994\r'
"""
logging.info(__name__ + ' : Identify the device')
return self._execute('V')
def _do_get_level(self):
"""
Get Helium level of channel 1.
Args:
None
Returns:
result (float) : Helium level
"""
logging.info(__name__ + ' : Read level of channel 1')
result = self._execute('R1')
return float(result.replace("R", "")) / 10
def _do_get_status(self):
"""
Get status of the device.
"""
logging.info(__name__ + ' : Get status of the device.')
result = self._execute('X')
usage = {
0: "Channel not in use",
1: "Channel used for Nitrogen level",
2: "Channel used for Helium Level (Normal pulsed operation)",
3: "Channel used for Helium Level (Continuous measurement)",
9: "Error on channel (Usually means probe unplugged)"
}
# current_flowing = {
# 0 : "Curent not flowing in Helium Probe Wire",
# 1 : "Curent not flowing in Helium Probe Wire"
# }
# auto_fill_status = {
# 00 : "End Fill (Level > FULL)",
# 01 : "Not Filling (Level < FULL, Level > FILL)",
# 10 : "Filling (Level < FULL, Level > FILL)",
# 11 : "Start Filling (Level < FILL)"
# }
return usage.get(int(result[1]), "Unknown")
def _do_get_rate(self):
"""
Get helium meter channel 1 probe rate
Input:
None
Output:
rate(int) :
0 : "SLOW"
1 : "FAST"
"""
rate = {
1: "1 : Helium Probe in FAST rate",
0: "0 : Helium Probe in SLOW rate"
}
result = self._execute('X')
return rate.get(int(format(int(result[5:7]), '08b')[6]), "Unknown")
[docs]
def remote(self):
"""
Set control to remote & locked
"""
logging.info(__name__ + ' : Set control to remote & locked')
self.set_remote_status(1)
[docs]
def local(self):
"""
Set control to local & locked
"""
logging.info(__name__ + ' : Set control to local & locked')
self.set_remote_status(0)
[docs]
def set_remote_status(self, mode):
"""
Set remote control status.
Args:
mode(int) :
0 : "Local and locked",
1 : "Remote and locked",
2 : "Local and unlocked",
3 : "Remote and unlocked",
Returns:
None
"""
status = {
0: "Local and locked",
1: "Remote and locked",
2: "Local and unlocked",
3: "Remote and unlocked",
}
logging.info(__name__ + ' : Setting remote control status to %s' %
status.get(mode, "Unknown"))
self._execute('C%s' % mode)
# Functions: Control commands (only recognised when in REMOTE control)
[docs]
def set_to_slow(self):
"""
Set helium meter channel 1 to slow mode.
"""
self.set_remote_status(1)
logging.info(__name__ + ' : Setting Helium Probe in SLOW rate')
self._execute('S1')
self.set_remote_status(3)
[docs]
def set_to_fast(self):
"""
Set helium meter channel 1 to fast mode.
"""
self.set_remote_status(1)
logging.info(__name__ + ' : Setting Helium Probe in FAST rate')
self._execute('T1')
self.set_remote_status(3)
def _do_set_rate(self, rate):
"""
Set helium meter channel 1 probe rate
Args:
rate(int) :
0 : "SLOW"
1 : "FAST"
"""
self.set_remote_status(1)
if rate == 0:
self.set_to_slow()
elif rate == 1:
self.set_to_fast()
self.set_remote_status(3)
logging.info(self._do_get_rate())