Amarisoft

Remote API - Constellation

 

The purpose of this tutorial is to show to how to collect PHY constellation data using RemoteAPI and how to post process it. The end goal is similar to what you already have in WebGUI (see NOTE) . But the advantage in this method is that you can extract the data for resource element of received channels and signals (e.g, pusch, srs) from a binary file only. You don't need to carry both binary and text log. In addition, this way is much easier to extract the data from binary comparing to the existing method mentioned here WebGUI (see NOTE)

NOTE : This method is supported from the release of the week of Feb 5th 2024.

NOTE :  For now, this is applicable to some of received physical channel only (e.g, pusch, npusch, pusch-dmrs, srs on Callbox) and is not applicable to transmitted channel (e.g, pdsch, pdsch dmrs, prs etc).

NOTE :  The nature of the data collected in this way is different from the data explained in these tutorial IQ_Capture , IQ_Plot and IQ_Decode. Followings are main differences

NOTE :  IQ data captured in Amarisoft (both raw IQ and remote API IQ) does not include meta data for SFN(Radio Frame, Subframe, Slot information)

 

Table of Contents

 

Test Setup

You can use any kind of test setup that allow you to get UE attached to callbox.

 

Test 1 : LTE PUSCH

In this test, I will show you an example of collecting the binary data (RE data : Resource Element Data) for LTE PUSCH. In this tutorial, I will not explain the basic procedure like eNB/gNB configuration file, operation of lte service etc. It is assumed that the readers are already familiar with basic usage of Amarisoft Callbox.

NOTE : you can use any enb configuration for this test. In this tutorial I used enb.default.cfg without any modification.

 

Command

Before starting the capture (e.g, before powering on UE), you need to enable phy signal capture. You can enable it via WebGUI as shown here or you can enable it with command line command in screen window using log command as follows.

(enb) log phy.signal=1

Then Run the Remote API command ./ws.js enb --bin /tmp/lte_pusch.bin --bin-chan /tmp/lte_pusch_chan.bin --bin-re /tmp/lte_pusch_re.bin '{"message":"register","register":"pusch"}' -l  at /root/enb directory. . NOTE :  It is important to put '-l' option at the end of the command, otherwise the websocket will be closed right away and data would not be captured.

You would notice a few options for the binary files

Then you would get the initial print out as shown below and the remoteAPI will wait for the received signal

[root@CBC-2023010100 enb]# ./ws.js enb --bin /tmp/lte_pusch.bin --bin-chan /tmp/lte_pusch_chan.bin --bin-re /tmp/lte_pusch_re.bin '{"message":"register","register":"pusch"}' -l

 

WebSocket remote API tool version ##VERSION##, Copyright (C) 2012-2024 Amarisoft

null

[

  '--bin',

  undefined,

  undefined,

  index: 0,

  input: '--bin',

  groups: undefined

]

[

  '--bin-chan',

  '-chan',

  'chan',

  index: 0,

  input: '--bin-chan',

  groups: undefined

]

[

  '--bin-re',

  '-re',

  're',

  index: 0,

  input: '--bin-re',

  groups: undefined

]

null

NOTE :  For pusch, you can use simplified command like this -  ./ws.js enb -e pusch --bin lte_pusch.bin --bin-chan /tmp/lte_pusch_chan.bin --bin-re /tmp/lte_pusch_re.bin -l

If the specified data(the data for the specified channel) is captured by gNB, you will get print as follows.

[root@CBC-2023010100 enb]# ./ws.js enb --bin /tmp/lte_pusch.bin --bin-chan /tmp/lte_pusch_chan.bin --bin-re /tmp/lte_pusch_re.bin '{"message":"register","register":"pusch"}' -l

 

WebSocket remote API tool version ##VERSION##, Copyright (C) 2012-2024 Amarisoft

null

[

  '--bin',

  undefined,

  undefined,

  index: 0,

  input: '--bin',

  groups: undefined

]

[

  '--bin-chan',

  '-chan',

  'chan',

  index: 0,

  input: '--bin-chan',

  groups: undefined

]

[

  '--bin-re',

  '-re',

  're',

  index: 0,

  input: '--bin-re',

  groups: undefined

]

null

[0.003] ### Connected to 127.0.0.1:9001

[0.003] ### Ready: name=ENB, type=ENB, version=2024-02-01

[0.023] <== Send message register id#1

[0.034] ==> Message received

{

    "message": "register",

    "message_id": "id#1",

    "time": 210.709,

    "utc": 1706903997.95

}

[12.640] Binary log received

    Label: re

    Log: harq=7 prb=19:4 symb=0:13 CW0: tb_len=11 mod=2 rv_idx=0 cr=0.11 retx=0 crc=OK snr=25.3 epre=-95.3 ta=-0.6 re_symb=48,48,48,0,48,48,48,48,48,48,0,48,48 n_ant=2 chan_symb=3,10

    Size: 2120

    Type: 1

    Len: 528

[12.640] Binary log received

    Label: chan

    Log: harq=7 prb=19:4 symb=0:13 CW0: tb_len=11 mod=2 rv_idx=0 cr=0.11 retx=0 crc=OK snr=25.3 epre=-95.3 ta=-0.6 re_symb=48,48,48,0,48,48,48,48,48,48,0,48,48 n_ant=2 chan_symb=3,10

    Size: 1544

    Type: 0

    Len: 192

[12.649] Binary log received

    Label: re

    Log: harq=0 prb=2:3 symb=0:14 CW0: tb_len=161 mod=4 rv_idx=0 cr=0.76 retx=0 crc=OK snr=24.9 epre=-95.3 ta=-0.5 re_symb=36,36,36,0,36,36,36,36,36,36,0,36,36,36 n_ant=2 chan_symb=3,10

    Size: 1736

    Type: 1

    Len: 432

[12.650] Binary log received

    Label: chan

    Log: harq=0 prb=2:3 symb=0:14 CW0: tb_len=161 mod=4 rv_idx=0 cr=0.76 retx=0 crc=OK snr=24.9 epre=-95.3 ta=-0.5 re_symb=36,36,36,0,36,36,36,36,36,36,0,36,36,36 n_ant=2 chan_symb=3,10

    Size: 1160

    Type: 0

    Len: 144

[12.760] Binary log received

    Label: re

    Log: harq=0 prb=20:3 symb=0:14 CW0: tb_len=161 mod=4 rv_idx=0 cr=0.76 retx=0 crc=OK snr=25.4 epre=-95.4 ta=-0.5 re_symb=36,36,36,0,36,36,36,36,36,36,0,36,36,36 n_ant=2 chan_symb=3,10

    Size: 1736

    Type: 1

    Len: 432

Press Ctrl+C when you want to stop the data capture.

 

Collecting the saved file

Once the data capture is complete, you would get the files saved as shown below.

RemoteAPI Constellation Test 1 Files 01

 

Post Processing

Once you capture the channel data that you wanted, you can write your own script to post process it as you desire. This is just a simple example of the post processing : plotting the captured data as a constellation.

RemoteAPI Constellation Test 1 PostProcessing 01

 

 

Test 2 : LTE SRS

In this test, I will show you an example of collecting the binary data (RE data : Resource Element Data) for LTE SRS. In this tutorial, I will not explain the basic procedure like eNB/gNB configuration file, operation of lte service etc. It is assumed that the readers are already familiar with basic usage of Amarisoft Callbox.

NOTE : you can use any enb configuration for this test. In this tutorial I used enb.default.cfg without any modification.

 

Command

Before starting the capture (e.g, before powering on UE), you need to enable phy signal capture. You can enable it via WebGUI as shown here or you can enable it with command line command in screen window using log command as follows.

(enb) log phy.signal=1

Then Run the Remote API command  ./ws_new.js enb --bin /tmp/lte_srs.bin '{"message":"register","register":"srs"}' -l  at /root/enb directory. .NOTE :  It is important to put '-l' option at the end of the command, otherwise the websocket will be closed right away and data would not be captured.

You would notice a few options for the binary files

Then you would get the initial print out as shown below and the remoteAPI will wait for the received signal

[root@CBC-2023010100 enb]# ./ws.js enb --bin /tmp/lte_srs.bin '{"message":"register","register":"srs"}' -l

 

WebSocket remote API tool version ##VERSION##, Copyright (C) 2012-2024 Amarisoft

null

[

  '--bin',

  undefined,

  undefined,

  index: 0,

  input: '--bin',

  groups: undefined

]

null

NOTE :  For pusch, you can use simplified command like this -  ./ws.js enb -e pusch --bin lte_pusch.bin --bin-chan /tmp/lte_pusch_chan.bin --bin-re /tmp/lte_pusch_re.bin -l

Then you would get the initial print out as shown below and the remoteAPI will wait for the received signal

[root@CBC-2023010100 enb]# ./ws_new.js enb --bin /tmp/lte_srs.bin '{"message":"register","register":"srs"}' -l

 

WebSocket remote API tool version ##VERSION##, Copyright (C) 2012-2024 Amarisoft

null

[

  '--bin',

  undefined,

  undefined,

  index: 0,

  input: '--bin',

  groups: undefined

]

null

[0.004] ### Connected to 127.0.0.1:9001

[0.004] ### Ready: name=ENB, type=ENB, version=2024-02-01

[0.024] <== Send message register id#1

[0.035] ==> Message received

{

    "message": "register",

    "message_id": "id#1",

    "time": 200.369,

    "utc": 1706902810.038

}

[84.110] Binary log received

    Label: chan

    Log: snr=24.6 epre=-97.5 ta=0.0 prb=6:4 symb=13:1 n_ant=2 chan_interp=2

    Size: 392

    Type: 0

    Len: 48

[84.111] Binary log received

    Label: chan

    Log: snr=26.5 epre=-97.6 ta=0.0 prb=14:4 symb=13:1 n_ant=2 chan_interp=2

    Size: 392

    Type: 0

    Len: 48

[84.220] Binary log received

    Label: chan

    Log: snr=23.6 epre=-98.2 ta=0.0 prb=2:4 symb=13:1 n_ant=2 chan_interp=2

    Size: 392

    Type: 0

    Len: 48

[84.220] Binary log received

    Label: chan

    Log: snr=18.6 epre=-96.7 ta=0.0 prb=10:4 symb=13:1 n_ant=2 chan_interp=2

    Size: 392

    Type: 0

    Len: 48

Press Ctrl+C when you want to stop the data capture.

 

Collecting the saved file

Once the data capture is complete, you would get the files saved as shown below.

RemoteAPI Constellation Test 2 Files 01

 

Post Processing

Once you capture the channel data that you wanted, you can write your own script to post process it as you desire. This is just a simple example of the post processing : plotting the captured data as a constellation.

RemoteAPI Constellation Test 2 PostProcessing 01

 

 

Post Processing Script : Example

Following is an example of Python script that shows how to process the collected bin file. For simplicity, I tried to keep the script as simple as possible without doing any complicated processing or cosmetics. You may use this as a template and extend it as you like.

NOTE : If you want to test this code with the same binary file that I used, down load file here and unzip it. I captured this binary file with Callbox software 2024-02-01 release.

import struct

import numpy as np

import matplotlib.pyplot as plt

import struct

 

 

def read_remoteAPI_bin(filename,nBlock=1,skipBlock=0):

    data = []

    with open(filename, 'rb') as f:

        for iblock in range(nBlock): # read nBlock number of blocks. the 'block' mean the chunk of data recieved by one remoteAPI Event

           # for every set of captured channel/signal, 3 x 4 bytes(12 bytes) of header are added as follows.

            total_length = struct.unpack('I', f.read(4))[0] # read 4 bytes and convert to integer.

            data_type = struct.unpack('H', f.read(2))[0]  # read 2 bytes and convert to integer

            data_label = struct.unpack('H', f.read(2))[0]  # read 2 bytes and convert to integer

            block_length = struct.unpack('I', f.read(4))[0] # read 4 bytes and convert to integer

 

            print(f'Size(Byte 0-3): {total_length}')

            print(f'Type(Byte 4-5): {data_type} - 0: 32 bits floats, 1: 16 bits integer, 2124: new data type')

            #print(f'Label(Byte 6-7): {data_label} - 1: chan, 2: re')

            print(f'Reserved(Byte 6-7): {data_label}')

            print(f'Len(Byte 8-11) Block Length = Number of complex number(I/Q Pair): {block_length}')

 

            if data_type== 0:  # 32 bits floats

                format_char = 'f'

            elif data_type == 1:  # 16 bits integer

                format_char = 'h'

            elif data_type == 2124:  # new data type

                format_char = 'f'  # replace with correct format character

            else:

                raise ValueError('Invalid data type')

 

            blockdata = []

            for ire in range(block_length):    

                re = struct.unpack(format_char, f.read(struct.calcsize(format_char)))[0]

                im = struct.unpack(format_char, f.read(struct.calcsize(format_char)))[0]

                blockdata.append(complex(re, im)) # create complex number

 

            # do not return the read data if the block index is less than skipBlock. This is mainly for debugging purpose. skipBlock=0 means return all of the read data(blocks)

            if iblock >= skipBlock:

                data.append(blockdata)   

                flat_data = [item for sublist in data for item in sublist] # flatten the list of lists. 'data' is 2D list. this is to make it 1D list

 

    return flat_data

 

#bin_file = "bin\\remoteApi_bin\\lte_pusch_re.bin"

bin_file = "bin\\remoteApi_bin\\lte_pusch_chan.bin"

#bin_file = "bin\\remoteApi_bin\\lte_pusch.bin"

#bin_file = "bin\\remoteApi_bin\\lte_srs.bin"

RE_list = read_remoteAPI_bin(bin_file,nBlock=5 , skipBlock=0)

print(f'total number of REs : {len(RE_list)}')

 

# Extract real and imaginary parts

real = [x.real for x in RE_list]

imag = [x.imag for x in RE_list]

 

# Create a scatter plot

plt.scatter(real, imag)

plt.xlabel('Real')

plt.ylabel('Imaginary')

plt.title('Constellation Diagram')

plt.grid(True)

plt.show()