kodeOS internals

Sheets and cards

The full-glass card kodeOS reuses for every page, the system cards and their shared frame, the keypad, the details of an app and a launch.

What it is

A sheet is a card that covers the whole glass. kodeOS builds one and writes a different page on it each time. The pages are:

A sheet has at most one button, such as Remove, Try again or Run again. BOT closes it, and so does a finger that goes down on the header of a card. The screen writes BOT for the bottom button. The write screen and the Removing card stay until their work ends. A list of choices and the keypad close back to the page that opened them. A settings page goes back one level with BOT or a swipe right. A system card takes no drag, except Turn off, which a swipe down closes; BOT alone closes the others, or answers no where the card asks a question.

Some words name the marks and colours of a sheet:

An answer has a role: primary, secondary or tertiary. The ink of a text is Soft Black or Light Grey, whichever reads on its ground.

How it is connected

flowchart TB
trig["a row, a hold, a write,<br/>a pairing, the system"] -- "gui_sheet_open_kind()" --> sig["GUI_SHEET_OPEN_SIG"]
subgraph gui["AO_Gui"]
  face["Gui_awake"] --> sf["Gui_sheet_face"]
  brow["Gui_browsing"] --> sh["Gui_sheet_home"]
  set["Gui_settings"] --> ss["Gui_sheet_settings"]
end
sig --> face
sig --> brow
sig --> set
sf --> card["the one sheet<br/>on lv_layer_top()"]
sh --> card
ss --> card
class sf ao
class sh ao
class ss ao
How a card gets onto the glass

One object, built once

ui_build() calls kui_sheet_create() on lv_layer_top(), the layer LVGL keeps above every screen, so one card covers the face, Apps and Settings without any of them knowing. The sheet is built at startup because a card created while a Bluetooth peer waits would spend the security manager's clock on an allocation.

Only one card exists at a time. When another page is wanted while a card is up, such as a pairing during a write or a zone list from the Time zone row, the same card is refilled (gui_sheet_fill_again_as()). You see its content change, and no second card slides in.

Three shells

QP gives a state one parent and a card can rise over three screens, so there are three short states, Gui_sheet_face, Gui_sheet_home and Gui_sheet_settings, each a child of its screen. Each calls the same gui_sheet_body(), which holds everything a sheet does: Q_SUPER(home) for what the sheet does not answer, Q_TRAN(home) for the close.

A transition from a child to its own parent runs no entry action, so the screen under the card is never reloaded.

Each shell swallows the auto-lock, the unlock, the door to Rest and the settings door, busy or not, because a door travels through the queue and could arrive after the card it was not meant to meet. GUI_LOCK_SIG and home are the two it answers.

How a card opens

gui_sheet_open_kind() posts the kind and whichever screen is up answers with its shell. Over the face (awake or at Rest) only a system card that opens no page behind it may rise: the write screen, Didn't open, a pairing, Stopped, Critical battery and Turn off. gui_sheet_open_taken() refuses every other kind there, because a settings page over the lock screen would open the board from behind. A list of choices and the keypad open only in place over their own page.

Two bodies

me->sheet_n says which is up, and the pad's grammar follows it.

What closes a card

This is the whole list of causes. Anything else leaves the card up, so a page never vanishes while you read it. The auto-lock never closes a card.

CauseWhat happens
BOT released, or a drag down on the header of a cardgui_sheet_close(). A list of choices and the keypad go back to their page. A system card has no header drag, so only BOT closes it
An answer on a cardRemove turns the card into Removing. Try again keeps the card and turns it back into the write screen. Turn off keeps the card, since the board may not stop on a cable. Run again keeps the card under the launch scene. A pick in a list keeps the card and goes back to the page
HOME, BOT held 0.6 sOver Apps or the face the card closes (Apps stays at its root), and the face stays the face. Over Settings the screen goes back to Apps' root and the card goes with it. A busy card swallows it
The auto-lockNever over a card: the count holds while a card is up
A page going backBOT released or a swipe right takes a settings page back one level
A pairing outcomeThe card closes after 1,200 ms (GUI_PAIR_HOLD_MS). A refusal by the peer closes it at once
The end of a busy writeThe write the launcher started stays until the restart. A failure turns the card into Didn't open in place
A guided factory testBTN_TEST_SIG and TOUCH_TEST_SIG prompts, LED_TEST_SIG and DISPLAY_TEST_SIG asks take the screen
The join of the keypadThe keypad goes back to the Wi-Fi page when the join works, and when Wi-Fi is turned off. The text is wiped first

A busy sheet answers no pad button and no touch: the write screen and the Removing card. The board does something a person cannot stop. They still disarm the presses they swallow, swallow HOME.

Specifications

The kinds

Every kind of card, what opens it and what it shows.

KindOpened byContentButton
WIFI, BLEThe Wi-Fi or Bluetooth circle, heldThe radio's rowsNone
MOREThe Settings circleOne flat list: Wi-Fi, Bluetooth, Battery, Time, Time zone, Sleep after, Kody's scene, Vibration, While charging, Header 3.3 V, Header 5 V, Self-test, About (internals/gui/settings-screen)None
TIMESettings' Time rowThe hour and the minute, each changed where it stands, in the zone in use. The rows follow the clock while the page is openNone
BATSettings' Battery row, or the battery circleReadingsNone
ABOUTSettings' About rowReadings and UpdateNone
ZONESettings' Time zone row, in the list's placeTwenty-one rows, the current zone checkedNone
KODYNo row of the list opens it in this buildThe pack that plays, where it comes from, the progress or the reason of a failureUpdate, Retry, or none
AUTOLOCKScreen's Auto-lock row, in Screen's placeSix times, the one that is on checkedNone
TESTSettings' Self-test rowThe row "Run the test", then the rows of the last runNone
KEYPADThe Wi-Fi page: a network with a password nobody saved, or Hidden networkA field, a Show password row and twelve keysThe yellow pill, Join or Next
DETAILSTOP held on an app's row, or a finger held on itThe icon, name, v<version> · <Category>, the rows Size and Built, the app's wordsRemove (danger), for an installed app
REMOVEThe Remove pill of the detailsRemove <app>?, "It leaves the Dot."Remove (danger)
REMOVINGThe answer of REMOVEThe app's name while it leavesNone: locked
OPENINGA launch of an app that cannot fly, or any flash writeThe percent in the middle of the edge, the name and a second lineNone: busy
FAILUREA failed write, or a run that never happenedThe name and up to three lines of whyTry again, or Close
STOPPEDThe first frame after an app that panicked, hit a watchdog or lost its supplyWhy, in one lineRun again (primary), when the app is listed
BATTERYThe charge at 10 % or under, not charging"Plug in the cable."None
POWEROFFThe held BOT on the face, or the Turn off circle"Turn off?" and two linesTurn off (danger), grey while a cable is in
PAIRINGA peer asking to pair (BLE_PAIR_SIG)The passkey, then the outcomeNone
RESETAbout's Factory reset row"Factory reset?", what goes and what staysReset (danger)
RESETTINGThe answer of RESETThe wait for AO_Boot, then why the reset did not happenNone: locked while it waits

The geometry

The kit decides it (kui_sheet.c). Pixels on the 410 x 502 glass.

PartWhere and how big
CardAt (0, 0), 410 x 502, radius 114 (KUI_RADIUS_GLASS), the page's black. The corners land on the panel's arcs, so nothing sits near them
HandleTop centre, y 14, 36 x 5, ink at 35 %
Header discAt (32, 56), 112 px. A kit glyph is drawn 56 px, an app icon 112 px with a transparent clipped disc, a monogram in kui_font_title
Title and subtitlex 160, 218 px wide, one line cut with dots. Title y 80 with a subtitle, y 95 without; subtitle y 121
BodyAt (32, 188), 346 x 264: three rows at y 188, 280 and 372; more rows scroll
Lines 1 to 3y 188, 218 and 248 (30 px apart); the note at y 262
Passkeycode at y 226, code_l1 at y 296, code_l2 at y 324, centred
Answer buttonOne 160 x 60 button, centred, at y 395
Header, where a finger takes the cardFrom the top down to the foot of the title disc, y 168. On the keypad it ends at y 92; on the details it is the whole card; a system card has none
RowsAt most 32 (GUI_SHEET_ROWS_MAX)

The settings pages do not wear the header disc: they stand in kshell's dress (internals/gui/settings-screen), with a centred title and a flat list of rows. Wi-Fi and Bluetooth wear a header of their own, the radio's disc, name and state, and their switch stays at the top while the rows under it scroll.

The system cards

A system card is a card the system raises, not a page a person opened. One table in kgui_ao.c, gui_syscard_of(), lists them, and every question about which kinds are system cards is asked of it: whether a kind is a system card, whether its answer is a hold, and whether it may rise over the face.

CardKind in gui_infoIts answerBOT
Write screenopeningNone: busy while a write or a run is in flightDoes nothing
Didn't openfailureTry again, a tap or TOP, or Close when nothing can be repeatedCloses it
PairingpairingNoneCloses it and refuses the passkey
StoppedstoppedRun again, a tap or TOPCloses it
Critical batterybatteryNoneCloses it
Turn offpoweroffTurn off, a tap or TOP releasedAnswers no. A swipe down answers no too
Factory resetresetReset, a tap or TOP releasedAnswers no
Factory reset, waitingresettingNone: locked until AO_Boot answersDoes nothing while it waits, closes the card after a refusal

Every one of them may rise over the face. The rule is the same for every row:

The button of Turn off is the red one: a tap or TOP released confirms, and the card asks first, as the question of a card must.

The system frame

kgui_sysscreen.c draws the frame of every system card, adapted from the system screens of kshell. The frame is a black page with the edge along the glass: a line 24 px wide whose outer side is flush with the glass, with corners of 102 px, over a dark track. Inside the edge stand these parts, dressed for each card:

The edge has forms of its own: it fills with the percentage of a write, it spins while something is waited for (22 steps make a lap of 1,980 ms), it closes in 297 ms when the work is done, turns green and pops, thickening by 8 px and back, and it is cut when the work failed: it turns resting grey and a red dot of 15 px stands where it stopped. kgui_edge.c holds the curves with no LVGL, and a host test walks them.

The Ring

The Ring is one module, kgui_ring.c, for the progress and the waits of the interface. A Ring is made once and moved by number, so a pass allocates nothing. It has three shapes and several forms.

The forms:

FormWhat it does
ProgressShows a number a caller counts, in steps of 5 %: the percent of a write
SpinA quarter of the circle that goes round in 8 positions, 90 ms apart. It is the wait of a launch
DoneFull: the edge closed, or the write's progress is whole. It stays in this form until it is cancelled
CloseThe edge closes, turns green and pops (kgui_ring_done())

kgui_ring_pct_at() and kgui_ring_spin_at() are the model, with no LVGL in them.

The ink on a ground

A text that stands on a ground asks gui_ink_on() for its colour. Soft Black or Light Grey, whichever has the better contrast. Light Grey on Kody Blue or on Bold Yellow is the pairing the brand forbids, and a hand-written colour on a row would bring it back.

The rows that change in place

Four rows of the Settings list are settings that change where they stand, each with its value on the second line. No page opens for them:

RowValuesConsole keyTOP or a tapLeft or right
Sleep afterWith the auto-lock, 30 s, 1 min, 5 min, 10 min, Neverkody_sleepThe next timeThe time before, the next time
Kody's sceneauto, or the name of a scenesceneThe next place of a ring: auto, then each scene of the packThe place before, the next place
VibrationOff, Light, Normalhaptic_uiThe next levelThe level before, the next level
While chargingStays awake, Sleepsusb_awakeFlips itNothing

A row that steps is Light Grey. A switch is Kody Blue while on and the off grey while off, and its second line says what it does, so colour is never the only sign.

The ring starts on row 0, so left and right change the brightness as soon as the page is up. A change applies at once, is stored, and is logged as <key>: <word> (stored). The same code serves the console, so a value changed there rebuilds the page too. A change clicks after it is made, so the level of haptics you chose is the one you feel; a change to Off is silent.

The brightness. Left and right change the level by 5 % (GUI_LEVEL_STEP), never below 10 % (GUI_BRIGHT_MIN_PCT), because zero would blank the screen that shows the control. config key="bright" keeps its own range, 5 to 100. A finger on the bar sets the level at its x and a drag follows it, at most one change per 50 ms (GUI_LEVEL_DRAG_MS). Every change goes to AO_Display with display_brightness_request(pct, persist). A press and its drag samples pass persist false and the lift writes once, so a drag of twenty samples a second never writes twenty times. The row clears LV_OBJ_FLAG_SCROLL_CHAIN and sets LV_OBJ_FLAG_PRESS_LOCK, so a drag on the track never scrolls the page.

Wi-Fi

