Compare commits

...
4 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 5 11d826bdfe touch: beta_71 release notes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 13:52:38 +02:00
Kaj SchittecatandClaude Opus 5 f49a950e30 lua: wada.ui.clear(), so an app can have more than one screen
The SDK documented building widgets in on_open() and gave no way to take them
down, so an app with a second screen drew it on top of the first. pisti87 hit
this building a multi-screen app and had been working around it with buttons;
he also noted that ui.list() has clear() while the page it sits on does not.

The reason this is not just lv_obj_clean() is the handles. WidgetUd holds a raw
lv_obj_t*, guarded only by `if (u->obj)`, so a Lua variable still referring to a
label from the screen just cleared would sail past that check into freed memory.
Every handle now records the generation it was created in, clear() bumps the
generation, and the accessors null the pointer of any handle from an older one.
The null guard that every method already has then turns a stale call into a
no-op. Given the day this codebase has had with use-after-frees, a clear() that
invited one would not have been worth shipping.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 13:02:15 +02:00
Kaj SchittecatandClaude Opus 5 80b9fd1693 lua: let an app reach all its contacts, and write a bigger app
Both reported by Jade, and both were arbitrary caps rather than real limits.

wada.mesh.contacts() scanned the first 200 contacts, returned at most 100, and
said nothing about having truncated. On a device holding 2000 contacts most of
them were simply unreachable. It takes an offset and a limit now, defaulting to
the old 100 and capped at 250 per call, because every entry is an eight-field
Lua table and building thousands in one go would exhaust the per-app heap. The
window moves, so everything is reachable by asking more than once, and the new
wada.mesh.contact_count() means an app can page without probing for the end.

The app source limit was 64 KB, which an ordinary program can reach. It is
192 KB now. The source sits in PSRAM only until luaL_loadbuffer has compiled it,
so this is a transient allocation, not a per-app cost, and the compiled chunk
still has to fit the 256 KB app heap. The Store's download buffer was the other
half of that ceiling and moves with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:23:16 +02:00
Kaj SchittecatandClaude Opus 5 2df6f087eb keyboard: do not decide the T-Deck's capability on one I2C read (#332)
jrote1 flashed the June 2025 C3 keyboard firmware, which supports raw column
frames, and still got no modifier latching.

The probe asked for a raw frame exactly once, on the first poll after boot, and
anything other than five bytes with every high bit clear committed the device to
legacy mode for the rest of the session. The first I2C read after boot is the
least trustworthy one there is: the C3 is still starting, so a short or garbled
frame is ordinary rather than exceptional. There was no recovery except a reboot,
and a reboot re-rolls the same dice.

It now retries for 1.5 seconds before settling for legacy. A single-byte answer
is still delivered as a keystroke while probing, so nothing typed in that window
is lost, and a genuinely old controller ends up in the same place a little later.

The fallback message also says what it actually saw -- how many bytes came back,
and whether a column had its high bit set. "Update your keyboard firmware" to
someone who just did is not a diagnosis, and this is the same lesson as #274:
a failure path that says nothing produces a bug report with nothing in it.

Verified on a T-Deck with the newer firmware: still raw mode, latching works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 12:08:42 +02:00
5 changed files with 138 additions and 17 deletions

No files matched your search

+4 -2
View File
@@ -238,6 +238,7 @@ return app</code></pre>
<tr><td>wada.ui.button(text, x, y, w, h, fn)</td><td>A tappable button at <code>x, y</code> sized <code>w &times; h</code>; <code>fn</code> is called with no arguments.</td></tr>
<tr><td>wada.ui.text_w(text [, size])</td><td>Rendered width of <code>text</code> in pixels, for the given size class.</td></tr>
<tr><td>wada.ui.text_lines(text, width [, size])</td><td>How many lines <code>text</code> wraps to inside <code>width</code>. Counts explicit newlines too. <b>Use this when you lay out your own rows.</b> <code>label:width()</code> turns wrapping on, so a line longer than the screen becomes two while a fixed <code>y</code> step still advances by one, and the next row is drawn on top of it. This is the single most common way an app's layout goes wrong; it happened to the SDK Test app's channel list.</td></tr>
<tr><td>wada.ui.clear()</td><td>Remove every widget from the app's page, leaving the page itself. This is how you build an app with more than one screen: clear, then create the next screen's widgets in the same place you built the first. <b>Any handle you still hold from before the clear is dead</b> &mdash; calling a method on it does nothing rather than crashing, but it will not come back, so re-create and re-assign. Older firmware has no <code>ui.clear</code>: write <code>local clear = ui.clear or function() end</code> if you need to run on both.</td></tr>
<tr><td>wada.ui.list(x, y, w, h)</td><td>A scrollable list of selectable rows &mdash; the "pick one of N" widget. Methods: <code>add(text [, fn])</code> returning the row index, <code>set(i, text)</code>, <code>color(i, c)</code>, <code>select(i)</code> (highlights <i>and</i> scrolls it into view), <code>selected()</code>, <code>count()</code>, <code>clear()</code>, <code>pos(x, y)</code>. Rows are real buttons, so keyboard and trackball navigation walks them on touchless boards without you doing anything.</td></tr>
<tr><td>wada.ui.input(title, initial, cb)</td><td>A modal text field. <code>cb(text)</code> on OK, <code>cb(nil)</code> on cancel or on the dialog being closed any other way &mdash; exactly one call, always, so an app that disabled itself while waiting always gets to re-enable. Uses the firmware's own dialog and keyboard, so it behaves identically on a touchscreen, on the Tanmatsu's physical keyboard and on a trackball board. One prompt at a time.</td></tr>
<tr><td>wada.ui.canvas(w, h)</td><td>A drawing surface. See the canvas methods below.</td></tr>
@@ -292,7 +293,8 @@ return app</code></pre>
<h2>wada.mesh</h2>
<div class="row"><div class="prose">
<table class="api">
<tr><td>wada.mesh.contacts()</td><td>Array of <code>{name, pubkey, type, ago_s, lat, lon, lat_e6, lon_e6}</code>. <code>pubkey</code> is the first 4 bytes as 8 hex characters &mdash; the same short form the rest of the interface uses, and what lines a contact up with a discovery hit.</td></tr>
<tr><td>wada.mesh.contacts([offset], [limit])</td><td>Array of <code>{name, pubkey, type, ago_s, lat, lon, lat_e6, lon_e6}</code>. <code>pubkey</code> is the first 4 bytes as 8 hex characters &mdash; the same short form the rest of the interface uses, and what lines a contact up with a discovery hit. Returns at most <code>limit</code> entries (default 100, maximum 250) starting at <code>offset</code>, because every entry is an eight-field table and building thousands in one call would exhaust the app heap. On a device with more contacts than that, page through them: ask for 100, then 100 from offset 100, and so on.</td></tr>
<tr><td>wada.mesh.contact_count()</td><td>How many contacts the device holds, so you can page through <code>contacts()</code> without calling it repeatedly to find where the list ends.</td></tr>
<tr><td>wada.mesh.rx_log()</td><td>Recent packets, newest first: <code>{ago_ms, type, rssi, snr, hops, route, len}</code>, plus whatever identity the frame actually carried &mdash; see below. The RF Monitor feed.</td></tr>
<tr><td>wada.mesh.stats()</td><td><code>{rssi, noise, rx_air_s, tx_air_s, rx_pkts, rx_err, tx_budget_ms, rx_events, rx_dropped, tx_pkts, freq, bw, sf, duty_pct}</code>.</td></tr>
<tr><td>wada.mesh.self()</td><td>This node: <code>{name, pubkey, lat, lon, lat_e6, lon_e6}</code>.</td></tr>
@@ -410,7 +412,7 @@ end</code></pre>
</table>
<p><code>http://</code> only, both of them. On-device TLS is not workable at the heap these boards have left once Wi-Fi has associated, so there is no <code>https</code> to offer; put a proxy in front if you need it. One request in flight at a time per app.</p>
<p>The fetch is asynchronous and runs on the firmware's existing network worker, so it does not block the UI. Your callback runs on the UI thread once the body is in memory.</p>
<div class="note"><p><b>Plain HTTP only, and that is not an oversight.</b> After Wi-Fi associates there is not enough free internal memory on the smaller boards for a TLS handshake — mbedTLS wants around 30&nbsp;KB and roughly 5&nbsp;KB is free. If you need an HTTPS source, put a small proxy in front of it, the way the map tiles do. Responses are capped (64&nbsp;KB by default).</p></div>
<div class="note"><p><b>Plain HTTP only, and that is not an oversight.</b> After Wi-Fi associates there is not enough free internal memory on the smaller boards for a TLS handshake — mbedTLS wants around 30&nbsp;KB and roughly 5&nbsp;KB is free. If you need an HTTPS source, put a small proxy in front of it, the way the map tiles do. Responses are capped (192&nbsp;KB by default).</p></div>
</div></div>
</section>
+14
View File
@@ -0,0 +1,14 @@
# beta_71 - test build. Stable stays beta_65.
# One user-facing note per non-blank, non-# line; # lines are section comments.
# --- For people writing Lua apps ------------------------------------------------------------
Apps can clear their page. There was no way to remove the widgets an app had already created, so anything with a second screen drew it on top of the first. wada.ui.clear() takes the page back to empty, and any widget you were still holding from before the clear becomes inert rather than dangerous.
Apps can reach all your contacts. The list stopped at 100, and it only ever looked at the first 200 in the first place, so on a device with a lot of contacts most of them were unreachable and nothing said so. wada.mesh.contacts() now takes an offset and a limit, and wada.mesh.contact_count() tells an app how many there are to page through.
Apps can be three times larger. The limit was 64 KB, which an ordinary program can reach. It is 192 KB now.
# --- T-Deck keyboard ---------------------------------------------------------------------------
Modifier latching could fail to turn on even with the right keyboard firmware installed. The firmware asked the keyboard what it was capable of exactly once, on the first read after boot, and the first read after boot is the one most likely to come back garbled because the keyboard controller is still starting. One bad answer meant no latching for the rest of the session, and rebooting simply asked again at the same unlucky moment. It now keeps asking for a second and a half before giving up, and says what it actually saw when it does.
# --- For testers ---------------------------------------------------------------------------------
This is a small drop on top of beta_70, which carried the fix for the crash when leaving the known-regions screen. If you have not tried that yet, it is still the thing most worth confirming: add and remove a few regions, leave the screen, check the device stays up.
If your screen is not going to sleep, we are still investigating (issue 333) and it is not fixed here.
+33 -5
View File
@@ -19,6 +19,11 @@ static uint8_t s_head = 0;
static uint8_t s_tail = 0;
static bool s_inited = false;
enum class KeyboardMode : uint8_t { Probe, Raw, Legacy };
// How long to keep asking the C3 for raw frames before settling for legacy mode.
// Generous on purpose: getting this wrong costs the user modifier latching for
// the entire session, and the only cost of waiting is that the first second of
// keystrokes goes through the legacy path, which still works.
static const uint32_t kProbeWindowMs = 1500;
static KeyboardMode s_mode = KeyboardMode::Probe;
static TDeckKeyboardState s_raw_state;
static bool s_modifier_input_allowed = true; // guarded by s_keyboard_mux
@@ -125,23 +130,46 @@ void tdeckKeyboardPoll() {
if (s_mode == KeyboardMode::Probe) {
// LilyGO keyboard firmware from June 2025 onward accepts 0x03 and returns
// five column-state bytes. Older controllers ignore it and keep returning
// one resolved ASCII byte; restore 0x04 key mode immediately in that case.
// one resolved ASCII byte; restore 0x04 key mode when that is what we have.
//
// KEEP RETRYING before concluding "old firmware". This used to decide on a
// SINGLE read, and the first I2C read after boot is the least trustworthy
// one there is: the C3 is still coming up, so a short or garbled frame is
// ordinary. One such frame committed the device to legacy mode for the whole
// session, with no recovery short of a reboot -- reported as latching simply
// not working on a T-Deck carrying the June 2025 firmware (#332).
static uint32_t s_probe_start_ms = 0;
const uint32_t now = millis();
if (!s_probe_start_ms) s_probe_start_ms = now;
keyboardCommand(0x03);
uint8_t frame[TDeckKeyboardState::COLS] = {};
const int count = keyboardRead(frame, sizeof frame);
bool valid_raw = count == (int)sizeof frame;
for (size_t col = 0; col < sizeof frame && valid_raw; ++col)
valid_raw = (frame[col] & 0x80) == 0;
size_t bad_col = 0;
for (size_t col = 0; col < sizeof frame && valid_raw; ++col) {
if (frame[col] & 0x80) { valid_raw = false; bad_col = col; }
}
if (valid_raw) {
s_mode = KeyboardMode::Raw;
Serial.println("[keyboard] T-Deck raw mode: modifier latching enabled");
processRawFrame(frame, millis());
return;
}
// A legacy controller answers with one resolved byte. Deliver it either way
// so nothing typed during the probe window is dropped.
if (count == 1) { keyboardCommand(0x04); processLegacyKey(frame[0]); }
if ((uint32_t)(now - s_probe_start_ms) < kProbeWindowMs) return; // try again next poll
keyboardCommand(0x04);
s_mode = KeyboardMode::Legacy;
Serial.println("[keyboard] T-Deck legacy mode: update keyboard C3 firmware for modifier latching");
if (count == 1) processLegacyKey(frame[0]);
// Say WHY, so the next report of "latching does not work" is one line to
// diagnose instead of a guess about which firmware someone has.
Serial.printf("[keyboard] T-Deck legacy mode: update keyboard C3 firmware for modifier "
"latching (probe read %d bytes, wanted %d%s)\n",
count, (int)sizeof frame,
(count == (int)sizeof frame) ? ", high bit set in a column" : "");
(void)bad_col;
return;
}
if (s_mode == KeyboardMode::Raw) {
+86 -9
View File
@@ -170,10 +170,25 @@ void budgetHook(lua_State* L, lua_Debug*) {
// host state
// ---------------------------------------------------------------------------
constexpr size_t kHeapCap = 256 * 1024; // per-app PSRAM cap
constexpr size_t kMaxSrc = 64 * 1024; // app source size limit
// App source size limit. Was 64 KB, which a real app can reach: reported from a
// 2000-contact device whose author hit it writing an ordinary Lua program, not
// anything pathological. The source lives in PSRAM only until luaL_loadbuffer
// has compiled it, so this is a transient allocation rather than a per-app cost,
// and the compiled chunk still has to fit the 256 KB per-app heap below.
constexpr size_t kMaxSrc = 192 * 1024; // app source size limit
constexpr size_t kStoreMax = 2048; // per-app persisted KV budget (bytes, serialized)
constexpr int kMinTickMs = 33; // fastest on_tick cadence (~30 fps)
// Bumped by wada.ui.clear(). Every widget handle records the generation it was
// created in; a handle from an older one has already been destroyed, so the
// accessors below null its pointer and the `if (u->obj)` guard that every
// method already has does the rest.
//
// Without this, clear() would be a use-after-free generator: WidgetUd holds a
// raw lv_obj_t*, so a Lua variable still referring to a label from the previous
// screen would sail past the null check straight into freed memory.
static uint32_t s_ui_gen = 1;
struct Host {
lua_State* L = nullptr;
LuaHeap heap;
@@ -275,7 +290,7 @@ const char* safeUiText(const char* text, size_t length, size_t* safe_length = nu
}
// ---- canvas userdata ----
struct CanvasUd { lv_obj_t* obj; lv_color_t* buf; int w, h; };
struct CanvasUd { lv_obj_t* obj; lv_color_t* buf; int w, h; uint32_t gen; };
CanvasUd* checkCanvas(lua_State* L) {
return (CanvasUd*)luaL_checkudata(L, 1, "wada.canvas");
@@ -368,6 +383,7 @@ int uiCanvas(lua_State* L) {
lv_canvas_set_buffer(cv, buf, w, h, LV_IMG_CF_TRUE_COLOR);
lv_canvas_fill_bg(cv, lv_color_hex(0x000000), LV_OPA_COVER);
CanvasUd* ud = (CanvasUd*)lua_newuserdatauv(L, sizeof(CanvasUd), 0);
ud->gen = s_ui_gen;
ud->obj = cv; ud->buf = buf; ud->w = w; ud->h = h;
luaL_setmetatable(L, "wada.canvas");
// Pin the handle in the registry for the app's lifetime. LVGL draws straight
@@ -383,8 +399,12 @@ int uiCanvas(lua_State* L) {
}
// ---- label userdata ----
struct WidgetUd { lv_obj_t* obj; };
WidgetUd* checkLabel(lua_State* L) { return (WidgetUd*)luaL_checkudata(L, 1, "wada.label"); }
struct WidgetUd { lv_obj_t* obj; uint32_t gen; };
WidgetUd* checkLabel(lua_State* L) {
WidgetUd* u = (WidgetUd*)luaL_checkudata(L, 1, "wada.label");
if (u->gen != s_ui_gen) u->obj = nullptr; // cleared out from under this handle
return u;
}
int lbSet(lua_State* L) {
WidgetUd* u = checkLabel(L);
@@ -435,14 +455,19 @@ int uiLabel(lua_State* L) {
lv_obj_set_style_text_font(l, font, LV_PART_MAIN);
lv_obj_set_style_text_color(l, lv_color_hex(color), LV_PART_MAIN);
WidgetUd* ud = (WidgetUd*)lua_newuserdatauv(L, sizeof(WidgetUd), 0);
ud->gen = s_ui_gen;
ud->obj = l;
luaL_setmetatable(L, "wada.label");
return 1;
}
// ---- chart userdata (the primitive the native Monitor/Airtime pages use) ----
struct ChartUd { lv_obj_t* obj; lv_chart_series_t* ser[2]; int n_ser; };
ChartUd* checkChart(lua_State* L) { return (ChartUd*)luaL_checkudata(L, 1, "wada.chart"); }
struct ChartUd { lv_obj_t* obj; lv_chart_series_t* ser[2]; int n_ser; uint32_t gen; };
ChartUd* checkChart(lua_State* L) {
ChartUd* u = (ChartUd*)luaL_checkudata(L, 1, "wada.chart");
if (u->gen != s_ui_gen) { u->obj = nullptr; u->n_ser = 0; }
return u;
}
int chPush(lua_State* L) { // chart:push(series_idx, value)
ChartUd* c = checkChart(L);
@@ -509,6 +534,7 @@ int uiChart(lua_State* L) { // wada.ui.chart(w, h, points, color1 [, color2] [
lv_obj_set_style_line_color(ch, lv_color_hex(0x1A1D1F), LV_PART_MAIN); // grid
lv_obj_set_style_line_width(ch, 2, LV_PART_ITEMS);
ChartUd* ud = (ChartUd*)lua_newuserdatauv(L, sizeof(ChartUd), 0);
ud->gen = s_ui_gen;
ud->obj = ch;
ud->n_ser = 0;
ud->ser[0] = lv_chart_add_series(ch, lv_color_hex(argColor(L, 4, 0x15B6A6)), LV_CHART_AXIS_PRIMARY_Y);
@@ -557,6 +583,7 @@ int uiButton(lua_State* L) {
lv_obj_add_event_cb(b, btnEventCb, LV_EVENT_CLICKED, nullptr);
}
WidgetUd* ud = (WidgetUd*)lua_newuserdatauv(L, sizeof(WidgetUd), 0);
ud->gen = s_ui_gen;
ud->obj = b;
luaL_setmetatable(L, "wada.label"); // shares set/pos/color methods
return 1;
@@ -573,8 +600,12 @@ int uiButton(lua_State* L) {
// keyboard/trackball focus navigation walks them on touchless boards for free,
// and a tap works on the ones with a screen. Per-row callbacks ride the same
// button-callback table uiButton uses.
struct ListUd { lv_obj_t* obj; int sel; };
ListUd* checkList(lua_State* L) { return (ListUd*)luaL_checkudata(L, 1, "wada.list"); }
struct ListUd { lv_obj_t* obj; int sel; uint32_t gen; };
ListUd* checkList(lua_State* L) {
ListUd* u = (ListUd*)luaL_checkudata(L, 1, "wada.list");
if (u->gen != s_ui_gen) u->obj = nullptr;
return u;
}
lv_obj_t* listRowAt(ListUd* u, int i) { // i is 1-based, as everywhere in Lua
if (!u->obj || i < 1) return nullptr;
@@ -626,6 +657,24 @@ int uiTextLines(lua_State* L) {
return 1;
}
// wada.ui.clear() -- remove every widget from the app's page.
//
// The SDK documented building widgets in on_open() and gave no way to take them
// down again, so an app with more than one screen drew the second on top of the
// first. Reported by pisti87, who had been working around it with buttons.
//
// Bumping the generation is what makes this safe. A Lua variable still holding a
// label from the screen just cleared keeps a raw pointer to freed memory; the
// accessors compare generations and null it, so the existing `if (u->obj)` guard
// in every method turns a stale call into a no-op instead of a crash.
static int uiClear(lua_State* L) {
(void)L;
if (!s_h || !s_h->body) return 0;
lv_obj_clean(s_h->body); // deletes the children, keeps the page itself
++s_ui_gen;
return 0;
}
int uiList(lua_State* L) {
if (!s_h || !s_h->body) return luaL_error(L, "no app body");
const int x = (int)luaL_checkinteger(L, 1), y = (int)luaL_checkinteger(L, 2);
@@ -643,6 +692,7 @@ int uiList(lua_State* L) {
lv_obj_set_scroll_dir(c, LV_DIR_VER);
lv_obj_add_flag(c, LV_OBJ_FLAG_SCROLLABLE);
ListUd* ud = (ListUd*)lua_newuserdatauv(L, sizeof(ListUd), 0);
ud->gen = s_ui_gen;
ud->obj = c; ud->sel = 0;
luaL_setmetatable(L, "wada.list");
return 1;
@@ -1835,10 +1885,35 @@ void pressCb(lv_event_t* e) {
}
// ---- wada.mesh (read-only) ----
// wada.mesh.contacts([offset], [limit])
//
// Used to scan the first 200 contacts and return at most 100, with no way to see
// past that and nothing to say it had truncated. On a device with 2000 contacts
// an app simply could not reach most of them (reported by Jade).
//
// It takes an offset and a limit now. The default limit stays 100 because every
// entry is an 8-field Lua table and building thousands of them in one call would
// blow the per-app heap -- but the window can be moved, so all contacts are
// reachable by asking twice. wada.mesh.contact_count() says how many there are.
// How many contacts the device holds, so an app can page through them without
// calling contacts() repeatedly just to discover where the list ends.
static int meshContactCount(lua_State* L) {
char name[36], pk[12]; int type; uint32_t ago; double lat, lon; int32_t lat6, lon6;
int n = 0;
while (n < 4096 && luaHostContactAt(n, name, sizeof name, &type, &ago, &lat, &lon,
pk, sizeof pk, &lat6, &lon6)) ++n;
lua_pushinteger(L, n);
return 1;
}
int meshContacts(lua_State* L) {
const int offset = (int)luaL_optinteger(L, 1, 0);
int limit = (int)luaL_optinteger(L, 2, 100);
if (limit < 1) limit = 1;
if (limit > 250) limit = 250; // one call still has to fit the app's heap
lua_newtable(L);
char name[36], pk[12]; int type; uint32_t ago; double lat, lon; int32_t lat6, lon6;
for (int i = 0, out = 0; i < 200 && out < 100; i++) {
for (int i = (offset > 0 ? offset : 0), out = 0; out < limit; i++) {
if (!luaHostContactAt(i, name, sizeof name, &type, &ago, &lat, &lon, pk, sizeof pk,
&lat6, &lon6)) break;
lua_createtable(L, 0, 8);
@@ -2249,6 +2324,7 @@ void openWada(lua_State* L) {
lua_setfield(L, -2, "colors");
lua_pushcfunction(L, uiInput); lua_setfield(L, -2, "input"); // modal text entry
lua_pushcfunction(L, uiList); lua_setfield(L, -2, "list"); // scrollable selectable rows
lua_pushcfunction(L, uiClear); lua_setfield(L, -2, "clear"); // wipe the page (#318 / Discord)
lua_setfield(L, -2, "ui");
lua_newtable(L); // wada.sys
@@ -2328,6 +2404,7 @@ void openWada(lua_State* L) {
lua_newtable(L); // wada.mesh (read-only)
lua_pushcfunction(L, meshContacts); lua_setfield(L, -2, "contacts");
lua_pushcfunction(L, meshContactCount); lua_setfield(L, -2, "contact_count");
lua_pushcfunction(L, meshRxLog); lua_setfield(L, -2, "rx_log");
lua_pushcfunction(L, meshStats); lua_setfield(L, -2, "stats");
lua_pushcfunction(L, meshSelf); lua_setfield(L, -2, "self");
+1 -1
View File
@@ -26523,7 +26523,7 @@ static bool luaStoreDownloadWorker(WiFiClient& client, HTTPClient& http,
luaHostAppPath(appsdir, sizeof appsdir, "/apps");
fs->mkdir(appsdir);
static const char* const kExt[2] = { "lua", "json" };
const size_t kCap[2] = { 64 * 1024, 1024 };
const size_t kCap[2] = { 192 * 1024, 1024 }; // must match kMaxSrc in LuaAppHost.cpp
for (int e = 0; e < 2; e++) {
char* buf = (char*)heap_caps_malloc(kCap[e], MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!buf) return false;