- Python 87.3%
- C++ 11.2%
- Shell 0.9%
- Dockerfile 0.4%
- Makefile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Everything SimplyPrint-facing was indexed by the firmware extruder id (0 = right, 1 = left), but slicers and SimplyPrint use the logical order from Studio's machine profile (physical_extruder_map [1, 0] for H2D/X2D: tool 0 = left). Result: AMS units, temperatures and the active slot appeared under the wrong nozzle, and after swapping nozzles in SimplyPrint its mms_map no longer resolved, failing the job locally. - ReportDocument.extruder_for_tool / tool_for_extruder / tool_temps - state_map: temperatures, per-nozzle AMS layout, externals and active slot go through the map; SlotTable keys are (logical nozzle, ext) so mms_map resolves directly - actuator side unchanged (3MF filament_maps 1=left/2=right -> cloud nozzle id, as Studio) - _fail_file now logs the reason (jobs no longer vanish silently between FILE and actuator); ams_unmapped lists the known slot keys - tests/sim updated to the logical convention |
||
| bambridge | ||
| deploy | ||
| docs/spikes | ||
| shim | ||
| tests | ||
| tools | ||
| .gitignore | ||
| Makefile | ||
| OPERATIONS.md | ||
| pyproject.toml | ||
| README.md | ||
bambridge — SimplyPrint ↔ Bambu Lab, cloud mode preserved
Use SimplyPrint (queue, start, monitor, pause/resume/cancel, webcam) with Bambu printers that stay bound to Bambu Cloud, so Bambu Handy, MakerWorld one-click printing and OTA firmware keep working exactly as before. Built for an H2D + X2D on a Linux home server; works for other models.
SimplyPrint cloud ──wss──▶ bridge ──HTTP 127.0.0.1:8765──▶ actuator ──▶ libbambu_networking.so ──▶ Bambu Cloud / LAN FTPS ──▶ printer
▲ (official plugin, unmodified)
printer ──MQTT 8883 (subscribe-only) + camera 322/6000──┘
- Reads come straight from the printer on the LAN (local MQTT + camera). No cloud needed.
- Writes (start print, pause/resume/stop) go through Bambu's own network plugin — the
same
libbambu_networking.soBambu Studio downloads — hosted in a small C++ shim. To Bambu's infrastructure this host is a logged-in Bambu Studio on Linux. Sends use Studio's own route ladder: LAN FTPS upload + cloud task record first, pure cloud upload second. - The bridge never trusts "sent"; a job counts as started only when the printer's MQTT report shows it running with the name we gave it.
Why not X? Bambu Connect has no Linux build and no automation surface; the Local Server SDK,
Fleet Hub and Developer Mode all unbind the printer from Bambu Cloud; SimplyPrint's own Bambu
client is monitor-only in cloud mode. See docs/spikes/S0-plugin-host.md.
Components
| Path | What |
|---|---|
shim/ |
libbnhost.so: plain-C facade over the plugin's C++ API (mirrors Studio's NetworkAgent). Built with the distro g++ so the libstdc++ ABI matches. |
bambridge/actuator/ |
Plugin fetch + version pin, headless Bambu login, .gcode.3mf inspection, AMS mapping, one-job-at-a-time send lane, HTTP API. Knows nothing about SimplyPrint. |
bambridge/bridge/ |
simplyprint-ws-client integration: local MQTT ingest, state mapping (X1/P1/A1 and H2D/X2D schemas), cameras, demand handlers, interlocks. |
bambridge/common/ |
Config (one TOML), logging (JSON lines, secrets redacted), notifier, report-document parser shared by both. |
deploy/linux/ |
systemd units + install.sh / uninstall.sh. deploy/docker/ alternative. |
docs/ |
Spike notes, operations. |
Install (Debian/Ubuntu host on the printers' LAN)
git clone … bambridge && cd bambridge
sudo ./deploy/linux/install.sh # builds the shim, venv in /opt/bambridge, state in /var/lib/bambridge
bambridge login --browser # sign in with Google/Apple/email in your browser (see below); or `bambridge login` for email+password
bambridge bridge add-printer # per printer: name + serial; IP and access code auto (LAN discovery + Bambu Cloud) or typed in
sudo systemctl enable --now bambridge-actuator bambridge-bridge
bambridge actuator selftest # plugin ok, logged in, cloud connected, printers bound, state flowing
bambridge bridge doctor # local MQTT + camera per printer, actuator reachable
Why auto-discovery is safe. LAN announcements are unauthenticated, so the bridge treats
them purely as a hint for where to connect. Who it is talking to is decided by TLS: the
printer must present a certificate that chains to Bambu's device CA and is issued to that
printer's serial number, or the handshake fails before the access code is sent (MQTT and the
port-6000 camera alike). Announcements are also ignored unless they come from the address they
advertise and that address is LAN-scoped, and a working link is never re-pointed by an
announcement; only a down link (or an explicit ip= in the TOML) moves. If you'd rather not
listen at all, set ip= for every printer and discovery has nothing to decide.
Nozzle numbering (H2D/X2D). SimplyPrint's nozzle 0 / "1" is the left nozzle and
nozzle 1 / "2" the right one — the slicer's logical order (Studio's physical_extruder_map
is [1, 0]: firmware extruder id 1 = left, 0 = right). Temperatures, the AMS layout per nozzle,
the active slot and the mms_map SimplyPrint sends are all translated through that map, so no
manual swapping is needed in SimplyPrint.
Camera. P1/A1 use the JPEG stream on port 6000; X1/H2/X2 use RTSPS on 322, which the
printer only serves with LAN Only Liveview switched on (it announces the state over MQTT and
the bridge follows it live). The bridge speaks RTSP itself (TLS verified against Bambu's device
CAs and pinned to the printer's serial, Digest auth, TCP-interleaved RTP, H.264/H.265) and uses
PyAV only to decode, so it does not depend on how your ffmpeg was built. bambridge bridge doctor walks the steps (tls → describe → setup → play → rtp → decode → jpeg) and says which
one fails.
Adding the printer in SimplyPrint. There is no local web UI; pairing uses SimplyPrint's
setup-code flow (same as their Duet/Bambu integrations). Once bambridge-bridge is running and
online, each not-yet-added printer gets a short code from SimplyPrint:
bambridge bridge status
# lab-h2d 0948… WAITING TO BE ADDED — setup code: K8QZ
(also logged as *** lab-h2d: not yet added in SimplyPrint. Setup code: K8QZ ***). In the
SimplyPrint web panel or app: Printers → Add printer, enter that code, pick the Bambu model
(H2D / X2D), and set Multi-material to Bambu AMS. bridge status then shows PAIRED. The
pairing is stored under state/simplyprint/ and survives restarts and re-installs. Give the printers DHCP reservations. On X1/H2/X2 models enable
LAN Mode Liveview (a.k.a. LAN Only Liveview) on the printer for the webcam; the printer
stays cloud-bound.
Login options. bambridge login --browser prints a bambulab.com URL; open it on any machine,
sign in however you normally do (Google works), and Bambu redirects the browser to
http://localhost:13618/?ticket=…. If the browser is on the bridge host, or you ran
ssh -L 13618:localhost:13618 <host> first, the ticket is picked up automatically; otherwise
the page fails to load, which is expected: copy the URL from the address bar and paste it into
the waiting prompt. This is the same one-time-ticket mechanism Bambu Studio uses for
third-party sign-in; the plugin exchanges the ticket for its session. --provider google skips
Bambu's page and goes straight to Google. Plain bambridge login does email + password (+ the
emailed code or TFA if Bambu asks); --token-file accepts a token JSON if you have one.
Docker instead: cd deploy/docker && docker compose run --rm bambridge login && docker compose run --rm bambridge bridge add-printer && docker compose up -d (host networking).
Configuration
One file, $BAMBRIDGE_HOME/bambridge.toml (mode 0600; /var/lib/bambridge when installed).
bambridge config prints it with secrets masked. The interesting knobs:
[bambu]
region_country_code = "NL" # picks api.bambulab.com vs .cn
plugin_version = "02.08.02.54" # pinned; `bambridge plugin fetch` refuses silent changes
studio_version = "02.08.02.61" # whose headers/cert we present
[actuator]
listen_host = "127.0.0.1" # non-loopback requires `secret`
routes = ["lan_record", "cloud"] # Studio's order; add "lan" for no-history LAN-only as last resort
send_timeout_s = 240
[bridge]
start_timeout_s = 180 # FILE demand → printer reports PREPARE/RUNNING with our name
control_timeout_s = 60
offline_after_s = 30
[notify]
kind = "ntfy" # ntfy | pushover | slack | none
target = "https://ntfy.sh/your-topic"
[[printer]]
name = "lab-h2d"
serial = "0948…"
ip = "" # blank = follow the printer's LAN announcements (UDP 2021); set it to pin an address
access_code = "" # blank = use the code Bambu Cloud reports (via the actuator); set it to override
camera = "auto" # auto | rtsp322 | jpeg6000 | off
pushall_on_connect = true
What SimplyPrint can and can't do through this bridge
| Works | Notes |
|---|---|
| Status, temps (both nozzles), progress, layers, time left | within a second or two of Handy |
| Job attribution | SimplyPrint-started jobs carry their job id (task name sp-<jobid>-<file>); Handy/Studio-started jobs appear as external jobs |
| AMS → material data | per nozzle; AMS / AMS 2 Pro / AMS HT / external spool; active slot |
| HMS / print_error → notifications | per-family tables from e.bambulab.com (auto-fetched, weekly refresh), with Studio's rules: codes Bambu marks internal or has no text for are not shown and don't block starts; bambridge hms explain <code> tells you why a code was or wasn't shown |
| Webcam | RTSPS 322 (X1/H2/X2/P2, needs LAN liveview toggle) or JPEG 6000 (P1/A1) |
Start print (.gcode.3mf) |
auto AMS mapping like Studio, or SimplyPrint's mms_map; bed-levelling/flow-cal/timelapse options honoured |
| Pause / resume / cancel | signed by the plugin; confirmed via MQTT |
| Not available | G-code terminal, manual temperature/motion, plain .gcode files (Bambu cloud printers need 3MF), host reboot from SimplyPrint |
Safety interlocks (bridge, before any send)
Printer must be IDLE/FINISH/FAILED, no fatal/serious HMS active, MQTT link up and fresh, no
other start in flight. The actuator repeats the state checks against the cloud view. A send
that the printer never acknowledges is not retried (no double prints); it fails as
start_unconfirmed and alerts. If Handy starts something while a SimplyPrint send is in
flight, the SimplyPrint job fails as printer_busy_external and the external job is shown.
Development
make -C shim # needs g++, nlohmann-json3-dev
uv venv -p 3.12 .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/pytest -q # report parsing, state map, 3MF, mapping, actuator (fake plugin), bridge client
.venv/bin/python tools/sim_e2e.py # whole pipeline against a fake SimplyPrint + simulated H2D; prints every message sent
BAMBRIDGE_HOME=$PWD/.dev bambridge init && bambridge plugin fetch && bambridge actuator serve
tests/fixtures/mqtt/*_habambulab.json are captured printer reports from
ha-bambulab (MIT). Add your own captures (redact
serials/access codes) as tests/fixtures/mqtt/<name>.json with a top-level pushall key.
Licence: AGPL-3.0-or-later (inherits from simplyprint-ws-client). bambridge/data/ carries
Bambu's device CA bundle and slicer certificate as shipped in Bambu Studio's resources/cert.