Merge pull request #185 from ashleysommer/meatpack_add
Backport meatpack feature from upstream
This commit is contained in:
@@ -3206,3 +3206,5 @@
|
|||||||
|
|
||||||
//#define KNUTWURST_MEGAS_ADV
|
//#define KNUTWURST_MEGAS_ADV
|
||||||
//#define KNUTWURST_TMC_ADV
|
//#define KNUTWURST_TMC_ADV
|
||||||
|
|
||||||
|
#define MEATPACK // Support for MeatPack G-code compression (https://github.com/scottmudge/OctoPrint-MeatPack)
|
@@ -29,7 +29,7 @@ static const char errormagic[] PROGMEM = "Error:";
|
|||||||
static const char echomagic[] PROGMEM = "echo:";
|
static const char echomagic[] PROGMEM = "echo:";
|
||||||
|
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
int8_t serial_port_index = 0;
|
serial_index_t serial_port_index = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void serialprintPGM(PGM_P str) {
|
void serialprintPGM(PGM_P str) {
|
||||||
|
@@ -45,10 +45,10 @@ enum MarlinDebugFlags : uint8_t {
|
|||||||
|
|
||||||
extern uint8_t marlin_debug_flags;
|
extern uint8_t marlin_debug_flags;
|
||||||
#define DEBUGGING(F) (marlin_debug_flags & (MARLIN_DEBUG_## F))
|
#define DEBUGGING(F) (marlin_debug_flags & (MARLIN_DEBUG_## F))
|
||||||
|
typedef int8_t serial_index_t;
|
||||||
#define SERIAL_BOTH 0x7F
|
#define SERIAL_BOTH 0x7F
|
||||||
#if NUM_SERIAL > 1
|
#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_REDIRECT(n,p) REMEMBER(n,serial_port_index,p)
|
||||||
#define _PORT_RESTORE(n) RESTORE(n)
|
#define _PORT_RESTORE(n) RESTORE(n)
|
||||||
#define SERIAL_OUT(WHAT, V...) do{ \
|
#define SERIAL_OUT(WHAT, V...) do{ \
|
||||||
|
228
Marlin/src/feature/meatpack.cpp
Normal file
228
Marlin/src/feature/meatpack.cpp
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
123
Marlin/src/feature/meatpack.h
Normal file
123
Marlin/src/feature/meatpack.h
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
@@ -112,5 +112,8 @@ void GcodeSuite::M115() {
|
|||||||
// CHAMBER_TEMPERATURE (M141, M191)
|
// CHAMBER_TEMPERATURE (M141, M191)
|
||||||
cap_line(PSTR("CHAMBER_TEMPERATURE"), ENABLED(HAS_HEATED_CHAMBER));
|
cap_line(PSTR("CHAMBER_TEMPERATURE"), ENABLED(HAS_HEATED_CHAMBER));
|
||||||
|
|
||||||
|
// MEATPACK Compresson
|
||||||
|
cap_line(PSTR("MEATPACK"), ENABLED(MEATPACK));
|
||||||
|
|
||||||
#endif // EXTENDED_CAPABILITIES_REPORT
|
#endif // EXTENDED_CAPABILITIES_REPORT
|
||||||
}
|
}
|
||||||
|
@@ -53,7 +53,7 @@ void GcodeSuite::M118() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if NUM_SERIAL > 1
|
#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))
|
if (WITHIN(port, 0, NUM_SERIAL))
|
||||||
serial_port_index = (
|
serial_port_index = (
|
||||||
port == 0 ? SERIAL_BOTH
|
port == 0 ? SERIAL_BOTH
|
||||||
|
@@ -43,6 +43,10 @@ GCodeQueue queue;
|
|||||||
#include "../feature/binary_protocol.h"
|
#include "../feature/binary_protocol.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(MEATPACK)
|
||||||
|
#include "../feature/meatpack.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ENABLED(POWER_LOSS_RECOVERY)
|
#if ENABLED(POWER_LOSS_RECOVERY)
|
||||||
#include "../feature/powerloss.h"
|
#include "../feature/powerloss.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -73,7 +77,7 @@ char GCodeQueue::command_buffer[BUFSIZE][MAX_CMD_SIZE];
|
|||||||
* The port that the command was received on
|
* The port that the command was received on
|
||||||
*/
|
*/
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
int16_t GCodeQueue::port[BUFSIZE];
|
serial_index_t GCodeQueue::port[BUFSIZE];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,12 +120,12 @@ void GCodeQueue::clear() {
|
|||||||
*/
|
*/
|
||||||
void GCodeQueue::_commit_command(bool say_ok
|
void GCodeQueue::_commit_command(bool say_ok
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
, int16_t p/*=-1*/
|
, serial_index_t serial_ind/*=-1*/
|
||||||
#endif
|
#endif
|
||||||
) {
|
) {
|
||||||
send_ok[index_w] = say_ok;
|
send_ok[index_w] = say_ok;
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
port[index_w] = p;
|
port[index_w] = serial_ind;
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(POWER_LOSS_RECOVERY)
|
#if ENABLED(POWER_LOSS_RECOVERY)
|
||||||
recovery.commit_sdpos(index_w);
|
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*/
|
bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
, int16_t pn/*=-1*/
|
, serial_index_t serial_ind/*=-1*/
|
||||||
#endif
|
#endif
|
||||||
) {
|
) {
|
||||||
if (*cmd == ';' || length >= BUFSIZE) return false;
|
if (*cmd == ';' || length >= BUFSIZE) return false;
|
||||||
strcpy(command_buffer[index_w], cmd);
|
strcpy(command_buffer[index_w], cmd);
|
||||||
_commit_command(say_ok
|
_commit_command(say_ok
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
, pn
|
, serial_ind
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
@@ -256,9 +260,9 @@ void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
|
|||||||
*/
|
*/
|
||||||
void GCodeQueue::ok_to_send() {
|
void GCodeQueue::ok_to_send() {
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
const int16_t pn = port[index_r];
|
const serial_index_t serial_ind = command_port();
|
||||||
if (pn < 0) return;
|
if (serial_ind < 0) return; // Never mind. Command came from SD or Flash Drive
|
||||||
PORT_REDIRECT(pn); // Reply to the serial port that sent the command
|
PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command
|
||||||
#endif
|
#endif
|
||||||
if (!send_ok[index_r]) return;
|
if (!send_ok[index_r]) return;
|
||||||
SERIAL_ECHOPGM(STR_OK);
|
SERIAL_ECHOPGM(STR_OK);
|
||||||
@@ -282,9 +286,9 @@ void GCodeQueue::ok_to_send() {
|
|||||||
*/
|
*/
|
||||||
void GCodeQueue::flush_and_request_resend() {
|
void GCodeQueue::flush_and_request_resend() {
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
const int16_t pn = port[index_r];
|
const serial_index_t serial_ind = port[index_r];
|
||||||
if (pn < 0) return;
|
if (serial_ind < 0) return;
|
||||||
PORT_REDIRECT(pn); // Reply to the serial port that sent the command
|
PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command
|
||||||
#endif
|
#endif
|
||||||
SERIAL_FLUSH();
|
SERIAL_FLUSH();
|
||||||
SERIAL_ECHOPGM(STR_RESEND);
|
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) {
|
void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
|
||||||
PORT_REDIRECT(pn); // Reply to the serial port that sent the command
|
PORT_REDIRECT(serial_ind); // Reply to the serial port that sent the command
|
||||||
SERIAL_ERROR_START();
|
SERIAL_ERROR_START();
|
||||||
serialprintPGM(err);
|
serialprintPGM(err);
|
||||||
SERIAL_ECHOLN(last_N);
|
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();
|
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
|
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.
|
* keep sensor readings going and watchdog alive.
|
||||||
*/
|
*/
|
||||||
inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
|
inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
|
||||||
sis = PS_NORMAL;
|
sis = PS_NORMAL; // "Normal" Serial Input State
|
||||||
buff[ind] = 0;
|
buff[ind] = '\0'; // Of course, I'm a Terminator.
|
||||||
if (ind) { ind = 0; return false; }
|
const bool is_empty = (ind == 0); // An empty line?
|
||||||
thermalManager.manage_heater();
|
if (is_empty)
|
||||||
return true;
|
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
|
* Loop while serial characters are incoming and the queue is not full
|
||||||
*/
|
*/
|
||||||
while (length < BUFSIZE && serial_data_available()) {
|
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;
|
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 (ISEOL(serial_char)) {
|
||||||
if (process_line_done(serial_input_state[i], serial_line_buffer[i], serial_count[i]))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
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* command = serial_line_buffer[p];
|
||||||
char *npos = (*command == 'N') ? command : nullptr; // Require the N parameter to start the line
|
|
||||||
|
|
||||||
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) {
|
bool M110 = strstr_P(command, PSTR("M110")) != nullptr;
|
||||||
char* n2pos = strchr(command + 4, 'N');
|
|
||||||
if (n2pos) npos = n2pos;
|
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)
|
if (IsStopped()) {
|
||||||
return gcode_line_error(PSTR(STR_ERR_LINE_NO), i);
|
char* gpos = strchr(command, 'G');
|
||||||
|
if (gpos) {
|
||||||
char *apos = strrchr(command, '*');
|
switch (strtol(gpos + 1, nullptr, 10)) {
|
||||||
if (apos) {
|
case 0: case 1:
|
||||||
uint8_t checksum = 0, count = uint8_t(apos - command);
|
#if ENABLED(ARC_SUPPORT)
|
||||||
while (count) checksum ^= command[--count];
|
case 2: case 3:
|
||||||
if (strtol(apos + 1, nullptr, 10) != checksum)
|
#endif
|
||||||
return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), i);
|
#if ENABLED(BEZIER_CURVE_SUPPORT)
|
||||||
}
|
case 5:
|
||||||
else
|
#endif
|
||||||
return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
|
PORT_REDIRECT(p); // Reply to the serial port that sent the command
|
||||||
|
SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
|
||||||
last_N = gcode_N;
|
LCD_MESSAGEPGM(MSG_STOPPED);
|
||||||
}
|
break;
|
||||||
#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 DISABLED(EMERGENCY_PARSER)
|
#if DISABLED(EMERGENCY_PARSER)
|
||||||
// Process critical commands early
|
// Process critical commands early
|
||||||
if (strcmp(command, "M108") == 0) {
|
if (strcmp(command, "M108") == 0) {
|
||||||
wait_for_heatup = false;
|
wait_for_heatup = false;
|
||||||
#if HAS_LCD_MENU
|
#if HAS_LCD_MENU
|
||||||
wait_for_user = false;
|
wait_for_user = false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (strcmp(command, "M112") == 0) kill(M112_KILL_STR, nullptr, true);
|
if (strcmp(command, "M112") == 0) kill(M112_KILL_STR, nullptr, true);
|
||||||
if (strcmp(command, "M410") == 0) quickstop_stepper();
|
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
|
|
||||||
#endif
|
#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
|
} // for NUM_SERIAL
|
||||||
} // queue has space, serial has data
|
} // queue has space, serial has data
|
||||||
}
|
}
|
||||||
|
@@ -55,7 +55,7 @@ public:
|
|||||||
* The port that the command was received on
|
* The port that the command was received on
|
||||||
*/
|
*/
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
static int16_t port[BUFSIZE];
|
static serial_index_t port[BUFSIZE];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
GCodeQueue();
|
GCodeQueue();
|
||||||
@@ -135,13 +135,13 @@ private:
|
|||||||
|
|
||||||
static void _commit_command(bool say_ok
|
static void _commit_command(bool say_ok
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
, int16_t p=-1
|
, serial_index_t serial_ind=-1
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
static bool _enqueue(const char* cmd, bool say_ok=false
|
static bool _enqueue(const char* cmd, bool say_ok=false
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
, int16_t p=-1
|
, serial_index_t serial_ind=-1
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ private:
|
|||||||
*/
|
*/
|
||||||
static bool enqueue_one(const char* cmd);
|
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);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -2833,3 +2833,10 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
|
|||||||
#if SAVED_POSITIONS > 256
|
#if SAVED_POSITIONS > 256
|
||||||
#error "SAVED_POSITIONS must be an integer from 0 to 256."
|
#error "SAVED_POSITIONS must be an integer from 0 to 256."
|
||||||
#endif
|
#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
|
||||||
|
@@ -1101,7 +1101,7 @@ void CardReader::fileHasFinished() {
|
|||||||
uint8_t CardReader::auto_report_sd_interval = 0;
|
uint8_t CardReader::auto_report_sd_interval = 0;
|
||||||
millis_t CardReader::next_sd_report_ms;
|
millis_t CardReader::next_sd_report_ms;
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
int8_t CardReader::auto_report_port;
|
serial_index_t CardReader::auto_report_port;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void CardReader::auto_report_sd_status() {
|
void CardReader::auto_report_sd_status() {
|
||||||
|
@@ -255,7 +255,7 @@ private:
|
|||||||
static uint8_t auto_report_sd_interval;
|
static uint8_t auto_report_sd_interval;
|
||||||
static millis_t next_sd_report_ms;
|
static millis_t next_sd_report_ms;
|
||||||
#if NUM_SERIAL > 1
|
#if NUM_SERIAL > 1
|
||||||
static int8_t auto_report_port;
|
static serial_index_t auto_report_port;
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ set -e
|
|||||||
|
|
||||||
# Build examples
|
# Build examples
|
||||||
restore_configs
|
restore_configs
|
||||||
|
opt_enable MEATPACK
|
||||||
use_example_configs FYSETC/S6
|
use_example_configs FYSETC/S6
|
||||||
exec_test $1 $2 "FYSETC S6 Example"
|
exec_test $1 $2 "FYSETC S6 Example"
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user