* Add separate class for sftp file system
* Add separate serial IO handling class
* Replace function name mangling with gcode handler registration system
* Add states to virtual Bambu printer that manage state specific interaction
* Add synchronization utilities to work with virtual printer as if it is a binary stream
* Add unittests with mocked Bambu printer to ensure core functionality works as expected
* Fix formatting to be automatically processed by black formatter
* Fix python 3.10 type annotations for readability
This commit is contained in:
Anton Skrypnyk
2024-07-30 05:49:12 +03:00
committed by GitHub
parent ad862d5ebd
commit fda4b86cbc
26 changed files with 2723 additions and 1422 deletions

View File

@ -0,0 +1,46 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from octoprint_bambu_printer.printer.bambu_virtual_printer import (
BambuVirtualPrinter,
)
class APrinterState:
def __init__(self, printer: BambuVirtualPrinter) -> None:
self._log = logging.getLogger(
"octoprint.plugins.bambu_printer.BambuPrinter.states"
)
self._printer = printer
def init(self):
pass
def finalize(self):
pass
def handle_gcode(self, gcode):
self._log.debug(f"{self.__class__.__name__} gcode execution disabled")
def update_print_job_info(self):
self._log_skip_state_transition("start_new_print")
def start_new_print(self):
self._log_skip_state_transition("start_new_print")
def pause_print(self):
self._log_skip_state_transition("pause_print")
def cancel_print(self):
self._log_skip_state_transition("cancel_print")
def resume_print(self):
self._log_skip_state_transition("resume_print")
def _log_skip_state_transition(self, method):
self._log.debug(
f"skipping {self.__class__.__name__} state transition for '{method}'"
)

View File

@ -0,0 +1,58 @@
from __future__ import annotations
from pathlib import Path
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
from octoprint_bambu_printer.printer.print_job import PrintJob
from octoprint_bambu_printer.printer.states.a_printer_state import APrinterState
class IdleState(APrinterState):
def start_new_print(self):
selected_file = self._printer.selected_file
if selected_file is None:
self._log.warn("Cannot start print job if file was not selected")
return
print_command = self._get_print_command_for_file(selected_file)
self._log.debug(f"Sending print command: {print_command}")
if self._printer.bambu_client.publish(print_command):
self._log.info(f"Started print for {selected_file.file_name}")
else:
self._log.warn(f"Failed to start print for {selected_file.file_name}")
def _get_print_command_for_file(self, selected_file: FileInfo):
# URL to print. Root path, protocol can vary. E.g., if sd card, "ftp:///myfile.3mf", "ftp:///cache/myotherfile.3mf"
filesystem_root = (
"file:///mnt/sdcard/"
if self._printer._settings.get_boolean(["device_type"]) in ["X1", "X1C"]
else "file:///"
)
print_command = {
"print": {
"sequence_id": 0,
"command": "project_file",
"param": "Metadata/plate_1.gcode",
"md5": "",
"profile_id": "0",
"project_id": "0",
"subtask_id": "0",
"task_id": "0",
"subtask_name": selected_file.file_name,
"url": f"{filesystem_root}{selected_file.path.as_posix()}",
"bed_type": "auto",
"timelapse": self._printer._settings.get_boolean(["timelapse"]),
"bed_leveling": self._printer._settings.get_boolean(["bed_leveling"]),
"flow_cali": self._printer._settings.get_boolean(["flow_cali"]),
"vibration_cali": self._printer._settings.get_boolean(
["vibration_cali"]
),
"layer_inspect": self._printer._settings.get_boolean(["layer_inspect"]),
"use_ams": self._printer._settings.get_boolean(["use_ams"]),
"ams_mapping": "",
}
}
return print_command

View File

@ -0,0 +1,51 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from octoprint_bambu_printer.printer.bambu_virtual_printer import (
BambuVirtualPrinter,
)
import threading
import pybambu.commands
from octoprint.util import RepeatedTimer
from octoprint_bambu_printer.printer.states.a_printer_state import APrinterState
class PausedState(APrinterState):
def __init__(self, printer: BambuVirtualPrinter) -> None:
super().__init__(printer)
self._pausedLock = threading.Event()
self._paused_repeated_report = None
def init(self):
if not self._pausedLock.is_set():
self._pausedLock.set()
self._printer.sendIO("// action:paused")
self._printer.start_continuous_status_report(3)
def finalize(self):
if self._pausedLock.is_set():
self._pausedLock.clear()
if self._paused_repeated_report is not None:
self._paused_repeated_report.join()
self._paused_repeated_report = None
def start_new_print(self):
if self._printer.bambu_client.connected:
if self._printer.bambu_client.publish(pybambu.commands.RESUME):
self._log.info("print resumed")
else:
self._log.info("print resume failed")
def cancel_print(self):
if self._printer.bambu_client.connected:
if self._printer.bambu_client.publish(pybambu.commands.STOP):
self._log.info("print cancelled")
self._printer.finalize_print_job()
else:
self._log.info("print cancel failed")

View File

@ -0,0 +1,92 @@
from __future__ import annotations
import time
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from octoprint_bambu_printer.printer.bambu_virtual_printer import (
BambuVirtualPrinter,
)
import threading
import pybambu
import pybambu.models
import pybambu.commands
from octoprint_bambu_printer.printer.print_job import PrintJob
from octoprint_bambu_printer.printer.states.a_printer_state import APrinterState
class PrintingState(APrinterState):
def __init__(self, printer: BambuVirtualPrinter) -> None:
super().__init__(printer)
self._is_printing = False
self._sd_printing_thread = None
def init(self):
self._is_printing = True
self._printer.remove_project_selection()
self.update_print_job_info()
self._start_worker_thread()
def finalize(self):
if self._sd_printing_thread is not None and self._sd_printing_thread.is_alive():
self._is_printing = False
self._sd_printing_thread.join()
self._sd_printing_thread = None
def _start_worker_thread(self):
if self._sd_printing_thread is None:
self._is_printing = True
self._sd_printing_thread = threading.Thread(target=self._printing_worker)
self._sd_printing_thread.start()
def _printing_worker(self):
while (
self._is_printing
and self._printer.current_print_job is not None
and self._printer.current_print_job.progress < 100
):
self.update_print_job_info()
self._printer.report_print_job_status()
time.sleep(3)
self.update_print_job_info()
if (
self._printer.current_print_job is not None
and self._printer.current_print_job.progress >= 100
):
self._printer.finalize_print_job()
def update_print_job_info(self):
print_job_info = self._printer.bambu_client.get_device().print_job
task_name: str = print_job_info.subtask_name
project_file_info = self._printer.project_files.get_file_by_stem(
task_name, [".gcode", ".3mf"]
)
if project_file_info is None:
self._log.debug(f"No 3mf file found for {print_job_info}")
self._current_print_job = None
self._printer.change_state(self._printer._state_idle)
return
progress = print_job_info.print_percentage
self._printer.current_print_job = PrintJob(project_file_info, progress)
self._printer.select_project_file(project_file_info.path.as_posix())
def pause_print(self):
if self._printer.bambu_client.connected:
if self._printer.bambu_client.publish(pybambu.commands.PAUSE):
self._log.info("print paused")
else:
self._log.info("print pause failed")
def cancel_print(self):
if self._printer.bambu_client.connected:
if self._printer.bambu_client.publish(pybambu.commands.STOP):
self._log.info("print cancelled")
self._printer.finalize_print_job()
else:
self._log.info("print cancel failed")