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 re 19import time 20import logging 21import pandas as pd 22 23from acts import asserts 24from acts.libs.proc import job 25from acts.base_test import BaseTestClass 26 27from acts.metrics.loggers.blackbox import BlackboxMetricLogger 28from acts_contrib.test_utils.bt.bt_power_test_utils import MediaControl 29from acts_contrib.test_utils.bt.ble_performance_test_utils import run_ble_throughput_and_read_rssi 30from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory 31 32import acts_contrib.test_utils.bt.bt_test_utils as bt_utils 33import acts_contrib.test_utils.wifi.wifi_performance_test_utils as wifi_utils 34 35PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music' 36 37FORCE_SAR_ADB_COMMAND = ('am broadcast -n' 38 'com.google.android.apps.scone/.coex.TestReceiver -a ' 39 'com.google.android.apps.scone.coex.SIMULATE_STATE ') 40 41SLEEP_DURATION = 2 42 43DEFAULT_DURATION = 5 44DEFAULT_MAX_ERROR_THRESHOLD = 2 45DEFAULT_AGG_MAX_ERROR_THRESHOLD = 2 46FIXED_ATTENUATION = 36 47 48 49class BtSarBaseTest(BaseTestClass): 50 """ Base class for all BT SAR Test classes. 51 52 This class implements functions common to BT SAR test Classes. 53 """ 54 BACKUP_BT_SAR_TABLE_NAME = 'backup_bt_sar_table.csv' 55 56 def __init__(self, controllers): 57 BaseTestClass.__init__(self, controllers) 58 self.power_file_paths = [ 59 '/vendor/etc/bluetooth_power_limits.csv', 60 '/data/vendor/radio/bluetooth_power_limits.csv' 61 ] 62 self.sar_test_result = BlackboxMetricLogger.for_test_case( 63 metric_name='pass') 64 self.sar_file_name = os.path.basename(self.power_file_paths[0]) 65 self.power_column = 'BluetoothPower' 66 self.REG_DOMAIN_DICT = { 67 ('us', 'ca', 'in'): 'US', 68 ('uk', 'fr', 'es', 'de', 'it', 'ie', 'sg', 'au', 'tw'): 'EU', 69 ('jp', ): 'JP' 70 } 71 72 def setup_class(self): 73 """Initializes common test hardware and parameters. 74 75 This function initializes hardware and compiles parameters that are 76 common to all tests in this class and derived classes. 77 """ 78 super().setup_class() 79 80 self.test_params = self.user_params.get('bt_sar_test_params', {}) 81 if not self.test_params: 82 self.log.warning( 83 'bt_sar_test_params was not found in the config file.') 84 85 self.user_params.update(self.test_params) 86 req_params = ['bt_devices', 'calibration_params', 'custom_files'] 87 88 self.unpack_userparams( 89 req_params, 90 country_code='us', 91 duration=DEFAULT_DURATION, 92 sort_order=None, 93 max_error_threshold=DEFAULT_MAX_ERROR_THRESHOLD, 94 agg_error_threshold=DEFAULT_AGG_MAX_ERROR_THRESHOLD, 95 tpc_threshold=[2, 8], 96 sar_margin={ 97 'BDR': 0, 98 'EDR': 0, 99 'BLE': 0 100 }) 101 102 self.attenuator = self.attenuators[0] 103 self.dut = self.android_devices[0] 104 105 for key in self.REG_DOMAIN_DICT.keys(): 106 if self.country_code.lower() in key: 107 self.reg_domain = self.REG_DOMAIN_DICT[key] 108 109 self.sar_version_2 = False 110 111 if 'Error' not in self.dut.adb.shell('bluetooth_sar_test -r'): 112 #Flag for SAR version 2 113 self.sar_version_2 = True 114 self.power_column = 'BluetoothEDRPower' 115 self.power_file_paths[0] = os.path.join( 116 os.path.dirname(self.power_file_paths[0]), 117 'bluetooth_power_limits_{}.csv'.format(self.reg_domain)) 118 self.sar_file_name = os.path.basename(self.power_file_paths[0]) 119 120 if self.sar_version_2: 121 custom_file_suffix = 'version2' 122 else: 123 custom_file_suffix = 'version1' 124 125 for file in self.custom_files: 126 if 'custom_sar_table_{}.csv'.format(custom_file_suffix) in file: 127 self.custom_sar_path = file 128 break 129 else: 130 raise RuntimeError('Custom Sar File is missing') 131 132 self.sar_file_path = self.power_file_paths[0] 133 self.atten_min = 0 134 self.atten_max = int(self.attenuator.get_max_atten()) 135 136 # Get music file and push it to the phone and initialize Media controller 137 music_files = self.user_params.get('music_files', []) 138 if music_files: 139 music_src = music_files[0] 140 music_dest = PHONE_MUSIC_FILE_DIRECTORY 141 success = self.dut.push_system_file(music_src, music_dest) 142 if success: 143 self.music_file = os.path.join(PHONE_MUSIC_FILE_DIRECTORY, 144 os.path.basename(music_src)) 145 # Initialize media_control class 146 self.media = MediaControl(self.dut, self.music_file) 147 148 #Initializing BT device controller 149 if self.bt_devices: 150 attr, idx = self.bt_devices.split(':') 151 self.bt_device_controller = getattr(self, attr)[int(idx)] 152 self.bt_device = bt_factory().generate(self.bt_device_controller) 153 else: 154 self.log.error('No BT devices config is provided!') 155 156 bt_utils.enable_bqr(self.android_devices) 157 158 self.log_path = os.path.join(logging.log_path, 'results') 159 os.makedirs(self.log_path, exist_ok=True) 160 161 # Reading BT SAR table from the phone 162 self.bt_sar_df = self.read_sar_table(self.dut) 163 164 def setup_test(self): 165 super().setup_test() 166 167 #Reset SAR test result to 0 before every test 168 self.sar_test_result.metric_value = 0 169 170 # Starting BT on the master 171 self.dut.droid.bluetoothFactoryReset() 172 bt_utils.enable_bluetooth(self.dut.droid, self.dut.ed) 173 174 # Starting BT on the slave 175 self.bt_device.reset() 176 self.bt_device.power_on() 177 178 # Connect master and slave 179 bt_utils.connect_phone_to_headset(self.dut, self.bt_device, 60) 180 181 # Playing music 182 self.media.play() 183 184 # Find and set PL10 level for the DUT 185 self.pl10_atten = self.set_PL10_atten_level(self.dut) 186 self.attenuator.set_atten(self.pl10_atten) 187 188 def teardown_test(self): 189 #Stopping Music 190 if hasattr(self, 'media'): 191 self.media.stop() 192 193 # Stopping BT on slave 194 self.bt_device.reset() 195 self.bt_device.power_off() 196 197 #Stopping BT on master 198 bt_utils.disable_bluetooth(self.dut.droid) 199 200 #Resetting the atten to initial levels 201 self.attenuator.set_atten(self.atten_min) 202 self.log.info('Attenuation set to {} dB'.format(self.atten_min)) 203 204 def teardown_class(self): 205 206 super().teardown_class() 207 self.dut.droid.bluetoothFactoryReset() 208 209 # Stopping BT on slave 210 self.bt_device.reset() 211 self.bt_device.power_off() 212 213 #Stopping BT on master 214 bt_utils.disable_bluetooth(self.dut.droid) 215 216 def save_sar_plot(self, df): 217 """ Saves SAR plot to the path given. 218 219 Args: 220 df: Processed SAR table sweep results 221 """ 222 self.plot.add_line( 223 df.index, 224 df['expected_tx_power'], 225 legend='expected', 226 marker='circle') 227 self.plot.add_line( 228 df.index, 229 df['measured_tx_power'], 230 legend='measured', 231 marker='circle') 232 self.plot.add_line( 233 df.index, df['delta'], legend='delta', marker='circle') 234 235 results_file_path = os.path.join(self.log_path, '{}.html'.format( 236 self.current_test_name)) 237 self.plot.generate_figure() 238 wifi_utils.BokehFigure.save_figures([self.plot], results_file_path) 239 240 def sweep_power_cap(self): 241 sar_df = self.bt_sar_df 242 sar_df['BDR_power_cap'] = -128 243 sar_df['EDR_power_cap'] = -128 244 sar_df['BLE_power_cap'] = -128 245 246 if self.sar_version_2: 247 power_column_dict = { 248 'BDR': 'BluetoothBDRPower', 249 'EDR': 'BluetoothEDRPower', 250 'BLE': 'BluetoothLEPower' 251 } 252 else: 253 power_column_dict = {'EDR': self.power_column} 254 255 power_cap_error = False 256 257 for type, column_name in power_column_dict.items(): 258 259 self.log.info("Performing sanity test on {}".format(type)) 260 # Iterating through the BT SAR scenarios 261 for scenario in range(0, self.bt_sar_df.shape[0]): 262 # Reading BT SAR table row into dict 263 read_scenario = sar_df.loc[scenario].to_dict() 264 start_time = self.dut.adb.shell('date +%s.%m') 265 time.sleep(SLEEP_DURATION) 266 267 # Setting SAR state to the read BT SAR row 268 self.set_sar_state(self.dut, read_scenario, self.country_code) 269 270 # Reading device power cap from logcat after forcing SAR State 271 scenario_power_cap = self.get_current_power_cap( 272 self.dut, start_time, type=type) 273 sar_df.loc[scenario, '{}_power_cap'.format( 274 type)] = scenario_power_cap 275 self.log.info( 276 'scenario: {}, ' 277 'sar_power: {}, power_cap:{}'.format( 278 scenario, sar_df.loc[scenario, column_name], 279 sar_df.loc[scenario, '{}_power_cap'.format(type)])) 280 281 if not sar_df['{}_power_cap'.format(type)].equals(sar_df[column_name]): 282 power_cap_error = True 283 284 results_file_path = os.path.join(self.log_path, '{}.csv'.format( 285 self.current_test_name)) 286 sar_df.to_csv(results_file_path) 287 288 return power_cap_error 289 290 def sweep_table(self, 291 client_ad=None, 292 server_ad=None, 293 client_conn_id=None, 294 gatt_server=None, 295 gatt_callback=None, 296 isBLE=False): 297 """Iterates over the BT SAR table and forces signal states. 298 299 Iterates over BT SAR table and forces signal states, 300 measuring RSSI and power level for each state. 301 302 Args: 303 client_ad: the Android device performing the connection. 304 server_ad: the Android device accepting the connection. 305 client_conn_id: the client connection ID. 306 gatt_server: the gatt server 307 gatt_callback: Gatt callback objec 308 isBLE : boolean variable for BLE connection 309 Returns: 310 sar_df : SAR table sweep results in pandas dataframe 311 """ 312 313 sar_df = self.bt_sar_df.copy() 314 sar_df['power_cap'] = -128 315 sar_df['slave_rssi'] = -128 316 sar_df['master_rssi'] = -128 317 sar_df['ble_rssi'] = -128 318 sar_df['pwlv'] = -1 319 320 # Sorts the table 321 if self.sort_order: 322 if self.sort_order.lower() == 'ascending': 323 sar_df = sar_df.sort_values( 324 by=[self.power_column], ascending=True) 325 else: 326 sar_df = sar_df.sort_values( 327 by=[self.power_column], ascending=False) 328 sar_df = sar_df.reset_index(drop=True) 329 330 # Sweeping BT SAR table 331 for scenario in range(sar_df.shape[0]): 332 # Reading BT SAR Scenario from the table 333 read_scenario = sar_df.loc[scenario].to_dict() 334 335 start_time = self.dut.adb.shell('date +%s.%m') 336 time.sleep(SLEEP_DURATION) 337 338 #Setting SAR State 339 self.set_sar_state(self.dut, read_scenario, self.country_code) 340 341 if isBLE: 342 sar_df.loc[scenario, 'power_cap'] = self.get_current_power_cap( 343 self.dut, start_time, type='BLE') 344 345 sar_df.loc[ 346 scenario, 'ble_rssi'] = run_ble_throughput_and_read_rssi( 347 client_ad, server_ad, client_conn_id, gatt_server, 348 gatt_callback) 349 350 self.log.info('scenario:{}, power_cap:{}, ble_rssi:{}'.format( 351 scenario, sar_df.loc[scenario, 'power_cap'], 352 sar_df.loc[scenario, 'ble_rssi'])) 353 else: 354 sar_df.loc[scenario, 'power_cap'] = self.get_current_power_cap( 355 self.dut, start_time) 356 357 processed_bqr_results = bt_utils.get_bt_metric( 358 self.android_devices, self.duration) 359 sar_df.loc[scenario, 'slave_rssi'] = processed_bqr_results[ 360 'rssi'][self.bt_device_controller.serial] 361 sar_df.loc[scenario, 'master_rssi'] = processed_bqr_results[ 362 'rssi'][self.dut.serial] 363 sar_df.loc[scenario, 'pwlv'] = processed_bqr_results['pwlv'][ 364 self.dut.serial] 365 self.log.info( 366 'scenario:{}, power_cap:{}, s_rssi:{}, m_rssi:{}, m_pwlv:{}' 367 .format(scenario, sar_df.loc[scenario, 'power_cap'], 368 sar_df.loc[scenario, 'slave_rssi'], 369 sar_df.loc[scenario, 'master_rssi'], 370 sar_df.loc[scenario, 'pwlv'])) 371 372 self.log.info('BT SAR Table swept') 373 374 return sar_df 375 376 def process_table(self, sar_df): 377 """Processes the results of sweep_table and computes BT TX power. 378 379 Processes the results of sweep_table and computes BT TX power 380 after factoring in the path loss and FTM offsets. 381 382 Args: 383 sar_df: BT SAR table after the sweep 384 385 Returns: 386 sar_df: processed BT SAR table 387 """ 388 389 sar_df['pathloss'] = self.calibration_params['pathloss'] 390 391 if hasattr(self, 'pl10_atten'): 392 sar_df['atten'] = self.pl10_atten 393 else: 394 sar_df['atten'] = FIXED_ATTENUATION 395 396 # BT SAR Backoff for each scenario 397 if self.sar_version_2: 398 #Reads OTP values from the phone 399 self.otp = bt_utils.read_otp(self.dut) 400 401 #OTP backoff 402 edr_otp = min(0, float(self.otp['EDR']['10'])) 403 bdr_otp = min(0, float(self.otp['BR']['10'])) 404 ble_otp = min(0, float(self.otp['BLE']['10'])) 405 406 # EDR TX Power for PL10 407 edr_tx_power_pl10 = self.calibration_params['target_power']['EDR']['10'] - edr_otp 408 409 # BDR TX Power for PL10 410 bdr_tx_power_pl10 = self.calibration_params['target_power']['BDR']['10'] - bdr_otp 411 412 # RSSI being measured is BDR 413 offset = bdr_tx_power_pl10 - edr_tx_power_pl10 414 415 # BDR-EDR offset 416 sar_df['offset'] = offset 417 418 # Max TX power permissible 419 sar_df['max_power'] = self.calibration_params['max_power'] 420 421 # Adding a target power column 422 if 'ble_rssi' in sar_df.columns: 423 sar_df[ 424 'target_power'] = self.calibration_params['target_power']['BLE']['10'] - ble_otp 425 else: 426 sar_df['target_power'] = sar_df['pwlv'].astype(str).map( 427 self.calibration_params['target_power']['EDR']) - edr_otp 428 429 #Translates power_cap values to expected TX power level 430 sar_df['cap_tx_power'] = sar_df['power_cap'] / 4.0 431 432 sar_df['expected_tx_power'] = sar_df[[ 433 'cap_tx_power', 'target_power', 'max_power' 434 ]].min(axis=1) 435 436 if hasattr(self, 'pl10_atten'): 437 sar_df[ 438 'measured_tx_power'] = sar_df['slave_rssi'] + sar_df['pathloss'] + self.pl10_atten - offset 439 else: 440 sar_df[ 441 'measured_tx_power'] = sar_df['ble_rssi'] + sar_df['pathloss'] + FIXED_ATTENUATION 442 443 else: 444 445 # Adding a target power column 446 sar_df['target_power'] = sar_df['pwlv'].astype(str).map( 447 self.calibration_params['target_power']['EDR']['10']) 448 449 # Adding a ftm power column 450 sar_df['ftm_power'] = sar_df['pwlv'].astype(str).map( 451 self.calibration_params['ftm_power']['EDR']) 452 sar_df[ 453 'backoff'] = sar_df['target_power'] - sar_df['power_cap'] / 4.0 454 455 sar_df[ 456 'expected_tx_power'] = sar_df['ftm_power'] - sar_df['backoff'] 457 sar_df[ 458 'measured_tx_power'] = sar_df['slave_rssi'] + sar_df['pathloss'] + self.pl10_atten 459 460 sar_df[ 461 'delta'] = sar_df['expected_tx_power'] - sar_df['measured_tx_power'] 462 463 self.log.info('Sweep results processed') 464 465 results_file_path = os.path.join(self.log_path, self.current_test_name) 466 sar_df.to_csv('{}.csv'.format(results_file_path)) 467 self.save_sar_plot(sar_df) 468 469 return sar_df 470 471 def process_results(self, sar_df, type='EDR'): 472 """Determines the test results of the sweep. 473 474 Parses the processed table with computed BT TX power values 475 to return pass or fail. 476 477 Args: 478 sar_df: processed BT SAR table 479 """ 480 if self.sar_version_2: 481 breach_error_result = ( 482 sar_df['expected_tx_power'] + self.sar_margin[type] > 483 sar_df['measured_tx_power']).all() 484 if not breach_error_result: 485 asserts.fail('Measured TX power exceeds expected') 486 487 else: 488 # checks for errors at particular points in the sweep 489 max_error_result = abs( 490 sar_df['delta']) > self.max_error_threshold[type] 491 if max_error_result: 492 asserts.fail('Maximum Error Threshold Exceeded') 493 494 # checks for error accumulation across the sweep 495 if sar_df['delta'].sum() > self.agg_error_threshold[type]: 496 asserts.fail( 497 'Aggregate Error Threshold Exceeded. Error: {} Threshold: {}'. 498 format(sar_df['delta'].sum(), self.agg_error_threshold)) 499 500 self.sar_test_result.metric_value = 1 501 asserts.explicit_pass('Measured and Expected Power Values in line') 502 503 def set_sar_state(self, ad, signal_dict, country_code='us'): 504 """Sets the SAR state corresponding to the BT SAR signal. 505 506 The SAR state is forced using an adb command that takes 507 device signals as input. 508 509 Args: 510 ad: android_device object. 511 signal_dict: dict of BT SAR signals read from the SAR file. 512 Returns: 513 enforced_state: dict of device signals. 514 """ 515 signal_dict = {k: max(int(v), 0) for (k, v) in signal_dict.items()} 516 signal_dict["Wifi"] = signal_dict['WIFI5Ghz'] 517 signal_dict['WIFI2Ghz'] = 0 if signal_dict['WIFI5Ghz'] else 1 518 519 device_state_dict = { 520 ('Earpiece', 'earpiece'): signal_dict['Head'], 521 ('Wifi', 'wifi'): signal_dict['WIFI5Ghz'], 522 ('Wifi 2.4G', 'wifi_24g'): signal_dict['WIFI2Ghz'], 523 ('Voice', 'voice'): 0, 524 ('Wifi AP', 'wifi_ap'): signal_dict['HotspotVoice'], 525 ('Bluetooth', 'bluetooth'): 1, 526 ('Bluetooth media', 'bt_media'): signal_dict['BTMedia'], 527 ('Radio', 'radio_power'): signal_dict['Cell'], 528 ('Motion', 'motion'): signal_dict['IMU'], 529 ('Bluetooth connected', 'bt_connected'): 1 530 } 531 532 if 'BTHotspot' in signal_dict.keys(): 533 device_state_dict[('Bluetooth tethering', 534 'bt_tethering')] = signal_dict['BTHotspot'] 535 536 enforced_state = {} 537 sar_state_command = FORCE_SAR_ADB_COMMAND 538 for key in device_state_dict: 539 enforced_state[key[0]] = device_state_dict[key] 540 sar_state_command = '{} --ei {} {}'.format( 541 sar_state_command, key[1], device_state_dict[key]) 542 if self.sar_version_2: 543 sar_state_command = '{} --es country_iso "{}"'.format( 544 sar_state_command, country_code.lower()) 545 546 #Forcing the SAR state 547 adb_output = ad.adb.shell(sar_state_command) 548 549 # Checking if command was successfully enforced 550 if 'result=0' in adb_output: 551 self.log.info('Requested BT SAR state successfully enforced.') 552 return enforced_state 553 else: 554 self.log.error("Couldn't force BT SAR state.") 555 556 def parse_bt_logs(self, ad, begin_time, regex=''): 557 """Returns bt software stats by parsing logcat since begin_time. 558 559 The quantity to be fetched is dictated by the regex provided. 560 561 Args: 562 ad: android_device object. 563 begin_time: time stamp to start the logcat parsing. 564 regex: regex for fetching the required BT software stats. 565 566 Returns: 567 stat: the desired BT stat. 568 """ 569 # Waiting for logcat to update 570 time.sleep(SLEEP_DURATION) 571 bt_adb_log = ad.adb.logcat('-b all -t %s' % begin_time) 572 for line in bt_adb_log.splitlines(): 573 if re.findall(regex, line): 574 stat = re.findall(regex, line)[0] 575 return stat 576 577 def set_country_code(self, ad, cc): 578 """Sets the SAR regulatory domain as per given country code 579 580 The SAR regulatory domain is forced using an adb command that takes 581 country code as input. 582 583 Args: 584 ad: android_device object. 585 cc: country code 586 """ 587 588 ad.adb.shell("{} --es country_iso {}".format(FORCE_SAR_ADB_COMMAND, 589 cc)) 590 self.log.info("Country Code set to {}".format(cc)) 591 592 def get_country_code(self, ad, begin_time): 593 """Returns the enforced regulatory domain since begin_time 594 595 Returns enforced regulatory domain since begin_time by parsing logcat. 596 Function should follow a function call to set a country code 597 598 Args: 599 ad : android_device obj 600 begin_time: time stamp to start 601 602 Returns: 603 read enforced regulatory domain 604 """ 605 606 reg_domain_regex = "updateRegulatoryDomain:\s+(\S+)" 607 reg_domain = self.parse_bt_logs(ad, begin_time, reg_domain_regex) 608 return reg_domain 609 610 def get_current_power_cap(self, ad, begin_time, type='EDR'): 611 """ Returns the enforced software EDR power cap since begin_time. 612 613 Returns the enforced EDR power cap since begin_time by parsing logcat. 614 Function should follow a function call that forces a SAR state 615 616 Args: 617 ad: android_device obj. 618 begin_time: time stamp to start. 619 620 Returns: 621 read enforced power cap 622 """ 623 power_cap_regex_dict = { 624 'BDR': [ 625 'Bluetooth powers: BR:\s+(\d+), EDR:\s+\d+', 626 'Bluetooth Tx Power Cap\s+(\d+)' 627 ], 628 'EDR': [ 629 'Bluetooth powers: BR:\s+\d+, EDR:\s+(\d+)', 630 'Bluetooth Tx Power Cap\s+(\d+)' 631 ], 632 'BLE': [ 633 'Bluetooth powers: BR:\s+\d+, EDR:\s+\d+, BLE:\s+(\d+)', 634 'Bluetooth Tx Power Cap\s+(\d+)' 635 ] 636 } 637 638 power_cap_regex_list = power_cap_regex_dict[type] 639 640 for power_cap_regex in power_cap_regex_list: 641 power_cap = self.parse_bt_logs(ad, begin_time, power_cap_regex) 642 if power_cap: 643 return int(power_cap) 644 645 raise ValueError('Failed to get TX power cap') 646 647 def get_current_device_state(self, ad, begin_time): 648 """ Returns the device state of the android dut since begin_time. 649 650 Returns the device state of the android dut by parsing logcat since 651 begin_time. Function should follow a function call that forces 652 a SAR state. 653 654 Args: 655 ad: android_device obj. 656 begin_time: time stamp to start. 657 658 Returns: 659 device_state: device state of the android device. 660 """ 661 662 device_state_regex = 'updateDeviceState: DeviceState: ([\s*\S+\s]+)' 663 time.sleep(SLEEP_DURATION) 664 device_state = self.parse_bt_logs(ad, begin_time, device_state_regex) 665 if device_state: 666 return device_state 667 668 raise ValueError("Couldn't fetch device state") 669 670 def read_sar_table(self, ad, output_path=''): 671 """Extracts the BT SAR table from the phone. 672 673 Extracts the BT SAR table from the phone into the android device 674 log path directory. 675 676 Args: 677 ad: android_device object. 678 output_path: path to custom sar table 679 Returns: 680 df : BT SAR table (as pandas DataFrame). 681 """ 682 if not output_path: 683 output_path = os.path.join(ad.device_log_path, self.sar_file_name) 684 ad.adb.pull('{} {}'.format(self.sar_file_path, output_path)) 685 686 df = pd.read_csv(output_path) 687 self.log.info('BT SAR table read from the phone') 688 return df 689 690 def push_table(self, ad, src_path, dest_path=''): 691 """Pushes a BT SAR table to the phone. 692 693 Pushes a BT SAR table to the android device and reboots the device. 694 Also creates a backup file if backup flag is True. 695 696 Args: 697 ad: android_device object. 698 src_path: path to the BT SAR table. 699 """ 700 #Copying the to-be-pushed file for logging 701 if os.path.dirname(src_path) != ad.device_log_path: 702 job.run('cp {} {}'.format(src_path, ad.device_log_path)) 703 704 #Pushing the file provided in the config 705 if dest_path: 706 ad.push_system_file(src_path, dest_path) 707 else: 708 ad.push_system_file(src_path, self.sar_file_path) 709 self.log.info('BT SAR table pushed') 710 ad.reboot() 711 712 self.bt_sar_df = self.read_sar_table(self.dut, src_path) 713 714 def set_PL10_atten_level(self, ad): 715 """Finds the attenuation level at which the phone is at PL10 716 717 Finds PL10 attenuation level by sweeping the attenuation range. 718 If the power level is not achieved during sweep, 719 returns the max atten level 720 721 Args: 722 ad: android object class 723 Returns: 724 atten : attenuation level when the phone is at PL10 725 """ 726 BT_SAR_ATTEN_STEP = 3 727 728 for atten in range(self.atten_min, self.atten_max, BT_SAR_ATTEN_STEP): 729 self.attenuator.set_atten(atten) 730 # Sleep required for BQR to reflect the change in parameters 731 time.sleep(SLEEP_DURATION) 732 metrics = bt_utils.get_bt_metric(ad) 733 if metrics['pwlv'][ad.serial] == 10: 734 self.log.info( 735 'PL10 located at {}'.format(atten + BT_SAR_ATTEN_STEP)) 736 return atten + BT_SAR_ATTEN_STEP 737 738 self.log.warn( 739 "PL10 couldn't be located in the given attenuation range") 740