Skip to content

Commit

Permalink
Merge pull request #2 from Galvant/dev
Browse files Browse the repository at this point in the history
Pull from upstream
  • Loading branch information
cgranade committed Apr 10, 2013
2 parents 5f55a74 + 3aade5c commit 4ddcabb
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 65 deletions.
1 change: 1 addition & 0 deletions python/src/instruments/abstract_instruments/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from instruments.abstract_instruments.wrapperabc import WrapperABC
from instruments.abstract_instruments.instrument import Instrument
from instruments.abstract_instruments.multimeter import Multimeter

89 changes: 64 additions & 25 deletions python/src/instruments/abstract_instruments/gi_gpib.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
import numpy as np

import serialManager
from instruments.abstract_instruments import WrapperABC

## CLASSES #####################################################################

class GPIBWrapper(io.IOBase):
class GPIBWrapper(io.IOBase, WrapperABC):
'''
Wraps a SocketWrapper or PySerial.Serial connection for use with
Galvant Industries GPIBUSB or GPIBETHERNET adapters.
Expand All @@ -44,7 +45,10 @@ class GPIBWrapper(io.IOBase):
'''
def __init__(self, filelike, gpib_address):
self._file = filelike
self._address = gpib_address
self._gpib_address = gpib_address
self._terminator = 10
self._eoi = 1
self._file.terminator = '\r'

def __repr__(self):
return "<GPIBWrapper object at 0x{:X} "\
Expand All @@ -54,16 +58,32 @@ def __repr__(self):

@property
def address(self):
return self._address
return [self._gpib_address, self._file.address]
@address.setter
def address(self, newval):
# TODO: Should take a hardware connection address to pass on, as well
# as the currently implemented GPIB address
if not isinstance(newval, int):
raise TypeError("New GPIB address must be specified as "
"an integer.")
if (newval < 1) or (newval > 30):
raise ValueError("GPIB address must be between 1 and 30.")
'''
Change GPIB address and downstream address associated with
the instrument.
If specified as an integer, only changes the GPIB address. If specified
as a list, the first element changes the GPIB address, while the second
is passed downstream.
Example: [<int>gpib_address, downstream_address]
Where downstream_address needs to be formatted as appropriate for the
connection (eg SerialWrapper, SocketWrapper, etc).
'''
if isinstance(newval, int):
if (newval < 1) or (newval > 30):
raise ValueError("GPIB address must be between 1 and 30.")
self._gpib_address = newval
elif isinstance(newval, list):
self.address = newval[0] # Set GPIB address
self._file.address = newval[1] # Send downstream address
else:
raise TypeError("Not a valid input type for Instrument address.")


@property
def timeout(self):
Expand All @@ -72,6 +92,31 @@ def timeout(self):
def timeout(self, newval):
raise NotImplementedError

@property
def terminator(self):
if not self._eoi:
return self._terminator
else:
return 'eoi'
@terminator.setter
def terminator(self, newval):
if isinstance(newval, str):
newval = newval.lower()
if newval is 'eoi':
self._eoi = 1
elif not isinstance(newval, int):
raise TypeError('GPIB termination must be integer 0-255 '
'represending decimal value of ASCII '
'termination character or a string containing'
' "eoi".')
elif (newval < 0) or (newval > 255):
raise ValueError('GPIB termination must be integer 0-255 '
'represending decimal value of ASCII '
'termination character.')
else:
self._eoi = 0
self._terminator = str(newval)

## FILE-LIKE METHODS ##

def close(self):
Expand All @@ -82,34 +127,28 @@ def read(self, size):
Read characters from wrapped class (ie SocketWrapper or
PySerial.Serial).
If size = 0, characters will be read until termination character
If size = -1, characters will be read until termination character
is found.
GI GPIB adapters always terminate serial connections with a CR.
Function will read until a CR is found.
'''
if (size > 0):
return self._file.read(size)
elif (size == 0):
result = np.bytearray()
c = 0
while c != '\r':
c = self._file.read(1)
result += c
return bytes(result)
else:
raise ValueError('Must read a positive value of characters.')
return self._file.read(size)

def write(self, msg):
'''
Write data string to GPIB connected instrument.
This function sends all the necessary GI-GPIB adapter internal commands
that are required for the specified instrument.
'''
# TODO: include other internal flags such as +eoi
self._file.write('+a:' + str(self._address) + '\r')
self._file.write('+a:' + str(self._gpib_address))
time.sleep(0.02)
self._file.write('+eoi:{}'.format(self._eoi))
time.sleep(0.02)
self._file.write(msg + '\r')
if self._eoi is 0:
self._file.write('+eos:{}'.format(self._terminator))
time.sleep(0.02)
self._file.write(msg)
time.sleep(0.02)


64 changes: 30 additions & 34 deletions python/src/instruments/abstract_instruments/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import serialManager as sm
import socketwrapper as sw
import gi_gpib
from instruments.abstract_instruments import WrapperABC

## CLASSES #####################################################################

Expand All @@ -44,30 +45,41 @@ class Instrument(object):
_terminator = "\n"

def __init__(self, filelike):
self._file = filelike
# Check to make sure filelike is a subclass of WrapperABC
if isinstance(filelike, WrapperABC):
self._file = filelike
else:
raise TypeError('Instrument must be initialized with a filelike '
'object that is a subclass of WrapperABC.')

## COMMAND-HANDLING METHODS ##

def sendcmd(self, cmd):
"""
Sends an SCPI command without waiting for a response.
Sends a command without waiting for a response.
:param str cmd: String containing the SCPI command to
:param str cmd: String containing the command to
be sent.
"""
self.write(str(cmd) + self._terminator)
self.write(str(cmd))

def query(self, cmd):
def query(self, cmd, size=-1):
"""
Executes the given SCPI query.
Executes the given query.
:param str cmd: String containing the SCPI query to
:param str cmd: String containing the query to
execute.
:param int size: Number of bytes to be read. Default is read until
termination character is found.
:return: The result of the query as returned by the
connected instrument.
:rtype: `str`
"""
return super(SCPIInstrument, self).query(cmd + self._terminator)
self.write(msg)
# TODO: Move the +read logic to GPIBWrapper
#if '?' not in msg:
# self._file.write('+read')
return self._file.read(size)

## PROPERTIES ##

Expand All @@ -80,19 +92,17 @@ def timeout(self, newval):

@property
def address(self):
# TODO: Incorporate other hardware connections
if isinstance(self._file, gi_gpib.GPIBWrapper):
return self._file.address
else:
# TODO: raise some error
return None
return self._file.address
@address.setter
def address(self, newval):
if isinstance(self._file, gi_gpib.GPIBWrapper):
self._file.address = newval
else:
# TODO: raise some error
return None
self._file.address = newval

@property
def terminator(self):
return self._file.terminator
@terminator.setter
def terminator(self, newval):
self._file.terminator = newval

## BASIC I/O METHODS ##

Expand All @@ -102,21 +112,7 @@ def write(self, msg):
This function sends all the necessary GI-GPIB adapter internal commands
that are required for the specified instrument.
'''
self._file.write(msg)

def query(self, msg, size=0):
'''
Query instrument for data. Supplied msg is sent to the instrument
and the responce is read. If msg does not contain a ``?`` the internal
GPIBUSB adapter command ``+read`` is sent which forces the adapter
into talk mode.
Size defines the number of characters to read from
'''
self._file.write(msg)
if '?' not in msg:
self._file.write('+read')
return self._file.read(size)
self._file.write(msg)

def binblockread(self,dataWidth):
'''
Expand Down
7 changes: 5 additions & 2 deletions python/src/instruments/abstract_instruments/serialManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

import serial

import serialwrapper as sw

## GLOBALS #####################################################################

serialObjDict = {}
Expand All @@ -47,9 +49,10 @@ def newSerialConnection(port, baud = 460800, timeout=3, writeTimeout=3):
raise TypeError('Serial port must be specified as a string.')

if port not in serialObjDict:
serialObjDict[port] = serial.Serial(port,
serialObjDict[port] = sw.SocketWrapper(
serial.Serial(port,
baud,
timeout=timeout,
writeTimeout=writeTimeout)
writeTimeout=writeTimeout))

return serialObjDict[port]
107 changes: 107 additions & 0 deletions python/src/instruments/abstract_instruments/serialwrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
##
# socketwrapper.py: Wraps sockets into a filelike object.
##
# © 2013 Steven Casagrande ([email protected]).
#
# This file is a part of the GPIBUSB adapter project.
# Licensed under the AGPL version 3.
##
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##
##

## IMPORTS #####################################################################

import io
import serial

import numpy as np

from instruments.abstract_instruments import WrapperABC

## CLASSES #####################################################################

class SerialWrapper(io.IOBase, WrapperABC):
"""
Wraps a pyserial Serial object to add a few properties as well as
handling of termination characters.
"""

def __init__(self, conn):
if isinstance(conn, serial.Serial):
self._conn = conn
self._terminator = '\n'
else:
raise TypeError('SerialWrapper must wrap a serial.Serial object.')

def __repr__(self):
return "<SerialWrapper object at 0x{:X} "\
"connected to {}>".format(id(self), self._conn.port)

## PROPERTIES ##

@property
def address(self):
return self._conn.port
@address.setter
def address(self, newval):
# TODO: Input checking on Serial port newval
# TODO: Add port changing capability to serialmanager
# self._conn.port = newval
raise NotImplementedError

@property
def terminator(self):
return self._terminator
@terminator.setter
def terminator(self, newval):
if not isinstance(newval, str):
raise TypeError('Terminator for SerialWrapper must be specified '
'as a single character string.')
if len(newval) > 1:
raise ValueError('Terminator for SerialWrapper must only be 1 '
'character long.')
self._terminator = newval

## FILE-LIKE METHODS ##

def close(self):
try:
self._conn.shutdown()
finally:
self._conn.close()

def read(self, size):
if (size >= 0):
return self._conn.recv(size)
elif (size == -1):
result = np.bytearray()
c = 0
while c != self._terminator:
c = self._file.read(1)
result += c
return bytes(result)
else:
raise ValueError('Must read a positive value of characters.')

def write(self, string):
self._conn.sendall(string + self._terminator)

def seek(self, offset):
return NotImplemented

def tell(self):
return NotImplemented
Loading

0 comments on commit 4ddcabb

Please sign in to comment.