mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
241 lines
11 KiB
HTML
241 lines
11 KiB
HTML
<!-- VDL2 AIRCRAFT DATALINK MODE -->
|
|
<div id="vdl2Mode" class="mode-content" style="display: none;">
|
|
<div class="section">
|
|
<h3>VDL2 Datalink</h3>
|
|
<div class="info-text" style="margin-bottom: 15px;">
|
|
Decode VDL Mode 2 (VHF Digital Link) messages on ~136 MHz. VDL2 is the digital successor to ACARS, using D8PSK modulation for higher throughput aircraft datalink communications.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Region & Frequencies</h3>
|
|
<div class="form-group">
|
|
<label>Region</label>
|
|
<select id="vdl2RegionSelect" onchange="updateVdl2MainFreqs()" style="width: 100%;">
|
|
<option value="na">North America</option>
|
|
<option value="eu">Europe</option>
|
|
<option value="ap">Asia-Pacific</option>
|
|
</select>
|
|
</div>
|
|
<div id="vdl2MainFreqSelector" style="display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; font-size: 11px;">
|
|
<!-- Populated by JS -->
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Gain (dB, 0 = auto)</label>
|
|
<input type="number" id="vdl2GainInput" value="40" min="0" max="50" placeholder="0-50">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>Status</h3>
|
|
<div id="vdl2StatusDisplay" class="info-text">
|
|
<p>Status: <span id="vdl2StatusText" style="color: var(--accent-yellow);">Standby</span></p>
|
|
<p>Messages: <span id="vdl2MessageCount">0</span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Antenna Guide -->
|
|
<div class="section">
|
|
<h3>Antenna Guide</h3>
|
|
<div style="font-size: 11px; color: var(--text-dim); line-height: 1.5;">
|
|
<p style="margin-bottom: 8px; color: var(--accent-cyan); font-weight: 600;">
|
|
VHF Airband (~137 MHz) — stock SDR antenna may work at close range
|
|
</p>
|
|
|
|
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; margin-bottom: 10px;">
|
|
<strong style="color: var(--accent-cyan); font-size: 12px;">Simple Dipole</strong>
|
|
<ul style="margin: 6px 0 0 14px; padding: 0;">
|
|
<li><strong style="color: var(--text-primary);">Element length:</strong> ~55 cm each (quarter-wave at 137 MHz)</li>
|
|
<li><strong style="color: var(--text-primary);">Material:</strong> Wire, coat hanger, or copper rod</li>
|
|
<li><strong style="color: var(--text-primary);">Orientation:</strong> Vertical (airband is vertically polarized)</li>
|
|
<li><strong style="color: var(--text-primary);">Placement:</strong> Outdoors, as high as possible with clear sky view</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div style="background: var(--bg-primary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px;">
|
|
<strong style="color: var(--accent-cyan); font-size: 12px;">Quick Reference</strong>
|
|
<table style="width: 100%; margin-top: 6px; font-size: 10px; border-collapse: collapse;">
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Primary (worldwide)</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">136.975 MHz</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Quarter-wave length</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">55 cm</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Modulation</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">D8PSK 31.5 kbps</td>
|
|
</tr>
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Bandwidth</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">25 kHz</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 3px 4px; color: var(--text-dim);">Polarization</td>
|
|
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">Vertical</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="run-btn" id="startVdl2Btn" onclick="startVdl2Mode()">
|
|
Start VDL2
|
|
</button>
|
|
<button class="stop-btn" id="stopVdl2Btn" onclick="stopVdl2Mode()" style="display: none;">
|
|
Stop VDL2
|
|
</button>
|
|
</div>
|
|
|
|
<script>
|
|
let vdl2MainEventSource = null;
|
|
let vdl2MainMsgCount = 0;
|
|
|
|
// VDL2 frequencies in Hz (as required by dumpvdl2)
|
|
const vdl2MainFrequencies = {
|
|
'na': ['136975000', '136100000', '136650000', '136700000', '136800000'],
|
|
'eu': ['136975000', '136675000', '136725000', '136775000', '136825000'],
|
|
'ap': ['136975000', '136900000']
|
|
};
|
|
|
|
// Display-friendly MHz labels
|
|
const vdl2FreqLabels = {
|
|
'136975000': '136.975',
|
|
'136100000': '136.100',
|
|
'136650000': '136.650',
|
|
'136700000': '136.700',
|
|
'136800000': '136.800',
|
|
'136675000': '136.675',
|
|
'136725000': '136.725',
|
|
'136775000': '136.775',
|
|
'136825000': '136.825',
|
|
'136900000': '136.900'
|
|
};
|
|
|
|
function updateVdl2MainFreqs() {
|
|
const region = document.getElementById('vdl2RegionSelect').value;
|
|
const freqs = vdl2MainFrequencies[region] || vdl2MainFrequencies['na'];
|
|
const container = document.getElementById('vdl2MainFreqSelector');
|
|
|
|
const previouslyChecked = new Set();
|
|
container.querySelectorAll('input:checked').forEach(cb => previouslyChecked.add(cb.value));
|
|
|
|
container.innerHTML = freqs.map(freq => {
|
|
const checked = previouslyChecked.size === 0 || previouslyChecked.has(freq) ? 'checked' : '';
|
|
const label = vdl2FreqLabels[freq] || freq;
|
|
return `
|
|
<label style="display: flex; align-items: center; gap: 3px; padding: 2px 6px; background: var(--bg-secondary); border-radius: 3px; cursor: pointer;">
|
|
<input type="checkbox" class="vdl2-main-freq-cb" value="${freq}" ${checked} style="margin: 0; cursor: pointer;">
|
|
<span>${label}</span>
|
|
</label>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function getVdl2MainSelectedFreqs() {
|
|
const checkboxes = document.querySelectorAll('.vdl2-main-freq-cb:checked');
|
|
const selected = Array.from(checkboxes).map(cb => cb.value);
|
|
if (selected.length === 0) {
|
|
const region = document.getElementById('vdl2RegionSelect').value;
|
|
return vdl2MainFrequencies[region] || vdl2MainFrequencies['na'];
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
function startVdl2Mode() {
|
|
const gain = document.getElementById('vdl2GainInput').value || '40';
|
|
const device = document.getElementById('deviceSelect')?.value || '0';
|
|
const frequencies = getVdl2MainSelectedFreqs();
|
|
|
|
fetch('/vdl2/start', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ device, gain, frequencies })
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.status === 'started') {
|
|
document.getElementById('startVdl2Btn').style.display = 'none';
|
|
document.getElementById('stopVdl2Btn').style.display = 'block';
|
|
document.getElementById('vdl2StatusText').textContent = 'Listening';
|
|
document.getElementById('vdl2StatusText').style.color = 'var(--accent-green)';
|
|
vdl2MainMsgCount = 0;
|
|
startVdl2MainSSE();
|
|
} else {
|
|
alert(data.message || 'Failed to start VDL2');
|
|
}
|
|
})
|
|
.catch(err => alert('Error: ' + err.message));
|
|
}
|
|
|
|
function stopVdl2Mode() {
|
|
fetch('/vdl2/stop', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ source: 'vdl2_mode' })
|
|
})
|
|
.then(async (r) => {
|
|
const text = await r.text();
|
|
const data = text ? JSON.parse(text) : {};
|
|
if (!r.ok || (data.status !== 'stopped' && data.status !== 'success')) {
|
|
throw new Error(data.message || `HTTP ${r.status}`);
|
|
}
|
|
return data;
|
|
})
|
|
.then(() => {
|
|
document.getElementById('startVdl2Btn').style.display = 'block';
|
|
document.getElementById('stopVdl2Btn').style.display = 'none';
|
|
document.getElementById('vdl2StatusText').textContent = 'Standby';
|
|
document.getElementById('vdl2StatusText').style.color = 'var(--accent-yellow)';
|
|
if (vdl2MainEventSource) {
|
|
vdl2MainEventSource.close();
|
|
vdl2MainEventSource = null;
|
|
}
|
|
})
|
|
.catch(err => alert('Failed to stop VDL2: ' + err.message));
|
|
}
|
|
|
|
function startVdl2MainSSE() {
|
|
if (vdl2MainEventSource) vdl2MainEventSource.close();
|
|
|
|
vdl2MainEventSource = new EventSource('/vdl2/stream');
|
|
vdl2MainEventSource.onmessage = function(e) {
|
|
try {
|
|
const data = JSON.parse(e.data);
|
|
if (data.type === 'vdl2') {
|
|
vdl2MainMsgCount++;
|
|
document.getElementById('vdl2MessageCount').textContent = vdl2MainMsgCount;
|
|
}
|
|
} catch (err) {}
|
|
};
|
|
|
|
vdl2MainEventSource.onerror = function() {
|
|
setTimeout(() => {
|
|
if (document.getElementById('stopVdl2Btn').style.display === 'block') {
|
|
startVdl2MainSSE();
|
|
}
|
|
}, 2000);
|
|
};
|
|
}
|
|
|
|
// Check initial status
|
|
fetch('/vdl2/status')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.running) {
|
|
document.getElementById('startVdl2Btn').style.display = 'none';
|
|
document.getElementById('stopVdl2Btn').style.display = 'block';
|
|
document.getElementById('vdl2StatusText').textContent = 'Listening';
|
|
document.getElementById('vdl2StatusText').style.color = 'var(--accent-green)';
|
|
document.getElementById('vdl2MessageCount').textContent = data.message_count || 0;
|
|
vdl2MainMsgCount = data.message_count || 0;
|
|
startVdl2MainSSE();
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
|
|
// Initialize frequency checkboxes
|
|
document.addEventListener('DOMContentLoaded', () => updateVdl2MainFreqs());
|
|
</script>
|