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, sample_time, relative_time, current): 46 """Creates a new MonsoonDataRecord. 47 48 Args: 49 sample_time: the unix timestamp of the sample. 50 relative_time: the time since the start of the measurement. 51 current: The current in Amperes as a string. 52 """ 53 self._sample_time = sample_time 54 self._relative_time = relative_time 55 self._current = current 56 57 @property 58 def time(self): 59 """The time the record was fetched.""" 60 return self._sample_time 61 62 @property 63 def relative_time(self): 64 """The time the record was fetched, relative to collection start.""" 65 return self._relative_time 66 67 @property 68 def current(self): 69 """The amount of current in Amperes measured for the given record.""" 70 return self._current 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 start_time = None 111 for line in f: 112 # Remove the newline character. 113 line.strip() 114 sample_time, current = map(float, line.split(' ')) 115 if start_time is None: 116 start_time = sample_time 117 yield MonsoonDataRecord(sample_time, 118 sample_time - start_time, 119 current) 120 121 return MonsoonDataIterator(self.tag) 122 123 @property 124 def num_samples(self): 125 """The number of samples recorded during the test.""" 126 return self._num_samples 127 128 @property 129 def average_current(self): 130 """Average current in mA.""" 131 if self.num_samples == 0: 132 return 0 133 return round(self._sum_currents * 1000 / self.num_samples, 134 self.ROUND_TO) 135 136 @property 137 def total_charge(self): 138 """Total charged used in the unit of mAh.""" 139 return round((self._sum_currents / self._hz) * 1000 / 3600, 140 self.ROUND_TO) 141 142 @property 143 def total_power(self): 144 """Total power used.""" 145 return round(self.average_current * self._voltage, self.ROUND_TO) 146 147 @property 148 def voltage(self): 149 """The voltage during the measurement (in Volts).""" 150 return self._voltage 151 152 def __str__(self): 153 return ('avg current: %s\n' 154 'total charge: %s\n' 155 'total power: %s\n' 156 'total samples: %s' % (self.average_current, self.total_charge, 157 self.total_power, self._num_samples)) 158