use super::protocol::{HEADER_SIZE, Protocol};
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 atom_track;
pub mod auto_approach;
pub mod beam_defl;
pub mod bias;
pub mod bias_spectr;
pub mod bias_sweep;
pub mod cpd_comp;
pub mod current;
pub mod data_log;
pub mod dig_lines;
pub mod folme;
pub mod gen_pi_ctrl;
pub mod gen_swp;
pub mod hs_swp;
pub mod interf;
pub mod kelvin_ctrl;
pub mod laser;
pub mod lockin;
pub mod lockin_freq_swp;
pub mod marks;
pub mod motor;
pub mod mpass;
pub mod oc_sync;
pub mod oscilloscope;
pub mod pattern;
pub mod pi_ctrl;
pub mod piezo;
pub mod pll;
pub mod pll_freq_swp;
pub mod pll_signal_anlzr;
pub mod safe_tip;
pub mod scan;
pub mod script;
pub mod signal_chart;
pub mod signals;
pub mod spectrum_anlzr;
pub mod tcplog;
pub mod tip_recovery;
pub mod user_in;
pub mod user_out;
pub mod util;
pub mod z_ctrl;
pub mod z_spectr;
#[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,
safe_tip_on_drop: 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 safe_tip_on_drop(mut self, enabled: bool) -> Self {
self.safe_tip_on_drop = enabled;
self
}
pub fn build(self) -> Result<NanonisClient, NanonisError> {
let address = self
.address
.ok_or_else(|| NanonisError::Protocol("Address must be specified".to_string()))?;
let port = self
.port
.ok_or_else(|| NanonisError::Protocol("Port must be specified".to_string()))?;
let socket_addr: SocketAddr = format!("{address}:{port}")
.parse()
.map_err(|_| NanonisError::Protocol(format!("Invalid address: {address}")))?;
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}");
NanonisError::from_io(e, 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,
address,
port,
debug: self.debug,
config: self.config,
safe_tip_on_drop: self.safe_tip_on_drop,
poisoned: false,
})
}
}
pub struct NanonisClient {
stream: TcpStream,
address: String,
port: u16,
debug: bool,
config: ConnectionConfig,
safe_tip_on_drop: bool,
poisoned: bool,
}
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()
}
#[deprecated(since = "0.2.0", note = "Use NanonisClient::builder() instead")]
pub fn with_config(addr: &str, config: ConnectionConfig) -> Result<Self, NanonisError> {
let (host, port_str) = addr.rsplit_once(':').ok_or_else(|| {
NanonisError::Protocol(format!(
"Invalid address format '{addr}': expected 'host:port'"
))
})?;
let port: u16 = port_str
.parse()
.map_err(|_| NanonisError::Protocol(format!("Invalid port in address '{addr}'")))?;
Self::builder()
.address(host)
.port(port)
.config(config)
.build()
}
pub fn set_debug(&mut self, debug: bool) {
self.debug = debug;
}
pub fn config(&self) -> &ConnectionConfig {
&self.config
}
pub fn is_poisoned(&self) -> bool {
self.poisoned
}
pub fn reconnect(&mut self) -> Result<(), NanonisError> {
let socket_addr: SocketAddr = format!("{}:{}", self.address, self.port)
.parse()
.map_err(|_| NanonisError::Protocol(format!("Invalid address: {}", self.address)))?;
debug!("Reconnecting to Nanonis at {}:{}", self.address, self.port);
let stream = TcpStream::connect_timeout(&socket_addr, self.config.connect_timeout)
.map_err(|e| {
warn!("Failed to reconnect to {}:{}: {e}", self.address, self.port);
NanonisError::from_io(
e,
format!("Failed to reconnect to {}:{}", self.address, self.port),
)
})?;
stream.set_read_timeout(Some(self.config.read_timeout))?;
stream.set_write_timeout(Some(self.config.write_timeout))?;
self.stream = stream;
self.poisoned = false;
debug!("Successfully reconnected to Nanonis");
Ok(())
}
pub fn quick_send(
&mut self,
command: &str,
args: Vec<NanonisValue>,
argument_types: Vec<&str>,
return_types: Vec<&str>,
) -> Result<Vec<NanonisValue>, NanonisError> {
debug!("Return types: {:?}", return_types);
let response_body = self.quick_send_raw(command, args, argument_types)?;
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
})?;
if result.len() < return_types.len() {
return Err(NanonisError::Protocol(format!(
"{command}: expected {} return values, got {}",
return_types.len(),
result.len()
)));
}
debug!("=== COMMAND SUCCESS: {} ===", command);
debug!("Parsed result: {:?}", result);
Ok(result)
}
pub fn quick_send_raw(
&mut self,
command: &str,
args: Vec<NanonisValue>,
argument_types: Vec<&str>,
) -> Result<Vec<u8>, NanonisError> {
if self.poisoned {
return Err(NanonisError::Io {
source: std::io::Error::new(
std::io::ErrorKind::NotConnected,
"connection poisoned after previous I/O error",
),
context: format!(
"Cannot send '{command}': call reconnect() to re-establish the connection"
),
});
}
debug!("=== COMMAND START: {} ===", command);
debug!("Arguments: {:?}", args);
debug!("Argument types: {:?}", argument_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);
self.poisoned = true;
NanonisError::from_io(e, "Writing command header")
})?;
if !body.is_empty() {
debug!("Sending body ({} bytes)...", body.len());
self.stream.write_all(&body).map_err(|e| {
debug!("Failed to write body: {}", e);
self.poisoned = true;
NanonisError::from_io(e, "Writing command body")
})?;
}
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);
self.poisoned = true;
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);
self.poisoned = true;
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()
};
Ok(response_body)
}
}
impl Drop for NanonisClient {
fn drop(&mut self) {
if self.safe_tip_on_drop {
if self.poisoned {
warn!(
"Skipping safe-tip-on-drop: connection is poisoned \
(rely on Nanonis safe-tip hardware protection)"
);
return;
}
use motor::{MotorDirection, MotorGroup};
let _ = self.z_ctrl_withdraw(false, Duration::from_secs(2));
let _ = self.motor_start_move(MotorDirection::ZMinus, 15u16, MotorGroup::Group1, false);
}
}
}