1# Copyright (c) 2017 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Interface for SCPI Protocol. 6 7Helper module to communicate with devices that uses SCPI protocol. 8 9https://en.wikipedia.org/wiki/Standard_Commands_for_Programmable_Instruments 10 11This will be used by RF Switch that was designed to connect WiFi AP and 12WiFi Clients RF enclosures for interoperability testing. 13 14""" 15 16from __future__ import print_function 17 18import logging 19import six 20import socket 21import sys 22 23 24class ScpiException(Exception): 25 """Exception for SCPI Errors.""" 26 27 def __init__(self, msg=None, cause=None): 28 messages = [] 29 if msg: 30 messages.append(msg) 31 if cause: 32 messages.append('Wrapping exception: %s: %s' % ( 33 type(cause).__name__, str(cause))) 34 super(ScpiException, self).__init__(', '.join(messages)) 35 36 37class Scpi(object): 38 """Controller for devices using SCPI protocol.""" 39 40 SCPI_PORT = 5025 41 DEFAULT_READ_LEN = 4096 42 43 CMD_IDENTITY = '*IDN?' 44 CMD_RESET = '*RST' 45 CMD_STATUS = '*STB?' 46 CMD_ERROR_CHECK = 'SYST:ERR?' 47 48 def __init__(self, host, port=SCPI_PORT): 49 """ 50 Controller for devices using SCPI protocol. 51 52 @param host: hostname or IP address of device using SCPI protocol 53 @param port: Int SCPI port number (default 5025) 54 55 @raises SCPIException: on error connecting to device 56 57 """ 58 self.host = host 59 self.port = port 60 61 # Open a socket connection for communication with chassis. 62 try: 63 self.socket = socket.socket() 64 self.socket.connect((host, port)) 65 except (socket.error, socket.timeout) as e: 66 logging.error('Error connecting to SCPI device.') 67 six.reraise(ScpiException(cause=e), None, sys.exc_info()[2]) 68 69 def close(self): 70 """Close the connection.""" 71 if hasattr(self, 'socket'): 72 self.socket.close() 73 del self.socket 74 75 def write(self, data): 76 """Send data to socket. 77 78 @param data: Data to send 79 80 @returns number of bytes sent 81 82 """ 83 return self.socket.send(data) 84 85 def read(self, buffer_size=DEFAULT_READ_LEN): 86 """Safely read the query response. 87 88 @param buffer_size: Int max data to read at once (default 4096) 89 90 @returns String data read from the socket 91 92 """ 93 return str(self.socket.recv(buffer_size)) 94 95 def query(self, data, buffer_size=DEFAULT_READ_LEN): 96 """Send the query and get response. 97 98 @param data: data (Query) to send 99 @param buffer_size: Int max data to read at once (default 4096) 100 101 @returns String data read from the socket 102 103 """ 104 self.write(data) 105 return self.read(buffer_size) 106 107 def info(self): 108 """Get Chassis Info. 109 110 @returns dictionary information of Chassis 111 112 """ 113 # Returns a comma separated text as below converted to dict. 114 # 'VTI Instruments Corporation,EX7200-S-11539,138454,3.13.8\n' 115 return dict( 116 zip(('Manufacturer', 'Model', 'Serial', 'Version'), 117 self.query('%s\n' % self.CMD_IDENTITY) 118 .strip().split(',', 3))) 119 120 def reset(self): 121 """Reset the chassis. 122 123 @returns number of bytes sent 124 """ 125 return self.write('%s\n' % self.CMD_RESET) 126 127 def status(self): 128 """Get status of relays. 129 130 @returns Int status of relays 131 132 """ 133 return int(self.query('%s\n' % self.CMD_STATUS)) 134 135 def error_query(self): 136 """Check for any error. 137 138 @returns tuple of error code and error message 139 140 """ 141 code, msg = self.query('%s\n' % self.CMD_ERROR_CHECK).split(', ') 142 return int(code), msg.strip().strip('"') 143