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
|
self.gcode_help[cmd] = desc
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime):
|
||||||
return "gcodein=%d" % (self.bytes_read,)
|
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.is_printer_ready = True
|
||||||
self.gcode_handlers = self.ready_gcode_handlers
|
self.gcode_handlers = self.ready_gcode_handlers
|
||||||
# Lookup printer components
|
# Lookup printer components
|
||||||
|
@ -85,14 +96,6 @@ class GCodeParser:
|
||||||
def reset_last_position(self):
|
def reset_last_position(self):
|
||||||
if self.toolhead is not None:
|
if self.toolhead is not None:
|
||||||
self.last_position = self.toolhead.get_position()
|
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):
|
def motor_heater_off(self):
|
||||||
if self.toolhead is None:
|
if self.toolhead is None:
|
||||||
return
|
return
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 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 sys, optparse, ConfigParser, logging, time, threading
|
import sys, optparse, logging, time, threading
|
||||||
import util, reactor, queuelogger, msgproto, gcode
|
import collections, ConfigParser, importlib
|
||||||
import pins, mcu, chipmisc, toolhead, extruder, heater, fan
|
import util, reactor, queuelogger, msgproto
|
||||||
|
import gcode, pins, mcu, chipmisc, toolhead, extruder, heater, fan
|
||||||
|
|
||||||
message_ready = "Printer is ready"
|
message_ready = "Printer is ready"
|
||||||
|
|
||||||
|
@ -134,8 +135,8 @@ class Printer:
|
||||||
if bglogger is not None:
|
if bglogger is not None:
|
||||||
bglogger.set_rollover_info("config", None)
|
bglogger.set_rollover_info("config", None)
|
||||||
self.reactor = reactor.Reactor()
|
self.reactor = reactor.Reactor()
|
||||||
self.gcode = gcode.GCodeParser(self, input_fd)
|
gc = gcode.GCodeParser(self, input_fd)
|
||||||
self.objects = {'gcode': self.gcode}
|
self.objects = collections.OrderedDict({'gcode': gc})
|
||||||
self.stats_timer = self.reactor.register_timer(self._stats)
|
self.stats_timer = self.reactor.register_timer(self._stats)
|
||||||
self.connect_timer = self.reactor.register_timer(
|
self.connect_timer = self.reactor.register_timer(
|
||||||
self._connect, self.reactor.NOW)
|
self._connect, self.reactor.NOW)
|
||||||
|
@ -145,7 +146,8 @@ class Printer:
|
||||||
self.async_shutdown_msg = ""
|
self.async_shutdown_msg = ""
|
||||||
self.run_result = None
|
self.run_result = None
|
||||||
self.fileconfig = None
|
self.fileconfig = None
|
||||||
self.mcus = []
|
self.stats_cb = []
|
||||||
|
self.state_cb = []
|
||||||
def get_start_args(self):
|
def get_start_args(self):
|
||||||
return self.start_args
|
return self.start_args
|
||||||
def get_reactor(self):
|
def get_reactor(self):
|
||||||
|
@ -165,8 +167,7 @@ class Printer:
|
||||||
return default
|
return default
|
||||||
def lookup_module_objects(self, module_name):
|
def lookup_module_objects(self, module_name):
|
||||||
prefix = module_name + ' '
|
prefix = module_name + ' '
|
||||||
objs = [self.objects[n]
|
objs = [self.objects[n] for n in self.objects if n.startswith(prefix)]
|
||||||
for n in sorted(self.objects) if n.startswith(prefix)]
|
|
||||||
if module_name in self.objects:
|
if module_name in self.objects:
|
||||||
return [self.objects[module_name]] + objs
|
return [self.objects[module_name]] + objs
|
||||||
return objs
|
return objs
|
||||||
|
@ -180,12 +181,8 @@ class Printer:
|
||||||
is_active = toolhead.check_active(eventtime)
|
is_active = toolhead.check_active(eventtime)
|
||||||
if not is_active and not force_output:
|
if not is_active and not force_output:
|
||||||
return eventtime + 1.
|
return eventtime + 1.
|
||||||
out = []
|
stats = [cb(eventtime) for cb in self.stats_cb]
|
||||||
out.append(self.gcode.stats(eventtime))
|
logging.info("Stats %.1f: %s", eventtime, ' '.join(stats))
|
||||||
out.append(toolhead.stats(eventtime))
|
|
||||||
for m in self.mcus:
|
|
||||||
out.append(m.stats(eventtime))
|
|
||||||
logging.info("Stats %.1f: %s", eventtime, ' '.join(out))
|
|
||||||
return eventtime + 1.
|
return eventtime + 1.
|
||||||
def _load_config(self):
|
def _load_config(self):
|
||||||
self.fileconfig = ConfigParser.RawConfigParser()
|
self.fileconfig = ConfigParser.RawConfigParser()
|
||||||
|
@ -200,7 +197,6 @@ class Printer:
|
||||||
config = ConfigWrapper(self, 'printer')
|
config = ConfigWrapper(self, 'printer')
|
||||||
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
|
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
|
||||||
m.add_printer_objects(self, config)
|
m.add_printer_objects(self, config)
|
||||||
self.mcus = self.lookup_module_objects('mcu')
|
|
||||||
# Validate that there are no undefined parameters in the config file
|
# Validate that there are no undefined parameters in the config file
|
||||||
valid_sections = { s: 1 for s, o in self.all_config_options }
|
valid_sections = { s: 1 for s, o in self.all_config_options }
|
||||||
for section in self.fileconfig.sections():
|
for section in self.fileconfig.sections():
|
||||||
|
@ -214,14 +210,24 @@ class Printer:
|
||||||
raise self.config_error(
|
raise self.config_error(
|
||||||
"Unknown option '%s' in section '%s'" % (
|
"Unknown option '%s' in section '%s'" % (
|
||||||
option, section))
|
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):
|
def _connect(self, eventtime):
|
||||||
self.reactor.unregister_timer(self.connect_timer)
|
self.reactor.unregister_timer(self.connect_timer)
|
||||||
try:
|
try:
|
||||||
self._load_config()
|
self._load_config()
|
||||||
for m in self.mcus:
|
for cb in self.state_cb:
|
||||||
m.connect()
|
if self.state_message is not message_startup:
|
||||||
self.gcode.connect()
|
return self.reactor.NEVER
|
||||||
|
cb('connect')
|
||||||
self.state_message = message_ready
|
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:
|
if self.start_args.get('debugoutput') is None:
|
||||||
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
||||||
except (self.config_error, pins.error) as e:
|
except (self.config_error, pins.error) as e:
|
||||||
|
@ -257,10 +263,11 @@ class Printer:
|
||||||
self.invoke_shutdown(self.async_shutdown_msg)
|
self.invoke_shutdown(self.async_shutdown_msg)
|
||||||
continue
|
continue
|
||||||
self._stats(self.reactor.monotonic(), force_output=True)
|
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.microcontroller_restart()
|
||||||
m.disconnect()
|
for cb in self.state_cb:
|
||||||
|
cb('disconnect')
|
||||||
except:
|
except:
|
||||||
logging.exception("Unhandled exception during post run")
|
logging.exception("Unhandled exception during post run")
|
||||||
return run_result
|
return run_result
|
||||||
|
@ -269,12 +276,8 @@ class Printer:
|
||||||
return
|
return
|
||||||
self.is_shutdown = True
|
self.is_shutdown = True
|
||||||
self.state_message = "%s%s" % (msg, message_shutdown)
|
self.state_message = "%s%s" % (msg, message_shutdown)
|
||||||
for m in self.mcus:
|
for cb in self.state_cb:
|
||||||
m.do_shutdown()
|
cb('shutdown')
|
||||||
self.gcode.do_shutdown()
|
|
||||||
toolhead = self.objects.get('toolhead')
|
|
||||||
if toolhead is not None:
|
|
||||||
toolhead.do_shutdown()
|
|
||||||
def invoke_async_shutdown(self, msg):
|
def invoke_async_shutdown(self, msg):
|
||||||
self.async_shutdown_msg = msg
|
self.async_shutdown_msg = msg
|
||||||
self.request_exit("shutdown")
|
self.request_exit("shutdown")
|
||||||
|
|
|
@ -581,7 +581,7 @@ class MCU:
|
||||||
self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
|
self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
|
||||||
for c in self._init_cmds:
|
for c in self._init_cmds:
|
||||||
self.send(self.create_command(c))
|
self.send(self.create_command(c))
|
||||||
def connect(self):
|
def _connect(self):
|
||||||
if self.is_fileoutput():
|
if self.is_fileoutput():
|
||||||
self._connect_file()
|
self._connect_file()
|
||||||
else:
|
else:
|
||||||
|
@ -666,9 +666,18 @@ class MCU:
|
||||||
def monotonic(self):
|
def monotonic(self):
|
||||||
return self._reactor.monotonic()
|
return self._reactor.monotonic()
|
||||||
# Restarts
|
# 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):
|
def _restart_arduino(self):
|
||||||
logging.info("Attempting MCU '%s' reset", self._name)
|
logging.info("Attempting MCU '%s' reset", self._name)
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
serialhdl.arduino_reset(self._serialport, self._reactor)
|
serialhdl.arduino_reset(self._serialport, self._reactor)
|
||||||
def _restart_via_command(self):
|
def _restart_via_command(self):
|
||||||
if ((self._reset_cmd is None and self._config_reset_cmd is None)
|
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
|
# Attempt reset via config_reset command
|
||||||
logging.info("Attempting MCU '%s' config_reset command", self._name)
|
logging.info("Attempting MCU '%s' config_reset command", self._name)
|
||||||
self._is_shutdown = True
|
self._is_shutdown = True
|
||||||
self.do_shutdown(force=True)
|
self._shutdown(force=True)
|
||||||
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
||||||
self.send(self._config_reset_cmd.encode())
|
self.send(self._config_reset_cmd.encode())
|
||||||
else:
|
else:
|
||||||
|
@ -687,10 +696,10 @@ class MCU:
|
||||||
logging.info("Attempting MCU '%s' reset command", self._name)
|
logging.info("Attempting MCU '%s' reset command", self._name)
|
||||||
self.send(self._reset_cmd.encode())
|
self.send(self._reset_cmd.encode())
|
||||||
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
def _restart_rpi_usb(self):
|
def _restart_rpi_usb(self):
|
||||||
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
|
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
chelper.run_hub_ctrl(0)
|
chelper.run_hub_ctrl(0)
|
||||||
self._reactor.pause(self._reactor.monotonic() + 2.)
|
self._reactor.pause(self._reactor.monotonic() + 2.)
|
||||||
chelper.run_hub_ctrl(1)
|
chelper.run_hub_ctrl(1)
|
||||||
|
@ -735,17 +744,15 @@ class MCU:
|
||||||
self._mcu_tick_stddev)
|
self._mcu_tick_stddev)
|
||||||
return ' '.join([msg, self._serial.stats(eventtime),
|
return ' '.join([msg, self._serial.stats(eventtime),
|
||||||
self._clocksync.stats(eventtime)])
|
self._clocksync.stats(eventtime)])
|
||||||
def do_shutdown(self, force=False):
|
def printer_state(self, state):
|
||||||
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
|
if state == 'connect':
|
||||||
return
|
self._connect()
|
||||||
self.send(self._emergency_stop_cmd.encode())
|
elif state == 'disconnect':
|
||||||
def disconnect(self):
|
self._disconnect()
|
||||||
self._serial.disconnect()
|
elif state == 'shutdown':
|
||||||
if self._steppersync is not None:
|
self._shutdown()
|
||||||
self._ffi_lib.steppersync_free(self._steppersync)
|
|
||||||
self._steppersync = None
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
|
|
||||||
Common_MCU_errors = {
|
Common_MCU_errors = {
|
||||||
("Timer too close", "No next step", "Missed scheduling of next "): """
|
("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)
|
buffer_time = max(0., self.print_time - est_print_time)
|
||||||
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
||||||
self.print_time, buffer_time, self.print_stall)
|
self.print_time, buffer_time, self.print_stall)
|
||||||
def do_shutdown(self):
|
def printer_state(self, state):
|
||||||
try:
|
if state == 'shutdown':
|
||||||
self.move_queue.reset()
|
try:
|
||||||
self.reset_print_time()
|
self.move_queue.reset()
|
||||||
except:
|
self.reset_print_time()
|
||||||
logging.exception("Exception in do_shutdown")
|
except:
|
||||||
|
logging.exception("Exception in toolhead shutdown")
|
||||||
def get_kinematics(self):
|
def get_kinematics(self):
|
||||||
return self.kin
|
return self.kin
|
||||||
def get_max_velocity(self):
|
def get_max_velocity(self):
|
||||||
|
|
Loading…
Reference in New Issue