From 2199627a35397ead8f356b7e5d8a9c7ab9cf92fb Mon Sep 17 00:00:00 2001 From: acidburns Date: Mon, 2 Feb 2026 23:01:55 +0100 Subject: [PATCH] Fix OLED autosleep timing and battery sampling cadence - Track last OLED activity to avoid double timeout; keep power gating on transitions - Copy TZ before setenv() in timegm_fallback to avoid invalid pointer reuse - Add BATTERY_SAMPLE_INTERVAL_MS and only refresh cache at batch start when due - Keep battery sampling to a single ADC read (Arduino core lacks explicit ADC power gating) --- README.md | 11 +++++++---- include/config.h | 1 + src/display_ui.cpp | 28 ++++++++-------------------- src/main.cpp | 6 +++++- src/power_manager.cpp | 7 ++----- src/rtc_ds3231.cpp | 9 ++++++--- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index aef7a7e..d394147 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,8 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C }; ``` ## OLED Behavior -- Sender: OLED stays **ON for 10 seconds** on each wake, then powers down for sleep. +- Sender: OLED stays on for `OLED_AUTO_OFF_MS` after boot or last activity. +- Activity is detected while `PIN_OLED_CTRL` is held high, or on the high→low edge when the control is released. - Receiver: OLED is always on (no auto-off). - Pages rotate every 4s. @@ -260,6 +261,7 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C }; - 4.2 V = 100% - Uses deep sleep between cycles (`SENDER_WAKE_INTERVAL_SEC`). - Sender CPU is throttled to 80 MHz and LoRa RX is only enabled in short windows (ACK wait or time-sync). +- Battery sampling uses a single ADC read and updates at most once per `BATTERY_SAMPLE_INTERVAL_MS` (default 60s). ## Web UI - AP SSID: `DD3-Bridge-` (prefix configurable) @@ -311,9 +313,10 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C }; ## Config Knobs Key timing settings in `include/config.h`: -- `METER_SAMPLE_INTERVAL_MS` -- `METER_SEND_INTERVAL_MS` -- `BATCH_ACK_TIMEOUT_MS` + - `METER_SAMPLE_INTERVAL_MS` + - `METER_SEND_INTERVAL_MS` + - `BATTERY_SAMPLE_INTERVAL_MS` + - `BATCH_ACK_TIMEOUT_MS` - `BATCH_MAX_RETRIES` - `BATCH_QUEUE_DEPTH` - `BATCH_RETRY_POLICY` (keep or drop on retry exhaustion) diff --git a/include/config.h b/include/config.h index 78fd5ca..038338b 100644 --- a/include/config.h +++ b/include/config.h @@ -69,6 +69,7 @@ constexpr uint32_t OLED_AUTO_OFF_MS = 10UL * 60UL * 1000UL; constexpr uint32_t SENDER_OLED_READ_MS = 10000; constexpr uint32_t METER_SAMPLE_INTERVAL_MS = 1000; constexpr uint32_t METER_SEND_INTERVAL_MS = 30000; +constexpr uint32_t BATTERY_SAMPLE_INTERVAL_MS = 60000; constexpr uint32_t BATCH_ACK_TIMEOUT_MS = 3000; constexpr uint8_t BATCH_MAX_RETRIES = 2; constexpr uint8_t METER_BATCH_MAX_SAMPLES = 30; diff --git a/src/display_ui.cpp b/src/display_ui.cpp index e22f9ba..4f1032a 100644 --- a/src/display_ui.cpp +++ b/src/display_ui.cpp @@ -36,10 +36,9 @@ static bool g_mqtt_ok = false; static bool g_oled_on = true; static bool g_prev_ctrl_high = false; -static uint32_t g_oled_off_start = 0; static uint32_t g_last_page_ms = 0; static uint8_t g_page = 0; -static uint32_t g_boot_ms = 0; +static uint32_t g_last_activity_ms = 0; static bool g_display_ready = false; static uint32_t g_last_init_attempt_ms = 0; static bool g_last_oled_on = true; @@ -83,7 +82,7 @@ void display_init() { display.display(); } g_last_init_attempt_ms = millis(); - g_boot_ms = millis(); + g_last_activity_ms = millis(); } void display_set_role(DeviceRole role) { @@ -380,27 +379,16 @@ void display_tick() { ctrl_high = digitalRead(PIN_OLED_CTRL) == HIGH; } - bool in_boot_window = (millis() - g_boot_ms) < OLED_AUTO_OFF_MS; + uint32_t now_ms = millis(); + bool ctrl_falling_edge = g_prev_ctrl_high && !ctrl_high; if (g_role == DeviceRole::Receiver) { g_oled_on = true; - g_oled_off_start = 0; - } else if (in_boot_window) { - g_oled_on = true; + g_last_activity_ms = now_ms; } else { - if (ctrl_high) { - g_oled_on = true; - g_oled_off_start = 0; - } else if (g_prev_ctrl_high && !ctrl_high) { - g_oled_off_start = millis(); - } else if (!g_prev_ctrl_high && !ctrl_high && g_oled_off_start == 0) { - g_oled_off_start = millis(); + if (ctrl_high || ctrl_falling_edge) { + g_last_activity_ms = now_ms; } - - if (!ctrl_high && g_oled_off_start > 0 && millis() - g_oled_off_start > OLED_AUTO_OFF_MS) { - g_oled_on = false; - } - - // fall through to power gating below + g_oled_on = (now_ms - g_last_activity_ms) < OLED_AUTO_OFF_MS; } if (g_oled_on) { diff --git a/src/main.cpp b/src/main.cpp index 736c11f..754a68b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -152,6 +152,10 @@ static void update_battery_cache() { g_last_battery_ms = millis(); } +static bool battery_sample_due(uint32_t now_ms) { + return g_last_battery_ms == 0 || now_ms - g_last_battery_ms >= BATTERY_SAMPLE_INTERVAL_MS; +} + static bool sender_timesync_window_due() { uint32_t interval_sec = SENDER_TIMESYNC_CHECK_SEC_FAST; if (time_is_synced() && time_rtc_present()) { @@ -742,7 +746,7 @@ static void sender_loop() { note_fault(g_sender_faults, g_sender_last_error, g_sender_last_error_utc, g_sender_last_error_ms, FaultType::MeterRead); display_set_last_error(g_sender_last_error, g_sender_last_error_utc, g_sender_last_error_ms); } - if (g_build_count == 0) { + if (g_build_count == 0 && battery_sample_due(now_ms)) { update_battery_cache(); } data.battery_voltage_v = g_last_battery_voltage_v; diff --git a/src/power_manager.cpp b/src/power_manager.cpp index ba9f7a8..4b2e44a 100644 --- a/src/power_manager.cpp +++ b/src/power_manager.cpp @@ -34,11 +34,8 @@ void power_configure_unused_pins_sender() { } void read_battery(MeterData &data) { - uint32_t sum = 0; - sum += analogRead(PIN_BAT_ADC); - sum += analogRead(PIN_BAT_ADC); - float avg = static_cast(sum) / 2.0f; - float v = (avg / 4095.0f) * ADC_REF_V * BATTERY_DIVIDER * BATTERY_CAL; + uint32_t raw = analogRead(PIN_BAT_ADC); + float v = (static_cast(raw) / 4095.0f) * ADC_REF_V * BATTERY_DIVIDER * BATTERY_CAL; data.battery_voltage_v = v; data.battery_percent = battery_percent_from_voltage(v); diff --git a/src/rtc_ds3231.cpp b/src/rtc_ds3231.cpp index e68fcfc..f4103d7 100644 --- a/src/rtc_ds3231.cpp +++ b/src/rtc_ds3231.cpp @@ -1,6 +1,7 @@ #include "rtc_ds3231.h" #include "config.h" #include +#include #include static constexpr uint8_t DS3231_ADDR = 0x68; @@ -17,12 +18,14 @@ static time_t timegm_fallback(struct tm *tm_utc) { if (!tm_utc) { return static_cast(-1); } - char *old_tz = getenv("TZ"); + const char *old_tz = getenv("TZ"); + // getenv() may return a pointer into mutable storage that becomes invalid after setenv(). + std::string old_tz_copy = old_tz ? old_tz : ""; setenv("TZ", "UTC0", 1); tzset(); time_t t = mktime(tm_utc); - if (old_tz) { - setenv("TZ", old_tz, 1); + if (!old_tz_copy.empty()) { + setenv("TZ", old_tz_copy.c_str(), 1); } else { unsetenv("TZ"); }