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"
One console command, from the cable to the LED

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.

PriorityRunsWhy there
1An 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
2AO_Net and its two workersA TLS handshake or a four-second ping delays only the network
3The renderer (KODE_PRIO_USER_GUI): AO_Gui in kodeOSThe heaviest work and the least urgent. A slow repaint must not stutter the console
4AO_Radio and its workers, and the console reader taskBlocking pumps that sit below every AO with real work
5AO_Storage and storage_sdThe longest blocking I/O in the system, a slow card
6AO_LogAbove the reader, so a keystroke is echoed the moment it arrives. Below everything with a deadline
7 to 9AO_LedMgr, AO_Power, AO_AppMgrShort steps, or steps cut into chunks
10, 11AO_Autotest, AO_ConsoleThe console prints a row's record before the sequencer asks the next row
12AO_BootRarely runs. When it does, the board is about to reset
13 to 15AO_Buttons, AO_Touch, AO_DisplayShort polls and DMA kick-offs on the pixel path
16AO_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
17AO_IoExpOne I2C read and one publish per edge, the shortest step in the system
18 to 23AO_Ir, AO_Nfc, AO_Audio, AO_Imu, AO_Haptic, AO_RfidLevels above 16 exist because QF_MAX_ACTIVE is 25. Each step is short
24AO_LowPowerThe 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.

#StartsOptionThe constraint
1AO_LogalwaysFirst, so every later line goes through the owner of the console. The boot banner follows it
2AO_BootalwaysBefore AO_AppMgr: it reads the boot request that tells why the board started
3AO_LowPowerKODE_LOWPOWERThird, so every owner started after it can join the rounds of rest in its own initial transition
4AO_SchedulerKODE_SCHEDULERAfter 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()
5AO_StoragealwaysBefore AO_Radio and AO_AppMgr, which post to it
6AO_RadioKODEOS_RADIOLink-up is asynchronous, nothing below waits for the C5
7AO_NetKODEOS_NETAfter AO_Radio: it asks for the Wi-Fi state in its initial transition
8AO_LedMgralways
9 to 13AO_Ir, AO_Nfc, AO_Rfid, AO_Audio, AO_ImualwaysSelf-contained: nothing depends on them
14AO_HapticalwaysAfter AO_Imu: the haptic row posts to the IMU from its first step
15AO_AppMgrKODEOS_APPMGRAfter AO_Boot and AO_Storage
16AO_AutotestKODEOS_AUTOTESTBefore the console, its first client
17AO_ConsoleKODEOS_CONSOLE
18AO_Poweralways
19AO_IoExpalwaysBefore the buttons, the touch and the display, which post to it or subscribe to it
20, 21AO_Buttons, AO_Touchalways
22AO_DisplayalwaysLast: 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 ObjectResources it claims in its initial transition
AO_LogKBSP_RES_CONSOLE
AO_BootKBSP_RES_OTADATA, KBSP_RES_APPS_BOOTREQ
AO_StorageKBSP_RES_SDMMC, KBSP_RES_FATFS
AO_AppMgrKBSP_RES_APPS_REGION, KBSP_RES_APP_PENDING, KBSP_RES_OTA_SLOT
AO_PowerKBSP_RES_PMIC, KBSP_RES_GAUGE
AO_IoExpKBSP_RES_IOEXP: the whole TCA6408A and its INT line
AO_ButtonsKBSP_RES_BTN_TOP: GPIO35
AO_Touch, AO_LedMgrKBSP_RES_TOUCH, KBSP_RES_LED
AO_DisplayKBSP_RES_PANEL, and KBSP_RES_JPEG and KBSP_RES_PPA when KODEOS_MIRROR is on
AO_Audio, AO_Imu, AO_HapticKBSP_RES_AUDIO, KBSP_RES_IMU, KBSP_RES_HAPTIC
AO_Ir, AO_Nfc, AO_RfidKBSP_RES_IR, KBSP_RES_NFC, KBSP_RES_RFID
AO_RadioKBSP_RES_C5
AO_Net, AO_Autotest, AO_Console, AO_TestDotNone at boot. AO_TestDot takes J3 only when the cable wakes it
AO_LowPower, AO_SchedulerNone. 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

OptionDefaultWhat it gates
KODEOS_BOOT_ROLE_APP / _OSAppWhat AO_Boot does with otadata: an app never writes it (sdk/kaos/boot)
KODEOS_HOME_HOLDyHolding the bottom button resets an app home. App role only
KODEOS_CONSOLEyAO_Console. Selects KODEOS_APPMGR and KODEOS_AUTOTEST
KODEOS_APPMGRyAO_AppMgr, the writer of the apps region
KODEOS_AUTOTESTnAO_Autotest. The console selects it
KODEOS_RADIOyAO_Radio and the esp-hosted component
KODEOS_NETyAO_Net. Needs Wi-Fi
KODE_LOWPOWERn in the SDK, y in kodeOSAO_LowPower and the states AWAKE, ASLEEP and OFF. Depends on KODEOS_RADIO (sdk/kaos/lowpower)
KODE_SCHEDULERn in the SDK, y in kodeOSAO_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

FileWhat it does
include/kaos.hThe 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.hThe console's six error codes (kcli_err_t), defined once
include/kode_config.hThe settings registry: kode_config_t, KODE_CONFIG_ADD(), the 1 s answer bound
include/kode_variables.h, include/kode_functions.hThe tables an app registers so the console can read its variables and call its functions
src/kaos_claim.c, src/kaos_claim.hkaos_claim(): claim a resource or stop the boot naming both claimants. The header is private to kaos
src/kaos_wire.hThe base64 encoder and the slice sizes every record producer shares
src/kode_config.ckaos_config_add() and kode_config_answer(): the SDK's settings and an owner's half of one
src/kregistry.c, src/kregistry.hThe console-facing registry of variables, functions and settings. No QP, so the host tests run it
src/kset.hThe 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 ObjectOwns or doesPriorityPage
AO_Boototadata and the boot request: which firmware starts12sdk/kaos/boot
AO_LowPowerThe board's state: awake, asleep or off24sdk/kaos/lowpower
AO_SchedulerThe cron entries and the mqtt check16sdk/kaos/scheduler
AO_LogThe console device and the log6sdk/kaos/log
AO_ConsoleThe command line: parsing and dispatch11sdk/kaos/console
AO_StorageThe microSD card and the internal FAT5sdk/kaos/storage
AO_AppMgrThe apps region and the inactive kodeOS slot9sdk/kaos/appmgr
AO_AutotestThe self-test rows and the walk over them10sdk/kaos/autotest
AO_PowerThe BQ25896 charger and the CW2217 gauge8sdk/kaos/power
AO_ButtonsThe six buttons13sdk/kaos/buttons
AO_IoExpThe TCA6408A expander and its interrupt17sdk/kaos/ioexp
AO_DisplayThe AMOLED panel15sdk/kaos/display
AO_TouchThe touch controller14sdk/kaos/touch
AO_LedMgrThe RGB LED7sdk/kaos/led
AO_AudioThe ES8311 codec: speaker and microphone20sdk/kaos/audio
AO_HapticThe AW86233 and the motor22sdk/kaos/haptic
AO_ImuThe QMI8658A and the QMC5883P21sdk/kaos/imu
AO_NfcThe ST25R3916B reader19sdk/kaos/nfc
AO_RfidThe 125 kHz front end23sdk/kaos/rfid
AO_IrThe infrared block, served by the C518sdk/kaos/ir
AO_RadioThe ESP32-C5 link, Wi-Fi and Bluetooth4sdk/kaos/radio
AO_NetPing, HTTP, UDP, MQTT and the Kode channel2sdk/kaos/net
AO_TestDotThe E5 final test: its run, its station on J3 and its lock1sdk/kaos/test-dot

In the SDK