You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.9 KiB
147 lines
4.9 KiB
#!/usr/bin/python2
|
|
# Copyright (c) 2013 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.
|
|
|
|
import cellular_logging
|
|
import cellular_system_error
|
|
|
|
log = cellular_logging.SetupCellularLogging('scpi_driver')
|
|
|
|
|
|
class _ErrorCheckerContext(object):
|
|
"""Reference-count our error-checking state and only check for
|
|
errors when we take the first ref or drop the last ref.
|
|
|
|
This way, we can minimize the number of checks; each one takes a
|
|
bit of time. You will likely want to set always_check to True when
|
|
debugging new SCPI interactions.
|
|
|
|
On first entry, we check for errors, but do not stop if we find
|
|
them; these are errors that were accumulated on the device before
|
|
this test ran.
|
|
"""
|
|
|
|
def __init__(self, scpi):
|
|
self.always_check = True # True for serious debugging
|
|
self.scpi = scpi
|
|
self.depth = 0
|
|
self.raise_on_error = True
|
|
|
|
def __enter__(self):
|
|
log.debug('ErrorCheckerContext Depth: %s' % self.depth)
|
|
if self.depth == 0 or self.always_check:
|
|
errors = self.scpi._WaitAndFetchErrors(
|
|
raise_on_error=False) # Never raise when clearing old errors
|
|
self.depth += 1
|
|
return self
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
self.depth -= 1
|
|
if self.depth <= 0 or self.always_check:
|
|
self.scpi._WaitAndFetchErrors()
|
|
return
|
|
|
|
|
|
class Scpi(object):
|
|
"""Wrapper for SCPI.
|
|
|
|
SCPI = "standard commands for programmable instruments",
|
|
a relative of GPIB.
|
|
|
|
The SCPI driver must export: Query, Send, Reset and Close
|
|
"""
|
|
|
|
def __init__(self, driver, opc_on_stanza=False):
|
|
self.driver = driver
|
|
self.opc_on_stanza = opc_on_stanza
|
|
self.checker_context = _ErrorCheckerContext(self)
|
|
|
|
def Query(self, command):
|
|
"""Send the SCPI command and return the response."""
|
|
response = self.driver.Query(command)
|
|
return response
|
|
|
|
def Send(self, command):
|
|
"""Send the SCPI command."""
|
|
self.driver.Send(command)
|
|
|
|
def Reset(self):
|
|
"""Tell the device to reset with *RST."""
|
|
# Some devices (like the prologix) require special handling for
|
|
# reset.
|
|
self.driver.Reset()
|
|
|
|
def Close(self):
|
|
"""Close the device."""
|
|
self.driver.Close()
|
|
|
|
def RetrieveErrors(self):
|
|
"""Retrieves all SYSTem:ERRor messages from the device."""
|
|
errors = []
|
|
while True:
|
|
error = self.Query('SYSTem:ERRor?')
|
|
if '+0,"No error"' in error:
|
|
# We've reached the end of the error stack
|
|
break
|
|
|
|
if '-420' in error and 'Query UNTERMINATED' in error:
|
|
# This is benign; the GPIB bridge asked for a response when
|
|
# the device didn't have one to give.
|
|
|
|
# TODO(rochberg): This is a layering violation; we should
|
|
# really only accept -420 if the underlying driver is in a
|
|
# mode that is known to cause this
|
|
continue
|
|
|
|
if '+292' in error and 'Data arrived on unknown SAPI' in error:
|
|
# This may be benign; It is known to occur when we do a switch
|
|
# from GPRS to WCDMA
|
|
continue
|
|
|
|
errors.append(error)
|
|
|
|
self.Send('*CLS') # Clear status
|
|
errors.reverse()
|
|
return errors
|
|
|
|
def _WaitAndFetchErrors(self, raise_on_error=True):
|
|
"""Waits for command completion, returns errors."""
|
|
self.Query('*OPC?') # Wait for operation complete
|
|
errors = self.RetrieveErrors()
|
|
if errors and raise_on_error:
|
|
raise cellular_system_error.BadScpiCommand('\n'.join(errors))
|
|
return errors
|
|
|
|
def SimpleVerify(self, command, arg):
|
|
"""Sends "command arg", then "command?", expecting arg back.
|
|
|
|
Arguments:
|
|
command: SCPI command
|
|
arg: Argument. We currently check for exact equality: you should
|
|
send strings quoted with " because that's what the 8960 returns.
|
|
We also fail if you send 1 and receive +1 back.
|
|
|
|
Raises:
|
|
Error: Verification failed
|
|
"""
|
|
self.always_check = False
|
|
with self.checker_context:
|
|
self.Send('%s %s' % (command, arg))
|
|
result = self.Query('%s?' % (command,))
|
|
if result != arg:
|
|
raise cellular_system_error.BadScpiCommand(
|
|
'Error on %s: sent %s, got %s' % (command, arg, result))
|
|
|
|
def SendStanza(self, commands):
|
|
"""
|
|
Sends a list of commands and verifies that they complete correctly.
|
|
"""
|
|
with self.checker_context:
|
|
for c in commands:
|
|
if self.opc_on_stanza:
|
|
self.Send(c)
|
|
self.Query('*OPC?')
|
|
else:
|
|
self.Send(c)
|