diff --git a/Cargo.toml b/Cargo.toml index 599fca1..f07078b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" gtk = "0.14.1" gio = "0.14.6" glib = "0.14.5" +#pretty-hex = "0.2.1" #gtk4 = {git = "https://github.com/gtk-rs/gtk4-rs/"} #gtk4 = "0.3.0" #gio4_sys ="0." diff --git a/src/hexdump.rs b/src/hexdump.rs new file mode 100644 index 0000000..465825c --- /dev/null +++ b/src/hexdump.rs @@ -0,0 +1,68 @@ +pub fn get_hex(prefix_len: u32, data: Vec) -> String { + let mut header_begin = prefix_len >> 4; + let mut counter = prefix_len % 16; + // println!("{}", header_begin); + // println!("{}", counter); + let mut ret = String::new(); + + for i in data { + if counter % 16 == 0 { + counter = 0; + // if header_begin != 0 { + // ret.push('\n'); + // } + header_begin += 1; + ret += &format!("{:04x}\t", header_begin - 1); + } + counter += 1; + ret = ret + &*format!("{:02x} ", i); + if counter % 16 ==0 { + ret.push('\n'); + } + } + return ret; +} + +pub fn get_ascii(prefix_len: u32, data: Vec) -> String { + let mut header_begin = prefix_len >> 4; + let mut counter = prefix_len % 16; + // println!("{}", header_begin); + // println!("{}", counter); + let mut ret = String::new(); + + for i in data { + if counter % 16 == 0 { + counter = 0; + // if header_begin != 0 { ret.push('\n'); } + header_begin += 1; + ret += &format!("{:04x}\t", header_begin - 1); + } + counter += 1; + + if i.is_ascii_alphanumeric() || i.is_ascii_punctuation() || i.is_ascii_graphic() { + // ret += &*(i as char).to_string(); + ret.push(char::from(i)); + // basic_show.push(i as char); + } else { + ret += "."; + } + + if counter % 16 ==0 { + ret.push('\n'); + } + } + return ret; +} + +#[cfg(test)] +mod tests { + use crate::hexdump::get_hex; + + #[test] + fn exploration() { + let data = vec![10, 20, 203]; + let tmp = get_hex(14, data); + println!("{}", tmp); + // assert_eq!(2 + 2, 4); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 174fa1e..a306c8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ #![windows_subsystem = "windows"] + mod port; +mod hexdump; use closure::closure; use gtk::{Inhibit, TextView, Statusbar, Label}; use gtk::{gio, glib}; -use gtk::prelude::{GtkWindowExt, BuilderExtManual, GtkMenuItemExt, WidgetExt, EntryExt, TextBufferExt}; +use gtk::prelude::{GtkWindowExt, BuilderExtManual, GtkMenuItemExt, WidgetExt, EntryExt, TextBufferExt, ScrolledWindowExt, AdjustmentExt}; use gio::prelude::{ApplicationExt, ApplicationExtManual}; use std::sync::mpsc::{Sender, Receiver, channel}; use serialport::{StopBits, FlowControl, Parity, DataBits}; @@ -13,6 +15,10 @@ use crate::port::*; use gtk::prelude::TextViewExt; use gtk::prelude::LabelExt; +use std::str; +use std::time::Duration; +use std::num::ParseIntError; + fn main() { let application = gtk::Application::new( Some("com.github.gtk-rs.examples.menu_bar_system"), @@ -27,8 +33,8 @@ fn main() { fn build_ui(application: >k::Application) { let builder = gtk::Builder::from_string(include_str!("test.ui")); - let main_window: gtk::ApplicationWindow = builder.object("main_window").expect("Can not get window!"); - main_window.set_application(Some(application)); + let ui_main_window: gtk::ApplicationWindow = builder.object("main_window").expect("Can not get window!"); + ui_main_window.set_application(Some(application)); let menu_view_command: gtk::MenuItem = builder.object("view_command").expect("Can not get command item"); let command_entry: gtk::Entry = builder.object("command_entry").expect("Can not get command entry"); let tmp = command_entry.clone(); @@ -40,23 +46,38 @@ fn build_ui(application: >k::Application) { tmp.set_has_focus(true); } }); - let log_window: gtk::Window = builder.object("log_window").expect("Can not get log view"); + let ui_log_window: gtk::Window = builder.object("log_window").expect("Can not get log view"); let menu_log_command: gtk::MenuItem = builder.object("log_command").expect("Can not get log command"); + let menu_hex_command: gtk::MenuItem = builder.object("hex_command").expect("Can not get log command"); let ui_receive_view: TextView = builder.object("receive_view").expect("can not get receive view"); let ui_baudrate: Label = builder.object("ui_baudrate").unwrap(); let ui_databit: Label = builder.object("ui_databit").unwrap(); let ui_parity: Label = builder.object("ui_parity").unwrap(); let ui_stopbit: Label = builder.object("ui_stopbit").unwrap(); + let ui_period: Label = builder.object("ui_period").unwrap(); let ui_com: Label = builder.object("ui_com").unwrap(); let ui_statusbar: Statusbar = builder.object("statusbar").unwrap(); let ui_log_view: TextView = builder.object("log_view").unwrap(); + let ui_hex_view: TextView = builder.object("hex_view").unwrap(); + let ui_hex_window: gtk::Window = builder.object("hex_window").unwrap(); + let ui_ascii_view: TextView = builder.object("ascii_view").unwrap(); + let ui_hex_scroll: gtk::ScrolledWindow = builder.object("ui_hex_scroll").unwrap(); - log_window.show(); - log_window.connect_delete_event(|a, _| { + // ui_viewport.set_vscroll_policy(Sc) + + + ui_log_window.show(); + // ui_hex_window.show(); + ui_log_window.connect_delete_event(|a, _| { a.hide(); Inhibit(true) }); - let tmp = log_window.clone(); + + ui_hex_window.connect_delete_event(|a, _| { + a.hide(); + Inhibit(true) + }); + let tmp = ui_log_window.clone(); menu_log_command.connect_activate(move |_| { if tmp.is_visible() { tmp.hide(); @@ -64,6 +85,16 @@ fn build_ui(application: >k::Application) { tmp.show(); } }); + + let tmp = ui_hex_window.clone(); + menu_hex_command.connect_activate(move |_| { + if tmp.is_visible() { + tmp.hide(); + } else { + tmp.show(); + } + }); + let mut receive_char_counter = 0; let (sender_ui_upd, receiver_ui_upd) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); receiver_ui_upd.attach(None, move |msg| { match msg { @@ -71,19 +102,41 @@ fn build_ui(application: >k::Application) { let buf = ui_log_view.buffer().unwrap(); buf.insert(&mut buf.end_iter(), text.as_str()); } - Message::UiUpdateReceiveMsg(text) => { + Message::UiUpdateReceiveMsg(data) => { + let mut basic_show = String::new(); + for i in data.clone() { + if i.is_ascii_alphanumeric() || i.is_ascii_whitespace() || i.is_ascii_punctuation() || i.is_ascii_graphic() { + basic_show.push(i as char); + } + } + let buf = ui_hex_view.buffer().unwrap(); + let hex = hexdump::get_hex(receive_char_counter, data.clone()); + buf.insert(&mut buf.end_iter(), hex.as_str()); + + // println!("{:?}",std::mem::size_of_val(&buf)); let buf = ui_receive_view.buffer().unwrap(); - buf.insert(&mut buf.end_iter(), text.as_str()); - ui_receive_view.scroll_to_mark(&buf.get_insert().unwrap(), 0.0, true, 0.5, 0.5); + buf.insert(&mut buf.end_iter(), basic_show.as_str()); + + + let buf = ui_ascii_view.buffer().unwrap(); + let ascii = hexdump::get_ascii(receive_char_counter, data.clone()); + buf.insert(&mut buf.end_iter(), ascii.as_str()); + + receive_char_counter += data.len() as u32; + let tmp = ui_hex_scroll.vadjustment(); + tmp.set_value(tmp.upper()); } Message::UiCleanReceiveMsg => ui_receive_view.buffer().unwrap().set_text(""), Message::UiCleanLog => ui_log_view.buffer().unwrap().set_text(""), - Message::UiShowLog => log_window.show(), - Message::UiHideLog => log_window.hide(), + Message::UiShowLog => ui_log_window.show(), + Message::UiHideLog => ui_log_window.hide(), + Message::UiShowHex => ui_hex_window.show(), + Message::UiHideHex => ui_hex_window.hide(), Message::UiUpdateStatusBarBaudrate(value) => ui_baudrate.set_text(value.to_string().as_str()), Message::UiUpdateStatusBarDatabit(value) => ui_databit.set_text(value.to_string().as_str()), Message::UiUpdateStatusBarParity(value) => ui_parity.set_text(value.to_string().as_str()), Message::UiUpdateStatusBarStopbit(value) => ui_stopbit.set_text(value.to_string().as_str()), + Message::UiUpdateStatusBarPeriod(value) => ui_period.set_text(&*(value.to_string() + " ms")), Message::UiUpdateStatusBarCom(value) => ui_com.set_text(value.to_string().as_str()), _ => {} } @@ -102,7 +155,7 @@ fn build_ui(application: >k::Application) { process_cmd(command, to_ui.clone(), gui2main_tx.clone()); }); - main_window.show(); + ui_main_window.show(); } @@ -135,7 +188,21 @@ fn process_cmd(command: String, to_ui: glib::Sender, gui2main_tx: Sende to_ui.send(Message::UiUpdateLog(list_available_ports().0)).unwrap(); } "send" => { - gui2main_tx.send(Message::MainCmdSend(command[5..].parse().unwrap())).unwrap(); + if command.len() <= 5 { + to_ui.send(Message::UiUpdateLog("no data!\n".parse().unwrap())).unwrap(); + } else { + gui2main_tx.send(Message::MainCmdSend(command[5..].parse().unwrap())).unwrap(); + } + } + "loop" => { + if command.len() <= 5 { + to_ui.send(Message::UiUpdateLog("no data!\n".parse().unwrap())).unwrap(); + } else { + gui2main_tx.send(Message::MainCmdSendPeriod(command[5..].parse().unwrap())).unwrap(); + } + } + "loopend" => { + gui2main_tx.send(Message::MainCmdStopPeriod).unwrap(); } "clean" => { to_ui.send(Message::UiCleanReceiveMsg).unwrap(); @@ -146,7 +213,8 @@ fn process_cmd(command: String, to_ui: glib::Sender, gui2main_tx: Sende to_ui.send(Message::UiUpdateLog("No enough argument\n".to_string())).unwrap(); } else { match command_split[1] { - "log" => { to_ui.send(Message::UiShowLog).unwrap(); } + "log" => to_ui.send(Message::UiShowLog).unwrap(), + "hex" => to_ui.send(Message::UiShowHex).unwrap(), _ => {} } } @@ -156,7 +224,8 @@ fn process_cmd(command: String, to_ui: glib::Sender, gui2main_tx: Sende to_ui.send(Message::UiUpdateLog("No enough argument\n".to_string())).unwrap(); } else { match command_split[1] { - "log" => { to_ui.send(Message::UiHideLog).unwrap(); } + "log" => to_ui.send(Message::UiHideLog).unwrap(), + "hex" => to_ui.send(Message::UiHideHex).unwrap(), _ => {} } } @@ -168,7 +237,6 @@ fn process_cmd(command: String, to_ui: glib::Sender, gui2main_tx: Sende fn main_proc(to_main_tx: Sender, rx: Receiver, ui_upd: glib::Sender) { let (mut main2port_tx, _rx_port_receive) = channel::(); - let mut default_com_port = "USB0".to_string(); let mut port_default_config = PortConfig { baud_rate: 115_200, data_bits: DataBits::Eight, @@ -178,8 +246,8 @@ fn main_proc(to_main_tx: Sender, rx: Receiver, ui_upd: glib::S timeout: 10, }; let mut port_status_open = false; - default_com_port = list_available_ports().1; - ui_upd.send(Message::UiUpdateStatusBarCom(default_com_port.clone())); + let mut default_com_port = list_available_ports().1; + ui_upd.send(Message::UiUpdateStatusBarCom(default_com_port.clone())).unwrap(); loop { if let Ok(msg) = rx.try_recv() { match msg { @@ -187,19 +255,19 @@ fn main_proc(to_main_tx: Sender, rx: Receiver, ui_upd: glib::S Message::MainCmdOpen(arg) => { if port_status_open { ui_upd.send(Message::UiUpdateLog("Port Already Open\n".to_string())).unwrap(); - continue; + continue; } let (tmp_to_port, main2port_rx) = channel::(); main2port_tx = tmp_to_port; - let port_name = if arg.is_empty() {default_com_port.clone()} else {arg}; + let port_name = if arg.is_empty() { default_com_port.clone() } else { arg }; default_com_port = port_name.clone(); - ui_upd.send(Message::UiUpdateStatusBarCom(default_com_port.clone())); + ui_upd.send(Message::UiUpdateStatusBarCom(default_com_port.clone())).unwrap(); match port_open(port_name, port_default_config) { Ok(port) => { port_status_open = true; std::thread::spawn(closure!(clone to_main_tx, || port_proc(port, to_main_tx, main2port_rx))); - ui_upd.send(Message::UiUpdateLog("open Port succeed\n".to_string())).unwrap(); + ui_upd.send(Message::UiUpdateLog("open port succeed\n".to_string())).unwrap(); } Err(e) => { match e { @@ -211,16 +279,15 @@ fn main_proc(to_main_tx: Sender, rx: Receiver, ui_upd: glib::S } } Message::MainCmdClose => { - if port_status_open{ + if port_status_open { main2port_tx.send(Message::PortCmdClose).unwrap(); - } - else { + } else { ui_upd.send(Message::UiUpdateLog("No port opened\n".to_string())).unwrap(); } } - Message::MainCmdSend(msg) => { - main2port_tx.send(Message::PortCmdSend(msg)).unwrap(); - } + Message::MainCmdSend(msg) => main2port_tx.send(Message::PortCmdSend(msg)).unwrap(), + Message::MainCmdSendPeriod(msg) => { main2port_tx.send(Message::PortPeriodSend(msg)).unwrap() } + Message::MainCmdStopPeriod=> { main2port_tx.send(Message::PortPeriodStop).unwrap() } Message::MainCmdSet(args) => { match args[0].as_str() { "baudrate" => { @@ -287,7 +354,7 @@ fn main_proc(to_main_tx: Sender, rx: Receiver, ui_upd: glib::S match value { 1 => port_default_config.stopbit = StopBits::One, 2 => port_default_config.stopbit = StopBits::Two, - _ => { ui_upd.send(Message::UiUpdateLog("Invalid command!\n".to_string())).unwrap(); } + _ => ui_upd.send(Message::UiUpdateLog("Invalid command!\n".to_string())).unwrap(), } } else { ui_upd.send(Message::UiUpdateLog("Invalid command!\n".to_string())).unwrap(); @@ -306,31 +373,44 @@ fn main_proc(to_main_tx: Sender, rx: Receiver, ui_upd: glib::S } } } + "period" => { + if args.len() != 2 { + ui_upd.send(Message::UiUpdateLog("Invalid command!\n".to_string())).unwrap(); + } else { + match args[1].parse::() { + Ok(value) => { + if port_status_open { + main2port_tx.send(Message::PortPeriodDuration(value)).unwrap(); + ui_upd.send(Message::UiUpdateStatusBarPeriod(value)).unwrap(); + } + else{ + ui_upd.send(Message::UiUpdateLog("No port open\n".parse().unwrap())).unwrap(); + } + } + Err(e) => ui_upd.send(Message::UiUpdateLog(format!("{:?}\n", e))).unwrap(), + } + } + } _ => { ui_upd.send(Message::UiUpdateLog("command does not support!\n".to_string())).unwrap(); } } } Message::PortHaveData(data) => { - let mut message_show = String::new(); - for i in data { - // let char = i as char; - // // let char = 'i'; - if i.is_ascii_alphanumeric() || i.is_ascii_whitespace() || i.is_ascii_punctuation() || i.is_ascii_graphic() { - message_show.push(i as char); - } - } - // ui_upd.send(Message::UiUpdateReceiveMsg(String::from_utf8_lossy(&*data).parse().unwrap())).unwrap(); - ui_upd.send(Message::UiUpdateReceiveMsg(message_show)).unwrap(); + ui_upd.send(Message::UiUpdateReceiveMsg(data)).unwrap(); } Message::PortCloseSucceed => { ui_upd.send(Message::UiUpdateLog("Close port succeed\n".to_string())).unwrap(); - port_status_open = false; + port_status_open = false; } Message::PortError(msg) => { - ui_upd.send(Message::UiUpdateLog(format!("port closed by error: {}", msg))).unwrap(); + ui_upd.send(Message::UiUpdateLog(format!("port closed by error: {}\n", msg))).unwrap(); port_status_open = false; } + Message::PortPeriodDuration(value) => { + ui_upd.send(Message::UiUpdateStatusBarPeriod(value)).unwrap(); + } _ => {} } } + std::thread::sleep(Duration::from_millis(20)); } } diff --git a/src/port.rs b/src/port.rs index 4267609..2b0fec8 100644 --- a/src/port.rs +++ b/src/port.rs @@ -6,26 +6,35 @@ use std::time::Duration; pub enum Message { UiUpdateLog(String), - UiUpdateReceiveMsg(String), + UiUpdateReceiveMsg(Vec), UiCleanReceiveMsg, UiCleanLog, UiShowLog, UiHideLog, + UiShowHex, + UiHideHex, UiUpdateStatusBarCom(String), UiUpdateStatusBarBaudrate(u32), UiUpdateStatusBarParity(String), UiUpdateStatusBarStopbit(u32), + UiUpdateStatusBarPeriod(u64), UiUpdateStatusBarDatabit(u32), MainCmdOpen(String), MainCmdClose, MainCmdSend(String), MainCmdSet(Vec), + MainCmdSendPeriod(String), + MainCmdStopPeriod, PortCmdClose, PortCmdSend(String), + PortPeriodSend(String), + PortPeriodStop, + PortPeriodDuration(u64), + PortCloseSucceed, PortHaveData(Vec), PortError(String), @@ -46,14 +55,14 @@ pub struct PortConfig { } pub fn port_open(port: String, config: PortConfig) -> Result, Error> { - let mut prelude = ""; if env::consts::OS == "linux" { prelude = "/dev/tty"; } - else if env::consts::OS == "windows" { prelude = "" } + let mut prelude = ""; + if env::consts::OS == "linux" { prelude = "/dev/tty"; } else if env::consts::OS == "windows" { prelude = "" } let port_builder = serialport::new(prelude.to_owned() + &port, config.baud_rate) .data_bits(config.data_bits) .parity(config.parity) - .flow_control(config.flow_control) - .stop_bits(config.stopbit) - .timeout(Duration::from_millis(config.timeout as u64)); + .flow_control(config.flow_control) + .stop_bits(config.stopbit) + .timeout(Duration::from_millis(config.timeout as u64)); match port_builder.open() { Ok(port) => { return Ok(port); } Err(e) => { @@ -65,6 +74,11 @@ pub fn port_open(port: String, config: PortConfig) -> Result pub fn port_proc(mut port: Box, to_main: Sender, from_main: Receiver) { let mut buf: Vec = vec![0; 128]; + let mut period_send = false; + let mut period_timeout = std::time::Duration::from_millis(100); + let mut period_send_msg = String::new(); + let mut last_send = std::time::Instant::now(); + to_main.send(Message::PortPeriodDuration(100)); loop { if let Ok(msg) = from_main.try_recv() { match msg { @@ -80,6 +94,16 @@ pub fn port_proc(mut port: Box, to_main: Sender, from_m _ => {} } } + Message::PortPeriodSend(msg) => { + period_send = true; + period_send_msg = msg; + } + Message::PortPeriodStop=> { + period_send = false; + } + Message::PortPeriodDuration(value) => { + period_timeout = std::time::Duration::from_millis(value); + } _ => {} } } @@ -92,10 +116,17 @@ pub fn port_proc(mut port: Box, to_main: Sender, from_m } } } + if period_send && (last_send.elapsed() > period_timeout){ + last_send = std::time::Instant::now(); + match port.write(period_send_msg.as_bytes()) { + Err(e) => to_main.send(Message::PortError(format!("{}", e).to_string())).unwrap(), + _ => {} + } + } } } -pub(crate) fn list_available_ports() -> (String, String){ +pub(crate) fn list_available_ports() -> (String, String) { let mut ret = String::new(); let mut ret_portname = String::new(); match serialport::available_ports() { @@ -111,5 +142,5 @@ pub(crate) fn list_available_ports() -> (String, String){ ret = format!("{}", err); } } - (ret,ret_portname) + (ret, ret_portname) } diff --git a/src/test.ui b/src/test.ui index 2c0fc4f..8bd8ece 100644 --- a/src/test.ui +++ b/src/test.ui @@ -2,8 +2,60 @@ + + False + 900 + 400 + + + True + True + in + + + True + False + out + + + True + True + True + + + True + True + False + True + + + False + True + + + + + True + True + False + True + + + True + True + + + + + + + + + False + 300 + 400 network-server @@ -22,8 +74,8 @@ False - 300 - 400 + 600 + 800 network-server @@ -176,7 +228,7 @@ - + True False hex @@ -238,6 +290,7 @@ True True False + True @@ -318,6 +371,19 @@ 5 + + + True + False + True + 100 ms + + + False + True + 6 + + False