1#!/usr/bin/env python3.4
2#
3#   Copyright 2021 - 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 collections
18import itertools
19
20import pyvisa
21import time
22from acts import logger
23from acts import asserts as acts_asserts
24from acts_contrib.test_utils.cellular.performance import cellular_performance_test_utils as cputils
25
26SHORT_SLEEP = 1
27VERY_SHORT_SLEEP = 0.1
28MEDIUM_SLEEP = 5
29SUBFRAME_DURATION = 0.001
30VISA_QUERY_DELAY = 0.01
31
32
33class Keysight5GTestApp(object):
34    """Controller for the Keysight 5G NR Test Application.
35
36    This controller enables interacting with a Keysight Test Application
37    running on a remote test PC and implements many of the configuration
38    parameters supported in test app.
39    """
40
41    VISA_LOCATION = '/opt/keysight/iolibs/libktvisa32.so'
42
43    def __init__(self, config):
44        self.config = config
45        self.test_app_settings = {
46            'lte_cell_count': 0,
47            'nr_cell_count': 0,
48            'lte_cell_configs': [],
49            'nr_cell_configs': []
50        }
51        self.log = logger.create_tagged_trace_logger("{}{}".format(
52            self.config['brand'], self.config['model']))
53        self.resource_manager = pyvisa.ResourceManager(self.VISA_LOCATION)
54        self.test_app = self.resource_manager.open_resource(
55            'TCPIP0::{}::{}::INSTR'.format(self.config['ip_address'],
56                                           self.config['hislip_interface']))
57        self.test_app.timeout = 200000
58        self.test_app.write_termination = '\n'
59        self.test_app.read_termination = '\n'
60        self.test_app.query_delay = VISA_QUERY_DELAY
61        self.last_loaded_scpi = None
62
63        inst_id = self.send_cmd('*IDN?', 1)
64        if 'Keysight' not in inst_id[0]:
65            self.log.error(
66                'Failed to connect to Keysight Test App: {}'.format(inst_id))
67        else:
68            self.log.info("Test App ID: {}".format(inst_id))
69
70        self.test_app_settings['lte_cell_count'] = self.get_cell_count('LTE')
71        self.test_app_settings['nr_cell_count'] = self.get_cell_count('NR5G')
72
73    def destroy(self):
74        self.test_app.close()
75
76    ### Programming Utilities
77    @staticmethod
78    def _format_cells(cells):
79        "Helper function to format list of cells."
80        if isinstance(cells, int):
81            return 'CELL{}'.format(cells)
82        elif isinstance(cells, str):
83            return cells
84        elif isinstance(cells, list):
85            cell_list = [
86                Keysight5GTestApp._format_cells(cell) for cell in cells
87            ]
88            cell_list = ','.join(cell_list)
89            return cell_list
90
91    @staticmethod
92    def _format_response(response):
93        "Helper function to format test app response."
94
95        def _format_response_entry(entry):
96            try:
97                formatted_entry = float(entry)
98            except:
99                formatted_entry = entry
100            return formatted_entry
101
102        if ',' not in response:
103            return _format_response_entry(response)
104        response = response.split(',')
105        formatted_response = [
106            _format_response_entry(entry) for entry in response
107        ]
108        return formatted_response
109
110    def send_cmd(self, command, read_response=0, check_errors=1):
111        "Helper function to write to or query test app."
112        if read_response:
113            try:
114                response = Keysight5GTestApp._format_response(
115                    self.test_app.query(command))
116                time.sleep(VISA_QUERY_DELAY)
117                if check_errors:
118                    error = self.test_app.query('SYSTem:ERRor?')
119                    time.sleep(VISA_QUERY_DELAY)
120                    if 'No error' not in error:
121                        self.log.warning("Command: {}. Error: {}".format(
122                            command, error))
123                return response
124            except:
125                raise RuntimeError('Lost connection to test app.')
126        else:
127            try:
128                self.test_app.write(command)
129                time.sleep(VISA_QUERY_DELAY)
130                if check_errors:
131                    error = self.test_app.query('SYSTem:ERRor?')
132                    if 'No error' not in error:
133                        self.log.warning("Command: {}. Error: {}".format(
134                            command, error))
135                self.send_cmd('*OPC?', 1, check_errors)
136                time.sleep(VISA_QUERY_DELAY)
137            except:
138                raise RuntimeError('Lost connection to test app.')
139            return None
140
141    def check_error(self):
142        error = self.test_app.query('SYSTem:ERRor?')
143        if 'No error' not in error:
144            self.log.warning("Error: {}".format(error))
145            return True
146        else:
147            return False
148
149    def import_scpi_file(self, file_name, check_last_loaded=0):
150        """Function to import SCPI file specified in file_name.
151
152        Args:
153            file_name: name of SCPI file to run
154            check_last_loaded: flag to check last loaded scpi and
155            only load if different.
156        """
157        if file_name == self.last_loaded_scpi and check_last_loaded:
158            self.log.info('Skipping SCPI import.')
159        self.send_cmd("SYSTem:SCPI:IMPort '{}'".format(file_name))
160        while int(self.send_cmd('SYSTem:SCPI:IMPort:STATus?', 1)):
161            self.send_cmd('*OPC?', 1)
162        self.log.info('Done with SCPI import')
163
164    ### Configure Cells
165    def assert_cell_off_decorator(func):
166        "Decorator function that ensures cells or off when configuring them"
167
168        def inner(self, *args, **kwargs):
169            if "nr" in func.__name__:
170                cell_type = 'NR5G'
171            else:
172                cell_type = kwargs.get('cell_type', args[0])
173            cell = kwargs.get('cell', args[1])
174            cell_state = self.get_cell_state(cell_type, cell)
175            if cell_state:
176                self.log.error('Cell must be off when calling {}'.format(
177                    func.__name__))
178            return (func(self, *args, **kwargs))
179
180        return inner
181
182    ### Configure Cells
183    def skip_config_if_none_decorator(func):
184        "Decorator function that skips the config function if any args are none"
185
186        def inner(self, *args, **kwargs):
187            none_arg = False
188            for arg in args:
189                if arg is None:
190                    none_arg = True
191            for key, value in kwargs.items():
192                if value is None:
193                    none_arg = True
194            if none_arg:
195                self.log.warning(
196                    'Skipping {}. Received incomplete arguments.'.format(
197                        func.__name__))
198                return
199            return (func(self, *args, **kwargs))
200
201        return inner
202
203    def assert_cell_off(self, cell_type, cell):
204        cell_state = self.get_cell_state(cell_type, cell)
205        if cell_state:
206            self.log.error('Cell must be off')
207
208    def select_cell(self, cell_type, cell):
209        """Function to select active cell.
210
211        Args:
212            cell_type: LTE or NR5G cell
213            cell: cell/carrier number
214        """
215        self.send_cmd('BSE:SELected:CELL {},{}'.format(
216            cell_type, Keysight5GTestApp._format_cells(cell)))
217
218    def select_display_tab(self, cell_type, cell, tab, subtab):
219        """Function to select display tab.
220
221        Args:
222            cell_type: LTE or NR5G cell
223            cell: cell/carrier number
224            tab: tab to display for the selected cell
225        """
226        supported_tabs = {
227            'PHY': [
228                'BWP', 'HARQ', 'PDSCH', 'PDCCH', 'PRACH', 'PUSCH', 'PUCCH',
229                'SRSC'
230            ],
231            'BTHR': ['SUMMARY', 'OTAGRAPH', 'ULOTA', 'DLOTA'],
232            'CSI': []
233        }
234        if (tab not in supported_tabs) or (subtab not in supported_tabs[tab]):
235            return
236        self.select_cell(cell_type, cell)
237        self.send_cmd('DISPlay:{} {},{}'.format(cell_type, tab, subtab))
238
239    def get_cell_count(self, cell_type):
240        """Function to get cell count
241
242        Args:
243            cell_type: LTE or NR5G cell
244        Returns:
245            cell_count: number of cells of cell_type supported.
246        """
247        cell_count = int(
248            self.send_cmd('BSE:CONFig:{}:CELL:COUNt?'.format(cell_type), 1))
249        return cell_count
250
251    def get_cell_state(self, cell_type, cell):
252        """Function to get cell on/off state.
253
254        Args:
255            cell_type: LTE or NR5G cell
256            cell: cell/carrier number
257        Returns:
258            cell_state: boolean. True if cell on
259        """
260        cell_state = int(
261            self.send_cmd(
262                'BSE:CONFig:{}:{}:ACTive:STATe?'.format(
263                    cell_type, Keysight5GTestApp._format_cells(cell)), 1))
264        return cell_state
265
266    def wait_for_cell_status(self,
267                             cell_type,
268                             cell,
269                             states,
270                             timeout,
271                             polling_interval=SHORT_SLEEP):
272        """Function to wait for a specific cell status
273
274        Args:
275            cell_type: LTE or NR5G cell
276            cell: cell/carrier number
277            states: list of acceptable states (ON, CONN, AGG, ACT, etc)
278            timeout: amount of time to wait for requested status
279        Returns:
280            True if one of the listed states is achieved
281            False if timed out waiting for acceptable state.
282        """
283        states = [states] if isinstance(states, str) else states
284        for i in range(int(timeout / polling_interval)):
285            current_state = self.send_cmd(
286                'BSE:STATus:{}:{}?'.format(
287                    cell_type, Keysight5GTestApp._format_cells(cell)), 1)
288            if current_state in states:
289                return True
290            time.sleep(polling_interval)
291        self.log.warning('Timeout waiting for {} {} {}'.format(
292            cell_type, Keysight5GTestApp._format_cells(cell), states))
293        return False
294
295    def set_cell_state(self, cell_type, cell, state):
296        """Function to set cell state
297
298        Args:
299            cell_type: LTE or NR5G cell
300            cell: cell/carrier number
301            state: requested state
302        """
303        self.send_cmd('BSE:CONFig:{}:{}:ACTive:STATe {}'.format(
304            cell_type, Keysight5GTestApp._format_cells(cell), state))
305
306    def turn_all_cells_off(self):
307        for cell in range(self.test_app_settings['lte_cell_count']):
308            self.set_cell_state('LTE', cell + 1, 0)
309        for cell in range(self.test_app_settings['nr_cell_count']):
310            self.set_cell_state('NR5G', cell + 1, 0)
311
312    def set_nr_cell_type(self, cell_type, cell, nr_cell_type):
313        """Function to set cell duplex mode
314
315        Args:
316            cell_type: LTE or NR5G cell
317            cell: cell/carrier number
318            nr_cell_type: SA or NSA
319        """
320        self.assert_cell_off(cell_type, cell)
321        self.send_cmd('BSE:CONFig:{}:{}:TYPE {}'.format(
322            cell_type, Keysight5GTestApp._format_cells(cell), nr_cell_type))
323
324    def set_cell_duplex_mode(self, cell_type, cell, duplex_mode):
325        """Function to set cell duplex mode
326
327        Args:
328            cell_type: LTE or NR5G cell
329            cell: cell/carrier number
330            duplex_mode: TDD or FDD
331        """
332        self.assert_cell_off(cell_type, cell)
333        self.send_cmd('BSE:CONFig:{}:{}:DUPLEX:MODe {}'.format(
334            cell_type, Keysight5GTestApp._format_cells(cell), duplex_mode))
335
336    def set_cell_band(self, cell_type, cell, band):
337        """Function to set cell band
338
339        Args:
340            cell_type: LTE or NR5G cell
341            cell: cell/carrier number
342            band: LTE or NR band (e.g. 1,3,N260, N77)
343        """
344        self.assert_cell_off(cell_type, cell)
345        self.send_cmd('BSE:CONFig:{}:{}:BAND {}'.format(
346            cell_type, Keysight5GTestApp._format_cells(cell), band))
347
348    def set_cell_channel(self, cell_type, cell, channel, arfcn=1):
349        """Function to set cell frequency/channel
350
351        Args:
352            cell_type: LTE or NR5G cell
353            cell: cell/carrier number
354            channel: requested channel (ARFCN) or frequency in MHz
355        """
356        self.assert_cell_off(cell_type, cell)
357        if cell_type == 'NR5G' and isinstance(
358                channel, str) and channel.lower() in ['low', 'mid', 'high']:
359            self.send_cmd('BSE:CONFig:{}:{}:TESTChanLoc {}'.format(
360                cell_type, Keysight5GTestApp._format_cells(cell),
361                channel.upper()))
362        elif arfcn == 1:
363            self.send_cmd('BSE:CONFig:{}:{}:DL:CHANnel {}'.format(
364                cell_type, Keysight5GTestApp._format_cells(cell), channel))
365        else:
366            self.send_cmd('BSE:CONFig:{}:{}:DL:FREQuency:MAIN {}'.format(
367                cell_type, Keysight5GTestApp._format_cells(cell),
368                channel * 1e6))
369
370    def toggle_contiguous_nr_channels(self, force_contiguous):
371        self.assert_cell_off('NR5G', 1)
372        self.log.warning(
373            'Forcing contiguous NR channels overrides channel config.')
374        self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 0')
375        if force_contiguous:
376            self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 1')
377
378    def configure_contiguous_nr_channels(self, cell, band, channel):
379        """Function to set cell frequency/channel
380
381        Args:
382            cell: cell/carrier number
383            band: band to set channel in (only required for preset)
384            channel_preset: frequency in MHz or preset in [low, mid, or high]
385        """
386        self.assert_cell_off('NR5G', cell)
387        self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 0')
388        if channel.lower() in ['low', 'mid', 'high']:
389            pcc_arfcn = cputils.PCC_PRESET_MAPPING[band][channel]
390            self.set_cell_channel('NR5G', cell, pcc_arfcn, 1)
391        else:
392            self.set_cell_channel('NR5G', cell, channel, 0)
393        self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 1')
394
395    def configure_noncontiguous_nr_channels(self, cells, band, channels):
396        """Function to set cell frequency/channel
397
398        Args:
399            cell: cell/carrier number
400            band: band number
401            channel: frequency in MHz
402        """
403        for cell in cells:
404            self.assert_cell_off('NR5G', cell)
405        self.send_cmd('BSE:CONFig:NR5G:PHY:OPTimize:CONTiguous:STATe 0')
406        for cell, channel in zip(cells, channels):
407            self.set_cell_channel('NR5G', cell, channel, arfcn=0)
408
409    def set_cell_bandwidth(self, cell_type, cell, bandwidth):
410        """Function to set cell bandwidth
411
412        Args:
413            cell_type: LTE or NR5G cell
414            cell: cell/carrier number
415            bandwidth: requested bandwidth
416        """
417        self.assert_cell_off(cell_type, cell)
418        self.send_cmd('BSE:CONFig:{}:{}:DL:BW {}'.format(
419            cell_type, Keysight5GTestApp._format_cells(cell), bandwidth))
420
421    def set_nr_subcarrier_spacing(self, cell, subcarrier_spacing):
422        """Function to set cell bandwidth
423
424        Args:
425            cell: cell/carrier number
426            subcarrier_spacing: requested SCS
427        """
428        self.assert_cell_off('NR5G', cell)
429        self.send_cmd('BSE:CONFig:NR5G:{}:SUBCarrier:SPACing:COMMon {}'.format(
430            Keysight5GTestApp._format_cells(cell), subcarrier_spacing))
431
432    def set_cell_mimo_config(self, cell_type, cell, link, mimo_config):
433        """Function to set cell mimo config.
434
435        Args:
436            cell_type: LTE or NR5G cell
437            cell: cell/carrier number
438            link: uplink or downlink
439            mimo_config: requested mimo configuration (refer to SCPI
440                         documentation for allowed range of values)
441        """
442        self.assert_cell_off(cell_type, cell)
443        if cell_type == 'NR5G':
444            self.send_cmd('BSE:CONFig:{}:{}:{}:MIMO:CONFig {}'.format(
445                cell_type, Keysight5GTestApp._format_cells(cell), link,
446                mimo_config))
447        else:
448            self.send_cmd('BSE:CONFig:{}:{}:PHY:DL:ANTenna:CONFig {}'.format(
449                cell_type, Keysight5GTestApp._format_cells(cell), mimo_config))
450
451    def set_lte_cell_transmission_mode(self, cell, transmission_mode):
452        """Function to set LTE cell transmission mode.
453
454        Args:
455            cell: cell/carrier number
456            transmission_mode: one of TM1, TM2, TM3, TM4 ...
457        """
458
459        self.assert_cell_off('LTE', cell)
460        self.send_cmd('BSE:CONFig:LTE:{}:RRC:TMODe {}'.format(
461            Keysight5GTestApp._format_cells(cell), transmission_mode))
462
463    @skip_config_if_none_decorator
464    def set_lte_cell_num_layers(self, cell, num_layers):
465        """Function to set LTE cell number of layers."""
466
467        self.assert_cell_off('LTE', cell)
468        self.send_cmd('BSE:CONFig:LTE:{}:PHY:DL:NUMLayers {}'.format(
469            Keysight5GTestApp._format_cells(cell), num_layers))
470
471    @skip_config_if_none_decorator
472    def set_lte_cell_num_codewords(self, cell, num_codewords):
473        """Function to set LTE number of codewords."""
474
475        self.assert_cell_off('LTE', cell)
476        self.send_cmd('BSE:CONFig:LTE:{}:PHY:DL:NUMCodewords {}'.format(
477            Keysight5GTestApp._format_cells(cell), num_codewords))
478
479    @skip_config_if_none_decorator
480    def set_lte_cell_dl_subframe_allocation(self,
481                                            cell,
482                                            dl_subframe_allocation=[1] * 10):
483        """Function to set LTE downlink subrframe allocation.
484
485        Args:
486            cell: cell/carrier number
487            dl_subframe_allocation: string or bool list indicating allocation
488            (1 enabled, 0 disabled)
489        """
490        if isinstance(dl_subframe_allocation, list):
491            dl_subframe_allocation = str(dl_subframe_allocation)[1:-1].replace(
492                '\'', '')
493        self.assert_cell_off('LTE', cell)
494        self.send_cmd(
495            'BSE:CONFig:LTE:{}:PHY:DL:SFRame:ALLocation:ALL {}'.format(
496                Keysight5GTestApp._format_cells(cell), dl_subframe_allocation))
497
498    def set_cell_dl_power(self, cell_type, cell, power, full_bw):
499        """Function to set cell power
500
501        Args:
502            cell_type: LTE or NR5G cell
503            cell: cell/carrier number
504            power: requested power
505            full_bw: boolean controlling if requested power is per channel
506                     or subcarrier
507        """
508        if full_bw:
509            self.send_cmd('BSE:CONFig:{}:{}:DL:POWer:CHANnel {}'.format(
510                cell_type, Keysight5GTestApp._format_cells(cell), power))
511        else:
512            self.send_cmd('BSE:CONFig:{}:{}:DL:POWer:EPRE {}'.format(
513                cell_type, Keysight5GTestApp._format_cells(cell), power))
514        time.sleep(VERY_SHORT_SLEEP)
515        self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
516
517    def set_cell_ul_power_control(self, cell_type, cell, mode, target_power=0):
518        """Function configure UL power control
519
520        Args:
521            cell_type: LTE or NR5G cell
522            cell: cell/carrier number
523            mode: UL power control mode. One of [TARget | MANual | UP | DOWN | DISabled]
524            target_power: target power for PUSCH
525        """
526        self.send_cmd('BSE:CONFig:{}:{}:UL:PUSCh:CLPControl:MODE {}'.format(
527            cell_type, Keysight5GTestApp._format_cells(cell), mode))
528        if cell_type == 'NR5G' and mode == 'TARget':
529            self.send_cmd(
530                'BSE:CONFig:{}:{}:UL:PUSCh:CLPControl:TARGet:POWer {}'.format(
531                    cell_type, Keysight5GTestApp._format_cells(cell),
532                    target_power))
533        elif cell_type == 'LTE' and mode == 'TARget':
534            self.send_cmd(
535                'BSE:CONFig:{}:{}:UL:CLPControl:TARGet:POWer:PUSCH {}'.format(
536                    cell_type, Keysight5GTestApp._format_cells(cell),
537                    target_power))
538        self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
539
540    def set_cell_input_power(self, cell_type, cell, power):
541        """Function to set cell input power
542
543        Args:
544            cell_type: LTE or NR5G cell
545            cell: cell/carrier number
546            power: expected input power
547        """
548        if power == "AUTO" and cell_type == "LTE":
549            self.send_cmd('BSE:CONFig:{}:{}:CONTrol:POWer:AUTO ON'.format(
550                cell_type, Keysight5GTestApp._format_cells(cell)))
551        elif cell_type == "LTE":
552            self.send_cmd('BSE:CONFig:{}:{}:CONTrol:POWer:AUTO OFF'.format(
553                cell_type, Keysight5GTestApp._format_cells(cell)))
554            self.send_cmd('BSE:CONFig:{}:{}:MANual:POWer {}'.format(
555                cell_type, Keysight5GTestApp._format_cells(cell), power))
556        if power == "AUTO" and cell_type == "NR5G":
557            self.send_cmd('BSE:CONFig:{}:UL:EIP:AUTO ON'.format(cell_type))
558        elif cell_type == "NR5G":
559            self.send_cmd('BSE:CONFig:{}:UL:EIP:AUTO OFF'.format(cell_type))
560            self.send_cmd('BSE:CONFig:{}:{}:MANual:POWer {}'.format(
561                cell_type, Keysight5GTestApp._format_cells(cell), power))
562        self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
563
564    def set_cell_duplex_mode(self, cell_type, cell, duplex_mode):
565        """Function to set cell power
566
567        Args:
568            cell_type: LTE or NR5G cell
569            cell: cell/carrier number
570            duplex mode: TDD or FDD
571        """
572        self.assert_cell_off(cell_type, cell)
573        self.send_cmd('BSE:CONFig:{}:{}:DUPLEX:MODe {}'.format(
574            cell_type, Keysight5GTestApp._format_cells(cell), duplex_mode))
575
576    def set_dl_carriers(self, cells):
577        """Function to set aggregated DL NR5G carriers
578
579        Args:
580            cells: list of DL cells/carriers to aggregate with LTE (e.g. [1,2])
581        """
582        self.send_cmd('BSE:CONFig:NR5G:CELL1:CAGGregation:NRCC:DL {}'.format(
583            Keysight5GTestApp._format_cells(cells)))
584
585    def set_ul_carriers(self, cells):
586        """Function to set aggregated UL NR5G carriers
587
588        Args:
589            cells: list of DL cells/carriers to aggregate with LTE (e.g. [1,2])
590        """
591        self.send_cmd('BSE:CONFig:NR5G:CELL1:CAGGregation:NRCC:UL {}'.format(
592            Keysight5GTestApp._format_cells(cells)))
593
594    def set_nr_cell_schedule_scenario(self, cell, scenario):
595        """Function to set NR schedule to one of predefince quick configs.
596
597        Args:
598            cell: cell number to address. schedule will apply to all cells
599            scenario: one of the predefined test app schedlue quick configs
600                      (e.g. FULL_TPUT, BASIC).
601        """
602        self.assert_cell_off('NR5G', cell)
603        self.send_cmd(
604            'BSE:CONFig:NR5G:{}:SCHeduling:QCONFig:SCENario {}'.format(
605                Keysight5GTestApp._format_cells(cell), scenario))
606        self.send_cmd('BSE:CONFig:NR5G:SCHeduling:QCONFig:APPLy:ALL')
607
608    def set_nr_schedule_slot_ratio(self, cell, slot_ratio):
609        """Function to set NR schedule to one of predefince quick configs.
610
611        Args:
612            cell: cell number to address. schedule will apply to all cells
613            slot_ratio: downlink slot ratio
614        """
615        self.assert_cell_off('NR5G', cell)
616        self.send_cmd('BSE:CONFig:NR5G:{}:SCHeduling:QCONFig:RATIo {}'.format(
617            Keysight5GTestApp._format_cells(cell), slot_ratio))
618        self.send_cmd('BSE:CONFig:NR5G:SCHeduling:QCONFig:APPLy:ALL')
619
620    def set_nr_schedule_tdd_pattern(self, cell, tdd_pattern):
621        """Function to set NR schedule to one of predefince quick configs.
622
623        Args:
624            cell: cell number to address. schedule will apply to all cells
625            tdd_pattern: 0 for disabled, 1/enabled, or current
626        """
627        tdd_pattern_mapping = {
628            0: 'DISabled',
629            1: 'ENABled',
630            'current': 'CURRent'
631        }
632        self.assert_cell_off('NR5G', cell)
633        self.send_cmd(
634            'BSE:CONFig:NR5G:{}:SCHeduling:QCONFig:TDD:PATTern {}'.format(
635                Keysight5GTestApp._format_cells(cell),
636                tdd_pattern_mapping[tdd_pattern]))
637        self.send_cmd('BSE:CONFig:NR5G:SCHeduling:QCONFig:APPLy:ALL')
638
639    def set_nr_cell_mcs(self, cell, dl_mcs, ul_mcs):
640        """Function to set NR cell DL & UL MCS
641
642        Args:
643            cell: cell number to address. MCS will apply to all cells
644            dl_mcs: mcs index to use on DL
645            ul_mcs: mcs index to use on UL
646        """
647        self.assert_cell_off('NR5G', cell)
648        frame_config_count = 5
649        slot_config_count = 8
650        if isinstance(dl_mcs, dict):
651            self.configure_nr_link_adaptation(cell, link_config=dl_mcs)
652        else:
653            for frame, slot in itertools.product(range(frame_config_count),
654                                                 range(slot_config_count)):
655                self.send_cmd(
656                    'BSE:CONFig:NR5G:{}:SCHeduling:BWP0:FC{}:SC{}:DL:RRESource:APOLicy FIXed'
657                    .format(Keysight5GTestApp._format_cells(cell), frame,
658                            slot))
659            self.send_cmd(
660                'BSE:CONFig:NR5G:SCHeduling:SETParameter "CELLALL:BWPALL:FCALL:SCALL", "DL:IMCS", "{}"'
661                .format(dl_mcs))
662        self.send_cmd(
663            'BSE:CONFig:NR5G:SCHeduling:SETParameter "CELLALL:BWPALL:FCALL:SCALL", "UL:IMCS", "{}"'
664            .format(ul_mcs))
665
666    def configure_nr_link_adaptation(self, cell, link_config):
667        frame_config_count = 5
668        slot_config_count = 8
669        for frame, slot in itertools.product(range(frame_config_count),
670                                             range(slot_config_count)):
671            self.send_cmd(
672                'BSE:CONFig:NR5G:{}:SCHeduling:BWP0:FC{}:SC{}:DL:RRESource:APOLicy {}'
673                .format(Keysight5GTestApp._format_cells(cell), frame, slot,
674                        link_config['link_policy']))
675            self.send_cmd(
676                'BSE:CONFig:NR5G:{}:SCHeduling:BWP0:FC{}:SC{}:DL:IMCS {}'.
677                format(Keysight5GTestApp._format_cells(cell), frame, slot,
678                       link_config['initial_mcs']))
679            self.send_cmd(
680                'BSE:CONFig:NR5G:{}:SCHeduling:BWP0:FC{}:SC{}:DL:MAXimum:IMCS {}'
681                .format(Keysight5GTestApp._format_cells(cell), frame, slot,
682                        link_config['maximum_mcs']))
683        self.send_cmd(
684            'BSE:CONFig:NR5G:{}:MAC:LADaptation:NTX:BEValuation {}'.format(
685                Keysight5GTestApp._format_cells(cell),
686                link_config.get('adaptation_interval', 10000)))
687        self.send_cmd(
688            'BSE:CONFig:NR5G:{}:MAC:LADaptation:TARGet:NACK:COUNt {}'.format(
689                Keysight5GTestApp._format_cells(cell),
690                link_config.get('target_nack_count', 1000)))
691        self.send_cmd(
692            'BSE:CONFig:NR5G:{}:MAC:LADaptation:TARGet:NACK:MARGin {}'.format(
693                Keysight5GTestApp._format_cells(cell),
694                link_config.get('target_nack_margin', 100)))
695        self.send_cmd(
696            'BSE:CONFig:NR5G:{}:MAC:DL:LADaptation:MCS:INCRement {}'.format(
697                Keysight5GTestApp._format_cells(cell),
698                link_config.get('mcs_step', 1)))
699
700    def set_lte_cell_mcs(
701        self,
702        cell,
703        dl_mcs_table,
704        dl_mcs,
705        ul_mcs_table,
706        ul_mcs,
707    ):
708        """Function to set NR cell DL & UL MCS
709
710        Args:
711            cell: cell number to address. MCS will apply to all cells
712            dl_mcs: mcs index to use on DL
713            ul_mcs: mcs index to use on UL
714        """
715        if dl_mcs_table == 'QAM256':
716            dl_mcs_table_formatted = 'ASUBframe'
717        elif dl_mcs_table == 'QAM1024':
718            dl_mcs_table_formatted = 'ASUB1024'
719        elif dl_mcs_table == 'QAM64':
720            dl_mcs_table_formatted = 'DISabled'
721        self.assert_cell_off('LTE', cell)
722        self.send_cmd(
723            'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL", "DL:MCS:TABle", "{}"'
724            .format(dl_mcs_table_formatted))
725        self.configure_lte_periodic_csi_reporting(cell, 1)
726        if dl_mcs == 'WCQI':
727            self.send_cmd('BSE:CONFig:LTE:{}:PHY:DL:IMCS:MODE WCQI'.format(
728                Keysight5GTestApp._format_cells(cell)))
729        else:
730            self.send_cmd('BSE:CONFig:LTE:{}:PHY:DL:IMCS:MODE EXPLicit'.format(
731                Keysight5GTestApp._format_cells(cell)))
732            self.send_cmd(
733                'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL:SFALL:CWALL", "DL:IMCS", "{}"'
734                .format(dl_mcs))
735        self.send_cmd(
736            'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL", "UL:MCS:TABle", "{}"'
737            .format(ul_mcs_table))
738        self.send_cmd(
739            'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL:SFALL", "UL:IMCS", "{}"'
740            .format(ul_mcs))
741
742    def configure_lte_periodic_csi_reporting(self, cell, enable):
743        """Function to enable/disable LTE CSI reporting."""
744
745        self.send_cmd('BSE:CONFig:LTE:{}:PHY:CSI:PERiodic:STATe {}'.format(
746            Keysight5GTestApp._format_cells(cell), enable))
747
748    def set_lte_control_region_size(self, cell, num_symbols):
749        self.assert_cell_off('LTE', cell)
750        self.send_cmd('BSE:CONFig:LTE:{}:PHY:PCFich:CFI {}'.format(
751            Keysight5GTestApp._format_cells(cell), num_symbols))
752
753    def set_lte_ul_mac_padding(self, mac_padding):
754        self.assert_cell_off('LTE', 'CELL1')
755        padding_str = 'TRUE' if mac_padding else 'FALSE'
756        self.send_cmd(
757            'BSE:CONFig:LTE:SCHeduling:SETParameter "CELLALL", "UL:MAC:PADDING", "{}"'
758            .format(padding_str))
759
760    def set_nr_ul_dft_precoding(self, cell, precoding):
761        """Function to configure DFT-precoding on uplink.
762
763        Args:
764            cell: cell number to address. MCS will apply to all cells
765            precoding: 0/1 to disable/enable precoding
766        """
767        self.assert_cell_off('NR5G', cell)
768        precoding_str = "ENABled" if precoding else "DISabled"
769        self.send_cmd(
770            'BSE:CONFig:NR5G:{}:SCHeduling:QCONFig:UL:TRANsform:PRECoding {}'.
771            format(Keysight5GTestApp._format_cells(cell), precoding_str))
772        precoding_str = "True" if precoding else "False"
773        self.send_cmd(
774            'BSE:CONFig:NR5G:SCHeduling:SETParameter "CELLALL:BWPALL", "UL:TPEnabled", "{}"'
775            .format(precoding_str))
776
777    def configure_ul_clpc(self, channel, mode, target):
778        """Function to configure UL power control on all cells/carriers
779
780        Args:
781            channel: physical channel must be PUSCh or PUCCh
782            mode: mode supported by test app (all up/down bits, target, etc)
783            target: target power if mode is set to target
784        """
785        self.send_cmd('BSE:CONFig:NR5G:UL:{}:CLPControl:MODE:ALL {}'.format(
786            channel, mode))
787        if "tar" in mode.lower():
788            self.send_cmd(
789                'BSE:CONFig:NR5G:UL:{}:CLPControl:TARGet:POWer:ALL {}'.format(
790                    channel, target))
791
792    def configure_channel_emulator(self, cell_type, cell, fading_model):
793        if cell_type == 'LTE':
794            self.send_cmd('BSE:CONFig:{}:{}:CMODel {}'.format(
795                cell_type, Keysight5GTestApp._format_cells(cell),
796                fading_model['channel_model']))
797            self.send_cmd('BSE:CONFig:{}:{}:CMATrix {}'.format(
798                cell_type, Keysight5GTestApp._format_cells(cell),
799                fading_model['correlation_matrix']))
800            self.send_cmd('BSE:CONFig:{}:{}:MDSHift {}'.format(
801                cell_type, Keysight5GTestApp._format_cells(cell),
802                fading_model['max_doppler']))
803        elif cell_type == 'NR5G':
804            #TODO: check that this is FR1
805            self.send_cmd('BSE:CONFig:{}:{}:FRANge1:CMODel {}'.format(
806                cell_type, Keysight5GTestApp._format_cells(cell),
807                fading_model['channel_model']))
808            self.send_cmd('BSE:CONFig:{}:{}:FRANge1:CMATrix {}'.format(
809                cell_type, Keysight5GTestApp._format_cells(cell),
810                fading_model['correlation_matrix']))
811            self.send_cmd('BSE:CONFig:{}:{}:FRANge1:MDSHift {}'.format(
812                cell_type, Keysight5GTestApp._format_cells(cell),
813                fading_model['max_doppler']))
814
815    def set_channel_emulator_state(self, state):
816        self.send_cmd('BSE:CONFig:FADing:ENABle {}'.format(int(state)))
817
818    def apply_lte_carrier_agg(self, cells):
819        """Function to start LTE carrier aggregation on already configured cells"""
820        if self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
821            self.send_cmd(
822                'BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:SCC {}'.format(
823                    Keysight5GTestApp._format_cells(cells)))
824            self.send_cmd(
825                'BSE:CONFig:LTE:CELL1:CAGGregation:ACTivate:SCC {}'.format(
826                    Keysight5GTestApp._format_cells(cells)))
827
828    def apply_carrier_agg(self):
829        """Function to start carrier aggregation on already configured cells"""
830        if not self.wait_for_cell_status('LTE', 'CELL1', 'CONN', 60):
831            raise RuntimeError('LTE must be connected to start aggregation.')
832        # Continue if LTE connected
833        self.send_cmd('BSE:CONFig:LTE:CELL1:CAGGregation:AGGRegate:NRCC:APPly',
834                      0, 0)
835        time.sleep(MEDIUM_SLEEP)
836        error = self.check_error()
837        if error:
838            acts_asserts.fail('Failed to apply NR carrier aggregation.')
839
840    def get_ip_throughput(self, cell_type):
841        """Function to query IP layer throughput on LTE or NR
842
843        Args:
844            cell_type: LTE or NR5G
845        Returns:
846            dict containing DL and UL IP-layer throughput
847        """
848        #Tester reply format
849        #{ report-count, total-bytes, current transfer-rate, average transfer-rate, peak transfer-rate }
850        dl_tput = self.send_cmd(
851            'BSE:MEASure:{}:BTHRoughput:DL:THRoughput:IP?'.format(cell_type),
852            1)
853        ul_tput = self.send_cmd(
854            'BSE:MEASure:{}:BTHRoughput:UL:THRoughput:IP?'.format(cell_type),
855            1)
856        return {'dl_tput': dl_tput, 'ul_tput': ul_tput}
857
858    def _get_throughput(self, cell_type, link, cell):
859        """Helper function to get PHY layer throughput on single cell"""
860        if cell_type == 'LTE':
861            tput_response = self.send_cmd(
862                'BSE:MEASure:LTE:BTHRoughput:{}:THRoughput:OTA:{}?'.format(
863                    link, Keysight5GTestApp._format_cells(cell)), 1)
864        elif cell_type == 'NR5G':
865            # Tester reply format
866            #progress-count, ack-count, ack-ratio, nack-count, nack-ratio,  statdtx-count,  statdtx-ratio,  pdschBlerCount,  pdschBlerRatio,  pdschTputRatio.
867            tput_response = self.send_cmd(
868                'BSE:MEASure:NR5G:BTHRoughput:{}:THRoughput:OTA:{}?'.format(
869                    link, Keysight5GTestApp._format_cells(cell)), 1)
870        tput_result = {
871            'frame_count': tput_response[0] / 1e6,
872            'current_tput': tput_response[1] / 1e6,
873            'min_tput': tput_response[2] / 1e6,
874            'max_tput': tput_response[3] / 1e6,
875            'average_tput': tput_response[4] / 1e6,
876            'theoretical_tput': tput_response[5] / 1e6,
877        }
878        return tput_result
879
880    def get_throughput(self, cell_type, dl_cells, ul_cells):
881        """Function to get PHY layer throughput on on or more cells
882
883        This function returns the throughput data on the requested cells
884        during the last BLER test run, i.e., throughpt data must be fetch at
885        the end/after a BLE test run on the Keysight Test App.
886
887        Args:
888            cell_type: LTE or NR5G
889            cells: list of cells to query for throughput data
890        Returns:
891            tput_result: dict containing all throughput statistics in Mbps
892        """
893        if not isinstance(dl_cells, list):
894            dl_cells = [dl_cells]
895        if not isinstance(ul_cells, list):
896            ul_cells = [ul_cells]
897        tput_result = collections.OrderedDict()
898        for cell in dl_cells:
899            tput_result.setdefault(cell, {})
900            tput_result[cell]['DL'] = self._get_throughput(
901                cell_type, 'DL', cell)
902            frame_count = tput_result[cell]['DL']['frame_count']
903        for cell in ul_cells:
904            tput_result.setdefault(cell, {})
905            tput_result[cell]['UL'] = self._get_throughput(
906                cell_type, 'UL', cell)
907        agg_tput = {
908            'DL': {
909                'frame_count': frame_count,
910                'current_tput': 0,
911                'min_tput': 0,
912                'max_tput': 0,
913                'average_tput': 0,
914                'theoretical_tput': 0
915            },
916            'UL': {
917                'frame_count': frame_count,
918                'current_tput': 0,
919                'min_tput': 0,
920                'max_tput': 0,
921                'average_tput': 0,
922                'theoretical_tput': 0
923            }
924        }
925        for cell, cell_tput in tput_result.items():
926            for link, link_tput in cell_tput.items():
927                for key, value in link_tput.items():
928                    if 'tput' in key:
929                        agg_tput[link][key] = agg_tput[link][key] + value
930        tput_result['total'] = agg_tput
931        return tput_result
932
933    def _clear_bler_measurement(self, cell_type):
934        """Helper function to clear BLER results."""
935        if cell_type == 'LTE':
936            self.send_cmd('BSE:MEASure:LTE:CELL1:BTHRoughput:CLEar')
937        elif cell_type == 'NR5G':
938            self.send_cmd('BSE:MEASure:NR5G:BTHRoughput:CLEar')
939
940    def _configure_bler_measurement(self, cell_type, continuous, length):
941        """Helper function to configure BLER results."""
942        if continuous:
943            if cell_type == 'LTE':
944                self.send_cmd('BSE:MEASure:LTE:CELL1:BTHRoughput:CONTinuous 1')
945            elif cell_type == 'NR5G':
946                self.send_cmd('BSE:MEASure:NR5G:BTHRoughput:CONTinuous 1')
947        elif length > 1:
948            if cell_type == 'LTE':
949                self.send_cmd(
950                    'BSE:MEASure:LTE:CELL1:BTHRoughput:LENGth {}'.format(
951                        length))
952                self.send_cmd('BSE:MEASure:LTE:CELL1:BTHRoughput:CONTinuous 0')
953            elif cell_type == 'NR5G':
954                self.send_cmd(
955                    'BSE:MEASure:NR5G:BTHRoughput:LENGth {}'.format(length))
956                self.send_cmd('BSE:MEASure:NR5G:BTHRoughput:CONTinuous 0')
957
958    def _set_bler_measurement_state(self, cell_type, state):
959        """Helper function to start or stop BLER measurement."""
960        if cell_type == 'LTE':
961            self.send_cmd(
962                'BSE:MEASure:LTE:CELL1:BTHRoughput:STATe {}'.format(state))
963        elif cell_type == 'NR5G':
964            self.send_cmd(
965                'BSE:MEASure:NR5G:BTHRoughput:STATe {}'.format(state))
966
967    def start_bler_measurement(self, cell_type, cells, length):
968        """Function to kick off a BLER measurement
969
970        Args:
971            cell_type: LTE or NR5G
972            length: integer length of BLER measurements in subframes
973        """
974        self._clear_bler_measurement(cell_type)
975        self._set_bler_measurement_state(cell_type, 0)
976        self._configure_bler_measurement(cell_type, 0, length)
977        self._set_bler_measurement_state(cell_type, 1)
978        time.sleep(0.1)
979        #bler_check = self.get_bler_result(cell_type, cells, length, 0)
980        #if bler_check['total']['DL']['frame_count'] == 0:
981        #    self.log.warning('BLER measurement did not start. Retrying')
982        #    self.start_bler_measurement(cell_type, cells, length)
983
984    def _get_bler(self, cell_type, link, cell):
985        """Helper function to get single-cell BLER measurement results."""
986        if cell_type == 'LTE':
987            bler_response = self.send_cmd(
988                'BSE:MEASure:LTE:BTHRoughput:{}:BLER:{}?'.format(
989                    link, Keysight5GTestApp._format_cells(cell)), 1)
990            bler_items = [
991                'frame_count', 'ack_count', 'ack_ratio', 'nack_count',
992                'nack_ratio', 'statDtx_count', 'statDtx_ratio',
993                'nackStatDtx_count', 'nackStatDtx_ratio', 'pdschBler_count',
994                'pdschBler_ratio', 'any_count', 'any_ratio'
995            ]
996            bler_result = {
997                bler_items[x]: bler_response[x]
998                for x in range(len(bler_response))
999            }
1000        elif cell_type == 'NR5G':
1001            bler_response = self.send_cmd(
1002                'BSE:MEASure:NR5G:BTHRoughput:{}:BLER:{}?'.format(
1003                    link, Keysight5GTestApp._format_cells(cell)), 1)
1004            bler_items = [
1005                'frame_count', 'ack_count', 'ack_ratio', 'nack_count',
1006                'nack_ratio', 'statDtx_count', 'statDtx_ratio',
1007                'pdschBler_count', 'pdschBler_ratio', 'pdschTputRatio'
1008            ]
1009
1010            bler_result = {
1011                bler_items[x]: bler_response[x]
1012                for x in range(len(bler_response))
1013            }
1014        return bler_result
1015
1016    def get_bler_result(self,
1017                        cell_type,
1018                        dl_cells,
1019                        ul_cells,
1020                        length,
1021                        wait_for_length=1,
1022                        polling_interval=SHORT_SLEEP):
1023        """Function to get BLER results.
1024
1025        This function gets the BLER measurements results on one or more
1026        requested cells. The function can either return BLER statistics
1027        immediately or wait until a certain number of subframes have been
1028        counted (e.g. if the BLER measurement is done)
1029
1030        Args:
1031            cell_type: LTE or NR5G
1032            cells: list of cells for which to get BLER
1033            length: number of subframes to wait for (typically set to the
1034                    configured length of the BLER measurements)
1035            wait_for_length: boolean to block/wait till length subframes have
1036            been counted.
1037        Returns:
1038            bler_result: dict containing per-cell and aggregate BLER results
1039        """
1040        if not isinstance(dl_cells, list):
1041            dl_cells = [dl_cells]
1042        if not isinstance(ul_cells, list):
1043            ul_cells = [ul_cells]
1044        while wait_for_length:
1045            dl_bler = self._get_bler(cell_type, 'DL', dl_cells[0])
1046            if dl_bler['frame_count'] < length:
1047                time.sleep(polling_interval)
1048            else:
1049                break
1050
1051        bler_result = collections.OrderedDict()
1052        for cell in dl_cells:
1053            bler_result.setdefault(cell, {})
1054            bler_result[cell]['DL'] = self._get_bler(cell_type, 'DL', cell)
1055        for cell in ul_cells:
1056            bler_result.setdefault(cell, {})
1057            bler_result[cell]['UL'] = self._get_bler(cell_type, 'UL', cell)
1058        agg_bler = {
1059            'DL': {
1060                'frame_count': length,
1061                'ack_count': 0,
1062                'ack_ratio': 0,
1063                'nack_count': 0,
1064                'nack_ratio': 0
1065            },
1066            'UL': {
1067                'frame_count': length,
1068                'ack_count': 0,
1069                'ack_ratio': 0,
1070                'nack_count': 0,
1071                'nack_ratio': 0
1072            }
1073        }
1074        for cell, cell_bler in bler_result.items():
1075            for link, link_bler in cell_bler.items():
1076                for key, value in link_bler.items():
1077                    if 'ack_count' in key:
1078                        agg_bler[link][key] = agg_bler[link][key] + value
1079        dl_ack_nack = agg_bler['DL']['ack_count'] + agg_bler['DL']['nack_count']
1080        ul_ack_nack = agg_bler['UL']['ack_count'] + agg_bler['UL']['nack_count']
1081        try:
1082            agg_bler['DL'][
1083                'ack_ratio'] = agg_bler['DL']['ack_count'] / dl_ack_nack
1084            agg_bler['DL'][
1085                'nack_ratio'] = agg_bler['DL']['nack_count'] / dl_ack_nack
1086            agg_bler['UL'][
1087                'ack_ratio'] = agg_bler['UL']['ack_count'] / ul_ack_nack
1088            agg_bler['UL'][
1089                'nack_ratio'] = agg_bler['UL']['nack_count'] / ul_ack_nack
1090        except:
1091            self.log.debug(bler_result)
1092            agg_bler['DL']['ack_ratio'] = 0
1093            agg_bler['DL']['nack_ratio'] = 1
1094            agg_bler['UL']['ack_ratio'] = 0
1095            agg_bler['UL']['nack_ratio'] = 1
1096        bler_result['total'] = agg_bler
1097        return bler_result
1098
1099    def measure_bler(self, cell_type, cells, length):
1100        """Function to start and wait for BLER results.
1101
1102        This function starts a BLER test on a number of cells and waits for the
1103        test to complete before returning the BLER measurements.
1104
1105        Args:
1106            cell_type: LTE or NR5G
1107            cells: list of cells for which to get BLER
1108            length: number of subframes to wait for (typically set to the
1109                    configured length of the BLER measurements)
1110        Returns:
1111            bler_result: dict containing per-cell and aggregate BLER results
1112        """
1113        self.start_bler_measurement(cell_type, cells, length)
1114        time.sleep(length * SUBFRAME_DURATION)
1115        bler_result = self.get_bler_result(cell_type, cells, length, 1)
1116        return bler_result
1117
1118    def start_nr_rsrp_measurement(self, cells, length):
1119        """Function to start 5G NR RSRP measurement.
1120
1121        Args:
1122            cells: list of NR cells to get RSRP on
1123            length: length of RSRP measurement in milliseconds
1124        Returns:
1125            rsrp_result: dict containing per-cell and aggregate BLER results
1126        """
1127        for cell in cells:
1128            self.send_cmd('BSE:MEASure:NR5G:{}:L1:RSRPower:STOP'.format(
1129                Keysight5GTestApp._format_cells(cell)))
1130        for cell in cells:
1131            self.send_cmd('BSE:MEASure:NR5G:{}:L1:RSRPower:LENGth {}'.format(
1132                Keysight5GTestApp._format_cells(cell), length))
1133        for cell in cells:
1134            self.send_cmd('BSE:MEASure:NR5G:{}:L1:RSRPower:STARt'.format(
1135                Keysight5GTestApp._format_cells(cell)))
1136
1137    def get_nr_rsrp_measurement_state(self, cells):
1138        for cell in cells:
1139            self.log.info(
1140                self.send_cmd(
1141                    'BSE:MEASure:NR5G:{}:L1:RSRPower:STATe?'.format(
1142                        Keysight5GTestApp._format_cells(cell)), 1))
1143
1144    def get_nr_rsrp_measurement_results(self, cells):
1145        for cell in cells:
1146            self.log.info(
1147                self.send_cmd(
1148                    'BSE:MEASure:NR5G:{}:L1:RSRPower:REPorts:JSON?'.format(
1149                        Keysight5GTestApp._format_cells(cell)), 1))
1150
1151    def release_rrc_connection(self, cell_type, cell):
1152        if cell_type == 'LTE':
1153            self.send_cmd('BSE:FUNCtion:LTE:{}:RELease:SEND'.format(
1154                Keysight5GTestApp._format_cells(cell)))
1155        elif cell_type == 'NR5G':
1156            self.send_cmd(
1157                'BSE:CONFig:NR5G:{}:RCONtrol:RRC:STARt RRELease'.format(
1158                    Keysight5GTestApp._format_cells(cell)))
1159
1160    def send_rrc_paging(self, cell_type, cell):
1161        if cell_type == 'LTE':
1162            self.send_cmd('BSE:FUNCtion:LTE:{}:PAGing:PAGE'.format(
1163                Keysight5GTestApp._format_cells(cell)))
1164        elif cell_type == 'NR5G':
1165            self.send_cmd(
1166                'BSE:CONFig:NR5G:{}:RCONtrol:RRC:STARt PAGing'.format(
1167                    Keysight5GTestApp._format_cells(cell)))
1168
1169    def enable_rach(self, cell_type, cell, enabled):
1170        self.send_cmd('BSE:CONFig:{}:{}:MAC:RACH:IGNore {}'.format(
1171            cell_type, Keysight5GTestApp._format_cells(cell), int(enabled)))
1172        self.send_cmd('BSE:CONFig:{}:APPLY'.format(cell_type))
1173
1174    def enable_preamble_report(self, cell_type, enable):
1175        self.send_cmd('BSE:CONFig:{}:PReamble:REPort:STATe {}'.format(
1176            cell_type, int(enable)))
1177
1178    def fetch_preamble_report(self, cell_type, cell, num_reports=10):
1179        report = self.send_cmd(
1180            'BSE:CONFig:{}:{}:PReamble:REPort:FETCh:JSON? {}'.format(
1181                cell_type, Keysight5GTestApp._format_cells(cell), num_reports),
1182            read_response=1)
1183        self.send_cmd('BSE:CONFig:{}:PReamble:REPort:CLEAr'.format(cell_type))
1184        if 'No Data' in report:
1185            report = None
1186        return report
1187