Skip to content

Commit

Permalink
GPIBUSB connection enhancements (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
scasagrande committed Jan 2, 2020
1 parent 141861f commit eba82b2
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 44 deletions.
10 changes: 5 additions & 5 deletions instruments/abstract_instruments/comm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

from .abstract_comm import AbstractCommunicator

from .file_communicator import FileCommunicator
from .gpib_communicator import GPIBCommunicator
from .loopback_communicator import LoopbackCommunicator
from .serial_communicator import SerialCommunicator
from .socket_communicator import SocketCommunicator
from .usb_communicator import USBCommunicator
from .serial_communicator import SerialCommunicator
from .visa_communicator import VisaCommunicator
from .loopback_communicator import LoopbackCommunicator
from .gi_gpib_communicator import GPIBCommunicator
from .file_communicator import FileCommunicator
from .usbtmc_communicator import USBTMCCommunicator
from .visa_communicator import VisaCommunicator
from .vxi11_communicator import VXI11Communicator
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""
Provides a communication layer for an instrument connected via a Galvant
Industries GPIB adapter.
Industries or Prologix GPIB adapter.
"""

# IMPORTS #####################################################################
Expand All @@ -11,6 +11,7 @@
from __future__ import division
from __future__ import unicode_literals

from enum import Enum
import io
import time

Expand All @@ -27,29 +28,43 @@ class GPIBCommunicator(io.IOBase, AbstractCommunicator):

"""
Communicates with a SocketCommunicator or SerialCommunicator object for
use with Galvant Industries GPIBUSB or GPIBETHERNET adapters.
use with Galvant Industries or Prologix GPIBUSB or GPIBETHERNET adapters.
It essentially wraps those physical communication layers with the extra
overhead required by the Galvant GPIB adapters.
overhead required by the GPIB adapters.
"""

# pylint: disable=too-many-instance-attributes
def __init__(self, filelike, gpib_address):
def __init__(self, filelike, gpib_address, model="gi"):
super(GPIBCommunicator, self).__init__(self)

self._model = self.Model(model)
self._file = filelike
self._gpib_address = gpib_address
self._file.terminator = "\r"
self._version = int(self._file.query("+ver"))
if self._model == GPIBCommunicator.Model.gi:
self._version = int(self._file.query("+ver"))
if self._model == GPIBCommunicator.Model.pl:
self._file.sendcmd("++auto 0")
self._terminator = None
self.terminator = "\n"
self._eoi = True
self._timeout = 1000 * pq.millisecond
if self._version <= 4:
if self._model == GPIBCommunicator.Model.gi and self._version <= 4:
self._eos = 10
else:
self._eos = "\n"

# ENUMS #

class Model(Enum):
"""
Enum containing the supported GPIB controller models
"""
#: Galvant Industries
gi = "gi"
#: Prologix, LLC
pl = "pl"

# PROPERTIES #

@property
Expand Down Expand Up @@ -95,12 +110,12 @@ def timeout(self):
@timeout.setter
def timeout(self, newval):
newval = assume_units(newval, pq.second)
if self._version <= 4:
if self._model == GPIBCommunicator.Model.gi and self._version <= 4:
newval = newval.rescale(pq.second)
self._file.sendcmd('+t:{}'.format(newval.magnitude))
elif self._version >= 5:
self._file.sendcmd('+t:{}'.format(int(newval.magnitude)))
else:
newval = newval.rescale(pq.millisecond)
self._file.sendcmd("++read_tmo_ms {}".format(newval.magnitude))
self._file.sendcmd("++read_tmo_ms {}".format(int(newval.magnitude)))
self._file.timeout = newval.rescale(pq.second)
self._timeout = newval.rescale(pq.second)

Expand All @@ -127,7 +142,7 @@ def terminator(self, newval):
if isinstance(newval, str):
newval = newval.lower()

if self._version <= 4:
if self._model == GPIBCommunicator.Model.gi and self._version <= 4:
if newval == 'eoi':
self.eoi = True
elif not isinstance(newval, int):
Expand All @@ -148,7 +163,7 @@ def terminator(self, newval):
self.eoi = False
self.eos = newval
self._terminator = chr(newval)
elif self._version >= 5:
else:
if newval != "eoi":
self.eos = newval
self.eoi = False
Expand Down Expand Up @@ -182,10 +197,10 @@ def eoi(self, newval):
if not isinstance(newval, bool):
raise TypeError("EOI status must be specified as a boolean")
self._eoi = newval
if self._version >= 5:
self._file.sendcmd("++eoi {}".format('1' if newval else '0'))
else:
if self._model == GPIBCommunicator.Model.gi and self._version <= 4:
self._file.sendcmd("+eoi:{}".format('1' if newval else '0'))
else:
self._file.sendcmd("++eoi {}".format('1' if newval else '0'))

@property
def eos(self):
Expand All @@ -203,12 +218,12 @@ def eos(self):

@eos.setter
def eos(self, newval):
if self._version <= 4:
if self._model == GPIBCommunicator.Model.gi and self._version <= 4:
if isinstance(newval, (str, bytes)):
newval = ord(newval)
self._file.sendcmd("+eos:{}".format(newval))
self._eos = newval
elif self._version >= 5:
else:
if isinstance(newval, int):
newval = str(chr(newval))
if newval == "\r\n":
Expand Down Expand Up @@ -299,8 +314,8 @@ def flush_input(self):
def _sendcmd(self, msg):
"""
This is the implementation of ``sendcmd`` for communicating with
the Galvant Industries GPIB adapter. This function is in turn wrapped by
the concrete method `AbstractCommunicator.sendcmd` to provide consistent
the GPIB adapters. This function is in turn wrapped by the concrete
method `AbstractCommunicator.sendcmd` to provide consistent
logging functionality across all communication layers.
:param str msg: The command message to send to the instrument
Expand All @@ -309,7 +324,10 @@ def _sendcmd(self, msg):

if msg == '':
return
self._file.sendcmd('+a:' + str(self._gpib_address))
if self._model == GPIBCommunicator.Model.gi:
self._file.sendcmd("+a:{0}".format(str(self._gpib_address)))
else:
self._file.sendcmd("++addr {0}".format(str(self._gpib_address)))
time.sleep(sleep_time)
self.eoi = self.eoi
time.sleep(sleep_time)
Expand All @@ -323,13 +341,18 @@ def _sendcmd(self, msg):
def _query(self, msg, size=-1):
"""
This is the implementation of ``query`` for communicating with
the Galvant Industries GPIB adapter. This function is in turn wrapped by
the concrete method `AbstractCommunicator.query` to provide consistent
the GPIB adapters. This function is in turn wrapped by the concrete
method `AbstractCommunicator.query` to provide consistent
logging functionality across all communication layers.
If a ``?`` is not present in ``msg`` then the adapter will be
instructed to get the response from the instrument via the ``+read``
command.
The Galvant Industries adaptor is set to automatically get a
response if a ``?`` is present in ``msg``. If it is not present,
then the adapter will be instructed to get the response from the
instrument via the ``+read`` command.
The Prologix adapter is set to not get a response unless told to do
so. It is instructed to get a response from the instrument via the
``++read`` command.
:param str msg: The query message to send to the instrument
:param int size: The number of bytes to read back from the instrument
Expand All @@ -338,6 +361,8 @@ def _query(self, msg, size=-1):
:rtype: `str`
"""
self.sendcmd(msg)
if '?' not in msg:
if self._model == GPIBCommunicator.Model.gi and '?' not in msg:
self._file.sendcmd('+read')
if self._model == GPIBCommunicator.Model.pl:
self._file.sendcmd('++read')
return self._file.read(size).strip()
24 changes: 17 additions & 7 deletions instruments/abstract_instruments/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ def open_serial(cls, port=None, baud=9600, vid=None, pid=None,
return cls(ser)

@classmethod
def open_gpibusb(cls, port, gpib_address, timeout=3, write_timeout=3):
def open_gpibusb(cls, port, gpib_address, timeout=3, write_timeout=3, model="gi"):
"""
Opens an instrument, connecting via a
`Galvant Industries GPIB-USB adapter`_.
Expand All @@ -544,6 +544,8 @@ def open_gpibusb(cls, port, gpib_address, timeout=3, write_timeout=3):
instrument before timing out.
:param float write_timeout: Number of seconds to wait when writing to the
instrument before timing out.
:param str model: The brand of adapter to be connected to. Currently supported
is "gi" for Galvant Industries, and "pl" for Prologix LLC.
:rtype: `Instrument`
:return: Object representing the connected instrument.
Expand All @@ -559,18 +561,26 @@ def open_gpibusb(cls, port, gpib_address, timeout=3, write_timeout=3):
timeout=timeout,
write_timeout=write_timeout
)
return cls(GPIBCommunicator(ser, gpib_address))
return cls(GPIBCommunicator(ser, gpib_address, model))

@classmethod
def open_gpibethernet(cls, host, port, gpib_address):
def open_gpibethernet(cls, host, port, gpib_address, model="pl"):
"""
.. warning:: The GPIB-Ethernet adapter that this connection would
use does not actually exist, and thus this class method should
not be used.
Opens an instrument, connecting via a Prologix GPIBETHERNET adapter.
:param str host: Name or IP address of the instrument.
:param int port: TCP port on which the insturment is listening.
:param int gpib_address: Address on the connected GPIB bus assigned to
the instrument.
:param str model: The brand of adapter to be connected to. Currently supported
is "gi" for Galvant Industries, and "pl" for Prologix LLC.
.. warning:: This function has been setup for use with the Prologix
GPIBETHERNET adapter but has not been tested as confirmed working.
"""
conn = socket.socket()
conn.connect((host, port))
return cls(GPIBCommunicator(conn, gpib_address))
return cls(GPIBCommunicator(conn, gpib_address, model))

@classmethod
def open_visa(cls, resource_name):
Expand Down
5 changes: 3 additions & 2 deletions instruments/tests/test_base_instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def test_instrument_open_gpibusb(mock_serial_manager, mock_gpib_comm):
mock_serial_manager.new_serial_connection.return_value.__class__ = SerialCommunicator
mock_gpib_comm.return_value.__class__ = GPIBCommunicator

inst = ik.Instrument.open_gpibusb("/dev/port", gpib_address=1)
inst = ik.Instrument.open_gpibusb("/dev/port", gpib_address=1, model="gi")

assert isinstance(inst._file, GPIBCommunicator) is True

Expand All @@ -270,7 +270,8 @@ def test_instrument_open_gpibusb(mock_serial_manager, mock_gpib_comm):

mock_gpib_comm.assert_called_with(
mock_serial_manager.new_serial_connection.return_value,
1
1,
"gi"
)


Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for the GI GPIBUSB communication layer
Unit tests for the GPIBUSB communication layer
"""

# IMPORTS ####################################################################
Expand Down Expand Up @@ -199,7 +199,7 @@ def test_gpibusbcomm_timeout():
unit_eq(comm.timeout, 1000 * pq.millisecond)

comm.timeout = 5000 * pq.millisecond
comm._file.sendcmd.assert_called_with("++read_tmo_ms 5000.0")
comm._file.sendcmd.assert_called_with("++read_tmo_ms 5000")


def test_gpibusbcomm_close():
Expand Down Expand Up @@ -235,7 +235,7 @@ def test_gpibusbcomm_sendcmd():
comm._file.sendcmd.assert_has_calls([
mock.call("+a:1"),
mock.call("++eoi 1"),
mock.call("++read_tmo_ms 1000.0"),
mock.call("++read_tmo_ms 1000"),
mock.call("++eos 2"),
mock.call("mock")
])
Expand Down

0 comments on commit eba82b2

Please sign in to comment.