feat(export): AIS UDP NMEA forward and JSON export endpoints for AIS/ADS-B

AIS:
- New optional NMEA UDP forwarding via AIS-catcher's -u flag, configurable
  from the AIS sidebar (host + port). Lets OpenCPN and other NMEA tools
  receive live vessel data directly. All SDR builders updated.
- New GET /ais/vessels endpoint — clean JSON snapshot of tracked vessels
  for REST integration

ADS-B:
- New GET /adsb/aircraft endpoint — JSON snapshot of all tracked aircraft,
  with optional ?icao= and ?military=true filters. Response includes a
  reminder that port 30003 (SBS) is already available for tools like
  Virtual Radar Server and OpenCPN's AIS/target plugin.

Closes #90
This commit is contained in:
James Smith
2026-04-05 16:20:10 +01:00
parent 50de9a9932
commit a1af859cd1
9 changed files with 138 additions and 8 deletions
+35
View File
@@ -803,6 +803,41 @@ def adsb_status():
})
@adsb_bp.route('/aircraft')
def adsb_aircraft_export():
"""Export current ADS-B aircraft data as JSON.
Returns a snapshot of all tracked aircraft suitable for integration
with external tools. For SBS (BaseStation) format, connect directly
to port 30003 which dump1090 exposes natively.
Query parameters:
icao: Filter to a specific ICAO hex code (optional)
military: 'true' to return only military aircraft (optional)
Returns:
JSON with aircraft list and metadata.
"""
aircraft = dict(app_module.adsb_aircraft)
icao_filter = request.args.get('icao', '').upper()
if icao_filter:
aircraft = {k: v for k, v in aircraft.items() if k.upper() == icao_filter}
if request.args.get('military') == 'true':
try:
from utils.military_icao import is_military_icao
aircraft = {k: v for k, v in aircraft.items() if is_military_icao(k)}
except ImportError:
pass
return jsonify({
'count': len(aircraft),
'aircraft': list(aircraft.values()),
'sbs_port': 30003, # dump1090 SBS stream for tools like Virtual Radar Server
})
@adsb_bp.route('/session')
def adsb_session():
"""Get ADS-B session status and uptime."""
+39 -1
View File
@@ -408,11 +408,24 @@ def start_ais():
bias_t = data.get('bias_t', False)
tcp_port = AIS_TCP_PORT
# Optional UDP NMEA forwarding (e.g. for OpenCPN on port 10110)
udp_host = data.get('udp_host') or None
udp_port = None
if udp_host:
try:
udp_port = int(data.get('udp_port', 10110))
if not 1 <= udp_port <= 65535:
raise ValueError
except (TypeError, ValueError):
return api_error('Invalid udp_port (1-65535)', 400)
cmd = builder.build_ais_command(
device=sdr_device,
gain=float(gain),
bias_t=bias_t,
tcp_port=tcp_port
tcp_port=tcp_port,
udp_host=udp_host,
udp_port=udp_port,
)
# Use the found AIS-catcher path
@@ -535,6 +548,31 @@ def get_vessel_dsc(mmsi: str):
return api_success(data={'mmsi': mmsi, 'dsc_messages': matches})
@ais_bp.route('/vessels')
def ais_vessels():
"""Export current AIS vessel data as JSON.
Returns a snapshot of all tracked vessels suitable for integration
with external tools (OpenCPN, ship tracking apps, etc.).
Query parameters:
mmsi: Filter to a specific MMSI (optional)
Returns:
JSON with vessel list and metadata.
"""
vessels = dict(app_module.ais_vessels)
mmsi_filter = request.args.get('mmsi')
if mmsi_filter:
vessels = {k: v for k, v in vessels.items() if str(k) == str(mmsi_filter)}
return jsonify({
'count': len(vessels),
'vessels': list(vessels.values()),
})
@ais_bp.route('/dashboard')
def ais_dashboard():
"""Popout AIS dashboard."""