Fix TWDT starvation, vsync race, and UI responsiveness from review

Three bugs and two UX defects found on review, plus hardening:

- The control loop fed the task watchdog then slept exactly the watchdog
  period (both 10s), tripping the TWDT fractionally late on every single
  iteration. Sleep is now sliced 1s with a reset per slice. Also set
  CONFIG_ESP_TASK_WDT_PANIC=y - the sdkconfig comment claimed a hung task
  panics the chip, but nothing actually made that true.

- The LVGL flush called draw_bitmap (which only queues the framebuffer
  switch) and released LVGL immediately, letting it render the next frame
  into the buffer still being scanned out - the exact PRD 8.2 race the
  double-buffer design exists to prevent. display_present_frame() now
  blocks on a vsync handshake until the swap has latched.

- WiFi reconnect slept up to 30s inside the system event handler, stalling
  all queued WiFi/IP events. Moved to an esp_timer.

- The setpoint label, arc, and mode/room lines rendered the control loop's
  copy of state, which refreshes every 10s - every knob detent looked dead
  for up to 10 seconds. The UI now reads settings live; when the fallback
  ladder clamps the setpoint it shows both values. Room-select focus now
  shows which room is being selected.

- z2m ISO_8601_local timestamps carry a UTC offset the parser ignored,
  which would shift apparent reading age by the local offset (5-6h in the
  US) and permanently trip the fallback ladder. Offset is now honoured.

- Suppressed the cosmetic "control loop stopped" fault in the window
  between relay_init and the control task's first call-in at boot.

- Explicit pullups on the encoder A/B lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mattandClaude Opus 5 committed 2026-08-03 20:05:51 -07:00
1 parent d2e4c974dd
commit 4c7b120473
10 files changed
+206 -27

No files matched your search

+29 -2
View File
@@ -103,10 +103,37 @@ static bool parse_sensor_age(const cJSON *root, uint32_t *age_s)
seen = (time_t)(ls->valuedouble / 1000.0);
} else if (cJSON_IsString(ls) && ls->valuestring) {
struct tm tm = {0};
if (!strptime(ls->valuestring, "%Y-%m-%dT%H:%M:%S", &tm)) {
const char *rest = strptime(ls->valuestring, "%Y-%m-%dT%H:%M:%S", &tm);
if (!rest) {
return false;
}
seen = (time_t)tm_to_epoch_utc(&tm);
int64_t epoch = tm_to_epoch_utc(&tm);
/*
* Honour the offset suffix. z2m's ISO_8601 setting emits UTC ("Z"),
* but ISO_8601_local emits local time with an offset ("-05:00").
* Ignoring that suffix would shift every reading's apparent age by
* the local offset — on a US install that is five or six hours, which
* makes live sensors look stale and trips the fallback ladder
* permanently. local = UTC + offset, so subtract it.
*/
if (*rest == '.') { /* fractional seconds */
rest++;
while (*rest >= '0' && *rest <= '9') {
rest++;
}
}
if (*rest == '+' || *rest == '-') {
int sign = (*rest == '-') ? -1 : 1;
int oh = 0, om = 0;
if (sscanf(rest + 1, "%2d:%2d", &oh, &om) < 1) {
return false;
}
epoch -= (int64_t)sign * (oh * 3600 + om * 60);
}
/* 'Z' or nothing: already UTC. */
seen = (time_t)epoch;
} else {
return false;
}