Compare commits

...
Author SHA1 Message Date
MarcelineVPQandClaude Opus 4.7 89847ed06e docs(changelog): cut v0.6.7 — provisional charge ETA on plug-in
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:39:35 -06:00
MarcelineVPQandClaude Opus 4.7 0eceb1d5e2 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>
2026-05-23 20:29:46 -06:00
2 changed files with 46 additions and 12 deletions
+14
View File
@@ -10,6 +10,20 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
--- ---
## [0.6.7-oled-edition] — 2026-05-23
UF2s attached to [the GitHub release](https://github.com/MarcelineVPQ/DS5Dongle-OLED-Edition/releases/tag/v0.6.7-oled-edition) (built by `.github/workflows/release.yml`).
### Changed
- **Charge ETA now shows a provisional estimate immediately on plug-in.** Instead of sitting on `~--m` for the ~15-20 min until the first 10% step is timed, the Status screen shows a default-rate estimate `~Nm?` (the trailing `?` marks it provisional) the moment charging starts. The `?` drops and the number switches to the measured rate once a clean 10% step completes. Default is ~15 min per 10% step (`kDefaultStepUs`), taper-weighted exactly like the measured path, so the provisional figure is in the right ballpark and self-corrects.
### Companion web tool
- `DS5Dongle-OLED-Config-Web` gains **lightbar controls** (mode dropdown + four favorite-color pickers) in the config view, the provisional charge-ETA token in the OLED preview to match this firmware, and translations for two preview notes that were English-only. Build housekeeping: `tsconfig.tsbuildinfo` is no longer tracked.
---
## [0.6.6-oled-edition] — 2026-05-23 ## [0.6.6-oled-edition] — 2026-05-23
Community-issue follow-ups: configurable OLED idle-ladder thresholds (#5) and a diagnostic counter clarifying the trigger-flow numbers (#6). UF2s attached to [the GitHub release](https://github.com/MarcelineVPQ/DS5Dongle-OLED-Edition/releases/tag/v0.6.6-oled-edition) (built by `.github/workflows/release.yml`). The companion `DS5Dongle-OLED-Config-Web` config tool gains matching screen-timeout controls and is synced to the v0.6.5+ `Config_body` layout (fixes a latent issue where saving via the old web tool would zero the lightbar fields). Community-issue follow-ups: configurable OLED idle-ladder thresholds (#5) and a diagnostic counter clarifying the trigger-flow numbers (#6). UF2s attached to [the GitHub release](https://github.com/MarcelineVPQ/DS5Dongle-OLED-Edition/releases/tag/v0.6.6-oled-edition) (built by `.github/workflows/release.yml`). The companion `DS5Dongle-OLED-Config-Web` config tool gains matching screen-timeout controls and is synced to the v0.6.5+ `Config_body` layout (fixes a latent issue where saving via the old web tool would zero the lightbar fields).
+32 -12
View File
@@ -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);