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