6f2d618b8d
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.
173 lines
5.5 KiB
C++
173 lines
5.5 KiB
C++
// Process control and persisted settings, plus the one-function `log` table.
|
|
|
|
#include "../../net.h"
|
|
#include "../../settings.h"
|
|
#include "../bindings.h"
|
|
#include "../lua_app.h"
|
|
|
|
static int l_sys_millis(lua_State* L) {
|
|
lua_pushinteger(L, millis());
|
|
return 1;
|
|
}
|
|
|
|
static int l_sys_exit(lua_State* L) {
|
|
app(L)->requestExit();
|
|
return 0;
|
|
}
|
|
|
|
static int l_sys_launch(lua_State* L) {
|
|
app(L)->requestLaunch(luaL_checkstring(L, 1));
|
|
app(L)->requestExit();
|
|
return 0;
|
|
}
|
|
|
|
static int l_sys_getRotation(lua_State* L) {
|
|
lua_pushinteger(L, settings.rotation);
|
|
return 1;
|
|
}
|
|
|
|
// Persisted, unlike gui.setRotation() which only changes the current frame.
|
|
static int l_sys_setRotation(lua_State* L) {
|
|
if (!settings.setRotation(luaL_checkinteger(L, 1))) {
|
|
lua_pushboolean(L, false);
|
|
return 1;
|
|
}
|
|
app(L)->tft.setRotation(settings.rotationIndex());
|
|
lua_pushboolean(L, settings.save());
|
|
return 1;
|
|
}
|
|
|
|
static int l_sys_getTheme(lua_State* L) {
|
|
lua_pushstring(L, settings.theme.c_str());
|
|
return 1;
|
|
}
|
|
|
|
// The firmware only stores the name; /lib/theme.lua decides what it looks like.
|
|
static int l_sys_setTheme(lua_State* L) {
|
|
size_t length;
|
|
const char* name = luaL_checklstring(L, 1, &length);
|
|
if (!length || length > 32) {
|
|
lua_pushboolean(L, false);
|
|
return 1;
|
|
}
|
|
settings.theme = String(name, length);
|
|
lua_pushboolean(L, settings.save());
|
|
return 1;
|
|
}
|
|
|
|
static int l_sys_getTimezone(lua_State* L) {
|
|
lua_pushstring(L, settings.timezone.c_str());
|
|
return 1;
|
|
}
|
|
|
|
// Takes a POSIX TZ rule, not a zone name: /lib/timezones.lua is only a picker, so a
|
|
// zone missing from that list is still reachable by writing the rule.
|
|
static int l_sys_setTimezone(lua_State* L) {
|
|
size_t length;
|
|
const char* tz = luaL_checklstring(L, 1, &length);
|
|
if (!length || length > 48) {
|
|
lua_pushboolean(L, false);
|
|
return 1;
|
|
}
|
|
settings.timezone = String(tz, length);
|
|
net::applyTimezone();
|
|
lua_pushboolean(L, settings.save());
|
|
return 1;
|
|
}
|
|
|
|
static int l_sys_setCalibration(lua_State* L) {
|
|
settings.touchX0 = luaL_checkinteger(L, 1);
|
|
settings.touchY0 = luaL_checkinteger(L, 2);
|
|
settings.touchX1 = luaL_checkinteger(L, 3);
|
|
settings.touchY1 = luaL_checkinteger(L, 4);
|
|
lua_pushboolean(L, settings.save());
|
|
return 1;
|
|
}
|
|
|
|
// Lua's os.time()/os.date() work off the same system clock, so this is the only
|
|
// binding a clock UI needs: it says whether that clock means anything yet.
|
|
static int l_sys_clockSynced(lua_State* L) {
|
|
lua_pushboolean(L, net::clockSynced());
|
|
return 1;
|
|
}
|
|
|
|
static int logAt(lua_State* L, const char* level) {
|
|
Serial.printf("[lua:%s] %s\n", level, luaL_checkstring(L, 1));
|
|
return 0;
|
|
}
|
|
|
|
static int l_log_debug(lua_State* L) { return logAt(L, "debug"); }
|
|
static int l_log_info(lua_State* L) { return logAt(L, "info"); }
|
|
static int l_log_error(lua_State* L) { return logAt(L, "error"); }
|
|
|
|
// Parity with crosspoint: sleeping is the one thing a script cannot express itself,
|
|
// since the runtime owns the loop.
|
|
static int l_sys_delay(lua_State* L) {
|
|
delay(luaL_checkinteger(L, 1));
|
|
return 0;
|
|
}
|
|
|
|
void registerSys(lua_State* L) {
|
|
static const luaL_Reg lib[] = {
|
|
// --- Milliseconds since boot.
|
|
// @return integer
|
|
{"millis", l_sys_millis},
|
|
// --- Blocks for the given time.
|
|
// @param ms integer
|
|
{"delay", l_sys_delay},
|
|
// --- Ends this app and returns to the launcher.
|
|
{"exit", l_sys_exit},
|
|
// --- Ends this app and starts another one.
|
|
// @param path string Absolute path to the app's main.lua.
|
|
{"launch", l_sys_launch},
|
|
// --- Saved screen rotation in degrees clockwise.
|
|
// @return integer
|
|
{"getRotation", l_sys_getRotation},
|
|
// --- Rotates the screen and saves it.
|
|
// @param degrees integer 0, 90, 180 or 270.
|
|
// @return boolean Whether the setting was saved.
|
|
{"setRotation", l_sys_setRotation},
|
|
// --- Name of the active theme in /lib/theme.lua.
|
|
// @return string
|
|
{"getTheme", l_sys_getTheme},
|
|
// --- Selects a theme by name and saves it.
|
|
// @param name string
|
|
// @return boolean Whether the setting was saved.
|
|
{"setTheme", l_sys_setTheme},
|
|
// --- Stores touch calibration, in the panel's unrotated frame.
|
|
// @param x0 integer Raw reading at the left edge.
|
|
// @param y0 integer Raw reading at the top edge.
|
|
// @param x1 integer Raw reading at the right edge.
|
|
// @param y1 integer Raw reading at the bottom edge.
|
|
// @return boolean Whether the setting was saved.
|
|
{"setCalibration", l_sys_setCalibration},
|
|
// --- Whether SNTP has answered. Until it has, os.time() is only a build-time floor.
|
|
// @return boolean
|
|
{"clockSynced", l_sys_clockSynced},
|
|
// --- Active POSIX timezone rule.
|
|
// @return string
|
|
{"getTimezone", l_sys_getTimezone},
|
|
// --- Sets the timezone from a POSIX TZ rule and saves it.
|
|
// @param tz string For example EST5EDT,M3.2.0,M11.1.0.
|
|
// @return boolean Whether the setting was saved.
|
|
{"setTimezone", l_sys_setTimezone},
|
|
{nullptr, nullptr}};
|
|
luaL_newlib(L, lib);
|
|
lua_setglobal(L, "sys");
|
|
|
|
// Too small to deserve its own translation unit.
|
|
static const luaL_Reg logLib[] = {
|
|
// --- Writes a debug line to the serial log.
|
|
// @param message string
|
|
{"debug", l_log_debug},
|
|
// --- Writes an info line to the serial log.
|
|
// @param message string
|
|
{"info", l_log_info},
|
|
// --- Writes an error line to the serial log.
|
|
// @param message string
|
|
{"error", l_log_error},
|
|
{nullptr, nullptr}};
|
|
luaL_newlib(L, logLib);
|
|
lua_setglobal(L, "log");
|
|
}
|