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 acts.test_utils.bt.bt_test_utils as bt_utils 20import acts.test_utils.wifi.wifi_performance_test_utils as wifi_utils 21 22from acts import asserts 23from acts.test_utils.bt.BtSarBaseTest import BtSarBaseTest 24 25 26class BtSarPowerLimitTest(BtSarBaseTest): 27 """Class to define BT SAR power cap tests. 28 29 This class defines tests that iterate over and force different 30 states in the BT SAR table and calculates the TX power at the 31 antenna port. 32 """ 33 def setup_test(self): 34 super().setup_test() 35 36 # BokehFigure object 37 self.plot = wifi_utils.BokehFigure(title='{}'.format( 38 self.current_test_name), 39 x_label='Scenarios', 40 primary_y_label='TX power(dBm)') 41 42 def save_sar_plot(self, df, results_file_path): 43 """ Saves SAR plot to the path given. 44 45 Args: 46 df: Processed SAR table sweep results 47 results_file_path: path where the bokeh figure is to saved 48 """ 49 self.plot.add_line(df.index, 50 df['expected_tx_power'], 51 'expected', 52 marker='circle') 53 self.plot.add_line(df.index, 54 df['measured_tx_power'], 55 'measured', 56 marker='circle') 57 58 results_file_path = os.path.join( 59 self.log_path, '{}.html'.format(self.current_test_name)) 60 self.plot.generate_figure() 61 wifi_utils.BokehFigure.save_figures([self.plot], results_file_path) 62 63 def sweep_table(self, sort_order=''): 64 """Iterates over the BT SAR table and forces signal states. 65 66 Iterates over BT SAR table and forces signal states, 67 measuring RSSI and power level for each state. 68 69 Args: 70 sort_order: decides the sort order of the BT SAR Table. 71 Returns: 72 sar_df : SAR table sweep results in pandas dataframe 73 """ 74 75 sar_df = self.bt_sar_df.copy() 76 sar_df['power_cap'] = -128 77 sar_df['slave_rssi'] = -128 78 sar_df['master_rssi'] = -128 79 sar_df['pwlv'] = -1 80 81 # Sorts the table 82 if sort_order: 83 if sort_order.lower() == 'ascending': 84 sar_df = sar_df.sort_values(by=['BluetoothPower'], 85 ascending=True) 86 else: 87 sar_df = sar_df.sort_values(by=['BluetoothPower'], 88 ascending=False) 89 # Sweeping BT SAR table 90 for scenario in range(sar_df.shape[0]): 91 # Reading BT SAR Scenario from the table 92 read_scenario = sar_df.loc[scenario].to_dict() 93 94 start_time = self.dut.adb.shell('date +%s.%m') 95 time.sleep(1) 96 self.set_sar_state(self.dut, read_scenario) 97 98 # Appending BT metrics to the table 99 sar_df.loc[scenario, 'power_cap'] = self.get_current_power_cap( 100 self.dut, start_time) 101 processed_bqr_results = bt_utils.get_bt_metric( 102 self.android_devices, self.duration) 103 sar_df.loc[scenario, 'slave_rssi'] = processed_bqr_results['rssi'][ 104 self.bt_device_controller.serial] 105 sar_df.loc[scenario, 'master_rssi'] = processed_bqr_results[ 106 'rssi'][self.dut.serial] 107 sar_df.loc[scenario, 'pwlv'] = processed_bqr_results['pwlv'][ 108 self.dut.serial] 109 self.log.info( 110 'scenario:{}, power_cap:{}, s_rssi:{}, m_rssi:{}, m_pwlv:{}'. 111 format(scenario, sar_df.loc[scenario, 'power_cap'], 112 sar_df.loc[scenario, 'slave_rssi'], 113 sar_df.loc[scenario, 'master_rssi'], 114 sar_df.loc[scenario, 'pwlv'])) 115 self.log.info('BT SAR Table swept') 116 117 return sar_df 118 119 def process_table(self, sar_df): 120 """Processes the results of sweep_table and computes BT TX power. 121 122 Processes the results of sweep_table and computes BT TX power 123 after factoring in the path loss and FTM offsets. 124 125 Args: 126 sar_df: BT SAR table after the sweep 127 128 Returns: 129 sar_df: processed BT SAR table 130 """ 131 132 pathloss = self.calibration_params['pathloss'] 133 134 # Adding a target power column 135 sar_df['target_power'] = sar_df['pwlv'].astype(str).map( 136 self.calibration_params['target_power']) 137 138 # Adding a ftm power column 139 sar_df['ftm_power'] = sar_df['pwlv'].astype(str).map( 140 self.calibration_params['ftm_power']) 141 142 # BT SAR Backoff for each scenario 143 sar_df['backoff'] = sar_df['target_power'] - sar_df['power_cap'] / 4 144 145 sar_df['measured_tx_power'] = sar_df['slave_rssi'] + pathloss 146 sar_df['expected_tx_power'] = sar_df['ftm_power'] - sar_df['backoff'] 147 148 sar_df['delta'] = sar_df['expected_tx_power'] - sar_df[ 149 'measured_tx_power'] 150 self.log.info('Sweep results processed') 151 152 results_file_path = os.path.join(self.log_path, self.current_test_name) 153 sar_df.to_csv('{}.csv'.format(results_file_path)) 154 self.save_sar_plot(sar_df, results_file_path) 155 156 return sar_df 157 158 def process_results(self, sar_df): 159 """Determines the test results of the sweep. 160 161 Parses the processed table with computed BT TX power values 162 to return pass or fail. 163 164 Args: 165 sar_df: processed BT SAR table 166 """ 167 168 # checks for errors at particular points in the sweep 169 max_error_result = abs(sar_df['delta']) > self.max_error_threshold 170 if False in max_error_result: 171 asserts.fail('Maximum Error Threshold Exceeded') 172 173 # checks for error accumulation across the sweep 174 if sar_df['delta'].sum() > self.agg_error_threshold: 175 asserts.fail( 176 'Aggregate Error Threshold Exceeded. Error: {} Threshold: {}'. 177 format(sar_df['delta'].sum(), self.agg_error_threshold)) 178 179 else: 180 asserts.explicit_pass('Measured and Expected Power Values in line') 181 182 def test_bt_sar_table(self): 183 sar_df = self.sweep_table() 184 sar_df = self.process_table(sar_df) 185 self.process_results(sar_df) 186 187 def test_bt_sar_custom_table(self): 188 # Pushes the custom file 189 backup_sar_path = os.path.join(self.dut.device_log_path, 190 self.BACKUP_BT_SAR_TABLE_NAME) 191 self.push_table(self.dut, self.custom_sar_path, backup_sar_path) 192 193 # Connect master and slave 194 bt_utils.connect_phone_to_headset(self.dut, self.bt_device, 60) 195 196 sar_df = self.sweep_table() 197 sar_df = self.process_table(sar_df) 198 199 # Pushing the backup table back 200 self.push_table(self.dut, backup_sar_path) 201 202 self.process_results(sar_df) 203