WIP Refactor gcode execution

This commit is contained in:
Anton Skrypnyk 2024-07-24 17:15:45 +03:00
parent 527ec9ef3c
commit 4da769da49
3 changed files with 378 additions and 140 deletions

View File

@ -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": "",

View File

@ -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)}"

View 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')})"