Skip to main content

PyGrbl_Streamer 0.2.0

PyPI

Robust, source-agnostic G-code streamer for GRBL controllers over serial.

Part of the pygrbl family, a set of libraries to manage GRBL. Companion to pygrbl_build and pygrbl_server

v0.0.1 — Complete rewrite. The API is not compatible with previous internal versions and may change before 0.1.0.

Features

  • Stream from any source: lists, generators, files, network — stream() accepts any iterable of commands
  • Constant memory and instant start: files of any size are read lazily in a single pass — no preloading, no counting pass
  • Zero-cost progress: file progress is derived from bytes consumed vs file size, accurate to within a few commands
  • Character-counting streaming protocol with a configurable RX buffer and a conservative 128-byte default
  • Clean connect/disconnect lifecycle — threads are joined, nothing hangs
  • Physical disconnection detection with automatic reconnect support
  • Real-time job control: pause, resume, stop
  • Event callbacks for progress, state changes, alarms, errors, raw I/O, and internal diagnostics
  • Every blocking wait is bounded by a timeout
  • Lightweight: runs multiple machines concurrently on a Raspberry Pi

Installation

pip install pygrbl-streamer

Requires Python 3.10+ and pyserial.

Quick start

from pygrbl_streamer import GrblStreamer

g = GrblStreamer(port='/dev/ttyUSB0')  # 'COM3' on Windows
g.progress_callback = lambda pct, cmd: print(f'{pct}%')

g.connect()
g.send_file('job.gcode')   # any size, constant memory, starts instantly
g.disconnect()

Streaming from any source

stream() consumes commands lazily from any iterable. Your application decides where the G-code comes from:

def square(size=10, power=300, feed=1000):
    yield 'G90 G21'
    yield f'M4 S{power}'
    yield f'G1 X{size} F{feed}'
    yield f'G1 Y{size}'
    yield 'G1 X0'
    yield 'G1 Y0'
    yield 'M5'

g.stream(square(), total=7)

Chain chunks back-to-back without stopping the machine between them:

g.stream(chunk_1, wait_for_idle=False)
g.stream(chunk_2, wait_for_idle=False)
g.stream(final_chunk)   # only the last chunk waits for Idle

Progress reporting

progress_callback(percent, command) fires on acknowledged commands. The percentage source, in order of precedence:

  1. Source-provided — if your iterable exposes a percent() method returning 0–100, it is the authority. send_file() uses this internally (bytes read vs file size).
  2. total — pass the command count to stream() for exact 0–100%.
  3. Heartbeat — with neither, the callback fires every 100 acked commands with percent=-1.

Job control

Streaming calls are blocking. Run them in a thread to control the job from elsewhere:

import threading

threading.Thread(target=g.send_file, args=('job.gcode',)).start()

g.pause()   # immediate feed hold (!)
g.resume()  # cycle start (~)
g.stop()    # abort: feed hold + soft reset

API overview

Method Description
connect() / disconnect() open/close the session; safe to call repeatedly
stream(commands, total=None, ...) stream any iterable of commands
send_file(path, ...) stream a file lazily; same options as stream()
command(cmd) send one command interactively, wait for ok/error
pause() / resume() / stop() real-time job control
unlock() / home() $X / $H
reconnect(retries, delay) retry loop after a physical disconnect

Callbacks

Assign as attributes or override in a subclass. All callbacks run on a dedicated thread and can never block serial communication. If one of your callbacks raises, the exception is reported through log_callback instead of being silently swallowed.

Callback Signature Fires on
progress_callback (percent, command) acknowledged command progress (-1 for unbounded streams)
state_callback (state) state machine transitions
alarm_callback (line) GRBL ALARM:n
error_callback (line) GRBL error:n or internal errors
send_callback / receive_callback (data) raw serial traffic
disconnect_callback (reason) physical disconnection
log_callback (level, message) internal diagnostics ('debug'/'info'/'warning')

Logging integration

The library imposes no logging framework. Wire the callbacks to Python's standard logging in your application:

import logging
log = logging.getLogger('laser1')

g.log_callback = lambda lv, m: getattr(log, lv)(m)
g.error_callback = lambda l: log.warning('GRBL error: %s', l)
g.alarm_callback = lambda l: log.error('ALARM: %s', l)
g.disconnect_callback = lambda r: log.critical('disconnected: %s', r)
g.receive_callback = lambda l: log.debug('<< %s', l)
g.send_callback = lambda d: log.debug('>> %s', d.strip())

States

DISCONNECTED → CONNECTING → IDLE ⇄ STREAMING ⇄ PAUSED, plus ALARM.

An alarm aborts the running job and is never cleared automatically — call unlock() explicitly. After stop(), machine position is untrusted: run home() before the next job.

Compatibility

Works with any GRBL 1.1 (or compatible, e.g. grblHAL) controller: diode laser engravers, CNC routers, pen plotters, drag-knife cutters.

Not supported: Ruida-based CO2 lasers, galvo fiber lasers (EZCad/BJJCZ controllers — entirely different protocol), and Marlin-based machines (no character-counting buffer or real-time commands).

I use this library daily in production, driving several lasers concurrently from a Raspberry Pi 4. Tested so far on:

  • Acmer P1S
  • Acmer P2
  • Longer Ray5 20W
  • AtomStack A24 Pro
  • AtomStack Atelier — a diode galvo running GRBL (unlike the gantry machines above)

The Atelier streams identically to the rest, with one current caveat: the machine ships locked and, for now, has to be connected once through LightBurn or lasergrbl to get unlocked before pygrbl_streamer can connect and drive it normally — the same unlock step lasergrbl performs on its own first connection. Reproducing that unlock handshake directly from the library is a work in progress.

Reports of it working (or not) on other machines are welcome via issues.

Safety notes

  • Laser users: verify $32=1 (laser mode) so the beam is disabled during feed hold.
  • Commands larger than the configured GRBL RX capacity are skipped with an error event instead of deadlocking the stream.
  • This library streams G-code; it does not validate it. Garbage in, garbage out.

License

MIT Controllers with a larger receive buffer can configure it per connection while the standard GRBL-safe default remains 128 bytes:

laser = GrblStreamer("/dev/ttyUSB0", 460800, rx_buffer_size=4096)

Publishing to PyPI

.github/workflows/build.yml builds and tests on pushes to main (excluding documentation-only changes), on published GitHub releases, and on manual runs. Only Actions → Build distributions → Run workflow → main publishes to PyPI, after all required jobs succeed. Pushes and releases only create CI artifacts.

Before publishing a new version, update version in pyproject.toml. The workflow does not increment versions or skip existing PyPI files.

One-time setup: create the pypi-release GitHub environment and add a GitHub Trusted Publisher in the PyPI project's Publishing settings with:

  • Owner: offerrall
  • Repository: PyGrbl_Streamer
  • Workflow filename: build.yml
  • Environment: pypi-release

Authentication uses OIDC (id-token: write); no PyPI API token secret is needed. See PyPI's Trusted Publisher setup.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pygrbl_streamer-0.2.0.tar.gz (18.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pygrbl_streamer-0.2.0-py3-none-any.whl (15.8 kB view details)

Uploaded Python 3

File details

Details for the file pygrbl_streamer-0.2.0.tar.gz.

File metadata

  • Download URL: pygrbl_streamer-0.2.0.tar.gz
  • Upload date:
  • Size: 18.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pygrbl_streamer-0.2.0.tar.gz
Algorithm Hash digest
SHA256 380e396b05a9dde6a3a554c588c270e9317437ae5d69ebe072060f0ab378947b
MD5 12539e5550902eec8f9e7b50781ab1b9
BLAKE2b-256 71a1c622e98bb5e78c0fb09b6ae666f31d9513e19154978e6ab1a76b201b998a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygrbl_streamer-0.2.0.tar.gz:

Publisher: build.yml on offerrall/PyGrbl_Streamer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pygrbl_streamer-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pygrbl_streamer-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 63ae398d00bf187b3362ce511e4b981c0c562e63b4f737448ee4496f40c252d5
MD5 8eff8aab3542a86a99fa487a0299805e
BLAKE2b-256 ec18cad19c9817ee63968b12171cc8d8e3a8df7bd613fe711150a495c8d35e06

See more details on using hashes here.

Provenance

The following attestation bundles were made for pygrbl_streamer-0.2.0-py3-none-any.whl:

Publisher: build.yml on offerrall/PyGrbl_Streamer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page