Metadata-Version: 2.4
Name: beambam
Version: 1.4.0
Summary: Signed-MQTT bridge + daemon for Bambu Lab printers (X1/P1/A1/H2/X2D, Jan-2025+ firmware). Includes Home Assistant publisher, MCP server, web UI, slicing helpers, and a print-plan analyzer.
Project-URL: Homepage, https://beambam.boo
Project-URL: Documentation, https://github.com/tribixbite/beambam/tree/main/docs
Project-URL: Repository, https://github.com/tribixbite/beambam
Project-URL: Issues, https://github.com/tribixbite/beambam/issues
Project-URL: Changelog, https://github.com/tribixbite/beambam/blob/main/CHANGELOG.md
Author-email: Will Stone <willstone@gmail.com>
License: MIT License
        
        Copyright (c) 2025-2026 Will Stone (tribixbite)
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
        ---
        
        This MIT license covers the `beambam` Python package and associated tooling
        (bridge, daemon, MCP server, Home Assistant publisher, web UI, slicing helpers,
        analyze/simulate/cloud-fetch commands).
        
        It does NOT cover:
        
          * `BambuStudio/`, `bs-bionic/`, `bs-cli/`, `bs-gui/` — patched fork of
            Bambulab/BambuStudio (AGPL-3.0). See BambuStudio/LICENSE.
        
          * `patches/*.patch` — diffs against BambuStudio sources; same AGPL-3.0
            inherits from the upstream they modify.
        
          * `runtime/network_shim/`, `runtime/bambu_extract/` — interop tooling
            that links against BambuStudio internals; AGPL-3.0 applies to derivative
            work that links those translation units.
        
        The pure-Python tree (the PyPI distribution) does NOT link any AGPL code at
        runtime — it talks to printers over the wire (signed MQTT, FTPS, HTTP) and is
        safe to use under MIT for both open- and closed-source downstream projects.
License-File: LICENSE
Keywords: 3d-printing,a1,ams,bambu,bambulab,ftps,h2d,home-assistant,mcp,model-context-protocol,mqtt,p1s,slicer,x1c,x2d
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Home Automation
Classifier: Topic :: Printing
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Requires-Dist: cryptography>=41
Requires-Dist: paho-mqtt>=2.0
Provides-Extra: all
Requires-Dist: aiohttp>=3.9; extra == 'all'
Requires-Dist: anthropic>=0.30; extra == 'all'
Requires-Dist: mcp>=1.0; extra == 'all'
Requires-Dist: numpy-stl; extra == 'all'
Requires-Dist: openai>=1.30; extra == 'all'
Requires-Dist: pillow; extra == 'all'
Provides-Extra: assistant
Requires-Dist: anthropic>=0.30; extra == 'assistant'
Requires-Dist: openai>=1.30; extra == 'assistant'
Provides-Extra: dev
Requires-Dist: aiohttp>=3.9; extra == 'dev'
Requires-Dist: aiortc>=1.10; extra == 'dev'
Requires-Dist: amqtt>=0.11; extra == 'dev'
Requires-Dist: hatchling>=1.18; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: numpy; extra == 'dev'
Requires-Dist: numpy-stl; extra == 'dev'
Requires-Dist: pillow; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: slicing
Requires-Dist: numpy-stl; extra == 'slicing'
Requires-Dist: pillow; extra == 'slicing'
Provides-Extra: webui
Requires-Dist: aiohttp>=3.9; extra == 'webui'
Description-Content-Type: text/markdown

# beambam — LAN-first stack for Bambu Lab printers (any model, any firmware)

