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)
- The data mentioned in IQ_Capture , IQ_Plot and IQ_Decode are raw binary data, which includes all the captured I/Q data. These data is before/without any kind of Physical layer processing (e.g, equalization, OFDM demodulation, handling precoding etc)
- The data explained in this tutorial is the I/Q value on OFDM Resource Grid. It means the I/Q value captured in this tutorial is the captured after equalization, OFDM demodulation, handling precoding etc
Table of Contents
- Remote API - Constellation
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.
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. .
You would notice a few options for the binary files
-
--bin : the specified file store every data on the resource grid for the registerred channel. (for example, if you register pusch, the pusch data is composed of two different types of data, PUSCH user data and PUSCH DMRS data. in --bin option, the file saves both the user data and dmrs data)
-
--bin-re : the specified file store the user data only
-
--bin-chan : the specified file store the DMRS data only
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 |
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.
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.
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.
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. .
You would notice a few options for the binary files
-
--bin : the specified file store every data on the resource grid for the registerred signal. (NOTE : In case of SRS, you don't need to specify --bin-re, --bin-chan since SRS is made up of only one type of data)
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 |
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.
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.
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.
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() |