From b1ba6821e1140d9af0ddb71fe7cf0082897b0a3f Mon Sep 17 00:00:00 2001 From: Stephen Minakian Date: Thu, 17 Jul 2025 17:47:13 -0600 Subject: [PATCH] Initial commit --- .gitignore | 113 +++++++++++ CMakeLists.txt | 6 + README.md | 232 ++++++++++++++++++++++ main/CMakeLists.txt | 15 ++ main/PlantWater.c | 6 + main/led_strip.c | 234 ++++++++++++++++++++++ main/led_strip.h | 21 ++ main/main.c | 180 +++++++++++++++++ main/ota_server.c | 462 ++++++++++++++++++++++++++++++++++++++++++++ main/ota_server.h | 33 ++++ main/wifi_manager.c | 385 ++++++++++++++++++++++++++++++++++++ main/wifi_manager.h | 36 ++++ partitions.csv | 8 + 13 files changed, 1731 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 main/CMakeLists.txt create mode 100644 main/PlantWater.c create mode 100644 main/led_strip.c create mode 100644 main/led_strip.h create mode 100644 main/main.c create mode 100644 main/ota_server.c create mode 100644 main/ota_server.h create mode 100644 main/wifi_manager.c create mode 100644 main/wifi_manager.h create mode 100644 partitions.csv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..900db30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# ESP-IDF build artifacts +build/ +sdkconfig +sdkconfig.old +sdkconfig.ci +sdkconfig.defaults.* + +# ESP-IDF managed components +managed_components/ +dependencies.lock + +# Python cache +*.pyc +__pycache__/ +.python_env/ + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store +.clang_complete +.gcc-flags.json + +# Debug files +*.log +*.tlog + +# Backup files +*.bak +*.orig + +# Flash download tool +flash_download_tool*/ +download.config + +# Component lock files +components/*/.component_hash + +# ESP-IDF tools +.espressif/ +tools/ + +# Local environment +.env +.env.local + +# Temporary files +*.tmp +*.temp + +# Binary files (but not in components) +*.bin +!components/**/*.bin + +# Object files +*.o +*.a +*.d + +# Monitor output +monitor.txt +monitor_*.txt + +# OTA artifacts +ota_data_initial.bin + +# Partition table +partition-table.bin +partition_table/*.bin + +# Bootloader +bootloader/*.bin +bootloader/bootloader.map + +# Generated documentation +docs/_build/ +doxygen/ + +# Unit test app builds +test_apps/**/build/ +test_apps/**/sdkconfig + +# Examples builds (if you have examples) +examples/**/build/ +examples/**/sdkconfig + +# CI artifacts +test_results/ +coverage_report/ +*.gcda +*.gcno + +# ESP32 core dump files +core_dump.elf +core_dump.bin + +# Custom certificates and keys (for production) +*.pem +*.key +*.crt +*.der +!server_certs/ # Allow example certs if needed + +# WiFi credentials file (if you create one) +wifi_credentials.h +credentials.h + +# Project-specific ignores +# Add any project-specific files to ignore here +main/Kconfig.projbuild \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..266082d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(PlantWater) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b310e0c --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ +# ESP32-S3 WiFi OTA Template + +A reusable template for ESP32-S3 projects featuring WiFi connectivity and Over-The-Air (OTA) firmware updates. This project is designed for the SparkFun ESP32-S3 Thing Plus but can be adapted for other ESP32-S3 boards. + +## Features + +- 🌐 **WiFi Manager** - Automatic connection with credential storage in NVS +- 🔄 **OTA Updates** - Web-based firmware updates with drag-and-drop interface +- 💡 **RGB LED Control** - WS2812 driver for visual feedback (GPIO 46) +- 🔧 **Docker-based Development** - No local ESP-IDF installation required +- 📦 **Modular Design** - Easy to extend with additional features + +## Hardware Requirements + +- SparkFun ESP32-S3 Thing Plus (or compatible ESP32-S3 board) +- USB-C cable (data capable, not charge-only) +- 2.4GHz WiFi network + +## Project Structure + +``` +. +├── main/ +│ ├── main.c # Main application +│ ├── wifi_manager.c/h # WiFi connection management +│ ├── ota_server.c/h # OTA update server +│ ├── led_strip.c/h # RGB LED driver +│ ├── CMakeLists.txt # Component configuration +| └── Kconfig.projbuild # To store wifi ssid and pass +├── partitions.csv # Flash partition table (4MB) +├── sdkconfig # Project configuration (auto-generated) +├── .gitignore # Git ignore rules +└── README.md # This file +``` + +## Quick Start + +### 1. Prerequisites + +- Docker installed on your system +- Git for version control +- Terminal/command line access + +### 2. Clone and Configure + +```bash +# Clone the repository (or create new project) +git clone +cd esp32-s3-template + +# Create the file main/Kconfig.projbuild +touch main/Kconfig.projbuild +``` +Fill it in with the following: +``` +menu "Wi-Fi Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "" + help + The SSID of the WiFi network. + +config WIFI_PASSWORD + string "WiFi Password" + default "" + help + The password of the WiFi network. + +endmenu +``` +Then fill in your SSID and Password + +A new project can be started with: +```bash +docker run --user $(id -u):$(id -g) --rm -v $PWD:/project -w /project -it espressif/idf:latest idf.py create-project +``` + +### 3. Build the Project + +```bash +docker run --user $(id -u):$(id -g) --rm -v $PWD:/project -w /project -it espressif/idf:latest idf.py build +``` + +### 4. Flash to Device + +```bash +docker run --privileged --rm -v $PWD:/project -w /project --device=/dev/ttyACM0 -it espressif/idf:latest idf.py flash -p /dev/ttyACM0 +``` + +> **Note**: Your device might appear as `/dev/ttyUSB0` or another port. Check with `ls /dev/tty*` after connecting. + +### 5. Monitor Serial Output + +```bash +docker run --privileged --rm -v $PWD:/project -w /project --device=/dev/ttyACM0 -it espressif/idf:latest idf.py monitor -p /dev/ttyACM0 +``` + +Press `Ctrl+]` to exit the monitor. + +## Using OTA Updates + +1. **Connect to WiFi** - The device will automatically connect using stored credentials +2. **Find IP Address** - Check serial monitor for "Got IP: xxx.xxx.xxx.xxx" +3. **Open Web Interface** - Navigate to `http:///` in your browser +4. **Upload Firmware**: + - Build new version: Update `APP_VERSION` in main.c + - Run build command again + - Upload `build/.bin` via web interface + - Device will automatically restart with new firmware + +### Testing OTA Updates + +Try these modifications to test OTA: + +```c +// Version 2.0.0 - Faster blinking +#define APP_VERSION "2.0.0" +#define BLINK_DELAY_MS 200 // Was 500 + +// Version 3.0.0 - Different colors +static const color_t colors[] = { + {255, 128, 0, "Orange"}, + {128, 0, 255, "Purple"}, + {255, 192, 203, "Pink"}, +}; +``` + +## API Usage + +### WiFi Manager + +```c +// Set new credentials +wifi_manager_set_credentials("NewSSID", "NewPassword"); + +// Check connection status +if (wifi_manager_is_connected()) { + // Connected +} + +// Clear stored credentials +wifi_manager_clear_credentials(); +``` + +### OTA Server + +```c +// Set version string +ota_server_set_version("2.0.0"); + +// Register progress callback +ota_server_register_progress_callback(my_progress_handler); +``` + +### LED Control + +```c +// Set LED color +led_strip_set_pixel(strip, 0, 255, 0, 0); // Red +led_strip_refresh(strip); + +// Turn off +led_strip_clear(strip); +``` + +## Troubleshooting + +### Build Issues +- Ensure Docker is running and you have internet connection +- Clean build: `idf.py fullclean` before building + +### Flash Issues +- Check USB cable is data-capable (not charge-only) +- Try different USB port +- Verify device path (`/dev/ttyACM0`, `/dev/ttyUSB0`, etc.) +- May need to add user to `dialout` group: `sudo usermod -a -G dialout $USER` + +### WiFi Connection Issues +- Verify 2.4GHz network (ESP32 doesn't support 5GHz) +- Check for special characters in SSID/password +- Look for trailing spaces in SSID +- Monitor serial output for specific error codes + +### OTA Issues +- Ensure device has sufficient free space (check web interface) +- Verify binary file size fits in OTA partition (1.25MB max) +- Check same network connectivity between computer and ESP32 + +## Memory Layout + +| Partition | Type | Size | Purpose | +|-----------|---------|---------|-------------------| +| nvs | data | 16KB | WiFi credentials | +| otadata | data | 8KB | OTA selection | +| phy_init | data | 4KB | PHY calibration | +| factory | app | 1.25MB | Factory firmware | +| ota_0 | app | 1.25MB | OTA partition 1 | +| ota_1 | app | 1.25MB | OTA partition 2 | + +## Security Considerations + +For production deployments: +- Add authentication to OTA web interface +- Use HTTPS for OTA updates +- Implement firmware signature verification +- Store WiFi credentials securely +- Consider encrypted flash storage + +## Extending the Template + +This template provides core functionality. Add your application-specific features: + +1. **Remove LED code** if not using RGB LED +2. **Add sensors** - I2C/SPI initialization in main.c +3. **Add MQTT** - Build on WiFi manager callbacks +4. **Add web API** - Extend OTA server with custom endpoints +5. **Add BLE** - ESP32-S3 supports dual-mode + +## License + +[Your License Here] + +## Acknowledgments + +- Built with ESP-IDF v6.0 +- Designed for SparkFun ESP32-S3 Thing Plus +- RGB LED driver uses RMT peripheral + +--- + +For more information about ESP-IDF: https://docs.espressif.com/ \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..3d0c579 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,15 @@ +idf_component_register( + SRCS + "main.c" + "wifi_manager.c" + "ota_server.c" + "led_strip.c" + INCLUDE_DIRS + "." + REQUIRES + nvs_flash + esp_wifi + esp_http_server + app_update + driver +) \ No newline at end of file diff --git a/main/PlantWater.c b/main/PlantWater.c new file mode 100644 index 0000000..7b66f33 --- /dev/null +++ b/main/PlantWater.c @@ -0,0 +1,6 @@ +#include + +void app_main(void) +{ + +} diff --git a/main/led_strip.c b/main/led_strip.c new file mode 100644 index 0000000..083c21b --- /dev/null +++ b/main/led_strip.c @@ -0,0 +1,234 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/rmt_tx.h" +#include "esp_log.h" +#include "led_strip.h" + +static const char *TAG = "LED_STRIP"; + +// WS2812 Timing (in nanoseconds) +#define WS2812_T0H_NS 350 +#define WS2812_T0L_NS 1000 +#define WS2812_T1H_NS 1000 +#define WS2812_T1L_NS 350 +#define WS2812_RESET_US 280 + +// LED strip structure +struct led_strip_t { + rmt_channel_handle_t channel; + rmt_encoder_handle_t encoder; + uint16_t led_count; + uint8_t *pixel_buf; +}; + +// RMT encoder for WS2812 +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} ws2812_encoder_t; + +static size_t ws2812_encode(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + ws2812_encoder_t *ws2812_encoder = __containerof(encoder, ws2812_encoder_t, base); + rmt_encode_state_t session_state = RMT_ENCODING_RESET; + rmt_encode_state_t state = RMT_ENCODING_RESET; + size_t encoded_symbols = 0; + + switch (ws2812_encoder->state) { + case 0: // Send RGB data + encoded_symbols += ws2812_encoder->bytes_encoder->encode(ws2812_encoder->bytes_encoder, channel, + primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + ws2812_encoder->state = 1; // Switch to reset code + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; + } + // Fall through + + case 1: // Send reset code + encoded_symbols += ws2812_encoder->copy_encoder->encode(ws2812_encoder->copy_encoder, channel, + &ws2812_encoder->reset_code, + sizeof(ws2812_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + ws2812_encoder->state = 0; // Back to RGB data + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; + } + } + +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t ws2812_del(rmt_encoder_t *encoder) +{ + ws2812_encoder_t *ws2812_encoder = __containerof(encoder, ws2812_encoder_t, base); + rmt_del_encoder(ws2812_encoder->bytes_encoder); + rmt_del_encoder(ws2812_encoder->copy_encoder); + free(ws2812_encoder); + return ESP_OK; +} + +static esp_err_t ws2812_reset(rmt_encoder_t *encoder) +{ + ws2812_encoder_t *ws2812_encoder = __containerof(encoder, ws2812_encoder_t, base); + rmt_encoder_reset(ws2812_encoder->bytes_encoder); + rmt_encoder_reset(ws2812_encoder->copy_encoder); + ws2812_encoder->state = 0; + return ESP_OK; +} + +static esp_err_t ws2812_encoder_new(rmt_encoder_handle_t *ret_encoder) +{ + ws2812_encoder_t *ws2812_encoder = calloc(1, sizeof(ws2812_encoder_t)); + if (!ws2812_encoder) { + ESP_LOGE(TAG, "No memory for WS2812 encoder"); + return ESP_ERR_NO_MEM; + } + + ws2812_encoder->base.encode = ws2812_encode; + ws2812_encoder->base.del = ws2812_del; + ws2812_encoder->base.reset = ws2812_reset; + + // Create byte encoder + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = WS2812_T0H_NS / 25, // 25ns resolution + .level1 = 0, + .duration1 = WS2812_T0L_NS / 25, + }, + .bit1 = { + .level0 = 1, + .duration0 = WS2812_T1H_NS / 25, + .level1 = 0, + .duration1 = WS2812_T1L_NS / 25, + }, + .flags.msb_first = 1, + }; + ESP_ERROR_CHECK(rmt_new_bytes_encoder(&bytes_encoder_config, &ws2812_encoder->bytes_encoder)); + + // Create copy encoder for reset code + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_ERROR_CHECK(rmt_new_copy_encoder(©_encoder_config, &ws2812_encoder->copy_encoder)); + + // Setup reset code + ws2812_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = WS2812_RESET_US * 40, // 25ns resolution, 40 = 1000/25 + .level1 = 0, + .duration1 = 0, + }; + + *ret_encoder = &ws2812_encoder->base; + return ESP_OK; +} + +led_strip_t* led_strip_init(uint8_t gpio, uint16_t led_count) +{ + led_strip_t *strip = calloc(1, sizeof(led_strip_t)); + if (!strip) { + ESP_LOGE(TAG, "No memory for LED strip"); + return NULL; + } + + strip->led_count = led_count; + strip->pixel_buf = calloc(led_count, 3); // 3 bytes per LED (RGB) + if (!strip->pixel_buf) { + ESP_LOGE(TAG, "No memory for pixel buffer"); + free(strip); + return NULL; + } + + // Configure RMT TX channel + rmt_tx_channel_config_t tx_config = { + .gpio_num = gpio, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 40000000, // 40MHz, 25ns resolution + .mem_block_symbols = 64, + .trans_queue_depth = 4, + }; + + if (rmt_new_tx_channel(&tx_config, &strip->channel) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create RMT TX channel"); + free(strip->pixel_buf); + free(strip); + return NULL; + } + + // Create WS2812 encoder + if (ws2812_encoder_new(&strip->encoder) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create WS2812 encoder"); + rmt_del_channel(strip->channel); + free(strip->pixel_buf); + free(strip); + return NULL; + } + + // Enable RMT channel + rmt_enable(strip->channel); + + ESP_LOGI(TAG, "LED strip initialized on GPIO %d with %d LEDs", gpio, led_count); + return strip; +} + +esp_err_t led_strip_set_pixel(led_strip_t *strip, uint16_t index, uint8_t red, uint8_t green, uint8_t blue) +{ + if (!strip || index >= strip->led_count) { + return ESP_ERR_INVALID_ARG; + } + + // WS2812 expects GRB order + strip->pixel_buf[index * 3 + 0] = green; + strip->pixel_buf[index * 3 + 1] = red; + strip->pixel_buf[index * 3 + 2] = blue; + + return ESP_OK; +} + +esp_err_t led_strip_refresh(led_strip_t *strip) +{ + if (!strip) { + return ESP_ERR_INVALID_ARG; + } + + rmt_transmit_config_t tx_config = { + .loop_count = 0, + }; + + return rmt_transmit(strip->channel, strip->encoder, strip->pixel_buf, + strip->led_count * 3, &tx_config); +} + +esp_err_t led_strip_clear(led_strip_t *strip) +{ + if (!strip) { + return ESP_ERR_INVALID_ARG; + } + + memset(strip->pixel_buf, 0, strip->led_count * 3); + return led_strip_refresh(strip); +} + +void led_strip_deinit(led_strip_t *strip) +{ + if (strip) { + rmt_disable(strip->channel); + rmt_del_channel(strip->channel); + rmt_del_encoder(strip->encoder); + free(strip->pixel_buf); + free(strip); + } +} \ No newline at end of file diff --git a/main/led_strip.h b/main/led_strip.h new file mode 100644 index 0000000..a3419f2 --- /dev/null +++ b/main/led_strip.h @@ -0,0 +1,21 @@ +#ifndef LED_STRIP_H +#define LED_STRIP_H + +#include +#include "esp_err.h" + +// LED configuration for SparkFun ESP32-S3 Thing Plus +#define LED_STRIP_GPIO 46 +#define LED_STRIP_LED_COUNT 1 + +// Simple LED strip driver +typedef struct led_strip_t led_strip_t; + +// LED strip functions +led_strip_t* led_strip_init(uint8_t gpio, uint16_t led_count); +esp_err_t led_strip_set_pixel(led_strip_t *strip, uint16_t index, uint8_t red, uint8_t green, uint8_t blue); +esp_err_t led_strip_refresh(led_strip_t *strip); +esp_err_t led_strip_clear(led_strip_t *strip); +void led_strip_deinit(led_strip_t *strip); + +#endif // LED_STRIP_H \ No newline at end of file diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..ee9537a --- /dev/null +++ b/main/main.c @@ -0,0 +1,180 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "esp_chip_info.h" +#include "wifi_manager.h" +#include "ota_server.h" +#include "led_strip.h" + +#include "sdkconfig.h" + +static const char *TAG = "MAIN"; + +// WiFi credentials - Change these to your network +// #define WIFI_SSID "YOUR_SSID" +// #define WIFI_PASSWORD "YOUR_PASSWORD" + +const char *ssid = CONFIG_WIFI_SSID; +const char *password = CONFIG_WIFI_PASSWORD; + +// Application version +#define APP_VERSION "1.0.0" + +// LED colors and timing +typedef struct { + uint8_t r, g, b; + const char *name; +} color_t; + +static const color_t colors[] = { + {255, 0, 0, "Red"}, + {0, 255, 0, "Green"}, + {0, 0, 255, "Blue"}, + {255, 255, 0, "Yellow"}, + {255, 0, 255, "Magenta"}, + {0, 255, 255, "Cyan"}, + {255, 255, 255, "White"}, +}; + +#define NUM_COLORS (sizeof(colors) / sizeof(colors[0])) +#define BLINK_DELAY_MS 500 + +// WiFi event handler +static void wifi_event_handler(wifi_state_t state) +{ + switch (state) { + case WIFI_STATE_CONNECTED: + ESP_LOGI(TAG, "WiFi connected - starting OTA server"); + ota_server_start(); + break; + + case WIFI_STATE_DISCONNECTED: + ESP_LOGW(TAG, "WiFi disconnected - stopping OTA server"); + ota_server_stop(); + break; + + case WIFI_STATE_ERROR: + ESP_LOGE(TAG, "WiFi connection failed"); + break; + + default: + break; + } +} + +// OTA progress handler +static void ota_progress_handler(int percent) +{ + ESP_LOGI(TAG, "OTA Progress: %d%%", percent); +} + +void print_chip_info(void) +{ + esp_chip_info_t chip_info; + + esp_chip_info(&chip_info); + ESP_LOGI(TAG, "This is %s chip with %d CPU core(s), WiFi%s%s, ", + CONFIG_IDF_TARGET, + chip_info.cores, + (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", + (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); + + ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision); + + ESP_LOGI(TAG, "Minimum free heap size: %d bytes", esp_get_minimum_free_heap_size()); +} + +void app_main(void) +{ + ESP_LOGI(TAG, "ESP32-S3 Thing Plus RGB Blinker v%s", APP_VERSION); + + // Print chip information + print_chip_info(); + + // Initialize RGB LED + led_strip_t *led_strip = led_strip_init(LED_STRIP_GPIO, LED_STRIP_LED_COUNT); + if (!led_strip) { + ESP_LOGE(TAG, "Failed to initialize LED strip"); + } else { + ESP_LOGI(TAG, "RGB LED initialized on GPIO %d", LED_STRIP_GPIO); + // Turn LED off initially + led_strip_clear(led_strip); + } + + // Initialize WiFi manager + ESP_ERROR_CHECK(wifi_manager_init()); + wifi_manager_register_callback(wifi_event_handler); + + // Initialize OTA server + ESP_ERROR_CHECK(ota_server_init()); + ota_server_set_version(APP_VERSION); + ota_server_register_progress_callback(ota_progress_handler); + + // Check if we have stored WiFi credentials + char stored_ssid[33] = {0}; + char stored_pass[65] = {0}; + + // Force update with new credentials (remove this after first successful connection) + ESP_LOGI(TAG, "Updating WiFi credentials - SSID: '%s'", ssid); + wifi_manager_set_credentials(ssid, password); + + /* + // Normal flow - only update if no credentials stored + if (wifi_manager_get_credentials(stored_ssid, sizeof(stored_ssid), + stored_pass, sizeof(stored_pass)) != ESP_OK) { + ESP_LOGI(TAG, "No stored WiFi credentials, saving default ones"); + ESP_LOGI(TAG, "Setting SSID: '%s'", ssid); + wifi_manager_set_credentials(ssid, password); + } else { + ESP_LOGI(TAG, "Found stored credentials - SSID: '%s'", stored_ssid); + } + */ + + // Start WiFi connection + esp_err_t ret = wifi_manager_start(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start WiFi manager"); + } + + // Main loop with RGB LED blinking + int color_index = 0; + bool led_on = false; + + while (1) { + // Blink the RGB LED through different colors + if (led_strip) { + if (led_on) { + // Turn LED on with current color + led_strip_set_pixel(led_strip, 0, + colors[color_index].r, + colors[color_index].g, + colors[color_index].b); + led_strip_refresh(led_strip); + ESP_LOGI(TAG, "LED ON - Color: %s", colors[color_index].name); + } else { + // Turn LED off + led_strip_clear(led_strip); + ESP_LOGI(TAG, "LED OFF"); + + // Move to next color when turning off + color_index = (color_index + 1) % NUM_COLORS; + } + + led_on = !led_on; + } + + // Print heap info every 10 blinks (5 seconds) + static int blink_count = 0; + if (++blink_count >= 10) { + ESP_LOGI(TAG, "Free heap: %d bytes, WiFi: %s", + esp_get_free_heap_size(), + wifi_manager_is_connected() ? "Connected" : "Disconnected"); + blink_count = 0; + } + + vTaskDelay(BLINK_DELAY_MS / portTICK_PERIOD_MS); + } +} \ No newline at end of file diff --git a/main/ota_server.c b/main/ota_server.c new file mode 100644 index 0000000..5515d7a --- /dev/null +++ b/main/ota_server.c @@ -0,0 +1,462 @@ +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_http_server.h" +#include "esp_flash_partitions.h" +#include "esp_partition.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "ota_server.h" + +// Define MIN macro if not already defined +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static const char *TAG = "OTA_SERVER"; + +// HTML page for OTA update +static const char *ota_html = +"" +"" +"" +"ESP32 OTA Update" +"" +"" +"" +"" +"
" +"

