App Kit reference
New screens start with #include "app_framework.h" and APP_HELPER(). Up, Down,
Left, Right, Select, and Escape are already bound. A tool fills .on_view
and sleeps until a key or .every_ms. While the header band is showing,
that sleep is at most one second and the body is redrawn with the clock.
Pass .keys, .live,
.game or .fullscreen, .on_tick, .on_load, .on_ready, .on_draw, or .state / .state_size when a screen needs
a different map, a frame every tick, no chrome, a timed step, RAM-heavy setup at boot,
setup after the UI exists, a canvas instead of a scene, or a session restored on start and resume and stored on quit.
Pixels are fw/ui.h. Files and pins are fw/io.h. Include those only when the screen uses them.
Title and help come from app.json. The launcher icon is <symbol>_icon.
.type defaults to APP_TYPE_TOOL. .fps applies only with .live or .game, and then it defaults to 30.
The short example is in docs/apps.md.
#include "app_kit.h" is the lower-level umbrella (APP_DEFINE, open/exit)
plus the UI components under apps/ui/components/.
fw/events.h wraps the kernel bus (os_event_subscribe / os_event_publish).
A screen that calls those links kernel/event.c. Key presses do not publish
on that bus.
Short how-to for adding a builtin: docs/apps.md.
Layout
apps/
app_framework.h # the one include for an app
fw/ui.h # pixels, text, frame timing
fw/io.h # keys, settings store, files
fw/layout.h # canvas, screen, menu, catalog, status
fw/events.h # os_event_subscribe / os_event_publish
app_kit.h / app_kit.c # runtime: ctx, APP_DEFINE, focus, open/exit
stdapps/<name>/ # one folder per builtin app
flowchart TB
app[stdapps] --> kit[app_kit]
kit --> canvas
kit --> screen
kit --> menu
kit --> catalog
kit --> status
screen --> canvas
screen --> status
menu --> canvas
menu --> catalog
menu --> display
menu --> status
catalog --> display
Lifecycle
- Install manifests with
stdapps_install(), which also builds the launcher catalog once. os_boot_load()paints the splash (ArdubotOS, the last 8 characters of the git commit, a progress bar, and the step name). It mounts storage, starts clock, notify, and sensors, then calls each installed app'son_loadonce.app_start(stdapps_start_name()). On a full image that name issensors. Escape from any other app resumes the launcher.- Kit runs
on_init→ loopon_frame+ sleep →on_cleanupon hard exit. - Only the foreground app receives keys and may flush the display.
app_open(from, name)starts or resumes the child and suspends the caller.app_request_exit/app_bind_backsoft-leaves a non-home app (suspends it, keeps workers/state) and resumes launcher.
on_load is void (*)(void). It runs once during the splash, before the app
task and UI exist. Put tables, caches, and other RAM-heavy setup there.
on_ready still runs later, when the app actually starts and the UI exists.
A second app_kit_load does not call the hook again. An app with no hook is
still named on the splash.
APP_DEFINE
APP_DEFINE(symbol, "install_name",
.type = APP_TYPE_USER, /* 0 means "keep the default", which is USER */
.icon = &my_app_icon, /* optional 16×16 launcher bitmap (app-owned) */
.fps = 30,
.on_init = on_init,
.on_frame = on_frame,
.on_cleanup = on_cleanup /* optional */
);
Name, version, author, and description come from the app's app.json at compile time. Pass them in APP_DEFINE only to override that file. APP_HELPER is the screen path: it reads title and help from the same file, registers <symbol>_icon, and uses APP_TYPE_TOOL when .type is left out.
Apps own their icons (const app_icon_t in a *_icon.c). APP_DEFINE registers
.icon via app_kit_set_icon; the catalog/launcher never hardcodes glyphs by name.
Runtime API (app_kit.h)
| Call | Purpose |
|---|---|
app_bind_key(app, SIM_KEY_*, fn, user) |
Map a key; fn(app, user) on press |
app_bind_back(app) |
Escape → app_request_exit |
app_open(from, "name") |
Start or resume another app; suspend caller |
app_request_exit(app) |
Soft-leave non-home app (suspend + resume launcher) |
app_kit_is_foreground(app) |
Focus check (canvas uses this) |
app_kit_set_demand(app, POWER_DEMAND_*) |
Compute ceiling for the governor. See Power |
app_mark_dirty / app_is_dirty |
Redraw scheduling (also via canvas) |
Components
Display — display.h
Compile-time panel size. CMake sets APP_DISPLAY_WIDTH / APP_DISPLAY_HEIGHT
from ARDUBOT_DISPLAY_WIDTH / ARDUBOT_DISPLAY_HEIGHT. The NodeMCU profile is
128×32. A simulator build (ARDUBOT_BUILD_SIM) forces 320×172.
Canvas — canvas.h
| Call | Purpose |
|---|---|
app_mark_dirty / app_is_dirty / app_clear_dirty |
Dirty flag |
app_clear |
Clear framebuffer (foreground only) |
app_text / app_textf |
Draw text |
app_flush |
Present + clear dirty |
Screen — screen.h
Titled frames without hand-rolling dirty/clear/flush:
static void on_frame(app_ctx_t* app) {
if (!app_screen_begin(app, "Counter")) {
return;
}
app_textf(app, 0, 16, "Count: %d", g_count);
app_screen_end(app);
}
Menu — menu.h
| Call | Purpose |
|---|---|
app_menu_init / app_menu_add / app_menu_load_catalog |
Build list |
app_menu_move(app, menu, delta, user) |
Move; consumes user, marks dirty |
APP_MENU_ONE_UP / APP_MENU_ONE_DOWN |
Deltas |
app_menu_bind_nav(app, menu) |
Bind Up/Down for you |
app_menu_selected |
Current item |
app_menu_draw(app, menu, title, help) |
Full redraw |
Header — header_app.c
The status band (battery, uptime clock, separator). Every standard app gets
it on a panel at least 280×150, including the launcher. app_display_flush
reads the paint from header_app.c after the app draws, so the band does
not scroll with content. .game and .fullscreen skip it.
app_header_height() is 25 on those panels and 0 on a smaller one. The
focused app wakes at least once a second while the band is up and redraws
its body in that same frame, so the clock is not the only thing moving.
Status — status.h
Top-right corner icons (Wi-Fi glyph, 4-bar signal, battery). Drawn automatically from
app_screen_end and app_menu_draw. Wi-Fi credentials come from
device_secrets.yaml (see README.md). Set the pack level once at boot:
app_status_set_battery_percent(100); /* USB-powered boards */
app_status_set_link(APP_STATUS_LINK_UP, -50); /* associated, RSSI dBm */
| Call | Purpose |
|---|---|
app_status_set_battery_percent / _battery_percent |
0–100 level |
app_status_set_link / _link / _rssi |
Association + RSSI for the icons |
app_status_signal_bars |
0–4 bars from link + RSSI |
app_status_redraw_due |
At most 10 full-panel paints per second |
app_status_stable_bars |
Ignore signal-bar changes shorter than 400 ms |
app_status_draw(app) |
Blit icons (foreground only) |
app_status_blit(...) |
Same glyphs via a set_pixel callback (board shell) |
Catalog — catalog.h
Built once after installs:
app_kit_catalog_build("launcher"); /* exclude home from the list */
| Call | Purpose |
|---|---|
app_kit_catalog_build / _clear / _count / _at |
Snapshot |
app_type_tag(type) |
"[SYS]" / "[USR]" / "[GME]" / "[TOL]" |
app_menu_load_catalog(menu) |
Fill a menu from the snapshot |
Minimal app sketch
#include "app_kit.h"
static int32_t g_count;
static void on_inc(app_ctx_t* app, void* user) {
(void)user;
g_count++;
app_mark_dirty(app);
}
static void on_init(app_ctx_t* app) {
app_bind_key(app, SIM_KEY_1, on_inc, NULL);
app_bind_back(app);
}
static void on_frame(app_ctx_t* app) {
if (!app_screen_begin(app, "Demo")) {
return;
}
app_textf(app, 0, 16, "%d", g_count);
app_screen_end(app);
}
APP_DEFINE(demo_app, "demo", .on_init = on_init, .on_frame = on_frame);
Tests
| Binary | Covers |
|---|---|
test_app_helper |
chrome from app.json, keys, frames, layout math |
test_app_manifest |
name, version, author, description, title, help |
test_app_kit |
manifest, bind, back, exit, foreground |
test_app_ui |
rows, bars, key tables |
test_stdapps_register |
install set and the sensors boot name |
test_event_bus |
kernel bus and os_event_publish |
test_components_canvas |
dirty / draw safety |
test_components_screen |
begin/end |
test_components_menu |
move, nav bind, display macros |
test_components_catalog |
boot catalog |
test_status |
battery clamp + blit pixels |
test_sensors_app / test_sensor_service |
sensor lines and the config registry |
test_clock / test_clock_service |
wall clock and /flash/clock.dat |
Logging
APP_INFO, APP_WARN, APP_ERROR, APP_DEBUG via app_kit.h /
app_framework.h.