Upstream version 6.35.121.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
23
24 Power = collections.namedtuple('Power', ['amps', 'volts'])
25
26
27 class Monsoon:
28   """Provides a simple class to use the power meter.
29
30   mon = monsoon.Monsoon()
31   mon.SetVoltage(3.7)
32   mon.StartDataCollection()
33   mydata = []
34   while len(mydata) < 1000:
35     mydata.extend(mon.CollectData())
36   mon.StopDataCollection()
37   """
38
39   def __init__(self, device=None, serialno=None, wait=True):
40     """Establish a connection to a Monsoon.
41
42     By default, opens the first available port, waiting if none are ready.
43     A particular port can be specified with 'device', or a particular Monsoon
44     can be specified with 'serialno' (using the number printed on its back).
45     With wait=False, IOError is thrown if a device is not immediately available.
46     """
47
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
52
53     if device:
54       self.ser = serial.Serial(device, timeout=1)
55       return
56
57     while 1:  # Try all /dev/ttyACM* until we find one we can use.
58       for dev in os.listdir('/dev'):
59         if not dev.startswith('ttyACM'):
60           continue
61         tmpname = '/tmp/monsoon.%s.%s' % (os.uname()[0], dev)
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
66           fcntl.lockf(self._tempfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
67         except IOError:
68           logging.error('device %s is in use', dev)
69           continue
70
71         try:  # Try to open the device.
72           self.ser = serial.Serial('/dev/%s' % dev, 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', dev, e)
78           continue
79
80         if not status:
81           logging.error('no response from device %s', dev)
82         elif serialno and status['serialNumber'] != serialno:
83           logging.error('device %s is #%d', dev, 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
90
91       self._tempfile = None
92       if not wait:
93         raise IOError('No device found')
94       logging.info('waiting for device...')
95       time.sleep(1)
96
97   def GetStatus(self):
98     """Requests and waits for status.  Returns status dictionary."""
99
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',
117     ]
118
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
128
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
147
148
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))
155
156
157   def SetMaxCurrent(self, i):
158     """Set the max output current."""
159     assert i >= 0 and i <= 8
160
161     val = 1023 - int((i/8)*1023)
162     self._SendStruct('BBB', 0x01, 0x0a, val & 0xff)
163     self._SendStruct('BBB', 0x01, 0x0b, val >> 8)
164
165   def SetUsbPassthrough(self, val):
166     """Set the USB passthrough mode: 0 = off, 1 = on,  2 = auto."""
167     self._SendStruct('BBB', 0x01, 0x10, val)
168
169
170   def StartDataCollection(self):
171     """Tell the device to start collecting and sending measurement data."""
172     self._SendStruct('BBB', 0x01, 0x1b, 0x01)  # Mystery command.
173     self._SendStruct('BBBBBBB', 0x02, 0xff, 0xff, 0xff, 0xff, 0x03, 0xe8)
174
175
176   def StopDataCollection(self):
177     """Tell the device to stop collecting measurement data."""
178     self._SendStruct('BB', 0x03, 0x00)  # Stop.
179
180
181   def CollectData(self):
182     """Return some current samples.  Call StartDataCollection() first."""
183     while 1:  # Loop until we get data or a timeout.
184       data = self._ReadPacket()
185       if not data:
186         return None
187       if len(data) < 4 + 8 + 1 or data[0] < '\x20' or data[0] > '\x2F':
188         logging.debug('wanted data, dropped type=0x%02x, len=%d',
189             ord(data[0]), len(data))
190         continue
191
192       seq, packet_type, x, _ = struct.unpack('BBBB', data[:4])
193       data = [struct.unpack(">hhhh", data[x:x+8])
194               for x in range(4, len(data) - 8, 8)]
195
196       if self._last_seq and seq & 0xF != (self._last_seq + 1) & 0xF:
197         logging.info('data sequence skipped, lost packet?')
198       self._last_seq = seq
199
200       if packet_type == 0:
201         if not self._coarse_scale or not self._fine_scale:
202           logging.info('waiting for calibration, dropped data packet')
203           continue
204
205         out = []
206         for main, usb, _, voltage in data:
207           main_voltage_v = self._voltage_multiplier * (voltage & ~3)
208           sample = 0.0
209           if main & 1:
210             sample += ((main & ~1) - self._coarse_zero) * self._coarse_scale
211           else:
212             sample += (main - self._fine_zero) * self._fine_scale
213           if usb & 1:
214             sample += ((usb & ~1) - self._coarse_zero) * self._coarse_scale
215           else:
216             sample += (usb - self._fine_zero) * self._fine_scale
217           out.append(Power(sample, main_voltage_v))
218         return out
219
220       elif packet_type == 1:
221         self._fine_zero = data[0][0]
222         self._coarse_zero = data[1][0]
223
224       elif packet_type == 2:
225         self._fine_ref = data[0][0]
226         self._coarse_ref = data[1][0]
227
228       else:
229         logging.debug('discarding data packet type=0x%02x', packet_type)
230         continue
231
232       if self._coarse_ref != self._coarse_zero:
233         self._coarse_scale = 2.88 / (self._coarse_ref - self._coarse_zero)
234       if self._fine_ref != self._fine_zero:
235         self._fine_scale = 0.0332 / (self._fine_ref - self._fine_zero)
236
237
238   def _SendStruct(self, fmt, *args):
239     """Pack a struct (without length or checksum) and send it."""
240     data = struct.pack(fmt, *args)
241     data_len = len(data) + 1
242     checksum = (data_len + sum(struct.unpack('B' * len(data), data))) % 256
243     out = struct.pack('B', data_len) + data + struct.pack('B', checksum)
244     self.ser.write(out)
245
246
247   def _ReadPacket(self):
248     """Read a single data record as a string (without length or checksum)."""
249     len_char = self.ser.read(1)
250     if not len_char:
251       logging.error('timeout reading from serial port')
252       return None
253
254     data_len = struct.unpack('B', len_char)
255     data_len = ord(len_char)
256     if not data_len:
257       return ''
258
259     result = self.ser.read(data_len)
260     if len(result) != data_len:
261       return None
262     body = result[:-1]
263     checksum = (data_len + sum(struct.unpack('B' * len(body), body))) % 256
264     if result[-1] != struct.pack('B', checksum):
265       logging.error('invalid checksum from serial port')
266       return None
267     return result[:-1]
268
269   def _FlushInput(self):
270     """Flush all read data until no more available."""
271     self.ser.flush()
272     flushed = 0
273     while True:
274       ready_r, _, ready_x = select.select([self.ser], [], [self.ser], 0)
275       if len(ready_x) > 0:
276         logging.error('exception from serial port')
277         return None
278       elif len(ready_r) > 0:
279         flushed += 1
280         self.ser.read(1)  # This may cause underlying buffering.
281         self.ser.flush()  # Flush the underlying buffer too.
282       else:
283         break
284     if flushed > 0:
285       logging.debug('dropped >%d bytes', flushed)