From 72f04367612b6998ff82a8754840df3b6f59324d Mon Sep 17 00:00:00 2001 From: Quinn Damerell Date: Sat, 25 May 2024 08:47:52 -0700 Subject: [PATCH] Adding a few features to the Bambu Connect installer. --- .vscode/launch.json | 2 +- linux_host/networksearch.py | 78 ++++++++++++++++--- .../NetworkConnectors/BambuConnector.py | 33 +++++++- 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2851e15..c54e0bb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -119,7 +119,7 @@ // The module requires this json object to be passed. // Normally the install.sh script runs, ensure everything is installed, creates a virtural env, and then runs this modlue giving it these args. // But for debugging, we can skip that assuming it's already been ran. - "{\"OE_REPO_DIR\":\"/home/pi/octoeverywhere\",\"OE_ENV\":\"/home/pi/octoeverywhere-env\",\"USERNAME\":\"pi\",\"USER_HOME\":\"/home/pi\",\"CMD_LINE_ARGS\":\"-skipsudoactions -bambu\"}" + "{\"OE_REPO_DIR\":\"/home/pi/octoeverywhere\",\"OE_ENV\":\"/home/pi/octoeverywhere-env\",\"USERNAME\":\"pi\",\"USER_HOME\":\"/home/pi\",\"CMD_LINE_ARGS\":\"-skipsudoactions -bambu -debug\"}" ] }, { diff --git a/linux_host/networksearch.py b/linux_host/networksearch.py index 87660dd..f379293 100644 --- a/linux_host/networksearch.py +++ b/linux_host/networksearch.py @@ -36,7 +36,8 @@ class NetworkSearch: def ScanForInstances_Bambu(logger:logging.Logger, accessCode:str, printerSn:str, portStr:str = None) -> List[str]: def callback(ip:str): return NetworkSearch.ValidateConnection_Bambu(logger, ip, accessCode, printerSn, portStr, timeoutSec=5) - return NetworkSearch._ScanForInstances(logger, callback) + # 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) # 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. @@ -190,7 +191,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) -> List[str]: + def _ScanForInstances(logger:logging.Logger, testConFunction, returnAfterNumberFound = 0) -> List[str]: foundIps = [] try: localIp = NetworkSearch._TryToGetLocalIp() @@ -207,25 +208,78 @@ def _ScanForInstances(logger:logging.Logger, testConFunction) -> List[str]: return foundIps ipPrefix = localIp[:lastDot+1] + # In the past, we did this wide with 255 threads. + # We got some feedback that the system was hanging on lower powered systems, but then I also found a bug where + # 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 + outstandingIpsToCheck = [] counter = 0 + while counter < 255: + # The first IP will be 1, the last 255 + counter += 1 + outstandingIpsToCheck.append(ipPrefix + str(counter)) + + # Start the threads + # We must use arrays so they get captured by ref in the threads. doneThreads = [0] - totalThreads = 255 + hasFoundRequestedNumberOfIps = [False] threadLock = threading.Lock() doneEvent = threading.Event() - while counter <= totalThreads: - fullIp = ipPrefix + str(counter) - def threadFunc(ip): + counter = 0 + while counter < totalThreads: + def threadFunc(threadId): try: - result = testConFunction(ip) + # Loop until we run out of IPs or the test is done by the bool flag. + while True: + # Get the next IP + ip = "none" + with threadLock: + # If there are no IPs left, this thread is done. + if len(outstandingIpsToCheck) == 0: + # This will invoke the finally block. + return + # If enough IPs have been found, we are done. + if hasFoundRequestedNumberOfIps[0] is True: + return + # Get the next IP. + ip = outstandingIpsToCheck.pop() + + # Outside of lock, test the IP + result = testConFunction(ip) + + # re-lock and set the result. + with threadLock: + # If successful, add the IP to the found list. + if result.Success(): + # Enure we haven't already found the requested number of IPs, + # because then the result list might have already been returned + # and we don't want to mess with it. + if hasFoundRequestedNumberOfIps[0] is True: + return + + # Add the IP to the list + foundIps.append(ip) + + # Test if we have found all of the IPs we wanted to find. + if returnAfterNumberFound != 0 and len(foundIps) >= returnAfterNumberFound: + hasFoundRequestedNumberOfIps[0] = True + # We set this now, which allows the function to return the result list + # but the other threads will run until the current test ip is done. + # That's ok since we protect the result list from being added to. + doneEvent.set() + except Exception as e: + # Report the error. + logger.error(f"Server scan failed for {ip} "+str(e)) + finally: + # Important - when we leave for any reason, mark this thread done. with threadLock: - if result.Success(): - foundIps.append(ip) doneThreads[0] += 1 + logger.debug(f"Thread {threadId} done. Done: {doneThreads[0]}; Total: {totalThreads}") + # If all of the threads are done, we are done. if doneThreads[0] == totalThreads: doneEvent.set() - except Exception as e: - logger.error(f"Server scan failed for {ip} "+str(e)) - t = threading.Thread(target=threadFunc, args=[fullIp]) + t = threading.Thread(target=threadFunc, args=(counter,)) t.start() counter += 1 doneEvent.wait() diff --git a/py_installer/NetworkConnectors/BambuConnector.py b/py_installer/NetworkConnectors/BambuConnector.py index 8e1a6a5..f6ff9da 100644 --- a/py_installer/NetworkConnectors/BambuConnector.py +++ b/py_installer/NetworkConnectors/BambuConnector.py @@ -34,7 +34,7 @@ def EnsureBambuConnection(self, context:Context): Logger.Info(f"Keeping the existing Bambu Lab printer connection setup. {ip} - {printerSn}") return - ipOrHostname, port, accessToken, printerSn = self._SetupNewBambuConnection() + ipOrHostname, port, accessToken, printerSn = self._SetupNewBambuConnection(context) Logger.Info(f"You Bambu printer was found and authentication was successful! IP: {ipOrHostname}") # Ensure the X1 camera is setup. @@ -49,7 +49,7 @@ def EnsureBambuConnection(self, context:Context): # Helps the user setup a bambu connection via auto scanning or manual setup. # Returns (ip:str, port:str, accessToken:str, printerSn:str) - def _SetupNewBambuConnection(self): + def _SetupNewBambuConnection(self, context:Context): while True: Logger.Blank() Logger.Blank() @@ -62,6 +62,9 @@ def _SetupNewBambuConnection(self): Logger.Info("Bambu Connect needs your printer's Access Code and Serial Number to connect to your printer.") Logger.Info("If you have any trouble, we are happy to help! Contact us at support@octoeverywhere.com") + # Try to get an an existing access code or SN, so the user doesn't have to re-enter them if they are already there. + oldConfigAccessCode, oldConfigPrinterSn = ConfigHelper.TryToGetBambuData(context) + # Get the access code. accessCode = None while True: @@ -75,6 +78,19 @@ def _SetupNewBambuConnection(self): Logger.Blank() Logger.Info("The access code is case sensitive - make sure to enter it exactly as shown on your printer.") Logger.Blank() + + # If there is already an access code, ask if the user wants to use it. + if oldConfigAccessCode is not None and len(oldConfigAccessCode) > 0: + Logger.Info(f"Your previously entered Access Code is: '{oldConfigAccessCode}'") + if Util.AskYesOrNoQuestion("Do you want to continue using this Access Code?"): + accessCode = oldConfigAccessCode + break + # Set it to None so we wont ask again. + oldConfigAccessCode = None + Logger.Blank() + Logger.Blank() + + # Ask for the access code. accessCode = input("Enter your printer's Access Code: ") # Validate @@ -112,6 +128,19 @@ def _SetupNewBambuConnection(self): Logger.Warn("Follow this link for a step-by-step guide to find the Serial Number for your printer:") Logger.Warn("https://octoeverywhere.com/s/bambu-sn") Logger.Blank() + + # If there is already an sn, ask if the user wants to use it. + if oldConfigPrinterSn is not None and len(oldConfigPrinterSn) > 0: + Logger.Info(f"Your previously entered Serial Number is: '{oldConfigPrinterSn}'") + if Util.AskYesOrNoQuestion("Do you want to continue using this Serial Number?"): + printerSn = oldConfigPrinterSn + break + # Set it to None so we wont ask again. + oldConfigPrinterSn = None + Logger.Blank() + Logger.Blank() + + # Ask for the sn. printerSn = input("Enter your printer's Serial Number: ") # The SN should always be upper case letters.