| 
									
										
										
										
											2017-06-27 20:23:30 -04:00
										 |  |  | #!/usr/bin/env python2 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | # Main code for host side printer firmware | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  | # Copyright (C) 2016-2018  Kevin O'Connor <kevin@koconnor.net> | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | # | 
					
						
							|  |  |  | # This file may be distributed under the terms of the GNU GPLv3 license. | 
					
						
							| 
									
										
										
										
											2018-01-19 21:25:58 -05:00
										 |  |  | import sys, os, optparse, logging, time, threading | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  | import collections, ConfigParser, importlib | 
					
						
							|  |  |  | import util, reactor, queuelogger, msgproto | 
					
						
							| 
									
										
										
										
											2018-07-12 22:15:45 -04:00
										 |  |  | import gcode, pins, heater, mcu, toolhead | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-11 22:21:55 -05:00
										 |  |  | message_ready = "Printer is ready" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  | message_startup = """
 | 
					
						
							| 
									
										
										
										
											2018-03-12 23:12:39 -04:00
										 |  |  | Printer is not ready | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  | The klippy host software is attempting to connect.  Please | 
					
						
							|  |  |  | retry in a few moments. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | message_restart = """
 | 
					
						
							| 
									
										
										
										
											2016-11-30 23:47:40 -05:00
										 |  |  | Once the underlying issue is corrected, use the "RESTART" | 
					
						
							|  |  |  | command to reload the config and restart the host software. | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  | Printer is halted | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 23:50:13 -05:00
										 |  |  | message_protocol_error = """
 | 
					
						
							|  |  |  | This type of error is frequently caused by running an older | 
					
						
							|  |  |  | version of the firmware on the micro-controller (fix by | 
					
						
							|  |  |  | recompiling and flashing the firmware). | 
					
						
							|  |  |  | Once the underlying issue is corrected, use the "RESTART" | 
					
						
							|  |  |  | command to reload the config and restart the host software. | 
					
						
							|  |  |  | Protocol error connecting to printer | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  | message_mcu_connect_error = """
 | 
					
						
							| 
									
										
										
										
											2017-03-08 22:26:10 -05:00
										 |  |  | Once the underlying issue is corrected, use the | 
					
						
							|  |  |  | "FIRMWARE_RESTART" command to reset the firmware, reload the | 
					
						
							|  |  |  | config, and restart the host software. | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  | Error configuring printer | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | message_shutdown = """
 | 
					
						
							| 
									
										
										
										
											2017-04-13 14:53:41 -04:00
										 |  |  | Once the underlying issue is corrected, use the | 
					
						
							|  |  |  | "FIRMWARE_RESTART" command to reset the firmware, reload the | 
					
						
							|  |  |  | config, and restart the host software. | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  | Printer is shutdown | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | class ConfigWrapper: | 
					
						
							| 
									
										
										
										
											2016-11-30 15:05:26 -05:00
										 |  |  |     error = ConfigParser.Error | 
					
						
							| 
									
										
										
										
											2016-11-30 15:39:36 -05:00
										 |  |  |     class sentinel: | 
					
						
							|  |  |  |         pass | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |     def __init__(self, printer, fileconfig, section): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.printer = printer | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         self.fileconfig = fileconfig | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.section = section | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |     def get_printer(self): | 
					
						
							|  |  |  |         return self.printer | 
					
						
							|  |  |  |     def get_name(self): | 
					
						
							|  |  |  |         return self.section | 
					
						
							|  |  |  |     def _get_wrapper(self, parser, option, default, | 
					
						
							|  |  |  |                      minval=None, maxval=None, above=None, below=None): | 
					
						
							| 
									
										
										
										
											2016-11-30 15:39:36 -05:00
										 |  |  |         if (default is not self.sentinel | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |             and not self.fileconfig.has_option(self.section, option)): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             return default | 
					
						
							| 
									
										
										
										
											2016-11-30 16:07:17 -05:00
										 |  |  |         self.printer.all_config_options[ | 
					
						
							|  |  |  |             (self.section.lower(), option.lower())] = 1 | 
					
						
							| 
									
										
										
										
											2016-11-30 15:39:36 -05:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2017-04-11 11:37:09 -04:00
										 |  |  |             v = parser(self.section, option) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except self.error as e: | 
					
						
							| 
									
										
										
										
											2016-11-30 15:39:36 -05:00
										 |  |  |             raise | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             raise self.error("Unable to parse option '%s' in section '%s'" % ( | 
					
						
							|  |  |  |                 option, self.section)) | 
					
						
							| 
									
										
										
										
											2017-04-11 11:37:09 -04:00
										 |  |  |         if minval is not None and v < minval: | 
					
						
							|  |  |  |             raise self.error( | 
					
						
							|  |  |  |                 "Option '%s' in section '%s' must have minimum of %s" % ( | 
					
						
							|  |  |  |                     option, self.section, minval)) | 
					
						
							|  |  |  |         if maxval is not None and v > maxval: | 
					
						
							|  |  |  |             raise self.error( | 
					
						
							|  |  |  |                 "Option '%s' in section '%s' must have maximum of %s" % ( | 
					
						
							|  |  |  |                     option, self.section, maxval)) | 
					
						
							|  |  |  |         if above is not None and v <= above: | 
					
						
							|  |  |  |             raise self.error( | 
					
						
							|  |  |  |                 "Option '%s' in section '%s' must be above %s" % ( | 
					
						
							|  |  |  |                     option, self.section, above)) | 
					
						
							|  |  |  |         if below is not None and v >= below: | 
					
						
							|  |  |  |             raise self.error( | 
					
						
							|  |  |  |                 "Option '%s' in section '%s' must be below %s" % ( | 
					
						
							|  |  |  |                     option, self.section, below)) | 
					
						
							|  |  |  |         return v | 
					
						
							| 
									
										
										
										
											2016-11-30 15:39:36 -05:00
										 |  |  |     def get(self, option, default=sentinel): | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         return self._get_wrapper(self.fileconfig.get, option, default) | 
					
						
							| 
									
										
										
										
											2017-04-11 11:37:09 -04:00
										 |  |  |     def getint(self, option, default=sentinel, minval=None, maxval=None): | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |         return self._get_wrapper( | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |             self.fileconfig.getint, option, default, minval, maxval) | 
					
						
							|  |  |  |     def getfloat(self, option, default=sentinel, | 
					
						
							|  |  |  |                  minval=None, maxval=None, above=None, below=None): | 
					
						
							|  |  |  |         return self._get_wrapper(self.fileconfig.getfloat, option, default, | 
					
						
							|  |  |  |                                  minval, maxval, above, below) | 
					
						
							| 
									
										
										
										
											2016-11-30 15:39:36 -05:00
										 |  |  |     def getboolean(self, option, default=sentinel): | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         return self._get_wrapper(self.fileconfig.getboolean, option, default) | 
					
						
							| 
									
										
										
										
											2016-11-30 15:50:28 -05:00
										 |  |  |     def getchoice(self, option, choices, default=sentinel): | 
					
						
							|  |  |  |         c = self.get(option, default) | 
					
						
							|  |  |  |         if c not in choices: | 
					
						
							|  |  |  |             raise self.error( | 
					
						
							| 
									
										
										
										
											2018-04-20 13:30:58 -04:00
										 |  |  |                 "Choice '%s' for option '%s' in section '%s'" | 
					
						
							|  |  |  |                 " is not a valid choice" % (c, option, self.section)) | 
					
						
							| 
									
										
										
										
											2016-11-30 15:50:28 -05:00
										 |  |  |         return choices[c] | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def getsection(self, section): | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         return ConfigWrapper(self.printer, self.fileconfig, section) | 
					
						
							| 
									
										
										
										
											2017-04-29 13:57:02 -04:00
										 |  |  |     def has_section(self, section): | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         return self.fileconfig.has_section(section) | 
					
						
							| 
									
										
										
										
											2017-07-04 12:24:11 -04:00
										 |  |  |     def get_prefix_sections(self, prefix): | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         return [self.getsection(s) for s in self.fileconfig.sections() | 
					
						
							| 
									
										
										
										
											2017-07-04 12:24:11 -04:00
										 |  |  |                 if s.startswith(prefix)] | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-24 10:17:12 -05:00
										 |  |  | class ConfigLogger(): | 
					
						
							| 
									
										
										
										
											2017-05-01 13:44:06 -04:00
										 |  |  |     def __init__(self, cfg, bglogger): | 
					
						
							|  |  |  |         self.lines = ["===== Config file ====="] | 
					
						
							| 
									
										
										
										
											2016-12-24 10:17:12 -05:00
										 |  |  |         cfg.write(self) | 
					
						
							| 
									
										
										
										
											2017-05-01 13:44:06 -04:00
										 |  |  |         self.lines.append("=======================") | 
					
						
							|  |  |  |         data = "\n".join(self.lines) | 
					
						
							|  |  |  |         logging.info(data) | 
					
						
							|  |  |  |         bglogger.set_rollover_info("config", data) | 
					
						
							| 
									
										
										
										
											2016-12-24 10:17:12 -05:00
										 |  |  |     def write(self, data): | 
					
						
							| 
									
										
										
										
											2017-05-01 13:44:06 -04:00
										 |  |  |         self.lines.append(data.strip()) | 
					
						
							| 
									
										
										
										
											2016-12-24 10:17:12 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | class Printer: | 
					
						
							| 
									
										
										
										
											2017-09-06 14:51:47 -04:00
										 |  |  |     config_error = ConfigParser.Error | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     def __init__(self, input_fd, bglogger, start_args): | 
					
						
							| 
									
										
										
										
											2017-05-01 13:44:06 -04:00
										 |  |  |         self.bglogger = bglogger | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |         self.start_args = start_args | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.reactor = reactor.Reactor() | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |         gc = gcode.GCodeParser(self, input_fd) | 
					
						
							|  |  |  |         self.objects = collections.OrderedDict({'gcode': gc}) | 
					
						
							| 
									
										
										
										
											2017-08-26 00:13:36 -04:00
										 |  |  |         self.stats_timer = self.reactor.register_timer(self._stats) | 
					
						
							| 
									
										
										
										
											2016-11-28 13:14:56 -05:00
										 |  |  |         self.connect_timer = self.reactor.register_timer( | 
					
						
							| 
									
										
										
										
											2017-08-26 00:13:36 -04:00
										 |  |  |             self._connect, self.reactor.NOW) | 
					
						
							| 
									
										
										
										
											2016-11-30 16:07:17 -05:00
										 |  |  |         self.all_config_options = {} | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |         self.state_message = message_startup | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |         self.is_shutdown = False | 
					
						
							|  |  |  |         self.async_shutdown_msg = "" | 
					
						
							| 
									
										
										
										
											2016-11-30 23:47:40 -05:00
										 |  |  |         self.run_result = None | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |         self.stats_cb = [] | 
					
						
							|  |  |  |         self.state_cb = [] | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     def get_start_args(self): | 
					
						
							|  |  |  |         return self.start_args | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |     def get_reactor(self): | 
					
						
							|  |  |  |         return self.reactor | 
					
						
							|  |  |  |     def get_state_message(self): | 
					
						
							|  |  |  |         return self.state_message | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |     def _set_state(self, msg): | 
					
						
							|  |  |  |         self.state_message = msg | 
					
						
							|  |  |  |         if (msg != message_ready | 
					
						
							|  |  |  |             and self.start_args.get('debuginput') is not None): | 
					
						
							|  |  |  |             self.request_exit('error_exit') | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |     def add_object(self, name, obj): | 
					
						
							|  |  |  |         if obj in self.objects: | 
					
						
							|  |  |  |             raise self.config_error( | 
					
						
							|  |  |  |                 "Printer object '%s' already created" % (name,)) | 
					
						
							|  |  |  |         self.objects[name] = obj | 
					
						
							|  |  |  |     def lookup_object(self, name, default=ConfigWrapper.sentinel): | 
					
						
							|  |  |  |         if name in self.objects: | 
					
						
							|  |  |  |             return self.objects[name] | 
					
						
							|  |  |  |         if default is ConfigWrapper.sentinel: | 
					
						
							|  |  |  |             raise self.config_error("Unknown config object '%s'" % (name,)) | 
					
						
							|  |  |  |         return default | 
					
						
							|  |  |  |     def lookup_module_objects(self, module_name): | 
					
						
							|  |  |  |         prefix = module_name + ' ' | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |         objs = [self.objects[n] for n in self.objects if n.startswith(prefix)] | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |         if module_name in self.objects: | 
					
						
							|  |  |  |             return [self.objects[module_name]] + objs | 
					
						
							|  |  |  |         return objs | 
					
						
							|  |  |  |     def set_rollover_info(self, name, info): | 
					
						
							| 
									
										
										
										
											2018-04-03 12:13:06 -04:00
										 |  |  |         logging.info(info) | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |         if self.bglogger is not None: | 
					
						
							|  |  |  |             self.bglogger.set_rollover_info(name, info) | 
					
						
							| 
									
										
										
										
											2017-08-26 00:13:36 -04:00
										 |  |  |     def _stats(self, eventtime, force_output=False): | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |         stats = [cb(eventtime) for cb in self.stats_cb] | 
					
						
							| 
									
										
										
										
											2018-02-05 13:52:05 -05:00
										 |  |  |         if max([s[0] for s in stats] + [force_output]): | 
					
						
							|  |  |  |             logging.info("Stats %.1f: %s", eventtime, | 
					
						
							|  |  |  |                          ' '.join([s[1] for s in stats])) | 
					
						
							| 
									
										
										
										
											2016-11-22 12:14:25 -05:00
										 |  |  |         return eventtime + 1. | 
					
						
							| 
									
										
										
										
											2018-03-07 14:41:09 -05:00
										 |  |  |     def try_load_module(self, config, section): | 
					
						
							| 
									
										
										
										
											2018-01-19 21:25:58 -05:00
										 |  |  |         if section in self.objects: | 
					
						
							| 
									
										
										
										
											2018-06-14 12:00:55 -04:00
										 |  |  |             return self.objects[section] | 
					
						
							| 
									
										
										
										
											2018-02-03 12:53:11 -05:00
										 |  |  |         module_parts = section.split() | 
					
						
							|  |  |  |         module_name = module_parts[0] | 
					
						
							| 
									
										
										
										
											2018-01-19 21:25:58 -05:00
										 |  |  |         py_name = os.path.join(os.path.dirname(__file__), | 
					
						
							|  |  |  |                                'extras', module_name + '.py') | 
					
						
							| 
									
										
										
										
											2018-06-27 13:01:48 -04:00
										 |  |  |         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): | 
					
						
							| 
									
										
										
										
											2018-06-14 12:00:55 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2018-01-19 21:25:58 -05:00
										 |  |  |         mod = importlib.import_module('extras.' + module_name) | 
					
						
							| 
									
										
										
										
											2018-02-03 12:53:11 -05:00
										 |  |  |         init_func = 'load_config' | 
					
						
							|  |  |  |         if len(module_parts) > 1: | 
					
						
							|  |  |  |             init_func = 'load_config_prefix' | 
					
						
							|  |  |  |         init_func = getattr(mod, init_func, None) | 
					
						
							|  |  |  |         if init_func is not None: | 
					
						
							|  |  |  |             self.objects[section] = init_func(config.getsection(section)) | 
					
						
							| 
									
										
										
										
											2018-06-14 12:00:55 -04:00
										 |  |  |             return self.objects[section] | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |     def _read_config(self): | 
					
						
							|  |  |  |         fileconfig = ConfigParser.RawConfigParser() | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |         config_file = self.start_args['config_file'] | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         res = fileconfig.read(config_file) | 
					
						
							| 
									
										
										
										
											2016-11-30 14:57:18 -05:00
										 |  |  |         if not res: | 
					
						
							| 
									
										
										
										
											2017-09-06 14:51:47 -04:00
										 |  |  |             raise self.config_error("Unable to open config file %s" % ( | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |                 config_file,)) | 
					
						
							| 
									
										
										
										
											2017-05-01 13:44:06 -04:00
										 |  |  |         if self.bglogger is not None: | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |             ConfigLogger(fileconfig, self.bglogger) | 
					
						
							| 
									
										
										
										
											2017-04-29 13:57:02 -04:00
										 |  |  |         # Create printer components | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         config = ConfigWrapper(self, fileconfig, 'printer') | 
					
						
							| 
									
										
										
										
											2018-04-03 17:01:10 -04:00
										 |  |  |         for m in [pins, heater, mcu]: | 
					
						
							| 
									
										
										
										
											2018-07-12 22:26:32 -04:00
										 |  |  |             m.add_printer_objects(config) | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |         for section in fileconfig.sections(): | 
					
						
							| 
									
										
										
										
											2018-03-07 14:41:09 -05:00
										 |  |  |             self.try_load_module(config, section) | 
					
						
							| 
									
										
										
										
											2018-07-12 22:15:45 -04:00
										 |  |  |         for m in [toolhead]: | 
					
						
							| 
									
										
										
										
											2018-07-12 22:26:32 -04:00
										 |  |  |             m.add_printer_objects(config) | 
					
						
							| 
									
										
										
										
											2017-03-12 22:43:05 -04:00
										 |  |  |         # Validate that there are no undefined parameters in the config file | 
					
						
							| 
									
										
										
										
											2017-06-06 12:35:13 -04:00
										 |  |  |         valid_sections = { s: 1 for s, o in self.all_config_options } | 
					
						
							| 
									
										
										
										
											2018-05-25 12:47:51 -04:00
										 |  |  |         for section_name in fileconfig.sections(): | 
					
						
							|  |  |  |             section = section_name.lower() | 
					
						
							| 
									
										
										
										
											2018-01-19 21:25:58 -05:00
										 |  |  |             if section not in valid_sections and section not in self.objects: | 
					
						
							| 
									
										
										
										
											2018-04-20 13:30:58 -04:00
										 |  |  |                 raise self.config_error( | 
					
						
							|  |  |  |                     "Section '%s' is not a valid config section" % (section,)) | 
					
						
							| 
									
										
										
										
											2018-05-25 12:47:51 -04:00
										 |  |  |             for option in fileconfig.options(section_name): | 
					
						
							| 
									
										
										
										
											2016-11-30 16:07:17 -05:00
										 |  |  |                 option = option.lower() | 
					
						
							|  |  |  |                 if (section, option) not in self.all_config_options: | 
					
						
							| 
									
										
										
										
											2017-09-06 14:51:47 -04:00
										 |  |  |                     raise self.config_error( | 
					
						
							| 
									
										
										
										
											2018-04-20 13:30:58 -04:00
										 |  |  |                         "Option '%s' is not valid in section '%s'" % ( | 
					
						
							| 
									
										
										
										
											2016-11-30 16:07:17 -05:00
										 |  |  |                             option, section)) | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |         # 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')] | 
					
						
							| 
									
										
										
										
											2017-08-26 00:13:36 -04:00
										 |  |  |     def _connect(self, eventtime): | 
					
						
							| 
									
										
										
										
											2017-09-19 17:12:04 -04:00
										 |  |  |         self.reactor.unregister_timer(self.connect_timer) | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2018-02-03 12:17:42 -05:00
										 |  |  |             self._read_config() | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |             for cb in self.state_cb: | 
					
						
							|  |  |  |                 if self.state_message is not message_startup: | 
					
						
							|  |  |  |                     return self.reactor.NEVER | 
					
						
							|  |  |  |                 cb('connect') | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |             self._set_state(message_ready) | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |             for cb in self.state_cb: | 
					
						
							|  |  |  |                 if self.state_message is not message_ready: | 
					
						
							|  |  |  |                     return self.reactor.NEVER | 
					
						
							|  |  |  |                 cb('ready') | 
					
						
							| 
									
										
										
										
											2017-09-19 17:12:04 -04:00
										 |  |  |             if self.start_args.get('debugoutput') is None: | 
					
						
							|  |  |  |                 self.reactor.update_timer(self.stats_timer, self.reactor.NOW) | 
					
						
							| 
									
										
										
										
											2017-09-06 14:51:47 -04:00
										 |  |  |         except (self.config_error, pins.error) as e: | 
					
						
							| 
									
										
										
										
											2016-11-30 14:57:18 -05:00
										 |  |  |             logging.exception("Config error") | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |             self._set_state("%s%s" % (str(e), message_restart)) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except msgproto.error as e: | 
					
						
							| 
									
										
										
										
											2017-01-09 23:50:13 -05:00
										 |  |  |             logging.exception("Protocol error") | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |             self._set_state("%s%s" % (str(e), message_protocol_error)) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except mcu.error as e: | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |             logging.exception("MCU error during connect") | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |             self._set_state("%s%s" % (str(e), message_mcu_connect_error)) | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |         except: | 
					
						
							|  |  |  |             logging.exception("Unhandled exception during connect") | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |             self._set_state("Internal error during connect.%s" % ( | 
					
						
							|  |  |  |                 message_restart,)) | 
					
						
							| 
									
										
										
										
											2016-11-28 13:14:56 -05:00
										 |  |  |         return self.reactor.NEVER | 
					
						
							|  |  |  |     def run(self): | 
					
						
							| 
									
										
										
										
											2017-02-06 13:31:34 -05:00
										 |  |  |         systime = time.time() | 
					
						
							|  |  |  |         monotime = self.reactor.monotonic() | 
					
						
							| 
									
										
										
										
											2017-09-27 11:43:14 -04:00
										 |  |  |         logging.info("Start printer at %s (%.1f %.1f)", | 
					
						
							|  |  |  |                      time.asctime(time.localtime(systime)), systime, monotime) | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |         while 1: | 
					
						
							|  |  |  |             # Enter main reactor loop | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 self.reactor.run() | 
					
						
							|  |  |  |             except: | 
					
						
							|  |  |  |                 logging.exception("Unhandled exception during run") | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |                 return "error_exit" | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |             # Check restart flags | 
					
						
							|  |  |  |             run_result = self.run_result | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 if run_result == 'shutdown': | 
					
						
							| 
									
										
										
										
											2017-12-03 18:13:47 -05:00
										 |  |  |                     self.invoke_shutdown(self.async_shutdown_msg) | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 self._stats(self.reactor.monotonic(), force_output=True) | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |                 if run_result == 'firmware_restart': | 
					
						
							|  |  |  |                     for m in self.lookup_module_objects('mcu'): | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |                         m.microcontroller_restart() | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |                 for cb in self.state_cb: | 
					
						
							|  |  |  |                     cb('disconnect') | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |             except: | 
					
						
							|  |  |  |                 logging.exception("Unhandled exception during post run") | 
					
						
							|  |  |  |             return run_result | 
					
						
							| 
									
										
										
										
											2017-12-03 18:13:47 -05:00
										 |  |  |     def invoke_shutdown(self, msg): | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |         if self.is_shutdown: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         self.is_shutdown = True | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |         self._set_state("%s%s" % (msg, message_shutdown)) | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |         for cb in self.state_cb: | 
					
						
							|  |  |  |             cb('shutdown') | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |     def invoke_async_shutdown(self, msg): | 
					
						
							|  |  |  |         self.async_shutdown_msg = msg | 
					
						
							|  |  |  |         self.request_exit("shutdown") | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |     def request_exit(self, result): | 
					
						
							| 
									
										
										
										
											2017-03-08 22:26:10 -05:00
										 |  |  |         self.run_result = result | 
					
						
							| 
									
										
										
										
											2016-11-30 23:47:40 -05:00
										 |  |  |         self.reactor.end() | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Startup | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-14 11:46:35 -04:00
										 |  |  | def arg_dictionary(option, opt_str, value, parser): | 
					
						
							|  |  |  |     key, fname = "dictionary", value | 
					
						
							|  |  |  |     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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | def main(): | 
					
						
							|  |  |  |     usage = "%prog [options] <config file>" | 
					
						
							|  |  |  |     opts = optparse.OptionParser(usage) | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     opts.add_option("-i", "--debuginput", dest="debuginput", | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |                     help="read commands from file instead of from tty port") | 
					
						
							| 
									
										
										
										
											2016-11-20 20:40:31 -05:00
										 |  |  |     opts.add_option("-I", "--input-tty", dest="inputtty", default='/tmp/printer', | 
					
						
							|  |  |  |                     help="input tty name (default is /tmp/printer)") | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     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") | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     opts.add_option("-o", "--debugoutput", dest="debugoutput", | 
					
						
							|  |  |  |                     help="write output to file instead of to serial port") | 
					
						
							| 
									
										
										
										
											2017-08-14 11:46:35 -04:00
										 |  |  |     opts.add_option("-d", "--dictionary", dest="dictionary", type="string", | 
					
						
							|  |  |  |                     action="callback", callback=arg_dictionary, | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |                     help="file to read for mcu protocol dictionary") | 
					
						
							|  |  |  |     options, args = opts.parse_args() | 
					
						
							|  |  |  |     if len(args) != 1: | 
					
						
							|  |  |  |         opts.error("Incorrect number of arguments") | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     start_args = {'config_file': args[0], 'start_reason': 'startup'} | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     input_fd = bglogger = None | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     debuglevel = logging.INFO | 
					
						
							|  |  |  |     if options.verbose: | 
					
						
							|  |  |  |         debuglevel = logging.DEBUG | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     if options.debuginput: | 
					
						
							|  |  |  |         start_args['debuginput'] = options.debuginput | 
					
						
							|  |  |  |         debuginput = open(options.debuginput, 'rb') | 
					
						
							| 
									
										
										
										
											2016-11-20 20:40:31 -05:00
										 |  |  |         input_fd = debuginput.fileno() | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         input_fd = util.create_pty(options.inputtty) | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     if options.debugoutput: | 
					
						
							|  |  |  |         start_args['debugoutput'] = options.debugoutput | 
					
						
							| 
									
										
										
										
											2017-08-14 11:46:35 -04:00
										 |  |  |         start_args.update(options.dictionary) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     if options.logfile: | 
					
						
							| 
									
										
										
										
											2016-11-11 20:22:39 -05:00
										 |  |  |         bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     else: | 
					
						
							|  |  |  |         logging.basicConfig(level=debuglevel) | 
					
						
							|  |  |  |     logging.info("Starting Klippy...") | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     start_args['software_version'] = util.get_git_version() | 
					
						
							| 
									
										
										
										
											2017-05-01 13:44:06 -04:00
										 |  |  |     if bglogger is not None: | 
					
						
							| 
									
										
										
										
											2018-04-03 12:13:06 -04:00
										 |  |  |         versions = "\n".join([ | 
					
						
							|  |  |  |             "Args: %s" % (sys.argv,), | 
					
						
							|  |  |  |             "Git version: %s" % (repr(start_args['software_version']),), | 
					
						
							|  |  |  |             "CPU: %s" % (util.get_cpu_info(),), | 
					
						
							|  |  |  |             "Python: %s" % (repr(sys.version),)]) | 
					
						
							|  |  |  |         logging.info(versions) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     # Start Printer() class | 
					
						
							| 
									
										
										
										
											2016-11-30 23:47:40 -05:00
										 |  |  |     while 1: | 
					
						
							| 
									
										
										
										
											2018-04-03 12:13:06 -04:00
										 |  |  |         if bglogger is not None: | 
					
						
							|  |  |  |             bglogger.clear_rollover_info() | 
					
						
							|  |  |  |             bglogger.set_rollover_info('versions', versions) | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |         printer = Printer(input_fd, bglogger, start_args) | 
					
						
							| 
									
										
										
										
											2016-11-30 23:47:40 -05:00
										 |  |  |         res = printer.run() | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |         if res in ['exit', 'error_exit']: | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |             break | 
					
						
							|  |  |  |         time.sleep(1.) | 
					
						
							|  |  |  |         logging.info("Restarting printer") | 
					
						
							|  |  |  |         start_args['start_reason'] = res | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-11 20:22:39 -05:00
										 |  |  |     if bglogger is not None: | 
					
						
							|  |  |  |         bglogger.stop() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |     if res == 'error_exit': | 
					
						
							|  |  |  |         sys.exit(-1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     main() |