WIP Refactor gcode execution
This commit is contained in:
parent
527ec9ef3c
commit
4da769da49
@ -65,7 +65,7 @@ class BambuPrintPlugin(
|
|||||||
"bed_leveling": True,
|
"bed_leveling": True,
|
||||||
"flow_cali": False,
|
"flow_cali": False,
|
||||||
"vibration_cali": True,
|
"vibration_cali": True,
|
||||||
"layer_inspect": True,
|
"layer_inspect": False,
|
||||||
"use_ams": False,
|
"use_ams": False,
|
||||||
"local_mqtt": True,
|
"local_mqtt": True,
|
||||||
"region": "",
|
"region": "",
|
||||||
|
@ -14,17 +14,21 @@ from typing import Any, Dict, List, Optional
|
|||||||
import asyncio
|
import asyncio
|
||||||
from pybambu import BambuClient, commands
|
from pybambu import BambuClient, commands
|
||||||
import logging
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
|
||||||
from serial import SerialTimeoutException
|
from serial import SerialTimeoutException
|
||||||
from octoprint.util import RepeatedTimer, to_bytes, to_unicode, get_dos_filename
|
from octoprint.util import RepeatedTimer, to_bytes, to_unicode, get_dos_filename
|
||||||
from octoprint.util.files import unix_timestamp_to_m20_timestamp
|
from octoprint.util.files import unix_timestamp_to_m20_timestamp
|
||||||
|
|
||||||
|
from octoprint_bambu_printer.gcode_executor import GCodeExecutor
|
||||||
|
|
||||||
from .char_counting_queue import CharCountingQueue
|
from .char_counting_queue import CharCountingQueue
|
||||||
from .ftpsclient import IoTFTPSClient
|
from .ftpsclient import IoTFTPSClient
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
class BambuVirtualPrinter:
|
class BambuVirtualPrinter:
|
||||||
|
gcode_executor = GCodeExecutor()
|
||||||
command_regex = re.compile(r"^([GM])(\d+)")
|
command_regex = re.compile(r"^([GM])(\d+)")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -68,7 +72,6 @@ class BambuVirtualPrinter:
|
|||||||
self.current_line = 0
|
self.current_line = 0
|
||||||
self._writingToSd = False
|
self._writingToSd = False
|
||||||
|
|
||||||
self._sdCardReady = True
|
|
||||||
self._sdPrinter = None
|
self._sdPrinter = None
|
||||||
self._sdPrinting = False
|
self._sdPrinting = False
|
||||||
self._sdPrintStarting = False
|
self._sdPrintStarting = False
|
||||||
@ -89,19 +92,17 @@ class BambuVirtualPrinter:
|
|||||||
self._faked_baudrate = faked_baudrate
|
self._faked_baudrate = faked_baudrate
|
||||||
self._plugin_data_folder = data_folder
|
self._plugin_data_folder = data_folder
|
||||||
|
|
||||||
self._seriallog = logging.getLogger(
|
self._serial_log = logging.getLogger(
|
||||||
"octoprint.plugins.bambu_printer.BambuPrinter.serial"
|
"octoprint.plugins.bambu_printer.BambuPrinter.serial"
|
||||||
)
|
)
|
||||||
self._seriallog.setLevel(logging.CRITICAL)
|
self._serial_log.setLevel(logging.CRITICAL)
|
||||||
self._seriallog.propagate = False
|
self._serial_log.propagate = False
|
||||||
|
|
||||||
if seriallog_handler is not None:
|
if seriallog_handler is not None:
|
||||||
import logging.handlers
|
self._serial_log.addHandler(seriallog_handler)
|
||||||
|
self._serial_log.setLevel(logging.INFO)
|
||||||
|
|
||||||
self._seriallog.addHandler(seriallog_handler)
|
self._serial_log.debug("-" * 78)
|
||||||
self._seriallog.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
self._seriallog.debug("-" * 78)
|
|
||||||
|
|
||||||
self._read_timeout = read_timeout
|
self._read_timeout = read_timeout
|
||||||
self._write_timeout = write_timeout
|
self._write_timeout = write_timeout
|
||||||
@ -242,7 +243,7 @@ class BambuVirtualPrinter:
|
|||||||
self._logger.debug(
|
self._logger.debug(
|
||||||
f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}"
|
f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}"
|
||||||
)
|
)
|
||||||
self.bambu = BambuClient(
|
self._bambu = BambuClient(
|
||||||
device_type=self._settings.get(["device_type"]),
|
device_type=self._settings.get(["device_type"]),
|
||||||
serial=self._settings.get(["serial"]),
|
serial=self._settings.get(["serial"]),
|
||||||
host=self._settings.get(["host"]),
|
host=self._settings.get(["host"]),
|
||||||
@ -257,10 +258,10 @@ class BambuVirtualPrinter:
|
|||||||
email=self._settings.get(["email"]),
|
email=self._settings.get(["email"]),
|
||||||
auth_token=self._settings.get(["auth_token"]),
|
auth_token=self._settings.get(["auth_token"]),
|
||||||
)
|
)
|
||||||
self.bambu.on_disconnect = self.on_disconnect(self.bambu.on_disconnect)
|
self._bambu.on_disconnect = self.on_disconnect(self._bambu.on_disconnect)
|
||||||
self.bambu.on_connect = self.on_connect(self.bambu.on_connect)
|
self._bambu.on_connect = self.on_connect(self._bambu.on_connect)
|
||||||
self.bambu.connect(callback=self.new_update)
|
self._bambu.connect(callback=self.new_update)
|
||||||
self._logger.info(f"bambu connection status: {self.bambu.connected}")
|
self._logger.info(f"bambu connection status: {self._bambu.connected}")
|
||||||
self._sendOk()
|
self._sendOk()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -289,7 +290,6 @@ class BambuVirtualPrinter:
|
|||||||
self._feedrate_multiplier = 100
|
self._feedrate_multiplier = 100
|
||||||
self._flowrate_multiplier = 100
|
self._flowrate_multiplier = 100
|
||||||
|
|
||||||
self._sdCardReady = True
|
|
||||||
self._sdPrinting = False
|
self._sdPrinting = False
|
||||||
self._sdPrintStarting = False
|
self._sdPrintStarting = False
|
||||||
if self._sdPrinter:
|
if self._sdPrinter:
|
||||||
@ -460,25 +460,13 @@ class BambuVirtualPrinter:
|
|||||||
command = command_match.group(0)
|
command = command_match.group(0)
|
||||||
letter = command_match.group(1)
|
letter = command_match.group(1)
|
||||||
|
|
||||||
try:
|
if letter in self.gcode_executor:
|
||||||
# if we have a method _gcode_G, _gcode_M or _gcode_T, execute that first
|
handled = self.run_gcode_handler(letter, data)
|
||||||
letter_handler = f"_gcode_{letter}"
|
|
||||||
if hasattr(self, letter_handler):
|
|
||||||
code = command_match.group(2)
|
|
||||||
handled = getattr(self, letter_handler)(code, data)
|
|
||||||
if handled:
|
|
||||||
self._sendOk()
|
|
||||||
continue
|
|
||||||
|
|
||||||
# then look for a method _gcode_<command> and execute that if it exists
|
|
||||||
command_handler = f"_gcode_{command}"
|
|
||||||
if hasattr(self, command_handler):
|
|
||||||
handled = getattr(self, command_handler)(data)
|
|
||||||
if handled:
|
|
||||||
self._sendOk()
|
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
|
handled = self.run_gcode_handler(command, data)
|
||||||
|
if handled:
|
||||||
self._sendOk()
|
self._sendOk()
|
||||||
|
continue
|
||||||
|
|
||||||
if self.bambu.connected:
|
if self.bambu.connected:
|
||||||
GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
|
GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
|
||||||
@ -487,70 +475,36 @@ class BambuVirtualPrinter:
|
|||||||
self._logger.info("command sent successfully")
|
self._logger.info("command sent successfully")
|
||||||
self._sendOk()
|
self._sendOk()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
finally:
|
|
||||||
self._logger.debug(f"{data}")
|
self._logger.debug(f"{data}")
|
||||||
|
|
||||||
self._logger.debug("Closing down read loop")
|
self._logger.debug("Closing down read loop")
|
||||||
|
|
||||||
##~~ command implementations
|
##~~ command implementations
|
||||||
|
def run_gcode_handler(self, gcode, data):
|
||||||
|
self.gcode_executor.execute(self, gcode, data)
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
@gcode_executor.register("M21")
|
||||||
def _gcode_M20(self, data: str) -> bool:
|
|
||||||
if self._sdCardReady:
|
|
||||||
self._listSd(incl_long="L" in data, incl_timestamp="T" in data)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def _gcode_M21(self, data: str) -> bool:
|
def _gcode_M21(self, data: str) -> bool:
|
||||||
self._sdCardReady = True
|
|
||||||
self._send("SD card ok")
|
self._send("SD card ok")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
@gcode_executor.register("M23")
|
||||||
def _gcode_M22(self, data: str) -> bool:
|
|
||||||
self._logger.debug("ignoring M22 command.")
|
|
||||||
self._send("M22 disabled for Bambu")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _gcode_M23(self, data: str) -> bool:
|
def _gcode_M23(self, data: str) -> bool:
|
||||||
if self._sdCardReady:
|
filename = data.split(maxsplit=1)[1].strip()
|
||||||
filename = data.split(None, 1)[1].strip()
|
|
||||||
self._selectSdFile(filename)
|
self._selectSdFile(filename)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
@gcode_executor.register("M26")
|
||||||
def _gcode_M24(self, data: str) -> bool:
|
|
||||||
if self._sdCardReady:
|
|
||||||
self._startSdPrint()
|
|
||||||
return True
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def _gcode_M25(self, data: str) -> bool:
|
|
||||||
if self._sdCardReady:
|
|
||||||
self._pauseSdPrint()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _gcode_M524(self, data: str) -> bool:
|
|
||||||
if self._sdCardReady:
|
|
||||||
return self._cancelSdPrint()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _gcode_M26(self, data: str) -> bool:
|
def _gcode_M26(self, data: str) -> bool:
|
||||||
if data == "M26 S0":
|
if data == "M26 S0":
|
||||||
if self._sdCardReady:
|
|
||||||
return self._cancelSdPrint()
|
return self._cancelSdPrint()
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
self._logger.debug("ignoring M26 command.")
|
self._logger.debug("ignoring M26 command.")
|
||||||
self._send("M26 disabled for Bambu")
|
self._send("M26 disabled for Bambu")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@gcode_executor.register("M27")
|
||||||
def _gcode_M27(self, data: str) -> bool:
|
def _gcode_M27(self, data: str) -> bool:
|
||||||
def report():
|
|
||||||
if self._sdCardReady:
|
|
||||||
self._reportSdStatus()
|
|
||||||
|
|
||||||
matchS = re.search(r"S([0-9]+)", data)
|
matchS = re.search(r"S([0-9]+)", data)
|
||||||
if matchS:
|
if matchS:
|
||||||
interval = int(matchS.group(1))
|
interval = int(matchS.group(1))
|
||||||
@ -558,41 +512,26 @@ class BambuVirtualPrinter:
|
|||||||
self._sdstatus_reporter.cancel()
|
self._sdstatus_reporter.cancel()
|
||||||
|
|
||||||
if interval > 0:
|
if interval > 0:
|
||||||
self._sdstatus_reporter = RepeatedTimer(interval, report)
|
self._sdstatus_reporter = RepeatedTimer(interval, self._reportSdStatus)
|
||||||
self._sdstatus_reporter.start()
|
self._sdstatus_reporter.start()
|
||||||
else:
|
else:
|
||||||
self._sdstatus_reporter = None
|
self._sdstatus_reporter = None
|
||||||
|
|
||||||
report()
|
self._reportSdStatus()
|
||||||
return True
|
|
||||||
|
|
||||||
def _gcode_M28(self, data: str) -> bool:
|
|
||||||
self._logger.debug("ignoring M28 command.")
|
|
||||||
self._send("M28 disabled for Bambu")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def _gcode_M29(self, data: str) -> bool:
|
|
||||||
self._logger.debug("ignoring M29 command.")
|
|
||||||
self._send("M29 disabled for Bambu")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@gcode_executor.register("M30")
|
||||||
def _gcode_M30(self, data: str) -> bool:
|
def _gcode_M30(self, data: str) -> bool:
|
||||||
if self._sdCardReady:
|
|
||||||
filename = data.split(None, 1)[1].strip()
|
filename = data.split(None, 1)[1].strip()
|
||||||
self._deleteSdFile(filename)
|
self._deleteSdFile(filename)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _gcode_M33(self, data: str) -> bool:
|
@gcode_executor.register("M105")
|
||||||
self._logger.debug("ignoring M33 command.")
|
|
||||||
self._send("M33 disabled for Bambu")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def _gcode_M105(self, data: str) -> bool:
|
def _gcode_M105(self, data: str) -> bool:
|
||||||
return self._processTemperatureQuery()
|
return self._processTemperatureQuery()
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
|
@gcode_executor.register("M115")
|
||||||
def _gcode_M115(self, data: str) -> bool:
|
def _gcode_M115(self, data: str) -> bool:
|
||||||
self._send("Bambu Printer Integration")
|
self._send("Bambu Printer Integration")
|
||||||
self._send("Cap:EXTENDED_M20:1")
|
self._send("Cap:EXTENDED_M20:1")
|
||||||
@ -600,12 +539,14 @@ class BambuVirtualPrinter:
|
|||||||
self._send("Cap:LFN_WRITE:1")
|
self._send("Cap:LFN_WRITE:1")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@gcode_executor.register("M117")
|
||||||
def _gcode_M117(self, data: str) -> bool:
|
def _gcode_M117(self, data: str) -> bool:
|
||||||
# we'll just use this to echo a message, to allow playing around with pause triggers
|
# we'll just use this to echo a message, to allow playing around with pause triggers
|
||||||
result = re.search(r"M117\s+(.*)", data).group(1)
|
result = re.search(r"M117\s+(.*)", data).group(1)
|
||||||
self._send(f"echo:{result}")
|
self._send(f"echo:{result}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@gcode_executor.register("M118")
|
||||||
def _gcode_M118(self, data: str) -> bool:
|
def _gcode_M118(self, data: str) -> bool:
|
||||||
match = re.search(r"M118 (?:(?P<parameter>A1|E1|Pn[012])\s)?(?P<text>.*)", data)
|
match = re.search(r"M118 (?:(?P<parameter>A1|E1|Pn[012])\s)?(?P<text>.*)", data)
|
||||||
if not match:
|
if not match:
|
||||||
@ -624,6 +565,7 @@ class BambuVirtualPrinter:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
|
@gcode_executor.register("M220")
|
||||||
def _gcode_M220(self, data: str) -> bool:
|
def _gcode_M220(self, data: str) -> bool:
|
||||||
if self.bambu.connected:
|
if self.bambu.connected:
|
||||||
gcode_command = commands.SEND_GCODE_TEMPLATE
|
gcode_command = commands.SEND_GCODE_TEMPLATE
|
||||||
@ -650,10 +592,6 @@ class BambuVirtualPrinter:
|
|||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def _gcode_M400(self, data: str) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_param_letters(letters, data):
|
def _check_param_letters(letters, data):
|
||||||
# Checks if any of the params (letters) are included in data
|
# Checks if any of the params (letters) are included in data
|
||||||
@ -701,7 +639,8 @@ class BambuVirtualPrinter:
|
|||||||
|
|
||||||
request_resend()
|
request_resend()
|
||||||
|
|
||||||
def _listSd(self, incl_long=False, incl_timestamp=False):
|
@gcode_executor.register_no_data("M20")
|
||||||
|
def _listSd(self):
|
||||||
line = '{dosname} {size} {timestamp} "{name}"'
|
line = '{dosname} {size} {timestamp} "{name}"'
|
||||||
|
|
||||||
self._send("Begin file list")
|
self._send("Begin file list")
|
||||||
@ -795,7 +734,7 @@ class BambuVirtualPrinter:
|
|||||||
|
|
||||||
file = self._getSdFileData(filename)
|
file = self._getSdFileData(filename)
|
||||||
if file is None:
|
if file is None:
|
||||||
self._listSd(incl_long=True, incl_timestamp=True)
|
self._listSd()
|
||||||
self._sendOk()
|
self._sendOk()
|
||||||
file = self._getSdFileData(filename)
|
file = self._getSdFileData(filename)
|
||||||
if file is None:
|
if file is None:
|
||||||
@ -810,7 +749,8 @@ class BambuVirtualPrinter:
|
|||||||
self._send(f"File opened: {file['name']} Size: {self._selectedSdFileSize}")
|
self._send(f"File opened: {file['name']} Size: {self._selectedSdFileSize}")
|
||||||
self._send("File selected")
|
self._send("File selected")
|
||||||
|
|
||||||
def _startSdPrint(self, from_printer: bool = False) -> None:
|
@gcode_executor.register_no_data("M24")
|
||||||
|
def _startSdPrint(self, from_printer: bool = False) -> bool:
|
||||||
self._logger.debug(f"_startSdPrint: from_printer={from_printer}")
|
self._logger.debug(f"_startSdPrint: from_printer={from_printer}")
|
||||||
if self._selectedSdFile is not None:
|
if self._selectedSdFile is not None:
|
||||||
if self._sdPrinter is None:
|
if self._sdPrinter is None:
|
||||||
@ -820,16 +760,16 @@ class BambuVirtualPrinter:
|
|||||||
target=self._sdPrintingWorker, kwargs={"from_printer": from_printer}
|
target=self._sdPrintingWorker, kwargs={"from_printer": from_printer}
|
||||||
)
|
)
|
||||||
self._sdPrinter.start()
|
self._sdPrinter.start()
|
||||||
# self._sdPrintingSemaphore.set()
|
|
||||||
if self._sdPrinter is not None:
|
if self._sdPrinter is not None:
|
||||||
if self.bambu.connected:
|
if self.bambu.connected:
|
||||||
if self.bambu.publish(commands.RESUME):
|
if self.bambu.publish(commands.RESUME):
|
||||||
self._logger.info("print resumed")
|
self._logger.info("print resumed")
|
||||||
# if not self._sdPrintingSemaphore.is_set():
|
|
||||||
# self._sdPrintingSemaphore.set()
|
|
||||||
else:
|
else:
|
||||||
self._logger.info("print resume failed")
|
self._logger.info("print resume failed")
|
||||||
|
return True
|
||||||
|
|
||||||
|
@gcode_executor.register_no_data("M25")
|
||||||
def _pauseSdPrint(self):
|
def _pauseSdPrint(self):
|
||||||
if self.bambu.connected:
|
if self.bambu.connected:
|
||||||
if self.bambu.publish(commands.PAUSE):
|
if self.bambu.publish(commands.PAUSE):
|
||||||
@ -837,6 +777,7 @@ class BambuVirtualPrinter:
|
|||||||
else:
|
else:
|
||||||
self._logger.info("print pause failed")
|
self._logger.info("print pause failed")
|
||||||
|
|
||||||
|
@gcode_executor.register("M524")
|
||||||
def _cancelSdPrint(self) -> bool:
|
def _cancelSdPrint(self) -> bool:
|
||||||
if self.bambu.connected:
|
if self.bambu.connected:
|
||||||
if self.bambu.publish(commands.STOP):
|
if self.bambu.publish(commands.STOP):
|
||||||
@ -846,6 +787,7 @@ class BambuVirtualPrinter:
|
|||||||
else:
|
else:
|
||||||
self._logger.info("print cancel failed")
|
self._logger.info("print cancel failed")
|
||||||
return False
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
def _setSdPos(self, pos):
|
def _setSdPos(self, pos):
|
||||||
self._newSdFilePos = pos
|
self._newSdFilePos = pos
|
||||||
@ -1003,20 +945,6 @@ class BambuVirtualPrinter:
|
|||||||
def _setUnbusy(self):
|
def _setUnbusy(self):
|
||||||
self._busy = None
|
self._busy = None
|
||||||
|
|
||||||
# def _processBuffer(self):
|
|
||||||
# while self.buffered is not None:
|
|
||||||
# try:
|
|
||||||
# line = self.buffered.get(timeout=0.5)
|
|
||||||
# except queue.Empty:
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# if line is None:
|
|
||||||
# continue
|
|
||||||
#
|
|
||||||
# self.buffered.task_done()
|
|
||||||
#
|
|
||||||
# self._logger.debug("Closing down buffer loop")
|
|
||||||
|
|
||||||
def _showPrompt(self, text, choices):
|
def _showPrompt(self, text, choices):
|
||||||
self._hidePrompt()
|
self._hidePrompt()
|
||||||
self._send(f"//action:prompt_begin {text}")
|
self._send(f"//action:prompt_begin {text}")
|
||||||
@ -1036,7 +964,7 @@ class BambuVirtualPrinter:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if b"M112" in data:
|
if b"M112" in data:
|
||||||
self._seriallog.debug(f"<<< {u_data}")
|
self._serial_log.debug(f"<<< {u_data}")
|
||||||
self._kill()
|
self._kill()
|
||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
@ -1044,7 +972,7 @@ class BambuVirtualPrinter:
|
|||||||
written = self.incoming.put(
|
written = self.incoming.put(
|
||||||
data, timeout=self._write_timeout, partial=True
|
data, timeout=self._write_timeout, partial=True
|
||||||
)
|
)
|
||||||
self._seriallog.debug(f"<<< {u_data}")
|
self._serial_log.debug(f"<<< {u_data}")
|
||||||
return written
|
return written
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
self._logger.info(
|
self._logger.info(
|
||||||
@ -1053,12 +981,13 @@ class BambuVirtualPrinter:
|
|||||||
raise SerialTimeoutException()
|
raise SerialTimeoutException()
|
||||||
|
|
||||||
def readline(self) -> bytes:
|
def readline(self) -> bytes:
|
||||||
|
assert self.outgoing is not None
|
||||||
timeout = self._read_timeout
|
timeout = self._read_timeout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# fetch a line from the queue, wait no longer than timeout
|
# fetch a line from the queue, wait no longer than timeout
|
||||||
line = to_unicode(self.outgoing.get(timeout=timeout), errors="replace")
|
line = to_unicode(self.outgoing.get(timeout=timeout), errors="replace")
|
||||||
self._seriallog.debug(f">>> {line.strip()}")
|
self._serial_log.debug(f">>> {line.strip()}")
|
||||||
self.outgoing.task_done()
|
self.outgoing.task_done()
|
||||||
return to_bytes(line)
|
return to_bytes(line)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
@ -1076,9 +1005,7 @@ class BambuVirtualPrinter:
|
|||||||
def _sendOk(self):
|
def _sendOk(self):
|
||||||
if self.outgoing is None:
|
if self.outgoing is None:
|
||||||
return
|
return
|
||||||
ok = self._ok()
|
self._send("ok")
|
||||||
if ok:
|
|
||||||
self._send(ok)
|
|
||||||
|
|
||||||
def _isPaused(self):
|
def _isPaused(self):
|
||||||
return self._sdPrintingPausedSemaphore.is_set()
|
return self._sdPrintingPausedSemaphore.is_set()
|
||||||
@ -1100,8 +1027,5 @@ class BambuVirtualPrinter:
|
|||||||
if self.outgoing is not None:
|
if self.outgoing is not None:
|
||||||
self.outgoing.put(line)
|
self.outgoing.put(line)
|
||||||
|
|
||||||
def _ok(self):
|
|
||||||
return "ok"
|
|
||||||
|
|
||||||
def _error(self, error: str, *args, **kwargs) -> str:
|
def _error(self, error: str, *args, **kwargs) -> str:
|
||||||
return f"Error: {self._errors.get(error).format(*args, **kwargs)}"
|
return f"Error: {self._errors.get(error).format(*args, **kwargs)}"
|
||||||
|
314
octoprint_bambu_printer/gcode_executor.py
Normal file
314
octoprint_bambu_printer/gcode_executor.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
from inspect import signature
|
||||||
|
|
||||||
|
|
||||||
|
GCODE_DOCUMENTATION = {
|
||||||
|
"G0": "Linear Move",
|
||||||
|
"G1": "Linear Move",
|
||||||
|
"G2": "Arc or Circle Move",
|
||||||
|
"G3": "Arc or Circle Move",
|
||||||
|
"G4": "Dwell",
|
||||||
|
"G5": "Bézier cubic spline",
|
||||||
|
"G6": "Direct Stepper Move",
|
||||||
|
"G10": "Retract",
|
||||||
|
"G11": "Recover",
|
||||||
|
"G12": "Clean the Nozzle",
|
||||||
|
"G17": "CNC Workspace Planes",
|
||||||
|
"G18": "CNC Workspace Planes",
|
||||||
|
"G19": "CNC Workspace Planes",
|
||||||
|
"G20": "Inch Units",
|
||||||
|
"G21": "Millimeter Units",
|
||||||
|
"G26": "Mesh Validation Pattern",
|
||||||
|
"G27": "Park toolhead",
|
||||||
|
"G28": "Auto Home",
|
||||||
|
"G29": "Bed Leveling",
|
||||||
|
"G29": "Bed Leveling (3-Point)",
|
||||||
|
"G29": "Bed Leveling (Linear)",
|
||||||
|
"G29": "Bed Leveling (Manual)",
|
||||||
|
"G29": "Bed Leveling (Bilinear)",
|
||||||
|
"G29": "Bed Leveling (Unified)",
|
||||||
|
"G30": "Single Z-Probe",
|
||||||
|
"G31": "Dock Sled",
|
||||||
|
"G32": "Undock Sled",
|
||||||
|
"G33": "Delta Auto Calibration",
|
||||||
|
"G34": "Z Steppers Auto-Alignment",
|
||||||
|
"G34": "Mechanical Gantry Calibration",
|
||||||
|
"G35": "Tramming Assistant",
|
||||||
|
"G38.2": "Probe target",
|
||||||
|
"G38.3": "Probe target",
|
||||||
|
"G38.4": "Probe target",
|
||||||
|
"G38.5": "Probe target",
|
||||||
|
"G42": "Move to mesh coordinate",
|
||||||
|
"G53": "Move in Machine Coordinates",
|
||||||
|
"G60": "Save Current Position",
|
||||||
|
"G61": "Return to Saved Position",
|
||||||
|
"G76": "Probe temperature calibration",
|
||||||
|
"G80": "Cancel Current Motion Mode",
|
||||||
|
"G90": "Absolute Positioning",
|
||||||
|
"G91": "Relative Positioning",
|
||||||
|
"G92": "Set Position",
|
||||||
|
"G425": "Backlash Calibration",
|
||||||
|
"M0": "Unconditional stop",
|
||||||
|
"M1": "Unconditional stop",
|
||||||
|
"M3": "Spindle CW / Laser On",
|
||||||
|
"M4": "Spindle CCW / Laser On",
|
||||||
|
"M5": "Spindle / Laser Off",
|
||||||
|
"M7": "Coolant Controls",
|
||||||
|
"M8": "Coolant Controls",
|
||||||
|
"M9": "Coolant Controls",
|
||||||
|
"M10": "Vacuum / Blower Control",
|
||||||
|
"M11": "Vacuum / Blower Control",
|
||||||
|
"M16": "Expected Printer Check",
|
||||||
|
"M17": "Enable Steppers",
|
||||||
|
"M18": "Disable steppers",
|
||||||
|
"M84": "Disable steppers",
|
||||||
|
"M20": "List SD Card",
|
||||||
|
"M21": "Init SD card",
|
||||||
|
"M22": "Release SD card",
|
||||||
|
"M23": "Select SD file",
|
||||||
|
"M24": "Start or Resume SD print",
|
||||||
|
"M25": "Pause SD print",
|
||||||
|
"M26": "Set SD position",
|
||||||
|
"M27": "Report SD print status",
|
||||||
|
"M28": "Start SD write",
|
||||||
|
"M29": "Stop SD write",
|
||||||
|
"M30": "Delete SD file",
|
||||||
|
"M31": "Print time",
|
||||||
|
"M32": "Select and Start",
|
||||||
|
"M33": "Get Long Path",
|
||||||
|
"M34": "SDCard Sorting",
|
||||||
|
"M42": "Set Pin State",
|
||||||
|
"M43": "Debug Pins",
|
||||||
|
"M48": "Probe Repeatability Test",
|
||||||
|
"M73": "Set Print Progress",
|
||||||
|
"M75": "Start Print Job Timer",
|
||||||
|
"M76": "Pause Print Job Timer",
|
||||||
|
"M77": "Stop Print Job Timer",
|
||||||
|
"M78": "Print Job Stats",
|
||||||
|
"M80": "Power On",
|
||||||
|
"M81": "Power Off",
|
||||||
|
"M82": "E Absolute",
|
||||||
|
"M83": "E Relative",
|
||||||
|
"M85": "Inactivity Shutdown",
|
||||||
|
"M86": "Hotend Idle Timeout",
|
||||||
|
"M87": "Disable Hotend Idle Timeout",
|
||||||
|
"M92": "Set Axis Steps-per-unit",
|
||||||
|
"M100": "Free Memory",
|
||||||
|
"M102": "Configure Bed Distance Sensor",
|
||||||
|
"M104": "Set Hotend Temperature",
|
||||||
|
"M105": "Report Temperatures",
|
||||||
|
"M106": "Set Fan Speed",
|
||||||
|
"M107": "Fan Off",
|
||||||
|
"M108": "Break and Continue",
|
||||||
|
"M109": "Wait for Hotend Temperature",
|
||||||
|
"M110": "Set / Get Line Number",
|
||||||
|
"M111": "Debug Level",
|
||||||
|
"M112": "Full Shutdown",
|
||||||
|
"M113": "Host Keepalive",
|
||||||
|
"M114": "Get Current Position",
|
||||||
|
"M115": "Firmware Info",
|
||||||
|
"M117": "Set LCD Message",
|
||||||
|
"M118": "Serial print",
|
||||||
|
"M119": "Endstop States",
|
||||||
|
"M120": "Enable Endstops",
|
||||||
|
"M121": "Disable Endstops",
|
||||||
|
"M122": "TMC Debugging",
|
||||||
|
"M123": "Fan Tachometers",
|
||||||
|
"M125": "Park Head",
|
||||||
|
"M126": "Baricuda 1 Open",
|
||||||
|
"M127": "Baricuda 1 Close",
|
||||||
|
"M128": "Baricuda 2 Open",
|
||||||
|
"M129": "Baricuda 2 Close",
|
||||||
|
"M140": "Set Bed Temperature",
|
||||||
|
"M141": "Set Chamber Temperature",
|
||||||
|
"M143": "Set Laser Cooler Temperature",
|
||||||
|
"M145": "Set Material Preset",
|
||||||
|
"M149": "Set Temperature Units",
|
||||||
|
"M150": "Set RGB(W) Color",
|
||||||
|
"M154": "Position Auto-Report",
|
||||||
|
"M155": "Temperature Auto-Report",
|
||||||
|
"M163": "Set Mix Factor",
|
||||||
|
"M164": "Save Mix",
|
||||||
|
"M165": "Set Mix",
|
||||||
|
"M166": "Gradient Mix",
|
||||||
|
"M190": "Wait for Bed Temperature",
|
||||||
|
"M191": "Wait for Chamber Temperature",
|
||||||
|
"M192": "Wait for Probe temperature",
|
||||||
|
"M193": "Set Laser Cooler Temperature",
|
||||||
|
"M200": "Set Filament Diameter",
|
||||||
|
"M201": "Print / Travel Move Limits",
|
||||||
|
"M203": "Set Max Feedrate",
|
||||||
|
"M204": "Set Starting Acceleration",
|
||||||
|
"M205": "Set Advanced Settings",
|
||||||
|
"M206": "Set Home Offsets",
|
||||||
|
"M207": "Set Firmware Retraction",
|
||||||
|
"M208": "Firmware Recover",
|
||||||
|
"M209": "Set Auto Retract",
|
||||||
|
"M211": "Software Endstops",
|
||||||
|
"M217": "Filament swap parameters",
|
||||||
|
"M218": "Set Hotend Offset",
|
||||||
|
"M220": "Set Feedrate Percentage",
|
||||||
|
"M221": "Set Flow Percentage",
|
||||||
|
"M226": "Wait for Pin State",
|
||||||
|
"M240": "Trigger Camera",
|
||||||
|
"M250": "LCD Contrast",
|
||||||
|
"M255": "LCD Sleep/Backlight Timeout",
|
||||||
|
"M256": "LCD Brightness",
|
||||||
|
"M260": "I2C Send",
|
||||||
|
"M261": "I2C Request",
|
||||||
|
"M280": "Servo Position",
|
||||||
|
"M281": "Edit Servo Angles",
|
||||||
|
"M282": "Detach Servo",
|
||||||
|
"M290": "Babystep",
|
||||||
|
"M300": "Play Tone",
|
||||||
|
"M301": "Set Hotend PID",
|
||||||
|
"M302": "Cold Extrude",
|
||||||
|
"M303": "PID autotune",
|
||||||
|
"M304": "Set Bed PID",
|
||||||
|
"M305": "User Thermistor Parameters",
|
||||||
|
"M306": "Model Predictive Temp. Control",
|
||||||
|
"M350": "Set micro-stepping",
|
||||||
|
"M351": "Set Microstep Pins",
|
||||||
|
"M355": "Case Light Control",
|
||||||
|
"M360": "SCARA Theta A",
|
||||||
|
"M361": "SCARA Theta-B",
|
||||||
|
"M362": "SCARA Psi-A",
|
||||||
|
"M363": "SCARA Psi-B",
|
||||||
|
"M364": "SCARA Psi-C",
|
||||||
|
"M380": "Activate Solenoid",
|
||||||
|
"M381": "Deactivate Solenoids",
|
||||||
|
"M400": "Finish Moves",
|
||||||
|
"M401": "Deploy Probe",
|
||||||
|
"M402": "Stow Probe",
|
||||||
|
"M403": "MMU2 Filament Type",
|
||||||
|
"M404": "Set Filament Diameter",
|
||||||
|
"M405": "Filament Width Sensor On",
|
||||||
|
"M406": "Filament Width Sensor Off",
|
||||||
|
"M407": "Filament Width",
|
||||||
|
"M410": "Quickstop",
|
||||||
|
"M412": "Filament Runout",
|
||||||
|
"M413": "Power-loss Recovery",
|
||||||
|
"M420": "Bed Leveling State",
|
||||||
|
"M421": "Set Mesh Value",
|
||||||
|
"M422": "Set Z Motor XY",
|
||||||
|
"M423": "X Twist Compensation",
|
||||||
|
"M425": "Backlash compensation",
|
||||||
|
"M428": "Home Offsets Here",
|
||||||
|
"M430": "Power Monitor",
|
||||||
|
"M486": "Cancel Objects",
|
||||||
|
"M493": "Fixed-Time Motion",
|
||||||
|
"M500": "Save Settings",
|
||||||
|
"M501": "Restore Settings",
|
||||||
|
"M502": "Factory Reset",
|
||||||
|
"M503": "Report Settings",
|
||||||
|
"M504": "Validate EEPROM contents",
|
||||||
|
"M510": "Lock Machine",
|
||||||
|
"M511": "Unlock Machine",
|
||||||
|
"M512": "Set Passcode",
|
||||||
|
"M524": "Abort SD print",
|
||||||
|
"M540": "Endstops Abort SD",
|
||||||
|
"M569": "Set TMC stepping mode",
|
||||||
|
"M575": "Serial baud rate",
|
||||||
|
"M592": "Nonlinear Extrusion Control",
|
||||||
|
"M593": "ZV Input Shaping",
|
||||||
|
"M600": "Filament Change",
|
||||||
|
"M603": "Configure Filament Change",
|
||||||
|
"M605": "Multi Nozzle Mode",
|
||||||
|
"M665": "Delta Configuration",
|
||||||
|
"M665": "SCARA Configuration",
|
||||||
|
"M666": "Set Delta endstop adjustments",
|
||||||
|
"M666": "Set dual endstop offsets",
|
||||||
|
"M672": "Duet Smart Effector sensitivity",
|
||||||
|
"M701": "Load filament",
|
||||||
|
"M702": "Unload filament",
|
||||||
|
"M710": "Controller Fan settings",
|
||||||
|
"M808": "Repeat Marker",
|
||||||
|
"M851": "XYZ Probe Offset",
|
||||||
|
"M852": "Bed Skew Compensation",
|
||||||
|
"M871": "Probe temperature config",
|
||||||
|
"M876": "Handle Prompt Response",
|
||||||
|
"M900": "Linear Advance Factor",
|
||||||
|
"M906": "Stepper Motor Current",
|
||||||
|
"M907": "Set Motor Current",
|
||||||
|
"M908": "Set Trimpot Pins",
|
||||||
|
"M909": "DAC Print Values",
|
||||||
|
"M910": "Commit DAC to EEPROM",
|
||||||
|
"M911": "TMC OT Pre-Warn Condition",
|
||||||
|
"M912": "Clear TMC OT Pre-Warn",
|
||||||
|
"M913": "Set Hybrid Threshold Speed",
|
||||||
|
"M914": "TMC Bump Sensitivity",
|
||||||
|
"M915": "TMC Z axis calibration",
|
||||||
|
"M916": "L6474 Thermal Warning Test",
|
||||||
|
"M917": "L6474 Overcurrent Warning Test",
|
||||||
|
"M918": "L6474 Speed Warning Test",
|
||||||
|
"M919": "TMC Chopper Timing",
|
||||||
|
"M928": "Start SD Logging",
|
||||||
|
"M951": "Magnetic Parking Extruder",
|
||||||
|
"M993": "Back up flash settings to SD",
|
||||||
|
"M994": "Restore flash from SD",
|
||||||
|
"M995": "Touch Screen Calibration",
|
||||||
|
"M997": "Firmware update",
|
||||||
|
"M999": "STOP Restart",
|
||||||
|
"M7219": "MAX7219 Control",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GCodeExecutor:
|
||||||
|
def __init__(self):
|
||||||
|
self._log = logging.getLogger(
|
||||||
|
"octoprint.plugins.bambu_printer.BambuPrinter.gcode_executor"
|
||||||
|
)
|
||||||
|
self.handler_names = set()
|
||||||
|
self.gcode_handlers = {}
|
||||||
|
self.gcode_handlers_no_data = {}
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self.gcode_handlers or item in self.gcode_handlers_no_data
|
||||||
|
|
||||||
|
def _get_required_args_count(self, func):
|
||||||
|
sig = signature(func)
|
||||||
|
required_count = sum(
|
||||||
|
1
|
||||||
|
for p in sig.parameters.values()
|
||||||
|
if (p.kind == p.POSITIONAL_OR_KEYWORD or p.kind == p.POSITIONAL_ONLY)
|
||||||
|
and p.default == p.empty
|
||||||
|
)
|
||||||
|
return required_count
|
||||||
|
|
||||||
|
def register(self, gcode):
|
||||||
|
def decorator(func):
|
||||||
|
required_count = self._get_required_args_count(func)
|
||||||
|
if required_count == 1:
|
||||||
|
self.gcode_handlers_no_data[gcode] = func
|
||||||
|
elif required_count == 2:
|
||||||
|
self.gcode_handlers[gcode] = func
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Cannot register function with {required_count} required parameters"
|
||||||
|
)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def register_no_data(self, gcode):
|
||||||
|
def decorator(func):
|
||||||
|
self.gcode_handlers_no_data[gcode] = func
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def execute(self, printer, gcode, data):
|
||||||
|
gcode_info = self._gcode_with_info(gcode)
|
||||||
|
if gcode in self.gcode_handlers:
|
||||||
|
self._log.debug(f"Executing {gcode_info}")
|
||||||
|
return self.gcode_handlers[gcode](printer, data)
|
||||||
|
elif gcode in self.gcode_handlers_no_data:
|
||||||
|
self._log.debug(f"Executing {gcode_info}")
|
||||||
|
return self.gcode_handlers_no_data[gcode](printer)
|
||||||
|
else:
|
||||||
|
self._log.debug(f"ignoring {gcode_info} command.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _gcode_with_info(self, gcode):
|
||||||
|
return f"{gcode} ({GCODE_DOCUMENTATION.get(gcode, 'Info not specified')})"
|
Loading…
x
Reference in New Issue
Block a user