Home | History | Annotate | Download | only in cellular
      1 #!/usr/bin/python
      2 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import cellular_system_error
      7 import cellular_logging
      8 import os
      9 import select
     10 import socket
     11 import traceback
     12 
     13 
     14 class PrologixScpiDriver:
     15     """Wrapper for a Prologix TCP<->GPIB bridge.
     16     http://prologix.biz/gpib-ethernet-controller.html
     17     http://prologix.biz/index.php?dispatch=attachments.getfile&attachment_id=1
     18 
     19     Communication is over a plain TCP stream on port 1234.  Commands to
     20     the bridge are in-band, prefixed with ++.
     21 
     22     Notable instance variables include:
     23 
     24       self.auto: When 1, the bridge automatically addresses the target
     25         in listen mode.  When 0, we must issue a ++read after every
     26         query.  As of Aug '11, something between us and the Agilent 8960
     27         is wrong such that running in auto=0 mode leaves us hanging if
     28         we issue '*RST;*OPC?'
     29     """
     30     all_open_connections = {}
     31 
     32     def __init__(self, hostname, port=1234, gpib_address=14,
     33                  read_timeout_seconds=30, connect_timeout_seconds=5):
     34         """Constructs a wrapper for the Prologix TCP<->GPIB bridge :
     35         Arguments:
     36             hostname: hostname of prologix device
     37             port: port number
     38             gpib_address: initial GPIB device to connect to
     39             read_timeout_seconds: the read time out for the socket to the
     40                 prologix box
     41             connect_timeout_seconds: the read time out for the socket to the
     42                 prologix box
     43         """
     44         logger_name = 'prologix'
     45         s = 'IP:%s GPIB:%s: ' % (hostname, gpib_address)
     46         formatter_string = '%(asctime)s %(filename)s %(lineno)d ' + s + \
     47                            '- %(message)s'
     48         self.scpi_logger = cellular_logging.SetupCellularLogging(
     49             logger_name, formatter_string)
     50 
     51         self.connection_key = "%s:%s" % (hostname, port)
     52         self.connection_data = {self.connection_key: traceback.format_stack()}
     53         if self.connection_key in self.all_open_connections.keys():
     54             raise cellular_system_error.BadState(
     55               'IP network connection to '
     56               'prologix is already in use. : %s ' % self.all_open_connections)
     57         self.all_open_connections[self.connection_key] = self.connection_data
     58         self.socket = connect_to_port(hostname, port, connect_timeout_seconds)
     59         self.read_timeout_seconds = read_timeout_seconds
     60         self.socket.setblocking(0)
     61         self.SetAuto(1)
     62         self._AddCarrigeReturnsToResponses()
     63         self.SetGpibAddress(gpib_address)
     64         self.scpi_logger.debug('set read_timeout_seconds: %s ' %
     65                                self.read_timeout_seconds)
     66 
     67     def __del__(self):
     68         self.Close()
     69 
     70     def _AddCarrigeReturnsToResponses(self):
     71         """
     72         Have the prologix box add a line feed to each response.
     73         Some instruments may need this.
     74         """
     75         pass
     76         self.Send('++eot_enable 1')
     77         self.Send('++eot_char 10')
     78 
     79     def SetAuto(self, auto):
     80         """Controls Prologix read-after-write (aka 'auto') mode."""
     81         # Must be an int so we can send it as an arg to ++auto.
     82         self.auto = int(auto)
     83         self.Send('++auto %d' % self.auto)
     84 
     85     def Close(self):
     86         """Closes the socket."""
     87         try:
     88             self.scpi_logger.error('Closing prologix devices at : %s ' %
     89                                    self.connection_key)
     90             self.all_open_connections.pop(self.connection_key)
     91         except KeyError:
     92             self.scpi_logger.error('Closed %s more then once' %
     93                                    self.connection_key)
     94         try:
     95             self.socket.close()
     96         except AttributeError:  # Maybe we close before we finish building.
     97             pass
     98 
     99     def SetGpibAddress(self, gpib_address):
    100         max_tries = 10
    101         while max_tries > 0:
    102             max_tries -= 1
    103             self.Send('++addr %s' % gpib_address)
    104             read_back_value = self._DirectQuery('++addr')
    105             try:
    106                 if int(read_back_value) == int(gpib_address):
    107                     break
    108             except ValueError:
    109                 # If we read a string, don't raise, just try again.
    110                 pass
    111             self.scpi_logger.error('Set gpib addr to: %s, read back: %s' %
    112                                    (gpib_address, read_back_value))
    113             self.scpi_logger.error('Setting the GPIB address failed. ' +
    114                                    'Trying again...')
    115 
    116     def Send(self, command):
    117         self.scpi_logger.info('] %s', command)
    118         try:
    119             self.socket.send(command + '\n')
    120         except Exception as e:
    121             self.scpi_logger.error('sending SCPI command %s failed. ' %
    122                                    command)
    123             self.scpi_logger.exception(e)
    124             raise SystemError('Sending SCPI command failed. '
    125                               'Did the instrument stopped talking?')
    126 
    127     def Reset(self):
    128         """Sends a standard SCPI reset and waits for it to complete."""
    129         # There is some misinteraction between the devices such that if we
    130         # send *RST and *OPC? and then manually query with ++read,
    131         # occasionally that ++read doesn't come back.  We currently depend
    132         # on self.Query to turn on Prologix auto mode to avoid this
    133         self.Send('*RST')
    134         self.Query('*OPC?')
    135 
    136     def Read(self):
    137         """Read a response from the bridge."""
    138         try:
    139             ready = select.select([self.socket], [], [],
    140                                   self.read_timeout_seconds)
    141         except Exception as e:
    142             self.scpi_logger.exception(e)
    143             s = 'Read from the instrument failed. Timeout:%s' % \
    144                 self.read_timeout_seconds
    145             self.scpi_logger.error(s)
    146             raise SystemError(s)
    147 
    148         if ready[0]:
    149             response = self.socket.recv(4096)
    150             response = response.rstrip()
    151             self.scpi_logger.info('[ %s', response)
    152             return response
    153         else:
    154             self.Close()
    155             s = 'Connection to the prologix adapter worked.' \
    156                 'But there was not data to read from the instrument.' \
    157                 'Does that command return a result?' \
    158                 'Bad GPIB port number, or timeout too short?'
    159         raise cellular_system_error.InstrumentTimeout(s)
    160 
    161     def Query(self, command):
    162         """Send a GPIB command and return the response."""
    163         #self.SetAuto(1) #maybe useful?
    164 
    165         s = list(self.scpi_logger.findCaller())
    166         s[0] = os.path.basename(s[0])
    167 
    168         s = list(self.scpi_logger.findCaller())
    169         s[0] = os.path.basename(s[0])
    170         self.scpi_logger.debug('caller :' + str(s) + command)
    171 
    172         self.Send(command)
    173         if not self.auto:
    174             self.Send('++read eoi')
    175         output = self.Read()
    176         #self.SetAuto(0) #maybe useful?
    177         return output
    178 
    179     def _DirectQuery(self, command):
    180         """Sends a query to the prologix (do not send ++read).
    181 
    182         Returns: response of the query.
    183         """
    184         self.Send(command)
    185         return self.Read()
    186 
    187 
    188 def connect_to_port(hostname, port, connect_timeout_seconds):
    189     # Right out of the python documentation,
    190     #  http://docs.python.org/library/socket.html
    191     for res in socket.getaddrinfo(
    192                 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
    193         af, socktype, proto, _, sa = res
    194         try:
    195             s = socket.socket(af, socktype, proto)
    196         except socket.error as msg:
    197             raise cellular_system_error.SocketTimeout(
    198                 'Failed to make a new socket object. ' + str(msg))
    199         try:
    200             s.settimeout(connect_timeout_seconds)
    201             s.connect(sa)
    202         except socket.error as msg:
    203             try:
    204                 s.close()
    205             except Exception:
    206                 pass  # Try to close it, but it may not have been created.
    207             temp_string_var = ' Could be bad IP address. Tried: %s : %s' % \
    208                               (hostname, port)
    209             raise cellular_system_error.SocketTimeout(str(msg) +
    210                                                       temp_string_var)
    211     return s
    212