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>
155 lines
4.4 KiB
C
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;
|
|
}
|