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:
helpprints every verb with its arguments.pinganswerspong, the liveness check.infosays what the board is and what runs on it.pslists the Active Objects and the memory.led_rgb rgb=0,0,200sets the LED to blue.bat_infoshows the battery.ls path="/"lists the files.screenshottakes a picture of the glass.configlists every stored setting.
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
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:
| Option | Verbs it guards |
|---|---|
KODE_LOWPOWER | sleep, 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_SCHEDULER | cron. In a build without it every form answers >err code=4 msg=not_built |
KODEOS_MIRROR | The mirror_* verbs |
KODEOS_RADIO | The radio, identity and exp_power verbs |
KODEOS_WIFI | The wifi_* verbs |
KODEOS_BLE | The ble_* verbs |
KODEOS_NET | The network verbs |
KODEOS_MQTT | The MQTT verbs and channel_info |
KODEOS_SCREENSHOT | The capture |
KODE_TEST_DOT | test 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:
| File | What it holds |
|---|---|
kaos/console/src/console_ao.c | CMDS[], the key tables A_*, every handler and the replies of the owners |
kaos/console/src/console_fs.c and console_fs.h | The working directory and the path commands: ls, cd, stat, cat, mkdir, rm, rmdir, mv, cp, write, file_put |
kaos/console/src/console_grammar.c | Tokenizer and quoting |
kaos/autotest/src/autotest_ao.c | The 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.
| Verb | Arguments | Answer |
|---|---|---|
info | none | Records 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 |
help | none | One cmd record per verb with args, then >ok. Sliced |
ping | none | The record pong, then >ok |
ps | none | prio 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 |
tasks | none | task 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) |
hcf | none | None. Fires Q_ERROR(), the board resets and leaves a core dump |
i2c_scan | none | addr for each of the addresses 0x08 to 0x77 that answers, then found. Probes 8 addresses per step, 10 ms each |
What the records say:
slotcarriesstateas running, inactive, empty or absent.coredumpreads none, or present withownerkodeos or other, and the task and PC when known.recogin thec5record says what the P4 recognises on the C5: kode, stock, old or nolink.- In
ps,int_lowis the lowest the internal pool has been since boot and the number to judge a build by. alloc_failcounts refused allocations,log_droppedcounts console lines the event pool could not carry andrx_droppedcounts bytes lost on input.
LED
Owner: AO_LedMgr. Each verb publishes one event and answers >ok at once.
| Verb | Arguments | Answer |
|---|---|---|
led_on, led_off | none | >ok |
led_toggle | none | >ok |
led_blink | period uint 0 to 65535 ms, required | >ok. Also turns the heartbeat on |
led_hb | state bool, required | >ok. Heartbeat on or off |
led_rgb | rgb 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.
| Verb | Arguments | Answer |
|---|---|---|
sd_mount, fs_mount | none | >ok, an accept. The outcome is in ! lines |
sd_unmount, fs_unmount | none | >ok, an accept |
sd_ls, fs_ls | none | name size medium per file at the top of the medium, then >ok |
sd_info, fs_info | none | medium 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.
| Verb | Arguments | Answer |
|---|---|---|
ls | path string, optional | name type size medium per entry, then >ok |
cd | path string, required | >ok. Refuses a file with notdir |
pwd | none | path, then >ok |
stat | path string, required | path type size medium, then >ok |
cat | path string, max uint 1 to 32768 default 4096 | line per line of the file, then >ok. Refuses a directory with isdir. Watchdog 30 s once the stat confirms a file |
mkdir | path string, required | >ok. One level only: noparent, exists, notdir, iofail |
rm | path string, required | >ok. Files only, isdir otherwise |
rmdir | path string, required | >ok. Empty directories only, notempty otherwise |
mv | src string, dst string, both required | >ok. One volume only: path (code 3) across two, src for a typo |
cp | src string, dst string, both required | >ok when the whole copy is written. Crosses volumes. Watchdog 60 s |
write | path string, append bool default off, line uint 0 to 65535 default 0, text text, path and text required | >ok. See below |
file_put | path string, size uint, crc32 uint, all required | The record ready, the payload flow, then >ok |
How write behaves:
line=0writes the whole file, truncating it or appending withappend=on.line=<n>replaces linenthrough a temporary file and three renames. It answersnoline(code 3) for a line the file lacks andtoolargewhen the file or the result passes 32 KB.append=onwith a non-zerolineanswersappendlineat once.text=""writes an empty file.- A
\ninside the quotes writes a newline.
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.
| Verb | Arguments | Answer |
|---|---|---|
file_get | name string, required | The record file name size medium, base64 d records, then >ok. Searches the card first, then the internal partition. Not over the channel |
file_desc | name string, required | name project category display from the first bytes of a .bin, then >ok. A file with no manifest has category other |
screenshot | dst 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 only | Console: 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.
- With none of
fps,secs, the answer isscreenshot dst=http bytes id seq. - With either, a stream starts and the answer is
screenshot dst=http mode=stream fps secsat once, with defaults of 2 frames a second and 60 s. stop=onends a stream and takes no other key.
The refusals are in internals/kode-channel.
Apps
Owner: AO_AppMgr. See internals/apps-region and internals/boot-model.
| Verb | Arguments | Answer |
|---|---|---|
app_list | none | name version size_kb category display per installed app, then >ok. Read in pages |
app_run | name string, required | >ok, an accept, and the board resets. A .bin name is flashed and then run |
app_erase | name string, required | >ok, an accept |
app_cat | name string, set string, clear bool | With no keys, cat project cats per stored override. With name, exactly one of set or clear=on |
install | src string, dst one of os, app, c5, pending, run bool default off, size uint, crc32 uint, name string | See below |
appdata_ls | none | project medium json icon app per project, then >ok. 1 s per row, up to 48 projects |
appdata_info | name string, required | project medium, then field src value per field, then >ok |
appdata_rm | name 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:
oswrites the inactive kodeOS slot, switches to it and resets. The new image has to confirm itself or the board returns to the old one.appwrites the apps region. It runs the app only withrun=on.runis refused with any other destination. From the terminal,nameis the image's project name: the region makes room before the image arrives, and withnamethe old copy of that app goes first instead of the oldest app. Each app the install evicts prints>|evicted name="<project>" kept=offbefore>|ready.nameis refused with code 3 outsidesrc="terminal" dst=app.c5updates the ESP32-C5 firmware. It runs from the cable and the base only.pendingparks the image undecoded in the mailbox.src="pending" dst=pendingis refused.
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.
| Verb | Arguments | Answer |
|---|---|---|
variable_list | none | variable watch desc per row, then >ok |
function_list | none | function arg desc per row, with min max or enum where they apply, then >ok |
variable | name string, required. watch uint 100 to 60000 ms, or 0 | variable value, then >ok. With watch, >ok first and then a variable record each period. 0 stops it. 4 watches at once, else slots |
function | name string, required. arg row | >ok. A row with answer_ms sends records first and then >ok or >err |
config | key string, value row | See settings below |
Refusals of variable and function:
- Unknown row,
value=orwatch=on a function, andarg=on a variable are code 3. - A watch period from 1 to 99 is code 3 with
msg=watch. - An
argin the wrong form, out of range or refused by the row is code 3 withmsg=arg. - A getter or
runthat fails is code 5 with the row name.
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.
| Key | Owner | Type and values | Default |
|---|---|---|---|
bright | AO_Display | int 5 to 100 | 100 |
wifi, ble | AO_Radio | bool. The wish that NVS stores, not the link state | The build default |
log | AO_Log | one of d, i, n, w, e, off | The build level |
vol | AO_Audio | int 0 to 100 | 100 |
mic_db | AO_Audio | int 0 to 42 in steps of 6 | 24 |
mute | AO_Audio | bool | off |
mag_cal | AO_Imu | string, the magnetometer's calibration "ox oy oz sx sy sz" (offsets in mG, scales in thousandths), "" to forget it | none |
haptic | AO_Haptic | int 0 to 100, a scale on every buzz an app or haptic_play asks for | 100 |
led_activity | AO_Power | bool, the blue pulse while a flash writes | on |
charge | AO_Power | bool | on |
charge_ma | AO_Power | int 64 to 896, read back on the chip's 64 mA grid | 512 |
wifi_join | AO_Radio | bool, join a remembered network when the link comes up | The build default |
wifi_ps | AO_Radio | bool, Wi-Fi power save | on |
chan | AO_Net | bool, join the Kode channel | on |
chan_keep | AO_Net | one of 1h, 6h, 1d, 3d, 7d, never | 1d |
sleep_after | AO_LowPower | one of 30s, 1m, 2m, 5m, 10m, never | 2m |
off_after | AO_LowPower | one of 1h, 6h, 24h, 3d, 7d, never | 1h |
zone | AO_Boot | string, a valid POSIX TZ string of at most 32 characters | "UTC0" |
lock | AO_Gui | one of 30s, 1m, 2m, 5m, 10m, never | 1m |
scene | AO_Gui | string, a scene name or auto | "auto" |
clock | AO_Gui | one of 24h, 12h | 24h |
kody_sleep | AO_Gui | one of lock, 30s, 1m, 5m, 10m, never | lock |
haptic_ui | AO_Gui | one of off, light, normal | light |
usb_awake | AO_Gui | bool, a cable keeps the Lock out of Rest | off |
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:
- Unknown key: code 3
msg=key. valuewithoutkey: code 2.- A wrong form or range: code 3
msg=value. - A value the owner refuses: code 5
msg=value. Azonethat is not a POSIX TZ string is one of these. - An owner whose queue has fewer than 4 free slots: code 4
msg=busy. Nothing is sent and nothing changes. - An owner that does not answer in 1 s: code 6.
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:
ble=onmeans advertise from boot.chargeandcharge_mareplace the boot policy of the charger.chan=offleaves the channel at once.chan_keepis how long the broker keeps the channel's session while the board is away, so how long the commands sent meanwhile wait. A set is stored at once and used at the next connect, a renewal included.channel_infoshows the live age askeep=.sleep_afteris the time without input before the board goes from AWAKE to ASLEEP, andoff_afterthe time ASLEEP before OFF. A button, a touch or a console line from any port restarts the count, andnevercounts nothing.sleep_afterdoes not depend onlockorkody_sleep.scene=autoerases the chosen scene.kody_sleep=lockerases its key.
Buttons, power and display
| Verb | Arguments | Answer |
|---|---|---|
btn_read | none | btn pressed per button, then >ok. Owner AO_Buttons. Code 5 if the expander does not answer |
bat_info | none | vbat_mv vbus_mv vbus boost current_ma soc charging fault, then >ok. With no battery it times out with code 6 |
sleep | none | >ok when the board is ASLEEP, or >err code=4 msg="<who>: <why>" |
power_off | none | >ok when the board is going OFF, or >err code=4 msg="<who>: <why>" |
ship_mode | none | >ok. Ship mode. On USB the board cannot switch off |
lowpower_info | last bool, optional | A lowpower record and a lowpower_stats record, then >ok. With last=on, a lowpower_last record and one lp_sample per sample, then >ok |
boost | state bool, required | op=boost boost=on or off, then >ok. Refused with code 3 when USB is present, no reading exists, or the cell is low |
pmic_reset | none | >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.
sleeptakes the board to ASLEEP: light sleep with the radios kept up and the time on the dimmed glass. AO_LowPower asks every owner to put its device at rest, and>okcomes when the state has changed. A refusal is quoted:cable: external powerwhile VBUS is present, or the name of the owner that is busy, for examplestorage: file write. A busy or silent owner is not final. The request is begun again every second for 10 s and the verb is answered once, at the end. A cable is refused at once. The verb works from every port, and a line on Bluetooth or the Kode channel wakes a board that sleeps. The console waits up to 17 s for the answer.power_offtakes the board to OFF: deep sleep with the C5 held in reset.>okcomes when the state has changed, a moment before the P4 stops. It is refused for the same reasons assleep. Only the bottom button wakes the board, and the wake is a boot.ship_modeputs the board into ship mode and is the only door to it: no app and no other verb reaches it. On battery nothing could answer afterwards. To switch the board on again, press the bottom button, which is wired to the QON pin of the charger chip. On USB the board cannot switch off.lowpower_infoprints>|lowpower state=awake|asleep|off for_s= cause= sleep_after= off_after= wakes_bot= wakes_ble= wakes_mqtt= wakes_timer= last_busy=and>|lowpower_stats slept_pct= sleeps= wakes_c5= wakes_tx= tx_sta= tx_serial= tx_hci= knocks_max= deaf=. The cause isrequest,idle,off_after,bot,ble,mqtt,cable,timerorboot. The two times are seconds and 0 is never.wakes_bot,wakes_bleandwakes_mqttcount the wakes from ASLEEP by the bottom button, a Bluetooth line and the Kode channel, andwakes_timerthe wakes for an owner's time (the radio wakes, in which the Kode channel works with the glass dark).last_busyis the last refusal, quoted, or-. Thetx_fields are-until a wake has been noted.slept_pctis the share of the time since boot spent in light sleep.lowpower_info last=onprints the summary the board keeps of its last rest, so a run on battery can be read back on the cable:>|lowpower_last start= for_s= slept_pct= sleeps= wakes_c5= wakes_tx= deaf= end= samples=(startin Unix seconds,-when the clock was not set;endis a cause word, orasleepwhen nothing ended it), then one>|lp_sample t= ma= mv=per minute, oldest first: the seconds from the start, the cell current in mA (negative out of the cell) and the cell voltage in mV. It goes out in slices likehelp. A board with no summary prints>|lowpower_last present=no.
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.
| Verb | Arguments | Answer |
|---|---|---|
cron | name string, when row, cmd string, all optional | One 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 records come in the order of the table,
mqttfirst, then>ok. A set answers with the entry as it now is, then>ok. A removal, andmqttswitched off, answer>okalone. ownerisconsole,codeorbuiltin.nextis the next minute as Unix seconds in UTC, or-when the clock is not set or the entry is off.cmdis-for an entry with no line (an entry of code, andmqtt).storedsays whether the entry survives a reset. A switched-offmqttlists withwhen="off"andnext=-, and a time given to it switches it on again.- The time is the UTC calendar of the epoch clock in five fields: minute, hour, day of month, month, day of week. Each takes
*, numbers, lists (1,15), ranges (9-17) and steps (*/5,9-17/2). The least period is one minute. - The line runs with the source SCHED when the minute comes. It must start with a verb this build has, other than
cron, or the entry is refused when it is made. The payload verbs are accepted then and refused only when they run (>err code=5 msg=schedin the log). If the console is in the middle of another command at that minute, AO_Scheduler gives the line again every second, for up to 10 s from the start of the minute's work; past that it drops the line with a warning,the entry '<name>' did not run: the work of its minute ran out of time, and the entry runs again at its next time. Its replies go to the log as notices,sched: <reply>.config key="log" value=whides them. A record whose line does not fit one console line with the other fields is cut, after acmd: cut to fit one linewarning. The entry keeps the whole line. - From the Kode channel
cronis accepted like every other verb, and the entry it leaves is stored (internals/kode-channel). mqtttakes nocmd. Its work is the look at the sessions of the Kode channel in a radio wake.
The console refuses before it posts anything:
| Typed | Answer |
|---|---|
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:
| Reason | Answer | Meaning |
|---|---|---|
when, fields, range, step, syntax, never | >err code=3 msg=when | The 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=name | The 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=cmd | The line is empty, too long, or given to mqtt |
full | >err code=4 msg=full | The table holds 16 entries, mqtt included |
owner | >err code=5 msg=owner | The name belongs to an entry of code |
partial | >err code=5 msg=partial | A list lost an entry because the console's queue was full. The records that arrived come first |
memory | >err code=5 msg=memory | The 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.
| Verb | Arguments | Answer |
|---|---|---|
pmic, gauge, ioexp, tp, sd, audio, haptic, c5 | field string, value uint 0 to 0x7FFFFFFF | No 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_help | none | field unit min max rw desc per field, then >ok. No board is touched. Sliced |
sd_verify | confirm bool, required | step 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:
pmicrefusesbatfet_dis(ship mode isship_mode) andreg_rst(usepmic_reset).gaugevalues are unsigned.ioexpwrites toc7oro7change the supply of the panel.tpwithdis_auto_sleepset to 0 puts the controller to sleep until a finger touches the glass or the board resets.sdfields from the card are read-only.width_req(1 or 4) andfreq_req_khz(400 to 50000) set the next mount.c5has no writable row and answers from the snapshot of the last link start.audioreads the register back, sincemic_dbrounds.
The field tables are on the pages of the drivers, for example sdk/kdrv/bq25896.
Clocks, time and temperature
No owner. These answer immediately.
| Verb | Arguments | Answer |
|---|---|---|
clocks | none | clk=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 |
temp | none | temp=p4 c=<degrees>, one decimal. c=- and code 5 when the sensor is silent |
display_info | none | display 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 |
time | set string, sync bool | No 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.
| Verb | Arguments | Answer |
|---|---|---|
audio_tone | freq uint 400 to 3400 default 1020, ms uint 10 to 2000 default 300, amp uint 1 to 50 default 25 | tone ms amp, then >ok at once. The frequency printed is the one snapped to a detector bin |
audio_rec | ms uint 100 to 30000 default 1000, file string, required | rec 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.
| Verb | Arguments | Answer |
|---|---|---|
ir | none | Five ir records: tx, rx (with deaf), carrier hz=38000, level and last, then >ok |
ir_help | none | field desc per field, then >ok |
ir_send | proto 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_rx | ms uint 100 to 30000 default 5000 | ir=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.
| Verb | Arguments | Answer |
|---|---|---|
nfc | none | Five nfc records: chip, field, emulate, counts, last, then >ok |
nfc_help | none | field unit min max step rw per field, then >ok |
nfc_read | ms uint 100 to 10000 default 1500 | nfc=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_write | url string, text text. Exactly one | nfc=tag, then >ok. Code 2 for none or both |
nfc_keys | block uint 0 to 255 default 0 | nfc=keys block result tried, then >ok. Tries 2038 public keys and takes minutes |
nfc_dump | none | nfc=dump sectors blocks, then >ok. Sectors that no known key opens are skipped |
nfc_field | ms uint 1 to 200, required | nfc min mean peak mod ext n, then >ok. Code 4 busy, code 5 absent or error |
nfc_emulate | uid, 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_off | none | >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:
- 3: no key opened it.
- 1: no NDEF message.
- 2: the tag stopped answering.
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.
| Verb | Arguments | Answer |
|---|---|---|
rfid | none | rfid=field up, rfid=last and rfid=counts, then >ok |
rfid_help | none | field desc per field, then >ok |
rfid_read | ms uint 100 to 10000 default 2000 | rfid id rf, then >ok. id is five bytes of an EM4100 tag. Code 4 with msg="no tag in the window" when empty |
rfid_write | id string, ten hex digits, colons optional | rfid=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_field | ms uint 1 to 150, required | rfid 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.
| Verb | Arguments | Answer |
|---|---|---|
haptic_play | ms uint 1 to 2000 default 200, gain uint 0 to 100 default 50 | play=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.
| Verb | Arguments | Answer |
|---|---|---|
exp | pos 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 500 | No 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_help | none | exp gpio caps may lent per position, four syntax records, kodeos_ctrl, then >ok |
exp_power | state 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:
- Any key without
posis code 2msg=pos. poswithoutmodeis code 2msg=mode.- The first write claims
KBSP_RES_EXPfor "console (bench)" until the next boot. - If an app holds the header, every write is code 5
msg=owned, while reads still work. - A request
khal_exprefuses is code 5msg=exp.
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.
| Verb | Arguments | Answer |
|---|---|---|
touch | x 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 |
tap | x 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 |
swipe | x0 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 |
btn | id 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.
btnwithholdis fortopandbotonly, anddoublefortoponly. Other ids answer code 3msg=id.- An injected press never starts the 600 ms hold clock of AO_Buttons, so a host that wants a held thumb sends press, hold, release. The hold of
botis the HOME signal of AO_Boot. pressandreleasego through the same door as a real button, sokode_buttons_read()follows them.
The mirror
Owner: AO_Display. Built with KODEOS_MIRROR. See internals/mirror.
| Verb | Arguments | Answer |
|---|---|---|
mirror_start | scale 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_stop | none | >ok. A frame in progress is cut with mabort |
mirror_ack | seq uint 0 to 65535, required | >ok. Opens the credit window and wakes a paused session |
mirror_info | none | mirror paused scale q window in_flight sent stalls, then >ok |
GUI and boot
| Verb | Arguments | Answer |
|---|---|---|
gui_info | none | A 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_info | none | running addr size_kb otadata reason, then >ok. Owner AO_Boot |
factory_reset | confirm bool, required | Cable 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:
selis the selection of the launcher in quotes, or-for none: the project name of an installed app, the file name of a file, and/<group>for a group.levelisrootorgroup, andgroupis the name of the open group in quotes, or-at the root.sheetis the card that is up, or-.stateisfacewhile the Lock is awake andrestat Rest, andreturningwhile the first frame after an app waits for the list.kodyisplaying,paused,asleepornone.lock_sis the auto-lock time in seconds, ornever.stack_freeis the bytes of AO_Gui's stack that were never used.- The
gesturerecord hasdrag(idle,pending,followorsettle) and counters since boot that each stop at 65535: the presses that ended as a tap, the drags the recognizer claimed, the followed drags that landed (commits) and those released short (shorts), and the presses it rejected by reason (diagonal,axis,zone,blocked). - The record reaches the Kode channel as it reaches the cable. The log lines that
gui_infoalso writes do not. One of those lines starts withui:and gives the interface settings and the buzzes asked and held back (internals/gui/buttons-and-gestures).
The NVS store
No owner. A value is read by the next app through kode_nvs_get(). Values sit in plain flash.
| Verb | Arguments | Answer |
|---|---|---|
nvs_set | key string, value string, both required | >ok. Code 3 for a key of 16 characters or more or a value too long |
nvs_get | key string, required | key value, or key state=unset, then >ok |
nvs_del | key string, required | >ok, also when the key was absent |
nvs_list | none | key per stored name, up to 24, then >ok |
The radio and the identity
Owner: AO_Radio. Built with KODEOS_RADIO.
| Verb | Arguments | Answer |
|---|---|---|
radio_up | none | >ok, an accept. Starting the link resets the C5 and takes about 10 s |
radio_down | none | >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_info | none | link fw sd, link off, starting, up, stopping or failed, then >ok |
id_sign | nonce 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_cert | none | id serial, id part of der in parts of 144 bytes, id unit_len len, then >ok |
ident_csr | none | csr part of der per part, then >ok. Creates the key when none exists. Factory only |
ident_cert | part 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_lock | confirm bool, required | >ok. Closes ident_csr and ident_cert for good. Factory only |
c5_wipe | confirm 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.
| Verb | Arguments | Answer |
|---|---|---|
wifi_up | none | State record, then >ok. Stores the wish, starts the link, joins a remembered network when wifi_join is on |
wifi_down | none | State record, then >ok. Clears the flag and stops the station |
wifi_scan | none | ssid rssi ch auth bssid per network as the sweep finds it, then >ok. The sweep takes about 38 s |
wifi_join | ssid string, pass string, remember bool default on | State record, then >ok. Working credentials go to the vault in the C5. remember=off keeps nothing |
wifi_auto | none | State record, then >ok. Scans and joins the strongest remembered network |
wifi_list | none | ssid per remembered network, most recent first, then >ok. Names only |
wifi_forget | ssid string, required | >ok. Removes the network from the vault, does not disconnect |
wifi_leave | none | State record, then >ok. The network stays remembered |
wifi_info | none | State record, then >ok |
Bluetooth
Owner: AO_Radio. Built with KODEOS_BLE. See internals/ble-console.
| Verb | Arguments | Answer |
|---|---|---|
ble_up | none | ble addr, then >ok. Stores the wish, starts NimBLE and advertises |
ble_down | none | >ok. Stops advertising and disconnects. Does not stop NimBLE. Over Bluetooth the >ok never arrives |
ble_scan | ms uint 500 to 30000 default 5000 | addr rssi name type mfg svc per device, then >ok. mfg is hex with the company id first, svc the 16-bit service UUIDs |
ble_info | none | ble addr console tx_drop bonds backlog retry hidden pair, then >ok |
ble_unpair | none | State record, then >ok. Does nothing while a console is attached, and logs a warning |
ble_pair | none | State 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.
| Verb | Arguments | Answer |
|---|---|---|
net_ping | host string, count uint 1 to 10 default 4 | seq time_ms ttl per reply, seq timeout=on per loss, then ip tx rx min_ms avg_ms max_ms, then >ok |
net_info | none | ip gw mask dns mqtt rx_dropped, then >ok |
http_get | url string, required | body 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_send | host string, port uint 1 to 65535, data string, all required | sent, then >ok |
mqtt_up | uri string, user string, pass string | Closed 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_down | none | Closed by the state record |
mqtt_pub | topic 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_unsub | topic string, required | >ok. Messages arrive as ! lines. With no network, >err code=5 msg=no_net |
mqtt_info | none | mqtt uri why, then >ok. State down, connecting or up. why none, no_net, refused or timeout |
channel_info | none | channel 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
| Verb | Arguments | Answer |
|---|---|---|
log_level | tag 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_color | state 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.
- At
nthe console shows answers, notices, warnings and errors. - At
offit shows answers only. - The guided tests print their prompts at notice level, so
ehides them on the console and not on the glass. - A line that the build removed with
CONFIG_KLOG_MAXIMUM_LEVELcannot come back.
Self-tests
test runs one row or a group. test_report prints the report that the last whole user run kept in NVS.
| Verb | Arguments | Answer |
|---|---|---|
test | component one of the rows below or a group (all, main, user), default all | For 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_report | none | row 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:
| Group | Rows |
|---|---|
all | The 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 |
main | The rows a main board answers alone: pmic, gauge, ioexp, clocks, imu, mag |
user | The 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:
buttons: six prompts, 20 s each, a press let go within 3 s.led(three colours) anddisplay(five fields): TOP is yes and BOT is no.tp: two targets, 64 px tolerance, 20 s each.
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.
| Verb | What it does |
|---|---|
test_dot_place | Looks for the four marks under the camera |
test_dot_poll | Reads what the run said since its line closed |
test_dot_screen | Puts the connecting or the result screen on the glass |
test_dot_photos, test_dot_blob | Send the kept photos and pictures as blobs |
test_dot_relays, test_dot_adc | Drive the station's relays and read its ADC |
test_dot_loop | Sends frames round the dock's pads |
test_dot_radio | Checks the C5 and two networks and a BLE name |
test_dot_boost | Switches the pads delivery test's boost |
test_dot_result | Tells the Kode Dot the verdict. A pass runs the factory reset |
test_dot_lock | Locks the station verbs for good |
test_dot_end | Gives back everything the test took |
test_dot_info | Says where AO_TestDot is |
test_dot_camera | Runs 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.
- Connect the Kode Dot over the USB-C cable and load ESP-IDF in your terminal, which provides pyserial.
- Run
python3 tools/kcli.py -p PORT --settle 5 cmd 'ls path="/"'. It prints one record forsdcard, one forstorageand thenok. - Run
python3 tools/kcli.py -p PORT cmd 'led_rgb rgb=0,0,200'. The LED turns blue. - Run
python3 tools/kcli.py -p PORT cmd 'config key="bright" value=40'. The record showsvalue=40 stored=yesand the glass dims. - For a verb that works for a while, add
--gap 10aftercmdsokcli.pykeeps 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
components/kaos/console:console_ao.choldsCMDS[]and every handler.components/kaos/autotest: theTESTS[]table behindtest.components/kaos/test_dot: AO_TestDot and thetest_dot_*verbs.tools/kcli.py: the reference host program.