rfe 0.1.0

Communicate with RF Explorer spectrum analyzers and signal generators over USB serial
Documentation
use super::{Attenuation, PowerLevel};
use crate::common::Frequency;
use std::{borrow::Cow, time::Duration};

#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum Command {
    RfPowerOn,
    RfPowerOff,
    StartAmpSweep {
        cw: Frequency,
        start_attenuation: Attenuation,
        start_power_level: PowerLevel,
        stop_attenuation: Attenuation,
        stop_power_level: PowerLevel,
        step_delay: Duration,
    },
    StartAmpSweepExp {
        cw: Frequency,
        start_power_dbm: f64,
        step_power_db: f64,
        stop_power_dbm: f64,
        step_delay: Duration,
    },
    StartCw {
        cw: Frequency,
        attenuation: Attenuation,
        power_level: PowerLevel,
    },
    StartCwExp {
        cw: Frequency,
        power_dbm: f64,
    },
    StartFreqSweep {
        start: Frequency,
        attenuation: Attenuation,
        power_level: PowerLevel,
        sweep_steps: u16,
        step: Frequency,
        step_delay: Duration,
    },
    StartFreqSweepExp {
        start: Frequency,
        power_dbm: f64,
        sweep_steps: u16,
        step: Frequency,
        step_delay: Duration,
    },
    StartTracking {
        start: Frequency,
        attenuation: Attenuation,
        power_level: PowerLevel,
        sweep_steps: u16,
        step: Frequency,
    },
    StartTrackingExp {
        start: Frequency,
        power_dbm: f64,
        sweep_steps: u16,
        step: Frequency,
    },
    TrackingStep(u16),
}

impl From<Command> for Cow<'static, [u8]> {
    fn from(command: Command) -> Cow<'static, [u8]> {
        match command {
            Command::RfPowerOn => Cow::Borrowed(&[b'#', 5, b'C', b'P', b'1'][..]),
            Command::RfPowerOff => Cow::Borrowed(&[b'#', 5, b'C', b'P', b'0']),
            Command::StartAmpSweep {
                cw,
                start_attenuation,
                start_power_level,
                stop_attenuation,
                stop_power_level,
                step_delay,
            } => {
                let mut command = vec![b'#', 28];
                command.extend(
                    format!(
                        "C3-A:{:07.0},{},{},{},{},{:05}",
                        cw.as_khz(),
                        u8::from(start_attenuation),
                        u8::from(start_power_level),
                        u8::from(stop_attenuation),
                        u8::from(stop_power_level),
                        step_delay.as_millis()
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::StartAmpSweepExp {
                cw,
                start_power_dbm,
                step_power_db,
                stop_power_dbm,
                step_delay,
            } => {
                let mut command = vec![b'#', 38];
                command.extend(
                    format!(
                        "C5-A:{:07.0},{:+05.1},{:+05.1},{:05.1},{:05}",
                        cw.as_khz(),
                        start_power_dbm,
                        step_power_db,
                        stop_power_dbm,
                        step_delay.as_millis()
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::StartCw {
                cw,
                attenuation,
                power_level,
            } => {
                let mut command = vec![b'#', 18];
                command.extend(
                    format!(
                        "C3-F:{:07.0},{},{}",
                        cw.as_khz(),
                        u8::from(attenuation),
                        u8::from(power_level)
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::StartCwExp { cw, power_dbm } => {
                let mut command = vec![b'#', 20];
                command.extend(format!("C5-F:{:07.0},{:+05.1}", cw.as_khz(), power_dbm).bytes());
                Cow::Owned(command)
            }
            Command::StartFreqSweep {
                start,
                attenuation,
                power_level,
                sweep_steps,
                step,
                step_delay,
            } => {
                let mut command = vec![b'#', 37];
                command.extend(
                    format!(
                        "C3-F:{:07.0},{},{},{:04},{:07.0},{:05}",
                        start.as_khz(),
                        u8::from(attenuation),
                        u8::from(power_level),
                        sweep_steps,
                        step.as_khz(),
                        step_delay.as_millis()
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::StartFreqSweepExp {
                start,
                power_dbm,
                sweep_steps,
                step,
                step_delay,
            } => {
                let mut command = vec![b'#', 39];
                command.extend(
                    format!(
                        "C5-F:{:07.0},{:+05.1},{:04},{:07.0},{:05}",
                        start.as_khz(),
                        power_dbm,
                        sweep_steps,
                        step.as_khz(),
                        step_delay.as_millis()
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::StartTracking {
                start,
                attenuation,
                power_level,
                sweep_steps,
                step,
            } => {
                let mut command = vec![b'#', 31];
                command.extend(
                    format!(
                        "C3-T:{:07.0},{},{},{:04},{:07.0}",
                        start.as_khz(),
                        u8::from(attenuation),
                        u8::from(power_level),
                        sweep_steps,
                        step.as_khz()
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::StartTrackingExp {
                start,
                power_dbm,
                sweep_steps,
                step,
            } => {
                let mut command = vec![b'#', 33];
                command.extend(
                    format!(
                        "C5-T:{:07.0},{:+05.1},{:04},{:07.0}",
                        start.as_khz(),
                        power_dbm,
                        sweep_steps,
                        step.as_khz()
                    )
                    .bytes(),
                );
                Cow::Owned(command)
            }
            Command::TrackingStep(steps) => {
                let steps_bytes = steps.to_be_bytes();
                Cow::Owned(vec![b'#', 5, b'k', steps_bytes[0], steps_bytes[1]])
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! assert_correct_size {
        ($command:expr) => {
            let command_bytes = Cow::from($command);
            assert_eq!(
                command_bytes[1],
                command_bytes.len() as u8,
                "Command: {:?}",
                String::from_utf8_lossy(&command_bytes)
            );
        };
    }

    #[test]
    fn correct_command_size_fields() {
        assert_correct_size!(Command::RfPowerOn);
        assert_correct_size!(Command::RfPowerOff);
        assert_correct_size!(Command::StartAmpSweep {
            cw: Frequency::from_khz(100_000),
            start_attenuation: Attenuation::On,
            start_power_level: PowerLevel::Low,
            stop_attenuation: Attenuation::Off,
            stop_power_level: PowerLevel::Highest,
            step_delay: Duration::from_secs(1),
        });
        assert_correct_size!(Command::StartAmpSweepExp {
            cw: Frequency::from_khz(100_000),
            start_power_dbm: -40.,
            step_power_db: 2.,
            stop_power_dbm: 0.,
            step_delay: Duration::from_secs(1),
        });
        assert_correct_size!(Command::StartCw {
            cw: Frequency::from_mhz(1),
            attenuation: Attenuation::Off,
            power_level: PowerLevel::Low
        });
        assert_correct_size!(Command::StartCwExp {
            cw: Frequency::from_ghz(1),
            power_dbm: 10.
        });
        assert_correct_size!(Command::StartFreqSweep {
            start: Frequency::from_ghz(1),
            attenuation: Attenuation::Off,
            power_level: PowerLevel::High,
            sweep_steps: 10,
            step: Frequency::from_mhz(1),
            step_delay: Duration::from_secs(2)
        });
        assert_correct_size!(Command::StartFreqSweepExp {
            start: Frequency::from_ghz(1),
            power_dbm: -10.,
            sweep_steps: 10,
            step: Frequency::from_mhz(1),
            step_delay: Duration::from_secs(2)
        });
        assert_correct_size!(Command::StartTracking {
            start: Frequency::from_ghz(1),
            attenuation: Attenuation::Off,
            power_level: PowerLevel::High,
            sweep_steps: 10,
            step: Frequency::from_mhz(1),
        });
        assert_correct_size!(Command::StartTrackingExp {
            start: Frequency::from_ghz(1),
            power_dbm: -10.,
            sweep_steps: 10,
            step: Frequency::from_mhz(1),
        });
        assert_correct_size!(Command::TrackingStep(10));
    }
}