Newer
Older
/**
* 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 <http://www.gnu.org/licenses/>.
*
*/
/**
* queue.cpp - The G-code command queue
*/
#include "queue.h"
#include "gcode.h"
#include "../lcd/ultralcd.h"
#include "../sd/cardreader.h"
#include "../module/planner.h"
#include "../module/temperature.h"
#include "../MarlinCore.h"
#if ENABLED(PRINTER_EVENT_LEDS)
#include "../feature/leds/printer_event_leds.h"
#if ENABLED(BINARY_FILE_TRANSFER)
#include "../feature/binary_protocol.h"
#endif
/**
* GCode line number handling. Hosts may opt to include line numbers when
* sending commands to Marlin, and lines will be checked for sequentiality.
* M110 N<int> sets the current line number.
*/
long GCodeQueue::last_N[NUM_SERIAL];
/**
* GCode Command Queue
* A simple ring buffer of BUFSIZE command strings.
*
* Commands are copied into this buffer by the command injectors
* (immediate, serial, sd card) and they are processed sequentially by
* the main loop. The gcode.process_next_command method parses the next
* command and hands off execution to individual handler functions.
*/
uint8_t GCodeQueue::length = 0, // Count of commands in the queue
GCodeQueue::index_r = 0, // Ring buffer read position
GCodeQueue::index_w = 0; // Ring buffer write position
char GCodeQueue::command_buffer[BUFSIZE][MAX_CMD_SIZE];
/*
* The port that the command was received on
*/
int16_t GCodeQueue::port[BUFSIZE];
/**
* Serial command injection
*/
// Number of characters read in the current line of serial input
bool send_ok[BUFSIZE];
/**
* Next Injected PROGMEM Command pointer. (nullptr == empty)
* Internal commands are enqueued ahead of serial / SD commands.
PGM_P GCodeQueue::injected_commands_P; // = nullptr
/**
* Injected SRAM Commands
*/
char GCodeQueue::injected_commands[64]; // = { 0 }
GCodeQueue::GCodeQueue() {
// Send "ok" after commands by default
LOOP_L_N(i, COUNT(send_ok)) send_ok[i] = true;
/**
* Check whether there are any commands yet to be executed
*/
bool GCodeQueue::has_commands_queued() {
return queue.length || injected_commands_P || injected_commands[0];
/**
* Clear the Marlin command queue
*/
void GCodeQueue::clear() {
index_r = index_w = length = 0;
}
/**
* Once a new command is in the ring buffer, call this to commit it
*/
void GCodeQueue::_commit_command(bool say_ok
send_ok[index_w] = say_ok;
TERN_(HAS_MULTI_SERIAL, port[index_w] = p);
TERN_(POWER_LOSS_RECOVERY, recovery.commit_sdpos(index_w));
if (++index_w >= BUFSIZE) index_w = 0;
length++;
}
/**
* Copy a command from RAM into the main command buffer.
* Return true if the command was successfully added.
* Return false for a full buffer, or if the 'command' is a comment.
*/
bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
if (*cmd == ';' || length >= BUFSIZE) return false;
return true;
}
#define ISEOL(C) ((C) == '\n' || (C) == '\r')
/**
* Enqueue with Serial Echo
* Return true if the command was consumed
bool GCodeQueue::enqueue_one(const char* cmd) {
//SERIAL_ECHOPGM("enqueue_one(\"");
if (*cmd == 0 || ISEOL(*cmd)) return true;
SERIAL_ECHO_MSG(STR_ENQUEUEING, cmd, "\"");
return true;
}
return false;
}
* Process the next "immediate" command from PROGMEM.
* Return 'true' if any commands were processed.
bool GCodeQueue::process_injected_command_P() {
if (injected_commands_P == nullptr) return false;
char c;
size_t i = 0;
while ((c = pgm_read_byte(&injected_commands_P[i])) && c != '\n') i++;
// Extract current command and move pointer to next command
char cmd[i + 1];
memcpy_P(cmd, injected_commands_P, i);
cmd[i] = '\0';
injected_commands_P = c ? injected_commands_P + i + 1 : nullptr;
// Execute command if non-blank
if (i) {
parser.parse(cmd);
gcode.process_parsed_command();
}
}
/**
* Process the next "immediate" command from SRAM.
* Return 'true' if any commands were processed.
bool GCodeQueue::process_injected_command() {
if (injected_commands[0] == '\0') return false;
char c;
size_t i = 0;
while ((c = injected_commands[i]) && c != '\n') i++;
// Execute a non-blank command
if (i) {
injected_commands[i] = '\0';
parser.parse(injected_commands);
gcode.process_parsed_command();
}
// Copy the next command into place
for (
uint8_t d = 0, s = i + !!c; // dst, src
(injected_commands[d] = injected_commands[s]); // copy, exit if 0
d++, s++ // next dst, src
);
/**
* Enqueue and return only when commands are actually enqueued.
* Never call this from a G-code handler!
*/
void GCodeQueue::enqueue_one_now(const char* cmd) { while (!enqueue_one(cmd)) idle(); }
/**
* Attempt to enqueue a single G-code command
* and return 'true' if successful.
*/
bool GCodeQueue::enqueue_one_P(PGM_P const pgcode) {
size_t i = 0;
PGM_P p = pgcode;
char c;
while ((c = pgm_read_byte(&p[i])) && c != '\n') i++;
char cmd[i + 1];
memcpy_P(cmd, p, i);
cmd[i] = '\0';
return _enqueue(cmd);
}
/**
* Enqueue from program memory and return only when commands are actually enqueued
* Never call this from a G-code handler!
*/
void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
size_t i = 0;
PGM_P p = pgcode;
for (;;) {
char c;
while ((c = pgm_read_byte(&p[i])) && c != '\n') i++;
char cmd[i + 1];
memcpy_P(cmd, p, i);
cmd[i] = '\0';
enqueue_one_now(cmd);
if (!c) break;
p += i + 1;
/**
* Send an "ok" message to the host, indicating
* that a command was successfully processed.
*
* If ADVANCED_OK is enabled also include:
* N<int> Line number of the command, if any
* P<int> Planner space remaining
* B<int> Block queue space remaining
*/
void GCodeQueue::ok_to_send() {
const int16_t pn = command_port();
PORT_REDIRECT(pn); // Reply to the serial port that sent the command
if (!send_ok[index_r]) return;
#if ENABLED(ADVANCED_OK)
if (*p == 'N') {
SERIAL_ECHO(' ');
SERIAL_ECHO(*p++);
while (NUMERIC_SIGNED(*p))
SERIAL_ECHOPAIR_P(SP_P_STR, int(planner.moves_free()));
SERIAL_ECHOPAIR(" B", int(BUFSIZE - length));
}
/**
* Send a "Resend: nnn" message to the host to
* indicate that a command needs to be re-sent.
*/
void GCodeQueue::flush_and_request_resend() {
const int16_t pn = command_port();
if (pn < 0) return;
PORT_REDIRECT(pn); // Reply to the serial port that sent the command
SERIAL_ECHOPGM(STR_RESEND);
inline bool serial_data_available() {
return MYSERIAL0.available() || TERN0(HAS_MULTI_SERIAL, MYSERIAL1.available());
inline int read_serial(const uint8_t index) {
case 1: return MYSERIAL1.read();
#endif
default: return -1;
}
void GCodeQueue::gcode_line_error(PGM_P const err, const int8_t pn) {
PORT_REDIRECT(pn); // Reply to the serial port that sent the command
SERIAL_ERROR_START();
serialprintPGM(err);
SERIAL_ECHOLN(last_N[pn]);
while (read_serial(pn) != -1); // Clear out the RX buffer
flush_and_request_resend();
serial_count[pn] = 0;
FORCE_INLINE bool is_M29(const char * const cmd) { // matches "M29" & "M29 ", but not "M290", etc
const char * const m29 = strstr_P(cmd, PSTR("M29"));
return m29 && !NUMERIC(m29[3]);
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#define PS_NORMAL 0
#define PS_EOL 1
#define PS_QUOTED 2
#define PS_PAREN 3
#define PS_ESC 4
inline void process_stream_char(const char c, uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
if (sis == PS_EOL) return; // EOL comment or overflow
#if ENABLED(PAREN_COMMENTS)
else if (sis == PS_PAREN) { // Inline comment
if (c == ')') sis = PS_NORMAL;
return;
}
#endif
else if (sis >= PS_ESC) // End escaped char
sis -= PS_ESC;
else if (c == '\\') { // Start escaped char
sis += PS_ESC;
if (sis == PS_ESC) return; // Keep if quoting
}
#if ENABLED(GCODE_QUOTED_STRINGS)
else if (sis == PS_QUOTED) {
if (c == '"') sis = PS_NORMAL; // End quoted string
}
else if (c == '"') // Start quoted string
sis = PS_QUOTED;
#endif
else if (c == ';') { // Start end-of-line comment
sis = PS_EOL;
return;
}
#if ENABLED(PAREN_COMMENTS)
else if (c == '(') { // Start inline comment
sis = PS_PAREN;
return;
}
#endif
// Backspace erases previous characters
if (c == 0x08) {
if (ind) buff[--ind] = '\0';
}
else {
buff[ind++] = c;
if (ind >= MAX_CMD_SIZE - 1)
sis = PS_EOL; // Skip the rest on overflow
}
/**
* Handle a line being completed. For an empty line
* keep sensor readings going and watchdog alive.
*/
inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
sis = PS_NORMAL;
buff[ind] = 0;
if (ind) { ind = 0; return false; }
thermalManager.manage_heater();
return true;
/**
* Get all commands waiting on the serial port and queue them.
* Exit when the buffer is full or when no more characters are
* left on the serial port.
*/
void GCodeQueue::get_serial_commands() {
static char serial_line_buffer[NUM_SERIAL][MAX_CMD_SIZE];
static uint8_t serial_input_state[NUM_SERIAL] = { PS_NORMAL };
#if ENABLED(BINARY_FILE_TRANSFER)
/**
* 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[card.transfer_port_index].receive(serial_line_buffer[card.transfer_port_index]);
return;
}
#endif
// If the command buffer is empty for too long,
// send "wait" to indicate Marlin is still waiting.
static millis_t last_command_time = 0;
const millis_t ms = millis();
if (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
SERIAL_ECHOLNPGM(STR_WAIT);
last_command_time = ms;
}
#endif
/**
* Loop while serial characters are incoming and the queue is not full
*/
while (length < BUFSIZE && serial_data_available()) {
const int c = read_serial(i);
if (c < 0) continue;
const char serial_char = c;
// Reset our state, continue if the line was empty
if (process_line_done(serial_input_state[i], serial_line_buffer[i], serial_count[i]))
continue;
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 (M110) {
char* n2pos = strchr(command + 4, 'N');
if (n2pos) npos = n2pos;
}
const long gcode_N = strtol(npos + 1, nullptr, 10);
if (gcode_N != last_N[i] + 1 && !M110)
return gcode_line_error(PSTR(STR_ERR_LINE_NO), i);
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), i);
return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
last_N[i] = gcode_N;
// Pronterface "M29" and "M29 " has no line number
return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), i);
//
// 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:
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;
}
}
}
TERN_(HAS_LCD_MENU, wait_for_user = false);
if (strcmp_P(command, PSTR("M112")) == 0) kill(M112_KILL_STR, nullptr, true);
if (strcmp_P(command, PSTR("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
else
process_stream_char(serial_char, serial_input_state[i], serial_line_buffer[i], serial_count[i]);
} // queue has space, serial has data
}
#if ENABLED(SDSUPPORT)
/**
* Get lines from the SD Card until the command buffer is full
* or until the end of the file is reached. Because this method
* always receives complete command-lines, they can go directly
* into the main command queue.
inline void GCodeQueue::get_sdcard_commands() {
static uint8_t sd_input_state = PS_NORMAL;
int sd_count = 0;
bool card_eof = card.eof();
while (length < BUFSIZE && !card_eof) {
const int16_t n = card.get();
card_eof = card.eof();
if (n < 0 && !card_eof) { SERIAL_ERROR_MSG(STR_SD_ERR_READ); continue; }
const char sd_char = (char)n;
const bool is_eol = ISEOL(sd_char);
// Reset stream state, terminate the buffer, and commit a non-empty command
if (!is_eol && sd_count) ++sd_count; // End of file with no newline
if (!process_line_done(sd_input_state, command_buffer[index_w], sd_count)) {
#if ENABLED(POWER_LOSS_RECOVERY)
recovery.cmd_sdpos = card.getIndex(); // Prime for the NEXT _commit_command
if (card_eof) card.fileHasFinished(); // Handle end of file reached
else
process_stream_char(sd_char, sd_input_state, command_buffer[index_w], sd_count);
}
}
#endif // SDSUPPORT
/**
* Add to the circular command queue the next command from:
* - The command-injection queues (injected_commands_P, injected_commands)
* - The active serial input (usually USB)
* - The SD card file being actively printed
*/
void GCodeQueue::get_available_commands() {
get_serial_commands();
TERN_(SDSUPPORT, get_sdcard_commands());
}
/**
* Get the next command in the queue, optionally log it to SD, then dispatch it
*/
void GCodeQueue::advance() {
// Process immediate commands
if (process_injected_command_P() || process_injected_command()) return;
// Return if the G-code buffer is empty
if (!length) return;
#if ENABLED(SDSUPPORT)
// M29 closes the file
card.closefile();
SERIAL_ECHOLNPGM(STR_FILE_SAVED);
#if ENABLED(SERIAL_STATS_DROPPED_RX)
Eduardo José Tagle
committed
SERIAL_ECHOLNPAIR("Dropped bytes: ", MYSERIAL0.dropped());
#if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
Eduardo José Tagle
committed
SERIAL_ECHOLNPAIR("Max RX Queue Size: ", MYSERIAL0.rxMaxEnqueued());
#endif // !defined(__AVR__) || !defined(USBCON)
ok_to_send();
}
else {
// Write the string from the read buffer to SD
card.write_command(command);
gcode.process_next_command(); // The card is saving because it's logging
else
ok_to_send();
}
}
gcode.process_next_command();
#else
gcode.process_next_command();
#endif // SDSUPPORT
// The queue may be reset by a command handler or by code invoked by idle() within a handler
--length;
if (++index_r >= BUFSIZE) index_r = 0;