• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
6import cellular_system_error
7import cellular_logging
8import os
9import select
10import socket
11import traceback
12
13
14class 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
188def 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