diff --git a/g2core/alarm.cpp b/g2core/alarm.cpp
new file mode 100644
index 00000000..7a962f5d
--- /dev/null
+++ b/g2core/alarm.cpp
@@ -0,0 +1,247 @@
+/*
+ * alarm.cpp - canonical machine alarm handlers
+ * This file is part of the g2core project
+ *
+ * Copyright (c) 2010 - 2017 Alden S Hart, Jr.
+ * Copyright (c) 2014 - 2017 Robert 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 .
+ *
+ * As a special exception, you may use this file as part of a software library without
+ * restriction. Specifically, if other files instantiate templates or use macros or
+ * inline functions from this file, or you compile this file and link it with other
+ * files to produce an executable, this file does not by itself cause the resulting
+ * executable to be covered by the GNU General Public License. This exception does not
+ * however invalidate any other reasons why the executable file might be covered by the
+ * GNU General Public License.
+ *
+ * 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.
+ */
+
+#include "g2core.h" // #1
+#include "config.h" // #2
+#include "gcode.h" // #3
+#include "canonical_machine.h"
+#include "planner.h"
+#include "report.h"
+#include "spindle.h"
+#include "coolant.h"
+#include "temperature.h"
+#include "util.h"
+
+/********************************************************************************
+ * ALARM, SHUTDOWN, and PANIC are nested dolls.
+ *
+ * cm_alrm() - invoke alarm from command
+ * cm_shutd() - invoke shutdown from command
+ * cm_pnic() - invoke panic from command
+ * cm_clr() - clear alarm or shutdown from command
+ *
+ * The alarm states can be invoked from the above commands for testing and clearing
+ */
+stat_t cm_alrm(nvObj_t *nv) // invoke alarm from command
+{
+ cm_alarm(STAT_ALARM, "sent by host");
+ return (STAT_OK);
+}
+
+stat_t cm_shutd(nvObj_t *nv) // invoke shutdown from command
+{
+ cm_shutdown(STAT_SHUTDOWN, "sent by host");
+ return (STAT_OK);
+}
+
+stat_t cm_pnic(nvObj_t *nv) // invoke panic from command
+{
+ cm_panic(STAT_PANIC, "sent by host");
+ return (STAT_OK);
+}
+
+stat_t cm_clr(nvObj_t *nv) // clear alarm or shutdown from command line
+{
+ cm_clear();
+ return (STAT_OK);
+}
+
+/*
+ * cm_clear() - clear ALARM and SHUTDOWN states
+ * cm_parse_clear() - parse incoming gcode for M30 or M2 clears if in ALARM state
+ *
+ * Parse clear interprets an M30 or M2 PROGRAM_END as a $clear condition and clear ALARM
+ * but not SHUTDOWN or PANIC. Assumes Gcode string has no leading or embedded whitespace
+ */
+
+void cm_clear()
+{
+ if (cm->machine_state == MACHINE_ALARM) {
+ cm->machine_state = MACHINE_PROGRAM_STOP;
+ } else if (cm->machine_state == MACHINE_SHUTDOWN) {
+ cm->machine_state = MACHINE_READY;
+ }
+}
+
+void cm_parse_clear(const char *s)
+{
+ if (cm->machine_state == MACHINE_ALARM) {
+ if (toupper(s[0]) == 'M') {
+ if (( (s[1]=='3') && (s[2]=='0') && (s[3]==0)) || ((s[1]=='2') && (s[2]==0) )) {
+ cm_clear();
+ }
+ }
+ }
+}
+
+/*
+ * cm_is_alarmed() - return alarm status code or OK if no alarms
+ */
+
+stat_t cm_is_alarmed()
+{
+ if (cm->machine_state == MACHINE_ALARM) { return (STAT_COMMAND_REJECTED_BY_ALARM); }
+ if (cm->machine_state == MACHINE_SHUTDOWN) { return (STAT_COMMAND_REJECTED_BY_SHUTDOWN); }
+ if (cm->machine_state == MACHINE_PANIC) { return (STAT_COMMAND_REJECTED_BY_PANIC); }
+ return (STAT_OK);
+}
+
+/*
+ * cm_halt_all() - stop, spindle and coolant immediately
+ * cm_halt_motion() - stop motion immediately. Does not affect spindle, coolant, or other IO
+ *
+ * Stop motors and reset all system states accordingly.
+ * Does not de-energize motors as in some cases the motors must remain energized
+ * in order to prevent an axis from crashing.
+ */
+
+void cm_halt_all(void)
+{
+ cm_halt_motion();
+ cm_spindle_off_immediate();
+ cm_coolant_off_immediate();
+}
+
+void cm_halt_motion(void)
+{
+ mp_halt_runtime(); // stop the runtime. Do this immediately. (Reset is in cm_clear)
+ canonical_machine_reset(cm); // halt the currently active machine
+ cm->cycle_state = CYCLE_OFF; // Note: leaves machine_state alone
+ cm->motion_state = MOTION_STOP;
+ cm->hold_state = FEEDHOLD_OFF;
+}
+
+/*
+ * cm_alarm() - enter ALARM state
+ *
+ * An ALARM sets the ALARM machine state, starts a feedhold to stop motion, stops the
+ * spindle, turns off coolant, clears out queued planner moves and serial input,
+ * and rejects new action commands (gcode blocks, SET commands, and other actions)
+ * until the alarm is cleared.
+ *
+ * ALARM is typically entered by a soft limit or a limit switch being hit. In the
+ * limit switch case the INPUT_ACTION will override the feedhold - i.e. if the
+ * input action is "FAST_STOP" or "HALT" that setting will take precedence over
+ * the feedhold native to the alarm function.
+ *
+ * Gcode and machine state is preserved. It may be possible to recover the job from
+ * an alarm, but in many cases this is not possible. Since ALARM attempts to preserve
+ * Gcode and machine state it does not END the job.
+ *
+ * ALARM may also be invoked from the command line using {alarm:n} or $alarm
+ * ALARM can be manually cleared by entering: {clear:n}, {clr:n}, $clear, or $clr
+ * ALARMs will also clear on receipt of an M30 or M2 command if one is received
+ * while draining the host command queue.
+ */
+
+stat_t cm_alarm(const stat_t status, const char *msg)
+{
+ if ((cm->machine_state == MACHINE_ALARM) || (cm->machine_state == MACHINE_SHUTDOWN) ||
+ (cm->machine_state == MACHINE_PANIC)) {
+ return (STAT_OK); // don't alarm if already in an alarm state
+ }
+ cm->machine_state = MACHINE_ALARM;
+ cm_request_feedhold(); // stop motion
+ cm_request_queue_flush(); // do a queue flush once runtime is not busy
+
+// TBD - these functions should probably be called - See cm_shutdown()
+// cm_spindle_control_immediate(SPINDLE_OFF);
+// cm_coolant_off_immediate();
+// cm_spindle_optional_pause(spindle.pause_on_hold);
+// cm_coolant_optional_pause(coolant.pause_on_hold);
+ rpt_exception(status, msg); // send alarm message
+
+ // If "stat" is in the status report, we need to poke it to send.
+ sr_request_status_report(SR_REQUEST_TIMED);
+ return (status);
+}
+/*
+ * cm_shutdown() - enter shutdown state
+ *
+ * SHUTDOWN stops all motion, spindle and coolant immediately, sets a SHUTDOWN machine
+ * state, clears out queued moves and serial input, and rejects new action commands
+ * (gcode blocks, SET commands, and some others).
+ *
+ * Shutdown is typically invoked as an electrical input signal sent to the board as
+ * part of an external emergency stop (Estop). Shutdown is meant to augment but not
+ * replace the external Estop functions that shut down power to motors, spindles and
+ * other moving parts.
+ *
+ * Shutdown may also be invoked from the command line using {shutd:n} or $shutd
+ * Shutdown must be manually cleared by entering: {clear:n}, {clr:n}, $clear, or $clr
+ * Shutdown does not clear on M30 or M2 Gcode commands
+ */
+
+stat_t cm_shutdown(const stat_t status, const char *msg)
+{
+ if ((cm->machine_state == MACHINE_SHUTDOWN) || (cm->machine_state == MACHINE_PANIC)) {
+ return (STAT_OK); // don't shutdown if shutdown or panic'd
+ }
+ cm_halt_motion(); // halt motors (may have already been done from GPIO)
+ spindle_reset(); // stop spindle immediately and set speed to 0 RPM
+ coolant_reset(); // stop coolant immediately
+ temperature_reset(); // turn off heaters and fans
+ cm_queue_flush(); // flush all queues and reset positions
+
+ for (uint8_t i = 0; i < HOMING_AXES; i++) { // unhome axes and the machine
+ cm->homed[i] = false;
+ }
+ cm->homing_state = HOMING_NOT_HOMED;
+
+ cm->machine_state = MACHINE_SHUTDOWN; // do this after all other activity
+ rpt_exception(status, msg); // send exception report
+ return (status);
+}
+
+/*
+ * cm_panic() - enter panic state
+ *
+ * PANIC occurs if the firmware has detected an unrecoverable internal error
+ * such as an assertion failure or a code condition that should never occur.
+ * It sets PANIC machine state, and leaves the system inspect able (if possible).
+ *
+ * PANIC can only be exited by a hardware reset or soft reset (^x)
+ */
+
+stat_t cm_panic(const stat_t status, const char *msg)
+{
+ _debug_trap(msg);
+
+ if (cm->machine_state == MACHINE_PANIC) { // only do this once
+ return (STAT_OK);
+ }
+ cm_halt_motion(); // halt motors (may have already been done from GPIO)
+ spindle_reset(); // stop spindle immediately and set speed to 0 RPM
+ coolant_reset(); // stop coolant immediately
+ temperature_reset(); // turn off heaters and fans
+ cm_queue_flush(); // flush all queues and reset positions
+
+ cm->machine_state = MACHINE_PANIC; // don't reset anything. Panics are not recoverable
+ rpt_exception(status, msg); // send panic report
+ return (status);
+}
diff --git a/g2core/canonical_machine.cpp b/g2core/canonical_machine.cpp
index e0f11046..4117289c 100644
--- a/g2core/canonical_machine.cpp
+++ b/g2core/canonical_machine.cpp
@@ -107,7 +107,7 @@
#include "temperature.h"
#include "hardware.h"
#include "util.h"
-#include "xio.h" // for serial queue flush
+#include "xio.h"
/***********************************************************************************
**** CM GLOBALS & STRUCTURE ALLOCATIONS *******************************************
@@ -136,6 +136,9 @@ static int8_t _axis(const index_t index);
**** CODE *************************************************************************
***********************************************************************************/
+/***********************************************************************************
+ **** Initialization ***************************************************************
+ ***********************************************************************************/
/*
* canonical_machine_inits() - combined cm inits
* canonical_machine_init() - initialize cm struct
@@ -247,10 +250,9 @@ stat_t canonical_machine_test_assertions(cmMachine_t *_cm)
return (STAT_OK);
}
-/*************************************
- * Internal getters and setters *
- * Canonical Machine State functions *
- *************************************/
+/***********************************************************************************
+ **** Canonical Machine State Management *******************************************
+ ***********************************************************************************/
/*
* cm_set_motion_state() - adjusts active model pointer as well
*/
@@ -331,10 +333,9 @@ cmCombinedState cm_get_combined_state()
}
}
-/***********************************
- * Model State Getters and Setters *
- ***********************************/
-
+/***********************************************************************************
+ **** Model State Getters and Setters **********************************************
+ ***********************************************************************************/
/* These getters and setters will work on any gm model with inputs:
* MODEL (GCodeState_t *)&cm->gm // absolute pointer from canonical machine gm model
* RUNTIME (GCodeState_t *)&mr->gm // absolute pointer from runtime mm struct
@@ -377,7 +378,8 @@ void cm_set_model_linenum(const uint32_t linenum)
}
/***********************************************************************************
- * COORDINATE SYSTEMS AND OFFSETS
+ **** COORDINATE SYSTEMS AND OFFSETS ***********************************************
+ ***********************************************************************************
* Functions to get, set and report coordinate systems and work offsets
* These functions are not part of the NIST defined functions
***********************************************************************************/
@@ -478,7 +480,8 @@ float cm_get_work_position(const GCodeState_t *gcode_state, const uint8_t axis)
}
/***********************************************************************************
- * CRITICAL HELPERS
+ **** CRITICAL HELPERS *************************************************************
+ ***********************************************************************************
* Core functions supporting the canonical machining functions
* These functions are not part of the NIST defined functions
***********************************************************************************/
@@ -761,233 +764,17 @@ stat_t cm_test_soft_limits(const float target[])
return (STAT_OK);
}
-/*************************************************************************
- * CANONICAL MACHINING FUNCTIONS
- * Values are passed in pre-unit_converted state (from gn structure)
- * All operations occur on gm (current model state)
- *
- * These are organized by section number (x.x.x) in the order they are
- * found in NIST RS274 NGCv3
- ************************************************************************/
+/***********************************************************************************
+ **** CANONICAL MACHINING FUNCTIONS ************************************************
+ ***********************************************************************************
+ * Organized by section number in the order they are found in NIST RS274 NGCv3
+ **********************************************************************************/
-/**************************
- * Alarms *
- **************************/
+/***********************************************************************************
+ **** Representation (4.3.3) *******************************************************
+ ***********************************************************************************/
-/********************************************************************************
- * ALARM, SHUTDOWN, and PANIC are nested dolls.
- *
- * cm_alrm() - invoke alarm from command
- * cm_shutd() - invoke shutdown from command
- * cm_pnic() - invoke panic from command
- * cm_clr() - clear alarm or shutdown from command
- *
- * The alarm states can be invoked from the above commands for testing and clearing
- */
-stat_t cm_alrm(nvObj_t *nv) // invoke alarm from command
-{
- cm_alarm(STAT_ALARM, "sent by host");
- return (STAT_OK);
-}
-
-stat_t cm_shutd(nvObj_t *nv) // invoke shutdown from command
-{
- cm_shutdown(STAT_SHUTDOWN, "sent by host");
- return (STAT_OK);
-}
-
-stat_t cm_pnic(nvObj_t *nv) // invoke panic from command
-{
- cm_panic(STAT_PANIC, "sent by host");
- return (STAT_OK);
-}
-
-stat_t cm_clr(nvObj_t *nv) // clear alarm or shutdown from command line
-{
- cm_clear();
- return (STAT_OK);
-}
-
-/*
- * cm_clear() - clear ALARM and SHUTDOWN states
- * cm_parse_clear() - parse incoming gcode for M30 or M2 clears if in ALARM state
- *
- * Parse clear interprets an M30 or M2 PROGRAM_END as a $clear condition and clear ALARM
- * but not SHUTDOWN or PANIC. Assumes Gcode string has no leading or embedded whitespace
- */
-
-void cm_clear()
-{
- if (cm->machine_state == MACHINE_ALARM) {
- cm->machine_state = MACHINE_PROGRAM_STOP;
- } else if (cm->machine_state == MACHINE_SHUTDOWN) {
- cm->machine_state = MACHINE_READY;
- }
-}
-
-void cm_parse_clear(const char *s)
-{
- if (cm->machine_state == MACHINE_ALARM) {
- if (toupper(s[0]) == 'M') {
- if (( (s[1]=='3') && (s[2]=='0') && (s[3]==NUL)) || ((s[1]=='2') && (s[2]==NUL) )) {
- cm_clear();
- }
- }
- }
-}
-
-/*
- * cm_is_alarmed() - return alarm status code or OK if no alarms
- */
-
-stat_t cm_is_alarmed()
-{
- if (cm->machine_state == MACHINE_ALARM) { return (STAT_COMMAND_REJECTED_BY_ALARM); }
- if (cm->machine_state == MACHINE_SHUTDOWN) { return (STAT_COMMAND_REJECTED_BY_SHUTDOWN); }
- if (cm->machine_state == MACHINE_PANIC) { return (STAT_COMMAND_REJECTED_BY_PANIC); }
- return (STAT_OK);
-}
-
-/*
- * cm_halt_all() - stop, spindle and coolant immediately
- * cm_halt_motion() - stop motion immediately. Does not affect spindle, coolant, or other IO
- *
- * Stop motors and reset all system states accordingly.
- * Does not de-energize motors as in some cases the motors must remain energized
- * in order to prevent an axis from crashing.
- */
-
-void cm_halt_all(void)
-{
- cm_halt_motion();
- cm_spindle_off_immediate();
- cm_coolant_off_immediate();
-}
-
-void cm_halt_motion(void)
-{
- mp_halt_runtime(); // stop the runtime. Do this immediately. (Reset is in cm_clear)
- canonical_machine_reset(cm); // halt the currently active machine
- cm->cycle_state = CYCLE_OFF; // Note: leaves machine_state alone
- cm->motion_state = MOTION_STOP;
- cm->hold_state = FEEDHOLD_OFF;
-}
-
-/*
- * cm_alarm() - enter ALARM state
- *
- * An ALARM sets the ALARM machine state, starts a feedhold to stop motion, stops the
- * spindle, turns off coolant, clears out queued planner moves and serial input,
- * and rejects new action commands (gcode blocks, SET commands, and other actions)
- * until the alarm is cleared.
- *
- * ALARM is typically entered by a soft limit or a limit switch being hit. In the
- * limit switch case the INPUT_ACTION will override the feedhold - i.e. if the
- * input action is "FAST_STOP" or "HALT" that setting will take precedence over
- * the feedhold native to the alarm function.
- *
- * Gcode and machine state is preserved. It may be possible to recover the job from
- * an alarm, but in many cases this is not possible. Since ALARM attempts to preserve
- * Gcode and machine state it does not END the job.
- *
- * ALARM may also be invoked from the command line using {alarm:n} or $alarm
- * ALARM can be manually cleared by entering: {clear:n}, {clr:n}, $clear, or $clr
- * ALARMs will also clear on receipt of an M30 or M2 command if one is received
- * while draining the host command queue.
- */
-
-stat_t cm_alarm(const stat_t status, const char *msg)
-{
- if ((cm->machine_state == MACHINE_ALARM) || (cm->machine_state == MACHINE_SHUTDOWN) ||
- (cm->machine_state == MACHINE_PANIC)) {
- return (STAT_OK); // don't alarm if already in an alarm state
- }
- cm->machine_state = MACHINE_ALARM;
- cm_request_feedhold(); // stop motion
- cm_request_queue_flush(); // do a queue flush once runtime is not busy
-
-// TBD - these functions should probably be called - See cm_shutdown()
-// cm_spindle_control_immediate(SPINDLE_OFF);
-// cm_coolant_off_immediate();
-// cm_spindle_optional_pause(spindle.pause_on_hold);
-// cm_coolant_optional_pause(coolant.pause_on_hold);
- rpt_exception(status, msg); // send alarm message
-
- // If "stat" is in the status report, we need to poke it to send.
- sr_request_status_report(SR_REQUEST_TIMED);
- return (status);
-}
-/*
- * cm_shutdown() - enter shutdown state
- *
- * SHUTDOWN stops all motion, spindle and coolant immediately, sets a SHUTDOWN machine
- * state, clears out queued moves and serial input, and rejects new action commands
- * (gcode blocks, SET commands, and some others).
- *
- * Shutdown is typically invoked as an electrical input signal sent to the board as
- * part of an external emergency stop (Estop). Shutdown is meant to augment but not
- * replace the external Estop functions that shut down power to motors, spindles and
- * other moving parts.
- *
- * Shutdown may also be invoked from the command line using {shutd:n} or $shutd
- * Shutdown must be manually cleared by entering: {clear:n}, {clr:n}, $clear, or $clr
- * Shutdown does not clear on M30 or M2 Gcode commands
- */
-
-stat_t cm_shutdown(const stat_t status, const char *msg)
-{
- if ((cm->machine_state == MACHINE_SHUTDOWN) || (cm->machine_state == MACHINE_PANIC)) {
- return (STAT_OK); // don't shutdown if shutdown or panic'd
- }
- cm_halt_motion(); // halt motors (may have already been done from GPIO)
- spindle_reset(); // stop spindle immediately and set speed to 0 RPM
- coolant_reset(); // stop coolant immediately
- temperature_reset(); // turn off heaters and fans
- cm_queue_flush(); // flush all queues and reset positions
-
- for (uint8_t i = 0; i < HOMING_AXES; i++) { // unhome axes and the machine
- cm->homed[i] = false;
- }
- cm->homing_state = HOMING_NOT_HOMED;
-
- cm->machine_state = MACHINE_SHUTDOWN; // do this after all other activity
- rpt_exception(status, msg); // send exception report
- return (status);
-}
-
-/*
- * cm_panic() - enter panic state
- *
- * PANIC occurs if the firmware has detected an unrecoverable internal error
- * such as an assertion failure or a code condition that should never occur.
- * It sets PANIC machine state, and leaves the system inspect able (if possible).
- *
- * PANIC can only be exited by a hardware reset or soft reset (^x)
- */
-
-stat_t cm_panic(const stat_t status, const char *msg)
-{
- _debug_trap(msg);
-
- if (cm->machine_state == MACHINE_PANIC) { // only do this once
- return (STAT_OK);
- }
- cm_halt_motion(); // halt motors (may have already been done from GPIO)
- spindle_reset(); // stop spindle immediately and set speed to 0 RPM
- coolant_reset(); // stop coolant immediately
- temperature_reset(); // turn off heaters and fans
- cm_queue_flush(); // flush all queues and reset positions
-
- cm->machine_state = MACHINE_PANIC; // don't reset anything. Panics are not recoverable
- rpt_exception(status, msg); // send panic report
- return (status);
-}
-
-/**************************
- * Representation (4.3.3) *
- **************************/
-
-/**************************************************************************
+/************************************************************************************
* Representation functions that affect the Gcode model only (asynchronous)
*
* cm_select_plane() - G17,G18,G19 select axis plane
@@ -1092,12 +879,11 @@ stat_t cm_set_g10_data(const uint8_t P_word, const bool P_flag,
/******************************************************************************************
* Representation functions that affect gcode model and are queued to planner (synchronous)
- */
-/*
+ *
* cm_set_tl_offset() - G43
* cm_cancel_tl_offset() - G49
* cm_set_coord_system() - G54-G59
- * _exec_offset() - callback from planner
+ * _exec_offset() - callback from planner
*/
stat_t cm_set_tl_offset(const uint8_t H_word, const bool H_flag, const bool apply_additional)
@@ -1293,9 +1079,9 @@ stat_t cm_resume_origin_offsets()
return (STAT_OK);
}
-/*****************************
- * Free Space Motion (4.3.4) *
- *****************************/
+/***********************************************************************************
+ **** Free Space Motion (4.3.4) ****************************************************
+ ***********************************************************************************/
/*
* cm_straight_traverse() - G0 linear rapid
*/
@@ -1323,7 +1109,7 @@ stat_t cm_straight_traverse(const float target[], const bool flags[])
return (status);
}
-/*
+/***********************************************************************************
* cm_set_g28_position() - G28.1
* cm_goto_g28_position() - G28
* cm_set_g30_position() - G30.1
@@ -1384,9 +1170,9 @@ stat_t cm_goto_g30_position(const float target[], const bool flags[])
return (_goto_stored_position(cm->gmx.g30_position, target, flags));
}
-/********************************
- * Machining Attributes (4.3.5) *
- ********************************/
+/***********************************************************************************
+ **** Machining Attributes (4.3.5) *************************************************
+ ***********************************************************************************/
/*
* cm_set_feed_rate() - F parameter (affects MODEL only)
*
@@ -1406,7 +1192,7 @@ stat_t cm_set_feed_rate(const float feed_rate)
return (STAT_OK);
}
-/*
+/***********************************************************************************
* cm_set_feed_rate_mode() - G93, G94 (affects MODEL only)
*
* INVERSE_TIME_MODE = 0, // G93
@@ -1420,7 +1206,7 @@ stat_t cm_set_feed_rate_mode(const uint8_t mode)
return (STAT_OK);
}
-/*
+/***********************************************************************************
* cm_set_path_control() - G61, G61.1, G64
*/
@@ -1430,15 +1216,14 @@ stat_t cm_set_path_control(GCodeState_t *gcode_state, const uint8_t mode)
return (STAT_OK);
}
-
-/*******************************
- * Machining Functions (4.3.6) *
- *******************************/
+/***********************************************************************************
+ **** Machining Functions (4.3.6) **************************************************
+ ***********************************************************************************/
/*
* cm_arc_feed() - SEE plan_arc.cpp
*/
-/*
+/***********************************************************************************
* cm_dwell() - G4, P parameter (seconds)
*/
stat_t cm_dwell(const float seconds)
@@ -1448,7 +1233,7 @@ stat_t cm_dwell(const float seconds)
return (STAT_OK);
}
-/*
+/***********************************************************************************
* cm_straight_feed() - G1
*/
stat_t cm_straight_feed(const float target[], const bool flags[])
@@ -1480,14 +1265,14 @@ stat_t cm_straight_feed(const float target[], const bool flags[])
return (status);
}
-/*****************************
- * Spindle Functions (4.3.7) *
- *****************************/
+/***********************************************************************************
+ **** Spindle Functions (4.3.7) ****************************************************
+ ***********************************************************************************/
// see spindle.cpp/.h
-/**************************
- * Tool Functions (4.3.8) *
- **************************/
+/***********************************************************************************
+ **** Tool Functions (4.3.8) *******************************************************
+ ***********************************************************************************/
/*
* cm_select_tool() - T parameter
* _exec_select_tool() - execution callback
@@ -1527,12 +1312,12 @@ static void _exec_change_tool(float *value, bool *flag)
cm->gm.tool = (uint8_t)value[0];
}
-/***********************************
- * Miscellaneous Functions (4.3.9) *
- ***********************************/
+/***********************************************************************************
+ **** Miscellaneous Functions (4.3.9) **********************************************
+ ***********************************************************************************/
// see coolant.cpp/.h
-/*
+/***********************************************************************************
* cm_message() - queue a RAM string as a message in the response (unconditionally)
*/
@@ -1541,7 +1326,11 @@ void cm_message(const char *message)
nv_add_string((const char *)"msg", message); // add message to the response object
}
-/*
+/***********************************************************************************
+ **** Overrides ********************************************************************
+ ***********************************************************************************/
+
+/***********************************************************************************
* cm_reset_overrides() - reset manual feedrate and spindle overrides to initial conditions
*/
@@ -1560,7 +1349,7 @@ static void _exec_feed_override(const bool m48_enable, const bool m50_enable, co
}
*/
-/*
+/***********************************************************************************
* cm_m48_enable() - M48, M49
*
* M48 is the master enable for manual feedrate override and spindle override
@@ -1590,8 +1379,9 @@ stat_t cm_m48_enable(uint8_t enable) // M48, M49
return (STAT_OK);
}
-/*
+/***********************************************************************************
* cm_mfo_control() - M50 manual feed rate override comtrol
+ * cm_mto_control() - M50.1 manual traverse override comtrol
*
* M50 enables manual feedrate override and the optional P override parameter.
* P is expressed as M% to N% of programmed feedrate, typically a value from 0.05 to 2.000.
@@ -1688,351 +1478,10 @@ stat_t cm_mto_control(const float P_word, const bool P_flag) // M50.1
cm->gmx.mto_enable = new_enable; // always update the enable state
return (STAT_OK);
}
-/************************************************
- * Feedhold and Related Functions (no NIST ref) *
- ************************************************/
-/*
- * Feedholds, queue flushes and end_holds are all related. The request functions set flags
- * or change state to "REQUESTED". The sequencing callback interprets the flags as so:
- * - A feedhold request received during motion should be honored
- * - A feedhold request received during a feedhold should be ignored
- * - A feedhold request received during a motion stop should be ignored
- *
- * - A queue flush request should only be honored while in a feedhold
- * - Said queue flush request received during a feedhold should be deferred until
- * the feedhold enters a HOLD state (i.e. until deceleration is complete and motors stop).
- * - A queue flush request received during a motion stop should be honored
- *
- * - An end_hold (cycle start) request should only be honored while in a feedhold
- * - Said end_hold request received during a feedhold should be deferred until the
- * feedhold enters a HOLD state (i.e. until deceleration is complete).
- * If a queue flush request is also present the queue flush should be done first
- *
- * Below the request level, feedholds work like this:
- * - The hold is initiated by calling cm_start_hold(). cm->hold_state is set to
- * FEEDHOLD_SYNC, motion_state is set to MOTION_HOLD, and the spindle is turned off
- * (if it it on). The remainder of feedhold
- * processing occurs in plan_exec.c in the mp_exec_aline() function.
- *
- * - MOTION_HOLD and FEEDHOLD_SYNC tells mp_exec_aline() to begin feedhold processing
- * after the current move segment is finished (< 5 ms later). (Cases handled by
- * feedhold processing are listed in plan_exec.c).
- *
- * - FEEDHOLD_SYNC causes the current move in mr to be replanned into a deceleration.
- * If the distance remaining in the executing move is sufficient for a full deceleration
- * then motion will stop in the current block. Otherwise the deceleration phase
- * will extend across as many blocks necessary until one will stop.
- *
- * - Once deceleration is complete hold state transitions to FEEDHOLD_FINALIZING and
- * the distance remaining in the bf last block is replanned up from zero velocity.
- * The move in the bf block is NOT released (unlike normal operation), as it will
- * be used again to restart from hold.
- *
- * - When cm_end_hold() is called it releases the hold, restarts the move and restarts
- * the spindle if the spindle is active.
- */
-/* Queue Flush operation
- *
- * This one's complicated. See here first:
- * https://github.com/synthetos/g2/wiki/Alarm-Processing
- * https://github.com/synthetos/g2/wiki/Job-Exception-Handling
- *
- * We want to use queue flush for a few different use cases, as per the above wiki pages.
- * The % behavior implements Exception Handling cases 1 and 2 - Stop a Single Move and
- * Stop Multiple Moves. This is complicated further by the processing in single USB and
- * dual USB being different. Also, the state handling is located in xio.cpp / readline(),
- * controller.cpp _dispatch_kernel() and cm_request_queue_flush(), below.
- * So it's documented here.
- *
- * Single or Dual USB Channels:
- * - If a % is received outside of a feed hold or ALARM state, ignore it.
- * Change the % to a ; comment symbol (xio)
- *
- * Single USB Channel Operation:
- * - Enter a feedhold (!)
- * - Receive a queue flush (%) Both dispatch it and store a marker (ACK) in the input
- * buffer in place of the the % (xio)
- * - Execute the feedhold to a hold condition (plan_exec)
- * - Execute the dispatched % to flush queues (canonical_machine)
- * - Silently reject any commands up to the % in the input queue (controller)
- * - When ETX is encountered transition to STOP state (controller/canonical_machine)
- *
- * Dual USB Channel Operation:
- * - Same as above except that we expect the % to arrive on the control channel
- * - The system will read and dump all commands in the data channel until either a
- * clear is encountered ({clear:n} or $clear), or an ETX is encountered on either
- * channel, but it really should be on the data channel to ensure all queued commands
- * are dumped. It is the host's responsibility to both write the clear (or ETX), and
- * to ensure that it either arrives on the data channel or that the data channel is
- * empty before writing it to the control channel.
- */
-/* With the addition of the secondary CM, feedhold state management gets tricky.
- What you see below is a temporary solution until we decide the general solution.
-
- The general solution causes a feedhold from the primary context to switch
- into the secondary context to perform feedhold actions. When in the secondary
- context an additional feedhold will perform the usual STOP operation, but
- will remain in the secondary context, and therefore not perform any feedhold
- actions (lifts, spindle, etc.). This is needed to support homing and probing
- operations from within the secondary context.
-
- Oddities of the general solution:
- - Should we allow a feedhold to be performed if the tool is not moving?
- Right now we don't, but with the secondary context this might be useful
-
- What you see here is a Q&D to only allow feedholds from the primary context.
- It has the following limitations:
- - Feedhold requests are only honored form the primary context
- - Queue flush requests are only honored form the primary context
- - Machine alarm state is not (yet) taken into account in feedhold sequencing and restart
- */
-/*
- * cm_request_feedhold()
- * cm_request_end_hold()
- * cm_request_queue_flush()
- * cm_feedhold_sequencing_callback() - sequence feedhold, queue_flush, and end_hold requests
- */
-
-void cm_request_feedhold(void)
-{
- // do not generate a feedhold request from the secondary context
- if (cm_select != CM_PRIMARY) {
- return;
- }
- // only generate request if not already in a feedhold and the machine is in motion
-// if ((cm1.hold_state == FEEDHOLD_OFF) && (cm1.motion_state != MOTION_STOP)) {
-
- // only generate request if not already in a feedhold
- // It's OK if the machine is stopped - this just enters the feedhold context
- if (cm1.hold_state == FEEDHOLD_OFF) {
- cm1.hold_state = FEEDHOLD_REQUESTED;
- }
-}
-
-void cm_request_end_hold(void) // This is usually requested form the secondary context
-{
- if (cm1.hold_state != FEEDHOLD_OFF) {
- cm1.end_hold_requested = true;
- }
-}
-
-void cm_request_queue_flush()
-{
- // do not generate a queue flush request from the secondary context
- if (cm_select != CM_PRIMARY) {
- return;
- }
- if ((cm1.hold_state != FEEDHOLD_OFF) && // don't honor request unless you are in a feedhold
- (cm1.queue_flush_state == FLUSH_OFF)) { // ...and only once
- cm1.queue_flush_state = FLUSH_REQUESTED; // request planner flush once motion has stopped
-
- // NOTE: we used to flush the input buffers, but this is handled in xio *prior* to queue flush now
- }
-}
-
-stat_t cm_feedhold_sequencing_callback()
-{
- if (cm1.hold_state == FEEDHOLD_REQUESTED) {
- cm_start_hold(); // feed won't run unless the machine is moving
- }
- if (cm1.hold_state == FEEDHOLD_FINALIZING) {
- cm1.hold_state = FEEDHOLD_HOLD;
- cm_switch_to_hold_context(); // perform Z lift, spindle & coolant operations
- }
- if (cm1.queue_flush_state == FLUSH_REQUESTED) {
- cm_queue_flush(); // queue flush won't run until runtime is idle
- }
- if (cm1.end_hold_requested) {
- if (cm1.queue_flush_state == FLUSH_OFF) { // either no flush or wait until it's done flushing
- cm_end_hold();
- }
- }
- return (STAT_OK);
-}
-
-/*
- * cm_has_hold() - return true if a hold condition exists (or a pending hold request)
- * cm_start_hold() - start a feedhhold by signalling the exec
- * cm_end_hold() - end a feedhold by returning the system to normal operation
- */
-bool cm_has_hold()
-{
- return (cm1.hold_state != FEEDHOLD_OFF);
-}
-
-void cm_start_hold()
-{
- if (mp_has_runnable_buffer(mp)) { //+++++ // meaning there's something running
- cm_set_motion_state(MOTION_HOLD);
- cm->hold_state = FEEDHOLD_SYNC; // invokes hold from aline execution
- }
-}
-
-void cm_end_hold()
-{
- if (cm1.hold_state == FEEDHOLD_HOLD) {
- cm1.end_hold_requested = false;
- cm_return_from_hold_context();
- }
-}
-
-/*
- * cm_switch_to_hold_context() - switch to secondary machine context
- *
- * Moving between contexts is only safe when the machine is completely stopped
- * either during a feedhold or when idle.
- */
-
-stat_t cm_switch_to_hold_context()
-{
- // Must be in the primary CM and fully stopped in a hold
- if ((cm != &cm1) || (cm->hold_state != FEEDHOLD_HOLD)) {
- return (STAT_COMMAND_NOT_ACCEPTED);
- }
-
- // copy the primary canonical machine to the secondary,
- // fix the planner pointer, and reset the secondary planner
- memcpy(&cm2, &cm1, sizeof(cmMachine_t));
- cm2.mp = &mp2;
- planner_reset((mpPlanner_t *)cm2.mp); // mp is a void pointer
-
- // set parameters in cm, gm and gmx so you can actually use it
- cmMachine_t *_cm = &cm2;
- _cm->hold_state = FEEDHOLD_OFF;
- _cm->gm.motion_mode = MOTION_MODE_CANCEL_MOTION_MODE;
- _cm->gm.absolute_override = ABSOLUTE_OVERRIDE_OFF;
- _cm->gm.feed_rate = 0;
-
- // clear the target and set the positions to the current hold position
- memset(&(_cm->gm.target), 0, sizeof(_cm->gm.target));
- copy_vector(_cm->gm.target_comp, cm->gm.target_comp); // preserve original Kahan compensation
- copy_vector(_cm->gmx.position, mr->position);
- copy_vector(mp2.position, mr->position);
- copy_vector(mr2.position, mr->position);
-
- // reassign the globals to the secondary CM
- cm = &cm2;
- mp = (mpPlanner_t *)cm->mp; // mp is a void pointer
- mr = mp->mr;
- cm_select = CM_SECONDARY;
-
- // set motion state and ACTIVE_MODEL. This must be performed after cm is set to cm2
- cm_set_g30_position();
- cm_set_motion_state(MOTION_STOP);
-
- // optional Z lift
- if (fp_NOT_ZERO(cm->feedhold_z_lift)) {
- float stored_distance_mode = cm_get_distance_mode(MODEL);
- cm_set_distance_mode(INCREMENTAL_DISTANCE_MODE);
- bool flags[] = { 0,0,1,0,0,0 };
- float target[] = { 0,0, cm->feedhold_z_lift, 0,0,0 };
- cm_straight_traverse(target, flags);
- cm_set_distance_mode(stored_distance_mode);
- }
-
- // optional spindle stop
- if (spindle.pause_on_hold) {
-
- }
- return (STAT_OK);
-}
-
-/*
- * cm_return_from_hold_context() - initiate return from secondary context
- * cm_return_from_hold_callback() - main loop callback to finsh return once moves are done
- * _planner_done_callback() - callback to sync to end of planner operations
- *
- * Moving between contexts is only safe when the machine is completely stopped
- * either during a feedhold or when idle.
- */
-
-// Callback to run at when the G30 return move is finished
-static void _planner_done_callback(float* vect, bool* flag)
-{
- cm2.waiting_for_planner_done = false;
-}
-
-stat_t cm_return_from_hold_context() // LATER: if value == true return with offset corrections
-{
- // Must be in the secondary CM and fully stopped
- if ((cm != &cm2) || (cm->motion_state != MOTION_STOP)) {
- return (STAT_COMMAND_NOT_ACCEPTED);
- }
-
- // *** While still in secondary machine:
-/*
- if (cm->machine_state == MACHINE_ALARM) {
- cm_spindle_off_immediate();
- cm_coolant_off_immediate();
-*/
- // restart spindle (with optional dwell)
-
- // restart coolant
-
- // perform the G30 move and queue a wait
- float target[] = { 0,0,0,0,0,0 }; // LATER: Make this move return through XY, then Z
- bool flags[] = { 0,0,0,0,0,0 };
- cm_goto_g30_position(target, flags); // initiate a return move
- cm->waiting_for_planner_done = true; // indicates running the final G30 move in the secondary
- mp_queue_command(_planner_done_callback, nullptr, nullptr);
- cm_select = CM_SECONDARY_RETURN;
- return (STAT_OK);
-
- // return_to_primary completes in cm_return_callback() after the wait
-}
-
-stat_t cm_return_from_hold_callback()
-{
- if (cm_select != CM_SECONDARY_RETURN) { // exit if not in secondary planner
- return (STAT_NOOP);
- }
- if (cm->waiting_for_planner_done) { // sync to planner move ends (via _return_move_callback)
- return (STAT_EAGAIN);
- }
-
- // return to primary machine
- cm = &cm1;
- mp = (mpPlanner_t *)cm->mp; // cm->mp is a void pointer
- mr = mp->mr;
- cm_select = CM_PRIMARY;
-
- cm->hold_state = FEEDHOLD_OFF;
- if (mp_has_runnable_buffer(mp)) { //+++++ Should MP be passed or global?
- cm_set_motion_state(MOTION_RUN);
- cm_cycle_start();
- st_request_exec_move();
- sr_request_status_report(SR_REQUEST_IMMEDIATE);
- } else {
- cm_set_motion_state(MOTION_STOP);
- cm_cycle_end();
- }
- return (STAT_OK);
-}
-
-/*
- * cm_queue_flush() - Flush planner queue and correct model positions
- */
-
-void cm_queue_flush()
-{
- if (mp_runtime_is_idle()) { // can't flush planner during movement
- mp_flush_planner(mp); // +++++ Active planner. Potential cleanup
-
- for (uint8_t axis = AXIS_X; axis < AXES; axis++) { // set all positions
- cm_set_position(axis, mp_get_runtime_absolute_position(axis));
- }
- if(cm->hold_state == FEEDHOLD_HOLD) { // end feedhold if we're in one
- cm_end_hold();
- }
- cm->queue_flush_state = FLUSH_OFF;
- qr_request_queue_report(0); // request a queue report, since we've changed the number of buffers available
- }
-}
-
-/******************************
- * Program Functions (4.3.10) *
- ******************************/
+/***********************************************************************************
+ **** Program Functions (4.3.10) ***************************************************
+ ***********************************************************************************/
/* This group implements stop, start, and end functions.
* It is extended beyond the NIST spec to handle various situations.
*
@@ -2163,7 +1612,9 @@ void cm_program_end()
mp_queue_command(_exec_program_finalize, value, flags);
}
-
+/***********************************************************************************
+ **** Additional Fucntions *********************************************************
+ ***********************************************************************************/
/*
* cm_json_command() - M100
*/
@@ -2186,7 +1637,8 @@ stat_t cm_json_wait(char *json_string)
**************************************/
/***********************************************************************************
- * CONFIGURATION AND INTERFACE FUNCTIONS
+ **** CONFIGURATION AND INTERFACE FUNCTIONS ****************************************
+ ***********************************************************************************
* Functions to get and set variables from the cfgArray table
* These functions are not part of the NIST defined functions
***********************************************************************************/
diff --git a/g2core/canonical_machine.h b/g2core/canonical_machine.h
index 90abd505..821bb900 100644
--- a/g2core/canonical_machine.h
+++ b/g2core/canonical_machine.h
@@ -289,9 +289,10 @@ typedef struct cmToolTable { // struct to keep a global tool tabl
/**** Externs - See canonical_machine.cpp for allocation ****/
-extern cmMachine_t *cm; // pointer to active canonical machine
-extern cmMachine_t cm1; // canonical machine primary machine
-extern cmMachine_t cm2; // canonical machine secondary machine
+extern cmMachineSelect cm_select; // CM_PRIMARY, CM_SECONDARY, CM_SECONDARY_RETURN
+extern cmMachine_t *cm; // pointer to active canonical machine
+extern cmMachine_t cm1; // canonical machine primary machine
+extern cmMachine_t cm2; // canonical machine secondary machine
extern cmToolTable_t tt;
/*****************************************************************************
@@ -433,7 +434,20 @@ stat_t cm_mfo_control(const float P_word, const bool P_flag); // M50
stat_t cm_mto_control(const float P_word, const bool P_flag); // M50.1
// See spindle.cpp for cm_sso_control() // M51
-// Feedhold and related functions
+// Program Functions (4.3.10)
+void cm_cycle_start(void); // (no Gcode)
+void cm_cycle_end(void); // (no Gcode)
+void cm_canned_cycle_end(void); // end of canned cycle
+void cm_program_stop(void); // M0
+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_wait(char *json_string); // M102
+
+/*--- Cycles ---*/
+
+// Feedhold and related functions (cycle_feedhold.cpp)
void cm_request_feedhold(void);
void cm_request_end_hold(void);
void cm_request_queue_flush(void);
@@ -450,32 +464,19 @@ stat_t cm_return_from_hold_callback(void); // main loop cal
void cm_queue_flush(void); // flush serial and planner queues with coordinate resets
void cm_end_queue_flush(void);
-// Program Functions (4.3.10)
-void cm_cycle_start(void); // (no Gcode)
-void cm_cycle_end(void); // (no Gcode)
-void cm_canned_cycle_end(void); // end of canned cycle
-void cm_program_stop(void); // M0
-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_wait(char *json_string); // M102
-
-/*--- Cycles ---*/
-
-// Homing cycles
+// Homing cycles (cycle_homing.cpp)
stat_t cm_homing_cycle_start(const float axes[], const bool flags[]); // G28.2
stat_t cm_homing_cycle_start_no_set(const float axes[], const bool flags[]); // G28.4
stat_t cm_homing_cycle_callback(void); // G28.2/.4 main loop callback
-// Probe cycles
+// Probe cycles (cycle_probing.cpp)
stat_t cm_straight_probe(float target[],
bool flags[],
bool failure_is_fatal,
bool moving_toward_switch); // G38.x
stat_t cm_probing_cycle_callback(void); // G38.x main loop callback
-// Jogging cycle
+// Jogging cycle (cycle_jogging.cpp)
stat_t cm_jogging_cycle_callback(void); // jogging cycle main loop
stat_t cm_jogging_cycle_start(uint8_t axis); // {"jogx":-100.3}
float cm_get_jogging_dest(void); // get jogging destination
diff --git a/g2core/cycle_feedhold.cpp b/g2core/cycle_feedhold.cpp
new file mode 100644
index 00000000..a0a5efa1
--- /dev/null
+++ b/g2core/cycle_feedhold.cpp
@@ -0,0 +1,377 @@
+/*
+ * cycle_feedhold.cpp - canonical machine feedhold processing
+ * This file is part of the g2core project
+ *
+ * Copyright (c) 2010 - 2017 Alden S Hart, Jr.
+ * Copyright (c) 2014 - 2017 Robert 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 .
+ *
+ * As a special exception, you may use this file as part of a software library without
+ * restriction. Specifically, if other files instantiate templates or use macros or
+ * inline functions from this file, or you compile this file and link it with other
+ * files to produce an executable, this file does not by itself cause the resulting
+ * executable to be covered by the GNU General Public License. This exception does not
+ * however invalidate any other reasons why the executable file might be covered by the
+ * GNU General Public License.
+ *
+ * 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.
+ */
+
+#include "g2core.h" // #1
+#include "config.h" // #2
+#include "gcode.h" // #3
+#include "canonical_machine.h"
+#include "planner.h"
+#include "stepper.h"
+#include "spindle.h"
+#include "coolant.h"
+#include "util.h"
+
+/***********************************************************************************
+ **** CODE *************************************************************************
+ ***********************************************************************************/
+/*
+ * Feedholds, queue flushes and end_holds are all related. The request functions set flags
+ * or change state to "REQUESTED". The sequencing callback interprets the flags as so:
+ * - A feedhold request received during motion should be honored
+ * - A feedhold request received during a feedhold should be ignored
+ * - A feedhold request received during a motion stop should be ignored
+ *
+ * - A queue flush request should only be honored while in a feedhold
+ * - Said queue flush request received during a feedhold should be deferred until
+ * the feedhold enters a HOLD state (i.e. until deceleration is complete and motors stop).
+ * - A queue flush request received during a motion stop should be honored
+ *
+ * - An end_hold (cycle start) request should only be honored while in a feedhold
+ * - Said end_hold request received during a feedhold should be deferred until the
+ * feedhold enters a HOLD state (i.e. until deceleration is complete).
+ * If a queue flush request is also present the queue flush should be done first
+ *
+ * Below the request level, feedholds work like this:
+ * - The hold is initiated by calling cm_start_hold(). cm->hold_state is set to
+ * FEEDHOLD_SYNC, motion_state is set to MOTION_HOLD, and the spindle is turned off
+ * (if it it on). The remainder of feedhold
+ * processing occurs in plan_exec.c in the mp_exec_aline() function.
+ *
+ * - MOTION_HOLD and FEEDHOLD_SYNC tells mp_exec_aline() to begin feedhold processing
+ * after the current move segment is finished (< 5 ms later). (Cases handled by
+ * feedhold processing are listed in plan_exec.c).
+ *
+ * - FEEDHOLD_SYNC causes the current move in mr to be replanned into a deceleration.
+ * If the distance remaining in the executing move is sufficient for a full deceleration
+ * then motion will stop in the current block. Otherwise the deceleration phase
+ * will extend across as many blocks necessary until one will stop.
+ *
+ * - Once deceleration is complete hold state transitions to FEEDHOLD_FINALIZING and
+ * the distance remaining in the bf last block is replanned up from zero velocity.
+ * The move in the bf block is NOT released (unlike normal operation), as it will
+ * be used again to restart from hold.
+ *
+ * - When cm_end_hold() is called it releases the hold, restarts the move and restarts
+ * the spindle if the spindle is active.
+ */
+
+/* With the addition of the secondary CM, feedhold state management gets tricky.
+ What you see below is a temporary solution until we decide the general solution.
+
+ The general solution causes a feedhold from the primary context to switch
+ into the secondary context to perform feedhold actions. When in the secondary
+ context an additional feedhold will perform the usual STOP operation, but
+ will remain in the secondary context, and therefore not perform any feedhold
+ actions (lifts, spindle, etc.). This is needed to support homing and probing
+ operations from within the secondary context.
+
+ Oddities of the general solution:
+ - Should we allow a feedhold to be performed if the tool is not moving?
+ Right now we don't, but with the secondary context this might be useful
+
+ What you see here is a Q&D to only allow feedholds from the primary context.
+ It has the following limitations:
+ - Feedhold requests are only honored form the primary context
+ - Queue flush requests are only honored form the primary context
+ - Machine alarm state is not (yet) taken into account in feedhold sequencing and restart
+ */
+
+/*
+ * cm_request_feedhold()
+ * cm_request_end_hold()
+ * cm_request_queue_flush()
+ * cm_feedhold_sequencing_callback() - sequence feedhold, queue_flush, and end_hold requests
+ */
+
+void cm_request_feedhold(void)
+{
+ // do not generate a feedhold request from the secondary context
+ if (cm_select != CM_PRIMARY) {
+ return;
+ }
+ // only generate request if not already in a feedhold and the machine is in motion
+ if ((cm1.hold_state == FEEDHOLD_OFF) && (cm1.motion_state != MOTION_STOP)) {
+ cm1.hold_state = FEEDHOLD_REQUESTED;
+ }
+}
+
+void cm_request_end_hold(void) // This is usually requested form the secondary context
+{
+ if (cm1.hold_state != FEEDHOLD_OFF) {
+ cm1.end_hold_requested = true;
+ }
+}
+
+void cm_request_queue_flush()
+{
+ // do not generate a queue flush request from the secondary context
+ if (cm_select != CM_PRIMARY) {
+ return;
+ }
+ if ((cm1.hold_state != FEEDHOLD_OFF) && // don't honor request unless you are in a feedhold
+ (cm1.queue_flush_state == FLUSH_OFF)) { // ...and only once
+ cm1.queue_flush_state = FLUSH_REQUESTED; // request planner flush once motion has stopped
+
+ // NOTE: we used to flush the input buffers, but this is handled in xio *prior* to queue flush now
+ }
+}
+
+stat_t cm_feedhold_sequencing_callback()
+{
+ if (cm1.hold_state == FEEDHOLD_REQUESTED) {
+ cm_start_hold(); // feed won't run unless the machine is moving
+ }
+ if (cm1.hold_state == FEEDHOLD_FINALIZING) {
+ cm1.hold_state = FEEDHOLD_HOLD;
+ cm_switch_to_hold_context(); // perform Z lift, spindle & coolant operations
+ }
+ if (cm1.queue_flush_state == FLUSH_REQUESTED) {
+ cm_queue_flush(); // queue flush won't run until runtime is idle
+ }
+ if (cm1.end_hold_requested) {
+ if (cm1.queue_flush_state == FLUSH_OFF) { // either no flush or wait until it's done flushing
+ cm_end_hold();
+ }
+ }
+ return (STAT_OK);
+}
+
+/*
+ * cm_has_hold() - return true if a hold condition exists (or a pending hold request)
+ * cm_start_hold() - start a feedhhold by signalling the exec
+ * cm_end_hold() - end a feedhold by returning the system to normal operation
+ */
+bool cm_has_hold()
+{
+ return (cm1.hold_state != FEEDHOLD_OFF);
+}
+
+void cm_start_hold()
+{
+ if (mp_has_runnable_buffer(mp)) { //+++++ // meaning there's something running
+ cm_set_motion_state(MOTION_HOLD);
+ cm->hold_state = FEEDHOLD_SYNC; // invokes hold from aline execution
+ }
+}
+
+void cm_end_hold()
+{
+ if (cm1.hold_state == FEEDHOLD_HOLD) {
+ cm1.end_hold_requested = false;
+ cm_return_from_hold_context();
+ }
+}
+
+/*
+ * cm_switch_to_hold_context() - switch to secondary machine context
+ *
+ * Moving between contexts is only safe when the machine is completely stopped
+ * either during a feedhold or when idle.
+ */
+
+stat_t cm_switch_to_hold_context()
+{
+ // Must be in the primary CM and fully stopped in a hold
+ if ((cm != &cm1) || (cm->hold_state != FEEDHOLD_HOLD)) {
+ return (STAT_COMMAND_NOT_ACCEPTED);
+ }
+
+ // copy the primary canonical machine to the secondary,
+ // fix the planner pointer, and reset the secondary planner
+ memcpy(&cm2, &cm1, sizeof(cmMachine_t));
+ cm2.mp = &mp2;
+ planner_reset((mpPlanner_t *)cm2.mp); // mp is a void pointer
+
+ // set parameters in cm, gm and gmx so you can actually use it
+ cmMachine_t *_cm = &cm2;
+ _cm->hold_state = FEEDHOLD_OFF;
+ _cm->gm.motion_mode = MOTION_MODE_CANCEL_MOTION_MODE;
+ _cm->gm.absolute_override = ABSOLUTE_OVERRIDE_OFF;
+ _cm->gm.feed_rate = 0;
+
+ // clear the target and set the positions to the current hold position
+ memset(&(_cm->gm.target), 0, sizeof(_cm->gm.target));
+ copy_vector(_cm->gm.target_comp, cm->gm.target_comp); // preserve original Kahan compensation
+ copy_vector(_cm->gmx.position, mr->position);
+ copy_vector(mp2.position, mr->position);
+ copy_vector(mr2.position, mr->position);
+
+ // reassign the globals to the secondary CM
+ cm = &cm2;
+ mp = (mpPlanner_t *)cm->mp; // mp is a void pointer
+ mr = mp->mr;
+ cm_select = CM_SECONDARY;
+
+ // set motion state and ACTIVE_MODEL. This must be performed after cm is set to cm2
+ cm_set_g30_position();
+ cm_set_motion_state(MOTION_STOP);
+
+ // optional Z lift
+ if (fp_NOT_ZERO(cm->feedhold_z_lift)) {
+ float stored_distance_mode = cm_get_distance_mode(MODEL);
+ cm_set_distance_mode(INCREMENTAL_DISTANCE_MODE);
+ bool flags[] = { 0,0,1,0,0,0 };
+ float target[] = { 0,0, cm->feedhold_z_lift, 0,0,0 };
+ cm_straight_traverse(target, flags);
+ cm_set_distance_mode(stored_distance_mode);
+ }
+
+ // optional spindle stop
+ if (spindle.pause_on_hold) {
+
+ }
+ return (STAT_OK);
+}
+
+/*
+ * cm_return_from_hold_context() - initiate return from secondary context
+ * cm_return_from_hold_callback() - main loop callback to finsh return once moves are done
+ * _planner_done_callback() - callback to sync to end of planner operations
+ *
+ * Moving between contexts is only safe when the machine is completely stopped
+ * either during a feedhold or when idle.
+ */
+
+// Callback to run at when the G30 return move is finished
+static void _planner_done_callback(float* vect, bool* flag)
+{
+ cm2.waiting_for_planner_done = false;
+}
+
+stat_t cm_return_from_hold_context() // LATER: if value == true return with offset corrections
+{
+ // Must be in the secondary CM and fully stopped
+ if ((cm != &cm2) || (cm->motion_state != MOTION_STOP)) {
+ return (STAT_COMMAND_NOT_ACCEPTED);
+ }
+
+ // *** While still in secondary machine:
+/*
+ if (cm->machine_state == MACHINE_ALARM) {
+ cm_spindle_off_immediate();
+ cm_coolant_off_immediate();
+*/
+ // restart spindle (with optional dwell)
+
+ // restart coolant
+
+ // perform the G30 move and queue a wait
+ float target[] = { 0,0,0,0,0,0 }; // LATER: Make this move return through XY, then Z
+ bool flags[] = { 0,0,0,0,0,0 };
+ cm_goto_g30_position(target, flags); // initiate a return move
+ cm->waiting_for_planner_done = true; // indicates running the final G30 move in the secondary
+ mp_queue_command(_planner_done_callback, nullptr, nullptr);
+ cm_select = CM_SECONDARY_RETURN;
+ return (STAT_OK);
+
+ // return_to_primary completes in cm_return_callback() after the wait
+}
+
+stat_t cm_return_from_hold_callback()
+{
+ if (cm_select != CM_SECONDARY_RETURN) { // exit if not in secondary planner
+ return (STAT_NOOP);
+ }
+ if (cm->waiting_for_planner_done) { // sync to planner move ends (via _return_move_callback)
+ return (STAT_EAGAIN);
+ }
+
+ // return to primary machine
+ cm = &cm1;
+ mp = (mpPlanner_t *)cm->mp; // cm->mp is a void pointer
+ mr = mp->mr;
+ cm_select = CM_PRIMARY;
+
+ cm->hold_state = FEEDHOLD_OFF;
+ if (mp_has_runnable_buffer(mp)) { //+++++ Should MP be passed or global?
+ cm_set_motion_state(MOTION_RUN);
+ cm_cycle_start();
+ st_request_exec_move();
+ sr_request_status_report(SR_REQUEST_IMMEDIATE);
+ } else {
+ cm_set_motion_state(MOTION_STOP);
+ cm_cycle_end();
+ }
+ return (STAT_OK);
+}
+
+/* Queue Flush operation
+ *
+ * This one's complicated. See here first:
+ * https://github.com/synthetos/g2/wiki/Alarm-Processing
+ * https://github.com/synthetos/g2/wiki/Job-Exception-Handling
+ *
+ * We want to use queue flush for a few different use cases, as per the above wiki pages.
+ * The % behavior implements Exception Handling cases 1 and 2 - Stop a Single Move and
+ * Stop Multiple Moves. This is complicated further by the processing in single USB and
+ * dual USB being different. Also, the state handling is located in xio.cpp / readline(),
+ * controller.cpp _dispatch_kernel() and cm_request_queue_flush(), below.
+ * So it's documented here.
+ *
+ * Single or Dual USB Channels:
+ * - If a % is received outside of a feed hold or ALARM state, ignore it.
+ * Change the % to a ; comment symbol (xio)
+ *
+ * Single USB Channel Operation:
+ * - Enter a feedhold (!)
+ * - Receive a queue flush (%) Both dispatch it and store a marker (ACK) in the input
+ * buffer in place of the the % (xio)
+ * - Execute the feedhold to a hold condition (plan_exec)
+ * - Execute the dispatched % to flush queues (canonical_machine)
+ * - Silently reject any commands up to the % in the input queue (controller)
+ * - When ETX is encountered transition to STOP state (controller/canonical_machine)
+ *
+ * Dual USB Channel Operation:
+ * - Same as above except that we expect the % to arrive on the control channel
+ * - The system will read and dump all commands in the data channel until either a
+ * clear is encountered ({clear:n} or $clear), or an ETX is encountered on either
+ * channel, but it really should be on the data channel to ensure all queued commands
+ * are dumped. It is the host's responsibility to both write the clear (or ETX), and
+ * to ensure that it either arrives on the data channel or that the data channel is
+ * empty before writing it to the control channel.
+ */
+/*
+ * cm_queue_flush() - Flush planner queue and correct model positions
+ */
+
+void cm_queue_flush()
+{
+ if (mp_runtime_is_idle()) { // can't flush planner during movement
+ mp_flush_planner(mp); // +++++ Active planner. Potential cleanup
+
+ for (uint8_t axis = AXIS_X; axis < AXES; axis++) { // set all positions
+ cm_set_position(axis, mp_get_runtime_absolute_position(axis));
+ }
+ if(cm->hold_state == FEEDHOLD_HOLD) { // end feedhold if we're in one
+ cm_end_hold();
+ }
+ cm->queue_flush_state = FLUSH_OFF;
+ qr_request_queue_report(0); // request a queue report, since we've changed the number of buffers available
+ }
+}
+
diff --git a/g2core/g2core.cppproj b/g2core/g2core.cppproj
index c8cc86a8..af27d1e0 100644
--- a/g2core/g2core.cppproj
+++ b/g2core/g2core.cppproj
@@ -1521,6 +1521,9 @@
bin\TestQuadratic\
+
+ compile
+
compile
@@ -1692,6 +1695,9 @@
compile
+
+ compile
+
compile