Skip to content

Commit

Permalink
Updating the bambu reconnect logic to be less aggressive when trying …
Browse files Browse the repository at this point in the history
…to reconnect to the printer.
  • Loading branch information
QuinnDamerell committed Jan 24, 2025
1 parent 8f827a5 commit d083535
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 11 deletions.
24 changes: 16 additions & 8 deletions bambu_octoeverywhere/bambuclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,23 +155,26 @@ def _ClientWorker(self):
except Exception as e:
if isinstance(e, ConnectionRefusedError):
# This means there was no open socket at the given IP and port.
self.Logger.error(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}, we will retry in a bit. "+str(e))
# This happens when the printer is offline, so we only need to log sometimes.
self.Logger.warning(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}, we will retry in a bit. "+str(e))
elif isinstance(e, TimeoutError):
# This means there was no open socket at the given IP and port.
self.Logger.error(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}, we will retry in a bit. "+str(e))
self.Logger.warning(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}, we will retry in a bit. "+str(e))
elif isinstance(e, OSError) and ("Network is unreachable" in str(e) or "No route to host" in str(e)):
# This means the IP doesn't route to a device.
self.Logger.error(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}, we will retry in a bit. "+str(e))
self.Logger.warning(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}, we will retry in a bit. "+str(e))
elif isinstance(e, socket.timeout) and "timed out" in str(e):
# This means the IP doesn't route to a device.
self.Logger.error(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr} due to a timeout, we will retry in a bit. "+str(e))
self.Logger.warning(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr} due to a timeout, we will retry in a bit. "+str(e))
else:
# Random other errors.
Sentry.Exception(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}. We will retry in a bit.", e)

# Sleep for a bit between tries.
# The main consideration here is to not log too much when the printer is off. But we do still want to connect quickly, when it's back on.
localBackoffCounter = min(localBackoffCounter, 5)
# Note that the system might also do a printer scan after many failed attempts, which can be CPU intensive.
# Right now we allow it to ramp up to 30 seconds between retries.
localBackoffCounter = min(localBackoffCounter, 6)
time.sleep(5 * localBackoffCounter)


Expand Down Expand Up @@ -376,9 +379,11 @@ def _Publish(self, msg:dict) -> bool:
def _GetConnectionContextToTry(self) -> ConnectionContext:
# Increment and reset if it's too high.
# This will restart the process of trying cloud connect and falling back.
doPrinterSearch = False
self.ConsecutivelyFailedConnectionAttempts += 1
if self.ConsecutivelyFailedConnectionAttempts > 6:
self.ConsecutivelyFailedConnectionAttempts = 0
doPrinterSearch = True

# Get the connection mode set by the user. This defaults to local, but the user can explicitly set it to either.
connectionMode = self.Config.GetStr(Config.SectionBambu, Config.BambuConnectionMode, Config.BambuConnectionModeDefault)
Expand All @@ -392,18 +397,21 @@ def _GetConnectionContextToTry(self) -> ConnectionContext:
self.Logger.warning("We tried to connect via Bambu Cloud, but failed. We will try a local connection.")

# On the first few attempts, use the expected IP or the cloud config.
# The first attempt will always be attempt 1, since it's reset to 0 and incremented before connecting.
# Every time we reset the count, we will try a network scan to see if we can find the printer guessing it's IP might have changed.
# The IP can be empty, like if the docker container is used, in which case we should always search for the printer.
configIpOrHostname = self.Config.GetStr(Config.SectionCompanion, Config.CompanionKeyIpOrHostname, None)
if self.ConsecutivelyFailedConnectionAttempts < 4:
if doPrinterSearch is False:
# If we aren't using a cloud connection or it failed, return the local hostname
if configIpOrHostname is not None and len(configIpOrHostname) > 0:
return self._GetLocalConnectionContext(configIpOrHostname)

# If we fail too many times, try to scan for the printer on the local subnet, the IP could have changed.
# Since we 100% identify the printer by the access token and printer SN, we can try to scan for it.
# Note we don't want to do this too often since it's CPU intensive and the printer might just be off.
# We use a lower thread count and delay before each action to reduce the required load.
# Using this config, it takes about 30 seconds to scan for the printer.
self.Logger.info(f"Searching for your Bambu Lab printer {self.PrinterSn}")
ips = NetworkSearch.ScanForInstances_Bambu(self.Logger, self.LanAccessCode, self.PrinterSn)
ips = NetworkSearch.ScanForInstances_Bambu(self.Logger, self.LanAccessCode, self.PrinterSn, threadCount=25, delaySec=0.2)

# If we get an IP back, it is the printer.
# The scan above will only return an IP if the printer was successfully connected to, logged into, and fully authorized with the Access Token and Printer SN.
Expand Down
14 changes: 11 additions & 3 deletions linux_host/networksearch.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ssl
import time
import json
import socket
import logging
Expand Down Expand Up @@ -32,12 +33,17 @@ class NetworkSearch:


# Scans the local IP LAN subset for Bambu servers that successfully authorize given the access code and printer sn.
# Thread count and delay can be used to control how aggressive the scan is.
@staticmethod
def ScanForInstances_Bambu(logger:logging.Logger, accessCode:str, printerSn:str, portStr:str = None) -> List[str]:
def ScanForInstances_Bambu(logger:logging.Logger, accessCode:str, printerSn:str, portStr:str = None, threadCount:int=None, delaySec:float=0.0) -> List[str]:
def callback(ip:str):
# This is a quick fix to slow down the scan so it doesn't eat a lot of CPU load on the device while the printer is off
# and the plugin is trying to find it. But it's important this scan also be fast, for the installer.
if delaySec > 0:
time.sleep(delaySec)
return NetworkSearch.ValidateConnection_Bambu(logger, ip, accessCode, printerSn, portStr, timeoutSec=5)
# We want to return if any one IP is found, since there can only be one printer that will match the printer 100% correct.
return NetworkSearch._ScanForInstances(logger, callback, returnAfterNumberFound=1)
return NetworkSearch._ScanForInstances(logger, callback, returnAfterNumberFound=1, threadCount=threadCount)


# The final two steps can happen in different orders, so we need to wait for both the sub success and state object to be received.
Expand Down Expand Up @@ -193,7 +199,7 @@ def message(client, userdata:dict, mqttMsg:mqtt.MQTTMessage):
# testConFunction must be a function func(ip:str) -> NetworkValidationResult
# Returns a list of IPs that reported Success() == True
@staticmethod
def _ScanForInstances(logger:logging.Logger, testConFunction, returnAfterNumberFound = 0) -> List[str]:
def _ScanForInstances(logger:logging.Logger, testConFunction, returnAfterNumberFound:int = 0, threadCount:int = None) -> List[str]:
foundIps = []
try:
localIp = NetworkSearch._TryToGetLocalIp()
Expand All @@ -215,6 +221,8 @@ def _ScanForInstances(logger:logging.Logger, testConFunction, returnAfterNumberF
# if an exception was thrown in the thread, it would hang the system.
# I fixed that but also lowered the concurrent thread count to 100, which seems more comfortable.
totalThreads = 100
if threadCount is not None:
totalThreads = threadCount
outstandingIpsToCheck = []
counter = 0
while counter < 255:
Expand Down

0 comments on commit d083535

Please sign in to comment.