The Wi-Fi page has a header: the Wi-Fi disc, the name, and Off, On or the network the board is on. Row 0 is the switch, always, and it stays at the top of the page while the rows under it scroll: accent when the service is on, KUI_COLOR_OFF when off, with on or off on its second line. With the service off it is the only row, over the line "Turn it on to see the networks near you". With it on, these follow:

  1. The network the board is on (accent, check glyph, Connected, -58 dBm).
  2. Kode ecosystem, while the board is linked. Its second line follows the channel's state (below).
  3. The networks the board remembers, most recently used first, minus the one it is on, with the lock glyph: Saved, or Saved, -60 dBm while the scan finds it.
  4. The networks found by the scan, minus the ones already shown and the ones that hide their name (Open, -71 dBm or Password, -71 dBm, with the lock glyph on those that need a password). Until a scan has found anything the page says Looking for networks..., or No networks found.
  5. Hidden network, the last row, with the second line Type its name.
Kode ecosystem stateSecond line
upConnected (accent, check glyph)
joiningConnecting...
waitingRetrying in <n> s, then Retrying now
refusedRefused: <why>, underscores to spaces, cut at 22 characters (GUI_CHAN_LINE_MAX)
no_identityNo identity
off, no_netNot connected

What TOP does on each row:

RowTOP released or a tapTOP held
The switchStarts or stops the serviceNothing
The connected network, the Kode ecosystem rowNothingNothing
A saved networkJoins it from the C5's vault, with an empty password and no keypadForgets it
An open scan resultJoins itNothing
A scan result with a password nobody savedOpens the keypad for the passwordNothing
Hidden networkOpens the keypad for the network's nameNothing

Rows follow WIFI_STATE_SIG, never the press. While the page is open and the service is on, it scans by itself: a scan and a listing of the remembered networks on its first tick and every 10 s (GUI_WIFI_SCAN_MS) after the last answer, never while one is out. There is no row to search again. The channel's own switch is config key="chan".

The keypad

The keypad types a password or a hidden network's name on twelve keys, the way a phone with no touch screen did. It opens in the place of the Wi-Fi page. The card shows, from the top down:

The card has no header disc. The pad moves the focus over the controls, TOP presses the control with the focus, and a tap presses the control it lands on. The focus opens on the key 5. The keys type by multitap, the rules of kui_multitap (sdk/kui): the same key pressed again within 800 ms replaces the pending character with the next one of the key. A password holds up to 63 characters and a name up to 32; at the cap a press that would add a character is refused with the hint Password is full or Name is full.

A hidden network has two stages, the name and then the password. Next with nothing typed shows Type the name; Join with nothing typed shows Type the password. While the radio scans, Join shows Scanning, try again. While the join is out, the field says Joining, the pill is dimmed and no control acts; BOT, home and the lock still leave.

The card reads WIFI_STATE_SIG as the answer:

What the radio publishesWhat the card does
GOT_IP for the network askedWipes the text, shows Joined and closes back to the Wi-Fi page
DISCONNECTEDShows Wrong password; the keypad comes back with its text
IDLEShows Could not join; the keypad comes back with its text
Nothing for 30 s (GUI_KBD_JOIN_GUARD_MS)Shows Could not join
Wi-Fi turned offWipes the text and closes back to the Wi-Fi page

DISCONNECTED covers both a wrong password and a network out of range, and Wrong password is the driver's best word for it.

The text is a secret. It lives in kgui_keyboard.c and nowhere else: in the multitap's buffer and in the 64 plain characters the field shows. It is not in the Gui struct, a log line, gui_info or any event but the join request, which carries its own copy. The module zeroes both buffers through a volatile pointer on every road away from the keypad. While the keypad is up, AO_Gui holds the log tags kaos.buttons, kaos.touch and kcli at NOTICE with kode_log_hold(), writes its own gesture lines without coordinates, and writes no haptic: line, so a log read in order would not spell the password. gui_info says keypad: <stage>, mode <word>, show on|off and nothing of the text, not even its length.

Bluetooth

The Bluetooth page has a header: the Bluetooth disc, the name, and the name the phones see. Row 0 is the switch (Off, On, or Visible while advertising), and it stays at the top of the page as Wi-Fi's does. With the stack off it is the only row. The page lists no devices and runs no scan. With the stack on, three more rows can appear:

Console and Paired are readings. The circle on the control centre pulses at 1 Hz while the radio advertises; the switch row does not.

Battery

Every row is a reading and nothing acts. Row 0 is a bar with the percentage. Then the flow (Charging, Charged or Discharging) with the current in milliamps, then the cell voltage, and, once the gauge reports a value from -20 to 80 degrees C, the temperature. Readings move in place; the page is rebuilt only when its shape changes. The temperature is asked of the gauge every 30 s.

About

The rows of the About page, with the ring starting on kodeOS. The zone and Kody's pack are rows of the Settings page.

RowShowsTOP or tap
kodeOSv<version> of the image that runsNothing
NameThe printed serial, Unknown when the identity is invalidNothing
RadioESP32-C5 · v1.2.3, or ESP32-C5 · off before the link has answeredNothing
microSD cardThe size in GB, No card, Mounted, or Asking...Nothing
MACThe board's MAC, Unknown on failureNothing
Last startHow the board startedNothing
Up forTime since start, rewritten every 2 sNothing
UpdateWhat it would act on, from a kodeos.bin on the cardWrites kodeos.bin into the inactive kodeOS slot, then restarts
Factory resetErase apps and settingsOpens the question of the factory reset (internals/gui/settings-screen)

Factory reset is the last row and Update the one before it, on purpose: a press that rewrites the OS or erases the unit must not be one that can happen by accident. The row stays enabled without the file and its second line says there is none. During the write the write screen reads Updating with the name kodeOS.

The lists

The list of zones has 21 rows, west to east, the zone in force checked in accent and a Custom zone checking none. A pick asks AO_Boot to take the zone with the same config key="zone" set a console line makes (gui_zone_pick()), then goes back to the page. AO_Boot applies and stores it in kset/zone, and its answer repaints the face and the Time zone row. The list of auto-lock times has six rows, 30 s, 1 min, 2 min, 5 min, 10 min, Never, and a pick stores kgui/lock and goes back to Screen.

How a list answers input:

The 21 zones and the POSIX TZ string each one stores:

RowPOSIX TZ
HonoluluHST10
Los AngelesPST8PDT,M3.2.0,M11.1.0
DenverMST7MDT,M3.2.0,M11.1.0
ChicagoCST6CDT,M3.2.0,M11.1.0
Mexico CityCST6
New YorkEST5EDT,M3.2.0,M11.1.0
Bogota, Lima<-05>5
Sao Paulo<-03>3
UTCUTC0, the zone of a board with nothing stored
London, LisbonGMT0BST,M3.5.0/1,M10.5.0
Madrid, ParisCET-1CEST,M3.5.0,M10.5.0/3
AthensEET-2EEST,M3.5.0/3,M10.5.0/4
JohannesburgSAST-2
Istanbul, Moscow<+03>-3
Dubai<+04>-4
MumbaiIST-5:30
Bangkok<+07>-7
Beijing, Singapore<+08>-8
Tokyo, SeoulJST-9
SydneyAEST-10AEDT,M10.1.0,M4.1.0/3
AucklandNZST-12NZDT,M9.5.0,M4.1.0/3

The Kody card

The Kody page says which pack plays and where it comes from. It has one capsule.

StateLinesButton
IdleThe pack that playsUpdate
An update runsUpdating and 3 of 14 filesNone
An update failedThe update failed: and the reason, such as offlineRetry

Small print says why the button is missing, or what it does:

Update fetches the pack named by CONFIG_KGUI_ASSETS_URL into the internal FAT and never writes the card. The install is on internals/gui/kody-pack.

The details and Remove

The details of an app and the two cards of Remove are described with the rail they open from, on internals/gui/launcher. In the sheet they are the kinds DETAILS, REMOVE and REMOVING. kgui_details.c builds the page once on the card and takes the kit's handle and header disc off the screen; kgui_dtext.c writes the size, the build date and the line under the name. The whole card is its header, so a drag down from anywhere on it closes it.

Launching an installed app

The launch of an installed app is a short scene on the layer above everything. The icon leaves its row and flies to the middle of the glass while the screen darkens under it. A Ring turns round the icon. Then the Ring closes in green and the icon pops, the screen fades to black, and the board resets into the app. The scene shows no words: the icon and the Ring are all there is.

StageFromWhat is on the screen
flight0 msThe icon of the row, 80 px, flies to the middle (205, 251), growing to 96 px (KGUI_LAUNCH_ICON_PX), in 220 ms on an ease-out. The screen darkens in 4 steps
ring220 msThe Ring turns round the icon, 144 px across with a line of 12 px, in Focus Blue over a charcoal track, for 540 ms (KGUI_LAUNCH_SPIN_MS)
close760 msThe Ring turns the success green and closes while the icon pops (scale 1.06 and back, 180 ms)
fade940 msA black layer over the whole scene, 3 steps 50 ms apart
done1,090 msThe screen is black. The scene reports once and the run is asked for

