Files
g2/g2core/marlin_compatibility.cpp

613 lines
18 KiB
C++

/*
* marlin_compatibility.cpp - support for marlin protocol and gcode
* This file is part of the g2core project
*
* Copyright (c) 2017 - 2018 Alden S. Hart, Jr.
* Copyright (c) 2017 - 2018 Rob Giseburt
*
* This file ("the software") is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2 as published by the
* Free Software Foundation. You should have received a copy of the GNU General Public
* License, version 2 along with the software. If not, see <http://www.gnu.org/licenses/>.
*
* THE SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY
* WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* This file contains most of the code needed to support marlin compatibility mode.
* Other files that are affected include:
* - gcode_parser.cpp/.h big additions for handling Marlin gcode and other functions
* - canonical_machine.cpp/.h setting targets for relative_extruder_mode coordinates
* - controller.cpp main-loop callback, fake_stk500, specialized dispatching
* - report.cpp system ready message suppressed for Marlin
* - xio.cpp/.h Marlin protocol support
*/
#include "g2core.h" // #1
#include "config.h" // #2
#include "settings.h"
#if MARLIN_COMPAT_ENABLED == true
#include "controller.h"
#include "gcode_parser.h"
#include "canonical_machine.h"
#include "util.h"
#include "xio.h" // for char definitions
#include "temperature.h" // for temperature controls
#include "json_parser.h"
#include "planner.h"
#include "stepper.h" // for MOTOR_TIMEOUT_SECONDS_MIN/MOTOR_TIMEOUT_SECONDS_MAX
#include "MotateTimers.h" // for char definitions
#include "MotateUniqueID.h" // for Motate::UUID
// 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,
};
// Global state variables
MarlinStateExtended_t mst; // Marlin state object
// Local variables
bool temperature_requested = false;
bool position_requested = false;
// State machine to handle marlin temperature controls
enum class MarlinSetTempState {
Idle = 0,
SettingTemperature,
StartingUpdates,
StartingWait,
StoppingUpdates,
SettingTemperatureNoWait
};
MarlinSetTempState set_temp_state; // record the state for the temperature-control pseudo-cycle
// These next parameters for the next temperature-control pseudo-cycle are only needed until the calls are queued.
float next_temperature; // as it says
uint8_t next_temperature_tool; // 0-based, with 2 being the heat-bed
// Information about if we are to be dumping periodic temperature updates
bool temperature_updates_requested = false;
Motate::Timeout temperature_update_timeout;
// local helper functions and macros
/***********************************************************************************
* _get_specific_nv() - convenience function to get an NV object
*/
nvObj_t *_get_specific_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
return nullptr; // since we JUST provided the keys, this should never happen
}
strcpy(nv->group, cfgArray[nv->index].group); // capture the group string if there is one
nv_get(nv);
return nv;
}
/***********************************************************************************
* _report_temperatures() - convenience function called from marlin_response() and marlin_callback()
*/
void _report_temperatures(char *(&str)) {
// Tool 0 is extruder 1
uint8_t tool = cm->gm.tool;
str_concat(str, " T:");
str += floattoa(str, cm_get_temperature(tool), 2);
str_concat(str, " /");
str += floattoa(str, cm_get_set_temperature(tool), 2);
str_concat(str, " B:");
str += floattoa(str, cm_get_temperature(3), 2);
str_concat(str, " /");
str += floattoa(str, cm_get_set_temperature(3), 2);
str_concat(str, " @:");
str += floattoa(str, cm_get_heater_output(tool), 0);
str_concat(str, " B@:");
str += floattoa(str, cm_get_heater_output(3), 0);
}
/***********************************************************************************
* _report_position() - convenience function called from marlin_response()
*/
void _report_position(char *(&str)) {
str_concat(str, " X:");
str += floattoa(str, cm_get_display_position(ACTIVE_MODEL, 0), 2);
str_concat(str, " Y:");
str += floattoa(str, cm_get_display_position(ACTIVE_MODEL, 1), 2);
str_concat(str, " Z:");
str += floattoa(str, cm_get_display_position(ACTIVE_MODEL, 2), 2);
uint8_t tool = cm->gm.tool;
if ((tool > 0) && (tool < 3)) {
str_concat(str, " E:");
str += floattoa(str, cm_get_display_position(ACTIVE_MODEL, 2), tool + 2); // A or B, depending on tool
}
}
/***********************************************************************************
*** MARLIN GCODES AND MCODES
*** Called from gcore_parser.cpp
***********************************************************************************/
/***********************************************************************************
* marlin_start_tramming_bed() - G29 called from gcode parser
* marlin G29 support - run a script to emulate a G29 homing command
*/
#ifdef MARLIN_G29_SCRIPT
auto marlin_g29_file = make_xio_flash_file(MARLIN_G29_SCRIPT);
#endif
stat_t marlin_start_tramming_bed() {
#ifndef MARLIN_G29_SCRIPT
return (STAT_G29_NOT_CONFIGURED);
#else
xio_send_file(marlin_g29_file);
return (STAT_OK);
#endif
}
/***********************************************************************************
* marlin_list_sd_response() - M20 called from gcode parser
* marlin_select_sd_response() - M23 called from gcode parser
*/
stat_t marlin_list_sd_response()
{
char buffer[128];
char *str = buffer;
str_concat(str, "Begin file list\nEnd file list\n");
*str = 0;
xio_writeline(buffer);
return (STAT_OK);
}
stat_t marlin_select_sd_response(const char *file)
{
char buffer[128];
char *str = buffer;
str_concat(str, "open failed, File: ");
strncpy(str, file, Motate::strlen(file));
str += Motate::strlen(file);
str_concat(str, "\n");
*str = 0;
xio_writeline(buffer);
return (STAT_OK);
}
/***********************************************************************************
* cm_marlin_set_extruder_mode() - M82, M83 called from gcode parser (affects MODEL only)
*
* EXTRUDER_MOVES_NORMAL = 0, // M82
* EXTRUDER_MOVES_RELATIVE, // M83
* EXTRUDER_MOVES_VOLUMETRIC // Ultimaker2Marlin
*/
stat_t marlin_set_extruder_mode(const uint8_t mode)
{
mst.extruder_mode = (cmExtruderMode)mode;
return (STAT_OK);
}
/***********************************************************************************
* marlin_disable_motors() - M84 (without S) called from gcode parser
*/
stat_t marlin_disable_motors()
{
char buffer[128];
char *str = buffer;
// TODO: support other parameters
str_concat(str, "{md:0}");
cm_json_command(buffer);
return (STAT_OK);
}
/***********************************************************************************
* marlin_set_motor_timeout() - M84 (with S), M85 Sxxx called from gcode parser
*/
stat_t marlin_set_motor_timeout(float s) // M18 Sxxx, M84 Sxxx, M85 Sxxx
{
if (s < MOTOR_TIMEOUT_SECONDS_MIN) {
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
}
if (s > MOTOR_TIMEOUT_SECONDS_MAX) {
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
}
char buffer[128];
char *str = buffer;
// TODO: support other fans, or remapping output
str_concat(str, "{mt:");
str += floattoa(str, s, 1);
str_concat(str, "}");
cm_json_command(buffer);
return (STAT_OK);
}
/***********************************************************************************
* marlin_set_temperature() - M104, M140, M109, M190 called from gcode parser
* _queue_next_temperature_comands() - returns true if it finished
* _marlin_start_temperature_updates()
* _marlin_end_temperature_updates()
*/
void _marlin_start_temperature_updates(float* vect, bool* flag) {
temperature_updates_requested = true;
temperature_update_timeout.set(1); // immediately
}
void _marlin_end_temperature_updates(float* vect, bool* flag) {
temperature_updates_requested = false;
}
bool _queue_next_temperature_commands()
{
if (MarlinSetTempState::Idle != set_temp_state) {
if (mp_planner_is_full(mp)) {
return false;
}
char buffer[128];
char *str = buffer;
if ((MarlinSetTempState::SettingTemperature == set_temp_state) ||
(MarlinSetTempState::SettingTemperatureNoWait == set_temp_state))
{
str_concat(str, "{he");
str += inttoa(str, next_temperature_tool);
str_concat(str, "st:");
str += floattoa(str, next_temperature, 2);
str_concat(str, "}");
cm_json_command(buffer);
if (MarlinSetTempState::SettingTemperatureNoWait == set_temp_state) {
set_temp_state = MarlinSetTempState::Idle;
return true;
}
set_temp_state = MarlinSetTempState::StartingUpdates;
if (mp_planner_is_full(mp)) {
return false;
}
}
if (MarlinSetTempState::StartingUpdates == set_temp_state) {
mp_queue_command(_marlin_start_temperature_updates, nullptr, nullptr);
set_temp_state = MarlinSetTempState::StartingWait;
if (mp_planner_is_full(mp)) {
return false;
}
}
if (MarlinSetTempState::StartingWait == set_temp_state) {
str = buffer;
str_concat(str, "{he");
str += inttoa(str, next_temperature_tool);
str_concat(str, "at:t}");
cm_json_wait(buffer);
set_temp_state = MarlinSetTempState::StoppingUpdates;
if (mp_planner_is_full(mp)) {
return false;
}
}
if (MarlinSetTempState::StoppingUpdates == set_temp_state) {
mp_queue_command(_marlin_end_temperature_updates, nullptr, nullptr);
set_temp_state = MarlinSetTempState::Idle;
}
}
return true;
}
stat_t marlin_set_temperature(uint8_t tool, float temperature, bool wait) {
if (MarlinSetTempState::Idle != set_temp_state) {
return (STAT_BUFFER_FULL_FATAL); // we shouldn't be here
}
if ((tool < 1) || (tool > 3)) {
return STAT_INPUT_VALUE_RANGE_ERROR;
}
set_temp_state = wait ? MarlinSetTempState::SettingTemperature : MarlinSetTempState::SettingTemperatureNoWait;
next_temperature = temperature;
next_temperature_tool = tool;
_queue_next_temperature_commands(); // we can ignore the return
return (STAT_OK);
}
/***********************************************************************************
* marlin_request_temperature_report() - M105 called from gcode parser
*/
stat_t marlin_request_temperature_report() // M105
{
uint8_t tool = cm->gm.tool;
if ((tool < 1) || (tool > 2)) {
return STAT_INPUT_VALUE_RANGE_ERROR;
}
temperature_requested = true;
return STAT_OK;
}
/***********************************************************************************
* marlin_set_fan_speed() - M106, M107 called from gcode parser
*/
stat_t marlin_set_fan_speed(const uint8_t fan, float speed)
{
char buffer[128];
char *str = buffer;
if ((fan != 0) || (speed < 0.0) || (speed > 255.0)) {
return STAT_INPUT_VALUE_RANGE_ERROR;
}
// TODO: support other fans, or remapping output
str_concat(str, "{out4:");
str += floattoa(str, (speed < 1.0) ? speed : (speed / 255.0), 4);
str_concat(str, "}");
cm_json_command(buffer);
return (STAT_OK);
}
/***********************************************************************************
* marlin_request_position_report() - M114 called from gcode parser
*/
stat_t marlin_request_position_report() // M114
{
position_requested = true;
return STAT_OK;
}
/***********************************************************************************
* marlin_report_version() - M115
*/
stat_t marlin_report_version()
{
char buffer[128];
char *str = buffer;
str_concat(str, "ok FIRMWARE_NAME:Marlin g2core-");
str_concat(str, G2CORE_FIRMWARE_BUILD_STRING);
*str = 0;
xio_writeline(buffer);
str = buffer;
*str = 0;
str_concat(str, " SOURCE_CODE_URL:https://github.com/synthetos/g2");
*str = 0;
xio_writeline(buffer);
str = buffer; *str = 0;
str_concat(str, " PROTOCOL_VERSION:1.0");
*str = 0;
xio_writeline(buffer);
str = buffer; *str = 0;
str_concat(str, " MACHINE_TYPE:");
#ifdef SETTINGS_FILE
#define settings_file_string1(s) #s
#define settings_file_string2(s) settings_file_string1(s)
str_concat(str, settings_file_string2(SETTINGS_FILE));
#undef settings_file_string1
#undef settings_file_string2
#else
str_concat(str, "<default-settings>");
#endif
*str = 0;
xio_writeline(buffer);
str = buffer; *str = 0;
// TODO: make this configurable, based on the tool table
str_concat(str, " EXTRUDER_COUNT:1");
*str = 0;
xio_writeline(buffer);
str = buffer; *str = 0;
str_concat(str, " UUID:");
const char *uuid = Motate::UUID;
strncpy(str, uuid, Motate::strlen(uuid));
str += Motate::strlen(uuid);
str_concat(str, "\n");
*str = 0;
xio_writeline(buffer);
str = buffer; *str = 0;
return (STAT_OK);
}
/***********************************************************************************
*** MARLIN INTERNAL FUNCTIONS
***********************************************************************************/
/***********************************************************************************
* marlin_callback() - called by controller dispatcher - return STAT_EAGAIN if it failed
*/
stat_t marlin_callback()
{
if ((js.json_mode == MARLIN_COMM_MODE) && temperature_updates_requested && (temperature_update_timeout.isPast())) {
char buffer[128];
char *str = buffer;
_report_temperatures(str);
*str++ = '\n';
*str++ = 0;
temperature_update_timeout.set(1000); // every second
xio_writeline(buffer);
} // temperature updates
if (!_queue_next_temperature_commands()) {
return STAT_EAGAIN;
}
return STAT_OK;
}
/***********************************************************************************
* marlin_response() - marlin mirror of text_response(), called from _dispatch_kernel() in controller.cpp
*/
void marlin_response(const stat_t status, char *buf)
{
char buffer[128];
char *str = buffer;
bool request_resend = false;
if (cs.responses_suppressed) {
return;
}
if ((status == STAT_OK) || (status == STAT_EAGAIN) || (status == STAT_NOOP)) {
str_concat(str, "ok");
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 if (status == STAT_CHECKSUM_MATCH_FAILED) {
str_concat(str, "Error:checksum mismatch, Last Line: ");
str += inttoa(str, cm->gmx.last_line_number);
request_resend = true;
}
else if (status == STAT_LINE_NUMBER_OUT_OF_SEQUENCE) {
str_concat(str, "Error:Line Number is not Last Line Number+1, Last Line: ");
str += inttoa(str, cm->gmx.last_line_number);
request_resend = true;
}
else {
str += sprintf(str, "Error:%s", get_status_message(status));
}
*str++ = '\n';
*str++ = 0;
// reset requests
temperature_requested = false;
position_requested = false;
xio_writeline(buffer);
if (request_resend) {
str = buffer;
str_concat(str, "Resend: ");
str += inttoa(str, cm->gmx.last_line_number+1);
*str++ = '\n';
*str++ = 0;
xio_writeline(buffer);
}
}
/***********************************************************************************
* marlin_handle_fake_stk500() - returns true if it handled something (IOW, don't futher process the line)
* _marlin_fake_stk500_response() - convenience function for formang responses from marlin_handle_fake_stk500()
*/
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);
}
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;
}
#endif // MARLIN_COMPAT_ENABLED == true