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:
Kevin O'Connor 2017-03-08 22:26:10 -05:00
parent b0329465ec
commit 64407dc5d2
5 changed files with 70 additions and 24 deletions

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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,))

View File

@ -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()