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