Fixed shutdown and cleanup, standardized setters, and improved TX
This commit is contained in:
parent
c673967a90
commit
96d864aa0b
|
|
@ -1,4 +1,4 @@
|
|||
import time
|
||||
import gc
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ import numpy as np
|
|||
from bladerf import _bladerf
|
||||
|
||||
from ria_toolkit_oss.datatypes import Recording
|
||||
from ria_toolkit_oss.sdr import SDR
|
||||
from ria_toolkit_oss.sdr import SDR, SDRError, SDRParameterError
|
||||
|
||||
|
||||
class Blade(SDR):
|
||||
|
|
@ -22,7 +22,7 @@ class Blade(SDR):
|
|||
"""
|
||||
|
||||
if identifier != "":
|
||||
print(f"Warning, radio identifier {identifier} provided for Blade but will not be used.")
|
||||
warnings.warn(f"Blade: Identifier '{identifier}' will be ignored", UserWarning)
|
||||
|
||||
uut = self._probe_bladerf()
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ class Blade(SDR):
|
|||
|
||||
self.device = _bladerf.BladeRF(uut)
|
||||
self._print_versions(device=self.device)
|
||||
self.bytes_per_sample = 4
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -42,8 +43,10 @@ class Blade(SDR):
|
|||
if board is not None:
|
||||
board.close()
|
||||
|
||||
# TODO why does this create an error under any conditions?
|
||||
raise OSError("Shutdown initiated with error code: {}".format(error))
|
||||
if error != 0:
|
||||
raise OSError(f"BladeRF shutdown with error code: {error}")
|
||||
else:
|
||||
print("BladeRF shutdown successfully")
|
||||
|
||||
def _probe_bladerf(self):
|
||||
device = None
|
||||
|
|
@ -85,24 +88,25 @@ class Blade(SDR):
|
|||
:type sample_rate: int or float
|
||||
:param center_frequency: The center frequency of the recording.
|
||||
:type center_frequency: int or float
|
||||
:param gain: The gain set for receiving on the BladeRF
|
||||
:param gain: The gain set for receiving on the BladeRF.
|
||||
:type gain: int
|
||||
:param channel: The channel the BladeRF is set to.
|
||||
:type channel: int
|
||||
:param buffer_size: The buffer size during receive. Defaults to 8192.
|
||||
:type buffer_size: int
|
||||
:param gain_mode: 'absolute' passes gain directly to the sdr,
|
||||
'relative' means that gain should be a negative value, and it will be subtracted from the max gain (60).
|
||||
:param gain_mode: 'absolute' passes gain directly to the SDR;
|
||||
'relative' means that gain should be a negative value, and it will be subtracted from the max gain (60).
|
||||
:type gain_mode: str
|
||||
"""
|
||||
|
||||
print("Initializing RX")
|
||||
|
||||
# Configure BladeRF
|
||||
self._set_rx_channel(channel)
|
||||
self._set_rx_sample_rate(sample_rate)
|
||||
self._set_rx_center_frequency(center_frequency)
|
||||
self._set_rx_gain(channel, gain, gain_mode)
|
||||
self._set_rx_buffer_size(buffer_size)
|
||||
self.set_rx_channel(channel)
|
||||
self.set_rx_sample_rate(sample_rate)
|
||||
self.set_rx_center_frequency(center_frequency)
|
||||
self.set_rx_gain(channel, gain, gain_mode)
|
||||
self.set_rx_buffer_size(buffer_size)
|
||||
|
||||
bw = self.rx_sample_rate
|
||||
if bw < 200000:
|
||||
|
|
@ -128,10 +132,8 @@ class Blade(SDR):
|
|||
stream_timeout=3500000000,
|
||||
)
|
||||
|
||||
self.rx_ch.enable = True
|
||||
self.bytes_per_sample = 4
|
||||
|
||||
print("Blade Starting RX...")
|
||||
self.rx_ch.enable = True
|
||||
self._enable_rx = True
|
||||
|
||||
while self._enable_rx:
|
||||
|
|
@ -148,18 +150,34 @@ class Blade(SDR):
|
|||
print("Blade RX Completed.")
|
||||
self.rx_ch.enable = False
|
||||
|
||||
def record(self, num_samples: Optional[int] = None, rx_time: Optional[int | float] = None):
|
||||
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 Blade.
|
||||
Either num_samples or rx_time must be provided.
|
||||
init_rx() must be called before record()
|
||||
|
||||
:param num_samples: The number of samples to record.
|
||||
:type num_samples: int, optional
|
||||
:param rx_time: The time to record.
|
||||
:type rx_time: int or float, optional
|
||||
|
||||
returns: Recording object (iq samples and metadata)
|
||||
"""
|
||||
if not self._rx_initialized:
|
||||
raise RuntimeError("RX was not initialized. init_rx() must be called before _stream_rx() or 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:
|
||||
self._num_samples_to_record = num_samples
|
||||
elif rx_time is not None:
|
||||
self._num_samples_to_record = 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")
|
||||
|
||||
# Setup synchronous stream
|
||||
self.device.sync_config(
|
||||
|
|
@ -171,11 +189,10 @@ class Blade(SDR):
|
|||
stream_timeout=3500000000,
|
||||
)
|
||||
|
||||
self.rx_ch.enable = True
|
||||
self.bytes_per_sample = 4
|
||||
|
||||
print("Blade Starting RX...")
|
||||
self._enable_rx = True
|
||||
with self._param_lock:
|
||||
self._enable_rx = True
|
||||
self.rx_ch.enable = True
|
||||
|
||||
store_array = np.zeros(
|
||||
(1, (self._num_samples_to_record // self.rx_buffer_size + 1) * self.rx_buffer_size), dtype=np.complex64
|
||||
|
|
@ -191,7 +208,8 @@ class Blade(SDR):
|
|||
|
||||
# Disable module
|
||||
print("Blade RX Completed.")
|
||||
self.rx_ch.enable = False
|
||||
with self._param_lock:
|
||||
self.rx_ch.enable = False
|
||||
metadata = {
|
||||
"source": self.__class__.__name__,
|
||||
"sample_rate": self.rx_sample_rate,
|
||||
|
|
@ -207,7 +225,7 @@ class Blade(SDR):
|
|||
center_frequency: int | float,
|
||||
gain: int,
|
||||
channel: int,
|
||||
buffer_size: Optional[int] = 8192,
|
||||
buffer_size: Optional[int] = 32768,
|
||||
gain_mode: Optional[str] = "absolute",
|
||||
):
|
||||
"""
|
||||
|
|
@ -224,16 +242,24 @@ class Blade(SDR):
|
|||
:param buffer_size: The buffer size during transmission. Defaults to 8192.
|
||||
:type buffer_size: int
|
||||
:param gain_mode: 'absolute' passes gain directly to the sdr,
|
||||
'relative' means that gain should be a negative value, and it will be subtracted from the max gain (60).
|
||||
'relative' means that gain should be a negative value, and it will be subtracted from the max gain (60).
|
||||
:type gain_mode: str
|
||||
|
||||
:return: 0 if successful, -1 if there's an error.
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
# Configure BladeRF
|
||||
self._set_tx_channel(channel)
|
||||
self._set_tx_sample_rate(sample_rate)
|
||||
self._set_tx_center_frequency(center_frequency)
|
||||
self._set_tx_gain(channel=channel, gain=gain, gain_mode=gain_mode)
|
||||
self._set_tx_buffer_size(buffer_size)
|
||||
self.set_tx_channel(channel)
|
||||
self.set_tx_sample_rate(sample_rate)
|
||||
self.set_tx_center_frequency(center_frequency)
|
||||
self.set_tx_gain(channel=channel, gain=gain, gain_mode=gain_mode)
|
||||
self.set_tx_buffer_size(buffer_size)
|
||||
|
||||
if self.tx_sample_rate >= 7.5e6 and self.tx_buffer_size < 65536:
|
||||
warnings.warn(
|
||||
"Blade: For high sample rates, a buffer size of 65536, 131072, or 262144 is recommended", UserWarning
|
||||
)
|
||||
|
||||
bw = self.tx_sample_rate
|
||||
if bw < 200000:
|
||||
|
|
@ -302,13 +328,13 @@ class Blade(SDR):
|
|||
"""
|
||||
|
||||
if num_samples is not None and tx_time is not None:
|
||||
raise ValueError("Only input one of num_samples or tx_time")
|
||||
raise SDRParameterError("Only input one of num_samples or tx_time")
|
||||
elif num_samples is not None:
|
||||
tx_time = num_samples / self.tx_sample_rate
|
||||
elif tx_time is not None:
|
||||
pass
|
||||
elif tx_time is not None:
|
||||
num_samples = int(tx_time * self.tx_sample_rate)
|
||||
else:
|
||||
tx_time = len(recording) / self.tx_sample_rate
|
||||
num_samples = len(recording)
|
||||
|
||||
if isinstance(recording, np.ndarray):
|
||||
samples = recording
|
||||
|
|
@ -317,9 +343,15 @@ class Blade(SDR):
|
|||
warnings.warn("Recording object is multichannel, only channel 0 data was used for transmission")
|
||||
samples = recording.data[0]
|
||||
else:
|
||||
raise TypeError("recording must be np.ndarray or Recording")
|
||||
raise SDRParameterError("recording must be np.ndarray or Recording")
|
||||
|
||||
samples = samples.astype(np.complex64, copy=False)
|
||||
tx_bytes = self._convert_tx_samples(samples)
|
||||
|
||||
# Transmit in chunks
|
||||
samples_sent = 0
|
||||
len_samples = len(samples)
|
||||
chunk_size = self.tx_buffer_size
|
||||
|
||||
# Setup stream
|
||||
self.device.sync_config(
|
||||
|
|
@ -335,26 +367,21 @@ class Blade(SDR):
|
|||
self.tx_ch.enable = True
|
||||
|
||||
print("Blade Starting TX...")
|
||||
|
||||
# Transmit samples - repeat as needed for the duration
|
||||
start_time = time.time()
|
||||
sample_index = 0
|
||||
|
||||
try:
|
||||
while time.time() - start_time < tx_time:
|
||||
# Get next chunk
|
||||
chunk_size = min(self.tx_buffer_size, len(samples) - sample_index)
|
||||
if chunk_size == 0:
|
||||
# Reached end, loop back
|
||||
sample_index = 0
|
||||
chunk_size = min(self.tx_buffer_size, len(samples))
|
||||
while samples_sent < num_samples:
|
||||
this_chunk_size = min(chunk_size, num_samples - samples_sent)
|
||||
|
||||
chunk = samples[sample_index : sample_index + chunk_size]
|
||||
sample_index += chunk_size
|
||||
start_idx = (samples_sent % len_samples) * self.bytes_per_sample
|
||||
end_idx = start_idx + this_chunk_size * self.bytes_per_sample
|
||||
end_idx %= len_samples * self.bytes_per_sample
|
||||
|
||||
# Convert and transmit
|
||||
byte_array = self._convert_tx_samples(chunk)
|
||||
self.device.sync_tx(byte_array, len(chunk))
|
||||
if end_idx > start_idx:
|
||||
chunk_bytes_arr = tx_bytes[start_idx:end_idx]
|
||||
else:
|
||||
chunk_bytes_arr = tx_bytes[start_idx:] + tx_bytes[:end_idx]
|
||||
|
||||
self.device.sync_tx(chunk_bytes_arr, this_chunk_size)
|
||||
samples_sent += this_chunk_size
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nTransmission interrupted by user")
|
||||
|
|
@ -384,73 +411,146 @@ class Blade(SDR):
|
|||
byte_array = tx_samples.tobytes()
|
||||
return byte_array
|
||||
|
||||
def _set_rx_channel(self, channel):
|
||||
def set_rx_channel(self, channel):
|
||||
if channel != 0 and channel != 1:
|
||||
raise SDRParameterError("Channel must be either 0 or 1.")
|
||||
|
||||
self.rx_channel = channel
|
||||
self.rx_ch = self.device.Channel(_bladerf.CHANNEL_RX(channel))
|
||||
print(f"\nBlade channel = {self.rx_ch}")
|
||||
|
||||
def _set_rx_sample_rate(self, sample_rate):
|
||||
self.rx_sample_rate = sample_rate
|
||||
self.rx_ch.sample_rate = self.rx_sample_rate
|
||||
print(f"Blade sample rate = {self.rx_ch.sample_rate}")
|
||||
|
||||
def _set_rx_center_frequency(self, center_frequency):
|
||||
self.rx_center_frequency = center_frequency
|
||||
self.rx_ch.frequency = center_frequency
|
||||
print(f"Blade center frequency = {self.rx_ch.frequency}")
|
||||
|
||||
def _set_rx_gain(self, channel, gain, gain_mode):
|
||||
|
||||
rx_gain_min = self.device.get_gain_range(channel)[0]
|
||||
rx_gain_max = self.device.get_gain_range(channel)[1]
|
||||
|
||||
if gain_mode == "relative":
|
||||
if gain > 0:
|
||||
raise ValueError(
|
||||
"When gain_mode = 'relative', gain must be < 0. This sets \
|
||||
the gain relative to the maximum possible gain."
|
||||
)
|
||||
def set_rx_sample_rate(self, sample_rate):
|
||||
"""
|
||||
Set the sample rate of the receiver.
|
||||
Not callable during recording; Blade requires stream stop/restart to change sample rate.
|
||||
"""
|
||||
with self._param_lock:
|
||||
if hasattr(self, "rx_channel"):
|
||||
range_list = self.device.get_sample_rate_range(self.rx_channel)
|
||||
min_rate, max_rate = range_list[0], range_list[1]
|
||||
else:
|
||||
abs_gain = rx_gain_max + gain
|
||||
else:
|
||||
abs_gain = gain
|
||||
raise SDRError("Must set channel before setting center frequency")
|
||||
|
||||
if abs_gain < rx_gain_min or abs_gain > rx_gain_max:
|
||||
abs_gain = min(max(gain, rx_gain_min), rx_gain_max)
|
||||
print(f"Gain {abs_gain} out of range for Blade.")
|
||||
print(f"Gain range: {rx_gain_min} to {rx_gain_max} dB")
|
||||
if sample_rate < min_rate or sample_rate > max_rate:
|
||||
raise SDRParameterError(
|
||||
f"{self.__class__.__name__}: Sample rate {sample_rate/1e6:.3f} Msps "
|
||||
f"out of range: [{min_rate/1e6:.3f} - {max_rate/1e6:.3f} Msps]"
|
||||
)
|
||||
|
||||
self.rx_gain = abs_gain
|
||||
self.rx_ch.gain = abs_gain
|
||||
self.rx_sample_rate = sample_rate
|
||||
self.rx_ch.sample_rate = self.rx_sample_rate
|
||||
print(f"Blade sample rate = {self.rx_ch.sample_rate}")
|
||||
|
||||
print(f"Blade gain = {self.rx_ch.gain}")
|
||||
def set_rx_center_frequency(self, center_frequency):
|
||||
"""
|
||||
Set the center frequency of the receiver.
|
||||
Not callable during recording; Blade requires stream stop/restart to change center frequency.
|
||||
"""
|
||||
with self._param_lock:
|
||||
if hasattr(self, "rx_channel"):
|
||||
range_list = self.device.get_frequency_range(self.rx_channel)
|
||||
min_rate, max_rate = range_list[0], range_list[1]
|
||||
else:
|
||||
raise SDRError("Must set channel before setting center frequency")
|
||||
|
||||
def _set_rx_buffer_size(self, buffer_size):
|
||||
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.rx_center_frequency = center_frequency
|
||||
self.rx_ch.frequency = center_frequency
|
||||
print(f"Blade center frequency = {self.rx_ch.frequency}")
|
||||
|
||||
def set_rx_gain(self, channel, gain, gain_mode):
|
||||
"""
|
||||
Set the gain of the receiver.
|
||||
Not callable during recording; Blade requires stream stop/restart to change gain.
|
||||
"""
|
||||
with self._param_lock:
|
||||
rx_gain_min = self.device.get_gain_range(channel)[0]
|
||||
rx_gain_max = self.device.get_gain_range(channel)[1]
|
||||
|
||||
if gain_mode == "relative":
|
||||
if gain > 0:
|
||||
raise SDRParameterError(
|
||||
"When gain_mode = 'relative', gain must be < 0. This sets \
|
||||
the gain relative to the maximum possible gain."
|
||||
)
|
||||
else:
|
||||
abs_gain = rx_gain_max + gain
|
||||
else:
|
||||
abs_gain = gain
|
||||
|
||||
if abs_gain < rx_gain_min or abs_gain > rx_gain_max:
|
||||
abs_gain = min(max(gain, rx_gain_min), rx_gain_max)
|
||||
print(f"Gain {abs_gain} out of range for Blade.")
|
||||
print(f"Gain range: {rx_gain_min} to {rx_gain_max} dB")
|
||||
|
||||
self.rx_gain = abs_gain
|
||||
self.rx_ch.gain = abs_gain
|
||||
|
||||
print(f"Blade gain = {self.rx_ch.gain}")
|
||||
|
||||
def set_rx_buffer_size(self, buffer_size):
|
||||
self.rx_buffer_size = buffer_size
|
||||
|
||||
def _set_tx_channel(self, channel):
|
||||
def set_tx_channel(self, channel):
|
||||
if channel != 0 and channel != 1:
|
||||
raise SDRParameterError("Channel must be either 0 or 1.")
|
||||
|
||||
self.tx_channel = channel
|
||||
self.tx_ch = self.device.Channel(_bladerf.CHANNEL_TX(self.tx_channel))
|
||||
print(f"\nBlade channel = {self.tx_ch}")
|
||||
|
||||
def _set_tx_sample_rate(self, sample_rate):
|
||||
def set_tx_sample_rate(self, sample_rate):
|
||||
if hasattr(self, "tx_channel"):
|
||||
range_list = self.device.get_sample_rate_range(self.tx_channel)
|
||||
min_rate, max_rate = range_list[0], range_list[1]
|
||||
else:
|
||||
raise SDRError("Must set channel before setting center frequency")
|
||||
|
||||
if sample_rate < min_rate or sample_rate > max_rate:
|
||||
raise SDRParameterError(
|
||||
f"{self.__class__.__name__}: Sample rate {sample_rate/1e6:.3f} Msps "
|
||||
f"out of range: [{min_rate/1e6:.3f} - {max_rate/1e6:.3f} Msps]"
|
||||
)
|
||||
|
||||
if sample_rate < min_rate or sample_rate > max_rate:
|
||||
raise SDRParameterError(
|
||||
f"{self.__class__.__name__}: Sample rate {sample_rate/1e6:.3f} Msps "
|
||||
f"out of range: [{min_rate/1e6:.3f} - {max_rate/1e6:.3f} Msps]"
|
||||
)
|
||||
|
||||
self.tx_sample_rate = sample_rate
|
||||
self.tx_ch.sample_rate = self.tx_sample_rate
|
||||
print(f"Blade sample rate = {self.tx_ch.sample_rate}")
|
||||
|
||||
def _set_tx_center_frequency(self, center_frequency):
|
||||
def set_tx_center_frequency(self, center_frequency):
|
||||
if hasattr(self, "tx_channel"):
|
||||
range_list = self.device.get_frequency_range(self.tx_channel)
|
||||
min_rate, max_rate = range_list[0], range_list[1]
|
||||
else:
|
||||
raise SDRError("Must set channel before setting center frequency")
|
||||
|
||||
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.tx_center_frequency = center_frequency
|
||||
self.tx_ch.frequency = center_frequency
|
||||
print(f"Blade center frequency = {self.tx_ch.frequency}")
|
||||
|
||||
def _set_tx_gain(self, channel, gain, gain_mode):
|
||||
|
||||
def set_tx_gain(self, channel, gain, gain_mode):
|
||||
tx_gain_min = self.device.get_gain_range(channel)[0]
|
||||
tx_gain_max = self.device.get_gain_range(channel)[1]
|
||||
|
||||
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."
|
||||
)
|
||||
|
|
@ -469,7 +569,7 @@ class Blade(SDR):
|
|||
|
||||
print(f"Blade gain = {self.tx_ch.gain}")
|
||||
|
||||
def _set_tx_buffer_size(self, buffer_size):
|
||||
def set_tx_buffer_size(self, buffer_size):
|
||||
self.tx_buffer_size = buffer_size
|
||||
|
||||
def set_clock_source(self, source):
|
||||
|
|
@ -499,4 +599,20 @@ class Blade(SDR):
|
|||
print(f"BladeRF bias tee {state} on channel {channel}.")
|
||||
|
||||
def close(self):
|
||||
self.device.close()
|
||||
if hasattr(self, "device") and self.device is not None:
|
||||
try:
|
||||
if hasattr(self, "tx_ch"):
|
||||
self.tx_ch.enable = False
|
||||
if hasattr(self, "rx_ch"):
|
||||
self.rx_ch.enable = False
|
||||
|
||||
self.device.close()
|
||||
except Exception as e:
|
||||
print(f"Warning: error closing bladeRF: {e}")
|
||||
finally:
|
||||
del self.device
|
||||
self.device = None
|
||||
gc.collect()
|
||||
|
||||
def supports_dynamic_updates(self) -> dict:
|
||||
return {"center_frequency": False, "sample_rate": False, "gain": False}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user