Handle position auto-reports
This commit is contained in:
parent
2a707c0399
commit
df88b5a40c
4 changed files with 99 additions and 47 deletions
|
@ -4,7 +4,7 @@ use anyhow::{Context, Result, anyhow};
|
|||
use futures::never::Never;
|
||||
use red::gamepad::Gamepad;
|
||||
use red::jogger;
|
||||
use red::printer::{AutoReport, Printer};
|
||||
use red::printer::{AutoReportSetting, Printer};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
@ -38,7 +38,7 @@ fn jog() -> Result<()> {
|
|||
let mut printer = Printer::connect_to_path(&printer_tty_path)
|
||||
.with_context(|| anyhow!("Initializing printer connection"))?;
|
||||
|
||||
printer.set_position_auto_report(AutoReport::EverySeconds(1))?;
|
||||
printer.set_position_auto_report(AutoReportSetting::EverySeconds(1))?;
|
||||
|
||||
jogger::jog(&mut gamepad, printer).with_context(|| anyhow!("Running jog mode"))
|
||||
}
|
||||
|
|
|
@ -15,39 +15,12 @@ impl M114Command {
|
|||
}
|
||||
|
||||
impl GcodeCommand for M114Command {
|
||||
type Reply = PrinterPosition;
|
||||
type Reply = ();
|
||||
fn command(&self) -> String {
|
||||
"M114".into()
|
||||
}
|
||||
|
||||
fn parse_reply(&self, reply: &str) -> Result<Self::Reply> {
|
||||
lazy_static! {
|
||||
static ref RE_SET: Vec<Regex> = vec![
|
||||
Regex::new(r"X:(\d+(?:\.\d+))").unwrap(),
|
||||
Regex::new(r"Y:(\d+(?:\.\d+))").unwrap(),
|
||||
Regex::new(r"Z:(\d+(?:\.\d+))").unwrap(),
|
||||
Regex::new(r"E:(\d+(?:\.\d+))").unwrap(),
|
||||
];
|
||||
}
|
||||
|
||||
let fields: Vec<Result<f64>> = RE_SET
|
||||
.iter()
|
||||
.map(|re| {
|
||||
re.captures(reply)
|
||||
.and_then(|cpt| cpt.get(1).map(|mtch| mtch.as_str().to_string()))
|
||||
.and_then(|s| s.parse().ok())
|
||||
.ok_or(GcodeReplyError {
|
||||
parsed_input: reply.to_string(),
|
||||
problem: format!("Failed to match to regex {}", re.as_str()),
|
||||
sent_command: self.command(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(PrinterPosition {
|
||||
x: fields[0].clone()?,
|
||||
y: fields[1].clone()?,
|
||||
z: fields[2].clone()?,
|
||||
})
|
||||
super::parse_empty_reply(self.command(), reply)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,16 +14,19 @@ mod g28;
|
|||
mod g90;
|
||||
mod g91;
|
||||
mod m114;
|
||||
mod m997;
|
||||
mod m154;
|
||||
mod m997;
|
||||
|
||||
use crate::printer::PrinterPosition;
|
||||
pub use g0::G0Command;
|
||||
pub use g28::G28Command;
|
||||
pub use g90::G90Command;
|
||||
pub use g91::G91Command;
|
||||
use lazy_static::lazy_static;
|
||||
pub use m114::M114Command;
|
||||
pub use m997::M997Command;
|
||||
pub use m154::M154Command;
|
||||
pub use m997::M997Command;
|
||||
use regex::Regex;
|
||||
use std::fmt::Debug;
|
||||
|
||||
type Result<T> = std::result::Result<T, GcodeReplyError>;
|
||||
|
@ -67,3 +70,58 @@ fn parse_empty_reply(sent_command: String, reply: &str) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AutoReport {
|
||||
NotRecognized,
|
||||
/// Auto-report contained printer position. The position might not contain all axes
|
||||
Position(Option<f64>, Option<f64>, Option<f64>),
|
||||
}
|
||||
|
||||
/// Parse all known kinds of auto-reports that can be sent by marlin
|
||||
/// TODO: implement temp readings
|
||||
pub fn parse_autoreport_line(line: &str) -> AutoReport {
|
||||
match parse_position_line(line) {
|
||||
Ok((x, y, z)) => AutoReport::Position(x, y, z),
|
||||
Err(_) => AutoReport::NotRecognized,
|
||||
}
|
||||
}
|
||||
|
||||
/// Can the line be interpreted as an auto-report by the printer
|
||||
fn is_auto_report(line: &str) -> bool {
|
||||
matches!(parse_autoreport_line(line), AutoReport::NotRecognized)
|
||||
}
|
||||
|
||||
fn parse_position_line(line: &str) -> Result<(Option<f64>, Option<f64>, Option<f64>)> {
|
||||
lazy_static! {
|
||||
static ref RE_SET: Vec<Regex> = vec![
|
||||
Regex::new(r"X:(-?\d+(?:\.\d+))").unwrap(),
|
||||
Regex::new(r"Y:(-?\d+(?:\.\d+))").unwrap(),
|
||||
Regex::new(r"Z:(-?\d+(?:\.\d+))").unwrap(),
|
||||
Regex::new(r"E:(-?\d+(?:\.\d+))").unwrap(),
|
||||
];
|
||||
}
|
||||
|
||||
let fields: Vec<Result<f64>> = RE_SET
|
||||
.iter()
|
||||
.map(|re| {
|
||||
re.captures(line)
|
||||
.and_then(|cpt| cpt.get(1).map(|mtch| mtch.as_str().to_string()))
|
||||
.and_then(|s| s.parse().ok())
|
||||
.ok_or(GcodeReplyError {
|
||||
parsed_input: line.to_string(),
|
||||
problem: format!("Failed to match to regex {}", re.as_str()),
|
||||
sent_command: "".into(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
if fields.iter().all(|r| r.is_err()) {
|
||||
return Err(fields[0].clone().unwrap_err());
|
||||
}
|
||||
|
||||
Ok((
|
||||
fields[0].clone().ok(),
|
||||
fields[1].clone().ok(),
|
||||
fields[2].clone().ok(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub mod gcode;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::printer::gcode::{G91Command, M114Command};
|
||||
use crate::printer::gcode::{AutoReport, G91Command, M114Command};
|
||||
pub use gcode::GcodeCommand;
|
||||
use regex::Regex;
|
||||
use serialport::SerialPort;
|
||||
|
@ -20,7 +20,7 @@ use std::sync::mpsc::TryRecvError;
|
|||
use std::time::{Duration, Instant};
|
||||
use std::{io, str};
|
||||
use std::sync::mpsc::RecvTimeoutError;
|
||||
use self::gcode::{G0Command, G28Command, G90Command, GcodeReplyError, M154Command};
|
||||
use self::gcode::{G0Command, G28Command, G90Command, GcodeReplyError, M154Command, parse_autoreport_line};
|
||||
|
||||
/// Recv buffer string will be initialized with this capacity.
|
||||
/// This should fit a simple "OK Pnn Bn" reply for GCODE commands that
|
||||
|
@ -41,7 +41,7 @@ pub enum Port {
|
|||
Path(String),
|
||||
}
|
||||
|
||||
pub enum AutoReport {
|
||||
pub enum AutoReportSetting {
|
||||
Disabled,
|
||||
EverySeconds(u64),
|
||||
}
|
||||
|
@ -221,9 +221,14 @@ impl Printer {
|
|||
}
|
||||
|
||||
/// Update the internal position by asking the printer for it
|
||||
fn update_position(&mut self) -> Result<PrinterPosition, PrinterError> {
|
||||
let res = self.send_gcode(M114Command)?;
|
||||
self.state.lock().unwrap().deref_mut().position = res;
|
||||
pub fn update_position(&mut self) -> Result<PrinterPosition, PrinterError> {
|
||||
self.send_gcode(M114Command)?;
|
||||
// The printer will handle the position reply within the IO-Thread.
|
||||
//
|
||||
// I do this because I detect and filter out auto-reports by parsing a regex, and thus I
|
||||
// can't differentiate between a position report that was polled for and one that was
|
||||
// auto-reported. (see `.::handle_printer_autoreport`)
|
||||
let res = self.state.lock().unwrap().deref_mut().position;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
@ -346,10 +351,10 @@ impl Printer {
|
|||
|
||||
/// Set an interval at which to report the printer position or disable automatic position
|
||||
/// updates
|
||||
pub fn set_position_auto_report(&mut self, report: AutoReport) -> Result<(), PrinterError> {
|
||||
pub fn set_position_auto_report(&mut self, report: AutoReportSetting) -> Result<(), PrinterError> {
|
||||
match report {
|
||||
AutoReport::Disabled => self.send_gcode(M154Command { interval: 0 }),
|
||||
AutoReport::EverySeconds(n) => self.send_gcode(M154Command { interval: n }),
|
||||
AutoReportSetting::Disabled => self.send_gcode(M154Command { interval: 0 }),
|
||||
AutoReportSetting::EverySeconds(n) => self.send_gcode(M154Command { interval: n }),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,23 +369,27 @@ impl Printer {
|
|||
mut port: TTYPort,
|
||||
state: Arc<Mutex<State>>,
|
||||
) {
|
||||
// Bytes that were read from the port that don't constitute a whole line
|
||||
let mut partial_reads: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
match from_user_thread.try_recv() {
|
||||
Ok(user_command) => handle_user_command(
|
||||
&mut port,
|
||||
user_command,
|
||||
state.clone(),
|
||||
&mut partial_reads,
|
||||
to_user_thread.clone(),
|
||||
),
|
||||
Err(TryRecvError::Disconnected) => break,
|
||||
Err(TryRecvError::Empty) => handle_printer_autoreport(&mut port, state.clone()),
|
||||
Err(TryRecvError::Empty) => handle_printer_autoreport(&mut port, &mut partial_reads, state.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for auto-report messages coming in from the printer and update the `state` accordingly
|
||||
fn handle_printer_autoreport(port: &mut TTYPort, state: Arc<Mutex<State>>) {
|
||||
fn handle_printer_autoreport(port: &mut TTYPort, partial_reads: &mut Vec<u8>, state: Arc<Mutex<State>>) {
|
||||
return;
|
||||
if port
|
||||
.bytes_to_read()
|
||||
.expect("`handle_printer_autoreport`: Failed to check for available data")
|
||||
|
@ -461,6 +470,7 @@ fn handle_user_command(
|
|||
port: &mut TTYPort,
|
||||
user_command: String,
|
||||
state: Arc<Mutex<State>>,
|
||||
partial_line: &mut Vec<u8>,
|
||||
to_user_thread: Sender<Vec<u8>>,
|
||||
) {
|
||||
// TODO: Add timeout?
|
||||
|
@ -470,19 +480,30 @@ fn handle_user_command(
|
|||
port.flush().unwrap();
|
||||
|
||||
let mut already_read_lines = Vec::new();
|
||||
let mut rest = Vec::new();
|
||||
let start_time = Instant::now();
|
||||
loop {
|
||||
if start_time.elapsed() > DEFAULT_COMMAND_TIMEOUT {
|
||||
panic!("No reply from printer within timeout");
|
||||
}
|
||||
let line = read_line(port, &mut rest, DEFAULT_COMMAND_TIMEOUT)
|
||||
let line = read_line(port, partial_line, DEFAULT_COMMAND_TIMEOUT)
|
||||
.expect("Failed to read from printer");
|
||||
|
||||
let str_line = String::from_utf8(line.clone()).expect("Read line was no valid utf8");
|
||||
println!("<< {:?}", str_line);
|
||||
|
||||
if str_line.starts_with("ok") {
|
||||
if let AutoReport::Position(x, y, z) = parse_autoreport_line(&str_line) {
|
||||
let mut position_guard = state.lock().unwrap();
|
||||
if let Some(x) = x {
|
||||
position_guard.position.x = x;
|
||||
}
|
||||
if let Some(y) = y {
|
||||
position_guard.position.y = y;
|
||||
}
|
||||
if let Some(z) = z {
|
||||
position_guard.position.z = z;
|
||||
}
|
||||
}
|
||||
else if str_line.starts_with("ok") {
|
||||
state.lock().unwrap().last_buffer_capacity =
|
||||
Printer::parse_ok(&str_line).expect("Couldn't parse line as 'ok'-message");
|
||||
to_user_thread
|
||||
|
|
Loading…
Add table
Reference in a new issue