Nimbin[12]?Embedded / esp_garden / server_hsnr/main/WiFiManager.cpp

esp_garden git · master

Distributed ESP sensor and actuator system (garden)

esp32 esp-idf iot sensors c++ sql · first commit 2025-10-15 · last commit 2026-01-20 (8 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/mic/esp_garden

C++ 70.7% Markdown 18% C 10.7%
git clone https://git.christianimmanuel.de/embedded/esp_garden.gitwget https://git.christianimmanuel.de/embedded/esp_garden/archive/esp_garden.tar.gz
server_hsnr/main/WiFiManager.cpp 17.1 KB · 513 lines raw
#include "WiFiManager.h"
#include "secrets.h"

#include <inttypes.h>
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cinttypes>
#include <ctime>

#include "esp_sntp.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_http_client.h"
#include "freertos/event_groups.h"

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_eap_client.h"
#include "nvs_flash.h"

#define WIFI_CONNECTED_BIT BIT0

static EventGroupHandle_t
wifi_event_group;

static
esp_netif_t *sta_netif;

static const char*
TAG = "wifi_peap";

extern const uint8_t
ca_pem_start[] asm("_binary_ca_pem_start");

extern const uint8_t
ca_pem_end[]   asm("_binary_ca_pem_end");






static void
wifi_EventHandler(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) {
        ESP_LOGW(TAG, "Disconnected, retrying...");
        esp_wifi_connect();
        xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT);
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

esp_err_t WiFi_connect() {
    static const char *TAG = "WiFi_connect";

    ESP_ERROR_CHECK(nvs_flash_init());

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    sta_netif = esp_netif_create_default_wifi_sta();
    wifi_event_group = xEventGroupCreate();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_EventHandler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_EventHandler, NULL));

    wifi_config_t wifi_config = { 0 };
    strncpy((char *)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid));

    ESP_LOGI(TAG, "Connecting to SSID: %s", wifi_config.sta.ssid);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));

    // Configure EAP client
    ESP_ERROR_CHECK(esp_eap_client_set_ca_cert(ca_pem_start,
                                               ca_pem_end - ca_pem_start));
    ESP_ERROR_CHECK(esp_eap_client_set_identity((uint8_t *)EAP_IDENTITY,
                                                strlen(EAP_IDENTITY)));
    ESP_ERROR_CHECK(esp_eap_client_set_username((uint8_t *)EAP_USERNAME,
                                                strlen(EAP_USERNAME)));
    ESP_ERROR_CHECK(esp_eap_client_set_password((uint8_t *)EAP_PASSWORD,
                                                strlen(EAP_PASSWORD)));
    ESP_ERROR_CHECK(esp_eap_client_set_eap_methods(ESP_EAP_TYPE_PEAP));
    ESP_ERROR_CHECK(esp_wifi_sta_enterprise_enable());


    ESP_ERROR_CHECK(esp_wifi_start());

    return ESP_OK;
}

esp_err_t WiFi_disconnect() {
    esp_err_t err = esp_wifi_disconnect();
    ESP_ERROR_CHECK(esp_wifi_stop());
    ESP_ERROR_CHECK(esp_wifi_deinit());
    return err;
}

void WIFI_SyncTime() {
    static const char *TAG = "WIFI_SyncTime";
    ESP_LOGI(TAG, "Syncing time...");

    // Configure SNTP
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, NTP_SERVER);
    sntp_init();

    // Wait until time is set (or timeout)
    const int64_t min_valid_time = 1600000000;
    const int max_wait_ms = 10000;
    const int interval_ms = 500;
    int waited_ms = 0;
    time_t now = 0;

    while (waited_ms < max_wait_ms) {
        time(&now);
        if (now > min_valid_time) break;
        vTaskDelay(pdMS_TO_TICKS(interval_ms));
        waited_ms += interval_ms;
    }

    if (now < min_valid_time) {
        ESP_LOGW(TAG, "NTP sync failed (no valid time within %d ms)", max_wait_ms);
    } else {
        // Set timezone
        setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
        tzset();
        char buf[64];
        struct tm timeinfo;
        localtime_r(&now, &timeinfo);
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
        ESP_LOGI(TAG, "Time synced: %s", buf);
    }

    // Should i stop it?
}



