ESP-IDF v5.3.2 firmware for the CrowPanel 2.1" rotary display. Builds clean;
nothing has been run on hardware yet.
Hardware verified against Elecrow's own source for this board and
cross-checked against their ESPHome config, per PRD 8.1. Three corrections to
the PRD's pinout, all documented in docs/HARDWARE.md:
- touch is CST826 at 0x15, not CST816 (chip ID reads 0x11, not 0xB4)
- the ST7701S needs a 3-wire SPI bus on GPIO 16/2/1 that the PRD omitted;
without it the init sequence cannot be sent and the panel stays black
- the backlight is GPIO6
Two further departures from the PRD, both forced by facts on the ground:
- Transport is MQTT, not HTTP. zigbee2mqtt has no REST endpoint for live
sensor state. Subscribing to the broker also gets retained topics, so
every room's last reading arrives on connect rather than after a poll.
- The furnace is 2-wire R/W with no C, not 3-wire. The board needs its own
5V supply. The thermostat's 5 CPH setting is enforced as a minimum
12-minute period between heat-call starts, which a minimum off-time alone
does not achieve.
All of PRD section 5 is implemented in the relay layer itself: fail-open,
watchdog-gated output, max on-time, min off-time, cycle-rate limit, startup
lockout, and the stale-data fallback ladder down to open-loop freeze
protection. The one gap that cannot be closed in software — the PCF8574 not
resetting when the ESP32 does — is documented rather than papered over.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
140 lines
4.0 KiB
C
140 lines
4.0 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_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 void schedule_reconnect(void)
|
|
{
|
|
ESP_LOGW(TAG, "reconnecting in %d ms", s_retry_ms);
|
|
vTaskDelay(pdMS_TO_TICKS(s_retry_ms));
|
|
|
|
s_retry_ms *= 2;
|
|
if (s_retry_ms > RETRY_MAX_MS) {
|
|
s_retry_ms = RETRY_MAX_MS;
|
|
}
|
|
|
|
esp_wifi_connect();
|
|
}
|
|
|
|
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);
|
|
/*
|
|
* Runs on the event task, so the delay in schedule_reconnect() blocks
|
|
* only WiFi event processing — never the control loop, which lives on
|
|
* its own task and does not consult connection state anyway.
|
|
*/
|
|
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();
|
|
|
|
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;
|
|
}
|