bambu cloud <-> simplyprint bridge
  • Python 87.3%
  • C++ 11.2%
  • Shell 0.9%
  • Dockerfile 0.4%
  • Makefile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Kate Meerburg f415bfda82
Dual-nozzle: number nozzles in the slicer's logical order (0 = left); log local refusals
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
2026-09-14 00:57:24 +00:00
bambridge Dual-nozzle: number nozzles in the slicer's logical order (0 = left); log local refusals 2026-09-14 00:57:24 +00:00
deploy Surface SimplyPrint setup codes: loud log line, state file, bridge status 2026-09-09 05:51:54 +00:00
docs/spikes Camera: follow the printer's ipcam.rtsp_url; redact codes in camera errors; fix nested change detection 2026-09-13 21:35:34 +00:00
shim Initial bridge + actuator implementation 2026-09-09 04:22:01 +00:00
tests Dual-nozzle: number nozzles in the slicer's logical order (0 = left); log local refusals 2026-09-14 00:57:24 +00:00
tools Dual-nozzle: number nozzles in the slicer's logical order (0 = left); log local refusals 2026-09-14 00:57:24 +00:00
.gitignore Initial bridge + actuator implementation 2026-09-09 04:22:01 +00:00
Makefile Add deploy (systemd, Docker), operations docs, spike checklists, plugin hang guard 2026-09-09 04:26:50 +00:00
OPERATIONS.md Dual-nozzle: number nozzles in the slicer's logical order (0 = left); log local refusals 2026-09-14 00:57:24 +00:00
pyproject.toml Harden learned-intermediate handling (security review) 2026-09-13 22:55:20 +00:00
README.md Dual-nozzle: number nozzles in the slicer's logical order (0 = left); log local refusals 2026-09-14 00:57:24 +00:00

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.so Bambu 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.