Work through the remaining review items in TODO.md

Keep the HTTP session alive between weather polls (TODO #1). Re-creating the
client each time forced a fresh TLS handshake inside the window where the UI
is frozen. Measured on hardware: the fetch went from ~1.3s to ~80ms, so the
pause is no longer long enough to matter once thermostat controls exist. The
timeout drops from 30s to 10s once a session is warm, since only a cold
handshake needs the longer budget. Any transport error or non-200 tears the
session down so a half-open connection cannot poison every later poll.

The response buffer is now reset per request rather than on
HTTP_EVENT_ON_CONNECTED -- a kept-alive connection does not re-fire that
event, so the buffer would have accumulated across polls.

Indicate stale readings (TODO #2). After two consecutive failures the
temperature dims and the status line shows the age. Age comes from tick
counts, not weather_data_t.last_update: there is no SNTP client here, so
time(NULL) starts at the epoch and any age derived from it is meaningless.

Gate the touch readout behind UI_DEBUG_TOUCH, default off (TODO #3). Kept the
note about LVGL not bubbling events to parents -- it documents a real trap.

Delete main/utils (TODO #4); it had no callers outside itself. Also refreshed
project_structure.txt, which was additionally missing i2c_bus and backlight.

Lay out two 4MB OTA slots (TODO #5) while no devices are fielded, and document
in the README that an OTA download cannot run while the panel is scanning --
the flash write faults the bounce-buffer ISR.

Log the truncation warning once per response instead of once per chunk, and
record the weather task's stack high-water mark (TODO #6): ~2400 bytes spare
of 6144.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
mattandClaude committed 2026-08-04 09:08:51 -07:00
1 parent 5bbb50c4eb
commit d4b2ab7f99
10 files changed
+222 -101

No files matched your search

+46 -2
View File
@@ -18,15 +18,36 @@
static const char *TAG = "main";
/*
* 6144 bytes. Measured headroom after a fetch including the TLS handshake --
* the deepest this task gets -- is ~2400 bytes, so roughly 3.7KB is in use.
* mbedTLS keeps its heap in PSRAM (CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC), which
* is why the handshake fits at all. Re-check the high-water-mark log if more
* work lands in this task.
*/
#define WEATHER_TASK_STACK 6144
#define WEATHER_TASK_PRIO 3
#define WEATHER_FIRST_DELAY_MS 3000
/* Consecutive failed polls before the displayed reading is treated as stale. */
#define WEATHER_STALE_AFTER_POLLS 2
static void weather_update_task(void *arg)
{
weather_data_t weather;
char line[96];
/*
* Age is tracked in ticks, not from weather_data_t.last_update. That field
* comes from time(NULL), and with no SNTP client the system clock starts
* at the epoch -- an age derived from it would be nonsense. Tick
* subtraction is also wrap-safe.
*/
TickType_t last_success = 0;
int consecutive_failures = 0;
bool have_reading = false;
bool stack_logged = false;
vTaskDelay(pdMS_TO_TICKS(WEATHER_FIRST_DELAY_MS));
while (1) {
@@ -48,6 +69,10 @@ static void weather_update_task(void *arg)
lvgl_port_resume();
if (err == ESP_OK) {
last_success = xTaskGetTickCount();
consecutive_failures = 0;
have_reading = true;
weather_get_string(&weather, line, sizeof(line));
ESP_LOGI(TAG, "Weather: %s", line);
@@ -59,12 +84,31 @@ static void weather_update_task(void *arg)
* below 0°F, turning -10.6 into -10. */
ui_update_weather((int16_t)lroundf(weather.temp_f),
weather.has_humidity ? detail : "Outdoor");
ui_set_weather_stale(false);
wifi_get_status(line, sizeof(line));
ui_set_status(line);
/* One-shot, after a fetch that included a TLS handshake -- that is
* the deepest this task's stack ever gets. */
if (!stack_logged) {
stack_logged = true;
ESP_LOGI(TAG, "weather task stack headroom: %u bytes",
(unsigned)(uxTaskGetStackHighWaterMark(NULL) * sizeof(StackType_t)));
}
} else {
ESP_LOGW(TAG, "Weather update failed");
ui_set_status("Weather update failed");
consecutive_failures++;
ESP_LOGW(TAG, "Weather update failed (%d consecutive)", consecutive_failures);
if (have_reading && consecutive_failures >= WEATHER_STALE_AFTER_POLLS) {
const uint32_t age_min =
((xTaskGetTickCount() - last_success) * portTICK_PERIOD_MS) / 60000U;
ui_set_weather_stale(true);
snprintf(line, sizeof(line), "No update for %lum", (unsigned long)age_min);
ui_set_status(line);
} else {
ui_set_status("Weather update failed");
}
}
vTaskDelay(pdMS_TO_TICKS(WEATHER_UPDATE_INTERVAL_MS));