From 8ce1d95821c667ffe8374b118be492a6a90125d8 Mon Sep 17 00:00:00 2001 From: Danila Gamkov Date: Thu, 6 Mar 2025 17:59:11 +0300 Subject: [PATCH] refactor module for communication with serial port. Create TCP server for accept requests from client and interacting with serial port with command from clients. It is works --- Cargo.lock | 115 ++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 338 +++++++++++++++++++++++++++++++--------------------- 3 files changed, 318 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cfc4c3..4d83ded 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -79,6 +129,46 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clap" +version = "4.5.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "clipboard-win" version = "4.5.0" @@ -90,6 +180,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "core-foundation" version = "0.10.0" @@ -175,6 +271,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -208,6 +310,12 @@ dependencies = [ "mach2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "js-sys" version = "0.3.77" @@ -498,6 +606,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.98" @@ -534,6 +648,7 @@ name = "uart_server" version = "0.1.0" dependencies = [ "chrono", + "clap", "log", "regex", "rustyline", diff --git a/Cargo.toml b/Cargo.toml index 4cdf5ec..ece2c19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ rustyline = "9.*" regex = "1.7.0" chrono = "0.4.40" log = "0.4.26" +clap = { version = "4.*", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index 7852eb9..fd82208 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use std::fs::OpenOptions; use std::io::{BufReader, BufRead}; use std::net::{TcpListener, TcpStream}; use std::collections::HashMap; +use clap::{Parser}; pub mod perf { use chrono::{DateTime, Duration, Utc, Local}; @@ -64,45 +65,6 @@ pub mod perf { use crate::perf::Perf; -fn tcp_server() { - let listener = TcpListener::bind("127.0.0.1:10000").unwrap(); - println!("TCP-server is running"); - - for stream in listener.incoming() { - match stream { - Ok(stream) => { - println!("connect to client: {:?}", stream); - handle_client(stream); - - }, - Err(ref msg) => { - println!("error: failed attempt to set connection with client: ({:?}): {}", - stream, msg); - } - } - } -} - -fn handle_client(mut stream: TcpStream) { - let mut cmd_descr: HashMap::<&str, &str> = HashMap::new(); - cmd_descr.insert("send", "send data to serial port"); - cmd_descr.insert("exit", "cancel connection with server"); - - loop { - let mut reader = BufReader::new(&stream); - let mut rx_data = String::new(); - if let Err(msg) = reader.read_line(&mut rx_data) { - println!("error: failed attempt to read data from client: {}", msg); - return; - } - - if rx_data.contains("exit") { - println!("The client requested to close the connection"); - return; - } - } -} - pub mod uartTransact { const MAX_BUF: usize = 4096; @@ -280,6 +242,8 @@ pub mod uartTransact { if self.type_ == Type_en::tx_rx { self.cmd_writer.log(&self.tx_packet).unwrap(); + // println!("write to port: ({:?})", self.tx_packet.as_bytes()); + match self.port.write(&self.tx_packet.as_bytes()) { Ok(_) => {}, Err(e) => eprintln!("Error sending data: {}", e), @@ -341,6 +305,8 @@ pub mod uartTransact { } } +use crate::uartTransact::*; + fn read_str(s: &str) -> Result, String> { let re = match Regex::new(r#""([^"]+)"|(\S+)"#) { Ok(val) => val, @@ -354,15 +320,111 @@ fn read_str(s: &str) -> Result, String> { Ok(res) } +fn uart_cmd_interp(cmd: &str) -> String { + let mut out = String::new(); + + let args = match read_str(&cmd) { + Ok(arg) => arg, + Err(msg) => { + return format!("error: failed read input string: {}", msg); + } + }; + + let args_cnt = args.len(); + if args_cnt == 0 { return format!("{}", out); } + let cmd_head = &args[0].clone(); + + if cmd_head.contains("exit") { return format!("exit"); } + else if cmd_head.contains("help") { + out.push_str(&format!("Serial port terminal supports the following commands:")); + out.push_str(&format!("\n1. exit - \t\texcecute quit from this program")); + out.push_str(&format!("\n2. help - \t\texplain how to use this program")); + out.push_str(&format!("\n3. show_ports - \tshows all available serial ports")); + out.push_str(&format!("\n4. send - \t\tsends specified \n\tUse: send \n\tExample: send /dev/ttyUSB0 \"command data1 data2\"")); + out.push_str(&format!("\n5. open_port - open port with specified settings\n \tUse: open_port \n \tWhere: \n\t\t - port name \n\t\t - speed (9600 for example) \n\t\t: 5, 6, 7, 8 \n\t\t: None, Even, Odd \n\t\t: 1, 2 \n\t\t: None, Software, Hardware \n\t\t - timeout for receive and transmit in msec \n\t\t: tx, rx, tx_rx, rx_tx (tx - transmit, rx - receive) \n\t\t: true, false \n\tExample: open_port /dev/ttyUSB0 9600 8 Even 1 None 1000 tx_rx true\n")); + return out; + } + else if cmd_head.contains("show_ports") { + match serialport::available_ports() { + Ok(ports) => { + for port in ports { + out.push_str(&format!("{}: {:?}\n", port.port_name, port.port_type)); + } + } + Err(msg) => { return format!("error while get all available serial port list: {}", msg); } + } + + return out; + } + else if cmd_head == "send" { + if args_cnt != 3 { return format!("Error while parse command! {}", "arguments count must be 2"); } + else { + let port_name = &args[1]; + let timeout: u64 = 200; + let port_set = PortSettings_t::new(9600, 8, "None", 1, "None", timeout).unwrap(); + let save_data_flag_ = true; + let save_cmd_flag_ = true; + let trans_type = "tx_rx"; + + let mut uartTrans = UartTransact::new(trans_type, port_name, + port_set, save_data_flag_, save_cmd_flag_).unwrap(); + + let mut data_out = args[2].clone(); + data_out.push_str(std::str::from_utf8(b"\x0D\x0A").unwrap()); // CR + LF + + uartTrans.addPacketForTx(data_out.clone(), data_out.len() as u32).unwrap(); + uartTrans.startTransact().unwrap(); + + let s = uartTrans.getReceivedPacket(); + let data = format!("{}: {}", Perf::cur_time("%H:%M:%S.%3f"), s.trim()); + return data; + } + } + else if cmd_head == "send_loop" { + if args_cnt != 5 { return format!("Error while parse command! {}", "arguments count must be 4"); } + else { + let cnt = args[3].parse::().unwrap(); + let period = args[4].parse::().unwrap(); + let mut i = 0; + while i < cnt { + i += 1; + let mut data_out = args[2].clone(); + thread::sleep(time::Duration::from_millis(period)); + } + + return format!("command have not yet realized"); + } + } + else if cmd_head.contains("open_port") { + + if args_cnt != 10 { return format!("Error while parse command! {}", "arguments count must be 9"); } + else { + return format!("command have not yet realized"); + + // let port_name = &args[1]; + // let speed = args[3].parse::().unwrap(); + // let data_bits = args[3].parse::().unwrap(); + // let parity = &args[4]; + // let stop_bits = args[5].parse::().unwrap(); + // let flow_ctrl = &args[6]; + // let timeout = args[7].parse::().unwrap(); + // let trans_type = &args[8]; + // let save_data_flag = args[9].parse::().unwrap(); + // let save_cmd_flag = save_data_flag; + + // let port_set = PortSettings_t::new(speed, data_bits, parity, stop_bits, + // flow_ctrl, timeout).unwrap(); + + // let mut uartTrans = UartTransact::new(trans_type, &port_name, + // port_set, save_data_flag, save_cmd_flag).unwrap(); + } + } + + return out; +} - - -fn main() { - use crate::perf::Perf; - use crate::uartTransact::*; - let mut perf = Perf::new(); - +fn cli_mode() { let fname_hist = "../settings/cmd_history.log".to_string(); let mut editor = Editor::<()>::new(); if editor.load_history(&fname_hist).is_err() { @@ -393,106 +455,110 @@ fn main() { editor.add_history_entry(&s); + let reply = uart_cmd_interp(&s); - let args = match read_str(&s) { - Ok(arg) => arg, - Err(msg) => { - println!("{}", msg); - continue; - } - }; + if reply.contains("exit") { break; } + else { println!("{}", reply); } - let args_cnt = args.len(); + } - let cmd_head = &args[0].clone(); + editor.save_history(&fname_hist).unwrap(); +} - if cmd_head.contains("exit") { break; } - else if cmd_head.contains("help") { - println!("Serial port terminal supports the following commands:"); - println!("\nexit - excecute quit from this program"); - println!("\nhelp - explain how to use this program"); - println!("\nshow_ports - shows all available serial ports"); - println!("\nsend - sends specified "); - println!("\nsend_loop \n\tsends specified to in loop mode: times with period specified in "); - println!("\nopen_port .\n\tExample1: open_port /dev/ttyUSB0 9600 8 Even 1 None 1000 tx_rx true"); - } - else if cmd_head.contains("show_ports") { - match serialport::available_ports() { - Ok(ports) => { - for port in ports { - println!("{}: {:?}", port.port_name, port.port_type); - } - } - Err(msg) => { eprintln!("error while get all available serial port list: {}", msg); return; } - } - } - else if cmd_head == "send" { - if args_cnt != 3 { eprintln!("Error while parse command! {}", "arguments count must be 2"); } - else { - let port_name = &args[1]; - let timeout: u64 = 200; - let port_set = PortSettings_t::new(9600, 8, "None", 1, "None", timeout).unwrap(); - let save_data_flag_ = true; - let save_cmd_flag_ = true; - let trans_type = "tx_rx"; +fn tcp_server() { + let listener = TcpListener::bind("127.0.0.1:10000").unwrap(); + println!("TCP-server is running"); - let mut uartTrans = UartTransact::new(trans_type, port_name, - port_set, save_data_flag_, save_cmd_flag_).unwrap(); + for stream in listener.incoming() { + match stream { + Ok(stream) => { + println!("connect to client: {:?}", stream); + handle_client(stream); - let mut perf = Perf::new(); - perf.start(); - let mut data_out = args[2].clone(); - data_out.push_str(std::str::from_utf8(b"\x0D\x0A").unwrap()); // CR + LF - - uartTrans.addPacketForTx(data_out.clone(), data_out.len() as u32).unwrap(); - uartTrans.startTransact().unwrap(); - - let s = uartTrans.getReceivedPacket(); - let data = format!("{}: {}", Perf::cur_time("%H:%M:%S.%f"), s.trim()); - println!("{}", data); - perf.stop("time for transaction", "mks"); - } - } - else if cmd_head == "send_loop" { - if args_cnt != 5 { eprintln!("Error while parse command! {}", "arguments count must be 4"); } - else { - let cnt = args[3].parse::().unwrap(); - let period = args[4].parse::().unwrap(); - let mut i = 0; - while i < cnt { - i += 1; - let mut data_out = args[2].clone(); - thread::sleep(time::Duration::from_millis(period)); - } - } - } - else if cmd_head.contains("open_port") { - - if args_cnt != 10 { eprintln!("Error while parse command! {}", "arguments count must be 9"); } - else { - let port_name = &args[1]; - let speed = args[3].parse::().unwrap(); - let data_bits = args[3].parse::().unwrap(); - let parity = &args[4]; - let stop_bits = args[5].parse::().unwrap(); - let flow_ctrl = &args[6]; - let timeout = args[7].parse::().unwrap(); - let trans_type = &args[8]; - let save_data_flag = args[9].parse::().unwrap(); - let save_cmd_flag = save_data_flag; - - let port_set = PortSettings_t::new(speed, data_bits, parity, stop_bits, - flow_ctrl, timeout).unwrap(); - - let mut uartTrans = UartTransact::new(trans_type, &port_name, - port_set, save_data_flag, save_cmd_flag).unwrap(); + }, + Err(ref msg) => { + println!("error: failed attempt to set connection with client: ({:?}): {}", + stream, msg); } } } +} - // close ports - editor.save_history(&fname_hist).unwrap(); - // drop(port1); +fn handle_client(mut stream: TcpStream) { + let mut cmd_descr: HashMap::<&str, &str> = HashMap::new(); + cmd_descr.insert("send", "send data to serial port"); + cmd_descr.insert("exit", "cancel connection with server"); + + loop { + let mut perf = Perf::new(); + let mut reader = BufReader::new(&stream); + let mut rx_data = String::new(); + if let Err(msg) = reader.read_line(&mut rx_data) { + println!("error: failed attempt to read data from client: {}", msg); + return; + } + + // println!("rx_data: ({})", rx_data.trim()); + + perf.start(); + + if rx_data.starts_with("send") || + rx_data.starts_with("help") || + rx_data.starts_with("show_ports") { + let mut reply = uart_cmd_interp(&rx_data.trim()); + + // reply.push('\n'); + if let Err(msg) = stream.write_all(reply.as_bytes()) { + println!("error: failed attempt to send data to client: {}", msg); + return; + } + } + else if rx_data.starts_with("exit") { + println!("The client requested to close the connection"); + return; + + } + else if rx_data == "" { + let data = format!("empty string! "); + + if let Err(msg) = stream.write_all(rx_data.as_bytes()) { + println!("error: failed attempt to send data to client: {}", msg); + return; + } + return; + } + else { + let data = format!("unsuppoted command: ({})\n", rx_data.trim()); + + if let Err(msg) = stream.write_all(data.as_bytes()) { + println!("error: failed attempt to send data to client: {}", msg); + return; + + } + } + + perf.stop("speed: ", "mks"); + } +} + +#[derive(Parser, Debug)] +#[command(version, author, about, long_about = None)] +struct Cli { + /// working mode: tcp - remote control with TCP/IP, cli - command line interface + #[arg(long, short = 'm', value_name = "MODE")] + mode: Option, +} + +fn main() { + + let cli = Cli::parse(); + // println!("{:#?}", cli); + + if let Some(mode) = &cli.mode { + if mode == "cli" { cli_mode(); } + else if mode == "tcp" { tcp_server(); } + else { println!("error: incorrect parameter. type --help"); } + } }