xref: /aosp_15_r20/cts/tools/utils/monsoon.py (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1*b7c941bbSAndroid Build Coastguard Worker#!/usr/bin/env python
2*b7c941bbSAndroid Build Coastguard Worker
3*b7c941bbSAndroid Build Coastguard Worker# Copyright (C) 2014 The Android Open Source Project
4*b7c941bbSAndroid Build Coastguard Worker#
5*b7c941bbSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*b7c941bbSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*b7c941bbSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*b7c941bbSAndroid Build Coastguard Worker#
9*b7c941bbSAndroid Build Coastguard Worker#       http://www.apache.org/licenses/LICENSE-2.0
10*b7c941bbSAndroid Build Coastguard Worker#
11*b7c941bbSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*b7c941bbSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*b7c941bbSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*b7c941bbSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*b7c941bbSAndroid Build Coastguard Worker# limitations under the License.
16*b7c941bbSAndroid Build Coastguard Worker
17*b7c941bbSAndroid Build Coastguard Worker"""Interface for a USB-connected Monsoon power meter
18*b7c941bbSAndroid Build Coastguard Worker(http://msoon.com/LabEquipment/PowerMonitor/).
19*b7c941bbSAndroid Build Coastguard WorkerThis file requires gflags, which requires setuptools.
20*b7c941bbSAndroid Build Coastguard WorkerTo install setuptools: sudo apt-get install python-setuptools
21*b7c941bbSAndroid Build Coastguard WorkerTo install gflags, see http://code.google.com/p/python-gflags/
22*b7c941bbSAndroid Build Coastguard WorkerTo install pyserial, see http://pyserial.sourceforge.net/
23*b7c941bbSAndroid Build Coastguard Worker
24*b7c941bbSAndroid Build Coastguard WorkerExample usages:
25*b7c941bbSAndroid Build Coastguard Worker  Set the voltage of the device 7536 to 4.0V
26*b7c941bbSAndroid Build Coastguard Worker  python monsoon.py --voltage=4.0 --serialno 7536
27*b7c941bbSAndroid Build Coastguard Worker
28*b7c941bbSAndroid Build Coastguard Worker  Get 5000hz data from device number 7536, with unlimited number of samples
29*b7c941bbSAndroid Build Coastguard Worker  python monsoon.py --samples -1 --hz 5000 --serialno 7536
30*b7c941bbSAndroid Build Coastguard Worker
31*b7c941bbSAndroid Build Coastguard Worker  Get 200Hz data for 5 seconds (1000 events) from default device
32*b7c941bbSAndroid Build Coastguard Worker  python monsoon.py --samples 100 --hz 200
33*b7c941bbSAndroid Build Coastguard Worker
34*b7c941bbSAndroid Build Coastguard Worker  Get unlimited 200Hz data from device attached at /dev/ttyACM0
35*b7c941bbSAndroid Build Coastguard Worker  python monsoon.py --samples -1 --hz 200 --device /dev/ttyACM0
36*b7c941bbSAndroid Build Coastguard Worker
37*b7c941bbSAndroid Build Coastguard WorkerOutput columns for collection with --samples, separated by space:
38*b7c941bbSAndroid Build Coastguard Worker
39*b7c941bbSAndroid Build Coastguard Worker  TIMESTAMP OUTPUT OUTPUT_AVG USB USB_AVG
40*b7c941bbSAndroid Build Coastguard Worker   |                |          |   |
41*b7c941bbSAndroid Build Coastguard Worker   |                |          |   ` (if --includeusb and --avg)
42*b7c941bbSAndroid Build Coastguard Worker   |                |          ` (if --includeusb)
43*b7c941bbSAndroid Build Coastguard Worker   |                ` (if --avg)
44*b7c941bbSAndroid Build Coastguard Worker   ` (if --timestamp)
45*b7c941bbSAndroid Build Coastguard Worker"""
46*b7c941bbSAndroid Build Coastguard Worker
47*b7c941bbSAndroid Build Coastguard Workerimport fcntl
48*b7c941bbSAndroid Build Coastguard Workerimport os
49*b7c941bbSAndroid Build Coastguard Workerimport select
50*b7c941bbSAndroid Build Coastguard Workerimport signal
51*b7c941bbSAndroid Build Coastguard Workerimport stat
52*b7c941bbSAndroid Build Coastguard Workerimport struct
53*b7c941bbSAndroid Build Coastguard Workerimport sys
54*b7c941bbSAndroid Build Coastguard Workerimport time
55*b7c941bbSAndroid Build Coastguard Workerimport collections
56*b7c941bbSAndroid Build Coastguard Worker
57*b7c941bbSAndroid Build Coastguard Workerimport gflags as flags  # http://code.google.com/p/python-gflags/
58*b7c941bbSAndroid Build Coastguard Worker
59*b7c941bbSAndroid Build Coastguard Workerimport serial           # http://pyserial.sourceforge.net/
60*b7c941bbSAndroid Build Coastguard Worker
61*b7c941bbSAndroid Build Coastguard WorkerFLAGS = flags.FLAGS
62*b7c941bbSAndroid Build Coastguard Worker
63*b7c941bbSAndroid Build Coastguard Workerclass Monsoon:
64*b7c941bbSAndroid Build Coastguard Worker  """
65*b7c941bbSAndroid Build Coastguard Worker  Provides a simple class to use the power meter, e.g.
66*b7c941bbSAndroid Build Coastguard Worker  mon = monsoon.Monsoon()
67*b7c941bbSAndroid Build Coastguard Worker  mon.SetVoltage(3.7)
68*b7c941bbSAndroid Build Coastguard Worker  mon.StartDataCollection()
69*b7c941bbSAndroid Build Coastguard Worker  mydata = []
70*b7c941bbSAndroid Build Coastguard Worker  while len(mydata) < 1000:
71*b7c941bbSAndroid Build Coastguard Worker    mydata.extend(mon.CollectData())
72*b7c941bbSAndroid Build Coastguard Worker  mon.StopDataCollection()
73*b7c941bbSAndroid Build Coastguard Worker  """
74*b7c941bbSAndroid Build Coastguard Worker
75*b7c941bbSAndroid Build Coastguard Worker  def __init__(self, device=None, serialno=None, wait=1):
76*b7c941bbSAndroid Build Coastguard Worker    """
77*b7c941bbSAndroid Build Coastguard Worker    Establish a connection to a Monsoon.
78*b7c941bbSAndroid Build Coastguard Worker    By default, opens the first available port, waiting if none are ready.
79*b7c941bbSAndroid Build Coastguard Worker    A particular port can be specified with "device", or a particular Monsoon
80*b7c941bbSAndroid Build Coastguard Worker    can be specified with "serialno" (using the number printed on its back).
81*b7c941bbSAndroid Build Coastguard Worker    With wait=0, IOError is thrown if a device is not immediately available.
82*b7c941bbSAndroid Build Coastguard Worker    """
83*b7c941bbSAndroid Build Coastguard Worker
84*b7c941bbSAndroid Build Coastguard Worker    self._coarse_ref = self._fine_ref = self._coarse_zero = self._fine_zero = 0
85*b7c941bbSAndroid Build Coastguard Worker    self._coarse_scale = self._fine_scale = 0
86*b7c941bbSAndroid Build Coastguard Worker    self._last_seq = 0
87*b7c941bbSAndroid Build Coastguard Worker    self.start_voltage = 0
88*b7c941bbSAndroid Build Coastguard Worker
89*b7c941bbSAndroid Build Coastguard Worker    if device:
90*b7c941bbSAndroid Build Coastguard Worker      self.ser = serial.Serial(device, timeout=1)
91*b7c941bbSAndroid Build Coastguard Worker      return
92*b7c941bbSAndroid Build Coastguard Worker
93*b7c941bbSAndroid Build Coastguard Worker    while True:  # try all /dev/ttyACM* until we find one we can use
94*b7c941bbSAndroid Build Coastguard Worker      for dev in os.listdir("/dev"):
95*b7c941bbSAndroid Build Coastguard Worker        if not dev.startswith("ttyACM"): continue
96*b7c941bbSAndroid Build Coastguard Worker        tmpname = "/tmp/monsoon.%s.%s" % (os.uname()[0], dev)
97*b7c941bbSAndroid Build Coastguard Worker        self._tempfile = open(tmpname, "w")
98*b7c941bbSAndroid Build Coastguard Worker        try:
99*b7c941bbSAndroid Build Coastguard Worker          os.chmod(tmpname, 0666)
100*b7c941bbSAndroid Build Coastguard Worker        except OSError:
101*b7c941bbSAndroid Build Coastguard Worker          pass
102*b7c941bbSAndroid Build Coastguard Worker        try:  # use a lockfile to ensure exclusive access
103*b7c941bbSAndroid Build Coastguard Worker          fcntl.lockf(self._tempfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
104*b7c941bbSAndroid Build Coastguard Worker        except IOError as e:
105*b7c941bbSAndroid Build Coastguard Worker          print >>sys.stderr, "device %s is in use" % dev
106*b7c941bbSAndroid Build Coastguard Worker          continue
107*b7c941bbSAndroid Build Coastguard Worker
108*b7c941bbSAndroid Build Coastguard Worker        try:  # try to open the device
109*b7c941bbSAndroid Build Coastguard Worker          self.ser = serial.Serial("/dev/%s" % dev, timeout=1)
110*b7c941bbSAndroid Build Coastguard Worker          self.StopDataCollection()  # just in case
111*b7c941bbSAndroid Build Coastguard Worker          self._FlushInput()  # discard stale input
112*b7c941bbSAndroid Build Coastguard Worker          status = self.GetStatus()
113*b7c941bbSAndroid Build Coastguard Worker        except Exception as e:
114*b7c941bbSAndroid Build Coastguard Worker          print >>sys.stderr, "error opening device %s: %s" % (dev, e)
115*b7c941bbSAndroid Build Coastguard Worker          continue
116*b7c941bbSAndroid Build Coastguard Worker
117*b7c941bbSAndroid Build Coastguard Worker        if not status:
118*b7c941bbSAndroid Build Coastguard Worker          print >>sys.stderr, "no response from device %s" % dev
119*b7c941bbSAndroid Build Coastguard Worker        elif serialno and status["serialNumber"] != serialno:
120*b7c941bbSAndroid Build Coastguard Worker          print >>sys.stderr, ("Note: another device serial #%d seen on %s" %
121*b7c941bbSAndroid Build Coastguard Worker                               (status["serialNumber"], dev))
122*b7c941bbSAndroid Build Coastguard Worker        else:
123*b7c941bbSAndroid Build Coastguard Worker          self.start_voltage = status["voltage1"]
124*b7c941bbSAndroid Build Coastguard Worker          return
125*b7c941bbSAndroid Build Coastguard Worker
126*b7c941bbSAndroid Build Coastguard Worker      self._tempfile = None
127*b7c941bbSAndroid Build Coastguard Worker      if not wait: raise IOError("No device found")
128*b7c941bbSAndroid Build Coastguard Worker      print >>sys.stderr, "waiting for device..."
129*b7c941bbSAndroid Build Coastguard Worker      time.sleep(1)
130*b7c941bbSAndroid Build Coastguard Worker
131*b7c941bbSAndroid Build Coastguard Worker
132*b7c941bbSAndroid Build Coastguard Worker  def GetStatus(self):
133*b7c941bbSAndroid Build Coastguard Worker    """ Requests and waits for status.  Returns status dictionary. """
134*b7c941bbSAndroid Build Coastguard Worker
135*b7c941bbSAndroid Build Coastguard Worker    # status packet format
136*b7c941bbSAndroid Build Coastguard Worker    STATUS_FORMAT = ">BBBhhhHhhhHBBBxBbHBHHHHBbbHHBBBbbbbbbbbbBH"
137*b7c941bbSAndroid Build Coastguard Worker    STATUS_FIELDS = [
138*b7c941bbSAndroid Build Coastguard Worker        "packetType", "firmwareVersion", "protocolVersion",
139*b7c941bbSAndroid Build Coastguard Worker        "mainFineCurrent", "usbFineCurrent", "auxFineCurrent", "voltage1",
140*b7c941bbSAndroid Build Coastguard Worker        "mainCoarseCurrent", "usbCoarseCurrent", "auxCoarseCurrent", "voltage2",
141*b7c941bbSAndroid Build Coastguard Worker        "outputVoltageSetting", "temperature", "status", "leds",
142*b7c941bbSAndroid Build Coastguard Worker        "mainFineResistor", "serialNumber", "sampleRate",
143*b7c941bbSAndroid Build Coastguard Worker        "dacCalLow", "dacCalHigh",
144*b7c941bbSAndroid Build Coastguard Worker        "powerUpCurrentLimit", "runTimeCurrentLimit", "powerUpTime",
145*b7c941bbSAndroid Build Coastguard Worker        "usbFineResistor", "auxFineResistor",
146*b7c941bbSAndroid Build Coastguard Worker        "initialUsbVoltage", "initialAuxVoltage",
147*b7c941bbSAndroid Build Coastguard Worker        "hardwareRevision", "temperatureLimit", "usbPassthroughMode",
148*b7c941bbSAndroid Build Coastguard Worker        "mainCoarseResistor", "usbCoarseResistor", "auxCoarseResistor",
149*b7c941bbSAndroid Build Coastguard Worker        "defMainFineResistor", "defUsbFineResistor", "defAuxFineResistor",
150*b7c941bbSAndroid Build Coastguard Worker        "defMainCoarseResistor", "defUsbCoarseResistor", "defAuxCoarseResistor",
151*b7c941bbSAndroid Build Coastguard Worker        "eventCode", "eventData", ]
152*b7c941bbSAndroid Build Coastguard Worker
153*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BBB", 0x01, 0x00, 0x00)
154*b7c941bbSAndroid Build Coastguard Worker    while True:  # Keep reading, discarding non-status packets
155*b7c941bbSAndroid Build Coastguard Worker      bytes = self._ReadPacket()
156*b7c941bbSAndroid Build Coastguard Worker      if not bytes: return None
157*b7c941bbSAndroid Build Coastguard Worker      if len(bytes) != struct.calcsize(STATUS_FORMAT) or bytes[0] != "\x10":
158*b7c941bbSAndroid Build Coastguard Worker        print >>sys.stderr, "wanted status, dropped type=0x%02x, len=%d" % (
159*b7c941bbSAndroid Build Coastguard Worker                ord(bytes[0]), len(bytes))
160*b7c941bbSAndroid Build Coastguard Worker        continue
161*b7c941bbSAndroid Build Coastguard Worker
162*b7c941bbSAndroid Build Coastguard Worker      status = dict(zip(STATUS_FIELDS, struct.unpack(STATUS_FORMAT, bytes)))
163*b7c941bbSAndroid Build Coastguard Worker      assert status["packetType"] == 0x10
164*b7c941bbSAndroid Build Coastguard Worker      for k in status.keys():
165*b7c941bbSAndroid Build Coastguard Worker        if k.endswith("VoltageSetting"):
166*b7c941bbSAndroid Build Coastguard Worker          status[k] = 2.0 + status[k] * 0.01
167*b7c941bbSAndroid Build Coastguard Worker        elif k.endswith("FineCurrent"):
168*b7c941bbSAndroid Build Coastguard Worker          pass # needs calibration data
169*b7c941bbSAndroid Build Coastguard Worker        elif k.endswith("CoarseCurrent"):
170*b7c941bbSAndroid Build Coastguard Worker          pass # needs calibration data
171*b7c941bbSAndroid Build Coastguard Worker        elif k.startswith("voltage") or k.endswith("Voltage"):
172*b7c941bbSAndroid Build Coastguard Worker          status[k] = status[k] * 0.000125
173*b7c941bbSAndroid Build Coastguard Worker        elif k.endswith("Resistor"):
174*b7c941bbSAndroid Build Coastguard Worker          status[k] = 0.05 + status[k] * 0.0001
175*b7c941bbSAndroid Build Coastguard Worker          if k.startswith("aux") or k.startswith("defAux"): status[k] += 0.05
176*b7c941bbSAndroid Build Coastguard Worker        elif k.endswith("CurrentLimit"):
177*b7c941bbSAndroid Build Coastguard Worker          status[k] = 8 * (1023 - status[k]) / 1023.0
178*b7c941bbSAndroid Build Coastguard Worker      return status
179*b7c941bbSAndroid Build Coastguard Worker
180*b7c941bbSAndroid Build Coastguard Worker  def RampVoltage(self, start, end):
181*b7c941bbSAndroid Build Coastguard Worker    v = start
182*b7c941bbSAndroid Build Coastguard Worker    if v < 3.0: v = 3.0       # protocol doesn't support lower than this
183*b7c941bbSAndroid Build Coastguard Worker    while (v < end):
184*b7c941bbSAndroid Build Coastguard Worker      self.SetVoltage(v)
185*b7c941bbSAndroid Build Coastguard Worker      v += .1
186*b7c941bbSAndroid Build Coastguard Worker      time.sleep(.1)
187*b7c941bbSAndroid Build Coastguard Worker    self.SetVoltage(end)
188*b7c941bbSAndroid Build Coastguard Worker
189*b7c941bbSAndroid Build Coastguard Worker  def SetVoltage(self, v):
190*b7c941bbSAndroid Build Coastguard Worker    """ Set the output voltage, 0 to disable. """
191*b7c941bbSAndroid Build Coastguard Worker    if v == 0:
192*b7c941bbSAndroid Build Coastguard Worker      self._SendStruct("BBB", 0x01, 0x01, 0x00)
193*b7c941bbSAndroid Build Coastguard Worker    else:
194*b7c941bbSAndroid Build Coastguard Worker      self._SendStruct("BBB", 0x01, 0x01, int((v - 2.0) * 100))
195*b7c941bbSAndroid Build Coastguard Worker
196*b7c941bbSAndroid Build Coastguard Worker
197*b7c941bbSAndroid Build Coastguard Worker  def SetMaxCurrent(self, i):
198*b7c941bbSAndroid Build Coastguard Worker    """Set the max output current."""
199*b7c941bbSAndroid Build Coastguard Worker    assert i >= 0 and i <= 8
200*b7c941bbSAndroid Build Coastguard Worker
201*b7c941bbSAndroid Build Coastguard Worker    val = 1023 - int((i/8)*1023)
202*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BBB", 0x01, 0x0a, val & 0xff)
203*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BBB", 0x01, 0x0b, val >> 8)
204*b7c941bbSAndroid Build Coastguard Worker
205*b7c941bbSAndroid Build Coastguard Worker  def SetUsbPassthrough(self, val):
206*b7c941bbSAndroid Build Coastguard Worker    """ Set the USB passthrough mode: 0 = off, 1 = on,  2 = auto. """
207*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BBB", 0x01, 0x10, val)
208*b7c941bbSAndroid Build Coastguard Worker
209*b7c941bbSAndroid Build Coastguard Worker
210*b7c941bbSAndroid Build Coastguard Worker  def StartDataCollection(self):
211*b7c941bbSAndroid Build Coastguard Worker    """ Tell the device to start collecting and sending measurement data. """
212*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BBB", 0x01, 0x1b, 0x01) # Mystery command
213*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BBBBBBB", 0x02, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8)
214*b7c941bbSAndroid Build Coastguard Worker
215*b7c941bbSAndroid Build Coastguard Worker
216*b7c941bbSAndroid Build Coastguard Worker  def StopDataCollection(self):
217*b7c941bbSAndroid Build Coastguard Worker    """ Tell the device to stop collecting measurement data. """
218*b7c941bbSAndroid Build Coastguard Worker    self._SendStruct("BB", 0x03, 0x00) # stop
219*b7c941bbSAndroid Build Coastguard Worker
220*b7c941bbSAndroid Build Coastguard Worker
221*b7c941bbSAndroid Build Coastguard Worker  def CollectData(self):
222*b7c941bbSAndroid Build Coastguard Worker    """ Return some current samples.  Call StartDataCollection() first. """
223*b7c941bbSAndroid Build Coastguard Worker    while True:  # loop until we get data or a timeout
224*b7c941bbSAndroid Build Coastguard Worker      bytes = self._ReadPacket()
225*b7c941bbSAndroid Build Coastguard Worker      if not bytes: return None
226*b7c941bbSAndroid Build Coastguard Worker      if len(bytes) < 4 + 8 + 1 or bytes[0] < "\x20" or bytes[0] > "\x2F":
227*b7c941bbSAndroid Build Coastguard Worker        print >>sys.stderr, "wanted data, dropped type=0x%02x, len=%d" % (
228*b7c941bbSAndroid Build Coastguard Worker            ord(bytes[0]), len(bytes))
229*b7c941bbSAndroid Build Coastguard Worker        continue
230*b7c941bbSAndroid Build Coastguard Worker
231*b7c941bbSAndroid Build Coastguard Worker      seq, type, x, y = struct.unpack("BBBB", bytes[:4])
232*b7c941bbSAndroid Build Coastguard Worker      data = [struct.unpack(">hhhh", bytes[x:x+8])
233*b7c941bbSAndroid Build Coastguard Worker              for x in range(4, len(bytes) - 8, 8)]
234*b7c941bbSAndroid Build Coastguard Worker
235*b7c941bbSAndroid Build Coastguard Worker      if self._last_seq and seq & 0xF != (self._last_seq + 1) & 0xF:
236*b7c941bbSAndroid Build Coastguard Worker        print >>sys.stderr, "data sequence skipped, lost packet?"
237*b7c941bbSAndroid Build Coastguard Worker      self._last_seq = seq
238*b7c941bbSAndroid Build Coastguard Worker
239*b7c941bbSAndroid Build Coastguard Worker      if type == 0:
240*b7c941bbSAndroid Build Coastguard Worker        if not self._coarse_scale or not self._fine_scale:
241*b7c941bbSAndroid Build Coastguard Worker          print >>sys.stderr, "waiting for calibration, dropped data packet"
242*b7c941bbSAndroid Build Coastguard Worker          continue
243*b7c941bbSAndroid Build Coastguard Worker
244*b7c941bbSAndroid Build Coastguard Worker        def scale(val):
245*b7c941bbSAndroid Build Coastguard Worker          if val & 1:
246*b7c941bbSAndroid Build Coastguard Worker            return ((val & ~1) - self._coarse_zero) * self._coarse_scale
247*b7c941bbSAndroid Build Coastguard Worker          else:
248*b7c941bbSAndroid Build Coastguard Worker            return (val - self._fine_zero) * self._fine_scale
249*b7c941bbSAndroid Build Coastguard Worker
250*b7c941bbSAndroid Build Coastguard Worker        out_main = []
251*b7c941bbSAndroid Build Coastguard Worker        out_usb = []
252*b7c941bbSAndroid Build Coastguard Worker        for main, usb, aux, voltage in data:
253*b7c941bbSAndroid Build Coastguard Worker          out_main.append(scale(main))
254*b7c941bbSAndroid Build Coastguard Worker          out_usb.append(scale(usb))
255*b7c941bbSAndroid Build Coastguard Worker        return (out_main, out_usb)
256*b7c941bbSAndroid Build Coastguard Worker
257*b7c941bbSAndroid Build Coastguard Worker      elif type == 1:
258*b7c941bbSAndroid Build Coastguard Worker        self._fine_zero = data[0][0]
259*b7c941bbSAndroid Build Coastguard Worker        self._coarse_zero = data[1][0]
260*b7c941bbSAndroid Build Coastguard Worker        # print >>sys.stderr, "zero calibration: fine 0x%04x, coarse 0x%04x" % (
261*b7c941bbSAndroid Build Coastguard Worker        #     self._fine_zero, self._coarse_zero)
262*b7c941bbSAndroid Build Coastguard Worker
263*b7c941bbSAndroid Build Coastguard Worker      elif type == 2:
264*b7c941bbSAndroid Build Coastguard Worker        self._fine_ref = data[0][0]
265*b7c941bbSAndroid Build Coastguard Worker        self._coarse_ref = data[1][0]
266*b7c941bbSAndroid Build Coastguard Worker        # print >>sys.stderr, "ref calibration: fine 0x%04x, coarse 0x%04x" % (
267*b7c941bbSAndroid Build Coastguard Worker        #     self._fine_ref, self._coarse_ref)
268*b7c941bbSAndroid Build Coastguard Worker
269*b7c941bbSAndroid Build Coastguard Worker      else:
270*b7c941bbSAndroid Build Coastguard Worker        print >>sys.stderr, "discarding data packet type=0x%02x" % type
271*b7c941bbSAndroid Build Coastguard Worker        continue
272*b7c941bbSAndroid Build Coastguard Worker
273*b7c941bbSAndroid Build Coastguard Worker      if self._coarse_ref != self._coarse_zero:
274*b7c941bbSAndroid Build Coastguard Worker        self._coarse_scale = 2.88 / (self._coarse_ref - self._coarse_zero)
275*b7c941bbSAndroid Build Coastguard Worker      if self._fine_ref != self._fine_zero:
276*b7c941bbSAndroid Build Coastguard Worker        self._fine_scale = 0.0332 / (self._fine_ref - self._fine_zero)
277*b7c941bbSAndroid Build Coastguard Worker
278*b7c941bbSAndroid Build Coastguard Worker
279*b7c941bbSAndroid Build Coastguard Worker  def _SendStruct(self, fmt, *args):
280*b7c941bbSAndroid Build Coastguard Worker    """ Pack a struct (without length or checksum) and send it. """
281*b7c941bbSAndroid Build Coastguard Worker    data = struct.pack(fmt, *args)
282*b7c941bbSAndroid Build Coastguard Worker    data_len = len(data) + 1
283*b7c941bbSAndroid Build Coastguard Worker    checksum = (data_len + sum(struct.unpack("B" * len(data), data))) % 256
284*b7c941bbSAndroid Build Coastguard Worker    out = struct.pack("B", data_len) + data + struct.pack("B", checksum)
285*b7c941bbSAndroid Build Coastguard Worker    self.ser.write(out)
286*b7c941bbSAndroid Build Coastguard Worker
287*b7c941bbSAndroid Build Coastguard Worker
288*b7c941bbSAndroid Build Coastguard Worker  def _ReadPacket(self):
289*b7c941bbSAndroid Build Coastguard Worker    """ Read a single data record as a string (without length or checksum). """
290*b7c941bbSAndroid Build Coastguard Worker    len_char = self.ser.read(1)
291*b7c941bbSAndroid Build Coastguard Worker    if not len_char:
292*b7c941bbSAndroid Build Coastguard Worker      print >>sys.stderr, "timeout reading from serial port"
293*b7c941bbSAndroid Build Coastguard Worker      return None
294*b7c941bbSAndroid Build Coastguard Worker
295*b7c941bbSAndroid Build Coastguard Worker    data_len = struct.unpack("B", len_char)
296*b7c941bbSAndroid Build Coastguard Worker    data_len = ord(len_char)
297*b7c941bbSAndroid Build Coastguard Worker    if not data_len: return ""
298*b7c941bbSAndroid Build Coastguard Worker
299*b7c941bbSAndroid Build Coastguard Worker    result = self.ser.read(data_len)
300*b7c941bbSAndroid Build Coastguard Worker    if len(result) != data_len: return None
301*b7c941bbSAndroid Build Coastguard Worker    body = result[:-1]
302*b7c941bbSAndroid Build Coastguard Worker    checksum = (data_len + sum(struct.unpack("B" * len(body), body))) % 256
303*b7c941bbSAndroid Build Coastguard Worker    if result[-1] != struct.pack("B", checksum):
304*b7c941bbSAndroid Build Coastguard Worker      print >>sys.stderr, "invalid checksum from serial port"
305*b7c941bbSAndroid Build Coastguard Worker      return None
306*b7c941bbSAndroid Build Coastguard Worker    return result[:-1]
307*b7c941bbSAndroid Build Coastguard Worker
308*b7c941bbSAndroid Build Coastguard Worker  def _FlushInput(self):
309*b7c941bbSAndroid Build Coastguard Worker    """ Flush all read data until no more available. """
310*b7c941bbSAndroid Build Coastguard Worker    self.ser.flush()
311*b7c941bbSAndroid Build Coastguard Worker    flushed = 0
312*b7c941bbSAndroid Build Coastguard Worker    while True:
313*b7c941bbSAndroid Build Coastguard Worker      ready_r, ready_w, ready_x = select.select([self.ser], [], [self.ser], 0)
314*b7c941bbSAndroid Build Coastguard Worker      if len(ready_x) > 0:
315*b7c941bbSAndroid Build Coastguard Worker        print >>sys.stderr, "exception from serial port"
316*b7c941bbSAndroid Build Coastguard Worker        return None
317*b7c941bbSAndroid Build Coastguard Worker      elif len(ready_r) > 0:
318*b7c941bbSAndroid Build Coastguard Worker        flushed += 1
319*b7c941bbSAndroid Build Coastguard Worker        self.ser.read(1)  # This may cause underlying buffering.
320*b7c941bbSAndroid Build Coastguard Worker        self.ser.flush()  # Flush the underlying buffer too.
321*b7c941bbSAndroid Build Coastguard Worker      else:
322*b7c941bbSAndroid Build Coastguard Worker        break
323*b7c941bbSAndroid Build Coastguard Worker    if flushed > 0:
324*b7c941bbSAndroid Build Coastguard Worker      print >>sys.stderr, "dropped >%d bytes" % flushed
325*b7c941bbSAndroid Build Coastguard Worker
326*b7c941bbSAndroid Build Coastguard Workerdef main(argv):
327*b7c941bbSAndroid Build Coastguard Worker  """ Simple command-line interface for Monsoon."""
328*b7c941bbSAndroid Build Coastguard Worker  useful_flags = ["voltage", "status", "usbpassthrough", "samples", "current"]
329*b7c941bbSAndroid Build Coastguard Worker  if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
330*b7c941bbSAndroid Build Coastguard Worker    print __doc__.strip()
331*b7c941bbSAndroid Build Coastguard Worker    print FLAGS.MainModuleHelp()
332*b7c941bbSAndroid Build Coastguard Worker    return
333*b7c941bbSAndroid Build Coastguard Worker
334*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.includeusb:
335*b7c941bbSAndroid Build Coastguard Worker    num_channels = 2
336*b7c941bbSAndroid Build Coastguard Worker  else:
337*b7c941bbSAndroid Build Coastguard Worker    num_channels = 1
338*b7c941bbSAndroid Build Coastguard Worker
339*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.avg and FLAGS.avg < 0:
340*b7c941bbSAndroid Build Coastguard Worker    print "--avg must be greater than 0"
341*b7c941bbSAndroid Build Coastguard Worker    return
342*b7c941bbSAndroid Build Coastguard Worker
343*b7c941bbSAndroid Build Coastguard Worker  mon = Monsoon(device=FLAGS.device, serialno=FLAGS.serialno)
344*b7c941bbSAndroid Build Coastguard Worker
345*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.voltage is not None:
346*b7c941bbSAndroid Build Coastguard Worker    if FLAGS.ramp is not None:
347*b7c941bbSAndroid Build Coastguard Worker      mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
348*b7c941bbSAndroid Build Coastguard Worker    else:
349*b7c941bbSAndroid Build Coastguard Worker      mon.SetVoltage(FLAGS.voltage)
350*b7c941bbSAndroid Build Coastguard Worker
351*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.current is not None:
352*b7c941bbSAndroid Build Coastguard Worker    mon.SetMaxCurrent(FLAGS.current)
353*b7c941bbSAndroid Build Coastguard Worker
354*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.status:
355*b7c941bbSAndroid Build Coastguard Worker    items = sorted(mon.GetStatus().items())
356*b7c941bbSAndroid Build Coastguard Worker    print "\n".join(["%s: %s" % item for item in items])
357*b7c941bbSAndroid Build Coastguard Worker
358*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.usbpassthrough:
359*b7c941bbSAndroid Build Coastguard Worker    if FLAGS.usbpassthrough == 'off':
360*b7c941bbSAndroid Build Coastguard Worker      mon.SetUsbPassthrough(0)
361*b7c941bbSAndroid Build Coastguard Worker    elif FLAGS.usbpassthrough == 'on':
362*b7c941bbSAndroid Build Coastguard Worker      mon.SetUsbPassthrough(1)
363*b7c941bbSAndroid Build Coastguard Worker    elif FLAGS.usbpassthrough == 'auto':
364*b7c941bbSAndroid Build Coastguard Worker      mon.SetUsbPassthrough(2)
365*b7c941bbSAndroid Build Coastguard Worker    else:
366*b7c941bbSAndroid Build Coastguard Worker      sys.exit('bad passthrough flag: %s' % FLAGS.usbpassthrough)
367*b7c941bbSAndroid Build Coastguard Worker
368*b7c941bbSAndroid Build Coastguard Worker  if FLAGS.samples:
369*b7c941bbSAndroid Build Coastguard Worker    # Make sure state is normal
370*b7c941bbSAndroid Build Coastguard Worker    mon.StopDataCollection()
371*b7c941bbSAndroid Build Coastguard Worker    status = mon.GetStatus()
372*b7c941bbSAndroid Build Coastguard Worker    native_hz = status["sampleRate"] * 1000
373*b7c941bbSAndroid Build Coastguard Worker
374*b7c941bbSAndroid Build Coastguard Worker    # Collect and average samples as specified
375*b7c941bbSAndroid Build Coastguard Worker    mon.StartDataCollection()
376*b7c941bbSAndroid Build Coastguard Worker
377*b7c941bbSAndroid Build Coastguard Worker    # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
378*b7c941bbSAndroid Build Coastguard Worker    # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
379*b7c941bbSAndroid Build Coastguard Worker    # This is the error accumulator in a variation of Bresenham's algorithm.
380*b7c941bbSAndroid Build Coastguard Worker    emitted = offset = 0
381*b7c941bbSAndroid Build Coastguard Worker    chan_buffers = tuple([] for _ in range(num_channels))
382*b7c941bbSAndroid Build Coastguard Worker    # past n samples for rolling average
383*b7c941bbSAndroid Build Coastguard Worker    history_deques = tuple(collections.deque() for _ in range(num_channels))
384*b7c941bbSAndroid Build Coastguard Worker
385*b7c941bbSAndroid Build Coastguard Worker    try:
386*b7c941bbSAndroid Build Coastguard Worker      last_flush = time.time()
387*b7c941bbSAndroid Build Coastguard Worker      while emitted < FLAGS.samples or FLAGS.samples == -1:
388*b7c941bbSAndroid Build Coastguard Worker        # The number of raw samples to consume before emitting the next output
389*b7c941bbSAndroid Build Coastguard Worker        need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
390*b7c941bbSAndroid Build Coastguard Worker        if need > len(chan_buffers[0]):     # still need more input samples
391*b7c941bbSAndroid Build Coastguard Worker          chans_samples = mon.CollectData()
392*b7c941bbSAndroid Build Coastguard Worker          if not all(chans_samples): break
393*b7c941bbSAndroid Build Coastguard Worker          for chan_buffer, chan_samples in zip(chan_buffers, chans_samples):
394*b7c941bbSAndroid Build Coastguard Worker            chan_buffer.extend(chan_samples)
395*b7c941bbSAndroid Build Coastguard Worker        else:
396*b7c941bbSAndroid Build Coastguard Worker          # Have enough data, generate output samples.
397*b7c941bbSAndroid Build Coastguard Worker          # Adjust for consuming 'need' input samples.
398*b7c941bbSAndroid Build Coastguard Worker          offset += need * FLAGS.hz
399*b7c941bbSAndroid Build Coastguard Worker          while offset >= native_hz:  # maybe multiple, if FLAGS.hz > native_hz
400*b7c941bbSAndroid Build Coastguard Worker            this_sample = [sum(chan[:need]) / need for chan in chan_buffers]
401*b7c941bbSAndroid Build Coastguard Worker
402*b7c941bbSAndroid Build Coastguard Worker            if FLAGS.timestamp: print int(time.time()),
403*b7c941bbSAndroid Build Coastguard Worker
404*b7c941bbSAndroid Build Coastguard Worker            if FLAGS.avg:
405*b7c941bbSAndroid Build Coastguard Worker              chan_avgs = []
406*b7c941bbSAndroid Build Coastguard Worker              for chan_deque, chan_sample in zip(history_deques, this_sample):
407*b7c941bbSAndroid Build Coastguard Worker                chan_deque.appendleft(chan_sample)
408*b7c941bbSAndroid Build Coastguard Worker                if len(chan_deque) > FLAGS.avg: chan_deque.pop()
409*b7c941bbSAndroid Build Coastguard Worker                chan_avgs.append(sum(chan_deque) / len(chan_deque))
410*b7c941bbSAndroid Build Coastguard Worker              # Interleave channel rolling avgs with latest channel data
411*b7c941bbSAndroid Build Coastguard Worker              data_to_print = [datum
412*b7c941bbSAndroid Build Coastguard Worker                               for pair in zip(this_sample, chan_avgs)
413*b7c941bbSAndroid Build Coastguard Worker                               for datum in pair]
414*b7c941bbSAndroid Build Coastguard Worker            else:
415*b7c941bbSAndroid Build Coastguard Worker              data_to_print = this_sample
416*b7c941bbSAndroid Build Coastguard Worker
417*b7c941bbSAndroid Build Coastguard Worker            fmt = ' '.join('%f' for _ in data_to_print)
418*b7c941bbSAndroid Build Coastguard Worker            print fmt % tuple(data_to_print)
419*b7c941bbSAndroid Build Coastguard Worker
420*b7c941bbSAndroid Build Coastguard Worker            sys.stdout.flush()
421*b7c941bbSAndroid Build Coastguard Worker
422*b7c941bbSAndroid Build Coastguard Worker            offset -= native_hz
423*b7c941bbSAndroid Build Coastguard Worker            emitted += 1              # adjust for emitting 1 output sample
424*b7c941bbSAndroid Build Coastguard Worker          chan_buffers = tuple(c[need:] for c in chan_buffers)
425*b7c941bbSAndroid Build Coastguard Worker          now = time.time()
426*b7c941bbSAndroid Build Coastguard Worker          if now - last_flush >= 0.99:  # flush every second
427*b7c941bbSAndroid Build Coastguard Worker            sys.stdout.flush()
428*b7c941bbSAndroid Build Coastguard Worker            last_flush = now
429*b7c941bbSAndroid Build Coastguard Worker    except KeyboardInterrupt:
430*b7c941bbSAndroid Build Coastguard Worker      print >>sys.stderr, "interrupted"
431*b7c941bbSAndroid Build Coastguard Worker
432*b7c941bbSAndroid Build Coastguard Worker    mon.StopDataCollection()
433*b7c941bbSAndroid Build Coastguard Worker
434*b7c941bbSAndroid Build Coastguard Worker
435*b7c941bbSAndroid Build Coastguard Workerif __name__ == '__main__':
436*b7c941bbSAndroid Build Coastguard Worker  # Define flags here to avoid conflicts with people who use us as a library
437*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_boolean("status", None, "Print power meter status")
438*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_integer("avg", None,
439*b7c941bbSAndroid Build Coastguard Worker                       "Also report average over last n data points")
440*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_float("voltage", None, "Set output voltage (0 for off)")
441*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_float("current", None, "Set max output current")
442*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_string("usbpassthrough", None, "USB control (on, off, auto)")
443*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_integer("samples", None,
444*b7c941bbSAndroid Build Coastguard Worker                       "Collect and print this many samples. "
445*b7c941bbSAndroid Build Coastguard Worker                       "-1 means collect indefinitely.")
446*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_integer("hz", 5000, "Print this many samples/sec")
447*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_string("device", None,
448*b7c941bbSAndroid Build Coastguard Worker                      "Path to the device in /dev/... (ex:/dev/ttyACM1)")
449*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_integer("serialno", None, "Look for a device with this serial number")
450*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_boolean("timestamp", None,
451*b7c941bbSAndroid Build Coastguard Worker                       "Also print integer (seconds) timestamp on each line")
452*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_boolean("ramp", True, "Gradually increase voltage")
453*b7c941bbSAndroid Build Coastguard Worker  flags.DEFINE_boolean("includeusb", False, "Include measurements from USB channel")
454*b7c941bbSAndroid Build Coastguard Worker
455*b7c941bbSAndroid Build Coastguard Worker  main(FLAGS(sys.argv))
456