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
Cargo.lock
mount

View file

@ -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<f64, PrinterUnits>;
/// 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();
println!("Using absolute movements");
loop {

View file

@ -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<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");
let serial_ports = tokio_serial::available_ports()
.expect("Could not list serial ports")
@ -22,7 +47,7 @@ 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

View file

@ -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<T: AsyncRead + AsyncWrite + Sized> {
pub state: State,
serial_tx: SplitSink<Framed<SerialStream, LineCodec>, String>,
serial_rx: SplitStream<Framed<SerialStream, LineCodec>>,
serial_tx: SplitSink<Framed<T, LineCodec>, String>,
serial_rx: SplitStream<Framed<T, LineCodec>>,
last_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
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());
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<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");
pub async fn connect(port: S) -> Result<Self, PrinterError> {
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<PrinterPosition, PrinterError> {
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<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;
impl Decoder for LineCodec {