1312 lines
44 KiB
C
1312 lines
44 KiB
C
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <stdatomic.h>
|
||
#include <stdarg.h>
|
||
#include <freertos/FreeRTOS.h>
|
||
#include <freertos/task.h>
|
||
#include <freertos/event_groups.h>
|
||
#include <freertos/projdefs.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 <cJSON.h>
|
||
#include "esp_http_server.h"
|
||
#include "esp_adc/adc_oneshot.h"
|
||
#include "esp_adc/adc_cali.h"
|
||
#include "esp_adc/adc_cali_scheme.h"
|
||
#include "esp_task_wdt.h"
|
||
#include "mdns.h"
|
||
#include "esp_wifi.h"
|
||
#include "esp_event.h"
|
||
#include "esp_log.h"
|
||
#include "nvs_flash.h"
|
||
#include "freertos/event_groups.h"
|
||
#include "lwip/inet.h"
|
||
|
||
#include "sdkconfig.h"
|
||
|
||
#include "frontend.h"
|
||
#include "setup_frontend.h"
|
||
#include "favicon_svg.h"
|
||
|
||
// ============================================================================
|
||
// Configuration validation
|
||
// ============================================================================
|
||
|
||
#if !defined(CONFIG_PUMP_PIN)
|
||
#error "CONFIG_PUMP_PIN must be defined in menuconfig"
|
||
#endif
|
||
|
||
#if !defined(CONFIG_AP_WIFI_SSID)
|
||
#error "CONFIG_AP_WIFI_SSID must be defined in menuconfig"
|
||
#endif
|
||
|
||
#if !defined(CONFIG_AP_IP) || !defined(CONFIG_AP_GATEWAY) || !defined(CONFIG_AP_NETMASK)
|
||
#error "AP network configuration must be defined in menuconfig"
|
||
#endif
|
||
|
||
// ============================================================================
|
||
// Constants
|
||
// ============================================================================
|
||
|
||
#define AP_MAX_CONN 4
|
||
#define AP_CHANNEL 6
|
||
|
||
#define SENSOR_CHAN ADC_CHANNEL_4 // D32 on esp32
|
||
#define ADC_ATTEN_DB ADC_ATTEN_DB_12
|
||
|
||
#define THRESHOLD_UP_NVS_NAME "threshold_up"
|
||
#define THRESHOLD_LOW_NVS_NAME "threshold_low"
|
||
#define NVS_PARTITION "nvs"
|
||
|
||
#define MAX_JSON_CONTENT 512
|
||
|
||
#define FILTER_SAMPLES 5
|
||
|
||
#define PRIORITY_HTTP 1
|
||
#define PRIORITY_CONTROL 2
|
||
#define PRIORITY_SENSOR 3
|
||
|
||
#define MDNS_DOMAIN "pumpctl"
|
||
|
||
#define RESET_BTN_GPIO GPIO_NUM_15
|
||
#define LED_GPIO GPIO_NUM_2
|
||
|
||
#define WIFI_SSID_MAX_LEN 32
|
||
#define WIFI_PASS_MAX_LEN 64
|
||
|
||
#define WIFI_CONNECTED_BIT BIT0
|
||
#define WIFI_FAIL_BIT BIT1
|
||
|
||
#define DEFAULT_THRESHOLD_LOW 0
|
||
#define DEFAULT_THRESHOLD_UP 300
|
||
|
||
#define MAX_AP_SCAN_RESULTS 20
|
||
|
||
// ============================================================================
|
||
// Error codes
|
||
// ============================================================================
|
||
|
||
typedef enum {
|
||
APP_ERR_OK = 0,
|
||
APP_ERR_NVS_INIT_FAIL,
|
||
APP_ERR_NVS_OPEN_FAIL,
|
||
APP_ERR_NVS_READ_FAIL,
|
||
APP_ERR_NVS_WRITE_FAIL,
|
||
APP_ERR_ADC_INIT_FAIL,
|
||
APP_ERR_ADC_CALIB_FAIL,
|
||
APP_ERR_PUMP_INIT_FAIL,
|
||
APP_ERR_WIFI_INIT_FAIL,
|
||
APP_ERR_WIFI_CONNECT_FAIL,
|
||
APP_ERR_MDNS_INIT_FAIL,
|
||
APP_ERR_HTTP_SERVER_START_FAIL,
|
||
APP_ERR_TASK_CREATE_FAIL,
|
||
APP_ERR_GPIO_CONFIG_FAIL,
|
||
APP_ERR_INVALID_CONFIG,
|
||
APP_ERR_MEMORY_ALLOC_FAIL,
|
||
APP_ERR_JSON_PARSE_FAIL
|
||
} app_error_t;
|
||
|
||
// ============================================================================
|
||
// Global error handler
|
||
// ============================================================================
|
||
|
||
static const char *TAG = "Pump Controller";
|
||
|
||
typedef struct {
|
||
app_error_t code;
|
||
const char *message;
|
||
bool fatal;
|
||
} error_info_t;
|
||
|
||
static const error_info_t error_table[] = {
|
||
{APP_ERR_OK, "Success", false},
|
||
{APP_ERR_NVS_INIT_FAIL, "NVS initialization failed", true},
|
||
{APP_ERR_NVS_OPEN_FAIL, "NVS open failed", true},
|
||
{APP_ERR_NVS_READ_FAIL, "NVS read failed", false},
|
||
{APP_ERR_NVS_WRITE_FAIL, "NVS write failed", false},
|
||
{APP_ERR_ADC_INIT_FAIL, "ADC initialization failed", true},
|
||
{APP_ERR_ADC_CALIB_FAIL, "ADC calibration failed", false},
|
||
{APP_ERR_PUMP_INIT_FAIL, "Pump GPIO initialization failed", true},
|
||
{APP_ERR_WIFI_INIT_FAIL, "WiFi initialization failed", true},
|
||
{APP_ERR_WIFI_CONNECT_FAIL, "WiFi connection failed", true},
|
||
{APP_ERR_MDNS_INIT_FAIL, "mDNS service initialization failed", false},
|
||
{APP_ERR_HTTP_SERVER_START_FAIL, "HTTP server start failed", false},
|
||
{APP_ERR_TASK_CREATE_FAIL, "Task creation failed", true},
|
||
{APP_ERR_GPIO_CONFIG_FAIL, "GPIO configuration failed", true},
|
||
{APP_ERR_INVALID_CONFIG, "Invalid configuration", true},
|
||
{APP_ERR_MEMORY_ALLOC_FAIL, "Memory allocation failed", false},
|
||
{APP_ERR_JSON_PARSE_FAIL, "JSON parse failed", false}
|
||
};
|
||
|
||
static void handle_error(app_error_t error) {
|
||
const char *message = "Unknown error";
|
||
bool fatal = true;
|
||
|
||
for (int i = 0; i < sizeof(error_table) / sizeof(error_info_t); i++) {
|
||
if (error_table[i].code == error) {
|
||
message = error_table[i].message;
|
||
fatal = error_table[i].fatal;
|
||
break;
|
||
}
|
||
}
|
||
|
||
ESP_LOGE(TAG, "ERROR [%d]: %s", error, message);
|
||
|
||
if (fatal) {
|
||
ESP_LOGE(TAG, "Fatal error! Restarting in 5 seconds...");
|
||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||
esp_restart();
|
||
}
|
||
}
|
||
|
||
#define CHECK_ERROR(expr, error_code) \
|
||
do { \
|
||
esp_err_t __err = (expr); \
|
||
if (__err != ESP_OK) { \
|
||
ESP_LOGE(TAG, "Error at %s:%d: %s", __FILE__, __LINE__, esp_err_to_name(__err)); \
|
||
handle_error(error_code); \
|
||
return error_code; \
|
||
} \
|
||
} while(0)
|
||
|
||
#define CHECK_COND(cond, error_code) \
|
||
do { \
|
||
if (!(cond)) { \
|
||
ESP_LOGE(TAG, "Condition failed at %s:%d", __FILE__, __LINE__); \
|
||
handle_error(error_code); \
|
||
return error_code; \
|
||
} \
|
||
} while(0)
|
||
|
||
#define CHECK_PTR(ptr, error_code) \
|
||
do { \
|
||
if ((ptr) == NULL) { \
|
||
ESP_LOGE(TAG, "Null pointer at %s:%d", __FILE__, __LINE__); \
|
||
handle_error(error_code); \
|
||
return error_code; \
|
||
} \
|
||
} while(0)
|
||
|
||
// ============================================================================
|
||
// Global state
|
||
// ============================================================================
|
||
|
||
static EventGroupHandle_t wifi_event_group = NULL;
|
||
static int retry_count = 0;
|
||
static const int MAX_RETRY_COUNT = 5;
|
||
|
||
static adc_oneshot_unit_handle_t adc_handle = NULL;
|
||
static adc_cali_handle_t cali_handle = NULL;
|
||
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 bool g_pump_enabled = false;
|
||
static bool g_setup_mode = false;
|
||
|
||
// ============================================================================
|
||
// Forward declarations
|
||
// ============================================================================
|
||
|
||
static app_error_t nvs_init(void);
|
||
static app_error_t gpio_init(void);
|
||
static app_error_t adc_init(void);
|
||
static app_error_t pump_init(void);
|
||
static app_error_t load_thresholds(void);
|
||
static app_error_t save_thresholds_to_nvs(int low, int up);
|
||
static app_error_t wifi_softap_init(void);
|
||
static app_error_t wifi_sta_init(const char *ssid, const char *password);
|
||
static app_error_t mdns_init_service(void);
|
||
static app_error_t http_server_start(void);
|
||
static app_error_t setup_http_server_start(void);
|
||
static app_error_t tasks_create(void);
|
||
static bool is_wifi_config_exists(void);
|
||
static void reset_settings(void);
|
||
|
||
// ============================================================================
|
||
// Utility functions
|
||
// ============================================================================
|
||
|
||
static int map(int x, int in_min, int in_max, int out_min, int out_max) {
|
||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||
}
|
||
|
||
static int convert_adc_to_pressure_atm(int adc_value) {
|
||
return map(adc_value, 330, 3145, 0, 1184);
|
||
}
|
||
|
||
static void pump_disable(void) {
|
||
gpio_set_level(CONFIG_PUMP_PIN, false);
|
||
g_pump_enabled = false;
|
||
}
|
||
|
||
static void pump_enable(void) {
|
||
gpio_set_level(CONFIG_PUMP_PIN, true);
|
||
g_pump_enabled = true;
|
||
}
|
||
|
||
static void indicate_led(void) {
|
||
for (int i = 0; i < 3; i++) {
|
||
gpio_set_level(LED_GPIO, 1);
|
||
vTaskDelay(pdMS_TO_TICKS(200));
|
||
gpio_set_level(LED_GPIO, 0);
|
||
vTaskDelay(pdMS_TO_TICKS(200));
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// ADC functions
|
||
// ============================================================================
|
||
|
||
static app_error_t adc_init(void) {
|
||
adc_oneshot_unit_init_cfg_t init_config = {
|
||
.unit_id = ADC_UNIT_1,
|
||
};
|
||
CHECK_ERROR(adc_oneshot_new_unit(&init_config, &adc_handle), APP_ERR_ADC_INIT_FAIL);
|
||
|
||
adc_oneshot_chan_cfg_t config = {
|
||
.atten = ADC_ATTEN_DB,
|
||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||
};
|
||
CHECK_ERROR(adc_oneshot_config_channel(adc_handle, SENSOR_CHAN, &config), APP_ERR_ADC_INIT_FAIL);
|
||
|
||
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 calibration successful");
|
||
} else if (ret == ESP_ERR_NOT_SUPPORTED) {
|
||
ESP_LOGW(TAG, "ADC calibration not available");
|
||
is_calibrated = false;
|
||
} else {
|
||
CHECK_ERROR(ret, APP_ERR_ADC_CALIB_FAIL);
|
||
}
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
static int adc_read_raw() {
|
||
adc_channel_t adc_channel = SENSOR_CHAN;
|
||
|
||
CHECK_PTR(adc_handle, APP_ERR_ADC_INIT_FAIL);
|
||
|
||
int raw_value = 0;
|
||
CHECK_ERROR(adc_oneshot_read(adc_handle, adc_channel, &raw_value), APP_ERR_ADC_INIT_FAIL);
|
||
return raw_value;
|
||
}
|
||
|
||
static int adc_read_voltage() {
|
||
int raw_value = adc_read_raw();
|
||
if (raw_value < 0) return -1;
|
||
|
||
if (is_calibrated) {
|
||
int voltage_mv = 0;
|
||
CHECK_ERROR(adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage_mv), APP_ERR_ADC_INIT_FAIL);
|
||
return voltage_mv;
|
||
} else {
|
||
return (raw_value * 3300) / 4095;
|
||
}
|
||
}
|
||
|
||
static int read_voltage_filtered(void) {
|
||
int sum = 0;
|
||
for (int i = 0; i < FILTER_SAMPLES; i++) {
|
||
int voltage = adc_read_voltage();
|
||
if (voltage < 0) return -1;
|
||
sum += voltage;
|
||
vTaskDelay(pdMS_TO_TICKS(10));
|
||
}
|
||
return sum / FILTER_SAMPLES;
|
||
}
|
||
|
||
// ============================================================================
|
||
// NVS functions
|
||
// ============================================================================
|
||
|
||
static app_error_t nvs_init(void) {
|
||
esp_err_t ret = nvs_flash_init();
|
||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||
CHECK_ERROR(nvs_flash_erase(), APP_ERR_NVS_INIT_FAIL);
|
||
CHECK_ERROR(nvs_flash_init(), APP_ERR_NVS_INIT_FAIL);
|
||
} else {
|
||
CHECK_ERROR(ret, APP_ERR_NVS_INIT_FAIL);
|
||
}
|
||
return ERR_OK;
|
||
}
|
||
|
||
static bool is_wifi_config_exists(void) {
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open(NVS_PARTITION, NVS_READONLY, &nvs_handle);
|
||
if (err != ESP_OK) {
|
||
return false;
|
||
}
|
||
|
||
char ssid[WIFI_SSID_MAX_LEN];
|
||
size_t ssid_len = sizeof(ssid);
|
||
err = nvs_get_str(nvs_handle, "wifi_ssid", ssid, &ssid_len);
|
||
nvs_close(nvs_handle);
|
||
|
||
return (err == ESP_OK);
|
||
}
|
||
|
||
static app_error_t load_thresholds(void) {
|
||
nvs_handle_t my_handle;
|
||
CHECK_ERROR(nvs_open(NVS_PARTITION, NVS_READWRITE, &my_handle), APP_ERR_NVS_OPEN_FAIL);
|
||
|
||
int32_t threshold_low = DEFAULT_THRESHOLD_LOW;
|
||
esp_err_t err = nvs_get_i32(my_handle, THRESHOLD_LOW_NVS_NAME, &threshold_low);
|
||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
|
||
nvs_close(my_handle);
|
||
CHECK_ERROR(err, APP_ERR_NVS_READ_FAIL);
|
||
}
|
||
atomic_store(&g_threshold_low, threshold_low);
|
||
|
||
int32_t threshold_up = DEFAULT_THRESHOLD_UP;
|
||
err = nvs_get_i32(my_handle, THRESHOLD_UP_NVS_NAME, &threshold_up);
|
||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
|
||
nvs_close(my_handle);
|
||
CHECK_ERROR(err, APP_ERR_NVS_READ_FAIL);
|
||
}
|
||
atomic_store(&g_threshold_up, threshold_up);
|
||
|
||
nvs_close(my_handle);
|
||
|
||
ESP_LOGI(TAG, "Thresholds loaded: low=%d, up=%d", threshold_low, threshold_up);
|
||
return ERR_OK;
|
||
}
|
||
|
||
static app_error_t save_thresholds_to_nvs(int low, int up) {
|
||
nvs_handle_t my_handle;
|
||
CHECK_ERROR(nvs_open(NVS_PARTITION, NVS_READWRITE, &my_handle), APP_ERR_NVS_OPEN_FAIL);
|
||
|
||
CHECK_ERROR(nvs_set_i32(my_handle, THRESHOLD_LOW_NVS_NAME, low), APP_ERR_NVS_WRITE_FAIL);
|
||
CHECK_ERROR(nvs_set_i32(my_handle, THRESHOLD_UP_NVS_NAME, up), APP_ERR_NVS_WRITE_FAIL);
|
||
CHECK_ERROR(nvs_commit(my_handle), APP_ERR_NVS_WRITE_FAIL);
|
||
|
||
nvs_close(my_handle);
|
||
return ERR_OK;
|
||
}
|
||
|
||
static void save_wifi_config(const char *ssid, const char *password) {
|
||
nvs_handle_t nvs_handle;
|
||
if (nvs_open(NVS_PARTITION, NVS_READWRITE, &nvs_handle) != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to open NVS for WiFi config");
|
||
return;
|
||
}
|
||
|
||
nvs_set_str(nvs_handle, "wifi_ssid", ssid);
|
||
nvs_set_str(nvs_handle, "wifi_pass", password);
|
||
nvs_commit(nvs_handle);
|
||
nvs_close(nvs_handle);
|
||
|
||
ESP_LOGI(TAG, "WiFi config saved: SSID=%s", ssid);
|
||
}
|
||
|
||
static void reset_settings(void) {
|
||
ESP_LOGI(TAG, "Resetting settings...");
|
||
nvs_flash_erase();
|
||
nvs_flash_init();
|
||
vTaskDelay(pdMS_TO_TICKS(500));
|
||
}
|
||
|
||
// ============================================================================
|
||
// GPIO functions
|
||
// ============================================================================
|
||
|
||
static app_error_t gpio_init(void) {
|
||
gpio_config_t led_conf = {
|
||
.pin_bit_mask = (1ULL << LED_GPIO),
|
||
.mode = GPIO_MODE_OUTPUT,
|
||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||
.pull_down_en = GPIO_PULLUP_DISABLE,
|
||
.intr_type = GPIO_INTR_DISABLE,
|
||
};
|
||
CHECK_ERROR(gpio_config(&led_conf), APP_ERR_GPIO_CONFIG_FAIL);
|
||
gpio_set_level(LED_GPIO, 0);
|
||
|
||
gpio_config_t btn_conf = {
|
||
.pin_bit_mask = (1ULL << RESET_BTN_GPIO),
|
||
.mode = GPIO_MODE_INPUT,
|
||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||
.intr_type = GPIO_INTR_DISABLE,
|
||
};
|
||
CHECK_ERROR(gpio_config(&btn_conf), APP_ERR_GPIO_CONFIG_FAIL);
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
static app_error_t pump_init(void) {
|
||
gpio_config_t io_conf = {
|
||
.pin_bit_mask = (1ULL << CONFIG_PUMP_PIN),
|
||
.mode = GPIO_MODE_INPUT_OUTPUT,
|
||
.intr_type = GPIO_INTR_DISABLE,
|
||
.pull_down_en = 1,
|
||
.pull_up_en = 0,
|
||
};
|
||
CHECK_ERROR(gpio_config(&io_conf), APP_ERR_PUMP_INIT_FAIL);
|
||
pump_disable();
|
||
return ERR_OK;
|
||
}
|
||
|
||
// ============================================================================
|
||
// WiFi functions
|
||
// ============================================================================
|
||
|
||
// Добавьте в глобальные переменные:
|
||
static bool g_wifi_test_in_progress = false;
|
||
|
||
// Дополните существующий wifi_event_handler:
|
||
static void wifi_event_handler(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();
|
||
ESP_LOGI(TAG, "Attempting to connect to WiFi...");
|
||
}
|
||
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||
if (g_wifi_test_in_progress) {
|
||
// Если идет тестирование, сразу сигнализируем об ошибке
|
||
xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
|
||
} else if (retry_count < MAX_RETRY_COUNT) {
|
||
esp_wifi_connect();
|
||
retry_count++;
|
||
ESP_LOGI(TAG, "Retry connecting (%d/%d)...", retry_count, MAX_RETRY_COUNT);
|
||
} else {
|
||
xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
|
||
ESP_LOGE(TAG, "Failed to connect after %d retries", MAX_RETRY_COUNT);
|
||
}
|
||
}
|
||
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
|
||
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||
retry_count = 0;
|
||
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
|
||
}
|
||
}
|
||
|
||
bool test_wifi_credentials(const char* ssid, const char* password, int timeout_ms) {
|
||
wifi_config_t old_config;
|
||
esp_wifi_get_config(WIFI_IF_STA, &old_config);
|
||
|
||
if (wifi_event_group == NULL) {
|
||
wifi_event_group = xEventGroupCreate();
|
||
if (wifi_event_group == NULL) {
|
||
ESP_LOGE(TAG, "Failed to create event group");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Очищаем предыдущие биты
|
||
xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);
|
||
|
||
// Устанавливаем флаг тестирования
|
||
g_wifi_test_in_progress = true;
|
||
|
||
// Останавливаем текущее подключение если есть
|
||
esp_wifi_disconnect();
|
||
vTaskDelay(pdMS_TO_TICKS(500));
|
||
|
||
// Настраиваем STA интерфейс с новыми credentials
|
||
wifi_config_t wifi_config = {0};
|
||
strncpy((char*)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
|
||
strncpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);
|
||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||
|
||
esp_err_t ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to set WiFi config: %s", esp_err_to_name(ret));
|
||
g_wifi_test_in_progress = false;
|
||
return false;
|
||
}
|
||
|
||
// Запускаем подключение
|
||
ret = esp_wifi_connect();
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "Failed to start WiFi connection: %s", esp_err_to_name(ret));
|
||
g_wifi_test_in_progress = false;
|
||
return false;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Testing WiFi connection to SSID: %s", ssid);
|
||
|
||
// Ожидаем результат
|
||
EventBits_t bits = xEventGroupWaitBits(wifi_event_group,
|
||
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||
pdFALSE,
|
||
pdFALSE,
|
||
timeout_ms / portTICK_PERIOD_MS);
|
||
|
||
bool success = (bits & WIFI_CONNECTED_BIT) != 0;
|
||
|
||
if (success) {
|
||
ESP_LOGI(TAG, "✓ Successfully connected to %s", ssid);
|
||
esp_wifi_disconnect();
|
||
|
||
esp_wifi_set_config(WIFI_IF_STA, &old_config);
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(500));
|
||
} else {
|
||
ESP_LOGW(TAG, "✗ Failed to connect to %s", ssid);
|
||
}
|
||
|
||
g_wifi_test_in_progress = false;
|
||
return success;
|
||
}
|
||
|
||
static app_error_t wifi_softap_init(void) {
|
||
CHECK_ERROR(esp_netif_init(), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_event_loop_create_default(), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
|
||
CHECK_PTR(ap_netif, APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||
CHECK_PTR(sta_netif, APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||
CHECK_ERROR(esp_wifi_init(&cfg), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
|
||
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);
|
||
|
||
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;
|
||
}
|
||
|
||
CHECK_ERROR(esp_wifi_set_mode(WIFI_MODE_APSTA), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
CHECK_ERROR(esp_wifi_set_config(WIFI_IF_AP, &wifi_config), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
wifi_config_t sta_config = {
|
||
.sta = {
|
||
.ssid = "",
|
||
.password = "",
|
||
.scan_method = WIFI_FAST_SCAN,
|
||
.sort_method = WIFI_CONNECT_AP_BY_SIGNAL,
|
||
.threshold.rssi = -127,
|
||
.threshold.authmode = WIFI_AUTH_OPEN,
|
||
},
|
||
};
|
||
|
||
CHECK_ERROR(esp_wifi_set_config(WIFI_IF_STA, &sta_config), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
CHECK_ERROR(esp_wifi_start(), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
esp_netif_ip_info_t ip_info;
|
||
ip_info.ip.addr = inet_addr(CONFIG_AP_IP);
|
||
ip_info.gw.addr = inet_addr(CONFIG_AP_GATEWAY);
|
||
ip_info.netmask.addr = inet_addr(CONFIG_AP_NETMASK);
|
||
|
||
CHECK_ERROR(esp_netif_dhcps_stop(ap_netif), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_netif_set_ip_info(ap_netif, &ip_info), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_netif_dhcps_start(ap_netif), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
static app_error_t wifi_sta_init(const char* ssid, const char* password) {
|
||
wifi_event_group = xEventGroupCreate();
|
||
CHECK_PTR(wifi_event_group, APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
CHECK_ERROR(esp_netif_init(), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_event_loop_create_default(), APP_ERR_WIFI_INIT_FAIL);
|
||
esp_netif_create_default_wifi_sta();
|
||
|
||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||
CHECK_ERROR(esp_wifi_init(&cfg), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
CHECK_ERROR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
wifi_config_t wifi_config = {
|
||
.sta = {
|
||
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
|
||
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
|
||
},
|
||
};
|
||
strcpy((char*)wifi_config.sta.ssid, ssid);
|
||
strcpy((char*)wifi_config.sta.password, password);
|
||
|
||
CHECK_ERROR(esp_wifi_set_mode(WIFI_MODE_STA), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_wifi_set_config(WIFI_IF_STA, &wifi_config), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(esp_wifi_start(), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
ESP_LOGI(TAG, "WiFi STA started");
|
||
|
||
EventBits_t bits = xEventGroupWaitBits(wifi_event_group,
|
||
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||
pdFALSE, pdFALSE, portMAX_DELAY);
|
||
|
||
if (bits & WIFI_CONNECTED_BIT) {
|
||
ESP_LOGI(TAG, "Successfully connected to WiFi!");
|
||
return ERR_OK;
|
||
} else {
|
||
return APP_ERR_WIFI_CONNECT_FAIL;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// mDNS functions
|
||
// ============================================================================
|
||
|
||
static app_error_t mdns_init_service(void) {
|
||
CHECK_ERROR(mdns_init(), APP_ERR_MDNS_INIT_FAIL);
|
||
CHECK_ERROR(mdns_hostname_set(MDNS_DOMAIN), APP_ERR_MDNS_INIT_FAIL);
|
||
CHECK_ERROR(mdns_instance_name_set("PumpController"), APP_ERR_MDNS_INIT_FAIL);
|
||
CHECK_ERROR(mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0), APP_ERR_MDNS_INIT_FAIL);
|
||
return ERR_OK;
|
||
}
|
||
|
||
// ============================================================================
|
||
// HTTP handlers
|
||
// ============================================================================
|
||
|
||
static esp_err_t favicon_get_handler(httpd_req_t *req) {
|
||
httpd_resp_set_type(req, "image/svg+xml");
|
||
httpd_resp_send(req, (const char*)assets_favicon_svg, assets_favicon_svg_len);
|
||
return ESP_OK;
|
||
}
|
||
|
||
static esp_err_t send_json_response(httpd_req_t *req, const char *format, ...) {
|
||
esp_err_t ret = ESP_OK;
|
||
char *response = NULL;
|
||
va_list args;
|
||
va_list args_copy;
|
||
|
||
va_start(args, format);
|
||
va_copy(args_copy, args);
|
||
int len = vsnprintf(NULL, 0, format, args_copy);
|
||
va_end(args_copy);
|
||
va_end(args);
|
||
|
||
if (len < 0) {
|
||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Format error");
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
response = malloc(len + 1);
|
||
if (response == NULL) {
|
||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory");
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
va_start(args, format);
|
||
vsnprintf(response, len + 1, format, args);
|
||
va_end(args);
|
||
|
||
httpd_resp_set_type(req, "application/json");
|
||
ret = httpd_resp_send(req, response, len);
|
||
|
||
free(response);
|
||
return ret;
|
||
}
|
||
|
||
static esp_err_t receive_http_content(httpd_req_t *req, char **content) {
|
||
size_t content_len = req->content_len;
|
||
|
||
if (content_len > MAX_JSON_CONTENT) {
|
||
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");
|
||
handle_error(APP_ERR_MEMORY_ALLOC_FAIL);
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
int received = 0;
|
||
while (received < content_len) {
|
||
int ret = httpd_req_recv(req, *content + received, content_len - received);
|
||
if (ret <= 0) break;
|
||
received += ret;
|
||
}
|
||
(*content)[content_len] = '\0';
|
||
|
||
return ESP_OK;
|
||
}
|
||
|
||
static esp_err_t parse_thresholds_json(const char *content, int *low_value, int *up_value) {
|
||
cJSON *json = cJSON_Parse(content);
|
||
if (!json) {
|
||
handle_error(APP_ERR_JSON_PARSE_FAIL);
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
cJSON *low_item = cJSON_GetObjectItem(json, "low");
|
||
cJSON *up_item = cJSON_GetObjectItem(json, "up");
|
||
|
||
bool valid = cJSON_IsNumber(low_item) && cJSON_IsNumber(up_item);
|
||
if (valid) {
|
||
*low_value = low_item->valueint;
|
||
*up_value = up_item->valueint;
|
||
}
|
||
|
||
cJSON_Delete(json);
|
||
return valid ? ESP_OK : ESP_FAIL;
|
||
}
|
||
|
||
static esp_err_t root_get_handler(httpd_req_t *req) {
|
||
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
||
httpd_resp_send(req, (const char*)assets_index_html, assets_index_html_len);
|
||
return ESP_OK;
|
||
}
|
||
|
||
static esp_err_t current_pressure_handler(httpd_req_t *req) {
|
||
int sensor_value = atomic_load(&g_current_pressure);
|
||
return send_json_response(req, "{\"value\":%d}", sensor_value);
|
||
}
|
||
|
||
static esp_err_t pump_state_handler(httpd_req_t *req) {
|
||
return send_json_response(req, "{\"state\":%d}", g_pump_enabled ? 1 : 0);
|
||
}
|
||
|
||
static esp_err_t save_thresholds_handler(httpd_req_t *req) {
|
||
char *content = NULL;
|
||
if (receive_http_content(req, &content) != ESP_OK) {
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
int low_value = 0, up_value = 0;
|
||
if (parse_thresholds_json(content, &low_value, &up_value) != ESP_OK) {
|
||
free(content);
|
||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing or invalid 'low' or 'up' parameters");
|
||
return ESP_FAIL;
|
||
}
|
||
free(content);
|
||
|
||
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;
|
||
}
|
||
|
||
if (save_thresholds_to_nvs(low_value, up_value) != APP_ERR_OK) {
|
||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save to NVS");
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
atomic_store(&g_threshold_low, low_value);
|
||
atomic_store(&g_threshold_up, up_value);
|
||
|
||
ESP_LOGI(TAG, "Thresholds saved: low=%d, up=%d", low_value, up_value);
|
||
return send_json_response(req, "{\"success\":true,\"low\":%d,\"up\":%d}", low_value, up_value);
|
||
}
|
||
|
||
static esp_err_t get_thresholds_handler(httpd_req_t *req) {
|
||
int low_threshold = atomic_load(&g_threshold_low);
|
||
int up_threshold = atomic_load(&g_threshold_up);
|
||
return send_json_response(req, "{\"low\":%d,\"up\":%d}", low_threshold, up_threshold);
|
||
}
|
||
|
||
static esp_err_t set_thresholds_handler(httpd_req_t *req) {
|
||
char *content = NULL;
|
||
if (receive_http_content(req, &content) != ESP_OK) {
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
int low_value = 0, up_value = 0;
|
||
if (parse_thresholds_json(content, &low_value, &up_value) != ESP_OK) {
|
||
free(content);
|
||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing or invalid 'low' or 'up' parameters");
|
||
return ESP_FAIL;
|
||
}
|
||
free(content);
|
||
|
||
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);
|
||
ESP_LOGI(TAG, "Thresholds updated: low=%d, up=%d", low_value, up_value);
|
||
|
||
return send_json_response(req, "{\"success\":true,\"low\":%d,\"up\":%d}", low_value, up_value);
|
||
}
|
||
|
||
// ============================================================================
|
||
// Setup mode HTTP handlers
|
||
// ============================================================================
|
||
|
||
static esp_err_t setup_get_handler(httpd_req_t *req) {
|
||
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
||
httpd_resp_send(req, (const char*)assets_setup_html, assets_setup_html_len);
|
||
return ESP_OK;
|
||
}
|
||
|
||
static esp_err_t parse_wifi_settings_json(const char *content, char *ssid, char *password) {
|
||
cJSON *json = cJSON_Parse(content);
|
||
if (!json) {
|
||
handle_error(APP_ERR_JSON_PARSE_FAIL);
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
cJSON *ssid_item = cJSON_GetObjectItem(json, "ssid");
|
||
cJSON *pass_item = cJSON_GetObjectItem(json, "password");
|
||
|
||
if (!ssid_item || !pass_item ||
|
||
ssid_item->type != cJSON_String ||
|
||
pass_item->type != cJSON_String) {
|
||
cJSON_Delete(json);
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
if (strlen(ssid_item->valuestring) >= WIFI_SSID_MAX_LEN ||
|
||
strlen(pass_item->valuestring) >= WIFI_PASS_MAX_LEN) {
|
||
cJSON_Delete(json);
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
strlcpy(ssid, ssid_item->valuestring, WIFI_SSID_MAX_LEN);
|
||
strlcpy(password, pass_item->valuestring, WIFI_PASS_MAX_LEN);
|
||
|
||
cJSON_Delete(json);
|
||
return ESP_OK;
|
||
}
|
||
|
||
static esp_err_t setup_set_settings_handler(httpd_req_t *req) {
|
||
char *content = NULL;
|
||
|
||
if (receive_http_content(req, &content) != ESP_OK) {
|
||
httpd_resp_set_type(req, "application/json");
|
||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "{\"success\":false,\"message\":\"Failed to receive content\"}");
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
char ssid[WIFI_SSID_MAX_LEN];
|
||
char password[WIFI_PASS_MAX_LEN];
|
||
|
||
if (parse_wifi_settings_json(content, ssid, password) != ESP_OK) {
|
||
free(content);
|
||
httpd_resp_set_type(req, "application/json");
|
||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "{\"success\":false,\"message\":\"Missing or invalid 'ssid' or 'password' parameters\"}");
|
||
return ESP_FAIL;
|
||
}
|
||
free(content);
|
||
|
||
// Проверяем, что пароль не пустой для защищенных сетей
|
||
if (strlen(ssid) == 0) {
|
||
httpd_resp_set_type(req, "application/json");
|
||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "{\"success\":false,\"message\":\"SSID cannot be empty\"}");
|
||
return ESP_FAIL;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Testing WiFi connection to SSID: %s", ssid);
|
||
|
||
// Сохраняем текущий режим WiFi перед тестированием
|
||
wifi_mode_t current_mode;
|
||
esp_wifi_get_mode(¤t_mode);
|
||
|
||
// Убеждаемся, что STA режим активен
|
||
if (!(current_mode & WIFI_MODE_STA)) {
|
||
esp_wifi_set_mode(WIFI_MODE_APSTA);
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
}
|
||
|
||
bool wifi_ok = test_wifi_credentials(ssid, password, 15000); // 15 секунд таймаут
|
||
|
||
if (wifi_ok) {
|
||
save_wifi_config(ssid, password);
|
||
|
||
httpd_resp_set_type(req, "application/json");
|
||
httpd_resp_set_status(req, HTTPD_200);
|
||
const char *success_response = "{\"success\":true,\"message\":\"WiFi connected successfully! Rebooting in 2 seconds...\"}";
|
||
httpd_resp_send(req, success_response, strlen(success_response));
|
||
|
||
ESP_LOGI(TAG, "WiFi test SUCCESSFUL, rebooting...");
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
esp_restart();
|
||
} else {
|
||
httpd_resp_set_type(req, "application/json");
|
||
const char *error_response = "{\"success\":false,\"message\":\"Failed to connect to WiFi. Please check SSID and password.\"}";
|
||
httpd_resp_send(req, error_response, strlen(error_response));
|
||
|
||
ESP_LOGW(TAG, "WiFi test FAILED for SSID: %s", ssid);
|
||
}
|
||
|
||
return ESP_OK;
|
||
}
|
||
|
||
|
||
|
||
static esp_err_t setup_get_wifi_list_handler(httpd_req_t *req) {
|
||
esp_err_t ret;
|
||
uint16_t ap_count = 0;
|
||
wifi_ap_record_t ap_info[MAX_AP_SCAN_RESULTS];
|
||
memset(ap_info, 0, sizeof(ap_info));
|
||
|
||
// Получаем текущий режим Wi-Fi
|
||
wifi_mode_t current_mode;
|
||
ret = esp_wifi_get_mode(¤t_mode);
|
||
if (ret != ESP_OK) {
|
||
send_json_response(req, "{\"success\":false,\"error\":\"Failed to get WiFi mode\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
// Запускаем сканирование
|
||
wifi_scan_config_t scan_config = {
|
||
.ssid = NULL,
|
||
.bssid = NULL,
|
||
.channel = 0,
|
||
.show_hidden = true,
|
||
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
|
||
.scan_time = {
|
||
.active = {
|
||
.min = 100,
|
||
.max = 300
|
||
}
|
||
}
|
||
};
|
||
|
||
ret = esp_wifi_scan_start(&scan_config, true);
|
||
if (ret != ESP_OK) {
|
||
send_json_response(req, "{\"success\":false,\"error\":\"Failed to start WiFi scan\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
ret = esp_wifi_scan_get_ap_num(&ap_count);
|
||
if (ret != ESP_OK) {
|
||
send_json_response(req, "{\"success\":false,\"error\":\"Failed to get AP count\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
if (ap_count > MAX_AP_SCAN_RESULTS) {
|
||
ap_count = MAX_AP_SCAN_RESULTS;
|
||
}
|
||
|
||
ret = esp_wifi_scan_get_ap_records(&ap_count, ap_info);
|
||
if (ret != ESP_OK) {
|
||
send_json_response(req, "{\"success\":false,\"error\":\"Failed to get AP records\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
cJSON *root = cJSON_CreateObject();
|
||
if (root == NULL) {
|
||
send_json_response(req, "{\"success\":false,\"error\":\"JSON creation failed\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
cJSON_AddBoolToObject(root, "success", true);
|
||
|
||
cJSON *networks_array = cJSON_CreateArray();
|
||
if (networks_array == NULL) {
|
||
cJSON_Delete(root);
|
||
send_json_response(req, "{\"success\":false,\"error\":\"JSON array creation failed\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
for (int i = 0; i < ap_count; i++) {
|
||
cJSON *network = cJSON_CreateObject();
|
||
if (network == NULL) {
|
||
continue;
|
||
}
|
||
|
||
char ssid_str[33];
|
||
memcpy(ssid_str, ap_info[i].ssid, 32);
|
||
ssid_str[32] = '\0';
|
||
cJSON_AddStringToObject(network, "ssid", ssid_str);
|
||
|
||
// RSSI (сигнал)
|
||
cJSON_AddNumberToObject(network, "rssi", ap_info[i].rssi);
|
||
|
||
// Тип шифрования (в читаемом виде)
|
||
const char *auth_mode_str;
|
||
switch (ap_info[i].authmode) {
|
||
case WIFI_AUTH_OPEN:
|
||
auth_mode_str = "Open";
|
||
break;
|
||
case WIFI_AUTH_WEP:
|
||
auth_mode_str = "WEP";
|
||
break;
|
||
case WIFI_AUTH_WPA_PSK:
|
||
auth_mode_str = "WPA-PSK";
|
||
break;
|
||
case WIFI_AUTH_WPA2_PSK:
|
||
auth_mode_str = "WPA2-PSK";
|
||
break;
|
||
case WIFI_AUTH_WPA_WPA2_PSK:
|
||
auth_mode_str = "WPA/WPA2-PSK";
|
||
break;
|
||
case WIFI_AUTH_WPA3_PSK:
|
||
auth_mode_str = "WPA3-PSK";
|
||
break;
|
||
case WIFI_AUTH_WPA2_WPA3_PSK:
|
||
auth_mode_str = "WPA2/WPA3-PSK";
|
||
break;
|
||
default:
|
||
auth_mode_str = "Unknown";
|
||
break;
|
||
}
|
||
cJSON_AddStringToObject(network, "auth_mode", auth_mode_str);
|
||
|
||
cJSON_AddItemToArray(networks_array, network);
|
||
}
|
||
|
||
cJSON_AddItemToObject(root, "networks", networks_array);
|
||
cJSON_AddNumberToObject(root, "count", ap_count);
|
||
|
||
char *json_string = cJSON_PrintUnformatted(root);
|
||
if (json_string == NULL) {
|
||
cJSON_Delete(root);
|
||
send_json_response(req, "{\"success\":false,\"error\":\"JSON string conversion failed\"}");
|
||
return ESP_OK;
|
||
}
|
||
|
||
send_json_response(req, json_string);
|
||
|
||
free(json_string);
|
||
cJSON_Delete(root);
|
||
|
||
return ESP_OK;
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// HTTP server tasks
|
||
// ============================================================================
|
||
|
||
static app_error_t http_server_start(void) {
|
||
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 = 4096;
|
||
|
||
CHECK_ERROR(httpd_start(&server, &config), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
|
||
|
||
httpd_uri_t uris[] = {
|
||
{.uri = "/", .method = HTTP_GET, .handler = root_get_handler},
|
||
{.uri = "/favicon.svg", .method = HTTP_GET, .handler = favicon_get_handler},
|
||
{.uri = "/pressure", .method = HTTP_GET, .handler = current_pressure_handler},
|
||
{.uri = "/state", .method = HTTP_GET, .handler = pump_state_handler},
|
||
{.uri = "/thresholds", .method = HTTP_GET, .handler = get_thresholds_handler},
|
||
{.uri = "/thresholds", .method = HTTP_POST, .handler = set_thresholds_handler},
|
||
{.uri = "/persist_thresholds", .method = HTTP_POST, .handler = save_thresholds_handler},
|
||
};
|
||
|
||
for (int i = 0; i < sizeof(uris) / sizeof(httpd_uri_t); i++) {
|
||
CHECK_ERROR(httpd_register_uri_handler(server, &uris[i]), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
}
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
static app_error_t setup_http_server_start(void) {
|
||
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;
|
||
|
||
CHECK_ERROR(httpd_start(&server, &config), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
ESP_LOGI(TAG, "Setup HTTP server started on port %d", config.server_port);
|
||
|
||
httpd_uri_t uris[] = {
|
||
{.uri = "/", .method = HTTP_GET, .handler = setup_get_handler},
|
||
{.uri = "/favicon.svg", .method = HTTP_GET, .handler = favicon_get_handler},
|
||
{.uri = "/settings", .method = HTTP_POST, .handler = setup_set_settings_handler},
|
||
{.uri = "/wifi_list", .method = HTTP_GET, .handler = setup_get_wifi_list_handler},
|
||
};
|
||
|
||
for (int i = 0; i < sizeof(uris) / sizeof(httpd_uri_t); i++) {
|
||
CHECK_ERROR(httpd_register_uri_handler(server, &uris[i]), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
}
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
// ============================================================================
|
||
// Application tasks
|
||
// ============================================================================
|
||
|
||
static void vReadSensorTask(void *pvParameters) {
|
||
esp_task_wdt_add(xTaskGetCurrentTaskHandle());
|
||
|
||
while (1) {
|
||
int voltage = read_voltage_filtered();
|
||
if (voltage >= 0) {
|
||
int pressure = convert_adc_to_pressure_atm(voltage);
|
||
atomic_store(&g_current_pressure, pressure);
|
||
}
|
||
esp_task_wdt_reset();
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
}
|
||
}
|
||
|
||
static void vPumpControlTask(void *pvParameters) {
|
||
esp_task_wdt_add(xTaskGetCurrentTaskHandle());
|
||
|
||
while (1) {
|
||
int current_pressure = atomic_load(&g_current_pressure);
|
||
int low_threshold = atomic_load(&g_threshold_low);
|
||
int up_threshold = atomic_load(&g_threshold_up);
|
||
|
||
if ((current_pressure < low_threshold) && !g_pump_enabled) {
|
||
pump_enable();
|
||
ESP_LOGI(TAG, "Pump enabled (pressure=%d < low=%d)", current_pressure, low_threshold);
|
||
} else if ((current_pressure >= up_threshold) && g_pump_enabled) {
|
||
pump_disable();
|
||
ESP_LOGI(TAG, "Pump disabled (pressure=%d >= up=%d)", current_pressure, up_threshold);
|
||
}
|
||
|
||
esp_task_wdt_reset();
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// Tasks creation
|
||
// ============================================================================
|
||
|
||
static app_error_t tasks_create(void) {
|
||
CHECK_ERROR(xTaskCreate(vReadSensorTask, "read_sensor", 2048, NULL, PRIORITY_SENSOR, NULL) == pdPASS ? ESP_OK : ESP_FAIL, APP_ERR_TASK_CREATE_FAIL);
|
||
CHECK_ERROR(xTaskCreate(vPumpControlTask, "pump_control", 2048, NULL, PRIORITY_CONTROL, NULL) == pdPASS ? ESP_OK : ESP_FAIL, APP_ERR_TASK_CREATE_FAIL);
|
||
return ERR_OK;
|
||
}
|
||
|
||
// ============================================================================
|
||
// Mode initialization
|
||
// ============================================================================
|
||
|
||
static app_error_t setup_mode_init(void) {
|
||
g_setup_mode = true;
|
||
|
||
CHECK_ERROR(wifi_softap_init(), APP_ERR_WIFI_INIT_FAIL);
|
||
CHECK_ERROR(setup_http_server_start(), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
|
||
ESP_LOGI(TAG, "=========================================");
|
||
ESP_LOGI(TAG, "Setup mode active");
|
||
ESP_LOGI(TAG, "📱 Connect to Wi-Fi: %s", CONFIG_AP_WIFI_SSID);
|
||
ESP_LOGI(TAG, "🔑 Password: %s", strlen(CONFIG_AP_WIFI_PASS) ? CONFIG_AP_WIFI_PASS : "Open network");
|
||
ESP_LOGI(TAG, "🌐 Open browser: http://%s:%d", CONFIG_AP_IP, CONFIG_WEBINTERFACE_PORT);
|
||
ESP_LOGI(TAG, "=========================================");
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
static app_error_t ap_normal_mode_init() {
|
||
CHECK_ERROR(wifi_softap_init(), APP_ERR_WIFI_INIT_FAIL);
|
||
|
||
CHECK_ERROR(load_thresholds(), APP_ERR_NVS_READ_FAIL);
|
||
CHECK_ERROR(adc_init(), APP_ERR_ADC_INIT_FAIL);
|
||
CHECK_ERROR(pump_init(), APP_ERR_PUMP_INIT_FAIL);
|
||
|
||
CHECK_ERROR(http_server_start(), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
CHECK_ERROR(tasks_create(), APP_ERR_TASK_CREATE_FAIL);
|
||
|
||
ESP_LOGI(TAG, "=========================================");
|
||
ESP_LOGI(TAG, "AP normal mode active");
|
||
ESP_LOGI(TAG, "📱 Connect to Wi-Fi: %s", CONFIG_AP_WIFI_SSID);
|
||
ESP_LOGI(TAG, "🔑 Password: %s", strlen(CONFIG_AP_WIFI_PASS) ? CONFIG_AP_WIFI_PASS : "Open network");
|
||
ESP_LOGI(TAG, "🌐 Open browser: http://%s:%d", CONFIG_AP_IP, CONFIG_WEBINTERFACE_PORT);
|
||
ESP_LOGI(TAG, "=========================================");
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
static app_error_t normal_mode_init(void) {
|
||
g_setup_mode = false;
|
||
|
||
CHECK_ERROR(load_thresholds(), APP_ERR_NVS_READ_FAIL);
|
||
CHECK_ERROR(adc_init(), APP_ERR_ADC_INIT_FAIL);
|
||
CHECK_ERROR(pump_init(), APP_ERR_PUMP_INIT_FAIL);
|
||
|
||
nvs_handle_t nvs_handle;
|
||
CHECK_ERROR(nvs_open(NVS_PARTITION, NVS_READONLY, &nvs_handle), APP_ERR_NVS_OPEN_FAIL);
|
||
|
||
char ssid[WIFI_SSID_MAX_LEN];
|
||
char password[WIFI_PASS_MAX_LEN];
|
||
size_t ssid_len = sizeof(ssid);
|
||
size_t pass_len = sizeof(password);
|
||
|
||
CHECK_ERROR(nvs_get_str(nvs_handle, "wifi_ssid", ssid, &ssid_len), APP_ERR_NVS_READ_FAIL);
|
||
CHECK_ERROR(nvs_get_str(nvs_handle, "wifi_pass", password, &pass_len), APP_ERR_NVS_READ_FAIL);
|
||
nvs_close(nvs_handle);
|
||
|
||
ESP_LOGI(TAG, "Connecting to WiFi: %s", ssid);
|
||
CHECK_ERROR(wifi_sta_init(ssid, password), APP_ERR_WIFI_CONNECT_FAIL);
|
||
|
||
mdns_init_service();
|
||
CHECK_ERROR(http_server_start(), APP_ERR_HTTP_SERVER_START_FAIL);
|
||
CHECK_ERROR(tasks_create(), APP_ERR_TASK_CREATE_FAIL);
|
||
|
||
return ERR_OK;
|
||
}
|
||
|
||
// ============================================================================
|
||
// Main application
|
||
// ============================================================================
|
||
|
||
void app_main(void) {
|
||
// Initialize NVS
|
||
if (nvs_init() != APP_ERR_OK) {
|
||
handle_error(APP_ERR_NVS_INIT_FAIL);
|
||
return;
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(50));
|
||
|
||
// Initialize GPIO
|
||
if (gpio_init() != APP_ERR_OK) {
|
||
handle_error(APP_ERR_GPIO_CONFIG_FAIL);
|
||
return;
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(100));
|
||
|
||
#if CONFIG_WIFI_AP
|
||
if (ap_normal_mode_init() != APP_ERR_OK) {
|
||
handle_error(APP_ERR_WIFI_INIT_FAIL);
|
||
}
|
||
#else // CONFIG_WIFI_AP
|
||
// Check reset button
|
||
if (gpio_get_level(RESET_BTN_GPIO) == 0) {
|
||
ESP_LOGI(TAG, "RESET button pressed, resetting settings...");
|
||
reset_settings();
|
||
indicate_led();
|
||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||
esp_restart();
|
||
return;
|
||
}
|
||
|
||
// Start appropriate mode
|
||
if (!is_wifi_config_exists()) {
|
||
if (setup_mode_init() != APP_ERR_OK) {
|
||
handle_error(APP_ERR_WIFI_INIT_FAIL);
|
||
}
|
||
} else {
|
||
if (normal_mode_init() != APP_ERR_OK) {
|
||
handle_error(APP_ERR_WIFI_CONNECT_FAIL);
|
||
}
|
||
}
|
||
#endif // CONFIG_WIFI_AP
|
||
vTaskDelete(NULL);
|
||
}
|