1211 lines
40 KiB
C
1211 lines
40 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"
|
|
|
|
// ============================================================================
|
|
// 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 ADC_CHAN0 ADC_CHANNEL_4
|
|
#define ADC_CHAN1 ADC_CHANNEL_5
|
|
#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 SENSOR_ADC_CHAN 0
|
|
#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", false},
|
|
{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, ADC_CHAN0, &config), APP_ERR_ADC_INIT_FAIL);
|
|
CHECK_ERROR(adc_oneshot_config_channel(adc_handle, ADC_CHAN1, &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(uint8_t channel) {
|
|
adc_channel_t adc_channel;
|
|
|
|
if (channel == 0) {
|
|
adc_channel = ADC_CHAN0;
|
|
} else if (channel == 1) {
|
|
adc_channel = ADC_CHAN1;
|
|
} else {
|
|
ESP_LOGE(TAG, "Invalid ADC channel: %d", channel);
|
|
return -1;
|
|
}
|
|
|
|
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(uint8_t channel) {
|
|
int raw_value = adc_read_raw(channel);
|
|
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(SENSOR_ADC_CHAN);
|
|
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 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 (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);
|
|
handle_error(APP_ERR_WIFI_CONNECT_FAIL);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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_status(req, "204 No Content");
|
|
httpd_resp_send(req, NULL, 0);
|
|
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) {
|
|
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_send_err(req, HTTPD_400_BAD_REQUEST, "Missing or invalid 'ssid' or 'password' parameters");
|
|
return ESP_FAIL;
|
|
}
|
|
free(content);
|
|
|
|
send_json_response(req, "{\"success\":true}");
|
|
save_wifi_config(ssid, password);
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
esp_restart();
|
|
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.ico", .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 root = {
|
|
.uri = "/", .method = HTTP_GET, .handler = setup_get_handler
|
|
};
|
|
CHECK_ERROR(httpd_register_uri_handler(server, &root), APP_ERR_HTTP_SERVER_START_FAIL);
|
|
|
|
httpd_uri_t favicon = {
|
|
.uri = "/favicon.ico", .method = HTTP_GET, .handler = favicon_get_handler
|
|
};
|
|
CHECK_ERROR(httpd_register_uri_handler(server, &favicon), APP_ERR_HTTP_SERVER_START_FAIL);
|
|
|
|
httpd_uri_t settings = {
|
|
.uri = "/settings", .method = HTTP_POST, .handler = setup_set_settings_handler
|
|
};
|
|
CHECK_ERROR(httpd_register_uri_handler(server, &settings), APP_ERR_HTTP_SERVER_START_FAIL);
|
|
|
|
httpd_uri_t wifi_list = {
|
|
.uri = "/wifi_list", .method = HTTP_GET, .handler = setup_get_wifi_list_handler
|
|
};
|
|
CHECK_ERROR(httpd_register_uri_handler(server, &wifi_list), 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);
|
|
}
|