Files
wadamesh/scripts/build/gen-lua-builtin.py
T
Kaj SchittecatandClaude Opus 5 e3c9f9b2cb Merge PR #292: ThinkNode M9 compass (QMC6309), GPS motion in the SDK, GPS Compass app
cvhviz. The M9's magnetometer was documented on the board and nothing had ever
talked to it. The driver is written from the datasheet's register map rather
than SensorLib, whose setOutputDataRate() writes the ODR into the OSR bits, and
the axis orientation is measured on hardware at four headings rather than
inherited from a declaration Meshtastic marks unverified and never uses. The
+-32 G range looks absurd for a 0.5 G planet until you measure the board's own
hard-iron bias at about 7x Earth's field.

Also carries several fixes found while testing on hardware: every Lua app opened
on a white page on keypad-nav boards (the focus highlight harvested the app body
as a target and reverse-video filled the page), a use-after-free in the Lua net
worker when an app closed mid-request, an unfreed http_get buffer, canvas pixel
buffers GC'd while LVGL still drew from them, one RTC I2C read per contact, and
map re-open costing 2.5 s on every visit.

Three changes on merge:

  - The map tile-keep gate read `total && total < 4 MB`, so a board reporting
    zero PSRAM -- the most constrained case there is -- landed on the roomy side
    of the test and kept its tiles. Dropped the non-zero guard.
  - gpscompass is 55 KB of Lua, more than every other app combined, and it wants
    a magnetometer the seeded boards do not have. The author deliberately left
    it out of lua_builtin.h; that intent now lives in the catalog as
    "seed": false rather than in whether someone remembers to regenerate, since
    the generator runs from a pre-build hook as of this branch.
  - consoleModeToggleCb was defined inside a !HAS_TANMATSU region while the
    Settings row that binds it compiles on every board, so the Tanmatsu link
    broke. Moved it out. The console boot path is gated on CAP_CONSOLE alone, so
    the switch now does what it says there too.

Built on all seven S3 envs plus both ESP32-P4 targets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:54:26 +02:00

84 lines
3.7 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Bake the catalog's Lua apps into the firmware as seeded built-ins.
The Lua host already supports this: luaAppLaunchFile(id, title, embedded, len)
tries <data>/apps/<id>.lua first and falls back to an embedded source when the
file is not there. That file-first order is what makes a seeded app updatable —
download a newer version from the Store and the file simply wins.
This generates src/ui-touch/lua_builtin.h from deploy/apps/, i.e. exactly the
sources the Store serves, so a board that cannot reach the Store still ships
with the apps rather than an empty drawer.
It used to read out/firmware/apps/ instead. Nothing writes that directory --
deploy-apps.sh rsyncs deploy/apps/ straight to the VPS -- so it was a stale
mirror, and the "same sources the Store serves" guarantee was false: a bumped
app shipped to the Store while the baked-in copy stayed on whatever version was
mirrored last (#257).
Run from the repo root: python3 scripts/build/gen-lua-builtin.py
"""
import json, os, re, sys
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
APPS = os.path.join(ROOT, 'deploy/apps') # canonical; what deploy-apps.sh publishes
OUT = os.path.join(ROOT, 'src/ui-touch/lua_builtin.h')
DELIM = 'WADALUA' # raw-string delimiter: keeps Lua source verbatim
def main():
cat_path = os.path.join(APPS, 'apps.json')
if not os.path.exists(cat_path):
sys.exit('no catalog at ' + cat_path)
cat = json.load(open(cat_path, encoding='utf-8'))['apps']
entries = []
for app in cat:
aid, ver, name = app['id'], app['ver'], app['name']
# "seed": false keeps an app out of the image while still publishing it to
# the Store. Seeding is not free: it is flash on the boards that have the
# least of it, which are exactly the CAP_BUILTIN_LUA_APPS boards. An app
# that is large, or that needs hardware those boards do not have, earns its
# place rather than getting it by being in the catalog.
if app.get('seed') is False:
print(' skip %s: seed=false in apps.json' % aid)
continue
src = os.path.join(APPS, aid, ver, aid + '.lua')
if not os.path.exists(src):
print(' skip %s: no %s' % (aid, os.path.relpath(src, ROOT)))
continue
code = open(src, encoding='utf-8').read()
if (')' + DELIM + '"') in code:
sys.exit('%s contains the raw-string delimiter' % aid)
entries.append((aid, name, ver, code))
if not entries:
sys.exit('no app sources found under ' + APPS)
w = ['// Generated by scripts/build/gen-lua-builtin.py — DO NOT EDIT.',
'// Source: deploy/apps/ (the same .lua the Lua Store serves).',
'// Seeded built-ins: a downloaded <data>/apps/<id>.lua always wins.',
'#pragma once', '',
'struct LuaBuiltinApp { const char* id; const char* name; const char* ver; const char* src; };', '']
for aid, name, ver, code in entries:
w.append('static const char kLuaSrc_%s[] = R"%s(%s)%s";' % (aid, DELIM, code, DELIM))
w.append('')
w.append('static const LuaBuiltinApp kLuaBuiltin[] = {')
for aid, name, ver, _ in entries:
w.append(' { "%s", "%s", "%s", kLuaSrc_%s },' % (aid, name, ver, aid))
w.append('};')
w.append('static const int kLuaBuiltinCount = (int)(sizeof(kLuaBuiltin)/sizeof(kLuaBuiltin[0]));')
w.append('')
open(OUT, 'w', encoding='utf-8').write('\n'.join(w))
total = sum(len(c) for _, _, _, c in entries)
print('%s: %d apps, %d bytes of Lua' %
(os.path.relpath(OUT, ROOT), len(entries), total))
for aid, _, ver, code in entries:
print(' %-10s v%-4s %6d B' % (aid, ver, len(code)))
if __name__ == '__main__':
main()