From 4495e221cb94d0a36466d0ba524227e8b9019652 Mon Sep 17 00:00:00 2001 From: Alexey Shkarin Date: Wed, 22 Dec 2021 21:33:49 +0100 Subject: [PATCH] Added KJL300 pressure gauge --- docs/.apidoc/pylablib.devices.KJL.rst | 24 +++++ docs/.apidoc/pylablib.devices.rst | 1 + docs/devices/KJL.rst | 39 ++++++++ docs/devices/basic_sensors_list.txt | 1 + docs/devices/basic_sensors_root.rst | 3 +- pylablib/devices/KJL/__init__.py | 1 + pylablib/devices/KJL/base.py | 117 ++++++++++++++++++++++++ pylablib/thread/devices/KJL/__init__.py | 1 + pylablib/thread/devices/KJL/base.py | 31 +++++++ 9 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 docs/.apidoc/pylablib.devices.KJL.rst create mode 100644 docs/devices/KJL.rst create mode 100644 pylablib/devices/KJL/__init__.py create mode 100644 pylablib/devices/KJL/base.py create mode 100644 pylablib/thread/devices/KJL/__init__.py create mode 100644 pylablib/thread/devices/KJL/base.py diff --git a/docs/.apidoc/pylablib.devices.KJL.rst b/docs/.apidoc/pylablib.devices.KJL.rst new file mode 100644 index 0000000..6456a7c --- /dev/null +++ b/docs/.apidoc/pylablib.devices.KJL.rst @@ -0,0 +1,24 @@ +pylablib.devices.KJL package +============================ + +Submodules +---------- + +pylablib.devices.KJL.base module +-------------------------------- + +.. automodule:: pylablib.devices.KJL.base + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pylablib.devices.KJL + :members: + :inherited-members: + :undoc-members: + :show-inheritance: diff --git a/docs/.apidoc/pylablib.devices.rst b/docs/.apidoc/pylablib.devices.rst index e1c39e3..dc45426 100644 --- a/docs/.apidoc/pylablib.devices.rst +++ b/docs/.apidoc/pylablib.devices.rst @@ -17,6 +17,7 @@ Subpackages pylablib.devices.HighFinesse pylablib.devices.IMAQ pylablib.devices.IMAQdx + pylablib.devices.KJL pylablib.devices.Lakeshore pylablib.devices.LaserQuantum pylablib.devices.Leybold diff --git a/docs/devices/KJL.rst b/docs/devices/KJL.rst new file mode 100644 index 0000000..587fb90 --- /dev/null +++ b/docs/devices/KJL.rst @@ -0,0 +1,39 @@ +.. _sensors_kjl: + +.. note:: + Basic sensors communication concepts are described on the corresponding :ref:`page ` + +Kurt J. Lesker pressure gauges +============================== + +KJL manufactures a range of pressure gauges and controllers with several different standards and communication protocols. The code has been tested with KJL300 pressure gauge using its built-in RS232 connection. + +The main device classes are :class:`pylablib.devices.KJL.KJL300<.KJL.base.KJL300>`. + + +Software requirements +----------------------- + +The devices provide a bare RS232 interface, so any appropriate USB-to-RS232 adapter should work. + + +Connection +----------------------- + +Since the devices are identified as COM ports, they use the standard :ref:`connection method `, and all you need to know is their COM-port address (e.g., ``COM5``):: + + >> from pylablib.devices import KJL + >> gauge = KJL.KJL300("COM5") + >> gauge.close() + + +Operation +----------------------- + +KJL300 +~~~~~~~~~~~~~~~~~~~~~~~ + +The operation of this gauge is fairly straightforward, but there is a couple of points to keep in mind: + + - Even standard RS232 operation requires specifying the device RS485 address. IT can be specified using ``addr`` parameter on creation. By default, the class assumes the factory default of 1, but if it is ever changed on the device, it needs to be specified correctly. + - By default, the pressure is always returned and set in Pa regardless of the display units. \ No newline at end of file diff --git a/docs/devices/basic_sensors_list.txt b/docs/devices/basic_sensors_list.txt index 38af04f..54393b8 100644 --- a/docs/devices/basic_sensors_list.txt +++ b/docs/devices/basic_sensors_list.txt @@ -3,4 +3,5 @@ - :ref:`Lakeshore `: temperature sensors. Tested with Lakeshore 218. - :ref:`Pfeiffer `: pressure gauges. Tested with TPG261 and DPG202 controllers. - :ref:`Leybold `: pressure gauges. Tested with ITR90 gauge. +- :ref:`Kurt J. Lesker `: pressure gauges. Tested with KJL300 gauge. - :ref:`Thorlabs quadrature detector controller `. Tested with TPA101. \ No newline at end of file diff --git a/docs/devices/basic_sensors_root.rst b/docs/devices/basic_sensors_root.rst index cf82091..9715ced 100644 --- a/docs/devices/basic_sensors_root.rst +++ b/docs/devices/basic_sensors_root.rst @@ -18,4 +18,5 @@ Currently supported sensors: Ophir Lakeshore Pfeiffer - Leybold \ No newline at end of file + Leybold + KJL \ No newline at end of file diff --git a/pylablib/devices/KJL/__init__.py b/pylablib/devices/KJL/__init__.py new file mode 100644 index 0000000..a117707 --- /dev/null +++ b/pylablib/devices/KJL/__init__.py @@ -0,0 +1 @@ +from .base import KJL300, KJLError \ No newline at end of file diff --git a/pylablib/devices/KJL/base.py b/pylablib/devices/KJL/base.py new file mode 100644 index 0000000..aadea28 --- /dev/null +++ b/pylablib/devices/KJL/base.py @@ -0,0 +1,117 @@ +from ...core.devio import comm_backend + +import collections +import re +import time + + + +class KJLError(comm_backend.DeviceError): + """Generic KJL device error""" +class KJLBackendError(KJLError,comm_backend.DeviceBackendError): + """Generic KJL backend communication error""" + + + +TKJL300DeviceInfo=collections.namedtuple("TKJL300DeviceInfo",["swver"]) +class KJL300(comm_backend.ICommBackendWrapper): + """ + KJL300 series pressure gauge. + + Args: + conn: serial connection parameters (usually port or a tuple containing port and baudrate) + addr: RS485 address (reqired both for RS-485 and for RS-232 communication; factory default is 1) + """ + Error=KJLError + def __init__(self, conn, addr=1): + instr=comm_backend.new_backend(conn,"serial",term_read="\r",term_write="\r",defaults={"serial":("COM1",19200)},datatype="str",reraise_error=KJLBackendError) + comm_backend.ICommBackendWrapper.__init__(self,instr) + self.addr=addr + self._add_info_variable("device_info",self.get_device_info) + self._add_status_variable("pressure",self.get_pressure,priority=5) + self._add_settings_variable("relay_setpoints",self.get_relay_setpoints,self.set_relay_setpoints,mux=((1,2),)) + try: + self.query("VER") + except self.instr.Error: + self.close() + raise + + def _make_msg(self, msg): + return "#{:02X}{}".format(self.addr,msg) + _reply_re=re.compile(r"^(\*|\?)(\d{2}) (.*)$") + def _parse_reply(self, msg): + m=self._reply_re.match(msg) + if m is None: + raise self.Error("can not parse the reply: {}".format(msg)) + if int(m[2])!=self.addr: + raise self.Error("reply address {} does not agree with the set address {}".format(int(m[1]),self.addr)) + if m[1]=="?": + raise self.Error("request raised an error: {}".format(m[3])) + return m[3] + def comm(self, msg): + """Send a command to the device""" + fmsg=self._make_msg(msg) + freply=self.instr.ask(fmsg) + reply=self._parse_reply(freply) + if reply!="PROGM OK": + raise self.Error("unexpected command reply: '{}' (expect '{}')".format(reply,"PROGM OK")) + def query(self, msg): + fmsg=self._make_msg(msg) + freply=self.instr.ask(fmsg) + return self._parse_reply(freply) + + def get_device_info(self): + """Get device info (a tuple ``(swver)``)""" + return TKJL300DeviceInfo(self.query("VER")) + + def reset(self, confirm_addr=False): + """ + Reset the controller. + + If ``confirm_addr==True``, set current RS485 address again (required for resetting after some commands). + """ + if confirm_addr: + self.comm("SA{:02X}".format(self.addr)) + fmsg=self._make_msg("RST") + self.instr.write(fmsg) + time.sleep(50E-3) + + def _toPa(self, v): + return float(v)*133.322 # return and set values are always in Torr + def _fromPa(self, v): + return "{:0.2E}".format(v/133.322) # return and set values are always in Torr + def get_pressure(self): + """Get current pressure in Pa""" + return self._toPa(self.query("RD")) + + def get_relay_setpoints(self, relay=1): + """ + Get relay setpoints (in Pa). + + `relay` is the relay index (either 1 or 2). + Return tuple ``(on, off)`` for on-below and off-above pressures (``on`` is always smaller than ``off``) + """ + q="RL" if relay==1 else "RH" + return self._toPa(self.query("{}+".format(q))),self._toPa(self.query("{}-".format(q))) + def set_relay_setpoints(self, relay=1, on=None, off=None, reset=True): + """ + Set relay setpoints (in Pa). + + `relay` is the relay index (either 1 or 2). `on` and `off` are on-below and off-above pressures (``on`` is always smaller than ``off``). + If ``reset==True``, reset the device after changing the setpoints (required to take effect). + ``None`` values are left unchanged. + """ + q="SL" if relay==1 else "SH" + if on is not None: + self.comm("{}+{}".format(q,self._fromPa(on))) + if off is not None: + self.comm("{}-{}".format(q,self._fromPa(off))) + if reset: + self.reset(confirm_addr=True) + return self.get_relay_setpoints() + def set_zero(self, pressure=0): + """Set vacuum calibration point (in Pa)""" + self.comm("TZ{}".format(self._fromPa(pressure))) + def set_span(self, pressure=1E5): + """Set atmosphere calibration point (in Pa)""" + self.comm("TS{}".format(self._fromPa(pressure))) \ No newline at end of file diff --git a/pylablib/thread/devices/KJL/__init__.py b/pylablib/thread/devices/KJL/__init__.py new file mode 100644 index 0000000..9c3db53 --- /dev/null +++ b/pylablib/thread/devices/KJL/__init__.py @@ -0,0 +1 @@ +from .base import KJL300Thread \ No newline at end of file diff --git a/pylablib/thread/devices/KJL/base.py b/pylablib/thread/devices/KJL/base.py new file mode 100644 index 0000000..9eb04aa --- /dev/null +++ b/pylablib/thread/devices/KJL/base.py @@ -0,0 +1,31 @@ +from ... import device_thread + + +class KJL300Thread(device_thread.DeviceThread): + """ + KJL300 pressure gauge device thread. + + Device args: + - ``conn``: device connection (usually, COM-port address) + - ``addr``: RS485 address (reqired both for RS-485 and for RS-232 communication; factory default is 1) + + Variables: + - ``pressure``: last measured pressure + """ + def connect_device(self): + with self.using_devclass("KJL.KJL300",host=self.remote) as cls: + self.device=cls(conn=self.conn,addr=self.addr) + def setup_task(self, conn, addr=1, remote=None): + self.device_reconnect_tries=5 + self.conn=conn + self.addr=addr + self.remote=remote + self.add_job("update_measurements",self.update_measurements,.5) + self.add_job("update_parameters",self.update_parameters,5) + def update_measurements(self): + """Update current measurements""" + if self.open(): + self.v["pressure"]=self.device.get_pressure() + else: + self.v["pressure"]=0 + self.sleep(1.) \ No newline at end of file