aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2020-08-04 16:32:19 -0400
committerKevin O'Connor <kevin@koconnor.net>2020-08-06 14:21:08 -0400
commit447374f73efd0584deec14961b4f041791a2310c (patch)
tree3dca57fd3e4c44da233a5ec6c9a119da2ce31e62
parent795bd03b336c34bb09b3dcf190da08d0fa430258 (diff)
downloadkutter-447374f73efd0584deec14961b4f041791a2310c.tar.gz
kutter-447374f73efd0584deec14961b4f041791a2310c.tar.xz
kutter-447374f73efd0584deec14961b4f041791a2310c.zip
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>
-rw-r--r--klippy/gcode.py238
1 files changed, 127 insertions, 111 deletions
diff --git a/klippy/gcode.py b/klippy/gcode.py
index 32c0fa4f..ed77cd1e 100644
--- a/klippy/gcode.py
+++ b/klippy/gcode.py
@@ -69,29 +69,15 @@ class GCodeCommand:
# Parse and handle G-Code commands
class GCodeParser:
error = homing.CommandError
- RETRY_TIME = 0.100
def __init__(self, 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:shutdown", self._handle_shutdown)
printer.register_event_handler("klippy:disconnect",
self._handle_disconnect)
printer.register_event_handler("extruder: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
webhooks = self.printer.lookup_object('webhooks')
webhooks.register_endpoint(
@@ -104,7 +90,7 @@ class GCodeParser:
"gcode/firmware_restart", self._handle_remote_restart)
# Command handling
self.is_printer_ready = False
- self.mutex = self.reactor.mutex()
+ self.mutex = printer.get_reactor().mutex()
self.output_callbacks = []
self.base_gcode_handlers = self.gcode_handlers = {}
self.ready_gcode_handlers = {}
@@ -185,8 +171,6 @@ class GCodeParser:
self.move_with_transform = transform.move
self.position_with_transform = transform.get_position
return old_transform
- def stats(self, eventtime):
- return False, "gcodein=%d" % (self.bytes_read,)
def _action_emergency_stop(self, msg="action_emergency_stop"):
self.printer.invoke_shutdown("Shutdown due to %s" % (msg,))
return ""
@@ -206,14 +190,12 @@ class GCodeParser:
return self.speed_factor * 60.
def get_status(self, eventtime=None):
move_position = self._get_gcode_position()
- busy = self.is_processing_data
return {
'speed_factor': self._get_gcode_speed_override(),
'speed': self._get_gcode_speed(),
'extrude_factor': self.extrude_factor,
'absolute_coordinates': self.absolute_coord,
'absolute_extrude': self.absolute_extrude,
- 'busy': busy,
'move_xpos': move_position[0],
'move_ypos': move_position[1],
'move_zpos': move_position[2],
@@ -239,9 +221,6 @@ class GCodeParser:
return
self.is_printer_ready = False
self.gcode_handlers = self.base_gcode_handlers
- self._dump_debug()
- if self.is_fileinput:
- self.printer.request_exit('error_exit')
self._respond_state("Shutdown")
def _handle_disconnect(self):
self._respond_state("Disconnect")
@@ -252,9 +231,6 @@ class GCodeParser:
if self.move_transform is None:
self.move_with_transform = self.toolhead.move
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")
def _handle_activate_extruder(self):
self.reset_last_position()
@@ -263,20 +239,6 @@ class GCodeParser:
def reset_last_position(self):
if self.is_printer_ready:
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
args_r = re.compile('([A-Z_]+|[A-Z*/])')
def _process_commands(self, commands, need_ack=True):
@@ -317,71 +279,6 @@ class GCodeParser:
if not need_ack:
raise
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):
self._process_commands(script.split('\n'), need_ack=False)
def run_script(self, script):
@@ -395,12 +292,6 @@ class GCodeParser:
def respond_raw(self, msg):
for cb in self.output_callbacks:
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):
if log:
logging.info(msg)
@@ -719,6 +610,131 @@ class GCodeParser:
if cmd in self.gcode_help:
cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd]))
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):
printer.add_object('gcode', GCodeParser(printer))
+ printer.add_object('gcode_io', GCodeIO(printer))