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

+42 -17
View File
@@ -3,35 +3,60 @@
#include <stdint.h>
// The stopwatch of the dynamic pages, started and stopped in the web UI and Home Assistant.
// Times are millis(). Pure logic, tested in test/test_esp32.
// Times are the real time in ms since 1970 (UTC), so it counts from a date, keeps counting
// while the display is off or restarting (system/stopwatch_store keeps it in NVS) and can
// run for years. Pure logic, tested in test/test_esp32.
class Stopwatch
{
public:
void start(uint32_t now)
// The state as it is stored
struct State
{
if (_running)
bool running = false;
int64_t started = 0; // when it was started (again)
int64_t before = 0; // ms counted before the last start
};
void start(int64_t now)
{
if (_state.running)
return;
_running = true;
_started = now;
_state.running = true;
_state.started = now;
}
void stop(uint32_t now)
void stop(int64_t now)
{
if (!_running)
if (!_state.running)
return;
_before += now - _started;
_running = false;
_state.before = elapsed(now);
_state.running = false;
}
// Back to 0, stopped
void reset()
void reset() { _state = State(); }
// Counts from this moment (in the past) on, running; false (nothing changed) when it is
// in the future
bool setStart(int64_t start, int64_t now)
{
_running = false;
_before = 0;
if (start > now)
return false;
_state.running = true;
_state.started = start;
_state.before = 0;
return true;
}
bool running() const { return _running; }
uint32_t elapsed(uint32_t now) const { return _before + (_running ? now - _started : 0); }
bool running() const { return _state.running; }
// Never negative (the clock set back)
uint64_t elapsed(int64_t now) const
{
int64_t ms = _state.before;
if (_state.running && now > _state.started)
ms += now - _state.started;
return ms > 0 ? (uint64_t)ms : 0;
}
const State &state() const { return _state; }
void restore(const State &state) { _state = state; }
private:
bool _running = false;
uint32_t _started = 0;
uint32_t _before = 0; // ms counted before the last start
State _state;
};