/** * 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 #include "../core/serial_hook.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 { // 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'; bool cmd_is_next; // A command is pending uint8_t state; // Configuration state uint8_t second_char; // Buffers a character if dealing with out-of-sequence pairs 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. uint8_t char_out_buf[2]; // Output buffer for caching up to 2 characters public: // Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences, // and will control state internally. 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. */ uint8_t get_result_char(char * const __restrict out); void reset_state(); void report_state(); uint8_t unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out); void handle_command(const MeatPack_Command c); void handle_output_char(const uint8_t c); void handle_rx_char_inner(const uint8_t c); MeatPack() : cmd_is_next(false), state(0), second_char(0), cmd_count(0), full_char_count(0), char_out_count(0) {} }; // Implement the MeatPack serial class so it's transparent to rest of the code template struct MeatpackSerial : public SerialBase > { typedef SerialBase< MeatpackSerial > BaseClassT; SerialT & out; MeatPack meatpack; char serialBuffer[2]; uint8_t charCount; uint8_t readIndex; NO_INLINE size_t write(uint8_t c) { return out.write(c); } void flush() { out.flush(); } void begin(long br) { out.begin(br); readIndex = 0; } void end() { out.end(); } void msgDone() { out.msgDone(); } // Existing instances implement Arduino's operator bool, so use that if it's available bool connected() { return Private::HasMember_connected::value ? CALL_IF_EXISTS(bool, &out, connected) : (bool)out; } void flushTX() { CALL_IF_EXISTS(void, &out, flushTX); } SerialFeature features(serial_index_t index) const { return SerialFeature::MeatPack | CALL_IF_EXISTS(SerialFeature, &out, features, index); } int available(serial_index_t index) { if (charCount) return charCount; // The buffer still has data if (out.available(index) <= 0) return 0; // No data to read // Don't read in read method, instead do it here, so we can make progress in the read method const int r = out.read(index); if (r == -1) return 0; // This is an error from the underlying serial code meatpack.handle_rx_char((uint8_t)r, index); charCount = meatpack.get_result_char(serialBuffer); readIndex = 0; return charCount; } int readImpl(const serial_index_t index) { // Not enough char to make progress? if (charCount == 0 && available(index) == 0) return -1; charCount--; return serialBuffer[readIndex++]; } int read(serial_index_t index) { return readImpl(index); } int available() { return available(0); } int read() { return readImpl(0); } MeatpackSerial(const bool e, SerialT & out) : BaseClassT(e), out(out) {} };