#!/usr/bin/env python3 # # Copyright 2019 - The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import time import os import acts_contrib.test_utils.power.PowerBaseTest as PBT from acts import base_test from acts.controllers import monsoon from bokeh.layouts import column, layout from bokeh.models import CustomJS, ColumnDataSource from bokeh.models import tools as bokeh_tools from bokeh.models.widgets import DataTable, TableColumn from bokeh.plotting import figure, output_file, save from acts.controllers.monsoon_lib.api.common import PassthroughStates from acts.controllers.monsoon_lib.api.common import MonsoonError LOGTIME_RETRY_COUNT = 3 RESET_BATTERY_STATS = 'dumpsys batterystats --reset' RECOVER_MONSOON_RETRY_COUNT = 3 MONSOON_RETRY_INTERVAL = 300 class PowerGnssBaseTest(PBT.PowerBaseTest): """ Base Class for all GNSS Power related tests """ def setup_class(self): super().setup_class() req_params = ['customjsfile', 'maskfile', 'dpooff_nv_dict', 'dpoon_nv_dict', 'mdsapp', 'modemparfile'] self.unpack_userparams(req_params) def collect_power_data(self): """Measure power and plot.""" samples = super().collect_power_data() plot_title = '{}_{}_{}_Power'.format(self.test_name, self.dut.model, self.dut.build_info['build_id']) self.monsoon_data_plot_power(samples, self.mon_voltage, self.mon_info.data_path, plot_title) return samples def monsoon_data_plot_power(self, samples, voltage, dest_path, plot_title): """Plot the monsoon power data using bokeh interactive plotting tool. Args: samples: a list of tuples in which the first element is a timestamp and the second element is the sampled current at that time voltage: the voltage that was used during the measurement dest_path: destination path plot_title: a filename and title for the plot. """ logging.info('Plotting the power measurement data.') time_relative = [sample[0] for sample in samples] duration = time_relative[-1] - time_relative[0] current_data = [sample[1] * 1000 for sample in samples] avg_current = sum(current_data) / len(current_data) power_data = [current * voltage for current in current_data] color = ['navy'] * len(samples) # Preparing the data and source link for bokehn java callback source = ColumnDataSource( data=dict(x0=time_relative, y0=power_data, color=color)) s2 = ColumnDataSource( data=dict( z0=[duration], y0=[round(avg_current, 2)], x0=[round(avg_current * voltage, 2)], z1=[round(avg_current * voltage * duration, 2)], z2=[round(avg_current * duration, 2)])) # Setting up data table for the output columns = [ TableColumn(field='z0', title='Total Duration (s)'), TableColumn(field='y0', title='Average Current (mA)'), TableColumn(field='x0', title='Average Power (4.2v) (mW)'), TableColumn(field='z1', title='Average Energy (mW*s)'), TableColumn(field='z2', title='Normalized Average Energy (mA*s)') ] dt = DataTable( source=s2, columns=columns, width=1300, height=60, editable=True) output_file(os.path.join(dest_path, plot_title + '.html')) tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save' # Create a new plot with the datatable above plot = figure( plot_width=1300, plot_height=700, title=plot_title, tools=tools, output_backend='webgl') plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width')) plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height')) plot.line('x0', 'y0', source=source, line_width=2) plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color') plot.xaxis.axis_label = 'Time (s)' plot.yaxis.axis_label = 'Power (mW)' plot.title.text_font_size = {'value': '15pt'} jsscript = open(self.customjsfile, 'r') customjsscript = jsscript.read() # Callback Java scripting source.callback = CustomJS( args=dict(mytable=dt), code=customjsscript) # Layout the plot and the datatable bar save(layout([[dt], [plot]])) def disconnect_usb(self, ad, sleeptime): """Disconnect usb while device is on sleep and connect the usb again once the sleep time completes sleeptime: sleep time where dut is disconnected from usb """ self.dut.adb.shell(RESET_BATTERY_STATS) time.sleep(1) for _ in range(LOGTIME_RETRY_COUNT): self.monsoons[0].usb(PassthroughStates.OFF) if not ad.is_connected(): time.sleep(sleeptime) self.monsoons[0].usb(PassthroughStates.ON) break else: self.log.error('Test failed after maximum retry') for _ in range(RECOVER_MONSOON_RETRY_COUNT): if self.monsoon_recover(): break else: self.log.warning( 'Wait for {} second then try again'.format( MONSOON_RETRY_INTERVAL)) time.sleep(MONSOON_RETRY_INTERVAL) else: self.log.error('Failed to recover monsoon') def monsoon_recover(self): """Test loop to wait for monsoon recover from unexpected error. Wait for a certain time duration, then quit.0 Args: mon: monsoon object Returns: True/False """ try: self.power_monitor.connect_usb() logging.info('Monsoon recovered from unexpected error') time.sleep(2) return True except MonsoonError: try: self.log.info(self.monsoons[0]._mon.ser.in_waiting) except AttributeError: # This attribute does not exist for HVPMs. pass logging.warning('Unable to recover monsoon from unexpected error') return False