/////////////////////////////////////////////////
/////////////////////////////////////////////////
/// Post data to server

static std::string http_response;

esp_err_t http_event_handler(esp_http_client_event_t *evt) {
    static const char *TAG = "http_event_handler";
    switch (evt->event_id) {
        case HTTP_EVENT_ERROR:
            ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
            http_response.clear();
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key ?: "", evt->header_value ?: "");
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            if (evt->data && evt->data_len > 0) {
                // append chunk to accumulating std::string
                http_response.append(reinterpret_cast<const char*>(evt->data), evt->data_len);
            }
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
            // leave http_response for caller to inspect
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
            // clear response if disconnected prematurely
            // (caller will check content-length / status)
            break;
        case HTTP_EVENT_REDIRECT:
            ESP_LOGI(TAG, "HTTP_EVENT_REDIRECT");
            break;
    }
    return ESP_OK;
}


void post_ToServer( const char* path, char* json, size_t len) {
    static const char *TAG = "post_ToServer";

    ESP_LOGI(TAG, "Posting json:");
    ESP_LOGI(TAG, "%s", json);

    // prepare HTTP client
    char url[256];
    snprintf(url, sizeof(url), "https://%s%s", server_host, path);

    // clear previous response and set up config
    http_response.clear();
    esp_http_client_config_t config = {
        .url = url,
        .event_handler = http_event_handler,
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    if (!client) {
        ESP_LOGE(TAG, "esp_http_client_init failed");
        free(json);
        g_received_ok_from_server = true;
        return;
    }

    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_header(client, "X-API-Token", api_token);
    esp_http_client_set_post_field(client, json, (int)len);

    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        int status = esp_http_client_get_status_code(client);
        int content_length = esp_http_client_get_content_length(client);
        ESP_LOGI(TAG, "HTTP Status = %d, content_length = %d", status, content_length);
    } else {
        ESP_LOGE(TAG, "esp_http_client_perform failed: %d", err);
    }

    // cleanup client
    esp_err_t cleanup_err = esp_http_client_cleanup(client);
    if (cleanup_err != ESP_OK) {
        ESP_LOGW(TAG, "esp_http_client_cleanup returned %d", cleanup_err);
    }

}

