Drive splash animations by session usage rate

Group splash animations into four moods (idle/normal/active/heavy) and pick
which group plays based on a 5-min sliding rate of session_pct. Thresholds
sit at 0.10/0.20/0.33 %/min so Heavy fires when usage matches or beats the
5-hour session reset pace. Splash auto-rotates within the current group
every 20s; group recomputes on every BLE poll and the splash re-picks
immediately if it changes mid-display. 4-min minimum window prevents
single-sample bumps after boot from falsely landing in Heavy.
This commit is contained in:
Hermann Björgvin Haraldsson
2026-05-11 02:05:38 +00:00
parent 12172d12bf
commit 7ed4710d28
5 changed files with 180 additions and 0 deletions
+75
View File
@@ -1,7 +1,9 @@
#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
@@ -23,8 +25,49 @@ 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];
@@ -79,6 +122,8 @@ void splash_init(lv_obj_t *parent) {
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 {
@@ -93,6 +138,12 @@ void splash_init(lv_obj_t *parent) {
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;
@@ -109,6 +160,7 @@ void splash_next(void) {
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);
@@ -119,6 +171,7 @@ void splash_prev(void) {
cur_anim = (cur_anim + SPLASH_ANIM_COUNT - 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);
@@ -126,7 +179,29 @@ void splash_prev(void) {
void splash_set_active(bool a) { active = a; }
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;
}