Implement connecting to raw file descriptors
This commit is contained in:
parent
5954377a89
commit
c7a35d033e
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.idea
|
1
red/.gitignore
vendored
1
red/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
.vscode
|
||||
target
|
||||
Cargo.lock
|
||||
mount
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue