gcode_nom/binary/gcode_block/
mod.rs

1use core::fmt::Display;
2
3use crate::binary::{default_params::param_parser, BlockError};
4use nom::{
5    bytes::streaming::take,
6    combinator::verify,
7    number::streaming::{le_u16, le_u32},
8    sequence::preceded,
9    IResult, Parser,
10};
11
12use super::{
13    block_header::{block_header_parser, BlockHeader},
14    default_params::Param,
15    inflate::decompress_data_block,
16    Markdown,
17};
18
19/// Parser extracts `Vec<GCodeBlock>` from file.
20pub mod extractor;
21/// Converts a gcode block into a SVG file.
22pub mod svg;
23
24/// A wrapper for a series of gcode commands.
25///
26/// also wraps header, encoding and checksum
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub struct GCodeBlock<'a> {
29    /// Header
30    pub header: BlockHeader,
31    /// Param the data's encoding.
32    pub param: Param,
33    /// A series of gcode commands
34    pub data: &'a [u8],
35    checksum: Option<u32>,
36}
37
38impl Display for GCodeBlock<'_> {
39    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40        let datablock: String =
41            match decompress_data_block(self.data, &self.param.encoding, &self.header) {
42                Ok((_remain, data)) => String::from_utf8_lossy(&data).to_string(),
43                Err(_e) => String::from("failed to decompress"),
44            };
45        writeln!(
46            f,
47            "-------------------------- GCodeBlock --------------------------"
48        )?;
49        writeln!(f, "Params")?;
50        writeln!(f, "encoding {:#?}", self.param.encoding)?;
51        writeln!(f)?;
52        writeln!(f, "DataBlock {datablock:?}")?;
53        writeln!(f)?;
54        write!(f, "-------------------------- GCodeBlock ")?;
55        match self.checksum {
56            Some(checksum) => writeln!(f, "Checksum Ox{checksum:X} ---------")?,
57            None => writeln!(f, "No checksum")?,
58        }
59        Ok(())
60    }
61}
62
63impl Markdown for Vec<GCodeBlock<'_>> {
64    fn markdown<W>(&self, f: &mut W) -> core::fmt::Result
65    where
66        W: core::fmt::Write,
67    {
68        if self.is_empty() {
69            return Ok(());
70        }
71        writeln!(f)?;
72        writeln!(f, "## GCodeBlocks")?;
73        for (i, gcode) in self.iter().enumerate() {
74            writeln!(f)?;
75            // All titles (for a given level), must be unique
76            writeln!(f, "### GCodeBlock {i}")?;
77            writeln!(f)?;
78            gcode.headless_markdown(&mut *f)?;
79        }
80        Ok(())
81    }
82}
83
84impl GCodeBlock<'_> {
85    /// Write to formatter a markdown block.
86    pub(super) fn headless_markdown<W>(&self, mut f: W) -> core::fmt::Result
87    where
88        W: std::fmt::Write,
89    {
90        let datablock = match decompress_data_block(self.data, &self.param.encoding, &self.header) {
91            Ok((_remain, data)) => String::from_utf8_lossy(&data).to_string(),
92            Err(_e) => String::from("failed to decompress"),
93        };
94        writeln!(f, "### Params")?;
95        writeln!(f)?;
96        writeln!(f, "encoding {:#?}", self.param.encoding)?;
97        writeln!(f)?;
98        writeln!(f, "<details>")?;
99        writeln!(f, "<summary>DataBlock</summary>")?;
100        writeln!(f, "<br>")?;
101        writeln!(f, "{datablock:?}")?;
102        writeln!(f, "</details>")?;
103        writeln!(f)?;
104
105        match self.checksum {
106            Some(checksum) => writeln!(f, "Checksum Ox{checksum:X}")?,
107            None => writeln!(f, "No checksum")?,
108        }
109        Ok(())
110    }
111}
112
113static CODE_BLOCK_ID: u16 = 1u16;
114
115/// Parses a gcode block without validating checksum.
116///
117/// See also `gcode_parser_with_checksum()`.
118///
119/// # Errors
120///
121/// When no match is found.
122pub fn gcode_parser(input: &[u8]) -> IResult<&[u8], GCodeBlock<'_>, BlockError> {
123    let (after_block_header, header) = preceded(
124        verify(le_u16, |block_type| {
125            log::debug!(
126                "gcode_block: Looking for CODE_BLOCK_ID {CODE_BLOCK_ID} found {block_type} cond {}",
127                *block_type == CODE_BLOCK_ID
128            );
129            *block_type == CODE_BLOCK_ID
130        }),
131        block_header_parser,
132    )
133    .parse(input)
134    .map_err(|e| {
135        log::error!("Failed to parse block header {e}");
136        e.map(|_e| BlockError::Gcode)
137    })?;
138
139    log::info!("Found G-code block id.");
140    let (after_param, param) = param_parser(after_block_header).map_err(|e| {
141        log::error!("Failed to parse param {e}");
142        e.map(|_e| BlockError::Gcode)
143    })?;
144
145    log::info!("param {param:?}");
146    // Decompress data block.
147    let (after_data, data) = match header.compressed_size {
148        Some(size) => take(size)(after_param)?,
149        None => take(header.uncompressed_size)(after_param)?,
150    };
151
152    let (after_checksum, checksum) = match le_u32::<_, BlockError>(after_data) {
153        Ok((after_checksum, checksum)) => (after_checksum, checksum),
154        Err(_e) => {
155            let msg = "gcode_block: Failed to extract checksum".to_string();
156            log::error!("{msg}");
157            return Err(nom::Err::Error(BlockError::Gcode));
158        }
159    };
160
161    Ok((
162        after_checksum,
163        GCodeBlock {
164            header,
165            param,
166            data,
167            checksum: Some(checksum),
168        },
169    ))
170}
171
172/// Parses a gcode block, while validating checksum.
173///
174/// See also `gcode_parser()`.
175///
176/// # Errors
177///
178/// When no match is found.
179pub fn gcode_parser_with_checksum(input: &[u8]) -> IResult<&[u8], GCodeBlock<'_>, BlockError> {
180    let (remain, gcode) = gcode_parser(input)?;
181    if let Some(checksum) = gcode.checksum {
182        let param_size = 2;
183        let block_size =
184            gcode.header.size_in_bytes() + param_size + gcode.header.payload_size_in_bytes();
185        let crc_input = &input[..block_size];
186        let computed_checksum = crc32fast::hash(crc_input);
187
188        log::debug!("gcode checksum 0x{checksum:04x} computed checksum 0x{computed_checksum:04x} ");
189        if checksum == computed_checksum {
190            log::debug!("checksum match");
191        } else {
192            log::error!("fail checksum");
193            return Err(nom::Err::Error(BlockError::Gcode));
194        }
195    }
196
197    Ok((remain, gcode))
198}