Skip to content
Snippets Groups Projects
mogdriver.rs 27.84 KiB
//! Structures for interacting with MOGLabs devices over a serial connection.
//!
//! Based off the official source with modifications for running on non-Windows
//! machines. See Appendix C of the manual for more details on individual
//! commands.

use std::{
    fmt,
    io::{ BufReader, BufRead, Read, Write },
    path::Path,
    str::FromStr,
    time::Duration,
};
use byteorder::{ ByteOrder, LittleEndian };
use serial2::{
    SerialPort,
    IntoSettings,
};
pub use serial2::{
    CharSize,
    Parity,
    StopBits,
};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum MOGError {
    #[error("couldn't connect to MOGLabs device: {0}")]
    FailedConnect(String),

    #[error("couldn't set read timeout: {0}")]
    FailedSetReadTimeout(String),

    #[error("couldn't set write timeout: {0}")]
    FailedSetWriteTimeout(String),

    #[error("invalid channel number {0}")]
    InvalidChannel(usize),

    #[error("binary response has unexpected length")]
    BinUnexpectedLen,

    #[error("invalid table index {0}")]
    InvalidTableIndex(usize),

    #[error("encountered invalid channel mode response: '{0}'")]
    InvalidRespChannelMode(String),

    #[error("encountered invalid output mode response: '{0}'")]
    InvalidRespOutputMode(String),

    #[error("encountered invalid modulation mode response: '{0}'")]
    InvalidRespModulationMode(String),

    #[error("encountered invalid numerical response: '{0}'")]
    InvalidRespNumber(String),

    #[error("frequency must be in the range [10, 200]")]
    FrequencyOutOfRange,

    #[error("device error: {0}")]
    MOGDeviceError(String),

    #[error("serial communication error: {0}")]
    SerialCommunicationError(#[from] std::io::Error),
}
pub type MOGResult<T> = Result<T, MOGError>;

pub const CRLF: &str = "\r\n";
/// Settings for a serial connection.
#[derive(Copy, Clone, Debug)]
pub struct SerialSettings {
    pub baud_rate: u32,
    pub char_size: CharSize,
    pub parity: Parity,
    pub stop_bits: StopBits,
    pub timeout_sec: f64,
    pub timeout_retry: usize,
}

impl Default for SerialSettings {
    fn default() -> Self {
        return Self {
            baud_rate: 115200,
            char_size: CharSize::Bits8,
            parity: Parity::None,
            stop_bits: StopBits::One,
            timeout_sec: 1.0,
            timeout_retry: 0,
        };
    }
}

impl IntoSettings for SerialSettings {
    fn apply_to_settings(self, settings: &mut serial2::Settings)
        -> Result<(), std::io::Error>
    {
        settings.set_baud_rate(self.baud_rate)?;
        settings.set_char_size(self.char_size);
        settings.set_parity(self.parity);
        settings.set_stop_bits(self.stop_bits);
        return Ok(());
    }
}

/// Main driver type to handle communication with a MOGLabs Quad RF driver over
/// USB.
///
/// Provided methods are tailored specifically for a Quad RF driver, but this
/// type can in principle handle other MOGLabs devices as well.
pub struct MOGDriver {
    dev: BufReader<SerialPort>,
    timeout_retry: usize,
}

impl fmt::Debug for MOGDriver {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        return write!(f, "MOGDriver");
    }
}

impl MOGDriver {
    /// Connect to a serial device at the given address, creating a new instance
    /// of `Self`.
    pub fn connect<P>(addr: P, settings: SerialSettings) -> MOGResult<Self>
    where P: AsRef<Path>
    {
        let timeout_sec: f64 = settings.timeout_sec;
        let timeout_retry = settings.timeout_retry;
        let mut dev
            = SerialPort::open(addr, settings)
            .map_err(|e| MOGError::FailedConnect(e.to_string()))?;
        dev.set_read_timeout(std::time::Duration::from_secs_f64(timeout_sec))
            .map_err(|e| MOGError::FailedSetReadTimeout(e.to_string()))?;
        dev.set_write_timeout(std::time::Duration::from_secs_f64(timeout_sec))
            .map_err(|e| MOGError::FailedSetWriteTimeout(e.to_string()))?;
        return Ok(Self { dev: BufReader::new(dev), timeout_retry });
    }
    /// Disconnect, dropping `self`.
    pub fn disconnect(self) { }

