Browse Source

MeatPack serial encoding (#20802)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
ellensp 4 years ago
parent
commit
3921369f98
No account linked to committer's email address

+ 2
- 0
Marlin/Configuration_adv.h View File

@@ -3298,6 +3298,8 @@
3298 3298
   //#define GCODE_QUOTED_STRINGS  // Support for quoted string parameters
3299 3299
 #endif
3300 3300
 
3301
+//#define MEATPACK                // Support for MeatPack G-code compression (https://github.com/scottmudge/OctoPrint-MeatPack)
3302
+
3301 3303
 //#define GCODE_CASE_INSENSITIVE  // Accept G-code sent to the firmware in lowercase
3302 3304
 
3303 3305
 //#define REPETIER_GCODE_M360     // Add commands originally from Repetier FW

+ 254
- 0
Marlin/src/feature/meatpack.cpp View File

@@ -0,0 +1,254 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+/**
24
+ * MeatPack G-code Compression
25
+ *
26
+ * Algorithm & Implementation: Scott Mudge - mail@scottmudge.com
27
+ * Date: Dec. 2020
28
+ *
29
+ * Character Frequencies from ~30 MB of comment-stripped gcode:
30
+ *  '1' -> 4451136    '4' -> 1353273   '\n' -> 1087683    '-' ->   90242
31
+ *  '0' -> 4253577    '9' -> 1352147    'G' -> 1075806    'Z' ->   34109
32
+ *  ' ' -> 3053297    '3' -> 1262929    'X' ->  975742    'M' ->   11879
33
+ *  '.' -> 3035310    '5' -> 1189871    'E' ->  965275    'S' ->    9910
34
+ *  '2' -> 1523296    '6' -> 1127900    'Y' ->  965274
35
+ *  '8' -> 1366812    '7' -> 1112908    'F' ->   99416
36
+ *
37
+ * When space is omitted the letter 'E' is used in its place
38
+ */
39
+
40
+#include "../inc/MarlinConfig.h"
41
+
42
+#if ENABLED(MEATPACK)
43
+
44
+#include "meatpack.h"
45
+MeatPack meatpack;
46
+
47
+#define MeatPack_ProtocolVersion "PV01"
48
+//#define MEATPACK_LOOKUP_TABLE
49
+//#define MP_DEBUG
50
+
51
+#define DEBUG_OUT ENABLED(MP_DEBUG)
52
+#include "../core/debug_out.h"
53
+
54
+bool MeatPack::cmd_is_next = false;       // A command is pending
55
+uint8_t MeatPack::state = 0;              // Configuration state OFF
56
+uint8_t MeatPack::second_char = 0;        // The unpacked 2nd character from an out-of-sequence packed pair
57
+uint8_t MeatPack::cmd_count = 0,          // Counts how many command bytes are received (need 2)
58
+        MeatPack::full_char_count = 0,    // Counts how many full-width characters are to be received
59
+        MeatPack::char_out_count = 0;     // Stores number of characters to be read out.
60
+uint8_t MeatPack::char_out_buf[2];        // Output buffer for caching up to 2 characters
61
+
62
+#if ENABLED(MEATPACK_LOOKUP_TABLE)
63
+  // The 15 most-common characters used in G-code, ~90-95% of all G-code uses these characters
64
+  // Stored in SRAM for performance.
65
+  static const uint8_t meatPackLookupTable[16] = {
66
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
67
+    '.', ' ', '\n', 'G', 'X',
68
+    '\0' // Unused. 0b1111 indicates a literal character
69
+  };
70
+#endif
71
+
72
+uint8_t MeatPack::unpacked_char(register const uint8_t in) {
73
+  #if ENABLED(MEATPACK_LOOKUP_TABLE)
74
+
75
+    return meatPackLookupTable[in];
76
+
77
+  #else
78
+
79
+      switch (in) {
80
+        case 0b0000 ... 0b1001: return '0' + in;
81
+        case 0b1010: return '.';
82
+        case 0b1011: return (state & MPConfig_Bit_NoSpaces) ? kSpaceCharReplace : ' ';
83
+        case 0b1100: return '\n';
84
+        case 0b1101: return 'G';
85
+        case 0b1110: return 'X';
86
+      }
87
+      return 0;
88
+
89
+  #endif
90
+}
91
+
92
+TERN_(MP_DEBUG, uint8_t chars_decoded = 0); // Log the first 64 bytes after each reset
93
+
94
+void MeatPack::reset_state() {
95
+  state = 0;
96
+  cmd_is_next = false;
97
+  second_char = 0;
98
+  cmd_count = full_char_count = char_out_count = 0;
99
+  TERN_(MP_DEBUG, chars_decoded = 0);
100
+  report_state();
101
+}
102
+
103
+/**
104
+ * Unpack one or two characters from a packed byte into a buffer.
105
+ * Return flags indicating whether any literal bytes follow.
106
+ */
107
+uint8_t MeatPack::unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out) {
108
+  uint8_t out = 0;
109
+
110
+  // If lower nybble is 1111, the higher nybble is unused, and next char is full.
111
+  if ((pk & kFirstNotPacked) == kFirstNotPacked)
112
+    out = kFirstCharIsLiteral;
113
+  else {
114
+    const uint8_t chr = pk & 0x0F;
115
+    chars_out[0] = unpacked_char(chr); // Set the first char
116
+  }
117
+
118
+  // Check if upper nybble is 1111... if so, we don't need the second char.
119
+  if ((pk & kSecondNotPacked) == kSecondNotPacked)
120
+    out |= kSecondCharIsLiteral;
121
+  else {
122
+    const uint8_t chr = (pk >> 4) & 0x0F;
123
+    chars_out[1] = unpacked_char(chr); // Set the second char
124
+  }
125
+
126
+  return out;
127
+}
128
+
129
+/**
130
+ * Interpret a single (non-command) character
131
+ * according to the current MeatPack state.
132
+ */
133
+void MeatPack::handle_rx_char_inner(const uint8_t c) {
134
+  if (TEST(state, MPConfig_Bit_Active)) {                   // Is MeatPack active?
135
+    if (!full_char_count) {                                 // No literal characters to fetch?
136
+      uint8_t buf[2] = { 0, 0 };
137
+      register const uint8_t res = unpack_chars(c, buf);    // Decode the byte into one or two characters.
138
+      if (res & kFirstCharIsLiteral) {                      // The 1st character couldn't be packed.
139
+        ++full_char_count;                                  // So the next stream byte is a full character.
140
+        if (res & kSecondCharIsLiteral) ++full_char_count;  // The 2nd character couldn't be packed. Another stream byte is a full character.
141
+        else second_char = buf[1];                          // Retain the unpacked second character.
142
+      }
143
+      else {
144
+        handle_output_char(buf[0]);                           // Send the unpacked first character out.
145
+        if (buf[0] != '\n') {                                 // After a newline the next char won't be set
146
+          if (res & kSecondCharIsLiteral) ++full_char_count;  // The 2nd character couldn't be packed. The next stream byte is a full character.
147
+          else handle_output_char(buf[1]);                    // Send the unpacked second character out.
148
+        }
149
+      }
150
+    }
151
+    else {
152
+      handle_output_char(c);                                // Pass through the character that couldn't be packed...
153
+      if (second_char) {
154
+        handle_output_char(second_char);                    // ...and send an unpacked 2nd character, if set.
155
+        second_char = 0;
156
+      }
157
+      --full_char_count;                                    // One literal character was consumed
158
+    }
159
+  }
160
+  else // Packing not enabled, just copy character to output
161
+    handle_output_char(c);
162
+}
163
+
164
+/**
165
+ * Buffer a single output character which will be picked up in
166
+ * GCodeQueue::get_serial_commands via calls to get_result_char
167
+ */
168
+void MeatPack::handle_output_char(const uint8_t c) {
169
+  char_out_buf[char_out_count++] = c;
170
+
171
+  #if ENABLED(MP_DEBUG)
172
+    if (chars_decoded < 1024) {
173
+      ++chars_decoded;
174
+      DEBUG_ECHOPGM("RB: ");
175
+      MYSERIAL.print((char)c);
176
+      DEBUG_EOL();
177
+    }
178
+  #endif
179
+}
180
+
181
+/**
182
+ * Process a MeatPack command byte to update the state.
183
+ * Report the new state to serial.
184
+ */
185
+void MeatPack::handle_command(const MeatPack_Command c) {
186
+  switch (c) {
187
+    case MPCommand_EnablePacking:   SBI(state, MPConfig_Bit_Active);   DEBUG_ECHOLNPGM("[MPDBG] ENA REC");   break;
188
+    case MPCommand_DisablePacking:  CBI(state, MPConfig_Bit_Active);   DEBUG_ECHOLNPGM("[MPDBG] DIS REC");   break;
189
+    case MPCommand_TogglePacking:   TBI(state, MPConfig_Bit_Active);   DEBUG_ECHOLNPGM("[MPDBG] TGL REC");   break;
190
+    case MPCommand_ResetAll:        reset_state();                     DEBUG_ECHOLNPGM("[MPDBG] RESET REC"); break;
191
+    case MPCommand_EnableNoSpaces:  SBI(state, MPConfig_Bit_NoSpaces); DEBUG_ECHOLNPGM("[MPDBG] ENA NSP");
192
+                                    TERN_(USE_LOOKUP_TABLE, MeatPackLookupTbl[kSpaceCharIdx] = kSpaceCharReplace);
193
+                                    break;
194
+    case MPCommand_DisableNoSpaces: CBI(state, MPConfig_Bit_NoSpaces); DEBUG_ECHOLNPGM("[MPDBG] DIS NSP");
195
+                                    TERN_(USE_LOOKUP_TABLE, MeatPackLookupTbl[kSpaceCharIdx] = ' ');
196
+                                    break;
197
+    default:                                                           DEBUG_ECHOLNPGM("[MPDBG] UNK CMD REC");
198
+    case MPCommand_QueryConfig: break;
199
+  }
200
+  report_state();
201
+}
202
+
203
+void MeatPack::report_state() {
204
+  // NOTE: if any configuration vars are added below, the outgoing sync text for host plugin
205
+  // should not contain the "PV' substring, as this is used to indicate protocol version
206
+  SERIAL_ECHOPGM("[MP] ");
207
+  SERIAL_ECHOPGM(MeatPack_ProtocolVersion);
208
+  serialprint_onoff(TEST(state, MPConfig_Bit_Active));
209
+  SERIAL_CHAR(' ');
210
+  serialprintPGM(TEST(state, MPConfig_Bit_NoSpaces) ? PSTR("NSP") : PSTR("ESP"));
211
+  SERIAL_EOL();
212
+}
213
+
214
+/**
215
+ * Interpret a single character received from serial
216
+ * according to the current meatpack state.
217
+ */
218
+void MeatPack::handle_rx_char(const uint8_t c) {
219
+  if (c == kCommandByte) {                // A command (0xFF) byte?
220
+    if (cmd_count) {                      // In fact, two in a row?
221
+      cmd_is_next = true;                 // Then a MeatPack command follows
222
+      cmd_count = 0;
223
+    }
224
+    else
225
+      ++cmd_count; // cmd_count = 1       // One command byte received so far...
226
+    return;
227
+  }
228
+
229
+  if (cmd_is_next) {                      // Were two command bytes received?
230
+    handle_command((MeatPack_Command)c);  // Then the byte is a MeatPack command
231
+    cmd_is_next = false;
232
+    return;
233
+  }
234
+
235
+  if (cmd_count) {                        // Only a single 0xFF was received
236
+    handle_rx_char_inner(kCommandByte);   // A single 0xFF is passed on literally so it can be interpreted as kFirstNotPacked|kSecondNotPacked
237
+    cmd_count = 0;
238
+  }
239
+
240
+  handle_rx_char_inner(c);                // Other characters are passed on for MeatPack decoding
241
+}
242
+
243
+uint8_t MeatPack::get_result_char(char* const __restrict out) {
244
+  uint8_t res = 0;
245
+  if (char_out_count) {
246
+    res = char_out_count;
247
+    char_out_count = 0;
248
+    for (register uint8_t i = 0; i < res; ++i)
249
+      out[i] = (char)char_out_buf[i];
250
+  }
251
+  return res;
252
+}
253
+
254
+#endif // MEATPACK

