Compare commits

...

8 Commits

Author SHA1 Message Date
3d0cc26147 0.0.12
fix issue with last PR that broke the ability to recognize currently printing file.
2024-02-12 21:35:09 -05:00
ff58636e41 0.0.11
support cache folder listing for P1 devices
add gcode command support
2024-02-12 19:14:10 -05:00
f54ab5c29f Merge pull request #5 from Pavulon87/use-cache-folder
Allow use of cache folder and custom gcode commands
2024-02-12 18:33:27 -05:00
7a4439c53e allow use of cache folder and custom g-gcode commands 2024-02-12 16:17:17 +01:00
9eb8b0da65 0.0.10
fix cancel command, #4
2024-02-12 00:09:04 -05:00
ef969d3d3b 0.0.9
fix upload_file and delete_file to return boolean as it did before switching the ftps client module for A1/P1 devices
2024-02-11 15:40:28 -05:00
3d92d73879 0.0.8
fix delete command
2024-02-10 17:56:50 -05:00
41dad23c49 pin paho-mqtt to versions less than 2 2024-02-10 13:33:09 -05:00
3 changed files with 99 additions and 48 deletions

View File

@ -125,7 +125,7 @@ class IoTFTPSClient:
with open(dest, "wb") as file: with open(dest, "wb") as file:
self.ftps_session.retrbinary(f"RETR {source}", file.write) self.ftps_session.retrbinary(f"RETR {source}", file.write)
def upload_file(self, source: str, dest: str, callback=None): def upload_file(self, source: str, dest: str, callback=None) -> bool:
"""upload a file to a path inside the FTPS server""" """upload a file to a path inside the FTPS server"""
file_size = os.path.getsize(source) file_size = os.path.getsize(source)
@ -133,6 +133,7 @@ class IoTFTPSClient:
block_size = max(file_size // 100, 8192) block_size = max(file_size // 100, 8192)
rest = None rest = None
try:
# Taken from ftplib.storbinary but with custom ssl handling # Taken from ftplib.storbinary but with custom ssl handling
# due to the shitty bambu p1p ftps server TODO fix properly. # due to the shitty bambu p1p ftps server TODO fix properly.
with open(source, "rb") as fp: with open(source, "rb") as fp:
@ -161,15 +162,21 @@ class IoTFTPSClient:
else: else:
conn.shutdown(socket.SHUT_RDWR) conn.shutdown(socket.SHUT_RDWR)
return self.ftps_session.voidresp() return True
except Exception as ex:
print(f"unexpected exception occurred: {ex}")
pass
return False
# Old api call. def delete_file(self, path: str) -> bool:
# self.ftps_session.storbinary(
# f"STOR {dest}", file, blocksize=block_size, callback=callback)
def delete_file(self, path: str):
"""delete a file from under a path inside the FTPS server""" """delete a file from under a path inside the FTPS server"""
try:
self.ftps_session.delete(path) self.ftps_session.delete(path)
return True
except Exception as ex:
print(f"unexpected exception occurred: {ex}")
pass
return False
def move_file(self, source: str, dest: str): def move_file(self, source: str, dest: str):
"""move a file inside the FTPS server to another path inside the FTPS server""" """move a file inside the FTPS server to another path inside the FTPS server"""

View File

@ -68,6 +68,7 @@ class BambuPrinter:
self._sdCardReady = True self._sdCardReady = True
self._sdPrinter = None self._sdPrinter = None
self._sdPrinting = False self._sdPrinting = False
self._sdPrintStarting = False
self._sdPrintingSemaphore = threading.Event() self._sdPrintingSemaphore = threading.Event()
self._sdPrintingPausedSemaphore = threading.Event() self._sdPrintingPausedSemaphore = threading.Event()
self._selectedSdFile = None self._selectedSdFile = None
@ -163,14 +164,14 @@ class BambuPrinter:
self.bedTargetTemp = temperatures.get("target_bed_temp", 0.0) self.bedTargetTemp = temperatures.get("target_bed_temp", 0.0)
self.chamberTemp = temperatures.get("chamber_temp", 0.0) self.chamberTemp = temperatures.get("chamber_temp", 0.0)
if print_job.get("gcode_state") == "RUNNING": if print_job.get("gcode_state") == "RUNNING" or print_job.get("gcode_state") == "PREPARE":
if not self._sdPrintingSemaphore.is_set(): if not self._sdPrintingSemaphore.is_set():
self._sdPrintingSemaphore.set() self._sdPrintingSemaphore.set()
if self._sdPrintingPausedSemaphore.is_set(): if self._sdPrintingPausedSemaphore.is_set():
self._sdPrintingPausedSemaphore.clear() self._sdPrintingPausedSemaphore.clear()
self._sdPrintStarting = False
if not self._sdPrinting: if not self._sdPrinting:
filename = print_job.get("subtask_name") filename = print_job.get("subtask_name")
# TODO: swap this out to use 8 dot 3 name based on long name/path
self._selectSdFile(filename) self._selectSdFile(filename)
self._startSdPrint(from_printer=True) self._startSdPrint(from_printer=True)
@ -185,7 +186,10 @@ class BambuPrinter:
self._send("// action:paused") self._send("// action:paused")
self._sendPaused() self._sendPaused()
if print_job.get("gcode_state") == "FINISH" and self._sdPrintingSemaphore.is_set(): if ( print_job.get("gcode_state") == "FINISH" or print_job.get("gcode_state") == "FAILED" ):
if self._sdPrintStarting is False:
self._sdPrinting = False
if self._sdPrintingSemaphore.is_set():
self._selectedSdFilePos = self._selectedSdFileSize self._selectedSdFilePos = self._selectedSdFileSize
self._finishSdPrint() self._finishSdPrint()
def _create_connection(self): def _create_connection(self):
@ -241,6 +245,7 @@ class BambuPrinter:
self._sdCardReady = True self._sdCardReady = True
self._sdPrinting = False self._sdPrinting = False
self._sdPrintStarting = False
if self._sdPrinter: if self._sdPrinter:
self._sdPrinting = False self._sdPrinting = False
self._sdPrintingSemaphore.clear() self._sdPrintingSemaphore.clear()
@ -429,6 +434,14 @@ class BambuPrinter:
else: else:
self._sendOk() self._sendOk()
if self.bambu.connected:
GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
GCODE_COMMAND['print']['param'] = data + "\n"
if self.bambu.publish(GCODE_COMMAND):
self._logger.info("command sent successfully")
self._sendOk()
continue
finally: finally:
self._logger.debug(f"{data}") self._logger.debug(f"{data}")
@ -474,10 +487,15 @@ class BambuPrinter:
def _gcode_M524(self, data: str) -> bool: def _gcode_M524(self, data: str) -> bool:
if self._sdCardReady: if self._sdCardReady:
self._cancelSdPrint() return self._cancelSdPrint()
return False return False
def _gcode_M26(self, data: str) -> bool: def _gcode_M26(self, data: str) -> bool:
if data == "M26 S0":
if self._sdCardReady:
return self._cancelSdPrint()
return False
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
@ -509,8 +527,8 @@ class BambuPrinter:
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def _gcode_M29(self, data: str) -> bool: def _gcode_M29(self, data: str) -> bool:
self._logger.debug("ignoring M28 command.") self._logger.debug("ignoring M29 command.")
self._send("M28 disabled for Bambu") self._send("M29 disabled for Bambu")
return True return True
def _gcode_M30(self, data: str) -> bool: def _gcode_M30(self, data: str) -> bool:
@ -625,7 +643,7 @@ class BambuPrinter:
access_code = self._settings.get(["access_code"]) access_code = self._settings.get(["access_code"])
ftp = IoTFTPSClient(f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True) ftp = IoTFTPSClient(f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True)
filelist = ftp.list_files("", ".3mf") filelist = ftp.list_files("", ".3mf") or []
for entry in filelist: for entry in filelist:
if entry.startswith("/"): if entry.startswith("/"):
@ -643,8 +661,29 @@ class BambuPrinter:
"size": filesize, "size": filesize,
"timestamp": unix_timestamp_to_m20_timestamp(int(filedate)) "timestamp": unix_timestamp_to_m20_timestamp(int(filedate))
} }
result[filename.lower()] = data
result[dosname.lower()] = filename.lower() result[dosname.lower()] = filename.lower()
result[filename.lower()] = data
filelistcache = ftp.list_files("cache/", ".3mf") or []
for entry in filelistcache:
if entry.startswith("/"):
filename = entry[1:]
else:
filename = entry
filesize = ftp.ftps_session.size("cache/"+entry)
date_str = ftp.ftps_session.sendcmd(f"MDTM cache/{entry}").replace("213 ", "")
filedate = datetime.datetime.strptime(date_str, "%Y%m%d%H%M%S").replace(tzinfo=datetime.timezone.utc).timestamp()
dosname = get_dos_filename(filename, existing_filenames=list(result.keys())).lower()
data = {
"dosname": dosname,
"name": filename,
"path": "cache/"+filename,
"size": filesize,
"timestamp": unix_timestamp_to_m20_timestamp(int(filedate))
}
result[dosname.lower()] = filename.lower()
result[filename.lower()] = data
return result return result
@ -680,6 +719,7 @@ class BambuPrinter:
if self._selectedSdFile is not None: if self._selectedSdFile is not None:
if self._sdPrinter is None: if self._sdPrinter is None:
self._sdPrinting = True self._sdPrinting = True
self._sdPrintStarting = True
self._sdPrinter = threading.Thread(target=self._sdPrintingWorker, kwargs={"from_printer": from_printer}) self._sdPrinter = threading.Thread(target=self._sdPrintingWorker, kwargs={"from_printer": from_printer})
self._sdPrinter.start() self._sdPrinter.start()
# self._sdPrintingSemaphore.set() # self._sdPrintingSemaphore.set()
@ -699,18 +739,21 @@ class BambuPrinter:
else: else:
self._logger.info("print pause failed") self._logger.info("print pause failed")
def _cancelSdPrint(self): def _cancelSdPrint(self) -> bool:
if self.bambu.connected: if self.bambu.connected:
if self.bambu.publish(commands.STOP): if self.bambu.publish(commands.STOP):
self._logger.info("print cancelled") self._logger.info("print cancelled")
self._finishSdPrint()
return True
else: else:
self._logger.info("print cancel failed") self._logger.info("print cancel failed")
return False
def _setSdPos(self, pos): def _setSdPos(self, pos):
self._newSdFilePos = pos self._newSdFilePos = pos
def _reportSdStatus(self): def _reportSdStatus(self):
if self._sdPrinter is not None and (self._sdPrintingSemaphore.is_set() or self._sdPrintingPausedSemaphore.is_set()): if ( self._sdPrinter is not None or self._sdPrintStarting is True ) and self._selectedSdFileSize > 0:
self._send(f"SD printing byte {self._selectedSdFilePos}/{self._selectedSdFileSize}") self._send(f"SD printing byte {self._selectedSdFilePos}/{self._selectedSdFileSize}")
else: else:
self._send("Not SD printing") self._send("Not SD printing")
@ -799,6 +842,7 @@ class BambuPrinter:
self._selectedSdFilePos = 0 self._selectedSdFilePos = 0
self._selectedSdFileSize = 0 self._selectedSdFileSize = 0
self._sdPrinting = False self._sdPrinting = False
self._sdPrintStarting = False
self._sdPrinter = None self._sdPrinter = None
def _deleteSdFile(self, filename: str) -> None: def _deleteSdFile(self, filename: str) -> None:
@ -811,7 +855,7 @@ class BambuPrinter:
if file is not None: if file is not None:
ftp = IoTFTPSClient(f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True) ftp = IoTFTPSClient(f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True)
try: try:
if ftp.delete_file(filename): if ftp.delete_file(file["path"]):
self._logger.debug(f"{filename} deleted") self._logger.debug(f"{filename} deleted")
else: else:
raise Exception("delete failed") raise Exception("delete failed")

View File

@ -14,7 +14,7 @@ plugin_package = "octoprint_bambu_printer"
plugin_name = "OctoPrint-BambuPrinter" plugin_name = "OctoPrint-BambuPrinter"
# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module # The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "0.0.6" plugin_version = "0.0.12"
# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin # The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module # module
@ -33,7 +33,7 @@ plugin_url = "https://github.com/jneilliii/OctoPrint-BambuPrinter"
plugin_license = "AGPLv3" plugin_license = "AGPLv3"
# Any additional requirements besides OctoPrint should be listed here # Any additional requirements besides OctoPrint should be listed here
plugin_requires = ["paho-mqtt", "python-dateutil", "pybambu>=1.0.1"] plugin_requires = ["paho-mqtt<2", "python-dateutil", "pybambu>=1.0.1"]
### -------------------------------------------------------------------------------------------------------------------- ### --------------------------------------------------------------------------------------------------------------------
### More advanced options that you usually shouldn't have to touch follow after this point ### More advanced options that you usually shouldn't have to touch follow after this point