beam (send) + bam (fast). Homepage: beambam.boo.
A drop-in replacement for the Bambu Network Plugin + Bambu Cloud, built for the printers Bambu's first-party stack doesn't reach: X2D, H2D, H2S, H2C, P2S, X1E, and refreshed P1+X1 firmware (Jan-2025+) that requires RSA-SHA256 signed MQTT — and for the platforms Bambu doesn't ship to (aarch64 Linux / Termux / Android phones / macOS / WSL).
The repository was originally
x2d(the printer that motivated the work); the package was renamed tobeambamin v1.1.0 to reflect that the bridge supports every Bambu model, not just the X2D.
Roadmap: see ROADMAP.md for the backlog. Per-release
notes in CHANGELOG.md. How beambam stacks up against
other open clients: docs/COMPARISON.md.
Three things in one repo:
beambamCLI (pip install beambam) — a pure-Python LAN client that speaks Bambu's signed-MQTT control plane. Pause / resume / stop / G-code / temp / chamber light / AMS load / jog / FTPS upload + start print — same wire format the cloud plug-in uses, but readable, hackable, and running where you want. (x2d_bridgesurvives as a console-script alias ofbeambamfor back-compat.)- A six-surface daemon stack built on top: REST API, Server-Sent Events, Prometheus metrics, structured access log, Home Assistant MQTT auto-discovery, WebRTC chamber-camera streaming, MCP server (Claude Desktop / Cursor / Continue), and a mobile-friendly web UI with multi-printer queue, timelapse browser, AI assistant, and real-time AMS-color → filament-profile auto-resolve.
- A Termux/aarch64 BambuStudio port: source-patches against
upstream
BambuStudio v02.06.00.51, anLD_PRELOADGTK/locale shim, anlibbambu_networking.soABI shim that lets the GUI's "Connect / AMS sync / Print" buttons drive printers via this bridge.
- You own an X2D / H2D / X1E / refreshed P1+X1 and Bambu Studio refuses to connect — because the Network Plugin has no aarch64 build, OR the firmware needs RSA-SHA256 signed MQTT, OR you don't want a live cloud connection (or Developer Mode) just to drive your printer over LAN.
- You want to drive your printer from Home Assistant, Claude Desktop / MCP, a phone in your pocket, or a homelab dashboard — and you don't want to run a 1.5 GB Bambu Studio install just to hit "pause".
- You want a single small Python binary you can audit, fork, and run on anything from Termux to a Raspberry Pi to a Docker container.
| Capability | this repo | Bambu Studio + Cloud | ha-bambulab |
|---|---|---|---|
| LAN-only operation (no cloud login) | ✅ | ||
| X2D / H2D RSA-SHA256 signed MQTT | ✅ | ✅ (cloud) | ❌ blocker |
| aarch64 / Termux / Android | ✅ | ❌ (no plugin .so) | ❌ (HA Core required) |
| One-line install | ✅ | ❌ | |
| CLI control (pause/resume/temp/...) | ✅ | ❌ (GUI only) | ❌ |
| HTTP REST API + Server-Sent Events | ✅ | ❌ | ❌ |
Prometheus /metrics endpoint |
✅ | ❌ | ❌ |
| Multi-printer auto-discovery (SSDP) | ✅ | ✅ | |
| Home Assistant MQTT auto-discovery | ✅ (32+ entities) | ❌ | ✅ |
| WebRTC chamber camera (~100 ms) | ✅ | ❌ (HLS only) | ❌ |
| MCP server for Claude Desktop / IDE | ✅ | ❌ | ❌ |
| Mobile-friendly web UI | ✅ | ❌ | ❌ |
| Multi-printer print queue | ✅ | ❌ | ❌ |
| Auto-recorded timelapses + ffmpeg stitch | ✅ | partial (cloud) | ❌ |
| Natural-language assistant in UI | ✅ | ❌ | ❌ |
| Real-time AMS color → profile sync | ✅ | ✅ | ❌ |
| BambuStudio GUI on aarch64 Termux | ✅ | ❌ | n/a |
Full per-entity comparison vs ha-bambulab in
docs/HA_VS_BAMBULAB.md.
On Bambu's Jan-2025+ authorization-control firmware, the printer rejects
unsigned control commands. Almost every open client handles writes by asking the
user to turn on the printer's Developer LAN Mode — which by design severs
Bambu Cloud and disables auth verification entirely. beambam is the only one
that signs print.* and starts a print over pure LAN with no Developer Mode
and no live cloud connection at print time, by recovering the per-installation
signing key from a Bambu Handy install and RSA-encrypting the file location
(url_enc) to the printer's device cert. (That key is Bambu-issued — a prior
Handy cloud login provisioned it; beambam's setup is a one-time adb extraction
of it, not an ongoing cloud dependency.)
| Project | LAN-only¹ | Signs print.* on signed FW |
Start print, signed FW, no DevMode | X2D/H2D | aarch64/Termux |
|---|---|---|---|---|---|
| beambam | ◐¹ | ✓ (auto-recovered per-install key) | ✓ | ✓ | ✓ |
| bambu-mcp | ✓ | ◐ user must supply key | ◐ start needs DevMode | ✗ | ◐ |
| ha-bambulab / pybambu | ◐ | ◐ write needs DevMode | ✗ (DevMode only) | partial | ✓ |
| bambulabs_api | ✓ | ✗ | ✗ (DevMode only) | ✗ | ✓ |
| OrcaSlicer / BambuStudio | ◐ | ◐ Studio = cloud client | ◐ DevMode or cloud | ✓ (Studio) | ✗ |
¹ beambam runs LAN-only at print time (no live cloud, no Developer Mode); the
one-time signing-key recovery needs a Bambu Handy that's been signed in — the key
is Bambu-issued, provisioned by a prior cloud login — plus adb to read it out.
Full matrix, sources, and honest caveats: docs/COMPARISON.md.
The bridge talks to every Bambu Lab printer that exposes the standard LAN MQTT + FTPS endpoints. How the signed-MQTT requirement (Jan-2025+ firmware on most models, always-on for the X2D/H2D family) is satisfied depends on the firmware:
-
Older / unenforced firmware — the bundled publicly-leaked Bambu Connect cert (
bambu_cert.py) signs control directly; no extra setup, no cloud account. -
Authorization-control firmware (X2D/H2D family, refreshed P1/X1) — the printer rejects unsigned
print.*and wants a signing cert whose CN matches its own serial, so the shared leaked cert no longer works. You pick how to authorize writes — beambam supports either:How Trade-off A. Extract the key beambam key --adb <ip:port>reads the per-installation key out of a signed-in Bambu Handy (one time, over adb). X2D/H2D LAN print also wantsbeambam device-certfor theurl_encfile location.More setup, but cloud + remote access keep working — no Developer Mode. B. Developer Mode Turn on the printer's Developer LAN Mode; beambam then drives it over the unsigned LAN path (no key, no cert). One toggle, but it disconnects Bambu Cloud + remote access while on. Both run entirely over your LAN. Option A's key is Bambu-issued, so it only exists in a Handy that's been signed in — a prior cloud login is a one-time setup prerequisite, not a runtime dependency. See how beambam compares.
| Model | Bambu code | Signed MQTT | Status | Upload | AMS | Camera | Notes | |
|---|---|---|---|---|---|---|---|---|
| X1 / X1C | BL-P002 / BL-P001 | optional¹ | ✅ | ✅ | ✅ | ✅ | RTSPS | original H2 family |
| X1E | C13 | required | ✅ | ✅ | ✅ | ✅ | RTSPS | enterprise variant |
| P1P / P1S | C11 / C12 | required² | ✅ | ✅ | ✅ | ✅ | HTTP/MJPEG | P-series |
| P2S | N7 | required | ✅ | ✅ | ✅ | ✅ | HTTP/MJPEG | newer P-series |
| A1 / A1 mini | N2S / N1 | required² | ✅ | ✅ | ✅ | ✅ (AMS lite) | HTTP/MJPEG | direct drive |
| H2D / H2D Pro | O1D / O1E | required (dual nozzle) | ✅ | ✅ | ✅ | ✅ (multi-AMS) | RTSPS | |
| H2S / H2C | O1S / O1C2 | required | ✅ | ✅ | ✅ | ✅ | RTSPS | |
| X2D | N6 | required | ✅ | ✅ | ✅ | ✅ (multi-AMS, dynamic map) | RTSPS | primary target — beambam analyze was developed against X2D 3-color prints |
¹ X1/X1C with pre-2025 firmware accept unsigned MQTT; the bridge signs
anyway with the leaked Bambu Connect cert (zero overhead, forward-compat).
² P1S/P1P/A1 enforcement varies by firmware. On pre-2025 firmware the
leaked cert suffices; on refreshed (authorization-control) firmware,
print.* needs the per-installation key from beambam key (and X2D/H2D
LAN print also needs beambam device-cert).
If your model isn't listed and it has the LAN MQTT switch in
Settings → Network, it almost certainly works — open an issue with the
X-BBL-Device-Model header from status.
pip install beambam # core bridge + CLI
pip install beambam[all] # + HA publisher, MCP, web UI, AI assistant
beambam --versioncat >~/.x2d/credentials <<'EOF'
[printer]
ip = 192.168.1.42
code = 12345678
serial = 03ABC0001234567
EOF
beambam status # pull live state
beambam analyze model.3mf # NEW v1.1.0 — preview the print plan
beambam print model.3mf # FTPS upload + signed MQTT startbeambam daemon --http 0.0.0.0:8765 \
--queue --timelapse \
--auth-token "$(openssl rand -hex 32)"
xdg-open http://localhost:8765/# One-line install. Pulls latest release tarball + builds bridge runtime;
# idempotent. Required for the BambuStudio GUI + libbambu_networking.so
# shim — the pip package alone covers everything except the GUI.
bash <(curl -fsSL https://raw.githubusercontent.com/tribixbite/beambam/main/install.sh)- Python 3.10+ (3.10–3.13 tested in CI on Ubuntu + macOS)
paho-mqtt >= 2.0+cryptography >= 41(auto-installed)- For Termux GUI: aarch64-Termux + termux-x11 + the release tarball
- For HA integration: any MQTT broker reachable from the daemon
Five short H.264 MP4s in docs/demos/ — render with
PYTHONPATH=. python3.12 runtime/demos/render.py to regenerate.
| File | Duration | What |
|---|---|---|
docs/demos/cli_demo.mp4 |
47s | bridge CLI: status / chamber-light / print / daemon up |
docs/demos/gui_demo.mp4 |
16s | BambuStudio Termux port: Prepare tab, Device tab, sidebar shrink, X2D preset |
docs/demos/mcp_demo.mp4 |
57s | MCP stdio handshake: initialize → tools/list → tools/call status |
docs/demos/webui_demo.mp4 |
15s | thin web UI at S25 Ultra portrait + landscape + tablet viewport |
docs/demos/ha_demo.mp4 |
55s | Home Assistant registry: 32 entities + 1 Device with live values |
| Feature | Doc |
|---|---|
| Quick start | docs/QUICKSTART.md |
| Web UI | docs/WEB_UI.md |
| MCP server (Claude Desktop, Cursor, ...) | docs/MCP.md |
| WebRTC streaming | docs/WEBRTC.md |
| Home Assistant — overview | docs/HA.md |
| Home Assistant — install + dashboard guide | docs/HA_SETUP.md |
| HA vs ha-bambulab feature parity | docs/HA_VS_BAMBULAB.md |
| Cloud bridge (cloud-state, cloud-print) | docs/CLOUD_BRIDGE.md |
| Local control paths (LAN MQTT, FTPS, RTSPS, LVL_Local) | docs/LOCAL_CONTROL_PATHS.md |
| Signed vs unsigned MQTT — leaked-key truth table | docs/SIGNED_VS_UNSIGNED.md |
| X2D runtime pipeline (canonical reference) | docs/X2D_RUNTIME_PIPELINE.md |
| Multi-printer setup | docs/MULTI_PRINTER.md |
| Print queue | docs/QUEUE.md |
| Timelapse browser | docs/TIMELAPSE.md |
| AI assistant | docs/ASSISTANT.md |
| AMS color sync | docs/COLORSYNC.md |
| BambuStudio Termux port | this README §"BambuStudio Termux port" below |
The Bambu Network Plug-in
.sois shipped only for x86_64 Linux and arm64 macOS. On aarch64 Termux it has no equivalent build, so out of the box BambuStudio's GUI cannot connect to a LAN printer or sync AMS spool data. The bridge in this repo replaces what the plug-in would have done for the LAN-only path.
If you want the BambuStudio GUI itself to launch + connect on aarch64
Termux (rather than only driving the printer headlessly via the bridge),
this repo also ships the source patches and LD_PRELOAD shim required.
Layout:
.
├── patches/ # 50+ unified diffs against upstream BambuStudio
│ │ # (touchscreen-tap slop, AMSControl width,
│ │ # GLCanvas3D termux-x11 paint, MainFrame
│ │ # Ctrl+R/Ctrl+P slice/print accelerators,
│ │ # Plater sidebar, deps build wiring, …)
│ ├── Button.cpp.termux.patch # touch-target 15-px slop
│ ├── AxisCtrlButton.cpp.termux.patch # touch-target 15-px slop
│ ├── SideButton.cpp.termux.patch # touch-target 15-px slop
│ ├── TabButton.cpp.termux.patch # touch-target 15-px slop
│ ├── BBLTopbar.cpp.termux.patch # vertical Print/plate stack
│ ├── BBLTopbar.hpp.termux.patch
│ ├── MainFrame.cpp.termux.patch # Ctrl+R/Ctrl+P + sidebar tog
│ ├── GLCanvas3D.cpp.termux.patch # termux-x11 force-paint
│ ├── AMSControl.cpp.termux.patch # AMS strip width
│ ├── (… and ~40 more — patches/ for the full set)
├── runtime/
│ ├── preload_gtkinit.c # LD_PRELOAD shim: GTK pre-init, locale fix,
│ │ # wxLocale ICU bypass, wx 3.3 assert silencer,
│ │ # hidden config_wizard_startup override
│ ├── network_shim/ # libbambu_networking.so ABI shim — lets the
│ │ # GUI's Connect/Print/AMS sync buttons drive
│ │ # the bridge over a Unix socket (#1)
│ ├── webrtc/ # aiortc gateway (#45)
│ ├── webui/ # web UI test harnesses (#46-48)
│ ├── ha/ # MQTT auto-discovery publisher (#50-54)
│ ├── mcp/ # MCP stdio server (#42-44)
│ ├── queue/ # multi-printer queue manager (#55)
│ ├── timelapse/ # auto-timelapse recorder (#56)
│ ├── assistant/ # natural-language MCP router (#57)
│ └── colorsync/ # AMS color → filament profile (#58)
├── web/ # static HTML/CSS/JS for the thin web UI
├── docs/ # per-feature documentation
├── run_gui_clean.sh # canonical GUI launcher
├── beambam/ # signed-MQTT LAN client + multi-surface daemon
│ ├── cli/ # subcommand handlers (control / cloud / info / daemon / lan)
│ ├── mqtt.py # X2DClient + sign_payload (Bambu's signed control plane)
│ ├── ftps.py # implicit-TLS FTPS for SD-card upload/download
│ └── … # 31 modules, see `python -m beambam.cli --help`
├── bambu_cert.py # publicly-leaked Bambu Connect signing key
├── lan_upload.py # FTPS:990 implicit-TLS uploader (helper subset)
├── lan_print.py # upload + start_print combo
├── make_frame.py # generates a debossed picture-frame STL
├── inject_thumbnails.py # injects the 5 PNG thumbnails firmware needs
├── resolve_profile.py # flatten BambuStudio profile inheritance
└── test_signed_mqtt.py # diagnostic: pushall with RSA-SHA256 signature
bs-bionic/ (the BambuStudio source tree) and dist/ (built tarball) are
gitignored — the patches in patches/ reproduce the source changes against
a clean clone.
TL;DR runtime guide: see docs/QUICKSTART.md for the install → credentials → launch → connect → print walkthrough, plus copy-pasteable shortcuts for every print-control verb, the camera proxy URLs, and a troubleshooting table.
bash <(curl -fsSL https://raw.githubusercontent.com/tribixbite/beambam/main/install.sh)
Idempotent — runs the platform check, pkg installs the runtime deps,
fetches the latest release tarball, verifies SHA-256, drops the
libbambu_networking.so + libBambuSource.so shims into
~/.config/BambuStudioInternal/plugins/, pre-seeds
~/.config/BambuStudioInternal/BambuStudio.conf with
the X2D model entry, writes a chmod-600 ~/.x2d/credentials skeleton
for you to fill in, and (if Termux:Boot is installed) drops a boot-time
launcher for the bridge daemon. Re-run any time to upgrade — your
BambuStudio.conf and credentials file are preserved.
A prebuilt tarball is attached to the GitHub release:
curl -L -o bs-x2d.tar.xz \
https://github.com/tribixbite/beambam/releases/latest/download/bambustudio-x2d-termux-aarch64.tar.xz
tar -xJf bs-x2d.tar.xz
cd bambustudio-x2d-termux-aarch64
./run_gui.sh # needs termux-x11 running on DISPLAY=:1
pkg install x11-repo
pkg install \
wxwidgets gtk3 webkit2gtk-4.1 \
glew glfw mesa libllvm llvm \
glib pango cairo gdk-pixbuf atk \
fontconfig freetype libpng libjpeg libtiff \
openssl curl libcurl \
opencv libdbus libwebp \
libavcodec libswscale libavutil ffmpeg \
python python-cryptography xdotool \
openbox
pip install paho-mqtt
Why openbox is in the list: termux-x11 ships without a window manager.
Without one, wxFileDialogs open undecorated at (0,0) and stack under
the main frame so Cancel-button taps land on the main frame instead of
the dialog ("Cancel buttons don't work"); transient dialogs can't be
dragged; wxFrame::Maximize is a no-op. Openbox (≈600 KB) supplies a
minimal EWMH-aware WM that fixes all of those. run_gui.sh will spawn
it automatically on launch if it's installed and not already running.
The most version-sensitive of these is libllvm — Mesa requires it at
the same major version. If pkg upgrade ever leaves them mismatched
you'll get EGL_BAD_PARAMETER / "Unable to get EGL Display"; recover
with pacman -S libllvm llvm.
You also need a working X server. The reference setup is termux-x11
(install the Android app and run termux-x11 in a Termux session,
reachable on :1).
git clone --recurse-submodules https://github.com/bambulab/BambuStudio
cd BambuStudio
git checkout v02.06.00.51
for p in /path/to/x2d/patches/*.termux.patch; do git apply "$p"; done
# Use the bionic-toolchain steps from
# ~/.claude/skills/bambustudio-on-termux-aarch64.md
mkdir build && cd build && cmake -GNinja .. && ninja bambu-studio
gcc -fPIC -shared ../runtime/preload_gtkinit.c \
$(pkg-config --cflags --libs gtk+-3.0) -ldl -o ../runtime/libpreloadgtk.so
The Bambu Network Plug-in .so is shipped only for x86_64 Linux + arm64
macOS, so on aarch64 Termux the GUI's "Connect / AMS / Print" buttons
have nothing to dlopen. runtime/network_shim/ builds a drop-in
replacement that exports the entire NetworkAgent ABI (103 bambu_network_*
symbols + 21 ft_* symbols) and forwards the LAN-relevant calls over a
Unix-domain socket to a long-running beambam serve process (spawned
as python3 -m beambam.cli serve by the shim, with x2d_bridge /
beambam console-scripts as PATH-lookup fallbacks). Cloud-only entry
points return success-with-empty so the GUI's cloud panels stay quiet
but the LAN flow works end-to-end.
cd runtime/network_shim
make # builds libbambu_networking.so + libBambuSource.so
make install # → ~/.config/BambuStudioInternal/plugins/
A self-test harness lives at runtime/network_shim/tests/test_shim_e2e.py.
It dlopens the .so, asserts every host-expected symbol is exported, then
spawns a fresh bridge subprocess and round-trips through the protocol
against a real X2D (handshake → connect → state event → disconnect):
python3.12 runtime/network_shim/tests/test_shim_e2e.py
Wire format and op set are spec'd in runtime/network_shim/PROTOCOL.md.
The shim spawns the bridge itself if no socket is found at
$X2D_BRIDGE_SOCK (default ~/.x2d/bridge.sock), so the GUI launch
flow is just: install the .so once, then ./run_gui.sh.
Save credentials once. Either a single [printer] section (the
default, used when no --printer NAME is passed) or as many named
[printer:NAME] sections as you want:
mkdir -p ~/.x2d && chmod 700 ~/.x2d
cat > ~/.x2d/credentials <<EOF
# Single-printer setup:
[printer]
ip = 192.168.x.y
code = <8-char access code from printer screen>
serial = <printer serial from device sticker>
# OR, multiple named printers:
[printer:studio]
ip = 192.168.x.y
code = …
serial = …
[printer:basement]
ip = 10.0.0.50
code = …
serial = …
EOF
chmod 600 ~/.x2d/credentials
Pick a named printer with --printer NAME, the X2D_PRINTER env
var, or — when only one named section exists and there's no plain
[printer] — automatically.
Then:
# One-shot state dump
beambam status
# Upload + start print on AMS slot 4
beambam print myfile.gcode.3mf --slot 3
# Long-running monitor — polls every 5s, exposes JSON at http://127.0.0.1:8765/state
beambam daemon --http 127.0.0.1:8765 --quiet
# Same daemon also exposes /healthz for uptime monitoring:
# 200 + {"healthy": true, ...} when fresh state arrived recently
# 503 + {"healthy": false, ...} when MQTT silently disconnected
# Threshold is configurable via --max-staleness (default 30s).
curl http://127.0.0.1:8765/healthz
# Prometheus scrape: gauges for nozzle/bed/chamber temps, AMS humidity,
# print progress; counters for total_messages, mqtt_disconnects, ssdp_notifies.
curl http://127.0.0.1:8765/metrics
# Every HTTP hit is appended as one JSON line to ~/.x2d/access.log
# (ts, method, path, status, duration_ms, printer, authed, client).
# Rotates to access.log.1 at 1 MiB.
tail -f ~/.x2d/access.log
Credentials can also come from --ip / --code / --serial flags or
X2D_IP / X2D_CODE / X2D_SERIAL environment variables.
End-to-end one-shot: slice an STL, upload to the printer, queue the
print. The 3MF is the slicer's authoritative contract — cmd_print
auto-derives bed_type and bed initial-layer temperature from
Metadata/project_settings.config and refuses to publish if the
target AMS slot is empty or holds an incompatible filament class
(PLA-vs-PETG-vs-ABS). The hard guards (empty slot, class mismatch)
are not bypassable; soft brand/colour mismatches warn under --force.
# Standalone slicer (STL → .gcode.3mf with proper plate + filament profile)
python3.12 x2d_slice.py model.stl --out out.gcode.3mf \
--scale 1.0 # uniform geometry scale
--color "PLA Silk Gold" # Bambu colour-name resolution against
# filaments_color_codes.json (304 entries)
--bed supertack # plate type — cool/engineering/high_temp/
# textured/supertack OR int 1..5
# One-shot slice → upload → queue
beambam slice-print model.stl --slot 3 --color Gold --bed 5 --timelapse
# Send an already-sliced .gcode.3mf
beambam print model.gcode.3mf --slot 3 --timelapse
# bed_type + bed_temp auto-derived from the 3MF; refuses on AMS
# slot empty / wrong-class filament. Pass --force to bypass soft
# brand/colour mismatches; hard guards never bypassable.
Each verb is a single signed-MQTT publish — same protocol the official GUI uses, just routed through our bridge so it works on aarch64. Every command exits as soon as the printer ACKs the publish.
⚠️ LAN signing limitation. Onlysystem.*commands (chamber light, etc.) actually verify against the leaked Bambu Connect global cert.print.*commands (project_file, ams_change_filament, etc.) are verified by the firmware against per-device factory certs and silently dropped if our cert isn't in the trust list. Seedocs/SIGNED_VS_UNSIGNED.md. Forprint.*actions the working path iscloud-print(cloud broker, JWT auth, no signing) or starting the print from the touchscreen after the bridge has uploaded the 3MF.
beambam pause # pause the current print
beambam resume # resume after pause
beambam stop # abort the current print
beambam home # G28 — home all axes
beambam level # G29 — auto bed-level
beambam set-temp bed 60 # set_bed_temp 60°C
beambam set-temp nozzle 220 --idx 0 # set_nozzle_temp on extruder 0
beambam set-temp chamber 35 # M141 S35 (chamber heater)
beambam led on # ledctrl on / off / flashing
beambam led flashing --on-time 200 --off-time 200 --loops 5
# (alias kept: `chamber-light`)
beambam ams-load 0 3 --tar-temp 220 # AMS 0 / slot 3, preheat to 220
beambam ams-unload 0 --tar-temp 220 # unload from AMS 0
beambam jog X 10 # relative move +10 mm on X
beambam jog Z -5 --feed 600 # relative -5 mm on Z @ 600 mm/min
beambam gcode "M115" # send arbitrary gcode_line
# AMS slot metadata — push tray_info_idx / temps / color to one slot or
# the whole bank in one command. Profile files live in flat-profiles/.
beambam ams set 7 'flat-profiles/eSUN PLA+ @BBL X2D 0.4 nozzle.json' \
--color F98C36 # one slot, explicit color
beambam ams sync # batch from flat-profiles/ams-sync.json
beambam ams sync --dry-run # preview (no MQTT, no creds needed)
See flat-profiles/AMS_MAPPING.md for
the bundled 13-slot sync map and the schema each profile JSON must
follow.
Payload schemas reverse-engineered from
bs-bionic/src/slic3r/GUI/DeviceManager.cpp::MachineObject::command_*
so the printer behaviour matches what the official GUI sends.
The printer's chamber camera streams over RTSPS, but only after you
enable LAN-mode liveview on the printer's touchscreen
(Settings → Network → Liveview). Otherwise the stream lives on a closed
proprietary protocol on TCP/6000 that requires the x86_64-only
libBambuSource.so to decode.
Once enabled, run:
beambam cam start # bind 127.0.0.1:8766 by default (was `camera`)
beambam cam start --bind 0.0.0.0:8766 # expose on LAN (be careful!)
beambam cam start --idle-timeout 60 # stop ffmpeg after 60s of no viewers
beambam cam stop # kill running proxy (PID in ~/.x2d/cam.pid)
beambam cam # one-shot snapshot to ./cam.jpg
beambam cam watch # live in-terminal viewer
# default 30s; set very high to keep
# the pump permanently warm
The pump is on-demand since 2026-05-06: ffmpeg only starts after
the first /cam.mjpeg connect or /cam.jpg//cam.m3u8 request, and
the supervisor reaps it after --idle-timeout seconds of zero
viewers + zero one-shot endpoint hits. Subsequent requests respawn
within ~3-4 s (RTSPS reconnect + first JPEG). On phone hosts this
drops idle CPU from a continuous ~66% to 0%.
Then point any browser at http://127.0.0.1:8766/cam.mjpeg for the
multipart MJPEG stream, or /cam.jpg for a one-shot snapshot. Multiple
viewers share one ffmpeg subprocess, so bandwidth to the printer is
constant regardless of how many people are watching.
Pre-flight checks ipcam.rtsp_url via signed MQTT and bails with a
clear hint if liveview is still disabled. Pass --skip-check to bypass
(useful when MQTT is flaky but RTSP is open).
Two transport options:
--proto rtsp(default): RTSPS:322 via ffmpeg. Fast, well-tested, gated onipcam.rtsp_url != "disable".--proto local: TLS:6000 LVL_Local — Bambu's proprietary stream protocol. The TLS handshake + 80-byte auth blob + 16-byte frame framing are reverse-engineered inruntime/network_shim/lvl_local.py. The same printer-touchscreen "LAN-mode liveview" toggle gates this path too — without it the printer rejects with status0x0003013fand a clear error message.
Three HTTP outputs:
/cam.mjpeg— multipart/x-mixed-replace, browser-renderable, low latency/cam.jpg— single latest JPEG snapshot (one-shot)/cam.m3u8— HLS playlist with 2-second segments (12s sliding window). Plays in any mobile browser via<video src="…/cam.m3u8" controls>. Higher latency than MJPEG (~6-8s) but survives flaky connections better and supports seeking.
/state /healthz /cam.mjpeg /cam.jpg are open on loopback by
default (single-user local use). Binding non-loopback (--http 0.0.0.0:8765 or --bind 0.0.0.0:8766) requires a bearer token,
otherwise every request returns 401 Unauthorized with a
WWW-Authenticate: Bearer hint:
# Generate a long random token, then:
export X2D_AUTH_TOKEN=$(openssl rand -hex 32)
beambam daemon --http 0.0.0.0:8765
beambam camera --bind 0.0.0.0:8766
# From another box on the LAN:
curl -H "Authorization: Bearer $X2D_AUTH_TOKEN" http://<phone>:8765/healthz
--auth-token on the command line takes precedence over the env var.
Loopback binds keep the open path even with a token configured? — no:
once you set a token, loopback also enforces it, so the same curl
command works against both 127.0.0.1 and a LAN-exposed bind.
When no cloud session exists, bambu_network_get_user_presets
falls back to a local catalog so the GUI's AMS spool dropdown
isn't empty:
- All
instantiation: trueBBL filament profiles shipped underresources/profiles/BBL/filament/(1464 entries on the current binary — every Bambu vendor variant for every supported model). - A small community-curated set under
runtime/network_shim/data/community_filaments.jsoncovering Generic PLA / PETG / ABS / TPU / ASA plus Polymaker / Prusament / eSun / Hatchbox flavours. Edit that file to add your own.
If you sign in via cloud-login, the cloud-synced presets take
precedence — the local fallback only activates anonymously.
The bridge can talk to the Bambu Lab cloud REST API to populate the
GUI surfaces that the shim was previously stubbing — is_user_login,
get_user_id, get_user_presets, get_user_tasks. Without a cloud
session, those calls keep returning empty (LAN-only flow is unaffected).
beambam cloud-login --email me@example.com --password '…'
beambam cloud-status # confirms session, expiry, region
beambam cloud-logout # wipes ~/.x2d/cloud_session.json
Tokens land at ~/.x2d/cloud_session.json (chmod 600). The bridge
auto-refreshes the access token when it's within 5 min of expiry.
Region is auto-detected from the email TLD (.cn → CN, else US),
override with --region.
This module hits the same endpoints the open-source community already
documented (pybambu, bambu-farm-manager, OrcaSlicer). Bambu has
no published SDK — if they rotate URLs, this module needs to rotate
in lockstep with the upstream consumers.
Once cloud-login has succeeded, twelve cloud-* CLIs work off-LAN by
routing through Bambu's cloud MQTT broker. Useful when the X2D's
LAN-direct print.* is blocked by the per-installation-cert wall
(items #65 / #66 / #68): the cloud broker uses standard TLS, no per-
installation cert needed.
beambam cloud-state # remote monitoring
beambam cloud-pause # remote control
beambam cloud-print rumi_frame.gcode.3mf # full upload + print
Same --http :8765 daemon also exposes /cloud/{status,printers,state,publish}
HTTP routes for the web UI / Home Assistant. Full surface +
implementation details: docs/CLOUD_BRIDGE.md.
The bridge daemon serves a mobile-friendly remote-control surface at
/. No build step, no framework — web/index.html + web/index.js
web/index.css(~17 KB total). Live state arrives over Server-Sent Events (/state.events); pause / resume / stop / lights / heat presets / AMS slot loads POST to/control/<verb>and the daemon publishes via the long-lived MQTT client. Camera tabs let you flip between/cam.jpgsnapshot polling, native HLS at/cam.m3u8, and WebRTC at/cam.webrtc/offer.
beambam daemon --http 0.0.0.0:8765 --auth-token "$X2D_AUTH_TOKEN"
# Then open http://<phone-ip>:8765/ in any modern browser.Test harness: PYTHONPATH=. python3.12 runtime/webui/test_webui.py →
33/33 PASS, exercises every static / SSE / control route end-to-end.
The bridge ships an MCP server at runtime/mcp/server.py so any
MCP-aware client can drive prints conversationally. Eighteen tools
(status, pause, resume, stop, gcode, set_temp, chamber_light,
ams_load/unload, jog, upload, print, camera_snapshot, list_printers,
healthz, metrics, home, level) plus two resources (x2d://state,
x2d://camera/snapshot).
Smoke-test the server before wiring it in:
python3.12 runtime/mcp/test_mcp.py # 47/47 PASS, ALL TESTS PASSEDFull per-platform install + Termux-via-SSH setup in docs/MCP.md.
| Symptom | Root cause | Patch |
|---|---|---|
GUI aborts at start with Gtk-ERROR: Can't create a GtkStyleContext without a display connection |
wxFont static init touches GTK CSS before gtk_init |
runtime/preload_gtkinit.c (constructor 101) |
| GUI shows "Switching language en_US failed" then exits | wx 3.3 wxUILocale::IsAvailable is ICU-backed, Termux libicu has no en_US |
shim overrides the symbol |
setlocale("en_US", …) returns NULL → modal exit |
bionic accepts en_US.UTF-8 but not bare en_US |
shim retries with .UTF-8 suffix |
GUI runs ~20s then dies on first GL draw with zink_kopper.c:720 assert |
Mesa picks zink (Vulkan→GL); kopper needs DRI3/Present which termux-x11 lacks | run_gui_clean.sh forces GALLIUM_DRIVER=llvmpipe |
| Cancel buttons / AMS spool taps / sidebar buttons silently dropped | custom Button::mouseReleased strict bounds check vs. touch-drift |
patches/{Button,AxisCtrlButton,SideButton,TabButton}.cpp.termux.patch |
| Maximize button does nothing / window goes off-screen in portrait | termux-x11 has no WM; BBLTopbar relies on wxFrame::Maximize(); min-size 1000×600 exceeds portrait width |
patches/BBLTopbar.{cpp,hpp}.termux.patch |
| LAN connect / AMS sync / print impossible | Network Plug-in is x86_64 only | runtime/network_shim/ (libbambu_networking.so stub) + beambam serve |
mqtt message verify failed (err_code 84033543) on every command |
Jan-2025+ firmware requires RSA-SHA256 signature in header block |
bambu_cert.py (publicly-leaked Bambu Connect cert) |
Built and tested on:
- Termux aarch64, x11-repo packages (
wxwidgets 3.3,gtk3,webkit2gtk-4.1,mesa 26.0.5,libllvm 21, …) - termux-x11 Android app, software-rendering display
:1 - Bambu Lab X2D, dual-extruder, AMS HT 4-slot, firmware ≥ Jan 2025
License: AGPL-3.0-or-later. Copyright © 2025–2026 Will Stone (tribixbite).
beambam is free software: you may redistribute and/or modify it under the GNU
Affero General Public License, version 3, or (at your option) any later version
— see LICENSE. Per AGPL §13, anyone who runs a modified beambam
as a network service must offer its users the complete corresponding source.
The bundled BambuStudio fork and runtime/network_shim/ are AGPL-3.0 by
derivation from upstream BambuStudio.
Bambu and BambuStudio are trademarks of Shenzhen Bambu Lab Technology Co., Ltd. — this repo is not affiliated with or endorsed by them.