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