void WiFi_PostDataToServer() {
    static const char *TAG = "WiFi_PostDataToServer";

    ESP_LOGI(TAG, "Buffer len: %zu", g_buffer_count);

    // allocate JSON buffer
    const size_t json_cap = JSON_BUFFER_SIZE;
    char *json = (char*)malloc(json_cap);
    if (!json) {
        ESP_LOGE(TAG, "malloc(json) failed");
        g_received_ok_from_server = true;
        return;
    }

    // build JSON array
    size_t len = 0;
    int ret = snprintf(json + len, (len < json_cap) ? (json_cap - len) : 0, "[");
    if (ret < 0 || (size_t)ret >= (json_cap - len)) {
        free(json);
        g_received_ok_from_server = true;
        return;
    }
    len += (size_t)ret;

    for (size_t i = 0; i < g_buffer_count; ++i) {
        // separator
        if (i != 0) {
            ret = snprintf(json + len, (len < json_cap) ? (json_cap - len) : 0, ",");
            if (ret < 0 || (size_t)ret >= (json_cap - len)) {
                // truncate safely
                len = (json_cap > 0) ? (json_cap - 1) : 0;
                break;
            }
            len += (size_t)ret;
        }

        const char *table = g_buffer[i].table;
        const char *value = g_buffer[i].value;

        // format object
        ret = snprintf(json + len, (len < json_cap) ? (json_cap - len) : 0,
                "{\"table\":\"%s\",\"value\":\"%s\",\"time\":%" PRId64 ",\"pump_reached\":%d}",
                table,
                value,
                g_buffer[i].time,
                g_buffer[i].pump_reached);
        if (ret < 0) {
            free(json);
            g_received_ok_from_server = true;
            return;
        }
        if ((size_t)ret >= (json_cap - len)) {
            // truncat if not enough room for object
            len = (json_cap > 0) ? (json_cap - 1) : 0;
            break;
        }
        len += (size_t)ret;
    }

    // close JSON
    ret = snprintf(json + len, (len < json_cap) ? (json_cap - len) : 0, "]");
    if (ret < 0 || (size_t)ret >= (json_cap - len)) {
        // null terminate
        if (json_cap > 0) json[json_cap - 1] = '\0';
    } else {
        len += (size_t)ret;
    }


    post_ToServer("/insert.php", json, len);

    g_buffer_count = 0;

    // parse pending_sensors from response
    g_pending_sensor_configs_count = 0;
    if (!http_response.empty()) {
        const std::string needle = "\"pending_sensors\":[";
        size_t start = http_response.find(needle);
        if (start != std::string::npos) {
            start += needle.size();
            size_t end = http_response.find(']', start);
            if (end != std::string::npos && end > start) {
                std::string arr = http_response.substr(start, end - start);

                // parse tokens
                size_t pos = 0;
                while (g_pending_sensor_configs_count < MAX_PENDING_SENSOR_CONFIGS && pos < arr.size()) {
                    // skip whitespace
                    while (pos < arr.size() && isspace((unsigned char)arr[pos])) ++pos;
                    if (pos >= arr.size()) break;

                    // find next comma
                    bool in_quote = false;
                    size_t token_start = pos;
                    size_t token_end = pos;
                    for (; token_end < arr.size(); ++token_end) {
                        char c = arr[token_end];
                        if (c == '"' ) in_quote = !in_quote;
                        else if (c == ',' && !in_quote) break;
                    }

                    std::string token = arr.substr(token_start, token_end - token_start);
                    pos = (token_end < arr.size()) ? token_end + 1 : token_end;

                    // trim whitespace
                    size_t a = 0;
                    while (a < token.size() && isspace((unsigned char)token[a])) ++a;
                    size_t b = token.size();
                    while (b > a && isspace((unsigned char)token[b - 1])) --b;
                    if (a >= b) continue;
                    token = token.substr(a, b - a);

                    // remove surrounding quotes
                    if (token.size() >= 2 && token.front() == '"' && token.back() == '"') {
                        token = token.substr(1, token.size() - 2);
                    }

                    if (!token.empty()) {
                        // copy into your existing slot
                        size_t maxlen = sizeof(g_pending_sensor_configs[0]);
                        strncpy(g_pending_sensor_configs[g_pending_sensor_configs_count], token.c_str(), maxlen);
                        // ensure null termination
                        g_pending_sensor_configs[g_pending_sensor_configs_count][maxlen - 1] = '\0';
                        g_pending_sensor_configs_count++;
                    }
                }
                ESP_LOGI(TAG, "COUNTED: %d", g_pending_sensor_configs_count);
            }
        }
    }

    if (g_pending_sensor_configs_count > 0) {
        ESP_LOGI(TAG, "NEW CONFIGS RECEIVED AT WIFIManager");
        for (size_t i = 0; i < g_pending_sensor_configs_count; i++) {
            ESP_LOGI(TAG, "  %s", g_pending_sensor_configs[i]);
        }
    }

    g_received_ok_from_server = true;
    free(json);
}






//////////////////////////////////////
/// Config stuff

static int findKey(const std::string &src, const char *key) {
    std::string pattern = std::string("\"") + key + "\":";
    auto pos = src.find(pattern);
    return (pos == std::string::npos) ? -1 : (int)pos + pattern.size();
}


