Skip to content

Instantly share code, notes, and snippets.

@Alex-Schaefer
Last active March 16, 2025 20:41
Show Gist options
  • Save Alex-Schaefer/72a9e2491a42da2ef99fb87601955cc3 to your computer and use it in GitHub Desktop.
Save Alex-Schaefer/72a9e2491a42da2ef99fb87601955cc3 to your computer and use it in GitHub Desktop.
Python implementation to send a fake SSDP discovery message to Bambu Studio or Orca Slicer
# Derived from this: https://github.com/gashton/bambustudio_tools/blob/master/bambudiscovery.sh
# Python implementation without need for linux
# Send the IP address of your BambuLab printer to port 2021/udp, which BambuStudio is listens on.
# Ensure your PC has firewall pot 2021/udp open. This is required as the proper response would usually go to the ephemeral source port that the M-SEARCH ssdp:discover message.
# But we are are blindly sending a response directly to the BambuStudio listening service port (2021/udp).
# Temporary solution to BambuStudio not allowing you to manually specify the Printer IP.
# Usage:
# 0. Edit the constants below with your printer SN, model name and the friendly name you want to see in Studio / Orca Slicer
# 1. start Bambu Studio / Orca Slicer
# 2. python bambu-ssdp-discovery.py PRINTER_IP
# 3. connect to the printer
# The script needs to be run every time you start Studio or Orca Slicer
import sys
import socket
from datetime import datetime
TARGET_IP = "127.0.0.1" # Change this to the IP of the computer with the printer software. If you're running this on the same computer, leave it as is.
PRINTER_USN = "YOUR_PRINTER_SN" # This is the serial number of the printer. https://wiki.bambulab.com/en/general/find-sn
PRINTER_DEV_MODEL = "3DPrinter-X1-Carbon" # "3DPrinter-X1-Carbon", "3DPrinter-X1", "C11" (for P1P), "C12" (for P1S), "C13" (for X1E), "N1" (A1 mini), "N2S" (A1)
PRINTER_DEV_NAME = "X1C-1" # The friendly name displayed in Bambu Studio / Orca Slicer. Set this to whatever you want.
PRINTER_DEV_SIGNAL = "-44" # Fake wifi signal strength
PRINTER_DEV_CONNECT = "lan" # printer is in lan only mode
PRINTER_DEV_BIND = "free" # and is not bound to any cloud account
PRINTER_IP = None # If you want to hardcode the printer IP, set it here. Otherwise, pass it as the first argument to the script.
TARGET_PORT = 2021 # The port used for SSDP discovery
def send_udp_response(response):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
try:
sock.sendto(response.encode(), (TARGET_IP, TARGET_PORT))
print("UDP packet sent successfully.")
except socket.error as e:
print("Error sending UDP packet:", e)
def resolve_and_validate(input_str):
"""Resolve a hostname or FQDN to an IP address, or just return the IP address after validating it."""
try:
# This will work for both FQDN and hostname
return socket.gethostbyname(input_str)
except socket.gaierror:
# If resolution fails, check if it's a valid IP
try:
socket.inet_aton(input_str)
return input_str # It's a valid IP, so return it as-is
except socket.error:
print(f"Unable to resolve {input_str} to an IP address.")
sys.exit(2)
def main():
if PRINTER_IP is None:
# If PRINTER_IP is not set, check if it was passed as an argument
if len(sys.argv) == 2:
provided_ip = sys.argv[1]
else:
print("Please specify your printer's IP, FQDN or hostname.\nusage:", sys.argv[0], "<PRINTER_IP>\nAlternatively, set PRINTER_IP in the script.")
sys.exit(2)
else:
# If PRINTER_IP is set, use that
provided_ip = PRINTER_IP
# Now that we have a printer IP, FQDN or hostname, resolve and validate it
printer_ip = resolve_and_validate(provided_ip)
response = (
f"HTTP/1.1 200 OK\r\n"
f"Server: Buildroot/2018.02-rc3 UPnP/1.0 ssdpd/1.8\r\n"
f"Date: {datetime.now()}\r\n"
f"Location: {printer_ip}\r\n"
f"ST: urn:bambulab-com:device:3dprinter:1\r\n"
f"EXT:\r\n"
f"USN: {PRINTER_USN}\r\n"
f"Cache-Control: max-age=1800\r\n"
f"DevModel.bambu.com: {PRINTER_DEV_MODEL}\r\n"
f"DevName.bambu.com: {PRINTER_DEV_NAME}\r\n"
f"DevSignal.bambu.com: {PRINTER_DEV_SIGNAL}\r\n"
f"DevConnect.bambu.com: {PRINTER_DEV_CONNECT}\r\n"
f"DevBind.bambu.com: {PRINTER_DEV_BIND}\r\n\r\n"
)
print(f"Sending response with PRINTER_IP={printer_ip} to {TARGET_IP}:{TARGET_PORT}")
send_udp_response(response)
if __name__ == "__main__":
main()
@fire1ce
Copy link

fire1ce commented Sep 7, 2023

Hey @Alex-Schaefer. Thanks fot the scirpt and the work.

May i suggest the following feature which will allow the use of ip/hostname/fqdn?
I've tested it and it works.

PRINTER_DEV_SIGNAL = "-44"  # Fake wifi signal strength
PRINTER_DEV_CONNECT = "lan"  # printer is in lan only mode
PRINTER_DEV_BIND = "free"  # and is not bound to any cloud account
TARGET_PC_PORT = 2021  # The port used for SSDP discovery


def resolve_to_ip(input_str):
    try:
        # This will work for both FQDN and hostname
        return socket.gethostbyname(input_str)
    except socket.gaierror:
        # If resolution fails, check if it's a valid IP
        try:
            socket.inet_aton(input_str)
            return input_str  # It's a valid IP, so return it as-is
        except socket.error:
            print(f"Unable to resolve {input_str} to an IP address.")
            sys.exit(2)


PRINTER_IP = resolve_to_ip(sys.argv[1])  # IP will be passed as an argument to the script


def send_udp_response(response):
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:

@Alex-Schaefer
Copy link
Author

Thanks, that's a nice suggestion, I've added it.
Additionally, you can now optionally hardcode the printer IP (or FQDN or hostname) in the script.

But honestly, most of the work was done by @gashton not me.
I just implemented it in Python with minor changes.

@TobyStoe
Copy link

Just a note for anyone who needs it: PRINTER_DEV_MODEL for the P1S is "C12". At least for my P1S EU-Edition.
Otherwise it would not send the g-code to the printer.

@mjrider
Copy link

mjrider commented Feb 18, 2024

after a bit of fiddling, i got my p1s which is in cloud mode to working in orcaslicer without a cloud account
( printer is registered to my partners account, which i do not use )
With this firewall in place orcaslicer doesn't see the printer anymore, and the fake ssdp announces it to the slicer, and it works as if the printer is in lan mode

ip=PRINTERIPHERE
sudo iptables -A INPUT -s ${IP} -d 255.255.255.255/32 -p udp -m udp --sport 1900 --dport 2021 -j DROP
sudo iptables -A INPUT -s ${IP} -d 239.255.255.250/32 -j DROP
python3 ~/bin//bambu-ssdp-discovery.py ${IP}

@paulvay
Copy link

paulvay commented Feb 27, 2024

For X1E - PRINTER_DEV_MODEL="C13"
Figured this out in resources/printers/C13.json

@Alex-Schaefer
Copy link
Author

For X1E - PRINTER_DEV_MODEL="C13" Figured this out in resources/printers/C13.json

Nice, I've added all the possible models to the script, based on the different json files from the BambuStudio Repo.

@RagingRoosevelt
Copy link

Pretty sure the printer_ip = resolve_and_validate(sys.argv[1]) line should be moved into the if PRINTER_IP is None: block (but not the if len(sys.argv) < 2: block).

@Alex-Schaefer
Copy link
Author

Thanks @RagingRoosevelt the logic was indeed flawed. Didn't notice that, because I'm using a slightly different script at work that reads from a config file and does some other stuff.

Updated the script, should be right now.

@bentemple
Copy link

Thank you for this <3 !!!

@Benny-Git
Copy link

I had some issues with your script, in that Orca Slicer sometimes did not recognize the printer even if the messages were sent.

I then analyzed the broadcast SSDP packages coming from the printer (a P1S), and it keeps sending packages every 5 seconds, source port always being 1900, destination port toggling between 1990 and 2021. The message content is always the same however:

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
Server: UPnP/1.0
Location: 192.168.3.8
NT: urn:bambulab-com:device:3dprinter:1
USN: <serial>
Cache-Control: max-age=1800
DevModel.bambu.com: C12
DevName.bambu.com: Drucker 001
DevSignal.bambu.com: -61
DevConnect.bambu.com: lan
DevBind.bambu.com: free
Devseclink.bambu.com: secure
DevVersion.bambu.com: 01.07.00.00
DevCap.bambu.com: 1

So I took your script, adapted it to send the message above instead, destination port 2021. I also added a while loop to be able to just keep the script running in the background for now. With this running, Orca Slicer happily finds the printer every time.

Feel free to adapt this if you want, I didn't want to just publish (you're the original, so feel free to update yours) my version. Others might be interested though.

@jrentsch
Copy link

@Benny-Git I am interested if you are willing to share.

@Alex-Schaefer
Copy link
Author

@Benny-Git Honestly, I never looked into the original discovery packet or how often / in which interval it's send, just re-implemented the packet from @gashton original script: https://github.com/gashton/bambustudio_tools/blob/master/bambudiscovery.sh

Feel free to share your script, there are several other re-implementations, for example a PowerShell version, or other Python implementations.

After reading a few, it seems like X1 and P1 series have different messages, this one is similar to your message: https://gist.github.com/narner90/4c6cd23c452df990606e9b848938b8ee
The other ones I found for X1 series have the same message as in this script.
Probably revising this script tomorrow, to check the dev model and send the right message.

I think you don't need to keep sending, if the message is right it should work with just one. But maybe if the printer's Wi-Fi connection is unstable, this could be helpful.

@Benny-Git
Copy link

@jrentsch since @Alex-Schaefer is fine with derivatives, I have published it here: https://gist.github.com/Benny-Git/9d51f470650f1fd7bb98e57a2b98f223

While one message is enough for the Orca Slicer or Bambu Studio to recognize the printer, I don't want to have to remember to start the script whenever I open my slicer. Triggering the script as part of the slicer startup might also not be enough, as the slicer's listening port would not yet be open.

I currently just leave it up and running which makes sure that whenever I reopen the slicer, it recognizes the printer quite quickly.

@skintigh
Copy link

What does one do when it seems Bambu Studio isn't listening on 2021, or at least Windows sort of thinks so? Some tools tell me BS is listening on 2021, but then Windows Filtering Platform (WFP) drops packets sent to 2021, something it does when no one is listening on that part. These drops occur before the packed even gets to the firewall.

I think this is the reason 99% of LAN-only print jobs fail at either 30% or 70% progress. Turning off the firewall, which also silently turns off WFP, allows the print to succeed. But no amount of firewall rules help as WFP is dropping the packet before the firewall sees it.

@shininghero
Copy link

Added a couple of lines to my copy to more closely match the detection specs for wireshark and SSDP. Turns out you also need a source port of 1900, or wireshark treats it as raw UDP.

def send_udp_response(response):
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        try:
            sock.bind(('', 1900))
            sock.sendto(response.encode(), (TARGET_IP, TARGET_PORT))
            print("UDP packet sent successfully.")
            sock.close()
        except socket.error as e:
            print("Error sending UDP packet:", e)

@Benny-Git
Copy link

@skintigh The port 2021 should only be needed for the initial printer detection -- after that, other ports are needed to send data to the printer. Those are initiated by Bambu Studio, and replies should come in as related. Not sure whether Windows Firewall blocks any of those by default?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment