diff --git a/.env.example b/.env.example index 8e2c1ff..e78804b 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,32 @@ -# Uncomment and set to use external storage for ADS-B history -# PGDATA_PATH=/mnt/external/intercept/pgdata +# ============================================================================= +# INTERCEPT CONTROLLER (.env) +# ============================================================================= +# Copy to .env and edit for your setup + +# Container timezone (e.g. America/New_York, Europe/London, Australia/Sydney) +TZ=UTC + +# Postgres password (default: intercept) +INTERCEPT_ADSB_DB_PASSWORD=intercept + +# Auto-start ADS-B when dashboard loads +INTERCEPT_ADSB_AUTO_START=false + +# Share observer location across all modules +INTERCEPT_SHARED_OBSERVER_LOCATION=true + +# Observer coordinates (uncomment and set to skip GPS prompt) +# INTERCEPT_DEFAULT_LAT=40.7128 +# INTERCEPT_DEFAULT_LON=-74.0060 + +# ============================================================================= +# AGENT SETTINGS (for docker-compose.agent.yml on remote Pis) +# ============================================================================= + +# Agent identity +AGENT_NAME=sdr-agent-1 +AGENT_PORT=8020 + +# Controller connection (IP of the machine running docker-compose.yml) +CONTROLLER_URL=http://192.168.1.100:5050 +AGENT_API_KEY=changeme diff --git a/docker-compose.yml b/docker-compose.yml index b6318ba..99b98cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: # Optional: mount logs directory # - ./logs:/app/logs environment: + - TZ=${TZ:-UTC} - INTERCEPT_HOST=0.0.0.0 - INTERCEPT_PORT=5050 - INTERCEPT_LOG_LEVEL=INFO @@ -78,6 +79,7 @@ services: volumes: - ./data:/app/data environment: + - TZ=${TZ:-UTC} - INTERCEPT_HOST=0.0.0.0 - INTERCEPT_PORT=5050 - INTERCEPT_LOG_LEVEL=INFO @@ -108,6 +110,7 @@ services: profiles: - history environment: + - TZ=${TZ:-UTC} - POSTGRES_DB=intercept_adsb - POSTGRES_USER=intercept - POSTGRES_PASSWORD=intercept diff --git a/routes/acars.py b/routes/acars.py index 7b9da78..db05df0 100644 --- a/routes/acars.py +++ b/routes/acars.py @@ -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, @@ -33,10 +33,11 @@ from utils.process import register_process, unregister_process acars_bp = Blueprint('acars', __name__, url_prefix='/acars') -# Default VHF ACARS frequencies (MHz) - common worldwide +# Default VHF ACARS frequencies (MHz) - North America primary DEFAULT_ACARS_FREQUENCIES = [ - '131.725', # North America - '131.825', # North America + '131.550', # North America primary + '130.025', # North America secondary + '129.125', # North America tertiary ] # Message counter for statistics @@ -120,6 +121,16 @@ def stream_acars_output(process: subprocess.Popen, is_text_mode: bool = False) - data['type'] = 'acars' data['timestamp'] = datetime.utcnow().isoformat() + 'Z' + # Enrich with translated label and parsed fields + try: + from utils.acars_translator import translate_message + translation = translate_message(data) + data['label_description'] = translation['label_description'] + data['message_type'] = translation['message_type'] + data['parsed'] = translation['parsed'] + except Exception: + pass + # Update stats acars_message_count += 1 acars_last_message_time = time.time() @@ -411,25 +422,25 @@ def stop_acars() -> Response: return jsonify({'status': 'stopped'}) -@acars_bp.route('/stream') -def stream_acars() -> Response: - """SSE stream for ACARS messages.""" - def _on_msg(msg: dict[str, Any]) -> None: - process_event('acars', msg, msg.get('type')) - - response = Response( - sse_stream_fanout( - source_queue=app_module.acars_queue, - channel_key='acars', - 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 +@acars_bp.route('/stream') +def stream_acars() -> Response: + """SSE stream for ACARS messages.""" + def _on_msg(msg: dict[str, Any]) -> None: + process_event('acars', msg, msg.get('type')) + + response = Response( + sse_stream_fanout( + source_queue=app_module.acars_queue, + channel_key='acars', + 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 @acars_bp.route('/frequencies') @@ -438,7 +449,7 @@ def get_frequencies() -> Response: return jsonify({ 'default': DEFAULT_ACARS_FREQUENCIES, 'regions': { - 'north_america': ['131.725', '131.825'], + 'north_america': ['131.550', '130.025', '129.125'], 'europe': ['131.525', '131.725', '131.550'], 'asia_pacific': ['131.550', '131.450'], } diff --git a/routes/adsb.py b/routes/adsb.py index 65e44a2..0cdfd6d 100644 --- a/routes/adsb.py +++ b/routes/adsb.py @@ -1189,4 +1189,17 @@ def get_aircraft_messages(icao: str): from utils.flight_correlator import get_flight_correlator messages = get_flight_correlator().get_messages_for_aircraft(icao=icao.upper(), callsign=callsign) + + # Backfill translation on messages missing label_description + try: + from utils.acars_translator import translate_message + for msg in messages.get('acars', []): + if not msg.get('label_description'): + translation = translate_message(msg) + msg['label_description'] = translation['label_description'] + msg['message_type'] = translation['message_type'] + msg['parsed'] = translation['parsed'] + except Exception: + pass + return jsonify({'status': 'success', 'icao': icao.upper(), **messages}) diff --git a/static/css/modes/acars.css b/static/css/modes/acars.css index 043bb68..bdbaef5 100644 --- a/static/css/modes/acars.css +++ b/static/css/modes/acars.css @@ -89,3 +89,22 @@ 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(74, 158, 255, 0.7); } 50% { opacity: 0.6; box-shadow: 0 0 6px 3px rgba(74, 158, 255, 0.3); } } + +/* ACARS Standalone Message Feed */ +.acars-message-feed { + scrollbar-width: thin; + scrollbar-color: var(--border-color) transparent; +} +.acars-message-feed::-webkit-scrollbar { + width: 4px; +} +.acars-message-feed::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 2px; +} +.acars-feed-card { + transition: background 0.15s; +} +.acars-feed-card:hover { + background: rgba(74, 158, 255, 0.05); +} diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 11940ef..17b0329 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -2891,12 +2891,130 @@ sudo make install
| Primary (N. America) | -131.725 / 131.825 MHz | +131.550 / 130.025 MHz |
| Quarter-wave length | @@ -82,6 +82,14 @@ + + +