use std::time::Duration;
use serde::{Deserialize, Serialize};
use super::super::ScpiTcpConfig;
pub(super) const CHANNEL_COUNT: usize = 2;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum Waveform {
Sine,
Square,
Ramp,
Pulse,
Noise,
Dc,
}
impl Waveform {
pub(super) fn scpi(self) -> &'static str {
match self {
Self::Sine => "SINE",
Self::Square => "SQUARE",
Self::Ramp => "RAMP",
Self::Pulse => "PULSE",
Self::Noise => "NOISE",
Self::Dc => "DC",
}
}
pub(super) fn uses_frequency(self) -> bool {
!matches!(self, Self::Noise | Self::Dc)
}
pub(super) fn uses_amplitude(self) -> bool {
!matches!(self, Self::Noise | Self::Dc)
}
pub(super) fn uses_offset(self) -> bool {
!matches!(self, Self::Noise)
}
pub(super) fn uses_duty(self) -> bool {
matches!(self, Self::Square | Self::Pulse)
}
pub(super) fn uses_phase(self) -> bool {
matches!(self, Self::Sine | Self::Square | Self::Ramp)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum Load {
HighImpedance,
Ohms(u32),
}
impl Load {
fn validate(self) -> Result<(), String> {
match self {
Self::HighImpedance => Ok(()),
Self::Ohms(ohms) if (50..=100_000).contains(&ohms) => Ok(()),
Self::Ohms(ohms) => Err(format!("load {ohms} ohms is outside 50..=100000")),
}
}
pub(super) fn scpi(self) -> String {
match self {
Self::HighImpedance => "100000".to_owned(),
Self::Ohms(ohms) => ohms.to_string(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ChannelConfig {
pub waveform: Waveform,
pub amplitude_vpp: f64,
pub load: Load,
pub frequency_hz: (f64, f64),
pub offset_voltage_v: (f64, f64),
pub pulse_duty_cycle: (f64, f64),
pub phase_deg: (f64, f64),
pub stdev: (f64, f64),
}
impl Default for ChannelConfig {
fn default() -> Self {
Self {
waveform: Waveform::Dc,
amplitude_vpp: 1.0,
load: Load::HighImpedance,
frequency_hz: (1.0e-6, 40.0e6),
offset_voltage_v: (-10.0, 10.0),
pulse_duty_cycle: (0.0, 1.0),
phase_deg: (0.0, 360.0),
stdev: (0.0, 10.0),
}
}
}
impl ChannelConfig {
fn validate(&self, channel: usize) -> Result<(), String> {
self.load
.validate()
.map_err(|err| format!("channel {channel}: {err}"))?;
if self.waveform.uses_amplitude()
&& (self.amplitude_vpp.is_nan()
|| self.amplitude_vpp <= 0.0
|| self.amplitude_vpp == f64::INFINITY)
{
return Err(format!(
"channel {channel}: amplitude_vpp must be finite and positive"
));
}
validate_range("frequency_hz", self.frequency_hz, channel)?;
validate_range("offset_voltage_v", self.offset_voltage_v, channel)?;
validate_range("pulse_duty_cycle", self.pulse_duty_cycle, channel)?;
validate_range("phase_deg", self.phase_deg, channel)?;
validate_range("stdev", self.stdev, channel)?;
if self.pulse_duty_cycle.0 < 0.0 || self.pulse_duty_cycle.1 > 1.0 {
return Err(format!(
"channel {channel}: pulse_duty_cycle range must be within 0..=1"
));
}
Ok(())
}
}
fn validate_range(name: &str, range: (f64, f64), channel: usize) -> Result<(), String> {
if !range.0.is_nan()
&& !range.1.is_nan()
&& range.0 != f64::NEG_INFINITY
&& range.1 != f64::INFINITY
&& range.0 <= range.1
{
Ok(())
} else {
Err(format!(
"channel {channel}: {name} range must contain finite ascending bounds"
))
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Config {
pub connection: ScpiTcpConfig,
pub channels: [ChannelConfig; CHANNEL_COUNT],
}
impl Config {
pub fn new(host: impl Into<String>, serial_number: u64) -> Self {
let mut connection = ScpiTcpConfig::new(host, serial_number, "SIGLENT", "SDG2042X");
connection.read_timeout = Duration::from_secs(1);
Self {
connection,
channels: std::array::from_fn(|_| ChannelConfig::default()),
}
}
pub(super) fn validate(&self) -> Result<(), String> {
self.connection.validate()?;
for (index, channel) in self.channels.iter().enumerate() {
channel.validate(index + 1)?;
}
Ok(())
}
}