ui: make housekeeping refresh path on-demand

Stop doing UI work nobody asked for. The 5 s housekeeping tick was
reading env sensors (I2C, 10-50 ms), the battery ADC (regulator
toggle + 8 samples, every 60 s), and re-rendering the display
unconditionally — all while the display might be off and nothing
on-air had requested any of it.

Now:
- render_sensors() reads env sensors only when the user is on
  that page (event-driven, never fires during idle)
- battery refresh is lazy on ui_pages_render() with a 30 s
  freshness guard; explicit ui_set_battery() calls also count
- the unconditional OLED rerender from housekeeping is gone;
  real state changes (messages, BLE, button press) still fire
  schedule_render() directly

Telemetry / stats paths read fresh ADC + sensors on demand and
were never using the UI cache, so over-the-air consumers are
unaffected.
This commit is contained in:
liquidraver
2026-05-20 22:10:14 +02:00
parent c115803786
commit 3a35fec8b3
8 changed files with 75 additions and 89 deletions
+1
View File
@@ -100,3 +100,4 @@ zephcore/SX126X_LBT_RX_DEBUG_HANDOFF.md
PSRAM_HANDOFF.md
BLE_AUDIT_INDEX.md
POWER_AUDIT_HANDOFF.md
POWER_AUDIT_INDEX.md
+2 -23
View File
@@ -231,13 +231,8 @@ extern "C" void mesh_housekeeping_ui_refresh(void)
return;
}
/* Battery voltage changes slowly — read every ~60s (12 × 5s housekeeping)
* instead of every 5s. Saves power (regulator toggle + ADC) and log noise. */
static uint8_t batt_counter;
if (++batt_counter >= 12) {
batt_counter = 0;
ui_set_battery(s_board->getBattMilliVolts(), 0);
}
/* Battery is now refreshed lazily from ui_pages_render() with a 30 s
* freshness guard — no periodic ADC fire here. */
/* Update top bar clock from RTC */
ui_set_clock(s_rtc_clock->getCurrentTime());
@@ -292,22 +287,6 @@ extern "C" void mesh_housekeeping_ui_refresh(void)
}
}
/* Read environment sensors if available */
if (env_sensors_available()) {
struct env_data edata;
if (env_sensors_read(&edata) == 0) {
ui_set_sensor_data(
edata.has_temperature ? (int16_t)(edata.temperature_c * 10) : 0,
edata.has_pressure ? (uint32_t)(edata.pressure_hpa * 100) : 0,
edata.has_humidity ? (uint16_t)(edata.humidity_pct * 10) : 0,
0);
}
}
/* Update offline queue message count */
ui_set_msg_count(s_mesh->getOfflineQueueCount());
/* Trigger display refresh */
ui_refresh_display();
}
+22 -17
View File
@@ -15,8 +15,11 @@
*/
#include "ui_pages.h"
#include "ui_task.h"
#include "display.h"
#include <ZephyrSensorManager.h>
#include <zephyr/kernel.h>
#include <stdio.h>
#include <string.h>
@@ -547,34 +550,31 @@ static void render_sensors(void)
char buf[24];
int y = CONTENT_Y;
/* Temperature */
if (state.temperature_c10 != 0) {
/* Lazy read: only fetch sensors when the user is actually looking at
* this page. Render is event-driven (schedule_render only on real UI
* events), so this never fires periodically when idle. */
struct env_data edata;
bool have_env = env_sensors_available() && env_sensors_read(&edata) == 0;
if (have_env && edata.has_temperature) {
int16_t t10 = (int16_t)(edata.temperature_c * 10);
snprintf(buf, sizeof(buf), "Temp: %d.%d C",
state.temperature_c10 / 10,
abs(state.temperature_c10 % 10));
t10 / 10, abs(t10 % 10));
mc_display_text(0, y, buf, false);
y += LINE_H;
}
/* Pressure */
if (state.pressure_pa != 0) {
if (have_env && edata.has_pressure) {
snprintf(buf, sizeof(buf), "Press: %u hPa",
(unsigned)(state.pressure_pa / 100));
(unsigned)edata.pressure_hpa);
mc_display_text(0, y, buf, false);
y += LINE_H;
}
/* Humidity */
if (state.humidity_rh10 != 0) {
if (have_env && edata.has_humidity) {
uint16_t h10 = (uint16_t)(edata.humidity_pct * 10);
snprintf(buf, sizeof(buf), "Humid: %u.%u%%",
state.humidity_rh10 / 10, state.humidity_rh10 % 10);
mc_display_text(0, y, buf, false);
y += LINE_H;
}
/* Light */
if (state.light_lux != 0) {
snprintf(buf, sizeof(buf), "Light: %u lux", state.light_lux);
h10 / 10, h10 % 10);
mc_display_text(0, y, buf, false);
y += LINE_H;
}
@@ -718,6 +718,11 @@ struct ui_state *ui_pages_get_state(void)
void ui_pages_render(void)
{
/* Refresh battery lazily — ADC only fires when the cached reading is
* stale (≥30 s). Render is event-driven, so during idle this never
* runs. Telemetry / stats paths bypass the cache entirely. */
ui_refresh_battery();
mc_display_clear();
render_top_bar();
render_page_indicator();
-6
View File
@@ -86,12 +86,6 @@ struct ui_state {
/* LEDs page */
bool leds_disabled; /* true = LEDs off */
/* Sensors page */
int16_t temperature_c10; /* temp in 0.1°C */
uint32_t pressure_pa; /* pressure in Pa */
uint16_t humidity_rh10; /* humidity in 0.1% RH */
uint16_t light_lux;
/* Current page */
enum ui_page current_page;
+31 -31
View File
@@ -1065,12 +1065,43 @@ void ui_set_gps_data(bool has_fix, uint8_t sats,
s->gps_alt_mm = alt_mm;
}
/* Lazy battery refresh: render path calls ui_refresh_battery(); we only hit
* the ADC if the cached value is older than UI_BATT_REFRESH_MS. Telemetry /
* stats paths bypass this and read fresh directly via _batt_cb, so over-the-
* air consumers are unaffected — this gate only governs the local display. */
#define UI_BATT_REFRESH_MS 30000
static uint16_t (*s_batt_provider)(void);
static uint32_t s_batt_last_read_ms;
static bool s_batt_ever_read;
void ui_set_battery(uint16_t mv, uint8_t pct)
{
struct ui_state *s = get_state();
s->battery_mv = mv;
s->battery_pct = pct;
/* Treat any explicit setter call as a fresh read so the lazy guard
* doesn't immediately re-fire the ADC. */
s_batt_last_read_ms = k_uptime_get_32();
s_batt_ever_read = true;
}
void ui_set_battery_provider(uint16_t (*provider)(void))
{
s_batt_provider = provider;
}
void ui_refresh_battery(void)
{
if (!s_batt_provider) {
return;
}
uint32_t now = k_uptime_get_32();
if (s_batt_ever_read && (now - s_batt_last_read_ms) < UI_BATT_REFRESH_MS) {
return;
}
ui_set_battery(s_batt_provider(), 0);
}
void ui_set_clock(uint32_t epoch)
@@ -1127,17 +1158,6 @@ void ui_set_node_name(const char *name)
#endif
}
void ui_set_sensor_data(int16_t temp_c10, uint32_t pressure_pa,
uint16_t humidity_rh10, uint16_t light_lux)
{
struct ui_state *s = get_state();
s->temperature_c10 = temp_c10;
s->pressure_pa = pressure_pa;
s->humidity_rh10 = humidity_rh10;
s->light_lux = light_lux;
}
void ui_set_gps_available(bool available)
{
struct ui_state *s = get_state();
@@ -1204,23 +1224,3 @@ void ui_set_heartbeat_led(bool enabled)
#endif
}
void ui_refresh_display(void)
{
if (!ui_initialized) {
return;
}
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
/* EPD displays: skip periodic housekeeping renders.
* Each full e-paper refresh takes ~2s and causes visible flashing.
* All meaningful events (messages, BLE, GPS fix, button presses)
* already trigger renders via their own ui_set_*() → schedule_render().
* Housekeeping just updates slow-changing data (clock, contact ages)
* which will appear on the next event-driven render. */
if (mc_display_is_epd()) {
return;
}
schedule_render();
#endif
}
+10 -4
View File
@@ -116,8 +116,6 @@ void ui_clear_recent(void);
/**
* Update sensor data for display.
*/
void ui_set_sensor_data(int16_t temp_c10, uint32_t pressure_pa,
uint16_t humidity_rh10, uint16_t light_lux);
/**
* Set whether GPS hardware was detected at boot.
@@ -164,9 +162,17 @@ void ui_set_heartbeat_led(bool enabled);
void ui_set_offgrid_mode(bool enabled);
/**
* Trigger a display refresh (for periodic updates from housekeeping).
* Register a battery-voltage provider used by ui_refresh_battery().
* provider() must return millivolts (0 if no battery hardware).
*/
void ui_refresh_display(void);
void ui_set_battery_provider(uint16_t (*provider)(void));
/**
* Lazy battery refresh: re-read the ADC only if cached value is stale.
* Called from the page render path so the ADC fires at most once per
* 30 s and only when the display is actually being drawn.
*/
void ui_refresh_battery(void);
#ifdef __cplusplus
}
+1
View File
@@ -603,6 +603,7 @@ int main(void)
companion_mesh.setWriteFrameCallback(write_frame);
companion_mesh.setPushCallback(push_callback);
companion_mesh.setBatteryCallback(get_battery_mv);
ui_set_battery_provider(get_battery_mv);
companion_mesh.setRadioReconfigureCallback(radio_reconfigure);
companion_mesh.setPinChangeCallback([](uint32_t new_pin) {
zephcore_ble_set_passkey(new_pin);
+8 -8
View File
@@ -276,6 +276,11 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time)
#ifdef ZEPHCORE_LORA
static mesh::ZephyrBoard zephyr_board;
static uint16_t get_battery_mv(void)
{
return zephyr_board.getBattMilliVolts();
}
/* Radio is constructed with no prefs pointer; main() binds it to
* repeater_mesh._prefs via setPrefs() before repeater_mesh.begin(). */
@@ -364,15 +369,9 @@ static void repeater_event_loop(void)
lora_radio.getNoiseFloor());
}
/* Battery every ~60s (12 × 5s housekeeping) */
static uint8_t batt_counter;
if (++batt_counter >= 12) {
batt_counter = 0;
ui_set_battery(zephyr_board.getBattMilliVolts(), 0);
}
/* Battery is now refreshed lazily from ui_pages_render() with
* a 30 s freshness guard — no periodic ADC fire here. */
#endif
ui_refresh_display();
}
}
}
@@ -506,6 +505,7 @@ int main(void)
prefs->cr,
prefs->tx_power_dbm,
lora_radio.getNoiseFloor());
ui_set_battery_provider(get_battery_mv);
ui_set_battery(zephyr_board.getBattMilliVolts(), 0);
ui_set_gps_available(gps_is_available());