dapps 0.0.3__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {dapps-0.0.3 → dapps-0.0.4}/PKG-INFO +16 -11
  2. {dapps-0.0.3 → dapps-0.0.4}/README.md +15 -10
  3. dapps-0.0.4/VERSION +1 -0
  4. {dapps-0.0.3 → dapps-0.0.4}/examples/spectrum_dapp.py +25 -12
  5. {dapps-0.0.3 → dapps-0.0.4}/src/dapp/dapp.py +1 -1
  6. {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/defs/e3.asn +1 -1
  7. {dapps-0.0.3 → dapps-0.0.4}/src/spectrum/spectrum_dapp.py +40 -31
  8. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/dashboard.py +14 -22
  9. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/iq.py +4 -4
  10. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/templates/index.html +115 -28
  11. dapps-0.0.3/VERSION +0 -1
  12. {dapps-0.0.3 → dapps-0.0.4}/.github/workflows/mirror.yml +0 -0
  13. {dapps-0.0.3 → dapps-0.0.4}/.github/workflows/tag-and-release.yml +0 -0
  14. {dapps-0.0.3 → dapps-0.0.4}/.gitignore +0 -0
  15. {dapps-0.0.3 → dapps-0.0.4}/LICENSE +0 -0
  16. {dapps-0.0.3 → dapps-0.0.4}/MANIFEST.in +0 -0
  17. {dapps-0.0.3 → dapps-0.0.4}/docker/Dockerfile +0 -0
  18. {dapps-0.0.3 → dapps-0.0.4}/docker/build.sh +0 -0
  19. {dapps-0.0.3 → dapps-0.0.4}/docs/dapp_uml.txt +0 -0
  20. {dapps-0.0.3 → dapps-0.0.4}/docs/gui_uml.txt +0 -0
  21. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/README.md +0 -0
  22. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/build_asn.sh +0 -0
  23. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/client/client.c +0 -0
  24. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/dump_packets.sh +0 -0
  25. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/server/server.py +0 -0
  26. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/wrapper/e3.c +0 -0
  27. {dapps-0.0.3 → dapps-0.0.4}/e3protocol/wrapper/e3.h +0 -0
  28. {dapps-0.0.3 → dapps-0.0.4}/pyproject.toml +0 -0
  29. {dapps-0.0.3 → dapps-0.0.4}/src/dapp/__init__.py +0 -0
  30. {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/__init__.py +0 -0
  31. {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/e3_connector.py +0 -0
  32. {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/e3_interface.py +0 -0
  33. {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/e3_logging.py +0 -0
  34. {dapps-0.0.3 → dapps-0.0.4}/src/spectrum/__init__.py +0 -0
  35. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/__init__.py +0 -0
  36. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/energy.py +0 -0
  37. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/grafana.py +0 -0
  38. {dapps-0.0.3 → dapps-0.0.4}/src/visualization/influx.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dapps
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: A library for creating and connecting dApps to Open RAN units.
5
5
  Project-URL: Homepage, https://openrangym.com/o-ran-frameworks/dapps
6
6
  Project-URL: Package, https://pypi.org/project/dapps/
@@ -48,21 +48,24 @@ A complete tutorial on how to deploy a dApp can be found on the [OpenRAN Gym web
48
48
 
49
49
  ## Installation
50
50
 
51
- ### Python package installation
51
+ ### Python package installation (recommended)
52
52
 
53
53
  ```
54
54
  pip3 install dapps[all]
55
55
  ```
56
56
 
57
- ### Manual installation
58
- Clone the repository and install it manually
57
+ The dApp can be found in the `examples` directory or it can be downloaded from this repository.
58
+
59
+ ### Manual installation (use only for library development)
60
+
61
+ Clone the repository and install it manually with:
59
62
 
60
63
  ```
61
64
  hatch build
62
65
  pip3 install dist/*.tar.gz
63
66
  ```
64
67
 
65
- ### Launch the Spectrum Sharing dApp
68
+ ### Launch the Spectrum Sharing dApp example
66
69
 
67
70
  OAI should start _before_ running the dApp
68
71
 
@@ -70,23 +73,25 @@ OAI should start _before_ running the dApp
70
73
  python3 examples/spectrum_dapp.py
71
74
  ```
72
75
 
73
- This dApp implements a spectrum sharing use case discussed in [our paper](https://arxiv.org/pdf/2501.16502).
74
-
75
-
76
- The dApp can be controlled through the following command-line arguments:
76
+ This dApp implements a spectrum sharing use case discussed in [our paper](https://doi.org/10.1016/j.comnet.2025.111342) ([here for the archive version](https://arxiv.org/pdf/2501.16502)).
77
+ The dApp parameters can be controlled through the following command-line arguments:
77
78
 
78
- - `--ota` (store_true, default: False): Specify if this is OTA or on Colosseum
79
79
  - `--link` (str, default: 'zmq', choices: [layer.value for layer in E3LinkLayer]): Specify the link layer to be used
80
80
  - `--transport` (str, default: 'ipc', choices: [layer.value for layer in E3TransportLayer]): Specify the transport layer to be used
81
81
  - `--save-iqs` (store_true, default: False): Specify if this is data collection run or not. In the first case I/Q samples will be saved
82
82
  - `--control` (store_true, default: False): Set whether to perform control of PRB
83
+ - `--noise-floor-threshold` (int, default: None): Set the noise floor threshold for determining the presence of incumbents and for detecting the PRBs affected.
84
+ - `--ota` (store_true, default: False): Specify if the setup used is OTA or on Colosseum for determining the noise floor threshold. If the `noise_floor_threshold` parameter is specified, this parameter is ignored
83
85
  - `--energy-gui` (store_true, default: False): Set whether to enable the energy GUI
84
86
  - `--iq-plotter-gui` (store_true, default: False): Set whether to enable the IQ Plotter GUI
85
87
  - `--demo-gui` (store_true, default: False): Set whether to enable the Demo GUI
88
+ - `--num-prbs` (int, default: 106): Number of PRBs
89
+ - `--num-subcarrier-spacing` (int, default: 30): Subcarrier spacing in kHz (FR1 is 30)
90
+ - `--e` (store_true, default: False): Set if 3/4 sampling for FFT size is set on the gNB (-E option on OAI)
91
+ - `--center-freq` (float, default: 3.6192e9): Center frequency in Hz
86
92
  - `--timed` (store_true, default: False): Run with a 5-minute time limit
87
93
  - `--model` (str, default: ''): Path to the CNN model file to be used
88
94
  - `--time-window` (int, default: 5): Number of input vectors to pass to the CNN model
89
- - `--input-vector` (int, default: 1536): Number of I/Q samples per input vector
90
95
  - `--moving-avg-window` (int, default: 30): Window size (in samples) for the moving average used to detect energy peaks in the spectrum
91
96
  - `--extraction-window` (int, default: 600): Number of samples to retain after detecting an energy peak
92
97
 
@@ -4,21 +4,24 @@ A complete tutorial on how to deploy a dApp can be found on the [OpenRAN Gym web
4
4
 
5
5
  ## Installation
6
6
 
7
- ### Python package installation
7
+ ### Python package installation (recommended)
8
8
 
9
9
  ```
10
10
  pip3 install dapps[all]
11
11
  ```
12
12
 
13
- ### Manual installation
14
- Clone the repository and install it manually
13
+ The dApp can be found in the `examples` directory or it can be downloaded from this repository.
14
+
15
+ ### Manual installation (use only for library development)
16
+
17
+ Clone the repository and install it manually with:
15
18
 
16
19
  ```
17
20
  hatch build
18
21
  pip3 install dist/*.tar.gz
19
22
  ```
20
23
 
21
- ### Launch the Spectrum Sharing dApp
24
+ ### Launch the Spectrum Sharing dApp example
22
25
 
23
26
  OAI should start _before_ running the dApp
24
27
 
@@ -26,23 +29,25 @@ OAI should start _before_ running the dApp
26
29
  python3 examples/spectrum_dapp.py
27
30
  ```
28
31
 
29
- This dApp implements a spectrum sharing use case discussed in [our paper](https://arxiv.org/pdf/2501.16502).
30
-
31
-
32
- The dApp can be controlled through the following command-line arguments:
32
+ This dApp implements a spectrum sharing use case discussed in [our paper](https://doi.org/10.1016/j.comnet.2025.111342) ([here for the archive version](https://arxiv.org/pdf/2501.16502)).
33
+ The dApp parameters can be controlled through the following command-line arguments:
33
34
 
34
- - `--ota` (store_true, default: False): Specify if this is OTA or on Colosseum
35
35
  - `--link` (str, default: 'zmq', choices: [layer.value for layer in E3LinkLayer]): Specify the link layer to be used
36
36
  - `--transport` (str, default: 'ipc', choices: [layer.value for layer in E3TransportLayer]): Specify the transport layer to be used
37
37
  - `--save-iqs` (store_true, default: False): Specify if this is data collection run or not. In the first case I/Q samples will be saved
38
38
  - `--control` (store_true, default: False): Set whether to perform control of PRB
39
+ - `--noise-floor-threshold` (int, default: None): Set the noise floor threshold for determining the presence of incumbents and for detecting the PRBs affected.
40
+ - `--ota` (store_true, default: False): Specify if the setup used is OTA or on Colosseum for determining the noise floor threshold. If the `noise_floor_threshold` parameter is specified, this parameter is ignored
39
41
  - `--energy-gui` (store_true, default: False): Set whether to enable the energy GUI
40
42
  - `--iq-plotter-gui` (store_true, default: False): Set whether to enable the IQ Plotter GUI
41
43
  - `--demo-gui` (store_true, default: False): Set whether to enable the Demo GUI
44
+ - `--num-prbs` (int, default: 106): Number of PRBs
45
+ - `--num-subcarrier-spacing` (int, default: 30): Subcarrier spacing in kHz (FR1 is 30)
46
+ - `--e` (store_true, default: False): Set if 3/4 sampling for FFT size is set on the gNB (-E option on OAI)
47
+ - `--center-freq` (float, default: 3.6192e9): Center frequency in Hz
42
48
  - `--timed` (store_true, default: False): Run with a 5-minute time limit
43
49
  - `--model` (str, default: ''): Path to the CNN model file to be used
44
50
  - `--time-window` (int, default: 5): Number of input vectors to pass to the CNN model
45
- - `--input-vector` (int, default: 1536): Number of I/Q samples per input vector
46
51
  - `--moving-avg-window` (int, default: 30): Window size (in samples) for the moving average used to detect energy peaks in the spectrum
47
52
  - `--extraction-window` (int, default: 600): Number of samples to retain after detecting an energy peak
48
53
 
dapps-0.0.4/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -37,27 +37,36 @@ def main(args, time_to_wait: float = 60.0):
37
37
  )
38
38
  exit(-1)
39
39
 
40
- if args.ota:
41
- print(f'Using OTA configuration')
42
- noise_floor_threshold = 20 # this really depends on the RF conditions and should be carefully calibrated
43
- else: # Colosseum
44
- print(f'Using Colosseum configuration')
45
- noise_floor_threshold = 53
46
-
47
- print(f'Threshold {noise_floor_threshold}')
40
+ # This value really depends on the RF conditions and the RU used and should be carefully calibrated
41
+ if args.noise_floor_threshold:
42
+ print(f'Using custom configuration')
43
+ noise_floor_threshold = args.noise_floor_threshold
44
+ else:
45
+ if args.ota:
46
+ print(f'Using OTA configuration')
47
+ noise_floor_threshold = 20
48
+ else: # Colosseum
49
+ print(f'Using Colosseum configuration')
50
+ noise_floor_threshold = 53
51
+
52
+ print(f'Threshold is {noise_floor_threshold}')
48
53
 
49
54
  classifier = None
50
55
  if args.model:
56
+ import math
57
+ ofdm_symbol_size = args.num_prbs * 12 # 12 subcarriers per PRB always fixed by LTE/NR standard
58
+ fft_size = 2 ** math.ceil(math.log2(ofdm_symbol_size)) # Next power of 2
51
59
  classifier = Classifier(
52
60
  time_window = args.time_window,
53
- input_vector = args.input_vector,
61
+ input_vector = fft_size,
54
62
  moving_avg_window = args.moving_avg_window,
55
63
  extraction_window = args.extraction_window,
56
64
  model_path = args.model
57
65
  )
58
66
 
59
67
  dapp = SpectrumSharingDApp(noise_floor_threshold=noise_floor_threshold, save_iqs=args.save_iqs, control=args.control, link=args.link, transport=args.transport,
60
- energyGui=args.energy_gui, iqPlotterGui=args.iq_plotter_gui, dashboard=args.demo_gui, classifier=classifier)
68
+ energyGui=args.energy_gui, iqPlotterGui=args.iq_plotter_gui, dashboard=args.demo_gui, classifier=classifier, center_freq=args.center_freq,
69
+ num_prbs=args.num_prbs, e_sampling=args.e, num_subcarrier_spacing= args.num_subcarrier_spacing)
61
70
 
62
71
  dapp.setup_connection()
63
72
 
@@ -76,18 +85,22 @@ def main(args, time_to_wait: float = 60.0):
76
85
 
77
86
  if __name__ == "__main__":
78
87
  parser = argparse.ArgumentParser(description="Example of a dApp for Spectrum Sharing")
79
- parser.add_argument('--ota', action='store_true', default=False, help="Specify if this is OTA or on Colosseum")
80
88
  parser.add_argument('--link', type=str, default='zmq', choices=[layer.value for layer in E3LinkLayer], help="Specify the link layer to be used")
81
89
  parser.add_argument('--transport', type=str, default='ipc', choices=[layer.value for layer in E3TransportLayer], help="Specify the transport layer to be used")
82
90
  parser.add_argument('--save-iqs', action='store_true', default=False, help="Specify if this is data collection run or not. In the first case I/Q samples will be saved")
83
91
  parser.add_argument('--control', action='store_true', default=False, help="Set whether to perform control of PRB")
92
+ parser.add_argument('--noise-floor-threshold', type=int, default=None, help="Set the noise floor threshold for determining the presence of incumbents and for detecting the PRBs affected.")
93
+ parser.add_argument('--ota', action='store_true', default=False, help="Specify if the setup used is OTA or on Colosseum for determining the noise floor threshold. If the `noise_floor_threshold` parameter is specified, this parameter is ignored")
84
94
  parser.add_argument('--energy-gui', action='store_true', default=False, help="Set whether to enable the energy GUI")
85
95
  parser.add_argument('--iq-plotter-gui', action='store_true', default=False, help="Set whether to enable the IQ Plotter GUI")
86
96
  parser.add_argument('--demo-gui', action='store_true', default=False, help="Set whether to enable the Demo GUI")
97
+ parser.add_argument('--num-prbs', type=int, default=106, help="Number of PRBs")
98
+ parser.add_argument('--num-subcarrier-spacing', type=int, default=30, help="Subcarrier spacing in kHz (FR1 is 30)")
99
+ parser.add_argument('--e', action='store_true', default=False, help="Set if 3/4 sampling for FFT size is set on the gNB (-E option on OAI)")
100
+ parser.add_argument('--center-freq', type=float, default=3.6192e9, help="Center frequency in Hz")
87
101
  parser.add_argument('--timed', action='store_true', default=False, help="Run with a 5-minute time limit")
88
102
  parser.add_argument('--model', type=str, default='', help="Path to the CNN model file to be used")
89
103
  parser.add_argument('--time-window', type=int, default=5, help="Number of input vectors to pass to the CNN model.")
90
- parser.add_argument('--input-vector', type=int, default=1536, help="Number of I/Q samples per input vector.")
91
104
  parser.add_argument('--moving-avg-window', type=int, default=30, help="Window size (in samples) for the moving average used to detect energy peaks in the spectrum.")
92
105
  parser.add_argument('--extraction-window', type=int, default=600, help="Number of samples to retain after detecting an energy peak.")
93
106
 
@@ -19,7 +19,7 @@ LOG_DIR = ('.' if os.geteuid() != 0 else '') + '/logs/'
19
19
  class DApp(ABC):
20
20
  e3_interface: E3Interface
21
21
 
22
- def __init__(self, id: int = 1, link: str = 'posix', transport:str = 'uds', callbacks: list = [], **kwargs):
22
+ def __init__(self, id: int = 1, link: str = 'posix', transport:str = 'ipc', callbacks: list = [], **kwargs):
23
23
  super().__init__()
24
24
  self.dapp_id = id
25
25
  self.e3_interface = E3Interface(link=link, transport=transport)
@@ -10,7 +10,7 @@ E3 DEFINITIONS AUTOMATIC TAGS ::= BEGIN
10
10
  }
11
11
 
12
12
  E3-IndicationMessage ::= SEQUENCE {
13
- protocolData OCTET STRING (SIZE(1..8192))
13
+ protocolData OCTET STRING (SIZE(1..16384))
14
14
  }
15
15
 
16
16
  E3-ControlAction ::= SEQUENCE {
@@ -15,26 +15,36 @@ from e3interface.e3_logging import dapp_logger, LOG_DIR
15
15
 
16
16
  class SpectrumSharingDApp(DApp):
17
17
 
18
- ### Configuration ###
19
- # gNB runs with BW = 40 MHz, with -E (3/4 sampling)
18
+ ### Default Configuration ###
19
+ # gNB runs with BW = ~40 MHz
20
20
  # Center frequency = 3.6192 GHz
21
- # OAI sampling frequency 46.08e6
21
+ # OAI sampling frequency 46.08e6 # not useful for visualization
22
22
  # No SRS for now, hence in gNB config file set do_SRS = 0
23
- # gNB->frame_parms.ofdm_symbol_size = 1536
24
- # gNB->frame_parms.first_carrier_offset = 900
25
- # Noise floor threshold needs to be calibrated
26
- # We receive the symbols and average them over some frames, and do thresholding.
27
-
28
- def __init__(self, id: int = 1, noise_floor_threshold: int = 53, save_iqs: bool = False, control: bool = False, link: str = 'posix', transport:str = 'uds', **kwargs):
29
- super().__init__(link=link, transport=transport, **kwargs)
30
-
31
- self.bw = 40.08e6 # Bandwidth in Hz
32
- self.center_freq = 3.6192e9 # Center frequency in Hz
33
- self.First_carrier_offset = 900
34
- self.Num_car_prb = 12
35
- self.prb_thrs = 75 # This avoids blacklisting PRBs where the BWP is scheduled (it’s a workaround bc the UE and gNB would not be able to communicate anymore, a cleaner fix is to move the BWP if needed or things like that)
36
- self.FFT_SIZE = 1536
37
- self.Average_over_frames = 63
23
+ # gNB->frame_parms.ofdm_symbol_size = 1536 # FFT size (even though is called ofdm_symbol_size) for USRP, with -E flag (3/4 sampling)
24
+ # gNB->frame_parms.ofdm_symbol_size = 2048 # FFT size (even though is called ofdm_symbol_size) for RUs, with no -E flag
25
+ # Noise floor threshold needs to be calibrated according to the RU
26
+ # We receive the symbols, average them over 63 frames and do PRB thresholding.
27
+
28
+ def __init__(self, id: int = 1, link: str = 'posix', transport: str = 'ipc', noise_floor_threshold: int = 53, save_iqs: bool = False, control: bool = False,
29
+ center_freq: float = 3.6192e9, num_prbs: int = 106, num_subcarrier_spacing: int = 30, e_sampling: bool = False, **kwargs):
30
+ super().__init__(id=id, link=link, transport=transport, **kwargs)
31
+
32
+ # gNB radio configuration used to compute exact values
33
+ self.num_consecutive_subcarriers_for_prb: int = 12 # Always fixed by the LTE/NR standard to 12
34
+ self.num_prbs = num_prbs
35
+ self.num_subcarrier_spacing = num_subcarrier_spacing # subcarrier spacing in kHz (FR1 is 30)
36
+ self.ofdm_symbol_size = num_prbs * self.num_consecutive_subcarriers_for_prb
37
+ self.bw = self.num_prbs * self.num_consecutive_subcarriers_for_prb * self.num_subcarrier_spacing * 1e3 # Bandwidth in Hz
38
+ self.center_freq = center_freq # Center frequency in Hz
39
+ self.fft_size = 2 ** int(np.ceil(np.log2(self.ofdm_symbol_size))) # Next power of 2 to include guard bands
40
+ if e_sampling:
41
+ self.fft_size = int(self.fft_size * 3 / 4) # 3/4 of sampling for USRPs (-E flag)
42
+ self.first_carrier_offset = self.fft_size - (self.ofdm_symbol_size // 2)
43
+
44
+ # dApp configuration
45
+ # The first 76 PRB usually have channels that should not be nulled (but we should investigate more options to enable this)
46
+ self.prb_thrs = 75 # This avoids blacklisting PRBs where the BWP is scheduled (it’s a workaround since the UE and gNB would not be able to communicate anymore, a cleaner fix is to move the BWP if needed or things like that)
47
+ self.average_over_frames = 63
38
48
  self.noise_floor_threshold = noise_floor_threshold
39
49
  self.save_iqs = save_iqs
40
50
  self.e3_interface.add_callback(self.get_iqs_from_ran)
@@ -50,25 +60,24 @@ class SpectrumSharingDApp(DApp):
50
60
  self.dashboard = kwargs.get('dashboard', False)
51
61
 
52
62
  self.control_count = 1
53
- self.abs_iq_av = np.zeros(self.FFT_SIZE)
63
+ self.abs_iq_av = np.zeros(self.fft_size)
54
64
 
55
65
  if self.energyGui:
56
66
  from visualization.energy import EnergyPlotter
57
67
  self.sig_queue = multiprocessing.Queue()
58
- self.energyPlotter = EnergyPlotter(self.FFT_SIZE, bw=self.bw, center_freq=self.center_freq)
68
+ self.energyPlotter = EnergyPlotter(self.fft_size, bw=self.bw, center_freq=self.center_freq)
59
69
 
60
70
  if self.iqPlotterGui:
61
71
  from visualization.iq import IQPlotter
62
72
  self.iq_queue = multiprocessing.Queue()
63
- iq_size = self.FFT_SIZE * 2 # double the size of ofdm_symbol_size since real and imaginary parts are interleaved
64
- self.iqPlotter = IQPlotter(buffer_size=500, iq_size=iq_size, bw=self.bw, center_freq=self.center_freq)
73
+ self.iqPlotter = IQPlotter(buffer_size=500, fft_size=self.fft_size, bw=self.bw, center_freq=self.center_freq)
65
74
 
66
75
  if self.dashboard:
67
76
  from visualization.dashboard import Dashboard
68
77
  self.demo_queue = multiprocessing.Queue()
69
- iq_size = self.FFT_SIZE * 2 # double the size of ofdm_symbol_size since real and imaginary parts are interleaved
70
78
  classifier = kwargs.get('classifier', None)
71
- self.demo = Dashboard(buffer_size=100, iq_size=iq_size, classifier=classifier)
79
+ self.demo = Dashboard(buffer_size=100, ofdm_symbol_size=self.ofdm_symbol_size, first_carrier_offset=self.first_carrier_offset,
80
+ bw=self.bw, center_freq=self.center_freq, num_prbs=num_prbs, classifier=classifier)
72
81
 
73
82
  def get_iqs_from_ran(self, data):
74
83
  dapp_logger.debug(f'Triggered callback')
@@ -83,7 +92,7 @@ class SpectrumSharingDApp(DApp):
83
92
 
84
93
  iq_arr = np.frombuffer(data, dtype=np.int16)
85
94
  dapp_logger.debug(f"Shape of iq_arr {iq_arr.shape}")
86
-
95
+
87
96
  if self.iqPlotterGui:
88
97
  self.iq_queue.put(iq_arr)
89
98
 
@@ -100,9 +109,9 @@ class SpectrumSharingDApp(DApp):
100
109
  self.control_count += 1
101
110
  dapp_logger.debug(f"Control count is: {self.control_count}")
102
111
 
103
- if self.control_count == self.Average_over_frames:
104
- abs_iq_av_db = 20 * np.log10(1 + (self.abs_iq_av/(self.Average_over_frames)))
105
- abs_iq_av_db_offset_correct = np.append(abs_iq_av_db[self.First_carrier_offset:self.FFT_SIZE],abs_iq_av_db[0:self.First_carrier_offset])
112
+ if self.control_count == self.average_over_frames:
113
+ abs_iq_av_db = 20 * np.log10(1 + (self.abs_iq_av/(self.average_over_frames)))
114
+ abs_iq_av_db_offset_correct = np.append(abs_iq_av_db[self.first_carrier_offset:self.fft_size],abs_iq_av_db[0:self.first_carrier_offset])
106
115
  dapp_logger.info(f'--- AVG VALUES ----')
107
116
  dapp_logger.info(f'abs_iq_av_db_offset_correct: {abs_iq_av_db_offset_correct.mean()}')
108
117
  dapp_logger.info(f'--- MAX VALUES ----')
@@ -111,11 +120,11 @@ class SpectrumSharingDApp(DApp):
111
120
  # dapp_logger.info(f'Last 20 max values (abs_iq_av_db_offset_correct): {last_5_max_abs_iq_av_db_offset_correct}')
112
121
 
113
122
  # PRB blocking based on the noise floor threshold
114
- f_ind = np.arange(self.FFT_SIZE)
123
+ f_ind = np.arange(self.fft_size)
115
124
  blklist_sub_carrier = f_ind[abs_iq_av_db_offset_correct > self.noise_floor_threshold]
116
125
  np.sort(blklist_sub_carrier)
117
126
  dapp_logger.info(f'blklist_sub_carrier: {blklist_sub_carrier}')
118
- prb_blk_list = np.unique((np.floor(blklist_sub_carrier/self.Num_car_prb))).astype(np.uint16)
127
+ prb_blk_list = np.unique((np.floor(blklist_sub_carrier/self.num_consecutive_subcarriers_for_prb))).astype(np.uint16)
119
128
  dapp_logger.info(f'prb_blk_list: {prb_blk_list}')
120
129
  prb_blk_list = prb_blk_list[prb_blk_list > self.prb_thrs]
121
130
  # prb_blk_list = np.array([76, 77, 78, 79, 80, 81, 82, 83], dtype=np.uint16) # 76, 77, 78, 79, 80, 81, 82, 83
@@ -137,7 +146,7 @@ class SpectrumSharingDApp(DApp):
137
146
  self.demo_queue.put(("prb_list", prb_blk_list))
138
147
 
139
148
  # Reset the variables
140
- self.abs_iq_av = np.zeros(self.FFT_SIZE)
149
+ self.abs_iq_av = np.zeros(self.fft_size)
141
150
  self.control_count = 1
142
151
 
143
152
  def _control_loop(self):
@@ -14,18 +14,21 @@ except ModuleNotFoundError:
14
14
  import numpy as np
15
15
 
16
16
  class Dashboard:
17
- def __init__(self, buffer_size=100, iq_size=1536, bw=46.08e6, center_freq=3.6192e9, classifier=None):
18
- # TODO double check killing conditions
17
+ # The default values of the dashboard are based on the default configuration
18
+ def __init__(self, buffer_size: int = 100, ofdm_symbol_size: int = 1272, bw: float = 38.16e6, center_freq: float = 3.6192e9,
19
+ num_prbs: int = 106, first_carrier_offset: int = 900, classifier = None):
19
20
  # Flask app setup
20
21
  self.app = Flask(__name__)
21
22
  self.app.config['TEMPLATES_AUTO_RELOAD'] = True
22
23
  self.socketio = SocketIO(self.app)
23
24
 
24
25
  # Parameters
25
- self.BUFFER_SIZE = buffer_size
26
- self.iq_size = iq_size
27
- self.BW = bw
28
- self.CENTER_FREQ = center_freq
26
+ self.buffer_size = buffer_size
27
+ self.ofdm_symbol_size = ofdm_symbol_size
28
+ self.bw = bw
29
+ self.center_freq = center_freq
30
+ self.num_prbs = num_prbs
31
+ self.first_carrier_offset = first_carrier_offset
29
32
 
30
33
  # Route setup
31
34
  self.app.add_url_rule("/", view_func=self.index)
@@ -51,20 +54,12 @@ class Dashboard:
51
54
  self.run_thread.start()
52
55
 
53
56
  def handle_initial_connection(self):
54
- num_prbs = 106
55
- iqs_to_show = int(
56
- self.iq_size / 2
57
- ) # / 2 since the size received is real and imaginary part
58
- # Each PRB corresponds to 12 subcarriers
59
- # Parameter first_carrier_offset (currently set to 900) is the first subcarrier.
60
- # So PRB 0 is [900-911], 1 is [912-923], etc. They wrap around fft_size (now at 1536)
61
- # The carrier frequency is in the middle PRB (including DC leak on some radios), this means it falls at (first_carrier_offset + 106*12/2) % fft_size? where % is the modulo operation
62
- # The first 76 PRB usually have channels that should not be nulled (but we should investigate more options to enable this)
63
- # So if you want the spectrum waterfall to correspond to the PRB masking plot, you can rotate them as per bullet 2 above and drop [635-899]
64
- iqs_to_show -= 264 # is the size of the array to drop
65
57
  data_buffer = {
66
- "magnitude": np.zeros((self.BUFFER_SIZE, iqs_to_show)).tolist(),
67
- "num_prbs": num_prbs,
58
+ "magnitude": np.zeros((self.buffer_size, self.ofdm_symbol_size)).tolist(),
59
+ "center_freq": self.center_freq,
60
+ "bw": self.bw,
61
+ "num_prbs": self.num_prbs,
62
+ "first_carrier_offset": self.first_carrier_offset,
68
63
  "predicted_label": self.classifier is not None
69
64
  }
70
65
  self.socketio.emit("initialize_plot", data_buffer)
@@ -84,8 +79,6 @@ class Dashboard:
84
79
  plot, payload = message
85
80
  if plot == "iq_data":
86
81
  iq_data = payload
87
- if iq_data.size != self.iq_size:
88
- raise ValueError(f'Expected IQ data of size {self.iq_size}, but got {iq_data.size}')
89
82
 
90
83
  self.sampling_counter += 1
91
84
  if self.sampling_counter >= self.sampling_threshold:
@@ -105,7 +98,6 @@ class Dashboard:
105
98
  def stop(self):
106
99
  """Stops the server and kills the thread."""
107
100
  if self.run_thread and self.run_thread.is_alive():
108
- self.socketio.stop()
109
101
  self.run_thread.join()
110
102
 
111
103
  if __name__ == "__main__":
@@ -17,19 +17,19 @@ import os
17
17
  print(os.environ.get('DISPLAY'))
18
18
 
19
19
  class IQPlotter:
20
- def __init__(self, buffer_size=100, iq_size=1536, bw=46.08e6, center_freq=3.6192e9):
20
+ def __init__(self, buffer_size=100, fft_size=1536, bw=38.16e6, center_freq=3.6192e9):
21
21
  """
22
22
  Initialize the IQ plotter with buffer size and IQ data dimensions.
23
23
  buffer_size: Number of columns (timesteps) to store and plot.
24
- iq_size: Size of the IQ data, should be double the size of ofdm_symbol_size with real and imaginary interleaved.
24
+ fft_size: Size of the FFT.
25
25
  bw: Sampling frequency.
26
26
  center_freq: Center frequency of the signal.
27
27
  """
28
28
  self.buffer_size = buffer_size
29
- self.iq_size = iq_size
29
+ self.iq_size = fft_size * 2 # double the size of fft_size since real and imaginary parts are interleaved
30
30
  self.bw = bw
31
31
  self.center_freq = center_freq
32
- self.iq_shape = (iq_size // 2, buffer_size) # Half the size after splitting real and imaginary parts
32
+ self.iq_shape = (self.iq_size // 2, buffer_size) # Half the size after splitting real and imaginary parts
33
33
 
34
34
  # Initialize the buffer
35
35
  self.buffer = np.zeros(self.iq_shape)
@@ -4,7 +4,7 @@
4
4
  <head>
5
5
  <meta charset="UTF-8">
6
6
  <title>Real-Time Plots</title>
7
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
7
+ <script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
8
8
  <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
9
9
  </head>
10
10
 
@@ -18,27 +18,38 @@
18
18
  z: [],
19
19
  type: 'heatmap',
20
20
  colorscale: "Viridis",
21
+ zmin: 0,
22
+ zmax: 100,
21
23
  colorbar: {
22
24
  title:{
23
25
  text: "Magnitude [dB]",
24
26
  side: 'right'
25
27
  },
26
- orientation: 'h'
28
+ orientation: 'v'
27
29
  }
28
30
  }];
29
31
 
30
32
  var waterfallLayout = {
31
- title: "Sensed Spectrum",
33
+ title: {
34
+ text: "Sensed Spectrum",
35
+ font: { size: 16 }
36
+ },
32
37
  xaxis: {
33
- title: "OFDM Symbol Subcarriers"
38
+ title: {
39
+ text: "OFDM Symbol Subcarriers",
40
+ font: { size: 14 }
41
+ }
34
42
  },
35
43
  yaxis: {
36
- title: "Time samples",
44
+ title: {
45
+ text: "Time samples",
46
+ font: { size: 14 }
47
+ },
37
48
  autorange: 'reversed'
38
49
  },
39
50
  annotations: [
40
51
  {
41
- x: 1272 / 2,
52
+ x: 0, // Will be updated dynamically
42
53
  y: 1.15,
43
54
  xref: 'x',
44
55
  yref: 'paper',
@@ -49,21 +60,21 @@
49
60
  }
50
61
  },
51
62
  {
52
- x: 10,
63
+ x: 0, // Will be updated dynamically
53
64
  y: 1.1,
54
65
  xref: 'x',
55
66
  yref: 'paper',
56
- text: '3.5992',
67
+ text: `0.0000`,
57
68
  showarrow: false,
58
69
  font: {
59
70
  size: 12
60
71
  }
61
72
  }, {
62
- x: 1272 / 4,
73
+ x: 0, // Will be updated dynamically
63
74
  y: 1.1,
64
75
  xref: 'x',
65
76
  yref: 'paper',
66
- text: '3.6092',
77
+ text: `0.0000`,
67
78
  showarrow: false,
68
79
  font: {
69
80
  size: 12
@@ -71,33 +82,33 @@
71
82
  },
72
83
 
73
84
  {
74
- x: 1272 / 2,
85
+ x: 0, // Will be updated dynamically
75
86
  y: 1.1,
76
87
  xref: 'x',
77
88
  yref: 'paper',
78
- text: '3.6192',
89
+ text: `0.0000`,
79
90
  showarrow: false,
80
91
  font: {
81
92
  size: 12
82
93
  }
83
94
  },
84
95
  {
85
- x: 1272 * 3 / 4,
96
+ x: 0, // Will be updated dynamically
86
97
  y: 1.1,
87
98
  xref: 'x',
88
99
  yref: 'paper',
89
- text: '3.6292',
100
+ text: `0.0000`,
90
101
  showarrow: false,
91
102
  font: {
92
103
  size: 12
93
104
  }
94
105
  },
95
106
  {
96
- x: 1272 - 23,
107
+ x: 0, // Will be updated dynamically
97
108
  y: 1.1,
98
109
  xref: 'x',
99
110
  yref: 'paper',
100
- text: '3.6392',
111
+ text: `0.0000`,
101
112
  showarrow: false,
102
113
  font: {
103
114
  size: 12
@@ -145,12 +156,21 @@
145
156
  align: 'right'
146
157
  }
147
158
  ],
148
- title: "PRB Status Grid",
159
+ title: {
160
+ text: "PRB Status Grid",
161
+ font: { size: 16 }
162
+ },
149
163
  xaxis: {
150
- title: "PRB Index"
164
+ title: {
165
+ text: "PRB Index",
166
+ font: { size: 14 }
167
+ }
151
168
  },
152
169
  yaxis: {
153
- title: "Status",
170
+ title: {
171
+ text: "Status",
172
+ font: { size: 14 }
173
+ },
154
174
  showticklabels: false
155
175
  },
156
176
  height: 200,
@@ -172,11 +192,41 @@
172
192
  var waterfall_buffer;
173
193
  var prbs_buffer;
174
194
  var predictedLabel;
195
+ var start_ignore;
196
+ var end_ignore;
197
+ var initialized = false;
175
198
 
176
199
  var socket = io.connect('http://' + document.domain + ':' + location.port);
177
200
  socket.on('initialize_plot', function (data) {
201
+ var ofdm_symbol_size = data.magnitude[0].length;
202
+ var first_carrier_offset = data.first_carrier_offset;
203
+ var freq_step = data.bw / ofdm_symbol_size;
204
+ var start_freq = data.center_freq - (ofdm_symbol_size / 2) * freq_step;
205
+ var freq_labels = [];
206
+ for (let i = 0; i < 5; i++) {
207
+ let pos = Math.floor(i * ofdm_symbol_size / 4);
208
+ let f = (start_freq + pos * freq_step) / 1e9;
209
+ freq_labels.push(f.toFixed(4));
210
+ }
211
+
212
+ // Update x values for annotations (frequency markers) with proper edge margins
213
+ var left_margin = Math.max(25, ofdm_symbol_size * 0.02); // At least 25 or 2% of width
214
+ var right_margin = Math.max(65, ofdm_symbol_size * 0.02); // At least 65 or 2% of width
215
+
216
+ waterfallLayout.annotations[0].x = ofdm_symbol_size / 2 + ofdm_symbol_size / 50; // slightly centered to the right
217
+ waterfallLayout.annotations[1].x = left_margin;
218
+ waterfallLayout.annotations[2].x = ofdm_symbol_size / 4;
219
+ waterfallLayout.annotations[3].x = ofdm_symbol_size / 2;
220
+ waterfallLayout.annotations[4].x = ofdm_symbol_size * 3 / 4;
221
+ waterfallLayout.annotations[5].x = ofdm_symbol_size - right_margin;
222
+ waterfallLayout.annotations[1].text = `${freq_labels[0]}`;
223
+ waterfallLayout.annotations[2].text = `${freq_labels[1]}`;
224
+ waterfallLayout.annotations[3].text = `${freq_labels[2]}`;
225
+ waterfallLayout.annotations[4].text = `${freq_labels[3]}`;
226
+ waterfallLayout.annotations[5].text = `${freq_labels[4]}`;
227
+
178
228
  waterfall_buffer = data.magnitude;
179
- waterfall[0].z.push(waterfall_buffer);
229
+ waterfall[0].z = waterfall_buffer;
180
230
  prbs_buffer = [new Array(data.num_prbs).fill(0)];
181
231
  prbs[0].z = prbs_buffer;
182
232
  Plotly.newPlot('magnitude', waterfall, waterfallLayout);
@@ -184,29 +234,66 @@
184
234
  if(data.predicted_label){
185
235
  Plotly.newPlot("predicted_label", [], classifierLayout);
186
236
  }
237
+
238
+ // Dynamic preprocessing based on first_carrier_offset
239
+ // The carrier frequency is in the middle PRB (including DC leak on some radios)
240
+ console.log("Initial values:", {
241
+ ofdm_symbol_size: ofdm_symbol_size,
242
+ first_carrier_offset: first_carrier_offset,
243
+ num_prbs: data.num_prbs
244
+ });
245
+ const num_consecutive_subcarriers_for_prb = 12; // Always 12 by LTE / NR standard
246
+ // Calculate the middle PRB position
247
+ var middle_prb_offset = data.num_prbs * num_consecutive_subcarriers_for_prb / 2;
248
+ console.log("Calculated middle_prb_offset:", middle_prb_offset);
249
+
250
+ // Calculate slice points dynamically
251
+ // We want to show the spectrum centered around the middle PRB
252
+ // The interval to consider is from the middle prb offset to the first carrier offset
253
+ start_ignore = middle_prb_offset;
254
+ end_ignore = first_carrier_offset;
255
+
256
+ console.log("Slice points:", {
257
+ start_ignore: start_ignore,
258
+ end_ignore: end_ignore
259
+ });
260
+
261
+ initialized = true;
187
262
  });
188
263
 
189
264
  socket.on('update_plot', function (data) {
265
+ if (!initialized) {
266
+ return;
267
+ }
268
+
190
269
  if ('magnitude' in data) {
191
270
  // console.log("Magnitude is: ", data.magnitude)
192
- // Preprocess data
193
- let part1 = data.magnitude.slice(0, 635);
194
- // let ignoredPart = data.magnitude.slice(635, 899);
195
- let part3 = data.magnitude.slice(899);
271
+
272
+ var magnitude_length = data.magnitude.length;
273
+ // Dynamic slicing based on calculated positions
274
+ let part1 = data.magnitude.slice(0, start_ignore);
275
+ // Ignore the middle section (guards symbols)
276
+ let part3 = data.magnitude.slice(end_ignore);
196
277
  let magnitudeToShow = part3.concat(part1).reverse();
278
+
279
+ // console.log("Parts lengths:", {
280
+ // // part1_length: part1.length,
281
+ // // part3_length: part3.length,
282
+ // magnitudeToShow_length: magnitudeToShow.length
283
+ // });
284
+
197
285
  // Update waterfall plot
198
286
  waterfall_buffer.pop();
199
287
  waterfall_buffer.unshift(magnitudeToShow);
200
288
  Plotly.restyle('magnitude', { z: [waterfall_buffer] });
201
- }
202
- else if ('prb_list' in data) {
203
- console.log("PRBs are: ", data.prb_list);
289
+ } else if ('prb_list' in data) {
290
+ // console.log("PRBs are: ", data.prb_list);
204
291
  // We get a list of indexes to replace each time
205
292
  prbs_buffer[0].fill(0);
206
293
  data.prb_list.forEach(index => prbs_buffer[0][index] = 1);
207
294
  Plotly.restyle('prbs', { z: [prbs_buffer] });
208
295
  } else if ('predicted_label' in data) {
209
- console.log("RFI Predicted is ", data.predicted_label);
296
+ // console.log("RFI Predicted is ", data.predicted_label);
210
297
  Plotly.relayout('predicted_label', {
211
298
  'title.text': `RFI Predicted: ${data.predicted_label}`
212
299
  });
dapps-0.0.3/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.0.3
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes