Skip to content

PyPI version

bambu-printer-manager

bambu-printer-manager is an all in one pure python wrapper for interacting with and managing Bambu Lab printers.

Become a Sponsor

While caffiene and sleepness nights drive the delivery of this project, they unfortunately do not cover the financial expense necessary to further its development. Please consider becoming a bambu-printer-manager sponsor today!

Project Composition

Text Only
bpm/  
    bambucommands.py            # collection of constants mainly representing Bambu Lab `mqtt` request commands 
    bambuconfig.py              # contains the `BambuConfig` class used for storing configuration data
    bambulogger.py              # internal class used for logging
    bambuprinterlogger.json     # internal configuration file for configuration of logging
    bambuprinter.py             # the main `bambu-printer-manager` class `BambuPrinter` lives here
    bambuspool.py               # contains the `BambuSpool` class used for storing spool data
    bambutools.py               # contains a collection of methods used as tools (mostly internal)

    ftpsclient/
        _client.py              # internal class used for performing `FTPS` operations

Dependencies

Text Only
Python 3.12.1+

* mkdocstrings, webcolors and paho-mqtt install automatically as predefined dependencies

Installation

Text Only
pip install bambu-printer-manager

Imports

Python
from bpm.bambuconfig import BambuConfig
from bpm.bambuprinter import BambuPrinter
from bpm.bambutools import PrinterState
from bpm.bambutools import parseStage
from bpm.bambutools import parseFan

Constructor(s)

Python
config = BambuConfig(hostname="{host name}", access_code="{access code}", serial_number="{serial #}")
printer = BambuPrinter(config=config)
or variations such as:
Python
config = BambuConfig()
printer = BambuPrinter()

config.hostname = "{host name}"
config.access_code = "{access code}"
config.serial_number = "{serial #}"

printer.config = config
or
Python
config = BambuConfig()

config.hostname = "{host name}"
config.access_code = "{access code}"
config.serial_number = "{serial #}"

printer = BambuPrinter(config=config)
or even
Python
printer = BambuPrinter(config=BambuConfig(hostname="{host name}", access_code="{access code}", serial_number="{serial #}"))

Usage Patterns

You can either poll BambuPrinter periodically for data updates or rely on a callback when new data becomes available. The preferred pattern should be callback based, however both of these approaches will work fine.

Please consider the examples provided here as mere starting points.

Data Polling Example

Python
import time
import sys
import os

from bpm.bambuconfig import BambuConfig
from bpm.bambuprinter import BambuPrinter
from bpm.bambutools import PrinterState

hostname = os.getenv('BAMBU_HOSTNAME')
access_code = os.getenv('BAMBU_ACCESS_CODE')
serial_number = os.getenv('BAMBU_SERIAL_NUMBER')

if not hostname or not access_code or not serial_number:
    print()
    print("BAMBU_HOSTNAME, BAMBU_ACCESS_CODE, and BAMBU_SERIAL_NUMBER environment variables must be set")
    print()
    sys.exit(1)

config = BambuConfig(hostname=hostname, access_code=access_code, serial_number=serial_number)
printer = BambuPrinter(config=config)

printer.start_session()

while printer.state != PrinterState.CONNECTED:
    print("waiting for bpm to connect to printer", flush=True)
    if printer.internalException:
        print(f"retrying connection - reason: {printer.internalException if not printer.internalException is None else "no internal exception"}")
        printer.start_session()
    time.sleep(1)

print("\r\nrefreshing sdcard 3mf files", flush=True)
printer.get_sdcard_3mf_files()
print("sdcard 3mf files refreshed\r\n", flush=True)

print("bpm is ready for business\r\n")

while True:
    print(f"tool=[{round(printer.tool_temp, 1)}/{round(printer.tool_temp_target, 1)}] " +
          f"bed=[{round(printer.bed_temp, 1)}/{round(printer.bed_temp_target, 1)}] " + 
          f"fan=[{parseFan(printer.fan_speed)}] print=[{printer.gcode_state}] speed=[{printer.speed_level}] " +
          f"light=[{'on' if printer.light_state else 'off'}]")

    print(f"stg_cur=[{parseStage(printer.current_stage)}] file=[{printer.gcode_file}] " +
          f"layers=[{printer.layer_count}] layer=[{printer.current_layer}] " +
          f"%=[{printer.percent_complete}] eta=[{printer.time_remaining} min] " +
          f"spool=[{printer.active_spool} ({printer.spool_state})]")

    time.sleep(1)

@app.route('/api/printer')
def get_printer_info():
    global printer
    # checking both the spools tuple and recent_update 
    # states ensure we have a healthy data stream
    if printer.recent_update and printer.spools:
        return printer.toJson()
    else:
        return {"status": "warn", "reason": "no data to send"}

Callback Pattern

Python
import sys
import os

from bpm.bambuconfig import BambuConfig
from bpm.bambuprinter import BambuPrinter

hostname = os.getenv('BAMBU_HOSTNAME')
access_code = os.getenv('BAMBU_ACCESS_CODE')
serial_number = os.getenv('BAMBU_SERIAL_NUMBER')

if not hostname or not access_code or not serial_number:
    print()
    print("BAMBU_HOSTNAME, BAMBU_ACCESS_CODE, and BAMBU_SERIAL_NUMBER environment variables must be set")
    print()
    sys.exit(1)

config = BambuConfig(hostname=hostname, access_code=access_code, serial_number=serial_number)
printer = BambuPrinter(config=config)

def on_update(printer):
    print(f"tool=[{round(printer.tool_temp, 1)}/{round(printer.tool_temp_target, 1)}] " +
          f"bed=[{round(printer.bed_temp, 1)}/{round(printer.bed_temp_target, 1)}] " + 
          f"fan=[{parseFan(printer.fan_speed)}] print=[{printer.gcode_state}] speed=[{printer.speed_level}] " +
          f"light=[{'on' if printer.light_state else 'off'}]")

    print(f"stg_cur=[{parseStage(printer.current_stage)}] file=[{printer.gcode_file}] " +
          f"layers=[{printer.layer_count}] layer=[{printer.current_layer}] " +
          f"%=[{printer.percent_complete}] eta=[{printer.time_remaining} min] " +
          f"spool=[{printer.active_spool} ({printer.spool_state})]")

printer.on_update = on_update
printer.start_session()

# go do other stuff

CLI w/ Callback

Python
import json
import traceback
import time
import sys
import os

from console.utils import wait_key

from bpm.bambuconfig import BambuConfig
from bpm.bambuprinter import BambuPrinter
from bpm.bambutools import PrinterState
from bpm.bambutools import parseStage
from bpm.bambutools import parseFan

gcodeState = ""
firmware = "N/A"
ams_firmware = "N/A"

def on_update(printer):
    global firmware, ams_firmware, gcodeState

    if gcodeState != printer.gcode_state:
        gcodeState = printer.gcode_state

    if firmware != printer.config.firmware_version:
        firmware = printer.config.firmware_version
        print(f"\r\nprinter firmware: [{firmware}] serial #: [{printer.config.serial_number}]\r")
    if ams_firmware != printer.config.ams_firmware_version: 
        ams_firmware = printer.config.ams_firmware_version
        print(f"ams firmware: [{ams_firmware}]\r")

    print(f"\r\ntool=[{round(printer.tool_temp * 1.0, 1)}/{round(printer.tool_temp_target * 1.0, 1)}] " +
         f"bed=[{round(printer.bed_temp * 1.0, 1)}/{round(printer.bed_temp_target * 1.0, 1)}] " + 
         f"fan=[{parseFan(printer.fan_speed)}] print=[{printer.gcode_state}] speed=[{printer.speed_level}] " +
         f"light=[{'on' if printer.light_state else 'off'}]")

    print(f"\rstg_cur=[{parseStage(printer.current_stage)}] file=[{printer.gcode_file}] " +
          f"layers=[{printer.layer_count}] layer=[{printer.current_layer}] " +
          f"%=[{printer.percent_complete}] eta=[{printer.time_remaining} min] " +
          f"spool=[{printer.active_spool} ({printer.spool_state})]\r")

print("\r")                    

hostname = os.getenv('BAMBU_HOSTNAME')
access_code = os.getenv('BAMBU_ACCESS_CODE')
serial_number = os.getenv('BAMBU_SERIAL_NUMBER')

if not hostname or not access_code or not serial_number:
    print()
    print("BAMBU_HOSTNAME, BAMBU_ACCESS_CODE, and BAMBU_SERIAL_NUMBER environment variables must be set")
    print()
    sys.exit(1)

config = BambuConfig(hostname=hostname, access_code=access_code, serial_number=serial_number)
printer = BambuPrinter(config=config)

