Pathpilot Reset Functionality
- kwanlokto
- Offline
- New Member
-
Less
More
- Posts: 8
- Thank you received: 0
03 Nov 2025 14:06 #337755
by kwanlokto
Pathpilot Reset Functionality was created by kwanlokto
I'm working on creating a new Reset Function that will clear the machine's E-STOP state. I'd like to implement this as an API route so that I can control the mill's functionality remotely from another computer.Currently, my route is based on thefunction from. However, PathPilot crashes approximately 1 out of every 5 times when I call this endpoint.Does anyone have any ideas what could be causing this intermittent crashing issue?
on_reset_button_clicked()/home/{my_user}/tmc/python/tormach_mill_ui.pyPlease Log in or Create an account to join the conversation.
- kwanlokto
- Offline
- New Member
-
Less
More
- Posts: 8
- Thank you received: 0
03 Nov 2025 16:01 #337761
by kwanlokto
Replied by kwanlokto on topic Pathpilot Reset Functionality
I wish it did print something out. I added a bunch of error logging to print to the system logs and It seems to be crash at different lines (which I find to be odd).
I have the two computers on the same network and I'm hitting those endpoints via HTTP.
If it helps here's the code that I've added to the tormach_mill_ui.py:
def ss_reset_route(self):
"""
/reset
Reset the machine after an emergency stop was pressed.
Note: This function is the same as on_reset_button_clicked()
"""
self.hal["pp-estop-fault"] = 0 # clear any existing pp software estops
self.halt_world_flag = False # note issues with halt world. Do we lock it out during reset
# and then check or reset this at end of on_reset_button_clicked
self.clear_message_line_text()
self.hide_m01_image()
self.unlock_enclosure_door()
# turn off air blast off rack tool changer
if self.machineconfig.rack_tool_changer_supported() and self.rack_tool_changer:
self.rack_tool_changer.stop_air_blast()
if self.hal["mesa-watchdog-has-bit"]:
# since resetting the mesa card io_error parameter is more involved now with ethernet,
# only do this if we really did see a watchdog bite.
# clear Mesa IO errors (if any). this must be done PRIOR to setting the mesa-watchdog-has-bit pin low.
clear_hostmot2_board_io_error(self.inifile)
# clear Mesa card watchdog
self.hal["mesa-watchdog-has-bit"] = 0
self.mesa_watchdog_has_bit_seen = False
# give it a second to re-establish IO link before jamming commands at it.
time.sleep(1.0)
self.poll()
# did the watchdog re-bite already? If so, re-establishing the IO link didn't work.
# leave us in e-stop.
if self.hal["mesa-watchdog-has-bit"]:
self.mesa_watchdog_has_bit_seen = True
self.error_handler.write(
"Machine interface error. Check cabling and power to machine and then press RESET.",
ALARM_LEVEL_MEDIUM,
)
self.call_ui_hook("reset_button")
return Server_Response_Factory.create_server_response(400, "Failed IO Link")
# Configure the A axis hardware
# This happens in the reset handler because __init__ is too early, and the change to traj.max_angular_ vel is ignored
a_axis = self.machineconfig.a_axis.selected()
if a_axis:
self.configure_a_axis(a_axis)
# order of these is important. Answer queries first, then check for random stop/reset presses
if self.set_response_cancel():
return Server_Response_Factory.create_server_response(
400, "Outstanding Prompts"
) # check for outstanding prompts and cancel,True is message answered
if self.atc.worker_thread_busy.is_set():
self.atc.stop_reset.set() # only if atc thread in progress
self.atc.feed_hold_clear.set() # signal that any feed holds are cleared
# RACK_QUESTION what about router pdb on and rack disabled?
if self.rack_tool_changer:
if self.rack_tool_changer.worker_thread_busy.is_set():
self.rack_tool_changer.stop_reset.set() # only if rack atc thread in progress
self.rack_tool_changer.feed_hold_clear.set() # signal that any feed holds are cleared
self.set_image("feedhold_image", "Feedhold-Black.jpg")
# clear feedhold
self.clear_feedhold()
# reset e-stop
if self.status.task_state != linuxcnc.STATE_ESTOP_RESET:
# this actually ends up doing a linuxcnc command abort internally
# and that will run any on_abort ngc code.
self.command.state(linuxcnc.STATE_ESTOP_RESET)
self.command.wait_complete()
self.poll()
if self.status.task_state not in [linuxcnc.STATE_ESTOP_RESET, linuxcnc.STATE_ON]:
self.error_handler.write(
"Failed to bring machine out of E-stop. Please check machine power, limit switches, and communication cable from the controller to the machine."
)
self.call_ui_hook("reset_button")
return Server_Response_Factory.create_server_response(
400, "Failed to bring machine out of E-stop"
)
# clear alarm
self.estop_alarm = False
self.display_estop_msg = True
# Prevent coming out of Reset if a limit switch is active.
if self.status.limit[0] != 0:
error_msg = "X limit switch active."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
if self.status.limit[1] != 0:
error_msg = "Y limit switch active."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
if self.status.limit[2] != 0:
error_msg = "Z limit switch active."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
if (self.status.limit[0] != 0) or (self.status.limit[1] != 0) or (self.status.limit[2] != 0):
error_msg = "Disable limit switches in Settings, then push Reset, then carefully jog off limit switch, then re-enable limit switches in Settings."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
self.call_ui_hook("reset_button")
return Server_Response_Factory.create_server_response(400, "Disable limit switches in Settings")
time.sleep(0.25) # TODO: Added time.sleep()
# must be turned on again after being reset from estop
if self.status.task_state != linuxcnc.STATE_ON:
# this actually ends up doing a linuxcnc command abort internally
# and that will run any on_abort ngc code.
self.command.state(linuxcnc.STATE_ON)
self.command.wait_complete()
self.poll()
if self.status.task_state != linuxcnc.STATE_ON:
err_msg = "Failed to bring machine out of E-stop. Please check machine power, limit switches, and communication cable from the controller to the machine."
self.error_handler.write(err_msg)
return Server_Response_Factory.create_server_response(400, err_msg)
# saw a rare case where the ATC stuff above times out after taking 5 long seconds and during that
# time, the operator presses the e-stop button. So just check again to be sure before we start
# running commands. If it is e-stopped, the periodics will take appropriate action.
if self.hal["machine-ok"] == False:
return Server_Response_Factory.create_server_response(400, "Failed to bring machine out of E-stop")
# stop motion
self.command.abort()
self.command.wait_complete()
# reset/rewind program
if (self.status.limit[0] == 0) and (self.status.limit[1] == 0) and (self.status.limit[2] == 0):
# HACK manually decrement the part counters by one since M30 increments them
# if this ever becomes optional, then make sure not to do this if M30 incrementing is disabled
self.issue_mdi("#5650 = [#5650 - 1] #5651 = [#5651 - 1]")
self.command.wait_complete()
self.issue_mdi("M30")
self.command.wait_complete()
# clear SB status
self.single_block_active = False
self.single_block_button.set_led_state(self.single_block_active)
# clear live plotter
self.gremlin.clear_live_plotter()
# refresh work offsets
self.refresh_work_offset_liststore()
# rewind program listing and set starting line
if self.is_gcode_program_loaded:
self.set_start_settings_and_mark(1)
# some folks got confused because their program ended, the M30 reset current line to 0 and
# the 50ms periodic auto-scrolled back up to the start line. But then they managed to scroll
# around in the view and then press the Reset button and they expect it to auto-scroll to the
# top again. The 50ms periodic doesn't do anything because the current line hasn't 'changed'
# from 0 so we need this here to always smack the display back to the start line.
self.scroll_sourceview_to_mark("start")
self.call_ui_hook("reset_button")
self.do_first_run_setup()
self.axis_motor_command(0, MOTOR_CMD_NORMAL)
self.axis_motor_command(1, MOTOR_CMD_NORMAL)
self.axis_motor_command(2, MOTOR_CMD_NORMAL)
# Make sure the override sliders are enabled. The ATC code disables them and tries to restore them, but
# in certain aborted situations, they can get stuck off.
self.command.set_feed_override(True)
self.command.set_spindle_override(True)
# g21 and machineconfig need to be accurate before setting scaled jog increment
# self.update_gui_for_units(self.g21) # TODO: this seems to breaking some things, need to figure why
# self.start_loop_recording() # TODO: this seems to breaking some things, need to figure why
return Server_Response_Factory.create_server_response(200, "Mill has been reset")
I have the two computers on the same network and I'm hitting those endpoints via HTTP.
If it helps here's the code that I've added to the tormach_mill_ui.py:
def ss_reset_route(self):
"""
/reset
Reset the machine after an emergency stop was pressed.
Note: This function is the same as on_reset_button_clicked()
"""
self.hal["pp-estop-fault"] = 0 # clear any existing pp software estops
self.halt_world_flag = False # note issues with halt world. Do we lock it out during reset
# and then check or reset this at end of on_reset_button_clicked
self.clear_message_line_text()
self.hide_m01_image()
self.unlock_enclosure_door()
# turn off air blast off rack tool changer
if self.machineconfig.rack_tool_changer_supported() and self.rack_tool_changer:
self.rack_tool_changer.stop_air_blast()
if self.hal["mesa-watchdog-has-bit"]:
# since resetting the mesa card io_error parameter is more involved now with ethernet,
# only do this if we really did see a watchdog bite.
# clear Mesa IO errors (if any). this must be done PRIOR to setting the mesa-watchdog-has-bit pin low.
clear_hostmot2_board_io_error(self.inifile)
# clear Mesa card watchdog
self.hal["mesa-watchdog-has-bit"] = 0
self.mesa_watchdog_has_bit_seen = False
# give it a second to re-establish IO link before jamming commands at it.
time.sleep(1.0)
self.poll()
# did the watchdog re-bite already? If so, re-establishing the IO link didn't work.
# leave us in e-stop.
if self.hal["mesa-watchdog-has-bit"]:
self.mesa_watchdog_has_bit_seen = True
self.error_handler.write(
"Machine interface error. Check cabling and power to machine and then press RESET.",
ALARM_LEVEL_MEDIUM,
)
self.call_ui_hook("reset_button")
return Server_Response_Factory.create_server_response(400, "Failed IO Link")
# Configure the A axis hardware
# This happens in the reset handler because __init__ is too early, and the change to traj.max_angular_ vel is ignored
a_axis = self.machineconfig.a_axis.selected()
if a_axis:
self.configure_a_axis(a_axis)
# order of these is important. Answer queries first, then check for random stop/reset presses
if self.set_response_cancel():
return Server_Response_Factory.create_server_response(
400, "Outstanding Prompts"
) # check for outstanding prompts and cancel,True is message answered
if self.atc.worker_thread_busy.is_set():
self.atc.stop_reset.set() # only if atc thread in progress
self.atc.feed_hold_clear.set() # signal that any feed holds are cleared
# RACK_QUESTION what about router pdb on and rack disabled?
if self.rack_tool_changer:
if self.rack_tool_changer.worker_thread_busy.is_set():
self.rack_tool_changer.stop_reset.set() # only if rack atc thread in progress
self.rack_tool_changer.feed_hold_clear.set() # signal that any feed holds are cleared
self.set_image("feedhold_image", "Feedhold-Black.jpg")
# clear feedhold
self.clear_feedhold()
# reset e-stop
if self.status.task_state != linuxcnc.STATE_ESTOP_RESET:
# this actually ends up doing a linuxcnc command abort internally
# and that will run any on_abort ngc code.
self.command.state(linuxcnc.STATE_ESTOP_RESET)
self.command.wait_complete()
self.poll()
if self.status.task_state not in [linuxcnc.STATE_ESTOP_RESET, linuxcnc.STATE_ON]:
self.error_handler.write(
"Failed to bring machine out of E-stop. Please check machine power, limit switches, and communication cable from the controller to the machine."
)
self.call_ui_hook("reset_button")
return Server_Response_Factory.create_server_response(
400, "Failed to bring machine out of E-stop"
)
# clear alarm
self.estop_alarm = False
self.display_estop_msg = True
# Prevent coming out of Reset if a limit switch is active.
if self.status.limit[0] != 0:
error_msg = "X limit switch active."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
if self.status.limit[1] != 0:
error_msg = "Y limit switch active."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
if self.status.limit[2] != 0:
error_msg = "Z limit switch active."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
if (self.status.limit[0] != 0) or (self.status.limit[1] != 0) or (self.status.limit[2] != 0):
error_msg = "Disable limit switches in Settings, then push Reset, then carefully jog off limit switch, then re-enable limit switches in Settings."
self.error_handler.write(error_msg, ALARM_LEVEL_MEDIUM)
self.call_ui_hook("reset_button")
return Server_Response_Factory.create_server_response(400, "Disable limit switches in Settings")
time.sleep(0.25) # TODO: Added time.sleep()
# must be turned on again after being reset from estop
if self.status.task_state != linuxcnc.STATE_ON:
# this actually ends up doing a linuxcnc command abort internally
# and that will run any on_abort ngc code.
self.command.state(linuxcnc.STATE_ON)
self.command.wait_complete()
self.poll()
if self.status.task_state != linuxcnc.STATE_ON:
err_msg = "Failed to bring machine out of E-stop. Please check machine power, limit switches, and communication cable from the controller to the machine."
self.error_handler.write(err_msg)
return Server_Response_Factory.create_server_response(400, err_msg)
# saw a rare case where the ATC stuff above times out after taking 5 long seconds and during that
# time, the operator presses the e-stop button. So just check again to be sure before we start
# running commands. If it is e-stopped, the periodics will take appropriate action.
if self.hal["machine-ok"] == False:
return Server_Response_Factory.create_server_response(400, "Failed to bring machine out of E-stop")
# stop motion
self.command.abort()
self.command.wait_complete()
# reset/rewind program
if (self.status.limit[0] == 0) and (self.status.limit[1] == 0) and (self.status.limit[2] == 0):
# HACK manually decrement the part counters by one since M30 increments them
# if this ever becomes optional, then make sure not to do this if M30 incrementing is disabled
self.issue_mdi("#5650 = [#5650 - 1] #5651 = [#5651 - 1]")
self.command.wait_complete()
self.issue_mdi("M30")
self.command.wait_complete()
# clear SB status
self.single_block_active = False
self.single_block_button.set_led_state(self.single_block_active)
# clear live plotter
self.gremlin.clear_live_plotter()
# refresh work offsets
self.refresh_work_offset_liststore()
# rewind program listing and set starting line
if self.is_gcode_program_loaded:
self.set_start_settings_and_mark(1)
# some folks got confused because their program ended, the M30 reset current line to 0 and
# the 50ms periodic auto-scrolled back up to the start line. But then they managed to scroll
# around in the view and then press the Reset button and they expect it to auto-scroll to the
# top again. The 50ms periodic doesn't do anything because the current line hasn't 'changed'
# from 0 so we need this here to always smack the display back to the start line.
self.scroll_sourceview_to_mark("start")
self.call_ui_hook("reset_button")
self.do_first_run_setup()
self.axis_motor_command(0, MOTOR_CMD_NORMAL)
self.axis_motor_command(1, MOTOR_CMD_NORMAL)
self.axis_motor_command(2, MOTOR_CMD_NORMAL)
# Make sure the override sliders are enabled. The ATC code disables them and tries to restore them, but
# in certain aborted situations, they can get stuck off.
self.command.set_feed_override(True)
self.command.set_spindle_override(True)
# g21 and machineconfig need to be accurate before setting scaled jog increment
# self.update_gui_for_units(self.g21) # TODO: this seems to breaking some things, need to figure why
# self.start_loop_recording() # TODO: this seems to breaking some things, need to figure why
return Server_Response_Factory.create_server_response(200, "Mill has been reset")
Please Log in or Create an account to join the conversation.
- kwanlokto
- Offline
- New Member
-
Less
More
- Posts: 8
- Thank you received: 0
03 Nov 2025 17:59 #337772
by kwanlokto
Replied by kwanlokto on topic Pathpilot Reset Functionality
Thanks for the reply!
Yes, it's inside the mill(TormachUIBase) class. The indentation got messed up when I copied it over, but I can assure you it's correct in the actual code because the function does work 'sometimes'.
The suggestion to keep the code concise is a good idea, but I'm hesitant to trim it down too much as this mirrors the PathPilot reset code. The last thing I'd want is to initialize one of the HAL pins correctly and that would cause even more issues. I'm really not experienced in the CNCs and have been only tinkering on and on off for about 3 years.
Yes, it's inside the mill(TormachUIBase) class. The indentation got messed up when I copied it over, but I can assure you it's correct in the actual code because the function does work 'sometimes'.
The suggestion to keep the code concise is a good idea, but I'm hesitant to trim it down too much as this mirrors the PathPilot reset code. The last thing I'd want is to initialize one of the HAL pins correctly and that would cause even more issues. I'm really not experienced in the CNCs and have been only tinkering on and on off for about 3 years.
Please Log in or Create an account to join the conversation.
- snowgoer540
-
- Offline
- Moderator
-
Less
More
- Posts: 2534
- Thank you received: 864
03 Nov 2025 18:41 #337775
by snowgoer540
Replied by snowgoer540 on topic Pathpilot Reset Functionality
What version of PathPilot are you running?
You said you added logging, what did you do exactly? Where was it crashing? I assume you running PathPilot in a way that allows you to see the terminal, so you could wrap the offending areas in a try/except and print the exception to the screen when it fails.
I would not mess with refactoring Tormach's code, that otherwise works.
You said you added logging, what did you do exactly? Where was it crashing? I assume you running PathPilot in a way that allows you to see the terminal, so you could wrap the offending areas in a try/except and print the exception to the screen when it fails.
I would not mess with refactoring Tormach's code, that otherwise works.
Please Log in or Create an account to join the conversation.
- kwanlokto
- Offline
- New Member
-
Less
More
- Posts: 8
- Thank you received: 0
03 Nov 2025 21:31 #337786
by kwanlokto
Replied by kwanlokto on topic Pathpilot Reset Functionality
Pathpilot is v2.10.1
I would use Tormach's internal debugging system which would write to the status logs
self.error_handler.write("My message", ALARM_LEVEL_DEBUG). I wrapped the endpoint in a try catch block so that's why I'm also very puzzled. I do however run my flask application in development like this, so I'm not sure if I'm running into a locking issue:
class FlaskAppWrapper:
def __init__(self):
....
def run(self):
try:
threading.Thread(target=self.run_app_at_port, args=(mill_port,)).start()
except Exception as e:
print "error in threading: \n" + str(e) + "\n"
def run_app_at_port(self, port):
try:
self.app.run(host="0.0.0.0", port=port, threaded=True)
except Exception as e:
print "error has occurred when running flask: \n" + str(e) + "\n"
I would use Tormach's internal debugging system which would write to the status logs
self.error_handler.write("My message", ALARM_LEVEL_DEBUG). I wrapped the endpoint in a try catch block so that's why I'm also very puzzled. I do however run my flask application in development like this, so I'm not sure if I'm running into a locking issue:
class FlaskAppWrapper:
def __init__(self):
....
def run(self):
try:
threading.Thread(target=self.run_app_at_port, args=(mill_port,)).start()
except Exception as e:
print "error in threading: \n" + str(e) + "\n"
def run_app_at_port(self, port):
try:
self.app.run(host="0.0.0.0", port=port, threaded=True)
except Exception as e:
print "error has occurred when running flask: \n" + str(e) + "\n"
Please Log in or Create an account to join the conversation.
- kwanlokto
- Offline
- New Member
-
Less
More
- Posts: 8
- Thank you received: 0
03 Nov 2025 21:36 #337787
by kwanlokto
Replied by kwanlokto on topic Pathpilot Reset Functionality
Good idea! I'll add a locking mechanism to the route and see if that helps. I do already have try-except wrapping the entire endpoint.
Since I'm modifying Tormach's code directly, I'm trying to keep changes minimal to avoid breaking PathPilot's core functionality.
Since I'm modifying Tormach's code directly, I'm trying to keep changes minimal to avoid breaking PathPilot's core functionality.
Please Log in or Create an account to join the conversation.
Time to create page: 0.112 seconds