aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/klippy.py
diff options
context:
space:
mode:
Diffstat (limited to 'klippy/klippy.py')
-rw-r--r--klippy/klippy.py246
1 files changed, 157 insertions, 89 deletions
diff --git a/klippy/klippy.py b/klippy/klippy.py
index 316343cb..757a2efc 100644
--- a/klippy/klippy.py
+++ b/klippy/klippy.py
@@ -22,9 +22,11 @@ command to reload the config and restart the host software.
Printer is halted
"""
+
class Printer:
config_error = configfile.error
command_error = gcode.CommandError
+
def __init__(self, main_reactor, bglogger, start_args):
self.bglogger = bglogger
self.start_args = start_args
@@ -38,10 +40,13 @@ class Printer:
# Init printer components that must be setup prior to config
for m in [gcode, webhooks]:
m.add_early_printer_objects(self)
+
def get_start_args(self):
return self.start_args
+
def get_reactor(self):
return self.reactor
+
def get_state_message(self):
if self.state_message == message_ready:
category = "ready"
@@ -52,58 +57,64 @@ class Printer:
else:
category = "error"
return self.state_message, category
+
def is_shutdown(self):
return self.in_shutdown_state
+
def _set_state(self, msg):
if self.state_message in (message_ready, message_startup):
self.state_message = msg
- if (msg != message_ready
- and self.start_args.get('debuginput') is not None):
- self.request_exit('error_exit')
+ if msg != message_ready and self.start_args.get("debuginput") is not None:
+ self.request_exit("error_exit")
+
def update_error_msg(self, oldmsg, newmsg):
- if (self.state_message != oldmsg
+ if (
+ self.state_message != oldmsg
or self.state_message in (message_ready, message_startup)
- or newmsg in (message_ready, message_startup)):
+ or newmsg in (message_ready, message_startup)
+ ):
return
self.state_message = newmsg
logging.error(newmsg)
+
def add_object(self, name, obj):
if name in self.objects:
- raise self.config_error(
- "Printer object '%s' already created" % (name,))
+ raise self.config_error("Printer object '%s' already created" % (name,))
self.objects[name] = obj
+
def lookup_object(self, name, default=configfile.sentinel):
if name in self.objects:
return self.objects[name]
if default is configfile.sentinel:
raise self.config_error("Unknown config object '%s'" % (name,))
return default
+
def lookup_objects(self, module=None):
if module is None:
return list(self.objects.items())
- prefix = module + ' '
- objs = [(n, self.objects[n])
- for n in self.objects if n.startswith(prefix)]
+ prefix = module + " "
+ objs = [(n, self.objects[n]) for n in self.objects if n.startswith(prefix)]
if module in self.objects:
return [(module, self.objects[module])] + objs
return objs
+
def load_object(self, config, section, default=configfile.sentinel):
if section in self.objects:
return self.objects[section]
module_parts = section.split()
module_name = module_parts[0]
- py_name = os.path.join(os.path.dirname(__file__),
- 'extras', module_name + '.py')
- py_dirname = os.path.join(os.path.dirname(__file__),
- 'extras', module_name, '__init__.py')
+ py_name = os.path.join(os.path.dirname(__file__), "extras", module_name + ".py")
+ py_dirname = os.path.join(
+ os.path.dirname(__file__), "extras", module_name, "__init__.py"
+ )
if not os.path.exists(py_name) and not os.path.exists(py_dirname):
if default is not configfile.sentinel:
return default
raise self.config_error("Unable to load module '%s'" % (section,))
- mod = importlib.import_module('extras.' + module_name)
- init_func = 'load_config'
+ mod = importlib.import_module("extras." + module_name)
+ init_func = "load_config"
if len(module_parts) > 1:
- init_func = 'load_config_prefix'
+ init_func = "load_config_prefix"
init_func = getattr(mod, init_func, None)
if init_func is None:
if default is not configfile.sentinel:
@@ -111,20 +122,22 @@ class Printer:
raise self.config_error("Unable to load module '%s'" % (section,))
self.objects[section] = init_func(config.getsection(section))
return self.objects[section]
+
def _read_config(self):
- self.objects['configfile'] = pconfig = configfile.PrinterConfig(self)
+ self.objects["configfile"] = pconfig = configfile.PrinterConfig(self)
config = pconfig.read_main_config()
if self.bglogger is not None:
pconfig.log_config(config)
# Create printer components
for m in [pins, mcu]:
m.add_printer_objects(config)
- for section_config in config.get_prefix_sections(''):
+ for section_config in config.get_prefix_sections(""):
self.load_object(config, section_config.get_name(), None)
for m in [toolhead]:
m.add_printer_objects(config)
# Validate that there are no undefined parameters in the config file
pconfig.check_unused_options(config)
+
def _connect(self, eventtime):
try:
self._read_config()
@@ -153,8 +166,13 @@ class Printer:
return
except Exception as e:
logging.exception("Unhandled exception during connect")
- self._set_state("Internal error during connect: %s\n%s"
- % (str(e), message_restart,))
+ self._set_state(
+ "Internal error during connect: %s\n%s"
+ % (
+ str(e),
+ message_restart,
+ )
+ )
return
try:
self._set_state(message_ready)
@@ -164,13 +182,17 @@ class Printer:
cb()
except Exception as e:
logging.exception("Unhandled exception during ready callback")
- self.invoke_shutdown("Internal error during ready callback: %s"
- % (str(e),))
+ self.invoke_shutdown("Internal error during ready callback: %s" % (str(e),))
+
def run(self):
systime = time.time()
monotime = self.reactor.monotonic()
- logging.info("Start printer at %s (%.1f %.1f)",
- time.asctime(time.localtime(systime)), systime, monotime)
+ logging.info(
+ "Start printer at %s (%.1f %.1f)",
+ time.asctime(time.localtime(systime)),
+ systime,
+ monotime,
+ )
# Enter main reactor loop
try:
self.reactor.run()
@@ -179,8 +201,7 @@ class Printer:
logging.exception(msg)
# Exception from a reactor callback - try to shutdown
try:
- self.reactor.register_callback((lambda e:
- self.invoke_shutdown(msg)))
+ self.reactor.register_callback((lambda e: self.invoke_shutdown(msg)))
self.reactor.run()
except:
logging.exception("Repeat unhandled exception during run")
@@ -189,17 +210,19 @@ class Printer:
# Check restart flags
run_result = self.run_result
try:
- if run_result == 'firmware_restart':
+ if run_result == "firmware_restart":
self.send_event("klippy:firmware_restart")
self.send_event("klippy:disconnect")
except:
logging.exception("Unhandled exception during post run")
return run_result
+
def set_rollover_info(self, name, info, log=True):
if log:
logging.info(info)
if self.bglogger is not None:
self.bglogger.set_rollover_info(name, info)
+
def invoke_shutdown(self, msg, details={}):
if self.in_shutdown_state:
return
@@ -211,16 +234,20 @@ class Printer:
cb()
except:
logging.exception("Exception during shutdown handler")
- logging.info("Reactor garbage collection: %s",
- self.reactor.get_gc_stats())
+ logging.info("Reactor garbage collection: %s", self.reactor.get_gc_stats())
self.send_event("klippy:notify_mcu_shutdown", msg, details)
+
def invoke_async_shutdown(self, msg, details={}):
self.reactor.register_async_callback(
- (lambda e: self.invoke_shutdown(msg, details)))
+ (lambda e: self.invoke_shutdown(msg, details))
+ )
+
def register_event_handler(self, event, callback):
self.event_handlers.setdefault(event, []).append(callback)
+
def send_event(self, event, *params):
return [cb(*params) for cb in self.event_handlers.get(event, [])]
+
def request_exit(self, result):
if self.run_result is None:
self.run_result = result
@@ -231,137 +258,178 @@ class Printer:
# Startup
######################################################################
+
def import_test():
# Import all optional modules (used as a build test)
dname = os.path.dirname(__file__)
- for mname in ['extras', 'kinematics']:
+ for mname in ["extras", "kinematics"]:
for fname in os.listdir(os.path.join(dname, mname)):
- if fname.endswith('.py') and fname != '__init__.py':
+ if fname.endswith(".py") and fname != "__init__.py":
module_name = fname[:-3]
else:
- iname = os.path.join(dname, mname, fname, '__init__.py')
+ iname = os.path.join(dname, mname, fname, "__init__.py")
if not os.path.exists(iname):
continue
module_name = fname
- importlib.import_module(mname + '.' + module_name)
+ importlib.import_module(mname + "." + module_name)
sys.exit(0)
+
def arg_dictionary(option, opt_str, value, parser):
key, fname = "dictionary", value
- if '=' in value:
- mcu_name, fname = value.split('=', 1)
+ if "=" in value:
+ mcu_name, fname = value.split("=", 1)
key = "dictionary_" + mcu_name
if parser.values.dictionary is None:
parser.values.dictionary = {}
parser.values.dictionary[key] = fname
+
def main():
usage = "%prog [options] <config file>"
opts = optparse.OptionParser(usage)
- opts.add_option("-i", "--debuginput", dest="debuginput",
- 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("-a", "--api-server", dest="apiserver",
- help="api server unix domain socket filename")
- 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("-o", "--debugoutput", dest="debugoutput",
- help="write output to file instead of to serial port")
- opts.add_option("-d", "--dictionary", dest="dictionary", type="string",
- action="callback", callback=arg_dictionary,
- help="file to read for mcu protocol dictionary")
- opts.add_option("--import-test", action="store_true",
- help="perform an import module test")
+ opts.add_option(
+ "-i",
+ "--debuginput",
+ dest="debuginput",
+ 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(
+ "-a",
+ "--api-server",
+ dest="apiserver",
+ help="api server unix domain socket filename",
+ )
+ 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(
+ "-o",
+ "--debugoutput",
+ dest="debugoutput",
+ help="write output to file instead of to serial port",
+ )
+ opts.add_option(
+ "-d",
+ "--dictionary",
+ dest="dictionary",
+ type="string",
+ action="callback",
+ callback=arg_dictionary,
+ help="file to read for mcu protocol dictionary",
+ )
+ opts.add_option(
+ "--import-test", action="store_true", help="perform an import module test"
+ )
options, args = opts.parse_args()
if options.import_test:
import_test()
if len(args) != 1:
opts.error("Incorrect number of arguments")
- start_args = {'config_file': args[0], 'apiserver': options.apiserver,
- 'start_reason': 'startup'}
+ start_args = {
+ "config_file": args[0],
+ "apiserver": options.apiserver,
+ "start_reason": "startup",
+ }
debuglevel = logging.INFO
if options.verbose:
debuglevel = logging.DEBUG
if options.debuginput:
- start_args['debuginput'] = options.debuginput
- debuginput = open(options.debuginput, 'rb')
- start_args['gcode_fd'] = debuginput.fileno()
+ start_args["debuginput"] = options.debuginput
+ debuginput = open(options.debuginput, "rb")
+ start_args["gcode_fd"] = debuginput.fileno()
else:
- start_args['gcode_fd'] = util.create_pty(options.inputtty)
+ start_args["gcode_fd"] = util.create_pty(options.inputtty)
if options.debugoutput:
- start_args['debugoutput'] = options.debugoutput
+ start_args["debugoutput"] = options.debugoutput
start_args.update(options.dictionary)
bglogger = None
if options.logfile:
- start_args['log_file'] = options.logfile
+ start_args["log_file"] = options.logfile
bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel)
else:
logging.getLogger().setLevel(debuglevel)
logging.info("Starting Klippy...")
git_info = util.get_git_version()
git_vers = git_info["version"]
- extra_files = [fname for code, fname in git_info["file_status"]
- if (code in ('??', '!!') and fname.endswith('.py')
- and (fname.startswith('klippy/kinematics/')
- or fname.startswith('klippy/extras/')))]
- modified_files = [fname for code, fname in git_info["file_status"]
- if code == 'M']
+ extra_files = [
+ fname
+ for code, fname in git_info["file_status"]
+ if (
+ code in ("??", "!!")
+ and fname.endswith(".py")
+ and (
+ fname.startswith("klippy/kinematics/")
+ or fname.startswith("klippy/extras/")
+ )
+ )
+ ]
+ modified_files = [fname for code, fname in git_info["file_status"] if code == "M"]
extra_git_desc = ""
if extra_files:
- if not git_vers.endswith('-dirty'):
- git_vers = git_vers + '-dirty'
+ if not git_vers.endswith("-dirty"):
+ git_vers = git_vers + "-dirty"
if len(extra_files) > 10:
extra_files[10:] = ["(+%d files)" % (len(extra_files) - 10,)]
- extra_git_desc += "\nUntracked files: %s" % (', '.join(extra_files),)
+ extra_git_desc += "\nUntracked files: %s" % (", ".join(extra_files),)
if modified_files:
if len(modified_files) > 10:
modified_files[10:] = ["(+%d files)" % (len(modified_files) - 10,)]
- extra_git_desc += "\nModified files: %s" % (', '.join(modified_files),)
+ extra_git_desc += "\nModified files: %s" % (", ".join(modified_files),)
extra_git_desc += "\nBranch: %s" % (git_info["branch"])
extra_git_desc += "\nRemote: %s" % (git_info["remote"])
extra_git_desc += "\nTracked URL: %s" % (git_info["url"])
- start_args['software_version'] = git_vers
- start_args['cpu_info'] = util.get_cpu_info()
+ start_args["software_version"] = git_vers
+ start_args["cpu_info"] = util.get_cpu_info()
if bglogger is not None:
- versions = "\n".join([
- "Args: %s" % (sys.argv,),
- "Git version: %s%s" % (repr(start_args['software_version']),
- extra_git_desc),
- "CPU: %s" % (start_args['cpu_info'],),
- "Python: %s" % (repr(sys.version),)])
+ versions = "\n".join(
+ [
+ "Args: %s" % (sys.argv,),
+ "Git version: %s%s"
+ % (repr(start_args["software_version"]), extra_git_desc),
+ "CPU: %s" % (start_args["cpu_info"],),
+ "Python: %s" % (repr(sys.version),),
+ ]
+ )
logging.info(versions)
elif not options.debugoutput:
- logging.warning("No log file specified!"
- " Severe timing issues may result!")
+ logging.warning("No log file specified!" " Severe timing issues may result!")
gc.disable()
# Start Printer() class
while 1:
if bglogger is not None:
bglogger.clear_rollover_info()
- bglogger.set_rollover_info('versions', versions)
+ bglogger.set_rollover_info("versions", versions)
gc.collect()
main_reactor = reactor.Reactor(gc_checking=True)
printer = Printer(main_reactor, bglogger, start_args)
res = printer.run()
- if res in ['exit', 'error_exit']:
+ if res in ["exit", "error_exit"]:
break
- time.sleep(1.)
+ time.sleep(1.0)
main_reactor.finalize()
main_reactor = printer = None
logging.info("Restarting printer")
- start_args['start_reason'] = res
+ start_args["start_reason"] = res
if bglogger is not None:
bglogger.stop()
- if res == 'error_exit':
+ if res == "error_exit":
sys.exit(-1)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()