Remove Iridium (demo-only) and add version display

- Remove Iridium satellite decoding feature (was placeholder/demo only)
  - Delete routes/iridium.py
  - Remove Iridium UI elements from index.html
  - Remove Iridium CSS styles
  - Remove Iridium dependencies and logger
  - Clean up SDR docstrings mentioning Iridium

- Add version number display (fixes #31)
  - Add VERSION constant to config.py
  - Add --version / -V CLI flag to intercept.py
  - Display version badge in web UI header
This commit is contained in:
Smittix
2026-01-05 08:17:08 +00:00
parent 06f82bb88f
commit 351e478dd8
16 changed files with 28 additions and 446 deletions

4
app.py
View File

@@ -25,6 +25,7 @@ from typing import Any
from flask import Flask, render_template, jsonify, send_file, Response, request
from config import VERSION
from utils.dependencies import check_tool, check_all_dependencies, TOOL_DEPENDENCIES
from utils.process import cleanup_stale_processes
from utils.sdr import SDRFactory
@@ -91,7 +92,6 @@ bt_services = {} # MAC -> list of services
adsb_aircraft = {} # ICAO hex -> aircraft info
# Satellite state
iridium_bursts = [] # List of detected Iridium bursts
satellite_passes = [] # Predicted satellite passes
@@ -107,7 +107,7 @@ def index() -> str:
'rtl_433': check_tool('rtl_433')
}
devices = [d.to_dict() for d in SDRFactory.detect_devices()]
return render_template('index.html', tools=tools, devices=devices)
return render_template('index.html', tools=tools, devices=devices, version=VERSION)
@app.route('/favicon.svg')

View File

@@ -6,6 +6,9 @@ import logging
import os
import sys
# Application version
VERSION = "1.0.0"
def _get_env(key: str, default: str) -> str:
"""Get environment variable with default."""
@@ -56,10 +59,6 @@ DEFAULT_DEVICE = _get_env('DEFAULT_DEVICE', '0')
# Pager defaults
DEFAULT_PAGER_FREQ = _get_env('PAGER_FREQ', '929.6125M')
# Iridium defaults
DEFAULT_IRIDIUM_FREQ = _get_env('IRIDIUM_FREQ', '1626.0')
DEFAULT_IRIDIUM_SAMPLE_RATE = _get_env('IRIDIUM_SAMPLE_RATE', '2.048e6')
# Timeouts
PROCESS_TIMEOUT = _get_env_int('PROCESS_TIMEOUT', 5)
SOCKET_TIMEOUT = _get_env_int('SOCKET_TIMEOUT', 5)
@@ -82,9 +81,6 @@ SATELLITE_UPDATE_INTERVAL = _get_env_int('SATELLITE_UPDATE_INTERVAL', 30)
SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30)
SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45)
# Maximum burst count for Iridium monitoring
IRIDIUM_MAX_BURSTS = _get_env_int('IRIDIUM_MAX_BURSTS', 100)
def configure_logging() -> None:
"""Configure application logging."""

View File

@@ -41,7 +41,6 @@ Complete feature list for all modules.
- **Celestrak integration** - fetch by category (Amateur, Weather, ISS, Starlink, etc.)
- **Next pass countdown** - time remaining, visibility duration, max elevation
- **Telemetry panel** - real-time azimuth, elevation, range, velocity
- **Iridium burst detection** monitoring (demo mode)
- **Multiple satellite tracking** simultaneously
## WiFi Reconnaissance

View File

@@ -90,7 +90,6 @@ The system highlights aircraft transmitting emergency squawks:
4. **View Sky Plot** - Polar plot shows satellite positions in real-time
5. **Ground Track** - Map displays satellite orbit path and current position
6. **Full Dashboard** - Click "Full Screen Dashboard" for dedicated satellite view
7. **Iridium Mode** - Switch tabs to monitor for Iridium burst transmissions
### Adding Satellites from Celestrak

View File

