Liking cljdoc? Tell your friends :D

Bambu printer network seams (read-only reference study)

References: clones-ref/mcp-bambu at aaef252 (paths below prefixed P/ = bambu_mcp/); clones-ref/bambu-mcp at c8be014 (T/ = src/). No printer was contacted; protocol claims describe implementation, not verified hardware behavior. No Rust cdylib or GUI/document engine exists in these two network references: the native work is printer networking (P/printer.py:1-12; T/mqtt-client.ts:1-20).

Entry, ownership and dispatch

SurfaceActual import/call shapeLifetime / dispatch
Python LANconfig.PrinterSpec(name,host,serial,access_code); config.load_config(path=None) resolves TOML and credentials; printer.BambuPrinter(spec).get_report(timeout=12), .send(payload,settle=1.5), .pause/.resume/.stop/.set_light/.start_print, .list_dir/.upload/.download_bytes/.download (P/config.py:19-29,101-160; P/printer.py:34-52,54-130,133-242).Instance holds spec and mutable sequence counter, not a persistent socket; each report/send creates a paho client, connects, starts loop thread, stops and disconnects (P/printer.py:38-52,54-118). get_report waits up to 12s and can return partial merged state; send has no device ACK (P/printer.py:54-102,104-126). INFERRED: serialize access to one instance's _seq when called from multiple JVM threads (P/printer.py:38-52).
Python monitormonitor.Watcher(spec,cfg,notifier,rules).client() installs callbacks; monitor.main() maintains clients and loops until interrupt (P/monitor.py:38-56,144-181,460-509).Owns mutable state and event/snapshot files, MQTT callback thread, reconnect and optional daemon threads for diagnosis/finish (P/monitor.py:43-56,68-127,144-181,280-287,434-435). Snapshot merges deltas because report is sparse (P/monitor.py:80-111).
Python MCPserver.call_tool(name,arguments) maps 17 named handlers and await asyncio.to_thread(handler, arguments or {}), catches PrinterError, KeyError and Exception; handlers return MCP TextContent, not JSON result objects (P/server.py:280-311). handle_start_print verifies confirm, idle and faults before sending (P/server.py:387-417).Importing server loads config and creates MCP Server immediately (P/server.py:28-45). INFERRED: import lower-level modules rather than server for libpython-clj; do not start stdio server (P/server.py:28-45,280-311).
TypeScript LANnew BambuMQTTClient(config).connect(), requestStatus()/getCachedStatus(), print/AMS/LED/camera controls and disconnect() are exported (T/mqtt-client.ts:101-129,218-225,330-368,441-569).Instance owns one mqtt client, sequence, cached status/time; connect subscribes, sets reconnect period, receives asynchronous deltas; disconnect ends connection (T/mqtt-client.ts:101-210,218-225). Async Promise methods require a live Node event loop (T/mqtt-client.ts:115-210,238-298).
TypeScript MCPindex.ts constructs BambuLabMCP on import, loads credentials/env, registers modules' createHandlers(ctx) into a name→async-handler object; CallToolRequestSchema invokes checkConfirmation, then await handler(args), catches into err() (T/index.ts:51-118,120-166,215-244,278-279).INFERRED: do not require dist/index.js as library: import side effects start stdio MCP server; import dist/mqtt-client.js, dist/tools/files.js, dist/tools/camera.js directly (T/index.ts:51-118,278-279; T/tools/files.ts:57-105; T/tools/camera.ts:11-81).

Not a 500+ command registry: the 500+ count in the craft brief applies to the graphics references, not Bambu. Python's 17-name dict is built inside call_tool and routes to synchronous handlers (P/server.py:280-307); TypeScript's modules expose tool arrays and handler maps assembled by BambuLabMCP (T/index.ts:108-155,215-244). Neither lower-level transport exports a universal execute(id,params-json)->result-json: Python send accepts a dict and returns None, TS's private sendCommand(command,params,waitForResponse) builds a message and resolves an object (P/printer.py:104-126; T/mqtt-client.ts:235-304). Closest reusable JSON boundary is MQTT JSON encoding/decoding and the MCP dispatch, not a control-channel server (P/printer.py:64-88; T/mqtt-client.ts:153-161,249-299; P/server.py:280-311; T/index.ts:225-244).

