Docs
/

@leumas/machines

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...

@leumas/machines

Drive a machine that makes something — a 3D printer, a CNC mill or router, a laser engraver.

Studio's Devices domain could already see almost every kind of device a customer owns and could drive none of them. This is the layer underneath the Printers, CNC / PCB and Serial tabs: a G-code lexer, a firmware dialect table, one streamer that covers both flow-control models, a job runner and the safety interlocks. It does no I/O; transports are injected, so the same session runs over a host serial port, over a LeumasNode, and over a mock.

Roadmap: ops/todos/devices-machines-roadmap.md.

The one idea

A printer, a mill and a laser are the same code path plus a small table. Everything that differs between firmwares is declared in a dialect; everything above the dialect is written once.

import { createStreamer } from '@leumas/machines';
import { dialectFor } from '@leumas/machines/dialects';

const streamer = createStreamer({ dialect: dialectFor('grbl'), write, writeBytes });
streamer.enqueue('G1 X10 Y10 F1000');
streamer.feed(dialect.parseLine(lineFromTheWire));   // an ack frees a slot and sends the next line
await streamer.realtime('estop');                    // bypasses the queue entirely

can is derived from what a dialect implements, never declared — so a surface renders a temperature control because can.setTemp is true, and there is no kind === 'fdm' test anywhere above this layer. Delete encode.setTemp from a dialect and the temperature controls disappear.

Layout

src/vocab.js · limits.jsthe frozen vocabularies and every bound, with the measurement behind each
src/gcode/{lex,lines,modal,scan}.jsone line classified; chunks → lines; modal state; the preflight
src/dialect.jsdefineDialect / extendDialect — validated at definition time
src/dialects/marlin · grbl · fluidnc (≈20 lines) · reprap (≈25 lines)
src/streamer.jsthe one hard file — see below
src/interlocks.jstemperature ceiling, jog-while-running, envelope, the laser gate
src/host/Node transports over @leumas/serial (lazy — absent is unavailable, never a crash)
src/mock/virtual Marlin and GRBL with a real 128-byte buffer

The streamer, and why it is one file

Marlin acks line-by-line; GRBL counts characters against a 128-byte receive buffer. Two senders would mean pause, resume, cancel, progress, checkpoints and the watchdog written twice. Instead both reduce to may I send the next line? and the difference is three declared fields.

Seven traps are written into that file, each a bug that has shipped in real G-code senders:

  1. error: credits exactly like ok — GRBL frees the slot for a rejected line too. Credit only

on ok and the stream stalls forever after the first bad line. The most common bug in the genre.

  1. busy: and status reports credit nothing — they renew the watchdog, that is all.
  2. A line larger than the buffer deadlocks; it is refused up front instead.
  3. Resend: is fatal, not a retry — lines are not numbered, so it cannot be honoured honestly.
  4. Blocking commands withhold their ok for minutes (M109 heat soak, G28 homing). One fixed

ack timeout kills every print at its first heat-up, so the timeout is per-line.

  1. Realtime bytes bypass the queue and are never charged. An emergency stop modelled as a queued

command waits behind every buffered move.

  1. Writes are serialised — fire-and-forget interleaves two lines on the wire.

Testing without hardware

src/mock/ ships a virtual Marlin and a virtual GRBL. The GRBL board throws on a buffer overrun rather than silently corrupting, so a sender that miscounts fails a test instead of ruining a workpiece. Its replies are copied from a real capture — including ok T:0.0 /0.0 B:0.0 /0.0 @:0 B@:0 (the temperature rides on the ack) and X:0.00 … Count X: 0 Y:0 (inconsistent spacing), both of which break a parser written against the tidy documentation examples.

pnpm --filter @leumas/machines test

What is deliberately absent

  • Klipper. A Klipper machine's USB port speaks Klipper's own binary MCU protocol, not G-code; it

cannot be driven over serial at all. A klipper dialect would connect, say nothing, and never ack.

  • Line numbering and checksums. See trap 4.
  • A slicer. @leumas/adapter-fabrication-plan emits a toolchain descriptor and executes nothing.

What it exports

151 exports: 34 components, 0 hooks, 117 helpers. Import from @leumas/machines.

ART_MATERIALSART_MODESAXESCAM_PROFILESCLIPPING_INSTALL_HINTDEFAULT_LIMITSDIALECT_IDSDIALECTSDITHER_MODESENCODERSFLOW_CONTROLFRAME_MAX_PERCENTGRBL_RX_BUFFERIMAGE_DECODER_INSTALL_HINTIMAGE_FORMATSJOB_STATESLINK_KINDSMACHINE_KIND_LABELSMACHINE_KINDSMACHINE_STATESMAX_DECODED_PIXELSMAX_ENGRAVE_PIXELSMAX_IMAGE_BYTESMAX_MODEL_BYTESMODEL_FORMATSORIGINSORIGINSPCB_TOOLSPROGRAM_EXTENSIONSREALTIME_ACTIONSSERIAL_INSTALL_HINTSLICE_TIMEOUT_MSSLICERSSTROKE_FACESacceptsMotionartLimitationsartMaterialChoicesartMaterialForbuildRasterIrbuildToolpathbuildVectorIrcamProfileChoicescamProfileForcanFireLasercanJogcanRunUnattendedcapabilitiesOfcheckEnvelopecheckLaserPowercheckLinecheckProgramcheckTemperatureclassifyLineconvexHullcreateJobRunnercreateLineSplittercreateMachineSessioncreateMockMachinecreateModalTrackercreateSerialTransportcreateStreamercreateVirtualGrblcreateVirtualMarlincustomPrintercustomPrinterdecodePngdefineDialectdefinePrinterdefinePrinterdetectSlicerdialectFordialectsForKinddithereffectiveWidthengraveArtextendDialectfetchModelfitsEnvelopeflattenPathfluidncformatOfframeJobgerberLimitationsgrblidentifyDialectidentifyPrinteridentifyPrinterimageLimitationsinflateisAxisisMachineKindisMachineStateisolationRingsisRealtimeTextjobHoldsMachinelistPortslistPrinterslistPrinterslooksLikeProgrammachineFromPrintermachineFromPrintermachineFromProfilemakeOnMachinemakeOnMachinemarlinmatchDrillsmaterialRefusalsmillBoardnormalizeMachineopenMachineopenMachineoutlineRingoverscanForparseGrblLineparseMarlinLineparseTransformpcbLimitationspostEngravepostProcesspreflightpreflightprinterForprinterForprofileChoicesreadCircuitJsonreadCircuitJsonRichreadExcellonreadFingerprintreadFingerprintreadGerberreadImagereadModelreadSvgregisterPrinterregisterPrinterreprapresampleToresolveLimitsringsBoundsrunSlicersanitizeMachinescanProgramsliceModelsniffImagesourceFromLinesstripCommentsstrokeFaceChoicessvgLimitationstextPathstoGrayscaletranspose
Source shared/packages/machines/README.md (no-git)markdownjson
Generated from the Leumas repository. Every page cites the file it came from.leumas.techllms.txt