xref: /aosp_15_r20/external/autotest/server/cros/cellular/simulation_utils/LteSimulation.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Copyright 2021 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import math
6import time
7from enum import Enum
8
9import common
10
11from autotest_lib.server.cros.cellular.simulation_utils.BaseSimulation import BaseSimulation
12from autotest_lib.server.cros.cellular.simulation_utils import BaseCellularDut
13
14
15class TransmissionMode(Enum):
16    """ Transmission modes for LTE (e.g., TM1, TM4, ...) """
17    TM1 = "TM1"
18    TM2 = "TM2"
19    TM3 = "TM3"
20    TM4 = "TM4"
21    TM7 = "TM7"
22    TM8 = "TM8"
23    TM9 = "TM9"
24
25
26class MimoMode(Enum):
27    """ Mimo modes """
28    MIMO_1x1 = "1x1"
29    MIMO_2x2 = "2x2"
30    MIMO_4x4 = "4x4"
31
32
33class SchedulingMode(Enum):
34    """ Traffic scheduling modes (e.g., STATIC, DYNAMIC) """
35    DYNAMIC = "DYNAMIC"
36    STATIC = "STATIC"
37
38
39class DuplexMode(Enum):
40    """ DL/UL Duplex mode """
41    FDD = "FDD"
42    TDD = "TDD"
43
44
45class ModulationType(Enum):
46    """DL/UL Modulation order."""
47    QPSK = 'QPSK'
48    Q16 = '16QAM'
49    Q64 = '64QAM'
50    Q256 = '256QAM'
51
52
53class LteSimulation(BaseSimulation):
54    """ Single-carrier LTE simulation. """
55
56    # Simulation config keywords contained in the test name
57    PARAM_FRAME_CONFIG = "tddconfig"
58    PARAM_BW = "bw"
59    PARAM_SCHEDULING = "scheduling"
60    PARAM_SCHEDULING_STATIC = "static"
61    PARAM_SCHEDULING_DYNAMIC = "dynamic"
62    PARAM_PATTERN = "pattern"
63    PARAM_TM = "tm"
64    PARAM_UL_PW = 'pul'
65    PARAM_DL_PW = 'pdl'
66    PARAM_BAND = "band"
67    PARAM_MIMO = "mimo"
68    PARAM_DL_MCS = 'dlmcs'
69    PARAM_UL_MCS = 'ulmcs'
70    PARAM_SSF = 'ssf'
71    PARAM_CFI = 'cfi'
72    PARAM_PAGING = 'paging'
73    PARAM_PHICH = 'phich'
74    PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
75    PARAM_DRX = 'drx'
76
77    # Test config keywords
78    KEY_TBS_PATTERN = "tbs_pattern_on"
79    KEY_DL_256_QAM = "256_qam_dl"
80    KEY_UL_64_QAM = "64_qam_ul"
81
82    # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY
83    DOWNLINK_SIGNAL_LEVEL_UNITS = "RSRP"
84
85    # RSRP signal levels thresholds (as reported by Android) in dBm/15KHz.
86    # Excellent is set to -75 since callbox B Tx power is limited to -30 dBm
87    DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {
88            'excellent': -75,
89            'high': -110,
90            'medium': -115,
91            'weak': -120,
92            'disconnected': -170
93    }
94
95    # Transmitted output power for the phone (dBm)
96    UPLINK_SIGNAL_LEVEL_DICTIONARY = {
97            'max': 24,
98            'high': 13,
99            'medium': 3,
100            'low': -20
101    }
102
103    # Bandwidth [MHz] to total RBs mapping
104    total_rbs_dictionary = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6}
105
106    # Bandwidth [MHz] to RB group size
107    rbg_dictionary = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1}
108
109    # Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE
110    min_dl_rbs_dictionary = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2}
111
112    # Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE
113    min_ul_rbs_dictionary = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1}
114
115    # Allowed bandwidth for each band.
116    allowed_bandwidth_dictionary = {
117            1: [5, 10, 15, 20],
118            2: [1.4, 3, 5, 10, 15, 20],
119            3: [1.4, 3, 5, 10, 15, 20],
120            4: [1.4, 3, 5, 10, 15, 20],
121            5: [1.4, 3, 5, 10],
122            7: [5, 10, 15, 20],
123            8: [1.4, 3, 5, 10],
124            10: [5, 10, 15, 20],
125            11: [5, 10],
126            12: [1.4, 3, 5, 10],
127            13: [5, 10],
128            14: [5, 10],
129            17: [5, 10],
130            18: [5, 10, 15],
131            19: [5, 10, 15],
132            20: [5, 10, 15, 20],
133            21: [5, 10, 15],
134            22: [5, 10, 15, 20],
135            24: [5, 10],
136            25: [1.4, 3, 5, 10, 15, 20],
137            26: [1.4, 3, 5, 10, 15],
138            27: [1.4, 3, 5, 10],
139            28: [3, 5, 10, 15, 20],
140            29: [3, 5, 10],
141            30: [5, 10],
142            31: [1.4, 3, 5],
143            32: [5, 10, 15, 20],
144            33: [5, 10, 15, 20],
145            34: [5, 10, 15],
146            35: [1.4, 3, 5, 10, 15, 20],
147            36: [1.4, 3, 5, 10, 15, 20],
148            37: [5, 10, 15, 20],
149            38: [20],
150            39: [5, 10, 15, 20],
151            40: [5, 10, 15, 20],
152            41: [5, 10, 15, 20],
153            42: [5, 10, 15, 20],
154            43: [5, 10, 15, 20],
155            44: [3, 5, 10, 15, 20],
156            45: [5, 10, 15, 20],
157            46: [10, 20],
158            47: [10, 20],
159            48: [5, 10, 15, 20],
160            49: [10, 20],
161            50: [3, 5, 10, 15, 20],
162            51: [3, 5],
163            52: [5, 10, 15, 20],
164            65: [5, 10, 15, 20],
165            66: [1.4, 3, 5, 10, 15, 20],
166            67: [5, 10, 15, 20],
167            68: [5, 10, 15],
168            69: [5],
169            70: [5, 10, 15],
170            71: [5, 10, 15, 20],
171            72: [1.4, 3, 5],
172            73: [1.4, 3, 5],
173            74: [1.4, 3, 5, 10, 15, 20],
174            75: [5, 10, 15, 20],
175            76: [5],
176            85: [5, 10],
177            252: [20],
178            255: [20]
179    }
180
181    # Peak throughput lookup tables for each TDD subframe
182    # configuration and bandwidth
183    # yapf: disable
184    tdd_config4_tput_lut = {
185        0: {
186            5: {'DL': 3.82, 'UL': 2.63},
187            10: {'DL': 11.31,'UL': 9.03},
188            15: {'DL': 16.9, 'UL': 20.62},
189            20: {'DL': 22.88, 'UL': 28.43}
190        },
191        1: {
192            5: {'DL': 6.13, 'UL': 4.08},
193            10: {'DL': 18.36, 'UL': 9.69},
194            15: {'DL': 28.62, 'UL': 14.21},
195            20: {'DL': 39.04, 'UL': 19.23}
196        },
197        2: {
198            5: {'DL': 5.68, 'UL': 2.30},
199            10: {'DL': 25.51, 'UL': 4.68},
200            15: {'DL': 39.3, 'UL': 7.13},
201            20: {'DL': 53.64, 'UL': 9.72}
202        },
203        3: {
204            5: {'DL': 8.26, 'UL': 3.45},
205            10: {'DL': 23.20, 'UL': 6.99},
206            15: {'DL': 35.35, 'UL': 10.75},
207            20: {'DL': 48.3, 'UL': 14.6}
208        },
209        4: {
210            5: {'DL': 6.16, 'UL': 2.30},
211            10: {'DL': 26.77, 'UL': 4.68},
212            15: {'DL': 40.7, 'UL': 7.18},
213            20: {'DL': 55.6, 'UL': 9.73}
214        },
215        5: {
216            5: {'DL': 6.91, 'UL': 1.12},
217            10: {'DL': 30.33, 'UL': 2.33},
218            15: {'DL': 46.04, 'UL': 3.54},
219            20: {'DL': 62.9, 'UL': 4.83}
220        },
221        6: {
222            5: {'DL': 6.13, 'UL': 4.13},
223            10: {'DL': 14.79, 'UL': 11.98},
224            15: {'DL': 23.28, 'UL': 17.46},
225            20: {'DL': 31.75, 'UL': 23.95}
226        }
227    }
228
229    tdd_config3_tput_lut = {
230        0: {
231            5: {'DL': 5.04, 'UL': 3.7},
232            10: {'DL': 15.11, 'UL': 17.56},
233            15: {'DL': 22.59, 'UL': 30.31},
234            20: {'DL': 30.41, 'UL': 41.61}
235        },
236        1: {
237            5: {'DL': 8.07, 'UL': 5.66},
238            10: {'DL': 24.58, 'UL': 13.66},
239            15: {'DL': 39.05, 'UL': 20.68},
240            20: {'DL': 51.59, 'UL': 28.76}
241        },
242        2: {
243            5: {'DL': 7.59, 'UL': 3.31},
244            10: {'DL': 34.08, 'UL': 6.93},
245            15: {'DL': 53.64, 'UL': 10.51},
246            20: {'DL': 70.55, 'UL': 14.41}
247        },
248        3: {
249            5: {'DL': 10.9, 'UL': 5.0},
250            10: {'DL': 30.99, 'UL': 10.25},
251            15: {'DL': 48.3, 'UL': 15.81},
252            20: {'DL': 63.24, 'UL': 21.65}
253        },
254        4: {
255            5: {'DL': 8.11, 'UL': 3.32},
256            10: {'DL': 35.74, 'UL': 6.95},
257            15: {'DL': 55.6, 'UL': 10.51},
258            20: {'DL': 72.72, 'UL': 14.41}
259        },
260        5: {
261            5: {'DL': 9.28, 'UL': 1.57},
262            10: {'DL': 40.49, 'UL': 3.44},
263            15: {'DL': 62.9, 'UL': 5.23},
264            20: {'DL': 82.21, 'UL': 7.15}
265        },
266        6: {
267            5: {'DL': 8.06, 'UL': 5.74},
268            10: {'DL': 19.82, 'UL': 17.51},
269            15: {'DL': 31.75, 'UL': 25.77},
270            20: {'DL': 42.12, 'UL': 34.91}
271        }
272    }
273
274    tdd_config2_tput_lut = {
275        0: {
276            5: {'DL': 3.11, 'UL': 2.55},
277            10: {'DL': 9.93, 'UL': 11.1},
278            15: {'DL': 13.9, 'UL': 21.51},
279            20: {'DL': 20.02, 'UL': 41.66}
280        },
281        1: {
282            5: {'DL': 5.33, 'UL': 4.27},
283            10: {'DL': 15.14, 'UL': 13.95},
284            15: {'DL': 33.84, 'UL': 19.73},
285            20: {'DL': 44.61, 'UL': 27.35}
286        },
287        2: {
288            5: {'DL': 6.87, 'UL': 3.32},
289            10: {'DL': 17.06, 'UL': 6.76},
290            15: {'DL': 49.63, 'UL': 10.5},
291            20: {'DL': 65.2, 'UL': 14.41}
292        },
293        3: {
294            5: {'DL': 5.41, 'UL': 4.17},
295            10: {'DL': 16.89, 'UL': 9.73},
296            15: {'DL': 44.29, 'UL': 15.7},
297            20: {'DL': 53.95, 'UL': 19.85}
298        },
299        4: {
300            5: {'DL': 8.7, 'UL': 3.32},
301            10: {'DL': 17.58, 'UL': 6.76},
302            15: {'DL': 51.08, 'UL': 10.47},
303            20: {'DL': 66.45, 'UL': 14.38}
304        },
305        5: {
306            5: {'DL': 9.46, 'UL': 1.55},
307            10: {'DL': 19.02, 'UL': 3.48},
308            15: {'DL': 58.89, 'UL': 5.23},
309            20: {'DL': 76.85, 'UL': 7.1}
310        },
311        6: {
312            5: {'DL': 4.74, 'UL': 3.9},
313            10: {'DL': 12.32, 'UL': 13.37},
314            15: {'DL': 27.74, 'UL': 25.02},
315            20: {'DL': 35.48, 'UL': 32.95}
316        }
317    }
318
319    tdd_config1_tput_lut = {
320        0: {
321            5: {'DL': 4.25, 'UL': 3.35},
322            10: {'DL': 8.38, 'UL': 7.22},
323            15: {'DL': 12.41, 'UL': 13.91},
324            20: {'DL': 16.27, 'UL': 24.09}
325        },
326        1: {
327            5: {'DL': 7.28, 'UL': 4.61},
328            10: {'DL': 14.73, 'UL': 9.69},
329            15: {'DL': 21.91, 'UL': 13.86},
330            20: {'DL': 27.63, 'UL': 17.18}
331        },
332        2: {
333            5: {'DL': 10.37, 'UL': 2.27},
334            10: {'DL': 20.92, 'UL': 4.66},
335            15: {'DL': 31.01, 'UL': 7.04},
336            20: {'DL': 42.03, 'UL': 9.75}
337        },
338        3: {
339            5: {'DL': 9.25, 'UL': 3.44},
340            10: {'DL': 18.38, 'UL': 6.95},
341            15: {'DL': 27.59, 'UL': 10.62},
342            20: {'DL': 34.85, 'UL': 13.45}
343        },
344        4: {
345            5: {'DL': 10.71, 'UL': 2.26},
346            10: {'DL': 21.54, 'UL': 4.67},
347            15: {'DL': 31.91, 'UL': 7.2},
348            20: {'DL': 43.35, 'UL': 9.74}
349        },
350        5: {
351            5: {'DL': 12.34, 'UL': 1.08},
352            10: {'DL': 24.78, 'UL': 2.34},
353            15: {'DL': 36.68, 'UL': 3.57},
354            20: {'DL': 49.84, 'UL': 4.81}
355        },
356        6: {
357            5: {'DL': 5.76, 'UL': 4.41},
358            10: {'DL': 11.68, 'UL': 9.7},
359            15: {'DL': 17.34, 'UL': 17.95},
360            20: {'DL': 23.5, 'UL': 23.42}
361        }
362    }
363    # yapf: enable
364
365    # Peak throughput lookup table dictionary
366    tdd_config_tput_lut_dict = {
367            'TDD_CONFIG1':
368            tdd_config1_tput_lut,  # DL 256QAM, UL 64QAM & TBS turned OFF
369            'TDD_CONFIG2':
370            tdd_config2_tput_lut,  # DL 256QAM, UL 64 QAM turned ON & TBS OFF
371            'TDD_CONFIG3':
372            tdd_config3_tput_lut,  # DL 256QAM, UL 64QAM & TBS turned ON
373            'TDD_CONFIG4':
374            tdd_config4_tput_lut  # DL 256QAM, UL 64 QAM turned OFF & TBS ON
375    }
376
377    class BtsConfig(BaseSimulation.BtsConfig):
378        """ Extension of the BaseBtsConfig to implement parameters that are
379         exclusive to LTE.
380
381        Attributes:
382            band: an integer indicating the required band number.
383            dlul_config: an integer indicating the TDD config number.
384            ssf_config: an integer indicating the Special Sub-Frame config.
385            bandwidth: a float indicating the required channel bandwidth.
386            mimo_mode: an instance of LteSimulation.MimoMode indicating the
387                required MIMO mode for the downlink signal.
388            transmission_mode: an instance of LteSimulation.TransmissionMode
389                indicating the required TM.
390            scheduling_mode: an instance of LteSimulation.SchedulingMode
391                indicating whether to use Static or Dynamic scheduling.
392            dl_rbs: an integer indicating the number of downlink RBs
393            ul_rbs: an integer indicating the number of uplink RBs
394            dl_mcs: an integer indicating the MCS for the downlink signal
395            ul_mcs: an integer indicating the MCS for the uplink signal
396            dl_modulation_order: a string indicating a DL modulation scheme
397            ul_modulation_order: a string indicating an UL modulation scheme
398            tbs_pattern_on: a boolean indicating whether full allocation mode
399                should be used or not
400            dl_channel: an integer indicating the downlink channel number
401            cfi: an integer indicating the Control Format Indicator
402            paging_cycle: an integer indicating the paging cycle duration in
403                milliseconds
404            phich: a string indicating the PHICH group size parameter
405            drx_connected_mode: a boolean indicating whether cDRX mode is
406                on or off
407            drx_on_duration_timer: number of PDCCH subframes representing
408                DRX on duration
409            drx_inactivity_timer: number of PDCCH subframes to wait before
410                entering DRX mode
411            drx_retransmission_timer: number of consecutive PDCCH subframes
412                to wait for retransmission
413            drx_long_cycle: number of subframes representing one long DRX cycle.
414                One cycle consists of DRX sleep + DRX on duration
415            drx_long_cycle_offset: number representing offset in range
416                0 to drx_long_cycle - 1
417        """
418
419        def __init__(self):
420            """ Initialize the base station config by setting all its
421            parameters to None. """
422            super(LteSimulation.BtsConfig, self).__init__()
423            self.band = None
424            self.dlul_config = None
425            self.ssf_config = None
426            self.bandwidth = None
427            self.mimo_mode = None
428            self.transmission_mode = None
429            self.scheduling_mode = None
430            self.dl_rbs = None
431            self.ul_rbs = None
432            self.dl_mcs = None
433            self.ul_mcs = None
434            self.dl_modulation_order = None
435            self.ul_modulation_order = None
436            self.tbs_pattern_on = None
437            self.dl_channel = None
438            self.cfi = None
439            self.paging_cycle = None
440            self.phich = None
441            self.drx_connected_mode = None
442            self.drx_on_duration_timer = None
443            self.drx_inactivity_timer = None
444            self.drx_retransmission_timer = None
445            self.drx_long_cycle = None
446            self.drx_long_cycle_offset = None
447
448    def __init__(self, simulator, log, dut, test_config, calibration_table):
449        """ Initializes the simulator for a single-carrier LTE simulation.
450
451        Loads a simple LTE simulation environment with 1 basestation.
452
453        Args:
454            simulator: a cellular simulator controller
455            log: a logger handle
456            dut: a device handler implementing BaseCellularDut
457            test_config: test configuration obtained from the config file
458            calibration_table: a dictionary containing path losses for
459                different bands.
460
461        """
462
463        super(LteSimulation, self).__init__(simulator, log, dut, test_config,
464                                            calibration_table)
465
466        self.dut.set_preferred_network_type(
467                BaseCellularDut.PreferredNetworkType.LTE_ONLY)
468
469        # Get TBS pattern setting from the test configuration
470        if self.KEY_TBS_PATTERN not in test_config:
471            self.log.warning("The key '{}' is not set in the config file. "
472                             "Setting to true by default.".format(
473                                     self.KEY_TBS_PATTERN))
474        self.primary_config.tbs_pattern_on = test_config.get(
475                self.KEY_TBS_PATTERN, True)
476
477        # Get the 256-QAM setting from the test configuration
478        if self.KEY_DL_256_QAM not in test_config:
479            self.log.warning("The key '{}' is not set in the config file. "
480                             "Setting to false by default.".format(
481                                     self.KEY_DL_256_QAM))
482
483        self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False)
484
485        if self.dl_256_qam:
486            if not self.simulator.LTE_SUPPORTS_DL_256QAM:
487                self.log.warning("The key '{}' is set to true but the "
488                                 "simulator doesn't support that modulation "
489                                 "order.".format(self.KEY_DL_256_QAM))
490                self.dl_256_qam = False
491            else:
492                self.primary_config.dl_modulation_order = ModulationType.Q256
493
494        else:
495            self.log.warning(
496                    'dl modulation 256QAM is not specified in config, '
497                    'setting to default value 64QAM')
498            self.primary_config.dl_modulation_order = ModulationType.Q64
499        # Get the 64-QAM setting from the test configuration
500        if self.KEY_UL_64_QAM not in test_config:
501            self.log.warning("The key '{}' is not set in the config file. "
502                             "Setting to false by default.".format(
503                                     self.KEY_UL_64_QAM))
504
505        self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False)
506
507        if self.ul_64_qam:
508            if not self.simulator.LTE_SUPPORTS_UL_64QAM:
509                self.log.warning("The key '{}' is set to true but the "
510                                 "simulator doesn't support that modulation "
511                                 "order.".format(self.KEY_UL_64_QAM))
512                self.ul_64_qam = False
513            else:
514                self.primary_config.ul_modulation_order = ModulationType.Q64
515        else:
516            self.log.warning('ul modulation 64QAM is not specified in config, '
517                             'setting to default value 16QAM')
518            self.primary_config.ul_modulation_order = ModulationType.Q16
519
520        self.simulator.configure_bts(self.primary_config)
521
522    def setup_simulator(self):
523        """ Do initial configuration in the simulator. """
524        self.simulator.setup_lte_scenario()
525
526    def parse_parameters(self, parameters):
527        """ Configs an LTE simulation using a list of parameters.
528
529        Calls the parent method first, then consumes parameters specific to LTE.
530
531        Args:
532            parameters: list of parameters
533        """
534
535        # Instantiate a new configuration object
536        new_config = self.BtsConfig()
537
538        # Setup band
539
540        values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
541
542        if not values:
543            raise ValueError(
544                    "The test name needs to include parameter '{}' followed by "
545                    "the required band number.".format(self.PARAM_BAND))
546
547        new_config.band = values[1]
548
549        # Set TDD-only configs
550        if self.get_duplex_mode(new_config.band) == DuplexMode.TDD:
551
552            # Sub-frame DL/UL config
553            values = self.consume_parameter(parameters,
554                                            self.PARAM_FRAME_CONFIG, 1)
555            if not values:
556                raise ValueError(
557                        "When a TDD band is selected the frame "
558                        "structure has to be indicated with the '{}' "
559                        "parameter followed by a number from 0 to 6.".format(
560                                self.PARAM_FRAME_CONFIG))
561
562            new_config.dlul_config = int(values[1])
563
564            # Special Sub-Frame configuration
565            values = self.consume_parameter(parameters, self.PARAM_SSF, 1)
566
567            if not values:
568                self.log.warning(
569                        'The {} parameter was not provided. Setting '
570                        'Special Sub-Frame config to 6 by default.'.format(
571                                self.PARAM_SSF))
572                new_config.ssf_config = 6
573            else:
574                new_config.ssf_config = int(values[1])
575
576        # Setup bandwidth
577
578        values = self.consume_parameter(parameters, self.PARAM_BW, 1)
579
580        if not values:
581            raise ValueError(
582                    "The test name needs to include parameter {} followed by an "
583                    "int value (to indicate 1.4 MHz use 14).".format(
584                            self.PARAM_BW))
585
586        bw = float(values[1])
587
588        if bw == 14:
589            bw = 1.4
590
591        new_config.bandwidth = bw
592
593        # Setup mimo mode
594
595        values = self.consume_parameter(parameters, self.PARAM_MIMO, 1)
596
597        if not values:
598            raise ValueError(
599                    "The test name needs to include parameter '{}' followed by the "
600                    "mimo mode.".format(self.PARAM_MIMO))
601
602        for mimo_mode in MimoMode:
603            if values[1] == mimo_mode.value:
604                new_config.mimo_mode = mimo_mode
605                break
606        else:
607            raise ValueError("The {} parameter needs to be followed by either "
608                             "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
609
610        if (new_config.mimo_mode == MimoMode.MIMO_4x4
611                    and not self.simulator.LTE_SUPPORTS_4X4_MIMO):
612            raise ValueError("The test requires 4x4 MIMO, but that is not "
613                             "supported by the cellular simulator.")
614
615        # Setup transmission mode
616
617        values = self.consume_parameter(parameters, self.PARAM_TM, 1)
618
619        if not values:
620            raise ValueError(
621                    "The test name needs to include parameter {} followed by an "
622                    "int value from 1 to 4 indicating transmission mode.".
623                    format(self.PARAM_TM))
624
625        for tm in TransmissionMode:
626            if values[1] == tm.value[2:]:
627                new_config.transmission_mode = tm
628                break
629        else:
630            raise ValueError("The {} parameter needs to be followed by either "
631                             "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
632                                     self.PARAM_MIMO))
633
634        # Setup scheduling mode
635
636        values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
637
638        if not values:
639            new_config.scheduling_mode = SchedulingMode.STATIC
640            self.log.warning(
641                    "The test name does not include the '{}' parameter. Setting to "
642                    "static by default.".format(self.PARAM_SCHEDULING))
643        elif values[1] == self.PARAM_SCHEDULING_DYNAMIC:
644            new_config.scheduling_mode = SchedulingMode.DYNAMIC
645        elif values[1] == self.PARAM_SCHEDULING_STATIC:
646            new_config.scheduling_mode = SchedulingMode.STATIC
647        else:
648            raise ValueError(
649                    "The test name parameter '{}' has to be followed by either "
650                    "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING))
651
652        if new_config.scheduling_mode == SchedulingMode.STATIC:
653
654            values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
655
656            if not values:
657                self.log.warning(
658                        "The '{}' parameter was not set, using 100% RBs for both "
659                        "DL and UL. To set the percentages of total RBs include "
660                        "the '{}' parameter followed by two ints separated by an "
661                        "underscore indicating downlink and uplink percentages."
662                        .format(self.PARAM_PATTERN, self.PARAM_PATTERN))
663                dl_pattern = 100
664                ul_pattern = 100
665            else:
666                dl_pattern = int(values[1])
667                ul_pattern = int(values[2])
668
669            if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100):
670                raise ValueError(
671                        "The scheduling pattern parameters need to be two "
672                        "positive numbers between 0 and 100.")
673
674            new_config.dl_rbs, new_config.ul_rbs = (
675                    self.allocation_percentages_to_rbs(
676                            new_config.bandwidth, new_config.transmission_mode,
677                            dl_pattern, ul_pattern))
678
679            # Look for a DL MCS configuration in the test parameters. If it is
680            # not present, use a default value.
681            dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, 1)
682
683            if dlmcs:
684                new_config.dl_mcs = int(dlmcs[1])
685            else:
686                self.log.warning(
687                        'The test name does not include the {} parameter. Setting '
688                        'to the max value by default'.format(
689                                self.PARAM_DL_MCS))
690                if self.dl_256_qam and new_config.bandwidth == 1.4:
691                    new_config.dl_mcs = 26
692                elif (not self.dl_256_qam
693                      and self.primary_config.tbs_pattern_on
694                      and new_config.bandwidth != 1.4):
695                    new_config.dl_mcs = 28
696                else:
697                    new_config.dl_mcs = 27
698
699            # Look for an UL MCS configuration in the test parameters. If it is
700            # not present, use a default value.
701            ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, 1)
702
703            if ulmcs:
704                new_config.ul_mcs = int(ulmcs[1])
705            else:
706                self.log.warning(
707                        'The test name does not include the {} parameter. Setting '
708                        'to the max value by default'.format(
709                                self.PARAM_UL_MCS))
710                if self.ul_64_qam:
711                    new_config.ul_mcs = 28
712                else:
713                    new_config.ul_mcs = 23
714
715        # Configure the simulation for DRX mode
716
717        drx = self.consume_parameter(parameters, self.PARAM_DRX, 5)
718
719        if drx and len(drx) == 6:
720            new_config.drx_connected_mode = True
721            new_config.drx_on_duration_timer = drx[1]
722            new_config.drx_inactivity_timer = drx[2]
723            new_config.drx_retransmission_timer = drx[3]
724            new_config.drx_long_cycle = drx[4]
725            try:
726                long_cycle = int(drx[4])
727                long_cycle_offset = int(drx[5])
728                if long_cycle_offset in range(0, long_cycle):
729                    new_config.drx_long_cycle_offset = long_cycle_offset
730                else:
731                    self.log.error(
732                            ("The cDRX long cycle offset must be in the "
733                             "range 0 to (long cycle  - 1). Setting "
734                             "long cycle offset to 0"))
735                    new_config.drx_long_cycle_offset = 0
736
737            except ValueError:
738                self.log.error(("cDRX long cycle and long cycle offset "
739                                "must be integers. Disabling cDRX mode."))
740                new_config.drx_connected_mode = False
741        else:
742            self.log.warning(("DRX mode was not configured properly. "
743                              "Please provide the following 5 values: "
744                              "1) DRX on duration timer "
745                              "2) Inactivity timer "
746                              "3) Retransmission timer "
747                              "4) Long DRX cycle duration "
748                              "5) Long DRX cycle offset "
749                              "Example: drx_2_6_16_20_0"))
750
751        # Setup LTE RRC status change function and timer for LTE idle test case
752        values = self.consume_parameter(parameters,
753                                        self.PARAM_RRC_STATUS_CHANGE_TIMER, 1)
754        if not values:
755            self.log.info(
756                    "The test name does not include the '{}' parameter. Disabled "
757                    "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
758            self.simulator.set_lte_rrc_state_change_timer(False)
759        else:
760            timer = int(values[1])
761            self.simulator.set_lte_rrc_state_change_timer(True, timer)
762            self.rrc_sc_timer = timer
763
764        # Channel Control Indicator
765        values = self.consume_parameter(parameters, self.PARAM_CFI, 1)
766
767        if not values:
768            self.log.warning('The {} parameter was not provided. Setting '
769                             'CFI to BESTEFFORT.'.format(self.PARAM_CFI))
770            new_config.cfi = 'BESTEFFORT'
771        else:
772            new_config.cfi = values[1]
773
774        # PHICH group size
775        values = self.consume_parameter(parameters, self.PARAM_PHICH, 1)
776
777        if not values:
778            self.log.warning('The {} parameter was not provided. Setting '
779                             'PHICH group size to 1 by default.'.format(
780                                     self.PARAM_PHICH))
781            new_config.phich = '1'
782        else:
783            if values[1] == '16':
784                new_config.phich = '1/6'
785            elif values[1] == '12':
786                new_config.phich = '1/2'
787            elif values[1] in ['1/6', '1/2', '1', '2']:
788                new_config.phich = values[1]
789            else:
790                raise ValueError('The {} parameter can only be followed by 1,'
791                                 '2, 1/2 (or 12) and 1/6 (or 16).'.format(
792                                         self.PARAM_PHICH))
793
794        # Paging cycle duration
795        values = self.consume_parameter(parameters, self.PARAM_PAGING, 1)
796
797        if not values:
798            self.log.warning('The {} parameter was not provided. Setting '
799                             'paging cycle duration to 1280 ms by '
800                             'default.'.format(self.PARAM_PAGING))
801            new_config.paging_cycle = 1280
802        else:
803            try:
804                new_config.paging_cycle = int(values[1])
805            except ValueError:
806                raise ValueError(
807                        'The {} parameter has to be followed by the paging cycle '
808                        'duration in milliseconds.'.format(self.PARAM_PAGING))
809
810        # Get uplink power
811
812        ul_power = self.get_uplink_power_from_parameters(parameters)
813
814        # Power is not set on the callbox until after the simulation is
815        # started. Saving this value in a variable for later
816        self.sim_ul_power = ul_power
817
818        # Get downlink power
819
820        dl_power = self.get_downlink_power_from_parameters(parameters)
821
822        # Power is not set on the callbox until after the simulation is
823        # started. Saving this value in a variable for later
824        self.sim_dl_power = dl_power
825
826        # Setup the base station with the obtained configuration and then save
827        # these parameters in the current configuration object
828        self.simulator.configure_bts(new_config)
829        self.primary_config.incorporate(new_config)
830
831        # Now that the band is set, calibrate the link if necessary
832        self.load_pathloss_if_required()
833
834    def calibrated_downlink_rx_power(self, bts_config, rsrp):
835        """ LTE simulation overrides this method so that it can convert from
836        RSRP to total signal power transmitted from the basestation.
837
838        Args:
839            bts_config: the current configuration at the base station
840            rsrp: desired rsrp, contained in a key value pair
841        """
842
843        power = self.rsrp_to_signal_power(rsrp, bts_config)
844
845        self.log.info(
846                "Setting downlink signal level to {} RSRP ({} dBm)".format(
847                        rsrp, power))
848
849        # Use parent method to calculate signal level
850        return super(LteSimulation,
851                     self).calibrated_downlink_rx_power(bts_config, power)
852
853    def downlink_calibration(self, rat=None, power_units_conversion_func=None):
854        """ Computes downlink path loss and returns the calibration value.
855
856        See base class implementation for details.
857
858        Args:
859            rat: ignored, replaced by 'lteRsrp'
860            power_units_conversion_func: ignored, replaced by
861                self.rsrp_to_signal_power
862
863        Returns:
864            Downlink calibration value and measured DL power. Note that the
865            phone only reports RSRP of the primary chain
866        """
867
868        return super().downlink_calibration(
869                rat='lteDbm',
870                power_units_conversion_func=self.rsrp_to_signal_power)
871
872    def rsrp_to_signal_power(self, rsrp, bts_config):
873        """ Converts rsrp to total band signal power
874
875        RSRP is measured per subcarrier, so total band power needs to be
876        multiplied by the number of subcarriers being used.
877
878        Args:
879            rsrp: desired rsrp in dBm
880            bts_config: a base station configuration object
881        Returns:
882            Total band signal power in dBm
883        """
884
885        bandwidth = bts_config.bandwidth
886
887        if bandwidth == 20:  # 100 RBs
888            power = rsrp + 30.79
889        elif bandwidth == 15:  # 75 RBs
890            power = rsrp + 29.54
891        elif bandwidth == 10:  # 50 RBs
892            power = rsrp + 27.78
893        elif bandwidth == 5:  # 25 RBs
894            power = rsrp + 24.77
895        elif bandwidth == 3:  # 15 RBs
896            power = rsrp + 22.55
897        elif bandwidth == 1.4:  # 6 RBs
898            power = rsrp + 18.57
899        else:
900            raise ValueError("Invalid bandwidth value.")
901
902        return power
903
904    def maximum_downlink_throughput(self):
905        """ Calculates maximum achievable downlink throughput in the current
906            simulation state.
907
908        Returns:
909            Maximum throughput in mbps.
910
911        """
912
913        return self.bts_maximum_downlink_throughtput(self.primary_config)
914
915    def bts_maximum_downlink_throughtput(self, bts_config):
916        """ Calculates maximum achievable downlink throughput for a single
917        base station from its configuration object.
918
919        Args:
920            bts_config: a base station configuration object.
921
922        Returns:
923            Maximum throughput in mbps.
924
925        """
926        if bts_config.mimo_mode == MimoMode.MIMO_1x1:
927            streams = 1
928        elif bts_config.mimo_mode == MimoMode.MIMO_2x2:
929            streams = 2
930        elif bts_config.mimo_mode == MimoMode.MIMO_4x4:
931            streams = 4
932        else:
933            raise ValueError('Unable to calculate maximum downlink throughput '
934                             'because the MIMO mode has not been set.')
935
936        bandwidth = bts_config.bandwidth
937        rb_ratio = bts_config.dl_rbs / self.total_rbs_dictionary[bandwidth]
938        mcs = bts_config.dl_mcs
939
940        max_rate_per_stream = None
941
942        tdd_subframe_config = bts_config.dlul_config
943        duplex_mode = self.get_duplex_mode(bts_config.band)
944
945        if duplex_mode == DuplexMode.TDD:
946            if self.dl_256_qam:
947                if mcs == 27:
948                    if bts_config.tbs_pattern_on:
949                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
950                                'TDD_CONFIG3'][tdd_subframe_config][bandwidth][
951                                        'DL']
952                    else:
953                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
954                                'TDD_CONFIG2'][tdd_subframe_config][bandwidth][
955                                        'DL']
956            else:
957                if mcs == 28:
958                    if bts_config.tbs_pattern_on:
959                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
960                                'TDD_CONFIG4'][tdd_subframe_config][bandwidth][
961                                        'DL']
962                    else:
963                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
964                                'TDD_CONFIG1'][tdd_subframe_config][bandwidth][
965                                        'DL']
966
967        elif duplex_mode == DuplexMode.FDD:
968            if (not self.dl_256_qam and bts_config.tbs_pattern_on
969                        and mcs == 28):
970                max_rate_per_stream = {
971                        3: 9.96,
972                        5: 17.0,
973                        10: 34.7,
974                        15: 52.7,
975                        20: 72.2
976                }.get(bandwidth, None)
977            if (not self.dl_256_qam and bts_config.tbs_pattern_on
978                        and mcs == 27):
979                max_rate_per_stream = {
980                        1.4: 2.94,
981                }.get(bandwidth, None)
982            elif (not self.dl_256_qam and not bts_config.tbs_pattern_on
983                  and mcs == 27):
984                max_rate_per_stream = {
985                        1.4: 2.87,
986                        3: 7.7,
987                        5: 14.4,
988                        10: 28.7,
989                        15: 42.3,
990                        20: 57.7
991                }.get(bandwidth, None)
992            elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 27:
993                max_rate_per_stream = {
994                        3: 13.2,
995                        5: 22.9,
996                        10: 46.3,
997                        15: 72.2,
998                        20: 93.9
999                }.get(bandwidth, None)
1000            elif self.dl_256_qam and bts_config.tbs_pattern_on and mcs == 26:
1001                max_rate_per_stream = {
1002                        1.4: 3.96,
1003                }.get(bandwidth, None)
1004            elif (self.dl_256_qam and not bts_config.tbs_pattern_on
1005                  and mcs == 27):
1006                max_rate_per_stream = {
1007                        3: 11.3,
1008                        5: 19.8,
1009                        10: 44.1,
1010                        15: 68.1,
1011                        20: 88.4
1012                }.get(bandwidth, None)
1013            elif (self.dl_256_qam and not bts_config.tbs_pattern_on
1014                  and mcs == 26):
1015                max_rate_per_stream = {
1016                        1.4: 3.96,
1017                }.get(bandwidth, None)
1018
1019        if not max_rate_per_stream:
1020            raise NotImplementedError(
1021                    "The calculation for tbs pattern = {} "
1022                    "and mcs = {} is not implemented.".format(
1023                            "FULLALLOCATION"
1024                            if bts_config.tbs_pattern_on else "OFF", mcs))
1025
1026        return max_rate_per_stream * streams * rb_ratio
1027
1028    def maximum_uplink_throughput(self):
1029        """ Calculates maximum achievable uplink throughput in the current
1030            simulation state.
1031
1032        Returns:
1033            Maximum throughput in mbps.
1034
1035        """
1036
1037        return self.bts_maximum_uplink_throughtput(self.primary_config)
1038
1039    def bts_maximum_uplink_throughtput(self, bts_config):
1040        """ Calculates maximum achievable uplink throughput for the selected
1041        basestation from its configuration object.
1042
1043        Args:
1044            bts_config: an LTE base station configuration object.
1045
1046        Returns:
1047            Maximum throughput in mbps.
1048
1049        """
1050
1051        bandwidth = bts_config.bandwidth
1052        rb_ratio = bts_config.ul_rbs / self.total_rbs_dictionary[bandwidth]
1053        mcs = bts_config.ul_mcs
1054
1055        max_rate_per_stream = None
1056
1057        tdd_subframe_config = bts_config.dlul_config
1058        duplex_mode = self.get_duplex_mode(bts_config.band)
1059
1060        if duplex_mode == DuplexMode.TDD:
1061            if self.ul_64_qam:
1062                if mcs == 28:
1063                    if bts_config.tbs_pattern_on:
1064                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
1065                                'TDD_CONFIG3'][tdd_subframe_config][bandwidth][
1066                                        'UL']
1067                    else:
1068                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
1069                                'TDD_CONFIG2'][tdd_subframe_config][bandwidth][
1070                                        'UL']
1071            else:
1072                if mcs == 23:
1073                    if bts_config.tbs_pattern_on:
1074                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
1075                                'TDD_CONFIG4'][tdd_subframe_config][bandwidth][
1076                                        'UL']
1077                    else:
1078                        max_rate_per_stream = self.tdd_config_tput_lut_dict[
1079                                'TDD_CONFIG1'][tdd_subframe_config][bandwidth][
1080                                        'UL']
1081
1082        elif duplex_mode == DuplexMode.FDD:
1083            if mcs == 23 and not self.ul_64_qam:
1084                max_rate_per_stream = {
1085                        1.4: 2.85,
1086                        3: 7.18,
1087                        5: 12.1,
1088                        10: 24.5,
1089                        15: 36.5,
1090                        20: 49.1
1091                }.get(bandwidth, None)
1092            elif mcs == 28 and self.ul_64_qam:
1093                max_rate_per_stream = {
1094                        1.4: 4.2,
1095                        3: 10.5,
1096                        5: 17.2,
1097                        10: 35.3,
1098                        15: 53.0,
1099                        20: 72.6
1100                }.get(bandwidth, None)
1101
1102        if not max_rate_per_stream:
1103            raise NotImplementedError(
1104                    "The calculation fir mcs = {} is not implemented.".format(
1105                            "FULLALLOCATION"
1106                            if bts_config.tbs_pattern_on else "OFF", mcs))
1107
1108        return max_rate_per_stream * rb_ratio
1109
1110    def allocation_percentages_to_rbs(self, bw, tm, dl, ul):
1111        """ Converts usage percentages to number of DL/UL RBs
1112
1113        Because not any number of DL/UL RBs can be obtained for a certain
1114        bandwidth, this function calculates the number of RBs that most
1115        closely matches the desired DL/UL percentages.
1116
1117        Args:
1118            bw: the bandwidth for the which the RB configuration is requested
1119            tm: the transmission in which the base station will be operating
1120            dl: desired percentage of downlink RBs
1121            ul: desired percentage of uplink RBs
1122        Returns:
1123            a tuple indicating the number of downlink and uplink RBs
1124        """
1125
1126        # Validate the arguments
1127        if (not 0 <= dl <= 100) or (not 0 <= ul <= 100):
1128            raise ValueError("The percentage of DL and UL RBs have to be two "
1129                             "positive between 0 and 100.")
1130
1131        # Get min and max values from tables
1132        max_rbs = self.total_rbs_dictionary[bw]
1133        min_dl_rbs = self.min_dl_rbs_dictionary[bw]
1134        min_ul_rbs = self.min_ul_rbs_dictionary[bw]
1135
1136        def percentage_to_amount(min_val, max_val, percentage):
1137            """ Returns the integer between min_val and max_val that is closest
1138            to percentage/100*max_val
1139            """
1140
1141            # Calculate the value that corresponds to the required percentage.
1142            closest_int = round(max_val * percentage / 100)
1143            # Cannot be less than min_val
1144            closest_int = max(closest_int, min_val)
1145            # RBs cannot be more than max_rbs
1146            closest_int = min(closest_int, max_val)
1147
1148            return closest_int
1149
1150        # Calculate the number of DL RBs
1151
1152        # Get the number of DL RBs that corresponds to
1153        #  the required percentage.
1154        desired_dl_rbs = percentage_to_amount(min_val=min_dl_rbs,
1155                                              max_val=max_rbs,
1156                                              percentage=dl)
1157
1158        if tm == TransmissionMode.TM3 or tm == TransmissionMode.TM4:
1159
1160            # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a
1161            # multiple of the RBG size
1162
1163            if desired_dl_rbs == max_rbs:
1164                dl_rbs = max_rbs
1165            else:
1166                dl_rbs = (math.ceil(desired_dl_rbs / self.rbg_dictionary[bw]) *
1167                          self.rbg_dictionary[bw])
1168
1169        else:
1170            # The other TMs allow any number of RBs between 1 and max_rbs
1171            dl_rbs = desired_dl_rbs
1172
1173        # Calculate the number of UL RBs
1174
1175        # Get the number of UL RBs that corresponds
1176        # to the required percentage
1177        desired_ul_rbs = percentage_to_amount(min_val=min_ul_rbs,
1178                                              max_val=max_rbs,
1179                                              percentage=ul)
1180
1181        # Create a list of all possible UL RBs assignment
1182        # The standard allows any number that can be written as
1183        # 2**a * 3**b * 5**c for any combination of a, b and c.
1184
1185        def pow_range(max_value, base):
1186            """ Returns a range of all possible powers of base under
1187              the given max_value.
1188          """
1189            return range(int(math.ceil(math.log(max_value, base))))
1190
1191        possible_ul_rbs = [
1192            2**a * 3**b * 5**c for a in pow_range(max_rbs, 2)
1193            for b in pow_range(max_rbs, 3)
1194            for c in pow_range(max_rbs, 5)
1195            if 2**a * 3**b * 5**c <= max_rbs] # yapf: disable
1196
1197        # Find the value in the list that is closest to desired_ul_rbs
1198        differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs]
1199        ul_rbs = possible_ul_rbs[differences.index(min(differences))]
1200
1201        # Report what are the obtained RB percentages
1202        self.log.info("Requested a {}% / {}% RB allocation. Closest possible "
1203                      "percentages are {}% / {}%.".format(
1204                              dl, ul, round(100 * dl_rbs / max_rbs),
1205                              round(100 * ul_rbs / max_rbs)))
1206
1207        return dl_rbs, ul_rbs
1208
1209    def calibrate(self, band):
1210        """ Calculates UL and DL path loss if it wasn't done before
1211
1212        Before running the base class implementation, configure the base station
1213        to only use one downlink antenna with maximum bandwidth.
1214
1215        Args:
1216            band: the band that is currently being calibrated.
1217        """
1218
1219        # Save initial values in a configuration object so they can be restored
1220        restore_config = self.BtsConfig()
1221        restore_config.mimo_mode = self.primary_config.mimo_mode
1222        restore_config.transmission_mode = self.primary_config.transmission_mode
1223        restore_config.bandwidth = self.primary_config.bandwidth
1224
1225        # Set up a temporary calibration configuration.
1226        temporary_config = self.BtsConfig()
1227        temporary_config.mimo_mode = MimoMode.MIMO_1x1
1228        temporary_config.transmission_mode = TransmissionMode.TM1
1229        temporary_config.bandwidth = max(
1230                self.allowed_bandwidth_dictionary[int(band)])
1231        self.simulator.configure_bts(temporary_config)
1232        self.primary_config.incorporate(temporary_config)
1233
1234        super().calibrate(band)
1235
1236        # Restore values as they were before changing them for calibration.
1237        self.simulator.configure_bts(restore_config)
1238        self.primary_config.incorporate(restore_config)
1239
1240    def start_traffic_for_calibration(self):
1241        """
1242            If TBS pattern is set to full allocation, there is no need to start
1243            IP traffic.
1244        """
1245        if not self.primary_config.tbs_pattern_on:
1246            super().start_traffic_for_calibration()
1247
1248    def stop_traffic_for_calibration(self):
1249        """
1250            If TBS pattern is set to full allocation, IP traffic wasn't started
1251        """
1252        if not self.primary_config.tbs_pattern_on:
1253            super().stop_traffic_for_calibration()
1254
1255    def get_duplex_mode(self, band):
1256        """ Determines if the band uses FDD or TDD duplex mode
1257
1258        Args:
1259            band: a band number
1260        Returns:
1261            an variable of class DuplexMode indicating if band is FDD or TDD
1262        """
1263
1264        if 33 <= int(band) <= 46:
1265            return DuplexMode.TDD
1266        else:
1267            return DuplexMode.FDD
1268
1269    def get_measured_ul_power(self, samples=5, wait_after_sample=3):
1270        """ Calculates UL power using measurements from the callbox and the
1271        calibration data.
1272
1273        Args:
1274            samples: the numble of samples to average
1275            wait_after_sample: time in seconds to wait in between samples
1276
1277        Returns:
1278            the ul power at the UE antenna ports in dBs
1279        """
1280        ul_power_sum = 0
1281        samples_left = samples
1282
1283        while samples_left > 0:
1284            ul_power_sum += self.simulator.get_measured_pusch_power()
1285            samples_left -= 1
1286            time.sleep(wait_after_sample)
1287
1288        # Got enough samples, return calibrated average
1289        if self.dl_path_loss:
1290            return ul_power_sum / samples + self.ul_path_loss
1291        else:
1292            self.log.warning('No uplink calibration data. Returning '
1293                             'uncalibrated values as measured by the '
1294                             'callbox.')
1295            return ul_power_sum / samples
1296
1297    def send_sms(self, sms_message):
1298        """ Sets the SMS message for the simulation. """
1299        self.simulator.send_sms(sms_message)
1300