/* * Rotary Thermostat — CrowPanel 2.1" HMI Rotary Display. * * Boot ordering here is not arbitrary. Reading top to bottom: * * 1. Relay off, first, before anything can fail. A PCF8574 does not reset * when the ESP32 does, so if we got here from a panic while the contact * was closed, the furnace is still running right now. Nothing else in * this function matters as much as the next few milliseconds. * 2. WiFi before the display (PRD 8.2). The radio's PSRAM allocations and * the RGB panel's bandwidth demands do not mix well the other way round. * 3. Display, then LVGL, then UI. * 4. Control loop last, once everything it reports on exists. */ #include "control.h" #include "cst826.h" #include "display.h" #include "encoder.h" #include "esp_log.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "i2c_bus.h" #include "lvgl_port.h" #include "pcf8574.h" #include "relay.h" #include "settings.h" #include "ui_manager.h" #include "wifi_manager.h" #include "zigbee_client.h" static const char *TAG = "main"; /* * Set to 1 to draw colour bars and stop before LVGL. * * This is the PRD phase-2 check. It proves the ST7701S init sequence, the RGB * bus and the timings on their own, with no LVGL in the picture to blame. */ #define BRINGUP_TEST_PATTERN 0 void app_main(void) { ESP_LOGI(TAG, "Rotary Thermostat starting"); /* ---- 1. Make the furnace safe ------------------------------------- */ ESP_ERROR_CHECK(i2c_bus_init()); /* * The scan is the cheap sanity check from PRD 8.1: finding the PCF8574 at * 0x21 and the CST826 at 0x15 proves the board is alive and the I2C pins * are right, independently of the display. */ i2c_bus_scan(); if (pcf8574_init() != ESP_OK) { /* * Without the expander there is no way to guarantee the relay is open, * and no way to open it if it is closed. Continuing would mean running * a heating controller that cannot turn the heat off. Reboot and try * again rather than pretend. */ ESP_LOGE(TAG, "expander init failed — cannot control the relay, " "restarting in 5s"); vTaskDelay(pdMS_TO_TICKS(5000)); esp_restart(); } /* pcf8574_init() already pushed the all-high shadow, which de-asserts the * relay. This is belt and braces, and it is cheap. */ relay_emergency_off(); ESP_LOGI(TAG, "relay de-asserted"); /* ---- 2. Settings and network -------------------------------------- */ settings_init(); /* Non-blocking: returns as soon as the driver is started. */ wifi_manager_init(); /* * Give the association a few seconds before bringing the panel up. Not * required for correctness — everything downstream tolerates no network — * but PRD 8.2 wants WiFi up first, and it means the first frame drawn * usually already has real readings on it. */ if (wifi_manager_wait_connected(6000)) { ESP_LOGI(TAG, "network up before display init"); } else { ESP_LOGW(TAG, "no network yet — continuing, it will connect in the " "background"); } zigbee_client_init(); /* ---- 3. Display ---------------------------------------------------- */ if (display_init() != ESP_OK) { /* * A blind thermostat is still a thermostat. It has a setpoint from * NVS, sensors over MQTT and a relay; losing the screen should not * lose the heat. */ ESP_LOGE(TAG, "display init failed — continuing headless"); } else { #if BRINGUP_TEST_PATTERN display_test_pattern(); ESP_LOGW(TAG, "BRINGUP_TEST_PATTERN is set — stopping here"); while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } #endif cst826_init(); if (lvgl_port_init() == ESP_OK) { ui_manager_init(); } else { ESP_LOGE(TAG, "LVGL port failed — continuing headless"); } } if (encoder_init() != ESP_OK) { ESP_LOGE(TAG, "encoder init failed — setpoint fixed at stored value"); } /* ---- 4. Arm the heat ----------------------------------------------- */ ESP_ERROR_CHECK(relay_init()); ESP_ERROR_CHECK(control_init()); ESP_LOGI(TAG, "running"); /* * Everything from here is task-driven. This loop only reports, at a * cadence slow enough to be readable in a terminal left open on the bench. */ while (1) { vTaskDelay(pdMS_TO_TICKS(60000)); control_state_t st; relay_status_t rs; control_get_state(&st); relay_status(&rs); ESP_LOGI(TAG, "temp %.1fC set %.1fC | %s | heat %s | %s | wifi %s mqtt %s", st.governing_temp_c, st.effective_setpoint_c, (st.governing_room >= 0) ? zigbee_client_room_name(st.governing_room) : "none", rs.heat_on ? "ON" : "off", control_source_str(st.source), wifi_manager_is_connected() ? "up" : "down", zigbee_client_is_connected() ? "up" : "down"); } }