Public Access
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:
1 parent
f5e77c7eca
commit
0166d0ee46
20 files changed
+278
-67
No files matched your search
@@ -778,13 +778,23 @@ static void formatCountdown(const PageElement &e, const LocalTime &t, char *out,
|
||||
snprintf(out, size, "%s%s", e.text, value);
|
||||
}
|
||||
|
||||
static void formatStopwatch(const PageElement &e, uint32_t ms, char *out, size_t size)
|
||||
static void formatStopwatch(const PageElement &e, uint64_t ms, char *out, size_t size)
|
||||
{
|
||||
unsigned long s = ms / 1000, h = s / 3600, m = s % 3600 / 60, sec = s % 60;
|
||||
unsigned long s = (unsigned long)(ms / 1000), h = s / 3600, m = s % 3600 / 60, sec = s % 60;
|
||||
unsigned long days = h / 24;
|
||||
char value[24];
|
||||
if (e.format == STOPWATCH_H_MM)
|
||||
if (e.format == STOPWATCH_W_D)
|
||||
{
|
||||
if (days >= 7)
|
||||
snprintf(value, sizeof(value), "%luw %lud", days / 7, days % 7);
|
||||
else
|
||||
snprintf(value, sizeof(value), "%lud", days);
|
||||
}
|
||||
else if (e.format == STOPWATCH_D_H_MM_SS && days)
|
||||
snprintf(value, sizeof(value), "%lud %lu:%02lu:%02lu", days, h % 24, m, sec);
|
||||
else if (e.format == STOPWATCH_H_MM)
|
||||
snprintf(value, sizeof(value), "%lu:%02lu", h, m);
|
||||
else if (e.format == STOPWATCH_H_MM_SS || h)
|
||||
else if (e.format == STOPWATCH_H_MM_SS || e.format == STOPWATCH_D_H_MM_SS || h)
|
||||
snprintf(value, sizeof(value), "%lu:%02lu:%02lu", h, m, sec);
|
||||
else
|
||||
snprintf(value, sizeof(value), "%lu:%02lu", m, sec);
|
||||
@@ -860,7 +870,7 @@ bool elementShowsSeconds(const PageElement &e, const LocalTime &t, bool stopwatc
|
||||
case ElementType::Clock:
|
||||
return e.format == CLOCK_HH_MM_SS || e.format == CLOCK_H_MM_SS;
|
||||
case ElementType::Stopwatch:
|
||||
return stopwatch_running && e.format != STOPWATCH_H_MM;
|
||||
return stopwatch_running && e.format != STOPWATCH_H_MM && e.format != STOPWATCH_W_D;
|
||||
case ElementType::Countdown:
|
||||
{
|
||||
int64_t left = secondsLeft(e, t);
|
||||
|
||||
@@ -97,7 +97,9 @@ const uint8_t COUNTDOWN_FORMAT_COUNT = 4;
|
||||
const uint8_t STOPWATCH_M_SS = 0; // 4:05, from an hour on 1:04:05
|
||||
const uint8_t STOPWATCH_H_MM = 1; // 0:04 (hours and minutes)
|
||||
const uint8_t STOPWATCH_H_MM_SS = 2; // 0:04:05
|
||||
const uint8_t STOPWATCH_FORMAT_COUNT = 3;
|
||||
const uint8_t STOPWATCH_D_H_MM_SS = 3; // 12d 4:05:09, below a day 4:05:09
|
||||
const uint8_t STOPWATCH_W_D = 4; // 3w 2d, without the weeks below a week
|
||||
const uint8_t STOPWATCH_FORMAT_COUNT = 5;
|
||||
|
||||
// Progress bar formats
|
||||
const uint8_t BAR_PLAIN = 0; // only the bar
|
||||
@@ -238,7 +240,7 @@ const PageSlot *activeSlot(const DynamicPage &page, int minute_of_day);
|
||||
struct ElementValues
|
||||
{
|
||||
const char *ha_value = nullptr; // nullptr: not known yet, shown as "--"
|
||||
uint32_t stopwatch_ms = 0;
|
||||
uint64_t stopwatch_ms = 0;
|
||||
};
|
||||
|
||||
// The text of an element (not for images). out_size must be at least 2.
|
||||
|
||||
@@ -270,7 +270,7 @@ uint32_t renderPage(const DynamicPage &page, const LocalTime &time, uint32_t now
|
||||
|
||||
uint32_t next = 0;
|
||||
bool running = resources.stopwatchRunning();
|
||||
uint32_t stopwatch_ms = resources.stopwatchMs();
|
||||
uint64_t stopwatch_ms = resources.stopwatchMs();
|
||||
for (int i = 0; i < page.element_count && i < PAGE_ELEMENTS; i++)
|
||||
{
|
||||
const PageElement &e = page.elements[i];
|
||||
@@ -292,10 +292,10 @@ uint32_t renderPage(const DynamicPage &page, const LocalTime &time, uint32_t now
|
||||
next = sooner(next, drawTextElement(e, text, now_ms, resources, bitmap, box));
|
||||
|
||||
if (elementShowsSeconds(e, time, running)) // the stopwatch's seconds count from its start
|
||||
next = sooner(next, e.type == ElementType::Stopwatch ? 1000 - stopwatch_ms % 1000
|
||||
next = sooner(next, e.type == ElementType::Stopwatch ? 1000 - (uint32_t)(stopwatch_ms % 1000)
|
||||
: 1000 - (time.millisecond % 1000));
|
||||
else if (e.type == ElementType::Stopwatch && running) // H:MM: the next stopwatch minute
|
||||
next = sooner(next, 60000 - stopwatch_ms % 60000);
|
||||
else if (e.type == ElementType::Stopwatch && running) // H:MM, weeks/days: the next stopwatch minute
|
||||
next = sooner(next, 60000 - (uint32_t)(stopwatch_ms % 60000));
|
||||
}
|
||||
if (boxes)
|
||||
boxes[i] = box;
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
virtual Font font(const char *name) = 0;
|
||||
// The current value of a Home Assistant element; nullptr when not known
|
||||
virtual const char *haValue(const PageElement &element) = 0;
|
||||
virtual uint32_t stopwatchMs() { return 0; }
|
||||
virtual uint64_t stopwatchMs() { return 0; }
|
||||
virtual bool stopwatchRunning() { return false; }
|
||||
// The running Game of Life of the shown page when this element starts like it
|
||||
// (sameLifeStart); nullptr otherwise (the start is drawn then)
|
||||
|
||||
+42
-17
@@ -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;
|
||||
};
|
||||
Reference in new issue
Block a user