143 lines
3.8 KiB
Python
143 lines
3.8 KiB
Python
# Copyright (c) 2017 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Interface for SCPI Protocol.
|
|
|
|
Helper module to communicate with devices that uses SCPI protocol.
|
|
|
|
https://en.wikipedia.org/wiki/Standard_Commands_for_Programmable_Instruments
|
|
|
|
This will be used by RF Switch that was designed to connect WiFi AP and
|
|
WiFi Clients RF enclosures for interoperability testing.
|
|
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
import six
|
|
import socket
|
|
import sys
|
|
|
|
|
|
class ScpiException(Exception):
|
|
"""Exception for SCPI Errors."""
|
|
|
|
def __init__(self, msg=None, cause=None):
|
|
messages = []
|
|
if msg:
|
|
messages.append(msg)
|
|
if cause:
|
|
messages.append('Wrapping exception: %s: %s' % (
|
|
type(cause).__name__, str(cause)))
|
|
super(ScpiException, self).__init__(', '.join(messages))
|
|
|
|
|
|
class Scpi(object):
|
|
"""Controller for devices using SCPI protocol."""
|
|
|
|
SCPI_PORT = 5025
|
|
DEFAULT_READ_LEN = 4096
|
|
|
|
CMD_IDENTITY = '*IDN?'
|
|
CMD_RESET = '*RST'
|
|
CMD_STATUS = '*STB?'
|
|
CMD_ERROR_CHECK = 'SYST:ERR?'
|
|
|
|
def __init__(self, host, port=SCPI_PORT):
|
|
"""
|
|
Controller for devices using SCPI protocol.
|
|
|
|
@param host: hostname or IP address of device using SCPI protocol
|
|
@param port: Int SCPI port number (default 5025)
|
|
|
|
@raises SCPIException: on error connecting to device
|
|
|
|
"""
|
|
self.host = host
|
|
self.port = port
|
|
|
|
# Open a socket connection for communication with chassis.
|
|
try:
|
|
self.socket = socket.socket()
|
|
self.socket.connect((host, port))
|
|
except (socket.error, socket.timeout) as e:
|
|
logging.error('Error connecting to SCPI device.')
|
|
six.reraise(ScpiException(cause=e), None, sys.exc_info()[2])
|
|
|
|
def close(self):
|
|
"""Close the connection."""
|
|
if hasattr(self, 'socket'):
|
|
self.socket.close()
|
|
del self.socket
|
|
|
|
def write(self, data):
|
|
"""Send data to socket.
|
|
|
|
@param data: Data to send
|
|
|
|
@returns number of bytes sent
|
|
|
|
"""
|
|
return self.socket.send(data)
|
|
|
|
def read(self, buffer_size=DEFAULT_READ_LEN):
|
|
"""Safely read the query response.
|
|
|
|
@param buffer_size: Int max data to read at once (default 4096)
|
|
|
|
@returns String data read from the socket
|
|
|
|
"""
|
|
return str(self.socket.recv(buffer_size))
|
|
|
|
def query(self, data, buffer_size=DEFAULT_READ_LEN):
|
|
"""Send the query and get response.
|
|
|
|
@param data: data (Query) to send
|
|
@param buffer_size: Int max data to read at once (default 4096)
|
|
|
|
@returns String data read from the socket
|
|
|
|
"""
|
|
self.write(data)
|
|
return self.read(buffer_size)
|
|
|
|
def info(self):
|
|
"""Get Chassis Info.
|
|
|
|
@returns dictionary information of Chassis
|
|
|
|
"""
|
|
# Returns a comma separated text as below converted to dict.
|
|
# 'VTI Instruments Corporation,EX7200-S-11539,138454,3.13.8\n'
|
|
return dict(
|
|
zip(('Manufacturer', 'Model', 'Serial', 'Version'),
|
|
self.query('%s\n' % self.CMD_IDENTITY)
|
|
.strip().split(',', 3)))
|
|
|
|
def reset(self):
|
|
"""Reset the chassis.
|
|
|
|
@returns number of bytes sent
|
|
"""
|
|
return self.write('%s\n' % self.CMD_RESET)
|
|
|
|
def status(self):
|
|
"""Get status of relays.
|
|
|
|
@returns Int status of relays
|
|
|
|
"""
|
|
return int(self.query('%s\n' % self.CMD_STATUS))
|
|
|
|
def error_query(self):
|
|
"""Check for any error.
|
|
|
|
@returns tuple of error code and error message
|
|
|
|
"""
|
|
code, msg = self.query('%s\n' % self.CMD_ERROR_CHECK).split(', ')
|
|
return int(code), msg.strip().strip('"')
|