"""
Flow and Pressure controller, PR4000B, from MKS Instruments.
"""
import re
from msl.equipment.resources import register
from msl.equipment.connection_serial import ConnectionSerial
from msl.equipment.exceptions import MKSInstrumentsError
[docs]@register(manufacturer=r'^MKS', model=r'PR4000B', flags=re.IGNORECASE)
class PR4000B(ConnectionSerial):
ERROR_CODES = {
'#E010': 'Syntax Error',
'#E020': 'Failed to execute command',
'#E001': 'Communication Error',
'#E002': 'ADC Overflow or Underflow',
'#E003': 'Range Error, Setpoint < 0 or out of range',
'#W001': 'Offset > 250 mV'
}
UNITS = {
0: 'ubar',
1: 'mbar',
2: 'bar',
3: 'mTor',
4: 'Torr',
5: 'KTor',
6: 'Pa',
7: 'kPa',
8: 'mH2O',
9: 'cH2O',
10: 'PSI',
11: 'N/qm',
12: 'SCCM',
13: 'SLM',
14: 'SCM',
15: 'SCFH',
16: 'SCFM',
17: 'mA',
18: 'V',
19: '%',
20: 'C'
}
SIGNAL_MODES = {
0: 'METER',
1: 'OFF',
2: 'INDEP',
3: 'EXTRN',
4: 'SLAVE',
5: 'RTD'
}
LIMIT_MODES = {
0: 'SLEEP',
1: 'LIMIT',
2: 'BAND',
3: 'MLIMIT',
4: 'MBAND'
}
TAGS = {
0: 'SP',
1: 'VA',
2: 'CH',
3: 'FL',
4: 'PR',
5: 'EX'
}
def __init__(self, record):
"""Flow and Pressure controller, PR4000B, from MKS Instruments.
The default settings for the RS232 connection are:
* Baud rate = 9600
* Data bits = 7
* Stop bits = 1
* Parity = ODD
* Flow control = None
The baud rate and parity can be changed on the controller. The data bits,
stop bits, and flow control cannot be changed. A null modem (cross over)
cable is required when using a USB to RS232 converter. RS485 support is
not implemented.
Do not instantiate this class directly. Use the :meth:`~.EquipmentRecord.connect`
method to connect to the equipment.
Parameters
----------
record : :class:`~.EquipmentRecord`
A record from an :ref:`equipment-database`.
"""
super(PR4000B, self).__init__(record)
self.set_exception_class(MKSInstrumentsError)
def _check(self, reply):
"""Check a reply for an error.
Parameters
----------
reply : :class:`str`
The reply to check.
Returns
-------
:class:`str`
The reply if the there was no error, otherwise raises an exception.
"""
if reply.startswith('#'):
msg = self.ERROR_CODES.get(reply, 'Undefined error code')
self.raise_exception('{}: {}'.format(reply, msg))
return reply
def _check_channel(self, channel):
if channel < 1 or channel > 2:
self.raise_exception('Invalid channel {}'.format(channel))
def _get_index_from_value(self, string, dictionary):
string_lower = string.lower()
for key, value in dictionary.items():
if value.lower() == string_lower:
return key
[docs] def auto_zero(self, channel):
"""Auto zero a channel
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The offset.
"""
self._check_channel(channel)
return int(self.query('AZ{}'.format(channel)))
[docs] def default(self, mode):
"""Reset to the default configuration.
Parameters
----------
mode : :class:`str`
The mode to reset. One of ``Pressure``, ``Flow``, ``P`` or ``F`` (case insensitive).
"""
upper = mode.upper()
if upper not in ['P', 'F', 'PRESSURE', 'FLOW']:
self.raise_exception('Invalid default mode {!r}, must one of: Pressure, Flow, P or F'.format(mode))
self._check(self.query('DF,{}'.format(upper[0])))
[docs] def displays_enable(self, display, enable):
"""Turn a display on or off.
Parameters
----------
display : :class:`int`
The display number [1, 4].
enable : :class:`bool`
Whether to turn the display on, :data:`True`, or off, :data:`False`.
"""
if display < 1 or display > 4:
self.raise_exception('Invalid display number {}, must be between [1, 4]'.format(display))
state = 'ON' if enable else 'OFF'
self._check(self.query('DP{},{}'.format(display, state)))
[docs] def displays_setup(self, display, line, tag, channel):
"""Configure a display.
Parameters
----------
display : :class:`int`
The display number [1, 4].
line : :class:`int`
The line number, 1 or 2.
tag : :class:`int` or :class:`str`
The tag to use (0=SP, 1=VA, 2=CH, 3=FL, 4=PR, 5=EX). For example,
setting tag to 4 or ``'PR'`` are equivalent.
channel : :class:`int`
The channel, either 1 or 2.
"""
if display < 1 or display > 4:
self.raise_exception('Invalid display number {}, must be between [1, 4]'.format(display))
if line < 1 or line > 2:
self.raise_exception('Invalid line number {}, must be 1 or 2'.format(line))
self._check_channel(channel)
if isinstance(tag, str):
index = self._get_index_from_value(tag, self.TAGS)
if index is None:
self.raise_exception('Invalid tag {!r}'.format(tag))
else:
index = int(tag)
self._check(self.query('DP{},{},{},{}'.format(display, line, index, channel)))
[docs] def display_4(self, enable):
"""Whether to enable or disable display 4.
Parameters
----------
enable : :class:`bool`
Whether to enable or disable display 4.
"""
state = 'ON' if enable else 'OFF'
self._check(self.query('DP4,{}'.format(state)))
[docs] def get_access_channel(self, channel):
"""Get the setpoint and the state of the valve of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The setpoint value.
:class:`bool`
Whether the valve is on, :data:`True`, or off, :data:`False`.
"""
self._check_channel(channel)
split = self.query('?AC{}'.format(channel)).rstrip().split(',')
return float(split[0]), split[1] == 'ON'
[docs] def get_actual_value(self, channel):
"""Get the actual value of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The value.
"""
self._check_channel(channel)
return float(self.query('AV{}'.format(channel)))
[docs] def get_address(self):
"""Get the address.
Returns
-------
:class:`int`
The address.
"""
return int(self.query('?AD'))
[docs] def get_dead_band(self, channel):
"""Get the dead band of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The dead band.
"""
self._check_channel(channel)
return float(self.query('?DB{}'.format(channel)))
[docs] def get_dialog(self):
"""Get the current dialog index that is displayed.
Returns
-------
:class:`int`
The dialog index.
"""
return int(self.query('?DG'))
[docs] def get_display_text(self):
"""Get the display text.
Returns
-------
:class:`str`
The display text.
"""
return self.query('?DT').rstrip()
[docs] def get_external_output_range(self, channel):
"""Get the external output range of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The external output range.
"""
self._check_channel(channel)
return int(self.query('?EO{}'.format(channel)))
[docs] def get_gain(self, channel):
"""Get the gain of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The gain.
"""
self._check_channel(channel)
return float(self.query('?GN{}'.format(channel)))
[docs] def get_interface_mode(self):
"""Get the interface mode.
Returns
-------
:class:`int`
The interface mode.
"""
return int(self.query('?IM'))
[docs] def get_limit_mode(self, channel):
"""Get the limit mode of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The index of the limit mode.
:class:`str`
The name of the limit mode.
"""
self._check_channel(channel)
mode = int(self.query('?LM{}'.format(channel)))
return mode, self.LIMIT_MODES[mode]
[docs] def get_linearization_point(self, channel, point):
"""Get the point in the linearization table of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
point : :class:`int`
The point in the table [0, 10].
Returns
-------
:class:`float`
The x value.
:class:`float`
The y value.
"""
self._check_channel(channel)
if point < 0 or point > 10:
self.raise_exception('Invalid point {}, must be between [0, 10]'.format(point))
split = self.query('?LN{},{}'.format(channel, point)).split(',')
return float(split[0]), float(split[1])
[docs] def get_linearization_size(self, channel):
"""Get the size of the linearization table of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The size of the table.
"""
self._check_channel(channel)
return int(self.query('?LS{}'.format(channel)))
[docs] def get_lower_limit(self, channel):
"""Get the lower limit of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The lower limit.
"""
self._check_channel(channel)
return float(self.query('?LL{}'.format(channel)))
[docs] def get_offset(self, channel):
"""Get the offset of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The offset.
"""
self._check_channel(channel)
return int(self.query('?OF{}'.format(channel)))
[docs] def get_output_range(self, channel):
"""Get the output range of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The output range.
"""
self._check_channel(channel)
return int(self.query('?OT{}'.format(channel)))
[docs] def get_range(self, channel):
"""Get the range and unit of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The range.
:class:`int`
The unit index.
:class:`str`
The unit name.
"""
self._check_channel(channel)
split = self.query('?RG{}'.format(channel)).rstrip().split(',')
unit = int(split[1])
return float(split[0]), unit, self.UNITS[unit]
[docs] def get_relays(self, channel):
"""Get the relay state of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`bool`
Whether the relay is enabled or disabled.
"""
self._check_channel(channel)
return self.query('?RL{}'.format(channel)).rstrip() == 'ON'
[docs] def get_remote_mode(self):
"""Get the remote operation mode.
Returns
-------
:class:`bool`
Whether the remote operation mode is enabled, :data:`True`,
or disabled, :data:`False`.
"""
return self.query('?RT').rstrip() == 'ON'
[docs] def get_resolution(self):
"""Get whether 16-bit resolution is enabled.
Returns
-------
:class:`bool`
Whether 16-bit resolution is enabled, :data:`True`, or disabled, :data:`False`.
"""
return self.query('?RS').rstrip() == 'ON'
[docs] def get_rtd_offset(self, channel):
"""Get the RTD offset of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The offset.
"""
self._check_channel(channel)
return int(self.query('?RO{}'.format(channel)))
[docs] def get_scale(self, channel):
"""Get the scale of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The scale.
"""
return float(self.query('?SC{}'.format(channel)))
[docs] def get_setpoint(self, channel):
"""Get the setpoint of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The setpoint.
"""
self._check_channel(channel)
return float(self.query('?SP{}'.format(channel)))
[docs] def get_signal_mode(self, channel):
"""Get the signal mode of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`int`
The index number of the signal mode.
:class:`str`
The name of the signal mode.
"""
self._check_channel(channel)
mode = int(self.query('?SM{}'.format(channel)))
return mode, self.SIGNAL_MODES[mode]
[docs] def get_upper_limit(self, channel):
"""Get the upper limit of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`float`
The upper limit.
"""
self._check_channel(channel)
return float(self.query('?UL{}'.format(channel)))
[docs] def get_valves(self, channel):
"""Get the state of the valve of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
Returns
-------
:class:`bool`
Whether the valve is enabled or disabled.
"""
self._check_channel(channel)
return self.query('?VL{}'.format(channel)).rstrip() == 'ON'
[docs] def identity(self):
"""Returns the identity.
Returns
-------
:class:`str`
The identity (e.g., ``PR42vvrrsssss``, where vv is the version,
rr is the release and sssss is the serial number).
"""
return self.query('?ID')
[docs] def lock(self):
"""Lock setup."""
self._check(self.query('#1'))
[docs] def request_key(self):
"""Requests most recent key that was pressed.
Returns
-------
:class:`int`
The key that was most recently pressed.
:class:`int`
The number of key presses that occurred since the last time this
method was called.
"""
split = self.query('?KY').split(',')
return int(split[0]), int(split[1])
[docs] def reset_status(self):
"""Send the reset/status command."""
self._check(self.query('RE'))
[docs] def set_access_channel(self, channel, setpoint, valve):
"""Set the setpoint and the state of the valve for a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
setpoint : :class:`float`
The setpoint value.
valve : :class:`bool`
Whether to enable or disable the valve.
Returns
-------
:class:`float`
The actual setpoint value.
"""
self._check_channel(channel)
state = 'ON' if valve else 'OFF'
return float(self._check(self.query('AC{},{},{}'.format(channel, setpoint, state))))
[docs] def set_actual_value(self, channel, setpoint):
"""Set the actual value of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
setpoint : :class:`float`
The setpoint.
Returns
-------
:class:`float`
The actual value.
"""
self._check_channel(channel)
return float(self.query('AV{},{}'.format(channel, setpoint)))
[docs] def set_address(self, address):
"""Set the address.
Parameters
----------
address : :class:`int`
The address [0, 31].
"""
if address < 0 or address > 31:
self.raise_exception('Invalid address {}, must be between [0, 31]'.format(address))
self._check(self.query('AD,{}'.format(address)))
[docs] def set_dead_band(self, channel, band):
"""Set the dead band of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
band : :class:`float`
The dead band [0.0% to 9.9% of full scale].
"""
self._check_channel(channel)
self._check(self.query('DB{},{}'.format(channel, band)))
[docs] def set_dialog(self, index):
"""Set the display dialog.
Parameters
----------
index : :class:`int`
The dialog index (between 0 and 29 inclusive). See Appendix D
of the manual for more information.
"""
if index < 0 or index > 29:
self.raise_exception('Invalid dialog index {}, must be in the range [0, 29]'.format(index))
self._check(self.query('DG,{}'.format(index)))
[docs] def set_display_text(self, text, clear=True):
"""Set the display text.
To view the text on the display you must call :meth:`.set_dialog`
with the index equal to 3.
Parameters
----------
text : :class:`str`
The text to display. Maximum 32 characters.
clear : :class:`bool`, optional
Whether to clear the current display text before setting the new text.
"""
if len(text) > 32:
self.raise_exception('The display text must be <= 32 characters, got {!r}'.format(text))
if clear:
self._check(self.query('!DT'))
self._check(self.query('DT,{}'.format(text)))
[docs] def set_external_output_range(self, channel, range):
"""Set the external output range of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
range : :class:`int`
The external output range [1, 10] in Volts.
"""
self._check_channel(channel)
if range < 1 or range > 10:
self.raise_exception('Invalid external output range {}'.format(range))
self._check(self.query('EO{},{}'.format(channel, range)))
[docs] def set_gain(self, channel, gain):
"""Set the gain of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
gain : :class:`float`
The gain [0.001, 2.000].
"""
self._check_channel(channel)
self._check(self.query('GN{},{}'.format(channel, gain)))
[docs] def set_interface_mode(self, mode):
"""Set the interface mode.
Parameters
----------
mode : :class:`int`
The interface mode.
"""
self._check(self.query('IM,{}'.format(mode)))
[docs] def set_limit_mode(self, channel, mode):
"""Set the limit mode of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
mode : :class:`int` or :class:`str`
The limit mode as either an index number [0, 4] or a name (e.g., ``SLEEP``).
"""
self._check_channel(channel)
if isinstance(mode, str):
index = self._get_index_from_value(mode, self.LIMIT_MODES)
if index is None:
self.raise_exception('Invalid limit mode {!r}'.format(mode))
else:
index = int(mode)
if index < 0 or index > 4:
self.raise_exception('Invalid limit mode index {}, must be between [0, 4]'.format(index))
self._check(self.query('LM{},{}'.format(channel, index)))
[docs] def set_linearization_point(self, channel, point, x, y):
"""Set a point in the linearization table of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
point : :class:`int`
The point in the table [0, 10].
x : :class:`float`
The x value [-5% to 100% of full scale].
y : :class:`float`
The y value [-5% to 100% of full scale].
"""
self._check_channel(channel)
if point < 0 or point > 10:
self.raise_exception('Invalid point {}, must be between [0, 10]'.format(point))
self._check(self.query('LN{},{},{},{}'.format(channel, point, x, y)))
[docs] def set_linearization_size(self, channel, size):
"""Set the size of the linearization table of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
size : :class:`int`
The size of the table.
"""
self._check_channel(channel)
if size < 0 or size > 10:
self.raise_exception('Invalid size {}, must be between [0, 10]'.format(size))
self._check(self.query('LS{},{}'.format(channel, size)))
[docs] def set_lower_limit(self, channel, limit):
"""Set the lower limit of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
limit : :class:`float`
The lower limit [-5% to 110% of full scale].
"""
self._check_channel(channel)
self._check(self.query('LL{},{}'.format(channel, limit)))
[docs] def set_offset(self, channel, offset):
"""Set the offset of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
offset : :class:`int`
The offset [-250, 250].
"""
self._check_channel(channel)
if offset < -250 or offset > 250:
self.raise_exception('Invalid offset {}, must be between [-250, 250]'.format(offset))
self._check(self.query('OF{},{}'.format(channel, offset)))
[docs] def set_output_range(self, channel, range):
"""Set the output range of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
range : :class:`int`
The output range [1, 10] in Volts.
"""
self._check_channel(channel)
if range < 1 or range > 10:
self.raise_exception('Invalid output range {}'.format(range))
self._check(self.query('OT{},{}'.format(channel, range)))
[docs] def set_range(self, channel, range, unit):
"""Set the range and unit of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
range : :class:`float`
The range value.
unit : :class:`int` or :class:`str`
The unit as either an index number [0, 20] or a name (e.g., ``kPa``).
"""
self._check_channel(channel)
if isinstance(unit, str):
index = self._get_index_from_value(unit, self.UNITS)
if index is None:
self.raise_exception('Invalid unit {!r}'.format(unit))
else:
index = int(unit)
if index < 0 or index > 20:
self.raise_exception('Invalid unit index {}, must be between [0, 20]'.format(index))
self._check(self.query('RG{},{},{}'.format(channel, range, index)))
[docs] def set_relays(self, channel, enable):
"""Set the relay state of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
enable : :class:`bool`
Whether to enable or disable the relay.
"""
self._check_channel(channel)
state = 'ON' if enable else 'OFF'
self._check(self.query('RL{},{}'.format(channel, state)))
[docs] def set_remote_mode(self, enable):
"""Set the remote operation mode to be enable or disabled.
Parameters
----------
enable : :class:`bool`
Whether to enable or disable remote operation.
"""
mode = 'ON' if enable else 'OFF'
self._check(self.query('RT,{}'.format(mode)))
[docs] def set_resolution(self, enable):
"""Set the 16-bit resolution to be enable or disabled.
Parameters
----------
enable : :class:`bool`
Whether to enable or disable 16-bit resolution.
"""
state = 'ON' if enable else 'OFF'
self._check(self.query('RS,{}'.format(state)))
[docs] def set_rtd_offset(self, channel, offset):
"""Set the RTD offset of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
offset : :class:`int`
The RTD offset [-250, 250].
"""
self._check_channel(channel)
if offset < -250 or offset > 250:
self.raise_exception('Invalid RTD offset {}, must be between [-250, 250]'.format(offset))
self._check(self.query('RO{},{}'.format(channel, offset)))
[docs] def set_scale(self, channel, scale):
"""Set the scale of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
scale : :class:`float`
The scale.
"""
self._check_channel(channel)
self._check(self.query('SC{},{}'.format(channel, scale)))
[docs] def set_setpoint(self, channel, setpoint):
"""Set the setpoint of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
setpoint : :class:`float`
The setpoint.
"""
self._check_channel(channel)
self._check(self.query('SP{},{}'.format(channel, setpoint)))
[docs] def set_signal_mode(self, channel, mode):
"""Set the range and unit of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
mode : :class:`int` or :class:`str`
The signal mode as either an index number (e.g., between 0 and 5 inclusive)
or a name (e.g., ``INDEP``).
"""
self._check_channel(channel)
if isinstance(mode, str):
index = self._get_index_from_value(mode, self.SIGNAL_MODES)
if index is None:
self.raise_exception('Invalid signal mode {!r}'.format(mode))
else:
index = int(mode)
if index < 0 or index > 5:
self.raise_exception('Invalid signal mode index {}, must be between [0, 5]'.format(index))
self._check(self.query('SM{},{}'.format(channel, index)))
[docs] def set_tweak_control(self, enable):
"""Set tweak control.
Parameters
----------
enable : :class:`bool`
Whether to switch tweak control on or off.
"""
command = '$1' if enable else '$0'
self._check(self.query(command))
[docs] def set_upper_limit(self, channel, limit):
"""Set the upper limit of a particular channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
limit : :class:`float`
The upper limit [-5% to 110% of full scale].
"""
self._check_channel(channel)
self._check(self.query('UL{},{}'.format(channel, limit)))
[docs] def set_valves(self, channel, enable):
"""Set the state of the valve of a channel.
Parameters
----------
channel : :class:`int`
The channel, either 1 or 2.
"""
if channel == 0:
self.raise_exception('The manual indicates that you can specify channel=0 '
'to set both valves simultaneously, but that does not work')
self._check_channel(channel)
state = 'ON' if enable else 'OFF'
self._check(self.query('VL{},{}'.format(channel, state)))
[docs] def status(self):
"""Request status bits.
Returns
-------
:class:`int`
The status value.
:class:`str`
The binary representation of the value.
"""
value = int(self.query('ST'))
return value, '{:08b}'.format(value)
[docs] def unlock(self):
"""Unlock setup."""
self._check(self.query('#0'))