New firmware/esp32p4-sensor-node/ ESP-IDF (C, FreeRTOS) project skeleton per docs/superpowers/specs/2026-07-23-esp32-sensor-node-design.md's Workstream I: - Wi-Fi station-mode connect with exponential-backoff reconnect (wifi_manager.c), credentials from a gitignored main/device_config.h the seeker fills in (template: device_config.h.example). - Telemetry HTTP client (telemetry_client.c) POSTing the spec's exact contract shape to /api/device/telemetry with a Bearer token, via esp_http_client + cJSON. - BME280 I2C driver (bme280.c) with Bosch's public double-precision compensation formulas, using ESP-IDF's newer driver/i2c_master.h API. - LD2410 mmWave presence driver (ld2410.c) over UART, chosen over a plain PIR for its distance/motion data richness — its frame-offset parsing is flagged as the least-certain code in the firmware. - sensor_driver_t registry (sensor_driver.h, sensor_registry.c) so new sensors are a new driver file + one array line, no main-loop changes. - README.md: build steps, manual-config walkthrough, wiring/pinouts, and an explicit "what's verified vs. not" section plus a real hardware caveat (ESP32-P4 has no integrated Wi-Fi radio). UNVERIFIED AGAINST REAL HARDWARE per the spec's honesty-policy note — no ESP-IDF toolchain or physical boards available in this environment. Syntax-checked with gcc against hand-written ESP-IDF API stubs (not committed) as a best-effort substitute for a real idf.py build. Workstream J (RTL-SDR experimental module) is explicitly out of scope here; firmware/esp32p4-sensor-node/components/ is left in place for it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
132 lines
5.0 KiB
C
132 lines
5.0 KiB
C
// Wi-Fi station-mode connection manager — see wifi_manager.h for the
|
|
// hardware caveat about ESP32-P4 not having an integrated radio.
|
|
//
|
|
// Structure follows ESP-IDF's own documented Wi-Fi station example pattern
|
|
// (event-driven connect via WIFI_EVENT/IP_EVENT + an event group the rest
|
|
// of the app can block on), extended with an exponential-backoff reconnect
|
|
// instead of giving up after N attempts — a sensor node left running for
|
|
// weeks needs to ride out a router reboot or a home Wi-Fi outage
|
|
// indefinitely, not park itself in a permanent failure state.
|
|
//
|
|
// UNVERIFIED AGAINST REAL HARDWARE — see README.md.
|
|
|
|
#include <inttypes.h>
|
|
#include "wifi_manager.h"
|
|
#include "device_config.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_event.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "nvs_flash.h"
|
|
#include "freertos/event_groups.h"
|
|
|
|
static const char *TAG = "wifi_manager";
|
|
|
|
#define WIFI_CONNECTED_BIT BIT0
|
|
|
|
#define WIFI_RECONNECT_BASE_DELAY_MS 1000
|
|
#define WIFI_RECONNECT_MAX_DELAY_MS 30000
|
|
#define WIFI_RECONNECT_BACKOFF_CAP_SHIFT 5 // 1000ms << 5 = 32000ms, then clamped to MAX
|
|
|
|
static EventGroupHandle_t s_wifi_event_group = NULL;
|
|
static esp_timer_handle_t s_reconnect_timer = NULL;
|
|
static uint32_t s_retry_count = 0;
|
|
|
|
static void reconnect_timer_cb(void *arg) {
|
|
ESP_LOGI(TAG, "attempting Wi-Fi reconnect");
|
|
esp_err_t err = esp_wifi_connect();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(TAG, "esp_wifi_connect() failed: %s", esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
static void schedule_reconnect(void) {
|
|
uint32_t shift = s_retry_count < WIFI_RECONNECT_BACKOFF_CAP_SHIFT ? s_retry_count : WIFI_RECONNECT_BACKOFF_CAP_SHIFT;
|
|
uint32_t delay_ms = WIFI_RECONNECT_BASE_DELAY_MS << shift;
|
|
if (delay_ms > WIFI_RECONNECT_MAX_DELAY_MS) {
|
|
delay_ms = WIFI_RECONNECT_MAX_DELAY_MS;
|
|
}
|
|
s_retry_count++;
|
|
ESP_LOGW(TAG, "reconnecting in %" PRIu32 " ms (attempt %" PRIu32 ")", delay_ms, s_retry_count);
|
|
esp_timer_stop(s_reconnect_timer); // no-op if not running; keeps this idempotent
|
|
esp_timer_start_once(s_reconnect_timer, (uint64_t)delay_ms * 1000ULL);
|
|
}
|
|
|
|
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
|
int32_t event_id, void *event_data) {
|
|
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
|
esp_wifi_connect();
|
|
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
|
wifi_event_sta_disconnected_t *disc = (wifi_event_sta_disconnected_t *)event_data;
|
|
ESP_LOGW(TAG, "Wi-Fi disconnected (reason %d)", disc ? disc->reason : -1);
|
|
schedule_reconnect();
|
|
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
|
ip_event_got_ip_t *evt = (ip_event_got_ip_t *)event_data;
|
|
ESP_LOGI(TAG, "got IP: " IPSTR, IP2STR(&evt->ip_info.ip));
|
|
s_retry_count = 0;
|
|
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
|
}
|
|
}
|
|
|
|
esp_err_t wifi_manager_start(void) {
|
|
esp_err_t err = nvs_flash_init();
|
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
err = nvs_flash_init();
|
|
}
|
|
ESP_ERROR_CHECK(err);
|
|
|
|
s_wifi_event_group = xEventGroupCreate();
|
|
|
|
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, &wifi_event_handler, NULL, NULL));
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(
|
|
IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
|
|
|
|
const esp_timer_create_args_t timer_args = {
|
|
.callback = &reconnect_timer_cb,
|
|
.name = "wifi_reconnect",
|
|
};
|
|
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &s_reconnect_timer));
|
|
|
|
wifi_config_t wifi_config = {
|
|
.sta = {
|
|
.ssid = DEVICE_WIFI_SSID,
|
|
.password = DEVICE_WIFI_PASSWORD,
|
|
.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_config));
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|
|
|
ESP_LOGI(TAG, "Wi-Fi station starting, SSID \"%s\"", DEVICE_WIFI_SSID);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t wifi_manager_wait_connected(TickType_t timeout_ticks) {
|
|
if (s_wifi_event_group == NULL) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
EventBits_t bits = xEventGroupWaitBits(
|
|
s_wifi_event_group, WIFI_CONNECTED_BIT,
|
|
pdFALSE /* don't clear on exit */, pdFALSE /* any bit */, timeout_ticks);
|
|
return (bits & WIFI_CONNECTED_BIT) ? ESP_OK : ESP_ERR_TIMEOUT;
|
|
}
|
|
|
|
bool wifi_manager_is_connected(void) {
|
|
if (s_wifi_event_group == NULL) {
|
|
return false;
|
|
}
|
|
return (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) != 0;
|
|
}
|