    fn check_channel(ch: usize) -> MOGResult<usize> {
        return (1..=4).contains(&ch).then_some(ch)
            .ok_or(MOGError::InvalidChannel(ch));
    }

    fn trim_ok(s: &str) -> String {
        return if s.starts_with("OK:") {
            s[..3].trim().to_string()
        } else if s.starts_with("OK") {
            s[..2].trim().to_string()
        } else {
            s.trim().to_string()
        };
    }

    /// Get the read timeout time, in seconds, of the serial connection.
    pub fn get_read_timeout(&self) -> MOGResult<f64> {
        return Ok(
            self.dev.get_ref().get_read_timeout()
                .map(|dur| dur.as_secs_f64())?
        );
    }

    /// Get the write timeout time, in seconds, of the serial connection.
    pub fn get_write_timeout(&self) -> MOGResult<f64> {
        return Ok(
            self.dev.get_ref().get_write_timeout()
                .map(|dur| dur.as_secs_f64())?
        );
    }

    /// Set the read timeout time, in seconds, of the serial connection.
    pub fn set_read_timeout(&mut self, timeout_sec: f64)
        -> MOGResult<&mut Self>
    {
        self.dev.get_mut()
            .set_read_timeout(Duration::from_secs_f64(timeout_sec))
            .map_err(|e| MOGError::FailedSetReadTimeout(e.to_string()))?;
        return Ok(self);
    }

    /// Set the write timeout time, in seconds, of the serial connection.
    pub fn set_write_timeout(&mut self, timeout_sec: f64)
        -> MOGResult<&mut Self>
    {
        self.dev.get_mut()
            .set_write_timeout(Duration::from_secs_f64(timeout_sec))
            .map_err(|e| MOGError::FailedSetWriteTimeout(e.to_string()))?;
        return Ok(self);
    }

    /// Send a raw string of bytes to the device.
    pub fn send_raw(&mut self, cmd: &[u8]) -> MOGResult<&mut Self> {
        self.dev.get_mut().write_all(cmd)?;
        return Ok(self);
    }

    /// Send `cmd` to the device, appending newline if not already present.
    pub fn send(&mut self, cmd: &str) -> MOGResult<&mut Self> {
        let mut cmd: String = cmd.to_string();
        if !cmd.ends_with(CRLF) { cmd += CRLF; }
        return self.send_raw(cmd.as_bytes());
    }

    /// Receive exactly `nbytes` from the device, blocking until all bytes have
    /// been read or an error occurs.
    pub fn recv_raw(&mut self, nbytes: usize) -> MOGResult<Vec<u8>> {
        let mut buffer: Vec<u8> = vec![0; nbytes];
        let read_bytes: usize = self.dev.read(&mut buffer)?;
        return if read_bytes == nbytes {
            Ok(buffer)
        } else {
            Err(MOGError::BinUnexpectedLen)
        };
    }

    /// Read bytes from the device into a `String` until newline or EOF is
    /// reached. The newline character will be included in the returned string.
    pub fn recv(&mut self) -> MOGResult<String> {
        let mut buffer = String::new();
        self.dev.read_line(&mut buffer)?;
        return Ok(buffer);
    }

    /// Flush and return all available bytes from the device's output queue.
    pub fn flush(&mut self) -> MOGResult<Vec<u8>> {
        let mut buffer: Vec<u8> = Vec::new();
        self.dev.read_to_end(&mut buffer)?;
        return Ok(buffer);
    }

    /// [`send`][Self::send] followed by [`recv`][Self::recv], returning the
    /// response as a string. This method will also call [`flush`][Self::flush]
    /// before sending the `cmd` and return a [`MOGError::MOGDeviceError`] if
    /// the response is an error message from the device. Leading and trailing
    /// whitespace characters are removed.
    pub fn ask(&mut self, cmd: &str) -> MOGResult<String> {
        // self.flush()?;
        self.send(cmd)?;
        let response: String = self.recv()?;
        return if response.starts_with("ERR:") {
            Err(MOGError::MOGDeviceError(response[..4].trim().to_string()))
        } else {
            Ok(response.trim().to_string())
        };
    }

