Update sdk/platform-tools to version 26.0.0.
[android_tools.git] / sdk / platform-tools / systrace / catapult / telemetry / telemetry / internal / platform / profiler / monsoon.py
blob72425f7f688857946b8f93f5b25393c649cdb08d
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Interface for a USB-connected Monsoon power meter.
7 http://msoon.com/LabEquipment/PowerMonitor/
8 Currently Unix-only. Relies on fcntl, /dev, and /tmp.
9 """
11 import collections
12 import logging
13 import os
14 import select
15 import struct
16 import time
18 import serial # pylint: disable=import-error,no-name-in-module
19 import serial.tools.list_ports # pylint: disable=import-error,no-name-in-module
22 Power = collections.namedtuple('Power', ['amps', 'volts'])
25 class Monsoon(object):
26 """Provides a simple class to use the power meter.
28 mon = monsoon.Monsoon()
29 mon.SetVoltage(3.7)
30 mon.StartDataCollection()
31 mydata = []
32 while len(mydata) < 1000:
33 mydata.extend(mon.CollectData())
34 mon.StopDataCollection()
35 """
37 def __init__(self, device=None, serialno=None, wait=True):
38 """Establish a connection to a Monsoon.
40 By default, opens the first available port, waiting if none are ready.
41 A particular port can be specified with 'device', or a particular Monsoon
42 can be specified with 'serialno' (using the number printed on its back).
43 With wait=False, IOError is thrown if a device is not immediately available.
44 """
45 assert float(serial.VERSION) >= 2.7, \
46 'Monsoon requires pyserial v2.7 or later. You have %s' % serial.VERSION
48 self._coarse_ref = self._fine_ref = self._coarse_zero = self._fine_zero = 0
49 self._coarse_scale = self._fine_scale = 0
50 self._last_seq = 0
51 self._voltage_multiplier = None
53 if device:
54 self.ser = serial.Serial(device, timeout=1)
55 return
57 while 1:
58 for (port, desc, _) in serial.tools.list_ports.comports():
59 if not desc.lower().startswith('mobile device power monitor'):
60 continue
61 tmpname = '/tmp/monsoon.%s.%s' % (os.uname()[0], os.path.basename(port))
62 self._tempfile = open(tmpname, 'w')
63 try: # Use a lockfile to ensure exclusive access.
64 # Put the import in here to avoid doing it on unsupported platforms.
65 import fcntl # pylint: disable=import-error
66 fcntl.lockf(self._tempfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
67 except IOError:
68 logging.error('device %s is in use', port)
69 continue
71 try: # Try to open the device.
72 self.ser = serial.Serial(port, timeout=1)
73 self.StopDataCollection() # Just in case.
74 self._FlushInput() # Discard stale input.
75 status = self.GetStatus()
76 except IOError, e:
77 logging.error('error opening device %s: %s', port, e)
78 continue
80 if not status:
81 logging.error('no response from device %s', port)
82 elif serialno and status['serialNumber'] != serialno:
83 logging.error('device %s is #%d', port, status['serialNumber'])
84 else:
85 if status['hardwareRevision'] == 1:
86 self._voltage_multiplier = 62.5 / 10**6
87 else:
88 self._voltage_multiplier = 125.0 / 10**6
89 return
91 self._tempfile = None
92 if not wait:
93 raise IOError('No device found')
94 logging.info('waiting for device...')
95 time.sleep(1)
97 def GetStatus(self):
98 """Requests and waits for status. Returns status dictionary."""
100 # status packet format
101 STATUS_FORMAT = '>BBBhhhHhhhHBBBxBbHBHHHHBbbHHBBBbbbbbbbbbBH'
102 STATUS_FIELDS = [
103 'packetType', 'firmwareVersion', 'protocolVersion',
104 'mainFineCurrent', 'usbFineCurrent', 'auxFineCurrent', 'voltage1',
105 'mainCoarseCurrent', 'usbCoarseCurrent', 'auxCoarseCurrent', 'voltage2',
106 'outputVoltageSetting', 'temperature', 'status', 'leds',
107 'mainFineResistor', 'serialNumber', 'sampleRate',
108 'dacCalLow', 'dacCalHigh',
109 'powerUpCurrentLimit', 'runTimeCurrentLimit', 'powerUpTime',
110 'usbFineResistor', 'auxFineResistor',
111 'initialUsbVoltage', 'initialAuxVoltage',
112 'hardwareRevision', 'temperatureLimit', 'usbPassthroughMode',
113 'mainCoarseResistor', 'usbCoarseResistor', 'auxCoarseResistor',
114 'defMainFineResistor', 'defUsbFineResistor', 'defAuxFineResistor',
115 'defMainCoarseResistor', 'defUsbCoarseResistor', 'defAuxCoarseResistor',
116 'eventCode', 'eventData',
119 self._SendStruct('BBB', 0x01, 0x00, 0x00)
120 while 1: # Keep reading, discarding non-status packets.
121 data = self._ReadPacket()
122 if not data:
123 return None
124 if len(data) != struct.calcsize(STATUS_FORMAT) or data[0] != '\x10':
125 logging.debug('wanted status, dropped type=0x%02x, len=%d',
126 ord(data[0]), len(data))
127 continue
129 status = dict(zip(STATUS_FIELDS, struct.unpack(STATUS_FORMAT, data)))
130 assert status['packetType'] == 0x10
131 for k in status.keys():
132 if k.endswith('VoltageSetting'):
133 status[k] = 2.0 + status[k] * 0.01
134 elif k.endswith('FineCurrent'):
135 pass # Needs calibration data.
136 elif k.endswith('CoarseCurrent'):
137 pass # Needs calibration data.
138 elif k.startswith('voltage') or k.endswith('Voltage'):
139 status[k] = status[k] * 0.000125
140 elif k.endswith('Resistor'):
141 status[k] = 0.05 + status[k] * 0.0001
142 if k.startswith('aux') or k.startswith('defAux'):
143 status[k] += 0.05
144 elif k.endswith('CurrentLimit'):
145 status[k] = 8 * (1023 - status[k]) / 1023.0
146 return status
149 def SetVoltage(self, v):
150 """Set the output voltage, 0 to disable."""
151 if v == 0:
152 self._SendStruct('BBB', 0x01, 0x01, 0x00)
153 else:
154 self._SendStruct('BBB', 0x01, 0x01, int((v - 2.0) * 100))
156 def SetStartupCurrent(self, a):
157 """Set the max startup output current. the unit of |a| : Amperes """
158 assert a >= 0 and a <= 8
160 val = 1023 - int((a/8.0)*1023)
161 self._SendStruct('BBB', 0x01, 0x08, val & 0xff)
162 self._SendStruct('BBB', 0x01, 0x09, val >> 8)
164 def SetMaxCurrent(self, a):
165 """Set the max output current. the unit of |a| : Amperes """
166 assert a >= 0 and a <= 8
168 val = 1023 - int((a/8.0)*1023)
169 self._SendStruct('BBB', 0x01, 0x0a, val & 0xff)
170 self._SendStruct('BBB', 0x01, 0x0b, val >> 8)
172 def SetUsbPassthrough(self, val):
173 """Set the USB passthrough mode: 0 = off, 1 = on, 2 = auto."""
174 self._SendStruct('BBB', 0x01, 0x10, val)
177 def StartDataCollection(self):
178 """Tell the device to start collecting and sending measurement data."""
179 self._SendStruct('BBB', 0x01, 0x1b, 0x01) # Mystery command.
180 self._SendStruct('BBBBBBB', 0x02, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8)
183 def StopDataCollection(self):
184 """Tell the device to stop collecting measurement data."""
185 self._SendStruct('BB', 0x03, 0x00) # Stop.
188 def CollectData(self):
189 """Return some current samples. Call StartDataCollection() first."""
190 while 1: # Loop until we get data or a timeout.
191 data = self._ReadPacket()
192 if not data:
193 return None
194 if len(data) < 4 + 8 + 1 or data[0] < '\x20' or data[0] > '\x2F':
195 logging.debug('wanted data, dropped type=0x%02x, len=%d',
196 ord(data[0]), len(data))
197 continue
199 seq, packet_type, x, _ = struct.unpack('BBBB', data[:4])
200 data = [struct.unpack(">hhhh", data[x:x+8])
201 for x in range(4, len(data) - 8, 8)]
203 if self._last_seq and seq & 0xF != (self._last_seq + 1) & 0xF:
204 logging.info('data sequence skipped, lost packet?')
205 self._last_seq = seq
207 if packet_type == 0:
208 if not self._coarse_scale or not self._fine_scale:
209 logging.info('waiting for calibration, dropped data packet')
210 continue
212 out = []
213 for main, usb, _, voltage in data:
214 main_voltage_v = self._voltage_multiplier * (voltage & ~3)
215 sample = 0.0
216 if main & 1:
217 sample += ((main & ~1) - self._coarse_zero) * self._coarse_scale
218 else:
219 sample += (main - self._fine_zero) * self._fine_scale
220 if usb & 1:
221 sample += ((usb & ~1) - self._coarse_zero) * self._coarse_scale
222 else:
223 sample += (usb - self._fine_zero) * self._fine_scale
224 out.append(Power(sample, main_voltage_v))
225 return out
227 elif packet_type == 1:
228 self._fine_zero = data[0][0]
229 self._coarse_zero = data[1][0]
231 elif packet_type == 2:
232 self._fine_ref = data[0][0]
233 self._coarse_ref = data[1][0]
235 else:
236 logging.debug('discarding data packet type=0x%02x', packet_type)
237 continue
239 if self._coarse_ref != self._coarse_zero:
240 self._coarse_scale = 2.88 / (self._coarse_ref - self._coarse_zero)
241 if self._fine_ref != self._fine_zero:
242 self._fine_scale = 0.0332 / (self._fine_ref - self._fine_zero)
245 def _SendStruct(self, fmt, *args):
246 """Pack a struct (without length or checksum) and send it."""
247 data = struct.pack(fmt, *args)
248 data_len = len(data) + 1
249 checksum = (data_len + sum(struct.unpack('B' * len(data), data))) % 256
250 out = struct.pack('B', data_len) + data + struct.pack('B', checksum)
251 self.ser.write(out)
254 def _ReadPacket(self):
255 """Read a single data record as a string (without length or checksum)."""
256 len_char = self.ser.read(1)
257 if not len_char:
258 logging.error('timeout reading from serial port')
259 return None
261 data_len = struct.unpack('B', len_char)
262 data_len = ord(len_char)
263 if not data_len:
264 return ''
266 result = self.ser.read(data_len)
267 if len(result) != data_len:
268 return None
269 body = result[:-1]
270 checksum = (data_len + sum(struct.unpack('B' * len(body), body))) % 256
271 if result[-1] != struct.pack('B', checksum):
272 logging.error('invalid checksum from serial port')
273 return None
274 return result[:-1]
276 def _FlushInput(self):
277 """Flush all read data until no more available."""
278 self.ser.flush()
279 flushed = 0
280 while True:
281 ready_r, _, ready_x = select.select([self.ser], [], [self.ser], 0)
282 if len(ready_x) > 0:
283 logging.error('exception from serial port')
284 return None
285 elif len(ready_r) > 0:
286 flushed += 1
287 self.ser.read(1) # This may cause underlying buffering.
288 self.ser.flush() # Flush the underlying buffer too.
289 else:
290 break
291 if flushed > 0:
292 logging.debug('dropped >%d bytes', flushed)