diff --git a/octoprint_bambu_printer/printer/bambu_virtual_printer.py b/octoprint_bambu_printer/printer/bambu_virtual_printer.py index c4ca1b8..ba4f469 100644 --- a/octoprint_bambu_printer/printer/bambu_virtual_printer.py +++ b/octoprint_bambu_printer/printer/bambu_virtual_printer.py @@ -335,6 +335,16 @@ class BambuVirtualPrinter: self._mqtt_connected = False self._bambu_client = None self._custom_connected = False + + # Store initial connection errors to avoid logging the same errors repeatedly + self._connection_error_logged = False + self._camera_error_logged = False + self._last_connection_attempt = 0 + self._connection_retry_backoff = 10 # Start with 10 seconds between retries + + # Track if we should disable camera functionality due to persistent errors + self._disable_camera = self._settings.get_boolean(["disable_camera"]) or False + self._bambu_client: BambuClient = self._create_client_connection_async() @property @@ -458,6 +468,12 @@ class BambuVirtualPrinter: client.subscribe(device_topic) self._log.debug(f"Subscribed to topic: {device_topic}") self._log.info(f"MQTT connection successful. Connected: {self.is_connected}") + + # Try to patch client for better error handling after successful connection + try: + self._patch_bambu_client_for_connection_errors() + except Exception as e: + self._log.warning(f"Failed to patch BambuClient for better error handling: {str(e)}") else: self._mqtt_connected = False self._custom_connected = False @@ -871,21 +887,43 @@ class BambuVirtualPrinter: 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"]), - host=self._settings.get(["host"]), - username=( + + # Set up client parameters + client_params = { + "device_type": self._settings.get(["device_type"]), + "serial": self._settings.get(["serial"]), + "host": self._settings.get(["host"]), + "username": ( "bblp" if self._settings.get_boolean(["local_mqtt"]) else self._settings.get(["username"]) ), - access_code=self._settings.get(["access_code"]), - 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"]), - ) + "access_code": self._settings.get(["access_code"]), + "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"]), + } + + # Add disable_camera parameter if it's enabled in settings + if self._disable_camera: + self._log.info("Camera functionality is disabled in settings") + client_params["disable_camera"] = True + client_params["enable_camera_stream"] = False + + try: + bambu_client = BambuClient(**client_params) + except TypeError as e: + # Handle the case where pybambu doesn't support these parameters yet + if "disable_camera" in str(e) or "enable_camera_stream" in str(e): + self._log.warning("This version of pybambu doesn't support camera disabling parameters, trying without them") + if "disable_camera" in client_params: + del client_params["disable_camera"] + if "enable_camera_stream" in client_params: + del client_params["enable_camera_stream"] + bambu_client = BambuClient(**client_params) + else: + raise bambu_client.on_disconnect = self.on_disconnect(bambu_client.on_disconnect) bambu_client.on_connect = self.on_connect(bambu_client.on_connect) @@ -898,6 +936,15 @@ class BambuVirtualPrinter: while connection_attempts < max_attempts: try: self._log.info(f"Connection attempt {connection_attempts + 1}/{max_attempts}...") + + # Check if we need to wait based on backoff + current_time = time.time() + if current_time - self._last_connection_attempt < self._connection_retry_backoff: + wait_time = self._connection_retry_backoff - (current_time - self._last_connection_attempt) + self._log.debug(f"Waiting {wait_time:.1f}s before retry due to backoff") + time.sleep(wait_time) + + self._last_connection_attempt = time.time() bambu_client.connect(callback=self.new_update) # Wait a moment to verify connection @@ -905,14 +952,27 @@ class BambuVirtualPrinter: if bambu_client.connected: self._log.info(f"Bambu connection successful: {bambu_client.connected}") + # Reset connection error flags on successful connection + self._connection_error_logged = False + self._connection_retry_backoff = 10 # Reset backoff on success break self._log.warning("Connection attempt failed, retrying...") connection_attempts += 1 + # Increase backoff time for future connection attempts + self._connection_retry_backoff = min(300, self._connection_retry_backoff * 2) # Cap at 5 minutes time.sleep(retry_delay) except Exception as e: - self._log.error(f"Error during connection attempt {connection_attempts + 1}: {str(e)}", exc_info=True) + if not self._connection_error_logged: + self._log.error(f"Error during connection attempt {connection_attempts + 1}: {str(e)}", exc_info=True) + self._connection_error_logged = True + else: + self._log.debug(f"Repeated connection error during attempt {connection_attempts + 1}: {str(e)}") + connection_attempts += 1 + # Increase backoff time for future connection attempts + self._connection_retry_backoff = min(300, self._connection_retry_backoff * 2) + if connection_attempts < max_attempts: time.sleep(retry_delay) @@ -922,6 +982,40 @@ class BambuVirtualPrinter: self._log.info(f"Custom connection status: {self.is_connected}") self.sendOk() + def _patch_bambu_client_for_connection_errors(self): + """Patch the BambuClient instance to handle connection errors gracefully""" + if not self._bambu_client: + return + + # If we need to modify how the library handles connection errors, particularly + # for the Chamber Image functionality, we can patch the relevant methods here + + # Check if the client has chamber image functionality and it's causing errors + if hasattr(self._bambu_client, '_chamber_image_thread') and self._bambu_client._chamber_image_thread: + original_run = None + + # Find the run function in the thread class + if hasattr(self._bambu_client._chamber_image_thread, 'run'): + original_run = self._bambu_client._chamber_image_thread.run + + # Create a wrapper that catches connection errors + def patched_run(*args, **kwargs): + try: + return original_run(*args, **kwargs) + except ConnectionRefusedError as e: + # Only log first occurrence to avoid log spam + if not self._camera_error_logged: + self._log.warning(f"Chamber image connection refused: {str(e)}. Further errors will be suppressed.") + self._camera_error_logged = True + return None + except Exception as e: + self._log.error(f"Chamber image error: {str(e)}", exc_info=True) + return None + + # Apply the patched method + self._bambu_client._chamber_image_thread.run = patched_run + self._log.debug("Patched chamber image thread to handle connection errors") + def publish_mqtt(self, topic, payload): """Publish a message to the MQTT broker""" if self._mqtt_client and self._mqtt_connected: