From 56b9991a16edc47ed0d4dd1f1508ad15844fedbe Mon Sep 17 00:00:00 2001 From: Rob Giseburt Date: Wed, 18 Jan 2017 12:44:12 -0600 Subject: [PATCH] More Marlin compatibility work --- g2core/config.h | 3 +- g2core/controller.cpp | 30 ++- g2core/gcode_parser.cpp | 32 +++ g2core/marlin_compatibility.cpp | 200 ++++++++++++++++++ g2core/marlin_compatibility.h | 6 + g2core/report.cpp | 3 +- g2core/settings/settings_Printrbot_Play.h | 2 + g2core/settings/settings_Printrbot_Plus.h | 1 + .../settings/settings_Printrbot_Simple_1403.h | 2 + .../settings/settings_Printrbot_Simple_1608.h | 1 + g2core/xio.cpp | 123 +++++++++++ g2core/xio.h | 6 +- 12 files changed, 405 insertions(+), 4 deletions(-) diff --git a/g2core/config.h b/g2core/config.h index 7f65852a..106dc81b 100644 --- a/g2core/config.h +++ b/g2core/config.h @@ -195,7 +195,8 @@ typedef uint16_t index_t; // use this if there are > 255 indexed o typedef enum { TEXT_MODE = 0, // sticky text mode JSON_MODE, // sticky JSON mode - AUTO_MODE // auto-configure communications mode + AUTO_MODE, // auto-configure communications mode + MARLIN_COMM_MODE, // sticky marlin-compatibility mode (if compiled in) } commMode; typedef enum { diff --git a/g2core/controller.cpp b/g2core/controller.cpp index 8ba769ef..505fec4c 100755 --- a/g2core/controller.cpp +++ b/g2core/controller.cpp @@ -48,6 +48,10 @@ #include "MotatePower.h" +#if MARLIN_COMPAT_ENABLED == true +#include "marlin_compatibility.h" +#endif + /*********************************************************************************** **** STRUCTURE ALLOCATIONS ********************************************************* ***********************************************************************************/ @@ -221,7 +225,15 @@ static void _dispatch_kernel(const devflags_t flags) // It's possible to let some stuff through, but that's not happening yet. return; } - + +#if MARLIN_COMPAT_ENABLED == true + if (marlin_handle_fake_stk500(cs.bufp)) { + cs.comm_mode = MARLIN_COMM_MODE; + js.json_mode = MARLIN_COMM_MODE; + return; + } +#endif + while ((*cs.bufp == SPC) || (*cs.bufp == TAB)) { // position past any leading whitespace cs.bufp++; } @@ -262,6 +274,12 @@ static void _dispatch_kernel(const devflags_t flags) cs.comm_request_mode = TEXT_MODE; // mode of this command text_response(gcode_parser(cs.bufp), cs.saved_buf); } +#endif +#if MARLIN_COMPAT_ENABLED == true + else if (js.json_mode == MARLIN_COMM_MODE) { // handle marlin-specific protocol gcode + cs.comm_request_mode = MARLIN_COMM_MODE; // mode of this command + marlin_response(gcode_parser(cs.bufp), cs.saved_buf); + } #endif else { // anything else is interpreted as Gcode cs.comm_request_mode = JSON_MODE; // mode of this command @@ -272,6 +290,16 @@ static void _dispatch_kernel(const devflags_t flags) nv_copy_string(nv, cs.bufp); // copy the Gcode line nv->valuetype = TYPE_STRING; status = gcode_parser(cs.bufp); +#if MARLIN_COMPAT_ENABLED == true + if (js.json_mode == MARLIN_COMM_MODE) { // in case a marlin-specific M-code was found + cs.comm_request_mode = MARLIN_COMM_MODE; // mode of this command + // We ae switching to marlin_comm_mode, kill status reports and queue reports + sr.status_report_verbosity = SR_OFF; + qr.queue_report_verbosity = QR_OFF; + marlin_response(gcode_parser(cs.bufp), cs.saved_buf); + return; + } +#endif nv_print_list(status, TEXT_NO_PRINT, JSON_RESPONSE_FORMAT); sr_request_status_report(SR_REQUEST_TIMED); // generate incremental status report to show any changes } diff --git a/g2core/gcode_parser.cpp b/g2core/gcode_parser.cpp index cc2ff40b..3a89d071 100644 --- a/g2core/gcode_parser.cpp +++ b/g2core/gcode_parser.cpp @@ -30,6 +30,7 @@ #if MARLIN_COMPAT_ENABLED == true #include "marlin_compatibility.h" +#include "json_parser.h" // so we can switch js.comm_mode on a marlin M-code #endif // Structures used by Gcode parser @@ -71,6 +72,11 @@ typedef struct GCodeInputValue { // Gcode inputs - meaning depends on context bool mfo_control; // M50 feedrate override control bool mto_control; // M50.1 traverse override control bool sso_control; // M51 spindle speed override control + +#if MARLIN_COMPAT_ENABLED == true + bool marlin_temp_requested; // M105 temperature report request (Marlin-only) + bool marlin_position_requested; // M114 position report request (Marlin-only) +#endif } GCodeValue_t; typedef struct GCodeFlags { // Gcode input flags @@ -110,6 +116,11 @@ typedef struct GCodeFlags { // Gcode input flags bool mfo_control; bool mto_control; bool sso_control; + +#if MARLIN_COMPAT_ENABLED == true + bool marlin_temp_requested; + bool marlin_position_requested; +#endif } GCodeFlag_t; typedef struct GCodeParser { @@ -684,6 +695,13 @@ stat_t _parse_gcode_block(char *buf, char *active_comment) case 51: SET_MODAL (MODAL_GROUP_M9, sso_control, true); case 100: SET_NON_MODAL (next_action, NEXT_ACTION_JSON_COMMAND_SYNC); case 101: SET_NON_MODAL (next_action, NEXT_ACTION_JSON_WAIT); + +#if MARLIN_COMPAT_ENABLED == true +#warning MARLIN_COMPAT_ENABLED + case 105: SET_NON_MODAL (marlin_temp_requested, true); + case 114: SET_NON_MODAL (marlin_position_requested, true); +#endif // MARLIN_COMPAT_ENABLED + default: status = STAT_MCODE_COMMAND_UNSUPPORTED; } break; @@ -865,6 +883,20 @@ stat_t _execute_gcode_block(char *active_comment) } } +#if MARLIN_COMPAT_ENABLED == true + // Handle Marlin specifics + if (gf.marlin_temp_requested) { // spindle speed override + js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch + ritorno(marlin_request_temperature_report()); + } + if (gf.marlin_position_requested) { // spindle speed override + js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch + ritorno(marlin_request_position_report()); + } +#endif // MARLIN_COMPAT_ENABLED + + + // do the program stops and ends : M0, M1, M2, M30, M60 if (gf.program_flow == true) { if (gv.program_flow == PROGRAM_STOP) { diff --git a/g2core/marlin_compatibility.cpp b/g2core/marlin_compatibility.cpp index 54c5e022..156fdf1f 100644 --- a/g2core/marlin_compatibility.cpp +++ b/g2core/marlin_compatibility.cpp @@ -28,8 +28,28 @@ #include "canonical_machine.h" #include "util.h" #include "xio.h" // for char definitions +#include "json_parser.h" // Structures used +enum STK500 { + // Success + STATUS_CMD_OK = 0x00, + + // Warnings + STATUS_CMD_TOUT = 0x80, + STATUS_RDY_BSY_TOUT = 0x81, + STATUS_SET_PARAM_MISSING = 0x82, + + // Errors + STATUS_CMD_FAILED = 0xC0, + STATUS_CKSUM_ERROR = 0xC1, + STATUS_CMD_UNKNOWN = 0xC9, +}; + +// Local variables + +bool temperature_requested = false; +bool position_requested = false; // local helper functions and macros @@ -37,6 +57,7 @@ * marlin_verify_checksum() - check to see if we have a line number (cheaply) and a valid checksum */ + stat_t marlin_verify_checksum(char *str) { if (*str != 'N') { return STAT_OK; } // we only check if we have a line number @@ -56,6 +77,58 @@ stat_t marlin_verify_checksum(char *str) return STAT_OK; } +void _marlin_fake_stk500_response(char *resp, uint16_t length) +{ + char *str = resp; + + str[2] = (length >> 8) & 0xFF; + str[3] = (length) & 0xFF; + + uint8_t crc = 0; + + for (uint16_t i = length + 5; i>0; i--) { + crc ^= *resp++; + } + + *resp = crc; + + xio_write(str, length + 6); +} + +/* + * marlin_handle_fake_stk500() - returns true if it handled something (IOW, don't futher process the line) + */ +bool marlin_handle_fake_stk500(char *str) +{ + char *resp = str; + if (*str != 0x1B) { return false; } + + // we handle only a handful of messages ... poorly + // for example: this is where we should validate the checksum, but we are going to not for now. + + str += 1 + 1 + 2 + 1; // 1 for 0x1B, 1 for sequence, 2 for length, 1 for 0x0E + + char c = *str++; + + if ((c == 0x01) || // CMD_SIGN_ON + (c == 0x10) || // CMD_ENTER_PROGMODE_ISP + (c == 0x11) // CMD_LEAVE_PROGMODE_ISP + ) + { + *str = STATUS_CMD_OK; + _marlin_fake_stk500_response(resp, 2); + + if (c == 0x11) { // CMD_LEAVE_PROGMODE_ISP + xio_exit_fake_bootloader(); + } + + return true; + } + + *str = STATUS_CMD_UNKNOWN; + _marlin_fake_stk500_response(resp, 2); + return true; +} /*********************************************************************************** @@ -63,6 +136,133 @@ stat_t marlin_verify_checksum(char *str) * Functions to get and set variables from the cfgArray table ***********************************************************************************/ +stat_t marlin_request_temperature_report() // M105 +{ + uint8_t tool = cm.gm.tool_select; + if (tool > 1) { + return STAT_INPUT_VALUE_RANGE_ERROR; + } + + temperature_requested = true; + return STAT_OK; +} + +stat_t marlin_request_position_report() // M114 +{ + position_requested = true; + return STAT_OK; +} + +nvObj_t *_get_spcific_nv(const char *key) { + nvObj_t *nv = nv_reset_nv_list(); // returns first object in the body + + strncpy(nv->token, key, TOKEN_LEN); + + // validate and post-process the token + if ((nv->index = nv_get_index((const char *)"", nv->token)) == NO_MATCH) { // get index or fail it + // since we JUST provided the keys, this should never happen + return nullptr; + } + strcpy(nv->group, cfgArray[nv->index].group); // capture the group string if there is one + + nv_get(nv); + + return nv; +} + +void _report_temperatures(char *(&str)) { + // Tool 0 is extruder 1 + uint8_t tool = cm.gm.tool_select; + + nvObj_t *nv = nullptr; + + if (tool == 0) { + nv = _get_spcific_nv("he1t"); + } else if (tool == 1) { + nv = _get_spcific_nv("he2t"); + } else { + return; // we have no way of reporting errors here, ATM + } + if (!nv) { return; } + str += sprintf(str, " T:%.2f", (float)nv->value); + + if (tool == 0) { + nv = _get_spcific_nv("he1st"); + } else if (tool == 1) { + nv = _get_spcific_nv("he2st"); + } else { + return; // we have no way of reporting errors here, ATM + } + if (!nv) { return; } + str += sprintf(str, " /%.2f", (float)nv->value); + + nv = _get_spcific_nv("he3t"); + if (!nv) { return; } + str += sprintf(str, " B:%.2f", (float)nv->value); + + nv = _get_spcific_nv("he3st"); + if (!nv) { return; } + str += sprintf(str, " /%.2f", (float)nv->value); +} + +void _report_position(char *(&str)) { + // + // Tool 0 is extruder 1 + uint8_t tool = cm.gm.tool_select; + + str += sprintf(str, " X:%.2f", cm_get_work_position(ACTIVE_MODEL, 0)); + str += sprintf(str, " Y:%.2f", cm_get_work_position(ACTIVE_MODEL, 1)); + str += sprintf(str, " Z:%.2f", cm_get_work_position(ACTIVE_MODEL, 2)); + + if (tool == 0) { + str += sprintf(str, " E:%.2f", cm_get_work_position(ACTIVE_MODEL, 3)); + } else if (tool == 1) { + str += sprintf(str, " E:%.2f", cm_get_work_position(ACTIVE_MODEL, 4)); + } +} + +/* + * marlin_response() - marlin mirror if text_response() + */ +void marlin_response(const stat_t status, char *buf) +{ + char buffer[128]; + char *str = buffer; + + if ((status == STAT_OK) || (status == STAT_EAGAIN) || (status == STAT_NOOP)) { + strncpy(str, "ok", 2); + str += 2; + + if (temperature_requested) { + _report_temperatures(str); + } + + if (position_requested) { + _report_position(str); + } + +// nvObj_t *nv = nv_body+1; +// +// if (nv_get_type(nv) == NV_TYPE_MESSAGE) { +// p += sprintf(p, (char *)*nv->stringp); +// } +// sprintf(p, "\n"); + + } else { +// str += sprintf(p, "echo:M%d %s", (int)status, get_status_message(status), buf); + str += sprintf(str, "Error:[%d] %s", (int)status, get_status_message(status)); + } + + *str++ = '\n'; + *str++ = 0; + + // reset requests + temperature_requested = false; + position_requested = false; + + xio_writeline(buffer); +} + //stat_t gc_get_gc(nvObj_t *nv) //{ // ritorno(nv_copy_string(nv, cs.saved_buf)); diff --git a/g2core/marlin_compatibility.h b/g2core/marlin_compatibility.h index 19070a03..b062a7b4 100644 --- a/g2core/marlin_compatibility.h +++ b/g2core/marlin_compatibility.h @@ -29,5 +29,11 @@ */ stat_t marlin_verify_checksum(char *str); +bool marlin_handle_fake_stk500(char *str); + +stat_t marlin_request_temperature_report(); // M105 +stat_t marlin_request_position_report(); // M114 + +void marlin_response(const stat_t status, char *buf); #endif // End of include guard: MARLIN_COMPAT_H_ONCE diff --git a/g2core/report.cpp b/g2core/report.cpp index 829545e6..02748634 100644 --- a/g2core/report.cpp +++ b/g2core/report.cpp @@ -111,7 +111,8 @@ void rpt_print_loading_configs_message(void) void rpt_print_system_ready_message(void) { - _startup_helper(STAT_OK, "SYSTEM READY"); +#warning DEAL WITH STARTUP MESSAGE + //_startup_helper(STAT_OK, "SYSTEM READY"); if (cs.comm_mode == TEXT_MODE) { text_response(STAT_OK, (char *)"");}// prompt } diff --git a/g2core/settings/settings_Printrbot_Play.h b/g2core/settings/settings_Printrbot_Play.h index 317f6da6..121c9520 100644 --- a/g2core/settings/settings_Printrbot_Play.h +++ b/g2core/settings/settings_Printrbot_Play.h @@ -57,8 +57,10 @@ // Communications and reporting settings +#define MARLIN_COMPAT_ENABLED true // enable marlin compatibility mode #define COMM_MODE JSON_MODE // one of: TEXT_MODE, JSON_MODE #define XIO_ENABLE_FLOW_CONTROL FLOW_CONTROL_RTS // FLOW_CONTROL_OFF, FLOW_CONTROL_RTS +#define XIO_UART_MUTES_WHEN_USB_CONNECTED 1 // Mute the UART when USB connects #define TEXT_VERBOSITY TV_VERBOSE // one of: TV_SILENT, TV_VERBOSE #define JSON_VERBOSITY JV_LINENUM // one of: JV_SILENT, JV_FOOTER, JV_CONFIGS, JV_MESSAGES, JV_LINENUM, JV_VERBOSE diff --git a/g2core/settings/settings_Printrbot_Plus.h b/g2core/settings/settings_Printrbot_Plus.h index 4460a922..8ecb3c49 100644 --- a/g2core/settings/settings_Printrbot_Plus.h +++ b/g2core/settings/settings_Printrbot_Plus.h @@ -60,6 +60,7 @@ #define MARLIN_COMPAT_ENABLED true // enable marlin compatibility mode #define COMM_MODE JSON_MODE // one of: TEXT_MODE, JSON_MODE #define XIO_ENABLE_FLOW_CONTROL FLOW_CONTROL_RTS // FLOW_CONTROL_OFF, FLOW_CONTROL_RTS +#define XIO_UART_MUTES_WHEN_USB_CONNECTED 1 // Mute the UART when USB connects #define TEXT_VERBOSITY TV_VERBOSE // one of: TV_SILENT, TV_VERBOSE #define JSON_VERBOSITY JV_MESSAGES // one of: JV_SILENT, JV_FOOTER, JV_CONFIGS, JV_MESSAGES, JV_LINENUM, JV_VERBOSE diff --git a/g2core/settings/settings_Printrbot_Simple_1403.h b/g2core/settings/settings_Printrbot_Simple_1403.h index c32c4739..72a82f48 100644 --- a/g2core/settings/settings_Printrbot_Simple_1403.h +++ b/g2core/settings/settings_Printrbot_Simple_1403.h @@ -57,8 +57,10 @@ // Communications and reporting settings +#define MARLIN_COMPAT_ENABLED true // enable marlin compatibility mode #define COMM_MODE JSON_MODE // one of: TEXT_MODE, JSON_MODE #define XIO_ENABLE_FLOW_CONTROL FLOW_CONTROL_RTS // FLOW_CONTROL_OFF, FLOW_CONTROL_RTS +#define XIO_UART_MUTES_WHEN_USB_CONNECTED 1 // Mute the UART when USB connects #define TEXT_VERBOSITY TV_VERBOSE // one of: TV_SILENT, TV_VERBOSE #define JSON_VERBOSITY JV_MESSAGES // one of: JV_SILENT, JV_FOOTER, JV_CONFIGS, JV_MESSAGES, JV_LINENUM, JV_VERBOSE diff --git a/g2core/settings/settings_Printrbot_Simple_1608.h b/g2core/settings/settings_Printrbot_Simple_1608.h index a24510b6..ccc3181d 100644 --- a/g2core/settings/settings_Printrbot_Simple_1608.h +++ b/g2core/settings/settings_Printrbot_Simple_1608.h @@ -57,6 +57,7 @@ // Communications and reporting settings +#define MARLIN_COMPAT_ENABLED true // enable marlin compatibility mode #define COMM_MODE JSON_MODE // one of: TEXT_MODE, JSON_MODE #define XIO_ENABLE_FLOW_CONTROL FLOW_CONTROL_RTS // FLOW_CONTROL_OFF, FLOW_CONTROL_RTS #define XIO_UART_MUTES_WHEN_USB_CONNECTED 1 // Mute the UART when USB connects diff --git a/g2core/xio.cpp b/g2core/xio.cpp index ef61bd7c..7eac3898 100755 --- a/g2core/xio.cpp +++ b/g2core/xio.cpp @@ -173,6 +173,10 @@ struct xioDeviceWrapperBase { // C++ base class for device primit virtual int16_t write(const char *buffer, int16_t len) { return -1; }; virtual char *readline(devflags_t limit_flags, uint16_t &size) { return nullptr; }; + +#if MARLIN_COMPAT_ENABLED == true + virtual void exitFakeBootloaderMode() {}; +#endif }; // Here we create the xio_t class, which has convenience methods to handle cross-device actions as a whole. @@ -397,6 +401,14 @@ struct xio_t { return (NULL); }; +#if MARLIN_COMPAT_ENABLED == true + void exitFakeBootloaderMode() { + for (int8_t i = 0; i < _dev_count; ++i) { + DeviceWrappers[i]->exitFakeBootloaderMode(); + } + }; +#endif + uint16_t magic_end; }; @@ -447,6 +459,32 @@ struct LineRXBuffer : RXBuffer<_size, owner_type, char> { bool _last_returned_a_control = false; +#if MARLIN_COMPAT_ENABLED == true + enum class STK500V2_State { + Done, // not in the faked stk500v2 bootloader + Timeout, // timeout period, waiting for a start character + Start, // waiting for 0x1B + Sequence, // waiting for sequence byte + Length_0, // waiting for MSB of length + Length_1, // waiting for LSB of length + Header_End,// waiting for 0x0E + Data, // waiting for more data + Checksum // waiting for checksum byte + }; + STK500V2_State _stk_parser_state; + uint16_t _stk_packet_data_length; + Motate::Timeout _stk_timeout; + + void startFakeBootloaderMode() { + _stk_parser_state = STK500V2_State::Timeout; + _stk_timeout.set(2000); // two seconds + } + + void exitFakeBootloaderMode() { + _stk_parser_state = STK500V2_State::Done; + } +#endif + LineRXBuffer(owner_type owner) : parent_type{owner} {}; void init() { @@ -610,6 +648,70 @@ struct LineRXBuffer : RXBuffer<_size, owner_type, char> { return false; } +#if MARLIN_COMPAT_ENABLED == true + // it's possible something will try to talk stk500v2 to us. + // See https://github.com/synthetos/g2/wiki/Marlin-Compatibility#stk500v2 + if (_stk_parser_state >= STK500V2_State::Timeout) + { + if (_stk_parser_state == STK500V2_State::Timeout) { + if (_stk_timeout.isPast()) { + _stk_parser_state = STK500V2_State::Done; + // start over, outside of stk500v2 mode + continue; + } + // if we got something before the timeout, then we're in stk500v2 mode + // we'll look at what we got and maybe exit anyway + _stk_parser_state = STK500V2_State::Start; + } + if (_stk_parser_state == STK500V2_State::Start) { + if (c == 0x1B) { + _stk_parser_state = STK500V2_State::Sequence; + + // this is the start of this "line" and we can "read" (skip) everything up to here. + _read_offset = _scan_offset; + _line_start_offset = _scan_offset; + } + else if ((c == '{') || (c == '\n') || (c == '\r') || (c == 'G') || (c == 'M')) { + // jump out of bootloader mode + _stk_parser_state = STK500V2_State::Done; + continue; + } + + } else if (_stk_parser_state == STK500V2_State::Sequence) { + // we ignore the sequence + _stk_parser_state = STK500V2_State::Length_0; + } else if (_stk_parser_state == STK500V2_State::Length_0) { + _stk_packet_data_length = c << 8; + _stk_parser_state = STK500V2_State::Length_1; + } else if (_stk_parser_state == STK500V2_State::Length_1) { + _stk_packet_data_length |= c; + _stk_parser_state = STK500V2_State::Header_End; + } else if (_stk_parser_state == STK500V2_State::Header_End) { + if (c == 0x0E) { + _stk_parser_state = STK500V2_State::Data; + } else { + // end-of-header marker was corrupt, start over + _stk_packet_data_length = 0; + _stk_parser_state = STK500V2_State::Start; + } + } else if (_stk_parser_state == STK500V2_State::Data) { + + // we don't read the data here, just return it + if (--_stk_packet_data_length == 0) { + _stk_parser_state = STK500V2_State::Checksum; + } + } else if (_stk_parser_state == STK500V2_State::Checksum) { + // we do NOT check the checksum, since if it's corrupt, we + // need to reply, and we can't reply here. + + // at this point, we at least have a complete packet + // we will use the "control" return machanism to handle this + // since controls don't have to be \r\n-terminated + is_control = true; + } + } + else +#endif // Look for line endings if (c == '\r' || c == '\n') { if (!_at_start_of_line) { // We only mark ends_line for the first end-line char, and if @@ -1003,6 +1105,10 @@ struct xioDeviceWrapper : xioDeviceWrapperBase { // describes a device for re setAsConnectedAndReady(); +#if MARLIN_COMPAT_ENABLED == true + _rx_buffer.startFakeBootloaderMode(); +#endif + if (isAlwaysDataAndCtrl()) { // Case 1 (ignoring others) setActive(); @@ -1087,6 +1193,13 @@ struct xioDeviceWrapper : xioDeviceWrapperBase { // describes a device for re } // flags & DEV_IS_CONNECTED } }; + +#if MARLIN_COMPAT_ENABLED == true + void exitFakeBootloaderMode() override { + _rx_buffer.exitFakeBootloaderMode(); + }; +#endif + }; @@ -1289,6 +1402,16 @@ void xio_flush_to_command() { return xio.flushToCommand(); } +#if MARLIN_COMPAT_ENABLED == true +/* + * xio_end_fake_bootloader() - end the fake bootloader mode + */ + +void xio_exit_fake_bootloader() { + return xio.exitFakeBootloaderMode(); +} +#endif + /*********************************************************************************** * newlib-nano support functions * Here we wire printf to xio diff --git a/g2core/xio.h b/g2core/xio.h index 1f4eba5e..065fafe2 100755 --- a/g2core/xio.h +++ b/g2core/xio.h @@ -49,8 +49,9 @@ #define XIO_H_ONCE //#include "g2core.h" // not required if used in g2core project -#include "config.h" // required for nvObj typedef +#include "config.h" // required for nvObj typedef #include "canonical_machine.h" // needed for cm_has_hold() +#include "settings.h" // needed for MARLIN_COMPAT_ENABLED /**** Defines, Macros, and Assorted Parameters ****/ @@ -122,6 +123,9 @@ char *xio_readline(devflags_t &flags, uint16_t &size); int16_t xio_writeline(const char *buffer, bool only_to_muted = false); bool xio_connected(); void xio_flush_to_command(); +#if MARLIN_COMPAT_ENABLED == true +void xio_exit_fake_bootloader(); +#endif stat_t xio_set_spi(nvObj_t *nv);