3D printers over MQTT: snapshot plus deltas, self-reconnect, and liveness checks
MQTT learned on real work: printer status for a live stream, a snapshot merged with deltas, self-reconnect, a side-car process, and tokens that expire silently.
- MQTT passes messages through a broker by topic. Devices send when something changes, so you never poll
- If a device sends only what changed (deltas), request the full picture (a snapshot) at the start and again periodically to recover anything missed
- Devices do not tell you when they break. Run a watcher that checks on a schedule, and keep the process that talks to devices apart from the main server
We run a live stream of 3D printing that combines several cameras into one picture, with a bar along the bottom showing each printer’s status: percent done, minutes left, nozzle temperature. Several printer brands in this setup report status over MQTT. This article uses that real work to explain MQTT from the basics up to the patterns that keep the system running all day without anyone restarting it.
Examples use Python 3.11 with the paho-mqtt library, on Windows and a home network.
MQTT in five minutes
MQTT sends messages through a broker. Senders do not know receivers; both only know a topic, a channel name such as device/01S00A123/report.
- publish sends a message to a topic
- subscribe tells the broker which topics you want; it delivers every new message to you
Unlike calling an API, you never ask over and over whether something changed. The device sends when it does, which suits devices with flaky networks and limited power.
| Term | Meaning |
|---|---|
| QoS 0 | sent once, may be lost; fine for values sent often anyway |
| QoS 1 | guaranteed to arrive, possibly more than once |
| QoS 2 | arrives exactly once, slowest |
| retained | the broker keeps a topic’s last message, so late subscribers get it at once |
| last will | a message the broker announces for you if you drop without saying goodbye; used to signal offline |
| keepalive | how often to signal “still here”; silent for too long and the broker treats you as gone |
Pattern 1: request a snapshot, then merge deltas
Bambu Lab printers are a broker themselves, reached over TLS on port 8883 on the local network. Once connected, we subscribe to the printer’s status topic.
The catch: the printer does not send its full status every time, only what changed (a delta), such as {"print": {"mc_percent": 42}}. Connect in the middle of a print and you do not know the job name, temperatures or time left until those values happen to change.
The fix is to ask for the full picture the moment you connect, then merge each delta into it:
def on_connect(client, userdata, flags, rc, properties=None):
client.subscribe(f"device/{serial}/report")
client.publish(f"device/{serial}/request",
json.dumps({"pushing": {"sequence_id": "0", "command": "pushall"}}))
def on_message(client, userdata, msg):
payload = json.loads(msg.payload)
state.update(payload.get("print", {})) # merge the delta into the picture we hold
And ask for the full picture again every 15 seconds, even when nothing seems wrong. If any delta is lost on the way, the picture we hold stays wrong forever; asking regularly limits any error to 15 seconds.
Principle: a system that sends only changes must always offer a way to get the whole picture, at the start and periodically.
Pattern 2: reconnect by itself
Wi-Fi drops are normal, and printers restart. A system that runs all day must reconnect without being told:
client.reconnect_delay_set(min_delay=1, max_delay=30)
client.connect(host, 8883, keepalive=30)
client.loop_start()
reconnect_delay_set retries with growing gaps, starting at 1 second and capped at 30: not so often that it floods a printer that is just booting, not so rarely that data goes missing for long.
Also, announce offline the moment you drop. Never leave the screen showing the last values as if all were well:
def on_disconnect(client, userdata, *args):
state_online = False
write_status() # write offline now, not on the next cycle
Pattern 3: a separate process for the device
At first the main server connected to MQTT itself, and the connection timed out every time even though credentials and network were fine. On Windows, the server needs one kind of event loop to run subprocesses (for ffmpeg), while the MQTT library needs another, and the two cannot share one program.
The fix was a small side-car process with one job: talk to the printer and write its status to a JSON file, at most every 2 seconds. The main server just reads the file:
{"online": true, "progress": 42, "remaining_min": 87, "nozzle": 219.5, "ts": 1760598000}
It brought more than we aimed for:
- A printer problem cannot take the server down. If the side-car crashes, it can be restarted separately
tsrecords the time. If the file goes stale, the reader knows the data is old and shows “unknown” instead of wrong values- Easy to test. Fake the status file; no real printer needed
Pattern 4: watch what expires silently
Another printer brand sends its video link over MQTT. The link carries a token that expires by itself. Once it does, the link returns 404, the video fetcher keeps retrying the dead link, and the live picture freezes at 0 frames, with nothing warning anyone.
So a watcher runs every 30 seconds:
- Try the current video link
- If it returns 404 or no data, tell the printer over MQTT to start capturing again and request fresh status for a new token
- If the token changed, reconfigure the video fetcher, then wait until data is really flowing
Devices do not tell you when they break. Anything that can expire needs someone checking regularly, and that someone should be a program.
Security
- Never expose a device’s MQTT to the internet. Keep it on the local network. To watch from outside, go through a VPN or your own server that relays only the data needed
- Self-signed certificates. Many printers use certificates that fail normal checks, so sample code often switches verification off. That is acceptable only on a local network you control. Better: store that device’s certificate and check it matches every time
- The device’s access code is a secret. Keep it in a config file that never goes into git
Common mistakes
- Subscribing and waiting without asking for a snapshot, so the screen stays empty until values change
- Merging deltas without ever re-requesting the snapshot, so a missed value stays wrong forever
- Not announcing offline on disconnect, so the screen shows old values as if working
- Putting device code inside the main server, so a stuck device stalls the server
- Writing the status file on every incoming message when several arrive each second
FAQ
Do I need to run my own broker?
It depends on the device. Some printers are a broker themselves on the local network, so you connect straight to them. For sensors you build yourself, such as with an ESP32, you usually need a central broker such as Mosquitto on a home machine or a server.
Which QoS should I use?
Frequent status updates are fine at QoS 0, since a new value is always coming. Commands that must arrive, such as stopping a print, should use QoS 1, and be safe to receive twice.
Commands and settings were tested on sample systems. Try them on a test machine before production.