    /// Like [`ask`][Self::ask], but for a command that returns a binary
    /// response.
    pub fn ask_bin(&mut self, cmd: &str) -> MOGResult<Vec<u8>> {
        // self.flush()?;
        self.send(cmd)?;
        let head: Vec<u8> = self.recv_raw(4)?;
        if head == b"ERR:" {
            let error: String = self.recv()?;
            return Err(MOGError::MOGDeviceError(error.trim().to_string()));
        }
        let datalen: usize = LittleEndian::read_u32(&head) as usize;
        let data: Vec<u8> = self.recv_raw(datalen)?;
        return Ok(data);
    }

    /// Like [`ask`][Self::ask], but will retry [`self.timeout_retry`] times if
    /// the response is a timeout error from the device (note that this is
    /// distinct from an I/O error reporting a timeout in a read/write operation
    /// to the device).
    pub fn cmd(&mut self, cmd: &str) -> MOGResult<String> {
        return match self.ask(cmd) {
            Ok(resp) => Ok(resp),
            Err(MOGError::MOGDeviceError(err)) => {
                if err.contains("Reached timeout") && self.timeout_retry > 0 {
                    for _ in 0..self.timeout_retry {
                        match self.ask(cmd) {
                            Ok(resp) => { return Ok(resp); },
                            Err(MOGError::MOGDeviceError(err)) => {
                                if err.contains("Reached timeout") {
                                    continue;
                                } else {
                                    return Err(MOGError::MOGDeviceError(err));
                                }
                            },
                            err => { return err; },
                        }
                    }
                }
                Err(MOGError::MOGDeviceError(err))
            },
            err => err,
        }
    }

    /// Issue a reboot command to the device and close the connection.
    pub fn reboot(mut self) -> MOGResult<()> {
        self.send("REBOOT")?;
        self.disconnect();
        return Ok(());
    }

    /// Report information about the device.
    pub fn get_info(&mut self) -> MOGResult<String> { self.cmd("INFO") }

    /// Report versions of firmware currently running on the device.
    pub fn get_version(&mut self) -> MOGResult<String> { self.cmd("VERSION") }

    /// Report measured temperatures and fan speeds.
    pub fn get_temp(&mut self) -> MOGResult<String> { self.cmd("TEMP") }

    /// Report the current operational status of a channel.
    pub fn get_status(&mut self, ch: usize) -> MOGResult<String> {
        Self::check_channel(ch)?;
        return self.cmd(&format!("STATUS,{}", ch));
    }

