Stopwatch counts in real time from a start date, kept over a restart

The stopwatch counted with millis(), so it started at 0 after every
restart and would overflow after 49 days. It now keeps the real start
time (UTC ms) and the time counted before a pause, stored in NVS
(system/stopwatch_store) on every change, so it keeps counting while
the display is off or restarting. Stop pauses, Start continues, and the
Stopwatch tab can set the start to a date and time (local, summer time
by mktime) to count from, e.g. days since an event. Start, stop and set
need the synced time.

New page formats: 12d 4:05:09 and 3w 2d.

Version 2.19.0, tested on the display over OTA (set start, future and
invalid dates refused, pause, restart).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-20 22:22:32 +02:00
1 parent f5e77c7eca
commit 0166d0ee46
20 files changed
+278 -67

No files matched your search

+55 -11
View File
@@ -1475,21 +1475,42 @@ void test_countdown_shows_seconds_only_when_needed()
void test_stopwatch()
{
const int64_t T = 1760000000000LL; // ms since 1970, in 2025
Stopwatch sw;
TEST_ASSERT_EQUAL_UINT32(0, sw.elapsed(5000));
sw.start(1000);
TEST_ASSERT_EQUAL_UINT64(0, sw.elapsed(T));
sw.start(T + 1000);
TEST_ASSERT_TRUE(sw.running());
TEST_ASSERT_EQUAL_UINT32(2000, sw.elapsed(3000));
sw.stop(4000);
TEST_ASSERT_EQUAL_UINT32(3000, sw.elapsed(9000));
sw.start(10000);
sw.start(11000); // already running: no restart
TEST_ASSERT_EQUAL_UINT32(5000, sw.elapsed(12000));
TEST_ASSERT_EQUAL_UINT64(2000, sw.elapsed(T + 3000));
sw.stop(T + 4000);
TEST_ASSERT_EQUAL_UINT64(3000, sw.elapsed(T + 9000));
sw.start(T + 10000);
sw.start(T + 11000); // already running: no restart
TEST_ASSERT_EQUAL_UINT64(5000, sw.elapsed(T + 12000));
TEST_ASSERT_EQUAL_UINT64(3000, sw.elapsed(T + 5000)); // clock set back: never less than before
sw.reset();
TEST_ASSERT_FALSE(sw.running());
TEST_ASSERT_EQUAL_UINT32(0, sw.elapsed(20000));
sw.start(0xFFFFFF00u); // across the millis() overflow
TEST_ASSERT_EQUAL_UINT32(0x200, sw.elapsed(0x100));
TEST_ASSERT_EQUAL_UINT64(0, sw.elapsed(T + 20000));
// Past 49 days (millis() would overflow)
sw.start(T);
TEST_ASSERT_EQUAL_UINT64(60ULL * 86400000ULL, sw.elapsed(T + 60LL * 86400000LL));
// A start date by hand: counts from there, pausing keeps the time
TEST_ASSERT_FALSE(sw.setStart(T + 1000, T)); // in the future
TEST_ASSERT_TRUE(sw.setStart(T - 3 * 86400000LL, T));
TEST_ASSERT_TRUE(sw.running());
TEST_ASSERT_EQUAL_UINT64(3 * 86400000ULL, sw.elapsed(T));
sw.stop(T);
TEST_ASSERT_EQUAL_UINT64(3 * 86400000ULL, sw.elapsed(T + 5000));
// Kept over a restart (system/stopwatch_store stores the state)
Stopwatch copy;
copy.restore(sw.state());
TEST_ASSERT_FALSE(copy.running());
TEST_ASSERT_EQUAL_UINT64(3 * 86400000ULL, copy.elapsed(T + 99999));
sw.start(T + 1000);
copy.restore(sw.state());
TEST_ASSERT_EQUAL_UINT64(3 * 86400000ULL + 1000, copy.elapsed(T + 2000));
PageElement e; // textOf: 65 s on the stopwatch
e.type = ElementType::Stopwatch;
@@ -1502,6 +1523,29 @@ void test_stopwatch()
TEST_ASSERT_EQUAL_STRING("T 0:01:05", textOf(e).c_str());
TEST_ASSERT_TRUE(elementShowsSeconds(e, LocalTime(), true));
TEST_ASSERT_FALSE(elementShowsSeconds(e, LocalTime(), false)); // stopped
strcpy(e.text, "");
// Days and weeks
ElementValues values;
char text[40];
e.format = STOPWATCH_D_H_MM_SS;
values.stopwatch_ms = 65000;
elementText(e, LocalTime(), values, text, sizeof(text));
TEST_ASSERT_EQUAL_STRING("0:01:05", text); // below a day: without the days
values.stopwatch_ms = (12ULL * 86400 + 4 * 3600 + 5 * 60 + 9) * 1000;
elementText(e, LocalTime(), values, text, sizeof(text));
TEST_ASSERT_EQUAL_STRING("12d 4:05:09", text);
TEST_ASSERT_TRUE(elementShowsSeconds(e, LocalTime(), true));
e.format = STOPWATCH_W_D;
elementText(e, LocalTime(), values, text, sizeof(text));
TEST_ASSERT_EQUAL_STRING("1w 5d", text);
TEST_ASSERT_FALSE(elementShowsSeconds(e, LocalTime(), true));
values.stopwatch_ms = 2ULL * 86400000 + 5000;
elementText(e, LocalTime(), values, text, sizeof(text));
TEST_ASSERT_EQUAL_STRING("2d", text);
values.stopwatch_ms = 400ULL * 86400000; // more than a year, past 32 bit ms
elementText(e, LocalTime(), values, text, sizeof(text));
TEST_ASSERT_EQUAL_STRING("57w 1d", text);
}
void test_home_assistant_multiplier()