@@ -6,7 +6,7 @@ A comprehensive signal intelligence tool featuring:
- Pager decoding (POCSAG/FLEX)
- 433MHz sensor monitoring
- ADS-B aircraft tracking with WarGames-style display
- Satellite pass prediction and Iridium burst detection
- Satellite pass prediction
- WiFi reconnaissance and drone detection
- Bluetooth scanning
@@ -25,6 +25,12 @@ if sys.version_info < (3, 9):
print(" - Or use pyenv to install a newer version")
sys.exit(1)
# Handle --version early before other imports
if '--version' in sys.argv or '-V' in sys.argv:
from config import VERSION
print(f"INTERCEPT v{VERSION}")
sys.exit(0)
import site
# Ensure user site-packages is available (may be disabled when running as root/sudo)

View File

@@ -8,7 +8,6 @@ def register_blueprints(app):
from .bluetooth import bluetooth_bp
from .adsb import adsb_bp
from .satellite import satellite_bp
from .iridium import iridium_bp
from .gps import gps_bp
app.register_blueprint(pager_bp)
@@ -17,5 +16,4 @@ def register_blueprints(app):
app.register_blueprint(bluetooth_bp)
app.register_blueprint(adsb_bp)
app.register_blueprint(satellite_bp)
app.register_blueprint(iridium_bp)
app.register_blueprint(gps_bp)

View File

