• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cbruiz / printhor / 13625735260

03 Mar 2025 07:38AM UTC coverage: 89.928% (+1.8%) from 88.116%
13625735260

Pull #34

github

web-flow
Merge ac7b0af61 into 6708673b5
Pull Request #34: Refactor to clean-up dead code

390 of 392 new or added lines in 9 files covered. (99.49%)

2 existing lines in 2 files now uncovered.

13964 of 15528 relevant lines covered (89.93%)

786063.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.91
/printhor/src/bin/control/processing/gcode_parser.rs
1
#[allow(unused)]
2
use crate::control::{EFSXYZ, EXYZ, FXYZ, GCodeCmd, GCodeValue, N, S};
3
use crate::helpers;
4
use crate::hwa;
5
use hwa::CommChannel;
6

7
#[derive(Debug)]
8
pub enum GCodeLineParserError {
9
    /// There was an error parsing
10
    ParseError(u32),
11
    /// The Gcode was not implemented
12
    GCodeNotImplemented(u32, alloc::string::String),
13
    /// EOF Reading from parser
14
    EOF,
15
    /// Unexpected fatal error
16
    #[allow(unused)]
17
    FatalError,
18
}
19

20
#[cfg(feature = "debug-gcode")]
21
struct FormatableGCode<'a>(&'a async_gcode::GCode);
22

23
#[cfg(feature = "debug-gcode")]
24
impl core::fmt::Debug for FormatableGCode<'_> {
25
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26
        match self.0 {
27
            async_gcode::GCode::StatusCommand => {
28
                f.write_str("StatusCommand")?;
29
            }
30
            async_gcode::GCode::BlockDelete => {
31
                f.write_str("BlockDelete")?;
32
            }
33
            async_gcode::GCode::LineNumber(_ln) => {
34
                core::write!(f, "LineNumber<{:?}>", _ln)?;
35
            }
36
            async_gcode::GCode::Word(_w, _v) => {
37
                core::write!(f, "Word[{}", _w.to_uppercase())?;
38
                match _v {
39
                    async_gcode::RealValue::Literal(_l) => {
40
                        match _l {
41
                            async_gcode::Literal::RealNumber(_r) => {
42
                                let div = 10_i32.pow(_r.scale().into());
43
                                let w0 = _r.integer_part() / div;
44
                                let w1 = _r.integer_part() % div;
45
                                core::write!(
46
                                    f,
47
                                    "{}.{:0width$}",
48
                                    w0,
49
                                    w1,
50
                                    width = _r.scale() as usize
51
                                )?;
52
                            }
53
                            async_gcode::Literal::String(_s) => {
54
                                core::write!(f, " {}", _s)?;
55
                            }
56
                        }
57
                        core::write!(f, "]")?;
58
                    }
59
                    _ => {}
60
                }
61
            }
62
            async_gcode::GCode::Text(_t) => match _t {
63
                async_gcode::RealValue::Literal(async_gcode::Literal::String(_s)) => {
64
                    core::write!(f, "String[{}]", _s)?;
65
                }
66
                _ => {}
67
            },
68
            async_gcode::GCode::Execute => {
69
                core::write!(f, "Execute")?;
70
            }
71
            #[allow(unreachable_patterns)]
72
            _ => {}
73
        }
74
        Ok(())
75
    }
76
}
77
#[cfg(all(feature = "with-defmt", feature = "debug-gcode"))]
78
impl defmt::Format for FormatableGCode<'_> {
79
    fn format(&self, f: defmt::Formatter) {
80
        match self.0 {
81
            async_gcode::GCode::StatusCommand => {
82
                defmt::write!(f, "StatusCommand");
83
            }
84
            async_gcode::GCode::BlockDelete => {
85
                defmt::write!(f, "BlockDelete");
86
            }
87
            async_gcode::GCode::LineNumber(_ln) => {
88
                defmt::write!(f, "LineNumber<{:?}>", _ln);
89
            }
90
            async_gcode::GCode::Word(_w, _v) => {
91
                use alloc::string::ToString;
92
                defmt::write!(f, "Word[{:?}", _w.to_uppercase().to_string().as_str());
93
                match _v {
94
                    async_gcode::RealValue::Literal(_l) => {
95
                        match _l {
96
                            async_gcode::Literal::RealNumber(_r) => {
97
                                defmt::write!(
98
                                    f,
99
                                    " val: {}, scale: {}",
100
                                    _r.integer_part(),
101
                                    _r.scale()
102
                                );
103
                            }
104
                            async_gcode::Literal::String(_s) => {
105
                                defmt::write!(f, " {}", _s.as_str());
106
                            }
107
                        }
108
                        defmt::write!(f, "]");
109
                    }
110
                    _ => {}
111
                }
112
            }
113
            async_gcode::GCode::Text(_t) => match _t {
114
                async_gcode::RealValue::Literal(async_gcode::Literal::String(_s)) => {
115
                    defmt::write!(f, "String[{}]", _s.as_str());
116
                }
117
                _ => {}
118
            },
119
            async_gcode::GCode::Execute => {
120
                defmt::write!(f, "Execute");
121
            }
122
            #[allow(unreachable_patterns)]
123
            _ => {}
124
        }
125
    }
126
}
127

128
#[cfg(feature = "with-defmt")]
129
impl defmt::Format for GCodeLineParserError {
130
    fn format(&self, fmt: defmt::Formatter) {
131
        match self {
132
            GCodeLineParserError::ParseError(ln) => {
133
                defmt::write!(fmt, "ParseError(line={})", ln);
134
            }
135
            GCodeLineParserError::GCodeNotImplemented(ln, string) => {
136
                defmt::write!(
137
                    fmt,
138
                    "GCodeNotImplemented(line={}, gcode={})",
139
                    ln,
140
                    string.as_str()
141
                );
142
            }
143
            GCodeLineParserError::FatalError => {
144
                defmt::write!(fmt, "FatalError");
145
            }
146
            GCodeLineParserError::EOF => {
147
                defmt::write!(fmt, "EOF");
148
            }
149
        }
150
    }
151
}
152

153
/// Represents the raw specification of a G-code command.
154
///
155
/// G-code is a language used to control CNC machines, 3D printers, and other similar equipment.
156
/// A `RawGCodeSpec` captures the initial character of the command (e.g., 'G', 'M') and any numerical
157
/// specifics associated with it, potentially including a sub-value used for more granular control.
158
///
159
/// # Fields
160
/// - `code`: The main character of the G-code command.
161
/// - `spec`: An optional numerical part that follows the main character. For example, 'G1' would have
162
///   '1' as its spec.
163
/// - `sub`: An optional sub-value that provides additional specificity. For example, 'G1.1' would have
164
///   '1' as its spec and '1' as its sub.
165
///
166
/// # Example
167
///
168
/// ```
169
/// let gcode = RawTGCodeSpec::from('G', Some((1, 0)));
170
/// assert_eq!(format!("{:?}", gcode), "G1");
171
/// ```
172
pub struct RawGCodeSpec {
173
    code: char,
174
    spec: Option<i32>,
175
    sub: Option<i32>,
176
}
177

178
impl RawGCodeSpec {
179
    #[allow(unused)]
180
    pub fn from(code: char, spec: Option<(i32, u8)>) -> Self {
452,404✔
181
        match spec {
452,404✔
182
            None => Self {
×
183
                code,
×
184
                spec: None,
×
185
                sub: None,
×
186
            },
×
187
            Some((_num, _scale)) => {
452,404✔
188
                if _scale == 0 {
452,404✔
189
                    Self {
452,390✔
190
                        code,
452,390✔
191
                        spec: Some(_num),
452,390✔
192
                        sub: None,
452,390✔
193
                    }
452,390✔
194
                } else {
195
                    let sc = 10_i32.pow(_scale as u32);
14✔
196
                    let sp = _num / sc;
14✔
197
                    let ss = _num % sc;
14✔
198
                    Self {
14✔
199
                        code,
14✔
200
                        spec: Some(sp),
14✔
201
                        sub: Some(ss),
14✔
202
                    }
14✔
203
                }
204
            }
205
        }
206
    }
452,404✔
207
}
208
impl core::fmt::Debug for RawGCodeSpec {
209
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
333✔
210
        core::write!(f, "{}", self.code.to_uppercase())?;
333✔
211
        match &self.spec {
333✔
212
            Some(_spec) => {
333✔
213
                core::write!(f, "{}", _spec)?;
333✔
214
                match &self.sub {
333✔
215
                    Some(_sub) => {
×
216
                        core::write!(f, ".{}", _sub)
×
217
                    }
218
                    _ => Ok(()),
333✔
219
                }
220
            }
221
            _ => Ok(()),
×
222
        }
223
    }
333✔
224
}
225

226
// dyn trait could reduce code size a lot with the penalty of the indirection
227
pub struct GCodeLineParser<STREAM>
228
where
229
    STREAM: async_gcode::ByteStream<Item = Result<u8, async_gcode::Error>>,
230
{
231
    raw_parser: async_gcode::Parser<STREAM, async_gcode::Error>,
232
    gcode_line: Option<u32>,
233
}
234

235
impl<STREAM> GCodeLineParser<STREAM>
236
where
237
    STREAM: async_gcode::ByteStream<Item = Result<u8, async_gcode::Error>>,
238
{
239
    pub fn new(stream: STREAM) -> Self {
70✔
240
        Self {
70✔
241
            raw_parser: async_gcode::Parser::new(stream),
70✔
242
            gcode_line: None,
70✔
243
        }
70✔
244
    }
70✔
245

246
    pub fn gcode_line(&self) -> Option<u32> {
28✔
247
        self.gcode_line
28✔
248
    }
28✔
249

250
    pub async fn next_gcode(
452,443✔
251
        &mut self,
452,443✔
252
        _channel: CommChannel,
452,443✔
253
    ) -> Result<GCodeCmd, GCodeLineParserError> {
452,443✔
254
        // The GcodeCmd being constructed.
452,443✔
255
        // * Initially set to none
452,443✔
256
        // * Reset back to None when built, updated and returned
452,443✔
257
        let mut current_gcode: Option<GCodeCmd> = None;
452,443✔
258
        // Same as previous but unparsed
452,443✔
259
        let mut raw_gcode_spec: Option<RawGCodeSpec> = None;
452,443✔
260

452,443✔
261
        let mut tagged_line_num = None;
452,443✔
262
        let mut skip_gcode = false;
452,443✔
263

264
        loop {
265
            match self.raw_parser.next().await {
2,280,975✔
266
                None => {
267
                    hwa::trace!("EOF reading from stream");
19✔
268
                    self.gcode_line = tagged_line_num;
19✔
269
                    match current_gcode.take() {
19✔
270
                        None => {
271
                            match raw_gcode_spec.take() {
3✔
272
                                None => {
273
                                    return Err(GCodeLineParserError::EOF);
3✔
274
                                } // Empty line. Just ignore
275
                                Some(rgs) => {
×
276
                                    return Err(GCodeLineParserError::GCodeNotImplemented(
×
277
                                        self.raw_parser.get_current_line(),
×
278
                                        alloc::format!("{:?}", rgs),
×
279
                                    ));
×
280
                                }
281
                            }
282
                        }
283
                        Some(mut cgv) => {
16✔
284
                            cgv.order_num = self.raw_parser.get_current_line();
16✔
285
                            return Ok(cgv);
16✔
286
                        }
287
                    }
288
                }
289
                Some(parser_gcode) => {
2,280,920✔
290
                    match parser_gcode {
2,280,920✔
291
                        Ok(g) => {
2,280,917✔
292
                            #[cfg(feature = "debug-gcode")]
2,280,917✔
293
                            hwa::info!(
2,280,917✔
294
                                "[debug-gcode] channel: {:?} gcode: {:?}",
2,280,917✔
295
                                _channel,
2,280,917✔
296
                                FormatableGCode(&g)
2,280,917✔
297
                            );
2,280,917✔
298
                            match g {
2,280,917✔
299
                                // FIXME Undo this hacky temporary solution in async-gcode.
300
                                // A sub-protocol approach is preferred
301
                                #[cfg(feature = "grbl-compat")]
302
                                async_gcode::GCode::StatusCommand => {
303
                                    return Ok(GCodeCmd::new(
304
                                        self.raw_parser.get_current_line(),
305
                                        tagged_line_num,
306
                                        GCodeValue::Status,
307
                                    ));
308
                                }
309
                                async_gcode::GCode::BlockDelete => {
×
310
                                    skip_gcode = true;
×
311
                                    //crate::debug!("BlockDelete");
×
312
                                }
×
313
                                async_gcode::GCode::LineNumber(n) => {
×
314
                                    tagged_line_num = n;
×
315
                                }
×
316
                                async_gcode::GCode::Word(ch, fv) => {
1,738,908✔
317
                                    if skip_gcode {
1,738,908✔
318
                                        continue;
327✔
319
                                    }
1,738,581✔
320
                                    let frx = match &fv {
1,738,581✔
321
                                        async_gcode::RealValue::Literal(
322
                                            async_gcode::Literal::RealNumber(f),
1,738,569✔
323
                                        ) => Some((f.integer_part(), f.scale())),
1,738,569✔
324
                                        _ => None,
12✔
325
                                    };
326
                                    if let Some(current_gcode) = &mut current_gcode {
1,738,581✔
327
                                        // We already are building a GCodeCmd, so we update its fields
328
                                        update_current(current_gcode, ch, frx, fv)
1,286,177✔
329
                                    } else {
330
                                        raw_gcode_spec.replace(RawGCodeSpec::from(ch, frx));
452,404✔
331

332
                                        let has_text_argument = match (ch, frx) {
452,404✔
333
                                            ('m', Some((23, 0)))
334
                                            | ('m', Some((117, 0)))
335
                                            | ('m', Some((118, 0))) => true,
20✔
336
                                            _ => false,
452,384✔
337
                                        };
338
                                        // We need to start building the current GCodeCmd that is being parsed
339
                                        match init_current(ch, frx) {
452,404✔
340
                                            None => {
333✔
341
                                                // Unable to init: The GCodeValue is unexpected
333✔
342
                                                // Hence, we will skip every [async_gcode::GCode::Word] until [async_gcode::GCode::Execute]
333✔
343
                                                skip_gcode = true;
333✔
344
                                            }
333✔
345
                                            Some(gcode_value) => {
452,071✔
346
                                                current_gcode.replace(GCodeCmd::new(
452,071✔
347
                                                    0, // Will update later at [async_gcode::GCode::Execute]
452,071✔
348
                                                    tagged_line_num,
452,071✔
349
                                                    gcode_value,
452,071✔
350
                                                ));
452,071✔
351
                                                if has_text_argument {
452,071✔
352
                                                    self.raw_parser.switch_to_text();
20✔
353
                                                }
452,051✔
354
                                            }
355
                                        }
356
                                    }
357
                                }
358
                                async_gcode::GCode::Text(value) => {
20✔
359
                                    if let Some(current_gcode) = &mut current_gcode {
20✔
360
                                        // We already are building a GCodeCmd, so we update its fields
361
                                        update_current(current_gcode, 'T', None, value)
20✔
362
                                    }
×
363
                                }
364
                                async_gcode::GCode::Execute => {
365
                                    // Reset skip_gcode status
366
                                    skip_gcode = false;
541,989✔
367
                                    self.gcode_line = tagged_line_num;
541,989✔
368
                                    match current_gcode.take() {
541,989✔
369
                                        None => {
370
                                            match raw_gcode_spec.take() {
89,937✔
371
                                                None => {
372
                                                    #[cfg(feature = "trace-commands")]
373
                                                    hwa::warn!("Ignoring empty line");
374
                                                    continue;
89,604✔
375
                                                } // Empty line. Just ignore
376
                                                Some(rgs) => {
333✔
377
                                                    return Err(
333✔
378
                                                        GCodeLineParserError::GCodeNotImplemented(
333✔
379
                                                            self.raw_parser.get_current_line(),
333✔
380
                                                            alloc::format!("{:?}", rgs),
333✔
381
                                                        ),
333✔
382
                                                    );
333✔
383
                                                }
384
                                            }
385
                                        }
386
                                        Some(mut cgv) => {
452,052✔
387
                                            cgv.order_num = self.raw_parser.get_current_line();
452,052✔
388
                                            return Ok(cgv);
452,052✔
389
                                        }
390
                                    }
391
                                }
392
                                _ => {
393
                                    return Err(GCodeLineParserError::GCodeNotImplemented(
×
394
                                        self.raw_parser.get_current_line(),
×
395
                                        alloc::string::String::from("N/A"),
×
396
                                    ));
×
397
                                }
398
                            }
399
                        }
400
                        Err(error) => {
3✔
401
                            match error {
3✔
402
                                async_gcode::Error::UnexpectedByte(_b) => {
3✔
403
                                    hwa::warn!("Unexpected byte: {} ({})", _b, char::from(_b));
3✔
404
                                }
405
                                async_gcode::Error::NumberOverflow => {
406
                                    hwa::warn!("Number overflow");
×
407
                                }
408
                                async_gcode::Error::BadNumberFormat => {
409
                                    hwa::warn!("Bad number format");
×
410
                                }
411
                                _e => {
×
412
                                    #[cfg(feature = "native")]
×
413
                                    hwa::error!("Parse error {:?}", _e);
×
414
                                    hwa::error!("Parse error");
×
415
                                }
416
                            }
417
                            return Err(GCodeLineParserError::ParseError(
3✔
418
                                self.raw_parser.get_current_line(),
3✔
419
                            ));
3✔
420
                        }
421
                    }
422
                }
423
            }
424
        }
425
    }
452,407✔
426

427
    pub async fn reset(&mut self) {
24✔
428
        hwa::debug!("AsyncGcodeParser reset");
24✔
429
        self.raw_parser.reset().await;
24✔
430
    }
24✔
431

432
    pub fn reset_current_line(&mut self) {
×
433
        hwa::warn!("AsyncGcodeParser reset_current_line");
×
434
        self.raw_parser.update_current_line(0);
×
435
    }
×
436

437
    pub fn get_state(&self) -> async_gcode::AsyncParserState {
24✔
438
        self.raw_parser.get_state()
24✔
439
    }
24✔
440

441
    pub fn get_line(&self) -> u32 {
452,359✔
442
        self.raw_parser.get_current_line()
452,359✔
443
    }
452,359✔
444

445
    pub async fn close(&mut self) {
10✔
446
        self.raw_parser.reset().await;
10✔
447
    }
10✔
448
}
449

450
/// This trait is just a hack to give backward compatibility with unpatched async-gcode 0.3.0
451
/// Normally, won't be used as it is patched in cargo.toml
452
/// Purpose of this trait and impl is just to be able to publish printhor in crates.io
453
#[allow(unused)]
454
trait FixedAdaptor {
455
    fn integer_part(&self) -> i32;
456
    fn scale(&self) -> u8;
457
}
458

459
impl FixedAdaptor for f64 {
460
    fn integer_part(&self) -> i32 {
×
461
        panic!("Please, use patched async-gcode instead")
×
462
    }
463

464
    fn scale(&self) -> u8 {
×
465
        panic!("Please, use patched async-gcode instead")
×
466
    }
467
}
468

469
/// Initialize and EMPTY GCodeValue variant from ch, frx spec coming from parser
470
fn init_current(ch: char, frx: Option<(i32, u8)>) -> Option<GCodeValue> {
452,404✔
471
    match (ch, frx) {
452,404✔
472
        #[cfg(feature = "grbl-compat")]
473
        ('$', None) => Some(GCodeValue::GRBLCmd),
474
        ('g', None) => Some(GCodeValue::G),
×
475
        #[cfg(feature = "with-motion")]
476
        ('g', Some((0, 0))) => Some(GCodeValue::G0(FXYZ::new())),
44✔
477
        #[cfg(feature = "with-motion")]
478
        ('g', Some((1, 0))) => Some(GCodeValue::G1(EFSXYZ::new())),
439,977✔
479
        #[cfg(feature = "with-motion")]
480
        ('g', Some((4, 0))) => Some(GCodeValue::G4(S::new())),
7✔
481
        #[cfg(feature = "with-motion")]
482
        ('g', Some((10, 0))) => Some(GCodeValue::G10),
×
483
        #[cfg(feature = "with-motion")]
484
        ('g', Some((17, 0))) => Some(GCodeValue::G17),
×
485
        #[cfg(feature = "with-motion")]
486
        ('g', Some((21, 0))) => Some(GCodeValue::G21),
10✔
487
        #[cfg(feature = "with-motion")]
488
        ('g', Some((28, 0))) => Some(GCodeValue::G28(EXYZ::new())),
13✔
489
        #[cfg(feature = "with-motion")]
490
        ('g', Some((29, 0))) => Some(GCodeValue::G29),
4✔
491
        #[cfg(feature = "with-motion")]
492
        ('g', Some((291, 1))) => Some(GCodeValue::G29_1),
4✔
493
        #[cfg(feature = "with-motion")]
494
        ('g', Some((292, 1))) => Some(GCodeValue::G29_2),
4✔
495
        #[cfg(feature = "with-probe")]
496
        ('g', Some((31, 0))) => Some(GCodeValue::G31),
4✔
497
        #[cfg(feature = "with-probe")]
498
        ('g', Some((32, 0))) => Some(GCodeValue::G32),
×
499
        ('g', Some((80, 0))) => Some(GCodeValue::G80),
3✔
500
        #[cfg(feature = "with-motion")]
501
        ('g', Some((90, 0))) => Some(GCodeValue::G90),
13✔
502
        #[cfg(feature = "with-motion")]
503
        ('g', Some((91, 0))) => Some(GCodeValue::G91),
×
504
        #[cfg(feature = "with-motion")]
505
        ('g', Some((92, 0))) => Some(GCodeValue::G92(EXYZ::new())),
730✔
506
        #[cfg(feature = "with-motion")]
507
        ('g', Some((94, 0))) => Some(GCodeValue::G94),
×
UNCOV
508
        ('m', None) => Some(GCodeValue::M),
×
509
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
510
        ('m', Some((2, 0))) => Some(GCodeValue::M2),
7✔
511
        ('m', Some((3, 0))) => Some(GCodeValue::M3),
×
512
        ('m', Some((4, 0))) => Some(GCodeValue::M4),
4✔
513
        ('m', Some((5, 0))) => Some(GCodeValue::M5),
4✔
514
        #[cfg(feature = "with-sd-card")]
515
        ('m', Some((20, 0))) => Some(GCodeValue::M20(None)),
×
516
        #[cfg(feature = "with-sd-card")]
517
        ('m', Some((21, 0))) => Some(GCodeValue::M21),
×
518
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
519
        ('m', Some((23, 0))) => Some(GCodeValue::M23(None)),
12✔
520
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
521
        ('m', Some((24, 0))) => Some(GCodeValue::M24),
×
522
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
523
        ('m', Some((25, 0))) => Some(GCodeValue::M25),
×
524
        ('m', Some((37, 0))) => Some(GCodeValue::M37(S::new())),
×
525
        ('m', Some((73, 0))) => Some(GCodeValue::M73),
1,176✔
526
        ('m', Some((79, 0))) => Some(GCodeValue::M79),
×
527
        #[cfg(feature = "with-ps-on")]
528
        ('m', Some((80, 0))) => Some(GCodeValue::M80),
3✔
529
        #[cfg(feature = "with-ps-on")]
530
        ('m', Some((81, 0))) => Some(GCodeValue::M81),
3✔
531
        ('m', Some((83, 0))) => Some(GCodeValue::M83),
6✔
532
        #[cfg(feature = "with-motion")]
533
        ('m', Some((84, 0))) => Some(GCodeValue::M84),
3✔
534
        ('m', Some((100, 0))) => Some(GCodeValue::M100),
×
535
        #[cfg(feature = "with-hot-end")]
536
        ('m', Some((104, 0))) => Some(GCodeValue::M104(S::new())),
6✔
537
        ('m', Some((105, 0))) => Some(GCodeValue::M105),
×
538
        #[cfg(feature = "with-fan-layer")]
539
        ('m', Some((106, 0))) => Some(GCodeValue::M106),
640✔
540
        #[cfg(feature = "with-fan-layer")]
541
        ('m', Some((107, 0))) => Some(GCodeValue::M107),
10✔
542
        #[cfg(feature = "with-hot-end")]
543
        ('m', Some((109, 0))) => Some(GCodeValue::M109(S::new())),
2✔
544
        ('m', Some((110, 0))) => Some(GCodeValue::M110(N::new())),
×
545
        #[cfg(feature = "with-motion")]
546
        ('m', Some((114, 0))) => Some(GCodeValue::M114),
10✔
547
        ('m', Some((115, 0))) => Some(GCodeValue::M115),
3✔
548
        ('m', Some((117, 0))) => Some(GCodeValue::M117(None)),
4✔
549
        ('m', Some((118, 0))) => Some(GCodeValue::M118(None)),
4✔
550
        ('m', Some((119, 0))) => Some(GCodeValue::M119),
×
551
        #[cfg(feature = "with-hot-bed")]
552
        ('m', Some((140, 0))) => Some(GCodeValue::M140(S::new())),
4✔
553
        #[cfg(feature = "with-hot-bed")]
554
        ('m', Some((190, 0))) => Some(GCodeValue::M190),
2✔
555
        #[cfg(feature = "with-motion")]
556
        ('m', Some((201, 0))) => Some(GCodeValue::M201),
3✔
557
        #[cfg(feature = "with-motion")]
558
        ('m', Some((203, 0))) => Some(GCodeValue::M203),
3✔
559
        ('m', Some((204, 0))) => Some(GCodeValue::M204),
9,309✔
560
        ('m', Some((205, 0))) => Some(GCodeValue::M205),
6✔
561
        #[cfg(feature = "with-motion")]
562
        ('m', Some((206, 0))) => Some(GCodeValue::M206),
10✔
563
        ('m', Some((220, 0))) => Some(GCodeValue::M220(S::new())),
×
564
        ('m', Some((221, 0))) => Some(GCodeValue::M221(S::new())),
6✔
565
        ('m', Some((502, 0))) => Some(GCodeValue::M502),
×
566
        ('m', Some((503, 0))) => Some(GCodeValue::M503(S::new())),
×
567
        #[cfg(feature = "with-motion")]
568
        ('m', Some((8621, 1))) => Some(GCodeValue::M862_1),
3✔
569
        #[cfg(feature = "with-motion")]
570
        ('m', Some((8623, 1))) => Some(GCodeValue::M862_3),
3✔
571
        #[cfg(feature = "with-motion")]
572
        ('m', Some((900, 0))) => Some(GCodeValue::M900),
6✔
573
        #[cfg(feature = "with-motion")]
574
        ('m', Some((907, 0))) => Some(GCodeValue::M907),
6✔
575
        _ => None,
333✔
576
    }
577
}
452,404✔
578

579
fn update_current(
1,286,197✔
580
    gcode_cmd: &mut GCodeCmd,
1,286,197✔
581
    ch: char,
1,286,197✔
582
    frx: Option<(i32, u8)>,
1,286,197✔
583
    fv: async_gcode::RealValue,
1,286,197✔
584
) {
1,286,197✔
585
    match &mut gcode_cmd.value {
1,286,197✔
586
        #[cfg(feature = "grbl-compat")]
587
        GCodeValue::Status => match (ch, frx) {
588
            ('I', Some(_val)) => {
589
                hwa::warn!("TODO!!");
590
            }
591
            _ => {}
592
        },
593
        #[cfg(feature = "with-motion")]
594
        GCodeValue::G0(coord) => match (ch, frx) {
142✔
595
            ('f', Some(val)) => {
31✔
596
                coord.f.replace(helpers::to_fixed(val));
31✔
597
            }
31✔
598
            //
599
            #[cfg(feature = "with-x-axis")]
600
            ('x', Some(val)) => {
20✔
601
                coord.x.replace(helpers::to_fixed(val));
20✔
602
            }
20✔
603
            #[cfg(feature = "with-y-axis")]
604
            ('y', Some(val)) => {
20✔
605
                coord.y.replace(helpers::to_fixed(val));
20✔
606
            }
20✔
607
            #[cfg(feature = "with-z-axis")]
608
            ('z', Some(val)) => {
31✔
609
                coord.z.replace(helpers::to_fixed(val));
31✔
610
            }
31✔
611
            //
612
            #[cfg(feature = "with-a-axis")]
613
            ('a', Some(val)) => {
1✔
614
                coord.a.replace(helpers::to_fixed(val));
1✔
615
            }
1✔
616
            #[cfg(feature = "with-b-axis")]
617
            ('b', Some(val)) => {
1✔
618
                coord.b.replace(helpers::to_fixed(val));
1✔
619
            }
1✔
620
            #[cfg(feature = "with-c-axis")]
621
            ('c', Some(val)) => {
1✔
622
                coord.c.replace(helpers::to_fixed(val));
1✔
623
            }
1✔
624
            //
625
            #[cfg(feature = "with-i-axis")]
626
            ('i', Some(val)) => {
1✔
627
                coord.i.replace(helpers::to_fixed(val));
1✔
628
            }
1✔
629
            #[cfg(feature = "with-j-axis")]
630
            ('j', Some(val)) => {
1✔
631
                coord.j.replace(helpers::to_fixed(val));
1✔
632
            }
1✔
633
            #[cfg(feature = "with-k-axis")]
634
            ('k', Some(val)) => {
1✔
635
                coord.k.replace(helpers::to_fixed(val));
1✔
636
            }
1✔
637
            //
638
            #[cfg(feature = "with-u-axis")]
639
            ('u', Some(val)) => {
1✔
640
                coord.u.replace(helpers::to_fixed(val));
1✔
641
            }
1✔
642
            #[cfg(feature = "with-v-axis")]
643
            ('v', Some(val)) => {
1✔
644
                coord.v.replace(helpers::to_fixed(val));
1✔
645
            }
1✔
646
            #[cfg(feature = "with-w-axis")]
647
            ('w', Some(val)) => {
1✔
648
                coord.w.replace(helpers::to_fixed(val));
1✔
649
            }
1✔
650
            _ => {}
31✔
651
        },
652

653
        #[cfg(feature = "with-motion")]
654
        GCodeValue::G1(coord) => match (ch, frx) {
1,272,760✔
655
            #[cfg(feature = "with-e-axis")]
656
            ('e', Some(val)) => {
267,540✔
657
                coord.e.replace(helpers::to_fixed(val));
267,540✔
658
            }
267,540✔
659
            ('f', Some(val)) => {
49,581✔
660
                coord.f.replace(helpers::to_fixed(val));
49,581✔
661
            }
49,581✔
662
            ('s', Some(val)) => {
3,393✔
663
                coord.s.replace(helpers::to_fixed(val));
3,393✔
664
            }
3,393✔
665
            //
666
            #[cfg(feature = "with-x-axis")]
667
            ('x', Some(val)) => {
403,646✔
668
                coord.x.replace(helpers::to_fixed(val));
403,646✔
669
            }
403,646✔
670
            #[cfg(feature = "with-y-axis")]
671
            ('y', Some(val)) => {
403,643✔
672
                coord.y.replace(helpers::to_fixed(val));
403,643✔
673
            }
403,643✔
674
            #[cfg(feature = "with-z-axis")]
675
            ('z', Some(val)) => {
11,150✔
676
                coord.z.replace(helpers::to_fixed(val));
11,150✔
677
            }
11,150✔
678
            //
679
            #[cfg(feature = "with-a-axis")]
680
            ('a', Some(val)) => {
1✔
681
                coord.a.replace(helpers::to_fixed(val));
1✔
682
            }
1✔
683
            #[cfg(feature = "with-b-axis")]
684
            ('b', Some(val)) => {
1✔
685
                coord.b.replace(helpers::to_fixed(val));
1✔
686
            }
1✔
687
            #[cfg(feature = "with-c-axis")]
688
            ('c', Some(val)) => {
1✔
689
                coord.c.replace(helpers::to_fixed(val));
1✔
690
            }
1✔
691
            //
692
            #[cfg(feature = "with-i-axis")]
693
            ('i', Some(val)) => {
1✔
694
                coord.i.replace(helpers::to_fixed(val));
1✔
695
            }
1✔
696
            #[cfg(feature = "with-j-axis")]
697
            ('j', Some(val)) => {
1✔
698
                coord.j.replace(helpers::to_fixed(val));
1✔
699
            }
1✔
700
            #[cfg(feature = "with-k-axis")]
701
            ('k', Some(val)) => {
1✔
702
                coord.k.replace(helpers::to_fixed(val));
1✔
703
            }
1✔
704
            //
705
            #[cfg(feature = "with-u-axis")]
706
            ('u', Some(val)) => {
1✔
707
                coord.u.replace(helpers::to_fixed(val));
1✔
708
            }
1✔
709
            #[cfg(feature = "with-v-axis")]
710
            ('v', Some(val)) => {
1✔
711
                coord.v.replace(helpers::to_fixed(val));
1✔
712
            }
1✔
713
            #[cfg(feature = "with-w-axis")]
714
            ('w', Some(val)) => {
1✔
715
                coord.w.replace(helpers::to_fixed(val));
1✔
716
            }
1✔
717
            _ => {}
133,798✔
718
        },
719
        #[cfg(feature = "with-motion")]
720
        GCodeValue::G4(param) => match (ch, frx) {
×
721
            ('s', Some(val)) => {
×
722
                param.s.replace(helpers::to_fixed(val).abs());
×
723
            }
×
724
            _ => {}
×
725
        },
726
        #[cfg(feature = "with-motion")]
727
        GCodeValue::G28(coord) => match (ch, frx) {
9✔
728
            #[cfg(feature = "with-e-axis")]
729
            ('e', Some(val)) => {
×
730
                coord.e.replace(helpers::to_fixed(val));
×
731
            }
×
732
            //
733
            #[cfg(feature = "with-x-axis")]
734
            ('x', Some(val)) => {
×
735
                coord.x.replace(helpers::to_fixed(val));
×
736
            }
×
737
            #[cfg(feature = "with-y-axis")]
738
            ('y', Some(val)) => {
×
739
                coord.y.replace(helpers::to_fixed(val));
×
740
            }
×
741
            #[cfg(feature = "with-z-axis")]
742
            ('z', Some(val)) => {
×
743
                coord.z.replace(helpers::to_fixed(val));
×
744
            }
×
745
            //
746
            #[cfg(feature = "with-a-axis")]
747
            ('a', Some(val)) => {
×
748
                coord.a.replace(helpers::to_fixed(val));
×
749
            }
×
750
            #[cfg(feature = "with-b-axis")]
751
            ('b', Some(val)) => {
×
752
                coord.b.replace(helpers::to_fixed(val));
×
753
            }
×
754
            #[cfg(feature = "with-c-axis")]
755
            ('c', Some(val)) => {
×
756
                coord.c.replace(helpers::to_fixed(val));
×
757
            }
×
758
            #[cfg(feature = "with-i-axis")]
759
            ('i', Some(val)) => {
×
760
                coord.i.replace(helpers::to_fixed(val));
×
761
            }
×
762
            #[cfg(feature = "with-j-axis")]
763
            ('j', Some(val)) => {
×
764
                coord.j.replace(helpers::to_fixed(val));
×
765
            }
×
766
            #[cfg(feature = "with-k-axis")]
767
            ('k', Some(val)) => {
×
768
                coord.k.replace(helpers::to_fixed(val));
×
769
            }
×
770
            #[cfg(feature = "with-u-axis")]
771
            ('u', Some(val)) => {
×
772
                coord.u.replace(helpers::to_fixed(val));
×
773
            }
×
774
            #[cfg(feature = "with-v-axis")]
775
            ('v', Some(val)) => {
×
776
                coord.v.replace(helpers::to_fixed(val));
×
777
            }
×
778
            #[cfg(feature = "with-w-axis")]
779
            ('w', Some(val)) => {
×
780
                coord.w.replace(helpers::to_fixed(val));
×
781
            }
×
782
            _ => {}
9✔
783
        },
784
        #[cfg(feature = "with-motion")]
785
        GCodeValue::G92(coord) => match (ch, frx) {
782✔
786
            #[cfg(feature = "with-e-axis")]
787
            ('e', Some(val)) => {
486✔
788
                coord.e.replace(helpers::to_fixed(val));
486✔
789
            }
486✔
790
            //
791
            #[cfg(feature = "with-x-axis")]
792
            ('x', Some(val)) => {
4✔
793
                coord.x.replace(helpers::to_fixed(val));
4✔
794
            }
4✔
795
            #[cfg(feature = "with-y-axis")]
796
            ('y', Some(val)) => {
4✔
797
                coord.y.replace(helpers::to_fixed(val));
4✔
798
            }
4✔
799
            #[cfg(feature = "with-z-axis")]
800
            ('z', Some(val)) => {
4✔
801
                coord.z.replace(helpers::to_fixed(val));
4✔
802
            }
4✔
803
            //
804
            #[cfg(feature = "with-a-axis")]
805
            ('a', Some(val)) => {
1✔
806
                coord.a.replace(helpers::to_fixed(val));
1✔
807
            }
1✔
808
            #[cfg(feature = "with-b-axis")]
809
            ('b', Some(val)) => {
1✔
810
                coord.b.replace(helpers::to_fixed(val));
1✔
811
            }
1✔
812
            #[cfg(feature = "with-c-axis")]
813
            ('c', Some(val)) => {
1✔
814
                coord.c.replace(helpers::to_fixed(val));
1✔
815
            }
1✔
816
            //
817
            #[cfg(feature = "with-i-axis")]
818
            ('i', Some(val)) => {
1✔
819
                coord.i.replace(helpers::to_fixed(val));
1✔
820
            }
1✔
821
            #[cfg(feature = "with-j-axis")]
822
            ('j', Some(val)) => {
1✔
823
                coord.j.replace(helpers::to_fixed(val));
1✔
824
            }
1✔
825
            #[cfg(feature = "with-k-axis")]
826
            ('k', Some(val)) => {
1✔
827
                coord.k.replace(helpers::to_fixed(val));
1✔
828
            }
1✔
829
            //
830
            #[cfg(feature = "with-u-axis")]
831
            ('u', Some(val)) => {
1✔
832
                coord.u.replace(helpers::to_fixed(val));
1✔
833
            }
1✔
834
            #[cfg(feature = "with-v-axis")]
835
            ('v', Some(val)) => {
1✔
836
                coord.v.replace(helpers::to_fixed(val));
1✔
837
            }
1✔
838
            #[cfg(feature = "with-w-axis")]
839
            ('w', Some(val)) => {
1✔
840
                coord.w.replace(helpers::to_fixed(val));
1✔
841
            }
1✔
842
            _ => {}
275✔
843
        },
844
        #[cfg(feature = "with-sd-card")]
845
        GCodeValue::M20(path) => {
×
846
            if ch == 'f' {
×
847
                if let async_gcode::RealValue::Literal(async_gcode::Literal::String(mstr)) = fv {
×
848
                    path.replace(mstr);
×
849
                }
×
850
            }
×
851
        }
852
        #[cfg(all(feature = "with-sd-card", feature = "with-print-job"))]
853
        GCodeValue::M23(file) => {
12✔
854
            if let async_gcode::RealValue::Literal(async_gcode::Literal::String(mstr)) = fv {
12✔
855
                file.replace(mstr);
12✔
856
            }
12✔
857
        }
858
        GCodeValue::M37(coord) => match (ch, frx) {
×
859
            ('s', Some(val)) => {
×
860
                coord.s.replace(helpers::to_fixed(val));
×
861
            }
×
862
            _ => {}
×
863
        },
864
        #[cfg(feature = "with-hot-end")]
865
        GCodeValue::M104(coord) => match (ch, frx) {
6✔
866
            ('s', Some(val)) => {
6✔
867
                coord.s.replace(helpers::to_fixed(val));
6✔
868
            }
6✔
869
            _ => {}
×
870
        },
871
        #[cfg(feature = "with-hot-end")]
872
        GCodeValue::M109(coord) => match (ch, frx) {
2✔
873
            ('s', Some(val)) => {
2✔
874
                coord.s.replace(helpers::to_fixed(val));
2✔
875
            }
2✔
876
            _ => {}
×
877
        },
878
        GCodeValue::M117(msg) | GCodeValue::M118(msg) => {
4✔
879
            if let async_gcode::RealValue::Literal(async_gcode::Literal::String(text)) = fv {
8✔
880
                msg.replace(text);
8✔
881
            }
8✔
882
        }
883
        #[cfg(feature = "with-hot-bed")]
884
        GCodeValue::M140(coord) => match (ch, frx) {
4✔
885
            ('s', Some(val)) => {
4✔
886
                coord.s.replace(helpers::to_fixed(val));
4✔
887
            }
4✔
888
            _ => {}
×
889
        },
890
        GCodeValue::M220(coord) => match (ch, frx) {
×
891
            ('s', Some(val)) => {
×
892
                coord.s.replace(helpers::to_fixed(val));
×
893
            }
×
894
            _ => {}
×
895
        },
896
        GCodeValue::M110(coord) => match (ch, frx) {
×
897
            ('n', Some(val)) => {
×
898
                coord.n.replace(helpers::to_fixed(val));
×
899
            }
×
900
            _ => {}
×
901
        },
902
        GCodeValue::M503(param) => match (ch, frx) {
×
903
            ('s', Some(val)) => {
×
904
                param.s.replace(helpers::to_fixed(val));
×
905
            }
×
906
            _ => {}
×
907
        },
908
        _ => {}
12,472✔
909
    }
910
}
1,286,197✔
911

912
#[cfg(feature = "native")]
913
#[cfg(test)]
914
mod tests {
915
    use super::*;
916
    use std::collections::VecDeque;
917

918
    struct BufferStream {
919
        buff: VecDeque<u8>,
920
    }
921

922
    impl BufferStream {
923
        const fn new(buff: VecDeque<u8>) -> Self {
48✔
924
            Self { buff }
48✔
925
        }
48✔
926
    }
927

928
    impl async_gcode::ByteStream for BufferStream {
929
        type Item = Result<u8, async_gcode::Error>;
930

931
        async fn next(&mut self) -> Option<Self::Item> {
1,120✔
932
            match self.buff.pop_front() {
1,120✔
933
                None => None,
32✔
934
                Some(_b) => Some(Ok(_b)),
1,088✔
935
            }
936
        }
1,120✔
937

938
        async fn recovery_check(&mut self) {}
×
939
    }
940

941
    async fn check_m32_benchy_result(parser: &mut GCodeLineParser<BufferStream>) {
12✔
942
        match parser.next_gcode(CommChannel::Internal).await {
12✔
943
            Ok(_cmd) => match _cmd.value {
12✔
944
                GCodeValue::M23(Some(_path)) => {
12✔
945
                    assert!(_path.eq("BENCHY.G"), "Got the proper string param")
12✔
946
                }
947
                _ => assert!(false, "Unexpected code value"),
×
948
            },
949
            Err(_e) => {
×
950
                assert!(false, "Got an error");
×
951
            }
952
        }
953
    }
12✔
954

955
    async fn check_display_m117_result(parser: &mut GCodeLineParser<BufferStream>) {
4✔
956
        match parser.next_gcode(CommChannel::Internal).await {
4✔
957
            Ok(_cmd) => match _cmd.value {
4✔
958
                GCodeValue::M117(Some(_msg)) => {
4✔
959
                    assert!(_msg.eq("Hello World!"), "Got the proper string param")
4✔
960
                }
961
                _ => assert!(false, "Unexpected code value"),
×
962
            },
963
            Err(_e) => {
×
964
                assert!(false, "Got an error");
×
965
            }
966
        }
967
    }
4✔
968
    async fn check_display_m118_result(parser: &mut GCodeLineParser<BufferStream>) {
4✔
969
        match parser.next_gcode(CommChannel::Internal).await {
4✔
970
            Ok(_cmd) => match _cmd.value {
4✔
971
                GCodeValue::M118(Some(_msg)) => {
4✔
972
                    assert!(_msg.eq("Hello World!"), "Got the proper string param")
4✔
973
                }
974
                _ => assert!(false, "Unexpected code value"),
×
975
            },
976
            Err(_e) => {
×
977
                assert!(false, "Got an error");
×
978
            }
979
        }
980
    }
4✔
981
    #[futures_test::test]
982
    async fn test_m23_with_newline() {
4✔
983
        let stream = BufferStream::new("M23 BENCHY.G\n".as_bytes().to_vec().into());
4✔
984
        let mut parser = GCodeLineParser::new(stream);
4✔
985
        check_m32_benchy_result(&mut parser).await;
4✔
986
    }
8✔
987

988
    #[futures_test::test]
989
    async fn test_m23_with_eof_condition() {
4✔
990
        let stream = BufferStream::new("M23 BENCHY.G".as_bytes().to_vec().into());
4✔
991
        let mut parser = GCodeLineParser::new(stream);
4✔
992
        check_m32_benchy_result(&mut parser).await;
4✔
993
    }
8✔
994

995
    #[futures_test::test]
996
    async fn test_m23_with_trailin_comments() {
4✔
997
        let stream = BufferStream::new("M23 BENCHY.G".as_bytes().to_vec().into());
4✔
998
        let mut parser = GCodeLineParser::new(stream);
4✔
999
        check_m32_benchy_result(&mut parser).await;
4✔
1000
    }
8✔
1001

1002
    #[futures_test::test]
1003
    async fn test_m117() {
4✔
1004
        let stream = BufferStream::new("M117 Hello World!".as_bytes().to_vec().into());
4✔
1005
        let mut parser = GCodeLineParser::new(stream);
4✔
1006
        check_display_m117_result(&mut parser).await;
4✔
1007
    }
8✔
1008

1009
    #[futures_test::test]
1010
    async fn test_m118() {
4✔
1011
        let stream = BufferStream::new("M118 Hello World!".as_bytes().to_vec().into());
4✔
1012
        let mut parser = GCodeLineParser::new(stream);
4✔
1013
        check_display_m118_result(&mut parser).await;
4✔
1014
    }
8✔
1015

1016
    #[futures_test::test]
1017
    async fn test_gs() {
4✔
1018
        let stream = BufferStream::new("G0 F0 E0 X0 Y0 Z0 A0 B0 C0 I0 J0 K0 U0 V0 W0\n".as_bytes().to_vec().into());
4✔
1019
        let mut parser = GCodeLineParser::new(stream);
4✔
1020
        let _ = parser.gcode_line();
4✔
1021
        parser.next_gcode(CommChannel::Internal).await.expect("G0 OK");
4✔
1022

4✔
1023
        let stream = BufferStream::new("G1 F0 E0 X0 Y0 Z0 A0 B0 C0 I0 J0 K0 U0 V0 W0\n".as_bytes().to_vec().into());
4✔
1024
        let mut parser = GCodeLineParser::new(stream);
4✔
1025
        let _ = parser.gcode_line();
4✔
1026
        parser.next_gcode(CommChannel::Internal).await.expect("G1 OK");
4✔
1027

4✔
1028
        let stream = BufferStream::new("G92 F0 E0 X0 Y0 Z0 A0 B0 C0 I0 J0 K0 U0 V0 W0\n".as_bytes().to_vec().into());
4✔
1029
        let mut parser = GCodeLineParser::new(stream);
4✔
1030
        let _ = parser.gcode_line();
4✔
1031
        parser.next_gcode(CommChannel::Internal).await.expect("G1 OK");
4✔
1032

4✔
1033
        let stream = BufferStream::new("G29 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
4✔
1034
        let mut parser = GCodeLineParser::new(stream);
4✔
1035
        let _ = parser.gcode_line();
4✔
1036
        parser.next_gcode(CommChannel::Internal).await.expect("G1 OK");
4✔
1037

4✔
1038
        let stream = BufferStream::new("G29.1 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
4✔
1039
        let mut parser = GCodeLineParser::new(stream);
4✔
1040
        let _ = parser.gcode_line();
4✔
1041
        parser.next_gcode(CommChannel::Internal).await.expect("G29.1 OK");
4✔
1042

4✔
1043
        let stream = BufferStream::new("G29.2 F0 E0 X0 Y0 Z0\n".as_bytes().to_vec().into());
4✔
1044
        let mut parser = GCodeLineParser::new(stream);
4✔
1045
        let _ = parser.gcode_line();
4✔
1046
        parser.next_gcode(CommChannel::Internal).await.expect("G29.2 OK");
4✔
1047

4✔
1048
        let stream = BufferStream::new("G31\n".as_bytes().to_vec().into());
4✔
1049
        let mut parser = GCodeLineParser::new(stream);
4✔
1050
        let _ = parser.gcode_line();
4✔
1051
        parser.next_gcode(CommChannel::Internal).await.expect("G31 OK");
4✔
1052
    }
4✔
1053
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc