Add persistent script storage + hot-reload, verified live
lua_runtime now loads the running script from a new LittleFS-backed drivers::storage (joltwallet/littlefs, the project's fifth external managed component -- mounts the "storage" partition already reserved in partitions.csv), falling back to the compiled-in default script only on first boot. transport exposes GET/POST /api/script -- raw Lua text, not JSON, since json_helpers.h can't round-trip scripts safely -- relayed to lua_runtime via the same callback-registration pattern already used for LedColorHandler, keeping transport free of a dependency on lua_runtime. lua_runtime_reload() persists and hot-swaps to a fresh Lua state immediately, no reboot. Verified on real ESP32-C6 hardware: script loads via kvida-sdk, an edited script hot-reloads within one tick, and the edit survives a power cycle (proving LittleFS persistence, not just an in-RAM change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,20 +6,21 @@ Built on `espressif/lua` (v5.5.0, pinned in `idf_component.yml`) -- Espressif's
|
||||
|
||||
## What's here
|
||||
|
||||
- `lua_runtime_init()` creates the Lua state, opens only `base`/`table`/`string`/`math` (deliberately *not* `luaL_openlibs()`, which would also expose `io`, `os`, `package`/`require`, `debug` -- see AGENTS.md's "Lua should never access ESP-IDF directly"), registers the `kvida` API table, then loads the default script (`src/default_script.h`, a compiled-in string) which just *defines* `on_tick()`.
|
||||
- `lua_runtime_init()` mounts storage (`drivers::storage_init()`) and tries to read `/storage/script.lua`; if none is stored yet (first boot, or the read fails), it falls back to the compiled-in default script (`src/default_script.h`). Either way it then builds the Lua state: opens only `base`/`table`/`string`/`math` (deliberately *not* `luaL_openlibs()`, which would also expose `io`, `os`, `package`/`require`, `debug` -- see AGENTS.md's "Lua should never access ESP-IDF directly"), registers the `kvida` API table, then runs the script, which just *defines* `on_tick()`.
|
||||
- `lua_runtime_tick()` calls `on_tick()` -- one "wake, execute Lua, publish changes, sleep" cycle. A Lua runtime error is logged, not fatal.
|
||||
- `lua_runtime_reload(script, len)` persists a new script to storage and hot-reloads it into a fresh Lua state immediately -- no reboot. Wired to `transport::set_script_change_handler()` (POST /api/script) by `main.cpp`. `lua_runtime_current_script(buf, cap)` is the read side, wired to `transport::set_script_provider()` (GET /api/script).
|
||||
- The `kvida` table exposed to scripts:
|
||||
- `kvida.chip_temperature()` -- reads the ESP32-C6's internal die temperature via `profiles::ChipTemperatureSensor` (the same `Sensor` implementation `main.cpp` used directly before this component existed).
|
||||
- `kvida.set_led(r, g, b)` -- sets the onboard WS2812 via `drivers::rgb_led_set_color()`.
|
||||
- `kvida.publish(name, value)` -- calls `transport::publish_value()`, AGENTS.md's semantic publish API, built for the first time here.
|
||||
- The default script reads chip temperature, maps it to a blue(cool)-to-red(hot) LED color (linear interpolation, 20-60C, clamped), and publishes the reading to Home Assistant. Verified end-to-end on real hardware.
|
||||
|
||||
## Known limitation, accepted for now
|
||||
## Known limitations, accepted for now
|
||||
|
||||
The onboard LED is also controllable manually from Home Assistant (an MQTT light entity, see `components/transport/src/mqtt.cpp`). The two aren't reconciled: `on_tick()` overwrites a manually-chosen HA color every 30s. Accepted as a conflict between two "quick proof" demos rather than solved now -- a real answer (e.g. a Lua-settable "mode" toggle, or the MQTT light entity deferring to Lua) needs product input, not a guess.
|
||||
- The onboard LED is also controllable manually from Home Assistant (an MQTT light entity, see `components/transport/src/mqtt.cpp`). The two aren't reconciled: `on_tick()` overwrites a manually-chosen HA color every 30s. Accepted as a conflict between two "quick proof" demos rather than solved now -- a real answer (e.g. a Lua-settable "mode" toggle, or the MQTT light entity deferring to Lua) needs product input, not a guess.
|
||||
- `lua_runtime_reload()` doesn't roll back to the last-good script if the new one fails to compile/run -- it just leaves `on_tick` undefined until the next successful reload (same behavior `lua_runtime_init()` already has for a bad default script). Storage and `lua_runtime_current_script()` always reflect the most recently POSTed script either way, even a broken one, so a user editing from `kvida-sdk` can see (and fix) exactly what they submitted.
|
||||
|
||||
## Not yet done
|
||||
|
||||
- The script is compiled in, not loaded from storage -- there's no upload mechanism yet. Once `kvida-sdk` can push a script (and `drivers` mounts the `storage` LittleFS partition -- both still TODO), scripts should be loaded from there instead.
|
||||
- No Blockly-to-Lua compiler exists yet (that's `kvida-sdk`'s job per AGENTS.md's "Level 2").
|
||||
- No memory/CPU/runtime limits on the Lua state beyond the restricted library set -- a script with an infinite loop would hang `lua_runtime_tick()` (and, since it currently runs on the same timer callback, block other `esp_timer` callbacks too). Not a concern for a compiled-in, developer-authored script; becomes one once arbitrary user scripts can be uploaded.
|
||||
- No Blockly-to-Lua compiler exists yet (that's `kvida-sdk`'s job per AGENTS.md's "Level 2") -- for now scripts are hand-written Lua, edited as raw text in `kvida-sdk`.
|
||||
- No memory/CPU/runtime limits on the Lua state beyond the restricted library set -- a script with an infinite loop would hang `lua_runtime_tick()` (and, since it currently runs on the same timer callback, block other `esp_timer` callbacks too). Was a non-issue for a compiled-in, developer-authored script; now that scripts are user-editable and persisted, this is a real gap, just not one this pass solves.
|
||||
|
||||
Reference in New Issue
Block a user