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:
Smittix
2026-01-05 20:39:55 +00:00
parent ea54ab4bce
commit 938d021e06
8 changed files with 756 additions and 55 deletions
+88 -8
View File
@@ -16,9 +16,11 @@ from utils.gps import (
is_serial_available,
get_gps_reader,
start_gps,
start_gpsd,
stop_gps,
get_current_position,
GPSPosition,
GPSDClient,
)
logger = get_logger('intercept.gps')
@@ -51,6 +53,34 @@ def check_gps_available():
})
@gps_bp.route('/gpsd/check')
def check_gpsd_available():
"""Check if gpsd is reachable."""
import socket
host = request.args.get('host', 'localhost')
port = int(request.args.get('port', 2947))
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2.0)
sock.connect((host, port))
sock.close()
return jsonify({
'available': True,
'host': host,
'port': port,
'message': f'gpsd reachable at {host}:{port}'
})
except Exception as e:
return jsonify({
'available': False,
'host': host,
'port': port,
'message': f'Cannot connect to gpsd at {host}:{port}: {e}'
})
@gps_bp.route('/devices')
def list_gps_devices():
"""List available GPS serial devices."""
@@ -109,19 +139,15 @@ def start_gps_reader():
except queue.Empty:
break
# Start the GPS reader
success = start_gps(device_path, baudrate)
# Start the GPS reader with callback pre-registered (avoids race condition)
success = start_gps(device_path, baudrate, callback=_position_callback)
if success:
# Register callback for SSE streaming
reader = get_gps_reader()
if reader:
reader.add_callback(_position_callback)
return jsonify({
'status': 'started',
'device': device_path,
'baudrate': baudrate
'baudrate': baudrate,
'source': 'serial'
})
else:
reader = get_gps_reader()
@@ -132,6 +158,58 @@ def start_gps_reader():
}), 500
@gps_bp.route('/gpsd/start', methods=['POST'])
def start_gpsd_client():
"""Start GPS client connected to gpsd."""
# Check if already running
reader = get_gps_reader()
if reader and reader.is_running:
return jsonify({
'status': 'error',
'message': 'GPS reader already running'
}), 409
data = request.json or {}
host = data.get('host', 'localhost')
port = data.get('port', 2947)
# Validate port
try:
port = int(port)
if not (1 <= port <= 65535):
raise ValueError("Port out of range")
except (ValueError, TypeError):
return jsonify({
'status': 'error',
'message': 'Invalid port number'
}), 400
# Clear the queue
while not _gps_queue.empty():
try:
_gps_queue.get_nowait()
except queue.Empty:
break
# Start the gpsd client with callback pre-registered
success = start_gpsd(host, port, callback=_position_callback)
if success:
return jsonify({
'status': 'started',
'host': host,
'port': port,
'source': 'gpsd'
})
else:
reader = get_gps_reader()
error = reader.error if reader else 'Unknown error'
return jsonify({
'status': 'error',
'message': f'Failed to connect to gpsd: {error}'
}), 500
@gps_bp.route('/stop', methods=['POST'])
def stop_gps_reader():
"""Stop GPS reader."""
@@ -205,8 +283,10 @@ def debug_gps():
})
position = reader.position
source = 'gpsd' if isinstance(reader, GPSDClient) else 'serial'
return jsonify({
'running': reader.is_running,
'source': source,
'device': reader.device_path,
'baudrate': reader.baudrate,
'has_position': position is not None,