Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable, behaviour-affecting changes land here. Format follows
uses lightweight semantic-ish versioning (bumped on any user-visible
change, not on every commit).

## v0.29.9 — 2026-06

### Added

- **Boot / "loading" splash.** On power-up the device now shows a "Bamboard"
splash — a spinner over a status line that walks the boot phases (Connecting
to Wi-Fi… → Checking for updates… → Connecting to Bambuddy…) — instead of a
frozen, data-less dashboard during the ~20-30 s it takes to join Wi-Fi, run
the boot-time OTA check and pull the first data. It dismisses itself the
moment printer data arrives, or after a 25 s grace period so an unreachable
Bambuddy can't pin it. All five UI languages.

## v0.29.8 — 2026-06

### Fixed
Expand Down
5 changes: 5 additions & 0 deletions firmware/src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ constexpr uint32_t DIM_AFTER_MS = 60UL * 1000UL;
// Ambient idle clock: floats over the current screen once the panel has been
// untouched this long AND the whole farm is quiet (nothing printing/faulted).
constexpr uint32_t AMBIENT_AFTER_MS = 45UL * 1000UL;

// Boot "loading" splash: the longest it stays up before giving way to the
// (possibly offline) dashboard, so an unreachable Bambuddy can't pin it forever.
// Covers the worst case: Wi-Fi connect (~15 s) + boot-time OTA check (~8 s).
constexpr uint32_t BOOT_SPLASH_MAX_MS = 25UL * 1000UL;
constexpr uint8_t BL_FULL = 255;
constexpr uint8_t BL_DIM = 40;