@@ -1,205 +0,0 @@
"""Iridium monitoring routes.
NOTE: This module is currently in DEMO MODE. The burst detection generates
simulated data for demonstration purposes. Real Iridium decoding requires
gr-iridium or iridium-toolkit which are not yet integrated.
"""
from __future__ import annotations
import json
import queue
import random
import shutil
import subprocess
import threading
import time
from datetime import datetime
from typing import Any, Generator
from flask import Blueprint, jsonify, request, Response
import app as app_module
from utils.logging import iridium_logger as logger
from utils.validation import validate_frequency, validate_device_index, validate_gain
from utils.sse import format_sse
from utils.sdr import SDRFactory, SDRType
iridium_bp = Blueprint('iridium', __name__, url_prefix='/iridium')
# Flag indicating this is demo mode (simulated data)
DEMO_MODE = True
def monitor_iridium(process):
"""
Monitor Iridium capture and detect bursts.
NOTE: Currently generates SIMULATED data for demonstration.
Real Iridium decoding is not yet implemented.
"""
try:
burst_count = 0
# Send initial demo mode warning
app_module.satellite_queue.put({
'type': 'info',
'message': '⚠️ DEMO MODE: Generating simulated Iridium bursts for demonstration'
})
while process.poll() is None:
data = process.stdout.read(1024)
if data:
if len(data) > 0 and burst_count < 100:
# DEMO: Generate simulated bursts (1% chance per read)
if random.random() < 0.01:
burst = {
'type': 'burst',
'demo': True, # Flag as demo data
'time': datetime.now().strftime('%H:%M:%S.%f')[:-3],
'frequency': f"{1616 + random.random() * 10:.3f}",
'data': f"[SIMULATED] Frame data - Burst #{burst_count + 1}"
}
app_module.satellite_queue.put(burst)
app_module.iridium_bursts.append(burst)
burst_count += 1
time.sleep(0.1)
except Exception as e:
logger.error(f"Monitor error: {e}")
@iridium_bp.route('/tools')
def check_iridium_tools():
"""Check for Iridium decoding tools."""
has_iridium = shutil.which('iridium-extractor') is not None or shutil.which('iridium-parser') is not None
has_rtl = shutil.which('rtl_fm') is not None
return jsonify({
'available': has_iridium or has_rtl,
'demo_mode': DEMO_MODE,
'message': 'Demo mode active - generating simulated data' if DEMO_MODE else None
})
@iridium_bp.route('/start', methods=['POST'])
def start_iridium():
"""Start Iridium burst capture (DEMO MODE - simulated data)."""
with app_module.satellite_lock:
if app_module.satellite_process and app_module.satellite_process.poll() is None:
return jsonify({'status': 'error', 'message': 'Iridium capture already running'}), 409
data = request.json or {}
# Validate inputs
try:
freq = validate_frequency(data.get('freq', '1626.0'), min_mhz=1610.0, max_mhz=1650.0)
gain = validate_gain(data.get('gain', '40'))
device = validate_device_index(data.get('device', '0'))
except ValueError as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
sample_rate = data.get('sampleRate', '2.048e6')
# Validate sample rate format
try:
float(sample_rate.replace('e', 'E'))
except (ValueError, AttributeError):
return jsonify({'status': 'error', 'message': 'Invalid sample rate format'}), 400
# Get SDR type from request
sdr_type_str = data.get('sdr_type', 'rtlsdr')
try:
sdr_type = SDRType(sdr_type_str)
except ValueError:
sdr_type = SDRType.RTL_SDR
# Check for required tools based on SDR type
if sdr_type == SDRType.RTL_SDR:
if not shutil.which('iridium-extractor') and not shutil.which('rtl_fm'):
return jsonify({
'status': 'error',
'message': 'Iridium tools not found. Requires rtl_fm or iridium-extractor.'
}), 503
else:
if not shutil.which('rx_fm'):
return jsonify({
'status': 'error',
'message': f'rx_fm not found for {sdr_type.value}. Install SoapySDR tools.'
}), 503
try:
# Create device object and build command via abstraction layer
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
builder = SDRFactory.get_builder(sdr_type)
# Parse sample rate
sample_rate_hz = int(float(sample_rate))
# Build FM demodulation command
cmd = builder.build_fm_demod_command(
device=sdr_device,
frequency_mhz=float(freq),
sample_rate=sample_rate_hz,
gain=float(gain),
ppm=None,
modulation='fm',
squelch=None
)
app_module.satellite_process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
thread = threading.Thread(target=monitor_iridium, args=(app_module.satellite_process,), daemon=True)
thread.start()
return jsonify({
'status': 'started',
'demo_mode': DEMO_MODE,
'message': 'Demo mode active - data is simulated' if DEMO_MODE else None
})
except FileNotFoundError as e:
logger.error(f"Tool not found: {e}")
return jsonify({'status': 'error', 'message': f'Tool not found: {e.filename}'}), 503
except Exception as e:
logger.error(f"Start error: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500
@iridium_bp.route('/stop', methods=['POST'])
def stop_iridium():
"""Stop Iridium capture."""
with app_module.satellite_lock:
if app_module.satellite_process:
app_module.satellite_process.terminate()
try:
app_module.satellite_process.wait(timeout=5)
except subprocess.TimeoutExpired:
app_module.satellite_process.kill()
app_module.satellite_process = None
return jsonify({'status': 'stopped'})
@iridium_bp.route('/stream')
def stream_iridium():
"""SSE stream for Iridium bursts."""
def generate():
last_keepalive = time.time()
keepalive_interval = 30.0
while True:
try:
msg = app_module.satellite_queue.get(timeout=1)
last_keepalive = time.time()
yield format_sse(msg)
except queue.Empty:
now = time.time()
if now - last_keepalive >= keepalive_interval:
yield format_sse({'type': 'keepalive'})
last_keepalive = now
response = Response(generate(), mimetype='text/event-stream')
response.headers['Cache-Control'] = 'no-cache'
response.headers['X-Accel-Buffering'] = 'no'
return response

View File

@@ -96,6 +96,16 @@ header h1 {
text-shadow: 0 0 30px var(--accent-cyan-dim);
}
.version-badge {
font-size: 0.35em;
font-weight: 400;
letter-spacing: 1px;
color: var(--text-muted);
vertical-align: middle;
margin-left: 5px;
opacity: 0.7;
}
header p {
color: var(--text-secondary);
font-size: 14px;
@@ -2059,46 +2069,6 @@ header p {
color: var(--accent-cyan);
}
/* Iridium Burst Styles */
.iridium-warning {
padding: 12px;
margin-bottom: 15px;
background: rgba(255, 102, 0, 0.1);
border: 1px solid var(--accent-orange);
border-radius: 4px;
color: var(--accent-orange);
font-size: 12px;
}
.iridium-warning strong {
display: block;
margin-bottom: 4px;
}
.burst-card {
padding: 10px;
margin-bottom: 6px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-left: 3px solid #9370DB;
font-family: monospace;
font-size: 11px;
}
.burst-time {
color: var(--text-secondary);
}
.burst-freq {
color: #9370DB;
font-weight: 600;
}
.burst-data {
color: var(--text-primary);
word-break: break-all;
}
/* Popout window styles */
.popout-container {
position: fixed;

View File

@@ -101,7 +101,7 @@
<path d="M50 88 L45 83 L50 83 Z" fill="#00d4ff"/>
</svg>
</div>
<h1>INTERCEPT</h1>
<h1>INTERCEPT <span class="version-badge">v{{ version }}</span></h1>
<p>Signal Intelligence // by smittix <span class="active-mode-indicator" id="activeModeIndicator"><span class="pulse-dot"></span>PAGER</span></p>
<!-- Header Stats (mode-specific) -->
@@ -814,8 +814,7 @@
<div id="satelliteMode" class="mode-content">
<a href="/satellite/dashboard" target="_blank" class="run-btn" style="display: block; text-align: center; text-decoration: none; margin-bottom: 15px;">Full Screen Dashboard</a>
<div class="satellite-tabs">
<button class="satellite-tab active" onclick="switchSatelliteTab('predictor')">🛰️ Pass Predictor</button>
<button class="satellite-tab" onclick="switchSatelliteTab('iridium')">📡 Iridium</button>
<button class="satellite-tab active">🛰️ Pass Predictor</button>
</div>
<!-- Pass Predictor Sub-tab -->
@@ -919,47 +918,6 @@
</button>
</div>
<!-- Iridium Sub-tab -->
<div id="iridiumTab" class="satellite-content">
<div class="iridium-warning">
<strong>⚠️ Hardware Required</strong>
Iridium burst detection requires:<br>
• RTL-SDR dongle<br>
• L-band patch antenna (1616-1626 MHz)<br>
• Low Noise Amplifier (LNA)<br>
• Clear view of sky
</div>
<div class="section">
<h3>Iridium Settings</h3>
<div class="form-group">
<label>Center Frequency (MHz)</label>
<input type="text" id="iridiumFreq" value="1626.0" placeholder="1626.0">
</div>
<div class="form-group">
<label>Gain (dB)</label>
<input type="text" id="iridiumGain" value="40" placeholder="40">
</div>
<div class="form-group">
<label>Sample Rate</label>
<select id="iridiumSampleRate">
<option value="2.4e6">2.4 MSPS</option>
<option value="2.048e6" selected>2.048 MSPS</option>
</select>
</div>
</div>
<div class="info-text" style="margin-bottom: 10px;" id="iridiumToolStatus">
<span>iridium-extractor:</span> <span class="tool-status" id="iridiumExtractorStatus">Checking...</span>
</div>
<button class="run-btn" id="startIridiumBtn" onclick="startIridiumCapture()">
Start Capture
</button>
<button class="stop-btn" id="stopIridiumBtn" onclick="stopIridiumCapture()" style="display: none;">
Stop Capture
</button>
</div>
</div>
<button class="preset-btn" onclick="killAll()" style="width: 100%; margin-top: 10px; border-color: #ff3366; color: #ff3366;">
@@ -1005,7 +963,6 @@
</div>
<div class="stats" id="satelliteStats" style="display: none;">
<div title="Upcoming Passes">🛰️ <span id="passCount">0</span></div>
<div title="Iridium Bursts">📡 <span id="burstCount">0</span></div>
</div>
</div>
</div>
@@ -1248,20 +1205,6 @@
</div>
</div>
<!-- Iridium Burst Log -->
<div id="iridiumBurstLog" style="display: none; margin-top: 15px;">
<div class="pass-list-container">
<div class="pass-list-header">
<span>Iridium Burst Log</span>
<button class="preset-btn" onclick="clearIridiumLog()" style="padding: 2px 8px; font-size: 10px;">Clear</button>
</div>
<div id="burstList">
<div style="color: #666; text-align: center; padding: 30px; font-size: 11px;">
Iridium bursts will appear here when detected.
</div>
</div>
</div>
</div>
</div>
<!-- Satellite Popout Container -->
@@ -1435,7 +1378,6 @@
let isRunning = false;
let isSensorRunning = false;
let isAdsbRunning = false;
let isIridiumRunning = false;
let isWifiRunning = false;
let isBtRunning = false;
let currentMode = 'pager';
@@ -1492,7 +1434,6 @@
// Satellite stats
document.getElementById('headerPassCount').textContent = document.getElementById('passCount')?.textContent || '0';
document.getElementById('headerBurstCount').textContent = document.getElementById('burstCount')?.textContent || '0';
}
// Sync stats periodically
setInterval(syncHeaderStats, 500);
@@ -1717,8 +1658,6 @@
let satellitePasses = [];
let selectedPass = null;
let selectedPassIndex = 0;
let iridiumBursts = [];
let iridiumEventSource = null;
let countdownInterval = null;
// Start satellite countdown timer
@@ -1857,7 +1796,6 @@
if (isWifiRunning) stopWifiScan();
if (isBtRunning) stopBtScan();
if (isAdsbRunning) stopAdsbScan();
if (isIridiumRunning) stopIridiumCapture();
currentMode = mode;
document.querySelectorAll('.mode-tab').forEach(tab => {
@@ -1958,7 +1896,6 @@
} else if (mode === 'satellite') {
initPolarPlot();
initSatelliteList();
checkIridiumTools();
}
}
@@ -6234,16 +6171,6 @@
// SATELLITE MODE FUNCTIONS
// ============================================
function switchSatelliteTab(tab) {
document.querySelectorAll('.satellite-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.satellite-content').forEach(c => c.classList.remove('active'));
document.querySelector(`.satellite-tab:nth-child(${tab === 'predictor' ? 1 : 2})`).classList.add('active');
document.getElementById(tab === 'predictor' ? 'predictorTab' : 'iridiumTab').classList.add('active');
// Toggle Iridium burst log visibility
document.getElementById('iridiumBurstLog').style.display = tab === 'iridium' ? 'block' : 'none';
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
@@ -7277,99 +7204,6 @@
}
}
// Iridium functions
function checkIridiumTools() {
fetch('/iridium/tools')
.then(r => r.json())
.then(data => {
const status = document.getElementById('iridiumExtractorStatus');
status.textContent = data.available ? 'OK' : 'Not found';
status.className = 'tool-status ' + (data.available ? 'ok' : 'missing');
});
}
function startIridiumCapture() {
const freq = document.getElementById('iridiumFreq').value;
const gain = document.getElementById('iridiumGain').value;
const sampleRate = document.getElementById('iridiumSampleRate').value;
const device = getSelectedDevice();
const sdr_type = getSelectedSDRType();
fetch('/iridium/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ freq, gain, sampleRate, device, sdr_type })
})
.then(r => r.json())
.then(data => {
if (data.status === 'started') {
isIridiumRunning = true;
document.getElementById('startIridiumBtn').style.display = 'none';
document.getElementById('stopIridiumBtn').style.display = 'block';
document.getElementById('statusDot').className = 'status-dot active';
document.getElementById('statusText').textContent = 'Iridium Capture';
startIridiumStream();
} else {
alert('Error: ' + data.message);
}
});
}
function stopIridiumCapture() {
fetch('/iridium/stop', { method: 'POST' })
.then(r => r.json())
.then(data => {
isIridiumRunning = false;
document.getElementById('startIridiumBtn').style.display = 'block';
document.getElementById('stopIridiumBtn').style.display = 'none';
document.getElementById('statusDot').className = 'status-dot';
document.getElementById('statusText').textContent = 'Idle';
if (iridiumEventSource) {
iridiumEventSource.close();
iridiumEventSource = null;
}
});
}
function startIridiumStream() {
if (iridiumEventSource) iridiumEventSource.close();
iridiumEventSource = new EventSource('/iridium/stream');
iridiumEventSource.onmessage = function(e) {
const data = JSON.parse(e.data);
if (data.type === 'burst') {
iridiumBursts.unshift(data);
document.getElementById('burstCount').textContent = iridiumBursts.length;
addBurstToLog(data);
}
};
}
function addBurstToLog(burst) {
const container = document.getElementById('burstList');
const placeholder = container.querySelector('div[style*="color: #666"]');
if (placeholder) placeholder.remove();
const card = document.createElement('div');
card.className = 'burst-card';
card.innerHTML = `
<div class="burst-time">${burst.time}</div>
<div class="burst-freq">${burst.frequency} MHz</div>
<div class="burst-data">${burst.data || 'No payload data'}</div>
`;
container.insertBefore(card, container.firstChild);
while (container.children.length > 100) {
container.removeChild(container.lastChild);
}
}
function clearIridiumLog() {
iridiumBursts = [];
document.getElementById('burstCount').textContent = '0';
document.getElementById('burstList').innerHTML = '<div style="color: #666; text-align: center; padding: 30px; font-size: 11px;">Iridium bursts will appear here when detected.</div>';
}
// Utility function
function showInfo(message) {
// Simple notification - could be enhanced
@@ -7513,7 +7347,6 @@
<li>Add satellites manually or fetch from Celestrak by category</li>
<li>Categories: Amateur, Weather, ISS, Starlink, GPS, and more</li>
<li>View next pass predictions with elevation and duration</li>
<li>Monitor for Iridium satellite bursts</li>
</ul>
<h3>📶 WiFi Mode</h3>

View File

@@ -19,7 +19,6 @@ from .logging import (
bluetooth_logger,
adsb_logger,
satellite_logger,
iridium_logger,
)
from .validation import (
escape_html,

View File

@@ -189,18 +189,6 @@ TOOL_DEPENDENCIES = {
}
}
},
'iridium': {
'name': 'Iridium Monitoring',
'tools': {
'iridium-extractor': {
'required': False,
'description': 'Iridium burst extractor',
'install': {
'manual': 'https://github.com/muccc/gr-iridium'
}
}
}
},
'sdr_hardware': {
'name': 'SDR Hardware Support',
'tools': {

View File

@@ -27,4 +27,3 @@ wifi_logger = get_logger('intercept.wifi')
bluetooth_logger = get_logger('intercept.bluetooth')
adsb_logger = get_logger('intercept.adsb')
satellite_logger = get_logger('intercept.satellite')
iridium_logger = get_logger('intercept.iridium')

View File

@@ -83,7 +83,7 @@ class CommandBuilder(ABC):
squelch: Optional[int] = None
) -> list[str]:
"""
Build FM demodulation command (for pager, iridium).
Build FM demodulation command (for pager decoding).
Args:
device: The SDR device to use

View File

@@ -65,7 +65,7 @@ class HackRFCommandBuilder(CommandBuilder):
"""
Build SoapySDR rx_fm command for FM demodulation.
For pager decoding and iridium capture with HackRF.
For pager decoding with HackRF.
"""
device_str = self._build_device_string(device)

View File

@@ -46,7 +46,7 @@ class LimeSDRCommandBuilder(CommandBuilder):
"""
Build SoapySDR rx_fm command for FM demodulation.
For pager decoding and iridium capture with LimeSDR.
For pager decoding with LimeSDR.
"""
device_str = self._build_device_string(device)

View File

@@ -40,7 +40,7 @@ class RTLSDRCommandBuilder(CommandBuilder):
"""
Build rtl_fm command for FM demodulation.
Used for pager decoding and iridium capture.
Used for pager decoding.
"""
cmd = [
'rtl_fm',