dapps 0.1.1__tar.gz → 0.2.0__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.
- {dapps-0.1.1 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/new_sm.yml +1 -1
- {dapps-0.1.1 → dapps-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- {dapps-0.1.1 → dapps-0.2.0}/CONTRIBUTING.md +1 -1
- {dapps-0.1.1 → dapps-0.2.0}/PKG-INFO +5 -3
- dapps-0.2.0/VERSION +1 -0
- {dapps-0.1.1 → dapps-0.2.0}/examples/spectrum_dapp.py +112 -30
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/include/iqsaver/iqsaver.hpp +1 -1
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/include/iqsaver/types.hpp +1 -1
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/src/iq_saver_writer.cpp +26 -2
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/swig/iqsaver_swig.hpp +1 -1
- {dapps-0.1.1 → dapps-0.2.0}/pyproject.toml +3 -2
- {dapps-0.1.1 → dapps-0.2.0}/src/dapp/dapp.py +5 -2
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/defs/e3.json +63 -141
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/e3_connector.py +39 -4
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/e3_encoder.py +125 -28
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/e3_interface.py +241 -144
- {dapps-0.1.1 → dapps-0.2.0}/src/iq_saver/iq_saver.py +75 -11
- dapps-0.2.0/src/simple/defs/e3sm_simple.asn +111 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/simple/simple_dapp.py +52 -6
- dapps-0.2.0/src/spectrum/README.md +56 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/spectrum/adaptive_noise_floor.py +22 -3
- dapps-0.2.0/src/spectrum/defs/e3sm_oai_l1_kpm.asn +97 -0
- dapps-0.2.0/src/spectrum/defs/e3sm_spectrum.asn +156 -0
- dapps-0.2.0/src/spectrum/defs/e3sm_spectrum.json +250 -0
- dapps-0.2.0/src/spectrum/e3_l2_sensing_reader.py +155 -0
- dapps-0.2.0/src/spectrum/e3_ran_buffers_reader.py +338 -0
- dapps-0.2.0/src/spectrum/spectrum_dapp.py +2055 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/spectrum/threshold_detector.py +32 -2
- dapps-0.2.0/src/visualization/subcarrier/insi_white.png +0 -0
- dapps-0.2.0/src/visualization/subcarrier/start_subcarrier_visualizer.sh +51 -0
- dapps-0.2.0/src/visualization/subcarrier/subcarrier_visualizer.py +2504 -0
- dapps-0.2.0/src/visualization/subcarrier_pub.py +192 -0
- dapps-0.2.0/start_dapp.sh +2 -0
- dapps-0.2.0/tests/test_e3_concurrency.py +68 -0
- dapps-0.2.0/tests/test_encoder_roundtrip.py +186 -0
- dapps-0.2.0/tests/test_indication_dispatch.py +182 -0
- {dapps-0.1.1 → dapps-0.2.0}/tests/test_iq_saver.py +215 -12
- dapps-0.2.0/tests/test_sensing_policy.py +253 -0
- dapps-0.2.0/tests/test_spectrum_telemetry.py +87 -0
- dapps-0.1.1/VERSION +0 -1
- dapps-0.1.1/src/simple/defs/e3sm_simple.asn +0 -54
- dapps-0.1.1/src/spectrum/defs/e3sm_spectrum.asn +0 -76
- dapps-0.1.1/src/spectrum/defs/e3sm_spectrum.json +0 -137
- dapps-0.1.1/src/spectrum/spectrum_dapp.py +0 -743
- dapps-0.1.1/src/visualization/dashboard.py +0 -185
- dapps-0.1.1/src/visualization/templates/index.html +0 -508
- dapps-0.1.1/tests/test_dashboard.py +0 -43
- {dapps-0.1.1 → dapps-0.2.0}/.github/CODEOWNERS +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/question.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/workflows/mirror.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/workflows/tag-and-release.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.github/workflows/test.yml +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/.gitignore +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/CMakeLists.txt +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/CONTRIBUTORS +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/LICENSE +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/MANIFEST.in +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/README.md +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/SECURITY.md +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/docs/dapp_uml.txt +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/docs/ground_truth_labeling.md +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/docs/gui_uml.txt +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/examples/nvidia_dapp.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/examples/simple_dapp.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/CMakeLists.txt +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/cmake/libiqsaver.pc.in +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/cmake/libiqsaverConfig.cmake.in +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/include/iqsaver/annotation.hpp +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/include/iqsaver/iq_saver_writer.hpp +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/include/iqsaver/version.hpp.in +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/swig/iqsaver.i +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/libiqsaver/swig/iqsaver_swig.cpp +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/dapp/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/defs/e3.asn +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/e3interface/e3_logging.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/iq_saver/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/nvidia/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/nvidia/defs/e3sm_nvidia.asn +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/nvidia/defs/e3sm_nvidia.json +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/nvidia/nvidia_dapp.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/simple/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/spectrum/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/spectrum/embargo_manager.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/visualization/__init__.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/visualization/energy.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/visualization/grafana.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/visualization/influx.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/src/visualization/iq.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/tools/README.md +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/tools/asntojson.py +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/tools/generate_sm.sh +0 -0
- {dapps-0.1.1 → dapps-0.2.0}/tools/read_recording.py +0 -0
|
@@ -43,7 +43,7 @@ These mirror what CI (`.github/workflows/test.yml`) enforces. **All boxes must b
|
|
|
43
43
|
|
|
44
44
|
## Twin-repo coordination
|
|
45
45
|
|
|
46
|
-
dapps is paired with [`libe3`](https://github.com/wineslab/
|
|
46
|
+
dapps is paired with [`libe3`](https://github.com/wineslab/libe3) and [`dApp-openairinterface5g`](https://github.com/wineslab/dApp-openairinterface5g). Breaking changes to the twin repos are not accepted.
|
|
47
47
|
|
|
48
48
|
- [ ] This PR does not break the E3 wire protocol, OR a paired PR exists in the affected twin repo (link below).
|
|
49
49
|
|
|
@@ -58,7 +58,7 @@ The following are **mandatory** for every contribution. PRs that do not meet the
|
|
|
58
58
|
|
|
59
59
|
### Service Models and twin-repo coordination
|
|
60
60
|
|
|
61
|
-
This repository is part of a framework that includes [`libe3`](https://github.com/wineslab/
|
|
61
|
+
This repository is part of a framework that includes [`libe3`](https://github.com/wineslab/libe3) (the gNB-side E3 agent) and [`dApp-openairinterface5g`](https://github.com/wineslab/dApp-openairinterface5g) (the OAI fork). PRs that change the E3 wire protocol or add a new Service Model must come with a paired PR in the affected twin repo, linked from the PR description.
|
|
62
62
|
|
|
63
63
|
## Code of Conduct
|
|
64
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dapps
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A library for creating and connecting dApps to Open RAN units.
|
|
5
5
|
Keywords: dApps,O-RAN,5G,cellular,networks
|
|
6
6
|
Author-Email: Andrea Lacava <thecave003@gmail.com>
|
|
@@ -28,14 +28,16 @@ Provides-Extra: cnn
|
|
|
28
28
|
Requires-Dist: libiq; extra == "cnn"
|
|
29
29
|
Provides-Extra: gui
|
|
30
30
|
Requires-Dist: flask; extra == "gui"
|
|
31
|
-
Requires-Dist: flask-
|
|
31
|
+
Requires-Dist: flask-sock; extra == "gui"
|
|
32
|
+
Requires-Dist: flask-cors; extra == "gui"
|
|
32
33
|
Requires-Dist: matplotlib; extra == "gui"
|
|
33
34
|
Provides-Extra: network
|
|
34
35
|
Requires-Dist: pysctp; extra == "network"
|
|
35
36
|
Provides-Extra: all
|
|
36
37
|
Requires-Dist: pysctp; extra == "all"
|
|
37
38
|
Requires-Dist: flask; extra == "all"
|
|
38
|
-
Requires-Dist: flask-
|
|
39
|
+
Requires-Dist: flask-sock; extra == "all"
|
|
40
|
+
Requires-Dist: flask-cors; extra == "all"
|
|
39
41
|
Requires-Dist: matplotlib; extra == "all"
|
|
40
42
|
Requires-Dist: influxdb_client; extra == "all"
|
|
41
43
|
Requires-Dist: libiq; extra == "all"
|
dapps-0.2.0/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.0
|
|
@@ -9,7 +9,11 @@ import time
|
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
11
|
from e3interface.e3_connector import E3LinkLayer, E3TransportLayer
|
|
12
|
-
from spectrum.spectrum_dapp import
|
|
12
|
+
from spectrum.spectrum_dapp import (
|
|
13
|
+
SpectrumSharingDApp,
|
|
14
|
+
compute_fft_size,
|
|
15
|
+
make_periodic_toggle_callback,
|
|
16
|
+
)
|
|
13
17
|
from spectrum.threshold_detector import StaticThresholdDetector, AdaptiveThresholdDetector
|
|
14
18
|
|
|
15
19
|
LOG_DIR = '/tmp/'
|
|
@@ -23,17 +27,15 @@ def stop_program(time_to_wait, dapp: SpectrumSharingDApp):
|
|
|
23
27
|
|
|
24
28
|
def main(args):
|
|
25
29
|
if args.model:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
exit(-1)
|
|
30
|
+
# CNN signal classification is not wired into SpectrumSharingDApp; a
|
|
31
|
+
# classifier= kwarg would fall through to **kwargs and be dropped, so
|
|
32
|
+
# --model (and --time-window/--moving-avg-window/--extraction-window)
|
|
33
|
+
# would silently do nothing. Fail loudly instead.
|
|
34
|
+
raise SystemExit(
|
|
35
|
+
"--model (CNN signal classification) is not supported by "
|
|
36
|
+
"SpectrumSharingDApp; remove --model and the associated "
|
|
37
|
+
"--time-window/--moving-avg-window/--extraction-window flags."
|
|
38
|
+
)
|
|
37
39
|
|
|
38
40
|
# This value really depends on the RF conditions and the RU used and
|
|
39
41
|
# should be carefully calibrated.
|
|
@@ -81,16 +83,6 @@ def main(args):
|
|
|
81
83
|
f" | window: {args.average_over_frames} frames"
|
|
82
84
|
)
|
|
83
85
|
|
|
84
|
-
classifier = None
|
|
85
|
-
if args.model:
|
|
86
|
-
classifier = Classifier(
|
|
87
|
-
time_window=args.time_window,
|
|
88
|
-
input_vector=fft_size,
|
|
89
|
-
moving_avg_window=args.moving_avg_window,
|
|
90
|
-
extraction_window=args.extraction_window,
|
|
91
|
-
model_path=args.model,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
86
|
dapp = SpectrumSharingDApp(
|
|
95
87
|
detector=detector,
|
|
96
88
|
save_iqs=args.save_iqs,
|
|
@@ -100,14 +92,21 @@ def main(args):
|
|
|
100
92
|
energyGui=args.energy_gui,
|
|
101
93
|
iqPlotterGui=args.iq_plotter_gui,
|
|
102
94
|
dashboard=args.demo_gui,
|
|
103
|
-
|
|
95
|
+
viz_web_port=args.viz_web_port,
|
|
96
|
+
viz_zmq_port=args.viz_zmq_port,
|
|
97
|
+
external_viz=args.external_viz,
|
|
104
98
|
center_freq=args.center_freq,
|
|
105
99
|
num_prbs=args.num_prbs,
|
|
106
100
|
e_sampling=args.e,
|
|
107
101
|
num_subcarrier_spacing=args.num_subcarrier_spacing,
|
|
108
102
|
sampling_threshold=args.sampling_threshold,
|
|
103
|
+
max_samples_per_file=args.max_samples_per_file,
|
|
104
|
+
fp16_beta=args.fp16_beta,
|
|
105
|
+
sensing_only=args.sensing_only,
|
|
106
|
+
strict_sensing=args.strict_sensing,
|
|
107
|
+
min_sensing_symbols=args.min_sensing_symbols,
|
|
108
|
+
encoding_method=args.encoding_method,
|
|
109
109
|
ground_truth=args.ground_truth,
|
|
110
|
-
show_controls=args.show_controls,
|
|
111
110
|
dapp_name="SpectrumSharing",
|
|
112
111
|
dapp_version="1.0.0",
|
|
113
112
|
vendor="WinesLab",
|
|
@@ -137,7 +136,29 @@ def main(args):
|
|
|
137
136
|
)
|
|
138
137
|
time.sleep(1)
|
|
139
138
|
|
|
140
|
-
dapp.send_subscription_request()
|
|
139
|
+
if not dapp.send_subscription_request():
|
|
140
|
+
# send_subscription_request returns True only if the gNB accepted both
|
|
141
|
+
# the L1-KPM (RF=2) and Spectrum (RF=1) subscriptions; anything else
|
|
142
|
+
# means the dApp would run deaf, so abort with a non-zero exit.
|
|
143
|
+
dapp.stop()
|
|
144
|
+
raise SystemExit("[FATAL] gNB did not accept the dApp subscriptions; aborting")
|
|
145
|
+
|
|
146
|
+
# Optional sensing-policy toggle. When --toggle-period > 0, installs
|
|
147
|
+
# a periodic on/off toggle that flips the gNB's masked UL TDA
|
|
148
|
+
# selector via spectrum_sm ctrl_id=2. Operator must have configured
|
|
149
|
+
# additional_ul_tdas in the gNB (e.g. "0:7") for the scheduler to
|
|
150
|
+
# have a short TDA to switch to. See examples/README or the gNB
|
|
151
|
+
# boot log ([additional] tag) to verify.
|
|
152
|
+
if args.toggle_period > 0:
|
|
153
|
+
print(f"[INFO] Installing sensing-policy toggle: "
|
|
154
|
+
f"period={args.toggle_period}s "
|
|
155
|
+
f"n_slots={args.toggle_n_slots} "
|
|
156
|
+
f"mask_when_on=0x{args.toggle_mask:04x}")
|
|
157
|
+
dapp.set_sensing_policy_logic(make_periodic_toggle_callback(
|
|
158
|
+
period_s=args.toggle_period,
|
|
159
|
+
n_slots=args.toggle_n_slots,
|
|
160
|
+
mask_when_on=args.toggle_mask,
|
|
161
|
+
))
|
|
141
162
|
|
|
142
163
|
if args.timed:
|
|
143
164
|
timer = threading.Thread(target=stop_program, args=(args.timed, dapp), daemon=False)
|
|
@@ -167,7 +188,8 @@ if __name__ == "__main__":
|
|
|
167
188
|
parser.add_argument('--save-iqs', action='store_true', default=False,
|
|
168
189
|
help="Save I/Q samples to SigMF files")
|
|
169
190
|
parser.add_argument('--control', action='store_true', default=False,
|
|
170
|
-
help="Send PRB
|
|
191
|
+
help="Send PRB-block control messages to the gNB when "
|
|
192
|
+
"PRBs are detected above the noise threshold")
|
|
171
193
|
parser.add_argument('--noise-floor-threshold', type=int, default=None,
|
|
172
194
|
help="Detection threshold in dB (static) or dB above noise floor (adaptive)")
|
|
173
195
|
parser.add_argument('--use-adaptive-noise-floor', action='store_true', default=False,
|
|
@@ -184,7 +206,13 @@ if __name__ == "__main__":
|
|
|
184
206
|
parser.add_argument('--iq-plotter-gui', action='store_true', default=False,
|
|
185
207
|
help="Enable IQ time-domain plotter")
|
|
186
208
|
parser.add_argument('--demo-gui', action='store_true', default=False,
|
|
187
|
-
help="
|
|
209
|
+
help="Publish per-slot subcarrier power to the visualizer")
|
|
210
|
+
parser.add_argument('--viz-web-port', type=int, default=5001,
|
|
211
|
+
help="Visualizer web UI port (default 5001)")
|
|
212
|
+
parser.add_argument('--viz-zmq-port', type=int, default=5559,
|
|
213
|
+
help="ZMQ port the dApp publishes on / the visualizer reads (default 5559)")
|
|
214
|
+
parser.add_argument('--external-viz', action='store_true', default=False,
|
|
215
|
+
help="Don't spawn the visualizer; publish to an already-running one")
|
|
188
216
|
parser.add_argument('--num-prbs', type=int, default=106,
|
|
189
217
|
help="Number of PRBs")
|
|
190
218
|
parser.add_argument('--num-subcarrier-spacing', type=int, default=30,
|
|
@@ -205,14 +233,68 @@ if __name__ == "__main__":
|
|
|
205
233
|
help="Samples to retain after CNN energy peak detection")
|
|
206
234
|
parser.add_argument('--sampling-threshold', type=int, default=5,
|
|
207
235
|
help="Render a new dashboard frame every N IQ batches (visualization only, does not affect IQ delivery or recording)")
|
|
236
|
+
parser.add_argument('--max-samples-per-file', type=int, default=46_080_000,
|
|
237
|
+
help="Rotate the SigMF capture file once a segment reaches "
|
|
238
|
+
"this many true IQ samples (default 46080000). Each "
|
|
239
|
+
"indication is kept whole, so a segment may exceed the "
|
|
240
|
+
"threshold by up to one indication's worth of samples. "
|
|
241
|
+
"Segment wall-clock duration depends on the capture rate. "
|
|
242
|
+
"Only used with --save-iqs.")
|
|
208
243
|
parser.add_argument('--ground-truth', type=str, default='', metavar='LABEL',
|
|
209
244
|
help="Initial ground truth label written into IQ annotations "
|
|
210
245
|
"(only used with --save-iqs). "
|
|
211
246
|
"Updatable at runtime via the dashboard GUI "
|
|
212
247
|
"when --demo-gui is also set.")
|
|
213
|
-
parser.add_argument('--
|
|
214
|
-
help="
|
|
215
|
-
"
|
|
248
|
+
parser.add_argument('--fp16-beta', type=float, default=1.0 / 2048.0,
|
|
249
|
+
help="FP16 IQ rescale factor; MUST match the gNB "
|
|
250
|
+
"E3Configuration.fp16_beta (the reader rescales by "
|
|
251
|
+
"1/beta). Default 1/2048 matches the gNB code default; "
|
|
252
|
+
"the X410 sample conf overrides it to 0.0078125 (1/128).")
|
|
253
|
+
parser.add_argument('--encoding-method', type=str, default='asn1',
|
|
254
|
+
choices=['asn1', 'json'],
|
|
255
|
+
help="Wire encoding for Spectrum-* envelopes (default: asn1).")
|
|
256
|
+
parser.add_argument('--no-sensing-only', dest='sensing_only',
|
|
257
|
+
action='store_false', default=True,
|
|
258
|
+
help="Disable the sensing-window filter. By default the dApp uses the "
|
|
259
|
+
"sensing ranges from the Spectrum SM (RF=1) to slice the detector "
|
|
260
|
+
"input to sensing-PUSCH cells; passing this flag uses every cell.")
|
|
261
|
+
parser.add_argument('--strict-sensing', dest='strict_sensing',
|
|
262
|
+
action='store_true', default=False,
|
|
263
|
+
help="Stricter sensing filter: drop any slot whose sensing window "
|
|
264
|
+
"doesn't cover the full slot (i.e. any UE PUSCH was granted "
|
|
265
|
+
"in that slot). Eliminates UE CP/spectral bleed at the cost "
|
|
266
|
+
"of a sparser waterfall under heavy UE traffic. Requires "
|
|
267
|
+
"--sensing-only (the default).")
|
|
268
|
+
parser.add_argument('--min-sensing-symbols', type=int, default=1,
|
|
269
|
+
help="Minimum number of kept symbols required to emit the slot to "
|
|
270
|
+
"the dashboard. Default 1: display every slot that has any "
|
|
271
|
+
"sensing symbol — keeps the waterfall scrolling even under "
|
|
272
|
+
"heavy UE UL where the MAC scheduler leaves only one free "
|
|
273
|
+
"symbol per slot. Set to 2 to drop the typical \"only sym 13 "
|
|
274
|
+
"survived\" partial-sensing slots (evens out brightness at "
|
|
275
|
+
"the cost of dashboard freezes under load). Set to 14 to "
|
|
276
|
+
"require fully-clean slots (equivalent to "
|
|
277
|
+
"--strict-sensing).")
|
|
278
|
+
|
|
279
|
+
# --- Optional sensing-policy toggle ----------------------------- #
|
|
280
|
+
# Drives the gNB's masked UL TDA selector via spectrum_sm ctrl_id=2.
|
|
281
|
+
# Disabled by default (toggle-period=0); set --toggle-period to a
|
|
282
|
+
# positive value to enable. Requires additional_ul_tdas (e.g. "0:7")
|
|
283
|
+
# on the gNB so the scheduler has a short TDA to switch to.
|
|
284
|
+
parser.add_argument('--toggle-period', type=float, default=0.0,
|
|
285
|
+
metavar='SECONDS',
|
|
286
|
+
help="Toggle the gNB sensing policy on/off every N seconds "
|
|
287
|
+
"(0 = disabled, default). When > 0, alternates between "
|
|
288
|
+
"installing the mask (active) and clearing it (deactivate). "
|
|
289
|
+
"Requires additional_ul_tdas configured on the gNB.")
|
|
290
|
+
parser.add_argument('--toggle-n-slots', type=int, default=20,
|
|
291
|
+
help="numb_slots_frame of the gNB for the per-slot mask "
|
|
292
|
+
"(mu=1 -> 20). Must match the gNB exactly or the gNB "
|
|
293
|
+
"will NACK the policy with an n_slots mismatch.")
|
|
294
|
+
parser.add_argument('--toggle-mask', type=lambda s: int(s, 0), default=0x3F80,
|
|
295
|
+
metavar='HEX',
|
|
296
|
+
help="14-bit symbol bitmap applied uniformly to every slot "
|
|
297
|
+
"when the toggle is active (default 0x3F80 = syms 7..13).")
|
|
216
298
|
|
|
217
299
|
args = parser.parse_args()
|
|
218
300
|
print("Start dApp")
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// libiqsaver is a SigMF-compliant IQ-sample writer with sample-count and
|
|
6
6
|
// time-based file rotation, deferred annotations, and a configurable SigMF
|
|
7
|
-
// extension namespace (default: "
|
|
7
|
+
// extension namespace (default: "dapp:"). The library is the C++ backend
|
|
8
8
|
// for the Python `iq_saver` package used by the SPEAR spectrum dApp; it is
|
|
9
9
|
// also usable directly as a standalone C++ library installed via CMake +
|
|
10
10
|
// pkg-config (libiqsaver.pc).
|
|
@@ -19,7 +19,7 @@ struct IQSaverConfig {
|
|
|
19
19
|
std::string filename;
|
|
20
20
|
std::optional<uint64_t> max_samples_per_file;
|
|
21
21
|
std::optional<double> rotation_interval;
|
|
22
|
-
std::string extension_namespace = "
|
|
22
|
+
std::string extension_namespace = "dapp";
|
|
23
23
|
// JSON-encoded object of extra global metadata fields. Keys whose names
|
|
24
24
|
// do not already start with `core:` or `<extension_namespace>:` are
|
|
25
25
|
// prefixed with `<extension_namespace>:` before being written.
|
|
@@ -29,6 +29,12 @@ namespace {
|
|
|
29
29
|
|
|
30
30
|
constexpr const char* kSigMFVersion = "1.0.0";
|
|
31
31
|
|
|
32
|
+
// Fallback version stamped in the SigMF `core:extensions` entry for the
|
|
33
|
+
// configured extension namespace when the caller supplies no `schema_version`
|
|
34
|
+
// in extra metadata. Callers (e.g. the spectrum dApp) that co-version their
|
|
35
|
+
// namespace pass an explicit `schema_version`, which takes precedence.
|
|
36
|
+
constexpr const char* kDefaultExtensionVersion = "1.0.0";
|
|
37
|
+
|
|
32
38
|
double now_seconds() {
|
|
33
39
|
using namespace std::chrono;
|
|
34
40
|
return duration_cast<duration<double>>(
|
|
@@ -189,6 +195,18 @@ struct IQSaverWriter::Impl {
|
|
|
189
195
|
if (!config.hw_info.empty()) {
|
|
190
196
|
record_global["core:hw"] = config.hw_info;
|
|
191
197
|
}
|
|
198
|
+
// Declare the extension namespace we stamp so SigMF readers know which
|
|
199
|
+
// extension the `<ns>:*` fields belong to. Version is co-versioned with
|
|
200
|
+
// the namespace: use the caller's `schema_version` when provided, else
|
|
201
|
+
// fall back to kDefaultExtensionVersion. `optional=false` — the `<ns>:*`
|
|
202
|
+
// geometry/domain fields are required to interpret the recording.
|
|
203
|
+
record_global["core:extensions"] = json::array(
|
|
204
|
+
{json{{"name", config.extension_namespace},
|
|
205
|
+
{"version",
|
|
206
|
+
extra_metadata.value(
|
|
207
|
+
"schema_version",
|
|
208
|
+
std::string(kDefaultExtensionVersion))},
|
|
209
|
+
{"optional", false}}});
|
|
192
210
|
for (auto it = extra_metadata.begin(); it != extra_metadata.end();
|
|
193
211
|
++it) {
|
|
194
212
|
if (it.key() == "sampling_threshold") continue;
|
|
@@ -314,9 +332,15 @@ struct IQSaverWriter::Impl {
|
|
|
314
332
|
std::optional<double> timestamp) {
|
|
315
333
|
double ts = timestamp.value_or(now_seconds());
|
|
316
334
|
|
|
317
|
-
// Rotation check
|
|
335
|
+
// Rotation check. max_samples_per_file is a count of true IQ *samples*,
|
|
336
|
+
// so it is compared against iq_sample_count — NOT frame_count, which
|
|
337
|
+
// counts save_samples() calls. The check runs before the write so each
|
|
338
|
+
// call's samples stay whole within one segment; a segment therefore
|
|
339
|
+
// reaches *at least* max_samples_per_file and may exceed it by up to one
|
|
340
|
+
// call's worth of samples. It is a rotation threshold, not a hard cap —
|
|
341
|
+
// splitting a single call across files would fragment an indication.
|
|
318
342
|
if (config.max_samples_per_file.has_value() &&
|
|
319
|
-
|
|
343
|
+
iq_sample_count >= *config.max_samples_per_file) {
|
|
320
344
|
rotate_file(ts);
|
|
321
345
|
} else if (config.rotation_interval.has_value() &&
|
|
322
346
|
file_start_time.has_value() &&
|
|
@@ -33,7 +33,7 @@ struct IQSaverConfigSwig {
|
|
|
33
33
|
// Negative => unset.
|
|
34
34
|
long long max_samples_per_file = -1;
|
|
35
35
|
double rotation_interval = -1.0;
|
|
36
|
-
std::string extension_namespace = "
|
|
36
|
+
std::string extension_namespace = "dapp";
|
|
37
37
|
std::string extra_metadata_json = "{}";
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -43,12 +43,13 @@ Source = "https://github.com/wineslab/dApp-library"
|
|
|
43
43
|
[project.optional-dependencies]
|
|
44
44
|
api = ["influxdb_client"]
|
|
45
45
|
cnn = ["libiq"]
|
|
46
|
-
gui = ["flask", "flask-
|
|
46
|
+
gui = ["flask", "flask-sock", "flask-cors", "matplotlib"]
|
|
47
47
|
network = ["pysctp"]
|
|
48
48
|
all = [
|
|
49
49
|
"pysctp",
|
|
50
50
|
"flask",
|
|
51
|
-
"flask-
|
|
51
|
+
"flask-sock",
|
|
52
|
+
"flask-cors",
|
|
52
53
|
"matplotlib",
|
|
53
54
|
"influxdb_client",
|
|
54
55
|
"libiq",
|
|
@@ -127,8 +127,11 @@ class DApp(ABC):
|
|
|
127
127
|
else:
|
|
128
128
|
dapp_logger.warning(f"Subscription response not positive: {data}")
|
|
129
129
|
|
|
130
|
-
def send_subscription_request(self, subscriptionTime: int | None = None, periodicity: int | None = None) -> bool:
|
|
130
|
+
def send_subscription_request(self, subscriptionTime: int | None = None, periodicity: int | None = None) -> bool:
|
|
131
131
|
self.e3_interface.add_subscription_callback(self.dapp_id, self.manage_subscription_response)
|
|
132
|
+
|
|
133
|
+
self.e3_interface.add_indication_callback(self.dapp_id, 0, self._handle_indication)
|
|
134
|
+
self.e3_interface.add_xapp_control_callback(self.dapp_id, 0, self._handle_xapp_control)
|
|
132
135
|
dapp_logger.debug(f"Subscription callbacks: {self.e3_interface.subscription_callbacks}")
|
|
133
136
|
scheduled = self.e3_interface.send_subscription_request(
|
|
134
137
|
self.dapp_id,
|
|
@@ -148,7 +151,7 @@ class DApp(ABC):
|
|
|
148
151
|
pass
|
|
149
152
|
|
|
150
153
|
@abstractmethod
|
|
151
|
-
def _handle_indication(self, dapp_identifier: int, data: bytes):
|
|
154
|
+
def _handle_indication(self, dapp_identifier: int, ran_function_id: int, data: bytes):
|
|
152
155
|
# This in the future might become a class
|
|
153
156
|
pass
|
|
154
157
|
|
|
@@ -344,150 +344,72 @@
|
|
|
344
344
|
},
|
|
345
345
|
"E3-PDU": {
|
|
346
346
|
"type": "object",
|
|
347
|
+
"required": ["type", "id"],
|
|
347
348
|
"properties": {
|
|
348
|
-
"
|
|
349
|
-
"
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
],
|
|
363
|
-
"additionalProperties": false
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
"type": "object",
|
|
367
|
-
"properties": {
|
|
368
|
-
"setupResponse": {
|
|
369
|
-
"$ref": "#/$defs/E3-SetupResponse"
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
"required": [
|
|
373
|
-
"setupResponse"
|
|
374
|
-
],
|
|
375
|
-
"additionalProperties": false
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
"type": "object",
|
|
379
|
-
"properties": {
|
|
380
|
-
"subscriptionRequest": {
|
|
381
|
-
"$ref": "#/$defs/E3-SubscriptionRequest"
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
"required": [
|
|
385
|
-
"subscriptionRequest"
|
|
386
|
-
],
|
|
387
|
-
"additionalProperties": false
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
"type": "object",
|
|
391
|
-
"properties": {
|
|
392
|
-
"subscriptionDelete": {
|
|
393
|
-
"$ref": "#/$defs/E3-SubscriptionDelete"
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
"required": [
|
|
397
|
-
"subscriptionDelete"
|
|
398
|
-
],
|
|
399
|
-
"additionalProperties": false
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
"type": "object",
|
|
403
|
-
"properties": {
|
|
404
|
-
"subscriptionResponse": {
|
|
405
|
-
"$ref": "#/$defs/E3-SubscriptionResponse"
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
|
-
"required": [
|
|
409
|
-
"subscriptionResponse"
|
|
410
|
-
],
|
|
411
|
-
"additionalProperties": false
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
"type": "object",
|
|
415
|
-
"properties": {
|
|
416
|
-
"indicationMessage": {
|
|
417
|
-
"$ref": "#/$defs/E3-IndicationMessage"
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
"required": [
|
|
421
|
-
"indicationMessage"
|
|
422
|
-
],
|
|
423
|
-
"additionalProperties": false
|
|
424
|
-
},
|
|
425
|
-
{
|
|
426
|
-
"type": "object",
|
|
427
|
-
"properties": {
|
|
428
|
-
"dAppControlAction": {
|
|
429
|
-
"$ref": "#/$defs/E3-DAppControlAction"
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
"required": [
|
|
433
|
-
"dAppControlAction"
|
|
434
|
-
],
|
|
435
|
-
"additionalProperties": false
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
"type": "object",
|
|
439
|
-
"properties": {
|
|
440
|
-
"dAppReport": {
|
|
441
|
-
"$ref": "#/$defs/E3-DAppReport"
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
"required": [
|
|
445
|
-
"dAppReport"
|
|
446
|
-
],
|
|
447
|
-
"additionalProperties": false
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
"type": "object",
|
|
451
|
-
"properties": {
|
|
452
|
-
"xAppControlAction": {
|
|
453
|
-
"$ref": "#/$defs/E3-XAppControlAction"
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
"required": [
|
|
457
|
-
"xAppControlAction"
|
|
458
|
-
],
|
|
459
|
-
"additionalProperties": false
|
|
460
|
-
},
|
|
461
|
-
{
|
|
462
|
-
"type": "object",
|
|
463
|
-
"properties": {
|
|
464
|
-
"releaseMessage": {
|
|
465
|
-
"$ref": "#/$defs/E3-ReleaseMessage"
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
"required": [
|
|
469
|
-
"releaseMessage"
|
|
470
|
-
],
|
|
471
|
-
"additionalProperties": false
|
|
472
|
-
},
|
|
473
|
-
{
|
|
474
|
-
"type": "object",
|
|
475
|
-
"properties": {
|
|
476
|
-
"messageAck": {
|
|
477
|
-
"$ref": "#/$defs/E3-MessageAck"
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
"required": [
|
|
481
|
-
"messageAck"
|
|
482
|
-
],
|
|
483
|
-
"additionalProperties": false
|
|
484
|
-
}
|
|
349
|
+
"type": {
|
|
350
|
+
"type": "string",
|
|
351
|
+
"enum": [
|
|
352
|
+
"setupRequest",
|
|
353
|
+
"setupResponse",
|
|
354
|
+
"subscriptionRequest",
|
|
355
|
+
"subscriptionDelete",
|
|
356
|
+
"subscriptionResponse",
|
|
357
|
+
"indicationMessage",
|
|
358
|
+
"dAppControlAction",
|
|
359
|
+
"dAppReport",
|
|
360
|
+
"xAppControlAction",
|
|
361
|
+
"releaseMessage",
|
|
362
|
+
"messageAck"
|
|
485
363
|
]
|
|
486
|
-
}
|
|
364
|
+
},
|
|
365
|
+
"id": { "$ref": "#/$defs/E3-MessageID" },
|
|
366
|
+
"timestamp": { "type": "integer", "minimum": 0 }
|
|
487
367
|
},
|
|
488
|
-
"
|
|
489
|
-
|
|
490
|
-
|
|
368
|
+
"oneOf": [
|
|
369
|
+
{
|
|
370
|
+
"properties": { "type": { "const": "setupRequest" } },
|
|
371
|
+
"allOf": [ { "$ref": "#/$defs/E3-SetupRequest" } ]
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"properties": { "type": { "const": "setupResponse" } },
|
|
375
|
+
"allOf": [ { "$ref": "#/$defs/E3-SetupResponse" } ]
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"properties": { "type": { "const": "subscriptionRequest" } },
|
|
379
|
+
"allOf": [ { "$ref": "#/$defs/E3-SubscriptionRequest" } ]
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"properties": { "type": { "const": "subscriptionDelete" } },
|
|
383
|
+
"allOf": [ { "$ref": "#/$defs/E3-SubscriptionDelete" } ]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"properties": { "type": { "const": "subscriptionResponse" } },
|
|
387
|
+
"allOf": [ { "$ref": "#/$defs/E3-SubscriptionResponse" } ]
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"properties": { "type": { "const": "indicationMessage" } },
|
|
391
|
+
"allOf": [ { "$ref": "#/$defs/E3-IndicationMessage" } ]
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"properties": { "type": { "const": "dAppControlAction" } },
|
|
395
|
+
"allOf": [ { "$ref": "#/$defs/E3-DAppControlAction" } ]
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"properties": { "type": { "const": "dAppReport" } },
|
|
399
|
+
"allOf": [ { "$ref": "#/$defs/E3-DAppReport" } ]
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"properties": { "type": { "const": "xAppControlAction" } },
|
|
403
|
+
"allOf": [ { "$ref": "#/$defs/E3-XAppControlAction" } ]
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"properties": { "type": { "const": "releaseMessage" } },
|
|
407
|
+
"allOf": [ { "$ref": "#/$defs/E3-ReleaseMessage" } ]
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"properties": { "type": { "const": "messageAck" } },
|
|
411
|
+
"allOf": [ { "$ref": "#/$defs/E3-MessageAck" } ]
|
|
412
|
+
}
|
|
491
413
|
]
|
|
492
414
|
}
|
|
493
415
|
}
|