    /// Get the the current operational mode of a channel.
    pub fn get_mode(&mut self, ch: usize) -> MOGResult<ChannelMode> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("MODE,{}", ch))?;
        return Self::trim_ok(&response).parse();
    }

    /// Set the operational mode of a channel.
    ///
    /// Automatically disables output on the channel. See the manual for details
    /// on the available modes.
    pub fn set_mode(&mut self, ch: usize, mode: ChannelMode)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        self.cmd(&format!("MODE,{},{:?}", ch, mode))?;
        return Ok(self);
    }

    /// Set the output of a channel.
    pub fn set_output(&mut self, ch: usize, onoff: bool)
        -> MOGResult<&mut Self>
    {
        return self.set_output_type(ch, onoff, OutputMode::ALL);
    }

    /// Set the output of a specific mode on a channel.
    pub fn set_output_type(&mut self, ch: usize, onoff: bool, mode: OutputMode)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        let state: &str = if onoff { "ON" } else { "OFF" };
        self.cmd(&format!("{},{},{:?}", state, ch, mode))?;
        return Ok(self);
    }

    /// Pause the microcontroller operation for some time, in milliseconds,
    /// during which it will be unresponsive.
    pub fn sleep(&mut self, dt_ms: f64) -> MOGResult<&mut Self> {
        self.cmd(&format!("SLEEP,{}", dt_ms))?;
        return Ok(self);
    }

    /// Get the current frequency, in megahertz, of the output from a channel.
    pub fn get_frequency(&mut self, ch: usize) -> MOGResult<f64> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("FREQ,{}", ch))?;
        return Self::trim_ok(&response).parse()
            .map_err(|_| MOGError::InvalidRespNumber(response));
    }

    /// Set the frequency, in megahertz, of the output from a channel, as
    /// permitted by the DDS's 32-bit frequency resolution. The frequency must
    /// be between 10 and 200 MHz.
    pub fn set_frequency(&mut self, ch: usize, freq: f64)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        (10.0_f64..=200.0).contains(&freq).then_some(())
            .ok_or(MOGError::FrequencyOutOfRange)?;
        self.cmd(&format!("FREQ,{},{}", ch, freq))?;
        return Ok(self);
    }

    /// Get the power, in dBm, of the output from a channel, as computed using
    /// the factory calibration.
    pub fn get_power(&mut self, ch: usize) -> MOGResult<f64> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("POW,{}", ch))?;
        return Self::trim_ok(&response).parse()
            .map_err(|_| MOGError::InvalidRespNumber(response));
    }

    /// Set the power, in dBm, of the output from a channel, as computed using
    /// the factory calibration. Power is limited to the value set by the
    /// [`set_limit`][Self::set_limit] command.
    pub fn set_power(&mut self, ch: usize, pow: f64) -> MOGResult<&mut Self> {
        Self::check_channel(ch)?;
        self.cmd(&format!("POW,{},{}", ch, pow))?;
        return Ok(self);
    }

    /// Get the maximum RF power, in dBm, for a channel.
    pub fn get_limit(&mut self, ch: usize) -> MOGResult<f64> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("LIMIT,{}", ch))?;
        return Self::trim_ok(&response).parse()
            .map_err(|_| MOGError::InvalidRespNumber(response));
    }

    /// Set the maximum power, in dBm, for the specified channel. If the limit
    /// is set below the current power level, the current power is reduced to
    /// the limit.
    pub fn set_limit(&mut self, ch: usize, pow: f64) -> MOGResult<&mut Self> {
        Self::check_channel(ch)?;
        self.cmd(&format!("POW,{},{}", ch, pow))?;
        return Ok(self);
    }

    /// Get the current phase offset, in degreed, of a channel.
    pub fn get_phase(&mut self, ch: usize) -> MOGResult<f64> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("PHASE,{}", ch))?;
        return Self::trim_ok(&response).parse()
            .map_err(|_| MOGError::InvalidRespNumber(response));
    }

    /// Set the phase offset, in degrees, of a channel.
    pub fn set_phase(&mut self, ch: usize, phase: f64) -> MOGResult<&mut Self> {
        Self::check_channel(ch)?;
        self.cmd(&format!("PHASE,{},{}", ch, phase))?;
        return Ok(self);
    }

    /// Simultaneously resets the phase accumulators of the specified channels.
    /// If no channels are specified, all channels are reset.
    pub fn align_phase<I>(&mut self, ch_iter: I) -> MOGResult<&mut Self>
    where I: IntoIterator<Item = usize>
    {
        let ch_list: String
            = ch_iter.into_iter()
            .map(|ch| Self::check_channel(ch).map(|ch| ch.to_string()))
            .collect::<MOGResult<Vec<String>>>()?
            .join(",");
        let some_ch: &str = if !ch_list.is_empty() { "," } else { "" };
        self.cmd(&format!("ALIGNPH{}{}", some_ch, ch_list))?;
        return Ok(self);
    }

    /// Enable or disable automatic phase alignment on the specified channels,
    /// which will be performed after any [`set_frequency`][Self::set_frequency]
    /// command is issued. If no channels are specified, apply to all channels.
    pub fn set_phase_reset<I>(&mut self, ch_iter: I, onoff: bool)
        -> MOGResult<&mut Self>
    where I: IntoIterator<Item = usize>
    {
        let ch_list: String
            = ch_iter.into_iter()
            .map(|ch| Self::check_channel(ch).map(|ch| ch.to_string()))
            .collect::<MOGResult<Vec<String>>>()?
            .join(",");
        let some_ch: &str = if !ch_list.is_empty() { "," } else { "" };
        let state: &str = if onoff { "ON" } else { "OFF" };
        self.cmd(&format!("PHRESET,{}{}{}", state, some_ch, ch_list))?;
        return Ok(self);
    }

    /// Get the current modulation mode on a channel.
    pub fn get_modulation(&mut self, ch: usize) -> MOGResult<String> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("MOD,{}", ch))?;
        return Ok(response);
        // return Self::trim_ok(&response).parse();
    }

    /// Set the activation and gain for a modulation mode on a channel.
    pub fn set_modulation(
        &mut self,
        ch: usize,
        mode: ModulationMode,
        gain: Option<f64>,
    ) -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        let state: &str = if gain.is_some() { "ON" } else { "OFF" };
        let command: String
            = if let Some(g) = gain {
                format!("MOD,{},{:?},{},{}", ch, mode, state, g)
            } else {
                format!("MOD,{},{:?},{}", ch, mode, state)
            };
        self.cmd(&command)?;
        return Ok(self);
    }

    /// Set the primary and secondary modulation types.
    pub fn set_modulation_dual(
        &mut self,
        ch: usize,
        mode1: ModulationMode,
        mode2: ModulationMode,
    ) -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        self.cmd(&format!("MOD,{},{:?},{:?}", ch, mode1, mode2))?;
        return Ok(self);
    }

    /// Get the current clock source.
    pub fn get_clock_source(&mut self) -> MOGResult<String> {
        return self.cmd("CLKSRC");
    }

    /// Set the clock source.
    pub fn set_clock_source(&mut self, source: ClockSource)
        -> MOGResult<&mut Self>
    {
        if let ClockSource::EXT(freq) = source {
            self.cmd(&format!("CLKSRC,EXT,{}", freq))?;
        } else {
            self.cmd("CLKSRC,INT")?;
        }
        return Ok(self);
    }

    /// Get the current execution status of the table on a channel.
    pub fn get_table_status(&mut self, ch: usize) -> MOGResult<String> {
        Self::check_channel(ch)?;
        return self.cmd(&format!("TABLE,STATUS,{}", ch));
    }

    /// Stop execution of and clear all entries from the table on a channel.
    pub fn table_clear(&mut self, ch: usize) -> MOGResult<&mut Self> {
        Self::check_channel(ch)?;
        self.cmd(&format!("TABLE,CLEAR,{}", ch))?;
        return Ok(self);
    }

    /// Get the `idx`-th table entry on a channel. Indexing starts from 1.
    pub fn get_table_entry(&mut self, ch: usize, idx: usize)
        -> MOGResult<String>
    {
        Self::check_channel(ch)?;
        (1..=8192).contains(&idx).then_some(())
            .ok_or(MOGError::InvalidTableIndex(idx))?;
        return self.cmd(&format!("TABLE,ENTRY,{},{}", ch, idx));
    }

    /// Set the `idx`-th table entry on a channel. `freq` is expected in
    /// megahertz, `power` in dBm, `phase` in degrees, and `dur` in seconds.
    /// Note that durations are discretized to multiples of 5 μs, so `dur` will
    /// be rounded down to the nearest multiple. Indexing starts from 1.
    pub fn set_table_entry(&mut self, ch: usize, idx: usize, entry: TableEntry)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        (1..=8192).contains(&idx).then_some(())
            .ok_or(MOGError::InvalidTableIndex(idx))?;
        let TableEntry { freq, pow, phase, dur, flags } = entry;
        let flags: &str = match flags {
            TableEntryFlags::SIG => "SIG",
            TableEntryFlags::POW => "POW",
            TableEntryFlags::SIGPOW => "SIG,POW",
        };
        let mut dursteps: usize = (1e6 * dur.abs() / 5.0).floor() as usize;
        while dursteps >= 65535 {
            self.cmd(&format!(
                "TABLE,ENTRY,{},{},{},{},{},65535,{}",
                ch, idx, freq, pow, phase, flags,
            ))?;
            dursteps -= 65535;
        }
        if dursteps > 0 {
            self.cmd(&format!(
                "TABLE,ENTRY,{}{}{}{}{}{}{}",
                ch, idx, freq, pow, phase, dursteps, flags,
            ))?;
        }
        return Ok(self);
    }

    /// Get the number of the last entry of the table on a channel.
    pub fn get_table_entries(&mut self, ch: usize) -> MOGResult<usize> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("TABLE,ENTRIES,{}", ch))?;
        return Self::trim_ok(&response).parse()
            .map_err(|_| MOGError::InvalidRespNumber(response));
    }

    /// Set the number of the last entry of the table on a channel.
    pub fn set_table_entries(&mut self, ch: usize, num: usize)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        self.cmd(&format!("TABLE,ENTRIES,{},{}", ch, num))?;
        return Ok(self);
    }

    /// Copy all table data from `ch1` to `ch2`.
    pub fn table_copy(&mut self, ch1: usize, ch2: usize)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch1)?;
        Self::check_channel(ch2)?;
        self.cmd(&format!("TABLE,APPEND,{},{}", ch1, ch2))?;
        return Ok(self);
    }

    /// Get the current name of the table on a channel.
    pub fn get_table_name(&mut self, ch: usize) -> MOGResult<String>
    {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("TABLE,NAME,{}", ch))?;
        return Ok(Self::trim_ok(&response));
    }

    /// Assign a character string to the table on a channel for identification
    /// purposes. Use quotation marks to preserve case.
    pub fn set_table_name(&mut self, ch: usize, name: &str)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        self.cmd(&format!("TABLE,NAME,{},{}", ch, name))?;
        return Ok(self);
    }

    /// Load the table(s) on the specified channel(s) for execution and begin
    /// watching for wither a software or hardware trigger.
    pub fn table_arm<I>(&mut self, ch_iter: I) -> MOGResult<&mut Self>
    where I: IntoIterator<Item = usize>
    {
        let ch_list: String
            = ch_iter.into_iter()
            .map(|ch| Self::check_channel(ch).map(|ch| ch.to_string()))
            .collect::<MOGResult<Vec<String>>>()?
            .join(",");
        let some_ch: &str = if !ch_list.is_empty() { "," } else { "" };
        self.cmd(&format!("TABLE,ARM,{}{}", some_ch, ch_list))?;
        return Ok(self);
    }

    /// Send a software trigger to begin execution of the table(s) on the
    /// specified channel(s).
    pub fn table_start<I>(&mut self, ch_iter: I) -> MOGResult<&mut Self>
    where I: IntoIterator<Item = usize>
    {
        let ch_list: String
            = ch_iter.into_iter()
            .map(|ch| Self::check_channel(ch).map(|ch| ch.to_string()))
            .collect::<MOGResult<Vec<String>>>()?
            .join(",");
        let some_ch: &str = if !ch_list.is_empty() { "," } else { "" };
        self.cmd(&format!("TABLE,START{}{}", some_ch, ch_list))?;
        return Ok(self);
    }

    /// Send a kill signal to halt execution of the table(s) on the specified
    /// channel(s) at the end of the current step.
    pub fn table_stop<I>(&mut self, ch_iter: I) -> MOGResult<&mut Self>
    where I: IntoIterator<Item = usize>
    {
        let ch_list: String
            = ch_iter.into_iter()
            .map(|ch| Self::check_channel(ch).map(|ch| ch.to_string()))
            .collect::<MOGResult<Vec<String>>>()?
            .join(",");
        let some_ch: &str = if !ch_list.is_empty() { "," } else { "" };
        self.cmd(&format!("TABLE,STOP{}{}", some_ch, ch_list))?;
        return Ok(self);
    }

    /// Get the current status of the table rearm switch for a channel.
    pub fn get_table_rearm(&mut self, ch: usize) -> MOGResult<String> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("TABLE,REARM,{}", ch))?;
        return Ok(Self::trim_ok(&response));
    }

    /// Enable or disable the automatic re-arming of the table on the specified
    /// channel upon its completion.
    pub fn set_table_rearm(&mut self, ch: usize, onoff: bool)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        let state: &str = if onoff { "ON" } else { "OFF" };
        self.cmd(&format!("TABLE,REARM,{},{}", ch, state))?;
        return Ok(self);
    }

    /// Get the current status of the table restart switch for the specified
    /// channel.
    pub fn get_table_restart(&mut self, ch: usize) -> MOGResult<String> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("TABLE,RESTART,{}", ch))?;
        return Ok(Self::trim_ok(&response));
    }

    /// Enable or disable the automatic restart of the table on the specified
    /// channel upon its completion.
    pub fn set_table_restart(&mut self, ch: usize, onoff: bool)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        let state: &str = if onoff { "ON" } else { "OFF" };
        self.cmd(&format!("TABLE,RESTART,{},{}", ch, state))?;
        return Ok(self);
    }

    /// Get the current TTL trigger edge for the table on the specified channel.
    pub fn get_table_edge(&mut self, ch: usize) -> MOGResult<String> {
        Self::check_channel(ch)?;
        let response: String = self.cmd(&format!("TABLE,EDGE,{}", ch))?;
        return Ok(Self::trim_ok(&response));
    }

    /// Set the TTL trigger edge for table execution on a channel.
    pub fn set_table_edge(&mut self, ch: usize, edge: TableTriggerEdge)
        -> MOGResult<&mut Self>
    {
        Self::check_channel(ch)?;
        self.cmd(&format!("TABLE,EDGE,{},{:?}", ch, edge))?;
        return Ok(self);
    }
}

