• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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