klippy: Support FIRMWARE_RESTART command
Add initial support for micro-controller resets via the Arduino reset mechanism. Also, automatically attempt a firmware restart if the printer CRC does not match. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
b0329465ec
commit
64407dc5d2
|
@ -14,8 +14,6 @@ Host user interaction
|
||||||
* Provide startup scripts so that Klippy can startup at system
|
* Provide startup scripts so that Klippy can startup at system
|
||||||
bootup.
|
bootup.
|
||||||
|
|
||||||
* Allow loading of a new config without having to restart the mcu.
|
|
||||||
|
|
||||||
* Improve gcode interface:
|
* Improve gcode interface:
|
||||||
|
|
||||||
* Provide a better way to handle print nozzle z offsets. The M206
|
* Provide a better way to handle print nozzle z offsets. The M206
|
||||||
|
|
|
@ -46,8 +46,8 @@ class GCodeParser:
|
||||||
handlers = ['G1', 'G4', 'G20', 'G21', 'G28', 'G90', 'G91', 'G92',
|
handlers = ['G1', 'G4', 'G20', 'G21', 'G28', 'G90', 'G91', 'G92',
|
||||||
'M18', 'M82', 'M83', 'M105', 'M110', 'M112', 'M114', 'M115',
|
'M18', 'M82', 'M83', 'M105', 'M110', 'M112', 'M114', 'M115',
|
||||||
'M206', 'M400',
|
'M206', 'M400',
|
||||||
'HELP', 'QUERY_ENDSTOPS', 'RESTART', 'CLEAR_SHUTDOWN',
|
'HELP', 'QUERY_ENDSTOPS', 'CLEAR_SHUTDOWN',
|
||||||
'STATUS']
|
'RESTART', 'FIRMWARE_RESTART', 'STATUS']
|
||||||
if self.heater_nozzle is not None:
|
if self.heater_nozzle is not None:
|
||||||
handlers.extend(['M104', 'M109', 'PID_TUNE'])
|
handlers.extend(['M104', 'M109', 'PID_TUNE'])
|
||||||
if self.heater_bed is not None:
|
if self.heater_bed is not None:
|
||||||
|
@ -119,7 +119,7 @@ class GCodeParser:
|
||||||
self.toolhead.force_shutdown()
|
self.toolhead.force_shutdown()
|
||||||
self.respond_error('Internal error on command:"%s"' % (cmd,))
|
self.respond_error('Internal error on command:"%s"' % (cmd,))
|
||||||
if self.is_fileinput:
|
if self.is_fileinput:
|
||||||
self.printer.request_exit_eof()
|
self.printer.request_exit('exit_eof')
|
||||||
break
|
break
|
||||||
self.ack()
|
self.ack()
|
||||||
def process_data(self, eventtime):
|
def process_data(self, eventtime):
|
||||||
|
@ -144,7 +144,7 @@ class GCodeParser:
|
||||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
if not data and self.is_fileinput:
|
if not data and self.is_fileinput:
|
||||||
self.motor_heater_off()
|
self.motor_heater_off()
|
||||||
self.printer.request_exit_eof()
|
self.printer.request_exit('exit_eof')
|
||||||
# Response handling
|
# Response handling
|
||||||
def ack(self, msg=None):
|
def ack(self, msg=None):
|
||||||
if not self.need_ack or self.is_fileinput:
|
if not self.need_ack or self.is_fileinput:
|
||||||
|
@ -373,16 +373,23 @@ class GCodeParser:
|
||||||
self.cmd_default(params)
|
self.cmd_default(params)
|
||||||
return
|
return
|
||||||
self.printer.mcu.clear_shutdown()
|
self.printer.mcu.clear_shutdown()
|
||||||
self.printer.request_restart()
|
self.printer.request_exit('restart')
|
||||||
cmd_RESTART_when_not_ready = True
|
def prep_restart(self):
|
||||||
cmd_RESTART_help = "Reload config file and restart host software"
|
|
||||||
def cmd_RESTART(self, params):
|
|
||||||
if self.is_printer_ready:
|
if self.is_printer_ready:
|
||||||
self.respond_info("Preparing to restart...")
|
self.respond_info("Preparing to restart...")
|
||||||
self.motor_heater_off()
|
self.motor_heater_off()
|
||||||
self.toolhead.dwell(0.500)
|
self.toolhead.dwell(0.500)
|
||||||
self.toolhead.wait_moves()
|
self.toolhead.wait_moves()
|
||||||
self.printer.request_restart()
|
cmd_RESTART_when_not_ready = True
|
||||||
|
cmd_RESTART_help = "Reload config file and restart host software"
|
||||||
|
def cmd_RESTART(self, params):
|
||||||
|
self.prep_restart()
|
||||||
|
self.printer.request_exit('restart')
|
||||||
|
cmd_FIRMWARE_RESTART_when_not_ready = True
|
||||||
|
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
|
||||||
|
def cmd_FIRMWARE_RESTART(self, params):
|
||||||
|
self.prep_restart()
|
||||||
|
self.printer.request_exit('firmware_restart')
|
||||||
cmd_STATUS_when_not_ready = True
|
cmd_STATUS_when_not_ready = True
|
||||||
cmd_STATUS_help = "Report the printer status"
|
cmd_STATUS_help = "Report the printer status"
|
||||||
def cmd_STATUS(self, params):
|
def cmd_STATUS(self, params):
|
||||||
|
|
|
@ -32,9 +32,9 @@ Protocol error connecting to printer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_mcu_connect_error = """
|
message_mcu_connect_error = """
|
||||||
This is an unrecoverable error. Please manually restart the
|
Once the underlying issue is corrected, use the
|
||||||
micro-controller and then issue the "RESTART" command to
|
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||||
restart the host software.
|
config, and restart the host software.
|
||||||
Error configuring printer
|
Error configuring printer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -94,8 +94,10 @@ class ConfigLogger():
|
||||||
logging.info(data.strip())
|
logging.info(data.strip())
|
||||||
|
|
||||||
class Printer:
|
class Printer:
|
||||||
def __init__(self, conffile, input_fd, is_fileinput=False, version="?"):
|
def __init__(self, conffile, input_fd, startup_state
|
||||||
|
, is_fileinput=False, version="?"):
|
||||||
self.conffile = conffile
|
self.conffile = conffile
|
||||||
|
self.startup_state = startup_state
|
||||||
self.software_version = version
|
self.software_version = version
|
||||||
self.reactor = reactor.Reactor()
|
self.reactor = reactor.Reactor()
|
||||||
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
|
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
|
||||||
|
@ -226,11 +228,18 @@ class Printer:
|
||||||
self.mcu.disconnect()
|
self.mcu.disconnect()
|
||||||
except:
|
except:
|
||||||
logging.exception("Unhandled exception during disconnect")
|
logging.exception("Unhandled exception during disconnect")
|
||||||
def request_restart(self):
|
def firmware_restart(self):
|
||||||
self.run_result = "restart"
|
try:
|
||||||
self.reactor.end()
|
if self.mcu is not None:
|
||||||
def request_exit_eof(self):
|
self.stats(self.reactor.monotonic())
|
||||||
self.run_result = "exit_eof"
|
self.mcu.disconnect()
|
||||||
|
self.mcu.microcontroller_restart()
|
||||||
|
except:
|
||||||
|
logging.exception("Unhandled exception during firmware_restart")
|
||||||
|
def get_startup_state(self):
|
||||||
|
return self.startup_state
|
||||||
|
def request_exit(self, result="exit"):
|
||||||
|
self.run_result = result
|
||||||
self.reactor.end()
|
self.reactor.end()
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,9 +296,11 @@ def main():
|
||||||
logging.info("CPU: %s" % (util.get_cpu_info(),))
|
logging.info("CPU: %s" % (util.get_cpu_info(),))
|
||||||
|
|
||||||
# Start firmware
|
# Start firmware
|
||||||
|
res = 'startup'
|
||||||
while 1:
|
while 1:
|
||||||
is_fileinput = debuginput is not None
|
is_fileinput = debuginput is not None
|
||||||
printer = Printer(conffile, input_fd, is_fileinput, software_version)
|
printer = Printer(
|
||||||
|
conffile, input_fd, res, is_fileinput, software_version)
|
||||||
if debugoutput:
|
if debugoutput:
|
||||||
proto_dict = read_dictionary(options.read_dictionary)
|
proto_dict = read_dictionary(options.read_dictionary)
|
||||||
printer.set_fileoutput(debugoutput, proto_dict)
|
printer.set_fileoutput(debugoutput, proto_dict)
|
||||||
|
@ -299,6 +310,11 @@ def main():
|
||||||
time.sleep(1.)
|
time.sleep(1.)
|
||||||
logging.info("Restarting printer")
|
logging.info("Restarting printer")
|
||||||
continue
|
continue
|
||||||
|
elif res == 'firmware_restart':
|
||||||
|
printer.firmware_restart()
|
||||||
|
time.sleep(1.)
|
||||||
|
logging.info("Restarting printer")
|
||||||
|
continue
|
||||||
elif res == 'exit_eof':
|
elif res == 'exit_eof':
|
||||||
printer.disconnect()
|
printer.disconnect()
|
||||||
break
|
break
|
||||||
|
|
|
@ -336,8 +336,9 @@ class MCU:
|
||||||
self._config = config
|
self._config = config
|
||||||
# Serial port
|
# Serial port
|
||||||
baud = config.getint('baud', 250000)
|
baud = config.getint('baud', 250000)
|
||||||
serialport = config.get('serial', '/dev/ttyS0')
|
self._serialport = config.get('serial', '/dev/ttyS0')
|
||||||
self.serial = serialhdl.SerialReader(printer.reactor, serialport, baud)
|
self.serial = serialhdl.SerialReader(
|
||||||
|
printer.reactor, self._serialport, baud)
|
||||||
self.is_shutdown = False
|
self.is_shutdown = False
|
||||||
self._shutdown_msg = ""
|
self._shutdown_msg = ""
|
||||||
self._is_fileoutput = False
|
self._is_fileoutput = False
|
||||||
|
@ -423,6 +424,10 @@ class MCU:
|
||||||
def clear_shutdown(self):
|
def clear_shutdown(self):
|
||||||
logging.info("Sending clear_shutdown command")
|
logging.info("Sending clear_shutdown command")
|
||||||
self.send(self._clear_shutdown_cmd.encode())
|
self.send(self._clear_shutdown_cmd.encode())
|
||||||
|
def microcontroller_restart(self):
|
||||||
|
logging.info("Attempting a microcontroller reset")
|
||||||
|
self.disconnect()
|
||||||
|
serialhdl.arduino_reset(self._serialport, self._printer.reactor)
|
||||||
def is_fileoutput(self):
|
def is_fileoutput(self):
|
||||||
return self._is_fileoutput
|
return self._is_fileoutput
|
||||||
# Configuration phase
|
# Configuration phase
|
||||||
|
@ -472,6 +477,7 @@ class MCU:
|
||||||
config_params = self.serial.send_with_response(msg, 'config')
|
config_params = self.serial.send_with_response(msg, 'config')
|
||||||
if not config_params['is_config']:
|
if not config_params['is_config']:
|
||||||
# Send config commands
|
# Send config commands
|
||||||
|
logging.info("Sending printer configuration...")
|
||||||
for c in self._config_cmds:
|
for c in self._config_cmds:
|
||||||
self.send(self.create_command(c))
|
self.send(self.create_command(c))
|
||||||
if not self._is_fileoutput:
|
if not self._is_fileoutput:
|
||||||
|
@ -482,6 +488,12 @@ class MCU:
|
||||||
self._shutdown_msg,))
|
self._shutdown_msg,))
|
||||||
raise error("Unable to configure printer")
|
raise error("Unable to configure printer")
|
||||||
if self._config_crc != config_params['crc']:
|
if self._config_crc != config_params['crc']:
|
||||||
|
if self._printer.get_startup_state() != 'firmware_restart':
|
||||||
|
# Attempt a firmware restart to fix the CRC error
|
||||||
|
logging.info(
|
||||||
|
"Printer CRC mismatch - attempting firmware restart")
|
||||||
|
self._printer.request_exit('firmware_restart')
|
||||||
|
self._printer.reactor.pause(0.100)
|
||||||
raise error("Printer CRC does not match config")
|
raise error("Printer CRC does not match config")
|
||||||
move_count = config_params['move_count']
|
move_count = config_params['move_count']
|
||||||
logging.info("Configured (%d moves)" % (move_count,))
|
logging.info("Configured (%d moves)" % (move_count,))
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, threading
|
import logging, threading, time
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
import msgproto, chelper, util
|
import msgproto, chelper, util
|
||||||
|
@ -325,3 +325,16 @@ def stk500v2_leave(ser, reactor):
|
||||||
res = ser.read(4096)
|
res = ser.read(4096)
|
||||||
logging.debug("Got %s from stk500v2" % (repr(res),))
|
logging.debug("Got %s from stk500v2" % (repr(res),))
|
||||||
ser.baudrate = origbaud
|
ser.baudrate = origbaud
|
||||||
|
|
||||||
|
# Attempt an arduino style reset on a serial port
|
||||||
|
def arduino_reset(serialport, reactor):
|
||||||
|
# First try opening the port at 1200 baud
|
||||||
|
ser = serial.Serial(serialport, 1200, timeout=0)
|
||||||
|
ser.read(1)
|
||||||
|
time.sleep(0.100)
|
||||||
|
# Then try toggling DTR
|
||||||
|
ser.dtr = True
|
||||||
|
time.sleep(0.100)
|
||||||
|
ser.dtr = False
|
||||||
|
time.sleep(0.100)
|
||||||
|
ser.close()
|
||||||
|
|
Loading…
Reference in New Issue