1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from acts.signals import ControllerError 18 19 20class MonsoonError(ControllerError): 21 """Raised for exceptions encountered when interfacing with a Monsoon device. 22 """ 23 24 25class PassthroughStates(object): 26 """An enum containing the values for power monitor's passthrough states.""" 27 # "Off" or 0 means USB always off. 28 OFF = 0 29 # "On" or 1 means USB always on. 30 ON = 1 31 # "Auto" or 2 means USB is automatically turned off during sampling, and 32 # turned back on after sampling. 33 AUTO = 2 34 35 36PASSTHROUGH_STATES = { 37 'off': PassthroughStates.OFF, 38 'on': PassthroughStates.ON, 39 'auto': PassthroughStates.AUTO 40} 41 42 43class MonsoonDataRecord(object): 44 """A data class for Monsoon data points.""" 45 def __init__(self, time, current): 46 """Creates a new MonsoonDataRecord. 47 48 Args: 49 time: the string '{time}s', where time is measured in seconds since 50 the beginning of the data collection. 51 current: The current in Amperes as a string. 52 """ 53 self._time = float(time[:-1]) 54 self._current = float(current) 55 56 @property 57 def time(self): 58 """The time the record was fetched.""" 59 return self._time 60 61 @property 62 def current(self): 63 """The amount of current in Amperes measured for the given record.""" 64 return self._current 65 66 @classmethod 67 def create_from_record_line(cls, line): 68 """Creates a data record from the line passed in from the output file. 69 """ 70 return cls(*line.split(' ')) 71 72 73class MonsoonResult(object): 74 """An object that contains aggregated data collected during sampling. 75 76 Attributes: 77 _num_samples: The number of samples gathered. 78 _sum_currents: The total sum of all current values gathered, in amperes. 79 _hz: The frequency sampling is being done at. 80 _voltage: The voltage output during sampling. 81 """ 82 83 # The number of decimal places to round a value to. 84 ROUND_TO = 6 85 86 def __init__(self, num_samples, sum_currents, hz, voltage, datafile_path): 87 """Creates a new MonsoonResult. 88 89 Args: 90 num_samples: the number of samples collected. 91 sum_currents: the total summation of every current measurement. 92 hz: the number of samples per second. 93 voltage: the voltage used during the test. 94 datafile_path: the path to the monsoon data file. 95 """ 96 self._num_samples = num_samples 97 self._sum_currents = sum_currents 98 self._hz = hz 99 self._voltage = voltage 100 self.tag = datafile_path 101 102 def get_data_points(self): 103 """Returns an iterator of MonsoonDataRecords.""" 104 class MonsoonDataIterator: 105 def __init__(self, file): 106 self.file = file 107 108 def __iter__(self): 109 with open(self.file, 'r') as f: 110 for line in f: 111 # Remove the newline character. 112 line.strip() 113 yield MonsoonDataRecord.create_from_record_line(line) 114 115 return MonsoonDataIterator(self.tag) 116 117 @property 118 def num_samples(self): 119 """The number of samples recorded during the test.""" 120 return self._num_samples 121 122 @property 123 def average_current(self): 124 """Average current in mA.""" 125 if self.num_samples == 0: 126 return 0 127 return round(self._sum_currents * 1000 / self.num_samples, 128 self.ROUND_TO) 129 130 @property 131 def total_charge(self): 132 """Total charged used in the unit of mAh.""" 133 return round((self._sum_currents / self._hz) * 1000 / 3600, 134 self.ROUND_TO) 135 136 @property 137 def total_power(self): 138 """Total power used.""" 139 return round(self.average_current * self._voltage, self.ROUND_TO) 140 141 @property 142 def voltage(self): 143 """The voltage during the measurement (in Volts).""" 144 return self._voltage 145 146 def __str__(self): 147 return ('avg current: %s\n' 148 'total charge: %s\n' 149 'total power: %s\n' 150 'total samples: %s' % (self.average_current, self.total_charge, 151 self.total_power, self._num_samples)) 152