| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | # Parse gcode commands | 
					
						
							|  |  |  | # | 
					
						
							| 
									
										
										
										
											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. | 
					
						
							| 
									
										
										
										
											2017-02-06 13:31:34 -05:00
										 |  |  | import os, re, logging, collections | 
					
						
							| 
									
										
										
										
											2018-07-12 22:15:45 -04:00
										 |  |  | import homing, kinematics.extruder | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  | class error(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Parse and handle G-Code commands | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  | class GCodeParser: | 
					
						
							| 
									
										
										
										
											2017-12-03 20:22:44 -05:00
										 |  |  |     error = error | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     RETRY_TIME = 0.100 | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |     def __init__(self, printer, fd): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.printer = printer | 
					
						
							|  |  |  |         self.fd = fd | 
					
						
							|  |  |  |         # Input handling | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |         self.reactor = printer.get_reactor() | 
					
						
							| 
									
										
										
										
											2016-11-16 10:56:10 -05:00
										 |  |  |         self.is_processing_data = False | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |         self.is_fileinput = not not printer.get_start_args().get("debuginput") | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.fd_handle = None | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |         if not self.is_fileinput: | 
					
						
							| 
									
										
										
										
											2016-11-22 19:38:51 -05:00
										 |  |  |             self.fd_handle = self.reactor.register_fd(self.fd, self.process_data) | 
					
						
							| 
									
										
										
										
											2017-05-19 21:05:46 -04:00
										 |  |  |         self.partial_input = "" | 
					
						
							| 
									
										
										
										
											2018-02-02 09:38:04 -05:00
										 |  |  |         self.pending_commands = [] | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.bytes_read = 0 | 
					
						
							| 
									
										
										
										
											2016-09-24 18:10:24 -04:00
										 |  |  |         self.input_log = collections.deque([], 50) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         # Command handling | 
					
						
							| 
									
										
										
										
											2016-11-22 19:38:51 -05:00
										 |  |  |         self.is_printer_ready = False | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |         self.base_gcode_handlers = self.gcode_handlers = {} | 
					
						
							|  |  |  |         self.ready_gcode_handlers = {} | 
					
						
							| 
									
										
										
										
											2018-05-20 12:33:43 -04:00
										 |  |  |         self.mux_commands = {} | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |         self.gcode_help = {} | 
					
						
							|  |  |  |         for cmd in self.all_handlers: | 
					
						
							|  |  |  |             func = getattr(self, 'cmd_' + cmd) | 
					
						
							|  |  |  |             wnr = getattr(self, 'cmd_' + cmd + '_when_not_ready', False) | 
					
						
							|  |  |  |             desc = getattr(self, 'cmd_' + cmd + '_help', None) | 
					
						
							|  |  |  |             self.register_command(cmd, func, wnr, desc) | 
					
						
							|  |  |  |             for a in getattr(self, 'cmd_' + cmd + '_aliases', []): | 
					
						
							|  |  |  |                 self.register_command(a, func, wnr) | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |         # G-Code coordinate manipulation | 
					
						
							|  |  |  |         self.absolutecoord = self.absoluteextrude = True | 
					
						
							|  |  |  |         self.base_position = [0.0, 0.0, 0.0, 0.0] | 
					
						
							|  |  |  |         self.last_position = [0.0, 0.0, 0.0, 0.0] | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |         self.homing_position = [0.0, 0.0, 0.0, 0.0] | 
					
						
							| 
									
										
										
										
											2017-12-21 20:53:44 -05:00
										 |  |  |         self.speed_factor = 1. / 60. | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |         self.extrude_factor = 1. | 
					
						
							| 
									
										
										
										
											2018-01-20 23:47:36 -05:00
										 |  |  |         self.move_transform = self.move_with_transform = None | 
					
						
							|  |  |  |         self.position_with_transform = (lambda: [0., 0., 0., 0.]) | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |         # G-Code state | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.need_ack = False | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         self.toolhead = self.fan = self.extruder = None | 
					
						
							|  |  |  |         self.heaters = [] | 
					
						
							| 
									
										
										
										
											2016-12-09 17:28:09 -05:00
										 |  |  |         self.speed = 25.0 | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3} | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |     def register_command(self, cmd, func, when_not_ready=False, desc=None): | 
					
						
							| 
									
										
										
										
											2018-01-17 11:26:09 -05:00
										 |  |  |         if func is None: | 
					
						
							|  |  |  |             if cmd in self.ready_gcode_handlers: | 
					
						
							|  |  |  |                 del self.ready_gcode_handlers[cmd] | 
					
						
							|  |  |  |             if cmd in self.base_gcode_handlers: | 
					
						
							|  |  |  |                 del self.base_gcode_handlers[cmd] | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2018-05-20 13:04:52 -04:00
										 |  |  |         if cmd in self.ready_gcode_handlers: | 
					
						
							|  |  |  |             raise error("gcode command %s already registered" % (cmd,)) | 
					
						
							| 
									
										
										
										
											2017-12-03 20:22:44 -05:00
										 |  |  |         if not (len(cmd) >= 2 and not cmd[0].isupper() and cmd[1].isdigit()): | 
					
						
							|  |  |  |             origfunc = func | 
					
						
							|  |  |  |             func = lambda params: origfunc(self.get_extended_params(params)) | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |         self.ready_gcode_handlers[cmd] = func | 
					
						
							|  |  |  |         if when_not_ready: | 
					
						
							|  |  |  |             self.base_gcode_handlers[cmd] = func | 
					
						
							|  |  |  |         if desc is not None: | 
					
						
							|  |  |  |             self.gcode_help[cmd] = desc | 
					
						
							| 
									
										
										
										
											2018-05-20 12:33:43 -04:00
										 |  |  |     def register_mux_command(self, cmd, key, value, func, desc=None): | 
					
						
							|  |  |  |         prev = self.mux_commands.get(cmd) | 
					
						
							|  |  |  |         if prev is None: | 
					
						
							|  |  |  |             self.register_command(cmd, self.cmd_mux, desc=desc) | 
					
						
							|  |  |  |             self.mux_commands[cmd] = prev = (key, {}) | 
					
						
							|  |  |  |         prev_key, prev_values = prev | 
					
						
							|  |  |  |         if prev_key != key: | 
					
						
							|  |  |  |             raise error("mux command %s %s %s may have only one key (%s)" % ( | 
					
						
							|  |  |  |                 cmd, key, value, prev_key)) | 
					
						
							|  |  |  |         if value in prev_values: | 
					
						
							|  |  |  |             raise error("mux command %s %s %s already registered (%s)" % ( | 
					
						
							| 
									
										
										
										
											2018-06-01 22:44:52 +03:00
										 |  |  |                 cmd, key, value, prev_values)) | 
					
						
							| 
									
										
										
										
											2018-05-20 12:33:43 -04:00
										 |  |  |         prev_values[value] = func | 
					
						
							| 
									
										
										
										
											2018-01-20 23:47:36 -05:00
										 |  |  |     def set_move_transform(self, transform): | 
					
						
							|  |  |  |         if self.move_transform is not None: | 
					
						
							|  |  |  |             raise self.printer.config_error( | 
					
						
							|  |  |  |                 "G-Code move transform already specified") | 
					
						
							|  |  |  |         self.move_transform = transform | 
					
						
							|  |  |  |         self.move_with_transform = transform.move | 
					
						
							|  |  |  |         self.position_with_transform = transform.get_position | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def stats(self, eventtime): | 
					
						
							| 
									
										
										
										
											2018-02-05 13:52:05 -05:00
										 |  |  |         return False, "gcodein=%d" % (self.bytes_read,) | 
					
						
							| 
									
										
										
										
											2018-02-20 20:50:06 -05:00
										 |  |  |     def get_status(self, eventtime): | 
					
						
							|  |  |  |         busy = self.is_processing_data | 
					
						
							|  |  |  |         return {'speed_factor': self.speed_factor * 60., 'busy': busy} | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |     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: | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |                 self.printer.request_exit('error_exit') | 
					
						
							| 
									
										
										
										
											2018-01-19 22:49:27 -05:00
										 |  |  |             return | 
					
						
							|  |  |  |         if state != 'ready': | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |         self.is_printer_ready = True | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |         self.gcode_handlers = self.ready_gcode_handlers | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |         # Lookup printer components | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |         self.toolhead = self.printer.lookup_object('toolhead') | 
					
						
							| 
									
										
										
										
											2018-01-20 23:47:36 -05:00
										 |  |  |         if self.move_transform is None: | 
					
						
							|  |  |  |             self.move_with_transform = self.toolhead.move | 
					
						
							|  |  |  |             self.position_with_transform = self.toolhead.get_position | 
					
						
							| 
									
										
										
										
											2018-07-12 22:15:45 -04:00
										 |  |  |         extruders = kinematics.extruder.get_printer_extruders(self.printer) | 
					
						
							| 
									
										
										
										
											2017-04-29 14:56:39 -04:00
										 |  |  |         if extruders: | 
					
						
							|  |  |  |             self.extruder = extruders[0] | 
					
						
							|  |  |  |             self.toolhead.set_extruder(self.extruder) | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         self.heaters = [ e.get_heater() for e in extruders ] | 
					
						
							| 
									
										
										
										
											2018-01-19 22:22:17 -05:00
										 |  |  |         self.heaters.append(self.printer.lookup_object('heater_bed', None)) | 
					
						
							|  |  |  |         self.fan = self.printer.lookup_object('fan', None) | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |         if self.is_fileinput and self.fd_handle is None: | 
					
						
							| 
									
										
										
										
											2016-11-22 19:38:51 -05:00
										 |  |  |             self.fd_handle = self.reactor.register_fd(self.fd, self.process_data) | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |     def reset_last_position(self): | 
					
						
							| 
									
										
										
										
											2018-01-20 23:47:36 -05:00
										 |  |  |         self.last_position = self.position_with_transform() | 
					
						
							| 
									
										
										
										
											2016-11-30 19:05:25 -05:00
										 |  |  |     def dump_debug(self): | 
					
						
							| 
									
										
										
										
											2017-09-27 11:54:53 -04:00
										 |  |  |         out = [] | 
					
						
							|  |  |  |         out.append("Dumping gcode input %d blocks" % ( | 
					
						
							| 
									
										
										
										
											2016-11-30 19:05:25 -05:00
										 |  |  |             len(self.input_log),)) | 
					
						
							|  |  |  |         for eventtime, data in self.input_log: | 
					
						
							| 
									
										
										
										
											2017-09-27 11:54:53 -04:00
										 |  |  |             out.append("Read %f: %s" % (eventtime, repr(data))) | 
					
						
							| 
									
										
										
										
											2017-12-21 22:23:07 -05:00
										 |  |  |         out.append( | 
					
						
							|  |  |  |             "gcode state: absolutecoord=%s absoluteextrude=%s" | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |             " base_position=%s last_position=%s homing_position=%s" | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |             " speed_factor=%s extrude_factor=%s speed=%s" % ( | 
					
						
							| 
									
										
										
										
											2017-12-21 22:23:07 -05:00
										 |  |  |                 self.absolutecoord, self.absoluteextrude, | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |                 self.base_position, self.last_position, self.homing_position, | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |                 self.speed_factor, self.extrude_factor, self.speed)) | 
					
						
							| 
									
										
										
										
											2017-09-27 11:54:53 -04:00
										 |  |  |         logging.info("\n".join(out)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     # Parse input into commands | 
					
						
							| 
									
										
										
										
											2017-10-10 11:12:15 -04:00
										 |  |  |     args_r = re.compile('([A-Z_]+|[A-Z*/])') | 
					
						
							| 
									
										
										
										
											2017-05-19 21:05:46 -04:00
										 |  |  |     def process_commands(self, commands, need_ack=True): | 
					
						
							|  |  |  |         for line in commands: | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             # Ignore comments and leading/trailing spaces | 
					
						
							|  |  |  |             line = origline = line.strip() | 
					
						
							|  |  |  |             cpos = line.find(';') | 
					
						
							|  |  |  |             if cpos >= 0: | 
					
						
							|  |  |  |                 line = line[:cpos] | 
					
						
							|  |  |  |             # Break command into parts | 
					
						
							| 
									
										
										
										
											2017-06-06 15:04:01 -04:00
										 |  |  |             parts = self.args_r.split(line.upper())[1:] | 
					
						
							|  |  |  |             params = { parts[i]: parts[i+1].strip() | 
					
						
							| 
									
										
										
										
											2017-06-06 12:35:13 -04:00
										 |  |  |                        for i in range(0, len(parts), 2) } | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             params['#original'] = origline | 
					
						
							| 
									
										
										
										
											2017-06-06 15:04:01 -04:00
										 |  |  |             if parts and parts[0] == 'N': | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |                 # Skip line number at start of command | 
					
						
							|  |  |  |                 del parts[:2] | 
					
						
							|  |  |  |             if not parts: | 
					
						
							| 
									
										
										
										
											2018-02-01 10:55:45 -05:00
										 |  |  |                 # Treat empty line as empty command | 
					
						
							|  |  |  |                 parts = ['', ''] | 
					
						
							| 
									
										
										
										
											2017-06-06 15:04:01 -04:00
										 |  |  |             params['#command'] = cmd = parts[0] + parts[1].strip() | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             # Invoke handler for command | 
					
						
							| 
									
										
										
										
											2017-05-19 21:05:46 -04:00
										 |  |  |             self.need_ack = need_ack | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             handler = self.gcode_handlers.get(cmd, self.cmd_default) | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 handler(params) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |             except error as e: | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |                 self.respond_error(str(e)) | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |                 self.reset_last_position() | 
					
						
							| 
									
										
										
										
											2018-02-01 12:13:48 -05:00
										 |  |  |                 if not need_ack: | 
					
						
							|  |  |  |                     raise | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             except: | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |                 msg = 'Internal error on command:"%s"' % (cmd,) | 
					
						
							|  |  |  |                 logging.exception(msg) | 
					
						
							|  |  |  |                 self.printer.invoke_shutdown(msg) | 
					
						
							|  |  |  |                 self.respond_error(msg) | 
					
						
							| 
									
										
										
										
											2018-02-01 12:13:48 -05:00
										 |  |  |                 if not need_ack: | 
					
						
							|  |  |  |                     raise | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             self.ack() | 
					
						
							| 
									
										
										
										
											2018-02-02 09:38:04 -05:00
										 |  |  |     m112_r = re.compile('^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)') | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def process_data(self, eventtime): | 
					
						
							| 
									
										
										
										
											2018-02-02 09:38:04 -05:00
										 |  |  |         # Read input, separate by newline, and add to pending_commands | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         data = os.read(self.fd, 4096) | 
					
						
							| 
									
										
										
										
											2016-09-24 18:10:24 -04:00
										 |  |  |         self.input_log.append((eventtime, data)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         self.bytes_read += len(data) | 
					
						
							|  |  |  |         lines = data.split('\n') | 
					
						
							| 
									
										
										
										
											2017-05-19 21:05:46 -04:00
										 |  |  |         lines[0] = self.partial_input + lines[0] | 
					
						
							|  |  |  |         self.partial_input = lines.pop() | 
					
						
							| 
									
										
										
										
											2018-02-02 09:38:04 -05:00
										 |  |  |         pending_commands = self.pending_commands | 
					
						
							|  |  |  |         pending_commands.extend(lines) | 
					
						
							| 
									
										
										
										
											2018-02-11 12:49:33 -05:00
										 |  |  |         # Special handling for debug file input EOF | 
					
						
							|  |  |  |         if not data and self.is_fileinput: | 
					
						
							|  |  |  |             if not self.is_processing_data: | 
					
						
							| 
									
										
										
										
											2018-02-11 13:52:20 -05:00
										 |  |  |                 self.request_restart('exit') | 
					
						
							| 
									
										
										
										
											2018-02-11 12:49:33 -05:00
										 |  |  |             pending_commands.append("") | 
					
						
							|  |  |  |         # Handle case where multiple commands pending | 
					
						
							|  |  |  |         if self.is_processing_data or len(pending_commands) > 1: | 
					
						
							|  |  |  |             if len(pending_commands) < 20: | 
					
						
							|  |  |  |                 # Check for M112 out-of-order | 
					
						
							|  |  |  |                 for line in lines: | 
					
						
							|  |  |  |                     if self.m112_r.match(line) is not None: | 
					
						
							|  |  |  |                         self.cmd_M112({}) | 
					
						
							|  |  |  |             if self.is_processing_data: | 
					
						
							|  |  |  |                 if len(pending_commands) >= 20: | 
					
						
							|  |  |  |                     # Stop reading input | 
					
						
							|  |  |  |                     self.reactor.unregister_fd(self.fd_handle) | 
					
						
							|  |  |  |                     self.fd_handle = None | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2018-02-02 09:38:04 -05:00
										 |  |  |         # Process commands | 
					
						
							| 
									
										
										
										
											2016-11-16 10:56:10 -05:00
										 |  |  |         self.is_processing_data = True | 
					
						
							| 
									
										
										
										
											2018-02-11 12:49:33 -05:00
										 |  |  |         self.pending_commands = [] | 
					
						
							|  |  |  |         self.process_commands(pending_commands) | 
					
						
							|  |  |  |         if self.pending_commands: | 
					
						
							|  |  |  |             self.process_pending() | 
					
						
							|  |  |  |         self.is_processing_data = False | 
					
						
							|  |  |  |     def process_pending(self): | 
					
						
							|  |  |  |         pending_commands = self.pending_commands | 
					
						
							| 
									
										
										
										
											2018-02-02 09:38:04 -05:00
										 |  |  |         while pending_commands: | 
					
						
							|  |  |  |             self.pending_commands = [] | 
					
						
							|  |  |  |             self.process_commands(pending_commands) | 
					
						
							|  |  |  |             pending_commands = self.pending_commands | 
					
						
							|  |  |  |         if self.fd_handle is None: | 
					
						
							|  |  |  |             self.fd_handle = self.reactor.register_fd(self.fd, self.process_data) | 
					
						
							| 
									
										
										
										
											2017-10-10 11:12:15 -04:00
										 |  |  |     def process_batch(self, command): | 
					
						
							|  |  |  |         if self.is_processing_data: | 
					
						
							|  |  |  |             return False | 
					
						
							|  |  |  |         self.is_processing_data = True | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             self.process_commands([command], need_ack=False) | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             if self.pending_commands: | 
					
						
							|  |  |  |                 self.process_pending() | 
					
						
							|  |  |  |             self.is_processing_data = False | 
					
						
							|  |  |  |         return True | 
					
						
							| 
									
										
										
										
											2018-06-30 14:08:02 -04:00
										 |  |  |     def run_script_from_command(self, script): | 
					
						
							| 
									
										
										
										
											2018-02-02 18:28:48 -05:00
										 |  |  |         prev_need_ack = self.need_ack | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             self.process_commands(script.split('\n'), need_ack=False) | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             self.need_ack = prev_need_ack | 
					
						
							| 
									
										
										
										
											2018-06-30 14:10:59 -04:00
										 |  |  |     def run_script(self, script): | 
					
						
							|  |  |  |         curtime = self.reactor.monotonic() | 
					
						
							|  |  |  |         for line in script.split('\n'): | 
					
						
							|  |  |  |             while 1: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     res = self.process_batch(line) | 
					
						
							|  |  |  |                 except: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 if res: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 curtime = self.reactor.pause(curtime + 0.100) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     # Response handling | 
					
						
							|  |  |  |     def ack(self, msg=None): | 
					
						
							| 
									
										
										
										
											2016-11-20 20:40:31 -05:00
										 |  |  |         if not self.need_ack or self.is_fileinput: | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             return | 
					
						
							|  |  |  |         if msg: | 
					
						
							|  |  |  |             os.write(self.fd, "ok %s\n" % (msg,)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             os.write(self.fd, "ok\n") | 
					
						
							|  |  |  |         self.need_ack = False | 
					
						
							|  |  |  |     def respond(self, msg): | 
					
						
							| 
									
										
										
										
											2016-11-20 20:40:31 -05:00
										 |  |  |         if self.is_fileinput: | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             return | 
					
						
							|  |  |  |         os.write(self.fd, msg+"\n") | 
					
						
							| 
									
										
										
										
											2016-12-21 11:33:03 -05:00
										 |  |  |     def respond_info(self, msg): | 
					
						
							| 
									
										
										
										
											2017-07-17 11:24:15 -04:00
										 |  |  |         logging.debug(msg) | 
					
						
							| 
									
										
										
										
											2016-12-21 11:33:03 -05:00
										 |  |  |         lines = [l.strip() for l in msg.strip().split('\n')] | 
					
						
							|  |  |  |         self.respond("// " + "\n// ".join(lines)) | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |     def respond_error(self, msg): | 
					
						
							| 
									
										
										
										
											2017-07-17 11:24:15 -04:00
										 |  |  |         logging.warning(msg) | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |         lines = msg.strip().split('\n') | 
					
						
							| 
									
										
										
										
											2016-12-21 11:33:03 -05:00
										 |  |  |         if len(lines) > 1: | 
					
						
							| 
									
										
										
										
											2018-03-12 22:37:14 -04:00
										 |  |  |             self.respond_info("\n".join(lines)) | 
					
						
							|  |  |  |         self.respond('!! %s' % (lines[0].strip(),)) | 
					
						
							| 
									
										
										
										
											2018-06-16 15:15:17 -04:00
										 |  |  |         if self.is_fileinput: | 
					
						
							|  |  |  |             self.printer.request_exit('error_exit') | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |     # Parameter parsing helpers | 
					
						
							| 
									
										
										
										
											2017-12-03 20:16:20 -05:00
										 |  |  |     class sentinel: pass | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |     def get_str(self, name, params, default=sentinel, parser=str, | 
					
						
							|  |  |  |                 minval=None, maxval=None, above=None, below=None): | 
					
						
							|  |  |  |         if name not in params: | 
					
						
							|  |  |  |             if default is self.sentinel: | 
					
						
							|  |  |  |                 raise error("Error on '%s': missing %s" % ( | 
					
						
							|  |  |  |                     params['#original'], name)) | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |             return default | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |         try: | 
					
						
							|  |  |  |             value = parser(params[name]) | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             raise error("Error on '%s': unable to parse %s" % ( | 
					
						
							|  |  |  |                 params['#original'], params[name])) | 
					
						
							|  |  |  |         if minval is not None and value < minval: | 
					
						
							|  |  |  |             raise self.error("Error on '%s': %s must have minimum of %s" % ( | 
					
						
							|  |  |  |                 params['#original'], name, minval)) | 
					
						
							|  |  |  |         if maxval is not None and value > maxval: | 
					
						
							|  |  |  |             raise self.error("Error on '%s': %s must have maximum of %s" % ( | 
					
						
							|  |  |  |                 params['#original'], name, maxval)) | 
					
						
							|  |  |  |         if above is not None and value <= above: | 
					
						
							|  |  |  |             raise self.error("Error on '%s': %s must be above %s" % ( | 
					
						
							|  |  |  |                 params['#original'], name, above)) | 
					
						
							|  |  |  |         if below is not None and value >= below: | 
					
						
							|  |  |  |             raise self.error("Error on '%s': %s must be below %s" % ( | 
					
						
							|  |  |  |                 params['#original'], name, below)) | 
					
						
							|  |  |  |         return value | 
					
						
							|  |  |  |     def get_int(self, name, params, default=sentinel, minval=None, maxval=None): | 
					
						
							|  |  |  |         return self.get_str(name, params, default, parser=int, | 
					
						
							|  |  |  |                             minval=minval, maxval=maxval) | 
					
						
							|  |  |  |     def get_float(self, name, params, default=sentinel, | 
					
						
							|  |  |  |                   minval=None, maxval=None, above=None, below=None): | 
					
						
							|  |  |  |         return self.get_str(name, params, default, parser=float, minval=minval, | 
					
						
							|  |  |  |                             maxval=maxval, above=above, below=below) | 
					
						
							| 
									
										
										
										
											2017-06-06 15:04:01 -04:00
										 |  |  |     extended_r = re.compile( | 
					
						
							|  |  |  |         r'^\s*(?:N[0-9]+\s*)?' | 
					
						
							|  |  |  |         r'(?P<cmd>[a-zA-Z_][a-zA-Z_]+)(?:\s+|$)' | 
					
						
							|  |  |  |         r'(?P<args>[^#*;]*?)' | 
					
						
							|  |  |  |         r'\s*(?:[#*;].*)?$') | 
					
						
							|  |  |  |     def get_extended_params(self, params): | 
					
						
							|  |  |  |         m = self.extended_r.match(params['#original']) | 
					
						
							|  |  |  |         if m is None: | 
					
						
							|  |  |  |             # Not an "extended" command | 
					
						
							|  |  |  |             return params | 
					
						
							|  |  |  |         eargs = m.group('args') | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             eparams = [earg.split('=', 1) for earg in eargs.split()] | 
					
						
							| 
									
										
										
										
											2017-10-02 21:48:45 -04:00
										 |  |  |             eparams = { k.upper(): v for k, v in eparams } | 
					
						
							|  |  |  |             eparams.update({k: params[k] for k in params if k.startswith('#')}) | 
					
						
							|  |  |  |             return eparams | 
					
						
							| 
									
										
										
										
											2017-06-06 15:04:01 -04:00
										 |  |  |         except ValueError as e: | 
					
						
							|  |  |  |             raise error("Malformed command '%s'" % (params['#original'],)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     # Temperature wrappers | 
					
						
							| 
									
										
										
										
											2017-10-11 22:16:02 -04:00
										 |  |  |     def get_temp(self, eventtime): | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         # Tn:XXX /YYY B:XXX /YYY | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         out = [] | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         for i, heater in enumerate(self.heaters): | 
					
						
							|  |  |  |             if heater is not None: | 
					
						
							| 
									
										
										
										
											2017-10-11 22:16:02 -04:00
										 |  |  |                 cur, target = heater.get_temp(eventtime) | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |                 name = "B" | 
					
						
							|  |  |  |                 if i < len(self.heaters) - 1: | 
					
						
							|  |  |  |                     name = "T%d" % (i,) | 
					
						
							|  |  |  |                 out.append("%s:%.1f /%.1f" % (name, cur, target)) | 
					
						
							| 
									
										
										
										
											2017-10-11 22:16:02 -04:00
										 |  |  |         if not out: | 
					
						
							|  |  |  |             return "T:0" | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         return " ".join(out) | 
					
						
							|  |  |  |     def bg_temp(self, heater): | 
					
						
							| 
									
										
										
										
											2016-11-20 20:40:31 -05:00
										 |  |  |         if self.is_fileinput: | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2017-02-06 13:31:34 -05:00
										 |  |  |         eventtime = self.reactor.monotonic() | 
					
						
							| 
									
										
										
										
											2016-11-17 12:37:57 -05:00
										 |  |  |         while self.is_printer_ready and heater.check_busy(eventtime): | 
					
						
							| 
									
										
										
										
											2017-02-06 14:07:55 -05:00
										 |  |  |             print_time = self.toolhead.get_last_move_time() | 
					
						
							| 
									
										
										
										
											2017-10-11 22:16:02 -04:00
										 |  |  |             self.respond(self.get_temp(eventtime)) | 
					
						
							| 
									
										
										
										
											2016-11-17 12:37:57 -05:00
										 |  |  |             eventtime = self.reactor.pause(eventtime + 1.) | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |     def set_temp(self, params, is_bed=False, wait=False): | 
					
						
							| 
									
										
										
										
											2017-05-01 14:58:07 -04:00
										 |  |  |         temp = self.get_float('S', params, 0.) | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         heater = None | 
					
						
							|  |  |  |         if is_bed: | 
					
						
							|  |  |  |             heater = self.heaters[-1] | 
					
						
							|  |  |  |         elif 'T' in params: | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |             index = self.get_int( | 
					
						
							|  |  |  |                 'T', params, minval=0, maxval=len(self.heaters)-2) | 
					
						
							|  |  |  |             heater = self.heaters[index] | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         elif self.extruder is not None: | 
					
						
							|  |  |  |             heater = self.extruder.get_heater() | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |         if heater is None: | 
					
						
							| 
									
										
										
										
											2017-05-01 14:58:07 -04:00
										 |  |  |             if temp > 0.: | 
					
						
							|  |  |  |                 self.respond_error("Heater not configured") | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2016-07-07 14:23:48 -04:00
										 |  |  |         print_time = self.toolhead.get_last_move_time() | 
					
						
							| 
									
										
										
										
											2017-02-21 10:48:42 -05:00
										 |  |  |         try: | 
					
						
							|  |  |  |             heater.set_temp(print_time, temp) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except heater.error as e: | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |             raise error(str(e)) | 
					
						
							| 
									
										
										
										
											2018-01-03 10:33:10 -05:00
										 |  |  |         if wait and temp: | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             self.bg_temp(heater) | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |     def set_fan_speed(self, speed): | 
					
						
							|  |  |  |         if self.fan is None: | 
					
						
							| 
									
										
										
										
											2017-10-11 14:28:19 -04:00
										 |  |  |             if speed and not self.is_fileinput: | 
					
						
							| 
									
										
										
										
											2017-05-01 14:58:07 -04:00
										 |  |  |                 self.respond_info("Fan not configured") | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |             return | 
					
						
							|  |  |  |         print_time = self.toolhead.get_last_move_time() | 
					
						
							|  |  |  |         self.fan.set_speed(print_time, speed) | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |     # G-Code special command handlers | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_default(self, params): | 
					
						
							| 
									
										
										
										
											2016-11-22 19:38:51 -05:00
										 |  |  |         if not self.is_printer_ready: | 
					
						
							| 
									
										
										
										
											2016-11-30 14:30:45 -05:00
										 |  |  |             self.respond_error(self.printer.get_state_message()) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             return | 
					
						
							|  |  |  |         cmd = params.get('#command') | 
					
						
							|  |  |  |         if not cmd: | 
					
						
							|  |  |  |             logging.debug(params['#original']) | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2017-04-29 14:56:39 -04:00
										 |  |  |         if cmd[0] == 'T' and len(cmd) > 1 and cmd[1].isdigit(): | 
					
						
							|  |  |  |             # Tn command has to be handled specially | 
					
						
							|  |  |  |             self.cmd_Tn(params) | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2017-07-17 11:24:15 -04:00
										 |  |  |         self.respond_info('Unknown command:"%s"' % (cmd,)) | 
					
						
							| 
									
										
										
										
											2017-04-29 14:56:39 -04:00
										 |  |  |     def cmd_Tn(self, params): | 
					
						
							|  |  |  |         # Select Tool | 
					
						
							| 
									
										
										
										
											2018-07-12 22:15:45 -04:00
										 |  |  |         extruders = kinematics.extruder.get_printer_extruders(self.printer) | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |         index = self.get_int('T', params, minval=0, maxval=len(extruders)-1) | 
					
						
							| 
									
										
										
										
											2017-04-29 14:56:39 -04:00
										 |  |  |         e = extruders[index] | 
					
						
							|  |  |  |         if self.extruder is e: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2018-06-30 14:08:02 -04:00
										 |  |  |         self.run_script_from_command(self.extruder.get_activate_gcode(False)) | 
					
						
							| 
									
										
										
										
											2017-04-29 14:56:39 -04:00
										 |  |  |         try: | 
					
						
							|  |  |  |             self.toolhead.set_extruder(e) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except homing.EndstopError as e: | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |             raise error(str(e)) | 
					
						
							| 
									
										
										
										
											2017-04-29 14:56:39 -04:00
										 |  |  |         self.extruder = e | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |         self.reset_last_position() | 
					
						
							| 
									
										
										
										
											2018-05-20 13:47:00 -04:00
										 |  |  |         self.extrude_factor = 1. | 
					
						
							|  |  |  |         self.base_position[3] = self.last_position[3] | 
					
						
							| 
									
										
										
										
											2018-06-30 14:08:02 -04:00
										 |  |  |         self.run_script_from_command(self.extruder.get_activate_gcode(True)) | 
					
						
							| 
									
										
										
										
											2018-05-20 12:33:43 -04:00
										 |  |  |     def cmd_mux(self, params): | 
					
						
							|  |  |  |         key, values = self.mux_commands[params['#command']] | 
					
						
							|  |  |  |         if None in values: | 
					
						
							|  |  |  |             key_param = self.get_str(key, params, None) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             key_param = self.get_str(key, params) | 
					
						
							|  |  |  |         if key_param not in values: | 
					
						
							|  |  |  |             raise error("The value '%s' is not valid for %s" % (key_param, key)) | 
					
						
							|  |  |  |         values[key_param](params) | 
					
						
							| 
									
										
										
										
											2017-04-25 18:34:15 -04:00
										 |  |  |     all_handlers = [ | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |         'G1', 'G4', 'G28', 'M18', 'M400', | 
					
						
							| 
									
										
										
										
											2018-04-20 19:58:37 -04:00
										 |  |  |         'G20', 'M82', 'M83', 'G90', 'G91', 'G92', 'M114', 'M220', 'M221', | 
					
						
							|  |  |  |         'SET_GCODE_OFFSET', 'M206', | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |         'M105', 'M104', 'M109', 'M140', 'M190', 'M106', 'M107', | 
					
						
							| 
									
										
										
										
											2018-07-16 10:06:30 -04:00
										 |  |  |         'M112', 'M115', 'IGNORE', 'GET_POSITION', | 
					
						
							| 
									
										
										
										
											2017-06-06 15:04:01 -04:00
										 |  |  |         'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP'] | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |     # G-Code movement commands | 
					
						
							| 
									
										
										
										
											2016-11-30 22:34:38 -05:00
										 |  |  |     cmd_G1_aliases = ['G0'] | 
					
						
							|  |  |  |     def cmd_G1(self, params): | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         # Move | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |             for axis in 'XYZ': | 
					
						
							|  |  |  |                 if axis in params: | 
					
						
							|  |  |  |                     v = float(params[axis]) | 
					
						
							|  |  |  |                     pos = self.axis2pos[axis] | 
					
						
							|  |  |  |                     if not self.absolutecoord: | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |                         # value relative to position of last move | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |                         self.last_position[pos] += v | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |                     else: | 
					
						
							|  |  |  |                         # value relative to base coordinate position | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |                         self.last_position[pos] = v + self.base_position[pos] | 
					
						
							|  |  |  |             if 'E' in params: | 
					
						
							|  |  |  |                 v = float(params['E']) * self.extrude_factor | 
					
						
							|  |  |  |                 if not self.absolutecoord or not self.absoluteextrude: | 
					
						
							|  |  |  |                     # value relative to position of last move | 
					
						
							|  |  |  |                     self.last_position[3] += v | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     # value relative to base coordinate position | 
					
						
							|  |  |  |                     self.last_position[3] = v + self.base_position[3] | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |             if 'F' in params: | 
					
						
							| 
									
										
										
										
											2017-12-21 20:53:44 -05:00
										 |  |  |                 speed = float(params['F']) * self.speed_factor | 
					
						
							| 
									
										
										
										
											2017-05-01 18:04:33 -04:00
										 |  |  |                 if speed <= 0.: | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |                     raise error("Invalid speed in '%s'" % (params['#original'],)) | 
					
						
							| 
									
										
										
										
											2017-05-01 18:04:33 -04:00
										 |  |  |                 self.speed = speed | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except ValueError as e: | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |             raise error("Unable to parse move '%s'" % (params['#original'],)) | 
					
						
							| 
									
										
										
										
											2016-09-30 16:00:32 -04:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2018-01-20 23:47:36 -05:00
										 |  |  |             self.move_with_transform(self.last_position, self.speed) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except homing.EndstopError as e: | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |             raise error(str(e)) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_G4(self, params): | 
					
						
							|  |  |  |         # Dwell | 
					
						
							|  |  |  |         if 'S' in params: | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |             delay = self.get_float('S', params, minval=0.) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |             delay = self.get_float('P', params, 0., minval=0.) / 1000. | 
					
						
							| 
									
										
										
										
											2016-07-07 14:23:48 -04:00
										 |  |  |         self.toolhead.dwell(delay) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_G28(self, params): | 
					
						
							|  |  |  |         # Move to origin | 
					
						
							| 
									
										
										
										
											2016-07-25 23:47:30 -04:00
										 |  |  |         axes = [] | 
					
						
							|  |  |  |         for axis in 'XYZ': | 
					
						
							|  |  |  |             if axis in params: | 
					
						
							|  |  |  |                 axes.append(self.axis2pos[axis]) | 
					
						
							|  |  |  |         if not axes: | 
					
						
							|  |  |  |             axes = [0, 1, 2] | 
					
						
							| 
									
										
										
										
											2017-12-06 09:59:00 -05:00
										 |  |  |         homing_state = homing.Homing(self.toolhead) | 
					
						
							| 
									
										
										
										
											2016-11-20 20:40:31 -05:00
										 |  |  |         if self.is_fileinput: | 
					
						
							| 
									
										
										
										
											2016-11-28 11:23:26 -05:00
										 |  |  |             homing_state.set_no_verify_retract() | 
					
						
							| 
									
										
										
										
											2016-12-08 18:12:20 -05:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2017-12-06 09:59:00 -05:00
										 |  |  |             homing_state.home_axes(axes) | 
					
						
							| 
									
										
										
										
											2017-06-09 23:32:49 -04:00
										 |  |  |         except homing.EndstopError as e: | 
					
						
							| 
									
										
										
										
											2017-12-03 19:30:49 -05:00
										 |  |  |             raise error(str(e)) | 
					
						
							| 
									
										
										
										
											2016-12-08 18:12:20 -05:00
										 |  |  |         for axis in homing_state.get_axes(): | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |             self.base_position[axis] = self.homing_position[axis] | 
					
						
							| 
									
										
										
										
											2018-01-16 21:46:42 -05:00
										 |  |  |         self.reset_last_position() | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |     cmd_M18_aliases = ["M84"] | 
					
						
							|  |  |  |     def cmd_M18(self, params): | 
					
						
							|  |  |  |         # Turn off motors | 
					
						
							|  |  |  |         self.toolhead.motor_off() | 
					
						
							|  |  |  |     def cmd_M400(self, params): | 
					
						
							|  |  |  |         # Wait for current moves to finish | 
					
						
							|  |  |  |         self.toolhead.wait_moves() | 
					
						
							|  |  |  |     # G-Code coordinate manipulation | 
					
						
							|  |  |  |     def cmd_G20(self, params): | 
					
						
							|  |  |  |         # Set units to inches | 
					
						
							|  |  |  |         self.respond_error('Machine does not support G20 (inches) command') | 
					
						
							|  |  |  |     def cmd_M82(self, params): | 
					
						
							|  |  |  |         # Use absolute distances for extrusion | 
					
						
							|  |  |  |         self.absoluteextrude = True | 
					
						
							|  |  |  |     def cmd_M83(self, params): | 
					
						
							|  |  |  |         # Use relative distances for extrusion | 
					
						
							|  |  |  |         self.absoluteextrude = False | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_G90(self, params): | 
					
						
							|  |  |  |         # Use absolute coordinates | 
					
						
							|  |  |  |         self.absolutecoord = True | 
					
						
							|  |  |  |     def cmd_G91(self, params): | 
					
						
							|  |  |  |         # Use relative coordinates | 
					
						
							|  |  |  |         self.absolutecoord = False | 
					
						
							|  |  |  |     def cmd_G92(self, params): | 
					
						
							|  |  |  |         # Set position | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |         offsets = { p: self.get_float(a, params) | 
					
						
							|  |  |  |                     for a, p in self.axis2pos.items() if a in params } | 
					
						
							|  |  |  |         for p, offset in offsets.items(): | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |             if p == 3: | 
					
						
							|  |  |  |                 offset *= self.extrude_factor | 
					
						
							| 
									
										
										
										
											2017-03-15 23:40:46 -04:00
										 |  |  |             self.base_position[p] = self.last_position[p] - offset | 
					
						
							|  |  |  |         if not offsets: | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |             self.base_position = list(self.last_position) | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |     cmd_M114_when_not_ready = True | 
					
						
							|  |  |  |     def cmd_M114(self, params): | 
					
						
							|  |  |  |         # Get Current Position | 
					
						
							|  |  |  |         p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)] | 
					
						
							|  |  |  |         p[3] /= self.extrude_factor | 
					
						
							|  |  |  |         self.respond("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p)) | 
					
						
							| 
									
										
										
										
											2017-12-21 20:53:44 -05:00
										 |  |  |     def cmd_M220(self, params): | 
					
						
							|  |  |  |         # Set speed factor override percentage | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |         value = self.get_float('S', params, 100., above=0.) / (60. * 100.) | 
					
						
							| 
									
										
										
										
											2017-12-21 20:53:44 -05:00
										 |  |  |         self.speed_factor = value | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |     def cmd_M221(self, params): | 
					
						
							|  |  |  |         # Set extrude factor override percentage | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |         new_extrude_factor = self.get_float('S', params, 100., above=0.) / 100. | 
					
						
							| 
									
										
										
										
											2017-12-21 21:33:38 -05:00
										 |  |  |         last_e_pos = self.last_position[3] | 
					
						
							|  |  |  |         e_value = (last_e_pos - self.base_position[3]) / self.extrude_factor | 
					
						
							|  |  |  |         self.base_position[3] = last_e_pos - e_value * new_extrude_factor | 
					
						
							|  |  |  |         self.extrude_factor = new_extrude_factor | 
					
						
							| 
									
										
										
										
											2018-04-20 19:58:37 -04:00
										 |  |  |     cmd_SET_GCODE_OFFSET_help = "Set a virtual offset to g-code positions" | 
					
						
							|  |  |  |     def cmd_SET_GCODE_OFFSET(self, params): | 
					
						
							|  |  |  |         for axis, pos in self.axis2pos.items(): | 
					
						
							|  |  |  |             if axis in params: | 
					
						
							|  |  |  |                 offset = self.get_float(axis, params) | 
					
						
							|  |  |  |             elif axis + '_ADJUST' in params: | 
					
						
							|  |  |  |                 offset = self.homing_position[pos] | 
					
						
							|  |  |  |                 offset += self.get_float(axis + '_ADJUST', params) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2018-05-29 14:09:04 -04:00
										 |  |  |             delta = offset - self.homing_position[pos] | 
					
						
							|  |  |  |             self.last_position[pos] += delta | 
					
						
							|  |  |  |             self.base_position[pos] += delta | 
					
						
							| 
									
										
										
										
											2018-04-20 19:58:37 -04:00
										 |  |  |             self.homing_position[pos] = offset | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |     def cmd_M206(self, params): | 
					
						
							|  |  |  |         # Offset axes | 
					
						
							|  |  |  |         offsets = { self.axis2pos[a]: self.get_float(a, params) | 
					
						
							|  |  |  |                     for a in 'XYZ' if a in params } | 
					
						
							|  |  |  |         for p, offset in offsets.items(): | 
					
						
							|  |  |  |             self.base_position[p] -= self.homing_position[p] + offset | 
					
						
							|  |  |  |             self.homing_position[p] = -offset | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |     # G-Code temperature and fan commands | 
					
						
							| 
									
										
										
										
											2016-11-30 22:34:38 -05:00
										 |  |  |     cmd_M105_when_not_ready = True | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_M105(self, params): | 
					
						
							|  |  |  |         # Get Extruder Temperature | 
					
						
							| 
									
										
										
										
											2017-10-11 22:16:02 -04:00
										 |  |  |         self.ack(self.get_temp(self.reactor.monotonic())) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_M104(self, params): | 
					
						
							|  |  |  |         # Set Extruder Temperature | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         self.set_temp(params) | 
					
						
							| 
									
										
										
										
											2016-05-25 11:37:40 -04:00
										 |  |  |     def cmd_M109(self, params): | 
					
						
							|  |  |  |         # Set Extruder Temperature and Wait | 
					
						
							| 
									
										
										
										
											2017-04-29 15:26:53 -04:00
										 |  |  |         self.set_temp(params, wait=True) | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |     def cmd_M140(self, params): | 
					
						
							|  |  |  |         # Set Bed Temperature | 
					
						
							|  |  |  |         self.set_temp(params, is_bed=True) | 
					
						
							|  |  |  |     def cmd_M190(self, params): | 
					
						
							|  |  |  |         # Set Bed Temperature and Wait | 
					
						
							|  |  |  |         self.set_temp(params, is_bed=True, wait=True) | 
					
						
							|  |  |  |     def cmd_M106(self, params): | 
					
						
							|  |  |  |         # Set fan speed | 
					
						
							| 
									
										
										
										
											2018-04-20 21:41:13 -04:00
										 |  |  |         self.set_fan_speed(self.get_float('S', params, 255., minval=0.) / 255.) | 
					
						
							| 
									
										
										
										
											2017-12-21 20:45:07 -05:00
										 |  |  |     def cmd_M107(self, params): | 
					
						
							|  |  |  |         # Turn fan off | 
					
						
							|  |  |  |         self.set_fan_speed(0.) | 
					
						
							|  |  |  |     # G-Code miscellaneous commands | 
					
						
							| 
									
										
										
										
											2017-12-03 18:16:23 -05:00
										 |  |  |     cmd_M112_when_not_ready = True | 
					
						
							| 
									
										
										
										
											2016-12-09 13:55:18 -05:00
										 |  |  |     def cmd_M112(self, params): | 
					
						
							|  |  |  |         # Emergency Stop | 
					
						
							| 
									
										
										
										
											2017-10-12 15:15:14 -04:00
										 |  |  |         self.printer.invoke_shutdown("Shutdown due to M112 command") | 
					
						
							| 
									
										
										
										
											2016-12-28 21:45:29 -05:00
										 |  |  |     cmd_M115_when_not_ready = True | 
					
						
							|  |  |  |     def cmd_M115(self, params): | 
					
						
							|  |  |  |         # Get Firmware Version and Capabilities | 
					
						
							| 
									
										
										
										
											2017-08-21 17:19:43 -04:00
										 |  |  |         software_version = self.printer.get_start_args().get('software_version') | 
					
						
							|  |  |  |         kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version} | 
					
						
							| 
									
										
										
										
											2016-12-28 21:45:29 -05:00
										 |  |  |         self.ack(" ".join(["%s:%s" % (k, v) for k, v in kw.items()])) | 
					
						
							| 
									
										
										
										
											2017-04-20 10:11:10 -04:00
										 |  |  |     cmd_IGNORE_when_not_ready = True | 
					
						
							|  |  |  |     cmd_IGNORE_aliases = ["G21", "M110", "M21"] | 
					
						
							|  |  |  |     def cmd_IGNORE(self, params): | 
					
						
							|  |  |  |         # Commands that are just silently accepted | 
					
						
							|  |  |  |         pass | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |     cmd_GET_POSITION_when_not_ready = True | 
					
						
							|  |  |  |     def cmd_GET_POSITION(self, params): | 
					
						
							|  |  |  |         if self.toolhead is None: | 
					
						
							|  |  |  |             self.cmd_default(params) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         kin = self.toolhead.get_kinematics() | 
					
						
							| 
									
										
										
										
											2018-07-16 10:16:16 -04:00
										 |  |  |         steppers = kin.get_steppers() | 
					
						
							| 
									
										
										
										
											2018-06-21 18:15:23 -04:00
										 |  |  |         mcu_pos = " ".join(["%s:%d" % (s.get_name(), s.get_mcu_position()) | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |                             for s in steppers]) | 
					
						
							|  |  |  |         stepper_pos = " ".join( | 
					
						
							| 
									
										
										
										
											2018-06-21 18:15:23 -04:00
										 |  |  |             ["%s:%.6f" % (s.get_name(), s.get_commanded_position()) | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |              for s in steppers]) | 
					
						
							|  |  |  |         kinematic_pos = " ".join(["%s:%.6f"  % (a, v) | 
					
						
							| 
									
										
										
										
											2018-06-22 12:27:37 -04:00
										 |  |  |                                   for a, v in zip("XYZE", kin.calc_position())]) | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |         toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip( | 
					
						
							|  |  |  |             "XYZE", self.toolhead.get_position())]) | 
					
						
							|  |  |  |         gcode_pos = " ".join(["%s:%.6f"  % (a, v) | 
					
						
							|  |  |  |                               for a, v in zip("XYZE", self.last_position)]) | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |         base_pos = " ".join(["%s:%.6f"  % (a, v) | 
					
						
							|  |  |  |                              for a, v in zip("XYZE", self.base_position)]) | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |         homing_pos = " ".join(["%s:%.6f"  % (a, v) | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |                                for a, v in zip("XYZ", self.homing_position)]) | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |         self.respond_info( | 
					
						
							|  |  |  |             "mcu: %s\n" | 
					
						
							|  |  |  |             "stepper: %s\n" | 
					
						
							|  |  |  |             "kinematic: %s\n" | 
					
						
							|  |  |  |             "toolhead: %s\n" | 
					
						
							|  |  |  |             "gcode: %s\n" | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |             "gcode base: %s\n" | 
					
						
							| 
									
										
										
										
											2018-03-09 21:43:24 -05:00
										 |  |  |             "gcode homing: %s" % ( | 
					
						
							|  |  |  |                 mcu_pos, stepper_pos, kinematic_pos, toolhead_pos, | 
					
						
							| 
									
										
										
										
											2018-04-20 19:42:26 -04:00
										 |  |  |                 gcode_pos, base_pos, homing_pos)) | 
					
						
							| 
									
										
										
										
											2018-02-11 13:52:20 -05:00
										 |  |  |     def request_restart(self, result): | 
					
						
							| 
									
										
										
										
											2017-02-12 18:42:29 -05:00
										 |  |  |         if self.is_printer_ready: | 
					
						
							|  |  |  |             self.respond_info("Preparing to restart...") | 
					
						
							| 
									
										
										
										
											2018-02-11 13:52:20 -05:00
										 |  |  |             self.toolhead.motor_off() | 
					
						
							|  |  |  |             print_time = self.toolhead.get_last_move_time() | 
					
						
							|  |  |  |             for heater in self.heaters: | 
					
						
							|  |  |  |                 if heater is not None: | 
					
						
							|  |  |  |                     heater.set_temp(print_time, 0.) | 
					
						
							|  |  |  |             if self.fan is not None: | 
					
						
							|  |  |  |                 self.fan.set_speed(print_time, 0.) | 
					
						
							| 
									
										
										
										
											2017-02-12 18:42:29 -05:00
										 |  |  |             self.toolhead.dwell(0.500) | 
					
						
							|  |  |  |             self.toolhead.wait_moves() | 
					
						
							| 
									
										
										
										
											2018-02-11 13:52:20 -05:00
										 |  |  |         self.printer.request_exit(result) | 
					
						
							| 
									
										
										
										
											2017-03-08 22:26:10 -05:00
										 |  |  |     cmd_RESTART_when_not_ready = True | 
					
						
							|  |  |  |     cmd_RESTART_help = "Reload config file and restart host software" | 
					
						
							|  |  |  |     def cmd_RESTART(self, params): | 
					
						
							| 
									
										
										
										
											2018-02-11 13:52:20 -05:00
										 |  |  |         self.request_restart('restart') | 
					
						
							| 
									
										
										
										
											2017-03-08 22:26:10 -05:00
										 |  |  |     cmd_FIRMWARE_RESTART_when_not_ready = True | 
					
						
							|  |  |  |     cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config" | 
					
						
							|  |  |  |     def cmd_FIRMWARE_RESTART(self, params): | 
					
						
							| 
									
										
										
										
											2018-02-11 13:52:20 -05:00
										 |  |  |         self.request_restart('firmware_restart') | 
					
						
							| 
									
										
										
										
											2017-09-02 11:59:27 -04:00
										 |  |  |     cmd_ECHO_when_not_ready = True | 
					
						
							|  |  |  |     def cmd_ECHO(self, params): | 
					
						
							|  |  |  |         self.respond_info(params['#original']) | 
					
						
							| 
									
										
										
										
											2016-12-21 11:33:03 -05:00
										 |  |  |     cmd_STATUS_when_not_ready = True | 
					
						
							|  |  |  |     cmd_STATUS_help = "Report the printer status" | 
					
						
							|  |  |  |     def cmd_STATUS(self, params): | 
					
						
							|  |  |  |         msg = self.printer.get_state_message() | 
					
						
							|  |  |  |         if self.is_printer_ready: | 
					
						
							|  |  |  |             self.respond_info(msg) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.respond_error(msg) | 
					
						
							| 
									
										
										
										
											2016-11-30 23:47:40 -05:00
										 |  |  |     cmd_HELP_when_not_ready = True | 
					
						
							| 
									
										
										
										
											2016-11-30 22:34:38 -05:00
										 |  |  |     def cmd_HELP(self, params): | 
					
						
							| 
									
										
										
										
											2016-12-21 11:33:03 -05:00
										 |  |  |         cmdhelp = [] | 
					
						
							|  |  |  |         if not self.is_printer_ready: | 
					
						
							|  |  |  |             cmdhelp.append("Printer is not ready - not all commands available.") | 
					
						
							|  |  |  |         cmdhelp.append("Available extended commands:") | 
					
						
							| 
									
										
										
										
											2017-04-26 10:44:22 -04:00
										 |  |  |         for cmd in sorted(self.gcode_handlers): | 
					
						
							| 
									
										
										
										
											2017-12-03 18:53:42 -05:00
										 |  |  |             if cmd in self.gcode_help: | 
					
						
							|  |  |  |                 cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd])) | 
					
						
							| 
									
										
										
										
											2016-12-21 11:33:03 -05:00
										 |  |  |         self.respond_info("\n".join(cmdhelp)) |