feat: http bindings matching crosspoint-reader, plus generated Lua stubs

The http table copies crosspoint-reader's signatures exactly -- get/head/delete/post/
patch returning (body|nil, status), download taking maxBytes/expectedSize/sha256, the
same 50000 byte body cap and the same -1 for a request that never left the device --
so a script that talks to a server runs on either firmware. docs/lua-api-parity.md
records that, and every other place the two APIs agree, differ for a reason, or differ
because nobody noticed.

Two crosspoint behaviours are deliberately not copied. It reinterprets a string in
argument 2 of a GET as a request body, which turns a mistyped headers table into a
silent protocol error. More seriously it calls setInsecure() on every request, so TLS
is encrypted but unauthenticated on the very path a firmware update would use; this
verifies against the root bundle already sitting in the framework, and the emulator
confirms expired.badssl.com is refused while a wrong sha256 deletes the file.

Downloading exposed two failures worth naming. A 2KB read buffer on the stack tripped
the loop task's canary because a TLS handshake had already spent it, and the
hand-rolled read loop spun forever on a stream that stopped producing -- HTTPClient's
own writeToStream handles both, so the loop is gone and the loop task gets 16KB.

scripts/gen_lua_stubs.py generates stubs/esp32lcd.lua in the same LuaLS format
crosspoint uses, reading annotations off the luaL_Reg tables so a module's docs sit
with its registration. make test runs --check, which crosspoint's copy never wired up.
This commit is contained in:
2026-08-01 17:33:47 -04:00
parent 2cdb7b3702
commit 6f2d618b8d
14 changed files with 1125 additions and 36 deletions
+5
View File
@@ -17,6 +17,11 @@ static constexpr int TOUCH_CS = 33;
static const char* LAUNCHER = "/apps/launcher/main.lua";
// Arduino's default 8KB loop stack is not enough once a TLS handshake runs inside a
// Lua binding: the first HTTPS download tripped the stack canary. Everything the VM
// calls runs on this task, so the headroom belongs here rather than in each caller.
SET_LOOP_TASK_STACK_SIZE(16 * 1024);
TFT_eSPI tft;
SPIClass touchSpi(HSPI);
SPIClass& sdSpi = SPI;