Skip to content

Commit

Permalink
Merge pull request #10 from cgranade/dev
Browse files Browse the repository at this point in the history
Bug fixes and file communicator class
  • Loading branch information
scasagrande committed Apr 18, 2013
2 parents 9c039d3 + abbb78c commit 9bfe711
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 115 deletions.
151 changes: 151 additions & 0 deletions python/src/instruments/abstract_instruments/file_communicator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
##
# file_communicator.py: Treats a file on the filesystem as a communicator
# (aka wrapper).
##
# © 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 errno
import io
import time
from instruments.abstract_instruments import WrapperABC
import os

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

class FileCommunicator(io.IOBase, WrapperABC):
"""
Wraps a `file` object, providing ``sendcmd`` and ``query`` methods,
while passing everything else through.
:param filelike: File or name of a file to be wrapped as a communicator.
Any file-like object wrapped by this class **must** support both
reading and writing. If using the `open` builtin function, the mode
``r+`` is recommended, and has been tested to work with character
devices under Linux.
:type filelike: `str` or `file`
"""

def __init__(self, filelike):
if isinstance(filelike, str):
filelike = open(filelike, 'r+')

self._filelike = filelike
self._terminator = "\n" # Use the system default line ending by default.
self._debug = False

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

## PROPERTIES ##

@property
def address(self):
if hasattr(self._filelike, 'name'):
return self._filelike.name
else:
return None
@address.setter
def address(self, newval):
raise NotImplementedError("Changing addresses of a file communicator is not yet supported.")

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

@property
def debug(self):
"""
Gets/sets whether debug mode is enabled for this connection.
If `True`, all output is echoed to stdout.
:type: `bool`
"""
return self._debug
@debug.setter
def debug(self, newval):
self._debug = bool(newval)

## FILE-LIKE METHODS ##

def close(self):
try:
self._filelike.close()
except:
pass

def read(self, size):
msg = self._filelike.read(size)
if self._debug:
print " -> {} ".format(repr(msg))
return msg

def write(self, msg):
if self._debug:
print " <- {} ".format(repr(msg))
self._filelike.write(msg)

def seek(self, offset):
self._filelike.seek(offset)

def tell(self):
return self._filelike.tell()

def flush(self):
self._filelike.flush()

## METHODS ##

def sendcmd(self, msg):
msg = msg + self._terminator
self.write(msg)
self.flush()

def query(self, msg, size=-1):
self.sendcmd(msg)
time.sleep(0.02) # Give the bus time to respond.
try:
# Note that we use readline here to avoid having to wait for
# the timeout to be convinced that there was no more output
# from the device.
resp = self._filelike.readline()
except IOError as ex:
if ex.errno != errno.EPIPE:
raise # Reraise the existing exception.
else: # Give a more helpful and specific exception.
raise IOError(
"Pipe broken when reading from {}; this probably "
"indicates that the driver "
"providing the device file is unable to communicate with "
"the instrument. Consider restarting the instrument.".format(
self.address
))
if self._debug:
print " -> {}".format(repr(resp))
return resp

Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,51 @@
import abc

from instruments.abstract_instruments import Instrument
import instruments.units as u
from instruments.util_fns import assume_units

import quantities as pq

from flufl.enum import Enum

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

class FunctionGenerator(Instrument):
__metaclass__ = abc.ABCMeta

## ENUMS ##

class VoltageMode(Enum):
peak_to_peak = 'VPP'
rms = 'VRMS'
dBm = 'DBM'

## PROPERTIES ##
class Function(Enum):
sinusoid = 'SIN'
square = 'SQU'
triangle = 'TRI'
ramp = 'RAMP'
noise = 'NOIS'
arbitrary = 'ARB'

## ABSTRACT METHODS ##

@abc.abstractmethod
def _get_amplitude_(self):
pass
@abc.abstractmethod
def _set_amplitude_(self, magnitude, units):
pass

## ABSTRACT PROPERTIES ##

