diff --git a/octoprint_bambu_printer/printer/bambu_virtual_printer.py b/octoprint_bambu_printer/printer/bambu_virtual_printer.py index 64c29d9..a8b7964 100644 --- a/octoprint_bambu_printer/printer/bambu_virtual_printer.py +++ b/octoprint_bambu_printer/printer/bambu_virtual_printer.py @@ -64,7 +64,7 @@ class BambuMqttBridgeClient: self._host = host self._mqtt_port = mqtt_port self.connected = False - self._mqtt_client = mqtt.Client() + self._mqtt_client = mqtt.Client(f"octoprint_bambu_bridge_{serial}") self._device_data = self._create_empty_device_data() self._callbacks = {} @@ -85,12 +85,12 @@ class BambuMqttBridgeClient: device.temperature.nozzle_temp = AMBIENT_TEMPERATURE device.temperature.target_nozzle_temp = 0.0 device.temperature.bed_temp = AMBIENT_TEMPERATURE - device.temperature.target_bed_temp = 0.0 + device.temperature.target_bed_temp = 0.0 device.temperature.chamber_temp = AMBIENT_TEMPERATURE device.hms = SimpleNamespace() device.hms.errors = {"Count": 0} return device - + def _on_connect(self, client, userdata, flags, rc): if rc == 0: self._log.info(f"Connected to MQTT broker at {self._host}:{self._mqtt_port}") @@ -100,6 +100,7 @@ class BambuMqttBridgeClient: topic_base = f"device/{self._device_type}/{self._serial}" self._mqtt_client.subscribe(f"{topic_base}/report") self._mqtt_client.subscribe(f"{topic_base}/report_hms") + self._log.info(f"Subscribed to topic: {topic_base}/#") if 'callback' in self._callbacks: self._callbacks['callback']("event_printer_data_update") @@ -112,6 +113,7 @@ class BambuMqttBridgeClient: def _on_disconnect(self, client, userdata, rc): self._log.warning(f"Disconnected from MQTT broker with code: {rc}") self.connected = False + if hasattr(self, 'on_disconnect') and callable(self.on_disconnect): self.on_disconnect(client, userdata, rc) @@ -135,36 +137,73 @@ class BambuMqttBridgeClient: def _process_report_message(self, data): """Process printer status report messages""" - if 'print' in data and 'gcode_state' in data['print']: - self._device_data.print_job.gcode_state = data['print']['gcode_state'] - - if 'temperature' in data: - temp = self._device_data.temperature - temp_data = data['temperature'] - - if 'nozzle_temp' in temp_data: - temp.nozzle_temp = temp_data['nozzle_temp'] - if 'target_nozzle_temp' in temp_data: - temp.target_nozzle_temp = temp_data['target_nozzle_temp'] - if 'bed_temp' in temp_data: - temp.bed_temp = temp_data['bed_temp'] - if 'target_bed_temp' in temp_data: - temp.target_bed_temp = temp_data['target_bed_temp'] - if 'chamber_temp' in temp_data: - temp.chamber_temp = temp_data['chamber_temp'] + try: + # Update print state if available + if 'print' in data: + print_data = data['print'] + # Update printer state + if 'gcode_state' in print_data: + self._device_data.print_job.gcode_state = print_data['gcode_state'] + self._log.debug(f"Updated printer state: {print_data['gcode_state']}") + + # Process direct temperature values in print data + if 'nozzle_temper' in print_data: + self._device_data.temperature.nozzle_temp = float(print_data['nozzle_temper']) + if 'nozzle_target_temper' in print_data: + self._device_data.temperature.target_nozzle_temp = float(print_data['nozzle_target_temper']) + if 'bed_temper' in print_data: + self._device_data.temperature.bed_temp = float(print_data['bed_temper']) + if 'bed_target_temper' in print_data: + self._device_data.temperature.target_bed_temp = float(print_data['bed_target_temper']) + if 'chamber_temper' in print_data: + self._device_data.temperature.chamber_temp = float(print_data['chamber_temper']) + + # Process temperature section if available + if 'temperature' in data: + temp = self._device_data.temperature + temp_data = data['temperature'] + + if 'nozzle_temp' in temp_data: + temp.nozzle_temp = float(temp_data['nozzle_temp']) + if 'target_nozzle_temp' in temp_data: + temp.target_nozzle_temp = float(temp_data['target_nozzle_temp']) + if 'bed_temp' in temp_data: + temp.bed_temp = float(temp_data['bed_temp']) + if 'target_bed_temp' in temp_data: + temp.target_bed_temp = float(temp_data['target_bed_temp']) + if 'chamber_temp' in temp_data: + temp.chamber_temp = float(temp_data['chamber_temp']) + + self._log.debug(f"Updated temperatures - Nozzle: {self._device_data.temperature.nozzle_temp}/" + + f"{self._device_data.temperature.target_nozzle_temp}, " + + f"Bed: {self._device_data.temperature.bed_temp}/" + + f"{self._device_data.temperature.target_bed_temp}, " + + f"Chamber: {self._device_data.temperature.chamber_temp}") + except Exception as e: + self._log.error(f"Error processing report message: {str(e)}", exc_info=True) + def _process_hms_message(self, data): """Process HMS error messages""" - if 'hms' in data: - error_count = 0 - hms_errors = {"Count": 0} - - for error in data['hms']: - error_count += 1 - hms_errors[f"{error_count}-Error"] = error['msg'] + try: + if 'hms' in data: + error_count = 0 + hms_errors = {"Count": 0} - hms_errors["Count"] = error_count - self._device_data.hms.errors = hms_errors + for error in data['hms']: + error_count += 1 + if isinstance(error, dict) and 'msg' in error: + hms_errors[f"{error_count}-Error"] = error['msg'] + else: + hms_errors[f"{error_count}-Error"] = str(error) + + hms_errors["Count"] = error_count + self._device_data.hms.errors = hms_errors + + if error_count > 0: + self._log.info(f"Found {error_count} HMS errors") + except Exception as e: + self._log.error(f"Error processing HMS message: {str(e)}", exc_info=True) def connect(self, callback=None): """Connect to MQTT broker""" @@ -172,11 +211,15 @@ class BambuMqttBridgeClient: self._callbacks['callback'] = callback try: + self._log.info(f"Connecting to MQTT broker {self._host}:{self._mqtt_port}") self._mqtt_client.connect(self._host, self._mqtt_port) self._mqtt_client.loop_start() - return True + + # Wait a bit for the connection to establish + time.sleep(1) + return self.connected except Exception as e: - self._log.error(f"Failed to connect to MQTT broker: {str(e)}") + self._log.error(f"Failed to connect to MQTT broker: {str(e)}", exc_info=True) return False def disconnect(self): @@ -193,22 +236,25 @@ class BambuMqttBridgeClient: def publish(self, command): """Publishes command to device""" if not self.connected: + self._log.error("Cannot publish: Not connected to MQTT broker") return False try: topic_base = f"device/{self._device_type}/{self._serial}" if 'print' in command and 'param' in command['print']: - # Assuming commands go to command topic + # Commands go to command topic message = json.dumps(command) - self._mqtt_client.publish(f"{topic_base}/cmd", message) - return True + result = self._mqtt_client.publish(f"{topic_base}/cmd", message) + self._log.debug(f"Published command to {topic_base}/cmd: {message}") + return result.rc == mqtt.MQTT_ERR_SUCCESS + else: + self._log.warning(f"Invalid command format: {command}") except Exception as e: - self._log.error(f"Failed to publish command: {str(e)}") + self._log.error(f"Failed to publish command: {str(e)}", exc_info=True) return False -# noinspection PyBroadException class BambuVirtualPrinter: gcode_executor = GCodeExecutor() @@ -267,12 +313,10 @@ class BambuVirtualPrinter: self._serial_io.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() @property @@ -295,10 +339,6 @@ class BambuVirtualPrinter: def current_print_job(self, value): self._current_print_job = value - @property - def selected_file(self): - return self._selected_project_file - @property def has_selected_file(self): return self._selected_project_file is not None @@ -340,39 +380,19 @@ class BambuVirtualPrinter: 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): - self._state_change_queue.put(new_state) - def new_update(self, event_type): - if event_type == "event_hms_errors": + """Custom property to track connection status without modifying BambuClient directly""" + if event_type == "event_printer_data_update": + self._log.debug("Received event_printer_data_update") self._update_hms_errors() - elif event_type == "event_printer_data_update": - self._update_printer_info() - - def _update_printer_info(self): - # Verwende direkt die Telemetrie-Daten statt der BambuClient-Struktur - self.lastTempAt = time.monotonic() - - # Der Rest der Methode kann unverändert bleiben, da wir die Telemetrie - # direkt in _process_print_data aktualisieren - - # Gib den aktuellen Status detaillierter aus - 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}") - - # Rufe trotzdem die BambuClient-Daten ab, falls verfügbar - try: + # Gib den aktuellen Status detaillierter aus device_data = self.bambu_client.get_device() print_job_state = device_data.print_job.gcode_state - self._log.debug(f"BambuClient printer state: {print_job_state}") - - if ( - print_job_state == "IDLE" - or print_job_state == "FINISH" - or print_job_state == "FAILED" - ): + 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}") + if print_job_state == "IDLE" 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) @@ -380,8 +400,15 @@ class BambuVirtualPrinter: 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}") + elif event_type == "event_hms_errors": + self._log.debug("Received event_hms_errors") + bambu_printer = self.bambu_client.get_device() + if bambu_printer.hms.errors != self._last_hms_errors: + self._log.debug(f"HMS Error: {bambu_printer.hms.errors}") + for n in range(1, bambu_printer.hms.errors["Count"] + 1): + error = bambu_printer.hms.errors[f"{n}-Error"].strip() + self.sendIO(f"// action:notification {error}") + self._last_hms_errors = bambu_printer.hms.errors def _update_hms_errors(self): bambu_printer = self.bambu_client.get_device() @@ -406,18 +433,14 @@ class BambuVirtualPrinter: def _on_mqtt_connect(self, client, userdata, flags, rc): self._log.debug(f"MQTT connected with result code: {rc}") if rc == 0: + # Notify that we're connected 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 @@ -452,7 +475,6 @@ class BambuVirtualPrinter: 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) @@ -468,12 +490,10 @@ class BambuVirtualPrinter: self._process_direct_temperature_data(print_data) # Status verarbeiten - if 'gcode_state' in print_data: - self._process_print_state(print_data['gcode_state']) + self._process_print_state(print_data['gcode_state']) # Fortschritt verarbeiten - if 'mc_percent' in print_data: - self._process_progress_data(print_data) + self._process_progress_data(print_data) # Schicht-Informationen verarbeiten self._process_layer_data(print_data) @@ -481,24 +501,20 @@ class BambuVirtualPrinter: # 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") + 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) @@ -523,13 +539,12 @@ class BambuVirtualPrinter: if total_layers is not None: self.current_print_job.total_layers = total_layers - # Aktualisiere auch die pybambu-Datenstruktur + # Aktualisiere auch die pybambu-Datenstruktur, wenn vorhanden 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) @@ -603,7 +618,7 @@ class BambuVirtualPrinter: # 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'] @@ -616,7 +631,6 @@ class BambuVirtualPrinter: # 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) @@ -669,7 +683,7 @@ class BambuVirtualPrinter: 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: " + 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 @@ -684,7 +698,7 @@ class BambuVirtualPrinter: 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 @@ -714,7 +728,7 @@ class BambuVirtualPrinter: 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}, " + @@ -735,8 +749,7 @@ class BambuVirtualPrinter: 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}") - + self._log.error(f"Error updating BambuClient temperature: {e}", exc_info=True) except Exception as e: self._log.error(f"Error processing temperature data: {e}", exc_info=True) @@ -744,6 +757,7 @@ class BambuVirtualPrinter: """Verarbeitet Fortschrittsdaten aus MQTT-Nachrichten""" try: progress = -1 + if 'mc_percent' in print_data: progress = int(print_data['mc_percent']) @@ -771,7 +785,7 @@ class BambuVirtualPrinter: 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'] @@ -792,7 +806,7 @@ class BambuVirtualPrinter: 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}") + self._log.error(f"Error updating BambuClient state: {e}", exc_info=True) def _create_client_connection_async(self): self._create_client_connection() @@ -808,7 +822,7 @@ class BambuVirtualPrinter: msg = "invalid settings to start connection with Bambu Printer" self._log.debug(msg) raise ValueError(msg) - + # Check if we should use MQTT bridge mode use_mqtt_bridge = self._settings.get_boolean(["use_mqtt_bridge"]) @@ -816,6 +830,9 @@ class BambuVirtualPrinter: self._log.debug( f"connecting via mqtt bridge: {self._settings.get(['mqtt_host'])}:{self._settings.get(['mqtt_port'])}" ) + self._log.debug( + f"Creating MQTT bridge client: {self._settings.get(['mqtt_host'])}:{self._settings.get(['mqtt_port'])}" + ) # Create MQTT bridge client bambu_client = BambuMqttBridgeClient( device_type=self._settings.get(["device_type"]), @@ -828,6 +845,9 @@ class BambuVirtualPrinter: self._log.debug( f"connecting via local mqtt: {self._settings.get_boolean(['local_mqtt'])}" ) + self._log.debug( + f"Creating standard BambuClient with local_mqtt: {self._settings.get_boolean(['local_mqtt'])}" + ) bambu_client = BambuClient( device_type=self._settings.get(["device_type"]), serial=self._settings.get(["serial"]), @@ -843,7 +863,7 @@ class BambuVirtualPrinter: email=self._settings.get(["email"]), 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) bambu_client.connect(callback=self.new_update) @@ -869,7 +889,6 @@ class BambuVirtualPrinter: serial = self._settings.get(["serial"]) topic = f"device/{serial}/request" - return self.publish_mqtt(topic, command) def __str__(self): @@ -879,6 +898,10 @@ class BambuVirtualPrinter: options={ "device_type": self._settings.get(["device_type"]), "host": self._settings.get(["host"]), + "local_mqtt": self._settings.get_boolean(["local_mqtt"]), + "region": self._settings.get(["region"]), + "email": self._settings.get(["email"]), + "auth_token": self._settings.get(["auth_token"]), }, ) @@ -894,7 +917,6 @@ class BambuVirtualPrinter: if self._settings.get_boolean(["simulateReset"]): for item in self._settings.get(["resetLines"]): self.sendIO(item + "\n") - self._serial_io.reset() def write(self, data: bytes) -> int: @@ -926,6 +948,7 @@ class BambuVirtualPrinter: file_info = self._project_files_view.get_file_by_stem( file_path, [".gcode", ".3mf"] ) + if ( self._selected_project_file is not None and file_info is not None @@ -1025,7 +1048,6 @@ class BambuVirtualPrinter: self.start_continuous_temp_report(interval) else: self.stop_continuous_temp_report() - self.report_print_job_status() return True @@ -1126,7 +1148,7 @@ class BambuVirtualPrinter: acc_mag = acc_mag_scaled(speed_percentage) feed = feed_rate_scaled(speed_percentage) # speed_level = 1.539 * (acc_mag**2) - 0.7032 * acc_mag + 4.0834 - return f"M204.2 K{acc_mag:.2f}\nM220 K{feed:.2f}\nM73.2 R{speed_frac:.2f}\n" # M1002 set_gcode_claim_speed_level ${speed_level:.0f}\n + return f"M204.2 K{acc_mag:.2f}\nM220 K{feed:.2f}\nM73.2 R{speed_frac:.2f}\n" # M1002 set_gcode_claim_speed_level ${speed_level:.0f}\n" speed_command = speed_adjust(percent) @@ -1237,7 +1259,6 @@ class BambuVirtualPrinter: def _processTemperatureQuery(self) -> bool: # Debug-Log hinzufügen, um zu prüfen, ob die Methode aufgerufen wird self._log.debug(f"Processing temperature query - connected: {self.is_connected}") - # Aktuelle Temperaturdaten ausgeben self._log.debug(f"Current temperature data: Nozzle={self._telemetry.temp[0]}/{self._telemetry.targetTemp[0]}, " + f"Bed={self._telemetry.bedTemp}/{self._telemetry.bedTargetTemp}") @@ -1246,7 +1267,6 @@ class BambuVirtualPrinter: output = self._create_temperature_message() self._log.debug(f"Sending temperature message: {output.strip()}") self.sendIO(output) - return True def close(self): @@ -1257,7 +1277,6 @@ class BambuVirtualPrinter: 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: