uart server works correctly in CLI and TCP mode

This commit is contained in:
Danila Gamkov 2025-03-31 20:46:57 +03:00
parent 120aaadbb9
commit 90bd7fbae9
3 changed files with 201 additions and 61 deletions

84
README.markdown Normal file
View File

@ -0,0 +1,84 @@
# uart_server
The uart server is intended for work with serial ports (COM-ports): UART (RS232), RS485 via RS485-USB and etc. interface converters
## Contents
- **Setup**
- **Using**
- work with uart_server in CLI mode
- work with uart_server in TCP mode
- **Output uart_server data files description**
- **Contacts**
## Setup
1. Install Rust compiler (if you don't have).
Installation on Linux:
```
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
```
Installation on Windows:
Go to address https://www.rust-lang.org/tools/install and follow instructions
For more detailed information you can go to: https://doc.rust-lang.ru/book/ch01-01-installation.html
**Instruction for setup uart_server project**
2. Clone the repo to your computer:
```
git clone http://heagit.cosmos.ru/gamkov/uart_server.git
```
3. Enter the repo and compile it:
```
cd <PATH_TO_UART_SERVER>
cargo build --release
```
After running this commands you will get an execution file (uart_server) in the following directory:
\<PATH_TO_UART_SERVER\>/target/release/
## Using
### work with uart_server in CLI mode
1. See current available serial ports (Ubuntu):
```
sudo setserial -g /dev/ttyUSB*
```
2. for access to operation with hardware run program with sudo (specify available serial port):
```
sudo ./uart_server -m cli -p /dev/ttyUSB1
```
3. In cli mode type 'help' in order to get information about programs command system
### work with uart_server in TCP mode
1. See current available serial ports (Ubuntu):
```
sudo setserial -g /dev/ttyUSB*
```
2. for access to operation with hardware run program with sudo (specify available serial port and desired IP-address, port for uart-server):
```
sudo ./uart_server -m tcp:127.0.0.1:10000 -p /dev/ttyUSB1
```
## Output uart_server data files description
log files are located in directory:
\<PATH_TO_UART_SERVER\>/log/
**description:**
cmd_\<data\>\_\<user_name_file\>.log - file with output data to serial port
data_\<data\>\_\<user_name_file\>.log - file with input data from serial port
cmd_data_\<data\>\_\<user_name_file\>.log - file with output and input data to and from serial port
where \<data\> - file generation date,
\<user_name_file\> - user filename addition (for more information type help in CLI mode)
**File data log fromat:**
data represented in csv-format (with \';\' separation)
*column 1:* timestamp in format: YYYY.mm.dd HH:MM:SS.zzz (for example: 2025.03.26 16:00:01.158)
*column 2 for cmd and cmd_data log files:* data that was sent to the serial port
*column 2 for data log file:* data that was received from serial port
*column 3 for cmd_data log file:* data that was received from serial port
*column 3 for cmd and data log files:* no data available
## Contatcs
For questions about the program, please contact Danila Gamkov, mail: danila_gamkov@cosmos.ru

BIN
release/uart_server Executable file

Binary file not shown.

View File

@ -146,7 +146,10 @@ pub mod uart_transact {
let file = match path.exists() {
true => OpenOptions::new().write(true).append(true).open(filename.as_ref())?,
false => File::create(filename.as_ref())?
false => {
println!("path: {:?}", path);
File::create(filename.as_ref())?
},
};
let out = BufWriter::new(file);
@ -185,12 +188,13 @@ pub mod uart_transact {
tx_packet: String,
rx_packet: String,
port: Box<dyn SerialPort>,
pub port: Box<dyn SerialPort>,
save_data_flag: bool,
save_cmd_flag: bool,
data_writer: Logger,
cmd_writer: Logger,
cmd_data_writer: Logger,
specific_fname: String
}
impl UartTransact {
@ -198,7 +202,8 @@ pub mod uart_transact {
port_name: &str,
port_settings: PortSettingsT,
save_data_flag: bool,
save_cmd_flag: bool) -> Result<Self, io::Error> {
save_cmd_flag: bool,
specific_fname: &str) -> Result<Self, io::Error> {
let mut type_ = TypeEn::TxRx;
if trans_type == "TxRx" { type_ = TypeEn::TxRx; }
@ -206,17 +211,23 @@ pub mod uart_transact {
else if trans_type == "RxTx" { type_ = TypeEn::RxTx; }
else if trans_type == "Rx" { type_ = TypeEn::Rx; }
let mut filename = format!("../log/data_{}.log", Perf::cur_time("%Y%m%d"));
let mut filename = format!("../log/data_{}_{}.log",
Perf::cur_time("%Y%m%d"),
specific_fname);
let data_writer = Logger::new(filename.clone(), save_data_flag)?;
filename = format!("../log/commands_{}.log", Perf::cur_time("%Y%m%d"));
filename = format!("../log/cmd_{}_{}.log",
Perf::cur_time("%Y%m%d"),
specific_fname);
let cmd_writer = Logger::new(filename.clone(), save_data_flag)?;
filename = format!("../log/cmd_data_{}.log", Perf::cur_time("%Y%m%d"));
filename = format!("../log/cmd_data_{}_{}.log",
Perf::cur_time("%Y%m%d"),
specific_fname);
let cmd_data_writer = Logger::new(filename.clone(), save_data_flag)?;
let mut port = Self::set_port_settings(port_name,
let port = Self::open_port_settings(port_name,
port_settings.clone()).unwrap();
println!("{:#?}", port);
// println!("port: {:#?}", port);
let tx_packet = String::new();
let rx_packet = String::new();
@ -231,6 +242,7 @@ pub mod uart_transact {
port,
tx_packet,
rx_packet,
specific_fname: specific_fname.to_string(),
})
}
@ -244,9 +256,10 @@ pub mod uart_transact {
return Ok(());
}
pub fn start_transact(&mut self) -> Result<(), String> {
pub fn start_transact(&mut self, type_: &str) ->
Result<(), String> {
if self.type_ == TypeEn::TxRx {
if type_ == "txRx" {
self.cmd_writer.log(&self.tx_packet).unwrap();
match self.port.write(&self.tx_packet.as_bytes()) {
@ -259,7 +272,10 @@ pub mod uart_transact {
let mut reader = BufReader::new(&mut self.port);
let mut s = String::new();
if let Err(err) = reader.read_line(&mut s) {
println!("error: {}", err);
let msg = format!("serial port error: {}", err);
self.rx_packet = msg.clone();
self.data_writer.log(&msg).unwrap();
self.cmd_data_writer.log_cmd_data(&self.tx_packet, &msg).unwrap();
return Ok(());
}
@ -269,6 +285,18 @@ pub mod uart_transact {
self.cmd_data_writer.log_cmd_data(&self.tx_packet, &s).unwrap();
}
}
else if type_ == "tx" {
self.cmd_writer.log(&self.tx_packet).unwrap();
self.cmd_data_writer.log_cmd_data(&self.tx_packet,
"").unwrap();
match self.port.write(&self.tx_packet.as_bytes()) {
Ok(_) => {},
Err(e) => eprintln!("Error sending data: {}", e),
}
self.port.flush().unwrap();
}
Ok(())
}
@ -276,7 +304,7 @@ pub mod uart_transact {
self.rx_packet.clone()
}
fn set_port_settings(port_name: &str,
fn open_port_settings(port_name: &str,
set: PortSettingsT) -> Result<Box<dyn SerialPort>, String> {
let mut port = match serialport::new(port_name, set.baud_rate)
@ -326,7 +354,7 @@ fn read_str(s: &str) -> Result<Vec<String>, String> {
Ok(res)
}
fn uart_cmd_interp(cmd: &str) -> String {
fn uart_cmd_interp(cmd: &str, uart_trans: &mut UartTransact) -> String {
let mut out = String::new();
let args = match read_str(&cmd) {
@ -338,25 +366,19 @@ fn uart_cmd_interp(cmd: &str) -> String {
let args_cnt = args.len();
if args_cnt == 0 { return format!("{}", out); }
let cmd_head = &args[0].clone();
let cmd_head = &args[0].trim();
let mut timeout: u64 = 1000;
let mut port_set = PortSettingsT::new(19200, 8, "Even", 1, "None", timeout).unwrap();
let mut save_data_flag = true;
let mut save_cmd_flag = true;
let mut trans_type = "TxRx";
if cmd_head == "exit" { return format!("exit"); }
if *cmd_head == "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 <data>\n\tUse: send <port_name> <data>\n\tExample: send /dev/ttyUSB0 \"command data1 data2\""));
out.push_str(&format!("\n5. set_port - set port with specified settings\n \tUse: set_port <port_name> <speed> <data_bits> <parity> <stop_bits> <flow_ctrl> <timeout> <transact_type> <save_data_flags>\n \tWhere: \n\t\t<port name> - port name \n\t\t<speed> - speed (9600 for example) \n\t\t<data bits>: 5, 6, 7, 8 \n\t\t<parity>: None, Even, Odd \n\t\t<stop bits>: 1, 2 \n\t\t<flow ctrl>: None, Software, Hardware \n\t\t<timeout> - timeout for receive and transmit in msec \n\t\t<transaction type>: tx, rx, TxRx, RxTx (tx - transmit, rx - receive) \n\t\t<save_data_flags>: true, false \n\tExample: set_port /dev/ttyUSB0 9600 8 Even 1 None 1000 TxRx true\n"));
out.push_str(&format!("\n4. send - \t\tsends specified <data> to serial port\n\tUse: send <transaction mode: txRx or tx> <data>\n\tExample: send txRx \"command data1 data2\""));
out.push_str(&format!("\n5. open_port - open serial port with specified settings\n \tUse: open_port <port_name> <speed> <data_bits> <parity> <stop_bits> <flow_ctrl> <timeout> <transact_type> <save_data_flags> <specific filename>\n \tWhere: \n\t\t<port name> - port name \n\t\t<speed> - speed (9600 for example) \n\t\t<data bits>: 5, 6, 7, 8 \n\t\t<parity>: None, Even, Odd \n\t\t<stop bits>: 1, 2 \n\t\t<flow ctrl>: None, Software, Hardware \n\t\t<timeout> - timeout for receive and transmit in msec \n\t\t<transaction type>: tx, rx, TxRx, RxTx (tx - transmit, rx - receive) \n\t\t<save_data_flags>: true, false \n\t\t<specific filename>: specify a specific string that will be added to the filename for logging commands and data \n\tExample: open_port /dev/ttyUSB0 9600 8 Even 1 None 1000 TxRx true device1\n"));
return out;
}
else if cmd_head.contains("show_ports") {
else if *cmd_head == "show_ports" {
match serialport::available_ports() {
Ok(ports) => {
for port in ports {
@ -368,33 +390,38 @@ fn uart_cmd_interp(cmd: &str) -> String {
return out;
}
else if cmd_head == "send" {
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 transact_type = &args[1];
// let mut perf = Perf::new();
// perf.start();
// println!("port_set: {:#?}", uart_trans.port);
println!("port_set: {:#?}", port_set);
let mut uart_trans = UartTransact::new(trans_type, port_name,
port_set, save_data_flag, save_cmd_flag).unwrap();
// perf.stop("time for create port: ", "mks");
let mut data_out = args[2].clone();
data_out.push_str(std::str::from_utf8(b"\x0D\x0A").unwrap()); // CR + LF
println!("data out: {:?}", data_out);
// perf.stop_restart("time for push_str: ", "mks");
// println!("data out: {:?}", data_out);
uart_trans.add_packet_for_tx(data_out.clone(), data_out.len() as u32).unwrap();
uart_trans.start_transact().unwrap();
// perf.stop_restart("time for add packet for tx: ", "mks");
uart_trans.start_transact(&transact_type).unwrap();
// perf.stop_restart("time for transaction: ", "mks");
let mut data = "".to_string();
if transact_type == "txRx" {
let s = uart_trans.get_received_packet();
data = format!("{}: {}", Perf::cur_time("%H:%M:%S.%3f"), s.trim());
}
let s = uart_trans.get_received_packet();
let data = format!("{}: {}", Perf::cur_time("%H:%M:%S.%3f"), s.trim());
return data;
}
}
else if cmd_head.contains("set_port") {
else if *cmd_head == "open_port" {
if args_cnt != 10 { return format!("Error while parse command! {}", "arguments count must be 9"); }
if args_cnt != 11 { return format!("Error while parse command! {}", "arguments count must be 10"); }
else {
let port_name = &args[1];
let speed = args[2].parse::<u32>().unwrap();
@ -402,17 +429,22 @@ fn uart_cmd_interp(cmd: &str) -> String {
let parity = &args[4];
let stop_bits = args[5].parse::<u8>().unwrap();
let flow_ctrl = &args[6];
timeout = args[7].parse::<u64>().unwrap();
trans_type = &args[8];
save_data_flag = args[9].parse::<bool>().unwrap();
save_cmd_flag = save_data_flag;
let timeout = args[7].parse::<u64>().unwrap();
let trans_type = &args[8];
let save_data_flag = args[9].parse::<bool>().unwrap();
let save_cmd_flag = save_data_flag;
let specific_fname = &args[10];
port_set = PortSettingsT::new(speed, data_bits, parity, stop_bits,
let port_set = PortSettingsT::new(speed, data_bits, parity, stop_bits,
flow_ctrl, timeout).unwrap();
println!("port_set: {:#?}", port_set);
let uart_trans = UartTransact::new(trans_type, &port_name,
port_set.clone(), save_data_flag, save_cmd_flag).unwrap();
// println!("port_set: {:#?}", port_set);
*uart_trans = UartTransact::new(trans_type,
&port_name,
port_set.clone(),
save_data_flag,
save_cmd_flag,
&specific_fname).unwrap();
let data = format!("{}: port {} was set with the following settings: {:?}",
Perf::cur_time("%H:%M:%S.%3f"),
@ -421,12 +453,14 @@ fn uart_cmd_interp(cmd: &str) -> String {
return data;
}
}
else {
return "unknown command!".to_string();
}
return out;
}
fn cli_mode() {
fn cli_mode(uart_trans: &mut UartTransact) {
let fname_hist = "../settings/cmd_history.log".to_string();
let mut editor = Editor::<()>::new();
if editor.load_history(&fname_hist).is_err() {
@ -457,7 +491,7 @@ fn cli_mode() {
editor.add_history_entry(&s);
let reply = uart_cmd_interp(&s);
let reply = uart_cmd_interp(&s, uart_trans);
if reply == "exit" { break; }
else { println!("{}", reply); }
@ -467,15 +501,15 @@ fn cli_mode() {
editor.save_history(&fname_hist).unwrap();
}
fn tcp_server() {
let listener = TcpListener::bind("127.0.0.1:10000").unwrap();
println!("TCP-server is running");
fn tcp_server(uart_transact: &mut UartTransact, addr: &str) {
let listener = TcpListener::bind(addr).unwrap();
println!("TCP-server is running on address: {}", addr);
for stream in listener.incoming() {
match stream {
Ok(stream) => {
println!("connect to client: {:?}", stream);
handle_client(stream);
handle_client(stream, uart_transact);
},
Err(ref msg) => {
@ -486,7 +520,7 @@ fn tcp_server() {
}
}
fn handle_client(mut stream: TcpStream) {
fn handle_client(mut stream: TcpStream, uart_transact: &mut UartTransact) {
loop {
// let mut perf = Perf::new();
// perf.start();
@ -500,12 +534,15 @@ fn handle_client(mut stream: TcpStream) {
// println!("rx_data: ({})", rx_data.trim());
if rx_data.starts_with("send") ||
rx_data.starts_with("help") ||
rx_data.starts_with("set_port") ||
rx_data.starts_with("open_port") ||
rx_data.starts_with("show_ports") {
let reply = uart_cmd_interp(&rx_data.trim());
let reply = uart_cmd_interp(&rx_data.trim(), uart_transact);
// reply.push('\n');
println!("rx_uart: ({})", reply.trim());
if rx_data.starts_with("send") ||
rx_data.starts_with("open_port") {
println!("rx_uart: ({})", reply.trim());
}
if let Err(msg) = stream.write_all(reply.as_bytes()) {
println!("error: failed attempt to send data to client: {}", msg);
return;
@ -540,20 +577,39 @@ fn handle_client(mut stream: TcpStream) {
#[derive(Parser, Debug)]
#[command(version, author, about, long_about = None)]
struct Cli {
/// working mode: tcp - remote control with TCP/IP, cli - command line interface
/// working mode:
/// tcp:ip:port - remote control with TCP/IP, or cli - command line interface.
/// Example1: <uart_server> -m tcp:127.0.0.1:10000.
/// Example2: <uart_server> -m cli
#[arg(long, short = 'm', value_name = "MODE")]
mode: Option<String>,
/// port name: type serial port name in order to start working
#[arg(long, short = 'p', value_name = "PORT_NAME")]
port_name: Option<String>,
}
fn main() {
let cli = Cli::parse();
// println!("{:#?}", cli);
let mut port_name = "/dev/ttyUSB1".to_string();
let port_set = PortSettingsT::new(9600, 8, "None", 1, "None", 1000).unwrap();
if let Some(port) = &cli.port_name { port_name = port.to_string(); }
else
{ println!("error: incorrect parameter: PORT_NAME. Type --help"); return; }
let mut uart_trans = UartTransact::new("TxRx", &port_name,
port_set.clone(),
false,
false,
"data").unwrap();
if let Some(mode) = &cli.mode {
if mode == "cli" { cli_mode(); }
else if mode == "tcp" { tcp_server(); }
else { println!("error: incorrect parameter. type --help"); }
if mode.contains("cli") { cli_mode(&mut uart_trans); }
else if mode.contains("tcp") { tcp_server(&mut uart_trans, &mode[4..]); }
else { println!("error: incorrect parameter: MODE. type --help"); }
}
}