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}
build: .
container_name: intercept
profiles:
- basic
ports:
- "5050:5050"
# Uncomment for HTTPS support (set INTERCEPT_HTTPS=true below)

View File

@@ -13,7 +13,7 @@ import subprocess
import threading
import time
from datetime import datetime
from typing import Generator
from typing import Any, Generator
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_ACARS_FREQUENCIES = [
'131.550', # North America primary
'131.550', # Primary worldwide / North America
'130.025', # North America secondary
'129.125', # North America tertiary
'131.725', # North America (major US carriers)
'131.825', # North America (major US carriers)
]
# Message counter for statistics
@@ -456,10 +458,11 @@ def get_acars_messages() -> Response:
@acars_bp.route('/clear', methods=['POST'])
def clear_acars_messages() -> Response:
"""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
get_flight_correlator().clear_acars()
acars_message_count = 0
acars_last_message_time = None
return jsonify({'status': 'cleared'})
@@ -469,7 +472,7 @@ def get_frequencies() -> Response:
return jsonify({
'default': DEFAULT_ACARS_FREQUENCIES,
'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'],
'asia_pacific': ['131.550', '131.450'],
}

View File

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