A line from the environment
A 3D printer on the lineStep 1 of 7The route asks for a print
A P1S arrives at site 1. Its screen shows an IP address, a serial and an access code.
The printers in .env become a line: one station per enabled printer, its serial as its address, its access code kept out of the repository.
The situation
A printer’s serial, address and access code are what it takes to reach it, and the access code is a password. They belong in one file nobody commits, and the line should follow from them: add a printer, enable it, and it is on the line. Built by hand, that is a config service and a deploy for every printer. Here .env describes the printers and the site setup writes the line.
The setup
One block per station, named in BAMBU_PRINTERS:
# The site and line the printers formBAMBU_SITE_ID=site-1BAMBU_LINE_ID=print-1# One block per station in BAMBU_PRINTERS: the station upper-cased, - as _BAMBU_PRINTERS=p1s-01BAMBU_P1S_01_SERIAL=<printer serial>BAMBU_P1S_01_HOST=<printer LAN address>BAMBU_P1S_01_CODE=<printer access code>BAMBU_P1S_01_ENABLED=true
# A Long Tail login the print driver signs in withLT_USERNAME=superadminLT_PASSWORD=<the password you set>env.ts turns each enabled printer into a print station:
function printerDef(p: BambuPrinter): MachineDef { return { machineId: p.station, address: p.serial, process: PRINT_PROCESS, description: `Bambu printer ${p.station}`, carrier: 'none', cycleSec: 3600, changeoverSec: 0, rejectReason: 'PRINT-FAILED', recipes: { [PRINT_RECIPE.id]: [PRINT_RECIPE.version] }, materials: [], measurements: [], faults: {}, queueDepth: 1, requireArtifact: true, };}
/** The line of enabled printers: one print station each, a person unloads, a person services. */export function bambuLine(printers: BambuPrinter[], env: Env = process.env): LineDef { const enabled = printers.filter((p) => p.enabled); if (!enabled.length) throw new Error('no printer is enabled; set BAMBU_<STATION>_ENABLED=true for one'); return { siteId: required(env, 'BAMBU_SITE_ID'), lineId: required(env, 'BAMBU_LINE_ID'), roles: { exit: BAMBU_ROLES.UNLOAD, operator: BAMBU_ROLES.SERVICING }, machines: enabled.map(printerDef), };}machine-site writes the result next to the site’s certificates, and Long Tail loads it with MACHINE_LINE_CONFIG or Machines.loadLine:
{ "siteId": "site-1", "lineId": "print-1", "roles": { "exit": "print-unload", "operator": "print-servicing" }, "machines": [ { "machineId": "p1s-01", "address": "01P00A000000001", "process": "print", "description": "Bambu printer p1s-01", "carrier": "none", "cycleSec": 3600, "changeoverSec": 0, "rejectReason": "PRINT-FAILED", "recipes": { "print-file": ["1"] }, "materials": [], "measurements": [], "faults": {}, "queueDepth": 1, "requireArtifact": true } ]}On the floor
A second printer is one more station in BAMBU_PRINTERS and one more block. Setting ENABLED=false takes a printer off the line without deleting its block, for a nozzle swap or a repair. .env is gitignored, so the access codes stay on the machine that runs the site.
The guarantee
The line holds only enabled printers, each addressed by its serial. queueDepth: 1 gives each printer one print at a time, and requireArtifact: true refuses a print escalation that names no file. See Machines.
Related patterns
The physical world
A machine is a resolver
Step 1 of 12 · A factory line over MQTT
A workflow parks an escalation and waits; a machine resolves it over MQTT exactly as a person would from the dashboard.
The physical world
One leader per line
Step 3 of 12 · A factory line over MQTT
Every replica runs the same config; one holds a Postgres advisory lock per line and runs its gateway, and the rest take over on loss.
The physical world
Learned addresses, approved by a person
Step 4 of 12 · A factory line over MQTT
A station learns its machine from first contact; any later disagreement becomes a registration escalation a person approves or rejects.
