Files
clawdmeter/firmware/src/splash.cpp
T
Hermann Björgvin Haraldsson f1feacb3dc Remove dead functions and unused Montserrat font flags
Six public API functions had no callers anywhere in the firmware:
splash_prev, splash_set_active, splash_current_name, splash_count,
ble_is_connected, imu_set_rotation, power_is_usb_connected,
power_usb_changed. Trimming them lets power.cpp drop the VBUS state
machine (cached_usb, usb_changed_flag, last_vbus_ms,
enableVbusVoltageMeasure). usage_rate_reset becomes file-static since
the only caller is in usage_rate.cpp itself. Montserrat font flags in
platformio.ini were enabled but never referenced.
2026-05-11 02:19:45 +00:00

204 lines
6.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "splash.h"
#include "splash_animations.h"
#include "theme.h"
#include "usage_rate.h"
#include <Arduino.h>
#include <string.h>
#include <esp_heap_caps.h>
// 20x20 grid scaled 24x to fill 480x480
#define GRID 20
#define CELL 24
#define CANVAS_W (GRID * CELL)
#define CANVAS_H (GRID * CELL)
// Background fallback when palette is missing
#define COL_EMPTY 0x0000 // true black (matches THEME_BG)
LV_FONT_DECLARE(font_styrene_28);
static lv_obj_t *splash_container = NULL;
static lv_obj_t *canvas = NULL;
static lv_obj_t *label_status = NULL; // shown only when no animations loaded
static uint16_t *canvas_buf = NULL; // 480x480 RGB565 (PSRAM)
static uint16_t cur_anim = 0;
static uint16_t cur_frame = 0;
static uint32_t frame_started_ms = 0;
static uint32_t last_pick_ms = 0;
static bool active = false;
// While splash is showing, auto-cycle to the next animation in the current
// rate-driven group every this many ms.
#define SPLASH_ROTATE_INTERVAL_MS 20000
// Usage-rate animation groups: 4 groups × up to 4 animations each.
// Filled at init by matching literal names from splash_anims[].
#define GROUP_COUNT 4
#define GROUP_MAX 4
static int8_t group_lists[GROUP_COUNT][GROUP_MAX];
static uint8_t group_size[GROUP_COUNT] = {0};
static uint8_t group_rotation[GROUP_COUNT] = {0};
static const char* GROUP_NAMES[GROUP_COUNT][GROUP_MAX] = {
// Group 0 — idle / sleepy
{ "expression sleep", "idle breathe", "idle blink", "expression wink" },
// Group 1 — normal pace
{ "idle look around", "work think", "work coding", NULL },
// Group 2 — active
{ "dance sway", "expression surprise", "dance bounce", NULL },
// Group 3 — heavy
{ "dance bounce dj", "dance sway dj", "dance djmix", NULL },
};
static void resolve_group_lists(void) {
for (int g = 0; g < GROUP_COUNT; g++) {
group_size[g] = 0;
for (int s = 0; s < GROUP_MAX; s++) {
group_lists[g][s] = -1;
const char* want = GROUP_NAMES[g][s];
if (!want) continue;
for (int i = 0; i < SPLASH_ANIM_COUNT; i++) {
if (strcmp(splash_anims[i].name, want) == 0) {
group_lists[g][group_size[g]++] = (int8_t)i;
break;
}
}
}
}
}
static void render_frame(const uint8_t *cells, const uint16_t *palette) {
for (int gy = 0; gy < GRID; gy++) {
uint16_t row[CANVAS_W];
for (int gx = 0; gx < GRID; gx++) {
uint8_t code = cells[gy * GRID + gx];
uint16_t color = (palette && code < SPLASH_PALETTE_SIZE) ? palette[code] : COL_EMPTY;
uint16_t *p = &row[gx * CELL];
for (int i = 0; i < CELL; i++) p[i] = color;
}
for (int dy = 0; dy < CELL; dy++) {
memcpy(&canvas_buf[(gy * CELL + dy) * CANVAS_W], row, CANVAS_W * 2);
}
}
if (canvas) lv_obj_invalidate(canvas);
}
static void show_placeholder() {
// Solid dark background + centered status label.
for (int i = 0; i < CANVAS_W * CANVAS_H; i++) canvas_buf[i] = COL_EMPTY;
if (canvas) lv_obj_invalidate(canvas);
if (label_status) lv_obj_clear_flag(label_status, LV_OBJ_FLAG_HIDDEN);
}
void splash_init(lv_obj_t *parent) {
canvas_buf = (uint16_t*)heap_caps_malloc(CANVAS_W * CANVAS_H * 2, MALLOC_CAP_SPIRAM);
if (!canvas_buf) {
Serial.println("splash: failed to alloc canvas buffer");
return;
}
splash_container = lv_obj_create(parent);
lv_obj_set_size(splash_container, 480, 480);
lv_obj_set_pos(splash_container, 0, 0);
lv_obj_set_style_bg_color(splash_container, THEME_BG, 0);
lv_obj_set_style_bg_opa(splash_container, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(splash_container, 0, 0);
lv_obj_set_style_pad_all(splash_container, 0, 0);
lv_obj_clear_flag(splash_container, LV_OBJ_FLAG_SCROLLABLE);
canvas = lv_canvas_create(splash_container);
lv_canvas_set_buffer(canvas, canvas_buf, CANVAS_W, CANVAS_H, LV_COLOR_FORMAT_RGB565);
lv_obj_center(canvas);
// Placeholder label (visible only when no animations are loaded)
label_status = lv_label_create(splash_container);
lv_label_set_text(label_status,
"no animations loaded\n\n"
"run tools/scrape_claudepix.js\n"
"then tools/convert_to_c.js");
lv_obj_set_style_text_font(label_status, &font_styrene_28, 0);
lv_obj_set_style_text_color(label_status, lv_color_hex(0xb0aea5), 0);
lv_obj_set_style_text_align(label_status, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(label_status);
resolve_group_lists();
if (SPLASH_ANIM_COUNT == 0) {
show_placeholder();
} else {
lv_obj_add_flag(label_status, LV_OBJ_FLAG_HIDDEN);
const splash_anim_def_t *a = &splash_anims[0];
render_frame(a->frames[0], a->palette);
frame_started_ms = millis();
}
lv_obj_add_flag(splash_container, LV_OBJ_FLAG_HIDDEN);
}
void splash_tick(void) {
if (!active || SPLASH_ANIM_COUNT == 0) return;
// Auto-rotate to the next animation in the current group.
if (millis() - last_pick_ms >= SPLASH_ROTATE_INTERVAL_MS) {
splash_pick_for_current_rate();
}
const splash_anim_def_t *a = &splash_anims[cur_anim];
if (a->frame_count == 0) return;
uint16_t hold = a->holds[cur_frame];
if (millis() - frame_started_ms >= hold) {
cur_frame = (cur_frame + 1) % a->frame_count;
frame_started_ms = millis();
render_frame(a->frames[cur_frame], a->palette);
}
}
void splash_next(void) {
if (SPLASH_ANIM_COUNT == 0) return;
cur_anim = (cur_anim + 1) % SPLASH_ANIM_COUNT;
cur_frame = 0;
frame_started_ms = millis();
last_pick_ms = frame_started_ms;
const splash_anim_def_t *a = &splash_anims[cur_anim];
render_frame(a->frames[0], a->palette);
Serial.printf("splash: -> %s\n", a->name);
}
void splash_pick_for_current_rate(void) {
if (SPLASH_ANIM_COUNT == 0) return;
int g = usage_rate_group();
if (g < 0 || g >= GROUP_COUNT) g = 0;
if (group_size[g] == 0) return;
uint8_t slot = group_rotation[g] % group_size[g];
group_rotation[g]++;
int8_t idx = group_lists[g][slot];
if (idx < 0) return;
cur_anim = (uint16_t)idx;
cur_frame = 0;
frame_started_ms = millis();
last_pick_ms = frame_started_ms;
const splash_anim_def_t *a = &splash_anims[cur_anim];
render_frame(a->frames[0], a->palette);
}
bool splash_is_active(void) { return active; }
void splash_show(void) {
splash_pick_for_current_rate();
if (splash_container) lv_obj_clear_flag(splash_container, LV_OBJ_FLAG_HIDDEN);
active = true;
}
void splash_hide(void) {
if (splash_container) lv_obj_add_flag(splash_container, LV_OBJ_FLAG_HIDDEN);
active = false;
}
lv_obj_t* splash_get_root(void) {
return splash_container;
}