From 8a84804509eb6750061884e560eab556893fc6d8 Mon Sep 17 00:00:00 2001 From: thek4n Date: Fri, 5 Jun 2026 00:38:34 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + CMakeLists.txt | 6 + justfile | 27 +++ main/CMakeLists.txt | 3 + main/main.c | 516 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 554 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100755 justfile create mode 100644 main/CMakeLists.txt create mode 100644 main/main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b744e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +sdkconfig diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..aa0ac96 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pump_controller_web) diff --git a/justfile b/justfile new file mode 100755 index 0000000..d95d96c --- /dev/null +++ b/justfile @@ -0,0 +1,27 @@ +#!/usr/bin/env -S just --justfile + +PORT := env("PORT", "/dev/ttyUSB0") +ESPRESSIF := env("ESPRESSIF", "${HOME}/playground/espressif") + + +init: + #!/bin/sh + . "{{ESPRESSIF}}/export.sh" + idf.py set-target esp32 + +build: + #!/bin/sh + . "{{ESPRESSIF}}/export.sh" + idf.py + + +upload: + #!/bin/sh + . "{{ESPRESSIF}}/export.sh" + idf.py -p "{{PORT}}" flash + + +term: + #!/bin/sh + . "{{ESPRESSIF}}/export.sh" + idf.py -p "{{PORT}}" monitor diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..6353ce1 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES esp_wifi esp_http_server nvs_flash esp_driver_gpio esp_adc + INCLUDE_DIRS ".") diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..b7a5c5a --- /dev/null +++ b/main/main.c @@ -0,0 +1,516 @@ +#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" + +// ==================== НАСТРОЙКИ ТОЧКИ ДОСТУПА ==================== +#define AP_SSID "ESP32_Hotspot" // Имя Wi-Fi сети +#define AP_PASS "12345678" // Пароль (минимум 8 символов) +#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 + +static adc_oneshot_unit_handle_t adc_handle; +static adc_cali_handle_t cali_handle; +static bool is_calibrated = false; + + +// ==================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ==================== +static const char *TAG = "ESP32_AP_SERVER"; + +atomic_int low_treshhold = 0; +atomic_int up_treshhold = 0; + +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; +} + +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; + } +} + + +// ==================== ОБРАБОТЧИКИ HTTP ЗАПРОСОВ ==================== + +// Обработчик главной страницы +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, response, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +// Управление GPIO +static esp_err_t gpio_handler(httpd_req_t *req) +{ + char param[10]; + char state_str[10]; + + // Получаем параметр state из URL + if (httpd_req_get_url_query_str(req, param, sizeof(param)) == ESP_OK) { + if (httpd_query_key_value(param, "state", state_str, sizeof(state_str)) == ESP_OK) { + int state = atoi(state_str); + + // Настройка GPIO2 (встроенный LED на многих ESP32) + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << 2), + .mode = GPIO_MODE_OUTPUT, + .intr_type = GPIO_INTR_DISABLE, + .pull_down_en = 0, + .pull_up_en = 0, + }; + gpio_config(&io_conf); + gpio_set_level(2, state); + + char response[100]; + snprintf(response, sizeof(response), "{\"status\":\"%s\"}", state ? "on" : "off"); + httpd_resp_set_type(req, "application/json"); + httpd_resp_send(req, response, strlen(response)); + return ESP_OK; + } + } + + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing state parameter"); + return ESP_FAIL; +} + +// Имитация датчика +static esp_err_t sensor_handler(httpd_req_t *req) { + int volt0 = adc_read_voltage(0); + int sensor_value = volt0; + char response[100]; + snprintf(response, sizeof(response), + "{\"value\":%d,\"message\":\"Случайное значение датчика\"}", + sensor_value); + + httpd_resp_set_type(req, "application/json"); + httpd_resp_send(req, response, strlen(response)); + return ESP_OK; +} + +// Статистика системы +static esp_err_t stats_handler(httpd_req_t *req) +{ + char response[200]; + snprintf(response, sizeof(response), + "{\"free_heap\":%lu,\"uptime\":%d}", + esp_get_free_heap_size(), + (int)(xTaskGetTickCount() * portTICK_PERIOD_MS / 1000)); + + 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_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 = AP_SSID, + .ssid_len = strlen(AP_SSID), + .password = AP_PASS, + .max_connection = AP_MAX_CONN, + .authmode = WIFI_AUTH_WPA_WPA2_PSK, + .channel = AP_CHANNEL, + }, + }; + + // Если пароль не задан - открытая сеть + if (strlen(AP_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_LOGI(TAG, "========================================="); + ESP_LOGI(TAG, "📡 Точка доступа запущена"); + ESP_LOGI(TAG, "📶 Имя сети (SSID): %s", AP_SSID); + ESP_LOGI(TAG, "🔑 Пароль: %s", strlen(AP_PASS) ? AP_PASS : "ОТКРЫТАЯ СЕТЬ"); + ESP_LOGI(TAG, "🌐 IP адрес сервера: 192.168.4.1"); + ESP_LOGI(TAG, "========================================="); +} + +// void setup_adc() { +// // Настройка разрешения (9-12 бит) +// adc1_config_width(ADC_WIDTH_BIT_12); +// +// // Настройка аттенюации (диапазон входного напряжения) +// adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); +// } +// +// int read_mid_pressure(void) { +// int N = 5; +// int pause = 20; +// +// int sum = 0; +// for (int i = 0; i < N; i++) { +// sum += adc1_get_raw(ADC1_CHANNEL_0); +// vTaskDelay(pdMS_TO_TICKS(pause)); +// } +// +// return sum /= N; +// } + +// static void pressure_control_task(void *pvParameters) { +// gpio_config_t io_conf = { +// .pin_bit_mask = (1ULL << 2), +// .mode = GPIO_MODE_OUTPUT, +// .intr_type = GPIO_INTR_DISABLE, +// .pull_down_en = 1, +// .pull_up_en = 0, +// }; +// gpio_config(&io_conf); +// +// while(1) { +// int low_treshhold_pressure = atomic_load(&low_treshhold); +// int up_treshhold_pressure = atomic_load(&up_treshhold); +// +// int pressure = read_mid_pressure(); +// +// if (pressure < low_treshhold_pressure) { +// gpio_set_level(2, 1); +// } else if (pressure >= up_treshhold_pressure) { +// gpio_set_level(2, 0); +// } +// +// vTaskDelay(pdMS_TO_TICKS(400)); +// } +// } + +// ==================== ЗАДАЧА HTTP СЕРВЕРА ==================== +static void http_server_task(void *pvParameters) +{ + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + 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 gpio = { + .uri = "/gpio", + .method = HTTP_GET, + .handler = gpio_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(server, &gpio); + + httpd_uri_t sensor = { + .uri = "/sensor", + .method = HTTP_GET, + .handler = sensor_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(server, &sensor); + + httpd_uri_t stats = { + .uri = "/stats", + .method = HTTP_GET, + .handler = stats_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(server, &stats); + + ESP_LOGI(TAG, "📝 Зарегистрированы обработчики:"); + ESP_LOGI(TAG, " - GET / (главная страница)"); + ESP_LOGI(TAG, " - GET /gpio (управление LED)"); + ESP_LOGI(TAG, " - GET /sensor (чтение датчика)"); + ESP_LOGI(TAG, " - GET /stats (статистика)"); + } else { + ESP_LOGE(TAG, "❌ Ошибка запуска HTTP сервера"); + } + + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} + +// ==================== ГЛАВНАЯ ФУНКЦИЯ ==================== +void app_main(void) +{ + // Инициализация NVS + 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); + + adc_init(); + + ESP_LOGI(TAG, "========================================="); + ESP_LOGI(TAG, "ESP32 Точка Доступа + HTTP Сервер"); + ESP_LOGI(TAG, "========================================="); + + + // Инициализация точки доступа + wifi_init_softap(); + + // Небольшая задержка для стабилизации + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Создание задачи HTTP сервера + xTaskCreate(http_server_task, "http_server", 8192, NULL, 5, NULL); + + // Демонстрационная задача для имитации датчика + ESP_LOGI(TAG, "✅ Система готова к работе"); + ESP_LOGI(TAG, "📱 Подключитесь к Wi-Fi: %s", AP_SSID); + ESP_LOGI(TAG, "🌐 Откройте браузер: http://192.168.4.1"); + + while (1) { + // Вывод информации о подключенных клиентах каждые 10 секунд + 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); + ESP_LOGI(TAG, "💾 Свободно памяти: %d байт", esp_get_free_heap_size()); + } +} +