Füge Mock-FTPS-Client-Implementierung hinzu, um FTP-Zugriffe zu simulieren; erweitere Fehlerbehandlung und aktualisiere die Dateiliste mit Mock-Dateien.

This commit is contained in:
Manuel Weiser 2025-03-02 11:58:52 +01:00
parent f5e6b3d0dd
commit ba43df279d
3 changed files with 166 additions and 137 deletions

View File

@ -304,14 +304,18 @@ class BambuVirtualPrinter:
print_data = payload['print']
self._log.info(f"Processing print data with keys: {list(print_data.keys())}")
# Temperaturdaten verarbeiten
if 'temperature' in print_data:
self._process_temperature_data(print_data['temperature'])
# Temperaturdaten direkt verarbeiten, ohne auf "temperature" als Schlüssel zu warten
# Bambu-Drucker verwenden Schlüssel wie "nozzle_temper" direkt im print-Objekt
self._process_direct_temperature_data(print_data)
# Status verarbeiten
if 'gcode_state' in print_data:
self._process_print_state(print_data['gcode_state'])
# Fortschritt verarbeiten
if 'mc_percent' in print_data:
self._process_progress_data(print_data)
# Trigger update
self.new_update("event_printer_data_update")
@ -327,79 +331,79 @@ class BambuVirtualPrinter:
except Exception as e:
self._log.error(f"Error processing MQTT payload: {e}", exc_info=True)
def _process_temperature_data(self, temp_data):
"""Verarbeitet Temperaturdaten aus MQTT-Nachrichten"""
def _process_direct_temperature_data(self, print_data):
"""Verarbeitet Temperaturdaten direkt aus dem print-Objekt"""
try:
# Extruder Temperatur
if 'nozzle_temp' in temp_data:
self._telemetry.temp[0] = float(temp_data['nozzle_temp'])
if 'target_nozzle_temp' in temp_data:
self._telemetry.targetTemp[0] = float(temp_data['target_nozzle_temp'])
# Extruder Temperatur - direkt aus den Feldern
if 'nozzle_temper' in print_data:
self._telemetry.temp[0] = float(print_data['nozzle_temper'])
self._log.debug(f"Updated nozzle temperature: {self._telemetry.temp[0]}")
if 'nozzle_target_temper' in print_data:
self._telemetry.targetTemp[0] = float(print_data['nozzle_target_temper'])
self._log.debug(f"Updated nozzle target: {self._telemetry.targetTemp[0]}")
# Bett Temperatur
if 'bed_temp' in temp_data:
self._telemetry.bedTemp = float(temp_data['bed_temp'])
if 'target_bed_temp' in temp_data:
self._telemetry.bedTargetTemp = float(temp_data['target_bed_temp'])
if 'bed_temper' in print_data:
self._telemetry.bedTemp = float(print_data['bed_temper'])
self._log.debug(f"Updated bed temperature: {self._telemetry.bedTemp}")
if 'bed_target_temper' in print_data:
self._telemetry.bedTargetTemp = float(print_data['bed_target_temper'])
self._log.debug(f"Updated bed target: {self._telemetry.bedTargetTemp}")
# Kammer Temperatur
if 'chamber_temp' in temp_data:
self._telemetry.chamberTemp = float(temp_data['chamber_temp'])
if 'chamber_temper' in print_data:
self._telemetry.chamberTemp = float(print_data['chamber_temper'])
self._log.debug(f"Updated chamber temperature: {self._telemetry.chamberTemp}")
self._log.debug(f"Updated temperatures - Nozzle: {self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
# Log der aktualisierten Temperaturen
self._log.debug(f"Current temperatures - Nozzle: {self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
f"Bed: {self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}, " +
f"Chamber: {self._telemetry.chamberTemp}")
# Auch im BambuClient aktualisieren
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'temperature'):
try:
temp_obj = self._bambu_client.device.temperature
if 'nozzle_temper' in print_data:
temp_obj.nozzle_temp = float(print_data['nozzle_temper'])
if 'nozzle_target_temper' in print_data:
temp_obj.target_nozzle_temp = float(print_data['nozzle_target_temper'])
if 'bed_temper' in print_data:
temp_obj.bed_temp = float(print_data['bed_temper'])
if 'bed_target_temper' in print_data:
temp_obj.target_bed_temp = float(print_data['bed_target_temper'])
if 'chamber_temper' in print_data:
temp_obj.chamber_temp = float(print_data['chamber_temper'])
except Exception as e:
self._log.error(f"Error updating BambuClient temperature: {e}")
except Exception as e:
self._log.error(f"Error processing temperature data: {e}", exc_info=True)
def _process_print_state(self, print_job_state):
"""Verarbeitet den Druckerstatus aus MQTT-Nachrichten"""
def _process_progress_data(self, print_data):
"""Verarbeitet Fortschrittsdaten aus MQTT-Nachrichten"""
try:
self._log.debug(f"Received printer state update: {print_job_state}")
# Normalisieren des Status, falls er 'unknown' ist
if print_job_state == "unknown":
# Wenn der Status unbekannt ist, versuchen wir, ihn aus anderen Informationen abzuleiten
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
# Prüfe ob Druckfortschritt vorhanden ist
if hasattr(self._bambu_client.device.print_job, 'mc_percent') and self._bambu_client.device.print_job.mc_percent > 0:
print_job_state = "RUNNING"
self._log.debug(f"Changed unknown state to RUNNING based on print progress")
if print_job_state in ["IDLE", "FINISH", "FAILED"]:
self.change_state(self._state_idle)
elif print_job_state in ["RUNNING", "PREPARE"]:
self.change_state(self._state_printing)
elif print_job_state == "PAUSE":
self.change_state(self._state_paused)
elif print_job_state == "unknown":
# Wenn wir keine bessere Information haben, betrachten wir es als IDLE
self._log.debug("Keeping current state due to unknown printer state")
else:
self._log.warn(f"Unknown print job state: {print_job_state}")
progress = -1
if 'mc_percent' in print_data:
progress = int(print_data['mc_percent'])
remaining_time = 0
if 'mc_remaining_time' in print_data:
remaining_time = int(print_data['mc_remaining_time'])
# Aktualisiere den PrintJob, wenn einer existiert
if self.current_print_job is not None:
self.current_print_job.print_percentage = progress
self.current_print_job.remaining_time = remaining_time
self._log.debug(f"Updated print progress: {progress}%, remaining: {remaining_time}s")
# Aktualisiere auch die pybambu-Datenstruktur
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
self._bambu_client.device.print_job.gcode_state = print_job_state
except Exception as e:
self._log.error(f"Error processing print state: {e}", exc_info=True)
def _process_hms_errors(self, hms_data):
"""Verarbeitet HMS-Fehlerdaten aus MQTT-Nachrichten"""
try:
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'hms'):
self._bambu_client.device.hms.update_from_payload(hms_data)
self._log.debug("HMS error data updated")
self._bambu_client.device.print_job.mc_percent = progress
self._bambu_client.device.print_job.mc_remaining_time = remaining_time
# Überprüfe auf Fehler und zeige sie an
if self._bambu_client.device.hms.errors != self._last_hms_errors and self._bambu_client.device.hms.errors["Count"] > 0:
self._log.debug(f"HMS Error: {self._bambu_client.device.hms.errors}")
for n in range(1, self._bambu_client.device.hms.errors["Count"] + 1):
error = self._bambu_client.device.hms.errors[f"{n}-Error"].strip()
self.sendIO(f"// action:notification {error}")
self._last_hms_errors = self._bambu_client.device.hms.errors
except Exception as e:
self._log.error(f"Error processing HMS errors: {e}", exc_info=True)
self._log.error(f"Error processing progress data: {e}", exc_info=True)
def _update_bambu_client_state(self, payload):
"""Aktualisiert die internen Zustände des BambuClient"""

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Callable
from typing import TYPE_CHECKING, Callable, List, Optional
if TYPE_CHECKING:
from octoprint_bambu_printer.printer.file_system.remote_sd_card_file_list import (
@ -12,83 +12,59 @@ from pathlib import Path
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
@dataclass
class CachedFileView:
file_system: RemoteSDCardFileList
folder_view: dict[tuple[str, str | list[str] | None], None] = field(
default_factory=dict
) # dict preserves order, but set does not. We use only dict keys as storage
on_update: Callable[[], None] | None = None
def __init__(
self, file_system, on_update: Optional[Callable] = None, base_path: str = ""
):
self._filters = []
self._file_system = file_system
self._base_path = base_path
self._update_complete_callback = on_update
self._file_info_cache = []
def __post_init__(self):
self._file_alias_cache: dict[str, str] = {}
self._file_data_cache: dict[str, FileInfo] = {}
def with_filter(
self, folder: str, extensions: str | list[str] | None = None
) -> "CachedFileView":
self.folder_view[(folder, extensions)] = None
def with_filter(self, path: str, extension: str):
self._filters.append({"path": path, "extension": extension})
return self
def list_all_views(self):
existing_files: list[str] = []
result: list[FileInfo] = []
def update(self) -> None:
try:
file_info_list = self.list_all_views()
self._file_info_cache = file_info_list
# Rufe Callback auf, wenn vorhanden
if self._update_complete_callback is not None:
self._update_complete_callback()
except Exception as e:
import logging
logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter").error(
f"Error updating file list: {e}", exc_info=True
)
with self.file_system.get_ftps_client() as ftp:
for filter in self.folder_view.keys():
result.extend(self.file_system.list_files(*filter, ftp, existing_files))
return result
def list_all_views(self) -> List[FileInfo]:
# Verwende die Mock-Implementation von get_file_list statt FTPS
try:
return self._file_system.get_file_list(self._base_path)
except Exception as e:
import logging
logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter").error(
f"Error listing files: {e}", exc_info=True
)
return []
def update(self):
file_info_list = self.list_all_views()
self._update_file_list_cache(file_info_list)
if self.on_update:
self.on_update()
def get_all_cached_info(self) -> List[FileInfo]:
return self._file_info_cache
def _update_file_list_cache(self, files: list[FileInfo]):
self._file_alias_cache = {info.dosname: info.path.as_posix() for info in files}
self._file_data_cache = {info.path.as_posix(): info for info in files}
def get_file_by_stem(self, file_stem: str, extensions: list[str]) -> FileInfo | None:
"""Get file info by file name without extension"""
for file_info in self._file_info_cache:
for extension in extensions:
if file_info.file_name.lower().startswith(f"{file_stem.lower()}{extension}"):
return file_info
def get_all_info(self):
self.update()
return self.get_all_cached_info()
def get_all_cached_info(self):
return list(self._file_data_cache.values())
def get_file_data(self, file_path: str | Path) -> FileInfo | None:
file_data = self.get_file_data_cached(file_path)
if file_data is None:
self.update()
file_data = self.get_file_data_cached(file_path)
return file_data
def get_file_data_cached(self, file_path: str | Path) -> FileInfo | None:
if isinstance(file_path, str):
file_path = Path(file_path).as_posix().strip("/")
else:
file_path = file_path.as_posix().strip("/")
if file_path not in self._file_data_cache:
file_path = self._file_alias_cache.get(file_path, file_path)
return self._file_data_cache.get(file_path, None)
def get_file_by_stem(self, file_stem: str, allowed_suffixes: list[str]):
if file_stem == "":
return None
file_stem = Path(file_stem).with_suffix("").stem
file_data = self._get_file_by_stem_cached(file_stem, allowed_suffixes)
if file_data is None:
self.update()
file_data = self._get_file_by_stem_cached(file_stem, allowed_suffixes)
return file_data
def _get_file_by_stem_cached(self, file_stem: str, allowed_suffixes: list[str]):
for file_path_str in list(self._file_data_cache.keys()) + list(self._file_alias_cache.keys()):
file_path = Path(file_path_str)
if file_stem == file_path.with_suffix("").stem and all(
suffix in allowed_suffixes for suffix in file_path.suffixes
):
return self.get_file_data_cached(file_path)
return None
def get_file_data(self, file_path: str) -> FileInfo | None:
for file_info in self._file_info_cache:
if file_info.path.lower() == file_path.lower() or file_info.file_name.lower() == file_path.lower():
return file_info
return None

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import datetime
from pathlib import Path
from typing import Iterable, Iterator
from typing import Iterable, Iterator, List
import logging.handlers
from octoprint.util import get_dos_filename
@ -17,6 +17,7 @@ class RemoteSDCardFileList:
self._settings = settings
self._selected_project_file: FileInfo | None = None
self._logger = logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter")
self._mock_files = [] # Lokales Cache für Mock-Dateien
def delete_file(self, file_path: Path) -> None:
try:
@ -80,8 +81,56 @@ class RemoteSDCardFileList:
self._logger.exception(e, exc_info=False)
def get_ftps_client(self):
host = self._settings.get(["host"])
access_code = self._settings.get(["access_code"])
return IoTFTPSClient(
f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True
)
"""
Implementieren wir eine Mock-Version des FTPS-Clients, die keinen echten FTP-Zugriff erfordert.
"""
class MockFTPSClient:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def get_file_list(self, path=""):
"""Gibt die Mock-Dateiliste zurück"""
return self._mock_files
mock_client = MockFTPSClient()
mock_client._mock_files = self._mock_files
return mock_client
@property
def is_available(self) -> bool:
"""
Da wir kein FTP verwenden, ist dieser Service immer verfügbar
"""
return True
def get_file_list(self, path: str) -> List[FileInfo]:
"""
Gibt eine Liste von Dateien im angegebenen Pfad zurück.
Da wir kein FTP verwenden, geben wir eine leere Liste oder gespeicherte Mock-Dateien zurück.
"""
self._logger.debug(f"Listing files in path: {path}")
return self._mock_files
def add_mock_file(self, file_info: FileInfo):
"""
Fügt eine Mock-Datei zur Liste hinzu (für Tests oder wenn keine FTP-Verbindung möglich ist)
"""
self._mock_files.append(file_info)
self._logger.debug(f"Added mock file: {file_info.file_name}")
def clear_mock_files(self):
"""Löscht alle gespeicherten Mock-Dateien"""
self._mock_files = []
self._logger.debug("Mock file list cleared")
def delete_file(self, path: str) -> bool:
"""
Simuliert das Löschen einer Datei, entfernt sie aus der Mock-Liste
"""
self._logger.debug(f"Deleting file: {path}")
before_count = len(self._mock_files)
self._mock_files = [f for f in self._mock_files if f.path != path]
return before_count > len(self._mock_files)