Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions src/Rule.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ class Rule {
/**
* @brief Reset temperature-based runtime extension (call on mode change).
*/
void resetTemperatureExtension() { _activeEndMinutes = 0; }
void resetTemperatureExtension() {
_activeEndMinutes = 0;
_activeEndTime = 0;
}

/**
* @brief Get current temperature-extended end time in minutes since midnight.
Expand Down Expand Up @@ -195,14 +198,27 @@ class Rule {
timerActive = (difftime(now, start) >= 0) && (difftime(now, end) <= 0);
}

uint16_t nowMinutes = time.tm_hour * 60 + time.tm_min;

// Step 1: If timer is active, apply temperature extension
if (timerActive && poolTemp > 0.0f && poolTemp == poolTemp) {
uint16_t extendedEnd = calculateEffectiveEndMinutes(baseStartMinutes, baseEndMinutes, poolTemp);

// Only extend, never shorten
if (extendedEnd > _activeEndMinutes) {
// Absolute expiry anchored to the current cycle's base start. Minutes
// since midnight alone cannot tell a wrap-past-midnight end apart from
// the next cycle's start — e.g. a full-day extension (max runtime 1440)
// ends exactly at the next base start and would otherwise never expire,
// keeping the pump on even after the water cools.
time_t cycleStart = start;
if (crossesMidnight && now < start) {
// Post-midnight part of a midnight-crossing window: the cycle started
// on the previous day.
cycleStart = start - 86400;
}
int totalRuntime = static_cast<int>(extendedEnd) - static_cast<int>(baseStartMinutes);
time_t expiry = cycleStart + static_cast<time_t>(totalRuntime) * 60;
Comment thread
stritti marked this conversation as resolved.

// Only extend, never shorten (within the current cycle)
if (expiry > _activeEndTime) {
_activeEndTime = expiry;
_activeEndMinutes = extendedEnd;
uint8_t eh = (_activeEndMinutes / 60) % 24;
uint8_t em = _activeEndMinutes % 60;
Expand All @@ -213,27 +229,18 @@ class Rule {
}

// Step 2: Check if we're within the extended window
if (_activeEndMinutes > 0) {
// Normalize extended end to 0-1439 for comparison with nowMinutes
uint16_t normalizedEnd = _activeEndMinutes % 1440;

bool inExtendedWindow;
if (crossesMidnight) {
// Extended window with midnight crossing
inExtendedWindow = (nowMinutes >= baseStartMinutes || nowMinutes <= normalizedEnd);
} else {
inExtendedWindow = (nowMinutes < normalizedEnd);
}

if (inExtendedWindow) {
if (_activeEndTime > 0) {
if (now < _activeEndTime) {
#ifdef DEBUG_RULE_TIMER
uint16_t normalizedEnd = _activeEndMinutes % 1440;
Serial.printf(
" checkPoolPumpTimer = true (extended to %02d:%02d)\n", (uint8_t)(normalizedEnd / 60), (uint8_t)(normalizedEnd % 60));
#endif
return true;
}

// Extended window expired — reset
_activeEndTime = 0;
_activeEndMinutes = 0;
}

Expand Down Expand Up @@ -328,4 +335,5 @@ class Rule {

TimerSetting _timerSetting;
uint16_t _activeEndMinutes = 0; ///< Temperature-extended end in minutes since midnight
time_t _activeEndTime = 0; ///< Absolute expiry of the temperature extension (0 = none)
};
12 changes: 12 additions & 0 deletions test/native/mocks/TestTime.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once
#include <ctime>

/**
* @brief Test-only hook to control the mock clock used by getCurrentDateTime().
*
* The native test harness stubs getTimeFor() (see stubs.cpp) to return a
* controllable epoch. Tests that need a specific wall-clock time build the
* epoch via mktime() from a tm struct, so the localtime_r() round-trip inside
* getCurrentDateTime() is independent of the host timezone.
*/
void setMockTime(time_t t);
12 changes: 11 additions & 1 deletion test/native/mocks/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,20 @@ uint8_t getTimeDegradationRedHours() {
return 24;
}

// Controllable mock clock for timer-window tests. Defaults to epoch 0
// (1970-01-01 00:00 UTC) to preserve existing test behavior; tests that need
// a specific wall-clock time call setMockTime() with an epoch built via
// mktime() so the localtime_r() round-trip is host-timezone independent.
static time_t g_mockTime = 0;

void setMockTime(time_t t) {
g_mockTime = t;
}

time_t getTimeFor(int tzIndex, TimeChangeRule **tcr) {
if (tcr)
*tcr = nullptr;
return 0;
return g_mockTime;
}
String getTimeInfoFor(int) {
return String("UTC");
Expand Down
Loading
Loading