# Lua API parity with crosspoint-reader Both firmwares expose a Lua API to apps on an SD card, and both are ESP32 devices, so a script that only touches files and the network should behave the same on either. This records where they agree, where they differ **for a reason**, and where they differ because nobody noticed. The last group is a bug list, not a design. Compared against crosspoint-reader at `src/util/lua/LuaBindings*.cpp`. ## Identical `fs.listDirs`, `fs.listFiles`, `fs.exists`, `fs.readFile`, `fs.writeFile`, `gui.getWidth`, `gui.getHeight`, `gui.fillRect`, `gui.drawRect`, `gui.drawLine`, `sys.getMillis`, `sys.delay`, `log.debug/info/error`, the whole `http` table, `sys.setTickInterval`, `wifi.getStatus`, `wifi.isConnected`, `wifi.getLocalIP`, and the `draw()` / `on_tick()` callbacks. `init()` is required in both, so a misspelled entry point is an error rather than an app that quietly draws nothing. `wifi.getStatus()` returns `{state, ssid, ip, rssi}` in both. crosspoint returned a bare string until this was reconciled; a string had nowhere to put the address and signal strength a status screen wants. Its `state` vocabulary is a subset: crosspoint reports `disconnected`, `connecting`, `connected` and `failed`, while this firmware adds `not_found`, because it joins one named network rather than walking a credential list. ## Deliberate differences | Area | crosspoint-reader | slate32 | Why | |---|---|---|---| | App lifecycle | `sys.exit()`, `init()` | `sys.back()`, `sys.launch/replace(path, arg)`, `init(arg)` | slate32 apps form a reloadable route stack; only the path and optional string cross between Lua states. | | Input | `input.wasPressed(button)` and friends, 8 named buttons | `input.getTouch`, `getRawTouch`, `touched` | Different hardware. A touch panel has no button names and a button device has no coordinates. | | Drawing | `gui.drawText(font, x, y, text, color, style)`, `getTextWidth(font, text)` | `gui.drawText(text, x, y, color, bg)`, `gui.getTextWidth(text)` | crosspoint ships several fonts; this firmware has one built-in font scaled by `gui.setTextSize(n)`, and needs an opaque background colour because the panel is not e-ink. | | Refresh | `gui.refresh(mode)`, `REFRESH_FULL/HALF/FAST` | none | An LCD has no waveform modes. | | Colour | `COLOR_*` constants, 4 grey levels | `gui.color(r, g, b)` returning RGB565 | 16-bit colour has too many values to enumerate. | | Shapes | `drawRoundedRect` + `fillRoundedRect` | one `gui.roundRect(...)` with gradient and border | Fill and border derive from a single distance field, so their edges cannot disagree. | | Themes | none | `settings.getTheme/setTheme`, `/lib/theme.lua` | Colour panel. | | Rotation | `gui.setOrientation("portrait")` | `settings.setRotation(degrees)` persisted, `gui.setRotation(degrees)` for one frame | This device stores rotation in settings and remaps touch to match. | | Clock | nothing exposed; UTC offset is a C++ setting | `sys.isClockSynced`, `settings.getTimezone/setTimezone` with POSIX TZ rules | Timezone here is a stored rule, so `os.date()` returns local time with DST handled by libc. | | Launching | launcher is C++ | `sys.launch(path)`, home is a Lua app | The launcher is just another app here, named `home`. | | Modules | single-file apps; `require` unusable | `require` works, `package.searchers` reads the SD card, `/lib` on the path | Shared code such as `ui.lua` needs it. **crosspoint should adopt this.** | | BLE | `ble.*` | none | No BLE use case here yet. | | TLS memory | `TlsScratchLoan` lends the framebuffer to wolfSSL | none | crosspoint is heap-starved; this device has ~280KB free. | ## Accidental differences — drift, not design Each of these is the same concept spelled two ways. Fixing them means changing one repo. | Concern | crosspoint-reader | slate32 | Suggested resolution | |---|---|---|---| | `wifi.connect()` | no arguments, uses stored credentials | `(ssid, password)`, saves them | Both are wanted: a no-argument reconnect and an explicit join. | | `fs.readFile` cap | 50000 bytes | 65536 bytes | Arbitrary in both. | | `fs` mutation | `mkdir`, `rename`, `remove`, `removeTree`, plus a path-safety check rejecting `..` | absent | **This repo is missing them, including the traversal guard.** | | `fs.fileSize`, `fs.readLineAt` | present | absent | Worth porting; `readLineAt` exists for paging large files. | | Timers | `timer.after/every/cancel` + `on_timer(id)` | absent | Worth porting. | ## The `http` table Signatures match crosspoint exactly, so scripts port unchanged: ```lua http.get(url, headers?) -> body|nil, status http.head(url, headers?) -> body|nil, status http.delete(url, headers?) -> body|nil, status http.post(url, body?, headers?) -> body|nil, status http.patch(url, body?, headers?) -> body|nil, status http.download(url, dest, options) -> bytesWritten | nil, error http.urlencode(input) -> string ``` `status` is `-1` when the request never left the device. Bodies are capped at 50000 bytes, matching crosspoint, and a larger response yields `nil` with the real status. `http.download` requires HTTPS, requires `maxBytes`, accepts `expectedSize` and `sha256`, rejects unknown option keys, and deletes the file if any check fails. Two behaviours are **deliberately not** copied: 1. **No argument shifting.** crosspoint (`LuaBindingsNet.cpp:209`) treats a string in argument 2 of `get`/`head`/`delete` as a request *body*, so a mistyped headers table becomes a silent protocol error. Here that raises. 2. **Certificates are verified.** crosspoint calls `setInsecure()` for every Lua request and for `http.download`, so traffic is encrypted but unauthenticated — including the path a firmware update would use. This firmware verifies against the root bundle already embedded in the framework (`_binary_x509_crt_bundle_start`, ~62KB, linked only when referenced). Verified in the emulator: `https://expired.badssl.com` returns status `-1`, and a matching `sha256` accepts while a wrong one deletes the file. The cost of matching the signatures is that there is nowhere to put a per-request CA or an insecure escape hatch, so TLS policy is device-wide and scripts cannot weaken it. That is the right trade for a device that flashes itself. ## Stub generation `scripts/gen_lua_stubs.py` emits `stubs/slate32.lua` in the same LuaLS `---@meta` format crosspoint uses for `data/lua/crosspoint.lua`, so one editor setup covers both. The parser differs: crosspoint annotates each C function and reads `addFunction(...)` calls, while this repo annotates the `luaL_Reg` table so a module's documentation stays contiguous with its registration. `make test` runs `--check`. crosspoint's generator has no such wiring, so its stub can drift from its bindings silently; that is worth copying back.