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:
1 parent
d2e4c974dd
commit
4c7b120473
10 files changed
+206
-27
No files matched your search
+5
-5
@@ -36,12 +36,12 @@ static void flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
|
||||
(void)area;
|
||||
|
||||
/*
|
||||
* px_map is one of the panel's own framebuffers, so this does not copy —
|
||||
* the RGB driver just switches which buffer is being scanned out and waits
|
||||
* for the switch to take effect.
|
||||
* px_map is one of the panel's own framebuffers, so no copy happens — the
|
||||
* RGB driver switches which buffer is scanned out. present_frame blocks
|
||||
* until that switch has latched at vsync; releasing LVGL any earlier would
|
||||
* let it render the next frame into the buffer still on screen.
|
||||
*/
|
||||
esp_lcd_panel_handle_t panel = display_panel();
|
||||
esp_lcd_panel_draw_bitmap(panel, 0, 0, DISPLAY_H_RES, DISPLAY_V_RES, px_map);
|
||||
display_present_frame(px_map);
|
||||
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
|
||||
+29
-9
@@ -179,7 +179,7 @@ static void build_screen(void)
|
||||
|
||||
static void refresh_temperatures(const control_state_t *st)
|
||||
{
|
||||
char buf[32];
|
||||
char buf[40];
|
||||
|
||||
if (isnan(st->governing_temp_c)) {
|
||||
set_text_if_changed(s_lbl_temp, "--" DEG);
|
||||
@@ -189,15 +189,27 @@ static void refresh_temperatures(const control_state_t *st)
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the effective setpoint, not the requested one. When the fallback
|
||||
* ladder has clamped it, the screen must say what the thermostat is
|
||||
* actually doing — the fault text below explains why.
|
||||
* The setpoint shown must come from settings, live — not from control
|
||||
* state, which only refreshes every CONTROL_INTERVAL_S. Rendering the
|
||||
* control loop's copy makes the knob feel dead for up to 10 seconds per
|
||||
* detent. When the fallback ladder has clamped the working setpoint, show
|
||||
* both: what the user asked for and what the thermostat is actually
|
||||
* holding — the fault text below explains why they differ.
|
||||
*/
|
||||
snprintf(buf, sizeof(buf), "Set %.0f%s",
|
||||
c_to_display(st->effective_setpoint_c), unit_suffix());
|
||||
float live_sp = control_get_setpoint();
|
||||
bool capped = st->effective_setpoint_c < live_sp - 0.05f;
|
||||
|
||||
if (capped) {
|
||||
snprintf(buf, sizeof(buf), "Set %.0f%s (cap %.0f)",
|
||||
c_to_display(live_sp), unit_suffix(),
|
||||
c_to_display(st->effective_setpoint_c));
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Set %.0f%s",
|
||||
c_to_display(live_sp), unit_suffix());
|
||||
}
|
||||
set_text_if_changed(s_lbl_setpoint, buf);
|
||||
|
||||
int pct = (int)((st->effective_setpoint_c - CONTROL_SETPOINT_MIN_C) * 100.0f /
|
||||
int pct = (int)((live_sp - CONTROL_SETPOINT_MIN_C) * 100.0f /
|
||||
(CONTROL_SETPOINT_MAX_C - CONTROL_SETPOINT_MIN_C));
|
||||
if (pct < 0) {
|
||||
pct = 0;
|
||||
@@ -216,13 +228,21 @@ static void refresh_room_and_mode(const control_state_t *st)
|
||||
: "No sensor";
|
||||
set_text_if_changed(s_lbl_room, room);
|
||||
|
||||
/*
|
||||
* While a selection is in progress, read the *settings* value, not control
|
||||
* state: the control loop only re-evaluates every CONTROL_INTERVAL_S, and
|
||||
* showing its lagging copy would make the knob appear to do nothing.
|
||||
* control_get_mode()/control_get_selected_room() go straight to settings.
|
||||
*/
|
||||
char buf[48];
|
||||
switch (s_focus) {
|
||||
case FOCUS_ROOM:
|
||||
snprintf(buf, sizeof(buf), "> SELECT ROOM <");
|
||||
snprintf(buf, sizeof(buf), "> ROOM: %s <",
|
||||
zigbee_client_room_name(control_get_selected_room()));
|
||||
break;
|
||||
case FOCUS_MODE:
|
||||
snprintf(buf, sizeof(buf), "> MODE: %s <", control_mode_str(st->mode));
|
||||
snprintf(buf, sizeof(buf), "> MODE: %s <",
|
||||
control_mode_str(control_get_mode()));
|
||||
break;
|
||||
default:
|
||||
snprintf(buf, sizeof(buf), "%s", control_mode_str(st->mode));
|
||||
|
||||
Reference in new issue
Block a user