Write a machine driver
A machine connection sends a file to a machine's manager and tracks the job, or fires a command at an actuator. When the manager speaks plain HTTP (OctoPrint, Duet, Moonraker, PrusaLink), you teach Cobblr to drive it with a driver manifest: JSON describing the calls. The declarative engine interprets one manifest into a working driver, so a new machine manager needs no platform deploy.
Machine connections live inside the Digital Fabrication module, which ships as Experimental; see the status note at the top of Digital fabrication for what that means.
For a manager that isn't reachable over HTTP (MQTT, a binary protocol, a handshake), the manifest can't express it. Put an adapter next to the hardware instead, covered under when a manifest isn't enough.
A fabrication driver
This is the built-in OctoPrint driver, which is a good template for any manager that takes a file and runs it:
{
"id": "octoprint",
"name": "OctoPrint",
"version": "1.0.0",
"auth": { "kind": "header", "header": "X-Api-Key", "from": "apiKey" },
"routing": false,
"test": { "method": "GET", "path": "/api/version" },
"listDevices": {
"method": "GET",
"path": "/api/printer",
"result": "single",
"map": { "id": "='octoprint'", "name": "='OctoPrint'", "state": "$.state.text" }
},
"upload": {
"method": "POST",
"path": "/api/files/local",
"body": "multipart",
"fileField": "file",
"result": { "fileId": "$.files.local.name" }
},
"submit": {
"method": "POST",
"path": "/api/files/local/{fileId}",
"body": { "command": "select", "print": true },
"result": { "jobId": "={fileId}", "queued": "='true'" }
},
"status": {
"method": "GET",
"path": "/api/job",
"result": {
"state": {
"from": "$.state",
"map": {
"Operational": "completed",
"Printing": "printing",
"Paused": "paused",
"Error": "failed",
"Offline": "failed"
}
},
"progress": "$.progress.completion"
}
}
}
The sections map onto what Cobblr does with a machine:
auth: a header carrying a connection credential.frompicks which stored field (apiKey,username, orpassword);prefixis prepended, so a token goes out asAuthorization: Bearer <token>while the user pastes only the token.test: a liveness read for the Test button.listDevices: the printer list for the one-time mapping between a Cobblr machine and the manager's device.result: "single"means the response is one device;"array"reads a list atarrayPath.upload: how the sliced file's bytes are sent."multipart"posts a form part namedfileField;"raw"makes the file bytes the request body, which is what Duet and PrusaLink want (the filename then rides in the path via{filename}).submit: start the uploaded file.{fileId}in the path and body is filled from the upload result.status: poll the job.state.fromreads the manager's status string andstate.maptranslates its vocabulary into Cobblr's job states (queued,printing,paused,completed,failed,cancelled).
The values are the same expression language as everywhere else: "$.a.b" reads a
path, "='lit'" is a literal, "={var}" fills a template var. The template vars
available in paths and submit.body strings are fileId, jobId, deviceId,
tag, and filename.
For a manager that reports status as plain text rather than JSON (a GRBL
<Idle|MPos:...> report, for instance), set status.parse to "text" and make
from and progress regexes whose first capture group is the value.
An actuator driver
A driver can carry commands instead of (or alongside) the fabrication sections.
A command maps a name to one outbound request, filled from the wire's per-entity
args, and returns an ack. No file, no job to poll. This is the Home Assistant
driver, which is commands-only:
{
"id": "home-assistant",
"name": "Home Assistant",
"version": "1.0.0",
"auth": { "kind": "header", "header": "Authorization", "from": "apiKey", "prefix": "Bearer " },
"test": { "method": "GET", "path": "/api/" },
"listDevices": {
"method": "GET",
"path": "/api/",
"result": "single",
"map": { "id": "='home-assistant'", "name": "='Home Assistant'" }
},
"commands": {
"run-zone": {
"method": "POST",
"path": "/api/services/script/turn_on",
"body": {
"entity_id": "script.water_zone",
"variables": { "zone": "{zone}", "seconds": "{seconds}" }
}
}
}
}
The {zone} and {seconds} placeholders in path and body are filled from
the command's params, which a wire supplies from an entity's fields. A wire
reaches a command through the digifab:run-command action.
When a manifest isn't enough
The HTTP manifest covers a REST manager. Two other paths handle the rest:
- A machine on a private network Cobblr can't dial. Point a declarative connection at the machine and run the software bridge, or serve the fixed webhook contract yourself. Both are the edge-adapter shape: Cobblr only ever makes an HTTP call to a URL you control. See edge-bridge drivers and the edge-adapter contract.
- A protocol no manifest can express (MQTT, binary, a login handshake). Write an edge-bridge driver, which is code that runs where the machine is and translates the fixed contract into the real protocol.
For the cheapest possible actuator or sensor (a relay, a valve, a scale), the edge firmware turns an ESP32 into a connector that speaks the same contract with no separate host.
To ship your driver as a built-in that every workspace can pick, register it in the drivers catalog; see the contribution guide.