bool WIFI_GetSensorConfigServer(const char* sensor_name, SensorConfigServer_s &cfg) {
    static const char *TAG = "WIFI_GetSensorConfigServer";

    // construct URL
    char name_enc[64];
    snprintf(name_enc, sizeof(name_enc), "%s", sensor_name);
    char url[256];
    snprintf(url, sizeof(url), "https://%s:%d/get_sensor_config.php?name=%s", server_host, server_port, name_enc);

    ESP_LOGI(TAG, "Requesting config for sensor: %s", sensor_name);
    http_response.clear();

    // configure HTTP client
    esp_http_client_config_t config = {
        .url = url,
        .event_handler = http_event_handler,
    };

    esp_http_client_handle_t client = esp_http_client_init(&config);
    if (!client) {
        ESP_LOGE(TAG, "esp_http_client_init failed");
        return false;
    }

    esp_http_client_set_method(client, HTTP_METHOD_GET);
    esp_http_client_set_header(client, "X-API-Token", api_token);

    esp_err_t err = esp_http_client_perform(client);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "HTTP GET failed: %s", esp_err_to_name(err));
        esp_http_client_cleanup(client);
        return false;
    }

    int status = esp_http_client_get_status_code(client);
    esp_http_client_cleanup(client);

    if (status != 200) {
        ESP_LOGW(TAG, "Server returned HTTP %d", status);
        return false;
    }

    ESP_LOGI(TAG, "Received %zu bytes", http_response.size());
    ESP_LOGD(TAG, "Body: %s", http_response.c_str());

    // Parse json response
    const std::string &body = http_response;
    memset(&cfg, 0, sizeof(cfg));

    auto extractInt = [&](const char *key) -> int {
        int pos = findKey(body, key);
        if (pos < 0) return 0;
        return std::atoi(body.c_str() + pos);
    };

    auto extractBool = [&](const char *key) -> bool {
        int pos = findKey(body, key);
        if (pos < 0) return false;
        const char *p = body.c_str() + pos;
        return (strncmp(p, "true", 4) == 0 || *p == '1');
    };

    auto extractString = [&](const char *key, char *out, size_t out_size) {
        std::string pattern = std::string("\"") + key + "\":\"";
        auto start = body.find(pattern);
        if (start == std::string::npos) { out[0] = '\0'; return; }
        start += pattern.size();
        auto end = body.find('"', start);
        if (end == std::string::npos) { out[0] = '\0'; return; }
        size_t len = end - start;
        if (len >= out_size) len = out_size - 1;
        memcpy(out, body.c_str() + start, len);
        out[len] = '\0';
    };

    cfg.success  = extractBool("success");
    extractString("sensor_name", cfg.sensor_name, sizeof(cfg.sensor_name));
    cfg.pin      = extractInt("pin");
    cfg.use_time = extractBool("use_time");
    cfg.delay_ms = extractInt("delay_ms");
    cfg.min_moisture = extractInt("min_moisture");
    cfg.is_active    = extractBool("is_active");
    extractString("notification_name", cfg.notification_name, sizeof(cfg.notification_name));
    cfg.pumping_delay_sec = extractInt("pumping_delay_sec");
    cfg.pumping_duration  = extractInt("pumping_duration");

    // Parse times array
    cfg.time_count = 0;
    size_t arr_start = body.find("\"times\":[");
    if (arr_start != std::string::npos) {
        arr_start += 9;
        size_t arr_end = body.find(']', arr_start);
        if (arr_end != std::string::npos) {
            std::string arr = body.substr(arr_start, arr_end - arr_start);
            size_t pos = 0;
            while (cfg.time_count < 8 && pos < arr.size()) {
                size_t comma = arr.find(',', pos);
                std::string token = arr.substr(pos, (comma == std::string::npos ? arr.size() : comma) - pos);
                pos = (comma == std::string::npos) ? arr.size() : comma + 1;
                int val = std::atoi(token.c_str());
                if (val > 0) cfg.times[cfg.time_count++] = (uint16_t)val;
            }
        }
    }

    ESP_LOGI(TAG, "Parsed config: success=%d pin=%d min=%d delay=%d use_time=%d time_count=%u",
            cfg.success, cfg.pin, cfg.min_moisture, cfg.delay_ms, cfg.use_time, cfg.time_count);
    ESP_LOGI(TAG, "sensor_name='%s' notification='%s' pumping_delay_sec=%d pumping_duration=%d",
            cfg.sensor_name, cfg.notification_name, cfg.pumping_delay_sec, cfg.pumping_duration);

    return cfg.success;
}