kodeOS internals

Command set

Every verb of the kodeOS console, grouped by the Active Object that owns it, with its arguments, its answer and its limits.

What it is

This page lists every command of the console of a Kode Dot: its arguments, what it answers and how long the console waits for it. The grammar of a line and the shape of each answer are on internals/console-protocol. Commands that an app adds are on internals/variables-and-functions.

The board has one list of commands, the array CMDS[] near the end of console_ao.c. The parser reads it, help prints it and this page follows it. If this page and help ever differ, believe help: it prints the verbs of your build, and a Kconfig option can remove a group. The groups below follow the parts of the board. The order of help is the order of CMDS[].

Try these first:

How it is connected

flowchart LR
host["Host line<br/>cable or Bluetooth"]
subgraph board["On the Kode Dot"]
  con["AO_Console<br/>CMDS[]"]
  own["Owning AO"]
  dev["kdrv driver"]
  con -- "publish or post" --> own
  own --> dev
  own -. "answer event" .-> con
end
host -- "command" --> con
con -- "records, >ok" --> host
class host ext
class con,own ao
Where a verb goes

AO_Console parses and validates. It owns no device. A handler posts a request to the Active Object that owns the device (led_* to AO_LedMgr, app_* to AO_AppMgr, wifi_* to AO_Radio) and the owner answers by event.

A line comes from one of four sources: the cable (and the charging base), Bluetooth, the Kode channel and the scheduler. A line from the channel waits in a queue when the console is busy and is not answered busy (internals/kode-channel). A line from the scheduler is a cron entry running on its own account, and its replies go to the log. sdk/kaos/console has the policy of the sources.

A few bench verbs have no owner to ask and run inside the AO_Console step: i2c_scan, clocks, temp, display_info, time, exp, exp_help, pwd, the nvs_* verbs and the _help tables of the chips.

A Kconfig option can remove a verb. This table lists which option guards which verbs:

OptionVerbs it guards
KODE_LOWPOWERsleep, power_off and lowpower_info. In a build without it sleep and power_off answer >err code=4 msg=not_built, and lowpower_info does not exist
KODE_SCHEDULERcron. In a build without it every form answers >err code=4 msg=not_built
KODEOS_MIRRORThe mirror_* verbs
KODEOS_RADIOThe radio, identity and exp_power verbs
KODEOS_WIFIThe wifi_* verbs
KODEOS_BLEThe ble_* verbs
KODEOS_NETThe network verbs
KODEOS_MQTTThe MQTT verbs and channel_info
KODEOS_SCREENSHOTThe capture
KODE_TEST_DOTtest component=all and the test_dot_* verbs, the E5 final test. In a build without it test component=all answers >err code=5 msg=not_built and no test_dot_* verb exists

See internals/console-protocol for the in-flight rule and the watchdog classes.

These files hold the console:

FileWhat it holds
kaos/console/src/console_ao.cCMDS[], the key tables A_*, every handler and the replies of the owners
kaos/console/src/console_fs.c and console_fs.hThe working directory and the path commands: ls, cd, stat, cat, mkdir, rm, rmdir, mv, cp, write, file_put
kaos/console/src/console_grammar.cTokenizer and quoting
kaos/autotest/src/autotest_ao.cThe table TESTS[] that test walks

Specifications

Argument cells write the key, its type and whether it is required. Optional keys show their default after =. An enum lists its words after "one of". A record cell names the keys of each >| record without the prefix.

System and liveness

No Active Object owns these verbs. They answer from the AO_Console task.

