refactor: run apps on the shared esp32-lua-api runtime

Replaces the firmware's own lua_State, bindings, module loader, node tree
and navigation history with lib/esp32-lua-api. What is left in src/host is
the hardware behind the provider interfaces plus the chrome the firmware
owns: the viewport, the status bar and touch polling.

src/lua became src/host because src is on the include path, so a directory
named lua shadowed the library's <lua/providers.h> and #pragma once then
silently skipped it.
This commit is contained in:
2026-08-03 18:12:33 -04:00
parent a40d2441f6
commit 45abf8a767
23 changed files with 1223 additions and 2901 deletions
+112
View File
@@ -0,0 +1,112 @@
#include <Arduino.h>
#include <esp_heap_caps.h>
#include "../net.h"
#include "../settings.h"
#include "lua_host.h"
#include "providers.h"
namespace slate {
using esp32lua::Status;
void Log::write(esp32lua::LogLevel level, const std::string& message) {
const char* tag = level == esp32lua::LogLevel::Error ? "error"
: (level == esp32lua::LogLevel::Info ? "info" : "debug");
Serial.printf("[lua] %s: %s\n", tag, message.c_str());
}
int32_t Settings::rotation() const { return settings.rotation; }
// Applies the rotation itself, because a setter that needs a follow-up call is a bug in the
// setter: the frame, the viewport and the bar all move together or not at all.
Status Settings::setRotation(int32_t degrees) {
if (!settings.setRotation(degrees)) return Status::failure("rotation must be 0, 90, 180 or 270");
host.applyRotation();
return settings.save() ? Status::success() : Status::failure("cannot save settings");
}
std::string Settings::timezone() const { return settings.timezone.c_str(); }
Status Settings::setTimezone(const std::string& timezone) {
if (timezone.empty() || timezone.size() > 48) return Status::failure("timezone must be 1 to 48 characters");
settings.timezone = timezone.c_str();
net::applyTimezone();
return settings.save() ? Status::success() : Status::failure("cannot save settings");
}
int32_t Sys::millis() const { return static_cast<int32_t>(::millis()); }
esp32lua::MemoryInfo Sys::memory() const {
esp32lua::MemoryInfo info;
info.freeBytes = static_cast<int32_t>(ESP.getFreeHeap());
info.totalBytes = static_cast<int32_t>(ESP.getHeapSize());
info.largestFreeBlock = static_cast<int32_t>(heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT));
return info;
}
bool Sys::isClockSynced() const { return net::clockSynced(); }
bool Touch::touch(int32_t& x, int32_t& y) const {
if (!panel.touched()) return false;
int16_t mappedX = 0, mappedY = 0;
host.mapTouch(panel.getPoint(), mappedX, mappedY);
x = mappedX;
y = mappedY;
return true;
}
bool Touch::rawTouch(int32_t& x, int32_t& y) const {
if (!panel.touched()) return false;
const TS_Point point = panel.getPoint();
// The controller's axes are swapped relative to the panel, which calibration undoes.
x = point.y;
y = point.x;
return true;
}
bool Touch::isTouched() const { return panel.touched(); }
Status Touch::setCalibration(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
settings.touchX0 = x0;
settings.touchY0 = y0;
settings.touchX1 = x1;
settings.touchY1 = y1;
return settings.save() ? Status::success() : Status::failure("cannot save settings");
}
// A fixed slot table rather than a heap list: the whole point of the arena elsewhere is that
// a Lua app cannot fragment the heap, and timers are created from Lua.
Status Timer::schedule(esp32lua::TimerId id, int32_t intervalMs, bool repeating) {
for (int at = 0; at < SLOTS; at++) {
if (slots[at].id != 0) continue;
slots[at].id = id;
slots[at].dueMs = ::millis() + intervalMs;
slots[at].intervalMs = intervalMs;
slots[at].repeating = repeating;
return Status::success();
}
return Status::failure("no timer slot available");
}
void Timer::cancel(esp32lua::TimerId id) {
for (int at = 0; at < SLOTS; at++) {
if (slots[at].id == id) slots[at] = Slot();
}
}
void Timer::collectDue(uint32_t nowMs, std::vector<esp32lua::TimerId>& due) {
for (int at = 0; at < SLOTS; at++) {
if (slots[at].id == 0) continue;
// Signed comparison, so a deadline still pending across the millis() wrap is not "due".
if (static_cast<int32_t>(nowMs - slots[at].dueMs) < 0) continue;
due.push_back(slots[at].id);
if (slots[at].repeating) {
slots[at].dueMs = nowMs + slots[at].intervalMs;
} else {
slots[at] = Slot();
}
}
}
} // namespace slate