From 369658bb21266a71d65f6c0882b4aa9277a99a99 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 7 Dec 2024 21:51:09 -0500 Subject: [PATCH 01/32] Suggestion for type validation of the correction values --- src/bladerf.rs | 17 +++++------ src/types.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/bladerf.rs b/src/bladerf.rs index a0eae99..1f7a2d1 100644 --- a/src/bladerf.rs +++ b/src/bladerf.rs @@ -732,18 +732,14 @@ impl BladeRF { // **Correction Functions** /// Set the value of the specified correction parameter - pub fn set_correction( - &self, - channel: Channel, - corr: Correction, - value: CorrectionValue, - ) -> Result<()> { + pub fn set_correction(&self, channel: Channel, corr: CorrectionValue) -> Result<()> { + let correction_type: Correction = corr.into(); let res = unsafe { bladerf_set_correction( self.device, channel as bladerf_channel, - corr as bladerf_correction, - value, + correction_type as bladerf_correction, + corr.into_inner(), ) }; check_res!(res); @@ -752,7 +748,7 @@ impl BladeRF { /// Obtain the current value of the specified correction parameter pub fn get_correction(&self, channel: Channel, corr: Correction) -> Result { - let mut value: CorrectionValue = 0; + let mut value: i16 = 0; let res = unsafe { bladerf_get_correction( self.device, @@ -762,7 +758,8 @@ impl BladeRF { ) }; check_res!(res); - Ok(value) + // Safety: the bladerf should return a valid value in the correct range. + Ok(unsafe { CorrectionValue::new_from_raw(corr, value) }) } // Corrections and Calibration diff --git a/src/types.rs b/src/types.rs index 5acdae7..63a8bd1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -526,7 +526,86 @@ impl From<&bladerf_range> for Range { } /// Correction value, in arbitrary units -pub type CorrectionValue = i16; +/// +/// Units taken from here: +/// +/// Type validation is done to ensure the values are in the correct range, returning None if they are not. +/// +/// | Enum Vaiant | Units | +/// |---------|---------| +/// | DcOffsetI | Adjusts the in-phase DC offset. Valid values are [-2048, 2048], which are scaled to the available control bits. | +/// | DcOffsetQ | Adjusts the quadrature DC offset. Valid values are [-2048, 2048], which are scaled to the available control bits. | +/// | Phase | Adjusts phase correction of [-10, 10] degrees, via a provided count value of [-4096, 4096]. | +/// | Gain | Adjusts gain correction value in [-1.0, 1.0], via provided values in the range of [-4096, 4096]. | + +#[derive(Debug, Clone, Copy)] +pub enum CorrectionValue { + DcOffsetI(i16), + DcOffsetQ(i16), + Phase(i16), + Gain(i16), +} + +impl CorrectionValue { + pub fn new_gain(gain: i16) -> Option { + match gain { + -4096..4096 => Some(CorrectionValue::Gain(gain)), + _ => None, + } + } + + pub fn new_phase(phase: i16) -> Option { + match phase { + -4096..4096 => Some(CorrectionValue::Phase(phase)), + _ => None, + } + } + + pub fn new_dc_offset_i(offset: i16) -> Option { + match offset { + -2048..2048 => Some(CorrectionValue::DcOffsetI(offset)), + _ => None, + } + } + + pub fn new_dc_offset_q(offset: i16) -> Option { + match offset { + -2048..2048 => Some(CorrectionValue::DcOffsetQ(offset)), + _ => None, + } + } + + /// # Safety + /// This does not do type validation. + pub unsafe fn new_from_raw(corr: Correction, value: i16) -> CorrectionValue { + match corr { + Correction::DcOffsetI => CorrectionValue::DcOffsetI(value), + Correction::DcOffsetQ => CorrectionValue::DcOffsetQ(value), + Correction::Phase => CorrectionValue::Gain(value), + Correction::Gain => CorrectionValue::Phase(value), + } + } + + pub fn into_inner(self) -> i16 { + match self { + CorrectionValue::DcOffsetI(val) => val, + CorrectionValue::DcOffsetQ(val) => val, + CorrectionValue::Phase(val) => val, + CorrectionValue::Gain(val) => val, + } + } +} + +impl From for Correction { + fn from(value: CorrectionValue) -> Self { + match value { + CorrectionValue::DcOffsetI(_) => Correction::DcOffsetI, + CorrectionValue::DcOffsetQ(_) => Correction::DcOffsetQ, + CorrectionValue::Phase(_) => Correction::Phase, + CorrectionValue::Gain(_) => Correction::Gain, + } + } +} /// Correction parameter selection #[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] From 1b6b405c205450711797eedbd1d7222536bb60ba Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Mon, 9 Dec 2024 18:17:01 -0500 Subject: [PATCH 02/32] subtle changes to bounds checking --- src/bladerf.rs | 2 +- src/types.rs | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/bladerf.rs b/src/bladerf.rs index 1f7a2d1..29ffb32 100644 --- a/src/bladerf.rs +++ b/src/bladerf.rs @@ -759,7 +759,7 @@ impl BladeRF { }; check_res!(res); // Safety: the bladerf should return a valid value in the correct range. - Ok(unsafe { CorrectionValue::new_from_raw(corr, value) }) + Ok(unsafe { CorrectionValue::new_unchecked(corr, value) }) } // Corrections and Calibration diff --git a/src/types.rs b/src/types.rs index 63a8bd1..bf1cafb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -549,35 +549,38 @@ pub enum CorrectionValue { impl CorrectionValue { pub fn new_gain(gain: i16) -> Option { match gain { - -4096..4096 => Some(CorrectionValue::Gain(gain)), + -4096..=4096 => Some(CorrectionValue::Gain(gain)), _ => None, } } pub fn new_phase(phase: i16) -> Option { match phase { - -4096..4096 => Some(CorrectionValue::Phase(phase)), + -4096..=4096 => Some(CorrectionValue::Phase(phase)), _ => None, } } pub fn new_dc_offset_i(offset: i16) -> Option { match offset { - -2048..2048 => Some(CorrectionValue::DcOffsetI(offset)), + -2048..=2048 => Some(CorrectionValue::DcOffsetI(offset)), _ => None, } } pub fn new_dc_offset_q(offset: i16) -> Option { match offset { - -2048..2048 => Some(CorrectionValue::DcOffsetQ(offset)), + -2048..=2048 => Some(CorrectionValue::DcOffsetQ(offset)), _ => None, } } /// # Safety /// This does not do type validation. - pub unsafe fn new_from_raw(corr: Correction, value: i16) -> CorrectionValue { + /// The given correction need to be in its valid range. + /// Techinically does not need to be marked unsafe because I am fairly certain that an error will get passed up, but + /// I want to write this in a way that is more ideomatic to rust where checks are performed at compile time and unwrap() can be used without the code being able to panic. + pub unsafe fn new_unchecked(corr: Correction, value: i16) -> CorrectionValue { match corr { Correction::DcOffsetI => CorrectionValue::DcOffsetI(value), Correction::DcOffsetQ => CorrectionValue::DcOffsetQ(value), @@ -586,6 +589,15 @@ impl CorrectionValue { } } + pub fn new(corr: Correction, value: i16) -> Option { + match corr { + Correction::DcOffsetI => CorrectionValue::new_dc_offset_i(value), + Correction::DcOffsetQ => CorrectionValue::new_dc_offset_q(value), + Correction::Phase => CorrectionValue::new_phase(value), + Correction::Gain => CorrectionValue::new_gain(value), + } + } + pub fn into_inner(self) -> i16 { match self { CorrectionValue::DcOffsetI(val) => val, From 9eb28f5cfca6d34b362bf49087999cfbe997e926 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Mon, 9 Dec 2024 18:20:08 -0500 Subject: [PATCH 03/32] An initial incomplete signal generation program --- Cargo.lock | 253 +++++++++++++++++++++++++++++++- Cargo.toml | 7 +- examples/siggen.rs | 356 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 613 insertions(+), 3 deletions(-) create mode 100644 examples/siggen.rs diff --git a/Cargo.lock b/Cargo.lock index 19aa7b8..c3ff74f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.93" @@ -63,9 +69,11 @@ dependencies = [ "num-complex", "once_cell", "parking_lot", + "ratatui", "strum", "tempfile", "thiserror", + "tui-textarea", ] [[package]] @@ -80,6 +88,21 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -106,6 +129,20 @@ dependencies = [ "libloading", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -146,6 +183,47 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "either" version = "1.13.0" @@ -172,6 +250,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -188,12 +272,35 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.5.0" @@ -206,6 +313,32 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itertools" version = "0.13.0" @@ -215,6 +348,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "libbladerf-sys" version = "0.1.0" @@ -262,6 +401,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -344,6 +492,22 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.22" @@ -356,9 +520,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -372,6 +536,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -435,6 +620,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -483,6 +674,18 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -549,12 +752,52 @@ dependencies = [ "syn", ] +[[package]] +name = "tui-textarea" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" +dependencies = [ + "crossterm", + "ratatui", + "unicode-width 0.2.0", +] + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -664,3 +907,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index f6b8efd..18e6600 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "bladerf" repository = "https://github.com/MerchGuardian/seify-bladerf" -authors = ["Troy Neubauer ", "Ryan Kurte "] +authors = [ + "Troy Neubauer ", + "Ryan Kurte ", +] description = "WIP Rust wrapper for libbladerf" readme = "README.md" license = "MIT" @@ -20,10 +23,12 @@ num-complex = "0.4.6" parking_lot = "0.12.3" strum = { version = "0.26.3", features = ["derive", "strum_macros"] } thiserror = "1.0.64" +tui-textarea = "0.7.0" [dev-dependencies] anyhow = "1" crossbeam-channel = "0.5" +ratatui = "0.29.0" crossterm = "0.28" once_cell = "1.20" tempfile = "3.13" diff --git a/examples/siggen.rs b/examples/siggen.rs new file mode 100644 index 0000000..9c4ca3f --- /dev/null +++ b/examples/siggen.rs @@ -0,0 +1,356 @@ +use std::io; + +use anyhow::Context; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; +use ratatui::{ + buffer::Buffer, + layout::Rect, + style::Stylize, + symbols::border, + text::{Line, Text}, + widgets::{Block, Borders, Paragraph, Widget}, + DefaultTerminal, Frame, +}; + +use ratatui::prelude::*; + +use bladerf::{BladeRF, Correction, CorrectionValue}; +use tui_textarea::{Input, Key, TextArea}; + +#[derive(Debug, Clone, Copy)] +enum SelectedInput { + Frequency, + DcOffsetI, + DcOffsetQ, + Phase, + Gain, +} + +impl SelectedInput { + fn up(&mut self) { + *self = match self { + SelectedInput::Frequency => SelectedInput::Gain, + SelectedInput::DcOffsetI => SelectedInput::Frequency, + SelectedInput::DcOffsetQ => SelectedInput::DcOffsetI, + SelectedInput::Phase => SelectedInput::DcOffsetQ, + SelectedInput::Gain => SelectedInput::Phase, + } + } + fn down(&mut self) { + *self = match self { + SelectedInput::Frequency => SelectedInput::DcOffsetI, + SelectedInput::DcOffsetI => SelectedInput::DcOffsetQ, + SelectedInput::DcOffsetQ => SelectedInput::Phase, + SelectedInput::Phase => SelectedInput::Gain, + SelectedInput::Gain => SelectedInput::Frequency, + } + } +} + +pub struct App { + channel: bladerf::Channel, + // frequency: u64, + // i_corr: i16, + // q_corr: i16, + // phase: i16, + // gain: i16, + // transmitting: bool, + device: BladeRF, + selected_input: SelectedInput, + exit: bool, +} + +fn validate_frequency(textarea: &mut TextArea) -> bool { + match textarea.lines()[0].parse::() { + Err(err) => { + textarea.set_style(Style::default().fg(Color::LightRed)); + textarea.set_block( + Block::default() + .borders(Borders::ALL) + .border_style(Color::LightRed) + .title(format!("ERROR: {}", err)), + ); + false + } + Ok(freq) if (freq > 300000000) && (freq < 3000000000) => { + textarea.set_style(Style::default().fg(Color::LightGreen)); + textarea.set_block( + Block::default() + .border_style(Color::LightGreen) + .borders(Borders::ALL) + .title("OK"), + ); + true + } + Ok(_) => { + textarea.set_style(Style::default().fg(Color::LightRed)); + textarea.set_block( + Block::default() + .borders(Borders::ALL) + .border_style(Color::LightRed) + .title("ERROR: out of range"), + ); + false + } + } +} + +fn validate_correction(textarea: &mut TextArea, corr: Correction) -> bool { + match textarea.lines()[0] + .parse::() + .map(|x| CorrectionValue::new(corr, x)) + { + Err(err) => { + textarea.set_style(Style::default().fg(Color::LightRed)); + textarea.set_block( + Block::default() + .borders(Borders::ALL) + .border_style(Color::LightRed) + .title(format!("ERROR: {}", err)), + ); + false + } + Ok(Some(_)) => { + textarea.set_style(Style::default().fg(Color::LightGreen)); + textarea.set_block( + Block::default() + .border_style(Color::LightGreen) + .borders(Borders::ALL) + .title("OK"), + ); + true + } + Ok(None) => { + textarea.set_style(Style::default().fg(Color::LightRed)); + textarea.set_block( + Block::default() + .borders(Borders::ALL) + .border_style(Color::LightRed) + .title("ERROR: out of range"), + ); + false + } + } +} + +impl App { + fn new(dev: BladeRF) -> App { + let channel = bladerf::Channel::Tx1; + App { + channel, + // frequency: dev.get_frequency(channel).unwrap(), + // i_corr: dev + // .get_correction(channel, bladerf::Correction::DcOffsetI) + // .unwrap(), + // q_corr: dev + // .get_correction(channel, bladerf::Correction::DcOffsetQ) + // .unwrap(), + // phase: dev + // .get_correction(channel, bladerf::Correction::Phase) + // .unwrap(), + // gain: dev + // .get_correction(channel, bladerf::Correction::Gain) + // .unwrap(), + // transmitting: false, + device: dev, + selected_input: SelectedInput::Frequency, + exit: false, + } + } + + /// runs the application's main loop until the user quits + pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { + let mut frequency_input = TextArea::new(vec![self.get_freq().to_string()]); + validate_frequency(&mut frequency_input); + + let mut icorr_input = TextArea::new(vec![self.get_icorr().to_string()]); + validate_correction(&mut icorr_input, Correction::DcOffsetI); + + let mut qcorr_input = TextArea::new(vec![self.get_qcorr().to_string()]); + validate_correction(&mut qcorr_input, Correction::DcOffsetQ); + + let mut phase_input = TextArea::new(vec![self.get_phase().to_string()]); + validate_correction(&mut phase_input, Correction::Phase); + + let mut gain_input = TextArea::new(vec![self.get_gain().to_string()]); + validate_correction(&mut gain_input, Correction::Gain); + + while !self.exit { + let debug_test = Text::from(format!("Sel: {:?}", self.selected_input)); + + frequency_input.set_cursor_style(Style::default()); + icorr_input.set_cursor_style(Style::default()); + qcorr_input.set_cursor_style(Style::default()); + phase_input.set_cursor_style(Style::default()); + gain_input.set_cursor_style(Style::default()); + + let selected_text_field = match self.selected_input { + SelectedInput::Frequency => &mut frequency_input, + SelectedInput::DcOffsetI => &mut icorr_input, + SelectedInput::DcOffsetQ => &mut qcorr_input, + SelectedInput::Phase => &mut phase_input, + SelectedInput::Gain => &mut gain_input, + }; + + selected_text_field.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); + + terminal.draw(|frame| { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Length(4), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + ]) + .split(frame.area()); + + frame.render_widget(&frequency_input, layout[0]); + frame.render_widget(&icorr_input, layout[1]); + frame.render_widget(&qcorr_input, layout[2]); + frame.render_widget(&phase_input, layout[3]); + frame.render_widget(&gain_input, layout[4]); + frame.render_widget(&debug_test, layout[5]); + })?; + + let (selected_text_field, selected_validation): ( + _, + Box bool>, + ) = match self.selected_input { + SelectedInput::Frequency => { + (&mut frequency_input, Box::new(|x| validate_frequency(x))) + } + SelectedInput::DcOffsetI => ( + &mut icorr_input, + Box::new(|x| validate_correction(x, Correction::DcOffsetI)), + ), + SelectedInput::DcOffsetQ => ( + &mut qcorr_input, + Box::new(|x| validate_correction(x, Correction::DcOffsetQ)), + ), + SelectedInput::Phase => ( + &mut phase_input, + Box::new(|x| validate_correction(x, Correction::Phase)), + ), + SelectedInput::Gain => ( + &mut gain_input, + Box::new(|x| validate_correction(x, Correction::Gain)), + ), + }; + + self.handle_events(selected_text_field, selected_validation)?; + } + Ok(()) + } + + fn selected_up(&mut self) { + self.selected_input.up(); + } + + fn selected_down(&mut self) { + self.selected_input.down(); + } + + fn exit(&mut self) { + self.exit = true; + } + + fn get_freq(&self) -> u64 { + self.device.get_frequency(self.channel).unwrap() + } + + fn get_icorr(&self) -> i16 { + self.device + .get_correction(self.channel, bladerf::Correction::DcOffsetI) + .unwrap() + .into_inner() + } + + fn get_qcorr(&self) -> i16 { + self.device + .get_correction(self.channel, bladerf::Correction::DcOffsetQ) + .unwrap() + .into_inner() + } + + fn get_phase(&self) -> i16 { + self.device + .get_correction(self.channel, bladerf::Correction::Phase) + .unwrap() + .into_inner() + } + + fn get_gain(&self) -> i16 { + self.device + .get_correction(self.channel, bladerf::Correction::Gain) + .unwrap() + .into_inner() + } + + fn set_freq(&self, freq: u64) { + self.device.set_frequency(self.channel, freq).unwrap() + } + + fn set_corr(&self, corr: CorrectionValue) { + self.device.set_correction(self.channel, corr).unwrap() + } + + /// updates the application's state based on user input + fn handle_events( + &mut self, + textarea: &mut TextArea, + validation_fn: Box bool>, + ) -> io::Result<()> { + // match event::read()? { + // // it's important to check that the event is a key press event as + // // crossterm also emits key release and repeat events on Windows. + // Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { + // self.handle_key_event(key_event) + // } + // _ => {} + // }; + + match crossterm::event::read()?.into() { + Input { key: Key::Esc, .. } => self.exit(), + Input { key: Key::Up, .. } => self.selected_up(), + Input { key: Key::Down, .. } => self.selected_down(), + input => { + if textarea.input(input) { + validation_fn(textarea); + } + } + } + + Ok(()) + } + + fn handle_key_event(&mut self, key_event: KeyEvent) { + match key_event.code { + KeyCode::Char('q') => self.exit(), + KeyCode::Up => self.selected_up(), + KeyCode::Down => self.selected_down(), + _ => {} + } + } +} + +impl Widget for &App { + fn render(self, area: Rect, buf: &mut Buffer) { + let title = Line::from(" BladeRF SigGen ".bold()); + + Paragraph::new(title).render(area, buf); + } +} + +fn main() -> io::Result<()> { + let device = BladeRF::open_first() + .context("Unable to open a BladeRF device") + .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; + + let mut terminal = ratatui::init(); + let app_result = App::new(device).run(&mut terminal); + ratatui::restore(); + app_result +} From eb33fd44f7f3fa2261e6033358a05b66eed128ad Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Mon, 9 Dec 2024 20:58:40 -0500 Subject: [PATCH 04/32] another intermidate commit to get the tui inputs working more (not fully working though) --- examples/siggen.rs | 314 +++++++++++++++++++++++++-------------------- 1 file changed, 176 insertions(+), 138 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 9c4ca3f..ca52ada 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,6 +1,5 @@ -use std::io; +use std::{error::Error, io}; -use anyhow::Context; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ buffer::Buffer, @@ -60,77 +59,149 @@ pub struct App { exit: bool, } -fn validate_frequency(textarea: &mut TextArea) -> bool { - match textarea.lines()[0].parse::() { - Err(err) => { - textarea.set_style(Style::default().fg(Color::LightRed)); - textarea.set_block( - Block::default() - .borders(Borders::ALL) - .border_style(Color::LightRed) - .title(format!("ERROR: {}", err)), - ); - false - } - Ok(freq) if (freq > 300000000) && (freq < 3000000000) => { - textarea.set_style(Style::default().fg(Color::LightGreen)); - textarea.set_block( - Block::default() - .border_style(Color::LightGreen) - .borders(Borders::ALL) - .title("OK"), - ); - true - } - Ok(_) => { - textarea.set_style(Style::default().fg(Color::LightRed)); - textarea.set_block( - Block::default() - .borders(Borders::ALL) - .border_style(Color::LightRed) - .title("ERROR: out of range"), - ); - false - } +// fn validate_frequency(textarea: &mut TextArea) -> bool { +// match textarea.lines()[0].parse::() { +// Err(err) => { +// textarea.set_style(Style::default().fg(Color::LightRed)); +// textarea.set_block( +// Block::default() +// .borders(Borders::ALL) +// .border_style(Color::LightRed) +// .title(format!("ERROR: {}", err)), +// ); +// false +// } +// Ok(freq) if (freq > 300000000) && (freq < 3000000000) => { +// textarea.set_style(Style::default().fg(Color::LightGreen)); +// textarea.set_block( +// Block::default() +// .border_style(Color::LightGreen) +// .borders(Borders::ALL) +// .title("OK"), +// ); +// true +// } +// Ok(_) => { +// textarea.set_style(Style::default().fg(Color::LightRed)); +// textarea.set_block( +// Block::default() +// .borders(Borders::ALL) +// .border_style(Color::LightRed) +// .title("ERROR: out of range"), +// ); +// false +// } +// } +// } + +fn validate_frequency(val: &str) -> Result { + match val.parse::() { + Err(err) => Err(format!("{}", err)), + Ok(freq) if (freq > 300000000) && (freq < 3000000000) => Ok(freq), + Ok(invalid_freq) => Err(format!("Value `{}` out of range", invalid_freq)), + } +} + +fn validate_correction(val: &str, corr: Correction) -> Result { + match val.parse::().map(|x| CorrectionValue::new(corr, x)) { + Err(err) => Err(format!("{}", err)), + Ok(Some(x)) => Ok(x), + Ok(None) => Err(format!("Value `{val}` out of range")), } } -fn validate_correction(textarea: &mut TextArea, corr: Correction) -> bool { - match textarea.lines()[0] - .parse::() - .map(|x| CorrectionValue::new(corr, x)) +/// A custom numeric input widget with validation +pub struct NumericInput<'a, T, E> { + textarea: TextArea<'a>, + validation_fn: Box Result>, // Validation logic +} + +impl<'a, T> NumericInput<'a, T, String> { + /// Creates a new `NumericInput` with the provided initial value and validation function. + pub fn new(initial_value: String, validation_fn: F) -> Self + where + F: Fn(&str) -> Result + 'static, { - Err(err) => { - textarea.set_style(Style::default().fg(Color::LightRed)); - textarea.set_block( - Block::default() - .borders(Borders::ALL) - .border_style(Color::LightRed) - .title(format!("ERROR: {}", err)), - ); - false - } - Ok(Some(_)) => { - textarea.set_style(Style::default().fg(Color::LightGreen)); - textarea.set_block( - Block::default() - .border_style(Color::LightGreen) - .borders(Borders::ALL) - .title("OK"), - ); - true + let mut numeric_input = Self { + textarea: TextArea::new(vec![initial_value]), + validation_fn: Box::new(validation_fn), + }; + numeric_input.validate(); + numeric_input.remove_focus(); + numeric_input + } + + fn validate(&mut self) { + match (self.validation_fn)(&self.textarea.lines()[0]) { + Ok(_) => { + self.textarea + .set_style(Style::default().fg(Color::LightGreen)); + self.textarea.set_block( + Block::default() + .border_style(Color::LightGreen) + .borders(Borders::ALL) + .title("OK"), + ); + } + Err(err) => { + self.textarea + .set_style(Style::default().fg(Color::LightRed)); + self.textarea.set_block( + Block::default() + .borders(Borders::ALL) + .border_style(Color::LightRed) + .title(format!("ERROR: {err}")), + ); + } } - Ok(None) => { - textarea.set_style(Style::default().fg(Color::LightRed)); - textarea.set_block( - Block::default() - .borders(Borders::ALL) - .border_style(Color::LightRed) - .title("ERROR: out of range"), - ); - false + } + /// Handles input events and revalidates the value + pub fn handle_input_inner(&mut self, input: Input) { + if self.textarea.input(input) { + self.validate(); } } + + /// Sets focus (cursor style) to this input + pub fn set_focus(&mut self) { + self.textarea + .set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); + } + + /// Removes focus from this input + pub fn remove_focus(&mut self) { + self.textarea.set_cursor_style(Style::default()); + } + + /// Retrieves the current value as a string + pub fn value(&self) -> String { + self.textarea.lines().join("") + } +} + +trait NumericInputHandle { + fn handle_input(&mut self, input: Input); +} + +impl<'a, T> NumericInputHandle for &mut NumericInput<'a, T, String> { + fn handle_input(&mut self, input: Input) { + self.handle_input_inner(input); + } +} + +impl<'a, T> NumericInputHandle for NumericInput<'a, T, String> { + fn handle_input(&mut self, input: Input) { + self.handle_input_inner(input); + } +} + +impl<'a, T, E> Widget for &NumericInput<'a, T, E> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + self.textarea.render(area, buf); + } } impl App { @@ -160,45 +231,47 @@ impl App { /// runs the application's main loop until the user quits pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { - let mut frequency_input = TextArea::new(vec![self.get_freq().to_string()]); - validate_frequency(&mut frequency_input); + let mut frequency_input = + NumericInput::new(self.get_freq().to_string(), validate_frequency); - let mut icorr_input = TextArea::new(vec![self.get_icorr().to_string()]); - validate_correction(&mut icorr_input, Correction::DcOffsetI); + let mut icorr_input = NumericInput::new(self.get_icorr().to_string(), |x| { + validate_correction(x, Correction::DcOffsetI) + }); - let mut qcorr_input = TextArea::new(vec![self.get_qcorr().to_string()]); - validate_correction(&mut qcorr_input, Correction::DcOffsetQ); + let mut qcorr_input = NumericInput::new(self.get_qcorr().to_string(), |x| { + validate_correction(x, Correction::DcOffsetQ) + }); - let mut phase_input = TextArea::new(vec![self.get_phase().to_string()]); - validate_correction(&mut phase_input, Correction::Phase); + let mut phase_input = NumericInput::new(self.get_phase().to_string(), |x| { + validate_correction(x, Correction::Phase) + }); - let mut gain_input = TextArea::new(vec![self.get_gain().to_string()]); - validate_correction(&mut gain_input, Correction::Gain); + let mut gain_input = NumericInput::new(self.get_gain().to_string(), |x| { + validate_correction(x, Correction::Gain) + }); while !self.exit { let debug_test = Text::from(format!("Sel: {:?}", self.selected_input)); - frequency_input.set_cursor_style(Style::default()); - icorr_input.set_cursor_style(Style::default()); - qcorr_input.set_cursor_style(Style::default()); - phase_input.set_cursor_style(Style::default()); - gain_input.set_cursor_style(Style::default()); - - let selected_text_field = match self.selected_input { - SelectedInput::Frequency => &mut frequency_input, - SelectedInput::DcOffsetI => &mut icorr_input, - SelectedInput::DcOffsetQ => &mut qcorr_input, - SelectedInput::Phase => &mut phase_input, - SelectedInput::Gain => &mut gain_input, + frequency_input.remove_focus(); + icorr_input.remove_focus(); + qcorr_input.remove_focus(); + phase_input.remove_focus(); + gain_input.remove_focus(); + + match self.selected_input { + SelectedInput::Frequency => frequency_input.set_focus(), + SelectedInput::DcOffsetI => icorr_input.set_focus(), + SelectedInput::DcOffsetQ => qcorr_input.set_focus(), + SelectedInput::Phase => phase_input.set_focus(), + SelectedInput::Gain => gain_input.set_focus(), }; - selected_text_field.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); - terminal.draw(|frame| { let layout = Layout::default() .direction(Direction::Vertical) .constraints(vec![ - Constraint::Length(4), + Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), @@ -215,32 +288,15 @@ impl App { frame.render_widget(&debug_test, layout[5]); })?; - let (selected_text_field, selected_validation): ( - _, - Box bool>, - ) = match self.selected_input { - SelectedInput::Frequency => { - (&mut frequency_input, Box::new(|x| validate_frequency(x))) - } - SelectedInput::DcOffsetI => ( - &mut icorr_input, - Box::new(|x| validate_correction(x, Correction::DcOffsetI)), - ), - SelectedInput::DcOffsetQ => ( - &mut qcorr_input, - Box::new(|x| validate_correction(x, Correction::DcOffsetQ)), - ), - SelectedInput::Phase => ( - &mut phase_input, - Box::new(|x| validate_correction(x, Correction::Phase)), - ), - SelectedInput::Gain => ( - &mut gain_input, - Box::new(|x| validate_correction(x, Correction::Gain)), - ), + match self.selected_input { + // let selected_text_field: dyn NumericInputHandle = match self.selected_input { + SelectedInput::Frequency => self.handle_events(&mut frequency_input)?, + SelectedInput::DcOffsetI => self.handle_events(&mut icorr_input)?, + SelectedInput::DcOffsetQ => self.handle_events(&mut qcorr_input)?, + SelectedInput::Phase => self.handle_events(&mut phase_input)?, + SelectedInput::Gain => self.handle_events(&mut gain_input)?, }; - - self.handle_events(selected_text_field, selected_validation)?; + // self.handle_events(selected_text_field)?; } Ok(()) } @@ -298,29 +354,12 @@ impl App { } /// updates the application's state based on user input - fn handle_events( - &mut self, - textarea: &mut TextArea, - validation_fn: Box bool>, - ) -> io::Result<()> { - // match event::read()? { - // // it's important to check that the event is a key press event as - // // crossterm also emits key release and repeat events on Windows. - // Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { - // self.handle_key_event(key_event) - // } - // _ => {} - // }; - + fn handle_events(&mut self, idk: &mut dyn NumericInputHandle) -> io::Result<()> { match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => self.exit(), Input { key: Key::Up, .. } => self.selected_up(), Input { key: Key::Down, .. } => self.selected_down(), - input => { - if textarea.input(input) { - validation_fn(textarea); - } - } + input => idk.handle_input(input), } Ok(()) @@ -345,9 +384,8 @@ impl Widget for &App { } fn main() -> io::Result<()> { - let device = BladeRF::open_first() - .context("Unable to open a BladeRF device") - .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; + let device = + BladeRF::open_first().map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; let mut terminal = ratatui::init(); let app_result = App::new(device).run(&mut terminal); From 37eac65bcd007e2290bc2a5850d4b246e5079587 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Mon, 9 Dec 2024 21:59:02 -0500 Subject: [PATCH 05/32] Make discrete types instead of the CorrectionValue enum since enums can not have private internals (as far as I know) --- src/bladerf.rs | 11 ++-- src/types.rs | 152 ++++++++++++++++++++++++++++++------------------- 2 files changed, 101 insertions(+), 62 deletions(-) diff --git a/src/bladerf.rs b/src/bladerf.rs index 29ffb32..ff04d9c 100644 --- a/src/bladerf.rs +++ b/src/bladerf.rs @@ -732,14 +732,14 @@ impl BladeRF { // **Correction Functions** /// Set the value of the specified correction parameter - pub fn set_correction(&self, channel: Channel, corr: CorrectionValue) -> Result<()> { - let correction_type: Correction = corr.into(); + pub fn set_correction(&self, channel: Channel, corr: T) -> Result<()> { + let correction_type: Correction = T::TYPE; let res = unsafe { bladerf_set_correction( self.device, channel as bladerf_channel, correction_type as bladerf_correction, - corr.into_inner(), + corr.value(), ) }; check_res!(res); @@ -747,7 +747,8 @@ impl BladeRF { } /// Obtain the current value of the specified correction parameter - pub fn get_correction(&self, channel: Channel, corr: Correction) -> Result { + pub fn get_correction(&self, channel: Channel) -> Result { + let corr = T::TYPE; let mut value: i16 = 0; let res = unsafe { bladerf_get_correction( @@ -759,7 +760,7 @@ impl BladeRF { }; check_res!(res); // Safety: the bladerf should return a valid value in the correct range. - Ok(unsafe { CorrectionValue::new_unchecked(corr, value) }) + Ok(unsafe { T::new_unchecked(value) }) } // Corrections and Calibration diff --git a/src/types.rs b/src/types.rs index bf1cafb..585fac9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -538,85 +538,123 @@ impl From<&bladerf_range> for Range { /// | Phase | Adjusts phase correction of [-10, 10] degrees, via a provided count value of [-4096, 4096]. | /// | Gain | Adjusts gain correction value in [-1.0, 1.0], via provided values in the range of [-4096, 4096]. | +pub trait CorrectionValue: Sized { + const TYPE: Correction; + fn value(&self) -> i16; + unsafe fn new_unchecked(val: i16) -> Self; +} + #[derive(Debug, Clone, Copy)] -pub enum CorrectionValue { - DcOffsetI(i16), - DcOffsetQ(i16), - Phase(i16), - Gain(i16), -} - -impl CorrectionValue { - pub fn new_gain(gain: i16) -> Option { - match gain { - -4096..=4096 => Some(CorrectionValue::Gain(gain)), - _ => None, +pub struct CorrectionDcOffsetI(pub i16); + +// Implement constructors with validation for each struct +impl CorrectionDcOffsetI { + pub fn new(value: i16) -> Option { + if (-2048..=2048).contains(&value) { + Some(Self(value)) + } else { + None } } - pub fn new_phase(phase: i16) -> Option { - match phase { - -4096..=4096 => Some(CorrectionValue::Phase(phase)), - _ => None, - } + pub fn into_inner(self) -> i16 { + self.0 } +} - pub fn new_dc_offset_i(offset: i16) -> Option { - match offset { - -2048..=2048 => Some(CorrectionValue::DcOffsetI(offset)), - _ => None, - } +impl CorrectionValue for CorrectionDcOffsetI { + const TYPE: Correction = Correction::DcOffsetI; + fn value(&self) -> i16 { + self.into_inner() } - pub fn new_dc_offset_q(offset: i16) -> Option { - match offset { - -2048..=2048 => Some(CorrectionValue::DcOffsetQ(offset)), - _ => None, - } + unsafe fn new_unchecked(value: i16) -> Self { + Self(value) } +} - /// # Safety - /// This does not do type validation. - /// The given correction need to be in its valid range. - /// Techinically does not need to be marked unsafe because I am fairly certain that an error will get passed up, but - /// I want to write this in a way that is more ideomatic to rust where checks are performed at compile time and unwrap() can be used without the code being able to panic. - pub unsafe fn new_unchecked(corr: Correction, value: i16) -> CorrectionValue { - match corr { - Correction::DcOffsetI => CorrectionValue::DcOffsetI(value), - Correction::DcOffsetQ => CorrectionValue::DcOffsetQ(value), - Correction::Phase => CorrectionValue::Gain(value), - Correction::Gain => CorrectionValue::Phase(value), +#[derive(Debug, Clone, Copy)] +pub struct CorrectionDcOffsetQ(pub i16); + +impl CorrectionDcOffsetQ { + pub fn new(value: i16) -> Option { + if (-2048..=2048).contains(&value) { + Some(Self(value)) + } else { + None } } - pub fn new(corr: Correction, value: i16) -> Option { - match corr { - Correction::DcOffsetI => CorrectionValue::new_dc_offset_i(value), - Correction::DcOffsetQ => CorrectionValue::new_dc_offset_q(value), - Correction::Phase => CorrectionValue::new_phase(value), - Correction::Gain => CorrectionValue::new_gain(value), + pub fn into_inner(self) -> i16 { + self.0 + } +} + +impl CorrectionValue for CorrectionDcOffsetQ { + const TYPE: Correction = Correction::DcOffsetQ; + fn value(&self) -> i16 { + self.into_inner() + } + + unsafe fn new_unchecked(value: i16) -> Self { + Self(value) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct CorrectionPhase(pub i16); + +impl CorrectionPhase { + pub fn new(value: i16) -> Option { + if (-4096..=4096).contains(&value) { + Some(Self(value)) + } else { + None } } pub fn into_inner(self) -> i16 { - match self { - CorrectionValue::DcOffsetI(val) => val, - CorrectionValue::DcOffsetQ(val) => val, - CorrectionValue::Phase(val) => val, - CorrectionValue::Gain(val) => val, - } + self.0 } } -impl From for Correction { - fn from(value: CorrectionValue) -> Self { - match value { - CorrectionValue::DcOffsetI(_) => Correction::DcOffsetI, - CorrectionValue::DcOffsetQ(_) => Correction::DcOffsetQ, - CorrectionValue::Phase(_) => Correction::Phase, - CorrectionValue::Gain(_) => Correction::Gain, +impl CorrectionValue for CorrectionPhase { + const TYPE: Correction = Correction::Phase; + fn value(&self) -> i16 { + self.into_inner() + } + + unsafe fn new_unchecked(value: i16) -> Self { + Self(value) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct CorrectionGain(pub i16); + +impl CorrectionGain { + pub fn new(value: i16) -> Option { + if (-4096..=4096).contains(&value) { + Some(Self(value)) + } else { + None } } + + pub fn into_inner(self) -> i16 { + self.0 + } +} + +impl CorrectionValue for CorrectionGain { + const TYPE: Correction = Correction::Gain; + fn value(&self) -> i16 { + self.into_inner() + } + + unsafe fn new_unchecked(value: i16) -> Self { + Self(value) + } } /// Correction parameter selection From 514f8d21956f6b16638311d2636369d436f301ff Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Mon, 9 Dec 2024 22:39:21 -0500 Subject: [PATCH 06/32] Break up types.rs into different files --- src/types.rs | 746 ------------------------------------- src/types/backend.rs | 28 ++ src/types/channel.rs | 30 ++ src/types/config.rs | 7 + src/types/correction.rs | 25 ++ src/types/dev_info.rs | 43 +++ src/types/direction.rs | 26 ++ src/types/format.rs | 66 ++++ src/types/gain.rs | 51 +++ src/types/log_level.rs | 23 ++ src/types/loopback.rs | 47 +++ src/types/lpf_mode.rs | 20 + src/types/metadata.rs | 45 +++ src/types/mod.rs | 62 +++ src/types/module_config.rs | 9 + src/types/quick_tune.rs | 9 + src/types/range.rs | 35 ++ src/types/rational_rate.rs | 20 + src/types/rx_mux.rs | 21 ++ src/types/sampling.rs | 20 + src/types/trigger.rs | 72 ++++ src/types/tuning_mode.rs | 20 + src/types/version.rs | 177 +++++++++ 23 files changed, 856 insertions(+), 746 deletions(-) delete mode 100644 src/types.rs create mode 100644 src/types/backend.rs create mode 100644 src/types/channel.rs create mode 100644 src/types/config.rs create mode 100644 src/types/correction.rs create mode 100644 src/types/dev_info.rs create mode 100644 src/types/direction.rs create mode 100644 src/types/format.rs create mode 100644 src/types/gain.rs create mode 100644 src/types/log_level.rs create mode 100644 src/types/loopback.rs create mode 100644 src/types/lpf_mode.rs create mode 100644 src/types/metadata.rs create mode 100644 src/types/mod.rs create mode 100644 src/types/module_config.rs create mode 100644 src/types/quick_tune.rs create mode 100644 src/types/range.rs create mode 100644 src/types/rational_rate.rs create mode 100644 src/types/rx_mux.rs create mode 100644 src/types/sampling.rs create mode 100644 src/types/trigger.rs create mode 100644 src/types/tuning_mode.rs create mode 100644 src/types/version.rs diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 5acdae7..0000000 --- a/src/types.rs +++ /dev/null @@ -1,746 +0,0 @@ -use crate::{sys::*, BladeRF, Error, Result}; - -use bytemuck::cast_slice; -use enum_map::Enum; -use num_complex::Complex; -use std::{cmp, ffi::CStr}; -use strum::FromRepr; - -/// BladeRF module config object -#[derive(Clone, Debug)] -pub struct ModuleConfig { - pub frequency: u64, - pub sample_rate: u32, - pub bandwidth: u32, - /// Set overall system gain - pub gain: i32, -} - -#[derive(Copy, Clone, Debug)] -pub struct Version { - pub major: u16, - pub minor: u16, - pub patch: u16, - /// Textual description of the release, or None if not available or if not UTF-8 - pub describe: Option<&'static str>, -} - -impl Version { - /// Converts the ffi type `bladerf_version` to `Self`. - /// - /// # Safety - /// `version` must come from a bladerf ffi call. - /// More specifically: - /// `version.describe` must be a null-terminated, immutable, statically-allocated (always valid), - /// string. - pub unsafe fn from_ffi(version: &bladerf_version) -> Self { - let describe = if !version.describe.is_null() { - // SAFETY: bladefr docs on field say do not try to modify or free this, - // which sounds like a static lifetime to me - let cstr = unsafe { CStr::from_ptr::<'static>(version.describe) }; - cstr.to_str().ok() - } else { - None - }; - - Version { - major: version.major, - minor: version.minor, - patch: version.patch, - describe, - } - } -} - -impl std::fmt::Display for Version { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(desc) = self.describe { - f.write_fmt(format_args!( - "v{}.{}.{} ({})", - self.major, self.minor, self.patch, desc - )) - } else { - f.write_fmt(format_args!( - "v{}.{}.{}", - self.major, self.minor, self.patch, - )) - } - } -} - -impl PartialOrd for Version { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Version { - fn cmp(&self, other: &Self) -> cmp::Ordering { - let major_ord = self.major.cmp(&other.major); - if major_ord != cmp::Ordering::Equal { - return major_ord; - } - let minor_ord = self.minor.cmp(&other.minor); - if minor_ord != cmp::Ordering::Equal { - return minor_ord; - } - self.patch.cmp(&other.patch) - } -} - -impl PartialEq for Version { - fn eq(&self, other: &Self) -> bool { - self.major == other.major && self.minor == other.minor && self.patch == other.patch - } -} - -impl Eq for Version {} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(u32)] -pub enum LogLevel { - Verbose = bladerf_log_level_BLADERF_LOG_LEVEL_VERBOSE, - Debug = bladerf_log_level_BLADERF_LOG_LEVEL_DEBUG, - Info = bladerf_log_level_BLADERF_LOG_LEVEL_INFO, - Warning = bladerf_log_level_BLADERF_LOG_LEVEL_WARNING, - Error = bladerf_log_level_BLADERF_LOG_LEVEL_ERROR, - Critical = bladerf_log_level_BLADERF_LOG_LEVEL_CRITICAL, - Silent = bladerf_log_level_BLADERF_LOG_LEVEL_SILENT, -} - -impl TryFrom for LogLevel { - type Error = Error; - - fn try_from(level: bladerf_log_level) -> Result { - Self::from_repr(level).ok_or_else(|| format!("Invalid bladerf log level: {level}").into()) - } -} - -pub struct RationalRate { - /// Integer portion - pub integer: u64, - /// Numerator in fractional portion - pub num: u64, - /// Denominator in fractional portion. This must be greater than 0 - pub den: u64, -} - -impl From for RationalRate { - fn from(rate: bladerf_rational_rate) -> Self { - Self { - integer: rate.integer, - num: rate.num, - den: rate.den, - } - } -} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum Backend { - Any = bladerf_backend_BLADERF_BACKEND_ANY as i32, - Linux = bladerf_backend_BLADERF_BACKEND_LINUX as i32, - LibUsb = bladerf_backend_BLADERF_BACKEND_LIBUSB as i32, - Cypress = bladerf_backend_BLADERF_BACKEND_CYPRESS as i32, - Dummy = bladerf_backend_BLADERF_BACKEND_DUMMY as i32, -} - -impl TryFrom for Backend { - type Error = Error; - - fn try_from(backend: bladerf_backend) -> Result { - Self::from_repr(backend as i32) - .ok_or_else(|| format!("Invalid bladerf backend: {backend}").into()) - } -} - -impl From for bladerf_backend { - fn from(value: Backend) -> Self { - value as i32 as bladerf_backend - } -} - -/// Information about a bladerf device connect to the system -#[derive(Clone, Debug)] -pub struct DevInfo(pub(crate) bladerf_devinfo); - -impl DevInfo { - pub fn backend(&self) -> Result { - self.0.backend.try_into() - } - pub fn serial(&self) -> String { - String::from_utf8_lossy(cast_slice(&self.0.serial[..32])).to_string() - } - pub fn usb_bus(&self) -> Option { - Some(self.0.usb_bus) - } - pub fn usb_addr(&self) -> Option { - Some(self.0.usb_addr) - } - pub fn instance(&self) -> u32 { - self.0.instance - } - pub fn manufacturer(&self) -> String { - // TODO: This seems to be `Nuandwn>` instead of `Nuandwn` (what bladeRF-cli --probe gets) - String::from_utf8_lossy(cast_slice(&self.0.manufacturer)).to_string() - } - pub fn product(&self) -> String { - String::from_utf8_lossy(cast_slice(&self.0.product)).to_string() - } - - pub fn open(&self) -> Result { - BladeRF::open_with_devinfo(self) - } -} - -impl From for DevInfo { - fn from(dev: bladerf_devinfo) -> Self { - Self(dev) - } -} - -/// Combined RX and TX config -pub struct Config { - pub tx: ModuleConfig, - pub rx: ModuleConfig, -} - -#[derive(Copy, Clone, Debug, Enum, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum Channel { - Rx1 = bladerf_channel_layout_BLADERF_RX_X1 as i32, - Rx2 = bladerf_channel_layout_BLADERF_RX_X2 as i32, - Tx1 = bladerf_channel_layout_BLADERF_TX_X1 as i32, - Tx2 = bladerf_channel_layout_BLADERF_TX_X2 as i32, -} - -impl Channel { - pub fn is_rx(&self) -> bool { - matches!(self, Channel::Rx1 | Channel::Rx2) - } - pub fn is_tx(&self) -> bool { - matches!(self, Channel::Tx1 | Channel::Tx2) - } -} - -impl TryFrom for Channel { - type Error = Error; - - fn try_from(channel: bladerf_channel) -> Result { - Self::from_repr(channel).ok_or_else(|| format!("Invalid bladerf channel: {channel}").into()) - } -} - -// Additional types for Metadata -#[derive(Clone, Debug)] -pub struct Metadata { - pub timestamp: u64, - pub flags: u32, - // Add other fields as necessary -} - -impl Default for Metadata { - fn default() -> Self { - Self::new() - } -} - -impl Metadata { - pub fn new() -> Self { - Self { - timestamp: 0, - flags: 0, - } - } -} - -impl From<&bladerf_metadata> for Metadata { - fn from(meta: &bladerf_metadata) -> Self { - Self { - timestamp: meta.timestamp, - flags: meta.flags, - } - } -} - -impl From<&Metadata> for bladerf_metadata { - fn from(val: &Metadata) -> Self { - bladerf_metadata { - timestamp: val.timestamp, - flags: val.flags, - status: 0, - actual_count: 0, - reserved: [0u8; 32], - } - } -} - -// Direction Enum -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(u32)] -pub enum Direction { - RX = bladerf_direction_BLADERF_RX, - TX = bladerf_direction_BLADERF_TX, -} - -impl From for bladerf_direction { - fn from(dir: Direction) -> Self { - dir as bladerf_direction - } -} - -impl TryFrom for Direction { - type Error = Error; - - fn try_from(value: bladerf_direction) -> Result { - Self::from_repr(value) - .ok_or_else(|| Error::msg(format!("Invalid Direction value: {value}"))) - } -} - -/// Loopback configuration -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(u32)] -pub enum Loopback { - None = bladerf_loopback_BLADERF_LB_NONE, - RfLna1 = bladerf_loopback_BLADERF_LB_RF_LNA1, - RfLna2 = bladerf_loopback_BLADERF_LB_RF_LNA2, - RfLna3 = bladerf_loopback_BLADERF_LB_RF_LNA3, - Firmware = bladerf_loopback_BLADERF_LB_FIRMWARE, - RficBist = bladerf_loopback_BLADERF_LB_RFIC_BIST, - BbTxlpfRxlpf = bladerf_loopback_BLADERF_LB_BB_TXLPF_RXLPF, - BbTxlpfRxvga2 = bladerf_loopback_BLADERF_LB_BB_TXLPF_RXVGA2, - BbTxvga1Rxlpf = bladerf_loopback_BLADERF_LB_BB_TXVGA1_RXLPF, - BbTxvga1Rxvga2 = bladerf_loopback_BLADERF_LB_BB_TXVGA1_RXVGA2, -} - -impl TryFrom for Loopback { - type Error = Error; - - fn try_from(loopback: bladerf_loopback) -> Result { - Self::from_repr(loopback) - .ok_or_else(|| format!("Invalid bladerf loopback mode: {loopback}").into()) - } -} - -pub struct LoopbackModeInfo { - pub name: Option, - pub mode: Loopback, -} - -impl From for LoopbackModeInfo { - fn from(mode_info: bladerf_loopback_modes) -> Self { - let name = unsafe { CStr::from_ptr(mode_info.name) } - .to_str() - .map(|s| s.to_string()) - .ok(); - Self { - name, - mode: Loopback::from_repr(mode_info.mode).unwrap_or(Loopback::None), - } - } -} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(u32)] -pub enum Format { - // TODO: See if we can pull in the bladerf docs wholesale - #[doc = "[`bladerf_format_BLADERF_FORMAT_SC16_Q11`]"] - Sc16Q11 = bladerf_format_BLADERF_FORMAT_SC16_Q11, - #[doc = "[`bladerf_format_BLADERF_FORMAT_SC8_Q7`]"] - Sc8Q7 = bladerf_format_BLADERF_FORMAT_SC8_Q7, - // TODO: implement meta parsing - // #[doc = "[`bladerf_format_BLADERF_FORMAT_SC16_Q11_META`]"] - // Sc16Q11Meta = bladerf_format_BLADERF_FORMAT_SC16_Q11_META, - // #[doc = "[`bladerf_format_BLADERF_FORMAT_PACKET_META`]"] - // PacketMeta = bladerf_format_BLADERF_FORMAT_PACKET_META, - // #[doc = "[`bladerf_format_BLADERF_FORMAT_SC8_Q7_META`]"] - // Sc8Q7Meta = bladerf_format_BLADERF_FORMAT_SC8_Q7_META, -} - -impl TryFrom for Format { - type Error = Error; - - fn try_from(format: bladerf_format) -> Result { - Self::from_repr(format).ok_or_else(|| format!("Invalid bladerf format: {format}").into()) - } -} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum Sampling { - Unknown = bladerf_sampling_BLADERF_SAMPLING_UNKNOWN as i32, - Internal = bladerf_sampling_BLADERF_SAMPLING_INTERNAL as i32, - External = bladerf_sampling_BLADERF_SAMPLING_EXTERNAL as i32, -} - -impl TryFrom for Sampling { - type Error = Error; - - fn try_from(value: bladerf_sampling) -> Result { - Self::from_repr(value as i32) - .ok_or_else(|| Error::msg(format!("Invalid Sampling value: {value}"))) - } -} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum RxMux { - Invalid = bladerf_rx_mux_BLADERF_RX_MUX_INVALID, - Baseband = bladerf_rx_mux_BLADERF_RX_MUX_BASEBAND, - Counter12bit = bladerf_rx_mux_BLADERF_RX_MUX_12BIT_COUNTER, - Counter32bit = bladerf_rx_mux_BLADERF_RX_MUX_32BIT_COUNTER, - DigitalLoopback = bladerf_rx_mux_BLADERF_RX_MUX_DIGITAL_LOOPBACK, -} - -impl TryFrom for RxMux { - type Error = Error; - - fn try_from(value: bladerf_rx_mux) -> Result { - Self::from_repr(value).ok_or_else(|| Error::msg(format!("Invalid RxMux value: {value}"))) - } -} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum LPFMode { - Normal = bladerf_lpf_mode_BLADERF_LPF_NORMAL as i32, - Bypassed = bladerf_lpf_mode_BLADERF_LPF_BYPASSED as i32, - Disabled = bladerf_lpf_mode_BLADERF_LPF_DISABLED as i32, -} - -impl TryFrom for LPFMode { - type Error = Error; - - fn try_from(value: bladerf_lpf_mode) -> Result { - Self::from_repr(value as i32) - .ok_or_else(|| Error::msg(format!("Invalid LPFMode value: {value}"))) - } -} - -#[derive(Clone, Debug)] -#[repr(C)] -pub struct QuickTune { - pub freqsel: u8, - pub vcocap: u8, - pub nint: u16, - pub nfrac: u32, - pub flags: u8, -} - -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum TuningMode { - Host = bladerf_tuning_mode_BLADERF_TUNING_MODE_HOST, - FPGA = bladerf_tuning_mode_BLADERF_TUNING_MODE_FPGA, - Invalid = bladerf_tuning_mode_BLADERF_TUNING_MODE_INVALID, -} - -impl TryFrom for TuningMode { - type Error = Error; - - fn try_from(value: bladerf_tuning_mode) -> Result { - Self::from_repr(value) - .ok_or_else(|| Error::msg(format!("Invalid TuningMode value: {value}"))) - } -} - -/// Gain value, in decibels (dB) -pub type Gain = i32; - -/// Gain control modes -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum GainMode { - /// Device-specific default (automatic, when available) - Default = bladerf_gain_mode_BLADERF_GAIN_DEFAULT as i32, - /// Manual gain control - Manual = bladerf_gain_mode_BLADERF_GAIN_MGC as i32, - /// Automatic gain control, fast attack (advanced) - FastAttackAgc = bladerf_gain_mode_BLADERF_GAIN_FASTATTACK_AGC as i32, - /// Automatic gain control, slow attack (advanced) - SlowAttackAgc = bladerf_gain_mode_BLADERF_GAIN_SLOWATTACK_AGC as i32, - /// Automatic gain control, hybrid attack (advanced) - HybridAgc = bladerf_gain_mode_BLADERF_GAIN_HYBRID_AGC as i32, -} - -impl TryFrom for GainMode { - type Error = Error; - - fn try_from(value: bladerf_gain_mode) -> Result { - Self::from_repr(value as i32) - .ok_or_else(|| Error::msg(format!("Invalid GainMode value: {value}"))) - } -} - -/// Mapping between C string description of gain modes and `GainMode` -pub struct GainModeInfo { - pub name: &'static str, - pub mode: GainMode, -} - -impl From for GainModeInfo { - fn from(mode_info: bladerf_gain_modes) -> Self { - let name = unsafe { CStr::from_ptr(mode_info.name) } - .to_str() - .unwrap_or("Unknown"); - Self { - name, - mode: GainMode::from_repr(mode_info.mode as i32).unwrap_or(GainMode::Default), - } - } -} - -/// Range struct to represent `bladerf_range` -#[derive(Debug)] -pub struct Range { - pub min: f64, - pub max: f64, - pub step: f64, -} - -impl Range { - pub fn contains(&self, query: impl Into) -> bool { - let steps = (query.into() as f64 - self.min) / self.step; - steps % 1.0 < 1e-8 - } -} - -impl std::fmt::Display for Range { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "{:.0}..{:.0} (step {:.0})", - self.min, self.max, self.step, - )) - } -} - -impl From<&bladerf_range> for Range { - fn from(range: &bladerf_range) -> Self { - Self { - min: range.min as f64 * range.scale as f64, - max: range.max as f64 * range.scale as f64, - step: range.step as f64 * range.scale as f64, - } - } -} - -/// Correction value, in arbitrary units -pub type CorrectionValue = i16; - -/// Correction parameter selection -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum Correction { - DcOffsetI = bladerf_correction_BLADERF_CORR_DCOFF_I as i32, - DcOffsetQ = bladerf_correction_BLADERF_CORR_DCOFF_Q as i32, - Phase = bladerf_correction_BLADERF_CORR_PHASE as i32, - Gain = bladerf_correction_BLADERF_CORR_GAIN as i32, -} - -impl TryFrom for Correction { - type Error = Error; - - fn try_from(value: bladerf_correction) -> Result { - Self::from_repr(value as i32) - .ok_or_else(|| Error::msg(format!("Invalid Correction value: {value}"))) - } -} - -/// Trigger role -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum TriggerRole { - Invalid = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_INVALID, - Disabled = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_DISABLED, - Master = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_MASTER, - Slave = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_SLAVE, -} - -impl TryFrom for TriggerRole { - type Error = Error; - - fn try_from(value: bladerf_trigger_role) -> Result { - Self::from_repr(value) - .ok_or_else(|| Error::msg(format!("Invalid TriggerRole value: {value}"))) - } -} - -/// Trigger signal selection -#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] -#[repr(i32)] -pub enum TriggerSignal { - Invalid = bladerf_trigger_signal_BLADERF_TRIGGER_INVALID, - J71_4 = bladerf_trigger_signal_BLADERF_TRIGGER_J71_4, - J51_1 = bladerf_trigger_signal_BLADERF_TRIGGER_J51_1, - MiniExp1 = bladerf_trigger_signal_BLADERF_TRIGGER_MINI_EXP_1, - User0 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_0, - User1 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_1, - User2 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_2, - User3 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_3, - User4 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_4, - User5 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_5, - User6 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_6, - User7 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_7, -} - -impl TryFrom for TriggerSignal { - type Error = Error; - - fn try_from(value: bladerf_trigger_signal) -> Result { - Self::from_repr(value) - .ok_or_else(|| Error::msg(format!("Invalid TriggerSignal value: {value}"))) - } -} - -/// Trigger configuration -pub struct Trigger { - pub channel: Channel, - pub role: TriggerRole, - pub signal: TriggerSignal, - pub options: u64, -} - -impl TryFrom for Trigger { - type Error = Error; - - fn try_from(t: bladerf_trigger) -> Result { - Ok(Self { - channel: t.channel.try_into()?, - role: t.role.try_into()?, - signal: t.signal.try_into()?, - options: t.options, - }) - } -} - -/// Supported sample types from the bladeRF. -/// -/// # Safety -/// `is_compatible` must only return true if it is valid to re-interpret bytes from the device as `Self`. -/// -/// Currently this is only implemented for: -/// - `Format::Sc16Q11` => `Complex` -/// - `Format::Sc8Q7` => `Complex` -pub unsafe trait SampleFormat: Sized { - /// Returns true if this data type is commutable with the given format enum - fn is_compatible(format: Format) -> bool; - - fn check_compatability(format: Format) -> Result<()> { - if Self::is_compatible(format) { - Ok(()) - } else { - Err(Error::msg(format!( - "{} is not compatable with configured format {format:?}", - std::any::type_name::() - ))) - } - } -} - -// Implementations for supported types -unsafe impl SampleFormat for Complex { - fn is_compatible(format: Format) -> bool { - matches!(format, Format::Sc16Q11) - } -} - -unsafe impl SampleFormat for Complex { - fn is_compatible(format: Format) -> bool { - matches!(format, Format::Sc8Q7) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn version_cmp() { - let v1 = Version { - major: 2, - minor: 0, - patch: 0, - describe: None, - }; - let v2 = Version { - major: 1, - minor: 5, - patch: 10, - describe: None, - }; - - assert!(v1 > v2); - assert!(v2 < v1); - - let v1 = Version { - major: 1, - minor: 6, - patch: 0, - describe: None, - }; - let v2 = Version { - major: 1, - minor: 5, - patch: 10, - describe: None, - }; - - assert!(v1 > v2); - assert!(v2 < v1); - - let v1 = Version { - major: 1, - minor: 5, - patch: 11, - describe: None, - }; - let v2 = Version { - major: 1, - minor: 5, - patch: 10, - describe: None, - }; - - assert!(v1 > v2); - assert!(v2 < v1); - - let v1 = Version { - major: 1, - minor: 5, - patch: 10, - describe: Some("test"), - }; - let v2 = Version { - major: 1, - minor: 5, - patch: 10, - describe: Some("another test"), - }; - - assert_eq!(v1, v2); - - let v1 = Version { - major: 1, - minor: 5, - patch: 11, - describe: None, - }; - let v2 = Version { - major: 1, - minor: 6, - patch: 0, - describe: None, - }; - let v3 = Version { - major: 2, - minor: 0, - patch: 0, - describe: None, - }; - - assert!(v1 < v2); - assert!(v2 < v3); - assert!(v1 < v3); - } -} diff --git a/src/types/backend.rs b/src/types/backend.rs new file mode 100644 index 0000000..7c239d6 --- /dev/null +++ b/src/types/backend.rs @@ -0,0 +1,28 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum Backend { + Any = bladerf_backend_BLADERF_BACKEND_ANY as i32, + Linux = bladerf_backend_BLADERF_BACKEND_LINUX as i32, + LibUsb = bladerf_backend_BLADERF_BACKEND_LIBUSB as i32, + Cypress = bladerf_backend_BLADERF_BACKEND_CYPRESS as i32, + Dummy = bladerf_backend_BLADERF_BACKEND_DUMMY as i32, +} + +impl TryFrom for Backend { + type Error = Error; + + fn try_from(backend: bladerf_backend) -> Result { + Self::from_repr(backend as i32) + .ok_or_else(|| format!("Invalid bladerf backend: {backend}").into()) + } +} + +impl From for bladerf_backend { + fn from(value: Backend) -> Self { + value as i32 as bladerf_backend + } +} diff --git a/src/types/channel.rs b/src/types/channel.rs new file mode 100644 index 0000000..724d799 --- /dev/null +++ b/src/types/channel.rs @@ -0,0 +1,30 @@ +use enum_map::Enum; +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, Enum, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum Channel { + Rx1 = bladerf_channel_layout_BLADERF_RX_X1 as i32, + Rx2 = bladerf_channel_layout_BLADERF_RX_X2 as i32, + Tx1 = bladerf_channel_layout_BLADERF_TX_X1 as i32, + Tx2 = bladerf_channel_layout_BLADERF_TX_X2 as i32, +} + +impl Channel { + pub fn is_rx(&self) -> bool { + matches!(self, Channel::Rx1 | Channel::Rx2) + } + pub fn is_tx(&self) -> bool { + matches!(self, Channel::Tx1 | Channel::Tx2) + } +} + +impl TryFrom for Channel { + type Error = Error; + + fn try_from(channel: bladerf_channel) -> Result { + Self::from_repr(channel).ok_or_else(|| format!("Invalid bladerf channel: {channel}").into()) + } +} diff --git a/src/types/config.rs b/src/types/config.rs new file mode 100644 index 0000000..4e53b75 --- /dev/null +++ b/src/types/config.rs @@ -0,0 +1,7 @@ +use super::ModuleConfig; + +/// Combined RX and TX config +pub struct Config { + pub tx: ModuleConfig, + pub rx: ModuleConfig, +} diff --git a/src/types/correction.rs b/src/types/correction.rs new file mode 100644 index 0000000..33d3cb6 --- /dev/null +++ b/src/types/correction.rs @@ -0,0 +1,25 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +/// Correction value, in arbitrary units +pub type CorrectionValue = i16; + +/// Correction parameter selection +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum Correction { + DcOffsetI = bladerf_correction_BLADERF_CORR_DCOFF_I as i32, + DcOffsetQ = bladerf_correction_BLADERF_CORR_DCOFF_Q as i32, + Phase = bladerf_correction_BLADERF_CORR_PHASE as i32, + Gain = bladerf_correction_BLADERF_CORR_GAIN as i32, +} + +impl TryFrom for Correction { + type Error = Error; + + fn try_from(value: bladerf_correction) -> Result { + Self::from_repr(value as i32) + .ok_or_else(|| Error::msg(format!("Invalid Correction value: {value}"))) + } +} diff --git a/src/types/dev_info.rs b/src/types/dev_info.rs new file mode 100644 index 0000000..d4640d7 --- /dev/null +++ b/src/types/dev_info.rs @@ -0,0 +1,43 @@ +use crate::{sys::*, BladeRF, Result}; +use bytemuck::cast_slice; + +use super::Backend; + +/// Information about a bladerf device connect to the system +#[derive(Clone, Debug)] +pub struct DevInfo(pub(crate) bladerf_devinfo); + +impl DevInfo { + pub fn backend(&self) -> Result { + self.0.backend.try_into() + } + pub fn serial(&self) -> String { + String::from_utf8_lossy(cast_slice(&self.0.serial[..32])).to_string() + } + pub fn usb_bus(&self) -> Option { + Some(self.0.usb_bus) + } + pub fn usb_addr(&self) -> Option { + Some(self.0.usb_addr) + } + pub fn instance(&self) -> u32 { + self.0.instance + } + pub fn manufacturer(&self) -> String { + // TODO: This seems to be `Nuandwn>` instead of `Nuandwn` (what bladeRF-cli --probe gets) + String::from_utf8_lossy(cast_slice(&self.0.manufacturer)).to_string() + } + pub fn product(&self) -> String { + String::from_utf8_lossy(cast_slice(&self.0.product)).to_string() + } + + pub fn open(&self) -> Result { + BladeRF::open_with_devinfo(self) + } +} + +impl From for DevInfo { + fn from(dev: bladerf_devinfo) -> Self { + Self(dev) + } +} diff --git a/src/types/direction.rs b/src/types/direction.rs new file mode 100644 index 0000000..11e25f2 --- /dev/null +++ b/src/types/direction.rs @@ -0,0 +1,26 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +/// Direction Enum +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(u32)] +pub enum Direction { + RX = bladerf_direction_BLADERF_RX, + TX = bladerf_direction_BLADERF_TX, +} + +impl From for bladerf_direction { + fn from(dir: Direction) -> Self { + dir as bladerf_direction + } +} + +impl TryFrom for Direction { + type Error = Error; + + fn try_from(value: bladerf_direction) -> Result { + Self::from_repr(value) + .ok_or_else(|| Error::msg(format!("Invalid Direction value: {value}"))) + } +} diff --git a/src/types/format.rs b/src/types/format.rs new file mode 100644 index 0000000..7ac5ae3 --- /dev/null +++ b/src/types/format.rs @@ -0,0 +1,66 @@ +use num_complex::Complex; +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(u32)] +pub enum Format { + // TODO: See if we can pull in the bladerf docs wholesale + #[doc = "[`bladerf_format_BLADERF_FORMAT_SC16_Q11`]"] + Sc16Q11 = bladerf_format_BLADERF_FORMAT_SC16_Q11, + #[doc = "[`bladerf_format_BLADERF_FORMAT_SC8_Q7`]"] + Sc8Q7 = bladerf_format_BLADERF_FORMAT_SC8_Q7, + // TODO: implement meta parsing + // #[doc = "[`bladerf_format_BLADERF_FORMAT_SC16_Q11_META`]"] + // Sc16Q11Meta = bladerf_format_BLADERF_FORMAT_SC16_Q11_META, + // #[doc = "[`bladerf_format_BLADERF_FORMAT_PACKET_META`]"] + // PacketMeta = bladerf_format_BLADERF_FORMAT_PACKET_META, + // #[doc = "[`bladerf_format_BLADERF_FORMAT_SC8_Q7_META`]"] + // Sc8Q7Meta = bladerf_format_BLADERF_FORMAT_SC8_Q7_META, +} + +impl TryFrom for Format { + type Error = Error; + + fn try_from(format: bladerf_format) -> Result { + Self::from_repr(format).ok_or_else(|| format!("Invalid bladerf format: {format}").into()) + } +} + +/// Supported sample types from the bladeRF. +/// +/// # Safety +/// `is_compatible` must only return true if it is valid to re-interpret bytes from the device as `Self`. +/// +/// Currently this is only implemented for: +/// - `Format::Sc16Q11` => `Complex` +/// - `Format::Sc8Q7` => `Complex` +pub unsafe trait SampleFormat: Sized { + /// Returns true if this data type is commutable with the given format enum + fn is_compatible(format: Format) -> bool; + + fn check_compatability(format: Format) -> Result<()> { + if Self::is_compatible(format) { + Ok(()) + } else { + Err(Error::msg(format!( + "{} is not compatable with configured format {format:?}", + std::any::type_name::() + ))) + } + } +} + +// Implementations for supported types +unsafe impl SampleFormat for Complex { + fn is_compatible(format: Format) -> bool { + matches!(format, Format::Sc16Q11) + } +} + +unsafe impl SampleFormat for Complex { + fn is_compatible(format: Format) -> bool { + matches!(format, Format::Sc8Q7) + } +} diff --git a/src/types/gain.rs b/src/types/gain.rs new file mode 100644 index 0000000..ac465e0 --- /dev/null +++ b/src/types/gain.rs @@ -0,0 +1,51 @@ +use std::ffi::CStr; + +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +/// Gain value, in decibels (dB) +pub type Gain = i32; + +/// Gain control modes +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum GainMode { + /// Device-specific default (automatic, when available) + Default = bladerf_gain_mode_BLADERF_GAIN_DEFAULT as i32, + /// Manual gain control + Manual = bladerf_gain_mode_BLADERF_GAIN_MGC as i32, + /// Automatic gain control, fast attack (advanced) + FastAttackAgc = bladerf_gain_mode_BLADERF_GAIN_FASTATTACK_AGC as i32, + /// Automatic gain control, slow attack (advanced) + SlowAttackAgc = bladerf_gain_mode_BLADERF_GAIN_SLOWATTACK_AGC as i32, + /// Automatic gain control, hybrid attack (advanced) + HybridAgc = bladerf_gain_mode_BLADERF_GAIN_HYBRID_AGC as i32, +} + +impl TryFrom for GainMode { + type Error = Error; + + fn try_from(value: bladerf_gain_mode) -> Result { + Self::from_repr(value as i32) + .ok_or_else(|| Error::msg(format!("Invalid GainMode value: {value}"))) + } +} + +/// Mapping between C string description of gain modes and `GainMode` +pub struct GainModeInfo { + pub name: &'static str, + pub mode: GainMode, +} + +impl From for GainModeInfo { + fn from(mode_info: bladerf_gain_modes) -> Self { + let name = unsafe { CStr::from_ptr(mode_info.name) } + .to_str() + .unwrap_or("Unknown"); + Self { + name, + mode: GainMode::from_repr(mode_info.mode as i32).unwrap_or(GainMode::Default), + } + } +} diff --git a/src/types/log_level.rs b/src/types/log_level.rs new file mode 100644 index 0000000..a41a26b --- /dev/null +++ b/src/types/log_level.rs @@ -0,0 +1,23 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(u32)] +pub enum LogLevel { + Verbose = bladerf_log_level_BLADERF_LOG_LEVEL_VERBOSE, + Debug = bladerf_log_level_BLADERF_LOG_LEVEL_DEBUG, + Info = bladerf_log_level_BLADERF_LOG_LEVEL_INFO, + Warning = bladerf_log_level_BLADERF_LOG_LEVEL_WARNING, + Error = bladerf_log_level_BLADERF_LOG_LEVEL_ERROR, + Critical = bladerf_log_level_BLADERF_LOG_LEVEL_CRITICAL, + Silent = bladerf_log_level_BLADERF_LOG_LEVEL_SILENT, +} + +impl TryFrom for LogLevel { + type Error = Error; + + fn try_from(level: bladerf_log_level) -> Result { + Self::from_repr(level).ok_or_else(|| format!("Invalid bladerf log level: {level}").into()) + } +} diff --git a/src/types/loopback.rs b/src/types/loopback.rs new file mode 100644 index 0000000..0087d93 --- /dev/null +++ b/src/types/loopback.rs @@ -0,0 +1,47 @@ +use std::ffi::CStr; + +use crate::{sys::*, Error, Result}; +use strum::FromRepr; + +/// Loopback configuration +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(u32)] +pub enum Loopback { + None = bladerf_loopback_BLADERF_LB_NONE, + RfLna1 = bladerf_loopback_BLADERF_LB_RF_LNA1, + RfLna2 = bladerf_loopback_BLADERF_LB_RF_LNA2, + RfLna3 = bladerf_loopback_BLADERF_LB_RF_LNA3, + Firmware = bladerf_loopback_BLADERF_LB_FIRMWARE, + RficBist = bladerf_loopback_BLADERF_LB_RFIC_BIST, + BbTxlpfRxlpf = bladerf_loopback_BLADERF_LB_BB_TXLPF_RXLPF, + BbTxlpfRxvga2 = bladerf_loopback_BLADERF_LB_BB_TXLPF_RXVGA2, + BbTxvga1Rxlpf = bladerf_loopback_BLADERF_LB_BB_TXVGA1_RXLPF, + BbTxvga1Rxvga2 = bladerf_loopback_BLADERF_LB_BB_TXVGA1_RXVGA2, +} + +impl TryFrom for Loopback { + type Error = Error; + + fn try_from(loopback: bladerf_loopback) -> Result { + Self::from_repr(loopback) + .ok_or_else(|| format!("Invalid bladerf loopback mode: {loopback}").into()) + } +} + +pub struct LoopbackModeInfo { + pub name: Option, + pub mode: Loopback, +} + +impl From for LoopbackModeInfo { + fn from(mode_info: bladerf_loopback_modes) -> Self { + let name = unsafe { CStr::from_ptr(mode_info.name) } + .to_str() + .map(|s| s.to_string()) + .ok(); + Self { + name, + mode: Loopback::from_repr(mode_info.mode).unwrap_or(Loopback::None), + } + } +} diff --git a/src/types/lpf_mode.rs b/src/types/lpf_mode.rs new file mode 100644 index 0000000..55c263d --- /dev/null +++ b/src/types/lpf_mode.rs @@ -0,0 +1,20 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum LPFMode { + Normal = bladerf_lpf_mode_BLADERF_LPF_NORMAL as i32, + Bypassed = bladerf_lpf_mode_BLADERF_LPF_BYPASSED as i32, + Disabled = bladerf_lpf_mode_BLADERF_LPF_DISABLED as i32, +} + +impl TryFrom for LPFMode { + type Error = Error; + + fn try_from(value: bladerf_lpf_mode) -> Result { + Self::from_repr(value as i32) + .ok_or_else(|| Error::msg(format!("Invalid LPFMode value: {value}"))) + } +} diff --git a/src/types/metadata.rs b/src/types/metadata.rs new file mode 100644 index 0000000..b6be62a --- /dev/null +++ b/src/types/metadata.rs @@ -0,0 +1,45 @@ +use crate::sys::*; + +/// Additional types for Metadata +#[derive(Clone, Debug)] +pub struct Metadata { + pub timestamp: u64, + pub flags: u32, + // Add other fields as necessary +} + +impl Default for Metadata { + fn default() -> Self { + Self::new() + } +} + +impl Metadata { + pub fn new() -> Self { + Self { + timestamp: 0, + flags: 0, + } + } +} + +impl From<&bladerf_metadata> for Metadata { + fn from(meta: &bladerf_metadata) -> Self { + Self { + timestamp: meta.timestamp, + flags: meta.flags, + } + } +} + +impl From<&Metadata> for bladerf_metadata { + fn from(val: &Metadata) -> Self { + bladerf_metadata { + timestamp: val.timestamp, + flags: val.flags, + status: 0, + actual_count: 0, + reserved: [0u8; 32], + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..1653cfa --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,62 @@ +pub mod module_config; +pub use module_config::ModuleConfig; + +pub mod version; +pub use version::Version; + +pub mod log_level; +pub use log_level::LogLevel; + +pub mod rational_rate; +pub use rational_rate::RationalRate; + +pub mod backend; +pub use backend::Backend; + +pub mod dev_info; +pub use dev_info::DevInfo; + +pub mod config; +pub use config::Config; + +pub mod channel; +pub use channel::Channel; + +pub mod metadata; +pub use metadata::Metadata; + +pub mod direction; +pub use direction::Direction; + +pub mod loopback; +pub use loopback::{Loopback, LoopbackModeInfo}; + +pub mod format; +pub use format::{Format, SampleFormat}; + +pub mod sampling; +pub use sampling::Sampling; + +pub mod rx_mux; +pub use rx_mux::RxMux; + +pub mod lpf_mode; +pub use lpf_mode::LPFMode; + +pub mod quick_tune; +pub use quick_tune::QuickTune; + +pub mod tuning_mode; +pub use tuning_mode::TuningMode; + +pub mod gain; +pub use gain::{Gain, GainMode, GainModeInfo}; + +pub mod range; +pub use range::Range; + +pub mod correction; +pub use correction::{Correction, CorrectionValue}; + +pub mod trigger; +pub use trigger::{Trigger, TriggerRole, TriggerSignal}; diff --git a/src/types/module_config.rs b/src/types/module_config.rs new file mode 100644 index 0000000..49b871d --- /dev/null +++ b/src/types/module_config.rs @@ -0,0 +1,9 @@ +/// BladeRF module config object +#[derive(Clone, Debug)] +pub struct ModuleConfig { + pub frequency: u64, + pub sample_rate: u32, + pub bandwidth: u32, + /// Set overall system gain + pub gain: i32, +} diff --git a/src/types/quick_tune.rs b/src/types/quick_tune.rs new file mode 100644 index 0000000..c9e4760 --- /dev/null +++ b/src/types/quick_tune.rs @@ -0,0 +1,9 @@ +#[derive(Clone, Debug)] +#[repr(C)] +pub struct QuickTune { + pub freqsel: u8, + pub vcocap: u8, + pub nint: u16, + pub nfrac: u32, + pub flags: u8, +} diff --git a/src/types/range.rs b/src/types/range.rs new file mode 100644 index 0000000..332c238 --- /dev/null +++ b/src/types/range.rs @@ -0,0 +1,35 @@ +use crate::sys::*; + +/// Range struct to represent `bladerf_range` +#[derive(Debug)] +pub struct Range { + pub min: f64, + pub max: f64, + pub step: f64, +} + +impl Range { + pub fn contains(&self, query: impl Into) -> bool { + let steps = (query.into() as f64 - self.min) / self.step; + steps % 1.0 < 1e-8 + } +} + +impl std::fmt::Display for Range { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "{:.0}..{:.0} (step {:.0})", + self.min, self.max, self.step, + )) + } +} + +impl From<&bladerf_range> for Range { + fn from(range: &bladerf_range) -> Self { + Self { + min: range.min as f64 * range.scale as f64, + max: range.max as f64 * range.scale as f64, + step: range.step as f64 * range.scale as f64, + } + } +} diff --git a/src/types/rational_rate.rs b/src/types/rational_rate.rs new file mode 100644 index 0000000..d91afa7 --- /dev/null +++ b/src/types/rational_rate.rs @@ -0,0 +1,20 @@ +use crate::sys::*; + +pub struct RationalRate { + /// Integer portion + pub integer: u64, + /// Numerator in fractional portion + pub num: u64, + /// Denominator in fractional portion. This must be greater than 0 + pub den: u64, +} + +impl From for RationalRate { + fn from(rate: bladerf_rational_rate) -> Self { + Self { + integer: rate.integer, + num: rate.num, + den: rate.den, + } + } +} diff --git a/src/types/rx_mux.rs b/src/types/rx_mux.rs new file mode 100644 index 0000000..42e85d9 --- /dev/null +++ b/src/types/rx_mux.rs @@ -0,0 +1,21 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum RxMux { + Invalid = bladerf_rx_mux_BLADERF_RX_MUX_INVALID, + Baseband = bladerf_rx_mux_BLADERF_RX_MUX_BASEBAND, + Counter12bit = bladerf_rx_mux_BLADERF_RX_MUX_12BIT_COUNTER, + Counter32bit = bladerf_rx_mux_BLADERF_RX_MUX_32BIT_COUNTER, + DigitalLoopback = bladerf_rx_mux_BLADERF_RX_MUX_DIGITAL_LOOPBACK, +} + +impl TryFrom for RxMux { + type Error = Error; + + fn try_from(value: bladerf_rx_mux) -> Result { + Self::from_repr(value).ok_or_else(|| Error::msg(format!("Invalid RxMux value: {value}"))) + } +} diff --git a/src/types/sampling.rs b/src/types/sampling.rs new file mode 100644 index 0000000..320688e --- /dev/null +++ b/src/types/sampling.rs @@ -0,0 +1,20 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum Sampling { + Unknown = bladerf_sampling_BLADERF_SAMPLING_UNKNOWN as i32, + Internal = bladerf_sampling_BLADERF_SAMPLING_INTERNAL as i32, + External = bladerf_sampling_BLADERF_SAMPLING_EXTERNAL as i32, +} + +impl TryFrom for Sampling { + type Error = Error; + + fn try_from(value: bladerf_sampling) -> Result { + Self::from_repr(value as i32) + .ok_or_else(|| Error::msg(format!("Invalid Sampling value: {value}"))) + } +} diff --git a/src/types/trigger.rs b/src/types/trigger.rs new file mode 100644 index 0000000..cd57b87 --- /dev/null +++ b/src/types/trigger.rs @@ -0,0 +1,72 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +use super::Channel; + +/// Trigger role +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum TriggerRole { + Invalid = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_INVALID, + Disabled = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_DISABLED, + Master = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_MASTER, + Slave = bladerf_trigger_role_BLADERF_TRIGGER_ROLE_SLAVE, +} + +impl TryFrom for TriggerRole { + type Error = Error; + + fn try_from(value: bladerf_trigger_role) -> Result { + Self::from_repr(value) + .ok_or_else(|| Error::msg(format!("Invalid TriggerRole value: {value}"))) + } +} + +/// Trigger signal selection +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum TriggerSignal { + Invalid = bladerf_trigger_signal_BLADERF_TRIGGER_INVALID, + J71_4 = bladerf_trigger_signal_BLADERF_TRIGGER_J71_4, + J51_1 = bladerf_trigger_signal_BLADERF_TRIGGER_J51_1, + MiniExp1 = bladerf_trigger_signal_BLADERF_TRIGGER_MINI_EXP_1, + User0 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_0, + User1 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_1, + User2 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_2, + User3 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_3, + User4 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_4, + User5 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_5, + User6 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_6, + User7 = bladerf_trigger_signal_BLADERF_TRIGGER_USER_7, +} + +impl TryFrom for TriggerSignal { + type Error = Error; + + fn try_from(value: bladerf_trigger_signal) -> Result { + Self::from_repr(value) + .ok_or_else(|| Error::msg(format!("Invalid TriggerSignal value: {value}"))) + } +} + +/// Trigger configuration +pub struct Trigger { + pub channel: Channel, + pub role: TriggerRole, + pub signal: TriggerSignal, + pub options: u64, +} + +impl TryFrom for Trigger { + type Error = Error; + + fn try_from(t: bladerf_trigger) -> Result { + Ok(Self { + channel: t.channel.try_into()?, + role: t.role.try_into()?, + signal: t.signal.try_into()?, + options: t.options, + }) + } +} diff --git a/src/types/tuning_mode.rs b/src/types/tuning_mode.rs new file mode 100644 index 0000000..fd79ab0 --- /dev/null +++ b/src/types/tuning_mode.rs @@ -0,0 +1,20 @@ +use strum::FromRepr; + +use crate::{sys::*, Error, Result}; + +#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] +#[repr(i32)] +pub enum TuningMode { + Host = bladerf_tuning_mode_BLADERF_TUNING_MODE_HOST, + FPGA = bladerf_tuning_mode_BLADERF_TUNING_MODE_FPGA, + Invalid = bladerf_tuning_mode_BLADERF_TUNING_MODE_INVALID, +} + +impl TryFrom for TuningMode { + type Error = Error; + + fn try_from(value: bladerf_tuning_mode) -> Result { + Self::from_repr(value) + .ok_or_else(|| Error::msg(format!("Invalid TuningMode value: {value}"))) + } +} diff --git a/src/types/version.rs b/src/types/version.rs new file mode 100644 index 0000000..5a370db --- /dev/null +++ b/src/types/version.rs @@ -0,0 +1,177 @@ +use std::{cmp, ffi::CStr}; + +use libbladerf_sys::bladerf_version; + +#[derive(Copy, Clone, Debug)] +pub struct Version { + pub major: u16, + pub minor: u16, + pub patch: u16, + /// Textual description of the release, or None if not available or if not UTF-8 + pub describe: Option<&'static str>, +} + +impl Version { + /// Converts the ffi type `bladerf_version` to `Self`. + /// + /// # Safety + /// `version` must come from a bladerf ffi call. + /// More specifically: + /// `version.describe` must be a null-terminated, immutable, statically-allocated (always valid), + /// string. + pub unsafe fn from_ffi(version: &bladerf_version) -> Self { + let describe = if !version.describe.is_null() { + // SAFETY: bladefr docs on field say do not try to modify or free this, + // which sounds like a static lifetime to me + let cstr = unsafe { CStr::from_ptr::<'static>(version.describe) }; + cstr.to_str().ok() + } else { + None + }; + + Version { + major: version.major, + minor: version.minor, + patch: version.patch, + describe, + } + } +} + +impl std::fmt::Display for Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(desc) = self.describe { + f.write_fmt(format_args!( + "v{}.{}.{} ({})", + self.major, self.minor, self.patch, desc + )) + } else { + f.write_fmt(format_args!( + "v{}.{}.{}", + self.major, self.minor, self.patch, + )) + } + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Version { + fn cmp(&self, other: &Self) -> cmp::Ordering { + let major_ord = self.major.cmp(&other.major); + if major_ord != cmp::Ordering::Equal { + return major_ord; + } + let minor_ord = self.minor.cmp(&other.minor); + if minor_ord != cmp::Ordering::Equal { + return minor_ord; + } + self.patch.cmp(&other.patch) + } +} + +impl PartialEq for Version { + fn eq(&self, other: &Self) -> bool { + self.major == other.major && self.minor == other.minor && self.patch == other.patch + } +} + +impl Eq for Version {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn version_cmp() { + let v1 = Version { + major: 2, + minor: 0, + patch: 0, + describe: None, + }; + let v2 = Version { + major: 1, + minor: 5, + patch: 10, + describe: None, + }; + + assert!(v1 > v2); + assert!(v2 < v1); + + let v1 = Version { + major: 1, + minor: 6, + patch: 0, + describe: None, + }; + let v2 = Version { + major: 1, + minor: 5, + patch: 10, + describe: None, + }; + + assert!(v1 > v2); + assert!(v2 < v1); + + let v1 = Version { + major: 1, + minor: 5, + patch: 11, + describe: None, + }; + let v2 = Version { + major: 1, + minor: 5, + patch: 10, + describe: None, + }; + + assert!(v1 > v2); + assert!(v2 < v1); + + let v1 = Version { + major: 1, + minor: 5, + patch: 10, + describe: Some("test"), + }; + let v2 = Version { + major: 1, + minor: 5, + patch: 10, + describe: Some("another test"), + }; + + assert_eq!(v1, v2); + + let v1 = Version { + major: 1, + minor: 5, + patch: 11, + describe: None, + }; + let v2 = Version { + major: 1, + minor: 6, + patch: 0, + describe: None, + }; + let v3 = Version { + major: 2, + minor: 0, + patch: 0, + describe: None, + }; + + assert!(v1 < v2); + assert!(v2 < v3); + assert!(v1 < v3); + } +} From 094d6bb8d6a754b4091841b6e2b17bc761a38f79 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Thu, 12 Dec 2024 20:35:11 -0500 Subject: [PATCH 07/32] Only export the types, not the modules --- src/types/mod.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index 1653cfa..f12fa67 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,62 +1,62 @@ -pub mod module_config; +mod module_config; pub use module_config::ModuleConfig; -pub mod version; +mod version; pub use version::Version; -pub mod log_level; +mod log_level; pub use log_level::LogLevel; -pub mod rational_rate; +mod rational_rate; pub use rational_rate::RationalRate; -pub mod backend; +mod backend; pub use backend::Backend; -pub mod dev_info; +mod dev_info; pub use dev_info::DevInfo; -pub mod config; +mod config; pub use config::Config; -pub mod channel; +mod channel; pub use channel::Channel; -pub mod metadata; +mod metadata; pub use metadata::Metadata; -pub mod direction; +mod direction; pub use direction::Direction; -pub mod loopback; +mod loopback; pub use loopback::{Loopback, LoopbackModeInfo}; -pub mod format; +mod format; pub use format::{Format, SampleFormat}; -pub mod sampling; +mod sampling; pub use sampling::Sampling; -pub mod rx_mux; +mod rx_mux; pub use rx_mux::RxMux; -pub mod lpf_mode; +mod lpf_mode; pub use lpf_mode::LPFMode; -pub mod quick_tune; +mod quick_tune; pub use quick_tune::QuickTune; -pub mod tuning_mode; +mod tuning_mode; pub use tuning_mode::TuningMode; -pub mod gain; +mod gain; pub use gain::{Gain, GainMode, GainModeInfo}; -pub mod range; +mod range; pub use range::Range; -pub mod correction; +mod correction; pub use correction::{Correction, CorrectionValue}; -pub mod trigger; +mod trigger; pub use trigger::{Trigger, TriggerRole, TriggerSignal}; From 32a1d1984d4453e7a779980b168eb2bed5d7fa18 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Fri, 13 Dec 2024 00:26:47 -0500 Subject: [PATCH 08/32] Additional trait method `new` for Correction value --- src/types/correction.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/types/correction.rs b/src/types/correction.rs index 264bdcf..2d8a8c7 100644 --- a/src/types/correction.rs +++ b/src/types/correction.rs @@ -17,6 +17,7 @@ use crate::{sys::*, Error, Result}; pub trait CorrectionValue: Sized { const TYPE: Correction; + fn new(value: i16) -> Option; fn value(&self) -> i16; unsafe fn new_unchecked(val: i16) -> Self; } @@ -41,6 +42,11 @@ impl CorrectionDcOffsetI { impl CorrectionValue for CorrectionDcOffsetI { const TYPE: Correction = Correction::DcOffsetI; + + fn new(value: i16) -> Option { + Self::new(value) + } + fn value(&self) -> i16 { self.into_inner() } @@ -69,6 +75,11 @@ impl CorrectionDcOffsetQ { impl CorrectionValue for CorrectionDcOffsetQ { const TYPE: Correction = Correction::DcOffsetQ; + + fn new(value: i16) -> Option { + Self::new(value) + } + fn value(&self) -> i16 { self.into_inner() } @@ -97,6 +108,11 @@ impl CorrectionPhase { impl CorrectionValue for CorrectionPhase { const TYPE: Correction = Correction::Phase; + + fn new(value: i16) -> Option { + Self::new(value) + } + fn value(&self) -> i16 { self.into_inner() } @@ -125,6 +141,11 @@ impl CorrectionGain { impl CorrectionValue for CorrectionGain { const TYPE: Correction = Correction::Gain; + + fn new(value: i16) -> Option { + Self::new(value) + } + fn value(&self) -> i16 { self.into_inner() } From dc9664af399c7c8d11a89be2b20941e2cdda19b3 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Fri, 13 Dec 2024 00:28:37 -0500 Subject: [PATCH 09/32] Changes to support new system of selecting corrections. --- examples/siggen.rs | 27 +++++++++++++++------------ src/types/mod.rs | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index ca52ada..968f482 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -13,7 +13,10 @@ use ratatui::{ use ratatui::prelude::*; -use bladerf::{BladeRF, Correction, CorrectionValue}; +use bladerf::{ + BladeRF, Correction, CorrectionDcOffsetI, CorrectionDcOffsetQ, CorrectionGain, CorrectionPhase, + CorrectionValue, +}; use tui_textarea::{Input, Key, TextArea}; #[derive(Debug, Clone, Copy)] @@ -102,8 +105,8 @@ fn validate_frequency(val: &str) -> Result { } } -fn validate_correction(val: &str, corr: Correction) -> Result { - match val.parse::().map(|x| CorrectionValue::new(corr, x)) { +fn validate_correction(val: &str) -> Result { + match val.parse::().map(|x| T::new(x)) { Err(err) => Err(format!("{}", err)), Ok(Some(x)) => Ok(x), Ok(None) => Err(format!("Value `{val}` out of range")), @@ -235,19 +238,19 @@ impl App { NumericInput::new(self.get_freq().to_string(), validate_frequency); let mut icorr_input = NumericInput::new(self.get_icorr().to_string(), |x| { - validate_correction(x, Correction::DcOffsetI) + validate_correction::(x) }); let mut qcorr_input = NumericInput::new(self.get_qcorr().to_string(), |x| { - validate_correction(x, Correction::DcOffsetQ) + validate_correction::(x) }); let mut phase_input = NumericInput::new(self.get_phase().to_string(), |x| { - validate_correction(x, Correction::Phase) + validate_correction::(x) }); let mut gain_input = NumericInput::new(self.get_gain().to_string(), |x| { - validate_correction(x, Correction::Gain) + validate_correction::(x) }); while !self.exit { @@ -319,28 +322,28 @@ impl App { fn get_icorr(&self) -> i16 { self.device - .get_correction(self.channel, bladerf::Correction::DcOffsetI) + .get_correction::(self.channel) .unwrap() .into_inner() } fn get_qcorr(&self) -> i16 { self.device - .get_correction(self.channel, bladerf::Correction::DcOffsetQ) + .get_correction::(self.channel) .unwrap() .into_inner() } fn get_phase(&self) -> i16 { self.device - .get_correction(self.channel, bladerf::Correction::Phase) + .get_correction::(self.channel) .unwrap() .into_inner() } fn get_gain(&self) -> i16 { self.device - .get_correction(self.channel, bladerf::Correction::Gain) + .get_correction::(self.channel) .unwrap() .into_inner() } @@ -349,7 +352,7 @@ impl App { self.device.set_frequency(self.channel, freq).unwrap() } - fn set_corr(&self, corr: CorrectionValue) { + fn set_corr(&self, corr: T) { self.device.set_correction(self.channel, corr).unwrap() } diff --git a/src/types/mod.rs b/src/types/mod.rs index f12fa67..5a23a3d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -56,7 +56,7 @@ mod range; pub use range::Range; mod correction; -pub use correction::{Correction, CorrectionValue}; +pub use correction::*; mod trigger; pub use trigger::{Trigger, TriggerRole, TriggerSignal}; From c28c1ad383b48005181365d6cf98e5bcc1491038 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Fri, 13 Dec 2024 07:36:47 -0500 Subject: [PATCH 10/32] Various testing --- examples/siggen.rs | 67 +++++++++------------------------------------- 1 file changed, 12 insertions(+), 55 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 968f482..4254f0a 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -51,51 +51,13 @@ impl SelectedInput { pub struct App { channel: bladerf::Channel, - // frequency: u64, - // i_corr: i16, - // q_corr: i16, - // phase: i16, - // gain: i16, - // transmitting: bool, device: BladeRF, selected_input: SelectedInput, + focused: bool, exit: bool, } -// fn validate_frequency(textarea: &mut TextArea) -> bool { -// match textarea.lines()[0].parse::() { -// Err(err) => { -// textarea.set_style(Style::default().fg(Color::LightRed)); -// textarea.set_block( -// Block::default() -// .borders(Borders::ALL) -// .border_style(Color::LightRed) -// .title(format!("ERROR: {}", err)), -// ); -// false -// } -// Ok(freq) if (freq > 300000000) && (freq < 3000000000) => { -// textarea.set_style(Style::default().fg(Color::LightGreen)); -// textarea.set_block( -// Block::default() -// .border_style(Color::LightGreen) -// .borders(Borders::ALL) -// .title("OK"), -// ); -// true -// } -// Ok(_) => { -// textarea.set_style(Style::default().fg(Color::LightRed)); -// textarea.set_block( -// Block::default() -// .borders(Borders::ALL) -// .border_style(Color::LightRed) -// .title("ERROR: out of range"), -// ); -// false -// } -// } -// } +type IntValidationFunction = Box Result>; fn validate_frequency(val: &str) -> Result { match val.parse::() { @@ -116,7 +78,7 @@ fn validate_correction(val: &str) -> Result { /// A custom numeric input widget with validation pub struct NumericInput<'a, T, E> { textarea: TextArea<'a>, - validation_fn: Box Result>, // Validation logic + validation_fn: IntValidationFunction, // Validation logic } impl<'a, T> NumericInput<'a, T, String> { @@ -212,22 +174,9 @@ impl App { let channel = bladerf::Channel::Tx1; App { channel, - // frequency: dev.get_frequency(channel).unwrap(), - // i_corr: dev - // .get_correction(channel, bladerf::Correction::DcOffsetI) - // .unwrap(), - // q_corr: dev - // .get_correction(channel, bladerf::Correction::DcOffsetQ) - // .unwrap(), - // phase: dev - // .get_correction(channel, bladerf::Correction::Phase) - // .unwrap(), - // gain: dev - // .get_correction(channel, bladerf::Correction::Gain) - // .unwrap(), - // transmitting: false, device: dev, selected_input: SelectedInput::Frequency, + focused: false, exit: false, } } @@ -299,6 +248,14 @@ impl App { SelectedInput::Phase => self.handle_events(&mut phase_input)?, SelectedInput::Gain => self.handle_events(&mut gain_input)?, }; + // let test: Option> = match self.selected_input { + // SelectedInput::DcOffsetI => Some(Box::new(frequency_input)), + // SelectedInput::DcOffsetQ => Some(Box::new(gain_input)), + // SelectedInput::Frequency => todo!(), + // SelectedInput::Phase => todo!(), + // SelectedInput::Gain => todo!(), + // }; + // self.handle_events(selected_text_field)?; } Ok(()) From 6a99dd80526288d36b23c426b261e95cb9718b96 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Fri, 13 Dec 2024 07:42:21 -0500 Subject: [PATCH 11/32] Glob Exports in types mod.rs for reexports. --- src/types/mod.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/types/mod.rs b/src/types/mod.rs index f12fa67..65c5021 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,62 +1,62 @@ mod module_config; -pub use module_config::ModuleConfig; +pub use module_config::*; mod version; -pub use version::Version; +pub use version::*; mod log_level; -pub use log_level::LogLevel; +pub use log_level::*; mod rational_rate; -pub use rational_rate::RationalRate; +pub use rational_rate::*; mod backend; -pub use backend::Backend; +pub use backend::*; mod dev_info; -pub use dev_info::DevInfo; +pub use dev_info::*; mod config; -pub use config::Config; +pub use config::*; mod channel; -pub use channel::Channel; +pub use channel::*; mod metadata; -pub use metadata::Metadata; +pub use metadata::*; mod direction; -pub use direction::Direction; +pub use direction::*; mod loopback; -pub use loopback::{Loopback, LoopbackModeInfo}; +pub use loopback::*; mod format; -pub use format::{Format, SampleFormat}; +pub use format::*; mod sampling; -pub use sampling::Sampling; +pub use sampling::*; mod rx_mux; -pub use rx_mux::RxMux; +pub use rx_mux::*; mod lpf_mode; -pub use lpf_mode::LPFMode; +pub use lpf_mode::*; mod quick_tune; -pub use quick_tune::QuickTune; +pub use quick_tune::*; mod tuning_mode; -pub use tuning_mode::TuningMode; +pub use tuning_mode::*; mod gain; -pub use gain::{Gain, GainMode, GainModeInfo}; +pub use gain::*; mod range; -pub use range::Range; +pub use range::*; mod correction; -pub use correction::{Correction, CorrectionValue}; +pub use correction::*; mod trigger; -pub use trigger::{Trigger, TriggerRole, TriggerSignal}; +pub use trigger::*; From 98261cc811b90ae0ff26ff50a8d109829a8dbec7 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 13:56:46 -0500 Subject: [PATCH 12/32] Enter to allow editing --- examples/siggen.rs | 67 +++++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 4254f0a..1931cfd 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -211,12 +211,14 @@ impl App { phase_input.remove_focus(); gain_input.remove_focus(); - match self.selected_input { - SelectedInput::Frequency => frequency_input.set_focus(), - SelectedInput::DcOffsetI => icorr_input.set_focus(), - SelectedInput::DcOffsetQ => qcorr_input.set_focus(), - SelectedInput::Phase => phase_input.set_focus(), - SelectedInput::Gain => gain_input.set_focus(), + if self.focused { + match self.selected_input { + SelectedInput::Frequency => frequency_input.set_focus(), + SelectedInput::DcOffsetI => icorr_input.set_focus(), + SelectedInput::DcOffsetQ => qcorr_input.set_focus(), + SelectedInput::Phase => phase_input.set_focus(), + SelectedInput::Gain => gain_input.set_focus(), + } }; terminal.draw(|frame| { @@ -240,13 +242,17 @@ impl App { frame.render_widget(&debug_test, layout[5]); })?; - match self.selected_input { - // let selected_text_field: dyn NumericInputHandle = match self.selected_input { - SelectedInput::Frequency => self.handle_events(&mut frequency_input)?, - SelectedInput::DcOffsetI => self.handle_events(&mut icorr_input)?, - SelectedInput::DcOffsetQ => self.handle_events(&mut qcorr_input)?, - SelectedInput::Phase => self.handle_events(&mut phase_input)?, - SelectedInput::Gain => self.handle_events(&mut gain_input)?, + if self.focused { + match self.selected_input { + // let selected_text_field: dyn NumericInputHandle = match self.selected_input { + SelectedInput::Frequency => self.handle_events(Some(&mut frequency_input))?, + SelectedInput::DcOffsetI => self.handle_events(Some(&mut icorr_input))?, + SelectedInput::DcOffsetQ => self.handle_events(Some(&mut qcorr_input))?, + SelectedInput::Phase => self.handle_events(Some(&mut phase_input))?, + SelectedInput::Gain => self.handle_events(Some(&mut gain_input))?, + } + } else { + self.handle_events(None)?; }; // let test: Option> = match self.selected_input { // SelectedInput::DcOffsetI => Some(Box::new(frequency_input)), @@ -273,6 +279,14 @@ impl App { self.exit = true; } + fn set_focus(&mut self) { + self.focused = true; + } + + fn unset_focus(&mut self) { + self.focused = false; + } + fn get_freq(&self) -> u64 { self.device.get_frequency(self.channel).unwrap() } @@ -314,12 +328,27 @@ impl App { } /// updates the application's state based on user input - fn handle_events(&mut self, idk: &mut dyn NumericInputHandle) -> io::Result<()> { - match crossterm::event::read()?.into() { - Input { key: Key::Esc, .. } => self.exit(), - Input { key: Key::Up, .. } => self.selected_up(), - Input { key: Key::Down, .. } => self.selected_down(), - input => idk.handle_input(input), + fn handle_events(&mut self, idk: Option<&mut dyn NumericInputHandle>) -> io::Result<()> { + if let Some(idk2) = idk { + match crossterm::event::read()?.into() { + Input { key: Key::Esc, .. } => self.exit(), + Input { key: Key::Up, .. } => self.selected_up(), + Input { key: Key::Down, .. } => self.selected_down(), + Input { + key: Key::Enter, .. + } => self.unset_focus(), + input => idk2.handle_input(input), + } + } else { + match crossterm::event::read()?.into() { + Input { key: Key::Esc, .. } => self.exit(), + Input { key: Key::Up, .. } => self.selected_up(), + Input { key: Key::Down, .. } => self.selected_down(), + Input { + key: Key::Enter, .. + } => self.set_focus(), + _ => {} + } } Ok(()) From 1a0c4e61eb5c9fe88efff63ca7a21d605436382d Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 17:57:58 -0500 Subject: [PATCH 13/32] A dynamic dispatch way of iterating through the inputs. --- examples/siggen.rs | 138 +++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 43 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 1931cfd..45fa4fb 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,4 +1,4 @@ -use std::{error::Error, io}; +use std::{any::Any, error::Error, io}; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ @@ -92,7 +92,7 @@ impl<'a, T> NumericInput<'a, T, String> { validation_fn: Box::new(validation_fn), }; numeric_input.validate(); - numeric_input.remove_focus(); + numeric_input.remove_focus_inner(); numeric_input } @@ -128,13 +128,13 @@ impl<'a, T> NumericInput<'a, T, String> { } /// Sets focus (cursor style) to this input - pub fn set_focus(&mut self) { + pub fn set_focus_inner(&mut self) { self.textarea .set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); } /// Removes focus from this input - pub fn remove_focus(&mut self) { + pub fn remove_focus_inner(&mut self) { self.textarea.set_cursor_style(Style::default()); } @@ -146,18 +146,45 @@ impl<'a, T> NumericInput<'a, T, String> { trait NumericInputHandle { fn handle_input(&mut self, input: Input); + fn set_focus(&mut self); + fn unset_focus(&mut self); + fn num_render(&self, area: Rect, buf: &mut Buffer); } impl<'a, T> NumericInputHandle for &mut NumericInput<'a, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } + + fn set_focus(&mut self) { + self.set_focus_inner(); + } + + fn unset_focus(&mut self) { + self.remove_focus_inner(); + } + + fn num_render(&self, area: Rect, buf: &mut Buffer) { + self.textarea.render(area, buf); + } } impl<'a, T> NumericInputHandle for NumericInput<'a, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } + + fn set_focus(&mut self) { + self.set_focus_inner(); + } + + fn unset_focus(&mut self) { + self.remove_focus_inner(); + } + + fn num_render(&self, area: Rect, buf: &mut Buffer) { + self.textarea.render(area, buf); + } } impl<'a, T, E> Widget for &NumericInput<'a, T, E> { @@ -169,6 +196,37 @@ impl<'a, T, E> Widget for &NumericInput<'a, T, E> { } } +impl<'a, T, E> Widget for NumericInput<'a, T, E> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + self.textarea.render(area, buf); + } +} + +trait NumericInputWidget: NumericInputHandle + Widget {} + +impl<'a, T> NumericInputWidget for NumericInput<'a, T, String> {} + +impl Widget for &dyn NumericInputWidget { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + self.num_render(area, buf); + } +} + +impl Widget for Box { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + self.num_render(area, buf); + } +} + impl App { fn new(dev: BladeRF) -> App { let channel = bladerf::Channel::Tx1; @@ -183,43 +241,48 @@ impl App { /// runs the application's main loop until the user quits pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { - let mut frequency_input = - NumericInput::new(self.get_freq().to_string(), validate_frequency); + let frequency_input = NumericInput::new(self.get_freq().to_string(), validate_frequency); - let mut icorr_input = NumericInput::new(self.get_icorr().to_string(), |x| { + let icorr_input = NumericInput::new(self.get_icorr().to_string(), |x| { validate_correction::(x) }); - let mut qcorr_input = NumericInput::new(self.get_qcorr().to_string(), |x| { + let qcorr_input = NumericInput::new(self.get_qcorr().to_string(), |x| { validate_correction::(x) }); - let mut phase_input = NumericInput::new(self.get_phase().to_string(), |x| { + let phase_input = NumericInput::new(self.get_phase().to_string(), |x| { validate_correction::(x) }); - let mut gain_input = NumericInput::new(self.get_gain().to_string(), |x| { + let gain_input = NumericInput::new(self.get_gain().to_string(), |x| { validate_correction::(x) }); + let mut items: Vec> = vec![ + Box::new(frequency_input), + Box::new(icorr_input), + Box::new(qcorr_input), + Box::new(phase_input), + Box::new(gain_input), + ]; + while !self.exit { let debug_test = Text::from(format!("Sel: {:?}", self.selected_input)); - frequency_input.remove_focus(); - icorr_input.remove_focus(); - qcorr_input.remove_focus(); - phase_input.remove_focus(); - gain_input.remove_focus(); + for item in items.iter_mut() { + item.unset_focus(); + } if self.focused { match self.selected_input { - SelectedInput::Frequency => frequency_input.set_focus(), - SelectedInput::DcOffsetI => icorr_input.set_focus(), - SelectedInput::DcOffsetQ => qcorr_input.set_focus(), - SelectedInput::Phase => phase_input.set_focus(), - SelectedInput::Gain => gain_input.set_focus(), + SelectedInput::Frequency => items[0].set_focus(), + SelectedInput::DcOffsetI => items[1].set_focus(), + SelectedInput::DcOffsetQ => items[2].set_focus(), + SelectedInput::Phase => items[3].set_focus(), + SelectedInput::Gain => items[4].set_focus(), } - }; + } terminal.draw(|frame| { let layout = Layout::default() @@ -234,35 +297,24 @@ impl App { ]) .split(frame.area()); - frame.render_widget(&frequency_input, layout[0]); - frame.render_widget(&icorr_input, layout[1]); - frame.render_widget(&qcorr_input, layout[2]); - frame.render_widget(&phase_input, layout[3]); - frame.render_widget(&gain_input, layout[4]); - frame.render_widget(&debug_test, layout[5]); + for (num_input, layout) in items.iter().zip(layout.iter()) { + let x = num_input.as_ref(); + frame.render_widget(x, *layout); + } + frame.render_widget(debug_test, layout[5]); })?; if self.focused { match self.selected_input { - // let selected_text_field: dyn NumericInputHandle = match self.selected_input { - SelectedInput::Frequency => self.handle_events(Some(&mut frequency_input))?, - SelectedInput::DcOffsetI => self.handle_events(Some(&mut icorr_input))?, - SelectedInput::DcOffsetQ => self.handle_events(Some(&mut qcorr_input))?, - SelectedInput::Phase => self.handle_events(Some(&mut phase_input))?, - SelectedInput::Gain => self.handle_events(Some(&mut gain_input))?, + SelectedInput::Frequency => self.handle_events(Some(items[0].as_mut()))?, + SelectedInput::DcOffsetI => self.handle_events(Some(items[1].as_mut()))?, + SelectedInput::DcOffsetQ => self.handle_events(Some(items[2].as_mut()))?, + SelectedInput::Phase => self.handle_events(Some(items[3].as_mut()))?, + SelectedInput::Gain => self.handle_events(Some(items[4].as_mut()))?, } } else { self.handle_events(None)?; }; - // let test: Option> = match self.selected_input { - // SelectedInput::DcOffsetI => Some(Box::new(frequency_input)), - // SelectedInput::DcOffsetQ => Some(Box::new(gain_input)), - // SelectedInput::Frequency => todo!(), - // SelectedInput::Phase => todo!(), - // SelectedInput::Gain => todo!(), - // }; - - // self.handle_events(selected_text_field)?; } Ok(()) } @@ -328,7 +380,7 @@ impl App { } /// updates the application's state based on user input - fn handle_events(&mut self, idk: Option<&mut dyn NumericInputHandle>) -> io::Result<()> { + fn handle_events(&mut self, idk: Option<&mut dyn NumericInputWidget>) -> io::Result<()> { if let Some(idk2) = idk { match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => self.exit(), From 5cc0ff354002214ef3f58b42e038ae3cc6cdc6c8 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 20:44:05 -0500 Subject: [PATCH 14/32] Ability to actually set the correction values. --- examples/siggen.rs | 165 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 40 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 45fa4fb..561cba3 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,4 +1,4 @@ -use std::{any::Any, error::Error, io}; +use std::{any::Any, error::Error, io, rc::Rc, str::FromStr}; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ @@ -7,7 +7,7 @@ use ratatui::{ style::Stylize, symbols::border, text::{Line, Text}, - widgets::{Block, Borders, Paragraph, Widget}, + widgets::{Block, Borders, List, Paragraph, Widget}, DefaultTerminal, Frame, }; @@ -227,6 +227,22 @@ impl Widget for Box { } } +trait BoxWidget { + fn render_box(self: Box, area: Rect, buf: &mut Buffer); +} + +impl BoxWidget for W { + fn render_box(self: Box, area: Rect, buf: &mut Buffer) { + (*self).render(area, buf) + } +} + +// impl Widget for Box { +// fn render(self, area: Rect, buf: &mut Buffer) { +// self.render_box(area, buf) +// } +// } + impl App { fn new(dev: BladeRF) -> App { let channel = bladerf::Channel::Tx1; @@ -241,51 +257,67 @@ impl App { /// runs the application's main loop until the user quits pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { - let frequency_input = NumericInput::new(self.get_freq().to_string(), validate_frequency); + let mut frequency_input = + NumericInput::new(self.get_freq().to_string(), validate_frequency); - let icorr_input = NumericInput::new(self.get_icorr().to_string(), |x| { + let mut icorr_input = NumericInput::new(self.get_icorr().to_string(), |x| { validate_correction::(x) }); - let qcorr_input = NumericInput::new(self.get_qcorr().to_string(), |x| { + let mut qcorr_input = NumericInput::new(self.get_qcorr().to_string(), |x| { validate_correction::(x) }); - let phase_input = NumericInput::new(self.get_phase().to_string(), |x| { + let mut phase_input = NumericInput::new(self.get_phase().to_string(), |x| { validate_correction::(x) }); - let gain_input = NumericInput::new(self.get_gain().to_string(), |x| { + let mut gain_input = NumericInput::new(self.get_gain().to_string(), |x| { validate_correction::(x) }); - let mut items: Vec> = vec![ - Box::new(frequency_input), - Box::new(icorr_input), - Box::new(qcorr_input), - Box::new(phase_input), - Box::new(gain_input), - ]; - while !self.exit { let debug_test = Text::from(format!("Sel: {:?}", self.selected_input)); - for item in items.iter_mut() { - item.unset_focus(); - } + frequency_input.unset_focus(); + icorr_input.unset_focus(); + qcorr_input.unset_focus(); + phase_input.unset_focus(); + gain_input.unset_focus(); + + let current_setpoint = vec![ + Paragraph::new(self.get_freq().to_string()) + .block(Block::new().borders(Borders::ALL).title("Set Frequency")), + Paragraph::new(self.get_icorr().to_string()) + .block(Block::new().borders(Borders::ALL).title("Set ICorr")), + Paragraph::new(self.get_qcorr().to_string()) + .block(Block::new().borders(Borders::ALL).title("Set QCorr")), + Paragraph::new(self.get_phase().to_string()) + .block(Block::new().borders(Borders::ALL).title("Set Phase")), + Paragraph::new(self.get_gain().to_string()) + .block(Block::new().borders(Borders::ALL).title("Set Gain")), + ]; if self.focused { match self.selected_input { - SelectedInput::Frequency => items[0].set_focus(), - SelectedInput::DcOffsetI => items[1].set_focus(), - SelectedInput::DcOffsetQ => items[2].set_focus(), - SelectedInput::Phase => items[3].set_focus(), - SelectedInput::Gain => items[4].set_focus(), + SelectedInput::Frequency => frequency_input.set_focus(), + SelectedInput::DcOffsetI => icorr_input.set_focus(), + SelectedInput::DcOffsetQ => qcorr_input.set_focus(), + SelectedInput::Phase => phase_input.set_focus(), + SelectedInput::Gain => gain_input.set_focus(), } } + let selected_idx = match self.selected_input { + SelectedInput::Frequency => 0_usize, + SelectedInput::DcOffsetI => 1, + SelectedInput::DcOffsetQ => 2, + SelectedInput::Phase => 3, + SelectedInput::Gain => 4, + }; + terminal.draw(|frame| { - let layout = Layout::default() + let row_layout = Layout::default() .direction(Direction::Vertical) .constraints(vec![ Constraint::Length(3), @@ -297,24 +329,69 @@ impl App { ]) .split(frame.area()); - for (num_input, layout) in items.iter().zip(layout.iter()) { - let x = num_input.as_ref(); - frame.render_widget(x, *layout); + let column_layout: Vec> = row_layout + .iter() + .map(|layout| { + Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Length(1), + Constraint::Percentage(50), + Constraint::Percentage(50), + ]) + .split(*layout) + }) + .collect(); + + frame.render_widget(&frequency_input, column_layout[0][1]); + frame.render_widget(&icorr_input, column_layout[1][1]); + frame.render_widget(&qcorr_input, column_layout[2][1]); + frame.render_widget(&phase_input, column_layout[3][1]); + frame.render_widget(&gain_input, column_layout[4][1]); + + for (idx, (layout, setpoint)) in + column_layout.iter().zip(current_setpoint).enumerate() + { + if idx == selected_idx { + frame.render_widget(Text::from(vec![" ".into(), ">".into()]), layout[0]); + } else { + frame.render_widget(" ", layout[0]); + } + frame.render_widget(setpoint, layout[2]); } - frame.render_widget(debug_test, layout[5]); + + frame.render_widget(debug_test, row_layout[5]); })?; - if self.focused { + let update_corrs = if self.focused { match self.selected_input { - SelectedInput::Frequency => self.handle_events(Some(items[0].as_mut()))?, - SelectedInput::DcOffsetI => self.handle_events(Some(items[1].as_mut()))?, - SelectedInput::DcOffsetQ => self.handle_events(Some(items[2].as_mut()))?, - SelectedInput::Phase => self.handle_events(Some(items[3].as_mut()))?, - SelectedInput::Gain => self.handle_events(Some(items[4].as_mut()))?, + SelectedInput::Frequency => self.handle_events(Some(&mut frequency_input))?, + SelectedInput::DcOffsetI => self.handle_events(Some(&mut icorr_input))?, + SelectedInput::DcOffsetQ => self.handle_events(Some(&mut qcorr_input))?, + SelectedInput::Phase => self.handle_events(Some(&mut phase_input))?, + SelectedInput::Gain => self.handle_events(Some(&mut gain_input))?, } } else { - self.handle_events(None)?; + self.handle_events::(None)? }; + + if update_corrs { + if let Ok(val) = (frequency_input.validation_fn)(frequency_input.value().as_str()) { + self.set_freq(val); + } + if let Ok(val) = (icorr_input.validation_fn)(icorr_input.value().as_str()) { + self.set_corr(val); + } + if let Ok(val) = (qcorr_input.validation_fn)(qcorr_input.value().as_str()) { + self.set_corr(val); + } + if let Ok(val) = (phase_input.validation_fn)(phase_input.value().as_str()) { + self.set_corr(val); + } + if let Ok(val) = (gain_input.validation_fn)(gain_input.value().as_str()) { + self.set_corr(val); + } + } } Ok(()) } @@ -380,15 +457,23 @@ impl App { } /// updates the application's state based on user input - fn handle_events(&mut self, idk: Option<&mut dyn NumericInputWidget>) -> io::Result<()> { + fn handle_events( + &mut self, + idk: Option<&mut NumericInput<'_, T, String>>, + ) -> io::Result { + let mut need_to_update = false; if let Some(idk2) = idk { match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => self.exit(), - Input { key: Key::Up, .. } => self.selected_up(), - Input { key: Key::Down, .. } => self.selected_down(), + // Input { key: Key::Up, .. } => self.selected_up(), + // Input { key: Key::Down, .. } => self.selected_down(), Input { key: Key::Enter, .. - } => self.unset_focus(), + } => { + need_to_update = true; + self.unset_focus(); + } + input => idk2.handle_input(input), } } else { @@ -403,7 +488,7 @@ impl App { } } - Ok(()) + Ok(need_to_update) } fn handle_key_event(&mut self, key_event: KeyEvent) { From dfa8641a0cfa3928c6cc91e2a2ed0c71bd6b324e Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 21:20:51 -0500 Subject: [PATCH 15/32] Testing out add trait for corrections. --- Cargo.lock | 309 +++++++++++++++++++++++++++++++++++++++- Cargo.toml | 8 +- src/types/correction.rs | 138 +++++++++++++++++- 3 files changed, 448 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19aa7b8..cedf1fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.93" @@ -60,12 +66,15 @@ dependencies = [ "enum-map", "libbladerf-sys", "log", + "num", "num-complex", "once_cell", "parking_lot", + "ratatui", "strum", "tempfile", "thiserror", + "tui-textarea", ] [[package]] @@ -80,6 +89,21 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -106,6 +130,20 @@ dependencies = [ "libloading", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -146,6 +184,47 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "either" version = "1.13.0" @@ -172,6 +251,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -188,12 +273,35 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.5.0" @@ -206,6 +314,32 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "itertools" version = "0.13.0" @@ -215,6 +349,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "libbladerf-sys" version = "0.1.0" @@ -262,6 +402,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -297,6 +446,30 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -306,6 +479,37 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -344,6 +548,22 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.22" @@ -356,9 +576,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -372,6 +592,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -435,6 +676,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -483,6 +730,18 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -549,12 +808,52 @@ dependencies = [ "syn", ] +[[package]] +name = "tui-textarea" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" +dependencies = [ + "crossterm", + "ratatui", + "unicode-width 0.2.0", +] + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -664,3 +963,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index f6b8efd..bbf22f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "bladerf" repository = "https://github.com/MerchGuardian/seify-bladerf" -authors = ["Troy Neubauer ", "Ryan Kurte "] +authors = [ + "Troy Neubauer ", + "Ryan Kurte ", +] description = "WIP Rust wrapper for libbladerf" readme = "README.md" license = "MIT" @@ -20,10 +23,13 @@ num-complex = "0.4.6" parking_lot = "0.12.3" strum = { version = "0.26.3", features = ["derive", "strum_macros"] } thiserror = "1.0.64" +num = "0.4.3" [dev-dependencies] anyhow = "1" crossbeam-channel = "0.5" +ratatui = "0.29.0" +tui-textarea = "0.7.0" crossterm = "0.28" once_cell = "1.20" tempfile = "3.13" diff --git a/src/types/correction.rs b/src/types/correction.rs index 2d8a8c7..a29bfbe 100644 --- a/src/types/correction.rs +++ b/src/types/correction.rs @@ -1,3 +1,6 @@ +use std::ops::Add; + +use num::{traits::SaturatingAdd, One}; use strum::FromRepr; use crate::{sys::*, Error, Result}; @@ -19,6 +22,8 @@ pub trait CorrectionValue: Sized { const TYPE: Correction; fn new(value: i16) -> Option; fn value(&self) -> i16; + /// # Safety + /// Make sure the value is within the range for the given correction unsafe fn new_unchecked(val: i16) -> Self; } @@ -27,8 +32,11 @@ pub struct CorrectionDcOffsetI(pub i16); // Implement constructors with validation for each struct impl CorrectionDcOffsetI { + const MAX: i16 = 2048; + const MIN: i16 = -2048; + pub fn new(value: i16) -> Option { - if (-2048..=2048).contains(&value) { + if (Self::MIN..=Self::MAX).contains(&value) { Some(Self(value)) } else { None @@ -56,12 +64,40 @@ impl CorrectionValue for CorrectionDcOffsetI { } } +impl Add for CorrectionDcOffsetI { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => { + let wrapped_offet = new - Self::MAX; + unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } + } + } + } +} + +impl SaturatingAdd for CorrectionDcOffsetI { + fn saturating_add(&self, rhs: &Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => unsafe { Self::new_unchecked(Self::MAX) }, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct CorrectionDcOffsetQ(pub i16); impl CorrectionDcOffsetQ { + const MAX: i16 = 2048; + const MIN: i16 = -2048; + pub fn new(value: i16) -> Option { - if (-2048..=2048).contains(&value) { + if (Self::MIN..=Self::MAX).contains(&value) { Some(Self(value)) } else { None @@ -89,12 +125,40 @@ impl CorrectionValue for CorrectionDcOffsetQ { } } +impl Add for CorrectionDcOffsetQ { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => { + let wrapped_offet = new - Self::MAX; + unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } + } + } + } +} + +impl SaturatingAdd for CorrectionDcOffsetQ { + fn saturating_add(&self, rhs: &Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => unsafe { Self::new_unchecked(Self::MAX) }, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct CorrectionPhase(pub i16); impl CorrectionPhase { + const MAX: i16 = 4096; + const MIN: i16 = -4096; + pub fn new(value: i16) -> Option { - if (-4096..=4096).contains(&value) { + if (Self::MIN..=Self::MAX).contains(&value) { Some(Self(value)) } else { None @@ -122,12 +186,40 @@ impl CorrectionValue for CorrectionPhase { } } +impl Add for CorrectionPhase { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => { + let wrapped_offet = new - Self::MAX; + unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } + } + } + } +} + +impl SaturatingAdd for CorrectionPhase { + fn saturating_add(&self, rhs: &Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => unsafe { Self::new_unchecked(Self::MAX) }, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct CorrectionGain(pub i16); impl CorrectionGain { + const MAX: i16 = 4096; + const MIN: i16 = -4096; + pub fn new(value: i16) -> Option { - if (-4096..=4096).contains(&value) { + if (Self::MIN..=Self::MAX).contains(&value) { Some(Self(value)) } else { None @@ -155,6 +247,31 @@ impl CorrectionValue for CorrectionGain { } } +impl Add for CorrectionGain { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => { + let wrapped_offet = new - Self::MAX; + unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } + } + } + } +} + +impl SaturatingAdd for CorrectionGain { + fn saturating_add(&self, rhs: &Self) -> Self { + let new = self.0 + rhs.0; + match Self::new(new) { + Some(val) => val, + None => unsafe { Self::new_unchecked(Self::MAX) }, + } + } +} + /// Correction parameter selection #[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] #[repr(i32)] @@ -173,3 +290,16 @@ impl TryFrom for Correction { .ok_or_else(|| Error::msg(format!("Invalid Correction value: {value}"))) } } + +#[cfg(test)] +mod tests { + #[test] + fn corrections_add_saturating() { + todo!() + } + + #[test] + fn corrections_add_wrapping() { + todo!() + } +} From 8ca486b0e0d1744b28a6e725e500e38639bc6d9d Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 21:23:43 -0500 Subject: [PATCH 16/32] Messed this up in a merge somehow --- Cargo.toml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91475e8..bbf22f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,6 @@ authors = [ "Troy Neubauer ", "Ryan Kurte ", ] -authors = [ - "Troy Neubauer ", - "Ryan Kurte ", -] description = "WIP Rust wrapper for libbladerf" readme = "README.md" license = "MIT" @@ -27,12 +23,13 @@ num-complex = "0.4.6" parking_lot = "0.12.3" strum = { version = "0.26.3", features = ["derive", "strum_macros"] } thiserror = "1.0.64" -tui-textarea = "0.7.0" +num = "0.4.3" [dev-dependencies] anyhow = "1" crossbeam-channel = "0.5" ratatui = "0.29.0" +tui-textarea = "0.7.0" crossterm = "0.28" once_cell = "1.20" tempfile = "3.13" From e7c21e928aff96ae15f1ffb38987e4f8fd679c1e Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 23:39:45 -0500 Subject: [PATCH 17/32] A more or less functional ui that does the vary basics of what I want. --- examples/siggen.rs | 163 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 30 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 561cba3..6f9812e 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,6 +1,7 @@ use std::{any::Any, error::Error, io, rc::Rc, str::FromStr}; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; +use num::{traits::SaturatingAdd, One}; use ratatui::{ buffer::Buffer, layout::Rect, @@ -76,12 +77,12 @@ fn validate_correction(val: &str) -> Result { } /// A custom numeric input widget with validation -pub struct NumericInput<'a, T, E> { +pub struct NumericInput<'a, T: SaturatingAdd, E> { textarea: TextArea<'a>, validation_fn: IntValidationFunction, // Validation logic } -impl<'a, T> NumericInput<'a, T, String> { +impl<'a, T: SaturatingAdd> NumericInput<'a, T, String> { /// Creates a new `NumericInput` with the provided initial value and validation function. pub fn new(initial_value: String, validation_fn: F) -> Self where @@ -142,6 +143,10 @@ impl<'a, T> NumericInput<'a, T, String> { pub fn value(&self) -> String { self.textarea.lines().join("") } + + pub fn inner_val(&self) -> Option { + (self.validation_fn)(self.value().as_str()).ok() + } } trait NumericInputHandle { @@ -151,7 +156,7 @@ trait NumericInputHandle { fn num_render(&self, area: Rect, buf: &mut Buffer); } -impl<'a, T> NumericInputHandle for &mut NumericInput<'a, T, String> { +impl<'a, T: SaturatingAdd> NumericInputHandle for &mut NumericInput<'a, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } @@ -169,7 +174,7 @@ impl<'a, T> NumericInputHandle for &mut NumericInput<'a, T, String> { } } -impl<'a, T> NumericInputHandle for NumericInput<'a, T, String> { +impl<'a, T: SaturatingAdd> NumericInputHandle for NumericInput<'a, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } @@ -187,7 +192,7 @@ impl<'a, T> NumericInputHandle for NumericInput<'a, T, String> { } } -impl<'a, T, E> Widget for &NumericInput<'a, T, E> { +impl<'a, T: SaturatingAdd, E> Widget for &NumericInput<'a, T, E> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -196,7 +201,7 @@ impl<'a, T, E> Widget for &NumericInput<'a, T, E> { } } -impl<'a, T, E> Widget for NumericInput<'a, T, E> { +impl<'a, T: SaturatingAdd, E> Widget for NumericInput<'a, T, E> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -207,7 +212,7 @@ impl<'a, T, E> Widget for NumericInput<'a, T, E> { trait NumericInputWidget: NumericInputHandle + Widget {} -impl<'a, T> NumericInputWidget for NumericInput<'a, T, String> {} +impl<'a, T: SaturatingAdd> NumericInputWidget for NumericInput<'a, T, String> {} impl Widget for &dyn NumericInputWidget { fn render(self, area: Rect, buf: &mut Buffer) @@ -227,22 +232,14 @@ impl Widget for Box { } } -trait BoxWidget { - fn render_box(self: Box, area: Rect, buf: &mut Buffer); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MyAppAction { + None, + Update, + Increment, + Decrement, } -impl BoxWidget for W { - fn render_box(self: Box, area: Rect, buf: &mut Buffer) { - (*self).render(area, buf) - } -} - -// impl Widget for Box { -// fn render(self, area: Rect, buf: &mut Buffer) { -// self.render_box(area, buf) -// } -// } - impl App { fn new(dev: BladeRF) -> App { let channel = bladerf::Channel::Tx1; @@ -363,7 +360,7 @@ impl App { frame.render_widget(debug_test, row_layout[5]); })?; - let update_corrs = if self.focused { + let action = if self.focused { match self.selected_input { SelectedInput::Frequency => self.handle_events(Some(&mut frequency_input))?, SelectedInput::DcOffsetI => self.handle_events(Some(&mut icorr_input))?, @@ -375,7 +372,106 @@ impl App { self.handle_events::(None)? }; - if update_corrs { + if action == MyAppAction::Increment { + match self.selected_input { + SelectedInput::Frequency => { + if let Some(val) = frequency_input.inner_val() { + frequency_input + .textarea + .set_yank_text((val + 1).to_string()); + frequency_input.textarea.select_all(); + frequency_input.textarea.paste(); + } + } + SelectedInput::DcOffsetI => { + if let Some(val) = icorr_input.inner_val() { + icorr_input + .textarea + .set_yank_text((val.into_inner() + 1).to_string()); + icorr_input.textarea.select_all(); + icorr_input.textarea.paste(); + } + } + SelectedInput::DcOffsetQ => { + if let Some(val) = qcorr_input.inner_val() { + qcorr_input + .textarea + .set_yank_text((val.into_inner() + 1).to_string()); + qcorr_input.textarea.select_all(); + qcorr_input.textarea.paste(); + } + } + SelectedInput::Phase => { + if let Some(val) = phase_input.inner_val() { + phase_input + .textarea + .set_yank_text((val.into_inner() + 1).to_string()); + phase_input.textarea.select_all(); + phase_input.textarea.paste(); + } + } + SelectedInput::Gain => { + if let Some(val) = gain_input.inner_val() { + gain_input + .textarea + .set_yank_text((val.into_inner() + 1).to_string()); + gain_input.textarea.select_all(); + gain_input.textarea.paste(); + } + } + } + } + if action == MyAppAction::Decrement { + match self.selected_input { + SelectedInput::Frequency => { + if let Some(val) = frequency_input.inner_val() { + frequency_input + .textarea + .set_yank_text((val - 1).to_string()); + frequency_input.textarea.select_all(); + frequency_input.textarea.paste(); + } + } + SelectedInput::DcOffsetI => { + if let Some(val) = icorr_input.inner_val() { + icorr_input + .textarea + .set_yank_text((val.into_inner() - 1).to_string()); + icorr_input.textarea.select_all(); + icorr_input.textarea.paste(); + } + } + SelectedInput::DcOffsetQ => { + if let Some(val) = qcorr_input.inner_val() { + qcorr_input + .textarea + .set_yank_text((val.into_inner() - 1).to_string()); + qcorr_input.textarea.select_all(); + qcorr_input.textarea.paste(); + } + } + SelectedInput::Phase => { + if let Some(val) = phase_input.inner_val() { + phase_input + .textarea + .set_yank_text((val.into_inner() - 1).to_string()); + phase_input.textarea.select_all(); + phase_input.textarea.paste(); + } + } + SelectedInput::Gain => { + if let Some(val) = gain_input.inner_val() { + gain_input + .textarea + .set_yank_text((val.into_inner() - 1).to_string()); + gain_input.textarea.select_all(); + gain_input.textarea.paste(); + } + } + } + } + + if action != MyAppAction::None { if let Ok(val) = (frequency_input.validation_fn)(frequency_input.value().as_str()) { self.set_freq(val); } @@ -457,20 +553,19 @@ impl App { } /// updates the application's state based on user input - fn handle_events( + fn handle_events( &mut self, idk: Option<&mut NumericInput<'_, T, String>>, - ) -> io::Result { - let mut need_to_update = false; + ) -> io::Result { + let mut app_action = MyAppAction::None; if let Some(idk2) = idk { match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => self.exit(), - // Input { key: Key::Up, .. } => self.selected_up(), - // Input { key: Key::Down, .. } => self.selected_down(), + Input { key: Key::Enter, .. } => { - need_to_update = true; + app_action = MyAppAction::Update; self.unset_focus(); } @@ -481,6 +576,14 @@ impl App { Input { key: Key::Esc, .. } => self.exit(), Input { key: Key::Up, .. } => self.selected_up(), Input { key: Key::Down, .. } => self.selected_down(), + Input { key: Key::Left, .. } => { + app_action = MyAppAction::Decrement; + } + Input { + key: Key::Right, .. + } => { + app_action = MyAppAction::Increment; + } Input { key: Key::Enter, .. } => self.set_focus(), @@ -488,7 +591,7 @@ impl App { } } - Ok(need_to_update) + Ok(app_action) } fn handle_key_event(&mut self, key_event: KeyEvent) { From bacd33fb93a8915a7406db3775082ec6982ffcf9 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 14 Dec 2024 23:49:47 -0500 Subject: [PATCH 18/32] instructions --- examples/siggen.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 6f9812e..332183b 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -274,7 +274,13 @@ impl App { }); while !self.exit { - let debug_test = Text::from(format!("Sel: {:?}", self.selected_input)); + // let debug_test = Text::from(format!("Sel: {:?}", self.selected_input)); + let instructions = Text::from(vec![ + "Use up down arrow keys to select field".into(), + "Hit enter to edit a field and ender again to exit, upon exit the value will be updated".into(), + "You can use the left right arroy keys to move between values".into(), + "Esc to quit (I don't know how to handle SIGINT".into() + ]); frequency_input.unset_focus(); icorr_input.unset_focus(); @@ -322,7 +328,7 @@ impl App { Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Length(3), + Constraint::Length(5), ]) .split(frame.area()); @@ -357,7 +363,7 @@ impl App { frame.render_widget(setpoint, layout[2]); } - frame.render_widget(debug_test, row_layout[5]); + frame.render_widget(instructions, row_layout[5]); })?; let action = if self.focused { From 8d3c0d8c58a6255582d31cc86d7869c8a2d788a0 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sun, 15 Dec 2024 00:15:25 -0500 Subject: [PATCH 19/32] Minor cleanup of warnings --- examples/siggen.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 332183b..b3a4335 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,21 +1,19 @@ -use std::{any::Any, error::Error, io, rc::Rc, str::FromStr}; +use std::{io, rc::Rc}; -use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; -use num::{traits::SaturatingAdd, One}; +use num::traits::SaturatingAdd; use ratatui::{ buffer::Buffer, layout::Rect, style::Stylize, - symbols::border, text::{Line, Text}, - widgets::{Block, Borders, List, Paragraph, Widget}, - DefaultTerminal, Frame, + widgets::{Block, Borders, Paragraph, Widget}, + DefaultTerminal, }; use ratatui::prelude::*; use bladerf::{ - BladeRF, Correction, CorrectionDcOffsetI, CorrectionDcOffsetQ, CorrectionGain, CorrectionPhase, + BladeRF, CorrectionDcOffsetI, CorrectionDcOffsetQ, CorrectionGain, CorrectionPhase, CorrectionValue, }; use tui_textarea::{Input, Key, TextArea}; @@ -567,7 +565,6 @@ impl App { if let Some(idk2) = idk { match crossterm::event::read()?.into() { Input { key: Key::Esc, .. } => self.exit(), - Input { key: Key::Enter, .. } => { @@ -599,15 +596,6 @@ impl App { Ok(app_action) } - - fn handle_key_event(&mut self, key_event: KeyEvent) { - match key_event.code { - KeyCode::Char('q') => self.exit(), - KeyCode::Up => self.selected_up(), - KeyCode::Down => self.selected_down(), - _ => {} - } - } } impl Widget for &App { From c5757bd4986ebbfe1caa14f5a49707ac46e380fd Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Tue, 17 Dec 2024 21:46:56 -0500 Subject: [PATCH 20/32] Need to change channel because of channel fix --- examples/siggen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index b3a4335..be2920a 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -240,7 +240,7 @@ enum MyAppAction { impl App { fn new(dev: BladeRF) -> App { - let channel = bladerf::Channel::Tx1; + let channel = bladerf::Channel::Tx0; App { channel, device: dev, From 29d65df1d5f4fe3a717c3ebe9ee26ecd795da5a1 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Tue, 17 Dec 2024 21:48:53 -0500 Subject: [PATCH 21/32] works with reference --- examples/siggen.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index be2920a..ca91897 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,6 +1,7 @@ -use std::{io, rc::Rc}; +use std::{io, rc::Rc, sync::Arc, thread}; use num::traits::SaturatingAdd; +use num_complex::Complex32; use ratatui::{ buffer::Buffer, layout::Rect, @@ -48,9 +49,9 @@ impl SelectedInput { } } -pub struct App { +pub struct App<'a> { channel: bladerf::Channel, - device: BladeRF, + device: &'a BladeRF, selected_input: SelectedInput, focused: bool, exit: bool, @@ -238,8 +239,8 @@ enum MyAppAction { Decrement, } -impl App { - fn new(dev: BladeRF) -> App { +impl<'a> App<'a> { + fn new(dev: &'a BladeRF) -> App<'a> { let channel = bladerf::Channel::Tx0; App { channel, @@ -598,7 +599,7 @@ impl App { } } -impl Widget for &App { +impl<'a> Widget for &App<'a> { fn render(self, area: Rect, buf: &mut Buffer) { let title = Line::from(" BladeRF SigGen ".bold()); @@ -610,8 +611,14 @@ fn main() -> io::Result<()> { let device = BladeRF::open_first().map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; + let arc_dev = Arc::new(device); + let thread_arc_dev = arc_dev.clone(); + thread::spawn(move || { + thread_arc_dev.set_gain(bladerf::Channel::Tx0, 0).unwrap(); + }); + let mut terminal = ratatui::init(); - let app_result = App::new(device).run(&mut terminal); + let app_result = App::new(&arc_dev).run(&mut terminal); ratatui::restore(); app_result } From e17d234dd30433cc6c6c7020dd5df7b34c2e0719 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 8 Feb 2025 11:49:26 -0500 Subject: [PATCH 22/32] Consolidated some implementation --- src/types/correction.rs | 109 +++++++++------------------------------- 1 file changed, 23 insertions(+), 86 deletions(-) diff --git a/src/types/correction.rs b/src/types/correction.rs index a29bfbe..6c55610 100644 --- a/src/types/correction.rs +++ b/src/types/correction.rs @@ -17,46 +17,37 @@ use crate::{sys::*, Error, Result}; /// | DcOffsetQ | Adjusts the quadrature DC offset. Valid values are [-2048, 2048], which are scaled to the available control bits. | /// | Phase | Adjusts phase correction of [-10, 10] degrees, via a provided count value of [-4096, 4096]. | /// | Gain | Adjusts gain correction value in [-1.0, 1.0], via provided values in the range of [-4096, 4096]. | - pub trait CorrectionValue: Sized { const TYPE: Correction; - fn new(value: i16) -> Option; - fn value(&self) -> i16; - /// # Safety - /// Make sure the value is within the range for the given correction - unsafe fn new_unchecked(val: i16) -> Self; -} -#[derive(Debug, Clone, Copy)] -pub struct CorrectionDcOffsetI(pub i16); - -// Implement constructors with validation for each struct -impl CorrectionDcOffsetI { - const MAX: i16 = 2048; - const MIN: i16 = -2048; + const MAX: i16; + const MIN: i16; - pub fn new(value: i16) -> Option { + fn new(value: i16) -> Option { if (Self::MIN..=Self::MAX).contains(&value) { - Some(Self(value)) + Some(unsafe { Self::new_unchecked(value) }) } else { None } } - pub fn into_inner(self) -> i16 { - self.0 - } + fn value(&self) -> i16; + /// # Safety + /// Make sure the value is within the range for the given correction + unsafe fn new_unchecked(val: i16) -> Self; } +#[derive(Debug, Clone, Copy)] +pub struct CorrectionDcOffsetI(pub i16); + impl CorrectionValue for CorrectionDcOffsetI { const TYPE: Correction = Correction::DcOffsetI; - fn new(value: i16) -> Option { - Self::new(value) - } + const MAX: i16 = 2048; + const MIN: i16 = -2048; fn value(&self) -> i16 { - self.into_inner() + self.0 } unsafe fn new_unchecked(value: i16) -> Self { @@ -92,32 +83,14 @@ impl SaturatingAdd for CorrectionDcOffsetI { #[derive(Debug, Clone, Copy)] pub struct CorrectionDcOffsetQ(pub i16); -impl CorrectionDcOffsetQ { - const MAX: i16 = 2048; - const MIN: i16 = -2048; - - pub fn new(value: i16) -> Option { - if (Self::MIN..=Self::MAX).contains(&value) { - Some(Self(value)) - } else { - None - } - } - - pub fn into_inner(self) -> i16 { - self.0 - } -} - impl CorrectionValue for CorrectionDcOffsetQ { const TYPE: Correction = Correction::DcOffsetQ; - fn new(value: i16) -> Option { - Self::new(value) - } + const MAX: i16 = 2048; + const MIN: i16 = -2048; fn value(&self) -> i16 { - self.into_inner() + self.0 } unsafe fn new_unchecked(value: i16) -> Self { @@ -153,32 +126,14 @@ impl SaturatingAdd for CorrectionDcOffsetQ { #[derive(Debug, Clone, Copy)] pub struct CorrectionPhase(pub i16); -impl CorrectionPhase { - const MAX: i16 = 4096; - const MIN: i16 = -4096; - - pub fn new(value: i16) -> Option { - if (Self::MIN..=Self::MAX).contains(&value) { - Some(Self(value)) - } else { - None - } - } - - pub fn into_inner(self) -> i16 { - self.0 - } -} - impl CorrectionValue for CorrectionPhase { const TYPE: Correction = Correction::Phase; - fn new(value: i16) -> Option { - Self::new(value) - } + const MAX: i16 = 4096; + const MIN: i16 = -4096; fn value(&self) -> i16 { - self.into_inner() + self.0 } unsafe fn new_unchecked(value: i16) -> Self { @@ -214,32 +169,14 @@ impl SaturatingAdd for CorrectionPhase { #[derive(Debug, Clone, Copy)] pub struct CorrectionGain(pub i16); -impl CorrectionGain { - const MAX: i16 = 4096; - const MIN: i16 = -4096; - - pub fn new(value: i16) -> Option { - if (Self::MIN..=Self::MAX).contains(&value) { - Some(Self(value)) - } else { - None - } - } - - pub fn into_inner(self) -> i16 { - self.0 - } -} - impl CorrectionValue for CorrectionGain { const TYPE: Correction = Correction::Gain; - fn new(value: i16) -> Option { - Self::new(value) - } + const MAX: i16 = 4096; + const MIN: i16 = -4096; fn value(&self) -> i16 { - self.into_inner() + self.0 } unsafe fn new_unchecked(value: i16) -> Self { From 352f37986400fd07262174fa474f00c95e00faa3 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Sat, 8 Feb 2025 12:45:15 -0500 Subject: [PATCH 23/32] Test for some initial correction arithmetic. --- src/types/correction.rs | 45 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/types/correction.rs b/src/types/correction.rs index 6c55610..cf1cde6 100644 --- a/src/types/correction.rs +++ b/src/types/correction.rs @@ -1,6 +1,6 @@ use std::ops::Add; -use num::{traits::SaturatingAdd, One}; +use num::traits::SaturatingAdd; use strum::FromRepr; use crate::{sys::*, Error, Result}; @@ -32,6 +32,7 @@ pub trait CorrectionValue: Sized { } fn value(&self) -> i16; + /// # Safety /// Make sure the value is within the range for the given correction unsafe fn new_unchecked(val: i16) -> Self; @@ -230,13 +231,51 @@ impl TryFrom for Correction { #[cfg(test)] mod tests { + use super::*; + #[test] fn corrections_add_saturating() { - todo!() + let correction_a = CorrectionDcOffsetI::new(CorrectionDcOffsetI::MAX - 8).unwrap(); + let correction_b = CorrectionDcOffsetI::new(50).unwrap(); + let new_correction = correction_a.saturating_add(&correction_b); + assert_eq!(new_correction.value(), CorrectionDcOffsetI::MAX); + + let correction_a = CorrectionDcOffsetQ::new(CorrectionDcOffsetQ::MAX - 8).unwrap(); + let correction_b = CorrectionDcOffsetQ::new(50).unwrap(); + let new_correction = correction_a.saturating_add(&correction_b); + assert_eq!(new_correction.value(), CorrectionDcOffsetQ::MAX); + + let correction_a = CorrectionGain::new(CorrectionGain::MAX - 8).unwrap(); + let correction_b = CorrectionGain::new(50).unwrap(); + let new_correction = correction_a.saturating_add(&correction_b); + assert_eq!(new_correction.value(), CorrectionGain::MAX); + + let correction_a = CorrectionPhase::new(CorrectionPhase::MAX - 8).unwrap(); + let correction_b = CorrectionPhase::new(50).unwrap(); + let new_correction = correction_a.saturating_add(&correction_b); + assert_eq!(new_correction.value(), CorrectionPhase::MAX); } #[test] fn corrections_add_wrapping() { - todo!() + let correction_a = CorrectionDcOffsetI::new(CorrectionDcOffsetI::MAX - 8).unwrap(); + let correction_b = CorrectionDcOffsetI::new(50).unwrap(); + let new_correction = correction_a + correction_b; + assert_eq!(new_correction.value(), CorrectionDcOffsetI::MIN + 50 - 8); + + let correction_a = CorrectionDcOffsetQ::new(CorrectionDcOffsetQ::MAX - 8).unwrap(); + let correction_b = CorrectionDcOffsetQ::new(50).unwrap(); + let new_correction = correction_a + correction_b; + assert_eq!(new_correction.value(), CorrectionDcOffsetQ::MIN + 50 - 8); + + let correction_a = CorrectionGain::new(CorrectionGain::MAX - 6).unwrap(); + let correction_b = CorrectionGain::new(50).unwrap(); + let new_correction = correction_a + correction_b; + assert_eq!(new_correction.value(), CorrectionGain::MIN + 50 - 6); + + let correction_a = CorrectionPhase::new(CorrectionPhase::MAX - 6).unwrap(); + let correction_b = CorrectionPhase::new(50).unwrap(); + let new_correction = correction_a + correction_b; + assert_eq!(new_correction.value(), CorrectionPhase::MIN + 50 - 6); } } From 30cd036b7f9143b7ab7d5e178ed7fa0e25f6d1f8 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Tue, 11 Feb 2025 22:02:02 -0500 Subject: [PATCH 24/32] Removed Add and SaturatingAdd traits. Instead provice new_saturating() as well as max() and min() "constructor" methods. --- src/types/correction.rs | 169 +++++----------------------------------- 1 file changed, 18 insertions(+), 151 deletions(-) diff --git a/src/types/correction.rs b/src/types/correction.rs index cf1cde6..c37531e 100644 --- a/src/types/correction.rs +++ b/src/types/correction.rs @@ -31,6 +31,24 @@ pub trait CorrectionValue: Sized { } } + fn new_saturating(value: i16) -> Self { + if value > Self::MAX { + unsafe { Self::new_unchecked(Self::MAX) } + } else if value < Self::MIN { + unsafe { Self::new_unchecked(Self::MIN) } + } else { + unsafe { Self::new_unchecked(value) } + } + } + + fn max() -> Self { + unsafe { Self::new_unchecked(Self::MAX) } + } + + fn min() -> Self { + unsafe { Self::new_unchecked(Self::MIN) } + } + fn value(&self) -> i16; /// # Safety @@ -56,31 +74,6 @@ impl CorrectionValue for CorrectionDcOffsetI { } } -impl Add for CorrectionDcOffsetI { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => { - let wrapped_offet = new - Self::MAX; - unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } - } - } - } -} - -impl SaturatingAdd for CorrectionDcOffsetI { - fn saturating_add(&self, rhs: &Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => unsafe { Self::new_unchecked(Self::MAX) }, - } - } -} - #[derive(Debug, Clone, Copy)] pub struct CorrectionDcOffsetQ(pub i16); @@ -99,31 +92,6 @@ impl CorrectionValue for CorrectionDcOffsetQ { } } -impl Add for CorrectionDcOffsetQ { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => { - let wrapped_offet = new - Self::MAX; - unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } - } - } - } -} - -impl SaturatingAdd for CorrectionDcOffsetQ { - fn saturating_add(&self, rhs: &Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => unsafe { Self::new_unchecked(Self::MAX) }, - } - } -} - #[derive(Debug, Clone, Copy)] pub struct CorrectionPhase(pub i16); @@ -142,31 +110,6 @@ impl CorrectionValue for CorrectionPhase { } } -impl Add for CorrectionPhase { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => { - let wrapped_offet = new - Self::MAX; - unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } - } - } - } -} - -impl SaturatingAdd for CorrectionPhase { - fn saturating_add(&self, rhs: &Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => unsafe { Self::new_unchecked(Self::MAX) }, - } - } -} - #[derive(Debug, Clone, Copy)] pub struct CorrectionGain(pub i16); @@ -185,31 +128,6 @@ impl CorrectionValue for CorrectionGain { } } -impl Add for CorrectionGain { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => { - let wrapped_offet = new - Self::MAX; - unsafe { Self::new_unchecked(Self::MIN + wrapped_offet) } - } - } - } -} - -impl SaturatingAdd for CorrectionGain { - fn saturating_add(&self, rhs: &Self) -> Self { - let new = self.0 + rhs.0; - match Self::new(new) { - Some(val) => val, - None => unsafe { Self::new_unchecked(Self::MAX) }, - } - } -} - /// Correction parameter selection #[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq)] #[repr(i32)] @@ -228,54 +146,3 @@ impl TryFrom for Correction { .ok_or_else(|| Error::msg(format!("Invalid Correction value: {value}"))) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn corrections_add_saturating() { - let correction_a = CorrectionDcOffsetI::new(CorrectionDcOffsetI::MAX - 8).unwrap(); - let correction_b = CorrectionDcOffsetI::new(50).unwrap(); - let new_correction = correction_a.saturating_add(&correction_b); - assert_eq!(new_correction.value(), CorrectionDcOffsetI::MAX); - - let correction_a = CorrectionDcOffsetQ::new(CorrectionDcOffsetQ::MAX - 8).unwrap(); - let correction_b = CorrectionDcOffsetQ::new(50).unwrap(); - let new_correction = correction_a.saturating_add(&correction_b); - assert_eq!(new_correction.value(), CorrectionDcOffsetQ::MAX); - - let correction_a = CorrectionGain::new(CorrectionGain::MAX - 8).unwrap(); - let correction_b = CorrectionGain::new(50).unwrap(); - let new_correction = correction_a.saturating_add(&correction_b); - assert_eq!(new_correction.value(), CorrectionGain::MAX); - - let correction_a = CorrectionPhase::new(CorrectionPhase::MAX - 8).unwrap(); - let correction_b = CorrectionPhase::new(50).unwrap(); - let new_correction = correction_a.saturating_add(&correction_b); - assert_eq!(new_correction.value(), CorrectionPhase::MAX); - } - - #[test] - fn corrections_add_wrapping() { - let correction_a = CorrectionDcOffsetI::new(CorrectionDcOffsetI::MAX - 8).unwrap(); - let correction_b = CorrectionDcOffsetI::new(50).unwrap(); - let new_correction = correction_a + correction_b; - assert_eq!(new_correction.value(), CorrectionDcOffsetI::MIN + 50 - 8); - - let correction_a = CorrectionDcOffsetQ::new(CorrectionDcOffsetQ::MAX - 8).unwrap(); - let correction_b = CorrectionDcOffsetQ::new(50).unwrap(); - let new_correction = correction_a + correction_b; - assert_eq!(new_correction.value(), CorrectionDcOffsetQ::MIN + 50 - 8); - - let correction_a = CorrectionGain::new(CorrectionGain::MAX - 6).unwrap(); - let correction_b = CorrectionGain::new(50).unwrap(); - let new_correction = correction_a + correction_b; - assert_eq!(new_correction.value(), CorrectionGain::MIN + 50 - 6); - - let correction_a = CorrectionPhase::new(CorrectionPhase::MAX - 6).unwrap(); - let correction_b = CorrectionPhase::new(50).unwrap(); - let new_correction = correction_a + correction_b; - assert_eq!(new_correction.value(), CorrectionPhase::MIN + 50 - 6); - } -} From a81c7afe9e9a533b3bb59b1e8c71a38a86293859 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Tue, 11 Feb 2025 22:03:03 -0500 Subject: [PATCH 25/32] Removed unused imports. --- src/types/correction.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/correction.rs b/src/types/correction.rs index c37531e..5348554 100644 --- a/src/types/correction.rs +++ b/src/types/correction.rs @@ -1,6 +1,3 @@ -use std::ops::Add; - -use num::traits::SaturatingAdd; use strum::FromRepr; use crate::{sys::*, Error, Result}; From 7ed4467b1b6e35bf24ae31d6c179fc0221d80d91 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Tue, 11 Feb 2025 22:30:24 -0500 Subject: [PATCH 26/32] Removed unnessecary cargo dependecies. --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1383ee4..5ccb3c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,14 +25,11 @@ num-complex = "0.4.6" parking_lot = "0.12.3" strum = { version = "0.26.3", features = ["derive", "strum_macros"] } thiserror = "2" -num = "0.4.3" [dev-dependencies] anyhow = "1" clap = { version = "4.5.27", features = ["derive"] } crossbeam-channel = "0.5" -ratatui = "0.29.0" -tui-textarea = "0.7.0" crossterm = "0.28" ctrlc = "3.4.5" indicatif = "0.17.11" From 71382ba683003e6e6e93bc11459084bb0a355e93 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Tue, 11 Feb 2025 22:54:09 -0500 Subject: [PATCH 27/32] Make sure bladerf returns a value within range. --- src/bladerf.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bladerf.rs b/src/bladerf.rs index 44c96da..9f6e7cd 100644 --- a/src/bladerf.rs +++ b/src/bladerf.rs @@ -840,8 +840,9 @@ pub trait BladeRF: Sized + Drop { ) }; check_res!(res); - // Safety: the bladerf should return a valid value in the correct range. - Ok(unsafe { T::new_unchecked(value) }) + T::new(value).ok_or(Error::Msg( + format!("Invalid correction value returned from bladerf: {value}").into_boxed_str(), + )) } // Corrections and Calibration From 36d9fb6e0a314a2b5a622da986d1e22e7088e39d Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Wed, 12 Feb 2025 19:58:02 -0500 Subject: [PATCH 28/32] compiling again --- Cargo.toml | 3 ++ examples/siggen.rs | 80 +++++++++++++++++++--------------------------- 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ccb3c3..38fd087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,13 @@ crossbeam-channel = "0.5" crossterm = "0.28" ctrlc = "3.4.5" indicatif = "0.17.11" +num = "0.4.3" once_cell = "1.20" pretty_env_logger = "0.5.0" +ratatui = "0.29.0" serial_test = "3.2.0" tempfile = "3.13" +tui-textarea = "0.7.0" [features] hwtest_any = [] diff --git a/examples/siggen.rs b/examples/siggen.rs index ca91897..c13ae7d 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,7 +1,5 @@ use std::{io, rc::Rc, sync::Arc, thread}; -use num::traits::SaturatingAdd; -use num_complex::Complex32; use ratatui::{ buffer::Buffer, layout::Rect, @@ -11,10 +9,12 @@ use ratatui::{ DefaultTerminal, }; +use num::traits::Num; + use ratatui::prelude::*; use bladerf::{ - BladeRF, CorrectionDcOffsetI, CorrectionDcOffsetQ, CorrectionGain, CorrectionPhase, + BladeRF, BladeRfAny, CorrectionDcOffsetI, CorrectionDcOffsetQ, CorrectionGain, CorrectionPhase, CorrectionValue, }; use tui_textarea::{Input, Key, TextArea}; @@ -51,7 +51,7 @@ impl SelectedInput { pub struct App<'a> { channel: bladerf::Channel, - device: &'a BladeRF, + device: &'a BladeRfAny, selected_input: SelectedInput, focused: bool, exit: bool, @@ -67,21 +67,21 @@ fn validate_frequency(val: &str) -> Result { } } -fn validate_correction(val: &str) -> Result { +fn validate_correction(val: &str) -> Result { match val.parse::().map(|x| T::new(x)) { Err(err) => Err(format!("{}", err)), - Ok(Some(x)) => Ok(x), + Ok(Some(x)) => Ok(x.value()), Ok(None) => Err(format!("Value `{val}` out of range")), } } /// A custom numeric input widget with validation -pub struct NumericInput<'a, T: SaturatingAdd, E> { +pub struct NumericInput<'a, T: Num, E> { textarea: TextArea<'a>, validation_fn: IntValidationFunction, // Validation logic } -impl<'a, T: SaturatingAdd> NumericInput<'a, T, String> { +impl<'a, T: Num> NumericInput<'a, T, String> { /// Creates a new `NumericInput` with the provided initial value and validation function. pub fn new(initial_value: String, validation_fn: F) -> Self where @@ -155,7 +155,7 @@ trait NumericInputHandle { fn num_render(&self, area: Rect, buf: &mut Buffer); } -impl<'a, T: SaturatingAdd> NumericInputHandle for &mut NumericInput<'a, T, String> { +impl<'a, T: Num> NumericInputHandle for &mut NumericInput<'a, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } @@ -173,7 +173,7 @@ impl<'a, T: SaturatingAdd> NumericInputHandle for &mut NumericInput<'a, T, Strin } } -impl<'a, T: SaturatingAdd> NumericInputHandle for NumericInput<'a, T, String> { +impl<'a, T: Num> NumericInputHandle for NumericInput<'a, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } @@ -191,7 +191,7 @@ impl<'a, T: SaturatingAdd> NumericInputHandle for NumericInput<'a, T, String> { } } -impl<'a, T: SaturatingAdd, E> Widget for &NumericInput<'a, T, E> { +impl<'a, T: Num, E> Widget for &NumericInput<'a, T, E> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -200,7 +200,7 @@ impl<'a, T: SaturatingAdd, E> Widget for &NumericInput<'a, T, E> { } } -impl<'a, T: SaturatingAdd, E> Widget for NumericInput<'a, T, E> { +impl<'a, T: Num, E> Widget for NumericInput<'a, T, E> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -211,7 +211,7 @@ impl<'a, T: SaturatingAdd, E> Widget for NumericInput<'a, T, E> { trait NumericInputWidget: NumericInputHandle + Widget {} -impl<'a, T: SaturatingAdd> NumericInputWidget for NumericInput<'a, T, String> {} +impl<'a, T: Num> NumericInputWidget for NumericInput<'a, T, String> {} impl Widget for &dyn NumericInputWidget { fn render(self, area: Rect, buf: &mut Buffer) @@ -240,7 +240,7 @@ enum MyAppAction { } impl<'a> App<'a> { - fn new(dev: &'a BladeRF) -> App<'a> { + fn new(dev: &'a BladeRfAny) -> App<'a> { let channel = bladerf::Channel::Tx0; App { channel, @@ -390,36 +390,28 @@ impl<'a> App<'a> { } SelectedInput::DcOffsetI => { if let Some(val) = icorr_input.inner_val() { - icorr_input - .textarea - .set_yank_text((val.into_inner() + 1).to_string()); + icorr_input.textarea.set_yank_text((val + 1).to_string()); icorr_input.textarea.select_all(); icorr_input.textarea.paste(); } } SelectedInput::DcOffsetQ => { if let Some(val) = qcorr_input.inner_val() { - qcorr_input - .textarea - .set_yank_text((val.into_inner() + 1).to_string()); + qcorr_input.textarea.set_yank_text((val + 1).to_string()); qcorr_input.textarea.select_all(); qcorr_input.textarea.paste(); } } SelectedInput::Phase => { if let Some(val) = phase_input.inner_val() { - phase_input - .textarea - .set_yank_text((val.into_inner() + 1).to_string()); + phase_input.textarea.set_yank_text((val + 1).to_string()); phase_input.textarea.select_all(); phase_input.textarea.paste(); } } SelectedInput::Gain => { if let Some(val) = gain_input.inner_val() { - gain_input - .textarea - .set_yank_text((val.into_inner() + 1).to_string()); + gain_input.textarea.set_yank_text((val + 1).to_string()); gain_input.textarea.select_all(); gain_input.textarea.paste(); } @@ -439,36 +431,28 @@ impl<'a> App<'a> { } SelectedInput::DcOffsetI => { if let Some(val) = icorr_input.inner_val() { - icorr_input - .textarea - .set_yank_text((val.into_inner() - 1).to_string()); + icorr_input.textarea.set_yank_text((val - 1).to_string()); icorr_input.textarea.select_all(); icorr_input.textarea.paste(); } } SelectedInput::DcOffsetQ => { if let Some(val) = qcorr_input.inner_val() { - qcorr_input - .textarea - .set_yank_text((val.into_inner() - 1).to_string()); + qcorr_input.textarea.set_yank_text((val - 1).to_string()); qcorr_input.textarea.select_all(); qcorr_input.textarea.paste(); } } SelectedInput::Phase => { if let Some(val) = phase_input.inner_val() { - phase_input - .textarea - .set_yank_text((val.into_inner() - 1).to_string()); + phase_input.textarea.set_yank_text((val - 1).to_string()); phase_input.textarea.select_all(); phase_input.textarea.paste(); } } SelectedInput::Gain => { if let Some(val) = gain_input.inner_val() { - gain_input - .textarea - .set_yank_text((val.into_inner() - 1).to_string()); + gain_input.textarea.set_yank_text((val - 1).to_string()); gain_input.textarea.select_all(); gain_input.textarea.paste(); } @@ -481,16 +465,16 @@ impl<'a> App<'a> { self.set_freq(val); } if let Ok(val) = (icorr_input.validation_fn)(icorr_input.value().as_str()) { - self.set_corr(val); + self.set_corr(CorrectionDcOffsetI::new_saturating(val)); } if let Ok(val) = (qcorr_input.validation_fn)(qcorr_input.value().as_str()) { - self.set_corr(val); + self.set_corr(CorrectionDcOffsetQ::new_saturating(val)); } if let Ok(val) = (phase_input.validation_fn)(phase_input.value().as_str()) { - self.set_corr(val); + self.set_corr(CorrectionPhase::new_saturating(val)); } if let Ok(val) = (gain_input.validation_fn)(gain_input.value().as_str()) { - self.set_corr(val); + self.set_corr(CorrectionGain::new_saturating(val)); } } } @@ -525,28 +509,28 @@ impl<'a> App<'a> { self.device .get_correction::(self.channel) .unwrap() - .into_inner() + .value() } fn get_qcorr(&self) -> i16 { self.device .get_correction::(self.channel) .unwrap() - .into_inner() + .value() } fn get_phase(&self) -> i16 { self.device .get_correction::(self.channel) .unwrap() - .into_inner() + .value() } fn get_gain(&self) -> i16 { self.device .get_correction::(self.channel) .unwrap() - .into_inner() + .value() } fn set_freq(&self, freq: u64) { @@ -558,7 +542,7 @@ impl<'a> App<'a> { } /// updates the application's state based on user input - fn handle_events( + fn handle_events( &mut self, idk: Option<&mut NumericInput<'_, T, String>>, ) -> io::Result { @@ -609,7 +593,7 @@ impl<'a> Widget for &App<'a> { fn main() -> io::Result<()> { let device = - BladeRF::open_first().map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; + BladeRfAny::open_first().map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; let arc_dev = Arc::new(device); let thread_arc_dev = arc_dev.clone(); From 03014d6e396c1a7f77fc7b32f0e4a73593964873 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Wed, 12 Feb 2025 23:10:45 -0500 Subject: [PATCH 29/32] changed some functions to return strings as to better relay an error message Honestely I should change this again to return a result, but eh, next commit --- examples/siggen.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index c13ae7d..4c9260e 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -505,32 +505,38 @@ impl<'a> App<'a> { self.device.get_frequency(self.channel).unwrap() } - fn get_icorr(&self) -> i16 { - self.device + fn get_icorr(&self) -> String { + match self + .device .get_correction::(self.channel) - .unwrap() - .value() + { + Ok(x) => x.value().to_string(), + Err(err) => err.to_string(), + } } - fn get_qcorr(&self) -> i16 { - self.device + fn get_qcorr(&self) -> String { + match self + .device .get_correction::(self.channel) - .unwrap() - .value() + { + Ok(x) => x.value().to_string(), + Err(err) => err.to_string(), + } } - fn get_phase(&self) -> i16 { - self.device - .get_correction::(self.channel) - .unwrap() - .value() + fn get_phase(&self) -> String { + match self.device.get_correction::(self.channel) { + Ok(x) => x.value().to_string(), + Err(err) => err.to_string(), + } } - fn get_gain(&self) -> i16 { - self.device - .get_correction::(self.channel) - .unwrap() - .value() + fn get_gain(&self) -> String { + match self.device.get_correction::(self.channel) { + Ok(x) => x.value().to_string(), + Err(err) => err.to_string(), + } } fn set_freq(&self, freq: u64) { From 67aeb9c67bf196588a88b6243f6f536b7a0506f9 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Wed, 12 Feb 2025 23:17:29 -0500 Subject: [PATCH 30/32] A ctrl-c handler (not sure if cross platform --- examples/siggen.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/siggen.rs b/examples/siggen.rs index 4c9260e..b002972 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -573,6 +573,11 @@ impl<'a> App<'a> { Input { key: Key::Left, .. } => { app_action = MyAppAction::Decrement; } + Input { + key: Key::Char('c'), + ctrl: true, + .. + } => self.exit(), Input { key: Key::Right, .. } => { From caf51b0a0fa8e4059d869f9887c47dd82db6d5c2 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Wed, 12 Feb 2025 23:20:43 -0500 Subject: [PATCH 31/32] Removed some elided lifetimes. --- examples/siggen.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index b002972..63fb5b9 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -1,4 +1,4 @@ -use std::{io, rc::Rc, sync::Arc, thread}; +use std::{io, rc::Rc, sync::Arc}; use ratatui::{ buffer::Buffer, @@ -81,7 +81,7 @@ pub struct NumericInput<'a, T: Num, E> { validation_fn: IntValidationFunction, // Validation logic } -impl<'a, T: Num> NumericInput<'a, T, String> { +impl NumericInput<'_, T, String> { /// Creates a new `NumericInput` with the provided initial value and validation function. pub fn new(initial_value: String, validation_fn: F) -> Self where @@ -155,7 +155,7 @@ trait NumericInputHandle { fn num_render(&self, area: Rect, buf: &mut Buffer); } -impl<'a, T: Num> NumericInputHandle for &mut NumericInput<'a, T, String> { +impl NumericInputHandle for &mut NumericInput<'_, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } @@ -173,7 +173,7 @@ impl<'a, T: Num> NumericInputHandle for &mut NumericInput<'a, T, String> { } } -impl<'a, T: Num> NumericInputHandle for NumericInput<'a, T, String> { +impl NumericInputHandle for NumericInput<'_, T, String> { fn handle_input(&mut self, input: Input) { self.handle_input_inner(input); } @@ -191,7 +191,7 @@ impl<'a, T: Num> NumericInputHandle for NumericInput<'a, T, String> { } } -impl<'a, T: Num, E> Widget for &NumericInput<'a, T, E> { +impl Widget for &NumericInput<'_, T, E> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -200,7 +200,7 @@ impl<'a, T: Num, E> Widget for &NumericInput<'a, T, E> { } } -impl<'a, T: Num, E> Widget for NumericInput<'a, T, E> { +impl Widget for NumericInput<'_, T, E> { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, @@ -211,7 +211,7 @@ impl<'a, T: Num, E> Widget for NumericInput<'a, T, E> { trait NumericInputWidget: NumericInputHandle + Widget {} -impl<'a, T: Num> NumericInputWidget for NumericInput<'a, T, String> {} +impl NumericInputWidget for NumericInput<'_, T, String> {} impl Widget for &dyn NumericInputWidget { fn render(self, area: Rect, buf: &mut Buffer) @@ -594,7 +594,7 @@ impl<'a> App<'a> { } } -impl<'a> Widget for &App<'a> { +impl Widget for &App<'_> { fn render(self, area: Rect, buf: &mut Buffer) { let title = Line::from(" BladeRF SigGen ".bold()); @@ -607,10 +607,6 @@ fn main() -> io::Result<()> { BladeRfAny::open_first().map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; let arc_dev = Arc::new(device); - let thread_arc_dev = arc_dev.clone(); - thread::spawn(move || { - thread_arc_dev.set_gain(bladerf::Channel::Tx0, 0).unwrap(); - }); let mut terminal = ratatui::init(); let app_result = App::new(&arc_dev).run(&mut terminal); From 41a3ee7bea10b9658fda9056b455f495ca236ad6 Mon Sep 17 00:00:00 2001 From: Erik Fong Date: Wed, 12 Feb 2025 23:29:22 -0500 Subject: [PATCH 32/32] minor usage test change --- examples/siggen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/siggen.rs b/examples/siggen.rs index 63fb5b9..29dc21b 100644 --- a/examples/siggen.rs +++ b/examples/siggen.rs @@ -278,7 +278,7 @@ impl<'a> App<'a> { "Use up down arrow keys to select field".into(), "Hit enter to edit a field and ender again to exit, upon exit the value will be updated".into(), "You can use the left right arroy keys to move between values".into(), - "Esc to quit (I don't know how to handle SIGINT".into() + "Esc or Ctrl-C to quit".into() ]); frequency_input.unset_focus();