mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-07 04:36:11 +00:00
feat(companion): clock tools — alarm, countdown timer, stopwatch
Add a Clock Tools screen (Enter on the home Clock page) with three time utilities, plus the engine that drives them from UITask::loop() so they work regardless of the current screen / display state: - Alarm: a single one-shot wake alarm. Persisted in NodePrefs as a local time-of-day; UITask schedules it as an ABSOLUTE fire instant recomputed from that time, so it survives RTC re-syncs (mesh/app/GPS/CLI all jump the clock) — small corrections still fire on time, a jump over the target still fires (late, up to 6 h). Disarms after firing. A bell icon signals an armed alarm on the clock face and the top status bar. - Timer: a millis-based countdown (sync-immune), big HH:MM:SS readout, rings even when off-screen. - Stopwatch: millis-based, keeps running in the background. Numeric fields use the shared framework DigitEditor (digit-by-digit, min/max enforced). The alarm/timer/ring engine lives in UITask alongside the locator/live-share engines (no screen-cast for time-critical logic). E-ink: live readouts refresh coarsely (and on any key); timing is exact regardless, and the countdown's buzzer fires on time. Rings override mute and are dismissed by any key (auto-stop after 1 min). Cannot wake from a full Shutdown (CPU/RAM powered down). NodePrefs: +alarm_on/alarm_hour/alarm_min (fit existing tail padding, sizeof unchanged 2488), SCHEMA_SENTINEL 0xC0DE0016 -> 0xC0DE0017, DataStore read/clamp/write in lockstep. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
59d1722207
commit
9ce04835da
@@ -89,6 +89,7 @@ class UITask : public AbstractUITask {
|
||||
UIScreen* compass_screen = nullptr;
|
||||
UIScreen* diag_screen = nullptr;
|
||||
UIScreen* repeater_screen = nullptr;
|
||||
UIScreen* clock_tools = nullptr;
|
||||
UIScreen* curr = nullptr;
|
||||
CayenneLPP _dash_lpp;
|
||||
TrailStore _trail;
|
||||
@@ -126,6 +127,25 @@ class UITask : public AbstractUITask {
|
||||
void fireLocator(bool arrived);
|
||||
void locatorProximityBeeper();
|
||||
|
||||
// Clock tools engine — owned here (not by ClockToolsScreen) so the one-shot
|
||||
// alarm and the countdown timer fire every loop regardless of the current
|
||||
// screen / display state. ClockToolsScreen is pure UI over this state. The
|
||||
// alarm is scheduled as an ABSOLUTE wall instant, recomputed from the stored
|
||||
// time-of-day, so it survives RTC re-syncs (mesh/app/GPS/CLI all jump the
|
||||
// clock) — small corrections still fire on time, a jump over the target still
|
||||
// fires (late). See evaluateAlarm(). Timer + ring are millis-based.
|
||||
uint32_t _alarm_next_fire = 0; // unix; 0 = (re)compute lazily once time is valid
|
||||
uint32_t _alarm_check_ms = 0; // throttle the wall-clock read to ~2 Hz
|
||||
bool _timer_running = false;
|
||||
uint32_t _timer_deadline_ms = 0;
|
||||
bool _ringing = false;
|
||||
uint32_t _ring_until_ms = 0;
|
||||
char _ring_label[20] = {0};
|
||||
uint32_t computeAlarmNextFire(uint32_t now_wall) const;
|
||||
void evaluateAlarm(); // alarm scheduling + fire detection
|
||||
void fireClockAlert(const char* label); // wake + alert + melody + start ring
|
||||
void tickClockTools(); // driven from loop(): ring + timer + alarm
|
||||
|
||||
// Course-over-ground ring — a heading source independent of trail recording.
|
||||
// Filled from the same periodic GPS poll regardless of _trail.isActive().
|
||||
// Heading = bearing across the window (oldest→newest) once the cumulative
|
||||
@@ -245,6 +265,24 @@ public:
|
||||
void gotoCompassScreen();
|
||||
void gotoDiagnosticsScreen();
|
||||
void gotoRepeaterScreen();
|
||||
void gotoClockTools(); // Alarm / Timer / Stopwatch (from the home Clock page)
|
||||
// Wake the display for an alarm/timer ring (force an immediate refresh).
|
||||
void wakeForAlarm();
|
||||
// Clear any active alert overlay early (alarm dismiss).
|
||||
void clearAlert() { _alert_expiry = 0; }
|
||||
// Clock tools engine API — ClockToolsScreen drives these; the engine itself
|
||||
// runs in tickClockTools() from loop() so it fires regardless of the screen.
|
||||
void onAlarmChanged() { _alarm_next_fire = 0; } // re-schedule after an alarm edit
|
||||
void startTimer(uint32_t duration_ms) { _timer_running = true; _timer_deadline_ms = millis() + duration_ms; }
|
||||
void stopTimer() { _timer_running = false; }
|
||||
bool isTimerRunning() const { return _timer_running; }
|
||||
uint32_t timerRemainingMs() const {
|
||||
if (!_timer_running) return 0;
|
||||
uint32_t now = millis();
|
||||
return (now >= _timer_deadline_ms) ? 0 : (_timer_deadline_ms - now);
|
||||
}
|
||||
bool isRinging() const { return _ringing; }
|
||||
void dismissRing() { stopMelody(); _ringing = false; clearAlert(); }
|
||||
TrailStore& trail() { return _trail; }
|
||||
WaypointStore& waypoints() { return _waypoints; }
|
||||
LiveTrackStore& liveTrack() { return _livetrack; }
|
||||
|
||||
Reference in New Issue
Block a user