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,
}