diff --git a/dist/gain_viz-0.1.0-py3-none-any.whl b/dist/gain_viz-0.1.0-py3-none-any.whl new file mode 100644 index 0000000..2ab6460 Binary files /dev/null and b/dist/gain_viz-0.1.0-py3-none-any.whl differ diff --git a/dist/gain_viz-0.1.0.tar.gz b/dist/gain_viz-0.1.0.tar.gz new file mode 100644 index 0000000..ff0c3dc Binary files /dev/null and b/dist/gain_viz-0.1.0.tar.gz differ diff --git a/gain_viz.egg-info/PKG-INFO b/gain_viz.egg-info/PKG-INFO new file mode 100644 index 0000000..a008da6 --- /dev/null +++ b/gain_viz.egg-info/PKG-INFO @@ -0,0 +1,72 @@ +Metadata-Version: 2.4 +Name: gain_viz +Version: 0.1.0 +Summary: Interactive srsRAN_Project gnb gain control and spectrum visualization tool +Author-email: "Qoherent Inc." +Maintainer-email: Gael Kamga , Ashkan Beigi +Keywords: radio,rf,sdr,software-defined radio,5G,gnb,gNodeB,srsRAN_Project,SCM,SignalCraft Conditioning Mpdule,USRP +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Requires-Dist: flask +Requires-Dist: matplotlib +Requires-Dist: numpy +Requires-Dist: pyzmq +Requires-Dist: pyserial + +# gain_viz + +![Python](https://img.shields.io/badge/python-3.8%2B-blue) +![Flask](https://img.shields.io/badge/flask-2.x-orange) + +# gain_viz + +**gain_viz** is a Python-based web application for adjusting RF gain settings and visualizing their effect in real-time. It integrates with USRP and SCM devices, providing live IQ time-series and spectrum visualization. + +--- + +## Features + +- Adjust **USRP Tx/Rx gains** and **SCM Tx/Rx gains** from a web interface. +- Live IQ **time-series plot** in milliseconds. +- Live **spectrum visualization** (waterfall / spectrogram). +- Fast refresh for near real-time feedback. +- Responsive and clean web interface built with HTML/CSS/JS. + + + +## Installation + +Make sure you have **Python 3.8+** installed. + +1. Clone the repository: + +```bash +git clone https://riahub.ai/gael/gain-viz.git +cd gain-viz +``` + +2. Build and install + +```bash +pip install --upgrade build +python3 -m build +pip install dist/gain_viz-0.1.0-py3-none-any.whl +export PATH=$PATH:~/.local/bin +source ~/.bashrc + + +``` + +## Usage + +Run the application +```bash +gain_viz +``` + +Open your browser at http://localhost:5000 + + +- Toggle the gain switches to enable input fields. +- Enter new gain values and press Update Gains. +- Observe the effect on the time-domain IQ plot and spectrum. diff --git a/gain_viz.egg-info/SOURCES.txt b/gain_viz.egg-info/SOURCES.txt new file mode 100644 index 0000000..2d96d4d --- /dev/null +++ b/gain_viz.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +README.md +pyproject.toml +gain_viz/__init__.py +gain_viz/app.py +gain_viz.egg-info/PKG-INFO +gain_viz.egg-info/SOURCES.txt +gain_viz.egg-info/dependency_links.txt +gain_viz.egg-info/entry_points.txt +gain_viz.egg-info/requires.txt +gain_viz.egg-info/top_level.txt +gain_viz/static/plot.png +gain_viz/templates/index.html \ No newline at end of file diff --git a/gain_viz.egg-info/dependency_links.txt b/gain_viz.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/gain_viz.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/gain_viz.egg-info/entry_points.txt b/gain_viz.egg-info/entry_points.txt new file mode 100644 index 0000000..609cae1 --- /dev/null +++ b/gain_viz.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +gain_viz = gain_viz.app:main diff --git a/gain_viz.egg-info/requires.txt b/gain_viz.egg-info/requires.txt new file mode 100644 index 0000000..00540fd --- /dev/null +++ b/gain_viz.egg-info/requires.txt @@ -0,0 +1,5 @@ +flask +matplotlib +numpy +pyzmq +pyserial diff --git a/gain_viz.egg-info/top_level.txt b/gain_viz.egg-info/top_level.txt new file mode 100644 index 0000000..8c59485 --- /dev/null +++ b/gain_viz.egg-info/top_level.txt @@ -0,0 +1 @@ +gain_viz diff --git a/gain_viz.json b/gain_viz.json new file mode 100644 index 0000000..dec5585 --- /dev/null +++ b/gain_viz.json @@ -0,0 +1,12 @@ +{ + "usrp_tx_gain": 20.0, + "usrp_rx_gain": 10.0, + "scm_tx_gain": 30, + "scm_rx_gain": 30, + "sample_rate": 46040000.0, + "window_ms": 10.0, + "center_freq": 3425000000.0, + "NFFT": 1024, + "tcp_port": 5556, + "streaming": true +} \ No newline at end of file diff --git a/gain_viz/app.py b/gain_viz/app.py index 68bf12a..a3e978f 100644 --- a/gain_viz/app.py +++ b/gain_viz/app.py @@ -8,6 +8,7 @@ import threading import time import serial import json +import subprocess app = Flask(__name__) PLOT_PATH = os.path.join(os.getcwd(), "plot.png") @@ -38,6 +39,12 @@ plot_thread = None stop_event = threading.Event() pause_event = threading.Event() +# TMUX output capture +tmux_output = [] +tmux_lock = threading.Lock() +tmux_thread = None +tmux_stop_event = threading.Event() + # ----------------- Serial / SCM ----------------- def connect_serial(port, baudrate=115200, timeout=1): """Connect to a serial port with even parity.""" @@ -89,6 +96,60 @@ def scm_conf(port, baudrate, rx_cmd, tx_cmd): return True return False +# ----------------- TMUX Output Capture ----------------- +def capture_tmux_output(): + """Capture tmux output in a separate thread""" + while not tmux_stop_event.is_set(): + try: + # First check if the tmux session exists + check_cmd = "tmux has-session -t ran 2>/dev/null" + result = subprocess.run(check_cmd, shell=True, capture_output=True) + + if result.returncode == 0: + # Capture tmux output + cmd = "tmux capture-pane -t ran -p" + output = subprocess.check_output(cmd, shell=True, text=True) + + with tmux_lock: + # Keep only the last 100 lines to avoid memory issues + lines = output.split('\n') + tmux_output[:] = lines[-100:] if len(lines) > 100 else lines + else: + with tmux_lock: + tmux_output[:] = ["TMUX session 'ran' not found. Please start the RAN application."] + + time.sleep(1) # Update every second + except Exception as e: + print(f"Error capturing tmux output: {e}") + with tmux_lock: + tmux_output[:] = [f"Error capturing tmux output: {str(e)}"] + time.sleep(5) # Wait longer if there's an error + +def start_tmux_capture(): + """Start the tmux capture thread""" + global tmux_thread + + tmux_stop_event.clear() + + if tmux_thread is None or not tmux_thread.is_alive(): + tmux_thread = threading.Thread(target=capture_tmux_output, daemon=True) + tmux_thread.start() + print("TMUX capture thread started") + + return True + +def stop_tmux_capture(): + """Stop the tmux capture thread""" + global tmux_thread + + tmux_stop_event.set() + + if tmux_thread and tmux_thread.is_alive(): + tmux_thread.join(timeout=2.0) + + print("TMUX capture thread stopped") + return True + # ----------------- Gain Updates ----------------- def gain_update(usrp_tx, usrp_rx, scm_tx, scm_rx): global usrp_tx_gain, usrp_rx_gain, scm_tx_gain, scm_rx_gain @@ -391,6 +452,7 @@ def start_stream(): try: success = start_plotting() if success: + start_tmux_capture() # Start capturing tmux output return jsonify({"status": "success", "message": "Streaming started"}) else: return jsonify({"status": "error", "message": "Failed to start streaming"}), 500 @@ -402,6 +464,7 @@ def stop_stream(): try: success = stop_plotting() if success: + stop_tmux_capture() # Stop capturing tmux output return jsonify({"status": "success", "message": "Streaming stopped"}) else: return jsonify({"status": "error", "message": "Failed to stop streaming"}), 500 @@ -430,6 +493,12 @@ def get_stream_state(): return jsonify({"state": state}) +@app.route('/tmux_output', methods=['GET']) +def get_tmux_output(): + """Return the captured tmux output""" + with tmux_lock: + return jsonify({"output": tmux_output}) + def save_config(): with config_lock: cfg = dict(config) diff --git a/gain_viz/plot.png b/gain_viz/plot.png new file mode 100644 index 0000000..bbec9b3 Binary files /dev/null and b/gain_viz/plot.png differ diff --git a/gain_viz/templates/index.html b/gain_viz/templates/index.html index 73f3cc2..e03e8fd 100644 --- a/gain_viz/templates/index.html +++ b/gain_viz/templates/index.html @@ -20,6 +20,8 @@ --success: #10b981; --warning: #f59e0b; --error: #ef4444; + --terminal-bg: #1e293b; + --terminal-text: #e2e8f0; } * { @@ -38,7 +40,7 @@ } .container { - max-width: 1400px; + max-width: 1600px; margin: 0 auto; background: var(--card); border-radius: var(--radius); @@ -378,21 +380,28 @@ border: 1px solid #fed7aa; } - /* Plot area */ + /* Plot area - OPTIMIZED FOR MAXIMUM SPACE */ + .visualization-panel { + display: flex; + flex-direction: column; + height: 100%; + } + .plot-area { flex: 1; - padding: 24px; + min-height: 0; + padding: 16px; /* Reduced padding */ background: #f8fafc; display: flex; flex-direction: column; - overflow: hidden; } .plot-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 16px; + margin-bottom: 12px; /* Reduced margin */ + flex-shrink: 0; } .plot-header h3 { @@ -416,25 +425,104 @@ } .plot-card { + flex: 1; + min-height: 0; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); - padding: 16px; - flex: 1; + padding: 4px; /* Minimal padding */ display: flex; flex-direction: column; overflow: hidden; box-shadow: var(--shadow); + position: relative; } #plotImage { + position: absolute; + top: 0; + left: 0; width: 100%; height: 100%; object-fit: contain; border-radius: 8px; display: block; background: #f8fafc; - border: 1px solid var(--border); + } + + /* TMUX Terminal - COMPACT */ + .tmux-container { + height: 150px; /* Further reduced */ + margin-top: 8px; /* Minimal margin */ + display: flex; + flex-direction: column; + flex-shrink: 0; + } + + .tmux-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 10px; /* Minimal padding */ + background: var(--terminal-bg); + color: var(--terminal-text); + border-top-left-radius: var(--radius); + border-top-right-radius: var(--radius); + font-size: 0.8rem; /* Smaller font */ + font-weight: 500; + flex-shrink: 0; + } + + .tmux-controls { + display: flex; + gap: 4px; /* Minimal gap */ + } + + .tmux-btn { + background: transparent; + border: none; + color: var(--terminal-text); + cursor: pointer; + padding: 2px 4px; /* Minimal padding */ + border-radius: 3px; + font-size: 0.7rem; /* Smaller font */ + } + + .tmux-btn:hover { + background: rgba(255, 255, 255, 0.1); + } + + .tmux-terminal { + flex: 1; + background: var(--terminal-bg); + color: var(--terminal-text); + font-family: 'Courier New', monospace; + font-size: 0.75rem; /* Smaller font */ + padding: 6px; /* Minimal padding */ + overflow-y: auto; + border-bottom-left-radius: var(--radius); + border-bottom-right-radius: var(--radius); + white-space: pre-wrap; + word-wrap: break-word; + line-height: 1.2; /* Tighter line height */ + } + + .tmux-terminal::-webkit-scrollbar { + width: 4px; /* Thinner scrollbar */ + } + + .tmux-terminal::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 2px; + } + + .tmux-terminal::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 2px; + } + + .tmux-terminal::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); } footer { @@ -478,7 +566,7 @@ } .controls-panel, .plot-area { - padding: 16px; + padding: 12px; } .gain-item { @@ -679,6 +767,18 @@
Spectrum Analysis plot
+ + +
+
+ TMUX Output (ran) +
+ + +
+
+
+
@@ -691,9 +791,13 @@ diff --git a/gain_viz/tmux_ran.log b/gain_viz/tmux_ran.log new file mode 100644 index 0000000..e69de29 diff --git a/plot.png b/plot.png new file mode 100644 index 0000000..ba9a0cf Binary files /dev/null and b/plot.png differ