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
+73
View File
@@ -0,0 +1,73 @@
#include "usage_rate.h"
#include <Arduino.h>
// Thresholds in %/min. A 5-hour (300 min) session ÷ 100% = 0.33 %/min to fill
// exactly at the same pace as the session itself resets — the user wants the
// "heavy" tier to start right there (filling in 45 hours).
// < 0.10 → Idle (17h+ to fill, basically dormant)
// < 0.20 → Normal (817h to fill, slow steady use)
// < 0.33 → Active (58h, heavy but not yet pace-matching)
// >=0.33 → Heavy (≤5h, matching or beating the session reset)
#define RATE_THRESH_NORMAL 0.10f
#define RATE_THRESH_ACTIVE 0.20f
#define RATE_THRESH_HEAVY 0.33f
// Minimum span between oldest and newest sample before we trust the computed
// rate. The whole point of the ring buffer is to smooth out single-sample
// jitter — at 60s daemon polling, a 1% bump between two consecutive samples
// looks like 1 %/min (Heavy) but really just means you grew 1% in the last
// minute. We require ~4 min of accumulated history so the rate reflects a
// real trend, not one noisy delta. Side-effect: ~4 min warm-up after boot
// during which we report Idle.
#define MIN_WINDOW_MS 240000UL
#define RING_SIZE 6
struct Sample { uint32_t ms; float pct; };
static Sample ring[RING_SIZE];
static uint8_t count = 0;
static uint8_t head = 0; // index of next write slot
static inline uint8_t oldest_idx(void) {
return (head + RING_SIZE - count) % RING_SIZE;
}
void usage_rate_reset(void) {
count = 0;
head = 0;
}
void usage_rate_sample(float session_pct) {
uint32_t now = millis();
if (count > 0) {
uint8_t latest = (head + RING_SIZE - 1) % RING_SIZE;
// Session reset: pct dropped substantially. Restart tracking.
if (session_pct + 5.0f < ring[latest].pct) {
usage_rate_reset();
}
}
ring[head] = { now, session_pct };
head = (head + 1) % RING_SIZE;
if (count < RING_SIZE) count++;
}
int usage_rate_group(void) {
if (count < 2) return 0;
uint8_t o = oldest_idx();
uint8_t l = (head + RING_SIZE - 1) % RING_SIZE;
uint32_t dt = ring[l].ms - ring[o].ms;
if (dt < MIN_WINDOW_MS) return 0;
float dp = ring[l].pct - ring[o].pct;
if (dp < 0.0f) dp = 0.0f;
float rate = dp * 60000.0f / (float)dt;
if (rate < RATE_THRESH_NORMAL) return 0;
if (rate < RATE_THRESH_ACTIVE) return 1;
if (rate < RATE_THRESH_HEAVY) return 2;
return 3;
}