use log::{debug, info};
use std::fmt;
use std::io::{Read, Write};
use std::net::{Shutdown, TcpStream};
use std::time::Duration;
use crate::error_codes::{ResponseCodes, RobotError};
#[derive(Debug, PartialEq, Clone)]
pub enum TCSCommand {
Mode,
Exit,
Power,
Select,
Attach,
Home,
Halt,
Loc,
LocXyz,
Profile,
Move,
MoveToCart,
MoveToJoints,
MotionState,
MoveOneAxis,
MoveRail,
GetParam,
GetLocJoints,
GetLocCart,
FreeMode,
NoOp,
SystemSpeed,
Payload,
WaitForEOM,
}
impl fmt::Display for TCSCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
TCSCommand::Mode => "mode",
TCSCommand::Exit => "exit",
TCSCommand::Power => "hp",
TCSCommand::Select => "selectRobot",
TCSCommand::Attach => "attach",
TCSCommand::Home => "home",
TCSCommand::Halt => "halt",
TCSCommand::Loc => "loc",
TCSCommand::LocXyz => "locXYZ",
TCSCommand::Profile => "profile",
TCSCommand::Move => "move",
TCSCommand::MoveToCart => "movec",
TCSCommand::MoveToJoints => "movej",
TCSCommand::MotionState => "state",
TCSCommand::MoveOneAxis => "moveoneaxis",
TCSCommand::MoveRail => "moveRail",
TCSCommand::GetParam => "pd",
TCSCommand::GetLocJoints => "wherej",
TCSCommand::GetLocCart => "wherec",
TCSCommand::FreeMode => "freemode",
TCSCommand::NoOp => "nop",
TCSCommand::SystemSpeed => "mspeed",
TCSCommand::Payload => "payload",
TCSCommand::WaitForEOM => "waitForEOM",
};
write!(f, "{}", s)
}
}
#[derive(Debug)]
pub struct TCSClient {
pub socket: Option<TcpStream>,
}
impl TCSClient {
pub(crate) const DEFAULT_TIMEOUT: f64 = 5.0;
const REQUEST_SEPARATOR: &'static str = "\n";
const RESPONSE_SEPARATOR: &'static str = "\r\n";
const SPACEBAR_SEPERATOR: &'static str = " ";
const TCS_SERVER_PORT: u16 = 10100;
pub fn new() -> TCSClient {
TCSClient { socket: None }
}
pub fn connect(&mut self, ip: &str, timeout: Option<f64>) -> Result<(), std::io::Error> {
let timeout = timeout.unwrap_or(TCSClient::DEFAULT_TIMEOUT);
let addr = format!("{}:{}", ip, Self::TCS_SERVER_PORT);
info!("tcs_client::connect called");
let conn_attempt = TcpStream::connect(addr.clone());
match conn_attempt {
Ok(stream) => {
stream.set_read_timeout(Some(Duration::from_secs_f64(timeout)))?;
stream.set_write_timeout(Some(Duration::from_secs_f64(timeout)))?;
self.socket = Some(stream);
debug!("connected to client");
Ok(())
}
Err(e) => {
debug!("failed to connect to client");
Err(e)
}
}
}
pub fn send_command<'a>(
&mut self,
command: TCSCommand,
command_args: Option<Vec<&str>>,
wait_for_response: bool,
read_timeout: Option<f64>,
) -> Result<Vec<String>, RobotError> {
info!("tcs_client::send_command called");
if read_timeout.is_some() {
self.socket
.as_ref()
.unwrap()
.set_read_timeout(Some(Duration::from_secs_f64(read_timeout.unwrap())))
.expect("Failed to set read timeout");
}
let payload: String;
if command_args.is_some() {
let payload_slice = command_args.unwrap().join(TCSClient::SPACEBAR_SEPERATOR);
payload = format!(
"{}{}{}{}",
command,
TCSClient::SPACEBAR_SEPERATOR,
payload_slice,
TCSClient::REQUEST_SEPARATOR
);
} else {
payload = format!("{}{}", command, TCSClient::REQUEST_SEPARATOR);
}
debug!("tcs_client::send_command payload: {}", payload);
self.socket
.as_ref()
.unwrap()
.write_all(payload.as_bytes())
.expect("Failed to write message");
if wait_for_response {
let response = self.get_response();
match response {
Ok(r) => {
let if_error_code = ResponseCodes::check_code(r[0].to_owned());
match if_error_code {
Ok(_) => Ok(r[1..].to_vec()),
Err(e) => Err(e),
}
}
Err(e) => Err(e.to_string()),
}
} else {
Ok(vec![])
}
}
fn get_response<'a>(&mut self) -> Result<Vec<String>, std::io::Error> {
info!("tcs_client::get_response called");
let read_buffer = &mut [0; 1024];
let mut response: Vec<u8> = Vec::new();
let read_attempt = self.socket.as_ref().unwrap().read(read_buffer);
match read_attempt {
Ok(bytes_read) => {
response.extend_from_slice(&read_buffer[..bytes_read]);
}
Err(e) => {
return Err(e);
}
}
debug!("tcs_client::get_response payload: {:#?}", read_attempt);
let response_str = std::str::from_utf8(&response).unwrap();
let response_parts = response_str
.split(TCSClient::SPACEBAR_SEPERATOR)
.collect::<Vec<&str>>();
let response_parts = response_parts
.iter()
.map(|part| part.trim_end_matches(TCSClient::RESPONSE_SEPARATOR))
.collect::<Vec<&str>>();
let collated_response = response_parts
.iter()
.map(|part| part.to_string())
.collect::<Vec<String>>();
debug!(
"tcs_client::get_response return value: {:#?}",
collated_response
);
Ok(collated_response)
}
pub fn disconnect(&mut self) -> Result<(), std::io::Error> {
info!("tcs_client::disconnect called");
if self.socket.is_some() {
self.socket
.as_ref()
.unwrap()
.shutdown(Shutdown::Both)
.expect("Shutdown failed so I'm failing I guess(?)");
self.socket = None;
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"Not connected to a TCS",
))
}
}
}