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 17import logging 18import time 19 20import os 21 22import acts_contrib.test_utils.power.PowerBaseTest as PBT 23 24from acts import base_test 25from acts.controllers import monsoon 26from bokeh.layouts import column, layout 27from bokeh.models import CustomJS, ColumnDataSource 28from bokeh.models import tools as bokeh_tools 29from bokeh.models.widgets import DataTable, TableColumn 30from bokeh.plotting import figure, output_file, save 31from acts.controllers.monsoon_lib.api.common import PassthroughStates 32from acts.controllers.monsoon_lib.api.common import MonsoonError 33 34LOGTIME_RETRY_COUNT = 3 35RESET_BATTERY_STATS = 'dumpsys batterystats --reset' 36RECOVER_MONSOON_RETRY_COUNT = 3 37MONSOON_RETRY_INTERVAL = 300 38 39class PowerGnssBaseTest(PBT.PowerBaseTest): 40 """ 41 Base Class for all GNSS Power related tests 42 """ 43 44 def setup_class(self): 45 super().setup_class() 46 req_params = ['customjsfile', 'maskfile', 'dpooff_nv_dict', 47 'dpoon_nv_dict', 'mdsapp', 'modemparfile'] 48 self.unpack_userparams(req_params) 49 50 def collect_power_data(self): 51 """Measure power and plot.""" 52 samples = super().collect_power_data() 53 plot_title = '{}_{}_{}_Power'.format(self.test_name, self.dut.model, 54 self.dut.build_info['build_id']) 55 self.monsoon_data_plot_power(samples, self.mon_voltage, 56 self.mon_info.data_path, plot_title) 57 return samples 58 59 def monsoon_data_plot_power(self, samples, voltage, dest_path, plot_title): 60 """Plot the monsoon power data using bokeh interactive plotting tool. 61 62 Args: 63 samples: a list of tuples in which the first element is a timestamp 64 and the second element is the sampled current at that time 65 voltage: the voltage that was used during the measurement 66 dest_path: destination path 67 plot_title: a filename and title for the plot. 68 69 """ 70 71 logging.info('Plotting the power measurement data.') 72 73 time_relative = [sample[0] for sample in samples] 74 duration = time_relative[-1] - time_relative[0] 75 current_data = [sample[1] * 1000 for sample in samples] 76 avg_current = sum(current_data) / len(current_data) 77 78 power_data = [current * voltage for current in current_data] 79 80 color = ['navy'] * len(samples) 81 82 # Preparing the data and source link for bokehn java callback 83 source = ColumnDataSource( 84 data=dict(x0=time_relative, y0=power_data, color=color)) 85 s2 = ColumnDataSource( 86 data=dict( 87 z0=[duration], 88 y0=[round(avg_current, 2)], 89 x0=[round(avg_current * voltage, 2)], 90 z1=[round(avg_current * voltage * duration, 2)], 91 z2=[round(avg_current * duration, 2)])) 92 # Setting up data table for the output 93 columns = [ 94 TableColumn(field='z0', title='Total Duration (s)'), 95 TableColumn(field='y0', title='Average Current (mA)'), 96 TableColumn(field='x0', title='Average Power (4.2v) (mW)'), 97 TableColumn(field='z1', title='Average Energy (mW*s)'), 98 TableColumn(field='z2', title='Normalized Average Energy (mA*s)') 99 ] 100 dt = DataTable( 101 source=s2, columns=columns, width=1300, height=60, editable=True) 102 103 output_file(os.path.join(dest_path, plot_title + '.html')) 104 tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save' 105 # Create a new plot with the datatable above 106 plot = figure( 107 plot_width=1300, 108 plot_height=700, 109 title=plot_title, 110 tools=tools, 111 output_backend='webgl') 112 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width')) 113 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height')) 114 plot.line('x0', 'y0', source=source, line_width=2) 115 plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color') 116 plot.xaxis.axis_label = 'Time (s)' 117 plot.yaxis.axis_label = 'Power (mW)' 118 plot.title.text_font_size = {'value': '15pt'} 119 jsscript = open(self.customjsfile, 'r') 120 customjsscript = jsscript.read() 121 # Callback Java scripting 122 source.callback = CustomJS( 123 args=dict(mytable=dt), 124 code=customjsscript) 125 126 # Layout the plot and the datatable bar 127 save(layout([[dt], [plot]])) 128 129 def disconnect_usb(self, ad, sleeptime): 130 """Disconnect usb while device is on sleep and 131 connect the usb again once the sleep time completes 132 133 sleeptime: sleep time where dut is disconnected from usb 134 """ 135 self.dut.adb.shell(RESET_BATTERY_STATS) 136 time.sleep(1) 137 for _ in range(LOGTIME_RETRY_COUNT): 138 self.monsoons[0].usb(PassthroughStates.OFF) 139 if not ad.is_connected(): 140 time.sleep(sleeptime) 141 self.monsoons[0].usb(PassthroughStates.ON) 142 break 143 else: 144 self.log.error('Test failed after maximum retry') 145 for _ in range(RECOVER_MONSOON_RETRY_COUNT): 146 if self.monsoon_recover(): 147 break 148 else: 149 self.log.warning( 150 'Wait for {} second then try again'.format( 151 MONSOON_RETRY_INTERVAL)) 152 time.sleep(MONSOON_RETRY_INTERVAL) 153 else: 154 self.log.error('Failed to recover monsoon') 155 156 def monsoon_recover(self): 157 """Test loop to wait for monsoon recover from unexpected error. 158 159 Wait for a certain time duration, then quit.0 160 Args: 161 mon: monsoon object 162 Returns: 163 True/False 164 """ 165 try: 166 self.power_monitor.connect_usb() 167 logging.info('Monsoon recovered from unexpected error') 168 time.sleep(2) 169 return True 170 except MonsoonError: 171 try: 172 self.log.info(self.monsoons[0]._mon.ser.in_waiting) 173 except AttributeError: 174 # This attribute does not exist for HVPMs. 175 pass 176 logging.warning('Unable to recover monsoon from unexpected error') 177 return False 178