feat(oled): provisional charge ETA on plug-in — no more ~--m wait
Show a default-rate estimate "~Nm?" the moment charging starts instead of "~--m" for the ~15-20 min until the first 10% step is timed. The "?" drops and the number switches to the measured rate once a clean step completes. Default ~15 min per 10% step (kDefaultStepUs), taper-weighted like the measured path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d99689bf75
commit
0eceb1d5e2
+32
-12
@@ -535,12 +535,20 @@ void handle_buttons() {
|
|||||||
// step's taper weight); the remaining steps are then re-weighted. This makes
|
// step's taper weight); the remaining steps are then re-weighted. This makes
|
||||||
// the estimate consistent whether the user plugged in near-empty or near-full.
|
// the estimate consistent whether the user plugged in near-empty or near-full.
|
||||||
struct ChargeEta {
|
struct ChargeEta {
|
||||||
bool charging; // pstate == 1 (so the token shows only while charging)
|
bool charging; // pstate == 1 (so the token shows only while charging)
|
||||||
bool valid; // at least one full step timed → minutes is meaningful
|
bool valid; // minutes is meaningful (provisional or measured)
|
||||||
int minutes; // estimated minutes to 100%
|
bool provisional; // true until a full step is timed — using the default rate
|
||||||
|
int minutes; // estimated minutes to 100%
|
||||||
};
|
};
|
||||||
ChargeEta g_charge_eta{};
|
ChargeEta g_charge_eta{};
|
||||||
|
|
||||||
|
// Default bulk-step duration used for a provisional estimate before any real
|
||||||
|
// step has been timed, so the token shows "~Nm?" immediately on plug-in instead
|
||||||
|
// of sitting on "~--m" for ~15-20 min. Tuned to an observed ~15 min per 10% on
|
||||||
|
// this dongle's charge current; it self-corrects to the measured rate (and drops
|
||||||
|
// the "?") as soon as the first clean step completes.
|
||||||
|
constexpr float kDefaultStepUs = 15.0f * 60.0f * 1000000.0f;
|
||||||
|
|
||||||
// Relative time the step *ending* at `to_level` (10% units, 1..10) takes vs a
|
// Relative time the step *ending* at `to_level` (10% units, 1..10) takes vs a
|
||||||
// bulk step. Tuned to the Li-ion CV taper: ~80% onward stretches out.
|
// bulk step. Tuned to the Li-ion CV taper: ~80% onward stretches out.
|
||||||
static float charge_step_weight(int to_level) {
|
static float charge_step_weight(int to_level) {
|
||||||
@@ -605,20 +613,30 @@ void sample_charge_eta() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_charge_eta.charging = true;
|
g_charge_eta.charging = true;
|
||||||
if (ring_count > 0 && cur_step < 10) {
|
if (cur_step < 10) {
|
||||||
float bulk = 0.0f;
|
// Use the measured rate once we have a timed step; until then fall back
|
||||||
for (int i = 0; i < ring_count; i++) bulk += ring[i];
|
// to the default rate and flag the estimate provisional (renders "?").
|
||||||
bulk /= (float)ring_count;
|
const bool measured = (ring_count > 0);
|
||||||
|
float bulk;
|
||||||
|
if (measured) {
|
||||||
|
bulk = 0.0f;
|
||||||
|
for (int i = 0; i < ring_count; i++) bulk += ring[i];
|
||||||
|
bulk /= (float)ring_count;
|
||||||
|
} else {
|
||||||
|
bulk = kDefaultStepUs;
|
||||||
|
}
|
||||||
float rem_us = 0.0f;
|
float rem_us = 0.0f;
|
||||||
for (int L = cur_step + 1; L <= 10; L++) rem_us += bulk * charge_step_weight(L);
|
for (int L = cur_step + 1; L <= 10; L++) rem_us += bulk * charge_step_weight(L);
|
||||||
int mins = (int)(rem_us / 60000000.0f + 0.5f);
|
int mins = (int)(rem_us / 60000000.0f + 0.5f);
|
||||||
if (mins < 0) mins = 0;
|
if (mins < 0) mins = 0;
|
||||||
if (mins > 999) mins = 999;
|
if (mins > 999) mins = 999;
|
||||||
g_charge_eta.valid = true;
|
g_charge_eta.valid = true;
|
||||||
|
g_charge_eta.provisional = !measured;
|
||||||
g_charge_eta.minutes = mins;
|
g_charge_eta.minutes = mins;
|
||||||
} else {
|
} else {
|
||||||
// cur_step == 10 → essentially full; nothing meaningful to count down.
|
// cur_step == 10 → essentially full; nothing meaningful to count down.
|
||||||
g_charge_eta.valid = (cur_step >= 10);
|
g_charge_eta.valid = true;
|
||||||
|
g_charge_eta.provisional = false;
|
||||||
g_charge_eta.minutes = 0;
|
g_charge_eta.minutes = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,13 +673,15 @@ __attribute__((noinline)) void render_screen() {
|
|||||||
draw_text(kContentX, 18, bbuf);
|
draw_text(kContentX, 18, bbuf);
|
||||||
draw_battery_icon(36, 18, pct);
|
draw_battery_icon(36, 18, pct);
|
||||||
|
|
||||||
// Charge ETA, right of the battery icon (icon ends at x≈90). Shown
|
// Charge ETA, right of the battery icon (icon ends at x≈90). Shown only
|
||||||
// only while charging: "~43m" once a step has been timed, "~--m" while
|
// while charging: "~43m?" is the provisional default-rate estimate shown
|
||||||
// still calibrating the first step. See sample_charge_eta().
|
// immediately on plug-in; the "?" drops to "~43m" once a real 10% step
|
||||||
|
// has been timed and the measured rate takes over. See sample_charge_eta().
|
||||||
if (g_charge_eta.charging) {
|
if (g_charge_eta.charging) {
|
||||||
char ebuf[8];
|
char ebuf[8];
|
||||||
if (g_charge_eta.valid)
|
if (g_charge_eta.valid)
|
||||||
snprintf(ebuf, sizeof(ebuf), "~%dm", g_charge_eta.minutes);
|
snprintf(ebuf, sizeof(ebuf), "~%dm%s", g_charge_eta.minutes,
|
||||||
|
g_charge_eta.provisional ? "?" : "");
|
||||||
else
|
else
|
||||||
snprintf(ebuf, sizeof(ebuf), "~--m");
|
snprintf(ebuf, sizeof(ebuf), "~--m");
|
||||||
draw_text(94, 18, ebuf);
|
draw_text(94, 18, ebuf);
|
||||||
|
|||||||
Reference in New Issue
Block a user