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