From 223002ad5b653cfd26224c198aa2cf9924bf81da Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Mon, 9 Nov 2026 15:18:51 +1100 Subject: [PATCH] task: an E-stop reset leaves a machine that is on alone emcTaskSetState() ran the whole E-stop reset for ESTOP_RESET in any state: it aborted motion and the interpreter, stopped the spindles, turned the flood off and aborted iocontrol. Sent to a machine that is on, the machine stayed on and lost whatever it was doing, a running program or a homing. The docs only describe the reset as leaving E-stop, and halui.abort is the way to abort. gmoccapy sends such a reset: when it shows a state change on its E-stop button, the button's handler sends that state again. With gstat reporting the reset after a reset and machine on that fall between two status polls (#4778), the reset arrives at a machine that is already on, and aborted a homing the ui-smoke driver had just started. Skip ESTOP_RESET when the machine is on. Every other state runs it as before, including configs that tie iocontrol.0.emc-enable-in high, where task never reports E-stop. No in-tree sender relies on the abort while on: AXIS, gscreen, tklinuxcnc and the qtvcp action button check for E-stop first, and the tests reset only from E-stop or at startup. --- src/emc/task/emctask.cc | 5 +++++ tests/abort/feed-rate/README | 3 +++ tests/abort/feed-rate/test-ui.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/emc/task/emctask.cc b/src/emc/task/emctask.cc index 2b4490aca48..fa3d6e9229b 100644 --- a/src/emc/task/emctask.cc +++ b/src/emc/task/emctask.cc @@ -343,6 +343,11 @@ int emcTaskSetState(EMC_TASK_STATE state) break; case EMC_TASK_STATE::ESTOP_RESET: + // a machine that is on has no E-stop to reset; the abort below + // would only stop whatever it is doing + if (emcStatus->motion.traj.enabled && !emcStatus->io.aux.estop) { + break; + } // reset the estop emcAuxEstopOff(); emcCoolantFloodOff();//TODO: race here diff --git a/tests/abort/feed-rate/README b/tests/abort/feed-rate/README index 32279053324..da41dc7cf0b 100644 --- a/tests/abort/feed-rate/README +++ b/tests/abort/feed-rate/README @@ -2,3 +2,6 @@ This test starts LinuxCNC, sets the feed rate (in G94 and G95), then Aborts and verifies that the expected feed rate is active. In 2.6.12 and 2.7.7 this test replicates Github Issue #180. + +It then runs a move with the spindle and flood on, sends an E-stop reset, +and verifies that the move, the spindle and the flood keep going. diff --git a/tests/abort/feed-rate/test-ui.py b/tests/abort/feed-rate/test-ui.py index 7482d009230..fe287f0083e 100755 --- a/tests/abort/feed-rate/test-ui.py +++ b/tests/abort/feed-rate/test-ui.py @@ -78,5 +78,33 @@ def wait_for_linuxcnc_startup(status, timeout=10.0): print("feed rate:{}".format(s.settings[1])) assert(math.fabs(s.settings[1] - feed_rate) < 0.0000001) + +def wait_for(condition, timeout=10.0): + start_time = time.time() + while time.time() - start_time < timeout: + s.poll() + if condition(): + return True + time.sleep(0.01) + return False + + +# An E-stop reset sent to a machine that is on leaves it alone, and does +# not run the abort that goes with leaving E-stop. +c.mdi('g20 g90 g94 m3 s1000 m8') +c.wait_complete() +c.mdi('g1 x3 f30') +assert wait_for(lambda: s.current_vel > 0), "the move did not start" +c.state(linuxcnc.STATE_ESTOP_RESET) +c.wait_complete() +s.poll() +x = s.position[0] +assert wait_for(lambda: s.position[0] > x + 0.1), "the reset stopped the move" +assert s.task_state == linuxcnc.STATE_ON, "the reset turned the machine off" +assert s.spindle[0]['enabled'], "the reset stopped the spindle" +assert s.flood, "the reset turned the flood off" +c.abort() +c.wait_complete() + sys.exit(0)