From 64407dc5d27da8fbc6b1cd6604a0e734da019b59 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 8 Mar 2017 22:26:10 -0500 Subject: [PATCH] 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 --- docs/Todo.md | 2 -- klippy/gcode.py | 25 ++++++++++++++++--------- klippy/klippy.py | 36 ++++++++++++++++++++++++++---------- klippy/mcu.py | 16 ++++++++++++++-- klippy/serialhdl.py | 15 ++++++++++++++- 5 files changed, 70 insertions(+), 24 deletions(-) diff --git a/docs/Todo.md b/docs/Todo.md index e16196e1..3237fe64 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -14,8 +14,6 @@ Host user interaction * Provide startup scripts so that Klippy can startup at system bootup. - * Allow loading of a new config without having to restart the mcu. - * Improve gcode interface: * Provide a better way to handle print nozzle z offsets. The M206 diff --git a/klippy/gcode.py b/klippy/gcode.py index ce3c3ffe..e0bf6aa9 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -46,8 +46,8 @@ class GCodeParser: handlers = ['G1', 'G4', 'G20', 'G21', 'G28', 'G90', 'G91', 'G92', 'M18', 'M82', 'M83', 'M105', 'M110', 'M112', 'M114', 'M115', 'M206', 'M400', - 'HELP', 'QUERY_ENDSTOPS', 'RESTART', 'CLEAR_SHUTDOWN', - 'STATUS'] + 'HELP', 'QUERY_ENDSTOPS', 'CLEAR_SHUTDOWN', + 'RESTART', 'FIRMWARE_RESTART', 'STATUS'] if self.heater_nozzle is not None: handlers.extend(['M104', 'M109', 'PID_TUNE']) if self.heater_bed is not None: @@ -119,7 +119,7 @@ class GCodeParser: self.toolhead.force_shutdown() self.respond_error('Internal error on command:"%s"' % (cmd,)) if self.is_fileinput: - self.printer.request_exit_eof() + self.printer.request_exit('exit_eof') break self.ack() def process_data(self, eventtime): @@ -144,7 +144,7 @@ class GCodeParser: self.fd_handle = self.reactor.register_fd(self.fd, self.process_data) if not data and self.is_fileinput: self.motor_heater_off() - self.printer.request_exit_eof() + self.printer.request_exit('exit_eof') # Response handling def ack(self, msg=None): if not self.need_ack or self.is_fileinput: @@ -373,16 +373,23 @@ class GCodeParser: self.cmd_default(params) return self.printer.mcu.clear_shutdown() - 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.printer.request_exit('restart') + def prep_restart(self): if self.is_printer_ready: self.respond_info("Preparing to restart...") self.motor_heater_off() self.toolhead.dwell(0.500) 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_help = "Report the printer status" def cmd_STATUS(self, params): diff --git a/klippy/klippy.py b/klippy/klippy.py index abd92208..412477ad 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -32,9 +32,9 @@ Protocol error connecting to printer """ message_mcu_connect_error = """ -This is an unrecoverable error. Please manually restart the -micro-controller and then issue the "RESTART" command to -restart the host software. +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. Error configuring printer """ @@ -94,8 +94,10 @@ class ConfigLogger(): logging.info(data.strip()) 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.startup_state = startup_state self.software_version = version self.reactor = reactor.Reactor() self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput) @@ -226,11 +228,18 @@ class Printer: self.mcu.disconnect() except: logging.exception("Unhandled exception during disconnect") - def request_restart(self): - self.run_result = "restart" - self.reactor.end() - def request_exit_eof(self): - self.run_result = "exit_eof" + def firmware_restart(self): + try: + if self.mcu is not None: + self.stats(self.reactor.monotonic()) + 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() @@ -287,9 +296,11 @@ def main(): logging.info("CPU: %s" % (util.get_cpu_info(),)) # Start firmware + res = 'startup' while 1: 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: proto_dict = read_dictionary(options.read_dictionary) printer.set_fileoutput(debugoutput, proto_dict) @@ -299,6 +310,11 @@ def main(): time.sleep(1.) logging.info("Restarting printer") continue + elif res == 'firmware_restart': + printer.firmware_restart() + time.sleep(1.) + logging.info("Restarting printer") + continue elif res == 'exit_eof': printer.disconnect() break diff --git a/klippy/mcu.py b/klippy/mcu.py index 73f99c54..7dae343a 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -336,8 +336,9 @@ class MCU: self._config = config # Serial port baud = config.getint('baud', 250000) - serialport = config.get('serial', '/dev/ttyS0') - self.serial = serialhdl.SerialReader(printer.reactor, serialport, baud) + self._serialport = config.get('serial', '/dev/ttyS0') + self.serial = serialhdl.SerialReader( + printer.reactor, self._serialport, baud) self.is_shutdown = False self._shutdown_msg = "" self._is_fileoutput = False @@ -423,6 +424,10 @@ class MCU: def clear_shutdown(self): logging.info("Sending clear_shutdown command") 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): return self._is_fileoutput # Configuration phase @@ -472,6 +477,7 @@ class MCU: config_params = self.serial.send_with_response(msg, 'config') if not config_params['is_config']: # Send config commands + logging.info("Sending printer configuration...") for c in self._config_cmds: self.send(self.create_command(c)) if not self._is_fileoutput: @@ -482,6 +488,12 @@ class MCU: self._shutdown_msg,)) raise error("Unable to configure printer") 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") move_count = config_params['move_count'] logging.info("Configured (%d moves)" % (move_count,)) diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 1af64000..8112d703 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -3,7 +3,7 @@ # Copyright (C) 2016 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, threading +import logging, threading, time import serial import msgproto, chelper, util @@ -325,3 +325,16 @@ def stk500v2_leave(ser, reactor): res = ser.read(4096) logging.debug("Got %s from stk500v2" % (repr(res),)) 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()