#include "WiFiManager.h" #include "secrets.h" #include #include #include #include #include #include #include #include "esp_sntp.h" #include "esp_log.h" #include "esp_wifi.h" #include "esp_http_client.h" #include "freertos/event_groups.h" #include #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(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; }