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:
parent
795bd03b33
commit
447374f73e
238
klippy/gcode.py
238
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))
|
||||
|
|
Loading…
Reference in New Issue