Initial commit

This commit is contained in:
thek4n 2026-06-05 00:38:34 +03:00
commit 8a84804509
5 changed files with 554 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build/
sdkconfig

6
CMakeLists.txt Normal file
View File

@ -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)

27
justfile Executable file
View File

@ -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

3
main/CMakeLists.txt Normal file
View File

@ -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 ".")

516
main/main.c Normal file
View File

@ -0,0 +1,516 @@
#include <stdint.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <driver/gpio.h>
#include <esp_event.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <lwip/err.h>
#include <lwip/sys.h>
#include "esp_http_server.h"
#include <stdio.h>
#include "freertos/projdefs.h"
#include "freertos/task.h"
#include "esp_log.h"
#include <stdatomic.h>
#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 =
"<!DOCTYPE html>"
"<html>"
"<head>"
" <title>ESP32 Access Point Server</title>"
" <meta charset='utf-8'>"
" <meta name='viewport' content='width=device-width, initial-scale=1'>"
" <style>"
" * { margin: 0; padding: 0; box-sizing: border-box; }"
" body {"
" font-family: 'Segoe UI', Arial, sans-serif;"
" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);"
" min-height: 100vh;"
" padding: 20px;"
" }"
" .container {"
" max-width: 800px;"
" margin: 0 auto;"
" background: white;"
" border-radius: 20px;"
" padding: 30px;"
" box-shadow: 0 20px 60px rgba(0,0,0,0.3);"
" }"
" h1 {"
" color: #667eea;"
" margin-bottom: 20px;"
" text-align: center;"
" }"
" .status-card {"
" background: #f0f0f0;"
" border-radius: 10px;"
" padding: 20px;"
" margin: 20px 0;"
" }"
" .info {"
" margin: 10px 0;"
" padding: 10px;"
" background: #e8f5e9;"
" border-left: 4px solid #4CAF50;"
" }"
" .button {"
" background: #667eea;"
" color: white;"
" border: none;"
" padding: 12px 24px;"
" border-radius: 8px;"
" cursor: pointer;"
" font-size: 16px;"
" margin: 5px;"
" transition: transform 0.2s;"
" }"
" .button:hover {"
" transform: scale(1.05);"
" }"
" .gpio-control {"
" margin: 20px 0;"
" padding: 15px;"
" background: #fff3e0;"
" border-radius: 10px;"
" }"
" .led-status {"
" display: inline-block;"
" width: 20px;"
" height: 20px;"
" border-radius: 50%;"
" margin-left: 10px;"
" }"
" .led-on { background: #4CAF50; box-shadow: 0 0 10px #4CAF50; }"
" .led-off { background: #f44336; }"
" </style>"
"</head>"
"<body>"
" <div class='container'>"
" <h1>🎯 ESP32 Точка Доступа</h1>"
" <div class='status-card'>"
" <h3>📡 Информация о сервере</h3>"
" <div class='info'>✅ HTTP сервер работает</div>"
" <div class='info'>🌐 IP адрес: 192.168.4.1</div>"
" <div class='info'>💾 Свободно памяти: <span id='heap'>0</span> байт</div>"
" <div class='info'>🕐 Время работы: <span id='uptime'>0</span> сек</div>"
" </div>"
" <div class='gpio-control'>"
" <h3>💡 Управление GPIO2 (встроенный LED)</h3>"
" <button class='button' onclick='controlGPIO(1)'>🔆 ВКЛ</button>"
" <button class='button' onclick='controlGPIO(0)'>💡 ВЫКЛ</button>"
" <span id='ledIndicator' class='led-status led-off'></span>"
" </div>"
" <div class='gpio-control'>"
" <h3>📊 Получить данные с датчика</h3>"
" <button class='button' onclick='getSensorData()'>📈 Обновить</button>"
" <div id='sensorData' class='info' style='margin-top:10px;'>Данные не загружены</div>"
" </div>"
" </div>"
" <script>"
" function controlGPIO(state) {"
" fetch('/gpio?state=' + state)"
" .then(response => response.json())"
" .then(data => {"
" const indicator = document.getElementById('ledIndicator');"
" if(data.status === 'on') {"
" indicator.className = 'led-status led-on';"
" } else {"
" indicator.className = 'led-status led-off';"
" }"
" });"
" }"
" function getSensorData() {"
" fetch('/sensor')"
" .then(response => response.json())"
" .then(data => {"
" document.getElementById('sensorData').innerHTML = "
" '📊 Значение: ' + data.value + '<br>' +"
" '📝 Сообщение: ' + data.message;"
" });"
" }"
" function updateStats() {"
" fetch('/stats')"
" .then(response => response.json())"
" .then(data => {"
" document.getElementById('heap').innerText = data.free_heap;"
" document.getElementById('uptime').innerText = data.uptime;"
" });"
" }"
" setInterval(updateStats, 2000);"
" getSensorData();"
" </script>"
"</body>"
"</html>";
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());
}
}