Initial commit
This commit is contained in:
commit
2f90902bb6
5 changed files with 301 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
Cargo.lock
|
||||
|
||||
target/
|
13
.vscode/tasks.json
vendored
Normal file
13
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "run",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"label": "rust: cargo run"
|
||||
}
|
||||
]
|
||||
}
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -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"
|
165
src/main.rs
Normal file
165
src/main.rs
Normal file
|
@ -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<Option<Port>> = 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::<u32>() {
|
||||
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();
|
||||
}
|
108
src/port.rs
Normal file
108
src/port.rs
Normal file
|
@ -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<dyn SerialPort>,
|
||||
}
|
||||
|
||||
impl Port {
|
||||
pub fn open(
|
||||
usb_device: UsbId,
|
||||
settings: SerialPortSettings,
|
||||
retries: usize,
|
||||
wait_time: u64,
|
||||
) -> Result<Self> {
|
||||
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<SerialReadResult> {
|
||||
let mut buf: Vec<u8> = 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<String> {
|
||||
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<Option<String>> {
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue