kodeOS internals
Settings
Every setting a Kode Dot stores across restarts, which Active Object owns it, its values and default, and how the console verb config reads and changes it.
What it is
kodeOS keeps some settings across a restart and a power cycle: the brightness, whether Wi-Fi and Bluetooth are wanted, the volume, the time zone and a few more. They all live in the nvs partition, the flash store ESP-IDF uses for small values. You read and change every one of them with one console verb, config.
Each setting is read and written by the one Active Object that applies it, and no other code touches it. So each stored value has one owner and the store needs no lock. The console never touches the store either. It sends a request for one key at a time, and the owner reads, applies, stores and answers.
Nothing in the store is a secret. Wi-Fi passwords and Bluetooth bonds live on the ESP32-C5. Every value here sits in plain flash, and any console and any app on the board can read it.
How it is connected
sequenceDiagram autonumber participant Host as kcli.py box "On the Kode Dot" participant Con as AO_Console participant Own as owner AO participant Nvs as khal_nvs end Host->>Con: config key="bright" value=40 Con->>Own: CONFIG_REQ_SIG (key, value) Own->>Own: apply Own->>Nvs: store Own->>Con: kode_config_answer() Con->>Host: >|config key="bright" value=40 stored=yes
Each setting is a row of type kode_config_t in a registry (kode_config.h). Each Active Object registers the rows it owns as it starts, and kgui registers its six when AO_Gui has a queue. A table with one bad row is refused whole, with a log line naming the row.
An app can add rows of its own with KODE_CONFIG_ADD() and answer them from its own Active Object. They join the same listing. The registry holds at most KODE_CONFIG_MAX = 32 rows in KODE_CONFIG_TABLES = 4 tables. A key is lowercase letters, digits and _, at most 15 characters.
The console reads and sets in three forms:
configlists every row, one record each.config key="bright"reads one.config key="bright" value=40sets one and answers with what the owner kept.
A record reads >|config key="bright" value=40 stored=yes. stored=no means the key is not in the store and the value shown is the default. Setting a value back to its default erases the key for log, for scene set to auto, for kody_sleep set to lock and for mag_cal set to "".
A value is typed like a function argument: a string is quoted and every other kind is bare, and the console refuses the wrong form with >err code=3 msg=value. A boolean is on or off. An integer outside its range, a word not in an enum, or a wrong form gets the same refusal. Strings hold at most 32 characters, and an enum lists at most six words (KODE_VALUE_ENUMS_MAX).
Specifications
The twenty-four settings
A row exists only in a build that has its feature. config prints the keys in the order their owners registered them. Each owner registers its table as it starts, so the order is the order of the start: in kodeOS log, zone, off_after, bright, lock, scene, clock, kody_sleep, haptic_ui, usb_awake, wifi, ble, wifi_join, wifi_ps, chan, chan_keep, vol, mic_db, mute, mag_cal, haptic, led_activity, charge, charge_ma. A build with Wi-Fi, Bluetooth, the Kode channel and low power lists 24 rows. An app built with CONFIG_KODE_LOWPOWER_IDLE also has sleep_after; kodeOS builds without it, because its Rest decides when the board rests. The tables below group them by what they control.
Settings: display, log, audio and haptic
The panel, the console's log level, the codec, the compass and the motor.
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
bright | AO_Display | Integer 5–100 | 100 | Panel brightness in per cent. 5 is the floor because 0 is black on this AMOLED and a settings screen must not turn off the only thing that can turn it back on. Applied at once and stored |
log | AO_Log | d, i, n, w, e, off | The build's level, CONFIG_KLOG_DEFAULT_LEVEL, info | The console's global log level. Stored, so it survives the reset that opening the port causes. Setting the build's level erases the key. log_level tag="<tag>" sets one tag and is never stored |
vol | AO_Audio | Integer 0–100 | 100 | Speaker level in per cent. Applied at once; the codec gets it at the next open |
mic_db | AO_Audio | Integer 0–42, multiples of 6 | 24 | Microphone gain in dB. A value off the 6 dB grid is refused and nothing is stored |
mute | AO_Audio | on, off | off | The DAC mute of the ES8311 |
mag_cal | AO_Imu | A quoted calibration, "ox oy oz sx sy sz", or "" | None | The magnetometer's correction: an offset per axis in mG (up to 9999 either way) and a scale per axis in thousandths (250 to 4000). "" forgets it. A text that does not read as six such numbers is refused and nothing is stored |
haptic | AO_Haptic | Integer 0–100 | 100 | A scale in per cent on the gain of every buzz an app or the console asks for. The factory test row never uses it |
Settings: radio and network
Wi-Fi, Bluetooth and the Kode channel. The rest of the board has its own table below.
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
wifi | AO_Radio | on, off | off | Whether Wi-Fi is wanted. On starts the link to the C5 and the automatic join. Default is CONFIG_KODEOS_WIFI_DEFAULT_ON, off. Needs CONFIG_KODEOS_WIFI |
ble | AO_Radio | on, off | on | Whether Bluetooth is wanted: on advertises from boot. Default is CONFIG_KODEOS_BLE_DEFAULT_ON, on. Needs CONFIG_KODEOS_BLE |
wifi_join | AO_Radio | on, off | The build's CONFIG_KODEOS_WIFI_AUTOJOIN, on | Whether the link, when it comes up for a wanted Wi-Fi, scans and joins a remembered network by itself. A wifi_join command that AO_Radio accepts also sets wifi on. Needs CONFIG_KODEOS_WIFI |
wifi_ps | AO_Radio | on, off | on | Whether the Wi-Fi station keeps its power save. Needs CONFIG_KODEOS_WIFI |
chan | AO_Net | on, off | on | Whether the board joins the Kode channel (internals/kode-channel) whenever it has Wi-Fi. Off leaves it at once. Needs CONFIG_KODEOS_CHANNEL |
chan_keep | AO_Net | 1h, 6h, 1d, 3d, 7d, never | 1d | How long the broker keeps the channel's session while the board is away, and so how long the commands sent meanwhile wait for it. never asks the broker for no expiry. A set is stored at once and used at the next connect, a renewal included. The channel is not reconnected for it. Needs CONFIG_KODEOS_CHANNEL |
Settings: the rest
When the board rests (sdk/kaos/lowpower).
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
sleep_after | AO_LowPower | 30s, 1m, 2m, 5m, 10m, never | 2m | The time without input after which the board goes from AWAKE to ASLEEP. A button, a touch or a console line from any port starts the count over, and never counts nothing. It does not depend on lock or kody_sleep. Needs CONFIG_KODE_LOWPOWER and CONFIG_KODE_LOWPOWER_IDLE, so kodeOS does not have it |
off_after | AO_LowPower | 1h, 6h, 24h, 3d, 7d, never | 1h | The time spent ASLEEP after which the board goes on to OFF. Needs CONFIG_KODE_LOWPOWER |
Settings: power
The LED and the charger.
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
led_activity | AO_Power | on, off | on | Whether the LED pulses blue while a flash write runs |
charge | AO_Power | on, off | on | Whether the cell charges. AO_Power writes it over the boot policy of the charger at every boot and after pmic_reset |
charge_ma | AO_Power | Integer 64–896 | 512 | Fast-charge current in mA (KBSP_CELL_ICHG_MA, ceiling KBSP_CELL_ICHG_MAX_MA). Stored as the chip kept it on its 64 mA grid, so 300 is stored as 320 |
Settings: the clock
The time zone belongs to AO_Boot, which applies it at boot before an app runs.
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
zone | AO_Boot | A quoted POSIX TZ string, at most 32 characters | "UTC0" | The time zone of the face's clock, for example "CET-1CEST,M3.5.0,M10.5.0/3". It also stamps FAT files in local time. The value must read as std offset [dst [offset] [,start[/time],end[/time]]]: a name of three or more letters or one in angle brackets, an offset of at most 24 hours, and rules in the Jn, n or Mm.w.d forms. Anything else is refused with >err code=5 msg=value and nothing changes |
Settings: the face
Four of the six rows kgui owns: the clock's format, the lock and Kody.
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
lock | kgui | 30s, 1m, 2m, 5m, 10m, never | 1m | The auto-lock time on the launcher and the settings screen |
scene | kgui | A quoted scene name, or "auto" | auto | The face's scene. auto lets the hour draw one. A name the active pack does not have is refused. The official pack in the SDK has tren, parque, camping, azotea, madrugada, desayuno, taller, lluvia, playa and reloj |
clock | kgui | 24h, 12h | 24h | The face's hour format, with no AM or PM |
kody_sleep | kgui | lock, 30s, 1m, 5m, 10m, never | lock | How long on the face without input before it dims to Rest, with Kody standing still (Sleep after, on the Settings list). lock follows the auto-lock time and erases the key. It is not sleep_after, which is the board's own time before ASLEEP |
Settings: the interface
The other three rows of kgui. Each applies at once and is stored on every set, even when the value is the stored one. A byte the setting does not have reads as the default, with a warning.
| Key | Owner | Values | Default | What it does |
|---|---|---|---|---|
haptic_ui | kgui | off, light, normal | light | How much of the interface buzzes. off plays nothing, light plays the click, the thud, the tick and the mark, and normal adds a tick for each move of the selection, at most 14 a second. haptic set to 0 silences the interface too |
usb_awake | kgui | on, off | off | on keeps the Lock out of Rest while a cable is in |
How each one is stored
All keys are in the NVS namespace kset, except kgui's six (lock, scene, clock, kody_sleep, haptic_ui, usb_awake), which are in kgui. Namespaces and keys are at most 15 characters, NVS's own limit.
| Key | Stored as |
|---|---|
bright, vol, mic_db, haptic | u8 |
wifi, ble, wifi_join, wifi_ps, chan, mute, led_activity, charge | u8 as a boolean |
chan_keep | A blob of one uint32_t, in seconds. 3600, 21600, 86400, 259200 or 604800, or 0xFFFFFFFF for never. Absent, another size or any other number means 86400 |
sleep_after, off_after | A blob of one uint32_t, in seconds. 0 is never. Absent means 120 s and 3600 s |
log | u8, a kode_log_lvl_t: 0 debug, 1 info, 2 notice, 3 warning, 4 error, 5 off. A byte above 5 is ignored |
charge_ma | A blob of one uint16_t, in mA |
zone, scene, mag_cal | A string |
lock, kody_sleep | A blob of one uint32_t, in seconds. 0 is never. A missing or unreadable lock means 60 s |
clock | u8, 1 for 12 h |
haptic_ui, usb_awake | u8. haptic_ui is 0, 1 or 2 (off, light, normal); usb_awake is 1 for on. Absent means the default |
Other things in the same partition
These have no row in config.
| Namespace and key | Owner | What it is |
|---|---|---|
kgui/last | AO_Gui | The row the launcher launched last, where the cursor starts after a restart |
kgui/recents, kgui/scene_hr, kgui/blename, kgui/volvib | AO_Gui | The apps run last, by project name, latest first; the scene the hour drew and for which hour; the Bluetooth name typed on the Bluetooth page; and whether the volume bar stands on its vibrate stop |
kclock/wall, kclock/approx | kdrv_clocks | The wall clock, kept every 60 s by AO_Power and before a switch-off, and the mark that it is approximate |
kset/lp_last | AO_LowPower | The summary of the last rest, written at most once a minute while asleep and once when it ends. lowpower_info last=on prints it |
kcron/e<slot>, kcron/mqtt | AO_Scheduler | One blob per entry made with cron, and one for the time of the built-in mqtt entry or its being off. A blob of another length or version is skipped. An app does not read or write them |
kident/c5mac | khal_identity | The C5's MAC, six bytes, so the board is named before the link is up |
kode/app_cats | AO_AppMgr | The category overrides of installed apps |
kapp/<key> | Apps and operators | A provisioning store kodeOS never interprets. Key at most 15 characters (KODE_NVS_KEY_MAX is 16 with the terminator), value at most 191 characters (KODE_NVS_VALUE_MAX is 192 with the terminator), 24 keys in a listing. Reached by kode_nvs_set(), kode_nvs_get(), kode_nvs_del() and the verbs nvs_set, nvs_get, nvs_del, nvs_list |
| NimBLE bond store | NimBLE | Bluetooth bonds, in the same partition, outside khal_nvs |
The nvs partition is 64 KB at 0xB000. khal_nvs_init() erases it and starts again when ESP-IDF reports no free pages or a newer version. A reader returns KODE_DENIED for an absent key and for a namespace never written, and that is not an error: the caller applies its default.
What survives a restart: the settings above, the last launched row, the wall clock (marked approximate), the entries of cron, the summary of the last rest, the category overrides, the kapp keys and the bonds.
What does not: the C5 link, the Wi-Fi connection and address, any MQTT session, the commands that wait in the queue of the Kode channel (RAM, already acknowledged by the broker), the scan tables and the selection in the launcher.
Gotchas
Example code
List every setting, set the brightness to 40 and read it back. The second command prints a record with value=40 and stored=yes. The last command writes all settings to settings.json, and config-import writes back only the keys that differ.
# settings.sh - 2026-10-28 - from Kode with love
#
# Lists every stored setting of a Kode Dot, dims the screen to 40 per cent,
# reads the value back, and saves all settings to a JSON file. The second
# command prints a record with value=40 and stored=yes. Opening the port
# resets the board, so the three config commands share one session.
python3 tools/kcli.py -p /dev/cu.usbmodem101 --settle 5 cmd 'config' 'config key="bright" value=40' 'config key="bright"'
python3 tools/kcli.py -p /dev/cu.usbmodem101 --settle 5 config-export -o settings.jsonTBD: this example has not been run on a Kode Dot yet.
In the SDK
components/kaos/core/src/kset.h: the names of theksetkeys and their limits.components/kaos/core/include/kode_config.h: the registry andKODE_CONFIG_ADD().components/kgui/kgui_ao.c: the sixkguirows and their owner.