# Variables and functions

Add your own verbs to the console: a variable a host can read or follow, a function a host can run, and a setting the board stores.

## What it is

An app is a whole firmware and carries the console with it, but the console knows only the verbs of kodeOS. Variables and functions let an app add verbs of its own, with the same grammar, the same `help` and the same errors as every other verb.

- A **variable** is a value the app shows, such as a temperature. The verb `variable` reads it once or follows it. It never runs anything.
- A **function** is something the app does, and it can change a value. The verb `function` runs it. It never reads anything.

A value a host can both see and change is a variable plus a function under two names, such as `gain` and `set_gain`. A third kind of row, a **setting**, is a value the board stores, owned by one Active Object and read and changed with `config`.

## How it is connected

```mermaid
flowchart LR
  host["host line"] --> con["AO_Console"]
  subgraph kodeos["In kodeOS"]
    con --> reg["kregistry"]
  end
  subgraph yours["In your app"]
    getter["your getter"]
    run["your run callback"]
    ao["your AO"]
    run -- "post event" --> ao
  end
  reg -- "get" --> getter
  reg -- "run" --> run
  ao -. "function_record,<br/>function_end" .-> con
  class host ext
  class con ao
  class getter,run,ao app
```

*From a verb to your callback*

The app declares tables of rows. The registry keeps each table by pointer, so a table must be `static const`. The console turns the rows into `variable`, `function`, `variable_list` and `function_list`, and a handler looks a row up by name and calls its callback.

**Every callback runs on the task of AO_Console, inside one step.** Return promptly and never block: no `kode_delay_ms()`, no `kode_net_*` call that waits for an answer, no `kode_fs_*`, no `kode_imu_read()`. A blocked callback stops the console and every verb after it, and the watchdog does not catch it, since the command is synchronous.

- A function that takes time answers `KODE_OK` as an accept, raises a flag or posts to the Active Object of the app, and tells the outcome through a variable or in records.
- A reading that is slow to fetch is sampled by an Active Object or a worker at the rate the part likes, and the getter only spells the last sample.
- A getter can read one aligned 32-bit value another task writes, because the P4 loads it in one instruction.

A function that changes state the app's Active Object owns should post an event instead of writing the state, so two tasks never own one number. The example `console_actions` does it that way: `reset` posts a static event, `add` posts a pooled event, and the Active Object applies both in a step of its own.

| File | What it holds |
| --- | --- |
| `kaos/core/include/kode_variables.h` | `kode_variable_t`, `KODE_VARIABLES_ADD()` and the limits |
| `kaos/core/include/kode_functions.h` | `kode_function_t`, `kode_value_t`, `KODE_FUNCTIONS_ADD()` |
| `kaos/core/include/kode_config.h` | `kode_config_t` and `KODE_CONFIG_ADD()` for settings |
| `kaos/core/src/kregistry.c` | The private registry: validation, lookup, parsing and spelling of both kinds |
| `kaos/console/src/console_ao.c` | `A_VARIABLE`, `A_FUNCTION`, `h_variable`, `h_function`, the list verbs and the four watch slots |
| `examples/console_actions` | A counter Active Object with a variable and two functions |

All three headers are included by `kode.h`.

## Specifications

### The verbs

| Verb | What it does | The answer |
| --- | --- | --- |
| `variable_list` | Lists the variables | `variable watch desc` per row in registration order, then `>ok`. `watch` is the period in ms, or 0 |
| `function_list` | Lists the functions | `function arg desc` per row. `arg` is none, int, bool, enum or string, with `min max` or `enum=a,b` where they apply |
| `variable name="<row>"` | Reads one variable | `variable value`, then `>ok` |
| `variable name="<row>" watch=<ms>` | Follows it | `>ok` at once, then the same `variable` record every period. `watch=0` stops it and answers `>ok` for any row |
| `function name="<row>" [arg=<value>]` | Runs one function | `>ok` and nothing else. A row with `answer_ms` answers in records first |

The names and the `desc` of a record are quoted. The lists go out in slices of 12 records, like `help`.

### Rows

| Thing | Value |
| --- | --- |
| Name | `[a-z0-9_]`, 1 to 15 characters (`KODE_ROW_NAME_MAX`), unique over every variable and function |
| Description | At most 48 characters (`KODE_ROW_DESC_MAX`). NULL prints `-` |
| Caps | Variables: 32 rows (`KODE_VARIABLES_MAX`) in 4 tables. Functions: 64 rows (`KODE_FUNCTIONS_MAX`) in 4 tables. Settings: 32 rows in 4 tables |
| Registration | After `kode_init()`. A table with one bad row is refused whole and a log line names the row. The result is `KODE_EXISTS` for a taken name, `KODE_NO_RESOURCES` past a cap and `KODE_DENIED` for any other rule, and always in a build without `CONFIG_KODEOS_CONSOLE` |
| Value word | A getter writes one word of at most 32 characters (`KODE_VALUE_STR_MAX`), bare. Whitespace, control characters and quotes go out as `_`. An empty or longer word is refused, never cut |
| Getter result | `KODE_OK`. Any other status refuses the read and ends a watch |
| Watch | 4 slots (`KODE_VARIABLE_WATCH_MAX`). Period 100 to 60000 ms. A busy port skips a sample. A failing getter ends the watch with one warning |
| Scaling | No float and no unsigned type. Decimals are scaled integers with the unit in the name, such as `temp_dc` or `vbat_mv`. A percentage is an int from 0 |

### Function arguments

The form on the wire follows the type: a string is quoted (`arg="hello there"`) and every other kind is bare (`arg=-5`, `arg=on`, `arg=fast`). The other form answers code 3 `msg=arg`.

| `kode_type_t` | Wire form | Notes |
| --- | --- | --- |
| `KODE_TYPE_NONE` | no `arg=` | `run` receives NULL |
| `KODE_TYPE_INT` | signed decimal | `min` and `max` bound it when they differ. The console refuses a value outside them before `run` is called |
| `KODE_TYPE_BOOL` | `on` or `off` | |
| `KODE_TYPE_ENUM` | one of up to 6 words (`KODE_VALUE_ENUMS_MAX`) | Each word is a valid name. The index travels to `run` |
| `KODE_TYPE_STRING` | quoted text, 1 to 256 characters (`KODE_ARG_STR_MAX`) | Valid for the call only: copy what you keep. The line itself is 240 bytes, so the longest argument the console can carry is shorter |

`run` gets NULL when the line has no `arg=`. It returns `KODE_OK` to accept, `KODE_DENIED` to refuse the argument or its absence (the console answers code 3 `msg=arg`), or any other status for the failure of the row (code 5 with the row name). Nothing runs on a parse failure. The console refuses `value=` and `watch=` on a function, `arg=` and `value=` on a variable, and a name of the other kind, all with code 3.

### A function that answers in records

A row with `answer_ms` set (at most 600000, ten minutes) keeps the line open. `run` still returns at once. Then the Active Object that got the request says what came of it. A `!` log line reaches the cable and Bluetooth and never the Kode channel, so an outcome a remote host must see is a record.

| Call | What it does |
| --- | --- |
| `function_ticket()` | Called by `run`. Returns the number of this run. Hand it to the Active Object you post to |
| `function_record(ticket, record)` | Sends one record, without the greater-than and bar prefix. Returns false for NULL, for a record of `KODE_FUNCTION_REC_MAX` (192) characters or more, or when the queue is full. A record is never cut |
| `function_quote()` | Writes a text the way every record does |
| `function_end(ticket, status, msg)` | Ends the function. `KODE_OK` closes with `>ok`, `KODE_DENIED` with code 3 `msg=arg`, anything else with code 5 and `msg`, or the function name when `msg` is NULL. False leaves the line open, so post it again |

The console compares the ticket with the run in flight and drops every record or end that carries another one, so a late answer cannot close the next function's line. While the line is open every other command answers `busy`. When the owner never ends the function the console closes it with code 6 after `answer_ms`. The records go to the port the command came from, so the cable, Bluetooth and the channel read the same lines. These calls are in `kaos.h`.

### The functions of kodeOS

kodeOS registers one table the same way, from kgui: seven `assets_` functions for Kody's pack, no variables. Each answers in `>|assets` records and then `>ok` or `>err`.

| Function | Argument | Job |
| --- | --- | --- |
| `assets_info` | none | Source, scenes and roles of the pack |
| `assets_check` | enum sd, fat | Checks the pack on that medium, activates nothing |
| `assets_reload` | none | Reads the pack again |
| `assets_begin` | int 1 to 2147483647 | Stages an install of this many bytes |
| `assets_commit` | none | Checks the staged pack and makes it active. Holds the console up to 120 s |
| `assets_update` | string, a URL or none | Fetches the pack. Holds the console up to 240 s |
| `assets_try` | string | Plays one file of the pack on the face |

### Settings

An app registers settings with `KODE_CONFIG_ADD()` and answers each request from its own Active Object with `kode_config_answer()`. The value is typed like a function argument, and a key is `[a-z0-9_]` up to 15 characters. The rows appear in `config` after the ones of kodeOS. `internals/command-set` lists the nineteen settings of kodeOS.

## Gotchas

> **WARNING**
>
> **A callback that blocks stops the console.** Every verb after it waits, and the 5 s watchdog does not fire because the command is synchronous. Move the waiting to your Active Object.

> **WARNING**
>
> **A table on the stack registers without an error and then reads garbage.** The registry keeps a pointer. Declare every table `static const`.

> **WARNING**
>
> **A row with `answer_ms` that nobody ends holds the console.** The line closes with code 6 only after `answer_ms`, and every other command answers `busy` until then. Call `function_end()` on every path, the refusals included.

> **NOTE**
>
> **One bad row costs the whole table, and `>ok` from a function means accepted.** A description of 49 characters refuses every row beside it. And an Active Object below the console applies the event after the console has gone quiet, so read the variable to see the outcome.

## Example code

The variable `count` shows a counter and the function `add` changes it. After flashing the app, type `function name="add" arg=5` and then `variable name="count"` on the console: the answer reads 5. The callbacks run on the console task and so do both users of the counter, so no lock is needed.

```c
/*
 * console_counter.c - 2026-10-28 - from Kode with love
 *
 * Adds a variable "count" and a function "add" to the console. Type
 * function name="add" arg=5, then variable name="count": the answer reads 5.
 */
#include <stdio.h>
#include "kode.h"

static int32_t s_count; /* only the console task touches it */

/* Spell the counter as the one word the console sends. */
static kode_status_t get_count(char *buf, size_t cap)
{
    (void)snprintf(buf, cap, "%ld", (long)s_count);
    return KODE_OK;
}

/* Add the argument to the counter; a line with no arg= is refused. */
static kode_status_t run_add(const kode_value_t *arg)
{
    if (arg == NULL) { return KODE_DENIED; }
    s_count += arg->i;
    return KODE_OK;
}

static const kode_variable_t VARS[] = {
    { .name = "count", .get = get_count, .desc = "The counter" } };
static const kode_function_t FUNCS[] = {
    { .name = "add", .arg = KODE_TYPE_INT, .min = -100, .max = 100,
      .run = run_add, .desc = "Add arg to the counter" } };

/* Bring the system up, register both tables, keep the app alive. */
void app_main(void)
{
    kode_init();
    (void)KODE_VARIABLES_ADD(VARS);
    (void)KODE_FUNCTIONS_ADD(FUNCS);
    while (1) { kode_delay_ms(1000); }
}
```

Compiled against the SDK with ESP-IDF v6.1 on 2026-10-28. **TBD**: a run on a Kode Dot.

## In the SDK

- [`components/kaos/core/include`](https://github.com/kodediy/kode-sdk/tree/main/components/kaos/core/include): `kode_variables.h`, `kode_functions.h` and `kode_config.h`.
- [`examples/console_actions`](https://github.com/kodediy/kode-sdk/tree/main/examples/console_actions): a counter Active Object with a variable and two functions, the LED showing its parity.
- [`console_ao.c`](https://github.com/kodediy/kode-sdk/blob/main/components/kaos/console/src/console_ao.c): the handlers `h_variable` and `h_function`.
