#include #include #include #include #include #include #include #include #include #include #include #include #include #include "esp_http_server.h" #include #include "freertos/projdefs.h" #include "freertos/task.h" #include "esp_log.h" #include #include "esp_adc/adc_oneshot.h" #include "esp_adc/adc_cali.h" #include "esp_adc/adc_cali_scheme.h" #include "frontend.h" #include #include "sdkconfig.h" // ==================== НАСТРОЙКИ ТОЧКИ ДОСТУПА ==================== #define AP_MAX_CONN 4 // Максимум клиентов #define AP_CHANNEL 6 // Wi-Fi канал #define ADC_CHAN0 ADC_CHANNEL_4 #define ADC_CHAN1 ADC_CHANNEL_5 #define ADC_ATTEN_DB ADC_ATTEN_DB_12 #define SENSOR_ADC_CHAN 0 static adc_oneshot_unit_handle_t adc_handle; static adc_cali_handle_t cali_handle; static bool is_calibrated = false; static atomic_int g_threshold_low = 0; static atomic_int g_threshold_up = 0; static atomic_int g_current_pressure = 0; static const char *TAG = "ESP32_AP_SERVER"; esp_err_t adc_init(void) { // Инициализация ADC adc_oneshot_unit_init_cfg_t init_config = { .unit_id = ADC_UNIT_1, }; ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); // Конфигурация каналов adc_oneshot_chan_cfg_t config = { .atten = ADC_ATTEN_DB, .bitwidth = ADC_BITWIDTH_DEFAULT, }; ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHAN0, &config)); ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHAN1, &config)); // Калибровка для ESP32 (Line Fitting) adc_cali_line_fitting_config_t cali_config = { .unit_id = ADC_UNIT_1, .atten = ADC_ATTEN_DB, .bitwidth = ADC_BITWIDTH_DEFAULT, }; esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_config, &cali_handle); if (ret == ESP_OK) { is_calibrated = true; ESP_LOGI(TAG, "ADC калибровка успешна"); } else if (ret == ESP_ERR_NOT_SUPPORTED) { ESP_LOGW(TAG, "Калибровка не доступна (eFuse не записан)"); } else { ESP_LOGE(TAG, "Ошибка калибровки"); } return ESP_OK; } static void pump_init(void) { gpio_config_t io_conf = { .pin_bit_mask = (1ULL << CONFIG_PUMP_PIN), .mode = GPIO_MODE_OUTPUT, .intr_type = GPIO_INTR_DISABLE, .pull_down_en = 1, .pull_up_en = 0, }; gpio_config(&io_conf); } int adc_read_raw(uint8_t channel) { int raw_value = 0; adc_channel_t adc_channel; if (channel == 0) { adc_channel = ADC_CHAN0; } else if (channel == 1) { adc_channel = ADC_CHAN1; } else { ESP_LOGE(TAG, "Неверный канал: %d", channel); return -1; } esp_err_t ret = adc_oneshot_read(adc_handle, adc_channel, &raw_value); if (ret != ESP_OK) { ESP_LOGE(TAG, "Ошибка чтения ADC"); return -1; } return raw_value; } int adc_read_voltage(uint8_t channel) { int raw_value = adc_read_raw(channel); if (raw_value < 0) return -1; if (is_calibrated) { int voltage_mv = 0; esp_err_t ret = adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage_mv); if (ret != ESP_OK) { ESP_LOGE(TAG, "Ошибка конвертации в напряжение"); return -1; } return voltage_mv; } else { // Приблизительный расчет без калибровки (12-bit ADC: 0-4095 -> 0-3300mV) return (raw_value * 3300) / 4095; } } static esp_err_t root_get_handler(httpd_req_t *req) { const char* response = "" "" "" " ESP32 Access Point Server" " " " " " " "" "" "
" "

🎯 ESP32 Точка Доступа

" "
" "

📡 Информация о сервере

" "
✅ HTTP сервер работает
" "
🌐 IP адрес: 192.168.4.1
" "
💾 Свободно памяти: 0 байт
" "
🕐 Время работы: 0 сек
" "
" "
" "

💡 Управление GPIO2 (встроенный LED)

" " " " " " " "
" "
" "

📊 Получить данные с датчика