+ 124
- 0
Marlin/src/feature/meatpack.h View File

@@ -0,0 +1,124 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+/*
24
+ * MeatPack G-code Compression
25
+ *
26
+ * Algorithm & Implementation: Scott Mudge - mail@scottmudge.com
27
+ * Date: Dec. 2020
28
+ *
29
+ * Specifically optimized for 3D printing G-Code, this is a zero-cost data compression method
30
+ * which packs ~180-190% more data into the same amount of bytes going to the CNC controller.
31
+ * As a majority of G-Code can be represented by a restricted alphabet, I performed histogram
32
+ * analysis on a wide variety of 3D printing gcode samples, and found ~93% of all gcode could
33
+ * be represented by the same 15-character alphabet.
34
+ *
35
+ * This allowed me to design a system of packing 2 8-bit characters into a single byte, assuming
36
+ * they fall within this limited 15-character alphabet. Using a 4-bit lookup table, these 8-bit
37
+ * characters can be represented by a 4-bit index.
38
+ *
39
+ * Combined with some logic to allow commingling of full-width characters outside of this 15-
40
+ * character alphabet (at the cost of an extra 8-bits per full-width character), and by stripping
41
+ * out unnecessary comments, the end result is gcode which is roughly half the original size.
42
+ *
43
+ * Why did I do this? I noticed micro-stuttering and other data-bottleneck issues while printing
44
+ * objects with high curvature, especially at high speeds. There is also the issue of the limited
45
+ * baud rate provided by Prusa's Atmega2560-based boards, over the USB serial connection. So soft-
46
+ * ware like OctoPrint would also suffer this same micro-stuttering and poor print quality issue.
47
+ *
48
+ */
49
+#pragma once
50
+
51
+#include <stdint.h>
52
+
53
+/**
54
+ * Commands sent to MeatPack to control its behavior.
55
+ * They are sent by first sending 2x MeatPack_CommandByte (0xFF) in sequence,
56
+ *      followed by one of the command bytes below.
57
+ * Provided that 0xFF is an exceedingly rare character that is virtually never
58
+ * present in G-code naturally, it is safe to assume 2 in sequence should never
59
+ * happen naturally, and so it is used as a signal here.
60
+ *
61
+ * 0xFF *IS* used in "packed" G-code (used to denote that the next 2 characters are
62
+ * full-width), however 2 in a row will never occur, as the next 2 bytes will always
63
+ * some non-0xFF character.
64
+ */
65
+enum MeatPack_Command : uint8_t {
66
+  MPCommand_None            = 0,
67
+  MPCommand_TogglePacking   = 0xFD,
68
+  MPCommand_EnablePacking   = 0xFB,
69
+  MPCommand_DisablePacking  = 0xFA,
70
+  MPCommand_ResetAll        = 0xF9,
71
+  MPCommand_QueryConfig     = 0xF8,
72
+  MPCommand_EnableNoSpaces  = 0xF7,
73
+  MPCommand_DisableNoSpaces = 0xF6
74
+};
75
+
76
+enum MeatPack_ConfigStateBits : uint8_t {
77
+  MPConfig_Bit_Active   = 0,
78
+  MPConfig_Bit_NoSpaces = 1
79
+};
80
+
81
+class MeatPack {
82
+private:
83
+  friend class GCodeQueue;
84
+
85
+  // Utility definitions
86
+  static const uint8_t kCommandByte         = 0b11111111,
87
+                       kFirstNotPacked      = 0b00001111,
88
+                       kSecondNotPacked     = 0b11110000,
89
+                       kFirstCharIsLiteral  = 0b00000001,
90
+                       kSecondCharIsLiteral = 0b00000010;
91
+
92
+  static const uint8_t kSpaceCharIdx = 11;
93
+  static const char kSpaceCharReplace = 'E';
94
+
95
+  static bool cmd_is_next;        // A command is pending
96
+  static uint8_t state;           // Configuration state
97
+  static uint8_t second_char;     // Buffers a character if dealing with out-of-sequence pairs
98
+  static uint8_t cmd_count,       // Counter of command bytes received (need 2)
99
+                 full_char_count, // Counter for full-width characters to be received
100
+                 char_out_count;  // Stores number of characters to be read out.
101
+  static uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters
102
+
103
+  // Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences,
104
+  // and will control state internally.
105
+  static void handle_rx_char(const uint8_t c);
106
+
107
+  /**
108
+   * After passing in rx'd char using above method, call this to get characters out.
109
+   * Can return from 0 to 2 characters at once.
110
+   * @param out [in] Output pointer for unpacked/processed data.
111
+   * @return Number of characters returned. Range from 0 to 2.
112
+   */
113
+  static uint8_t get_result_char(char* const __restrict out);
114
+
115
+  static void reset_state();
116
+  static void report_state();
117
+  static uint8_t unpacked_char(register const uint8_t in);
118
+  static uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out);
119
+  static void handle_command(const MeatPack_Command c);
120
+  static void handle_output_char(const uint8_t c);
121
+  static void handle_rx_char_inner(const uint8_t c);
122
+};
123
+
124
+extern MeatPack meatpack;

