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 os
18import time
19import numpy as np
20import acts.test_utils.bt.bt_test_utils as bt_utils
21import acts.test_utils.wifi.wifi_performance_test_utils as wifi_utils
22
23from acts import asserts
24from functools import partial
25from acts.test_utils.bt.BtSarBaseTest import BtSarBaseTest
26
27
28class BtSarTpcTest(BtSarBaseTest):
29    """ Class to define BT SAR Transmit power control tests in
30
31    This class defines tests to detect any anomalies in the
32    Transmit power control mechanisms
33    """
34    def __init__(self, controllers):
35        super().__init__(controllers)
36        req_params = ['scenario_count']
37        self.unpack_userparams(req_params)
38        self.tests = self.generate_test_cases()
39
40    def setup_class(self):
41        super().setup_class()
42
43        # Pushing custom table
44        self.backup_sar_path = os.path.join(self.dut.device_log_path,
45                                            self.BACKUP_BT_SAR_TABLE_NAME)
46        self.dut.adb.pull(self.sar_file_path, self.backup_sar_path)
47        self.push_table(self.dut, self.custom_sar_path)
48
49        self.attenuator.set_atten(self.atten_min)
50        self.pathloss = int(self.calibration_params['pathloss'])
51
52        self.sar_df = self.bt_sar_df.copy()
53        self.sar_df['power_cap'] = -128
54        self.sar_df['TPC_result'] = -1
55
56    def setup_test(self):
57        super().setup_test()
58
59        self.tpc_sweep_range = range(self.atten_min, self.pl10_atten)
60
61        self.tpc_plots_figure = wifi_utils.BokehFigure(
62            title='{}_{}'.format(self.current_test_name, 'curve'),
63            x_label='Pathloss(dBm)',
64            primary_y_label='Tx Power(dBm)')
65
66        self.tpc_plots_derivative_figure = wifi_utils.BokehFigure(
67            title='{}_{}'.format(self.current_test_name, 'curve_derivative'),
68            x_label='Pathloss(dBm)',
69            primary_y_label='Tx Power(dB)')
70
71    def teardown_class(self):
72        super().teardown_class()
73        result_file_name = '{}.csv'.format(self.__class__.__name__)
74        result_file_path = os.path.join(self.log_path, result_file_name)
75        self.sar_df.to_csv(result_file_path)
76
77        # Pushing default table back
78        self.push_table(self.dut, self.backup_sar_path)
79
80    def generate_test_cases(self):
81        """Function to generate test cases.
82        Function to generate a test case per BT SAR table row.
83
84        Returns: list of generated test cases.
85        """
86        test_cases = []
87        for scenario in range(int(self.scenario_count)):
88            test_name = 'test_bt_sar_tpc_{}'.format(scenario)
89            setattr(self, test_name, partial(self._test_bt_sar_tpc, scenario))
90            test_cases.append(test_name)
91        return test_cases
92
93    def process_tpc_results(self, tx_power_list, pwlv_list):
94        """Processes the results of tpc sweep.
95
96        Processes tpc sweep to ensure that tx power changes
97        as expected while sweeping.
98
99        Args:
100            tx_power_list: list of tx power measured during the TPC sweep.
101            pwlv_list: list of power levels observed during the TPC sweep.
102
103        Returns:
104            tpc_verdict : result of the tpc sweep; PASS/FAIL.
105            tx_power_derivative : peaks observed during TPC sweep.
106        """
107
108        tpc_verdict = 'FAIL'
109
110        # Locating power level changes in the sweep
111        pwlv_derivative_bool = list(np.diff([pwlv_list[0]] + pwlv_list) == 1)
112
113        # Diff-ing the list to get derivative of the list
114        tx_power_derivative = np.diff([tx_power_list[0]] + tx_power_list)
115
116        # Checking for negative peaks
117        if tx_power_derivative.min() < self.tpc_threshold['negative']:
118            return [tpc_verdict, tx_power_derivative]
119
120        # Locating legitimate tx power changes
121        tx_power_derivative_bool = [
122            self.tpc_threshold['positive'][0] < x <
123            self.tpc_threshold['positive'][1] for x in tx_power_derivative
124        ]
125
126        # Ensuring that changes in power level correspond to tx power changes
127        if pwlv_derivative_bool == tx_power_derivative_bool:
128            tpc_verdict = 'PASS'
129            return [tpc_verdict, tx_power_derivative]
130
131        return [tpc_verdict, tx_power_derivative]
132
133    def _test_bt_sar_tpc(self, scenario):
134        """Performs TCP sweep for the given scenario.
135
136        Function performs and evaluates TPC sweep for a given scenario.
137
138        Args:
139            scenario: row of the BT SAR table.
140        """
141
142        master_tx_power_list = []
143        pwlv_list = []
144
145        # Reading BT SAR Scenario from the table
146        start_time = self.dut.adb.shell('date +%s.%m')
147        time.sleep(1)
148
149        # Forcing the SAR state
150        read_scenario = self.sar_df.loc[scenario].to_dict()
151        self.set_sar_state(self.dut, read_scenario)
152
153        # Reading power cap
154        self.sar_df.loc[scenario, 'power_cap'] = self.get_current_power_cap(
155            self.dut, start_time)
156
157        # TPC sweep
158        for atten in self.tpc_sweep_range:
159
160            self.attenuator.set_atten(atten)
161            self.log.info('Current TPC attenuation {} dB; scenario {}'.format(
162                atten, scenario))
163
164            processed_bqr = bt_utils.get_bt_metric(self.android_devices,
165                                                   self.duration)
166            # Recording master rssi and pwlv
167            master_tx_power_list.append(
168                processed_bqr['rssi'][self.bt_device_controller.serial] +
169                self.pathloss + atten)
170            pwlv_list.append(processed_bqr['pwlv'][self.dut.serial])
171
172        # Processing tpc sweep results
173        [self.sar_df.loc[scenario, 'TPC_result'], tx_power_derivative
174         ] = self.process_tpc_results(master_tx_power_list, pwlv_list)
175
176        # Plot TPC curves
177        self.tpc_plots_figure.add_line(self.tpc_sweep_range,
178                                       master_tx_power_list,
179                                       str(scenario),
180                                       marker='circle')
181
182        self.tpc_plots_derivative_figure.add_line(self.tpc_sweep_range,
183                                                  tx_power_derivative,
184                                                  str(scenario),
185                                                  marker='circle')
186
187        self.tpc_plots_figure.generate_figure()
188        self.tpc_plots_derivative_figure.generate_figure()
189
190        # Saving the TPC curves
191        plot_file_path = os.path.join(self.log_path,
192                                      '{}.html'.format(self.current_test_name))
193        wifi_utils.BokehFigure.save_figures(
194            [self.tpc_plots_figure, self.tpc_plots_derivative_figure],
195            plot_file_path)
196
197        # Asserting pass and fail
198        if self.sar_df.loc[scenario, 'TPC_result'] == 'FAIL':
199            asserts.fail('TPC sweep failed for scenario: {}'.format(scenario))
200
201        else:
202            asserts.explicit_pass(
203                'TPC sweep passed for scenario: {}'.format(scenario))
204