style: apply ruff-format to entire codebase

First-time run of ruff-format via pre-commit hook normalises quote
style, trailing commas, and whitespace across 188 Python files.
No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-07-05 14:48:11 +01:00
parent 82e64104fe
commit 96172ca593
189 changed files with 19883 additions and 19552 deletions
+179 -130
View File
@@ -24,6 +24,7 @@ from utils.responses import api_error
try:
from flask_sock import Sock
WEBSOCKET_AVAILABLE = True
except ImportError:
WEBSOCKET_AVAILABLE = False
@@ -43,15 +44,15 @@ from utils.waterfall_fft import (
quantize_to_uint8,
)
logger = get_logger('intercept.meteor')
logger = get_logger("intercept.meteor")
# Module-level shared state
_state_lock = threading.Lock()
_state: dict[str, Any] = {
'running': False,
'device': None,
'frequency_mhz': 0.0,
'sample_rate': 0,
"running": False,
"device": None,
"frequency_mhz": 0.0,
"sample_rate": 0,
}
_detector: MeteorDetector | None = None
_sse_queue: queue.Queue = queue.Queue(maxsize=500)
@@ -80,12 +81,12 @@ def _push_sse(data: dict[str, Any]) -> None:
def _resolve_sdr_type(sdr_type_str: str) -> SDRType:
mapping = {
'rtlsdr': SDRType.RTL_SDR,
'rtl_sdr': SDRType.RTL_SDR,
'hackrf': SDRType.HACKRF,
'limesdr': SDRType.LIME_SDR,
'airspy': SDRType.AIRSPY,
'sdrplay': SDRType.SDRPLAY,
"rtlsdr": SDRType.RTL_SDR,
"rtl_sdr": SDRType.RTL_SDR,
"hackrf": SDRType.HACKRF,
"limesdr": SDRType.LIME_SDR,
"airspy": SDRType.AIRSPY,
"sdrplay": SDRType.SDRPLAY,
}
return mapping.get(sdr_type_str.lower(), SDRType.RTL_SDR)
@@ -96,8 +97,8 @@ def _build_dummy_device(device_index: int, sdr_type: SDRType) -> SDRDevice:
return SDRDevice(
sdr_type=sdr_type,
index=device_index,
name=f'{sdr_type.value}-{device_index}',
serial='N/A',
name=f"{sdr_type.value}-{device_index}",
serial="N/A",
driver=sdr_type.value,
capabilities=caps,
)
@@ -113,96 +114,99 @@ def _pick_sample_rate(span_hz: int, caps: SDRCapabilities, sdr_type: SDRType) ->
# ── Blueprint for REST/SSE endpoints ──
meteor_bp = Blueprint('meteor', __name__, url_prefix='/meteor')
meteor_bp = Blueprint("meteor", __name__, url_prefix="/meteor")
@meteor_bp.route('/status')
@meteor_bp.route("/status")
def meteor_status():
"""Return current meteor monitoring status."""
with _state_lock:
running = _state['running']
freq = _state['frequency_mhz']
device = _state['device']
sr = _state['sample_rate']
running = _state["running"]
freq = _state["frequency_mhz"]
device = _state["device"]
sr = _state["sample_rate"]
detector = _detector
stats = None
if detector:
stats = detector._build_stats(time.time())
return jsonify({
'running': running,
'frequency_mhz': freq,
'device': device,
'sample_rate': sr,
'stats': stats,
})
return jsonify(
{
"running": running,
"frequency_mhz": freq,
"device": device,
"sample_rate": sr,
"stats": stats,
}
)
@meteor_bp.route('/stream')
@meteor_bp.route("/stream")
def meteor_stream():
"""SSE endpoint for meteor detection events and stats."""
response = Response(
sse_stream_fanout(
source_queue=_sse_queue,
channel_key='meteor',
channel_key="meteor",
timeout=1.0,
keepalive_interval=30.0,
),
mimetype='text/event-stream',
mimetype="text/event-stream",
)
response.headers['Cache-Control'] = 'no-cache'
response.headers['X-Accel-Buffering'] = 'no'
response.headers['Connection'] = 'keep-alive'
response.headers["Cache-Control"] = "no-cache"
response.headers["X-Accel-Buffering"] = "no"
response.headers["Connection"] = "keep-alive"
return response
@meteor_bp.route('/events')
@meteor_bp.route("/events")
def meteor_events():
"""Return detected events as JSON."""
detector = _detector
if not detector:
return jsonify({'events': []})
limit = request.args.get('limit', 500, type=int)
return jsonify({'events': detector.get_events(limit=limit)})
return jsonify({"events": []})
limit = request.args.get("limit", 500, type=int)
return jsonify({"events": detector.get_events(limit=limit)})
@meteor_bp.route('/events/export')
@meteor_bp.route("/events/export")
def meteor_events_export():
"""Export events as CSV or JSON."""
detector = _detector
if not detector:
return api_error('No active session', 400)
return api_error("No active session", 400)
fmt = request.args.get('format', 'json').lower()
if fmt == 'csv':
fmt = request.args.get("format", "json").lower()
if fmt == "csv":
csv_data = detector.export_events_csv()
return Response(
csv_data,
mimetype='text/csv',
headers={'Content-Disposition': 'attachment; filename=meteor_events.csv'},
mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=meteor_events.csv"},
)
else:
json_data = detector.export_events_json()
return Response(
json_data,
mimetype='application/json',
headers={'Content-Disposition': 'attachment; filename=meteor_events.json'},
mimetype="application/json",
headers={"Content-Disposition": "attachment; filename=meteor_events.json"},
)
@meteor_bp.route('/events/clear', methods=['POST'])
@meteor_bp.route("/events/clear", methods=["POST"])
def meteor_events_clear():
"""Clear all detected events."""
detector = _detector
if not detector:
return jsonify({'cleared': 0})
return jsonify({"cleared": 0})
count = detector.clear_events()
return jsonify({'cleared': count})
return jsonify({"cleared": count})
# ── WebSocket handler ──
def init_meteor_websocket(app: Flask):
"""Initialize WebSocket meteor scatter streaming."""
global _detector
@@ -213,7 +217,7 @@ def init_meteor_websocket(app: Flask):
sock = Sock(app)
@sock.route('/ws/meteor')
@sock.route("/ws/meteor")
def meteor_stream_ws(ws):
"""WebSocket endpoint for meteor scatter waterfall + detection."""
global _detector
@@ -225,7 +229,7 @@ def init_meteor_websocket(app: Flask):
reader_thread = None
stop_event = threading.Event()
claimed_device = None
claimed_sdr_type = 'rtlsdr'
claimed_sdr_type = "rtlsdr"
send_queue: queue.Queue = queue.Queue(maxsize=120)
try:
@@ -264,9 +268,9 @@ def init_meteor_websocket(app: Flask):
except (json.JSONDecodeError, TypeError):
continue
cmd = data.get('cmd')
cmd = data.get("cmd")
if cmd == 'start':
if cmd == "start":
# Stop any existing capture
was_restarting = iq_process is not None
stop_event.set()
@@ -280,7 +284,7 @@ def init_meteor_websocket(app: Flask):
app_module.release_sdr_device(claimed_device, claimed_sdr_type)
claimed_device = None
with _state_lock:
_state['running'] = False
_state["running"] = False
stop_event.clear()
while not send_queue.empty():
try:
@@ -292,34 +296,38 @@ def init_meteor_websocket(app: Flask):
# Parse config
try:
frequency_mhz = float(data.get('frequency_mhz', 143.05))
frequency_mhz = float(data.get("frequency_mhz", 143.05))
validate_frequency(frequency_mhz)
gain_raw = data.get('gain')
if gain_raw is None or str(gain_raw).lower() == 'auto':
gain_raw = data.get("gain")
if gain_raw is None or str(gain_raw).lower() == "auto":
gain = None
else:
gain = validate_gain(float(gain_raw))
device_index = validate_device_index(int(data.get('device', 0)))
sdr_type_str = data.get('sdr_type', 'rtlsdr')
sample_rate_req = int(data.get('sample_rate', 250000))
fft_size = int(data.get('fft_size', 1024))
fps = int(data.get('fps', 20))
avg_count = int(data.get('avg_count', 4))
ppm = data.get('ppm')
device_index = validate_device_index(int(data.get("device", 0)))
sdr_type_str = data.get("sdr_type", "rtlsdr")
sample_rate_req = int(data.get("sample_rate", 250000))
fft_size = int(data.get("fft_size", 1024))
fps = int(data.get("fps", 20))
avg_count = int(data.get("avg_count", 4))
ppm = data.get("ppm")
if ppm is not None:
ppm = int(ppm)
bias_t = bool(data.get('bias_t', False))
bias_t = bool(data.get("bias_t", False))
# Detection settings
snr_threshold = float(data.get('snr_threshold', 6.0))
min_duration = float(data.get('min_duration_ms', 50.0))
cooldown = float(data.get('cooldown_ms', 200.0))
freq_drift = float(data.get('freq_drift_tolerance_hz', 500.0))
snr_threshold = float(data.get("snr_threshold", 6.0))
min_duration = float(data.get("min_duration_ms", 50.0))
cooldown = float(data.get("cooldown_ms", 200.0))
freq_drift = float(data.get("freq_drift_tolerance_hz", 500.0))
except (TypeError, ValueError) as exc:
ws.send(json.dumps({
'status': 'error',
'message': f'Invalid configuration: {exc}',
}))
ws.send(
json.dumps(
{
"status": "error",
"message": f"Invalid configuration: {exc}",
}
)
)
continue
# Clamp values
@@ -342,17 +350,21 @@ def init_meteor_websocket(app: Flask):
max_claim_attempts = 4 if was_restarting else 1
claim_err = None
for _attempt in range(max_claim_attempts):
claim_err = app_module.claim_sdr_device(device_index, 'meteor', sdr_type_str)
claim_err = app_module.claim_sdr_device(device_index, "meteor", sdr_type_str)
if not claim_err:
break
if _attempt < max_claim_attempts - 1:
time.sleep(0.4)
if claim_err:
ws.send(json.dumps({
'status': 'error',
'message': claim_err,
'error_type': 'DEVICE_BUSY',
}))
ws.send(
json.dumps(
{
"status": "error",
"message": claim_err,
"error_type": "DEVICE_BUSY",
}
)
)
continue
claimed_device = device_index
claimed_sdr_type = sdr_type_str
@@ -371,17 +383,21 @@ def init_meteor_websocket(app: Flask):
except NotImplementedError as e:
app_module.release_sdr_device(device_index, sdr_type_str)
claimed_device = None
ws.send(json.dumps({'status': 'error', 'message': str(e)}))
ws.send(json.dumps({"status": "error", "message": str(e)}))
continue
# Check binary exists
if not shutil.which(iq_cmd[0]):
app_module.release_sdr_device(device_index, sdr_type_str)
claimed_device = None
ws.send(json.dumps({
'status': 'error',
'message': f'Required tool "{iq_cmd[0]}" not found.',
}))
ws.send(
json.dumps(
{
"status": "error",
"message": f'Required tool "{iq_cmd[0]}" not found.',
}
)
)
continue
# Spawn I/Q capture
@@ -402,10 +418,10 @@ def init_meteor_websocket(app: Flask):
time.sleep(0.3)
if iq_process.poll() is not None:
stderr_out = ''
stderr_out = ""
if iq_process.stderr:
with suppress(Exception):
stderr_out = iq_process.stderr.read().decode('utf-8', errors='replace').strip()
stderr_out = iq_process.stderr.read().decode("utf-8", errors="replace").strip()
unregister_process(iq_process)
iq_process = None
if attempt < max_attempts - 1:
@@ -422,10 +438,14 @@ def init_meteor_websocket(app: Flask):
iq_process = None
app_module.release_sdr_device(device_index, sdr_type_str)
claimed_device = None
ws.send(json.dumps({
'status': 'error',
'message': f'Failed to start I/Q capture: {e}',
}))
ws.send(
json.dumps(
{
"status": "error",
"message": f"Failed to start I/Q capture: {e}",
}
)
)
continue
# Initialize detector
@@ -437,27 +457,39 @@ def init_meteor_websocket(app: Flask):
)
with _state_lock:
_state['running'] = True
_state['device'] = device_index
_state['frequency_mhz'] = frequency_mhz
_state['sample_rate'] = sample_rate
_state["running"] = True
_state["device"] = device_index
_state["frequency_mhz"] = frequency_mhz
_state["sample_rate"] = sample_rate
# Send confirmation
ws.send(json.dumps({
'status': 'started',
'frequency_mhz': frequency_mhz,
'start_freq': start_freq,
'end_freq': end_freq,
'fft_size': fft_size,
'sample_rate': sample_rate,
'span_mhz': span_mhz,
}))
ws.send(
json.dumps(
{
"status": "started",
"frequency_mhz": frequency_mhz,
"start_freq": start_freq,
"end_freq": end_freq,
"fft_size": fft_size,
"sample_rate": sample_rate,
"span_mhz": span_mhz,
}
)
)
# Start FFT reader + detection thread
def fft_reader(
proc, _send_q, stop_evt, detector,
_fft_size, _avg_count, _fps, _sample_rate,
_start_freq, _end_freq, _freq_mhz,
proc,
_send_q,
stop_evt,
detector,
_fft_size,
_avg_count,
_fps,
_sample_rate,
_start_freq,
_end_freq,
_freq_mhz,
):
required_fft_samples = _fft_size * _avg_count
timeslice_samples = max(required_fft_samples, int(_sample_rate / max(1, _fps)))
@@ -475,7 +507,7 @@ def init_meteor_websocket(app: Flask):
frame_start = time.monotonic()
# Read raw I/Q
raw = b''
raw = b""
remaining = bytes_per_frame
while remaining > 0 and not stop_evt.is_set():
chunk = proc.stdout.read(min(remaining, 65536))
@@ -489,7 +521,9 @@ def init_meteor_websocket(app: Flask):
# FFT pipeline
samples = cu8_to_complex(raw)
fft_samples = samples[-required_fft_samples:] if len(samples) > required_fft_samples else samples
fft_samples = (
samples[-required_fft_samples:] if len(samples) > required_fft_samples else samples
)
power_db = compute_power_spectrum(
fft_samples,
fft_size=_fft_size,
@@ -505,20 +539,27 @@ def init_meteor_websocket(app: Flask):
# Run detection on raw dB spectrum
now = time.time()
stats, event = detector.process_frame(
power_db, start_freq_hz, end_freq_hz, now,
power_db,
start_freq_hz,
end_freq_hz,
now,
)
# Push event immediately via SSE
if event:
_push_sse({
'type': 'event',
'event': event.to_dict(),
})
_push_sse(
{
"type": "event",
"event": event.to_dict(),
}
)
# Also send as JSON via WS for immediate UI update
event_msg = json.dumps({
'type': 'detection',
'event': event.to_dict(),
})
event_msg = json.dumps(
{
"type": "detection",
"event": event.to_dict(),
}
)
with suppress(queue.Full):
_send_q.put_nowait(event_msg)
@@ -539,26 +580,34 @@ def init_meteor_websocket(app: Flask):
reader_thread = threading.Thread(
target=fft_reader,
args=(
iq_process, send_queue, stop_event, _detector,
fft_size, avg_count, fps, sample_rate,
start_freq, end_freq, frequency_mhz,
iq_process,
send_queue,
stop_event,
_detector,
fft_size,
avg_count,
fps,
sample_rate,
start_freq,
end_freq,
frequency_mhz,
),
daemon=True,
)
reader_thread.start()
elif cmd == 'update_threshold':
elif cmd == "update_threshold":
detector = _detector
if detector:
detector.update_settings(
snr_threshold_db=data.get('snr_threshold'),
min_duration_ms=data.get('min_duration_ms'),
cooldown_ms=data.get('cooldown_ms'),
freq_drift_tolerance_hz=data.get('freq_drift_tolerance_hz'),
snr_threshold_db=data.get("snr_threshold"),
min_duration_ms=data.get("min_duration_ms"),
cooldown_ms=data.get("cooldown_ms"),
freq_drift_tolerance_hz=data.get("freq_drift_tolerance_hz"),
)
ws.send(json.dumps({'status': 'threshold_updated'}))
ws.send(json.dumps({"status": "threshold_updated"}))
elif cmd == 'stop':
elif cmd == "stop":
stop_event.set()
if reader_thread and reader_thread.is_alive():
reader_thread.join(timeout=2)
@@ -571,10 +620,10 @@ def init_meteor_websocket(app: Flask):
app_module.release_sdr_device(claimed_device, claimed_sdr_type)
claimed_device = None
with _state_lock:
_state['running'] = False
_state['device'] = None
_state["running"] = False
_state["device"] = None
stop_event.clear()
ws.send(json.dumps({'status': 'stopped'}))
ws.send(json.dumps({"status": "stopped"}))
except Exception as e:
logger.info(f"WebSocket meteor closed: {e}")
@@ -588,8 +637,8 @@ def init_meteor_websocket(app: Flask):
if claimed_device is not None:
app_module.release_sdr_device(claimed_device, claimed_sdr_type)
with _state_lock:
_state['running'] = False
_state['device'] = None
_state["running"] = False
_state["device"] = None
with suppress(Exception):
ws.close()
with suppress(Exception):