Skip to content

0.10.0 (unreleased): a stdlib an author knows — datetime, screens, no shader mode - #56

Merged
genmon merged 9 commits into
mainfrom
genmon/stdlib-basics
Oct 5, 2026
Merged

genmon merged 9 commits into
mainfrom
genmon/stdlib-basics

Conversation

@genmon

@genmon genmon commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Folds into the unreleased 0.10.0. Details in docs/changelog.md.

Breaking

  • Shader mode and its globals are gone (rgb, fract, beat, noise2d, bare math); apps use math.*. ctx is {time_ms, generation_id}.
  • The time module is Python 3's time with MicroPython's integer seconds and ticks_ms/ticks_diff.
  • The surfaces Lua module is retired; use screens.
  • An extension that registers no Lua functions gets no global.

New

  • datetime: Python's datetime in whole seconds (now, today, date, timedelta, arithmetic, strftime, isoformat, astimezone, synced), Lua over private C primitives, loaded on first touch. time's calendar half is deprecated for it and warns once per app load; its ticks are not.
  • Resident::DisplayDriver and Screen: screens belong to the drivers that own the glass, registered at initialize(), found by lvgl.bind(name) with no addPanel/addDisplay.
  • The screens Lua module (list, get, set, refresh), with commitScreen so interdependent settings are applied together.
  • PanelTarget::frameDone() after the last flush of a refresh.
  • LvglModule::addWidget(install): a board's own C++ widget classes (parent:Name{...}) through luavgl's public API. Installers run on every bind, because the sandbox clears late globals at each app load.

Tests: 268/268 unit (new: test_datetime_module, test_screens_module), and the agent plugin's run.sh. Exercised end to end by hawthorn-firmware's eleven boards and browser sims.

🤖 Generated with Claude Code

genmon and others added 9 commits October 3, 2026 17:38
…nreleased)

The `time` module is now modelled on Python 3's `time`: time(), gmtime(),
localtime(), mktime(), strftime() and a struct_time with Python's field names
and ranges (tm_mon 1..12, tm_wday Monday = 0, tm_yday 1..366, tm_zone,
tm_gmtoff). Where the VM's numbers bite it follows MicroPython instead: Lua
here is built with LUA_32BITS, so a float epoch is good only to 128 s and a
float seconds-since-boot loses its milliseconds within hours. Seconds are
integers (int32, good until 2038-01-19), elapsed time is ticks_ms() and
ticks_diff(), and there is deliberately no monotonic(). synced() replaces
is_valid(); hour/minute/second/day_id/has_timezone are gone.

The calendar maths and strftime are a new pure header, ResidentTimeCore.h,
rather than the C library's (whose %Z reads the process TZ, and whose
gmtime differs at the edges between newlib, glibc and emscripten), so the
device, the browser sim and the native tests agree to the byte.

Shader mode is removed: the `shader` message type, SandboxConfig::
shaderTemplate, Sandbox::loadShader, ShaderFields. So are the globals that
existed for shader expressions (rgb, fract, beat, noise2d and the bare math
functions) and the ctx fields (trigger_count, utc_h/m, localtime_h/m); ctx
is { time_ms, generation_id }.

Tested: 246 native unit tests, including test_time_module (16 cases: epoch,
leap days, 2038, pre-1970, offsets, mktime normalisation, strftime against
the C locale, the Lua surface with and without a zone, ticks across the
wrap, and the removed globals). The examples, prompts and agent-plugin
tooling move with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hawthorn's bodies no longer register lgfx, and arc carries this sheet
verbatim, so the bind-is-the-claim line reads true with or without it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A driver that owns glass declares its screens (screenCount/screen, each a
Screen: name, PanelTarget, shape, depth, dpi, bufferRows, group). Listed in
cfg.extensions, its screens are registered at initialize(), so lvgl.bind
finds them with no RenderTargets::addPanel and no LvglModule::addDisplay
(an explicit addDisplay still wins). One driver may own several screens —
seven e-paper panels on one bus, three key caps on one backlight rail.

Every SystemDisplay is now a DisplayDriver with no screens by default: the
panel that shows status text is very often the one apps draw on, and a
second Driver base would form a diamond and move the Extension subobject off
offset 0, which the LuaModule trampoline needs.

Lua `screens`: list(), get(name) (facts plus the driver's settings and
status), set(name, {key = value}) (an unknown key raises, naming it;
brightness and contrast 0..1 are the standard names) and refresh(name).
Drivers reset settings in onAppReset. `surfaces` stays until boards move.

PanelTarget::frameDone(): after LvglModule's last flush of a refresh and
after each LgfxModule flip, for panels that present whole frames.

Tested: 255 native unit tests, including test_screens_module (9 cases:
registration, multi-screen drivers, list/get/set/refresh, unknown keys,
reset on app load, hand-registered panels); fan-p1 converted end to end and
its sim passes smoke; all 11 hawthorn-firmware boards build.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
screens.set hands a driver its keys one at a time, in no set order. A driver
whose settings constrain each other (a one-bit screen's black below its
white) stages them in setScreen and applies and validates them in
commitScreen, called once per screens.set. Default: nothing to do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every listed extension used to get a Lua global named after it, empty when
it registered nothing — so a display driver reached only through lvgl.bind
and `screens` left `main`, `epd` or `vfd` behind as tables that look
like an API. Now an extension whose module table is empty (and has no
fallthrough metatable) gets no global.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
screens.list()/get() return what surfaces did, plus depth, dpi, group and
the screen's settings. Sheet, API reference and changelog follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`time` has every date and time in it, but as struct_time tables with no
arithmetic: no "tomorrow", "days until", "same day". Python answers that
with `datetime`, which authors and models already know.

- Lua `datetime`: now/today/fromtimestamp/synced, datetime(...) (the module
  is callable and its own datetime.datetime), date, timedelta, UTC. Every
  value aware, local or UTC; whole seconds; Python's arithmetic and
  comparison (same zone on the wall clock, across zones through UTC).
  Dates run 0001..9999 by ordinal; what needs epoch seconds is int32 and
  raises past 2038 rather than wrapping. Errors name the call and point at
  the app's line.
- Lua (src/ResidentDatetime.h) over a private table of C primitives passed
  as the chunk's `...`, so device, sim and tests run the same code. ~38 KB
  of Lua heap loaded (host figure), so `datetime` is a stub that loads on
  first touch, re-installed at every app load.
- time.time/gmtime/localtime/mktime/strftime/synced still work and each
  warns once per app load on log.warn's line; ticks are not deprecated.
- test_datetime_module (17 cases, CPython-generated expectations, a London
  DST window in the ezTime stub, Serial capture for the warnings).
- Docs, the agent plugin's sheet copy, and the validator now runs the
  device's own datetime source over stub primitives.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LvglModule::addWidget(install) runs each installer once per Lua state,
after the first bind has loaded luavgl: it adds a constructor to luavgl's
"widgets" table and the class's metatable, so parent:Name{...} builds a
board's C++ widget like any other and its own set{} takes its own keys.
Only luavgl's public API; marked in the state's registry, so a new app's
state gets it again even at the old state's address.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sandbox clears every global made after boot when the next app loads,
so a helper module a widget installer sets (the Shader's `shader`) has to
be put back for each app. Installers now run on every bind and must be
idempotent — luavgl_obj_newmetatable returns an existing metatable, and
setting a table field again costs nothing — instead of once per state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@genmon
genmon merged commit 09ddb84 into main Oct 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant