use futures::Stream;
use num_complex::Complex;
use crate::{Gain, GainElement, GainElementName, IqFormat, error};
const BRIDGE_QUEUE_DEPTH: usize = 32;
const DEFAULT_LNA_GAIN: u32 = 16;
const DEFAULT_VGA_GAIN: u32 = 20;
#[derive(Debug, Clone, PartialEq)]
pub struct HackRfConfig {
pub device_index: usize,
pub center_freq: u64,
pub sample_rate: u32,
pub gain: Gain,
pub amp_enable: bool,
pub bias_tee: bool,
}
impl HackRfConfig {
pub fn new(device_index: usize, center_freq: u64, sample_rate: u32, gain: Gain) -> Self {
Self {
device_index,
center_freq,
sample_rate,
gain,
amp_enable: false,
bias_tee: false,
}
}
}
#[derive(Debug, Clone)]
pub enum HackRfMessage {
Frequency(u64),
Gain(Gain),
}
fn open_and_configure(config: &HackRfConfig) -> error::Result<rs_hackrf::HackRf> {
let hackrf = rs_hackrf::HackRf::open_by_index(config.device_index)?;
hackrf.set_sample_rate(config.sample_rate)?;
hackrf.set_freq(config.center_freq)?;
apply_gain(&hackrf, &config.gain)?;
hackrf.set_amp_enable(config.amp_enable)?;
hackrf.set_antenna_enable(config.bias_tee)?;
Ok(hackrf)
}
fn apply_gain(hackrf: &rs_hackrf::HackRf, gain: &Gain) -> error::Result<()> {
match gain {
Gain::Auto => {
tracing::info!(
"HackRF has no AGC; using default LNA={DEFAULT_LNA_GAIN} VGA={DEFAULT_VGA_GAIN}"
);
hackrf.set_lna_gain(DEFAULT_LNA_GAIN)?;
hackrf.set_vga_gain(DEFAULT_VGA_GAIN)?;
}
Gain::Manual(db) => {
let total = *db as u32;
let lna = (total * 2 / 5).min(40); let vga = total.saturating_sub(lna).min(62); tracing::info!(
total,
lna,
vga,
"Setting HackRF manual gain (split LNA/VGA)"
);
hackrf.set_lna_gain(lna)?;
hackrf.set_vga_gain(vga)?;
}
Gain::Elements(elements) => {
let mut lna_set = false;
let mut vga_set = false;
for GainElement { name, value_db } in elements {
match name {
GainElementName::Lna => {
hackrf.set_lna_gain(*value_db as u32)?;
lna_set = true;
}
GainElementName::Vga => {
hackrf.set_vga_gain(*value_db as u32)?;
vga_set = true;
}
other => {
tracing::warn!(?other, "HackRF does not support gain element; ignoring");
}
}
}
if !lna_set {
hackrf.set_lna_gain(DEFAULT_LNA_GAIN)?;
}
if !vga_set {
hackrf.set_vga_gain(DEFAULT_VGA_GAIN)?;
}
}
}
Ok(())
}
pub struct HackRfReader {
config: HackRfConfig,
bg_rx: Option<std::sync::mpsc::Receiver<Result<Vec<u8>, String>>>,
}
impl HackRfReader {
pub fn new(config: &HackRfConfig) -> error::Result<Self> {
Ok(Self {
config: config.clone(),
bg_rx: None,
})
}
fn start_reader_thread(&mut self) -> error::Result<()> {
let (tx, rx) = std::sync::mpsc::sync_channel::<Result<Vec<u8>, String>>(64);
let (tx_init, rx_init) = std::sync::mpsc::sync_channel::<Result<(), String>>(1);
let config = self.config.clone();
std::thread::Builder::new()
.name("hackrf-sync".into())
.spawn(move || {
let mut hackrf = match open_and_configure(&config) {
Ok(dev) => dev,
Err(e) => {
let _ = tx_init.send(Err(e.to_string()));
return;
}
};
if let Err(e) = hackrf.start_rx() {
let _ = tx_init.send(Err(e.to_string()));
return;
}
let _ = tx_init.send(Ok(()));
let mut buf = vec![0u8; rs_hackrf::RECOMMENDED_BUFFER_SIZE];
loop {
match hackrf.read_sync(&mut buf) {
Ok(n) if n > 0 => {
if tx.send(Ok(buf[..n].to_vec())).is_err() {
break;
}
}
Ok(_) => continue, Err(e) => {
let _ = tx.send(Err(e.to_string()));
break;
}
}
}
})
.map_err(|e| {
error::Error::device(format!("Failed to spawn HackRF reader thread: {e}"))
})?;
match rx_init.recv() {
Ok(Ok(())) => {
self.bg_rx = Some(rx);
Ok(())
}
Ok(Err(msg)) => Err(error::Error::Other(msg)),
Err(_) => Err(error::Error::device(
"Failed to initialize HackRF reader thread",
)),
}
}
pub fn tune(&mut self, center_freq: u64) -> error::Result<()> {
if self.bg_rx.is_none() {
self.config.center_freq = center_freq;
}
Ok(())
}
pub fn set_gain(&mut self, gain: Gain) -> error::Result<()> {
if self.bg_rx.is_none() {
self.config.gain = gain;
}
Ok(())
}
}
impl Iterator for HackRfReader {
type Item = error::Result<Vec<Complex<f32>>>;
fn next(&mut self) -> Option<Self::Item> {
if self.bg_rx.is_none()
&& let Err(e) = self.start_reader_thread()
{
return Some(Err(e));
}
if let Some(ref rx) = self.bg_rx {
return match rx.recv() {
Ok(Ok(bytes)) => {
let samples = crate::convert_bytes_to_complex(IqFormat::Cs8, &bytes);
Some(Ok(samples))
}
Ok(Err(msg)) => Some(Err(error::Error::Other(msg))),
Err(_) => None,
};
}
None
}
}
pub struct AsyncHackRfReader {
control: rs_hackrf::AsyncReadControlHandle,
samples_rx: tokio::sync::mpsc::Receiver<error::Result<Vec<Complex<f32>>>>,
}
impl AsyncHackRfReader {
pub fn new(config: &HackRfConfig) -> error::Result<Self> {
let hackrf = open_and_configure(config)?;
let reader = hackrf
.into_streaming_reader(0, 0)
.map_err(|e| error::Error::device(format!("Failed to start HackRF streaming: {e}")))?;
let control = reader.control_handle();
let (samples_tx, samples_rx) = tokio::sync::mpsc::channel(BRIDGE_QUEUE_DEPTH);
std::thread::Builder::new()
.name("hackrf-bridge".into())
.spawn(move || {
while let Some(result) = reader.recv() {
match result {
Ok(bytes) => {
if bytes.is_empty() {
continue;
}
let samples =
Ok(crate::convert_bytes_to_complex(IqFormat::Cs8, &bytes));
if samples_tx.blocking_send(samples).is_err() {
break; }
}
Err(e) => {
tracing::error!("HackRF read error: {e}");
let _ = samples_tx.blocking_send(Err(error::Error::device(format!(
"HackRF read error: {e}"
))));
break;
}
}
}
})
.map_err(|e| {
error::Error::device(format!("Failed to spawn HackRF bridge thread: {e}"))
})?;
Ok(Self {
control,
samples_rx,
})
}
pub fn adjust(&self, message: HackRfMessage) -> error::Result<()> {
match &message {
HackRfMessage::Frequency(freq) => tracing::debug!("HackRF tune -> {freq} Hz"),
HackRfMessage::Gain(gain) => tracing::debug!("HackRF gain -> {gain:?}"),
}
match message {
HackRfMessage::Frequency(freq) => self
.control
.tune(freq)
.map_err(|e| error::Error::device(format!("HackRF tune failed: {e}"))),
HackRfMessage::Gain(gain) => apply_gain_via_control(&self.control, &gain),
}
}
pub fn tune(&self, center_freq: u64) -> error::Result<()> {
self.adjust(HackRfMessage::Frequency(center_freq))
}
pub fn set_gain(&self, gain: Gain) -> error::Result<()> {
self.adjust(HackRfMessage::Gain(gain))
}
}
fn apply_gain_via_control(
control: &rs_hackrf::AsyncReadControlHandle,
gain: &Gain,
) -> error::Result<()> {
match gain {
Gain::Auto => {
tracing::info!(
"HackRF has no AGC; using default LNA={DEFAULT_LNA_GAIN} VGA={DEFAULT_VGA_GAIN}"
);
control
.set_lna_gain(DEFAULT_LNA_GAIN)
.map_err(|e| error::Error::device(format!("HackRF set LNA gain failed: {e}")))?;
control
.set_vga_gain(DEFAULT_VGA_GAIN)
.map_err(|e| error::Error::device(format!("HackRF set VGA gain failed: {e}")))?;
}
Gain::Manual(db) => {
let total = *db as u32;
let lna = (total * 2 / 5).min(40);
let vga = total.saturating_sub(lna).min(62);
control
.set_lna_gain(lna)
.map_err(|e| error::Error::device(format!("HackRF set LNA gain failed: {e}")))?;
control
.set_vga_gain(vga)
.map_err(|e| error::Error::device(format!("HackRF set VGA gain failed: {e}")))?;
}
Gain::Elements(elements) => {
let mut lna_set = false;
let mut vga_set = false;
for GainElement { name, value_db } in elements {
match name {
GainElementName::Lna => {
control.set_lna_gain(*value_db as u32).map_err(|e| {
error::Error::device(format!("HackRF set LNA gain failed: {e}"))
})?;
lna_set = true;
}
GainElementName::Vga => {
control.set_vga_gain(*value_db as u32).map_err(|e| {
error::Error::device(format!("HackRF set VGA gain failed: {e}"))
})?;
vga_set = true;
}
other => {
tracing::warn!(?other, "HackRF does not support gain element; ignoring");
}
}
}
if !lna_set {
control.set_lna_gain(DEFAULT_LNA_GAIN).map_err(|e| {
error::Error::device(format!("HackRF set LNA gain failed: {e}"))
})?;
}
if !vga_set {
control.set_vga_gain(DEFAULT_VGA_GAIN).map_err(|e| {
error::Error::device(format!("HackRF set VGA gain failed: {e}"))
})?;
}
}
}
Ok(())
}
impl Stream for AsyncHackRfReader {
type Item = error::Result<Vec<Complex<f32>>>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.samples_rx.poll_recv(cx)
}
}