SDK

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, 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:

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:

WayGoes toUsed for
PostOne AO, by nameA request ("set the LED blue") and its answer
PublishEvery AO that subscribed to the signalNews ("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.

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

WordIn one sentenceMore
Active Object (AO)A task with its own queue and state machine, handling one event at a timesdk/kaos/overview
OwnerThe one AO that may use a chip; it claims the chip at bootsdk/design-rules
EventA message with a signal and a copy of its datasdk/events
SignalThe name of an event, such as BUTTON_EVT_SIGsdk/events
PostSend an event to one AOsdk/kqp
Publish, subscribeSend an event to every AO that asked for that signalsdk/kqp
State machineThe part of an AO that decides what an event means nowsdk/kqp
WorkerAn AO's helper task: runs one slow job, posts the result back, decides nothingsdk/kqp
FacadeThe kode_* calls an app makes; each becomes an eventsdk/kode
LayerOne component of the SDK; it uses the layers below it, never abovesdk/overview

How it is connected

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

LayerWhat it holdsPage
kdefsOne type, kode_status_t, that every public function returnssdk/kdefs
kbspThe board described as constants: pins, I2C addresses, screen size, the battery, who owns whatsdk/kbsp
klogThe log: macros, levels, and the path that still works inside a crashsdk/klog
khalWhat several drivers share: the I2C bus, the console bytes, time, NVS, PSRAMsdk/khal
kqp, qpcQP/C, the Active Object framework, and how this system uses it: pools, priorities, the tick. Every signal is listed in sdk/eventssdk/kqp
kdrvOne driver per chip, and the only layer that knows a part numbersdk/kdrv/overview
kaosThe Active Objects: one owner per device, each deciding when its device is usedsdk/kaos/overview
kodeThe facade: kode_init(), and every call an app makessdk/kode
kuiThe UI kit: colours, fonts, the pill and the sheet, and Kody's playersdk/kui
kscreenLVGL on the screen, for an Active Object of your ownsdk/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