#include #include #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "ambient_weather.h" #include "app_config.h" #include "backlight.h" #include "display_rgb.h" #include "i2c_bus.h" #include "lvgl_port.h" #include "touch_gt911.h" #include "ui_manager.h" #include "wifi_manager.h" static const char *TAG = "main"; #define WEATHER_TASK_STACK 6144 #define WEATHER_TASK_PRIO 3 #define WEATHER_FIRST_DELAY_MS 3000 static void weather_update_task(void *arg) { weather_data_t weather; char line[96]; vTaskDelay(pdMS_TO_TICKS(WEATHER_FIRST_DELAY_MS)); while (1) { if (!wifi_is_connected()) { ui_set_status("WiFi: reconnecting..."); vTaskDelay(pdMS_TO_TICKS(WEATHER_UPDATE_INTERVAL_MS)); continue; } /* * Freeze rendering for the duration of the fetch. The TLS session, * the LVGL redraw and the flush into the PSRAM framebuffer all * compete with the LCD's own DMA out of that same PSRAM; letting them * overlap is what produces visible artefacts. The panel keeps * scanning the last completed frame, so the screen simply holds still. */ lvgl_port_pause(); const esp_err_t err = weather_update(&weather); lvgl_port_resume(); if (err == ESP_OK) { weather_get_string(&weather, line, sizeof(line)); ESP_LOGI(TAG, "Weather: %s", line); char detail[64]; snprintf(detail, sizeof(detail), "%.0f%% humidity", weather.has_humidity ? weather.humidity : 0.0f); ui_update_weather((int16_t)(weather.temp_f + 0.5f), weather.has_humidity ? detail : "Outdoor"); wifi_get_status(line, sizeof(line)); ui_set_status(line); } else { ESP_LOGW(TAG, "Weather update failed"); ui_set_status("Weather update failed"); } vTaskDelay(pdMS_TO_TICKS(WEATHER_UPDATE_INTERVAL_MS)); } } void app_main(void) { ESP_LOGI(TAG, "Thermostat HMI -- CrowPanel 7.0\" ESP32-S3 (800x480)"); /* * WiFi comes up FIRST, before the RGB panel exists. * * The panel's bounce-buffer ISR reads the framebuffer out of PSRAM, and * PSRAM shares the cache with flash on the ESP32-S3. Bringing up NVS and * the WiFi PHY writes calibration data to flash, which disables the cache * and faults that ISR. Doing it here means every flash write is finished * before the panel starts scanning. */ const esp_err_t wifi_err = wifi_init_sta(); if (wifi_err != ESP_OK) { ESP_LOGE(TAG, "WiFi failed -- continuing, weather will be disabled"); } /* LVGL core must exist before the display and touch drivers register * themselves with it. */ ESP_ERROR_CHECK(lvgl_port_init()); /* Shared bus: STC8H1K28 board controller and GT911 both live on it. */ ESP_ERROR_CHECK(i2c_bus_init()); /* Must precede touch_init(): the STC8H1K28 holds the GT911 in reset * until its init sequence runs. */ if (backlight_init() != ESP_OK) { ESP_LOGE(TAG, "STC8H1K28 init failed -- backlight and touch will not work"); } i2c_bus_scan(TAG); ESP_ERROR_CHECK(display_init()); if (touch_init() != ESP_OK) { /* A dead touch panel should not stop the display from working. */ ESP_LOGW(TAG, "Touch unavailable -- continuing without input"); } ui_init(); ESP_ERROR_CHECK(lvgl_port_start()); /* First frame is rendered by now, so it is safe to light the panel. */ vTaskDelay(pdMS_TO_TICKS(100)); ESP_ERROR_CHECK(backlight_set(255)); if (wifi_err != ESP_OK) { ui_set_status("WiFi: failed"); return; } char status[96]; wifi_get_status(status, sizeof(status)); ui_set_status(status); if (weather_init() != ESP_OK) { ui_set_status("Weather: no API credentials"); return; } xTaskCreate(weather_update_task, "weather", WEATHER_TASK_STACK, NULL, WEATHER_TASK_PRIO, NULL); ESP_LOGI(TAG, "System ready"); }