From 9f391527c209d075cdd9812702129f7962134e80 Mon Sep 17 00:00:00 2001 From: Smittix Date: Thu, 15 Jan 2026 22:13:30 +0000 Subject: [PATCH] Fix RTL-SDR device conflicts when running ADS-B and airband simultaneously Problems fixed: 1. Added start_new_session=True to dump1090 Popen - creates proper process group for clean shutdown 2. Use os.killpg() to kill entire process group when stopping ADS-B - ensures child processes are terminated and device is released 3. Track active device index in adsb_active_device for debugging 4. Add device info to /adsb/status endpoint 5. Add logging when starting/stopping ADS-B with device info These changes ensure the RTL-SDR device is properly released when ADS-B stops, allowing another process (e.g., airband) to use a different device. Co-Authored-By: Claude Opus 4.5 --- routes/adsb.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/routes/adsb.py b/routes/adsb.py index 6525f98..d63ce3e 100644 --- a/routes/adsb.py +++ b/routes/adsb.py @@ -46,6 +46,7 @@ adsb_messages_received = 0 adsb_last_message_time = None adsb_bytes_received = 0 adsb_lines_received = 0 +adsb_active_device = None # Track which device index is being used # Track ICAOs already looked up in aircraft database (avoid repeated lookups) _looked_up_icaos: set[str] = set() @@ -286,6 +287,7 @@ def adsb_status(): return jsonify({ 'tracking_active': adsb_using_service, + 'active_device': adsb_active_device, 'connected_to_sbs': adsb_connected, 'messages_received': adsb_messages_received, 'bytes_received': adsb_bytes_received, @@ -303,7 +305,7 @@ def adsb_status(): @adsb_bp.route('/start', methods=['POST']) def start_adsb(): """Start ADS-B tracking.""" - global adsb_using_service + global adsb_using_service, adsb_active_device with app_module.adsb_lock: if adsb_using_service: @@ -364,17 +366,20 @@ def start_adsb(): if not dump1090_path: return jsonify({'status': 'error', 'message': f'readsb or dump1090 not found for {sdr_type.value}. Install readsb with SoapySDR support.'}) - # Kill any stale app-started process + # Kill any stale app-started process (use process group to ensure full cleanup) if app_module.adsb_process: try: - app_module.adsb_process.terminate() + pgid = os.getpgid(app_module.adsb_process.pid) + os.killpg(pgid, 15) # SIGTERM app_module.adsb_process.wait(timeout=PROCESS_TERMINATE_TIMEOUT) - except (subprocess.TimeoutExpired, OSError): + except (subprocess.TimeoutExpired, ProcessLookupError, OSError): try: - app_module.adsb_process.kill() - except OSError: + pgid = os.getpgid(app_module.adsb_process.pid) + os.killpg(pgid, 9) # SIGKILL + except (ProcessLookupError, OSError): pass app_module.adsb_process = None + logger.info("Killed stale ADS-B process") # Create device object and build command via abstraction layer sdr_device = SDRFactory.create_default_device(sdr_type, index=device) @@ -393,10 +398,12 @@ def start_adsb(): cmd[0] = dump1090_path try: + logger.info(f"Starting dump1090 with device index {device}: {' '.join(cmd)}") app_module.adsb_process = subprocess.Popen( cmd, stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, + start_new_session=True # Create new process group for clean shutdown ) time.sleep(DUMP1090_START_WAIT) @@ -421,10 +428,11 @@ def start_adsb(): return jsonify({'status': 'error', 'message': error_msg}) adsb_using_service = True + adsb_active_device = device # Track which device is being used thread = threading.Thread(target=parse_sbs_stream, args=(f'localhost:{ADSB_SBS_PORT}',), daemon=True) thread.start() - return jsonify({'status': 'started', 'message': 'ADS-B tracking started'}) + return jsonify({'status': 'started', 'message': 'ADS-B tracking started', 'device': device}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}) @@ -432,17 +440,26 @@ def start_adsb(): @adsb_bp.route('/stop', methods=['POST']) def stop_adsb(): """Stop ADS-B tracking.""" - global adsb_using_service + global adsb_using_service, adsb_active_device with app_module.adsb_lock: if app_module.adsb_process: - app_module.adsb_process.terminate() try: + # Kill the entire process group to ensure all child processes are terminated + pgid = os.getpgid(app_module.adsb_process.pid) + os.killpg(pgid, 15) # SIGTERM app_module.adsb_process.wait(timeout=ADSB_TERMINATE_TIMEOUT) - except subprocess.TimeoutExpired: - app_module.adsb_process.kill() + except (subprocess.TimeoutExpired, ProcessLookupError, OSError): + try: + # Force kill if terminate didn't work + pgid = os.getpgid(app_module.adsb_process.pid) + os.killpg(pgid, 9) # SIGKILL + except (ProcessLookupError, OSError): + pass app_module.adsb_process = None + logger.info("ADS-B process stopped") adsb_using_service = False + adsb_active_device = None app_module.adsb_aircraft.clear() _looked_up_icaos.clear()