/// Parameters of a table entry. For use with [`MOGDriver::set_table_entry`].
#[derive(Copy, Clone, Debug)]
pub struct TableEntry {
    /// Frequency in megahertz
    pub freq: f64,
    /// Power in dBm
    pub pow: f64,
    /// Phase in degrees
    pub phase: f64,
    /// Duration in seconds
    pub dur: f64,
    /// Entry type
    pub flags: TableEntryFlags,
}

/// For use with [`TableEntry`].
#[derive(Copy, Clone, Debug)]
pub enum TableEntryFlags {
    SIG,
    POW,
    SIGPOW,
}

/// Operational mode of a single channel. For use with [`MOGDriver::set_mode`].
#[derive(Copy, Clone, Debug)]
pub enum ChannelMode {
    NSB,
    NSA,
    TSB,
}

impl FromStr for ChannelMode {
    type Err = MOGError;

    fn from_str(s: &str) -> MOGResult<Self> {
        return match s {
            "NSB" => Ok(Self::NSB),
            "NSA" => Ok(Self::NSA),
            "TSB" => Ok(Self::TSB),
            x => Err(MOGError::InvalidRespChannelMode(x.to_string())),
        };
    }
}

/// Output mode of a single channel. For use with [`MOGDriver::set_output`].
#[derive(Copy, Clone, Debug)]
pub enum OutputMode {
    ALL,
    SIG,
    POW,
}

