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
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
mount
|
mount
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue