use std::{
fmt::Debug,
io,
sync::{Arc, Condvar, Mutex},
thread,
time::Duration,
};
use super::{
Attenuation, Config, ConfigAmpSweep, ConfigAmpSweepExp, ConfigCw, ConfigCwExp, ConfigExp,
ConfigFreqSweep, ConfigFreqSweepExp, Model, PowerLevel, Temperature,
};
use crate::rf_explorer::{
ConfigCallback, NEXT_SCREEN_DATA_TIMEOUT, RECEIVE_INITIAL_DEVICE_INFO_TIMEOUT, ScreenData,
SerialNumber, SetupInfo, impl_rf_explorer,
};
use crate::{ConnectionError, ConnectionResult, Device, Error, Frequency, Result};
#[derive(Debug)]
pub struct SignalGenerator {
rfe: Device<MessageContainer>,
}
impl_rf_explorer!(SignalGenerator, MessageContainer);
impl SignalGenerator {
const MAX_7_DIGIT_KHZ: u64 = 9_999_999;
const MAX_4_DIGIT_DECIMAL: u16 = 9_999;
const MAX_5_DIGIT_MILLIS: u128 = 99_999;
const MIN_POWER_DB: f64 = -99.9;
const MAX_POWER_DB: f64 = 99.9;
pub fn serial_number(&self) -> Option<String> {
if let Some(ref serial_number) = *self.messages().serial_number.0.lock().unwrap() {
return Some(serial_number.to_string());
}
self.send_command(crate::rf_explorer::Command::RequestSerialNumber)
.ok()?;
let (lock, cvar) = &self.messages().serial_number;
tracing::trace!("Waiting to receive SerialNumber from RF Explorer");
let _ = cvar
.wait_timeout_while(
lock.lock().unwrap(),
std::time::Duration::from_secs(2),
|serial_number| serial_number.is_none(),
)
.unwrap();
(*self.messages().serial_number.0.lock().unwrap())
.as_ref()
.map(|sn| sn.to_string())
}
pub fn firmware_version(&self) -> String {
self.messages()
.setup_info
.0
.lock()
.unwrap()
.as_ref()
.map(|setup_info| setup_info.firmware_version.clone())
.unwrap_or_default()
}
pub fn config(&self) -> Option<Config> {
*self.messages().config.0.lock().unwrap()
}
pub fn config_expansion(&self) -> Option<ConfigExp> {
*self.messages().config_exp.0.lock().unwrap()
}
pub fn config_amp_sweep(&self) -> Option<ConfigAmpSweep> {
*self.messages().config_amp_sweep.0.lock().unwrap()
}
pub fn config_amp_sweep_expansion(&self) -> Option<ConfigAmpSweepExp> {
*self.messages().config_amp_sweep_exp.0.lock().unwrap()
}
pub fn config_cw(&self) -> Option<ConfigCw> {
*self.messages().config_cw.0.lock().unwrap()
}
pub fn config_cw_expansion(&self) -> Option<ConfigCwExp> {
*self.messages().config_cw_exp.0.lock().unwrap()
}
pub fn config_freq_sweep(&self) -> Option<ConfigFreqSweep> {
*self.messages().config_freq_sweep.0.lock().unwrap()
}
pub fn config_freq_sweep_expansion(&self) -> Option<ConfigFreqSweepExp> {
*self.messages().config_freq_sweep_exp.0.lock().unwrap()
}
pub fn screen_data(&self) -> Option<ScreenData> {
self.messages().screen_data.0.lock().unwrap().clone()
}
pub fn wait_for_next_screen_data(&self) -> Result<ScreenData> {
self.wait_for_next_screen_data_with_timeout(NEXT_SCREEN_DATA_TIMEOUT)
}
pub fn wait_for_next_screen_data_with_timeout(&self, timeout: Duration) -> Result<ScreenData> {
let previous_screen_data = self.screen_data();
let (screen_data, condvar) = &self.messages().screen_data;
let (screen_data, wait_result) = condvar
.wait_timeout_while(screen_data.lock().unwrap(), timeout, |screen_data| {
*screen_data == previous_screen_data || screen_data.is_none()
})
.unwrap();
match &*screen_data {
Some(screen_data) if !wait_result.timed_out() => Ok(screen_data.clone()),
_ => Err(crate::Error::TimedOut(timeout)),
}
}
pub fn temperature(&self) -> Option<Temperature> {
*self.messages().temperature.0.lock().unwrap()
}
pub fn main_radio_model(&self) -> Option<Model> {
self.messages()
.setup_info
.0
.lock()
.unwrap()
.as_ref()
.unwrap()
.main_radio_model
}
pub fn expansion_radio_model(&self) -> Option<Model> {
self.messages()
.setup_info
.0
.lock()
.unwrap()
.as_ref()
.unwrap()
.expansion_radio_model
}
pub fn active_radio_model(&self) -> Model {
let Some(exp_model) = self.expansion_radio_model() else {
return self.main_radio_model().unwrap_or_default();
};
if self.config_expansion().is_some() {
exp_model
} else {
self.main_radio_model().unwrap_or_default()
}
}
pub fn inactive_radio_model(&self) -> Option<Model> {
let exp_model = self.expansion_radio_model()?;
if self.config_expansion().is_some() {
self.main_radio_model()
} else {
Some(exp_model)
}
}
pub fn start_amp_sweep(
&self,
cw: impl Into<Frequency>,
start_attenuation: Attenuation,
start_power_level: PowerLevel,
stop_attenuation: Attenuation,
stop_power_level: PowerLevel,
step_delay: Duration,
) -> Result<()> {
let cw = cw.into();
Self::validate_command_frequency("cw", cw)?;
Self::validate_step_delay(step_delay)?;
self.send_command(super::Command::StartAmpSweep {
cw,
start_attenuation,
start_power_level,
stop_attenuation,
stop_power_level,
step_delay,
})?;
Ok(())
}
pub fn start_amp_sweep_exp(
&self,
cw: impl Into<Frequency>,
start_power_dbm: f64,
step_power_db: f64,
stop_power_dbm: f64,
step_delay: Duration,
) -> Result<()> {
let cw = cw.into();
Self::validate_command_frequency("cw", cw)?;
Self::validate_power_db("start_power_dbm", start_power_dbm)?;
Self::validate_power_db("step_power_db", step_power_db)?;
Self::validate_power_db("stop_power_dbm", stop_power_dbm)?;
Self::validate_step_delay(step_delay)?;
self.send_command(super::Command::StartAmpSweepExp {
cw,
start_power_dbm,
step_power_db,
stop_power_dbm,
step_delay,
})?;
Ok(())
}
pub fn start_cw(
&self,
cw: impl Into<Frequency>,
attenuation: Attenuation,
power_level: PowerLevel,
) -> Result<()> {
let cw = cw.into();
Self::validate_command_frequency("cw", cw)?;
self.send_command(super::Command::StartCw {
cw,
attenuation,
power_level,
})?;
Ok(())
}
pub fn start_cw_exp(&self, cw: impl Into<Frequency>, power_dbm: f64) -> Result<()> {
let cw = cw.into();
Self::validate_command_frequency("cw", cw)?;
Self::validate_power_db("power_dbm", power_dbm)?;
self.send_command(super::Command::StartCwExp { cw, power_dbm })?;
Ok(())
}
pub fn start_freq_sweep(
&self,
start: impl Into<Frequency>,
attenuation: Attenuation,
power_level: PowerLevel,
sweep_steps: u16,
step_hz: u64,
step_delay: Duration,
) -> Result<()> {
let start = start.into();
let step = Frequency::from_hz(step_hz);
Self::validate_command_frequency("start", start)?;
Self::validate_command_frequency("step", step)?;
Self::validate_sweep_steps(sweep_steps)?;
Self::validate_step_delay(step_delay)?;
self.send_command(super::Command::StartFreqSweep {
start,
attenuation,
power_level,
sweep_steps,
step,
step_delay,
})?;
Ok(())
}
pub fn start_freq_sweep_exp(
&self,
start: impl Into<Frequency>,
power_dbm: f64,
sweep_steps: u16,
step: impl Into<Frequency>,
step_delay: Duration,
) -> Result<()> {
let start = start.into();
let step = step.into();
Self::validate_command_frequency("start", start)?;
Self::validate_command_frequency("step", step)?;
Self::validate_power_db("power_dbm", power_dbm)?;
Self::validate_sweep_steps(sweep_steps)?;
Self::validate_step_delay(step_delay)?;
self.send_command(super::Command::StartFreqSweepExp {
start,
power_dbm,
sweep_steps,
step,
step_delay,
})?;
Ok(())
}
pub fn start_tracking(
&self,
start: impl Into<Frequency>,
attenuation: Attenuation,
power_level: PowerLevel,
sweep_steps: u16,
step: impl Into<Frequency>,
) -> Result<()> {
let start = start.into();
let step = step.into();
Self::validate_command_frequency("start", start)?;
Self::validate_command_frequency("step", step)?;
Self::validate_sweep_steps(sweep_steps)?;
self.send_command(super::Command::StartTracking {
start,
attenuation,
power_level,
sweep_steps,
step,
})?;
Ok(())
}
pub fn start_tracking_exp(
&self,
start: impl Into<Frequency>,
power_dbm: f64,
sweep_steps: u16,
step: impl Into<Frequency>,
) -> Result<()> {
let start = start.into();
let step = step.into();
Self::validate_command_frequency("start", start)?;
Self::validate_command_frequency("step", step)?;
Self::validate_power_db("power_dbm", power_dbm)?;
Self::validate_sweep_steps(sweep_steps)?;
self.send_command(super::Command::StartTrackingExp {
start,
power_dbm,
sweep_steps,
step,
})?;
Ok(())
}
pub fn tracking_step(&self, steps: u16) -> io::Result<()> {
self.send_command(super::Command::TrackingStep(steps))
}
pub fn set_config_callback(&self, cb: impl Fn(Config) + Send + Sync + 'static) {
*self.messages().config_callback.lock().unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_callback(&self) {
*self.messages().config_callback.lock().unwrap() = None;
}
pub fn set_config_exp_callback(&self, cb: impl Fn(ConfigExp) + Send + Sync + 'static) {
*self.messages().config_exp_callback.lock().unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_exp_callback(&self) {
*self.messages().config_exp_callback.lock().unwrap() = None;
}
pub fn set_config_amp_sweep_callback(
&self,
cb: impl Fn(ConfigAmpSweep) + Send + Sync + 'static,
) {
*self.messages().config_amp_sweep_callback.lock().unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_amp_sweep_callback(&self) {
*self.messages().config_amp_sweep_callback.lock().unwrap() = None;
}
pub fn set_config_amp_sweep_exp_callback(
&self,
cb: impl Fn(ConfigAmpSweepExp) + Send + Sync + 'static,
) {
*self
.messages()
.config_amp_sweep_exp_callback
.lock()
.unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_amp_sweep_exp_callback(&self) {
*self
.messages()
.config_amp_sweep_exp_callback
.lock()
.unwrap() = None;
}
pub fn set_config_cw_callback(&self, cb: impl Fn(ConfigCw) + Send + Sync + 'static) {
*self.messages().config_cw_callback.lock().unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_cw_callback(&self) {
*self.messages().config_cw_callback.lock().unwrap() = None;
}
pub fn set_config_cw_exp_callback(&self, cb: impl Fn(ConfigCwExp) + Send + Sync + 'static) {
*self.messages().config_cw_exp_callback.lock().unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_cw_exp_callback(&self) {
*self.messages().config_cw_exp_callback.lock().unwrap() = None;
}
pub fn set_config_freq_sweep_callback(
&self,
cb: impl Fn(ConfigFreqSweep) + Send + Sync + 'static,
) {
*self.messages().config_freq_sweep_callback.lock().unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_freq_sweep_callback(&self) {
*self.messages().config_freq_sweep_callback.lock().unwrap() = None;
}
pub fn set_config_freq_sweep_exp_callback(
&self,
cb: impl Fn(ConfigFreqSweepExp) + Send + Sync + 'static,
) {
*self
.messages()
.config_freq_sweep_exp_callback
.lock()
.unwrap() = Some(Arc::new(Box::new(cb)));
}
pub fn remove_config_freq_sweep_exp_callback(&self) {
*self
.messages()
.config_freq_sweep_exp_callback
.lock()
.unwrap() = None;
}
pub fn rf_power_on(&self) -> io::Result<()> {
self.send_command(super::Command::RfPowerOn)
}
pub fn rf_power_off(&self) -> io::Result<()> {
self.send_command(super::Command::RfPowerOff)
}
fn validate_command_frequency(name: &str, frequency: Frequency) -> Result<()> {
if frequency.as_khz() > Self::MAX_7_DIGIT_KHZ {
return Err(Error::InvalidInput(format!(
"{name} must be 9,999,999 kHz or less"
)));
}
Ok(())
}
fn validate_sweep_steps(sweep_steps: u16) -> Result<()> {
if sweep_steps > Self::MAX_4_DIGIT_DECIMAL {
return Err(Error::InvalidInput(
"sweep_steps must be 9,999 or less".to_string(),
));
}
Ok(())
}
fn validate_step_delay(step_delay: Duration) -> Result<()> {
if step_delay.as_millis() > Self::MAX_5_DIGIT_MILLIS {
return Err(Error::InvalidInput(
"step_delay must be 99,999 ms or less".to_string(),
));
}
Ok(())
}
fn validate_power_db(name: &str, power_db: f64) -> Result<()> {
if !power_db.is_finite() || !(Self::MIN_POWER_DB..=Self::MAX_POWER_DB).contains(&power_db) {
return Err(Error::InvalidInput(format!(
"{name} must be a finite value from -99.9 to 99.9"
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_command_frequency_accepts_largest_7_digit_khz_value() {
assert!(
SignalGenerator::validate_command_frequency(
"frequency",
Frequency::from_khz(9_999_999)
)
.is_ok()
);
}
#[test]
fn validate_command_frequency_rejects_8_digit_khz_value() {
assert!(
SignalGenerator::validate_command_frequency(
"frequency",
Frequency::from_khz(10_000_000)
)
.is_err()
);
}
#[test]
fn validate_sweep_steps_accepts_largest_4_digit_value() {
assert!(SignalGenerator::validate_sweep_steps(9_999).is_ok());
}
#[test]
fn validate_sweep_steps_rejects_5_digit_value() {
assert!(SignalGenerator::validate_sweep_steps(10_000).is_err());
}
#[test]
fn validate_step_delay_accepts_largest_5_digit_millisecond_value() {
assert!(SignalGenerator::validate_step_delay(Duration::from_millis(99_999)).is_ok());
}
#[test]
fn validate_step_delay_rejects_6_digit_millisecond_value() {
assert!(SignalGenerator::validate_step_delay(Duration::from_millis(100_000)).is_err());
}
#[test]
fn validate_power_db_accepts_boundary_values() {
assert!(SignalGenerator::validate_power_db("power_dbm", -99.9).is_ok());
assert!(SignalGenerator::validate_power_db("power_dbm", 99.9).is_ok());
}
#[test]
fn validate_power_db_rejects_out_of_range_and_non_finite_values() {
assert!(SignalGenerator::validate_power_db("power_dbm", -100.0).is_err());
assert!(SignalGenerator::validate_power_db("power_dbm", 100.0).is_err());
assert!(SignalGenerator::validate_power_db("power_dbm", f64::NAN).is_err());
assert!(SignalGenerator::validate_power_db("power_dbm", f64::INFINITY).is_err());
}
}
#[derive(Default)]
struct MessageContainer {
pub(crate) config: (Mutex<Option<Config>>, Condvar),
pub(crate) config_callback: Mutex<ConfigCallback<Config>>,
pub(crate) config_exp: (Mutex<Option<ConfigExp>>, Condvar),
pub(crate) config_exp_callback: Mutex<ConfigCallback<ConfigExp>>,
pub(crate) config_amp_sweep: (Mutex<Option<ConfigAmpSweep>>, Condvar),
pub(crate) config_amp_sweep_callback: Mutex<ConfigCallback<ConfigAmpSweep>>,
pub(crate) config_amp_sweep_exp: (Mutex<Option<ConfigAmpSweepExp>>, Condvar),
pub(crate) config_amp_sweep_exp_callback: Mutex<ConfigCallback<ConfigAmpSweepExp>>,
pub(crate) config_cw: (Mutex<Option<ConfigCw>>, Condvar),
pub(crate) config_cw_callback: Mutex<ConfigCallback<ConfigCw>>,
pub(crate) config_cw_exp: (Mutex<Option<ConfigCwExp>>, Condvar),
pub(crate) config_cw_exp_callback: Mutex<ConfigCallback<ConfigCwExp>>,
pub(crate) config_freq_sweep: (Mutex<Option<ConfigFreqSweep>>, Condvar),
pub(crate) config_freq_sweep_callback: Mutex<ConfigCallback<ConfigFreqSweep>>,
pub(crate) config_freq_sweep_exp: (Mutex<Option<ConfigFreqSweepExp>>, Condvar),
pub(crate) config_freq_sweep_exp_callback: Mutex<ConfigCallback<ConfigFreqSweepExp>>,
pub(crate) screen_data: (Mutex<Option<ScreenData>>, Condvar),
pub(crate) temperature: (Mutex<Option<Temperature>>, Condvar),
pub(crate) setup_info: (Mutex<Option<SetupInfo<Model>>>, Condvar),
pub(crate) serial_number: (Mutex<Option<SerialNumber>>, Condvar),
}
impl crate::common::MessageContainer for MessageContainer {
type Message = super::Message;
fn cache_message(&self, message: Self::Message) {
match message {
Self::Message::Config(config) => {
*self.config.0.lock().unwrap() = Some(config);
self.config.1.notify_one();
if let Some(cb) = self.config_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigAmpSweep(config) => {
*self.config_amp_sweep.0.lock().unwrap() = Some(config);
self.config_amp_sweep.1.notify_one();
if let Some(cb) = self.config_amp_sweep_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigCw(config) => {
*self.config_cw.0.lock().unwrap() = Some(config);
self.config_cw.1.notify_one();
if let Some(cb) = self.config_cw_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigFreqSweep(config) => {
*self.config_freq_sweep.0.lock().unwrap() = Some(config);
self.config_freq_sweep.1.notify_one();
if let Some(cb) = self.config_freq_sweep_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigExp(config) => {
*self.config_exp.0.lock().unwrap() = Some(config);
self.config_exp.1.notify_one();
if let Some(cb) = self.config_exp_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigAmpSweepExp(config) => {
*self.config_amp_sweep_exp.0.lock().unwrap() = Some(config);
self.config_amp_sweep_exp.1.notify_one();
if let Some(cb) = self.config_amp_sweep_exp_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigCwExp(config) => {
*self.config_cw_exp.0.lock().unwrap() = Some(config);
self.config_cw_exp.1.notify_one();
if let Some(cb) = self.config_cw_exp_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ConfigFreqSweepExp(config) => {
*self.config_freq_sweep_exp.0.lock().unwrap() = Some(config);
self.config_freq_sweep_exp.1.notify_one();
if let Some(cb) = self.config_freq_sweep_exp_callback.lock().unwrap().clone() {
thread::spawn(move || {
cb(config);
});
}
}
Self::Message::ScreenData(screen_data) => {
*self.screen_data.0.lock().unwrap() = Some(screen_data);
self.screen_data.1.notify_one();
}
Self::Message::SerialNumber(serial_number) => {
*self.serial_number.0.lock().unwrap() = Some(serial_number);
self.serial_number.1.notify_one();
}
Self::Message::SetupInfo(setup_info) => {
*self.setup_info.0.lock().unwrap() = Some(setup_info);
self.setup_info.1.notify_one();
}
Self::Message::Temperature(temperature) => {
*self.temperature.0.lock().unwrap() = Some(temperature);
self.temperature.1.notify_one();
}
}
}
fn wait_for_device_info(&self) -> ConnectionResult<()> {
let (config_lock, config_cvar) = &self.config;
let (setup_info_lock, setup_info_cvar) = &self.setup_info;
if config_lock.lock().unwrap().is_some() && setup_info_lock.lock().unwrap().is_some() {
return Ok(());
}
if config_cvar
.wait_timeout_while(
config_lock.lock().unwrap(),
RECEIVE_INITIAL_DEVICE_INFO_TIMEOUT,
|config| config.is_none(),
)
.unwrap()
.0
.is_some()
&& setup_info_cvar
.wait_timeout_while(
setup_info_lock.lock().unwrap(),
RECEIVE_INITIAL_DEVICE_INFO_TIMEOUT,
|setup_info| setup_info.is_none(),
)
.unwrap()
.0
.is_some()
{
Ok(())
} else {
Err(ConnectionError::DeviceInfoNotReceived)
}
}
}
impl Debug for MessageContainer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MessageContainer")
.field("config", &self.config.0.lock().unwrap())
.field("config_exp", &self.config_exp.0.lock().unwrap())
.field("config_cw", &self.config_cw.0.lock().unwrap())
.field("config_cw_exp", &self.config_cw_exp.0.lock().unwrap())
.field("config_amp_sweep", &self.config_amp_sweep.0.lock().unwrap())
.field(
"config_amp_sweep_exp",
&self.config_amp_sweep_exp.0.lock().unwrap(),
)
.field(
"config_freq_sweep",
&self.config_freq_sweep.0.lock().unwrap(),
)
.field(
"config_freq_sweep_exp",
&self.config_freq_sweep_exp.0.lock().unwrap(),
)
.field("screen_data", &self.screen_data.0.lock().unwrap())
.field("temperature", &self.temperature.0.lock().unwrap())
.field("setup_info", &self.setup_info.0.lock().unwrap())
.field("serial_number", &self.serial_number.0.lock().unwrap())
.finish()
}
}