|
|
@ -258,14 +258,15 @@ void gcode_line_error(PGM_P err, uint8_t port) { |
|
|
|
} |
|
|
|
|
|
|
|
static bool serial_data_available() { |
|
|
|
return (MYSERIAL0.available() ? true : |
|
|
|
return false |
|
|
|
|| MYSERIAL0.available() |
|
|
|
#if NUM_SERIAL > 1 |
|
|
|
MYSERIAL1.available() ? true : |
|
|
|
|| MYSERIAL1.available() |
|
|
|
#endif |
|
|
|
false); |
|
|
|
; |
|
|
|
} |
|
|
|
|
|
|
|
static int read_serial(const int index) { |
|
|
|
static int read_serial(const uint8_t index) { |
|
|
|
switch (index) { |
|
|
|
case 0: return MYSERIAL0.read(); |
|
|
|
#if NUM_SERIAL > 1 |
|
|
@ -275,6 +276,263 @@ static int read_serial(const int index) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#if ENABLED(FAST_FILE_TRANSFER) |
|
|
|
|
|
|
|
#if ENABLED(SDSUPPORT) |
|
|
|
#define CARD_CHAR_P(C) SERIAL_CHAR_P(card.transfer_port, C) |
|
|
|
#define CARD_ECHO_P(V) SERIAL_ECHO_P(card.transfer_port, V) |
|
|
|
#define CARD_ECHOLN_P(V) SERIAL_ECHOLN_P(card.transfer_port, V) |
|
|
|
#endif |
|
|
|
|
|
|
|
static bool serial_data_available(const uint8_t index) { |
|
|
|
switch (index) { |
|
|
|
case 0: return MYSERIAL0.available(); |
|
|
|
#if NUM_SERIAL > 1 |
|
|
|
case 1: return MYSERIAL1.available(); |
|
|
|
#endif |
|
|
|
default: return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class BinaryStream { |
|
|
|
public: |
|
|
|
enum class StreamState : uint8_t { |
|
|
|
STREAM_RESET, |
|
|
|
PACKET_RESET, |
|
|
|
STREAM_HEADER, |
|
|
|
PACKET_HEADER, |
|
|
|
PACKET_DATA, |
|
|
|
PACKET_VALIDATE, |
|
|
|
PACKET_RESEND, |
|
|
|
PACKET_FLUSHRX, |
|
|
|
PACKET_TIMEOUT, |
|
|
|
STREAM_COMPLETE, |
|
|
|
STREAM_FAILED, |
|
|
|
}; |
|
|
|
|
|
|
|
#pragma pack(push, 1) |
|
|
|
|
|
|
|
struct StreamHeader { |
|
|
|
uint16_t token; |
|
|
|
uint32_t filesize; |
|
|
|
}; |
|
|
|
union { |
|
|
|
uint8_t stream_header_bytes[sizeof(StreamHeader)]; |
|
|
|
StreamHeader stream_header; |
|
|
|
}; |
|
|
|
|
|
|
|
struct Packet { |
|
|
|
struct Header { |
|
|
|
uint32_t id; |
|
|
|
uint16_t size, checksum; |
|
|
|
}; |
|
|
|
union { |
|
|
|
uint8_t header_bytes[sizeof(Header)]; |
|
|
|
Header header; |
|
|
|
}; |
|
|
|
uint32_t bytes_received; |
|
|
|
uint16_t checksum; |
|
|
|
millis_t timeout; |
|
|
|
} packet{}; |
|
|
|
|
|
|
|
#pragma pack(pop) |
|
|
|
|
|
|
|
void packet_reset() { |
|
|
|
packet.header.id = 0; |
|
|
|
packet.header.size = 0; |
|
|
|
packet.header.checksum = 0; |
|
|
|
packet.bytes_received = 0; |
|
|
|
packet.checksum = 0x53A2; |
|
|
|
packet.timeout = millis() + STREAM_MAX_WAIT; |
|
|
|
} |
|
|
|
|
|
|
|
void stream_reset() { |
|
|
|
packets_received = 0; |
|
|
|
bytes_received = 0; |
|
|
|
packet_retries = 0; |
|
|
|
buffer_next_index = 0; |
|
|
|
stream_header.token = 0; |
|
|
|
stream_header.filesize = 0; |
|
|
|
} |
|
|
|
|
|
|
|
uint32_t checksum(uint32_t seed, uint8_t value) { |
|
|
|
return ((seed ^ value) ^ (seed << 8)) & 0xFFFF; |
|
|
|
} |
|
|
|
|
|
|
|
// read the next byte from the data stream keeping track of
|
|
|
|
// whether the stream times out from data starvation
|
|
|
|
// takes the data variable by reference in order to return status
|
|
|
|
bool stream_read(uint8_t& data) { |
|
|
|
if (ELAPSED(millis(), packet.timeout)) { |
|
|
|
stream_state = StreamState::PACKET_TIMEOUT; |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (!serial_data_available(card.transfer_port)) return false; |
|
|
|
data = read_serial(card.transfer_port); |
|
|
|
packet.timeout = millis() + STREAM_MAX_WAIT; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
template<const size_t buffer_size> |
|
|
|
void receive(char (&buffer)[buffer_size]) { |
|
|
|
uint8_t data = 0; |
|
|
|
millis_t tranfer_timeout = millis() + RX_TIMESLICE; |
|
|
|
while (PENDING(millis(), tranfer_timeout)) { |
|
|
|
switch (stream_state) { |
|
|
|
case StreamState::STREAM_RESET: |
|
|
|
stream_reset(); |
|
|
|
case StreamState::PACKET_RESET: |
|
|
|
packet_reset(); |
|
|
|
stream_state = StreamState::PACKET_HEADER; |
|
|
|
break; |
|
|
|
case StreamState::STREAM_HEADER: // we could also transfer the filename in this packet, rather than handling it in the gcode
|
|
|
|
for (size_t i = 0; i < sizeof(stream_header); ++i) { |
|
|
|
stream_header_bytes[i] = buffer[i]; |
|
|
|
} |
|
|
|
if (stream_header.token == 0x1234) { |
|
|
|
stream_state = StreamState::PACKET_RESET; |
|
|
|
bytes_received = 0; |
|
|
|
time_stream_start = millis(); |
|
|
|
CARD_ECHO_P("echo: Datastream initialized ("); |
|
|
|
CARD_ECHO_P(stream_header.filesize); |
|
|
|
CARD_ECHOLN_P("Bytes expected)"); |
|
|
|
CARD_ECHO_P("so"); // confirm active stream and the maximum block size supported
|
|
|
|
CARD_CHAR_P(static_cast<uint8_t>(buffer_size & 0xFF)); |
|
|
|
CARD_CHAR_P(static_cast<uint8_t>((buffer_size >> 8) & 0xFF)); |
|
|
|
CARD_CHAR_P('\n'); |
|
|
|
} |
|
|
|
else { |
|
|
|
CARD_ECHOLN_P("echo: Datastream initialization error (invalid token)"); |
|
|
|
stream_state = StreamState::STREAM_FAILED; |
|
|
|
} |
|
|
|
buffer_next_index = 0; |
|
|
|
break; |
|
|
|
case StreamState::PACKET_HEADER: |
|
|
|
if (!stream_read(data)) break; |
|
|
|
|
|
|
|
packet.header_bytes[packet.bytes_received++] = data; |
|
|
|
if (packet.bytes_received == sizeof(Packet::Header)) { |
|
|
|
if (packet.header.id == packets_received) { |
|
|
|
buffer_next_index = 0; |
|
|
|
packet.bytes_received = 0; |
|
|
|
stream_state = StreamState::PACKET_DATA; |
|
|
|
} |
|
|
|
else { |
|
|
|
CARD_ECHO_P("echo: Datastream packet out of order"); |
|
|
|
stream_state = StreamState::PACKET_FLUSHRX; |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
case StreamState::PACKET_DATA: |
|
|
|
if (!stream_read(data)) break; |
|
|
|
|
|
|
|
if (buffer_next_index < buffer_size) { |
|
|
|
buffer[buffer_next_index] = data; |
|
|
|
} |
|
|
|
else { |
|
|
|
CARD_ECHO_P("echo: Datastream packet data buffer overrun"); |
|
|
|
stream_state = StreamState::STREAM_FAILED; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
packet.checksum = checksum(packet.checksum, data); |
|
|
|
packet.bytes_received ++; |
|
|
|
buffer_next_index ++; |
|
|
|
|
|
|
|
if (packet.bytes_received == packet.header.size) { |
|
|
|
stream_state = StreamState::PACKET_VALIDATE; |
|
|
|
} |
|
|
|
break; |
|
|
|
case StreamState::PACKET_VALIDATE: |
|
|
|
if (packet.header.checksum == packet.checksum) { |
|
|
|
packet_retries = 0; |
|
|
|
packets_received ++; |
|
|
|
bytes_received += packet.header.size; |
|
|
|
|
|
|
|
if (packet.header.id == 0) { // id 0 is always the stream descriptor
|
|
|
|
stream_state = StreamState::STREAM_HEADER; // defer packet confirmation to STREAM_HEADER state
|
|
|
|
} |
|
|
|
else { |
|
|
|
if (bytes_received < stream_header.filesize) { |
|
|
|
stream_state = StreamState::PACKET_RESET; // reset and receive next packet
|
|
|
|
CARD_ECHOLN_P("ok"); // transmit confirm packet received and valid token
|
|
|
|
} |
|
|
|
else { |
|
|
|
stream_state = StreamState::STREAM_COMPLETE; // no more data required
|
|
|
|
} |
|
|
|
if (card.write(buffer, buffer_next_index) < 0) { |
|
|
|
stream_state = StreamState::STREAM_FAILED; |
|
|
|
CARD_ECHO_P("echo: IO ERROR"); |
|
|
|
break; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
CARD_ECHO_P("echo: Block("); |
|
|
|
CARD_ECHO_P(packet.header.id); |
|
|
|
CARD_ECHOLN_P(") Corrupt"); |
|
|
|
stream_state = StreamState::PACKET_FLUSHRX; |
|
|
|
} |
|
|
|
break; |
|
|
|
case StreamState::PACKET_RESEND: |
|
|
|
if (packet_retries < MAX_RETRIES) { |
|
|
|
packet_retries ++; |
|
|
|
stream_state = StreamState::PACKET_RESET; |
|
|
|
CARD_ECHO_P("echo: Resend request "); |
|
|
|
CARD_ECHOLN_P(packet_retries); |
|
|
|
CARD_ECHOLN_P("rs"); // transmit resend packet token
|
|
|
|
} |
|
|
|
else { |
|
|
|
stream_state = StreamState::STREAM_FAILED; |
|
|
|
} |
|
|
|
break; |
|
|
|
case StreamState::PACKET_FLUSHRX: |
|
|
|
if (ELAPSED(millis(), packet.timeout)) { |
|
|
|
stream_state = StreamState::PACKET_RESEND; |
|
|
|
break; |
|
|
|
} |
|
|
|
if (!serial_data_available(card.transfer_port)) break; |
|
|
|
read_serial(card.transfer_port); // throw away data
|
|
|
|
packet.timeout = millis() + STREAM_MAX_WAIT; |
|
|
|
break; |
|
|
|
case StreamState::PACKET_TIMEOUT: |
|
|
|
CARD_ECHOLN_P("echo: Datastream timeout"); |
|
|
|
stream_state = StreamState::PACKET_RESEND; |
|
|
|
break; |
|
|
|
case StreamState::STREAM_COMPLETE: |
|
|
|
stream_state = StreamState::STREAM_RESET; |
|
|
|
card.binary_mode = false; |
|
|
|
card.closefile(); |
|
|
|
CARD_ECHO_P("echo: "); |
|
|
|
CARD_ECHO_P(card.filename); |
|
|
|
CARD_ECHO_P(" transfer completed @ "); |
|
|
|
CARD_ECHO_P(((bytes_received / (millis() - time_stream_start) * 1000) / 1024 )); |
|
|
|
CARD_ECHOLN_P("KiB/s"); |
|
|
|
CARD_ECHOLN_P("sc"); // transmit stream complete token
|
|
|
|
return; |
|
|
|
case StreamState::STREAM_FAILED: |
|
|
|
stream_state = StreamState::STREAM_RESET; |
|
|
|
card.binary_mode = false; |
|
|
|
card.closefile(); |
|
|
|
card.removeFile(card.filename); |
|
|
|
CARD_ECHOLN_P("echo: File transfer failed"); |
|
|
|
CARD_ECHOLN_P("sf"); // transmit stream failed token
|
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static const uint16_t STREAM_MAX_WAIT = 500, RX_TIMESLICE = 20, MAX_RETRIES = 3; |
|
|
|
uint8_t packet_retries; |
|
|
|
uint16_t buffer_next_index; |
|
|
|
uint32_t packets_received, bytes_received; |
|
|
|
millis_t time_stream_start; |
|
|
|
StreamState stream_state = StreamState::STREAM_RESET; |
|
|
|
|
|
|
|
} binaryStream{}; |
|
|
|
|
|
|
|
#endif // FAST_FILE_TRANSFER
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all commands waiting on the serial port and queue them. |
|
|
|
* Exit when the buffer is full or when no more characters are |
|
|
@ -288,6 +546,18 @@ inline void get_serial_commands() { |
|
|
|
#endif |
|
|
|
; |
|
|
|
|
|
|
|
#if ENABLED(FAST_FILE_TRANSFER) |
|
|
|
if (card.saving && card.binary_mode) { |
|
|
|
/**
|
|
|
|
* For binary stream file transfer, use serial_line_buffer as the working |
|
|
|
* receive buffer (which limits the packet size to MAX_CMD_SIZE). |
|
|
|
* The receive buffer also limits the packet size for reliable transmission. |
|
|
|
*/ |
|
|
|
binaryStream.receive(serial_line_buffer[card.transfer_port]); |
|
|
|
return; |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
// If the command buffer is empty for too long,
|
|
|
|
// send "wait" to indicate Marlin is still waiting.
|
|
|
|
#if NO_TIMEOUTS > 0 |
|
|
|