diff --git a/g2core/canonical_machine.cpp b/g2core/canonical_machine.cpp index d9a6b9fc..c5b38249 100644 --- a/g2core/canonical_machine.cpp +++ b/g2core/canonical_machine.cpp @@ -594,7 +594,10 @@ static float _calc_ABC(const uint8_t axis, const float target[]) if ((cm.a[axis].axis_mode == AXIS_STANDARD) || (cm.a[axis].axis_mode == AXIS_INHIBITED)) { return(target[axis]); // no mm conversion - it's in degrees } - return(_to_millimeters(target[axis]) * 360 / (2 * M_PI * cm.a[axis].radius)); + + // radius mode + + return (_to_millimeters(target[axis]) * 360.0 / (2.0 * M_PI * cm.a[axis].radius)); } void cm_set_model_target(const float target[], const bool flags[]) @@ -624,9 +627,27 @@ void cm_set_model_target(const float target[], const bool flags[]) } else { tmp = _calc_ABC(axis, target); } +#if MARLIN_COMPAT_ENABLED == true + // If we are in absolute mode (generally), but the extruder is relative, + // then we adjust the extruder to a relative position + if (cm.gmx.marlin_flavor && (cm.a[axis].axis_mode == AXIS_RADIUS)) { + if (cm.gmx.extruder_mode == EXTRUDER_MOVES_ABSOLUTE) { + cm.gm.target[axis] = tmp + cm_get_active_coord_offset(axis); + } + else if (cm.gmx.extruder_mode == EXTRUDER_MOVES_RELATIVE) { + cm.gm.target[axis] += tmp; + } + // TODO +// else { +// cm.gm.target[axis] += tmp * cm.gmx.volume_to_filament_length[axis-3]; +// } + } + else +#endif // MARLIN_COMPAT_ENABLED if (cm.gm.distance_mode == ABSOLUTE_DISTANCE_MODE) { cm.gm.target[axis] = tmp + cm_get_active_coord_offset(axis); // sacidu93's fix to Issue #22 - } else { + } + else { cm.gm.target[axis] += tmp; } } @@ -2024,6 +2045,14 @@ stat_t cm_json_command(char *json_string) return mp_json_command(json_string); } +/* + * cm_json_command_immediate() - M100.1 + */ +stat_t cm_json_command_immediate(char *json_string) +{ + return mp_json_command_immediate(json_string); +} + /* * cm_json_wait() - M102 */ diff --git a/g2core/canonical_machine.h b/g2core/canonical_machine.h index b89ef5b0..622c30ee 100644 --- a/g2core/canonical_machine.h +++ b/g2core/canonical_machine.h @@ -180,8 +180,16 @@ typedef enum { // these are in order to optimized CASE NEXT_ACTION_JSON_WAIT, // M101 #if MARLIN_COMPAT_ENABLED == true - NEXT_ACTION_MARLIN_SET_EXTRUDER_TEMP, // 104, 109 - NEXT_ACTION_MARLIN_SET_BED_TEMP, // 140, 190 + NEXT_ACTION_MARLIN_PRINT_TEMPERATURES, // M105 + NEXT_ACTION_MARLIN_PRINT_POSITION, // M114 + NEXT_ACTION_MARLIN_SET_EXTRUDER_TEMP, // M104, M109 + NEXT_ACTION_MARLIN_SET_BED_TEMP, // M140, M190 + NEXT_ACTION_MARLIN_CANCEL_WAIT_TEMP, // M108 + NEXT_ACTION_MARLIN_TRAM_BED, // G29 + NEXT_ACTION_MARLIN_SET_FAN_SPEED, // M106 + NEXT_ACTION_MARLIN_STOP_FAN, // M107 + NEXT_ACTION_MARLIN_DISABLE_MOTORS, // M84 + NEXT_ACTION_MARLIN_DISPLAY_ON_SCREEN, // M117 #endif } cmNextAction; @@ -384,6 +392,14 @@ typedef struct GCodeState { // Gcode model state - used by model, pl }; } GCodeState_t; +#if MARLIN_COMPAT_ENABLED == true +enum cmExtruderMode { + EXTRUDER_MOVES_ABSOLUTE = 0, // M82 + EXTRUDER_MOVES_RELATIVE, // M83 + EXTRUDER_MOVES_VOLUMETRIC // Ultimaker2Marlin +}; +#endif // MARLIN_COMPAT_ENABLED + typedef struct GCodeStateExtended { // Gcode dynamic state extensions - used by model and arcs uint16_t magic_start; // magic number to test memory integrity uint8_t next_action; // handles G modal group 1 moves & non-modals @@ -409,7 +425,8 @@ typedef struct GCodeStateExtended { // Gcode dynamic state extensions - used #if MARLIN_COMPAT_ENABLED == true bool marlin_flavor; -#endif + cmExtruderMode extruder_mode; // Mode of the extruder - changes how "E" is interpreted +#endif // MARLIN_COMPAT_ENABLED uint16_t magic_end; @@ -679,6 +696,7 @@ void cm_optional_program_stop(void); // M1 void cm_program_end(void); // M2 stat_t cm_json_command(char *json_string); // M100 +stat_t cm_json_command_immediate(char *json_string); // M100.1 stat_t cm_json_wait(char *json_string); // M102 /*--- Cycles ---*/ diff --git a/g2core/controller.cpp b/g2core/controller.cpp index 044ab720..6f0faaee 100755 --- a/g2core/controller.cpp +++ b/g2core/controller.cpp @@ -328,6 +328,18 @@ static stat_t _controller_state() return (STAT_OK); } + +/* + * _reset_comms_mode() - reset the communications mode (and other effected settings) after connection or disconnection + */ +void _reset_comms_mode() { + // reset the communications mode + cs.comm_mode = COMM_MODE; + js.json_mode = (COMM_MODE < AUTO_MODE) ? COMM_MODE : JSON_MODE; + sr.status_report_verbosity = STATUS_REPORT_VERBOSITY; + qr.queue_report_verbosity = QUEUE_REPORT_VERBOSITY; +} + /* * controller_set_connected(bool) - hook for xio to tell the controller that we * have/don't have a connection. @@ -335,6 +347,8 @@ static stat_t _controller_state() void controller_set_connected(bool is_connected) { if (is_connected) { + _reset_comms_mode(); + cs.controller_state = CONTROLLER_CONNECTED; // we JUST connected } else { // we just disconnected from the last device, we'll expect a banner again cs.controller_state = CONTROLLER_NOT_CONNECTED; @@ -353,6 +367,8 @@ void controller_set_muted(bool is_muted) { const bool only_to_muted = true; xio_writeline("{\"muted\":true}\n", only_to_muted); } else { + _reset_comms_mode(); + // one channel just got unmuted, announce it (except to the muted) xio_writeline("{\"muted\":false}\n"); } diff --git a/g2core/error.h b/g2core/error.h index 724247f9..0376ad5a 100644 --- a/g2core/error.h +++ b/g2core/error.h @@ -206,7 +206,7 @@ char *get_status_message(stat_t status); #define STAT_INPUT_FROM_MUTED_CHANNEL_ERROR 117 // input from a muted channel was ignored #define STAT_CHECKSUM_MATCH_FAILED 118 // the provided checksum didn't match -#define STAT_LINE_NUMBER_OUT_OF_SEQUENCE 119 +#define STAT_LINE_NUMBER_OUT_OF_SEQUENCE 119 // the provided line number was out of sequence #define STAT_ERROR_120 120 #define STAT_ERROR_121 121 @@ -315,7 +315,7 @@ char *get_status_message(stat_t status); #define STAT_TEMPERATURE_CONTROL_ERROR 209 // temperature controls err'd out -#define STAT_ERROR_210 210 +#define STAT_G29_NOT_CONFIGURED 210 #define STAT_ERROR_211 211 #define STAT_ERROR_212 212 #define STAT_ERROR_213 213 @@ -501,9 +501,9 @@ static const char stat_113[] = "JSON string too long"; static const char stat_114[] = "JSON txt fields cannot be nested"; static const char stat_115[] = "JSON maximum nesting depth exceeded"; static const char stat_116[] = "JSON value does not agree with variable type"; -static const char stat_117[] = "117"; -static const char stat_118[] = "118"; -static const char stat_119[] = "119"; +static const char stat_117[] = "Input from a muted channel was ignored"; +static const char stat_118[] = "The provided checksum didn't match"; +static const char stat_119[] = "The provided line number was out of sequence"; static const char stat_120[] = "120"; static const char stat_121[] = "121"; @@ -606,7 +606,7 @@ static const char stat_207[] = "Kill job"; static const char stat_208[] = "No GPIO for this value"; static const char stat_209[] = "209"; -static const char stat_210[] = "210"; +static const char stat_210[] = "Marlin G29 command was not configured at compile-time"; static const char stat_211[] = "211"; static const char stat_212[] = "212"; static const char stat_213[] = "213"; diff --git a/g2core/gcode_parser.cpp b/g2core/gcode_parser.cpp index ace88476..d05c75d8 100644 --- a/g2core/gcode_parser.cpp +++ b/g2core/gcode_parser.cpp @@ -74,10 +74,9 @@ typedef struct GCodeInputValue { // Gcode inputs - meaning depends on context 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) + float E_word; // E - "extruder" - may be interpreted any number of ways - bool marlin_wait_for_temp; // M140 or M190 - wait for temperature (Marlin-only) + bool marlin_relative_extruder_mode; // M82, M83 (Marlin-only) #endif } GCodeValue_t; @@ -120,8 +119,11 @@ typedef struct GCodeFlags { // Gcode input flags bool sso_control; #if MARLIN_COMPAT_ENABLED == true - bool marlin_temp_requested; - bool marlin_position_requested; + bool E_word; + + bool marlin_wait_for_temp; // M140 or M190 - wait for temperature (Marlin-only) + + bool marlin_relative_extruder_mode; #endif } GCodeFlag_t; @@ -488,18 +490,31 @@ stat_t _get_next_gcode_word(char **pstr, char *letter, float *value) *letter = **pstr; (*pstr)++; - // X-axis-becomes-a-hexadecimal-number get-value case, e.g. G0X100 --> G255 - if ((**pstr == '0') && (*(*pstr+1) == 'X')) { - *value = 0; - (*pstr)++; - return (STAT_OK); // pointer points to X - } +// // X-axis-becomes-a-hexadecimal-number get-value case, e.g. G0X100 --> G255 +// if ((**pstr == '0') && (*(*pstr+1) == 'X')) { +// *value = 0; +// (*pstr)++; +// return (STAT_OK); // pointer points to X +// } +// +// // get-value general case +// char *end = *pstr; +// *value = strtof(*pstr, &end); // get-value general case - char *end; - *value = strtof(*pstr, &end); + char *end = *pstr; + *value = c_atof(end); + if(end == *pstr) { +#if MARLIN_COMPAT_ENABLED == true + if (cm.gmx.marlin_flavor) { + *value = 0; + } else { + return(STAT_BAD_NUMBER_FORMAT); + } +#else return(STAT_BAD_NUMBER_FORMAT); +#endif } // more robust test then checking for value=0; *pstr = end; return (STAT_OK); // pointer points to next character after the word @@ -585,7 +600,14 @@ stat_t _parse_gcode_block(char *buf, char *active_comment) case 21: SET_MODAL (MODAL_GROUP_G6, units_mode, MILLIMETERS); case 28: { switch (_point(value)) { - case 0: SET_MODAL (MODAL_GROUP_G0, next_action, NEXT_ACTION_GOTO_G28_POSITION); + case 0: { +#if MARLIN_COMPAT_ENABLED == true + if (cm.gmx.marlin_flavor) { + SET_NON_MODAL (next_action, NEXT_ACTION_SEARCH_HOME); + } +#endif + SET_MODAL (MODAL_GROUP_G0, next_action, NEXT_ACTION_GOTO_G28_POSITION); + } case 1: SET_MODAL (MODAL_GROUP_G0, next_action, NEXT_ACTION_SET_G28_POSITION); case 2: SET_NON_MODAL (next_action, NEXT_ACTION_SEARCH_HOME); case 3: SET_NON_MODAL (next_action, NEXT_ACTION_SET_ABSOLUTE_ORIGIN); @@ -668,6 +690,10 @@ stat_t _parse_gcode_block(char *buf, char *active_comment) case 93: SET_MODAL (MODAL_GROUP_G5, feed_rate_mode, INVERSE_TIME_MODE); case 94: SET_MODAL (MODAL_GROUP_G5, feed_rate_mode, UNITS_PER_MINUTE_MODE); // case 95: SET_MODAL (MODAL_GROUP_G5, feed_rate_mode, UNITS_PER_REVOLUTION_MODE); + +#if MARLIN_COMPAT_ENABLED == true + case 29: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_TRAM_BED); +#endif // MARLIN_COMPAT_ENABLED default: status = STAT_GCODE_COMMAND_UNSUPPORTED; } break; @@ -695,19 +721,36 @@ stat_t _parse_gcode_block(char *buf, char *active_comment) } break; case 51: SET_MODAL (MODAL_GROUP_M9, sso_control, true); - case 100: SET_NON_MODAL (next_action, NEXT_ACTION_JSON_COMMAND_SYNC); + case 100: + switch (_point(value)) { + case 0: SET_NON_MODAL (next_action, NEXT_ACTION_JSON_COMMAND_SYNC); + case 1: SET_NON_MODAL (next_action, NEXT_ACTION_JSON_COMMAND_ASYNC); + default: status = STAT_GCODE_COMMAND_UNSUPPORTED; + } + break; 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); + case 105: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_PRINT_TEMPERATURES); + case 114: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_PRINT_POSITION); - case 140: gv.marlin_wait_for_temp = true; // NO break! + case 140: gf.marlin_wait_for_temp = true; // NO break! case 104: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_SET_EXTRUDER_TEMP); - case 190: gv.marlin_wait_for_temp = true; // NO break! + case 190: gf.marlin_wait_for_temp = true; // NO break! case 109: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_SET_BED_TEMP); + + case 108: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_CANCEL_WAIT_TEMP); + + case 82: SET_NON_MODAL (marlin_relative_extruder_mode, false); + case 83: SET_NON_MODAL (marlin_relative_extruder_mode, true); + + case 106: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_SET_FAN_SPEED); + case 107: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_STOP_FAN); + + case 117: return STAT_OK; //SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_DISPLAY_ON_SCREEN); + + case 84: SET_NON_MODAL (next_action, NEXT_ACTION_MARLIN_DISABLE_MOTORS); #endif // MARLIN_COMPAT_ENABLED default: status = STAT_MCODE_COMMAND_UNSUPPORTED; @@ -734,6 +777,9 @@ stat_t _parse_gcode_block(char *buf, char *active_comment) case 'L': SET_NON_MODAL (L_word, value); case 'R': SET_NON_MODAL (arc_radius, value); case 'N': SET_NON_MODAL (linenum,(uint32_t)value); // line number +#if MARLIN_COMPAT_ENABLED == true + case 'E': SET_NON_MODAL (E_word, value); // extruder value +#endif // MARLIN_COMPAT_ENABLED default: status = STAT_GCODE_COMMAND_UNSUPPORTED; } if(status != STAT_OK) break; @@ -792,33 +838,116 @@ stat_t _execute_gcode_block(char *active_comment) EXEC_FUNC(cm_set_feed_rate, F_word); // F #if MARLIN_COMPAT_ENABLED == true + // adjust T real quick + if (cm.gmx.marlin_flavor && gf.tool_select) { + cm_select_tool(gv.tool_select); // We need to go ahead and apply to tool select + } // Handle Marlin specifics - // must be before we deal with S_word and P_word, since it MIGHT use those - if (gf.marlin_temp_requested) { // M105 - js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch - ritorno(marlin_request_temperature_report()); - } - if (gf.marlin_position_requested) { // M114 - js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch - ritorno(marlin_request_position_report()); + + // Deal with E + EXEC_FUNC(cm_marlin_set_extruder_mode, marlin_relative_extruder_mode); // M82, M83 + if (gf.E_word) { + cm.gmx.marlin_flavor = true; // E should ONLY be seen in marlin flavor + + // Ennn T0 -> Annn + if (cm.gm.tool_select == 0) { + gf.target[AXIS_A] = true; + gv.target[AXIS_A] = gv.E_word; + } + // Ennn T1 -> Bnnn + else if (cm.gm.tool_select == 1) { + gf.target[AXIS_B] = true; + gv.target[AXIS_B] = gv.E_word; + } + else { + return STAT_INPUT_VALUE_RANGE_ERROR; + } } + switch (gv.next_action) { - case NEXT_ACTION_MARLIN_SET_EXTRUDER_TEMP: { // M104 or M140 - float temp = 0; - if (gf.S_word) { temp = gv.S_word; } - if (gf.P_word) { temp = gv.P_word; } // we treat them the same, for now - status = marlin_set_temperature(cm.gm.tool_select, temp, gv.marlin_wait_for_temp); - return status; + case NEXT_ACTION_MARLIN_PRINT_TEMPERATURES: { // M105 + js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch + ritorno(marlin_request_temperature_report()); break; } + case NEXT_ACTION_MARLIN_PRINT_POSITION: { // M114 + js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch + ritorno(marlin_request_position_report()); + break; + } + case NEXT_ACTION_MARLIN_SET_EXTRUDER_TEMP: // M104 or M140 case NEXT_ACTION_MARLIN_SET_BED_TEMP: { // M109 or M190 + cm.gmx.marlin_flavor = true; // these gcodes are ONLY in marlin flavor float temp = 0; if (gf.S_word) { temp = gv.S_word; } if (gf.P_word) { temp = gv.P_word; } // we treat them the same, for now - // Note: tool is zero-based, so to gt he3 (the bed) we pass tool as 2 - status = marlin_set_temperature(2, temp, gv.marlin_wait_for_temp); + uint8_t tool = (NEXT_ACTION_MARLIN_SET_EXTRUDER_TEMP == gv.next_action) ? cm.gm.tool_select : 2; // heat bed is 2 here, normally 3 + + ritorno(marlin_set_temperature(tool, temp, gf.marlin_wait_for_temp)); + + gf.P_word = false; + gf.S_word = false; + break; + } + case NEXT_ACTION_MARLIN_CANCEL_WAIT_TEMP: { // M108 + js.json_mode = MARLIN_COMM_MODE; // we use M105 to know when to switch + cm_request_feedhold(); + cm_request_queue_flush(); + break; + } + case NEXT_ACTION_MARLIN_TRAM_BED: { // G29 + cm.gmx.marlin_flavor = true; // these gcodes are ONLY in marlin flavor + ritorno(marlin_start_tramming_bed()); + break; + } + case NEXT_ACTION_MARLIN_SET_FAN_SPEED: { // M106 + cm.gmx.marlin_flavor = true; // these gcodes are ONLY in marlin flavor + ritorno(marlin_set_fan_speed(gf.P_word?gv.P_word:0, gf.S_word?gv.S_word:0)); + gf.P_word = false; + gf.S_word = false; + break; + } + case NEXT_ACTION_MARLIN_STOP_FAN: { // M107 + cm.gmx.marlin_flavor = true; // these gcodes are ONLY in marlin flavor + ritorno(marlin_set_fan_speed(gf.P_word?gv.P_word:0, 0)); + gf.P_word = false; + gf.S_word = false; + break; + } + // adjust G28 (already adjusted to G28.2) + // with no X, Y, or Z, we assume all three + case NEXT_ACTION_SEARCH_HOME: { // G28 (g2core G28.2) + if (!gf.target[AXIS_X] && !gf.target[AXIS_Y] && !gf.target[AXIS_Z]) { + gv.target[AXIS_X]=0.0; gf.target[AXIS_X]=true; + gv.target[AXIS_Y]=0.0; gf.target[AXIS_Y]=true; + gv.target[AXIS_Z]=0.0; gf.target[AXIS_Z]=true; + } + break; + } + case NEXT_ACTION_MARLIN_DISABLE_MOTORS: { // M84 + ritorno(marlin_disable_motors()); + break; + } + case NEXT_ACTION_MARLIN_DISPLAY_ON_SCREEN: { // M184 + // ignore for now return status; + } + case NEXT_ACTION_DEFAULT: { + if (cm.gmx.marlin_flavor) { + // adjust G0 to almost always be the same as G1 + if (gf.motion_mode) + { + if (gf.E_word && + (!gf.target[AXIS_X] && !gf.target[AXIS_Y] && !gf.target[AXIS_Z]) + ) + { + gv.motion_mode = MOTION_MODE_STRAIGHT_TRAVERSE; // G0 + } else { + gv.motion_mode = MOTION_MODE_STRAIGHT_FEED; // G1 + } + } + } break; } } @@ -900,9 +1029,9 @@ stat_t _execute_gcode_block(char *active_comment) case NEXT_ACTION_SUSPEND_ORIGIN_OFFSETS: { status = cm_suspend_origin_offsets(); break;} // G92.2 case NEXT_ACTION_RESUME_ORIGIN_OFFSETS: { status = cm_resume_origin_offsets(); break;} // G92.3 - case NEXT_ACTION_JSON_COMMAND_SYNC: { status = cm_json_command(active_comment); break;} // M100 + case NEXT_ACTION_JSON_COMMAND_SYNC: { status = cm_json_command(active_comment); break;} // M100.0 + case NEXT_ACTION_JSON_COMMAND_ASYNC: { status = cm_json_command_immediate(active_comment); break;} // M100.1 case NEXT_ACTION_JSON_WAIT: { status = cm_json_wait(active_comment); break;} // M101 -// case NEXT_ACTION_JSON_COMMAND_IMMEDIATE: { status = mp_json_command_immediate(active_comment); break;} // M102 case NEXT_ACTION_DEFAULT: { cm_set_absolute_override(MODEL, gv.absolute_override); // apply absolute override diff --git a/g2core/json_parser.cpp b/g2core/json_parser.cpp index 9ae5da39..43977e10 100644 --- a/g2core/json_parser.cpp +++ b/g2core/json_parser.cpp @@ -85,7 +85,7 @@ static stat_t _get_nv_pair(nvObj_t *nv, char **pstr, int8_t *depth); * _json_parser_execute() executes sets and gets in an application agnostic way. It should work for other apps than g2core */ -void json_parser(char *str) +stat_t json_parser(char *str, bool suppress_response) // suppress_response defaults to false, see decalaration in .h { nvObj_t *nv = nv_reset_nv_list(); // get a fresh nvObj list stat_t status = _json_parser_kernal(nv, str); @@ -93,11 +93,12 @@ void json_parser(char *str) nv = nv_body; status = _json_parser_execute(nv); } - if (status == STAT_COMPLETE) { // skip the print if returning from something that already did it. - return; + if (suppress_response || (status == STAT_COMPLETE)) { // skip the print if returning from something that already did it. + return status; } 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 + return STAT_OK; } // This is almost the same as json_parser, except it doesn't *always* execute the parsed out list, and it never returns a reponse diff --git a/g2core/json_parser.h b/g2core/json_parser.h index c5343b74..4bef7817 100644 --- a/g2core/json_parser.h +++ b/g2core/json_parser.h @@ -82,7 +82,7 @@ extern jsSingleton_t js; /**** Function Prototypes ****/ -void json_parser(char *str); +stat_t json_parser(char *str, bool suppress_response = false); void json_parse_for_exec(char *str, bool execute); uint16_t json_serialize(nvObj_t *nv, char *out_buf, uint16_t size); void json_print_object(nvObj_t *nv); diff --git a/g2core/marlin_compatibility.cpp b/g2core/marlin_compatibility.cpp index 039a3aea..5c201250 100644 --- a/g2core/marlin_compatibility.cpp +++ b/g2core/marlin_compatibility.cpp @@ -65,7 +65,7 @@ enum class MarlinSetTempState { 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; // this is a g2core (1-based) tool +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; @@ -74,10 +74,101 @@ Motate::Timeout temperature_update_timeout; // local helper functions and macros /* - * marlin_verify_checksum() - check to see if we have a line number (cheaply) and a valid checksum + * _report_temperatures() - convenience function to get a value via the JSON NV system */ +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; +} + +/* + * _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_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); + + if (tool == 0) { + nv = _get_spcific_nv("he1op"); + } else if (tool == 1) { + nv = _get_spcific_nv("he2op"); + } if (!nv) { return; } + str += sprintf(str, " @:%.0f", (float)nv->value * 255.0); + + nv = _get_spcific_nv("he3op"); + if (!nv) { return; } + str += sprintf(str, " B@:%.0f", (float)nv->value * 255.0); +} + +/* + * _report_position() - convenience function called from marlin_response() + */ +void _report_position(char *(&str)) { + 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)); + + uint8_t tool = cm.gm.tool_select; + 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)); + } +} + +/*********************************************************************************** + * CONFIGURATION AND INTERFACE FUNCTIONS + * Functions to get and set variables from the cfgArray table + ***********************************************************************************/ + +/* + * marlin_verify_checksum() - check to see if we have a line number (cheaply) and a valid checksum + * called from gcode_parser + */ stat_t marlin_verify_checksum(char *str) { if (*str != 'N') { return STAT_OK; } // we only check if we have a line number @@ -91,12 +182,18 @@ stat_t marlin_verify_checksum(char *str) // c might be 0 here, in which case we didn't get a checksum and we return STAT_OK - if ((c == '*') && strtol(str, NULL, 10) != checksum) { - return STAT_CHECKSUM_MATCH_FAILED; + if (c == '*') { + *(str-1) = 0; // null terminate, the parser won't like this * here! + if (strtol(str, NULL, 10) != checksum) { + return STAT_CHECKSUM_MATCH_FAILED; + } } return STAT_OK; } +/* + * _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; @@ -151,15 +248,13 @@ bool marlin_handle_fake_stk500(char *str) } -/*********************************************************************************** - * CONFIGURATION AND INTERFACE FUNCTIONS - * Functions to get and set variables from the cfgArray table - ***********************************************************************************/ - +/* + * marlin_request_temperature_report() - called from the gcode parser for M105 + */ stat_t marlin_request_temperature_report() // M105 { uint8_t tool = cm.gm.tool_select; - if (tool > 1) { + if ((tool < 0) || (tool > 1)) { return STAT_INPUT_VALUE_RANGE_ERROR; } @@ -167,82 +262,19 @@ stat_t marlin_request_temperature_report() // M105 return STAT_OK; } + +/* + * marlin_request_temperature_report() - called from the gcode parser for M114 + */ 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() + * marlin_response() - marlin mirror of text_response(), called from _dispatch_kernel() in controller.cpp */ void marlin_response(const stat_t status, char *buf) { @@ -283,6 +315,7 @@ void marlin_response(const stat_t status, char *buf) xio_writeline(buffer); } + /* * _marlin_start_temperature_updates()/_marlin_end_temperature_updates() - * calls from commands in the buffer to manage temperature_updates_requested @@ -291,11 +324,11 @@ 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; } + /* * _queue_next_temperature_comands() - returns true if it finished */ @@ -312,7 +345,7 @@ bool _queue_next_temperature_commands() if ((MarlinSetTempState::SettingTemperature == set_temp_state) || (MarlinSetTempState::SettingTemperatureNoWait == set_temp_state)) { - str += sprintf(str, "{he%dst:%.2f}", next_temperature_tool, next_temperature); + str += sprintf(str, "{he%dst:%.2f}", next_temperature_tool+1, next_temperature); cm_json_command(buffer); if (MarlinSetTempState::SettingTemperatureNoWait == set_temp_state) { @@ -337,7 +370,7 @@ bool _queue_next_temperature_commands() if (MarlinSetTempState::StartingWait == set_temp_state) { str = buffer; - str += sprintf(str, "{he%dat:t}", next_temperature_tool); + str += sprintf(str, "{he%dat:t}", next_temperature_tool+1); cm_json_wait(buffer); set_temp_state = MarlinSetTempState::StoppingUpdates; @@ -356,12 +389,13 @@ bool _queue_next_temperature_commands() return true; } + /* * marlin_callback() - called by controller dispatcher - return STAT_EAGAIN if it failed */ stat_t marlin_callback() { - if (temperature_updates_requested && (temperature_update_timeout.isPast())) { + if ((js.json_mode == MARLIN_COMM_MODE) && temperature_updates_requested && (temperature_update_timeout.isPast())) { char buffer[128]; char *str = buffer; @@ -383,22 +417,95 @@ stat_t marlin_callback() } +/* + * marlin_set_temperature() - called from the gcode parser for M104,M140,M109,M190 + */ stat_t marlin_set_temperature(uint8_t tool, float temperature, bool wait) { if (MarlinSetTempState::Idle != set_temp_state) { - return (STAT_BUFFER_FULL_FATAL); // this shouldn't happen + return (STAT_BUFFER_FULL_FATAL); // we shouldn't be here } - if ((tool < 0) || (tool > 3)) { + if ((tool < 0) || (tool > 2)) { return STAT_INPUT_VALUE_RANGE_ERROR; } set_temp_state = wait ? MarlinSetTempState::SettingTemperature : MarlinSetTempState::SettingTemperatureNoWait; next_temperature = temperature; - // Note the conversion from a "marlin" tool (zero-base) to a g2core tool (1-based) - next_temperature_tool = tool + 1; + next_temperature_tool = tool; _queue_next_temperature_commands(); // we can ignore the return return (STAT_OK); } + +#ifdef MARLIN_G29_SCRIPT +auto marlin_g29_file = make_xio_flash_file(MARLIN_G29_SCRIPT); +#endif + +/* + * marlin_start_tramming_bed() - called from the gcode parser for G29 + */ +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 +} + + + +/* + * cm_marlin_set_extruder_mode() - M82, M83 (affects MODEL only) + * + * EXTRUDER_MOVES_ABSOLUTE = 0, // M82 + * EXTRUDER_MOVES_RELATIVE, // M83 + * EXTRUDER_MOVES_VOLUMETRIC // Ultimaker2Marlin + */ + +stat_t cm_marlin_set_extruder_mode(const uint8_t mode) +{ + cm.gmx.extruder_mode = (cmExtruderMode)mode; + return (STAT_OK); +} + + +/* + * marlin_set_fan_speed() - M106, M107 + * + */ + +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 += sprintf(str, "{out4:%.2f}", (speed < 1.0) ? speed : (speed / 255.0)); + cm_json_command(buffer); + + return (STAT_OK); +} + + +/* + * marlin_disable_motors() - M84 + * + */ + +stat_t marlin_disable_motors() +{ + char buffer[128]; + char *str = buffer; + + // TODO: support other parameters + strncpy(str, "{md:0}", 6); + cm_json_command(buffer); + + return (STAT_OK); +} #endif // MARLIN_COMPAT_ENABLED == true diff --git a/g2core/marlin_compatibility.h b/g2core/marlin_compatibility.h index 7ad35fc4..36a031a7 100644 --- a/g2core/marlin_compatibility.h +++ b/g2core/marlin_compatibility.h @@ -28,15 +28,24 @@ * Global Scope Functions */ +// gcode parsing and fack stk500v2 stat_t marlin_verify_checksum(char *str); bool marlin_handle_fake_stk500(char *str); +// gcode handling +stat_t marlin_start_tramming_bed(); //G29 stat_t marlin_request_temperature_report(); // M105 stat_t marlin_request_position_report(); // M114 stat_t marlin_set_temperature(uint8_t tool, float temperature, bool wait); // M104, M109, M140, M190 +// response handler (primarily just prints "ok") void marlin_response(const stat_t status, char *buf); +// controller loop callback stat_t marlin_callback(); +stat_t cm_marlin_set_extruder_mode(const uint8_t mode); // M82, M82 +stat_t marlin_set_fan_speed(const uint8_t fan, float speed); // M106, M107 + +stat_t marlin_disable_motors(); // M84 #endif // End of include guard: MARLIN_COMPAT_H_ONCE diff --git a/g2core/planner.cpp b/g2core/planner.cpp index 057010c2..efec41f4 100644 --- a/g2core/planner.cpp +++ b/g2core/planner.cpp @@ -311,7 +311,7 @@ stat_t mp_runtime_command(mpBuf_t *bf) /************************************************************************* * mp_json_command() - queue a json command - * _exec_json_command() - execute json string + * _exec_json_command() - execute json string (from exec system) */ stat_t mp_json_command(char *json_string) @@ -334,6 +334,14 @@ static void _exec_json_command(float *value, bool *flag) jc.free_buffer(); } +/************************************************************************* + * mp_json_command_immediate() - execute a json command with response suppressed + */ + +stat_t mp_json_command_immediate(char *json_string) +{ + return json_parser(json_string); +} /************************************************************************* * mp_json_wait() - queue a json wait command diff --git a/g2core/planner.h b/g2core/planner.h index dcc14f53..7ccadb57 100644 --- a/g2core/planner.h +++ b/g2core/planner.h @@ -537,6 +537,7 @@ stat_t mp_runtime_command(mpBuf_t *bf); stat_t mp_json_command(char *json_string); stat_t mp_json_wait(char *json_string); +stat_t mp_json_command_immediate(char *json_string); stat_t mp_dwell(const float seconds); void mp_end_dwell(void); diff --git a/g2core/report.cpp b/g2core/report.cpp index 02748634..6a46ae05 100644 --- a/g2core/report.cpp +++ b/g2core/report.cpp @@ -111,8 +111,11 @@ void rpt_print_loading_configs_message(void) void rpt_print_system_ready_message(void) { -#warning DEAL WITH STARTUP MESSAGE - //_startup_helper(STAT_OK, "SYSTEM READY"); +#if MARLIN_COMPAT_ENABLED == true + #warning DEAL WITH STARTUP MESSAGE +#else + _startup_helper(STAT_OK, "SYSTEM READY"); +#endif if (cs.comm_mode == TEXT_MODE) { text_response(STAT_OK, (char *)"");}// prompt } diff --git a/g2core/settings/settings_Printrbot_Simple_1608.h b/g2core/settings/settings_Printrbot_Simple_1608.h index ccc3181d..0e68c25f 100644 --- a/g2core/settings/settings_Printrbot_Simple_1608.h +++ b/g2core/settings/settings_Printrbot_Simple_1608.h @@ -87,6 +87,23 @@ #define GCODE_DEFAULT_PATH_CONTROL PATH_CONTINUOUS #define GCODE_DEFAULT_DISTANCE_MODE ABSOLUTE_DISTANCE_MODE +#define MARLIN_G29_SCRIPT \ + "(MSG Tramming started)\n" \ + "M100 ({\"_leds\":3})\n" \ + "G1 X0 Y145 Z6 F20000\n" \ + "G38.2 Z-10 F200\n" \ + "G1 Z5 F20000\n" \ + "M100 ({\"_leds\":5})\n" \ + "G1 X210 Y65 F20000\n" \ + "G38.2 Z-10 F200\n" \ + "G1 Z5 F20000\n" \ + "M100 ({\"_leds\":6})\n" \ + "G1 X0 Y10 F20000\n" \ + "G38.2 Z-10 F200\n" \ + "G1 Z5 F20000\n" \ + "M100 ({\"_leds\":3})\n" \ + "M100 ({\"tram\":1})" \ + "(MSG Tramming completed)\n" // *** motor settings ************************************************************************************ diff --git a/g2core/temperature.cpp b/g2core/temperature.cpp index a3666df7..864c5642 100755 --- a/g2core/temperature.cpp +++ b/g2core/temperature.cpp @@ -365,7 +365,10 @@ struct PID { if (_rise_time_timeout.isPast()) { if (input < _rise_time_checkpoint) { // FAILURE!! - cm_alarm(STAT_TEMPERATURE_CONTROL_ERROR, "Heater temperature failed to rise fast enough."); + char buffer[128]; + char *str = buffer; + str += sprintf(str, "Heater temperature failed to rise fast enough. At: %f Set: %f", input, _set_point); + cm_alarm(STAT_TEMPERATURE_CONTROL_ERROR, buffer); _set_point = 0; _rise_time_timeout.clear(); return -1; diff --git a/g2core/util.h b/g2core/util.h index a662463c..463569af 100644 --- a/g2core/util.h +++ b/g2core/util.h @@ -173,6 +173,22 @@ inline T avg(const T a,const T b) {return (a+b)/2; } #define M_SQRT3 (1.73205080756888) #endif +// Fraction part +constexpr float c_atof_frac_(char *&p_, float v_, float m_) { + return ((*p_ >= '0') && (*p_ <= '9')) ? (v_ = ((v_) + ((*p_) - '0') * m_), c_atof_frac_(++p_, v_, m_ / 10.0)) : v_; +} + +// Integer part +template +constexpr float c_atof_int_(char *&p_, int_type v_) { + return (*p_ == '.') + ? (float)(v_) + c_atof_frac_(++p_, 0, 1.0 / 10.0) + : (((*p_ >= '0') && (*p_ <= '9')) ? ((v_ = ((*p_) - '0') + (v_ * 10)), c_atof_int_(++p_, v_)) : v_); +} + +// Start portion +constexpr float c_atof(char *&p_) { return (*p_ == '-') ? (c_atof_int_(++p_, 0) * -1.0) : (c_atof_int_(p_, 0)); } + // It's assumed that the string buffer contains at lest count_ non-\0 chars //constexpr int c_strreverse(char * const t, const int count_, char hold = 0) { diff --git a/g2core/xio.cpp b/g2core/xio.cpp index 92227ded..55a7ff22 100755 --- a/g2core/xio.cpp +++ b/g2core/xio.cpp @@ -640,15 +640,17 @@ struct LineRXBuffer : RXBuffer<_size, owner_type, char> { char c = _data[_scan_offset]; - if (c == 0) { + +#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::Done) && (c == 0)) { _debug_trap("scan ran into NULL"); flush(); // consider the connection and all data trashed 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) { @@ -672,6 +674,7 @@ struct LineRXBuffer : RXBuffer<_size, owner_type, char> { else if ((c == '{') || (c == '\n') || (c == '\r') || (c == 'G') || (c == 'M')) { // jump out of bootloader mode _stk_parser_state = STK500V2_State::Done; + _read_offset = _scan_offset; continue; } @@ -690,6 +693,7 @@ struct LineRXBuffer : RXBuffer<_size, owner_type, char> { } else { // end-of-header marker was corrupt, start over _stk_packet_data_length = 0; + _read_offset = _scan_offset; _stk_parser_state = STK500V2_State::Start; } } else if (_stk_parser_state == STK500V2_State::Data) { @@ -706,9 +710,21 @@ struct LineRXBuffer : RXBuffer<_size, owner_type, char> { // we will use the "control" return machanism to handle this // since controls don't have to be \r\n-terminated is_control = true; + ends_line = true; + + // this line is complete, reset the state engine + _stk_parser_state = STK500V2_State::Start; } } else +#else + + if (c == 0) { + _debug_trap("scan ran into NULL"); + flush(); // consider the connection and all data trashed + return false; + } + #endif // Look for line endings if (c == '\r' || c == '\n') { @@ -1237,6 +1253,12 @@ struct xioFlashFileDeviceWrapper : xioDeviceWrapperBase { // describes a devi _current_file = nullptr; } + bool flushToCommand() final { + // the end of the file is the next "command" + _current_file = nullptr; + return false; + } + int16_t write(const char *buffer, int16_t len) final { return -1; }