Dev Mode
Byonk includes a development mode that provides a web-based device simulator with live reload capabilities, making it easier to develop and test screens.
Starting Dev Mode
# Start with dev mode enabled
byonk dev
# With external screens directory for live reload
SCREENS_DIR=./screens byonk dev
Once started, open your browser to http://localhost:3000/dev to access the device simulator.

Features
Device Simulator
The simulator displays your rendered screens in a visual frame resembling a TRMNL device. You can:
- Select a screen from the dropdown (populated from config.yaml and auto-discovered screens)
- Select a device to auto-load its configured screen, parameters, panel, and dither settings
- Simulate device context: battery voltage, WiFi RSSI, and time override
- View the rendered PNG exactly as it would appear on the device
- Pixel inspector: hover over the image to see a magnified view
Live Reload
When SCREENS_DIR is set to an external directory, the dev mode watches for changes to .lua and .svg files. When you save a file:
- The file watcher detects the change
- An event is sent to connected browsers via Server-Sent Events (SSE)
- The screen automatically re-renders with the latest code
Custom Parameters
The dev UI includes a JSON editor for passing custom parameters to your Lua scripts. These are available in your script via the params table.
Error Display
Errors are displayed in a console below the device preview, including Lua syntax/runtime errors, template errors, and render failures.
Display Calibration
Dev mode provides tools for calibrating dithering to match your physical display. Changes made in the dev UI are synced live to the actual device — what you tune is what the device shows.
Dither Algorithm Selection
The dither dropdown lets you try all 9 algorithms on your content:
atkinson(default) — Atkinson error diffusion (75% propagation)atkinson-hybrid— Atkinson with hybrid propagation (100% achromatic / 75% chromatic)floyd-steinberg— Floyd-Steinberg with blue noise jitterjarvis-judice-ninke— wide 12-neighbor kernelsierra,sierra-two-row,sierra-lite— Sierra familystucki— wide 12-neighbor kernel similar to JJNburkes— 7-neighbor kernel, good balance of speed and quality
Dither Tuning Controls
The Render Options panel exposes three tuning parameters:
| Control | Effect |
|---|---|
| Error clamp | Caps how much accumulated error one pixel may carry into its neighbours. The default is 1.0 — full scale in a channel. Lowering it suppresses diffusion, which quietens oscillation but makes saturated areas render flat. |
| Noise scale | Controls blue noise jitter strength. Higher values break “worm” artifacts more aggressively. |
| Chroma clamp | Limits chromatic error propagation. Prevents color bleeding on chromatic palettes. |
Color Calibration
Click any actual-color swatch to open the HSL adjustment popup. Adjust hue, saturation, and lightness with live preview to match what your panel really displays. The adjusted colors_actual string can be copied to config.yaml.
Live Device Sync
When you select a device entry and adjust dither algorithm, tuning parameters, or measured colors, changes are synced to the production /api/display handler. The physical device picks up the new settings on its next refresh.
Calibrator Screen
Byonk ships a built-in calibration screen (byonk-builtin/calibration/color) designed specifically for display calibration. Assign it to your device temporarily while tuning:
devices:
"ABCDE-FGHJK":
screen: byonk-builtin/calibration/color
panel: my_panel
dither: atkinson
The calibrator shows everything you need to evaluate dithering quality:
- White-to-color gradients for each palette color — reveals error diffusion artifacts, oscillation, and color bleeding
- Full hue sweep at 100% saturation — shows how the ditherer maps arbitrary colors to your limited palette
- Test photo — real-world image to judge overall photo reproduction
- Solid color patches with hex labels — compare what the panel actually displays against the expected color values
Use the calibrator on your physical device while adjusting tuning in dev mode — the live sync means every change you make is immediately visible on the display.
Gamut Patch Screen
byonk-builtin/calibration/gamut answers a narrower question: which colors can
this panel actually mix, and which does it give up on?
devices:
"ABCDE-FGHJK":
screen: byonk-builtin/calibration/gamut
panel: my_panel
params:
hues: 24 # hue columns around the full circle (2-48)
levels: 6 # lightness rows (1-12)
It draws the hue circle as isolated flat patches rather than the calibrator’s smooth gradient. That difference is the point: in a gradient, neighbouring hues bleed together, so a hue the panel cannot reproduce still looks like it is doing something. Here each patch stands alone, so you can read it directly:
- A speckled patch — the ditherer mixed several palette colors to approximate the request. This is what working output looks like.
- A solid patch — the ditherer picked one palette entry for every pixel. On a 6-color panel expect solid blue across roughly 225°–270°: that genuinely is the best the palette offers, not a bug.
- A solid white patch (drawn outlined, so it doesn’t read as a missing cell) — the request collapsed to white entirely. Cyan around 180° does this on 6-color panels, whose bluest and greenest inks are both dark.
Rows vary lightness because reachability depends on it — a hue may mix cleanly when dark and collapse when light.
Tone Marker A/B Screen
byonk-builtin/calibration/tone answers a different question again: what does
the gamut mapper actually change on real content, on your real panel?
devices:
"ABCDE-FGHJK":
screen: byonk-builtin/calibration/tone
panel: my_panel
params:
hues: 12 # patch grid hue columns (2-48)
levels: 5 # patch grid rows (1-12)
It renders the same content — a photograph, a hue sweep, and a colour patch
grid, top to bottom — twice, side by side. The two columns are identical
markup with one difference: only the right-hand column is marked
data-byonk-tone="continuous". That mark drives three things at once — the
right column is matched against the panel’s measured colours and gamut
mapped, while the left column is matched against the official palette and
exact-match pinned (see
Marking continuous-tone content).
The left column is the untouched control. Whatever visibly differs between the
two columns on your device is what that whole difference in treatment is doing
to your content. The hue sweep is a fixed gradient of 12 equal hue steps, not
driven by either param.
There are no gamut-mapping knobs on this screen — it deliberately shows you what a real screen gets, not a tuning surface.
Calibration Workflow
- Assign the
byonk-builtin/calibration/colorscreen to your device inconfig.yaml - Select your device in dev mode — this loads its screen, panel, and dither settings
- Choose a dither algorithm that works well for your content type
- Adjust tuning parameters (max_error, noise_scale, chroma_clamp, strength) until the preview looks good
- Calibrate measured colors by clicking actual-color swatches and adjusting HSL to match the solid patches on the physical display
- Verify on device — changes sync automatically; wait for the next device refresh
- Commit to config — copy the values to
config.yamland switch back to your normal screen:
panels:
my_panel:
name: "My Panel"
colors: "#000000,#FFFFFF,#FF0000,#FFFF00"
colors_actual: "#303030,#D0D0C8,#C04040,#D0D020" # from dev mode calibration
devices:
"ABCDE-FGHJK":
screen: examples/gphoto
panel: my_panel
dither: floyd-steinberg
max_error: 1.0 # from dev mode tuning
noise_scale: 0.5 # from dev mode tuning
Tuning values can also be set per-script in the Lua return table — see Lua API.
Configuration
Dev mode uses the same environment variables as the normal server:
| Variable | Description | Default |
|---|---|---|
BIND_ADDR | Server bind address | 0.0.0.0:3000 |
SCREENS_DIR | External screens directory (enables live reload) | (embedded) |
FONTS_DIR | External fonts directory | (embedded) |
CONFIG_FILE | External config file | (embedded) |
Example Workflow
-
Extract embedded assets to work with:
byonk init --all -
Start dev mode with external screens:
SCREENS_DIR=./screens CONFIG_FILE=./config.yaml byonk dev -
Open
http://localhost:3000/devin your browser -
Select the screen you want to work on
-
Edit your Lua script or SVG template — changes appear automatically
-
Use the calibration tools to tune dithering for your panel
-
Check the console below the preview if something goes wrong
Differences from Production
Dev mode includes a few differences from the production byonk serve command:
- Additional
/dev/*routes for the simulator UI - File watching enabled (when using external SCREENS_DIR)
- No content caching — always renders fresh content
- More verbose logging by default
- Tuning and color overrides are session-only (reset on server restart)