fix: HackRF users get misleading RTL-SDR error in rtlamr/sstv/weather-sat modes

Several modes didn't pass sdr_type to claim_sdr_device(), defaulting to
'rtlsdr' and triggering an rtl_test USB probe that fails for HackRF with
a confusing "check that the RTL-SDR is connected" message.

- Add sdr_type to frontend start requests for rtlamr, weather-sat, sstv-general
- Read sdr_type in backend routes and pass to claim/release_sdr_device()
- Add early guard returning clear "not yet supported" error for non-RTL-SDR
  hardware in modes that are hardcoded to RTL-SDR tools
- Make probe_rtlsdr_device error message device-type-agnostic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-03-04 07:52:13 +00:00
parent 325dafacbc
commit 8d91c200a5
8 changed files with 60 additions and 22 deletions

View File

@@ -29,6 +29,7 @@ rtl_tcp_lock = threading.Lock()
# Track which device is being used
rtlamr_active_device: int | None = None
rtlamr_active_sdr_type: str = 'rtlsdr'
def stream_rtlamr_output(process: subprocess.Popen[bytes]) -> None:
@@ -62,7 +63,7 @@ def stream_rtlamr_output(process: subprocess.Popen[bytes]) -> None:
except Exception as e:
app_module.rtlamr_queue.put({'type': 'error', 'text': str(e)})
finally:
global rtl_tcp_process, rtlamr_active_device
global rtl_tcp_process, rtlamr_active_device, rtlamr_active_sdr_type
# Ensure rtlamr process is terminated
try:
process.terminate()
@@ -91,19 +92,26 @@ def stream_rtlamr_output(process: subprocess.Popen[bytes]) -> None:
app_module.rtlamr_process = None
# Release SDR device
if rtlamr_active_device is not None:
app_module.release_sdr_device(rtlamr_active_device)
app_module.release_sdr_device(rtlamr_active_device, rtlamr_active_sdr_type)
rtlamr_active_device = None
@rtlamr_bp.route('/start_rtlamr', methods=['POST'])
def start_rtlamr() -> Response:
global rtl_tcp_process, rtlamr_active_device
global rtl_tcp_process, rtlamr_active_device, rtlamr_active_sdr_type
with app_module.rtlamr_lock:
if app_module.rtlamr_process:
return jsonify({'status': 'error', 'message': 'RTLAMR already running'}), 409
data = request.json or {}
sdr_type_str = data.get('sdr_type', 'rtlsdr')
if sdr_type_str != 'rtlsdr':
return jsonify({
'status': 'error',
'message': f'{sdr_type_str.replace("_", " ").title()} is not yet supported for this mode. Please use an RTL-SDR device.'
}), 400
# Validate inputs
try:
@@ -116,7 +124,7 @@ def start_rtlamr() -> Response:
# Check if device is available
device_int = int(device)
error = app_module.claim_sdr_device(device_int, 'rtlamr')
error = app_module.claim_sdr_device(device_int, 'rtlamr', sdr_type_str)
if error:
return jsonify({
'status': 'error',
@@ -125,6 +133,7 @@ def start_rtlamr() -> Response:
}), 409
rtlamr_active_device = device_int
rtlamr_active_sdr_type = sdr_type_str
# Clear queue
while not app_module.rtlamr_queue.empty():
@@ -170,7 +179,7 @@ def start_rtlamr() -> Response:
logger.error(f"Failed to start rtl_tcp: {e}")
# Release SDR device on rtl_tcp failure
if rtlamr_active_device is not None:
app_module.release_sdr_device(rtlamr_active_device)
app_module.release_sdr_device(rtlamr_active_device, rtlamr_active_sdr_type)
rtlamr_active_device = None
return jsonify({'status': 'error', 'message': f'Failed to start rtl_tcp: {e}'}), 500
@@ -242,7 +251,7 @@ def start_rtlamr() -> Response:
rtl_tcp_process.wait(timeout=2)
rtl_tcp_process = None
if rtlamr_active_device is not None:
app_module.release_sdr_device(rtlamr_active_device)
app_module.release_sdr_device(rtlamr_active_device, rtlamr_active_sdr_type)
rtlamr_active_device = None
return jsonify({'status': 'error', 'message': 'rtlamr not found. Install from https://github.com/bemasher/rtlamr'})
except Exception as e:
@@ -253,14 +262,14 @@ def start_rtlamr() -> Response:
rtl_tcp_process.wait(timeout=2)
rtl_tcp_process = None
if rtlamr_active_device is not None:
app_module.release_sdr_device(rtlamr_active_device)
app_module.release_sdr_device(rtlamr_active_device, rtlamr_active_sdr_type)
rtlamr_active_device = None
return jsonify({'status': 'error', 'message': str(e)})
@rtlamr_bp.route('/stop_rtlamr', methods=['POST'])
def stop_rtlamr() -> Response:
global rtl_tcp_process, rtlamr_active_device
global rtl_tcp_process, rtlamr_active_device, rtlamr_active_sdr_type
# Grab process refs inside locks, clear state, then terminate outside
rtlamr_proc = None
@@ -293,7 +302,7 @@ def stop_rtlamr() -> Response:
# Release device from registry
if rtlamr_active_device is not None:
app_module.release_sdr_device(rtlamr_active_device)
app_module.release_sdr_device(rtlamr_active_device, rtlamr_active_sdr_type)
rtlamr_active_device = None
return jsonify({'status': 'stopped'})

View File

@@ -57,6 +57,7 @@ _timescale_lock = threading.Lock()
# Track which device is being used
sstv_active_device: int | None = None
sstv_active_sdr_type: str = 'rtlsdr'
def _progress_callback(data: dict) -> None:
@@ -154,6 +155,14 @@ def start_decoder():
# Get parameters
data = request.get_json(silent=True) or {}
sdr_type_str = data.get('sdr_type', 'rtlsdr')
if sdr_type_str != 'rtlsdr':
return jsonify({
'status': 'error',
'message': f'{sdr_type_str.replace("_", " ").title()} is not yet supported for this mode. Please use an RTL-SDR device.'
}), 400
frequency = data.get('frequency', ISS_SSTV_FREQ)
modulation = str(data.get('modulation', ISS_SSTV_MODULATION)).strip().lower()
device_index = data.get('device', 0)
@@ -209,9 +218,9 @@ def start_decoder():
longitude = None
# Claim SDR device
global sstv_active_device
global sstv_active_device, sstv_active_sdr_type
device_int = int(device_index)
error = app_module.claim_sdr_device(device_int, 'sstv')
error = app_module.claim_sdr_device(device_int, 'sstv', sdr_type_str)
if error:
return jsonify({
'status': 'error',
@@ -231,6 +240,7 @@ def start_decoder():
if success:
sstv_active_device = device_int
sstv_active_sdr_type = sdr_type_str
result = {
'status': 'started',
@@ -247,7 +257,7 @@ def start_decoder():
return jsonify(result)
else:
# Release device on failure
app_module.release_sdr_device(device_int)
app_module.release_sdr_device(device_int, sdr_type_str)
return jsonify({
'status': 'error',
'message': 'Failed to start decoder'
@@ -262,13 +272,13 @@ def stop_decoder():
Returns:
JSON confirmation.
"""
global sstv_active_device
global sstv_active_device, sstv_active_sdr_type
decoder = get_sstv_decoder()
decoder.stop()
# Release device from registry
if sstv_active_device is not None:
app_module.release_sdr_device(sstv_active_device)
app_module.release_sdr_device(sstv_active_device, sstv_active_sdr_type)
sstv_active_device = None
return jsonify({'status': 'stopped'})

View File

@@ -30,6 +30,7 @@ _sstv_general_queue: queue.Queue = queue.Queue(maxsize=100)
# Track which device is being used
_sstv_general_active_device: int | None = None
_sstv_general_active_sdr_type: str = 'rtlsdr'
# Predefined SSTV frequencies
SSTV_FREQUENCIES = [
@@ -119,6 +120,14 @@ def start_decoder():
break
data = request.get_json(silent=True) or {}
sdr_type_str = data.get('sdr_type', 'rtlsdr')
if sdr_type_str != 'rtlsdr':
return jsonify({
'status': 'error',
'message': f'{sdr_type_str.replace("_", " ").title()} is not yet supported for this mode. Please use an RTL-SDR device.'
}), 400
frequency = data.get('frequency')
modulation = data.get('modulation')
device_index = data.get('device', 0)
@@ -155,9 +164,9 @@ def start_decoder():
}), 400
# Claim SDR device
global _sstv_general_active_device
global _sstv_general_active_device, _sstv_general_active_sdr_type
device_int = int(device_index)
error = app_module.claim_sdr_device(device_int, 'sstv_general')
error = app_module.claim_sdr_device(device_int, 'sstv_general', sdr_type_str)
if error:
return jsonify({
'status': 'error',
@@ -175,6 +184,7 @@ def start_decoder():
if success:
_sstv_general_active_device = device_int
_sstv_general_active_sdr_type = sdr_type_str
return jsonify({
'status': 'started',
'frequency': frequency,
@@ -182,7 +192,7 @@ def start_decoder():
'device': device_index,
})
else:
app_module.release_sdr_device(device_int)
app_module.release_sdr_device(device_int, sdr_type_str)
return jsonify({
'status': 'error',
'message': 'Failed to start decoder',
@@ -192,12 +202,12 @@ def start_decoder():
@sstv_general_bp.route('/stop', methods=['POST'])
def stop_decoder():
"""Stop general SSTV decoder."""
global _sstv_general_active_device
global _sstv_general_active_device, _sstv_general_active_sdr_type
decoder = get_general_sstv_decoder()
decoder.stop()
if _sstv_general_active_device is not None:
app_module.release_sdr_device(_sstv_general_active_device)
app_module.release_sdr_device(_sstv_general_active_device, _sstv_general_active_sdr_type)
_sstv_general_active_device = None
return jsonify({'status': 'stopped'})

View File

@@ -136,6 +136,13 @@ def start_capture():
})
data = request.get_json(silent=True) or {}
sdr_type_str = data.get('sdr_type', 'rtlsdr')
if sdr_type_str != 'rtlsdr':
return jsonify({
'status': 'error',
'message': f'{sdr_type_str.replace("_", " ").title()} is not yet supported for this mode. Please use an RTL-SDR device.'
}), 400
# Validate satellite
satellite = data.get('satellite')
@@ -173,7 +180,7 @@ def start_capture():
if not rtl_tcp_host:
try:
import app as app_module
error = app_module.claim_sdr_device(device_index, 'weather_sat')
error = app_module.claim_sdr_device(device_index, 'weather_sat', sdr_type_str)
if error:
return jsonify({
'status': 'error',

View File

@@ -108,7 +108,7 @@ const SSTVGeneral = (function() {
const response = await fetch('/sstv-general/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ frequency, modulation, device })
body: JSON.stringify({ frequency, modulation, device, sdr_type: typeof getSelectedSDRType === 'function' ? getSelectedSDRType() : 'rtlsdr' })
});
const data = await response.json();

View File

@@ -258,6 +258,7 @@ const WeatherSat = (function() {
device,
gain,
bias_t: biasT,
sdr_type: typeof getSelectedSDRType === 'function' ? getSelectedSDRType() : 'rtlsdr',
};
// Add rtl_tcp params if using remote SDR

View File

@@ -5329,6 +5329,7 @@
gain: gain,
ppm: ppm,
device: device,
sdr_type: getSelectedSDRType(),
msgtype: msgtype,
filterid: filterid,
unique: unique,

View File

@@ -493,7 +493,7 @@ def probe_rtlsdr_device(device_index: int) -> str | None:
)
return (
f'SDR device {device_index} is not available — '
f'check that the RTL-SDR is connected and not in use by another process.'
f'check that the SDR device is connected and not in use by another process.'
)
except Exception as e: