Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fakebambu

A Go emulator for a Bambu Lab 3D printer running in LAN-only Developer Mode. It exposes the same network surface a real printer does — an MQTT broker for status/commands, an FTPS server for file uploads, and a UDP discovery responder — so it can be added to Bambu Studio, Bambu Handy, or third-party tools like Bambuddy using just an IP address, serial number, and access code.

It does not slice, print, or execute anything real. Print jobs are simulated (temperatures ramp, progress advances, filament is "consumed") purely for believable status reporting.

Docker Compose

services:
  fakebambu-p2s:
    image: jc21/fakebambu:latest
    network_mode: host
    command: ["-model", "p2s"]

Build

go build -o fakebambu .

Run

./fakebambu -model p1s

On startup it generates (or uses the values you pass) a serial number and LAN access code and prints them to the console, along with every IPv4 address the host can be reached on (or just the one address, if you passed -interface):

==============================================================
 fakebambu - Bambu Lab printer emulator
==============================================================
 Model:          P1S (p1s)
 Device name:    fakebambu-p1s
 Serial number:  01S188063640885  (generated)
 Access code:    HZUG1EHB  (generated)
--------------------------------------------------------------
 Listening on all interfaces (0.0.0.0); reachable at:
   - 192.168.0.194
 MQTT(S):        port 8883  (user "bblp", password = access code)
 FTPS:           port 990  (implicit TLS, same credentials)
 Discovery:      UDP port 2021 (SSDP-style, best-effort)
 Preview:        http://<ip>:8990/thumbnail  (poster of the active print)
==============================================================

In Bambu Studio: Add printer → enter this IP, the serial number, and the access code. Select the matching model from the printer type dropdown.

Flags

Flag Default Description
-model (required) p1p, p1s, p2s, x1c, x1e, x2d, a1, a1m, a2l, h2d, h2c, h2s
-serial generated Serial number to present
-access-code generated LAN access code
-name fakebambu-<model> Device name advertised over discovery
-interface 0.0.0.0 Interface name (e.g. eth1) or IP address to bind every listener to; 0.0.0.0 binds all interfaces
-mqtt-port 8883 MQTT(S) broker port
-ftp-port 990 FTPS (implicit TLS) port
-ftp-passive-port-start 50100 First port of the 100-port FTP passive-data range
-discovery-port 2021 UDP discovery port
-no-discovery off Disable the discovery broadcaster/responder
-data-dir ./data Where uploaded print files are written
-print-queue (none) Path to a YAML/JSON playlist of prints to emulate on a loop
-preview-port 8990 Port for the HTTP poster/thumbnail preview server
-log-level info debug, info, warn, error

Running multiple instances on one host

Each listener binds to -interface (default 0.0.0.0, every interface). Point each instance at a different NIC and they can share every other flag verbatim — identical port numbers included — since a socket bound to one interface's address doesn't collide with another bound to a different one:

./fakebambu -model p1s -interface eth1 -mqtt-port 8883 -ftp-port 990
./fakebambu -model x1c -interface eth2 -mqtt-port 8883 -ftp-port 990

-interface accepts either an interface name (eth1) or a literal IP address (192.168.1.50) — whichever's more convenient.

One thing that does not follow the interface split: FTP passive-mode data connections. ftpserverlib always binds its passive listener to 0.0.0.0 regardless of the control connection's address, so two instances sharing a passive port would collide even on different interfaces. Give each instance a distinct -ftp-passive-port-start (100 ports apart is enough — the default range is 100 wide) to avoid that:

./fakebambu -model p1s -interface eth1 -ftp-passive-port-start 50100
./fakebambu -model x1c -interface eth2 -ftp-passive-port-start 50200

Simulating a run of prints

Pass -print-queue examples/print-queue.yaml (or your own file) and the emulator behaves like a printer that's constantly busy: it works through the list one job at a time and loops back to the first when it reaches the end, forever. Real time drives everything — mc_percent, layer_num, and temperatures all advance relative to how far into each job's duration / layers the current moment is, exactly as they do during a normal print (IDLERUNNINGFINISH, with a short idle pause between jobs).

prints:
  - name: "3DBenchy"
    file: "benchy.gcode.3mf"
    duration: 45m
    layers: 220
    thumbnail: "posters/benchy.png"
    material: PLA
  - name: "Articulated Dragon"
    file: "dragon.gcode.3mf"
    duration: 6h30m
    layers: 1800
    thumbnail: "posters/dragon.png"
    material: PETG

See examples/print-queue.yaml for the full field reference. JSON works too — just use a .json extension with the same field names. thumbnail paths resolve relative to the playlist file's own directory, so you can keep posters alongside it.

Commands sent over MQTT (pause/resume/stop, or a manual print start from Bambu Studio) still work normally on top of the queue: pausing or resuming affects whichever job is currently running, and once the printer goes back to idle the queue driver picks up with the next job in the list.

Print preview / poster image

