Initial Commit
This commit is contained in:
15
main/CMakeLists.txt
Normal file
15
main/CMakeLists.txt
Normal file
@ -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
|
||||
)
|
||||
6
main/PlantWater.c
Normal file
6
main/PlantWater.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
|
||||
}
|
||||
234
main/led_strip.c
Normal file
234
main/led_strip.c
Normal file
@ -0,0 +1,234 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
21
main/led_strip.h
Normal file
21
main/led_strip.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef LED_STRIP_H
|
||||
#define LED_STRIP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
||||
175
main/main.c
Normal file
175
main/main.c
Normal file
@ -0,0 +1,175 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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"
|
||||
|
||||
static const char *TAG = "MAIN";
|
||||
|
||||
// WiFi credentials - Change these to your network
|
||||
#define WIFI_SSID "YOUR_SSID"
|
||||
#define WIFI_PASSWORD "YOUR_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'", WIFI_SSID);
|
||||
wifi_manager_set_credentials(WIFI_SSID, WIFI_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'", WIFI_SSID);
|
||||
wifi_manager_set_credentials(WIFI_SSID, WIFI_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);
|
||||
}
|
||||
}
|
||||
462
main/ota_server.c
Normal file
462
main/ota_server.c
Normal file
@ -0,0 +1,462 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#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 =
|
||||
"<!DOCTYPE html>"
|
||||
"<html>"
|
||||
"<head>"
|
||||
"<title>ESP32 OTA Update</title>"
|
||||
"<meta name='viewport' content='width=device-width, initial-scale=1'>"
|
||||
"<style>"
|
||||
"body { font-family: Arial, sans-serif; margin: 40px; background-color: #f0f0f0; }"
|
||||
".container { max-width: 600px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }"
|
||||
"h1 { color: #333; text-align: center; }"
|
||||
".info { background-color: #e7f3ff; border-left: 4px solid #2196F3; padding: 10px; margin-bottom: 20px; }"
|
||||
".upload-area { border: 2px dashed #ccc; border-radius: 5px; padding: 30px; text-align: center; margin: 20px 0; }"
|
||||
".upload-area.dragover { background-color: #e7f3ff; border-color: #2196F3; }"
|
||||
"input[type='file'] { display: none; }"
|
||||
".btn { background-color: #4CAF50; color: white; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }"
|
||||
".btn:hover { background-color: #45a049; }"
|
||||
".btn:disabled { background-color: #cccccc; cursor: not-allowed; }"
|
||||
".progress { width: 100%; background-color: #f0f0f0; border-radius: 4px; margin-top: 20px; display: none; }"
|
||||
".progress-bar { width: 0%; height: 30px; background-color: #4CAF50; border-radius: 4px; text-align: center; line-height: 30px; color: white; transition: width 0.3s; }"
|
||||
".status { margin-top: 20px; padding: 10px; border-radius: 4px; display: none; }"
|
||||
".status.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }"
|
||||
".status.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }"
|
||||
".file-info { margin-top: 10px; font-style: italic; color: #666; }"
|
||||
"</style>"
|
||||
"</head>"
|
||||
"<body>"
|
||||
"<div class='container'>"
|
||||
"<h1>ESP32 OTA Update</h1>"
|
||||
"<div class='info'>"
|
||||
"<strong>Current Version:</strong> <span id='version'>%s</span><br>"
|
||||
"<strong>Free Space:</strong> <span id='free-space'>%u KB</span>"
|
||||
"</div>"
|
||||
"<div class='upload-area' id='upload-area'>"
|
||||
"<p>Drag and drop firmware file here or click to select</p>"
|
||||
"<input type='file' id='file' accept='.bin'>"
|
||||
"<button class='btn' onclick='document.getElementById(\"file\").click()'>Select File</button>"
|
||||
"<div class='file-info' id='file-info'></div>"
|
||||
"</div>"
|
||||
"<button class='btn' id='upload-btn' onclick='uploadFirmware()' disabled>Upload Firmware</button>"
|
||||
"<div class='progress' id='progress'>"
|
||||
"<div class='progress-bar' id='progress-bar'>0%%</div>"
|
||||
"</div>"
|
||||
"<div class='status' id='status'></div>"
|
||||
"</div>"
|
||||
"<script>"
|
||||
"console.log('OTA page loaded');"
|
||||
"let selectedFile = null;"
|
||||
"const uploadArea = document.getElementById('upload-area');"
|
||||
"const fileInput = document.getElementById('file');"
|
||||
"const uploadBtn = document.getElementById('upload-btn');"
|
||||
"const progressDiv = document.getElementById('progress');"
|
||||
"const progressBar = document.getElementById('progress-bar');"
|
||||
"const statusDiv = document.getElementById('status');"
|
||||
"const fileInfo = document.getElementById('file-info');"
|
||||
""
|
||||
"// File input change handler"
|
||||
"fileInput.addEventListener('change', function(e) {"
|
||||
" console.log('File input changed', e.target.files);"
|
||||
" if (e.target.files.length > 0) {"
|
||||
" handleFile(e.target.files[0]);"
|
||||
" }"
|
||||
"});"
|
||||
""
|
||||
"// Drag and drop handlers"
|
||||
"uploadArea.addEventListener('dragover', function(e) {"
|
||||
" e.preventDefault();"
|
||||
" uploadArea.classList.add('dragover');"
|
||||
"});"
|
||||
""
|
||||
"uploadArea.addEventListener('dragleave', function() {"
|
||||
" uploadArea.classList.remove('dragover');"
|
||||
"});"
|
||||
""
|
||||
"uploadArea.addEventListener('drop', function(e) {"
|
||||
" e.preventDefault();"
|
||||
" uploadArea.classList.remove('dragover');"
|
||||
" const files = e.dataTransfer.files;"
|
||||
" console.log('Files dropped:', files);"
|
||||
" if (files.length > 0) {"
|
||||
" handleFile(files[0]);"
|
||||
" }"
|
||||
"});"
|
||||
""
|
||||
"function handleFile(file) {"
|
||||
" console.log('Handling file:', file.name, file.size);"
|
||||
" if (file.name.toLowerCase().endsWith('.bin')) {"
|
||||
" selectedFile = file;"
|
||||
" fileInfo.textContent = 'Selected: ' + file.name + ' (' + (file.size/1024).toFixed(2) + ' KB)';"
|
||||
" uploadBtn.disabled = false;"
|
||||
" uploadBtn.textContent = 'Upload ' + file.name;"
|
||||
" } else {"
|
||||
" alert('Please select a .bin file');"
|
||||
" selectedFile = null;"
|
||||
" fileInfo.textContent = '';"
|
||||
" uploadBtn.disabled = true;"
|
||||
" uploadBtn.textContent = 'Upload Firmware';"
|
||||
" }"
|
||||
"}"
|
||||
""
|
||||
"function showStatus(message, type) {"
|
||||
" statusDiv.textContent = message;"
|
||||
" statusDiv.className = 'status ' + type;"
|
||||
" statusDiv.style.display = 'block';"
|
||||
"}"
|
||||
""
|
||||
"function uploadFirmware() {"
|
||||
" if (!selectedFile) {"
|
||||
" console.error('No file selected');"
|
||||
" return;"
|
||||
" }"
|
||||
" "
|
||||
" console.log('Starting upload...');"
|
||||
" const xhr = new XMLHttpRequest();"
|
||||
" uploadBtn.disabled = true;"
|
||||
" progressDiv.style.display = 'block';"
|
||||
" statusDiv.style.display = 'none';"
|
||||
" "
|
||||
" xhr.upload.addEventListener('progress', function(e) {"
|
||||
" if (e.lengthComputable) {"
|
||||
" const percent = Math.round((e.loaded / e.total) * 100);"
|
||||
" progressBar.style.width = percent + '%%';"
|
||||
" progressBar.textContent = percent + '%%';"
|
||||
" console.log('Upload progress:', percent);"
|
||||
" }"
|
||||
" });"
|
||||
" "
|
||||
" xhr.addEventListener('load', function() {"
|
||||
" console.log('Upload complete, status:', xhr.status);"
|
||||
" if (xhr.status === 200) {"
|
||||
" showStatus('Firmware uploaded successfully! Device will restart...', 'success');"
|
||||
" setTimeout(function() { location.reload(); }, 5000);"
|
||||
" } else {"
|
||||
" showStatus('Upload failed: ' + xhr.responseText, 'error');"
|
||||
" uploadBtn.disabled = false;"
|
||||
" }"
|
||||
" });"
|
||||
" "
|
||||
" xhr.addEventListener('error', function() {"
|
||||
" console.error('Upload error');"
|
||||
" showStatus('Upload error occurred', 'error');"
|
||||
" uploadBtn.disabled = false;"
|
||||
" });"
|
||||
" "
|
||||
" xhr.open('POST', '/update');"
|
||||
" xhr.send(selectedFile);"
|
||||
"}"
|
||||
"</script>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
|
||||
// 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 = "<html><body><h1>ESP32 OTA Server Test</h1><p>Server is working!</p><a href='/'>Go to OTA Page</a></body></html>";
|
||||
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 =
|
||||
"<!DOCTYPE html>"
|
||||
"<html><head><title>ESP32 OTA Simple</title></head>"
|
||||
"<body>"
|
||||
"<h1>ESP32 OTA Update - Simple Version</h1>"
|
||||
"<p>Version: %s</p>"
|
||||
"<form>"
|
||||
"<input type='file' id='fw' accept='.bin'><br><br>"
|
||||
"<button type='button' onclick='doUpload()'>Upload</button>"
|
||||
"</form>"
|
||||
"<div id='msg'></div>"
|
||||
"<script>"
|
||||
"function doUpload(){"
|
||||
"var f=document.getElementById('fw').files[0];"
|
||||
"if(!f){alert('Select file first');return;}"
|
||||
"var x=new XMLHttpRequest();"
|
||||
"document.getElementById('msg').innerHTML='Uploading...';"
|
||||
"x.onload=function(){document.getElementById('msg').innerHTML=x.status==200?'Success!':'Error';};"
|
||||
"x.open('POST','/update');"
|
||||
"x.send(f);"
|
||||
"}"
|
||||
"</script>"
|
||||
"</body></html>";
|
||||
|
||||
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));
|
||||
}
|
||||
33
main/ota_server.h
Normal file
33
main/ota_server.h
Normal file
@ -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
|
||||
385
main/wifi_manager.c
Normal file
385
main/wifi_manager.c
Normal file
@ -0,0 +1,385 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
||||
36
main/wifi_manager.h
Normal file
36
main/wifi_manager.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef WIFI_MANAGER_H
|
||||
#define WIFI_MANAGER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#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
|
||||
Reference in New Issue
Block a user