klippy: Support generic printer_state() and stats() callbacks

Instead of hardcoding which objects are called on state transitions,
allow any "printer object" to be invoked if it has a printer_state()
method.  Convert connect, ready, shutdown, and disconnect callbacks to
this mechanism.

Similarly, allow all printer objects to provide a stats() callback.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2018-01-19 22:49:27 -05:00
parent 81013ba5c8
commit d3665699f1
4 changed files with 71 additions and 57 deletions

View File

@ -68,7 +68,18 @@ class GCodeParser:
self.gcode_help[cmd] = desc
def stats(self, eventtime):
return "gcodein=%d" % (self.bytes_read,)
def connect(self):
def printer_state(self, state):
if state == 'shutdown':
if not self.is_printer_ready:
return
self.is_printer_ready = False
self.gcode_handlers = self.base_gcode_handlers
self.dump_debug()
if self.is_fileinput:
self.printer.request_exit()
return
if state != 'ready':
return
self.is_printer_ready = True
self.gcode_handlers = self.ready_gcode_handlers
# Lookup printer components
@ -85,14 +96,6 @@ class GCodeParser:
def reset_last_position(self):
if self.toolhead is not None:
self.last_position = self.toolhead.get_position()
def do_shutdown(self):
if not self.is_printer_ready:
return
self.is_printer_ready = False
self.gcode_handlers = self.base_gcode_handlers
self.dump_debug()
if self.is_fileinput:
self.printer.request_exit()
def motor_heater_off(self):
if self.toolhead is None:
return

View File

@ -4,9 +4,10 @@
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, ConfigParser, logging, time, threading
import util, reactor, queuelogger, msgproto, gcode
import pins, mcu, chipmisc, toolhead, extruder, heater, fan
import sys, optparse, logging, time, threading
import collections, ConfigParser, importlib
import util, reactor, queuelogger, msgproto
import gcode, pins, mcu, chipmisc, toolhead, extruder, heater, fan
message_ready = "Printer is ready"
@ -134,8 +135,8 @@ class Printer:
if bglogger is not None:
bglogger.set_rollover_info("config", None)
self.reactor = reactor.Reactor()
self.gcode = gcode.GCodeParser(self, input_fd)
self.objects = {'gcode': self.gcode}
gc = gcode.GCodeParser(self, input_fd)
self.objects = collections.OrderedDict({'gcode': gc})
self.stats_timer = self.reactor.register_timer(self._stats)
self.connect_timer = self.reactor.register_timer(
self._connect, self.reactor.NOW)
@ -145,7 +146,8 @@ class Printer:
self.async_shutdown_msg = ""
self.run_result = None
self.fileconfig = None
self.mcus = []
self.stats_cb = []
self.state_cb = []
def get_start_args(self):
return self.start_args
def get_reactor(self):
@ -165,8 +167,7 @@ class Printer:
return default
def lookup_module_objects(self, module_name):
prefix = module_name + ' '
objs = [self.objects[n]
for n in sorted(self.objects) if n.startswith(prefix)]
objs = [self.objects[n] for n in self.objects if n.startswith(prefix)]
if module_name in self.objects:
return [self.objects[module_name]] + objs
return objs
@ -180,12 +181,8 @@ class Printer:
is_active = toolhead.check_active(eventtime)
if not is_active and not force_output:
return eventtime + 1.
out = []
out.append(self.gcode.stats(eventtime))
out.append(toolhead.stats(eventtime))
for m in self.mcus:
out.append(m.stats(eventtime))
logging.info("Stats %.1f: %s", eventtime, ' '.join(out))
stats = [cb(eventtime) for cb in self.stats_cb]
logging.info("Stats %.1f: %s", eventtime, ' '.join(stats))
return eventtime + 1.
def _load_config(self):
self.fileconfig = ConfigParser.RawConfigParser()
@ -200,7 +197,6 @@ class Printer:
config = ConfigWrapper(self, 'printer')
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
m.add_printer_objects(self, config)
self.mcus = self.lookup_module_objects('mcu')
# Validate that there are no undefined parameters in the config file
valid_sections = { s: 1 for s, o in self.all_config_options }
for section in self.fileconfig.sections():
@ -214,14 +210,24 @@ class Printer:
raise self.config_error(
"Unknown option '%s' in section '%s'" % (
option, section))
# Determine which printer objects have stats/state callbacks
self.stats_cb = [o.stats for o in self.objects.values()
if hasattr(o, 'stats')]
self.state_cb = [o.printer_state for o in self.objects.values()
if hasattr(o, 'printer_state')]
def _connect(self, eventtime):
self.reactor.unregister_timer(self.connect_timer)
try:
self._load_config()
for m in self.mcus:
m.connect()
self.gcode.connect()
for cb in self.state_cb:
if self.state_message is not message_startup:
return self.reactor.NEVER
cb('connect')
self.state_message = message_ready
for cb in self.state_cb:
if self.state_message is not message_ready:
return self.reactor.NEVER
cb('ready')
if self.start_args.get('debugoutput') is None:
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
except (self.config_error, pins.error) as e:
@ -257,10 +263,11 @@ class Printer:
self.invoke_shutdown(self.async_shutdown_msg)
continue
self._stats(self.reactor.monotonic(), force_output=True)
for m in self.mcus:
if run_result == 'firmware_restart':
for m in self.lookup_module_objects('mcu'):
m.microcontroller_restart()
m.disconnect()
for cb in self.state_cb:
cb('disconnect')
except:
logging.exception("Unhandled exception during post run")
return run_result
@ -269,12 +276,8 @@ class Printer:
return
self.is_shutdown = True
self.state_message = "%s%s" % (msg, message_shutdown)
for m in self.mcus:
m.do_shutdown()
self.gcode.do_shutdown()
toolhead = self.objects.get('toolhead')
if toolhead is not None:
toolhead.do_shutdown()
for cb in self.state_cb:
cb('shutdown')
def invoke_async_shutdown(self, msg):
self.async_shutdown_msg = msg
self.request_exit("shutdown")

View File

@ -581,7 +581,7 @@ class MCU:
self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
for c in self._init_cmds:
self.send(self.create_command(c))
def connect(self):
def _connect(self):
if self.is_fileoutput():
self._connect_file()
else:
@ -666,9 +666,18 @@ class MCU:
def monotonic(self):
return self._reactor.monotonic()
# Restarts
def _disconnect(self):
self._serial.disconnect()
if self._steppersync is not None:
self._ffi_lib.steppersync_free(self._steppersync)
self._steppersync = None
def _shutdown(self, force=False):
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
return
self.send(self._emergency_stop_cmd.encode())
def _restart_arduino(self):
logging.info("Attempting MCU '%s' reset", self._name)
self.disconnect()
self._disconnect()
serialhdl.arduino_reset(self._serialport, self._reactor)
def _restart_via_command(self):
if ((self._reset_cmd is None and self._config_reset_cmd is None)
@ -679,7 +688,7 @@ class MCU:
# Attempt reset via config_reset command
logging.info("Attempting MCU '%s' config_reset command", self._name)
self._is_shutdown = True
self.do_shutdown(force=True)
self._shutdown(force=True)
self._reactor.pause(self._reactor.monotonic() + 0.015)
self.send(self._config_reset_cmd.encode())
else:
@ -687,10 +696,10 @@ class MCU:
logging.info("Attempting MCU '%s' reset command", self._name)
self.send(self._reset_cmd.encode())
self._reactor.pause(self._reactor.monotonic() + 0.015)
self.disconnect()
self._disconnect()
def _restart_rpi_usb(self):
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
self.disconnect()
self._disconnect()
chelper.run_hub_ctrl(0)
self._reactor.pause(self._reactor.monotonic() + 2.)
chelper.run_hub_ctrl(1)
@ -735,17 +744,15 @@ class MCU:
self._mcu_tick_stddev)
return ' '.join([msg, self._serial.stats(eventtime),
self._clocksync.stats(eventtime)])
def do_shutdown(self, force=False):
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
return
self.send(self._emergency_stop_cmd.encode())
def disconnect(self):
self._serial.disconnect()
if self._steppersync is not None:
self._ffi_lib.steppersync_free(self._steppersync)
self._steppersync = None
def printer_state(self, state):
if state == 'connect':
self._connect()
elif state == 'disconnect':
self._disconnect()
elif state == 'shutdown':
self._shutdown()
def __del__(self):
self.disconnect()
self._disconnect()
Common_MCU_errors = {
("Timer too close", "No next step", "Missed scheduling of next "): """

View File

@ -369,12 +369,13 @@ class ToolHead:
buffer_time = max(0., self.print_time - est_print_time)
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
self.print_time, buffer_time, self.print_stall)
def do_shutdown(self):
def printer_state(self, state):
if state == 'shutdown':
try:
self.move_queue.reset()
self.reset_print_time()
except:
logging.exception("Exception in do_shutdown")
logging.exception("Exception in toolhead shutdown")
def get_kinematics(self):
return self.kin
def get_max_velocity(self):