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() |