SDK
Active Objects
The Active Objects of the Kode Dot: how they talk, their priorities, the start order and the page of each one.
What it is
kaos is the layer where every device of the Kode Dot gets an owner. The owner is an Active Object, or AO: a FreeRTOS task with its own queue of events and a state machine that handles one event at a time. The name is k + AOs. An AO decides when its device is used, and the driver below it in sdk/kdrv/overview knows how.
Your code never calls a driver. It sends the owner an event, or calls a function of the facade (sdk/kode) that sends it for you. The owner finishes one event before it starts the next, so two requests cannot interleave their writes to a chip and the code needs no lock.
kode_init() starts twenty-two AOs when every option is built in, and a twenty-third, AO_TestDot, with the E5 test. Device owners are never optional. What a firmware may leave out is policy: the console, the app installer, the self-test sequencer, the radio, the network, the rest of the board (AO_LowPower), the cron scheduler (AO_Scheduler) and the E5 final test (AO_TestDot). AO_LowPower decides whether the Kode Dot is awake, asleep or off, and AO_Scheduler runs entries on a clock. AO_Gui, the renderer of kodeOS, is not in kaos. It lives in kgui, above the SDK, and starts after kode_init() returns.
How it is connected
sequenceDiagram autonumber participant Host as Computer box kodeOS participant Log as AO_Log participant Con as AO_Console participant Led as AO_LedMgr end participant Chip as KTD2026 Host->>Log: bytes of "led_rgb rgb=0,0,200" Log->>Con: CONSOLE_LINE_SIG, published Con->>Led: LED_RGB_SIG, published Con->>Log: LOG_LINE_SIG, ">ok" Led->>Chip: four registers over I2C Log->>Host: ">ok"
Two ways to send
To post is to leave an event in the queue of one AO. To publish is to leave it for every AO that subscribed to the signal. A signal below MAX_PUB_SIG travels by publish, and one at or above it by post (sdk/kqp). AO_Log publishes the finished command line and does not know who listens. AO_Console subscribes, and so can an AO of yours.
One event at a time
An AO takes an event from its queue, handles it from start to end and takes the next. A step never waits for a network answer, a delay or a chip. A queue has a fixed size, and one more event than it holds stops the system with a QP assertion and a reset.
The worker rule
Work that has to wait leaves the AO for a worker, a task that belongs to that AO. The AO calls kqp_worker_submit() with a function and its argument, and carries on. The worker runs the job and posts one KqpWorkerDoneEvt back, with a sequence number, a status and one value.
A worker decides nothing. The state machine, the timeouts, the retries and the order of jobs stay in the AO, and the sequence number is how the AO tells a late answer from the one it waits for. Six AOs have workers: AO_Storage, AO_Radio, AO_Net, AO_Ir, AO_Audio and AO_Rfid. The list of workers is in sdk/kqp, and the rule is number 9 in sdk/design-rules.
Ownership is claimed at boot
In its initial transition each AO calls kaos_claim() for the resources it owns. A second claim on the same resource prints kodeOS: <resource> claimed by both <first> and <second> and stops the boot through Q_ERROR(), so the same mistake fails the same way at every boot.
The table of owners is a record. It takes no lock, and a driver called from another task is not stopped by it. The queue keeps the device safe.
Events carry copies
A list travels as one event per entry and an end marker. A page is posted to the AO that asked for it, never published, so a fast asker cannot fill the queue of a slow subscriber.
Specifications
The priorities
A higher number is a higher priority. The QP priority is the FreeRTOS priority of the task, so the console command ps prints this map live. The full table with the macros is in sdk/kqp.
| Priority | Runs | Why there |
|---|---|---|
| 1 | An AO of yours (KODE_PRIO_USER_LOW), or AO_TestDot (KODE_PRIO_TEST_DOT) | Below everything the SDK starts. It can only delay itself. The two share the level, and with KODE_TEST_DOT on the name KODE_PRIO_USER_LOW is not defined |
| 2 | AO_Net and its two workers | A TLS handshake or a four-second ping delays only the network |
| 3 | The renderer (KODE_PRIO_USER_GUI): AO_Gui in kodeOS | The heaviest work and the least urgent. A slow repaint must not stutter the console |
| 4 | AO_Radio and its workers, and the console reader task | Blocking pumps that sit below every AO with real work |
| 5 | AO_Storage and storage_sd | The longest blocking I/O in the system, a slow card |
| 6 | AO_Log | Above the reader, so a keystroke is echoed the moment it arrives. Below everything with a deadline |
| 7 to 9 | AO_LedMgr, AO_Power, AO_AppMgr | Short steps, or steps cut into chunks |
| 10, 11 | AO_Autotest, AO_Console | The console prints a row's record before the sequencer asks the next row |
| 12 | AO_Boot | Rarely runs. When it does, the board is about to reset |
| 13 to 15 | AO_Buttons, AO_Touch, AO_Display | Short polls and DMA kick-offs on the pixel path |
| 16 | AO_Scheduler (KODE_PRIO_SCHEDULER), or an AO of yours (KODE_PRIO_USER_HIGH) | Above the pixel path, so the minute arrives on time while AO_Gui draws. The two share the level, and with KODE_SCHEDULER on the name KODE_PRIO_USER_HIGH is not defined. Only with a reason you can write down |
| 17 | AO_IoExp | One I2C read and one publish per edge, the shortest step in the system |
| 18 to 23 | AO_Ir, AO_Nfc, AO_Audio, AO_Imu, AO_Haptic, AO_Rfid | Levels above 16 exist because QF_MAX_ACTIVE is 25. Each step is short |
| 24 | AO_LowPower | The one AO that belongs above every owner: a wake is decided before the owners it wakes run, and a sleep can ask them all to rest. A round is a publish and a count |
The order of levels 18 to 23 ranks no urgency. They are the levels left above the first owners, and each AO is allowed there because its own step is short: the infrared AO queues a frame and returns, the NFC AO advances a state machine one step, the audio AO runs everything that blocks on its worker. Level 24 is the highest FreeRTOS gives a task on this port, and AO_LowPower holds it, so no level is left above the map. An AO that needs a level must not copy 18: its step has to be short, or the map is renumbered.
The startup order
kode_init() is the one place that knows the order. It starts the AOs on the main task, and QP runs each initial transition inside QACTIVE_START, so an AO has claimed its resources by the time the next one starts.
| # | Starts | Option | The constraint |
|---|---|---|---|
| 1 | AO_Log | always | First, so every later line goes through the owner of the console. The boot banner follows it |
| 2 | AO_Boot | always | Before AO_AppMgr: it reads the boot request that tells why the board started |
| 3 | AO_LowPower | KODE_LOWPOWER | Third, so every owner started after it can join the rounds of rest in its own initial transition |
| 4 | AO_Scheduler | KODE_SCHEDULER | After AO_LowPower, which it joins, and before AO_Console, so the first cron line finds its queue. It posts to nobody until sched_system_up() |
| 5 | AO_Storage | always | Before AO_Radio and AO_AppMgr, which post to it |
| 6 | AO_Radio | KODEOS_RADIO | Link-up is asynchronous, nothing below waits for the C5 |
| 7 | AO_Net | KODEOS_NET | After AO_Radio: it asks for the Wi-Fi state in its initial transition |
| 8 | AO_LedMgr | always | |
| 9 to 13 | AO_Ir, AO_Nfc, AO_Rfid, AO_Audio, AO_Imu | always | Self-contained: nothing depends on them |
| 14 | AO_Haptic | always | After AO_Imu: the haptic row posts to the IMU from its first step |
| 15 | AO_AppMgr | KODEOS_APPMGR | After AO_Boot and AO_Storage |
| 16 | AO_Autotest | KODEOS_AUTOTEST | Before the console, its first client |
| 17 | AO_Console | KODEOS_CONSOLE | |
| 18 | AO_Power | always | |
| 19 | AO_IoExp | always | Before the buttons, the touch and the display, which post to it or subscribe to it |
| 20, 21 | AO_Buttons, AO_Touch | always | |
| 22 | AO_Display | always | Last: starting it brings the panel up and announces it |
Before AO_Log starts, with KODE_LOWPOWER, kode_init() calls kode_boot_gate(), which sends a board woken from OFF by any button but BOT back to deep sleep (sdk/kaos/lowpower). After the twenty-second AO, kode_init() registers the SDK's settings (kaos_config_add()), starts the console reader task (log_start_reader()), calls sched_system_up() when the scheduler is built and calls QF_run(), which returns at once because the scheduler is already running. Posting to an AO that has no queue yet is a FreeRTOS assertion, xQueueGenericSend queue.c:936, and a boot loop.
With KODE_TEST_DOT, kode_init() also starts AO_TestDot, after the console and before AO_Power, in either boot role. It is dormant until the cable asks (sdk/kaos/test-dot).
A consumer such as kgui starts its own AOs after kode_init() returns and asks for state (display_state_request()) instead of waiting for an announcement it missed.
Who claims what
| Active Object | Resources it claims in its initial transition |
|---|---|
| AO_Log | KBSP_RES_CONSOLE |
| AO_Boot | KBSP_RES_OTADATA, KBSP_RES_APPS_BOOTREQ |
| AO_Storage | KBSP_RES_SDMMC, KBSP_RES_FATFS |
| AO_AppMgr | KBSP_RES_APPS_REGION, KBSP_RES_APP_PENDING, KBSP_RES_OTA_SLOT |
| AO_Power | KBSP_RES_PMIC, KBSP_RES_GAUGE |
| AO_IoExp | KBSP_RES_IOEXP: the whole TCA6408A and its INT line |
| AO_Buttons | KBSP_RES_BTN_TOP: GPIO35 |
| AO_Touch, AO_LedMgr | KBSP_RES_TOUCH, KBSP_RES_LED |
| AO_Display | KBSP_RES_PANEL, and KBSP_RES_JPEG and KBSP_RES_PPA when KODEOS_MIRROR is on |
| AO_Audio, AO_Imu, AO_Haptic | KBSP_RES_AUDIO, KBSP_RES_IMU, KBSP_RES_HAPTIC |
| AO_Ir, AO_Nfc, AO_Rfid | KBSP_RES_IR, KBSP_RES_NFC, KBSP_RES_RFID |
| AO_Radio | KBSP_RES_C5 |
| AO_Net, AO_Autotest, AO_Console, AO_TestDot | None at boot. AO_TestDot takes J3 only when the cable wakes it |
| AO_LowPower, AO_Scheduler | None. They own a decision and a table, not a device |
KBSP_RES_EXP, the expansion header, has no owner at boot. The console claims it on the first exp write, and the facade claims it on the first kode_exp_*() call. Both claims are late and each is refused by the same table: the console answers >err code=5 msg=owned, and the facade returns KODE_EXISTS. sdk/kbsp lists the 25 resources and the pins behind them.
The options
| Option | Default | What it gates |
|---|---|---|
KODEOS_BOOT_ROLE_APP / _OS | App | What AO_Boot does with otadata: an app never writes it (sdk/kaos/boot) |
KODEOS_HOME_HOLD | y | Holding the bottom button resets an app home. App role only |
KODEOS_CONSOLE | y | AO_Console. Selects KODEOS_APPMGR and KODEOS_AUTOTEST |
KODEOS_APPMGR | y | AO_AppMgr, the writer of the apps region |
KODEOS_AUTOTEST | n | AO_Autotest. The console selects it |
KODEOS_RADIO | y | AO_Radio and the esp-hosted component |
KODEOS_NET | y | AO_Net. Needs Wi-Fi |
KODE_LOWPOWER | n in the SDK, y in kodeOS | AO_LowPower and the states AWAKE, ASLEEP and OFF. Depends on KODEOS_RADIO (sdk/kaos/lowpower) |
KODE_SCHEDULER | n in the SDK, y in kodeOS | AO_Scheduler and the verb cron. Takes the level of KODE_PRIO_USER_HIGH (sdk/kaos/scheduler) |
The radio, Bluetooth, MQTT and Kode channel options are on the pages of sdk/kaos/radio and sdk/kaos/net.
The files of core
| File | What it does |
|---|---|
include/kaos.h | The public header: the event structs for the AOs, every *_request() helper that posts to an owner, and the boot, run-mode and autotest constants |
include/kcli_proto.h | The console's six error codes (kcli_err_t), defined once |
include/kode_config.h | The settings registry: kode_config_t, KODE_CONFIG_ADD(), the 1 s answer bound |
include/kode_variables.h, include/kode_functions.h | The tables an app registers so the console can read its variables and call its functions |
src/kaos_claim.c, src/kaos_claim.h | kaos_claim(): claim a resource or stop the boot naming both claimants. The header is private to kaos |
src/kaos_wire.h | The base64 encoder and the slice sizes every record producer shares |
src/kode_config.c | kaos_config_add() and kode_config_answer(): the SDK's settings and an owner's half of one |
src/kregistry.c, src/kregistry.h | The console-facing registry of variables, functions and settings. No QP, so the host tests run it |
src/kset.h | The names of the NVS namespace kset. There is no kset.c: each key is written only by the AO that owns it |
Gotchas
The parts
| Active Object | Owns or does | Priority | Page |
|---|---|---|---|
| AO_Boot | otadata and the boot request: which firmware starts | 12 | sdk/kaos/boot |
| AO_LowPower | The board's state: awake, asleep or off | 24 | sdk/kaos/lowpower |
| AO_Scheduler | The cron entries and the mqtt check | 16 | sdk/kaos/scheduler |
| AO_Log | The console device and the log | 6 | sdk/kaos/log |
| AO_Console | The command line: parsing and dispatch | 11 | sdk/kaos/console |
| AO_Storage | The microSD card and the internal FAT | 5 | sdk/kaos/storage |
| AO_AppMgr | The apps region and the inactive kodeOS slot | 9 | sdk/kaos/appmgr |
| AO_Autotest | The self-test rows and the walk over them | 10 | sdk/kaos/autotest |
| AO_Power | The BQ25896 charger and the CW2217 gauge | 8 | sdk/kaos/power |
| AO_Buttons | The six buttons | 13 | sdk/kaos/buttons |
| AO_IoExp | The TCA6408A expander and its interrupt | 17 | sdk/kaos/ioexp |
| AO_Display | The AMOLED panel | 15 | sdk/kaos/display |
| AO_Touch | The touch controller | 14 | sdk/kaos/touch |
| AO_LedMgr | The RGB LED | 7 | sdk/kaos/led |
| AO_Audio | The ES8311 codec: speaker and microphone | 20 | sdk/kaos/audio |
| AO_Haptic | The AW86233 and the motor | 22 | sdk/kaos/haptic |
| AO_Imu | The QMI8658A and the QMC5883P | 21 | sdk/kaos/imu |
| AO_Nfc | The ST25R3916B reader | 19 | sdk/kaos/nfc |
| AO_Rfid | The 125 kHz front end | 23 | sdk/kaos/rfid |
| AO_Ir | The infrared block, served by the C5 | 18 | sdk/kaos/ir |
| AO_Radio | The ESP32-C5 link, Wi-Fi and Bluetooth | 4 | sdk/kaos/radio |
| AO_Net | Ping, HTTP, UDP, MQTT and the Kode channel | 2 | sdk/kaos/net |
| AO_TestDot | The E5 final test: its run, its station on J3 and its lock | 1 | sdk/kaos/test-dot |
In the SDK
components/kaos: one folder per Active Object, pluscore/.components/kaos/core:kaos.h, the claim, and the registry of settings.components/kbsp/owners: the table of ownerskaos_claim()writes to.