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