+ 3
- 0
Marlin/src/gcode/host/M115.cpp View File

@@ -141,6 +141,9 @@ void GcodeSuite::M115() {
141 141
     // CHAMBER_TEMPERATURE (M141, M191)
142 142
     cap_line(PSTR("CHAMBER_TEMPERATURE"), ENABLED(HAS_HEATED_CHAMBER));
143 143
 
144
+    // MEATPACK Compresson
145
+    cap_line(PSTR("MEATPACK"), ENABLED(MEATPACK));
146
+
144 147
     // Machine Geometry
145 148
     #if ENABLED(M115_GEOMETRY_REPORT)
146 149
       const xyz_pos_t dmin = { X_MIN_POS, Y_MIN_POS, Z_MIN_POS },

+ 88
- 73
Marlin/src/gcode/queue.cpp View File

@@ -48,6 +48,10 @@ GCodeQueue queue;
48 48
   #include "../feature/binary_stream.h"
49 49
 #endif
50 50
 
51
+#if ENABLED(MEATPACK)
52
+  #include "../feature/meatpack.h"
53
+#endif
54
+
51 55
 #if ENABLED(POWER_LOSS_RECOVERY)
52 56
   #include "../feature/powerloss.h"
53 57
 #endif
@@ -474,98 +478,109 @@ void GCodeQueue::get_serial_commands() {
474 478
       const int c = read_serial(i);
475 479
       if (c < 0) continue;
476 480
 
477
-      const char serial_char = c;
478
-
479
-      if (ISEOL(serial_char)) {
481
+      #if ENABLED(MEATPACK)
482
+        meatpack.handle_rx_char(uint8_t(c));
483
+        char c_res[2] = { 0, 0 };
484
+        const uint8_t char_count = meatpack.get_result_char(c_res);
485
+      #else
486
+        constexpr uint8_t char_count = 1;
487
+      #endif
480 488
 
481
-        // Reset our state, continue if the line was empty
482
-        if (process_line_done(serial_input_state[i], serial_line_buffer[i], serial_count[i]))
483
-          continue;
489
+      LOOP_L_N(char_index, char_count) {
490
+        const char serial_char = TERN(MEATPACK, c_res[char_index], c);
484 491
 
485
-        char* command = serial_line_buffer[i];
492
+        if (ISEOL(serial_char)) {
486 493
 
487
-        while (*command == ' ') command++;                   // Skip leading spaces
488
-        char *npos = (*command == 'N') ? command : nullptr;  // Require the N parameter to start the line
494
+          // Reset our state, continue if the line was empty
495
+          if (process_line_done(serial_input_state[i], serial_line_buffer[i], serial_count[i]))
496
+            continue;
489 497
 
490
-        if (npos) {
498
+          char* command = serial_line_buffer[i];
491 499
 
492
-          const bool M110 = !!strstr_P(command, PSTR("M110"));
500
+          while (*command == ' ') command++;                   // Skip leading spaces
501
+          char *npos = (*command == 'N') ? command : nullptr;  // Require the N parameter to start the line
493 502
 
494
-          if (M110) {
495
-            char* n2pos = strchr(command + 4, 'N');
496
-            if (n2pos) npos = n2pos;
497
-          }
503
+          if (npos) {
498 504
 
499
-          const long gcode_N = strtol(npos + 1, nullptr, 10);
505
+            const bool M110 = !!strstr_P(command, PSTR("M110"));
500 506
 
501
-          if (gcode_N != last_N[i] + 1 && !M110)
502
-            return gcode_line_error(PSTR(STR_ERR_LINE_NO), i);
507
+            if (M110) {
508
+              char* n2pos = strchr(command + 4, 'N');
509
+              if (n2pos) npos = n2pos;
510
+            }
503 511
 
504
-          char *apos = strrchr(command, '*');
505
-          if (apos) {
506
-            uint8_t checksum = 0, count = uint8_t(apos - command);
507
-            while (count) checksum ^= command[--count];
508
-            if (strtol(apos + 1, nullptr, 10) != checksum)
509
-              return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), i);
510
-          }
511
-          else
512
-            return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
512
+            const long gcode_N = strtol(npos + 1, nullptr, 10);
513 513
 
514
-          last_N[i] = gcode_N;
515
-        }
516
-        #if ENABLED(SDSUPPORT)
517
-          // Pronterface "M29" and "M29 " has no line number
518
-          else if (card.flag.saving && !is_M29(command))
519
-            return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
520
-        #endif
514
+            if (gcode_N != last_N[i] + 1 && !M110)
515
+              return gcode_line_error(PSTR(STR_ERR_LINE_NO), i);
521 516
 
522
-        //
523
-        // Movement commands give an alert when the machine is stopped
524
-        //
525
-
526
-        if (IsStopped()) {
527
-          char* gpos = strchr(command, 'G');
528
-          if (gpos) {
529
-            switch (strtol(gpos + 1, nullptr, 10)) {
530
-              case 0: case 1:
531
-              #if ENABLED(ARC_SUPPORT)
532
-                case 2: case 3:
533
-              #endif
534
-              #if ENABLED(BEZIER_CURVE_SUPPORT)
535
-                case 5:
536
-              #endif
537
-                PORT_REDIRECT(i);                      // Reply to the serial port that sent the command
538
-                SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
539
-                LCD_MESSAGEPGM(MSG_STOPPED);
540
-                break;
517
+            char *apos = strrchr(command, '*');
518
+            if (apos) {
519
+              uint8_t checksum = 0, count = uint8_t(apos - command);
520
+              while (count) checksum ^= command[--count];
521
+              if (strtol(apos + 1, nullptr, 10) != checksum)
522
+                return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), i);
541 523
             }
524
+            else
525
+              return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
526
+
527
+            last_N[i] = gcode_N;
542 528
           }
543
-        }
529
+          #if ENABLED(SDSUPPORT)
530
+            // Pronterface "M29" and "M29 " has no line number
531
+            else if (card.flag.saving && !is_M29(command))
532
+              return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
533
+          #endif
544 534
 
545
-        #if DISABLED(EMERGENCY_PARSER)
546
-          // Process critical commands early
547
-          if (command[0] == 'M') switch (command[3]) {
548
-            case '8': if (command[2] == '0' && command[1] == '1') { wait_for_heatup = false; TERN_(HAS_LCD_MENU, wait_for_user = false); } break;
549
-            case '2': if (command[2] == '1' && command[1] == '1') kill(M112_KILL_STR, nullptr, true); break;
550
-            case '0': if (command[1] == '4' && command[2] == '1') quickstop_stepper(); break;
535
+          //
536
+          // Movement commands give an alert when the machine is stopped
537
+          //
538
+
539
+          if (IsStopped()) {
540
+            char* gpos = strchr(command, 'G');
541
+            if (gpos) {
542
+              switch (strtol(gpos + 1, nullptr, 10)) {
543
+                case 0: case 1:
544
+                #if ENABLED(ARC_SUPPORT)
545
+                  case 2: case 3:
546
+                #endif
547
+                #if ENABLED(BEZIER_CURVE_SUPPORT)
548
+                  case 5:
549
+                #endif
550
+                  PORT_REDIRECT(i);                      // Reply to the serial port that sent the command
551
+                  SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
552
+                  LCD_MESSAGEPGM(MSG_STOPPED);
553
+                  break;
554
+              }
555
+            }
551 556
           }
552
-        #endif
553 557
 
554
-        #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0
555
-          last_command_time = ms;
556
-        #endif
558
+          #if DISABLED(EMERGENCY_PARSER)
559
+            // Process critical commands early
560
+            if (command[0] == 'M') switch (command[3]) {
561
+              case '8': if (command[2] == '0' && command[1] == '1') { wait_for_heatup = false; TERN_(HAS_LCD_MENU, wait_for_user = false); } break;
562
+              case '2': if (command[2] == '1' && command[1] == '1') kill(M112_KILL_STR, nullptr, true); break;
563
+              case '0': if (command[1] == '4' && command[2] == '1') quickstop_stepper(); break;
564
+            }
565
+          #endif
557 566
 
558
-        // Add the command to the queue
559
-        _enqueue(serial_line_buffer[i], true
560
-          #if HAS_MULTI_SERIAL
561
-            , i
567
+          #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0
568
+            last_command_time = ms;
562 569
           #endif
563
-        );
564
-      }
565
-      else
566
-        process_stream_char(serial_char, serial_input_state[i], serial_line_buffer[i], serial_count[i]);
567 570
 
568
-    } // for NUM_SERIAL
571
+          // Add the command to the queue
572
+          _enqueue(serial_line_buffer[i], true
573
+            #if HAS_MULTI_SERIAL
574
+              , i
575
+            #endif
576
+          );
577
+        }
578
+        else
579
+          process_stream_char(serial_char, serial_input_state[i], serial_line_buffer[i], serial_count[i]);
580
+
581
+      } // char_count loop
582
+
583
+    } // NUM_SERIAL loop
569 584
   } // queue has space, serial has data
