• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3.4
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the 'License');
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an 'AS IS' BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import time
18import math
19from enum import Enum
20
21from acts.controllers.anritsu_lib.md8475a import BtsBandwidth
22from acts.controllers.anritsu_lib.md8475a import BtsPacketRate
23from acts.test_utils.power.tel_simulations.BaseSimulation import BaseSimulation
24from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_ONLY
25
26
27class LteSimulation(BaseSimulation):
28    """ Simple LTE simulation with only one basestation.
29
30    """
31
32    # Simulation config files in the callbox computer.
33    # These should be replaced in the future by setting up
34    # the same configuration manually.
35    LTE_BASIC_SIM_FILE = 'SIM_default_LTE'
36    LTE_BASIC_CELL_FILE = 'CELL_LTE_config'
37
38    # Simulation config keywords contained in the test name
39    PARAM_FRAME_CONFIG = "tddconfig"
40    PARAM_BW = "bw"
41    PARAM_SCHEDULING = "scheduling"
42    PARAM_SCHEDULING_STATIC = "static"
43    PARAM_SCHEDULING_DYNAMIC = "dynamic"
44    PARAM_PATTERN = "pattern"
45    PARAM_TM = "tm"
46    PARAM_UL_PW = 'pul'
47    PARAM_DL_PW = 'pdl'
48    PARAM_BAND = "band"
49    PARAM_MIMO = "mimo"
50
51    # Test config keywords
52    KEY_TBS_PATTERN = "tbs_pattern_on"
53    KEY_DL_256_QAM = "256_qam_dl"
54    KEY_UL_64_QAM = "64_qam_ul"
55
56    class TransmissionMode(Enum):
57        ''' Transmission modes for LTE (e.g., TM1, TM4, ..)
58
59        '''
60        TM1 = "TM1"
61        TM2 = "TM2"
62        TM3 = "TM3"
63        TM4 = "TM4"
64        TM7 = "TM7"
65        TM8 = "TM8"
66        TM9 = "TM9"
67
68    class MimoMode(Enum):
69        """ Mimo modes """
70
71        MIMO_1x1 = "1x1"
72        MIMO_2x2 = "2x2"
73        MIMO_4x4 = "4x4"
74
75    class SchedulingMode(Enum):
76        ''' Traffic scheduling modes (e.g., STATIC, DYNAMIC)
77
78        '''
79        DYNAMIC = "DYNAMIC"
80        STATIC = "STATIC"
81
82    class DuplexMode(Enum):
83        ''' DL/UL Duplex mode
84
85        '''
86        FDD = "FDD"
87        TDD = "TDD"
88
89    # RSRP signal levels thresholds (as reported by Android) in dBm/15KHz.
90    # Excellent is set to -75 since callbox B Tx power is limited to -30 dBm
91    downlink_rsrp_dictionary = {
92        'excellent': -75,
93        'high': -110,
94        'medium': -115,
95        'weak': -120
96    }
97
98    # Transmitted output power for the phone (dBm)
99    uplink_signal_level_dictionary = {
100        'max': 23,
101        'high': 13,
102        'medium': 3,
103        'low': -20
104    }
105
106    # Total RBs for each bandwidth
107
108    total_rbs_dictionary = {
109        BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 100,
110        BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 75,
111        BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 50,
112        BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 25,
113        BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 15,
114        BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 6
115    }
116
117    # RB groups for each bandwidth
118
119    rbg_dictionary = {
120        BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 4,
121        BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 4,
122        BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 3,
123        BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 2,
124        BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 2,
125        BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 1
126    }
127
128    # Table of minimum number of RBs. This is needed to achieve peak
129    # throughput.
130
131    min_dl_rbs_dictionary = {
132        BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 16,
133        BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 12,
134        BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 9,
135        BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 4,
136        BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 4,
137        BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 2
138    }
139
140    min_ul_rbs_dictionary = {
141        BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 8,
142        BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 6,
143        BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 4,
144        BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 2,
145        BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 2,
146        BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 1
147    }
148
149    # Allowed bandwidth for each band.
150    allowed_bandwidth_dictionary = {
151        1: [5, 10, 15, 20],
152        2: [1.4, 3, 5, 10, 15, 20],
153        3: [1.4, 3, 5, 10, 15, 20],
154        4: [1.4, 3, 5, 10, 15, 20],
155        5: [1.4, 3, 5, 10],
156        7: [5, 10, 15, 20],
157        8: [1.4, 3, 5, 10],
158        10: [5, 10, 15, 20],
159        11: [5, 10],
160        12: [1.4, 3, 5, 10],
161        13: [5, 10],
162        14: [5, 10],
163        17: [5, 10],
164        18: [5, 10, 15],
165        19: [5, 10, 15],
166        20: [5, 10, 15, 20],
167        21: [5, 10, 15],
168        22: [5, 10, 15, 20],
169        24: [5, 10],
170        25: [1.4, 3, 5, 10, 15, 20],
171        26: [1.4, 3, 5, 10, 15],
172        27: [1.4, 3, 5, 10],
173        28: [3, 5, 10, 15, 20],
174        29: [3, 5, 10],
175        30: [5, 10],
176        31: [1.4, 3, 5],
177        32: [5, 10, 15, 20],
178        33: [5, 10, 15, 20],
179        34: [5, 10, 15],
180        35: [1.4, 3, 5, 10, 15, 20],
181        36: [1.4, 3, 5, 10, 15, 20],
182        37: [5, 10, 15, 20],
183        38: [20],
184        39: [5, 10, 15, 20],
185        40: [5, 10, 15, 20],
186        41: [5, 10, 15, 20],
187        42: [5, 10, 15, 20],
188        43: [5, 10, 15, 20],
189        44: [3, 5, 10, 15, 20],
190        45: [5, 10, 15, 20],
191        46: [10, 20],
192        47: [10, 20],
193        48: [5, 10, 15, 20],
194        49: [10, 20],
195        50: [3, 5, 10, 15, 20],
196        51: [3, 5],
197        52: [5, 10, 15, 20],
198        65: [5, 10, 15, 20],
199        66: [1.4, 3, 5, 10, 15, 20],
200        67: [5, 10, 15, 20],
201        68: [5, 10, 15],
202        69: [5],
203        70: [5, 10, 15],
204        71: [5, 10, 15, 20],
205        72: [1.4, 3, 5],
206        73: [1.4, 3, 5],
207        74: [1.4, 3, 5, 10, 15, 20],
208        75: [5, 10, 15, 20],
209        76: [5],
210        85: [5, 10],
211        252: [20],
212        255: [20]
213    }
214
215    def __init__(self, anritsu, log, dut, test_config, calibration_table):
216        """ Configures Anritsu system for LTE simulation with 1 basetation
217
218        Loads a simple LTE simulation enviroment with 1 basestation.
219
220        Args:
221            anritsu: the Anritsu callbox controller
222            log: a logger handle
223            dut: the android device handler
224            test_config: test configuration obtained from the config file
225            calibration_table: a dictionary containing path losses for
226                different bands.
227
228        """
229
230        super().__init__(anritsu, log, dut, test_config, calibration_table)
231        self.file_path = 'C:\\Users\\MD8475{}\\Documents\\DAN_configs\\'.format(
232            self.anritsu._md8475_version)
233
234        if self.anritsu._md8475_version == 'A':
235            self.sim_file_path = "{}{}.wnssp".format(self.file_path,
236                                                     self.LTE_BASIC_SIM_FILE)
237            self.cell_file_path = "{}{}.wnscp".format(self.file_path,
238                                                      self.LTE_BASIC_CELL_FILE)
239        else:
240            self.sim_file_path = "{}{}.wnssp2".format(self.file_path,
241                                                      self.LTE_BASIC_SIM_FILE)
242            self.cell_file_path = "{}{}.wnscp2".format(
243                self.file_path, self.LTE_BASIC_CELL_FILE)
244
245        anritsu.load_simulation_paramfile(self.sim_file_path)
246        anritsu.load_cell_paramfile(self.cell_file_path)
247
248        if not dut.droid.telephonySetPreferredNetworkTypesForSubscription(
249                NETWORK_MODE_LTE_ONLY,
250                dut.droid.subscriptionGetDefaultSubId()):
251            log.error("Couldn't set preferred network type.")
252        else:
253            log.info("Preferred network type set.")
254
255        # Get TBS pattern setting from the test configuration
256        if self.KEY_TBS_PATTERN not in test_config:
257            self.log.warning("The key '{}' is not set in the config file. "
258                             "Setting to true by default.".format(
259                                 self.KEY_TBS_PATTERN))
260
261        self.tbs_pattern_on = test_config.get(self.KEY_TBS_PATTERN, True)
262
263        # Get the 256-QAM setting from the test configuration
264        if self.KEY_DL_256_QAM not in test_config:
265            self.log.warning("The key '{}' is not set in the config file. "
266                             "Setting to false by default.".format(
267                                 self.KEY_DL_256_QAM))
268
269        self.dl_256_qam = test_config.get(self.KEY_DL_256_QAM, False)
270
271        if self.dl_256_qam:
272            if anritsu._md8475_version == 'A':
273                self.log.warning("The key '{}' is set to true but MD8475A "
274                                 "callbox doesn't support that modulation "
275                                 "order.".format(self.KEY_DL_256_QAM))
276                self.dl_256_qam = False
277            else:
278                self.bts1.lte_dl_modulation_order = "256QAM"
279
280        # Get the 64-QAM setting from the test configuration
281        if self.KEY_UL_64_QAM not in test_config:
282            self.log.warning("The key '{}' is not set in the config file. "
283                             "Setting to false by default.".format(
284                                 self.KEY_UL_64_QAM))
285
286        self.ul_64_qam = test_config.get(self.KEY_UL_64_QAM, False)
287
288        if self.ul_64_qam:
289            if anritsu._md8475_version == 'A':
290                self.log.warning("The key '{}' is set to true but MD8475A "
291                                 "callbox doesn't support that modulation "
292                                 "order.".format(self.KEY_UL_64_QAM))
293                self.ul_64_qam = False
294            else:
295                self.bts1.lte_ul_modulation_order = "64QAM"
296
297    def parse_parameters(self, parameters):
298        """ Configs an LTE simulation using a list of parameters.
299
300        Calls the parent method first, then consumes parameters specific to LTE.
301
302        Args:
303            parameters: list of parameters
304        """
305
306        super().parse_parameters(parameters)
307
308        # Setup band
309
310        values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
311
312        if not values:
313            raise ValueError(
314                "The test name needs to include parameter '{}' followed by "
315                "the required band number.".format(self.PARAM_BAND))
316
317        band = values[1]
318
319        self.set_band(self.bts1, band)
320
321        # Set DL/UL frame configuration
322        if self.get_duplex_mode(band) == self.DuplexMode.TDD:
323
324            values = self.consume_parameter(parameters,
325                                            self.PARAM_FRAME_CONFIG, 1)
326            if not values:
327                raise ValueError("When a TDD band is selected the frame "
328                                 "structure has to be indicated with the '{}' "
329                                 "parameter followed by a number from 0 to 6."
330                                 .format(self.PARAM_FRAME_CONFIG))
331
332            frame_config = int(values[1])
333
334            self.set_dlul_configuration(self.bts1, frame_config)
335
336        # Setup bandwidth
337
338        values = self.consume_parameter(parameters, self.PARAM_BW, 1)
339
340        if not values:
341            raise ValueError(
342                "The test name needs to include parameter {} followed by an "
343                "int value (to indicate 1.4 MHz use 14).".format(
344                    self.PARAM_BW))
345
346        bw = float(values[1])
347
348        if bw == 14:
349            bw = 1.4
350
351        self.set_channel_bandwidth(self.bts1, bw)
352
353        # Setup mimo mode
354
355        values = self.consume_parameter(parameters, self.PARAM_MIMO, 1)
356
357        if not values:
358            raise ValueError(
359                "The test name needs to include parameter '{}' followed by the "
360                "mimo mode.".format(self.PARAM_MIMO))
361
362        for mimo_mode in LteSimulation.MimoMode:
363            if values[1] == mimo_mode.value:
364                self.set_mimo_mode(self.bts1, mimo_mode)
365                break
366        else:
367            raise ValueError("The {} parameter needs to be followed by either "
368                             "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
369
370        if (mimo_mode == LteSimulation.MimoMode.MIMO_4x4
371                and self.anritsu._md8475_version == 'A'):
372            raise ValueError("The test requires 4x4 MIMO, but that is not "
373                             "supported by the MD8475A callbox.")
374
375        self.set_mimo_mode(self.bts1, mimo_mode)
376
377        # Setup transmission mode
378
379        values = self.consume_parameter(parameters, self.PARAM_TM, 1)
380
381        if not values:
382            raise ValueError(
383                "The test name needs to include parameter {} followed by an "
384                "int value from 1 to 4 indicating transmission mode.".format(
385                    self.PARAM_TM))
386
387        for tm in LteSimulation.TransmissionMode:
388            if values[1] == tm.value[2:]:
389                self.set_transmission_mode(self.bts1, tm)
390                break
391        else:
392            raise ValueError("The {} parameter needs to be followed by either "
393                             "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
394                                 self.PARAM_MIMO))
395
396        # Setup scheduling mode
397
398        values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
399
400        if not values:
401            scheduling = LteSimulation.SchedulingMode.STATIC
402            self.log.warning(
403                "The test name does not include the '{}' parameter. Setting to "
404                "static by default.".format(self.PARAM_SCHEDULING))
405        elif values[1] == self.PARAM_SCHEDULING_DYNAMIC:
406            scheduling = LteSimulation.SchedulingMode.DYNAMIC
407        elif values[1] == self.PARAM_SCHEDULING_STATIC:
408            scheduling = LteSimulation.SchedulingMode.STATIC
409        else:
410            raise ValueError(
411                "The test name parameter '{}' has to be followed by either "
412                "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING))
413
414        if scheduling == LteSimulation.SchedulingMode.STATIC:
415
416            values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
417
418            if not values:
419                self.log.warning(
420                    "The '{}' parameter was not set, using 100% RBs for both "
421                    "DL and UL. To set the percentages of total RBs include "
422                    "the '{}' parameter followed by two ints separated by an "
423                    "underscore indicating downlink and uplink percentages."
424                    .format(self.PARAM_PATTERN, self.PARAM_PATTERN))
425                dl_pattern = 100
426                ul_pattern = 100
427            else:
428                dl_pattern = int(values[1])
429                ul_pattern = int(values[2])
430
431            if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100):
432                raise ValueError(
433                    "The scheduling pattern parameters need to be two "
434                    "positive numbers between 0 and 100.")
435
436            dl_rbs, ul_rbs = self.allocation_percentages_to_rbs(
437                self.bts1, dl_pattern, ul_pattern)
438
439            if self.dl_256_qam and bw == 1.4:
440                mcs_dl = 26
441            elif not self.dl_256_qam and self.tbs_pattern_on and bw != 1.4:
442                mcs_dl = 28
443            else:
444                mcs_dl = 27
445
446            if self.ul_64_qam:
447                mcs_ul = 28
448            else:
449                mcs_ul = 23
450
451            self.set_scheduling_mode(
452                self.bts1,
453                LteSimulation.SchedulingMode.STATIC,
454                packet_rate=BtsPacketRate.LTE_MANUAL,
455                nrb_dl=dl_rbs,
456                nrb_ul=ul_rbs,
457                mcs_ul=mcs_ul,
458                mcs_dl=mcs_dl)
459
460        else:
461
462            self.set_scheduling_mode(self.bts1,
463                                     LteSimulation.SchedulingMode.DYNAMIC)
464
465        # Get uplink power
466
467        ul_power = self.get_uplink_power_from_parameters(parameters)
468
469        # Power is not set on the callbox until after the simulation is
470        # started. Saving this value in a variable for later
471        self.sim_ul_power = ul_power
472
473        # Get downlink power
474
475        dl_power = self.get_downlink_power_from_parameters(parameters)
476
477        # Power is not set on the callbox until after the simulation is
478        # started. Saving this value in a variable for later
479        self.sim_dl_power = dl_power
480
481    def get_uplink_power_from_parameters(self, parameters):
482        """ Reads uplink power from a list of parameters. """
483
484        values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1)
485
486        if not values or values[1] not in self.uplink_signal_level_dictionary:
487            raise ValueError(
488                "The test name needs to include parameter {} followed by one "
489                "the following values: {}.".format(self.PARAM_UL_PW, [
490                    val for val in self.uplink_signal_level_dictionary.keys()
491                ]))
492
493        return self.uplink_signal_level_dictionary[values[1]]
494
495    def get_downlink_power_from_parameters(self, parameters):
496        """ Reads downlink power from a list of parameters. """
497
498        values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1)
499
500        if values:
501            if values[1] not in self.downlink_rsrp_dictionary:
502                raise ValueError("Invalid signal level value {}.".format(
503                    values[1]))
504            else:
505                return self.downlink_rsrp_dictionary[values[1]]
506        else:
507            # Use default value
508            power = self.downlink_rsrp_dictionary['excellent']
509            self.log.info(
510                "No DL signal level value was indicated in the test "
511                "parameters. Using default value of {} RSRP.".format(power))
512            return power
513
514    def set_downlink_rx_power(self, bts, rsrp):
515        """ Sets downlink rx power in RSRP using calibration
516
517        Lte simulation overrides this method so that it can convert from
518        RSRP to total signal power transmitted from the basestation.
519
520        Args:
521            bts: the base station in which to change the signal level
522            rsrp: desired rsrp, contained in a key value pair
523        """
524
525        power = self.rsrp_to_signal_power(rsrp, bts)
526
527        self.log.info(
528            "Setting downlink signal level to {} RSRP ({} dBm)".format(
529                rsrp, power))
530
531        # Use parent method to set signal level
532        super().set_downlink_rx_power(bts, power)
533
534    def downlink_calibration(self,
535                             bts,
536                             rat=None,
537                             power_units_conversion_func=None):
538        """ Computes downlink path loss and returns the calibration value
539
540        The bts needs to be set at the desired config (bandwidth, mode, etc)
541        before running the calibration. The phone also needs to be attached
542        to the desired basesation for calibration
543
544        Args:
545            bts: basestation handle
546            rat: ignored, replaced by 'lteRsrp'
547            power_units_conversion_func: ignored, replaced by
548                self.rsrp_to_signal_power
549
550        Returns:
551            Dowlink calibration value and measured DL power. Note that the
552            phone only reports RSRP of the primary chain
553        """
554
555        return super().downlink_calibration(
556            bts,
557            rat='lteRsrp',
558            power_units_conversion_func=self.rsrp_to_signal_power)
559
560    def rsrp_to_signal_power(self, rsrp, bts):
561        """ Converts rsrp to total band signal power
562
563        RSRP is measured per subcarrier, so total band power needs to be
564        multiplied by the number of subcarriers being used.
565
566        Args:
567            rsrp: desired rsrp in dBm
568            bts: basestation handler for which the unit conversion is done
569
570        Returns:
571            Total band signal power in dBm
572        """
573
574        bandwidth = bts.bandwidth
575
576        if bandwidth == BtsBandwidth.LTE_BANDWIDTH_20MHz.value:  # 100 RBs
577            power = rsrp + 30.79
578        elif bandwidth == BtsBandwidth.LTE_BANDWIDTH_15MHz.value:  # 75 RBs
579            power = rsrp + 29.54
580        elif bandwidth == BtsBandwidth.LTE_BANDWIDTH_10MHz.value:  # 50 RBs
581            power = rsrp + 27.78
582        elif bandwidth == BtsBandwidth.LTE_BANDWIDTH_5MHz.value:  # 25 RBs
583            power = rsrp + 24.77
584        elif bandwidth == BtsBandwidth.LTE_BANDWIDTH_3MHz.value:  # 15 RBs
585            power = rsrp + 22.55
586        elif bandwidth == BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value:  # 6 RBs
587            power = rsrp + 18.57
588        else:
589            raise ValueError("Invalid bandwidth value.")
590
591        return power
592
593    def maximum_downlink_throughput(self):
594        """ Calculates maximum achievable downlink throughput in the current
595            simulation state.
596
597        Returns:
598            Maximum throughput in mbps.
599
600        """
601
602        return self.bts_maximum_downlink_throughtput(self.bts1)
603
604    def bts_maximum_downlink_throughtput(self, bts):
605        """ Calculates maximum achievable downlink throughput for the selected
606        basestation.
607
608        Args:
609            bts: basestation handle
610
611        Returns:
612            Maximum throughput in mbps.
613
614        """
615
616        bandwidth = bts.bandwidth
617        rb_ratio = float(bts.nrb_dl) / self.total_rbs_dictionary[bandwidth]
618        streams = float(bts.dl_antenna)
619        mcs = bts.lte_mcs_dl
620
621        max_rate_per_stream = None
622
623        if not self.dl_256_qam and self.tbs_pattern_on and mcs == "28":
624            max_rate_per_stream = {
625                BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 9.96,
626                BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 17.0,
627                BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 34.7,
628                BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 52.7,
629                BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 72.2
630            }.get(bandwidth, None)
631        if not self.dl_256_qam and self.tbs_pattern_on and mcs == "27":
632            max_rate_per_stream = {
633                BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 2.94,
634            }.get(bandwidth, None)
635        elif not self.dl_256_qam and not self.tbs_pattern_on and mcs == "27":
636            max_rate_per_stream = {
637                BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 2.87,
638                BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 7.7,
639                BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 14.4,
640                BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 28.7,
641                BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 42.3,
642                BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 57.7
643            }.get(bandwidth, None)
644        elif self.dl_256_qam and self.tbs_pattern_on and mcs == "27":
645            max_rate_per_stream = {
646                BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 13.2,
647                BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 22.9,
648                BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 46.3,
649                BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 72.2,
650                BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 93.9
651            }.get(bandwidth, None)
652        elif self.dl_256_qam and self.tbs_pattern_on and mcs == "26":
653            max_rate_per_stream = {
654                BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 3.96,
655            }.get(bandwidth, None)
656        elif self.dl_256_qam and not self.tbs_pattern_on and mcs == "27":
657            max_rate_per_stream = {
658                BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 11.3,
659                BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 19.8,
660                BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 44.1,
661                BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 68.1,
662                BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 88.4
663            }.get(bandwidth, None)
664        elif self.dl_256_qam and not self.tbs_pattern_on and mcs == "26":
665            max_rate_per_stream = {
666                BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 3.96,
667            }.get(bandwidth, None)
668
669        if not max_rate_per_stream:
670            raise NotImplementedError(
671                "The calculation for tbs pattern = {} "
672                "and mcs = {} is not implemented.".format(
673                    "FULLALLOCATION" if self.tbs_pattern_on else "OFF", mcs))
674
675        return max_rate_per_stream * streams * rb_ratio
676
677    def maximum_uplink_throughput(self):
678        """ Calculates maximum achievable uplink throughput in the current
679            simulation state.
680
681        Returns:
682            Maximum throughput in mbps.
683
684        """
685
686        return self.bts_maximum_uplink_throughtput(self.bts1)
687
688    def bts_maximum_uplink_throughtput(self, bts):
689        """ Calculates maximum achievable uplink throughput for the selected
690        basestation.
691
692        Args:
693            bts: basestation handle
694
695        Returns:
696            Maximum throughput in mbps.
697
698        """
699
700        bandwidth = bts.bandwidth
701        rb_ratio = float(bts.nrb_ul) / self.total_rbs_dictionary[bandwidth]
702        mcs = bts.lte_mcs_ul
703
704        max_rate_per_stream = None
705        if mcs == "23" and not self.ul_64_qam:
706            max_rate_per_stream = {
707                BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 2.85,
708                BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 7.18,
709                BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 12.1,
710                BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 24.5,
711                BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 36.5,
712                BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 49.1
713            }.get(bandwidth, None)
714        elif mcs == "28" and self.ul_64_qam:
715            max_rate_per_stream = {
716                BtsBandwidth.LTE_BANDWIDTH_1dot4MHz.value: 4.2,
717                BtsBandwidth.LTE_BANDWIDTH_3MHz.value: 10.5,
718                BtsBandwidth.LTE_BANDWIDTH_5MHz.value: 17.2,
719                BtsBandwidth.LTE_BANDWIDTH_10MHz.value: 35.3,
720                BtsBandwidth.LTE_BANDWIDTH_15MHz.value: 53.0,
721                BtsBandwidth.LTE_BANDWIDTH_20MHz.value: 72.6
722            }.get(bandwidth, None)
723
724        if not max_rate_per_stream:
725            raise NotImplementedError("The calculation fir mcs = {} is not "
726                                      "implemented.".format(
727                                          "FULLALLOCATION" if
728                                          self.tbs_pattern_on else "OFF", mcs))
729
730        return max_rate_per_stream * rb_ratio
731
732    def set_transmission_mode(self, bts, tmode):
733        """ Sets the transmission mode for the LTE basetation
734
735        Args:
736            bts: basestation handle
737            tmode: Enum list from class 'TransmissionModeLTE'
738        """
739
740        # If the selected transmission mode does not support the number of DL
741        # antennas, throw an exception.
742        if (tmode in [self.TransmissionMode.TM1, self.TransmissionMode.TM7]
743                and bts.dl_antenna != '1'):
744            # TM1 and TM7 only support 1 DL antenna
745            raise ValueError("{} allows only one DL antenna. Change the "
746                             "number of DL antennas before setting the "
747                             "transmission mode.".format(tmode.value))
748        elif tmode == self.TransmissionMode.TM8 and bts.dl_antenna != '2':
749            # TM8 requires 2 DL antennas
750            raise ValueError("TM2 requires two DL antennas. Change the "
751                             "number of DL antennas before setting the "
752                             "transmission mode.")
753        elif (tmode in [
754                self.TransmissionMode.TM2, self.TransmissionMode.TM3,
755                self.TransmissionMode.TM4, self.TransmissionMode.TM9
756        ] and bts.dl_antenna == '1'):
757            # TM2, TM3, TM4 and TM9 require 2 or 4 DL antennas
758            raise ValueError("{} requires at least two DL atennas. Change the "
759                             "number of DL antennas before setting the "
760                             "transmission mode.".format(tmode.value))
761
762        # The TM mode is allowed for the current number of DL antennas, so it
763        # is safe to change this setting now
764        bts.transmode = tmode.value
765
766        time.sleep(5)  # It takes some time to propagate the new settings
767
768    def set_mimo_mode(self, bts, mimo):
769        """ Sets the number of DL antennas for the desired MIMO mode.
770
771        Args:
772            bts: basestation handle
773            mimo: object of class MimoMode
774        """
775
776        # If the requested mimo mode is not compatible with the current TM,
777        # warn the user before changing the value.
778
779        if mimo == self.MimoMode.MIMO_1x1:
780            if bts.transmode not in [
781                    self.TransmissionMode.TM1, self.TransmissionMode.TM7
782            ]:
783                self.log.warning(
784                    "Using only 1 DL antennas is not allowed with "
785                    "the current transmission mode. Changing the "
786                    "number of DL antennas will override this "
787                    "setting.")
788            bts.dl_antenna = 1
789        elif mimo == self.MimoMode.MIMO_2x2:
790            if bts.transmode not in [
791                    self.TransmissionMode.TM2, self.TransmissionMode.TM3,
792                    self.TransmissionMode.TM4, self.TransmissionMode.TM8,
793                    self.TransmissionMode.TM9
794            ]:
795                self.log.warning("Using two DL antennas is not allowed with "
796                                 "the current transmission mode. Changing the "
797                                 "number of DL antennas will override this "
798                                 "setting.")
799            bts.dl_antenna = 2
800        elif mimo == self.MimoMode.MIMO_4x4:
801            if bts.transmode not in [
802                    self.TransmissionMode.TM2, self.TransmissionMode.TM3,
803                    self.TransmissionMode.TM4, self.TransmissionMode.TM9
804            ]:
805                self.log.warning("Using four DL antennas is not allowed with "
806                                 "the current transmission mode. Changing the "
807                                 "number of DL antennas will override this "
808                                 "setting.")
809
810            bts.dl_antenna = 4
811        else:
812            RuntimeError("The requested MIMO mode is not supported.")
813
814    def set_scheduling_mode(self,
815                            bts,
816                            scheduling,
817                            packet_rate=None,
818                            mcs_dl=None,
819                            mcs_ul=None,
820                            nrb_dl=None,
821                            nrb_ul=None):
822        """ Sets the scheduling mode for LTE
823
824        Args:
825            bts: basestation handle
826            scheduling: DYNAMIC or STATIC scheduling (Enum list)
827            mcs_dl: Downlink MCS (only for STATIC scheduling)
828            mcs_ul: Uplink MCS (only for STATIC scheduling)
829            nrb_dl: Number of RBs for downlink (only for STATIC scheduling)
830            nrb_ul: Number of RBs for uplink (only for STATIC scheduling)
831        """
832
833        bts.lte_scheduling_mode = scheduling.value
834
835        if scheduling == self.SchedulingMode.STATIC:
836
837            if not packet_rate:
838                raise RuntimeError("Packet rate needs to be indicated when "
839                                   "selecting static scheduling.")
840
841            bts.packet_rate = packet_rate
842            bts.tbs_pattern = "FULLALLOCATION" if self.tbs_pattern_on else "OFF"
843
844            if packet_rate == BtsPacketRate.LTE_MANUAL:
845
846                if not (mcs_dl and mcs_ul and nrb_dl and nrb_ul):
847                    raise RuntimeError("When using manual packet rate the "
848                                       "number of dl/ul RBs and the dl/ul "
849                                       "MCS needs to be indicated with the "
850                                       "optional arguments.")
851
852                bts.lte_mcs_dl = mcs_dl
853                bts.lte_mcs_ul = mcs_ul
854                bts.nrb_dl = nrb_dl
855                bts.nrb_ul = nrb_ul
856
857        time.sleep(5)  # It takes some time to propagate the new settings
858
859    def allocation_percentages_to_rbs(self, bts, dl, ul):
860        """ Converts usage percentages to number of DL/UL RBs
861
862        Because not any number of DL/UL RBs can be obtained for a certain
863        bandwidth, this function calculates the number of RBs that most
864        closely matches the desired DL/UL percentages.
865
866        Args:
867            bts: base station handle
868            dl: desired percentage of downlink RBs
869            ul: desired percentage of uplink RBs
870        Returns:
871            a tuple indicating the number of downlink and uplink RBs
872        """
873
874        # Validate the arguments
875        if (not 0 <= dl <= 100) or (not 0 <= ul <= 100):
876            raise ValueError("The percentage of DL and UL RBs have to be two "
877                             "positive between 0 and 100.")
878
879        # Get the available number of RBs for the channel bandwidth
880        bw = bts.bandwidth
881        # Get the current transmission mode
882        tm = bts.transmode
883        # Get min and max values from tables
884        max_rbs = self.total_rbs_dictionary[bw]
885        min_dl_rbs = self.min_dl_rbs_dictionary[bw]
886        min_ul_rbs = self.min_ul_rbs_dictionary[bw]
887
888        def percentage_to_amount(min_val, max_val, percentage):
889            """ Returns the integer between min_val and max_val that is closest
890            to percentage/100*max_val
891            """
892
893            # Calculate the value that corresponds to the required percentage.
894            closest_int = round(max_val * percentage / 100)
895            # Cannot be less than min_val
896            closest_int = max(closest_int, min_val)
897            # RBs cannot be more than max_rbs
898            closest_int = min(closest_int, max_val)
899
900            return closest_int
901
902        # Calculate the number of DL RBs
903
904        # Get the number of DL RBs that corresponds to
905        #  the required percentage.
906        desired_dl_rbs = percentage_to_amount(
907            min_val=min_dl_rbs, max_val=max_rbs, percentage=dl)
908
909        if (tm == self.TransmissionMode.TM3.value
910                or tm == self.TransmissionMode.TM4.value):
911
912            # For TM3 and TM4 the number of DL RBs needs to be max_rbs or a
913            # multiple of the RBG size
914
915            if desired_dl_rbs == max_rbs:
916                dl_rbs = max_rbs
917            else:
918                dl_rbs = (math.ceil(desired_dl_rbs / self.rbg_dictionary[bw]) *
919                          self.rbg_dictionary[bw])
920
921        else:
922            # The other TMs allow any number of RBs between 1 and max_rbs
923            dl_rbs = desired_dl_rbs
924
925        # Calculate the number of UL RBs
926
927        # Get the number of UL RBs that corresponds
928        # to the required percentage
929        desired_ul_rbs = percentage_to_amount(
930            min_val=min_ul_rbs, max_val=max_rbs, percentage=ul)
931
932        # Create a list of all possible UL RBs assignment
933        # The standard allows any number that can be written as
934        # 2**a * 3**b * 5**c for any combination of a, b and c.
935
936        def pow_range(max_value, base):
937            """ Returns a range of all possible powers of base under
938              the given max_value.
939          """
940            return range(int(math.ceil(math.log(max_value, base))))
941
942        possible_ul_rbs = [
943            2**a * 3**b * 5**c
944            for a in pow_range(max_rbs, 2) for b in pow_range(max_rbs, 3)
945            for c in pow_range(max_rbs, 5) if 2**a * 3**b * 5**c <= max_rbs
946        ]
947
948        # Find the value in the list that is closest to desired_ul_rbs
949        differences = [abs(rbs - desired_ul_rbs) for rbs in possible_ul_rbs]
950        ul_rbs = possible_ul_rbs[differences.index(min(differences))]
951
952        # Report what are the obtained RB percentages
953        self.log.info("Requested a {}% / {}% RB allocation. Closest possible "
954                      "percentages are {}% / {}%.".format(
955                          dl, ul,
956                          round(100 * dl_rbs / max_rbs),
957                          round(100 * ul_rbs / max_rbs)))
958
959        return dl_rbs, ul_rbs
960
961    def set_channel_bandwidth(self, bts, bandwidth):
962        """ Sets the LTE channel bandwidth (MHz)
963
964        Args:
965            bts: basestation handle
966            bandwidth: desired bandwidth (MHz)
967        """
968        if bandwidth == 20:
969            bts.bandwidth = BtsBandwidth.LTE_BANDWIDTH_20MHz
970        elif bandwidth == 15:
971            bts.bandwidth = BtsBandwidth.LTE_BANDWIDTH_15MHz
972        elif bandwidth == 10:
973            bts.bandwidth = BtsBandwidth.LTE_BANDWIDTH_10MHz
974        elif bandwidth == 5:
975            bts.bandwidth = BtsBandwidth.LTE_BANDWIDTH_5MHz
976        elif bandwidth == 3:
977            bts.bandwidth = BtsBandwidth.LTE_BANDWIDTH_3MHz
978        elif bandwidth == 1.4:
979            bts.bandwidth = BtsBandwidth.LTE_BANDWIDTH_1dot4MHz
980        else:
981            msg = "Bandwidth = {} MHz is not valid for LTE".format(bandwidth)
982            self.log.Error(msg)
983            raise ValueError(msg)
984        time.sleep(5)  # It takes some time to propagate the new settings
985
986    def set_dlul_configuration(self, bts, config):
987        """ Sets the frame structure for TDD bands.
988
989        Args:
990            config: the desired frame structure. An int between 0 and 6.
991        """
992
993        if not 0 <= config <= 6:
994            raise ValueError("The frame structure configuration has to be a "
995                             "number between 0 and 6")
996
997        bts.uldl_configuration = config
998
999        # Wait for the setting to propagate
1000        time.sleep(5)
1001
1002    def calibrate(self):
1003        """ Calculates UL and DL path loss if it wasn't done before
1004
1005        This method overrides the baseclass specifically for LTE calibration.
1006        For LTE cal, the simulation is set to TM1 and 1 antenna.
1007
1008        """
1009
1010        # Set in TM1 mode and 1 antenna for downlink calibration for LTE
1011        init_dl_antenna = None
1012        init_transmode = None
1013        if int(self.bts1.dl_antenna) != 1:
1014            init_dl_antenna = self.bts1.dl_antenna
1015            init_transmode = self.bts1.transmode
1016            self.bts1.dl_antenna = 1
1017            self.bts1.transmode = "TM1"
1018            time.sleep(5)  # It takes some time to propagate the new settings
1019
1020        super().calibrate()
1021
1022        if init_dl_antenna is not None:
1023            self.bts1.dl_antenna = init_dl_antenna
1024            self.bts1.transmode = init_transmode
1025            time.sleep(5)  # It takes some time to propagate the new settings
1026
1027    def get_duplex_mode(self, band):
1028        """ Determines if the band uses FDD or TDD duplex mode
1029
1030        Args:
1031            band: a band number
1032        Returns:
1033            an variable of class DuplexMode indicating if band is FDD or TDD
1034        """
1035
1036        if 33 <= int(band) <= 46:
1037            return self.DuplexMode.TDD
1038        else:
1039            return self.DuplexMode.FDD
1040
1041    def set_band(self, bts, band, calibrate_if_necessary=True):
1042        """ Sets the right duplex mode before switching to a new band.
1043
1044        Args:
1045            bts: basestation handle
1046            band: desired band
1047            calibrate_if_necessary: if False calibration will be skipped
1048        """
1049
1050        bts.duplex_mode = self.get_duplex_mode(band).value
1051
1052        super().set_band(bts, band, calibrate_if_necessary)
1053