Glowforge Utilities (gfutilities)
A collection of utilities to aid in firmware development for the Glowforge laser cutter. Its centerpiece is a machine emulator that authenticates to the Glowforge cloud service, speaks the real-time control protocol, and responds to the service the way a physical Glowforge does — useful for studying the protocol, exercising the cloud workflow, and as a foundation for alternative control software.
⚠️ Disclaimer — please read
This project is not affiliated nor endorsed by Glowforge, Inc.
As Glowforge's own software is in continuous BETA, so will this software. It is recommended not to rely on this code for production as Glowforge does not publish their protocols nor do they provide any change notices.
As a result, this code may break without warning.
USE AT YOUR OWN RISK!
Table of contents
- What it does
- How it works
- Requirements
- Installation
- Configuration
- Running the emulator
- Startup / action sequence
- Project layout
- Library overview
- The pulse (
.puls) file format - Machine settings
- Logging
- Compatibility
- License
What it does
gfutilities implements the machine side of the Glowforge cloud workflow:
- Authenticates a machine to the web service (
/machines/sign_in) using its serial number and password, retrieving the session and WebSocket tokens. - Checks for and downloads firmware advertised by the service.
- Opens the real-time WebSocket control channel to the status service and reacts to the service's action messages.
- Emulates a machine's responses: it reports the full machine settings
schema, uploads camera images, downloads and parses motion ("pulse")
files, and emits the lifecycle events the service expects (
:starting,:capture:*,:upload:*,:completed, …). - Provides helpers for working with the pulse byte-stream format (decoding motion statistics, generating simple linear moves).
The bundled examples/gf-machine-emulator.py
ties these together into a runnable emulator. The Emulator responds to the
service with canned camera images and the downloaded motion files, so a full
homing → motion → print cycle completes without any hardware attached.
How it works
Two channels are used, mirroring the real device:
- HTTPS (
requests) — sign-in, firmware download, image upload, and motion (pulse) file download. - WebSocket (
websocket-client) — a persistent, auto-reconnecting control channel (subprotocolglowforge) carrying JSON action messages from the service and event messages from the machine.
┌──────────────────────────────┐
HTTPS sign_in / │ Glowforge cloud │
update / images / │ app.glowforge.com (HTTPS) │
motion files │ status.glowforge.com (WSS) │
┌──────────▶└─────────────┬────────────────┘
│ │ WSS: action messages ▲ events
│ ▼ │
┌────────┴────────┐ ┌─────────────────┐ ┌────────┴─────────┐
│ authentication │ │ WsClient │──────▶│ GFUIService │
│ websocket(HTTP)│ │ (websocket- │ rx │ dispatch loop │
└─────────────────┘ │ client thread)│◀──────│ │
└─────────────────┘ tx └────────┬─────────┘
│ actions
▼
┌─────────────────────────┐
│ BaseMachine / Emulator │
│ (settings, images, │
│ motion/pulse files) │
└─────────────────────────┘
GFUIService owns the receive/transmit queues and dispatches each incoming
action to the machine object; the machine performs the work (capture, upload,
download, etc.) and pushes status events back onto the transmit queue, which the
WsClient drains to the service.
Requirements
- Python 3.8+
requests≥ 2.31urllib3≥ 2websocket-client≥ 1.7
(See requirements.txt / setup.py.)
Installation
Install the latest release from PyPI:
pip install gfutilities
Or install from source (for development, or to track master):
git clone https://github.com/ScottW514/Glowforge-Utilities.git
cd Glowforge-Utilities
# (recommended) create and activate a virtual environment
python -m venv .venv
# Windows: .venv\Scripts\activate
# POSIX: source .venv/bin/activate
pip install -e . # editable install (or `pip install .`)
Configuration
The emulator is driven by an INI-style configuration file. Copy the sample and edit it:
cd examples
cp gf-machine-emulator.cfg.sample gf-machine-emulator.cfg
Configuration is parsed by gfutilities/configuration.py:
section/option names are upper-cased into flat SECTION.OPTION keys, the literal
strings True/False become booleans, and %(name)s interpolation is
supported within a section.
| Section | Key | Purpose |
|---|---|---|
[SERVICE] |
server_url |
HTTPS API base (default https://app.glowforge.com). |
status_service_url |
WebSocket control URL (wss://status.glowforge.com). |
|
[MACHINE] |
serial, password |
Credentials the machine signs in with (see below). |
hostname, head_id, head_serial, head_firmware |
Optional identity overrides reported in the settings report. | |
[FACTORY_FIRMWARE] |
check |
Whether to query/download advertised firmware. |
download_dir |
Where downloaded firmware is written. | |
fw_version, app_version |
Optional reported-version overrides. | |
[EMULATOR] |
base_dir |
Root for the emulator's resource folders. |
image_src_dir |
Canned camera images (HOME_*.jpg, LID_IMAGE.jpg, …). |
|
motion_dl_dir |
Where downloaded motion/pulse files are written. | |
bypass_homing |
Experimental: start with the homing cycle skipped. | |
material_thickness |
Selects which canned head image to return. | |
[LOGGING] |
file, level, console_level |
Log file path and log levels. |
[THERMAL], [MOTION] |
… | Additional tunables read from the config file. |
Obtaining your machine credentials
serial and password are derived from the i.MX6 OCOTP fuses on a real
Glowforge (the serial from HW_OCOTP_MAC0, the password from HW_OCOTP_SRK0..7).
You must have serial access to your device.
Hostname is simple. It's the identifier shown at the command prompt (use all caps).
To obtain the serial number, enter the following in the python shell on your GF:
def read_file(filename):
with open(filename) as f:
return f.read()
int(read_file('/sys/fsl_otp/HW_OCOTP_MAC0'), 16)
To obtain the password, enter the following in the python shell on your GF:
def read_file(filename):
with open(filename) as f:
return f.read()
password = ''
for x in range(8):
password += "%08x" % int(read_file('/sys/fsl_otp/HW_OCOTP_SRK%d' % d), 16)
print password
DO NOT SHARE your serial or password — they cannot be changed. Keep them secret.
Running the emulator
Run from the examples/ directory so the relative _RESOURCES paths and the
gf-machine-emulator.cfg file resolve correctly:
cd examples
python gf-machine-emulator.py
The entry point parses gf-machine-emulator.cfg, configures logging, then:
from gfutilities.configuration import parse
from gfutilities import GFUIService, Emulator
parse('gf-machine-emulator.cfg')
service = GFUIService(Emulator())
service.connect() # sign in, check firmware, open the WSS channel
service.run() # dispatch service actions until interrupted
Startup / action sequence
Once connected, the service drives the machine through a sequence of actions. The emulator handles each and replies with the appropriate events:
| Action | Emulator behavior |
|---|---|
settings |
Sends the full machine settings report. |
update_check |
Ignored. |
hunt |
Downloads the focus-homing pulse file; emits hunt:starting / hunt:completed. |
lid_image / head_image / lidar_image |
"Captures" a canned JPEG and uploads it to the presigned storage URL supplied in the action's endpoint field; emits :capture:* and :upload:* events. |
motion |
Downloads and parses the motion pulse file; emits motion:starting / motion:completed. |
print |
Downloads the print pulse file, waits for the button, then emits the warmup/running/return-to-home/completed events. |
Project layout
Glowforge-Utilities/
├── gfutilities/
│ ├── __init__.py # exports GFUIService, Emulator, BaseMachine
│ ├── _common.py # LOGGER_NAME, MachineSetting namedtuple
│ ├── configuration.py # INI config parsing, get_cfg / set_cfg
│ ├── service/
│ │ ├── authentication.py # machine sign-in (HTTPS)
│ │ ├── gfuiservice.py # GFUIService: connect + action dispatch loop
│ │ └── websocket.py # WSS client, HTTP helpers, image upload, pulse download
│ ├── device/
│ │ ├── basemachine.py # BaseMachine abstract base + action threads
│ │ ├── emulator.py # Emulator: canned-image / pulse-file machine
│ │ └── settings.py # MACHINE_SETTINGS schema + settings report
│ └── puls/
│ └── pulsedata.py # decode_all_steps, generate_linear_puls
├── examples/
│ ├── gf-machine-emulator.py # runnable emulator entry point
│ ├── gf-machine-emulator.cfg.sample # configuration template
│ └── _RESOURCES/ # IMG/ MOTION/ FW/ LOG/ assets
├── requirements.txt
├── setup.py
├── LICENSE
└── README.md
Library overview
| Component | Responsibility |
|---|---|
GFUIService (service/gfuiservice.py) |
Top-level connector: authenticates, checks firmware, opens the WSS channel, and runs the action-dispatch loop. |
authenticate_machine (service/authentication.py) |
Signs the machine in over HTTPS (with retry/back-off) and stores the auth/WS tokens. |
WsClient + helpers (service/websocket.py) |
websocket-client control channel plus HTTP helpers: firmware_check/firmware_download, img_upload, load_motion, send_wss_event. |
BaseMachine (device/basemachine.py) |
Abstract base implementing the action lifecycle and threading; concrete machines override the _initialize, _head_image, _lid_image, _hunt, _motion, _button_wait, and _shutdown hooks. |
Emulator (device/emulator.py) |
The reference BaseMachine implementation used by the example. |
settings (device/settings.py) |
MACHINE_SETTINGS schema and the send_report settings-report builder. |
puls (puls/pulsedata.py) |
decode_all_steps (motion statistics from a pulse stream) and generate_linear_puls. |
Extending
BaseMachine is the extension point: subclass it (as Emulator does) and
implement the hardware hooks to back the cloud protocol with something other
than canned assets.
The pulse (.puls) file format
Motion, hunt, and print "pulse" files describe a job. Each begins with a small
header — a magic (GF1), a header length, and a series of 4-character
key/value tags (machine-setting overrides for the job) — followed by a raw,
per-tick step/laser byte-stream that clocks the X/Y/Z steppers and the laser.
load_motion()downloads a pulse file, parses the header (buffering across chunks so headers larger than one read are handled), writes the body, and returns header data plus computed motion statistics.decode_all_steps()decodes a pulse byte-stream into per-axis step counts and converts them to millimeters/inches.generate_linear_puls()produces a simple trapezoidal-profile linear move.
Machine settings
MACHINE_SETTINGS in device/settings.py is the
catalog of 4-character setting codes the machine exchanges with the service
(<2-char subsystem><2-char field>, e.g. EFid = exhaust-fan idle duty,
HTvl = head-temperature value). send_report() serializes the reportable
entries into the settings report the service requests at startup. Each entry is
a MachineSetting(type, in_report, min, max, default, …).
Logging
Logging uses the standard logging module under the logger name openglow.
The example configures both a console handler and a file handler; set
[LOGGING] level / console_level to DEBUG, INFO, WARNING, ERROR, or
CRITICAL.
Compatibility
- Runs on current Python (3.8+; developed/tested on 3.14) with modern
requests/urllib32.x /websocket-client. - Exercised against Glowforge production firmware
2.6.0-2228.
Because the protocol is undocumented and changes without notice, compatibility with any given service/firmware version is not guaranteed — see the disclaimer.
License
MIT © 2026 Scott Wiederhold <s.e.wiederhold@gmail.com>
⚠️ Reminder
This project is not affiliated nor endorsed by Glowforge, Inc., Glowforge's protocols are undocumented and change without notice, and this code may break without warning.
USE AT YOUR OWN RISK!
Release files for gfutilities 0.9.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gfutilities-0.9.1.tar.gz | 31.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gfutilities-0.9.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.0 kB
Release files / gfutilities-0.9.1.tar.gz
| Download URL | gfutilities-0.9.1.tar.gz |
|---|---|
| Size | 31.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
39e2d4c5fa943d148604368f9a5826064700b60b586e967588885a5d54238518
|
|
BLAKE2b-256 checksum How to use checksums |
835593e57258c0e41be0764c91565f3b0a10c738fd091a47aef36120d14e16b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.
Transparency logRelease files / gfutilities-0.9.1-py3-none-any.whl
| Download URL | gfutilities-0.9.1-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7c18bf58549e3b41137639ddba0c522a4f49f64452a4d7d32996f231b01113d1
|
|
BLAKE2b-256 checksum How to use checksums |
384e8c461e860a49d8fc51365f44ae42eab87a5e4ac36ca64480dbdb59bc7e4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 17, 2026.
Transparency log