# Overview

How the Kode Dot's firmware is built: the layers of the SDK, what each one holds, and which way they talk to each other.

## What it is

The SDK is the code every Kode Dot firmware is built from: kodeOS, the
examples, and your own app. It lives in one repository,
[`kodediy/kode-sdk`](https://github.com/kodediy/kode-sdk), as a set of
ESP-IDF components. You add it to a project, call `kode_init()`, and the
whole system runs inside your image: the drivers, the owners of every chip,
the console, the radio link.

Each component is a **layer**. A layer uses the layers under it and never one
above it, so a change to the screen code cannot break a driver, and a
firmware with no screen can leave the screen layers out.

The build files enforce the order. Each component names in `REQUIRES` the
layers it may use, and a script, `tools/check_layers.sh`, checks every
`#include` against the rules before every merge.

kodeOS uses the SDK the same way your app does. It has no private door into
it. Its own code, the screens and the launcher, sits above the SDK in the
kodeOS repository.

## The ideas behind it

The whole SDK is built on a few ideas. Every other page uses their names,
so they are explained here once, in plain words.

### Active Objects own the chips

An **Active Object**, or **AO**, is a small program inside the firmware with
three things of its own:

- a **task**, so it runs by itself, side by side with the others;
- a **queue** where messages for it wait their turn;
- a **state machine**, which decides what to do with a message from the
  state the AO is in. The same button press can mean "unlock" on one screen
  and "go back" on another.

An AO takes one message from its queue, handles it from start to finish, and
only then takes the next. Nothing else touches its data halfway, so that data
needs no lock.

Every chip on the board has **one owner**, and the owner is an AO: AO_Power
owns the charger, AO_LedMgr the LED, AO_Storage the microSD card. Nobody else
touches that chip. Whoever wants something from it sends the owner a message.
Two owners for one chip stop the board at boot, naming both.

### Events are the messages

A message between AOs is an **event**. It has a name, its **signal** (for
example `BUTTON_EVT_SIG`), and it may carry data: a copy, not a pointer into
someone else's memory (two events too big to copy are the exceptions,
listed in `sdk/design-rules`). An event travels one of two ways:

| Way | Goes to | Used for |
| --- | --- | --- |
| **Post** | One AO, by name | A request ("set the LED blue") and its answer |
| **Publish** | Every AO that **subscribed** to the signal | News ("a button was pressed", "the battery changed") |

The sender never waits: it leaves the event in a queue and carries on. The
SDK is built on QP/C, a framework for Active Objects, which moves the events
and runs the state machines.

### Workers do the waiting

Some jobs make you wait: a page from the network, a file on the card, a chip
that is slow to answer. An AO must never wait, because while it waits it
reads no messages, and its queue fills up.

So an AO that has slow jobs gets a **worker**: a helper task of its own that
runs one job at a time and posts the result back to its AO as an event. The
worker decides nothing. Which job comes next, how long to wait, whether to
try again: the AO decides all of it, in its state machine.

```mermaid
sequenceDiagram
  autonumber
  participant app as Your app
  participant ao as The owner AO
  participant wk as Its worker
  participant chip as The chip or the network
  app->>ao: request (post)
  ao->>wk: one job
  wk->>chip: ask, then wait
  chip-->>wk: data
  wk-->>ao: done event (post)
  ao-->>app: answer (post or publish)
  Note over ao: keeps reading its queue while the worker waits
```

*One slow request: the AO decides, the worker waits*

### Your app makes calls, the facade sends events

You do not have to send events yourself. Your app calls plain functions such
as `kode_led_rgb()`, and the **facade**, the `kode` layer, turns each call
into the right event for the right owner. An app needs Active Objects of its
own only when it wants to react to events, and the examples show how.

### The words, in one table

| Word | In one sentence | More |
| --- | --- | --- |
| Active Object (AO) | A task with its own queue and state machine, handling one event at a time | `sdk/kaos/overview` |
| Owner | The one AO that may use a chip; it claims the chip at boot | `sdk/design-rules` |
| Event | A message with a signal and a copy of its data | `sdk/events` |
| Signal | The name of an event, such as `BUTTON_EVT_SIG` | `sdk/events` |
| Post | Send an event to one AO | `sdk/kqp` |
| Publish, subscribe | Send an event to every AO that asked for that signal | `sdk/kqp` |
| State machine | The part of an AO that decides what an event means now | `sdk/kqp` |
| Worker | An AO's helper task: runs one slow job, posts the result back, decides nothing | `sdk/kqp` |
| Facade | The `kode_*` calls an app makes; each becomes an event | `sdk/kode` |
| Layer | One component of the SDK; it uses the layers below it, never above | `sdk/overview` |

## How it is connected

```mermaid
flowchart TB
  app["Your app<br/>or kodeOS"]
  subgraph sdk["The SDK"]
    kode["kode<br/>the facade"]
    kui["kui + kscreen<br/>the UI kit"]
    kaos["kaos<br/>the Active Objects"]
    kdrv["kdrv<br/>the chip drivers"]
    kqp["kqp<br/>QP/C in this system"]
    qpc["qpc<br/>QP/C itself"]
    khal["khal<br/>shared services"]
    klog["klog<br/>the log"]
    kbsp["kbsp<br/>the board"]
    kdefs["kdefs<br/>kode_status_t"]
  end
  app --> kode
  app --> kui
  kode --> kaos
  kaos --> kdrv
  kaos --> kqp
  kdrv --> khal
  kqp --> qpc
  khal --> klog
  khal --> kbsp
  khal --> kdefs
  class app app
  class kaos ao
```

*The layers, top to bottom*

Two kinds of arrow run between the layers:

- A **function call** goes down: the caller waits for the answer.
- An **event** is a message left in a queue, and the sender does not wait.

A command goes down like this:

1. Your app calls `kode_led_rgb()`.
2. The facade turns that into an event for **AO_LedMgr**, the Active Object that owns the LED.
3. AO_LedMgr calls the LED driver.

What happens comes back up the same way. An Active Object publishes a button
press, and any Active Object of yours that subscribed receives it.

Only four layers may include ESP-IDF headers (`esp_*`, `driver/`,
`freertos/`): kbsp, khal, kdrv and klog. Everything above them sees the
hardware through Kode's own types, and every public function returns
`kode_status_t`.

The radio chip, the ESP32-C5, runs a firmware of its own. Its source is a
second repository, `kodediy/kode_slave_c5`, included in the SDK as the
submodule `c5/`.

## The layers

One row per layer, with what it holds and the page that documents it.

| Layer | What it holds | Page |
| --- | --- | --- |
| kdefs | One type, `kode_status_t`, that every public function returns | `sdk/kdefs` |
| kbsp | The board described as constants: pins, I2C addresses, screen size, the battery, who owns what | `sdk/kbsp` |
| klog | The log: macros, levels, and the path that still works inside a crash | `sdk/klog` |
| khal | What several drivers share: the I2C bus, the console bytes, time, NVS, PSRAM | `sdk/khal` |
| kqp, qpc | QP/C, the Active Object framework, and how this system uses it: pools, priorities, the tick. Every signal is listed in `sdk/events` | `sdk/kqp` |
| kdrv | One driver per chip, and the only layer that knows a part number | `sdk/kdrv/overview` |
| kaos | The Active Objects: one owner per device, each deciding when its device is used | `sdk/kaos/overview` |
| kode | The facade: `kode_init()`, and every call an app makes | `sdk/kode` |
| kui | The UI kit: colours, fonts, the pill and the sheet, and Kody's player | `sdk/kui` |
| kscreen | LVGL on the screen, for an Active Object of your own | `sdk/kscreen` |

Two folders in the SDK are other people's code, kept under their own names:
`qpc`, Quantum Leaps' QP/C, and `rfal`, ST's library for the NFC chip. Only
kqp includes the first, and only kdrv the second.

The rules that keep the layers apart, and the checks that enforce them, are
in `sdk/design-rules`.

## Where a new file goes

Ask in order. The first yes is the answer.

1. It defines a pin or an I2C address: **kbsp**.
2. Several drivers share it, or it could have a second implementation: **khal**.
3. It is the code for one chip: **kdrv**.
4. It owns a device or decides when something happens: **kaos**.
5. It is what an app calls: **kode**.
6. It is a program on top of the system: a component of its own, above the SDK.

A type goes with the layer that defines it. A type no layer owns goes in
kdefs, which is why kdefs exists at all.

## In the SDK

- [`components/`](https://github.com/kodediy/kode-sdk/tree/main/components): one folder per layer.
- [`tools/check_layers.sh`](https://github.com/kodediy/kode-sdk/blob/main/tools/check_layers.sh): the include rules, as a script.
- [`examples/`](https://github.com/kodediy/kode-sdk/tree/main/examples): small apps, one Active Object per thing they do.
