/**
* 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