# 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

```mermaid
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`.

| 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

> **WARNING**
>
> **The board boot-loops with `assert failed: xQueueGenericSend queue.c:936`.** Something posted to an AO that has no queue yet, usually from `app_main()` before `kode_init()` returned. Call `kode_init()` first, then subscribe, then ask the owner for the state you want.

> **WARNING**
>
> **A step that waits stalls every priority beneath it.** `kode_delay_ms()`, the blocking network calls and the blocking sensor reads of the facade are for `app_main()` and tasks of your own. Inside an AO, post the request and handle the answer as an event.

> **NOTE**
>
> **`kodeOS: … claimed by both …` is the table doing its job.** Two AOs of yours, or one of yours and one of the SDK, claimed the same resource. Name your own resources in your own code, and never claim one that appears in the table above. If the message never shows and the board hangs silently, a debugger left running on the board can be the cause.

## 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`](https://github.com/kodediy/kode-sdk/tree/main/components/kaos): one folder per Active Object, plus `core/`.
- [`components/kaos/core`](https://github.com/kodediy/kode-sdk/tree/main/components/kaos/core): `kaos.h`, the claim, and the registry of settings.
- [`components/kbsp/owners`](https://github.com/kodediy/kode-sdk/tree/main/components/kbsp/owners): the table of owners `kaos_claim()` writes to.