[![ci](https://github.com/tribixbite/beambam/actions/workflows/ci.yml/badge.svg)](https://github.com/tribixbite/beambam/actions/workflows/ci.yml)
[![pypi](https://img.shields.io/pypi/v/beambam.svg)](https://pypi.org/project/beambam/)
[![python](https://img.shields.io/pypi/pyversions/beambam.svg)](https://pypi.org/project/beambam/)
[![license](https://img.shields.io/pypi/l/beambam.svg)](LICENSE)

> *beam* (send) + *bam* (fast). Homepage: [beambam.boo](https://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 to `beambam` in v1.1.0 to reflect that
> the bridge supports every Bambu model, not just the X2D.

Roadmap: see [`ROADMAP.md`](ROADMAP.md) for v1.3.0 candidates + the
long-tail backlog. Per-release notes in [`CHANGELOG.md`](CHANGELOG.md).

## What is this

**Three things in one repo:**

1. **`x2d_bridge.py`** — 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.
2. **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.
3. **A Termux/aarch64 BambuStudio port**: source-patches against
   upstream `BambuStudio v02.06.00.51`, an `LD_PRELOAD` GTK/locale
   shim, an `libbambu_networking.so` ABI shim that lets the GUI's
   "Connect / AMS sync / Print" buttons drive printers via this bridge.

## Who is this for

* 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 cloud account just to print 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.

## Feature matrix

| Capability                          | this repo | Bambu Studio + Cloud | ha-bambulab |
|-------------------------------------|:---------:|:--------------------:|:-----------:|
| LAN-only operation (no cloud login) | ✅        | ⚠️ partial           | ⚠️ requires creds |
| **X2D / H2D RSA-SHA256 signed MQTT**| ✅        | ✅ (cloud)           | ❌ blocker  |
| aarch64 / Termux / Android          | ✅        | ❌ (no plugin .so)   | ❌ (HA Core required) |
| One-line install                    | ✅        | ❌                   | ⚠️ HACS    |
| CLI control (pause/resume/temp/...) | ✅        | ❌ (GUI only)        | ❌          |
| HTTP REST API + Server-Sent Events  | ✅        | ❌                   | ❌          |
| Prometheus `/metrics` endpoint      | ✅        | ❌                   | ❌          |
| Multi-printer auto-discovery (SSDP) | ✅        | ✅                   | ⚠️ manual  |
| 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`](docs/HA_VS_BAMBULAB.md).

## Printer compatibility

The bridge talks to every Bambu Lab printer that exposes the standard
LAN MQTT + FTPS endpoints. The signed-MQTT requirement (Jan-2025+ firmware
on most models, always-on for X2D/H2D-family) is handled transparently
using the publicly-leaked Bambu Connect cert — no cloud account or token
needed.

| Model       | Bambu code | Signed MQTT | Status | Upload | Print | 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; bridge signs anyway
(zero overhead, forward-compat).
² P1S/P1P/A1 enforcement varies by firmware; bridge signs regardless.

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`.

## Quick install + start

### `pip install beambam` (any Linux, macOS, Windows-WSL — recommended)

```bash
pip install beambam            # core bridge + CLI
pip install beambam[all]       # + HA publisher, MCP, web UI, AI assistant

beambam --version
```

### Configure the printer

```bash
cat >~/.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 start
```

### Web UI / daemon

```bash
beambam daemon --http 0.0.0.0:8765 \
    --queue --timelapse \
    --auth-token "$(openssl rand -hex 32)"
xdg-open http://localhost:8765/
```

### aarch64 Termux + BambuStudio GUI (Android phones / tablets)

```bash
# 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)
```

### Requirements
- 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

## Demo media

Five short H.264 MP4s in [`docs/demos/`](docs/demos/) — render with
`PYTHONPATH=. python3.12 runtime/demos/render.py` to regenerate.

| File | Duration | What |
|---|---|---|
| [`docs/demos/cli_demo.mp4`](docs/demos/cli_demo.mp4)   | 47s | bridge CLI: status / chamber-light / print / daemon up |
| [`docs/demos/gui_demo.mp4`](docs/demos/gui_demo.mp4)   | 16s | BambuStudio Termux port: Prepare tab, Device tab, sidebar shrink, X2D preset |
| [`docs/demos/mcp_demo.mp4`](docs/demos/mcp_demo.mp4)   | 57s | MCP stdio handshake: initialize → tools/list → tools/call status |
| [`docs/demos/webui_demo.mp4`](docs/demos/webui_demo.mp4) | 15s | thin web UI at S25 Ultra portrait + landscape + tablet viewport |
| [`docs/demos/ha_demo.mp4`](docs/demos/ha_demo.mp4)     | 55s | Home Assistant registry: 32 entities + 1 Device with live values |

## Per-feature documentation

| Feature              | Doc |
|----------------------|-----|
| Quick start          | [`docs/QUICKSTART.md`](docs/QUICKSTART.md) |
| Web UI               | [`docs/WEB_UI.md`](docs/WEB_UI.md) |
| MCP server (Claude Desktop, Cursor, ...) | [`docs/MCP.md`](docs/MCP.md) |
| WebRTC streaming     | [`docs/WEBRTC.md`](docs/WEBRTC.md) |
| Home Assistant — overview | [`docs/HA.md`](docs/HA.md) |
| Home Assistant — install + dashboard guide | [`docs/HA_SETUP.md`](docs/HA_SETUP.md) |
| HA vs ha-bambulab feature parity | [`docs/HA_VS_BAMBULAB.md`](docs/HA_VS_BAMBULAB.md) |
| Cloud bridge (cloud-state, cloud-print) | [`docs/CLOUD_BRIDGE.md`](docs/CLOUD_BRIDGE.md) |
| Local control paths (LAN MQTT, FTPS, RTSPS, LVL_Local) | [`docs/LOCAL_CONTROL_PATHS.md`](docs/LOCAL_CONTROL_PATHS.md) |
| Signed vs unsigned MQTT — leaked-key truth table | [`docs/SIGNED_VS_UNSIGNED.md`](docs/SIGNED_VS_UNSIGNED.md) |
| X2D runtime pipeline (canonical reference) | [`docs/X2D_RUNTIME_PIPELINE.md`](docs/X2D_RUNTIME_PIPELINE.md) |
| Multi-printer setup  | [`docs/MULTI_PRINTER.md`](docs/MULTI_PRINTER.md) |
| Print queue          | [`docs/QUEUE.md`](docs/QUEUE.md) |
| Timelapse browser    | [`docs/TIMELAPSE.md`](docs/TIMELAPSE.md) |
| AI assistant         | [`docs/ASSISTANT.md`](docs/ASSISTANT.md) |
| AMS color sync       | [`docs/COLORSYNC.md`](docs/COLORSYNC.md) |
| BambuStudio Termux port | this README §"BambuStudio Termux port" below |

> The Bambu Network Plug-in `.so` is 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.

## BambuStudio Termux port

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
├── x2d_bridge.py             # signed-MQTT LAN client + multi-surface daemon
├── 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.

## Detailed install / credentials / launch walkthrough

**TL;DR runtime guide:** see [docs/QUICKSTART.md](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.

### Use the prebuilt distribution

### One-line install (recommended)

```
bash <(curl -fsSL https://raw.githubusercontent.com/tribixbite/beambam/main/install.sh)
```

Idempotent — runs the platform check, `pkg install`s 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.

### Manual install (alternative)

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
```

### Termux dependencies

```
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, `wxFileDialog`s 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`).

### Build from source

```
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
```

## libbambu\_networking.so shim — making the GUI talk to LAN printers

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 `x2d_bridge.py serve` process.
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 `x2d_bridge.py serve` 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 `x2d_bridge.py serve` 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`.

## Using the LAN bridge (`x2d_bridge.py`)

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
x2d_bridge.py status

# Upload + start print on AMS slot 4
x2d_bridge.py print myfile.gcode.3mf --slot 3

# Long-running monitor — polls every 5s, exposes JSON at http://127.0.0.1:8765/state
x2d_bridge.py 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.

### Slicing + printing an STL

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
x2d_bridge.py slice-print model.stl --slot 3 --color Gold --bed 5 --timelapse

# Send an already-sliced .gcode.3mf
x2d_bridge.py 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.
```

### Print-control commands

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.** Only `system.*` 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. See
> [`docs/SIGNED_VS_UNSIGNED.md`](docs/SIGNED_VS_UNSIGNED.md). For
> `print.*` actions the working path is `cloud-print` (cloud broker,
> JWT auth, no signing) or starting the print from the touchscreen
> after the bridge has uploaded the 3MF.

```
x2d_bridge.py pause                      # pause the current print
x2d_bridge.py resume                     # resume after pause
x2d_bridge.py stop                       # abort the current print
x2d_bridge.py home                       # G28 — home all axes
x2d_bridge.py level                      # G29 — auto bed-level
x2d_bridge.py set-temp bed     60        # set_bed_temp 60°C
x2d_bridge.py set-temp nozzle 220 --idx 0  # set_nozzle_temp on extruder 0
x2d_bridge.py 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`)
x2d_bridge.py ams-load 0 3 --tar-temp 220   # AMS 0 / slot 3, preheat to 220
x2d_bridge.py ams-unload 0 --tar-temp 220   # unload from AMS 0
x2d_bridge.py jog X 10                   # relative move +10 mm on X
x2d_bridge.py jog Z -5 --feed 600        # relative -5 mm on Z @ 600 mm/min
x2d_bridge.py 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`](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.

### Camera proxy

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 on `ipcam.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 in `runtime/network_shim/lvl_local.py`.
  The same printer-touchscreen "LAN-mode liveview" toggle gates this
  path too — without it the printer rejects with status `0x0003013f`
  and 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.

### Exposing the daemon / camera on the LAN

`/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)
x2d_bridge.py daemon --http 0.0.0.0:8765
x2d_bridge.py 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.

### Filament presets without a cloud account

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: true` BBL filament profiles shipped under
  `resources/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.json` covering
  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.

### Bambu cloud login (optional)

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).

```
x2d_bridge.py cloud-login --email me@example.com --password '…'
x2d_bridge.py cloud-status     # confirms session, expiry, region
x2d_bridge.py 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.

### Cloud-mediated print + remote control

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.

```
x2d_bridge.py cloud-state                          # remote monitoring
x2d_bridge.py cloud-pause                          # remote control
x2d_bridge.py 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`](docs/CLOUD_BRIDGE.md).

## Thin web UI (`http://<phone>:8765/`)

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.jpg` snapshot polling, native HLS at `/cam.m3u8`, and
WebRTC at `/cam.webrtc/offer`.

```bash
python3.12 x2d_bridge.py 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.

## MCP server (Claude Desktop, Cursor, Continue, …)

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`).

```jsonc
// Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (mac)
//                 %APPDATA%\Claude\claude_desktop_config.json           (Windows)
//                 ~/.config/Claude/claude_desktop_config.json           (Linux)
{
  "mcpServers": {
    "x2d": {
      "command": "python3.12",
      "args": ["-m", "mcp_x2d"],
      "cwd": "/absolute/path/to/x2d"
    }
  }
}
```

Smoke-test the server before wiring it in:

```bash
python3.12 runtime/mcp/test_mcp.py     # 47/47 PASS, ALL TESTS PASSED
```

Full per-platform install + Termux-via-SSH setup in [`docs/MCP.md`](docs/MCP.md).

## What's broken on Termux without these patches

| 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) + `x2d_bridge.py 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) |

## Provenance

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

GPL-3.0+ (matches upstream BambuStudio). Bambu and BambuStudio are
trademarks of Shenzhen Bambu Lab Technology Co., Ltd. — this repo is not
affiliated with or endorsed by them.
