desperado/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3use std::{
4    pin::Pin,
5    task::{Context, Poll},
6};
7
8use futures::Stream;
9use num_complex::Complex;
10
11pub mod iqread;
12#[cfg(feature = "rtlsdr")]
13pub mod rtlsdr;
14
15/**
16 * I/Q Data Format
17 */
18#[derive(Debug, Copy, Clone)]
19pub enum IqFormat {
20    /// Complex unsigned 8-bit (Cu8)
21    Cu8,
22    /// Complex signed 8-bit (Cs8)
23    Cs8,
24    /// Complex signed 16-bit (Cs16)
25    Cs16,
26    /// Complex 32-bit float (Cf32)
27    Cf32,
28}
29
30/**
31 * Synchronous I/Q Data Source (iterable)
32 */
33pub enum IqSource {
34    /// File-based IQ source
35    IqFile(iqread::IqRead<std::io::BufReader<std::fs::File>>),
36    #[cfg(feature = "rtlsdr")]
37    /// RTL-SDR-based IQ source (requires "rtlsdr" feature)
38    RtlSdr(rtlsdr::RtlSdrReader),
39}
40
41impl Iterator for IqSource {
42    type Item = Result<Vec<Complex<f32>>, std::io::Error>;
43
44    fn next(&mut self) -> Option<Self::Item> {
45        match self {
46            IqSource::IqFile(source) => source.next(),
47            #[cfg(feature = "rtlsdr")]
48            IqSource::RtlSdr(_source) => todo!(), /*source.next()*/
49        }
50    }
51}
52impl IqSource {
53    /// Create a new file-based I/Q source
54    pub fn from_file<P: AsRef<std::path::Path>>(
55        path: P,
56        center_freq: u32,
57        sample_rate: u32,
58        chunk_size: usize,
59        iq_format: IqFormat,
60    ) -> Result<Self, std::io::Error> {
61        let source =
62            iqread::IqRead::from_file(path, center_freq, sample_rate, chunk_size, iq_format)?;
63        Ok(IqSource::IqFile(source))
64    }
65
66    #[cfg(feature = "rtlsdr")]
67    /// Create a new RTL-SDR-based I/Q source
68    pub fn from_rtlsdr(
69        device_index: usize,
70        center_freq: u32,
71        sample_rate: u32,
72        gain: Option<i32>,
73    ) -> Result<Self, rtl_sdr_rs::error::RtlsdrError> {
74        let config = rtlsdr::RtlSdrConfig {
75            device_index,
76            center_freq,
77            sample_rate,
78            gain,
79        };
80        let source = rtlsdr::RtlSdrReader::new(&config)?;
81        Ok(IqSource::RtlSdr(source))
82    }
83}
84
85/**
86 * Asynchronous I/Q Data Source (streamable)
87 */
88pub enum IqAsyncSource {
89    /// File-based IQ source
90    IqAsyncFile(iqread::IqAsyncRead<tokio::io::BufReader<tokio::fs::File>>),
91    /// Stdin-based IQ source
92    IqAsyncStdin(iqread::IqAsyncRead<tokio::io::BufReader<tokio::io::Stdin>>),
93    /// TCP-based IQ source
94    IqAsyncTcp(iqread::IqAsyncRead<tokio::io::BufReader<tokio::net::TcpStream>>),
95    #[cfg(feature = "rtlsdr")]
96    /// RTL-SDR-based IQ source (requires "rtlsdr" feature)
97    RtlSdr(rtlsdr::AsyncRtlSdrReader),
98}
99
100impl IqAsyncSource {
101    /// Create a new file-based asynchronous I/Q source
102    pub async fn from_file<P: AsRef<std::path::Path>>(
103        path: P,
104        center_freq: u32,
105        sample_rate: u32,
106        chunk_size: usize,
107        iq_format: IqFormat,
108    ) -> Result<Self, std::io::Error> {
109        let source =
110            iqread::IqAsyncRead::from_file(path, center_freq, sample_rate, chunk_size, iq_format)
111                .await?;
112        Ok(IqAsyncSource::IqAsyncFile(source))
113    }
114
115    /// Create a new stdin-based asynchronous I/Q source
116    pub fn from_stdin(
117        center_freq: u32,
118        sample_rate: u32,
119        chunk_size: usize,
120        iq_format: IqFormat,
121    ) -> Self {
122        let source =
123            iqread::IqAsyncRead::from_stdin(center_freq, sample_rate, chunk_size, iq_format);
124        IqAsyncSource::IqAsyncStdin(source)
125    }
126
127    /// Create a new TCP-based asynchronous I/Q source
128    pub async fn from_tcp(
129        addr: &str,
130        port: u16,
131        center_freq: u32,
132        sample_rate: u32,
133        chunk_size: usize,
134        iq_format: IqFormat,
135    ) -> Result<Self, std::io::Error> {
136        let source = iqread::IqAsyncRead::from_tcp(
137            addr,
138            port,
139            center_freq,
140            sample_rate,
141            chunk_size,
142            iq_format,
143        )
144        .await?;
145        Ok(IqAsyncSource::IqAsyncTcp(source))
146    }
147
148    #[cfg(feature = "rtlsdr")]
149    /// Create a new RTL-SDR-based asynchronous I/Q source
150    pub async fn from_rtlsdr(
151        device_index: usize,
152        center_freq: u32,
153        sample_rate: u32,
154        gain: Option<i32>,
155    ) -> Result<Self, rtl_sdr_rs::error::RtlsdrError> {
156        let config = rtlsdr::RtlSdrConfig {
157            device_index,
158            center_freq,
159            sample_rate,
160            gain,
161        };
162        let async_reader = rtlsdr::AsyncRtlSdrReader::new(&config)?;
163        Ok(IqAsyncSource::RtlSdr(async_reader))
164    }
165}
166
167impl Stream for IqAsyncSource {
168    type Item = Result<Vec<Complex<f32>>, std::io::Error>;
169
170    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
171        match self.get_mut() {
172            IqAsyncSource::IqAsyncFile(source) => Pin::new(source).poll_next(cx),
173            IqAsyncSource::IqAsyncStdin(source) => Pin::new(source).poll_next(cx),
174            IqAsyncSource::IqAsyncTcp(source) => Pin::new(source).poll_next(cx),
175            #[cfg(feature = "rtlsdr")]
176            IqAsyncSource::RtlSdr(_source) => todo!(), /*Pin::new(source).poll_next(cx)*/
177        }
178    }
179}
180
181fn convert_bytes_to_complex(format: IqFormat, buffer: &[u8]) -> Vec<Complex<f32>> {
182    match format {
183        IqFormat::Cu8 => buffer
184            .chunks_exact(2)
185            .map(|c| Complex::new((c[0] as f32 - 127.5) / 128.0, (c[1] as f32 - 127.5) / 128.0))
186            .collect(),
187        IqFormat::Cs8 => buffer
188            .chunks_exact(2)
189            .map(|c| Complex::new((c[0] as i8) as f32 / 128.0, (c[1] as i8) as f32 / 128.0))
190            .collect(),
191        IqFormat::Cs16 => buffer
192            .chunks_exact(4)
193            .map(|c| {
194                Complex::new(
195                    i16::from_le_bytes([c[0], c[1]]) as f32 / 32768.0,
196                    i16::from_le_bytes([c[2], c[3]]) as f32 / 32768.0,
197                )
198            })
199            .collect(),
200        IqFormat::Cf32 => buffer
201            .chunks_exact(8)
202            .map(|c| {
203                Complex::new(
204                    f32::from_le_bytes([c[0], c[1], c[2], c[3]]),
205                    f32::from_le_bytes([c[4], c[5], c[6], c[7]]),
206                )
207            })
208            .collect(),
209    }
210}