dapps 0.1.0__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.
Files changed (103) hide show
  1. {dapps-0.1.0 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/new_sm.yml +1 -1
  2. {dapps-0.1.0 → dapps-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +3 -3
  3. {dapps-0.1.0 → dapps-0.2.0}/.github/workflows/tag-and-release.yml +8 -3
  4. {dapps-0.1.0 → dapps-0.2.0}/.github/workflows/test.yml +8 -3
  5. dapps-0.2.0/CMakeLists.txt +22 -0
  6. {dapps-0.1.0 → dapps-0.2.0}/CONTRIBUTING.md +4 -4
  7. dapps-0.2.0/MANIFEST.in +6 -0
  8. {dapps-0.1.0 → dapps-0.2.0}/PKG-INFO +56 -23
  9. {dapps-0.1.0 → dapps-0.2.0}/README.md +32 -1
  10. dapps-0.2.0/VERSION +1 -0
  11. {dapps-0.1.0 → dapps-0.2.0}/docs/ground_truth_labeling.md +4 -4
  12. {dapps-0.1.0 → dapps-0.2.0}/examples/spectrum_dapp.py +112 -30
  13. dapps-0.2.0/libiqsaver/CMakeLists.txt +274 -0
  14. dapps-0.2.0/libiqsaver/cmake/libiqsaver.pc.in +10 -0
  15. dapps-0.2.0/libiqsaver/cmake/libiqsaverConfig.cmake.in +9 -0
  16. dapps-0.2.0/libiqsaver/include/iqsaver/annotation.hpp +6 -0
  17. dapps-0.2.0/libiqsaver/include/iqsaver/iq_saver_writer.hpp +72 -0
  18. dapps-0.2.0/libiqsaver/include/iqsaver/iqsaver.hpp +17 -0
  19. dapps-0.2.0/libiqsaver/include/iqsaver/types.hpp +31 -0
  20. dapps-0.2.0/libiqsaver/include/iqsaver/version.hpp.in +10 -0
  21. dapps-0.2.0/libiqsaver/src/iq_saver_writer.cpp +536 -0
  22. dapps-0.2.0/libiqsaver/swig/iqsaver.i +38 -0
  23. dapps-0.2.0/libiqsaver/swig/iqsaver_swig.cpp +107 -0
  24. dapps-0.2.0/libiqsaver/swig/iqsaver_swig.hpp +85 -0
  25. dapps-0.2.0/pyproject.toml +89 -0
  26. {dapps-0.1.0 → dapps-0.2.0}/src/dapp/dapp.py +5 -2
  27. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/defs/e3.json +63 -141
  28. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/e3_connector.py +39 -4
  29. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/e3_encoder.py +125 -28
  30. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/e3_interface.py +241 -144
  31. dapps-0.2.0/src/iq_saver/iq_saver.py +279 -0
  32. dapps-0.2.0/src/simple/defs/e3sm_simple.asn +111 -0
  33. {dapps-0.1.0 → dapps-0.2.0}/src/simple/simple_dapp.py +52 -6
  34. dapps-0.2.0/src/spectrum/README.md +56 -0
  35. {dapps-0.1.0 → dapps-0.2.0}/src/spectrum/adaptive_noise_floor.py +22 -3
  36. dapps-0.2.0/src/spectrum/defs/e3sm_oai_l1_kpm.asn +97 -0
  37. dapps-0.2.0/src/spectrum/defs/e3sm_spectrum.asn +156 -0
  38. dapps-0.2.0/src/spectrum/defs/e3sm_spectrum.json +250 -0
  39. dapps-0.2.0/src/spectrum/e3_l2_sensing_reader.py +155 -0
  40. dapps-0.2.0/src/spectrum/e3_ran_buffers_reader.py +338 -0
  41. dapps-0.2.0/src/spectrum/spectrum_dapp.py +2055 -0
  42. {dapps-0.1.0 → dapps-0.2.0}/src/spectrum/threshold_detector.py +32 -2
  43. dapps-0.2.0/src/visualization/subcarrier/insi_white.png +0 -0
  44. dapps-0.2.0/src/visualization/subcarrier/start_subcarrier_visualizer.sh +51 -0
  45. dapps-0.2.0/src/visualization/subcarrier/subcarrier_visualizer.py +2504 -0
  46. dapps-0.2.0/src/visualization/subcarrier_pub.py +192 -0
  47. dapps-0.2.0/start_dapp.sh +2 -0
  48. dapps-0.2.0/tests/test_e3_concurrency.py +68 -0
  49. dapps-0.2.0/tests/test_encoder_roundtrip.py +186 -0
  50. dapps-0.2.0/tests/test_indication_dispatch.py +182 -0
  51. {dapps-0.1.0 → dapps-0.2.0}/tests/test_iq_saver.py +215 -12
  52. dapps-0.2.0/tests/test_sensing_policy.py +253 -0
  53. dapps-0.2.0/tests/test_spectrum_telemetry.py +87 -0
  54. dapps-0.1.0/.github/workflows/publish-docker-image.yml +0 -54
  55. dapps-0.1.0/MANIFEST.in +0 -5
  56. dapps-0.1.0/VERSION +0 -1
  57. dapps-0.1.0/docker/Dockerfile-oai-dapp +0 -130
  58. dapps-0.1.0/docker/Dockerfile-uhd-ubuntu24 +0 -63
  59. dapps-0.1.0/pyproject.toml +0 -72
  60. dapps-0.1.0/src/iq_saver/iq_saver.py +0 -503
  61. dapps-0.1.0/src/simple/defs/e3sm_simple.asn +0 -54
  62. dapps-0.1.0/src/spectrum/defs/e3sm_spectrum.asn +0 -76
  63. dapps-0.1.0/src/spectrum/defs/e3sm_spectrum.json +0 -137
  64. dapps-0.1.0/src/spectrum/spectrum_dapp.py +0 -743
  65. dapps-0.1.0/src/visualization/dashboard.py +0 -185
  66. dapps-0.1.0/src/visualization/templates/index.html +0 -508
  67. dapps-0.1.0/tests/test_dashboard.py +0 -43
  68. {dapps-0.1.0 → dapps-0.2.0}/.github/CODEOWNERS +0 -0
  69. {dapps-0.1.0 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  70. {dapps-0.1.0 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  71. {dapps-0.1.0 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/documentation.yml +0 -0
  72. {dapps-0.1.0 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  73. {dapps-0.1.0 → dapps-0.2.0}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  74. {dapps-0.1.0 → dapps-0.2.0}/.github/workflows/mirror.yml +0 -0
  75. {dapps-0.1.0 → dapps-0.2.0}/.gitignore +0 -0
  76. {dapps-0.1.0 → dapps-0.2.0}/CONTRIBUTORS +0 -0
  77. {dapps-0.1.0 → dapps-0.2.0}/LICENSE +0 -0
  78. {dapps-0.1.0 → dapps-0.2.0}/SECURITY.md +0 -0
  79. {dapps-0.1.0 → dapps-0.2.0}/docs/dapp_uml.txt +0 -0
  80. {dapps-0.1.0 → dapps-0.2.0}/docs/gui_uml.txt +0 -0
  81. {dapps-0.1.0 → dapps-0.2.0}/examples/nvidia_dapp.py +0 -0
  82. {dapps-0.1.0 → dapps-0.2.0}/examples/simple_dapp.py +0 -0
  83. {dapps-0.1.0 → dapps-0.2.0}/src/dapp/__init__.py +0 -0
  84. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/__init__.py +0 -0
  85. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/defs/e3.asn +0 -0
  86. {dapps-0.1.0 → dapps-0.2.0}/src/e3interface/e3_logging.py +0 -0
  87. {dapps-0.1.0 → dapps-0.2.0}/src/iq_saver/__init__.py +0 -0
  88. {dapps-0.1.0 → dapps-0.2.0}/src/nvidia/__init__.py +0 -0
  89. {dapps-0.1.0 → dapps-0.2.0}/src/nvidia/defs/e3sm_nvidia.asn +0 -0
  90. {dapps-0.1.0 → dapps-0.2.0}/src/nvidia/defs/e3sm_nvidia.json +0 -0
  91. {dapps-0.1.0 → dapps-0.2.0}/src/nvidia/nvidia_dapp.py +0 -0
  92. {dapps-0.1.0 → dapps-0.2.0}/src/simple/__init__.py +0 -0
  93. {dapps-0.1.0 → dapps-0.2.0}/src/spectrum/__init__.py +0 -0
  94. {dapps-0.1.0 → dapps-0.2.0}/src/spectrum/embargo_manager.py +0 -0
  95. {dapps-0.1.0 → dapps-0.2.0}/src/visualization/__init__.py +0 -0
  96. {dapps-0.1.0 → dapps-0.2.0}/src/visualization/energy.py +0 -0
  97. {dapps-0.1.0 → dapps-0.2.0}/src/visualization/grafana.py +0 -0
  98. {dapps-0.1.0 → dapps-0.2.0}/src/visualization/influx.py +0 -0
  99. {dapps-0.1.0 → dapps-0.2.0}/src/visualization/iq.py +0 -0
  100. {dapps-0.1.0 → dapps-0.2.0}/tools/README.md +0 -0
  101. {dapps-0.1.0 → dapps-0.2.0}/tools/asntojson.py +0 -0
  102. {dapps-0.1.0 → dapps-0.2.0}/tools/generate_sm.sh +0 -0
  103. {dapps-0.1.0 → dapps-0.2.0}/tools/read_recording.py +0 -0
@@ -77,7 +77,7 @@ body:
77
77
  id: paired_issue
78
78
  attributes:
79
79
  label: Paired libe3 issue / PR (if any)
80
- placeholder: "wineslab/dApp-libe3#123"
80
+ placeholder: "wineslab/libe3#123"
81
81
  validations:
82
82
  required: false
83
83
 
@@ -30,8 +30,8 @@ Closes #
30
30
 
31
31
  These mirror what CI (`.github/workflows/test.yml`) enforces. **All boxes must be ticked before review.**
32
32
 
33
- - [ ] `hatch run pytest tests/ -v` passes locally on Python 3.12
34
- - [ ] `hatch build` succeeds (verifies packaging metadata)
33
+ - [ ] `pytest tests/ -v` passes locally on Python 3.12 (after `pip install -e ".[all]"`)
34
+ - [ ] `python -m build` succeeds (verifies packaging metadata and the C++/SWIG build)
35
35
  - [ ] `VERSION` file bumped per [SemVer](https://semver.org/) if the public API or wire protocol changed
36
36
  - [ ] `README.md` updated if interface, environment variables, or workflow changed
37
37
  - [ ] `CONTRIBUTING.md` updated if contributor-facing rules changed
@@ -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/dApp-libe3) and [`dApp-openairinterface5g`](https://github.com/wineslab/dApp-openairinterface5g). Breaking changes to the twin repos are not accepted.
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
 
@@ -63,8 +63,14 @@ jobs:
63
63
  python-version: "3.8"
64
64
  - name: Install pypa/build
65
65
  run: python3 -m pip install build --user
66
- - name: Build a binary wheel and a source tarball
67
- run: python3 -m build
66
+ - name: Build a source tarball (sdist)
67
+ # sdist only: the compiled extension produces a non-portable
68
+ # linux_x86_64 wheel that PyPI rejects. `pip install dapps` builds the
69
+ # sdist on the user's machine; scikit-build-core + FetchContent pull
70
+ # cmake/swig/ninja/nlohmann automatically, so no system prereqs are
71
+ # required. Building the sdist does not compile anything, so this job
72
+ # needs no C++ toolchain or nlohmann/json either.
73
+ run: python3 -m build --sdist
68
74
  - name: Store the distribution packages
69
75
  uses: actions/upload-artifact@v4
70
76
  with:
@@ -113,7 +119,6 @@ jobs:
113
119
  with:
114
120
  inputs: |
115
121
  ./dist/*.tar.gz
116
- ./dist/*.whl
117
122
  - name: Create GitHub Release
118
123
  env:
119
124
  GITHUB_TOKEN: ${{ github.token }}
@@ -17,8 +17,13 @@ jobs:
17
17
  with:
18
18
  python-version: "3.12"
19
19
 
20
- - name: Install hatch
21
- run: pip install hatch
20
+ - name: Install build prerequisites
21
+ run: |
22
+ sudo apt-get update
23
+ sudo apt-get install -y cmake swig nlohmann-json3-dev
24
+
25
+ - name: Install package (editable, with test deps)
26
+ run: pip install -e ".[all]" pytest
22
27
 
23
28
  - name: Run tests
24
- run: hatch run pytest tests/ -v
29
+ run: pytest tests/ -v
@@ -0,0 +1,22 @@
1
+ # Root CMake — drives the Python wheel build via scikit-build-core.
2
+ #
3
+ # scikit-build-core invokes this file when `pip install .` / `python -m
4
+ # build` is run. We turn on SWIG and direct the SWIG outputs into the
5
+ # wheel's `iq_saver/` package directory. The standalone C++ install of
6
+ # libiqsaver (pkg-config, headers under /usr/local) is driven directly
7
+ # from the libiqsaver/ subdirectory CMake; see libiqsaver/CMakeLists.txt.
8
+ #
9
+ # SPDX-License-Identifier: Apache-2.0
10
+
11
+ cmake_minimum_required(VERSION 3.16)
12
+
13
+ # Tell libiqsaver/CMakeLists.txt to: (1) build SWIG bindings, (2) skip
14
+ # the system-wide install logic, (3) install SWIG outputs into the
15
+ # wheel's iq_saver/ package directory.
16
+ set(LIBIQSAVER_ENABLE_SWIG ON CACHE BOOL "" FORCE)
17
+ set(LIBIQSAVER_BUILD_TESTS OFF CACHE BOOL "" FORCE)
18
+ set(LIBIQSAVER_PY_INSTALL_DIR "iq_saver" CACHE STRING "" FORCE)
19
+
20
+ project(iqsaver_pywheel LANGUAGES CXX)
21
+
22
+ add_subdirectory(libiqsaver)
@@ -49,16 +49,16 @@ The following are **mandatory** for every contribution. PRs that do not meet the
49
49
 
50
50
  - All PRs must use `.github/PULL_REQUEST_TEMPLATE.md` and complete every checklist item.
51
51
  - The following CI workflow must be green on the latest commit before review:
52
- - **`Run tests`** (`.github/workflows/test.yml`) — runs `hatch run pytest tests/ -v` on Python 3.12.
52
+ - **`Run tests`** (`.github/workflows/test.yml`) — installs the package via `pip install -e ".[all]"` and runs `pytest tests/ -v` on Python 3.12.
53
53
  - The following local checks must be reported in the PR description as run by the contributor (mirroring CI):
54
- - `hatch run pytest tests/ -v` passes.
55
- - `hatch build` succeeds.
54
+ - `pytest tests/ -v` passes (after `pip install -e ".[all]"`).
55
+ - `python -m build` succeeds (the build backend is scikit-build-core; this also verifies the C++/SWIG extension compiles).
56
56
  - `VERSION` file bumped per [SemVer](https://semver.org/) when public API or wire protocol changes.
57
57
  - `README.md` and/or `CONTRIBUTING.md` updated when contributor- or user-facing behavior changes.
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/dApp-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.
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
 
@@ -0,0 +1,6 @@
1
+ include VERSION
2
+ include README.md
3
+ include LICENSE
4
+ include CMakeLists.txt
5
+ recursive-include libiqsaver *
6
+ recursive-include src/visualization/templates *
@@ -1,44 +1,46 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dapps
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A library for creating and connecting dApps to Open RAN units.
5
- Project-URL: Homepage, https://openrangym.com/o-ran-frameworks/dapps
6
- Project-URL: Package, https://pypi.org/project/dapps/
7
- Project-URL: Source, https://github.com/wineslab/dApp-library
8
- Project-URL: Bug Tracker, https://github.com/wineslab/dApp-library/issues
9
- Author-email: Andrea Lacava <thecave003@gmail.com>
5
+ Keywords: dApps,O-RAN,5G,cellular,networks
6
+ Author-Email: Andrea Lacava <thecave003@gmail.com>
10
7
  License-Expression: Apache-2.0
11
8
  License-File: LICENSE
12
- Keywords: 5G,O-RAN,cellular,dApps,networks
13
9
  Classifier: Development Status :: 4 - Beta
14
10
  Classifier: Programming Language :: Python
15
11
  Classifier: Programming Language :: Python :: 3.12
16
12
  Classifier: Programming Language :: Python :: Implementation :: CPython
17
13
  Classifier: Programming Language :: Python :: Implementation :: PyPy
14
+ Project-URL: Homepage, https://openrangym.com/o-ran-frameworks/dapps
15
+ Project-URL: Package, https://pypi.org/project/dapps/
16
+ Project-URL: Source, https://github.com/wineslab/dApp-library
17
+ Project-URL: Bug Tracker, https://github.com/wineslab/dApp-library/issues
18
18
  Requires-Python: >=3.12
19
19
  Requires-Dist: asn1tools
20
- Requires-Dist: jsonschema>=4.18.0
20
+ Requires-Dist: typing_extensions
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: pyzmq
23
23
  Requires-Dist: sigmf
24
- Requires-Dist: typing-extensions
25
- Provides-Extra: all
26
- Requires-Dist: flask; extra == 'all'
27
- Requires-Dist: flask-socketio; extra == 'all'
28
- Requires-Dist: influxdb-client; extra == 'all'
29
- Requires-Dist: libiq; extra == 'all'
30
- Requires-Dist: matplotlib; extra == 'all'
31
- Requires-Dist: pysctp; extra == 'all'
24
+ Requires-Dist: jsonschema>=4.18.0
32
25
  Provides-Extra: api
33
- Requires-Dist: influxdb-client; extra == 'api'
26
+ Requires-Dist: influxdb_client; extra == "api"
34
27
  Provides-Extra: cnn
35
- Requires-Dist: libiq; extra == 'cnn'
28
+ Requires-Dist: libiq; extra == "cnn"
36
29
  Provides-Extra: gui
37
- Requires-Dist: flask; extra == 'gui'
38
- Requires-Dist: flask-socketio; extra == 'gui'
39
- Requires-Dist: matplotlib; extra == 'gui'
30
+ Requires-Dist: flask; extra == "gui"
31
+ Requires-Dist: flask-sock; extra == "gui"
32
+ Requires-Dist: flask-cors; extra == "gui"
33
+ Requires-Dist: matplotlib; extra == "gui"
40
34
  Provides-Extra: network
41
- Requires-Dist: pysctp; extra == 'network'
35
+ Requires-Dist: pysctp; extra == "network"
36
+ Provides-Extra: all
37
+ Requires-Dist: pysctp; extra == "all"
38
+ Requires-Dist: flask; extra == "all"
39
+ Requires-Dist: flask-sock; extra == "all"
40
+ Requires-Dist: flask-cors; extra == "all"
41
+ Requires-Dist: matplotlib; extra == "all"
42
+ Requires-Dist: influxdb_client; extra == "all"
43
+ Requires-Dist: libiq; extra == "all"
42
44
  Description-Content-Type: text/markdown
43
45
 
44
46
  # dApp Library
@@ -60,10 +62,41 @@ The dApp can be found in the `examples` directory or it can be downloaded from t
60
62
  Clone the repository and install it manually with:
61
63
 
62
64
  ```
63
- hatch build
65
+ pip3 install -e ".[all]"
66
+ ```
67
+
68
+ Building a distributable sdist / wheel:
69
+
70
+ ```
71
+ pip install build
72
+ python -m build # produces dist/*.tar.gz and dist/*.whl
64
73
  pip3 install "dist/*.tar.gz[all]"
65
74
  ```
66
75
 
76
+ The wheel build uses [scikit-build-core](https://scikit-build-core.readthedocs.io/) to compile the bundled C++ extension (see *libiqsaver* below). System prerequisites: a C++17 compiler, CMake ≥ 3.16, SWIG ≥ 4.0, and [`nlohmann/json`](https://github.com/nlohmann/json) (≥ 3.11). On Debian/Ubuntu: `apt install cmake swig nlohmann-json3-dev`.
77
+
78
+ ### libiqsaver — standalone C++ library
79
+
80
+ The `iq_saver` Python package wraps **libiqsaver**, a SigMF-compliant IQ-sample writer written in C++. The same library is also usable directly from C++ projects. To install it system-wide:
81
+
82
+ ```
83
+ cd libiqsaver
84
+ mkdir build && cd build
85
+ cmake .. -DCMAKE_BUILD_TYPE=Release
86
+ sudo make install
87
+ ```
88
+
89
+ After install:
90
+
91
+ ```
92
+ pkg-config --cflags --libs libiqsaver
93
+ # or, in CMake:
94
+ find_package(libiqsaver REQUIRED)
95
+ target_link_libraries(my_app PRIVATE iqsaver::iqsaver)
96
+ ```
97
+
98
+ The standalone install (headers under `${CMAKE_INSTALL_PREFIX}/include/iqsaver`, library and pkg-config under `${CMAKE_INSTALL_LIBDIR}`) is independent of `pip install dapps` — both can coexist on the same machine.
99
+
67
100
  ### Launch the Spectrum Sharing dApp example
68
101
 
69
102
  OAI should start _before_ running the dApp
@@ -17,10 +17,41 @@ The dApp can be found in the `examples` directory or it can be downloaded from t
17
17
  Clone the repository and install it manually with:
18
18
 
19
19
  ```
20
- hatch build
20
+ pip3 install -e ".[all]"
21
+ ```
22
+
23
+ Building a distributable sdist / wheel:
24
+
25
+ ```
26
+ pip install build
27
+ python -m build # produces dist/*.tar.gz and dist/*.whl
21
28
  pip3 install "dist/*.tar.gz[all]"
22
29
  ```
23
30
 
31
+ The wheel build uses [scikit-build-core](https://scikit-build-core.readthedocs.io/) to compile the bundled C++ extension (see *libiqsaver* below). System prerequisites: a C++17 compiler, CMake ≥ 3.16, SWIG ≥ 4.0, and [`nlohmann/json`](https://github.com/nlohmann/json) (≥ 3.11). On Debian/Ubuntu: `apt install cmake swig nlohmann-json3-dev`.
32
+
33
+ ### libiqsaver — standalone C++ library
34
+
35
+ The `iq_saver` Python package wraps **libiqsaver**, a SigMF-compliant IQ-sample writer written in C++. The same library is also usable directly from C++ projects. To install it system-wide:
36
+
37
+ ```
38
+ cd libiqsaver
39
+ mkdir build && cd build
40
+ cmake .. -DCMAKE_BUILD_TYPE=Release
41
+ sudo make install
42
+ ```
43
+
44
+ After install:
45
+
46
+ ```
47
+ pkg-config --cflags --libs libiqsaver
48
+ # or, in CMake:
49
+ find_package(libiqsaver REQUIRED)
50
+ target_link_libraries(my_app PRIVATE iqsaver::iqsaver)
51
+ ```
52
+
53
+ The standalone install (headers under `${CMAKE_INSTALL_PREFIX}/include/iqsaver`, library and pkg-config under `${CMAKE_INSTALL_LIBDIR}`) is independent of `pip install dapps` — both can coexist on the same machine.
54
+
24
55
  ### Launch the Spectrum Sharing dApp example
25
56
 
26
57
  OAI should start _before_ running the dApp
dapps-0.2.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -63,7 +63,7 @@ The detector flags any PRB whose power exceeds a fixed absolute threshold.
63
63
 
64
64
  **Interactive:**
65
65
  ```bash
66
- hatch run python examples/spectrum_dapp.py \
66
+ python3 examples/spectrum_dapp.py \
67
67
  --link zmq --transport ipc \
68
68
  --save-iqs \
69
69
  --control \
@@ -77,7 +77,7 @@ hatch run python examples/spectrum_dapp.py \
77
77
 
78
78
  **CI/CD:**
79
79
  ```bash
80
- hatch run python examples/spectrum_dapp.py \
80
+ python3 examples/spectrum_dapp.py \
81
81
  --link zmq --transport ipc \
82
82
  --save-iqs \
83
83
  --control \
@@ -96,7 +96,7 @@ after the last detection before being released.
96
96
 
97
97
  **Interactive:**
98
98
  ```bash
99
- hatch run python examples/spectrum_dapp.py \
99
+ python3 examples/spectrum_dapp.py \
100
100
  --link zmq --transport ipc \
101
101
  --save-iqs \
102
102
  --control \
@@ -112,7 +112,7 @@ hatch run python examples/spectrum_dapp.py \
112
112
 
113
113
  **CI/CD:**
114
114
  ```bash
115
- hatch run python examples/spectrum_dapp.py \
115
+ python3 examples/spectrum_dapp.py \
116
116
  --link zmq --transport ipc \
117
117
  --save-iqs \
118
118
  --control \
@@ -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 SpectrumSharingDApp, compute_fft_size
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
- try:
27
- from libiq.classifier.cnn import Classifier
28
- except ModuleNotFoundError:
29
- print(
30
- "Optional dependencies to run this example are not installed.\n"
31
- "Fix this by running:\n\n"
32
- " pip install libiq # OR\n"
33
- " pip install dapps[cnn] (minimal) # OR\n"
34
- " pip install dapps[all] (preferred)\n"
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
- classifier=classifier,
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 blacklist control messages to the gNB")
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="Enable dashboard visualization")
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('--show-controls', action='store_true', default=False,
214
- help="Show tunable controls (sampling threshold) in the dashboard GUI. "
215
- "Only meaningful with --demo-gui.")
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")