570 585
 }
571 586
 

+ 8
- 0
Marlin/src/inc/SanityCheck.h View File

@@ -3303,6 +3303,14 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
3303 3303
   #endif
3304 3304
 #endif
3305 3305
 
3306
+
3307
+/**
3308
+ * Sanity Check for MEATPACK and BINARY_FILE_TRANSFER Features
3309
+ */
3310
+#if BOTH(MEATPACK, BINARY_FILE_TRANSFER)
3311
+  #error "Either enable MEATPACK or enable BINARY_FILE_TRANSFER."
3312
+#endif
3313
+
3306 3314
 /**
3307 3315
  * Sanity check for valid stepper driver types
3308 3316
  */

+ 1
- 0
buildroot/tests/FYSETC_S6-tests View File

@@ -9,6 +9,7 @@ set -e
9 9
 # Build examples
10 10
 restore_configs
11 11
 use_example_configs FYSETC/S6
12
+opt_enable MEATPACK
12 13
 opt_set Y_DRIVER_TYPE TMC2209
13 14
 opt_set Z_DRIVER_TYPE TMC2130
14 15
 exec_test $1 $2 "FYSETC S6 Example" "$3"

+ 3
- 1
platformio.ini View File

@@ -97,6 +97,7 @@ default_src_filter = +<src/*> -<src/config> -<src/HAL> +<src/HAL/shared>
97 97
   -<src/feature/leds/printer_event_leds.cpp>
98 98
   -<src/feature/leds/tempstat.cpp>
99 99
   -<src/feature/max7219.cpp>
100
+  -<src/feature/meatpack.cpp>
100 101
   -<src/feature/mixing.cpp>
101 102
   -<src/feature/mmu/mmu.cpp>
102 103
   -<src/feature/mmu/mmu2.cpp> -<src/gcode/feature/prusa_MMU2>
@@ -319,6 +320,7 @@ PCA9632                 = src_filter=+<src/feature/leds/pca9632.cpp>
319 320
 PRINTER_EVENT_LEDS      = src_filter=+<src/feature/leds/printer_event_leds.cpp>
320 321
 TEMP_STAT_LEDS          = src_filter=+<src/feature/leds/tempstat.cpp>
321 322
 MAX7219_DEBUG           = src_filter=+<src/feature/max7219.cpp> +<src/gcode/feature/leds/M7219.cpp>
323
+MEATPACK                = src_filter=+<src/feature/meatpack.cpp>
322 324
 MIXING_EXTRUDER         = src_filter=+<src/feature/mixing.cpp> +<src/gcode/feature/mixing/M163-M165.cpp>
323 325
 HAS_PRUSA_MMU1          = src_filter=+<src/feature/mmu/mmu.cpp>
324 326
 HAS_PRUSA_MMU2          = src_filter=+<src/feature/mmu/mmu2.cpp> +<src/gcode/feature/prusa_MMU2>
@@ -706,7 +708,7 @@ extra_scripts     = ${common.extra_scripts}
706 708
 src_filter        = ${common.default_src_filter} +<src/HAL/LPC1768> +<src/HAL/shared/backtrace>
707 709
 lib_deps          = ${common.lib_deps}
708 710
   Servo
709
-custom_marlin.USES_LIQUIDCRYSTAL = LiquidCrystal@1.0.0
711
+custom_marlin.USES_LIQUIDCRYSTAL = LiquidCrystal~1.0.7
710 712
 custom_marlin.NEOPIXEL_LED = Adafruit NeoPixel=https://github.com/p3p/Adafruit_NeoPixel/archive/1.5.0.zip
711 713
 build_flags       = ${common.build_flags} -DU8G_HAL_LINKS -IMarlin/src/HAL/LPC1768/include -IMarlin/src/HAL/LPC1768/u8g
712 714
   # debug options for backtrace

Loading…
Cancel
Save