mirror of
https://github.com/smittix/intercept.git
synced 2026-07-15 04:58:10 -07:00
Fix remote VDL2 streaming path and improve decoder reliability
This commit is contained in:
+56
-31
@@ -1,13 +1,17 @@
|
|||||||
"""VDL2 aircraft datalink routes."""
|
"""VDL2 aircraft datalink routes."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import io
|
||||||
import queue
|
import json
|
||||||
import shutil
|
import os
|
||||||
import subprocess
|
import platform
|
||||||
import threading
|
import pty
|
||||||
import time
|
import queue
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
@@ -51,17 +55,22 @@ def find_dumpvdl2():
|
|||||||
return shutil.which('dumpvdl2')
|
return shutil.which('dumpvdl2')
|
||||||
|
|
||||||
|
|
||||||
def stream_vdl2_output(process: subprocess.Popen) -> 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'})
|
||||||
|
|
||||||
for line in iter(process.stdout.readline, b''):
|
# Use appropriate sentinel based on mode (text mode for pty on macOS)
|
||||||
line = line.decode('utf-8', errors='replace').strip()
|
sentinel = '' if is_text_mode else b''
|
||||||
if not line:
|
for line in iter(process.stdout.readline, sentinel):
|
||||||
continue
|
if is_text_mode:
|
||||||
|
line = line.strip()
|
||||||
|
else:
|
||||||
|
line = line.decode('utf-8', errors='replace').strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(line)
|
data = json.loads(line)
|
||||||
@@ -243,12 +252,28 @@ def start_vdl2() -> Response:
|
|||||||
logger.info(f"Starting VDL2 decoder: {' '.join(cmd)}")
|
logger.info(f"Starting VDL2 decoder: {' '.join(cmd)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(
|
is_text_mode = False
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
# On macOS, use pty to avoid stdout buffering issues
|
||||||
stderr=subprocess.PIPE,
|
if platform.system() == 'Darwin':
|
||||||
start_new_session=True
|
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
|
# Wait briefly to check if process started
|
||||||
time.sleep(PROCESS_START_WAIT)
|
time.sleep(PROCESS_START_WAIT)
|
||||||
@@ -270,12 +295,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,),
|
args=(process, is_text_mode),
|
||||||
daemon=True
|
daemon=True
|
||||||
)
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
|||||||
@@ -485,7 +485,7 @@ async function syncLocalModeStates() {
|
|||||||
*/
|
*/
|
||||||
function showAgentModeWarnings(runningModes, modesDetail = {}) {
|
function showAgentModeWarnings(runningModes, modesDetail = {}) {
|
||||||
// SDR modes that can't run simultaneously on same device
|
// SDR modes that can't run simultaneously on same device
|
||||||
const sdrModes = ['sensor', 'pager', 'adsb', 'ais', 'acars', 'aprs', 'rtlamr', 'listening_post', 'tscm', 'dsc'];
|
const sdrModes = ['sensor', 'pager', 'adsb', 'ais', 'acars', 'vdl2', 'aprs', 'rtlamr', 'listening_post', 'tscm', 'dsc'];
|
||||||
const runningSdrModes = runningModes.filter(m => sdrModes.includes(m));
|
const runningSdrModes = runningModes.filter(m => sdrModes.includes(m));
|
||||||
|
|
||||||
let warning = document.getElementById('agentModeWarning');
|
let warning = document.getElementById('agentModeWarning');
|
||||||
@@ -621,7 +621,7 @@ function checkAgentModeConflict(modeToStart, deviceToUse = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sdrModes = ['sensor', 'pager', 'adsb', 'ais', 'acars', 'aprs', 'rtlamr', 'listening_post', 'tscm', 'dsc'];
|
const sdrModes = ['sensor', 'pager', 'adsb', 'ais', 'acars', 'vdl2', 'aprs', 'rtlamr', 'listening_post', 'tscm', 'dsc'];
|
||||||
|
|
||||||
// If we're trying to start an SDR mode
|
// If we're trying to start an SDR mode
|
||||||
if (sdrModes.includes(modeToStart)) {
|
if (sdrModes.includes(modeToStart)) {
|
||||||
|
|||||||
@@ -3879,36 +3879,37 @@ sudo make install</code>
|
|||||||
function startVdl2Stream(isAgentMode = false) {
|
function startVdl2Stream(isAgentMode = false) {
|
||||||
if (vdl2EventSource) vdl2EventSource.close();
|
if (vdl2EventSource) vdl2EventSource.close();
|
||||||
|
|
||||||
// Use different stream endpoint for agent mode
|
// For remote agent mode, stream directly from the selected agent via controller proxy.
|
||||||
const streamUrl = isAgentMode ? '/controller/stream/all' : '/vdl2/stream';
|
// This does not depend on push ingestion being enabled.
|
||||||
|
const streamUrl = isAgentMode && vdl2CurrentAgent !== null
|
||||||
|
? `/controller/agents/${vdl2CurrentAgent}/vdl2/stream`
|
||||||
|
: '/vdl2/stream';
|
||||||
vdl2EventSource = new EventSource(streamUrl);
|
vdl2EventSource = new EventSource(streamUrl);
|
||||||
|
|
||||||
vdl2EventSource.onmessage = function(e) {
|
vdl2EventSource.onmessage = function(e) {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
let message = null;
|
||||||
|
|
||||||
if (isAgentMode) {
|
// Backward compatibility: handle wrapped multi-agent payloads if encountered.
|
||||||
// Handle multi-agent stream format
|
if (data.scan_type === 'vdl2' && data.payload && data.payload.type === 'vdl2') {
|
||||||
if (data.scan_type === 'vdl2' && data.payload) {
|
message = data.payload;
|
||||||
const payload = data.payload;
|
if (isAgentMode) {
|
||||||
if (payload.type === 'vdl2') {
|
message.agent_name = data.agent_name || message.agent_name || 'Remote Agent';
|
||||||
vdl2MessageCount++;
|
|
||||||
if (typeof stats !== 'undefined') stats.vdl2Messages = (stats.vdl2Messages || 0) + 1;
|
|
||||||
document.getElementById('vdl2Count').textContent = vdl2MessageCount;
|
|
||||||
document.getElementById('stripVdl2').textContent = vdl2MessageCount;
|
|
||||||
payload.agent_name = data.agent_name;
|
|
||||||
addVdl2Message(payload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else if (data.type === 'vdl2') {
|
||||||
// Local stream format
|
message = data;
|
||||||
if (data.type === 'vdl2') {
|
if (isAgentMode && !message.agent_name) {
|
||||||
vdl2MessageCount++;
|
message.agent_name = 'Remote Agent';
|
||||||
if (typeof stats !== 'undefined') stats.vdl2Messages = (stats.vdl2Messages || 0) + 1;
|
|
||||||
document.getElementById('vdl2Count').textContent = vdl2MessageCount;
|
|
||||||
document.getElementById('stripVdl2').textContent = vdl2MessageCount;
|
|
||||||
addVdl2Message(data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
vdl2MessageCount++;
|
||||||
|
if (typeof stats !== 'undefined') stats.vdl2Messages = (stats.vdl2Messages || 0) + 1;
|
||||||
|
document.getElementById('vdl2Count').textContent = vdl2MessageCount;
|
||||||
|
document.getElementById('stripVdl2').textContent = vdl2MessageCount;
|
||||||
|
addVdl2Message(message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vdl2EventSource.onerror = function() {
|
vdl2EventSource.onerror = function() {
|
||||||
@@ -3919,52 +3920,6 @@ sudo make install</code>
|
|||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start polling fallback for agent mode
|
|
||||||
if (isAgentMode) {
|
|
||||||
startVdl2Polling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track last VDL2 message count for polling
|
|
||||||
let lastVdl2MessageCount = 0;
|
|
||||||
|
|
||||||
function startVdl2Polling() {
|
|
||||||
if (vdl2PollTimer) return;
|
|
||||||
lastVdl2MessageCount = 0;
|
|
||||||
|
|
||||||
const pollInterval = 2000;
|
|
||||||
vdl2PollTimer = setInterval(async () => {
|
|
||||||
if (!isVdl2Running || !vdl2CurrentAgent) {
|
|
||||||
clearInterval(vdl2PollTimer);
|
|
||||||
vdl2PollTimer = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/controller/agents/${vdl2CurrentAgent}/vdl2/data`);
|
|
||||||
if (!response.ok) return;
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
const result = data.result || data;
|
|
||||||
const messages = result.data || [];
|
|
||||||
|
|
||||||
if (messages.length > lastVdl2MessageCount) {
|
|
||||||
const newMessages = messages.slice(lastVdl2MessageCount);
|
|
||||||
newMessages.forEach(msg => {
|
|
||||||
vdl2MessageCount++;
|
|
||||||
if (typeof stats !== 'undefined') stats.vdl2Messages = (stats.vdl2Messages || 0) + 1;
|
|
||||||
document.getElementById('vdl2Count').textContent = vdl2MessageCount;
|
|
||||||
document.getElementById('stripVdl2').textContent = vdl2MessageCount;
|
|
||||||
msg.agent_name = result.agent_name || 'Remote Agent';
|
|
||||||
addVdl2Message(msg);
|
|
||||||
});
|
|
||||||
lastVdl2MessageCount = messages.length;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('VDL2 polling error:', err);
|
|
||||||
}
|
|
||||||
}, pollInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
|
|||||||
Reference in New Issue
Block a user