Fix response messages. Fix filesystem name transformations.

This commit is contained in:
Anton Skrypnyk 2024-07-24 17:15:47 +03:00
parent 19cac21db6
commit 42ba306e4f
8 changed files with 89 additions and 66 deletions

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import, annotations
import os
from pathlib import Path
import threading
import time
import flask
@ -195,17 +196,14 @@ class BambuPrintPlugin(
f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True
)
if self._settings.get(["device_type"]) in ["X1", "X1C"]:
timelapse_file_list = ftp.list_files("timelapse/", ".mp4") or []
timelapse_file_list = ftp.list_files("timelapse/", ".mp4")
else:
timelapse_file_list = ftp.list_files("timelapse/", ".avi") or []
timelapse_file_list = ftp.list_files("timelapse/", ".avi")
for entry in timelapse_file_list:
if entry.startswith("/"):
filename = entry[1:].replace("timelapse/", "")
else:
filename = entry.replace("timelapse/", "")
filesize = ftp.ftps_session.size(f"timelapse/{filename}")
filename = entry.name
filesize = ftp.ftps_session.size(entry.as_posix())
date_str = ftp.ftps_session.sendcmd(
f"MDTM timelapse/{filename}"
f"MDTM {entry.as_posix()}"
).replace("213 ", "")
filedate = (
datetime.datetime.strptime(date_str, "%Y%m%d%H%M%S")

View File

@ -67,7 +67,7 @@ class BambuVirtualPrinter:
self._running = True
self._printer_thread = threading.Thread(
target=self._printer_worker,
name="octoprint.plugins.bambu_printer.printer_worker",
name="octoprint.plugins.bambu_printer.printer_state",
)
self._state_change_queue = queue.Queue()
@ -229,7 +229,7 @@ class BambuVirtualPrinter:
bambu_client.on_connect = self.on_connect(bambu_client.on_connect)
bambu_client.connect(callback=self.new_update)
self._log.info(f"bambu connection status: {bambu_client.connected}")
self._serial_io.sendOk()
self.sendOk()
self._bambu_client = bambu_client
def __str__(self):
@ -304,11 +304,21 @@ class BambuVirtualPrinter:
##~~ command implementations
@gcode_executor.register_no_data("M21")
def _sd_status(self) -> None:
self.sendIO("SD card ok")
@gcode_executor.register("M23")
def _select_sd_file(self, data: str) -> bool:
filename = data.split(maxsplit=1)[1].strip()
self._list_sd()
return self.file_system.select_file(filename)
if self.file_system.select_file(filename):
assert self.file_system.selected_file is not None
self.sendIO(
f"File opened: {self.file_system.selected_file.file_name} "
f"Size: {self.file_system.selected_file.size}"
)
self.sendIO("File selected")
@gcode_executor.register("M26")
def _set_sd_position(self, data: str) -> bool:
@ -335,6 +345,7 @@ class BambuVirtualPrinter:
else:
self._sdstatus_reporter = None
self.update_print_job_info()
self.report_print_job_status()
return True
@ -350,8 +361,8 @@ class BambuVirtualPrinter:
return self._processTemperatureQuery()
# noinspection PyUnusedLocal
@gcode_executor.register("M115")
def _report_firmware_info(self, data: str) -> bool:
@gcode_executor.register_no_data("M115")
def _report_firmware_info(self) -> bool:
self.sendIO("Bambu Printer Integration")
self.sendIO("Cap:EXTENDED_M20:1")
self.sendIO("Cap:LFN_WRITE:1")
@ -412,7 +423,7 @@ class BambuVirtualPrinter:
self._log.debug(f"processing gcode {gcode} command = {full_command}")
handled = self.gcode_executor.execute(self, gcode, full_command)
if handled:
self._serial_io.sendOk()
self.sendOk()
return
# post gcode to printer otherwise
@ -421,7 +432,7 @@ class BambuVirtualPrinter:
GCODE_COMMAND["print"]["param"] = full_command + "\n"
if self.bambu_client.publish(GCODE_COMMAND):
self._log.info("command sent successfully")
self._serial_io.sendOk()
self.sendOk()
@gcode_executor.register_no_data("M112")
def _shutdown(self):
@ -432,8 +443,8 @@ class BambuVirtualPrinter:
self._serial_io.close()
return True
@gcode_executor.register_no_data("M20")
def _list_sd(self):
@gcode_executor.register("M20")
def _list_sd(self, data: str = ""):
self.sendIO("Begin file list")
for item in map(lambda f: f.get_log_info(), self.file_system.get_all_files()):
self.sendIO(item)
@ -464,7 +475,7 @@ class BambuVirtualPrinter:
else:
self.sendIO("Not SD printing")
def _generateTemperatureOutput(self) -> str:
def _create_temperature_message(self) -> str:
template = "{heater}:{actual:.2f}/ {target:.2f}"
temps = collections.OrderedDict()
temps["T"] = (self._telemetry.temp[0], self._telemetry.targetTemp[0])
@ -487,7 +498,7 @@ class BambuVirtualPrinter:
def _processTemperatureQuery(self) -> bool:
# includeOk = not self._okBeforeCommandOutput
if self.bambu_client.connected:
output = self._generateTemperatureOutput()
output = self._create_temperature_message()
self.sendIO(output)
return True
else:

View File

@ -26,6 +26,7 @@ wrapper for FTPS server interactions
import ftplib
import os
from pathlib import Path
import socket
import ssl
from typing import Optional, Union, List
@ -34,6 +35,7 @@ from contextlib import redirect_stdout
import io
import re
class ImplicitTLS(ftplib.FTP_TLS):
"""ftplib.FTP_TLS sub-class to support implicit SSL FTPS"""
@ -57,9 +59,9 @@ class ImplicitTLS(ftplib.FTP_TLS):
conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
if self._prot_p:
conn = self.context.wrap_socket(conn,
server_hostname=self.host,
session=self.sock.session) # this is the fix
conn = self.context.wrap_socket(
conn, server_hostname=self.host, session=self.sock.session
) # this is the fix
return conn, size
@ -106,7 +108,8 @@ class IoTFTPSClient:
self.ftps_session.set_debuglevel(0)
self.welcome = self.ftps_session.connect(
host=self.ftps_host, port=self.ftps_port)
host=self.ftps_host, port=self.ftps_port
)
if self.ftps_user and self.ftps_pass:
self.ftps_session.login(user=self.ftps_user, passwd=self.ftps_pass)
@ -137,7 +140,7 @@ class IoTFTPSClient:
# Taken from ftplib.storbinary but with custom ssl handling
# due to the shitty bambu p1p ftps server TODO fix properly.
with open(source, "rb") as fp:
self.ftps_session.voidcmd('TYPE I')
self.ftps_session.voidcmd("TYPE I")
with self.ftps_session.transfercmd(f"STOR {dest}", rest) as conn:
while 1:
@ -152,7 +155,9 @@ class IoTFTPSClient:
callback(buf)
# shutdown ssl layer
if ftplib._SSLSocket is not None and isinstance(conn, ftplib._SSLSocket):
if ftplib._SSLSocket is not None and isinstance(
conn, ftplib._SSLSocket
):
# Yeah this is suposed to be conn.unwrap
# But since we operate in prot p mode
# we can close the connection always.
@ -185,19 +190,24 @@ class IoTFTPSClient:
def mkdir(self, path: str) -> str:
return self.ftps_session.mkd(path)
def list_files(self, path: str, file_pattern: Optional[str] = None) -> Union[List[str], None]:
def list_files(self, list_path: str, extensions: str | list[str] | None = None):
"""list files under a path inside the FTPS server"""
if extensions is None:
_extension_acceptable = lambda p: True
else:
if isinstance(extensions, str):
extensions = [extensions]
_extension_acceptable = lambda p: any(s in p.suffixes for s in extensions)
try:
files = self.ftps_session.nlst(path)
if not files:
return
if file_pattern:
return [f for f in files if file_pattern in f]
return files
list_result = self.ftps_session.nlst(list_path) or []
for file_name in list_result:
path = Path(list_path) / file_name
if _extension_acceptable(path):
yield path
except Exception as ex:
print(f"unexpected exception occurred: {ex}")
pass
return
def list_files_ex(self, path: str) -> Union[list[str], None]:
"""list files under a path inside the FTPS server"""
@ -208,7 +218,8 @@ class IoTFTPSClient:
s = f.getvalue()
files = []
for row in s.split("\n"):
if len(row) <= 0: continue
if len(row) <= 0:
continue
attribs = row.split(" ")

