From 92bfd1b495750e7328231415e72906abe7b2a517 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Fri, 20 Aug 2021 13:45:16 +1000 Subject: [PATCH] Backport meatpack feature from upstream --- Marlin/Configuration_adv.h | 2 + Marlin/src/core/serial.cpp | 2 +- Marlin/src/core/serial.h | 4 +- Marlin/src/feature/meatpack.cpp | 228 ++++++++++++++++++++++++++ Marlin/src/feature/meatpack.h | 123 ++++++++++++++ Marlin/src/gcode/host/M115.cpp | 3 + Marlin/src/gcode/host/M118.cpp | 2 +- Marlin/src/gcode/queue.cpp | 218 ++++++++++++------------ Marlin/src/gcode/queue.h | 8 +- Marlin/src/inc/SanityCheck.h | 7 + Marlin/src/sd/cardreader.cpp | 2 +- Marlin/src/sd/cardreader.h | 2 +- buildroot/share/tests/FYSETC_S6-tests | 1 + 13 files changed, 491 insertions(+), 111 deletions(-) create mode 100644 Marlin/src/feature/meatpack.cpp create mode 100644 Marlin/src/feature/meatpack.h diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index c1fda7b7..ca7328e5 100755 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -3206,3 +3206,5 @@ //#define KNUTWURST_MEGAS_ADV //#define KNUTWURST_TMC_ADV + +#define MEATPACK // Support for MeatPack G-code compression (https://github.com/scottmudge/OctoPrint-MeatPack) \ No newline at end of file diff --git a/Marlin/src/core/serial.cpp b/Marlin/src/core/serial.cpp index 304aa09a..a0352e75 100755 --- a/Marlin/src/core/serial.cpp +++ b/Marlin/src/core/serial.cpp @@ -29,7 +29,7 @@ static const char errormagic[] PROGMEM = "Error:"; static const char echomagic[] PROGMEM = "echo:"; #if NUM_SERIAL > 1 - int8_t serial_port_index = 0; + serial_index_t serial_port_index = 0; #endif void serialprintPGM(PGM_P str) { diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h index 812ff547..9db32dab 100755 --- a/Marlin/src/core/serial.h +++ b/Marlin/src/core/serial.h @@ -45,10 +45,10 @@ enum MarlinDebugFlags : uint8_t { extern uint8_t marlin_debug_flags; #define DEBUGGING(F) (marlin_debug_flags & (MARLIN_DEBUG_## F)) - +typedef int8_t serial_index_t; #define SERIAL_BOTH 0x7F #if NUM_SERIAL > 1 - extern int8_t serial_port_index; + extern serial_index_t serial_port_index; #define _PORT_REDIRECT(n,p) REMEMBER(n,serial_port_index,p) #define _PORT_RESTORE(n) RESTORE(n) #define SERIAL_OUT(WHAT, V...) do{ \ diff --git a/Marlin/src/feature/meatpack.cpp b/Marlin/src/feature/meatpack.cpp new file mode 100644 index 00000000..881df4b9 --- /dev/null +++ b/Marlin/src/feature/meatpack.cpp @@ -0,0 +1,228 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/** + * MeatPack G-code Compression + * + * Algorithm & Implementation: Scott Mudge - mail@scottmudge.com + * Date: Dec. 2020 + * + * Character Frequencies from ~30 MB of comment-stripped gcode: + * '1' -> 4451136 '4' -> 1353273 '\n' -> 1087683 '-' -> 90242 + * '0' -> 4253577 '9' -> 1352147 'G' -> 1075806 'Z' -> 34109 + * ' ' -> 3053297 '3' -> 1262929 'X' -> 975742 'M' -> 11879 + * '.' -> 3035310 '5' -> 1189871 'E' -> 965275 'S' -> 9910 + * '2' -> 1523296 '6' -> 1127900 'Y' -> 965274 + * '8' -> 1366812 '7' -> 1112908 'F' -> 99416 + * + * When space is omitted the letter 'E' is used in its place + */ + +#include "../inc/MarlinConfig.h" + +#if ENABLED(MEATPACK) + +#include "meatpack.h" +MeatPack meatpack; + +#define MeatPack_ProtocolVersion "PV01" +//#define MP_DEBUG + +#define DEBUG_OUT ENABLED(MP_DEBUG) +#include "../core/debug_out.h" + +bool MeatPack::cmd_is_next = false; // A command is pending +uint8_t MeatPack::state = 0; // Configuration state OFF +uint8_t MeatPack::second_char = 0; // The unpacked 2nd character from an out-of-sequence packed pair +uint8_t MeatPack::cmd_count = 0, // Counts how many command bytes are received (need 2) +MeatPack::full_char_count = 0, // Counts how many full-width characters are to be received +MeatPack::char_out_count = 0; // Stores number of characters to be read out. +uint8_t MeatPack::char_out_buf[2]; // Output buffer for caching up to 2 characters + +// The 15 most-common characters used in G-code, ~90-95% of all G-code uses these characters +// Stored in SRAM for performance. +uint8_t meatPackLookupTable[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '.', ' ', '\n', 'G', 'X', + '\0' // Unused. 0b1111 indicates a literal character +}; + +TERN_(MP_DEBUG, uint8_t chars_decoded = 0); // Log the first 64 bytes after each reset + +void MeatPack::reset_state() { + state = 0; + cmd_is_next = false; + second_char = 0; + cmd_count = full_char_count = char_out_count = 0; + TERN_(MP_DEBUG, chars_decoded = 0); +} + +/** + * Unpack one or two characters from a packed byte into a buffer. + * Return flags indicating whether any literal bytes follow. + */ +uint8_t MeatPack::unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out) { + uint8_t out = 0; + + // If lower nybble is 1111, the higher nybble is unused, and next char is full. + if ((pk & kFirstNotPacked) == kFirstNotPacked) + out = kFirstCharIsLiteral; + else { + const uint8_t chr = pk & 0x0F; + chars_out[0] = meatPackLookupTable[chr]; // Set the first char + } + + // Check if upper nybble is 1111... if so, we don't need the second char. + if ((pk & kSecondNotPacked) == kSecondNotPacked) + out |= kSecondCharIsLiteral; + else { + const uint8_t chr = (pk >> 4) & 0x0F; + chars_out[1] = meatPackLookupTable[chr]; // Set the second char + } + + return out; +} + +/** + * Interpret a single (non-command) character + * according to the current MeatPack state. + */ +void MeatPack::handle_rx_char_inner(const uint8_t c) { + if (TEST(state, MPConfig_Bit_Active)) { // Is MeatPack active? + if (!full_char_count) { // No literal characters to fetch? + uint8_t buf[2] = { 0, 0 }; + register const uint8_t res = unpack_chars(c, buf); // Decode the byte into one or two characters. + if (res & kFirstCharIsLiteral) { // The 1st character couldn't be packed. + ++full_char_count; // So the next stream byte is a full character. + if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. Another stream byte is a full character. + else second_char = buf[1]; // Retain the unpacked second character. + } + else { + handle_output_char(buf[0]); // Send the unpacked first character out. + if (buf[0] != '\n') { // After a newline the next char won't be set + if (res & kSecondCharIsLiteral) ++full_char_count; // The 2nd character couldn't be packed. The next stream byte is a full character. + else handle_output_char(buf[1]); // Send the unpacked second character out. + } + } + } + else { + handle_output_char(c); // Pass through the character that couldn't be packed... + if (second_char) { + handle_output_char(second_char); // ...and send an unpacked 2nd character, if set. + second_char = 0; + } + --full_char_count; // One literal character was consumed + } + } + else // Packing not enabled, just copy character to output + handle_output_char(c); +} + +/** + * Buffer a single output character which will be picked up in + * GCodeQueue::get_serial_commands via calls to get_result_char + */ +void MeatPack::handle_output_char(const uint8_t c) { + char_out_buf[char_out_count++] = c; + + #if ENABLED(MP_DEBUG) + if (chars_decoded < 1024) { + ++chars_decoded; + DEBUG_ECHOPGM("RB: "); + MYSERIAL.print((char)c); + DEBUG_EOL(); + } + #endif +} + +/** + * Process a MeatPack command byte to update the state. + * Report the new state to serial. + */ +void MeatPack::handle_command(const MeatPack_Command c) { + switch (c) { + case MPCommand_QueryConfig: break; + case MPCommand_EnablePacking: SBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] ENA REC"); break; + case MPCommand_DisablePacking: CBI(state, MPConfig_Bit_Active); DEBUG_ECHOLNPGM("[MPDBG] DIS REC"); break; + case MPCommand_ResetAll: reset_state(); DEBUG_ECHOLNPGM("[MPDBG] RESET REC"); break; + case MPCommand_EnableNoSpaces: + SBI(state, MPConfig_Bit_NoSpaces); + meatPackLookupTable[kSpaceCharIdx] = kSpaceCharReplace; DEBUG_ECHOLNPGM("[MPDBG] ENA NSP"); break; + case MPCommand_DisableNoSpaces: + CBI(state, MPConfig_Bit_NoSpaces); + meatPackLookupTable[kSpaceCharIdx] = ' '; DEBUG_ECHOLNPGM("[MPDBG] DIS NSP"); break; + default: DEBUG_ECHOLNPGM("[MPDBG] UNK CMD REC"); + } + report_state(); +} + +void MeatPack::report_state() { + // NOTE: if any configuration vars are added below, the outgoing sync text for host plugin + // should not contain the "PV' substring, as this is used to indicate protocol version + SERIAL_ECHOPGM("[MP] "); + SERIAL_ECHOPGM(MeatPack_ProtocolVersion " "); + serialprint_onoff(TEST(state, MPConfig_Bit_Active)); + serialprintPGM(TEST(state, MPConfig_Bit_NoSpaces) ? PSTR(" NSP\n") : PSTR(" ESP\n")); +} + +/** + * Interpret a single character received from serial + * according to the current meatpack state. + */ +void MeatPack::handle_rx_char(const uint8_t c, const serial_index_t serial_ind) { + if (c == kCommandByte) { // A command (0xFF) byte? + if (cmd_count) { // In fact, two in a row? + cmd_is_next = true; // Then a MeatPack command follows + cmd_count = 0; + } + else + ++cmd_count; // cmd_count = 1 // One command byte received so far... + return; + } + + if (cmd_is_next) { // Were two command bytes received? + PORT_REDIRECT(serial_ind); + handle_command((MeatPack_Command)c); // Then the byte is a MeatPack command + cmd_is_next = false; + return; + } + + if (cmd_count) { // Only a single 0xFF was received + handle_rx_char_inner(kCommandByte); // A single 0xFF is passed on literally so it can be interpreted as kFirstNotPacked|kSecondNotPacked + cmd_count = 0; + } + + handle_rx_char_inner(c); // Other characters are passed on for MeatPack decoding +} + +uint8_t MeatPack::get_result_char(char* const __restrict out) { + uint8_t res = 0; + if (char_out_count) { + res = char_out_count; + char_out_count = 0; + for (register uint8_t i = 0; i < res; ++i) + out[i] = (char)char_out_buf[i]; + } + return res; +} + +#endif // MEATPACK diff --git a/Marlin/src/feature/meatpack.h b/Marlin/src/feature/meatpack.h new file mode 100644 index 00000000..ac5b22fd --- /dev/null +++ b/Marlin/src/feature/meatpack.h @@ -0,0 +1,123 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * MeatPack G-code Compression + * + * Algorithm & Implementation: Scott Mudge - mail@scottmudge.com + * Date: Dec. 2020 + * + * Specifically optimized for 3D printing G-Code, this is a zero-cost data compression method + * which packs ~180-190% more data into the same amount of bytes going to the CNC controller. + * As a majority of G-Code can be represented by a restricted alphabet, I performed histogram + * analysis on a wide variety of 3D printing gcode samples, and found ~93% of all gcode could + * be represented by the same 15-character alphabet. + * + * This allowed me to design a system of packing 2 8-bit characters into a single byte, assuming + * they fall within this limited 15-character alphabet. Using a 4-bit lookup table, these 8-bit + * characters can be represented by a 4-bit index. + * + * Combined with some logic to allow commingling of full-width characters outside of this 15- + * character alphabet (at the cost of an extra 8-bits per full-width character), and by stripping + * out unnecessary comments, the end result is gcode which is roughly half the original size. + * + * Why did I do this? I noticed micro-stuttering and other data-bottleneck issues while printing + * objects with high curvature, especially at high speeds. There is also the issue of the limited + * baud rate provided by Prusa's Atmega2560-based boards, over the USB serial connection. So soft- + * ware like OctoPrint would also suffer this same micro-stuttering and poor print quality issue. + * + */ +#pragma once + +#include + +/** + * Commands sent to MeatPack to control its behavior. + * They are sent by first sending 2x MeatPack_CommandByte (0xFF) in sequence, + * followed by one of the command bytes below. + * Provided that 0xFF is an exceedingly rare character that is virtually never + * present in G-code naturally, it is safe to assume 2 in sequence should never + * happen naturally, and so it is used as a signal here. + * + * 0xFF *IS* used in "packed" G-code (used to denote that the next 2 characters are + * full-width), however 2 in a row will never occur, as the next 2 bytes will always + * some non-0xFF character. + */ +enum MeatPack_Command : uint8_t { + MPCommand_None = 0, + MPCommand_EnablePacking = 0xFB, + MPCommand_DisablePacking = 0xFA, + MPCommand_ResetAll = 0xF9, + MPCommand_QueryConfig = 0xF8, + MPCommand_EnableNoSpaces = 0xF7, + MPCommand_DisableNoSpaces = 0xF6 +}; + +enum MeatPack_ConfigStateBits : uint8_t { + MPConfig_Bit_Active = 0, + MPConfig_Bit_NoSpaces = 1 +}; + +class MeatPack { +private: + friend class GCodeQueue; + + // Utility definitions + static const uint8_t kCommandByte = 0b11111111, + kFirstNotPacked = 0b00001111, + kSecondNotPacked = 0b11110000, + kFirstCharIsLiteral = 0b00000001, + kSecondCharIsLiteral = 0b00000010; + + static const uint8_t kSpaceCharIdx = 11; + static const char kSpaceCharReplace = 'E'; + + static bool cmd_is_next; // A command is pending + static uint8_t state; // Configuration state + static uint8_t second_char; // Buffers a character if dealing with out-of-sequence pairs + static uint8_t cmd_count, // Counter of command bytes received (need 2) + full_char_count, // Counter for full-width characters to be received + char_out_count; // Stores number of characters to be read out. + static uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters + + // Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences, + // and will control state internally. + static void handle_rx_char(const uint8_t c, const serial_index_t serial_ind); + + /** + * After passing in rx'd char using above method, call this to get characters out. + * Can return from 0 to 2 characters at once. + * @param out [in] Output pointer for unpacked/processed data. + * @return Number of characters returned. Range from 0 to 2. + */ + static uint8_t get_result_char(char* const __restrict out); + + static void reset_state(); + static void report_state(); + static uint8_t unpacked_char(register const uint8_t in); + static uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out); + static void handle_command(const MeatPack_Command c); + static void handle_output_char(const uint8_t c); + static void handle_rx_char_inner(const uint8_t c); +}; + +extern MeatPack meatpack; \ No newline at end of file diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index d74f909c..105d86f3 100755 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -112,5 +112,8 @@ void GcodeSuite::M115() { // CHAMBER_TEMPERATURE (M141, M191) cap_line(PSTR("CHAMBER_TEMPERATURE"), ENABLED(HAS_HEATED_CHAMBER)); + // MEATPACK Compresson + cap_line(PSTR("MEATPACK"), ENABLED(MEATPACK)); + #endif // EXTENDED_CAPABILITIES_REPORT } diff --git a/Marlin/src/gcode/host/M118.cpp b/Marlin/src/gcode/host/M118.cpp index ba52a4f8..8b8c30b8 100755 --- a/Marlin/src/gcode/host/M118.cpp +++ b/Marlin/src/gcode/host/M118.cpp @@ -53,7 +53,7 @@ void GcodeSuite::M118() { } #if NUM_SERIAL > 1 - const int8_t old_serial = serial_port_index; + const serial_index_t old_serial = serial_port_index; if (WITHIN(port, 0, NUM_SERIAL)) serial_port_index = ( port == 0 ? SERIAL_BOTH diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp index abb50b96..1b4f7a14 100755 --- a/Marlin/src/gcode/queue.cpp +++ b/Marlin/src/gcode/queue.cpp @@ -43,6 +43,10 @@ GCodeQueue queue; #include "../feature/binary_protocol.h" #endif +#if ENABLED(MEATPACK) + #include "../feature/meatpack.h" +#endif + #if ENABLED(POWER_LOSS_RECOVERY) #include "../feature/powerloss.h" #endif @@ -73,7 +77,7 @@ char GCodeQueue::command_buffer[BUFSIZE][MAX_CMD_SIZE]; * The port that the command was received on */ #if NUM_SERIAL > 1 - int16_t GCodeQueue::port[BUFSIZE]; + serial_index_t GCodeQueue::port[BUFSIZE]; #endif /** @@ -116,12 +120,12 @@ void GCodeQueue::clear() { */ void GCodeQueue::_commit_command(bool say_ok #if NUM_SERIAL > 1 - , int16_t p/*=-1*/ + , serial_index_t serial_ind/*=-1*/ #endif ) { send_ok[index_w] = say_ok; #if NUM_SERIAL > 1 - port[index_w] = p; + port[index_w] = serial_ind; #endif #if ENABLED(POWER_LOSS_RECOVERY) recovery.commit_sdpos(index_w); @@ -137,14 +141,14 @@ void GCodeQueue::_commit_command(bool say_ok */ bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/ #if NUM_SERIAL > 1 - , int16_t pn/*=-1*/ + , serial_index_t serial_ind/*=-1*/ #endif ) { if (*cmd == ';' || length >= BUFSIZE) return false; strcpy(command_buffer[index_w], cmd); _commit_command(say_ok #if NUM_SERIAL > 1 - , pn + , serial_ind #endif ); return true; @@ -256,9 +260,9 @@ void GCodeQueue::enqueue_now_P(PGM_P const pgcode) { */ void GCodeQueue::ok_to_send() { #if NUM_SERIAL > 1 - const int16_t pn = port[index_r]; - if (pn < 0) return; - PORT_REDIRECT(pn); // Reply to the serial port that sent the command + const serial_index_t serial_ind = command_port(); + if (serial_ind < 0) return; // Never mind. Command came from SD or Flash Drive + PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command #endif if (!send_ok[index_r]) return; SERIAL_ECHOPGM(STR_OK); @@ -282,9 +286,9 @@ void GCodeQueue::ok_to_send() { */ void GCodeQueue::flush_and_request_resend() { #if NUM_SERIAL > 1 - const int16_t pn = port[index_r]; - if (pn < 0) return; - PORT_REDIRECT(pn); // Reply to the serial port that sent the command + const serial_index_t serial_ind = port[index_r]; + if (serial_ind < 0) return; + PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command #endif SERIAL_FLUSH(); SERIAL_ECHOPGM(STR_RESEND); @@ -311,14 +315,14 @@ inline int read_serial(const uint8_t index) { } } -void GCodeQueue::gcode_line_error(PGM_P const err, const int8_t pn) { - PORT_REDIRECT(pn); // Reply to the serial port that sent the command +void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) { + PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command SERIAL_ERROR_START(); serialprintPGM(err); SERIAL_ECHOLN(last_N); - while (read_serial(pn) != -1); // Clear out the RX buffer + while (read_serial(serial_ind) != -1); // Clear out the RX buffer flush_and_request_resend(); - serial_count[pn] = 0; + serial_count[serial_ind] = 0; } FORCE_INLINE bool is_M29(const char * const cmd) { // matches "M29" & "M29 ", but not "M290", etc @@ -383,11 +387,14 @@ inline void process_stream_char(const char c, uint8_t &sis, char (&buff)[MAX_CMD * keep sensor readings going and watchdog alive. */ inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) { - sis = PS_NORMAL; - buff[ind] = 0; - if (ind) { ind = 0; return false; } - thermalManager.manage_heater(); - return true; + sis = PS_NORMAL; // "Normal" Serial Input State + buff[ind] = '\0'; // Of course, I'm a Terminator. + const bool is_empty = (ind == 0); // An empty line? + if (is_empty) + thermalManager.manage_heater(); // Keep sensors satisfied + else + ind = 0; // Start a new line + return is_empty; // Inform the caller } /** @@ -427,105 +434,114 @@ void GCodeQueue::get_serial_commands() { * Loop while serial characters are incoming and the queue is not full */ while (length < BUFSIZE && serial_data_available()) { - LOOP_L_N(i, NUM_SERIAL) { + LOOP_L_N(p, NUM_SERIAL) { - const int c = read_serial(i); + const int c = read_serial(p); if (c < 0) continue; - const char serial_char = c; + #if ENABLED(MEATPACK) + meatpack.handle_rx_char(uint8_t(c), p); + char c_res[2] = { 0, 0 }; + const uint8_t char_count = meatpack.get_result_char(c_res); + #else + constexpr uint8_t char_count = 1; + #endif - if (ISEOL(serial_char)) { + LOOP_L_N(char_index, char_count) { + const char serial_char = TERN(MEATPACK, c_res[char_index], c); - // Reset our state, continue if the line was empty - if (process_line_done(serial_input_state[i], serial_line_buffer[i], serial_count[i])) - continue; + if (ISEOL(serial_char)) { - char* command = serial_line_buffer[i]; + // Reset our state, continue if the line was empty + if (process_line_done(serial_input_state[p], serial_line_buffer[p], serial_count[p])) + continue; - while (*command == ' ') command++; // Skip leading spaces - char *npos = (*command == 'N') ? command : nullptr; // Require the N parameter to start the line + char* command = serial_line_buffer[p]; - if (npos) { + while (*command == ' ') command++; // Skip leading spaces + char *npos = (*command == 'N') ? command : nullptr; // Require the N parameter to start the line - bool M110 = strstr_P(command, PSTR("M110")) != nullptr; + if (npos) { - if (M110) { - char* n2pos = strchr(command + 4, 'N'); - if (n2pos) npos = n2pos; + bool M110 = strstr_P(command, PSTR("M110")) != nullptr; + + if (M110) { + char* n2pos = strchr(command + 4, 'N'); + if (n2pos) npos = n2pos; + } + + gcode_N = strtol(npos + 1, nullptr, 10); + + if (gcode_N != last_N + 1 && !M110) + return gcode_line_error(PSTR(STR_ERR_LINE_NO), p); + + char *apos = strrchr(command, '*'); + if (apos) { + uint8_t checksum = 0, count = uint8_t(apos - command); + while (count) checksum ^= command[--count]; + if (strtol(apos + 1, nullptr, 10) != checksum) + return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), p); + } + else + return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p); + + last_N = gcode_N; } + #if ENABLED(SDSUPPORT) + // Pronterface "M29" and "M29 " has no line number + else if (card.flag.saving && !is_M29(command)) + return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p); + #endif - gcode_N = strtol(npos + 1, nullptr, 10); + // + // Movement commands give an alert when the machine is stopped + // - if (gcode_N != last_N + 1 && !M110) - return gcode_line_error(PSTR(STR_ERR_LINE_NO), i); - - char *apos = strrchr(command, '*'); - if (apos) { - uint8_t checksum = 0, count = uint8_t(apos - command); - while (count) checksum ^= command[--count]; - if (strtol(apos + 1, nullptr, 10) != checksum) - return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), i); - } - else - return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i); - - last_N = gcode_N; - } - #if ENABLED(SDSUPPORT) - // Pronterface "M29" and "M29 " has no line number - else if (card.flag.saving && !is_M29(command)) - return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i); - #endif - - // - // Movement commands give an alert when the machine is stopped - // - - if (IsStopped()) { - char* gpos = strchr(command, 'G'); - if (gpos) { - switch (strtol(gpos + 1, nullptr, 10)) { - case 0: case 1: - #if ENABLED(ARC_SUPPORT) - case 2: case 3: - #endif - #if ENABLED(BEZIER_CURVE_SUPPORT) - case 5: - #endif - PORT_REDIRECT(i); // Reply to the serial port that sent the command - SERIAL_ECHOLNPGM(STR_ERR_STOPPED); - LCD_MESSAGEPGM(MSG_STOPPED); - break; + if (IsStopped()) { + char* gpos = strchr(command, 'G'); + if (gpos) { + switch (strtol(gpos + 1, nullptr, 10)) { + case 0: case 1: + #if ENABLED(ARC_SUPPORT) + case 2: case 3: + #endif + #if ENABLED(BEZIER_CURVE_SUPPORT) + case 5: + #endif + PORT_REDIRECT(p); // Reply to the serial port that sent the command + SERIAL_ECHOLNPGM(STR_ERR_STOPPED); + LCD_MESSAGEPGM(MSG_STOPPED); + break; + } } } - } - #if DISABLED(EMERGENCY_PARSER) - // Process critical commands early - if (strcmp(command, "M108") == 0) { - wait_for_heatup = false; - #if HAS_LCD_MENU - wait_for_user = false; - #endif - } - if (strcmp(command, "M112") == 0) kill(M112_KILL_STR, nullptr, true); - if (strcmp(command, "M410") == 0) quickstop_stepper(); - #endif - - #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0 - last_command_time = ms; - #endif - - // Add the command to the queue - _enqueue(serial_line_buffer[i], true - #if NUM_SERIAL > 1 - , i + #if DISABLED(EMERGENCY_PARSER) + // Process critical commands early + if (strcmp(command, "M108") == 0) { + wait_for_heatup = false; + #if HAS_LCD_MENU + wait_for_user = false; + #endif + } + if (strcmp(command, "M112") == 0) kill(M112_KILL_STR, nullptr, true); + if (strcmp(command, "M410") == 0) quickstop_stepper(); #endif - ); - } - else - process_stream_char(serial_char, serial_input_state[i], serial_line_buffer[i], serial_count[i]); + #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0 + last_command_time = ms; + #endif + + // Add the command to the queue + _enqueue(serial_line_buffer[p], true + #if NUM_SERIAL > 1 + , p + #endif + ); + } + else + process_stream_char(serial_char, serial_input_state[p], serial_line_buffer[p], serial_count[p]); + } // char_count loop } // for NUM_SERIAL } // queue has space, serial has data } diff --git a/Marlin/src/gcode/queue.h b/Marlin/src/gcode/queue.h index 6a87d47a..40d1a897 100755 --- a/Marlin/src/gcode/queue.h +++ b/Marlin/src/gcode/queue.h @@ -55,7 +55,7 @@ public: * The port that the command was received on */ #if NUM_SERIAL > 1 - static int16_t port[BUFSIZE]; + static serial_index_t port[BUFSIZE]; #endif GCodeQueue(); @@ -135,13 +135,13 @@ private: static void _commit_command(bool say_ok #if NUM_SERIAL > 1 - , int16_t p=-1 + , serial_index_t serial_ind=-1 #endif ); static bool _enqueue(const char* cmd, bool say_ok=false #if NUM_SERIAL > 1 - , int16_t p=-1 + , serial_index_t serial_ind=-1 #endif ); @@ -154,7 +154,7 @@ private: */ static bool enqueue_one(const char* cmd); - static void gcode_line_error(PGM_P const err, const int8_t pn); + static void gcode_line_error(PGM_P const err, const serial_index_t serial_ind); }; diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 2fd61d4b..123b87bd 100755 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -2833,3 +2833,10 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) #if SAVED_POSITIONS > 256 #error "SAVED_POSITIONS must be an integer from 0 to 256." #endif + +/** + * Sanity Check for MEATPACK and BINARY_FILE_TRANSFER Features + */ +#if BOTH(MEATPACK, BINARY_FILE_TRANSFER) + #error "Either enable MEATPACK or enable BINARY_FILE_TRANSFER." +#endif diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index e1702419..35465f65 100755 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -1101,7 +1101,7 @@ void CardReader::fileHasFinished() { uint8_t CardReader::auto_report_sd_interval = 0; millis_t CardReader::next_sd_report_ms; #if NUM_SERIAL > 1 - int8_t CardReader::auto_report_port; + serial_index_t CardReader::auto_report_port; #endif void CardReader::auto_report_sd_status() { diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h index 0dd52de2..839b151e 100755 --- a/Marlin/src/sd/cardreader.h +++ b/Marlin/src/sd/cardreader.h @@ -255,7 +255,7 @@ private: static uint8_t auto_report_sd_interval; static millis_t next_sd_report_ms; #if NUM_SERIAL > 1 - static int8_t auto_report_port; + static serial_index_t auto_report_port; #endif #endif diff --git a/buildroot/share/tests/FYSETC_S6-tests b/buildroot/share/tests/FYSETC_S6-tests index a9e0331a..6b7e4e02 100755 --- a/buildroot/share/tests/FYSETC_S6-tests +++ b/buildroot/share/tests/FYSETC_S6-tests @@ -8,6 +8,7 @@ set -e # Build examples restore_configs +opt_enable MEATPACK use_example_configs FYSETC/S6 exec_test $1 $2 "FYSETC S6 Example"