{"id":"16758965-58a0-4e09-849d-e7aaf3c00862","slug":"beambam","kind":"mcp","name":"beambam","summary":"BambuStudio v02.06.00.51 patches + termux-x11 toolkit + signed-MQTT LAN bridge for Bambu X2D/H2D printers on aarch64 Android (Termux)","owner":"tribixbite","repoUrl":"https://github.com/tribixbite/beambam","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":9,"match":null,"fewStars":false,"format":"mcp","createdAt":"2026-04-25T13:18:10.000Z","addedAt":"2026-10-21T17:15:07.184Z","logoUrl":"https://avatars.githubusercontent.com/u/381345?u=3c298e737fe1584eb57e6234d9a91174c52eb041&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["utilities"],"tags":[],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-25T15:07:15.673Z","sample":false,"url":"https://conduits.dev/tools/beambam","language":"Python","transports":["stdio"],"install":{"pypi":"beambam"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# beambam — LAN-first stack for Bambu Lab printers (any model, any firmware)\n\n[![ci](https://github.com/tribixbite/beambam/actions/workflows/ci.yml/badge.svg)](https://github.com/tribixbite/beambam/actions/workflows/ci.yml)\n[![pypi](https://img.shields.io/pypi/v/beambam.svg)](https://pypi.org/project/beambam/)\n[![python](https://img.shields.io/pypi/pyversions/beambam.svg)](https://pypi.org/project/beambam/)\n[![license](https://img.shields.io/pypi/l/beambam.svg)](LICENSE)\n\n> *beam* (send) + *bam* (fast). Homepage: [beambam.boo](https://beambam.boo).\n\nA drop-in replacement for the Bambu Network Plugin + Bambu Cloud, built\nfor the printers Bambu's first-party stack doesn't reach: **X2D, H2D,\nH2S, H2C, P2S, X1E, and refreshed P1+X1 firmware (Jan-2025+) that\nrequires RSA-SHA256 signed MQTT** — and for the platforms Bambu\ndoesn't ship to (aarch64 Linux / Termux / Android phones / macOS / WSL).\n\n> The repository was originally `x2d` (the printer that motivated the\n> work); the package was renamed to `beambam` in v1.1.0 to reflect that\n> the bridge supports every Bambu model, not just the X2D.\n\nRoadmap: see [`ROADMAP.md`](ROADMAP.md) for the backlog. Per-release\nnotes in [`CHANGELOG.md`](CHANGELOG.md). How beambam stacks up against\nother open clients: [`docs/COMPARISON.md`](docs/COMPARISON.md).\n\n## What is this\n\n**Three things in one repo:**\n\n1. **`beambam` CLI** (`pip install beambam`) — a pure-Python LAN client\n   that speaks Bambu's signed-MQTT control plane. Pause / resume / stop\n   / G-code / temp / chamber light / AMS load / jog / FTPS upload + start\n   print — same wire format the cloud plug-in uses, but readable,\n   hackable, and running where you want. (`x2d_bridge` survives as a\n   console-script alias of `beambam` for back-compat.)\n2. **A six-surface daemon stack** built on top: REST API,\n   Server-Sent Events, Prometheus metrics, structured access log,\n   Home Assistant MQTT auto-discovery, WebRTC chamber-camera streaming,\n   MCP server (Claude Desktop / Cursor / Continue), and a mobile-friendly\n   web UI with multi-printer queue, timelapse browser, AI assistant,\n   and real-time AMS-color → filament-profile auto-resolve.\n3. **A Termux/aarch64 BambuStudio port**: source-patches against\n   upstream `BambuStudio v02.06.00.51`, an `LD_PRELOAD` GTK/locale\n   shim, an `libbambu_networking.so` ABI shim that lets the GUI's\n   \"Connect / AMS sync / Print\" buttons drive printers via this bridge.\n\n## Who is this for\n\n* You own an **X2D / H2D / X1E / refreshed P1+X1** and Bambu Studio\n  refuses to connect — because the Network Plugin has no aarch64\n  build, OR the firmware needs RSA-SHA256 signed MQTT, OR you don't\n  want a live cloud connection (or Developer Mode) just to drive your\n  printer over LAN.\n* You want to drive your printer from **Home Assistant**, **Claude\n  Desktop / MCP**, **a phone in your pocket**, or **a homelab dashboard**\n  — and you don't want to run a 1.5 GB Bambu Studio install just to\n  hit \"pause\".\n* You want a **single small Python binary** you can audit, fork, and\n  run on anything from Termux to a Raspberry Pi to a Docker container.\n\n## Feature matrix\n\n| Capability                          | this repo | Bambu Studio + Cloud | ha-bambulab |\n|-------------------------------------|:---------:|:--------------------:|:-----------:|\n| LAN-only operation (no cloud login) | ✅        | ⚠️ partial           | ⚠️ requires creds |\n| **X2D / H2D RSA-SHA256 signed MQTT**| ✅        | ✅ (cloud)           | ❌ blocker  |\n| aarch64 / Termux / Android          | ✅        | ❌ (no plugin .so)   | ❌ (HA Core required) |\n| One-line install                    | ✅        | ❌                   | ⚠️ HACS    |\n| CLI control (pause/resume/temp/...) | ✅        | ❌ (GUI only)        | ❌          |\n| HTTP REST API + Server-Sent Events  | ✅        | ❌                   | ❌          |\n| Prometheus `/metrics` endpoint      | ✅        | ❌                   | ❌          |\n| Multi-printer auto-discovery (SSDP) | ✅        | ✅                   | ⚠️ manual  |\n| Home Assistant MQTT auto-discovery  | ✅ (32+ entities) | ❌            | ✅          |\n| WebRTC chamber camera (~100 ms)     | ✅        | ❌ (HLS only)        | ❌          |\n| MCP server for Claude Desktop / IDE | ✅        | ❌                   | ❌          |\n| Mobile-friendly web UI              | ✅        | ❌                   | ❌          |\n| Multi-printer print queue           | ✅        | ❌                   | ❌          |\n| Auto-recorded timelapses + ffmpeg stitch | ✅   | partial (cloud)      | ❌          |\n| Natural-language assistant in UI    | ✅        | ❌                   | ❌          |\n| Real-time AMS color → profile sync  | ✅        | ✅                   | ❌          |\n| BambuStudio GUI on aarch64 Termux   | ✅        | ❌                   | n/a         |\n\nFull per-entity comparison vs ha-bambulab in\n[`docs/HA_VS_BAMBULAB.md`](docs/HA_VS_BAMBULAB.md).\n\n## How beambam compares to other open-source Bambu tools\n\nOn Bambu's Jan-2025+ **authorization-control** firmware, the printer rejects\nunsigned control commands. Almost every open client handles writes by asking the\nuser to turn on the printer's **Developer LAN Mode** — which by design *severs\nBambu Cloud and disables auth verification entirely*. beambam is the only one\nthat signs `print.*` and **starts a print over pure LAN with no Developer Mode\nand no live cloud connection at print time**, by recovering the per-installation\nsigning key from a Bambu Handy install and RSA-encrypting the file location\n(`url_enc`) to the printer's device cert. (That key is Bambu-issued — a prior\nHandy cloud login provisioned it; beambam's setup is a one-time `adb` extraction\nof it, not an ongoing cloud dependency.)\n\n| Project | LAN-only¹ | Signs `print.*` on signed FW | **Start print, signed FW, no DevMode** | X2D/H2D | aarch64/Termux |\n|---|:---:|:---:|:---:|:---:|:---:|\n| **beambam** | ◐¹ | ✓ (auto-recovered per-install key) | **✓** | ✓ | ✓ |\n| bambu-mcp | ✓ | ◐ user must supply key | ◐ start needs DevMode | ✗ | ◐ |\n| ha-bambulab / pybambu | ◐ | ◐ write needs DevMode | ✗ (DevMode only) | partial | ✓ |\n| bambulabs_api | ✓ | ✗ | ✗ (DevMode only) | ✗ | ✓ |\n| OrcaSlicer / BambuStudio | ◐ | ◐ Studio = cloud client | ◐ DevMode or cloud | ✓ (Studio) | ✗ |\n\n¹ beambam runs LAN-only at print time (no live cloud, no Developer Mode); the\none-time signing-key recovery needs a Bambu Handy that's been signed in — the key\nis Bambu-issued, provisioned by a prior cloud login — plus `adb` to read it out.\n\nFull matrix, sources, and honest caveats: [`docs/COMPARISON.md`](docs/COMPARISON.md).\n\n## Printer compatibility\n\nThe bridge talks to every Bambu Lab printer that exposes the standard\nLAN MQTT + FTPS endpoints. How the signed-MQTT requirement (Jan-2025+\nfirmware on most models, always-on for the X2D/H2D family) is satisfied\ndepends on the firmware:\n\n- **Older / unenforced firmware** — the bundled publicly-leaked Bambu\n  Connect cert (`bambu_cert.py`) signs control directly; no extra setup,\n  no cloud account.\n- **Authorization-control firmware** (X2D/H2D family, refreshed P1/X1) — the\n  printer rejects unsigned `print.*` and wants a signing cert whose CN matches\n  its *own* serial, so the shared leaked cert no longer works. **You pick how to\n  authorize writes — beambam supports either:**\n\n  | | How | Trade-off |\n  |---|---|---|\n  | **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 wants `beambam device-cert` for the `url_enc` file location. | More setup, but **cloud + remote access keep working** — no Developer Mode. |\n  | **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. |\n\n  Both run entirely over your LAN. Option A's key is Bambu-issued, so it only\n  exists in a Handy that's been signed in — a prior cloud login is a one-time\n  setup prerequisite, not a runtime dependency. See\n  [how beambam compares](docs/COMPARISON.md).\n\n| Model       | Bambu code | Signed MQTT | Status | Upload | Print | AMS | Camera | Notes |\n|-------------|:----------:|:-----------:|:------:|:------:|:-----:|:---:|:------:|-------|\n| X1 / X1C    | BL-P002 / BL-P001 | optional¹ | ✅ | ✅ | ✅ | ✅ | RTSPS | original H2 family |\n| X1E         | C13        | required    | ✅ | ✅ | ✅ | ✅ | RTSPS | enterprise variant |\n| P1P / P1S   | C11 / C12  | required²   | ✅ | ✅ | ✅ | ✅ | HTTP/MJPEG | P-series |\n| P2S         | N7         | required    | ✅ | ✅ | ✅ | ✅ | HTTP/MJPEG | newer P-series |\n| A1 / A1 mini| N2S / N1   | required²   | ✅ | ✅ | ✅ | ✅ (AMS lite) | HTTP/MJPEG | direct drive |\n| H2D / H2D Pro | O1D / O1E | required (dual nozzle) | ✅ | ✅ | ✅ | ✅ (multi-AMS) | RTSPS | |\n| H2S / H2C   | O1S / O1C2 | required    | ✅ | ✅ | ✅ | ✅ | RTSPS | |\n| **X2D**     | N6         | required    | ✅ | ✅ | ✅ | ✅ (multi-AMS, dynamic map) | RTSPS | primary target — `beambam analyze` was developed against X2D 3-color prints |\n\n¹ X1/X1C with pre-2025 firmware accept unsigned MQTT; the bridge signs\nanyway with the leaked Bambu Connect cert (zero overhead, forward-compat).\n² P1S/P1P/A1 enforcement varies by firmware. On pre-2025 firmware the\nleaked cert suffices; on refreshed (authorization-control) firmware,\n`print.*` needs the per-installation key from `beambam key` (and X2D/H2D\nLAN print also needs `beambam device-cert`).\n\nIf your model isn't listed and it has the LAN MQTT switch in\nSettings → Network, it almost certainly works — open an issue with the\n`X-BBL-Device-Model` header from `status`.\n\n## Quick install + start\n\n### `pip install beambam` (any Linux, macOS, Windows-WSL — recommended)\n\n```bash\npip install beambam            # core bridge + CLI\npip install beambam[all]       # + HA publisher, MCP, web UI, AI assistant\n\nbeambam --version\n```\n\n### Configure the printer\n\n```bash\ncat >~/.x2d/credentials <<'EOF'\n[printer]\nip     = 192.168.1.42\ncode   = 12345678\nserial = 03ABC0001234567\nEOF\n\nbeambam status                 # pull live state\nbeambam analyze model.3mf      # NEW v1.1.0 — preview the print plan\nbeambam print model.3mf        # FTPS upload + signed MQTT start\n```\n\n### Web UI / daemon\n\n```bash\nbeambam daemon --http 0.0.0.0:8765 \\\n    --queue --timelapse \\\n    --auth-token \"$(openssl rand -hex 32)\"\nxdg-open http://localhost:8765/\n```\n\n### aarch64 Termux + BambuStudio GUI (Android phones / tablets)\n\n```bash\n# One-line install. Pulls latest release tarball + builds bridge runtime;\n# idempotent. Required for the BambuStudio GUI + libbambu_networking.so\n# shim — the pip package alone covers everything except the GUI.\nbash <(curl -fsSL https://raw.githubusercontent.com/tribixbite/beambam/main/install.sh)\n```\n\n### Requirements\n- Python **3.10+** (3.10–3.13 tested in CI on Ubuntu + macOS)\n- `paho-mqtt >= 2.0` + `cryptography >= 41` (auto-installed)\n- For Termux GUI: aarch64-Termux + termux-x11 + the release tarball\n- For HA integration: any MQTT broker reachable from the daemon\n\n## Demo media\n\nFive short H.264 MP4s in [`docs/demos/`](docs/demos/) — render with\n`PYTHONPATH=. python3.12 runtime/demos/render.py` to regenerate.\n\n| File | Duration | What |\n|---|---|---|\n| [`docs/demos/cli_demo.mp4`](docs/demos/cli_demo.mp4)   | 47s | bridge CLI: status / chamber-light / print / daemon up |\n| [`docs/demos/gui_demo.mp4`](docs/demos/gui_demo.mp4)   | 16s | BambuStudio Termux port: Prepare tab, Device tab, sidebar shrink, X2D preset |\n| [`docs/demos/mcp_demo.mp4`](docs/demos/mcp_demo.mp4)   | 57s | MCP stdio handshake: initialize → tools/list → tools/call status |\n| [`docs/demos/webui_demo.mp4`](docs/demos/webui_demo.mp4) | 15s | thin web UI at S25 Ultra portrait + landscape + tablet viewport |\n| [`docs/demos/ha_demo.mp4`](docs/demos/ha_demo.mp4)     | 55s | Home Assistant registry: 32 entities + 1 Device with live values |\n\n## Per-feature documentation\n\n| Feature              | Doc |\n|----------------------|-----|\n| Quick start          | [`docs/QUICKSTART.md`](docs/QUICKSTART.md) |\n| Web UI               | [`docs/WEB_UI.md`](docs/WEB_UI.md) |\n| MCP server (Claude Desktop, Cursor, ...) | [`docs/MCP.md`](docs/MCP.md) |\n| WebRTC streaming     | [`docs/WEBRTC.md`](docs/WEBRTC.md) |\n| Home Assistant — overview | [`docs/HA.md`](docs/HA.md) |\n| Home Assistant — install + dashboard guide | [`docs/HA_SETUP.md`](docs/HA_SETUP.md) |\n| HA vs ha-bambulab feature parity | [`docs/HA_VS_BAMBULAB.md`](docs/HA_VS_BAMBULAB.md) |\n| Cloud bridge (cloud-state, cloud-print) | [`docs/CLOUD_BRIDGE.md`](docs/CLOUD_BRIDGE.md) |\n| Local control paths (LAN MQTT, FTPS, RTSPS, LVL_Local) | [`docs/LOCAL_CONTROL_PATHS.md`](docs/LOCAL_CONTROL_PATHS.md) |\n| Signed vs unsigned MQTT — leaked-key truth table | [`docs/SIGNED_VS_UNSIGNED.md`](docs/SIGNED_VS_UNSIGNED.md) |\n| X2D runtime pipeline (canonical reference) | [`docs/X2D_RUNTIME_PIPELINE.md`](docs/X2D_RUNTIME_PIPELINE.md) |\n| Multi-printer setup  | [`docs/MULTI_PRINTER.md`](docs/MULTI_PRINTER.md) |\n| Print queue          | [`docs/QUEUE.md`](docs/QUEUE.md) |\n| Timelapse browser    | [`docs/TIMELAPSE.md`](docs/TIMELAPSE.md) |\n| AI assistant         | [`docs/ASSISTANT.md`](docs/ASSISTANT.md) |\n| AMS color sync       | [`docs/COLORSYNC.md`](docs/COLORSYNC.md) |\n| BambuStudio Termux port | this README §\"BambuStudio Termux port\" below |\n\n> The Bambu Network Plug-in `.so` is shipped only for x86\\_64 Linux and\n> arm64 macOS. On aarch64 Termux it has no equivalent build, so out of\n> the box BambuStudio's GUI cannot connect to a LAN printer or sync AMS\n> spool data. The bridge in this repo replaces what the plug-in would\n> have done for the LAN-only path.\n\n## BambuStudio Termux port\n\nIf you want the BambuStudio GUI itself to launch + connect on aarch64\nTermux (rather than only driving the printer headlessly via the bridge),\nthis repo also ships the source patches and `LD_PRELOAD` shim required.\nLayout:\n\n```\n.\n├── patches/                  # 50+ unified diffs against upstream BambuStudio\n│   │                         # (touchscreen-tap slop, AMSControl width,\n│   │                         #  GLCanvas3D termux-x11 paint, MainFrame\n│   │                         #  Ctrl+R/Ctrl+P slice/print accelerators,\n│   │                         #  Plater sidebar, deps build wiring, …)\n│   ├── Button.cpp.termux.patch              # touch-target 15-px slop\n│   ├── AxisCtrlButton.cpp.termux.patch      # touch-target 15-px slop\n│   ├── SideButton.cpp.termux.patch          # touch-target 15-px slop\n│   ├── TabButton.cpp.termux.patch           # touch-target 15-px slop\n│   ├── BBLTopbar.cpp.termux.patch           # vertical Print/plate stack\n│   ├── BBLTopbar.hpp.termux.patch\n│   ├── MainFrame.cpp.termux.patch           # Ctrl+R/Ctrl+P + sidebar tog\n│   ├── GLCanvas3D.cpp.termux.patch          # termux-x11 force-paint\n│   ├── AMSControl.cpp.termux.patch          # AMS strip width\n│   ├── (… and ~40 more — patches/ for the full set)\n├── runtime/\n│   ├── preload_gtkinit.c     # LD_PRELOAD shim: GTK pre-init, locale fix,\n│   │                         # wxLocale ICU bypass, wx 3.3 assert silencer,\n│   │                         # hidden config_wizard_startup override\n│   ├── network_shim/         # libbambu_networking.so ABI shim — lets the\n│   │                         # GUI's Connect/Print/AMS sync buttons drive\n│   │                         # the bridge over a Unix socket (#1)\n│   ├── webrtc/               # aiortc gateway (#45)\n│   ├── webui/                # web UI test harnesses (#46-48)\n│   ├── ha/                   # MQTT auto-discovery publisher (#50-54)\n│   ├── mcp/                  # MCP stdio server (#42-44)\n│   ├── queue/                # multi-printer queue manager (#55)\n│   ├── timelapse/            # auto-timelapse recorder (#56)\n│   ├── assistant/            # natural-language MCP router (#57)\n│   └── colorsync/            # AMS color → filament profile (#58)\n├── web/                      # static HTML/CSS/JS for the thin web UI\n├── docs/                     # per-feature documentation\n├── run_gui_clean.sh          # canonical GUI launcher\n├── beambam/                  # signed-MQTT LAN client + multi-surface daemon\n│   ├── cli/                  #   subcommand handlers (control / cloud / info / daemon / lan)\n│   ├── mqtt.py               #   X2DClient + sign_payload (Bambu's signed control plane)\n│   ├── ftps.py               #   implicit-TLS FTPS for SD-card upload/download\n│   └── …                     #   31 modules, see `python -m beambam.cli --help`\n├── bambu_cert.py             # publicly-leaked Bambu Connect signing key\n├── lan_upload.py             # FTPS:990 implicit-TLS uploader (helper subset)\n├── lan_print.py              # upload + start_print combo\n├── make_frame.py             # generates a debossed picture-frame STL\n├── inject_thumbnails.py      # injects the 5 PNG thumbnails firmware needs\n├── resolve_profile.py        # flatten BambuStudio profile inheritance\n└── test_signed_mqtt.py       # diagnostic: pushall with RSA-SHA256 signature\n```\n\n`bs-bionic/` (the BambuStudio source tree) and `dist/` (built tarball) are\ngitignored — the patches in `patches/` reproduce the source changes against\na clean clone.\n\n## Detailed install / credentials / launch walkthrough\n\n**TL;DR runtime guide:** see [docs/QUICKSTART.md](docs/QUICKSTART.md)\nfor the install → credentials → launch → connect → print walkthrough,\nplus copy-pasteable shortcuts for every print-control verb, the\ncamera proxy URLs, and a troubleshooting table.\n\n### Use the prebuilt distribution\n\n### One-line install (recommended)\n\n```\nbash <(curl -fsSL https://raw.githubusercontent.com/tribixbite/beambam/main/install.sh)\n```\n\nIdempotent — runs the platform check, `pkg install`s the runtime deps,\nfetches the latest release tarball, verifies SHA-256, drops the\n`libbambu_networking.so` + `libBambuSource.so` shims into\n`~/.config/BambuStudioInternal/plugins/`, pre-seeds\n`~/.config/BambuStudioInternal/BambuStudio.conf` with\nthe X2D model entry, writes a chmod-600 `~/.x2d/credentials` skeleton\nfor you to fill in, and (if Termux:Boot is installed) drops a boot-time\nlauncher for the bridge daemon. Re-run any time to upgrade — your\n`BambuStudio.conf` and credentials file are preserved.\n\n### Manual install (alternative)\n\nA prebuilt tarball is attached to the GitHub release:\n\n```\ncurl -L -o bs-x2d.tar.xz \\\n    https://github.com/tribixbite/beambam/releases/latest/download/bambustudio-x2d-termux-aarch64.tar.xz\ntar -xJf bs-x2d.tar.xz\ncd bambustudio-x2d-termux-aarch64\n./run_gui.sh                  # needs termux-x11 running on DISPLAY=:1\n```\n\n### Termux dependencies\n\n```\npkg install x11-repo\npkg install \\\n    wxwidgets gtk3 webkit2gtk-4.1 \\\n    glew glfw mesa libllvm llvm \\\n    glib pango cairo gdk-pixbuf atk \\\n    fontconfig freetype libpng libjpeg libtiff \\\n    openssl curl libcurl \\\n    opencv libdbus libwebp \\\n    libavcodec libswscale libavutil ffmpeg \\\n    python python-cryptography xdotool \\\n    openbox\npip install paho-mqtt\n```\n\n**Why openbox is in the list**: termux-x11 ships without a window manager.\nWithout one, `wxFileDialog`s open undecorated at (0,0) and stack *under*\nthe main frame so Cancel-button taps land on the main frame instead of\nthe dialog (\"Cancel buttons don't work\"); transient dialogs can't be\ndragged; `wxFrame::Maximize` is a no-op. Openbox (≈600 KB) supplies a\nminimal EWMH-aware WM that fixes all of those. `run_gui.sh` will spawn\nit automatically on launch if it's installed and not already running.\n\nThe most version-sensitive of these is `libllvm` — Mesa requires it at\nthe same major version. If `pkg upgrade` ever leaves them mismatched\nyou'll get `EGL_BAD_PARAMETER` / \"Unable to get EGL Display\"; recover\nwith `pacman -S libllvm llvm`.\n\nYou also need a working X server. The reference setup is **termux-x11**\n(install the Android app and run `termux-x11` in a Termux session,\nreachable on `:1`).\n\n### Build from source\n\n```\ngit clone --recurse-submodules https://github.com/bambulab/BambuStudio\ncd BambuStudio\ngit checkout v02.06.00.51\nfor p in /path/to/x2d/patches/*.termux.patch; do git apply \"$p\"; done\n# Use the bionic-toolchain steps from\n# ~/.claude/skills/bambustudio-on-termux-aarch64.md\nmkdir build && cd build && cmake -GNinja .. && ninja bambu-studio\ngcc -fPIC -shared ../runtime/preload_gtkinit.c \\\n    $(pkg-config --cflags --libs gtk+-3.0) -ldl -o ../runtime/libpreloadgtk.so\n```\n\n## libbambu\\_networking.so shim — making the GUI talk to LAN printers\n\nThe Bambu Network Plug-in `.so` is shipped only for x86\\_64 Linux + arm64\nmacOS, so on aarch64 Termux the GUI's \"Connect / AMS / Print\" buttons\nhave nothing to dlopen. `runtime/network_shim/` builds a drop-in\nreplacement that exports the entire NetworkAgent ABI (103 `bambu_network_*`\nsymbols + 21 `ft_*` symbols) and forwards the LAN-relevant calls over a\nUnix-domain socket to a long-running `beambam serve` process (spawned\nas `python3 -m beambam.cli serve` by the shim, with `x2d_bridge` /\n`beambam` console-scripts as PATH-lookup fallbacks). Cloud-only entry\npoints return success-with-empty so the GUI's cloud panels stay quiet\nbut the LAN flow works end-to-end.\n\n```\ncd runtime/network_shim\nmake            # builds libbambu_networking.so + libBambuSource.so\nmake install    # → ~/.config/BambuStudioInternal/plugins/\n```\n\nA self-test harness lives at `runtime/network_shim/tests/test_shim_e2e.py`.\nIt dlopens the .so, asserts every host-expected symbol is exported, then\nspawns a fresh bridge subprocess and round-trips through the protocol\nagainst a real X2D (handshake → connect → state event → disconnect):\n\n```\npython3.12 runtime/network_shim/tests/test_shim_e2e.py\n```\n\nWire format and op set are spec'd in `runtime/network_shim/PROTOCOL.md`.\nThe shim spawns the bridge itself if no socket is found at\n`$X2D_BRIDGE_SOCK` (default `~/.x2d/bridge.sock`), so the GUI launch\nflow is just: install the .so once, then `./run_gui.sh`.\n\n## Using the LAN bridge (`beambam` CLI)\n\nSave credentials once. Either a single `[printer]` section (the\ndefault, used when no `--printer NAME` is passed) or as many named\n`[printer:NAME]` sections as you want:\n\n```\nmkdir -p ~/.x2d && chmod 700 ~/.x2d\ncat > ~/.x2d/credentials <<EOF\n# Single-printer setup:\n[printer]\nip = 192.168.x.y\ncode = <8-char access code from printer screen>\nserial = <printer serial from device sticker>\n\n# OR, multiple named printers:\n[printer:studio]\nip = 192.168.x.y\ncode = …\nserial = …\n\n[printer:basement]\nip = 10.0.0.50\ncode = …\nserial = …\nEOF\nchmod 600 ~/.x2d/credentials\n```\n\nPick a named printer with `--printer NAME`, the `X2D_PRINTER` env\nvar, or — when only one named section exists and there's no plain\n`[printer]` — automatically.\n\nThen:\n\n```\n# One-shot state dump\nbeambam status\n\n# Upload + start print on AMS slot 4\nbeambam print myfile.gcode.3mf --slot 3\n\n# Long-running monitor — polls every 5s, exposes JSON at http://127.0.0.1:8765/state\nbeambam daemon --http 127.0.0.1:8765 --quiet\n\n# Same daemon also exposes /healthz for uptime monitoring:\n#   200 + {\"healthy\": true,  ...} when fresh state arrived recently\n#   503 + {\"healthy\": false, ...} when MQTT silently disconnected\n# Threshold is configurable via --max-staleness (default 30s).\ncurl http://127.0.0.1:8765/healthz\n\n# Prometheus scrape: gauges for nozzle/bed/chamber temps, AMS humidity,\n# print progress; counters for total_messages, mqtt_disconnects, ssdp_notifies.\ncurl http://127.0.0.1:8765/metrics\n\n# Every HTTP hit is appended as one JSON line to ~/.x2d/access.log\n# (ts, method, path, status, duration_ms, printer, authed, client).\n# Rotates to access.log.1 at 1 MiB.\ntail -f ~/.x2d/access.log\n```\n\nCredentials can also come from `--ip / --code / --serial` flags or\n`X2D_IP / X2D_CODE / X2D_SERIAL` environment variables.\n\n### Slicing + printing an STL\n\nEnd-to-end one-shot: slice an STL, upload to the printer, queue the\nprint. The 3MF is the slicer's authoritative contract — `cmd_print`\nauto-derives `bed_type` and bed initial-layer temperature from\n`Metadata/project_settings.config` and refuses to publish if the\ntarget AMS slot is empty or holds an incompatible filament class\n(PLA-vs-PETG-vs-ABS). The hard guards (empty slot, class mismatch)\nare not bypassable; soft brand/colour mismatches warn under `--force`.\n\n```\n# Standalone slicer (STL → .gcode.3mf with proper plate + filament profile)\npython3.12 x2d_slice.py model.stl --out out.gcode.3mf \\\n    --scale 1.0                     # uniform geometry scale\n    --color \"PLA Silk Gold\"         # Bambu colour-name resolution against\n                                     # filaments_color_codes.json (304 entries)\n    --bed supertack                 # plate type — cool/engineering/high_temp/\n                                     # textured/supertack OR int 1..5\n\n# One-shot slice → upload → queue\nbeambam slice-print model.stl --slot 3 --color Gold --bed 5 --timelapse\n\n# Send an already-sliced .gcode.3mf\nbeambam print model.gcode.3mf --slot 3 --timelapse\n    # bed_type + bed_temp auto-derived from the 3MF; refuses on AMS\n    # slot empty / wrong-class filament. Pass --force to bypass soft\n    # brand/colour mismatches; hard guards never bypassable.\n```\n\n### Print-control commands\n\nEach verb is a single signed-MQTT publish — same protocol the official\nGUI uses, just routed through our bridge so it works on aarch64. Every\ncommand exits as soon as the printer ACKs the publish.\n\n> ⚠️ **LAN signing limitation.** Only `system.*` commands (chamber\n> light, etc.) actually verify against the leaked Bambu Connect global\n> cert. `print.*` commands (project_file, ams_change_filament, etc.)\n> are verified by the firmware against per-device factory certs and\n> silently dropped if our cert isn't in the trust list. See\n> [`docs/SIGNED_VS_UNSIGNED.md`](docs/SIGNED_VS_UNSIGNED.md). For\n> `print.*` actions the working path is `cloud-print` (cloud broker,\n> JWT auth, no signing) or starting the print from the touchscreen\n> after the bridge has uploaded the 3MF.\n\n```\nbeambam pause                      # pause the current print\nbeambam resume                     # resume after pause\nbeambam stop                       # abort the current print\nbeambam home                       # G28 — home all axes\nbeambam level                      # G29 — auto bed-level\nbeambam set-temp bed     60        # set_bed_temp 60°C\nbeambam set-temp nozzle 220 --idx 0  # set_nozzle_temp on extruder 0\nbeambam set-temp chamber 35        # M141 S35 (chamber heater)\nbeambam led on                           # ledctrl on / off / flashing\nbeambam led flashing --on-time 200 --off-time 200 --loops 5\n                                          # (alias kept: `chamber-light`)\nbeambam ams-load 0 3 --tar-temp 220   # AMS 0 / slot 3, preheat to 220\nbeambam ams-unload 0 --tar-temp 220   # unload from AMS 0\nbeambam jog X 10                   # relative move +10 mm on X\nbeambam jog Z -5 --feed 600        # relative -5 mm on Z @ 600 mm/min\nbeambam gcode \"M115\"               # send arbitrary gcode_line\n\n# AMS slot metadata — push tray_info_idx / temps / color to one slot or\n# the whole bank in one command. Profile files live in flat-profiles/.\nbeambam ams set 7 'flat-profiles/eSUN PLA+ @BBL X2D 0.4 nozzle.json' \\\n    --color F98C36                       # one slot, explicit color\nbeambam ams sync                         # batch from flat-profiles/ams-sync.json\nbeambam ams sync --dry-run               # preview (no MQTT, no creds needed)\n```\n\nSee [`flat-profiles/AMS_MAPPING.md`](flat-profiles/AMS_MAPPING.md) for\nthe bundled 13-slot sync map and the schema each profile JSON must\nfollow.\n\nPayload schemas reverse-engineered from\n`bs-bionic/src/slic3r/GUI/DeviceManager.cpp::MachineObject::command_*`\nso the printer behaviour matches what the official GUI sends.\n\n### Camera proxy\n\nThe printer's chamber camera streams over RTSPS, but only after you\n**enable LAN-mode liveview on the printer's touchscreen**\n(Settings → Network → Liveview). Otherwise the stream lives on a closed\nproprietary protocol on TCP/6000 that requires the x86\\_64-only\n`libBambuSource.so` to decode.\n\nOnce enabled, run:\n\n```\nbeambam cam start                        # bind 127.0.0.1:8766 by default (was `camera`)\nbeambam cam start --bind 0.0.0.0:8766    # expose on LAN (be careful!)\nbeambam cam start --idle-timeout 60      # stop ffmpeg after 60s of no viewers\nbeambam cam stop                         # kill running proxy (PID in ~/.x2d/cam.pid)\nbeambam cam                              # one-shot snapshot to ./cam.jpg\nbeambam cam watch                        # live in-terminal viewer\n                                          # default 30s; set very high to keep\n                                          # the pump permanently warm\n```\n\nThe pump is **on-demand** since 2026-05-06: ffmpeg only starts after\nthe first `/cam.mjpeg` connect or `/cam.jpg`/`/cam.m3u8` request, and\nthe supervisor reaps it after `--idle-timeout` seconds of zero\nviewers + zero one-shot endpoint hits. Subsequent requests respawn\nwithin ~3-4 s (RTSPS reconnect + first JPEG). On phone hosts this\ndrops idle CPU from a continuous ~66% to 0%.\n\nThen point any browser at `http://127.0.0.1:8766/cam.mjpeg` for the\nmultipart MJPEG stream, or `/cam.jpg` for a one-shot snapshot. Multiple\nviewers share one ffmpeg subprocess, so bandwidth to the printer is\nconstant regardless of how many people are watching.\n\nPre-flight checks `ipcam.rtsp_url` via signed MQTT and bails with a\nclear hint if liveview is still disabled. Pass `--skip-check` to bypass\n(useful when MQTT is flaky but RTSP is open).\n\nTwo transport options:\n\n* `--proto rtsp` (default): RTSPS:322 via ffmpeg. Fast, well-tested,\n  gated on `ipcam.rtsp_url != \"disable\"`.\n* `--proto local`: TLS:6000 LVL_Local — Bambu's proprietary stream\n  protocol. The TLS handshake + 80-byte auth blob + 16-byte frame\n  framing are reverse-engineered in `runtime/network_shim/lvl_local.py`.\n  The same printer-touchscreen \"LAN-mode liveview\" toggle gates this\n  path too — without it the printer rejects with status `0x0003013f`\n  and a clear error message.\n\nThree HTTP outputs:\n\n* `/cam.mjpeg` — multipart/x-mixed-replace, browser-renderable, low latency\n* `/cam.jpg`   — single latest JPEG snapshot (one-shot)\n* `/cam.m3u8`  — HLS playlist with 2-second segments (12s sliding window).\n  Plays in any mobile browser via `<video src=\"…/cam.m3u8\" controls>`.\n  Higher latency than MJPEG (~6-8s) but survives flaky connections\n  better and supports seeking.\n\n### Exposing the daemon / camera on the LAN\n\n`/state` `/healthz` `/cam.mjpeg` `/cam.jpg` are open on loopback by\ndefault (single-user local use). Binding non-loopback (`--http\n0.0.0.0:8765` or `--bind 0.0.0.0:8766`) requires a bearer token,\notherwise every request returns `401 Unauthorized` with a\n`WWW-Authenticate: Bearer` hint:\n\n```\n# Generate a long random token, then:\nexport X2D_AUTH_TOKEN=$(openssl rand -hex 32)\nbeambam daemon --http 0.0.0.0:8765\nbeambam camera --bind 0.0.0.0:8766\n\n# From another box on the LAN:\ncurl -H \"Authorization: Bearer $X2D_AUTH_TOKEN\" http://<phone>:8765/healthz\n```\n\n`--auth-token` on the command line takes precedence over the env var.\nLoopback binds keep the open path even with a token configured? — no:\nonce you set a token, loopback also enforces it, so the same `curl`\ncommand works against both `127.0.0.1` and a LAN-exposed bind.\n\n### Filament presets without a cloud account\n\nWhen no cloud session exists, `bambu_network_get_user_presets`\nfalls back to a local catalog so the GUI's AMS spool dropdown\nisn't empty:\n\n* All `instantiation: true` BBL filament profiles shipped under\n  `resources/profiles/BBL/filament/` (1464 entries on the current\n  binary — every Bambu vendor variant for every supported model).\n* A small community-curated set under\n  `runtime/network_shim/data/community_filaments.json` covering\n  Generic PLA / PETG / ABS / TPU / ASA plus Polymaker / Prusament /\n  eSun / Hatchbox flavours. Edit that file to add your own.\n\nIf you sign in via `cloud-login`, the cloud-synced presets take\nprecedence — the local fallback only activates anonymously.\n\n### Bambu cloud login (optional)\n\nThe bridge can talk to the Bambu Lab cloud REST API to populate the\nGUI surfaces that the shim was previously stubbing — `is_user_login`,\n`get_user_id`, `get_user_presets`, `get_user_tasks`. Without a cloud\nsession, those calls keep returning empty (LAN-only flow is unaffected).\n\n```\nbeambam cloud-login --email me@example.com --password '…'\nbeambam cloud-status     # confirms session, expiry, region\nbeambam cloud-logout     # wipes ~/.x2d/cloud_session.json\n```\n\nTokens land at `~/.x2d/cloud_session.json` (chmod 600). The bridge\nauto-refreshes the access token when it's within 5 min of expiry.\nRegion is auto-detected from the email TLD (`.cn` → CN, else US),\noverride with `--region`.\n\nThis module hits the same endpoints the open-source community already\ndocumented (`pybambu`, `bambu-farm-manager`, `OrcaSlicer`). Bambu has\nno published SDK — if they rotate URLs, this module needs to rotate\nin lockstep with the upstream consumers.\n\n### Cloud-mediated print + remote control\n\nOnce `cloud-login` has succeeded, twelve cloud-* CLIs work off-LAN by\nrouting through Bambu's cloud MQTT broker. Useful when the X2D's\nLAN-direct `print.*` is blocked by the per-installation-cert wall\n(items #65 / #66 / #68): the cloud broker uses standard TLS, no per-\ninstallation cert needed.\n\n```\nbeambam cloud-state                          # remote monitoring\nbeambam cloud-pause                          # remote control\nbeambam cloud-print rumi_frame.gcode.3mf     # full upload + print\n```\n\nSame `--http :8765` daemon also exposes `/cloud/{status,printers,state,publish}`\nHTTP routes for the web UI / Home Assistant. Full surface +\nimplementation details: [`docs/CLOUD_BRIDGE.md`](docs/CLOUD_BRIDGE.md).\n\n## Thin web UI (`http://<phone>:8765/`)\n\nThe bridge daemon serves a mobile-friendly remote-control surface at\n`/`. No build step, no framework — `web/index.html` + `web/index.js`\n+ `web/index.css` (~17 KB total). Live state arrives over Server-Sent\nEvents (`/state.events`); pause / resume / stop / lights / heat\npresets / AMS slot loads POST to `/control/<verb>` and the daemon\npublishes via the long-lived MQTT client. Camera tabs let you flip\nbetween `/cam.jpg` snapshot polling, native HLS at `/cam.m3u8`, and\nWebRTC at `/cam.webrtc/offer`.\n\n```bash\nbeambam daemon --http 0.0.0.0:8765 --auth-token \"$X2D_AUTH_TOKEN\"\n# Then open http://<phone-ip>:8765/ in any modern browser.\n```\n\nTest harness: `PYTHONPATH=. python3.12 runtime/webui/test_webui.py` →\n33/33 PASS, exercises every static / SSE / control route end-to-end.\n\n## MCP server (Claude Desktop, Cursor, Continue, …)\n\nThe bridge ships an MCP server at `runtime/mcp/server.py` so any\nMCP-aware client can drive prints conversationally. Eighteen tools\n(status, pause, resume, stop, gcode, set_temp, chamber_light,\nams_load/unload, jog, upload, print, camera_snapshot, list_printers,\nhealthz, metrics, home, level) plus two resources (`x2d://state`,\n`x2d://camera/snapshot`).\n\n```jsonc\n// Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (mac)\n//                 %APPDATA%\\Claude\\claude_desktop_config.json           (Windows)\n//                 ~/.config/Claude/claude_desktop_config.json           (Linux)\n{\n  \"mcpServers\": {\n    \"x2d\": {\n      \"command\": \"python3.12\",\n      \"args\": [\"-m\", \"mcp_x2d\"],\n      \"cwd\": \"/absolute/path/to/x2d\"\n    }\n  }\n}\n```\n\nSmoke-test the server before wiring it in:\n\n```bash\npython3.12 runtime/mcp/test_mcp.py     # 47/47 PASS, ALL TESTS PASSED\n```\n\nFull per-platform install + Termux-via-SSH setup in [`docs/MCP.md`](docs/MCP.md).\n\n## What's broken on Termux without these patches\n\n| Symptom | Root cause | Patch |\n|---|---|---|\n| 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) |\n| 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 |\n| `setlocale(\"en_US\", …)` returns NULL → modal exit | bionic accepts `en_US.UTF-8` but not bare `en_US` | shim retries with `.UTF-8` suffix |\n| 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` |\n| 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` |\n| 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` |\n| LAN connect / AMS sync / print impossible | Network Plug-in is x86\\_64 only | `runtime/network_shim/` (libbambu\\_networking.so stub) + `beambam serve` |\n| `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) |\n\n## Provenance\n\nBuilt and tested on:\n\n* Termux aarch64, x11-repo packages (`wxwidgets 3.3`, `gtk3`, `webkit2gtk-4.1`,\n  `mesa 26.0.5`, `libllvm 21`, …)\n* termux-x11 Android app, software-rendering display `:1`\n* Bambu Lab X2D, dual-extruder, AMS HT 4-slot, firmware ≥ Jan 2025\n\n**License: AGPL-3.0-or-later.** Copyright © 2025–2026 Will Stone (tribixbite).\nbeambam is free software: you may redistribute and/or modify it under the GNU\nAffero General Public License, version 3, or (at your option) any later version\n— see [`LICENSE`](LICENSE). Per **AGPL §13**, anyone who runs a modified beambam\nas a network service must offer its users the complete corresponding source.\nThe bundled BambuStudio fork and `runtime/network_shim/` are AGPL-3.0 by\nderivation from upstream BambuStudio.\n\nBambu and BambuStudio are trademarks of Shenzhen Bambu Lab Technology Co.,\nLtd. — this repo is not affiliated with or endorsed by them.\n"}