From 2f90902bb607b601f1a0d707e0292dff62febf07 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Sun, 10 Mar 2024 18:16:39 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 + .vscode/tasks.json | 13 ++++ Cargo.toml | 12 ++++ src/main.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++ src/port.rs | 108 +++++++++++++++++++++++++++++ 5 files changed, 301 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/port.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a61e4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Cargo.lock + +target/ \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..ffef723 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "command": "run", + "problemMatcher": [ + "$rustc" + ], + "label": "rust: cargo run" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8463daf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "serial_reader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serialport = "4.3.0" +gtk = { version = "0.18.1", features = ["v3_24"] } +anyhow = { version = "1.0.80", features = ["backtrace"] } +glib = "0.18.5" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7236ca7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,165 @@ +use std::{ + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Mutex, + }, + thread, +}; + +use gtk::{prelude::*, PolicyType, TextView}; +use gtk::{Application, ApplicationWindow}; + +use gtk::{Button, Entry, Orientation, ScrolledWindow}; + +use glib::{self, source::Priority, ControlFlow}; + +mod port; +use port::*; + +static CONNECTED: AtomicBool = AtomicBool::new(false); +static PORT: Mutex> = Mutex::new(None); + +fn main() { + let app = Application::builder() + .application_id("org.example.HelloWorld") + .build(); + + app.connect_activate(move |app| { + // We create the main window. + let window = ApplicationWindow::builder() + .application(app) + .default_width(320) + .default_height(200) + .title("Hello, World!") + .build(); + + let master_box = gtk::Box::new(Orientation::Vertical, 5); + let top_bar_box = gtk::Box::new(Orientation::Horizontal, 5); + let pid = Entry::builder().text("0x27dd").editable(true).build(); + let vid = Entry::builder().text("0x16c0").editable(true).build(); + let connect_button = Button::with_label("Connect"); + let reset_button = Button::with_label("Reset"); + let serial_info = TextView::new(); + serial_info.set_editable(false); + let scrolled_window = ScrolledWindow::builder() + .vscrollbar_policy(PolicyType::Automatic) + .child(&serial_info) + .build(); + + top_bar_box.pack_end(&connect_button, true, true, 3); + top_bar_box.pack_end(&reset_button, true, true, 3); + top_bar_box.pack_end(&vid, true, true, 3); + top_bar_box.pack_end(&pid, true, true, 3); + + master_box.pack_end(&scrolled_window, true, true, 3); + master_box.pack_end(&top_bar_box, false, true, 3); + + let (tx, rx) = glib::MainContext::channel(Priority::DEFAULT); + + std::thread::spawn(move || { + // loop forever + loop { + let mut port_lock = PORT.lock().unwrap(); + + if let Some(port) = &mut *port_lock { + // handle incoming message + match port.read() { + Ok(port_read) => match port_read { + SerialReadResult::Message(msg) => { + if !msg.is_empty() { + tx.send(msg).unwrap(); + } + } + SerialReadResult::UtfConversion(err) => println!("{:?}", err), + SerialReadResult::Timeout => (), + }, + Err(err) => println!("{err}"), + } + } + + drop(port_lock); + + thread::sleep(Duration::from_millis(10)); + } + }); + + let buffer = serial_info.buffer().unwrap(); + + connect_button.connect_clicked(move |_| { + if !CONNECTED.load(SeqCst) { + let usb_id = UsbId { + vendor_id: u16::from_str_radix( + vid.buffer().text().trim_start_matches("0x"), + 16, + ) + .unwrap(), + product_id: u16::from_str_radix( + pid.buffer().text().trim_start_matches("0x"), + 16, + ) + .unwrap(), + }; + + let serialport_settings = SerialPortSettings { + baud_rate: 9600, + data_bits: DataBits::Eight, + parity: Parity::None, + stop_bits: StopBits::One, + flow_control: FlowControl::None, + timeout: Duration::from_millis(2500), + }; + + if let Ok(port) = Port::open(usb_id, serialport_settings, 50, 1) { + CONNECTED.store(true, SeqCst); + + let (mut buffer_start, mut buffer_end) = buffer.bounds(); + buffer.delete(&mut buffer_start, &mut buffer_end); + + *PORT.lock().unwrap() = Some(port); + } + } + }); + + reset_button.connect_clicked(move |_| { + if CONNECTED.load(SeqCst) { + CONNECTED.store(false, SeqCst); + + let mut port_lock = PORT.lock().unwrap(); + + if let Some(port) = &mut *port_lock { + if let Err(err) = port.write("reset") { + println!("{err}"); + } + } + + *port_lock = None; + } + }); + + let buffer = serial_info.buffer().unwrap(); + + rx.attach(None, move |message| { + if !message.is_empty() { + match message.parse::() { + Ok(n) => { + buffer.insert(&mut buffer.end_iter(), &format!("{:#02x}\n", n)); + } + Err(_) => { + buffer.insert(&mut buffer.end_iter(), &format!("{}\n", message)); + } + } + + serial_info.scroll_to_iter(&mut buffer.end_iter(), 0.0, true, 0.0, 0.0); + } + + ControlFlow::Continue + }); + + window.add(&master_box); + + // Don't forget to make all widgets visible. + window.show_all(); + }); + + app.run(); +} diff --git a/src/port.rs b/src/port.rs new file mode 100644 index 0000000..68622d8 --- /dev/null +++ b/src/port.rs @@ -0,0 +1,108 @@ +use anyhow::{anyhow, Result}; + +use std::{io, string::FromUtf8Error}; + +pub use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits}; +pub use std::time::Duration; + +pub enum SerialReadResult { + Message(String), + UtfConversion(FromUtf8Error), + Timeout, +} + +pub struct SerialPortSettings { + pub baud_rate: u32, + pub data_bits: DataBits, + pub parity: Parity, + pub stop_bits: StopBits, + pub flow_control: FlowControl, + pub timeout: Duration, +} + +pub struct UsbId { + pub vendor_id: u16, + pub product_id: u16, +} + +pub struct Port { + serial_port: Box, +} + +impl Port { + pub fn open( + usb_device: UsbId, + settings: SerialPortSettings, + retries: usize, + wait_time: u64, + ) -> Result { + let port_path = Self::loop_usb_devices(usb_device, retries, wait_time)?; + + println!("found device at path {}", port_path); + + let port = serialport::new(port_path, settings.baud_rate) + .data_bits(settings.data_bits) + .parity(settings.parity) + .stop_bits(settings.stop_bits) + .flow_control(settings.flow_control) + .timeout(settings.timeout) + .open()?; + + Ok(Port { serial_port: port }) + } + + #[allow(unused)] + pub fn read(&mut self) -> Result { + let mut buf: Vec = vec![0; 256]; + + match self.serial_port.read(&mut buf[..]) { + Ok(t) => match String::from_utf8(buf[0..t].to_vec()) { + Ok(s) => Ok(SerialReadResult::Message(s)), + Err(err) => Ok(SerialReadResult::UtfConversion(err)), + }, + Err(ref e) if e.kind() == io::ErrorKind::TimedOut => Ok(SerialReadResult::Timeout), + Err(err) => Err(anyhow!("failed reading serial port ({})", err)), + } + } + + pub fn write(&mut self, msg: &str) -> Result<()> { + self.serial_port + .write(msg.as_bytes()) + .map_err(|err| anyhow!("failed writing to serial port ({})", err))?; + + Ok(()) + } + + fn loop_usb_devices(usb_device: UsbId, retries: usize, wait_time: u64) -> Result { + for _ in 0..retries { + if let Some(device) = Self::find_macroboard(&usb_device)? { + return Ok(device); + } + + println!( + "no device found with {}:{}", + usb_device.vendor_id, usb_device.product_id + ); + + std::thread::sleep(Duration::from_secs(wait_time)); + } + + Err(anyhow!("no device found")) + } + + fn find_macroboard(usb_device: &UsbId) -> Result> { + let available_ports = serialport::available_ports() + .map_err(|err| anyhow!("error querying serial ports ( {})", err))?; + + for available_port in available_ports.iter() { + if let serialport::SerialPortType::UsbPort(usb_info) = &available_port.port_type { + // check for the correct device + if usb_info.vid == usb_device.vendor_id && usb_info.pid == usb_device.product_id { + return Ok(Some(available_port.port_name.clone())); + } + } + } + + return Ok(None); + } +}