| Index: common/monsoon/monsoon/monsoon_wrapper.py
 | 
| diff --git a/common/monsoon/monsoon/monsoon_wrapper.py b/common/monsoon/monsoon/monsoon_wrapper.py
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..dbbc76aa429c38147dab5cda8125222170f99b63
 | 
| --- /dev/null
 | 
| +++ b/common/monsoon/monsoon/monsoon_wrapper.py
 | 
| @@ -0,0 +1,124 @@
 | 
| +# Copyright 2017 The Chromium 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 atexit
 | 
| +import py_utils
 | 
| +import re
 | 
| +import subprocess
 | 
| +import tempfile
 | 
| +
 | 
| +USB_PASSTHROUGH_OFF = "off"
 | 
| +USB_PASSTHROUGH_ON = "on"
 | 
| +USB_PASSTHROUGH_AUTO = "auto"
 | 
| +
 | 
| +UsbModes = [USB_PASSTHROUGH_OFF, USB_PASSTHROUGH_ON, USB_PASSTHROUGH_AUTO]
 | 
| +
 | 
| +class MonsoonWrapper(object):
 | 
| +  def __init__(self, script):
 | 
| +    self.script = script
 | 
| +    self._process = None
 | 
| +    # Register atexit handler to kill any monsoon processes in the case of
 | 
| +    # abnormal program termination.
 | 
| +    atexit.register(self.TerminateMonsoonProcess)
 | 
| +
 | 
| +  def ReadStatusProperties(self):
 | 
| +    """ Reads status properties from Monsoon, returning a dictionary of
 | 
| +    properties.
 | 
| +    """
 | 
| +    status_text = self.ExecuteBlocking(["--status"])
 | 
| +    status = {}
 | 
| +    for status_line in status_text:
 | 
| +      # Sometimes transient USB serial errors are reported.  Just ignore them
 | 
| +      # and only use strings with : in them.
 | 
| +      if ":" in status_line:
 | 
| +        key, value = status_line.split(':')
 | 
| +        status[key] = value
 | 
| +    return status
 | 
| +
 | 
| +  def EnableUSB(self):
 | 
| +    """ Shorthand for SetUSBMode(USB_PASSTHROUGH_ON) """
 | 
| +    self.SetUSBMode(USB_PASSTHROUGH_ON)
 | 
| +
 | 
| +  def DisableUSB(self):
 | 
| +    """ Shorthand for SetUSBMode(USB_PASSTHROUGH_OFF) """
 | 
| +    self.SetUSBMode(USB_PASSTHROUGH_OFF)
 | 
| +
 | 
| +  def SetUSBMode(self, mode):
 | 
| +    """ Set the Monsoon USB mode.
 | 
| +    """
 | 
| +    if mode not in UsbModes: raise Exception("Invalid USB mode " + mode)
 | 
| +    # Sometimes Monsoon fails to make USB passthrough mode changes
 | 
| +    # permanent if there are transient USB serial errors.
 | 
| +    retry_count = 5
 | 
| +    while self.GetUSBMode() is not mode:
 | 
| +      self.ExecuteBlocking(["--usbpassthrough", mode])
 | 
| +      if retry_count is 0: Exception("Failed to set USB mode!")
 | 
| +      retry_count -= 1
 | 
| +
 | 
| +  def GetUSBMode(self):
 | 
| +    """ Retrieves the current Monsoon USB mode, returning USB_PASSTHROUGH_OFF
 | 
| +    if the status could not be retrieved.
 | 
| +    """
 | 
| +    properties = self.ReadStatusProperties()
 | 
| +    if "usbPassthroughMode" in properties:
 | 
| +      return UsbModes[int(properties["usbPassthroughMode"])]
 | 
| +    return USB_PASSTHROUGH_OFF
 | 
| +
 | 
| +  def ReadPower(self, time, frequency):
 | 
| +    """ Reads power from the Monsoon device over a specified perdiod while
 | 
| +    blocking, returning a file containing raw monsoon data. """
 | 
| +    sample_count = time * frequency
 | 
| +    args = ["--samples", str(sample_count), "--hz", str(frequency),
 | 
| +      "--timestamp"]
 | 
| +    return self.ExecuteBlocking(args)
 | 
| +
 | 
| +  def ExecuteBlocking(self, args):
 | 
| +    """ Executes a blocking operation on the Monsoon device, returning a file
 | 
| +    containing stdout output. """
 | 
| +    if self.IsProcessRunning():
 | 
| +      raise Exception("Monsoon subprocess already running!")
 | 
| +    result_file = tempfile.TemporaryFile()
 | 
| +    subprocess.check_call([self.script] + args, stdout=result_file)
 | 
| +    result_file.seek(0)
 | 
| +    return result_file
 | 
| +
 | 
| +  def BeginReadPower(self, frequency, out=subprocess.PIPE):
 | 
| +    """ Begins a non-blocking read of power from the Monsoon device, returning
 | 
| +    the stdout file of the subprocess.
 | 
| +    """
 | 
| +    args = ["--samples", "-1", "--hz", str(frequency), "--timestamp"]
 | 
| +    return self.BeginExecution(args=args, out=out)
 | 
| +
 | 
| +  def BeginExecution(self, args = [], out=subprocess.PIPE):
 | 
| +    """ Begins a non-blocking operation on the Monsoon device, returning the
 | 
| +    stdout file of the subprocess.
 | 
| +    """
 | 
| +    if self.IsProcessRunning():
 | 
| +      raise Exception("Monsoon subprocess already running!")
 | 
| +    self._process = subprocess.Popen([self.script] + args, stdout=out)
 | 
| +    return self._process.stdout
 | 
| +
 | 
| +  def EndExecution(self):
 | 
| +    """ Ends non-blocking execution of the Monsoon subprocess, returning the
 | 
| +    stdout file of the subprocess. """
 | 
| +    if self._process is None or self._process.poll() is not None:
 | 
| +      raise Exception("Monsoon subprocess not running when going to end "
 | 
| +        + "execution.")
 | 
| +    self._process.terminate()
 | 
| +    self._process.wait()
 | 
| +    result_file = self._process.stdout
 | 
| +    self._process = None
 | 
| +    self._result_file = None
 | 
| +    return result_file
 | 
| +
 | 
| +  def IsProcessRunning(self):
 | 
| +    """ Returns whether or not the Monsoon subprocess is running.
 | 
| +    """
 | 
| +    return self._process is not None and self._process.poll() is None
 | 
| +
 | 
| +  def TerminateMonsoonProcess(self):
 | 
| +    """ Terminates the Monsoon subprocess if it is running.
 | 
| +    """
 | 
| +    if self._process is not None and self._process.poll() is None:
 | 
| +      self._process.kill()
 | 
| 
 |