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