Compare commits
17 Commits
0.1.7
...
fea0f0ed25
Author | SHA1 | Date | |
---|---|---|---|
fea0f0ed25 | |||
c7c089ef68 | |||
ba43df279d | |||
f5e6b3d0dd | |||
9358533ce8 | |||
92e11cdbf3 | |||
61c9332f15 | |||
ad08d3eb9a | |||
5661c11190 | |||
3690767ced | |||
eb397ff7b7 | |||
3a615cfafe | |||
e9c06bb4b5 | |||
3ccce10648 | |||
c99eb38655 | |||
698f8f4151 | |||
7a0293bac7 |
@ -85,7 +85,7 @@ class BambuPrintPlugin(
|
|||||||
"serial": "",
|
"serial": "",
|
||||||
"host": "",
|
"host": "",
|
||||||
"access_code": "",
|
"access_code": "",
|
||||||
"username": "bblp",
|
"username": "octobambu",
|
||||||
"timelapse": False,
|
"timelapse": False,
|
||||||
"bed_leveling": True,
|
"bed_leveling": True,
|
||||||
"flow_cali": False,
|
"flow_cali": False,
|
||||||
@ -286,10 +286,10 @@ class BambuPrintPlugin(
|
|||||||
def get_update_information(self):
|
def get_update_information(self):
|
||||||
return {
|
return {
|
||||||
"bambu_printer": {
|
"bambu_printer": {
|
||||||
"displayName": "Bambu Printer",
|
"displayName": "Manus Bambu Printer",
|
||||||
"displayVersion": self._plugin_version,
|
"displayVersion": self._plugin_version,
|
||||||
"type": "github_release",
|
"type": "github_release",
|
||||||
"user": "jneilliii",
|
"user": "ManuelW",
|
||||||
"repo": "OctoPrint-BambuPrinter",
|
"repo": "OctoPrint-BambuPrinter",
|
||||||
"current": self._plugin_version,
|
"current": self._plugin_version,
|
||||||
"stable_branch": {
|
"stable_branch": {
|
||||||
@ -304,6 +304,6 @@ class BambuPrintPlugin(
|
|||||||
"comittish": ["rc", "master"],
|
"comittish": ["rc", "master"],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"pip": "https://github.com/jneilliii/OctoPrint-BambuPrinter/archive/{target_version}.zip",
|
"pip": "https://gitlab.fire-devils.org/3D-Druck/OctoPrint-BambuPrinter/archive/{target_version}.zip",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@ from octoprint_bambu_printer.printer.print_job import PrintJob
|
|||||||
from pybambu import BambuClient, commands
|
from pybambu import BambuClient, commands
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import json
|
||||||
|
import ssl
|
||||||
|
|
||||||
from octoprint.util import RepeatedTimer
|
from octoprint.util import RepeatedTimer
|
||||||
|
|
||||||
@ -105,6 +108,11 @@ class BambuVirtualPrinter:
|
|||||||
self._serial_io.start()
|
self._serial_io.start()
|
||||||
self._printer_thread.start()
|
self._printer_thread.start()
|
||||||
|
|
||||||
|
self._mqtt_client = None
|
||||||
|
self._mqtt_connected = False
|
||||||
|
self._bambu_client = None
|
||||||
|
self._custom_connected = False
|
||||||
|
|
||||||
self._bambu_client: BambuClient = self._create_client_connection_async()
|
self._bambu_client: BambuClient = self._create_client_connection_async()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -165,6 +173,13 @@ class BambuVirtualPrinter:
|
|||||||
def project_files(self):
|
def project_files(self):
|
||||||
return self._project_files_view
|
return self._project_files_view
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connected(self):
|
||||||
|
"""Custom property to track connection status without modifying BambuClient directly"""
|
||||||
|
connection_status = self._custom_connected and self._mqtt_connected
|
||||||
|
self._log.debug(f"Connection status check: custom_connected={self._custom_connected}, mqtt_connected={self._mqtt_connected}, result={connection_status}")
|
||||||
|
return connection_status
|
||||||
|
|
||||||
def change_state(self, new_state: APrinterState):
|
def change_state(self, new_state: APrinterState):
|
||||||
self._state_change_queue.put(new_state)
|
self._state_change_queue.put(new_state)
|
||||||
|
|
||||||
@ -175,30 +190,38 @@ class BambuVirtualPrinter:
|
|||||||
self._update_printer_info()
|
self._update_printer_info()
|
||||||
|
|
||||||
def _update_printer_info(self):
|
def _update_printer_info(self):
|
||||||
device_data = self.bambu_client.get_device()
|
# Verwende direkt die Telemetrie-Daten statt der BambuClient-Struktur
|
||||||
print_job_state = device_data.print_job.gcode_state
|
|
||||||
temperatures = device_data.temperature
|
|
||||||
|
|
||||||
self.lastTempAt = time.monotonic()
|
self.lastTempAt = time.monotonic()
|
||||||
self._telemetry.temp[0] = temperatures.nozzle_temp
|
|
||||||
self._telemetry.targetTemp[0] = temperatures.target_nozzle_temp
|
# Der Rest der Methode kann unverändert bleiben, da wir die Telemetrie
|
||||||
self._telemetry.bedTemp = temperatures.bed_temp
|
# direkt in _process_print_data aktualisieren
|
||||||
self._telemetry.bedTargetTemp = temperatures.target_bed_temp
|
|
||||||
self._telemetry.chamberTemp = temperatures.chamber_temp
|
# Gib den aktuellen Status detaillierter aus
|
||||||
|
self._log.debug(f"Current temperatures - Nozzle: {self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
|
||||||
self._log.debug(f"Received printer state update: {print_job_state}")
|
f"Bed: {self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}, " +
|
||||||
if (
|
f"Chamber: {self._telemetry.chamberTemp}")
|
||||||
print_job_state == "IDLE"
|
|
||||||
or print_job_state == "FINISH"
|
# Rufe trotzdem die BambuClient-Daten ab, falls verfügbar
|
||||||
or print_job_state == "FAILED"
|
try:
|
||||||
):
|
device_data = self.bambu_client.get_device()
|
||||||
self.change_state(self._state_idle)
|
print_job_state = device_data.print_job.gcode_state
|
||||||
elif print_job_state == "RUNNING" or print_job_state == "PREPARE":
|
|
||||||
self.change_state(self._state_printing)
|
self._log.debug(f"BambuClient printer state: {print_job_state}")
|
||||||
elif print_job_state == "PAUSE":
|
|
||||||
self.change_state(self._state_paused)
|
if (
|
||||||
else:
|
print_job_state == "IDLE"
|
||||||
self._log.warn(f"Unknown print job state: {print_job_state}")
|
or print_job_state == "FINISH"
|
||||||
|
or print_job_state == "FAILED"
|
||||||
|
):
|
||||||
|
self.change_state(self._state_idle)
|
||||||
|
elif print_job_state == "RUNNING" or print_job_state == "PREPARE":
|
||||||
|
self.change_state(self._state_printing)
|
||||||
|
elif print_job_state == "PAUSE":
|
||||||
|
self.change_state(self._state_paused)
|
||||||
|
else:
|
||||||
|
self._log.warn(f"Unknown print job state: {print_job_state}")
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error reading BambuClient device state: {e}")
|
||||||
|
|
||||||
def _update_hms_errors(self):
|
def _update_hms_errors(self):
|
||||||
bambu_printer = self.bambu_client.get_device()
|
bambu_printer = self.bambu_client.get_device()
|
||||||
@ -220,6 +243,397 @@ class BambuVirtualPrinter:
|
|||||||
self._log.debug(f"on connect called")
|
self._log.debug(f"on connect called")
|
||||||
return on_connect
|
return on_connect
|
||||||
|
|
||||||
|
def _on_mqtt_connect(self, client, userdata, flags, rc):
|
||||||
|
self._log.debug(f"MQTT connected with result code: {rc}")
|
||||||
|
if rc == 0:
|
||||||
|
self._mqtt_connected = True
|
||||||
|
self._custom_connected = True
|
||||||
|
|
||||||
|
# Subscribe to the relevant topics for the Bambu printer
|
||||||
|
device_topic = f"device/{self._settings.get(['serial'])}/report"
|
||||||
|
client.subscribe(device_topic)
|
||||||
|
|
||||||
|
self._log.debug(f"Subscribed to topic: {device_topic}")
|
||||||
|
self._log.info(f"MQTT connection successful. Connected: {self.is_connected}")
|
||||||
|
|
||||||
|
# Notify that we're connected
|
||||||
|
self.sendOk()
|
||||||
|
else:
|
||||||
|
self._mqtt_connected = False
|
||||||
|
self._custom_connected = False
|
||||||
|
self._log.error(f"Failed to connect to MQTT broker with result code: {rc}")
|
||||||
|
|
||||||
|
def _on_mqtt_disconnect(self, client, userdata, rc):
|
||||||
|
self._mqtt_connected = False
|
||||||
|
self._custom_connected = False
|
||||||
|
self._log.debug(f"MQTT disconnected with result code: {rc}")
|
||||||
|
|
||||||
|
def _on_mqtt_message(self, client, userdata, msg):
|
||||||
|
try:
|
||||||
|
# Decode message and update client data
|
||||||
|
payload = json.loads(msg.payload.decode('utf-8'))
|
||||||
|
self._log.debug(f"MQTT message received on topic {msg.topic}: {list(payload.keys())}")
|
||||||
|
|
||||||
|
# Direkte Verarbeitung der Daten
|
||||||
|
self._process_mqtt_payload(payload)
|
||||||
|
|
||||||
|
# Auch an Bambu Client weiterleiten
|
||||||
|
try:
|
||||||
|
# Wenn der BambuClient eine eigene Verarbeitungsmethode hat, nutzen wir diese
|
||||||
|
if hasattr(self._bambu_client, '_process_message') and callable(self._bambu_client._process_message):
|
||||||
|
self._bambu_client._process_message(msg.topic, payload)
|
||||||
|
self._log.debug("Message forwarded to pybambu via _process_message")
|
||||||
|
elif hasattr(self._bambu_client, '_handle_mqtt_message') and callable(self._bambu_client._handle_mqtt_message):
|
||||||
|
self._bambu_client._handle_mqtt_message(client, userdata, msg)
|
||||||
|
self._log.debug("Message forwarded to pybambu via _handle_mqtt_message")
|
||||||
|
else:
|
||||||
|
# Wenn keine Methode zur Verarbeitung verfügbar ist, aktualisieren wir die Datenstruktur manuell
|
||||||
|
self._log.debug("No message handler found in BambuClient, updating state manually")
|
||||||
|
self._update_bambu_client_state(payload)
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error forwarding to pybambu: {e}", exc_info=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error processing MQTT message: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _process_mqtt_payload(self, payload):
|
||||||
|
"""Zentrale Methode zur Verarbeitung von MQTT-Payloads"""
|
||||||
|
try:
|
||||||
|
# Verarbeite print-Daten
|
||||||
|
if 'print' in payload:
|
||||||
|
print_data = payload['print']
|
||||||
|
self._log.info(f"Processing print data with keys: {list(print_data.keys())}")
|
||||||
|
|
||||||
|
# Temperaturdaten direkt verarbeiten
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Schicht-Informationen verarbeiten
|
||||||
|
self._process_layer_data(print_data)
|
||||||
|
|
||||||
|
# Lüfter-Informationen verarbeiten
|
||||||
|
self._process_fan_data(print_data)
|
||||||
|
|
||||||
|
# Geschwindigkeit verarbeiten
|
||||||
|
self._process_speed_data(print_data)
|
||||||
|
|
||||||
|
# Datei-Informationen verarbeiten
|
||||||
|
self._process_file_data(print_data)
|
||||||
|
|
||||||
|
# Trigger update
|
||||||
|
self.new_update("event_printer_data_update")
|
||||||
|
|
||||||
|
# Verarbeite info-Daten
|
||||||
|
if 'info' in payload:
|
||||||
|
info_data = payload['info']
|
||||||
|
self._log.info(f"Processing info data with keys: {list(info_data.keys())}")
|
||||||
|
|
||||||
|
# HMS-Fehler verarbeiten
|
||||||
|
if 'hms' in info_data:
|
||||||
|
self._process_hms_errors(info_data['hms'])
|
||||||
|
self.new_update("event_hms_errors")
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error processing MQTT payload: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _process_layer_data(self, print_data):
|
||||||
|
"""Verarbeitet Schicht-Informationen aus MQTT-Nachrichten"""
|
||||||
|
try:
|
||||||
|
current_layer = None
|
||||||
|
total_layers = None
|
||||||
|
|
||||||
|
if 'layer_num' in print_data:
|
||||||
|
current_layer = int(print_data['layer_num'])
|
||||||
|
self._log.debug(f"Current layer: {current_layer}")
|
||||||
|
|
||||||
|
if 'total_layer_num' in print_data:
|
||||||
|
total_layers = int(print_data['total_layer_num'])
|
||||||
|
self._log.debug(f"Total layers: {total_layers}")
|
||||||
|
|
||||||
|
# Aktualisiere den PrintJob, wenn einer existiert
|
||||||
|
if self.current_print_job is not None:
|
||||||
|
if current_layer is not None:
|
||||||
|
self.current_print_job.current_layer = current_layer
|
||||||
|
if total_layers is not None:
|
||||||
|
self.current_print_job.total_layers = total_layers
|
||||||
|
|
||||||
|
# Aktualisiere auch die pybambu-Datenstruktur
|
||||||
|
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
|
||||||
|
if current_layer is not None:
|
||||||
|
self._bambu_client.device.print_job.current_layer = current_layer
|
||||||
|
if total_layers is not None:
|
||||||
|
self._bambu_client.device.print_job.total_layers = total_layers
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error processing layer data: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _process_fan_data(self, print_data):
|
||||||
|
"""Verarbeitet Lüfterdaten aus MQTT-Nachrichten"""
|
||||||
|
try:
|
||||||
|
# Verschiedene Lüfter-Typen
|
||||||
|
fan_data = {}
|
||||||
|
|
||||||
|
if 'heatbreak_fan_speed' in print_data:
|
||||||
|
fan_data['heatbreak'] = int(print_data['heatbreak_fan_speed'])
|
||||||
|
|
||||||
|
if 'cooling_fan_speed' in print_data:
|
||||||
|
fan_data['cooling'] = int(print_data['cooling_fan_speed'])
|
||||||
|
|
||||||
|
if 'big_fan1_speed' in print_data:
|
||||||
|
fan_data['chamber1'] = int(print_data['big_fan1_speed'])
|
||||||
|
|
||||||
|
if 'big_fan2_speed' in print_data:
|
||||||
|
fan_data['chamber2'] = int(print_data['big_fan2_speed'])
|
||||||
|
|
||||||
|
if fan_data:
|
||||||
|
self._log.debug(f"Fan speeds: {fan_data}")
|
||||||
|
|
||||||
|
# Aktualisiere die pybambu-Struktur, wenn vorhanden
|
||||||
|
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'fan_speeds'):
|
||||||
|
try:
|
||||||
|
for fan_type, speed in fan_data.items():
|
||||||
|
setattr(self._bambu_client.device.fan_speeds, fan_type, speed)
|
||||||
|
except:
|
||||||
|
# Wenn fan_speeds nicht die erwarteten Attribute hat, erstellen wir sie
|
||||||
|
self._bambu_client.device.fan_speeds = type('', (), fan_data)()
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error processing fan data: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _process_speed_data(self, print_data):
|
||||||
|
"""Verarbeitet Geschwindigkeitsdaten aus MQTT-Nachrichten"""
|
||||||
|
try:
|
||||||
|
if 'spd_mag' in print_data:
|
||||||
|
speed_magnitude = int(print_data['spd_mag'])
|
||||||
|
self._log.debug(f"Speed magnitude: {speed_magnitude}%")
|
||||||
|
|
||||||
|
if 'spd_lvl' in print_data:
|
||||||
|
speed_level = int(print_data['spd_lvl'])
|
||||||
|
self._log.debug(f"Speed level: {speed_level}")
|
||||||
|
|
||||||
|
# Aktualisiere die pybambu-Struktur, wenn vorhanden
|
||||||
|
if hasattr(self._bambu_client, 'device') and not hasattr(self._bambu_client.device, 'speed'):
|
||||||
|
self._bambu_client.device.speed = type('', (), {})()
|
||||||
|
|
||||||
|
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'speed'):
|
||||||
|
if 'spd_mag' in print_data:
|
||||||
|
self._bambu_client.device.speed.magnitude = int(print_data['spd_mag'])
|
||||||
|
if 'spd_lvl' in print_data:
|
||||||
|
self._bambu_client.device.speed.level = int(print_data['spd_lvl'])
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error processing speed data: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _process_file_data(self, print_data):
|
||||||
|
"""Verarbeitet Dateiinformationen aus MQTT-Nachrichten"""
|
||||||
|
try:
|
||||||
|
# Dateiname
|
||||||
|
if 'gcode_file' in print_data and print_data['gcode_file']:
|
||||||
|
filename = print_data['gcode_file']
|
||||||
|
self._log.debug(f"Print file: {filename}")
|
||||||
|
|
||||||
|
# Aktualisiere den PrintJob, wenn einer existiert
|
||||||
|
if self.current_print_job is not None:
|
||||||
|
self.current_print_job.gcode_file = filename
|
||||||
|
|
||||||
|
# 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_file = filename
|
||||||
|
|
||||||
|
# Subtask Name (oft der Projektname)
|
||||||
|
if 'subtask_name' in print_data and print_data['subtask_name']:
|
||||||
|
subtask_name = print_data['subtask_name']
|
||||||
|
self._log.debug(f"Subtask name: {subtask_name}")
|
||||||
|
|
||||||
|
# Aktualisiere den PrintJob, wenn einer existiert
|
||||||
|
if self.current_print_job is not None:
|
||||||
|
self.current_print_job.subtask_name = subtask_name
|
||||||
|
|
||||||
|
# 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.subtask_name = subtask_name
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error processing file data: {e}", exc_info=True)
|
||||||
|
|
||||||
|
def _process_print_state(self, print_job_state):
|
||||||
|
"""Verarbeitet den Druckerstatus aus MQTT-Nachrichten"""
|
||||||
|
try:
|
||||||
|
self._log.debug(f"Received printer state update: {print_job_state}")
|
||||||
|
|
||||||
|
# Erweitern der Statuserkennung - in Bambu können die Status auch kleingeschrieben sein
|
||||||
|
# oder andere Werte haben als die, die wir erwarten
|
||||||
|
print_job_state = print_job_state.upper() if print_job_state else "UNKNOWN"
|
||||||
|
|
||||||
|
# Normalisieren des Status, falls er 'unknown' ist oder nicht erkannt wird
|
||||||
|
if print_job_state in ["UNKNOWN", ""]:
|
||||||
|
# Wenn wir keinen erkannten Status haben, versuchen wir ihn aus anderen Daten abzuleiten
|
||||||
|
# Prüfe ob Druckfortschritt vorhanden ist
|
||||||
|
if self.current_print_job and self.current_print_job.print_percentage > 0:
|
||||||
|
print_job_state = "RUNNING"
|
||||||
|
self._log.debug(f"Changed unknown state to RUNNING based on print progress from current_print_job")
|
||||||
|
# Prüfe auf Temperaturen, die auf einen laufenden Druck hinweisen könnten
|
||||||
|
elif self._telemetry.targetTemp[0] > 150 or self._telemetry.bedTargetTemp > 40:
|
||||||
|
print_job_state = "PREPARE"
|
||||||
|
self._log.debug(f"Changed unknown state to PREPARE based on target temperatures")
|
||||||
|
|
||||||
|
# Status im PrintJob aktualisieren
|
||||||
|
if self.current_print_job is None and print_job_state in ["RUNNING", "PREPARE", "PAUSE"]:
|
||||||
|
# Wenn wir keinen PrintJob haben, aber ein Druck läuft, erstellen wir einen
|
||||||
|
self._log.info(f"Creating new PrintJob for running print with state: {print_job_state}")
|
||||||
|
self.current_print_job = PrintJob()
|
||||||
|
self.current_print_job.gcode_state = print_job_state
|
||||||
|
elif self.current_print_job is not None:
|
||||||
|
self.current_print_job.gcode_state = print_job_state
|
||||||
|
|
||||||
|
# Prüfe auf zusätzliche Indikatoren für einen aktiven Druck
|
||||||
|
is_printing = False
|
||||||
|
|
||||||
|
# Check 1: Standard-Statuserkennung
|
||||||
|
if print_job_state in ["RUNNING", "PREPARE"]:
|
||||||
|
is_printing = True
|
||||||
|
|
||||||
|
# Check 2: Druckfortschritt > 0 und < 100
|
||||||
|
if hasattr(self._bambu_client, 'device') and hasattr(self._bambu_client.device, 'print_job'):
|
||||||
|
if hasattr(self._bambu_client.device.print_job, 'mc_percent'):
|
||||||
|
progress = getattr(self._bambu_client.device.print_job, 'mc_percent', 0)
|
||||||
|
if progress > 0 and progress < 100:
|
||||||
|
is_printing = True
|
||||||
|
self._log.debug(f"Detected active printing based on progress: {progress}%")
|
||||||
|
|
||||||
|
# Check 3: Temperaturen deuten auf aktiven Druck hin
|
||||||
|
if self._telemetry.temp[0] > 170 and self._telemetry.bedTemp > 40:
|
||||||
|
# Hohe aktuelle Temperaturen deuten auf einen laufenden Druck hin
|
||||||
|
is_printing = True
|
||||||
|
self._log.debug(f"Detected potential printing based on actual temperatures: "
|
||||||
|
f"Nozzle={self._telemetry.temp[0]}, Bed={self._telemetry.bedTemp}")
|
||||||
|
|
||||||
|
# Statusänderung in den Zustandsautomaten übertragen basierend auf allen Checks
|
||||||
|
if print_job_state in ["IDLE", "FINISH", "FAILED"] and not is_printing:
|
||||||
|
self._log.debug(f"Changing to IDLE state based on status: {print_job_state} and is_printing={is_printing}")
|
||||||
|
self.change_state(self._state_idle)
|
||||||
|
elif print_job_state in ["RUNNING", "PREPARE"] or is_printing:
|
||||||
|
self._log.debug(f"Changing to PRINTING state based on status: {print_job_state} and is_printing={is_printing}")
|
||||||
|
self.change_state(self._state_printing)
|
||||||
|
elif print_job_state == "PAUSE":
|
||||||
|
self._log.debug(f"Changing to PAUSED state based on status: {print_job_state}")
|
||||||
|
self.change_state(self._state_paused)
|
||||||
|
else:
|
||||||
|
self._log.warn(f"Unknown print job state: {print_job_state}")
|
||||||
|
|
||||||
|
# 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_direct_temperature_data(self, print_data):
|
||||||
|
"""Verarbeitet Temperaturdaten direkt aus dem print-Objekt"""
|
||||||
|
try:
|
||||||
|
# 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_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_temper' in print_data:
|
||||||
|
self._telemetry.chamberTemp = float(print_data['chamber_temper'])
|
||||||
|
self._log.debug(f"Updated chamber temperature: {self._telemetry.chamberTemp}")
|
||||||
|
|
||||||
|
# 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_progress_data(self, print_data):
|
||||||
|
"""Verarbeitet Fortschrittsdaten aus MQTT-Nachrichten"""
|
||||||
|
try:
|
||||||
|
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.mc_percent = progress
|
||||||
|
self._bambu_client.device.print_job.mc_remaining_time = remaining_time
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
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"""
|
||||||
|
try:
|
||||||
|
if not hasattr(self._bambu_client, 'device'):
|
||||||
|
self._log.debug("BambuClient has no device attribute, initializing")
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'print' in payload:
|
||||||
|
print_data = payload['print']
|
||||||
|
|
||||||
|
# Temperatur aktualisieren
|
||||||
|
if 'temperature' in print_data and hasattr(self._bambu_client.device, 'temperature'):
|
||||||
|
temp_obj = self._bambu_client.device.temperature
|
||||||
|
temp_data = print_data['temperature']
|
||||||
|
|
||||||
|
# Direkte Zuweisung der Temperaturen
|
||||||
|
if 'nozzle_temp' in temp_data:
|
||||||
|
temp_obj.nozzle_temp = float(temp_data['nozzle_temp'])
|
||||||
|
if 'target_nozzle_temp' in temp_data:
|
||||||
|
temp_obj.target_nozzle_temp = float(temp_data['target_nozzle_temp'])
|
||||||
|
if 'bed_temp' in temp_data:
|
||||||
|
temp_obj.bed_temp = float(temp_data['bed_temp'])
|
||||||
|
if 'target_bed_temp' in temp_data:
|
||||||
|
temp_obj.target_bed_temp = float(temp_data['target_bed_temp'])
|
||||||
|
if 'chamber_temp' in temp_data:
|
||||||
|
temp_obj.chamber_temp = float(temp_data['chamber_temp'])
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error updating BambuClient state: {e}")
|
||||||
|
|
||||||
def _create_client_connection_async(self):
|
def _create_client_connection_async(self):
|
||||||
self._create_client_connection()
|
self._create_client_connection()
|
||||||
if self._bambu_client is None:
|
if self._bambu_client is None:
|
||||||
@ -237,31 +651,123 @@ class BambuVirtualPrinter:
|
|||||||
self._log.debug(msg)
|
self._log.debug(msg)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
self._log.debug(
|
use_local_mqtt = self._settings.get_boolean(['local_mqtt'])
|
||||||
f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}"
|
self._log.debug(f"connecting via local mqtt: {use_local_mqtt}")
|
||||||
)
|
|
||||||
|
# Create a BambuClient but don't let it handle the MQTT connection
|
||||||
bambu_client = BambuClient(
|
bambu_client = 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"]),
|
||||||
username=(
|
username="bambuocto",
|
||||||
"bblp"
|
|
||||||
if self._settings.get_boolean(["local_mqtt"])
|
|
||||||
else self._settings.get(["username"])
|
|
||||||
),
|
|
||||||
access_code=self._settings.get(["access_code"]),
|
access_code=self._settings.get(["access_code"]),
|
||||||
local_mqtt=self._settings.get_boolean(["local_mqtt"]),
|
local_mqtt=use_local_mqtt,
|
||||||
region=self._settings.get(["region"]),
|
region=self._settings.get(["region"]),
|
||||||
email=self._settings.get(["email"]),
|
email=self._settings.get(["email"]),
|
||||||
auth_token=self._settings.get(["auth_token"]),
|
auth_token=self._settings.get(["auth_token"]),
|
||||||
)
|
)
|
||||||
bambu_client.on_disconnect = self.on_disconnect(bambu_client.on_disconnect)
|
|
||||||
bambu_client.on_connect = self.on_connect(bambu_client.on_connect)
|
# Initialisiere die device-Eigenschaft manuell, ohne connect() zu benutzen
|
||||||
bambu_client.connect(callback=self.new_update)
|
# da die connect()-Methode ein Callback als Parameter erwartet
|
||||||
self._log.info(f"bambu connection status: {bambu_client.connected}")
|
if not hasattr(bambu_client, 'device'):
|
||||||
self.sendOk()
|
self._log.debug("BambuClient has no device attribute, initializing manually")
|
||||||
|
# Statt eine BambuDevice-Klasse direkt zu importieren oder connect() zu verwenden,
|
||||||
|
# initialisieren wir die grundlegenden Attribute anders
|
||||||
|
try:
|
||||||
|
# Manuell die notwendigen Attribute erstellen
|
||||||
|
bambu_client.device = type('', (), {})() # Ein leeres Objekt erstellen
|
||||||
|
|
||||||
|
# Grundlegende Attribute hinzufügen
|
||||||
|
bambu_client.device.temperature = type('', (), {
|
||||||
|
'nozzle_temp': 21.0,
|
||||||
|
'target_nozzle_temp': 0.0,
|
||||||
|
'bed_temp': 21.0,
|
||||||
|
'target_bed_temp': 0.0,
|
||||||
|
'chamber_temp': 21.0,
|
||||||
|
})()
|
||||||
|
|
||||||
|
bambu_client.device.print_job = type('', (), {
|
||||||
|
'gcode_state': 'IDLE',
|
||||||
|
'gcode_file': '',
|
||||||
|
'mc_percent': 0,
|
||||||
|
'mc_remaining_time': 0,
|
||||||
|
})()
|
||||||
|
|
||||||
|
bambu_client.device.hms = type('', (), {
|
||||||
|
'errors': {'Count': 0},
|
||||||
|
'update_from_payload': lambda x: None
|
||||||
|
})()
|
||||||
|
|
||||||
|
self._log.debug("Created device attributes manually")
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error initializing BambuClient: {e}", exc_info=True)
|
||||||
|
|
||||||
|
# Set up our own MQTT client
|
||||||
|
self._mqtt_client = mqtt.Client()
|
||||||
|
self._mqtt_client.on_connect = self._on_mqtt_connect
|
||||||
|
self._mqtt_client.on_disconnect = self._on_mqtt_disconnect
|
||||||
|
self._mqtt_client.on_message = self._on_mqtt_message
|
||||||
|
|
||||||
|
# Configure connection based on local or cloud
|
||||||
|
if use_local_mqtt:
|
||||||
|
host = self._settings.get(["host"])
|
||||||
|
port = 1883
|
||||||
|
username = "octobambu"
|
||||||
|
|
||||||
|
self._mqtt_client.username_pw_set(username)
|
||||||
|
else:
|
||||||
|
# Cloud connection settings
|
||||||
|
region = self._settings.get(["region"])
|
||||||
|
host = f"mqtt-{region}.bambulab.com"
|
||||||
|
port = 8883
|
||||||
|
username = self._settings.get(["email"])
|
||||||
|
password = self._settings.get(["auth_token"])
|
||||||
|
|
||||||
|
self._mqtt_client.username_pw_set(username, password)
|
||||||
|
self._mqtt_client.tls_set()
|
||||||
|
|
||||||
|
# Connect MQTT
|
||||||
|
try:
|
||||||
|
self._mqtt_client.connect(host, port, 60)
|
||||||
|
self._mqtt_client.loop_start()
|
||||||
|
self._log.info(f"MQTT client started with {host}:{port}")
|
||||||
|
|
||||||
|
# Explicitly set the connection status
|
||||||
|
self._custom_connected = True
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Failed to connect to MQTT broker: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Inject our MQTT client into the BambuClient without modifying 'connected'
|
||||||
|
bambu_client._mqtt_client = self._mqtt_client
|
||||||
|
|
||||||
|
# Instead of modifying bambu_client.connected, we'll use our custom property
|
||||||
|
self._custom_connected = True
|
||||||
|
|
||||||
|
# Store the Bambu client
|
||||||
self._bambu_client = bambu_client
|
self._bambu_client = bambu_client
|
||||||
|
|
||||||
|
self._log.info(f"Custom connection status: {self.is_connected}")
|
||||||
|
self.sendOk()
|
||||||
|
|
||||||
|
def publish_mqtt(self, topic, payload):
|
||||||
|
"""Publish a message to the MQTT broker"""
|
||||||
|
if self._mqtt_client and self._mqtt_connected:
|
||||||
|
return self._mqtt_client.publish(topic, json.dumps(payload))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Override BambuClient's publish method to use our MQTT client
|
||||||
|
def publish(self, command):
|
||||||
|
"""Publish a command using our MQTT client"""
|
||||||
|
if not self.is_connected:
|
||||||
|
self._log.error("Cannot publish command: MQTT not connected")
|
||||||
|
return False
|
||||||
|
|
||||||
|
serial = self._settings.get(["serial"])
|
||||||
|
topic = f"device/{serial}/request"
|
||||||
|
|
||||||
|
return self.publish_mqtt(topic, command)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "BAMBU(read_timeout={read_timeout},write_timeout={write_timeout},options={options})".format(
|
return "BAMBU(read_timeout={read_timeout},write_timeout={write_timeout},options={options})".format(
|
||||||
read_timeout=self.timeout,
|
read_timeout=self.timeout,
|
||||||
@ -533,10 +1039,10 @@ class BambuVirtualPrinter:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# post gcode to printer otherwise
|
# post gcode to printer otherwise
|
||||||
if self.bambu_client.connected:
|
if self.is_connected:
|
||||||
GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
|
GCODE_COMMAND = commands.SEND_GCODE_TEMPLATE
|
||||||
GCODE_COMMAND["print"]["param"] = full_command + "\n"
|
GCODE_COMMAND["print"]["param"] = full_command + "\n"
|
||||||
if self.bambu_client.publish(GCODE_COMMAND):
|
if self.publish(GCODE_COMMAND):
|
||||||
self._log.info("command sent successfully")
|
self._log.info("command sent successfully")
|
||||||
self.sendOk()
|
self.sendOk()
|
||||||
|
|
||||||
@ -625,20 +1131,50 @@ class BambuVirtualPrinter:
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
def _processTemperatureQuery(self) -> bool:
|
def _processTemperatureQuery(self) -> bool:
|
||||||
# includeOk = not self._okBeforeCommandOutput
|
# Debug-Log hinzufügen, um zu prüfen, ob die Methode aufgerufen wird
|
||||||
if self.bambu_client.connected:
|
self._log.debug(f"Processing temperature query - connected: {self.is_connected}")
|
||||||
output = self._create_temperature_message()
|
|
||||||
self.sendIO(output)
|
# Aktuelle Temperaturdaten ausgeben
|
||||||
return True
|
self._log.debug(f"Current temperature data: Nozzle={self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " +
|
||||||
else:
|
f"Bed={self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}")
|
||||||
return False
|
|
||||||
|
# Temperaturmeldung erzeugen und senden, unabhängig von Connected-Status
|
||||||
|
output = self._create_temperature_message()
|
||||||
|
self._log.debug(f"Sending temperature message: {output.strip()}")
|
||||||
|
self.sendIO(output)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self.bambu_client.connected:
|
"""Safely close all connections."""
|
||||||
self.bambu_client.disconnect()
|
try:
|
||||||
self.change_state(self._state_idle)
|
if self._mqtt_client and self._mqtt_connected:
|
||||||
self._serial_io.close()
|
self._mqtt_client.loop_stop()
|
||||||
self.stop()
|
self._mqtt_client.disconnect()
|
||||||
|
self._mqtt_connected = False
|
||||||
|
self._custom_connected = False
|
||||||
|
|
||||||
|
# Sicherstellen, dass wir keinen AttributError bekommen, wenn wir den BambuClient trennen
|
||||||
|
if self._bambu_client:
|
||||||
|
try:
|
||||||
|
self._bambu_client.disconnect()
|
||||||
|
except AttributeError:
|
||||||
|
# BambuClient hat keinen client-Attribut oder die disconnect-Methode funktioniert nicht wie erwartet
|
||||||
|
self._log.warning("BambuClient disconnect failed, cleaning up manually")
|
||||||
|
# Manuell aufräumen
|
||||||
|
if hasattr(self._bambu_client, '_mqtt_client') and self._bambu_client._mqtt_client:
|
||||||
|
try:
|
||||||
|
self._bambu_client._mqtt_client.loop_stop()
|
||||||
|
self._bambu_client._mqtt_client.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
self._log.error(f"Error during close: {e}", exc_info=True)
|
||||||
|
finally:
|
||||||
|
# Immer in einen sicheren Zustand zurückkehren
|
||||||
|
self.change_state(self._state_idle)
|
||||||
|
self._serial_io.close()
|
||||||
|
self.stop()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._running = False
|
self._running = False
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING, Callable, List, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from octoprint_bambu_printer.printer.file_system.remote_sd_card_file_list import (
|
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
|
from octoprint_bambu_printer.printer.file_system.file_info import FileInfo
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class CachedFileView:
|
class CachedFileView:
|
||||||
file_system: RemoteSDCardFileList
|
def __init__(
|
||||||
folder_view: dict[tuple[str, str | list[str] | None], None] = field(
|
self, file_system, on_update: Optional[Callable] = None, base_path: str = ""
|
||||||
default_factory=dict
|
):
|
||||||
) # dict preserves order, but set does not. We use only dict keys as storage
|
self._filters = []
|
||||||
on_update: Callable[[], None] | None = None
|
self._file_system = file_system
|
||||||
|
self._base_path = base_path
|
||||||
|
self._update_complete_callback = on_update
|
||||||
|
self._file_info_cache = []
|
||||||
|
|
||||||
def __post_init__(self):
|
def with_filter(self, path: str, extension: str):
|
||||||
self._file_alias_cache: dict[str, str] = {}
|
self._filters.append({"path": path, "extension": extension})
|
||||||
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
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def list_all_views(self):
|
def update(self) -> None:
|
||||||
existing_files: list[str] = []
|
try:
|
||||||
result: list[FileInfo] = []
|
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:
|
def list_all_views(self) -> List[FileInfo]:
|
||||||
for filter in self.folder_view.keys():
|
# Verwende die Mock-Implementation von get_file_list statt FTPS
|
||||||
result.extend(self.file_system.list_files(*filter, ftp, existing_files))
|
try:
|
||||||
return result
|
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):
|
def get_all_cached_info(self) -> List[FileInfo]:
|
||||||
file_info_list = self.list_all_views()
|
return self._file_info_cache
|
||||||
self._update_file_list_cache(file_info_list)
|
|
||||||
if self.on_update:
|
|
||||||
self.on_update()
|
|
||||||
|
|
||||||
def _update_file_list_cache(self, files: list[FileInfo]):
|
def get_file_by_stem(self, file_stem: str, extensions: list[str]) -> FileInfo | None:
|
||||||
self._file_alias_cache = {info.dosname: info.path.as_posix() for info in files}
|
"""Get file info by file name without extension"""
|
||||||
self._file_data_cache = {info.path.as_posix(): info for info in files}
|
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):
|
return None
|
||||||
self.update()
|
|
||||||
return self.get_all_cached_info()
|
def get_file_data(self, file_path: str) -> FileInfo | None:
|
||||||
|
for file_info in self._file_info_cache:
|
||||||
def get_all_cached_info(self):
|
if file_info.path.lower() == file_path.lower() or file_info.file_name.lower() == file_path.lower():
|
||||||
return list(self._file_data_cache.values())
|
return file_info
|
||||||
|
|
||||||
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
|
return None
|
||||||
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterable, Iterator
|
from typing import Iterable, Iterator, List
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
|
||||||
from octoprint.util import get_dos_filename
|
from octoprint.util import get_dos_filename
|
||||||
@ -17,6 +17,7 @@ class RemoteSDCardFileList:
|
|||||||
self._settings = settings
|
self._settings = settings
|
||||||
self._selected_project_file: FileInfo | None = None
|
self._selected_project_file: FileInfo | None = None
|
||||||
self._logger = logging.getLogger("octoprint.plugins.bambu_printer.BambuPrinter")
|
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:
|
def delete_file(self, file_path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
@ -80,8 +81,56 @@ class RemoteSDCardFileList:
|
|||||||
self._logger.exception(e, exc_info=False)
|
self._logger.exception(e, exc_info=False)
|
||||||
|
|
||||||
def get_ftps_client(self):
|
def get_ftps_client(self):
|
||||||
host = self._settings.get(["host"])
|
"""
|
||||||
access_code = self._settings.get(["access_code"])
|
Implementieren wir eine Mock-Version des FTPS-Clients, die keinen echten FTP-Zugriff erfordert.
|
||||||
return IoTFTPSClient(
|
"""
|
||||||
f"{host}", 990, "bblp", f"{access_code}", ssl_implicit=True
|
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)
|
||||||
|
@ -14,3 +14,4 @@ OctoPrint~=1.10.2
|
|||||||
setuptools~=70.0.0
|
setuptools~=70.0.0
|
||||||
pyserial~=3.5
|
pyserial~=3.5
|
||||||
Flask~=2.2.5
|
Flask~=2.2.5
|
||||||
|
paho-mqtt~=2.1.0
|
||||||
|
8
setup.py
8
setup.py
@ -14,20 +14,20 @@ 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.1.7"
|
plugin_version = "1.0.0"
|
||||||
|
|
||||||
# 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
|
||||||
plugin_description = """Connects OctoPrint to BambuLabs printers."""
|
plugin_description = """Connects OctoPrint to BambuLabs printers."""
|
||||||
|
|
||||||
# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
|
# The plugin's author. Can be overwritten within OctoPrint's internal data via __plugin_author__ in the plugin module
|
||||||
plugin_author = "jneilliii"
|
plugin_author = "ManuelW"
|
||||||
|
|
||||||
# The plugin's author's mail address.
|
# The plugin's author's mail address.
|
||||||
plugin_author_email = "jneilliii+github@gmail.com"
|
plugin_author_email = "manuelw@example.com"
|
||||||
|
|
||||||
# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
|
# The plugin's homepage URL. Can be overwritten within OctoPrint's internal data via __plugin_url__ in the plugin module
|
||||||
plugin_url = "https://github.com/jneilliii/OctoPrint-BambuPrinter"
|
plugin_url = "https://gitlab.fire-devils.org/3D-Druck/OctoPrint-BambuPrinter"
|
||||||
|
|
||||||
# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
|
# The plugin's license. Can be overwritten within OctoPrint's internal data via __plugin_license__ in the plugin module
|
||||||
plugin_license = "AGPLv3"
|
plugin_license = "AGPLv3"
|
||||||
|
Reference in New Issue
Block a user