1#!/usr/bin/env python3 2# 3# Copyright 2018 - 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 time 18from enum import Enum 19 20import numpy as np 21from acts.controllers import cellular_simulator 22from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength 23from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode 24from acts.test_utils.tel.tel_test_utils import toggle_cell_data_roaming 25from acts.test_utils.tel.tel_test_utils import get_rx_tx_power_levels 26 27 28class BaseSimulation(): 29 """ Base class for cellular connectivity simulations. 30 31 Classes that inherit from this base class implement different simulation 32 setups. The base class contains methods that are common to all simulation 33 configurations. 34 35 """ 36 37 NUM_UL_CAL_READS = 3 38 NUM_DL_CAL_READS = 5 39 MAX_BTS_INPUT_POWER = 30 40 MAX_PHONE_OUTPUT_POWER = 23 41 UL_MIN_POWER = -60.0 42 43 # Keys to obtain settings from the test_config dictionary. 44 KEY_CALIBRATION = "calibration" 45 KEY_ATTACH_RETRIES = "attach_retries" 46 KEY_ATTACH_TIMEOUT = "attach_timeout" 47 48 # Filepath to the config files stored in the Anritsu callbox. Needs to be 49 # formatted to replace {} with either A or B depending on the model. 50 CALLBOX_PATH_FORMAT_STR = 'C:\\Users\\MD8475{}\\Documents\\DAN_configs\\' 51 52 # Time in seconds to wait for the phone to settle 53 # after attaching to the base station. 54 SETTLING_TIME = 10 55 56 # Default time in seconds to wait for the phone to attach to the basestation 57 # after toggling airplane mode. This setting can be changed with the 58 # KEY_ATTACH_TIMEOUT keyword in the test configuration file. 59 DEFAULT_ATTACH_TIMEOUT = 120 60 61 # The default number of attach retries. This setting can be changed with 62 # the KEY_ATTACH_RETRIES keyword in the test configuration file. 63 DEFAULT_ATTACH_RETRIES = 3 64 65 # These two dictionaries allow to map from a string to a signal level and 66 # have to be overriden by the simulations inheriting from this class. 67 UPLINK_SIGNAL_LEVEL_DICTIONARY = {} 68 DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {} 69 70 # Units for downlink signal level. This variable has to be overriden by 71 # the simulations inheriting from this class. 72 DOWNLINK_SIGNAL_LEVEL_UNITS = None 73 74 class BtsConfig: 75 """ Base station configuration class. This class is only a container for 76 base station parameters and should not interact with the instrument 77 controller. 78 79 Atributes: 80 output_power: a float indicating the required signal level at the 81 instrument's output. 82 input_level: a float indicating the required signal level at the 83 instrument's input. 84 """ 85 def __init__(self): 86 """ Initialize the base station config by setting all its 87 parameters to None. """ 88 self.output_power = None 89 self.input_power = None 90 self.band = None 91 92 def incorporate(self, new_config): 93 """ Incorporates a different configuration by replacing the current 94 values with the new ones for all the parameters different to None. 95 """ 96 for attr, value in vars(new_config).items(): 97 if value: 98 setattr(self, attr, value) 99 100 def __init__(self, simulator, log, dut, test_config, calibration_table): 101 """ Initializes the Simulation object. 102 103 Keeps a reference to the callbox, log and dut handlers and 104 initializes the class attributes. 105 106 Args: 107 simulator: a cellular simulator controller 108 log: a logger handle 109 dut: the android device handler 110 test_config: test configuration obtained from the config file 111 calibration_table: a dictionary containing path losses for 112 different bands. 113 """ 114 115 self.simulator = simulator 116 self.log = log 117 self.dut = dut 118 self.calibration_table = calibration_table 119 120 # Turn calibration on or off depending on the test config value. If the 121 # key is not present, set to False by default 122 if self.KEY_CALIBRATION not in test_config: 123 self.log.warning('The {} key is not set in the testbed ' 124 'parameters. Setting to off by default. To ' 125 'turn calibration on, include the key with ' 126 'a true/false value.'.format( 127 self.KEY_CALIBRATION)) 128 129 self.calibration_required = test_config.get(self.KEY_CALIBRATION, 130 False) 131 132 # Obtain the allowed number of retries from the test configs 133 if self.KEY_ATTACH_RETRIES not in test_config: 134 self.log.warning('The {} key is not set in the testbed ' 135 'parameters. Setting to {} by default.'.format( 136 self.KEY_ATTACH_RETRIES, 137 self.DEFAULT_ATTACH_RETRIES)) 138 139 self.attach_retries = test_config.get(self.KEY_ATTACH_RETRIES, 140 self.DEFAULT_ATTACH_RETRIES) 141 142 # Obtain the attach timeout from the test configs 143 if self.KEY_ATTACH_TIMEOUT not in test_config: 144 self.log.warning('The {} key is not set in the testbed ' 145 'parameters. Setting to {} by default.'.format( 146 self.KEY_ATTACH_TIMEOUT, 147 self.DEFAULT_ATTACH_TIMEOUT)) 148 149 self.attach_timeout = test_config.get(self.KEY_ATTACH_TIMEOUT, 150 self.DEFAULT_ATTACH_TIMEOUT) 151 152 # Configuration object for the primary base station 153 self.primary_config = self.BtsConfig() 154 155 # Store the current calibrated band 156 self.current_calibrated_band = None 157 158 # Path loss measured during calibration 159 self.dl_path_loss = None 160 self.ul_path_loss = None 161 162 # Target signal levels obtained during configuration 163 self.sim_dl_power = None 164 self.sim_ul_power = None 165 166 # Stores RRC status change timer 167 self.rrc_sc_timer = None 168 169 # Set to default APN 170 log.info("Configuring APN.") 171 dut.droid.telephonySetAPN("test", "test", "default") 172 173 # Enable roaming on the phone 174 toggle_cell_data_roaming(self.dut, True) 175 176 # Make sure airplane mode is on so the phone won't attach right away 177 toggle_airplane_mode(self.log, self.dut, True) 178 179 # Wait for airplane mode setting to propagate 180 time.sleep(2) 181 182 # Prepare the simulator for this simulation setup 183 self.setup_simulator() 184 185 def setup_simulator(self): 186 """ Do initial configuration in the simulator. """ 187 raise NotImplementedError() 188 189 def attach(self): 190 """ Attach the phone to the basestation. 191 192 Sets a good signal level, toggles airplane mode 193 and waits for the phone to attach. 194 195 Returns: 196 True if the phone was able to attach, False if not. 197 """ 198 199 # Turn on airplane mode 200 toggle_airplane_mode(self.log, self.dut, True) 201 202 # Wait for airplane mode setting to propagate 203 time.sleep(2) 204 205 # Provide a good signal power for the phone to attach easily 206 new_config = self.BtsConfig() 207 new_config.input_power = -10 208 new_config.output_power = -30 209 self.simulator.configure_bts(new_config) 210 self.primary_config.incorporate(new_config) 211 212 # Try to attach the phone. 213 for i in range(self.attach_retries): 214 215 try: 216 217 # Turn off airplane mode 218 toggle_airplane_mode(self.log, self.dut, False) 219 220 # Wait for the phone to attach. 221 self.simulator.wait_until_attached(timeout=self.attach_timeout) 222 223 except cellular_simulator.CellularSimulatorError: 224 225 # The phone failed to attach 226 self.log.info( 227 "UE failed to attach on attempt number {}.".format(i + 1)) 228 229 # Turn airplane mode on to prepare the phone for a retry. 230 toggle_airplane_mode(self.log, self.dut, True) 231 232 # Wait for APM to propagate 233 time.sleep(3) 234 235 # Retry 236 if i < self.attach_retries - 1: 237 # Retry 238 continue 239 else: 240 # No more retries left. Return False. 241 return False 242 243 else: 244 # The phone attached successfully. 245 time.sleep(self.SETTLING_TIME) 246 self.log.info("UE attached to the callbox.") 247 break 248 249 return True 250 251 def detach(self): 252 """ Detach the phone from the basestation. 253 254 Turns airplane mode and resets basestation. 255 """ 256 257 # Set the DUT to airplane mode so it doesn't see the 258 # cellular network going off 259 toggle_airplane_mode(self.log, self.dut, True) 260 261 # Wait for APM to propagate 262 time.sleep(2) 263 264 # Power off basestation 265 self.simulator.detach() 266 267 def stop(self): 268 """ Detach phone from the basestation by stopping the simulation. 269 270 Stop the simulation and turn airplane mode on. """ 271 272 # Set the DUT to airplane mode so it doesn't see the 273 # cellular network going off 274 toggle_airplane_mode(self.log, self.dut, True) 275 276 # Wait for APM to propagate 277 time.sleep(2) 278 279 # Stop the simulation 280 self.simulator.stop() 281 282 def start(self): 283 """ Start the simulation by attaching the phone and setting the 284 required DL and UL power. 285 286 Note that this refers to starting the simulated testing environment 287 and not to starting the signaling on the cellular instruments, 288 which might have been done earlier depending on the cellular 289 instrument controller implementation. """ 290 291 if not self.attach(): 292 raise RuntimeError('Could not attach to base station.') 293 294 # Starts IP traffic while changing this setting to force the UE to be 295 # in Communication state, as UL power cannot be set in Idle state 296 self.start_traffic_for_calibration() 297 298 # Wait until it goes to communication state 299 self.simulator.wait_until_communication_state() 300 301 # Set uplink power to a minimum before going to the actual desired 302 # value. This avoid inconsistencies produced by the hysteresis in the 303 # PA switching points. 304 self.log.info('Setting UL power to -30 dBm before going to the ' 305 'requested value to avoid incosistencies caused by ' 306 'hysteresis.') 307 self.set_uplink_tx_power(-30) 308 309 # Set signal levels obtained from the test parameters 310 self.set_downlink_rx_power(self.sim_dl_power) 311 self.set_uplink_tx_power(self.sim_ul_power) 312 313 # Verify signal level 314 try: 315 rx_power, tx_power = get_rx_tx_power_levels(self.log, self.dut) 316 317 if not tx_power or not rx_power[0]: 318 raise RuntimeError('The method return invalid Tx/Rx values.') 319 320 self.log.info('Signal level reported by the DUT in dBm: Tx = {}, ' 321 'Rx = {}.'.format(tx_power, rx_power)) 322 323 if abs(self.sim_ul_power - tx_power) > 1: 324 self.log.warning('Tx power at the UE is off by more than 1 dB') 325 326 except RuntimeError as e: 327 self.log.error('Could not verify Rx / Tx levels: %s.' % e) 328 329 # Stop IP traffic after setting the UL power level 330 self.stop_traffic_for_calibration() 331 332 def parse_parameters(self, parameters): 333 """ Configures simulation using a list of parameters. 334 335 Consumes parameters from a list. 336 Children classes need to call this method first. 337 338 Args: 339 parameters: list of parameters 340 """ 341 342 raise NotImplementedError() 343 344 def consume_parameter(self, parameters, parameter_name, num_values=0): 345 """ Parses a parameter from a list. 346 347 Allows to parse the parameter list. Will delete parameters from the 348 list after consuming them to ensure that they are not used twice. 349 350 Args: 351 parameters: list of parameters 352 parameter_name: keyword to look up in the list 353 num_values: number of arguments following the 354 parameter name in the list 355 Returns: 356 A list containing the parameter name and the following num_values 357 arguments 358 """ 359 360 try: 361 i = parameters.index(parameter_name) 362 except ValueError: 363 # parameter_name is not set 364 return [] 365 366 return_list = [] 367 368 try: 369 for j in range(num_values + 1): 370 return_list.append(parameters.pop(i)) 371 except IndexError: 372 raise ValueError( 373 "Parameter {} has to be followed by {} values.".format( 374 parameter_name, num_values)) 375 376 return return_list 377 378 def set_uplink_tx_power(self, signal_level): 379 """ Configure the uplink tx power level 380 381 Args: 382 signal_level: calibrated tx power in dBm 383 """ 384 new_config = self.BtsConfig() 385 new_config.input_power = self.calibrated_uplink_tx_power( 386 self.primary_config, signal_level) 387 self.simulator.configure_bts(new_config) 388 self.primary_config.incorporate(new_config) 389 390 def set_downlink_rx_power(self, signal_level): 391 """ Configure the downlink rx power level 392 393 Args: 394 signal_level: calibrated rx power in dBm 395 """ 396 new_config = self.BtsConfig() 397 new_config.output_power = self.calibrated_downlink_rx_power( 398 self.primary_config, signal_level) 399 self.simulator.configure_bts(new_config) 400 self.primary_config.incorporate(new_config) 401 402 def get_uplink_power_from_parameters(self, parameters): 403 """ Reads uplink power from a list of parameters. """ 404 405 values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1) 406 407 if values: 408 if values[1] in self.UPLINK_SIGNAL_LEVEL_DICTIONARY: 409 return self.UPLINK_SIGNAL_LEVEL_DICTIONARY[values[1]] 410 else: 411 try: 412 if values[1][0] == 'n': 413 # Treat the 'n' character as a negative sign 414 return -int(values[1][1:]) 415 else: 416 return int(values[1]) 417 except ValueError: 418 pass 419 420 # If the method got to this point it is because PARAM_UL_PW was not 421 # included in the test parameters or the provided value was invalid. 422 raise ValueError( 423 "The test name needs to include parameter {} followed by the " 424 "desired uplink power expressed by an integer number in dBm " 425 "or by one the following values: {}. To indicate negative " 426 "values, use the letter n instead of - sign.".format( 427 self.PARAM_UL_PW, 428 list(self.UPLINK_SIGNAL_LEVEL_DICTIONARY.keys()))) 429 430 def get_downlink_power_from_parameters(self, parameters): 431 """ Reads downlink power from a list of parameters. """ 432 433 values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1) 434 435 if values: 436 if values[1] not in self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY: 437 raise ValueError("Invalid signal level value {}.".format( 438 values[1])) 439 else: 440 return self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY[values[1]] 441 else: 442 # Use default value 443 power = self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY['excellent'] 444 self.log.info("No DL signal level value was indicated in the test " 445 "parameters. Using default value of {} {}.".format( 446 power, self.DOWNLINK_SIGNAL_LEVEL_UNITS)) 447 return power 448 449 def calibrated_downlink_rx_power(self, bts_config, signal_level): 450 """ Calculates the power level at the instrument's output in order to 451 obtain the required rx power level at the DUT's input. 452 453 If calibration values are not available, returns the uncalibrated signal 454 level. 455 456 Args: 457 bts_config: the current configuration at the base station. derived 458 classes implementations can use this object to indicate power as 459 spectral power density or in other units. 460 signal_level: desired downlink received power, can be either a 461 key value pair, an int or a float 462 """ 463 464 # Obtain power value if the provided signal_level is a key value pair 465 if isinstance(signal_level, Enum): 466 power = signal_level.value 467 else: 468 power = signal_level 469 470 # Try to use measured path loss value. If this was not set, it will 471 # throw an TypeError exception 472 try: 473 calibrated_power = round(power + self.dl_path_loss) 474 if calibrated_power > self.simulator.MAX_DL_POWER: 475 self.log.warning( 476 "Cannot achieve phone DL Rx power of {} dBm. Requested TX " 477 "power of {} dBm exceeds callbox limit!".format( 478 power, calibrated_power)) 479 calibrated_power = self.simulator.MAX_DL_POWER 480 self.log.warning( 481 "Setting callbox Tx power to max possible ({} dBm)".format( 482 calibrated_power)) 483 484 self.log.info( 485 "Requested phone DL Rx power of {} dBm, setting callbox Tx " 486 "power at {} dBm".format(power, calibrated_power)) 487 time.sleep(2) 488 # Power has to be a natural number so calibration wont be exact. 489 # Inform the actual received power after rounding. 490 self.log.info( 491 "Phone downlink received power is {0:.2f} dBm".format( 492 calibrated_power - self.dl_path_loss)) 493 return calibrated_power 494 except TypeError: 495 self.log.info("Phone downlink received power set to {} (link is " 496 "uncalibrated).".format(round(power))) 497 return round(power) 498 499 def calibrated_uplink_tx_power(self, bts_config, signal_level): 500 """ Calculates the power level at the instrument's input in order to 501 obtain the required tx power level at the DUT's output. 502 503 If calibration values are not available, returns the uncalibrated signal 504 level. 505 506 Args: 507 bts_config: the current configuration at the base station. derived 508 classes implementations can use this object to indicate power as 509 spectral power density or in other units. 510 signal_level: desired uplink transmitted power, can be either a 511 key value pair, an int or a float 512 """ 513 514 # Obtain power value if the provided signal_level is a key value pair 515 if isinstance(signal_level, Enum): 516 power = signal_level.value 517 else: 518 power = signal_level 519 520 # Try to use measured path loss value. If this was not set, it will 521 # throw an TypeError exception 522 try: 523 calibrated_power = round(power - self.ul_path_loss) 524 if calibrated_power < self.UL_MIN_POWER: 525 self.log.warning( 526 "Cannot achieve phone UL Tx power of {} dBm. Requested UL " 527 "power of {} dBm exceeds callbox limit!".format( 528 power, calibrated_power)) 529 calibrated_power = self.UL_MIN_POWER 530 self.log.warning( 531 "Setting UL Tx power to min possible ({} dBm)".format( 532 calibrated_power)) 533 534 self.log.info( 535 "Requested phone UL Tx power of {} dBm, setting callbox Rx " 536 "power at {} dBm".format(power, calibrated_power)) 537 time.sleep(2) 538 # Power has to be a natural number so calibration wont be exact. 539 # Inform the actual transmitted power after rounding. 540 self.log.info( 541 "Phone uplink transmitted power is {0:.2f} dBm".format( 542 calibrated_power + self.ul_path_loss)) 543 return calibrated_power 544 except TypeError: 545 self.log.info("Phone uplink transmitted power set to {} (link is " 546 "uncalibrated).".format(round(power))) 547 return round(power) 548 549 def calibrate(self, band): 550 """ Calculates UL and DL path loss if it wasn't done before. 551 552 The should be already set to the required band before calling this 553 method. 554 555 Args: 556 band: the band that is currently being calibrated. 557 """ 558 559 if self.dl_path_loss and self.ul_path_loss: 560 self.log.info("Measurements are already calibrated.") 561 562 # Attach the phone to the base station 563 if not self.attach(): 564 self.log.info( 565 "Skipping calibration because the phone failed to attach.") 566 return 567 568 # If downlink or uplink were not yet calibrated, do it now 569 if not self.dl_path_loss: 570 self.dl_path_loss = self.downlink_calibration() 571 if not self.ul_path_loss: 572 self.ul_path_loss = self.uplink_calibration() 573 574 # Detach after calibrating 575 self.detach() 576 time.sleep(2) 577 578 def start_traffic_for_calibration(self): 579 """ 580 Starts UDP IP traffic before running calibration. Uses APN_1 581 configured in the phone. 582 """ 583 self.simulator.start_data_traffic() 584 585 def stop_traffic_for_calibration(self): 586 """ 587 Stops IP traffic after calibration. 588 """ 589 self.simulator.stop_data_traffic() 590 591 def downlink_calibration(self, rat=None, power_units_conversion_func=None): 592 """ Computes downlink path loss and returns the calibration value 593 594 The DUT needs to be attached to the base station before calling this 595 method. 596 597 Args: 598 rat: desired RAT to calibrate (matching the label reported by 599 the phone) 600 power_units_conversion_func: a function to convert the units 601 reported by the phone to dBm. needs to take two arguments: the 602 reported signal level and bts. use None if no conversion is 603 needed. 604 Returns: 605 Dowlink calibration value and measured DL power. 606 """ 607 608 # Check if this parameter was set. Child classes may need to override 609 # this class passing the necessary parameters. 610 if not rat: 611 raise ValueError( 612 "The parameter 'rat' has to indicate the RAT being used as " 613 "reported by the phone.") 614 615 # Save initial output level to restore it after calibration 616 restoration_config = self.BtsConfig() 617 restoration_config.output_power = self.primary_config.output_power 618 619 # Set BTS to a good output level to minimize measurement error 620 initial_screen_timeout = self.dut.droid.getScreenTimeout() 621 new_config = self.BtsConfig() 622 new_config.output_power = self.simulator.MAX_DL_POWER - 5 623 self.simulator.configure_bts(new_config) 624 625 # Set phone sleep time out 626 self.dut.droid.setScreenTimeout(1800) 627 self.dut.droid.goToSleepNow() 628 time.sleep(2) 629 630 # Starting IP traffic 631 self.start_traffic_for_calibration() 632 633 down_power_measured = [] 634 for i in range(0, self.NUM_DL_CAL_READS): 635 # For some reason, the RSRP gets updated on Screen ON event 636 self.dut.droid.wakeUpNow() 637 time.sleep(4) 638 signal_strength = get_telephony_signal_strength(self.dut) 639 down_power_measured.append(signal_strength[rat]) 640 self.dut.droid.goToSleepNow() 641 time.sleep(5) 642 643 # Stop IP traffic 644 self.stop_traffic_for_calibration() 645 646 # Reset phone and bts to original settings 647 self.dut.droid.goToSleepNow() 648 self.dut.droid.setScreenTimeout(initial_screen_timeout) 649 self.simulator.configure_bts(restoration_config) 650 time.sleep(2) 651 652 # Calculate the mean of the measurements 653 reported_asu_power = np.nanmean(down_power_measured) 654 655 # Convert from RSRP to signal power 656 if power_units_conversion_func: 657 avg_down_power = power_units_conversion_func( 658 reported_asu_power, self.primary_config) 659 else: 660 avg_down_power = reported_asu_power 661 662 # Calculate Path Loss 663 dl_target_power = self.simulator.MAX_DL_POWER - 5 664 down_call_path_loss = dl_target_power - avg_down_power 665 666 # Validate the result 667 if not 0 < down_call_path_loss < 100: 668 raise RuntimeError( 669 "Downlink calibration failed. The calculated path loss value " 670 "was {} dBm.".format(down_call_path_loss)) 671 672 self.log.info( 673 "Measured downlink path loss: {} dB".format(down_call_path_loss)) 674 675 return down_call_path_loss 676 677 def uplink_calibration(self): 678 """ Computes uplink path loss and returns the calibration value 679 680 The DUT needs to be attached to the base station before calling this 681 method. 682 683 Returns: 684 Uplink calibration value and measured UL power 685 """ 686 687 # Save initial input level to restore it after calibration 688 restoration_config = self.BtsConfig() 689 restoration_config.input_power = self.primary_config.input_power 690 691 # Set BTS1 to maximum input allowed in order to perform 692 # uplink calibration 693 target_power = self.MAX_PHONE_OUTPUT_POWER 694 initial_screen_timeout = self.dut.droid.getScreenTimeout() 695 new_config = self.BtsConfig() 696 new_config.input_power = self.MAX_BTS_INPUT_POWER 697 self.simulator.configure_bts(new_config) 698 699 # Set phone sleep time out 700 self.dut.droid.setScreenTimeout(1800) 701 self.dut.droid.wakeUpNow() 702 time.sleep(2) 703 704 # Start IP traffic 705 self.start_traffic_for_calibration() 706 707 up_power_per_chain = [] 708 # Get the number of chains 709 cmd = 'MONITOR? UL_PUSCH' 710 uplink_meas_power = self.anritsu.send_query(cmd) 711 str_power_chain = uplink_meas_power.split(',') 712 num_chains = len(str_power_chain) 713 for ichain in range(0, num_chains): 714 up_power_per_chain.append([]) 715 716 for i in range(0, self.NUM_UL_CAL_READS): 717 uplink_meas_power = self.anritsu.send_query(cmd) 718 str_power_chain = uplink_meas_power.split(',') 719 720 for ichain in range(0, num_chains): 721 if (str_power_chain[ichain] == 'DEACTIVE'): 722 up_power_per_chain[ichain].append(float('nan')) 723 else: 724 up_power_per_chain[ichain].append( 725 float(str_power_chain[ichain])) 726 727 time.sleep(3) 728 729 # Stop IP traffic 730 self.stop_traffic_for_calibration() 731 732 # Reset phone and bts to original settings 733 self.dut.droid.goToSleepNow() 734 self.dut.droid.setScreenTimeout(initial_screen_timeout) 735 self.simulator.configure_bts(restoration_config) 736 time.sleep(2) 737 738 # Phone only supports 1x1 Uplink so always chain 0 739 avg_up_power = np.nanmean(up_power_per_chain[0]) 740 if np.isnan(avg_up_power): 741 raise RuntimeError( 742 "Calibration failed because the callbox reported the chain to " 743 "be deactive.") 744 745 up_call_path_loss = target_power - avg_up_power 746 747 # Validate the result 748 if not 0 < up_call_path_loss < 100: 749 raise RuntimeError( 750 "Uplink calibration failed. The calculated path loss value " 751 "was {} dBm.".format(up_call_path_loss)) 752 753 self.log.info( 754 "Measured uplink path loss: {} dB".format(up_call_path_loss)) 755 756 return up_call_path_loss 757 758 def load_pathloss_if_required(self): 759 """ If calibration is required, try to obtain the pathloss values from 760 the calibration table and measure them if they are not available. """ 761 # Invalidate the previous values 762 self.dl_path_loss = None 763 self.ul_path_loss = None 764 765 # Load the new ones 766 if self.calibration_required: 767 768 band = self.primary_config.band 769 770 # Try loading the path loss values from the calibration table. If 771 # they are not available, use the automated calibration procedure. 772 try: 773 self.dl_path_loss = self.calibration_table[band]["dl"] 774 self.ul_path_loss = self.calibration_table[band]["ul"] 775 except KeyError: 776 self.calibrate(band) 777 778 # Complete the calibration table with the new values to be used in 779 # the next tests. 780 if band not in self.calibration_table: 781 self.calibration_table[band] = {} 782 783 if "dl" not in self.calibration_table[band] and self.dl_path_loss: 784 self.calibration_table[band]["dl"] = self.dl_path_loss 785 786 if "ul" not in self.calibration_table[band] and self.ul_path_loss: 787 self.calibration_table[band]["ul"] = self.ul_path_loss 788 789 def maximum_downlink_throughput(self): 790 """ Calculates maximum achievable downlink throughput in the current 791 simulation state. 792 793 Because thoughput is dependent on the RAT, this method needs to be 794 implemented by children classes. 795 796 Returns: 797 Maximum throughput in mbps 798 """ 799 raise NotImplementedError() 800 801 def maximum_uplink_throughput(self): 802 """ Calculates maximum achievable downlink throughput in the current 803 simulation state. 804 805 Because thoughput is dependent on the RAT, this method needs to be 806 implemented by children classes. 807 808 Returns: 809 Maximum throughput in mbps 810 """ 811 raise NotImplementedError() 812