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)
This commit is contained in:
11
README.md
11
README.md
@@ -250,7 +250,8 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C };
|
|||||||
```
|
```
|
||||||
|
|
||||||
## OLED Behavior
|
## 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).
|
- Receiver: OLED is always on (no auto-off).
|
||||||
- Pages rotate every 4s.
|
- Pages rotate every 4s.
|
||||||
|
|
||||||
@@ -260,6 +261,7 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C };
|
|||||||
- 4.2 V = 100%
|
- 4.2 V = 100%
|
||||||
- Uses deep sleep between cycles (`SENDER_WAKE_INTERVAL_SEC`).
|
- 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).
|
- 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
|
## Web UI
|
||||||
- AP SSID: `DD3-Bridge-<short_id>` (prefix configurable)
|
- AP SSID: `DD3-Bridge-<short_id>` (prefix configurable)
|
||||||
@@ -311,9 +313,10 @@ inline constexpr uint16_t EXPECTED_SENDER_IDS[NUM_SENDERS] = { 0xF19C };
|
|||||||
|
|
||||||
## Config Knobs
|
## Config Knobs
|
||||||
Key timing settings in `include/config.h`:
|
Key timing settings in `include/config.h`:
|
||||||
- `METER_SAMPLE_INTERVAL_MS`
|
- `METER_SAMPLE_INTERVAL_MS`
|
||||||
- `METER_SEND_INTERVAL_MS`
|
- `METER_SEND_INTERVAL_MS`
|
||||||
- `BATCH_ACK_TIMEOUT_MS`
|
- `BATTERY_SAMPLE_INTERVAL_MS`
|
||||||
|
- `BATCH_ACK_TIMEOUT_MS`
|
||||||
- `BATCH_MAX_RETRIES`
|
- `BATCH_MAX_RETRIES`
|
||||||
- `BATCH_QUEUE_DEPTH`
|
- `BATCH_QUEUE_DEPTH`
|
||||||
- `BATCH_RETRY_POLICY` (keep or drop on retry exhaustion)
|
- `BATCH_RETRY_POLICY` (keep or drop on retry exhaustion)
|
||||||
|
|||||||
@@ -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 SENDER_OLED_READ_MS = 10000;
|
||||||
constexpr uint32_t METER_SAMPLE_INTERVAL_MS = 1000;
|
constexpr uint32_t METER_SAMPLE_INTERVAL_MS = 1000;
|
||||||
constexpr uint32_t METER_SEND_INTERVAL_MS = 30000;
|
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 uint32_t BATCH_ACK_TIMEOUT_MS = 3000;
|
||||||
constexpr uint8_t BATCH_MAX_RETRIES = 2;
|
constexpr uint8_t BATCH_MAX_RETRIES = 2;
|
||||||
constexpr uint8_t METER_BATCH_MAX_SAMPLES = 30;
|
constexpr uint8_t METER_BATCH_MAX_SAMPLES = 30;
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ static bool g_mqtt_ok = false;
|
|||||||
|
|
||||||
static bool g_oled_on = true;
|
static bool g_oled_on = true;
|
||||||
static bool g_prev_ctrl_high = false;
|
static bool g_prev_ctrl_high = false;
|
||||||
static uint32_t g_oled_off_start = 0;
|
|
||||||
static uint32_t g_last_page_ms = 0;
|
static uint32_t g_last_page_ms = 0;
|
||||||
static uint8_t g_page = 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 bool g_display_ready = false;
|
||||||
static uint32_t g_last_init_attempt_ms = 0;
|
static uint32_t g_last_init_attempt_ms = 0;
|
||||||
static bool g_last_oled_on = true;
|
static bool g_last_oled_on = true;
|
||||||
@@ -83,7 +82,7 @@ void display_init() {
|
|||||||
display.display();
|
display.display();
|
||||||
}
|
}
|
||||||
g_last_init_attempt_ms = millis();
|
g_last_init_attempt_ms = millis();
|
||||||
g_boot_ms = millis();
|
g_last_activity_ms = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
void display_set_role(DeviceRole role) {
|
void display_set_role(DeviceRole role) {
|
||||||
@@ -380,27 +379,16 @@ void display_tick() {
|
|||||||
ctrl_high = digitalRead(PIN_OLED_CTRL) == HIGH;
|
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) {
|
if (g_role == DeviceRole::Receiver) {
|
||||||
g_oled_on = true;
|
g_oled_on = true;
|
||||||
g_oled_off_start = 0;
|
g_last_activity_ms = now_ms;
|
||||||
} else if (in_boot_window) {
|
|
||||||
g_oled_on = true;
|
|
||||||
} else {
|
} else {
|
||||||
if (ctrl_high) {
|
if (ctrl_high || ctrl_falling_edge) {
|
||||||
g_oled_on = true;
|
g_last_activity_ms = now_ms;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
g_oled_on = (now_ms - g_last_activity_ms) < OLED_AUTO_OFF_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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_oled_on) {
|
if (g_oled_on) {
|
||||||
|
|||||||
@@ -152,6 +152,10 @@ static void update_battery_cache() {
|
|||||||
g_last_battery_ms = millis();
|
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() {
|
static bool sender_timesync_window_due() {
|
||||||
uint32_t interval_sec = SENDER_TIMESYNC_CHECK_SEC_FAST;
|
uint32_t interval_sec = SENDER_TIMESYNC_CHECK_SEC_FAST;
|
||||||
if (time_is_synced() && time_rtc_present()) {
|
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);
|
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);
|
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();
|
update_battery_cache();
|
||||||
}
|
}
|
||||||
data.battery_voltage_v = g_last_battery_voltage_v;
|
data.battery_voltage_v = g_last_battery_voltage_v;
|
||||||
|
|||||||
@@ -34,11 +34,8 @@ void power_configure_unused_pins_sender() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void read_battery(MeterData &data) {
|
void read_battery(MeterData &data) {
|
||||||
uint32_t sum = 0;
|
uint32_t raw = analogRead(PIN_BAT_ADC);
|
||||||
sum += analogRead(PIN_BAT_ADC);
|
float v = (static_cast<float>(raw) / 4095.0f) * ADC_REF_V * BATTERY_DIVIDER * BATTERY_CAL;
|
||||||
sum += analogRead(PIN_BAT_ADC);
|
|
||||||
float avg = static_cast<float>(sum) / 2.0f;
|
|
||||||
float v = (avg / 4095.0f) * ADC_REF_V * BATTERY_DIVIDER * BATTERY_CAL;
|
|
||||||
|
|
||||||
data.battery_voltage_v = v;
|
data.battery_voltage_v = v;
|
||||||
data.battery_percent = battery_percent_from_voltage(v);
|
data.battery_percent = battery_percent_from_voltage(v);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "rtc_ds3231.h"
|
#include "rtc_ds3231.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
|
#include <string>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
static constexpr uint8_t DS3231_ADDR = 0x68;
|
static constexpr uint8_t DS3231_ADDR = 0x68;
|
||||||
@@ -17,12 +18,14 @@ static time_t timegm_fallback(struct tm *tm_utc) {
|
|||||||
if (!tm_utc) {
|
if (!tm_utc) {
|
||||||
return static_cast<time_t>(-1);
|
return static_cast<time_t>(-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);
|
setenv("TZ", "UTC0", 1);
|
||||||
tzset();
|
tzset();
|
||||||
time_t t = mktime(tm_utc);
|
time_t t = mktime(tm_utc);
|
||||||
if (old_tz) {
|
if (!old_tz_copy.empty()) {
|
||||||
setenv("TZ", old_tz, 1);
|
setenv("TZ", old_tz_copy.c_str(), 1);
|
||||||
} else {
|
} else {
|
||||||
unsetenv("TZ");
|
unsetenv("TZ");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user