use super::protocol::{Protocol, HEADER_SIZE};
use crate::error::NanonisError;
use crate::types::NanonisValue;
use log::{debug, warn};
use std::io::Write;
use std::net::{SocketAddr, TcpStream};
use std::time::Duration;
pub mod auto_approach;
pub mod bias;
pub mod bias_sweep;
pub mod current;
pub mod folme;
pub mod motor;
pub mod osci_1t;
pub mod osci_2t;
pub mod osci_hr;
pub mod pll;
pub mod safe_tip;
pub mod scan;
pub mod signals;
pub mod tcplog;
pub mod tip_recovery;
pub mod z_ctrl;
pub mod z_spectr;
pub use tip_recovery::{TipShaperConfig, TipShaperProps};
pub use z_spectr::ZSpectroscopyResult;
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub connect_timeout: Duration,
pub read_timeout: Duration,
pub write_timeout: Duration,
}
impl Default for ConnectionConfig {
fn default() -> Self {
Self {
connect_timeout: Duration::from_secs(5),
read_timeout: Duration::from_secs(10),
write_timeout: Duration::from_secs(5),
}
}
}
#[derive(Default)]
pub struct NanonisClientBuilder {
address: Option<String>,
port: Option<u16>,
config: ConnectionConfig,
debug: bool,
}
impl NanonisClientBuilder {
pub fn address(mut self, addr: &str) -> Self {
self.address = Some(addr.to_string());
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn config(mut self, config: ConnectionConfig) -> Self {
self.config = config;
self
}
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.config.connect_timeout = timeout;
self
}
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.config.read_timeout = timeout;
self
}
pub fn write_timeout(mut self, timeout: Duration) -> Self {
self.config.write_timeout = timeout;
self
}
pub fn build(self) -> Result<NanonisClient, NanonisError> {
let address = self
.address
.ok_or_else(|| NanonisError::InvalidCommand("Address must be specified".to_string()))?;
let port = self
.port
.ok_or_else(|| NanonisError::InvalidCommand("Port must be specified".to_string()))?;
let socket_addr: SocketAddr = format!("{address}:{port}")
.parse()
.map_err(|_| NanonisError::InvalidAddress(address.clone()))?;
debug!("Connecting to Nanonis at {address}");
let stream = TcpStream::connect_timeout(&socket_addr, self.config.connect_timeout)
.map_err(|e| {
warn!("Failed to connect to {address}: {e}");
if e.kind() == std::io::ErrorKind::TimedOut {
NanonisError::Timeout
} else {
NanonisError::Io {
source: e,
context: format!("Failed to connect to {address}"),
}
}
})?;
stream.set_read_timeout(Some(self.config.read_timeout))?;
stream.set_write_timeout(Some(self.config.write_timeout))?;
debug!("Successfully connected to Nanonis");
Ok(NanonisClient {
stream,
debug: self.debug,
config: self.config,
})
}
}
pub struct NanonisClient {
stream: TcpStream,
debug: bool,
config: ConnectionConfig,
}
impl NanonisClient {
pub fn new(addr: &str, port: u16) -> Result<Self, NanonisError> {
Self::builder().address(addr).port(port).build()
}
pub fn builder() -> NanonisClientBuilder {
NanonisClientBuilder::default()
}
pub fn with_config(addr: &str, config: ConnectionConfig) -> Result<Self, NanonisError> {
Self::builder().address(addr).config(config).build()
}
pub fn set_debug(&mut self, debug: bool) {
self.debug = debug;
}
pub fn config(&self) -> &ConnectionConfig {
&self.config
}
pub fn quick_send(
&mut self,
command: &str,
args: Vec<NanonisValue>,
argument_types: Vec<&str>,
return_types: Vec<&str>,
) -> Result<Vec<NanonisValue>, NanonisError> {
debug!("=== COMMAND START: {} ===", command);
debug!("Arguments: {:?}", args);
debug!("Argument types: {:?}", argument_types);
debug!("Return types: {:?}", return_types);
let mut body = Vec::new();
for (arg, arg_type) in args.iter().zip(argument_types.iter()) {
debug!("Serializing {:?} as {}", arg, arg_type);
Protocol::serialize_value(arg, arg_type, &mut body)?;
}
let header = Protocol::create_command_header(command, body.len() as u32);
debug!("Header size: {}, Body size: {}", header.len(), body.len());
debug!("Full header bytes: {:02x?}", header);
debug!(
"Command in header: {:?}",
String::from_utf8_lossy(&header[0..32]).trim_end_matches('\0')
);
debug!(
"Body size in header: {}",
u32::from_be_bytes([header[32], header[33], header[34], header[35]])
);
if !body.is_empty() {
debug!("Body bytes: {:02x?}", body);
}
debug!("Sending header ({} bytes)...", header.len());
self.stream.write_all(&header).map_err(|e| {
debug!("Failed to write header: {}", e);
NanonisError::Io {
source: e,
context: "Writing command header".to_string(),
}
})?;
if !body.is_empty() {
debug!("Sending body ({} bytes)...", body.len());
self.stream.write_all(&body).map_err(|e| {
debug!("Failed to write body: {}", e);
NanonisError::Io {
source: e,
context: "Writing command body".to_string(),
}
})?;
}
debug!("Command data sent successfully");
debug!("Reading response header ({} bytes)...", HEADER_SIZE);
let response_header =
Protocol::read_exact_bytes::<HEADER_SIZE>(&mut self.stream).map_err(|e| {
debug!("Failed to read response header: {}", e);
e
})?;
debug!("Response header received: {:02x?}", response_header);
debug!(
"Response command: {:?}",
String::from_utf8_lossy(&response_header[0..32]).trim_end_matches('\0')
);
let body_size = Protocol::validate_response_header(&response_header, command)?;
debug!("Expected response body size: {}", body_size);
let response_body = if body_size > 0 {
debug!("Reading response body ({} bytes)...", body_size);
let body = Protocol::read_variable_bytes(&mut self.stream, body_size as usize)
.map_err(|e| {
debug!("Failed to read response body: {}", e);
e
})?;
debug!(
"Response body received ({} bytes): {:02x?}",
body.len(),
if body.len() <= 100 {
&body[..]
} else {
&body[..100]
}
);
body
} else {
debug!("No response body expected");
Vec::new()
};
debug!("Parsing response with types: {:?}", return_types);
let result = Protocol::parse_response_with_error_check(&response_body, &return_types)
.map_err(|e| {
debug!("Failed to parse response: {}", e);
e
})?;
debug!("=== COMMAND SUCCESS: {} ===", command);
debug!("Parsed result: {:?}", result);
Ok(result)
}
}