"""
def getamplitude(self):
raise NotImplementedError('')
def setamplitude(self, newval):
raise NotImplementedError('')
amplitude = abc.abstractproperty(getamplitude, setamplitude)
"""

def getfrequency(self):
raise NotImplementedError('')
Expand All @@ -69,3 +101,51 @@ def getphase(self):
def setphase(self, newval):
raise NotImplementedError('')
phase = abc.abstractproperty(getphase, setphase)

## CONCRETE PROPERTIES ##

@property
def amplitude(self):
'''
Gets/sets the output amplitude of the function generator.
If set with units of :math:`\\text{dBm}`, then no voltage mode can
be passed.
If set with units of :math:`\\text{V}` as a `~quantities.Quantity` or a
`float` without a voltage mode, then the voltage mode is assumed to be
peak-to-peak.
:units: As specified, or assumed to be :math:`\\text{V}` if not
specified.
:type: Either a `tuple` of a `~quantities.Quantity` and a
`FunctionGenerator.VoltageMode`, or a `~quantities.Quantity`
if no voltage mode applies.
'''
mag, units = self._get_amplitude_()

if units == self.VoltageMode.dBm:
return pq.Quantity(mag, u.dBm)
else:
return pq.Quantity(mag, pq.V), units
@amplitude.setter
def amplitude(self, newval):
# Try and rescale to dBm... if it succeeds, set the magnitude
# and units accordingly, otherwise handle as a voltage.
try:
newval_dBm = newval.rescale(u.dBm)
mag = float(newval_dBm.magnitude)
units = self.VoltageMode.dBm
except (AttributeError, ValueError):
# OK, we have volts. Now, do we have a tuple? If not, assume Vpp.
if not isinstance(newval, tuple):
mag = newval
units = self.VoltageMode.peak_to_peak
else:
mag, units = newval

# Finally, convert the magnitude out to a float.
mag = float(assume_units(mag, pq.V).rescale(pq.V).magnitude)

self._set_amplitude_(mag, units)

17 changes: 16 additions & 1 deletion python/src/instruments/abstract_instruments/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import socketwrapper as sw
import usbwrapper as uw
import visawrapper as vw
import file_communicator as fc
import gi_gpib
from instruments.abstract_instruments import WrapperABC
import os
Expand Down Expand Up @@ -171,7 +172,7 @@ def binblockread(self,dataWidth):

## CLASS METHODS ##

URI_SCHEMES = ['serial', 'tcpip', 'gpib+usb', 'gpib+serial', 'visa']
URI_SCHEMES = ['serial', 'tcpip', 'gpib+usb', 'gpib+serial', 'visa', 'file']

@classmethod
def open_from_uri(cls, uri):
Expand Down Expand Up @@ -258,6 +259,8 @@ def open_from_uri(cls, uri):
# the vendor ID, product ID and serial number of the USB-VISA
# device.
return cls.open_visa(parsed_uri.netloc, **kwargs)
elif parsed_uri.scheme == 'file':
return cls.open_file(os.path.join(parsed_uri.netloc, parsed_uri.path), **kwargs)
else:
raise NotImplementedError("Invalid scheme or not yet implemented.")

Expand Down Expand Up @@ -327,3 +330,15 @@ def open_usb(cls, vid, pid):
raise IOError("USB descriptor not found.")

return cls(uw.USBWrapper(ep))

@classmethod
def open_file(cls, filename):
"""
Given a file, treats that file as a character device file that can
be read from and written to in order to communicate with the
instrument. This may be the case, for instance, if the instrument
is connected by the Linux ``usbtmc`` kernel driver.
:param str filename: Name of the character device to open.
"""
return cls(fc.FileCommunicator(filename))
1 change: 1 addition & 0 deletions python/src/instruments/generic_scpi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from instruments.generic_scpi.scpi_instrument import SCPIInstrument
from instruments.generic_scpi.scpi_multimeter import SCPIMultimeter, MultimeterMode
from instruments.generic_scpi.scpi_function_generator import SCPIFunctionGenerator
Loading

0 comments on commit 9bfe711

Please sign in to comment.