diff --git a/Cargo.lock b/Cargo.lock index 90be05b..b3c8f2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,7 +150,7 @@ dependencies = [ [[package]] name = "bluestation-bs" -version = "0.5.5" +version = "0.5.6" dependencies = [ "clap", "ctrlc", @@ -719,7 +719,7 @@ dependencies = [ [[package]] name = "net-tnmm-test" -version = "0.5.5" +version = "0.5.6" dependencies = [ "clap", "tetra-config", @@ -734,7 +734,7 @@ dependencies = [ [[package]] name = "net-tnmm-test-quic" -version = "0.5.5" +version = "0.5.6" dependencies = [ "quinn", "rcgen", @@ -886,7 +886,7 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "pdu-tool" -version = "0.5.5" +version = "0.5.6" dependencies = [ "clap", "tetra-config", @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "tetra-config" -version = "0.5.5" +version = "0.5.6" dependencies = [ "chrono-tz", "serde", @@ -1511,7 +1511,7 @@ dependencies = [ [[package]] name = "tetra-core" -version = "0.5.5" +version = "0.5.6" dependencies = [ "const_format", "git-version", @@ -1523,7 +1523,7 @@ dependencies = [ [[package]] name = "tetra-entities" -version = "0.5.5" +version = "0.5.6" dependencies = [ "as-any", "bitcode", @@ -1553,7 +1553,7 @@ dependencies = [ [[package]] name = "tetra-pdus" -version = "0.5.5" +version = "0.5.6" dependencies = [ "tetra-config", "tetra-core", @@ -1563,7 +1563,7 @@ dependencies = [ [[package]] name = "tetra-saps" -version = "0.5.5" +version = "0.5.6" dependencies = [ "tetra-core", "tracing", diff --git a/Cargo.toml b/Cargo.toml index f008d48..ac3e843 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ ] [workspace.package] -version = "0.5.5" +version = "0.5.6" edition = "2024" authors = ["Wouter Bokslag / Midnight Blue"] license = "MIT" diff --git a/bins/bluestation-bs/src/main.rs b/bins/bluestation-bs/src/main.rs index 8b04aee..c00f273 100644 --- a/bins/bluestation-bs/src/main.rs +++ b/bins/bluestation-bs/src/main.rs @@ -92,9 +92,9 @@ fn main() { eprintln!("░▀█▀░█▀▀░▀█▀░█▀▄░█▀█░░░░░█▀▄░█░░░█░█░█▀▀░█▀▀░▀█▀░█▀█░▀█▀░▀█▀░█▀█░█▀█"); eprintln!("░░█░░█▀▀░░█░░█▀▄░█▀█░▄▄▄░█▀▄░█░░░█░█░█▀▀░▀▀█░░█░░█▀█░░█░░░█░░█░█░█░█"); eprintln!("░░▀░░▀▀▀░░▀░░▀░▀░▀░▀░░░░░▀▀░░▀▀▀░▀▀▀░▀▀▀░▀▀▀░░▀░░▀░▀░░▀░░▀▀▀░▀▀▀░▀░▀\n"); - eprintln!(" Wouter Bokslag / Midnight Blue"); - eprintln!(" -> https://github.com/MidnightBlueLabs/tetra-bluestation"); - eprintln!(" -> https://midnightblue.nl\n"); + eprintln!(" Wouter Bokslag / Midnight Blue"); + eprintln!(" https://github.com/MidnightBlueLabs/tetra-bluestation"); + eprintln!(" Version: {}", tetra_core::STACK_VERSION); let args = Args::parse(); let mut cfg = load_config_from_toml(&args.config); diff --git a/crates/tetra-config/src/bluestation/parsing.rs b/crates/tetra-config/src/bluestation/parsing.rs index a990cf1..25b4c21 100644 --- a/crates/tetra-config/src/bluestation/parsing.rs +++ b/crates/tetra-config/src/bluestation/parsing.rs @@ -17,7 +17,7 @@ pub fn from_toml_str(toml_str: &str) -> Result Result>(); + let extra_keys_filtered = extra_keys + .iter() + .filter(|key| !(key.starts_with("rx_gain_") || key.starts_with("tx_gain_"))) + .collect::>(); if !extra_keys_filtered.is_empty() { return Err(format!("Unrecognized fields: phy_io.soapysdr::{:?}", extra_keys_filtered).into()); } diff --git a/crates/tetra-config/src/bluestation/sec_phy.rs b/crates/tetra-config/src/bluestation/sec_phy.rs index 236a70f..78d80e0 100644 --- a/crates/tetra-config/src/bluestation/sec_phy.rs +++ b/crates/tetra-config/src/bluestation/sec_phy.rs @@ -56,26 +56,40 @@ pub fn phy_dto_to_cfg(src: PhyIoDto) -> CfgPhyIo { tx_ch: soapy_dto.tx_channel, rx_ant: soapy_dto.rx_antenna, tx_ant: soapy_dto.tx_antenna, - rx_gains: soapy_dto.extra.iter().filter_map(|(key, value)| { - key.strip_prefix("rx_gain_").map(|gain_name| { - (gain_name.to_string().to_lowercase(), match value { - Value::Integer(v) => *v as f64, - Value::Float(v) => *v, - // TODO: should this error be returned somehow? - _ => panic!("RX gain value must be a number"), + rx_gains: soapy_dto + .extra + .iter() + .filter_map(|(key, value)| { + key.strip_prefix("rx_gain_").map(|gain_name| { + ( + gain_name.to_string().to_lowercase(), + match value { + Value::Integer(v) => *v as f64, + Value::Float(v) => *v, + // TODO: should this error be returned somehow? + _ => panic!("RX gain value must be a number"), + }, + ) }) }) - }).collect(), - tx_gains: soapy_dto.extra.iter().filter_map(|(key, value)| { - key.strip_prefix("tx_gain_").map(|gain_name| { - (gain_name.to_string().to_lowercase(), match value { - Value::Integer(v) => *v as f64, - Value::Float(v) => *v, - // TODO: should this error be returned somehow? - _ => panic!("TX gain value must be a number"), + .collect(), + tx_gains: soapy_dto + .extra + .iter() + .filter_map(|(key, value)| { + key.strip_prefix("tx_gain_").map(|gain_name| { + ( + gain_name.to_string().to_lowercase(), + match value { + Value::Integer(v) => *v as f64, + Value::Float(v) => *v, + // TODO: should this error be returned somehow? + _ => panic!("TX gain value must be a number"), + }, + ) }) }) - }).collect(), + .collect(), } }); diff --git a/crates/tetra-entities/src/phy/components/soapy_settings.rs b/crates/tetra-entities/src/phy/components/soapy_settings.rs index 61a71dd..3dfcd76 100644 --- a/crates/tetra-entities/src/phy/components/soapy_settings.rs +++ b/crates/tetra-entities/src/phy/components/soapy_settings.rs @@ -2,7 +2,6 @@ use tetra_config::bluestation::{StackMode, sec_phy_soapy::*}; - /// Enum of all supported devices pub enum SupportedDevice { LimeSdr(LimeSdrModel), @@ -34,43 +33,31 @@ impl SupportedDevice { /// Return None if the device is not supported. pub fn detect(driver_key: &str, hardware_key: &str) -> Option { match (driver_key, hardware_key) { - ("FX3", "LimeSDR-USB") => - Some(Self::LimeSdr(LimeSdrModel::LimeSdrUsb)), - ("FX3", _) => - Some(Self::LimeSdr(LimeSdrModel::OtherFx3)), + ("FX3", "LimeSDR-USB") => Some(Self::LimeSdr(LimeSdrModel::LimeSdrUsb)), + ("FX3", _) => Some(Self::LimeSdr(LimeSdrModel::OtherFx3)), - ("FT601", "LimeSDR-Mini_v2") => - Some(Self::LimeSdr(LimeSdrModel::LimeSdrMiniV2)), - ("FT601", "LimeNET-Micro") => - Some(Self::LimeSdr(LimeSdrModel::LimeNetMicro)), - ("FT601", _) => - Some(Self::LimeSdr(LimeSdrModel::OtherFt601)), + ("FT601", "LimeSDR-Mini_v2") => Some(Self::LimeSdr(LimeSdrModel::LimeSdrMiniV2)), + ("FT601", "LimeNET-Micro") => Some(Self::LimeSdr(LimeSdrModel::LimeNetMicro)), + ("FT601", _) => Some(Self::LimeSdr(LimeSdrModel::OtherFt601)), - ("sx", _) => - Some(Self::SXceiver), + ("sx", _) => Some(Self::SXceiver), - ("PlutoSDR", _) => - Some(Self::PlutoSdr), + ("PlutoSDR", _) => Some(Self::PlutoSdr), // USRP B210 seems to report as ("b200", "B210"), // but the driver key is also known to be "uhd" in some cases. // The reason is unknown but might be due to // gateware, firmware or driver version differences. // Try to detect USRP correctly in all cases. - ("b200", "B200") | ("uhd", "B200") => - Some(Self::Usrp(UsrpModel::B200)), - ("b200", "B210") | ("uhd", "B210") => - Some(Self::Usrp(UsrpModel::B210)), - ("b200", _) | ("uhd", _) => - Some(Self::Usrp(UsrpModel::Other)), + ("b200", "B200") | ("uhd", "B200") => Some(Self::Usrp(UsrpModel::B200)), + ("b200", "B210") | ("uhd", "B210") => Some(Self::Usrp(UsrpModel::B210)), + ("b200", _) | ("uhd", _) => Some(Self::Usrp(UsrpModel::Other)), // TODO: add other USRP models if needed - _ => None, } } } - #[derive(Clone, Debug)] pub struct SdrSettings { /// Settings template, holding which SDR is used @@ -137,7 +124,7 @@ impl SdrSettings { } if !cfg_gains.is_empty() { tracing::error!("Unsupported RX gains for {}: {:?}", settings.name, cfg_gains); - return Err(Error::InvalidConfiguration) + return Err(Error::InvalidConfiguration); } let mut cfg_gains = cfg.tx_gains.clone(); @@ -148,7 +135,7 @@ impl SdrSettings { } if !cfg_gains.is_empty() { tracing::error!("Unsupported TX gains for {}: {:?}", settings.name, cfg_gains); - return Err(Error::InvalidConfiguration) + return Err(Error::InvalidConfiguration); } // TODO: check for extra gain fields in cfg @@ -159,17 +146,13 @@ impl SdrSettings { /// Get default settings based on SDR type fn get_defaults(cfg: &CfgSoapySdr, device: SupportedDevice, mode: StackMode) -> Self { match device { - SupportedDevice::LimeSdr(model) => - Self::settings_limesdr(mode, model), + SupportedDevice::LimeSdr(model) => Self::settings_limesdr(mode, model), - SupportedDevice::SXceiver => - Self::settings_sxceiver(mode, cfg.fs), + SupportedDevice::SXceiver => Self::settings_sxceiver(mode, cfg.fs), - SupportedDevice::PlutoSdr => - Self::settings_pluto(mode), + SupportedDevice::PlutoSdr => Self::settings_pluto(mode), - SupportedDevice::Usrp(model) => - Self::settings_usrp(mode, model), + SupportedDevice::Usrp(model) => Self::settings_usrp(mode, model), } } @@ -220,25 +203,24 @@ impl SdrSettings { } .to_string(), - rx_ant: Some(match model { - LimeSdrModel::LimeSdrUsb => "LNAL", - _ => "LNAW", - }.to_string()), + rx_ant: Some( + match model { + LimeSdrModel::LimeSdrUsb => "LNAL", + _ => "LNAW", + } + .to_string(), + ), - tx_ant: Some(match model { - LimeSdrModel::LimeSdrUsb => "BAND1", - _ => "BAND2", - }.to_string()), + tx_ant: Some( + match model { + LimeSdrModel::LimeSdrUsb => "BAND1", + _ => "BAND2", + } + .to_string(), + ), - rx_gain: vec![ - ("LNA".to_string(), 18.0), - ("TIA".to_string(), 6.0), - ("PGA".to_string(), 10.0), - ], - tx_gain: vec![ - ("PAD".to_string(), 22.0), - ("IAMP".to_string(), 6.0), - ], + rx_gain: vec![("LNA".to_string(), 18.0), ("TIA".to_string(), 6.0), ("PGA".to_string(), 10.0)], + tx_gain: vec![("PAD".to_string(), 22.0), ("IAMP".to_string(), 6.0)], // Minimum latency for BS/MS, maximum throughput for monitor rx_args: vec![("latency".to_string(), if mode == StackMode::Mon { "1" } else { "0" }.to_string())], @@ -265,14 +247,8 @@ impl SdrSettings { rx_ant: Some("RX".to_string()), tx_ant: Some("TX".to_string()), - rx_gain: vec![ - ("LNA".to_string(), 42.0), - ("PGA".to_string(), 16.0), - ], - tx_gain: vec![ - ("DAC".to_string(), 9.0), - ("MIXER".to_string(), 30.0), - ], + rx_gain: vec![("LNA".to_string(), 42.0), ("PGA".to_string(), 16.0)], + tx_gain: vec![("DAC".to_string(), 9.0), ("MIXER".to_string(), 30.0)], rx_args: vec![("period".to_string(), block_size(fs).to_string())], tx_args: vec![("period".to_string(), block_size(fs).to_string())], @@ -327,7 +303,6 @@ impl SdrSettings { } } - /// Get processing block size in samples for a given sample rate. /// This can be used to optimize performance for some SDRs. pub fn block_size(fs: f64) -> usize { diff --git a/crates/tetra-entities/src/phy/components/soapyio.rs b/crates/tetra-entities/src/phy/components/soapyio.rs index 8dfc28c..cca64df 100644 --- a/crates/tetra-entities/src/phy/components/soapyio.rs +++ b/crates/tetra-entities/src/phy/components/soapyio.rs @@ -336,10 +336,8 @@ impl SoapyIo { } } - // Messy logic related to opening a device follows... - /// Struct to temporarily hold stuff related to opening and detecting a device struct OpenedDevice { dev_args: soapysdr::Args, @@ -370,7 +368,11 @@ fn open_given_device(dev_args: soapysdr::Args) -> Result Result Result return Ok(opened_device), - Err(_) => {}, + Err(_) => {} } } return Err(soapysdr::Error { @@ -414,10 +420,12 @@ fn open_device(soapy_cfg: &CfgSoapySdr, mode: StackMode) -> Result<(soapysdr::De let mut sdr_settings = match SdrSettings::get_settings(&soapy_cfg, opened_device.detected_device, mode) { Ok(sdr_settings) => sdr_settings, - Err(soapy_settings::Error::InvalidConfiguration) => return Err(soapysdr::Error { - code: soapysdr::ErrorCode::Other, - message: "Invalid SDR device configuration".to_string(), - }), + Err(soapy_settings::Error::InvalidConfiguration) => { + return Err(soapysdr::Error { + code: soapysdr::ErrorCode::Other, + message: "Invalid SDR device configuration".to_string(), + }); + } }; if opened_device.soapyremote_used { @@ -439,7 +447,10 @@ fn open_device(soapy_cfg: &CfgSoapySdr, mode: StackMode) -> Result<(soapysdr::De // Make sure device gets closed first. Not sure if needed. std::mem::drop(opened_device.dev); - opened_device.dev = soapycheck!("open SoapySDR device with additional arguments", soapysdr::Device::new(opened_device.dev_args)); + opened_device.dev = soapycheck!( + "open SoapySDR device with additional arguments", + soapysdr::Device::new(opened_device.dev_args) + ); // Make sure it is still the same device. // Unlikely to change, but who knows if a device got connected just in between, // or if the device broke from first opening attempt and something else got opened @@ -447,8 +458,13 @@ fn open_device(soapy_cfg: &CfgSoapySdr, mode: StackMode) -> Result<(soapysdr::De let new_driver_key = opened_device.dev.driver_key().unwrap_or_default(); let new_hardware_key = opened_device.dev.hardware_key().unwrap_or_default(); if new_driver_key != opened_device.driver_key || new_hardware_key != opened_device.hardware_key { - tracing::info!("Expected the same driver_key='{}' hardware_key='{}' after reopen, got driver_key='{}' hardware_key='{}'", - opened_device.driver_key, opened_device.hardware_key, new_driver_key, new_hardware_key); + tracing::info!( + "Expected the same driver_key='{}' hardware_key='{}' after reopen, got driver_key='{}' hardware_key='{}'", + opened_device.driver_key, + opened_device.hardware_key, + new_driver_key, + new_hardware_key + ); return Err(soapysdr::Error { code: soapysdr::ErrorCode::Other, message: "Reopened a different device".to_string(), diff --git a/example_config/config.toml b/example_config/config.toml index fea9666..f53540b 100644 --- a/example_config/config.toml +++ b/example_config/config.toml @@ -2,7 +2,7 @@ # This is an example configuration file for the TETRA base station stack # DO NOT RUN without editing to stay within legal limits of your jurisdiction -config_version = "0.5" +config_version = "0.6" # Stack operation mode: "Bs" (Base Station), "Ms" (Mobile Station), or "Mon" (Monitor) stack_mode = "Bs" @@ -28,7 +28,15 @@ backend = "SoapySdr" # !!! Make sure to also edit all related fields in the cell_info section to fit this frequency. tx_freq = 438025000 rx_freq = 433025000 -ppm_err = 0.0 # Adjust if your SDR has a non-negligible tuning error + +# Adjust if your SDR has a non-negligible tuning error +# ppm_err = 0.0 + +################################################################################################ +## OPTIONAL: specific device, antenna, gain selection ## +## Make sure to check your device for valid settings using SoapySDRUtil --probe ## +## or check: https://github.com/MidnightBlueLabs/tetra-bluestation-docs/wiki/03-Configuration ## +################################################################################################ # Optional device selection arguments. # If not specified, the first supported device found is selected automatically. @@ -40,18 +48,17 @@ ppm_err = 0.0 # Adjust if your SDR has a non-negligible tu # device = "driver=lime,serial=123456789" # Optional antenna selection to override device-specific defaults +# Check antenna names with SoapySDRUtil --probe # rx_antenna = "LNAW" # tx_antenna = "BAND2" # Optional gain values to override device-specific defaults. +# Check valid gain names with SoapySDRUtil --probe # For example, to increase transmit power on a LimeSDR: # tx_gain_pad = 50.0 # To adjust LNA gain to optimize RX performance on a LimeSDR or SXceiver: # rx_gain_lna = 30.0 -# Antennas, gain names and ranges of gain values are device dependent. -# To list them, try something like: SoapySDRUtil --probe=driver=lime - ############################################################################### # Network Information