" " " "
Данные не загружены
" "
" "
" " " "" ""; httpd_resp_set_type(req, "text/html; charset=utf-8"); httpd_resp_send(req, (const char*)assets_index_html, HTTPD_RESP_USE_STRLEN); return ESP_OK; } static esp_err_t current_pressure_handler(httpd_req_t *req) { int sensor_value = atomic_load(&g_current_pressure); char response[100]; snprintf(response, sizeof(response), "{\"value\":%d}", sensor_value); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, response, strlen(response)); return ESP_OK; } static esp_err_t save_thresholds_handler(httpd_req_t *req) { char response[100]; char *content = NULL; size_t content_len = req->content_len; // Проверка длины содержимого if (content_len > 512) { // Ограничение размера для безопасности httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too large"); return ESP_FAIL; } // Выделение памяти под содержимое запроса content = malloc(content_len + 1); if (!content) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed"); return ESP_FAIL; } // Чтение тела запроса int ret = httpd_req_recv(req, content, content_len); if (ret <= 0) { free(content); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to receive data"); return ESP_FAIL; } content[content_len] = '\0'; // Null-terminator // Парсинг JSON cJSON *json = cJSON_Parse(content); free(content); if (!json) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); return ESP_FAIL; } // Извлечение параметров low и up cJSON *low_item = cJSON_GetObjectItem(json, "low"); cJSON *up_item = cJSON_GetObjectItem(json, "up"); int low_value = 0; int up_value = 0; bool valid = true; if (cJSON_IsNumber(low_item)) { low_value = low_item->valueint; } else { valid = false; } if (cJSON_IsNumber(up_item)) { up_value = up_item->valueint; } else { valid = false; } cJSON_Delete(json); if (!valid) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing or invalid 'low' or 'up' parameters"); return ESP_FAIL; } // Проверка значений (опционально) if (low_value >= up_value) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Low value must be less than up value"); return ESP_FAIL; } nvs_handle_t my_handle; esp_err_t err; // 1. Открываем раздел "nvs" и пространство имен "storage" на запись/чтение err = nvs_open("storage", NVS_READWRITE, &my_handle); if (err != ESP_OK) { ESP_LOGE("TAG", "Error opening NVS"); return 1; } err = nvs_set_i32(my_handle, "threshold_low", low_value); ESP_ERROR_CHECK(err); err = nvs_set_i32(my_handle, "threshold_up", up_value); ESP_ERROR_CHECK(err); // 4. Сохраняем изменения во flash err = nvs_commit(my_handle); ESP_ERROR_CHECK(err); // 5. Закрываем хендл nvs_close(my_handle); // Формирование успешного ответа snprintf(response, sizeof(response), "{\"success\":true,\"low\":%d,\"up\":%d}", low_value, up_value); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, response, strlen(response)); return ESP_OK; } static esp_err_t set_thresholds_handler(httpd_req_t *req) { char response[100]; char *content = NULL; size_t content_len = req->content_len; // Проверка длины содержимого if (content_len > 512) { // Ограничение размера для безопасности httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too large"); return ESP_FAIL; } // Выделение памяти под содержимое запроса content = malloc(content_len + 1); if (!content) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed"); return ESP_FAIL; } // Чтение тела запроса int ret = httpd_req_recv(req, content, content_len); if (ret <= 0) { free(content); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Failed to receive data"); return ESP_FAIL; } content[content_len] = '\0'; // Null-terminator // Парсинг JSON cJSON *json = cJSON_Parse(content); free(content); if (!json) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); return ESP_FAIL; } // Извлечение параметров low и up cJSON *low_item = cJSON_GetObjectItem(json, "low"); cJSON *up_item = cJSON_GetObjectItem(json, "up"); int low_value = 0; int up_value = 0; bool valid = true; if (cJSON_IsNumber(low_item)) { low_value = low_item->valueint; } else { valid = false; } if (cJSON_IsNumber(up_item)) { up_value = up_item->valueint; } else { valid = false; } cJSON_Delete(json); if (!valid) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing or invalid 'low' or 'up' parameters"); return ESP_FAIL; } // Проверка значений (опционально) if (low_value >= up_value) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Low value must be less than up value"); return ESP_FAIL; } atomic_store(&g_threshold_low, low_value); atomic_store(&g_threshold_up, up_value); // Формирование успешного ответа snprintf(response, sizeof(response), "{\"success\":true,\"low\":%d,\"up\":%d}", low_value, up_value); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, response, strlen(response)); return ESP_OK; } void wifi_init_softap(void) { // Инициализация сетевого интерфейса ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap(); // Инициализация Wi-Fi wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // Настройка точки доступа wifi_config_t wifi_config = { .ap = { .ssid = CONFIG_AP_WIFI_SSID, .ssid_len = strlen(CONFIG_AP_WIFI_SSID), .password = CONFIG_AP_WIFI_PASS, .max_connection = AP_MAX_CONN, .authmode = WIFI_AUTH_WPA_WPA2_PSK, .channel = AP_CHANNEL, }, }; if (strlen(CONFIG_AP_WIFI_PASS) == 0) { wifi_config.ap.authmode = WIFI_AUTH_OPEN; } ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); esp_netif_ip_info_t ip_info; ip_info.ip.addr = ipaddr_addr(CONFIG_AP_IP); ip_info.gw.addr = ipaddr_addr(CONFIG_AP_GATEWAY); ip_info.netmask.addr = ipaddr_addr(CONFIG_AP_NETMASK); // Останавливаем DHCP сервер, который автоматически запустился ESP_ERROR_CHECK(esp_netif_dhcps_stop(ap_netif)); // Устанавливаем наши настройки IP ESP_ERROR_CHECK(esp_netif_set_ip_info(ap_netif, &ip_info)); ESP_ERROR_CHECK(esp_netif_dhcps_start(ap_netif)); ESP_LOGI(TAG, "========================================="); ESP_LOGI(TAG, "📶 Wifi name (SSID): %s", CONFIG_AP_WIFI_SSID); ESP_LOGI(TAG, "🔑 Password: %s", strlen(CONFIG_AP_WIFI_PASS) ? CONFIG_AP_WIFI_PASS : "Open network"); ESP_LOGI(TAG, "🌐 Webinterface IP address: %s:%s", CONFIG_AP_IP, CONFIG_WEBINTERFACE_PORT); ESP_LOGI(TAG, "========================================="); } static void disablePump(void) { gpio_set_level(CONFIG_PUMP_PIN, false); } static void enablePump(void) { gpio_set_level(CONFIG_PUMP_PIN, true); } static void vPumpControllTask(void *pvParameters) { while (1) { int current_pressure = atomic_load(&g_current_pressure); int low_treshhold = atomic_load(&g_threshold_low); int up_treshhold = atomic_load(&g_threshold_up); if (current_pressure < low_treshhold) { enablePump(); } else if (current_pressure >= up_treshhold) { disablePump(); } vTaskDelay(pdMS_TO_TICKS(1000)); } } static void vReadSensorTask(void *pvParameters) { while (1) { int pressure = adc_read_voltage(SENSOR_ADC_CHAN); atomic_store(&g_current_pressure, pressure); vTaskDelay(pdMS_TO_TICKS(1000)); } } static void vHttpServerTask(void *pvParameters) { httpd_handle_t server = NULL; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = CONFIG_WEBINTERFACE_PORT; config.max_uri_handlers = 10; config.stack_size = 8192; // Запуск HTTP сервера if (httpd_start(&server, &config) == ESP_OK) { ESP_LOGI(TAG, "🚀 HTTP сервер запущен на порту %d", config.server_port); // Регистрация URI обработчиков httpd_uri_t root = { .uri = "/", .method = HTTP_GET, .handler = root_get_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &root); httpd_uri_t pressure = { .uri = "/pressure", .method = HTTP_GET, .handler = current_pressure_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &pressure); httpd_uri_t set_thresholds = { .uri = "/thresholds", .method = HTTP_POST, .handler = set_thresholds_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &set_thresholds); httpd_uri_t save_thresholds = { .uri = "/persist_thresholds", .method = HTTP_POST, .handler = save_thresholds_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &save_thresholds); } else { ESP_LOGE(TAG, "❌ Ошибка запуска HTTP сервера"); } while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } } void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); nvs_handle_t my_handle; esp_err_t err; // 1. Открываем раздел "nvs" и пространство имен "storage" на запись/чтение err = nvs_open("storage", NVS_READWRITE, &my_handle); if (err != ESP_OK) { ESP_LOGE("TAG", "Error opening NVS"); } err = nvs_get_i32(my_handle, "treshhold_low", atomic_load(&g_threshold_low)); if (err == ESP_ERR_NVS_NOT_FOUND) { atomic_store(&g_threshold_low, 100); } else { ESP_ERROR_CHECK(err); } err = nvs_get_i32(my_handle, "treshhold_low", atomic_load(&g_threshold_up)); if (err == ESP_ERR_NVS_NOT_FOUND) { atomic_store(&g_threshold_up, 300); } else { ESP_ERROR_CHECK(err); } adc_init(); pump_init(); ESP_LOGI(TAG, "========================================="); ESP_LOGI(TAG, "ESP32 Точка Доступа + HTTP Сервер"); ESP_LOGI(TAG, "========================================="); wifi_init_softap(); vTaskDelay(pdMS_TO_TICKS(1000)); xTaskCreate(vHttpServerTask, "http_server", 8192, NULL, 5, NULL); xTaskCreate(vPumpControllTask, "pump_controll", 8192, NULL, 5, NULL); xTaskCreate(vReadSensorTask, "read_sensor", 8192, NULL, 5, NULL); ESP_LOGI(TAG, "✅ Система готова к работе"); ESP_LOGI(TAG, "📱 Подключитесь к Wi-Fi: %s", CONFIG_AP_WIFI_SSID); ESP_LOGI(TAG, "🌐 Откройте браузер: http://192.168.4.1"); while (1) { vTaskDelay(pdMS_TO_TICKS(10000)); wifi_sta_list_t sta_list; memset(&sta_list, 0, sizeof(sta_list)); esp_wifi_ap_get_sta_list(&sta_list); ESP_LOGI(TAG, "📊 Подключено клиентов: %d", sta_list.num); } }