|
|
|
@@ -97,9 +97,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
gc_parser_flags |= GC_PARSER_JOG_MOTION;
|
|
|
|
|
gc_block.modal.motion = MOTION_MODE_LINEAR;
|
|
|
|
|
gc_block.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN;
|
|
|
|
|
#ifdef USE_LINE_NUMBERS
|
|
|
|
|
#ifdef USE_LINE_NUMBERS
|
|
|
|
|
gc_block.values.n = JOG_LINE_NUMBER; // Initialize default line number reported during jog.
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------------
|
|
|
|
@@ -114,16 +114,23 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
float value;
|
|
|
|
|
uint8_t int_value = 0;
|
|
|
|
|
uint16_t mantissa = 0;
|
|
|
|
|
if (gc_parser_flags & GC_PARSER_JOG_MOTION) { char_counter = 3; } // Start parsing after `$J=`
|
|
|
|
|
else { char_counter = 0; }
|
|
|
|
|
if (gc_parser_flags & GC_PARSER_JOG_MOTION) {
|
|
|
|
|
char_counter = 3; // Start parsing after `$J=`
|
|
|
|
|
} else {
|
|
|
|
|
char_counter = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (line[char_counter] != 0) { // Loop until no more g-code words in line.
|
|
|
|
|
|
|
|
|
|
// Import the next g-code word, expecting a letter followed by a value. Otherwise, error out.
|
|
|
|
|
letter = line[char_counter];
|
|
|
|
|
if((letter < 'A') || (letter > 'Z')) { FAIL(STATUS_EXPECTED_COMMAND_LETTER); } // [Expected word letter]
|
|
|
|
|
if((letter < 'A') || (letter > 'Z')) {
|
|
|
|
|
FAIL(STATUS_EXPECTED_COMMAND_LETTER); // [Expected word letter]
|
|
|
|
|
}
|
|
|
|
|
char_counter++;
|
|
|
|
|
if (!read_float(line, &char_counter, &value)) { FAIL(STATUS_BAD_NUMBER_FORMAT); } // [Expected word value]
|
|
|
|
|
if (!read_float(line, &char_counter, &value)) {
|
|
|
|
|
FAIL(STATUS_BAD_NUMBER_FORMAT); // [Expected word value]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert values to smaller uint8 significand and mantissa values for parsing this word.
|
|
|
|
|
// NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more
|
|
|
|
@@ -146,36 +153,49 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
case 'G':
|
|
|
|
|
// Determine 'G' command and its modal group
|
|
|
|
|
switch(int_value) {
|
|
|
|
|
case 10: case 28: case 30: case 92:
|
|
|
|
|
case 10:
|
|
|
|
|
case 28:
|
|
|
|
|
case 30:
|
|
|
|
|
case 92:
|
|
|
|
|
// Check for G10/28/30/92 being called with G0/1/2/3/38 on same block.
|
|
|
|
|
// * G43.1 is also an axis command but is not explicitly defined this way.
|
|
|
|
|
if (mantissa == 0) { // Ignore G28.1, G30.1, and G92.1
|
|
|
|
|
if (axis_command) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict]
|
|
|
|
|
if (axis_command) {
|
|
|
|
|
FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict]
|
|
|
|
|
}
|
|
|
|
|
axis_command = AXIS_COMMAND_NON_MODAL;
|
|
|
|
|
}
|
|
|
|
|
// No break. Continues to next line.
|
|
|
|
|
case 4: case 53:
|
|
|
|
|
case 4:
|
|
|
|
|
case 53:
|
|
|
|
|
word_bit = MODAL_GROUP_G0;
|
|
|
|
|
gc_block.non_modal_command = int_value;
|
|
|
|
|
if ((int_value == 28) || (int_value == 30) || (int_value == 92)) {
|
|
|
|
|
if (!((mantissa == 0) || (mantissa == 10))) { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); }
|
|
|
|
|
if (!((mantissa == 0) || (mantissa == 10))) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND);
|
|
|
|
|
}
|
|
|
|
|
gc_block.non_modal_command += mantissa;
|
|
|
|
|
mantissa = 0; // Set to zero to indicate valid non-integer G command.
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 0: case 1: case 2: case 3:
|
|
|
|
|
case 0:
|
|
|
|
|
case 1:
|
|
|
|
|
case 2:
|
|
|
|
|
case 3:
|
|
|
|
|
#ifdef PROBE_PIN //only allow G38 "Probe" commands if a probe pin is defined.
|
|
|
|
|
case 38:
|
|
|
|
|
#endif
|
|
|
|
|
// Check for G0/1/2/3/38 being called with G10/28/30/92 on same block.
|
|
|
|
|
// * G43.1 is also an axis command but is not explicitly defined this way.
|
|
|
|
|
if (axis_command) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict]
|
|
|
|
|
if (axis_command) {
|
|
|
|
|
FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); // [Axis word/command conflict]
|
|
|
|
|
}
|
|
|
|
|
axis_command = AXIS_COMMAND_MOTION_MODE;
|
|
|
|
|
// No break. Continues to next line.
|
|
|
|
|
case 80:
|
|
|
|
|
word_bit = MODAL_GROUP_G1;
|
|
|
|
|
gc_block.modal.motion = int_value;
|
|
|
|
|
if (int_value == 38){
|
|
|
|
|
if (int_value == 38) {
|
|
|
|
|
if (!((mantissa == 20) || (mantissa == 30) || (mantissa == 40) || (mantissa == 50))) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G38.x command]
|
|
|
|
|
}
|
|
|
|
@@ -183,26 +203,33 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
mantissa = 0; // Set to zero to indicate valid non-integer G command.
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 17: case 18: case 19:
|
|
|
|
|
case 17:
|
|
|
|
|
case 18:
|
|
|
|
|
case 19:
|
|
|
|
|
word_bit = MODAL_GROUP_G2;
|
|
|
|
|
gc_block.modal.plane_select = int_value - 17;
|
|
|
|
|
break;
|
|
|
|
|
case 90: case 91:
|
|
|
|
|
case 90:
|
|
|
|
|
case 91:
|
|
|
|
|
if (mantissa == 0) {
|
|
|
|
|
word_bit = MODAL_GROUP_G3;
|
|
|
|
|
gc_block.modal.distance = int_value - 90;
|
|
|
|
|
} else {
|
|
|
|
|
word_bit = MODAL_GROUP_G4;
|
|
|
|
|
if ((mantissa != 10) || (int_value == 90)) { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [G90.1 not supported]
|
|
|
|
|
if ((mantissa != 10) || (int_value == 90)) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G90.1 not supported]
|
|
|
|
|
}
|
|
|
|
|
mantissa = 0; // Set to zero to indicate valid non-integer G command.
|
|
|
|
|
// Otherwise, arc IJK incremental mode is default. G91.1 does nothing.
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 93: case 94:
|
|
|
|
|
case 93:
|
|
|
|
|
case 94:
|
|
|
|
|
word_bit = MODAL_GROUP_G5;
|
|
|
|
|
gc_block.modal.feed_rate = 94 - int_value;
|
|
|
|
|
break;
|
|
|
|
|
case 20: case 21:
|
|
|
|
|
case 20:
|
|
|
|
|
case 21:
|
|
|
|
|
word_bit = MODAL_GROUP_G6;
|
|
|
|
|
gc_block.modal.units = 21 - int_value;
|
|
|
|
|
break;
|
|
|
|
@@ -212,84 +239,125 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// to support G40 commands that often appear in g-code program headers to setup defaults.
|
|
|
|
|
// gc_block.modal.cutter_comp = CUTTER_COMP_DISABLE; // G40
|
|
|
|
|
break;
|
|
|
|
|
case 43: case 49:
|
|
|
|
|
case 43:
|
|
|
|
|
case 49:
|
|
|
|
|
word_bit = MODAL_GROUP_G8;
|
|
|
|
|
// NOTE: The NIST g-code standard vaguely states that when a tool length offset is changed,
|
|
|
|
|
// there cannot be any axis motion or coordinate offsets updated. Meaning G43, G43.1, and G49
|
|
|
|
|
// all are explicit axis commands, regardless if they require axis words or not.
|
|
|
|
|
if (axis_command) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict] }
|
|
|
|
|
if (axis_command) {
|
|
|
|
|
FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT);
|
|
|
|
|
} // [Axis word/command conflict] }
|
|
|
|
|
axis_command = AXIS_COMMAND_TOOL_LENGTH_OFFSET;
|
|
|
|
|
if (int_value == 49) { // G49
|
|
|
|
|
gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_CANCEL;
|
|
|
|
|
} else if (mantissa == 10) { // G43.1
|
|
|
|
|
gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC;
|
|
|
|
|
} else { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [Unsupported G43.x command]
|
|
|
|
|
} else {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G43.x command]
|
|
|
|
|
}
|
|
|
|
|
mantissa = 0; // Set to zero to indicate valid non-integer G command.
|
|
|
|
|
break;
|
|
|
|
|
case 54: case 55: case 56: case 57: case 58: case 59:
|
|
|
|
|
case 54:
|
|
|
|
|
case 55:
|
|
|
|
|
case 56:
|
|
|
|
|
case 57:
|
|
|
|
|
case 58:
|
|
|
|
|
case 59:
|
|
|
|
|
// NOTE: G59.x are not supported. (But their int_values would be 60, 61, and 62.)
|
|
|
|
|
word_bit = MODAL_GROUP_G12;
|
|
|
|
|
gc_block.modal.coord_select = int_value - 54; // Shift to array indexing.
|
|
|
|
|
break;
|
|
|
|
|
case 61:
|
|
|
|
|
word_bit = MODAL_GROUP_G13;
|
|
|
|
|
if (mantissa != 0) { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [G61.1 not supported]
|
|
|
|
|
if (mantissa != 0) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G61.1 not supported]
|
|
|
|
|
}
|
|
|
|
|
// gc_block.modal.control = CONTROL_MODE_EXACT_PATH; // G61
|
|
|
|
|
break;
|
|
|
|
|
default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command]
|
|
|
|
|
default:
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command]
|
|
|
|
|
}
|
|
|
|
|
if (mantissa > 0) {
|
|
|
|
|
FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [Unsupported or invalid Gxx.x command]
|
|
|
|
|
}
|
|
|
|
|
if (mantissa > 0) { FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); } // [Unsupported or invalid Gxx.x command]
|
|
|
|
|
// Check for more than one command per modal group violations in the current block
|
|
|
|
|
// NOTE: Variable 'word_bit' is always assigned, if the command is valid.
|
|
|
|
|
if ( bit_istrue(command_words,bit(word_bit)) ) { FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); }
|
|
|
|
|
if ( bit_istrue(command_words,bit(word_bit)) ) {
|
|
|
|
|
FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION);
|
|
|
|
|
}
|
|
|
|
|
command_words |= bit(word_bit);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'M':
|
|
|
|
|
|
|
|
|
|
// Determine 'M' command and its modal group
|
|
|
|
|
if (mantissa > 0) { FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); } // [No Mxx.x commands]
|
|
|
|
|
if (mantissa > 0) {
|
|
|
|
|
FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); // [No Mxx.x commands]
|
|
|
|
|
}
|
|
|
|
|
switch(int_value) {
|
|
|
|
|
case 0: case 1: case 2: case 30:
|
|
|
|
|
case 0:
|
|
|
|
|
case 1:
|
|
|
|
|
case 2:
|
|
|
|
|
case 30:
|
|
|
|
|
word_bit = MODAL_GROUP_M4;
|
|
|
|
|
switch(int_value) {
|
|
|
|
|
case 0: gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED; break; // Program pause
|
|
|
|
|
case 1: break; // Optional stop not supported. Ignore.
|
|
|
|
|
default: gc_block.modal.program_flow = int_value; // Program end and reset
|
|
|
|
|
case 0:
|
|
|
|
|
gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED;
|
|
|
|
|
break; // Program pause
|
|
|
|
|
case 1:
|
|
|
|
|
break; // Optional stop not supported. Ignore.
|
|
|
|
|
default:
|
|
|
|
|
gc_block.modal.program_flow = int_value; // Program end and reset
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3: case 4: case 5:
|
|
|
|
|
case 3:
|
|
|
|
|
case 4:
|
|
|
|
|
case 5:
|
|
|
|
|
word_bit = MODAL_GROUP_M7;
|
|
|
|
|
switch(int_value) {
|
|
|
|
|
case 3:
|
|
|
|
|
gc_block.modal.spindle = SPINDLE_ENABLE_CW;
|
|
|
|
|
break;
|
|
|
|
|
case 4: // Supported if SPINDLE_DIR_PIN is defined or laser mode is on.
|
|
|
|
|
#ifndef SPINDLE_DIR_PIN
|
|
|
|
|
#ifndef SPINDLE_DIR_PIN
|
|
|
|
|
// if laser mode is not on then this is an unsupported command
|
|
|
|
|
if bit_isfalse(settings.flags,BITFLAG_LASER_MODE) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
gc_block.modal.spindle = SPINDLE_ENABLE_CCW;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
gc_block.modal.spindle = SPINDLE_DISABLE; break;
|
|
|
|
|
gc_block.modal.spindle = SPINDLE_DISABLE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 7: case 8: case 9:
|
|
|
|
|
case 6: // too change
|
|
|
|
|
grbl_send(CLIENT_ALL, "[MSG:Tool Change]\r\n");
|
|
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
case 8:
|
|
|
|
|
case 9:
|
|
|
|
|
word_bit = MODAL_GROUP_M8;
|
|
|
|
|
switch(int_value) {
|
|
|
|
|
#ifdef COOLANT_MIST_PIN
|
|
|
|
|
case 7: gc_block.modal.coolant = COOLANT_MIST_ENABLE; break;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef COOLANT_FLOOD_PIN
|
|
|
|
|
case 8: gc_block.modal.coolant = COOLANT_FLOOD_ENABLE; break;
|
|
|
|
|
#endif
|
|
|
|
|
case 9: gc_block.modal.coolant = COOLANT_DISABLE; break;
|
|
|
|
|
default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command]
|
|
|
|
|
#ifdef COOLANT_MIST_PIN
|
|
|
|
|
case 7:
|
|
|
|
|
gc_block.modal.coolant = COOLANT_MIST_ENABLE;
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef COOLANT_FLOOD_PIN
|
|
|
|
|
case 8:
|
|
|
|
|
gc_block.modal.coolant = COOLANT_FLOOD_ENABLE;
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
case 9:
|
|
|
|
|
gc_block.modal.coolant = COOLANT_DISABLE;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command]
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
@@ -298,7 +366,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
|
|
|
|
|
// Check for more than one command per modal group violations in the current block
|
|
|
|
|
// NOTE: Variable 'word_bit' is always assigned, if the command is valid.
|
|
|
|
|
if ( bit_istrue(command_words,bit(word_bit)) ) { FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); }
|
|
|
|
|
if ( bit_istrue(command_words,bit(word_bit)) ) {
|
|
|
|
|
FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION);
|
|
|
|
|
}
|
|
|
|
|
command_words |= bit(word_bit);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@@ -308,45 +378,108 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
/* Non-Command Words: This initial parsing phase only checks for repeats of the remaining
|
|
|
|
|
legal g-code words and stores their value. Error-checking is performed later since some
|
|
|
|
|
words (I,J,K,L,P,R) have multiple connotations and/or depend on the issued commands. */
|
|
|
|
|
switch(letter){
|
|
|
|
|
#ifdef A_AXIS
|
|
|
|
|
case 'A': word_bit = WORD_A; gc_block.values.xyz[A_AXIS] = value; axis_words |= (1<<A_AXIS); break;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef B_AXIS
|
|
|
|
|
case 'B': word_bit = WORD_B; gc_block.values.xyz[B_AXIS] = value; axis_words |= (1<<B_AXIS); break;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef C_AXIS
|
|
|
|
|
case 'C': word_bit = WORD_C; gc_block.values.xyz[C_AXIS] = value; axis_words |= (1<<C_AXIS); break;
|
|
|
|
|
#endif
|
|
|
|
|
switch(letter) {
|
|
|
|
|
#ifdef A_AXIS
|
|
|
|
|
case 'A':
|
|
|
|
|
word_bit = WORD_A;
|
|
|
|
|
gc_block.values.xyz[A_AXIS] = value;
|
|
|
|
|
axis_words |= (1<<A_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef B_AXIS
|
|
|
|
|
case 'B':
|
|
|
|
|
word_bit = WORD_B;
|
|
|
|
|
gc_block.values.xyz[B_AXIS] = value;
|
|
|
|
|
axis_words |= (1<<B_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef C_AXIS
|
|
|
|
|
case 'C':
|
|
|
|
|
word_bit = WORD_C;
|
|
|
|
|
gc_block.values.xyz[C_AXIS] = value;
|
|
|
|
|
axis_words |= (1<<C_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
// case 'D': // Not supported
|
|
|
|
|
case 'F': word_bit = WORD_F; gc_block.values.f = value; break;
|
|
|
|
|
case 'F':
|
|
|
|
|
word_bit = WORD_F;
|
|
|
|
|
gc_block.values.f = value;
|
|
|
|
|
break;
|
|
|
|
|
// case 'H': // Not supported
|
|
|
|
|
case 'I': word_bit = WORD_I; gc_block.values.ijk[X_AXIS] = value; ijk_words |= (1<<X_AXIS); break;
|
|
|
|
|
case 'J': word_bit = WORD_J; gc_block.values.ijk[Y_AXIS] = value; ijk_words |= (1<<Y_AXIS); break;
|
|
|
|
|
case 'K': word_bit = WORD_K; gc_block.values.ijk[Z_AXIS] = value; ijk_words |= (1<<Z_AXIS); break;
|
|
|
|
|
case 'L': word_bit = WORD_L; gc_block.values.l = int_value; break;
|
|
|
|
|
case 'N': word_bit = WORD_N; gc_block.values.n = trunc(value); break;
|
|
|
|
|
case 'P': word_bit = WORD_P; gc_block.values.p = value; break;
|
|
|
|
|
case 'I':
|
|
|
|
|
word_bit = WORD_I;
|
|
|
|
|
gc_block.values.ijk[X_AXIS] = value;
|
|
|
|
|
ijk_words |= (1<<X_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
case 'J':
|
|
|
|
|
word_bit = WORD_J;
|
|
|
|
|
gc_block.values.ijk[Y_AXIS] = value;
|
|
|
|
|
ijk_words |= (1<<Y_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
case 'K':
|
|
|
|
|
word_bit = WORD_K;
|
|
|
|
|
gc_block.values.ijk[Z_AXIS] = value;
|
|
|
|
|
ijk_words |= (1<<Z_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
case 'L':
|
|
|
|
|
word_bit = WORD_L;
|
|
|
|
|
gc_block.values.l = int_value;
|
|
|
|
|
break;
|
|
|
|
|
case 'N':
|
|
|
|
|
word_bit = WORD_N;
|
|
|
|
|
gc_block.values.n = trunc(value);
|
|
|
|
|
break;
|
|
|
|
|
case 'P':
|
|
|
|
|
word_bit = WORD_P;
|
|
|
|
|
gc_block.values.p = value;
|
|
|
|
|
break;
|
|
|
|
|
// NOTE: For certain commands, P value must be an integer, but none of these commands are supported.
|
|
|
|
|
// case 'Q': // Not supported
|
|
|
|
|
case 'R': word_bit = WORD_R; gc_block.values.r = value; break;
|
|
|
|
|
case 'S': word_bit = WORD_S; gc_block.values.s = value; break;
|
|
|
|
|
case 'T': word_bit = WORD_T;
|
|
|
|
|
if(value > MAX_TOOL_NUMBER) { FAIL(STATUS_GCODE_MAX_VALUE_EXCEEDED); }
|
|
|
|
|
case 'R':
|
|
|
|
|
word_bit = WORD_R;
|
|
|
|
|
gc_block.values.r = value;
|
|
|
|
|
break;
|
|
|
|
|
case 'S':
|
|
|
|
|
word_bit = WORD_S;
|
|
|
|
|
gc_block.values.s = value;
|
|
|
|
|
break;
|
|
|
|
|
case 'T':
|
|
|
|
|
word_bit = WORD_T;
|
|
|
|
|
if(value > MAX_TOOL_NUMBER) {
|
|
|
|
|
FAIL(STATUS_GCODE_MAX_VALUE_EXCEEDED);
|
|
|
|
|
}
|
|
|
|
|
grbl_sendf(CLIENT_ALL, "[MSG:Tool No: %d]\r\n", int_value);
|
|
|
|
|
gc_block.values.t = int_value;
|
|
|
|
|
break;
|
|
|
|
|
case 'X': word_bit = WORD_X; gc_block.values.xyz[X_AXIS] = value; axis_words |= (1<<X_AXIS); break;
|
|
|
|
|
case 'Y': word_bit = WORD_Y; gc_block.values.xyz[Y_AXIS] = value; axis_words |= (1<<Y_AXIS); break;
|
|
|
|
|
case 'Z': word_bit = WORD_Z; gc_block.values.xyz[Z_AXIS] = value; axis_words |= (1<<Z_AXIS); break;
|
|
|
|
|
default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND);
|
|
|
|
|
case 'X':
|
|
|
|
|
word_bit = WORD_X;
|
|
|
|
|
gc_block.values.xyz[X_AXIS] = value;
|
|
|
|
|
axis_words |= (1<<X_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
case 'Y':
|
|
|
|
|
word_bit = WORD_Y;
|
|
|
|
|
gc_block.values.xyz[Y_AXIS] = value;
|
|
|
|
|
axis_words |= (1<<Y_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
case 'Z':
|
|
|
|
|
word_bit = WORD_Z;
|
|
|
|
|
gc_block.values.xyz[Z_AXIS] = value;
|
|
|
|
|
axis_words |= (1<<Z_AXIS);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: Variable 'word_bit' is always assigned, if the non-command letter is valid.
|
|
|
|
|
if (bit_istrue(value_words,bit(word_bit))) { FAIL(STATUS_GCODE_WORD_REPEATED); } // [Word repeated]
|
|
|
|
|
if (bit_istrue(value_words,bit(word_bit))) {
|
|
|
|
|
FAIL(STATUS_GCODE_WORD_REPEATED); // [Word repeated]
|
|
|
|
|
}
|
|
|
|
|
// Check for invalid negative values for words F, N, P, T, and S.
|
|
|
|
|
// NOTE: Negative value check is done here simply for code-efficiency.
|
|
|
|
|
if ( bit(word_bit) & (bit(WORD_F)|bit(WORD_N)|bit(WORD_P)|bit(WORD_T)|bit(WORD_S)) ) {
|
|
|
|
|
if (value < 0.0) { FAIL(STATUS_NEGATIVE_VALUE); } // [Word value cannot be negative]
|
|
|
|
|
if (value < 0.0) {
|
|
|
|
|
FAIL(STATUS_NEGATIVE_VALUE); // [Word value cannot be negative]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
value_words |= bit(word_bit); // Flag to indicate parameter assigned.
|
|
|
|
|
|
|
|
|
@@ -386,13 +519,17 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// Determine implicit axis command conditions. Axis words have been passed, but no explicit axis
|
|
|
|
|
// command has been sent. If so, set axis command to current motion mode.
|
|
|
|
|
if (axis_words) {
|
|
|
|
|
if (!axis_command) { axis_command = AXIS_COMMAND_MOTION_MODE; } // Assign implicit motion-mode
|
|
|
|
|
if (!axis_command) {
|
|
|
|
|
axis_command = AXIS_COMMAND_MOTION_MODE; // Assign implicit motion-mode
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for valid line number N value.
|
|
|
|
|
if (bit_istrue(value_words,bit(WORD_N))) {
|
|
|
|
|
// Line number value cannot be less than zero (done) or greater than max line number.
|
|
|
|
|
if (gc_block.values.n > MAX_LINE_NUMBER) { FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); } // [Exceeds max line number]
|
|
|
|
|
if (gc_block.values.n > MAX_LINE_NUMBER) {
|
|
|
|
|
FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); // [Exceeds max line number]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// bit_false(value_words,bit(WORD_N)); // NOTE: Single-meaning value word. Set at end of error-checking.
|
|
|
|
|
|
|
|
|
@@ -409,14 +546,20 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// is not defined after switching to G94 from G93.
|
|
|
|
|
// NOTE: For jogging, ignore prior feed rate mode. Enforce G94 and check for required F word.
|
|
|
|
|
if (gc_parser_flags & GC_PARSER_JOG_MOTION) {
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_F))) { FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); }
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) { gc_block.values.f *= MM_PER_INCH; }
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_F))) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE);
|
|
|
|
|
}
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) {
|
|
|
|
|
gc_block.values.f *= MM_PER_INCH;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (gc_block.modal.feed_rate == FEED_RATE_MODE_INVERSE_TIME) { // = G93
|
|
|
|
|
// NOTE: G38 can also operate in inverse time, but is undefined as an error. Missing F word check added here.
|
|
|
|
|
if (axis_command == AXIS_COMMAND_MOTION_MODE) {
|
|
|
|
|
if ((gc_block.modal.motion != MOTION_MODE_NONE) || (gc_block.modal.motion != MOTION_MODE_SEEK)) {
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_F))) { FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); } // [F word missing]
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_F))) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [F word missing]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// NOTE: It seems redundant to check for an F word to be passed after switching from G94 to G93. We would
|
|
|
|
@@ -435,7 +578,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// - In units per mm mode: If F word passed, ensure value is in mm/min, otherwise push last state value.
|
|
|
|
|
if (gc_state.modal.feed_rate == FEED_RATE_MODE_UNITS_PER_MIN) { // Last state is also G94
|
|
|
|
|
if (bit_istrue(value_words,bit(WORD_F))) {
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) { gc_block.values.f *= MM_PER_INCH; }
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) {
|
|
|
|
|
gc_block.values.f *= MM_PER_INCH;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
gc_block.values.f = gc_state.feed_rate; // Push last state feed rate
|
|
|
|
|
}
|
|
|
|
@@ -445,7 +590,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// bit_false(value_words,bit(WORD_F)); // NOTE: Single-meaning value word. Set at end of error-checking.
|
|
|
|
|
|
|
|
|
|
// [4. Set spindle speed ]: S is negative (done.)
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_S))) { gc_block.values.s = gc_state.spindle_speed; }
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_S))) {
|
|
|
|
|
gc_block.values.s = gc_state.spindle_speed;
|
|
|
|
|
}
|
|
|
|
|
// bit_false(value_words,bit(WORD_S)); // NOTE: Single-meaning value word. Set at end of error-checking.
|
|
|
|
|
|
|
|
|
|
// [5. Select tool ]: NOT SUPPORTED. Only tracks value. T is negative (done.) Not an integer. Greater than max tool value.
|
|
|
|
@@ -458,7 +605,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
|
|
|
|
|
// [10. Dwell ]: P value missing. P is negative (done.) NOTE: See below.
|
|
|
|
|
if (gc_block.non_modal_command == NON_MODAL_DWELL) {
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_P))) { FAIL(STATUS_GCODE_VALUE_WORD_MISSING); } // [P word missing]
|
|
|
|
|
if (bit_isfalse(value_words,bit(WORD_P))) {
|
|
|
|
|
FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P word missing]
|
|
|
|
|
}
|
|
|
|
|
bit_false(value_words,bit(WORD_P));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -503,7 +652,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// is absent or if any of the other axis words are present.
|
|
|
|
|
if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET ) { // Indicates called in block.
|
|
|
|
|
if (gc_block.modal.tool_length == TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC) {
|
|
|
|
|
if (axis_words ^ (1<<TOOL_LENGTH_OFFSET_AXIS)) { FAIL(STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR); }
|
|
|
|
|
if (axis_words ^ (1<<TOOL_LENGTH_OFFSET_AXIS)) {
|
|
|
|
|
FAIL(STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -515,9 +666,13 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
float block_coord_system[N_AXIS];
|
|
|
|
|
memcpy(block_coord_system,gc_state.coord_system,sizeof(gc_state.coord_system));
|
|
|
|
|
if ( bit_istrue(command_words,bit(MODAL_GROUP_G12)) ) { // Check if called in block
|
|
|
|
|
if (gc_block.modal.coord_select > N_COORDINATE_SYSTEM) { FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); } // [Greater than N sys]
|
|
|
|
|
if (gc_block.modal.coord_select > N_COORDINATE_SYSTEM) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys]
|
|
|
|
|
}
|
|
|
|
|
if (gc_state.modal.coord_select != gc_block.modal.coord_select) {
|
|
|
|
|
if (!(settings_read_coord_data(gc_block.modal.coord_select,block_coord_system))) { FAIL(STATUS_SETTING_READ_FAIL); }
|
|
|
|
|
if (!(settings_read_coord_data(gc_block.modal.coord_select,block_coord_system))) {
|
|
|
|
|
FAIL(STATUS_SETTING_READ_FAIL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -535,24 +690,39 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.)
|
|
|
|
|
// [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to nCoordSys(max 9). Axis words missing.
|
|
|
|
|
// [G10 L20 Errors]: P must be 0 to nCoordSys(max 9). Axis words missing.
|
|
|
|
|
if (!axis_words) { FAIL(STATUS_GCODE_NO_AXIS_WORDS) }; // [No axis words]
|
|
|
|
|
if (bit_isfalse(value_words,((1<<WORD_P)|(1<<WORD_L)))) { FAIL(STATUS_GCODE_VALUE_WORD_MISSING); } // [P/L word missing]
|
|
|
|
|
if (!axis_words) {
|
|
|
|
|
FAIL(STATUS_GCODE_NO_AXIS_WORDS)
|
|
|
|
|
}; // [No axis words]
|
|
|
|
|
if (bit_isfalse(value_words,((1<<WORD_P)|(1<<WORD_L)))) {
|
|
|
|
|
FAIL(STATUS_GCODE_VALUE_WORD_MISSING); // [P/L word missing]
|
|
|
|
|
}
|
|
|
|
|
coord_select = trunc(gc_block.values.p); // Convert p value to int.
|
|
|
|
|
if (coord_select > N_COORDINATE_SYSTEM) { FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); } // [Greater than N sys]
|
|
|
|
|
if (coord_select > N_COORDINATE_SYSTEM) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); // [Greater than N sys]
|
|
|
|
|
}
|
|
|
|
|
if (gc_block.values.l != 20) {
|
|
|
|
|
if (gc_block.values.l == 2) {
|
|
|
|
|
if (bit_istrue(value_words,bit(WORD_R))) { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [G10 L2 R not supported]
|
|
|
|
|
} else { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [Unsupported L]
|
|
|
|
|
if (bit_istrue(value_words,bit(WORD_R))) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [G10 L2 R not supported]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported L]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bit_false(value_words,(bit(WORD_L)|bit(WORD_P)));
|
|
|
|
|
|
|
|
|
|
// Determine coordinate system to change and try to load from EEPROM.
|
|
|
|
|
if (coord_select > 0) { coord_select--; } // Adjust P1-P6 index to EEPROM coordinate data indexing.
|
|
|
|
|
else { coord_select = gc_block.modal.coord_select; } // Index P0 as the active coordinate system
|
|
|
|
|
if (coord_select > 0) {
|
|
|
|
|
coord_select--; // Adjust P1-P6 index to EEPROM coordinate data indexing.
|
|
|
|
|
} else {
|
|
|
|
|
coord_select = gc_block.modal.coord_select; // Index P0 as the active coordinate system
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: Store parameter data in IJK values. By rule, they are not in use with this command.
|
|
|
|
|
// FIXME: Instead of IJK, we'd better use: float vector[N_AXIS]; // [DG]
|
|
|
|
|
if (!settings_read_coord_data(coord_select,gc_block.values.ijk)) { FAIL(STATUS_SETTING_READ_FAIL); } // [EEPROM read fail]
|
|
|
|
|
if (!settings_read_coord_data(coord_select,gc_block.values.ijk)) {
|
|
|
|
|
FAIL(STATUS_SETTING_READ_FAIL); // [EEPROM read fail]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pre-calculate the coordinate data changes.
|
|
|
|
|
for (idx=0; idx<N_AXIS; idx++) { // Axes indices are consistent, so loop may be used.
|
|
|
|
@@ -562,7 +732,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// L20: Update coordinate system axis at current position (with modifiers) with programmed value
|
|
|
|
|
// WPos = MPos - WCS - G92 - TLO -> WCS = MPos - G92 - TLO - WPos
|
|
|
|
|
gc_block.values.ijk[idx] = gc_state.position[idx]-gc_state.coord_offset[idx]-gc_block.values.xyz[idx];
|
|
|
|
|
if (idx == TOOL_LENGTH_OFFSET_AXIS) { gc_block.values.ijk[idx] -= gc_state.tool_length_offset; }
|
|
|
|
|
if (idx == TOOL_LENGTH_OFFSET_AXIS) {
|
|
|
|
|
gc_block.values.ijk[idx] -= gc_state.tool_length_offset;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// L2: Update coordinate system axis to programmed value.
|
|
|
|
|
gc_block.values.ijk[idx] = gc_block.values.xyz[idx];
|
|
|
|
@@ -572,7 +744,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
break;
|
|
|
|
|
case NON_MODAL_SET_COORDINATE_OFFSET:
|
|
|
|
|
// [G92 Errors]: No axis words.
|
|
|
|
|
if (!axis_words) { FAIL(STATUS_GCODE_NO_AXIS_WORDS); } // [No axis words]
|
|
|
|
|
if (!axis_words) {
|
|
|
|
|
FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update axes defined only in block. Offsets current system to defined value. Does not update when
|
|
|
|
|
// active coordinate system is selected, but is still active unless G92.1 disables it.
|
|
|
|
@@ -580,7 +754,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
if (bit_istrue(axis_words,bit(idx)) ) {
|
|
|
|
|
// WPos = MPos - WCS - G92 - TLO -> G92 = MPos - WCS - TLO - WPos
|
|
|
|
|
gc_block.values.xyz[idx] = gc_state.position[idx]-block_coord_system[idx]-gc_block.values.xyz[idx];
|
|
|
|
|
if (idx == TOOL_LENGTH_OFFSET_AXIS) { gc_block.values.xyz[idx] -= gc_state.tool_length_offset; }
|
|
|
|
|
if (idx == TOOL_LENGTH_OFFSET_AXIS) {
|
|
|
|
|
gc_block.values.xyz[idx] -= gc_state.tool_length_offset;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
gc_block.values.xyz[idx] = gc_state.coord_offset[idx];
|
|
|
|
|
}
|
|
|
|
@@ -605,7 +781,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// Apply coordinate offsets based on distance mode.
|
|
|
|
|
if (gc_block.modal.distance == DISTANCE_MODE_ABSOLUTE) {
|
|
|
|
|
gc_block.values.xyz[idx] += block_coord_system[idx] + gc_state.coord_offset[idx];
|
|
|
|
|
if (idx == TOOL_LENGTH_OFFSET_AXIS) { gc_block.values.xyz[idx] += gc_state.tool_length_offset; }
|
|
|
|
|
if (idx == TOOL_LENGTH_OFFSET_AXIS) {
|
|
|
|
|
gc_block.values.xyz[idx] += gc_state.tool_length_offset;
|
|
|
|
|
}
|
|
|
|
|
} else { // Incremental mode
|
|
|
|
|
gc_block.values.xyz[idx] += gc_state.position[idx];
|
|
|
|
|
}
|
|
|
|
@@ -623,14 +801,20 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// Retreive G28/30 go-home position data (in machine coordinates) from EEPROM
|
|
|
|
|
// NOTE: Store parameter data in IJK values. By rule, they are not in use with this command.
|
|
|
|
|
if (gc_block.non_modal_command == NON_MODAL_GO_HOME_0) {
|
|
|
|
|
if (!settings_read_coord_data(SETTING_INDEX_G28,gc_block.values.ijk)) { FAIL(STATUS_SETTING_READ_FAIL); }
|
|
|
|
|
if (!settings_read_coord_data(SETTING_INDEX_G28,gc_block.values.ijk)) {
|
|
|
|
|
FAIL(STATUS_SETTING_READ_FAIL);
|
|
|
|
|
}
|
|
|
|
|
} else { // == NON_MODAL_GO_HOME_1
|
|
|
|
|
if (!settings_read_coord_data(SETTING_INDEX_G30,gc_block.values.ijk)) { FAIL(STATUS_SETTING_READ_FAIL); }
|
|
|
|
|
if (!settings_read_coord_data(SETTING_INDEX_G30,gc_block.values.ijk)) {
|
|
|
|
|
FAIL(STATUS_SETTING_READ_FAIL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (axis_words) {
|
|
|
|
|
// Move only the axes specified in secondary move.
|
|
|
|
|
for (idx=0; idx<N_AXIS; idx++) {
|
|
|
|
|
if (!(axis_words & (1<<idx))) { gc_block.values.ijk[idx] = gc_state.position[idx]; }
|
|
|
|
|
if (!(axis_words & (1<<idx))) {
|
|
|
|
|
gc_block.values.ijk[idx] = gc_state.position[idx];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
axis_command = AXIS_COMMAND_NONE; // Set to none if no intermediate motion.
|
|
|
|
@@ -658,7 +842,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
if (gc_block.modal.motion == MOTION_MODE_NONE) {
|
|
|
|
|
// [G80 Errors]: Axis word are programmed while G80 is active.
|
|
|
|
|
// NOTE: Even non-modal commands or TLO that use axis words will throw this strict error.
|
|
|
|
|
if (axis_words) { FAIL(STATUS_GCODE_AXIS_WORDS_EXIST); } // [No axis words allowed]
|
|
|
|
|
if (axis_words) {
|
|
|
|
|
FAIL(STATUS_GCODE_AXIS_WORDS_EXIST); // [No axis words allowed]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check remaining motion modes, if axis word are implicit (exist and not used by G10/28/30/92), or
|
|
|
|
|
// was explicitly commanded in the g-code block.
|
|
|
|
@@ -667,19 +853,25 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
if (gc_block.modal.motion == MOTION_MODE_SEEK) {
|
|
|
|
|
// [G0 Errors]: Axis letter not configured or without real value (done.)
|
|
|
|
|
// Axis words are optional. If missing, set axis command flag to ignore execution.
|
|
|
|
|
if (!axis_words) { axis_command = AXIS_COMMAND_NONE; }
|
|
|
|
|
if (!axis_words) {
|
|
|
|
|
axis_command = AXIS_COMMAND_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All remaining motion modes (all but G0 and G80), require a valid feed rate value. In units per mm mode,
|
|
|
|
|
// the value must be positive. In inverse time mode, a positive value must be passed with each block.
|
|
|
|
|
} else {
|
|
|
|
|
// Check if feed rate is defined for the motion modes that require it.
|
|
|
|
|
if (gc_block.values.f == 0.0) { FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); } // [Feed rate undefined]
|
|
|
|
|
if (gc_block.values.f == 0.0) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); // [Feed rate undefined]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (gc_block.modal.motion) {
|
|
|
|
|
case MOTION_MODE_LINEAR:
|
|
|
|
|
// [G1 Errors]: Feed rate undefined. Axis letter not configured or without real value.
|
|
|
|
|
// Axis words are optional. If missing, set axis command flag to ignore execution.
|
|
|
|
|
if (!axis_words) { axis_command = AXIS_COMMAND_NONE; }
|
|
|
|
|
if (!axis_words) {
|
|
|
|
|
axis_command = AXIS_COMMAND_NONE;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MOTION_MODE_CW_ARC:
|
|
|
|
|
gc_parser_flags |= GC_PARSER_ARC_IS_CLOCKWISE; // No break intentional.
|
|
|
|
@@ -691,8 +883,12 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// [G2/3 Full-Circle-Mode Errors]: NOT SUPPORTED. Axis words exist. No offsets programmed. P must be an integer.
|
|
|
|
|
// NOTE: Both radius and offsets are required for arc tracing and are pre-computed with the error-checking.
|
|
|
|
|
|
|
|
|
|
if (!axis_words) { FAIL(STATUS_GCODE_NO_AXIS_WORDS); } // [No axis words]
|
|
|
|
|
if (!(axis_words & (bit(axis_0)|bit(axis_1)))) { FAIL(STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE); } // [No axis words in plane]
|
|
|
|
|
if (!axis_words) {
|
|
|
|
|
FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words]
|
|
|
|
|
}
|
|
|
|
|
if (!(axis_words & (bit(axis_0)|bit(axis_1)))) {
|
|
|
|
|
FAIL(STATUS_GCODE_NO_AXIS_WORDS_IN_PLANE); // [No axis words in plane]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the change in position along each selected axis
|
|
|
|
|
float x,y;
|
|
|
|
@@ -701,10 +897,14 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
|
|
|
|
|
if (value_words & bit(WORD_R)) { // Arc Radius Mode
|
|
|
|
|
bit_false(value_words,bit(WORD_R));
|
|
|
|
|
if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Invalid target]
|
|
|
|
|
if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) {
|
|
|
|
|
FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert radius value to proper units.
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) { gc_block.values.r *= MM_PER_INCH; }
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) {
|
|
|
|
|
gc_block.values.r *= MM_PER_INCH;
|
|
|
|
|
}
|
|
|
|
|
/* We need to calculate the center of the circle that has the designated radius and passes
|
|
|
|
|
through both the current position and the target position. This method calculates the following
|
|
|
|
|
set of equations where [x,y] is the vector from current to target position, d == magnitude of
|
|
|
|
@@ -757,12 +957,16 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// than d. If so, the sqrt of a negative number is complex and error out.
|
|
|
|
|
float h_x2_div_d = 4.0 * gc_block.values.r*gc_block.values.r - x*x - y*y;
|
|
|
|
|
|
|
|
|
|
if (h_x2_div_d < 0) { FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); } // [Arc radius error]
|
|
|
|
|
if (h_x2_div_d < 0) {
|
|
|
|
|
FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); // [Arc radius error]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finish computing h_x2_div_d.
|
|
|
|
|
h_x2_div_d = -sqrt(h_x2_div_d)/hypot_f(x,y); // == -(h * 2 / d)
|
|
|
|
|
// Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below)
|
|
|
|
|
if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) { h_x2_div_d = -h_x2_div_d; }
|
|
|
|
|
if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) {
|
|
|
|
|
h_x2_div_d = -h_x2_div_d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The counter clockwise circle lies to the left of the target direction. When offset is positive,
|
|
|
|
|
the left hand circle will be generated - when it is negative the right hand circle is generated.
|
|
|
|
@@ -792,13 +996,17 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
gc_block.values.ijk[axis_1] = 0.5*(y+(x*h_x2_div_d));
|
|
|
|
|
|
|
|
|
|
} else { // Arc Center Format Offset Mode
|
|
|
|
|
if (!(ijk_words & (bit(axis_0)|bit(axis_1)))) { FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); } // [No offsets in plane]
|
|
|
|
|
if (!(ijk_words & (bit(axis_0)|bit(axis_1)))) {
|
|
|
|
|
FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); // [No offsets in plane]
|
|
|
|
|
}
|
|
|
|
|
bit_false(value_words,(bit(WORD_I)|bit(WORD_J)|bit(WORD_K)));
|
|
|
|
|
|
|
|
|
|
// Convert IJK values to proper units.
|
|
|
|
|
if (gc_block.modal.units == UNITS_MODE_INCHES) {
|
|
|
|
|
for (idx=0; idx<N_AXIS; idx++) { // Axes indices are consistent, so loop may be used to save flash space.
|
|
|
|
|
if (ijk_words & bit(idx)) { gc_block.values.ijk[idx] *= MM_PER_INCH; }
|
|
|
|
|
if (ijk_words & bit(idx)) {
|
|
|
|
|
gc_block.values.ijk[idx] *= MM_PER_INCH;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -813,22 +1021,34 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// Compute difference between current location and target radii for final error-checks.
|
|
|
|
|
float delta_r = fabs(target_r-gc_block.values.r);
|
|
|
|
|
if (delta_r > 0.005) {
|
|
|
|
|
if (delta_r > 0.5) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Arc definition error] > 0.5mm
|
|
|
|
|
if (delta_r > (0.001*gc_block.values.r)) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Arc definition error] > 0.005mm AND 0.1% radius
|
|
|
|
|
if (delta_r > 0.5) {
|
|
|
|
|
FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.5mm
|
|
|
|
|
}
|
|
|
|
|
if (delta_r > (0.001*gc_block.values.r)) {
|
|
|
|
|
FAIL(STATUS_GCODE_INVALID_TARGET); // [Arc definition error] > 0.005mm AND 0.1% radius
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MOTION_MODE_PROBE_TOWARD_NO_ERROR: case MOTION_MODE_PROBE_AWAY_NO_ERROR:
|
|
|
|
|
case MOTION_MODE_PROBE_TOWARD_NO_ERROR:
|
|
|
|
|
case MOTION_MODE_PROBE_AWAY_NO_ERROR:
|
|
|
|
|
gc_parser_flags |= GC_PARSER_PROBE_IS_NO_ERROR; // No break intentional.
|
|
|
|
|
case MOTION_MODE_PROBE_TOWARD: case MOTION_MODE_PROBE_AWAY:
|
|
|
|
|
case MOTION_MODE_PROBE_TOWARD:
|
|
|
|
|
case MOTION_MODE_PROBE_AWAY:
|
|
|
|
|
if ((gc_block.modal.motion == MOTION_MODE_PROBE_AWAY) ||
|
|
|
|
|
(gc_block.modal.motion == MOTION_MODE_PROBE_AWAY_NO_ERROR)) { gc_parser_flags |= GC_PARSER_PROBE_IS_AWAY; }
|
|
|
|
|
(gc_block.modal.motion == MOTION_MODE_PROBE_AWAY_NO_ERROR)) {
|
|
|
|
|
gc_parser_flags |= GC_PARSER_PROBE_IS_AWAY;
|
|
|
|
|
}
|
|
|
|
|
// [G38 Errors]: Target is same current. No axis words. Cutter compensation is enabled. Feed rate
|
|
|
|
|
// is undefined. Probe is triggered. NOTE: Probe check moved to probe cycle. Instead of returning
|
|
|
|
|
// an error, it issues an alarm to prevent further motion to the probe. It's also done there to
|
|
|
|
|
// allow the planner buffer to empty and move off the probe trigger before another probing cycle.
|
|
|
|
|
if (!axis_words) { FAIL(STATUS_GCODE_NO_AXIS_WORDS); } // [No axis words]
|
|
|
|
|
if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Invalid target]
|
|
|
|
|
if (!axis_words) {
|
|
|
|
|
FAIL(STATUS_GCODE_NO_AXIS_WORDS); // [No axis words]
|
|
|
|
|
}
|
|
|
|
|
if (isequal_position_vector(gc_state.position, gc_block.values.xyz)) {
|
|
|
|
|
FAIL(STATUS_GCODE_INVALID_TARGET); // [Invalid target]
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -845,8 +1065,12 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
bit_false(value_words,(bit(WORD_N)|bit(WORD_F)|bit(WORD_S)|bit(WORD_T))); // Remove single-meaning value words.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (axis_command) { bit_false(value_words,(bit(WORD_X)|bit(WORD_Y)|bit(WORD_Z)|bit(WORD_A)|bit(WORD_B)|bit(WORD_C))); } // Remove axis words.
|
|
|
|
|
if (value_words) { FAIL(STATUS_GCODE_UNUSED_WORDS); } // [Unused words]
|
|
|
|
|
if (axis_command) {
|
|
|
|
|
bit_false(value_words,(bit(WORD_X)|bit(WORD_Y)|bit(WORD_Z)|bit(WORD_A)|bit(WORD_B)|bit(WORD_C))); // Remove axis words.
|
|
|
|
|
}
|
|
|
|
|
if (value_words) {
|
|
|
|
|
FAIL(STATUS_GCODE_UNUSED_WORDS); // [Unused words]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------------
|
|
|
|
|
STEP 4: EXECUTE!!
|
|
|
|
@@ -866,15 +1090,21 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
if (gc_parser_flags & GC_PARSER_JOG_MOTION) {
|
|
|
|
|
// Only distance and unit modal commands and G53 absolute override command are allowed.
|
|
|
|
|
// NOTE: Feed rate word and axis word checks have already been performed in STEP 3.
|
|
|
|
|
if (command_words & ~(bit(MODAL_GROUP_G3) | bit(MODAL_GROUP_G6) | bit(MODAL_GROUP_G0)) ) { FAIL(STATUS_INVALID_JOG_COMMAND) };
|
|
|
|
|
if (!(gc_block.non_modal_command == NON_MODAL_ABSOLUTE_OVERRIDE || gc_block.non_modal_command == NON_MODAL_NO_ACTION)) { FAIL(STATUS_INVALID_JOG_COMMAND); }
|
|
|
|
|
if (command_words & ~(bit(MODAL_GROUP_G3) | bit(MODAL_GROUP_G6) | bit(MODAL_GROUP_G0)) ) {
|
|
|
|
|
FAIL(STATUS_INVALID_JOG_COMMAND)
|
|
|
|
|
};
|
|
|
|
|
if (!(gc_block.non_modal_command == NON_MODAL_ABSOLUTE_OVERRIDE || gc_block.non_modal_command == NON_MODAL_NO_ACTION)) {
|
|
|
|
|
FAIL(STATUS_INVALID_JOG_COMMAND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize planner data to current spindle and coolant modal state.
|
|
|
|
|
pl_data->spindle_speed = gc_state.spindle_speed;
|
|
|
|
|
plan_data.condition = (gc_state.modal.spindle | gc_state.modal.coolant);
|
|
|
|
|
|
|
|
|
|
uint8_t status = jog_execute(&plan_data, &gc_block);
|
|
|
|
|
if (status == STATUS_OK) { memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); }
|
|
|
|
|
if (status == STATUS_OK) {
|
|
|
|
|
memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz));
|
|
|
|
|
}
|
|
|
|
|
return(status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -912,15 +1142,17 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// [0. Non-specific/common error-checks and miscellaneous setup]:
|
|
|
|
|
// NOTE: If no line number is present, the value is zero.
|
|
|
|
|
gc_state.line_number = gc_block.values.n;
|
|
|
|
|
#ifdef USE_LINE_NUMBERS
|
|
|
|
|
#ifdef USE_LINE_NUMBERS
|
|
|
|
|
pl_data->line_number = gc_state.line_number; // Record data for planner use.
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// [1. Comments feedback ]: NOT SUPPORTED
|
|
|
|
|
|
|
|
|
|
// [2. Set feed rate mode ]:
|
|
|
|
|
gc_state.modal.feed_rate = gc_block.modal.feed_rate;
|
|
|
|
|
if (gc_state.modal.feed_rate) { pl_data->condition |= PL_COND_FLAG_INVERSE_TIME; } // Set condition flag for planner use.
|
|
|
|
|
if (gc_state.modal.feed_rate) {
|
|
|
|
|
pl_data->condition |= PL_COND_FLAG_INVERSE_TIME; // Set condition flag for planner use.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [3. Set feed rate ]:
|
|
|
|
|
gc_state.feed_rate = gc_block.values.f; // Always copy this value. See feed rate error-checking.
|
|
|
|
@@ -929,15 +1161,17 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// [4. Set spindle speed ]:
|
|
|
|
|
if ((gc_state.spindle_speed != gc_block.values.s) || bit_istrue(gc_parser_flags,GC_PARSER_LASER_FORCE_SYNC)) {
|
|
|
|
|
if (gc_state.modal.spindle != SPINDLE_DISABLE) {
|
|
|
|
|
#ifdef VARIABLE_SPINDLE
|
|
|
|
|
#ifdef VARIABLE_SPINDLE
|
|
|
|
|
if (bit_isfalse(gc_parser_flags,GC_PARSER_LASER_ISMOTION)) {
|
|
|
|
|
if (bit_istrue(gc_parser_flags,GC_PARSER_LASER_DISABLE)) {
|
|
|
|
|
spindle_sync(gc_state.modal.spindle, 0.0);
|
|
|
|
|
} else { spindle_sync(gc_state.modal.spindle, gc_block.values.s); }
|
|
|
|
|
} else {
|
|
|
|
|
spindle_sync(gc_state.modal.spindle, gc_block.values.s);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
spindle_sync(gc_state.modal.spindle, 0.0);
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
gc_state.spindle_speed = gc_block.values.s; // Update spindle speed state.
|
|
|
|
|
}
|
|
|
|
@@ -966,15 +1200,20 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
// NOTE: Coolant M-codes are modal. Only one command per line is allowed. But, multiple states
|
|
|
|
|
// can exist at the same time, while coolant disable clears all states.
|
|
|
|
|
coolant_sync(gc_block.modal.coolant);
|
|
|
|
|
if (gc_block.modal.coolant == COOLANT_DISABLE) { gc_state.modal.coolant = COOLANT_DISABLE; }
|
|
|
|
|
else { gc_state.modal.coolant |= gc_block.modal.coolant; }
|
|
|
|
|
if (gc_block.modal.coolant == COOLANT_DISABLE) {
|
|
|
|
|
gc_state.modal.coolant = COOLANT_DISABLE;
|
|
|
|
|
} else {
|
|
|
|
|
gc_state.modal.coolant |= gc_block.modal.coolant;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pl_data->condition |= gc_state.modal.coolant; // Set condition flag for planner use.
|
|
|
|
|
|
|
|
|
|
// [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. Always enabled.
|
|
|
|
|
|
|
|
|
|
// [10. Dwell ]:
|
|
|
|
|
if (gc_block.non_modal_command == NON_MODAL_DWELL) { mc_dwell(gc_block.values.p); }
|
|
|
|
|
if (gc_block.non_modal_command == NON_MODAL_DWELL) {
|
|
|
|
|
mc_dwell(gc_block.values.p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [11. Set active plane ]:
|
|
|
|
|
gc_state.modal.plane_select = gc_block.modal.plane_select;
|
|
|
|
@@ -1025,11 +1264,14 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
system_flag_wco_change();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case NON_MODAL_GO_HOME_0: case NON_MODAL_GO_HOME_1:
|
|
|
|
|
case NON_MODAL_GO_HOME_0:
|
|
|
|
|
case NON_MODAL_GO_HOME_1:
|
|
|
|
|
// Move to intermediate position before going home. Obeys current coordinate system and offsets
|
|
|
|
|
// and absolute and incremental modes.
|
|
|
|
|
pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag.
|
|
|
|
|
if (axis_command) { mc_line(gc_block.values.xyz, pl_data); }
|
|
|
|
|
if (axis_command) {
|
|
|
|
|
mc_line(gc_block.values.xyz, pl_data);
|
|
|
|
|
}
|
|
|
|
|
mc_line(gc_block.values.ijk, pl_data);
|
|
|
|
|
memcpy(gc_state.position, gc_block.values.ijk, N_AXIS*sizeof(float));
|
|
|
|
|
break;
|
|
|
|
@@ -1068,9 +1310,9 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
} else {
|
|
|
|
|
// NOTE: gc_block.values.xyz is returned from mc_probe_cycle with the updated position value. So
|
|
|
|
|
// upon a successful probing cycle, the machine position and the returned value should be the same.
|
|
|
|
|
#ifndef ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES
|
|
|
|
|
#ifndef ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES
|
|
|
|
|
pl_data->condition |= PL_COND_FLAG_NO_FEED_OVERRIDE;
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
gc_update_pos = mc_probe_cycle(gc_block.values.xyz, pl_data, gc_parser_flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1111,15 +1353,17 @@ uint8_t gc_execute_line(char *line, uint8_t client)
|
|
|
|
|
gc_state.modal.coolant = COOLANT_DISABLE;
|
|
|
|
|
// gc_state.modal.override = OVERRIDE_DISABLE; // Not supported.
|
|
|
|
|
|
|
|
|
|
#ifdef RESTORE_OVERRIDES_AFTER_PROGRAM_END
|
|
|
|
|
#ifdef RESTORE_OVERRIDES_AFTER_PROGRAM_END
|
|
|
|
|
sys.f_override = DEFAULT_FEED_OVERRIDE;
|
|
|
|
|
sys.r_override = DEFAULT_RAPID_OVERRIDE;
|
|
|
|
|
sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE;
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Execute coordinate change and spindle/coolant stop.
|
|
|
|
|
if (sys.state != STATE_CHECK_MODE) {
|
|
|
|
|
if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) { FAIL(STATUS_SETTING_READ_FAIL); }
|
|
|
|
|
if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) {
|
|
|
|
|
FAIL(STATUS_SETTING_READ_FAIL);
|
|
|
|
|
}
|
|
|
|
|
system_flag_wco_change(); // Set to refresh immediately just in case something altered.
|
|
|
|
|
spindle_set_state(SPINDLE_DISABLE,0.0);
|
|
|
|
|
coolant_set_state(COOLANT_DISABLE);
|
|
|
|
|