use lazy_static::lazy_static;
use regex::Regex;
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio::net::TcpStream;
use tokio::time;
#[derive(Debug, PartialEq, Eq)]
pub enum Mode {
USB,
LSB,
CW,
CWR,
RTTY,
RTTYR,
AM,
FM,
WFM,
AMS,
PKTLSB,
PKTUSB,
PKTFM,
ECSSUSB,
ECSSLSB,
FAX,
SAM,
SAL,
SAH,
DSB,
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Mode::USB => write!(f, "USB"),
Mode::LSB => write!(f, "LSB"),
Mode::CW => write!(f, "CW"),
Mode::CWR => write!(f, "CWR"),
Mode::RTTY => write!(f, "RTTY"),
Mode::RTTYR => write!(f, "RTTYR"),
Mode::AM => write!(f, "AM"),
Mode::FM => write!(f, "FM"),
Mode::WFM => write!(f, "WFM"),
Mode::AMS => write!(f, "AMS"),
Mode::PKTLSB => write!(f, "PKTLSB"),
Mode::PKTUSB => write!(f, "PKTUSB"),
Mode::PKTFM => write!(f, "PKTFM"),
Mode::ECSSUSB => write!(f, "ECSSUSB"),
Mode::ECSSLSB => write!(f, "ECSSLSB"),
Mode::FAX => write!(f, "FAX"),
Mode::SAM => write!(f, "SAM"),
Mode::SAL => write!(f, "SAL"),
Mode::SAH => write!(f, "SAH"),
Mode::DSB => write!(f, "DSB"),
}
}
}
impl FromStr for Mode {
type Err = RigError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"USB" => Ok(Mode::USB),
"LSB" => Ok(Mode::LSB),
"CW" => Ok(Mode::CW),
"CWR" => Ok(Mode::CWR),
"RTTY" => Ok(Mode::RTTY),
"RTTYR" => Ok(Mode::RTTYR),
"AM" => Ok(Mode::AM),
"FM" => Ok(Mode::FM),
"WFM" => Ok(Mode::WFM),
"AMS" => Ok(Mode::AMS),
"PKTLSB" => Ok(Mode::PKTLSB),
"PKTUSB" => Ok(Mode::PKTUSB),
"PKTFM" => Ok(Mode::PKTFM),
"ECSSUSB" => Ok(Mode::ECSSUSB),
"ECSSLSB" => Ok(Mode::ECSSLSB),
"FAX" => Ok(Mode::FAX),
"SAM" => Ok(Mode::SAM),
"SAL" => Ok(Mode::SAL),
"SAH" => Ok(Mode::SAH),
"DSB" => Ok(Mode::DSB),
_ => Err(RigError::InternalError),
}
}
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum RigError {
#[error("Failed to connect")]
ConnectionError,
#[error("Timeout in communication")]
CommunicationTimeout,
#[error("Connection lost")]
ConnectionLost,
#[error("Already connected")]
AlreadyConnected,
#[error("Internal error")]
InternalError,
}
pub struct Rig {
host: String,
port: u16,
reader: Option<BufReader<OwnedReadHalf>>,
writer: Option<OwnedWriteHalf>,
timeout: time::Duration,
}
impl Rig {
pub fn new(host: &str, port: u16) -> Rig {
Rig {
host: String::from(host),
port,
reader: None,
writer: None,
timeout: time::Duration::from_millis(250),
}
}
pub async fn connect(&mut self) -> Result<(), RigError> {
if self.is_connected() {
return Err(RigError::AlreadyConnected);
}
let constring = format!("{}:{}", self.host, self.port);
let stream = TcpStream::connect(constring)
.await
.map_err(|_| RigError::ConnectionError)?;
let (rx, tx) = stream.into_split();
self.reader = Some(BufReader::new(rx));
self.writer = Some(tx);
Ok(())
}
pub fn disconnect(&mut self) -> bool {
if self.is_connected() {
self.reader = None;
self.writer = None;
true
} else {
false
}
}
pub fn set_communication_timeout(&mut self, timeout: time::Duration) {
self.timeout = timeout;
}
pub fn is_connected(&self) -> bool {
self.reader.is_some() && self.writer.is_some()
}
pub async fn get_frequency(&mut self) -> Result<u64, RigError> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^get_freq:;Frequency: (\d+);RPRT 0$").unwrap();
}
let response = self.execute_command(r";\get_freq").await?;
let freq = RE
.captures(&response)
.map_or(Err(RigError::InternalError), |c| Ok(c.get(1).unwrap()))?;
let freq = freq.as_str().parse::<u64>().unwrap();
Ok(freq)
}
pub async fn set_frequency(&mut self, frequency: u64) -> Result<(), RigError> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^set_freq: (\d+);RPRT 0$").unwrap();
}
let request = format!(r";\set_freq {}", frequency);
let response = self.execute_command(&request).await?;
let freq = RE
.captures(&response)
.map_or(Err(RigError::InternalError), |c| Ok(c.get(1).unwrap()))?;
let freq_out = freq.as_str().parse::<u64>().unwrap();
if freq_out == frequency {
Ok(())
} else {
Err(RigError::InternalError)
}
}
pub async fn get_mode(&mut self) -> Result<(Mode, u16), RigError> {
lazy_static! {
static ref RE: Regex =
Regex::new(r"^get_mode:;Mode: ([A-Z]+);Passband: (\d+);RPRT 0$").unwrap();
}
let response = self.execute_command(r";\get_mode").await?;
let result = RE
.captures(&response)
.map_or(Err(RigError::InternalError), |c| {
Ok((c.get(1).unwrap(), c.get(2).unwrap()))
})?;
let mode = Mode::from_str(result.0.as_str())?;
let passband = result.1.as_str().parse::<u16>().unwrap();
Ok((mode, passband))
}
pub async fn set_mode(&mut self, mode: Mode, passband: u16) -> Result<(), RigError> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^set_mode: ([A-Z]+) (\d+);RPRT 0$").unwrap();
}
let request = format!(r";\set_mode {} {}", mode, passband);
let response = self.execute_command(&request).await?;
let result = RE
.captures(&response)
.map_or(Err(RigError::InternalError), |c| {
Ok((c.get(1).unwrap(), c.get(2).unwrap()))
})?;
let mode_out = Mode::from_str(result.0.as_str())?;
let passband_out = result.1.as_str().parse::<u16>().unwrap();
if mode == mode_out && passband_out == passband {
Ok(())
} else {
Err(RigError::InternalError)
}
}
async fn execute_command(&mut self, input: &str) -> Result<String, RigError> {
self.write_line(input).await?;
self.read_line(self.timeout).await
}
async fn read_line(&mut self, timeout: time::Duration) -> Result<String, RigError> {
let mut response = String::new();
let res = time::timeout(
timeout,
self.reader.as_mut().unwrap().read_line(&mut response),
)
.await
.map_err(|_| RigError::CommunicationTimeout)?;
let _ = match res {
Ok(0) => {
self.reader = None;
self.writer = None;
Err(RigError::ConnectionLost)
}
Err(_) => Err(RigError::InternalError),
Ok(num) => Ok(num),
}?;
response = String::from(response.trim_end());
Ok(response)
}
async fn write_line(&mut self, data: &str) -> Result<(), RigError> {
self.writer
.as_mut()
.unwrap()
.write_all(format!("{}\n", data).as_bytes())
.await
.map_err(|_| RigError::InternalError)
}
}