{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/machines",
  "slug": "packages/machines",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/machines",
  "name": "@leumas/machines",
  "eyebrow": null,
  "chip": null,
  "summary": "Drive a machine that makes something — 3D printers, CNC mills, routers and laser engravers. An isomorphic core (G-code lexer, dialect table, ONE credit-based streamer covering both Marlin's ok-acking...",
  "keywords": [
    "machines",
    "lexer",
    "streamer",
    "128-byte",
    "interlocks",
    "leumas machines",
    "printers",
    "laser"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# @leumas/machines\n\nDrive a machine that makes something — a 3D printer, a CNC mill or router, a laser engraver.\n\nStudio's Devices domain could already *see* almost every kind of device a customer owns and could drive\nnone of them. This is the layer underneath the **Printers**, **CNC / PCB** and **Serial** tabs: a G-code\nlexer, a firmware dialect table, one streamer that covers both flow-control models, a job runner and the\nsafety interlocks. It does no I/O; transports are injected, so the same session runs over a host serial\nport, over a LeumasNode, and over a mock.\n\nRoadmap: [`ops/todos/devices-machines-roadmap.md`](../../../ops/todos/devices-machines-roadmap.md).\n\n## The one idea\n\nA printer, a mill and a laser are the **same code path plus a small table**. Everything that differs\nbetween firmwares is declared in a dialect; everything above the dialect is written once.\n\n```js\nimport { createStreamer } from '@leumas/machines';\nimport { dialectFor } from '@leumas/machines/dialects';\n\nconst streamer = createStreamer({ dialect: dialectFor('grbl'), write, writeBytes });\nstreamer.enqueue('G1 X10 Y10 F1000');\nstreamer.feed(dialect.parseLine(lineFromTheWire));   // an ack frees a slot and sends the next line\nawait streamer.realtime('estop');                    // bypasses the queue entirely\n```\n\n`can` is **derived** from what a dialect implements, never declared — so a surface renders a temperature\ncontrol because `can.setTemp` is true, and there is no `kind === 'fdm'` test anywhere above this layer.\nDelete `encode.setTemp` from a dialect and the temperature controls disappear.\n\n## Layout\n\n| | |\n|---|---|\n| `src/vocab.js` · `limits.js` | the frozen vocabularies and every bound, with the measurement behind each |\n| `src/gcode/{lex,lines,modal,scan}.js` | one line classified; chunks → lines; modal state; the preflight |\n| `src/dialect.js` | `defineDialect` / `extendDialect` — validated at **definition** time |\n| `src/dialects/` | `marlin` · `grbl` · `fluidnc` (≈20 lines) · `reprap` (≈25 lines) |\n| `src/streamer.js` | **the one hard file** — see below |\n| `src/interlocks.js` | temperature ceiling, jog-while-running, envelope, the laser gate |\n| `src/host/` | Node transports over `@leumas/serial` (lazy — absent is `unavailable`, never a crash) |\n| `src/mock/` | virtual Marlin and GRBL with a **real** 128-byte buffer |\n\n## The streamer, and why it is one file\n\nMarlin acks line-by-line; GRBL counts characters against a 128-byte receive buffer. Two senders would\nmean pause, resume, cancel, progress, checkpoints and the watchdog written twice. Instead both reduce\nto *may I send the next line?* and the difference is three declared fields.\n\nSeven traps are written into that file, each a bug that has shipped in real G-code senders:\n\n1. **`error:` credits exactly like `ok`** — GRBL frees the slot for a *rejected* line too. Credit only\n   on `ok` and the stream stalls forever after the first bad line. The most common bug in the genre.\n2. `busy:` and status reports credit **nothing** — they renew the watchdog, that is all.\n3. A line larger than the buffer **deadlocks**; it is refused up front instead.\n4. `Resend:` is **fatal, not a retry** — lines are not numbered, so it cannot be honoured honestly.\n5. Blocking commands withhold their `ok` for **minutes** (`M109` heat soak, `G28` homing). One fixed\n   ack timeout kills every print at its first heat-up, so the timeout is per-line.\n6. **Realtime bytes bypass the queue and are never charged.** An emergency stop modelled as a queued\n   command waits behind every buffered move.\n7. Writes are **serialised** — fire-and-forget interleaves two lines on the wire.\n\n## Testing without hardware\n\n`src/mock/` ships a virtual Marlin and a virtual GRBL. The GRBL board **throws on a buffer overrun**\nrather than silently corrupting, so a sender that miscounts fails a test instead of ruining a workpiece.\nIts replies are copied from a real capture — including `ok T:0.0 /0.0 B:0.0 /0.0 @:0 B@:0` (the\ntemperature rides on the ack) and `X:0.00 … Count X: 0 Y:0` (inconsistent spacing), both of which break\na parser written against the tidy documentation examples.\n\n```sh\npnpm --filter @leumas/machines test\n```\n\n## What is deliberately absent\n\n- **Klipper.** A Klipper machine's USB port speaks Klipper's own binary MCU protocol, not G-code; it\n  cannot be driven over serial at all. A `klipper` dialect would connect, say nothing, and never ack.\n- **Line numbering and checksums.** See trap 4.\n- **A slicer.** `@leumas/adapter-fabrication-plan` emits a toolchain descriptor and executes nothing.\n",
  "source": {
    "path": "shared/packages/machines/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 4753,
    "hash": "08929a74daf3420e5a3eebb5837bda74ccd41224"
  },
  "urls": {
    "html": "/p/packages/machines",
    "json": "/docs/packages/machines.json",
    "md": "/docs/packages/machines.md"
  },
  "links": {
    "composes": [],
    "usedBy": [
      "pkg:@leumas/studio"
    ],
    "product": [
      "pkg:@leumas/studio"
    ],
    "howTo": [],
    "skills": []
  },
  "exports": {
    "total": 151,
    "component": 34,
    "hook": 0,
    "helper": 117,
    "names": [
      {
        "n": "ART_MATERIALS",
        "k": "component"
      },
      {
        "n": "ART_MODES",
        "k": "component"
      },
      {
        "n": "AXES",
        "k": "component"
      },
      {
        "n": "CAM_PROFILES",
        "k": "component"
      },
      {
        "n": "CLIPPING_INSTALL_HINT",
        "k": "component"
      },
      {
        "n": "DEFAULT_LIMITS",
        "k": "component"
      },
      {
        "n": "DIALECT_IDS",
        "k": "component"
      },
      {
        "n": "DIALECTS",
        "k": "component"
      },
      {
        "n": "DITHER_MODES",
        "k": "component"
      },
      {
        "n": "ENCODERS",
        "k": "component"
      },
      {
        "n": "FLOW_CONTROL",
        "k": "component"
      },
      {
        "n": "FRAME_MAX_PERCENT",
        "k": "component"
      },
      {
        "n": "GRBL_RX_BUFFER",
        "k": "component"
      },
      {
        "n": "IMAGE_DECODER_INSTALL_HINT",
        "k": "component"
      },
      {
        "n": "IMAGE_FORMATS",
        "k": "component"
      },
      {
        "n": "JOB_STATES",
        "k": "component"
      },
      {
        "n": "LINK_KINDS",
        "k": "component"
      },
      {
        "n": "MACHINE_KIND_LABELS",
        "k": "component"
      },
      {
        "n": "MACHINE_KINDS",
        "k": "component"
      },
      {
        "n": "MACHINE_STATES",
        "k": "component"
      },
      {
        "n": "MAX_DECODED_PIXELS",
        "k": "component"
      },
      {
        "n": "MAX_ENGRAVE_PIXELS",
        "k": "component"
      },
      {
        "n": "MAX_IMAGE_BYTES",
        "k": "component"
      },
      {
        "n": "MAX_MODEL_BYTES",
        "k": "component"
      },
      {
        "n": "MODEL_FORMATS",
        "k": "component"
      },
      {
        "n": "ORIGINS",
        "k": "component"
      },
      {
        "n": "ORIGINS",
        "k": "component"
      },
      {
        "n": "PCB_TOOLS",
        "k": "component"
      },
      {
        "n": "PROGRAM_EXTENSIONS",
        "k": "component"
      },
      {
        "n": "REALTIME_ACTIONS",
        "k": "component"
      },
      {
        "n": "SERIAL_INSTALL_HINT",
        "k": "component"
      },
      {
        "n": "SLICE_TIMEOUT_MS",
        "k": "component"
      },
      {
        "n": "SLICERS",
        "k": "component"
      },
      {
        "n": "STROKE_FACES",
        "k": "component"
      },
      {
        "n": "acceptsMotion",
        "k": "helper"
      },
      {
        "n": "artLimitations",
        "k": "helper"
      },
      {
        "n": "artMaterialChoices",
        "k": "helper"
      },
      {
        "n": "artMaterialFor",
        "k": "helper"
      },
      {
        "n": "buildRasterIr",
        "k": "helper"
      },
      {
        "n": "buildToolpath",
        "k": "helper"
      },
      {
        "n": "buildVectorIr",
        "k": "helper"
      },
      {
        "n": "camProfileChoices",
        "k": "helper"
      },
      {
        "n": "camProfileFor",
        "k": "helper"
      },
      {
        "n": "canFireLaser",
        "k": "helper"
      },
      {
        "n": "canJog",
        "k": "helper"
      },
      {
        "n": "canRunUnattended",
        "k": "helper"
      },
      {
        "n": "capabilitiesOf",
        "k": "helper"
      },
      {
        "n": "checkEnvelope",
        "k": "helper"
      },
      {
        "n": "checkLaserPower",
        "k": "helper"
      },
      {
        "n": "checkLine",
        "k": "helper"
      },
      {
        "n": "checkProgram",
        "k": "helper"
      },
      {
        "n": "checkTemperature",
        "k": "helper"
      },
      {
        "n": "classifyLine",
        "k": "helper"
      },
      {
        "n": "convexHull",
        "k": "helper"
      },
      {
        "n": "createJobRunner",
        "k": "helper"
      },
      {
        "n": "createLineSplitter",
        "k": "helper"
      },
      {
        "n": "createMachineSession",
        "k": "helper"
      },
      {
        "n": "createMockMachine",
        "k": "helper"
      },
      {
        "n": "createModalTracker",
        "k": "helper"
      },
      {
        "n": "createSerialTransport",
        "k": "helper"
      },
      {
        "n": "createStreamer",
        "k": "helper"
      },
      {
        "n": "createVirtualGrbl",
        "k": "helper"
      },
      {
        "n": "createVirtualMarlin",
        "k": "helper"
      },
      {
        "n": "customPrinter",
        "k": "helper"
      },
      {
        "n": "customPrinter",
        "k": "helper"
      },
      {
        "n": "decodePng",
        "k": "helper"
      },
      {
        "n": "defineDialect",
        "k": "helper"
      },
      {
        "n": "definePrinter",
        "k": "helper"
      },
      {
        "n": "definePrinter",
        "k": "helper"
      },
      {
        "n": "detectSlicer",
        "k": "helper"
      },
      {
        "n": "dialectFor",
        "k": "helper"
      },
      {
        "n": "dialectsForKind",
        "k": "helper"
      },
      {
        "n": "dither",
        "k": "helper"
      },
      {
        "n": "effectiveWidth",
        "k": "helper"
      },
      {
        "n": "engraveArt",
        "k": "helper"
      },
      {
        "n": "extendDialect",
        "k": "helper"
      },
      {
        "n": "fetchModel",
        "k": "helper"
      },
      {
        "n": "fitsEnvelope",
        "k": "helper"
      },
      {
        "n": "flattenPath",
        "k": "helper"
      },
      {
        "n": "fluidnc",
        "k": "helper"
      },
      {
        "n": "formatOf",
        "k": "helper"
      },
      {
        "n": "frameJob",
        "k": "helper"
      },
      {
        "n": "gerberLimitations",
        "k": "helper"
      },
      {
        "n": "grbl",
        "k": "helper"
      },
      {
        "n": "identifyDialect",
        "k": "helper"
      },
      {
        "n": "identifyPrinter",
        "k": "helper"
      },
      {
        "n": "identifyPrinter",
        "k": "helper"
      },
      {
        "n": "imageLimitations",
        "k": "helper"
      },
      {
        "n": "inflate",
        "k": "helper"
      },
      {
        "n": "isAxis",
        "k": "helper"
      },
      {
        "n": "isMachineKind",
        "k": "helper"
      },
      {
        "n": "isMachineState",
        "k": "helper"
      },
      {
        "n": "isolationRings",
        "k": "helper"
      },
      {
        "n": "isRealtimeText",
        "k": "helper"
      },
      {
        "n": "jobHoldsMachine",
        "k": "helper"
      },
      {
        "n": "listPorts",
        "k": "helper"
      },
      {
        "n": "listPrinters",
        "k": "helper"
      },
      {
        "n": "listPrinters",
        "k": "helper"
      },
      {
        "n": "looksLikeProgram",
        "k": "helper"
      },
      {
        "n": "machineFromPrinter",
        "k": "helper"
      },
      {
        "n": "machineFromPrinter",
        "k": "helper"
      },
      {
        "n": "machineFromProfile",
        "k": "helper"
      },
      {
        "n": "makeOnMachine",
        "k": "helper"
      },
      {
        "n": "makeOnMachine",
        "k": "helper"
      },
      {
        "n": "marlin",
        "k": "helper"
      },
      {
        "n": "matchDrills",
        "k": "helper"
      },
      {
        "n": "materialRefusals",
        "k": "helper"
      },
      {
        "n": "millBoard",
        "k": "helper"
      },
      {
        "n": "normalizeMachine",
        "k": "helper"
      },
      {
        "n": "openMachine",
        "k": "helper"
      },
      {
        "n": "openMachine",
        "k": "helper"
      },
      {
        "n": "outlineRing",
        "k": "helper"
      },
      {
        "n": "overscanFor",
        "k": "helper"
      },
      {
        "n": "parseGrblLine",
        "k": "helper"
      },
      {
        "n": "parseMarlinLine",
        "k": "helper"
      },
      {
        "n": "parseTransform",
        "k": "helper"
      },
      {
        "n": "pcbLimitations",
        "k": "helper"
      },
      {
        "n": "postEngrave",
        "k": "helper"
      },
      {
        "n": "postProcess",
        "k": "helper"
      },
      {
        "n": "preflight",
        "k": "helper"
      },
      {
        "n": "preflight",
        "k": "helper"
      },
      {
        "n": "printerFor",
        "k": "helper"
      },
      {
        "n": "printerFor",
        "k": "helper"
      },
      {
        "n": "profileChoices",
        "k": "helper"
      },
      {
        "n": "readCircuitJson",
        "k": "helper"
      },
      {
        "n": "readCircuitJsonRich",
        "k": "helper"
      },
      {
        "n": "readExcellon",
        "k": "helper"
      },
      {
        "n": "readFingerprint",
        "k": "helper"
      },
      {
        "n": "readFingerprint",
        "k": "helper"
      },
      {
        "n": "readGerber",
        "k": "helper"
      },
      {
        "n": "readImage",
        "k": "helper"
      },
      {
        "n": "readModel",
        "k": "helper"
      },
      {
        "n": "readSvg",
        "k": "helper"
      },
      {
        "n": "registerPrinter",
        "k": "helper"
      },
      {
        "n": "registerPrinter",
        "k": "helper"
      },
      {
        "n": "reprap",
        "k": "helper"
      },
      {
        "n": "resampleTo",
        "k": "helper"
      },
      {
        "n": "resolveLimits",
        "k": "helper"
      },
      {
        "n": "ringsBounds",
        "k": "helper"
      },
      {
        "n": "runSlicer",
        "k": "helper"
      },
      {
        "n": "sanitizeMachine",
        "k": "helper"
      },
      {
        "n": "scanProgram",
        "k": "helper"
      },
      {
        "n": "sliceModel",
        "k": "helper"
      },
      {
        "n": "sniffImage",
        "k": "helper"
      },
      {
        "n": "sourceFromLines",
        "k": "helper"
      },
      {
        "n": "stripComments",
        "k": "helper"
      },
      {
        "n": "strokeFaceChoices",
        "k": "helper"
      },
      {
        "n": "svgLimitations",
        "k": "helper"
      },
      {
        "n": "textPaths",
        "k": "helper"
      },
      {
        "n": "toGrayscale",
        "k": "helper"
      },
      {
        "n": "transpose",
        "k": "helper"
      }
    ]
  }
}
