diff --git a/Marlin/src/feature/binary_protocol.cpp b/Marlin/src/feature/binary_protocol.cpp
new file mode 100644
index 0000000000..81ccbfbe32
--- /dev/null
+++ b/Marlin/src/feature/binary_protocol.cpp
@@ -0,0 +1,36 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2019 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 .
+ *
+ */
+
+#include "../inc/MarlinConfigPre.h"
+
+#if ENABLED(BINARY_FILE_TRANSFER)
+
+#include "../sd/cardreader.h"
+#include "binary_protocol.h"
+
+char* SDFileTransferProtocol::Packet::Open::data = nullptr;
+size_t SDFileTransferProtocol::data_waiting, SDFileTransferProtocol::transfer_timeout, SDFileTransferProtocol::idle_timeout;
+bool SDFileTransferProtocol::transfer_active, SDFileTransferProtocol::dummy_transfer, SDFileTransferProtocol::compression;
+
+BinaryStream binaryStream[NUM_SERIAL];
+
+#endif // BINARY_FILE_TRANSFER
diff --git a/Marlin/src/feature/binary_protocol.h b/Marlin/src/feature/binary_protocol.h
new file mode 100644
index 0000000000..2db33e3505
--- /dev/null
+++ b/Marlin/src/feature/binary_protocol.h
@@ -0,0 +1,471 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2019 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 .
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+
+#define BINARY_STREAM_COMPRESSION
+
+#if ENABLED(BINARY_STREAM_COMPRESSION)
+ #include "../libs/heatshrink/heatshrink_decoder.h"
+#endif
+
+inline bool bs_serial_data_available(const uint8_t index) {
+ switch (index) {
+ case 0: return MYSERIAL0.available();
+ #if NUM_SERIAL > 1
+ case 1: return MYSERIAL1.available();
+ #endif
+ }
+ return false;
+}
+
+inline int bs_read_serial(const uint8_t index) {
+ switch (index) {
+ case 0: return MYSERIAL0.read();
+ #if NUM_SERIAL > 1
+ case 1: return MYSERIAL1.read();
+ #endif
+ }
+ return -1;
+}
+
+#if ENABLED(BINARY_STREAM_COMPRESSION)
+ static heatshrink_decoder hsd;
+ static uint8_t decode_buffer[512] = {};
+#endif
+
+class SDFileTransferProtocol {
+private:
+ struct Packet {
+ struct [[gnu::packed]] Open {
+ static bool validate(char* buffer, size_t length) {
+ return (length > sizeof(Open) && buffer[length - 1] == '\0');
+ }
+ static Open& decode(char* buffer) {
+ data = &buffer[2];
+ return *reinterpret_cast(buffer);
+ }
+ bool compression_enabled() { return compression & 0x1; }
+ bool dummy_transfer() { return dummy & 0x1; }
+ static char* filename() { return data; }
+ private:
+ uint8_t dummy, compression;
+ static char* data; // variable length strings complicate things
+ };
+ };
+
+ static bool file_open(char* filename) {
+ if (!dummy_transfer) {
+ card.initsd();
+ card.openFile(filename, false);
+ if (!card.isFileOpen()) return false;
+ }
+ transfer_active = true;
+ data_waiting = 0;
+ #if ENABLED(BINARY_STREAM_COMPRESSION)
+ heatshrink_decoder_reset(&hsd);
+ #endif
+ return true;
+ }
+
+ static bool file_write(char* buffer, const size_t length) {
+ #if ENABLED(BINARY_STREAM_COMPRESSION)
+ if (compression) {
+ size_t total_processed = 0, processed_count = 0;
+ HSD_poll_res presult;
+
+ while (total_processed < length) {
+ heatshrink_decoder_sink(&hsd, reinterpret_cast(&buffer[total_processed]), length - total_processed, &processed_count);
+ total_processed += processed_count;
+ do {
+ presult = heatshrink_decoder_poll(&hsd, &decode_buffer[data_waiting], sizeof(decode_buffer) - data_waiting, &processed_count);
+ data_waiting += processed_count;
+ if (data_waiting == sizeof(decode_buffer)) {
+ if (!dummy_transfer)
+ if (card.write(decode_buffer, data_waiting) < 0) {
+ return false;
+ }
+ data_waiting = 0;
+ }
+ } while (presult == HSDR_POLL_MORE);
+ }
+ return true;
+ }
+ #endif
+ return (dummy_transfer || card.write(buffer, length) >= 0);
+ }
+
+ static bool file_close() {
+ if (!dummy_transfer) {
+ #if ENABLED(BINARY_STREAM_COMPRESSION)
+ // flush any buffered data
+ if (data_waiting) {
+ if (card.write(decode_buffer, data_waiting) < 0) return false;
+ data_waiting = 0;
+ }
+ #endif
+ card.closefile();
+ card.release();
+ }
+ #if ENABLED(BINARY_STREAM_COMPRESSION)
+ heatshrink_decoder_finish(&hsd);
+ #endif
+ transfer_active = false;
+ return true;
+ }
+
+ static void transfer_abort() {
+ if (!dummy_transfer) {
+ card.closefile();
+ card.removeFile(card.filename);
+ card.release();
+ #if ENABLED(BINARY_STREAM_COMPRESSION)
+ heatshrink_decoder_finish(&hsd);
+ #endif
+ }
+ transfer_active = false;
+ return;
+ }
+
+ enum class FileTransfer : uint8_t { QUERY, OPEN, CLOSE, WRITE, ABORT };
+
+ static size_t data_waiting, transfer_timeout, idle_timeout;
+ static bool transfer_active, dummy_transfer, compression;
+
+public:
+
+ static void idle() {
+ // If a transfer is interrupted and a file is left open, abort it after TIMEOUT ms
+ const millis_t ms = millis();
+ if (transfer_active && ELAPSED(ms, idle_timeout)) {
+ idle_timeout = ms + IDLE_PERIOD;
+ if (ELAPSED(ms, transfer_timeout)) transfer_abort();
+ }
+ }
+
+ static void process(uint8_t packet_type, char* buffer, const uint16_t length) {
+ transfer_timeout = millis() + TIMEOUT;
+ switch (static_cast(packet_type)) {
+ case FileTransfer::QUERY:
+ SERIAL_ECHOPAIR("PFT:version:", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
+ #if ENABLED(BINARY_STREAM_COMPRESSION)
+ SERIAL_ECHOLNPAIR(":compresion:heatshrink,", HEATSHRINK_STATIC_WINDOW_BITS, ",", HEATSHRINK_STATIC_LOOKAHEAD_BITS);
+ #else
+ SERIAL_ECHOLNPGM(":compresion:none");
+ #endif
+ break;
+ case FileTransfer::OPEN:
+ if (transfer_active)
+ SERIAL_ECHOLNPGM("PFT:busy");
+ else {
+ if (Packet::Open::validate(buffer, length)) {
+ auto packet = Packet::Open::decode(buffer);
+ compression = packet.compression_enabled();
+ dummy_transfer = packet.dummy_transfer();
+ if (file_open(packet.filename())) {
+ SERIAL_ECHOLNPGM("PFT:success");
+ break;
+ }
+ }
+ SERIAL_ECHOLNPGM("PFT:fail");
+ }
+ break;
+ case FileTransfer::CLOSE:
+ if (transfer_active) {
+ if (file_close())
+ SERIAL_ECHOLNPGM("PFT:success");
+ else
+ SERIAL_ECHOLNPGM("PFT:ioerror");
+ }
+ else SERIAL_ECHOLNPGM("PFT:invalid");
+ break;
+ case FileTransfer::WRITE:
+ if (!transfer_active)
+ SERIAL_ECHOLNPGM("PFT:invalid");
+ else if (!file_write(buffer, length))
+ SERIAL_ECHOLNPGM("PFT:ioerror");
+ break;
+ case FileTransfer::ABORT:
+ transfer_abort();
+ SERIAL_ECHOLNPGM("PFT:success");
+ break;
+ default:
+ SERIAL_ECHOLNPGM("PTF:invalid");
+ break;
+ }
+ }
+
+ static const uint16_t VERSION_MAJOR = 0, VERSION_MINOR = 1, VERSION_PATCH = 0, TIMEOUT = 10000, IDLE_PERIOD = 1000;
+};
+
+class BinaryStream {
+public:
+ enum class Protocol : uint8_t { CONTROL, FILE_TRANSFER };
+
+ enum class ProtocolControl : uint8_t { SYNC = 1, CLOSE };
+
+ enum class StreamState : uint8_t { PACKET_RESET, PACKET_WAIT, PACKET_HEADER, PACKET_DATA, PACKET_FOOTER,
+ PACKET_PROCESS, PACKET_RESEND, PACKET_TIMEOUT, PACKET_ERROR };
+
+ struct Packet { // 10 byte protocol overhead, ascii with checksum and line number has a minimum of 7 increasing with line
+ struct [[gnu::packed]] Header {
+ static constexpr uint16_t HEADER_TOKEN = 0xB5AD;
+ uint16_t token; // packet start token
+ uint8_t sync; // stream sync, resend id and packet loss detection
+ uint8_t meta; // 4 bit protocol,
+ // 4 bit packet type
+ uint16_t size; // data length
+ uint16_t checksum; // header checksum
+
+ uint8_t protocol() { return (meta >> 4) & 0xF; }
+ uint8_t type() { return meta & 0xF; }
+ void reset() { token = 0; sync = 0; meta = 0; size = 0; checksum = 0; }
+ };
+
+ struct [[gnu::packed]] Footer {
+ uint16_t checksum; // full packet checksum
+ void reset() { checksum = 0; }
+ };
+
+ uint8_t header_data[sizeof(Header)],
+ footer_data[sizeof(Footer)];
+ uint32_t bytes_received;
+ uint16_t checksum, header_checksum;
+ millis_t timeout;
+ char* buffer;
+
+ Header& header() { return *reinterpret_cast(header_data); }
+ Footer& footer() { return *reinterpret_cast