Implement connecting to raw file descriptors

This commit is contained in:
zaubentrucker 2024-10-17 23:00:50 +02:00
parent 5954377a89
commit c7a35d033e
5 changed files with 112 additions and 41 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea

1
red/.gitignore vendored
View file

@ -1,3 +1,4 @@
.vscode
target target
Cargo.lock Cargo.lock
mount mount

View file

@ -4,6 +4,7 @@ use euclid::{vec3, Vector3D};
use futures::never::Never; use futures::never::Never;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::time::sleep; use tokio::time::sleep;
/// Time that a single movement command should take /// Time that a single movement command should take
@ -32,7 +33,7 @@ pub struct PrinterUnits;
pub type PrinterVec = Vector3D<f64, PrinterUnits>; pub type PrinterVec = Vector3D<f64, PrinterUnits>;
/// Jog the gantry by pumping loads of gcode into the printer board /// Jog the gantry by pumping loads of gcode into the printer board
pub async fn jog(gamepad: Arc<Gamepad>, mut printer: Printer) -> Never { pub async fn jog<T: AsyncRead + AsyncWrite + Sized>(gamepad: Arc<Gamepad>, mut printer: Printer<T>) -> Never {
printer.use_absolute_movements().await.unwrap(); printer.use_absolute_movements().await.unwrap();
println!("Using absolute movements"); println!("Using absolute movements");
loop { loop {

View file

@ -1,4 +1,6 @@
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
use std::os::fd::RawFd;
use futures::never::Never; use futures::never::Never;
use red::gamepad; use red::gamepad;
use red::jogger; use red::jogger;
@ -8,6 +10,29 @@ use tokio_serial::SerialPortInfo;
#[tokio::main] #[tokio::main]
async fn main() -> Never { async fn main() -> Never {
let args: Vec<String> = std::env::args().collect();
if args.len() > 1 {
inherit_printer().await
}
else {
look_for_printer().await
}
}
async fn inherit_printer() -> Never {
let args: Vec<String> = 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"); println!("Entering App");
let serial_ports = tokio_serial::available_ports() let serial_ports = tokio_serial::available_ports()
.expect("Could not list serial 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()); let port_path = Path::new("/dev").join(Path::new(&port.port_name).file_name().unwrap());
println!("Found serial port: {:?}", &port_path); println!("Found serial port: {:?}", &port_path);
let gamepad = gamepad::Gamepad::new().await.unwrap(); 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 .await
.unwrap(); .unwrap();
jogger::jog(gamepad, printer).await jogger::jog(gamepad, printer).await
} }

View file

@ -5,9 +5,12 @@ use bytes::BytesMut;
use futures::sink::SinkExt; use futures::sink::SinkExt;
use futures::stream::{SplitSink, SplitStream, StreamExt}; use futures::stream::{SplitSink, SplitStream, StreamExt};
use regex::Regex; 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 std::{fmt::Write, io, str};
use tokio; use tokio;
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::time::timeout; use tokio::time::timeout;
use tokio_serial::{SerialPortBuilderExt, SerialStream}; use tokio_serial::{SerialPortBuilderExt, SerialStream};
use tokio_util::codec::{Decoder, Encoder, Framed}; 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 RECV_BUFFER_CAPACITY: usize = 32;
const BAUD_RATE: u32 = 115200; const BAUD_RATE: u32 = 115200;
const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(2);
pub enum Port {
OpenFd(RawFd),
Path(String),
}
#[derive(Debug)] #[derive(Debug)]
pub enum PrinterError { pub enum PrinterError {
IO(std::io::Error), IO(std::io::Error),
@ -54,55 +64,65 @@ pub struct State {
pub movement_mode: MovementMode, pub movement_mode: MovementMode,
} }
pub struct Printer { pub struct Printer<T: AsyncRead + AsyncWrite + Sized> {
pub state: State, pub state: State,
serial_tx: SplitSink<Framed<SerialStream, LineCodec>, String>, serial_tx: SplitSink<Framed<T, LineCodec>, String>,
serial_rx: SplitStream<Framed<SerialStream, LineCodec>>, serial_rx: SplitStream<Framed<T, LineCodec>>,
last_buffer_capacity: usize, last_buffer_capacity: usize,
maximum_buffer_capacity: usize, maximum_buffer_capacity: usize,
} }
impl Printer { impl<S: AsyncRead + AsyncWrite + Sized> Printer<S> {
/// Send gcode to the printer and parse its reply /// Send gcode to the printer and parse its reply
async fn send_gcode<T: GcodeCommand>(&mut self, command: T) -> Result<T::Reply, PrinterError> { async fn send_gcode<T: GcodeCommand>(
&mut self,
command: T,
ignore_pos: bool,
timeout: Duration,
) -> Result<T::Reply, PrinterError> {
let command_text = format!("{}\n", command.command()); let command_text = format!("{}\n", command.command());
self.serial_tx.send(command_text.clone()).await.unwrap(); self.serial_tx.send(command_text.clone()).await.unwrap();
self.serial_tx.flush().await.unwrap();
let mut reply = String::with_capacity(RECV_BUFFER_CAPACITY); let mut reply = String::with_capacity(RECV_BUFFER_CAPACITY);
loop { loop {
let line = self let mut line = None;
.serial_rx let timeout = Instant::now() + timeout;
.next() while line.is_none() && Instant::now() < timeout {
.await line = self.serial_rx.next().await;
.ok_or(PrinterError::NoResponseFromPrinter( if line.is_none() {
"There are no more lines to get from the printer. Did the port close?" tokio::time::sleep(Duration::from_millis(5)).await;
.to_string(), }
))?; }
let line = line.unwrap(); if let Some(line) = line {
if line.contains("ok") { match line {
self.last_buffer_capacity = Self::parse_ok(&line)?; Ok(line) => {
let reply = command.parse_reply(&reply); if ignore_pos && (line.starts_with(" T:") || line.starts_with("X:")) {
return reply.map_err(PrinterError::GcodeReply); continue;
} else { }
reply.push_str(&line); 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<Self, PrinterError> { pub async fn connect(port: S) -> Result<Self, PrinterError> {
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");
let connection = LineCodec.framed(port); let connection = LineCodec.framed(port);
let (serial_tx, mut serial_rx) = connection.split(); 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 // 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: // to be received as it will otherwise stop responding for some reason:
loop { 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 { match message {
Some(Ok(reply)) => { Some(Ok(reply)) => {
println!("got stuff: {:?}", reply); println!("got stuff: {:?}", reply);
@ -112,6 +132,9 @@ impl Printer {
if reply.contains("ok") { if reply.contains("ok") {
break; break;
} }
if reply.starts_with("X:") {
break;
}
} }
Some(Err(e)) => { Some(Err(e)) => {
println!("Error reading from serial port: {:?}", e); println!("Error reading from serial port: {:?}", e);
@ -197,7 +220,7 @@ impl Printer {
/// Update the internal position by asking the printer for it /// Update the internal position by asking the printer for it
async fn update_position(&mut self) -> Result<PrinterPosition, PrinterError> { async fn update_position(&mut self) -> Result<PrinterPosition, PrinterError> {
let res = self.send_gcode(M114Command).await?; let res = self.send_gcode(M114Command, false, DEFAULT_COMMAND_TIMEOUT).await?;
self.state.position = res; self.state.position = res;
Ok(res) Ok(res)
} }
@ -210,7 +233,7 @@ impl Printer {
/// itself. (See its documentation) /// itself. (See its documentation)
pub async fn use_absolute_movements(&mut self) -> Result<(), PrinterError> { pub async fn use_absolute_movements(&mut self) -> Result<(), PrinterError> {
self.state.movement_mode = MovementMode::AbsoluteMovements; 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. /// Switch the printer to relative movement mode.
@ -221,7 +244,7 @@ impl Printer {
/// itself. (See its documentation) /// itself. (See its documentation)
pub async fn use_relative_movements(&mut self) -> Result<(), PrinterError> { pub async fn use_relative_movements(&mut self) -> Result<(), PrinterError> {
self.state.movement_mode = MovementMode::RelativeMovements; 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 /// 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 /// * `x, y, z` - Whether the axis should be homed. Axis that are set to `false` will not be
/// homed., /// homed.,
pub async fn auto_home(&mut self, x: bool, y: bool, z: bool) -> Result<(), PrinterError> { 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 { self.state.position = PrinterPosition {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
@ -265,11 +288,11 @@ impl Printer {
}; };
if let MovementMode::AbsoluteMovements = self.state.movement_mode { if let MovementMode::AbsoluteMovements = self.state.movement_mode {
self.use_relative_movements().await?; 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?; self.use_absolute_movements().await?;
res res
} else { } else {
self.send_gcode(command).await self.send_gcode(command, true, DEFAULT_COMMAND_TIMEOUT).await
}?; }?;
self.state.position.x += x; self.state.position.x += x;
@ -303,11 +326,11 @@ impl Printer {
}; };
if let MovementMode::RelativeMovements = self.state.movement_mode { if let MovementMode::RelativeMovements = self.state.movement_mode {
self.use_absolute_movements().await?; 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?; self.use_relative_movements().await?;
res res
} else { } else {
self.send_gcode(command).await self.send_gcode(command, true, DEFAULT_COMMAND_TIMEOUT).await
}?; }?;
self.state.position.x = x; self.state.position.x = x;
@ -318,6 +341,26 @@ impl Printer {
} }
} }
impl Printer<SerialStream> {
pub async fn connect_to_path(port_path: &str) -> Result<Self, PrinterError> {
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<File> {
pub async fn connect_to_raw_fd(port: RawFd) -> Result<Self, PrinterError> {
let port = unsafe { File::from_raw_fd(port) };
Self::connect(port).await
}
}
struct LineCodec; struct LineCodec;
impl Decoder for LineCodec { impl Decoder for LineCodec {