From f5c9b906f7ffeabef8a367cc9fba27e6be1b1bcf Mon Sep 17 00:00:00 2001 From: Ignacio Guarna Date: Tue, 8 Sep 2020 23:06:31 -0300 Subject: Moving cellular simulations library to the controllers package This will keep simulations and simulator classes in the same package. Bug: 167211745 Change-Id: If75bc05cd4219763eafe36417c2597e37127ea1d --- .../anritsu_lib/md8475_cellular_simulator.py | 2 +- .../controllers/cellular_lib/BaseSimulation.py | 811 ++++++++++++ .../acts/controllers/cellular_lib/GsmSimulation.py | 163 +++ .../controllers/cellular_lib/LteCaSimulation.py | 427 +++++++ .../controllers/cellular_lib/LteImsSimulation.py | 46 + .../acts/controllers/cellular_lib/LteSimulation.py | 1299 ++++++++++++++++++++ .../controllers/cellular_lib/UmtsSimulation.py | 303 +++++ .../acts/controllers/cellular_lib/__init__.py | 0 .../acts/controllers/cellular_simulator.py | 2 +- .../rohdeschwarz_lib/cmw500_cellular_simulator.py | 2 +- .../power/cellular/cellular_power_base_test.py | 10 +- .../power/tel_simulations/BaseSimulation.py | 811 ------------ .../power/tel_simulations/GsmSimulation.py | 163 --- .../power/tel_simulations/LteCaSimulation.py | 427 ------- .../power/tel_simulations/LteImsSimulation.py | 46 - .../power/tel_simulations/LteSimulation.py | 1299 -------------------- .../power/tel_simulations/UmtsSimulation.py | 303 ----- .../test_utils/power/tel_simulations/__init__.py | 0 .../power/tel/lab/init_simulation_test.py | 4 +- .../power/tel/lab/power_tel_traffic_e2e_test.py | 2 +- .../power/tel/lab/save_summary_to_file_test.py | 2 +- 21 files changed, 3061 insertions(+), 3061 deletions(-) create mode 100644 acts/framework/acts/controllers/cellular_lib/BaseSimulation.py create mode 100644 acts/framework/acts/controllers/cellular_lib/GsmSimulation.py create mode 100644 acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py create mode 100644 acts/framework/acts/controllers/cellular_lib/LteImsSimulation.py create mode 100644 acts/framework/acts/controllers/cellular_lib/LteSimulation.py create mode 100644 acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py create mode 100644 acts/framework/acts/controllers/cellular_lib/__init__.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/LteImsSimulation.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py delete mode 100644 acts/framework/acts/test_utils/power/tel_simulations/__init__.py diff --git a/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py b/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py index 98a779a89f..eea1c8a291 100644 --- a/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py +++ b/acts/framework/acts/controllers/anritsu_lib/md8475_cellular_simulator.py @@ -18,7 +18,7 @@ import math import ntpath import time import acts.controllers.cellular_simulator as cc -from acts.test_utils.power.tel_simulations import LteSimulation +from acts.controllers.cellular_lib import LteSimulation from acts.controllers.anritsu_lib import md8475a from acts.controllers.anritsu_lib import _anritsu_utils as anritsu diff --git a/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py b/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py new file mode 100644 index 0000000000..7d38caeb95 --- /dev/null +++ b/acts/framework/acts/controllers/cellular_lib/BaseSimulation.py @@ -0,0 +1,811 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +from enum import Enum + +import numpy as np +from acts.controllers import cellular_simulator +from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength +from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode +from acts.test_utils.tel.tel_test_utils import toggle_cell_data_roaming +from acts.test_utils.tel.tel_test_utils import get_rx_tx_power_levels + + +class BaseSimulation(): + """ Base class for cellular connectivity simulations. + + Classes that inherit from this base class implement different simulation + setups. The base class contains methods that are common to all simulation + configurations. + + """ + + NUM_UL_CAL_READS = 3 + NUM_DL_CAL_READS = 5 + MAX_BTS_INPUT_POWER = 30 + MAX_PHONE_OUTPUT_POWER = 23 + UL_MIN_POWER = -60.0 + + # Keys to obtain settings from the test_config dictionary. + KEY_CALIBRATION = "calibration" + KEY_ATTACH_RETRIES = "attach_retries" + KEY_ATTACH_TIMEOUT = "attach_timeout" + + # Filepath to the config files stored in the Anritsu callbox. Needs to be + # formatted to replace {} with either A or B depending on the model. + CALLBOX_PATH_FORMAT_STR = 'C:\\Users\\MD8475{}\\Documents\\DAN_configs\\' + + # Time in seconds to wait for the phone to settle + # after attaching to the base station. + SETTLING_TIME = 10 + + # Default time in seconds to wait for the phone to attach to the basestation + # after toggling airplane mode. This setting can be changed with the + # KEY_ATTACH_TIMEOUT keyword in the test configuration file. + DEFAULT_ATTACH_TIMEOUT = 120 + + # The default number of attach retries. This setting can be changed with + # the KEY_ATTACH_RETRIES keyword in the test configuration file. + DEFAULT_ATTACH_RETRIES = 3 + + # These two dictionaries allow to map from a string to a signal level and + # have to be overriden by the simulations inheriting from this class. + UPLINK_SIGNAL_LEVEL_DICTIONARY = {} + DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {} + + # Units for downlink signal level. This variable has to be overriden by + # the simulations inheriting from this class. + DOWNLINK_SIGNAL_LEVEL_UNITS = None + + class BtsConfig: + """ Base station configuration class. This class is only a container for + base station parameters and should not interact with the instrument + controller. + + Atributes: + output_power: a float indicating the required signal level at the + instrument's output. + input_level: a float indicating the required signal level at the + instrument's input. + """ + def __init__(self): + """ Initialize the base station config by setting all its + parameters to None. """ + self.output_power = None + self.input_power = None + self.band = None + + def incorporate(self, new_config): + """ Incorporates a different configuration by replacing the current + values with the new ones for all the parameters different to None. + """ + for attr, value in vars(new_config).items(): + if value: + setattr(self, attr, value) + + def __init__(self, simulator, log, dut, test_config, calibration_table): + """ Initializes the Simulation object. + + Keeps a reference to the callbox, log and dut handlers and + initializes the class attributes. + + Args: + simulator: a cellular simulator controller + log: a logger handle + dut: the android device handler + test_config: test configuration obtained from the config file + calibration_table: a dictionary containing path losses for + different bands. + """ + + self.simulator = simulator + self.log = log + self.dut = dut + self.calibration_table = calibration_table + + # Turn calibration on or off depending on the test config value. If the + # key is not present, set to False by default + if self.KEY_CALIBRATION not in test_config: + self.log.warning('The {} key is not set in the testbed ' + 'parameters. Setting to off by default. To ' + 'turn calibration on, include the key with ' + 'a true/false value.'.format( + self.KEY_CALIBRATION)) + + self.calibration_required = test_config.get(self.KEY_CALIBRATION, + False) + + # Obtain the allowed number of retries from the test configs + if self.KEY_ATTACH_RETRIES not in test_config: + self.log.warning('The {} key is not set in the testbed ' + 'parameters. Setting to {} by default.'.format( + self.KEY_ATTACH_RETRIES, + self.DEFAULT_ATTACH_RETRIES)) + + self.attach_retries = test_config.get(self.KEY_ATTACH_RETRIES, + self.DEFAULT_ATTACH_RETRIES) + + # Obtain the attach timeout from the test configs + if self.KEY_ATTACH_TIMEOUT not in test_config: + self.log.warning('The {} key is not set in the testbed ' + 'parameters. Setting to {} by default.'.format( + self.KEY_ATTACH_TIMEOUT, + self.DEFAULT_ATTACH_TIMEOUT)) + + self.attach_timeout = test_config.get(self.KEY_ATTACH_TIMEOUT, + self.DEFAULT_ATTACH_TIMEOUT) + + # Configuration object for the primary base station + self.primary_config = self.BtsConfig() + + # Store the current calibrated band + self.current_calibrated_band = None + + # Path loss measured during calibration + self.dl_path_loss = None + self.ul_path_loss = None + + # Target signal levels obtained during configuration + self.sim_dl_power = None + self.sim_ul_power = None + + # Stores RRC status change timer + self.rrc_sc_timer = None + + # Set to default APN + log.info("Configuring APN.") + dut.droid.telephonySetAPN("test", "test", "default") + + # Enable roaming on the phone + toggle_cell_data_roaming(self.dut, True) + + # Make sure airplane mode is on so the phone won't attach right away + toggle_airplane_mode(self.log, self.dut, True) + + # Wait for airplane mode setting to propagate + time.sleep(2) + + # Prepare the simulator for this simulation setup + self.setup_simulator() + + def setup_simulator(self): + """ Do initial configuration in the simulator. """ + raise NotImplementedError() + + def attach(self): + """ Attach the phone to the basestation. + + Sets a good signal level, toggles airplane mode + and waits for the phone to attach. + + Returns: + True if the phone was able to attach, False if not. + """ + + # Turn on airplane mode + toggle_airplane_mode(self.log, self.dut, True) + + # Wait for airplane mode setting to propagate + time.sleep(2) + + # Provide a good signal power for the phone to attach easily + new_config = self.BtsConfig() + new_config.input_power = -10 + new_config.output_power = -30 + self.simulator.configure_bts(new_config) + self.primary_config.incorporate(new_config) + + # Try to attach the phone. + for i in range(self.attach_retries): + + try: + + # Turn off airplane mode + toggle_airplane_mode(self.log, self.dut, False) + + # Wait for the phone to attach. + self.simulator.wait_until_attached(timeout=self.attach_timeout) + + except cellular_simulator.CellularSimulatorError: + + # The phone failed to attach + self.log.info( + "UE failed to attach on attempt number {}.".format(i + 1)) + + # Turn airplane mode on to prepare the phone for a retry. + toggle_airplane_mode(self.log, self.dut, True) + + # Wait for APM to propagate + time.sleep(3) + + # Retry + if i < self.attach_retries - 1: + # Retry + continue + else: + # No more retries left. Return False. + return False + + else: + # The phone attached successfully. + time.sleep(self.SETTLING_TIME) + self.log.info("UE attached to the callbox.") + break + + return True + + def detach(self): + """ Detach the phone from the basestation. + + Turns airplane mode and resets basestation. + """ + + # Set the DUT to airplane mode so it doesn't see the + # cellular network going off + toggle_airplane_mode(self.log, self.dut, True) + + # Wait for APM to propagate + time.sleep(2) + + # Power off basestation + self.simulator.detach() + + def stop(self): + """ Detach phone from the basestation by stopping the simulation. + + Stop the simulation and turn airplane mode on. """ + + # Set the DUT to airplane mode so it doesn't see the + # cellular network going off + toggle_airplane_mode(self.log, self.dut, True) + + # Wait for APM to propagate + time.sleep(2) + + # Stop the simulation + self.simulator.stop() + + def start(self): + """ Start the simulation by attaching the phone and setting the + required DL and UL power. + + Note that this refers to starting the simulated testing environment + and not to starting the signaling on the cellular instruments, + which might have been done earlier depending on the cellular + instrument controller implementation. """ + + if not self.attach(): + raise RuntimeError('Could not attach to base station.') + + # Starts IP traffic while changing this setting to force the UE to be + # in Communication state, as UL power cannot be set in Idle state + self.start_traffic_for_calibration() + + # Wait until it goes to communication state + self.simulator.wait_until_communication_state() + + # Set uplink power to a minimum before going to the actual desired + # value. This avoid inconsistencies produced by the hysteresis in the + # PA switching points. + self.log.info('Setting UL power to -30 dBm before going to the ' + 'requested value to avoid incosistencies caused by ' + 'hysteresis.') + self.set_uplink_tx_power(-30) + + # Set signal levels obtained from the test parameters + self.set_downlink_rx_power(self.sim_dl_power) + self.set_uplink_tx_power(self.sim_ul_power) + + # Verify signal level + try: + rx_power, tx_power = get_rx_tx_power_levels(self.log, self.dut) + + if not tx_power or not rx_power[0]: + raise RuntimeError('The method return invalid Tx/Rx values.') + + self.log.info('Signal level reported by the DUT in dBm: Tx = {}, ' + 'Rx = {}.'.format(tx_power, rx_power)) + + if abs(self.sim_ul_power - tx_power) > 1: + self.log.warning('Tx power at the UE is off by more than 1 dB') + + except RuntimeError as e: + self.log.error('Could not verify Rx / Tx levels: %s.' % e) + + # Stop IP traffic after setting the UL power level + self.stop_traffic_for_calibration() + + def parse_parameters(self, parameters): + """ Configures simulation using a list of parameters. + + Consumes parameters from a list. + Children classes need to call this method first. + + Args: + parameters: list of parameters + """ + + raise NotImplementedError() + + def consume_parameter(self, parameters, parameter_name, num_values=0): + """ Parses a parameter from a list. + + Allows to parse the parameter list. Will delete parameters from the + list after consuming them to ensure that they are not used twice. + + Args: + parameters: list of parameters + parameter_name: keyword to look up in the list + num_values: number of arguments following the + parameter name in the list + Returns: + A list containing the parameter name and the following num_values + arguments + """ + + try: + i = parameters.index(parameter_name) + except ValueError: + # parameter_name is not set + return [] + + return_list = [] + + try: + for j in range(num_values + 1): + return_list.append(parameters.pop(i)) + except IndexError: + raise ValueError( + "Parameter {} has to be followed by {} values.".format( + parameter_name, num_values)) + + return return_list + + def set_uplink_tx_power(self, signal_level): + """ Configure the uplink tx power level + + Args: + signal_level: calibrated tx power in dBm + """ + new_config = self.BtsConfig() + new_config.input_power = self.calibrated_uplink_tx_power( + self.primary_config, signal_level) + self.simulator.configure_bts(new_config) + self.primary_config.incorporate(new_config) + + def set_downlink_rx_power(self, signal_level): + """ Configure the downlink rx power level + + Args: + signal_level: calibrated rx power in dBm + """ + new_config = self.BtsConfig() + new_config.output_power = self.calibrated_downlink_rx_power( + self.primary_config, signal_level) + self.simulator.configure_bts(new_config) + self.primary_config.incorporate(new_config) + + def get_uplink_power_from_parameters(self, parameters): + """ Reads uplink power from a list of parameters. """ + + values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1) + + if values: + if values[1] in self.UPLINK_SIGNAL_LEVEL_DICTIONARY: + return self.UPLINK_SIGNAL_LEVEL_DICTIONARY[values[1]] + else: + try: + if values[1][0] == 'n': + # Treat the 'n' character as a negative sign + return -int(values[1][1:]) + else: + return int(values[1]) + except ValueError: + pass + + # If the method got to this point it is because PARAM_UL_PW was not + # included in the test parameters or the provided value was invalid. + raise ValueError( + "The test name needs to include parameter {} followed by the " + "desired uplink power expressed by an integer number in dBm " + "or by one the following values: {}. To indicate negative " + "values, use the letter n instead of - sign.".format( + self.PARAM_UL_PW, + list(self.UPLINK_SIGNAL_LEVEL_DICTIONARY.keys()))) + + def get_downlink_power_from_parameters(self, parameters): + """ Reads downlink power from a list of parameters. """ + + values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1) + + if values: + if values[1] not in self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY: + raise ValueError("Invalid signal level value {}.".format( + values[1])) + else: + return self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY[values[1]] + else: + # Use default value + power = self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY['excellent'] + self.log.info("No DL signal level value was indicated in the test " + "parameters. Using default value of {} {}.".format( + power, self.DOWNLINK_SIGNAL_LEVEL_UNITS)) + return power + + def calibrated_downlink_rx_power(self, bts_config, signal_level): + """ Calculates the power level at the instrument's output in order to + obtain the required rx power level at the DUT's input. + + If calibration values are not available, returns the uncalibrated signal + level. + + Args: + bts_config: the current configuration at the base station. derived + classes implementations can use this object to indicate power as + spectral power density or in other units. + signal_level: desired downlink received power, can be either a + key value pair, an int or a float + """ + + # Obtain power value if the provided signal_level is a key value pair + if isinstance(signal_level, Enum): + power = signal_level.value + else: + power = signal_level + + # Try to use measured path loss value. If this was not set, it will + # throw an TypeError exception + try: + calibrated_power = round(power + self.dl_path_loss) + if calibrated_power > self.simulator.MAX_DL_POWER: + self.log.warning( + "Cannot achieve phone DL Rx power of {} dBm. Requested TX " + "power of {} dBm exceeds callbox limit!".format( + power, calibrated_power)) + calibrated_power = self.simulator.MAX_DL_POWER + self.log.warning( + "Setting callbox Tx power to max possible ({} dBm)".format( + calibrated_power)) + + self.log.info( + "Requested phone DL Rx power of {} dBm, setting callbox Tx " + "power at {} dBm".format(power, calibrated_power)) + time.sleep(2) + # Power has to be a natural number so calibration wont be exact. + # Inform the actual received power after rounding. + self.log.info( + "Phone downlink received power is {0:.2f} dBm".format( + calibrated_power - self.dl_path_loss)) + return calibrated_power + except TypeError: + self.log.info("Phone downlink received power set to {} (link is " + "uncalibrated).".format(round(power))) + return round(power) + + def calibrated_uplink_tx_power(self, bts_config, signal_level): + """ Calculates the power level at the instrument's input in order to + obtain the required tx power level at the DUT's output. + + If calibration values are not available, returns the uncalibrated signal + level. + + Args: + bts_config: the current configuration at the base station. derived + classes implementations can use this object to indicate power as + spectral power density or in other units. + signal_level: desired uplink transmitted power, can be either a + key value pair, an int or a float + """ + + # Obtain power value if the provided signal_level is a key value pair + if isinstance(signal_level, Enum): + power = signal_level.value + else: + power = signal_level + + # Try to use measured path loss value. If this was not set, it will + # throw an TypeError exception + try: + calibrated_power = round(power - self.ul_path_loss) + if calibrated_power < self.UL_MIN_POWER: + self.log.warning( + "Cannot achieve phone UL Tx power of {} dBm. Requested UL " + "power of {} dBm exceeds callbox limit!".format( + power, calibrated_power)) + calibrated_power = self.UL_MIN_POWER + self.log.warning( + "Setting UL Tx power to min possible ({} dBm)".format( + calibrated_power)) + + self.log.info( + "Requested phone UL Tx power of {} dBm, setting callbox Rx " + "power at {} dBm".format(power, calibrated_power)) + time.sleep(2) + # Power has to be a natural number so calibration wont be exact. + # Inform the actual transmitted power after rounding. + self.log.info( + "Phone uplink transmitted power is {0:.2f} dBm".format( + calibrated_power + self.ul_path_loss)) + return calibrated_power + except TypeError: + self.log.info("Phone uplink transmitted power set to {} (link is " + "uncalibrated).".format(round(power))) + return round(power) + + def calibrate(self, band): + """ Calculates UL and DL path loss if it wasn't done before. + + The should be already set to the required band before calling this + method. + + Args: + band: the band that is currently being calibrated. + """ + + if self.dl_path_loss and self.ul_path_loss: + self.log.info("Measurements are already calibrated.") + + # Attach the phone to the base station + if not self.attach(): + self.log.info( + "Skipping calibration because the phone failed to attach.") + return + + # If downlink or uplink were not yet calibrated, do it now + if not self.dl_path_loss: + self.dl_path_loss = self.downlink_calibration() + if not self.ul_path_loss: + self.ul_path_loss = self.uplink_calibration() + + # Detach after calibrating + self.detach() + time.sleep(2) + + def start_traffic_for_calibration(self): + """ + Starts UDP IP traffic before running calibration. Uses APN_1 + configured in the phone. + """ + self.simulator.start_data_traffic() + + def stop_traffic_for_calibration(self): + """ + Stops IP traffic after calibration. + """ + self.simulator.stop_data_traffic() + + def downlink_calibration(self, rat=None, power_units_conversion_func=None): + """ Computes downlink path loss and returns the calibration value + + The DUT needs to be attached to the base station before calling this + method. + + Args: + rat: desired RAT to calibrate (matching the label reported by + the phone) + power_units_conversion_func: a function to convert the units + reported by the phone to dBm. needs to take two arguments: the + reported signal level and bts. use None if no conversion is + needed. + Returns: + Dowlink calibration value and measured DL power. + """ + + # Check if this parameter was set. Child classes may need to override + # this class passing the necessary parameters. + if not rat: + raise ValueError( + "The parameter 'rat' has to indicate the RAT being used as " + "reported by the phone.") + + # Save initial output level to restore it after calibration + restoration_config = self.BtsConfig() + restoration_config.output_power = self.primary_config.output_power + + # Set BTS to a good output level to minimize measurement error + initial_screen_timeout = self.dut.droid.getScreenTimeout() + new_config = self.BtsConfig() + new_config.output_power = self.simulator.MAX_DL_POWER - 5 + self.simulator.configure_bts(new_config) + + # Set phone sleep time out + self.dut.droid.setScreenTimeout(1800) + self.dut.droid.goToSleepNow() + time.sleep(2) + + # Starting IP traffic + self.start_traffic_for_calibration() + + down_power_measured = [] + for i in range(0, self.NUM_DL_CAL_READS): + # For some reason, the RSRP gets updated on Screen ON event + self.dut.droid.wakeUpNow() + time.sleep(4) + signal_strength = get_telephony_signal_strength(self.dut) + down_power_measured.append(signal_strength[rat]) + self.dut.droid.goToSleepNow() + time.sleep(5) + + # Stop IP traffic + self.stop_traffic_for_calibration() + + # Reset phone and bts to original settings + self.dut.droid.goToSleepNow() + self.dut.droid.setScreenTimeout(initial_screen_timeout) + self.simulator.configure_bts(restoration_config) + time.sleep(2) + + # Calculate the mean of the measurements + reported_asu_power = np.nanmean(down_power_measured) + + # Convert from RSRP to signal power + if power_units_conversion_func: + avg_down_power = power_units_conversion_func( + reported_asu_power, self.primary_config) + else: + avg_down_power = reported_asu_power + + # Calculate Path Loss + dl_target_power = self.simulator.MAX_DL_POWER - 5 + down_call_path_loss = dl_target_power - avg_down_power + + # Validate the result + if not 0 < down_call_path_loss < 100: + raise RuntimeError( + "Downlink calibration failed. The calculated path loss value " + "was {} dBm.".format(down_call_path_loss)) + + self.log.info( + "Measured downlink path loss: {} dB".format(down_call_path_loss)) + + return down_call_path_loss + + def uplink_calibration(self): + """ Computes uplink path loss and returns the calibration value + + The DUT needs to be attached to the base station before calling this + method. + + Returns: + Uplink calibration value and measured UL power + """ + + # Save initial input level to restore it after calibration + restoration_config = self.BtsConfig() + restoration_config.input_power = self.primary_config.input_power + + # Set BTS1 to maximum input allowed in order to perform + # uplink calibration + target_power = self.MAX_PHONE_OUTPUT_POWER + initial_screen_timeout = self.dut.droid.getScreenTimeout() + new_config = self.BtsConfig() + new_config.input_power = self.MAX_BTS_INPUT_POWER + self.simulator.configure_bts(new_config) + + # Set phone sleep time out + self.dut.droid.setScreenTimeout(1800) + self.dut.droid.wakeUpNow() + time.sleep(2) + + # Start IP traffic + self.start_traffic_for_calibration() + + up_power_per_chain = [] + # Get the number of chains + cmd = 'MONITOR? UL_PUSCH' + uplink_meas_power = self.anritsu.send_query(cmd) + str_power_chain = uplink_meas_power.split(',') + num_chains = len(str_power_chain) + for ichain in range(0, num_chains): + up_power_per_chain.append([]) + + for i in range(0, self.NUM_UL_CAL_READS): + uplink_meas_power = self.anritsu.send_query(cmd) + str_power_chain = uplink_meas_power.split(',') + + for ichain in range(0, num_chains): + if (str_power_chain[ichain] == 'DEACTIVE'): + up_power_per_chain[ichain].append(float('nan')) + else: + up_power_per_chain[ichain].append( + float(str_power_chain[ichain])) + + time.sleep(3) + + # Stop IP traffic + self.stop_traffic_for_calibration() + + # Reset phone and bts to original settings + self.dut.droid.goToSleepNow() + self.dut.droid.setScreenTimeout(initial_screen_timeout) + self.simulator.configure_bts(restoration_config) + time.sleep(2) + + # Phone only supports 1x1 Uplink so always chain 0 + avg_up_power = np.nanmean(up_power_per_chain[0]) + if np.isnan(avg_up_power): + raise RuntimeError( + "Calibration failed because the callbox reported the chain to " + "be deactive.") + + up_call_path_loss = target_power - avg_up_power + + # Validate the result + if not 0 < up_call_path_loss < 100: + raise RuntimeError( + "Uplink calibration failed. The calculated path loss value " + "was {} dBm.".format(up_call_path_loss)) + + self.log.info( + "Measured uplink path loss: {} dB".format(up_call_path_loss)) + + return up_call_path_loss + + def load_pathloss_if_required(self): + """ If calibration is required, try to obtain the pathloss values from + the calibration table and measure them if they are not available. """ + # Invalidate the previous values + self.dl_path_loss = None + self.ul_path_loss = None + + # Load the new ones + if self.calibration_required: + + band = self.primary_config.band + + # Try loading the path loss values from the calibration table. If + # they are not available, use the automated calibration procedure. + try: + self.dl_path_loss = self.calibration_table[band]["dl"] + self.ul_path_loss = self.calibration_table[band]["ul"] + except KeyError: + self.calibrate(band) + + # Complete the calibration table with the new values to be used in + # the next tests. + if band not in self.calibration_table: + self.calibration_table[band] = {} + + if "dl" not in self.calibration_table[band] and self.dl_path_loss: + self.calibration_table[band]["dl"] = self.dl_path_loss + + if "ul" not in self.calibration_table[band] and self.ul_path_loss: + self.calibration_table[band]["ul"] = self.ul_path_loss + + def maximum_downlink_throughput(self): + """ Calculates maximum achievable downlink throughput in the current + simulation state. + + Because thoughput is dependent on the RAT, this method needs to be + implemented by children classes. + + Returns: + Maximum throughput in mbps + """ + raise NotImplementedError() + + def maximum_uplink_throughput(self): + """ Calculates maximum achievable downlink throughput in the current + simulation state. + + Because thoughput is dependent on the RAT, this method needs to be + implemented by children classes. + + Returns: + Maximum throughput in mbps + """ + raise NotImplementedError() diff --git a/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py b/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py new file mode 100644 index 0000000000..d275cae208 --- /dev/null +++ b/acts/framework/acts/controllers/cellular_lib/GsmSimulation.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ntpath + +import time +from acts.controllers.anritsu_lib.md8475a import BtsGprsMode +from acts.controllers.anritsu_lib.md8475a import BtsNumber +from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim +from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation +from acts.test_utils.tel.anritsu_utils import GSM_BAND_DCS1800 +from acts.test_utils.tel.anritsu_utils import GSM_BAND_EGSM900 +from acts.test_utils.tel.anritsu_utils import GSM_BAND_GSM850 +from acts.test_utils.tel.anritsu_utils import GSM_BAND_RGSM900 +from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY + + +class GsmSimulation(BaseSimulation): + """ Single base station GSM. """ + + # Simulation config files in the callbox computer. + # These should be replaced in the future by setting up + # the same configuration manually. + + GSM_BASIC_SIM_FILE = 'SIM_default_GSM.wnssp' + + GSM_CELL_FILE = 'CELL_GSM_config.wnscp' + + # Test name parameters + + PARAM_BAND = "band" + PARAM_GPRS = "gprs" + PARAM_EGPRS = "edge" + PARAM_NO_GPRS = "nogprs" + PARAM_SLOTS = "slots" + + bands_parameter_mapping = { + '850': GSM_BAND_GSM850, + '900': GSM_BAND_EGSM900, + '1800': GSM_BAND_DCS1800, + '1900': GSM_BAND_RGSM900 + } + + def __init__(self, simulator, log, dut, test_config, calibration_table): + """ Initializes the simulator for a single-carrier GSM simulation. + + Loads a simple LTE simulation enviroment with 1 basestation. It also + creates the BTS handle so we can change the parameters as desired. + + Args: + simulator: a cellular simulator controller + log: a logger handle + dut: the android device handler + test_config: test configuration obtained from the config file + calibration_table: a dictionary containing path losses for + different bands. + + """ + # The GSM simulation relies on the cellular simulator to be a MD8475 + if not isinstance(self.simulator, anritsusim.MD8475CellularSimulator): + raise ValueError('The GSM simulation relies on the simulator to ' + 'be an Anritsu MD8475 A/B instrument.') + + # The Anritsu controller needs to be unwrapped before calling + # super().__init__ because setup_simulator() requires self.anritsu and + # will be called during the parent class initialization. + self.anritsu = self.simulator.anritsu + self.bts1 = self.anritsu.get_BTS(BtsNumber.BTS1) + + super().__init__(simulator, log, dut, test_config, calibration_table) + + if not dut.droid.telephonySetPreferredNetworkTypesForSubscription( + NETWORK_MODE_GSM_ONLY, + dut.droid.subscriptionGetDefaultSubId()): + log.error("Coold not set preferred network type.") + else: + log.info("Preferred network type set.") + + def setup_simulator(self): + """ Do initial configuration in the simulator. """ + + # Load callbox config files + callbox_config_path = self.CALLBOX_PATH_FORMAT_STR.format( + self.anritsu._md8475_version) + + self.anritsu.load_simulation_paramfile( + ntpath.join(callbox_config_path, self.GSM_BASIC_SIM_FILE)) + self.anritsu.load_cell_paramfile( + ntpath.join(callbox_config_path, self.GSM_CELL_FILE)) + + # Start simulation if it wasn't started + self.anritsu.start_simulation() + + def parse_parameters(self, parameters): + """ Configs a GSM simulation using a list of parameters. + + Calls the parent method first, then consumes parameters specific to GSM. + + Args: + parameters: list of parameters + """ + + # Setup band + + values = self.consume_parameter(parameters, self.PARAM_BAND, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter '{}' followed by " + "the required band number.".format(self.PARAM_BAND)) + + self.set_band(self.bts1, values[1]) + self.load_pathloss_if_required() + + # Setup GPRS mode + + if self.consume_parameter(parameters, self.PARAM_GPRS): + self.bts1.gsm_gprs_mode = BtsGprsMode.GPRS + elif self.consume_parameter(parameters, self.PARAM_EGPRS): + self.bts1.gsm_gprs_mode = BtsGprsMode.EGPRS + elif self.consume_parameter(parameters, self.PARAM_NO_GPRS): + self.bts1.gsm_gprs_mode = BtsGprsMode.NO_GPRS + else: + raise ValueError( + "GPRS mode needs to be indicated in the test name with either " + "{}, {} or {}.".format(self.PARAM_GPRS, self.PARAM_EGPRS, + self.PARAM_NO_GPRS)) + + # Setup slot allocation + + values = self.consume_parameter(parameters, self.PARAM_SLOTS, 2) + + if not values: + raise ValueError( + "The test name needs to include parameter {} followed by two " + "int values indicating DL and UL slots.".format( + self.PARAM_SLOTS)) + + self.bts1.gsm_slots = (int(values[1]), int(values[2])) + + def set_band(self, bts, band): + """ Sets the band used for communication. + + Args: + bts: basestation handle + band: desired band + """ + + bts.band = band + time.sleep(5) # It takes some time to propagate the new band diff --git a/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py new file mode 100644 index 0000000000..26f6e43d07 --- /dev/null +++ b/acts/framework/acts/controllers/cellular_lib/LteCaSimulation.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re +from acts.controllers.cellular_lib import LteSimulation + + +class LteCaSimulation(LteSimulation.LteSimulation): + """ Carrier aggregation LTE simulation. """ + + # Dictionary of lower DL channel number bound for each band. + LOWEST_DL_CN_DICTIONARY = { + 1: 0, + 2: 600, + 3: 1200, + 4: 1950, + 5: 2400, + 6: 2650, + 7: 2750, + 8: 3450, + 9: 3800, + 10: 4150, + 11: 4750, + 12: 5010, + 13: 5180, + 14: 5280, + 17: 5730, + 18: 5850, + 19: 6000, + 20: 6150, + 21: 6450, + 22: 6600, + 23: 7500, + 24: 7700, + 25: 8040, + 26: 8690, + 27: 9040, + 28: 9210, + 29: 9660, + 30: 9770, + 31: 9870, + 32: 36000, + 33: 36200, + 34: 36350, + 35: 36950, + 36: 37550, + 37: 37750, + 38: 38250, + 39: 38650, + 40: 39650, + 41: 41590, + 42: 45590, + 66: 66436 + } + + # Simulation config keywords contained in the test name + PARAM_CA = 'ca' + + # Test config keywords + KEY_FREQ_BANDS = "freq_bands" + + def __init__(self, simulator, log, dut, test_config, calibration_table): + """ Initializes the simulator for LTE simulation with carrier + aggregation. + + Loads a simple LTE simulation enviroment with 5 basestations. + + Args: + simulator: the cellular instrument controller + log: a logger handle + dut: the android device handler + test_config: test configuration obtained from the config file + calibration_table: a dictionary containing path losses for + different bands. + + """ + + super().__init__(simulator, log, dut, test_config, calibration_table) + + # Create a configuration object for each base station and copy initial + # settings from the PCC base station. + self.bts_configs = [self.primary_config] + + for bts_index in range(1, self.simulator.LTE_MAX_CARRIERS): + new_config = self.BtsConfig() + new_config.incorporate(self.primary_config) + self.simulator.configure_bts(new_config, bts_index) + self.bts_configs.append(new_config) + + # Get LTE CA frequency bands setting from the test configuration + if self.KEY_FREQ_BANDS not in test_config: + self.log.warning("The key '{}' is not set in the config file. " + "Setting to null by default.".format( + self.KEY_FREQ_BANDS)) + + self.freq_bands = test_config.get(self.KEY_FREQ_BANDS, True) + + def setup_simulator(self): + """ Do initial configuration in the simulator. """ + self.simulator.setup_lte_ca_scenario() + + def parse_parameters(self, parameters): + """ Configs an LTE simulation with CA using a list of parameters. + + Args: + parameters: list of parameters + """ + + # Get the CA band configuration + + values = self.consume_parameter(parameters, self.PARAM_CA, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter '{}' followed by " + "the CA configuration. For example: ca_3c7c28a".format( + self.PARAM_CA)) + + # Carrier aggregation configurations are indicated with the band numbers + # followed by the CA classes in a single string. For example, for 5 CA + # using 3C 7C and 28A the parameter value should be 3c7c28a. + ca_configs = re.findall(r'(\d+[abcABC])', values[1]) + + if not ca_configs: + raise ValueError( + "The CA configuration has to be indicated with one string as " + "in the following example: ca_3c7c28a".format(self.PARAM_CA)) + + # Apply the carrier aggregation combination + self.simulator.set_ca_combination(ca_configs) + + # Save the bands to the bts config objects + bts_index = 0 + for ca in ca_configs: + ca_class = ca[-1] + band = ca[:-1] + + self.bts_configs[bts_index].band = band + bts_index += 1 + + if ca_class.upper() == 'B' or ca_class.upper() == 'C': + # Class B and C means two carriers with the same band + self.bts_configs[bts_index].band = band + bts_index += 1 + + # Count the number of carriers in the CA combination + self.num_carriers = 0 + for ca in ca_configs: + ca_class = ca[-1] + # Class C means that there are two contiguous carriers, while other + # classes are a single one. + if ca_class.upper() == 'C': + self.num_carriers += 2 + else: + self.num_carriers += 1 + + # Create an array of configuration objects to set up the base stations. + new_configs = [self.BtsConfig() for _ in range(self.num_carriers)] + + # Get the bw for each carrier + # This is an optional parameter, by default the maximum bandwidth for + # each band will be selected. + + values = self.consume_parameter(parameters, self.PARAM_BW, + self.num_carriers) + + bts_index = 0 + + for ca in ca_configs: + + band = int(ca[:-1]) + ca_class = ca[-1] + + if values: + bw = int(values[1 + bts_index]) + else: + bw = max(self.allowed_bandwidth_dictionary[band]) + + new_configs[bts_index].bandwidth = bw + bts_index += 1 + + if ca_class.upper() == 'C': + + new_configs[bts_index].bandwidth = bw + + # Calculate the channel number for the second carrier to be + # contiguous to the first one + new_configs[bts_index].dl_channel = int( + self.LOWEST_DL_CN_DICTIONARY[int(band)] + bw * 10 - 2) + + bts_index += 1 + + # Get the TM for each carrier + # This is an optional parameter, by the default value depends on the + # MIMO mode for each carrier + + tm_values = self.consume_parameter(parameters, self.PARAM_TM, + self.num_carriers) + + # Get the MIMO mode for each carrier + + mimo_values = self.consume_parameter(parameters, self.PARAM_MIMO, + self.num_carriers) + + if not mimo_values: + raise ValueError( + "The test parameter '{}' has to be included in the " + "test name followed by the MIMO mode for each " + "carrier separated by underscores.".format(self.PARAM_MIMO)) + + if len(mimo_values) != self.num_carriers + 1: + raise ValueError( + "The test parameter '{}' has to be followed by " + "a number of MIMO mode values equal to the number " + "of carriers being used.".format(self.PARAM_MIMO)) + + for bts_index in range(self.num_carriers): + + # Parse and set the requested MIMO mode + + for mimo_mode in LteSimulation.MimoMode: + if mimo_values[bts_index + 1] == mimo_mode.value: + requested_mimo = mimo_mode + break + else: + raise ValueError( + "The mimo mode must be one of %s." % + {elem.value + for elem in LteSimulation.MimoMode}) + + if (requested_mimo == LteSimulation.MimoMode.MIMO_4x4 + and not self.simulator.LTE_SUPPORTS_4X4_MIMO): + raise ValueError("The test requires 4x4 MIMO, but that is not " + "supported by the MD8475A callbox.") + + new_configs[bts_index].mimo_mode = requested_mimo + + # Parse and set the requested TM + + if tm_values: + for tm in LteSimulation.TransmissionMode: + if tm_values[bts_index + 1] == tm.value[2:]: + requested_tm = tm + break + else: + raise ValueError( + "The TM must be one of %s." % + {elem.value + for elem in LteSimulation.MimoMode}) + else: + # Provide default values if the TM parameter is not set + if requested_mimo == LteSimulation.MimoMode.MIMO_1x1: + requested_tm = LteSimulation.TransmissionMode.TM1 + else: + requested_tm = LteSimulation.TransmissionMode.TM3 + + new_configs[bts_index].transmission_mode = requested_tm + + self.log.info("Cell {} will be set to {} and {} MIMO.".format( + bts_index + 1, requested_tm.value, requested_mimo.value)) + + # Get uplink power + + ul_power = self.get_uplink_power_from_parameters(parameters) + + # Power is not set on the callbox until after the simulation is + # started. Saving this value in a variable for later + self.sim_ul_power = ul_power + + # Get downlink power + + dl_power = self.get_downlink_power_from_parameters(parameters) + + # Power is not set on the callbox until after the simulation is + # started. Saving this value in a variable for later + self.sim_dl_power = dl_power + + # Setup scheduling mode + + values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1) + + if not values: + scheduling = LteSimulation.SchedulingMode.STATIC + self.log.warning( + "The test name does not include the '{}' parameter. Setting to " + "{} by default.".format(scheduling.value, + self.PARAM_SCHEDULING)) + else: + for scheduling_mode in LteSimulation.SchedulingMode: + if values[1].upper() == scheduling_mode.value: + scheduling = scheduling_mode + break + else: + raise ValueError( + "The test name parameter '{}' has to be followed by one of " + "{}.".format( + self.PARAM_SCHEDULING, + {elem.value + for elem in LteSimulation.SchedulingMode})) + + for bts_index in range(self.num_carriers): + new_configs[bts_index].scheduling_mode = scheduling + + if scheduling == LteSimulation.SchedulingMode.STATIC: + + values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2) + + if not values: + self.log.warning( + "The '{}' parameter was not set, using 100% RBs for both " + "DL and UL. To set the percentages of total RBs include " + "the '{}' parameter followed by two ints separated by an " + "underscore indicating downlink and uplink percentages.". + format(self.PARAM_PATTERN, self.PARAM_PATTERN)) + dl_pattern = 100 + ul_pattern = 100 + else: + dl_pattern = int(values[1]) + ul_pattern = int(values[2]) + + if (dl_pattern, ul_pattern) not in [(0, 100), (100, 0), + (100, 100)]: + raise ValueError( + "Only full RB allocation for DL or UL is supported in CA " + "sims. The allowed combinations are 100/0, 0/100 and " + "100/100.") + + for bts_index in range(self.num_carriers): + + # Look for a DL MCS configuration in the test parameters. If it + # is not present, use a default value. + dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, + 1) + if dlmcs: + mcs_dl = int(dlmcs[1]) + else: + self.log.warning( + 'The test name does not include the {} parameter. ' + 'Setting to the max value by default'.format( + self.PARAM_DL_MCS)) + + if self.dl_256_qam and new_configs[ + bts_index].bandwidth == 1.4: + mcs_dl = 26 + elif (not self.dl_256_qam + and self.primary_config.tbs_pattern_on + and new_configs[bts_index].bandwidth != 1.4): + mcs_dl = 28 + else: + mcs_dl = 27 + + # Look for an UL MCS configuration in the test parameters. If it + # is not present, use a default value. + ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, + 1) + if ulmcs: + mcs_ul = int(ulmcs[1]) + else: + self.log.warning( + 'The test name does not include the {} parameter. ' + 'Setting to the max value by default'.format( + self.PARAM_UL_MCS)) + + if self.ul_64_qam: + mcs_ul = 28 + else: + mcs_ul = 23 + + dl_rbs, ul_rbs = self.allocation_percentages_to_rbs( + new_configs[bts_index].bandwidth, + new_configs[bts_index].transmission_mode, dl_pattern, + ul_pattern) + + new_configs[bts_index].dl_rbs = dl_rbs + new_configs[bts_index].ul_rbs = ul_rbs + new_configs[bts_index].dl_mcs = mcs_dl + new_configs[bts_index].ul_mcs = mcs_ul + + # Setup the base stations with the obtained configurations and then save + # these parameters in the current configuration objects + for bts_index in range(len(new_configs)): + self.simulator.configure_bts(new_configs[bts_index], bts_index) + self.bts_configs[bts_index].incorporate(new_configs[bts_index]) + + # Now that the band is set, calibrate the link for the PCC if necessary + self.load_pathloss_if_required() + + def maximum_downlink_throughput(self): + """ Calculates maximum downlink throughput as the sum of all the active + carriers. + """ + return sum( + self.bts_maximum_downlink_throughtput(self.bts_configs[bts_index]) + for bts_index in range(self.num_carriers)) + + def start(self): + """ Set the signal level for the secondary carriers, as the base class + implementation of this method will only set up downlink power for the + primary carrier component. + + After that, attaches the secondary carriers.""" + + super().start() + + if self.sim_dl_power: + self.log.info('Setting DL power for secondary carriers.') + + for bts_index in range(1, self.num_carriers): + new_config = self.BtsConfig() + new_config.output_power = self.calibrated_downlink_rx_power( + self.bts_configs[bts_index], self.sim_dl_power) + self.simulator.configure_bts(new_config, bts_index) + self.bts_configs[bts_index].incorporate(new_config) + + self.simulator.lte_attach_secondary_carriers(self.freq_bands) diff --git a/acts/framework/acts/controllers/cellular_lib/LteImsSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteImsSimulation.py new file mode 100644 index 0000000000..e13eb297ba --- /dev/null +++ b/acts/framework/acts/controllers/cellular_lib/LteImsSimulation.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from acts.controllers.cellular_lib.LteSimulation import LteSimulation +import acts.test_utils.tel.anritsu_utils as anritsu_utils +import acts.controllers.anritsu_lib.md8475a as md8475a + + +class LteImsSimulation(LteSimulation): + + LTE_BASIC_SIM_FILE = 'VoLTE_ATT_Sim.wnssp' + LTE_BASIC_CELL_FILE = 'VoLTE_ATT_Cell.wnscp' + + def attach(self): + """ After attaching verify the UE has registered with the IMS server. + + Returns: + True if the phone was able to attach, False if not. + """ + + if not super().attach(): + return False + + # The phone should have registered with the IMS server before attaching. + # Make sure the IMS registration was successful by verifying the CSCF + # status is SIP IDLE. + if not anritsu_utils.wait_for_ims_cscf_status( + self.log, self.anritsu, + anritsu_utils.DEFAULT_IMS_VIRTUAL_NETWORK_ID, + md8475a.ImsCscfStatus.SIPIDLE.value): + self.log.error('UE failed to register with the IMS server.') + return False + + return True diff --git a/acts/framework/acts/controllers/cellular_lib/LteSimulation.py b/acts/framework/acts/controllers/cellular_lib/LteSimulation.py new file mode 100644 index 0000000000..b4c84bb703 --- /dev/null +++ b/acts/framework/acts/controllers/cellular_lib/LteSimulation.py @@ -0,0 +1,1299 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from enum import Enum + +from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation +from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_ONLY + + +class TransmissionMode(Enum): + """ Transmission modes for LTE (e.g., TM1, TM4, ...) """ + TM1 = "TM1" + TM2 = "TM2" + TM3 = "TM3" + TM4 = "TM4" + TM7 = "TM7" + TM8 = "TM8" + TM9 = "TM9" + + +class MimoMode(Enum): + """ Mimo modes """ + MIMO_1x1 = "1x1" + MIMO_2x2 = "2x2" + MIMO_4x4 = "4x4" + + +class SchedulingMode(Enum): + """ Traffic scheduling modes (e.g., STATIC, DYNAMIC) """ + DYNAMIC = "DYNAMIC" + STATIC = "STATIC" + + +class DuplexMode(Enum): + """ DL/UL Duplex mode """ + FDD = "FDD" + TDD = "TDD" + +class ModulationType(Enum): + """DL/UL Modulation order.""" + QPSK = 'QPSK' + Q16 = '16QAM' + Q64 = '64QAM' + Q256 = '256QAM' + + +class LteSimulation(BaseSimulation): + """ Single-carrier LTE simulation. """ + + # Simulation config keywords contained in the test name + PARAM_FRAME_CONFIG = "tddconfig" + PARAM_BW = "bw" + PARAM_SCHEDULING = "scheduling" + PARAM_SCHEDULING_STATIC = "static" + PARAM_SCHEDULING_DYNAMIC = "dynamic" + PARAM_PATTERN = "pattern" + PARAM_TM = "tm" + PARAM_UL_PW = 'pul' + PARAM_DL_PW = 'pdl' + PARAM_BAND = "band" + PARAM_MIMO = "mimo" + PARAM_DL_MCS = 'dlmcs' + PARAM_UL_MCS = 'ulmcs' + PARAM_SSF = 'ssf' + PARAM_CFI = 'cfi' + PARAM_PAGING = 'paging' + PARAM_PHICH = 'phich' + PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer" + PARAM_DRX = 'drx' + + # Test config keywords + KEY_TBS_PATTERN = "tbs_pattern_on" + KEY_DL_256_QAM = "256_qam_dl" + KEY_UL_64_QAM = "64_qam_ul" + + # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY + DOWNLINK_SIGNAL_LEVEL_UNITS = "RSRP" + + # RSRP signal levels thresholds (as reported by Android) in dBm/15KHz. + # Excellent is set to -75 since callbox B Tx power is limited to -30 dBm + DOWNLINK_SIGNAL_LEVEL_DICTIONARY = { + 'excellent': -75, + 'high': -110, + 'medium': -115, + 'weak': -120 + } + + # Transmitted output power for the phone (dBm) + UPLINK_SIGNAL_LEVEL_DICTIONARY = { + 'max': 27, + 'high': 13, + 'medium': 3, + 'low': -20 + } + + # Bandwidth [MHz] to total RBs mapping + total_rbs_dictionary = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6} + + # Bandwidth [MHz] to RB group size + rbg_dictionary = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1} + + # Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE + min_dl_rbs_dictionary = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2} + + # Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE + min_ul_rbs_dictionary = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1} + + # Allowed bandwidth for each band. + allowed_bandwidth_dictionary = { + 1: [5, 10, 15, 20], + 2: [1.4, 3, 5, 10, 15, 20], + 3: [1.4, 3, 5, 10, 15, 20], + 4: [1.4, 3, 5, 10, 15, 20], + 5: [1.4, 3, 5, 10], + 7: [5, 10, 15, 20], + 8: [1.4, 3, 5, 10], + 10: [5, 10, 15, 20], + 11: [5, 10], + 12: [1.4, 3, 5, 10], + 13: [5, 10], + 14: [5, 10], + 17: [5, 10], + 18: [5, 10, 15], + 19: [5, 10, 15], + 20: [5, 10, 15, 20], + 21: [5, 10, 15], + 22: [5, 10, 15, 20], + 24: [5, 10], + 25: [1.4, 3, 5, 10, 15, 20], + 26: [1.4, 3, 5, 10, 15], + 27: [1.4, 3, 5, 10], + 28: [3, 5, 10, 15, 20], + 29: [3, 5, 10], + 30: [5, 10], + 31: [1.4, 3, 5], + 32: [5, 10, 15, 20], + 33: [5, 10, 15, 20], + 34: [5, 10, 15], + 35: [1.4, 3, 5, 10, 15, 20], + 36: [1.4, 3, 5, 10, 15, 20], + 37: [5, 10, 15, 20], + 38: [20], + 39: [5, 10, 15, 20], + 40: [5, 10, 15, 20], + 41: [5, 10, 15, 20], + 42: [5, 10, 15, 20], + 43: [5, 10, 15, 20], + 44: [3, 5, 10, 15, 20], + 45: [5, 10, 15, 20], + 46: [10, 20], + 47: [10, 20], + 48: [5, 10, 15, 20], + 49: [10, 20], + 50: [3, 5, 10, 15, 20], + 51: [3, 5], + 52: [5, 10, 15, 20], + 65: [5, 10, 15, 20], + 66: [1.4, 3, 5, 10, 15, 20], + 67: [5, 10, 15, 20], + 68: [5, 10, 15], + 69: [5], + 70: [5, 10, 15], + 71: [5, 10, 15, 20], + 72: [1.4, 3, 5], + 73: [1.4, 3, 5], + 74: [1.4, 3, 5, 10, 15, 20], + 75: [5, 10, 15, 20], + 76: [5], + 85: [5, 10], + 252: [20], + 255: [20] + } + + # Peak throughput lookup tables for each TDD subframe + # configuration and bandwidth + # yapf: disable + tdd_config4_tput_lut = { + 0: { + 5: {'DL': 3.82, 'UL': 2.63}, + 10: {'DL': 11.31,'UL': 9.03}, + 15: {'DL': 16.9, 'UL': 20.62}, + 20: {'DL': 22.88, 'UL': 28.43} + }, + 1: { + 5: {'DL': 6.13, 'UL': 4.08}, + 10: {'DL': 18.36, 'UL': 9.69}, + 15: {'DL': 28.62, 'UL': 14.21}, + 20: {'DL': 39.04, 'UL': 19.23} + }, + 2: { + 5: {'DL': 5.68, 'UL': 2.30}, + 10: {'DL': 25.51, 'UL': 4.68}, + 15: {'DL': 39.3, 'UL': 7.13}, + 20: {'DL': 53.64, 'UL': 9.72} + }, + 3: { + 5: {'DL': 8.26, 'UL': 3.45}, + 10: {'DL': 23.20, 'UL': 6.99}, + 15: {'DL': 35.35, 'UL': 10.75}, + 20: {'DL': 48.3, 'UL': 14.6} + }, + 4: { + 5: {'DL': 6.16, 'UL': 2.30}, + 10: {'DL': 26.77, 'UL': 4.68}, + 15: {'DL': 40.7, 'UL': 7.18}, + 20: {'DL': 55.6, 'UL': 9.73} + }, + 5: { + 5: {'DL': 6.91, 'UL': 1.12}, + 10: {'DL': 30.33, 'UL': 2.33}, + 15: {'DL': 46.04, 'UL': 3.54}, + 20: {'DL': 62.9, 'UL': 4.83} + }, + 6: { + 5: {'DL': 6.13, 'UL': 4.13}, + 10: {'DL': 14.79, 'UL': 11.98}, + 15: {'DL': 23.28, 'UL': 17.46}, + 20: {'DL': 31.75, 'UL': 23.95} + } + } + + tdd_config3_tput_lut = { + 0: { + 5: {'DL': 5.04, 'UL': 3.7}, + 10: {'DL': 15.11, 'UL': 17.56}, + 15: {'DL': 22.59, 'UL': 30.31}, + 20: {'DL': 30.41, 'UL': 41.61} + }, + 1: { + 5: {'DL': 8.07, 'UL': 5.66}, + 10: {'DL': 24.58, 'UL': 13.66}, + 15: {'DL': 39.05, 'UL': 20.68}, + 20: {'DL': 51.59, 'UL': 28.76} + }, + 2: { + 5: {'DL': 7.59, 'UL': 3.31}, + 10: {'DL': 34.08, 'UL': 6.93}, + 15: {'DL': 53.64, 'UL': 10.51}, + 20: {'DL': 70.55, 'UL': 14.41} + }, + 3: { + 5: {'DL': 10.9, 'UL': 5.0}, + 10: {'DL': 30.99, 'UL': 10.25}, + 15: {'DL': 48.3, 'UL': 15.81}, + 20: {'DL': 63.24, 'UL': 21.65} + }, + 4: { + 5: {'DL': 8.11, 'UL': 3.32}, + 10: {'DL': 35.74, 'UL': 6.95}, + 15: {'DL': 55.6, 'UL': 10.51}, + 20: {'DL': 72.72, 'UL': 14.41} + }, + 5: { + 5: {'DL': 9.28, 'UL': 1.57}, + 10: {'DL': 40.49, 'UL': 3.44}, + 15: {'DL': 62.9, 'UL': 5.23}, + 20: {'DL': 82.21, 'UL': 7.15} + }, + 6: { + 5: {'DL': 8.06, 'UL': 5.74}, + 10: {'DL': 19.82, 'UL': 17.51}, + 15: {'DL': 31.75, 'UL': 25.77}, + 20: {'DL': 42.12, 'UL': 34.91} + } + } + + tdd_config2_tput_lut = { + 0: { + 5: {'DL': 3.11, 'UL': 2.55}, + 10: {'DL': 9.93, 'UL': 11.1}, + 15: {'DL': 13.9, 'UL': 21.51}, + 20: {'DL': 20.02, 'UL': 41.66} + }, + 1: { + 5: {'DL': 5.33, 'UL': 4.27}, + 10: {'DL': 15.14, 'UL': 13.95}, + 15: {'DL': 33.84, 'UL': 19.73}, + 20: {'DL': 44.61, 'UL': 27.35} + }, + 2: { + 5: {'DL': 6.87, 'UL': 3.32}, + 10: {'DL': 17.06, 'UL': 6.76}, + 15: {'DL': 49.63, 'UL': 10.5}, + 20: {'DL': 65.2, 'UL': 14.41} + }, + 3: { + 5: {'DL': 5.41, 'UL': 4.17}, + 10: {'DL': 16.89, 'UL': 9.73}, + 15: {'DL': 44.29, 'UL': 15.7}, + 20: {'DL': 53.95, 'UL': 19.85} + }, + 4: { + 5: {'DL': 8.7, 'UL': 3.32}, + 10: {'DL': 17.58, 'UL': 6.76}, + 15: {'DL': 51.08, 'UL': 10.47}, + 20: {'DL': 66.45, 'UL': 14.38} + }, + 5: { + 5: {'DL': 9.46, 'UL': 1.55}, + 10: {'DL': 19.02, 'UL': 3.48}, + 15: {'DL': 58.89, 'UL': 5.23}, + 20: {'DL': 76.85, 'UL': 7.1} + }, + 6: { + 5: {'DL': 4.74, 'UL': 3.9}, + 10: {'DL': 12.32, 'UL': 13.37}, + 15: {'DL': 27.74, 'UL': 25.02}, + 20: {'DL': 35.48, 'UL': 32.95} + } + } + + tdd_config1_tput_lut = { + 0: { + 5: {'DL': 4.25, 'UL': 3.35}, + 10: {'DL': 8.38, 'UL': 7.22}, + 15: {'DL': 12.41, 'UL': 13.91}, + 20: {'DL': 16.27, 'UL': 24.09} + }, + 1: { + 5: {'DL': 7.28, 'UL': 4.61}, + 10: {'DL': 14.73, 'UL': 9.69}, + 15: {'DL': 21.91, 'UL': 13.86}, + 20: {'DL': 27.63, 'UL': 17.18} + }, + 2: { + 5: {'DL': 10.37, 'UL': 2.27}, + 10: {'DL': 20.92, 'UL': 4.66}, + 15: {'DL': 31.01, 'UL': 7.04}, + 20: {'DL': 42.03, 'UL': 9.75} + }, + 3: { + 5: {'DL': 9.25, 'UL': 3.44}, + 10: {'DL': 18.38, 'UL': 6.95}, + 15: {'DL': 27.59, 'UL': 10.62}, + 20: {'DL': 34.85, 'UL': 13.45} + }, + 4: { + 5: {'DL': 10.71, 'UL': 2.26}, + 10: {'DL': 21.54, 'UL': 4.67}, + 15: {'DL': 31.91, 'UL': 7.2}, + 20: {'DL': 43.35, 'UL': 9.74} + }, + 5: { + 5: {'DL': 12.34, 'UL': 1.08}, + 10: {'DL': 24.78, 'UL': 2.34}, + 15: {'DL': 36.68, 'UL': 3.57}, + 20: {'DL': 49.84, 'UL': 4.81} + }, + 6: { + 5: {'DL': 5.76, 'UL': 4.41}, + 10: {'DL': 11.68, 'UL': 9.7}, + 15: {'DL': 17.34, 'UL': 17.95}, + 20: {'DL': 23.5, 'UL': 23.42} + } + } + # yapf: enable + + # Peak throughput lookup table dictionary + tdd_config_tput_lut_dict = { + 'TDD_CONFIG1': + tdd_config1_tput_lut, # DL 256QAM, UL 64QAM & TBS turned OFF + 'TDD_CONFIG2': + tdd_config2_tput_lut, # DL 256QAM, UL 64 QAM turned ON & TBS OFF + 'TDD_CONFIG3': + tdd_config3_tput_lut, # DL 256QAM, UL 64QAM & TBS turned ON + 'TDD_CONFIG4': + tdd_config4_tput_lut # DL 256QAM, UL 64 QAM turned OFF & TBS ON + } + + class BtsConfig(BaseSimulation.BtsConfig): + """ Extension of the BaseBtsConfig to implement parameters that are + exclusive to LTE. + + Attributes: + band: an integer indicating the required band number. + dlul_config: an integer indicating the TDD config number. + ssf_config: an integer indicating the Special Sub-Frame config. + bandwidth: a float indicating the required channel bandwidth. + mimo_mode: an instance of LteSimulation.MimoMode indicating the + required MIMO mode for the downlink signal. + transmission_mode: an instance of LteSimulation.TransmissionMode + indicating the required TM. + scheduling_mode: an instance of LteSimulation.SchedulingMode + indicating wether to use Static or Dynamic scheduling. + dl_rbs: an integer indicating the number of downlink RBs + ul_rbs: an integer indicating the number of uplink RBs + dl_mcs: an integer indicating the MCS for the downlink signal + ul_mcs: an integer indicating the MCS for the uplink signal + dl_modulation_order: a string indicating a DL modulation scheme + ul_modulation_order: a string indicating an UL modulation scheme + tbs_pattern_on: a boolean indicating whether full allocation mode + should be used or not + dl_channel: an integer indicating the downlink channel number + cfi: an integer indicating the Control Format Indicator + paging_cycle: an integer indicating the paging cycle duration in + milliseconds + phich: a string indicating the PHICH group size parameter + drx_connected_mode: a boolean indicating whether cDRX mode is + on or off + drx_on_duration_timer: number of PDCCH subframes representing + DRX on duration + drx_inactivity_timer: number of PDCCH subframes to wait before + entering DRX mode + drx_retransmission_timer: number of consecutive PDCCH subframes + to wait for retransmission + drx_long_cycle: number of subframes representing one long DRX cycle. + One cycle consists of DRX sleep + DRX on duration + drx_long_cycle_offset: number representing offset in range + 0 to drx_long_cycle - 1 + """ + def __init__(self): + """ Initialize the base station config by setting all its + parameters to None. """ + super().__init__() + self.band = None + self.dlul_config = None + self.ssf_config = None + self.bandwidth = None + self.mimo_mode = None + self.transmission_mode = None + self.scheduling_mode = None + self.dl_rbs = None + self.ul_rbs = None + self.dl_mcs = None + self.ul_mcs = None + self.dl_modulation_order = None + self.ul_modulation_order = None + self.tbs_pattern_on = None + self.dl_channel = None + self.cfi = None + self.paging_cycle = None + self.phich = None + self.drx_connected_mode = None + self.drx_on_duration_timer = None + self.drx_inactivity_timer = None + self.drx_retransmission_timer = None + self.drx_long_cycle = None + self.drx_long_cycle_offset = None + + def __init__(self, simulator, log, dut, test_config, calibration_table): + """ Initializes the simulator for a single-carrier LTE simulation. + + Loads a simple LTE simulation enviroment with 1 basestation. + + Args: + simulator: a cellular simulator controller + log: a logger handle + dut: the android device handler + test_config: test configuration obtained from the config file + calibration_table: a dictionary containing path losses for + different bands. + + """ + + super().__init__(simulator, log, dut, test_config, calibration_table) + + if not dut.droid.telephonySetPreferredNetworkTypesForSubscription( + NETWORK_MODE_LTE_ONLY, + dut.droid.subscriptionGetDefaultSubId()): + log.error("Couldn't set preferred network type.") + else: + log.info("Preferred network type set.") + + # Get TBS pattern setting from the test configuration + if self.KEY_TBS_PATTERN not in test_config: + self.log.warning("The key '{}' is not set in the config file. " + "Setting to true by default.".format( + self.KEY_TBS_PATTERN)) + self.primary_config.tbs_pattern_on = test_config.get( + self.KEY_TBS_PATTERN, True) + + # Get the 256-QAM setting from the test configuration + if self.KEY_DL_256_QAM not in test_config: + self.log.warning("The key '{}' is not set in the config file. " + "Setting to false by default.".format( + self.KEY_DL_256_QAM)) + + self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False) + + if self.dl_256_qam: + if not self.simulator.LTE_SUPPORTS_DL_256QAM: + self.log.warning("The key '{}' is set to true but the " + "simulator doesn't support that modulation " + "order.".format(self.KEY_DL_256_QAM)) + self.dl_256_qam = False + else: + self.primary_config.dl_modulation_order = ModulationType.Q256 + + else: + self.log.warning('dl modulation 256QAM is not specified in config, ' + 'setting to default value 64QAM') + self.primary_config.dl_modulation_order = ModulationType.Q64 + # Get the 64-QAM setting from the test configuration + if self.KEY_UL_64_QAM not in test_config: + self.log.warning("The key '{}' is not set in the config file. " + "Setting to false by default.".format( + self.KEY_UL_64_QAM)) + + self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False) + + if self.ul_64_qam: + if not self.simulator.LTE_SUPPORTS_UL_64QAM: + self.log.warning("The key '{}' is set to true but the " + "simulator doesn't support that modulation " + "order.".format(self.KEY_UL_64_QAM)) + self.ul_64_qam = False + else: + self.primary_config.ul_modulation_order = ModulationType.Q64 + else: + self.log.warning('ul modulation 64QAM is not specified in config, ' + 'setting to default value 16QAM') + self.primary_config.ul_modulation_order = ModulationType.Q16 + + self.simulator.configure_bts(self.primary_config) + + def setup_simulator(self): + """ Do initial configuration in the simulator. """ + self.simulator.setup_lte_scenario() + + def parse_parameters(self, parameters): + """ Configs an LTE simulation using a list of parameters. + + Calls the parent method first, then consumes parameters specific to LTE. + + Args: + parameters: list of parameters + """ + + # Instantiate a new configuration object + new_config = self.BtsConfig() + + # Setup band + + values = self.consume_parameter(parameters, self.PARAM_BAND, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter '{}' followed by " + "the required band number.".format(self.PARAM_BAND)) + + new_config.band = values[1] + + # Set TDD-only configs + if self.get_duplex_mode(new_config.band) == DuplexMode.TDD: + + # Sub-frame DL/UL config + values = self.consume_parameter(parameters, + self.PARAM_FRAME_CONFIG, 1) + if not values: + raise ValueError( + "When a TDD band is selected the frame " + "structure has to be indicated with the '{}' " + "parameter followed by a number from 0 to 6.".format( + self.PARAM_FRAME_CONFIG)) + + new_config.dlul_config = int(values[1]) + + # Special Sub-Frame configuration + values = self.consume_parameter(parameters, self.PARAM_SSF, 1) + + if not values: + self.log.warning( + 'The {} parameter was not provided. Setting ' + 'Special Sub-Frame config to 6 by default.'.format( + self.PARAM_SSF)) + new_config.ssf_config = 6 + else: + new_config.ssf_config = int(values[1]) + + # Setup bandwidth + + values = self.consume_parameter(parameters, self.PARAM_BW, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter {} followed by an " + "int value (to indicate 1.4 MHz use 14).".format( + self.PARAM_BW)) + + bw = float(values[1]) + + if bw == 14: + bw = 1.4 + + new_config.bandwidth = bw + + # Setup mimo mode + + values = self.consume_parameter(parameters, self.PARAM_MIMO, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter '{}' followed by the " + "mimo mode.".format(self.PARAM_MIMO)) + + for mimo_mode in MimoMode: + if values[1] == mimo_mode.value: + new_config.mimo_mode = mimo_mode + break + else: + raise ValueError("The {} parameter needs to be followed by either " + "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO)) + + if (new_config.mimo_mode == MimoMode.MIMO_4x4 + and not self.simulator.LTE_SUPPORTS_4X4_MIMO): + raise ValueError("The test requires 4x4 MIMO, but that is not " + "supported by the cellular simulator.") + + # Setup transmission mode + + values = self.consume_parameter(parameters, self.PARAM_TM, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter {} followed by an " + "int value from 1 to 4 indicating transmission mode.".format( + self.PARAM_TM)) + + for tm in TransmissionMode: + if values[1] == tm.value[2:]: + new_config.transmission_mode = tm + break + else: + raise ValueError("The {} parameter needs to be followed by either " + "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format( + self.PARAM_MIMO)) + + # Setup scheduling mode + + values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1) + + if not values: + new_config.scheduling_mode = SchedulingMode.STATIC + self.log.warning( + "The test name does not include the '{}' parameter. Setting to " + "static by default.".format(self.PARAM_SCHEDULING)) + elif values[1] == self.PARAM_SCHEDULING_DYNAMIC: + new_config.scheduling_mode = SchedulingMode.DYNAMIC + elif values[1] == self.PARAM_SCHEDULING_STATIC: + new_config.scheduling_mode = SchedulingMode.STATIC + else: + raise ValueError( + "The test name parameter '{}' has to be followed by either " + "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING)) + + if new_config.scheduling_mode == SchedulingMode.STATIC: + + values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2) + + if not values: + self.log.warning( + "The '{}' parameter was not set, using 100% RBs for both " + "DL and UL. To set the percentages of total RBs include " + "the '{}' parameter followed by two ints separated by an " + "underscore indicating downlink and uplink percentages.". + format(self.PARAM_PATTERN, self.PARAM_PATTERN)) + dl_pattern = 100 + ul_pattern = 100 + else: + dl_pattern = int(values[1]) + ul_pattern = int(values[2]) + + if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100): + raise ValueError( + "The scheduling pattern parameters need to be two " + "positive numbers between 0 and 100.") + + new_config.dl_rbs, new_config.ul_rbs = ( + self.allocation_percentages_to_rbs( + new_config.bandwidth, new_config.transmission_mode, + dl_pattern, ul_pattern)) + + # Look for a DL MCS configuration in the test parameters. If it is + # not present, use a default value. + dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, 1) + + if dlmcs: + new_config.dl_mcs = int(dlmcs[1]) + else: + self.log.warning( + 'The test name does not include the {} parameter. Setting ' + 'to the max value by default'.format(self.PARAM_DL_MCS)) + if self.dl_256_qam and new_config.bandwidth == 1.4: + new_config.dl_mcs = 26 + elif (not self.dl_256_qam + and self.primary_config.tbs_pattern_on + and new_config.bandwidth != 1.4): + new_config.dl_mcs = 28 + else: + new_config.dl_mcs = 27 + + # Look for an UL MCS configuration in the test parameters. If it is + # not present, use a default value. + ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, 1) + + if ulmcs: + new_config.ul_mcs = int(ulmcs[1]) + else: + self.log.warning( + 'The test name does not include the {} parameter. Setting ' + 'to the max value by default'.format(self.PARAM_UL_MCS)) + if self.ul_64_qam: + new_config.ul_mcs = 28 + else: + new_config.ul_mcs = 23 + + # Configure the simulation for DRX mode + + drx = self.consume_parameter(parameters, self.PARAM_DRX, 5) + + if drx and len(drx) == 6: + new_config.drx_connected_mode = True + new_config.drx_on_duration_timer = drx[1] + new_config.drx_inactivity_timer = drx[2] + new_config.drx_retransmission_timer = drx[3] + new_config.drx_long_cycle = drx[4] + try: + long_cycle = int(drx[4]) + long_cycle_offset = int(drx[5]) + if long_cycle_offset in range(0, long_cycle): + new_config.drx_long_cycle_offset = long_cycle_offset + else: + self.log.error(("The cDRX long cycle offset must be in the " + "range 0 to (long cycle - 1). Setting " + "long cycle offset to 0")) + new_config.drx_long_cycle_offset = 0 + + except ValueError: + self.log.error(("cDRX long cycle and long cycle offset " + "must be integers. Disabling cDRX mode.")) + new_config.drx_connected_mode = False + else: + self.log.warning(("DRX mode was not configured properly. " + "Please provide the following 5 values: " + "1) DRX on duration timer " + "2) Inactivity timer " + "3) Retransmission timer " + "4) Long DRX cycle duration " + "5) Long DRX cycle offset " + "Example: drx_2_6_16_20_0")) + + # Setup LTE RRC status change function and timer for LTE idle test case + values = self.consume_parameter(parameters, + self.PARAM_RRC_STATUS_CHANGE_TIMER, 1) + if not values: + self.log.info( + "The test name does not include the '{}' parameter. Disabled " + "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER)) + self.simulator.set_lte_rrc_state_change_timer(False) + else: + timer = int(values[1]) + self.simulator.set_lte_rrc_state_change_timer(True, timer) + self.rrc_sc_timer = timer + + # Channel Control Indicator + values = self.consume_parameter(parameters, self.PARAM_CFI, 1) + + if not values: + self.log.warning('The {} parameter was not provided. Setting ' + 'CFI to BESTEFFORT.'.format(self.PARAM_CFI)) + new_config.cfi = 'BESTEFFORT' + else: + new_config.cfi = values[1] + + # PHICH group size + values = self.consume_parameter(parameters, self.PARAM_PHICH, 1) + + if not values: + self.log.warning('The {} parameter was not provided. Setting ' + 'PHICH group size to 1 by default.'.format( + self.PARAM_PHICH)) + new_config.phich = '1' + else: + if values[1] == '16': + new_config.phich = '1/6' + elif values[1] == '12': + new_config.phich = '1/2' + elif values[1] in ['1/6', '1/2', '1', '2']: + new_config.phich = values[1] + else: + raise ValueError('The {} parameter can only be followed by 1,' + '2, 1/2 (or 12) and 1/6 (or 16).'.format( + self.PARAM_PHICH)) + + # Paging cycle duration + values = self.consume_parameter(parameters, self.PARAM_PAGING, 1) + + if not values: + self.log.warning('The {} parameter was not provided. Setting ' + 'paging cycle duration to 1280 ms by ' + 'default.'.format(self.PARAM_PAGING)) + new_config.paging_cycle = 1280 + else: + try: + new_config.paging_cycle = int(values[1]) + except ValueError: + raise ValueError( + 'The {} parameter has to be followed by the paging cycle ' + 'duration in milliseconds.'.format(self.PARAM_PAGING)) + + # Get uplink power + + ul_power = self.get_uplink_power_from_parameters(parameters) + + # Power is not set on the callbox until after the simulation is + # started. Saving this value in a variable for later + self.sim_ul_power = ul_power + + # Get downlink power + + dl_power = self.get_downlink_power_from_parameters(parameters) + + # Power is not set on the callbox until after the simulation is + # started. Saving this value in a variable for later + self.sim_dl_power = dl_power + + # Setup the base station with the obtained configuration and then save + # these parameters in the current configuration object + self.simulator.configure_bts(new_config) + self.primary_config.incorporate(new_config) + + # Now that the band is set, calibrate the link if necessary + self.load_pathloss_if_required() + + def calibrated_downlink_rx_power(self, bts_config, rsrp): + """ LTE simulation overrides this method so that it can convert from + RSRP to total signal power transmitted from the basestation. + + Args: + bts_config: the current configuration at the base station + rsrp: desired rsrp, contained in a key value pair + """ + + power = self.rsrp_to_signal_power(rsrp, bts_config) + + self.log.info( + "Setting downlink signal level to {} RSRP ({} dBm)".format( + rsrp, power)) + + # Use parent method to calculate signal level + return super().calibrated_downlink_rx_power(bts_config, power) + + def downlink_calibration(self, rat=None, power_units_conversion_func=None): + """ Computes downlink path loss and returns the calibration value. + + See base class implementation for details. + + Args: + rat: ignored, replaced by 'lteRsrp' + power_units_conversion_func: ignored, replaced by + self.rsrp_to_signal_power + + Returns: + Dowlink calibration value and measured DL power. Note that the + phone only reports RSRP of the primary chain + """ + + return super().downlink_calibration( + rat='lteDbm', + power_units_conversion_func=self.rsrp_to_signal_power) + + def rsrp_to_signal_power(self, rsrp, bts_config): + """ Converts rsrp to total band signal power + + RSRP is measured per subcarrier, so total band power needs to be + multiplied by the number of subcarriers being used. + + Args: + rsrp: desired rsrp in dBm + bts_config: a base station configuration object + Returns: + Total band signal power in dBm + """ + + bandwidth = bts_config.bandwidth + + if bandwidth == 20: # 100 RBs + power = rsrp + 30.79 + elif bandwidth == 15: # 75 RBs + power = rsrp + 29.54 + elif bandwidth == 10: # 50 RBs + power = rsrp + 27.78 + elif bandwidth == 5: # 25 RBs + power = rsrp + 24.77 + elif bandwidth == 3: # 15 RBs + power = rsrp + 22.55 + elif bandwidth == 1.4: # 6 RBs + power = rsrp + 18.57 + else: + raise ValueError("Invalid bandwidth value.") + + return power + + def maximum_downlink_throughput(self): + """ Calculates maximum achievable downlink throughput in the current + simulation state. + + Returns: + Maximum throughput in mbps. + + """ + + return self.bts_maximum_downlink_throughtput(self.primary_config) + + def bts_maximum_downlink_throughtput(self, bts_config): + """ Calculates maximum achievable downlink throughput for a single + base station from its configuration object. + + Args: + bts_config: a base station configuration object. + + Returns: + Maximum throughput in mbps. + + """ + if bts_config.mimo_mode == MimoMode.MIMO_1x1: + streams = 1 + elif bts_config.mimo_mode == MimoMode.MIMO_2x2: + streams = 2 + elif bts_config.mimo_mode == MimoMode.MIMO_4x4: + streams = 4 + else: + raise ValueError('Unable to calculate maximum downlink throughput ' + 'because the MIMO mode has not been set.') + + bandwidth = bts_config.bandwidth + rb_ratio = bts_config.dl_rbs / self.total_rbs_dictionary[bandwidth] + mcs = bts_config.dl_mcs + + max_rate_per_stream = None + + tdd_subframe_config = bts_config.dlul_config + duplex_mode = self.get_duplex_mode(bts_config.band) + + if duplex_mode == DuplexMode.TDD: + if self.dl_256_qam: + if mcs == 27: + if bts_config.tbs_pattern_on: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG3'][tdd_subframe_config][bandwidth][ + 'DL'] + else: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG2'][tdd_subframe_config][bandwidth][ + 'DL'] + else: + if mcs == 28: + if bts_config.tbs_pattern_on: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG4'][tdd_subframe_config][bandwidth][ + 'DL'] + else: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG1'][tdd_subframe_config][bandwidth][ + 'DL'] + + elif duplex_mode == DuplexMode.FDD: + if (not self.dl_256_qam and bts_config.tbs_pattern_on + and mcs == 28): + max_rate_per_stream = { + 3: 9.96, + 5: 17.0, + 10: 34.7, + 15: 52.7, + 20: 72.2 + }.get(bandwidth, None) + if (not self.dl_256_qam and bts_config.tbs_pattern_on + and mcs == 27): + max_rate_per_stream = { + 1.4: 2.94, + }.get(bandwidth, None) + elif (not self.dl_256_qam and not bts_config.tbs_pattern_on + and mcs == 27): + max_rate_per_stream = { + 1.4: 2.87, + 3: 7.7, + 5: 14.4, + 10: 28.7, + 15: 42.3, + 20: 57.7 + }.get(bandwidth, None) + elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 27: + max_rate_per_stream = { + 3: 13.2, + 5: 22.9, + 10: 46.3, + 15: 72.2, + 20: 93.9 + }.get(bandwidth, None) + elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 26: + max_rate_per_stream = { + 1.4: 3.96, + }.get(bandwidth, None) + elif (self.dl_256_qam and not bts_config.tbs_pattern_on + and mcs == 27): + max_rate_per_stream = { + 3: 11.3, + 5: 19.8, + 10: 44.1, + 15: 68.1, + 20: 88.4 + }.get(bandwidth, None) + elif (self.dl_256_qam and not bts_config.tbs_pattern_on + and mcs == 26): + max_rate_per_stream = { + 1.4: 3.96, + }.get(bandwidth, None) + + if not max_rate_per_stream: + raise NotImplementedError( + "The calculation for tbs pattern = {} " + "and mcs = {} is not implemented.".format( + "FULLALLOCATION" if bts_config.tbs_pattern_on else "OFF", + mcs)) + + return max_rate_per_stream * streams * rb_ratio + + def maximum_uplink_throughput(self): + """ Calculates maximum achievable uplink throughput in the current + simulation state. + + Returns: + Maximum throughput in mbps. + + """ + + return self.bts_maximum_uplink_throughtput(self.primary_config) + + def bts_maximum_uplink_throughtput(self, bts_config): + """ Calculates maximum achievable uplink throughput for the selected + basestation from its configuration object. + + Args: + bts_config: an LTE base station configuration object. + + Returns: + Maximum throughput in mbps. + + """ + + bandwidth = bts_config.bandwidth + rb_ratio = bts_config.ul_rbs / self.total_rbs_dictionary[bandwidth] + mcs = bts_config.ul_mcs + + max_rate_per_stream = None + + tdd_subframe_config = bts_config.dlul_config + duplex_mode = self.get_duplex_mode(bts_config.band) + + if duplex_mode == DuplexMode.TDD: + if self.ul_64_qam: + if mcs == 28: + if bts_config.tbs_pattern_on: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG3'][tdd_subframe_config][bandwidth][ + 'UL'] + else: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG2'][tdd_subframe_config][bandwidth][ + 'UL'] + else: + if mcs == 23: + if bts_config.tbs_pattern_on: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG4'][tdd_subframe_config][bandwidth][ + 'UL'] + else: + max_rate_per_stream = self.tdd_config_tput_lut_dict[ + 'TDD_CONFIG1'][tdd_subframe_config][bandwidth][ + 'UL'] + + elif duplex_mode == DuplexMode.FDD: + if mcs == 23 and not self.ul_64_qam: + max_rate_per_stream = { + 1.4: 2.85, + 3: 7.18, + 5: 12.1, + 10: 24.5, + 15: 36.5, + 20: 49.1 + }.get(bandwidth, None) + elif mcs == 28 and self.ul_64_qam: + max_rate_per_stream = { + 1.4: 4.2, + 3: 10.5, + 5: 17.2, + 10: 35.3, + 15: 53.0, + 20: 72.6 + }.get(bandwidth, None) + + if not max_rate_per_stream: + raise NotImplementedError( + "The calculation fir mcs = {} is not implemented.".format( + "FULLALLOCATION" if bts_config.tbs_pattern_on else "OFF", + mcs)) + + return max_rate_per_stream * rb_ratio + + def allocation_percentages_to_rbs(self, bw, tm, dl, ul): + """ Converts usage percentages to number of DL/UL RBs + + Because not any number of DL/UL RBs can be obtained for a certain + bandwidth, this function calculates the number of RBs that most + closely matches the desired DL/UL percentages. + + Args: + bw: the bandwidth for the which the RB configuration is requested + tm: the transmission in which the base station will be operating + dl: desired percentage of downlink RBs + ul: desired percentage of uplink RBs + Returns: + a tuple indicating the number of downlink and uplink RBs + """ + + # Validate the arguments + if (not 0 <= dl <= 100) or (not 0 <= ul <= 100): + raise ValueError("The percentage of DL and UL RBs have to be two " + "positive between 0 and 100.") + + # Get min and max values from tables + max_rbs = self.total_rbs_dictionary[bw] + min_dl_rbs = self.min_dl_rbs_dictionary[bw] + min_ul_rbs = self.min_ul_rbs_dictionary[bw] + + def percentage_to_amount(min_val, max_val, percentage): + """ Returns the integer between min_val and max_val that is closest + to percentage/100*max_val + """ + + # Calculate the value that corresponds to the required percentage. + closest_int = round(max_val * percentage / 100) + # Cannot be less than min_val + closest_int = max(closest_int, min_val) + # RBs cannot be more than max_rbs + closest_int = min(closest_int, max_val) + + return closest_int + + # Calculate the number of DL RBs + + # Get the number of DL RBs that corresponds to + # the required percentage. + desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs, + max_val=max_rbs, + percentage=dl) + + if tm == TransmissionMode.TM3 or tm == TransmissionMode.TM4: + + # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a + # multiple of the RBG size + + if desired_dl_rbs == max_rbs: + dl_rbs = max_rbs + else: + dl_rbs = (math.ceil(desired_dl_rbs / self.rbg_dictionary[bw]) * + self.rbg_dictionary[bw]) + + else: + # The other TMs allow any number of RBs between 1 and max_rbs + dl_rbs = desired_dl_rbs + + # Calculate the number of UL RBs + + # Get the number of UL RBs that corresponds + # to the required percentage + desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs, + max_val=max_rbs, + percentage=ul) + + # Create a list of all possible UL RBs assignment + # The standard allows any number that can be written as + # 2**a * 3**b * 5**c for any combination of a, b and c. + + def pow_range(max_value, base): + """ Returns a range of all possible powers of base under + the given max_value. + """ + return range(int(math.ceil(math.log(max_value, base)))) + + possible_ul_rbs = [ + 2**a * 3**b * 5**c for a in pow_range(max_rbs, 2) + for b in pow_range(max_rbs, 3) + for c in pow_range(max_rbs, 5) + if 2**a * 3**b * 5**c <= max_rbs] # yapf: disable + + # Find the value in the list that is closest to desired_ul_rbs + differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs] + ul_rbs = possible_ul_rbs[differences.index(min(differences))] + + # Report what are the obtained RB percentages + self.log.info("Requested a {}% / {}% RB allocation. Closest possible " + "percentages are {}% / {}%.".format( + dl, ul, round(100 * dl_rbs / max_rbs), + round(100 * ul_rbs / max_rbs))) + + return dl_rbs, ul_rbs + + def calibrate(self, band): + """ Calculates UL and DL path loss if it wasn't done before + + Before running the base class implementation, configure the base station + to only use one downlink antenna with maximum bandwidth. + + Args: + band: the band that is currently being calibrated. + """ + + # Save initial values in a configuration object so they can be restored + restore_config = self.BtsConfig() + restore_config.mimo_mode = self.primary_config.mimo_mode + restore_config.transmission_mode = self.primary_config.transmission_mode + restore_config.bandwidth = self.primary_config.bandwidth + + # Set up a temporary calibration configuration. + temporary_config = self.BtsConfig() + temporary_config.mimo_mode = MimoMode.MIMO_1x1 + temporary_config.transmission_mode = TransmissionMode.TM1 + temporary_config.bandwidth = max( + self.allowed_bandwidth_dictionary[int(band)]) + self.simulator.configure_bts(temporary_config) + self.primary_config.incorporate(temporary_config) + + super().calibrate(band) + + # Restore values as they were before changing them for calibration. + self.simulator.configure_bts(restore_config) + self.primary_config.incorporate(restore_config) + + def start_traffic_for_calibration(self): + """ + If TBS pattern is set to full allocation, there is no need to start + IP traffic. + """ + if not self.primary_config.tbs_pattern_on: + super().start_traffic_for_calibration() + + def stop_traffic_for_calibration(self): + """ + If TBS pattern is set to full allocation, IP traffic wasn't started + """ + if not self.primary_config.tbs_pattern_on: + super().stop_traffic_for_calibration() + + def get_duplex_mode(self, band): + """ Determines if the band uses FDD or TDD duplex mode + + Args: + band: a band number + Returns: + an variable of class DuplexMode indicating if band is FDD or TDD + """ + + if 33 <= int(band) <= 46: + return DuplexMode.TDD + else: + return DuplexMode.FDD + + def get_measured_ul_power(self, samples=5, wait_after_sample=3): + """ Calculates UL power using measurements from the callbox and the + calibration data. + + Args: + samples: the numble of samples to average + wait_after_sample: time in seconds to wait in between samples + + Returns: + the ul power at the UE antenna ports in dBs + """ + ul_power_sum = 0 + samples_left = samples + + while samples_left > 0: + ul_power_sum += self.simulator.get_measured_pusch_power() + samples_left -= 1 + time.sleep(wait_after_sample) + + # Got enough samples, return calibrated average + if self.dl_path_loss: + return ul_power_sum / samples + self.ul_path_loss + else: + self.log.warning('No uplink calibration data. Returning ' + 'uncalibrated values as measured by the ' + 'callbox.') + return ul_power_sum / samples diff --git a/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py b/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py new file mode 100644 index 0000000000..4a2619cf58 --- /dev/null +++ b/acts/framework/acts/controllers/cellular_lib/UmtsSimulation.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 - The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import ntpath +import time + +from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim +from acts.controllers.anritsu_lib.md8475a import BtsNumber +from acts.controllers.anritsu_lib.md8475a import BtsPacketRate +from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation +from acts.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY + + +class UmtsSimulation(BaseSimulation): + """ Single base station simulation. """ + + # Simulation config files in the callbox computer. + # These should be replaced in the future by setting up + # the same configuration manually. + + UMTS_BASIC_SIM_FILE = 'SIM_default_WCDMA.wnssp' + + UMTS_R99_CELL_FILE = 'CELL_WCDMA_R99_config.wnscp' + + UMTS_R7_CELL_FILE = 'CELL_WCDMA_R7_config.wnscp' + + UMTS_R8_CELL_FILE = 'CELL_WCDMA_R8_config.wnscp' + + # Test name parameters + PARAM_RELEASE_VERSION = "r" + PARAM_RELEASE_VERSION_99 = "99" + PARAM_RELEASE_VERSION_8 = "8" + PARAM_RELEASE_VERSION_7 = "7" + PARAM_UL_PW = 'pul' + PARAM_DL_PW = 'pdl' + PARAM_BAND = "band" + PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer" + + # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY + DOWNLINK_SIGNAL_LEVEL_UNITS = "RSCP" + + # RSCP signal levels thresholds (as reported by Android). Units are dBm + # Using LTE thresholds + 24 dB to have equivalent SPD + # 24 dB comes from 10 * log10(3.84 MHz / 15 KHz) + + DOWNLINK_SIGNAL_LEVEL_DICTIONARY = { + 'excellent': -51, + 'high': -76, + 'medium': -86, + 'weak': -96 + } + + # Transmitted output power for the phone + # Stronger Tx power means that the signal received by the BTS is weaker + # Units are dBm + + UPLINK_SIGNAL_LEVEL_DICTIONARY = { + 'low': -20, + 'medium': 8, + 'high': 15, + 'max': 23 + } + + # Converts packet rate to the throughput that can be actually obtained in + # Mbits/s + + packet_rate_to_dl_throughput = { + BtsPacketRate.WCDMA_DL384K_UL64K: 0.362, + BtsPacketRate.WCDMA_DL21_6M_UL5_76M: 18.5, + BtsPacketRate.WCDMA_DL43_2M_UL5_76M: 36.9 + } + + packet_rate_to_ul_throughput = { + BtsPacketRate.WCDMA_DL384K_UL64K: 0.0601, + BtsPacketRate.WCDMA_DL21_6M_UL5_76M: 5.25, + BtsPacketRate.WCDMA_DL43_2M_UL5_76M: 5.25 + } + + def __init__(self, simulator, log, dut, test_config, calibration_table): + """ Initializes the cellular simulator for a UMTS simulation. + + Loads a simple UMTS simulation enviroment with 1 basestation. It also + creates the BTS handle so we can change the parameters as desired. + + Args: + simulator: a cellular simulator controller + log: a logger handle + dut: the android device handler + test_config: test configuration obtained from the config file + calibration_table: a dictionary containing path losses for + different bands. + + """ + # The UMTS simulation relies on the cellular simulator to be a MD8475 + if not isinstance(self.simulator, anritsusim.MD8475CellularSimulator): + raise ValueError('The UMTS simulation relies on the simulator to ' + 'be an Anritsu MD8475 A/B instrument.') + + # The Anritsu controller needs to be unwrapped before calling + # super().__init__ because setup_simulator() requires self.anritsu and + # will be called during the parent class initialization. + self.anritsu = self.simulator.anritsu + self.bts1 = self.anritsu.get_BTS(BtsNumber.BTS1) + + super().__init__(simulator, log, dut, test_config, calibration_table) + + if not dut.droid.telephonySetPreferredNetworkTypesForSubscription( + NETWORK_MODE_WCDMA_ONLY, + dut.droid.subscriptionGetDefaultSubId()): + log.error("Coold not set preferred network type.") + else: + log.info("Preferred network type set.") + + self.release_version = None + self.packet_rate = None + + def setup_simulator(self): + """ Do initial configuration in the simulator. """ + + # Load callbox config files + callbox_config_path = self.CALLBOX_PATH_FORMAT_STR.format( + self.anritsu._md8475_version) + + self.anritsu.load_simulation_paramfile( + ntpath.join(callbox_config_path, self.UMTS_BASIC_SIM_FILE)) + + # Start simulation if it wasn't started + self.anritsu.start_simulation() + + def parse_parameters(self, parameters): + """ Configs an UMTS simulation using a list of parameters. + + Calls the parent method and consumes parameters specific to UMTS. + + Args: + parameters: list of parameters + """ + + # Setup band + + values = self.consume_parameter(parameters, self.PARAM_BAND, 1) + + if not values: + raise ValueError( + "The test name needs to include parameter '{}' followed by " + "the required band number.".format(self.PARAM_BAND)) + + self.set_band(self.bts1, values[1]) + self.load_pathloss_if_required() + + # Setup release version + + values = self.consume_parameter(parameters, self.PARAM_RELEASE_VERSION, + 1) + + if not values or values[1] not in [ + self.PARAM_RELEASE_VERSION_7, self.PARAM_RELEASE_VERSION_8, + self.PARAM_RELEASE_VERSION_99 + ]: + raise ValueError( + "The test name needs to include the parameter {} followed by a " + "valid release version.".format(self.PARAM_RELEASE_VERSION)) + + self.set_release_version(self.bts1, values[1]) + + # Setup W-CDMA RRC status change and CELL_DCH timer for idle test case + + values = self.consume_parameter(parameters, + self.PARAM_RRC_STATUS_CHANGE_TIMER, 1) + if not values: + self.log.info( + "The test name does not include the '{}' parameter. Disabled " + "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER)) + self.anritsu.set_umts_rrc_status_change(False) + else: + self.rrc_sc_timer = int(values[1]) + self.anritsu.set_umts_rrc_status_change(True) + self.anritsu.set_umts_dch_stat_timer(self.rrc_sc_timer) + + # Setup uplink power + + ul_power = self.get_uplink_power_from_parameters(parameters) + + # Power is not set on the callbox until after the simulation is + # started. Saving this value in a variable for later + self.sim_ul_power = ul_power + + # Setup downlink power + + dl_power = self.get_downlink_power_from_parameters(parameters) + + # Power is not set on the callbox until after the simulation is + # started. Saving this value in a variable for later + self.sim_dl_power = dl_power + + def set_release_version(self, bts, release_version): + """ Sets the release version. + + Loads the cell parameter file matching the requested release version. + Does nothing is release version is already the one requested. + + """ + + if release_version == self.release_version: + self.log.info( + "Release version is already {}.".format(release_version)) + return + if release_version == self.PARAM_RELEASE_VERSION_99: + + cell_parameter_file = self.UMTS_R99_CELL_FILE + self.packet_rate = BtsPacketRate.WCDMA_DL384K_UL64K + + elif release_version == self.PARAM_RELEASE_VERSION_7: + + cell_parameter_file = self.UMTS_R7_CELL_FILE + self.packet_rate = BtsPacketRate.WCDMA_DL21_6M_UL5_76M + + elif release_version == self.PARAM_RELEASE_VERSION_8: + + cell_parameter_file = self.UMTS_R8_CELL_FILE + self.packet_rate = BtsPacketRate.WCDMA_DL43_2M_UL5_76M + + else: + raise ValueError("Invalid UMTS release version number.") + + self.anritsu.load_cell_paramfile( + ntpath.join(self.callbox_config_path, cell_parameter_file)) + + self.release_version = release_version + + # Loading a cell parameter file stops the simulation + self.start() + + bts.packet_rate = self.packet_rate + + def maximum_downlink_throughput(self): + """ Calculates maximum achievable downlink throughput in the current + simulation state. + + Returns: + Maximum throughput in mbps. + + """ + + if self.packet_rate not in self.packet_rate_to_dl_throughput: + raise NotImplementedError("Packet rate not contained in the " + "throughput dictionary.") + return self.packet_rate_to_dl_throughput[self.packet_rate] + + def maximum_uplink_throughput(self): + """ Calculates maximum achievable uplink throughput in the current + simulation state. + + Returns: + Maximum throughput in mbps. + + """ + + if self.packet_rate not in self.packet_rate_to_ul_throughput: + raise NotImplementedError("Packet rate not contained in the " + "throughput dictionary.") + return self.packet_rate_to_ul_throughput[self.packet_rate] + + def set_downlink_rx_power(self, bts, signal_level): + """ Starts IP data traffic while setting downlink power. + + This is only necessary for UMTS for unclear reasons. b/139026916 """ + + # Starts IP traffic while changing this setting to force the UE to be + # in Communication state, as UL power cannot be set in Idle state + self.start_traffic_for_calibration() + + # Wait until it goes to communication state + self.anritsu.wait_for_communication_state() + + super().set_downlink_rx_power(bts, signal_level) + + # Stop IP traffic after setting the signal level + self.stop_traffic_for_calibration() + + def set_band(self, bts, band): + """ Sets the band used for communication. + + Args: + bts: basestation handle + band: desired band + """ + + bts.band = band + time.sleep(5) # It takes some time to propagate the new band diff --git a/acts/framework/acts/controllers/cellular_lib/__init__.py b/acts/framework/acts/controllers/cellular_lib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/acts/framework/acts/controllers/cellular_simulator.py b/acts/framework/acts/controllers/cellular_simulator.py index 2408611996..99adbd87be 100644 --- a/acts/framework/acts/controllers/cellular_simulator.py +++ b/acts/framework/acts/controllers/cellular_simulator.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from acts import logger -from acts.test_utils.power import tel_simulations as sims +from acts.controllers import cellular_lib as sims class AbstractCellularSimulator: diff --git a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py index 91e71b767a..6a6c3ffd0a 100644 --- a/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py +++ b/acts/framework/acts/controllers/rohdeschwarz_lib/cmw500_cellular_simulator.py @@ -17,7 +17,7 @@ import time from acts.controllers.rohdeschwarz_lib import cmw500 from acts.controllers import cellular_simulator as cc -from acts.test_utils.power.tel_simulations import LteSimulation +from acts.controllers.cellular_lib import LteSimulation CMW_TM_MAPPING = { LteSimulation.TransmissionMode.TM1: cmw500.TransmissionModes.TM1, diff --git a/acts/framework/acts/test_utils/power/cellular/cellular_power_base_test.py b/acts/framework/acts/test_utils/power/cellular/cellular_power_base_test.py index 6ffc7843b9..9fe7fd1301 100644 --- a/acts/framework/acts/test_utils/power/cellular/cellular_power_base_test.py +++ b/acts/framework/acts/test_utils/power/cellular/cellular_power_base_test.py @@ -21,11 +21,11 @@ import acts.controllers.cellular_simulator as simulator from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsu from acts.controllers.rohdeschwarz_lib import cmw500_cellular_simulator as cmw from acts.controllers.rohdeschwarz_lib import cmx500_cellular_simulator as cmx -from acts.test_utils.power.tel_simulations.GsmSimulation import GsmSimulation -from acts.test_utils.power.tel_simulations.LteSimulation import LteSimulation -from acts.test_utils.power.tel_simulations.UmtsSimulation import UmtsSimulation -from acts.test_utils.power.tel_simulations.LteCaSimulation import LteCaSimulation -from acts.test_utils.power.tel_simulations.LteImsSimulation import LteImsSimulation +from acts.controllers.cellular_lib.GsmSimulation import GsmSimulation +from acts.controllers.cellular_lib.LteSimulation import LteSimulation +from acts.controllers.cellular_lib.UmtsSimulation import UmtsSimulation +from acts.controllers.cellular_lib.LteCaSimulation import LteCaSimulation +from acts.controllers.cellular_lib.LteImsSimulation import LteImsSimulation from acts.test_utils.tel import tel_test_utils as telutils diff --git a/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py deleted file mode 100644 index 7d38caeb95..0000000000 --- a/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py +++ /dev/null @@ -1,811 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018 - The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import time -from enum import Enum - -import numpy as np -from acts.controllers import cellular_simulator -from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength -from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode -from acts.test_utils.tel.tel_test_utils import toggle_cell_data_roaming -from acts.test_utils.tel.tel_test_utils import get_rx_tx_power_levels - - -class BaseSimulation(): - """ Base class for cellular connectivity simulations. - - Classes that inherit from this base class implement different simulation - setups. The base class contains methods that are common to all simulation - configurations. - - """ - - NUM_UL_CAL_READS = 3 - NUM_DL_CAL_READS = 5 - MAX_BTS_INPUT_POWER = 30 - MAX_PHONE_OUTPUT_POWER = 23 - UL_MIN_POWER = -60.0 - - # Keys to obtain settings from the test_config dictionary. - KEY_CALIBRATION = "calibration" - KEY_ATTACH_RETRIES = "attach_retries" - KEY_ATTACH_TIMEOUT = "attach_timeout" - - # Filepath to the config files stored in the Anritsu callbox. Needs to be - # formatted to replace {} with either A or B depending on the model. - CALLBOX_PATH_FORMAT_STR = 'C:\\Users\\MD8475{}\\Documents\\DAN_configs\\' - - # Time in seconds to wait for the phone to settle - # after attaching to the base station. - SETTLING_TIME = 10 - - # Default time in seconds to wait for the phone to attach to the basestation - # after toggling airplane mode. This setting can be changed with the - # KEY_ATTACH_TIMEOUT keyword in the test configuration file. - DEFAULT_ATTACH_TIMEOUT = 120 - - # The default number of attach retries. This setting can be changed with - # the KEY_ATTACH_RETRIES keyword in the test configuration file. - DEFAULT_ATTACH_RETRIES = 3 - - # These two dictionaries allow to map from a string to a signal level and - # have to be overriden by the simulations inheriting from this class. - UPLINK_SIGNAL_LEVEL_DICTIONARY = {} - DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {} - - # Units for downlink signal level. This variable has to be overriden by - # the simulations inheriting from this class. - DOWNLINK_SIGNAL_LEVEL_UNITS = None - - class BtsConfig: - """ Base station configuration class. This class is only a container for - base station parameters and should not interact with the instrument - controller. - - Atributes: - output_power: a float indicating the required signal level at the - instrument's output. - input_level: a float indicating the required signal level at the - instrument's input. - """ - def __init__(self): - """ Initialize the base station config by setting all its - parameters to None. """ - self.output_power = None - self.input_power = None - self.band = None - - def incorporate(self, new_config): - """ Incorporates a different configuration by replacing the current - values with the new ones for all the parameters different to None. - """ - for attr, value in vars(new_config).items(): - if value: - setattr(self, attr, value) - - def __init__(self, simulator, log, dut, test_config, calibration_table): - """ Initializes the Simulation object. - - Keeps a reference to the callbox, log and dut handlers and - initializes the class attributes. - - Args: - simulator: a cellular simulator controller - log: a logger handle - dut: the android device handler - test_config: test configuration obtained from the config file - calibration_table: a dictionary containing path losses for - different bands. - """ - - self.simulator = simulator - self.log = log - self.dut = dut - self.calibration_table = calibration_table - - # Turn calibration on or off depending on the test config value. If the - # key is not present, set to False by default - if self.KEY_CALIBRATION not in test_config: - self.log.warning('The {} key is not set in the testbed ' - 'parameters. Setting to off by default. To ' - 'turn calibration on, include the key with ' - 'a true/false value.'.format( - self.KEY_CALIBRATION)) - - self.calibration_required = test_config.get(self.KEY_CALIBRATION, - False) - - # Obtain the allowed number of retries from the test configs - if self.KEY_ATTACH_RETRIES not in test_config: - self.log.warning('The {} key is not set in the testbed ' - 'parameters. Setting to {} by default.'.format( - self.KEY_ATTACH_RETRIES, - self.DEFAULT_ATTACH_RETRIES)) - - self.attach_retries = test_config.get(self.KEY_ATTACH_RETRIES, - self.DEFAULT_ATTACH_RETRIES) - - # Obtain the attach timeout from the test configs - if self.KEY_ATTACH_TIMEOUT not in test_config: - self.log.warning('The {} key is not set in the testbed ' - 'parameters. Setting to {} by default.'.format( - self.KEY_ATTACH_TIMEOUT, - self.DEFAULT_ATTACH_TIMEOUT)) - - self.attach_timeout = test_config.get(self.KEY_ATTACH_TIMEOUT, - self.DEFAULT_ATTACH_TIMEOUT) - - # Configuration object for the primary base station - self.primary_config = self.BtsConfig() - - # Store the current calibrated band - self.current_calibrated_band = None - - # Path loss measured during calibration - self.dl_path_loss = None - self.ul_path_loss = None - - # Target signal levels obtained during configuration - self.sim_dl_power = None - self.sim_ul_power = None - - # Stores RRC status change timer - self.rrc_sc_timer = None - - # Set to default APN - log.info("Configuring APN.") - dut.droid.telephonySetAPN("test", "test", "default") - - # Enable roaming on the phone - toggle_cell_data_roaming(self.dut, True) - - # Make sure airplane mode is on so the phone won't attach right away - toggle_airplane_mode(self.log, self.dut, True) - - # Wait for airplane mode setting to propagate - time.sleep(2) - - # Prepare the simulator for this simulation setup - self.setup_simulator() - - def setup_simulator(self): - """ Do initial configuration in the simulator. """ - raise NotImplementedError() - - def attach(self): - """ Attach the phone to the basestation. - - Sets a good signal level, toggles airplane mode - and waits for the phone to attach. - - Returns: - True if the phone was able to attach, False if not. - """ - - # Turn on airplane mode - toggle_airplane_mode(self.log, self.dut, True) - - # Wait for airplane mode setting to propagate - time.sleep(2) - - # Provide a good signal power for the phone to attach easily - new_config = self.BtsConfig() - new_config.input_power = -10 - new_config.output_power = -30 - self.simulator.configure_bts(new_config) - self.primary_config.incorporate(new_config) - - # Try to attach the phone. - for i in range(self.attach_retries): - - try: - - # Turn off airplane mode - toggle_airplane_mode(self.log, self.dut, False) - - # Wait for the phone to attach. - self.simulator.wait_until_attached(timeout=self.attach_timeout) - - except cellular_simulator.CellularSimulatorError: - - # The phone failed to attach - self.log.info( - "UE failed to attach on attempt number {}.".format(i + 1)) - - # Turn airplane mode on to prepare the phone for a retry. - toggle_airplane_mode(self.log, self.dut, True) - - # Wait for APM to propagate - time.sleep(3) - - # Retry - if i < self.attach_retries - 1: - # Retry - continue - else: - # No more retries left. Return False. - return False - - else: - # The phone attached successfully. - time.sleep(self.SETTLING_TIME) - self.log.info("UE attached to the callbox.") - break - - return True - - def detach(self): - """ Detach the phone from the basestation. - - Turns airplane mode and resets basestation. - """ - - # Set the DUT to airplane mode so it doesn't see the - # cellular network going off - toggle_airplane_mode(self.log, self.dut, True) - - # Wait for APM to propagate - time.sleep(2) - - # Power off basestation - self.simulator.detach() - - def stop(self): - """ Detach phone from the basestation by stopping the simulation. - - Stop the simulation and turn airplane mode on. """ - - # Set the DUT to airplane mode so it doesn't see the - # cellular network going off - toggle_airplane_mode(self.log, self.dut, True) - - # Wait for APM to propagate - time.sleep(2) - - # Stop the simulation - self.simulator.stop() - - def start(self): - """ Start the simulation by attaching the phone and setting the - required DL and UL power. - - Note that this refers to starting the simulated testing environment - and not to starting the signaling on the cellular instruments, - which might have been done earlier depending on the cellular - instrument controller implementation. """ - - if not self.attach(): - raise RuntimeError('Could not attach to base station.') - - # Starts IP traffic while changing this setting to force the UE to be - # in Communication state, as UL power cannot be set in Idle state - self.start_traffic_for_calibration() - - # Wait until it goes to communication state - self.simulator.wait_until_communication_state() - - # Set uplink power to a minimum before going to the actual desired - # value. This avoid inconsistencies produced by the hysteresis in the - # PA switching points. - self.log.info('Setting UL power to -30 dBm before going to the ' - 'requested value to avoid incosistencies caused by ' - 'hysteresis.') - self.set_uplink_tx_power(-30) - - # Set signal levels obtained from the test parameters - self.set_downlink_rx_power(self.sim_dl_power) - self.set_uplink_tx_power(self.sim_ul_power) - - # Verify signal level - try: - rx_power, tx_power = get_rx_tx_power_levels(self.log, self.dut) - - if not tx_power or not rx_power[0]: - raise RuntimeError('The method return invalid Tx/Rx values.') - - self.log.info('Signal level reported by the DUT in dBm: Tx = {}, ' - 'Rx = {}.'.format(tx_power, rx_power)) - - if abs(self.sim_ul_power - tx_power) > 1: - self.log.warning('Tx power at the UE is off by more than 1 dB') - - except RuntimeError as e: - self.log.error('Could not verify Rx / Tx levels: %s.' % e) - - # Stop IP traffic after setting the UL power level - self.stop_traffic_for_calibration() - - def parse_parameters(self, parameters): - """ Configures simulation using a list of parameters. - - Consumes parameters from a list. - Children classes need to call this method first. - - Args: - parameters: list of parameters - """ - - raise NotImplementedError() - - def consume_parameter(self, parameters, parameter_name, num_values=0): - """ Parses a parameter from a list. - - Allows to parse the parameter list. Will delete parameters from the - list after consuming them to ensure that they are not used twice. - - Args: - parameters: list of parameters - parameter_name: keyword to look up in the list - num_values: number of arguments following the - parameter name in the list - Returns: - A list containing the parameter name and the following num_values - arguments - """ - - try: - i = parameters.index(parameter_name) - except ValueError: - # parameter_name is not set - return [] - - return_list = [] - - try: - for j in range(num_values + 1): - return_list.append(parameters.pop(i)) - except IndexError: - raise ValueError( - "Parameter {} has to be followed by {} values.".format( - parameter_name, num_values)) - - return return_list - - def set_uplink_tx_power(self, signal_level): - """ Configure the uplink tx power level - - Args: - signal_level: calibrated tx power in dBm - """ - new_config = self.BtsConfig() - new_config.input_power = self.calibrated_uplink_tx_power( - self.primary_config, signal_level) - self.simulator.configure_bts(new_config) - self.primary_config.incorporate(new_config) - - def set_downlink_rx_power(self, signal_level): - """ Configure the downlink rx power level - - Args: - signal_level: calibrated rx power in dBm - """ - new_config = self.BtsConfig() - new_config.output_power = self.calibrated_downlink_rx_power( - self.primary_config, signal_level) - self.simulator.configure_bts(new_config) - self.primary_config.incorporate(new_config) - - def get_uplink_power_from_parameters(self, parameters): - """ Reads uplink power from a list of parameters. """ - - values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1) - - if values: - if values[1] in self.UPLINK_SIGNAL_LEVEL_DICTIONARY: - return self.UPLINK_SIGNAL_LEVEL_DICTIONARY[values[1]] - else: - try: - if values[1][0] == 'n': - # Treat the 'n' character as a negative sign - return -int(values[1][1:]) - else: - return int(values[1]) - except ValueError: - pass - - # If the method got to this point it is because PARAM_UL_PW was not - # included in the test parameters or the provided value was invalid. - raise ValueError( - "The test name needs to include parameter {} followed by the " - "desired uplink power expressed by an integer number in dBm " - "or by one the following values: {}. To indicate negative " - "values, use the letter n instead of - sign.".format( - self.PARAM_UL_PW, - list(self.UPLINK_SIGNAL_LEVEL_DICTIONARY.keys()))) - - def get_downlink_power_from_parameters(self, parameters): - """ Reads downlink power from a list of parameters. """ - - values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1) - - if values: - if values[1] not in self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY: - raise ValueError("Invalid signal level value {}.".format( - values[1])) - else: - return self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY[values[1]] - else: - # Use default value - power = self.DOWNLINK_SIGNAL_LEVEL_DICTIONARY['excellent'] - self.log.info("No DL signal level value was indicated in the test " - "parameters. Using default value of {} {}.".format( - power, self.DOWNLINK_SIGNAL_LEVEL_UNITS)) - return power - - def calibrated_downlink_rx_power(self, bts_config, signal_level): - """ Calculates the power level at the instrument's output in order to - obtain the required rx power level at the DUT's input. - - If calibration values are not available, returns the uncalibrated signal - level. - - Args: - bts_config: the current configuration at the base station. derived - classes implementations can use this object to indicate power as - spectral power density or in other units. - signal_level: desired downlink received power, can be either a - key value pair, an int or a float - """ - - # Obtain power value if the provided signal_level is a key value pair - if isinstance(signal_level, Enum): - power = signal_level.value - else: - power = signal_level - - # Try to use measured path loss value. If this was not set, it will - # throw an TypeError exception - try: - calibrated_power = round(power + self.dl_path_loss) - if calibrated_power > self.simulator.MAX_DL_POWER: - self.log.warning( - "Cannot achieve phone DL Rx power of {} dBm. Requested TX " - "power of {} dBm exceeds callbox limit!".format( - power, calibrated_power)) - calibrated_power = self.simulator.MAX_DL_POWER - self.log.warning( - "Setting callbox Tx power to max possible ({} dBm)".format( - calibrated_power)) - - self.log.info( - "Requested phone DL Rx power of {} dBm, setting callbox Tx " - "power at {} dBm".format(power, calibrated_power)) - time.sleep(2) - # Power has to be a natural number so calibration wont be exact. - # Inform the actual received power after rounding. - self.log.info( - "Phone downlink received power is {0:.2f} dBm".format( - calibrated_power - self.dl_path_loss)) - return calibrated_power - except TypeError: - self.log.info("Phone downlink received power set to {} (link is " - "uncalibrated).".format(round(power))) - return round(power) - - def calibrated_uplink_tx_power(self, bts_config, signal_level): - """ Calculates the power level at the instrument's input in order to - obtain the required tx power level at the DUT's output. - - If calibration values are not available, returns the uncalibrated signal - level. - - Args: - bts_config: the current configuration at the base station. derived - classes implementations can use this object to indicate power as - spectral power density or in other units. - signal_level: desired uplink transmitted power, can be either a - key value pair, an int or a float - """ - - # Obtain power value if the provided signal_level is a key value pair - if isinstance(signal_level, Enum): - power = signal_level.value - else: - power = signal_level - - # Try to use measured path loss value. If this was not set, it will - # throw an TypeError exception - try: - calibrated_power = round(power - self.ul_path_loss) - if calibrated_power < self.UL_MIN_POWER: - self.log.warning( - "Cannot achieve phone UL Tx power of {} dBm. Requested UL " - "power of {} dBm exceeds callbox limit!".format( - power, calibrated_power)) - calibrated_power = self.UL_MIN_POWER - self.log.warning( - "Setting UL Tx power to min possible ({} dBm)".format( - calibrated_power)) - - self.log.info( - "Requested phone UL Tx power of {} dBm, setting callbox Rx " - "power at {} dBm".format(power, calibrated_power)) - time.sleep(2) - # Power has to be a natural number so calibration wont be exact. - # Inform the actual transmitted power after rounding. - self.log.info( - "Phone uplink transmitted power is {0:.2f} dBm".format( - calibrated_power + self.ul_path_loss)) - return calibrated_power - except TypeError: - self.log.info("Phone uplink transmitted power set to {} (link is " - "uncalibrated).".format(round(power))) - return round(power) - - def calibrate(self, band): - """ Calculates UL and DL path loss if it wasn't done before. - - The should be already set to the required band before calling this - method. - - Args: - band: the band that is currently being calibrated. - """ - - if self.dl_path_loss and self.ul_path_loss: - self.log.info("Measurements are already calibrated.") - - # Attach the phone to the base station - if not self.attach(): - self.log.info( - "Skipping calibration because the phone failed to attach.") - return - - # If downlink or uplink were not yet calibrated, do it now - if not self.dl_path_loss: - self.dl_path_loss = self.downlink_calibration() - if not self.ul_path_loss: - self.ul_path_loss = self.uplink_calibration() - - # Detach after calibrating - self.detach() - time.sleep(2) - - def start_traffic_for_calibration(self): - """ - Starts UDP IP traffic before running calibration. Uses APN_1 - configured in the phone. - """ - self.simulator.start_data_traffic() - - def stop_traffic_for_calibration(self): - """ - Stops IP traffic after calibration. - """ - self.simulator.stop_data_traffic() - - def downlink_calibration(self, rat=None, power_units_conversion_func=None): - """ Computes downlink path loss and returns the calibration value - - The DUT needs to be attached to the base station before calling this - method. - - Args: - rat: desired RAT to calibrate (matching the label reported by - the phone) - power_units_conversion_func: a function to convert the units - reported by the phone to dBm. needs to take two arguments: the - reported signal level and bts. use None if no conversion is - needed. - Returns: - Dowlink calibration value and measured DL power. - """ - - # Check if this parameter was set. Child classes may need to override - # this class passing the necessary parameters. - if not rat: - raise ValueError( - "The parameter 'rat' has to indicate the RAT being used as " - "reported by the phone.") - - # Save initial output level to restore it after calibration - restoration_config = self.BtsConfig() - restoration_config.output_power = self.primary_config.output_power - - # Set BTS to a good output level to minimize measurement error - initial_screen_timeout = self.dut.droid.getScreenTimeout() - new_config = self.BtsConfig() - new_config.output_power = self.simulator.MAX_DL_POWER - 5 - self.simulator.configure_bts(new_config) - - # Set phone sleep time out - self.dut.droid.setScreenTimeout(1800) - self.dut.droid.goToSleepNow() - time.sleep(2) - - # Starting IP traffic - self.start_traffic_for_calibration() - - down_power_measured = [] - for i in range(0, self.NUM_DL_CAL_READS): - # For some reason, the RSRP gets updated on Screen ON event - self.dut.droid.wakeUpNow() - time.sleep(4) - signal_strength = get_telephony_signal_strength(self.dut) - down_power_measured.append(signal_strength[rat]) - self.dut.droid.goToSleepNow() - time.sleep(5) - - # Stop IP traffic - self.stop_traffic_for_calibration() - - # Reset phone and bts to original settings - self.dut.droid.goToSleepNow() - self.dut.droid.setScreenTimeout(initial_screen_timeout) - self.simulator.configure_bts(restoration_config) - time.sleep(2) - - # Calculate the mean of the measurements - reported_asu_power = np.nanmean(down_power_measured) - - # Convert from RSRP to signal power - if power_units_conversion_func: - avg_down_power = power_units_conversion_func( - reported_asu_power, self.primary_config) - else: - avg_down_power = reported_asu_power - - # Calculate Path Loss - dl_target_power = self.simulator.MAX_DL_POWER - 5 - down_call_path_loss = dl_target_power - avg_down_power - - # Validate the result - if not 0 < down_call_path_loss < 100: - raise RuntimeError( - "Downlink calibration failed. The calculated path loss value " - "was {} dBm.".format(down_call_path_loss)) - - self.log.info( - "Measured downlink path loss: {} dB".format(down_call_path_loss)) - - return down_call_path_loss - - def uplink_calibration(self): - """ Computes uplink path loss and returns the calibration value - - The DUT needs to be attached to the base station before calling this - method. - - Returns: - Uplink calibration value and measured UL power - """ - - # Save initial input level to restore it after calibration - restoration_config = self.BtsConfig() - restoration_config.input_power = self.primary_config.input_power - - # Set BTS1 to maximum input allowed in order to perform - # uplink calibration - target_power = self.MAX_PHONE_OUTPUT_POWER - initial_screen_timeout = self.dut.droid.getScreenTimeout() - new_config = self.BtsConfig() - new_config.input_power = self.MAX_BTS_INPUT_POWER - self.simulator.configure_bts(new_config) - - # Set phone sleep time out - self.dut.droid.setScreenTimeout(1800) - self.dut.droid.wakeUpNow() - time.sleep(2) - - # Start IP traffic - self.start_traffic_for_calibration() - - up_power_per_chain = [] - # Get the number of chains - cmd = 'MONITOR? UL_PUSCH' - uplink_meas_power = self.anritsu.send_query(cmd) - str_power_chain = uplink_meas_power.split(',') - num_chains = len(str_power_chain) - for ichain in range(0, num_chains): - up_power_per_chain.append([]) - - for i in range(0, self.NUM_UL_CAL_READS): - uplink_meas_power = self.anritsu.send_query(cmd) - str_power_chain = uplink_meas_power.split(',') - - for ichain in range(0, num_chains): - if (str_power_chain[ichain] == 'DEACTIVE'): - up_power_per_chain[ichain].append(float('nan')) - else: - up_power_per_chain[ichain].append( - float(str_power_chain[ichain])) - - time.sleep(3) - - # Stop IP traffic - self.stop_traffic_for_calibration() - - # Reset phone and bts to original settings - self.dut.droid.goToSleepNow() - self.dut.droid.setScreenTimeout(initial_screen_timeout) - self.simulator.configure_bts(restoration_config) - time.sleep(2) - - # Phone only supports 1x1 Uplink so always chain 0 - avg_up_power = np.nanmean(up_power_per_chain[0]) - if np.isnan(avg_up_power): - raise RuntimeError( - "Calibration failed because the callbox reported the chain to " - "be deactive.") - - up_call_path_loss = target_power - avg_up_power - - # Validate the result - if not 0 < up_call_path_loss < 100: - raise RuntimeError( - "Uplink calibration failed. The calculated path loss value " - "was {} dBm.".format(up_call_path_loss)) - - self.log.info( - "Measured uplink path loss: {} dB".format(up_call_path_loss)) - - return up_call_path_loss - - def load_pathloss_if_required(self): - """ If calibration is required, try to obtain the pathloss values from - the calibration table and measure them if they are not available. """ - # Invalidate the previous values - self.dl_path_loss = None - self.ul_path_loss = None - - # Load the new ones - if self.calibration_required: - - band = self.primary_config.band - - # Try loading the path loss values from the calibration table. If - # they are not available, use the automated calibration procedure. - try: - self.dl_path_loss = self.calibration_table[band]["dl"] - self.ul_path_loss = self.calibration_table[band]["ul"] - except KeyError: - self.calibrate(band) - - # Complete the calibration table with the new values to be used in - # the next tests. - if band not in self.calibration_table: - self.calibration_table[band] = {} - - if "dl" not in self.calibration_table[band] and self.dl_path_loss: - self.calibration_table[band]["dl"] = self.dl_path_loss - - if "ul" not in self.calibration_table[band] and self.ul_path_loss: - self.calibration_table[band]["ul"] = self.ul_path_loss - - def maximum_downlink_throughput(self): - """ Calculates maximum achievable downlink throughput in the current - simulation state. - - Because thoughput is dependent on the RAT, this method needs to be - implemented by children classes. - - Returns: - Maximum throughput in mbps - """ - raise NotImplementedError() - - def maximum_uplink_throughput(self): - """ Calculates maximum achievable downlink throughput in the current - simulation state. - - Because thoughput is dependent on the RAT, this method needs to be - implemented by children classes. - - Returns: - Maximum throughput in mbps - """ - raise NotImplementedError() diff --git a/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py deleted file mode 100644 index 6dc2082cd6..0000000000 --- a/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018 - The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import ntpath - -import time -from acts.controllers.anritsu_lib.md8475a import BtsGprsMode -from acts.controllers.anritsu_lib.md8475a import BtsNumber -from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim -from acts.test_utils.power.tel_simulations.BaseSimulation import BaseSimulation -from acts.test_utils.tel.anritsu_utils import GSM_BAND_DCS1800 -from acts.test_utils.tel.anritsu_utils import GSM_BAND_EGSM900 -from acts.test_utils.tel.anritsu_utils import GSM_BAND_GSM850 -from acts.test_utils.tel.anritsu_utils import GSM_BAND_RGSM900 -from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY - - -class GsmSimulation(BaseSimulation): - """ Single base station GSM. """ - - # Simulation config files in the callbox computer. - # These should be replaced in the future by setting up - # the same configuration manually. - - GSM_BASIC_SIM_FILE = 'SIM_default_GSM.wnssp' - - GSM_CELL_FILE = 'CELL_GSM_config.wnscp' - - # Test name parameters - - PARAM_BAND = "band" - PARAM_GPRS = "gprs" - PARAM_EGPRS = "edge" - PARAM_NO_GPRS = "nogprs" - PARAM_SLOTS = "slots" - - bands_parameter_mapping = { - '850': GSM_BAND_GSM850, - '900': GSM_BAND_EGSM900, - '1800': GSM_BAND_DCS1800, - '1900': GSM_BAND_RGSM900 - } - - def __init__(self, simulator, log, dut, test_config, calibration_table): - """ Initializes the simulator for a single-carrier GSM simulation. - - Loads a simple LTE simulation enviroment with 1 basestation. It also - creates the BTS handle so we can change the parameters as desired. - - Args: - simulator: a cellular simulator controller - log: a logger handle - dut: the android device handler - test_config: test configuration obtained from the config file - calibration_table: a dictionary containing path losses for - different bands. - - """ - # The GSM simulation relies on the cellular simulator to be a MD8475 - if not isinstance(self.simulator, anritsusim.MD8475CellularSimulator): - raise ValueError('The GSM simulation relies on the simulator to ' - 'be an Anritsu MD8475 A/B instrument.') - - # The Anritsu controller needs to be unwrapped before calling - # super().__init__ because setup_simulator() requires self.anritsu and - # will be called during the parent class initialization. - self.anritsu = self.simulator.anritsu - self.bts1 = self.anritsu.get_BTS(BtsNumber.BTS1) - - super().__init__(simulator, log, dut, test_config, calibration_table) - - if not dut.droid.telephonySetPreferredNetworkTypesForSubscription( - NETWORK_MODE_GSM_ONLY, - dut.droid.subscriptionGetDefaultSubId()): - log.error("Coold not set preferred network type.") - else: - log.info("Preferred network type set.") - - def setup_simulator(self): - """ Do initial configuration in the simulator. """ - - # Load callbox config files - callbox_config_path = self.CALLBOX_PATH_FORMAT_STR.format( - self.anritsu._md8475_version) - - self.anritsu.load_simulation_paramfile( - ntpath.join(callbox_config_path, self.GSM_BASIC_SIM_FILE)) - self.anritsu.load_cell_paramfile( - ntpath.join(callbox_config_path, self.GSM_CELL_FILE)) - - # Start simulation if it wasn't started - self.anritsu.start_simulation() - - def parse_parameters(self, parameters): - """ Configs a GSM simulation using a list of parameters. - - Calls the parent method first, then consumes parameters specific to GSM. - - Args: - parameters: list of parameters - """ - - # Setup band - - values = self.consume_parameter(parameters, self.PARAM_BAND, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter '{}' followed by " - "the required band number.".format(self.PARAM_BAND)) - - self.set_band(self.bts1, values[1]) - self.load_pathloss_if_required() - - # Setup GPRS mode - - if self.consume_parameter(parameters, self.PARAM_GPRS): - self.bts1.gsm_gprs_mode = BtsGprsMode.GPRS - elif self.consume_parameter(parameters, self.PARAM_EGPRS): - self.bts1.gsm_gprs_mode = BtsGprsMode.EGPRS - elif self.consume_parameter(parameters, self.PARAM_NO_GPRS): - self.bts1.gsm_gprs_mode = BtsGprsMode.NO_GPRS - else: - raise ValueError( - "GPRS mode needs to be indicated in the test name with either " - "{}, {} or {}.".format(self.PARAM_GPRS, self.PARAM_EGPRS, - self.PARAM_NO_GPRS)) - - # Setup slot allocation - - values = self.consume_parameter(parameters, self.PARAM_SLOTS, 2) - - if not values: - raise ValueError( - "The test name needs to include parameter {} followed by two " - "int values indicating DL and UL slots.".format( - self.PARAM_SLOTS)) - - self.bts1.gsm_slots = (int(values[1]), int(values[2])) - - def set_band(self, bts, band): - """ Sets the band used for communication. - - Args: - bts: basestation handle - band: desired band - """ - - bts.band = band - time.sleep(5) # It takes some time to propagate the new band diff --git a/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py deleted file mode 100644 index addc3a8aab..0000000000 --- a/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py +++ /dev/null @@ -1,427 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018 - The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import re -from acts.test_utils.power.tel_simulations import LteSimulation - - -class LteCaSimulation(LteSimulation.LteSimulation): - """ Carrier aggregation LTE simulation. """ - - # Dictionary of lower DL channel number bound for each band. - LOWEST_DL_CN_DICTIONARY = { - 1: 0, - 2: 600, - 3: 1200, - 4: 1950, - 5: 2400, - 6: 2650, - 7: 2750, - 8: 3450, - 9: 3800, - 10: 4150, - 11: 4750, - 12: 5010, - 13: 5180, - 14: 5280, - 17: 5730, - 18: 5850, - 19: 6000, - 20: 6150, - 21: 6450, - 22: 6600, - 23: 7500, - 24: 7700, - 25: 8040, - 26: 8690, - 27: 9040, - 28: 9210, - 29: 9660, - 30: 9770, - 31: 9870, - 32: 36000, - 33: 36200, - 34: 36350, - 35: 36950, - 36: 37550, - 37: 37750, - 38: 38250, - 39: 38650, - 40: 39650, - 41: 41590, - 42: 45590, - 66: 66436 - } - - # Simulation config keywords contained in the test name - PARAM_CA = 'ca' - - # Test config keywords - KEY_FREQ_BANDS = "freq_bands" - - def __init__(self, simulator, log, dut, test_config, calibration_table): - """ Initializes the simulator for LTE simulation with carrier - aggregation. - - Loads a simple LTE simulation enviroment with 5 basestations. - - Args: - simulator: the cellular instrument controller - log: a logger handle - dut: the android device handler - test_config: test configuration obtained from the config file - calibration_table: a dictionary containing path losses for - different bands. - - """ - - super().__init__(simulator, log, dut, test_config, calibration_table) - - # Create a configuration object for each base station and copy initial - # settings from the PCC base station. - self.bts_configs = [self.primary_config] - - for bts_index in range(1, self.simulator.LTE_MAX_CARRIERS): - new_config = self.BtsConfig() - new_config.incorporate(self.primary_config) - self.simulator.configure_bts(new_config, bts_index) - self.bts_configs.append(new_config) - - # Get LTE CA frequency bands setting from the test configuration - if self.KEY_FREQ_BANDS not in test_config: - self.log.warning("The key '{}' is not set in the config file. " - "Setting to null by default.".format( - self.KEY_FREQ_BANDS)) - - self.freq_bands = test_config.get(self.KEY_FREQ_BANDS, True) - - def setup_simulator(self): - """ Do initial configuration in the simulator. """ - self.simulator.setup_lte_ca_scenario() - - def parse_parameters(self, parameters): - """ Configs an LTE simulation with CA using a list of parameters. - - Args: - parameters: list of parameters - """ - - # Get the CA band configuration - - values = self.consume_parameter(parameters, self.PARAM_CA, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter '{}' followed by " - "the CA configuration. For example: ca_3c7c28a".format( - self.PARAM_CA)) - - # Carrier aggregation configurations are indicated with the band numbers - # followed by the CA classes in a single string. For example, for 5 CA - # using 3C 7C and 28A the parameter value should be 3c7c28a. - ca_configs = re.findall(r'(\d+[abcABC])', values[1]) - - if not ca_configs: - raise ValueError( - "The CA configuration has to be indicated with one string as " - "in the following example: ca_3c7c28a".format(self.PARAM_CA)) - - # Apply the carrier aggregation combination - self.simulator.set_ca_combination(ca_configs) - - # Save the bands to the bts config objects - bts_index = 0 - for ca in ca_configs: - ca_class = ca[-1] - band = ca[:-1] - - self.bts_configs[bts_index].band = band - bts_index += 1 - - if ca_class.upper() == 'B' or ca_class.upper() == 'C': - # Class B and C means two carriers with the same band - self.bts_configs[bts_index].band = band - bts_index += 1 - - # Count the number of carriers in the CA combination - self.num_carriers = 0 - for ca in ca_configs: - ca_class = ca[-1] - # Class C means that there are two contiguous carriers, while other - # classes are a single one. - if ca_class.upper() == 'C': - self.num_carriers += 2 - else: - self.num_carriers += 1 - - # Create an array of configuration objects to set up the base stations. - new_configs = [self.BtsConfig() for _ in range(self.num_carriers)] - - # Get the bw for each carrier - # This is an optional parameter, by default the maximum bandwidth for - # each band will be selected. - - values = self.consume_parameter(parameters, self.PARAM_BW, - self.num_carriers) - - bts_index = 0 - - for ca in ca_configs: - - band = int(ca[:-1]) - ca_class = ca[-1] - - if values: - bw = int(values[1 + bts_index]) - else: - bw = max(self.allowed_bandwidth_dictionary[band]) - - new_configs[bts_index].bandwidth = bw - bts_index += 1 - - if ca_class.upper() == 'C': - - new_configs[bts_index].bandwidth = bw - - # Calculate the channel number for the second carrier to be - # contiguous to the first one - new_configs[bts_index].dl_channel = int( - self.LOWEST_DL_CN_DICTIONARY[int(band)] + bw * 10 - 2) - - bts_index += 1 - - # Get the TM for each carrier - # This is an optional parameter, by the default value depends on the - # MIMO mode for each carrier - - tm_values = self.consume_parameter(parameters, self.PARAM_TM, - self.num_carriers) - - # Get the MIMO mode for each carrier - - mimo_values = self.consume_parameter(parameters, self.PARAM_MIMO, - self.num_carriers) - - if not mimo_values: - raise ValueError( - "The test parameter '{}' has to be included in the " - "test name followed by the MIMO mode for each " - "carrier separated by underscores.".format(self.PARAM_MIMO)) - - if len(mimo_values) != self.num_carriers + 1: - raise ValueError( - "The test parameter '{}' has to be followed by " - "a number of MIMO mode values equal to the number " - "of carriers being used.".format(self.PARAM_MIMO)) - - for bts_index in range(self.num_carriers): - - # Parse and set the requested MIMO mode - - for mimo_mode in LteSimulation.MimoMode: - if mimo_values[bts_index + 1] == mimo_mode.value: - requested_mimo = mimo_mode - break - else: - raise ValueError( - "The mimo mode must be one of %s." % - {elem.value - for elem in LteSimulation.MimoMode}) - - if (requested_mimo == LteSimulation.MimoMode.MIMO_4x4 - and not self.simulator.LTE_SUPPORTS_4X4_MIMO): - raise ValueError("The test requires 4x4 MIMO, but that is not " - "supported by the MD8475A callbox.") - - new_configs[bts_index].mimo_mode = requested_mimo - - # Parse and set the requested TM - - if tm_values: - for tm in LteSimulation.TransmissionMode: - if tm_values[bts_index + 1] == tm.value[2:]: - requested_tm = tm - break - else: - raise ValueError( - "The TM must be one of %s." % - {elem.value - for elem in LteSimulation.MimoMode}) - else: - # Provide default values if the TM parameter is not set - if requested_mimo == LteSimulation.MimoMode.MIMO_1x1: - requested_tm = LteSimulation.TransmissionMode.TM1 - else: - requested_tm = LteSimulation.TransmissionMode.TM3 - - new_configs[bts_index].transmission_mode = requested_tm - - self.log.info("Cell {} will be set to {} and {} MIMO.".format( - bts_index + 1, requested_tm.value, requested_mimo.value)) - - # Get uplink power - - ul_power = self.get_uplink_power_from_parameters(parameters) - - # Power is not set on the callbox until after the simulation is - # started. Saving this value in a variable for later - self.sim_ul_power = ul_power - - # Get downlink power - - dl_power = self.get_downlink_power_from_parameters(parameters) - - # Power is not set on the callbox until after the simulation is - # started. Saving this value in a variable for later - self.sim_dl_power = dl_power - - # Setup scheduling mode - - values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1) - - if not values: - scheduling = LteSimulation.SchedulingMode.STATIC - self.log.warning( - "The test name does not include the '{}' parameter. Setting to " - "{} by default.".format(scheduling.value, - self.PARAM_SCHEDULING)) - else: - for scheduling_mode in LteSimulation.SchedulingMode: - if values[1].upper() == scheduling_mode.value: - scheduling = scheduling_mode - break - else: - raise ValueError( - "The test name parameter '{}' has to be followed by one of " - "{}.".format( - self.PARAM_SCHEDULING, - {elem.value - for elem in LteSimulation.SchedulingMode})) - - for bts_index in range(self.num_carriers): - new_configs[bts_index].scheduling_mode = scheduling - - if scheduling == LteSimulation.SchedulingMode.STATIC: - - values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2) - - if not values: - self.log.warning( - "The '{}' parameter was not set, using 100% RBs for both " - "DL and UL. To set the percentages of total RBs include " - "the '{}' parameter followed by two ints separated by an " - "underscore indicating downlink and uplink percentages.". - format(self.PARAM_PATTERN, self.PARAM_PATTERN)) - dl_pattern = 100 - ul_pattern = 100 - else: - dl_pattern = int(values[1]) - ul_pattern = int(values[2]) - - if (dl_pattern, ul_pattern) not in [(0, 100), (100, 0), - (100, 100)]: - raise ValueError( - "Only full RB allocation for DL or UL is supported in CA " - "sims. The allowed combinations are 100/0, 0/100 and " - "100/100.") - - for bts_index in range(self.num_carriers): - - # Look for a DL MCS configuration in the test parameters. If it - # is not present, use a default value. - dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, - 1) - if dlmcs: - mcs_dl = int(dlmcs[1]) - else: - self.log.warning( - 'The test name does not include the {} parameter. ' - 'Setting to the max value by default'.format( - self.PARAM_DL_MCS)) - - if self.dl_256_qam and new_configs[ - bts_index].bandwidth == 1.4: - mcs_dl = 26 - elif (not self.dl_256_qam - and self.primary_config.tbs_pattern_on - and new_configs[bts_index].bandwidth != 1.4): - mcs_dl = 28 - else: - mcs_dl = 27 - - # Look for an UL MCS configuration in the test parameters. If it - # is not present, use a default value. - ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, - 1) - if ulmcs: - mcs_ul = int(ulmcs[1]) - else: - self.log.warning( - 'The test name does not include the {} parameter. ' - 'Setting to the max value by default'.format( - self.PARAM_UL_MCS)) - - if self.ul_64_qam: - mcs_ul = 28 - else: - mcs_ul = 23 - - dl_rbs, ul_rbs = self.allocation_percentages_to_rbs( - new_configs[bts_index].bandwidth, - new_configs[bts_index].transmission_mode, dl_pattern, - ul_pattern) - - new_configs[bts_index].dl_rbs = dl_rbs - new_configs[bts_index].ul_rbs = ul_rbs - new_configs[bts_index].dl_mcs = mcs_dl - new_configs[bts_index].ul_mcs = mcs_ul - - # Setup the base stations with the obtained configurations and then save - # these parameters in the current configuration objects - for bts_index in range(len(new_configs)): - self.simulator.configure_bts(new_configs[bts_index], bts_index) - self.bts_configs[bts_index].incorporate(new_configs[bts_index]) - - # Now that the band is set, calibrate the link for the PCC if necessary - self.load_pathloss_if_required() - - def maximum_downlink_throughput(self): - """ Calculates maximum downlink throughput as the sum of all the active - carriers. - """ - return sum( - self.bts_maximum_downlink_throughtput(self.bts_configs[bts_index]) - for bts_index in range(self.num_carriers)) - - def start(self): - """ Set the signal level for the secondary carriers, as the base class - implementation of this method will only set up downlink power for the - primary carrier component. - - After that, attaches the secondary carriers.""" - - super().start() - - if self.sim_dl_power: - self.log.info('Setting DL power for secondary carriers.') - - for bts_index in range(1, self.num_carriers): - new_config = self.BtsConfig() - new_config.output_power = self.calibrated_downlink_rx_power( - self.bts_configs[bts_index], self.sim_dl_power) - self.simulator.configure_bts(new_config, bts_index) - self.bts_configs[bts_index].incorporate(new_config) - - self.simulator.lte_attach_secondary_carriers(self.freq_bands) diff --git a/acts/framework/acts/test_utils/power/tel_simulations/LteImsSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/LteImsSimulation.py deleted file mode 100644 index 71102463cf..0000000000 --- a/acts/framework/acts/test_utils/power/tel_simulations/LteImsSimulation.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2019 - The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from acts.test_utils.power.tel_simulations.LteSimulation import LteSimulation -import acts.test_utils.tel.anritsu_utils as anritsu_utils -import acts.controllers.anritsu_lib.md8475a as md8475a - - -class LteImsSimulation(LteSimulation): - - LTE_BASIC_SIM_FILE = 'VoLTE_ATT_Sim.wnssp' - LTE_BASIC_CELL_FILE = 'VoLTE_ATT_Cell.wnscp' - - def attach(self): - """ After attaching verify the UE has registered with the IMS server. - - Returns: - True if the phone was able to attach, False if not. - """ - - if not super().attach(): - return False - - # The phone should have registered with the IMS server before attaching. - # Make sure the IMS registration was successful by verifying the CSCF - # status is SIP IDLE. - if not anritsu_utils.wait_for_ims_cscf_status( - self.log, self.anritsu, - anritsu_utils.DEFAULT_IMS_VIRTUAL_NETWORK_ID, - md8475a.ImsCscfStatus.SIPIDLE.value): - self.log.error('UE failed to register with the IMS server.') - return False - - return True diff --git a/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py deleted file mode 100644 index 44bcbf67dd..0000000000 --- a/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py +++ /dev/null @@ -1,1299 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018 - The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -from enum import Enum - -from acts.test_utils.power.tel_simulations.BaseSimulation import BaseSimulation -from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_ONLY - - -class TransmissionMode(Enum): - """ Transmission modes for LTE (e.g., TM1, TM4, ...) """ - TM1 = "TM1" - TM2 = "TM2" - TM3 = "TM3" - TM4 = "TM4" - TM7 = "TM7" - TM8 = "TM8" - TM9 = "TM9" - - -class MimoMode(Enum): - """ Mimo modes """ - MIMO_1x1 = "1x1" - MIMO_2x2 = "2x2" - MIMO_4x4 = "4x4" - - -class SchedulingMode(Enum): - """ Traffic scheduling modes (e.g., STATIC, DYNAMIC) """ - DYNAMIC = "DYNAMIC" - STATIC = "STATIC" - - -class DuplexMode(Enum): - """ DL/UL Duplex mode """ - FDD = "FDD" - TDD = "TDD" - -class ModulationType(Enum): - """DL/UL Modulation order.""" - QPSK = 'QPSK' - Q16 = '16QAM' - Q64 = '64QAM' - Q256 = '256QAM' - - -class LteSimulation(BaseSimulation): - """ Single-carrier LTE simulation. """ - - # Simulation config keywords contained in the test name - PARAM_FRAME_CONFIG = "tddconfig" - PARAM_BW = "bw" - PARAM_SCHEDULING = "scheduling" - PARAM_SCHEDULING_STATIC = "static" - PARAM_SCHEDULING_DYNAMIC = "dynamic" - PARAM_PATTERN = "pattern" - PARAM_TM = "tm" - PARAM_UL_PW = 'pul' - PARAM_DL_PW = 'pdl' - PARAM_BAND = "band" - PARAM_MIMO = "mimo" - PARAM_DL_MCS = 'dlmcs' - PARAM_UL_MCS = 'ulmcs' - PARAM_SSF = 'ssf' - PARAM_CFI = 'cfi' - PARAM_PAGING = 'paging' - PARAM_PHICH = 'phich' - PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer" - PARAM_DRX = 'drx' - - # Test config keywords - KEY_TBS_PATTERN = "tbs_pattern_on" - KEY_DL_256_QAM = "256_qam_dl" - KEY_UL_64_QAM = "64_qam_ul" - - # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY - DOWNLINK_SIGNAL_LEVEL_UNITS = "RSRP" - - # RSRP signal levels thresholds (as reported by Android) in dBm/15KHz. - # Excellent is set to -75 since callbox B Tx power is limited to -30 dBm - DOWNLINK_SIGNAL_LEVEL_DICTIONARY = { - 'excellent': -75, - 'high': -110, - 'medium': -115, - 'weak': -120 - } - - # Transmitted output power for the phone (dBm) - UPLINK_SIGNAL_LEVEL_DICTIONARY = { - 'max': 27, - 'high': 13, - 'medium': 3, - 'low': -20 - } - - # Bandwidth [MHz] to total RBs mapping - total_rbs_dictionary = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6} - - # Bandwidth [MHz] to RB group size - rbg_dictionary = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1} - - # Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE - min_dl_rbs_dictionary = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2} - - # Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE - min_ul_rbs_dictionary = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1} - - # Allowed bandwidth for each band. - allowed_bandwidth_dictionary = { - 1: [5, 10, 15, 20], - 2: [1.4, 3, 5, 10, 15, 20], - 3: [1.4, 3, 5, 10, 15, 20], - 4: [1.4, 3, 5, 10, 15, 20], - 5: [1.4, 3, 5, 10], - 7: [5, 10, 15, 20], - 8: [1.4, 3, 5, 10], - 10: [5, 10, 15, 20], - 11: [5, 10], - 12: [1.4, 3, 5, 10], - 13: [5, 10], - 14: [5, 10], - 17: [5, 10], - 18: [5, 10, 15], - 19: [5, 10, 15], - 20: [5, 10, 15, 20], - 21: [5, 10, 15], - 22: [5, 10, 15, 20], - 24: [5, 10], - 25: [1.4, 3, 5, 10, 15, 20], - 26: [1.4, 3, 5, 10, 15], - 27: [1.4, 3, 5, 10], - 28: [3, 5, 10, 15, 20], - 29: [3, 5, 10], - 30: [5, 10], - 31: [1.4, 3, 5], - 32: [5, 10, 15, 20], - 33: [5, 10, 15, 20], - 34: [5, 10, 15], - 35: [1.4, 3, 5, 10, 15, 20], - 36: [1.4, 3, 5, 10, 15, 20], - 37: [5, 10, 15, 20], - 38: [20], - 39: [5, 10, 15, 20], - 40: [5, 10, 15, 20], - 41: [5, 10, 15, 20], - 42: [5, 10, 15, 20], - 43: [5, 10, 15, 20], - 44: [3, 5, 10, 15, 20], - 45: [5, 10, 15, 20], - 46: [10, 20], - 47: [10, 20], - 48: [5, 10, 15, 20], - 49: [10, 20], - 50: [3, 5, 10, 15, 20], - 51: [3, 5], - 52: [5, 10, 15, 20], - 65: [5, 10, 15, 20], - 66: [1.4, 3, 5, 10, 15, 20], - 67: [5, 10, 15, 20], - 68: [5, 10, 15], - 69: [5], - 70: [5, 10, 15], - 71: [5, 10, 15, 20], - 72: [1.4, 3, 5], - 73: [1.4, 3, 5], - 74: [1.4, 3, 5, 10, 15, 20], - 75: [5, 10, 15, 20], - 76: [5], - 85: [5, 10], - 252: [20], - 255: [20] - } - - # Peak throughput lookup tables for each TDD subframe - # configuration and bandwidth - # yapf: disable - tdd_config4_tput_lut = { - 0: { - 5: {'DL': 3.82, 'UL': 2.63}, - 10: {'DL': 11.31,'UL': 9.03}, - 15: {'DL': 16.9, 'UL': 20.62}, - 20: {'DL': 22.88, 'UL': 28.43} - }, - 1: { - 5: {'DL': 6.13, 'UL': 4.08}, - 10: {'DL': 18.36, 'UL': 9.69}, - 15: {'DL': 28.62, 'UL': 14.21}, - 20: {'DL': 39.04, 'UL': 19.23} - }, - 2: { - 5: {'DL': 5.68, 'UL': 2.30}, - 10: {'DL': 25.51, 'UL': 4.68}, - 15: {'DL': 39.3, 'UL': 7.13}, - 20: {'DL': 53.64, 'UL': 9.72} - }, - 3: { - 5: {'DL': 8.26, 'UL': 3.45}, - 10: {'DL': 23.20, 'UL': 6.99}, - 15: {'DL': 35.35, 'UL': 10.75}, - 20: {'DL': 48.3, 'UL': 14.6} - }, - 4: { - 5: {'DL': 6.16, 'UL': 2.30}, - 10: {'DL': 26.77, 'UL': 4.68}, - 15: {'DL': 40.7, 'UL': 7.18}, - 20: {'DL': 55.6, 'UL': 9.73} - }, - 5: { - 5: {'DL': 6.91, 'UL': 1.12}, - 10: {'DL': 30.33, 'UL': 2.33}, - 15: {'DL': 46.04, 'UL': 3.54}, - 20: {'DL': 62.9, 'UL': 4.83} - }, - 6: { - 5: {'DL': 6.13, 'UL': 4.13}, - 10: {'DL': 14.79, 'UL': 11.98}, - 15: {'DL': 23.28, 'UL': 17.46}, - 20: {'DL': 31.75, 'UL': 23.95} - } - } - - tdd_config3_tput_lut = { - 0: { - 5: {'DL': 5.04, 'UL': 3.7}, - 10: {'DL': 15.11, 'UL': 17.56}, - 15: {'DL': 22.59, 'UL': 30.31}, - 20: {'DL': 30.41, 'UL': 41.61} - }, - 1: { - 5: {'DL': 8.07, 'UL': 5.66}, - 10: {'DL': 24.58, 'UL': 13.66}, - 15: {'DL': 39.05, 'UL': 20.68}, - 20: {'DL': 51.59, 'UL': 28.76} - }, - 2: { - 5: {'DL': 7.59, 'UL': 3.31}, - 10: {'DL': 34.08, 'UL': 6.93}, - 15: {'DL': 53.64, 'UL': 10.51}, - 20: {'DL': 70.55, 'UL': 14.41} - }, - 3: { - 5: {'DL': 10.9, 'UL': 5.0}, - 10: {'DL': 30.99, 'UL': 10.25}, - 15: {'DL': 48.3, 'UL': 15.81}, - 20: {'DL': 63.24, 'UL': 21.65} - }, - 4: { - 5: {'DL': 8.11, 'UL': 3.32}, - 10: {'DL': 35.74, 'UL': 6.95}, - 15: {'DL': 55.6, 'UL': 10.51}, - 20: {'DL': 72.72, 'UL': 14.41} - }, - 5: { - 5: {'DL': 9.28, 'UL': 1.57}, - 10: {'DL': 40.49, 'UL': 3.44}, - 15: {'DL': 62.9, 'UL': 5.23}, - 20: {'DL': 82.21, 'UL': 7.15} - }, - 6: { - 5: {'DL': 8.06, 'UL': 5.74}, - 10: {'DL': 19.82, 'UL': 17.51}, - 15: {'DL': 31.75, 'UL': 25.77}, - 20: {'DL': 42.12, 'UL': 34.91} - } - } - - tdd_config2_tput_lut = { - 0: { - 5: {'DL': 3.11, 'UL': 2.55}, - 10: {'DL': 9.93, 'UL': 11.1}, - 15: {'DL': 13.9, 'UL': 21.51}, - 20: {'DL': 20.02, 'UL': 41.66} - }, - 1: { - 5: {'DL': 5.33, 'UL': 4.27}, - 10: {'DL': 15.14, 'UL': 13.95}, - 15: {'DL': 33.84, 'UL': 19.73}, - 20: {'DL': 44.61, 'UL': 27.35} - }, - 2: { - 5: {'DL': 6.87, 'UL': 3.32}, - 10: {'DL': 17.06, 'UL': 6.76}, - 15: {'DL': 49.63, 'UL': 10.5}, - 20: {'DL': 65.2, 'UL': 14.41} - }, - 3: { - 5: {'DL': 5.41, 'UL': 4.17}, - 10: {'DL': 16.89, 'UL': 9.73}, - 15: {'DL': 44.29, 'UL': 15.7}, - 20: {'DL': 53.95, 'UL': 19.85} - }, - 4: { - 5: {'DL': 8.7, 'UL': 3.32}, - 10: {'DL': 17.58, 'UL': 6.76}, - 15: {'DL': 51.08, 'UL': 10.47}, - 20: {'DL': 66.45, 'UL': 14.38} - }, - 5: { - 5: {'DL': 9.46, 'UL': 1.55}, - 10: {'DL': 19.02, 'UL': 3.48}, - 15: {'DL': 58.89, 'UL': 5.23}, - 20: {'DL': 76.85, 'UL': 7.1} - }, - 6: { - 5: {'DL': 4.74, 'UL': 3.9}, - 10: {'DL': 12.32, 'UL': 13.37}, - 15: {'DL': 27.74, 'UL': 25.02}, - 20: {'DL': 35.48, 'UL': 32.95} - } - } - - tdd_config1_tput_lut = { - 0: { - 5: {'DL': 4.25, 'UL': 3.35}, - 10: {'DL': 8.38, 'UL': 7.22}, - 15: {'DL': 12.41, 'UL': 13.91}, - 20: {'DL': 16.27, 'UL': 24.09} - }, - 1: { - 5: {'DL': 7.28, 'UL': 4.61}, - 10: {'DL': 14.73, 'UL': 9.69}, - 15: {'DL': 21.91, 'UL': 13.86}, - 20: {'DL': 27.63, 'UL': 17.18} - }, - 2: { - 5: {'DL': 10.37, 'UL': 2.27}, - 10: {'DL': 20.92, 'UL': 4.66}, - 15: {'DL': 31.01, 'UL': 7.04}, - 20: {'DL': 42.03, 'UL': 9.75} - }, - 3: { - 5: {'DL': 9.25, 'UL': 3.44}, - 10: {'DL': 18.38, 'UL': 6.95}, - 15: {'DL': 27.59, 'UL': 10.62}, - 20: {'DL': 34.85, 'UL': 13.45} - }, - 4: { - 5: {'DL': 10.71, 'UL': 2.26}, - 10: {'DL': 21.54, 'UL': 4.67}, - 15: {'DL': 31.91, 'UL': 7.2}, - 20: {'DL': 43.35, 'UL': 9.74} - }, - 5: { - 5: {'DL': 12.34, 'UL': 1.08}, - 10: {'DL': 24.78, 'UL': 2.34}, - 15: {'DL': 36.68, 'UL': 3.57}, - 20: {'DL': 49.84, 'UL': 4.81} - }, - 6: { - 5: {'DL': 5.76, 'UL': 4.41}, - 10: {'DL': 11.68, 'UL': 9.7}, - 15: {'DL': 17.34, 'UL': 17.95}, - 20: {'DL': 23.5, 'UL': 23.42} - } - } - # yapf: enable - - # Peak throughput lookup table dictionary - tdd_config_tput_lut_dict = { - 'TDD_CONFIG1': - tdd_config1_tput_lut, # DL 256QAM, UL 64QAM & TBS turned OFF - 'TDD_CONFIG2': - tdd_config2_tput_lut, # DL 256QAM, UL 64 QAM turned ON & TBS OFF - 'TDD_CONFIG3': - tdd_config3_tput_lut, # DL 256QAM, UL 64QAM & TBS turned ON - 'TDD_CONFIG4': - tdd_config4_tput_lut # DL 256QAM, UL 64 QAM turned OFF & TBS ON - } - - class BtsConfig(BaseSimulation.BtsConfig): - """ Extension of the BaseBtsConfig to implement parameters that are - exclusive to LTE. - - Attributes: - band: an integer indicating the required band number. - dlul_config: an integer indicating the TDD config number. - ssf_config: an integer indicating the Special Sub-Frame config. - bandwidth: a float indicating the required channel bandwidth. - mimo_mode: an instance of LteSimulation.MimoMode indicating the - required MIMO mode for the downlink signal. - transmission_mode: an instance of LteSimulation.TransmissionMode - indicating the required TM. - scheduling_mode: an instance of LteSimulation.SchedulingMode - indicating wether to use Static or Dynamic scheduling. - dl_rbs: an integer indicating the number of downlink RBs - ul_rbs: an integer indicating the number of uplink RBs - dl_mcs: an integer indicating the MCS for the downlink signal - ul_mcs: an integer indicating the MCS for the uplink signal - dl_modulation_order: a string indicating a DL modulation scheme - ul_modulation_order: a string indicating an UL modulation scheme - tbs_pattern_on: a boolean indicating whether full allocation mode - should be used or not - dl_channel: an integer indicating the downlink channel number - cfi: an integer indicating the Control Format Indicator - paging_cycle: an integer indicating the paging cycle duration in - milliseconds - phich: a string indicating the PHICH group size parameter - drx_connected_mode: a boolean indicating whether cDRX mode is - on or off - drx_on_duration_timer: number of PDCCH subframes representing - DRX on duration - drx_inactivity_timer: number of PDCCH subframes to wait before - entering DRX mode - drx_retransmission_timer: number of consecutive PDCCH subframes - to wait for retransmission - drx_long_cycle: number of subframes representing one long DRX cycle. - One cycle consists of DRX sleep + DRX on duration - drx_long_cycle_offset: number representing offset in range - 0 to drx_long_cycle - 1 - """ - def __init__(self): - """ Initialize the base station config by setting all its - parameters to None. """ - super().__init__() - self.band = None - self.dlul_config = None - self.ssf_config = None - self.bandwidth = None - self.mimo_mode = None - self.transmission_mode = None - self.scheduling_mode = None - self.dl_rbs = None - self.ul_rbs = None - self.dl_mcs = None - self.ul_mcs = None - self.dl_modulation_order = None - self.ul_modulation_order = None - self.tbs_pattern_on = None - self.dl_channel = None - self.cfi = None - self.paging_cycle = None - self.phich = None - self.drx_connected_mode = None - self.drx_on_duration_timer = None - self.drx_inactivity_timer = None - self.drx_retransmission_timer = None - self.drx_long_cycle = None - self.drx_long_cycle_offset = None - - def __init__(self, simulator, log, dut, test_config, calibration_table): - """ Initializes the simulator for a single-carrier LTE simulation. - - Loads a simple LTE simulation enviroment with 1 basestation. - - Args: - simulator: a cellular simulator controller - log: a logger handle - dut: the android device handler - test_config: test configuration obtained from the config file - calibration_table: a dictionary containing path losses for - different bands. - - """ - - super().__init__(simulator, log, dut, test_config, calibration_table) - - if not dut.droid.telephonySetPreferredNetworkTypesForSubscription( - NETWORK_MODE_LTE_ONLY, - dut.droid.subscriptionGetDefaultSubId()): - log.error("Couldn't set preferred network type.") - else: - log.info("Preferred network type set.") - - # Get TBS pattern setting from the test configuration - if self.KEY_TBS_PATTERN not in test_config: - self.log.warning("The key '{}' is not set in the config file. " - "Setting to true by default.".format( - self.KEY_TBS_PATTERN)) - self.primary_config.tbs_pattern_on = test_config.get( - self.KEY_TBS_PATTERN, True) - - # Get the 256-QAM setting from the test configuration - if self.KEY_DL_256_QAM not in test_config: - self.log.warning("The key '{}' is not set in the config file. " - "Setting to false by default.".format( - self.KEY_DL_256_QAM)) - - self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False) - - if self.dl_256_qam: - if not self.simulator.LTE_SUPPORTS_DL_256QAM: - self.log.warning("The key '{}' is set to true but the " - "simulator doesn't support that modulation " - "order.".format(self.KEY_DL_256_QAM)) - self.dl_256_qam = False - else: - self.primary_config.dl_modulation_order = ModulationType.Q256 - - else: - self.log.warning('dl modulation 256QAM is not specified in config, ' - 'setting to default value 64QAM') - self.primary_config.dl_modulation_order = ModulationType.Q64 - # Get the 64-QAM setting from the test configuration - if self.KEY_UL_64_QAM not in test_config: - self.log.warning("The key '{}' is not set in the config file. " - "Setting to false by default.".format( - self.KEY_UL_64_QAM)) - - self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False) - - if self.ul_64_qam: - if not self.simulator.LTE_SUPPORTS_UL_64QAM: - self.log.warning("The key '{}' is set to true but the " - "simulator doesn't support that modulation " - "order.".format(self.KEY_UL_64_QAM)) - self.ul_64_qam = False - else: - self.primary_config.ul_modulation_order = ModulationType.Q64 - else: - self.log.warning('ul modulation 64QAM is not specified in config, ' - 'setting to default value 16QAM') - self.primary_config.ul_modulation_order = ModulationType.Q16 - - self.simulator.configure_bts(self.primary_config) - - def setup_simulator(self): - """ Do initial configuration in the simulator. """ - self.simulator.setup_lte_scenario() - - def parse_parameters(self, parameters): - """ Configs an LTE simulation using a list of parameters. - - Calls the parent method first, then consumes parameters specific to LTE. - - Args: - parameters: list of parameters - """ - - # Instantiate a new configuration object - new_config = self.BtsConfig() - - # Setup band - - values = self.consume_parameter(parameters, self.PARAM_BAND, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter '{}' followed by " - "the required band number.".format(self.PARAM_BAND)) - - new_config.band = values[1] - - # Set TDD-only configs - if self.get_duplex_mode(new_config.band) == DuplexMode.TDD: - - # Sub-frame DL/UL config - values = self.consume_parameter(parameters, - self.PARAM_FRAME_CONFIG, 1) - if not values: - raise ValueError( - "When a TDD band is selected the frame " - "structure has to be indicated with the '{}' " - "parameter followed by a number from 0 to 6.".format( - self.PARAM_FRAME_CONFIG)) - - new_config.dlul_config = int(values[1]) - - # Special Sub-Frame configuration - values = self.consume_parameter(parameters, self.PARAM_SSF, 1) - - if not values: - self.log.warning( - 'The {} parameter was not provided. Setting ' - 'Special Sub-Frame config to 6 by default.'.format( - self.PARAM_SSF)) - new_config.ssf_config = 6 - else: - new_config.ssf_config = int(values[1]) - - # Setup bandwidth - - values = self.consume_parameter(parameters, self.PARAM_BW, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter {} followed by an " - "int value (to indicate 1.4 MHz use 14).".format( - self.PARAM_BW)) - - bw = float(values[1]) - - if bw == 14: - bw = 1.4 - - new_config.bandwidth = bw - - # Setup mimo mode - - values = self.consume_parameter(parameters, self.PARAM_MIMO, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter '{}' followed by the " - "mimo mode.".format(self.PARAM_MIMO)) - - for mimo_mode in MimoMode: - if values[1] == mimo_mode.value: - new_config.mimo_mode = mimo_mode - break - else: - raise ValueError("The {} parameter needs to be followed by either " - "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO)) - - if (new_config.mimo_mode == MimoMode.MIMO_4x4 - and not self.simulator.LTE_SUPPORTS_4X4_MIMO): - raise ValueError("The test requires 4x4 MIMO, but that is not " - "supported by the cellular simulator.") - - # Setup transmission mode - - values = self.consume_parameter(parameters, self.PARAM_TM, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter {} followed by an " - "int value from 1 to 4 indicating transmission mode.".format( - self.PARAM_TM)) - - for tm in TransmissionMode: - if values[1] == tm.value[2:]: - new_config.transmission_mode = tm - break - else: - raise ValueError("The {} parameter needs to be followed by either " - "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format( - self.PARAM_MIMO)) - - # Setup scheduling mode - - values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1) - - if not values: - new_config.scheduling_mode = SchedulingMode.STATIC - self.log.warning( - "The test name does not include the '{}' parameter. Setting to " - "static by default.".format(self.PARAM_SCHEDULING)) - elif values[1] == self.PARAM_SCHEDULING_DYNAMIC: - new_config.scheduling_mode = SchedulingMode.DYNAMIC - elif values[1] == self.PARAM_SCHEDULING_STATIC: - new_config.scheduling_mode = SchedulingMode.STATIC - else: - raise ValueError( - "The test name parameter '{}' has to be followed by either " - "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING)) - - if new_config.scheduling_mode == SchedulingMode.STATIC: - - values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2) - - if not values: - self.log.warning( - "The '{}' parameter was not set, using 100% RBs for both " - "DL and UL. To set the percentages of total RBs include " - "the '{}' parameter followed by two ints separated by an " - "underscore indicating downlink and uplink percentages.". - format(self.PARAM_PATTERN, self.PARAM_PATTERN)) - dl_pattern = 100 - ul_pattern = 100 - else: - dl_pattern = int(values[1]) - ul_pattern = int(values[2]) - - if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100): - raise ValueError( - "The scheduling pattern parameters need to be two " - "positive numbers between 0 and 100.") - - new_config.dl_rbs, new_config.ul_rbs = ( - self.allocation_percentages_to_rbs( - new_config.bandwidth, new_config.transmission_mode, - dl_pattern, ul_pattern)) - - # Look for a DL MCS configuration in the test parameters. If it is - # not present, use a default value. - dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, 1) - - if dlmcs: - new_config.dl_mcs = int(dlmcs[1]) - else: - self.log.warning( - 'The test name does not include the {} parameter. Setting ' - 'to the max value by default'.format(self.PARAM_DL_MCS)) - if self.dl_256_qam and new_config.bandwidth == 1.4: - new_config.dl_mcs = 26 - elif (not self.dl_256_qam - and self.primary_config.tbs_pattern_on - and new_config.bandwidth != 1.4): - new_config.dl_mcs = 28 - else: - new_config.dl_mcs = 27 - - # Look for an UL MCS configuration in the test parameters. If it is - # not present, use a default value. - ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, 1) - - if ulmcs: - new_config.ul_mcs = int(ulmcs[1]) - else: - self.log.warning( - 'The test name does not include the {} parameter. Setting ' - 'to the max value by default'.format(self.PARAM_UL_MCS)) - if self.ul_64_qam: - new_config.ul_mcs = 28 - else: - new_config.ul_mcs = 23 - - # Configure the simulation for DRX mode - - drx = self.consume_parameter(parameters, self.PARAM_DRX, 5) - - if drx and len(drx) == 6: - new_config.drx_connected_mode = True - new_config.drx_on_duration_timer = drx[1] - new_config.drx_inactivity_timer = drx[2] - new_config.drx_retransmission_timer = drx[3] - new_config.drx_long_cycle = drx[4] - try: - long_cycle = int(drx[4]) - long_cycle_offset = int(drx[5]) - if long_cycle_offset in range(0, long_cycle): - new_config.drx_long_cycle_offset = long_cycle_offset - else: - self.log.error(("The cDRX long cycle offset must be in the " - "range 0 to (long cycle - 1). Setting " - "long cycle offset to 0")) - new_config.drx_long_cycle_offset = 0 - - except ValueError: - self.log.error(("cDRX long cycle and long cycle offset " - "must be integers. Disabling cDRX mode.")) - new_config.drx_connected_mode = False - else: - self.log.warning(("DRX mode was not configured properly. " - "Please provide the following 5 values: " - "1) DRX on duration timer " - "2) Inactivity timer " - "3) Retransmission timer " - "4) Long DRX cycle duration " - "5) Long DRX cycle offset " - "Example: drx_2_6_16_20_0")) - - # Setup LTE RRC status change function and timer for LTE idle test case - values = self.consume_parameter(parameters, - self.PARAM_RRC_STATUS_CHANGE_TIMER, 1) - if not values: - self.log.info( - "The test name does not include the '{}' parameter. Disabled " - "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER)) - self.simulator.set_lte_rrc_state_change_timer(False) - else: - timer = int(values[1]) - self.simulator.set_lte_rrc_state_change_timer(True, timer) - self.rrc_sc_timer = timer - - # Channel Control Indicator - values = self.consume_parameter(parameters, self.PARAM_CFI, 1) - - if not values: - self.log.warning('The {} parameter was not provided. Setting ' - 'CFI to BESTEFFORT.'.format(self.PARAM_CFI)) - new_config.cfi = 'BESTEFFORT' - else: - new_config.cfi = values[1] - - # PHICH group size - values = self.consume_parameter(parameters, self.PARAM_PHICH, 1) - - if not values: - self.log.warning('The {} parameter was not provided. Setting ' - 'PHICH group size to 1 by default.'.format( - self.PARAM_PHICH)) - new_config.phich = '1' - else: - if values[1] == '16': - new_config.phich = '1/6' - elif values[1] == '12': - new_config.phich = '1/2' - elif values[1] in ['1/6', '1/2', '1', '2']: - new_config.phich = values[1] - else: - raise ValueError('The {} parameter can only be followed by 1,' - '2, 1/2 (or 12) and 1/6 (or 16).'.format( - self.PARAM_PHICH)) - - # Paging cycle duration - values = self.consume_parameter(parameters, self.PARAM_PAGING, 1) - - if not values: - self.log.warning('The {} parameter was not provided. Setting ' - 'paging cycle duration to 1280 ms by ' - 'default.'.format(self.PARAM_PAGING)) - new_config.paging_cycle = 1280 - else: - try: - new_config.paging_cycle = int(values[1]) - except ValueError: - raise ValueError( - 'The {} parameter has to be followed by the paging cycle ' - 'duration in milliseconds.'.format(self.PARAM_PAGING)) - - # Get uplink power - - ul_power = self.get_uplink_power_from_parameters(parameters) - - # Power is not set on the callbox until after the simulation is - # started. Saving this value in a variable for later - self.sim_ul_power = ul_power - - # Get downlink power - - dl_power = self.get_downlink_power_from_parameters(parameters) - - # Power is not set on the callbox until after the simulation is - # started. Saving this value in a variable for later - self.sim_dl_power = dl_power - - # Setup the base station with the obtained configuration and then save - # these parameters in the current configuration object - self.simulator.configure_bts(new_config) - self.primary_config.incorporate(new_config) - - # Now that the band is set, calibrate the link if necessary - self.load_pathloss_if_required() - - def calibrated_downlink_rx_power(self, bts_config, rsrp): - """ LTE simulation overrides this method so that it can convert from - RSRP to total signal power transmitted from the basestation. - - Args: - bts_config: the current configuration at the base station - rsrp: desired rsrp, contained in a key value pair - """ - - power = self.rsrp_to_signal_power(rsrp, bts_config) - - self.log.info( - "Setting downlink signal level to {} RSRP ({} dBm)".format( - rsrp, power)) - - # Use parent method to calculate signal level - return super().calibrated_downlink_rx_power(bts_config, power) - - def downlink_calibration(self, rat=None, power_units_conversion_func=None): - """ Computes downlink path loss and returns the calibration value. - - See base class implementation for details. - - Args: - rat: ignored, replaced by 'lteRsrp' - power_units_conversion_func: ignored, replaced by - self.rsrp_to_signal_power - - Returns: - Dowlink calibration value and measured DL power. Note that the - phone only reports RSRP of the primary chain - """ - - return super().downlink_calibration( - rat='lteDbm', - power_units_conversion_func=self.rsrp_to_signal_power) - - def rsrp_to_signal_power(self, rsrp, bts_config): - """ Converts rsrp to total band signal power - - RSRP is measured per subcarrier, so total band power needs to be - multiplied by the number of subcarriers being used. - - Args: - rsrp: desired rsrp in dBm - bts_config: a base station configuration object - Returns: - Total band signal power in dBm - """ - - bandwidth = bts_config.bandwidth - - if bandwidth == 20: # 100 RBs - power = rsrp + 30.79 - elif bandwidth == 15: # 75 RBs - power = rsrp + 29.54 - elif bandwidth == 10: # 50 RBs - power = rsrp + 27.78 - elif bandwidth == 5: # 25 RBs - power = rsrp + 24.77 - elif bandwidth == 3: # 15 RBs - power = rsrp + 22.55 - elif bandwidth == 1.4: # 6 RBs - power = rsrp + 18.57 - else: - raise ValueError("Invalid bandwidth value.") - - return power - - def maximum_downlink_throughput(self): - """ Calculates maximum achievable downlink throughput in the current - simulation state. - - Returns: - Maximum throughput in mbps. - - """ - - return self.bts_maximum_downlink_throughtput(self.primary_config) - - def bts_maximum_downlink_throughtput(self, bts_config): - """ Calculates maximum achievable downlink throughput for a single - base station from its configuration object. - - Args: - bts_config: a base station configuration object. - - Returns: - Maximum throughput in mbps. - - """ - if bts_config.mimo_mode == MimoMode.MIMO_1x1: - streams = 1 - elif bts_config.mimo_mode == MimoMode.MIMO_2x2: - streams = 2 - elif bts_config.mimo_mode == MimoMode.MIMO_4x4: - streams = 4 - else: - raise ValueError('Unable to calculate maximum downlink throughput ' - 'because the MIMO mode has not been set.') - - bandwidth = bts_config.bandwidth - rb_ratio = bts_config.dl_rbs / self.total_rbs_dictionary[bandwidth] - mcs = bts_config.dl_mcs - - max_rate_per_stream = None - - tdd_subframe_config = bts_config.dlul_config - duplex_mode = self.get_duplex_mode(bts_config.band) - - if duplex_mode == DuplexMode.TDD: - if self.dl_256_qam: - if mcs == 27: - if bts_config.tbs_pattern_on: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG3'][tdd_subframe_config][bandwidth][ - 'DL'] - else: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG2'][tdd_subframe_config][bandwidth][ - 'DL'] - else: - if mcs == 28: - if bts_config.tbs_pattern_on: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG4'][tdd_subframe_config][bandwidth][ - 'DL'] - else: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG1'][tdd_subframe_config][bandwidth][ - 'DL'] - - elif duplex_mode == DuplexMode.FDD: - if (not self.dl_256_qam and bts_config.tbs_pattern_on - and mcs == 28): - max_rate_per_stream = { - 3: 9.96, - 5: 17.0, - 10: 34.7, - 15: 52.7, - 20: 72.2 - }.get(bandwidth, None) - if (not self.dl_256_qam and bts_config.tbs_pattern_on - and mcs == 27): - max_rate_per_stream = { - 1.4: 2.94, - }.get(bandwidth, None) - elif (not self.dl_256_qam and not bts_config.tbs_pattern_on - and mcs == 27): - max_rate_per_stream = { - 1.4: 2.87, - 3: 7.7, - 5: 14.4, - 10: 28.7, - 15: 42.3, - 20: 57.7 - }.get(bandwidth, None) - elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 27: - max_rate_per_stream = { - 3: 13.2, - 5: 22.9, - 10: 46.3, - 15: 72.2, - 20: 93.9 - }.get(bandwidth, None) - elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 26: - max_rate_per_stream = { - 1.4: 3.96, - }.get(bandwidth, None) - elif (self.dl_256_qam and not bts_config.tbs_pattern_on - and mcs == 27): - max_rate_per_stream = { - 3: 11.3, - 5: 19.8, - 10: 44.1, - 15: 68.1, - 20: 88.4 - }.get(bandwidth, None) - elif (self.dl_256_qam and not bts_config.tbs_pattern_on - and mcs == 26): - max_rate_per_stream = { - 1.4: 3.96, - }.get(bandwidth, None) - - if not max_rate_per_stream: - raise NotImplementedError( - "The calculation for tbs pattern = {} " - "and mcs = {} is not implemented.".format( - "FULLALLOCATION" if bts_config.tbs_pattern_on else "OFF", - mcs)) - - return max_rate_per_stream * streams * rb_ratio - - def maximum_uplink_throughput(self): - """ Calculates maximum achievable uplink throughput in the current - simulation state. - - Returns: - Maximum throughput in mbps. - - """ - - return self.bts_maximum_uplink_throughtput(self.primary_config) - - def bts_maximum_uplink_throughtput(self, bts_config): - """ Calculates maximum achievable uplink throughput for the selected - basestation from its configuration object. - - Args: - bts_config: an LTE base station configuration object. - - Returns: - Maximum throughput in mbps. - - """ - - bandwidth = bts_config.bandwidth - rb_ratio = bts_config.ul_rbs / self.total_rbs_dictionary[bandwidth] - mcs = bts_config.ul_mcs - - max_rate_per_stream = None - - tdd_subframe_config = bts_config.dlul_config - duplex_mode = self.get_duplex_mode(bts_config.band) - - if duplex_mode == DuplexMode.TDD: - if self.ul_64_qam: - if mcs == 28: - if bts_config.tbs_pattern_on: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG3'][tdd_subframe_config][bandwidth][ - 'UL'] - else: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG2'][tdd_subframe_config][bandwidth][ - 'UL'] - else: - if mcs == 23: - if bts_config.tbs_pattern_on: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG4'][tdd_subframe_config][bandwidth][ - 'UL'] - else: - max_rate_per_stream = self.tdd_config_tput_lut_dict[ - 'TDD_CONFIG1'][tdd_subframe_config][bandwidth][ - 'UL'] - - elif duplex_mode == DuplexMode.FDD: - if mcs == 23 and not self.ul_64_qam: - max_rate_per_stream = { - 1.4: 2.85, - 3: 7.18, - 5: 12.1, - 10: 24.5, - 15: 36.5, - 20: 49.1 - }.get(bandwidth, None) - elif mcs == 28 and self.ul_64_qam: - max_rate_per_stream = { - 1.4: 4.2, - 3: 10.5, - 5: 17.2, - 10: 35.3, - 15: 53.0, - 20: 72.6 - }.get(bandwidth, None) - - if not max_rate_per_stream: - raise NotImplementedError( - "The calculation fir mcs = {} is not implemented.".format( - "FULLALLOCATION" if bts_config.tbs_pattern_on else "OFF", - mcs)) - - return max_rate_per_stream * rb_ratio - - def allocation_percentages_to_rbs(self, bw, tm, dl, ul): - """ Converts usage percentages to number of DL/UL RBs - - Because not any number of DL/UL RBs can be obtained for a certain - bandwidth, this function calculates the number of RBs that most - closely matches the desired DL/UL percentages. - - Args: - bw: the bandwidth for the which the RB configuration is requested - tm: the transmission in which the base station will be operating - dl: desired percentage of downlink RBs - ul: desired percentage of uplink RBs - Returns: - a tuple indicating the number of downlink and uplink RBs - """ - - # Validate the arguments - if (not 0 <= dl <= 100) or (not 0 <= ul <= 100): - raise ValueError("The percentage of DL and UL RBs have to be two " - "positive between 0 and 100.") - - # Get min and max values from tables - max_rbs = self.total_rbs_dictionary[bw] - min_dl_rbs = self.min_dl_rbs_dictionary[bw] - min_ul_rbs = self.min_ul_rbs_dictionary[bw] - - def percentage_to_amount(min_val, max_val, percentage): - """ Returns the integer between min_val and max_val that is closest - to percentage/100*max_val - """ - - # Calculate the value that corresponds to the required percentage. - closest_int = round(max_val * percentage / 100) - # Cannot be less than min_val - closest_int = max(closest_int, min_val) - # RBs cannot be more than max_rbs - closest_int = min(closest_int, max_val) - - return closest_int - - # Calculate the number of DL RBs - - # Get the number of DL RBs that corresponds to - # the required percentage. - desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs, - max_val=max_rbs, - percentage=dl) - - if tm == TransmissionMode.TM3 or tm == TransmissionMode.TM4: - - # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a - # multiple of the RBG size - - if desired_dl_rbs == max_rbs: - dl_rbs = max_rbs - else: - dl_rbs = (math.ceil(desired_dl_rbs / self.rbg_dictionary[bw]) * - self.rbg_dictionary[bw]) - - else: - # The other TMs allow any number of RBs between 1 and max_rbs - dl_rbs = desired_dl_rbs - - # Calculate the number of UL RBs - - # Get the number of UL RBs that corresponds - # to the required percentage - desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs, - max_val=max_rbs, - percentage=ul) - - # Create a list of all possible UL RBs assignment - # The standard allows any number that can be written as - # 2**a * 3**b * 5**c for any combination of a, b and c. - - def pow_range(max_value, base): - """ Returns a range of all possible powers of base under - the given max_value. - """ - return range(int(math.ceil(math.log(max_value, base)))) - - possible_ul_rbs = [ - 2**a * 3**b * 5**c for a in pow_range(max_rbs, 2) - for b in pow_range(max_rbs, 3) - for c in pow_range(max_rbs, 5) - if 2**a * 3**b * 5**c <= max_rbs] # yapf: disable - - # Find the value in the list that is closest to desired_ul_rbs - differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs] - ul_rbs = possible_ul_rbs[differences.index(min(differences))] - - # Report what are the obtained RB percentages - self.log.info("Requested a {}% / {}% RB allocation. Closest possible " - "percentages are {}% / {}%.".format( - dl, ul, round(100 * dl_rbs / max_rbs), - round(100 * ul_rbs / max_rbs))) - - return dl_rbs, ul_rbs - - def calibrate(self, band): - """ Calculates UL and DL path loss if it wasn't done before - - Before running the base class implementation, configure the base station - to only use one downlink antenna with maximum bandwidth. - - Args: - band: the band that is currently being calibrated. - """ - - # Save initial values in a configuration object so they can be restored - restore_config = self.BtsConfig() - restore_config.mimo_mode = self.primary_config.mimo_mode - restore_config.transmission_mode = self.primary_config.transmission_mode - restore_config.bandwidth = self.primary_config.bandwidth - - # Set up a temporary calibration configuration. - temporary_config = self.BtsConfig() - temporary_config.mimo_mode = MimoMode.MIMO_1x1 - temporary_config.transmission_mode = TransmissionMode.TM1 - temporary_config.bandwidth = max( - self.allowed_bandwidth_dictionary[int(band)]) - self.simulator.configure_bts(temporary_config) - self.primary_config.incorporate(temporary_config) - - super().calibrate(band) - - # Restore values as they were before changing them for calibration. - self.simulator.configure_bts(restore_config) - self.primary_config.incorporate(restore_config) - - def start_traffic_for_calibration(self): - """ - If TBS pattern is set to full allocation, there is no need to start - IP traffic. - """ - if not self.primary_config.tbs_pattern_on: - super().start_traffic_for_calibration() - - def stop_traffic_for_calibration(self): - """ - If TBS pattern is set to full allocation, IP traffic wasn't started - """ - if not self.primary_config.tbs_pattern_on: - super().stop_traffic_for_calibration() - - def get_duplex_mode(self, band): - """ Determines if the band uses FDD or TDD duplex mode - - Args: - band: a band number - Returns: - an variable of class DuplexMode indicating if band is FDD or TDD - """ - - if 33 <= int(band) <= 46: - return DuplexMode.TDD - else: - return DuplexMode.FDD - - def get_measured_ul_power(self, samples=5, wait_after_sample=3): - """ Calculates UL power using measurements from the callbox and the - calibration data. - - Args: - samples: the numble of samples to average - wait_after_sample: time in seconds to wait in between samples - - Returns: - the ul power at the UE antenna ports in dBs - """ - ul_power_sum = 0 - samples_left = samples - - while samples_left > 0: - ul_power_sum += self.simulator.get_measured_pusch_power() - samples_left -= 1 - time.sleep(wait_after_sample) - - # Got enough samples, return calibrated average - if self.dl_path_loss: - return ul_power_sum / samples + self.ul_path_loss - else: - self.log.warning('No uplink calibration data. Returning ' - 'uncalibrated values as measured by the ' - 'callbox.') - return ul_power_sum / samples diff --git a/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py deleted file mode 100644 index 4d4aeebf8b..0000000000 --- a/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018 - The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import ntpath -import time - -from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim -from acts.controllers.anritsu_lib.md8475a import BtsNumber -from acts.controllers.anritsu_lib.md8475a import BtsPacketRate -from acts.test_utils.power.tel_simulations.BaseSimulation import BaseSimulation -from acts.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY - - -class UmtsSimulation(BaseSimulation): - """ Single base station simulation. """ - - # Simulation config files in the callbox computer. - # These should be replaced in the future by setting up - # the same configuration manually. - - UMTS_BASIC_SIM_FILE = 'SIM_default_WCDMA.wnssp' - - UMTS_R99_CELL_FILE = 'CELL_WCDMA_R99_config.wnscp' - - UMTS_R7_CELL_FILE = 'CELL_WCDMA_R7_config.wnscp' - - UMTS_R8_CELL_FILE = 'CELL_WCDMA_R8_config.wnscp' - - # Test name parameters - PARAM_RELEASE_VERSION = "r" - PARAM_RELEASE_VERSION_99 = "99" - PARAM_RELEASE_VERSION_8 = "8" - PARAM_RELEASE_VERSION_7 = "7" - PARAM_UL_PW = 'pul' - PARAM_DL_PW = 'pdl' - PARAM_BAND = "band" - PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer" - - # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY - DOWNLINK_SIGNAL_LEVEL_UNITS = "RSCP" - - # RSCP signal levels thresholds (as reported by Android). Units are dBm - # Using LTE thresholds + 24 dB to have equivalent SPD - # 24 dB comes from 10 * log10(3.84 MHz / 15 KHz) - - DOWNLINK_SIGNAL_LEVEL_DICTIONARY = { - 'excellent': -51, - 'high': -76, - 'medium': -86, - 'weak': -96 - } - - # Transmitted output power for the phone - # Stronger Tx power means that the signal received by the BTS is weaker - # Units are dBm - - UPLINK_SIGNAL_LEVEL_DICTIONARY = { - 'low': -20, - 'medium': 8, - 'high': 15, - 'max': 23 - } - - # Converts packet rate to the throughput that can be actually obtained in - # Mbits/s - - packet_rate_to_dl_throughput = { - BtsPacketRate.WCDMA_DL384K_UL64K: 0.362, - BtsPacketRate.WCDMA_DL21_6M_UL5_76M: 18.5, - BtsPacketRate.WCDMA_DL43_2M_UL5_76M: 36.9 - } - - packet_rate_to_ul_throughput = { - BtsPacketRate.WCDMA_DL384K_UL64K: 0.0601, - BtsPacketRate.WCDMA_DL21_6M_UL5_76M: 5.25, - BtsPacketRate.WCDMA_DL43_2M_UL5_76M: 5.25 - } - - def __init__(self, simulator, log, dut, test_config, calibration_table): - """ Initializes the cellular simulator for a UMTS simulation. - - Loads a simple UMTS simulation enviroment with 1 basestation. It also - creates the BTS handle so we can change the parameters as desired. - - Args: - simulator: a cellular simulator controller - log: a logger handle - dut: the android device handler - test_config: test configuration obtained from the config file - calibration_table: a dictionary containing path losses for - different bands. - - """ - # The UMTS simulation relies on the cellular simulator to be a MD8475 - if not isinstance(self.simulator, anritsusim.MD8475CellularSimulator): - raise ValueError('The UMTS simulation relies on the simulator to ' - 'be an Anritsu MD8475 A/B instrument.') - - # The Anritsu controller needs to be unwrapped before calling - # super().__init__ because setup_simulator() requires self.anritsu and - # will be called during the parent class initialization. - self.anritsu = self.simulator.anritsu - self.bts1 = self.anritsu.get_BTS(BtsNumber.BTS1) - - super().__init__(simulator, log, dut, test_config, calibration_table) - - if not dut.droid.telephonySetPreferredNetworkTypesForSubscription( - NETWORK_MODE_WCDMA_ONLY, - dut.droid.subscriptionGetDefaultSubId()): - log.error("Coold not set preferred network type.") - else: - log.info("Preferred network type set.") - - self.release_version = None - self.packet_rate = None - - def setup_simulator(self): - """ Do initial configuration in the simulator. """ - - # Load callbox config files - callbox_config_path = self.CALLBOX_PATH_FORMAT_STR.format( - self.anritsu._md8475_version) - - self.anritsu.load_simulation_paramfile( - ntpath.join(callbox_config_path, self.UMTS_BASIC_SIM_FILE)) - - # Start simulation if it wasn't started - self.anritsu.start_simulation() - - def parse_parameters(self, parameters): - """ Configs an UMTS simulation using a list of parameters. - - Calls the parent method and consumes parameters specific to UMTS. - - Args: - parameters: list of parameters - """ - - # Setup band - - values = self.consume_parameter(parameters, self.PARAM_BAND, 1) - - if not values: - raise ValueError( - "The test name needs to include parameter '{}' followed by " - "the required band number.".format(self.PARAM_BAND)) - - self.set_band(self.bts1, values[1]) - self.load_pathloss_if_required() - - # Setup release version - - values = self.consume_parameter(parameters, self.PARAM_RELEASE_VERSION, - 1) - - if not values or values[1] not in [ - self.PARAM_RELEASE_VERSION_7, self.PARAM_RELEASE_VERSION_8, - self.PARAM_RELEASE_VERSION_99 - ]: - raise ValueError( - "The test name needs to include the parameter {} followed by a " - "valid release version.".format(self.PARAM_RELEASE_VERSION)) - - self.set_release_version(self.bts1, values[1]) - - # Setup W-CDMA RRC status change and CELL_DCH timer for idle test case - - values = self.consume_parameter(parameters, - self.PARAM_RRC_STATUS_CHANGE_TIMER, 1) - if not values: - self.log.info( - "The test name does not include the '{}' parameter. Disabled " - "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER)) - self.anritsu.set_umts_rrc_status_change(False) - else: - self.rrc_sc_timer = int(values[1]) - self.anritsu.set_umts_rrc_status_change(True) - self.anritsu.set_umts_dch_stat_timer(self.rrc_sc_timer) - - # Setup uplink power - - ul_power = self.get_uplink_power_from_parameters(parameters) - - # Power is not set on the callbox until after the simulation is - # started. Saving this value in a variable for later - self.sim_ul_power = ul_power - - # Setup downlink power - - dl_power = self.get_downlink_power_from_parameters(parameters) - - # Power is not set on the callbox until after the simulation is - # started. Saving this value in a variable for later - self.sim_dl_power = dl_power - - def set_release_version(self, bts, release_version): - """ Sets the release version. - - Loads the cell parameter file matching the requested release version. - Does nothing is release version is already the one requested. - - """ - - if release_version == self.release_version: - self.log.info( - "Release version is already {}.".format(release_version)) - return - if release_version == self.PARAM_RELEASE_VERSION_99: - - cell_parameter_file = self.UMTS_R99_CELL_FILE - self.packet_rate = BtsPacketRate.WCDMA_DL384K_UL64K - - elif release_version == self.PARAM_RELEASE_VERSION_7: - - cell_parameter_file = self.UMTS_R7_CELL_FILE - self.packet_rate = BtsPacketRate.WCDMA_DL21_6M_UL5_76M - - elif release_version == self.PARAM_RELEASE_VERSION_8: - - cell_parameter_file = self.UMTS_R8_CELL_FILE - self.packet_rate = BtsPacketRate.WCDMA_DL43_2M_UL5_76M - - else: - raise ValueError("Invalid UMTS release version number.") - - self.anritsu.load_cell_paramfile( - ntpath.join(self.callbox_config_path, cell_parameter_file)) - - self.release_version = release_version - - # Loading a cell parameter file stops the simulation - self.start() - - bts.packet_rate = self.packet_rate - - def maximum_downlink_throughput(self): - """ Calculates maximum achievable downlink throughput in the current - simulation state. - - Returns: - Maximum throughput in mbps. - - """ - - if self.packet_rate not in self.packet_rate_to_dl_throughput: - raise NotImplementedError("Packet rate not contained in the " - "throughput dictionary.") - return self.packet_rate_to_dl_throughput[self.packet_rate] - - def maximum_uplink_throughput(self): - """ Calculates maximum achievable uplink throughput in the current - simulation state. - - Returns: - Maximum throughput in mbps. - - """ - - if self.packet_rate not in self.packet_rate_to_ul_throughput: - raise NotImplementedError("Packet rate not contained in the " - "throughput dictionary.") - return self.packet_rate_to_ul_throughput[self.packet_rate] - - def set_downlink_rx_power(self, bts, signal_level): - """ Starts IP data traffic while setting downlink power. - - This is only necessary for UMTS for unclear reasons. b/139026916 """ - - # Starts IP traffic while changing this setting to force the UE to be - # in Communication state, as UL power cannot be set in Idle state - self.start_traffic_for_calibration() - - # Wait until it goes to communication state - self.anritsu.wait_for_communication_state() - - super().set_downlink_rx_power(bts, signal_level) - - # Stop IP traffic after setting the signal level - self.stop_traffic_for_calibration() - - def set_band(self, bts, band): - """ Sets the band used for communication. - - Args: - bts: basestation handle - band: desired band - """ - - bts.band = band - time.sleep(5) # It takes some time to propagate the new band diff --git a/acts/framework/acts/test_utils/power/tel_simulations/__init__.py b/acts/framework/acts/test_utils/power/tel_simulations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/acts/framework/tests/test_utils/power/tel/lab/init_simulation_test.py b/acts/framework/tests/test_utils/power/tel/lab/init_simulation_test.py index 1bd2f074fc..24fe91a457 100644 --- a/acts/framework/tests/test_utils/power/tel/lab/init_simulation_test.py +++ b/acts/framework/tests/test_utils/power/tel/lab/init_simulation_test.py @@ -17,8 +17,8 @@ import unittest import mobly.config_parser as mobly_config_parser import mock_bokeh -from acts.test_utils.power.tel_simulations.LteSimulation import LteSimulation -from acts.test_utils.power.tel_simulations.UmtsSimulation import UmtsSimulation +from acts.controllers.cellular_lib.LteSimulation import LteSimulation +from acts.controllers.cellular_lib.UmtsSimulation import UmtsSimulation from unittest import mock diff --git a/acts/framework/tests/test_utils/power/tel/lab/power_tel_traffic_e2e_test.py b/acts/framework/tests/test_utils/power/tel/lab/power_tel_traffic_e2e_test.py index ff9599665f..065543ff1e 100644 --- a/acts/framework/tests/test_utils/power/tel/lab/power_tel_traffic_e2e_test.py +++ b/acts/framework/tests/test_utils/power/tel/lab/power_tel_traffic_e2e_test.py @@ -18,7 +18,7 @@ import unittest import mock_bokeh import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt import mobly.config_parser as mobly_config_parser -from acts.test_utils.power.tel_simulations.LteSimulation import LteSimulation +from acts.controllers.cellular_lib.LteSimulation import LteSimulation from acts.controllers.rohdeschwarz_lib import cmw500_cellular_simulator as cmw from unittest import mock diff --git a/acts/framework/tests/test_utils/power/tel/lab/save_summary_to_file_test.py b/acts/framework/tests/test_utils/power/tel/lab/save_summary_to_file_test.py index 1e1554a0dc..900c3a74da 100644 --- a/acts/framework/tests/test_utils/power/tel/lab/save_summary_to_file_test.py +++ b/acts/framework/tests/test_utils/power/tel/lab/save_summary_to_file_test.py @@ -17,7 +17,7 @@ import unittest import mobly.config_parser as mobly_config_parser import mock_bokeh -from acts.test_utils.power.tel_simulations.LteSimulation import LteSimulation +from acts.controllers.cellular_lib.LteSimulation import LteSimulation from unittest import mock from unittest.mock import mock_open -- cgit v1.2.3