-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Add autowake actions #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat-clock
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |||||||||||||||||
| #include "components/UITheme.h" | ||||||||||||||||||
| #include "fontIds.h" | ||||||||||||||||||
| #include "util/ButtonNavigator.h" | ||||||||||||||||||
| #include "util/ScheduledTaskRunner.h" | ||||||||||||||||||
| #include "util/ScreenshotUtil.h" | ||||||||||||||||||
|
|
||||||||||||||||||
| HalDisplay display; | ||||||||||||||||||
|
|
@@ -129,6 +130,8 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont); | |||||||||||||||||
| unsigned long t1 = 0; | ||||||||||||||||||
| unsigned long t2 = 0; | ||||||||||||||||||
|
|
||||||||||||||||||
| uint64_t calculateTimerWakeupUs(); // forward declaration | ||||||||||||||||||
|
|
||||||||||||||||||
| // Verify power button press duration on wake-up from deep sleep | ||||||||||||||||||
| // Pre-condition: isWakeupByPowerButton() == true | ||||||||||||||||||
| void verifyPowerButtonDuration() { | ||||||||||||||||||
|
|
@@ -168,8 +171,9 @@ void verifyPowerButtonDuration() { | |||||||||||||||||
|
|
||||||||||||||||||
| if (abort) { | ||||||||||||||||||
| // Button released too early. Returning to sleep. | ||||||||||||||||||
| // IMPORTANT: Re-arm the wakeup trigger before sleeping again | ||||||||||||||||||
| powerManager.startDeepSleep(gpio); | ||||||||||||||||||
| // IMPORTANT: Re-arm the wakeup trigger (and scheduled timer) before sleeping again | ||||||||||||||||||
| uint64_t timerUs = calculateTimerWakeupUs(); | ||||||||||||||||||
| powerManager.startDeepSleep(gpio, SETTINGS.useClock, timerUs); | ||||||||||||||||||
|
Comment on lines
+174
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restore clock before re-arming timer in abort path. At Line 175, 🔧 Proposed fix if (abort) {
// Button released too early. Returning to sleep.
// IMPORTANT: Re-arm the wakeup trigger (and scheduled timer) before sleeping again
+ HalClock::restore();
uint64_t timerUs = calculateTimerWakeupUs();
+ HalClock::saveBeforeSleep(SETTINGS.useClock);
powerManager.startDeepSleep(gpio, SETTINGS.useClock, timerUs);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -181,6 +185,37 @@ void waitForPowerRelease() { | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Returns microseconds until the next scheduled wakeup time, or 0 if timer should not be armed. | ||||||||||||||||||
| uint64_t calculateTimerWakeupUs() { | ||||||||||||||||||
| if (!SETTINGS.scheduledWakeEnabled || !SETTINGS.useClock) { | ||||||||||||||||||
| return 0; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!HalClock::isSynced()) { | ||||||||||||||||||
| return 0; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!SETTINGS.scheduledWakeTaskNtp && !SETTINGS.scheduledWakeTaskImg) { | ||||||||||||||||||
| return 0; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| time_t now = time(nullptr); | ||||||||||||||||||
| struct tm timeinfo; | ||||||||||||||||||
| localtime_r(&now, &timeinfo); | ||||||||||||||||||
|
|
||||||||||||||||||
| struct tm target = timeinfo; | ||||||||||||||||||
| target.tm_hour = SETTINGS.scheduledWakeHour; | ||||||||||||||||||
| target.tm_min = SETTINGS.scheduledWakeMinute; | ||||||||||||||||||
| target.tm_sec = 0; | ||||||||||||||||||
| time_t targetEpoch = mktime(&target); | ||||||||||||||||||
|
|
||||||||||||||||||
| // If target is in the past or within 60s, schedule for tomorrow | ||||||||||||||||||
| if (targetEpoch <= now + 60) { | ||||||||||||||||||
| targetEpoch += 24 * 3600; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| int64_t diffSeconds = static_cast<int64_t>(targetEpoch - now); | ||||||||||||||||||
| return static_cast<uint64_t>(diffSeconds) * 1000000ULL; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Enter deep sleep mode | ||||||||||||||||||
| void enterDeepSleep() { | ||||||||||||||||||
| HalPowerManager::Lock powerLock; // Ensure we are at normal CPU frequency for sleep preparation | ||||||||||||||||||
|
|
@@ -194,7 +229,8 @@ void enterDeepSleep() { | |||||||||||||||||
| LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1); | ||||||||||||||||||
| LOG_DBG("MAIN", "Entering deep sleep"); | ||||||||||||||||||
|
|
||||||||||||||||||
| powerManager.startDeepSleep(gpio, SETTINGS.useClock); | ||||||||||||||||||
| uint64_t timerUs = calculateTimerWakeupUs(); | ||||||||||||||||||
| powerManager.startDeepSleep(gpio, SETTINGS.useClock, timerUs); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| void setupDisplayAndFonts() { | ||||||||||||||||||
|
|
@@ -267,16 +303,29 @@ void setup() { | |||||||||||||||||
| ButtonNavigator::setMappedInputManager(mappedInputManager); | ||||||||||||||||||
|
|
||||||||||||||||||
| switch (gpio.getWakeupReason()) { | ||||||||||||||||||
| case HalGPIO::WakeupReason::TimerWake: { | ||||||||||||||||||
| // Scheduled timer wakeup — run background tasks headlessly, then re-sleep | ||||||||||||||||||
| LOG_DBG("MAIN", "Timer wakeup - running scheduled tasks"); | ||||||||||||||||||
| HalClock::restore(); | ||||||||||||||||||
| ScheduledTaskRunner::run(); | ||||||||||||||||||
| HalClock::saveBeforeSleep(true); // useClock must be true for timer wake | ||||||||||||||||||
| uint64_t timerUs = calculateTimerWakeupUs(); | ||||||||||||||||||
| powerManager.startDeepSleep(gpio, true, timerUs); | ||||||||||||||||||
| break; // never reached | ||||||||||||||||||
| } | ||||||||||||||||||
| case HalGPIO::WakeupReason::PowerButton: | ||||||||||||||||||
| // For normal wakeups, verify power button press duration | ||||||||||||||||||
| LOG_DBG("MAIN", "Verifying power button press duration"); | ||||||||||||||||||
| verifyPowerButtonDuration(); | ||||||||||||||||||
| break; | ||||||||||||||||||
| case HalGPIO::WakeupReason::AfterUSBPower: | ||||||||||||||||||
| case HalGPIO::WakeupReason::AfterUSBPower: { | ||||||||||||||||||
| // If USB power caused a cold boot, go back to sleep | ||||||||||||||||||
| LOG_DBG("MAIN", "Wakeup reason: After USB Power"); | ||||||||||||||||||
| powerManager.startDeepSleep(gpio); | ||||||||||||||||||
| HalClock::restore(); | ||||||||||||||||||
| uint64_t timerUs = calculateTimerWakeupUs(); | ||||||||||||||||||
| powerManager.startDeepSleep(gpio, SETTINGS.useClock, timerUs); | ||||||||||||||||||
| break; | ||||||||||||||||||
| } | ||||||||||||||||||
| case HalGPIO::WakeupReason::AfterFlash: | ||||||||||||||||||
| // After flashing, just proceed to boot | ||||||||||||||||||
| case HalGPIO::WakeupReason::Other: | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,125 @@ | ||||||
| #include "ScheduledTaskRunner.h" | ||||||
|
|
||||||
| #include <HalClock.h> | ||||||
| #include <HalPowerManager.h> | ||||||
| #include <HalStorage.h> | ||||||
| #include <Logging.h> | ||||||
| #include <WiFi.h> | ||||||
|
|
||||||
| #include "CrossPointSettings.h" | ||||||
| #include "WifiCredentialStore.h" | ||||||
| #include "network/HttpDownloader.h" | ||||||
|
|
||||||
| namespace ScheduledTaskRunner { | ||||||
|
|
||||||
| static constexpr uint16_t WIFI_TIMEOUT_MS = 15000; | ||||||
| static constexpr uint16_t LOW_BATTERY_THRESHOLD = 10; | ||||||
|
|
||||||
| static bool connectWifi() { | ||||||
| WIFI_STORE.loadFromFile(); | ||||||
| const std::string& lastSsid = WIFI_STORE.getLastConnectedSsid(); | ||||||
| if (lastSsid.empty()) { | ||||||
| LOG_ERR("SCHED", "No saved WiFi network"); | ||||||
| return false; | ||||||
| } | ||||||
| const auto* cred = WIFI_STORE.findCredential(lastSsid); | ||||||
| if (!cred) { | ||||||
| LOG_ERR("SCHED", "No credentials for %s", lastSsid.c_str()); | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| WiFi.mode(WIFI_STA); | ||||||
| if (!cred->password.empty()) { | ||||||
| WiFi.begin(cred->ssid.c_str(), cred->password.c_str()); | ||||||
| } else { | ||||||
| WiFi.begin(cred->ssid.c_str()); | ||||||
| } | ||||||
|
|
||||||
| unsigned long start = millis(); | ||||||
| while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_TIMEOUT_MS) { | ||||||
| delay(100); | ||||||
| } | ||||||
| if (WiFi.status() != WL_CONNECTED) { | ||||||
| LOG_ERR("SCHED", "WiFi connect timeout"); | ||||||
| WiFi.disconnect(true); | ||||||
| WiFi.mode(WIFI_OFF); | ||||||
| return false; | ||||||
| } | ||||||
| LOG_INF("SCHED", "WiFi connected to %s", lastSsid.c_str()); | ||||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| static void taskNtpSync() { | ||||||
| LOG_INF("SCHED", "Running NTP sync task"); | ||||||
| if (HalClock::syncNtp()) { | ||||||
| LOG_INF("SCHED", "NTP sync successful"); | ||||||
| } else { | ||||||
| LOG_ERR("SCHED", "NTP sync failed"); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| static void taskDownloadSleepImage() { | ||||||
| if (SETTINGS.scheduledWakeImageUrl[0] == '\0') { | ||||||
| LOG_DBG("SCHED", "No sleep image URL configured, skipping"); | ||||||
| return; | ||||||
| } | ||||||
| LOG_INF("SCHED", "Downloading sleep image from %s", SETTINGS.scheduledWakeImageUrl); | ||||||
|
|
||||||
|
Comment on lines
+66
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging full download URLs (possible secret leakage). On Line 66, logging the raw URL can expose embedded credentials or signed query tokens in device logs. 🔒 Proposed fix- LOG_INF("SCHED", "Downloading sleep image from %s", SETTINGS.scheduledWakeImageUrl);
+ LOG_INF("SCHED", "Downloading configured sleep image");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| const std::string tempPath = "/.crosspoint/sleep_download.tmp"; | ||||||
| const std::string destPath = "/sleep.bmp"; | ||||||
|
|
||||||
| auto result = HttpDownloader::downloadToFile(SETTINGS.scheduledWakeImageUrl, tempPath); | ||||||
|
|
||||||
| if (result == HttpDownloader::OK) { | ||||||
| if (Storage.exists(destPath.c_str())) { | ||||||
| Storage.remove(destPath.c_str()); | ||||||
| } | ||||||
| if (Storage.rename(tempPath.c_str(), destPath.c_str())) { | ||||||
| LOG_INF("SCHED", "Sleep image saved to %s", destPath.c_str()); | ||||||
| } else { | ||||||
| LOG_ERR("SCHED", "Failed to rename temp file to %s", destPath.c_str()); | ||||||
| Storage.remove(tempPath.c_str()); | ||||||
| } | ||||||
| } else { | ||||||
| LOG_ERR("SCHED", "Sleep image download failed (error %d)", result); | ||||||
| Storage.remove(tempPath.c_str()); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| void run() { | ||||||
| HalPowerManager::Lock powerLock; | ||||||
|
|
||||||
| // Skip tasks if battery is critically low | ||||||
| uint16_t battery = powerManager.getBatteryPercentage(); | ||||||
| if (battery > 0 && battery < LOW_BATTERY_THRESHOLD) { | ||||||
| LOG_INF("SCHED", "Battery critically low (%u%%), skipping scheduled tasks", battery); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| bool needWifi = SETTINGS.scheduledWakeTaskNtp || SETTINGS.scheduledWakeTaskImg; | ||||||
| bool wifiConnected = false; | ||||||
|
|
||||||
| if (needWifi) { | ||||||
| wifiConnected = connectWifi(); | ||||||
| if (!wifiConnected) { | ||||||
| LOG_ERR("SCHED", "WiFi required but failed to connect, aborting tasks"); | ||||||
| return; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (SETTINGS.scheduledWakeTaskNtp) { | ||||||
| taskNtpSync(); | ||||||
| } | ||||||
|
|
||||||
| if (SETTINGS.scheduledWakeTaskImg) { | ||||||
| taskDownloadSleepImage(); | ||||||
| } | ||||||
|
|
||||||
| if (wifiConnected) { | ||||||
| HalClock::wifiOff(true); // skip opportunistic NTP since we already synced | ||||||
| } | ||||||
|
|
||||||
| LOG_INF("SCHED", "Scheduled tasks completed"); | ||||||
| } | ||||||
|
|
||||||
| } // namespace ScheduledTaskRunner | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #pragma once | ||
|
|
||
| namespace ScheduledTaskRunner { | ||
| // Runs all enabled scheduled tasks headlessly (no display). | ||
| // Connects to WiFi if needed, executes tasks, disconnects. | ||
| void run(); | ||
| } // namespace ScheduledTaskRunner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 98
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 356
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 146
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 51
🏁 Script executed:
# Read the entire HalPowerManager.cpp file to understand context and error handling patterns wc -l lib/hal/HalPowerManager.cppRepository: jpirnay/crosspoint-reader
Length of output: 100
🏁 Script executed:
# View the file to understand error handling patterns and context cat -n lib/hal/HalPowerManager.cppRepository: jpirnay/crosspoint-reader
Length of output: 5027
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 319
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 854
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 51
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 4238
🏁 Script executed:
Repository: jpirnay/crosspoint-reader
Length of output: 1676
Check return value from
esp_sleep_enable_timer_wakeup()on line 82.The return value is being ignored. If the timer wakeup fails to arm, the code still logs success, resulting in silent failure of scheduled wakeup functionality.
🔧 Proposed fix
// Optionally arm a timer wakeup (for scheduled tasks) if (timerWakeupUs > 0) { - esp_sleep_enable_timer_wakeup(timerWakeupUs); - LOG_DBG("PWR", "Timer wakeup armed for %llu us (~%llu min)", timerWakeupUs, timerWakeupUs / 60000000ULL); + const esp_err_t err = esp_sleep_enable_timer_wakeup(timerWakeupUs); + if (err != ESP_OK) { + LOG_ERR("PWR", "Failed to arm timer wakeup: %d", static_cast<int>(err)); + } else { + LOG_DBG("PWR", "Timer wakeup armed for %llu us (~%llu min)", timerWakeupUs, timerWakeupUs / 60000000ULL); + } }📝 Committable suggestion
🤖 Prompt for AI Agents