Compare commits

...

2 commits

Author SHA1 Message Date
def0ecee5c Implement zeroing 2025-02-24 23:22:39 +01:00
79ddd63935 Improve documentation 2025-02-24 22:51:53 +01:00
7 changed files with 87 additions and 47 deletions

View file

@ -18,6 +18,7 @@ pub enum Axis {
#[derive(Debug)]
pub enum GamepadEvent {
AxisPosition(Axis, f32),
NullAxesPressed,
TerminatePressed,
}
@ -95,6 +96,7 @@ impl Gamepad {
}
}
}
_ => {}
}
}
(
@ -115,6 +117,7 @@ impl Gamepad {
Some(GamepadEvent::AxisPosition(Axis::ZPositive, value))
}
ButtonPressed(gilrs::Button::Start, _) => Some(GamepadEvent::TerminatePressed),
ButtonPressed(gilrs::Button::North, _) => Some(GamepadEvent::NullAxesPressed),
_ => None,
}
}

View file

@ -1,4 +1,4 @@
use crate::gamepad::Gamepad;
use crate::gamepad::{Gamepad, GamepadEvent};
use crate::printer::Printer;
use anyhow::{Context, Result, anyhow};
use euclid::{Vector3D, vec3};
@ -38,6 +38,9 @@ pub fn jog(gamepad: &mut Gamepad, mut printer: Printer) -> Result<()> {
loop {
let events = gamepad.get_pending();
if events.iter().any(|e| matches!(e, GamepadEvent::NullAxesPressed)){
printer.set_position(Some(0.), Some(0.), Some(0.)).with_context(|| anyhow!("Zeroing printer position failed"))?;
}
let (set_point_x, set_point_y, set_point_z) = gamepad.speed_set_point(&events);
let distance: PrinterVec = vec3(

View file

@ -0,0 +1,29 @@
use super::*;
/// Set Position
#[derive(Debug)]
pub struct G92Command {
pub x: Option<f64>,
pub y: Option<f64>,
pub z: Option<f64>,
}
impl GcodeCommand for G92Command {
type Reply = ();
fn command(&self) -> String {
fn unpack(letter: &str, o: Option<f64>) -> String {
o.map(|x| format!("{}{:.3}", letter, x)).unwrap_or_default()
}
format!(
"G92 {} {} {}",
unpack("X", self.x),
unpack("Y", self.y),
unpack("Z", self.z),
)
}
fn parse_reply(&self, reply: &str) -> Result<Self::Reply> {
// Usual position reply is swallowed by IO thread
super::parse_empty_reply(self.command(), reply)
}
}

View file

@ -18,6 +18,7 @@ impl GcodeCommand for M114Command {
}
fn parse_reply(&self, reply: &str) -> Result<Self::Reply> {
// Usual position reply is swallowed by IO thread
super::parse_empty_reply(self.command(), reply)
}
}

View file

@ -1,24 +0,0 @@
use super::*;
/// Auto Home
#[derive(Debug)]
pub struct M997Command;
impl GcodeCommand for M997Command {
type Reply = ();
fn command(&self) -> String {
"M997".into()
}
fn parse_reply(&self, reply: &str) -> Result<Self::Reply> {
if !reply.is_empty() {
Err(GcodeReplyError {
sent_command: self.command(),
parsed_input: reply.to_string(),
problem: "Expected no reply".to_string(),
})
} else {
Ok(())
}
}
}

View file

@ -3,6 +3,10 @@
/// - and the meaning of the reply can be parsed with `GcodeCommand::parse_reply(reply)`
///
/// The intended use is though the `Printer::send_gcode()` function.
use regex::Regex;
use std::fmt::Debug;
use lazy_static::lazy_static;
mod g0;
mod g28;
mod g90;
@ -10,19 +14,16 @@ mod g91;
mod m114;
mod m154;
mod m155;
mod m997;
mod g92;
pub use g0::G0Command;
pub use g28::G28Command;
pub use g90::G90Command;
pub use g91::G91Command;
use lazy_static::lazy_static;
pub use g92::G92Command;
pub use m114::M114Command;
pub use m154::M154Command;
pub use m155::M155Command;
pub use m997::M997Command;
use regex::Regex;
use std::fmt::Debug;
type Result<T> = std::result::Result<T, GcodeReplyError>;

View file

@ -5,7 +5,7 @@ use self::gcode::{
G0Command, G28Command, G90Command, GcodeReplyError, M154Command, parse_autoreport_line,
};
use crate::printer::gcode::{
AutoReport, G91Command, M114Command, M155Command, is_temperature_report,
AutoReport, G91Command, G92Command, M114Command, M155Command, is_temperature_report,
};
pub use gcode::GcodeCommand;
use regex::Regex;
@ -52,13 +52,19 @@ pub enum AutoReportSetting {
#[derive(Debug)]
pub enum PrinterError {
IO(io::Error),
PrinterTaskDown,
OutputChannelDropped,
/// The IO thread crashed. This can be recovered by dropping the `Printer`
/// struct and connecting a new one.
IoThreadDied,
/// The reply from the printer didn't match the GCODE that was sent
GcodeReply(GcodeReplyError),
// If the "ok" line can't be parsed
ConfirmationError { parsed_string: String },
/// The "ok" line can't be parsed. This can happen if the printer doesn't
/// have `#define ADVANCED_OK`.
ConfirmationError {
parsed_string: String,
},
/// The printer didn't reply to a GCODE command in time.
NoResponseFromPrinter(String),
// If the initial printer configuration fails
/// The initial printer configuration failed
InitializationError(String),
}
@ -69,6 +75,7 @@ impl std::fmt::Display for PrinterError {
}
impl std::error::Error for PrinterError {}
/// Position of the printer at a point in time in printer units (usually mm)
#[derive(Debug, Clone, Copy)]
pub struct PrinterPosition {
pub x: f64,
@ -82,6 +89,7 @@ pub enum MovementMode {
RelativeMovements,
}
/// State that a printer may have during operation
#[derive(Debug, Clone)]
pub struct State {
pub position: PrinterPosition,
@ -92,9 +100,10 @@ pub struct State {
pub struct Printer {
pub state: Arc<Mutex<State>>,
/// Queue a command to be sent to the IO thread
to_io_thread: std::sync::mpsc::Sender<String>,
to_io_thread: Sender<String>,
/// Used read replies as received from the printer. Should not be written to
from_io_thread: std::sync::mpsc::Receiver<Vec<u8>>,
from_io_thread: Receiver<Vec<u8>>,
/// Maximum
maximum_buffer_capacity: usize,
}
@ -114,13 +123,14 @@ impl Printer {
Err(RecvTimeoutError::Timeout) => {
Err(PrinterError::NoResponseFromPrinter(command_text))
}
Err(RecvTimeoutError::Disconnected) => Err(PrinterError::OutputChannelDropped),
Err(RecvTimeoutError::Disconnected) => Err(PrinterError::IoThreadDied),
}
}
pub fn connect(mut port: TTYPort) -> Result<Self, PrinterError> {
// 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:
// The printer will send some info after connecting for the first time. We need to wait for
// this to be received before sending anything on our own as it will otherwise stop
// responding for some reason:
port.set_timeout(SERIALPORT_TIMEOUT)
.expect("Cannot set serial port timeout");
@ -164,7 +174,7 @@ impl Printer {
from_io_thread,
to_io_thread,
state: state.clone(),
maximum_buffer_capacity: 0, // this is updated on the next call to `send_gcode()`
maximum_buffer_capacity: 0, // this is updated below
};
// This implicitly sets `res.last_buffer_capacity`
@ -204,12 +214,13 @@ impl Printer {
}
/// Parse the "Ok" confirmation line that the printer sends after every successfully received
/// command.
/// command. The printers Marlin firmware must have `#define ADVANCED_OK`.
fn parse_ok(line: &str) -> Result<usize, PrinterError> {
let make_err = || PrinterError::ConfirmationError {
parsed_string: line.to_string(),
};
lazy_static! {
// P = planner buffer, B = unprocessed command buffer
static ref RE: Regex = Regex::new(r"ok P(\d+) B(\d+)").unwrap();
}
let captures = RE.captures(line).ok_or_else(make_err)?;
@ -374,18 +385,34 @@ impl Printer {
}
}
/// Set the current position of the printer axis in software without physically moving them
pub fn set_position(
&mut self,
x: Option<f64>,
y: Option<f64>,
z: Option<f64>,
) -> Result<(), PrinterError> {
self.send_gcode(G92Command { x, y, z })
}
/// Background thread that handles direct communication with the printer serial port
///
/// Parameters
/// * `state` - State that is shared between background thread and the thread that owns the
/// according `Printer` struct
/// * `to_user_thread` - Replies from the printer to the last GCODE command on
/// `from_user_thread` will be sent here. Only whole lines are submitted
/// , and the `ok ...` lines will be filtered out.
/// * `from_user_thread` - GCODE commands may be sent here one at a time.
/// They should not end in `\n`.
/// * `port` - Serial port that the printer is connected to
/// * `state` - State that is shared between background thread and the
/// thread that owns the according `Printer` struct
fn io_thread_work(
to_user_thread: Sender<Vec<u8>>,
from_user_thread: Receiver<String>,
mut port: TTYPort,
state: Arc<Mutex<State>>,
) {
// Bytes that were read from the port that don't constitute a whole line
// Bytes that were read from the port that don't constitute a whole line yet
let mut partial_reads: Vec<u8> = Vec::new();
loop {
match from_user_thread.recv_timeout(Duration::from_millis(10)) {
@ -396,10 +423,10 @@ impl Printer {
&mut partial_reads,
to_user_thread.clone(),
),
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => {
handle_printer_autoreport(&mut port, &mut partial_reads, state.clone());
}
Err(RecvTimeoutError::Disconnected) => break,
}
}
}