kodeOS internals
Settings screen
The control centre that comes down over the face, the Settings page and the pages behind it, and the curtain that carries both.
What it is
The settings screen is a control centre. It switches the radios, shows the battery, turns the board off and sets the brightness and the volume. Three circles stand in the top row (Wi-Fi, Bluetooth and Settings). Under Wi-Fi stands the battery, under Settings stands Turn off, and the brightness level and the volume level run under them all.
Wi-Fi Bluetooth Settings
Battery Turn off
[ brightness level ------------ ]
[ volume level --------------- ]
It comes down over the face like a curtain. Open it with the pad's up, TOP held or a finger dragging down from anywhere on the face. Close it with BOT or a swipe up, and it goes back to the face.
A circle that has more to say opens a page over the screen. Settings opens one flat list: the radios, the battery, the time and its zone, the settings that change where they stand, the two supplies of the expansion header, Self-test and About. The battery circle opens the Battery page alone. Every page grows out of the middle of the glass and goes back into it.
How it is connected
flowchart LR power["AO_Power<br/>POWER_EVT_SIG"] --> gui["AO_Gui<br/>gui_cc_paint()"] radio["AO_Radio<br/>WIFI_STATE_SIG<br/>BLE_STATE_SIG"] --> gui display["AO_Display<br/>DISPLAY_STATE_SIG"] --> gui gui --> cc["control centre<br/>circles, levels"] cc -- "tap or TOP" --> act["gui_settings_act()"] cc -- "Settings, the battery, or a hold" --> page["a page<br/>on the sheet"] class power ao class radio ao class display ao class gui ao
Gui_settings is a child of Gui_running, like the face and Apps, so the tick, the touch feed and the flash-write takeover work here with no extra code. kgui_screen_settings.c builds the screen from the kit. The composition follows kshell.
The controls
| Part | Where | What it shows |
|---|---|---|
| Three circles | y 54 (KGUI_CC_Y0), x 43, 161 and 279, each 88 px | Wi-Fi, Bluetooth and Settings (a gear) |
| Battery | y 172 (KGUI_CC_Y1), x 43, under Wi-Fi | A circle in the off grey filled to the charge with a liquid whose surface is a wave |
| Turn off | y 172, x 279, under Settings | A circle in the off grey with a red ring and a red power glyph |
| Brightness level | x 18, y 290 (KGUI_CC_LEVEL_Y), 374 x 72 px | A capsule whose Light Grey fill grows from 72 px to the whole width, with a sun glyph in four steps and the figure |
| Volume level | x 18, y 378 (KGUI_CC_VOLUME_Y), 374 x 72 px | The same capsule, with a speaker glyph in four steps: struck through at 0 %, then one, two and three waves |
- The circles show what is. A radio that is on is Kody Blue with a Soft Black glyph, and a radio that is off is the off grey with a Light Grey glyph. Settings is the off grey. A circle carries no number, and a tap does not turn a circle blue: the state event of the radio does.
- The battery circle is a reading of the charge and a door to the Battery page. The liquid is OK green, and Danger red once the charge is low or critical, charging or not. A full cell keeps its surface 18 px under the top of the circle (
KGUI_CC_LIQUID_BRIM), so the wave always shows. While the circles show, the surface moves on: a wave every 2.4 s (KGUI_CC_WAVE_PERIOD_MS), one 2 px column at a time, and only the circle is drawn again when the column changes. It stands still under a page, and is drawn again then only when the charge or its colour changes. - The brightness level shows the brightness the panel is driven at. The figure stands inside the fill in Soft Black from 160 px of fill, and after the fill's edge in Light Grey before. The least the level sets is 10 % (
KGUI_CC_LEVEL_MIN_PCT). The console'sbrightkeeps its own range. - The volume level shows the speaker's volume, from 0 % to 100 %. AO_Gui reads it from AO_Audio as a
config key="vol"on every entry, and asks AO_Audio to set it, which stores it, when a gesture ends or the screen is left. - The cursor is the kit's yellow focus ring, on a circle or round a whole level. It shows while the pad is the input in use.
- There is no handle, no title and no label: each control says its own state.
The cursor and what a control does
The pad's left and right walk the top row and stop at its ends, and on the second row they go from the battery to Turn off and back. Down from Wi-Fi goes to the battery, down from Settings goes to Turn off, down from Bluetooth, the battery or Turn off goes to the brightness level, and down from the brightness level goes to the volume level. Up is the same walk back: from the volume to the brightness, from the brightness to Turn off, from Turn off to Settings and from the battery to Wi-Fi. Nothing wraps. On a level, left and right step it by 5 % (GUI_LEVEL_STEP).
A control acts on the release of TOP, or on a tap. gui_settings_act() is the one function that acts:
| Control | TOP released, or a tap | TOP held, or a finger held 0.6 s |
|---|---|---|
| Wi-Fi | Switches the radio on or off | Opens the Wi-Fi page |
| Bluetooth | Switches the radio on or off | Opens the Bluetooth page |
| Settings | Opens the Settings page | Nothing |
| Battery | Opens the Battery page alone over the circles | Nothing |
| Turn off | Asks "Turn off?" | Nothing |
| A level | Nothing | Nothing |
- The click that follows a finger's hold does not switch the radio.
- A level follows a finger. It is stored when the finger lifts or when the screen is left. A swipe that starts on a level is the level's, never the way back.
- Turn off asks before anything ends. The card has one red button, "Turn off", and BOT or a swipe down says no. On a cable the board does not stop and the card says why (
internals/gui/sheets). - The controls write four
configkeys and no other:wifi,ble,bright, andvol, which AO_Gui asks AO_Audio to set.
The pages
The Settings circle opens a page titled Settings, one flat list (GUI_SHEET_MORE, named more by gui_info, filled by gui_more_fill()). Each row is a disc, a name and a value. Some rows open a page, and the others change where they stand: TOP or a tap steps the value, and nothing opens.
| Row | Value | What TOP or a tap does |
|---|---|---|
| Wi-Fi | The state, or the network the board is on | Opens the Wi-Fi page |
| Bluetooth | The state | Opens the Bluetooth page |
| Battery | The charge and the state | Opens the Battery page |
| Time | The local time, hh:mm, following the clock while the list is open | Opens the Time page |
| Time zone | The zone | Opens the list of zones |
| Sleep after | The kody_sleep time | Steps to the next time |
| Kody's scene | The scene's English name, or Auto | Steps to the next scene |
| Vibration | Off, Light or Normal (haptic_ui) | Steps to the next |
| While charging | Stays awake or Sleeps (usb_awake), a switch | Flips it |
| Header 3.3 V | On or off, a switch | Asks AO_Radio to switch the header's 3.3 V supply (exp_power_request()), answered by EXP_POWER_SIG. Not stored |
| Header 5 V | On or off, a switch. Battery only while a cable is in | Asks AO_Power for the 5 V boost (power_ctrl_request(POWER_CTRL_BOOST)), answered by POWER_CTRL_SIG. Not stored. Refused with a thud, before asking, while a cable is in |
| Self-test | Last run: pass, Last run: <n> to look at, or Never run | Opens the Self-test page; it does not run until you start it. TBD: the row comes back before About on the branch fix/kgui-selftest-and-pack, not yet merged |
| About | The serial | Opens About |
One switch of each supply runs at a time. A refusal is a thud and a log line. The brightness and the volume are on the control centre, and the auto-lock time is set with config key="lock". The Kody page has no row in this list; its pack updates with assets_update (internals/gui/kody-pack).
The battery circle opens the same Battery page alone over the circles (bat_from_cc), and the way back from it is the control centre.
A page is a sheet in kshell's dress: Wi-Fi, Bluetooth, Settings, Screen, Battery, About, Time zone, Auto-lock, Self-test and Kody. The page has no header disc and no subtitle. Its name stands centred at y 30 in Outfit Bold 28, and its body runs from y 68 to y 458. Wi-Fi and Bluetooth have a header instead of a name: a disc of 56 px with the radio's glyph, Kody Blue while the radio is on and dark grey while off, then the radio's name and a grey line under it. Disc and words are measured and centred together at y 26 (KGUI_PAGE_HDR_Y), a name too long for 240 px ends in dots, and their body starts at y 100 (KGUI_PAGE_HDR_BODY_Y).
- A row is a disc of 56 px, Medium Grey with a 30 px Soft Black glyph, or Kody Blue when what it stands for is on. The name is Outfit Bold 34 and the value is 28 in Medium Grey. The row is 92 px tall with 12 px between rows, so the pitch is 104.
- A row with no disc is a capsule 366 px wide under the cursor, with the yellow ring round the capsule. An action's capsule is its own colour all the time.
- The focused row stays at one height, y 215 by its centre, and the rows pass under it. The body is padded so that the first and the last row reach that height. Wi-Fi and Bluetooth are the exception, below.
- The list is flat. Every row stands at the same x, whatever its height on the glass, and a row fades near the top and the foot of the body.
- Wi-Fi and Bluetooth pin their switch. The first row, the switch, stays at the top of the body and does not scroll (
gui_sheet_pin_first()). The rows under it are a plain list that scrolls under the switch, by the least that shows the ringed row. - A bar (the brightness, the battery) is the level's look in a row of its own.
- The pad walks the rows with up and down, wrapping at both ends. TOP released acts on the row.
- The way back is one level: BOT released or a swipe right goes from a page to Settings, and from Settings, Wi-Fi, Bluetooth or the battery's Battery page to the control centre. The page that opened another is ringed on the row that opened it. HOME goes to Apps' root.
- A page grows out of the middle, as Apps does out of the face (
gui_page_grow_start()). The growth plays on the curtain's two pictures, the glass as it is and the card drawn once; the real card leaves the glass while it runs and comes back on the last frame, which looks the same. A page that arrives, opened or a level deeper, grows over what is there in 198 ms (GUI_GROW_IN_MS); one that leaves, closed or a level back, fades into the middle over what it leaves in 231 ms (GUI_GROW_OUT_MS), both easing out. Nothing is scaled. Any other motion of the card, or a curtain, cuts the growth where it would have ended (gui_page_grow_cut()). - A page slides only when the pictures cannot be taken: in from the right and out to it in 120 ms (
GUI_PAGE_ANIM_MS), the new content from the right when it is deeper and from the left when it goes back. - Time zone and Auto-lock are lists in the place of the page that opened them. A pick applies, is stored and goes back to the page. BOT goes back unchanged.
- After a self-test run, the board opens Settings and then the Self-test page.
What each page holds is on internals/gui/sheets.
The factory reset
About ends with a red row, Factory reset, under Update, with the line Erase apps and settings. TOP released or a tap on it opens a question. Nothing is asked of the board until the question is answered. The reset is the one the console's factory_reset runs (internals/command-set) and the one a unit that passes the E5 test runs before it switches itself off (sdk/kaos/test-dot). From the settings screen it ends in a restart, so the welcome app greets the next boot.
The question is a system card on the frame of Turn off (reset in gui_info). It shows a red chip glyph, the title Factory reset?, the line Apps and settings go, three lines on what else goes and what stays, and one red Reset pill:
So do networks and paired devices. The welcome app, the name and files stay.
Like every system card it ignores all input for 700 ms after it begins to rise. Then:
- A tap on the pill, or TOP released, asks AO_Boot and turns the card into the wait.
- BOT released says no. The card closes and nothing is erased.
The wait is a locked card (resetting). The edge turns in red, the title reads Factory reset, the line Erasing and the detail The Kode restarts into the welcome app. There is nothing to press.
- HOME gives a buzz and does nothing.
- AO_LowPower's round is answered busy, so the board does not rest in the middle of the reset.
- The write card of the welcome app's install (Opening, Didn't open) does not take the glass from the wait.
When the reset works AO_Boot restarts the board and the wait stays on the glass until it does. When it does not, the card buzzes, the edge is cut, the second line reads Not done and the third says why. The card is not locked any more, and BOT or HOME closes it. It has no pill.
| The third line | Why |
|---|---|
The welcome app is not on this Kode | The welcome app is neither installed nor on the card. Nothing was erased |
Busy: try again in a moment | AO_AppMgr is writing, a reset or a restart is already on its way, or the request could not be posted. Nothing was changed |
Stopped at <step> | A step failed: find, install, apps, link, wipe, nvs or request. What the earlier steps erased stays erased |
gui_info prints reset: asked= answered= step= status= while the wait is up. kgui_reset_why() writes the third line from the answer of AO_Boot (FactoryResetRspEvt).
The curtain
The settings screen does not slide in as a screen load. It comes down over the face, and goes up again, as a curtain: two still pictures on a screen of its own. AO_Gui places the pictures, and nothing else is drawn while the curtain moves. The page that moves is a kui_drag_t of the kit (sdk/kui). The face is a curtain of the same make, and it comes down over Apps and over this screen (internals/gui/face).
The pictures. A curtain has a picture below and a picture of the panel:
- Opening, the picture below is the face as it stands. The panel is the settings screen, rendered as it will look when it lands.
- Closing, the picture below is the screen it goes back to, rendered as it will look when it lands: the face, or Apps' root when the door is home. The panel is the settings screen as it stands.
A black dim grows from nothing to 40 % (KGUI_CURTAIN_DIM_OPA) between the two as the panel comes down, and fades as it goes back up. The face curtain has no dim.
Before a picture is taken, the entry work that needs only what AO_Gui holds runs: the circles are painted, the ring stands on its control, the time is painted. The reads and the requests stay with the landing, so no circle changes colour at the landing. AO_Gui also asks the owners once, when it comes up, for the states the screen shows. The copy of a screen comes from the front frame buffer of the panel. When a picture cannot be taken, because PSRAM refused a buffer or the render failed, the door goes through with a cut and the log has a warning.
The motion. The page is a number from 0 (the panel is above the screen) to 502 (it covers the screen). A door aims it at the other end with a critically damped spring of 0.25 s, and a finger holds it. The spring starts at the finger's speed, or from rest for a door, and lands exactly on its end at most 320 ms after it started. The tick evaluates the spring from the clock, so the motion does not depend on how often it is stepped. It places the pictures about 30 times a second, every second refresh of the panel.
The doors.
- Opening, from the face: a drag down anywhere on the face, the pad's up, TOP held.
- Opening, from Apps: a drag down from the top 44 rows of the glass, or on the list at rest at its top (
internals/gui/launcher), and TOP held. Closing it goes back to Apps. - Closing, back to the face: BOT released, a drag up anywhere on the screen.
- Home, BOT held 0.6 s, closes the screen to Apps' root, with no rescan. The log says
door: settings -> launcher (bottom held).
Only a drag follows the finger. The other doors start the spring from rest. A settings door that arrives while a curtain moves is dropped, and the log says door dropped: the curtain is still moving. Over the details or a card Settings does not open: a drag down from the top edge of a card is the card's own, and it closes the card.
The auto-lock brings the face down over the settings screen: the face is the panel and this screen is the picture below.
Following the finger. The recognizer claims a drag down on the face, or a drag up on the settings screen, 12 px from the landing. At the claim AO_Gui tells LVGL to wait for the lift, takes the pictures, and grabs the page at the point of the claim, closed for a drag that opens and open for a drag that closes, so the curtain makes no jump. Every later sample feeds the page, which follows the finger with some resistance at the start.
While the finger follows and letting go would open the curtain, a cue shows: the line "Release for Settings" in the look of the hint. It moves with the panel and goes when letting go would take the curtain back. A spring has no finger, so it has no cue.
The release rule. When the finger lifts, the page opens or goes back:
- Faster than 800 px/s toward open: it opens.
- Faster than 800 px/s the other way: it goes back.
- Otherwise: it opens when it is at half or beyond, and goes back when it is not.
The speed is the one over the last 80 ms of samples. The log says gesture: release at <p>% <v> px/s -> open|back (settings).
The state changes on landing. While the curtain moves, the state of AO_Gui is the screen where the door started. When the spring lands, the tick posts GUI_CURTAIN_LAND_SIG, because a tick cannot transition, and the state answers. If the page landed on the far side the door is taken and the screen it leads to is loaded with a cut in the same step. If it went back to where it started, the screen it started from is loaded back and the state stays.
Catching the curtain. A press on a curtain that settles catches it where the spring has it. The page freezes, the pictures are put there at once, and the log says gesture: caught the curtain at <p>%. A drag grabs the page from there with no resistance. A lift with no drag lets the page go on to the end it was going to. A press on a curtain that a finger holds, or that has landed and waits to be taken, is blocked.
A button finishing it. A press of a button, a hold, home, a card that opens, and the doors to the face and Apps do not wait for a spring. Every state a curtain can start from lands a moving curtain at once, on the end it was going to, and the state it lands in handles the event. The release of a button passes through without finishing it, and a curtain that starts disarms an armed press of a side button.
Measured on the bench board 6c:0a, a settings curtain takes 356 to 380 ms over 11 frames, and every frame lands within two or three refreshes of the panel.
Entry, exit and the tick
On entry, gui_screen_enter() loads the screen with a cut, since the curtain has shown it. The entry also:
- Sets the cursor where it was left, because
set_seloutlives the screen. - Paints the control centre.
- Posts five requests:
display_state_request(), a read ofconfig key="vol"for AO_Audio,wifi_state_request(),channel_state_request()andble_state_request().
Battery is not among the requests, because AO_Gui already holds the last publish.
The tick has three extra jobs here:
- Every 2 s (
GUI_SET_RSSI_MS) it callswifi_rssi_request(). It does not while the keypad is up. - It pulses the glyph of the Bluetooth circle while the radio advertises.
- It counts the auto-lock and the hold of BOT towards the lock.
Nothing polls a radio for a screen nobody looks at.
Four subscriptions feed the circles. Gui_running handles them and not Gui_settings, so a view opens with current values rather than the last visit's.
| Event | Sets | Used by |
|---|---|---|
POWER_EVT_SIG (every 2 s) | soc, bat_state, vbat_mv, current_ma | Battery page |
POWER_FIELD_SIG | temp_c, temp_known | Battery page |
WIFI_STATE_SIG | wifi_on, wifi_linked, wifi_rssi, wifi_ssid | Wi-Fi circle and page |
BLE_STATE_SIG | ble_on, adv_on, ble_console, ble_bonds | Bluetooth circle and page |
DISPLAY_STATE_SIG | bright, through gui_seed_bright() | The brightness level |
RADIO_STATE_SIG | c5_fw[], c5_fw_known | About's Radio row |
STORAGE_INFO_SIG | sd_mounted, sd_card_mb, sd_known | About's card row |
WIFI_SCAN_*, WIFI_SAVED_* | scan_ssid[], scan_rssi[], scan_auth[], saved_ssid[] | Wi-Fi page |
Specifications
The page rail
The numbers of the Settings page and of every page, all named defines of kgui_screens.h.
| Thing | Value |
|---|---|
| Title | y 30 (KGUI_PAGE_TITLE_Y), Outfit Bold 28 |
| Body | y 68 to 458 (KGUI_PAGE_BODY_Y, KGUI_PAGE_BODY_H 390) |
| Row | 92 px tall, 12 px gap (KGUI_PAGE_RAIL_ROW_H, _GAP) |
| Disc | 56 px at x 30 (KGUI_PAGE_RAIL_DISC), glyph 30 px |
| Name and value | From x 104 |
| Capsule | 366 px wide at x 22 (KGUI_PAGE_RAIL_CAPSULE_W) |
| Focus | y 215 (KGUI_PAGE_RAIL_FOCUS_Y); not on Wi-Fi and Bluetooth |
| Pinned switch | Wi-Fi and Bluetooth: one row and its gap, 104 px, kept at the body's top (KGUI_PAGE_PIN_H) |
| Fade | From y 99 over 50 px at the top, from y 430 over 70 px at the foot |
| Page growth out of the middle | 160 ms (GUI_GROW_IN_MS) |
| Page back into the middle | 180 ms (GUI_GROW_OUT_MS) |
| Page slide, when the pictures fail | 180 ms (GUI_PAGE_ANIM_MS) |
| Radio page header | Disc 56 px at y 26, 16 px to the words, words 240 px at most, body from y 100 (KGUI_PAGE_HDR_*) |
The curtain
| Thing | Value |
|---|---|
| Page travel | 0 to 502 |
| Spring | Critically damped, 0.25 s, lands within 320 ms |
| Dim | 0 to 40 % (KGUI_CURTAIN_DIM_OPA), the settings curtain only |
| Release | 800 px/s flings; otherwise half decides |
| Claim | 12 px from the landing |
Gotchas
Example code
Read a control's state and open its page. Each step ends with something you see:
- On the face, press the pad's up button. The settings screen comes down with the ring on Wi-Fi and the label row reading its state.
- Look at the Bluetooth circle. It is blue and pulses once a second while the board advertises, and grey when Bluetooth is off.
- Press the pad's down button twice. The ring goes to the battery and then to the brightness level. Press the pad's right button and the level grows by 5 %.
- Press the pad's up button twice. The ring walks to Turn off and then to Settings. Press TOP. The Settings page grows out of the middle with the ring on Wi-Fi. Walk down to Vibration and press TOP: its value steps to the next, and nothing opens.
- Press BOT to go back to the control centre, then swipe up anywhere to return to the face.
In the SDK
kgui_screen_settings.c: the circles, the battery's liquid and the two levels.kgui_screens.hholds their numbers and the page rail's.kgui_ao.c:Gui_settings,gui_cc_paint(),gui_settings_act()and the curtain.kgui_curtain.c: the two pictures of a curtain.components/kui: the focus ring, the glyphs and the drag module.