Start building companion serial reader app
This commit is contained in:
parent
f74e59ddad
commit
f0bfc77504
6 changed files with 275 additions and 6 deletions
|
@ -13,6 +13,9 @@ defmt = "0.3"
|
||||||
defmt-rtt = "0.3"
|
defmt-rtt = "0.3"
|
||||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||||
|
|
||||||
|
usb-device= "0.2"
|
||||||
|
usbd-serial = "0.1"
|
||||||
|
|
||||||
# We're using a Pico by default on this template
|
# We're using a Pico by default on this template
|
||||||
rp-pico = "0.5"
|
rp-pico = "0.5"
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,13 @@ use bsp::hal::{
|
||||||
clocks::{init_clocks_and_plls, Clock},
|
clocks::{init_clocks_and_plls, Clock},
|
||||||
pac,
|
pac,
|
||||||
sio::Sio,
|
sio::Sio,
|
||||||
|
usb::UsbBus,
|
||||||
watchdog::Watchdog,
|
watchdog::Watchdog,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use usb_device::{class_prelude::*, prelude::*};
|
||||||
|
use usbd_serial::SerialPort;
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
info!("Program start");
|
info!("Program start");
|
||||||
|
@ -58,16 +62,54 @@ fn main() -> ! {
|
||||||
|
|
||||||
let mut led_pin = pins.led.into_push_pull_output();
|
let mut led_pin = pins.led.into_push_pull_output();
|
||||||
|
|
||||||
|
led_pin.set_low();
|
||||||
|
|
||||||
|
// Set up the USB driver
|
||||||
|
let usb_bus = UsbBusAllocator::new(UsbBus::new(
|
||||||
|
pac.USBCTRL_REGS,
|
||||||
|
pac.USBCTRL_DPRAM,
|
||||||
|
clocks.usb_clock,
|
||||||
|
true,
|
||||||
|
&mut pac.RESETS,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Set up the USB Communications Class Device driver
|
||||||
|
let mut _serial = SerialPort::new(&usb_bus);
|
||||||
|
|
||||||
|
// Create a USB device with a fake VID and PID
|
||||||
|
let mut _usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
|
||||||
|
.manufacturer("Fake company")
|
||||||
|
.product("Serial port")
|
||||||
|
.serial_number("TEST")
|
||||||
|
.device_class(2) // from: https://www.usb.org/defined-class-codes
|
||||||
|
.build();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
info!("on!");
|
info!("start!");
|
||||||
led_pin.set_high().unwrap();
|
|
||||||
|
|
||||||
delay.delay_ms(200);
|
// delay.delay_ms(1000);
|
||||||
|
|
||||||
info!("off!");
|
// serial.write("test".as_bytes());
|
||||||
led_pin.set_low().unwrap();
|
|
||||||
|
|
||||||
delay.delay_ms(200);
|
// serial_send(&mut serial, "test");
|
||||||
|
// delay.delay_ms(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serial_send(serial: &mut SerialPort<UsbBus>, msg: &str) {
|
||||||
|
let buf = [0u8; 64];
|
||||||
|
|
||||||
|
// Send back to the host
|
||||||
|
let mut wr_ptr = &buf[..msg.len()];
|
||||||
|
|
||||||
|
while !wr_ptr.is_empty() {
|
||||||
|
match serial.write(wr_ptr) {
|
||||||
|
Ok(len) => wr_ptr = &wr_ptr[len..],
|
||||||
|
// On error, just drop unwritten data.
|
||||||
|
// One possible error is Err(WouldBlock), meaning the USB
|
||||||
|
// write buffer is full.
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
7
rust/serial_reader/.vscode/settings.json
vendored
Normal file
7
rust/serial_reader/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"activityBar.background": "#083144",
|
||||||
|
"titleBar.activeBackground": "#0B4560",
|
||||||
|
"titleBar.activeForeground": "#F5FBFE"
|
||||||
|
}
|
||||||
|
}
|
11
rust/serial_reader/Cargo.toml
Normal file
11
rust/serial_reader/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[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 = "*"
|
||||||
|
gtk = { version = "*", features = ["v3_24"] }
|
||||||
|
anyhow = { version = "1.0", features = ["backtrace"] }
|
104
rust/serial_reader/src/main.rs
Normal file
104
rust/serial_reader/src/main.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::atomic::Ordering::SeqCst;
|
||||||
|
// use std::thread::JoinHandle;
|
||||||
|
|
||||||
|
use gtk::{prelude::*, TextView};
|
||||||
|
use gtk::{Application, ApplicationWindow};
|
||||||
|
|
||||||
|
use gtk::{Button, Entry, Orientation};
|
||||||
|
|
||||||
|
mod port;
|
||||||
|
use port::*;
|
||||||
|
|
||||||
|
static CONNECTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
// static mut THREAD: Option<JoinHandle<()>> = None;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = Application::builder()
|
||||||
|
.application_id("org.example.HelloWorld")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
app.connect_activate(|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");
|
||||||
|
|
||||||
|
top_bar_box.pack_end(&connect_button, true, true, 3);
|
||||||
|
top_bar_box.pack_end(&vid, true, true, 3);
|
||||||
|
top_bar_box.pack_end(&pid, true, true, 3);
|
||||||
|
|
||||||
|
let serial_info = TextView::new();
|
||||||
|
|
||||||
|
master_box.pack_end(&serial_info, true, true, 3);
|
||||||
|
master_box.pack_end(&top_bar_box, false, true, 3);
|
||||||
|
|
||||||
|
window.add(&master_box);
|
||||||
|
|
||||||
|
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: 57600,
|
||||||
|
data_bits: DataBits::Eight,
|
||||||
|
parity: Parity::None,
|
||||||
|
stop_bits: StopBits::One,
|
||||||
|
flow_control: FlowControl::None,
|
||||||
|
timeout: Duration::from_millis(2500),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mut port) = Port::open(usb_id, serialport_settings) {
|
||||||
|
CONNECTED.store(true, SeqCst);
|
||||||
|
|
||||||
|
// unsafe {
|
||||||
|
// THREAD = Some(std::thread::spawn(move || {
|
||||||
|
|
||||||
|
// loop forever
|
||||||
|
loop {
|
||||||
|
// handle incoming message
|
||||||
|
match port.read().unwrap() {
|
||||||
|
SerialReadResult::Message(msg) => {
|
||||||
|
let buffer = serial_info.buffer().unwrap();
|
||||||
|
|
||||||
|
buffer.insert(&mut buffer.end_iter(), &msg);
|
||||||
|
}
|
||||||
|
SerialReadResult::UtfConversion(err) => println!("{:?}", err),
|
||||||
|
SerialReadResult::Timeout => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't forget to make all widgets visible.
|
||||||
|
window.show_all();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
102
rust/serial_reader/src/port.rs
Normal file
102
rust/serial_reader/src/port.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
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) -> Result<Self> {
|
||||||
|
let port_path = Self::loop_usb_devices(usb_device)?;
|
||||||
|
|
||||||
|
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> = (0..255).collect();
|
||||||
|
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
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) -> Result<String> {
|
||||||
|
loop {
|
||||||
|
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(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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