mirror of
https://github.com/smittix/intercept.git
synced 2026-07-07 09:08:12 -07:00
Add Airspy SDR support and persist GPS coordinates
Airspy support: - Add AIRSPY to SDRType enum and driver mappings - Create AirspyCommandBuilder using SoapySDR tools (rx_fm, readsb, rtl_433) - Register in SDRFactory and add to hardware type dropdown - Supports Airspy R2/Mini (24MHz-1.8GHz) and HF+ devices GPS coordinate persistence: - Save observer location to localStorage when manually entered or via geolocation - Restore saved coordinates on page load in both index.html and adsb_dashboard.html - Coordinates are shared between both pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -141,6 +141,7 @@
|
||||
<option value="manual">Manual</option>
|
||||
<option value="browser">Browser</option>
|
||||
<option value="dongle">USB GPS</option>
|
||||
<option value="gpsd">gpsd</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group" id="browserGpsGroup">
|
||||
@@ -159,6 +160,13 @@
|
||||
<button class="gps-btn gps-connect-btn" onclick="startGpsDongle()">Connect</button>
|
||||
<button class="gps-btn gps-disconnect-btn" onclick="stopGpsDongle()" style="display: none; background: rgba(255,0,0,0.2); border-color: #ff4444;">Stop</button>
|
||||
</div>
|
||||
<div class="control-group gps-gpsd-controls" style="display: none;">
|
||||
<input type="text" id="gpsdHost" value="localhost" placeholder="Host" style="width: 80px; font-size: 10px;">
|
||||
<span style="color: #666;">:</span>
|
||||
<input type="number" id="gpsdPort" value="2947" min="1" max="65535" style="width: 50px; font-size: 10px;">
|
||||
<button class="gps-btn gps-connect-btn" onclick="startGpsdClient()">Connect</button>
|
||||
<button class="gps-btn gps-disconnect-btn" onclick="stopGpsDongle()" style="display: none; background: rgba(255,0,0,0.2); border-color: #ff4444;">Stop</button>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label style="display: flex; align-items: center; gap: 4px; font-size: 10px; cursor: pointer;">
|
||||
<input type="checkbox" id="useRemoteDump1090" onchange="toggleRemoteDump1090()">
|
||||
@@ -208,8 +216,17 @@
|
||||
messageTimestamps: []
|
||||
};
|
||||
|
||||
// Observer location and range rings
|
||||
let observerLocation = { lat: 51.5074, lon: -0.1278 };
|
||||
// Observer location and range rings (load from localStorage or default to London)
|
||||
let observerLocation = (function() {
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
let rangeRingsLayer = null;
|
||||
let observerMarker = null;
|
||||
|
||||
@@ -834,6 +851,10 @@
|
||||
if (!isNaN(lat) && !isNaN(lon) && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180) {
|
||||
observerLocation.lat = lat;
|
||||
observerLocation.lon = lon;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
|
||||
if (radarMap) {
|
||||
radarMap.setView([lat, lon], radarMap.getZoom());
|
||||
}
|
||||
@@ -858,6 +879,10 @@
|
||||
(position) => {
|
||||
observerLocation.lat = position.coords.latitude;
|
||||
observerLocation.lon = position.coords.longitude;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
|
||||
document.getElementById('obsLat').value = observerLocation.lat.toFixed(4);
|
||||
document.getElementById('obsLon').value = observerLocation.lon.toFixed(4);
|
||||
if (radarMap) {
|
||||
@@ -881,18 +906,22 @@
|
||||
const source = document.getElementById('gpsSource').value;
|
||||
const browserGroup = document.getElementById('browserGpsGroup');
|
||||
const dongleControls = document.querySelector('.gps-dongle-controls');
|
||||
const gpsdControls = document.querySelector('.gps-gpsd-controls');
|
||||
|
||||
// Hide all first
|
||||
browserGroup.style.display = 'none';
|
||||
dongleControls.style.display = 'none';
|
||||
gpsdControls.style.display = 'none';
|
||||
|
||||
if (source === 'dongle') {
|
||||
browserGroup.style.display = 'none';
|
||||
dongleControls.style.display = 'flex';
|
||||
refreshGpsDevices();
|
||||
} else if (source === 'browser') {
|
||||
browserGroup.style.display = 'flex';
|
||||
dongleControls.style.display = 'none';
|
||||
} else {
|
||||
browserGroup.style.display = 'none';
|
||||
dongleControls.style.display = 'none';
|
||||
} else if (source === 'gpsd') {
|
||||
gpsdControls.style.display = 'flex';
|
||||
}
|
||||
// 'manual' keeps everything hidden
|
||||
}
|
||||
|
||||
async function refreshGpsDevices() {
|
||||
@@ -935,8 +964,7 @@
|
||||
if (data.status === 'started') {
|
||||
gpsConnected = true;
|
||||
startGpsStream();
|
||||
document.querySelector('.gps-connect-btn').style.display = 'none';
|
||||
document.querySelector('.gps-disconnect-btn').style.display = 'block';
|
||||
updateGpsButtons(true, '.gps-dongle-controls');
|
||||
} else {
|
||||
alert('Failed to start GPS: ' + data.message);
|
||||
}
|
||||
@@ -945,6 +973,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function startGpsdClient() {
|
||||
const host = document.getElementById('gpsdHost').value || 'localhost';
|
||||
const port = parseInt(document.getElementById('gpsdPort').value) || 2947;
|
||||
|
||||
try {
|
||||
const response = await fetch('/gps/gpsd/start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ host: host, port: port })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'started') {
|
||||
gpsConnected = true;
|
||||
startGpsStream();
|
||||
updateGpsButtons(true, '.gps-gpsd-controls');
|
||||
} else {
|
||||
alert('Failed to connect to gpsd: ' + data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('gpsd connection error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updateGpsButtons(connected, containerSelector) {
|
||||
// Update buttons in the specified container
|
||||
const container = document.querySelector(containerSelector);
|
||||
if (container) {
|
||||
const connectBtn = container.querySelector('.gps-connect-btn');
|
||||
const disconnectBtn = container.querySelector('.gps-disconnect-btn');
|
||||
if (connectBtn) connectBtn.style.display = connected ? 'none' : 'block';
|
||||
if (disconnectBtn) disconnectBtn.style.display = connected ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function stopGpsDongle() {
|
||||
try {
|
||||
if (gpsEventSource) {
|
||||
@@ -953,8 +1016,9 @@
|
||||
}
|
||||
await fetch('/gps/stop', { method: 'POST' });
|
||||
gpsConnected = false;
|
||||
document.querySelector('.gps-connect-btn').style.display = 'block';
|
||||
document.querySelector('.gps-disconnect-btn').style.display = 'none';
|
||||
// Reset buttons in both containers
|
||||
updateGpsButtons(false, '.gps-dongle-controls');
|
||||
updateGpsButtons(false, '.gps-gpsd-controls');
|
||||
} catch (e) {
|
||||
console.warn('GPS stop error:', e);
|
||||
}
|
||||
@@ -1027,6 +1091,12 @@
|
||||
// INITIALIZATION
|
||||
// ============================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize observer location input fields from saved location
|
||||
const obsLatInput = document.getElementById('obsLat');
|
||||
const obsLonInput = document.getElementById('obsLon');
|
||||
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
|
||||
|
||||
initMap();
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
|
||||
+146
-30
@@ -275,6 +275,7 @@
|
||||
<option value="rtlsdr">RTL-SDR</option>
|
||||
<option value="limesdr">LimeSDR</option>
|
||||
<option value="hackrf">HackRF</option>
|
||||
<option value="airspy">Airspy</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -754,22 +755,36 @@
|
||||
📍 Use Browser Location
|
||||
</button>
|
||||
<div class="gps-dongle-section" style="display: none; margin-top: 8px; padding: 8px; background: rgba(0,212,255,0.05); border-radius: 4px;">
|
||||
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
|
||||
<select class="gps-device-select" style="flex: 1; font-size: 11px;">
|
||||
<option value="">Select GPS Device...</option>
|
||||
<div style="margin-bottom: 5px;">
|
||||
<select class="gps-source-select" onchange="toggleGpsSourceMode(this)" style="width: 100%; font-size: 11px;">
|
||||
<option value="serial">Serial Device</option>
|
||||
<option value="gpsd">gpsd (daemon)</option>
|
||||
</select>
|
||||
<button class="preset-btn" onclick="refreshGpsDevices()" style="padding: 2px 6px; font-size: 10px;" title="Refresh">🔄</button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
|
||||
<select class="gps-baudrate-select" style="flex: 1; font-size: 11px;">
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600" selected>9600</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="115200">115200</option>
|
||||
</select>
|
||||
<div class="gps-serial-controls">
|
||||
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
|
||||
<select class="gps-device-select" style="flex: 1; font-size: 11px;">
|
||||
<option value="">Select GPS Device...</option>
|
||||
</select>
|
||||
<button class="preset-btn" onclick="refreshGpsDevices()" style="padding: 2px 6px; font-size: 10px;" title="Refresh">🔄</button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
|
||||
<select class="gps-baudrate-select" style="flex: 1; font-size: 11px;">
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600" selected>9600</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="115200">115200</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gps-gpsd-controls" style="display: none;">
|
||||
<div style="display: flex; gap: 5px; margin-bottom: 5px;">
|
||||
<input type="text" class="gpsd-host-input" value="localhost" placeholder="Host" style="flex: 2; font-size: 11px;">
|
||||
<input type="number" class="gpsd-port-input" value="2947" placeholder="Port" style="flex: 1; font-size: 11px;">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 5px;">
|
||||
<button class="preset-btn gps-connect-btn" onclick="startGpsDongle(this.closest('.gps-dongle-section').querySelector('.gps-device-select').value, parseInt(this.closest('.gps-dongle-section').querySelector('.gps-baudrate-select').value))" style="flex: 1; font-size: 10px; padding: 4px;">
|
||||
<button class="preset-btn gps-connect-btn" onclick="startGpsFromSection(this.closest('.gps-dongle-section'))" style="flex: 1; font-size: 10px; padding: 4px;">
|
||||
Connect
|
||||
</button>
|
||||
<button class="preset-btn gps-disconnect-btn" onclick="stopGpsDongle()" style="flex: 1; display: none; font-size: 10px; padding: 4px; background: rgba(255,0,0,0.1); border-color: #ff4444;">
|
||||
@@ -865,25 +880,44 @@
|
||||
</button>
|
||||
<div class="gps-dongle-section" style="display: none; margin-top: 10px; padding: 10px; background: rgba(0,212,255,0.05); border-radius: 4px;">
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label style="font-size: 11px;">GPS Device</label>
|
||||
<div style="display: flex; gap: 5px;">
|
||||
<select class="gps-device-select" style="flex: 1;">
|
||||
<option value="">Select GPS Device...</option>
|
||||
</select>
|
||||
<button class="preset-btn" onclick="refreshGpsDevices()" style="padding: 4px 8px;" title="Refresh">🔄</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label style="font-size: 11px;">Baud Rate</label>
|
||||
<select class="gps-baudrate-select" style="width: 100%;">
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600" selected>9600</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="115200">115200</option>
|
||||
<label style="font-size: 11px;">GPS Source</label>
|
||||
<select class="gps-source-select" onchange="toggleGpsSourceMode(this)" style="width: 100%;">
|
||||
<option value="serial">Serial Device</option>
|
||||
<option value="gpsd">gpsd (daemon)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="gps-serial-controls">
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label style="font-size: 11px;">GPS Device</label>
|
||||
<div style="display: flex; gap: 5px;">
|
||||
<select class="gps-device-select" style="flex: 1;">
|
||||
<option value="">Select GPS Device...</option>
|
||||
</select>
|
||||
<button class="preset-btn" onclick="refreshGpsDevices()" style="padding: 4px 8px;" title="Refresh">🔄</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label style="font-size: 11px;">Baud Rate</label>
|
||||
<select class="gps-baudrate-select" style="width: 100%;">
|
||||
<option value="4800">4800</option>
|
||||
<option value="9600" selected>9600</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="115200">115200</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gps-gpsd-controls" style="display: none;">
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label style="font-size: 11px;">gpsd Host</label>
|
||||
<input type="text" class="gpsd-host-input" value="localhost" style="width: 100%;">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label style="font-size: 11px;">gpsd Port</label>
|
||||
<input type="number" class="gpsd-port-input" value="2947" style="width: 100%;">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 5px;">
|
||||
<button class="preset-btn gps-connect-btn" onclick="startGpsDongle(this.closest('.gps-dongle-section').querySelector('.gps-device-select').value, parseInt(this.closest('.gps-dongle-section').querySelector('.gps-baudrate-select').value))" style="flex: 1;">
|
||||
<button class="preset-btn gps-connect-btn" onclick="startGpsFromSection(this.closest('.gps-dongle-section'))" style="flex: 1;">
|
||||
Connect GPS
|
||||
</button>
|
||||
<button class="preset-btn gps-disconnect-btn" onclick="stopGpsDongle()" style="flex: 1; display: none; background: rgba(255,0,0,0.1); border-color: #ff4444;">
|
||||
@@ -1472,8 +1506,17 @@
|
||||
sessionStart: null // When tracking started
|
||||
};
|
||||
|
||||
// Observer location for distance calculations
|
||||
let observerLocation = { lat: 51.5074, lon: -0.1278 }; // Default London
|
||||
// Observer location for distance calculations (load from localStorage or default to London)
|
||||
let observerLocation = (function() {
|
||||
const saved = localStorage.getItem('observerLocation');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (parsed.lat && parsed.lon) return parsed;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { lat: 51.5074, lon: -0.1278 };
|
||||
})();
|
||||
let rangeRingsLayer = null;
|
||||
let observerMarkerAdsb = null;
|
||||
|
||||
@@ -1804,6 +1847,16 @@
|
||||
this.parentElement.classList.toggle('collapsed');
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize observer location input fields from saved location
|
||||
const adsbLatInput = document.getElementById('adsbObsLat');
|
||||
const adsbLonInput = document.getElementById('adsbObsLon');
|
||||
const obsLatInput = document.getElementById('obsLat');
|
||||
const obsLonInput = document.getElementById('obsLon');
|
||||
if (adsbLatInput) adsbLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (adsbLonInput) adsbLonInput.value = observerLocation.lon.toFixed(4);
|
||||
if (obsLatInput) obsLatInput.value = observerLocation.lat.toFixed(4);
|
||||
if (obsLonInput) obsLonInput.value = observerLocation.lon.toFixed(4);
|
||||
});
|
||||
|
||||
// Toggle section collapse
|
||||
@@ -6025,6 +6078,9 @@
|
||||
observerLocation.lat = lat;
|
||||
observerLocation.lon = lon;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
|
||||
// Center map on location
|
||||
if (aircraftMap) {
|
||||
aircraftMap.setView([observerLocation.lat, observerLocation.lon], 8);
|
||||
@@ -6058,6 +6114,9 @@
|
||||
observerLocation.lat = position.coords.latitude;
|
||||
observerLocation.lon = position.coords.longitude;
|
||||
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('observerLocation', JSON.stringify(observerLocation));
|
||||
|
||||
// Update input fields
|
||||
const latInput = document.getElementById('adsbObsLat');
|
||||
const lonInput = document.getElementById('adsbObsLon');
|
||||
@@ -6310,6 +6369,63 @@
|
||||
});
|
||||
}
|
||||
|
||||
function toggleGpsSourceMode(selectElement) {
|
||||
// Toggle between serial and gpsd controls
|
||||
const section = selectElement.closest('.gps-dongle-section');
|
||||
const serialControls = section.querySelector('.gps-serial-controls');
|
||||
const gpsdControls = section.querySelector('.gps-gpsd-controls');
|
||||
const source = selectElement.value;
|
||||
|
||||
if (source === 'gpsd') {
|
||||
serialControls.style.display = 'none';
|
||||
gpsdControls.style.display = 'block';
|
||||
} else {
|
||||
serialControls.style.display = 'block';
|
||||
gpsdControls.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function startGpsFromSection(section) {
|
||||
// Start GPS based on the selected source in the section
|
||||
const sourceSelect = section.querySelector('.gps-source-select');
|
||||
const source = sourceSelect ? sourceSelect.value : 'serial';
|
||||
|
||||
if (source === 'gpsd') {
|
||||
const host = section.querySelector('.gpsd-host-input').value || 'localhost';
|
||||
const port = parseInt(section.querySelector('.gpsd-port-input').value) || 2947;
|
||||
return await startGpsd(host, port);
|
||||
} else {
|
||||
const devicePath = section.querySelector('.gps-device-select').value;
|
||||
const baudrate = parseInt(section.querySelector('.gps-baudrate-select').value) || 9600;
|
||||
return await startGpsDongle(devicePath, baudrate);
|
||||
}
|
||||
}
|
||||
|
||||
async function startGpsd(host = 'localhost', port = 2947) {
|
||||
try {
|
||||
const response = await fetch('/gps/gpsd/start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ host: host, port: port })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'started') {
|
||||
gpsConnected = true;
|
||||
startGpsStream();
|
||||
updateGpsStatus(true);
|
||||
showInfo(`Connected to gpsd at ${host}:${port}`);
|
||||
return true;
|
||||
} else {
|
||||
showError('Failed to connect to gpsd: ' + data.message);
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
showError('gpsd connection error: ' + e.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function startGpsDongle(devicePath, baudrate = 9600) {
|
||||
if (!devicePath) {
|
||||
showError('Please select a GPS device');
|
||||
|
||||
Reference in New Issue
Block a user