diff options
Diffstat (limited to 'klippy/klippy.py')
-rw-r--r-- | klippy/klippy.py | 246 |
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() |