diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c14acc..2e4b260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/firmware/src/config.h b/firmware/src/config.h index d1178f2..ce106e0 100644 --- a/firmware/src/config.h +++ b/firmware/src/config.h @@ -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; diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 03d0313..a97bb67 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -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(); @@ -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 diff --git a/firmware/src/ui/i18n.cpp b/firmware/src/ui/i18n.cpp index fcfdf8e..ef98b7e 100644 --- a/firmware/src/ui/i18n.cpp +++ b/firmware/src/ui/i18n.cpp @@ -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, diff --git a/firmware/src/ui/i18n.h b/firmware/src/ui/i18n.h index d3529c8..784c0d8 100644 --- a/firmware/src/ui/i18n.h +++ b/firmware/src/ui/i18n.h @@ -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 }; diff --git a/firmware/src/ui/screens.h b/firmware/src/ui/screens.h index b80926d..f090995 100644 --- a/firmware/src/ui/screens.h +++ b/firmware/src/ui/screens.h @@ -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) diff --git a/firmware/src/ui/screens/overlays.cpp b/firmware/src/ui/screens/overlays.cpp index a164fc8..a2802e7 100644 --- a/firmware/src/ui/screens/overlays.cpp +++ b/firmware/src/ui/screens/overlays.cpp @@ -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 diff --git a/firmware/src/ui/ui.cpp b/firmware/src/ui/ui.cpp index af69847..b000a98 100644 --- a/firmware/src/ui/ui.cpp +++ b/firmware/src/ui/ui.cpp @@ -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 @@ -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. diff --git a/sim/lv_conf.h b/sim/lv_conf.h index 469332d..09fa1c8 100644 --- a/sim/lv_conf.h +++ b/sim/lv_conf.h @@ -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 diff --git a/sim/main.cpp b/sim/main.cpp index 5aa9cc5..0be3e0a 100644 --- a/sim/main.cpp +++ b/sim/main.cpp @@ -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, diff --git a/tests/fixtures_baseline/boot.png b/tests/fixtures_baseline/boot.png new file mode 100644 index 0000000..4016050 Binary files /dev/null and b/tests/fixtures_baseline/boot.png differ diff --git a/tests/fixtures_baseline/i18n/de/boot.png b/tests/fixtures_baseline/i18n/de/boot.png new file mode 100644 index 0000000..40029a2 Binary files /dev/null and b/tests/fixtures_baseline/i18n/de/boot.png differ diff --git a/tests/fixtures_baseline/i18n/es/boot.png b/tests/fixtures_baseline/i18n/es/boot.png new file mode 100644 index 0000000..5e58590 Binary files /dev/null and b/tests/fixtures_baseline/i18n/es/boot.png differ diff --git a/tests/fixtures_baseline/i18n/fr/boot.png b/tests/fixtures_baseline/i18n/fr/boot.png new file mode 100644 index 0000000..f432c72 Binary files /dev/null and b/tests/fixtures_baseline/i18n/fr/boot.png differ diff --git a/tests/fixtures_baseline/i18n/pt/boot.png b/tests/fixtures_baseline/i18n/pt/boot.png new file mode 100644 index 0000000..ab39cc8 Binary files /dev/null and b/tests/fixtures_baseline/i18n/pt/boot.png differ