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