impl FromStr for OutputMode {
    type Err = MOGError;

    fn from_str(s: &str) -> MOGResult<Self> {
        return match s {
            "ALL" => Ok(Self::ALL),
            "SIG" => Ok(Self::SIG),
            "POW" => Ok(Self::POW),
            x => Err(MOGError::InvalidRespOutputMode(x.to_string())),
        };
    }
}

/// Modulation mode of a single channel. For use with
/// [`MOGDriver::set_modulation`].
#[derive(Copy, Clone, Debug)]
pub enum ModulationMode {
    NONE,
    FREQ,
    AMPL,
    PHAS,
}

impl FromStr for ModulationMode {
    type Err = MOGError;

    fn from_str(s: &str) -> MOGResult<Self> {
        return match s {
            "NONE" => Ok(Self::NONE),
            "FREQ" => Ok(Self::FREQ),
            "AMPL" => Ok(Self::AMPL),
            "PHAS" => Ok(Self::PHAS),
            x => Err(MOGError::InvalidRespModulationMode(x.to_string())),
        };
    }
}

/// Clock source. For use with [`MOGDriver::set_clock_source`].
#[derive(Copy, Clone, Debug)]
pub enum ClockSource {
    INT,
    EXT(f64),
}

/// Table execution trigger edge. For use with [`MOGDriver::set_table_edge`].
#[derive(Copy, Clone, Debug)]
pub enum TableTriggerEdge {
    RISING,
    FALLING,
}