Expand Down
7 changes: 7 additions & 0 deletions firmware/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,8 @@ void setup() {
// current / GitHub down all fall through quickly so the device still
// boots. Dev-sentinel builds skip it so a USB-flashed work-in-progress
// isn't immediately pulled back to the published release.
ui::screens::boot_overlay_set_status(::i18n::tr(::i18n::Str::BOOT_UPDATE));
lv_timer_handler();
#if BAMBOARD_OTA_AUTOCHECK
if (!BAMBOARD_VERSION_IS_DEV) {
run_boot_update();
Expand All @@ -978,6 +980,11 @@ void setup() {
bambuddy::g_client.begin(g_cfg_bambuddy_url, g_cfg_api_key,
g_cfg_cf_id, g_cfg_cf_secret);

// Final boot phase: the net task (started just below) reaches Bambuddy. Show
// it on the splash; the UI manager dismisses the splash once data lands.
ui::screens::boot_overlay_set_status(::i18n::tr(::i18n::Str::BOOT_CONNECT));
lv_timer_handler();

// Arm the task watchdog (10 s, panic+reboot on timeout) before the UI task
// subscribes itself. Best-effort: if the Arduino runtime already initialised
// the TWDT this is a no-op and the existing timeout stands — the UI loop
Expand Down
4 changes: 4 additions & 0 deletions firmware/src/ui/i18n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ static const char* const T[][(int)Lang::COUNT] = {
/*CONFIRM_REMOVE*/{"Tap again to remove (3 s)", "Toca otra vez para quitar (3 s)", "Touchez encore pour retirer (3 s)", "Toque novamente p/ remover (3 s)", "Nochmal zum Entfernen (3 s)"},
/*AMBIENT_ALL_IDLE*/ {"All idle", "Todo inactivo", "Tout au repos", "Tudo parado", "Alles im Leerlauf"},
/*AMBIENT_NEXT*/ {"Next", "Siguiente", "Suivant", "Próximo", "Nächste"},
// -- boot splash --
/*BOOT_WIFI*/ {"Connecting to Wi-Fi...", "Conectando al Wi-Fi...", "Connexion au Wi-Fi...", "A ligar ao Wi-Fi...", "Verbinde mit WLAN..."},
/*BOOT_UPDATE*/ {"Checking for updates...", "Buscando actualizaciones...", "Recherche de mises à jour...", "A procurar atualizações...", "Suche nach Updates..."},
/*BOOT_CONNECT*/ {"Connecting to Bambuddy...", "Conectando a Bambuddy...", "Connexion à Bambuddy...", "A ligar ao Bambuddy...", "Verbinde mit Bambuddy..."},
};

static_assert(sizeof(T) / sizeof(T[0]) == (size_t)Str::_COUNT,
Expand Down
2 changes: 2 additions & 0 deletions firmware/src/ui/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ enum class Str : uint16_t {
QUEUE_EMPTY, QUEUE_POS, QUEUE_REMOVED, QUEUE_REMOVE_FAILED, CONFIRM_REMOVE,
// ambient idle clock
AMBIENT_ALL_IDLE, AMBIENT_NEXT,
// boot splash status
BOOT_WIFI, BOOT_UPDATE, BOOT_CONNECT,
_COUNT
};

Expand Down
10 changes: 10 additions & 0 deletions firmware/src/ui/screens.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ void ota_set_error (const char* msg);
void ota_apply ();
bool ota_is_active ();

// Full-screen boot / "loading" splash — the Bamboard wordmark, a spinner and a
// status line. Built visible at startup and dismissed by the UI manager once
// the device has printer data (or after BOOT_SPLASH_MAX_MS), so the user sees a
// clear "starting up" screen instead of a frozen, dataless dashboard.
// boot_overlay_set_status() is UI/setup-task only (called during boot phases).
lv_obj_t* build_boot_overlay(lv_obj_t* parent);
void boot_overlay_set_status(const char* msg);
void boot_overlay_hide();
bool boot_overlay_is_visible();

// Full-screen camera snapshot overlay. Opened by tapping the dashboard
// progress ring; while it's open the net task fetches + decodes JPEG frames
// (camera_decode_frame, called ON the net task), camera_apply() (UI task)
Expand Down
66 changes: 66 additions & 0 deletions firmware/src/ui/screens/overlays.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -850,4 +850,70 @@ void temp_graph_close() {
}
bool temp_graph_is_open() { return s_tg_open; }

// ---------------------------------------------------------------------------
// Boot / "loading" splash
// ---------------------------------------------------------------------------
//
// Full-screen overlay shown from power-on while Wi-Fi connects, the boot-time
// OTA check runs and the first Bambuddy data arrives — so the user sees an
// obvious "starting up" screen (brand wordmark + spinner + a per-phase status
// line) instead of a frozen, data-less dashboard. Built LAST in
// ui::Manager::begin() so it z-orders above everything and — unlike the other
// overlays — starts VISIBLE. ui::Manager::refresh() calls boot_overlay_hide()
// once a printer snapshot lands (or BOOT_SPLASH_MAX_MS elapses).

static lv_obj_t* s_boot_overlay = nullptr;
static lv_obj_t* s_boot_status = nullptr;

lv_obj_t* build_boot_overlay(lv_obj_t* parent) {
ensure_styles();
s_boot_overlay = lv_obj_create(parent);
lv_obj_remove_style_all(s_boot_overlay);
lv_obj_set_size(s_boot_overlay, LV_HOR_RES, LV_VER_RES);
lv_obj_align(s_boot_overlay, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_set_style_bg_color(s_boot_overlay, lv_color_hex(::ui::C_BG), 0);
lv_obj_set_style_bg_opa(s_boot_overlay, LV_OPA_COVER, 0);
lv_obj_clear_flag(s_boot_overlay, LV_OBJ_FLAG_SCROLLABLE);

// Brand wordmark — "board" recoloured in accent, same as the header.
lv_obj_t* mark = lv_label_create(s_boot_overlay);
lv_label_set_recolor(mark, true);
lv_label_set_text(mark, "Bam#00B7C3 board#");
lv_obj_set_style_text_font(mark, &bb_font_36, 0);
lv_obj_set_style_text_color(mark, lv_color_hex(::ui::C_TEXT), 0);
lv_obj_align(mark, LV_ALIGN_CENTER, 0, -44);

// Spinner — advances whenever lv_timer_handler() runs (the Wi-Fi-connect
// wait and once the UI task is up; it pauses during the single blocking
// OTA-check call, which the status line covers).
lv_obj_t* spin = lv_spinner_create(s_boot_overlay, 1000, 60);
lv_obj_set_size(spin, 46, 46);
lv_obj_align(spin, LV_ALIGN_CENTER, 0, 16);
lv_obj_set_style_arc_color(spin, lv_color_hex(::ui::C_PANEL_HI), LV_PART_MAIN);
lv_obj_set_style_arc_width(spin, 5, LV_PART_MAIN);
lv_obj_set_style_arc_color(spin, lv_color_hex(::ui::C_ACCENT), LV_PART_INDICATOR);
lv_obj_set_style_arc_width(spin, 5, LV_PART_INDICATOR);

s_boot_status = lv_label_create(s_boot_overlay);
lv_label_set_text(s_boot_status, i18n::tr(i18n::Str::BOOT_WIFI));
lv_obj_set_style_text_font(s_boot_status, &bb_font_14, 0);
lv_obj_set_style_text_color(s_boot_status, lv_color_hex(::ui::C_TEXT_DIM), 0);
lv_obj_align(s_boot_status, LV_ALIGN_CENTER, 0, 70);

// Starts visible (no HIDDEN flag) — it's the power-on state.
return s_boot_overlay;
}

void boot_overlay_set_status(const char* msg) {
if (s_boot_status && msg) lv_label_set_text(s_boot_status, msg);
}

void boot_overlay_hide() {
if (s_boot_overlay) lv_obj_add_flag(s_boot_overlay, LV_OBJ_FLAG_HIDDEN);
}

bool boot_overlay_is_visible() {
return s_boot_overlay && !lv_obj_has_flag(s_boot_overlay, LV_OBJ_FLAG_HIDDEN);
}

} // namespace ui::screens
12 changes: 12 additions & 0 deletions firmware/src/ui/ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ void Manager::begin() {
// Live temperature graph — likewise a hidden overlay floated on demand.
screens::build_temp_graph_overlay(s_root);

// Boot / "loading" splash — built LAST so it covers every screen + overlay,
// and (uniquely) starts VISIBLE. refresh() dismisses it once data lands.
screens::build_boot_overlay(s_root);

// Show the initial screen directly. We can't use go_to() here: current_
// already starts at Dashboard, so go_to(Dashboard) would early-return on
// its `s == current_` guard and leave the screen hidden (the loop above
Expand Down Expand Up @@ -137,6 +141,14 @@ void Manager::refresh() {
selected_printer_id_ = ps[0].id;
}

// Dismiss the boot/loading splash once we have printer data (or after a
// grace period, so an unreachable Bambuddy can't pin it) — revealing the
// dashboard underneath instead of a frozen, data-less screen.
if (screens::boot_overlay_is_visible() &&
(n > 0 || millis() > ::display::BOOT_SPLASH_MAX_MS)) {
screens::boot_overlay_hide();
}

// Fire a one-shot toast when any printer leaves a printing state for
// Finish (✓) or Failed/Error. Skipped on the first sight of a printer
// (no known previous state) so an already-finished job doesn't toast at boot.
Expand Down
4 changes: 3 additions & 1 deletion sim/lv_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@
#define LV_USE_MSGBOX 0
#define LV_USE_SPAN 0
#define LV_USE_SPINBOX 0
#define LV_USE_SPINNER 0
/* SPINNER on: the boot/loading splash (overlays.cpp build_boot_overlay) uses
lv_spinner; the firmware lv_conf enables it too. Depends on LV_USE_ARC (above). */
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 0
#define LV_USE_TILEVIEW 0
#define LV_USE_WIN 0
Expand Down
7 changes: 7 additions & 0 deletions sim/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ static void render_fixtures(const std::string& out) {
auto& C = bambuddy::g_client;
JsonDocument d;

// Boot / "loading" splash — rendered before any data is injected, so the
// overlay (built visible in ui::Manager::begin) is still up. Draw it with
// lv_timer_handler() directly, NOT pump(): pump() calls ui.refresh(), whose
// hide logic would dismiss the splash the moment it sees no printers.
lv_timer_handler(); lv_timer_handler();
dump_png(out, "boot");

// A small farm: #1 (focused) mid-print with a chained AMS — first unit
// drying; #2 also printing; #3 idle.
demo_status(1,
Expand Down
Binary file added tests/fixtures_baseline/boot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures_baseline/i18n/de/boot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures_baseline/i18n/es/boot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures_baseline/i18n/fr/boot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures_baseline/i18n/pt/boot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading