From c7a35d033e243718ede8782c2e34d89e6bcbe438 Mon Sep 17 00:00:00 2001 From: zaubentrucker Date: Thu, 17 Oct 2024 23:00:50 +0200 Subject: [PATCH] Implement connecting to raw file descriptors --- .gitignore | 1 + red/.gitignore | 1 + red/src/jogger.rs | 3 +- red/src/main.rs | 29 +++++++++- red/src/printer/mod.rs | 119 ++++++++++++++++++++++++++++------------- 5 files changed, 112 insertions(+), 41 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/red/.gitignore b/red/.gitignore index 77a334a..94c4620 100644 --- a/red/.gitignore +++ b/red/.gitignore @@ -1,3 +1,4 @@ +.vscode target Cargo.lock mount diff --git a/red/src/jogger.rs b/red/src/jogger.rs index 5902b69..7949579 100644 --- a/red/src/jogger.rs +++ b/red/src/jogger.rs @@ -4,6 +4,7 @@ use euclid::{vec3, Vector3D}; use futures::never::Never; use std::sync::Arc; use std::time::Duration; +use tokio::io::{AsyncRead, AsyncWrite}; use tokio::time::sleep; /// Time that a single movement command should take @@ -32,7 +33,7 @@ pub struct PrinterUnits; pub type PrinterVec = Vector3D; /// Jog the gantry by pumping loads of gcode into the printer board -pub async fn jog(gamepad: Arc, mut printer: Printer) -> Never { +pub async fn jog(gamepad: Arc, mut printer: Printer) -> Never { printer.use_absolute_movements().await.unwrap(); println!("Using absolute movements"); loop { diff --git a/red/src/main.rs b/red/src/main.rs index 2888426..dacf37b 100644 --- a/red/src/main.rs +++ b/red/src/main.rs @@ -1,4 +1,6 @@ #![warn(rust_2018_idioms)] + +use std::os::fd::RawFd; use futures::never::Never; use red::gamepad; use red::jogger; @@ -8,6 +10,29 @@ use tokio_serial::SerialPortInfo; #[tokio::main] async fn main() -> Never { + let args: Vec = std::env::args().collect(); + + if args.len() > 1 { + inherit_printer().await + } + else { + look_for_printer().await + } + +} + +async fn inherit_printer() -> Never { + let args: Vec = std::env::args().collect(); + + let fd: RawFd = args[1].parse().expect("Not a valid FD"); + let gamepad = gamepad::Gamepad::new().await.unwrap(); + let printer = Printer::connect_to_raw_fd(fd) + .await + .unwrap(); + jogger::jog(gamepad, printer).await +} + +async fn look_for_printer() -> Never { println!("Entering App"); let serial_ports = tokio_serial::available_ports() .expect("Could not list serial ports") @@ -22,8 +47,8 @@ async fn main() -> Never { let port_path = Path::new("/dev").join(Path::new(&port.port_name).file_name().unwrap()); println!("Found serial port: {:?}", &port_path); let gamepad = gamepad::Gamepad::new().await.unwrap(); - let printer = Printer::connect(&port_path.as_os_str().to_string_lossy()) + let printer = Printer::connect_to_path(&port_path.as_os_str().to_string_lossy()) .await .unwrap(); jogger::jog(gamepad, printer).await -} +} \ No newline at end of file diff --git a/red/src/printer/mod.rs b/red/src/printer/mod.rs index 42c9f59..854b096 100644 --- a/red/src/printer/mod.rs +++ b/red/src/printer/mod.rs @@ -5,9 +5,12 @@ use bytes::BytesMut; use futures::sink::SinkExt; use futures::stream::{SplitSink, SplitStream, StreamExt}; use regex::Regex; -use std::time::Duration; +use std::os::fd::{FromRawFd, RawFd}; +use std::time::{Duration, Instant}; use std::{fmt::Write, io, str}; use tokio; +use tokio::fs::File; +use tokio::io::{AsyncRead, AsyncWrite}; use tokio::time::timeout; use tokio_serial::{SerialPortBuilderExt, SerialStream}; use tokio_util::codec::{Decoder, Encoder, Framed}; @@ -24,6 +27,13 @@ use self::gcode::{G0Command, G28Command, G90Command, GcodeReplyError}; const RECV_BUFFER_CAPACITY: usize = 32; const BAUD_RATE: u32 = 115200; +const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(2); + +pub enum Port { + OpenFd(RawFd), + Path(String), +} + #[derive(Debug)] pub enum PrinterError { IO(std::io::Error), @@ -54,55 +64,65 @@ pub struct State { pub movement_mode: MovementMode, } -pub struct Printer { +pub struct Printer { pub state: State, - serial_tx: SplitSink, String>, - serial_rx: SplitStream>, + serial_tx: SplitSink, String>, + serial_rx: SplitStream>, last_buffer_capacity: usize, maximum_buffer_capacity: usize, } -impl Printer { +impl Printer { /// Send gcode to the printer and parse its reply - async fn send_gcode(&mut self, command: T) -> Result { + async fn send_gcode( + &mut self, + command: T, + ignore_pos: bool, + timeout: Duration, + ) -> Result { let command_text = format!("{}\n", command.command()); self.serial_tx.send(command_text.clone()).await.unwrap(); + self.serial_tx.flush().await.unwrap(); let mut reply = String::with_capacity(RECV_BUFFER_CAPACITY); loop { - let line = self - .serial_rx - .next() - .await - .ok_or(PrinterError::NoResponseFromPrinter( - "There are no more lines to get from the printer. Did the port close?" - .to_string(), - ))?; - let line = line.unwrap(); - if line.contains("ok") { - self.last_buffer_capacity = Self::parse_ok(&line)?; - let reply = command.parse_reply(&reply); - return reply.map_err(PrinterError::GcodeReply); - } else { - reply.push_str(&line); + let mut line = None; + let timeout = Instant::now() + timeout; + while line.is_none() && Instant::now() < timeout { + line = self.serial_rx.next().await; + if line.is_none() { + tokio::time::sleep(Duration::from_millis(5)).await; + } + } + if let Some(line) = line { + match line { + Ok(line) => { + if ignore_pos && (line.starts_with(" T:") || line.starts_with("X:")) { + continue; + } + if line.contains("ok") { + self.last_buffer_capacity = Self::parse_ok(&line)?; + let reply = command.parse_reply(&reply); + return reply.map_err(PrinterError::GcodeReply); + } else { + reply.push_str(&line); + } + } + Err(e) => { + println!("Failed to read from Printer: {}", e) + } + } } } } - pub async fn connect(port_path: &str) -> Result { - let mut port = tokio_serial::new(port_path, BAUD_RATE) - .open_native_async() - .expect("Unable to open serial port"); - - port.set_exclusive(false) - .expect("Unable to set serial port exclusive to false"); - + pub async fn connect(port: S) -> Result { let connection = LineCodec.framed(port); let (serial_tx, mut serial_rx) = connection.split(); // The printer will send some info after connecting for the first time. We need to wait for this // to be received as it will otherwise stop responding for some reason: loop { - if let Ok(message) = timeout(Duration::from_secs(2), serial_rx.next()).await { + if let Ok(message) = timeout(Duration::from_millis(100), serial_rx.next()).await { match message { Some(Ok(reply)) => { println!("got stuff: {:?}", reply); @@ -112,6 +132,9 @@ impl Printer { if reply.contains("ok") { break; } + if reply.starts_with("X:") { + break; + } } Some(Err(e)) => { println!("Error reading from serial port: {:?}", e); @@ -197,7 +220,7 @@ impl Printer { /// Update the internal position by asking the printer for it async fn update_position(&mut self) -> Result { - let res = self.send_gcode(M114Command).await?; + let res = self.send_gcode(M114Command, false, DEFAULT_COMMAND_TIMEOUT).await?; self.state.position = res; Ok(res) } @@ -210,7 +233,7 @@ impl Printer { /// itself. (See its documentation) pub async fn use_absolute_movements(&mut self) -> Result<(), PrinterError> { self.state.movement_mode = MovementMode::AbsoluteMovements; - self.send_gcode(G90Command).await + self.send_gcode(G90Command, true, DEFAULT_COMMAND_TIMEOUT).await } /// Switch the printer to relative movement mode. @@ -221,7 +244,7 @@ impl Printer { /// itself. (See its documentation) pub async fn use_relative_movements(&mut self) -> Result<(), PrinterError> { self.state.movement_mode = MovementMode::RelativeMovements; - self.send_gcode(G91Command).await + self.send_gcode(G91Command, true, DEFAULT_COMMAND_TIMEOUT).await } /// Home the printer using the hardware endstops @@ -230,7 +253,7 @@ impl Printer { /// * `x, y, z` - Whether the axis should be homed. Axis that are set to `false` will not be /// homed., pub async fn auto_home(&mut self, x: bool, y: bool, z: bool) -> Result<(), PrinterError> { - self.send_gcode(G28Command::new(x, y, z)).await?; + self.send_gcode(G28Command::new(x, y, z), true, DEFAULT_COMMAND_TIMEOUT).await?; self.state.position = PrinterPosition { x: 0.0, y: 0.0, @@ -265,11 +288,11 @@ impl Printer { }; if let MovementMode::AbsoluteMovements = self.state.movement_mode { self.use_relative_movements().await?; - let res = self.send_gcode(command).await; + let res = self.send_gcode(command, true, DEFAULT_COMMAND_TIMEOUT).await; self.use_absolute_movements().await?; res } else { - self.send_gcode(command).await + self.send_gcode(command, true, DEFAULT_COMMAND_TIMEOUT).await }?; self.state.position.x += x; @@ -303,11 +326,11 @@ impl Printer { }; if let MovementMode::RelativeMovements = self.state.movement_mode { self.use_absolute_movements().await?; - let res = self.send_gcode(command).await; + let res = self.send_gcode(command, true, DEFAULT_COMMAND_TIMEOUT).await; self.use_relative_movements().await?; res } else { - self.send_gcode(command).await + self.send_gcode(command, true, DEFAULT_COMMAND_TIMEOUT).await }?; self.state.position.x = x; @@ -318,6 +341,26 @@ impl Printer { } } +impl Printer { + pub async fn connect_to_path(port_path: &str) -> Result { + let mut port = tokio_serial::new(port_path, BAUD_RATE) + .open_native_async() + .expect("Unable to open serial port"); + + port.set_exclusive(true) + .expect("Unable to set serial port exclusive to false"); + + Self::connect(port).await + } +} + +impl Printer { + pub async fn connect_to_raw_fd(port: RawFd) -> Result { + let port = unsafe { File::from_raw_fd(port) }; + Self::connect(port).await + } +} + struct LineCodec; impl Decoder for LineCodec {