printer.on_update = on_update
printer.start_session()

def confirm(request):
    printer.pause_session()
    resp = input(f"Confirm [{request}] (y/n): ")
    printer.resume_session()
    return resp == "y" or resp == "Y"

special = False

while True: 
    key = wait_key()
    if key == "\x1b": 
        special = True
        continue
    if special:
        if key == "[": continue
        if key == "C":  # right arrow
            # client.publish("device/{}/request".format(SERIAL), json.dumps(MOVE_RIGHT))
            print("\rmove right\r")
        if key == "D":  # left arrow
            # client.publish("device/{}/request".format(SERIAL), json.dumps(MOVE_LEFT))
            print("\rmove left\r")
        special = False
        continue

    if not special and key == "\r":
        print("\r")

    if key == "?":
        print("\r\nCommands:\r\n")
        print("   ? = this list\r")
        print("   b = bed target temperature\r")
        print("   d = dump printer json object\r")
        print("   g = send gcode command\r")
        print("   f = fan speed (in percent)\r")
        print("   l = toggle light\r")
        print("   p = print 3MF file\r")
        print("   q = quit\r")
        print("   Q = restart without exiting")
        print("   r = request full data refresh\r")
        print("   s = change filament / spool\r")
        print("   S = change speed (1 to 4)\r")
        print("   t = tool target temperature\r")
        print("   u = unload filament / spool\r")
        print("   v = toggle verbose reporting\r")
        print("   w = wifi signal strength\r")
        print("   ! = abort job\r")
        print("   ~ = toggle subscription\n\r")

    if key == "d":
        print(json.dumps(printer, default=printer.jsonSerializer, indent=4, sort_keys=True).replace("\n", "\r\n"))

    if key == "w":
        print(f"\r\nwifi signal strength: [{printer.wifi_signal}]")

    if key == "v":
        printer.config.verbose = not printer.config.verbose

    if key == "q": 
        break

    if key == "Q":
        printer.quit()
        printer.start_session()

    if key == "l":
        printer.light_state = not printer.light_state

    if key == "t":
        printer.pause_session()
        temp = input("\r\nTool0 Target Temperature: ")
        printer.resume_session()
        if temp.isnumeric() and confirm("CHANGE_TOOL_TEMP"):
            printer.tool_temp_target = temp

    if key == "b":
        printer.pause_session()
        temp = input("\r\nBed Target Temperature: ")
        printer.resume_session()
        if temp.isnumeric():
            printer.bed_temp_target = temp

    if key == "f":
        printer.pause_session()
        speed = input("\r\nFan Speed (%): ")
        printer.resume_session()
        if speed.isnumeric():
            printer.fan_speed = speed

    if key == "r":
        printer.refresh()

    if key == "u" and confirm("UNLOAD_FILAMENT"):
        printer.unload_filament()

    if key == "s":
        printer.pause_session()
        slot = input("\r\nTarget Slot: ")
        printer.resume_session()
        if len(slot) > 0:
            printer.load_filament(slot)

    if key == "g":
        printer.pause_session()
        gcode = input("\r\nGcode: ")
        printer.resume_session()
        if len(gcode) > 0:
            printer.send_gcode(gcode)

    if key == "p":
        printer.pause_session()
        name = input("\r\n3MF filename to print: ")
        if len(name) > 0:
            bed = input("\rBed type (1=High Temp Plate, 2=Textured PEI Plate): ")
            if len(bed) > 0 and bed.isnumeric():
                ams = "[{}]".format(input("\rAMS mapping ([-1/0], [-1/1], [-1/2], [-1/3]): "))
                if len(ams) > 0:
                    printer.resume_session()
                    printer.print_3mf_file(name, bed, ams)
                    continue
        printer.resume_session()

    if key == "S":
        printer.pause_session()
        speed = input("\r\nNew speed (1=silent 2=standard 3=sport 4=ludicrous): ")
        printer.resume_session()
        if len(speed) > 0 and speed in ("1", "2", "3", "4"):
            printer.speed_level = speed

    if key == "!" and confirm("STOP"):
        printer.stop_printing()

    if key == "~":
        if printer.state == PrinterState.PAUSED:
            printer.resume_session()
            print("\rsession resumed\r")
        else:
            printer.pause_session()
            print("\rsession paused\r")

printer.quit()