View File

@ -17,4 +17,4 @@ class PrintJob:
@progress.setter
def progress(self, value):
self.file_position = int(self.file_info.size * ((value + 1) / 100))
self.file_position = int(self.file_info.size * value / 100)

View File

@ -27,7 +27,7 @@ class PrinterSerialIO(threading.Thread, BufferedIOBase):
write_timeout=10.0,
) -> None:
super().__init__(
name="octoprint.plugins.bambu_printer.serial_io_thread", daemon=True
name="octoprint.plugins.bambu_printer.printer_worker", daemon=True
)
self._handle_command_callback = handle_command_callback
self._settings = settings
@ -115,8 +115,8 @@ class PrinterSerialIO(threading.Thread, BufferedIOBase):
return 0
try:
self.input_bytes.put(data, timeout=self._write_timeout)
self._log.debug(f"<<< {u_data}")
self.input_bytes.put(data, timeout=self._write_timeout)
return len(data)
except queue.Full:
self._log.error(

View File

@ -77,18 +77,14 @@ class RemoteSDCardFileList:
yield file_info
existing_files.append(file_info.file_name)
existing_files.append(file_info.dosname)
def _get_existing_files_info(self):
ftp = self._connect_ftps_server()
file_list = []
file_list.extend(ftp.list_files("", ".3mf") or [])
file_list.extend(
[
(Path("cache/") / f).as_posix()
for f in (ftp.list_files("cache/", ".3mf") or [])
]
)
file_list.extend(ftp.list_files("", ".3mf"))
file_list.extend(ftp.list_files("cache/", ".3mf"))
existing_files = []
return list(self._scan_ftp_file_list(ftp, file_list, existing_files))
@ -112,7 +108,7 @@ class RemoteSDCardFileList:
def get_all_files(self):
self._update_existing_files_info()
self._logger.debug(f"_getSdFiles return: {self._file_data_cache}")
self._logger.debug(f"get_all_files return: {self._file_data_cache}")
return list(self._file_data_cache.values())
def _update_existing_files_info(self):
@ -124,7 +120,9 @@ class RemoteSDCardFileList:
def _get_cached_data_by_suffix(self, file_stem: str, allowed_suffixes: list[str]):
for suffix in allowed_suffixes:
file_data = self._get_cached_file_data(f"{file_stem}{suffix}")
file_data = self._get_cached_file_data(
Path(file_stem).with_suffix(suffix).as_posix()
)
if file_data is not None:
return file_data
return None
@ -154,9 +152,6 @@ class RemoteSDCardFileList:
return True
self._selected_file_info = file_info
self._logger.info(
f"File opened: {self._selected_file_info.file_name} Size: {self._selected_file_info.size}"
)
return True
def delete_file(self, file_path: str) -> None:

View File

@ -36,7 +36,6 @@ class PrintingState(APrinterState):
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()
@ -82,7 +81,8 @@ class PrintingState(APrinterState):
if self._printer.bambu_client.connected:
if self._printer.bambu_client.publish(pybambu.commands.STOP):
self._log.info("print cancelled")
self._printer.change_state(self._printer._state_finished)
self._finish_print()
self._printer.change_state(self._printer._state_idle)
else:
self._log.info("print cancel failed")
@ -91,5 +91,6 @@ class PrintingState(APrinterState):
self._log.debug(
f"SD File Print finishing: {self._printer.current_print_job.file_info.file_name}"
)
self._printer.sendIO("Done printing file")
self._printer.change_state(self._printer._state_idle)

View File

@ -107,7 +107,10 @@ def ftps_session_mock(files_info_ftp):
filter(lambda f: Path(f).parent == Path("."), all_files)
),
("cache/", ".3mf"): list(
filter(lambda f: Path(f).parent == Path("cache/"), all_files)
map(
lambda f: Path(f).name,
filter(lambda f: Path(f).parent == Path("cache/"), all_files),
)
),
}
)
@ -164,7 +167,7 @@ def printer(
pass
BambuVirtualPrinter._create_client_connection_async = _mock_connection
serial_obj = BambuVirtualPrinter(
printer_test = BambuVirtualPrinter(
settings,
profile_manager,
data_folder=output_test_folder,
@ -172,9 +175,11 @@ def printer(
read_timeout=0.01,
faked_baudrate=115200,
)
serial_obj._bambu_client = bambu_client_mock
yield serial_obj
serial_obj.close()
printer_test._bambu_client = bambu_client_mock
printer_test.flush()
printer_test.readlines()
yield printer_test
printer_test.close()
def test_initial_state(printer: BambuVirtualPrinter):
@ -206,7 +211,8 @@ def test_non_existing_file_not_selected(printer: BambuVirtualPrinter):
printer.write(b"M23 non_existing.3mf\n")
printer.flush()
result = printer.readlines()
assert result[0] == b"ok"
assert result[-2] != b"File selected"
assert result[-1] == b"ok"
assert printer.file_system.selected_file is None
@ -220,7 +226,8 @@ def test_print_started_with_selected_file(printer: BambuVirtualPrinter, print_jo
printer.write(b"M23 print.3mf\n")
printer.flush()
result = printer.readlines()
assert result[0] == b"ok"
assert result[-2] == b"File selected"
assert result[-1] == b"ok"
assert printer.file_system.selected_file is not None
assert printer.file_system.selected_file.file_name == "print.3mf"