Write a print-failure detector
Cobblr can watch a running print's camera and pause the job when it starts to fail. The watch runs behind a registry of detectors, one per way of scoring a frame. Some are in-process (a local model on the machine's bridge, or the workspace's vision AI). An external service (a self-hosted Obico ML API, PrintGuard, an in-house model box) is added as a detector manifest: a small package carrying JSON, no hardcoded service logic. See digital fabrication for how the watch fits the print workflow.
Detectors live inside the Digital Fabrication module, which ships as Experimental; the status note at the top of that page says what that means.
Cobblr folds each reading into its own rolling score and trips at your threshold. So a manifest only has to turn one sample into a number in the range 0 to 1 (or null when there is no usable reading).
Two shapes
Pick by where the camera lives:
frame-scorer: Cobblr hands the service a frame and reads a verdict back. Use it when the service scores an image you give it.camera-watcher: the service pulls its own camera and keeps a rolling score; Cobblr reads that score for a mapped camera.
A frame-scorer
This is the built-in Obico ML detector. Cobblr passes a snapshot URL, Obico fetches and scores it, and returns an array of detections. The probability is the highest confidence over the array (an empty array means a clean frame):
import type { DetectorPackage } from "../types.js";
import { DetectorManifest } from "../manifest.js";
export const builtin: DetectorPackage = {
key: "obico-ml",
name: "Obico ML API (self-hosted)",
summary: "The Spaghetti Detective's ml_api, run standalone. Frame-scorer, no auth.",
external: true,
manifest: DetectorManifest.parse({
id: "obico-ml",
name: "Obico ML API",
shape: "frame-scorer",
health: { method: "GET", path: "/hc/" },
detect: {
method: "GET",
path: "/p/?img={frameUrl}",
frameRef: "url",
probability: "$[*][1]",
reduce: "max",
},
}),
};
The detect block says how a frame is delivered and scored:
frameRef: "url"passes a snapshot URL the service fetches; the URL must be reachable by the service. Use"body"to POST the JPEG bytes instead, which works even for a relayed snapshot the service can't reach itself.- The read turns the response into a number. A numeric read gives a
probabilitypath expression and areduce("max"over an array of detections,"first", or omitted for a single scalar).$[*][1]here means the second element of every item in a top-level array, which is where Obico puts its confidences.
A categorical read
Some services report a class, not a number. Map the class to a probability with
label, failureValues, and successValues. This is the PrintGuard shape,
which returns a prediction string:
detect: {
method: "POST",
path: "/classify",
frameRef: "body",
bodyType: "raw",
contentType: "image/jpeg",
label: "$.prediction",
failureValues: ["failure"],
successValues: ["success"]
}
A matched failureValues reads as 1.0, a matched successValues as 0.0, and
anything else (an "unknown") as no reading, so an uncertain frame doesn't move
the score.
A camera-watcher
When the service owns the camera, give a status read instead of detect, and
optionally a listCameras so Cobblr can show a picker rather than a hand-typed
id:
DetectorManifest.parse({
id: "my-watcher",
name: "My Watcher",
shape: "camera-watcher",
auth: { kind: "header", header: "Authorization", from: "apiKey", prefix: "Bearer " },
status: { method: "GET", path: "/cameras/{deviceCam}", probability: "$.risk" },
listCameras: { method: "GET", path: "/cameras", map: { id: "$.id", name: "$.name" } }
})
{deviceCam} is filled with the mapped camera's id. If the service reports risk
as 0 to 100 instead of 0 to 1, add "divisor": 100 to the read.
Optional blocks
The manifest schema carries several optional blocks you add only if the service needs them:
health: a probe for the Test button (otherwise a bare GET of the base URL).serviceVersionandminServiceVersion: read the running version and enforce a minimum, so the Test button reports a too-old box clearly.- Full-mode management (
listCameras,listProviders,createPrinter,createMonitor,listPrinters,connectionMappings): only for a service like PrintGuard that owns its own cameras and printers and lets Cobblr register them. A plain scorer skips all of it.
How it loads
A detector is a folder under the detectors directory with an index.ts that
exports a builtin. The codegen picks it up and wires it into the registry; there
is no central list to edit, and deleting the folder holds it back without breaking
the build. external: true puts it in the catalog as a service an operator points
at a base URL. The repo's detector template walks the full manifest, and the
shipped obico-ml, printguard, and local-http packages are real examples of
each shape. See the contribution guide for shipping it.