gcode-rs
gcode-rs copied to clipboard
Testing on hardware and in real applications
I'm at a point where this crate will need a bit of testing in the real world to work out what the most ergonomic way of doing things is.
I'm looking for people who are experimenting with embedded devices and need to work with gcode. If you are developing a desktop application that deals with gcodes, I'm keen for your feedback too!
If you try out this crate and have any feedback, or if you are just experimenting and want to know how best to use it, let me know by making a comment here and I'll try to do everything I can to help out :)
You don't have an example of the high level parser yet, looking at the test code it looks like you are passing the results of the low level parser through println! to the high level parser:
let lines = low_level_parser .inspect(|c| println!("{:?}", c)) .collect::<Result<Vec<_>, >>() .unwrap(); let strongly_typed: Vec<> = lines.into_iter().map(type_check).collect();
since println! is part of std this won't work for embedded. Is there a different way to use the high level parser?
The inspect()
method does is lets you inspect the contents of an iterator without consuming or changing it, and I was using that to see what each element in the iterator was.
You should be able to delete it without affecting anything.
Great, got a quick version working this way:
let lexer = Tokenizer::new(GCODE.chars());
let tokens = lexer.filter_map(|t| t.ok());
let parser = BasicParser::new(tokens);
for line in parser {
if let Line::Cmd(x) = line.unwrap() {
if x.command() == (CommandType::G, 0) {
let args = x.args();
for arg in args.iter(){
match arg.kind {
ArgumentKind::X => new_x = arg.value,
ArgumentKind::Y => new_y = arg.value,
_ => {},
}
}
}
here's the full code (will make a repo once it's cleaned up a little more). https://pastebin.com/fZTJsquH
@etrombly wow, that looks awesome! Keep me updated on how you're going and if there's anything I can do to make the gcode library easier to use. It looks like it was really cumbersome to try and get the whole lexer/parser thing set up :(
It was actually pretty easy to use once I figured it out. Just had to dig around your tests to find examples. I think having an example like what I wrote would help people getting started with it. If I were making it more generic I would match on the command type and command number though.
I just rewrote the parser and deprecated the old low_level
one. The usage should be pretty much the same, I've just rearranged how you access things.
extern crate gcode;
use gcode::{Parser, Tokenizer, Line};
use gcode::parser::Number;
fn main() {
let src = include_str!("../tests/data/PI_octcat.gcode");
let lexer = Tokenizer::new(src.chars());
let tokens = lexer.filter_map(|t| t.ok());
let parser = Parser::new(tokens);
for line in parser {
handle_line(line.unwrap());
}
}
fn handle_line(line: Line) {
match line {
Line::ProgramNumber(n) => {}
Line::Cmd(cmd) => {
match (cmd.kind, cmd.number) {
(CommandKind::G, Number::Integer(90)) => { ... }
_ => {}
}
}
}
}
It's a little annoying because of the rightward drift and because you need to explicitly deal with the fact that the number for a G or M code may be a decimal. So The number is actually an enum which can either be an Integer(u32)
, or a Decimal(u32, u32)
(the numbers before and after the decimal point).
Just started working on this again since japaric updated RTFM. One way to handle rightward drift is:
const G0: (CommandKind, Number) = (CommandKind::G, Number::Integer(0));
match (cmd.kind, cmd.number) => {
G0 => { ... }
- => {}
}
A bit of a hassle defining all the constants though. Not sure how much additional size it would add to the library either.
Hello, I am trying to write a small command line tool for preprocessing Gcode using this library. I first went with trying to apply a translation to every X position using the following prototype:
extern crate gcode;
use gcode::*;
fn main() {
let src = "O1000
T1 M6
G90
G01 X-75 Y-75 S500 M3
G43 Z100 H1
G01 Z5
N20 G01 Z-20 F100";
let translate_x = 42.0;
let mut lines = gcode::parse(src);
let mut new_lines = vec!();
for line in lines {
if let Some(x) = line.value_for('X') {
let mut new_line = Gcode::new(line.mnemonic(), line.number(), line.span());
for word in line.args() {
if word.letter == 'X' {
let new_arg = Word::new(word.letter, word.value + translate_x, word.span);
new_line.add_argument(new_arg);
} else {
new_line.add_argument(word.clone());
}
}
new_lines.push(new_line);
} else {
new_lines.push(line);
}
}
for line in new_lines {
println!("{}", line);
}
}
This works well and translates the X value just fine, but I found it cumbersome to create a new Gcode and Word from existing ones. Maybe I am doing something wrong here, any idea on how to make this look better?
I'd never thought of updating a gcode in place, would it be easier if we had an args_mut()
method which yields items of &mut Word
?
What are your thoughts on this?
use gcode::{self, Gcode, Word};
fn translate_x(mut code: Gcode, dx: f32) -> Gcode {
for arg in code.args_mut() {
if arg.letter == 'X' {
arg.value += dx;
}
}
code
}
fn main() {
let src = "O1000
T1 M6
G90
G01 X-75 Y-75 S500 M3
G43 Z100 H1
G01 Z5
N20 G01 Z-20 F100";
let dx = 42.0;
let translated = gcode::parse(src).map(|g| translate_x(g, dx));
for code in translated {
println!("{}", code);
}
}
EDIT: And now it's a thing :stuck_out_tongue_winking_eye: