mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
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:
@@ -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)
|
||||||
|
|||||||
@@ -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'],
|
||||||
}
|
}
|
||||||
|
|||||||
165
routes/vdl2.py
165
routes/vdl2.py
@@ -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'})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user