Protocol, data and safety

  • MQTT TLS port 8883, username bblp, password access code; subscribe device/{serial}/report, publish JSON to device/{serial}/request. Python pushing.pushall payload contains string sequence_id and command; report merges the print dict; TS accepts print || mc_print, caches deltas (P/printer.py:28-32,43-88; T/mqtt-client.ts:139-161,249-266,333-349). TS sendCommand matches response section and sequence_id, times out after 10s; Python send merely sleeps after publish and makes no printer-ACK claim (T/mqtt-client.ts:270-303; P/printer.py:104-126). TS warns only one concurrent MQTT client on connection reset; Python monitor documents second connections producing empty reports (T/mqtt-client.ts:200-214; P/monitor.py:60-66): INFERRED prefer one persistent owner per printer rather than concurrent polling and monitoring.
  • TLS certificate validation is disabled in both clients (CERT_NONE/tls_insecure_set(True); rejectUnauthorized:false), not certificate pinning despite Python's comment. TS can attach RSA-SHA256 signature when privateKey and certId are present: JSON bytes signed with Node crypto.sign, optional top-level user_id, header sign_ver/sign_alg/sign_string/cert_id/payload_len; missing key sends unsigned (P/printer.py:42-47; T/mqtt-client.ts:126-139,307-329). getAppCert accepts env overrides or embeds a private key and certificate in source; do not copy those secrets into our documentation or artifacts (T/types.ts:18-42,44-93). TS connection handler obtains that cert and user id; Python does not sign outgoing commands (T/tools/connection.ts:50-74; P/printer.py:104-126).
  • Control payloads are nested print commands pause/resume/stop, project_file with Metadata/plate_N.gcode and file:///sdcard/<name>, flags (AMS, leveling, timelapse), and system.ledctrl (P/printer.py:129-174; T/mqtt-client.ts:353-369,417-465,516-539). Mismatch to resolve: Python project payload lacks file and ams_mapping; TS includes both; TS gcode_file uses bed_levelling while TS project_file uses bed_leveling (P/printer.py:149-174; T/mqtt-client.ts:377-465). TS can send arbitrary print.gcode_line, so guard that operation separately (T/mqtt-client.ts:373-375).
  • FTPS implicit TLS port 990, username bblp and access code. Python's list_dir/upload/download_bytes/download use external curl, -k, passive FTP and URL quoting; download to .part then rename (P/printer.py:175-242). TS ftpUploadFile uses basic-ftp secure:true, validation disabled, and falls back to an external curl process if library fails (T/tools/files.ts:7-8,39-105). Thus the draft wave-2 assertion that existing Python printer.upload is an all-in-process libpython-clj adapter, or that the TS tool is subprocess-free, is FALSE (P/printer.py:180-188,213-242; T/tools/files.ts:39-52,78-102). INFERRED: remove/bypass curl fallback at our adapter boundary and validate that implicit FTPS works with the in-process library before promising upload (T/tools/files.ts:68-102; P/printer.py:8-12).
  • Camera: TS captureSnapshot(host,accessCode,outputPath) uses TLS port 6000, sends an 80-byte auth packet with LE 0x40, 0x3000, bblp at offset 16 and code at 48; reads 16-byte frame header, 24-bit little-endian payload size, validates JPEG SOI/EOI, saves bytes to disk; 10s timeout (T/tools/camera.ts:11-81). Camera recording/timelapse go over MQTT rather than this stream (T/tools/camera.ts:149-162; T/mqtt-client.ts:523-538). INFERRED: bound untrusted advertised JPEG length before allocating/concatenating (T/tools/camera.ts:28-55).
  • Data crossing: Python PrinterSpec, PrinterStatus, AMSSlot are dataclasses, not declared JSON serializers; map their primitive fields explicitly; raw reports and manifest records are JSON dicts; download_bytes returns Python bytes requiring explicit byte conversion, upload takes pathlib.Path and returns filename (P/config.py:19-29; P/models.py:27-97; P/printer.py:209-232; P/manifest.py:26-60). TS interfaces are compile-time only, runtime report is an open dictionary; captureSnapshot returns disk path, not JPEG bytes (T/mqtt-client.ts:11-99; T/tools/camera.ts:11-81). Print status fields, AMS trays, HMS codes are domain nouns; names and definitions at P/models.py:14-97 and T/mqtt-client.ts:25-99. Job/source manifest links an exported job to model path, params, STL and timestamp (P/manifest.py:43-62).
  • Error boundaries: Python PrinterError for network, timeouts, FTP failures and missing files, plus config KeyError; MCP catches these and broad Exception as text. Python callback's invalid JSON is ignored; nonzero MQTT connect result can signal Event and become timeout/no-report, and send can return without ACK (P/printer.py:34-36,69-102,109-126,184-188,213-242; P/config.py:69-81; P/server.py:305-311). TS rejects promises on connection/publish/response timeout, catches malformed reports, MCP catches and wraps tool errors; ftpUploadFile catch triggers curl fallback (T/mqtt-client.ts:153-196,238-303; T/index.ts:225-244; T/tools/files.ts:68-102). INFERRED: never equate publish completion to physical execution (P/printer.py:104-126; T/mqtt-client.ts:268-303).
  • Headless: neither network path requires a GUI, GPU or tokio; Python paho uses its loop thread, blocking wait, and Python MCP dispatch offloads blocking functions through asyncio; TS uses Node MQTT callbacks/promises and timers (P/printer.py:54-126; P/server.py:280-311; T/mqtt-client.ts:115-196,238-303). No live printer test performed. Package requirements: Python >=3.11,<3.15, mcp>=1.28,<2, paho-mqtt>=2, defusedxml>=0.7 (mcp-bambu/pyproject.toml:1-27); TS ESM Node >=18, mqtt, basic-ftp, MCP SDK (bambu-mcp/package.json:1-15,32-44).
  • Vocabulary: open sets = printer names/serials, report keys, HMS code parts, tool module handlers, material/color/job names (P/config.py:43-46,150-159; P/models.py:14-36,63-97; T/mqtt-client.ts:94-99; T/index.ts:108-155). Closed local policy = Python PRINTABLE={.3mf,.gcode} and SLICE_ME (P/push.py:27-28); TS DANGEROUS_TOOLS is a manually maintained set (T/write-protection.ts:3-20). INFERRED: do not treat observed gcode_state as exhaustive firmware enum: Python only declares observed idle set and TS types it as string (P/models.py:13-14,40-42; T/mqtt-client.ts:25-29).

Ranked attachment points

  1. Python lower-level BambuPrinter for MQTT + PrinterStatus.from_report, manifest.record/lookup via libpython-clj: minimal direct imports, simple dict/value mapping; requires serialized per-printer access, paho lifecycle and Python exception conversion. Exclude BambuPrinter FTP methods because they spawn curl (P/printer.py:38-174,175-242; P/models.py:63-97; P/manifest.py:43-78).
  2. Node BambuMQTTClient via cljs npm mqtt or importing compiled class: persistent cached status, signed command path, explicit connect/disconnect; forces async session owner and secret handling. For upload use basic-ftp directly without existing ftpUploadFile fallback; camera TLS stream needs binary framing and bounded JPEG allocation (T/mqtt-client.ts:101-210,235-329; T/tools/files.ts:57-105; T/tools/camera.ts:11-81).
  3. MCP handler dispatch as reference for vocabulary/confirmation, NOT a subprocess adapter: both server paths provide schemas and error presentation, but import side effects, text wrapping and broad any/dict routing make them less suitable for typed values; TypeScript entry starts server immediately, Python handler still shells out on FTP (P/server.py:28-45,280-311,387-417; T/index.ts:120-155,215-279; P/printer.py:175-242). INFERRED: define a small closed host command vocabulary rather than masquerading as the graphics 500+ registry (P/server.py:280-307; T/index.ts:108-155).

Open questions for the wave-2 gate

  • Does the target firmware accept unsigned Python commands or require the TS signing header? Both implementations differ; neither was exercised against a printer (P/printer.py:104-126; T/mqtt-client.ts:307-329).
  • Can Node basic-ftp actually negotiate this printer's implicit FTPS and no-REST behavior without curl fallback? TS explicitly falls back, Python explicitly rejects library FTPS for this device (T/tools/files.ts:68-102; P/printer.py:8-12).
  • Which concurrent-client limit holds for each printer/firmware, and is MQTT report mc_print necessary for our target devices? Code warns on competition and TS accepts both report roots; no live measurement (P/monitor.py:60-66; T/mqtt-client.ts:153-161,200-214).

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close