Initial Commit

This commit is contained in:
2025-07-17 17:25:38 -06:00
commit 27729f73d7
13 changed files with 1705 additions and 0 deletions

15
main/CMakeLists.txt Normal file
View 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
View File

@ -0,0 +1,6 @@
#include <stdio.h>
void app_main(void)
{
}

234
main/led_strip.c Normal file
View 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(&copy_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
View 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
View 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
View 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
View 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
View 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
View 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