mirror of
				https://github.com/Klipper3d/klipper.git
				synced 2025-10-31 10:25:57 +01:00 
			
		
		
		
	gcode: Separate IO handling to its own class
Move the pseudo-tty IO handling from the main gcode class to a new gcode_io class. This simplifies the main gcode class. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
		
							
								
								
									
										238
									
								
								klippy/gcode.py
									
									
									
									
									
								
							
							
						
						
									
										238
									
								
								klippy/gcode.py
									
									
									
									
									
								
							| @@ -69,29 +69,15 @@ class GCodeCommand: | |||||||
| # Parse and handle G-Code commands | # Parse and handle G-Code commands | ||||||
| class GCodeParser: | class GCodeParser: | ||||||
|     error = homing.CommandError |     error = homing.CommandError | ||||||
|     RETRY_TIME = 0.100 |  | ||||||
|     def __init__(self, printer): |     def __init__(self, printer): | ||||||
|         self.printer = printer |         self.printer = printer | ||||||
|         self.fd = printer.get_start_args().get("gcode_fd") |         self.is_fileinput = not not printer.get_start_args().get("debuginput") | ||||||
|         printer.register_event_handler("klippy:ready", self._handle_ready) |         printer.register_event_handler("klippy:ready", self._handle_ready) | ||||||
|         printer.register_event_handler("klippy:shutdown", self._handle_shutdown) |         printer.register_event_handler("klippy:shutdown", self._handle_shutdown) | ||||||
|         printer.register_event_handler("klippy:disconnect", |         printer.register_event_handler("klippy:disconnect", | ||||||
|                                        self._handle_disconnect) |                                        self._handle_disconnect) | ||||||
|         printer.register_event_handler("extruder:activate_extruder", |         printer.register_event_handler("extruder:activate_extruder", | ||||||
|                                        self._handle_activate_extruder) |                                        self._handle_activate_extruder) | ||||||
|         # Input handling |  | ||||||
|         self.reactor = printer.get_reactor() |  | ||||||
|         self.is_processing_data = False |  | ||||||
|         self.is_fileinput = not not printer.get_start_args().get("debuginput") |  | ||||||
|         self.pipe_is_active = not self.is_fileinput |  | ||||||
|         self.fd_handle = None |  | ||||||
|         if not self.is_fileinput: |  | ||||||
|             self.fd_handle = self.reactor.register_fd(self.fd, |  | ||||||
|                                                       self._process_data) |  | ||||||
|         self.partial_input = "" |  | ||||||
|         self.pending_commands = [] |  | ||||||
|         self.bytes_read = 0 |  | ||||||
|         self.input_log = collections.deque([], 50) |  | ||||||
|         # Register webhooks |         # Register webhooks | ||||||
|         webhooks = self.printer.lookup_object('webhooks') |         webhooks = self.printer.lookup_object('webhooks') | ||||||
|         webhooks.register_endpoint( |         webhooks.register_endpoint( | ||||||
| @@ -104,7 +90,7 @@ class GCodeParser: | |||||||
|             "gcode/firmware_restart", self._handle_remote_restart) |             "gcode/firmware_restart", self._handle_remote_restart) | ||||||
|         # Command handling |         # Command handling | ||||||
|         self.is_printer_ready = False |         self.is_printer_ready = False | ||||||
|         self.mutex = self.reactor.mutex() |         self.mutex = printer.get_reactor().mutex() | ||||||
|         self.output_callbacks = [] |         self.output_callbacks = [] | ||||||
|         self.base_gcode_handlers = self.gcode_handlers = {} |         self.base_gcode_handlers = self.gcode_handlers = {} | ||||||
|         self.ready_gcode_handlers = {} |         self.ready_gcode_handlers = {} | ||||||
| @@ -185,8 +171,6 @@ class GCodeParser: | |||||||
|         self.move_with_transform = transform.move |         self.move_with_transform = transform.move | ||||||
|         self.position_with_transform = transform.get_position |         self.position_with_transform = transform.get_position | ||||||
|         return old_transform |         return old_transform | ||||||
|     def stats(self, eventtime): |  | ||||||
|         return False, "gcodein=%d" % (self.bytes_read,) |  | ||||||
|     def _action_emergency_stop(self, msg="action_emergency_stop"): |     def _action_emergency_stop(self, msg="action_emergency_stop"): | ||||||
|         self.printer.invoke_shutdown("Shutdown due to %s" % (msg,)) |         self.printer.invoke_shutdown("Shutdown due to %s" % (msg,)) | ||||||
|         return "" |         return "" | ||||||
| @@ -206,14 +190,12 @@ class GCodeParser: | |||||||
|         return self.speed_factor * 60. |         return self.speed_factor * 60. | ||||||
|     def get_status(self, eventtime=None): |     def get_status(self, eventtime=None): | ||||||
|         move_position = self._get_gcode_position() |         move_position = self._get_gcode_position() | ||||||
|         busy = self.is_processing_data |  | ||||||
|         return { |         return { | ||||||
|             'speed_factor': self._get_gcode_speed_override(), |             'speed_factor': self._get_gcode_speed_override(), | ||||||
|             'speed': self._get_gcode_speed(), |             'speed': self._get_gcode_speed(), | ||||||
|             'extrude_factor': self.extrude_factor, |             'extrude_factor': self.extrude_factor, | ||||||
|             'absolute_coordinates': self.absolute_coord, |             'absolute_coordinates': self.absolute_coord, | ||||||
|             'absolute_extrude': self.absolute_extrude, |             'absolute_extrude': self.absolute_extrude, | ||||||
|             'busy': busy, |  | ||||||
|             'move_xpos': move_position[0], |             'move_xpos': move_position[0], | ||||||
|             'move_ypos': move_position[1], |             'move_ypos': move_position[1], | ||||||
|             'move_zpos': move_position[2], |             'move_zpos': move_position[2], | ||||||
| @@ -239,9 +221,6 @@ class GCodeParser: | |||||||
|             return |             return | ||||||
|         self.is_printer_ready = False |         self.is_printer_ready = False | ||||||
|         self.gcode_handlers = self.base_gcode_handlers |         self.gcode_handlers = self.base_gcode_handlers | ||||||
|         self._dump_debug() |  | ||||||
|         if self.is_fileinput: |  | ||||||
|             self.printer.request_exit('error_exit') |  | ||||||
|         self._respond_state("Shutdown") |         self._respond_state("Shutdown") | ||||||
|     def _handle_disconnect(self): |     def _handle_disconnect(self): | ||||||
|         self._respond_state("Disconnect") |         self._respond_state("Disconnect") | ||||||
| @@ -252,9 +231,6 @@ class GCodeParser: | |||||||
|         if self.move_transform is None: |         if self.move_transform is None: | ||||||
|             self.move_with_transform = self.toolhead.move |             self.move_with_transform = self.toolhead.move | ||||||
|             self.position_with_transform = self.toolhead.get_position |             self.position_with_transform = self.toolhead.get_position | ||||||
|         if self.is_fileinput and self.fd_handle is None: |  | ||||||
|             self.fd_handle = self.reactor.register_fd(self.fd, |  | ||||||
|                                                       self._process_data) |  | ||||||
|         self._respond_state("Ready") |         self._respond_state("Ready") | ||||||
|     def _handle_activate_extruder(self): |     def _handle_activate_extruder(self): | ||||||
|         self.reset_last_position() |         self.reset_last_position() | ||||||
| @@ -263,20 +239,6 @@ class GCodeParser: | |||||||
|     def reset_last_position(self): |     def reset_last_position(self): | ||||||
|         if self.is_printer_ready: |         if self.is_printer_ready: | ||||||
|             self.last_position = self.position_with_transform() |             self.last_position = self.position_with_transform() | ||||||
|     def _dump_debug(self): |  | ||||||
|         out = [] |  | ||||||
|         out.append("Dumping gcode input %d blocks" % ( |  | ||||||
|             len(self.input_log),)) |  | ||||||
|         for eventtime, data in self.input_log: |  | ||||||
|             out.append("Read %f: %s" % (eventtime, repr(data))) |  | ||||||
|         out.append( |  | ||||||
|             "gcode state: absolute_coord=%s absolute_extrude=%s" |  | ||||||
|             " base_position=%s last_position=%s homing_position=%s" |  | ||||||
|             " speed_factor=%s extrude_factor=%s speed=%s" % ( |  | ||||||
|                 self.absolute_coord, self.absolute_extrude, |  | ||||||
|                 self.base_position, self.last_position, self.homing_position, |  | ||||||
|                 self.speed_factor, self.extrude_factor, self.speed)) |  | ||||||
|         logging.info("\n".join(out)) |  | ||||||
|     # Parse input into commands |     # Parse input into commands | ||||||
|     args_r = re.compile('([A-Z_]+|[A-Z*/])') |     args_r = re.compile('([A-Z_]+|[A-Z*/])') | ||||||
|     def _process_commands(self, commands, need_ack=True): |     def _process_commands(self, commands, need_ack=True): | ||||||
| @@ -317,71 +279,6 @@ class GCodeParser: | |||||||
|                 if not need_ack: |                 if not need_ack: | ||||||
|                     raise |                     raise | ||||||
|             gcmd.ack() |             gcmd.ack() | ||||||
|     m112_r = re.compile('^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)') |  | ||||||
|     def _process_data(self, eventtime): |  | ||||||
|         # Read input, separate by newline, and add to pending_commands |  | ||||||
|         try: |  | ||||||
|             data = os.read(self.fd, 4096) |  | ||||||
|         except os.error: |  | ||||||
|             logging.exception("Read g-code") |  | ||||||
|             return |  | ||||||
|         self.input_log.append((eventtime, data)) |  | ||||||
|         self.bytes_read += len(data) |  | ||||||
|         lines = data.split('\n') |  | ||||||
|         lines[0] = self.partial_input + lines[0] |  | ||||||
|         self.partial_input = lines.pop() |  | ||||||
|         pending_commands = self.pending_commands |  | ||||||
|         pending_commands.extend(lines) |  | ||||||
|         if not self.is_fileinput: |  | ||||||
|             self.pipe_is_active = True |  | ||||||
|         elif not data: |  | ||||||
|             # Special handling for debug file input EOF |  | ||||||
|             if not self.is_processing_data: |  | ||||||
|                 self.reactor.unregister_fd(self.fd_handle) |  | ||||||
|                 self.fd_handle = None |  | ||||||
|                 self.request_restart('exit') |  | ||||||
|             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(None) |  | ||||||
|             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 |  | ||||||
|         # Process commands |  | ||||||
|         self.is_processing_data = True |  | ||||||
|         while pending_commands: |  | ||||||
|             self.pending_commands = [] |  | ||||||
|             with self.mutex: |  | ||||||
|                 self._process_commands(pending_commands) |  | ||||||
|             pending_commands = self.pending_commands |  | ||||||
|         self.is_processing_data = False |  | ||||||
|         if self.fd_handle is None: |  | ||||||
|             self.fd_handle = self.reactor.register_fd(self.fd, |  | ||||||
|                                                       self._process_data) |  | ||||||
|     def _handle_remote_help(self, web_request): |  | ||||||
|         if web_request.get_method() != 'GET': |  | ||||||
|             raise web_request.error("Invalid Request Method") |  | ||||||
|         web_request.send(dict(self.gcode_help)) |  | ||||||
|     def _handle_remote_restart(self, web_request): |  | ||||||
|         if web_request.get_method() != 'POST': |  | ||||||
|             raise web_request.error("Invalid Request Method") |  | ||||||
|         path = web_request.get_path() |  | ||||||
|         if path == "gcode/restart": |  | ||||||
|             self.run_script('restart') |  | ||||||
|         elif path == "gcode/firmware_restart": |  | ||||||
|             self.run_script('firmware_restart') |  | ||||||
|     def _handle_remote_script(self, web_request): |  | ||||||
|         if web_request.get_method() != 'POST': |  | ||||||
|             raise web_request.error("Invalid Request Method") |  | ||||||
|         script = web_request.get('script') |  | ||||||
|         self.run_script(script) |  | ||||||
|     def run_script_from_command(self, script): |     def run_script_from_command(self, script): | ||||||
|         self._process_commands(script.split('\n'), need_ack=False) |         self._process_commands(script.split('\n'), need_ack=False) | ||||||
|     def run_script(self, script): |     def run_script(self, script): | ||||||
| @@ -395,12 +292,6 @@ class GCodeParser: | |||||||
|     def respond_raw(self, msg): |     def respond_raw(self, msg): | ||||||
|         for cb in self.output_callbacks: |         for cb in self.output_callbacks: | ||||||
|             cb(msg) |             cb(msg) | ||||||
|         if self.pipe_is_active: |  | ||||||
|             try: |  | ||||||
|                 os.write(self.fd, msg+"\n") |  | ||||||
|             except os.error: |  | ||||||
|                 logging.exception("Write g-code response") |  | ||||||
|                 self.pipe_is_active = False |  | ||||||
|     def respond_info(self, msg, log=True): |     def respond_info(self, msg, log=True): | ||||||
|         if log: |         if log: | ||||||
|             logging.info(msg) |             logging.info(msg) | ||||||
| @@ -719,6 +610,131 @@ class GCodeParser: | |||||||
|             if cmd in self.gcode_help: |             if cmd in self.gcode_help: | ||||||
|                 cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd])) |                 cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd])) | ||||||
|         gcmd.respond_info("\n".join(cmdhelp), log=False) |         gcmd.respond_info("\n".join(cmdhelp), log=False) | ||||||
|  |     # Webhooks | ||||||
|  |     def _handle_remote_help(self, web_request): | ||||||
|  |         if web_request.get_method() != 'GET': | ||||||
|  |             raise web_request.error("Invalid Request Method") | ||||||
|  |         web_request.send(dict(self.gcode_help)) | ||||||
|  |     def _handle_remote_restart(self, web_request): | ||||||
|  |         if web_request.get_method() != 'POST': | ||||||
|  |             raise web_request.error("Invalid Request Method") | ||||||
|  |         path = web_request.get_path() | ||||||
|  |         if path == "gcode/restart": | ||||||
|  |             self.run_script('restart') | ||||||
|  |         elif path == "gcode/firmware_restart": | ||||||
|  |             self.run_script('firmware_restart') | ||||||
|  |     def _handle_remote_script(self, web_request): | ||||||
|  |         if web_request.get_method() != 'POST': | ||||||
|  |             raise web_request.error("Invalid Request Method") | ||||||
|  |         script = web_request.get('script') | ||||||
|  |         self.run_script(script) | ||||||
|  |  | ||||||
|  | # Support reading gcode from a pseudo-tty interface | ||||||
|  | class GCodeIO: | ||||||
|  |     def __init__(self, printer): | ||||||
|  |         self.printer = printer | ||||||
|  |         printer.register_event_handler("klippy:ready", self._handle_ready) | ||||||
|  |         printer.register_event_handler("klippy:shutdown", self._handle_shutdown) | ||||||
|  |         self.gcode = printer.lookup_object('gcode') | ||||||
|  |         self.gcode_mutex = self.gcode.get_mutex() | ||||||
|  |         self.fd = printer.get_start_args().get("gcode_fd") | ||||||
|  |         self.reactor = printer.get_reactor() | ||||||
|  |         self.is_printer_ready = False | ||||||
|  |         self.is_processing_data = False | ||||||
|  |         self.is_fileinput = not not printer.get_start_args().get("debuginput") | ||||||
|  |         self.pipe_is_active = True | ||||||
|  |         self.fd_handle = None | ||||||
|  |         if not self.is_fileinput: | ||||||
|  |             self.gcode.register_output_handler(self._respond_raw) | ||||||
|  |             self.fd_handle = self.reactor.register_fd(self.fd, | ||||||
|  |                                                       self._process_data) | ||||||
|  |         self.partial_input = "" | ||||||
|  |         self.pending_commands = [] | ||||||
|  |         self.bytes_read = 0 | ||||||
|  |         self.input_log = collections.deque([], 50) | ||||||
|  |     def _handle_ready(self): | ||||||
|  |         self.is_printer_ready = True | ||||||
|  |         if self.is_fileinput and self.fd_handle is None: | ||||||
|  |             self.fd_handle = self.reactor.register_fd(self.fd, | ||||||
|  |                                                       self._process_data) | ||||||
|  |     def _dump_debug(self): | ||||||
|  |         out = [] | ||||||
|  |         out.append("Dumping gcode input %d blocks" % ( | ||||||
|  |             len(self.input_log),)) | ||||||
|  |         for eventtime, data in self.input_log: | ||||||
|  |             out.append("Read %f: %s" % (eventtime, repr(data))) | ||||||
|  |         out.append( | ||||||
|  |             "gcode state: absolute_coord=%s absolute_extrude=%s" | ||||||
|  |             " base_position=%s last_position=%s homing_position=%s" | ||||||
|  |             " speed_factor=%s extrude_factor=%s speed=%s" % ( | ||||||
|  |                 self.absolute_coord, self.absolute_extrude, | ||||||
|  |                 self.base_position, self.last_position, self.homing_position, | ||||||
|  |                 self.speed_factor, self.extrude_factor, self.speed)) | ||||||
|  |         logging.info("\n".join(out)) | ||||||
|  |     def _handle_shutdown(self): | ||||||
|  |         if not self.is_printer_ready: | ||||||
|  |             return | ||||||
|  |         self.is_printer_ready = False | ||||||
|  |         self._dump_debug() | ||||||
|  |         if self.is_fileinput: | ||||||
|  |             self.printer.request_exit('error_exit') | ||||||
|  |     m112_r = re.compile('^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)') | ||||||
|  |     def _process_data(self, eventtime): | ||||||
|  |         # Read input, separate by newline, and add to pending_commands | ||||||
|  |         try: | ||||||
|  |             data = os.read(self.fd, 4096) | ||||||
|  |         except os.error: | ||||||
|  |             logging.exception("Read g-code") | ||||||
|  |             return | ||||||
|  |         self.input_log.append((eventtime, data)) | ||||||
|  |         self.bytes_read += len(data) | ||||||
|  |         lines = data.split('\n') | ||||||
|  |         lines[0] = self.partial_input + lines[0] | ||||||
|  |         self.partial_input = lines.pop() | ||||||
|  |         pending_commands = self.pending_commands | ||||||
|  |         pending_commands.extend(lines) | ||||||
|  |         self.pipe_is_active = True | ||||||
|  |         # Special handling for debug file input EOF | ||||||
|  |         if not data and self.is_fileinput: | ||||||
|  |             if not self.is_processing_data: | ||||||
|  |                 self.reactor.unregister_fd(self.fd_handle) | ||||||
|  |                 self.fd_handle = None | ||||||
|  |                 self.gcode.request_restart('exit') | ||||||
|  |             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(None) | ||||||
|  |             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 | ||||||
|  |         # Process commands | ||||||
|  |         self.is_processing_data = True | ||||||
|  |         while pending_commands: | ||||||
|  |             self.pending_commands = [] | ||||||
|  |             with self.gcode_mutex: | ||||||
|  |                 self.gcode._process_commands(pending_commands) | ||||||
|  |             pending_commands = self.pending_commands | ||||||
|  |         self.is_processing_data = False | ||||||
|  |         if self.fd_handle is None: | ||||||
|  |             self.fd_handle = self.reactor.register_fd(self.fd, | ||||||
|  |                                                       self._process_data) | ||||||
|  |     def _respond_raw(self, msg): | ||||||
|  |         if self.pipe_is_active: | ||||||
|  |             try: | ||||||
|  |                 os.write(self.fd, msg+"\n") | ||||||
|  |             except os.error: | ||||||
|  |                 logging.exception("Write g-code response") | ||||||
|  |                 self.pipe_is_active = False | ||||||
|  |     def stats(self, eventtime): | ||||||
|  |         return False, "gcodein=%d" % (self.bytes_read,) | ||||||
|  |  | ||||||
| def add_early_printer_objects(printer): | def add_early_printer_objects(printer): | ||||||
|     printer.add_object('gcode', GCodeParser(printer)) |     printer.add_object('gcode', GCodeParser(printer)) | ||||||
|  |     printer.add_object('gcode_io', GCodeIO(printer)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user