Updated setters, added buffer size calculation, standardized errors

This commit is contained in:
M madrigal 2025-11-17 12:09:45 -05:00
parent 0ea81c37ba
commit c575fa798c

View File

@ -12,7 +12,7 @@ except ImportError as exc: # pragma: no cover - dependency provided by end user
raise ImportError("pyrtlsdr is required to use the RTLSDR class") from exc
from ria_toolkit_oss.datatypes.recording import Recording
from ria_toolkit_oss.sdr.sdr import SDR
from ria_toolkit_oss.sdr.sdr import SDR, SDRParameterError
class RTLSDR(SDR):
@ -45,8 +45,7 @@ class RTLSDR(SDR):
print(f"Initialized RTL-SDR with identifier [{identifier}].")
except Exception as e:
print(f"Failed to find RTL-SDR with identifier [{identifier}].")
raise e
raise RuntimeError(f"RTL-SDR: Failed to find device with identifier '{identifier}'\nError: {e}")
def init_rx(
self,
@ -55,18 +54,18 @@ class RTLSDR(SDR):
gain: Optional[int],
channel: int,
gain_mode: Optional[str] = "absolute",
buffer_size: Optional[int] = 256_000,
bias_t: bool = False,
):
if channel not in (0, None):
raise ValueError("RTL-SDR supports only channel 0 for RX.")
raise SDRParameterError("RTL-SDR supports only channel 0 for RX.")
self.set_rx_sample_rate(sample_rate=sample_rate)
self.set_rx_center_frequency(center_frequency=center_frequency)
self.set_rx_gain(gain=gain, gain_mode=gain_mode)
self.rx_buffer_size = int(buffer_size or self.rx_buffer_size)
self.rx_channel = 0
self.rx_buffer_size = self._calculate_optimal_buffer_size(sample_rate)
print(f"RTL-SDR buffer: {self.rx_buffer_size} samples for {sample_rate/1e6:.1f} MS/s")
if bias_t:
self.set_bias_tee(True)
@ -78,16 +77,42 @@ class RTLSDR(SDR):
return {"sample_rate": self.rx_sample_rate, "center_frequency": self.rx_center_frequency, "gain": self.rx_gain}
def set_rx_sample_rate(self, sample_rate):
"""
Set the sample rate of the receiver.
Not callable during recording; RTL-SDR requires stream stop/restart to change sample rate.
"""
if not ((sample_rate > 230e3 and sample_rate < 300e3) or (sample_rate > 900 and sample_rate < 3.2e6)):
raise SDRParameterError(
f"{self.__class__.__name__}: Sample rate {sample_rate/1e6:.3f} Msps "
f"out of range: [{2:.3f} - {20:.3f} Msps]"
)
self.radio.sample_rate = float(sample_rate)
self.rx_sample_rate = self.radio.sample_rate
print(f"RTL RX Sample Rate = {self.radio.get_sample_rate()}")
def set_rx_center_frequency(self, center_frequency):
"""
Set the center frequency of the receiver.
Not callable during recording; RTL-SDR requires stream stop/restart to change center frequency.
"""
with self._param_lock:
min_rate, max_rate = 25e6, 1.75e9
if center_frequency < min_rate or center_frequency > max_rate:
raise SDRParameterError(
f"{self.__class__.__name__}: Center frequency {center_frequency/1e9:.3f} GHz "
f"out of range: [{min_rate/1e9:.3f} - {max_rate/1e9:.3f} GHz]"
)
self.radio.center_freq = float(center_frequency)
self.rx_center_frequency = self.radio.center_freq
print(f"RTL RX Center Frequency = {self.radio.get_center_freq()}")
def set_rx_gain(self, gain, gain_mode="absolute"):
"""
Set the gain of the receiver. Callable during streaming.
"""
with self._param_lock:
available_gains = self.radio.get_gains()
if gain is None:
@ -106,7 +131,7 @@ class RTLSDR(SDR):
if gain_mode == "relative":
if gain > 0:
raise ValueError(
raise SDRParameterError(
"When gain_mode = 'relative', gain must be < 0. This sets\
the gain relative to the maximum possible gain."
)
@ -129,7 +154,25 @@ class RTLSDR(SDR):
print(f"RTL RX Gain = {self.radio.get_gain()}")
print(f"Available RTL RX Gains: {available_gains}")
def record(self, num_samples: Optional[int] = None, rx_time: Optional[int | float] = None):
def _calculate_optimal_buffer_size(self, sample_rate):
"""USB packet alignment for stability."""
# RTL-SDR USB transfers in 16k chunks
min_size = 16384
max_size = 262144 # 256k
# Target: 50ms of data per buffer
target = int(sample_rate * 0.05)
# Round up to 16k boundary
size = ((target + 16383) // 16384) * 16384
return max(min_size, min(size, max_size))
def record(
self,
num_samples: Optional[int] = None,
rx_time: Optional[int | float] = None,
) -> Recording:
"""
Create a radio recording (iq samples and metadata) of a given length from the RTL-SDR.
Either num_samples or rx_time must be provided.
@ -147,13 +190,13 @@ class RTLSDR(SDR):
raise RuntimeError("RX was not initialized. init_rx() must be called before record().")
if num_samples is not None and rx_time is not None:
raise ValueError("Only input one of num_samples or rx_time")
raise SDRParameterError("Only input one of num_samples or rx_time")
elif num_samples is not None:
pass
elif rx_time is not None:
num_samples = int(rx_time * self.rx_sample_rate)
else:
raise ValueError("Must provide input of one of num_samples or rx_time")
raise SDRParameterError("Must provide input of one of num_samples or rx_time")
# RTL-SDR has USB buffer limitations - use consistent 256k chunks
# Always read full chunks to avoid USB overflow issues with partial reads
@ -232,6 +275,10 @@ class RTLSDR(SDR):
def close(self):
try:
self.radio.close()
del self.radio
finally:
self._enable_rx = False
self._enable_tx = False
def supports_dynamic_updates(self) -> dict:
return {"center_frequency": False, "sample_rate": False, "gain": True}