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:
- The pages of the settings screen, in kshell's rail dress: Wi-Fi, Bluetooth, Settings and the six pages Settings opens (Screen, Battery, Time zone, Kody, Self-test and About), and the lists of choices (the zones and the auto-lock times).
- The keypad, which a row of the Wi-Fi page opens to type a password or the name of a hidden network.
- The details of an app, which TOP held or a finger held opens over a row of Apps, and the two cards Remove adds.
- The system cards: the write screen and Didn't open, Stopped, Critical battery, Turn off, the Bluetooth pairing code and the two cards of the factory reset.
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:
- The ring is the yellow outline that marks the selected row or button. The pad moves it.
- The Ring, with a capital, is the circle or the line that fills while a button is held and turns while something is waited for.
- The accent is Kody Blue, the colour of a radio that is on and of the chosen thing.
- Level yellow is Bold Yellow, the colour of a level such as the brightness, and the ground of the one primary answer of a card.
- Off grey is the warm charcoal (
KUI_COLOR_OFF) of a switch that is off.
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
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.
- The blocker is the transparent root, as large as the glass and clickable. It takes every touch while the card slides. It has no callback: a press that opens a card can still be down when the card appears, and the lift would arrive as a click on the blocker and close the card at once.
- The card is the part you see. An animation moves it between y 0 and 502 (
GUI_SHEET_HIDDEN_Y). - The sheet slides with an
lv_animand not a screen load, which keeps it off the path that can freeze the interface. - A system card rises on a spring instead, and the details open with the sheet's own rise.
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
- A sheet with rows shows a column of
pillrows inbody: the pages of Settings and the two lists of choices. - A sheet with text shows three lines, a note, a large code and one answer button, all permanent children of the card that the kit hides and C shows.
- The keypad is neither:
kgui_keyboard.cbuilds a field, a pill and twelve keys on the card.
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.
| Cause | What happens |
|---|---|
| BOT released, or a drag down on the header of a card | gui_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 card | Remove 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 s | Over 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-lock | Never over a card: the count holds while a card is up |
| A page going back | BOT released or a swipe right takes a settings page back one level |
| A pairing outcome | The card closes after 1,200 ms (GUI_PAIR_HOLD_MS). A refusal by the peer closes it at once |
| The end of a busy write | The write the launcher started stays until the restart. A failure turns the card into Didn't open in place |
| A guided factory test | BTN_TEST_SIG and TOUCH_TEST_SIG prompts, LED_TEST_SIG and DISPLAY_TEST_SIG asks take the screen |
| The join of the keypad | The 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.
| Kind | Opened by | Content | Button |
|---|---|---|---|
WIFI, BLE | The Wi-Fi or Bluetooth circle, held | The radio's rows | None |
MORE | The Settings circle | One 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 |
TIME | Settings' Time row | The hour and the minute, each changed where it stands, in the zone in use. The rows follow the clock while the page is open | None |
BAT | Settings' Battery row, or the battery circle | Readings | None |
ABOUT | Settings' About row | Readings and Update | None |
ZONE | Settings' Time zone row, in the list's place | Twenty-one rows, the current zone checked | None |
KODY | No row of the list opens it in this build | The pack that plays, where it comes from, the progress or the reason of a failure | Update, Retry, or none |
AUTOLOCK | Screen's Auto-lock row, in Screen's place | Six times, the one that is on checked | None |
TEST | Settings' Self-test row | The row "Run the test", then the rows of the last run | None |
KEYPAD | The Wi-Fi page: a network with a password nobody saved, or Hidden network | A field, a Show password row and twelve keys | The yellow pill, Join or Next |
DETAILS | TOP held on an app's row, or a finger held on it | The icon, name, v<version> · <Category>, the rows Size and Built, the app's words | Remove (danger), for an installed app |
REMOVE | The Remove pill of the details | Remove <app>?, "It leaves the Dot." | Remove (danger) |
REMOVING | The answer of REMOVE | The app's name while it leaves | None: locked |
OPENING | A launch of an app that cannot fly, or any flash write | The percent in the middle of the edge, the name and a second line | None: busy |
FAILURE | A failed write, or a run that never happened | The name and up to three lines of why | Try again, or Close |
STOPPED | The first frame after an app that panicked, hit a watchdog or lost its supply | Why, in one line | Run again (primary), when the app is listed |
BATTERY | The charge at 10 % or under, not charging | "Plug in the cable." | None |
POWEROFF | The held BOT on the face, or the Turn off circle | "Turn off?" and two lines | Turn off (danger), grey while a cable is in |
PAIRING | A peer asking to pair (BLE_PAIR_SIG) | The passkey, then the outcome | None |
RESET | About's Factory reset row | "Factory reset?", what goes and what stays | Reset (danger) |
RESETTING | The answer of RESET | The wait for AO_Boot, then why the reset did not happen | None: locked while it waits |
The geometry
The kit decides it (kui_sheet.c). Pixels on the 410 x 502 glass.
| Part | Where and how big |
|---|---|
| Card | At (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 |
| Handle | Top centre, y 14, 36 x 5, ink at 35 % |
| Header disc | At (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 subtitle | x 160, 218 px wide, one line cut with dots. Title y 80 with a subtitle, y 95 without; subtitle y 121 |
| Body | At (32, 188), 346 x 264: three rows at y 188, 280 and 372; more rows scroll |
| Lines 1 to 3 | y 188, 218 and 248 (30 px apart); the note at y 262 |
| Passkey | code at y 226, code_l1 at y 296, code_l2 at y 324, centred |
| Answer button | One 160 x 60 button, centred, at y 395 |
| Header, where a finger takes the card | From 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 |
| Rows | At 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.
| Card | Kind in gui_info | Its answer | BOT |
|---|---|---|---|
| Write screen | opening | None: busy while a write or a run is in flight | Does nothing |
| Didn't open | failure | Try again, a tap or TOP, or Close when nothing can be repeated | Closes it |
| Pairing | pairing | None | Closes it and refuses the passkey |
| Stopped | stopped | Run again, a tap or TOP | Closes it |
| Critical battery | battery | None | Closes it |
| Turn off | poweroff | Turn off, a tap or TOP released | Answers no. A swipe down answers no too |
| Factory reset | reset | Reset, a tap or TOP released | Answers no |
| Factory reset, waiting | resetting | None: locked until AO_Boot answers | Does 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:
- Raised by the system. A write, the failure of a write, a pairing, an app that stopped, a nearly empty cell or the hold of BOT on the face raises the card. It takes no drag, and the only tap that acts is the one on its button.
- Rise. A system card with no card under it rises from the bottom edge on a critically damped spring of 180 ms (
KGUI_RISE_PERIOD_MS) and leaves in 160 ms. A veil in the card's colour lies over its content and clears over the last tenth of the way, so the text and the button are not there before the card is up. - A gate of 700 ms. The card ignores every input for 700 ms (
KGUI_RISE_INPUT_MS) from the moment it began to rise, landed or not: the pad, the click of the button, TOP, and a held BOT. A press that began under the gate answers nothing even when its lift comes later. - A press already down is nobody's. A finger that is down as the card arrives, and a BOT that is down, are made to wait for their lift. The lift clicks nothing under the card, and the held button closes no card and locks nothing over it.
- Home closes it where it can be closed, over the face as over Apps. The face stays locked. The hold of the thumb that woke the face is not a door.
- A system card that takes over another in place (the write screen by Didn't open) does not rise again. Its gate shuts again for 700 ms.
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 protagonist is small, with its middle at y 104: an app's disc of 88 px, a glyph scaled to 96 px, a word, or the red battery of the Critical battery card.
- A write's face: the share in
kui_font_hero_160(DM Sans Bold 160) with its top at y 172, the%under it inkui_font_display_40at y 300, the name at y 359 and a second line at y 403, and small print at y 431. - A card's words: a name at y 160, a second line 46 px under it and up to three lines of detail, each 40 px under the one before. The words are centred in 350 px from x 30.
- A pairing's code in
kui_font_code_80(DM Sans Bold 80) at y 160. - The pill with its top at y 370, 72 px tall and at least 173 px wide, with room for its yellow focus ring.
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 circle is one LVGL arc that starts at the top and fills clockwise over a track. The launch turns one round the icon, 144 px across with a line of 12 px.
- The screen Ring is a line that follows the edge of the glass, 12 px wide and 6 px in, over a track. It starts at the top middle. It lives on the card of the sheet and serves the write screen.
- The edge of the system frame, above.
The forms:
| Form | What it does |
|---|---|
| Progress | Shows a number a caller counts, in steps of 5 %: the percent of a write |
| Spin | A quarter of the circle that goes round in 8 positions, 90 ms apart. It is the wait of a launch |
| Done | Full: the edge closed, or the write's progress is whole. It stays in this form until it is cancelled |
| Close | The 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:
| Row | Values | Console key | TOP or a tap | Left or right |
|---|---|---|---|---|
| Sleep after | With the auto-lock, 30 s, 1 min, 5 min, 10 min, Never | kody_sleep | The next time | The time before, the next time |
| Kody's scene | auto, or the name of a scene | scene | The next place of a ring: auto, then each scene of the pack | The place before, the next place |
| Vibration | Off, Light, Normal | haptic_ui | The next level | The level before, the next level |
| While charging | Stays awake, Sleeps | usb_awake | Flips it | Nothing |
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:
- The network the board is on (accent, check glyph,
Connected, -58 dBm). - Kode ecosystem, while the board is linked. Its second line follows the channel's state (below).
- The networks the board remembers, most recently used first, minus the one it is on, with the lock glyph:
Saved, orSaved, -60 dBmwhile the scan finds it. - The networks found by the scan, minus the ones already shown and the ones that hide their name (
Open, -71 dBmorPassword, -71 dBm, with the lock glyph on those that need a password). Until a scan has found anything the page saysLooking for networks..., orNo networks found. - Hidden network, the last row, with the second line
Type its name.
| Kode ecosystem state | Second line |
|---|---|
up | Connected (accent, check glyph) |
joining | Connecting... |
waiting | Retrying in <n> s, then Retrying now |
refused | Refused: <why>, underscores to spaces, cut at 22 characters (GUI_CHAN_LINE_MAX) |
no_identity | No identity |
off, no_net | Not connected |
What TOP does on each row:
| Row | TOP released or a tap | TOP held |
|---|---|---|
| The switch | Starts or stops the service | Nothing |
| The connected network, the Kode ecosystem row | Nothing | Nothing |
| A saved network | Joins it from the C5's vault, with an empty password and no keypad | Forgets it |
| An open scan result | Joins it | Nothing |
| A scan result with a password nobody saved | Opens the keypad for the password | Nothing |
| Hidden network | Opens the keypad for the network's name | Nothing |
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 title: the name of the network, or
Hidden networkuntil the name is in. - The field, a dark capsule. A password shows as dots except the last character, which stays plain while pending and for 1 s (
KUI_MULTITAP_SHOW_MS). A name shows plain. An empty field asks forPasswordorNetwork name. - The yellow pill:
Joinfor a password,Nextfor a name. - The Show password row with a switch, for a password only.
- Twelve keys in four rows of three: 1 to 9, then the mode key, 0 and Delete. The mode key wears
abc,ABC,123or#+=.
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 publishes | What the card does |
|---|---|
GOT_IP for the network asked | Wipes the text, shows Joined and closes back to the Wi-Fi page |
DISCONNECTED | Shows Wrong password; the keypad comes back with its text |
IDLE | Shows 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 off | Wipes 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 (
Connected) appears while a central is subscribed to the console's output. - Paired shows
N devices. - Forget all (danger colour) appears when N is above 0. It calls
ble_unpair_request(). AO_Radio refuses while a central is attached, and the row's second line saysDisconnect first.
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.
| Row | Shows | TOP or tap |
|---|---|---|
| kodeOS | v<version> of the image that runs | Nothing |
| Name | The printed serial, Unknown when the identity is invalid | Nothing |
| Radio | ESP32-C5 · v1.2.3, or ESP32-C5 · off before the link has answered | Nothing |
| microSD card | The size in GB, No card, Mounted, or Asking... | Nothing |
| MAC | The board's MAC, Unknown on failure | Nothing |
| Last start | How the board started | Nothing |
| Up for | Time since start, rewritten every 2 s | Nothing |
| Update | What it would act on, from a kodeos.bin on the card | Writes kodeos.bin into the inactive kodeOS slot, then restarts |
| Factory reset | Erase apps and settings | Opens 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:
- Up and down move the ring with wrap, TOP or a tap picks, and left and right do nothing.
- BOT, a swipe right or a drag down on the header go back to the page with nothing changed.
- BOT held takes the whole card away to Apps' root.
- Both lists open with the current choice under the finger that opened them, so a second tap picks the same choice again.
The 21 zones and the POSIX TZ string each one stores:
| Row | POSIX TZ |
|---|---|
| Honolulu | HST10 |
| Los Angeles | PST8PDT,M3.2.0,M11.1.0 |
| Denver | MST7MDT,M3.2.0,M11.1.0 |
| Chicago | CST6CDT,M3.2.0,M11.1.0 |
| Mexico City | CST6 |
| New York | EST5EDT,M3.2.0,M11.1.0 |
| Bogota, Lima | <-05>5 |
| Sao Paulo | <-03>3 |
| UTC | UTC0, the zone of a board with nothing stored |
| London, Lisbon | GMT0BST,M3.5.0/1,M10.5.0 |
| Madrid, Paris | CET-1CEST,M3.5.0,M10.5.0/3 |
| Athens | EET-2EEST,M3.5.0/3,M10.5.0/4 |
| Johannesburg | SAST-2 |
| Istanbul, Moscow | <+03>-3 |
| Dubai | <+04>-4 |
| Mumbai | IST-5:30 |
| Bangkok | <+07>-7 |
| Beijing, Singapore | <+08>-8 |
| Tokyo, Seoul | JST-9 |
| Sydney | AEST-10AEDT,M10.1.0,M4.1.0/3 |
| Auckland | NZST-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.
| State | Lines | Button |
|---|---|---|
| Idle | The pack that plays | Update |
| An update runs | Updating and 3 of 14 files | None |
| An update failed | The update failed: and the reason, such as offline | Retry |
Small print says why the button is missing, or what it does:
Connect Wi-Fi to update.Wait for the write to end.The card's pack is in use. Updates go to the Dot.Replaces the pack on the Dot. Your own scenes go on the card.
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.
| Stage | From | What is on the screen |
|---|---|---|
flight | 0 ms | The 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 |
ring | 220 ms | The 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) |
close | 760 ms | The Ring turns the success green and closes while the icon pops (scale 1.06 and back, 180 ms) |
fade | 940 ms | A black layer over the whole scene, 3 steps 50 ms apart |
done | 1,090 ms | The 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.
| Write | Second line | Failure title and sentence |
|---|---|---|
An install that runs (KWRITE_APP_RUN) | Installing, then Opening | Didn't open: "The file is missing or damaged. Copy it to the card from apps.kode.diy." |
An install that stays (KWRITE_APP) | Installing | Didn't install, the same three lines |
A kodeOS update (KWRITE_OS) | Restarts when done | Didn't update: "Nothing changed. kodeOS keeps running the version it had." |
A radio update (KWRITE_C5) | Keep the Dot on | Didn'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.
busy: AO_AppMgr could not look. AO_Gui asks again after 150 ms, up to 4 times, then saysBusy. Try again.- The image is bigger than the region: nothing is written and the hint says
Too big for this Dot. - It fits, with or without apps to push out: the write screen rises and the write starts. The log says
install: <n> app(s) make room for <app>when some go. - A locked card is up while apps would go: the install is given up with
Busy. Try again. - No answer within 1,500 ms: the hint says
No answer. Try again, and a late answer is ignored.
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 ended | The line |
|---|---|
| Panic | It crashed |
| Watchdog | It stopped responding |
| Brownout | The 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:
- TOP released, or a tap on the pill, confirms. The card asks and the button confirms: there is no hold.
- BOT released answers no, and closes the card with nothing done.
- A swipe down answers no too, at the lift, and the swipe must be at least 40 px.
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 happened | The card says |
|---|---|
| The devices bonded | Paired |
| The peer left in the middle | The other device disconnected |
| Encryption failed, or the security manager's 30 s ran out | Pairing failed |
| You pressed BOT | Nothing; 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):
OKon green with a check.Problemon danger with the row's own line such asLight not working.No answerandSkippedwith no icon.
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:
| Moment | The line reads |
|---|---|
| Start | Starting |
| A row | <n> of <N> and the row's label; for the card rows, Hold a card to the back |
| Waiting for hands | Now we need you and TOP to continue; TOP calls autotest_continue() |
| Charger row | Plug in the charger, Checking the charger, Keep it plugged in, Now unplug the charger |
| BOT before the hold | Stopping after this check; the row in flight ends first |
| Another client's run | The 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:
- 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.
- Clear the bond on both sides, on the board with Forget all on the Bluetooth page and on the computer in its Bluetooth settings.
- Attach over Bluetooth from the computer. The pairing card rises with a code such as
482 913. For 700 ms it answers no button. - Type the code on the computer. The card reads
Pairedfor 1.2 s and closes. - Repeat and press BOT while the code shows. The card closes and the computer reports a failed pairing at once.
In the SDK
kui_sheet.c: the kit's sheet, built once bykui_sheet_create().kgui_ao.c: the three shells,gui_sheet_body(),gui_syscard_of()and every page's fill function.kgui_sysscreen.candkgui_ring.c: the system frame, the edge and the Ring.kgui_assets.c: the update behind the Kody card.