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 ntpath
18import time
19
20from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsusim
21from acts.controllers.anritsu_lib.md8475a import BtsNumber
22from acts.controllers.anritsu_lib.md8475a import BtsPacketRate
23from acts.controllers.cellular_lib.BaseSimulation import BaseSimulation
24from acts.controllers.cellular_lib import BaseCellularDut
25
26
27class UmtsSimulation(BaseSimulation):
28    """ Single base station simulation. """
29
30    # Simulation config files in the callbox computer.
31    # These should be replaced in the future by setting up
32    # the same configuration manually.
33
34    UMTS_BASIC_SIM_FILE = 'SIM_default_WCDMA.wnssp'
35
36    UMTS_R99_CELL_FILE = 'CELL_WCDMA_R99_config.wnscp'
37
38    UMTS_R7_CELL_FILE = 'CELL_WCDMA_R7_config.wnscp'
39
40    UMTS_R8_CELL_FILE = 'CELL_WCDMA_R8_config.wnscp'
41
42    # Configuration dictionary keys
43    PARAM_RELEASE_VERSION = "r"
44    PARAM_RELEASE_VERSION_99 = "99"
45    PARAM_RELEASE_VERSION_8 = "8"
46    PARAM_RELEASE_VERSION_7 = "7"
47    PARAM_BAND = "band"
48    PARAM_RRC_STATUS_CHANGE_TIMER = "rrcstatuschangetimer"
49
50    # Units in which signal level is defined in DOWNLINK_SIGNAL_LEVEL_DICTIONARY
51    DOWNLINK_SIGNAL_LEVEL_UNITS = "RSCP"
52
53    # RSCP signal levels thresholds (as reported by Android). Units are dBm
54    # Using LTE thresholds + 24 dB to have equivalent SPD
55    # 24 dB comes from 10 * log10(3.84 MHz / 15 KHz)
56
57    DOWNLINK_SIGNAL_LEVEL_DICTIONARY = {
58        'excellent': -51,
59        'high': -76,
60        'medium': -86,
61        'weak': -96
62    }
63
64    # Transmitted output power for the phone
65    # Stronger Tx power means that the signal received by the BTS is weaker
66    # Units are dBm
67
68    UPLINK_SIGNAL_LEVEL_DICTIONARY = {
69        'low': -20,
70        'medium': 8,
71        'high': 15,
72        'max': 23
73    }
74
75    # Converts packet rate to the throughput that can be actually obtained in
76    # Mbits/s
77
78    packet_rate_to_dl_throughput = {
79        BtsPacketRate.WCDMA_DL384K_UL64K: 0.362,
80        BtsPacketRate.WCDMA_DL21_6M_UL5_76M: 18.5,
81        BtsPacketRate.WCDMA_DL43_2M_UL5_76M: 36.9
82    }
83
84    packet_rate_to_ul_throughput = {
85        BtsPacketRate.WCDMA_DL384K_UL64K: 0.0601,
86        BtsPacketRate.WCDMA_DL21_6M_UL5_76M: 5.25,
87        BtsPacketRate.WCDMA_DL43_2M_UL5_76M: 5.25
88    }
89
90    def __init__(self, simulator, log, dut, test_config, calibration_table):
91        """ Initializes the cellular simulator for a UMTS simulation.
92
93        Loads a simple UMTS simulation environment with 1 basestation. It also
94        creates the BTS handle so we can change the parameters as desired.
95
96        Args:
97            simulator: a cellular simulator controller
98            log: a logger handle
99            dut: a device handler implementing BaseCellularDut
100            test_config: test configuration obtained from the config file
101            calibration_table: a dictionary containing path losses for
102                different bands.
103
104        """
105        # The UMTS simulation relies on the cellular simulator to be a MD8475
106        if not isinstance(self.simulator, anritsusim.MD8475CellularSimulator):
107            raise ValueError('The UMTS simulation relies on the simulator to '
108                             'be an Anritsu MD8475 A/B instrument.')
109
110        # The Anritsu controller needs to be unwrapped before calling
111        # super().__init__ because setup_simulator() requires self.anritsu and
112        # will be called during the parent class initialization.
113        self.anritsu = self.simulator.anritsu
114        self.bts1 = self.anritsu.get_BTS(BtsNumber.BTS1)
115
116        super().__init__(simulator, log, dut, test_config, calibration_table)
117
118        self.dut.set_preferred_network_type(
119            BaseCellularDut.PreferredNetworkType.WCDMA_ONLY)
120
121        self.release_version = None
122        self.packet_rate = None
123
124    def setup_simulator(self):
125        """ Do initial configuration in the simulator. """
126
127        # Load callbox config files
128        callbox_config_path = self.CALLBOX_PATH_FORMAT_STR.format(
129            self.anritsu._md8475_version)
130
131        self.anritsu.load_simulation_paramfile(
132            ntpath.join(callbox_config_path, self.UMTS_BASIC_SIM_FILE))
133
134        # Start simulation if it wasn't started
135        self.anritsu.start_simulation()
136
137    def configure(self, parameters):
138        """ Configures simulation using a dictionary of parameters.
139
140        Processes UMTS configuration parameters.
141
142        Args:
143            parameters: a configuration dictionary
144        """
145        super().configure(parameters)
146
147        # Setup band
148        if self.PARAM_BAND not in parameters:
149            raise ValueError(
150                "The configuration dictionary must include a key '{}' with "
151                "the required band number.".format(self.PARAM_BAND))
152
153        self.set_band(self.bts1, parameters[self.PARAM_BAND])
154        self.load_pathloss_if_required()
155
156        # Setup release version
157        if (self.PARAM_RELEASE_VERSION not in parameters
158                or parameters[self.PARAM_RELEASE_VERSION] not in [
159                    self.PARAM_RELEASE_VERSION_7, self.PARAM_RELEASE_VERSION_8,
160                    self.PARAM_RELEASE_VERSION_99
161                ]):
162            raise ValueError(
163                "The configuration dictionary must include a key '{}' with a "
164                "valid release version.".format(self.PARAM_RELEASE_VERSION))
165
166        self.set_release_version(self.bts1,
167                                 parameters[self.PARAM_RELEASE_VERSION])
168
169        # Setup W-CDMA RRC status change and CELL_DCH timer for idle test case
170        if self.PARAM_RRC_STATUS_CHANGE_TIMER not in parameters:
171            self.log.info(
172                "The config dictionary does not include a '{}' key. Disabled "
173                "by default.".format(self.PARAM_RRC_STATUS_CHANGE_TIMER))
174            self.anritsu.set_umts_rrc_status_change(False)
175        else:
176            self.rrc_sc_timer = int(
177                parameters[self.PARAM_RRC_STATUS_CHANGE_TIMER])
178            self.anritsu.set_umts_rrc_status_change(True)
179            self.anritsu.set_umts_dch_stat_timer(self.rrc_sc_timer)
180
181    def set_release_version(self, bts, release_version):
182        """ Sets the release version.
183
184        Loads the cell parameter file matching the requested release version.
185        Does nothing is release version is already the one requested.
186
187        """
188
189        if release_version == self.release_version:
190            self.log.info(
191                "Release version is already {}.".format(release_version))
192            return
193        if release_version == self.PARAM_RELEASE_VERSION_99:
194
195            cell_parameter_file = self.UMTS_R99_CELL_FILE
196            self.packet_rate = BtsPacketRate.WCDMA_DL384K_UL64K
197
198        elif release_version == self.PARAM_RELEASE_VERSION_7:
199
200            cell_parameter_file = self.UMTS_R7_CELL_FILE
201            self.packet_rate = BtsPacketRate.WCDMA_DL21_6M_UL5_76M
202
203        elif release_version == self.PARAM_RELEASE_VERSION_8:
204
205            cell_parameter_file = self.UMTS_R8_CELL_FILE
206            self.packet_rate = BtsPacketRate.WCDMA_DL43_2M_UL5_76M
207
208        else:
209            raise ValueError("Invalid UMTS release version number.")
210
211        self.anritsu.load_cell_paramfile(
212            ntpath.join(self.callbox_config_path, cell_parameter_file))
213
214        self.release_version = release_version
215
216        # Loading a cell parameter file stops the simulation
217        self.start()
218
219        bts.packet_rate = self.packet_rate
220
221    def maximum_downlink_throughput(self):
222        """ Calculates maximum achievable downlink throughput in the current
223            simulation state.
224
225        Returns:
226            Maximum throughput in mbps.
227
228        """
229
230        if self.packet_rate not in self.packet_rate_to_dl_throughput:
231            raise NotImplementedError("Packet rate not contained in the "
232                                      "throughput dictionary.")
233        return self.packet_rate_to_dl_throughput[self.packet_rate]
234
235    def maximum_uplink_throughput(self):
236        """ Calculates maximum achievable uplink throughput in the current
237            simulation state.
238
239        Returns:
240            Maximum throughput in mbps.
241
242        """
243
244        if self.packet_rate not in self.packet_rate_to_ul_throughput:
245            raise NotImplementedError("Packet rate not contained in the "
246                                      "throughput dictionary.")
247        return self.packet_rate_to_ul_throughput[self.packet_rate]
248
249    def set_downlink_rx_power(self, bts, signal_level):
250        """ Starts IP data traffic while setting downlink power.
251
252        This is only necessary for UMTS for unclear reasons. b/139026916 """
253
254        # Starts IP traffic while changing this setting to force the UE to be
255        # in Communication state, as UL power cannot be set in Idle state
256        self.start_traffic_for_calibration()
257
258        # Wait until it goes to communication state
259        self.anritsu.wait_for_communication_state()
260
261        super().set_downlink_rx_power(bts, signal_level)
262
263        # Stop IP traffic after setting the signal level
264        self.stop_traffic_for_calibration()
265
266    def set_band(self, bts, band):
267        """ Sets the band used for communication.
268
269        Args:
270            bts: basestation handle
271            band: desired band
272        """
273
274        bts.band = band
275        time.sleep(5)  # It takes some time to propagate the new band
276