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, and 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, and 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, so 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, so 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.