ESP32 OTA Update

" +"
" +"Current Version: %s
" +"Free Space: %u KB" +"
" +"
" +"

Drag and drop firmware file here or click to select

" +"" +"" +"
" +"
" +"" +"
" +"
0%%
" +"
" +"
" +"
" +"" +"" +""; + +// Server handle +static httpd_handle_t s_server = NULL; + +// OTA state +static ota_state_t s_ota_state = OTA_STATE_IDLE; + +// Progress callback +static ota_progress_callback_t s_progress_callback = NULL; + +// Version string +static char s_version[32] = "1.0.0"; + +// OTA handle +static esp_ota_handle_t s_ota_handle = 0; +static const esp_partition_t *s_update_partition = NULL; +static int s_binary_file_length = 0; +static bool s_ota_ongoing = false; + +static esp_err_t index_handler(httpd_req_t *req) +{ + const esp_partition_t *running = esp_ota_get_running_partition(); + uint32_t free_space = 0; + + // Calculate free OTA space + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); + while (it != NULL) { + const esp_partition_t *p = esp_partition_get(it); + if (p != running && p->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_0 && + p->subtype <= ESP_PARTITION_SUBTYPE_APP_OTA_15) { + free_space = p->size; + break; + } + it = esp_partition_next(it); + } + esp_partition_iterator_release(it); + + // Allocate buffer for response + size_t response_size = strlen(ota_html) + 64; + char *response = malloc(response_size); + if (!response) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed"); + return ESP_FAIL; + } + + snprintf(response, response_size, ota_html, s_version, free_space / 1024); + + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, response, strlen(response)); + + free(response); + return ESP_OK; +} + +static esp_err_t update_handler(httpd_req_t *req) +{ + char buf[OTA_BUFFER_SIZE]; + int received; + int remaining = req->content_len; + int total_received = 0; + esp_err_t err = ESP_OK; + + if (s_ota_ongoing) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA already in progress"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Starting OTA update, file size: %d", req->content_len); + + s_update_partition = esp_ota_get_next_update_partition(NULL); + if (s_update_partition == NULL) { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "No OTA partition available"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", + s_update_partition->subtype, s_update_partition->address); + + err = esp_ota_begin(s_update_partition, req->content_len, &s_ota_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to begin OTA"); + return ESP_FAIL; + } + + s_ota_ongoing = true; + s_ota_state = OTA_STATE_UPDATING; + s_binary_file_length = req->content_len; + + while (remaining > 0) { + received = httpd_req_recv(req, buf, MIN(remaining, OTA_BUFFER_SIZE)); + if (received <= 0) { + if (received == HTTPD_SOCK_ERR_TIMEOUT) { + continue; + } + ESP_LOGE(TAG, "File reception failed"); + esp_ota_abort(s_ota_handle); + s_ota_ongoing = false; + s_ota_state = OTA_STATE_ERROR; + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); + return ESP_FAIL; + } + + err = esp_ota_write(s_ota_handle, (const void *)buf, received); + if (err != ESP_OK) { + ESP_LOGE(TAG, "OTA write failed: %s", esp_err_to_name(err)); + esp_ota_abort(s_ota_handle); + s_ota_ongoing = false; + s_ota_state = OTA_STATE_ERROR; + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write OTA data"); + return ESP_FAIL; + } + + total_received += received; + remaining -= received; + + // Report progress + if (s_progress_callback && s_binary_file_length > 0) { + int percent = (total_received * 100) / s_binary_file_length; + s_progress_callback(percent); + } + + ESP_LOGD(TAG, "Written %d bytes, %d remaining", total_received, remaining); + } + + err = esp_ota_end(s_ota_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err)); + s_ota_ongoing = false; + s_ota_state = OTA_STATE_ERROR; + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "OTA end failed"); + return ESP_FAIL; + } + + err = esp_ota_set_boot_partition(s_update_partition); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err)); + s_ota_ongoing = false; + s_ota_state = OTA_STATE_ERROR; + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to set boot partition"); + return ESP_FAIL; + } + + s_ota_ongoing = false; + s_ota_state = OTA_STATE_SUCCESS; + + httpd_resp_sendstr(req, "OTA update successful. Restarting..."); + + ESP_LOGI(TAG, "OTA update successful. Restarting in 1 second..."); + vTaskDelay(1000 / portTICK_PERIOD_MS); + esp_restart(); + + return ESP_OK; +} + +esp_err_t ota_server_init(void) +{ + // Check and print current partition info + const esp_partition_t *running = esp_ota_get_running_partition(); + ESP_LOGI(TAG, "Running partition: %s", running->label); + + // Mark current app as valid (for rollback support) + esp_ota_mark_app_valid_cancel_rollback(); + + return ESP_OK; +} + +static esp_err_t test_handler(httpd_req_t *req) +{ + const char* resp = "

ESP32 OTA Server Test

Server is working!

Go to OTA Page"; + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, resp, strlen(resp)); + return ESP_OK; +} + +static esp_err_t simple_handler(httpd_req_t *req) +{ + const char* simple_html = + "" + "ESP32 OTA Simple" + "" + "

ESP32 OTA Update - Simple Version

" + "

Version: %s

" + "
" + "

" + "" + "
" + "
" + "" + ""; + + char response[1024]; + snprintf(response, sizeof(response), simple_html, s_version); + + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, response, strlen(response)); + return ESP_OK; +} + +esp_err_t ota_server_start(void) +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = OTA_SERVER_PORT; + config.recv_wait_timeout = OTA_RECV_TIMEOUT; + config.max_uri_handlers = 8; // Increase handler limit + + ESP_LOGI(TAG, "Starting OTA server on port %d", config.server_port); + + if (httpd_start(&s_server, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start HTTP server"); + return ESP_FAIL; + } + + // Register URI handlers + httpd_uri_t index_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = index_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(s_server, &index_uri); + + httpd_uri_t update_uri = { + .uri = "/update", + .method = HTTP_POST, + .handler = update_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(s_server, &update_uri); + + httpd_uri_t test_uri = { + .uri = "/test", + .method = HTTP_GET, + .handler = test_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(s_server, &test_uri); + + httpd_uri_t simple_uri = { + .uri = "/simple", + .method = HTTP_GET, + .handler = simple_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(s_server, &simple_uri); + + ESP_LOGI(TAG, "OTA server started"); + return ESP_OK; +} + +esp_err_t ota_server_stop(void) +{ + if (s_server) { + httpd_stop(s_server); + s_server = NULL; + ESP_LOGI(TAG, "OTA server stopped"); + } + return ESP_OK; +} + +ota_state_t ota_server_get_state(void) +{ + return s_ota_state; +} + +void ota_server_register_progress_callback(ota_progress_callback_t callback) +{ + s_progress_callback = callback; +} + +const char* ota_server_get_version(void) +{ + return s_version; +} + +void ota_server_set_version(const char* version) +{ + strlcpy(s_version, version, sizeof(s_version)); +} \ No newline at end of file diff --git a/main/ota_server.h b/main/ota_server.h new file mode 100644 index 0000000..c29dcac --- /dev/null +++ b/main/ota_server.h @@ -0,0 +1,33 @@ +#ifndef OTA_SERVER_H +#define OTA_SERVER_H + +#include "esp_err.h" + +// OTA server configuration +#define OTA_SERVER_PORT 80 +#define OTA_BUFFER_SIZE 1024 +#define OTA_RECV_TIMEOUT 5000 // 5 seconds + +// OTA update states +typedef enum { + OTA_STATE_IDLE, + OTA_STATE_UPDATING, + OTA_STATE_SUCCESS, + OTA_STATE_ERROR +} ota_state_t; + +// OTA progress callback +typedef void (*ota_progress_callback_t)(int percent); + +// OTA server functions +esp_err_t ota_server_init(void); +esp_err_t ota_server_start(void); +esp_err_t ota_server_stop(void); +ota_state_t ota_server_get_state(void); +void ota_server_register_progress_callback(ota_progress_callback_t callback); + +// Version management +const char* ota_server_get_version(void); +void ota_server_set_version(const char* version); + +#endif // OTA_SERVER_H \ No newline at end of file diff --git a/main/wifi_manager.c b/main/wifi_manager.c new file mode 100644 index 0000000..63f0f98 --- /dev/null +++ b/main/wifi_manager.c @@ -0,0 +1,385 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "wifi_manager.h" + +static const char *TAG = "WIFI_MANAGER"; + +// FreeRTOS event group for WiFi events +static EventGroupHandle_t s_wifi_event_group; + +// Current WiFi state +static wifi_state_t s_wifi_state = WIFI_STATE_DISCONNECTED; + +// Retry counter +static int s_retry_num = 0; + +// Event callback +static wifi_event_callback_t s_event_callback = NULL; + +// Scan mode flag +static bool s_scan_mode = false; + +// NVS namespace +#define NVS_NAMESPACE "wifi_creds" + +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) { + switch (event_id) { + case WIFI_EVENT_STA_START: + if (!s_scan_mode) { // Only connect if not in scan mode + esp_wifi_connect(); + s_wifi_state = WIFI_STATE_CONNECTING; + if (s_event_callback) { + s_event_callback(s_wifi_state); + } + } + break; + + case WIFI_EVENT_STA_DISCONNECTED: + if (event_data) { + wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data; + ESP_LOGE(TAG, "Disconnect reason : %d", disconnected->reason); + switch(disconnected->reason) { + case WIFI_REASON_AUTH_EXPIRE: + ESP_LOGE(TAG, "Authentication expired"); + break; + case WIFI_REASON_AUTH_FAIL: + ESP_LOGE(TAG, "Authentication failed"); + break; + case WIFI_REASON_NO_AP_FOUND: + ESP_LOGE(TAG, "AP not found - check SSID"); + break; + case WIFI_REASON_HANDSHAKE_TIMEOUT: + ESP_LOGE(TAG, "Handshake timeout - check password"); + break; + default: + ESP_LOGE(TAG, "Other reason: %d", disconnected->reason); + } + } + if (s_retry_num < WIFI_MAXIMUM_RETRY) { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "Retry connecting to AP (%d/%d)", s_retry_num, WIFI_MAXIMUM_RETRY); + } else { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + s_wifi_state = WIFI_STATE_ERROR; + ESP_LOGI(TAG, "Failed to connect to AP"); + } + if (s_event_callback) { + s_event_callback(s_wifi_state); + } + break; + + default: + break; + } + } 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)); + s_retry_num = 0; + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + s_wifi_state = WIFI_STATE_CONNECTED; + if (s_event_callback) { + s_event_callback(s_wifi_state); + } + } +} + +esp_err_t wifi_manager_init(void) +{ + esp_err_t ret = ESP_OK; + + // Initialize NVS + 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); + + // Create event group + s_wifi_event_group = xEventGroupCreate(); + if (s_wifi_event_group == NULL) { + ESP_LOGE(TAG, "Failed to create event group"); + return ESP_FAIL; + } + + // Initialize TCP/IP stack + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + // Initialize WiFi with default config + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + // Register event handlers + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &wifi_event_handler, + NULL, + NULL)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &wifi_event_handler, + NULL, + NULL)); + + // Set WiFi mode to station + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + + // Disable power save mode to ensure WiFi works properly + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); + + // Set WiFi country (important for proper channel scanning) + wifi_country_t wifi_country = { + .cc = "US", + .schan = 1, + .nchan = 11, + .max_tx_power = 84, + .policy = WIFI_COUNTRY_POLICY_AUTO, + }; + ESP_ERROR_CHECK(esp_wifi_set_country(&wifi_country)); + + ESP_LOGI(TAG, "WiFi manager initialized"); + return ESP_OK; +} + +esp_err_t wifi_manager_start(void) +{ + char ssid[33] = {0}; + char password[65] = {0}; + + // Try to get credentials from NVS + if (wifi_manager_get_credentials(ssid, sizeof(ssid), password, sizeof(password)) == ESP_OK) { + ESP_LOGI(TAG, "Starting WiFi with stored credentials"); + ESP_LOGI(TAG, "Attempting to connect to SSID: '%s'", ssid); + ESP_LOGI(TAG, "SSID length: %d", strlen(ssid)); + + // Set scan mode to prevent auto-connect + s_scan_mode = true; + + // Start WiFi for scanning + ESP_ERROR_CHECK(esp_wifi_start()); + + // Wait a bit for WiFi to initialize + vTaskDelay(100 / portTICK_PERIOD_MS); + + // Scan for available networks + ESP_LOGI(TAG, "Starting WiFi scan..."); + 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, + }, + }, + }; + + esp_err_t scan_ret = esp_wifi_scan_start(&scan_config, true); + if (scan_ret != ESP_OK) { + ESP_LOGE(TAG, "WiFi scan failed with error: %s", esp_err_to_name(scan_ret)); + } else { + uint16_t ap_count = 0; + esp_wifi_scan_get_ap_num(&ap_count); + ESP_LOGI(TAG, "WiFi scan done! Found %d access points", ap_count); + + if (ap_count > 0) { + wifi_ap_record_t *ap_records = malloc(sizeof(wifi_ap_record_t) * ap_count); + if (ap_records) { + esp_wifi_scan_get_ap_records(&ap_count, ap_records); + bool target_found = false; + for (int i = 0; i < ap_count && i < 20; i++) { + ESP_LOGI(TAG, " %d. SSID: '%s', RSSI: %d, Channel: %d, Auth: %d", + i + 1, + ap_records[i].ssid, + ap_records[i].rssi, + ap_records[i].primary, + ap_records[i].authmode); + + // Check if our target SSID matches + if (strcmp((char*)ap_records[i].ssid, ssid) == 0) { + ESP_LOGI(TAG, " *** Target SSID found! Channel: %d, Auth: %d ***", + ap_records[i].primary, ap_records[i].authmode); + target_found = true; + } + } + + if (!target_found) { + ESP_LOGW(TAG, "Target SSID '%s' not found in scan results!", ssid); + } + + free(ap_records); + } + } else { + ESP_LOGW(TAG, "No access points found. Possible issues:"); + ESP_LOGW(TAG, " - WiFi antenna not connected"); + ESP_LOGW(TAG, " - Wrong country code"); + ESP_LOGW(TAG, " - Hardware issue"); + ESP_LOGW(TAG, " - All APs on 5GHz only"); + } + } + + // Clear scan mode + s_scan_mode = false; + + // Configure for connection + wifi_config_t wifi_config = { + .sta = { + .threshold.authmode = WIFI_AUTH_OPEN, // Accept any auth + .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, + }, + }; + + strlcpy((char*)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + strlcpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password)); + + ESP_LOGI(TAG, "Setting WiFi configuration..."); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + + // Now trigger connection + ESP_LOGI(TAG, "Connecting to WiFi..."); + esp_wifi_connect(); + + // Wait for connection + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, + pdFALSE, + 30000 / portTICK_PERIOD_MS); // 30 second timeout + + if (bits & WIFI_CONNECTED_BIT) { + ESP_LOGI(TAG, "Connected to AP"); + return ESP_OK; + } else if (bits & WIFI_FAIL_BIT) { + ESP_LOGE(TAG, "Failed to connect to AP after retries"); + return ESP_FAIL; + } else { + ESP_LOGE(TAG, "WiFi connection timeout"); + return ESP_ERR_TIMEOUT; + } + } else { + ESP_LOGE(TAG, "No WiFi credentials found"); + return ESP_ERR_NOT_FOUND; + } +} + +esp_err_t wifi_manager_stop(void) +{ + esp_err_t ret = esp_wifi_stop(); + if (ret == ESP_OK) { + s_wifi_state = WIFI_STATE_DISCONNECTED; + s_retry_num = 0; + xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT); + ESP_LOGI(TAG, "WiFi stopped"); + } + return ret; +} + +bool wifi_manager_is_connected(void) +{ + return s_wifi_state == WIFI_STATE_CONNECTED; +} + +wifi_state_t wifi_manager_get_state(void) +{ + return s_wifi_state; +} + +void wifi_manager_register_callback(wifi_event_callback_t callback) +{ + s_event_callback = callback; +} + +esp_err_t wifi_manager_set_credentials(const char* ssid, const char* password) +{ + nvs_handle_t nvs_handle; + esp_err_t ret; + + ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to open NVS namespace"); + return ret; + } + + ret = nvs_set_str(nvs_handle, "ssid", ssid); + if (ret != ESP_OK) { + nvs_close(nvs_handle); + return ret; + } + + ret = nvs_set_str(nvs_handle, "password", password); + if (ret != ESP_OK) { + nvs_close(nvs_handle); + return ret; + } + + ret = nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + if (ret == ESP_OK) { + ESP_LOGI(TAG, "WiFi credentials saved"); + } + + return ret; +} + +esp_err_t wifi_manager_get_credentials(char* ssid, size_t ssid_len, char* password, size_t pass_len) +{ + nvs_handle_t nvs_handle; + esp_err_t ret; + + ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + if (ret != ESP_OK) { + return ret; + } + + ret = nvs_get_str(nvs_handle, "ssid", ssid, &ssid_len); + if (ret != ESP_OK) { + nvs_close(nvs_handle); + return ret; + } + + ret = nvs_get_str(nvs_handle, "password", password, &pass_len); + nvs_close(nvs_handle); + + return ret; +} + +esp_err_t wifi_manager_clear_credentials(void) +{ + nvs_handle_t nvs_handle; + esp_err_t ret; + + ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (ret != ESP_OK) { + return ret; + } + + nvs_erase_key(nvs_handle, "ssid"); + nvs_erase_key(nvs_handle, "password"); + ret = nvs_commit(nvs_handle); + nvs_close(nvs_handle); + + if (ret == ESP_OK) { + ESP_LOGI(TAG, "WiFi credentials cleared"); + } + + return ret; +} \ No newline at end of file diff --git a/main/wifi_manager.h b/main/wifi_manager.h new file mode 100644 index 0000000..7a0eba9 --- /dev/null +++ b/main/wifi_manager.h @@ -0,0 +1,36 @@ +#ifndef WIFI_MANAGER_H +#define WIFI_MANAGER_H + +#include +#include "esp_err.h" + +// WiFi configuration +#define WIFI_MAXIMUM_RETRY 10 +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 + +// WiFi manager states +typedef enum { + WIFI_STATE_DISCONNECTED, + WIFI_STATE_CONNECTING, + WIFI_STATE_CONNECTED, + WIFI_STATE_ERROR +} wifi_state_t; + +// WiFi event callback +typedef void (*wifi_event_callback_t)(wifi_state_t state); + +// WiFi manager functions +esp_err_t wifi_manager_init(void); +esp_err_t wifi_manager_start(void); +esp_err_t wifi_manager_stop(void); +bool wifi_manager_is_connected(void); +wifi_state_t wifi_manager_get_state(void); +void wifi_manager_register_callback(wifi_event_callback_t callback); + +// Configuration functions +esp_err_t wifi_manager_set_credentials(const char* ssid, const char* password); +esp_err_t wifi_manager_get_credentials(char* ssid, size_t ssid_len, char* password, size_t pass_len); +esp_err_t wifi_manager_clear_credentials(void); + +#endif // WIFI_MANAGER_H \ No newline at end of file diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 0000000..f9470b0 --- /dev/null +++ b/partitions.csv @@ -0,0 +1,8 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x140000, +ota_0, app, ota_0, 0x150000,0x140000, +ota_1, app, ota_1, 0x290000,0x140000, \ No newline at end of file