Files
rotaryThermo/main/net/wifi_manager.c
T
mattandClaude Opus 5 4c7b120473 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>
2026-08-03 20:05:51 -07:00

155 lines
4.4 KiB
C

#include "wifi_manager.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "app_config.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_sntp.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
static const char *TAG = "wifi";
#define CONNECTED_BIT BIT0
#define RETRY_MIN_MS 1000
#define RETRY_MAX_MS 30000
static EventGroupHandle_t s_events;
static volatile bool s_connected;
static int s_retry_ms = RETRY_MIN_MS;
static bool s_sntp_started;
static esp_timer_handle_t s_reconnect_timer;
static void reconnect_timer_cb(void *arg)
{
(void)arg;
esp_wifi_connect();
}
/*
* Schedule the retry on a timer rather than sleeping here. This runs on the
* shared system event task — a vTaskDelay would stall every queued WiFi/IP
* event behind it (up to 30s at full backoff) and can overflow the default
* event queue during an outage.
*/
static void schedule_reconnect(void)
{
ESP_LOGW(TAG, "reconnecting in %d ms", s_retry_ms);
esp_timer_stop(s_reconnect_timer); /* no-op if not running */
esp_timer_start_once(s_reconnect_timer, (uint64_t)s_retry_ms * 1000);
s_retry_ms *= 2;
if (s_retry_ms > RETRY_MAX_MS) {
s_retry_ms = RETRY_MAX_MS;
}
}
static void event_handler(void *arg, esp_event_base_t base,
int32_t id, void *data)
{
(void)arg;
if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
s_connected = false;
xEventGroupClearBits(s_events, CONNECTED_BIT);
schedule_reconnect();
} else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *e = data;
ESP_LOGI(TAG, "connected, ip " IPSTR, IP2STR(&e->ip_info.ip));
s_connected = true;
s_retry_ms = RETRY_MIN_MS;
xEventGroupSetBits(s_events, CONNECTED_BIT);
/* Start SNTP once we first have an address. Schedule mode needs a
* real clock, and control.c refuses to act on one that looks like
* 1970. */
if (!s_sntp_started) {
esp_sntp_config_t cfg =
ESP_NETIF_SNTP_DEFAULT_CONFIG(APP_SNTP_SERVER);
if (esp_netif_sntp_init(&cfg) == ESP_OK) {
s_sntp_started = true;
ESP_LOGI(TAG, "sntp started (%s)", APP_SNTP_SERVER);
}
}
}
}
esp_err_t wifi_manager_init(void)
{
if (s_events) {
return ESP_OK;
}
s_events = xEventGroupCreate();
if (!s_events) {
return ESP_ERR_NO_MEM;
}
setenv("TZ", APP_TIMEZONE, 1);
tzset();
const esp_timer_create_args_t timer_args = {
.callback = reconnect_timer_cb,
.name = "wifi_retry",
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_reconnect_timer));
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t init_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&init_cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler, NULL, NULL));
wifi_config_t wifi_cfg = {0};
strlcpy((char *)wifi_cfg.sta.ssid, APP_WIFI_SSID, sizeof(wifi_cfg.sta.ssid));
strlcpy((char *)wifi_cfg.sta.password, APP_WIFI_PASS,
sizeof(wifi_cfg.sta.password));
wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
/* CONFIG_ESP_WIFI_NVS_ENABLED=n, so nothing here touches flash. */
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "started, joining \"%s\"", APP_WIFI_SSID);
return ESP_OK;
}
bool wifi_manager_is_connected(void)
{
return s_connected;
}
bool wifi_manager_wait_connected(int timeout_ms)
{
if (!s_events) {
return false;
}
EventBits_t bits = xEventGroupWaitBits(s_events, CONNECTED_BIT,
pdFALSE, pdFALSE,
pdMS_TO_TICKS(timeout_ms));
return (bits & CONNECTED_BIT) != 0;
}