Skip to main content
Version: 2026.8.1

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 probability path expression and a reduce ("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).
  • serviceVersion and minServiceVersion: 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.