3.0 KiB
slate32 Agent Guidelines
ESP32 firmware (C++/Arduino) hosting Lua apps off an SD card. C++ owns the panel, touch,
network and persistence; everything user-visible is Lua under sdcard/.
Build and Test
nix develop -c make test # C++ unit tests, Lua tests, and the stub check
nix develop -c pio run # firmware -> .pio/build/esp32-32e/firmware.bin
nix develop -c make stubs # regenerate stubs/slate32.lua after any binding change
make test fails if stubs/slate32.lua drifts from the bindings. Regenerate rather than
hand-editing it.
Binding Conventions
Accessors are getName / setName / isName. Bare names are actions (gui.fillRect,
wifi.scan) or pure conversions (gui.color, http.urlencode). A missing getter is fine;
an accessor without a prefix is not.
Namespaces have one job each: gui device primitives and the live frame, ui widgets and
palette, settings persisted preferences, sys process and runtime, plus wifi/http/
fs/input/log.
A setter that requires a follow-up call is a bug in the setter. settings.setTimezone()
applies the TZ itself; settings.setRotation() applies the frame and re-clips. The one
unavoidable exception is the theme, because C cannot reload the Lua palette — ui.setTheme()
is the seam that pairs them, and apps call that, never settings.setTheme().
Persisted intent is not live state. settings.getRotation() is what the user saved;
gui.getRotation() is the frame being drawn. They diverge on purpose while an app rotates
the panel transiently (touch calibration does). Never resolve one from the other.
Settings live in C++ (src/settings.h) because the firmware reads rotation and calibration
before any lua_State exists, and calibration again on every touch. Lua reaches them through
bindings so there is one writer.
Status Bar
sdcard/lib/statusbar.lua paints the top strip; the firmware only clips apps out of it and
calls draw(home) on a timer. Invalidation is entirely Lua's: draw() compares each
field against what it last painted and keys the whole cache on gui.getRotation() and
ui.themeName. A new app gets a fresh lua_State, so an empty cache already means "repaint
everything".
The one change Lua cannot observe is an app that painted over the bar while fullscreen —
apps toggle through statusbar.setFullscreen(), which drops the cache. Do not add
invalidation flags on the C++ side; the facts that drive the bar are only knowable in Lua.
Emulator
Full instructions in .pi/skills/test-e32r40t-firmware/SKILL.md. Two things that cost time:
- The launcher logs
[lua] home ready, not "launcher ready". It is/apps/home, an app like any other. wait-frameis an e-ink command and hangs on this board. Usewait-idle SECONDSbetween captures, andwait-logfor everything semantic.
Pure Lua logic belongs in test/*.lua against test/fake_device.lua, which is the single
definition of the binding surface for host tests. Renaming a binding means editing that file.
Reserve the emulator for the panel, touch and SD.