klipper-dgus/klippy/klippy.py

211 lines
8.1 KiB
Python
Raw Normal View History

#!/usr/bin/env python
# Main code for host side printer firmware
#
# Copyright (C) 2016 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 gcode, toolhead, util, mcu, fan, heater, extruder, reactor, queuelogger
message_startup = """
The klippy host software is attempting to connect. Please
retry in a few moments.
Printer is not ready
"""
message_restart = """
This is an unrecoverable error. Please correct the
underlying issue and then manually restart the klippy host
software.
Printer is halted
"""
message_mcu_connect_error = """
This is an unrecoverable error. Please manually restart
both the firmware and the host software.
Error configuring printer
"""
message_shutdown = """
This is an unrecoverable error. Please correct the
underlying issue and then manually restart both the
firmware and the host software.
Printer is shutdown
"""
class ConfigWrapper:
error = ConfigParser.Error
def __init__(self, printer, section):
self.printer = printer
self.section = section
def get(self, option, default=None):
if not self.printer.fileconfig.has_option(self.section, option):
return default
return self.printer.fileconfig.get(self.section, option)
def getint(self, option, default=None):
if not self.printer.fileconfig.has_option(self.section, option):
return default
return self.printer.fileconfig.getint(self.section, option)
def getfloat(self, option, default=None):
if not self.printer.fileconfig.has_option(self.section, option):
return default
return self.printer.fileconfig.getfloat(self.section, option)
def getboolean(self, option, default=None):
if not self.printer.fileconfig.has_option(self.section, option):
return default
return self.printer.fileconfig.getboolean(self.section, option)
def getsection(self, section):
return ConfigWrapper(self.printer, section)
class Printer:
def __init__(self, conffile, input_fd, is_fileinput=False):
self.conffile = conffile
self.reactor = reactor.Reactor()
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
self.stats_timer = self.reactor.register_timer(self.stats)
self.connect_timer = self.reactor.register_timer(
self.connect, self.reactor.NOW)
self.state_message = message_startup
self.debugoutput = self.dictionary = None
self.fileconfig = None
self.mcu = None
self.objects = {}
def set_fileoutput(self, debugoutput, dictionary):
self.debugoutput = debugoutput
self.dictionary = dictionary
def stats(self, eventtime):
out = []
out.append(self.gcode.stats(eventtime))
toolhead = self.objects.get('toolhead')
out.append(toolhead.stats(eventtime))
out.append(self.mcu.stats(eventtime))
logging.info("Stats %.0f: %s" % (eventtime, ' '.join(out)))
return eventtime + 1.
def load_config(self):
self.fileconfig = ConfigParser.RawConfigParser()
res = self.fileconfig.read(self.conffile)
if not res:
raise ConfigParser.Error("Unable to open config file %s" % (
self.conffile,))
self.mcu = mcu.MCU(self, ConfigWrapper(self, 'mcu'))
if self.fileconfig.has_section('fan'):
self.objects['fan'] = fan.PrinterFan(
self, ConfigWrapper(self, 'fan'))
if self.fileconfig.has_section('extruder'):
self.objects['extruder'] = extruder.PrinterExtruder(
self, ConfigWrapper(self, 'extruder'))
if self.fileconfig.has_section('heater_bed'):
self.objects['heater_bed'] = heater.PrinterHeater(
self, ConfigWrapper(self, 'heater_bed'))
self.objects['toolhead'] = toolhead.ToolHead(
self, ConfigWrapper(self, 'printer'))
def build_config(self):
for oname in sorted(self.objects.keys()):
self.objects[oname].build_config()
self.gcode.build_config()
self.mcu.build_config()
def connect(self, eventtime):
try:
self.load_config()
if self.debugoutput is None:
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
else:
self.mcu.connect_file(self.debugoutput, self.dictionary)
self.mcu.connect()
self.build_config()
self.gcode.set_printer_ready(True)
self.state_message = "Running"
except ConfigParser.Error, e:
logging.exception("Config error")
self.state_message = "%s%s" % (str(e), message_restart)
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
except mcu.error, e:
logging.exception("MCU error during connect")
self.state_message = "%s%s" % (str(e), message_mcu_connect_error)
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
except:
logging.exception("Unhandled exception during connect")
self.state_message = "Internal error during connect.%s" % (
message_restart)
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
self.reactor.unregister_timer(self.connect_timer)
return self.reactor.NEVER
def run(self):
try:
self.reactor.run()
except:
logging.exception("Unhandled exception during run")
return
# If gcode exits, then exit the MCU
self.stats(time.time())
self.mcu.disconnect()
self.stats(time.time())
def get_state_message(self):
return self.state_message
def note_shutdown(self, msg):
self.state_message = "Firmware shutdown: %s%s" % (
msg, message_shutdown)
self.gcode.set_printer_ready(False)
######################################################################
# Startup
######################################################################
def read_dictionary(filename):
dfile = open(filename, 'rb')
dictionary = dfile.read()
dfile.close()
return dictionary
def main():
usage = "%prog [options] <config file>"
opts = optparse.OptionParser(usage)
opts.add_option("-o", "--debugoutput", dest="outputfile",
help="write output to file instead of to serial port")
opts.add_option("-i", "--debuginput", dest="inputfile",
help="read commands from file instead of from tty port")
opts.add_option("-I", "--input-tty", dest="inputtty", default='/tmp/printer',
help="input tty name (default is /tmp/printer)")
opts.add_option("-l", "--logfile", dest="logfile",
help="write log to file instead of stderr")
opts.add_option("-v", action="store_true", dest="verbose",
help="enable debug messages")
opts.add_option("-d", dest="read_dictionary",
help="file to read for mcu protocol dictionary")
options, args = opts.parse_args()
if len(args) != 1:
opts.error("Incorrect number of arguments")
conffile = args[0]
input_fd = debuginput = debugoutput = bglogger = None
debuglevel = logging.INFO
if options.verbose:
debuglevel = logging.DEBUG
if options.inputfile:
debuginput = open(options.inputfile, 'rb')
input_fd = debuginput.fileno()
else:
input_fd = util.create_pty(options.inputtty)
if options.outputfile:
debugoutput = open(options.outputfile, 'wb')
if options.logfile:
bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel)
else:
logging.basicConfig(level=debuglevel)
logging.info("Starting Klippy...")
# Start firmware
printer = Printer(conffile, input_fd, is_fileinput=debuginput is not None)
if debugoutput:
proto_dict = read_dictionary(options.read_dictionary)
printer.set_fileoutput(debugoutput, proto_dict)
printer.run()
if bglogger is not None:
bglogger.stop()
if __name__ == '__main__':
main()