mirror of
https://github.com/synthetos/g2.git
synced 2026-02-07 12:17:20 +08:00
979 lines
39 KiB
C++
979 lines
39 KiB
C++
/*
|
|
* cycle_feedhold.cpp - canonical machine feedhold processing
|
|
* This file is part of the g2core project
|
|
*
|
|
* Copyright (c) 2010 - 2018 Alden S Hart, Jr.
|
|
* Copyright (c) 2014 - 2018 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 "canonical_machine.h"
|
|
#include "config.h" // #2
|
|
#include "coolant.h"
|
|
#include "g2core.h" // #1
|
|
#include "gcode.h" // #3
|
|
#include "plan_arc.h"
|
|
#include "planner.h"
|
|
#include "safety_manager.h"
|
|
#include "spindle.h"
|
|
#include "stepper.h"
|
|
#include "util.h"
|
|
#include "xio.h"
|
|
|
|
// static void _start_feedhold(void);
|
|
void _start_cycle_restart(void);
|
|
void _start_queue_flush(void);
|
|
void _start_job_kill(void);
|
|
|
|
// Feedhold actions
|
|
stat_t _feedhold_skip(void);
|
|
stat_t _feedhold_no_actions(void);
|
|
stat_t _feedhold_with_actions(void);
|
|
stat_t _feedhold_restart_with_actions(void);
|
|
stat_t _feedhold_restart_no_actions(void);
|
|
|
|
// Feedhold exits (finalization)
|
|
stat_t _run_restart_cycle(void);
|
|
stat_t _run_queue_flush(void);
|
|
stat_t _run_program_stop(void);
|
|
stat_t _run_program_end(void);
|
|
stat_t _run_alarm(void);
|
|
stat_t _run_shutdown(void);
|
|
stat_t _run_interlock_started(void);
|
|
stat_t _run_interlock_ended(void);
|
|
stat_t _run_reset_position(void);
|
|
|
|
/****************************************************************************************
|
|
* OPERATIONS AND ACTIONS
|
|
*
|
|
* Operations provide a way to assemble a multi-step function from underlying actions,
|
|
* then execute the actions in sequence until the operation either completes or returns
|
|
* an error. It handles actions that complete immediately (synchronous) as well as long
|
|
* running asynchronous operations such as a series of multiple moves.
|
|
*
|
|
* It works by assembling an operation using a series of add_action() calls, then running
|
|
* the operation by one or more run_operation() calls. The cm_operation_sequencing_callback()
|
|
* both runs long-running operations, as well as queues operation requests that must
|
|
* run sequentially of have other conditions.
|
|
*
|
|
* Actions are coded to return:
|
|
* STAT_OK - successful completion of the action
|
|
* STAT_EAGAIN - ran to continuation - the action needs to be called again to complete
|
|
* STAT_XXXXX - any other status is an error that should quit the operation
|
|
*
|
|
* run_operation() returns:
|
|
* STAT_NOOP - no operation is set up, but it's OK to call the operation runner
|
|
* STAT_OK - operation has completed successfully
|
|
* STAT_EAGAIN - operation needs to be re-entered to complete (via operation callback)
|
|
* STAT_XXXXX - any other status is an error that quits the operation
|
|
*
|
|
* Current constraints to keep this simple (at least for now):
|
|
* - Operations run to completion. They cannot be canceled, or preempted by other operations
|
|
* - Actions cannot be added to an operation once it is being run (without explicitly declaring it will)
|
|
* - Actions do not have parameters. Use the CM context if needed (e.g. hold_type)
|
|
*/
|
|
|
|
/*** Object Definitions ***/
|
|
|
|
#define ACTION_MAX 6 // maximum actions that can be queued for an operation
|
|
typedef stat_t (*action_exec_t)(); // callback to action execution function
|
|
|
|
typedef struct cmAction { // struct to manage execution of operations
|
|
uint8_t number; // DIAGNOSTIC for easier debugging. Not used functionally.
|
|
struct cmAction *nx; // static pointer to next buffer
|
|
action_exec_t func; // callback to operation action function. nullptr == disabled
|
|
|
|
void reset() { // clears function pointer
|
|
func = nullptr;
|
|
};
|
|
} cmAction_t;
|
|
|
|
typedef struct cmOperation { // operation runner object
|
|
cmAction action[ACTION_MAX]; // singly linked list of action structures
|
|
cmAction *add; // pointer to next action to be added
|
|
cmAction *run; // pointer to action being executed
|
|
bool in_operation; // set true when an operation is running
|
|
|
|
void reset() {
|
|
for (uint8_t i = 0; i < ACTION_MAX; i++) {
|
|
action[i].reset(); // reset the action controller object
|
|
action[i].number = i; // DIAGNOSTIC only. Otherwise not used
|
|
action[i].nx = &action[i + 1]; // link to the next action
|
|
}
|
|
action[ACTION_MAX - 1].nx = nullptr; // set last action (end of list)
|
|
add = action; // initialize pointers to first action struct
|
|
run = action;
|
|
in_operation = false;
|
|
};
|
|
|
|
stat_t repack() {
|
|
if (run == action) {
|
|
// nothing has been run, and there's no more room, bail
|
|
return (STAT_INPUT_EXCEEDS_MAX_LENGTH);
|
|
}
|
|
cmAction *old_add = add; // may be nullptr
|
|
add = action;
|
|
|
|
// Move the running and upcoming actions to the front
|
|
while (run != old_add) {
|
|
add->func = run->func;
|
|
add = add->nx;
|
|
run = run->nx;
|
|
}
|
|
cmAction *cleanup = run;
|
|
run = action;
|
|
|
|
// reset any remaining actions
|
|
while (cleanup != nullptr) {
|
|
cleanup->reset();
|
|
cleanup = cleanup->nx;
|
|
}
|
|
|
|
return (STAT_OK);
|
|
};
|
|
|
|
stat_t add_action(stat_t (*action_exec)(), bool allow_add_from_operation = false) {
|
|
if (in_operation) {
|
|
if (!allow_add_from_operation) {
|
|
return (STAT_COMMAND_NOT_ACCEPTED); // can't add
|
|
}
|
|
}
|
|
if (add == nullptr) {
|
|
// no more room, try to repack
|
|
ritorno(repack());
|
|
}
|
|
add->func = action_exec;
|
|
add = add->nx;
|
|
return (STAT_OK);
|
|
};
|
|
|
|
stat_t run_operation(void) {
|
|
if (run->func == nullptr) {
|
|
return (STAT_NOOP);
|
|
} // not an error. This is normal.
|
|
in_operation = true; // disable add_action during operations
|
|
|
|
stat_t status;
|
|
while ((status = run->func()) == STAT_OK) {
|
|
run = run->nx;
|
|
if (run == nullptr || run->func == nullptr) { // operation has completed
|
|
reset(); // setup for next operation
|
|
return (STAT_OK);
|
|
}
|
|
}
|
|
if (status == STAT_EAGAIN) {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
reset(); // reset operation if action threw an error
|
|
return (status); // return error code
|
|
};
|
|
|
|
} cmOperation_t;
|
|
|
|
cmOperation_t op; // operations runner object
|
|
|
|
/****************************************************************************************
|
|
* cm_operation_init()
|
|
*/
|
|
|
|
void cm_operation_init() { op.reset(); }
|
|
|
|
/****************************************************************************************
|
|
**** Feedhold and Related Functions ****************************************************
|
|
****************************************************************************************/
|
|
/*
|
|
* Feedholds, queue flushes and the various feedhold exits are all related.
|
|
* These are performed in this file and in plan_exec.cpp. Feedholds are implemented
|
|
* as a state machine (cmFeedholdState) that runs in these files using Operations.
|
|
*
|
|
* BACKGROUND: There are 2 planners: p1 (primary planner) and p2 (secondary planner).
|
|
*
|
|
* A feedhold (!) received while in p1 stops motion in p1 and optionally transitions to p2,
|
|
* where feedhold entry actions such as Z lift, parking moves, spindle and coolant pause
|
|
* are run. While in p2 (almost) all machine operations are available. There are different
|
|
* Types of feedholds; Feedhold with action and feedholds with no action transition to p2,
|
|
* but others do not (e.g. feedhold with sync).
|
|
*
|
|
* A cycle_start (~) returns to p1 and exits the feedhold, performing exit actions if entry
|
|
* actions were performed. Motion resumes in p1 from the held point.
|
|
*
|
|
* A queue_flush (%) returns to p1 and exits the feedhold, performing exit actions if entry
|
|
* actions were performed. The p1 planner is flushed, and motion does not resume. The machine
|
|
* executes a program_stop, and ends in the STOP state.
|
|
*
|
|
* A feedhold (!) received while in p2 (a feedhold within a feedhold - very Inception)
|
|
* stops motion in p2 and flushes the p2 planner. Control remains in p2.
|
|
*
|
|
* Other variants of feedhold and exit exist, but these are invoked internally only to put
|
|
* the machine in END, ALARM, SHUTDOWN, INTERLOCK and other states.
|
|
*/
|
|
/*
|
|
* Feedhold State Machine Processing
|
|
*
|
|
* Feedhold is run as a state machine using the following states:
|
|
* FEEDHOLD_OFF - Not in a feedhold. May be in a cycle or not running
|
|
* FEEDHOLD_HOLD - Feedhold stable state. Achieved when machine has stopped in the hold.
|
|
* FEEDHOLD_XXXX - Any other feedhold state is transient; the machine is headed towards
|
|
* FEEDHOLD_HOLD or FEEDHOLD_OFF.
|
|
*
|
|
* For internal purposes, any state other than FEEDHOLD_OFF is considered to be in a hold.
|
|
*
|
|
* Feedhold processing performs the following (in rough sequence order):
|
|
*
|
|
* (0) - Feedhold is request by calling cm_feedhold_request()
|
|
*
|
|
* Control transfers to plan_exec.cpp feedhold functions:
|
|
*
|
|
* (1) - Feedhold arrives while we are in the middle executing of a block
|
|
* (1a) - The block is currently accelerating - wait for the end of acceleration
|
|
* (1b) - The block is in a head, but has not started execution yet - start deceleration
|
|
* (1b1) - The deceleration fits into the current block
|
|
* (1b2) - The deceleration does not fit and needs to continue in the next block
|
|
* (1c) - The block is in a body - start deceleration
|
|
* (1c1) - The deceleration fits into the current block
|
|
* (1c2) - The deceleration does not fit and needs to continue in the next block
|
|
* (1d) - The block is currently in the tail - wait until the end of the block
|
|
* (1e) - We have a new block and a new feedhold request that arrived at EXACTLY the same time
|
|
* (unlikely, but handled as 1b).
|
|
*
|
|
* (2) - The block has decelerated to some velocity > zero, so needs continuation into next block
|
|
* (3) - The end of deceleration is detected inline in mp_exec_aline()
|
|
* (4) - Finished all runtime work, now wait for motion to stop at HOLD point. When it does:
|
|
* (4a) - It's a homing or probing feedhold - ditch the remaining buffer & go directly to OFF
|
|
* (4b) - It's a p2 feedhold - ditch the remaining buffer & signal we want a p2 queue flush
|
|
* (4c) - It's a normal feedhold - signal we want the p2 entry actions to execute
|
|
*
|
|
* Control transfers back to cycle_feedhold.cpp feedhold functions:
|
|
*
|
|
* (5) - Run the P2 entry actions and transition to HOLD state when complete
|
|
* (6) - Remove the hold state / there is queued motion - see cycle_feedhold.cpp
|
|
* (7) - Remove the hold state / there is no queued motion - see cycle_feedhold.cpp
|
|
*/
|
|
|
|
/****************************************************************************************
|
|
* cm_operation_runner_callback() - run feedhold operations and sequence queued requests
|
|
*
|
|
* Operations are requested by calling their repective request function, e.g. cm_request_feedhold().
|
|
* The operation callback runs the current operation, and sequences requests that must be queued.
|
|
* Expected behaviors: (no-hold means machine is not in hold, etc)
|
|
*
|
|
* (no-cycle) ! No action. Feedhold is not run (nothing to hold!)
|
|
* (no-hold) ~ No action. Cannot exit a feedhold that does not exist
|
|
* (no-hold) % No action. Queue flush is not honored except during a feedhold
|
|
* (in-cycle) ! Start a hold to motion in the p1 planner
|
|
* (in-hold) ~ Wait for feedhold actions to complete, exit feedhold, resume motion
|
|
* (in-hold) % Wait for feedhold actions to complete, exit feedhold, do not resume motion
|
|
* (in-p2) ! If moving in p2 during a p1 hold ! will perform a SYNC type hold in p2
|
|
* (in-cycle) !~ Start a feedhold, do enter and exit actions, exit feedhold, resume motion
|
|
* (in-cycle) !% Start a feedhold, do enter and exit actions, exit feedhold, do not resume motion
|
|
* (in-cycle) !%~ Same as above
|
|
* (in-cycle) !~% Same as above (this one's an anomaly, but the intent would be to Q flush)
|
|
*
|
|
* The requests are arranged in priority order, highest priority first.
|
|
* Note that feedholds from p1 are initiated immediately from cm_request_feedhld(),
|
|
* and are not triggered here. Only queued p2 feedholds (feedhold in feedhold) are
|
|
* handled in the sequencer.
|
|
*/
|
|
|
|
stat_t cm_operation_runner_callback() {
|
|
if (cm1.job_kill_state == JOB_KILL_REQUESTED) { // job kill must wait for any active hold to complete
|
|
_start_job_kill();
|
|
}
|
|
// if (cm1.hold_state == FEEDHOLD_REQUESTED) { // look for a queued p2 feedhold
|
|
// _start_feedhold();
|
|
// }
|
|
if (cm1.queue_flush_state == QUEUE_FLUSH_REQUESTED) { // look for a queued flush request
|
|
_start_queue_flush();
|
|
}
|
|
if (cm1.cycle_start_state == CYCLE_START_REQUESTED) { // look for a queued cycle start ot restart
|
|
_start_cycle_restart();
|
|
}
|
|
|
|
// run the operation or operation continuation (callback)
|
|
return (op.run_operation());
|
|
}
|
|
|
|
/*
|
|
* cm_has_hold() - return true if a hold condition exists (or a pending hold request)
|
|
*/
|
|
|
|
bool cm_has_hold() { return (cm1.hold_state != FEEDHOLD_OFF); }
|
|
|
|
/*
|
|
* cm_feedhold_command_blocker() - prevents new Gcode commands from reaching the parser while feedhold is in effect
|
|
*/
|
|
|
|
stat_t cm_feedhold_command_blocker() {
|
|
if (cm1.hold_state != FEEDHOLD_OFF) {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* end state functions and helpers
|
|
*/
|
|
|
|
stat_t _run_program_stop() {
|
|
cm1.hold_state = FEEDHOLD_OFF; // clear the feedhold
|
|
cm_cycle_end(); // end cycle and run program stop
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t _run_program_end() {
|
|
cm_program_end();
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t _run_reset_position() {
|
|
cm_reset_position_to_absolute_position(cm);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t _run_job_kill();
|
|
|
|
stat_t _run_alarm() {
|
|
if (cm1.hold_state == FEEDHOLD_HOLD) {
|
|
_run_job_kill();
|
|
} else {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
cm1.machine_state = MACHINE_ALARM;
|
|
return (STAT_OK);
|
|
}
|
|
stat_t _run_shutdown() {
|
|
if (cm1.hold_state == FEEDHOLD_HOLD) {
|
|
_run_job_kill();
|
|
} else {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
cm1.machine_state = MACHINE_SHUTDOWN;
|
|
return (STAT_OK);
|
|
}
|
|
stat_t _run_interlock_started() {
|
|
cm1.machine_state = MACHINE_INTERLOCK;
|
|
safety_manager->start_interlock_after_feedhold();
|
|
return (STAT_OK);
|
|
}
|
|
stat_t _run_interlock_ended() {
|
|
if (cm1.cycle_type != CYCLE_NONE) {
|
|
cm1.machine_state = MACHINE_CYCLE;
|
|
} else {
|
|
cm1.machine_state = MACHINE_PROGRAM_END;
|
|
}
|
|
safety_manager->end_interlock_after_feedhold();
|
|
return (_run_restart_cycle());
|
|
}
|
|
|
|
/****************************************************************************************
|
|
* cm_request_cycle_start() - set request enum only
|
|
* _start_cycle_start() - run the cycle start
|
|
*/
|
|
|
|
void cm_request_cycle_start() {
|
|
if (cm1.hold_state != FEEDHOLD_OFF) { // restart from a feedhold
|
|
if (cm1.queue_flush_state == QUEUE_FLUSH_REQUESTED) { // possible race condition. Flush wins
|
|
cm1.cycle_start_state = CYCLE_START_OFF;
|
|
} else {
|
|
cm1.cycle_start_state = CYCLE_START_REQUESTED;
|
|
}
|
|
} else { // execute cycle start directly
|
|
if (mp_has_runnable_buffer(&mp1)) {
|
|
cm_cycle_start();
|
|
st_request_exec_move();
|
|
}
|
|
cm1.cycle_start_state = CYCLE_START_OFF;
|
|
}
|
|
}
|
|
|
|
void _start_cycle_restart() {
|
|
// Feedhold cycle restart builds an operation to complete multiple actions
|
|
if (cm1.hold_state == FEEDHOLD_HOLD) {
|
|
cm1.cycle_start_state = CYCLE_START_OFF;
|
|
switch (cm1.hold_type) {
|
|
case FEEDHOLD_TYPE_HOLD: {
|
|
op.add_action(_feedhold_restart_no_actions);
|
|
break;
|
|
}
|
|
case FEEDHOLD_TYPE_ACTIONS: {
|
|
op.add_action(_feedhold_restart_with_actions);
|
|
break;
|
|
}
|
|
default: {
|
|
}
|
|
}
|
|
switch (cm1.hold_exit) {
|
|
case FEEDHOLD_EXIT_CYCLE: {
|
|
op.add_action(_run_restart_cycle);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_FLUSH: {
|
|
op.add_action(_run_queue_flush);
|
|
} // no break
|
|
case FEEDHOLD_EXIT_STOP: {
|
|
op.add_action(_run_program_stop);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_END: {
|
|
op.add_action(_run_program_end);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_INTERLOCK: {
|
|
op.add_action(_run_interlock_ended);
|
|
break;
|
|
}
|
|
default: {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************************
|
|
* cm_request_queue_flush() - set request enum only
|
|
* _start_queue_flush() - run a queue flush from a %
|
|
* _run_queue_flush() - run a queue flush from an action
|
|
*
|
|
* cm_request_queue_flush() should be called concurrently with xio_flush_to_command(), like this:
|
|
* { cm_request_queue_flush(); xio_flush_to_command(); }
|
|
*/
|
|
|
|
void cm_request_queue_flush() {
|
|
// Can only initiate a queue flush if in a feedhold and e-stop not pressed
|
|
if ((cm1.hold_state != FEEDHOLD_OFF) && safety_manager->can_queue_flush()) {
|
|
cm1.queue_flush_state = QUEUE_FLUSH_REQUESTED;
|
|
} else {
|
|
cm1.queue_flush_state = QUEUE_FLUSH_OFF;
|
|
}
|
|
}
|
|
|
|
void _start_queue_flush() {
|
|
devflags_t flags = DEV_IS_DATA;
|
|
|
|
// Don't initiate the queue until in HOLD state (this also means that runtime is idle)
|
|
if ((cm1.queue_flush_state == QUEUE_FLUSH_REQUESTED) && (cm1.hold_state == FEEDHOLD_HOLD)) {
|
|
xio_flush_device(flags);
|
|
if (cm1.hold_type == FEEDHOLD_TYPE_ACTIONS) {
|
|
op.add_action(_feedhold_restart_with_actions);
|
|
} else {
|
|
op.add_action(_feedhold_restart_no_actions);
|
|
}
|
|
op.add_action(_run_queue_flush);
|
|
op.add_action(_run_program_stop);
|
|
}
|
|
}
|
|
|
|
// _run_queue_flush() should not be called until motion has stopped.
|
|
// It is completely synchronous so it can be called directly;
|
|
// it does not need to be part of an operation().
|
|
|
|
stat_t _run_queue_flush() // typically runs from cm1 planner
|
|
{
|
|
// resetting the planner, but ALSO updating the planner position - do before aborts
|
|
planner_reset((mpPlanner_t *)cm->mp); // reset primary planner. also resets the mr under the planner
|
|
cm_reset_position_to_absolute_position(cm);
|
|
|
|
// now that the planner is reset, if the code in these aborts uses planner position, it'll be correct(ish)
|
|
cm_abort_arc(cm); // kill arcs so they don't just create more alines
|
|
cm_abort_homing(cm); // kill homing so it can reset cleanly
|
|
cm_abort_probing(cm); // kill probing so it can exit cleanly
|
|
cm1.queue_flush_state = QUEUE_FLUSH_OFF;
|
|
qr_request_queue_report(0); // request a queue report, since we've changed the number of buffers available
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/****************************************************************************************
|
|
* cm_request_job_kill() - Control-D handler - set request flag only by ^d
|
|
* _run_job_kill() - perform the job kill. queue flush, program_end
|
|
* _start_job_kill() - invoke the job kill function, which may start from various states
|
|
*
|
|
* cm_request_job_kill() should be called concurrently with xio_flush_to_command(), like this:
|
|
* { cm_request_job_kill(); xio_flush_to_command(); }
|
|
*
|
|
* Job kill cases: Actions:
|
|
* (0) job kill from ALARM, SHUTDOWN, PANIC no action, end request
|
|
* (1) job kill from READY, STOP, END perform PROGRAM_END
|
|
* (2a) Job kill from machining cycle hold, flush, perform PROGRAM_END
|
|
* (2b) Job kill from pending hold wait for hold to complete
|
|
* (2c) Job kill from finished hold flush, perform PROGRAM_END
|
|
* (3) Job kill from PROBE flush, perform PROGRAM_END
|
|
* (4) Job kill from HOMING flush, perform PROGRAM_END
|
|
* (5) Job kill from JOGGING flush, perform PROGRAM_END
|
|
* (6) job kill from INTERLOCK perform PROGRAM_END
|
|
*/
|
|
|
|
void cm_request_job_kill() { cm1.job_kill_state = JOB_KILL_REQUESTED; }
|
|
|
|
// _run_job_kill() should not be called until motion has stopped.
|
|
// It is completely synchronous so it can be called directly;
|
|
// it does not need to be part of an operation().
|
|
|
|
stat_t _run_job_kill() {
|
|
// if in p2 switch to p1 and copy actual position back to p1
|
|
if (cm == &cm2) {
|
|
cm = &cm1; // return to primary planner (p1)
|
|
mp = (mpPlanner_t *)cm->mp; // cm->mp is a void pointer
|
|
mr = mp->mr;
|
|
|
|
copy_vector(cm1.gmx.position, mr2.position); // transfer actual position back to p1
|
|
copy_vector(cm1.gm.target, mr2.position);
|
|
copy_vector(mp1.position, mr2.position);
|
|
copy_vector(mr1.position, mr2.position);
|
|
}
|
|
|
|
_run_queue_flush();
|
|
|
|
coolant_control_immediate(COOLANT_OFF, COOLANT_BOTH); // stop coolant
|
|
spindle_stop(); // stop spindle
|
|
|
|
cm_set_motion_state(MOTION_STOP); // set to stop and set the active model
|
|
cm->hold_state = FEEDHOLD_OFF;
|
|
cm_program_end();
|
|
|
|
rpt_exception(STAT_KILL_JOB, "Job killed by ^d");
|
|
sr_request_status_report(SR_REQUEST_IMMEDIATE);
|
|
cm->job_kill_state = JOB_KILL_OFF;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
// _start_job_kill() will be entered multiple times until the REQUEST is reset to OFF
|
|
|
|
void _start_job_kill() {
|
|
switch (cm1.machine_state) {
|
|
case MACHINE_ALARM: // Case 0's - nothing to do. turn off the request
|
|
case MACHINE_SHUTDOWN:
|
|
case MACHINE_PANIC: {
|
|
cm1.job_kill_state = JOB_KILL_OFF;
|
|
return;
|
|
}
|
|
case MACHINE_CYCLE: { // Case 2's
|
|
if (cm1.hold_state == FEEDHOLD_OFF) { // Case 2a - in cycle and not in a hold
|
|
op.add_action(_feedhold_no_actions);
|
|
// op.add_action(_run_job_kill);
|
|
}
|
|
if (cm1.hold_state == FEEDHOLD_HOLD) { // Case 2c - in a finished hold
|
|
_run_job_kill();
|
|
}
|
|
return; // Case 2b - hold is in progress. Wait for hold to reach HOLD
|
|
}
|
|
default: {
|
|
_run_job_kill();
|
|
} // Cases 1,3,4,5,6
|
|
}
|
|
}
|
|
|
|
/****************************************************************************************
|
|
* cm_request_feedhold() - request a feedhold - do not run it yet
|
|
* _feedhold_skip() - run feedhold that will skip remaining unused buffer length
|
|
* _feedhold_no_actions() - run feedhold with no entry actions
|
|
* _feedhold_with_actions() - run feedhold entry actions
|
|
* _feedhold_actions_done_callback() - planner callback to reach sync point
|
|
*
|
|
* Input arguments
|
|
* - See cmFeedholdType - how the feedhold will execute
|
|
* - See cmFeedholdFinal - the final state when the feedhold is exited
|
|
*/
|
|
|
|
void cm_request_feedhold(cmFeedholdType type, cmFeedholdExit exit) {
|
|
// Can only initiate a feedhold if not already in a feedhold
|
|
if ((cm1.hold_state == FEEDHOLD_OFF)) {
|
|
cm1.hold_type = type;
|
|
cm1.hold_exit = exit;
|
|
|
|
switch (cm1.hold_type) {
|
|
case FEEDHOLD_TYPE_HOLD: {
|
|
op.add_action(_feedhold_no_actions);
|
|
break;
|
|
}
|
|
case FEEDHOLD_TYPE_ACTIONS: {
|
|
op.add_action(_feedhold_with_actions);
|
|
break;
|
|
}
|
|
case FEEDHOLD_TYPE_SKIP: {
|
|
op.add_action(_feedhold_skip);
|
|
break;
|
|
}
|
|
default: {
|
|
}
|
|
}
|
|
switch (cm1.hold_exit) {
|
|
case FEEDHOLD_EXIT_STOP: {
|
|
op.add_action(_run_program_stop);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_END: {
|
|
op.add_action(_run_program_end);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_ALARM: {
|
|
op.add_action(_run_alarm);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_SHUTDOWN: {
|
|
op.add_action(_run_shutdown);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_INTERLOCK: {
|
|
op.add_action(_run_interlock_started);
|
|
break;
|
|
}
|
|
case FEEDHOLD_EXIT_RESET_POSITION: {
|
|
op.add_action(_run_reset_position);
|
|
break;
|
|
}
|
|
default: {
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Look for feedhols while exiting feedhold
|
|
|
|
if (cm1.hold_state == FEEDHOLD_EXIT_ACTIONS_PENDING) {
|
|
// re-load a hold
|
|
cm1.hold_type = type;
|
|
switch (cm1.hold_type) {
|
|
case FEEDHOLD_TYPE_HOLD: {
|
|
op.add_action(_feedhold_no_actions, true);
|
|
break;
|
|
}
|
|
case FEEDHOLD_TYPE_ACTIONS: {
|
|
op.add_action(_feedhold_with_actions, true);
|
|
break;
|
|
}
|
|
case FEEDHOLD_TYPE_SKIP: {
|
|
op.add_action(_feedhold_skip, true);
|
|
break;
|
|
}
|
|
default: {
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for p2 feedhold (feedhold in a feedhold)
|
|
if ((cm1.hold_state >= FEEDHOLD_HOLD) && (cm2.hold_state == FEEDHOLD_OFF) && (cm2.machine_state == MACHINE_CYCLE)) {
|
|
cm2.hold_state = FEEDHOLD_SYNC;
|
|
return;
|
|
}
|
|
|
|
// Reset the request if it's invalid
|
|
if ((cm1.machine_state != MACHINE_CYCLE) || (cm1.motion_state == MOTION_STOP)) {
|
|
cm->hold_state = FEEDHOLD_OFF; // cannot honor the feedhold request. reset it
|
|
}
|
|
}
|
|
/*
|
|
void _start_p2_feedhold()
|
|
{
|
|
// P2 feedholds only allow skip types
|
|
if ((cm2.hold_state == FEEDHOLD_REQUESTED) && (cm2.motion_state == MOTION_RUN)) {
|
|
op.add_action(_feedhold_skip);
|
|
cm2.hold_state = FEEDHOLD_SYNC;
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
* _enter_p2() - enter p2 planner with proper state transfer from p1
|
|
* _exit_p2() - reenter p1 planner with proper state transfer from p2
|
|
*
|
|
* Encapsulate entering and exiting p2, as this is tricky and must be done exactly right
|
|
*/
|
|
|
|
void _enter_p2() {
|
|
// Copy the primary canonical machine to the secondary. Here it's OK to co a memcpy.
|
|
// Set parameters in cm, gm and gmx so you can actually use it
|
|
memcpy(&cm2, &cm1, sizeof(cmMachine_t));
|
|
cm2.hold_state = FEEDHOLD_OFF;
|
|
// set gm to copy of current run buffer gm
|
|
mpBuf_t *bf = mp_get_run_buffer();
|
|
if (bf) {
|
|
cm2.gm = bf->gm;
|
|
}
|
|
cm2.gm.motion_mode = MOTION_MODE_CANCEL_MOTION_MODE;
|
|
cm2.gm.absolute_override = ABSOLUTE_OVERRIDE_OFF;
|
|
cm2.queue_flush_state = QUEUE_FLUSH_OFF;
|
|
cm2.gm.feed_rate = 0;
|
|
cm2.arc.run_state = BLOCK_INACTIVE; // Stop a running p1 arc from continuing to execute in p2
|
|
|
|
// Set mp planner to p2 and reset it
|
|
cm2.mp = &mp2;
|
|
planner_reset((mpPlanner_t *)cm2.mp); // mp is a void pointer
|
|
|
|
// Clear the target and set the positions to the current hold position
|
|
memset(&(cm2.return_flags), 0, sizeof(cm2.return_flags));
|
|
memset(&(cm2.gm.target), 0, sizeof(cm2.gm.target));
|
|
memset(&(cm2.gm.target_comp), 0, sizeof(cm2.gm.target_comp)); // zero Kahan compensation
|
|
|
|
copy_vector(cm2.gmx.position, mr1.position);
|
|
copy_vector(mp2.position, mr1.position);
|
|
copy_vector(mr2.position, mr1.position);
|
|
|
|
// Copy MR position and encoder terms - needed for following error correction state
|
|
copy_vector(mr2.target_steps, mr1.target_steps);
|
|
copy_vector(mr2.position_steps, mr1.position_steps);
|
|
copy_vector(mr2.commanded_steps, mr1.commanded_steps);
|
|
copy_vector(mr2.encoder_steps, mr1.encoder_steps); // NB: following error is re-computed in p2
|
|
|
|
// Reassign the globals to the secondary CM
|
|
cm = &cm2;
|
|
mp = (mpPlanner_t *)cm2.mp; // mp is a void pointer
|
|
mr = mp2.mr;
|
|
}
|
|
|
|
void _exit_p2() {
|
|
cm = &cm1; // return to primary planner (p1)
|
|
mp = (mpPlanner_t *)cm1.mp; // cm->mp is a void pointer
|
|
mr = mp1.mr;
|
|
}
|
|
|
|
void _check_motion_stopped() {
|
|
if (mp_runtime_is_idle()) { // wait for steppers to actually finish
|
|
|
|
mpBuf_t *bf = mp_get_r();
|
|
|
|
// Motion has stopped, so we can rely on positions and other values to be stable
|
|
// If SKIP type, discard the remainder of the block and position to the next block
|
|
// OR if the buffer is empty then there's nothing to discard, don't modify the buffer either
|
|
if ((cm->hold_type == FEEDHOLD_TYPE_SKIP) || (bf->buffer_state == MP_BUFFER_EMPTY)) {
|
|
copy_vector(mp->position, mr->position); // update planner position to the final runtime position
|
|
if (mp_get_run_buffer()) {
|
|
mp_free_run_buffer(); // advance to next block, discarding the rest of the move
|
|
}
|
|
} else { // Otherwise setup the block to complete motion (regardless of how hold will ultimately be exited)
|
|
bf->length = get_axis_vector_length(mr->position, mr->target); // update bf w/remaining length in move
|
|
bf->block_state = BLOCK_INITIAL_ACTION; // tell _exec to re-use the bf buffer
|
|
bf->buffer_state = MP_BUFFER_BACK_PLANNED; // so it can be forward planned again
|
|
bf->plannable = true; // needed so block can be re-planned
|
|
}
|
|
mr->reset(); // reset MR for next use and for forward planning
|
|
cm_set_motion_state(MOTION_STOP);
|
|
cm->hold_state = FEEDHOLD_MOTION_STOPPED;
|
|
sr_request_status_report(SR_REQUEST_IMMEDIATE);
|
|
}
|
|
}
|
|
|
|
stat_t _feedhold_skip() {
|
|
// check for actual motion to stop
|
|
if (cm1.machine_state != MACHINE_CYCLE) {
|
|
return (STAT_OK);
|
|
}
|
|
|
|
if (cm1.hold_state == FEEDHOLD_OFF) { // if entered while OFF start a feedhold
|
|
cm1.hold_type = FEEDHOLD_TYPE_SKIP;
|
|
cm1.hold_state = FEEDHOLD_SYNC; // ...FLUSH can be overridden by setting hold_exit after this function
|
|
}
|
|
if (cm1.hold_state < FEEDHOLD_MOTION_STOPPED) {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
cm1.hold_state = FEEDHOLD_OFF; // cannot be in HOLD or command won't plan (see mp_plan_block_list())
|
|
mp_replan_queue(mp_get_r()); // unplan current forward plan (bf head block), and reset all blocks
|
|
st_request_forward_plan(); // replan from the new bf buffer
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t _feedhold_no_actions() {
|
|
// initiate the feedhold
|
|
if (cm1.hold_state == FEEDHOLD_OFF) { // start a feedhold
|
|
cm1.hold_type = FEEDHOLD_TYPE_HOLD;
|
|
// cm1.hold_exit = FEEDHOLD_EXIT_STOP; // default exit for NO_ACTIONS is STOP...
|
|
|
|
if (cm1.motion_state == MOTION_STOP) { // if motion has already stopped declare that you are in a feedhold
|
|
_check_motion_stopped();
|
|
cm1.hold_state = FEEDHOLD_HOLD;
|
|
} else {
|
|
cm1.hold_state = FEEDHOLD_SYNC; // ... STOP can be overridden by setting hold_exit after this function
|
|
return (STAT_EAGAIN);
|
|
}
|
|
}
|
|
|
|
// wait until feedhold reaches the hold point
|
|
if (cm1.hold_state < FEEDHOLD_MOTION_STOPPED) {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
// complete the feedhold
|
|
mp_replan_queue(mp_get_r()); // unplan current forward plan (bf head block), and reset all blocks
|
|
st_request_forward_plan(); // replan from the new bf buffer
|
|
cm1.hold_state = FEEDHOLD_HOLD;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
void _feedhold_actions_done_callback(float *vect, bool *flag) {
|
|
cm1.hold_state = FEEDHOLD_HOLD_ACTIONS_COMPLETE; // penultimate state before transitioning to FEEDHOLD_HOLD
|
|
sr_request_status_report(SR_REQUEST_IMMEDIATE);
|
|
}
|
|
|
|
stat_t _feedhold_with_actions() // Execute Case (5)
|
|
{
|
|
// if entered while OFF start a feedhold
|
|
if (cm1.hold_state == FEEDHOLD_OFF) {
|
|
if (mp_runtime_is_idle()) { // if motion has already stopped declare that you are in a feedhold
|
|
_check_motion_stopped();
|
|
cm1.hold_state = FEEDHOLD_HOLD;
|
|
cm1.hold_type = FEEDHOLD_TYPE_HOLD; // no actions will be performed, don't try to undo them
|
|
} else {
|
|
cm1.hold_state = FEEDHOLD_SYNC; // ... STOP can be overridden by setting hold_exit after this function
|
|
return (STAT_EAGAIN);
|
|
}
|
|
}
|
|
|
|
// Code to run once motion has stopped
|
|
if (cm1.hold_state == FEEDHOLD_MOTION_STOPPED) {
|
|
cm1.hold_state = FEEDHOLD_HOLD_ACTIONS_PENDING; // next state
|
|
|
|
// check for re-entry into feedhold from a cancelled resume
|
|
if (cm != &cm2) {
|
|
_enter_p2(); // enter p2 correctly
|
|
cm_set_g30_position(); // set position to return to on exit
|
|
}
|
|
|
|
// execute feedhold actions
|
|
if (fp_NOT_ZERO(cm->feedhold_z_lift)) { // optional Z lift
|
|
bool flags[] = {0, 0, 1, 0, 0, 0};
|
|
float target[] = {0, 0, 0, 0, 0, 0}; // convert to inches if in inches mode
|
|
bool skip_move = false;
|
|
if (cm->feedhold_z_lift < 0) { // if the value is negative, we want to go to Z-max position with G53
|
|
if (cm->homed[AXIS_Z]) { // ONLY IF HOMED
|
|
cm_set_absolute_override(
|
|
MODEL, ABSOLUTE_OVERRIDE_ON_DISPLAY_WITH_OFFSETS); // Position stored in abs coords
|
|
cm_set_distance_mode(ABSOLUTE_DISTANCE_MODE); // Must run in absolute distance mode
|
|
target[AXIS_Z] = _to_inches(cm->a[AXIS_Z].travel_max);
|
|
} else {
|
|
skip_move = true;
|
|
}
|
|
} else {
|
|
cm_set_distance_mode(INCREMENTAL_DISTANCE_MODE);
|
|
target[AXIS_Z] = _to_inches(cm->feedhold_z_lift);
|
|
}
|
|
|
|
if (!skip_move) {
|
|
cm_straight_traverse(target, flags, PROFILE_NORMAL);
|
|
cm_set_distance_mode(cm1.gm.distance_mode); // restore distance mode to p1 setting
|
|
}
|
|
}
|
|
spindle_pause(); // optional spindle pause
|
|
coolant_control_sync(COOLANT_PAUSE, COOLANT_BOTH); // optional coolant pause
|
|
mp_queue_command(_feedhold_actions_done_callback, nullptr, nullptr);
|
|
return (STAT_EAGAIN);
|
|
}
|
|
|
|
// wait for hold actions to complete
|
|
if (cm1.hold_state == FEEDHOLD_HOLD_ACTIONS_PENDING) {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
|
|
// finalize feedhold entry after callback OR skipping actions (this is needed so we can return STAT_OK)
|
|
if ((cm1.hold_state == FEEDHOLD_HOLD_ACTIONS_COMPLETE) || (cm1.hold_state == FEEDHOLD_HOLD)) {
|
|
cm1.hold_state = FEEDHOLD_HOLD;
|
|
return (STAT_OK);
|
|
}
|
|
return (STAT_EAGAIN); // keep the compiler happy. Never executed.
|
|
}
|
|
|
|
/****************************************************************************************
|
|
* _feedhold_restart_no_actions() - perform hold restart with no actions
|
|
* _feedhold_restart_with_actions() - perform hold restart with actions
|
|
* _feedhold_restart_actions_done_callback()
|
|
*/
|
|
|
|
void _feedhold_restart_actions_done_callback(float *vect, bool *flag) {
|
|
cm1.hold_state = FEEDHOLD_EXIT_ACTIONS_COMPLETE; // penultimate state before transitioning to FEEDHOLD_OFF
|
|
sr_request_status_report(SR_REQUEST_IMMEDIATE);
|
|
}
|
|
|
|
//+++++ Make this more robust so it handles being called before reaching HOLD state
|
|
stat_t _feedhold_restart_no_actions() {
|
|
if (cm1.hold_state == FEEDHOLD_OFF) {
|
|
return (STAT_OK); // was called erroneously. Can happen for !%~
|
|
}
|
|
cm = &cm1; // return to primary planner (p1)
|
|
mp = (mpPlanner_t *)cm->mp; // cm->mp is a void pointer
|
|
mr = mp->mr;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t _feedhold_restart_with_actions() // Execute Cases (6) and (7)
|
|
{
|
|
if (cm1.hold_state == FEEDHOLD_OFF) {
|
|
return (STAT_OK); // was called erroneously. Can happen for !%~
|
|
}
|
|
|
|
// Check to run first-time code
|
|
if (cm1.hold_state == FEEDHOLD_HOLD) {
|
|
if (!coolant_ready() || (is_spindle_on_or_paused() && !is_spindle_ready_to_resume())) {
|
|
return (STAT_EAGAIN);
|
|
}
|
|
|
|
// perform end-hold actions --- while still in secondary machine
|
|
coolant_control_sync(COOLANT_RESUME, COOLANT_BOTH); // resume coolant if paused
|
|
spindle_resume(); // resume spindle if paused
|
|
|
|
// do return move though an intermediate point; queue a wait
|
|
cm2.return_flags[AXIS_Z] = false;
|
|
cm_goto_g30_position(cm2.gmx.g30_position, cm2.return_flags);
|
|
mp_queue_command(_feedhold_restart_actions_done_callback, nullptr, nullptr);
|
|
cm1.hold_state = FEEDHOLD_EXIT_ACTIONS_PENDING;
|
|
return (STAT_EAGAIN);
|
|
}
|
|
|
|
// wait for exit actions to complete
|
|
if (cm1.hold_state == FEEDHOLD_EXIT_ACTIONS_PENDING) {
|
|
if (cm2.hold_state == FEEDHOLD_MOTION_STOPPED) {
|
|
if (!mp_runtime_is_idle()) { // if there's still motion, wait
|
|
return (STAT_EAGAIN);
|
|
}
|
|
|
|
cm2.hold_state = FEEDHOLD_OFF; // "inner" feedhold is now done, clear it as off
|
|
cm1.hold_state = FEEDHOLD_MOTION_STOPPED; // pass back the state as motion has stopped for clean re-entry
|
|
|
|
// flush the queue of moves and commands for the exit, return cm2 to STOPPED
|
|
_run_queue_flush();
|
|
|
|
// now done with exiting
|
|
return (STAT_OK);
|
|
}
|
|
return (STAT_EAGAIN);
|
|
}
|
|
|
|
// finalize feedhold exit
|
|
if (cm1.hold_state == FEEDHOLD_EXIT_ACTIONS_COMPLETE) {
|
|
_exit_p2(); // re-enter p1 correctly
|
|
return (STAT_OK);
|
|
}
|
|
|
|
return (STAT_EAGAIN); // still waiting
|
|
}
|
|
|
|
stat_t _run_restart_cycle(void) {
|
|
if (cm1.hold_state == FEEDHOLD_MOTION_STOPPED) {
|
|
// the restart was cancelled, move along, nothing to see here...
|
|
return (STAT_OK);
|
|
}
|
|
cm1.hold_state = FEEDHOLD_OFF; // must precede st_request_exec_move()
|
|
|
|
if (mp_has_runnable_buffer(&mp1)) {
|
|
cm_cycle_start();
|
|
st_request_exec_move();
|
|
} else {
|
|
cm_cycle_end();
|
|
}
|
|
return (STAT_OK);
|
|
}
|