1#!/usr/bin/env python3 2# 3# Copyright 2021 - 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 18from glob import glob 19from time import sleep 20from collections import namedtuple 21from numpy import arange 22from pandas import DataFrame 23from acts.signals import TestError 24from acts.signals import TestFailure 25from acts.logger import epoch_to_log_line_timestamp 26from acts.context import get_current_context 27from acts_contrib.test_utils.gnss import LabTtffTestBase as lttb 28from acts_contrib.test_utils.gnss.gnss_test_utils import launch_eecoexer 29from acts_contrib.test_utils.gnss.gnss_test_utils import excute_eecoexer_function 30from acts_contrib.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool 31from acts_contrib.test_utils.gnss.gnss_test_utils import get_current_epoch_time 32from acts_contrib.test_utils.gnss.gnss_test_utils import check_current_focus_app 33from acts_contrib.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool 34from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data 35from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool 36from acts_contrib.test_utils.gnss.gnss_test_utils import start_pixel_logger 37from acts_contrib.test_utils.gnss.gnss_test_utils import stop_pixel_logger 38from acts_contrib.test_utils.gnss.dut_log_test_utils import start_diagmdlog_background 39from acts_contrib.test_utils.gnss.dut_log_test_utils import get_gpstool_logs 40from acts_contrib.test_utils.gnss.dut_log_test_utils import stop_background_diagmdlog 41from acts_contrib.test_utils.gnss.dut_log_test_utils import get_pixellogger_bcm_log 42from acts_contrib.test_utils.gnss.gnss_testlog_utils import parse_gpstool_ttfflog_to_df 43 44 45def range_wi_end(ad, start, stop, step): 46 """ 47 Generate a list of data from start to stop with the step. The list includes start and stop value 48 and also supports floating point. 49 Args: 50 start: start value. 51 Type, int or float. 52 stop: stop value. 53 Type, int or float. 54 step: step value. 55 Type, int or float. 56 Returns: 57 range_ls: the list of data. 58 """ 59 if step == 0: 60 ad.log.warn('Step is 0. Return empty list') 61 range_ls = [] 62 else: 63 if start == stop: 64 range_ls = [stop] 65 else: 66 range_ls = list(arange(start, stop, step)) 67 if len(range_ls) > 0: 68 if (step < 0 and range_ls[-1] > stop) or (step > 0 and 69 range_ls[-1] < stop): 70 range_ls.append(stop) 71 return range_ls 72 73 74def check_ttff_pe(ad, ttff_data, ttff_mode, pe_criteria): 75 """Verify all TTFF results from ttff_data. 76 77 Args: 78 ad: An AndroidDevice object. 79 ttff_data: TTFF data of secs, position error and signal strength. 80 ttff_mode: TTFF Test mode for current test item. 81 pe_criteria: Criteria for current test item. 82 83 """ 84 ret = True 85 ad.log.info("%d iterations of TTFF %s tests finished." % 86 (len(ttff_data.keys()), ttff_mode)) 87 ad.log.info("%s PASS criteria is %f meters" % (ttff_mode, pe_criteria)) 88 ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data)) 89 90 if len(ttff_data.keys()) == 0: 91 ad.log.error("GTW_GPSTool didn't process TTFF properly.") 92 raise TestFailure("GTW_GPSTool didn't process TTFF properly.") 93 94 if any( 95 float(ttff_data[key].ttff_pe) >= pe_criteria 96 for key in ttff_data.keys()): 97 ad.log.error("One or more TTFF %s are over test criteria %f meters" % 98 (ttff_mode, pe_criteria)) 99 ret = False 100 else: 101 ad.log.info("All TTFF %s are within test criteria %f meters." % 102 (ttff_mode, pe_criteria)) 103 ret = True 104 return ret 105 106 107class GnssBlankingBase(lttb.LabTtffTestBase): 108 """ LAB GNSS Cellular Coex Tx Power Sweep TTFF/FFPE Tests""" 109 110 def __init__(self, controllers): 111 """ Initializes class attributes. """ 112 super().__init__(controllers) 113 self.eecoex_func = '' 114 self.start_pwr = 10 115 self.stop_pwr = 24 116 self.offset = 1 117 self.result_cell_pwr = 10 118 self.gsm_sweep_params = None 119 self.lte_tdd_pc3_sweep_params = None 120 self.lte_tdd_pc2_sweep_params = None 121 self.sa_sensitivity = -150 122 self.gnss_pwr_lvl_offset = -5 123 self.maskfile = None 124 125 def setup_class(self): 126 super().setup_class() 127 req_params = ['sa_sensitivity', 'gnss_pwr_lvl_offset'] 128 self.unpack_userparams(req_param_names=req_params) 129 cell_sweep_params = self.user_params.get('cell_pwr_sweep', []) 130 self.gsm_sweep_params = cell_sweep_params.get("GSM", [10, 33, 1]) 131 self.lte_tdd_pc3_sweep_params = cell_sweep_params.get( 132 "LTE_TDD_PC3", [10, 24, 1]) 133 self.lte_tdd_pc2_sweep_params = cell_sweep_params.get( 134 "LTE_TDD_PC2", [10, 26, 1]) 135 self.sa_sensitivity = self.user_params.get('sa_sensitivity', -150) 136 self.gnss_pwr_lvl_offset = self.user_params.get('gnss_pwr_lvl_offset', -5) 137 138 def setup_test(self): 139 super().setup_test() 140 launch_eecoexer(self.dut) 141 142 # Set DUT temperature the limit to 60 degree 143 self.dut.adb.shell( 144 'setprop persist.com.google.eecoexer.cellular.temperature_limit 60') 145 146 # Get current context full path to create the log folder. 147 cur_test_item_dir = get_current_context().get_full_output_path() 148 self.gnss_log_path = os.path.join(self.log_path, cur_test_item_dir) 149 os.makedirs(self.gnss_log_path, exist_ok=True) 150 151 # Start GNSS chip log 152 if self.diag_option == "QCOM": 153 start_diagmdlog_background(self.dut, maskfile=self.maskfile) 154 else: 155 start_pixel_logger(self.dut) 156 157 def teardown_test(self): 158 super().teardown_test() 159 # Set gnss_vendor_log_path based on GNSS solution vendor. 160 gnss_vendor_log_path = os.path.join(self.gnss_log_path, 161 self.diag_option) 162 os.makedirs(gnss_vendor_log_path, exist_ok=True) 163 164 # Stop GNSS chip log and pull the logs to local file system. 165 if self.diag_option == "QCOM": 166 stop_background_diagmdlog(self.dut, 167 gnss_vendor_log_path, 168 keep_logs=False) 169 else: 170 stop_pixel_logger(self.dut) 171 self.log.info('Getting Pixel BCM Log!') 172 get_pixellogger_bcm_log(self.dut, 173 gnss_vendor_log_path, 174 keep_logs=False) 175 176 # Stop cellular Tx and close GPStool and EEcoexer APPs. 177 self.stop_cell_tx() 178 self.log.debug('Close GPStool APP') 179 self.dut.force_stop_apk("com.android.gpstool") 180 self.log.debug('Close EEcoexer APP') 181 self.dut.force_stop_apk("com.google.eecoexer") 182 183 def stop_cell_tx(self): 184 """ 185 Stop EEcoexer Tx power. 186 """ 187 # EEcoexer cellular stop Tx command. 188 stop_cell_tx_cmd = 'CELLR,19' 189 190 # Stop cellular Tx by EEcoexer. 191 self.log.info('Stop EEcoexer Test Command: {}'.format(stop_cell_tx_cmd)) 192 excute_eecoexer_function(self.dut, stop_cell_tx_cmd) 193 194 def analysis_ttff_ffpe(self, ttff_data, json_tag=''): 195 """ 196 Pull logs and parsing logs into json file. 197 Args: 198 ttff_data: ttff_data from test results. 199 Type, list. 200 json_tag: tag for parsed json file name. 201 Type, str. 202 """ 203 # Create log directory. 204 gps_log_path = os.path.join(self.gnss_log_path, 205 'Cell_Pwr_Sweep_Results') 206 207 # Pull logs of GTW GPStool. 208 get_gpstool_logs(self.dut, gps_log_path, False) 209 210 # Parsing the log of GTW GPStool into pandas dataframe. 211 target_log_name_regx = os.path.join(gps_log_path, 'GPSLogs', 'files', 212 'GNSS_*') 213 self.log.info('Get GPStool logs from: {}'.format(target_log_name_regx)) 214 gps_api_log_ls = glob(target_log_name_regx) 215 latest_gps_api_log = max(gps_api_log_ls, key=os.path.getctime) 216 self.log.info( 217 'Get latest GPStool log is: {}'.format(latest_gps_api_log)) 218 try: 219 df_ttff_ffpe = DataFrame( 220 parse_gpstool_ttfflog_to_df(latest_gps_api_log)) 221 222 # Add test case, TTFF and FFPE data into the dataframe. 223 ttff_dict = {} 224 for i in ttff_data: 225 data = ttff_data[i]._asdict() 226 ttff_dict[i] = dict(data) 227 ttff_time = [] 228 ttff_pe = [] 229 test_case = [] 230 for value in ttff_dict.values(): 231 ttff_time.append(value['ttff_sec']) 232 ttff_pe.append(value['ttff_pe']) 233 test_case.append(json_tag) 234 self.log.info('test_case length {}'.format(str(len(test_case)))) 235 236 df_ttff_ffpe['test_case'] = test_case 237 df_ttff_ffpe['ttff_sec'] = ttff_time 238 df_ttff_ffpe['ttff_pe'] = ttff_pe 239 json_file = 'gps_log_{}.json'.format(json_tag) 240 json_path = os.path.join(gps_log_path, json_file) 241 # Save dataframe into json file. 242 df_ttff_ffpe.to_json(json_path, orient='table', index=False) 243 except ValueError: 244 self.log.warning('Can\'t create the parsed the log data in file.') 245 246 def gnss_hot_start_ttff_ffpe_test(self, 247 iteration, 248 sweep_enable=False, 249 json_tag=''): 250 """ 251 GNSS hot start ttff ffpe tset 252 253 Args: 254 iteration: hot start TTFF test iteration. 255 Type, int. 256 Default, 1. 257 sweep_enable: Indicator for the function to check if it is run by cell_power_sweep() 258 Type, bool. 259 Default, False. 260 json_tag: if the function is run by cell_power_sweep(), the function would use 261 this as a part of file name to save TTFF and FFPE results into json file. 262 Type, str. 263 Default, ''. 264 Raise: 265 TestError: fail to send TTFF start_test_action. 266 """ 267 # Start GTW GPStool. 268 test_type = namedtuple('Type', ['command', 'criteria']) 269 test_type_ttff = test_type('Hot Start', self.hs_ttff_criteria) 270 test_type_pe = test_type('Hot Start', self.hs_ttff_pecriteria) 271 self.dut.log.info("Restart GTW GPSTool") 272 start_gnss_by_gtw_gpstool(self.dut, state=True) 273 274 # Get current time and convert to human readable format 275 begin_time = get_current_epoch_time() 276 log_begin_time = epoch_to_log_line_timestamp(begin_time) 277 self.dut.log.debug('Start time is {}'.format(log_begin_time)) 278 279 # Run hot start TTFF 280 for i in range(3): 281 self.log.info('Start hot start attempt %d' % (i + 1)) 282 self.dut.adb.shell( 283 "am broadcast -a com.android.gpstool.ttff_action " 284 "--es ttff hs --es cycle {} --ez raninterval False".format( 285 iteration)) 286 sleep(1) 287 if self.dut.search_logcat( 288 "act=com.android.gpstool.start_test_action", begin_time): 289 self.dut.log.info("Send TTFF start_test_action successfully.") 290 break 291 else: 292 check_current_focus_app(self.dut) 293 raise TestError("Fail to send TTFF start_test_action.") 294 295 # Verify hot start TTFF results 296 ttff_data = process_ttff_by_gtw_gpstool(self.dut, begin_time, 297 self.simulator_location) 298 299 # Stop GTW GPSTool 300 self.dut.log.info("Stop GTW GPSTool") 301 start_gnss_by_gtw_gpstool(self.dut, state=False) 302 303 if sweep_enable: 304 self.analysis_ttff_ffpe(ttff_data, json_tag) 305 306 result_ttff = check_ttff_data(self.dut, 307 ttff_data, 308 ttff_mode=test_type_ttff.command, 309 criteria=test_type_ttff.criteria) 310 result_pe = check_ttff_pe(self.dut, 311 ttff_data, 312 ttff_mode=test_type_pe.command, 313 pe_criteria=test_type_pe.criteria) 314 if not result_ttff or not result_pe: 315 self.dut.log.warning('%s TTFF fails to reach ' 316 'designated criteria' % test_type_ttff.command) 317 self.dut.log.info("Stop GTW GPSTool") 318 return False 319 320 return True 321 322 def hot_start_gnss_power_sweep(self, 323 start_pwr, 324 stop_pwr, 325 offset, 326 wait, 327 iteration=1, 328 sweep_enable=False, 329 title=''): 330 """ 331 GNSS simulator power sweep of hot start test. 332 333 Args: 334 start_pwr: GNSS simulator power sweep start power level. 335 Type, int. 336 stop_pwr: GNSS simulator power sweep stop power level. 337 Type, int. 338 offset: GNSS simulator power sweep offset 339 Type, int. 340 wait: Wait time before the power sweep. 341 Type, int. 342 iteration: The iteration times of hot start test. 343 Type, int. 344 Default, 1. 345 sweep_enable: Indicator for power sweep. 346 It will be True only in GNSS sensitivity search case. 347 Type, bool. 348 Defaule, False. 349 title: the target log folder title for GNSS sensitivity search test items. 350 Type, str. 351 Default, ''. 352 """ 353 354 # Calculate loop range list from gnss_simulator_power_level and sa_sensitivity 355 range_ls = range_wi_end(self.dut, start_pwr, stop_pwr, offset) 356 sweep_range = ','.join([str(x) for x in range_ls]) 357 358 self.log.debug( 359 'Start the GNSS simulator power sweep. The sweep range is [{}]'. 360 format(sweep_range)) 361 362 if sweep_enable: 363 self.start_gnss_and_wait(wait) 364 else: 365 self.dut.log.info('Wait %d seconds to start TTFF HS' % wait) 366 sleep(wait) 367 368 # Sweep GNSS simulator power level in range_ls. 369 # Do hot start for every power level. 370 # Check the TTFF result if it can pass the criteria. 371 gnss_pwr_lvl = -130 372 for gnss_pwr_lvl in range_ls: 373 374 # Set GNSS Simulator power level 375 self.log.info('Set GNSS simulator power level to %.1f' % 376 gnss_pwr_lvl) 377 self.gnss_simulator.set_power(gnss_pwr_lvl) 378 json_tag = title + '_gnss_pwr_' + str(gnss_pwr_lvl) 379 380 # GNSS hot start test 381 if not self.gnss_hot_start_ttff_ffpe_test(iteration, sweep_enable, 382 json_tag): 383 sensitivity = gnss_pwr_lvl - offset 384 return False, sensitivity 385 return True, gnss_pwr_lvl 386 387 def gnss_init_power_setting(self, first_wait=180): 388 """ 389 GNSS initial power level setting. 390 Args: 391 first_wait: wait time after the cold start. 392 Type, int. 393 Default, 180. 394 Returns: 395 True if the process is done successully and hot start results pass criteria. 396 Raise: 397 TestFailure: fail TTFF test criteria. 398 """ 399 400 # Start and set GNSS simulator 401 self.start_and_set_gnss_simulator_power() 402 403 # Start 1st time cold start to obtain ephemeris 404 process_gnss_by_gtw_gpstool(self.dut, self.test_types['cs'].criteria) 405 406 self.hot_start_gnss_power_sweep(self.gnss_simulator_power_level, 407 self.sa_sensitivity, 408 self.gnss_pwr_lvl_offset, first_wait) 409 410 return True 411 412 def start_gnss_and_wait(self, wait=60): 413 """ 414 The process of enable gnss and spend the wait time for GNSS to 415 gather enoung information that make sure the stability of testing. 416 417 Args: 418 wait: wait time between power sweep. 419 Type, int. 420 Default, 60. 421 """ 422 # Create log path for waiting section logs of GPStool. 423 gnss_wait_log_dir = os.path.join(self.gnss_log_path, 'GNSS_wait') 424 425 # Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds. 426 self.log.info('Enable GNSS for searching satellites') 427 start_gnss_by_gtw_gpstool(self.dut, state=True) 428 self.log.info('Wait for {} seconds'.format(str(wait))) 429 sleep(wait) 430 431 # Stop GNSS and pull the logs. 432 start_gnss_by_gtw_gpstool(self.dut, state=False) 433 get_gpstool_logs(self.dut, gnss_wait_log_dir, False) 434 435 def cell_power_sweep(self): 436 """ 437 Linear search cellular power level. Doing GNSS hot start with cellular coexistence 438 and checking if hot start can pass hot start criteria or not. 439 440 Returns: final power level of cellular power 441 """ 442 # Get parameters from user params. 443 ttft_iteration = self.user_params.get('ttff_iteration', 25) 444 wait_before_test = self.user_params.get('wait_before_test', 60) 445 wait_between_pwr = self.user_params.get('wait_between_pwr', 60) 446 power_th = self.start_pwr 447 448 # Generate the power sweep list. 449 power_search_ls = range_wi_end(self.dut, self.start_pwr, self.stop_pwr, 450 self.offset) 451 452 # Set GNSS simulator power level. 453 self.gnss_simulator.set_power(self.sa_sensitivity) 454 455 # Create gnss log folders for init and cellular sweep 456 gnss_init_log_dir = os.path.join(self.gnss_log_path, 'GNSS_init') 457 458 # Pull all exist GPStool logs into GNSS_init folder 459 get_gpstool_logs(self.dut, gnss_init_log_dir, False) 460 461 if power_search_ls: 462 # Run the cellular and GNSS coexistence test item. 463 for i, pwr_lvl in enumerate(power_search_ls): 464 self.log.info('Cellular power sweep loop: {}'.format(int(i))) 465 self.log.info('Cellular target power: {}'.format(int(pwr_lvl))) 466 467 # Enable GNSS to receive satellites' signals for "wait_between_pwr" seconds. 468 # Wait more time before 1st power level 469 if i == 0: 470 wait = wait_before_test 471 else: 472 wait = wait_between_pwr 473 self.start_gnss_and_wait(wait) 474 475 # Set cellular Tx power level. 476 eecoex_cmd = self.eecoex_func.format(str(pwr_lvl)) 477 eecoex_cmd_file_str = eecoex_cmd.replace(',', '_') 478 excute_eecoexer_function(self.dut, eecoex_cmd) 479 480 # Get the last power level that can pass hots start ttff/ffpe spec. 481 if self.gnss_hot_start_ttff_ffpe_test(ttft_iteration, True, 482 eecoex_cmd_file_str): 483 if i + 1 == len(power_search_ls): 484 power_th = pwr_lvl 485 else: 486 if i == 0: 487 power_th = self.start_pwr 488 else: 489 power_th = power_search_ls[i - 1] 490 491 # Stop cellular Tx after a test cycle. 492 self.stop_cell_tx() 493 494 else: 495 # Run the stand alone test item. 496 self.start_gnss_and_wait(wait_between_pwr) 497 498 eecoex_cmd_file_str = 'no_cellular_coex' 499 self.gnss_hot_start_ttff_ffpe_test(ttft_iteration, True, 500 eecoex_cmd_file_str) 501 502 self.log.info('The GNSS WWAN coex celluar Tx power is {}'.format( 503 str(power_th))) 504 505 return power_th 506