• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2020 - 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
17import logging
18import tempfile
19
20from acts.controllers import power_metrics
21from acts.controllers.monsoon_lib.api.common import MonsoonError
22
23
24class ResourcesRegistryError(Exception):
25    pass
26
27
28_REGISTRY = {}
29
30
31def update_registry(registry):
32    """Updates the registry with the one passed.
33
34    Overriding a previous value is not allowed.
35
36    Args:
37        registry: A dictionary.
38    Raises:
39        ResourceRegistryError if a property is updated with a different value.
40    """
41    for k, v in registry.items():
42        if k in _REGISTRY:
43            if v == _REGISTRY[k]:
44                continue
45            raise ResourcesRegistryError(
46                'Overwriting resources_registry fields is not allowed. %s was '
47                'already defined as %s and was attempted to be overwritten '
48                'with %s.' % (k, _REGISTRY[k], v))
49        _REGISTRY[k] = v
50
51
52def get_registry():
53    return _REGISTRY
54
55
56def _write_raw_data_in_standard_format(raw_data, path, start_time):
57    """Writes the raw data to a file in (seconds since epoch, amps).
58
59    TODO(b/155294049): Deprecate this once Monsoon controller output
60        format is updated.
61
62    Args:
63        start_time: Measurement start time in seconds since epoch
64        raw_data: raw data as list or generator of (timestamp, sample)
65        path: path to write output
66    """
67    with open(path, 'w') as f:
68        for timestamp, amps in raw_data:
69            f.write('%s %s\n' %
70                    (timestamp + start_time, amps))
71
72
73class BasePowerMonitor(object):
74
75    def setup(self, **kwargs):
76        raise NotImplementedError()
77
78    def connect_usb(self, **kwargs):
79        raise NotImplementedError()
80
81    def measure(self, **kwargs):
82        raise NotImplementedError()
83
84    def release_resources(self, **kwargs):
85        raise NotImplementedError()
86
87    def disconnect_usb(self, **kwargs):
88        raise NotImplementedError()
89
90    def get_metrics(self, **kwargs):
91        raise NotImplementedError()
92
93    def get_waveform(self, **kwargs):
94        raise NotImplementedError()
95
96    def teardown(self, **kwargs):
97        raise NotImplementedError()
98
99
100class PowerMonitorMonsoonFacade(BasePowerMonitor):
101
102    def __init__(self, monsoon):
103        """Constructs a PowerMonitorFacade.
104
105        Args:
106            monsoon: delegate monsoon object, either
107                acts.controllers.monsoon_lib.api.hvpm.monsoon.Monsoon or
108                acts.controllers.monsoon_lib.api.lvpm_stock.monsoon.Monsoon.
109        """
110        self.monsoon = monsoon
111        self._log = logging.getLogger()
112
113    def setup(self, monsoon_config=None, **__):
114        """Set up the Monsoon controller for this testclass/testcase."""
115
116        if monsoon_config is None:
117            raise MonsoonError('monsoon_config can not be None')
118
119        self._log.info('Setting up Monsoon %s' % self.monsoon.serial)
120        voltage = monsoon_config.get_numeric('voltage', 4.2)
121        self.monsoon.set_voltage_safe(voltage)
122        if 'max_current' in monsoon_config:
123            self.monsoon.set_max_current(
124                monsoon_config.get_numeric('max_current'))
125
126    def power_cycle(self, monsoon_config=None, **__):
127        """Power cycles the delegated monsoon controller."""
128
129        if monsoon_config is None:
130            raise MonsoonError('monsoon_config can not be None')
131
132        self._log.info('Setting up Monsoon %s' % self.monsoon.serial)
133        voltage = monsoon_config.get_numeric('voltage', 4.2)
134        self._log.info('Setting up Monsoon voltage %s' % voltage)
135        self.monsoon.set_voltage_safe(0)
136        if 'max_current' in monsoon_config:
137            self.monsoon.set_max_current(
138                monsoon_config.get_numeric('max_current'))
139            self.monsoon.set_max_initial_current(
140                monsoon_config.get_numeric('max_current'))
141        self.connect_usb()
142        self.monsoon.set_voltage_safe(voltage)
143
144    def connect_usb(self, **__):
145        self.monsoon.usb('on')
146
147    def measure(self, measurement_args=None, start_time=None,
148                monsoon_output_path=None, **__):
149        if measurement_args is None:
150            raise MonsoonError('measurement_args can not be None')
151
152        with tempfile.NamedTemporaryFile(prefix='monsoon_') as tmon:
153            self.monsoon.measure_power(**measurement_args,
154                                       output_path=tmon.name)
155
156            if monsoon_output_path and start_time is not None:
157                _write_raw_data_in_standard_format(
158                    power_metrics.import_raw_data(tmon.name),
159                    monsoon_output_path, start_time)
160
161    def release_resources(self, **__):
162        # nothing to do
163        pass
164
165    def disconnect_usb(self, **__):
166        self.monsoon.usb('off')
167
168    def get_waveform(self, file_path=None):
169        """Parses a file to obtain all current (in amps) samples.
170
171        Args:
172            file_path: Path to a monsoon file.
173
174        Returns:
175            A list of tuples in which the first element is a timestamp and the
176            second element is the sampled current at that time.
177        """
178        if file_path is None:
179            raise MonsoonError('file_path can not be None')
180
181        return list(power_metrics.import_raw_data(file_path))
182
183    def get_metrics(self, start_time=None, voltage=None, monsoon_file_path=None,
184                    timestamps=None, **__):
185        """Parses a monsoon_file_path to compute the consumed power and other
186        power related metrics.
187
188        Args:
189            start_time: Time when the measurement started, this is used to
190                correlate timestamps from the device and from the power samples.
191            voltage: Voltage used when the measurement started. Used to compute
192                power from current.
193            monsoon_file_path: Path to a monsoon file.
194            timestamps: Named timestamps delimiting the segments of interest.
195            **__:
196
197        Returns:
198            A list of power_metrics.Metric.
199        """
200        if start_time is None:
201            raise MonsoonError('start_time can not be None')
202        if voltage is None:
203            raise MonsoonError('voltage can not be None')
204        if monsoon_file_path is None:
205            raise MonsoonError('monsoon_file_path can not be None')
206        if timestamps is None:
207            raise MonsoonError('timestamps can not be None')
208
209        return power_metrics.generate_test_metrics(
210            power_metrics.import_raw_data(monsoon_file_path),
211            timestamps=timestamps, voltage=voltage)
212
213    def teardown(self, **__):
214        # nothing to do
215        pass
216