VerbArgumentsAnswer
infononeRecords serial (with product and board), mac_p4 (carries the C5's MAC), one slot per kodeOS partition, coredump, c5 (when the radio is built in), log (with source stored or default) and temp, then >ok. A board that never met its C5 answers - for serial and MAC
helpnoneOne cmd record per verb with args, then >ok. Sliced
pingnoneThe record pong, then >ok
psnoneprio ao owns per Active Object and task, worker name stack free per worker, res owner per ownership claim, exp when a header position is in use, then heap int_free int_max int_low alloc_fail log_dropped rx_dropped. Sliced
tasksnonetask prio stack_free per live task, then tasks shown. The snapshot holds 32 tasks and the list 28, so a board with more than 32 shows none (shown=0)
hcfnoneNone. Fires Q_ERROR(), the board resets and leaves a core dump
i2c_scannoneaddr for each of the addresses 0x08 to 0x77 that answers, then found. Probes 8 addresses per step, 10 ms each

What the records say:

LED

Owner: AO_LedMgr. Each verb publishes one event and answers >ok at once.

VerbArgumentsAnswer
led_on, led_offnone>ok
led_togglenone>ok
led_blinkperiod uint 0 to 65535 ms, required>ok. Also turns the heartbeat on
led_hbstate bool, required>ok. Heartbeat on or off
led_rgbrgb rgb, required>ok

Storage media

Owner: AO_Storage. The sd_* and fs_* verbs differ only in the medium: the microSD card or the internal FAT partition.

VerbArgumentsAnswer
sd_mount, fs_mountnone>ok, an accept. The outcome is in ! lines
sd_unmount, fs_unmountnone>ok, an accept
sd_ls, fs_lsnonename size medium per file at the top of the medium, then >ok
sd_info, fs_infononemedium mounted, plus total_kb free_kb when readable, then >ok

The file tree

Owner: AO_Storage, through console_fs.c. These verbs take a path, absolute or relative to one working directory that AO_Console keeps (it starts at /, moves only on cd and is lost at reset). / exists only in the console and has two children, sdcard and storage.

VerbArgumentsAnswer
lspath string, optionalname type size medium per entry, then >ok
cdpath string, required>ok. Refuses a file with notdir
pwdnonepath, then >ok
statpath string, requiredpath type size medium, then >ok
catpath string, max uint 1 to 32768 default 4096line per line of the file, then >ok. Refuses a directory with isdir. Watchdog 30 s once the stat confirms a file
mkdirpath string, required>ok. One level only: noparent, exists, notdir, iofail
rmpath string, required>ok. Files only, isdir otherwise
rmdirpath string, required>ok. Empty directories only, notempty otherwise
mvsrc string, dst string, both required>ok. One volume only: path (code 3) across two, src for a typo
cpsrc string, dst string, both required>ok when the whole copy is written. Crosses volumes. Watchdog 60 s
writepath string, append bool default off, line uint 0 to 65535 default 0, text text, path and text required>ok. See below
file_putpath string, size uint, crc32 uint, all requiredThe record ready, the payload flow, then >ok

How write behaves:

Missing volumes answer volume. file_put answers code 3 msg=path for a path that names no volume and runs on the cable, the base and Bluetooth.

Files and payloads

Owners: AO_Storage for the files, AO_Display for the capture. The bulk answers are base64 d records, described on internals/console-protocol.

VerbArgumentsAnswer
file_getname string, requiredThe record file name size medium, base64 d records, then >ok. Searches the card first, then the internal partition. Not over the channel
file_descname string, requiredname project category display from the first bytes of a .bin, then >ok. A file with no manifest has category other
screenshotdst one of console, file, http default console. name string default shot.bmp. token string, fps uint 1 to 10, secs uint 1 to 300, stop bool, all for dst=http onlyConsole: shot w h fmt len, d records, >ok, refused over the channel. File: shot name bytes medium, >ok. Http: see below

screenshot freezes the shadow copy of the glass in AO_Display and takes the rectangles the panel received, so it captures LVGL and raw pixel writers alike. A black screen is not an error and logs a warning. The enum word http exists in a build with KODEOS_SHOT_HTTP.

dst=http uploads a JPEG over the Kode channel. token is required (1 to 128 characters of letters, digits, _ and -) and refused with another destination.

The refusals are in internals/kode-channel.

Apps

Owner: AO_AppMgr. See internals/apps-region and internals/boot-model.

VerbArgumentsAnswer
app_listnonename version size_kb category display per installed app, then >ok. Read in pages
app_runname string, required>ok, an accept, and the board resets. A .bin name is flashed and then run
app_erasename string, required>ok, an accept
app_catname string, set string, clear boolWith no keys, cat project cats per stored override. With name, exactly one of set or clear=on
installsrc string, dst one of os, app, c5, pending, run bool default off, size uint, crc32 uint, name stringSee below
appdata_lsnoneproject medium json icon app per project, then >ok. 1 s per row, up to 48 projects
appdata_infoname string, requiredproject medium, then field src value per field, then >ok
appdata_rmname string, required>ok after removing the internal folder. missing, busy or notempty otherwise. Never touches the card

install takes its image from src: fs: or sd: (the prefix is removed and the name is searched on the card first), http://, https://, pending (the mailbox) or terminal. terminal streams the image down the port the command arrived on and needs size and crc32; every other source refuses them with code 3. The destinations:

From a medium >ok is an accept. From the terminal it is a result. kcli.py install and kble.py install run the exchange for you.

app_run, install dst=os and hcf reset the board.

The app's variables, functions and settings

Four verbs and config exist in every build with a console. internals/variables-and-functions explains them.

VerbArgumentsAnswer
variable_listnonevariable watch desc per row, then >ok
function_listnonefunction arg desc per row, with min max or enum where they apply, then >ok
variablename string, required. watch uint 100 to 60000 ms, or 0variable value, then >ok. With watch, >ok first and then a variable record each period. 0 stops it. 4 watches at once, else slots
functionname string, required. arg row>ok. A row with answer_ms sends records first and then >ok or >err
configkey string, value rowSee settings below

Refusals of variable and function:

Settings

config with no keys lists every setting in registration order. With key it reads one. With key and value it sets one, and the answer is the record with the new value. A string setting is quoted and every other kind is bare. stored=yes means the key is in NVS and stored=no means the owner's default applies. The console never touches NVS: it sends a request per key and the owner answers within 1 s. The first request for a key is published; once the owner has answered, the console posts to it directly and keeps 4 slots of its queue free.

KeyOwnerType and valuesDefault
brightAO_Displayint 5 to 100100
wifi, bleAO_Radiobool. The wish that NVS stores, not the link stateThe build default
logAO_Logone of d, i, n, w, e, offThe build level
volAO_Audioint 0 to 100100
mic_dbAO_Audioint 0 to 42 in steps of 624
muteAO_Audiobooloff
mag_calAO_Imustring, the magnetometer's calibration "ox oy oz sx sy sz" (offsets in mG, scales in thousandths), "" to forget itnone
hapticAO_Hapticint 0 to 100, a scale on every buzz an app or haptic_play asks for100
led_activityAO_Powerbool, the blue pulse while a flash writeson
chargeAO_Powerboolon
charge_maAO_Powerint 64 to 896, read back on the chip's 64 mA grid512
wifi_joinAO_Radiobool, join a remembered network when the link comes upThe build default
wifi_psAO_Radiobool, Wi-Fi power saveon
chanAO_Netbool, join the Kode channelon
chan_keepAO_Netone of 1h, 6h, 1d, 3d, 7d, never1d
sleep_afterAO_LowPowerone of 30s, 1m, 2m, 5m, 10m, never2m
off_afterAO_LowPowerone of 1h, 6h, 24h, 3d, 7d, never1h
zoneAO_Bootstring, a valid POSIX TZ string of at most 32 characters"UTC0"
lockAO_Guione of 30s, 1m, 2m, 5m, 10m, never1m
sceneAO_Guistring, a scene name or auto"auto"
clockAO_Guione of 24h, 12h24h
kody_sleepAO_Guione of lock, 30s, 1m, 5m, 10m, neverlock
haptic_uiAO_Guione of off, light, normallight
usb_awakeAO_Guibool, a cable keeps the Lock out of Restoff

wifi_join, wifi_ps, chan and chan_keep exist only in a build with Wi-Fi or the channel, off_after only with KODE_LOWPOWER, sleep_after only with KODE_LOWPOWER_IDLE as well, and wifi and ble only with their radio service. mag_cal exists with the IMU's owner. kodeOS builds without KODE_LOWPOWER_IDLE and lists 24 rows.

The refusals:

In a listing, a silent owner gives value=- stored=- and the listing goes on. An app adds rows with KODE_CONFIG_ADD(), up to 32 rows in 4 tables.

Rules worth knowing:

Buttons, power and display

VerbArgumentsAnswer
btn_readnonebtn pressed per button, then >ok. Owner AO_Buttons. Code 5 if the expander does not answer
bat_infononevbat_mv vbus_mv vbus boost current_ma soc charging fault, then >ok. With no battery it times out with code 6
sleepnone>ok when the board is ASLEEP, or >err code=4 msg="<who>: <why>"
power_offnone>ok when the board is going OFF, or >err code=4 msg="<who>: <why>"
ship_modenone>ok. Ship mode. On USB the board cannot switch off
lowpower_infolast bool, optionalA lowpower record and a lowpower_stats record, then >ok. With last=on, a lowpower_last record and one lp_sample per sample, then >ok
booststate bool, requiredop=boost boost=on or off, then >ok. Refused with code 3 when USB is present, no reading exists, or the cell is low
pmic_resetnone>ok once REG_RST has cleared and the boot policy is written again

The brightness is config key="bright". The charger is charge and charge_ma. charge_limit is not a verb.

Scheduled commands

Owner: AO_Scheduler (sdk/kaos/scheduler). cron keeps the table of entries. An entry is a name, a time in the five fields of cron and a console line to run at that time. It exists with KODE_SCHEDULER.

VerbArgumentsAnswer
cronname string, when row, cmd string, all optionalOne cron record per entry (name when cmd owner next stored), then >ok; or one of the refusals below

when is a string like every other, with one bare word: an expression is quoted and off is bare. A quoted "off" and a bare expression are both refused, so each has one way to be typed.

cron                                          list every entry, mqtt first
cron name="riego"                             show one
cron name="riego" when="0 7 * * *" cmd="ping" add an entry, or change it
cron name="riego" when=off                    remove it
cron name="mqtt" when="*/5 * * * *"           change the built-in entry
cron name="mqtt" when=off                     switch the safety net off

The console refuses before it posts anything:

TypedAnswer
when= bare and not off, or when="off" quoted>err code=3 msg=when
when=off with a cmd=>err code=3 msg=cmd
cmd= that does not start with a verb of this build, or starts with cron>err code=3 msg=cmd
cmd= without when=>err code=2 msg=when
when= without name=>err code=2 msg=name
cron run by a SCHED line>err code=5 msg=sched
The request could not be posted>err code=5 msg=cron
AO_Scheduler never answered in 5 s>err code=6 msg=cron

AO_Scheduler refuses with one of these reasons, and the console turns it into the answer:

ReasonAnswerMeaning
when, fields, range, step, syntax, never>err code=3 msg=whenThe expression is not five fields, a value is out of range, a step is bad, the text is not cron, or no minute in four years matches. A notice says cron: the expression is refused: <reason>
name, unknown>err code=3 msg=nameThe name is empty, over 15 characters, or has a character outside A-Z a-z 0-9 _ - .; or no such entry exists
cmd>err code=3 msg=cmdThe line is empty, too long, or given to mqtt
full>err code=4 msg=fullThe table holds 16 entries, mqtt included
owner>err code=5 msg=ownerThe name belongs to an entry of code
partial>err code=5 msg=partialA list lost an entry because the console's queue was full. The records that arrived come first
memory>err code=5 msg=memoryThe table could not be taken from PSRAM
any other>err code=5 msg=cron

Removing a name that no console entry holds answers msg=name.

Chip fields

Five chips share one shape. pmic and gauge (AO_Power), ioexp (AO_IoExp), tp (AO_Touch) and sd (AO_Storage) read and write the fields of one chip. audio, haptic and c5 have the same shape in their own sections.

VerbArgumentsAnswer
pmic, gauge, ioexp, tp, sd, audio, haptic, c5field string, value uint 0 to 0x7FFFFFFFNo keys: a dump in ! lines, then >ok. field: the record chip field value. Both: writes, and the record carries what the chip kept
pmic_help, gauge_help, ioexp_help, tp_help, sd_help, audio_help, haptic_help, c5_helpnonefield unit min max rw desc per field, then >ok. No board is touched. Sliced
sd_verifyconfirm bool, requiredstep of sd for probe and format, then verify=sd result, then >ok. Erases the card. Watchdog 46 s

An unknown field is code 3 msg=field. value without field is code 2. A read-only field, or a value the field cannot hold, is code 3 with the field name. A chip that does not answer is code 5. Writes are read back, so ichg_ma set to 500 answers 512.

Notes per chip:

The field tables are on the pages of the drivers, for example sdk/kdrv/bq25896.

Clocks, time and temperature

No owner. These answer immediately.

VerbArgumentsAnswer
clocksnoneclk=x1, slow_mux, x3 (state ok with millihz ppm, or silent), x1_from_x3, rc_slow, cpu, apb, rtc_slow, rtc_fast, then >ok. Blocks the console for about 92 ms
tempnonetemp=p4 c=<degrees>, one decimal. c=- and code 5 when the sensor is silent
display_infononedisplay frames underruns dpi ack phy other st0 st1. Counters of the DSI link since the panel came up. underruns are frames PSRAM could not feed in time
timeset string, sync boolNo keys: time state utc source, state known, approx or unset, source rtc, set, sntp or saved. set="YYYY-MM-DDThh:mm:ssZ" sets UTC and refuses anything else and any date before 2026. sync=on asks a time server and answers "asked", not "got"

Audio

Owner: AO_Audio.

VerbArgumentsAnswer
audio_tonefreq uint 400 to 3400 default 1020, ms uint 10 to 2000 default 300, amp uint 1 to 50 default 25tone ms amp, then >ok at once. The frequency printed is the one snapped to a detector bin
audio_recms uint 100 to 30000 default 1000, file string, requiredrec ms bytes, then >ok. Writes a WAV to the path. Watchdog is ms plus 30 s

audio and audio_help are in the chip fields. dac_vol_db and adc_vol_db use the chip's dB scale where 0 is the loudest clean level. adc_scale_db and pga are two different microphone gains. reg32, reg34 and drc_en are read-only.

Infrared

Owner: AO_Ir. The block is on the ESP32-C5.

VerbArgumentsAnswer
irnoneFive ir records: tx, rx (with deaf), carrier hz=38000, level and last, then >ok
ir_helpnonefield desc per field, then >ok
ir_sendproto one of nec (the default), addr uint 0 to 0xFFFF default 0, cmd uint 0 to 0xFFFF, required>ok once queued, not once sent
ir_rxms uint 100 to 30000 default 5000ir=rx per frame heard, with nec addr cmd symbols, then >ok when the window ends

NFC

Owner: AO_Nfc, the ST25R3916B on the antenna board. The reader field is on only while a verb waits. nfc_field and nfc_emulate keep it off.

VerbArgumentsAnswer
nfcnoneFive nfc records: chip, field, emulate, counts, last, then >ok
nfc_helpnonefield unit min max step rw per field, then >ok
nfc_readms uint 100 to 10000 default 1500nfc=tag with uid atqa sak type model, then nfc=ndef len text when there is a message, then >ok. Code 4 with msg="no tag in the window" when empty
nfc_writeurl string, text text. Exactly onenfc=tag, then >ok. Code 2 for none or both
nfc_keysblock uint 0 to 255 default 0nfc=keys block result tried, then >ok. Tries 2038 public keys and takes minutes
nfc_dumpnonenfc=dump sectors blocks, then >ok. Sectors that no known key opens are skipped
nfc_fieldms uint 1 to 200, requirednfc min mean peak mod ext n, then >ok. Code 4 busy, code 5 absent or error
nfc_emulateuid, atqa, sak hex strings, uri string, text text. At least one of uid, uri, text>ok reads=0 when the window is up
nfc_emulate_offnone>ok reads=<n>. Code 4 when nothing emulates

uid has 4, 7 or 10 bytes, atqa two, sak one. A coherent uid atqa sak set clones a card. uri or text presents a Type-2 tag a phone reads.

When a tag is in the window but the exchange fails, nfc_read and nfc_write put the raw kode_status_t in code:

nfc_read and nfc_write try the twenty most common Mifare Classic keys. kodeOS reads cards with keys it holds and does not recover a key that is on no list.

RFID, 125 kHz

Owner: AO_Rfid. The carrier is on only while a read, a clone or a test window runs.

VerbArgumentsAnswer
rfidnonerfid=field up, rfid=last and rfid=counts, then >ok
rfid_helpnonefield desc per field, then >ok
rfid_readms uint 100 to 10000 default 2000rfid id rf, then >ok. id is five bytes of an EM4100 tag. Code 4 with msg="no tag in the window" when empty
rfid_writeid string, ten hex digits, colons optionalrfid=clone id match=yes, then >ok. Writes a T5577 and reads it back. Code 4 with msg="clone not verified" when the read-back differs
rfid_fieldms uint 1 to 150, requiredrfid min mean peak ac n in ADC counts, then >ok. Reads the demodulator with the carrier off

A job that holds the front end makes the others answer code 3 msg="rfid busy or not fitted" for a read or a clone, and code 4 busy for rfid_field. A board with no antenna board closes its windows empty, since the board cannot sense the antenna. id that is not ten hex digits is code 3 msg=id.

Haptic

Owner: AO_Haptic, the AW86233 and its LRA. haptic and haptic_help follow the chip fields.

VerbArgumentsAnswer
haptic_playms uint 1 to 2000 default 200, gain uint 0 to 100 default 50play=1 ms gain, then >ok at once. The buzz is a 235 Hz sine fed to the chip's RTP FIFO. Code 5 msg=busy during another buzz

The record states what was asked for, not what was felt. gain is a percent of full scale, clamped to the rating of the LRA.

The expansion header

Owner: whoever holds KBSP_RES_EXP, or nobody. The fourteen positions of J3 are served by khal_exp.

VerbArgumentsAnswer
exppos uint 1 to 14, mode one of free, in, out, adc, pwm, pull one of none, up, down default none, level uint 0 to 1 default 0, hz uint 1 to 40000 default 1000, duty uint 0 to 1000 default 500No keys: one exp record per position (mode free, in, out, pwm with level; adc with raw mv cal; lent with tag), then one exp_ctrl per controller an app may take, then >ok. With pos and mode: acts and answers >ok
exp_helpnoneexp gpio caps may lent per position, four syntax records, kodeos_ctrl, then >ok
exp_powerstate bool, required>ok. Switches +3.3V_EXT of J3, a pin of the C5. c5 field="pwr_ext" reads it back. Needs the radio

A write needs both pos and mode:

duty is in tenths of a percent. caps letters are d digital, m matrix, a adc, l low power. The tag of a lent position is clipped to 8 characters.

Injected input

No owner. These verbs publish the events that AO_Touch and AO_Buttons publish, so no receiver can tell them from a finger or a thumb. All answer >ok as accepts.

VerbArgumentsAnswer
touchx uint 0 to 409, y uint 0 to 501, state one of down, up, move, all required>ok. Code 4 while a tap is armed
tapx uint 0 to 409, y uint 0 to 501, both required>ok after the press. The release is published about 60 ms later. Code 4 while another gesture is armed
swipex0 y0 x1 y1 uint in the panel, ms uint 10 to 5000, hold uint 0 to 5000 default 0>ok. The console replays the path at the sensor's rate of one sample per 10 ms, rests hold ms, then lifts. Code 4 while a gesture is armed
btnid one of up, down, left, right, top, bot. state one of press, release, hold, double. Both required>ok

Coordinates outside the panel are refused, never clamped.

The mirror

Owner: AO_Display. Built with KODEOS_MIRROR. See internals/mirror.

VerbArgumentsAnswer
mirror_startscale uint 1 to 2 default 2, q uint 1 to 100 default 70>ok, then the m stream. The board lowers q above 70 to 70. Not over the channel
mirror_stopnone>ok. A frame in progress is cut with mabort
mirror_ackseq uint 0 to 65535, required>ok. Opens the credit window and wakes a paused session
mirror_infononemirror paused scale q window in_flight sent stalls, then >ok

GUI and boot

VerbArgumentsAnswer
gui_infononeA record with state lvgl pills installed files writing, a gui record with state sel level group sheet kody lock_s stack_free, a gesture record with drag taps claims commits shorts diagonal axis zone blocked, then >ok. Owner AO_Gui. Code 6 means AO_Gui is stuck
boot_infononerunning addr size_kb otadata reason, then >ok. Owner AO_Boot
factory_resetconfirm bool, requiredCable only, and only in the kodeOS role. AO_Boot removes every app but the welcome app, wipes the C5's saved networks and keys and this chip's bonds, and erases every NVS namespace but the identity and the station lock. It then writes the welcome app's boot request and restarts the board. The answer is the record factory_reset result=done and >ok just before the restart. A step that fails answers the record factory_reset step=<name> status=<n> and >err code=5 msg=<name>, and the board stays up. >err code=4 msg=busy while an E5 run goes. The watchdog is 290 s

What the gui record says:

The NVS store

No owner. A value is read by the next app through kode_nvs_get(). Values sit in plain flash.

VerbArgumentsAnswer
nvs_setkey string, value string, both required>ok. Code 3 for a key of 16 characters or more or a value too long
nvs_getkey string, requiredkey value, or key state=unset, then >ok
nvs_delkey string, required>ok, also when the key was absent
nvs_listnonekey per stored name, up to 24, then >ok

The radio and the identity

Owner: AO_Radio. Built with KODEOS_RADIO.

VerbArgumentsAnswer
radio_upnone>ok, an accept. Starting the link resets the C5 and takes about 10 s
radio_downnone>ok, an accept. The link ends and the C5 goes into reset when no service is wanted. Does not clear the wifi and ble flags
radio_infononelink fw sd, link off, starting, up, stopping or failed, then >ok
id_signnonce string, 1 to 200 characters of letters, digits, ., _, -sig der="<base64>", then >ok. The C5 signs kode-auth/v1, a newline and the nonce with ECDSA P-256 over SHA-256
id_certnoneid serial, id part of der in parts of 144 bytes, id unit_len len, then >ok
ident_csrnonecsr part of der per part, then >ok. Creates the key when none exists. Factory only
ident_certpart uint 1 to 11, of uint 1 to 11, der string, all required>ok. Parts in order, the unit's certificate first. Factory only
ident_lockconfirm bool, required>ok. Closes ident_csr and ident_cert for good. Factory only
c5_wipeconfirm bool, required>ok. Forgets the Wi-Fi networks and BLE keys of the user, then the bonds of the P4. The identity stays

The identity verbs answer code 5 identity with no key or certificate, locked after ident_lock, and refused with the link down, another request in flight or no answer in 3 s.

ident_csr, ident_cert, ident_lock and c5_wipe refuse Bluetooth with msg=cable. confirm=on is required for the last two: without it the answer is code 3 msg=confirm and nothing happens. c5_wipe stops at the first step that fails with vault, btkeys or ble.

install dst=c5 verifies the image before it asks the C5 for anything: CRC, container digest, ESP header and chip id, so an image for the P4 is refused by name. Its >ok means the C5 took and activated the image. The update ends with the link off, so run radio_up and read c5. The C5 has no rollback: a bad image leaves it unreachable.

Wi-Fi

Owner: AO_Radio. Built with KODEOS_WIFI. Most verbs hand their deadline to the owner. The state record is wifi (idle, connecting, got_ip or disconnected) with ssid ip rssi en, where en is the stored flag and not the association.

VerbArgumentsAnswer
wifi_upnoneState record, then >ok. Stores the wish, starts the link, joins a remembered network when wifi_join is on
wifi_downnoneState record, then >ok. Clears the flag and stops the station
wifi_scannonessid rssi ch auth bssid per network as the sweep finds it, then >ok. The sweep takes about 38 s
wifi_joinssid string, pass string, remember bool default onState record, then >ok. Working credentials go to the vault in the C5. remember=off keeps nothing
wifi_autononeState record, then >ok. Scans and joins the strongest remembered network
wifi_listnonessid per remembered network, most recent first, then >ok. Names only
wifi_forgetssid string, required>ok. Removes the network from the vault, does not disconnect
wifi_leavenoneState record, then >ok. The network stays remembered
wifi_infononeState record, then >ok

Bluetooth

Owner: AO_Radio. Built with KODEOS_BLE. See internals/ble-console.

VerbArgumentsAnswer
ble_upnoneble addr, then >ok. Stores the wish, starts NimBLE and advertises
ble_downnone>ok. Stops advertising and disconnects. Does not stop NimBLE. Over Bluetooth the >ok never arrives
ble_scanms uint 500 to 30000 default 5000addr rssi name type mfg svc per device, then >ok. mfg is hex with the company id first, svc the 16-bit service UUIDs
ble_infononeble addr console tx_drop bonds backlog retry hidden pair, then >ok
ble_unpairnoneState record, then >ok. Does nothing while a console is attached, and logs a warning
ble_pairnoneState record, then >ok. Pair new device: a bonded board is visible to new devices for 5 minutes or until one pairs. With Bluetooth off nothing changes and the record reads pair=off

Network and MQTT

Owner: AO_Net. KODEOS_NET builds the network verbs and KODEOS_MQTT the MQTT verbs. lwIP runs on the P4.

VerbArgumentsAnswer
net_pinghost string, count uint 1 to 10 default 4seq time_ms ttl per reply, seq timeout=on per loss, then ip tx rx min_ms avg_ms max_ms, then >ok
net_infononeip gw mask dns mqtt rx_dropped, then >ok
http_geturl string, requiredbody records of printable text in slices of 191 bytes, up to 8, then status content_len body_len shown truncated, then >ok. A 404 is >ok
udp_sendhost string, port uint 1 to 65535, data string, all requiredsent, then >ok
mqtt_upuri string, user string, pass stringClosed by the state record. mqtt:// or mqtts://host[:port]. The host is a name of letters, digits, -, _ and . that neither starts nor ends with . or -, or an IPv6 address in brackets. Any other URI is >err code=3 msg=uri. With no network it answers >err code=5 msg=no_net
mqtt_downnoneClosed by the state record
mqtt_pubtopic string, data string, both required>ok at QoS 0, written to the socket at once. data is limited by a 240-byte line. With no network, >err code=5 msg=no_net
mqtt_sub, mqtt_unsubtopic string, required>ok. Messages arrive as ! lines. With no network, >err code=5 msg=no_net
mqtt_infononemqtt uri why, then >ok. State down, connecting or up. why none, no_net, refused or timeout
channel_infononechannel serial broker since_s why next_s lost fast shot sent fps_got left_s keep session, then >ok. State off, no_net, no_identity, joining, up, waiting or refused. keep is the session age in seconds that the live connect asked for, never or -; session is resumed, new or -

The five mqtt_* verbs answer code 4 busy while the Kode channel holds the one MQTT client. mqtt_down waits for the esp-mqtt task and has no console deadline. net_ping is the ICMP verb because ping is the liveness check. The ceiling of count is the ceiling of the listing, since every echo is an event. rx_dropped of net_info counts the MQTT messages the driver dropped since boot because its receive ring was full. It is not the rx_dropped of ps, which counts console input bytes that were lost. See internals/kode-channel for the fields of channel_info.

Logging

VerbArgumentsAnswer
log_leveltag string, level one of the log levels d, i, n, w, e, off>ok. Sets one tag for this boot. * and all are code 3 msg=tag. Not stored
log_colorstate bool, required>ok. ANSI colour on or off

The global level is config key="log". It is stored, and setting it back to the build level erases the key. Answers are never filtered.

Self-tests

test runs one row or a group. test_report prints the report that the last whole user run kept in NVS.

VerbArgumentsAnswer
testcomponent one of the rows below or a group (all, main, user), default allFor a row, main and user: test result per row with result pass, fail, timeout or skipped, then summary pass fail timeout, with skipped when a row was skipped, then >ok. Watchdog is the budget of each row plus 5 s. For all, the E5 final test, see below
test_reportnonerow result per row, then when rows problems, then >ok. report=none when no whole user run exists. when is UTC seconds, 0 for an unset clock

A timeout is a row nobody judged. fail counts every row that did not pass, timeouts included. A skipped row found nothing to judge, such as a slot with no card. Rows run one at a time in the order of TESTS[]. A verdict block of log lines with the tag kcli comes before the summary.

The groups:

GroupRows
allThe E5 final test, run by AO_TestDot on a station build: sd, imu, the camera's checks of the glass and the LED, the operator's buttons and touch, then audio, haptic, mag, pmic, gauge, ioexp, clocks, ir, nfc, rfid and mem. Cable only. See below
mainThe rows a main board answers alone: pmic, gauge, ioexp, clocks, imu, mag
userThe owner's profile that the Self-test page runs: ioexp, clocks, imu, mag, haptic, sd_user, c5_user, ir, nfc_field, rfid_field, audio, then hold=continue, then power_plug, buttons, led, display, tp, nfc_card, rfid_card, psram, mem

Every row also runs by name. Some run only by name: screenshot, jpeg (with the mirror), fs, boot, apps, kfw, id, ble_console and net (with their options), c5, psram, and the user rows sd_user, c5_user, nfc_field, rfid_field, power_plug, nfc_card and rfid_card.

The rows that need a person:

The criteria of each row are on internals/production-test.

The E5 final test

test component=all is the final test of a finished Kode Dot on the E5 station. A station computer starts it over the cable, and the Kode Dot runs the rows, the camera's checks of the glass and the LED, and the operator's presses and touches. Then the computer decides the verdict. AO_Console hands the line to AO_TestDot, which answers the records on the open line and closes it with >ok when the camera part ends. The rest arrives through test_dot_poll.

all is refused in this order: not_built, the source (cable, net or sched), locked, busy, then the fixture: camera, then station_i2c. On a Kode Dot without the station, test therefore ends in >err code=4 msg=camera and leaves the glass, the radio and every owner as they were.

The verbs of the test are cable only, and the station's computer is their only client. Their keys, records and refusals are on internals/e5-wire-contract.

VerbWhat it does
test_dot_placeLooks for the four marks under the camera
test_dot_pollReads what the run said since its line closed
test_dot_screenPuts the connecting or the result screen on the glass
test_dot_photos, test_dot_blobSend the kept photos and pictures as blobs
test_dot_relays, test_dot_adcDrive the station's relays and read its ADC
test_dot_loopSends frames round the dock's pads
test_dot_radioChecks the C5 and two networks and a BLE name
test_dot_boostSwitches the pads delivery test's boost
test_dot_resultTells the Kode Dot the verdict. A pass runs the factory reset
test_dot_lockLocks the station verbs for good
test_dot_endGives back everything the test took
test_dot_infoSays where AO_TestDot is
test_dot_cameraRuns the camera part alone

The owner is AO_TestDot (sdk/kaos/test-dot). The console's factory_reset is refused as busy while a run goes.

Gotchas

Example code

Send commands from a terminal with tools/kcli.py. It prints the records and checks the terminator.

    1. Connect the Kode Dot over the USB-C cable and load ESP-IDF in your terminal, which provides pyserial.
    2. Run python3 tools/kcli.py -p PORT --settle 5 cmd 'ls path="/"'. It prints one record for sdcard, one for storage and then ok.
    3. Run python3 tools/kcli.py -p PORT cmd 'led_rgb rgb=0,0,200'. The LED turns blue.
    4. Run python3 tools/kcli.py -p PORT cmd 'config key="bright" value=40'. The record shows value=40 stored=yes and the glass dims.
    5. For a verb that works for a while, add --gap 10 after cmd so kcli.py keeps listening for the ! lines of the outcome.

The verbs that carry a payload (install with src="terminal", file_put, file_get, screenshot) have their own kcli.py subcommands: install, file-put, file-get and screenshot -o shot.png. Do not send them with cmd.

In the SDK