feat(wifi): scan, connect and persist networks from the settings app
Adds a wifi binding over the Arduino API and a settings flow that scans, picks the strongest AP per SSID, takes a password from an on-screen keyboard, and reports connection state. Credentials join /settings.lua and reconnect at boot. Settings are now written through a temp file and rename, and strings are Lua-escaped, so a password cannot corrupt the file the firmware parses at boot.
This commit is contained in:
+56
-8
@@ -10,6 +10,8 @@ extern "C" {
|
||||
Settings settings;
|
||||
|
||||
static constexpr const char* PATH = "/settings.lua";
|
||||
static constexpr const char* TEMP_PATH = "/settings.tmp";
|
||||
static constexpr const char* BACKUP_PATH = "/settings.bak";
|
||||
|
||||
static int16_t fieldOr(lua_State* L, const char* key, int16_t fallback) {
|
||||
lua_getfield(L, -1, key);
|
||||
@@ -18,6 +20,32 @@ static int16_t fieldOr(lua_State* L, const char* key, int16_t fallback) {
|
||||
return value;
|
||||
}
|
||||
|
||||
static String stringFieldOr(lua_State* L, const char* key, const String& fallback) {
|
||||
lua_getfield(L, -1, key);
|
||||
String value = lua_isstring(L, -1) ? lua_tostring(L, -1) : fallback;
|
||||
lua_pop(L, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
static String luaString(const String& value) {
|
||||
String out = "\"";
|
||||
for (size_t i = 0; i < value.length(); i++) {
|
||||
unsigned char c = value[i];
|
||||
if (c == '\\' || c == '\"') {
|
||||
out += '\\';
|
||||
out += (char)c;
|
||||
} else if (c >= 32 && c < 127) {
|
||||
out += (char)c;
|
||||
} else {
|
||||
char escaped[5];
|
||||
snprintf(escaped, sizeof(escaped), "\\%03u", c);
|
||||
out += escaped;
|
||||
}
|
||||
}
|
||||
out += '\"';
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Settings::setRotation(int16_t degrees) {
|
||||
if (degrees % 90 != 0 || degrees < 0 || degrees > 270) return false;
|
||||
rotation = degrees;
|
||||
@@ -44,20 +72,40 @@ bool Settings::load() {
|
||||
touchY1 = fieldOr(L, "y1", touchY1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, -1, "wifi");
|
||||
if (lua_istable(L, -1)) {
|
||||
wifiSsid = stringFieldOr(L, "ssid", wifiSsid);
|
||||
wifiPassword = stringFieldOr(L, "password", wifiPassword);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_close(L);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool Settings::save() const {
|
||||
File f = SD.open(PATH, FILE_WRITE);
|
||||
String source = "return {\n rotation = " + String(rotation) + ",\n";
|
||||
source += " touch = { x0 = " + String(touchX0) + ", y0 = " + String(touchY0) +
|
||||
", x1 = " + String(touchX1) + ", y1 = " + String(touchY1) + " },\n";
|
||||
source += " wifi = { ssid = " + luaString(wifiSsid) +
|
||||
", password = " + luaString(wifiPassword) + " },\n}\n";
|
||||
|
||||
if (SD.exists(TEMP_PATH)) SD.remove(TEMP_PATH);
|
||||
File f = SD.open(TEMP_PATH, FILE_WRITE);
|
||||
if (!f) return false;
|
||||
char buf[200];
|
||||
int len = snprintf(buf, sizeof(buf),
|
||||
"return {\n rotation = %d,\n"
|
||||
" touch = { x0 = %d, y0 = %d, x1 = %d, y1 = %d },\n}\n",
|
||||
rotation, touchX0, touchY0, touchX1, touchY1);
|
||||
bool ok = f.write(reinterpret_cast<const uint8_t*>(buf), len) == (size_t)len;
|
||||
bool ok = f.write(reinterpret_cast<const uint8_t*>(source.c_str()), source.length()) == source.length();
|
||||
f.close();
|
||||
return ok;
|
||||
if (!ok) {
|
||||
SD.remove(TEMP_PATH);
|
||||
return false;
|
||||
}
|
||||
if (SD.exists(BACKUP_PATH)) SD.remove(BACKUP_PATH);
|
||||
bool hadSettings = SD.exists(PATH);
|
||||
if (hadSettings && !SD.rename(PATH, BACKUP_PATH)) return false;
|
||||
if (SD.rename(TEMP_PATH, PATH)) {
|
||||
if (hadSettings) SD.remove(BACKUP_PATH);
|
||||
return true;
|
||||
}
|
||||
if (hadSettings) SD.rename(BACKUP_PATH, PATH);
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user