The scene covers the glass and takes every touch. While it runs, Apps under it is locked: no pad, no touch, no Settings, no home and no lock. It starts only from an icon that is on the screen: the row the launcher launched, or the disc of the Stopped card. A launch with no icon to start from takes the write screen at 100 % and asks for the run at once.

gui_launch_tick() steps the scene before the pass that draws. When the fade is over, it logs launching <name> (the fade is over), arms a 3 s wait (GUI_OPEN_RUN_MS) and calls appmgr_run_request(). AO_Boot then writes the boot request. A run AO_AppMgr refuses turns the card into Didn't open after the 3 s.

The write screen and Didn't open

A flash write shows two kinds of card on the system frame: the write screen (opening, the Opening card of the pages about AO_AppMgr and AO_Storage) and Didn't open (failure). A flash write never uses the loading screen.

The write screen shows how far a write has gone. The edge fills round the glass, and the share stands in the middle in kui_font_hero_160 with its % under it. A small protagonist stands above: the app's disc, or a glyph for the system. Under the figure stand the name and a second line. It is busy: it answers no pad button and no touch, and BOT does nothing.

WriteSecond lineFailure title and sentence
An install that runs (KWRITE_APP_RUN)Installing, then OpeningDidn't open: "The file is missing or damaged. Copy it to the card from apps.kode.diy."
An install that stays (KWRITE_APP)InstallingDidn't install, the same three lines
A kodeOS update (KWRITE_OS)Restarts when doneDidn't update: "Nothing changed. kodeOS keeps running the version it had."
A radio update (KWRITE_C5)Keep the Dot onDidn't update: "Nothing changed. The radio keeps the firmware it had."

How a write ends. The figure follows APP_FLASH_PROGRESS_SIG. When a write that runs reaches its end, the edge closes in 297 ms and the figure climbs to 100 with it; the edge turns green and pops, the second line says Opening, and only then is the run asked for. A write that does not run closes the same way, the card leaves, and both lists of apps are asked again because the directory changed. The write screen of a run carries the small print Hold BOT+LEFT to come back. A write of the OS publishes no done event and ends with the restart into the new kodeOS.

Kody's writing and goodbye animations are not played by any screen in this interface: the write screen shows the edge and a figure, and an app runs when the edge has closed.

Didn't open is the write screen filled again. The edge is cut: it turns resting grey and a red dot stands where it stopped. The title is Didn't open, Didn't install or Didn't update, with the name under it and three lines of why. The pill says Try again when the request can be repeated, and Close otherwise. Try again asks the question again, and for a file it asks which apps the write would evict first, because the failed write may have evicted apps already. A refused launch, an install that fails and a run that never took the board away turn the write screen into this card. The card does not leave by itself.

Making room

An install of a file from the SD card asks no question. The apps region keeps its apps oldest first, and when the file does not fit, the oldest apps make room for it, as install does from the console. AO_AppMgr copies each app it pushes out to the SD card before it removes it; a copy that fails stops the install with nothing removed.

The install begins with a question to AO_AppMgr, in two stages that gui_info shows as launch: asking and writing. AO_Gui calls appmgr_evict_query() with the size and project name; AO_AppMgr answers one entry for each app that would go and an end marker.

There is never more than one question out. An end still missing 35 s after its question is taken as lost, so one dry event pool cannot block the installs for good.

Stopped

When a board comes back from an app that panicked, hit a watchdog or lost its supply, the Stopped card rises over Apps. The system frame shows the face of the app that ran, its name, and Stopped. The body gives one line of why:

How the app endedThe line
PanicIt crashed
WatchdogIt stopped responding
BrownoutThe battery dipped

The small print under it is Hold BOT+LEFT and to come back. The button is Run again, the yellow pill, and it is there only when the app is in the list. When no installed app sits where the app ran, the card says An app and has no button. A clean end shows no card, and an end that cannot be told reads as clean, so a healthy app is never accused.

Run again plays a click and launches the app as Apps launches any app. A run that is refused turns the card into Didn't open. BOT closes the card, and Apps stands as it was.

Critical battery

When the charge falls to 10 % or less (KGUI_BAT_CRIT_PCT) and the cell is not charging, the Critical battery card rises. The system frame shows a red battery with a sliver of charge, the name Battery and the second line Critical, <n> %. The detail says Plug in the cable., and The cable is in. while a cable is in. The card follows the charge and the cable while it is up. It has no button; BOT closes it.

The card rises over any screen, the face at Rest included, where its arrival lights the screen. It rises once for each entry into critical. A charge that wobbles around the line is not a new entry: the card can rise again after the charge climbs 3 points (KGUI_BAT_CRIT_REARM_PCT) above the line, or after the cell charges. A board that has not read a charge above 0 % yet raises nothing, because a gauge that reads no cell says 0 % on every reading. It does not rise over a locked card, another system card or a guided test.

Turn off

The Turn off card asks to confirm that the board switches off. The board never does this by itself. Two things raise it: BOT held on the face for 5 s after the red arc has filled, and the Turn off circle of the settings screen. The system frame shows a red power glyph, the title Turn off? and two lines: Hold the red button and to turn it on again.. The card has one red Turn off pill.

When a cable is in, the board does not switch off: AO_LowPower refuses the request. So the two lines give way to Not while USB is in and the pill is grey. The pill is still there, because AO_LowPower is the one that refuses, and it says why over the screen as a hint.

For 700 ms after it began to rise the card answers nothing. Then:

gui_off_ask() raises the card and logs turn off? asked (<why>). A confirmation logs turn off: confirmed and calls gui_power_off() once. That function logs power off requested before it calls kode_power_off(), and AO_LowPower asks every owner to rest and turns the board off (internals/power-and-sleep).

Pairing

When a central connects to the BLE console the board picks a six-digit passkey and the pairing card rises with it, broken after three digits (482 913), in kui_font_code_80. Two lines read "Type this code" and "on the other device". The system frame shows the Bluetooth glyph as its protagonist, with the title Pairing and the second line Another device. The card has no button.

The digits go nowhere else: no log line carries them, because AO_Log copies every line to the BLE console and a paired peer would read the next one's code.

What happenedThe card says
The devices bondedPaired
The peer left in the middleThe other device disconnected
Encryption failed, or the security manager's 30 s ran outPairing failed
You pressed BOTNothing; the card closes and the pairing is refused

The outcome replaces the digits for 1.2 s, then the tick posts the close. Leaving the card in any other way while the code is live refuses the pairing in the shell's exit, so the peer fails at once. A busy sheet, Gui_btn_test, Gui_tp_test and Gui_selftest refuse a pairing outright, and a write that takes the card over refuses it through gui_pair_yield(). The pairing policy is on internals/ble-console.

Self-test

The first row, Run the test (level yellow), starts the whole-unit check. Under it come the rows of the last run in plain words (Charger, Touch, Card slot):

Problems come first and the header counts them (13 Oct 09:41 - All good, 1 problem), in the face's time zone. Small print says Tap a row to test it again, and TOP or a tap on a row of the last run repeats that one row.

When a run starts the screen stops being a sheet and AO_Gui enters Gui_selftest, which shows the loading screen with Kody in his wheel. The line under him reads:

MomentThe line reads
StartStarting
A row<n> of <N> and the row's label; for the card rows, Hold a card to the back
Waiting for handsNow we need you and TOP to continue; TOP calls autotest_continue()
Charger rowPlug in the charger, Checking the charger, Keep it plugged in, Now unplug the charger
BOT before the holdStopping after this check; the row in flight ends first
Another client's runThe page says Another test is running

AO_Autotest owns the rows and the report (sdk/kaos/autotest). The auto-lock does not count during a run, and a stopped run keeps no report. After a run, the board opens Settings and then the Self-test page.

Gotchas

Example code

Pair a computer over Bluetooth and watch the card:

  1. Open a console session over the USB-C cable first. Opening the port resets the board, and attaching the cable after the card is up would end the pairing.
  2. Clear the bond on both sides, on the board with Forget all on the Bluetooth page and on the computer in its Bluetooth settings.
  3. Attach over Bluetooth from the computer. The pairing card rises with a code such as 482 913. For 700 ms it answers no button.
  4. Type the code on the computer. The card reads Paired for 1.2 s and closes.
  5. Repeat and press BOT while the code shows. The card closes and the computer reports a failed pairing at once.

In the SDK