Just put the file wherever thumbnail: in the playlist points — e.g. for examples/print-queue.yaml's thumbnail: "posters/benchy.png", that's examples/posters/benchy.png (paths resolve relative to the playlist file's own directory). Nothing else to configure.

When that job starts, the emulator packages the image into a minimal .gcode.3mf and writes it to the virtual SD card (<data-dir>/uploads/<job name>.gcode.3mf) — the same filename convention and embedded-thumbnail path (Metadata/plate_1.png) real Bambu Studio uses when it FTPs a sliced project to a printer. Bambuddy fetches print covers by FTP-downloading exactly that file (tried at /, /cache/, /model/, /data/, keyed off the print's subtask_name) and reading the thumbnail back out of it — this was confirmed directly against Bambuddy's own source (backend/app/services/bambu_ftp.py, backend/app/api/routes/printers.py, backend/app/services/archive.py). So a queue job with a thumbnail set just works in Bambuddy's Archives/cover UI with no extra steps; a plain curl/browser view is also available at:

GET http://<printer-ip>:8990/thumbnail   # fakebambu-only convenience, see caveats
GET http://<printer-ip>:8990/status      # current print-state snapshot as JSON

This only happens for -print-queue jobs, since those are the only prints with no real file behind them. A print actually sent from Bambu Studio already FTPs its own real, fully-thumbnailed 3MF — nothing to do there.

Also relevant: the MQTT report's home_flag sets bit 8 (SD card present) and bit 11 (store_to_sdcard, i.e. "Store sent files on external storage"), and includes a storage: {free, total} block — both read by Bambuddy's connection diagnostic and by Bambu Studio's own pre-flight before it will attempt an FTP send at all. fakebambu always reports these as enabled, since (unlike a real printer) every upload here is unconditionally persisted to disk.

What's emulated

  • MQTT broker (internal/mqtt) — an embedded broker (not just a client) on device/{serial}/report / device/{serial}/request, exactly like the mosquitto instance running on a real printer. Requires username bblp and password equal to the access code, matching Bambu Studio's LAN login. It pushes a full status report once a second (temperatures, gcode_state, print progress, AMS) and answers pushall, get_version, led_ctrl, project_file/print/pause/resume/stop, and print_speed. Any other command is logged and safely ignored rather than erroring.
  • FTPS server (internal/ftps) — implicit-TLS FTP on port 990, same bblp/access-code credentials, backing onto a real directory (<data-dir>/uploads) so uploaded .gcode.3mf files from Bambu Studio's "send to printer" flow succeed and are saved for inspection.
  • Discovery (internal/discovery) — UDP SSDP-style broadcaster/responder on port 2021, sending periodic "alive" announcements and replying to M-SEARCH probes.
  • Simulated state (internal/state) — idle temperatures with small jitter, AMS units/trays with filament type/color/remaining %, and a simulated print cycle (IDLE → RUNNING → FINISH) with layer count, percent complete, and temperature ramps, all driven by real elapsed time against a configurable duration/layer count (3 minutes / 100 layers by default for an ad-hoc MQTT-triggered print; whatever a -print-queue job specifies otherwise).
  • Print queue / autoplay (internal/queue, internal/autoplay) — optional looping playlist of simulated print jobs, see "Simulating a run of prints" above.
  • Synthetic 3MF / SD card (internal/threemf) — packages a queue job's poster image into a minimal .gcode.3mf (valid zip/OPC package, embedded Metadata/plate_1.png + _small.png) and writes it to the FTPS upload directory under the print's name, so FTP-based cover-fetching tools (see Bambuddy above) find a real file, not just a report field.
  • Preview server (internal/preview) — plain HTTP endpoint serving the active job's poster/thumbnail image and a JSON status snapshot.
  • TLS (internal/certs) — a fresh self-signed certificate is generated each run; Bambu Studio/Handy don't validate the LAN certificate chain, so this matches real printers (which also use self-signed certs).

Protocol fidelity — read this

Bambu's LAN protocol is not publicly documented; everything here is reconstructed from community reverse-engineering (Home Assistant's Bambu Lab integration, various write-ups, and captured traffic descriptions), not an official spec. In particular:

  • The MQTT JSON schema (internal/state/state.go Snapshot(), internal/mqtt/commands.go) covers the fields most tooling reads (temperatures, gcode_state, mc_percent, AMS trays, lights) but is not guaranteed byte-for-byte identical to a real printer's report.
  • Serial number prefixes (internal/profile/profile.go) and per-model ModelCode values are plausible-looking placeholders, not verified real allocations. They don't affect connectivity — Bambu Studio's manual "Add printer by IP" flow has you pick the model explicitly.
  • The discovery packet format (internal/discovery/ssdp.go) is a best effort; if Bambu Studio's auto-discovery doesn't pick up the emulator, use manual IP entry — that path only depends on MQTT (to verify the connection) and works reliably.
  • P2S/X2D/H2C/A2L exist as real models; their internal/profile/profile.go entries (dual-nozzle flag, AMS count, ethernet-implies-nothing-here, rod type) were cross-checked against Bambuddy's own model database, which is more reliable than guessing. Bed size, max temps, and firmware version on those four are still placeholders in the same spirit as every other profile — Bambuddy's file doesn't cover physical dimensions, and those numbers don't affect connectivity anyway.
  • The /thumbnail and /status preview endpoints (internal/preview) are a fakebambu-only convenience, not a reconstruction of any real Bambu camera/thumbnail protocol — real printers most likely expose this via the local camera stream (port 6000-ish) rather than plain HTTP, but that protocol isn't public either. If you need Bambu Studio itself to render the poster, this won't do that; it's meant for scripts/dashboards you control that are testing against the emulator. Bambuddy specifically does not need this endpoint — it gets covers via the synthetic-3MF/FTP path described above, which was verified against its actual source rather than guessed.

If you have real captured traffic from a printer, matching the JSON schema and discovery packet more precisely against it is straightforward — the relevant code is centralized in the three files listed above.

About

A service immitating a Bambu 3d Printer on the network

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages