Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

37 Commits

Repository files navigation

PyGrbl_Streamer 0.0.1

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 against GRBL's 128-byte RX buffer
  • 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

frompygrbl_streamerimportGrblStreamerg=GrblStreamer(port='/dev/ttyUSB0') # 'COM3' on Windowsg.progress_callback=lambdapct, cmd: print(f'{pct}%')
g.connect()
g.send_file('job.gcode') # any size, constant memory, starts instantlyg.disconnect()

Streaming from any source

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

defsquare(size=10, power=300, feed=1000):
yield'G90 G21'yieldf'M4 S{power}'yieldf'G1 X{size} F{feed}'yieldf'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:

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

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

CallbackSignatureFires 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:

importlogginglog=logging.getLogger('laser1')
g.log_callback=lambdalv, m: getattr(log, lv)(m)
g.error_callback=lambdal: log.warning('GRBL error: %s', l)
g.alarm_callback=lambdal: log.error('ALARM: %s', l)
g.disconnect_callback=lambdar: log.critical('disconnected: %s', r)
g.receive_callback=lambdal: log.debug('<< %s', l)
g.send_callback=lambdad: 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 longer than GRBL's RX buffer (127 chars) 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

About

A simple and minimalist library for controlling CNC machines with GRBL firmware from Python.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages