Fix review issues: profiles, imports, clear reset, frequencies, VDL2 enrichment

- Remove profiles: [basic] from intercept service so docker compose up -d
  works without --profile flag (fixes breaking change for existing deployments)
- Add missing Any import to routes/acars.py and routes/vdl2.py
- Reset last_message_time to None in ACARS and VDL2 clear endpoints
- Restore 131.725 and 131.825 to default ACARS frequencies (major US carriers)
- Copy VDL2 ACARS enrichment fields to top-level data dict instead of mutating
  nested acars_payload (consistent with ACARS route pattern)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mitchross
2026-02-23 23:31:20 -05:00
parent e19a990b64
commit 6a690abf82
3 changed files with 90 additions and 88 deletions

View File

@@ -16,8 +16,6 @@ services:
image: ${INTERCEPT_IMAGE:-intercept:latest} image: ${INTERCEPT_IMAGE:-intercept:latest}
build: . build: .
container_name: intercept container_name: intercept
profiles:
- basic
ports: ports:
- "5050:5050" - "5050:5050"
# Uncomment for HTTPS support (set INTERCEPT_HTTPS=true below) # Uncomment for HTTPS support (set INTERCEPT_HTTPS=true below)

View File

@@ -13,7 +13,7 @@ import subprocess
import threading import threading
import time import time
from datetime import datetime from datetime import datetime
from typing import Generator from typing import Any, Generator
from flask import Blueprint, jsonify, request, Response from flask import Blueprint, jsonify, request, Response
@@ -35,9 +35,11 @@ acars_bp = Blueprint('acars', __name__, url_prefix='/acars')
# Default VHF ACARS frequencies (MHz) - North America primary # Default VHF ACARS frequencies (MHz) - North America primary
DEFAULT_ACARS_FREQUENCIES = [ DEFAULT_ACARS_FREQUENCIES = [
'131.550', # North America primary '131.550', # Primary worldwide / North America
'130.025', # North America secondary '130.025', # North America secondary
'129.125', # North America tertiary '129.125', # North America tertiary
'131.725', # North America (major US carriers)
'131.825', # North America (major US carriers)
] ]
# Message counter for statistics # Message counter for statistics
@@ -456,10 +458,11 @@ def get_acars_messages() -> Response:
@acars_bp.route('/clear', methods=['POST']) @acars_bp.route('/clear', methods=['POST'])
def clear_acars_messages() -> Response: def clear_acars_messages() -> Response:
"""Clear stored ACARS messages and reset counter.""" """Clear stored ACARS messages and reset counter."""
global acars_message_count global acars_message_count, acars_last_message_time
from utils.flight_correlator import get_flight_correlator from utils.flight_correlator import get_flight_correlator
get_flight_correlator().clear_acars() get_flight_correlator().clear_acars()
acars_message_count = 0 acars_message_count = 0
acars_last_message_time = None
return jsonify({'status': 'cleared'}) return jsonify({'status': 'cleared'})
@@ -469,7 +472,7 @@ def get_frequencies() -> Response:
return jsonify({ return jsonify({
'default': DEFAULT_ACARS_FREQUENCIES, 'default': DEFAULT_ACARS_FREQUENCIES,
'regions': { 'regions': {
'north_america': ['131.550', '130.025', '129.125'], 'north_america': ['131.550', '130.025', '129.125', '131.725', '131.825'],
'europe': ['131.525', '131.725', '131.550'], 'europe': ['131.525', '131.725', '131.550'],
'asia_pacific': ['131.550', '131.450'], 'asia_pacific': ['131.550', '131.450'],
} }

View File

@@ -1,19 +1,19 @@
"""VDL2 aircraft datalink routes.""" """VDL2 aircraft datalink routes."""
from __future__ import annotations from __future__ import annotations
import io import io
import json import json
import os import os
import platform import platform
import pty import pty
import queue import queue
import shutil import shutil
import subprocess import subprocess
import threading import threading
import time import time
from datetime import datetime from datetime import datetime
from typing import Generator from typing import Any, Generator
from flask import Blueprint, jsonify, request, Response from flask import Blueprint, jsonify, request, Response
@@ -21,7 +21,7 @@ import app as app_module
from utils.logging import sensor_logger as logger from utils.logging import sensor_logger as logger
from utils.validation import validate_device_index, validate_gain, validate_ppm from utils.validation import validate_device_index, validate_gain, validate_ppm
from utils.sdr import SDRFactory, SDRType from utils.sdr import SDRFactory, SDRType
from utils.sse import sse_stream_fanout from utils.sse import sse_stream_fanout
from utils.event_pipeline import process_event from utils.event_pipeline import process_event
from utils.constants import ( from utils.constants import (
PROCESS_TERMINATE_TIMEOUT, PROCESS_TERMINATE_TIMEOUT,
@@ -55,22 +55,22 @@ def find_dumpvdl2():
return shutil.which('dumpvdl2') return shutil.which('dumpvdl2')
def stream_vdl2_output(process: subprocess.Popen, is_text_mode: bool = False) -> None: def stream_vdl2_output(process: subprocess.Popen, is_text_mode: bool = False) -> None:
"""Stream dumpvdl2 JSON output to queue.""" """Stream dumpvdl2 JSON output to queue."""
global vdl2_message_count, vdl2_last_message_time global vdl2_message_count, vdl2_last_message_time
try: try:
app_module.vdl2_queue.put({'type': 'status', 'status': 'started'}) app_module.vdl2_queue.put({'type': 'status', 'status': 'started'})
# Use appropriate sentinel based on mode (text mode for pty on macOS) # Use appropriate sentinel based on mode (text mode for pty on macOS)
sentinel = '' if is_text_mode else b'' sentinel = '' if is_text_mode else b''
for line in iter(process.stdout.readline, sentinel): for line in iter(process.stdout.readline, sentinel):
if is_text_mode: if is_text_mode:
line = line.strip() line = line.strip()
else: else:
line = line.decode('utf-8', errors='replace').strip() line = line.decode('utf-8', errors='replace').strip()
if not line: if not line:
continue continue
try: try:
data = json.loads(line) data = json.loads(line)
@@ -79,7 +79,7 @@ def stream_vdl2_output(process: subprocess.Popen, is_text_mode: bool = False) ->
data['type'] = 'vdl2' data['type'] = 'vdl2'
data['timestamp'] = datetime.utcnow().isoformat() + 'Z' data['timestamp'] = datetime.utcnow().isoformat() + 'Z'
# Enrich embedded ACARS payload with translated label # Enrich with translated ACARS label at top level (consistent with ACARS route)
try: try:
vdl2_inner = data.get('vdl2', data) vdl2_inner = data.get('vdl2', data)
acars_payload = (vdl2_inner.get('avlc') or {}).get('acars') acars_payload = (vdl2_inner.get('avlc') or {}).get('acars')
@@ -89,9 +89,9 @@ def stream_vdl2_output(process: subprocess.Popen, is_text_mode: bool = False) ->
'label': acars_payload.get('label'), 'label': acars_payload.get('label'),
'text': acars_payload.get('msg_text', ''), 'text': acars_payload.get('msg_text', ''),
}) })
acars_payload['label_description'] = translation['label_description'] data['label_description'] = translation['label_description']
acars_payload['message_type'] = translation['message_type'] data['message_type'] = translation['message_type']
acars_payload['parsed'] = translation['parsed'] data['parsed'] = translation['parsed']
except Exception: except Exception:
pass pass
@@ -268,28 +268,28 @@ def start_vdl2() -> Response:
logger.info(f"Starting VDL2 decoder: {' '.join(cmd)}") logger.info(f"Starting VDL2 decoder: {' '.join(cmd)}")
try: try:
is_text_mode = False is_text_mode = False
# On macOS, use pty to avoid stdout buffering issues # On macOS, use pty to avoid stdout buffering issues
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
master_fd, slave_fd = pty.openpty() master_fd, slave_fd = pty.openpty()
process = subprocess.Popen( process = subprocess.Popen(
cmd, cmd,
stdout=slave_fd, stdout=slave_fd,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
start_new_session=True start_new_session=True
) )
os.close(slave_fd) os.close(slave_fd)
# Wrap master_fd as a text file for line-buffered reading # Wrap master_fd as a text file for line-buffered reading
process.stdout = io.open(master_fd, 'r', buffering=1) process.stdout = io.open(master_fd, 'r', buffering=1)
is_text_mode = True is_text_mode = True
else: else:
process = subprocess.Popen( process = subprocess.Popen(
cmd, cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
start_new_session=True start_new_session=True
) )
# Wait briefly to check if process started # Wait briefly to check if process started
time.sleep(PROCESS_START_WAIT) time.sleep(PROCESS_START_WAIT)
@@ -311,12 +311,12 @@ def start_vdl2() -> Response:
app_module.vdl2_process = process app_module.vdl2_process = process
register_process(process) register_process(process)
# Start output streaming thread # Start output streaming thread
thread = threading.Thread( thread = threading.Thread(
target=stream_vdl2_output, target=stream_vdl2_output,
args=(process, is_text_mode), args=(process, is_text_mode),
daemon=True daemon=True
) )
thread.start() thread.start()
return jsonify({ return jsonify({
@@ -365,25 +365,25 @@ def stop_vdl2() -> Response:
return jsonify({'status': 'stopped'}) return jsonify({'status': 'stopped'})
@vdl2_bp.route('/stream') @vdl2_bp.route('/stream')
def stream_vdl2() -> Response: def stream_vdl2() -> Response:
"""SSE stream for VDL2 messages.""" """SSE stream for VDL2 messages."""
def _on_msg(msg: dict[str, Any]) -> None: def _on_msg(msg: dict[str, Any]) -> None:
process_event('vdl2', msg, msg.get('type')) process_event('vdl2', msg, msg.get('type'))
response = Response( response = Response(
sse_stream_fanout( sse_stream_fanout(
source_queue=app_module.vdl2_queue, source_queue=app_module.vdl2_queue,
channel_key='vdl2', channel_key='vdl2',
timeout=SSE_QUEUE_TIMEOUT, timeout=SSE_QUEUE_TIMEOUT,
keepalive_interval=SSE_KEEPALIVE_INTERVAL, keepalive_interval=SSE_KEEPALIVE_INTERVAL,
on_message=_on_msg, on_message=_on_msg,
), ),
mimetype='text/event-stream', mimetype='text/event-stream',
) )
response.headers['Cache-Control'] = 'no-cache' response.headers['Cache-Control'] = 'no-cache'
response.headers['X-Accel-Buffering'] = 'no' response.headers['X-Accel-Buffering'] = 'no'
return response return response
@vdl2_bp.route('/messages') @vdl2_bp.route('/messages')
@@ -399,10 +399,11 @@ def get_vdl2_messages() -> Response:
@vdl2_bp.route('/clear', methods=['POST']) @vdl2_bp.route('/clear', methods=['POST'])
def clear_vdl2_messages() -> Response: def clear_vdl2_messages() -> Response:
"""Clear stored VDL2 messages and reset counter.""" """Clear stored VDL2 messages and reset counter."""
global vdl2_message_count global vdl2_message_count, vdl2_last_message_time
from utils.flight_correlator import get_flight_correlator from utils.flight_correlator import get_flight_correlator
get_flight_correlator().clear_vdl2() get_flight_correlator().clear_vdl2()
vdl2_message_count = 0 vdl2_message_count = 0
vdl2_last_message_time = None
return jsonify({'status': 'cleared'}) return jsonify({'status': 'cleared'})