• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the 'License');
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an 'AS IS' BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import math
18from enum import Enum
19
20from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation
21from acts.controllers.cellular_lib import BaseCellularDut
22
23
24class TransmissionMode(Enum):
25    """ Transmission modes for LTE (e.g., TM1, TM4, ...) """
26    TM1 = "TM1"
27    TM2 = "TM2"
28    TM3 = "TM3"
29    TM4 = "TM4"
30    TM7 = "TM7"
31    TM8 = "TM8"
32    TM9 = "TM9"
33
34
35class MimoMode(Enum):
36    """ Mimo modes """
37    MIMO_1x1 = "1x1"
38    MIMO_2x2 = "2x2"
39    MIMO_4x4 = "4x4"
40
41
42class SchedulingMode(Enum):
43    """ Traffic scheduling modes (e.g., STATIC, DYNAMIC) """
44    DYNAMIC = "DYNAMIC"
45    STATIC = "STATIC"
46
47
48class DuplexMode(Enum):
49    """ DL/UL Duplex mode """
50    FDD = "FDD"
51    TDD = "TDD"
52
53class ModulationType(Enum):
54    """DL/UL Modulation order."""
55    QPSK = 'QPSK'
56    Q16 = '16QAM'
57    Q64 = '64QAM'
58    Q256 = '256QAM'
59
60
61class LteSimulation(BaseSimulation):
62    """ Single-carrier LTE simulation. """
63
64    # Simulation config keywords contained in the test name
65    PARAM_FRAME_CONFIG = "tddconfig"
66    PARAM_BW = "bw"
67    PARAM_SCHEDULING = "scheduling"
68    PARAM_SCHEDULING_STATIC = "static"
69    PARAM_SCHEDULING_DYNAMIC = "dynamic"
70    PARAM_PATTERN = "pattern"
71    PARAM_TM = "tm"
72    PARAM_UL_PW = 'pul'
73    PARAM_DL_PW = 'pdl'
74    PARAM_BAND = "band"
75    PARAM_MIMO = "mimo"
76    PARAM_DL_MCS = 'dlmcs'
77    PARAM_UL_MCS = 'ulmcs'
78    PARAM_SSF = 'ssf'
79    PARAM_CFI = 'cfi'
80    PARAM_PAGING = 'paging'
81    PARAM_PHICH = 'phich'
82    PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
83    PARAM_DRX = 'drx'
84
85    # Test config keywords
86    KEY_TBS_PATTERN = "tbs_pattern_on"
87    KEY_DL_256_QAM = "256_qam_dl"
88    KEY_UL_64_QAM = "64_qam_ul"
89
90    # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY
91    DOWNLINK_SIGNAL_LEVEL_UNITS = "RSRP"
92
93    # RSRP signal levels thresholds (as reported by Android) in dBm/15KHz.
94    # Excellent is set to -75 since callbox B Tx power is limited to -30 dBm
95    DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {
96        'excellent': -75,
97        'high': -110,
98        'medium': -115,
99        'weak': -120
100    }
101
102    # Transmitted output power for the phone (dBm)
103    UPLINK_SIGNAL_LEVEL_DICTIONARY = {
104        'max': 27,
105        'high': 13,
106        'medium': 3,
107        'low': -20
108    }
109
110    # Bandwidth [MHz] to total RBs mapping
111    total_rbs_dictionary = {20: 100, 15: 75, 10: 50, 5: 25, 3: 15, 1.4: 6}
112
113    # Bandwidth [MHz] to RB group size
114    rbg_dictionary = {20: 4, 15: 4, 10: 3, 5: 2, 3: 2, 1.4: 1}
115
116    # Bandwidth [MHz] to minimum number of DL RBs that can be assigned to a UE
117    min_dl_rbs_dictionary = {20: 16, 15: 12, 10: 9, 5: 4, 3: 4, 1.4: 2}
118
119    # Bandwidth [MHz] to minimum number of UL RBs that can be assigned to a UE
120    min_ul_rbs_dictionary = {20: 8, 15: 6, 10: 4, 5: 2, 3: 2, 1.4: 1}
121
122    # Allowed bandwidth for each band.
123    allowed_bandwidth_dictionary = {
124        1: [5, 10, 15, 20],
125        2: [1.4, 3, 5, 10, 15, 20],
126        3: [1.4, 3, 5, 10, 15, 20],
127        4: [1.4, 3, 5, 10, 15, 20],
128        5: [1.4, 3, 5, 10],
129        7: [5, 10, 15, 20],
130        8: [1.4, 3, 5, 10],
131        10: [5, 10, 15, 20],
132        11: [5, 10],
133        12: [1.4, 3, 5, 10],
134        13: [5, 10],
135        14: [5, 10],
136        17: [5, 10],
137        18: [5, 10, 15],
138        19: [5, 10, 15],
139        20: [5, 10, 15, 20],
140        21: [5, 10, 15],
141        22: [5, 10, 15, 20],
142        24: [5, 10],
143        25: [1.4, 3, 5, 10, 15, 20],
144        26: [1.4, 3, 5, 10, 15],
145        27: [1.4, 3, 5, 10],
146        28: [3, 5, 10, 15, 20],
147        29: [3, 5, 10],
148        30: [5, 10],
149        31: [1.4, 3, 5],
150        32: [5, 10, 15, 20],
151        33: [5, 10, 15, 20],
152        34: [5, 10, 15],
153        35: [1.4, 3, 5, 10, 15, 20],
154        36: [1.4, 3, 5, 10, 15, 20],
155        37: [5, 10, 15, 20],
156        38: [20],
157        39: [5, 10, 15, 20],
158        40: [5, 10, 15, 20],
159        41: [5, 10, 15, 20],
160        42: [5, 10, 15, 20],
161        43: [5, 10, 15, 20],
162        44: [3, 5, 10, 15, 20],
163        45: [5, 10, 15, 20],
164        46: [10, 20],
165        47: [10, 20],
166        48: [5, 10, 15, 20],
167        49: [10, 20],
168        50: [3, 5, 10, 15, 20],
169        51: [3, 5],
170        52: [5, 10, 15, 20],
171        65: [5, 10, 15, 20],
172        66: [1.4, 3, 5, 10, 15, 20],
173        67: [5, 10, 15, 20],
174        68: [5, 10, 15],
175        69: [5],
176        70: [5, 10, 15],
177        71: [5, 10, 15, 20],
178        72: [1.4, 3, 5],
179        73: [1.4, 3, 5],
180        74: [1.4, 3, 5, 10, 15, 20],
181        75: [5, 10, 15, 20],
182        76: [5],
183        85: [5, 10],
184        252: [20],
185        255: [20]
186    }
187
188    # Peak throughput lookup tables for each TDD subframe
189    # configuration and bandwidth
190    # yapf: disable
191    tdd_config4_tput_lut = {
192        0: {
193            5: {'DL': 3.82, 'UL': 2.63},
194            10: {'DL': 11.31,'UL': 9.03},
195            15: {'DL': 16.9, 'UL': 20.62},
196            20: {'DL': 22.88, 'UL': 28.43}
197        },
198        1: {
199            5: {'DL': 6.13, 'UL': 4.08},
200            10: {'DL': 18.36, 'UL': 9.69},
201            15: {'DL': 28.62, 'UL': 14.21},
202            20: {'DL': 39.04, 'UL': 19.23}
203        },
204        2: {
205            5: {'DL': 5.68, 'UL': 2.30},
206            10: {'DL': 25.51, 'UL': 4.68},
207            15: {'DL': 39.3, 'UL': 7.13},
208            20: {'DL': 53.64, 'UL': 9.72}
209        },
210        3: {
211            5: {'DL': 8.26, 'UL': 3.45},
212            10: {'DL': 23.20, 'UL': 6.99},
213            15: {'DL': 35.35, 'UL': 10.75},
214            20: {'DL': 48.3, 'UL': 14.6}
215        },
216        4: {
217            5: {'DL': 6.16, 'UL': 2.30},
218            10: {'DL': 26.77, 'UL': 4.68},
219            15: {'DL': 40.7, 'UL': 7.18},
220            20: {'DL': 55.6, 'UL': 9.73}
221        },
222        5: {
223            5: {'DL': 6.91, 'UL': 1.12},
224            10: {'DL': 30.33, 'UL': 2.33},
225            15: {'DL': 46.04, 'UL': 3.54},
226            20: {'DL': 62.9, 'UL': 4.83}
227        },
228        6: {
229            5: {'DL': 6.13, 'UL': 4.13},
230            10: {'DL': 14.79, 'UL': 11.98},
231            15: {'DL': 23.28, 'UL': 17.46},
232            20: {'DL': 31.75, 'UL': 23.95}
233        }
234    }
235
236    tdd_config3_tput_lut = {
237        0: {
238            5: {'DL': 5.04, 'UL': 3.7},
239            10: {'DL': 15.11, 'UL': 17.56},
240            15: {'DL': 22.59, 'UL': 30.31},
241            20: {'DL': 30.41, 'UL': 41.61}
242        },
243        1: {
244            5: {'DL': 8.07, 'UL': 5.66},
245            10: {'DL': 24.58, 'UL': 13.66},
246            15: {'DL': 39.05, 'UL': 20.68},
247            20: {'DL': 51.59, 'UL': 28.76}
248        },
249        2: {
250            5: {'DL': 7.59, 'UL': 3.31},
251            10: {'DL': 34.08, 'UL': 6.93},
252            15: {'DL': 53.64, 'UL': 10.51},
253            20: {'DL': 70.55, 'UL': 14.41}
254        },
255        3: {
256            5: {'DL': 10.9, 'UL': 5.0},
257            10: {'DL': 30.99, 'UL': 10.25},
258            15: {'DL': 48.3, 'UL': 15.81},
259            20: {'DL': 63.24, 'UL': 21.65}
260        },
261        4: {
262            5: {'DL': 8.11, 'UL': 3.32},
263            10: {'DL': 35.74, 'UL': 6.95},
264            15: {'DL': 55.6, 'UL': 10.51},
265            20: {'DL': 72.72, 'UL': 14.41}
266        },
267        5: {
268            5: {'DL': 9.28, 'UL': 1.57},
269            10: {'DL': 40.49, 'UL': 3.44},
270            15: {'DL': 62.9, 'UL': 5.23},
271            20: {'DL': 82.21, 'UL': 7.15}
272        },
273        6: {
274            5: {'DL': 8.06, 'UL': 5.74},
275            10: {'DL': 19.82, 'UL': 17.51},
276            15: {'DL': 31.75, 'UL': 25.77},
277            20: {'DL': 42.12, 'UL': 34.91}
278        }
279    }
280
281    tdd_config2_tput_lut = {
282        0: {
283            5: {'DL': 3.11, 'UL': 2.55},
284            10: {'DL': 9.93, 'UL': 11.1},
285            15: {'DL': 13.9, 'UL': 21.51},
286            20: {'DL': 20.02, 'UL': 41.66}
287        },
288        1: {
289            5: {'DL': 5.33, 'UL': 4.27},
290            10: {'DL': 15.14, 'UL': 13.95},
291            15: {'DL': 33.84, 'UL': 19.73},
292            20: {'DL': 44.61, 'UL': 27.35}
293        },
294        2: {
295            5: {'DL': 6.87, 'UL': 3.32},
296            10: {'DL': 17.06, 'UL': 6.76},
297            15: {'DL': 49.63, 'UL': 10.5},
298            20: {'DL': 65.2, 'UL': 14.41}
299        },
300        3: {
301            5: {'DL': 5.41, 'UL': 4.17},
302            10: {'DL': 16.89, 'UL': 9.73},
303            15: {'DL': 44.29, 'UL': 15.7},
304            20: {'DL': 53.95, 'UL': 19.85}
305        },
306        4: {
307            5: {'DL': 8.7, 'UL': 3.32},
308            10: {'DL': 17.58, 'UL': 6.76},
309            15: {'DL': 51.08, 'UL': 10.47},
310            20: {'DL': 66.45, 'UL': 14.38}
311        },
312        5: {
313            5: {'DL': 9.46, 'UL': 1.55},
314            10: {'DL': 19.02, 'UL': 3.48},
315            15: {'DL': 58.89, 'UL': 5.23},
316            20: {'DL': 76.85, 'UL': 7.1}
317        },
318        6: {
319            5: {'DL': 4.74, 'UL': 3.9},
320            10: {'DL': 12.32, 'UL': 13.37},
321            15: {'DL': 27.74, 'UL': 25.02},
322            20: {'DL': 35.48, 'UL': 32.95}
323        }
324    }
325
326    tdd_config1_tput_lut = {
327        0: {
328            5: {'DL': 4.25, 'UL': 3.35},
329            10: {'DL': 8.38, 'UL': 7.22},
330            15: {'DL': 12.41, 'UL': 13.91},
331            20: {'DL': 16.27, 'UL': 24.09}
332        },
333        1: {
334            5: {'DL': 7.28, 'UL': 4.61},
335            10: {'DL': 14.73, 'UL': 9.69},
336            15: {'DL': 21.91, 'UL': 13.86},
337            20: {'DL': 27.63, 'UL': 17.18}
338        },
339        2: {
340            5: {'DL': 10.37, 'UL': 2.27},
341            10: {'DL': 20.92, 'UL': 4.66},
342            15: {'DL': 31.01, 'UL': 7.04},
343            20: {'DL': 42.03, 'UL': 9.75}
344        },
345        3: {
346            5: {'DL': 9.25, 'UL': 3.44},
347            10: {'DL': 18.38, 'UL': 6.95},
348            15: {'DL': 27.59, 'UL': 10.62},
349            20: {'DL': 34.85, 'UL': 13.45}
350        },
351        4: {
352            5: {'DL': 10.71, 'UL': 2.26},
353            10: {'DL': 21.54, 'UL': 4.67},
354            15: {'DL': 31.91, 'UL': 7.2},
355            20: {'DL': 43.35, 'UL': 9.74}
356        },
357        5: {
358            5: {'DL': 12.34, 'UL': 1.08},
359            10: {'DL': 24.78, 'UL': 2.34},
360            15: {'DL': 36.68, 'UL': 3.57},
361            20: {'DL': 49.84, 'UL': 4.81}
362        },
363        6: {
364            5: {'DL': 5.76, 'UL': 4.41},
365            10: {'DL': 11.68, 'UL': 9.7},
366            15: {'DL': 17.34, 'UL': 17.95},
367            20: {'DL': 23.5, 'UL': 23.42}
368        }
369    }
370    # yapf: enable
371
372    # Peak throughput lookup table dictionary
373    tdd_config_tput_lut_dict = {
374        'TDD_CONFIG1':
375        tdd_config1_tput_lut,  # DL 256QAM, UL 64QAM & TBS turned OFF
376        'TDD_CONFIG2':
377        tdd_config2_tput_lut,  # DL 256QAM, UL 64 QAM turned ON & TBS OFF
378        'TDD_CONFIG3':
379        tdd_config3_tput_lut,  # DL 256QAM, UL 64QAM & TBS turned ON
380        'TDD_CONFIG4':
381        tdd_config4_tput_lut  # DL 256QAM, UL 64 QAM turned OFF & TBS ON
382    }
383
384    class BtsConfig(BaseSimulation.BtsConfig):
385        """ Extension of the BaseBtsConfig to implement parameters that are
386         exclusive to LTE.
387
388        Attributes:
389            band: an integer indicating the required band number.
390            dlul_config: an integer indicating the TDD config number.
391            ssf_config: an integer indicating the Special Sub-Frame config.
392            bandwidth: a float indicating the required channel bandwidth.
393            mimo_mode: an instance of LteSimulation.MimoMode indicating the
394                required MIMO mode for the downlink signal.
395            transmission_mode: an instance of LteSimulation.TransmissionMode
396                indicating the required TM.
397            scheduling_mode: an instance of LteSimulation.SchedulingMode
398                indicating wether to use Static or Dynamic scheduling.
399            dl_rbs: an integer indicating the number of downlink RBs
400            ul_rbs: an integer indicating the number of uplink RBs
401            dl_mcs: an integer indicating the MCS for the downlink signal
402            ul_mcs: an integer indicating the MCS for the uplink signal
403            dl_modulation_order: a string indicating a DL modulation scheme
404            ul_modulation_order: a string indicating an UL modulation scheme
405            tbs_pattern_on: a boolean indicating whether full allocation mode
406                should be used or not
407            dl_channel: an integer indicating the downlink channel number
408            cfi: an integer indicating the Control Format Indicator
409            paging_cycle: an integer indicating the paging cycle duration in
410                milliseconds
411            phich: a string indicating the PHICH group size parameter
412            drx_connected_mode: a boolean indicating whether cDRX mode is
413                on or off
414            drx_on_duration_timer: number of PDCCH subframes representing
415                DRX on duration
416            drx_inactivity_timer: number of PDCCH subframes to wait before
417                entering DRX mode
418            drx_retransmission_timer: number of consecutive PDCCH subframes
419                to wait for retransmission
420            drx_long_cycle: number of subframes representing one long DRX cycle.
421                One cycle consists of DRX sleep + DRX on duration
422            drx_long_cycle_offset: number representing offset in range
423                0 to drx_long_cycle - 1
424        """
425        def __init__(self):
426            """ Initialize the base station config by setting all its
427            parameters to None. """
428            super().__init__()
429            self.band = None
430            self.dlul_config = None
431            self.ssf_config = None
432            self.bandwidth = None
433            self.mimo_mode = None
434            self.transmission_mode = None
435            self.scheduling_mode = None
436            self.dl_rbs = None
437            self.ul_rbs = None
438            self.dl_mcs = None
439            self.ul_mcs = None
440            self.dl_modulation_order = None
441            self.ul_modulation_order = None
442            self.tbs_pattern_on = None
443            self.dl_channel = None
444            self.cfi = None
445            self.paging_cycle = None
446            self.phich = None
447            self.drx_connected_mode = None
448            self.drx_on_duration_timer = None
449            self.drx_inactivity_timer = None
450            self.drx_retransmission_timer = None
451            self.drx_long_cycle = None
452            self.drx_long_cycle_offset = None
453
454    def __init__(self, simulator, log, dut, test_config, calibration_table):
455        """ Initializes the simulator for a single-carrier LTE simulation.
456
457        Loads a simple LTE simulation enviroment with 1 basestation.
458
459        Args:
460            simulator: a cellular simulator controller
461            log: a logger handle
462            dut: a device handler implementing BaseCellularDut
463            test_config: test configuration obtained from the config file
464            calibration_table: a dictionary containing path losses for
465                different bands.
466
467        """
468
469        super().__init__(simulator, log, dut, test_config, calibration_table)
470
471        self.dut.set_preferred_network_type(
472            BaseCellularDut.PreferredNetworkType.LTE_ONLY)
473
474        # Get TBS pattern setting from the test configuration
475        if self.KEY_TBS_PATTERN not in test_config:
476            self.log.warning("The key '{}' is not set in the config file. "
477                             "Setting to true by default.".format(
478                                 self.KEY_TBS_PATTERN))
479        self.primary_config.tbs_pattern_on = test_config.get(
480            self.KEY_TBS_PATTERN, True)
481
482        # Get the 256-QAM setting from the test configuration
483        if self.KEY_DL_256_QAM not in test_config:
484            self.log.warning("The key '{}' is not set in the config file. "
485                             "Setting to false by default.".format(
486                                 self.KEY_DL_256_QAM))
487
488        self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False)
489
490        if self.dl_256_qam:
491            if not self.simulator.LTE_SUPPORTS_DL_256QAM:
492                self.log.warning("The key '{}' is set to true but the "
493                                 "simulator doesn't support that modulation "
494                                 "order.".format(self.KEY_DL_256_QAM))
495                self.dl_256_qam = False
496            else:
497                self.primary_config.dl_modulation_order = ModulationType.Q256
498
499        else:
500            self.log.warning('dl modulation 256QAM is not specified in config, '
501                             'setting to default value 64QAM')
502            self.primary_config.dl_modulation_order = ModulationType.Q64
503        # Get the 64-QAM setting from the test configuration
504        if self.KEY_UL_64_QAM not in test_config:
505            self.log.warning("The key '{}' is not set in the config file. "
506                             "Setting to false by default.".format(
507                                 self.KEY_UL_64_QAM))
508
509        self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False)
510
511        if self.ul_64_qam:
512            if not self.simulator.LTE_SUPPORTS_UL_64QAM:
513                self.log.warning("The key '{}' is set to true but the "
514                                 "simulator doesn't support that modulation "
515                                 "order.".format(self.KEY_UL_64_QAM))
516                self.ul_64_qam = False
517            else:
518                self.primary_config.ul_modulation_order = ModulationType.Q64
519        else:
520            self.log.warning('ul modulation 64QAM is not specified in config, '
521                             'setting to default value 16QAM')
522            self.primary_config.ul_modulation_order = ModulationType.Q16
523
524        self.simulator.configure_bts(self.primary_config)
525
526    def setup_simulator(self):
527        """ Do initial configuration in the simulator. """
528        self.simulator.setup_lte_scenario()
529
530    def parse_parameters(self, parameters):
531        """ Configs an LTE simulation using a list of parameters.
532
533        Calls the parent method first, then consumes parameters specific to LTE.
534
535        Args:
536            parameters: list of parameters
537        """
538
539        # Instantiate a new configuration object
540        new_config = self.BtsConfig()
541
542        # Setup band
543
544        values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
545
546        if not values:
547            raise ValueError(
548                "The test name needs to include parameter '{}' followed by "
549                "the required band number.".format(self.PARAM_BAND))
550
551        new_config.band = values[1]
552
553        # Set TDD-only configs
554        if self.get_duplex_mode(new_config.band) == DuplexMode.TDD:
555
556            # Sub-frame DL/UL config
557            values = self.consume_parameter(parameters,
558                                            self.PARAM_FRAME_CONFIG, 1)
559            if not values:
560                raise ValueError(
561                    "When a TDD band is selected the frame "
562                    "structure has to be indicated with the '{}' "
563                    "parameter followed by a number from 0 to 6.".format(
564                        self.PARAM_FRAME_CONFIG))
565
566            new_config.dlul_config = int(values[1])
567
568            # Special Sub-Frame configuration
569            values = self.consume_parameter(parameters, self.PARAM_SSF, 1)
570
571            if not values:
572                self.log.warning(
573                    'The {} parameter was not provided. Setting '
574                    'Special Sub-Frame config to 6 by default.'.format(
575                        self.PARAM_SSF))
576                new_config.ssf_config = 6
577            else:
578                new_config.ssf_config = int(values[1])
579
580        # Setup bandwidth
581
582        values = self.consume_parameter(parameters, self.PARAM_BW, 1)
583
584        if not values:
585            raise ValueError(
586                "The test name needs to include parameter {} followed by an "
587                "int value (to indicate 1.4 MHz use 14).".format(
588                    self.PARAM_BW))
589
590        bw = float(values[1])
591
592        if bw == 14:
593            bw = 1.4
594
595        new_config.bandwidth = bw
596
597        # Setup mimo mode
598
599        values = self.consume_parameter(parameters, self.PARAM_MIMO, 1)
600
601        if not values:
602            raise ValueError(
603                "The test name needs to include parameter '{}' followed by the "
604                "mimo mode.".format(self.PARAM_MIMO))
605
606        for mimo_mode in MimoMode:
607            if values[1] == mimo_mode.value:
608                new_config.mimo_mode = mimo_mode
609                break
610        else:
611            raise ValueError("The {} parameter needs to be followed by either "
612                             "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
613
614        if (new_config.mimo_mode == MimoMode.MIMO_4x4
615                and not self.simulator.LTE_SUPPORTS_4X4_MIMO):
616            raise ValueError("The test requires 4x4 MIMO, but that is not "
617                             "supported by the cellular simulator.")
618
619        # Setup transmission mode
620
621        values = self.consume_parameter(parameters, self.PARAM_TM, 1)
622
623        if not values:
624            raise ValueError(
625                "The test name needs to include parameter {} followed by an "
626                "int value from 1 to 4 indicating transmission mode.".format(
627                    self.PARAM_TM))
628
629        for tm in TransmissionMode:
630            if values[1] == tm.value[2:]:
631                new_config.transmission_mode = tm
632                break
633        else:
634            raise ValueError("The {} parameter needs to be followed by either "
635                             "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
636                                 self.PARAM_MIMO))
637
638        # Setup scheduling mode
639
640        values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
641
642        if not values:
643            new_config.scheduling_mode = SchedulingMode.STATIC
644            self.log.warning(
645                "The test name does not include the '{}' parameter. Setting to "
646                "static by default.".format(self.PARAM_SCHEDULING))
647        elif values[1] == self.PARAM_SCHEDULING_DYNAMIC:
648            new_config.scheduling_mode = SchedulingMode.DYNAMIC
649        elif values[1] == self.PARAM_SCHEDULING_STATIC:
650            new_config.scheduling_mode = SchedulingMode.STATIC
651        else:
652            raise ValueError(
653                "The test name parameter '{}' has to be followed by either "
654                "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING))
655
656        if new_config.scheduling_mode == SchedulingMode.STATIC:
657
658            values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
659
660            if not values:
661                self.log.warning(
662                    "The '{}' parameter was not set, using 100% RBs for both "
663                    "DL and UL. To set the percentages of total RBs include "
664                    "the '{}' parameter followed by two ints separated by an "
665                    "underscore indicating downlink and uplink percentages.".
666                    format(self.PARAM_PATTERN, self.PARAM_PATTERN))
667                dl_pattern = 100
668                ul_pattern = 100
669            else:
670                dl_pattern = int(values[1])
671                ul_pattern = int(values[2])
672
673            if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100):
674                raise ValueError(
675                    "The scheduling pattern parameters need to be two "
676                    "positive numbers between 0 and 100.")
677
678            new_config.dl_rbs, new_config.ul_rbs = (
679                self.allocation_percentages_to_rbs(
680                    new_config.bandwidth, new_config.transmission_mode,
681                    dl_pattern, ul_pattern))
682
683            # Look for a DL MCS configuration in the test parameters. If it is
684            # not present, use a default value.
685            dlmcs = self.consume_parameter(parameters, self.PARAM_DL_MCS, 1)
686
687            if dlmcs:
688                new_config.dl_mcs = int(dlmcs[1])
689            else:
690                self.log.warning(
691                    'The test name does not include the {} parameter. Setting '
692                    'to the max value by default'.format(self.PARAM_DL_MCS))
693                if self.dl_256_qam and new_config.bandwidth == 1.4:
694                    new_config.dl_mcs = 26
695                elif (not self.dl_256_qam
696                      and self.primary_config.tbs_pattern_on
697                      and new_config.bandwidth != 1.4):
698                    new_config.dl_mcs = 28
699                else:
700                    new_config.dl_mcs = 27
701
702            # Look for an UL MCS configuration in the test parameters. If it is
703            # not present, use a default value.
704            ulmcs = self.consume_parameter(parameters, self.PARAM_UL_MCS, 1)
705
706            if ulmcs:
707                new_config.ul_mcs = int(ulmcs[1])
708            else:
709                self.log.warning(
710                    'The test name does not include the {} parameter. Setting '
711                    'to the max value by default'.format(self.PARAM_UL_MCS))
712                if self.ul_64_qam:
713                    new_config.ul_mcs = 28
714                else:
715                    new_config.ul_mcs = 23
716
717        # Configure the simulation for DRX mode
718
719        drx = self.consume_parameter(parameters, self.PARAM_DRX, 5)
720
721        if drx and len(drx) == 6:
722            new_config.drx_connected_mode = True
723            new_config.drx_on_duration_timer = drx[1]
724            new_config.drx_inactivity_timer = drx[2]
725            new_config.drx_retransmission_timer = drx[3]
726            new_config.drx_long_cycle = drx[4]
727            try:
728                long_cycle = int(drx[4])
729                long_cycle_offset = int(drx[5])
730                if long_cycle_offset in range(0, long_cycle):
731                    new_config.drx_long_cycle_offset = long_cycle_offset
732                else:
733                    self.log.error(("The cDRX long cycle offset must be in the "
734                                    "range 0 to (long cycle  - 1). Setting "
735                                    "long cycle offset to 0"))
736                    new_config.drx_long_cycle_offset = 0
737
738            except ValueError:
739                self.log.error(("cDRX long cycle and long cycle offset "
740                                "must be integers. Disabling cDRX mode."))
741                new_config.drx_connected_mode = False
742        else:
743            self.log.warning(("DRX mode was not configured properly. "
744                              "Please provide the following 5 values: "
745                              "1) DRX on duration timer "
746                              "2) Inactivity timer "
747                              "3) Retransmission timer "
748                              "4) Long DRX cycle duration "
749                              "5) Long DRX cycle offset "
750                              "Example: drx_2_6_16_20_0"))
751
752        # Setup LTE RRC status change function and timer for LTE idle test case
753        values = self.consume_parameter(parameters,
754                                        self.PARAM_RRC_STATUS_CHANGE_TIMER, 1)
755        if not values:
756            self.log.info(
757                "The test name does not include the '{}' parameter. Disabled "
758                "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
759            self.simulator.set_lte_rrc_state_change_timer(False)
760        else:
761            timer = int(values[1])
762            self.simulator.set_lte_rrc_state_change_timer(True, timer)
763            self.rrc_sc_timer = timer
764
765        # Channel Control Indicator
766        values = self.consume_parameter(parameters, self.PARAM_CFI, 1)
767
768        if not values:
769            self.log.warning('The {} parameter was not provided. Setting '
770                             'CFI to BESTEFFORT.'.format(self.PARAM_CFI))
771            new_config.cfi = 'BESTEFFORT'
772        else:
773            new_config.cfi = values[1]
774
775        # PHICH group size
776        values = self.consume_parameter(parameters, self.PARAM_PHICH, 1)
777
778        if not values:
779            self.log.warning('The {} parameter was not provided. Setting '
780                             'PHICH group size to 1 by default.'.format(
781                                 self.PARAM_PHICH))
782            new_config.phich = '1'
783        else:
784            if values[1] == '16':
785                new_config.phich = '1/6'
786            elif values[1] == '12':
787                new_config.phich = '1/2'
788            elif values[1] in ['1/6', '1/2', '1', '2']:
789                new_config.phich = values[1]
790            else:
791                raise ValueError('The {} parameter can only be followed by 1,'
792                                 '2, 1/2 (or 12) and 1/6 (or 16).'.format(
793                                     self.PARAM_PHICH))
794
795        # Paging cycle duration
796        values = self.consume_parameter(parameters, self.PARAM_PAGING, 1)
797
798        if not values:
799            self.log.warning('The {} parameter was not provided. Setting '
800                             'paging cycle duration to 1280 ms by '
801                             'default.'.format(self.PARAM_PAGING))
802            new_config.paging_cycle = 1280
803        else:
804            try:
805                new_config.paging_cycle = int(values[1])
806            except ValueError:
807                raise ValueError(
808                    'The {} parameter has to be followed by the paging cycle '
809                    'duration in milliseconds.'.format(self.PARAM_PAGING))
810
811        # Get uplink power
812
813        ul_power = self.get_uplink_power_from_parameters(parameters)
814
815        # Power is not set on the callbox until after the simulation is
816        # started. Saving this value in a variable for later
817        self.sim_ul_power = ul_power
818
819        # Get downlink power
820
821        dl_power = self.get_downlink_power_from_parameters(parameters)
822
823        # Power is not set on the callbox until after the simulation is
824        # started. Saving this value in a variable for later
825        self.sim_dl_power = dl_power
826
827        # Setup the base station with the obtained configuration and then save
828        # these parameters in the current configuration object
829        self.simulator.configure_bts(new_config)
830        self.primary_config.incorporate(new_config)
831
832        # Now that the band is set, calibrate the link if necessary
833        self.load_pathloss_if_required()
834
835    def calibrated_downlink_rx_power(self, bts_config, rsrp):
836        """ LTE simulation overrides this method so that it can convert from
837        RSRP to total signal power transmitted from the basestation.
838
839        Args:
840            bts_config: the current configuration at the base station
841            rsrp: desired rsrp, contained in a key value pair
842        """
843
844        power = self.rsrp_to_signal_power(rsrp, bts_config)
845
846        self.log.info(
847            "Setting downlink signal level to {} RSRP ({} dBm)".format(
848                rsrp, power))
849
850        # Use parent method to calculate signal level
851        return super().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            Dowlink 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" if bts_config.tbs_pattern_on else "OFF",
1024                    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" if bts_config.tbs_pattern_on else "OFF",
1106                    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