diff --git a/routes/acars.py b/routes/acars.py index c79ceff..49af864 100644 --- a/routes/acars.py +++ b/routes/acars.py @@ -2,7 +2,11 @@ from __future__ import annotations +import io import json +import os +import platform +import pty import queue import shutil import subprocess @@ -45,20 +49,25 @@ def find_acarsdec(): return shutil.which('acarsdec') -def stream_acars_output(process: subprocess.Popen) -> None: +def stream_acars_output(process: subprocess.Popen, is_text_mode: bool = False) -> None: """Stream acarsdec JSON output to queue.""" global acars_message_count, acars_last_message_time try: app_module.acars_queue.put({'type': 'status', 'status': 'started'}) - for line in iter(process.stdout.readline, b''): - line = line.decode('utf-8', errors='replace').strip() + # 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: - # acarsdec -j outputs JSON, one message per line + # acarsdec -o 4 outputs JSON, one message per line data = json.loads(line) # Add our metadata @@ -167,33 +176,50 @@ def start_acars() -> Response: acars_last_message_time = None # Build acarsdec command - # acarsdec -j -r -g -p ... + # acarsdec -o 4 -g -p -r ... + # Note: -o 4 is JSON stdout, gain/ppm must come BEFORE -r cmd = [ acarsdec_path, - '-j', # JSON output - '-r', str(device), # RTL-SDR device index + '-o', '4', # JSON output to stdout ] - # Add gain if not auto + # Add gain if not auto (must be before -r) if gain and str(gain) != '0': cmd.extend(['-g', str(gain)]) - # Add PPM correction if specified + # Add PPM correction if specified (must be before -r) if ppm and str(ppm) != '0': cmd.extend(['-p', str(ppm)]) - # Add frequencies + # Add device and frequencies (-r takes device, remaining args are frequencies) + cmd.extend(['-r', str(device)]) cmd.extend(frequencies) logger.info(f"Starting ACARS decoder: {' '.join(cmd)}") try: - 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) @@ -214,7 +240,7 @@ def start_acars() -> Response: # Start output streaming thread thread = threading.Thread( target=stream_acars_output, - args=(process,), + args=(process, is_text_mode), daemon=True ) thread.start() diff --git a/static/css/adsb_dashboard.css b/static/css/adsb_dashboard.css index 0ccb93c..bf5ba13 100644 --- a/static/css/adsb_dashboard.css +++ b/static/css/adsb_dashboard.css @@ -280,9 +280,9 @@ body { } .acars-sidebar .acars-btn { - background: var(--bg-card); - border: 1px solid var(--accent-cyan); - color: var(--accent-cyan); + background: var(--accent-green); + border: none; + color: #fff; padding: 6px 10px; font-size: 10px; font-weight: 600; @@ -290,10 +290,21 @@ body { transition: all 0.2s; text-transform: uppercase; letter-spacing: 1px; + border-radius: 4px; } .acars-sidebar .acars-btn:hover { - background: rgba(74, 158, 255, 0.2); + background: #1db954; + box-shadow: 0 0 10px rgba(34, 197, 94, 0.3); +} + +.acars-sidebar .acars-btn.active { + background: var(--accent-red); +} + +.acars-sidebar .acars-btn.active:hover { + background: #dc2626; + box-shadow: 0 0 10px rgba(239, 68, 68, 0.3); } .acars-message-item { @@ -691,9 +702,9 @@ body { /* Start/stop button */ .start-btn { padding: 8px 20px; - border: 1px solid var(--accent-cyan); - background: rgba(74, 158, 255, 0.1); - color: var(--accent-cyan); + border: none; + background: var(--accent-green); + color: #fff; font-family: 'Orbitron', monospace; font-size: 11px; font-weight: 600; @@ -706,19 +717,18 @@ body { } .start-btn:hover { - background: var(--accent-cyan); - color: var(--bg-dark); - box-shadow: 0 0 20px rgba(74, 158, 255, 0.3); + background: #1db954; + box-shadow: 0 0 20px rgba(34, 197, 94, 0.3); } .start-btn.active { background: var(--accent-red); - border-color: var(--accent-red); color: #fff; } .start-btn.active:hover { - box-shadow: 0 0 20px rgba(255, 68, 68, 0.3); + background: #dc2626; + box-shadow: 0 0 20px rgba(239, 68, 68, 0.3); } /* GPS button */ @@ -841,9 +851,9 @@ body { .airband-btn { padding: 6px 12px; - background: rgba(74, 158, 255, 0.1); - border: 1px solid var(--accent-cyan); - color: var(--accent-cyan); + background: var(--accent-green); + border: none; + color: #fff; border-radius: 4px; cursor: pointer; font-size: 11px; @@ -858,13 +868,18 @@ body { } .airband-btn:hover { - background: rgba(74, 158, 255, 0.2); + background: #1db954; + box-shadow: 0 0 10px rgba(34, 197, 94, 0.3); } .airband-btn.active { - background: rgba(34, 197, 94, 0.2); - border-color: var(--accent-green); - color: var(--accent-green); + background: var(--accent-red); + color: #fff; +} + +.airband-btn.active:hover { + background: #dc2626; + box-shadow: 0 0 10px rgba(239, 68, 68, 0.3); } .airband-btn:disabled { diff --git a/static/css/index.css b/static/css/index.css index 7260b5a..3c8a341 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -4343,14 +4343,14 @@ body::before { } .radio-action-btn.scan { - background: var(--accent-cyan); - border-color: var(--accent-cyan); - color: var(--bg-primary); + background: var(--accent-green); + border-color: var(--accent-green); + color: #fff; } .radio-action-btn.scan:hover { - background: #5aa8ff; - box-shadow: 0 0 20px var(--accent-cyan-dim); + background: #1db954; + box-shadow: 0 0 20px rgba(34, 197, 94, 0.4); } .radio-action-btn.scan.active { diff --git a/static/css/satellite_dashboard.css b/static/css/satellite_dashboard.css index 12b4425..b6a4c2c 100644 --- a/static/css/satellite_dashboard.css +++ b/static/css/satellite_dashboard.css @@ -589,13 +589,14 @@ body { } .btn.primary { - background: var(--accent-cyan); - color: var(--bg-dark); + background: var(--accent-green); + color: #fff; margin-left: auto; } .btn.primary:hover { - box-shadow: 0 0 25px rgba(0, 212, 255, 0.5); + background: #1db954; + box-shadow: 0 0 25px rgba(34, 197, 94, 0.5); } /* Leaflet dark theme overrides */ diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index bd96590..049d5c3 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -2332,7 +2332,7 @@ sudo make install isAcarsRunning = true; acarsMessageCount = 0; document.getElementById('acarsToggleBtn').textContent = '■ STOP ACARS'; - document.getElementById('acarsToggleBtn').style.background = 'var(--accent-red)'; + document.getElementById('acarsToggleBtn').classList.add('active'); document.getElementById('acarsPanelIndicator').classList.add('active'); startAcarsStream(); } else { @@ -2348,7 +2348,7 @@ sudo make install .then(() => { isAcarsRunning = false; document.getElementById('acarsToggleBtn').textContent = '▶ START ACARS'; - document.getElementById('acarsToggleBtn').style.background = ''; + document.getElementById('acarsToggleBtn').classList.remove('active'); document.getElementById('acarsPanelIndicator').classList.remove('active'); if (acarsEventSource) { acarsEventSource.close(); diff --git a/templates/index.html b/templates/index.html index ea090fb..7ca5804 100644 --- a/templates/index.html +++ b/templates/index.html @@ -646,7 +646,7 @@ - @@ -892,8 +892,7 @@
@@ -911,6 +910,18 @@ + +
@@ -1017,7 +1028,7 @@
-

TSCM Sweep

+

TSCM Sweep Alpha

- -
@@ -1382,9 +1393,18 @@
- + +
@@ -1664,7 +1684,7 @@
- +
@@ -1820,6 +1840,72 @@ } + + +