esp_garden git · master
Distributed ESP sensor and actuator system (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.gzserver_hsnr/main/WiFiManager.cpp 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;
}