#include "battery_est.h" #include // Anchor-based, not a ring buffer (cf. usage_rate.cpp): we remember (time, pct) // at the moment discharge began and project from the average rate since then. // rate = (anchor_pct - pct_now) / minutes_elapsed // minutes_left = pct_now / rate // A long baseline beats a short ring for a signal that only ticks every few // minutes — the estimate just keeps tightening as the session runs. We withhold // a number until the cell has dropped a meaningful amount over a few minutes, so // the first figure isn't built on fuel-gauge settling noise right after boot. #define EST_MIN_DROP_PCT 3 // need >= 3% drained since the anchor #define EST_MIN_ELAPSED_MS 300000UL // ...over at least 5 minutes #define EST_MAX_MINUTES (100 * 60) static bool have_anchor = false; static uint32_t anchor_ms = 0; static int anchor_pct = -1; static int last_pct = -1; static bool last_charging = false; void battery_est_update(int percent, bool charging) { last_charging = charging; last_pct = percent; if (charging || percent < 0) { // On USB / charging / no reading the estimate is meaningless. Drop the // anchor so the next discharge starts from a fresh baseline. have_anchor = false; return; } // Discharging. Start an anchor, or re-anchor if the gauge ticked UP (it // relaxes upward when load drops) so we never compute a negative rate. if (!have_anchor || percent > anchor_pct) { have_anchor = true; anchor_ms = millis(); anchor_pct = percent; } } int battery_est_minutes(void) { if (last_charging || last_pct < 0) return -2; if (!have_anchor) return -1; int drop = anchor_pct - last_pct; uint32_t dt = millis() - anchor_ms; if (drop < EST_MIN_DROP_PCT || dt < EST_MIN_ELAPSED_MS) return -1; // Minutes each 1% has been taking, extrapolated across the remaining pct. float minutes_per_pct = (float)dt / 60000.0f / (float)drop; float mins = (float)last_pct * minutes_per_pct; if (mins < 0) return -1; if (mins > EST_MAX_MINUTES) mins = EST_MAX_MINUTES; return (int)(mins + 0.5f); }