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:
parent
81013ba5c8
commit
d3665699f1
|
@ -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
|
||||
|
|
|
@ -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':
|
||||
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")
|
||||
|
|
|
@ -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 "): """
|
||||
|
|
|
@ -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):
|
||||
try:
|
||||
self.move_queue.reset()
|
||||
self.reset_print_time()
|
||||
except:
|
||||
logging.exception("Exception in do_shutdown")
|
||||
def printer_state(self, state):
|
||||
if state == 'shutdown':
|
||||
try:
|
||||
self.move_queue.reset()
|
||||
self.reset_print_time()
|
||||
except:
|
||||
logging.exception("Exception in toolhead shutdown")
|
||||
def get_kinematics(self):
|
||||
return self.kin
|
||||
def get_max_velocity(self):
|
||||
|
|
Loading…
Reference in New Issue