One model, two runtimes: how a 3D-printer watchdog ships the same Python to Pyodide and CPython
PrintGuard 2.0 watches FDM 3D printers (melted filament, layer by layer extrusion) for print failures.
PrintGuard 2.0 watches FDM 3D printers (melted filament, layer by layer extrusion) for print failures.
A 3D printer that runs for ten hours without supervision is, in practice, a small computer-vision problem waiting to happen. Fused-deposition-modeling (FDM) printers lay down melted filament layer by layer, and the failure modes are visual: a print detaches from the bed, the extruder clogs, the job slowly turns into what makers call "spaghetti." PrintGuard 2.0, an open-source watchdog from developer Oliver Bravery, first announced on r/MachineLearning, takes a different engineering bet from the obvious path. It is not the model that is the news. A ShuffleNetV2 encoder plus a few-shot prototypical classifier is a known recipe, drawn from Bravery's own dissertation work. The news is the architecture around it: one Python engine that runs unchanged in a browser tab and on a server, with every runtime-specific line of code confined to a single Platform contract per side.
The deployment constraint is the interesting one. A small CV model that watches a workshop camera has to do the same job whether it lives in a browser next to a webcam over WebSocket or on a home server pulling an RTSP stream from a print farm. Most projects split at that fork and accumulate two divergent codebases, one for Pyodide (CPython compiled to WebAssembly) and one for CPython, and quietly let them drift. PrintGuard's design refuses that path. The engine never imports a transport-aware library directly. Instead, it talks to a small Platform contract that abstracts the transport layer: inference, camera discovery, frame capture, HTTP serving, JPEG encoding, and state persistence. There are exactly two implementations of that contract, one for the browser and one for the server, and the same JSON command and event protocol carries both. The model is exported as a roughly 5 MB TFLite file via LiteRT, then dispatched through LiteRT.js (WASM) in the browser and through ai-edge-litert on CPython.
The second piece of the bet is what Bravery calls a "water-filling" scheduler. A small host has, say, four CPU cores and six printers; it does not have enough workers to watch every camera every frame. The scheduler observes inference latency and redistributes workers across the cameras that are actively printing, giving more workers to cameras where the model is finishing quickly and fewer to cameras where it is not. Idle printers cost nothing: the engine gates inference on job state, so a printer that is sitting on the home screen never triggers a forward pass. The author has framed this as a fairness property, in the sense that no in-use camera is starved while an idle camera consumes a slot. It is a small systems idea, but it is the kind of design choice that shows up only when you actually run a model on a Raspberry Pi under load.
Per-printer tuning is the third bet, and the one most operators will feel first. The few-shot classifier assigns a new failure to the nearest prototype in embedding space, so the natural knob is the distance threshold. PrintGuard exposes sensitivity and threshold sliders per printer, and they map directly onto prototype distances. A user with a wide-angle webcam and overhead lighting can dial the threshold tighter than a user with a long-throw lens and warm-white LEDs, without retraining. That is the load-bearing claim of the design, and it is the one a serious operator will want to test. The author reports it, and the underlying research code is in the Edge-FDM-Fault-Detection repo, but no independent benchmark has been published. Until a third party runs the same model against the Obico Spaghetti Detection baseline on a held-out camera, the threshold-tuning story is a working hypothesis, not a published result. The author's own dissertation is also unpublished: the Edge-FDM-Fault-Detection README states an intent to revise it into an arXiv preprint.
A few constraints are worth flagging. A 5 MB TFLite model doing per-frame computer vision on a video stream is a real client-side cost: CPU time, memory, and battery, especially in a browser tab. The browser deployment is real, and the project's own docs site is the live demo, but the README is explicit that it works only over HTTPS or localhost, because that is what getUserMedia requires. Telegram's API sends no CORS headers, so the Telegram notification channel is hub-only. And the project ships no authentication. The README's recommendation is to put PrintGuard behind an identity layer, with Tailscale, Cloudflare Tunnel + Access, or oauth2-proxy named as concrete options. Calling this "production-ready" without that layer would be a material unsupported claim.
The interesting question is whether the pattern travels. A single Python engine that runs unchanged in Pyodide and CPython, with all transport-specific code confined to one Platform contract, is a way of buying an insurance policy against the browser build and the server build quietly drifting. It is also, in practice, a way of buying an extra maintenance cost: two Platform implementations that have to be kept in step at the contract level, not just at the type level. The PrintGuard repository shows what that maintenance looks like in the open, and it is a useful artifact for anyone considering the same pattern for a different small CV model.