kodeOS internals
Apps region
How kodeOS stores installed apps in flash: the region, the mailbox, install and eviction, and the app data folder with the icon and colour of each app.
What it is
The apps region is the part of the flash where kodeOS keeps the apps you install. It is the apps partition, 0xC40000, 11.75 MB, and it holds up to 64 apps. Each app is a complete firmware image. The bootloader starts one in place, without copying it, so installing an app is writing it once and starting it is a reset.
Three things sit around the region.
- The mailbox (
app_pending, 6 MB at0x1A00000) is where a computer leaves an image with oneesptoolcommand. - The boot request is the sector at the start of the region that says which app starts next (
internals/boot-model). - The app data folder is a small folder on the FAT volume or the microSD card that gives an app its icon, colour, version and description without rebuilding it.
sdk/kdrv/apps has the byte formats: the directory, the request, the .kfw container and the manifest. This page is how kodeOS uses them. AO_AppMgr owns the content of the region and the mailbox (KBSP_RES_APPS_REGION, KBSP_RES_APP_PENDING). AO_Boot owns the boot request sector (KBSP_RES_APPS_BOOTREQ). AO_Storage reads the app data folder.
How it is connected
flowchart LR host["Computer<br/>esptool"] subgraph board["On the Kode Dot"] src["Launcher, console,<br/>URL"] mb["app_pending"] am["AO_AppMgr"] reg["apps region"] boot["AO_Boot<br/>boot request"] bl["Bootloader<br/>override"] fat["FAT<br/>.kode/appsdata"] mb -- "adopt" --> am src -- "install" --> am am -- "write" --> reg am -- "request" --> boot boot --> bl bl -- "jump" --> reg am --> fat end host -- "write-flash" --> mb class host ext class mb,reg,fat store class am,boot ao
How an install behaves
An install works by name. The identity of an app is the project_name in its image header. An install with the name of an installed app replaces it in one directory commit, so a second install does not use a second entry. A new name is added. The installs that come from a file, the card, a URL, the console or the mailbox all end in the same code in AO_AppMgr.
The allocator is next-fit and the eviction is first in, first out. kdrv_apps_plan() puts a new image right after the newest one by install order, searching to the end of the region and then wrapping to the start. When nothing fits, the old copy of the app being replaced goes first, then the oldest apps until the image fits. So a new version of an installed app costs no other app unless it is larger than the room its old copy leaves. kodeOS keeps no record of which app you used last, so it has nothing else to rank by.
When all 64 directory entries hold other names, the oldest entry goes too. AO_AppMgr logs each eviction, evicting '<name>' to make room; its copy is on the card as a notice or evicting '<name>' to make room; no copy kept as a warning, publishes APP_EVICTED_SIG for it, and commits the directory, with the victims' flags cleared, before it touches the flash. The old copy of the app being replaced is not an eviction: the log says the old copy of the app being replaced makes room for its new version.
An eviction keeps a copy on the card when it can. If an install from a file, the card, a URL or the mailbox must evict, AO_AppMgr first copies each victim to /sdcard/<project>.bin (skipping one that is already there) and then restarts the install. The victim shows in the launcher as a file you can install again. If a copy fails and the install came from the card, the install fails and nothing is evicted. A terminal install cannot pause for this, so its victims are evicted without a copy.
The write is paced. AO_AppMgr writes an image in 32 KB chunks, spaced by a 10 ms gap, so the lower priorities (the screen included) get the CPU between erases. A write holds the cache down while it erases, and nothing in flash runs then.
Adoption copies the mailbox into the region at boot. In its initial transition AO_AppMgr asks whether the mailbox holds something worth copying. If it does, it posts APP_ADOPT_SIG to itself, because a copy of several megabytes must not run inside an initial transition. The copy goes through a static 4 KB buffer.
The app data folder is read by the owner of the media. AO_Storage finds the folder, reads app.json and the icon and posts the answer to kgui as events. A row in the launcher resolves each field on its own. For each one it takes the first source that has a value.
Specifications
The region
| Thing | Value |
|---|---|
Partition apps | Data, subtype 0x40, 0xC40000, size 0xBC0000 (11.75 MB), up to the FAT at 0x1800000 |
Partition app_pending | Data, subtype 0x41, 0x1A00000, 6 MB, non-executable |
| Directory copies | A at region offset 0x0000, B at 0x1000, one 4 KB sector each |
| Boot request | Region offset 0x2000, 20 bytes in a 4 KB sector |
| First image | Region offset 0x10000 (KAPPS_DATA_OFF); every image starts on a 64 KB boundary (KAPPS_ALIGN) |
| Apps | Up to 64 (KAPPS_MAX_ENTRIES); the launcher lists up to 64 of each kind (KODE_APP_LIST_MAX) |
| Largest install | The data area: 11.75 MB less the 64 KB in front of it |
| Largest mailbox push | 0x5FF000 bytes: 6 MB less the sector that holds the intent word |
| Eviction | The old copy of the app being replaced first, then the oldest install, by seq |
| Directory commit | Writes the copy not in use with gen plus 1 and a new CRC, then swaps. A power cut mid-commit leaves the old directory |
The region ends where the FAT begins and crosses the 16 MB mark on the way. That is legal because the cache maps 32-bit addresses (CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH). To grow the region, space has to come from storage.
Adoption of the mailbox
AO_AppMgr consumes the mailbox (it erases the first sector and the intent sector) only when the outcome is final, so a failed directory write is retried at the next boot.
| Case | What AO_AppMgr does |
|---|---|
The image fails esp_image_verify() in the region | The result repeats every time, so it consumes the mailbox and does not retry |
The intent sector holds KOTA | No directory is involved. It checks the image fits the free kodeOS slot, writes it there, consumes the mailbox before the reset, then asks AO_Boot to switch slot |
| A plain app | One install by name in one commit, evicting the oldest app first if the 64 entries are full. It consumes the mailbox after the commit, then launches the app with a sticky request (KBOOT_RUN_STICKY, state ALWAYS). This is the only place that state is written |
install src="pending" dst=os|app runs the same adoption with the destination named by the command. For dst=app, run=on writes a NEW request, never ALWAYS.
Verification
| Check | When | Where | What it checks |
|---|---|---|---|
kdrv_apps_pending_present() | At boot, before the copy | The mailbox | The length only: the segment headers of a bare image, or the header of a .kfw container. It catches a torn upload. No hash |
kdrv_apps_pending_verify() | Only when the install must evict an installed app | The mailbox | The full esp_image_verify(), so a broken upload costs nobody an app |
esp_image_verify() in kdrv_apps_write_end() | After the copy | The region | The full check with the hash. An app starts only after this passes |
The app data folder
| Thing | Value |
|---|---|
| Path on the FAT | /storage/.kode/appsdata/<project>/ |
| Path on the card | /sdcard/.kode/appsdata/<project>/ |
<project> | The project_name of the app, up to 31 characters, from the characters A-Z a-z 0-9 _ - . only. Any other name has no folder |
| Which medium wins | The FAT first, then the card. The first folder found is used whole; fields are never merged across media |
| Files | app.json (at most 1024 bytes) and the icon icon.k64 (exactly 12288 bytes) |
| Cost on the FAT | 20 KB of 4 KB clusters for a folder with an icon: one for the folder, one for app.json, three for the icon |
| Console writes | To the FAT only. A microSD card shipped with some units fails on write, and one failed write breaks every later read on that volume |
| Icon format | 64 by 64 RGB565 little-endian (8192 bytes), then a 64 by 64 alpha plane (4096 bytes). No header. A row draws it at the 80 px disc; the details draw it at 64 px |
| Decoding | The board decodes no PNG or JPEG. tools/kappdata.py converts on the computer |
app.json is one flat object of string values. These are its keys:
| Key | Limit |
|---|---|
kode | Required, "1" |
name | 31 characters |
category | 63 characters, a comma list lowercased on read |
version | 15 characters |
description | 52 characters, in the details of the app |
icon | 40 characters, a relative path with no .. |
color | #RRGGBB |
Unknown keys are ignored. The whole file is refused with size, syntax or revision on any fault, and the launcher then draws the app as if it had no folder. internals/app-icon has the steps.
Who writes the folder, and what an install does to it:
| What is installed | The folder |
|---|---|
A .kfw container with an app data block (KFW_FLAG_APPDATA) | Replaced whole by the block's app.json and icon |
| A container without a block | Deleted |
A bare .bin image (it starts with 0xE9) | Left as it is |
The install ends only after AO_Storage has answered, so a launch never runs before the folder exists. A write that fails is one warning and the install still succeeds. An erased app takes its folder with it. An install never touches a folder on the card.
Which source gives each field in the launcher:
| Field | 1st | 2nd | 3rd | Default |
|---|---|---|---|---|
| Name | Folder | Image manifest | Project name | |
| Category | Folder | app_cat override table | Image manifest | Empty (the app is a loose row at the root of Apps) |
| Version | Folder | The image's esp_app_desc_t.version (installed apps only) | Empty | |
| Description | Folder | Empty | ||
| Icon | Folder icon path | Folder icon.k64 | The apps glyph on the app's colour (the console calls it monogram) | |
| Colour | Folder | Medium Grey |
The identity of a row is always the project name. The folder changes only its label.
The answer to a request for one folder is three posted events, in this order:
APPDATA_FIELDS_SIGwith every field and two statuses (json_status:ok,none,size,syntax,revision;icon_status:ok,none,size,path,busy).- Zero or three
APPDATA_ICON_SIGblocks of 4096 bytes each. APPDATA_END_SIG.
AO_Storage takes the three icon blocks before it posts anything, so an icon arrives complete or not at all. While a console upload holds the jumbo pool, the fields event still goes out with icon_status=busy and kgui asks again at the next scan.
kgui keeps one 12 KB PSRAM buffer for each of its 128 list entries (64 installed apps and 64 files), about 1.5 MB, taken once.
Console
| Command | What it does |
|---|---|
install src="terminal" dst=app run=on name="<project>" | Streams a .bin or .kfw over the console into the region (kcli.py install FILE --dst app). name is the image's project name, which the board cannot read before the room is made; kcli and kble send it. Each app the install evicts prints >|evicted name="<project>" kept=off before >|ready |
install src="pending" dst=app | Adopts the mailbox now |
app_list, app_run name="<project>", app_erase name="<project>" | List, start with a NEW request, erase |
appdata_ls, appdata_info name=, appdata_rm name= | List every folder and orphan, show what the board resolved for one app, delete the FAT folder of one app |
kcli.py appdata put NAME DIR | Validate on the computer, convert the icon, create the folder and upload it in one session |
Gotchas
Example code
Install an app over the cable, start it and read the board's list. Each command prints what it did.
- Install and start:
python3 tools/kcli.py -p <port> --settle 5 install build/my_app.bin --dst app. The tool printsinstalled and bootingand the app starts. - Hold BOT and LEFT for about one second to go back to kodeOS. Apps opens with the app in focus, in its folder if it names a category.
- List the region:
python3 tools/kcli.py -p <port> --settle 5 cmd app_list. One record per installed app comes back, thenok. - Start it from the console:
python3 tools/kcli.py -p <port> --settle 5 cmd 'app_run name="my_app"'. The name is theproject()name of the build.
TBD: these steps have not been run on a Kode Dot from this page.
In the SDK
components/kdrv/apps: the region driver,kapps_fmt.h, the container decoder and theapp.jsonreader.components/kaos/appmgr/src/appmgr_ao.c: install, adoption, eviction and keeping a copy on the card.tools/kappdata.py: the host side of the app data folder.